pull/20/merge
Steven Imle 1 month ago committed by GitHub
commit 80131bcf20
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -20,6 +20,9 @@ import (
"periph.io/x/conn/v3/spi" "periph.io/x/conn/v3/spi"
) )
// I2CAddr i2c default address.
const I2CAddr uint16 = 0x77
// Oversampling affects how much time is taken to measure each of temperature, // Oversampling affects how much time is taken to measure each of temperature,
// pressure and humidity. // pressure and humidity.
// //

@ -0,0 +1,43 @@
package firmata
import (
"bytes"
"fmt"
)
type AnalogMappingResponse struct {
AnalogPinToDigital []uint8
DigitalPinToAnalog map[uint8]uint8
}
func ParseAnalogMappingResponse(data []byte) AnalogMappingResponse {
var response = AnalogMappingResponse{
AnalogPinToDigital: []uint8{},
DigitalPinToAnalog: map[uint8]uint8{},
}
for i := 0; i < len(data); i++ {
if data[i] != CapabilityResponsePinDelimiter {
response.DigitalPinToAnalog[uint8(i)] = uint8(len(response.AnalogPinToDigital))
response.AnalogPinToDigital = append(response.AnalogPinToDigital, uint8(i))
}
}
return response
}
func (a AnalogMappingResponse) String() string {
str := bytes.Buffer{}
for analogPin, digitalPin := range a.AnalogPinToDigital {
_, _ = fmt.Fprintf(&str, "A%d: %d\n", analogPin, digitalPin)
}
return str.String()
}
type ExtendedAnalogMappingResponse struct {
Pin uint8
}
func (a ExtendedAnalogMappingResponse) String() string {
return fmt.Sprintf("%d", a.Pin)
}

@ -0,0 +1,75 @@
package firmata
import (
"bytes"
"fmt"
"periph.io/x/conn/v3/pin"
)
var pinModeOrder = []pin.Func{
PinFuncDigitalInput,
PinFuncDigitalOutput,
PinFuncAnalogInput,
PinFuncPWM,
PinFuncServo,
PinFuncShift,
PinFuncI2C,
PinFuncOneWire,
PinFuncStepper,
PinFuncEncoder,
PinFuncSerial,
PinFuncInputPullUp,
PinFuncSPI,
PinFuncSonar,
PinFuncTone,
PinFuncDHT,
}
const CapabilityResponsePinDelimiter = 0x7F
type CapabilityResponse struct {
PinToModeToResolution []map[pin.Func]uint8
SupportedPinModes [][]pin.Func
}
func ParseCapabilityResponse(data []byte) CapabilityResponse {
var response = CapabilityResponse{
PinToModeToResolution: []map[pin.Func]uint8{{}},
SupportedPinModes: [][]pin.Func{{}},
}
var pindex = 0
for i := 0; i < len(data); {
if data[i] == CapabilityResponsePinDelimiter {
response.PinToModeToResolution = append(response.PinToModeToResolution, map[pin.Func]uint8{})
response.SupportedPinModes = append(response.SupportedPinModes, []pin.Func{})
i += 1
pindex++
} else {
pinFunc := pinModeToFuncMap[data[i]]
response.PinToModeToResolution[pindex][pinFunc] = data[i+1]
response.SupportedPinModes[pindex] = append(response.SupportedPinModes[pindex], pinFunc)
i += 2
}
}
return response
}
func (c CapabilityResponse) String() string {
str := bytes.Buffer{}
for p, modeMap := range c.PinToModeToResolution {
_, _ = fmt.Fprintf(&str, "pin %2v: [", p)
if len(modeMap) > 0 {
for _, mode := range pinModeOrder {
if resolution, ok := modeMap[mode]; ok {
_, _ = fmt.Fprintf(&str, "%s: %d, ", mode, resolution)
}
}
str.Truncate(str.Len() - 2)
}
_, _ = fmt.Fprintf(&str, "]\n")
}
return str.String()
}

