Skip to content

Commit a6fdfea

Browse files
authored
gRPC interface to monitors (#286)
* added a monitor service * added generic monitor type and its serial impl * added monitor service implementation * gracefully handle connection errors * more idiomatic definition * keep directory structure from the archive * move default value closer to the function using it * test the serial monitor * bump monkey
1 parent 19f5f9f commit a6fdfea

File tree

11 files changed

+798
-21
lines changed

11 files changed

+798
-21
lines changed

Diff for: Taskfile.yml

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ tasks:
55
desc: Compile protobuf definitions
66
cmds:
77
- '{{ default "protoc" .PROTOC_BINARY }} --proto_path=rpc --go_out=plugins=grpc,paths=source_relative:rpc ./rpc/commands/*.proto'
8+
- '{{ default "protoc" .PROTOC_BINARY }} --proto_path=rpc --go_out=plugins=grpc,paths=source_relative:rpc ./rpc/monitor/*.proto'
89

910
build:
1011
desc: Build the project

Diff for: appveyor.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ stack: go 1.12
1919
install:
2020
# install the task executor
2121
- curl -o task.zip -LO https://github.com/go-task/task/releases/download/v2.6.0/task_windows_amd64.zip
22-
- 7z e task.zip -o%GOPATH%\bin
22+
- 7z x task.zip -o%GOPATH%\bin
2323
# golang dependencies needed at test time
2424
- go get github.com/golangci/govet
2525
- go get golang.org/x/lint/golint
@@ -30,7 +30,7 @@ install:
3030
# because of this: https://github.com/protocolbuffers/protobuf/issues/3957
3131
- go get github.com/golang/protobuf/protoc-gen-go
3232
- curl -o protoc.zip -LO https://github.com/protocolbuffers/protobuf/releases/download/v3.4.0/protoc-3.4.0-win32.zip
33-
- 7z e protoc.zip -o%PROTOC_PATH%
33+
- 7z x protoc.zip -o%PROTOC_PATH%
3434

3535
test_script:
3636
# Check if the Go code is properly formatted and run the linter

Diff for: arduino/monitors/serial.go

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// This file is part of arduino-cli.
2+
//
3+
// Copyright 2019 ARDUINO SA (http://www.arduino.cc/)
4+
//
5+
// This software is released under the GNU General Public License version 3,
6+
// which covers the main part of arduino-cli.
7+
// The terms of this license can be found at:
8+
// https://www.gnu.org/licenses/gpl-3.0.en.html
9+
//
10+
// You can be released from the requirements of the above licenses by purchasing
11+
// a commercial license. Buying such a license is mandatory if you want to modify or
12+
// otherwise use the software for commercial activities involving the Arduino
13+
// software without disclosing the source code of your own applications. To purchase
14+
// a commercial license, send an email to [email protected].
15+
16+
package monitors
17+
18+
import (
19+
"github.com/pkg/errors"
20+
serial "go.bug.st/serial.v1"
21+
)
22+
23+
const (
24+
defaultBaudRate = 9600
25+
)
26+
27+
// SerialMonitor is a monitor for serial ports
28+
type SerialMonitor struct {
29+
port serial.Port
30+
}
31+
32+
// OpenSerialMonitor creates a monitor instance for a serial port
33+
func OpenSerialMonitor(portName string, baudRate int) (*SerialMonitor, error) {
34+
// use default baud rate if not provided
35+
if baudRate == 0 {
36+
baudRate = defaultBaudRate
37+
}
38+
39+
port, err := serial.Open(portName, &serial.Mode{BaudRate: baudRate})
40+
if err != nil {
41+
return nil, errors.Wrap(err, "error opening serial monitor")
42+
}
43+
44+
return &SerialMonitor{
45+
port: port,
46+
}, nil
47+
}
48+
49+
// Close the connection
50+
func (mon *SerialMonitor) Close() error {
51+
return mon.port.Close()
52+
}
53+
54+
// Read bytes from the port
55+
func (mon *SerialMonitor) Read(bytes []byte) (int, error) {
56+
return mon.port.Read(bytes)
57+
}
58+
59+
// Write bytes to the port
60+
func (mon *SerialMonitor) Write(bytes []byte) (int, error) {
61+
return mon.port.Write(bytes)
62+
}

Diff for: arduino/monitors/types.go

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// This file is part of arduino-cli.
2+
//
3+
// Copyright 2019 ARDUINO SA (http://www.arduino.cc/)
4+
//
5+
// This software is released under the GNU General Public License version 3,
6+
// which covers the main part of arduino-cli.
7+
// The terms of this license can be found at:
8+
// https://www.gnu.org/licenses/gpl-3.0.en.html
9+
//
10+
// You can be released from the requirements of the above licenses by purchasing
11+
// a commercial license. Buying such a license is mandatory if you want to modify or
12+
// otherwise use the software for commercial activities involving the Arduino
13+
// software without disclosing the source code of your own applications. To purchase
14+
// a commercial license, send an email to [email protected].
15+
16+
package monitors
17+
18+
import (
19+
"io"
20+
)
21+
22+
// Monitor is the interface implemented by different device monitors
23+
type Monitor interface {
24+
io.ReadWriteCloser
25+
}

Diff for: cli/daemon/daemon.go

+9-2
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ import (
2727

2828
"github.com/arduino/arduino-cli/cli/globals"
2929
"github.com/arduino/arduino-cli/commands/daemon"
30-
rpc "github.com/arduino/arduino-cli/rpc/commands"
30+
srv_commands "github.com/arduino/arduino-cli/rpc/commands"
31+
srv_monitor "github.com/arduino/arduino-cli/rpc/monitor"
3132
"github.com/spf13/cobra"
3233
"google.golang.org/grpc"
3334
)
@@ -59,14 +60,20 @@ func runDaemonCommand(cmd *cobra.Command, args []string) {
5960
globals.VersionInfo.VersionString, runtime.GOARCH, runtime.GOOS, runtime.Version(), globals.VersionInfo.Commit)
6061
headers := http.Header{"User-Agent": []string{userAgentValue}}
6162

63+
// register the commands service
6264
coreServer := daemon.ArduinoCoreServerImpl{
6365
DownloaderHeaders: headers,
6466
VersionString: globals.VersionInfo.VersionString,
6567
Config: globals.Config,
6668
}
67-
rpc.RegisterArduinoCoreServer(s, &coreServer)
69+
srv_commands.RegisterArduinoCoreServer(s, &coreServer)
70+
71+
// register the monitors service
72+
srv_monitor.RegisterMonitorServer(s, &daemon.MonitorService{})
73+
6874
if err := s.Serve(lis); err != nil {
6975
log.Fatalf("failed to serve: %v", err)
7076
}
77+
7178
fmt.Println("Done serving")
7279
}

