1
0
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:
Ron Evans 2015-06-30 14:14:11 -07:00
commit 10b2de6097
9 changed files with 907 additions and 801 deletions

View File

@ -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:

View 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
}

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

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

View File

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

View File

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

View File

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

View File

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

View File

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