Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion LICENSE.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
This file includes licensing information for serial-discovery

Copyright (c) 2018 ARDUINO SA (www.arduino.cc)
Copyright (c) 2018-2021 ARDUINO SA (www.arduino.cc)

The software is released under the GNU General Public License, which covers the main body
of the serial-discovery code. The terms of this license can be found at:
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Arduino pluggabe discovery for serial ports
# Arduino pluggable discovery for serial ports

The `serial-discovery` tool is a command line program that interacts via stdio. It accepts commands as plain ASCII strings terminated with LF `\n` and sends response as JSON.

Expand Down
9 changes: 5 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
module github.com/arduino/serial-discovery

require (
github.com/arduino/go-paths-helper v1.5.0 // indirect
github.com/arduino/go-properties-orderedmap v1.4.0
github.com/arduino/go-paths-helper v1.6.1 // indirect
github.com/arduino/go-properties-orderedmap v1.5.0
github.com/arduino/pluggable-discovery-protocol-handler v1.1.0
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/s-urbaniak/uevent v1.0.0
go.bug.st/serial v1.1.3
golang.org/x/sys v0.0.0-20210301091718-77cc2087c03b
go.bug.st/serial v1.3.1
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
)

go 1.13
17 changes: 10 additions & 7 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
github.com/arduino/go-paths-helper v1.0.1/go.mod h1:HpxtKph+g238EJHq4geEPv9p+gl3v5YYu35Yb+w31Ck=
github.com/arduino/go-paths-helper v1.5.0 h1:RVo189hD+GhUS1rQ3gixwK1nSbvVR8MGIGa7Gxv2bdM=
github.com/arduino/go-paths-helper v1.5.0/go.mod h1:V82BWgAAp4IbmlybxQdk9Bpkz8M4Qyx+RAFKaG9NuvU=
github.com/arduino/go-properties-orderedmap v1.4.0 h1:YEbbzPqm1gXWDM/Jaq8tlvmh09z2qeHPJTUw9/VA4Dk=
github.com/arduino/go-paths-helper v1.6.1 h1:lha+/BuuBsx0qTZ3gy6IO1kU23lObWdQ/UItkzVWQ+0=
github.com/arduino/go-paths-helper v1.6.1/go.mod h1:V82BWgAAp4IbmlybxQdk9Bpkz8M4Qyx+RAFKaG9NuvU=
github.com/arduino/go-properties-orderedmap v1.4.0/go.mod h1:DKjD2VXY/NZmlingh4lSFMEYCVubfeArCsGPGDwb2yk=
github.com/arduino/go-properties-orderedmap v1.5.0 h1:istmr13qQN3nneuU3lsqlMvI6jqB3u8QUfVU1tX/t/8=
github.com/arduino/go-properties-orderedmap v1.5.0/go.mod h1:DKjD2VXY/NZmlingh4lSFMEYCVubfeArCsGPGDwb2yk=
github.com/arduino/pluggable-discovery-protocol-handler v1.1.0 h1:/fYOQ9f6beV9+mv9rDs+kvkhNKJ1edIT0RvIRj5Jj4U=
github.com/arduino/pluggable-discovery-protocol-handler v1.1.0/go.mod h1:vQfYGJnunfcscLoUcZKqJBlEkZ/qiE28TQj+RV9UT74=
github.com/creack/goselect v0.1.2 h1:2DNy14+JPjRBgPzAd1thbQp4BSIihxcBf0IXhQXDRa0=
github.com/creack/goselect v0.1.2/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand All @@ -18,11 +21,11 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
go.bug.st/serial v1.1.3 h1:YEBxJa9pKS9Wdg46B/jiaKbvvbUrjhZZZITfJHEJhaE=
go.bug.st/serial v1.1.3/go.mod h1:8TT7u/SwwNIpJ8QaG4s+HTjFt9ReXs2cdOU7ZEk50Dk=
go.bug.st/serial v1.3.1 h1:ziLU+w7RzBZKMGacM/6iY6P7bhxsoyaoVLWcfAdt6w4=
go.bug.st/serial v1.3.1/go.mod h1:8TT7u/SwwNIpJ8QaG4s+HTjFt9ReXs2cdOU7ZEk50Dk=
golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210301091718-77cc2087c03b h1:kHlr0tATeLRMEiZJu5CknOw/E8V6h69sXXQFGoPtjcc=
golang.org/x/sys v0.0.0-20210301091718-77cc2087c03b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
Expand Down
223 changes: 60 additions & 163 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//
// This file is part of serial-discovery.
//
// Copyright 2021 ARDUINO SA (http://www.arduino.cc/)
// Copyright 2018-2021 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.
Expand All @@ -18,16 +18,11 @@
package main