@ -0,0 +1,679 @@
package firmata
import (
"bufio"
"errors"
"fmt"
"io"
"sync"
"periph.io/x/conn/v3/gpio"
"periph.io/x/conn/v3/i2c"
"periph.io/x/conn/v3/onewire"
"periph.io/x/conn/v3/pin"
)
// These max values are for data bytes as, within firmata, data is 7 bits long.
const (
MaxUInt8 uint8 = (1<<8 - 1) >> (8 / 8)
MaxUInt16 uint16 = (1<<16 - 1) >> (16 / 8)
MaxUInt24 uint32 = (1<<24 - 1) >> (24 / 8)
MaxUInt32 uint32 = (1<<32 - 1) >> (32 / 8)
)
var commandResponseMap = map[SysExCmd]SysExCmd{
SysExAnalogMappingQuery: SysExAnalogMappingResponse,
SysExCapabilityQuery: SysExCapabilityResponse,
SysExPinStateQuery: SysExPinStateResponse,
}
type ClientI interface {
SendSysEx(SysExCmd, ...byte) (chan []byte, error)
SendReset() error
ExtendedReportAnalogPin(uint8, uint8) error
CapabilityQuery() (chan CapabilityResponse, error)
PinStateQuery(uint8) (chan PinStateResponse, error)
ReportFirmware() (chan FirmwareReport, error)
SetPinMode(uint8, pin.Func) error
SetAnalogPinReporting(uint8, bool) error
SetDigitalPinReporting(uint8, bool) error
SetDigitalPortReporting(uint8, bool) error
SetSamplingInterval(uint16) error
SetDigitalPinValue(p uint8, value gpio.Level) error
SendAnalogMappingQuery() (chan AnalogMappingResponse, error)
AnalogPinToDigitalPin(p uint8) (uint8, error)
SetAnalogIOMessageListener(p uint8, ch chan uint16) (release func(), err error)
SetDigitalIOMessageListener(p uint8, ch chan gpio.Level) (release func(), err error)
SendAnalogIOMessage(uint8, uint16) error
OpenI2CBus() (i2c.Bus, error)
SetI2CAddressListener(addr uint8, ch chan I2CPacket) (release func(), err error)
WriteI2CData(address uint8, restart bool, data []uint8) error
ReadI2CData(address uint8, restart bool, len uint16) error
ReadI2CRegister(address uint8, restart bool, register uint8, len uint16) error
SendI2CConfig(delayMicroseconds uint8) error
OpenOneWireBus(p uint8) (bus onewire.BusCloser, err error)
SetOneWireListener(uint8, chan []byte) (release func(), err error)
GetPinName(uint8) string
GetPinFunctions(uint8) []pin.Func
Close() error
}
type Client struct {
board io.ReadWriteCloser
responseChannels map[SysExCmd][]chan []byte
sysExListenerChannels map[SysExCmd]chan []byte
i2cListeners map[uint8]chan I2CPacket
i2cMU sync.Mutex
onewireListeners map[uint8]chan []byte
onewireMU sync.Mutex
digitalIOMessageChannels map[uint8]chan gpio.Level
digitalPinMU sync.Mutex
analogIOMessageChannels map[uint8]chan uint16
analogPinMU sync.Mutex
mu sync.Mutex
started bool
i2cStarted bool
// We want to report these to the requester, but also save them for internal use.
cr CapabilityResponse
amr AnalogMappingResponse
}
func NewClient(board io.ReadWriteCloser) *Client {
return &Client{
board: board,
responseChannels: map[SysExCmd][]chan []byte{},
sysExListenerChannels: map[SysExCmd]chan []byte{},
i2cListeners: map[uint8]chan I2CPacket{},
onewireListeners: map[uint8]chan []byte{},
digitalIOMessageChannels: map[uint8]chan gpio.Level{},
analogIOMessageChannels: map[uint8]chan uint16{},
}
}
func (c *Client) Close() error {
c.mu.Lock()
defer c.mu.Unlock()
c.started = false
return c.board.Close()
}
type flusher interface {
Flush()
}
type flusherErr interface {
Flush() error
}
func (c *Client) Start() error {
c.mu.Lock()
if c.started {
c.mu.Unlock()
return ErrAlreadyStarted
}
c.started = true
c.mu.Unlock()
if err := c.SendReset(); err != nil {
return err
}
if b, ok := c.board.(flusher); ok {
b.Flush()
} else if b, ok := c.board.(flusherErr); ok {
if err := b.Flush(); err != nil {
return err
}
}
// Don't call ReportFirmware as it is automatic, but we want to register a listener for it.
firmChannel := make(chan []byte, 1)
c.mu.Lock()
c.responseChannels[SysExReportFirmware] = []chan []byte{firmChannel}
c.mu.Unlock()
responseChannel := make(chan FirmwareReport, 1)
go func() {
responseChannel <- ParseFirmwareReport(<-firmChannel)
}()
go func() {
err := c.responseWatcher()
if err != nil {
panic(err)
}
}()
// Do not move on until we receive our firmware report. This is a signal the device is ready.
// Anything sent before we receive this message will not be read by the device.
fmt.Println("Firmware Info:", <-responseChannel)
analogMappingQuery, err := c.SendAnalogMappingQuery()
if err != nil {
return err
}
capabilityQuery, err := c.CapabilityQuery()
if err != nil {
return err
}
fmt.Println(<-analogMappingQuery)
fmt.Println(<-capabilityQuery)
return nil
}
func (c *Client) write(payload []byte, withinMutex func()) error {
// Cannot allow multiple writes at the same time.
c.mu.Lock()
defer c.mu.Unlock()
// Write to the board.
_, err := c.board.Write(payload)
if err != nil {
return err
}
if withinMutex != nil {
withinMutex()
}
return nil
}
func (c *Client) responseWatcher() (err error) {
defer func() {
if errors.Is(err, io.EOF) {
err = ErrDeviceDisconnected
}
}()
reader := bufio.NewReader(c.board)
for {
var data []byte
b0, err := reader.ReadByte()
if err != nil {
return err
}
mt := MessageType(b0)
switch {
case mt == ProtocolVersion:
var version [2]byte
_, err := reader.Read(version[:])
if err != nil {
return err
}
fmt.Printf("Protocol Version: 0x%0.2X 0x%0.2X\n", version[0], version[1])
case AnalogIOMessage <= mt && mt <= (AnalogIOMessage+0xF):
v1, err := reader.ReadByte()
if err != nil {
return err
}
v2, err := reader.ReadByte()
if err != nil {
return err
}
c.analogIOMessageChannels[b0&0xF] <- TwoByteToWord(v1, v2)
case DigitalIOMessage <= mt && mt <= (DigitalIOMessage+0xF):
v1, err := reader.ReadByte()
if err != nil {
return err
}
v2, err := reader.ReadByte()
if err != nil {
return err
}
values := TwoByteToByte(v1, v2)
port := b0 & 0xF
pinMin := port * 8
pinMax := (port+1)*8 - 1
for p := pinMin; p <= pinMax; p++ {
if ch, ok := c.digitalIOMessageChannels[p]; ok {
lvl := gpio.Low
if values>>p%8 > 0 {
lvl = gpio.High
}
ch <- lvl
}
}
case mt == StartSysEx:
data, err = reader.ReadBytes(byte(EndSysEx))
if err != nil {
return err
}
if len(data) == 0 {
return ErrNoDataRead
}
cmd := SysExCmd(data[0])
data = data[1 : len(data)-1]
switch {
case cmd == SysExSerialDataV1:
fallthrough
case cmd == SysExSerialDataV2:
return fmt.Errorf("%w: %s", ErrUnsupportedFeature, cmd)
case cmd == SysExOneWireData:
p := data[1]
if l, ok := c.onewireListeners[p]; ok {
l <- data
} else {
return fmt.Errorf("%w: onewire cmd:0x%02X pin 0x%02X", ErrUnhandledMessage, data[0], p)
}
case cmd == SysExI2CReply:
address := TwoByteToByte(data[0], data[1])
register := TwoByteToByte(data[2], data[3])
ch, ok := c.i2cListeners[address]
if !ok {
return fmt.Errorf("%w: 0x%02X", ErrNoI2CListenerForAddress, address)
}
ch <- I2CPacket{
Register: register,
Data: TwoByteRepresentationToByteSlice(data[4:]),
}
case c.sysExListenerChannels[cmd] != nil:
c.sysExListenerChannels[cmd] <- data
case len(c.responseChannels[cmd]) != 0:
c.mu.Lock()
resp := c.responseChannels[cmd][0]
c.responseChannels[cmd] = c.responseChannels[cmd]
c.mu.Unlock()
resp <- data
close(resp)
case cmd == SysExStringData:
// TODO: The Firmata spec defines this as mostly an error statement.
// Should we fail on receiving a string message?
fmt.Printf("device: [%s]\n", TwoByteString(data))
default:
str := ""
if cmd == SysExStringData {
str = TwoByteString(data)
} else {
for _, b := range data {
str += fmt.Sprintf("%d", b)
}
}
return fmt.Errorf("%w: 0x%0.2X: %s", ErrUnexpectedSysExMessageTypeReceived, byte(cmd), str)
}
default:
return fmt.Errorf("%w: 0x%0.2X", ErrInvalidMessageTypeStart, b0)
}
}
}
func (c *Client) SendReset() error {
return c.write([]byte{byte(SystemReset)}, nil)
}
func (c *Client) AnalogPinToDigitalPin(p uint8) (uint8, error) {
if int(p) > len(c.amr.AnalogPinToDigital) {
return 0, ErrInvalidAnalogPin
}
return c.amr.AnalogPinToDigital[p], nil
}
func (c *Client) SetPinMode(p uint8, mode pin.Func) error {
return c.write([]uint8{uint8(SetPinMode), p, pinFuncToModeMap[mode]}, nil)
}
func (c *Client) SetDigitalPinValue(p uint8, value gpio.Level) error {
v := byte(0)
if value {
v = 1
}
return c.write([]uint8{uint8(SetDigitalPinValue), p, v}, nil)
}
func (c *Client) SendSysEx(cmd SysExCmd, payload ...byte) (chan []byte, error) {
// Create a response channel.
var data chan []byte
err := c.write(append([]byte{byte(StartSysEx), byte(cmd)}, append(payload, byte(EndSysEx))...), func() {
// This assumes that SysEx commands of the same type are responded to in order.
if resp, ok := commandResponseMap[cmd]; ok {
data = make(chan []byte, 1)
c.responseChannels[resp] = append(c.responseChannels[resp], data)
}
})
if err != nil {
return nil, err
}
return data, nil
}
func (c *Client) CapabilityQuery() (chan CapabilityResponse, error) {
future, err := c.SendSysEx(SysExCapabilityQuery)
if err != nil {
return nil, err
}
resp := make(chan CapabilityResponse, 1)
go func() {
data := <-future
var response = ParseCapabilityResponse(data)
c.cr = response
resp <- response
close(resp)
}()
return resp, nil
}
func (c *Client) SendAnalogMappingQuery() (chan AnalogMappingResponse, error) {
future, err := c.SendSysEx(SysExAnalogMappingQuery)
if err != nil {
return nil, err
}
resp := make(chan AnalogMappingResponse, 1)
go func() {
data := <-future
var response = ParseAnalogMappingResponse(data)
c.amr = response
resp <- response
close(resp)
}()
return resp, nil
}
func (c *Client) PinStateQuery(p uint8) (chan PinStateResponse, error) {
future, err := c.SendSysEx(SysExPinStateQuery, p)
if err != nil {
return nil, err
}
resp := make(chan PinStateResponse, 1)
go func() {
data := <-future
var response = ParsePinStateResponse(data)
resp <- response
close(resp)
}()
return resp, nil
}
func (c *Client) ReportFirmware() (chan FirmwareReport, error) {
future, err := c.SendSysEx(SysExReportFirmware)
if err != nil {
return nil, err
}
resp := make(chan FirmwareReport, 1)
go func() {
data := <-future
var response = ParseFirmwareReport(data)
resp <- response
close(resp)
}()
return resp, nil
}
func (c *Client) ExtendedReportAnalogPin(p uint8, value uint8) error {
lsb, msb := ByteToTwoByte(value)
_, err := c.SendSysEx(SysExExtendedAnalog, p, lsb, msb)
if err != nil {
return err
}
return nil
}
func (c *Client) SetAnalogPinReporting(analogPin uint8, report bool) error {
v := byte(0)
if report {
v = 1
}
return c.write([]byte{byte(ReportAnalogPin) | (analogPin & 0xF), v}, nil)
}
func (c *Client) SetDigitalPinReporting(p uint8, report bool) error {
return c.SetDigitalPortReporting(p%8, report)
}
func (c *Client) SetDigitalPortReporting(port uint8, report bool) error {
v := byte(0)
if report {
v = 1
}
return c.write([]byte{byte(ReportDigitalPort) | (port & 0xF), v}, nil)
}
func (c *Client) SetSamplingInterval(ms uint16) error {
if ms > MaxUInt16 {
return fmt.Errorf("%w: 0x0 - 0x%X", ErrValueOutOfRange, MaxUInt16)
}
return c.write([]byte{byte(SysExSamplingInterval), byte(ms), byte(ms >> 7)}, nil)
}
// WriteI2CData only supports 7-bit I2C addresses
func (c *Client) WriteI2CData(address uint8, restart bool, data []uint8) error {
if !c.i2cStarted {
return ErrI2CNotEnabled
}
byte2 := byte(I2CModeWrite)
if restart {
byte2 &= I2CRestartTransmission
}
payload := append([]byte{address, byte2}, ByteSliceToTwoByteRepresentation(data)...)
_, err := c.SendSysEx(SysExI2CRequest, payload...)
return err
}
// ReadI2CData only supports 7-bit I2C addresses
func (c *Client) ReadI2CData(address uint8, restart bool, length uint16) error {
if !c.i2cStarted {
return ErrI2CNotEnabled
}
if length > MaxUInt16 {
return fmt.Errorf("%w: 0x0 - 0x%X", ErrValueOutOfRange, MaxUInt16)
}
byte2 := byte(I2CModeRead)
if restart {
byte2 &= I2CRestartTransmission
}
lLSB, lMSB := WordToTwoByte(length)
_, err := c.SendSysEx(SysExI2CRequest, address, byte2, lLSB, lMSB)
return err
}
// ReadI2CRegister only supports 7-bit I2C addresses
func (c *Client) ReadI2CRegister(address uint8, restart bool, register uint8, length uint16) error {
if !c.i2cStarted {
return ErrI2CNotEnabled
}
if length > MaxUInt16 {
return fmt.Errorf("%w: 0x0 - 0x%X", ErrValueOutOfRange, MaxUInt16)
}
byte2 := byte(I2CModeRead)
if restart {
byte2 &= I2CRestartTransmission
}
rLSB, rMSB := ByteToTwoByte(register)
lLSB, lMSB := WordToTwoByte(length)
_, err := c.SendSysEx(SysExI2CRequest, address, byte2, rLSB, rMSB, lLSB, lMSB)
return err
}
func (c *Client) SendI2CConfig(delayMicroseconds uint8) error {
micLSB, micMSB := ByteToTwoByte(delayMicroseconds)
_, err := c.SendSysEx(SysExI2CConfig, micLSB, micMSB)
if err != nil {
return err
}
c.i2cStarted = true
return nil
}
func (c *Client) releaseI2CAddressListener(addr uint8) {
c.i2cMU.Lock()
defer c.i2cMU.Unlock()
delete(c.i2cListeners, addr)
}
// SetI2CAddressListener only supports 7-bit I2C addresses
func (c *Client) SetI2CAddressListener(addr uint8, ch chan I2CPacket) (release func(), err error) {
c.i2cMU.Lock()
defer c.i2cMU.Unlock()
if c.i2cListeners[addr] != nil {
return nil, ErrI2CAddressListenerNotReleased
}
c.i2cListeners[addr] = ch
return func() { c.releaseI2CAddressListener(addr) }, nil
}
func (c *Client) releaseAnalogIOMessageListener(p uint8) {
c.analogPinMU.Lock()
defer c.analogPinMU.Unlock()
delete(c.analogIOMessageChannels, p)
}
func (c *Client) SetAnalogIOMessageListener(p uint8, ch chan uint16) (release func(), err error) {
c.analogPinMU.Lock()
defer c.analogPinMU.Unlock()
if c.analogIOMessageChannels[p] != nil {
return nil, ErrPinListenerNotReleased
}
c.analogIOMessageChannels[p] = ch
return func() { c.releaseAnalogIOMessageListener(p) }, nil
}
func (c *Client) releaseDigitalIOMessageListener(p uint8) {
c.digitalPinMU.Lock()
defer c.digitalPinMU.Unlock()
delete(c.digitalIOMessageChannels, p)
}
func (c *Client) SetDigitalIOMessageListener(p uint8, ch chan gpio.Level) (release func(), err error) {
c.digitalPinMU.Lock()
defer c.digitalPinMU.Unlock()
if c.digitalIOMessageChannels[p] != nil {
return nil, ErrPinListenerNotReleased
}
c.digitalIOMessageChannels[p] = ch
return func() { c.releaseDigitalIOMessageListener(p) }, nil
}
func (c *Client) releaseOneWireListener(p uint8) {
c.onewireMU.Lock()
defer c.onewireMU.Unlock()
delete(c.onewireListeners, p)
}
func (c *Client) SetOneWireListener(p uint8, ch chan []byte) (release func(), err error) {
c.onewireMU.Lock()
defer c.onewireMU.Unlock()
if c.onewireListeners[p] != nil {
return nil, ErrPinListenerNotReleased
}
c.onewireListeners[p] = ch
return func() { c.releaseOneWireListener(p) }, nil
}
func (c *Client) OpenOneWireBus(p uint8) (bus onewire.BusCloser, err error) {
c.onewireMU.Lock()
defer c.onewireMU.Unlock()
if c.onewireListeners[p] != nil {
return nil, ErrPinListenerNotReleased
}
// Need to run configure or firmata will not initialize.
if _, err := c.SendSysEx(SysExOneWireData, byte(OneWireInstructionConfigure), p, 0x00); err != nil {
return nil, err
}
return newOneWireBus(c, newPin(c, p), func() error {
c.releaseOneWireListener(p)
return nil
}), nil
}
func (c *Client) SendAnalogIOMessage(p uint8, value uint16) error {
if p > 0xF {
return ErrAnalogIOMessagePinOutOfRange
}
lsb, msb := WordToTwoByte(value)
return c.write([]byte{byte(AnalogIOMessage) | p, lsb, msb}, nil)
}
func (c *Client) GetPinName(p uint8) string {
if v, ok := c.amr.DigitalPinToAnalog[p]; ok {
return fmt.Sprintf("A%d", v)
}
return fmt.Sprintf("%d", p)
}
func (c *Client) GetPinFunctions(p uint8) []pin.Func {
return c.cr.SupportedPinModes[int(p)]
}
func (c *Client) OpenI2CBus() (i2c.Bus, error) {
return newI2CBus(c)
}

