mirror of
https://github.com/hybridgroup/gobot.git
synced 2025-05-11 19:29:20 +08:00
Merge pull request #191 from hybridgroup/firmata_refactor2
Refactor firmata
This commit is contained in:
commit
10b2de6097
2
Makefile
2
Makefile
@ -1,4 +1,4 @@
|
||||
PACKAGES := gobot gobot/api gobot/platforms/intel-iot/edison gobot/sysfs $(shell ls ./platforms | sed -e 's/^/gobot\/platforms\//')
|
||||
PACKAGES := gobot gobot/api gobot/platforms/firmata/client gobot/platforms/intel-iot/edison gobot/sysfs $(shell ls ./platforms | sed -e 's/^/gobot\/platforms\//')
|
||||
.PHONY: test cover robeaux
|
||||
|
||||
test:
|
||||
|
477
platforms/firmata/client/client.go
Normal file
477
platforms/firmata/client/client.go
Normal file
@ -0,0 +1,477 @@
|
||||
// Package client provies a client for interacting with microcontrollers
|
||||
// using the Firmata protocol https://github.com/firmata/protocol.
|
||||
package client
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/hybridgroup/gobot"
|
||||
)
|
||||
|
||||
// Pin Modes
|
||||
const (
|
||||
Input = 0x00
|
||||
Output = 0x01
|
||||
Analog = 0x02
|
||||
Pwm = 0x03
|
||||
Servo = 0x04
|
||||
)
|
||||
|
||||
// Sysex Codes
|
||||
const (
|
||||
ProtocolVersion byte = 0xF9
|
||||
SystemReset byte = 0xFF
|
||||
DigitalMessage byte = 0x90
|
||||
DigitalMessageRangeStart byte = 0x90
|
||||
DigitalMessageRangeEnd byte = 0x9F
|
||||
AnalogMessage byte = 0xE0
|
||||
AnalogMessageRangeStart byte = 0xE0
|
||||
AnalogMessageRangeEnd byte = 0xEF
|
||||
ReportAnalog byte = 0xC0
|
||||
ReportDigital byte = 0xD0
|
||||
PinMode byte = 0xF4
|
||||
StartSysex byte = 0xF0
|
||||
EndSysex byte = 0xF7
|
||||
CapabilityQuery byte = 0x6B
|
||||
CapabilityResponse byte = 0x6C
|
||||
PinStateQuery byte = 0x6D
|
||||
PinStateResponse byte = 0x6E
|
||||
AnalogMappingQuery byte = 0x69
|
||||
AnalogMappingResponse byte = 0x6A
|
||||
StringData byte = 0x71
|
||||
I2CRequest byte = 0x76
|
||||
I2CReply byte = 0x77
|
||||
I2CConfig byte = 0x78
|
||||
FirmwareQuery byte = 0x79
|
||||
I2CModeWrite byte = 0x00
|
||||
I2CModeRead byte = 0x01
|
||||
I2CModeContinuousRead byte = 0x02
|
||||
I2CModeStopReading byte = 0x03
|
||||
ServoConfig byte = 0x70
|
||||
)
|
||||
|
||||
// Errors
|
||||
var (
|
||||
ErrConnected = errors.New("client is already connected")
|
||||
)
|
||||
|
||||
// Client represents a client connection to a firmata board
|
||||
type Client struct {
|
||||
pins []Pin
|
||||
FirmwareName string
|
||||
ProtocolVersion string
|
||||
connected bool
|
||||
connection io.ReadWriteCloser
|
||||
analogPins []int
|
||||
initTimeInterval time.Duration
|
||||
gobot.Eventer
|
||||
}
|
||||
|
||||
// Pin represents a pin on the firmata board
|
||||
type Pin struct {
|
||||
SupportedModes []int
|
||||
Mode int
|
||||
Value int
|
||||
State int
|
||||
AnalogChannel int
|
||||
}
|
||||
|
||||
// I2cReply represents the response from an I2cReply message
|
||||
type I2cReply struct {
|
||||
Address int
|
||||
Register int
|
||||
Data []byte
|
||||
}
|
||||
|
||||
// New returns a new Client
|
||||
func New() *Client {
|
||||
c := &Client{
|
||||
ProtocolVersion: "",
|
||||
FirmwareName: "",
|
||||
connection: nil,
|
||||
pins: []Pin{},
|
||||
analogPins: []int{},
|
||||
connected: false,
|
||||
Eventer: gobot.NewEventer(),
|
||||
}
|
||||
|
||||
for _, s := range []string{
|
||||
"FirmwareQuery",
|
||||
"CapabilityQuery",
|
||||
"AnalogMappingQuery",
|
||||
"ProtocolVersion",
|
||||
"I2cReply",
|
||||
"StringData",
|
||||
"Error",
|
||||
} {
|
||||
c.AddEvent(s)
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
// Disconnect disconnects the Client
|
||||
func (b *Client) Disconnect() (err error) {
|
||||
b.connected = false
|
||||
return b.connection.Close()
|
||||
}
|
||||
|
||||
// Connected returns the current connection state of the Client
|
||||
func (b *Client) Connected() bool {
|
||||
return b.connected
|
||||
}
|
||||
|
||||
// Pins returns all available pins
|
||||
func (b *Client) Pins() []Pin {
|
||||
return b.pins
|
||||
}
|
||||
|
||||
// Connect connects to the Client given conn. It first resets the firmata board
|
||||
// then continuously polls the firmata board for new information when it's
|
||||
// available.
|
||||
func (b *Client) Connect(conn io.ReadWriteCloser) (err error) {
|
||||
if b.connected {
|
||||
return ErrConnected
|
||||
}
|
||||
|
||||
b.connection = conn
|
||||
b.Reset()
|
||||
|
||||
initFunc := b.ProtocolVersionQuery
|
||||
|
||||
gobot.Once(b.Event("ProtocolVersion"), func(data interface{}) {
|
||||
initFunc = b.FirmwareQuery
|
||||
})
|
||||
|
||||
gobot.Once(b.Event("FirmwareQuery"), func(data interface{}) {
|
||||
initFunc = b.CapabilitiesQuery
|
||||
})
|
||||
|
||||
gobot.Once(b.Event("CapabilityQuery"), func(data interface{}) {
|
||||
initFunc = b.AnalogMappingQuery
|
||||
})
|
||||
|
||||
gobot.Once(b.Event("AnalogMappingQuery"), func(data interface{}) {
|
||||
initFunc = func() error { return nil }
|
||||
b.ReportDigital(0, 1)
|
||||
b.ReportDigital(1, 1)
|
||||
b.connected = true
|
||||
})
|
||||
|
||||
for {
|
||||
if err := initFunc(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := b.process(); err != nil {
|
||||
return err
|
||||
}
|
||||
if b.connected {
|
||||
go func() {
|
||||
for {
|
||||
if !b.connected {
|
||||
break
|
||||
}
|
||||
|
||||
if err := b.process(); err != nil {
|
||||
gobot.Publish(b.Event("Error"), err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Reset sends the SystemReset sysex code.
|
||||
func (b *Client) Reset() error {
|
||||
return b.write([]byte{SystemReset})
|
||||
}
|
||||
|
||||
// SetPinMode sets the pin to mode.
|
||||
func (b *Client) SetPinMode(pin int, mode int) error {
|
||||
b.pins[byte(pin)].Mode = mode
|
||||
return b.write([]byte{PinMode, byte(pin), byte(mode)})
|
||||
}
|
||||
|
||||
// DigitalWrite writes value to pin.
|
||||
func (b *Client) DigitalWrite(pin int, value int) error {
|
||||
port := byte(math.Floor(float64(pin) / 8))
|
||||
portValue := byte(0)
|
||||
|
||||
b.pins[pin].Value = value
|
||||
|
||||
for i := byte(0); i < 8; i++ {
|
||||
if b.pins[8*port+i].Value != 0 {
|
||||
portValue = portValue | (1 << i)
|
||||
}
|
||||
}
|
||||
return b.write([]byte{DigitalMessage | port, portValue & 0x7F, (portValue >> 7) & 0x7F})
|
||||
}
|
||||
|
||||
// ServoConfig sets the min and max pulse width for servo PWM range
|
||||
func (b *Client) ServoConfig(pin int, max int, min int) error {
|
||||
ret := []byte{
|
||||
ServoConfig,
|
||||
byte(pin),
|
||||
byte(max & 0x7F),
|
||||
byte((max >> 7) & 0x7F),
|
||||
byte(min & 0x7F),
|
||||
byte((min >> 7) & 0x7F),
|
||||
}
|
||||
return b.writeSysex(ret)
|
||||
}
|
||||
|
||||
// AnalogWrite writes value to pin.
|
||||
func (b *Client) AnalogWrite(pin int, value int) error {
|
||||
b.pins[pin].Value = value
|
||||
return b.write([]byte{AnalogMessage | byte(pin), byte(value & 0x7F), byte((value >> 7) & 0x7F)})
|
||||
}
|
||||
|
||||
// FirmwareQuery sends the FirmwareQuery sysex code.
|
||||
func (b *Client) FirmwareQuery() error {
|
||||
return b.writeSysex([]byte{FirmwareQuery})
|
||||
}
|
||||
|
||||
// PinStateQuery sends a PinStateQuery for pin.
|
||||
func (b *Client) PinStateQuery(pin int) error {
|
||||
return b.writeSysex([]byte{PinStateQuery, byte(pin)})
|
||||
}
|
||||
|
||||
// ProtocolVersionQuery sends the ProtocolVersion sysex code.
|
||||
func (b *Client) ProtocolVersionQuery() error {
|
||||
return b.write([]byte{ProtocolVersion})
|
||||
}
|
||||
|
||||
// CapabilitiesQuery sends the CapabilityQuery sysex code.
|
||||
func (b *Client) CapabilitiesQuery() error {
|
||||
return b.writeSysex([]byte{CapabilityQuery})
|
||||
}
|
||||
|
||||
// AnalogMappingQuery sends the AnalogMappingQuery sysex code.
|
||||
func (b *Client) AnalogMappingQuery() error {
|
||||
return b.writeSysex([]byte{AnalogMappingQuery})
|
||||
}
|
||||
|
||||
// ReportDigital enables or disables digital reporting for pin, a non zero
|
||||
// state enables reporting
|
||||
func (b *Client) ReportDigital(pin int, state int) error {
|
||||
return b.togglePinReporting(pin, state, ReportDigital)
|
||||
}
|
||||
|
||||
// ReportAnalog enables or disables analog reporting for pin, a non zero
|
||||
// state enables reporting
|
||||
func (b *Client) ReportAnalog(pin int, state int) error {
|
||||
return b.togglePinReporting(pin, state, ReportAnalog)
|
||||
}
|
||||
|
||||
// I2cRead reads numBytes from address once.
|
||||
func (b *Client) I2cRead(address int, numBytes int) error {
|
||||
return b.writeSysex([]byte{I2CRequest, byte(address), (I2CModeRead << 3),
|
||||
byte(numBytes) & 0x7F, (byte(numBytes) >> 7) & 0x7F})
|
||||
}
|
||||
|
||||
// I2cWrite writes data to address.
|
||||
func (b *Client) I2cWrite(address int, data []byte) error {
|
||||
ret := []byte{I2CRequest, byte(address), (I2CModeWrite << 3)}
|
||||
for _, val := range data {
|
||||
ret = append(ret, byte(val&0x7F))
|
||||
ret = append(ret, byte((val>>7)&0x7F))
|
||||
}
|
||||
return b.writeSysex(ret)
|
||||
}
|
||||
|
||||
// I2cConfig configures the delay in which a register can be read from after it
|
||||
// has been written to.
|
||||
func (b *Client) I2cConfig(delay int) error {
|
||||
return b.writeSysex([]byte{I2CConfig, byte(delay & 0xFF), byte((delay >> 8) & 0xFF)})
|
||||
}
|
||||
|
||||
func (b *Client) togglePinReporting(pin int, state int, mode byte) error {
|
||||
if state != 0 {
|
||||
state = 1
|
||||
} else {
|
||||
state = 0
|
||||
}
|
||||
|
||||
if err := b.write([]byte{byte(mode) | byte(pin), byte(state)}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (b *Client) writeSysex(data []byte) (err error) {
|
||||
return b.write(append([]byte{StartSysex}, append(data, EndSysex)...))
|
||||
}
|
||||
|
||||
func (b *Client) write(data []byte) (err error) {
|
||||
_, err = b.connection.Write(data[:])
|
||||
return
|
||||
}
|
||||
|
||||
func (b *Client) read(length int) (buf []byte, err error) {
|
||||
i := 0
|
||||
for length > 0 {
|
||||
tmp := make([]byte, length)
|
||||
if i, err = b.connection.Read(tmp); err != nil {
|
||||
if err.Error() != "EOF" {
|
||||
return
|
||||
}
|
||||
<-time.After(5 * time.Millisecond)
|
||||
}
|
||||
if i > 0 {
|
||||
buf = append(buf, tmp...)
|
||||
length = length - i
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (b *Client) process() (err error) {
|
||||
buf, err := b.read(3)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
messageType := buf[0]
|
||||
switch {
|
||||
case ProtocolVersion == messageType:
|
||||
b.ProtocolVersion = fmt.Sprintf("%v.%v", buf[1], buf[2])
|
||||
|
||||
gobot.Publish(b.Event("ProtocolVersion"), b.ProtocolVersion)
|
||||
case AnalogMessageRangeStart <= messageType &&
|
||||
AnalogMessageRangeEnd >= messageType:
|
||||
|
||||
value := uint(buf[1]) | uint(buf[2])<<7
|
||||
pin := int((messageType & 0x0F))
|
||||
|
||||
if len(b.analogPins) > pin {
|
||||
if len(b.pins) > b.analogPins[pin] {
|
||||
b.pins[b.analogPins[pin]].Value = int(value)
|
||||
gobot.Publish(b.Event(fmt.Sprintf("AnalogRead%v", pin)), b.pins[b.analogPins[pin]].Value)
|
||||
}
|
||||
}
|
||||
case DigitalMessageRangeStart <= messageType &&
|
||||
DigitalMessageRangeEnd >= messageType:
|
||||
|
||||
port := messageType & 0x0F
|
||||
portValue := buf[1] | (buf[2] << 7)
|
||||
|
||||
for i := 0; i < 8; i++ {
|
||||
pinNumber := int((8*byte(port) + byte(i)))
|
||||
if len(b.pins) > pinNumber {
|
||||
if b.pins[pinNumber].Mode == Input {
|
||||
b.pins[pinNumber].Value = int((portValue >> (byte(i) & 0x07)) & 0x01)
|
||||
gobot.Publish(b.Event(fmt.Sprintf("DigitalRead%v", pinNumber)), b.pins[pinNumber].Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
case StartSysex == messageType:
|
||||
currentBuffer := buf
|
||||
for {
|
||||
buf, err = b.read(1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
currentBuffer = append(currentBuffer, buf[0])
|
||||
if buf[0] == EndSysex {
|
||||
break
|
||||
}
|
||||
}
|
||||
command := currentBuffer[1]
|
||||
switch command {
|
||||
case CapabilityResponse:
|
||||
b.pins = []Pin{}
|
||||
supportedModes := 0
|
||||
n := 0
|
||||
|
||||
for _, val := range currentBuffer[2:(len(currentBuffer) - 5)] {
|
||||
if val == 127 {
|
||||
modes := []int{}
|
||||
for _, mode := range []int{Input, Output, Analog, Pwm, Servo} {
|
||||
if (supportedModes & (1 << byte(mode))) != 0 {
|
||||
modes = append(modes, mode)
|
||||
}
|
||||
}
|
||||
|
||||
b.pins = append(b.pins, Pin{SupportedModes: modes, Mode: Output})
|
||||
b.AddEvent(fmt.Sprintf("DigitalRead%v", len(b.pins)-1))
|
||||
b.AddEvent(fmt.Sprintf("PinState%v", len(b.pins)-1))
|
||||
supportedModes = 0
|
||||
n = 0
|
||||
continue
|
||||
}
|
||||
|
||||
if n == 0 {
|
||||
supportedModes = supportedModes | (1 << val)
|
||||
}
|
||||
n ^= 1
|
||||
}
|
||||
gobot.Publish(b.Event("CapabilityQuery"), nil)
|
||||
case AnalogMappingResponse:
|
||||
pinIndex := 0
|
||||
b.analogPins = []int{}
|
||||
|
||||
for _, val := range currentBuffer[2 : len(b.pins)-1] {
|
||||
|
||||
b.pins[pinIndex].AnalogChannel = int(val)
|
||||
|
||||
if val != 127 {
|
||||
b.analogPins = append(b.analogPins, pinIndex)
|
||||
}
|
||||
b.AddEvent(fmt.Sprintf("AnalogRead%v", pinIndex))
|
||||
pinIndex++
|
||||
}
|
||||
gobot.Publish(b.Event("AnalogMappingQuery"), nil)
|
||||
case PinStateResponse:
|
||||
pin := currentBuffer[2]
|
||||
b.pins[pin].Mode = int(currentBuffer[3])
|
||||
b.pins[pin].State = int(currentBuffer[4])
|
||||
|
||||
if len(currentBuffer) > 6 {
|
||||
b.pins[pin].State = int(uint(b.pins[pin].State) | uint(currentBuffer[5])<<7)
|
||||
}
|
||||
if len(currentBuffer) > 7 {
|
||||
b.pins[pin].State = int(uint(b.pins[pin].State) | uint(currentBuffer[6])<<14)
|
||||
}
|
||||
|
||||
gobot.Publish(b.Event(fmt.Sprintf("PinState%v", pin)), b.pins[pin])
|
||||
case I2CReply:
|
||||
reply := I2cReply{
|
||||
Address: int(byte(currentBuffer[2]) | byte(currentBuffer[3])<<7),
|
||||
Register: int(byte(currentBuffer[4]) | byte(currentBuffer[5])<<7),
|
||||
Data: []byte{byte(currentBuffer[6]) | byte(currentBuffer[7])<<7},
|
||||
}
|
||||
for i := 8; i < len(currentBuffer); i = i + 2 {
|
||||
if currentBuffer[i] == byte(0xF7) {
|
||||
break
|
||||
}
|
||||
if i+2 > len(currentBuffer) {
|
||||
break
|
||||
}
|
||||
reply.Data = append(reply.Data,
|
||||
byte(currentBuffer[i])|byte(currentBuffer[i+1])<<7,
|
||||
)
|
||||
}
|
||||
gobot.Publish(b.Event("I2cReply"), reply)
|
||||
case FirmwareQuery:
|
||||
name := []byte{}
|
||||
for _, val := range currentBuffer[4:(len(currentBuffer) - 1)] {
|
||||
if val != 0 {
|
||||
name = append(name, val)
|
||||
}
|
||||
}
|
||||
b.FirmwareName = string(name[:])
|
||||
gobot.Publish(b.Event("FirmwareQuery"), b.FirmwareName)
|
||||
case StringData:
|
||||
str := currentBuffer[2:len(currentBuffer)]
|
||||
gobot.Publish(b.Event("StringData"), string(str[:len(str)-1]))
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
217
platforms/firmata/client/client_test.go
Normal file
217
platforms/firmata/client/client_test.go
Normal file
@ -0,0 +1,217 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hybridgroup/gobot"
|
||||
)
|
||||
|
||||
type readWriteCloser struct{}
|
||||
|
||||
func (readWriteCloser) Write(p []byte) (int, error) {
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
var testReadData = []byte{}
|
||||
|
||||
func (readWriteCloser) Read(b []byte) (int, error) {
|
||||
size := len(b)
|
||||
if len(testReadData) < size {
|
||||
size = len(testReadData)
|
||||
}
|
||||
copy(b, []byte(testReadData)[:size])
|
||||
testReadData = testReadData[size:]
|
||||
|
||||
return size, nil
|
||||
}
|
||||
|
||||
func (readWriteCloser) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func testProtocolResponse() []byte {
|
||||
// arduino uno r3 protocol response "2.3"
|
||||
return []byte{249, 2, 3}
|
||||
}
|
||||
|
||||
func testFirmwareResponse() []byte {
|
||||
// arduino uno r3 firmware response "StandardFirmata.ino"
|
||||
return []byte{240, 121, 2, 3, 83, 0, 116, 0, 97, 0, 110, 0, 100, 0, 97,
|
||||
0, 114, 0, 100, 0, 70, 0, 105, 0, 114, 0, 109, 0, 97, 0, 116, 0, 97, 0, 46,
|
||||
0, 105, 0, 110, 0, 111, 0, 247}
|
||||
}
|
||||
|
||||
func testCapabilitiesResponse() []byte {
|
||||
// arduino uno r3 capabilities response
|
||||
return []byte{240, 108, 127, 127, 0, 1, 1, 1, 4, 14, 127, 0, 1, 1, 1, 3,
|
||||
8, 4, 14, 127, 0, 1, 1, 1, 4, 14, 127, 0, 1, 1, 1, 3, 8, 4, 14, 127, 0, 1,
|
||||
1, 1, 3, 8, 4, 14, 127, 0, 1, 1, 1, 4, 14, 127, 0, 1, 1, 1, 4, 14, 127, 0,
|
||||
1, 1, 1, 3, 8, 4, 14, 127, 0, 1, 1, 1, 3, 8, 4, 14, 127, 0, 1, 1, 1, 3, 8,
|
||||
4, 14, 127, 0, 1, 1, 1, 4, 14, 127, 0, 1, 1, 1, 4, 14, 127, 0, 1, 1, 1, 2,
|
||||
10, 127, 0, 1, 1, 1, 2, 10, 127, 0, 1, 1, 1, 2, 10, 127, 0, 1, 1, 1, 2, 10,
|
||||
127, 0, 1, 1, 1, 2, 10, 6, 1, 127, 0, 1, 1, 1, 2, 10, 6, 1, 127, 247}
|
||||
}
|
||||
|
||||
func testAnalogMappingResponse() []byte {
|
||||
// arduino uno r3 analog mapping response
|
||||
return []byte{240, 106, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
|
||||
127, 127, 127, 127, 0, 1, 2, 3, 4, 5, 247}
|
||||
}
|
||||
|
||||
func initTestFirmata() *Client {
|
||||
b := New()
|
||||
b.connection = readWriteCloser{}
|
||||
|
||||
for _, f := range []func() []byte{
|
||||
testProtocolResponse,
|
||||
testFirmwareResponse,
|
||||
testCapabilitiesResponse,
|
||||
testAnalogMappingResponse,
|
||||
} {
|
||||
testReadData = f()
|
||||
b.process()
|
||||
}
|
||||
|
||||
b.connected = true
|
||||
b.Connect(readWriteCloser{})
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func TestReportVersion(t *testing.T) {
|
||||
b := initTestFirmata()
|
||||
//test if functions executes
|
||||
b.ProtocolVersionQuery()
|
||||
}
|
||||
|
||||
func TestQueryFirmware(t *testing.T) {
|
||||
b := initTestFirmata()
|
||||
//test if functions executes
|
||||
b.FirmwareQuery()
|
||||
}
|
||||
|
||||
func TestQueryPinState(t *testing.T) {
|
||||
b := initTestFirmata()
|
||||
//test if functions executes
|
||||
b.PinStateQuery(1)
|
||||
}
|
||||
|
||||
func TestProcess(t *testing.T) {
|
||||
sem := make(chan bool)
|
||||
b := initTestFirmata()
|
||||
|
||||
tests := []struct {
|
||||
event string
|
||||
data []byte
|
||||
expected interface{}
|
||||
init func()
|
||||
}{
|
||||
{
|
||||
event: "ProtocolVersion",
|
||||
data: []byte{249, 2, 3},
|
||||
expected: "2.3",
|
||||
init: func() {},
|
||||
},
|
||||
{
|
||||
event: "AnalogRead0",
|
||||
data: []byte{0xE0, 0x23, 0x05},
|
||||
expected: 675,
|
||||
init: func() {},
|
||||
},
|
||||
{
|
||||
event: "AnalogRead1",
|
||||
data: []byte{0xE1, 0x23, 0x06},
|
||||
expected: 803,
|
||||
init: func() {},
|
||||
},
|
||||
{
|
||||
event: "DigitalRead2",
|
||||
data: []byte{0x90, 0x04, 0x00},
|
||||
expected: 1,
|
||||
init: func() { b.pins[2].Mode = Input },
|
||||
},
|
||||
{
|
||||
event: "DigitalRead4",
|
||||
data: []byte{0x90, 0x16, 0x00},
|
||||
expected: 1,
|
||||
init: func() { b.pins[4].Mode = Input },
|
||||
},
|
||||
{
|
||||
event: "PinState13",
|
||||
data: []byte{240, 110, 13, 1, 1, 247},
|
||||
expected: Pin{[]int{0, 1, 4}, 1, 0, 1, 127},
|
||||
init: func() {},
|
||||
},
|
||||
{
|
||||
event: "I2cReply",
|
||||
data: []byte{240, 119, 9, 0, 0, 0, 24, 1, 1, 0, 26, 1, 247},
|
||||
expected: I2cReply{
|
||||
Address: 9,
|
||||
Register: 0,
|
||||
Data: []byte{152, 1, 154},
|
||||
},
|
||||
init: func() {},
|
||||
},
|
||||
{
|
||||
event: "FirmwareQuery",
|
||||
data: []byte{240, 121, 2, 3, 83, 0, 116, 0, 97, 0, 110, 0, 100, 0, 97,
|
||||
0, 114, 0, 100, 0, 70, 0, 105, 0, 114, 0, 109, 0, 97, 0, 116, 0, 97, 0, 46,
|
||||
0, 105, 0, 110, 0, 111, 0, 247},
|
||||
expected: "StandardFirmata.ino",
|
||||
init: func() {},
|
||||
},
|
||||
{
|
||||
event: "StringData",
|
||||
data: append([]byte{240, 0x71}, append([]byte("Hello Firmata!"), 247)...),
|
||||
expected: "Hello Firmata!",
|
||||
init: func() {},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test.init()
|
||||
gobot.Once(b.Event(test.event), func(data interface{}) {
|
||||
gobot.Assert(t, data, test.expected)
|
||||
sem <- true
|
||||
})
|
||||
|
||||
testReadData = test.data
|
||||
go b.process()
|
||||
|
||||
select {
|
||||
case <-sem:
|
||||
case <-time.After(10 * time.Millisecond):
|
||||
t.Errorf("%v was not published", test.event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestConnect(t *testing.T) {
|
||||
b := New()
|
||||
|
||||
testReadData = testProtocolResponse()
|
||||
|
||||
gobot.Once(b.Event("ProtocolVersion"), func(data interface{}) {
|
||||
<-time.After(1 * time.Millisecond)
|
||||
testReadData = testFirmwareResponse()
|
||||
})
|
||||
|
||||
gobot.Once(b.Event("FirmwareQuery"), func(data interface{}) {
|
||||
<-time.After(1 * time.Millisecond)
|
||||
testReadData = testCapabilitiesResponse()
|
||||
})
|
||||
|
||||
gobot.Once(b.Event("CapabilityQuery"), func(data interface{}) {
|
||||
<-time.After(1 * time.Millisecond)
|
||||
testReadData = testAnalogMappingResponse()
|
||||
})
|
||||
|
||||
gobot.Once(b.Event("AnalogMappingQuery"), func(data interface{}) {
|
||||
|
||||
<-time.After(1 * time.Millisecond)
|
||||
testReadData = testProtocolResponse()
|
||||
})
|
||||
|
||||
gobot.Assert(t, b.Connect(readWriteCloser{}), nil)
|
||||
}
|
45
platforms/firmata/client/examples/blink.go
Normal file
45
platforms/firmata/client/examples/blink.go
Normal file
@ -0,0 +1,45 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/hybridgroup/gobot/platforms/firmata/client"
|
||||
"github.com/tarm/goserial"
|
||||
)
|
||||
|
||||
func main() {
|
||||
sp, err := serial.OpenPort(&serial.Config{Name: "/dev/ttyACM0", Baud: 57600})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
board := client.New(sp)
|
||||
|
||||
fmt.Println("connecting.....")
|
||||
err = board.Connect()
|
||||
defer board.Disconnect()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println("firmware name:", board.FirmwareName)
|
||||
fmt.Println("firmata version:", board.ProtocolVersion)
|
||||
|
||||
pin := 13
|
||||
if err = board.SetPinMode(pin, client.Output); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
level := 0
|
||||
|
||||
for {
|
||||
level ^= 1
|
||||
if err := board.DigitalWrite(pin, level); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println("level:", level)
|
||||
<-time.After(500 * time.Millisecond)
|
||||
}
|
||||
}
|
@ -1,446 +0,0 @@
|
||||
package firmata
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/hybridgroup/gobot"
|
||||
)
|
||||
|
||||
const (
|
||||
open byte = 1
|
||||
close byte = 0
|
||||
input byte = 0x00
|
||||
output byte = 0x01
|
||||
analog byte = 0x02
|
||||
pwm byte = 0x03
|
||||
servo byte = 0x04
|
||||
low byte = 0
|
||||
high byte = 1
|
||||
reportVersion byte = 0xF9
|
||||
systemReset byte = 0xFF
|
||||
digitalMessage byte = 0x90
|
||||
digitalMessageRangeStart byte = 0x90
|
||||
digitalMessageRangeEnd byte = 0x9F
|
||||
analogMessage byte = 0xE0
|
||||
analogMessageRangeStart byte = 0xE0
|
||||
analogMessageRangeEnd byte = 0xEF
|
||||
reportAnalog byte = 0xC0
|
||||
reportDigital byte = 0xD0
|
||||
pinMode byte = 0xF4
|
||||
startSysex byte = 0xF0
|
||||
endSysex byte = 0xF7
|
||||
capabilityQuery byte = 0x6B
|
||||
capabilityResponse byte = 0x6C
|
||||
pinStateQuery byte = 0x6D
|
||||
pinStateResponse byte = 0x6E
|
||||
analogMappingQuery byte = 0x69
|
||||
analogMappingResponse byte = 0x6A
|
||||
stringData byte = 0x71
|
||||
i2CRequest byte = 0x76
|
||||
i2CReply byte = 0x77
|
||||
i2CConfig byte = 0x78
|
||||
firmwareQuery byte = 0x79
|
||||
i2CModeWrite byte = 0x00
|
||||
i2CModeRead byte = 0x01
|
||||
i2CmodeContinuousRead byte = 0x02
|
||||
i2CModeStopReading byte = 0x03
|
||||
)
|
||||
|
||||
var defaultInitTimeInterval = 1 * time.Second
|
||||
|
||||
type board struct {
|
||||
serial io.ReadWriteCloser
|
||||
pins []pin
|
||||
analogPins []byte
|
||||
firmwareName string
|
||||
majorVersion byte
|
||||
minorVersion byte
|
||||
connected bool
|
||||
events map[string]*gobot.Event
|
||||
initTimeInterval time.Duration
|
||||
}
|
||||
|
||||
type pin struct {
|
||||
supportedModes []byte
|
||||
mode byte
|
||||
value int
|
||||
analogChannel byte
|
||||
}
|
||||
|
||||
// newBoard creates a new board connected in specified serial port.
|
||||
// Adds following events: "firmware_query", "capability_query",
|
||||
// "analog_mapping_query", "report_version", "i2c_reply",
|
||||
// "string_data", "firmware_query"
|
||||
func newBoard(sp io.ReadWriteCloser) *board {
|
||||
board := &board{
|
||||
majorVersion: 0,
|
||||
minorVersion: 0,
|
||||
serial: sp,
|
||||
firmwareName: "",
|
||||
pins: []pin{},
|
||||
analogPins: []byte{},
|
||||
connected: false,
|
||||
events: make(map[string]*gobot.Event),
|
||||
initTimeInterval: defaultInitTimeInterval,
|
||||
}
|
||||
|
||||
for _, s := range []string{
|
||||
"firmware_query",
|
||||
"capability_query",
|
||||
"analog_mapping_query",
|
||||
"report_version",
|
||||
"i2c_reply",
|
||||
"string_data",
|
||||
"firmware_query",
|
||||
} {
|
||||
board.events[s] = gobot.NewEvent()
|
||||
}
|
||||
|
||||
return board
|
||||
}
|
||||
|
||||
// connect starts connection to board.
|
||||
// Queries report version until connected
|
||||
func (b *board) connect() (err error) {
|
||||
if b.connected == false {
|
||||
if err = b.reset(); err != nil {
|
||||
return err
|
||||
}
|
||||
b.initBoard()
|
||||
for {
|
||||
if err = b.queryReportVersion(); err != nil {
|
||||
return err
|
||||
}
|
||||
<-time.After(b.initTimeInterval)
|
||||
if err = b.readAndProcess(); err != nil {
|
||||
return err
|
||||
}
|
||||
if b.connected == true {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// initBoard initializes board by listening for "firware_query", "capability_query"
|
||||
// and "analog_mapping_query" events
|
||||
func (b *board) initBoard() {
|
||||
gobot.Once(b.events["firmware_query"], func(data interface{}) {
|
||||
b.queryCapabilities()
|
||||
})
|
||||
|
||||
gobot.Once(b.events["capability_query"], func(data interface{}) {
|
||||
b.queryAnalogMapping()
|
||||
})
|
||||
|
||||
gobot.Once(b.events["analog_mapping_query"], func(data interface{}) {
|
||||
b.togglePinReporting(0, high, reportDigital)
|
||||
b.togglePinReporting(1, high, reportDigital)
|
||||
b.connected = true
|
||||
})
|
||||
}
|
||||
|
||||
// readAndProcess reads from serial port and parses data.
|
||||
func (b *board) readAndProcess() error {
|
||||
buf, err := b.read()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return b.process(buf)
|
||||
}
|
||||
|
||||
// reset writes system reset bytes.
|
||||
func (b *board) reset() error {
|
||||
return b.write([]byte{systemReset})
|
||||
}
|
||||
|
||||
// setPinMode writes pin mode bytes for specified pin.
|
||||
func (b *board) setPinMode(pin byte, mode byte) error {
|
||||
b.pins[pin].mode = mode
|
||||
return b.write([]byte{pinMode, pin, mode})
|
||||
}
|
||||
|
||||
// digitalWrite is used to send a digital value to a specified pin.
|
||||
func (b *board) digitalWrite(pin byte, value byte) error {
|
||||
port := byte(math.Floor(float64(pin) / 8))
|
||||
portValue := byte(0)
|
||||
|
||||
b.pins[pin].value = int(value)
|
||||
|
||||
for i := byte(0); i < 8; i++ {
|
||||
if b.pins[8*port+i].value != 0 {
|
||||
portValue = portValue | (1 << i)
|
||||
}
|
||||
}
|
||||
return b.write([]byte{digitalMessage | port, portValue & 0x7F, (portValue >> 7) & 0x7F})
|
||||
}
|
||||
|
||||
// analogWrite writes value to specified pin
|
||||
func (b *board) analogWrite(pin byte, value byte) error {
|
||||
b.pins[pin].value = int(value)
|
||||
return b.write([]byte{analogMessage | pin, value & 0x7F, (value >> 7) & 0x7F})
|
||||
}
|
||||
|
||||
// version returns board version following MAYOR.minor convention.
|
||||
func (b *board) version() string {
|
||||
return fmt.Sprintf("%v.%v", b.majorVersion, b.minorVersion)
|
||||
}
|
||||
|
||||
// queryFirmware writes bytes to query firmware from board.
|
||||
func (b *board) queryFirmware() error {
|
||||
return b.write([]byte{startSysex, firmwareQuery, endSysex})
|
||||
}
|
||||
|
||||
// queryPinState writes bytes to retrieve pin state
|
||||
func (b *board) queryPinState(pin byte) error {
|
||||
return b.write([]byte{startSysex, pinStateQuery, pin, endSysex})
|
||||
}
|
||||
|
||||
// queryReportVersion sends query for report version
|
||||
func (b *board) queryReportVersion() error {
|
||||
return b.write([]byte{reportVersion})
|
||||
}
|
||||
|
||||
// queryCapabilities is used to retrieve board capabilities.
|
||||
func (b *board) queryCapabilities() error {
|
||||
return b.write([]byte{startSysex, capabilityQuery, endSysex})
|
||||
}
|
||||
|
||||
// queryAnalogMapping returns analog mapping for board.
|
||||
func (b *board) queryAnalogMapping() error {
|
||||
return b.write([]byte{startSysex, analogMappingQuery, endSysex})
|
||||
}
|
||||
|
||||
// togglePinReporting is used to change pin reporting mode.
|
||||
func (b *board) togglePinReporting(pin byte, state byte, mode byte) error {
|
||||
return b.write([]byte{mode | pin, state})
|
||||
}
|
||||
|
||||
// i2cReadRequest reads from slaveAddress.
|
||||
func (b *board) i2cReadRequest(slaveAddress byte, numBytes uint) error {
|
||||
return b.write([]byte{startSysex, i2CRequest, slaveAddress, (i2CModeRead << 3),
|
||||
byte(numBytes & 0x7F), byte(((numBytes >> 7) & 0x7F)), endSysex})
|
||||
}
|
||||
|
||||
// i2cWriteRequest writes to slaveAddress.
|
||||
func (b *board) i2cWriteRequest(slaveAddress byte, data []byte) error {
|
||||
ret := []byte{startSysex, i2CRequest, slaveAddress, (i2CModeWrite << 3)}
|
||||
for _, val := range data {
|
||||
ret = append(ret, byte(val&0x7F))
|
||||
ret = append(ret, byte((val>>7)&0x7F))
|
||||
}
|
||||
ret = append(ret, endSysex)
|
||||
return b.write(ret)
|
||||
}
|
||||
|
||||
// i2xConfig returns i2c configuration.
|
||||
func (b *board) i2cConfig(data []byte) error {
|
||||
ret := []byte{startSysex, i2CConfig}
|
||||
for _, val := range data {
|
||||
ret = append(ret, byte(val&0xFF))
|
||||
ret = append(ret, byte((val>>8)&0xFF))
|
||||
}
|
||||
ret = append(ret, endSysex)
|
||||
return b.write(ret)
|
||||
}
|
||||
|
||||
// write is used to send commands to serial port
|
||||
func (b *board) write(commands []byte) (err error) {
|
||||
_, err = b.serial.Write(commands[:])
|
||||
return
|
||||
}
|
||||
|
||||
// read returns buffer reading from serial port (1024 bytes)
|
||||
func (b *board) read() (buf []byte, err error) {
|
||||
buf = make([]byte, 1024)
|
||||
_, err = b.serial.Read(buf)
|
||||
return
|
||||
}
|
||||
|
||||
// process uses incoming data and executes actions depending on what is received.
|
||||
// The following messages are processed: reportVersion, AnalogMessageRangeStart,
|
||||
// digitalMessageRangeStart.
|
||||
// And the following responses: capability, analog mapping, pin state,
|
||||
// i2c, firmwareQuery, string data.
|
||||
// If neither of those messages is received, then data is treated as "bad_byte"
|
||||
func (b *board) process(data []byte) (err error) {
|
||||
buf := bytes.NewBuffer(data)
|
||||
for {
|
||||
messageType, err := buf.ReadByte()
|
||||
if err != nil {
|
||||
// we ran out of bytes so we break out of the process loop
|
||||
break
|
||||
}
|
||||
switch {
|
||||
case reportVersion == messageType:
|
||||
if b.majorVersion, err = buf.ReadByte(); err != nil {
|
||||
return err
|
||||
}
|
||||
if b.minorVersion, err = buf.ReadByte(); err != nil {
|
||||
return err
|
||||
}
|
||||
gobot.Publish(b.events["report_version"], b.version())
|
||||
case analogMessageRangeStart <= messageType &&
|
||||
analogMessageRangeEnd >= messageType:
|
||||
|
||||
leastSignificantByte, err := buf.ReadByte()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mostSignificantByte, err := buf.ReadByte()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
value := uint(leastSignificantByte) | uint(mostSignificantByte)<<7
|
||||
pin := (messageType & 0x0F)
|
||||
|
||||
b.pins[b.analogPins[pin]].value = int(value)
|
||||
gobot.Publish(b.events[fmt.Sprintf("analog_read_%v", pin)],
|
||||
[]byte{
|
||||
byte(value >> 24),
|
||||
byte(value >> 16),
|
||||
byte(value >> 8),
|
||||
byte(value & 0xff),
|
||||
},
|
||||
)
|
||||
case digitalMessageRangeStart <= messageType &&
|
||||
digitalMessageRangeEnd >= messageType:
|
||||
|
||||
port := messageType & 0x0F
|
||||
firstBitmask, err := buf.ReadByte()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
secondBitmask, err := buf.ReadByte()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
portValue := firstBitmask | (secondBitmask << 7)
|
||||
|
||||
for i := 0; i < 8; i++ {
|
||||
pinNumber := (8*byte(port) + byte(i))
|
||||
pin := b.pins[pinNumber]
|
||||
if byte(pin.mode) == input {
|
||||
pin.value = int((portValue >> (byte(i) & 0x07)) & 0x01)
|
||||
gobot.Publish(b.events[fmt.Sprintf("digital_read_%v", pinNumber)],
|
||||
[]byte{byte(pin.value & 0xff)})
|
||||
}
|
||||
}
|
||||
case startSysex == messageType:
|
||||
currentBuffer := []byte{messageType}
|
||||
for {
|
||||
b, err := buf.ReadByte()
|
||||
if err != nil {
|
||||
// we ran out of bytes before we reached the endSysex so we break
|
||||
break
|
||||
}
|
||||
currentBuffer = append(currentBuffer, b)
|
||||
if currentBuffer[len(currentBuffer)-1] == endSysex {
|
||||
break
|
||||
}
|
||||
}
|
||||
command := currentBuffer[1]
|
||||
switch command {
|
||||
case capabilityResponse:
|
||||
supportedModes := 0
|
||||
n := 0
|
||||
|
||||
for _, val := range currentBuffer[2:(len(currentBuffer) - 5)] {
|
||||
if val == 127 {
|
||||
modes := []byte{}
|
||||
for _, mode := range []byte{input, output, analog, pwm, servo} {
|
||||
if (supportedModes & (1 << mode)) != 0 {
|
||||
modes = append(modes, mode)
|
||||
}
|
||||
}
|
||||
b.pins = append(b.pins, pin{modes, output, 0, 0})
|
||||
b.events[fmt.Sprintf("digital_read_%v", len(b.pins)-1)] = gobot.NewEvent()
|
||||
b.events[fmt.Sprintf("pin_%v_state", len(b.pins)-1)] = gobot.NewEvent()
|
||||
supportedModes = 0
|
||||
n = 0
|
||||
continue
|
||||
}
|
||||
|
||||
if n == 0 {
|
||||
supportedModes = supportedModes | (1 << val)
|
||||
}
|
||||
n ^= 1
|
||||
}
|
||||
gobot.Publish(b.events["capability_query"], nil)
|
||||
case analogMappingResponse:
|
||||
pinIndex := byte(0)
|
||||
|
||||
for _, val := range currentBuffer[2 : len(b.pins)-1] {
|
||||
|
||||
b.pins[pinIndex].analogChannel = val
|
||||
|
||||
if val != 127 {
|
||||
b.analogPins = append(b.analogPins, pinIndex)
|
||||
}
|
||||
b.events[fmt.Sprintf("analog_read_%v", pinIndex)] = gobot.NewEvent()
|
||||
pinIndex++
|
||||
}
|
||||
|
||||
gobot.Publish(b.events["analog_mapping_query"], nil)
|
||||
case pinStateResponse:
|
||||
pin := b.pins[currentBuffer[2]]
|
||||
pin.mode = currentBuffer[3]
|
||||
pin.value = int(currentBuffer[4])
|
||||
|
||||
if len(currentBuffer) > 6 {
|
||||
pin.value = int(uint(pin.value) | uint(currentBuffer[5])<<7)
|
||||
}
|
||||
if len(currentBuffer) > 7 {
|
||||
pin.value = int(uint(pin.value) | uint(currentBuffer[6])<<14)
|
||||
}
|
||||
|
||||
gobot.Publish(b.events[fmt.Sprintf("pin_%v_state", currentBuffer[2])],
|
||||
map[string]int{
|
||||
"pin": int(currentBuffer[2]),
|
||||
"mode": int(pin.mode),
|
||||
"value": int(pin.value),
|
||||
},
|
||||
)
|
||||
case i2CReply:
|
||||
i2cReply := map[string][]byte{
|
||||
"slave_address": []byte{byte(currentBuffer[2]) | byte(currentBuffer[3])<<7},
|
||||
"register": []byte{byte(currentBuffer[4]) | byte(currentBuffer[5])<<7},
|
||||
"data": []byte{byte(currentBuffer[6]) | byte(currentBuffer[7])<<7},
|
||||
}
|
||||
for i := 8; i < len(currentBuffer); i = i + 2 {
|
||||
if currentBuffer[i] == byte(0xF7) {
|
||||
break
|
||||
}
|
||||
if i+2 > len(currentBuffer) {
|
||||
break
|
||||
}
|
||||
i2cReply["data"] = append(i2cReply["data"],
|
||||
byte(currentBuffer[i])|byte(currentBuffer[i+1])<<7,
|
||||
)
|
||||
}
|
||||
gobot.Publish(b.events["i2c_reply"], i2cReply)
|
||||
case firmwareQuery:
|
||||
name := []byte{}
|
||||
for _, val := range currentBuffer[4:(len(currentBuffer) - 1)] {
|
||||
if val != 0 {
|
||||
name = append(name, val)
|
||||
}
|
||||
}
|
||||
b.firmwareName = string(name[:])
|
||||
gobot.Publish(b.events["firmware_query"], b.firmwareName)
|
||||
case stringData:
|
||||
str := currentBuffer[2:len(currentBuffer)]
|
||||
gobot.Publish(b.events["string_data"], string(str[:len(str)]))
|
||||
default:
|
||||
return fmt.Errorf("bad byte: 0x%x", command)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
@ -1,13 +1,12 @@
|
||||
package firmata
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/hybridgroup/gobot"
|
||||
"github.com/hybridgroup/gobot/platforms/firmata/client"
|
||||
"github.com/hybridgroup/gobot/platforms/gpio"
|
||||
"github.com/hybridgroup/gobot/platforms/i2c"
|
||||
"github.com/tarm/goserial"
|
||||
@ -23,14 +22,29 @@ var _ gpio.ServoWriter = (*FirmataAdaptor)(nil)
|
||||
|
||||
var _ i2c.I2c = (*FirmataAdaptor)(nil)
|
||||
|
||||
type firmataBoard interface {
|
||||
Connect(io.ReadWriteCloser) error
|
||||
Disconnect() error
|
||||
Pins() []client.Pin
|
||||
AnalogWrite(int, int) error
|
||||
SetPinMode(int, int) error
|
||||
ReportAnalog(int, int) error
|
||||
ReportDigital(int, int) error
|
||||
DigitalWrite(int, int) error
|
||||
I2cRead(int, int) error
|
||||
I2cWrite(int, []byte) error
|
||||
I2cConfig(int) error
|
||||
Event(string) *gobot.Event
|
||||
}
|
||||
|
||||
// FirmataAdaptor is the Gobot Adaptor for Firmata based boards
|
||||
type FirmataAdaptor struct {
|
||||
name string
|
||||
port string
|
||||
board *board
|
||||
i2cAddress byte
|
||||
board firmataBoard
|
||||
i2cAddress int
|
||||
conn io.ReadWriteCloser
|
||||
connect func(string) (io.ReadWriteCloser, error)
|
||||
openSP func(port string) (io.ReadWriteCloser, error)
|
||||
}
|
||||
|
||||
// NewFirmataAdaptor returns a new FirmataAdaptor with specified name and optionally accepts:
|
||||
@ -44,10 +58,11 @@ type FirmataAdaptor struct {
|
||||
// string port as a label to be displayed in the log and api.
|
||||
func NewFirmataAdaptor(name string, args ...interface{}) *FirmataAdaptor {
|
||||
f := &FirmataAdaptor{
|
||||
name: name,
|
||||
port: "",
|
||||
conn: nil,
|
||||
connect: func(port string) (io.ReadWriteCloser, error) {
|
||||
name: name,
|
||||
port: "",
|
||||
conn: nil,
|
||||
board: client.New(),
|
||||
openSP: func(port string) (io.ReadWriteCloser, error) {
|
||||
return serial.OpenPort(&serial.Config{Name: port, Baud: 57600})
|
||||
},
|
||||
}
|
||||
@ -67,23 +82,24 @@ func NewFirmataAdaptor(name string, args ...interface{}) *FirmataAdaptor {
|
||||
// Connect starts a connection to the board.
|
||||
func (f *FirmataAdaptor) Connect() (errs []error) {
|
||||
if f.conn == nil {
|
||||
sp, err := f.connect(f.Port())
|
||||
sp, err := f.openSP(f.Port())
|
||||
if err != nil {
|
||||
return []error{err}
|
||||
}
|
||||
f.conn = sp
|
||||
}
|
||||
f.board = newBoard(f.conn)
|
||||
f.board.connect()
|
||||
if err := f.board.Connect(f.conn); err != nil {
|
||||
return []error{err}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Disconnect closes the io connection to the board
|
||||
func (f *FirmataAdaptor) Disconnect() (err error) {
|
||||
if f.board != nil {
|
||||
return f.board.serial.Close()
|
||||
return f.board.Disconnect()
|
||||
}
|
||||
return errors.New("no board connected")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Finalize terminates the firmata connection
|
||||
@ -107,11 +123,13 @@ func (f *FirmataAdaptor) ServoWrite(pin string, angle byte) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
err = f.board.setPinMode(byte(p), servo)
|
||||
if err != nil {
|
||||
return err
|
||||
if f.board.Pins()[p].Mode != client.Servo {
|
||||
err = f.board.SetPinMode(p, client.Servo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err = f.board.analogWrite(byte(p), angle)
|
||||
err = f.board.AnalogWrite(p, int(angle))
|
||||
return
|
||||
}
|
||||
|
||||
@ -122,11 +140,13 @@ func (f *FirmataAdaptor) PwmWrite(pin string, level byte) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
err = f.board.setPinMode(byte(p), pwm)
|
||||
if err != nil {
|
||||
return err
|
||||
if f.board.Pins()[p].Mode != client.Pwm {
|
||||
err = f.board.SetPinMode(p, client.Pwm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err = f.board.analogWrite(byte(p), level)
|
||||
err = f.board.AnalogWrite(p, int(level))
|
||||
return
|
||||
}
|
||||
|
||||
@ -137,80 +157,60 @@ func (f *FirmataAdaptor) DigitalWrite(pin string, level byte) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
err = f.board.setPinMode(byte(p), output)
|
||||
if err != nil {
|
||||
return
|
||||
if f.board.Pins()[p].Mode != client.Output {
|
||||
err = f.board.SetPinMode(p, client.Output)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
err = f.board.digitalWrite(byte(p), level)
|
||||
err = f.board.DigitalWrite(p, int(level))
|
||||
return
|
||||
}
|
||||
|
||||
// DigitalRead retrieves digital value from specified pin.
|
||||
// Returns -1 if the response from the board has timed out
|
||||
func (f *FirmataAdaptor) DigitalRead(pin string) (val int, err error) {
|
||||
ret := make(chan int)
|
||||
|
||||
p, err := strconv.Atoi(pin)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if err = f.board.setPinMode(byte(p), input); err != nil {
|
||||
return
|
||||
}
|
||||
if err = f.board.togglePinReporting(byte(p), high, reportDigital); err != nil {
|
||||
return
|
||||
}
|
||||
if err = f.board.readAndProcess(); err != nil {
|
||||
return
|
||||
|
||||
if f.board.Pins()[p].Mode != client.Input {
|
||||
if err = f.board.SetPinMode(p, client.Input); err != nil {
|
||||
return
|
||||
}
|
||||
if err = f.board.ReportDigital(p, 1); err != nil {
|
||||
return
|
||||
}
|
||||
<-time.After(10 * time.Millisecond)
|
||||
}
|
||||
|
||||
gobot.Once(f.board.events[fmt.Sprintf("digital_read_%v", pin)], func(data interface{}) {
|
||||
ret <- int(data.([]byte)[0])
|
||||
})
|
||||
|
||||
select {
|
||||
case data := <-ret:
|
||||
return data, nil
|
||||
case <-time.After(10 * time.Millisecond):
|
||||
}
|
||||
return -1, nil
|
||||
return f.board.Pins()[p].Value, nil
|
||||
}
|
||||
|
||||
// AnalogRead retrieves value from analog pin.
|
||||
// Returns -1 if the response from the board has timed out
|
||||
func (f *FirmataAdaptor) AnalogRead(pin string) (val int, err error) {
|
||||
ret := make(chan int)
|
||||
|
||||
p, err := strconv.Atoi(pin)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// NOTE pins are numbered A0-A5, which translate to digital pins 14-19
|
||||
|
||||
p = f.digitalPin(p)
|
||||
if err = f.board.setPinMode(byte(p), analog); err != nil {
|
||||
return
|
||||
|
||||
if f.board.Pins()[p].Mode != client.Analog {
|
||||
if err = f.board.SetPinMode(p, client.Analog); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = f.board.ReportAnalog(p, 1); err != nil {
|
||||
return
|
||||
}
|
||||
<-time.After(10 * time.Millisecond)
|
||||
}
|
||||
|
||||
if err = f.board.togglePinReporting(byte(p), high, reportAnalog); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = f.board.readAndProcess(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
gobot.Once(f.board.events[fmt.Sprintf("analog_read_%v", pin)], func(data interface{}) {
|
||||
b := data.([]byte)
|
||||
ret <- int(uint(b[0])<<24 | uint(b[1])<<16 | uint(b[2])<<8 | uint(b[3]))
|
||||
})
|
||||
|
||||
select {
|
||||
case data := <-ret:
|
||||
return data, nil
|
||||
case <-time.After(10 * time.Millisecond):
|
||||
}
|
||||
return -1, nil
|
||||
return f.board.Pins()[p].Value, nil
|
||||
}
|
||||
|
||||
// digitalPin converts pin number to digital mapping
|
||||
@ -220,35 +220,29 @@ func (f *FirmataAdaptor) digitalPin(pin int) int {
|
||||
|
||||
// I2cStart starts an i2c device at specified address
|
||||
func (f *FirmataAdaptor) I2cStart(address byte) (err error) {
|
||||
f.i2cAddress = address
|
||||
return f.board.i2cConfig([]byte{0})
|
||||
f.i2cAddress = int(address)
|
||||
return f.board.I2cConfig(0)
|
||||
}
|
||||
|
||||
// I2cRead returns size bytes from the i2c device
|
||||
// Returns an empty array if the response from the board has timed out
|
||||
func (f *FirmataAdaptor) I2cRead(size uint) (data []byte, err error) {
|
||||
ret := make(chan []byte)
|
||||
if err = f.board.i2cReadRequest(f.i2cAddress, size); err != nil {
|
||||
|
||||
if err = f.board.I2cRead(f.i2cAddress, int(size)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = f.board.readAndProcess(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
gobot.Once(f.board.events["i2c_reply"], func(data interface{}) {
|
||||
ret <- data.(map[string][]byte)["data"]
|
||||
gobot.Once(f.board.Event("I2cReply"), func(data interface{}) {
|
||||
ret <- data.(client.I2cReply).Data
|
||||
})
|
||||
|
||||
select {
|
||||
case data := <-ret:
|
||||
return data, nil
|
||||
case <-time.After(10 * time.Millisecond):
|
||||
}
|
||||
return []byte{}, nil
|
||||
data = <-ret
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// I2cWrite writes data to i2c device
|
||||
func (f *FirmataAdaptor) I2cWrite(data []byte) (err error) {
|
||||
return f.board.i2cWriteRequest(f.i2cAddress, data)
|
||||
return f.board.I2cWrite(f.i2cAddress, data)
|
||||
}
|
||||
|
@ -2,45 +2,83 @@ package firmata
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hybridgroup/gobot"
|
||||
"github.com/hybridgroup/gobot/platforms/firmata/client"
|
||||
)
|
||||
|
||||
var connect = func(a *FirmataAdaptor) []error {
|
||||
defaultInitTimeInterval = 0 * time.Second
|
||||
gobot.After(1*time.Millisecond, func() {
|
||||
// arduino uno r3 firmware response "StandardFirmata.ino"
|
||||
a.board.process([]byte{240, 121, 2, 3, 83, 0, 116, 0, 97, 0, 110, 0, 100,
|
||||
0, 97, 0, 114, 0, 100, 0, 70, 0, 105, 0, 114, 0, 109, 0, 97, 0, 116, 0,
|
||||
97, 0, 46, 0, 105, 0, 110, 0, 111, 0, 247})
|
||||
// arduino uno r3 capabilities response
|
||||
a.board.process([]byte{240, 108, 127, 127, 0, 1, 1, 1, 4, 14, 127, 0, 1,
|
||||
1, 1, 3, 8, 4, 14, 127, 0, 1, 1, 1, 4, 14, 127, 0, 1, 1, 1, 3, 8, 4, 14,
|
||||
127, 0, 1, 1, 1, 3, 8, 4, 14, 127, 0, 1, 1, 1, 4, 14, 127, 0, 1, 1, 1,
|
||||
4, 14, 127, 0, 1, 1, 1, 3, 8, 4, 14, 127, 0, 1, 1, 1, 3, 8, 4, 14, 127,
|
||||
0, 1, 1, 1, 3, 8, 4, 14, 127, 0, 1, 1, 1, 4, 14, 127, 0, 1, 1, 1, 4, 14,
|
||||
127, 0, 1, 1, 1, 2, 10, 127, 0, 1, 1, 1, 2, 10, 127, 0, 1, 1, 1, 2, 10,
|
||||
127, 0, 1, 1, 1, 2, 10, 127, 0, 1, 1, 1, 2, 10, 6, 1, 127, 0, 1, 1, 1,
|
||||
2, 10, 6, 1, 127, 247})
|
||||
// arduino uno r3 analog mapping response
|
||||
a.board.process([]byte{240, 106, 127, 127, 127, 127, 127, 127, 127, 127,
|
||||
127, 127, 127, 127, 127, 127, 0, 1, 2, 3, 4, 5, 247})
|
||||
})
|
||||
return a.Connect()
|
||||
type readWriteCloser struct{}
|
||||
|
||||
func (readWriteCloser) Write(p []byte) (int, error) {
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
var testReadData = []byte{}
|
||||
|
||||
func (readWriteCloser) Read(b []byte) (int, error) {
|
||||
size := len(b)
|
||||
if len(testReadData) < size {
|
||||
size = len(testReadData)
|
||||
}
|
||||
copy(b, []byte(testReadData)[:size])
|
||||
testReadData = testReadData[size:]
|
||||
|
||||
return size, nil
|
||||
}
|
||||
|
||||
func (readWriteCloser) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type mockFirmataBoard struct {
|
||||
disconnectError error
|
||||
gobot.Eventer
|
||||
pins []client.Pin
|
||||
}
|
||||
|
||||
func newMockFirmataBoard() *mockFirmataBoard {
|
||||
m := &mockFirmataBoard{
|
||||
Eventer: gobot.NewEventer(),
|
||||
disconnectError: nil,
|
||||
pins: make([]client.Pin, 100),
|
||||
}
|
||||
|
||||
m.pins[1].Value = 1
|
||||
m.pins[15].Value = 133
|
||||
|
||||
m.AddEvent("I2cReply")
|
||||
return m
|
||||
}
|
||||
|
||||
func (mockFirmataBoard) Connect(io.ReadWriteCloser) error { return nil }
|
||||
func (m mockFirmataBoard) Disconnect() error {
|
||||
return m.disconnectError
|
||||
}
|
||||
func (m mockFirmataBoard) Pins() []client.Pin {
|
||||
return m.pins
|
||||
}
|
||||
func (mockFirmataBoard) AnalogWrite(int, int) error { return nil }
|
||||
func (mockFirmataBoard) SetPinMode(int, int) error { return nil }
|
||||
func (mockFirmataBoard) ReportAnalog(int, int) error { return nil }
|
||||
func (mockFirmataBoard) ReportDigital(int, int) error { return nil }
|
||||
func (mockFirmataBoard) DigitalWrite(int, int) error { return nil }
|
||||
func (mockFirmataBoard) I2cRead(int, int) error { return nil }
|
||||
func (mockFirmataBoard) I2cWrite(int, []byte) error { return nil }
|
||||
func (mockFirmataBoard) I2cConfig(int) error { return nil }
|
||||
|
||||
func initTestFirmataAdaptor() *FirmataAdaptor {
|
||||
a := NewFirmataAdaptor("board", "/dev/null")
|
||||
a.connect = func(port string) (io.ReadWriteCloser, error) {
|
||||
return &NullReadWriteCloser{}, nil
|
||||
a.board = newMockFirmataBoard()
|
||||
a.openSP = func(port string) (io.ReadWriteCloser, error) {
|
||||
return &readWriteCloser{}, nil
|
||||
}
|
||||
connect(a)
|
||||
a.Connect()
|
||||
return a
|
||||
}
|
||||
|
||||
func TestFirmataAdaptor(t *testing.T) {
|
||||
a := initTestFirmataAdaptor()
|
||||
gobot.Assert(t, a.Name(), "board")
|
||||
@ -51,31 +89,31 @@ func TestFirmataAdaptorFinalize(t *testing.T) {
|
||||
a := initTestFirmataAdaptor()
|
||||
gobot.Assert(t, len(a.Finalize()), 0)
|
||||
|
||||
closeErr = errors.New("close error")
|
||||
a = initTestFirmataAdaptor()
|
||||
a.board.(*mockFirmataBoard).disconnectError = errors.New("close error")
|
||||
gobot.Assert(t, a.Finalize()[0], errors.New("close error"))
|
||||
}
|
||||
|
||||
func TestFirmataAdaptorDisconnect(t *testing.T) {
|
||||
a := NewFirmataAdaptor("board", "/dev/null")
|
||||
gobot.Assert(t, a.Disconnect(), errors.New("no board connected"))
|
||||
}
|
||||
|
||||
func TestFirmataAdaptorConnect(t *testing.T) {
|
||||
a := NewFirmataAdaptor("board", "/dev/null")
|
||||
a.connect = func(port string) (io.ReadWriteCloser, error) {
|
||||
return &NullReadWriteCloser{}, nil
|
||||
var openSP = func(port string) (io.ReadWriteCloser, error) {
|
||||
return &readWriteCloser{}, nil
|
||||
}
|
||||
gobot.Assert(t, len(connect(a)), 0)
|
||||
a := NewFirmataAdaptor("board", "/dev/null")
|
||||
a.openSP = openSP
|
||||
a.board = newMockFirmataBoard()
|
||||
gobot.Assert(t, len(a.Connect()), 0)
|
||||
|
||||
a = NewFirmataAdaptor("board", "/dev/null")
|
||||
a.connect = func(port string) (io.ReadWriteCloser, error) {
|
||||
a.board = newMockFirmataBoard()
|
||||
a.openSP = func(port string) (io.ReadWriteCloser, error) {
|
||||
return nil, errors.New("connect error")
|
||||
}
|
||||
gobot.Assert(t, a.Connect()[0], errors.New("connect error"))
|
||||
|
||||
a = NewFirmataAdaptor("board", &NullReadWriteCloser{})
|
||||
gobot.Assert(t, len(connect(a)), 0)
|
||||
a = NewFirmataAdaptor("board", &readWriteCloser{})
|
||||
a.board = newMockFirmataBoard()
|
||||
gobot.Assert(t, len(a.Connect()), 0)
|
||||
|
||||
}
|
||||
|
||||
func TestFirmataAdaptorServoWrite(t *testing.T) {
|
||||
@ -95,41 +133,16 @@ func TestFirmataAdaptorDigitalWrite(t *testing.T) {
|
||||
|
||||
func TestFirmataAdaptorDigitalRead(t *testing.T) {
|
||||
a := initTestFirmataAdaptor()
|
||||
pinNumber := "1"
|
||||
// -1 on no data
|
||||
val, _ := a.DigitalRead(pinNumber)
|
||||
gobot.Assert(t, val, -1)
|
||||
|
||||
go func() {
|
||||
<-time.After(5 * time.Millisecond)
|
||||
gobot.Publish(a.board.events[fmt.Sprintf("digital_read_%v", pinNumber)],
|
||||
[]byte{0x01})
|
||||
}()
|
||||
val, _ = a.DigitalRead(pinNumber)
|
||||
gobot.Assert(t, val, 0x01)
|
||||
val, err := a.DigitalRead("1")
|
||||
gobot.Assert(t, err, nil)
|
||||
gobot.Assert(t, val, 1)
|
||||
}
|
||||
|
||||
func TestFirmataAdaptorAnalogRead(t *testing.T) {
|
||||
a := initTestFirmataAdaptor()
|
||||
pinNumber := "1"
|
||||
// -1 on no data
|
||||
val, _ := a.AnalogRead(pinNumber)
|
||||
gobot.Assert(t, val, -1)
|
||||
|
||||
value := 133
|
||||
go func() {
|
||||
<-time.After(5 * time.Millisecond)
|
||||
gobot.Publish(a.board.events[fmt.Sprintf("analog_read_%v", pinNumber)],
|
||||
[]byte{
|
||||
byte(value >> 24),
|
||||
byte(value >> 16),
|
||||
byte(value >> 8),
|
||||
byte(value & 0xff),
|
||||
},
|
||||
)
|
||||
}()
|
||||
val, _ = a.AnalogRead(pinNumber)
|
||||
val, err := a.AnalogRead("1")
|
||||
gobot.Assert(t, val, 133)
|
||||
gobot.Assert(t, err, nil)
|
||||
}
|
||||
|
||||
func TestFirmataAdaptorI2cStart(t *testing.T) {
|
||||
@ -138,18 +151,14 @@ func TestFirmataAdaptorI2cStart(t *testing.T) {
|
||||
}
|
||||
func TestFirmataAdaptorI2cRead(t *testing.T) {
|
||||
a := initTestFirmataAdaptor()
|
||||
// [] on no data
|
||||
data, _ := a.I2cRead(1)
|
||||
gobot.Assert(t, data, []byte{})
|
||||
|
||||
i := []byte{100}
|
||||
i2cReply := map[string][]byte{}
|
||||
i2cReply["data"] = i
|
||||
i2cReply := client.I2cReply{Data: i}
|
||||
go func() {
|
||||
<-time.After(5 * time.Millisecond)
|
||||
gobot.Publish(a.board.events["i2c_reply"], i2cReply)
|
||||
<-time.After(10 * time.Millisecond)
|
||||
gobot.Publish(a.board.Event("I2cReply"), i2cReply)
|
||||
}()
|
||||
data, _ = a.I2cRead(1)
|
||||
data, err := a.I2cRead(1)
|
||||
gobot.Assert(t, err, nil)
|
||||
gobot.Assert(t, data, i)
|
||||
}
|
||||
func TestFirmataAdaptorI2cWrite(t *testing.T) {
|
||||
|
@ -1,190 +0,0 @@
|
||||
package firmata
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hybridgroup/gobot"
|
||||
)
|
||||
|
||||
type NullReadWriteCloser struct{}
|
||||
|
||||
func (NullReadWriteCloser) Write(p []byte) (int, error) {
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (NullReadWriteCloser) Read(b []byte) (int, error) {
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
var closeErr error
|
||||
|
||||
func (NullReadWriteCloser) Close() error {
|
||||
return closeErr
|
||||
}
|
||||
|
||||
func initTestFirmata() *board {
|
||||
b := newBoard(NullReadWriteCloser{})
|
||||
b.initTimeInterval = 0 * time.Second
|
||||
// arduino uno r3 firmware response "StandardFirmata.ino"
|
||||
b.process([]byte{240, 121, 2, 3, 83, 0, 116, 0, 97, 0, 110, 0, 100, 0, 97,
|
||||
0, 114, 0, 100, 0, 70, 0, 105, 0, 114, 0, 109, 0, 97, 0, 116, 0, 97, 0, 46,
|
||||
0, 105, 0, 110, 0, 111, 0, 247})
|
||||
// arduino uno r3 capabilities response
|
||||
b.process([]byte{240, 108, 127, 127, 0, 1, 1, 1, 4, 14, 127, 0, 1, 1, 1, 3,
|
||||
8, 4, 14, 127, 0, 1, 1, 1, 4, 14, 127, 0, 1, 1, 1, 3, 8, 4, 14, 127, 0, 1,
|
||||
1, 1, 3, 8, 4, 14, 127, 0, 1, 1, 1, 4, 14, 127, 0, 1, 1, 1, 4, 14, 127, 0,
|
||||
1, 1, 1, 3, 8, 4, 14, 127, 0, 1, 1, 1, 3, 8, 4, 14, 127, 0, 1, 1, 1, 3, 8,
|
||||
4, 14, 127, 0, 1, 1, 1, 4, 14, 127, 0, 1, 1, 1, 4, 14, 127, 0, 1, 1, 1, 2,
|
||||
10, 127, 0, 1, 1, 1, 2, 10, 127, 0, 1, 1, 1, 2, 10, 127, 0, 1, 1, 1, 2, 10,
|
||||
127, 0, 1, 1, 1, 2, 10, 6, 1, 127, 0, 1, 1, 1, 2, 10, 6, 1, 127, 247})
|
||||
// arduino uno r3 analog mapping response
|
||||
b.process([]byte{240, 106, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
|
||||
127, 127, 127, 127, 0, 1, 2, 3, 4, 5, 247})
|
||||
return b
|
||||
}
|
||||
|
||||
func TestReportVersion(t *testing.T) {
|
||||
b := initTestFirmata()
|
||||
//test if functions executes
|
||||
b.queryReportVersion()
|
||||
}
|
||||
|
||||
func TestQueryFirmware(t *testing.T) {
|
||||
b := initTestFirmata()
|
||||
//test if functions executes
|
||||
b.queryFirmware()
|
||||
}
|
||||
|
||||
func TestQueryPinState(t *testing.T) {
|
||||
b := initTestFirmata()
|
||||
//test if functions executes
|
||||
b.queryPinState(byte(1))
|
||||
}
|
||||
|
||||
func TestProcess(t *testing.T) {
|
||||
b := initTestFirmata()
|
||||
sem := make(chan bool)
|
||||
//reportVersion
|
||||
gobot.Once(b.events["report_version"], func(data interface{}) {
|
||||
gobot.Assert(t, data.(string), "1.17")
|
||||
sem <- true
|
||||
})
|
||||
b.process([]byte{0xF9, 0x01, 0x11})
|
||||
select {
|
||||
case <-sem:
|
||||
case <-time.After(10 * time.Millisecond):
|
||||
t.Errorf("report_version was not published")
|
||||
}
|
||||
//analogMessageRangeStart
|
||||
gobot.Once(b.events["analog_read_0"], func(data interface{}) {
|
||||
b := data.([]byte)
|
||||
gobot.Assert(t,
|
||||
int(uint(b[0])<<24|uint(b[1])<<16|uint(b[2])<<8|uint(b[3])),
|
||||
675)
|
||||
sem <- true
|
||||
})
|
||||
b.process([]byte{0xE0, 0x23, 0x05})
|
||||
select {
|
||||
case <-sem:
|
||||
case <-time.After(10 * time.Millisecond):
|
||||
t.Errorf("analog_read_0 was not published")
|
||||
}
|
||||
gobot.Once(b.events["analog_read_1"], func(data interface{}) {
|
||||
b := data.([]byte)
|
||||
gobot.Assert(t,
|
||||
int(uint(b[0])<<24|uint(b[1])<<16|uint(b[2])<<8|uint(b[3])),
|
||||
803)
|
||||
sem <- true
|
||||
})
|
||||
b.process([]byte{0xE1, 0x23, 0x06})
|
||||
select {
|
||||
case <-sem:
|
||||
case <-time.After(10 * time.Millisecond):
|
||||
t.Errorf("analog_read_1 was not published")
|
||||
}
|
||||
//digitalMessageRangeStart
|
||||
b.pins[2].mode = input
|
||||
gobot.Once(b.events["digital_read_2"], func(data interface{}) {
|
||||
gobot.Assert(t, int(data.([]byte)[0]), 1)
|
||||
sem <- true
|
||||
})
|
||||
b.process([]byte{0x90, 0x04, 0x00})
|
||||
select {
|
||||
case <-sem:
|
||||
case <-time.After(10 * time.Millisecond):
|
||||
t.Errorf("digital_read_2 was not published")
|
||||
}
|
||||
gobot.Once(b.events["analog_read_1"], func(data interface{}) {
|
||||
b := data.([]byte)
|
||||
gobot.Assert(t,
|
||||
int(uint(b[0])<<24|uint(b[1])<<16|uint(b[2])<<8|uint(b[3])),
|
||||
803)
|
||||
sem <- true
|
||||
})
|
||||
b.pins[4].mode = input
|
||||
gobot.Once(b.events["digital_read_4"], func(data interface{}) {
|
||||
gobot.Assert(t, int(data.([]byte)[0]), 1)
|
||||
sem <- true
|
||||
})
|
||||
b.process([]byte{0x90, 0x16, 0x00})
|
||||
select {
|
||||
case <-sem:
|
||||
case <-time.After(10 * time.Millisecond):
|
||||
t.Errorf("digital_read_4 was not published")
|
||||
}
|
||||
//pinStateResponse
|
||||
gobot.Once(b.events["pin_13_state"], func(data interface{}) {
|
||||
gobot.Assert(t, data, map[string]int{
|
||||
"pin": 13,
|
||||
"mode": 1,
|
||||
"value": 1,
|
||||
})
|
||||
sem <- true
|
||||
})
|
||||
b.process([]byte{240, 110, 13, 1, 1, 247})
|
||||
select {
|
||||
case <-sem:
|
||||
case <-time.After(10 * time.Millisecond):
|
||||
t.Errorf("pin_13_state was not published")
|
||||
}
|
||||
//i2cReply
|
||||
gobot.Once(b.events["i2c_reply"], func(data interface{}) {
|
||||
i2cReply := map[string][]byte{
|
||||
"slave_address": []byte{9},
|
||||
"register": []byte{0},
|
||||
"data": []byte{152, 1, 154}}
|
||||
gobot.Assert(t, data.(map[string][]byte), i2cReply)
|
||||
sem <- true
|
||||
})
|
||||
b.process([]byte{240, 119, 9, 0, 0, 0, 24, 1, 1, 0, 26, 1, 247})
|
||||
select {
|
||||
case <-sem:
|
||||
case <-time.After(10 * time.Millisecond):
|
||||
t.Errorf("i2c_reply was not published")
|
||||
}
|
||||
//firmwareName
|
||||
gobot.Once(b.events["firmware_query"], func(data interface{}) {
|
||||
gobot.Assert(t, data.(string), "StandardFirmata.ino")
|
||||
sem <- true
|
||||
})
|
||||
b.process([]byte{240, 121, 2, 3, 83, 0, 116, 0, 97, 0, 110, 0, 100, 0, 97,
|
||||
0, 114, 0, 100, 0, 70, 0, 105, 0, 114, 0, 109, 0, 97, 0, 116, 0, 97, 0, 46,
|
||||
0, 105, 0, 110, 0, 111, 0, 247})
|
||||
select {
|
||||
case <-sem:
|
||||
case <-time.After(10 * time.Millisecond):
|
||||
t.Errorf("firmware_query was not published")
|
||||
}
|
||||
//stringData
|
||||
gobot.Once(b.events["string_data"], func(data interface{}) {
|
||||
gobot.Assert(t, data.(string), "Hello Firmata!")
|
||||
sem <- true
|
||||
})
|
||||
b.process(append([]byte{240, 0x71}, []byte("Hello Firmata!")...))
|
||||
select {
|
||||
case <-sem:
|
||||
case <-time.After(10 * time.Millisecond):
|
||||
t.Errorf("string_data was not published")
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
#!/bin/bash
|
||||
PACKAGES=('gobot' 'gobot/api' 'gobot/platforms/intel-iot/edison' 'gobot/sysfs' $(ls ./platforms | sed -e 's/^/gobot\/platforms\//'))
|
||||
PACKAGES=('gobot' 'gobot/api' 'gobot/platforms/firmata/client' 'gobot/platforms/intel-iot/edison' 'gobot/sysfs' $(ls ./platforms | sed -e 's/^/gobot\/platforms\//'))
|
||||
EXITCODE=0
|
||||
|
||||
go get code.google.com/p/go.tools/cmd/cover
|
||||
|
Loading…
x
Reference in New Issue
Block a user