microbit: add DigitalWriter, DigitalReader, and AnalogReader support

Signed-off-by: deadprogram <ron@hybridgroup.com>
This commit is contained in:
deadprogram 2017-04-15 17:07:53 +02:00
parent 8eedc24c75
commit ca2854bdf0
3 changed files with 349 additions and 0 deletions

View File

@ -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()
}

View File

@ -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))
}

View File

@ -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)
}