@ -0,0 +1,25 @@
package firmata
import (
"errors"
)
var (
ErrDeviceDisconnected = errors.New("device disconnected")
ErrUnsupportedFeature = errors.New("unsupported feature")
ErrUnhandledMessage = errors.New("message did not have an active listener")
ErrInvalidMessageTypeStart = errors.New("invalid message type start")
ErrNoDataRead = errors.New("no data read")
ErrUnexpectedSysExMessageTypeReceived = errors.New("unexpected sysex message type")
ErrAlreadyStarted = errors.New("client already started")
ErrValueOutOfRange = errors.New("value is out of range")
ErrNoI2CListenerForAddress = errors.New("no i2c listener registered for address")
ErrInvalidFirmataI2CBus = errors.New("firmata does not support multiple i2c buses")
ErrInvalidAnalogPin = errors.New("analog pin is outside of range")
ErrI2CNotEnabled = errors.New("i2c must started to use")
ErrInvalidOneWirePin = errors.New("onewire pin number cannot exceed 0x7F")
ErrAnalogIOMessagePinOutOfRange = errors.New("analog io message pin number cannot exceed 0xF")
ErrDigitalIOMessagePinOutOfRange = errors.New("digital io message port number cannot exceed 0xF")
ErrPinListenerNotReleased = errors.New("pin listener is already set for pin")
ErrI2CAddressListenerNotReleased = errors.New("address listener is already set")
)

