diff --git a/firmata/client.go b/firmata/client.go index 08e0e5b..03a5549 100644 --- a/firmata/client.go +++ b/firmata/client.go @@ -8,6 +8,7 @@ import ( "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" ) @@ -38,16 +39,18 @@ type ClientI interface { SetSamplingInterval(uint16) error SetDigitalPinValue(p uint8, value gpio.Level) error SendAnalogMappingQuery() (chan AnalogMappingResponse, error) - SetI2CMessageChannel(address uint8, ch chan I2CPacket) - 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 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) @@ -61,7 +64,9 @@ type Client struct { board io.ReadWriteCloser responseChannels map[SysExCmd][]chan []byte sysExListenerChannels map[SysExCmd]chan []byte - i2cListeners map[uint8]chan I2CPacket + + i2cListeners map[uint8]chan I2CPacket + i2cMU sync.Mutex onewireListeners map[uint8]chan []byte onewireMU sync.Mutex @@ -587,13 +592,25 @@ func (c *Client) SendI2CConfig(delayMicroseconds uint8) error { return nil } +func (c *Client) releaseI2CAddressListener(addr uint8) { + c.i2cMU.Lock() + defer c.i2cMU.Unlock() + + delete(c.i2cListeners, addr) +} + // This function only supports 7-bit I2C addresses -func (c *Client) SetI2CMessageChannel(address uint8, ch chan I2CPacket) { - if c.i2cListeners[address] != nil { - close(c.i2cListeners[address]) +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[address] = ch + c.i2cListeners[addr] = ch + + return func() { c.releaseI2CAddressListener(addr) }, nil } func (c *Client) releaseAnalogIOMessageListener(p uint8) { @@ -695,3 +712,7 @@ func (c *Client) GetPinName(p uint8) string { func (c *Client) GetPinFunctions(p uint8) []pin.Func { return c.cr.SupportedPinModes[int(p)] } + +func (c *Client) OpenI2CBus() (i2c.Bus, error) { + return newI2CBus(c) +} diff --git a/firmata/errors.go b/firmata/errors.go index 6919a33..4f6418a 100644 --- a/firmata/errors.go +++ b/firmata/errors.go @@ -21,4 +21,5 @@ var ( 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") ) diff --git a/firmata/i2c.go b/firmata/i2c.go index 8be3e9b..7aa50d4 100644 --- a/firmata/i2c.go +++ b/firmata/i2c.go @@ -1,33 +1,86 @@ package firmata import ( - "periph.io/x/conn/v3/gpio" + "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 (i *I2CBus) SCL() gpio.PinIO { - panic("implement me") -} +func newI2CBus(c ClientI) (*I2CBus, error) { + b := &I2CBus{ + c: c, + } -func (i *I2CBus) SDA() gpio.PinIO { - panic("implement me") + err := c.SendI2CConfig(0) + if err != nil { + return nil, err + } + + return b, nil } -func (i *I2CBus) Close() error { +func (b *I2CBus) Close() error { panic("implement me") } -func (i *I2CBus) String() string { +func (b *I2CBus) String() string { panic("implement me") } -func (i *I2CBus) Tx(addr uint16, w, r []byte) error { - 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 (i *I2CBus) SetSpeed(f physic.Frequency) error { - panic("implement me") +func (b *I2CBus) SetSpeed(f physic.Frequency) error { + return fmt.Errorf("%w: firmata does not support setting bus frequency", ErrUnsupportedFeature) }