import (
"bufio"
"encoding/json"
"fmt"
"os"
"regexp"
"strconv"
"strings"
"sync"

"github.com/arduino/go-properties-orderedmap"
discovery "github.com/arduino/pluggable-discovery-protocol-handler"
"github.com/arduino/serial-discovery/version"
"go.bug.st/serial/enumerator"
)
Expand All @@ -39,179 +34,81 @@ func main() {
return
}

syncStarted := false
var syncCloseChan chan<- bool

reader := bufio.NewReader(os.Stdin)
for {
fullCmd, err := reader.ReadString('\n')
if err != nil {
output(&genericMessageJSON{
EventType: "command_error",
Error: true,
Message: err.Error(),
})
os.Exit(1)
}
split := strings.Split(fullCmd, " ")
cmd := strings.ToUpper(strings.TrimSpace(split[0]))
switch cmd {
case "HELLO":
re := regexp.MustCompile(`(\d+) "([^"]+)"`)
matches := re.FindStringSubmatch(fullCmd[6:])
if len(matches) != 3 {
output(&genericMessageJSON{
EventType: "hello",
Error: true,
Message: "Invalid HELLO command",
})
continue
}
_ /* userAgent */ = matches[2]
_ /* reqProtocolVersion */, err := strconv.ParseUint(matches[1], 10, 64)
if err != nil {
output(&genericMessageJSON{
EventType: "hello",
Error: true,
Message: "Invalid protocol version: " + matches[2],
})
continue
}
output(&helloMessageJSON{
EventType: "hello",
ProtocolVersion: 1, // Protocol version 1 is the only supported for now...
Message: "OK",
})
case "START":
output(&genericMessageJSON{
EventType: "start",
Message: "OK",
})
case "STOP":
if syncStarted {
syncCloseChan <- true
syncStarted = false
}
output(&genericMessageJSON{
EventType: "stop",
Message: "OK",
})
case "LIST":
outputList()
case "QUIT":
output(&genericMessageJSON{
EventType: "quit",
Message: "OK",
})
os.Exit(0)
case "START_SYNC":
if syncStarted {
// sync already started, just acknowledge again...
output(&genericMessageJSON{
EventType: "start_sync",
Message: "OK",
})
} else if close, err := startSync(); err != nil {
output(&genericMessageJSON{
EventType: "start_sync",
Error: true,
Message: err.Error(),
})
} else {
syncCloseChan = close
syncStarted = true
}
default:
output(&genericMessageJSON{
EventType: "command_error",
Error: true,
Message: fmt.Sprintf("Command %s not supported", cmd),
})
}
serialDisc := &SerialDiscovery{}
disc := discovery.NewDiscoveryServer(serialDisc)
if err := disc.Run(os.Stdin, os.Stdout); err != nil {
fmt.Fprintf(os.Stderr, "Error: %s\n", err.Error())
os.Exit(1)
}
}