Diff for: commands/daemon/monitor.go

+134
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
// This file is part of arduino-cli.
2+
//
3+
// Copyright 2019 ARDUINO SA (http://www.arduino.cc/)
4+
//
5+
// This software is released under the GNU General Public License version 3,
6+
// which covers the main part of arduino-cli.
7+
// The terms of this license can be found at:
8+
// https://www.gnu.org/licenses/gpl-3.0.en.html
9+
//
10+
// You can be released from the requirements of the above licenses by purchasing
11+
// a commercial license. Buying such a license is mandatory if you want to
12+
// modify or otherwise use the software for commercial activities involving the
13+
// Arduino software without disclosing the source code of your own applications.
14+
// To purchase a commercial license, send an email to [email protected].
15+
16+
package daemon
17+
18+
import (
19+
"fmt"
20+
"io"
21+
22+
"github.com/arduino/arduino-cli/arduino/monitors"
23+
rpc "github.com/arduino/arduino-cli/rpc/monitor"
24+
)
25+
26+
// MonitorService implements the `Monitor` service
27+
type MonitorService struct{}
28+
29+
// StreamingOpen returns a stream response that can be used to fetch data from the
30+
// monitor target. The first message passed through the `StreamingOpenReq` must
31+
// contain monitor configuration params, not data.
32+
func (s *MonitorService) StreamingOpen(stream rpc.Monitor_StreamingOpenServer) error {
33+
// grab the first message
34+
msg, err := stream.Recv()
35+
if err != nil {
36+
return err
37+
}
38+
39+
// ensure it's a config message and not data
40+
config := msg.GetMonitorConfig()
41+
if config == nil {
42+
return fmt.Errorf("first message must contain monitor configuration, not data")
43+
}
44+
45+
// select which type of monitor we need
46+
var mon monitors.Monitor
47+
switch config.GetType() {
48+
case rpc.MonitorConfig_SERIAL:
49+
// grab port speed from additional config data
50+
var baudRate float64
51+
addCfg := config.GetAdditionalConfig()
52+
for k, v := range addCfg.GetFields() {
53+
if k == "BaudRate" {
54+
baudRate = v.GetNumberValue()
55+
break
56+
}
57+
}
58+
59+
// get the Monitor instance
60+
var err error
61+
if mon, err = monitors.OpenSerialMonitor(config.GetTarget(), int(baudRate)); err != nil {
62+
return err
63+
}
64+
}
65+
66+
// we'll use these channels to communicate with the goroutines
67+
// handling the stream and the target respectively
68+
streamClosed := make(chan error)
69+
targetClosed := make(chan error)
70+
71+
// now we can read the other messages and re-route to the monitor...
72+
go func() {
73+
for {
74+
msg, err := stream.Recv()
75+
if err == io.EOF {
76+
// stream was closed
77+
streamClosed <- nil
78+
break
79+
}
80+
81+
if err != nil {
82+
// error reading from stream
83+
streamClosed <- err
84+
break
85+
}
86+
87+
if _, err := mon.Write(msg.GetData()); err != nil {
88+
// error writing to target
89+
targetClosed <- err
90+
break
91+
}
92+
}
93+
}()
94+
95+
// ...and read from the monitor and forward to the output stream
96+
go func() {
97+
buf := make([]byte, 8)
98+
for {
99+
n, err := mon.Read(buf)
100+
if err != nil {
101+
// error reading from target
102+
targetClosed <- err
103+
break
104+
}
105+
106+
if n == 0 {
107+
// target was closed
108+
targetClosed <- nil
109+
break
110+
}
111+
112+
if err = stream.Send(&rpc.StreamingOpenResp{
113+
Data: buf[:n],
114+
}); err != nil {
115+
// error sending to stream
116+
streamClosed <- err
117+
break
118+
}
119+
}
120+
}()
121+
122+
// let goroutines route messages from/to the monitor
123+
// until either the client closes the stream or the
124+
// monitor target is closed
125+
for {
126+
select {
127+
case err := <-streamClosed:
128+
mon.Close()
129+
return err
130+
case err := <-targetClosed:
131+
return err
132+
}
133+
}
134+
}

0 commit comments

Comments
 (0)