From bd5b9c6e55febd26deab0c98035429841e5f4fa6 Mon Sep 17 00:00:00 2001 From: Thomas Kohler Date: Mon, 12 Feb 2024 15:38:00 +0100 Subject: [PATCH] neurosky: use serialport adaptor and move driver to drivers/serial --- README.md | 7 +- doc.go | 4 +- drivers/MIGRATION.md | 41 +++- drivers/ble/sphero/sphero_bb8_driver.go | 6 +- drivers/ble/sphero/sphero_ollie_driver.go | 38 +-- .../ble/sphero/sphero_ollie_driver_test.go | 6 +- drivers/ble/sphero/sphero_sprkplus_driver.go | 6 +- .../spherocommon.go} | 2 +- .../spherocommon_packets.go} | 2 +- .../spherocommon_test.go} | 2 +- drivers/serial/README.md | 1 + drivers/serial/helpers_test.go | 28 --- drivers/serial/neurosky/LICENSE | 13 + drivers/serial/neurosky/mindwave_driver.go | 196 ++++++++++++++++ .../serial/neurosky/mindwave_driver_test.go | 60 ++--- drivers/serial/serial_driver.go | 42 ++-- drivers/serial/serial_driver_test.go | 21 +- drivers/serial/sphero/LICENSE | 13 + drivers/serial/{ => sphero}/sphero_driver.go | 92 ++++---- .../serial/{ => sphero}/sphero_driver_test.go | 29 ++- drivers/serial/testutil/testutil.go | 62 +++++ examples/leap_sphero.go | 4 +- .../{neurosky.go => serialport_mindwave.go} | 9 +- ...{serial_sphero.go => serialport_sphero.go} | 12 +- ...sphero_api.go => serialport_sphero_api.go} | 4 +- ...on.go => serialport_sphero_calibration.go} | 4 +- ...onways.go => serialport_sphero_conways.go} | 9 +- ...hero_dpad.go => serialport_sphero_dpad.go} | 4 +- ..._master.go => serialport_sphero_master.go} | 6 +- ...tiple.go => serialport_sphero_multiple.go} | 7 +- .../{helper_test.go => helpers_test.go} | 0 platforms/jetson/jetson_adaptor.go | 4 +- platforms/mqtt/mqtt_adaptor.go | 4 +- platforms/neurosky/README.md | 81 +------ platforms/neurosky/doc.go | 70 ------ platforms/neurosky/neurosky_adaptor.go | 52 ---- platforms/neurosky/neurosky_adaptor_test.go | 94 -------- platforms/neurosky/neurosky_driver.go | 222 ------------------ platforms/raspi/raspi_adaptor.go | 4 +- platforms/rockpi/rockpi_adaptor.go | 4 +- platforms/serialport/README.md | 3 +- platforms/serialport/adaptor.go | 80 +++++-- platforms/serialport/adaptor_options.go | 28 +++ platforms/serialport/adaptor_options_test.go | 27 +++ platforms/serialport/adaptor_test.go | 163 ++++++++----- platforms/serialport/helpers_test.go | 36 +++ platforms/sphero/sphero/README.md | 2 +- 47 files changed, 798 insertions(+), 806 deletions(-) rename drivers/common/{sphero/sphero_driver.go => spherocommon/spherocommon.go} (97%) rename drivers/common/{sphero/sphero_packets.go => spherocommon/spherocommon_packets.go} (99%) rename drivers/common/{sphero/sphero_driver_test.go => spherocommon/spherocommon_test.go} (94%) delete mode 100644 drivers/serial/helpers_test.go create mode 100644 drivers/serial/neurosky/LICENSE create mode 100644 drivers/serial/neurosky/mindwave_driver.go rename platforms/neurosky/neurosky_driver_test.go => drivers/serial/neurosky/mindwave_driver_test.go (72%) create mode 100644 drivers/serial/sphero/LICENSE rename drivers/serial/{ => sphero}/sphero_driver.go (82%) rename drivers/serial/{ => sphero}/sphero_driver_test.go (80%) create mode 100644 drivers/serial/testutil/testutil.go rename examples/{neurosky.go => serialport_mindwave.go} (81%) rename examples/{serial_sphero.go => serialport_sphero.go} (68%) rename examples/{serial_sphero_api.go => serialport_sphero_api.go} (89%) rename examples/{serial_sphero_calibration.go => serialport_sphero_calibration.go} (93%) rename examples/{serial_sphero_conways.go => serialport_sphero_conways.go} (88%) rename examples/{serial_sphero_dpad.go => serialport_sphero_dpad.go} (91%) rename examples/{serial_sphero_master.go => serialport_sphero_master.go} (82%) rename examples/{serial_sphero_multiple.go => serialport_sphero_multiple.go} (82%) rename platforms/bleclient/{helper_test.go => helpers_test.go} (100%) delete mode 100644 platforms/neurosky/doc.go delete mode 100644 platforms/neurosky/neurosky_adaptor.go delete mode 100644 platforms/neurosky/neurosky_adaptor_test.go delete mode 100644 platforms/neurosky/neurosky_driver.go create mode 100644 platforms/serialport/adaptor_options.go create mode 100644 platforms/serialport/adaptor_options_test.go create mode 100644 platforms/serialport/helpers_test.go diff --git a/README.md b/README.md index 4d12f82bb..0af24c298 100644 --- a/README.md +++ b/README.md @@ -111,7 +111,7 @@ import ( func main() { adaptor := serialport.NewAdaptor("/dev/rfcomm0") - driver := serial.NewSpheroDriver(adaptor) + driver := sphero.NewSpheroDriver(adaptor) work := func() { gobot.Every(3*time.Second, func() { @@ -179,14 +179,14 @@ import ( "gobot.io/x/gobot/v2" "gobot.io/x/gobot/v2/api" - "gobot.io/x/gobot/v2/drivers/common/sphero" + "gobot.io/x/gobot/v2/drivers/common/spherocommon" "gobot.io/x/gobot/v2/drivers/serial" "gobot.io/x/gobot/v2/platforms/serialport" ) func NewSwarmBot(port string) *gobot.Robot { spheroAdaptor := serialport.NewAdaptor(port) - spheroDriver := serial.NewSpheroDriver(spheroAdaptor, serial.WithName("Sphero" + port)) + spheroDriver := sphero.NewSpheroDriver(spheroAdaptor, serial.WithName("Sphero" + port)) work := func() { spheroDriver.Stop() @@ -390,6 +390,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 Support for devices that use Serial Peripheral Interface (SPI) have a shared set of drivers provided using the `gobot/drivers/spi` package: diff --git a/doc.go b/doc.go index 25a8731ab..b24ca6738 100644 --- a/doc.go +++ b/doc.go @@ -88,14 +88,14 @@ Finally, you can use Master Gobot to add the complete Gobot API or control swarm "gobot.io/x/gobot/v2" "gobot.io/x/gobot/v2/api" - "gobot.io/x/gobot/v2/drivers/common/sphero" + "gobot.io/x/gobot/v2/drivers/common/spherocommon" "gobot.io/x/gobot/v2/drivers/serial" "gobot.io/x/gobot/v2/platforms/serialport" ) func NewSwarmBot(port string) *gobot.Robot { spheroAdaptor := serialport.NewAdaptor(port) - spheroDriver := serial.NewSpheroDriver(spheroAdaptor, serial.WithName("Sphero" + port)) + spheroDriver := sphero.NewSpheroDriver(spheroAdaptor, serial.WithName("Sphero" + port)) work := func() { spheroDriver.Stop() diff --git a/drivers/MIGRATION.md b/drivers/MIGRATION.md index c0fa1a279..02b12da32 100644 --- a/drivers/MIGRATION.md +++ b/drivers/MIGRATION.md @@ -80,7 +80,7 @@ returned instead and the log output needs to be done at caller side. ### Sphero adaptor split off The Serial Based Sphero adaptor was split off into a generic serial adaptor and the driver part. With this, the imports -needs to be adjusted. In addition all events now have a postfix "Event", see below. +needs to be adjusted. In addition all events now have a suffix "Event", see below. ```go // old @@ -100,19 +100,54 @@ import( // new import( ... - "gobot.io/x/gobot/v2/drivers/common/sphero" + "gobot.io/x/gobot/v2/drivers/common/spherocommon" "gobot.io/x/gobot/v2/drivers/serial" "gobot.io/x/gobot/v2/platforms/serialport" ... ) ... adaptor := serialport.NewAdaptor("/dev/rfcomm0") - spheroDriver := serial.NewSpheroDriver(adaptor) + spheroDriver := sphero.NewSpheroDriver(adaptor) ... _ = spheroDriver.On(sphero.CollisionEvent, func(data interface{}) { ... ``` +### 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 +adjusted. In addition all events now have a suffix "Event", see below. + +```go +// old +import( + ... + "gobot.io/x/gobot/v2/platforms/neurosky" + ... +) + +... + adaptor := neurosky.NewAdaptor("/dev/rfcomm0") + neuro := neurosky.NewDriver(adaptor) +... + _ = neuro.On(neurosky.Extended, func(data interface{}) { +... + +// new +import( + ... + "gobot.io/x/gobot/v2/drivers/serial/neurosky" + "gobot.io/x/gobot/v2/platforms/serialport" + ... +) +... + adaptor := serialport.NewAdaptor("/dev/rfcomm0", serialport.WithName("Neurosky"), serialport.WithBaudRate(57600)) + neuro := neurosky.NewMindWaveDriver(adaptor) +... + _ = neuro.On(neurosky.ExtendedEvent, func(data interface{}) { +... +``` + ## Switch from version 2.2.0 (gpio drivers affected) ### gpio.ButtonDriver, gpio.PIRMotionDriver: substitute parameter "v time.duration" diff --git a/drivers/ble/sphero/sphero_bb8_driver.go b/drivers/ble/sphero/sphero_bb8_driver.go index ee6f9088b..022359ac6 100644 --- a/drivers/ble/sphero/sphero_bb8_driver.go +++ b/drivers/ble/sphero/sphero_bb8_driver.go @@ -3,7 +3,7 @@ package sphero import ( "gobot.io/x/gobot/v2" "gobot.io/x/gobot/v2/drivers/ble" - "gobot.io/x/gobot/v2/drivers/common/sphero" + "gobot.io/x/gobot/v2/drivers/common/spherocommon" ) // BB8Driver represents a Sphero BB-8 @@ -17,8 +17,8 @@ func NewBB8Driver(a gobot.BLEConnector, opts ...ble.OptionApplier) *BB8Driver { } // bb8DefaultCollisionConfig returns a CollisionConfig with sensible collision defaults -func bb8DefaultCollisionConfig() sphero.CollisionConfig { - return sphero.CollisionConfig{ +func bb8DefaultCollisionConfig() spherocommon.CollisionConfig { + return spherocommon.CollisionConfig{ Method: 0x01, Xt: 0x20, Yt: 0x20, diff --git a/drivers/ble/sphero/sphero_ollie_driver.go b/drivers/ble/sphero/sphero_ollie_driver.go index 81c2daf9b..cd521e597 100644 --- a/drivers/ble/sphero/sphero_ollie_driver.go +++ b/drivers/ble/sphero/sphero_ollie_driver.go @@ -8,7 +8,7 @@ import ( "gobot.io/x/gobot/v2" "gobot.io/x/gobot/v2/drivers/ble" - "gobot.io/x/gobot/v2/drivers/common/sphero" + "gobot.io/x/gobot/v2/drivers/common/spherocommon" ) // MotorModes is used to configure the motor @@ -63,14 +63,14 @@ type Point2D struct { type OllieDriver struct { *ble.Driver gobot.Eventer - defaultCollisionConfig sphero.CollisionConfig + defaultCollisionConfig spherocommon.CollisionConfig seq uint8 collisionResponse []uint8 packetChannel chan *packet asyncBuffer []byte asyncMessage []byte locatorCallback func(p Point2D) - powerstateCallback func(p sphero.PowerStatePacket) + powerstateCallback func(p spherocommon.PowerStatePacket) } // NewOllieDriver creates a driver for a Sphero Ollie @@ -80,7 +80,7 @@ func NewOllieDriver(a gobot.BLEConnector, opts ...ble.OptionApplier) *OllieDrive func newOllieBaseDriver( a gobot.BLEConnector, name string, - dcc sphero.CollisionConfig, opts ...ble.OptionApplier, + dcc spherocommon.CollisionConfig, opts ...ble.OptionApplier, ) *OllieDriver { d := &OllieDriver{ defaultCollisionConfig: dcc, @@ -89,8 +89,8 @@ func newOllieBaseDriver( } d.Driver = ble.NewDriver(a, name, d.initialize, d.shutdown, opts...) - d.AddEvent(sphero.ErrorEvent) - d.AddEvent(sphero.CollisionEvent) + d.AddEvent(spherocommon.ErrorEvent) + d.AddEvent(spherocommon.CollisionEvent) return d } @@ -118,7 +118,7 @@ func (d *OllieDriver) Wake() error { } // ConfigureCollisionDetection configures the sensitivity of the detection. -func (d *OllieDriver) ConfigureCollisionDetection(cc sphero.CollisionConfig) { +func (d *OllieDriver) ConfigureCollisionDetection(cc spherocommon.CollisionConfig) { d.sendCraftPacket([]uint8{cc.Method, cc.Xt, cc.Yt, cc.Xs, cc.Ys, cc.Dead}, 0x02, 0x12) } @@ -130,7 +130,7 @@ func (d *OllieDriver) GetLocatorData(f func(p Point2D)) { } // GetPowerState calls the passed function with the Power State information from the sphero -func (d *OllieDriver) GetPowerState(f func(p sphero.PowerStatePacket)) { +func (d *OllieDriver) GetPowerState(f func(p spherocommon.PowerStatePacket)) { // CID 0x20 is the code for the power state d.sendCraftPacket([]uint8{}, 0x00, 0x20) d.powerstateCallback = f @@ -194,7 +194,7 @@ func (d *OllieDriver) Sleep() { } // SetDataStreamingConfig passes the config to the sphero to stream sensor data -func (d *OllieDriver) SetDataStreamingConfig(dsc sphero.DataStreamingConfig) error { +func (d *OllieDriver) SetDataStreamingConfig(dsc spherocommon.DataStreamingConfig) error { buf := new(bytes.Buffer) if err := binary.Write(buf, binary.BigEndian, dsc); err != nil { return err @@ -225,7 +225,7 @@ func (d *OllieDriver) initialize() error { packet := <-d.packetChannel err := d.writeCommand(packet) if err != nil { - d.Publish(d.Event(sphero.ErrorEvent), err) + d.Publish(d.Event(spherocommon.ErrorEvent), err) } } }() @@ -331,13 +331,13 @@ func (d *OllieDriver) handleDataStreaming(data []byte) { // data packet is the same as for the normal sphero, since the same communication api is used // only difference in communication is that the "newer" spheros use BLE for communications - var dataPacket sphero.DataStreamingPacket + var dataPacket spherocommon.DataStreamingPacket buffer := bytes.NewBuffer(data[5:]) // skip header if err := binary.Read(buffer, binary.BigEndian, &dataPacket); err != nil { panic(err) } - d.Publish(sphero.SensorDataEvent, dataPacket) + d.Publish(spherocommon.SensorDataEvent, dataPacket) } func (d *OllieDriver) handleLocatorDetected(data []uint8) { @@ -368,7 +368,7 @@ func (d *OllieDriver) handleLocatorDetected(data []uint8) { } func (d *OllieDriver) handlePowerStateDetected(data []uint8) { - var dataPacket sphero.PowerStatePacket + var dataPacket spherocommon.PowerStatePacket buffer := bytes.NewBuffer(data[5:]) // skip header if err := binary.Read(buffer, binary.BigEndian, &dataPacket); err != nil { panic(err) @@ -404,18 +404,18 @@ func (d *OllieDriver) handleCollisionDetected(data []uint8) { // confirm checksum size := len(d.collisionResponse) chk := d.collisionResponse[size-1] // last byte is checksum - if chk != sphero.CalculateChecksum(d.collisionResponse[2:size-1]) { + if chk != spherocommon.CalculateChecksum(d.collisionResponse[2:size-1]) { return } - var collision sphero.CollisionPacket + var collision spherocommon.CollisionPacket buffer := bytes.NewBuffer(d.collisionResponse[5:]) // skip header if err := binary.Read(buffer, binary.BigEndian, &collision); err != nil { panic(err) } d.collisionResponse = nil // clear the current response - d.Publish(sphero.CollisionEvent, collision) + d.Publish(spherocommon.CollisionEvent, collision) } func (d *OllieDriver) sendCraftPacket(body []uint8, did byte, cid byte) { @@ -430,15 +430,15 @@ func (d *OllieDriver) craftPacket(body []uint8, did byte, cid byte) *packet { packet := &packet{ body: body, header: hdr, - checksum: sphero.CalculateChecksum(buf[2:]), + checksum: spherocommon.CalculateChecksum(buf[2:]), } return packet } // ollieDefaultCollisionConfig returns a CollisionConfig with sensible collision defaults -func ollieDefaultCollisionConfig() sphero.CollisionConfig { - return sphero.CollisionConfig{ +func ollieDefaultCollisionConfig() spherocommon.CollisionConfig { + return spherocommon.CollisionConfig{ Method: 0x01, Xt: 0x20, Yt: 0x20, diff --git a/drivers/ble/sphero/sphero_ollie_driver_test.go b/drivers/ble/sphero/sphero_ollie_driver_test.go index 671cd1758..1c06021d0 100644 --- a/drivers/ble/sphero/sphero_ollie_driver_test.go +++ b/drivers/ble/sphero/sphero_ollie_driver_test.go @@ -12,7 +12,7 @@ import ( "gobot.io/x/gobot/v2" "gobot.io/x/gobot/v2/drivers/ble" "gobot.io/x/gobot/v2/drivers/ble/testutil" - "gobot.io/x/gobot/v2/drivers/common/sphero" + "gobot.io/x/gobot/v2/drivers/common/spherocommon" ) var _ gobot.Driver = (*OllieDriver)(nil) @@ -82,12 +82,12 @@ func TestLocatorData(t *testing.T) { func TestDataStreaming(t *testing.T) { d := initTestOllieDriver() - err := d.SetDataStreamingConfig(sphero.DefaultDataStreamingConfig()) + err := d.SetDataStreamingConfig(spherocommon.DefaultDataStreamingConfig()) require.NoError(t, err) responseChan := make(chan bool) err = d.On("sensordata", func(data interface{}) { - cont := data.(sphero.DataStreamingPacket) + cont := data.(spherocommon.DataStreamingPacket) // fmt.Printf("got streaming packet: %+v \n", cont) assert.Equal(t, int16(10), cont.RawAccX) responseChan <- true diff --git a/drivers/ble/sphero/sphero_sprkplus_driver.go b/drivers/ble/sphero/sphero_sprkplus_driver.go index 75af9f310..4748b3ec6 100644 --- a/drivers/ble/sphero/sphero_sprkplus_driver.go +++ b/drivers/ble/sphero/sphero_sprkplus_driver.go @@ -3,7 +3,7 @@ package sphero import ( "gobot.io/x/gobot/v2" "gobot.io/x/gobot/v2/drivers/ble" - "gobot.io/x/gobot/v2/drivers/common/sphero" + "gobot.io/x/gobot/v2/drivers/common/spherocommon" ) // SPRKPlusDriver represents a Sphero SPRK+ @@ -17,8 +17,8 @@ func NewSPRKPlusDriver(a gobot.BLEConnector, opts ...ble.OptionApplier) *SPRKPlu } // sprkplusDefaultCollisionConfig returns a CollisionConfig with sensible collision defaults -func sprkplusDefaultCollisionConfig() sphero.CollisionConfig { - return sphero.CollisionConfig{ +func sprkplusDefaultCollisionConfig() spherocommon.CollisionConfig { + return spherocommon.CollisionConfig{ Method: 0x01, Xt: 0x20, Yt: 0x20, diff --git a/drivers/common/sphero/sphero_driver.go b/drivers/common/spherocommon/spherocommon.go similarity index 97% rename from drivers/common/sphero/sphero_driver.go rename to drivers/common/spherocommon/spherocommon.go index d8391a150..978ac7dcd 100644 --- a/drivers/common/sphero/sphero_driver.go +++ b/drivers/common/spherocommon/spherocommon.go @@ -1,4 +1,4 @@ -package sphero +package spherocommon const ( // ErrorEvent event when error encountered diff --git a/drivers/common/sphero/sphero_packets.go b/drivers/common/spherocommon/spherocommon_packets.go similarity index 99% rename from drivers/common/sphero/sphero_packets.go rename to drivers/common/spherocommon/spherocommon_packets.go index 0f6333f85..6693aa9af 100644 --- a/drivers/common/sphero/sphero_packets.go +++ b/drivers/common/spherocommon/spherocommon_packets.go @@ -1,4 +1,4 @@ -package sphero +package spherocommon // LocatorConfig provides configuration for the Location api. // https://github.com/orbotix/DeveloperResources/blob/master/docs/Sphero_API_1.50.pdf diff --git a/drivers/common/sphero/sphero_driver_test.go b/drivers/common/spherocommon/spherocommon_test.go similarity index 94% rename from drivers/common/sphero/sphero_driver_test.go rename to drivers/common/spherocommon/spherocommon_test.go index 03759d7ff..baa5941b0 100644 --- a/drivers/common/sphero/sphero_driver_test.go +++ b/drivers/common/spherocommon/spherocommon_test.go @@ -1,4 +1,4 @@ -package sphero +package spherocommon import "testing" diff --git a/drivers/serial/README.md b/drivers/serial/README.md index 40da591f3..a8ec27b0c 100644 --- a/drivers/serial/README.md +++ b/drivers/serial/README.md @@ -13,3 +13,4 @@ Please refer to the main [README.md](https://github.com/hybridgroup/gobot/blob/r Gobot has a extensible system for connecting to hardware devices. The following Serial devices are currently supported: - Sphero: Sphero +- Neurosky: MindWave diff --git a/drivers/serial/helpers_test.go b/drivers/serial/helpers_test.go deleted file mode 100644 index b63f659f8..000000000 --- a/drivers/serial/helpers_test.go +++ /dev/null @@ -1,28 +0,0 @@ -package serial - -type serialTestAdaptor struct { - isConnected bool - name string -} - -func newSerialTestAdaptor() *serialTestAdaptor { - return &serialTestAdaptor{} -} - -func (t *serialTestAdaptor) IsConnected() bool { - return t.isConnected -} - -func (t *serialTestAdaptor) SerialRead(b []byte) (int, error) { - return len(b), nil -} - -func (t *serialTestAdaptor) SerialWrite(b []byte) (int, error) { - return len(b), nil -} - -// gobot.Adaptor interfaces -func (t *serialTestAdaptor) Connect() error { return nil } -func (t *serialTestAdaptor) Finalize() error { return nil } -func (t *serialTestAdaptor) Name() string { return t.name } -func (t *serialTestAdaptor) SetName(n string) { t.name = n } diff --git a/drivers/serial/neurosky/LICENSE b/drivers/serial/neurosky/LICENSE new file mode 100644 index 000000000..09094bb5e --- /dev/null +++ b/drivers/serial/neurosky/LICENSE @@ -0,0 +1,13 @@ +Copyright (c) 2013-2018 The Hybrid Group + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/drivers/serial/neurosky/mindwave_driver.go b/drivers/serial/neurosky/mindwave_driver.go new file mode 100644 index 000000000..96571d3af --- /dev/null +++ b/drivers/serial/neurosky/mindwave_driver.go @@ -0,0 +1,196 @@ +package neurosky + +import ( + "bytes" + "log" + + "gobot.io/x/gobot/v2" + "gobot.io/x/gobot/v2/drivers/serial" +) + +type mindWaveSerialAdaptor interface { + gobot.Adaptor + serial.SerialReader +} + +const ( + // mindWaveBTSync is the sync code + mindWaveBTSync byte = 0xAA + // mindWaveCodeEx Extended code + mindWaveCodeEx byte = 0x55 + // mindWaveCodeSignalQuality POOR_SIGNAL quality 0-255 + mindWaveCodeSignalQuality byte = 0x02 + // mindWaveCodeAttention ATTENTION eSense 0-100 + mindWaveCodeAttention byte = 0x04 + // mindWaveCodeMeditation MEDITATION eSense 0-100 + mindWaveCodeMeditation byte = 0x05 + // mindWaveCodeBlink BLINK strength 0-255 + mindWaveCodeBlink byte = 0x16 + // mindWaveCodeWave RAW wave value: 2-byte big-endian 2s-complement + mindWaveCodeWave byte = 0x80 + // mindWaveCodeAsicEEG ASIC EEG POWER 8 3-byte big-endian integers + mindWaveCodeAsicEEG byte = 0x83 + + ExtendedEvent = "extended" + SignalEvent = "signal" + AttentionEvent = "attention" + MeditationEvent = "meditation" + BlinkEvent = "blink" + WaveEvent = "wave" + EEGEvent = "eeg" + ErrorEvent = "error" +) + +// MindWaveDriver is the Gobot driver for the Neurosky MindWave Sensor +type MindWaveDriver struct { + *serial.Driver + gobot.Eventer +} + +// MindWaveEEGData is the EEG raw data returned from the sensor +type MindWaveEEGData struct { + Delta int + Theta int + LoAlpha int + HiAlpha int + LoBeta int + HiBeta int + LoGamma int + MidGamma int +} + +// NewMindWaveDriver creates a driver for Neurosky MindWave +// and adds the following events: +// +// extended - user's current extended level +// signal - shows signal strength +// attention - user's current attention level +// meditation - user's current meditation level +// blink - user's current blink level +// wave - shows wave data +// eeg - showing eeg data +func NewMindWaveDriver(a mindWaveSerialAdaptor, opts ...serial.OptionApplier) *MindWaveDriver { + d := &MindWaveDriver{ + Eventer: gobot.NewEventer(), + } + d.Driver = serial.NewDriver(a, "MindWave", d.initialize, nil, opts...) + + d.AddEvent(ExtendedEvent) + d.AddEvent(SignalEvent) + d.AddEvent(AttentionEvent) + d.AddEvent(MeditationEvent) + d.AddEvent(BlinkEvent) + d.AddEvent(WaveEvent) + d.AddEvent(EEGEvent) + d.AddEvent(ErrorEvent) + + return d +} + +// initialize creates a go routine to listen from serial port and parse buffer readings +// TODO: stop the go routine gracefully on Halt() +func (d *MindWaveDriver) initialize() error { + go func() { + for { + buff := make([]byte, 1024) + _, err := d.adaptor().SerialRead(buff) + if err != nil { + d.Publish(d.Event("error"), err) + } else { + if err := d.parse(bytes.NewBuffer(buff)); err != nil { + panic(err) + } + } + } + }() + return nil +} + +// parse converts bytes buffer into packets until no more data is present +func (d *MindWaveDriver) parse(buf *bytes.Buffer) error { + for buf.Len() > 2 { + b1, _ := buf.ReadByte() + b2, _ := buf.ReadByte() + if b1 == mindWaveBTSync && b2 == mindWaveBTSync { + length, _ := buf.ReadByte() + payload := make([]byte, length) + if _, err := buf.Read(payload); err != nil { + return err + } + // checksum, _ := buf.ReadByte() + buf.Next(1) + if err := d.parsePacket(bytes.NewBuffer(payload)); err != nil { + panic(err) + } + } + } + + return nil +} + +// parsePacket publishes event according to data parsed +func (d *MindWaveDriver) parsePacket(buf *bytes.Buffer) error { + for buf.Len() > 0 { + b, _ := buf.ReadByte() + switch b { + case mindWaveCodeEx: + d.Publish(d.Event(ExtendedEvent), nil) + case mindWaveCodeSignalQuality: + ret, _ := buf.ReadByte() + d.Publish(d.Event(SignalEvent), ret) + case mindWaveCodeAttention: + ret, _ := buf.ReadByte() + d.Publish(d.Event(AttentionEvent), ret) + case mindWaveCodeMeditation: + ret, _ := buf.ReadByte() + d.Publish(d.Event(MeditationEvent), ret) + case mindWaveCodeBlink: + ret, _ := buf.ReadByte() + d.Publish(d.Event(BlinkEvent), ret) + case mindWaveCodeWave: + buf.Next(1) + ret := make([]byte, 2) + if _, err := buf.Read(ret); err != nil { + return err + } + d.Publish(d.Event(WaveEvent), int16(ret[0])<<8|int16(ret[1])) + case mindWaveCodeAsicEEG: + ret := make([]byte, 25) + i, _ := buf.Read(ret) + if i == 25 { + d.Publish(d.Event(EEGEvent), d.parseEEG(ret)) + } + } + } + + return nil +} + +// parseEEG returns data converted into EEG map +func (d *MindWaveDriver) parseEEG(data []byte) MindWaveEEGData { + return MindWaveEEGData{ + Delta: d.parse3ByteInteger(data[0:3]), + Theta: d.parse3ByteInteger(data[3:6]), + LoAlpha: d.parse3ByteInteger(data[6:9]), + HiAlpha: d.parse3ByteInteger(data[9:12]), + LoBeta: d.parse3ByteInteger(data[12:15]), + HiBeta: d.parse3ByteInteger(data[15:18]), + LoGamma: d.parse3ByteInteger(data[18:21]), + MidGamma: d.parse3ByteInteger(data[21:25]), + } +} + +func (d *MindWaveDriver) parse3ByteInteger(data []byte) int { + return ((int(data[0]) << 16) | + (((1 << 16) - 1) & (int(data[1]) << 8)) | + (((1 << 8) - 1) & int(data[2]))) +} + +func (d *MindWaveDriver) adaptor() mindWaveSerialAdaptor { + if a, ok := d.Connection().(mindWaveSerialAdaptor); ok { + return a + } + + log.Printf("%s has no Neurosky serial connector\n", d.Name()) + return nil +} diff --git a/platforms/neurosky/neurosky_driver_test.go b/drivers/serial/neurosky/mindwave_driver_test.go similarity index 72% rename from platforms/neurosky/neurosky_driver_test.go rename to drivers/serial/neurosky/mindwave_driver_test.go index 00dd62a78..4e9eae5f0 100644 --- a/platforms/neurosky/neurosky_driver_test.go +++ b/drivers/serial/neurosky/mindwave_driver_test.go @@ -4,7 +4,6 @@ package neurosky import ( "bytes" "errors" - "io" "strings" "testing" "time" @@ -13,17 +12,15 @@ import ( "github.com/stretchr/testify/require" "gobot.io/x/gobot/v2" + "gobot.io/x/gobot/v2/drivers/serial/testutil" ) -var _ gobot.Driver = (*Driver)(nil) +var _ gobot.Driver = (*MindWaveDriver)(nil) -func initTestNeuroskyDriver() *Driver { - a := NewAdaptor("/dev/null") - a.connect = func(n *Adaptor) (io.ReadWriteCloser, error) { - return &NullReadWriteCloser{}, nil - } +func initTestNeuroskyDriver() *MindWaveDriver { + a := testutil.NewSerialTestAdaptor() _ = a.Connect() - return NewDriver(a) + return NewMindWaveDriver(a) } func TestNeuroskyDriver(t *testing.T) { @@ -33,7 +30,7 @@ func TestNeuroskyDriver(t *testing.T) { func TestNeuroskyDriverName(t *testing.T) { d := initTestNeuroskyDriver() - assert.True(t, strings.HasPrefix(d.Name(), "Neurosky")) + assert.True(t, strings.HasPrefix(d.Name(), "MindWave")) d.SetName("NewName") assert.Equal(t, "NewName", d.Name()) } @@ -41,24 +38,19 @@ func TestNeuroskyDriverName(t *testing.T) { func TestNeuroskyDriverStart(t *testing.T) { sem := make(chan bool) - rwc := &NullReadWriteCloser{} - a := NewAdaptor("/dev/null") - a.connect = func(n *Adaptor) (io.ReadWriteCloser, error) { - return rwc, nil - } + a := testutil.NewSerialTestAdaptor() _ = a.Connect() + a.SetSimulateReadError(true) - d := NewDriver(a) + d := NewMindWaveDriver(a) e := errors.New("read error") - _ = d.Once(d.Event(Error), func(data interface{}) { + _ = d.Once(d.Event(ErrorEvent), func(data interface{}) { assert.Equal(t, e, data.(error)) sem <- true }) require.NoError(t, d.Start()) - time.Sleep(50 * time.Millisecond) - rwc.ReadError(e) select { case <-sem: @@ -79,13 +71,13 @@ func TestNeuroskyDriverParse(t *testing.T) { sem := make(chan bool) d := initTestNeuroskyDriver() - // CodeEx + // mindWaveCodeEx go func() { time.Sleep(5 * time.Millisecond) _ = d.parse(bytes.NewBuffer([]byte{0xAA, 0xAA, 1, 0x55, 0x00})) }() - _ = d.On(d.Event(Extended), func(data interface{}) { + _ = d.On(d.Event(ExtendedEvent), func(data interface{}) { sem <- true }) @@ -95,72 +87,72 @@ func TestNeuroskyDriverParse(t *testing.T) { t.Errorf("Event \"extended\" was not published") } - // CodeSignalQuality + // mindWaveCodeSignalQuality go func() { time.Sleep(5 * time.Millisecond) _ = d.parse(bytes.NewBuffer([]byte{0xAA, 0xAA, 2, 0x02, 100, 0x00})) }() - _ = d.On(d.Event(Signal), func(data interface{}) { + _ = d.On(d.Event(SignalEvent), func(data interface{}) { assert.Equal(t, byte(100), data.(byte)) sem <- true }) <-sem - // CodeAttention + // mindWaveCodeAttention go func() { time.Sleep(5 * time.Millisecond) _ = d.parse(bytes.NewBuffer([]byte{0xAA, 0xAA, 2, 0x04, 40, 0x00})) }() - _ = d.On(d.Event(Attention), func(data interface{}) { + _ = d.On(d.Event(AttentionEvent), func(data interface{}) { assert.Equal(t, byte(40), data.(byte)) sem <- true }) <-sem - // CodeMeditation + // mindWaveCodeMeditation go func() { time.Sleep(5 * time.Millisecond) _ = d.parse(bytes.NewBuffer([]byte{0xAA, 0xAA, 2, 0x05, 60, 0x00})) }() - _ = d.On(d.Event(Meditation), func(data interface{}) { + _ = d.On(d.Event(MeditationEvent), func(data interface{}) { assert.Equal(t, byte(60), data.(byte)) sem <- true }) <-sem - // CodeBlink + // mindWaveCodeBlink go func() { time.Sleep(5 * time.Millisecond) _ = d.parse(bytes.NewBuffer([]byte{0xAA, 0xAA, 2, 0x16, 150, 0x00})) }() - _ = d.On(d.Event(Blink), func(data interface{}) { + _ = d.On(d.Event(BlinkEvent), func(data interface{}) { assert.Equal(t, byte(150), data.(byte)) sem <- true }) <-sem - // CodeWave + // mindWaveCodeWave go func() { time.Sleep(5 * time.Millisecond) _ = d.parse(bytes.NewBuffer([]byte{0xAA, 0xAA, 4, 0x80, 0x00, 0x40, 0x11, 0x00})) }() - _ = d.On(d.Event(Wave), func(data interface{}) { + _ = d.On(d.Event(WaveEvent), func(data interface{}) { assert.Equal(t, int16(16401), data.(int16)) sem <- true }) <-sem - // CodeAsicEEG + // mindWaveCodeAsicEEG go func() { time.Sleep(5 * time.Millisecond) _ = d.parse(bytes.NewBuffer([]byte{ @@ -170,9 +162,9 @@ func TestNeuroskyDriverParse(t *testing.T) { })) }() - _ = d.On(d.Event(EEG), func(data interface{}) { + _ = d.On(d.Event(EEGEvent), func(data interface{}) { assert.Equal(t, - EEGData{ + MindWaveEEGData{ Delta: 1573241, Theta: 5832801, LoAlpha: 1703966, @@ -182,7 +174,7 @@ func TestNeuroskyDriverParse(t *testing.T) { LoGamma: 8323090, MidGamma: 13565965, }, - data.(EEGData)) + data.(MindWaveEEGData)) sem <- true }) <-sem diff --git a/drivers/serial/serial_driver.go b/drivers/serial/serial_driver.go index 32e8ec7e7..ac6b428c1 100644 --- a/drivers/serial/serial_driver.go +++ b/drivers/serial/serial_driver.go @@ -15,8 +15,8 @@ type SerialWriter interface { SerialWrite(b []byte) (n int, err error) } -// optionApplier needs to be implemented by each configurable option type -type optionApplier interface { +// OptionApplier needs to be implemented by each configurable option type +type OptionApplier interface { apply(cfg *configuration) } @@ -29,7 +29,7 @@ type configuration struct { type nameOption string // Driver implements the interface gobot.Driver. -type driver struct { +type Driver struct { gobot.Commander connection interface{} driverCfg *configuration @@ -38,14 +38,24 @@ type driver struct { mutex *sync.Mutex } -// newDriver creates a new basic serial gobot driver. -func newDriver(a interface{}, name string, opts ...optionApplier) *driver { - d := driver{ +// NewDriver creates a new basic serial gobot driver. +func NewDriver(a interface{}, name string, + afterStart func() error, beforeHalt func() error, + opts ...OptionApplier, +) *Driver { + if afterStart == nil { + afterStart = func() error { return nil } + } + + if beforeHalt == nil { + beforeHalt = func() error { return nil } + } + d := Driver{ Commander: gobot.NewCommander(), connection: a, driverCfg: &configuration{name: gobot.DefaultName(name)}, - afterStart: func() error { return nil }, - beforeHalt: func() error { return nil }, + afterStart: afterStart, + beforeHalt: beforeHalt, mutex: &sync.Mutex{}, } @@ -57,23 +67,23 @@ func newDriver(a interface{}, name string, opts ...optionApplier) *driver { } // WithName is used to replace the default name of the driver. -func WithName(name string) optionApplier { +func WithName(name string) OptionApplier { return nameOption(name) } // Name returns the name of the driver. -func (d *driver) Name() string { +func (d *Driver) Name() string { return d.driverCfg.name } // SetName sets the name of the driver. // Deprecated: Please use option [serial.WithName] instead. -func (d *driver) SetName(name string) { +func (d *Driver) SetName(name string) { WithName(name).apply(d.driverCfg) } // Connection returns the connection of the driver. -func (d *driver) Connection() gobot.Connection { +func (d *Driver) Connection() gobot.Connection { if conn, ok := d.connection.(gobot.Connection); ok { return conn } @@ -83,7 +93,7 @@ func (d *driver) Connection() gobot.Connection { } // Start initializes the driver. -func (d *driver) Start() error { +func (d *Driver) Start() error { d.mutex.Lock() defer d.mutex.Unlock() @@ -93,7 +103,7 @@ func (d *driver) Start() error { } // Halt halts the driver. -func (d *driver) Halt() error { +func (d *Driver) Halt() error { d.mutex.Lock() defer d.mutex.Unlock() @@ -102,6 +112,10 @@ func (d *driver) Halt() error { return d.beforeHalt() } +func (d *Driver) Mutex() *sync.Mutex { + return d.mutex +} + // apply change the name in the configuration. func (o nameOption) apply(c *configuration) { c.name = string(o) diff --git a/drivers/serial/serial_driver_test.go b/drivers/serial/serial_driver_test.go index 7e965b609..a7109d94e 100644 --- a/drivers/serial/serial_driver_test.go +++ b/drivers/serial/serial_driver_test.go @@ -9,24 +9,25 @@ import ( "github.com/stretchr/testify/require" "gobot.io/x/gobot/v2" + "gobot.io/x/gobot/v2/drivers/serial/testutil" ) -var _ gobot.Driver = (*driver)(nil) +var _ gobot.Driver = (*Driver)(nil) -func initTestDriver() *driver { - a := newSerialTestAdaptor() - d := newDriver(a, "SERIAL_BASIC") +func initTestDriver() *Driver { + a := testutil.NewSerialTestAdaptor() + d := NewDriver(a, "SERIAL_BASIC", nil, nil) return d } -func Test_newDriver(t *testing.T) { +func TestNewDriver(t *testing.T) { // arrange const name = "mybot" - a := newSerialTestAdaptor() + a := testutil.NewSerialTestAdaptor() // act - d := newDriver(a, name) + d := NewDriver(a, name, nil, nil) // assert - assert.IsType(t, &driver{}, d) + assert.IsType(t, &Driver{}, d) assert.NotNil(t, d.driverCfg) assert.True(t, strings.HasPrefix(d.Name(), name)) assert.Equal(t, a, d.Connection()) @@ -44,9 +45,9 @@ func Test_newDriverWithName(t *testing.T) { name = "mybot" newName = "overwrite mybot" ) - a := newSerialTestAdaptor() + a := testutil.NewSerialTestAdaptor() // act - d := newDriver(a, name, WithName(newName)) + d := NewDriver(a, name, nil, nil, WithName(newName)) // assert assert.Equal(t, newName, d.Name()) } diff --git a/drivers/serial/sphero/LICENSE b/drivers/serial/sphero/LICENSE new file mode 100644 index 000000000..09094bb5e --- /dev/null +++ b/drivers/serial/sphero/LICENSE @@ -0,0 +1,13 @@ +Copyright (c) 2013-2018 The Hybrid Group + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/drivers/serial/sphero_driver.go b/drivers/serial/sphero/sphero_driver.go similarity index 82% rename from drivers/serial/sphero_driver.go rename to drivers/serial/sphero/sphero_driver.go index 6bdd969f5..c9a80cbaa 100644 --- a/drivers/serial/sphero_driver.go +++ b/drivers/serial/sphero/sphero_driver.go @@ -1,4 +1,4 @@ -package serial +package sphero import ( "bytes" @@ -8,13 +8,14 @@ import ( "time" "gobot.io/x/gobot/v2" - "gobot.io/x/gobot/v2/drivers/common/sphero" + "gobot.io/x/gobot/v2/drivers/common/spherocommon" + "gobot.io/x/gobot/v2/drivers/serial" ) type spheroSerialAdaptor interface { gobot.Adaptor - SerialReader - SerialWriter + serial.SerialReader + serial.SerialWriter IsConnected() bool } @@ -27,14 +28,15 @@ type packet struct { // SpheroDriver Represents a Sphero 2.0 type SpheroDriver struct { - *driver + *serial.Driver gobot.Eventer - seq uint8 - asyncResponse [][]uint8 - syncResponse [][]uint8 - packetChannel chan *packet - responseChannel chan []uint8 - originalColor []uint8 // Only used for calibration. + seq uint8 + asyncResponse [][]uint8 + syncResponse [][]uint8 + packetChannel chan *packet + responseChannel chan []uint8 + originalColor []uint8 // Only used for calibration. + shutdownWaitTime time.Duration } // NewSpheroDriver returns a new SpheroDriver given a Sphero Adaptor. @@ -51,19 +53,18 @@ type SpheroDriver struct { // "SetStabilization" - See SpheroDriver.SetStabilization // "SetDataStreaming" - See SpheroDriver.SetDataStreaming // "SetRotationRate" - See SpheroDriver.SetRotationRate -func NewSpheroDriver(a spheroSerialAdaptor, opts ...optionApplier) *SpheroDriver { +func NewSpheroDriver(a spheroSerialAdaptor, opts ...serial.OptionApplier) *SpheroDriver { d := &SpheroDriver{ - driver: newDriver(a, "Sphero", opts...), - Eventer: gobot.NewEventer(), - packetChannel: make(chan *packet, 1024), - responseChannel: make(chan []uint8, 1024), + Eventer: gobot.NewEventer(), + packetChannel: make(chan *packet, 1024), + responseChannel: make(chan []uint8, 1024), + shutdownWaitTime: 1 * time.Second, } - d.afterStart = d.initialize - d.beforeHalt = d.shutdown + d.Driver = serial.NewDriver(a, "Sphero", d.initialize, d.shutdown, opts...) - d.AddEvent(sphero.ErrorEvent) - d.AddEvent(sphero.CollisionEvent) - d.AddEvent(sphero.SensorDataEvent) + d.AddEvent(spherocommon.ErrorEvent) + d.AddEvent(spherocommon.CollisionEvent) + d.AddEvent(spherocommon.SensorDataEvent) //nolint:forcetypeassert // ok here d.AddCommand("SetRGB", func(params map[string]interface{}) interface{} { @@ -127,7 +128,7 @@ func NewSpheroDriver(a spheroSerialAdaptor, opts ...optionApplier) *SpheroDriver Pcnt := uint8(params["Pcnt"].(float64)) Mask2 := uint32(params["Mask2"].(float64)) - d.SetDataStreaming(sphero.DataStreamingConfig{N: N, M: M, Mask2: Mask2, Pcnt: Pcnt, Mask: Mask}) + d.SetDataStreaming(spherocommon.DataStreamingConfig{N: N, M: M, Mask2: Mask2, Pcnt: Pcnt, Mask: Mask}) return nil }) //nolint:forcetypeassert // ok here @@ -137,7 +138,7 @@ func NewSpheroDriver(a spheroSerialAdaptor, opts ...optionApplier) *SpheroDriver Y := int16(params["Y"].(float64)) YawTare := int16(params["YawTare"].(float64)) - d.ConfigureLocator(sphero.LocatorConfig{Flags: Flags, X: X, Y: Y, YawTare: YawTare}) + d.ConfigureLocator(spherocommon.LocatorConfig{Flags: Flags, X: X, Y: Y, YawTare: YawTare}) return nil }) @@ -200,7 +201,7 @@ func (d *SpheroDriver) Roll(speed uint8, heading uint16) { } // ConfigureLocator configures and enables the Locator -func (d *SpheroDriver) ConfigureLocator(lc sphero.LocatorConfig) { +func (d *SpheroDriver) ConfigureLocator(lc spherocommon.LocatorConfig) { buf := new(bytes.Buffer) if err := binary.Write(buf, binary.BigEndian, lc); err != nil { panic(err) @@ -210,7 +211,7 @@ func (d *SpheroDriver) ConfigureLocator(lc sphero.LocatorConfig) { } // SetDataStreaming enables sensor data streaming -func (d *SpheroDriver) SetDataStreaming(dsc sphero.DataStreamingConfig) { +func (d *SpheroDriver) SetDataStreaming(dsc spherocommon.DataStreamingConfig) { buf := new(bytes.Buffer) if err := binary.Write(buf, binary.BigEndian, dsc); err != nil { panic(err) @@ -225,7 +226,7 @@ func (d *SpheroDriver) Stop() { } // ConfigureCollisionDetection configures the sensitivity of the detection. -func (d *SpheroDriver) ConfigureCollisionDetection(cc sphero.CollisionConfig) { +func (d *SpheroDriver) ConfigureCollisionDetection(cc spherocommon.CollisionConfig) { d.sendCraftPacket([]uint8{cc.Method, cc.Xt, cc.Yt, cc.Xs, cc.Ys, cc.Dead}, 0x12) } @@ -264,16 +265,18 @@ func (d *SpheroDriver) FinishCalibration() { // // Emits the Events: // -// Collision sphero.CollisionPacket - On Collision Detected -// SensorData sphero.DataStreamingPacket - On Data Streaming event +// Collision spherocommon.CollisionPacket - On Collision Detected +// SensorData spherocommon.DataStreamingPacket - On Data Streaming event // Error error- On error while processing asynchronous response +// +// TODO: stop the go routines gracefully on shutdown() func (d *SpheroDriver) initialize() error { go func() { for { packet := <-d.packetChannel err := d.write(packet) if err != nil { - d.Publish(sphero.ErrorEvent, err) + d.Publish(spherocommon.ErrorEvent, err) } } }() @@ -292,7 +295,7 @@ func (d *SpheroDriver) initialize() error { body := d.readBody(header[4]) data := append(header, body...) checksum := data[len(data)-1] - if checksum != sphero.CalculateChecksum(data[2:len(data)-1]) { + if checksum != spherocommon.CalculateChecksum(data[2:len(data)-1]) { continue } switch header[1] { @@ -327,13 +330,12 @@ func (d *SpheroDriver) initialize() error { } // shutdown halts the SpheroDriver and sends a SpheroDriver.Stop command to the Sphero. -// Returns true on successful halt. func (d *SpheroDriver) shutdown() error { if d.adaptor().IsConnected() { gobot.Every(10*time.Millisecond, func() { d.Stop() }) - time.Sleep(1 * time.Second) + time.Sleep(d.shutdownWaitTime) } return nil } @@ -347,12 +349,12 @@ func (d *SpheroDriver) handleCollisionDetected(data []uint8) { if len(data) != 22 || data[4] != 17 { return } - var collision sphero.CollisionPacket + var collision spherocommon.CollisionPacket buffer := bytes.NewBuffer(data[5:]) // skip header if err := binary.Read(buffer, binary.BigEndian, &collision); err != nil { panic(err) } - d.Publish(sphero.CollisionEvent, collision) + d.Publish(spherocommon.CollisionEvent, collision) } func (d *SpheroDriver) handleDataStreaming(data []uint8) { @@ -360,12 +362,12 @@ func (d *SpheroDriver) handleDataStreaming(data []uint8) { if len(data) != 90 { return } - var dataPacket sphero.DataStreamingPacket + var dataPacket spherocommon.DataStreamingPacket buffer := bytes.NewBuffer(data[5:]) // skip header if err := binary.Read(buffer, binary.BigEndian, &dataPacket); err != nil { panic(err) } - d.Publish(sphero.SensorDataEvent, dataPacket) + d.Publish(spherocommon.SensorDataEvent, dataPacket) } func (d *SpheroDriver) getSyncResponse(packet *packet) []byte { @@ -397,15 +399,15 @@ func (d *SpheroDriver) craftPacket(body []uint8, cid byte) *packet { packet := &packet{ body: body, header: hdr, - checksum: sphero.CalculateChecksum(buf[2:]), + checksum: spherocommon.CalculateChecksum(buf[2:]), } return packet } func (d *SpheroDriver) write(packet *packet) error { - d.mutex.Lock() - defer d.mutex.Unlock() + d.Mutex().Lock() + defer d.Mutex().Unlock() buf := append(packet.header, packet.body...) buf = append(buf, packet.checksum) @@ -445,17 +447,17 @@ func (d *SpheroDriver) readNextChunk(length int) []uint8 { } func (d *SpheroDriver) adaptor() spheroSerialAdaptor { - if a, ok := d.connection.(spheroSerialAdaptor); ok { + if a, ok := d.Connection().(spheroSerialAdaptor); ok { return a } - log.Printf("%s has no Sphere serial connector\n", d.driverCfg.name) + log.Printf("%s has no Sphero serial connector\n", d.Name()) return nil } // spheroDefaultCollisionConfig returns a CollisionConfig with sensible collision defaults -func spheroDefaultCollisionConfig() sphero.CollisionConfig { - return sphero.CollisionConfig{ +func spheroDefaultCollisionConfig() spherocommon.CollisionConfig { + return spherocommon.CollisionConfig{ Method: 0x01, Xt: 0x80, Yt: 0x80, @@ -466,8 +468,8 @@ func spheroDefaultCollisionConfig() sphero.CollisionConfig { } // spheroDefaultLocatorConfig returns a LocatorConfig with defaults -func spheroDefaultLocatorConfig() sphero.LocatorConfig { - return sphero.LocatorConfig{ +func spheroDefaultLocatorConfig() spherocommon.LocatorConfig { + return spherocommon.LocatorConfig{ Flags: 0x01, X: 0x00, Y: 0x00, diff --git a/drivers/serial/sphero_driver_test.go b/drivers/serial/sphero/sphero_driver_test.go similarity index 80% rename from drivers/serial/sphero_driver_test.go rename to drivers/serial/sphero/sphero_driver_test.go index 55f57d23c..9ed175bf8 100644 --- a/drivers/serial/sphero_driver_test.go +++ b/drivers/serial/sphero/sphero_driver_test.go @@ -1,5 +1,5 @@ //nolint:forcetypeassert // ok here -package serial +package sphero import ( "bytes" @@ -11,14 +11,18 @@ import ( "github.com/stretchr/testify/require" "gobot.io/x/gobot/v2" - "gobot.io/x/gobot/v2/drivers/common/sphero" + "gobot.io/x/gobot/v2/drivers/common/spherocommon" + "gobot.io/x/gobot/v2/drivers/serial" + "gobot.io/x/gobot/v2/drivers/serial/testutil" ) var _ gobot.Driver = (*SpheroDriver)(nil) func initTestSpheroDriver() *SpheroDriver { - a := newSerialTestAdaptor() - return NewSpheroDriver(a) + a := testutil.NewSerialTestAdaptor() + d := NewSpheroDriver(a) + d.shutdownWaitTime = 0 // to speed up the tests + return d } func TestNewSpheroDriver(t *testing.T) { @@ -32,9 +36,9 @@ func TestNewSpheroDriverWithName(t *testing.T) { // tests for options can also be done by call of "WithOption(val).apply(cfg)". // arrange const newName = "new name" - a := newSerialTestAdaptor() + a := testutil.NewSerialTestAdaptor() // act - d := NewSpheroDriver(a, WithName(newName)) + d := NewSpheroDriver(a, serial.WithName(newName)) // assert assert.Equal(t, newName, d.Name()) } @@ -99,20 +103,21 @@ func TestSpheroStart(t *testing.T) { } func TestSpheroHalt(t *testing.T) { - a := newSerialTestAdaptor() - a.isConnected = true + a := testutil.NewSerialTestAdaptor() + _ = a.Connect() d := NewSpheroDriver(a) + d.shutdownWaitTime = 0 // to speed up the tests require.NoError(t, d.Halt()) } func TestSpheroSetDataStreaming(t *testing.T) { d := initTestSpheroDriver() - d.SetDataStreaming(sphero.DefaultDataStreamingConfig()) + d.SetDataStreaming(spherocommon.DefaultDataStreamingConfig()) data := <-d.packetChannel buf := new(bytes.Buffer) - _ = binary.Write(buf, binary.BigEndian, sphero.DefaultDataStreamingConfig()) + _ = binary.Write(buf, binary.BigEndian, spherocommon.DefaultDataStreamingConfig()) assert.Equal(t, buf.Bytes(), data.body) @@ -128,7 +133,7 @@ func TestSpheroSetDataStreaming(t *testing.T) { assert.Nil(t, ret) data = <-d.packetChannel - dconfig := sphero.DataStreamingConfig{N: 100, M: 200, Mask: 300, Pcnt: 255, Mask2: 400} + dconfig := spherocommon.DataStreamingConfig{N: 100, M: 200, Mask: 300, Pcnt: 255, Mask2: 400} buf = new(bytes.Buffer) _ = binary.Write(buf, binary.BigEndian, dconfig) @@ -156,7 +161,7 @@ func TestSpheroConfigureLocator(t *testing.T) { assert.Nil(t, ret) data = <-d.packetChannel - lconfig := sphero.LocatorConfig{Flags: 1, X: 100, Y: 100, YawTare: 0} + lconfig := spherocommon.LocatorConfig{Flags: 1, X: 100, Y: 100, YawTare: 0} buf = new(bytes.Buffer) _ = binary.Write(buf, binary.BigEndian, lconfig) diff --git a/drivers/serial/testutil/testutil.go b/drivers/serial/testutil/testutil.go new file mode 100644 index 000000000..155a9ad7f --- /dev/null +++ b/drivers/serial/testutil/testutil.go @@ -0,0 +1,62 @@ +package testutil + +import "fmt" + +type serialTestAdaptor struct { + isConnected bool + name string + + simulateConnectErr bool + simulateReadErr bool + simulateWriteErr bool +} + +func NewSerialTestAdaptor() *serialTestAdaptor { + return &serialTestAdaptor{} +} + +func (t *serialTestAdaptor) SetSimulateConnectError(val bool) { + t.simulateConnectErr = val +} + +func (t *serialTestAdaptor) SetSimulateReadError(val bool) { + t.simulateReadErr = val +} + +func (t *serialTestAdaptor) SetSimulateWriteError(val bool) { + t.simulateWriteErr = val +} + +func (t *serialTestAdaptor) IsConnected() bool { + return t.isConnected +} + +func (t *serialTestAdaptor) SerialRead(b []byte) (int, error) { + if t.simulateReadErr { + return 0, fmt.Errorf("read error") + } + + return len(b), nil +} + +func (t *serialTestAdaptor) SerialWrite(b []byte) (int, error) { + if t.simulateWriteErr { + return 0, fmt.Errorf("write error") + } + + return len(b), nil +} + +// gobot.Adaptor interfaces +func (t *serialTestAdaptor) Connect() error { + if t.simulateConnectErr { + return fmt.Errorf("connect error") + } + + t.isConnected = true + return nil +} + +func (t *serialTestAdaptor) Finalize() error { return nil } +func (t *serialTestAdaptor) Name() string { return t.name } +func (t *serialTestAdaptor) SetName(n string) { t.name = n } diff --git a/examples/leap_sphero.go b/examples/leap_sphero.go index b4aae6170..07f6ea5ec 100644 --- a/examples/leap_sphero.go +++ b/examples/leap_sphero.go @@ -10,7 +10,7 @@ import ( "math" "gobot.io/x/gobot/v2" - "gobot.io/x/gobot/v2/drivers/serial" + "gobot.io/x/gobot/v2/drivers/serial/sphero" "gobot.io/x/gobot/v2/platforms/leap" "gobot.io/x/gobot/v2/platforms/serialport" ) @@ -20,7 +20,7 @@ func main() { spheroAdaptor := serialport.NewAdaptor("/dev/tty.Sphero-YBW-RN-SPP") leapDriver := leap.NewDriver(leapAdaptor) - spheroDriver := serial.NewSpheroDriver(spheroAdaptor) + spheroDriver := sphero.NewSpheroDriver(spheroAdaptor) work := func() { _ = leapDriver.On(leap.MessageEvent, func(data interface{}) { diff --git a/examples/neurosky.go b/examples/serialport_mindwave.go similarity index 81% rename from examples/neurosky.go rename to examples/serialport_mindwave.go index 4c492230f..c27c685a5 100644 --- a/examples/neurosky.go +++ b/examples/serialport_mindwave.go @@ -10,12 +10,13 @@ import ( "fmt" "gobot.io/x/gobot/v2" - "gobot.io/x/gobot/v2/platforms/neurosky" + "gobot.io/x/gobot/v2/drivers/serial/neurosky" + "gobot.io/x/gobot/v2/platforms/serialport" ) func main() { - adaptor := neurosky.NewAdaptor("/dev/rfcomm0") - neuro := neurosky.NewDriver(adaptor) + adaptor := serialport.NewAdaptor("/dev/rfcomm0", serialport.WithName("Neurosky"), serialport.WithBaudRate(57600)) + neuro := neurosky.NewMindWaveDriver(adaptor) work := func() { _ = neuro.On(neuro.Event("extended"), func(data interface{}) { @@ -37,7 +38,7 @@ func main() { fmt.Println("Wave", data) }) _ = neuro.On(neuro.Event("eeg"), func(data interface{}) { - eeg := data.(neurosky.EEGData) + eeg := data.(neurosky.MindWaveEEGData) fmt.Println("Delta", eeg.Delta) fmt.Println("Theta", eeg.Theta) fmt.Println("LoAlpha", eeg.LoAlpha) diff --git a/examples/serial_sphero.go b/examples/serialport_sphero.go similarity index 68% rename from examples/serial_sphero.go rename to examples/serialport_sphero.go index d44143497..af709cc13 100644 --- a/examples/serial_sphero.go +++ b/examples/serialport_sphero.go @@ -11,23 +11,23 @@ import ( "time" "gobot.io/x/gobot/v2" - "gobot.io/x/gobot/v2/drivers/common/sphero" - "gobot.io/x/gobot/v2/drivers/serial" + "gobot.io/x/gobot/v2/drivers/common/spherocommon" + "gobot.io/x/gobot/v2/drivers/serial/sphero" "gobot.io/x/gobot/v2/platforms/serialport" ) func main() { adaptor := serialport.NewAdaptor("/dev/rfcomm0") - spheroDriver := serial.NewSpheroDriver(adaptor) + spheroDriver := sphero.NewSpheroDriver(adaptor) work := func() { - spheroDriver.SetDataStreaming(sphero.DefaultDataStreamingConfig()) + spheroDriver.SetDataStreaming(spherocommon.DefaultDataStreamingConfig()) - _ = spheroDriver.On(sphero.CollisionEvent, func(data interface{}) { + _ = spheroDriver.On(spherocommon.CollisionEvent, func(data interface{}) { fmt.Printf("Collision! %+v\n", data) }) - _ = spheroDriver.On(sphero.SensorDataEvent, func(data interface{}) { + _ = spheroDriver.On(spherocommon.SensorDataEvent, func(data interface{}) { fmt.Printf("Streaming Data! %+v\n", data) }) diff --git a/examples/serial_sphero_api.go b/examples/serialport_sphero_api.go similarity index 89% rename from examples/serial_sphero_api.go rename to examples/serialport_sphero_api.go index 558ae86d3..a6f2f3dfa 100644 --- a/examples/serial_sphero_api.go +++ b/examples/serialport_sphero_api.go @@ -9,7 +9,7 @@ package main import ( "gobot.io/x/gobot/v2" "gobot.io/x/gobot/v2/api" - "gobot.io/x/gobot/v2/drivers/serial" + "gobot.io/x/gobot/v2/drivers/serial/sphero" "gobot.io/x/gobot/v2/platforms/serialport" ) @@ -23,7 +23,7 @@ func main() { for name, port := range spheros { spheroAdaptor := serialport.NewAdaptor(port) - spheroDriver := serial.NewSpheroDriver(spheroAdaptor) + spheroDriver := sphero.NewSpheroDriver(spheroAdaptor) work := func() { spheroDriver.SetRGB(uint8(255), uint8(0), uint8(0)) diff --git a/examples/serial_sphero_calibration.go b/examples/serialport_sphero_calibration.go similarity index 93% rename from examples/serial_sphero_calibration.go rename to examples/serialport_sphero_calibration.go index d81830b27..ee3f46bd7 100644 --- a/examples/serial_sphero_calibration.go +++ b/examples/serialport_sphero_calibration.go @@ -9,7 +9,7 @@ package main import ( "gobot.io/x/gobot/v2" "gobot.io/x/gobot/v2/api" - "gobot.io/x/gobot/v2/drivers/serial" + "gobot.io/x/gobot/v2/drivers/serial/sphero" "gobot.io/x/gobot/v2/platforms/keyboard" "gobot.io/x/gobot/v2/platforms/serialport" ) @@ -20,7 +20,7 @@ func main() { a.Start() ballConn := serialport.NewAdaptor("/dev/rfcomm0") - ball := serial.NewSpheroDriver(ballConn) + ball := sphero.NewSpheroDriver(ballConn) keys := keyboard.NewDriver() diff --git a/examples/serial_sphero_conways.go b/examples/serialport_sphero_conways.go similarity index 88% rename from examples/serial_sphero_conways.go rename to examples/serialport_sphero_conways.go index d7acbf908..47af59964 100644 --- a/examples/serial_sphero_conways.go +++ b/examples/serialport_sphero_conways.go @@ -11,8 +11,9 @@ import ( "time" "gobot.io/x/gobot/v2" - "gobot.io/x/gobot/v2/drivers/common/sphero" + "gobot.io/x/gobot/v2/drivers/common/spherocommon" "gobot.io/x/gobot/v2/drivers/serial" + "gobot.io/x/gobot/v2/drivers/serial/sphero" "gobot.io/x/gobot/v2/platforms/serialport" ) @@ -20,7 +21,7 @@ type conway struct { alive bool age int contacts int - cell *serial.SpheroDriver + cell *sphero.SpheroDriver } func main() { @@ -35,7 +36,7 @@ func main() { for _, port := range spheros { spheroAdaptor := serialport.NewAdaptor(port) - cell := serial.NewSpheroDriver(spheroAdaptor, serial.WithName("Sphero"+port)) + cell := sphero.NewSpheroDriver(spheroAdaptor, serial.WithName("Sphero"+port)) work := func() { conway := new(conway) @@ -43,7 +44,7 @@ func main() { conway.birth() - _ = cell.On(sphero.CollisionEvent, func(data interface{}) { + _ = cell.On(spherocommon.CollisionEvent, func(data interface{}) { conway.contact() }) diff --git a/examples/serial_sphero_dpad.go b/examples/serialport_sphero_dpad.go similarity index 91% rename from examples/serial_sphero_dpad.go rename to examples/serialport_sphero_dpad.go index d24673d81..a6206dec7 100644 --- a/examples/serial_sphero_dpad.go +++ b/examples/serialport_sphero_dpad.go @@ -11,7 +11,7 @@ import ( "gobot.io/x/gobot/v2" "gobot.io/x/gobot/v2/api" - "gobot.io/x/gobot/v2/drivers/serial" + "gobot.io/x/gobot/v2/drivers/serial/sphero" "gobot.io/x/gobot/v2/platforms/serialport" ) @@ -21,7 +21,7 @@ func main() { a.Start() conn := serialport.NewAdaptor("/dev/rfcomm0") - ball := serial.NewSpheroDriver(conn) + ball := sphero.NewSpheroDriver(conn) robot := gobot.NewRobot("sphero-dpad", []gobot.Connection{conn}, diff --git a/examples/serial_sphero_master.go b/examples/serialport_sphero_master.go similarity index 82% rename from examples/serial_sphero_master.go rename to examples/serialport_sphero_master.go index d57be5d82..a4c0e42e8 100644 --- a/examples/serial_sphero_master.go +++ b/examples/serialport_sphero_master.go @@ -10,7 +10,7 @@ import ( "time" "gobot.io/x/gobot/v2" - "gobot.io/x/gobot/v2/drivers/serial" + "gobot.io/x/gobot/v2/drivers/serial/sphero" "gobot.io/x/gobot/v2/platforms/serialport" ) @@ -23,7 +23,7 @@ func main() { for name, port := range spheros { spheroAdaptor := serialport.NewAdaptor(port) - spheroDriver := serial.NewSpheroDriver(spheroAdaptor) + spheroDriver := sphero.NewSpheroDriver(spheroAdaptor) work := func() { spheroDriver.SetRGB(uint8(255), uint8(0), uint8(0)) @@ -41,7 +41,7 @@ func main() { robot := gobot.NewRobot("", func() { gobot.Every(1*time.Second, func() { - sphero := master.Robot("Sphero-BPO").Device("sphero").(*serial.SpheroDriver) + sphero := master.Robot("Sphero-BPO").Device("sphero").(*sphero.SpheroDriver) sphero.SetRGB(uint8(gobot.Rand(255)), uint8(gobot.Rand(255)), uint8(gobot.Rand(255)), diff --git a/examples/serial_sphero_multiple.go b/examples/serialport_sphero_multiple.go similarity index 82% rename from examples/serial_sphero_multiple.go rename to examples/serialport_sphero_multiple.go index 04cd68339..65fef75dd 100644 --- a/examples/serial_sphero_multiple.go +++ b/examples/serialport_sphero_multiple.go @@ -12,19 +12,20 @@ import ( "gobot.io/x/gobot/v2" "gobot.io/x/gobot/v2/api" - "gobot.io/x/gobot/v2/drivers/common/sphero" + "gobot.io/x/gobot/v2/drivers/common/spherocommon" "gobot.io/x/gobot/v2/drivers/serial" + "gobot.io/x/gobot/v2/drivers/serial/sphero" "gobot.io/x/gobot/v2/platforms/serialport" ) func NewSwarmBot(port string) *gobot.Robot { spheroAdaptor := serialport.NewAdaptor(port) - spheroDriver := serial.NewSpheroDriver(spheroAdaptor, serial.WithName("Sphero"+port)) + spheroDriver := sphero.NewSpheroDriver(spheroAdaptor, serial.WithName("Sphero"+port)) work := func() { spheroDriver.Stop() - _ = spheroDriver.On(sphero.CollisionEvent, func(data interface{}) { + _ = spheroDriver.On(spherocommon.CollisionEvent, func(data interface{}) { fmt.Println("Collision Detected!") }) diff --git a/platforms/bleclient/helper_test.go b/platforms/bleclient/helpers_test.go similarity index 100% rename from platforms/bleclient/helper_test.go rename to platforms/bleclient/helpers_test.go diff --git a/platforms/jetson/jetson_adaptor.go b/platforms/jetson/jetson_adaptor.go index 6ef35239a..1d81d3056 100644 --- a/platforms/jetson/jetson_adaptor.go +++ b/platforms/jetson/jetson_adaptor.go @@ -77,7 +77,7 @@ func NewAdaptor(opts ...interface{}) *Adaptor { return a } -// Name returns the Adaptor's name +// Name returns the Adaptors name func (a *Adaptor) Name() string { a.mutex.Lock() defer a.mutex.Unlock() @@ -85,7 +85,7 @@ func (a *Adaptor) Name() string { return a.name } -// SetName sets the Adaptor's name +// SetName sets the Adaptors name func (a *Adaptor) SetName(n string) { a.mutex.Lock() defer a.mutex.Unlock() diff --git a/platforms/mqtt/mqtt_adaptor.go b/platforms/mqtt/mqtt_adaptor.go index 3e93efe46..3ca206c57 100644 --- a/platforms/mqtt/mqtt_adaptor.go +++ b/platforms/mqtt/mqtt_adaptor.go @@ -60,10 +60,10 @@ func NewAdaptorWithAuth(host, clientID, username, password string) *Adaptor { } } -// Name returns the MQTT Adaptor's name +// Name returns the MQTT Adaptors name func (a *Adaptor) Name() string { return a.name } -// SetName sets the MQTT Adaptor's name +// SetName sets the MQTT Adaptors name func (a *Adaptor) SetName(n string) { a.name = n } // Port returns the Host name diff --git a/platforms/neurosky/README.md b/platforms/neurosky/README.md index b0cc9f33a..d21e32382 100644 --- a/platforms/neurosky/README.md +++ b/platforms/neurosky/README.md @@ -3,7 +3,7 @@ NeuroSky delivers fully integrated, single chip EEG biosensors. NeuroSky enables its partners and developers to bring their brainwave application ideas to market with the shortest amount of time, and lowest end consumer price. -This package contains the Gobot adaptor and driver for the [Neurosky Mindwave Mobile EEG](http://store.neurosky.com/products/mindwave-mobile). +This package contains the Gobot adaptor and driver for the [Neurosky MindWave Mobile EEG](http://store.neurosky.com/products/mindwave-mobile). ## How to Install @@ -13,31 +13,31 @@ Please refer to the main [README.md](https://github.com/hybridgroup/gobot/blob/r ### OSX -In order to allow Gobot running on your Mac to access the Mindwave, go to "Bluetooth > Open Bluetooth Preferences > Sharing Setup" +In order to allow Gobot running on your Mac to access the MindWave, go to "Bluetooth > Open Bluetooth Preferences > Sharing Setup" and make sure that "Bluetooth Sharing" is checked. -Now you must pair with the Mindwave. Open System Preferences > Bluetooth. Now with the Bluetooth devices windows open, hold -the On/Pair button on the Mindwave towards the On/Pair text until you see "Mindwave" pop up as available devices. Pair with -that device. Once paired your Mindwave will be accessable through the serial device similarly named as `/dev/tty.MindWaveMobile-DevA` +Now you must pair with the MindWave. Open System Preferences > Bluetooth. Now with the Bluetooth devices windows open, hold +the On/Pair button on the MindWave towards the On/Pair text until you see "MindWave" pop up as available devices. Pair with +that device. Once paired your MindWave will be accessable through the serial device similarly named as `/dev/tty.MindWaveMobile-DevA` ### Ubuntu -Connecting to the Mindwave from Ubuntu or any other Linux-based OS can be done entirely from the command line using [Gort](https://gobot.io/x/gort) +Connecting to the MindWave from Ubuntu or any other Linux-based OS can be done entirely from the command line using [Gort](https://gobot.io/x/gort) CLI commands. Here are the steps. -Find the address of the Mindwave, by using: +Find the address of the MindWave, by using: ```sh gort scan bluetooth ``` -Pair to Mindwave using this command (substituting the actual address of your Mindwave): +Pair to MindWave using this command (substituting the actual address of your MindWave): ```sh gort bluetooth pair
``` -Connect to the Mindwave using this command (substituting the actual address of your Mindwave): +Connect to the MindWave using this command (substituting the actual address of your MindWave): ```sh gort bluetooth connect
@@ -45,68 +45,9 @@ gort bluetooth connect
### Windows -You should be able to pair your Mindwave using your normal system tray applet for Bluetooth, and then connect to the +You should be able to pair your MindWave using your normal system tray applet for Bluetooth, and then connect to the COM port that is bound to the device, such as `COM3`. ## How to Use -This small program lets you connect the Neurosky an load data. - -```go -package main - -import ( - "fmt" - - "gobot.io/x/gobot/v2" - "gobot.io/x/gobot/v2/platforms/neurosky" -) - -func main() { - adaptor := neurosky.NewAdaptor("/dev/rfcomm0") - neuro := neurosky.NewDriver(adaptor) - - work := func() { - _ = neuro.On(neuro.Event("extended"), func(data interface{}) { - fmt.Println("Extended", data) - }) - _ = neuro.On(neuro.Event("signal"), func(data interface{}) { - fmt.Println("Signal", data) - }) - _ = neuro.On(neuro.Event("attention"), func(data interface{}) { - fmt.Println("Attention", data) - }) - _ = neuro.On(neuro.Event("meditation"), func(data interface{}) { - fmt.Println("Meditation", data) - }) - _ = neuro.On(neuro.Event("blink"), func(data interface{}) { - fmt.Println("Blink", data) - }) - _ = neuro.On(neuro.Event("wave"), func(data interface{}) { - fmt.Println("Wave", data) - }) - _ = neuro.On(neuro.Event("eeg"), func(data interface{}) { - eeg := data.(neurosky.EEGData) - fmt.Println("Delta", eeg.Delta) - fmt.Println("Theta", eeg.Theta) - fmt.Println("LoAlpha", eeg.LoAlpha) - fmt.Println("HiAlpha", eeg.HiAlpha) - fmt.Println("LoBeta", eeg.LoBeta) - fmt.Println("HiBeta", eeg.HiBeta) - fmt.Println("LoGamma", eeg.LoGamma) - fmt.Println("MidGamma", eeg.MidGamma) - fmt.Println("\n") - }) - } - - robot := gobot.NewRobot("brainBot", - []gobot.Connection{adaptor}, - []gobot.Device{neuro}, - work, - ) - - if err := robot.Start(); err != nil { - panic(err) - } -} -``` +Please refer to the provided example `examples/serialport_neurosky.go`. diff --git a/platforms/neurosky/doc.go b/platforms/neurosky/doc.go deleted file mode 100644 index bee9e928a..000000000 --- a/platforms/neurosky/doc.go +++ /dev/null @@ -1,70 +0,0 @@ -/* -Package neurosky contains the Gobot adaptor and driver for the Neurosky Mindwave Mobile EEG. - -Installing: - - Please refer to the main [README.md](https://github.com/hybridgroup/gobot/blob/release/README.md) - -Example: - - package main - - import ( - "fmt" - - "gobot.io/x/gobot/v2" - "gobot.io/x/gobot/v2/platforms/neurosky" - ) - - func main() { - adaptor := neurosky.NewAdaptor("/dev/rfcomm0") - neuro := neurosky.NewDriver(adaptor) - - work := func() { - _ = neuro.On(neuro.Event("extended"), func(data interface{}) { - fmt.Println("Extended", data) - }) - _ = neuro.On(neuro.Event("signal"), func(data interface{}) { - fmt.Println("Signal", data) - }) - _ = neuro.On(neuro.Event("attention"), func(data interface{}) { - fmt.Println("Attention", data) - }) - _ = neuro.On(neuro.Event("meditation"), func(data interface{}) { - fmt.Println("Meditation", data) - }) - _ = neuro.On(neuro.Event("blink"), func(data interface{}) { - fmt.Println("Blink", data) - }) - _ = neuro.On(neuro.Event("wave"), func(data interface{}) { - fmt.Println("Wave", data) - }) - _ = neuro.On(neuro.Event("eeg"), func(data interface{}) { - eeg := data.(neurosky.EEGData) - fmt.Println("Delta", eeg.Delta) - fmt.Println("Theta", eeg.Theta) - fmt.Println("LoAlpha", eeg.LoAlpha) - fmt.Println("HiAlpha", eeg.HiAlpha) - fmt.Println("LoBeta", eeg.LoBeta) - fmt.Println("HiBeta", eeg.HiBeta) - fmt.Println("LoGamma", eeg.LoGamma) - fmt.Println("MidGamma", eeg.MidGamma) - fmt.Println("\n") - }) - } - - robot := gobot.NewRobot("brainBot", - []gobot.Connection{adaptor}, - []gobot.Device{neuro}, - work, - ) - - if err := robot.Start(); err != nil { - panic(err) - } - } - -For further information refer to neuroky README: -https://github.com/hybridgroup/gobot/blob/master/platforms/neurosky/README.md -*/ -package neurosky // import "gobot.io/x/gobot/v2/platforms/neurosky" diff --git a/platforms/neurosky/neurosky_adaptor.go b/platforms/neurosky/neurosky_adaptor.go deleted file mode 100644 index a0bac066f..000000000 --- a/platforms/neurosky/neurosky_adaptor.go +++ /dev/null @@ -1,52 +0,0 @@ -// Package neurosky is the Gobot platform for the Neurosky Mindwave EEG -package neurosky - -import ( - "io" - - "go.bug.st/serial" -) - -// Adaptor is the Gobot Adaptor for the Neurosky Mindwave -type Adaptor struct { - name string - port string - sp io.ReadWriteCloser - connect func(*Adaptor) (io.ReadWriteCloser, error) -} - -// NewAdaptor creates a neurosky adaptor with specified port -func NewAdaptor(port string) *Adaptor { - return &Adaptor{ - name: "Neurosky", - port: port, - connect: func(n *Adaptor) (io.ReadWriteCloser, error) { - return serial.Open(n.Port(), &serial.Mode{BaudRate: 57600}) - }, - } -} - -// Name returns the Adaptor Name -func (n *Adaptor) Name() string { return n.name } - -// SetName sets the Adaptor Name -func (n *Adaptor) SetName(name string) { n.name = name } - -// Port returns the Adaptor port -func (n *Adaptor) Port() string { return n.port } - -// Connect returns true if connection to device is successful -func (n *Adaptor) Connect() error { - sp, err := n.connect(n) - if err != nil { - return err - } - - n.sp = sp - return nil -} - -// Finalize returns true if device finalization is successful -func (n *Adaptor) Finalize() error { - return n.sp.Close() -} diff --git a/platforms/neurosky/neurosky_adaptor_test.go b/platforms/neurosky/neurosky_adaptor_test.go deleted file mode 100644 index e80ca31e5..000000000 --- a/platforms/neurosky/neurosky_adaptor_test.go +++ /dev/null @@ -1,94 +0,0 @@ -package neurosky - -import ( - "errors" - "io" - "strings" - "sync" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "gobot.io/x/gobot/v2" -) - -var _ gobot.Adaptor = (*Adaptor)(nil) - -type NullReadWriteCloser struct { - mtx sync.Mutex - readError error - closeError error -} - -func (n *NullReadWriteCloser) ReadError(e error) { - n.mtx.Lock() - defer n.mtx.Unlock() - n.readError = e -} - -func (n *NullReadWriteCloser) CloseError(e error) { - n.mtx.Lock() - defer n.mtx.Unlock() - n.closeError = e -} - -func (n *NullReadWriteCloser) Write(p []byte) (int, error) { - return len(p), nil -} - -func (n *NullReadWriteCloser) Read(b []byte) (int, error) { - n.mtx.Lock() - defer n.mtx.Unlock() - return len(b), n.readError -} - -func (n *NullReadWriteCloser) Close() error { - n.mtx.Lock() - defer n.mtx.Unlock() - return n.closeError -} - -func initTestNeuroskyAdaptor() *Adaptor { - a := NewAdaptor("/dev/null") - a.connect = func(n *Adaptor) (io.ReadWriteCloser, error) { - return &NullReadWriteCloser{}, nil - } - return a -} - -func TestNeuroskyAdaptor(t *testing.T) { - a := NewAdaptor("/dev/null") - assert.Equal(t, "/dev/null", a.Port()) -} - -func TestNeuroskyAdaptorName(t *testing.T) { - a := NewAdaptor("/dev/null") - assert.True(t, strings.HasPrefix(a.Name(), "Neurosky")) - a.SetName("NewName") - assert.Equal(t, "NewName", a.Name()) -} - -func TestNeuroskyAdaptorConnect(t *testing.T) { - a := initTestNeuroskyAdaptor() - require.NoError(t, a.Connect()) - - a.connect = func(n *Adaptor) (io.ReadWriteCloser, error) { - return nil, errors.New("connection error") - } - require.ErrorContains(t, a.Connect(), "connection error") -} - -func TestNeuroskyAdaptorFinalize(t *testing.T) { - rwc := &NullReadWriteCloser{} - a := NewAdaptor("/dev/null") - a.connect = func(n *Adaptor) (io.ReadWriteCloser, error) { - return rwc, nil - } - _ = a.Connect() - require.NoError(t, a.Finalize()) - - rwc.CloseError(errors.New("close error")) - _ = a.Connect() - require.ErrorContains(t, a.Finalize(), "close error") -} diff --git a/platforms/neurosky/neurosky_driver.go b/platforms/neurosky/neurosky_driver.go deleted file mode 100644 index 893f28bdb..000000000 --- a/platforms/neurosky/neurosky_driver.go +++ /dev/null @@ -1,222 +0,0 @@ -package neurosky - -import ( - "bytes" - - "gobot.io/x/gobot/v2" -) - -const ( - // BTSync is the sync code - BTSync byte = 0xAA - - // CodeEx Extended code - CodeEx byte = 0x55 - - // CodeSignalQuality POOR_SIGNAL quality 0-255 - CodeSignalQuality byte = 0x02 - - // CodeAttention ATTENTION eSense 0-100 - CodeAttention byte = 0x04 - - // CodeMeditation MEDITATION eSense 0-100 - CodeMeditation byte = 0x05 - - // CodeBlink BLINK strength 0-255 - CodeBlink byte = 0x16 - - // CodeWave RAW wave value: 2-byte big-endian 2s-complement - CodeWave byte = 0x80 - - // CodeAsicEEG ASIC EEG POWER 8 3-byte big-endian integers - CodeAsicEEG byte = 0x83 - - // Extended event - Extended = "extended" - - // Signal event - Signal = "signal" - - // Attention event - Attention = "attention" - - // Meditation event - Meditation = "meditation" - - // Blink event - Blink = "blink" - - // Wave event - Wave = "wave" - - // EEG event - EEG = "eeg" - - // Error event - Error = "error" -) - -// Driver is the Gobot Driver for the Mindwave -type Driver struct { - name string - connection gobot.Connection - gobot.Eventer -} - -// EEGData is the EEG raw data returned from the Mindwave -type EEGData struct { - Delta int - Theta int - LoAlpha int - HiAlpha int - LoBeta int - HiBeta int - LoGamma int - MidGamma int -} - -// NewDriver creates a Neurosky Driver -// and adds the following events: -// -// extended - user's current extended level -// signal - shows signal strength -// attention - user's current attention level -// meditation - user's current meditation level -// blink - user's current blink level -// wave - shows wave data -// eeg - showing eeg data -func NewDriver(a *Adaptor) *Driver { - n := &Driver{ - name: "Neurosky", - connection: a, - Eventer: gobot.NewEventer(), - } - - n.AddEvent(Extended) - n.AddEvent(Signal) - n.AddEvent(Attention) - n.AddEvent(Meditation) - n.AddEvent(Blink) - n.AddEvent(Wave) - n.AddEvent(EEG) - n.AddEvent(Error) - - return n -} - -// Connection returns the Driver's connection -func (n *Driver) Connection() gobot.Connection { return n.connection } - -// Name returns the Driver name -func (n *Driver) Name() string { return n.name } - -// SetName sets the Driver name -func (n *Driver) SetName(name string) { n.name = name } - -// adaptor returns neurosky adaptor -func (n *Driver) adaptor() *Adaptor { - //nolint:forcetypeassert // ok here - return n.Connection().(*Adaptor) -} - -// Start creates a go routine to listen from serial port -// and parse buffer readings -func (n *Driver) Start() error { - go func() { - for { - buff := make([]byte, 1024) - _, err := n.adaptor().sp.Read(buff) - if err != nil { - n.Publish(n.Event("error"), err) - } else { - if err := n.parse(bytes.NewBuffer(buff)); err != nil { - panic(err) - } - } - } - }() - return nil -} - -// Halt stops neurosky driver (void) -func (n *Driver) Halt() error { return nil } - -// parse converts bytes buffer into packets until no more data is present -func (n *Driver) parse(buf *bytes.Buffer) error { - for buf.Len() > 2 { - b1, _ := buf.ReadByte() - b2, _ := buf.ReadByte() - if b1 == BTSync && b2 == BTSync { - length, _ := buf.ReadByte() - payload := make([]byte, length) - if _, err := buf.Read(payload); err != nil { - return err - } - // checksum, _ := buf.ReadByte() - buf.Next(1) - if err := n.parsePacket(bytes.NewBuffer(payload)); err != nil { - panic(err) - } - } - } - - return nil -} - -// parsePacket publishes event according to data parsed -func (n *Driver) parsePacket(buf *bytes.Buffer) error { - for buf.Len() > 0 { - b, _ := buf.ReadByte() - switch b { - case CodeEx: - n.Publish(n.Event("extended"), nil) - case CodeSignalQuality: - ret, _ := buf.ReadByte() - n.Publish(n.Event("signal"), ret) - case CodeAttention: - ret, _ := buf.ReadByte() - n.Publish(n.Event("attention"), ret) - case CodeMeditation: - ret, _ := buf.ReadByte() - n.Publish(n.Event("meditation"), ret) - case CodeBlink: - ret, _ := buf.ReadByte() - n.Publish(n.Event("blink"), ret) - case CodeWave: - buf.Next(1) - ret := make([]byte, 2) - if _, err := buf.Read(ret); err != nil { - return err - } - n.Publish(n.Event("wave"), int16(ret[0])<<8|int16(ret[1])) - case CodeAsicEEG: - ret := make([]byte, 25) - i, _ := buf.Read(ret) - if i == 25 { - n.Publish(n.Event("eeg"), n.parseEEG(ret)) - } - } - } - - return nil -} - -// parseEEG returns data converted into EEG map -func (n *Driver) parseEEG(data []byte) EEGData { - return EEGData{ - Delta: n.parse3ByteInteger(data[0:3]), - Theta: n.parse3ByteInteger(data[3:6]), - LoAlpha: n.parse3ByteInteger(data[6:9]), - HiAlpha: n.parse3ByteInteger(data[9:12]), - LoBeta: n.parse3ByteInteger(data[12:15]), - HiBeta: n.parse3ByteInteger(data[15:18]), - LoGamma: n.parse3ByteInteger(data[18:21]), - MidGamma: n.parse3ByteInteger(data[21:25]), - } -} - -func (n *Driver) parse3ByteInteger(data []byte) int { - return ((int(data[0]) << 16) | - (((1 << 16) - 1) & (int(data[1]) << 8)) | - (((1 << 8) - 1) & int(data[2]))) -} diff --git a/platforms/raspi/raspi_adaptor.go b/platforms/raspi/raspi_adaptor.go index 1d917b19a..b0e261463 100644 --- a/platforms/raspi/raspi_adaptor.go +++ b/platforms/raspi/raspi_adaptor.go @@ -83,7 +83,7 @@ func NewAdaptor(opts ...interface{}) *Adaptor { return a } -// Name returns the Adaptor's name +// Name returns the Adaptors name func (a *Adaptor) Name() string { a.mutex.Lock() defer a.mutex.Unlock() @@ -91,7 +91,7 @@ func (a *Adaptor) Name() string { return a.name } -// SetName sets the Adaptor's name +// SetName sets the Adaptors name func (a *Adaptor) SetName(n string) { a.mutex.Lock() defer a.mutex.Unlock() diff --git a/platforms/rockpi/rockpi_adaptor.go b/platforms/rockpi/rockpi_adaptor.go index cf159a716..8c4218432 100644 --- a/platforms/rockpi/rockpi_adaptor.go +++ b/platforms/rockpi/rockpi_adaptor.go @@ -57,7 +57,7 @@ func NewAdaptor(opts ...func(adaptors.DigitalPinsOptioner)) *Adaptor { return c } -// Name returns the Adaptor's name +// Name returns the Adaptors name func (c *Adaptor) Name() string { c.mutex.Lock() defer c.mutex.Unlock() @@ -65,7 +65,7 @@ func (c *Adaptor) Name() string { return c.name } -// SetName sets the Adaptor's name +// SetName sets the Adaptors name func (c *Adaptor) SetName(n string) { c.mutex.Lock() defer c.mutex.Unlock() diff --git a/platforms/serialport/README.md b/platforms/serialport/README.md index 2209a52a4..a8cadcc60 100644 --- a/platforms/serialport/README.md +++ b/platforms/serialport/README.md @@ -8,4 +8,5 @@ Please refer to the main [README.md](https://github.com/hybridgroup/gobot/blob/r ## How to Use -See documentation for [Sphero](https://github.com/hybridgroup/gobot/blob/master/platforms/sphero/sphero/README.md) +See documentation for [Sphero](https://github.com/hybridgroup/gobot/blob/master/platforms/sphero/sphero/README.md) or +[Neurosky MindWave](https://github.com/hybridgroup/gobot/blob/master/platforms/neurosky/README.md). diff --git a/platforms/serialport/adaptor.go b/platforms/serialport/adaptor.go index 688ca3571..f7863bff8 100644 --- a/platforms/serialport/adaptor.go +++ b/platforms/serialport/adaptor.go @@ -1,6 +1,7 @@ package serialport import ( + "fmt" "io" "go.bug.st/serial" @@ -8,35 +9,72 @@ import ( "gobot.io/x/gobot/v2" ) +// configuration contains all changeable attributes of the driver. +type configuration struct { + name string + baudRate int +} + // Adaptor represents a Gobot Adaptor for the Serial Communication type Adaptor struct { - name string - port string - sp io.ReadWriteCloser - connected bool - connect func(string) (io.ReadWriteCloser, error) + port string + cfg *configuration + + sp io.ReadWriteCloser + connected bool + connectFunc func(string, int) (io.ReadWriteCloser, error) } // NewAdaptor returns a new adaptor given a port for the serial communication -func NewAdaptor(port string) *Adaptor { - return &Adaptor{ - name: gobot.DefaultName("Serial"), +func NewAdaptor(port string, opts ...optionApplier) *Adaptor { + cfg := configuration{ + name: gobot.DefaultName("Serial"), + baudRate: 115200, + } + + a := Adaptor{ + cfg: &cfg, port: port, - connect: func(port string) (io.ReadWriteCloser, error) { - return serial.Open(port, &serial.Mode{BaudRate: 115200}) + connectFunc: func(port string, baudRate int) (io.ReadWriteCloser, error) { + return serial.Open(port, &serial.Mode{BaudRate: baudRate}) }, } + + for _, o := range opts { + o.apply(a.cfg) + } + + return &a } -// Name returns the Adaptor's name -func (a *Adaptor) Name() string { return a.name } +// WithName is used to replace the default name of the driver. +func WithName(name string) optionApplier { + return nameOption(name) +} -// SetName sets the Adaptor's name -func (a *Adaptor) SetName(n string) { a.name = n } +// WithName is used to replace the default name of the driver. +func WithBaudRate(baudRate int) optionApplier { + return baudRateOption(baudRate) +} + +// Name returns the Adaptors name +func (a *Adaptor) Name() string { + return a.cfg.name +} + +// SetName sets the Adaptors name +// Deprecated: Please use option [serialport.WithName] instead. +func (a *Adaptor) SetName(n string) { + WithName(n).apply(a.cfg) +} // Connect initiates a connection to the serial port. func (a *Adaptor) Connect() error { - sp, err := a.connect(a.Port()) + if a.connected { + return fmt.Errorf("serial port is already connected, try reconnect or run disconnect first") + } + + sp, err := a.connectFunc(a.port, a.cfg.baudRate) if err != nil { return err } @@ -73,7 +111,7 @@ func (a *Adaptor) Reconnect() error { return a.Connect() } -// Port returns the Adaptor's port +// Port returns the adaptors port func (a *Adaptor) Port() string { return a.port } // IsConnected returns the connection state @@ -81,12 +119,12 @@ func (a *Adaptor) IsConnected() bool { return a.connected } -// SerialRead reads from the port -func (a *Adaptor) SerialRead(p []byte) (int, error) { - return a.sp.Read(p) +// SerialRead reads from the port to the given reference +func (a *Adaptor) SerialRead(pData []byte) (int, error) { + return a.sp.Read(pData) } // SerialWrite writes to the port -func (a *Adaptor) SerialWrite(p []byte) (int, error) { - return a.sp.Write(p) +func (a *Adaptor) SerialWrite(data []byte) (int, error) { + return a.sp.Write(data) } diff --git a/platforms/serialport/adaptor_options.go b/platforms/serialport/adaptor_options.go new file mode 100644 index 000000000..6df21d44e --- /dev/null +++ b/platforms/serialport/adaptor_options.go @@ -0,0 +1,28 @@ +package serialport + +// optionApplier needs to be implemented by each configurable option type +type optionApplier interface { + apply(cfg *configuration) +} + +// nameOption is the type for applying another name to the configuration +type nameOption string + +// baudRateOption is the type for applying another baud rate than the default 115200 +type baudRateOption int + +func (o nameOption) String() string { + return "name option for Serial Port adaptors" +} + +func (o baudRateOption) String() string { + return "baud rate option for Serial Port adaptors" +} + +func (o nameOption) apply(cfg *configuration) { + cfg.name = string(o) +} + +func (o baudRateOption) apply(cfg *configuration) { + cfg.baudRate = int(o) +} diff --git a/platforms/serialport/adaptor_options_test.go b/platforms/serialport/adaptor_options_test.go new file mode 100644 index 000000000..d5a21f765 --- /dev/null +++ b/platforms/serialport/adaptor_options_test.go @@ -0,0 +1,27 @@ +package serialport + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestWithName(t *testing.T) { + // This is a general test, that options are applied by using the WithName() option. + // All other configuration options can also be tested by With..(val).apply(cfg). + // arrange & act + const newName = "new name" + a := NewAdaptor("port", WithName(newName)) + // assert + assert.Equal(t, newName, a.cfg.name) +} + +func TestWithBaudRate(t *testing.T) { + // arrange + newBaudRate := 5432 + cfg := &configuration{baudRate: 1234} + // act + WithBaudRate(newBaudRate).apply(cfg) + // assert + assert.Equal(t, newBaudRate, cfg.baudRate) +} diff --git a/platforms/serialport/adaptor_test.go b/platforms/serialport/adaptor_test.go index dd5321e8e..47ebea684 100644 --- a/platforms/serialport/adaptor_test.go +++ b/platforms/serialport/adaptor_test.go @@ -14,93 +14,142 @@ import ( var _ gobot.Adaptor = (*Adaptor)(nil) -type nullReadWriteCloser struct { - testAdaptorRead func(p []byte) (int, error) - testAdaptorWrite func(b []byte) (int, error) - testAdaptorClose func() error -} +func initTestAdaptor() (*Adaptor, *nullReadWriteCloser) { + a := NewAdaptor("/dev/null") + rwc := newNullReadWriteCloser() -func (n *nullReadWriteCloser) Write(p []byte) (int, error) { - return n.testAdaptorWrite(p) -} + a.connectFunc = func(string, int) (io.ReadWriteCloser, error) { + return rwc, nil + } -func (n *nullReadWriteCloser) Read(b []byte) (int, error) { - return n.testAdaptorRead(b) + if err := a.Connect(); err != nil { + panic(err) + } + return a, rwc } -func (n *nullReadWriteCloser) Close() error { - return n.testAdaptorClose() +func TestNewAdaptor(t *testing.T) { + // arrange + a := NewAdaptor("/dev/null") + assert.Equal(t, "/dev/null", a.Port()) + require.NotNil(t, a.cfg) + assert.Equal(t, 115200, a.cfg.baudRate) + assert.True(t, strings.HasPrefix(a.Name(), "Serial")) } -func NewNullReadWriteCloser() *nullReadWriteCloser { - return &nullReadWriteCloser{ - testAdaptorRead: func(p []byte) (int, error) { - return len(p), nil +func TestSerialRead(t *testing.T) { + tests := map[string]struct { + readDataBuffer []byte + simReadErr bool + wantCount int + wantErr string + }{ + "read_ok": { + readDataBuffer: []byte{0, 0}, + wantCount: 2, }, - testAdaptorWrite: func(b []byte) (int, error) { - return len(b), nil - }, - testAdaptorClose: func() error { - return nil + "error_read": { + readDataBuffer: []byte{}, + simReadErr: true, + wantErr: "read error", }, } -} - -func initTestAdaptor() (*Adaptor, *nullReadWriteCloser) { - a := NewAdaptor("/dev/null") - rwc := NewNullReadWriteCloser() - - a.connect = func(string) (io.ReadWriteCloser, error) { - return rwc, nil + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + // arrange + a, rwc := initTestAdaptor() + rwc.simulateReadErr = tc.simReadErr + // act + gotCount, err := a.SerialRead(tc.readDataBuffer) + // assert + if tc.wantErr == "" { + require.NoError(t, err) + } else { + require.EqualError(t, err, tc.wantErr) + } + assert.Equal(t, tc.wantCount, gotCount) + }) } - return a, rwc } -func TestNewAdaptor(t *testing.T) { - a := NewAdaptor("/dev/null") - assert.True(t, strings.HasPrefix(a.Name(), "Serial")) - assert.Equal(t, "/dev/null", a.Port()) +func TestSerialWrite(t *testing.T) { + tests := map[string]struct { + writeDataBuffer []byte + simWriteErr bool + wantCount int + wantWritten []byte + wantErr string + }{ + "write_ok": { + writeDataBuffer: []byte{1, 3, 6}, + wantWritten: []byte{1, 3, 6}, + wantCount: 3, + }, + "error_write": { + writeDataBuffer: []byte{}, + simWriteErr: true, + wantErr: "write error", + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + // arrange + a, rwc := initTestAdaptor() + rwc.simulateWriteErr = tc.simWriteErr + // act + gotCount, err := a.SerialWrite(tc.writeDataBuffer) + // assert + if tc.wantErr == "" { + require.NoError(t, err) + assert.Equal(t, tc.wantWritten, rwc.written) + } else { + require.EqualError(t, err, tc.wantErr) + } + assert.Equal(t, tc.wantCount, gotCount) + }) + } } -func TestName(t *testing.T) { +func TestConnect(t *testing.T) { + // arrange a, _ := initTestAdaptor() - assert.True(t, strings.HasPrefix(a.Name(), "Serial")) - a.SetName("NewName") - assert.Equal(t, "NewName", a.Name()) + require.True(t, a.IsConnected()) + // 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.connectFunc = func(string, int) (io.ReadWriteCloser, error) { + return nil, errors.New("connect error") + } + // act & assert + require.ErrorContains(t, a.Connect(), "connect error") + assert.False(t, a.IsConnected()) } func TestReconnect(t *testing.T) { + // arrange a, _ := initTestAdaptor() - require.NoError(t, a.Connect()) - assert.True(t, a.connected) + require.True(t, a.connected) + // act & assert require.NoError(t, a.Reconnect()) assert.True(t, a.connected) + // act & assert require.NoError(t, a.Disconnect()) assert.False(t, a.connected) + // act & assert require.NoError(t, a.Reconnect()) assert.True(t, a.connected) } func TestFinalize(t *testing.T) { + // arrange a, rwc := initTestAdaptor() - require.NoError(t, a.Connect()) + // act & assert require.NoError(t, a.Finalize()) - - rwc.testAdaptorClose = func() error { - return errors.New("close error") - } - + assert.False(t, a.IsConnected()) + // re-arrange error + rwc.simulateCloseErr = true a.connected = true + // act & assert require.ErrorContains(t, a.Finalize(), "close error") } - -func TestConnect(t *testing.T) { - a, _ := initTestAdaptor() - require.NoError(t, a.Connect()) - - a.connect = func(string) (io.ReadWriteCloser, error) { - return nil, errors.New("connect error") - } - - require.ErrorContains(t, a.Connect(), "connect error") -} diff --git a/platforms/serialport/helpers_test.go b/platforms/serialport/helpers_test.go new file mode 100644 index 000000000..b4b63f484 --- /dev/null +++ b/platforms/serialport/helpers_test.go @@ -0,0 +1,36 @@ +package serialport + +import "fmt" + +type nullReadWriteCloser struct { + written []byte + simulateReadErr bool + simulateWriteErr bool + simulateCloseErr bool +} + +func newNullReadWriteCloser() *nullReadWriteCloser { + return &nullReadWriteCloser{} +} + +func (rwc *nullReadWriteCloser) Write(data []byte) (int, error) { + if rwc.simulateWriteErr { + return 0, fmt.Errorf("write error") + } + rwc.written = append(rwc.written, data...) + return len(data), nil +} + +func (rwc *nullReadWriteCloser) Read(p []byte) (int, error) { + if rwc.simulateReadErr { + return 0, fmt.Errorf("read error") + } + return len(p), nil +} + +func (rwc *nullReadWriteCloser) Close() error { + if rwc.simulateCloseErr { + return fmt.Errorf("close error") + } + return nil +} diff --git a/platforms/sphero/sphero/README.md b/platforms/sphero/sphero/README.md index d156bce53..b7ff9c62e 100644 --- a/platforms/sphero/sphero/README.md +++ b/platforms/sphero/sphero/README.md @@ -70,7 +70,7 @@ import ( func main() { adaptor := serialport.NewAdaptor("/dev/rfcomm0") - driver := serial.NewSpheroDriver(adaptor) + driver := sphero.NewSpheroDriver(adaptor) work := func() { gobot.Every(3*time.Second, func() {