Ezkey.pm 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554
  1. #!/usr/bin/perl
  2. # Ezkey.pm
  3. # Interface to the Alpha Sign Communications Protocol, EZ KEY II
  4. # See http://www.ams-i.com/Pages/97088061.htm
  5. #
  6. # The purpose of this interface is to objectify and simplify communication
  7. # with LED signs like the BetaBrite: http://betabrite.com/
  8. #
  9. # @author Matt Sparks
  10. package Ezkey;
  11. use strict;
  12. use Data::Dumper;
  13. use IO::Handle;
  14. use Date::Format;
  15. use POSIX qw/floor/;
  16. require Exporter;
  17. our @ISA = qw/Exporter/;
  18. our @EXPORT = qw/%modes %graphics %positions/;
  19. # Display Modes (p89)
  20. our %modes = (
  21. "rotate" => "a",
  22. "hold" => "b",
  23. "flash" => "c",
  24. "roll_up" => "e",
  25. "roll_down" => "f",
  26. "roll_left" => "g",
  27. "roll_right" => "h",
  28. "wipe_up" => "i",
  29. "wipe_down" => "j",
  30. "wipe_left" => "k",
  31. "wipe_right" => "l",
  32. "scroll" => "m",
  33. "automode" => "o",
  34. "roll_in" => "p",
  35. "roll_out" => "q",
  36. "wipe_in" => "r",
  37. "wipe_out" => "s",
  38. "compressed_rotate" => "t", # only available on certain sign models
  39. "explode" => "u", # alpha 3.0 protocol
  40. "clock" => "v", # alpha 3.0 protocol
  41. # Special Modes
  42. "twinkle" => "n0",
  43. "sparkle" => "n1",
  44. "snow" => "n2",
  45. "interlock" => "n3",
  46. "switch" => "n4",
  47. "slide" => "n5", # only Betabrite 1036 (same as CYCLE_COLORS?)
  48. "spray" => "n6",
  49. "starburst" => "n7",
  50. "welcome" => "n8",
  51. "slot_machine" => "n9",
  52. "news_flash" => "nA", # only Betabrite 1036
  53. "trumpet_animation" => "nb", # only betabrite 1036
  54. "cycle_colors" => "nC", # only AlphaEclipse 3600
  55. # Special Graphics (these display before the message)
  56. "thank_you" => "nS",
  57. "no_smoking" => "nU",
  58. "dont_drive_drink" => "nV",
  59. "running_animal" => "nW",
  60. "fish_animation" => "nW",
  61. "fireworks" => "nX",
  62. "turbo_car" => "nY",
  63. "balloon_animation" => "nY",
  64. "cherry_bomb" => "nZ",
  65. );
  66. # Display Positions
  67. our %positions = (
  68. "middle_line" => "\x20",
  69. "top_line" => "\x22",
  70. "bottom_line" => "\x26",
  71. "fill" => "\x30",
  72. "left" => "\x31",
  73. "right" => "\x32",
  74. );
  75. # Character Sets
  76. our %charsets = (
  77. "five_high_std" => "1",
  78. "five_stroke" => "2",
  79. "seven_high_std" => "3",
  80. "seven_stroke" => "4",
  81. "seven_high_fancy" => "5",
  82. "ten_high_std" => "6",
  83. "seven_shadow" => "7",
  84. "full_height_fancy" => "8",
  85. "full_height_std" => "9",
  86. "seven_shadow_fancy"=> ":",
  87. "five_wide" => ";",
  88. "seven_wide" => "<",
  89. "seven_fancy_wide" => "=",
  90. "wide_stroke_five" => ">",
  91. # The following four only work on Alpha 2.0 and Alpha 3.0 protocols
  92. "five_high_cust" => "W",
  93. "seven_high_cust" => "X",
  94. "ten_high_cust" => "Y",
  95. "fifteen_high_cust" => "Z",
  96. );
  97. # Extended characters
  98. our %extchars = (
  99. "up_arrow" => "\x64",
  100. "down_arrow" => "\x65",
  101. "left_arrow" => "\x66",
  102. "right_arrow" => "\x67",
  103. "pacman" => "\x68",
  104. "sail_boat" => "\x69",
  105. "ball" => "\x6A",
  106. "telephone" => "\x6B",
  107. "heart" => "\x6C",
  108. "car" => "\x6D",
  109. "handicap" => "\x6E",
  110. "rhino" => "\x6F",
  111. "mug" => "\x70",
  112. "satellite_dish" => "\x71",
  113. "copyright_symbol" => "\x72",
  114. "male_symbol" => "\x73",
  115. "female_symbol" => "\x74",
  116. "bottle" => "\x75",
  117. "diskette" => "\x76",
  118. "printer" => "\x77",
  119. "musical_note" => "\x78",
  120. "infinity_symbol" => "\x79",
  121. );
  122. # Counters
  123. # We have 5 of them.
  124. our %counters = (
  125. 1 => "z",
  126. 2 => "{",
  127. 3 => "|",
  128. 4 => "}",
  129. 5 => "-",
  130. );
  131. # Colors
  132. our %colors = (
  133. "red" => "1",
  134. "green" => "2",
  135. "amber" => "3",
  136. "dim_red" => "4",
  137. "dim_green" => "5",
  138. "brown" => "6",
  139. "orange" => "7",
  140. "yellow" => "8",
  141. "rainbow_1" => "9",
  142. "rainbow_2" => "A",
  143. "color_mix" => "B",
  144. "autocolor" => "C",
  145. );
  146. # Command Codes
  147. use constant {
  148. WRITE_TEXT => "A", # Write TEXT file (p18)
  149. READ_TEXT => "B", # Read TEXT file (p19)
  150. WRITE_SPECIAL => "E", # Write SPECIAL FUNCTION commands (p21)
  151. READ_SPECIAL => "F", # Read SPECIAL FUNCTION commands (p29)
  152. WRITE_STRING => "G", # Write STRING (p37)
  153. READ_STRING => "H", # Read STRING (p38)
  154. WRITE_SMALL_DOTS => "I", # Write SMALL DOTS PICTURE file (p39)
  155. READ_SMALL_DOTS => "J", # Read SMALL DOTS PICTURE file (p41)
  156. WRITE_RGB_DOTS => "K", # Write RGB DOTS PICTURE file (p44)
  157. READ_RGB_DOTS => "L", # Read RGB DOTS PICTURE file (p46)
  158. WRITE_LARGE_DOTS => "M", # Write LARGE DOTS PICTURE file (p42)
  159. READ_LARGE_DOTS => "N", # Read LARGE DOTS PICTURE file (p43)
  160. WRITE_ALPHAVISION => "O", # Write ALPHAVISION BULLETIN (p48)
  161. SET_TIMEOUT => "T", # Set Timeout Message (p118) (Alpha 2.0/3.0)
  162. };
  163. # Constants used in transmission packets
  164. use constant {
  165. NUL => "\x00", # NULL
  166. SOH => "\x01", # Start of Header
  167. STX => "\x02", # Start of TeXt (precedes a command code)
  168. ETX => "\x03", # End of TeXt
  169. EOT => "\x04", # End Of Transmission
  170. # ENQ => "\x05", # Enquiry
  171. # ACK => "\x06", # Acknowledge
  172. BEL => "\x07", # Bell
  173. BS => "\x08", # Backspace
  174. HT => "\x09", # Horizontal tab
  175. LF => "\x0A", # Line Feed
  176. NL => "\x0A", # New Line
  177. VT => "\x0B", # Vertical Tab
  178. # FF => "\x0C", # Form Feed
  179. # NP => "\x0C", # New Page
  180. CR => "\x0D", # Carriage Return
  181. CAN => "\x18", # Cancel
  182. SUB => "\x1A", # Substitute (select charset)
  183. ESC => "\x1B", # Escape character
  184. };
  185. # Constructor
  186. # - device: device of the LED sign
  187. sub new {
  188. my($class,$device)=@_;
  189. my $self={
  190. "device" => $device,
  191. "type" => "Z", # Type Code, see protocol
  192. "address" => "00", # Sign Address, see protocol
  193. "mode" => "rotate", # Default display mode
  194. "position" => "middle_line", # Approrpriate for one-line signs
  195. "debug" => 0, # debugging
  196. };
  197. return bless $self,$class;
  198. }
  199. # Connect to the sign (open the serial device)
  200. # If no device is known, the default /dev/ttyS0 is selected
  201. sub connect {
  202. my($this,$device)=@_;
  203. $device ||= $this->{device};
  204. if (!$device) {
  205. warn "No device specified. Defaulting to /dev/ttyS0.\n";
  206. $device = "/dev/ttyS0";
  207. }
  208. # Open a connection to the sign
  209. open(OUT,">",$device)
  210. or die "Could not open $device for output: $!\n";
  211. OUT->autoflush(1);
  212. open(IN,"<",$device)
  213. or die "Could not open $device for input: $!\n";
  214. IN->autoflush(1);
  215. }
  216. # Disconnect from the sign (close the serial device)
  217. sub disconnect {
  218. my($this)=@_;
  219. close OUT;
  220. close IN;
  221. }
  222. sub _packet {
  223. my($this,$contents)=@_;
  224. return ((NUL x 5) . SOH . $this->{type} . $this->{address} . STX
  225. . $contents . EOT);
  226. }
  227. sub _write {
  228. my($this,$packet)=@_;
  229. die "Not connected to device. Use \$sign->connect().\n" if !OUT->opened;
  230. if ($this->{debug}) {
  231. # make human-readable packet for display
  232. my $hr = $packet;
  233. #for(0..27) {
  234. # my $hex = hex $_;
  235. # $hr =~ s/\x$hex/[$hex]/g;
  236. #}
  237. print "Writing packet: $hr\n";
  238. }
  239. print OUT $packet;
  240. }
  241. # Read from the sign
  242. # This does not seem to work correctly yet. All read_* functions therefore
  243. # do not work.
  244. sub _read {
  245. my($this)=@_;
  246. my $data;
  247. sysread IN,$data,1024;
  248. return $data;
  249. }
  250. sub dec2hex {
  251. my($dec) = @_;
  252. return sprintf("%lx ", $dec );
  253. }
  254. sub hex2dec {
  255. return hex($_[0]);
  256. }
  257. # Set display mode
  258. # for $mode, use one of the Standard Mode constants exported
  259. # if $mode is SPECIAL, set $special_mode to one of the defined special modes
  260. sub set_mode {
  261. my($this,$mode)=@_;
  262. if ($modes{$mode}) {
  263. $this->{mode} = $mode;
  264. }
  265. else {
  266. warn "Warning: '$mode' is an invalid mode\n";
  267. }
  268. }
  269. # Get display mode
  270. sub get_mode {
  271. my($this)=@_;
  272. return $this->{mode};
  273. }
  274. # Set display position
  275. # This is mostly unimportant for one line signs.
  276. sub set_position {
  277. my($this,$position)=@_;
  278. if ($positions{$position}) {
  279. $this->{position} = $position;
  280. }
  281. else {
  282. warn "Warning: '$position' is an invalid position\n";
  283. }
  284. }
  285. # Get display position
  286. sub get_position {
  287. my($this)=@_;
  288. return $this->{position};
  289. }
  290. # Write TEXT to the sign
  291. sub write_text {
  292. my($this,$msg,$label)=@_;
  293. $label ||= "A";
  294. # [WRITE_TEXT][File Label][ESC][Display Position][Mode Code]
  295. # [Special Specifier][ASCII Message]
  296. my $packet = $this->_packet(WRITE_TEXT . $label . ESC
  297. . $positions{$this->{position}}
  298. . $modes{$this->{mode}} . $msg);
  299. $this->_write($packet);
  300. }
  301. # Read TEXT from the sign
  302. sub read_text {
  303. my($this,$label)=@_;
  304. $label ||= "A";
  305. $this->_write($this->_packet(READ_TEXT . $label));
  306. return $this->_read();
  307. }
  308. # Create a STRING
  309. # This is necessary to allocate memory for the STRING on the sign
  310. #
  311. # $string_label: label of the STRING to create
  312. # $string_size: size of the STRING to create, in bytes. 125 max.
  313. # Default is 32.
  314. sub create_string {
  315. my($this,$string_label,$string_size)=@_;
  316. $string_label ||= 1;
  317. $string_size = 125 if $string_size > 125;
  318. $string_size ||= 32;
  319. my $size_hex = hex($string_size);
  320. $size_hex = "0"x(4-length($size_hex)).$size_hex if length($size_hex) < 4;
  321. my $packet = $this->_packet(WRITE_SPECIAL . "\$"
  322. . "A" # call label.. why does this matter?
  323. . "A" # text file type
  324. . "U" # this TEXT file is unlocked
  325. . "0100" # text file size in hex
  326. . "FF" # text file's start time (FF = always)
  327. . "00" # text file's stop time
  328. . $string_label
  329. . "B" # string file type
  330. . "L" # this string file is locked
  331. . $size_hex
  332. . "0000" # padding
  333. );
  334. $this->_write($packet);
  335. }
  336. # Write a STRING
  337. sub write_string {
  338. my($this,$data,$label)=@_;
  339. $label ||= 1;
  340. my $packet = $this->_packet(WRITE_STRING . $label . $data);
  341. $this->_write($packet);
  342. }
  343. sub read_string {
  344. my($this,$label)=@_;
  345. $label ||= 1;
  346. my $packet = $this->_packet(READ_STRING . $label);
  347. $this->_write($packet);
  348. return $this->_read();
  349. }
  350. # Call a STRING
  351. # Returns the control code of specified string label. This is for
  352. # inserting a STRING file into a TEXT file
  353. sub call_string {
  354. my($this,$string_label)=@_;
  355. $string_label ||= "1";
  356. return "\x10" . $string_label;
  357. }
  358. # Call Date
  359. # Returns the control code for the date to be inserted in a TEXT
  360. # $format: integer from 0 to 9
  361. # 0 - MM/DD/YY
  362. # 1 - DD/MM/YY
  363. # 2 - MM-DD-YY
  364. # 3 - DD-MM-YY
  365. # 4 - MM.DD.YY
  366. # 5 - DD.MM.YY
  367. # 6 - MM DD YY
  368. # 7 - DD MM YY
  369. # 8 - MMM.DD, YYYY
  370. # 9 - Day of week
  371. # Format defaults to 0 if invalid or not specified
  372. sub call_date {
  373. my($this,$format);
  374. $format ||= 0;
  375. $format = 0 if ($format < 0 || $format > 9);
  376. return "\x0B" . $format;
  377. }
  378. # Call Time
  379. # Returns control code for the time.
  380. sub call_time {
  381. my($this)=@_;
  382. return "\x13";
  383. }
  384. # Clear sign's memory
  385. sub clear_memory {
  386. my($this)=@_;
  387. my $packet = $this->_packet(WRITE_SPECIAL . "\$");
  388. $this->_write($packet);
  389. }
  390. # Generate a tone/beep
  391. # $frequency: frequency of tone to generate, in hex ("00" through "FE")
  392. # $duration: duration, in hex, of tone in 0.1s increments ("1" through "F")
  393. # $repeat: number of times, in hex, to repeat the tone ("0" through "F")
  394. sub beep {
  395. my($this,$frequency,$duration,$repeat)=@_;
  396. $frequency ||= "10";
  397. $duration ||= "2";
  398. $repeat ||= 0;
  399. my $packet = $this->_packet(WRITE_SPECIAL . "(2" . $frequency . $duration
  400. . $repeat);
  401. $this->_write($packet);
  402. }
  403. # Perform a soft reset on the sign (does not clear memory; non-destructive)
  404. sub soft_reset {
  405. my($this)=@_;
  406. my $packet = $this->_packet(WRITE_SPECIAL . ",");
  407. $this->_write($packet);
  408. }
  409. # Set the day of the week on the sign
  410. # $day must be an integer between 1 and 7.
  411. # 1 = Sunday, 2 = Monday, etc.
  412. # Omitting the $day parameter will cause today's day to be sent
  413. # Returns -1 if an invalid day is specified.
  414. sub set_day {
  415. my($this,$day)=@_;
  416. return -1 if ($day && ($day < 1 || $day > 7));
  417. $day ||= time2str("%w",time)+1;
  418. my $packet = $this->_packet(WRITE_SPECIAL . "&" . $day);
  419. $this->_write($packet);
  420. }
  421. # Sets the date in the memory of the sign. This must be done each day to keep
  422. # the clock 'up to date', because the sign will not automatically advance the
  423. # day.
  424. #
  425. # NOTE: each of the parameters must be two characters long.
  426. #
  427. # If no date is specified, today's date will be used.
  428. sub set_date {
  429. my($this,$year,$month,$day)=@_;
  430. $year ||= time2str("%y",time);
  431. $month ||= time2str("%m",time);
  432. $day ||= time2str("%d",time);
  433. my $packet = $this->_packet(WRITE_SPECIAL . ";" . $month . $day . $year);
  434. $this->_write($packet);
  435. }
  436. # Sets the hour and minute of the internal clock on the sign
  437. # $h: hour in twenty-four hour format (18 instead of 6 for 6PM)
  438. # $m: minute
  439. # If no time (or an invalid time) is specified, the current system time will
  440. # be used
  441. sub set_time {
  442. my($this,$h,$m)=@_;
  443. $h = "" if ($h < 0 or $h > 23);
  444. $m = "" if ($m < 0 or $m > 59);
  445. $h ||= time2str("%H",time);
  446. $m ||= time2str("%M",time);
  447. my $packet = $this->_packet(WRITE_SPECIAL . "\x20" . $h . $m);
  448. $this->_write($packet);
  449. }
  450. # Sets the time format on the sign
  451. # $format: 1 - 24-hour (military) time
  452. # 0 - 12-hour (standard am/pm) format
  453. # 12-hour is the default
  454. sub set_time_format {
  455. my($this,$format)=@_;
  456. $format ||= 0;
  457. $format = 0 if ($format > 1 || $format < 0);
  458. my $byte = ($format == 0) ? "S" : "M";
  459. my $packet = $this->_packet(WRITE_SPECIAL . "\x27" . $byte);
  460. $this->_write($packet);
  461. }
  462. # Returns color code for a specified color
  463. # If an invalid color is specified, autocolor will be used
  464. sub color {
  465. my($this,$color)=@_;
  466. $color = "autocolor" if !$colors{$color};
  467. return "\x1C" . $colors{$color};
  468. }
  469. # Returns control code for a specified character set
  470. # Defaults to 'five_high_std', Five High Standard
  471. sub charset {
  472. my($this,$charset)=@_;
  473. $charset = "five_high_std" if !$charsets{$charset};
  474. return "\x1A" . $charsets{$charset};
  475. }
  476. # Returns control code for a specified extended char
  477. # Defaults to 'left_arrow'
  478. sub extchar {
  479. my($this,$extchar)=@_;
  480. $extchar = "left_arrow" if !$extchars{$extchar};
  481. return "\x08" . $extchars{$extchar};
  482. }
  483. # Returns control code to set the character spacing.
  484. # $option: if 0, set proportional characters (default)
  485. # 1, fixed width left justified characters
  486. sub spacing {
  487. my($this,$option)=@_;
  488. my $byte = ($option == 0) ? 0 : 1;
  489. return "\x1E" . $byte;
  490. }
  491. # Set the speed
  492. # $speed: integer 1 (slowest) through 5 (fastest) inclusive.
  493. sub speed {
  494. my($this,$speed)=@_;
  495. $speed ||= 1;
  496. $speed = 1 if ($speed < 1 || $speed > 5);
  497. my $n = 20+$speed;
  498. return chr($n);
  499. }
  500. 1;