@ -0,0 +1,7 @@
package firmata
type Feature struct {
ID uint8
VersionMajor uint8
VersionMinor uint8
}

@ -0,0 +1,23 @@
package firmata
import (
"fmt"
)
type FirmwareReport struct {
Major byte
Minor byte
Name []byte
}
func ParseFirmwareReport(data []byte) FirmwareReport {
return FirmwareReport{
Major: data[0],
Minor: data[1],
Name: data[2:],
}
}
func (r FirmwareReport) String() string {
return fmt.Sprintf("%s [%d.%d]", TwoByteString(r.Name), r.Major, r.Minor)
}

@ -0,0 +1,86 @@
package firmata
import (
"errors"
"fmt"
"math"
"sync"
"periph.io/x/conn/v3/physic"
)
var (
Err10BitAddressingNotSupported = errors.New("10-bit addressing not supported")
)
type I2CBus struct {
c ClientI
mu sync.Mutex
}
func newI2CBus(c ClientI) (*I2CBus, error) {
b := &I2CBus{
c: c,
}
err := c.SendI2CConfig(0)
if err != nil {
return nil, err
}
return b, nil
}
func (b *I2CBus) Close() error {
panic("implement me")
}
func (b *I2CBus) String() string {
panic("implement me")
}
func (b *I2CBus) Tx(addr uint16, w, r []byte) error {
if addr >= 0b11111111 {
return fmt.Errorf("%w: 0x%04X", Err10BitAddressingNotSupported, addr)
}
if len(r) > math.MaxUint16 {
return fmt.Errorf("%w: cannot reach more than %d bytes", ErrValueOutOfRange, math.MaxUint16)
}
b.mu.Lock()
defer b.mu.Unlock()
ch := make(chan I2CPacket)
defer close(ch)
address := uint8(addr)
release, err := b.c.SetI2CAddressListener(address, ch)
if err != nil {
return err
}
defer release()
if len(w) > 0 {
if err := b.c.WriteI2CData(address, false, w); err != nil {
return err
}
}
if len(r) > 0 {
if err := b.c.ReadI2CData(address, false, uint16(len(r))); err != nil {
return err
}
pck := <-ch
copy(r, pck.Data)
}
return nil
}
func (b *I2CBus) SetSpeed(f physic.Frequency) error {
return fmt.Errorf("%w: firmata does not support setting bus frequency", ErrUnsupportedFeature)
}

@ -0,0 +1,20 @@
package firmata
const (
I2CRestartTransmission uint8 = 0b01000000
I2CModeMask uint8 = 0b00011000
)
type I2CMode uint8
const (
I2CModeWrite I2CMode = 0b00000000
I2CModeRead I2CMode = 0b00001000
I2CModeReadContinuously I2CMode = 0b00010000
I2CModeStopReading I2CMode = 0b00011000
)
type I2CPacket struct {
Register uint8
Data []byte
}

