-
-
Notifications
You must be signed in to change notification settings - Fork 5
/
packet.go
282 lines (245 loc) · 6.49 KB
/
packet.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
package dcc
import "time"
// DCC protocol-defined values for reference.
const (
BitOnePartMinDuration = 55 * time.Microsecond
BitOnePartMaxDuration = 61 * time.Microsecond
BitZeroPartMinDuration = 95 * time.Microsecond
BitZeroPartMaxDuration = 9900 * time.Microsecond
PacketSeparationMin = 5 * time.Millisecond
PacketSeparationMax = 30 * time.Millisecond
PreambleBitsMin = 14
)
// Some customizable DCC-related variables.
var (
BitOnePartDuration = 55 * time.Microsecond
BitZeroPartDuration = 100 * time.Microsecond
PacketSeparation = 15 * time.Millisecond
PreambleBits = 16
)
// HeadlightCompatMode controls if one bit in the speed instruction is
// reserved for headlight. This reduces speed steps from 32 to 16 steps.
var HeadlightCompatMode = false
// Packet represents the unit of information that can be sent to the DCC
// devices in the system. Packet implements the DCC protocol for converting
// the information into DCC-encoded 1 and 0s.
type Packet struct {
driver Driver
address byte
data []byte
ecc byte
// encoded holds an int64 (time.Duration) for each
// bit in a packet. It is an efficient representation
// to save extra function calls and IFs when sending
encoded []time.Duration
}
// NewPacket returns a new generic DCC Packet.
func NewPacket(d Driver, addr byte, data []byte) *Packet {
ecc := addr
for _, i := range data {
ecc = ecc ^ i
}
return &Packet{
driver: d,
address: addr,
data: data,
ecc: ecc,
}
}
// NewBaselinePacket returns a new generic baseline packet.
// Baseline packets are different because they use a 128 address
// space. Therefore the address is forced to start with bit 0.
func NewBaselinePacket(d Driver, addr byte, data []byte) *Packet {
addr = addr & 0x7F // 0b01111111 last 7 bits
return NewPacket(d, addr, data)
}
// NewSpeedAndDirectionPacket returns a new baseline DCC packet with speed and
// direction information.
func NewSpeedAndDirectionPacket(d Driver, addr byte, speed byte, dir Direction) *Packet {
addr = addr & 0x7F // 0b 0111 1111
if HeadlightCompatMode {
speed = speed & 0x0F // 4 lower bytes
} else {
speed = speed & 0x1F // 5 lower bytes
}
dirB := byte(0x1&dir) << 5
data := (1 << 6) | dirB | speed // 0b01DCSSSS
return &Packet{
driver: d,
address: addr,
data: []byte{data},
ecc: addr ^ data,
}
}
// NewFunctionGroupOnePacket returns an advanced DCC packet which allows to
// control FL,F1-F4 functions. FL is usually associated to the headlights.
func NewFunctionGroupOnePacket(d Driver, addr byte, fl, fl1, fl2, fl3, fl4 bool) *Packet {
var data, fln, fl1n, fl2n, fl3n, fl4n byte = 0, 0, 0, 0, 0, 0
if fl {
fln = 1 << 4
}
if fl1 {
fl1n = 1
}
if fl2 {
fl2n = 1 << 1
}
if fl3 {
fl3n = 1 << 2
}
if fl4 {
fl4n = 1 << 3
}
data = (1 << 7) | fln | fl1n | fl2n | fl3n | fl4n
return &Packet{
driver: d,
address: addr,
data: []byte{data},
ecc: addr ^ data,
}
}
// NewBroadcastResetPacket returns a new broadcast baseline DCC packet which
// makes the decoders erase their volatile memory and return to power up
// state. This stops all locomotives at non-zero speed.
func NewBroadcastResetPacket(d Driver) *Packet {
return &Packet{
driver: d,
address: 0,
data: []byte{0},
ecc: 0 ^ 0,
}
}
// NewBroadcastIdlePacket returns a new broadcast baseline DCC packet
// on which decoders perform no action.
func NewBroadcastIdlePacket(d Driver) *Packet {
return &Packet{
driver: d,
address: 0xFF,
data: []byte{0},
ecc: 0xFF ^ 0,
}
}
// NewBroadcastStopPacket returns a new broadcast baseline DCC packet which
// tells the decoders to stop all locomotives. If softStop is false, an
// emergency stop will happen by cutting power off the engine.
func NewBroadcastStopPacket(d Driver, dir Direction, softStop bool, ignoreDir bool) *Packet {
var speed byte
if !softStop {
speed = 1
}
if ignoreDir {
speed = speed | (1 << 4)
}
dirB := 0x1 & byte(dir)
data := (1 << 6) | (dirB << 5) | speed
return &Packet{
driver: d,
address: 0x0,
data: []byte{data},
ecc: 0x0 ^ data,
}
}
// delayPoll causes a active delay for the specified time
// by actively polling the clock. Unfortunately, for latencies
// under 100us, it is not possible to sleep reliably with
// syscall.Nanosleep().
func delayPoll(now time.Time, d time.Duration) {
for {
if time.Since(now) > d {
return
}
}
}
// PacketPause performs a pause by sleeping
// during the PacketSeparation time.
func (p *Packet) PacketPause() {
// Not really needed
p.driver.Low()
time.Sleep(PacketSeparation)
p.driver.High()
}
// Send encodes and sends a packet using the Driver associated to it.
func (p *Packet) Send() {
if p.driver == nil {
panic("No driver set")
}
if p.encoded == nil {
p.build()
}
// The way p.encoded is we reduce function calls and ifs
for _, b := range p.encoded {
p.driver.Low()
now := time.Now()
delayPoll(now, b)
p.driver.High()
now = time.Now()
delayPoll(now, b)
}
}
// Length returns the length of the DCC-encoded representation
// of a packet.
func (p *Packet) Length() int {
l := 0
l += PreambleBits // Preamble
l += 1 // Packet start
l += 8 // Address byte
for i := 0; i < len(p.data); i++ {
l += 1 // Data start
l += 8 // Data byte
}
l += 1 // ECC start
l += 8 // ECC byte
l += 1 // Packet end
return l
}
// By prebuilding packages we ensure more consistent Send() times.
func (p *Packet) build() {
enc := make([]time.Duration, 0, p.Length())
unpackByte := func(b byte) []time.Duration {
bs := make([]time.Duration, 8, 8)
for i := uint8(0); i < 8; i++ {
bit := (b >> (7 - i)) & 0x1
if bit == 0 {
bs[i] = BitZeroPartDuration
} else {
bs[i] = BitOnePartDuration
}
}
return bs
}
// Preamble
for i := 0; i < PreambleBits; i++ {
enc = append(enc, BitOnePartDuration)
}
// Packet start bit
enc = append(enc, BitZeroPartDuration)
// Address
enc = append(enc, unpackByte(p.address)...)
// Data
for _, d := range p.data {
enc = append(enc, BitZeroPartDuration) // Data start
enc = append(enc, unpackByte(d)...) // Data
}
// ECC
enc = append(enc, BitZeroPartDuration) // ECC start
enc = append(enc, unpackByte(p.ecc)...)
// Packet end
enc = append(enc, BitOnePartDuration)
p.encoded = enc
}
func (p *Packet) String() string {
if p.encoded == nil {
p.build()
}
var str string
for _, b := range p.encoded {
if b == BitZeroPartDuration {
str += "0"
} else if b == BitOnePartDuration {
str += "1"
} else {
panic("bad encoding")
}
}
return str
}