Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,25 @@ go-firmata
==========

Arduino Firmata client for golang

Sample usage (blink internal led on pin 13):


package main

import (
"time"
firmata "github.com/baol/go-firmata"
)

func main() {
c, err := firmata.NewClient("/dev/ttyUSB0", 57600)
if err != nil {
panic("Cannot open client")
panic(err)
}
time.Sleep(time.Duration(1) * time.Second)
c.DigitalWrite(13, true)
time.Sleep(time.Duration(1) * time.Second)
c.DigitalWrite(13, false)
}
292 changes: 148 additions & 144 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,197 +15,201 @@
package firmata

import (
"code.google.com/p/log4go"
"github.com/tarm/goserial"
"io/ioutil"

"fmt"
"io"
"time"
serial "github.com/tarm/goserial"

"fmt"
"io"
"log"
"os"
"time"
)

// Arduino Firmata client for golang
type FirmataClient struct {
serialDev string
baud int
conn *io.ReadWriteCloser
Log *log4go.Logger

protocolVersion []byte
firmwareVersion []int
firmwareName string

ready bool
analogMappingDone bool
capabilityDone bool

digitalPinState [8]byte

analogPinsChannelMap map[int]byte
analogChannelPinsMap map[byte]int
pinModes []map[PinMode]interface{}

valueChan chan FirmataValue
serialChan chan string
spiChan chan []byte
serialDev string
baud int
conn *io.ReadWriteCloser
Error *log.Logger
Info *log.Logger
Debug *log.Logger

protocolVersion []byte
firmwareVersion []int
firmwareName string

ready bool
analogMappingDone bool
capabilityDone bool

digitalPinState [8]byte

analogPinsChannelMap map[int]byte
analogChannelPinsMap map[byte]int
pinModes []map[PinMode]interface{}

valueChan chan FirmataValue
serialChan chan string
spiChan chan []byte
}

// Creates a new FirmataClient object and connects to the Arduino board
// over specified serial port. This function blocks till a connection is
// succesfullt established and pin mappings are retrieved.
func NewClient(dev string, baud int) (client *FirmataClient, err error) {
var conn io.ReadWriteCloser

c := &serial.Config{Name: dev, Baud: baud}
conn, err = serial.OpenPort(c)
if err != nil {
client.Log.Critical(err)
return
}

logger := make(log4go.Logger)
logger.AddFilter("stdout", log4go.INFO, log4go.NewConsoleLogWriter())
client = &FirmataClient{
serialDev: dev,
baud: baud,
conn: &conn,
Log: &logger,
}
go client.replyReader()

conn.Write([]byte{byte(SystemReset)})
t := time.NewTicker(time.Second)

for !(client.ready && client.analogMappingDone && client.capabilityDone) {
select {
case <-t.C:
//no-op
case <-time.After(time.Second * 15):
client.Log.Critical("No response in 30 seconds. Resetting arduino")
conn.Write([]byte{byte(SystemReset)})
case <-time.After(time.Second * 30):
client.Log.Critical("Unable to initialize connection")
conn.Close()
client = nil
}
}

client.Log.Info("Client ready to use")

return
var conn io.ReadWriteCloser

c := &serial.Config{Name: dev, Baud: baud}
conn, err = serial.OpenPort(c)
if err != nil {
panic(err)
}

client = &FirmataClient{
serialDev: dev,
baud: baud,
conn: &conn,
Error: log.New(os.Stderr, "go-firmata: ", log.Ldate|log.Ltime|log.Lshortfile),
Info: log.New(os.Stderr, "go-firmata: ", log.Ldate|log.Ltime|log.Lshortfile),
Debug: log.New(ioutil.Discard, "go-firmata: ", log.Ldate|log.Ltime|log.Lshortfile),
}
go client.replyReader()

conn.Write([]byte{byte(SystemReset)})
t := time.NewTicker(time.Second)

for !(client.ready && client.analogMappingDone && client.capabilityDone) {
select {
case <-t.C:
//no-op
case <-time.After(time.Second * 15):
client.Error.Print("No response in 30 seconds. Resetting arduino")
conn.Write([]byte{byte(SystemReset)})
case <-time.After(time.Second * 30):
client.Error.Print("Unable to initialize connection")
conn.Close()
client = nil
}
}

client.Info.Print("Client ready to use")

return
}

// Close the serial connection to properly clean up after ourselves
// Usage: defer client.Close()
func (c *FirmataClient) Close() {
(*c.conn).Close()
(*c.conn).Close()
}

// Sets the Pin mode (input, output, etc.) for the Arduino pin
func (c *FirmataClient) SetPinMode(pin byte, mode PinMode) (err error) {
if c.pinModes[pin][mode] == nil {
err = fmt.Errorf("Pin mode %v not supported by pin %v", mode, pin)
return
}
cmd := []byte{byte(SetPinMode), (pin & 0x7F), byte(mode)}
err = c.sendCommand(cmd)
return
if c.pinModes[pin][mode] == nil {
err = fmt.Errorf("Pin mode %v not supported by pin %v", mode, pin)
return
}
cmd := []byte{byte(SetPinMode), (pin & 0x7F), byte(mode)}
err = c.sendCommand(cmd)
return
}