@ -0,0 +1,50 @@
package firmata
type (
MessageType uint8
)
const (
AnalogIOMessage MessageType = 0xE0 // pin # LSB(bits 0-6) MSB(bits 7-13)
DigitalIOMessage MessageType = 0x90 // port LSB(bits 0-6) MSB(bits 7-13)
ReportAnalogPin MessageType = 0xC0 // pin # disable/enable(0/1) - n/a -
ReportDigitalPort MessageType = 0xD0 // port disable/enable(0/1) - n/a -
StartSysEx MessageType = 0xF0 //
SetPinMode MessageType = 0xF4 // pin # (0-127) pin mode
SetDigitalPinValue MessageType = 0xF5 // pin # (0-127) pin value(0/1)
EndSysEx MessageType = 0xF7 //
ProtocolVersion MessageType = 0xF9 // major version minor version
SystemReset MessageType = 0xFF //
)
var messageTypeToStringMap = map[MessageType]string{
AnalogIOMessage: "AnalogIOMessage",
DigitalIOMessage: "DigitalIOMessage",
ReportAnalogPin: "ReportAnalogPin",
ReportDigitalPort: "ReportDigitalPort",
StartSysEx: "StartSysEx",
SetPinMode: "SetPinMode",
SetDigitalPinValue: "SetDigitalPinValue",
EndSysEx: "EndSysEx",
ProtocolVersion: "ProtocolVersion",
SystemReset: "SystemReset",
}
func (m MessageType) String() string {
switch {
case AnalogIOMessage <= m && m <= (AnalogIOMessage+0xF):
return messageTypeToStringMap[AnalogIOMessage]
case DigitalIOMessage <= m && m <= (DigitalIOMessage+0xF):
return messageTypeToStringMap[DigitalIOMessage]
case ReportAnalogPin <= m && m <= (ReportAnalogPin+0xF):
return messageTypeToStringMap[ReportAnalogPin]
case ReportDigitalPort <= m && m <= (ReportDigitalPort+0xF):
return messageTypeToStringMap[ReportDigitalPort]
}
if v, ok := messageTypeToStringMap[m]; ok {
return v
}
return "Unknown"
}

@ -0,0 +1,181 @@
package firmata
import (
"encoding/binary"
"fmt"
"math"
"sync"
"periph.io/x/conn/v3/gpio"
"periph.io/x/conn/v3/onewire"
)
type OneWireBus struct {
c ClientI
q *Pin
mu sync.Mutex
closer func() error
cid uint16
}
func newOneWireBus(c ClientI, q *Pin, closer func() error) *OneWireBus {
return &OneWireBus{
c: c,
q: q,
closer: closer,
}
}
func (b *OneWireBus) Close() error {
return b.closer()
}
func (b *OneWireBus) String() string {
panic("implement me")
}
func (b *OneWireBus) Search(alarmOnly bool) ([]onewire.Address, error) {
b.mu.Lock()
defer b.mu.Unlock()
ch := make(chan []byte)
defer close(ch)
release, err := b.c.SetOneWireListener(b.q.pin, ch)
if err != nil {
return nil, err
}
defer release()
if alarmOnly {
if _, err := b.c.SendSysEx(SysExOneWireData, byte(OneWireInstructionSearchAlarmed), b.q.pin); err != nil {
return nil, err
}
} else {
if _, err := b.c.SendSysEx(SysExOneWireData, byte(OneWireInstructionSearch), b.q.pin); err != nil {
return nil, err
}
}
data := <-ch
ins := OneWireInstruction(data[0])
pin := data[1]
if ins != OneWireInstructionSearchAlarmedReply && ins != OneWireInstructionSearchReply {
return nil, fmt.Errorf("%w: did not receive search reply", ErrUnhandledMessage)
}
if pin != b.q.pin {
return nil, fmt.Errorf("%w: received message from wrong bus", ErrInvalidOneWirePin)
}
data = Decoder7Bit(data[2:])
addresses := make([]onewire.Address, len(data)/8)
for i := range addresses {
addresses[i] = onewire.Address(binary.LittleEndian.Uint64(data[i*8:]))
}
return addresses, nil
}
func (b *OneWireBus) Tx(w, r []byte, power onewire.Pullup) error {
if len(w) == 0 && len(r) == 0 {
return nil
}
if len(r) > math.MaxUint16 {
return fmt.Errorf("%w: cannot reach more than %d bytes", ErrValueOutOfRange, math.MaxUint16)
}
b.mu.Lock()
defer b.mu.Unlock()
ch := make(chan []byte)
defer close(ch)
release, err := b.c.SetOneWireListener(b.q.pin, ch)
if err != nil {
return err
}
defer release()
var powerVal byte = 0x00
if power {
powerVal = 0x01
}
if _, err := b.c.SendSysEx(SysExOneWireData, byte(OneWireInstructionConfigure), b.q.pin, powerVal); err != nil {
return err
}
cmd := OneWireCommandReset
if len(w) > 0 {
switch w[0] {
case 0xF0: // search rom
if _, err := b.c.SendSysEx(SysExOneWireData, byte(OneWireInstructionSearch), b.q.pin); err != nil {
return err
}
case 0xEC: // search rom (alarmed)
if _, err := b.c.SendSysEx(SysExOneWireData, byte(OneWireInstructionSearchAlarmed), b.q.pin); err != nil {
return err
}
case 0xCC: // skip rom
panic("not implemented")
case 0x33: // read rom
panic("not implemented")
case 0x55: // match rom
cmd |= OneWireCommandSelect
payload := w[1:9]
if len(w) > 9 { // command length (1) + address length (8)
cmd |= OneWireCommandWrite
}
if len(r) > 0 {
cmd |= OneWireCommandRead
// Write the amount of bytes to read.
payload = append(payload, byte(len(r)&0xFF), byte((len(r)>>8)&0xFF))
// Write the correlation id for sanity checking.
payload = append(payload, byte(b.cid&0xFF), byte((b.cid>>8)&0xFF))
b.cid++
}
cmd |= OneWireCommandDelay
payload = append(payload, 10, 0, 0, 0)
payload = append(payload, w[9:]...)
payload = append([]byte{byte(cmd), b.q.pin}, Encoder7Bit(payload)...)
if _, err := b.c.SendSysEx(SysExOneWireData, payload...); err != nil {
return err
}
default:
return fmt.Errorf("%w: no onewire command matching 0x%02X", ErrUnsupportedFeature, w[0])
}
}
if len(r) > 0 {
data := <-ch
ins := OneWireInstruction(data[0])
pin := data[1]
if ins != OneWireInstructionReadReply {
return fmt.Errorf("%w: did not receive read reply", ErrUnhandledMessage)
}
if pin != b.q.pin {
return fmt.Errorf("%w: received message from wrong bus", ErrInvalidOneWirePin)
}
data = Decoder7Bit(data[2:])
// Drop the correlation id when copying.
copy(r, data[2:])
}
return nil
}
func (b *OneWireBus) Q() gpio.PinIO {
return b.q
}

@ -0,0 +1,27 @@
package firmata
type OneWireInstruction uint8
const (
OneWireInstructionSearch OneWireInstruction = 0x40
OneWireInstructionConfigure OneWireInstruction = 0x41
OneWireInstructionSearchReply OneWireInstruction = 0x42
OneWireInstructionReadReply OneWireInstruction = 0x43
OneWireInstructionSearchAlarmed OneWireInstruction = 0x44
OneWireInstructionSearchAlarmedReply OneWireInstruction = 0x45
)
type OneWireConnectorWrapper struct {
client ClientI
}
type OneWireCommand uint8
const (
OneWireCommandReset OneWireCommand = 0b00000001
OneWireCommandSkip OneWireCommand = 0b00000010
OneWireCommandSelect OneWireCommand = 0b00000100
OneWireCommandRead OneWireCommand = 0b00001000
OneWireCommandDelay OneWireCommand = 0b00010000
OneWireCommandWrite OneWireCommand = 0b00100000
)

