-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathadapter.go
More file actions
151 lines (139 loc) · 3.21 KB
/
Copy pathadapter.go
File metadata and controls
151 lines (139 loc) · 3.21 KB
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
package csp
import (
"errors"
"io"
"time"
)
var ErrNoData = errors.New("no data available")
var ErrWrongChecksum = errors.New("wrong checksum")
var ErrWrite = errors.New("write failed")
var ErrWriteLength = errors.New("write failed to send all bytes")
var ErrTimeout = errors.New("timeout")
const BeaconInterval = 6 * time.Second
const maxPayload = 1 + 1 + 110 // CONFIG SET requests: ID[1], Offset[1], Data[up to 110 bytes]
const (
stateIdle byte = iota
stateHeader
stateDirection
stateLength
stateCommand
statePayload
stateChecksum
)
type Adapter struct {
wire io.ReadWriter
lowestID byte
beaconReferenceTime int64
state byte
message Message
}
func NewAdapter(wire io.ReadWriter) *Adapter {
return &Adapter{
wire: wire,
message: Message{
Payload: []byte{},
},
}
}
// Send a message.
func (a *Adapter) Send(message *Message) error {
bytes := make([]byte, 5+maxPayload+1) // optimisation to avoid heap allocation: could allocate only required size, but that is not constant
_ = message.Bytes(bytes)
logTs("SEND ")
for _, b := range bytes {
log(" %02X", b)
}
log("\n")
n, err := a.wire.Write(bytes[0:message.Size()])
if err != nil {
return ErrWrite
}
if n != len(bytes) {
return ErrWriteLength
}
return nil
}
// Receive a message; returns nil if no message is available (yet).
func (a *Adapter) Receive(result *Message) error {
buf := make([]byte, 16)
for {
n, err := a.wire.Read(buf)
if err != nil || n == 0 {
return ErrNoData
}
for i := 0; i < n; i++ {
b := buf[i]
switch a.state {
case stateIdle:
if b == '$' {
logTs("IDLE %02X\n", b)
a.message.Header[0] = b
a.state = stateHeader
}
case stateHeader:
if b == 'C' {
logTs("HEADER %02X\n", b)
a.message.Header[1] = b
a.state = stateDirection
} else {
a.state = stateIdle
}
case stateDirection:
logTs("DIRECTION %02X\n", b)
if b != byte(DirRequest) && b != byte(DirResponse) {
a.state = stateIdle
continue
}
a.message.Direction = Direction(b)
a.state = stateLength
case stateLength:
logTs("LENGTH %02X\n", b)
if b > maxPayload {
a.state = stateIdle
continue
}
a.message.Length = b
a.message.Payload = a.message.Payload[:0]
a.message.Checksum = b
a.state = stateCommand
case stateCommand:
logTs("COMMAND %02X\n", b)
a.message.Command = Command(b)
a.message.Checksum ^= b
a.state = statePayload
case statePayload:
a.message.Payload = append(a.message.Payload, b)
a.message.Checksum ^= b
if len(a.message.Payload) == int(a.message.Length) {
a.state = stateChecksum
}
case stateChecksum:
logTs("PAYLOAD ")
for _, bb := range a.message.Payload {
log(" %02X", bb)
}
log("\n")
logTs("CHECKSUM expected %02X ?= %02X actual\n", a.message.Checksum, b)
result.Copy(&a.message)
a.state = stateIdle
if result.Checksum == b {
a.handleBeaconMaybe(result)
return nil
} else {
return ErrWrongChecksum
}
}
}
}
}
// Reset the state machine and clear the message buffer.
func (a *Adapter) Reset() {
a.state = stateIdle
buf := make([]byte, 16)
for {
n, err := a.wire.Read(buf)
if err != nil || n == 0 {
return
}
}
}