diff --git a/examples/microbit_blink.go b/examples/microbit_blink.go new file mode 100644 index 00000000..46802f3e --- /dev/null +++ b/examples/microbit_blink.go @@ -0,0 +1,36 @@ +// +build example +// +// Do not build by default. + +package main + +import ( + "os" + "time" + + "gobot.io/x/gobot" + "gobot.io/x/gobot/drivers/gpio" + "gobot.io/x/gobot/platforms/ble" + "gobot.io/x/gobot/platforms/microbit" +) + +func main() { + bleAdaptor := ble.NewClientAdaptor(os.Args[1]) + + ubit := microbit.NewIOPinDriver(bleAdaptor) + led := gpio.NewLedDriver(ubit, "0") + + work := func() { + gobot.Every(1*time.Second, func() { + led.Toggle() + }) + } + + robot := gobot.NewRobot("bot", + []gobot.Connection{bleAdaptor, ubit}, + []gobot.Device{ubit, led}, + work, + ) + + robot.Start() +} diff --git a/platforms/microbit/io_pin_driver.go b/platforms/microbit/io_pin_driver.go new file mode 100644 index 00000000..c64f83f1 --- /dev/null +++ b/platforms/microbit/io_pin_driver.go @@ -0,0 +1,237 @@ +package microbit + +import ( + "bytes" + "encoding/binary" + "strconv" + + "gobot.io/x/gobot" + "gobot.io/x/gobot/platforms/ble" +) + +// IOPinDriver is the Gobot driver for the Microbit's built-in digital and +// analog I/O +type IOPinDriver struct { + name string + adMask int + ioMask int + connection gobot.Connection + gobot.Eventer +} + +const ( + // BLE services + ioPinService = "e95d127b251d470aa062fa1922dfa9a8" + + // BLE characteristics + pinDataCharacteristic = "e95d8d00251d470aa062fa1922dfa9a8" + pinADConfigCharacteristic = "e95d5899251d470aa062fa1922dfa9a8" + pinIOConfigCharacteristic = "e95db9fe251d470aa062fa1922dfa9a8" +) + +// PinData has the read data for a specific digital pin +type PinData struct { + pin uint8 + value uint8 +} + +// NewIOPinDriver creates a Microbit IOPinDriver +func NewIOPinDriver(a ble.BLEConnector) *IOPinDriver { + n := &IOPinDriver{ + name: gobot.DefaultName("Microbit IO Pins"), + connection: a, + Eventer: gobot.NewEventer(), + } + + return n +} + +// Connection returns the BLE connection +func (b *IOPinDriver) Connection() gobot.Connection { return b.connection } + +// Name returns the Driver Name +func (b *IOPinDriver) Name() string { return b.name } + +// SetName sets the Driver Name +func (b *IOPinDriver) SetName(n string) { b.name = n } + +// adaptor returns BLE adaptor +func (b *IOPinDriver) adaptor() ble.BLEConnector { + return b.Connection().(ble.BLEConnector) +} + +// Start tells driver to get ready to do work +func (b *IOPinDriver) Start() (err error) { + return +} + +// Halt stops driver (void) +func (b *IOPinDriver) Halt() (err error) { + return +} + +// ReadAllPinData reads and returns the pin data for all pins +func (b *IOPinDriver) ReadAllPinData() (pins []PinData) { + c, _ := b.adaptor().ReadCharacteristic(pinDataCharacteristic) + buf := bytes.NewBuffer(c) + pinsData := make([]PinData, buf.Len()/2) + + for i := 0; i < buf.Len()/2; i++ { + pinData := PinData{} + pinData.pin, _ = buf.ReadByte() + pinData.value, _ = buf.ReadByte() + pinsData[i] = pinData + } + + return pinsData +} + +// WritePinData writes the pin data for a single pin +func (b *IOPinDriver) WritePinData(pin string, data byte) (err error) { + i, err := strconv.Atoi(pin) + if err != nil { + return + } + + buf := []byte{byte(i), data} + err = b.adaptor().WriteCharacteristic(pinDataCharacteristic, buf) + return err +} + +// ReadPinADConfig reads and returns the pin A/D config mask for all pins +func (b *IOPinDriver) ReadPinADConfig() (config uint32, err error) { + var result uint32 + c, e := b.adaptor().ReadCharacteristic(pinADConfigCharacteristic) + if e != nil { + return 0, e + } + buf := bytes.NewBuffer(c) + binary.Read(buf, binary.LittleEndian, result) + b.adMask = int(result) + return result, nil +} + +// WritePinADConfig writes the pin A/D config mask for all pins +func (b *IOPinDriver) WritePinADConfig(config int) (err error) { + b.adMask = config + data := &bytes.Buffer{} + binary.Write(data, binary.LittleEndian, uint32(config)) + err = b.adaptor().WriteCharacteristic(pinADConfigCharacteristic, data.Bytes()) + return +} + +// ReadPinIOConfig reads and returns the pin IO config mask for all pins +func (b *IOPinDriver) ReadPinIOConfig() (config int, err error) { + var result uint32 + c, e := b.adaptor().ReadCharacteristic(pinIOConfigCharacteristic) + if e != nil { + return 0, e + } + buf := bytes.NewBuffer(c) + binary.Read(buf, binary.LittleEndian, result) + b.ioMask = int(result) + return int(result), nil +} + +// WritePinIOConfig writes the pin I/O config mask for all pins +func (b *IOPinDriver) WritePinIOConfig(config int) (err error) { + b.ioMask = config + data := &bytes.Buffer{} + binary.Write(data, binary.LittleEndian, uint32(config)) + err = b.adaptor().WriteCharacteristic(pinIOConfigCharacteristic, data.Bytes()) + return +} + +// Connect here to allow Driver to also act as an Adaptor +func (b *IOPinDriver) Connect() (err error) { + return nil +} + +// Finalize here to allow Driver to also act as an Adaptor +func (b *IOPinDriver) Finalize() (err error) { + return nil +} + +// DigitalRead reads from a pin +func (b *IOPinDriver) DigitalRead(pin string) (val int, err error) { + p, err := strconv.Atoi(pin) + if err != nil { + return + } + + b.ensureDigital(p) + b.ensureInput(p) + + pins := b.ReadAllPinData() + return int(pins[p].value), nil +} + +// DigitalWrite writes to a pin +func (b *IOPinDriver) DigitalWrite(pin string, level byte) (err error) { + p, err := strconv.Atoi(pin) + if err != nil { + return + } + + b.ensureDigital(p) + b.ensureOutput(p) + + return b.WritePinData(pin, level) +} + +// AnalogRead reads from a pin +func (b *IOPinDriver) AnalogRead(pin string) (val int, err error) { + p, err := strconv.Atoi(pin) + if err != nil { + return + } + + b.ensureAnalog(p) + b.ensureInput(p) + + pins := b.ReadAllPinData() + return int(pins[p].value), nil +} + +func (b *IOPinDriver) ensureDigital(pin int) { + if hasBit(b.adMask, pin) { + b.WritePinADConfig(clearBit(b.adMask, pin)) + } +} + +func (b *IOPinDriver) ensureAnalog(pin int) { + if !hasBit(b.adMask, pin) { + b.WritePinADConfig(setBit(b.adMask, pin)) + } +} + +func (b *IOPinDriver) ensureInput(pin int) { + if !hasBit(b.ioMask, pin) { + b.WritePinIOConfig(setBit(b.ioMask, pin)) + } +} + +func (b *IOPinDriver) ensureOutput(pin int) { + if hasBit(b.ioMask, pin) { + b.WritePinIOConfig(clearBit(b.ioMask, pin)) + } +} + +// via http://stackoverflow.com/questions/23192262/how-would-you-set-and-clear-a-single-bit-in-go + +// Sets the bit at pos in the integer n. +func setBit(n int, pos int) int { + n |= (1 << uint(pos)) + return n +} + +// Test if the bit at pos is set in the integer n. +func hasBit(n int, pos int) bool { + val := n & (1 << uint(pos)) + return (val > 0) +} + +// Clears the bit at pos in n. +func clearBit(n int, pos int) int { + return n &^ (1 << uint(pos)) +} diff --git a/platforms/microbit/io_pin_driver_test.go b/platforms/microbit/io_pin_driver_test.go new file mode 100644 index 00000000..47c5897d --- /dev/null +++ b/platforms/microbit/io_pin_driver_test.go @@ -0,0 +1,76 @@ +package microbit + +import ( + "strings" + "testing" + + "gobot.io/x/gobot" + "gobot.io/x/gobot/drivers/aio" + "gobot.io/x/gobot/drivers/gpio" + "gobot.io/x/gobot/gobottest" +) + +// the IOPinDriver is a Driver +var _ gobot.Driver = (*IOPinDriver)(nil) + +// and is also an Adaptor +var _ gobot.Adaptor = (*IOPinDriver)(nil) + +// that supports the DigitalReader, DigitalWriter, & AnalogReader interfaces +var _ gpio.DigitalReader = (*IOPinDriver)(nil) +var _ gpio.DigitalWriter = (*IOPinDriver)(nil) +var _ aio.AnalogReader = (*IOPinDriver)(nil) + +func initTestIOPinDriver() *IOPinDriver { + d := NewIOPinDriver(NewBleTestAdaptor()) + return d +} + +func TestIOPinDriver(t *testing.T) { + d := initTestIOPinDriver() + gobottest.Assert(t, strings.HasPrefix(d.Name(), "Microbit IO Pin"), true) + d.SetName("NewName") + gobottest.Assert(t, d.Name(), "NewName") +} + +func TestIOPinDriverStartAndHalt(t *testing.T) { + d := initTestIOPinDriver() + gobottest.Assert(t, d.Start(), nil) + gobottest.Assert(t, d.Halt(), nil) +} + +func TestIOPinDriverDigitalRead(t *testing.T) { + a := NewBleTestAdaptor() + d := NewIOPinDriver(a) + a.TestReadCharacteristic(func(cUUID string) ([]byte, error) { + return []byte{0, 1, 1, 0, 2, 1}, nil + }) + + val, _ := d.DigitalRead("0") + gobottest.Assert(t, val, 1) + + val, _ = d.DigitalRead("1") + gobottest.Assert(t, val, 0) +} + +func TestIOPinDriverDigitalWrite(t *testing.T) { + a := NewBleTestAdaptor() + d := NewIOPinDriver(a) + + // TODO: a better test + gobottest.Assert(t, d.DigitalWrite("0", 1), nil) +} + +func TestIOPinDriverAnalogRead(t *testing.T) { + a := NewBleTestAdaptor() + d := NewIOPinDriver(a) + a.TestReadCharacteristic(func(cUUID string) ([]byte, error) { + return []byte{0, 0, 1, 128, 2, 1}, nil + }) + + val, _ := d.AnalogRead("0") + gobottest.Assert(t, val, 0) + + val, _ = d.AnalogRead("1") + gobottest.Assert(t, val, 128) +}