Skip to content

Commit

Permalink
gRPC interface to monitors (arduino#286)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
masci authored Jul 29, 2019
1 parent 19f5f9f commit a6fdfea
Show file tree
Hide file tree
Showing 11 changed files with 798 additions and 21 deletions.
1 change: 1 addition & 0 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ tasks:
desc: Compile protobuf definitions
cmds:
- '{{ default "protoc" .PROTOC_BINARY }} --proto_path=rpc --go_out=plugins=grpc,paths=source_relative:rpc ./rpc/commands/*.proto'
- '{{ default "protoc" .PROTOC_BINARY }} --proto_path=rpc --go_out=plugins=grpc,paths=source_relative:rpc ./rpc/monitor/*.proto'

build:
desc: Build the project
Expand Down
4 changes: 2 additions & 2 deletions appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ stack: go 1.12
install:
# install the task executor
- curl -o task.zip -LO https://github.com/go-task/task/releases/download/v2.6.0/task_windows_amd64.zip
- 7z e task.zip -o%GOPATH%\bin
- 7z x task.zip -o%GOPATH%\bin
# golang dependencies needed at test time
- go get github.com/golangci/govet
- go get golang.org/x/lint/golint
Expand All @@ -30,7 +30,7 @@ install:
# because of this: https://github.com/protocolbuffers/protobuf/issues/3957
- go get github.com/golang/protobuf/protoc-gen-go
- curl -o protoc.zip -LO https://github.com/protocolbuffers/protobuf/releases/download/v3.4.0/protoc-3.4.0-win32.zip
- 7z e protoc.zip -o%PROTOC_PATH%
- 7z x protoc.zip -o%PROTOC_PATH%

test_script:
# Check if the Go code is properly formatted and run the linter
Expand Down
62 changes: 62 additions & 0 deletions arduino/monitors/serial.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// This file is part of arduino-cli.
//
// Copyright 2019 ARDUINO SA (http://www.arduino.cc/)
//
// This software is released under the GNU General Public License version 3,
// which covers the main part of arduino-cli.
// The terms of this license can be found at:
// https://www.gnu.org/licenses/gpl-3.0.en.html
//
// You can be released from the requirements of the above licenses by purchasing
// a commercial license. Buying such a license is mandatory if you want to modify or
// otherwise use the software for commercial activities involving the Arduino
// software without disclosing the source code of your own applications. To purchase
// a commercial license, send an email to [email protected].

package monitors

import (
"github.com/pkg/errors"
serial "go.bug.st/serial.v1"
)

const (
defaultBaudRate = 9600
)

// SerialMonitor is a monitor for serial ports
type SerialMonitor struct {
port serial.Port
}

// OpenSerialMonitor creates a monitor instance for a serial port
func OpenSerialMonitor(portName string, baudRate int) (*SerialMonitor, error) {
// use default baud rate if not provided
if baudRate == 0 {
baudRate = defaultBaudRate
}

port, err := serial.Open(portName, &serial.Mode{BaudRate: baudRate})
if err != nil {
return nil, errors.Wrap(err, "error opening serial monitor")
}

return &SerialMonitor{
port: port,
}, nil
}

// Close the connection
func (mon *SerialMonitor) Close() error {
return mon.port.Close()
}

// Read bytes from the port
func (mon *SerialMonitor) Read(bytes []byte) (int, error) {
return mon.port.Read(bytes)
}

// Write bytes to the port
func (mon *SerialMonitor) Write(bytes []byte) (int, error) {
return mon.port.Write(bytes)
}
25 changes: 25 additions & 0 deletions arduino/monitors/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// This file is part of arduino-cli.
//
// Copyright 2019 ARDUINO SA (http://www.arduino.cc/)
//
// This software is released under the GNU General Public License version 3,
// which covers the main part of arduino-cli.
// The terms of this license can be found at:
// https://www.gnu.org/licenses/gpl-3.0.en.html
//
// You can be released from the requirements of the above licenses by purchasing
// a commercial license. Buying such a license is mandatory if you want to modify or
// otherwise use the software for commercial activities involving the Arduino
// software without disclosing the source code of your own applications. To purchase
// a commercial license, send an email to [email protected].

package monitors

import (
"io"
)

// Monitor is the interface implemented by different device monitors
type Monitor interface {
io.ReadWriteCloser
}
11 changes: 9 additions & 2 deletions cli/daemon/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ import (

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

// register the commands service
coreServer := daemon.ArduinoCoreServerImpl{
DownloaderHeaders: headers,
VersionString: globals.VersionInfo.VersionString,
Config: globals.Config,
}
rpc.RegisterArduinoCoreServer(s, &coreServer)
srv_commands.RegisterArduinoCoreServer(s, &coreServer)

// register the monitors service
srv_monitor.RegisterMonitorServer(s, &daemon.MonitorService{})

if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}

fmt.Println("Done serving")
}
134 changes: 134 additions & 0 deletions commands/daemon/monitor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// This file is part of arduino-cli.
//
// Copyright 2019 ARDUINO SA (http://www.arduino.cc/)
//
// This software is released under the GNU General Public License version 3,
// which covers the main part of arduino-cli.
// The terms of this license can be found at:
// https://www.gnu.org/licenses/gpl-3.0.en.html
//
// You can be released from the requirements of the above licenses by purchasing
// a commercial license. Buying such a license is mandatory if you want to
// modify or otherwise use the software for commercial activities involving the
// Arduino software without disclosing the source code of your own applications.
// To purchase a commercial license, send an email to [email protected].