@ -0,0 +1,237 @@
package firmata
import (
"context"
"errors"
"sync"
"time"
"periph.io/x/conn/v3/gpio"
"periph.io/x/conn/v3/physic"
"periph.io/x/conn/v3/pin"
)
var (
ErrUnsupportedGPIOPull = errors.New("firmata: PullDown is not supported")
ErrNoMatchingGPIOPull = errors.New("firmata: pin was previously in a non-input mode")
)
type Pin struct {
c ClientI
pin uint8
edge gpio.Edge
ch chan gpio.Level
release func()
done chan struct{}
valueLast gpio.Level
valueNew gpio.Level
edgeChange chan gpio.Edge
mu sync.Mutex
}
func newPin(c ClientI, num uint8) *Pin {
p := &Pin{
c: c,
pin: num,
ch: make(chan gpio.Level),
}
go p.run()
return p
}
func (p *Pin) run() {
p.done = make(chan struct{})
for {
select {
case <-p.done:
close(p.ch)
return
case v := <-p.ch:
p.valueLast = p.valueNew
p.valueNew = v
func() {
p.mu.Lock()
defer p.mu.Unlock()
if p.edgeChange == nil {
return
}
if p.valueLast == true || p.valueNew == false {
p.edgeChange <- gpio.FallingEdge
}
if p.valueLast == false || p.valueNew == true {
p.edgeChange <- gpio.RisingEdge
}
}()
}
}
}
func (p *Pin) In(pull gpio.Pull, edge gpio.Edge) error {
var mode = PinFuncDigitalInput
switch pull {
case gpio.PullDown:
return ErrUnsupportedGPIOPull
case gpio.PullNoChange:
ch, err := p.c.PinStateQuery(p.pin)
if err != nil {
return err
}
s := <-ch
mode = s.Mode
switch mode {
case PinFuncInputPullUp:
case PinFuncDigitalInput:
default:
return ErrNoMatchingGPIOPull
}
case gpio.PullUp:
mode = PinFuncInputPullUp
case gpio.Float:
mode = PinFuncDigitalInput
}
if err := p.c.SetPinMode(p.pin, mode); err != nil {
return err
}
var err error
if p.release, err = p.c.SetDigitalIOMessageListener(p.pin, p.ch); err != nil {
return err
}
if err = p.c.SetDigitalPinReporting(p.pin, true); err != nil {
return err
}
p.edge = edge
return nil
}
func (p *Pin) Read() gpio.Level {
return p.valueNew
}
func (p *Pin) WaitForEdge(timeout time.Duration) bool {
defer func() {
p.mu.Lock()
defer p.mu.Unlock()
close(p.edgeChange)
p.edgeChange = nil
}()
func() {
p.mu.Lock()
defer p.mu.Unlock()
p.edgeChange = make(chan gpio.Edge)
}()
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
for {
select {
case change := <-p.edgeChange:
if p.edge == gpio.BothEdges || change == p.edge {
return true
}
case <-ctx.Done():
return false
}
}
}
func (p *Pin) Pull() gpio.Pull {
ch, err := p.c.PinStateQuery(p.pin)
if err != nil {
return gpio.PullNoChange
}
s := <-ch
switch s.Mode {
case PinFuncInputPullUp:
return gpio.PullUp
case PinFuncDigitalInput:
return gpio.Float
}
return gpio.PullNoChange
}
func (p *Pin) DefaultPull() gpio.Pull {
return gpio.PullNoChange
}
func (p *Pin) Out(l gpio.Level) error {
// No need to set mode as firmata does so automatically
return p.c.SetDigitalPinValue(p.pin, l)
}
const dutyMax gpio.Duty = 1<<8 - 1
// PWM ignores physic.Frequency as there is no way to set it through firmata
func (p *Pin) PWM(duty gpio.Duty, f physic.Frequency) error {
// No need to set mode as firmata does so automatically
// PWM duty scaled down from 24 to 8 bits
return p.c.ExtendedReportAnalogPin(p.pin, uint8((duty>>16)&0xFF))
}
func (p *Pin) Func() pin.Func {
ch, err := p.c.PinStateQuery(p.pin)
if err != nil {
return pin.FuncNone
}
s := <-ch
return s.Mode
}
func (p *Pin) SetFunc(f pin.Func) error {
return p.c.SetPinMode(p.pin, f)
}
func (p *Pin) SupportedFuncs() []pin.Func {
return p.c.GetPinFunctions(p.pin)
}
func (p *Pin) Halt() error {
close(p.done)
p.release()
if err := p.c.SetDigitalPinReporting(p.pin, false); err != nil {
return err
}
if err := p.c.SetAnalogPinReporting(p.pin, false); err != nil {
return err
}
if err := p.c.SetDigitalPinValue(p.pin, gpio.Low); err != nil {
return err
}
return nil
}
func (p *Pin) Name() string {
return p.c.GetPinName(p.pin)
}
func (p *Pin) String() string {
return p.Name()
}
func (p *Pin) Number() int {
return int(p.pin)
}
func (p *Pin) Function() string {
return string(p.Func())
}

@ -0,0 +1,62 @@
package firmata
import (
"periph.io/x/conn/v3/pin"
)
const (
PinFuncDigitalInput pin.Func = "Digital Input"
PinFuncDigitalOutput pin.Func = "Digital Output"
PinFuncAnalogInput pin.Func = "Analog Input"
PinFuncPWM pin.Func = "PWM"
PinFuncServo pin.Func = "Servo"
PinFuncShift pin.Func = "Shift"
PinFuncI2C pin.Func = "I2C"
PinFuncOneWire pin.Func = "OneWire"
PinFuncStepper pin.Func = "Stepper"
PinFuncEncoder pin.Func = "Encoder"
PinFuncSerial pin.Func = "Serial"
PinFuncInputPullUp pin.Func = "Input Pull-Up"
PinFuncSPI pin.Func = "SPI"
PinFuncSonar pin.Func = "Sonar"
PinFuncTone pin.Func = "Tone"
PinFuncDHT pin.Func = "DHT"
)
var pinFuncToModeMap = map[pin.Func]uint8{
PinFuncDigitalInput: 0x0,
PinFuncDigitalOutput: 0x1,
PinFuncAnalogInput: 0x2,
PinFuncPWM: 0x3,
PinFuncServo: 0x4,
PinFuncShift: 0x5,
PinFuncI2C: 0x6,
PinFuncOneWire: 0x7,
PinFuncStepper: 0x8,
PinFuncEncoder: 0x9,
PinFuncSerial: 0xA,
PinFuncInputPullUp: 0xB,
PinFuncSPI: 0xC,
PinFuncSonar: 0xD,
PinFuncTone: 0xE,
PinFuncDHT: 0xF,
}
var pinModeToFuncMap = map[uint8]pin.Func{
0x0: PinFuncDigitalInput,
0x1: PinFuncDigitalOutput,
0x2: PinFuncAnalogInput,
0x3: PinFuncPWM,
0x4: PinFuncServo,
0x5: PinFuncShift,
0x6: PinFuncI2C,
0x7: PinFuncOneWire,
0x8: PinFuncStepper,
0x9: PinFuncEncoder,
0xA: PinFuncSerial,
0xB: PinFuncInputPullUp,
0xC: PinFuncSPI,
0xD: PinFuncSonar,
0xE: PinFuncTone,
0xF: PinFuncDHT,
}

