-
Notifications
You must be signed in to change notification settings - Fork 0
/
betabrite.rb
executable file
·340 lines (302 loc) · 7.55 KB
/
betabrite.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
#!/usr/bin/env ruby
#
# betabrite.rb
# A Simple ruby class for interfacing with AMS
# Led matrix signs via the Alpha protocol.
#
# Not a complete implementation, just something
# to control my old Betabrite 1040 sign.
#
# Created by Vesa-Pekka Palmu on 2015-07-26.
# Copyright 2015 Vesa-Pekka Palmu.
# Licended under the MIT license, see LICENSE
#
require 'serialport'
class Betabrite
NULL = 0.chr
SOH = 1.chr
STX = 2.chr
ETX = 3.chr
EOT = 4.chr
ESC = 27.chr
NEW_PAGE = 0x0c.chr
NEW_LINE = 0x0d.chr
CALL_STRING = 0x10.chr # Must be followed by string file label
CALL_TIME = 0x13.chr
CALL_DOTS = 0x14.chr # Must be followed by small dots file label
# Message speeds
SPEED_1 = 0x15.chr
SPEED_2 = 0x16.chr
SPEED_3 = 0x17.chr
SPEED_4 = 0x18.chr
SPEED_5 = 0x19.chr
NO_HOLD = 0x09.chr
# Colors
SET_COLOR = 0x1c.chr # Must be followed by 1-9, A, B, C
COLOR_RED = SET_COLOR + '1'
COLOR_GREEN = SET_COLOR + '2'
COLOR_AMBER = SET_COLOR + '3'
COLOR_DIM_RED = SET_COLOR + '4'
COLOR_DIM_GREEN = SET_COLOR + '5'
COLOR_BROWN = SET_COLOR + '6'
COLOR_ORANGE = SET_COLOR + '7'
COLOR_YELLOW = SET_COLOR + '8'
COLOR_RAINBOW1 = SET_COLOR + '9'
COLOR_RAINBOW2 = SET_COLOR + 'A'
COLOR_MIX = SET_COLOR + 'B'
COLOR_AUTO = SET_COLOR + 'C'
# Different fonts supported by the Betabrite 1040
SET_FONT = 0x1a.chr
FONT_FIVE_STD = SET_FONT + '1'
FONT_FIVE_BOLD = SET_FONT + '2'
FONT_FIVE_WIDE = SET_FONT + 0x3b.chr
FONT_SEVEN_STD = SET_FONT + '3'
FONT_SEVEN_BOLD = SET_FONT + '4'
FONT_SEVEN_WIDE = SET_FONT + 0x3c.chr
# Character spacing
FONT_SPACING_PRORTIONAL = 0x1e.chr + '0'
FONT_SPACING_FIXED = 0x1e.chr + '1'
PRIORITY_FILE = 0x30.chr # Overrides other messages on the sign if it exists
# Connect to a sign via serial port and set the time and weekday
def initialize(tty)
# Open the serial port, 9600 baud, 7 data bits, 1 stop bit, even parity
@sp = SerialPort.new(tty, 9600, 7, 1, SerialPort::EVEN)
set_time
set_weekday
set_time_format
end
# Write a message to a TEXT file
def write_text(file, mode, text)
# Encode the display mode
case mode
when :rotate # Text scrolls from right to left
mode = 'a'
when :hold # Text is displayed centered, no animation
mode = 'b'
when :flash
mode = 'c' # At least my betabrite reboots with this mode
when :roll_up
mode = 'e'
when :roll_down
mode = 'f'
when :roll_left
mode = 'g'
when :roll_right
mode = 'h'
when :wipe_up
mode = 'i'
when :wipe_down
mode = 'j'
when :wipe_left
mode = 'k'
when :wipe_right
mode = 'l'
when :scroll
mode = 'm'
when :automode
mode = 'o'
when :roll_in
mode = 'p'
when :roll_out
mode = 'q'
when :wipe_in
mode = 'r'
when :wipe_out
mode = 's'
when :c_rotate
mode = 't'
when :twinkle # Pixels of the text twinkle on the display
mode = 'n0'
when :sparkle # Draw the new text one pixel at a time
mode = 'n1'
when :snow # Pixels snow down from top
mode = 'n2'
when :interlock
mode = 'n3'
when :switch
mode = 'n4'
when :slide
mode = 'n5'
when :spray
mode = 'n6'
when :starburst
mode = 'n7'
when :welcome
mode = 'n8'
when :slot_machine
mode = 'n9' # This also crashes my betabrite
else
mode = 'a'
end
data = command(:write_text)
data << file
data << ESC
data << 32.chr # Use middle line (we have only one line)
data << mode
data << escape_text(text)
send_command data
end
# Write text to a STRING file, they can be used in TEXT files.
def write_string(file, text)
data = command(:write_string)
data << file
data << escape_text(text)
send_command data
end
# Set the time on the sign
def set_time(time = Time.now)
data = command(:write_special)
data << 0x20.chr # Set time of day command
data << time.strftime('%H%M')
send_command data
end
# Set the week day on the sign
def set_weekday(day = Time.now.wday)
data = command(:write_special)
data << 0x26.chr # Set day of week
data << day
send_command data
end
# Set the current date on the sign. Betabrite 1040 doesn't support this.
# Newer Betabrite 1036 signs support the date, but it isn't updated by the sign itself.
def set_date(date = Time.now)
data = command(:write_special)
data << 0x3b.chr
data << date.strftime('%m%d%y')
send_command data
end
# Select between 12 hour and 24 hour time formats
def set_time_format(ampm = false)
data = command(:write_special)
data << 0x27.chr # Set time format
data << (ampm ? 'S' : 'M')
send_command data
end
# Turn sound on or off
def sound(sound_on = false)
data = command(:write_special)
data << 0x21
data << sound_on ? 'FF' : '00'
send_command data
end
# Soft reset the sign
def soft_reset
data = command(:write_special)
data << 0x2c.chr # Soft reset
send_command data
end
# Set the internal memory map on the sign.
# The betabrite has 30k of memory for messages
# You need to allocate that between text, string and graphics files.
def set_memory_map
data = command(:write_special)
data << 0x24.chr
# Create 5 text files A-E, max 256 byte size, run time set as always
('A'..'E').each do |i|
data << "#{i}AL0100FF00"
end
# Create 10 string files with labels 1-10 and max 125 byte size
(1..10).each do |i|
data << "#{i}BL007D0000"
end
send_command data
end
# This command sends the signs error register back on the serial port
def read_error_register
data = command(:read_special)
data << 0x2a.chr
send_command data
end
# Read the memory size from the sign
def read_memory_size
data = command(:read_special)
data << 0x23.chr
send_command data
end
def clear_text(file)
data = command(:write_text)
data << file
send_command data
end
private
# Header is 5-10 nulls (0x00) followed by start of header (0x01)
# Then comes the sign type code and address, we are only communicating
# with one sign, so send "Z00" for "broadcast to all signs"
# Then we send start-of-text (0x02). After that its time for the command
def header
h = String.new
# five nulls for the sign to detenct the baud rate
5.times do
h << NULL
end
# Start of header (SOH)
h << SOH
# Broadcast to all signs
h << 'Z00'
# Start of text (STX)
h << STX
return h
end
# Subset of command codes. They are the next thing to send after STX
def command(cmd)
case cmd
when :write_text
'A'
when :write_special
'E'
when :read_special
'F'
when :write_string
'G'
when :write_smalldots
'I'
when :write_rgb
'K'
when :write_largedots
'M'
end
end
# Calculates the checksum for the message
# This is a running 16bit sum starting at the STX
# and ending at the ETX, both control codes are included.
def checksum(data)
sum = 0
data.each_char do |c|
sum += c.ord
end
sum += STX.ord + ETX.ord
sum = sum % 65535
return (sprintf "%04x", sum).upcase
end
# Convert some non-ascii characters into escaped extended characters for the panel
def escape_text(text)
return nil if text.empty?
esc = 0x08.chr
extended_chars = {
'ä' => (esc + 0x24.chr),
'Ä' => (esc + 0x2e.chr),
'ö' => (esc + 0x34.chr),
'Ö' => (esc + 0x39.chr),
'å' => (esc + 0x26.chr),
'Å' => (esc + 0x2f.chr)
}
extended_chars.each_pair do |k,v|
text.gsub! k, v
end
escaped = String.new
text.each_char do |c|
if c.ord > 127
escaped << '_'
else
escaped << c
end
end
return escaped
end
# Send a command to the sign.
# We encapsulate it between the header and the checksum.
def send_command(data)
message = header + data + ETX + checksum(data) + EOT
@sp.puts message
end
end