alphasign.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518
  1. #!/usr/bin/python
  2. import datetime
  3. import os
  4. import sys
  5. import serial
  6. class Alpha:
  7. # Display Modes (p89)
  8. modes = {
  9. "rotate": "a",
  10. "hold": "b",
  11. "flash": "c",
  12. "roll_up": "e",
  13. "roll_down": "f",
  14. "roll_left": "g",
  15. "roll_right": "h",
  16. "wipe_up": "i",
  17. "wipe_down": "j",
  18. "wipe_left": "k",
  19. "wipe_right": "l",
  20. "scroll": "m",
  21. "automode": "o",
  22. "roll_in": "p",
  23. "roll_out": "q",
  24. "wipe_in": "r",
  25. "wipe_out": "s",
  26. "compressed_rotate": "t", # only available on certain sign models
  27. "explode": "u", # alpha 3.0 protocol
  28. "clock": "v", # alpha 3.0 protocol
  29. # Special Modes
  30. "twinkle": "n0",
  31. "sparkle": "n1",
  32. "snow": "n2",
  33. "interlock": "n3",
  34. "switch": "n4",
  35. "slide": "n5", # only Betabrite 1036 (same as CYCLE_COLORS?)
  36. "spray": "n6",
  37. "starburst": "n7",
  38. "welcome": "n8",
  39. "slot_machine": "n9",
  40. "news_flash": "nA", # only Betabrite 1036
  41. "trumpet_animation": "nb", # only betabrite 1036
  42. "cycle_colors": "nC", # only AlphaEclipse 3600
  43. # Special Graphics (these display before the message)
  44. "thank_you": "nS",
  45. "no_smoking": "nU",
  46. "dont_drive_drive": "nV",
  47. "running_animal": "nW",
  48. "fish_animation": "nW",
  49. "fireworks": "nX",
  50. "turbo_car": "nY",
  51. "balloon_animation": "nY",
  52. "cherry_bomb": "nZ",
  53. }
  54. # Display Positions
  55. positions = {
  56. "middle_line": "\x20",
  57. "top_line": "\x22",
  58. "bottom_line": "\x26",
  59. "fill": "\x30",
  60. "left": "\x31",
  61. "right": "\x32",
  62. }
  63. # Character Sets
  64. charsets = {
  65. "five_high_std": "1",
  66. "five_stroke": "2",
  67. "seven_high_std": "3",
  68. "seven_stroke": "4",
  69. "seven_high_fancy": "5",
  70. "ten_high_std": "6",
  71. "seven_shadow": "7",
  72. "full_height_fancy": "8",
  73. "full_height_std": "9",
  74. "seven_shadow_fancy": ":",
  75. "five_wide": ";",
  76. "seven_wide": "<",
  77. "seven_fancy_wide": "=",
  78. "wide_stroke_five": ">",
  79. # The following four only work on Alpha 2.0 and Alpha 3.0 protocols
  80. "five_high_cust": "W",
  81. "seven_high_cust": "X",
  82. "ten_high_cust": "Y",
  83. "fifteen_high_cust": "Z",
  84. }
  85. # Extended characters
  86. extchars = {
  87. "up_arrow": "\x64",
  88. "down_arrow": "\x65",
  89. "left_arrow": "\x66",
  90. "right_arrow": "\x67",
  91. "pacman": "\x68",
  92. "sail_boat": "\x69",
  93. "ball": "\x6A",
  94. "telephone": "\x6B",
  95. "heart": "\x6C",
  96. "car": "\x6D",
  97. "handicap": "\x6E",
  98. "rhino": "\x6F",
  99. "mug": "\x70",
  100. "satellite_dish": "\x71",
  101. "copyright_symbol": "\x72",
  102. "male_symbol": "\x73",
  103. "female_symbol": "\x74",
  104. "bottle": "\x75",
  105. "diskette": "\x76",
  106. "printer": "\x77",
  107. "musical_note": "\x78",
  108. "infinity_symbol": "\x79",
  109. }
  110. # Counters
  111. # We have 5 of them.
  112. counters = {
  113. 1: "z",
  114. 2: "{",
  115. 3: "|",
  116. 4: "}",
  117. 5: "-",
  118. }
  119. # Colors
  120. colors = {
  121. "red": "1",
  122. "green": "2",
  123. "amber": "3",
  124. "dim_red": "4",
  125. "dim_green": "5",
  126. "brown": "6",
  127. "orange": "7",
  128. "yellow": "8",
  129. "rainbow_1": "9",
  130. "rainbow_2": "A",
  131. "color_mix": "B",
  132. "autocolor": "C",
  133. }
  134. # Command Codes
  135. WRITE_TEXT = "A" # Write TEXT file (p18)
  136. READ_TEXT = "B" # Read TEXT file (p19)
  137. WRITE_SPECIAL = "E" # Write SPECIAL FUNCTION commands (p21)
  138. READ_SPECIAL = "F" # Read SPECIAL FUNCTION commands (p29)
  139. WRITE_STRING = "G" # Write STRING (p37)
  140. READ_STRING = "H" # Read STRING (p38)
  141. WRITE_SMALL_DOTS = "I" # Write SMALL DOTS PICTURE file (p39)
  142. READ_SMALL_DOTS = "J" # Read SMALL DOTS PICTURE file (p41)
  143. WRITE_RGB_DOTS = "K" # Write RGB DOTS PICTURE file (p44)
  144. READ_RGB_DOTS = "L" # Read RGB DOTS PICTURE file (p46)
  145. WRITE_LARGE_DOTS = "M" # Write LARGE DOTS PICTURE file (p42)
  146. READ_LARGE_DOTS = "N" # Read LARGE DOTS PICTURE file (p43)
  147. WRITE_ALPHAVISION = "O" # Write ALPHAVISION BULLETIN (p48)
  148. SET_TIMEOUT = "T" # Set Timeout Message (p118) (Alpha 2.0/3.0)
  149. # Constants used in transmission packets
  150. NUL = "\x00" # NULL
  151. SOH = "\x01" # Start of Header
  152. STX = "\x02" # Start of TeXt (precedes a command code)
  153. ETX = "\x03" # End of TeXt
  154. EOT = "\x04" # End Of Transmission
  155. #ENQ = "\x05" # Enquiry
  156. #ACK = "\x06" # Acknowledge
  157. BEL = "\x07" # Bell
  158. BS = "\x08" # Backspace
  159. HT = "\x09" # Horizontal tab
  160. LF = "\x0A" # Line Feed
  161. NL = "\x0A" # New Line
  162. VT = "\x0B" # Vertical Tab
  163. #FF = "\x0C" # Form Feed
  164. #NP = "\x0C" # New Page
  165. CR = "\x0D" # Carriage Return
  166. CAN = "\x18" # Cancel
  167. SUB = "\x1A" # Substitute (select charset)
  168. ESC = "\x1B" # Escape character
  169. def __init__(self, device="/dev/ttyS0"):
  170. self.device = device
  171. self.type = "Z" # Type Code (see protocol)
  172. self.address = "00" # Sign Address (see protocol)
  173. self.mode = "rotate" # Default display mode
  174. self.position = "middle_line" # Appropriate for one-line signs
  175. self.debug = False
  176. def connect(self):
  177. """Establish connection to the device.
  178. Args:
  179. device: character device (default: /dev/ttyS0)
  180. """
  181. # TODO(ms): these settings can probably be tweaked and still support most of
  182. # the devices.
  183. self._conn = serial.Serial(port=self.device,
  184. baudrate=4800,
  185. parity=serial.PARITY_EVEN,
  186. stopbits=serial.STOPBITS_TWO,
  187. timeout=1,
  188. xonxoff=0,
  189. rtscts=0)
  190. def disconnect(self):
  191. if self._conn:
  192. self._conn.close()
  193. def _packet(self, contents):
  194. pkt = ("%s%s%s%s%s%s%s" % (self.NUL * 5, self.SOH, self.type, self.address,
  195. self.STX, contents, self.EOT))
  196. return pkt
  197. def _write(self, packet):
  198. if not self._conn:
  199. return
  200. if self.debug:
  201. print "Writing packet: %s" % repr(packet)
  202. self._conn.write(packet)
  203. def write_text(self, msg, label="A"):
  204. # [WRITE_TEXT][File Label][ESC][Display Position][Mode Code]
  205. # [Special Specifier][ASCII Message]
  206. packet = self._packet("%s%s%s%s%s%s" % (self.WRITE_TEXT, label, self.ESC,
  207. self.positions[self.position],
  208. self.modes[self.mode],
  209. msg))
  210. self._write(packet)
  211. def create_string(self, string_label="1", string_size=32):
  212. """Create a STRING.
  213. This is necessary to allocate memory for the STRING on the sign
  214. Args:
  215. string_label: label of the STRING to create
  216. string_size: size of the STRING to create, in bytes. 125 max.
  217. Default is 32.
  218. """
  219. if string_size > 125:
  220. string_size = 125
  221. size_hex = "%04x" % string_size
  222. packet = self._packet("%s%s%s%s%s%s%s%s%s%s%s%s%s" %
  223. (self.WRITE_SPECIAL, "\$",
  224. "A", # call label.. why does this matter?
  225. "A", # text file type
  226. "U", # this TEXT file is unlocked
  227. "0100", # text file size in hex
  228. "FF", # text file's start time (FF = always)
  229. "00", # text file's stop time
  230. string_label,
  231. "B", # string file type
  232. "L", # this string file is locked
  233. size_hex,
  234. "0000")) # padding
  235. self._write(packet)
  236. def write_string(self, data, label="1"):
  237. """Write a STRING.
  238. Args:
  239. data: data to write
  240. label: STRING label to write
  241. """
  242. packet = self._packet("%s%s%s" % (self.WRITE_STRING, label, data))
  243. self._write(packet)
  244. def call_string(self, string_label="1"):
  245. """Call a STRING.
  246. This is for inserting a STRING file into a TEXT file.
  247. Args:
  248. string_label: label of string to call (default: 1)
  249. Returns:
  250. control code of specified string label
  251. """
  252. return "\x10%s" % string_label
  253. def call_date(self, format=0):
  254. """Call date for insertion into a TEXT file.
  255. Args:
  256. format: integer from 0 to 9
  257. 0 - MM/DD/YY
  258. 1 - DD/MM/YY
  259. 2 - MM-DD-YY
  260. 3 - DD-MM-YY
  261. 4 - MM.DD.YY
  262. 5 - DD.MM.YY
  263. 6 - MM DD YY
  264. 7 - DD MM YY
  265. 8 - MMM.DD, YYYY
  266. 9 - Day of week
  267. Returns:
  268. formatted string to use in a TEXT
  269. """
  270. if format < 0 or format > 9:
  271. format = 0
  272. return "\x0B%s" % format
  273. def call_time(self):
  274. """Call time for insertion into a TEXT file.
  275. Returns:
  276. formatted string to use in a TEXT
  277. """
  278. return "\x13"
  279. def set_mode(self, mode):
  280. """FIXME
  281. """
  282. if mode in self.modes:
  283. self.mode = mode
  284. # FIXME: error handling for invalid mode
  285. def set_position(self, mode):
  286. """FIXME
  287. """
  288. if position in self.positions:
  289. self.position = position
  290. # FIXME: error handling
  291. def clear_memory(self):
  292. """Clear the sign's memory.
  293. """
  294. packet = self._packet("%s%s" % (self.WRITE_SPECIAL, "$"))
  295. self._write(packet)
  296. def beep(self, frequency=0, duration=0.1, repeat=0):
  297. """Make the sign beep.
  298. Args:
  299. frequency: frequency integer (not in Hz), 0 - 254
  300. duration: beep duration, 0.1 - 1.5
  301. repeat: number of times to repeat, 0 - 15
  302. """
  303. if frequency < 0:
  304. frequency = 0
  305. elif frequency > 254:
  306. frequency = 254
  307. duration = int(duration / 0.1)
  308. if duration < 1:
  309. duration = 1
  310. elif duration > 15:
  311. duration = 15
  312. if repeat < 0:
  313. repeat = 0
  314. elif repeat > 15:
  315. repeat = 15
  316. packet = self._packet("%s%s%02X%X%X" % (self.WRITE_SPECIAL, "(2",
  317. frequency, duration, repeat))
  318. self._write(packet)
  319. def soft_reset(self):
  320. """Perform a soft reset on the sign.
  321. This is non-destructive and does not clear the sign's memory.
  322. """
  323. packet = self._packet("%s%s" % (self.WRITE_SPECIAL, ","))
  324. self._write(packet)
  325. def set_day(self, day=None):
  326. """Set the day of the week on the sign.
  327. If the argument is omitted, today's day will be used.
  328. Args:
  329. day (optional): integer between 1 (Sunday) and 7 (Saturday)
  330. """
  331. if day is None or day < 1 or day > 7:
  332. day = datetime.datetime.today().weekday() + 1
  333. packet = self._packet("%s%s%s" % (self.WRITE_SPECIAL, "&", day))
  334. self._write(packet)
  335. def set_date(self, year=None, month=None, day=None):
  336. """Sets the date in the memory of the sign. This must be done each day to
  337. keep the clock 'up to date', because the sign will not automatically advance
  338. the day.
  339. If the date is not specified in the arguments, today's date will be used.
  340. Args:
  341. year (optional): two-digit year (98, 99, 00, ...)
  342. month (optional): integer month (1, 2, ..., 12)
  343. day (optional): integer day (1, ..., 31)
  344. """
  345. today = datetime.datetime.today()
  346. if year is None:
  347. year = str(today.year)[2:4]
  348. if month is None:
  349. month = today.month
  350. if day is None:
  351. day = today.day
  352. packet = self._packet("%s%s%02d%02d%02d" % (self.WRITE_SPECIAL, ";",
  353. year, month, day))
  354. self._write(packet)
  355. def set_time(self, hour=None, minute=None):
  356. """Sets ths hour and minute of the internal clock on the sign.
  357. If the time is not specified in the arguments, the time now will be used.
  358. Args:
  359. hour: hour in 24-hour format (18 instead of 6 for 6PM)
  360. minute: minute (0 - 59)
  361. """
  362. now = datetime.datetime.today()
  363. if hour is None:
  364. hour = now.hour
  365. if minute is None:
  366. minute = now.minute
  367. packet = self._packet("%s%s%02d%02d" % (self.WRITE_SPECIAL, "\x20",
  368. hour, minute))
  369. self._write(packet)
  370. def set_time_format(self, format=1):
  371. """Sets the time format on the sign.
  372. Args:
  373. format: 1 - 24-hour (military) time
  374. 0 - 12-hour (standard am/pm) format
  375. """
  376. if format < 0 or format > 1:
  377. format = 1
  378. byte = (format == 0) and "S" or "M"
  379. packet = this._packet("%s%s%s" % (self.WRITE_SPECIAL, "\x27", byte))
  380. self._write(packet)
  381. def color(self, color="autocolor"):
  382. """Returns color code for a specified color.
  383. Args:
  384. color: color string
  385. Returns:
  386. FIXME
  387. """
  388. if color not in self.colors:
  389. color = "autocolor"
  390. return "%s%s" % ("\x1C", self.colors[color])
  391. def charset(self, charset="five_high_std"):
  392. """Returns control code for a specified character set.
  393. Args:
  394. charset: charset name string
  395. Returns:
  396. FIXME
  397. """
  398. if charset not in self.charsets:
  399. charset = "five_high_std"
  400. return "%s%s" % ("\x1A", self.charsets[charset])
  401. def extchar(self, extchar="left_arrow"):
  402. """Returns control code for a specified extended character.
  403. Args:
  404. extchar: extended character name
  405. Returns:
  406. FIXME
  407. """
  408. if extchar not in self.extchars:
  409. extchar = "left_arrow"
  410. return "%s%s" % ("\x08", self.extchars[extchar])
  411. def spacing(self, option=0):
  412. """Returns control code to set the character spacing.
  413. Args:
  414. option: 0 - set proportional characters
  415. 1 - fixed width left justified characters
  416. Returns:
  417. FIXME
  418. """
  419. byte = (option == 0) and "0" or "1"
  420. return "\x1E%s" % byte
  421. def speed(self, speed):
  422. """Set the speed of the scrolling text.
  423. Args:
  424. speed: integer 1 (slowest) through 5 (fastest) inclusive
  425. Returns:
  426. FIXME
  427. """
  428. if speed < 1:
  429. speed = 1
  430. elif speed > 5:
  431. speed = 5
  432. n = 20 + speed
  433. return chr(n)
  434. def main():
  435. sign = Alpha("/dev/rfcomm0")
  436. sign.debug = True
  437. sign.connect()
  438. sign.clear_memory()
  439. sign.soft_reset()
  440. sign.disconnect()
  441. if __name__ == "__main__":
  442. main()