-
Notifications
You must be signed in to change notification settings - Fork 0
/
ftdi_adapter.go
347 lines (270 loc) · 7.97 KB
/
ftdi_adapter.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
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
341
342
343
344
345
346
347
package eltee
/**
Mac OS wants the D2XX Helper thing installed
http://www.ftdichip.com/Drivers/D2XX.htm
*/
/*
#cgo pkg-config: libftdi1
#include <ftdi.h>
#include <stdio.h>
char fake_buff[512];
const char* fake_write(struct ftdi_context *ftdi, const unsigned char *buf, int size)
{
sprintf(fake_buff, "%d %d %d %d %d %d", buf[0], buf[1], buf[2], buf[3], buf[4], buf[5]);
return fake_buff;
}
*/
import "C"
import (
"fmt"
"time"
)
type FtdiVersion struct {
Major int
Minor int
Micro int
Version string
Snapshot string
}
// Provides the libftdi version information in a go struct. This is a super simple pass through
// that is at least useful to test whether the library is available and working at all.
func FtdiLibVersion() FtdiVersion {
out := FtdiVersion{}
info := C.ftdi_get_library_version()
out.Major = int(info.major)
out.Minor = int(info.minor)
out.Micro = int(info.micro)
out.Version = C.GoString(info.version_str)
out.Snapshot = C.GoString(info.snapshot_str)
return out
}
/////////////////////
type FtdiStatus int
const (
Stopped FtdiStatus = iota
Closed
Open
)
type FtdiContext struct {
ctx *C.struct_ftdi_context
status FtdiStatus
// The currentDMX is 513 bytes long because it is exactly what we will output
// all the time. There is a leading 0 byte and then a full DMX frame of data
currentDMX []byte
// This is a list of potentially partial buffers which will be used to update
// the output right before it is written on it's own clock cycle.
pendingDMX chan []byte
lastOpenAttempt time.Time
lastWriteAt time.Time
}
func NewFtdiContext() *FtdiContext {
fc := &FtdiContext{
status: Stopped,
currentDMX: make([]byte, 513),
pendingDMX: make(chan []byte, 80), // 2 seconds or so worth...
}
fc.ctx = C.ftdi_new()
// Check for null???
// // Some initial data
// fc.currentDMX[1] = 1
// fc.currentDMX[2] = 2
// fc.currentDMX[3] = 3
// fc.currentDMX[4] = 4
return fc
}
func (fc *FtdiContext) Start() error {
if fc == nil {
return fmt.Errorf("Can not start nil FtdiContext")
}
if fc.status != Stopped {
return fmt.Errorf("Context is already started")
}
fc.status = Closed
go fc.goOpener()
return nil
}
func (fc *FtdiContext) WriteDmx(dmx []byte) {
if fc == nil {
return
}
if fc.status != Open {
return
}
fc.pendingDMX <- dmx
}
////////////////////////////////////////////////
func (fc *FtdiContext) errorString() string {
return C.GoString(C.ftdi_get_error_string(fc.ctx))
}
const REOPEN_TIME time.Duration = time.Second * 5
// Go routine which attempts to open the interface
func (fc *FtdiContext) goOpener() {
for {
now := time.Now()
reopenAt := fc.lastOpenAttempt.Add(REOPEN_TIME)
if reopenAt.Before(now) {
fc.lastOpenAttempt = now
// Attempt to reopen
if fc.attemptOpen() {
// Cool! Start the pump routine
go fc.goFramePump()
// Done with this go routine
break
}
} else {
// Sleep until it is time
toSleep := reopenAt.Sub(now)
time.Sleep(toSleep)
}
}
}
// Make an attempt at opening the first device. Log the error and just
// return true or false
func (fc *FtdiContext) attemptOpen() bool {
log.Info("FTDI: Open: Attempting to open first ftdi device")
// Start by getting a list of devices so that we can use the first device
var list *C.struct_ftdi_device_list
count := C.ftdi_usb_find_all(fc.ctx, &list, 0, 0)
log.Infof("Found %v devices", count)
switch {
case count == 0:
log.Warning("FTDI: Open: No devices were found")
return false
case count < 0:
log.Warningf("FTDI: Open: ftdi_usb_find_all() %v", fc.errorString())
return false
}
defer C.ftdi_list_free(&list)
result := C.ftdi_set_interface(fc.ctx, C.INTERFACE_A)
if result != 0 {
log.Warningf("FTDI: Open: ftdi_set_interface() %v", fc.errorString())
return false
}
log.Debug("FTDI: Open: Attempting to open the first device")
result = C.ftdi_usb_open_dev(fc.ctx, list.dev)
if result != 0 {
log.Warningf("FTDI: Open: ftdi_usb_open_dev() %v", fc.errorString())
return false
}
// Reset it
result = C.ftdi_usb_reset(fc.ctx)
if result != 0 {
log.Warningf("FTDI: Open: ftdi_usb_reset() %v", fc.errorString())
return false
}
// Baud Rate
result = C.ftdi_set_baudrate(fc.ctx, 250000)
if result != 0 {
log.Warningf("FTDI: Open: ftdi_set_baudrate() %v", fc.errorString())
return false
}
// Line Properties
// 8 bits, 2 stop bits, no parity
result = C.ftdi_set_line_property(fc.ctx, C.BITS_8, C.STOP_BIT_2, C.NONE)
if result != 0 {
log.Warningf("FTDI: Open: ftdi_set_line_property() %v", fc.errorString())
return false
}
// Flow Control
result = C.ftdi_setflowctrl(fc.ctx, C.SIO_DISABLE_FLOW_CTRL)
if result != 0 {
log.Warningf("FTDI: Open: ftdi_setflowctrl() %v", fc.errorString())
return false
}
// Purge Buffers
result = C.ftdi_usb_purge_buffers(fc.ctx)
if result != 0 {
log.Warningf("FTDI: Open: ftdi_usb_purge_buffers() %v", fc.errorString())
return false
}
// Clear RTS
result = C.ftdi_setrts(fc.ctx, 0)
if result != 0 {
log.Warningf("FTDI: Open: ftdi_setrts() %v", fc.errorString())
return false
}
fc.status = Open
log.Info("FTDI: Open: Opened the device ok!")
return true
}
const DURATION_PER_FRAME = time.Second / 30.0
const DMX_BREAK = time.Microsecond * 110
const DMX_MAB = time.Microsecond * 16
// const DURATION_PER_FRAME = time.Second
// Once the device is opened this go routine is started to output frames at
// a hopefully consistent rate
func (fc *FtdiContext) goFramePump() {
for {
// log.Debug("goFramePump loop")
// While there are any frames that should update our current reality do that
// There is a possibility here that a caller could overwhelm us with frames to
// update at such a fast rate that we never get around to writing things out.
// That seems unlikely enough that we aren't going to bother to guard against it.
// This whole buffering scheme is really a minimal decoupling thing and we
// don't _expect_ there to be massive overruns. This is just a note for the
// crazy super performant future though, just in case ;)
if fc.status != Open {
log.Warningf("FTDI: Frame pump: Exiting because status is not open")
return
}
// Need a slice that preserves the leading 0
// log.Debug("Drain any pending frames")
currentDest := fc.currentDMX[1:]
Drain:
for {
select {
case frame := <-fc.pendingDMX:
// Copy frames into that slice directly
copy(currentDest, frame)
default:
break Drain
}
}
// Okay the fc.currentDMX is a valid buffer that we want sent out on the wire,
// so we do that
fc.lastWriteAt = time.Now()
// log.Debug("Writing a frame at %v", fc.lastWriteAt)
fc.setBreak(true)
time.Sleep(DMX_BREAK)
fc.setBreak(false)
time.Sleep(DMX_MAB)
b := &fc.currentDMX[0]
// str := C.GoString(C.fake_write(fc.ctx, (*_Ctype_uchar)(b), 513))
// log.Debugf("Fake Write> %v", str)
result := C.ftdi_write_data(fc.ctx, (*C.uchar)(b), 513)
//result := C.ftdi_write_data(fc.ctx, b, 513)
if result < 0 {
// This is bad - throw us into error state
log.Warningf("FTDI: Frame pump: %v", fc.errorString())
fc.enterErrorState()
return
}
// Life is grand, keep trucking
nextFrameAt := fc.lastWriteAt.Add(DURATION_PER_FRAME)
sleepTime := nextFrameAt.Sub(time.Now())
time.Sleep(sleepTime)
}
}
func (fc *FtdiContext) enterErrorState() {
log.Warningf("FTDI: Entering closed state because of error")
fc.status = Stopped
// Set this to now so a regular interval will pass before we try to re-open the device
// and get everything going again
fc.lastOpenAttempt = time.Now()
// Restart the attempt to open goroutine
fc.Start()
}
// This important little guy is needed to implement the proper serial signaling
// so that the fixtures actually find the frames of data!
func (fc *FtdiContext) setBreak(on bool) bool {
var result C.int
if on {
result = C.ftdi_set_line_property2(fc.ctx, C.BITS_8, C.STOP_BIT_2, C.NONE, C.BREAK_ON)
} else {
result = C.ftdi_set_line_property2(fc.ctx, C.BITS_8, C.STOP_BIT_2, C.NONE, C.BREAK_OFF)
}
if result < 0 {
return false
}
return true
}