diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3b2a210d..709073df 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -286,6 +286,11 @@ jobs: -k "${{ env.KEYCHAIN_PASSWORD }}" \ "${{ env.KEYCHAIN }}" + - name: Install Go + uses: actions/setup-go@v5 + with: + go-version: ${{ env.GO_VERSION }} + - name: Install gon for code signing uses: actions/checkout@v4 with: diff --git a/bufferflow.go b/bufferflow.go deleted file mode 100644 index a9fef8e5..00000000 --- a/bufferflow.go +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2022 Arduino SA -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -package main - -// Bufferflow interface -type Bufferflow interface { - Init() - OnIncomingData(data string) // implement this method - Close() // implement this method -} diff --git a/bufferflow_default.go b/bufferflow_default.go deleted file mode 100644 index 959737d5..00000000 --- a/bufferflow_default.go +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2022 Arduino SA -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -package main - -import ( - "encoding/json" - - log "github.com/sirupsen/logrus" -) - -// BufferflowDefault is the default bufferflow, whick means no buffering -type BufferflowDefault struct { - port string - output chan<- []byte - input chan string - done chan bool -} - -// NewBufferflowDefault create a new default bufferflow -func NewBufferflowDefault(port string, output chan<- []byte) *BufferflowDefault { - return &BufferflowDefault{ - port: port, - output: output, - input: make(chan string), - done: make(chan bool), - } -} - -// Init will initialize the bufferflow -func (b *BufferflowDefault) Init() { - log.Println("Initting default buffer flow (which means no buffering)") - go b.consumeInput() -} - -func (b *BufferflowDefault) consumeInput() { -Loop: - for { - select { - case data := <-b.input: - m := SpPortMessage{b.port, data} - message, _ := json.Marshal(m) - b.output <- message - case <-b.done: - break Loop //this is required, a simple break statement would only exit the innermost switch statement - } - } - close(b.input) // close the input channel at the end of the computation -} - -// OnIncomingData will forward the data -func (b *BufferflowDefault) OnIncomingData(data string) { - b.input <- data -} - -// Close will close the bufferflow -func (b *BufferflowDefault) Close() { - b.done <- true - close(b.done) -} diff --git a/bufferflow_timed.go b/bufferflow_timed.go index 36aaf08b..6c5fab04 100644 --- a/bufferflow_timed.go +++ b/bufferflow_timed.go @@ -33,8 +33,8 @@ type BufferflowTimed struct { bufferedOutput string } -// NewBufferflowTimed will create a new timed bufferflow -func NewBufferflowTimed(port string, output chan<- []byte) *BufferflowTimed { +// NewBufferFlowTimed will create a new timed bufferflow +func NewBufferFlowTimed(port string, output chan<- []byte) *BufferflowTimed { return &BufferflowTimed{ port: port, output: output, @@ -48,7 +48,7 @@ func NewBufferflowTimed(port string, output chan<- []byte) *BufferflowTimed { // Init will initialize the bufferflow func (b *BufferflowTimed) Init() { - log.Println("Initting timed buffer flow (output once every 16ms)") + log.Println("Start consuming from serial port (output once every 16ms)") go b.consumeInput() } diff --git a/bufferflow_timedraw.go b/bufferflow_timedraw.go deleted file mode 100644 index 08b34cab..00000000 --- a/bufferflow_timedraw.go +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright 2022 Arduino SA -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -package main - -import ( - "encoding/json" - "time" - - log "github.com/sirupsen/logrus" -) - -// BufferflowTimedRaw sends raw data once every 16ms -type BufferflowTimedRaw struct { - port string - output chan<- []byte - input chan string - done chan bool - ticker *time.Ticker - bufferedOutputRaw []byte - sPortRaw string -} - -// NewBufferflowTimedRaw will create a new raw bufferflow -func NewBufferflowTimedRaw(port string, output chan<- []byte) *BufferflowTimedRaw { - return &BufferflowTimedRaw{ - port: port, - output: output, - input: make(chan string), - done: make(chan bool), - ticker: time.NewTicker(16 * time.Millisecond), - bufferedOutputRaw: nil, - sPortRaw: "", - } -} - -// Init will initialize the bufferflow -func (b *BufferflowTimedRaw) Init() { - log.Println("Initting timed buffer raw flow (output once every 16ms)") - go b.consumeInput() -} - -func (b *BufferflowTimedRaw) consumeInput() { -Loop: - for { - select { - case data := <-b.input: // use the buffer and append data to it - b.bufferedOutputRaw = append(b.bufferedOutputRaw, []byte(data)...) - b.sPortRaw = b.port - case <-b.ticker.C: // after 16ms send the buffered output message - if b.bufferedOutputRaw != nil { - m := SpPortMessageRaw{b.sPortRaw, b.bufferedOutputRaw} - buf, _ := json.Marshal(m) - // since bufferedOutputRaw is a []byte is base64-encoded by json.Marshal() function automatically - b.output <- buf - // reset the buffer and the port - b.bufferedOutputRaw = nil - b.sPortRaw = "" - } - case <-b.done: - break Loop //this is required, a simple break statement would only exit the innermost switch statement - } - } - close(b.input) -} - -// OnIncomingData will forward the data -func (b *BufferflowTimedRaw) OnIncomingData(data string) { - b.input <- data -} - -// Close will close the bufferflow -func (b *BufferflowTimedRaw) Close() { - b.ticker.Stop() - b.done <- true - close(b.done) -} diff --git a/hub.go b/hub.go index a162dd01..81a16912 100755 --- a/hub.go +++ b/hub.go @@ -58,7 +58,7 @@ var h = hub{ const commands = `{ "Commands": [ "list", - "open [bufferAlgorithm: ({default}, timed, timedraw)]", + "open ", "(send, sendnobuf, sendraw) ", "close ", "restart", @@ -146,15 +146,13 @@ func checkCmd(m []byte) { go spErr("Problem converting baud rate " + args[2]) return } - // pass in buffer type now as string. if user does not - // ask for a buffer type pass in empty string - bufferAlgorithm := "default" // use the default buffer if none is specified + + // Ignore extra "buffer type" param for backward compatibility if len(args) > 3 { - // cool. we got a buffer type request - buftype := strings.Replace(args[3], "\n", "", -1) - bufferAlgorithm = buftype + log.Warn(fmt.Sprintf("Unexpected arguments for the open command. Ignored arguments: '%s'.", args[3:])) } - go spHandlerOpen(args[1], baud, bufferAlgorithm) + + go spHandlerOpen(args[1], baud) } else if strings.HasPrefix(sl, "close") { diff --git a/serial.go b/serial.go index 64e5b8f7..f5841457 100755 --- a/serial.go +++ b/serial.go @@ -67,7 +67,7 @@ var sh = serialhub{ func (sh *serialhub) Register(port *serport) { sh.mu.Lock() //log.Print("Registering a port: ", p.portConf.Name) - h.broadcastSys <- []byte("{\"Cmd\":\"Open\",\"Desc\":\"Got register/open on port.\",\"Port\":\"" + port.portConf.Name + "\",\"Baud\":" + strconv.Itoa(port.portConf.Baud) + ",\"BufferType\":\"" + port.BufferType + "\"}") + h.broadcastSys <- []byte("{\"Cmd\":\"Open\",\"Desc\":\"Got register/open on port.\",\"Port\":\"" + port.portConf.Name + "\",\"Baud\":" + strconv.Itoa(port.portConf.Baud) + "}") sh.ports[port] = true sh.mu.Unlock() } diff --git a/serialport.go b/serialport.go index 0d386bbf..9a06c7f9 100755 --- a/serialport.go +++ b/serialport.go @@ -22,7 +22,6 @@ import ( "strconv" "sync/atomic" "time" - "unicode/utf8" log "github.com/sirupsen/logrus" serial "go.bug.st/serial" @@ -57,10 +56,7 @@ type serport struct { // channel containing raw base64 encoded binary data (outbound messages) sendRaw chan string - // Do we have an extra channel/thread to watch our buffer? - BufferType string - //bufferwatcher *BufferflowDummypause - bufferwatcher Bufferflow + bufferFlow *BufferflowTimed } // SpPortMessage is the serial port message @@ -75,15 +71,13 @@ type SpPortMessageRaw struct { D []byte // the data, i.e. G0 X0 Y0 } -func (p *serport) reader(buftype string) { +func (p *serport) reader() { timeCheckOpen := time.Now() - var bufferedCh bytes.Buffer serialBuffer := make([]byte, 1024) for { n, err := p.portIo.Read(serialBuffer) - bufferPart := serialBuffer[:n] //if we detect that port is closing, break out of this for{} loop. if p.isClosing.Load() { @@ -96,39 +90,8 @@ func (p *serport) reader(buftype string) { // read can return legitimate bytes as well as an error // so process the n bytes red, if n > 0 if n > 0 && err == nil { - - log.Print("Read " + strconv.Itoa(n) + " bytes ch: " + string(bufferPart[:n])) - - data := "" - switch buftype { - case "timedraw", "timed": - data = string(bufferPart[:n]) - // give the data to our bufferflow so it can do it's work - // to read/translate the data to see if it wants to block - // writes to the serialport. each bufferflow type will decide - // this on its own based on its logic - p.bufferwatcher.OnIncomingData(data) - case "default": // the bufferbuftype is actually called default 🤷‍♂️ - // save the left out bytes for the next iteration due to UTF-8 encoding - bufferPart = append(bufferedCh.Bytes(), bufferPart[:n]...) - n += len(bufferedCh.Bytes()) - bufferedCh.Reset() - for i, w := 0, 0; i < n; i += w { - runeValue, width := utf8.DecodeRune(bufferPart[i:n]) // try to decode the first i bytes in the buffer (UTF8 runes do not have a fixed length) - if runeValue == utf8.RuneError { - bufferedCh.Write(bufferPart[i:n]) - break - } - if i == n { - bufferedCh.Reset() - } - data += string(runeValue) - w = width - } - p.bufferwatcher.OnIncomingData(data) - default: - log.Panicf("unknown buffer type %s", buftype) - } + log.Print("Read " + strconv.Itoa(n) + " bytes ch: " + string(serialBuffer[:n])) + p.bufferFlow.OnIncomingData(string(serialBuffer[:n])) } // double check that we got characters in the buffer @@ -273,7 +236,7 @@ func (p *serport) writerRaw() { h.broadcastSys <- []byte(msgstr) } -func spHandlerOpen(portname string, baud int, buftype string) { +func spHandlerOpen(portname string, baud int) { log.Print("Inside spHandler") @@ -312,23 +275,10 @@ func spHandlerOpen(portname string, baud int, buftype string) { portConf: conf, portIo: sp, portName: portname, - BufferType: buftype} - - var bw Bufferflow - - switch buftype { - case "timed": - bw = NewBufferflowTimed(portname, h.broadcastSys) - case "timedraw": - bw = NewBufferflowTimedRaw(portname, h.broadcastSys) - case "default": - bw = NewBufferflowDefault(portname, h.broadcastSys) - default: - log.Panicf("unknown buffer type: %s", buftype) } - bw.Init() - p.bufferwatcher = bw + p.bufferFlow = NewBufferFlowTimed(portname, h.broadcastSys) + p.bufferFlow.Init() sh.Register(p) defer sh.Unregister(p) @@ -343,7 +293,7 @@ func spHandlerOpen(portname string, baud int, buftype string) { // this is thread to send to serial port but with base64 decoding go p.writerRaw() - p.reader(buftype) + p.reader() serialPorts.List() } @@ -351,7 +301,7 @@ func spHandlerOpen(portname string, baud int, buftype string) { func (p *serport) Close() { p.isClosing.Store(true) - p.bufferwatcher.Close() + p.bufferFlow.Close() p.portIo.Close() serialPorts.MarkPortAsClosed(p.portName) serialPorts.List() diff --git a/tests/test_ws.py b/tests/test_ws.py index b8004649..2f3ee9fa 100644 --- a/tests/test_ws.py +++ b/tests/test_ws.py @@ -49,77 +49,28 @@ def test_list(socketio, message): running_on_ci(), reason="VMs have no serial ports", ) -def test_open_serial_default(socketio, serial_port, baudrate, message): - general_open_serial(socketio, serial_port, baudrate, message, "default") - - -@pytest.mark.skipif( - running_on_ci(), - reason="VMs have no serial ports", -) -def test_open_serial_timed(socketio, serial_port, baudrate, message): - general_open_serial(socketio, serial_port, baudrate, message, "timed") - - -@pytest.mark.skipif( - running_on_ci(), - reason="VMs have no serial ports", -) -def test_open_serial_timedraw(socketio, serial_port, baudrate, message): - general_open_serial(socketio, serial_port, baudrate, message, "timedraw") - +def test_open_serial(socketio, serial_port, baudrate, message): + general_open_serial(socketio, serial_port, baudrate, message) # NOTE run the following tests with a board connected to the PC and with the sketch found in tests/testdata/SerialEcho.ino on it be sure to change serial_address in conftest.py @pytest.mark.skipif( running_on_ci(), reason="VMs have no serial ports", ) -def test_send_serial_default(socketio, close_port, serial_port, baudrate, message): - general_send_serial(socketio, close_port, serial_port, baudrate, message, "default") - - -@pytest.mark.skipif( - running_on_ci(), - reason="VMs have no serial ports", -) -def test_send_serial_timed(socketio, close_port, serial_port, baudrate, message): - general_send_serial(socketio, close_port, serial_port, baudrate, message, "timed") +def test_send_serial(socketio, close_port, serial_port, baudrate, message): + general_send_serial(socketio, close_port, serial_port, baudrate, message) @pytest.mark.skipif( running_on_ci(), reason="VMs have no serial ports", ) -def test_send_serial_timedraw(socketio, close_port, serial_port, baudrate, message): - general_send_serial(socketio, close_port, serial_port, baudrate, message, "timedraw") +def test_send_emoji_serial(socketio, close_port, serial_port, baudrate, message): + general_send_emoji_serial(socketio, close_port, serial_port, baudrate, message) -@pytest.mark.skipif( - running_on_ci(), - reason="VMs have no serial ports", -) -def test_send_emoji_serial_default(socketio, close_port, serial_port, baudrate, message): - general_send_emoji_serial(socketio, close_port, serial_port, baudrate, message, "default") - - -@pytest.mark.skipif( - running_on_ci(), - reason="VMs have no serial ports", -) -def test_send_emoji_serial_timed(socketio, close_port, serial_port, baudrate, message): - general_send_emoji_serial(socketio, close_port, serial_port, baudrate, message, "timed") - - -@pytest.mark.skipif( - running_on_ci(), - reason="VMs have no serial ports", -) -def test_send_emoji_serial_timedraw(socketio, close_port, serial_port, baudrate, message): - general_send_emoji_serial(socketio, close_port, serial_port, baudrate, message, "timedraw") - - -def general_open_serial(socketio, serial_port, baudrate, message, buffertype): - open_serial_port(socketio, serial_port, baudrate, message, buffertype) +def general_open_serial(socketio, serial_port, baudrate, message): + open_serial_port(socketio, serial_port, baudrate, message) # test the closing of the serial port, we are gonna use close_port for the other tests socketio.emit('command', 'close ' + serial_port) time.sleep(.2) @@ -128,8 +79,8 @@ def general_open_serial(socketio, serial_port, baudrate, message, buffertype): assert any("\"IsOpen\": false," in i for i in message) -def general_send_serial(socketio, close_port, serial_port, baudrate, message, buffertype): - open_serial_port(socketio, serial_port, baudrate, message, buffertype) +def general_send_serial(socketio, close_port, serial_port, baudrate, message): + open_serial_port(socketio, serial_port, baudrate, message) # send the string "ciao" using the serial connection socketio.emit('command', 'send ' + serial_port + ' ciao') time.sleep(1) @@ -137,33 +88,27 @@ def general_send_serial(socketio, close_port, serial_port, baudrate, message, bu # check if the send command has been registered assert any("send " + serial_port + " ciao" in i for i in message) #check if message has been sent back by the connected board - if buffertype == "timedraw": - output = decode_output(extract_serial_data(message)) - elif buffertype in ("default", "timed"): - output = extract_serial_data(message) + output = extract_serial_data(message) assert "ciao" in output # the serial connection is closed by close_port() fixture: even if in case of test failure -def general_send_emoji_serial(socketio, close_port, serial_port, baudrate, message, buffertype): - open_serial_port(socketio, serial_port, baudrate, message, buffertype) +def general_send_emoji_serial(socketio, close_port, serial_port, baudrate, message): + open_serial_port(socketio, serial_port, baudrate, message) # send a lot of emoji: they can be messed up socketio.emit('command', 'send ' + serial_port + ' /"🧀🧀🧀🧀🧀🧀🧀🧀🧀🧀/"') time.sleep(1) print(message) # check if the send command has been registered assert any("send " + serial_port + " /\"🧀🧀🧀🧀🧀🧀🧀🧀🧀🧀/\"" in i for i in message) - if buffertype == "timedraw": - output = decode_output(extract_serial_data(message)) - elif buffertype in ("default", "timed"): - output = extract_serial_data(message) + output = extract_serial_data(message) assert "/\"🧀🧀🧀🧀🧀🧀🧀🧀🧀🧀/\"" in output # the serial connection is closed by close_port() fixture: even if in case of test failure -def open_serial_port(socketio, serial_port, baudrate, message, buffertype): - #open a new serial connection with the specified buffertype - socketio.emit('command', 'open ' + serial_port + ' ' + baudrate + ' ' + buffertype) +def open_serial_port(socketio, serial_port, baudrate, message): + #open a new serial connection + socketio.emit('command', 'open ' + serial_port + ' ' + baudrate) # give time to the message var to be filled time.sleep(.5) print(message) @@ -176,7 +121,7 @@ def open_serial_port(socketio, serial_port, baudrate, message, buffertype): reason="VMs have no serial ports", ) def test_sendraw_serial(socketio, close_port, serial_port, baudrate, message): - open_serial_port(socketio, serial_port, baudrate, message, "timedraw") + open_serial_port(socketio, serial_port, baudrate, message) #test with bytes integers = [1, 2, 3, 4, 5] bytes_array=bytearray(integers) @@ -202,7 +147,7 @@ def extract_serial_data(msg): serial_data+=json.loads(i)["D"] print("serialdata:"+serial_data) return serial_data - + def decode_output(raw_output): # print(raw_output) base64_bytes = raw_output.encode('ascii') #encode rawoutput message into a bytes-like object