@ -0,0 +1,31 @@
package firmata
import (
"fmt"
"periph.io/x/conn/v3/pin"
)
type PinStateResponse struct {
Pin uint8
Mode pin.Func
State int
}
func ParsePinStateResponse(data []byte) PinStateResponse {
var response = PinStateResponse{
Pin: data[0],
Mode: pinModeToFuncMap[data[1]],
State: 0,
}
for i, b := range data[2:] {
response.State |= int(b << (i * 7))
}
return response
}
func (p PinStateResponse) String() string {
return fmt.Sprintf("pin(%d) mode(%s) state(%d)", p.Pin, p.Mode, p.State)
}

@ -0,0 +1,109 @@
package firmata
type (
SysExCmd uint8
SysExExtendedCmd uint16
)
// Base Features
const (
SysExExtendedId SysExCmd = 0x00 // A value of 0x00 indicates the next 2 bytes define the extended ID
SysExAnalogMappingQuery SysExCmd = 0x69 // ask for mapping of analog pin names to pin numbers
SysExAnalogMappingResponse SysExCmd = 0x6A // reply with mapping info
SysExCapabilityQuery SysExCmd = 0x6B // ask for supported modes and resolution of all pins
SysExCapabilityResponse SysExCmd = 0x6C // reply with supported modes and resolution
SysExPinStateQuery SysExCmd = 0x6D // ask for a pin's current mode and state (different from value)
SysExPinStateResponse SysExCmd = 0x6E // reply with a pin's current mode and state (different from value)
SysExExtendedAnalog SysExCmd = 0x6F // analog write (PWM, Servo, etc.) to any pin
SysExStringData SysExCmd = 0x71 // a string message with 14-bits per char
SysExReportFirmware SysExCmd = 0x79 // report name and version of the firmware
SysExSamplingInterval SysExCmd = 0x7A // the interval at which analog input is sampled (default = 19ms)
SysExNonRealtime SysExCmd = 0x7E // MIDI Reserved for non-realtime messages
SysExRealtime SysExCmd = 0x7F // MIDI Reserved for realtime messages
)
// User Defined Feature Codes
// Assign these to whatever feature constant you define
const (
UserFeature1 SysExCmd = 0x01
UserFeature2 SysExCmd = 0x02
UserFeature3 SysExCmd = 0x03
UserFeature4 SysExCmd = 0x04
UserFeature5 SysExCmd = 0x05
UserFeature6 SysExCmd = 0x06
UserFeature7 SysExCmd = 0x07
UserFeature8 SysExCmd = 0x08
UserFeature9 SysExCmd = 0x09
UserFeatureA SysExCmd = 0x0A
UserFeatureB SysExCmd = 0x0B
UserFeatureC SysExCmd = 0x0C
UserFeatureD SysExCmd = 0x0D
UserFeatureE SysExCmd = 0x0E
UserFeatureF SysExCmd = 0x0F
)
// Optionally Included Features
const (
SysExRCOutputData SysExCmd = 0x5C // https://github.com/firmata/protocol/blob/master/proposals/rcswitch-proposal.md
SysExRCInputData SysExCmd = 0x5D // https://github.com/firmata/protocol/blob/master/proposals/rcswitch-proposal.md
SysExDeviceQuery SysExCmd = 0x5E // https://github.com/finson-release/Luni/blob/master/extras/v0.9/v0.8-device-driver-C-firmata-messages.md
SysExDeviceResponse SysExCmd = 0x5F // https://github.com/finson-release/Luni/blob/master/extras/v0.9/v0.8-device-driver-C-firmata-messages.md
SysExSerialDataV1 SysExCmd = 0x60 // https://github.com/firmata/protocol/blob/master/serial-1.0.md
SysExEncoderData SysExCmd = 0x61 // https://github.com/firmata/protocol/blob/master/encoder.md
SysExAccelStepperData SysExCmd = 0x62 // https://github.com/firmata/protocol/blob/master/accelStepperFirmata.md
SysExSerialDataV2 SysExCmd = 0x67 // https://github.com/firmata/protocol/blob/master/proposals/serial-2.0-proposal.md
SysExSPIData SysExCmd = 0x68 // https://github.com/firmata/protocol/blob/master/spi.md
SysExServoConfig SysExCmd = 0x70 // https://github.com/firmata/protocol/blob/master/servos.md
SysExStepperData SysExCmd = 0x72 // https://github.com/firmata/protocol/blob/master/stepper-legacy.md
SysExOneWireData SysExCmd = 0x73 // https://github.com/firmata/protocol/blob/master/onewire.md
SysExDHTSensorData SysExCmd = 0x74 // https://github.com/firmata/protocol/blob/master/dhtsensor.md
SysExShiftData SysExCmd = 0x75 // https://github.com/firmata/protocol/blob/master/proposals/shift-proposal.md
SysExI2CRequest SysExCmd = 0x76 // https://github.com/firmata/protocol/blob/master/i2c.md
SysExI2CReply SysExCmd = 0x77 // https://github.com/firmata/protocol/blob/master/i2c.md
SysExI2CConfig SysExCmd = 0x78 // https://github.com/firmata/protocol/blob/master/i2c.md
SysExSchedulerData SysExCmd = 0x7B // https://github.com/firmata/protocol/blob/master/scheduler.md
SysExFrequencyCommand SysExCmd = 0x7D // https://github.com/firmata/protocol/blob/master/frequency.md
)
var sysExCmdToStringMap = map[SysExCmd]string{
SysExExtendedId: "ExtendedId",
SysExAnalogMappingQuery: "AnalogMappingQuery",
SysExAnalogMappingResponse: "AnalogMappingResponse",
SysExCapabilityQuery: "CapabilityQuery",
SysExCapabilityResponse: "CapabilityResponse",
SysExPinStateQuery: "PinStateQuery",
SysExPinStateResponse: "PinStateResponse",
SysExExtendedAnalog: "ExtendedAnalog",
SysExStringData: "StringData",
SysExReportFirmware: "ReportFirmware",
SysExSamplingInterval: "SamplingInterval",
SysExNonRealtime: "NonRealtime",
SysExRealtime: "Realtime",
SysExRCOutputData: "RCOutputData",
SysExRCInputData: "RCInputData",
SysExDeviceQuery: "DeviceQuery",
SysExDeviceResponse: "DeviceResponse",
SysExSerialDataV1: "SerialDataV1",
SysExEncoderData: "EncoderData",
SysExAccelStepperData: "AccelStepperData",
SysExSerialDataV2: "SerialDataV2",
SysExSPIData: "SPIData",
SysExServoConfig: "ServoConfig",
SysExStepperData: "StepperData",
SysExOneWireData: "OneWireData",
SysExDHTSensorData: "DHTSensorData",
SysExShiftData: "ShiftData",
SysExI2CRequest: "I2CRequest",
SysExI2CReply: "I2CReply",
SysExI2CConfig: "I2CConfig",
SysExSchedulerData: "SchedulerData",
SysExFrequencyCommand: "FrequencyCommand",
}
func (s SysExCmd) String() string {
if v, ok := sysExCmdToStringMap[s]; ok {
return v
}
return "Unknown"
}

