-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathmessage.go
295 lines (267 loc) · 8.41 KB
/
message.go
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
package lcm
import (
"errors"
"fmt"
"strings"
)
// Message represents a serial port message with common bits easily accessible.
type Message []byte
// Type returns the message type.
func (m Message) Type() Type {
if len(m) == 0 {
return 0
}
return Type(m[0])
}
// Function returns the message function.
func (m Message) Function() Function {
if len(m) == 0 {
return 0
}
return Function(m[2])
}
func (m Message) Value() []byte {
if len(m) == 0 {
return nil
}
return m[3 : 3+m[1]]
}
func (m Message) Ok() bool {
if len(m) == 0 {
return false
}
return m[3] == 0
}
// ReplyOk returns a valid Reply for a Command.
func (m Message) ReplyOk() Message {
if m.Type() == Command {
return []byte{byte(Reply), 0x01, byte(m.Function()), 0x00}
}
return nil
}
// Check that the message is valid (message must not include a checksum).
func (m Message) Check() error {
if len(m) < 4 {
return errors.New("message too short")
}
if m.Type() != Command && m.Type() != Reply {
return errors.New("unknown message type")
}
if int(m[1])+3 != len(m) {
return errors.New("wrong message length")
}
return nil
}
// Type represents the message type.
type Type byte
// LCM message types.
const (
Command Type = 0xF0
Reply Type = 0xF1
)
// Function represents the message function.
type Function byte
const (
fflush Function = 0x00 // Not a real function.
Fon Function = 0x11
Fclear Function = 0x12
Fversion Function = 0x13
fsetClear2 Function = 0x21
Fstatus Function = 0x22
Fchar Function = 0x25
Fclear2 Function = 0x26
Ftext Function = 0x27
Fbutton Function = 0x80
)
// Known commands (for sending to display).
var (
// flushMCUBuffer is a made up message but is used to resolve
// serial communication errors, see (*LCM).forceFlushMCU.
flushMCUBuffer Message = []byte{byte(Command), 0x01, byte(fflush), 0x00}
// DisplayOn turns the display on.
DisplayOn Message = []byte{byte(Command), 0x01, byte(Fon), 0x01}
// DisplayOff turns the display off.
DisplayOff Message = []byte{byte(Command), 0x01, byte(Fon), 0x00}
// ClearDisplay clears the current text from the display.
// Called during re-initialization in lcmd.
ClearDisplay Message = []byte{byte(Command), 0x01, byte(Fclear), 0x01}
// ClearDisplayPrefix clears the screen and its behavior is
// altered by AlterClearDisplayPrefix.
//
// It is unused in lcmd.
ClearDisplayPrefix Message = []byte{byte(Command), 0x01, byte(Fclear2), 0x00}
// DisplayStatus has an unknown purpose. It is issued after
// DisplayOn in the init-routine and sometimes before/after
// updating the text.
//
// It could have some other purpose, like SetClearDisplayPrefix.
DisplayStatus Message = []byte{byte(Command), 0x01, byte(Fstatus), 0x00}
// RequestVersion reports the MCU version via command.
// The only observed version number so far is 0.1.2 on both
// AS604T and AS6204T.
//
// Note: Issuing this request takes 200+ms and acknowledging
// that we received the message often results in the display
// thinking we re-requested the version. ASUSTOR does not seem
// to use this, perhaps there is only one version out there.
//
// => 0xf001130105
// <= 0xf101130005 (ack)
// <= 0xf0031300010209 (version)
RequestVersion Message = []byte{byte(Command), 0x01, byte(Fversion), 0x01}
)
// UnknownCommand0x23, unused. Values come from function arguments.
//
// Observed behavior: Nothing.
var UnknownCommand0x23 Message = []byte{byte(Command), 0x02, 0x23, 0x00, 0x00}
// SetClearDisplayPrefix changes the behavior of ClearDisplayPrefix.
//
// Known values and behavior of ClearDisplayPrefix:
// * 0 = Clear screen (fully)
// * 1 = Clear screen with underscore
// * 2 = Clear screen, blink between underscore and block
//
// In lcmd this command is sometimes used after the text for line 0 has
// been set and before line 1 is cleared with spaces. Unless it has
// other unobserved behaviors, it's probably unused in practice.
func SetClearDisplayPrefix(method int) Message {
return []byte{byte(Command), 0x01, byte(fsetClear2), byte(method)}
}
// Replies are acknowledgements to commands, when the payload bit is
// zero, the command was successfully received (and applied), when it's
// non-zero, there was an error.
//
// We don't know exactly what the different non-zero bits mean other
// than an error occurred. The bits are usually either 0x02 or 0x04,
// but even ASUSTORs lcmd binary does not care, it simply re-issues
// commands on any non-zero bit.
//
// Documented here are some mysteries found in the lcmd binary.
var (
// UnknownReply0x10, unused in the lcmd binary. We don't know
// the purpose of the 0x10 function, but it may be possible for
// the display to issue this command, in which case this would
// be the (error) response.
UnknownReply0x10 Message = []byte{byte(Reply), 0x01, 0x10, 0x02}
// UnknownReply0x10, unused in the lcmd binary. This is an error
// reply issued by the display as a response to the On function,
// however, it's purpose in the lcmd binary is unknown.
UnknownReply0x11 Message = []byte{byte(Reply), 0x01, byte(Fon), 0x02}
)
// Button represents a LCM button.
//
//go:generate stringer -type=Button
type Button byte
// Button enums.
const (
Up Button = iota + 1
Down
Back
Enter
)
// DisplayLine specifies which line to write the text on.
type DisplayLine int
// DisplayLine enums.
const (
DisplayTop DisplayLine = iota
DisplayBottom
)
// SetDisplay allows 16 characters to be written on either the top or
// bottom line, and indent can be used in which case not all characters
// in the message will be visible.
//
// When using indent, it's a good idea to fill the display with spaces
// before (first) use so that there is no stray characters in the
// beginning. This can be achieved by first setting the display to the
// empty string as it will be padded with spaces.
//
// SetDisplay(DisplayTop, 0, "")
// SetDisplay(DisplayTop, 2, "My message")
func SetDisplay(line DisplayLine, indent int, text string) (raw Message, err error) {
if line != DisplayTop && line != DisplayBottom {
return nil, errors.New("display line out of bounds")
}
if indent > 0xF {
return nil, errors.New("indentation out of bounds, [0, 15]")
}
if len(text) > 16 {
return nil, errors.New("text too long")
}
if len(text) < 16 {
text += strings.Repeat(" ", 16-len(text))
}
raw = append([]byte{byte(Command), 0x12, byte(Ftext), byte(line), byte(indent)}, []byte(text)...)
return raw, nil
}
// SetDisplayCharacter writes a single character onto the display in the
// specificed location.
//
// In lcmd, it is used by Lcmd_User_Menu_Ctl.
func SetDisplayCharacter(line DisplayLine, column int, char byte) (Message, error) {
if line != DisplayTop && line != DisplayBottom {
return nil, errors.New("display line out of bounds")
}
if column > 0xF {
return nil, errors.New("column out of bounds, [0, 15]")
}
return []byte{byte(Command), 0x03, byte(Fchar), byte(line), byte(column), char}, nil
}
// Scroll the text on the display. Each invocation of next() will return
// a message to send. The start value indicates that the text is in the
// starting position and the done value indicates one rotation has
// completed. Done becomes true one step before start meaning that the
// starting position is not yet reached (we have scrolled to the end).
//
// next := lcm.Scroll(lcm.DisplayTop, "This text will scroll")
// for {
// b, start, done := next()
// send(m, b)
// if start {
// time.Sleep(2 * time.Second)
// } else {
// time.Sleep(1 * time.Second)
// }
// if start && done {
// break
// }
// }
func Scroll(line DisplayLine, text string) (next func() (raw Message, start, done bool)) {
i := 0
done := false
return func() (Message, bool, bool) {
if i >= len(text)-16 {
done = true
}
if i > len(text)-16 {
i = 0
}
start := i == 0
trunc := text[i:]
if len(trunc) > 16 {
trunc = trunc[:16]
}
i++
b, _ := SetDisplay(line, 0, trunc)
return b, start, done
}
}
// ShowAllCharCodes allows all character codes to be
func ShowAllCharCodes() (next func() (line1, line2 Message, start, done bool), goBack func()) {
var i uint8
chars := make([]byte, 16)
done := false
next = func() (Message, Message, bool, bool) {
for j := 0; j < 16; j++ {
chars[j] = 1 + i + uint8(j)
}
line1, _ := SetDisplay(DisplayTop, 0, string(chars))
line2, _ := SetDisplay(DisplayBottom, 0, fmt.Sprintf("%03d..........%03d", i, i+15))
start := i == 0
i += 16
if i == 0 {
done = true
}
return line1, line2, start, done
}
return next, func() { i -= 16 * 2 }
}