// 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 . // Supports Windows, Linux, Mac, BeagleBone Black, and Raspberry Pi package main import ( "encoding/json" "slices" "strconv" "strings" "sync" "time" discovery "github.com/arduino/pluggable-discovery-protocol-handler/v2" "github.com/sirupsen/logrus" ) type serialhub struct { // Opened serial ports. ports map[string]*serport mu sync.Mutex } // SerialPortList is the serial port list type SerialPortList struct { Ports []*SpPortItem portsLock sync.Mutex } // SpPortItem is the serial port item type SpPortItem struct { Name string SerialNumber string DeviceClass string IsOpen bool IsPrimary bool Baud int BufferAlgorithm string Ver string VendorID string ProductID string } // serialPorts contains the ports attached to the machine var serialPorts SerialPortList var sh = serialhub{ ports: make(map[string]*serport), } // Register serial ports from the connections. 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 + "\"}") sh.ports[port.portName] = port sh.mu.Unlock() } // Unregister requests from connections. func (sh *serialhub) Unregister(port *serport) { sh.mu.Lock() //log.Print("Unregistering a port: ", p.portConf.Name) h.broadcastSys <- []byte("{\"Cmd\":\"Close\",\"Desc\":\"Got unregister/close on port.\",\"Port\":\"" + port.portConf.Name + "\",\"Baud\":" + strconv.Itoa(port.portConf.Baud) + "}") delete(sh.ports, port.portName) close(port.sendBuffered) close(port.sendNoBuf) sh.mu.Unlock() } func (sh *serialhub) FindPortByName(portname string) (*serport, bool) { sh.mu.Lock() defer sh.mu.Unlock() port, ok := sh.ports[portname] return port, ok } // List broadcasts a Json representation of the ports found func (sp *SerialPortList) List() { sp.portsLock.Lock() ls, err := json.MarshalIndent(sp, "", "\t") sp.portsLock.Unlock() if err != nil { //log.Println(err) h.broadcastSys <- []byte("Error creating json on port list " + err.Error()) } else { h.broadcastSys <- ls } } // Run is the main loop for port discovery and management func (sp *SerialPortList) Run() { for retries := 0; retries < 10; retries++ { sp.runSerialDiscovery() logrus.Errorf("Serial discovery stopped working, restarting it in 10 seconds...") time.Sleep(10 * time.Second) } logrus.Errorf("Failed restarting serial discovery. Giving up...") } func (sp *SerialPortList) runSerialDiscovery() { // First ensure that all the discoveries are available if err := Tools.Download("builtin", "serial-discovery", "latest", "keep"); err != nil { logrus.Errorf("Error downloading serial-discovery: %s", err) panic(err) } sd, err := Tools.GetLocation("serial-discovery") if err != nil { logrus.Errorf("Error downloading serial-discovery: %s", err) panic(err) } d := discovery.NewClient("serial", sd+"/serial-discovery") dLogger := logrus.WithField("discovery", "serial") if *verbose { d.SetLogger(dLogger) } d.SetUserAgent("arduino-create-agent/" + version) if err := d.Run(); err != nil { logrus.Errorf("Error running serial-discovery: %s", err) panic(err) } defer d.Quit() events, err := d.StartSync(10) if err != nil { logrus.Errorf("Error starting event watcher on serial-discovery: %s", err) panic(err) } logrus.Infof("Serial discovery started, watching for events") for ev := range events { logrus.WithField("event", ev).Debugf("Serial discovery event") switch ev.Type { case "add": sp.add(ev.Port) case "remove": sp.remove(ev.Port) } } sp.reset() logrus.Errorf("Serial discovery stopped.") } func (sp *SerialPortList) reset() { sp.portsLock.Lock() defer sp.portsLock.Unlock() sp.Ports = []*SpPortItem{} } func (sp *SerialPortList) add(addedPort *discovery.Port) { if addedPort.Protocol != "serial" { return } props := addedPort.Properties if !props.ContainsKey("vid") { return } vid, pid := props.Get("vid"), props.Get("pid") if vid == "0x0000" || pid == "0x0000" { return } if portsFilter != nil && !portsFilter.MatchString(addedPort.Address) { logrus.Debugf("ignoring port not matching filter. port: %v\n", addedPort.Address) return } sp.portsLock.Lock() defer sp.portsLock.Unlock() // If the port is already in the list, just update the metadata... for _, oldPort := range sp.Ports { if oldPort.Name == addedPort.Address { oldPort.SerialNumber = props.Get("serialNumber") oldPort.VendorID = vid oldPort.ProductID = pid return } } // ...otherwise, add it to the list sp.Ports = append(sp.Ports, &SpPortItem{ Name: addedPort.Address, SerialNumber: props.Get("serialNumber"), VendorID: vid, ProductID: pid, Ver: version, IsOpen: false, IsPrimary: false, Baud: 0, BufferAlgorithm: "", }) } func (sp *SerialPortList) remove(removedPort *discovery.Port) { sp.portsLock.Lock() defer sp.portsLock.Unlock() // Remove the port from the list sp.Ports = slices.DeleteFunc(sp.Ports, func(oldPort *SpPortItem) bool { return oldPort.Name == removedPort.Address }) } // MarkPortAsOpened marks a port as opened by the user func (sp *SerialPortList) MarkPortAsOpened(portname string) { sp.portsLock.Lock() defer sp.portsLock.Unlock() port := sp.getPortByName(portname) if port != nil { port.IsOpen = true } } // MarkPortAsClosed marks a port as no more opened by the user func (sp *SerialPortList) MarkPortAsClosed(portname string) { sp.portsLock.Lock() defer sp.portsLock.Unlock() port := sp.getPortByName(portname) if port != nil { port.IsOpen = false } } func (sp *SerialPortList) getPortByName(portname string) *SpPortItem { for _, port := range sp.Ports { if port.Name == portname { return port } } return nil } func spErr(err string) { //log.Println("Sending err back: ", err) //h.broadcastSys <- []byte(err) h.broadcastSys <- []byte("{\"Error\" : \"" + err + "\"}") } func spClose(portname string) { if myport, ok := sh.FindPortByName(portname); ok { h.broadcastSys <- []byte("Closing serial port " + portname) myport.Close() } else { spErr("We could not find the serial port " + portname + " that you were trying to close.") } } func spWrite(arg string) { // we will get a string of comXX asdf asdf asdf //log.Println("Inside spWrite arg: " + arg) arg = strings.TrimPrefix(arg, " ") //log.Println("arg after trim: " + arg) args := strings.SplitN(arg, " ", 3) if len(args) != 3 { errstr := "Could not parse send command: " + arg //log.Println(errstr) spErr(errstr) return } bufferingMode := args[0] portname := strings.Trim(args[1], " ") data := args[2] //log.Println("The port to write to is:" + portname + "---") //log.Println("The data is:" + data + "---") // See if we have this port open port, ok := sh.FindPortByName(portname) if !ok { // we couldn't find the port, so send err spErr("We could not find the serial port " + portname + " that you were trying to write to.") return } // see if bufferingMode is valid switch bufferingMode { case "send", "sendnobuf", "sendraw": // valid buffering mode, go ahead default: spErr("Unsupported send command:" + args[0] + ". Please specify a valid one") return } // send it to the write channel port.Write(data, bufferingMode) }