@ -0,0 +1,114 @@
package firmata
import (
"fmt"
)
const SevenBitMask byte = 0b01111111
func TwoByteToByte(a, b byte) byte {
return (a & SevenBitMask) | ((b & SevenBitMask) << 7)
}
func TwoByteToWord(a, b byte) uint16 {
return uint16(a&SevenBitMask) | (uint16(b&SevenBitMask) << 7)
}
func TwoByteString(bytes []byte) string {
if len(bytes)%2 == 1 {
bytes = append(bytes, 0)
}
var s string
for i := 0; i < len(bytes); i += 2 {
s += string(TwoByteToByte(bytes[i], bytes[i+1]))
}
return s
}
func TwoByteRepresentationToByteSlice(bytes []byte) []byte {
if len(bytes)%2 == 1 {
bytes = append(bytes, 0)
}
d := make([]byte, len(bytes)/2)
i := 0
for di := range d {
d[di] = TwoByteToByte(bytes[i], bytes[i+1])
i += 2
}
return d
}
func ByteToTwoByte(b byte) (lsb, msb byte) {
return b & SevenBitMask, (b >> 7) & SevenBitMask
}
func WordToTwoByte(b uint16) (lsb, msb byte) {
return byte(b) & SevenBitMask, byte(b>>7) & SevenBitMask
}
func ByteSliceToTwoByteRepresentation(bytes []byte) []byte {
d := make([]byte, len(bytes)*2)
i := 0
for _, b := range bytes {
d[i], d[i+1] = ByteToTwoByte(b)
i += 2
}
return d
}
func SprintHexArray(data []byte) string {
s := ""
if len(data) == 0 {
return s
}
for _, b := range data {
s += fmt.Sprintf("0x%02X ", b)
}
return s[:len(s)-1]
}
// Encoder7Bit logic determined from here:
// - ConfigurableFirmata@2.10.1/src/Encoder7Bit.cpp:34
func Encoder7Bit(inData []byte) []byte {
var outData []byte
var previous byte
var shift = 0
for _, data := range inData {
if shift == 0 {
outData = append(outData, data&0x7f)
shift++
previous = data >> 7
} else {
outData = append(outData, ((data<<shift)&0x7f)|previous)
if shift == 6 {
outData = append(outData, data>>1)
shift = 0
} else {
shift++
previous = data >> (8 - shift)
}
}
}
if shift > 0 {
outData = append(outData, previous)
}
return outData
}
// Decoder7Bit logic determined from here:
// - ConfigurableFirmata@2.10.1/src/Encoder7Bit.h:17
// - ConfigurableFirmata@2.10.1/src/Encoder7Bit.cpp:54
func Decoder7Bit(inData []byte) []byte {
var outBytes = ((len(inData)) * 7) >> 3
var outData = make([]byte, outBytes)
for i := 0; i < outBytes; i++ {
var j = i << 3
var pos = j / 7
var shift = byte(j % 7)
outData[i] = (inData[pos] >> shift) | ((inData[pos+1] << (7 - shift)) & 0xFF)
}
return outData
}

@ -0,0 +1,129 @@
package firmata
import (
"fmt"
"reflect"
"testing"
)
func TestByteConversion(t *testing.T) {
for i := uint16(0x00); i <= 0xFF; i++ {
t.Run(fmt.Sprintf("0x%02X", i), func(t *testing.T) {
a, b := ByteToTwoByte(byte(i))
o := TwoByteToByte(a, b)
if byte(i) != o {
t.Errorf("ByteToTwoByte(0x%02X) = 0x%02X, 0x%02X => TwoByteToByte() = 0x%02X", i, a, b, o)
}
})
}
}
func TestTwoByteString(t *testing.T) {
tests := []struct {
name string
bytes []byte
want string
}{
{
name: "nil",
bytes: nil,
want: "",
},
{
name: "empty",
bytes: []byte{},
want: "",
},
{
name: "test string",
bytes: ByteSliceToTwoByteRepresentation([]byte{
0x74, 0x65, 0x73, 0x74, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67,
}),
want: "test string",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := TwoByteString(tt.bytes); got != tt.want {
t.Errorf("TwoByteString() = %v, want %v", got, tt.want)
}
})
}
}
func TestByteSliceTo2ByteRepresentation(t *testing.T) {
tests := []struct {
name string
input []byte
expected []byte
}{
{
name: "nil",
input: nil,
expected: []byte{},
},
{
name: "empty",
input: []byte{},
expected: []byte{},
},
{
name: "7 lsb set",
input: []byte{0b01111111, 0b11111111},
expected: []byte{0b01111111, 0b00000000, 0b01111111, 0b00000001},
},
{
name: "7 lsb not set",
input: []byte{0b00000000, 0b10000000},
expected: []byte{0b00000000, 0b00000000, 0b00000000, 0b00000001},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := ByteSliceToTwoByteRepresentation(tt.input); !reflect.DeepEqual(got, tt.expected) {
t.Errorf("ByteSliceToTwoByteRepresentation() = %v, want %v", got, tt.expected)
}
})
}
}
func TestTwoByteRepresentationToByteSlice(t *testing.T) {
tests := []struct {
name string
input []byte
expected []byte
}{
{
name: "nil",
input: nil,
expected: []byte{},
},
{
name: "empty",
input: []byte{},
expected: []byte{},
},
{
name: "7 lsb set",
input: []byte{0b01111111, 0b00000000, 0b01111111, 0b00000001},
expected: []byte{0b01111111, 0b11111111},
},
{
name: "7 lsb not set",
input: []byte{0b00000000, 0b00000000, 0b00000000, 0b00000001},
expected: []byte{0b00000000, 0b10000000},
},
{
name: "only 1 byte",
input: []byte{0b01000000},
expected: []byte{0b01000000},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := TwoByteRepresentationToByteSlice(tt.input); !reflect.DeepEqual(got, tt.expected) {
t.Errorf("TwoByteRepresentationToByteSlice() = %v, want %v", got, tt.expected)
}
})
}
}

@ -96,7 +96,7 @@ func (p *pin) Read() gpio.Level {
} }
func (p *pin) WaitForEdge(timeout time.Duration) bool { func (p *pin) WaitForEdge(timeout time.Duration) bool {
return false return gpio.INVALID.WaitForEdge(timeout)
} }
func (p *pin) Pull() gpio.Pull { func (p *pin) Pull() gpio.Pull {

Loading…
Cancel
Save