package daemon

import (
"fmt"
"io"

"github.com/arduino/arduino-cli/arduino/monitors"
rpc "github.com/arduino/arduino-cli/rpc/monitor"
)

// MonitorService implements the `Monitor` service
type MonitorService struct{}

// StreamingOpen returns a stream response that can be used to fetch data from the
// monitor target. The first message passed through the `StreamingOpenReq` must
// contain monitor configuration params, not data.
func (s *MonitorService) StreamingOpen(stream rpc.Monitor_StreamingOpenServer) error {
// grab the first message
msg, err := stream.Recv()
if err != nil {
return err
}

// ensure it's a config message and not data
config := msg.GetMonitorConfig()
if config == nil {
return fmt.Errorf("first message must contain monitor configuration, not data")
}

// select which type of monitor we need
var mon monitors.Monitor
switch config.GetType() {
case rpc.MonitorConfig_SERIAL:
// grab port speed from additional config data
var baudRate float64
addCfg := config.GetAdditionalConfig()
for k, v := range addCfg.GetFields() {
if k == "BaudRate" {
baudRate = v.GetNumberValue()
break
}
}

// get the Monitor instance
var err error
if mon, err = monitors.OpenSerialMonitor(config.GetTarget(), int(baudRate)); err != nil {
return err
}
}

// we'll use these channels to communicate with the goroutines
// handling the stream and the target respectively
streamClosed := make(chan error)
targetClosed := make(chan error)

// now we can read the other messages and re-route to the monitor...
go func() {
for {
msg, err := stream.Recv()
if err == io.EOF {
// stream was closed
streamClosed <- nil
break
}

if err != nil {
// error reading from stream
streamClosed <- err
break
}

if _, err := mon.Write(msg.GetData()); err != nil {
// error writing to target
targetClosed <- err
break
}
}
}()

// ...and read from the monitor and forward to the output stream
go func() {
buf := make([]byte, 8)
for {
n, err := mon.Read(buf)
if err != nil {
// error reading from target
targetClosed <- err
break
}

if n == 0 {
// target was closed
targetClosed <- nil
break
}

if err = stream.Send(&rpc.StreamingOpenResp{
Data: buf[:n],
}); err != nil {
// error sending to stream
streamClosed <- err
break
}
}
}()

// let goroutines route messages from/to the monitor
// until either the client closes the stream or the
// monitor target is closed
for {
select {
case err := <-streamClosed:
mon.Close()
return err
case err := <-targetClosed:
return err
}
}
}
Loading

0 comments on commit a6fdfea

Please sign in to comment.