mirror of https://github.com/periph/devices
Merge b9ac491105 into 1752d1b57a
commit
80131bcf20
@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue