Browse Source

Ezkey Perl implementation

Matt Sparks 15 years ago
commit
ece497c678
1 changed files with 554 additions and 0 deletions
  1. 554 0
      Ezkey.pm

+ 554 - 0
Ezkey.pm

@@ -0,0 +1,554 @@
+#!/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;