123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554 |
- #!/usr/bin/perl
- # Ezkey.pm
- # Interface to the Alpha Sign Communications Protocol, EZ KEY II
- # See http://www.ams-i.com/Pages/97088061.htm
- #
- # The purpose of this interface is to objectify and simplify communication
- # with LED signs like the BetaBrite: http://betabrite.com/
- #
- # @author Matt Sparks
- package Ezkey;
- use strict;
- use Data::Dumper;
- use IO::Handle;
- use Date::Format;
- use POSIX qw/floor/;
- require Exporter;
- our @ISA = qw/Exporter/;
- our @EXPORT = qw/%modes %graphics %positions/;
- # Display Modes (p89)
- our %modes = (
- "rotate" => "a",
- "hold" => "b",
- "flash" => "c",
- "roll_up" => "e",
- "roll_down" => "f",
- "roll_left" => "g",
- "roll_right" => "h",
- "wipe_up" => "i",
- "wipe_down" => "j",
- "wipe_left" => "k",
- "wipe_right" => "l",
- "scroll" => "m",
- "automode" => "o",
- "roll_in" => "p",
- "roll_out" => "q",
- "wipe_in" => "r",
- "wipe_out" => "s",
- "compressed_rotate" => "t", # only available on certain sign models
- "explode" => "u", # alpha 3.0 protocol
- "clock" => "v", # alpha 3.0 protocol
- # Special Modes
- "twinkle" => "n0",
- "sparkle" => "n1",
- "snow" => "n2",
- "interlock" => "n3",
- "switch" => "n4",
- "slide" => "n5", # only Betabrite 1036 (same as CYCLE_COLORS?)
- "spray" => "n6",
- "starburst" => "n7",
- "welcome" => "n8",
- "slot_machine" => "n9",
- "news_flash" => "nA", # only Betabrite 1036
- "trumpet_animation" => "nb", # only betabrite 1036
- "cycle_colors" => "nC", # only AlphaEclipse 3600
- # Special Graphics (these display before the message)
- "thank_you" => "nS",
- "no_smoking" => "nU",
- "dont_drive_drink" => "nV",
- "running_animal" => "nW",
- "fish_animation" => "nW",
- "fireworks" => "nX",
- "turbo_car" => "nY",
- "balloon_animation" => "nY",
- "cherry_bomb" => "nZ",
- );
- # Display Positions
- our %positions = (
- "middle_line" => "\x20",
- "top_line" => "\x22",
- "bottom_line" => "\x26",
- "fill" => "\x30",
- "left" => "\x31",
- "right" => "\x32",
- );
- # Character Sets
- our %charsets = (
- "five_high_std" => "1",
- "five_stroke" => "2",
- "seven_high_std" => "3",
- "seven_stroke" => "4",
- "seven_high_fancy" => "5",
- "ten_high_std" => "6",
- "seven_shadow" => "7",
- "full_height_fancy" => "8",
- "full_height_std" => "9",
- "seven_shadow_fancy"=> ":",
- "five_wide" => ";",
- "seven_wide" => "<",
- "seven_fancy_wide" => "=",
- "wide_stroke_five" => ">",
- # The following four only work on Alpha 2.0 and Alpha 3.0 protocols
- "five_high_cust" => "W",
- "seven_high_cust" => "X",
- "ten_high_cust" => "Y",
- "fifteen_high_cust" => "Z",
- );
- # Extended characters
- our %extchars = (
- "up_arrow" => "\x64",
- "down_arrow" => "\x65",
- "left_arrow" => "\x66",
- "right_arrow" => "\x67",
- "pacman" => "\x68",
- "sail_boat" => "\x69",
- "ball" => "\x6A",
- "telephone" => "\x6B",
- "heart" => "\x6C",
- "car" => "\x6D",
- "handicap" => "\x6E",
- "rhino" => "\x6F",
- "mug" => "\x70",
- "satellite_dish" => "\x71",
- "copyright_symbol" => "\x72",
- "male_symbol" => "\x73",
- "female_symbol" => "\x74",
- "bottle" => "\x75",
- "diskette" => "\x76",
- "printer" => "\x77",
- "musical_note" => "\x78",
- "infinity_symbol" => "\x79",
- );
- # Counters
- # We have 5 of them.
- our %counters = (
- 1 => "z",
- 2 => "{",
- 3 => "|",
- 4 => "}",
- 5 => "-",
- );
- # Colors
- our %colors = (
- "red" => "1",
- "green" => "2",
- "amber" => "3",
- "dim_red" => "4",
- "dim_green" => "5",
- "brown" => "6",
- "orange" => "7",
- "yellow" => "8",
- "rainbow_1" => "9",
- "rainbow_2" => "A",
- "color_mix" => "B",
- "autocolor" => "C",
- );
- # Command Codes
- use constant {
- WRITE_TEXT => "A", # Write TEXT file (p18)
- READ_TEXT => "B", # Read TEXT file (p19)
- WRITE_SPECIAL => "E", # Write SPECIAL FUNCTION commands (p21)
- READ_SPECIAL => "F", # Read SPECIAL FUNCTION commands (p29)
- WRITE_STRING => "G", # Write STRING (p37)
- READ_STRING => "H", # Read STRING (p38)
- WRITE_SMALL_DOTS => "I", # Write SMALL DOTS PICTURE file (p39)
- READ_SMALL_DOTS => "J", # Read SMALL DOTS PICTURE file (p41)
- WRITE_RGB_DOTS => "K", # Write RGB DOTS PICTURE file (p44)
- READ_RGB_DOTS => "L", # Read RGB DOTS PICTURE file (p46)
- WRITE_LARGE_DOTS => "M", # Write LARGE DOTS PICTURE file (p42)
- READ_LARGE_DOTS => "N", # Read LARGE DOTS PICTURE file (p43)
- WRITE_ALPHAVISION => "O", # Write ALPHAVISION BULLETIN (p48)
- SET_TIMEOUT => "T", # Set Timeout Message (p118) (Alpha 2.0/3.0)
- };
- # Constants used in transmission packets
- use constant {
- NUL => "\x00", # NULL
- SOH => "\x01", # Start of Header
- STX => "\x02", # Start of TeXt (precedes a command code)
- ETX => "\x03", # End of TeXt
- EOT => "\x04", # End Of Transmission
- # ENQ => "\x05", # Enquiry
- # ACK => "\x06", # Acknowledge
- BEL => "\x07", # Bell
- BS => "\x08", # Backspace
- HT => "\x09", # Horizontal tab
- LF => "\x0A", # Line Feed
- NL => "\x0A", # New Line
- VT => "\x0B", # Vertical Tab
- # FF => "\x0C", # Form Feed
- # NP => "\x0C", # New Page
- CR => "\x0D", # Carriage Return
- CAN => "\x18", # Cancel
- SUB => "\x1A", # Substitute (select charset)
- ESC => "\x1B", # Escape character
- };
- # Constructor
- # - device: device of the LED sign
- sub new {
- my($class,$device)=@_;
- my $self={
- "device" => $device,
- "type" => "Z", # Type Code, see protocol
- "address" => "00", # Sign Address, see protocol
- "mode" => "rotate", # Default display mode
- "position" => "middle_line", # Approrpriate for one-line signs
- "debug" => 0, # debugging
- };
- return bless $self,$class;
- }
- # Connect to the sign (open the serial device)
- # If no device is known, the default /dev/ttyS0 is selected
- sub connect {
- my($this,$device)=@_;
- $device ||= $this->{device};
- if (!$device) {
- warn "No device specified. Defaulting to /dev/ttyS0.\n";
- $device = "/dev/ttyS0";
- }
- # Open a connection to the sign
- open(OUT,">",$device)
- or die "Could not open $device for output: $!\n";
- OUT->autoflush(1);
- open(IN,"<",$device)
- or die "Could not open $device for input: $!\n";
- IN->autoflush(1);
- }
- # Disconnect from the sign (close the serial device)
- sub disconnect {
- my($this)=@_;
- close OUT;
- close IN;
- }
- sub _packet {
- my($this,$contents)=@_;
- return ((NUL x 5) . SOH . $this->{type} . $this->{address} . STX
- . $contents . EOT);
- }
- sub _write {
- my($this,$packet)=@_;
- die "Not connected to device. Use \$sign->connect().\n" if !OUT->opened;
- if ($this->{debug}) {
- # make human-readable packet for display
- my $hr = $packet;
- #for(0..27) {
- # my $hex = hex $_;
- # $hr =~ s/\x$hex/[$hex]/g;
- #}
- print "Writing packet: $hr\n";
- }
- print OUT $packet;
- }
- # Read from the sign
- # This does not seem to work correctly yet. All read_* functions therefore
- # do not work.
- sub _read {
- my($this)=@_;
- my $data;
- sysread IN,$data,1024;
- return $data;
- }
- sub dec2hex {
- my($dec) = @_;
- return sprintf("%lx ", $dec );
- }
- sub hex2dec {
- return hex($_[0]);
- }
- # Set display mode
- # for $mode, use one of the Standard Mode constants exported
- # if $mode is SPECIAL, set $special_mode to one of the defined special modes
- sub set_mode {
- my($this,$mode)=@_;
- if ($modes{$mode}) {
- $this->{mode} = $mode;
- }
- else {
- warn "Warning: '$mode' is an invalid mode\n";
- }
- }
- # Get display mode
- sub get_mode {
- my($this)=@_;
- return $this->{mode};
- }
- # Set display position
- # This is mostly unimportant for one line signs.
- sub set_position {
- my($this,$position)=@_;
- if ($positions{$position}) {
- $this->{position} = $position;
- }
- else {
- warn "Warning: '$position' is an invalid position\n";
- }
- }
- # Get display position
- sub get_position {
- my($this)=@_;
- return $this->{position};
- }
- # Write TEXT to the sign
- sub write_text {
- my($this,$msg,$label)=@_;
- $label ||= "A";
- # [WRITE_TEXT][File Label][ESC][Display Position][Mode Code]
- # [Special Specifier][ASCII Message]
- my $packet = $this->_packet(WRITE_TEXT . $label . ESC
- . $positions{$this->{position}}
- . $modes{$this->{mode}} . $msg);
- $this->_write($packet);
- }
- # Read TEXT from the sign
- sub read_text {
- my($this,$label)=@_;
- $label ||= "A";
- $this->_write($this->_packet(READ_TEXT . $label));
- return $this->_read();
- }
- # Create a STRING
- # This is necessary to allocate memory for the STRING on the sign
- #
- # $string_label: label of the STRING to create
- # $string_size: size of the STRING to create, in bytes. 125 max.
- # Default is 32.
- sub create_string {
- my($this,$string_label,$string_size)=@_;
- $string_label ||= 1;
- $string_size = 125 if $string_size > 125;
- $string_size ||= 32;
- my $size_hex = hex($string_size);
- $size_hex = "0"x(4-length($size_hex)).$size_hex if length($size_hex) < 4;
- my $packet = $this->_packet(WRITE_SPECIAL . "\$"
- . "A" # call label.. why does this matter?
- . "A" # text file type
- . "U" # this TEXT file is unlocked
- . "0100" # text file size in hex
- . "FF" # text file's start time (FF = always)
- . "00" # text file's stop time
- . $string_label
- . "B" # string file type
- . "L" # this string file is locked
- . $size_hex
- . "0000" # padding
- );
- $this->_write($packet);
- }
- # Write a STRING
- sub write_string {
- my($this,$data,$label)=@_;
- $label ||= 1;
- my $packet = $this->_packet(WRITE_STRING . $label . $data);
- $this->_write($packet);
- }
- sub read_string {
- my($this,$label)=@_;
- $label ||= 1;
- my $packet = $this->_packet(READ_STRING . $label);
- $this->_write($packet);
- return $this->_read();
- }
- # Call a STRING
- # Returns the control code of specified string label. This is for
- # inserting a STRING file into a TEXT file
- sub call_string {
- my($this,$string_label)=@_;
- $string_label ||= "1";
- return "\x10" . $string_label;
- }
- # Call Date
- # Returns the control code for the date to be inserted in a TEXT
- # $format: integer from 0 to 9
- # 0 - MM/DD/YY
- # 1 - DD/MM/YY
- # 2 - MM-DD-YY
- # 3 - DD-MM-YY
- # 4 - MM.DD.YY
- # 5 - DD.MM.YY
- # 6 - MM DD YY
- # 7 - DD MM YY
- # 8 - MMM.DD, YYYY
- # 9 - Day of week
- # Format defaults to 0 if invalid or not specified
- sub call_date {
- my($this,$format);
- $format ||= 0;
- $format = 0 if ($format < 0 || $format > 9);
- return "\x0B" . $format;
- }
- # Call Time
- # Returns control code for the time.
- sub call_time {
- my($this)=@_;
- return "\x13";
- }
- # Clear sign's memory
- sub clear_memory {
- my($this)=@_;
- my $packet = $this->_packet(WRITE_SPECIAL . "\$");
- $this->_write($packet);
- }
- # Generate a tone/beep
- # $frequency: frequency of tone to generate, in hex ("00" through "FE")
- # $duration: duration, in hex, of tone in 0.1s increments ("1" through "F")
- # $repeat: number of times, in hex, to repeat the tone ("0" through "F")
- sub beep {
- my($this,$frequency,$duration,$repeat)=@_;
- $frequency ||= "10";
- $duration ||= "2";
- $repeat ||= 0;
- my $packet = $this->_packet(WRITE_SPECIAL . "(2" . $frequency . $duration
- . $repeat);
- $this->_write($packet);
- }
- # Perform a soft reset on the sign (does not clear memory; non-destructive)
- sub soft_reset {
- my($this)=@_;
- my $packet = $this->_packet(WRITE_SPECIAL . ",");
- $this->_write($packet);
- }
- # Set the day of the week on the sign
- # $day must be an integer between 1 and 7.
- # 1 = Sunday, 2 = Monday, etc.
- # Omitting the $day parameter will cause today's day to be sent
- # Returns -1 if an invalid day is specified.
- sub set_day {
- my($this,$day)=@_;
- return -1 if ($day && ($day < 1 || $day > 7));
- $day ||= time2str("%w",time)+1;
- my $packet = $this->_packet(WRITE_SPECIAL . "&" . $day);
- $this->_write($packet);
- }
- # Sets the date in the memory of the sign. This must be done each day to keep
- # the clock 'up to date', because the sign will not automatically advance the
- # day.
- #
- # NOTE: each of the parameters must be two characters long.
- #
- # If no date is specified, today's date will be used.
- sub set_date {
- my($this,$year,$month,$day)=@_;
- $year ||= time2str("%y",time);
- $month ||= time2str("%m",time);
- $day ||= time2str("%d",time);
- my $packet = $this->_packet(WRITE_SPECIAL . ";" . $month . $day . $year);
- $this->_write($packet);
- }
- # Sets the hour and minute of the internal clock on the sign
- # $h: hour in twenty-four hour format (18 instead of 6 for 6PM)
- # $m: minute
- # If no time (or an invalid time) is specified, the current system time will
- # be used
- sub set_time {
- my($this,$h,$m)=@_;
- $h = "" if ($h < 0 or $h > 23);
- $m = "" if ($m < 0 or $m > 59);
- $h ||= time2str("%H",time);
- $m ||= time2str("%M",time);
- my $packet = $this->_packet(WRITE_SPECIAL . "\x20" . $h . $m);
- $this->_write($packet);
- }
- # Sets the time format on the sign
- # $format: 1 - 24-hour (military) time
- # 0 - 12-hour (standard am/pm) format
- # 12-hour is the default
- sub set_time_format {
- my($this,$format)=@_;
- $format ||= 0;
- $format = 0 if ($format > 1 || $format < 0);
- my $byte = ($format == 0) ? "S" : "M";
- my $packet = $this->_packet(WRITE_SPECIAL . "\x27" . $byte);
- $this->_write($packet);
- }
- # Returns color code for a specified color
- # If an invalid color is specified, autocolor will be used
- sub color {
- my($this,$color)=@_;
- $color = "autocolor" if !$colors{$color};
- return "\x1C" . $colors{$color};
- }
- # Returns control code for a specified character set
- # Defaults to 'five_high_std', Five High Standard
- sub charset {
- my($this,$charset)=@_;
- $charset = "five_high_std" if !$charsets{$charset};
- return "\x1A" . $charsets{$charset};
- }
- # Returns control code for a specified extended char
- # Defaults to 'left_arrow'
- sub extchar {
- my($this,$extchar)=@_;
- $extchar = "left_arrow" if !$extchars{$extchar};
- return "\x08" . $extchars{$extchar};
- }
- # Returns control code to set the character spacing.
- # $option: if 0, set proportional characters (default)
- # 1, fixed width left justified characters
- sub spacing {
- my($this,$option)=@_;
- my $byte = ($option == 0) ? 0 : 1;
- return "\x1E" . $byte;
- }
- # Set the speed
- # $speed: integer 1 (slowest) through 5 (fastest) inclusive.
- sub speed {
- my($this,$speed)=@_;
- $speed ||= 1;
- $speed = 1 if ($speed < 1 || $speed > 5);
- my $n = 20+$speed;
- return chr($n);
- }
- 1;
|