Skip to content

Commit 791b03e

Browse files
committed
add sendraw command to send base64 encoded bytes, add tests (for send raw and for open/close port)
1 parent d704dd2 commit 791b03e

File tree

6 files changed

+172
-38
lines changed

6 files changed

+172
-38
lines changed

go.sum

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,7 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3
391391
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
392392
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
393393
golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
394+
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425 h1:VvQyQJN0tSuecqgcIxMWnnfG5kSmgy9KZR9sW3W5QeA=
394395
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
395396
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
396397
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=

hub.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,7 @@ const commands = `{
4444
"Commands": [
4545
"list",
4646
"open <portName> <baud> [bufferAlgorithm: ({default}, timed, timedraw)]",
47-
"send <portName> <cmd>",
48-
"sendnobuf <portName> <cmd>",
47+
"(send, sendnobuf, sendraw) <portName> <cmd>",
4948
"close <portName>",
5049
"restart",
5150
"exit",
@@ -160,7 +159,7 @@ func checkCmd(m []byte) {
160159
}()
161160

162161
} else if strings.HasPrefix(sl, "send") {
163-
// will catch send and sendnobuf
162+
// will catch send and sendnobuf and sendraw
164163
go spWrite(s)
165164
} else if strings.HasPrefix(sl, "list") {
166165
go spList(false)

serial.go

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import (
1414
type writeRequest struct {
1515
p *serport
1616
d string
17-
buffer bool
17+
buffer string
1818
}
1919

2020
type serialhub struct {
@@ -89,13 +89,15 @@ func (sh *serialhub) run() {
8989
}
9090

9191
func write(wr writeRequest) {
92-
if wr.buffer {
93-
//log.Println("Send was normal send, so sending to wr.p.sendBuffered")
92+
switch wr.buffer {
93+
case "send":
9494
wr.p.sendBuffered <- wr.d
95-
} else {
96-
//log.Println("Send was sendnobuf, so sending to wr.p.sendNoBuf")
97-
wr.p.sendNoBuf <- wr.d
95+
case "sendnobuf":
96+
wr.p.sendNoBuf <- []byte(wr.d)
97+
case "sendraw":
98+
wr.p.sendRaw <- wr.d
9899
}
100+
// no default since we alredy verified in spWrite()
99101
}
100102

101103
// spList broadcasts a Json representation of the ports found
@@ -274,13 +276,13 @@ func spWrite(arg string) {
274276
var wr writeRequest
275277
wr.p = myport
276278

277-
// see if args[0] is send or sendnobuf
278-
if args[0] != "sendnobuf" {
279-
// we were just given a "send" so buffer it
280-
wr.buffer = true
281-
} else {
282-
//log.Println("sendnobuf specified so wr.buffer is false")
283-
wr.buffer = false
279+
// see if args[0] is send or sendnobuf or sendraw
280+
switch args[0] {
281+
case "send", "sendnobuf", "sendraw":
282+
wr.buffer = args[0]
283+
default:
284+
spErr("Unsupported send command:" + args[0] + ". Please specify a valid one")
285+
return
284286
}
285287

286288
// include newline or not in the write? that is the question.

serialport.go

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package main
22

33
import (
44
"bytes"
5+
"encoding/base64"
56
"io"
67
"strconv"
78
"time"
@@ -38,6 +39,9 @@ type serport struct {
3839
// unbuffered channel of outbound messages that bypass internal serial port buffer
3940
sendNoBuf chan []byte
4041

42+
// channel containing raw base64 encoded binary data (outbound messages)
43+
sendRaw chan string
44+
4145
// Do we have an extra channel/thread to watch our buffer?
4246
BufferType string
4347
//bufferwatcher *BufferflowDummypause
@@ -222,6 +226,46 @@ func (p *serport) writerNoBuf() {
222226
spList(false)
223227
}
224228

229+
// this method runs as its own thread because it's instantiated
230+
// as a "go" method. so if it blocks inside, it is ok
231+
func (p *serport) writerRaw() {
232+
// this method can panic if user closes serial port and something is
233+
// in BlockUntilReady() and then a send occurs on p.sendNoBuf
234+
235+
defer func() {
236+
if e := recover(); e != nil {
237+
log.Println("Got panic: ", e)
238+
}
239+
}()
240+
241+
// this for loop blocks on p.sendRaw until that channel
242+
// sees something come in
243+
for data := range p.sendRaw {
244+
245+
// Decode stuff
246+
sDec, err := base64.StdEncoding.DecodeString(data)
247+
if err != nil {
248+
log.Println("Decoding error:", err)
249+
}
250+
log.Println(string(sDec))
251+
252+
// we want to block here if we are being asked to pause.
253+
goodToGo, _ := p.bufferwatcher.BlockUntilReady(string(data), "")
254+
255+
if goodToGo == false {
256+
log.Println("We got back from BlockUntilReady() but apparently we must cancel this cmd")
257+
// since we won't get a buffer decrement in p.sendNoBuf, we must do it here
258+
p.itemsInBuffer--
259+
} else {
260+
// send to the non-buffered serial port writer
261+
p.sendNoBuf <- sDec
262+
}
263+
}
264+
msgstr := "writerRaw just got closed. make sure you make a new one. port:" + p.portConf.Name
265+
log.Println(msgstr)
266+
h.broadcastSys <- []byte(msgstr)
267+
}
268+
225269
func spHandlerOpen(portname string, baud int, buftype string) {
226270

227271
log.Print("Inside spHandler")
@@ -254,7 +298,7 @@ func spHandlerOpen(portname string, baud int, buftype string) {
254298
log.Print("Opened port successfully")
255299
//p := &serport{send: make(chan []byte, 256), portConf: conf, portIo: sp}
256300
// we can go up to 256,000 lines of gcode in the buffer
257-
p := &serport{sendBuffered: make(chan string, 256000), sendNoBuf: make(chan []byte), portConf: conf, portIo: sp, BufferType: buftype}
301+
p := &serport{sendBuffered: make(chan string, 256000), sendNoBuf: make(chan []byte), sendRaw: make(chan string), portConf: conf, portIo: sp, BufferType: buftype}
258302

259303
var bw Bufferflow
260304

@@ -282,6 +326,9 @@ func spHandlerOpen(portname string, baud int, buftype string) {
282326
go p.writerBuffered()
283327
// this is thread to send to serial port regardless of block
284328
go p.writerNoBuf()
329+
// this is thread to send to serial port but with base64 decoding
330+
go p.writerRaw()
331+
285332
p.reader(buftype)
286333

287334
spListDual(false)

test/conftest.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,21 @@ def socketio(base_url, agent):
5151
sio.connect(base_url)
5252
yield sio
5353
sio.disconnect()
54+
55+
@pytest.fixture(scope="session")
56+
def serial_port():
57+
return "/dev/ttyACM0" # maybe this could be enhanced by calling arduino-cli
58+
59+
@pytest.fixture(scope="session")
60+
def baudrate():
61+
return "9600"
62+
63+
# open_port cannot be coced as a fixture because of the buffertype parameter
64+
65+
# at the end of the test closes the serial port
66+
@pytest.fixture(scope="function")
67+
def close_port(socketio, serial_port):
68+
yield socketio
69+
socketio.emit('command', 'close ' + serial_port)
70+
time.sleep(.5)
71+

test/test_ws.py

Lines changed: 88 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -20,39 +20,84 @@ def test_list(socketio):
2020
assert any("Ports" in i for i in message)
2121
assert any("Network" in i for i in message)
2222

23-
# NOTE run the following tests on linux with a board connected to the PC and with the sketch found in test/testdata/SerialEcho.ino on it
23+
# NOTE run the following tests with a board connected to the PC
2424
@pytest.mark.skipif(
2525
running_on_ci(),
2626
reason="VMs have no serial ports",
2727
)
28-
def test_open_serial_default(socketio):
29-
general_test_serial(socketio, "default")
28+
def test_open_serial_default(socketio, serial_port, baudrate):
29+
general_open_serial(socketio, serial_port, baudrate, "default")
3030

3131

3232
@pytest.mark.skipif(
3333
running_on_ci(),
3434
reason="VMs have no serial ports",
3535
)
36-
def test_open_serial_timed(socketio):
37-
general_test_serial(socketio, "timed")
36+
def test_open_serial_timed(socketio, serial_port, baudrate):
37+
general_open_serial(socketio, serial_port, baudrate, "timed")
3838

3939

4040
@pytest.mark.skipif(
4141
running_on_ci(),
4242
reason="VMs have no serial ports",
4343
)
44-
def test_open_serial_timedraw(socketio):
45-
general_test_serial(socketio, "timedraw")
44+
def test_open_serial_timedraw(socketio, serial_port, baudrate):
45+
general_open_serial(socketio, serial_port, baudrate, "timedraw")
4646

4747

48-
def general_test_serial(socketio, buffertype):
49-
port = "/dev/ttyACM0"
48+
# NOTE run the following tests with a board connected to the PC and with the sketch found in test/testdata/SerialEcho.ino on it be sure to change serial_address in conftest.py
49+
@pytest.mark.skipif(
50+
running_on_ci(),
51+
reason="VMs have no serial ports",
52+
)
53+
def test_send_serial_default(socketio, close_port, serial_port, baudrate):
54+
general_send_serial(socketio, close_port, serial_port, baudrate, "default")
55+
56+
57+
@pytest.mark.skipif(
58+
running_on_ci(),
59+
reason="VMs have no serial ports",
60+
)
61+
def test_send_serial_timed(socketio, close_port, serial_port, baudrate):
62+
general_send_serial(socketio, close_port, serial_port, baudrate, "timed")
63+
64+
65+
@pytest.mark.skipif(
66+
running_on_ci(),
67+
reason="VMs have no serial ports",
68+
)
69+
def test_send_serial_timedraw(socketio, close_port, serial_port, baudrate):
70+
general_send_serial(socketio, close_port, serial_port, baudrate, "timedraw")
71+
72+
73+
def general_open_serial(socketio, serial_port, baudrate, buffertype):
74+
global message
75+
message = []
76+
# in message var we will find the "response"
77+
socketio.on('message', message_handler)
78+
socketio.emit('command', 'open ' + serial_port + ' ' + baudrate + ' ' + buffertype)
79+
# give time to the message var to be filled
80+
time.sleep(.5)
81+
print(message)
82+
# the serial connection should be open now
83+
assert any("\"IsOpen\": true" in i for i in message)
84+
85+
# close the serial port
86+
socketio.emit('command', 'close ' + serial_port)
87+
time.sleep(.2)
88+
print (message)
89+
#check if port has been closed
90+
assert any("\"IsOpen\": false," in i for i in message)
91+
92+
93+
94+
def general_send_serial(socketio, close_port, serial_port, baudrate, buffertype):
5095
global message
5196
message = []
5297
#in message var we will find the "response"
5398
socketio.on('message', message_handler)
5499
#open a new serial connection with the specified buffertype, if buffertype is empty it will use the default one
55-
socketio.emit('command', 'open ' + port + ' 9600 ' + buffertype)
100+
socketio.emit('command', 'open ' + serial_port + ' ' + baudrate + ' ' + buffertype)
56101
# give time to the message var to be filled
57102
time.sleep(.5)
58103
print(message)
@@ -61,11 +106,11 @@ def general_test_serial(socketio, buffertype):
61106

62107
#test with string
63108
# send the string "ciao" using the serial connection
64-
socketio.emit('command', 'send ' + port + ' /"ciao/"')
109+
socketio.emit('command', 'send ' + serial_port + ' /"ciao/"')
65110
time.sleep(1)
66111
print(message)
67112
# check if the send command has been registered
68-
assert any("send " + port + " /\"ciao/\"" in i for i in message)
113+
assert any("send " + serial_port + " /\"ciao/\"" in i for i in message)
69114
#check if message has been sent back by the connected board
70115
if buffertype == "timedraw":
71116
output = decode_output(extract_serial_data(message))
@@ -76,23 +121,45 @@ def general_test_serial(socketio, buffertype):
76121
#test with emoji
77122
message = [] # reinitialize the message buffer to have a clean situation
78123
# send a lot of emoji: they can be messed up
79-
socketio.emit('command', 'send ' + port + ' /"🧀🧀🧀🧀🧀🧀🧀🧀🧀🧀/"')
80-
time.sleep(.2)
124+
socketio.emit('command', 'send ' + serial_port + ' /"🧀🧀🧀🧀🧀🧀🧀🧀🧀🧀/"')
125+
time.sleep(.5)
81126
print(message)
82127
# check if the send command has been registered
83-
assert any("send " + port + " /\"🧀🧀🧀🧀🧀🧀🧀🧀🧀🧀/\"" in i for i in message)
128+
assert any("send " + serial_port + " /\"🧀🧀🧀🧀🧀🧀🧀🧀🧀🧀/\"" in i for i in message)
84129
if buffertype == "timedraw":
85130
output = decode_output(extract_serial_data(message))
86131
elif buffertype in ("default", "timed"):
87132
output = extract_serial_data(message)
88133
assert "/\"🧀🧀🧀🧀🧀🧀🧀🧀🧀🧀/\"" in output
134+
# the serial connection is closed by close_port() fixture: even if in case of test failure
89135

90-
#finally close the serial port
91-
socketio.emit('command', 'close ' + port)
92-
time.sleep(.2)
93-
print (message)
94-
#check if port has been closed
95-
assert any("\"IsOpen\": false," in i for i in message)
136+
137+
def test_sendraw_serial(socketio, close_port, serial_port, baudrate):
138+
global message
139+
message = []
140+
#in message var we will find the "response"
141+
socketio.on('message', message_handler)
142+
#open a new serial connection with the specified buffertype, if buffertype is empty it will use the default one
143+
socketio.emit('command', 'open ' + serial_port + ' ' + baudrate + ' timedraw')
144+
# give time to the message var to be filled
145+
time.sleep(.5)
146+
print(message)
147+
# the serial connection should be open now
148+
assert any("\"IsOpen\": true" in i for i in message)
149+
150+
#test with bytes
151+
integers = [1, 2, 3, 4, 5]
152+
bytes_array=bytearray(integers)
153+
encoded_integers = base64.b64encode(bytes_array).decode('ascii')
154+
socketio.emit('command', 'sendraw ' + serial_port + ' ' + encoded_integers)
155+
time.sleep(1)
156+
print(message)
157+
# check if the send command has been registered
158+
assert any(("sendraw " + serial_port + ' ' + encoded_integers) in i for i in message)
159+
#check if message has been sent back by the connected board
160+
output = extract_serial_data(message) # TODO use decode_output()
161+
print (output)
162+
assert encoded_integers in output
96163

97164

98165
# callback called by socketio when a message is received

0 commit comments

Comments
 (0)