forked from tarm/serial
/
serial.go
343 lines (315 loc) · 8.74 KB
/
serial.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
package serial
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"regexp"
"time"
)
// End of line character (AKA EOL), newline character (ASCII 10, CR, '\n'). is used by default.
const EOL_DEFAULT byte = '\n'
/*******************************************************************************************
******************************* TYPE DEFINITIONS ****************************************
*******************************************************************************************/
type SerialPort struct {
port io.ReadWriteCloser
name string
baud int
eol uint8
rxChar chan byte
closeReqChann chan bool
closeAckChann chan error
buff *bytes.Buffer
logger *log.Logger
portIsOpen bool
Verbose bool
// openPort func(port string, baud int) (io.ReadWriteCloser, error)
}
/*******************************************************************************************
******************************** BASIC FUNCTIONS ****************************************
*******************************************************************************************/
func New() *SerialPort {
// Create new file
file, err := os.OpenFile(fmt.Sprintf("log_serial_%d.txt", time.Now().Unix()), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
log.Fatalln("Failed to open log file", ":", err)
}
multi := io.MultiWriter(file, os.Stdout)
return &SerialPort{
logger: log.New(multi, "PREFIX: ", log.Ldate|log.Ltime),
eol: EOL_DEFAULT,
buff: bytes.NewBuffer(make([]uint8, 256)),
Verbose: true,
}
}
func (sp *SerialPort) Open(name string, baud int, timeout ...time.Duration) error {
// Check if port is open
if sp.portIsOpen {
return fmt.Errorf("\"%s\" is already open", name)
}
var readTimeout time.Duration
if len(timeout) > 0 {
readTimeout = timeout[0]
}
// Open serial port
comPort, err := openPort(name, baud, readTimeout)
if err != nil {
return fmt.Errorf("Unable to open port \"%s\" - %s", name, err)
}
// Open port succesfull
sp.name = name
sp.baud = baud
sp.port = comPort
sp.portIsOpen = true
sp.buff.Reset()
// Open channels
sp.rxChar = make(chan byte)
// Enable threads
go sp.readSerialPort()
go sp.processSerialPort()
sp.logger.SetPrefix(fmt.Sprintf("[%s] ", sp.name))
sp.log("Serial port %s@%d open", sp.name, sp.baud)
return nil
}
// This method close the current Serial Port.
func (sp *SerialPort) Close() error {
if sp.portIsOpen {
sp.portIsOpen = false
close(sp.rxChar)
sp.log("Serial port %s closed", sp.name)
return sp.port.Close()
}
return nil
}
// This method prints data trough the serial port.
func (sp *SerialPort) Write(data []byte) (n int, err error) {
if sp.portIsOpen {
n, err = sp.port.Write(data)
if err != nil {
// Do nothing
} else {
sp.log("Tx >> %s", string(data))
}
} else {
err = fmt.Errorf("Serial port is not open")
}
return
}
// This method prints data trough the serial port.
func (sp *SerialPort) Print(str string) error {
if sp.portIsOpen {
_, err := sp.port.Write([]byte(str))
if err != nil {
return err
} else {
sp.log("Tx >> %s", str)
}
} else {
return fmt.Errorf("Serial port is not open")
}
return nil
}
// Prints data to the serial port as human-readable ASCII text followed by a carriage return character
// (ASCII 13, CR, '\r') and a newline character (ASCII 10, LF, '\n').
func (sp *SerialPort) Println(str string) error {
return sp.Print(str + "\r\n")
}
// Printf formats according to a format specifier and print data trough the serial port.
func (sp *SerialPort) Printf(format string, args ...interface{}) error {
str := format
if len(args) > 0 {
str = fmt.Sprintf(format, args...)
}
return sp.Print(str)
}
//This method send a binary file trough the serial port. If EnableLog is active then this method will log file related data.
func (sp *SerialPort) SendFile(filepath string) error {
// Aux Vars
sentBytes := 0
q := 512
data := []byte{}
// Read file
file, err := ioutil.ReadFile(filepath)
if err != nil {
sp.log("DBG >> %s", "Invalid filepath")
return err
} else {
fileSize := len(file)
sp.log("INF >> %s", "File size is %d bytes", fileSize)
for sentBytes <= fileSize {
//Try sending slices of less or equal than 512 bytes at time
if len(file[sentBytes:]) > q {
data = file[sentBytes:(sentBytes + q)]
} else {
data = file[sentBytes:]
}
// Write binaries
_, err := sp.port.Write(data)
if err != nil {
sp.log("DBG >> %s", "Error while sending the file")
return err
} else {
sentBytes += q
time.Sleep(time.Millisecond * 100)
}
}
}
//Encode data to send
return nil
}
// Read the first byte of the serial buffer.
func (sp *SerialPort) Read() (byte, error) {
if sp.portIsOpen {
return sp.buff.ReadByte()
} else {
return 0x00, fmt.Errorf("Serial port is not open")
}
return 0x00, nil
}
// Read first available line from serial port buffer.
//
// Line is delimited by the EOL character, newline character (ASCII 10, LF, '\n') is used by default.
//
// The text returned from ReadLine does not include the line end ("\r\n" or '\n').
func (sp *SerialPort) ReadLine() (string, error) {
if sp.portIsOpen {
line, err := sp.buff.ReadString(sp.eol)
if err != nil {
return "", err
} else {
return removeEOL(line), nil
}
} else {
return "", fmt.Errorf("Serial port is not open")
}
return "", nil
}
// Wait for a defined regular expression for a defined amount of time.
func (sp *SerialPort) WaitForRegexTimeout(exp string, timeout time.Duration) (string, error) {
if sp.portIsOpen {
//Decode received data
timeExpired := false
regExpPatttern := regexp.MustCompile(exp)
//Timeout structure
c1 := make(chan string, 1)
go func() {
sp.log("INF >> Waiting for RegExp: \"%s\"", exp)
result := []string{}
for !timeExpired {
line, err := sp.ReadLine()
if err != nil {
// Do nothing
} else {
result = regExpPatttern.FindAllString(line, -1)
if len(result) > 0 {
c1 <- result[0]
break
}
}
}
}()
select {
case data := <-c1:
sp.log("INF >> The RegExp: \"%s\"", exp)
sp.log("INF >> Has been matched: \"%s\"", data)
return data, nil
case <-time.After(timeout):
timeExpired = true
sp.log("INF >> Unable to match RegExp: \"%s\"", exp)
return "", fmt.Errorf("Timeout expired")
}
} else {
return "", fmt.Errorf("Serial port is not open")
}
return "", nil
}
// Available return the total number of available unread bytes on the serial buffer.
func (sp *SerialPort) Available() int {
return sp.buff.Len()
}
// Change end of line character (AKA EOL), newline character (ASCII 10, LF, '\n') is used by default.
func (sp *SerialPort) EOL(c byte) {
sp.eol = c
}
/*******************************************************************************************
****************************** PRIVATE FUNCTIONS ****************************************
*******************************************************************************************/
func (sp *SerialPort) readSerialPort() {
rxBuff := make([]byte, 256)
for sp.portIsOpen {
n, _ := sp.port.Read(rxBuff)
// Write data to serial buffer
sp.buff.Write(rxBuff[:n])
for _, b := range rxBuff[:n] {
if sp.portIsOpen {
sp.rxChar <- b
}
}
}
}
func (sp *SerialPort) processSerialPort() {
screenBuff := make([]byte, 0)
var lastRxByte byte
for {
if sp.portIsOpen {
lastRxByte = <-sp.rxChar
// Print received lines
switch lastRxByte {
case sp.eol:
// EOL - Print received data
sp.log("Rx << %s", string(append(screenBuff, lastRxByte)))
screenBuff = make([]byte, 0) //Clean buffer
break
default:
screenBuff = append(screenBuff, lastRxByte)
}
} else {
break
}
}
}
func (sp *SerialPort) log(format string, a ...interface{}) {
if sp.Verbose {
sp.logger.Printf(format, a...)
}
}
func removeEOL(line string) string {
var data []byte
// Remove CR byte "\r"
for _, b := range []byte(line) {
switch b {
case '\r':
// Do nothing
case '\n':
// Do nothing
default:
data = append(data, b)
}
}
return string(data)
}
// Converts the timeout values for Linux / POSIX systems
func posixTimeoutValues(readTimeout time.Duration) (vmin uint8, vtime uint8) {
const MAXUINT8 = 1<<8 - 1 // 255
// set blocking / non-blocking read
var minBytesToRead uint8 = 1
var readTimeoutInDeci int64
if readTimeout > 0 {
// EOF on zero read
minBytesToRead = 0
// convert timeout to deciseconds as expected by VTIME
readTimeoutInDeci = (readTimeout.Nanoseconds() / 1e6 / 100)
// capping the timeout
if readTimeoutInDeci < 1 {
// min possible timeout 1 Deciseconds (0.1s)
readTimeoutInDeci = 1
} else if readTimeoutInDeci > MAXUINT8 {
// max possible timeout is 255 deciseconds (25.5s)
readTimeoutInDeci = MAXUINT8
}
}
return minBytesToRead, uint8(readTimeoutInDeci)
}