megapi: use serialport adaptor and move driver to drivers/serial (#1062)

This commit is contained in:
Thomas Kohler 2024-02-12 18:19:20 +01:00 committed by GitHub
parent e2b710bfe7
commit cb1f952d27
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 190 additions and 210 deletions

View File

@ -391,6 +391,7 @@ the `gobot/drivers/serial` package:
- [UART](https://en.wikipedia.org/wiki/Serial_port) <=> [Drivers](https://github.com/hybridgroup/gobot/tree/master/drivers/serial)
- Sphero: Sphero
- Neurosky: MindWave
- MegaPi: MotorDriver
Support for devices that use Serial Peripheral Interface (SPI) have
a shared set of drivers provided using the `gobot/drivers/spi` package:

View File

@ -115,7 +115,7 @@ import(
### Neurosky adaptor split off
The Neurosky adaptor now us the generic serial adaptor. The driver part was moved. With this, the imports needs to be
The Neurosky adaptor now use the generic serial adaptor. The driver part was moved. With this, the imports needs to be
adjusted. In addition all events now have a suffix "Event", see below.
```go
@ -148,6 +148,37 @@ import(
...
```
### MegaPi adaptor split off
The MegaPi adaptor now use the generic serial adaptor. The driver part was moved. With this, the imports needs to be
adjusted.
```go
// old
import(
...
"gobot.io/x/gobot/v2/platforms/megapi"
...
)
...
megaPiAdaptor := megapi.NewAdaptor("/dev/ttyS0")
motor := megapi.NewMotorDriver(megaPiAdaptor, 1)
...
// new
import(
...
"gobot.io/x/gobot/v2/drivers/serial/megapi"
"gobot.io/x/gobot/v2/platforms/serialport"
...
)
...
adaptor := serialport.NewAdaptor("/dev/ttyS0", serialport.WithName("MegaPi"))
motor := megapi.NewMotorDriver(adaptor, 1)
...
```
## Switch from version 2.2.0 (gpio drivers affected)
### gpio.ButtonDriver, gpio.PIRMotionDriver: substitute parameter "v time.duration"

View File

@ -14,3 +14,4 @@ Gobot has a extensible system for connecting to hardware devices. The following
- Sphero: Sphero
- Neurosky: MindWave
- MegaPi: MotorDriver

View File

@ -0,0 +1,130 @@
package megapi
import (
"bytes"
"encoding/binary"
"log"
"sync"
"time"
"gobot.io/x/gobot/v2"
"gobot.io/x/gobot/v2/drivers/serial"
)
var _ gobot.Driver = (*MotorDriver)(nil)
type megapiMotorSerialAdaptor interface {
gobot.Adaptor
serial.SerialWriter
}
// MotorDriver represents a motor
type MotorDriver struct {
*serial.Driver
port byte
halted bool
writeBytesChannel chan []byte
finalizeChannel chan struct{}
syncRoot *sync.Mutex
}
// NewMotorDriver creates a new MotorDriver at the given port
func NewMotorDriver(a megapiMotorSerialAdaptor, port byte, opts ...serial.OptionApplier) *MotorDriver {
d := &MotorDriver{
port: port,
halted: true,
syncRoot: &sync.Mutex{},
writeBytesChannel: make(chan []byte),
finalizeChannel: make(chan struct{}),
}
d.Driver = serial.NewDriver(a, "MegaPiMotor", d.initialize, d.shutdown, opts...)
return d
}
// Speed sets the motors speed to the specified value
func (d *MotorDriver) Speed(speed int16) error {
d.syncRoot.Lock()
defer d.syncRoot.Unlock()
if d.halted {
return nil
}
return d.speedHelper(speed)
}
// initialize implements the Driver interface
func (d *MotorDriver) initialize() error {
d.syncRoot.Lock()
defer d.syncRoot.Unlock()
// sleeping is required to give the board a chance to reset after connection is done
time.Sleep(2 * time.Second)
// kick off thread to send bytes to the board
go func() {
for {
select {
case bytes := <-d.writeBytesChannel:
if _, err := d.adaptor().SerialWrite(bytes); err != nil {
panic(err)
}
time.Sleep(10 * time.Millisecond)
case <-d.finalizeChannel:
d.finalizeChannel <- struct{}{}
return
default:
time.Sleep(10 * time.Millisecond)
}
}
}()
d.halted = false
return d.speedHelper(0)
}
// Halt terminates the Driver interface
func (d *MotorDriver) shutdown() error {
d.syncRoot.Lock()
defer d.syncRoot.Unlock()
d.finalizeChannel <- struct{}{}
<-d.finalizeChannel
d.halted = true
return d.speedHelper(0)
}
// there is some sort of bug on the hardware such that you cannot
// send the exact same speed to 2 different motors consecutively
// hence we ensure we always alternate speeds
func (d *MotorDriver) speedHelper(speed int16) error {
if err := d.sendSpeed(speed - 1); err != nil {
return err
}
return d.sendSpeed(speed)
}
// sendSpeed sets the motors speed to the specified value
func (d *MotorDriver) sendSpeed(speed int16) error {
bufOut := new(bytes.Buffer)
// byte sequence: 0xff, 0x55, id, action, device, port
bufOut.Write([]byte{0xff, 0x55, 0x6, 0x0, 0x2, 0xa, d.port})
if err := binary.Write(bufOut, binary.LittleEndian, speed); err != nil {
return err
}
bufOut.Write([]byte{0xa})
d.writeBytesChannel <- bufOut.Bytes()
return nil
}
func (d *MotorDriver) adaptor() megapiMotorSerialAdaptor {
if a, ok := d.Connection().(megapiMotorSerialAdaptor); ok {
return a
}
log.Printf("%s has no MegaPi serial connector\n", d.Name())
return nil
}

View File

@ -11,14 +11,15 @@ import (
"time"
"gobot.io/x/gobot/v2"
"gobot.io/x/gobot/v2/platforms/megapi"
"gobot.io/x/gobot/v2/drivers/serial/megapi"
"gobot.io/x/gobot/v2/platforms/serialport"
)
func main() {
// use "/dev/ttyUSB0" if connecting with USB cable
// use "/dev/ttyAMA0" on devices older than Raspberry Pi 3 Model B
megaPiAdaptor := megapi.NewAdaptor("/dev/ttyS0")
motor := megapi.NewMotorDriver(megaPiAdaptor, 1)
adaptor := serialport.NewAdaptor("/dev/ttyS0", serialport.WithName("MegaPi"))
motor := megapi.NewMotorDriver(adaptor, 1)
work := func() {
speed := int16(0)
@ -36,7 +37,7 @@ func main() {
}
robot := gobot.NewRobot("megaPiBot",
[]gobot.Connection{megaPiAdaptor},
[]gobot.Connection{adaptor},
[]gobot.Device{motor},
work,
)

View File

@ -15,16 +15,19 @@ Please refer to the main [README.md](https://github.com/hybridgroup/gobot/blob/r
package main
import (
"gobot.io/x/gobot/v2"
"gobot.io/x/gobot/v2/platforms/megapi"
"fmt"
"time"
"gobot.io/x/gobot/v2"
"gobot.io/x/gobot/v2/drivers/serial/megapi"
"gobot.io/x/gobot/v2/platforms/serialport"
)
func main() {
// use "/dev/ttyUSB0" if connecting with USB cable
// use "/dev/ttyAMA0" on devices older than Raspberry Pi 3 Model B
megaPiAdaptor := megapi.NewAdaptor("/dev/ttyS0")
motor := megapi.NewMotorDriver(megaPiAdaptor, 1)
adaptor := serialport.NewAdaptor("/dev/ttyS0", serialport.WithName("MegaPi"))
motor := megapi.NewMotorDriver(adaptor, 1)
work := func() {
speed := int16(0)
@ -40,13 +43,13 @@ func main() {
}
robot := gobot.NewRobot("megaPiBot",
[]gobot.Connection{megaPiAdaptor},
[]gobot.Connection{adaptor},
[]gobot.Device{motor},
work,
)
if err := robot.Start(); err != nil {
panic(err)
}
panic(err)
}
}
```

View File

@ -1,88 +0,0 @@
package megapi
import (
"io"
"time"
"go.bug.st/serial"
"gobot.io/x/gobot/v2"
)
var _ gobot.Adaptor = (*Adaptor)(nil)
// Adaptor is the Gobot adaptor for the MakeBlock MegaPi board
type Adaptor struct {
name string
port string
connection io.ReadWriteCloser
serialMode *serial.Mode
writeBytesChannel chan []byte
finalizeChannel chan struct{}
}
// NewAdaptor returns a new Adaptor with specified serial port used to talk to the MegaPi with a baud rate of 115200
func NewAdaptor(device string) *Adaptor {
c := &serial.Mode{BaudRate: 115200}
return &Adaptor{
name: "MegaPi",
connection: nil,
port: device,
serialMode: c,
writeBytesChannel: make(chan []byte),
finalizeChannel: make(chan struct{}),
}
}
// Name returns the name of this adaptor
func (megaPi *Adaptor) Name() string {
return megaPi.name
}
// SetName sets the name of this adaptor
func (megaPi *Adaptor) SetName(n string) {
megaPi.name = n
}
// Connect starts a connection to the board
func (megaPi *Adaptor) Connect() error {
if megaPi.connection == nil {
sp, err := serial.Open(megaPi.port, megaPi.serialMode)
if err != nil {
return err
}
// sleeping is required to give the board a chance to reset
time.Sleep(2 * time.Second)
megaPi.connection = sp
}
// kick off thread to send bytes to the board
go func() {
for {
select {
case bytes := <-megaPi.writeBytesChannel:
if _, err := megaPi.connection.Write(bytes); err != nil {
panic(err)
}
time.Sleep(10 * time.Millisecond)
case <-megaPi.finalizeChannel:
megaPi.finalizeChannel <- struct{}{}
return
default:
time.Sleep(10 * time.Millisecond)
}
}
}()
return nil
}
// Finalize terminates the connection to the board
func (megaPi *Adaptor) Finalize() error {
megaPi.finalizeChannel <- struct{}{}
<-megaPi.finalizeChannel
if err := megaPi.connection.Close(); err != nil {
return err
}
return nil
}

View File

@ -1,97 +0,0 @@
package megapi
import (
"bytes"
"encoding/binary"
"sync"
"gobot.io/x/gobot/v2"
)
var _ gobot.Driver = (*MotorDriver)(nil)
// MotorDriver represents a motor
type MotorDriver struct {
name string
megaPi *Adaptor
port byte
halted bool
syncRoot *sync.Mutex
}
// NewMotorDriver creates a new MotorDriver at the given port
func NewMotorDriver(megaPi *Adaptor, port byte) *MotorDriver {
return &MotorDriver{
name: "MegaPiMotor",
megaPi: megaPi,
port: port,
halted: true,
syncRoot: &sync.Mutex{},
}
}
// Name returns the name of this motor
func (d *MotorDriver) Name() string {
return d.name
}
// SetName sets the name of this motor
func (d *MotorDriver) SetName(n string) {
d.name = n
}
// Start implements the Driver interface
func (d *MotorDriver) Start() error {
d.syncRoot.Lock()
defer d.syncRoot.Unlock()
d.halted = false
return d.speedHelper(0)
}
// Halt terminates the Driver interface
func (d *MotorDriver) Halt() error {
d.syncRoot.Lock()
defer d.syncRoot.Unlock()
d.halted = true
return d.speedHelper(0)
}
// Connection returns the Connection associated with the Driver
func (d *MotorDriver) Connection() gobot.Connection {
return gobot.Connection(d.megaPi)
}
// Speed sets the motors speed to the specified value
func (d *MotorDriver) Speed(speed int16) error {
d.syncRoot.Lock()
defer d.syncRoot.Unlock()
if d.halted {
return nil
}
return d.speedHelper(speed)
}
// there is some sort of bug on the hardware such that you cannot
// send the exact same speed to 2 different motors consecutively
// hence we ensure we always alternate speeds
func (d *MotorDriver) speedHelper(speed int16) error {
if err := d.sendSpeed(speed - 1); err != nil {
return err
}
return d.sendSpeed(speed)
}
// sendSpeed sets the motors speed to the specified value
func (d *MotorDriver) sendSpeed(speed int16) error {
bufOut := new(bytes.Buffer)
// byte sequence: 0xff, 0x55, id, action, device, port
bufOut.Write([]byte{0xff, 0x55, 0x6, 0x0, 0x2, 0xa, d.port})
if err := binary.Write(bufOut, binary.LittleEndian, speed); err != nil {
return err
}
bufOut.Write([]byte{0xa})
d.megaPi.writeBytesChannel <- bufOut.Bytes()
return nil
}

View File

@ -21,7 +21,6 @@ type Adaptor struct {
cfg *configuration
sp io.ReadWriteCloser
connected bool
connectFunc func(string, int) (io.ReadWriteCloser, error)
}
@ -70,7 +69,7 @@ func (a *Adaptor) SetName(n string) {
// Connect initiates a connection to the serial port.
func (a *Adaptor) Connect() error {
if a.connected {
if a.sp != nil {
return fmt.Errorf("serial port is already connected, try reconnect or run disconnect first")
}
@ -80,7 +79,6 @@ func (a *Adaptor) Connect() error {
}
a.sp = sp
a.connected = true
return nil
}
@ -91,11 +89,11 @@ func (a *Adaptor) Finalize() error {
// Disconnect terminates the connection to the port.
func (a *Adaptor) Disconnect() error {
if a.connected {
if a.sp != nil {
if err := a.sp.Close(); err != nil {
return err
}
a.connected = false
a.sp = nil
}
return nil
}
@ -103,7 +101,7 @@ func (a *Adaptor) Disconnect() error {
// Reconnect attempts to reconnect to the port. If the port is connected it will first close
// that connection and then establish a new connection.
func (a *Adaptor) Reconnect() error {
if a.connected {
if a.sp != nil {
if err := a.Disconnect(); err != nil {
return err
}
@ -116,7 +114,7 @@ func (a *Adaptor) Port() string { return a.port }
// IsConnected returns the connection state
func (a *Adaptor) IsConnected() bool {
return a.connected
return a.sp != nil
}
// SerialRead reads from the port to the given reference

View File

@ -117,7 +117,7 @@ func TestConnect(t *testing.T) {
// act & assert
require.EqualError(t, a.Connect(), "serial port is already connected, try reconnect or run disconnect first")
// re-arrange error
a.connected = false
a.sp = nil
a.connectFunc = func(string, int) (io.ReadWriteCloser, error) {
return nil, errors.New("connect error")
}
@ -129,16 +129,16 @@ func TestConnect(t *testing.T) {
func TestReconnect(t *testing.T) {
// arrange
a, _ := initTestAdaptor()
require.True(t, a.connected)
require.NotNil(t, a.sp)
// act & assert
require.NoError(t, a.Reconnect())
assert.True(t, a.connected)
require.NotNil(t, a.sp)
// act & assert
require.NoError(t, a.Disconnect())
assert.False(t, a.connected)
require.Nil(t, a.sp)
// act & assert
require.NoError(t, a.Reconnect())
assert.True(t, a.connected)
require.NotNil(t, a.sp)
}
func TestFinalize(t *testing.T) {
@ -149,7 +149,7 @@ func TestFinalize(t *testing.T) {
assert.False(t, a.IsConnected())
// re-arrange error
rwc.simulateCloseErr = true
a.connected = true
require.NoError(t, a.Connect())
// act & assert
require.ErrorContains(t, a.Finalize(), "close error")
}