// Specified if a digital Pin should be watched for input.
// Values will be streamed back over a channel which can be retrieved by the GetValues() call
func (c *FirmataClient) EnableDigitalInput(pin uint, val bool) (err error) {
if pin < 0 || pin > uint(len(c.pinModes)) {
err = fmt.Errorf("Invalid pin number %v\n", pin)
return
}
port := (pin / 8) & 0x7F
pin = pin % 8

if val {
cmd := []byte{byte(EnableDigitalInput) | byte(port), 0x01}
err = c.sendCommand(cmd)
} else {
cmd := []byte{byte(EnableDigitalInput) | byte(port), 0x00}
err = c.sendCommand(cmd)
}

return
if pin < 0 || pin > uint(len(c.pinModes)) {
err = fmt.Errorf("Invalid pin number %v\n", pin)
return
}
port := (pin / 8) & 0x7F
pin = pin % 8

if val {
cmd := []byte{byte(EnableDigitalInput) | byte(port), 0x01}
err = c.sendCommand(cmd)
} else {
cmd := []byte{byte(EnableDigitalInput) | byte(port), 0x00}
err = c.sendCommand(cmd)
}

return
}

// Set the value of a digital pin
func (c *FirmataClient) DigitalWrite(pin uint, val bool) (err error) {
if pin < 0 || pin > uint(len(c.pinModes)) && c.pinModes[pin][Output] != nil {
err = fmt.Errorf("Invalid pin number %v\n", pin)
return
}
port := (pin / 8) & 0x7F
portData := &c.digitalPinState[port]
pin = pin % 8

if val {
(*portData) = (*portData) | (1 << pin)
} else {
(*portData) = (*portData) & ^(1 << pin)
}
data := to7Bit(*(portData))
cmd := []byte{byte(DigitalMessage) | byte(port), data[0], data[1]}
err = c.sendCommand(cmd)
return
if pin < 0 || pin > uint(len(c.pinModes)) && c.pinModes[pin][Output] != nil {
err = fmt.Errorf("Invalid pin number %v\n", pin)
return
}
port := (pin / 8) & 0x7F
portData := &c.digitalPinState[port]
pin = pin % 8

if val {
(*portData) = (*portData) | (1 << pin)
} else {
(*portData) = (*portData) & ^(1 << pin)
}
data := to7Bit(*(portData))
cmd := []byte{byte(DigitalMessage) | byte(port), data[0], data[1]}
err = c.sendCommand(cmd)
return
}

// Specified if a analog Pin should be watched for input.
// Values will be streamed back over a channel which can be retrieved by the GetValues() call
func (c *FirmataClient) EnableAnalogInput(pin uint, val bool) (err error) {
if pin < 0 || pin > uint(len(c.pinModes)) && c.pinModes[pin][Analog] != nil {
err = fmt.Errorf("Invalid pin number %v\n", pin)
return
}

ch := byte(c.analogPinsChannelMap[int(pin)])
c.Log.Debug("Enable analog inout on pin %v channel %v", pin, ch)
if val {
cmd := []byte{byte(EnableAnalogInput) | ch, 0x01}
err = c.sendCommand(cmd)
} else {
cmd := []byte{byte(EnableAnalogInput) | ch, 0x00}
err = c.sendCommand(cmd)
}

return
if pin < 0 || pin > uint(len(c.pinModes)) && c.pinModes[pin][Analog] != nil {
err = fmt.Errorf("Invalid pin number %v\n", pin)
return
}

ch := byte(c.analogPinsChannelMap[int(pin)])
c.Debug.Printf("Enable analog inout on pin %v channel %v", pin, ch)
if val {
cmd := []byte{byte(EnableAnalogInput) | ch, 0x01}
err = c.sendCommand(cmd)
} else {
cmd := []byte{byte(EnableAnalogInput) | ch, 0x00}
err = c.sendCommand(cmd)
}

return
}

// Set the value of a analog pin
func (c *FirmataClient) AnalogWrite(pin uint, pinData byte) (err error) {
if pin < 0 || pin > uint(len(c.pinModes)) && c.pinModes[pin][Analog] != nil {
err = fmt.Errorf("Invalid pin number %v\n", pin)
return
}

data := to7Bit(pinData)
cmd := []byte{byte(AnalogMessage) | byte(pin), data[0], data[1]}
err = c.sendCommand(cmd)
return
if pin < 0 || pin > uint(len(c.pinModes)) && c.pinModes[pin][Analog] != nil {
err = fmt.Errorf("Invalid pin number %v\n", pin)
return
}

data := to7Bit(pinData)
cmd := []byte{byte(AnalogMessage) | byte(pin), data[0], data[1]}
err = c.sendCommand(cmd)
return
}

func (c *FirmataClient) sendCommand(cmd []byte) (err error) {
bStr := ""
for _, b := range cmd {
bStr = bStr + fmt.Sprintf(" %#2x", b)
}
c.Log.Trace("Command send%v\n", bStr)

_, err = (*c.conn).Write(cmd)
return
bStr := ""
for _, b := range cmd {
bStr = bStr + fmt.Sprintf(" %#2x", b)
}
c.Debug.Printf("Command send%v\n", bStr)

_, err = (*c.conn).Write(cmd)
return
}

// Sets the polling interval in milliseconds for analog pin samples
func (c *FirmataClient) SetAnalogSamplingInterval(ms byte) (err error) {
data := to7Bit(ms)
err = c.sendSysEx(SamplingInterval, data[0], data[1])
return
data := to7Bit(ms)
err = c.sendSysEx(SamplingInterval, data[0], data[1])
return
}

// Get the channel to retrieve analog and digital pin values
func (c *FirmataClient) GetValues() <-chan FirmataValue {
return c.valueChan
return c.valueChan
}
Loading