type boardPortJSON struct {
Address string `json:"address"`
Label string `json:"label,omitempty"`
Protocol string `json:"protocol,omitempty"`
ProtocolLabel string `json:"protocolLabel,omitempty"`
Properties *properties.Map `json:"properties,omitempty"`
// SerialDiscovery is the implementation of the serial ports pluggable-discovery
type SerialDiscovery struct {
closeChan chan<- bool
}

type listOutputJSON struct {
EventType string `json:"eventType"`
Ports []*boardPortJSON `json:"ports"`
// Hello is the handler for the pluggable-discovery HELLO command
func (d *SerialDiscovery) Hello(userAgent string, protocolVersion int) error {
return nil
}

func outputList() {
list, err := enumerator.GetDetailedPortsList()
if err != nil {
output(&genericMessageJSON{
EventType: "list",
Error: true,
Message: err.Error(),
})
return
}
portsJSON := []*boardPortJSON{}
for _, port := range list {
portJSON := newBoardPortJSON(port)
portsJSON = append(portsJSON, portJSON)
}
output(&listOutputJSON{
EventType: "list",
Ports: portsJSON,
})
// Quit is the handler for the pluggable-discovery QUIT command
func (d *SerialDiscovery) Quit() {
}

func newBoardPortJSON(port *enumerator.PortDetails) *boardPortJSON {
prefs := properties.NewMap()
portJSON := &boardPortJSON{
Address: port.Name,
Label: port.Name,
Protocol: "serial",
ProtocolLabel: "Serial Port",
Properties: prefs,
}
if port.IsUSB {
portJSON.ProtocolLabel = "Serial Port (USB)"
portJSON.Properties.Set("vid", "0x"+port.VID)
portJSON.Properties.Set("pid", "0x"+port.PID)
portJSON.Properties.Set("serialNumber", port.SerialNumber)
}
return portJSON
// Start is the handler for the pluggable-discovery START command
func (d *SerialDiscovery) Start() error {
return nil
}

type helloMessageJSON struct {
EventType string `json:"eventType"`
ProtocolVersion int `json:"protocolVersion"`
Message string `json:"message"`
// Stop is the handler for the pluggable-discovery STOP command
func (d *SerialDiscovery) Stop() error {
if d.closeChan != nil {
d.closeChan <- true
close(d.closeChan)
d.closeChan = nil
}
return nil
}

type genericMessageJSON struct {
EventType string `json:"eventType"`
Error bool `json:"error,omitempty"`
Message string `json:"message"`
// List is the handler for the pluggable-discovery LIST command
func (d *SerialDiscovery) List() ([]*discovery.Port, error) {
list, err := enumerator.GetDetailedPortsList()
if err != nil {
return nil, err
}
ports := make([]*discovery.Port, len(list))
for i, port := range list {
ports[i] = toDiscoveryPort(port)
}
return ports, nil
}

func output(msg interface{}) {
d, err := json.MarshalIndent(msg, "", " ")
// StartSync is the handler for the pluggable-discovery START_SYNC command
func (d *SerialDiscovery) StartSync(eventCB discovery.EventCallback, errorCB discovery.ErrorCallback) error {
close, err := startSync(eventCB, errorCB)
if err != nil {
output(&genericMessageJSON{
EventType: "command_error",
Error: true,
Message: err.Error(),
})
} else {
syncronizedPrintLn(string(d))
return err
}
d.closeChan = close
return nil
}

var stdoutMutext sync.Mutex

func syncronizedPrintLn(a ...interface{}) {
stdoutMutext.Lock()
fmt.Println(a...)
stdoutMutext.Unlock()
func toDiscoveryPort(port *enumerator.PortDetails) *discovery.Port {
protocolLabel := "Serial Port"
props := properties.NewMap()
if port.IsUSB {
protocolLabel += " (USB)"
props.Set("vid", "0x"+port.VID)
props.Set("pid", "0x"+port.PID)
props.Set("serialNumber", port.SerialNumber)
}
res := &discovery.Port{
Address: port.Name,
AddressLabel: port.Name,
Protocol: "serial",
ProtocolLabel: protocolLabel,
Properties: props,
}
return res
}
48 changes: 44 additions & 4 deletions sync.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//
// This file is part of serial-discovery.
//
// Copyright 2018 ARDUINO SA (http://www.arduino.cc/)
// Copyright 2018-2021 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.
Expand All @@ -17,7 +17,47 @@

package main

type syncOutputJSON struct {
EventType string `json:"eventType"`
Port *boardPortJSON `json:"port"`
import (
discovery "github.com/arduino/pluggable-discovery-protocol-handler"
"go.bug.st/serial/enumerator"
)

// ProcessUpdates sends 'add' and 'remove' events by comparing two ports enumeration
// made at different times:
// - ports present in the new list but not in the old list are reported as 'added'
// - ports present in the old list but not in the new list are reported as 'removed'
func ProcessUpdates(old, new []*enumerator.PortDetails, eventCB discovery.EventCallback) {
for _, oldPort := range old {
if !PortListHas(new, oldPort) {
eventCB("remove", &discovery.Port{
Address: oldPort.Name,
Protocol: "serial",
})
}
}

for _, newPort := range new {
if !PortListHas(old, newPort) {
eventCB("add", toDiscoveryPort(newPort))
}
}
}

// PortListHas checks if port is contained in list. The port metadata are
// compared in particular the port address, and vid/pid if the port is a usb port.
func PortListHas(list []*enumerator.PortDetails, port *enumerator.PortDetails) bool {
for _, p := range list {
if port.Name == p.Name && port.IsUSB == p.IsUSB {
if p.IsUSB &&
port.VID == p.VID &&
port.PID == p.PID &&
port.SerialNumber == p.SerialNumber {
return true
}
if !p.IsUSB {
return true
}
}
}
return false
}
Loading