1
0
mirror of https://github.com/hybridgroup/gobot.git synced 2025-04-24 13:48:49 +08:00

BUGFIX: I2C connection-bus caching and multiple device usage

This commit is contained in:
Thomas Kohler 2023-02-04 13:22:22 +01:00 committed by GitHub
parent 9ce45c0056
commit c8335aaf85
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 465 additions and 396 deletions

View File

@ -93,10 +93,67 @@ type PWMPinnerProvider interface {
PWMPin(id string) (PWMPinner, error)
}
// I2cSystemDevicer is the interface to a i2c bus at system level.
// I2cSystemDevicer is the interface to a i2c bus at system level, according to I2C/SMBus specification.
// Some functions are not in the interface yet:
// * Process Call (WriteWordDataReadWordData)
// * Block Write - Block Read (WriteBlockDataReadBlockData)
// * Host Notify - WriteWordData() can be used instead
//
// see: https://docs.kernel.org/i2c/smbus-protocol.html#key-to-symbols
//
// S: Start condition; Sr: Repeated start condition, used to switch from write to read mode.
// P: Stop condition; Rd/Wr (1 bit): Read/Write bit. Rd equals 1, Wr equals 0.
// A, NA (1 bit): Acknowledge (ACK) and Not Acknowledge (NACK) bit
// Addr (7 bits): I2C 7 bit address. (10 bit I2C address not yet supported by gobot).
// Comm (8 bits): Command byte, a data byte which often selects a register on the device.
// Data (8 bits): A plain data byte. DataLow and DataHigh represent the low and high byte of a 16 bit word.
// Count (8 bits): A data byte containing the length of a block operation.
// [..]: Data sent by I2C device, as opposed to data sent by the host adapter.
//
type I2cSystemDevicer interface {
I2cOperations
SetAddress(int) error
// ReadByte must be implemented as the sequence:
// "S Addr Rd [A] [Data] NA P"
ReadByte(address int) (byte, error)
// ReadByteData must be implemented as the sequence:
// "S Addr Wr [A] Comm [A] Sr Addr Rd [A] [Data] NA P"
ReadByteData(address int, reg uint8) (uint8, error)
// ReadWordData must be implemented as the sequence:
// "S Addr Wr [A] Comm [A] Sr Addr Rd [A] [DataLow] A [DataHigh] NA P"
ReadWordData(address int, reg uint8) (uint16, error)
// ReadBlockData must be implemented as the sequence:
// "S Addr Wr [A] Comm [A] Sr Addr Rd [A] [Count] A [Data] A [Data] A ... A [Data] NA P"
ReadBlockData(address int, reg uint8, data []byte) error
// WriteByte must be implemented as the sequence:
// "S Addr Wr [A] Data [A] P"
WriteByte(address int, val byte) error
// WriteByteData must be implemented as the sequence:
// "S Addr Wr [A] Comm [A] Data [A] P"
WriteByteData(address int, reg uint8, val uint8) error
// WriteBlockData must be implemented as the sequence:
// "S Addr Wr [A] Comm [A] Count [A] Data [A] Data [A] ... [A] Data [A] P"
WriteBlockData(address int, reg uint8, data []byte) error
// WriteWordData must be implemented as the sequence:
// "S Addr Wr [A] Comm [A] DataLow [A] DataHigh [A] P"
WriteWordData(address int, reg uint8, val uint16) error
// WriteBytes writes the given data starting from the current register of bus device.
WriteBytes(address int, data []byte) error
// Read implements direct read operations.
Read(address int, b []byte) (int, error)
// Write implements direct write operations.
Write(address int, b []byte) (n int, err error)
// Close closes the character device file.
Close() error
}
// SpiSystemDevicer is the interface to a SPI bus at system level.
@ -123,46 +180,14 @@ type BusOperations interface {
}
// I2cOperations represents the i2c methods according to I2C/SMBus specification.
// Some functions are not in the interface yet:
// * Process Call (WriteWordDataReadWordData)
// * Block Write - Block Read (WriteBlockDataReadBlockData)
// * Host Notify - WriteWordData() can be used instead
//
// see: https://docs.kernel.org/i2c/smbus-protocol.html#key-to-symbols
//
// S: Start condition; Sr: Repeated start condition, used to switch from write to read mode.
// P: Stop condition; Rd/Wr (1 bit): Read/Write bit. Rd equals 1, Wr equals 0.
// A, NA (1 bit): Acknowledge (ACK) and Not Acknowledge (NACK) bit
// Addr (7 bits): I2C 7 bit address. (10 bit I2C address not yet supported by gobot).
// Comm (8 bits): Command byte, a data byte which often selects a register on the device.
// Data (8 bits): A plain data byte. DataLow and DataHigh represent the low and high byte of a 16 bit word.
// Count (8 bits): A data byte containing the length of a block operation.
// [..]: Data sent by I2C device, as opposed to data sent by the host adapter.
//
// ReadByteData must be implemented as the sequence:
// "S Addr Wr [A] Comm [A] Sr Addr Rd [A] [Data] NA P"
// ReadBlockData must be implemented as the sequence:
// "S Addr Wr [A] Comm [A] Sr Addr Rd [A] [Count] A [Data] A [Data] A ... A [Data] NA P"
// WriteByte must be implemented as the sequence:
// "S Addr Wr [A] Data [A] P"
// WriteByteData must be implemented as the sequence:
// "S Addr Wr [A] Comm [A] Data [A] P"
// WriteBlockData must be implemented as the sequence:
// "S Addr Wr [A] Comm [A] Count [A] Data [A] Data [A] ... [A] Data [A] P"
type I2cOperations interface {
io.ReadWriteCloser
BusOperations
// ReadByte must be implemented as the sequence:
// "S Addr Rd [A] [Data] NA P"
// ReadByte reads a byte from the current register of an i2c device.
ReadByte() (byte, error)
// ReadWordData must be implemented as the sequence:
// "S Addr Wr [A] Comm [A] Sr Addr Rd [A] [DataLow] A [DataHigh] NA P"
// ReadWordData reads a 16 bit value starting from the given register of an i2c device.
ReadWordData(reg uint8) (uint16, error)
// WriteWordData must be implemented as the sequence:
// "S Addr Wr [A] Comm [A] DataLow [A] DataHigh [A] P"
// WriteWordData writes the given 16 bit value starting from the given register of an i2c device.
WriteWordData(reg uint8, val uint16) error
}

View File

@ -187,7 +187,7 @@ func (d *Adafruit1109Driver) Halt() error {
// This is called by HD44780 driver to set one gpio output. We redirect the call to the i2c driver MCP23017.
// The given id is the same as defined in dataPins and has the syntax "<port>_<pin>".
func (d *Adafruit1109Driver) DigitalWrite(id string, val byte) error {
portio := adafruit1109ParseId(id)
portio := adafruit1109ParseID(id)
return d.writePin(portio, val)
}
@ -195,7 +195,7 @@ func (d *Adafruit1109Driver) DigitalWrite(id string, val byte) error {
// This is called by HD44780 driver to read one gpio input. We redirect the call to the i2c driver MCP23017.
// The given id is the same as defined in dataPins and has the syntax "<port>_<pin>".
func (d *Adafruit1109Driver) DigitalRead(id string) (int, error) {
portio := adafruit1109ParseId(id)
portio := adafruit1109ParseID(id)
uval, err := d.readPin(portio)
if err != nil {
return 0, err
@ -276,7 +276,7 @@ func (ap *adafruit1109PortPin) String() string {
return fmt.Sprintf("%s_%d", ap.port, ap.pin)
}
func adafruit1109ParseId(id string) adafruit1109PortPin {
func adafruit1109ParseID(id string) adafruit1109PortPin {
items := strings.Split(id, "_")
io := uint8(0)
if io64, err := strconv.ParseUint(items[1], 10, 32); err == nil {

View File

@ -81,7 +81,7 @@ func TestAdafruit1109StartReadErr(t *testing.T) {
adaptor.i2cReadImpl = func([]byte) (int, error) {
return 0, errors.New("read error")
}
gobottest.Assert(t, d.Start(), errors.New("read error"))
gobottest.Assert(t, d.Start(), errors.New("MCP write-read: MCP write-ReadByteData(reg=0): read error"))
}
func TestAdafruit1109Halt(t *testing.T) {
@ -287,7 +287,7 @@ func TestAdafruit1109RightButton(t *testing.T) {
}
}
func TestAdafruit1109_parseId(t *testing.T) {
func TestAdafruit1109_parseID(t *testing.T) {
// arrange
ports := []string{"A", "B"}
for _, port := range ports {
@ -295,7 +295,7 @@ func TestAdafruit1109_parseId(t *testing.T) {
id := fmt.Sprintf("%s_%d", port, pin)
t.Run(id, func(t *testing.T) {
// act
got := adafruit1109ParseId(id)
got := adafruit1109ParseID(id)
// assert
gobottest.Assert(t, got, adafruit1109PortPin{port, pin})
})

View File

@ -1,8 +1,7 @@
package i2c
import (
"errors"
"sync"
"fmt"
"gobot.io/x/gobot"
)
@ -21,10 +20,10 @@ const (
)
var (
ErrEncryptedBytes = errors.New("Encrypted bytes")
ErrNotEnoughBytes = errors.New("Not enough bytes read")
ErrNotReady = errors.New("Device is not ready")
ErrInvalidPosition = errors.New("Invalid position value")
// ErrNotEnoughBytes is used when the count of read bytes was too small
ErrNotEnoughBytes = fmt.Errorf("Not enough bytes read")
// ErrNotReady is used when the device is not ready
ErrNotReady = fmt.Errorf("Device is not ready")
)
type bitState uint8
@ -44,144 +43,71 @@ type Connection gobot.I2cOperations
type i2cConnection struct {
bus gobot.I2cSystemDevicer
address int
mutex sync.Mutex
}
// NewConnection creates and returns a new connection to a specific
// i2c device on a bus and address.
// NewConnection creates and returns a new connection to a specific i2c device on a bus and address.
func NewConnection(bus gobot.I2cSystemDevicer, address int) (connection *i2cConnection) {
return &i2cConnection{bus: bus, address: address}
}
// Read data from an i2c device.
func (c *i2cConnection) Read(data []byte) (read int, err error) {
c.mutex.Lock()
defer c.mutex.Unlock()
if err = c.bus.SetAddress(c.address); err != nil {
return 0, err
}
read, err = c.bus.Read(data)
return
return c.bus.Read(c.address, data)
}
// Write data to an i2c device.
func (c *i2cConnection) Write(data []byte) (written int, err error) {
c.mutex.Lock()
defer c.mutex.Unlock()
if err = c.bus.SetAddress(c.address); err != nil {
return 0, err
}
written, err = c.bus.Write(data)
return
return c.bus.Write(c.address, data)
}
// Close connection to i2c device.
// Close connection to i2c device. The bus was created by adaptor and will be closed there.
func (c *i2cConnection) Close() error {
c.mutex.Lock()
defer c.mutex.Unlock()
return c.bus.Close()
return nil
}
// ReadByte reads a single byte from the i2c device.
func (c *i2cConnection) ReadByte() (val byte, err error) {
c.mutex.Lock()
defer c.mutex.Unlock()
if err := c.bus.SetAddress(c.address); err != nil {
return 0, err
}
return c.bus.ReadByte()
func (c *i2cConnection) ReadByte() (byte, error) {
return c.bus.ReadByte(c.address)
}
// ReadByteData reads a byte value for a register on the i2c device.
func (c *i2cConnection) ReadByteData(reg uint8) (val uint8, err error) {
c.mutex.Lock()
defer c.mutex.Unlock()
if err := c.bus.SetAddress(c.address); err != nil {
return 0, err
}
return c.bus.ReadByteData(reg)
func (c *i2cConnection) ReadByteData(reg uint8) (uint8, error) {
return c.bus.ReadByteData(c.address, reg)
}
// ReadWordData reads a word value for a register on the i2c device.
func (c *i2cConnection) ReadWordData(reg uint8) (val uint16, err error) {
c.mutex.Lock()
defer c.mutex.Unlock()
if err := c.bus.SetAddress(c.address); err != nil {
return 0, err
}
return c.bus.ReadWordData(reg)
func (c *i2cConnection) ReadWordData(reg uint8) (uint16, error) {
return c.bus.ReadWordData(c.address, reg)
}
// ReadBlockData reads a block of bytes from a register on the i2c device.
func (c *i2cConnection) ReadBlockData(reg uint8, b []byte) (err error) {
c.mutex.Lock()
defer c.mutex.Unlock()
if err := c.bus.SetAddress(c.address); err != nil {
return err
}
return c.bus.ReadBlockData(reg, b)
func (c *i2cConnection) ReadBlockData(reg uint8, b []byte) error {
return c.bus.ReadBlockData(c.address, reg, b)
}
// WriteByte writes a single byte to the i2c device.
func (c *i2cConnection) WriteByte(val byte) (err error) {
c.mutex.Lock()
defer c.mutex.Unlock()
if err := c.bus.SetAddress(c.address); err != nil {
return err
}
return c.bus.WriteByte(val)
func (c *i2cConnection) WriteByte(val byte) error {
return c.bus.WriteByte(c.address, val)
}
// WriteByteData writes a byte value to a register on the i2c device.
func (c *i2cConnection) WriteByteData(reg uint8, val uint8) (err error) {
c.mutex.Lock()
defer c.mutex.Unlock()
if err := c.bus.SetAddress(c.address); err != nil {
return err
}
return c.bus.WriteByteData(reg, val)
func (c *i2cConnection) WriteByteData(reg uint8, val uint8) error {
return c.bus.WriteByteData(c.address, reg, val)
}
// WriteWordData writes a word value to a register on the i2c device.
func (c *i2cConnection) WriteWordData(reg uint8, val uint16) (err error) {
c.mutex.Lock()
defer c.mutex.Unlock()
if err := c.bus.SetAddress(c.address); err != nil {
return err
}
return c.bus.WriteWordData(reg, val)
return c.bus.WriteWordData(c.address, reg, val)
}
// WriteBlockData writes a block of bytes to a register on the i2c device.
func (c *i2cConnection) WriteBlockData(reg uint8, b []byte) (err error) {
c.mutex.Lock()
defer c.mutex.Unlock()
if err := c.bus.SetAddress(c.address); err != nil {
return err
}
return c.bus.WriteBlockData(reg, b)
return c.bus.WriteBlockData(c.address, reg, b)
}
// WriteBytes writes a block of bytes to the current register on the i2c device.
func (c *i2cConnection) WriteBytes(b []byte) (err error) {
c.mutex.Lock()
defer c.mutex.Unlock()
if err := c.bus.SetAddress(c.address); err != nil {
return err
}
return c.bus.WriteBytes(b)
return c.bus.WriteBytes(c.address, b)
}
// setBit is used to set a bit at a given position to 1.

View File

@ -16,37 +16,45 @@ import (
const dev = "/dev/i2c-1"
func syscallImpl(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) {
if (trap == syscall.SYS_IOCTL) && (a2 == system.I2C_FUNCS) {
var funcPtr *uint64 = (*uint64)(unsafe.Pointer(a3))
*funcPtr = system.I2C_FUNC_SMBUS_READ_BYTE | system.I2C_FUNC_SMBUS_READ_BYTE_DATA |
system.I2C_FUNC_SMBUS_READ_WORD_DATA |
system.I2C_FUNC_SMBUS_WRITE_BYTE | system.I2C_FUNC_SMBUS_WRITE_BYTE_DATA |
system.I2C_FUNC_SMBUS_WRITE_WORD_DATA
}
// Let all operations succeed
return 0, 0, 0
}
func getSyscallFuncImpl(errorMask byte) func(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) {
// bit 0: error on function query
// bit 1: error on set address
// bit 2: error on command
return func(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) {
// function query
if (trap == syscall.SYS_IOCTL) && (a2 == system.I2C_FUNCS) {
if errorMask&0x01 == 0x01 {
return 0, 0, 1
}
func syscallImplFail(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) {
if (trap == syscall.SYS_IOCTL) && (a2 == system.I2C_FUNCS) {
var funcPtr *uint64 = (*uint64)(unsafe.Pointer(a3))
*funcPtr = system.I2C_FUNC_SMBUS_READ_BYTE | system.I2C_FUNC_SMBUS_READ_BYTE_DATA |
system.I2C_FUNC_SMBUS_READ_WORD_DATA |
system.I2C_FUNC_SMBUS_WRITE_BYTE | system.I2C_FUNC_SMBUS_WRITE_BYTE_DATA |
system.I2C_FUNC_SMBUS_WRITE_WORD_DATA
// retrieve functions call succeed
var funcPtr *uint64 = (*uint64)(unsafe.Pointer(a3))
*funcPtr = system.I2C_FUNC_SMBUS_READ_BYTE | system.I2C_FUNC_SMBUS_READ_BYTE_DATA |
system.I2C_FUNC_SMBUS_READ_WORD_DATA |
system.I2C_FUNC_SMBUS_WRITE_BYTE | system.I2C_FUNC_SMBUS_WRITE_BYTE_DATA |
system.I2C_FUNC_SMBUS_WRITE_WORD_DATA
}
// set address
if (trap == syscall.SYS_IOCTL) && (a2 == system.I2C_SLAVE) {
if errorMask&0x02 == 0x02 {
return 0, 0, 1
}
}
// command
if (trap == syscall.SYS_IOCTL) && (a2 == system.I2C_SMBUS) {
if errorMask&0x04 == 0x04 {
return 0, 0, 1
}
}
// Let all operations succeed
return 0, 0, 0
}
// Let all operations fail
return 0, 0, 1
}
func initI2CDevice() gobot.I2cSystemDevicer {
a := system.NewAccesser()
a.UseMockFilesystem([]string{dev})
msc := a.UseMockSyscall()
msc.Impl = syscallImpl
msc.Impl = getSyscallFuncImpl(0x00)
d, _ := a.NewI2cDevice(dev)
return d
@ -56,7 +64,7 @@ func initI2CDeviceAddressError() gobot.I2cSystemDevicer {
a := system.NewAccesser()
a.UseMockFilesystem([]string{dev})
msc := a.UseMockSyscall()
msc.Impl = syscallImplFail
msc.Impl = getSyscallFuncImpl(0x02)
d, _ := a.NewI2cDevice(dev)
return d

View File

@ -67,6 +67,8 @@ var CustomLCDChars = map[string][8]byte{
"frowney": {0, 0, 10, 0, 0, 0, 14, 17},
}
var jhd1313m1ErrInvalidPosition = fmt.Errorf("Invalid position value")
// JHD1313M1Driver is a driver for the Jhd1313m1 LCD display which has two i2c addreses,
// one belongs to a controller and the other controls solely the backlight.
// This module was tested with the Seed Grove LCD RGB Backlight v2.0 display which requires 5V to operate.
@ -258,7 +260,7 @@ func (h *JHD1313M1Driver) Write(message string) error {
// 16..32 are the positions in the second display line.
func (h *JHD1313M1Driver) SetPosition(pos int) (err error) {
if pos < 0 || pos > 31 {
err = ErrInvalidPosition
err = jhd1313m1ErrInvalidPosition
return
}
offset := byte(pos)

View File

@ -162,8 +162,8 @@ func TestJHD1313MDriverSetSecondLinePosition(t *testing.T) {
func TestJHD1313MDriverSetPositionInvalid(t *testing.T) {
d, _ := initTestJHD1313M1DriverWithStubbedAdaptor()
d.Start()
gobottest.Assert(t, d.SetPosition(-1), ErrInvalidPosition)
gobottest.Assert(t, d.SetPosition(32), ErrInvalidPosition)
gobottest.Assert(t, d.SetPosition(-1), jhd1313m1ErrInvalidPosition)
gobottest.Assert(t, d.SetPosition(32), jhd1313m1ErrInvalidPosition)
}
func TestJHD1313MDriverScroll(t *testing.T) {

View File

@ -36,9 +36,9 @@ type bank struct {
portB port
}
// MCP23017Config contains the device configuration for the IOCON register.
// mcp23017Config contains the device configuration for the IOCON register.
// These fields should only be set with values 0 or 1.
type MCP23017Config struct {
type mcp23017Config struct {
bank uint8
mirror uint8
seqop uint8
@ -48,7 +48,7 @@ type MCP23017Config struct {
intpol uint8
}
type MCP23017Behavior struct {
type mcp23017Behavior struct {
forceRefresh bool
autoIODirOff bool
}
@ -56,8 +56,8 @@ type MCP23017Behavior struct {
// MCP23017Driver contains the driver configuration parameters.
type MCP23017Driver struct {
*Driver
mcpConf MCP23017Config
mcpBehav MCP23017Behavior
mcpConf mcp23017Config
mcpBehav mcp23017Behavior
gobot.Eventer
}
@ -79,7 +79,7 @@ type MCP23017Driver struct {
func NewMCP23017Driver(c Connector, options ...func(Config)) *MCP23017Driver {
d := &MCP23017Driver{
Driver: NewDriver(c, "MCP23017", mcp23017DefaultAddress),
mcpConf: MCP23017Config{},
mcpConf: mcp23017Config{},
Eventer: gobot.NewEventer(),
}
d.afterStart = d.initialize
@ -321,7 +321,7 @@ func (m *MCP23017Driver) initialize() (err error) {
func (m *MCP23017Driver) write(reg uint8, pin uint8, state bitState) (err error) {
valOrg, err := m.read(reg)
if err != nil {
return err
return fmt.Errorf("MCP write-read: %v", err)
}
var val uint8
@ -337,7 +337,7 @@ func (m *MCP23017Driver) write(reg uint8, pin uint8, state bitState) (err error)
m.mcpBehav.forceRefresh, m.GetAddressOrDefault(mcp23017DefaultAddress), reg, m.getRegName(reg), val)
}
if err = m.connection.WriteByteData(reg, val); err != nil {
return err
return fmt.Errorf("MCP write-WriteByteData(reg=%d,val=%d): %v", reg, val, err)
}
} else {
if mcp23017Debug {
@ -353,7 +353,7 @@ func (m *MCP23017Driver) write(reg uint8, pin uint8, state bitState) (err error)
func (m *MCP23017Driver) read(reg uint8) (val uint8, err error) {
val, err = m.connection.ReadByteData(reg)
if err != nil {
return val, err
return val, fmt.Errorf("MCP write-ReadByteData(reg=%d): %v", reg, err)
}
if mcp23017Debug {
log.Printf("reading done: MCP autoIODirOff: %t, address: 0x%X, register:0x%X, name: %s, value: 0x%X\n",
@ -377,7 +377,7 @@ func (m *MCP23017Driver) getPort(portStr string) (selectedPort port) {
}
// getUint8Value returns the configuration data as a packed value.
func (mc *MCP23017Config) getUint8Value() uint8 {
func (mc *mcp23017Config) getUint8Value() uint8 {
return mc.bank<<7 | mc.mirror<<6 | mc.seqop<<5 | mc.disslw<<4 | mc.haen<<3 | mc.odr<<2 | mc.intpol<<1
}

View File

@ -241,7 +241,7 @@ func TestMCP23017CommandsWriteGPIOErrIODIR(t *testing.T) {
// act
err := d.WriteGPIO(7, "A", 0)
// assert
gobottest.Assert(t, err, errors.New("write error"))
gobottest.Assert(t, err, errors.New("MCP write-read: MCP write-ReadByteData(reg=0): write error"))
}
func TestMCP23017CommandsWriteGPIOErrOLAT(t *testing.T) {
@ -258,7 +258,7 @@ func TestMCP23017CommandsWriteGPIOErrOLAT(t *testing.T) {
// act
err := d.WriteGPIO(7, "A", 0)
// assert
gobottest.Assert(t, err, errors.New("write error"))
gobottest.Assert(t, err, errors.New("MCP write-read: MCP write-ReadByteData(reg=20): write error"))
}
func TestMCP23017ReadGPIO(t *testing.T) {
@ -381,7 +381,7 @@ func TestMCP23017ReadGPIOErr(t *testing.T) {
// act
_, err := d.ReadGPIO(7, "A")
// assert
gobottest.Assert(t, err, errors.New("read error"))
gobottest.Assert(t, err, errors.New("MCP write-read: MCP write-ReadByteData(reg=0): read error"))
}
func TestMCP23017SetPinMode(t *testing.T) {
@ -431,7 +431,7 @@ func TestMCP23017SetPinModeErr(t *testing.T) {
// act
err := d.SetPinMode(7, "A", 0)
// assert
gobottest.Assert(t, err, errors.New("write error"))
gobottest.Assert(t, err, errors.New("MCP write-read: MCP write-ReadByteData(reg=0): write error"))
}
func TestMCP23017SetPullUp(t *testing.T) {
@ -481,7 +481,7 @@ func TestMCP23017SetPullUpErr(t *testing.T) {
// act
err := d.SetPullUp(7, "A", 0)
// assert
gobottest.Assert(t, err, errors.New("write error"))
gobottest.Assert(t, err, errors.New("MCP write-read: MCP write-ReadByteData(reg=12): write error"))
}
func TestMCP23017SetGPIOPolarity(t *testing.T) {
@ -531,7 +531,7 @@ func TestMCP23017SetGPIOPolarityErr(t *testing.T) {
// act
err := d.SetGPIOPolarity(7, "A", 0)
// assert
gobottest.Assert(t, err, errors.New("write error"))
gobottest.Assert(t, err, errors.New("MCP write-read: MCP write-ReadByteData(reg=2): write error"))
}
func TestMCP23017_write(t *testing.T) {
@ -553,7 +553,7 @@ func TestMCP23017_write(t *testing.T) {
return 0, errors.New("write error")
}
err = d.write(port.IODIR, uint8(7), 0)
gobottest.Assert(t, err, errors.New("write error"))
gobottest.Assert(t, err, errors.New("MCP write-read: MCP write-ReadByteData(reg=1): write error"))
// read error
d, a = initTestMCP23017WithStubbedAdaptor(0)
@ -561,7 +561,7 @@ func TestMCP23017_write(t *testing.T) {
return len(b), errors.New("read error")
}
err = d.write(port.IODIR, uint8(7), 0)
gobottest.Assert(t, err, errors.New("read error"))
gobottest.Assert(t, err, errors.New("MCP write-read: MCP write-ReadByteData(reg=1): read error"))
a.i2cReadImpl = func(b []byte) (int, error) {
return len(b), nil
}
@ -588,7 +588,7 @@ func TestMCP23017_read(t *testing.T) {
val, err := d.read(port.IODIR)
gobottest.Assert(t, val, uint8(0))
gobottest.Assert(t, err, errors.New("read error"))
gobottest.Assert(t, err, errors.New("MCP write-ReadByteData(reg=0): read error"))
// read
d, a = initTestMCP23017WithStubbedAdaptor(0)

View File

@ -147,7 +147,7 @@ func (p *PCA9685Driver) SetPWMFreq(freq float32) error {
prescalevel /= 4096
// Ratio between desired frequency and maximum
prescalevel /= freq
prescalevel -= 1
prescalevel--
// Round value to nearest whole
prescale := byte(prescalevel + 0.5)

View File

@ -471,19 +471,19 @@ func TestPCF8583WriteRam(t *testing.T) {
// arrange
d, a := initTestPCF8583WithStubbedAdaptor()
a.written = []byte{} // reset writes of Start() and former test
wantRamAddress := uint8(0xFF)
wantRamValue := uint8(0xEF)
wantRAMAddress := uint8(0xFF)
wantRAMValue := uint8(0xEF)
// arrange writes
a.i2cWriteImpl = func(b []byte) (int, error) {
return len(b), nil
}
// act
err := d.WriteRAM(wantRamAddress-pcf8583RamOffset, wantRamValue)
err := d.WriteRAM(wantRAMAddress-pcf8583RamOffset, wantRAMValue)
// assert
gobottest.Assert(t, err, nil)
gobottest.Assert(t, len(a.written), 2)
gobottest.Assert(t, a.written[0], wantRamAddress)
gobottest.Assert(t, a.written[1], wantRamValue)
gobottest.Assert(t, a.written[0], wantRAMAddress)
gobottest.Assert(t, a.written[1], wantRAMValue)
}
func TestPCF8583WriteRamAddressOverflowFails(t *testing.T) {
@ -505,7 +505,7 @@ func TestPCF8583ReadRam(t *testing.T) {
// arrange
d, a := initTestPCF8583WithStubbedAdaptor()
a.written = []byte{} // reset writes of Start() and former test
wantRamAddress := uint8(pcf8583RamOffset)
wantRAMAddress := uint8(pcf8583RamOffset)
want := uint8(0xAB)
// arrange writes
a.i2cWriteImpl = func(b []byte) (int, error) {
@ -519,12 +519,12 @@ func TestPCF8583ReadRam(t *testing.T) {
return len(b), nil
}
// act
got, err := d.ReadRAM(wantRamAddress - pcf8583RamOffset)
got, err := d.ReadRAM(wantRAMAddress - pcf8583RamOffset)
// assert
gobottest.Assert(t, err, nil)
gobottest.Assert(t, got, want)
gobottest.Assert(t, len(a.written), 1)
gobottest.Assert(t, a.written[0], wantRamAddress)
gobottest.Assert(t, a.written[0], wantRAMAddress)
gobottest.Assert(t, numCallsRead, 1)
}

View File

@ -15,7 +15,7 @@ import (
// and tests all implementations, so no further tests needed here for gobot.Driver interface
var _ gobot.Driver = (*TSL2561Driver)(nil)
func testIdReader(b []byte) (int, error) {
func testIDReader(b []byte) (int, error) {
buf := new(bytes.Buffer)
// Mock device responding 0xA
binary.Write(buf, binary.LittleEndian, uint8(0x0A))
@ -26,7 +26,7 @@ func testIdReader(b []byte) (int, error) {
func initTestTSL2561Driver() (*TSL2561Driver, *i2cTestAdaptor) {
a := newI2cTestAdaptor()
d := NewTSL2561Driver(a)
a.i2cReadImpl = testIdReader
a.i2cReadImpl = testIDReader
if err := d.Start(); err != nil {
panic(err)
}
@ -58,7 +58,7 @@ func TestTSL2561DriverOptions(t *testing.T) {
func TestTSL2561DriverStart(t *testing.T) {
a := newI2cTestAdaptor()
d := NewTSL2561Driver(a)
a.i2cReadImpl = testIdReader
a.i2cReadImpl = testIDReader
gobottest.Assert(t, d.Start(), nil)
}
@ -82,7 +82,7 @@ func TestTSL2561DriverHalt(t *testing.T) {
func TestTSL2561DriverRead16(t *testing.T) {
d, a := initTestTSL2561Driver()
a.i2cReadImpl = testIdReader
a.i2cReadImpl = testIDReader
a.i2cReadImpl = func(b []byte) (int, error) {
buf := new(bytes.Buffer)
// send low

View File

@ -1,6 +1,7 @@
package i2c
import (
"fmt"
"sync"
"time"
@ -20,6 +21,7 @@ const (
const wiichuckDefaultAddress = 0x52
// WiichuckDriver contains the attributes for the i2c driver
type WiichuckDriver struct {
*Driver
interval time.Duration
@ -82,16 +84,16 @@ func (w *WiichuckDriver) Joystick() map[string]float64 {
// update parses value to update buttons and joystick.
// If value is encrypted, warning message is printed
func (w *WiichuckDriver) update(value []byte) (err error) {
func (w *WiichuckDriver) update(value []byte) error {
if w.isEncrypted(value) {
return ErrEncryptedBytes
} else {
w.parse(value)
w.adjustOrigins()
w.updateButtons()
w.updateJoystick()
return fmt.Errorf("Encrypted bytes")
}
return
w.parse(value)
w.adjustOrigins()
w.updateButtons()
w.updateJoystick()
return nil
}
// setJoystickDefaultValue sets default value if value is -1

View File

@ -15,17 +15,19 @@ const yl40DefaultAddress = 0x48
const yl40Debug = false
// YL40Pin wraps the underlying string type for type safety
type YL40Pin string
const (
// brightness sensor, high brightness - low raw value, scaled to 0..1000 (high brightness - high value)
// YL40Bri for brightness sensor, high brightness - low raw value, scaled to 0..1000 (high brightness - high value)
YL40Bri YL40Pin = "brightness"
// temperature sensor, high temperature - low raw value, scaled to °C
// YL40Temp for temperature sensor, high temperature - low raw value, scaled to °C
YL40Temp YL40Pin = "temperature"
// wired to AOUT, scaled to voltage 3.3V
// YL40AIN2 is wired to AOUT, scaled to voltage 3.3V
YL40AIN2 YL40Pin = "analog input AIN2"
// adjustable resistor, turn clockwise will lower the raw value, scaled to -100..+100% (clockwise)
// YL40Poti is adjustable resistor, turn clockwise will lower the raw value, scaled to -100..+100% (clockwise)
YL40Poti YL40Pin = "potentiometer"
// YL40AOUT is the analog output
YL40AOUT YL40Pin = "analog output"
)

View File

@ -5,6 +5,7 @@ import (
"sync"
multierror "github.com/hashicorp/go-multierror"
"gobot.io/x/gobot"
"gobot.io/x/gobot/drivers/i2c"
"gobot.io/x/gobot/system"
)
@ -17,7 +18,7 @@ type I2cBusAdaptor struct {
validateNumber i2cBusNumberValidator
defaultBusNumber int
mutex sync.Mutex
connections map[string]i2c.Connection
buses map[int]gobot.I2cSystemDevicer
}
// NewI2cBusAdaptor provides the access to i2c buses of the board. The validator is used to check the bus number,
@ -36,51 +37,49 @@ func (a *I2cBusAdaptor) Connect() error {
a.mutex.Lock()
defer a.mutex.Unlock()
a.connections = make(map[string]i2c.Connection)
a.buses = make(map[int]gobot.I2cSystemDevicer)
return nil
}
// Finalize closes all i2c connections.
// Finalize closes all i2c buses.
func (a *I2cBusAdaptor) Finalize() error {
a.mutex.Lock()
defer a.mutex.Unlock()
var err error
for _, con := range a.connections {
if con != nil {
if e := con.Close(); e != nil {
for _, bus := range a.buses {
if bus != nil {
if e := bus.Close(); e != nil {
err = multierror.Append(err, e)
}
}
}
a.connections = nil
a.buses = nil
return err
}
// GetI2cConnection returns a connection to a device on a specified i2c bus
func (a *I2cBusAdaptor) GetI2cConnection(address int, busNum int) (connection i2c.Connection, err error) {
func (a *I2cBusAdaptor) GetI2cConnection(address int, busNum int) (i2c.Connection, error) {
a.mutex.Lock()
defer a.mutex.Unlock()
if a.connections == nil {
if a.buses == nil {
return nil, fmt.Errorf("not connected")
}
id := fmt.Sprintf("%d_%d", busNum, address)
con := a.connections[id]
if con == nil {
if err := a.validateNumber(busNum); err != nil {
return nil, err
}
bus, err := a.sys.NewI2cDevice(fmt.Sprintf("/dev/i2c-%d", busNum))
bus := a.buses[busNum]
if bus == nil {
err := a.validateNumber(busNum)
if err != nil {
return nil, err
}
con = i2c.NewConnection(bus, address)
a.connections[id] = con
bus, err = a.sys.NewI2cDevice(fmt.Sprintf("/dev/i2c-%d", busNum))
if err != nil {
return nil, err
}
a.buses[busNum] = bus
}
return con, err
return i2c.NewConnection(bus, address), nil
}
// DefaultI2cBus returns the default i2c bus number for this platform.

View File

@ -34,11 +34,11 @@ func initTestI2cAdaptorWithMockedFilesystem(mockPaths []string) (*I2cBusAdaptor,
func TestI2cWorkflow(t *testing.T) {
a, _ := initTestI2cAdaptorWithMockedFilesystem([]string{i2cBus1})
gobottest.Assert(t, len(a.connections), 0)
gobottest.Assert(t, len(a.buses), 0)
con, err := a.GetI2cConnection(0xff, 1)
gobottest.Assert(t, err, nil)
gobottest.Assert(t, len(a.connections), 1)
gobottest.Assert(t, len(a.buses), 1)
_, err = con.Write([]byte{0x00, 0x01})
gobottest.Assert(t, err, nil)
@ -49,7 +49,7 @@ func TestI2cWorkflow(t *testing.T) {
gobottest.Assert(t, data, []byte{0x00, 0x01})
gobottest.Assert(t, a.Finalize(), nil)
gobottest.Assert(t, len(a.connections), 0)
gobottest.Assert(t, len(a.buses), 0)
}
func TestI2cGetI2cConnection(t *testing.T) {
@ -59,18 +59,18 @@ func TestI2cGetI2cConnection(t *testing.T) {
c1, e1 := a.GetI2cConnection(0xff, 1)
gobottest.Assert(t, e1, nil)
gobottest.Refute(t, c1, nil)
gobottest.Assert(t, len(a.connections), 1)
gobottest.Assert(t, len(a.buses), 1)
// assert invalid bus gets error
c2, e2 := a.GetI2cConnection(0x01, 99)
gobottest.Assert(t, e2, fmt.Errorf("99 not valid"))
gobottest.Assert(t, c2, nil)
gobottest.Assert(t, len(a.connections), 1)
gobottest.Assert(t, len(a.buses), 1)
// assert unconnected gets error
gobottest.Assert(t, a.Finalize(), nil)
c3, e3 := a.GetI2cConnection(0x01, 99)
gobottest.Assert(t, e3, fmt.Errorf("not connected"))
gobottest.Assert(t, c3, nil)
gobottest.Assert(t, len(a.connections), 0)
gobottest.Assert(t, len(a.buses), 0)
}
func TestI2cFinalize(t *testing.T) {
@ -81,16 +81,16 @@ func TestI2cFinalize(t *testing.T) {
// arrange
gobottest.Assert(t, a.Connect(), nil)
a.GetI2cConnection(0xaf, 1)
gobottest.Assert(t, len(a.connections), 1)
gobottest.Assert(t, len(a.buses), 1)
// assert that Finalize after GetI2cConnection is working and clean up
gobottest.Assert(t, a.Finalize(), nil)
gobottest.Assert(t, len(a.connections), 0)
gobottest.Assert(t, len(a.buses), 0)
// assert that finalize after finalize is working
gobottest.Assert(t, a.Finalize(), nil)
// assert that close error is recognized
gobottest.Assert(t, a.Connect(), nil)
con, _ := a.GetI2cConnection(0xbf, 1)
gobottest.Assert(t, len(a.connections), 1)
gobottest.Assert(t, len(a.buses), 1)
con.Write([]byte{0xbf})
fs.WithCloseError = true
err := a.Finalize()
@ -104,8 +104,8 @@ func TestI2cReConnect(t *testing.T) {
// act
gobottest.Assert(t, a.Connect(), nil)
// assert
gobottest.Refute(t, a.connections, nil)
gobottest.Assert(t, len(a.connections), 0)
gobottest.Refute(t, a.buses, nil)
gobottest.Assert(t, len(a.buses), 0)
}
func TestI2cGetDefaultBus(t *testing.T) {

View File

@ -4,11 +4,15 @@ import (
"fmt"
"log"
"os"
"sync"
"syscall"
"unsafe"
)
const i2cDeviceDebug = false
const (
i2cDeviceDebug = false
forceSetAddress = false // normally address will be written only when changed, this behavior can be overridden
)
const (
// From /usr/include/linux/i2c-dev.h:
@ -51,39 +55,42 @@ type i2cSmbusIoctlData struct {
}
type i2cDevice struct {
location string
file File
funcs uint64 // adapter functionality mask
sys systemCaller
fs filesystem
location string
sys systemCaller
fs filesystem
file File
funcs uint64 // adapter functionality mask
lastAddress int
mutex sync.Mutex
}
// NewI2cDevice returns an io.ReadWriteCloser with the proper ioctrl given
// an i2c bus location.
// NewI2cDevice returns a Linux Kernel access by ioctrl to the given i2c bus location (character device).
// Important note for "os.ModeExclusive": this is undefined without create the file for character devices, this means
// a second open will not return an error e.g. due to a busy resource. If this is not wanted, e.g. to minimize count of
// open fd's this needs to be prevented at caller side by implementing a caching mechanism. Furthermore this behavior
// can lead to problems with multiple devices on the same bus because the cycle SetAddress()...Read()/Write() etc. can
// be interrupted when using multiple instances for the same location.
func (a *Accesser) NewI2cDevice(location string) (*i2cDevice, error) {
if location == "" {
return nil, fmt.Errorf("the given character device location is empty")
}
d := &i2cDevice{
location: location,
sys: a.sys,
fs: a.fs,
location: location,
sys: a.sys,
fs: a.fs,
lastAddress: -1,
}
return d, nil
}
// SetAddress sets the address of the i2c device to use.
func (d *i2cDevice) SetAddress(address int) error {
// for go vet false positives, see: https://github.com/golang/go/issues/41205
if err := d.syscallIoctl(I2C_SLAVE, unsafe.Pointer(uintptr(byte(address))), "Setting address"); err != nil {
return err
}
return nil
}
// Close closes the character device file.
// Close closes the character device file and resets the lazy variables.
func (d *i2cDevice) Close() error {
d.mutex.Lock()
defer d.mutex.Unlock()
d.funcs = 0
d.lastAddress = -1
if d.file != nil {
return d.file.Close()
}
@ -91,40 +98,52 @@ func (d *i2cDevice) Close() error {
}
// ReadByte reads a byte from the current register of an i2c device.
func (d *i2cDevice) ReadByte() (byte, error) {
func (d *i2cDevice) ReadByte(address int) (byte, error) {
d.mutex.Lock()
defer d.mutex.Unlock()
if err := d.queryFunctionality(I2C_FUNC_SMBUS_READ_BYTE, "read byte"); err != nil {
return 0, err
}
var data uint8 = 0xFC // set value for debugging purposes
err := d.smbusAccess(I2C_SMBUS_READ, 0, I2C_SMBUS_BYTE, unsafe.Pointer(&data))
err := d.smbusAccess(address, I2C_SMBUS_READ, 0, I2C_SMBUS_BYTE, unsafe.Pointer(&data))
return data, err
}
// ReadByteData reads a byte from the given register of an i2c device.
func (d *i2cDevice) ReadByteData(reg uint8) (val uint8, err error) {
func (d *i2cDevice) ReadByteData(address int, reg uint8) (val uint8, err error) {
d.mutex.Lock()
defer d.mutex.Unlock()
if err := d.queryFunctionality(I2C_FUNC_SMBUS_READ_BYTE_DATA, "read byte data"); err != nil {
return 0, err
}
var data uint8 = 0xFD // set value for debugging purposes
err = d.smbusAccess(I2C_SMBUS_READ, reg, I2C_SMBUS_BYTE_DATA, unsafe.Pointer(&data))
err = d.smbusAccess(address, I2C_SMBUS_READ, reg, I2C_SMBUS_BYTE_DATA, unsafe.Pointer(&data))
return data, err
}
// ReadWordData reads a 16 bit value starting from the given register of an i2c device.
func (d *i2cDevice) ReadWordData(reg uint8) (val uint16, err error) {
func (d *i2cDevice) ReadWordData(address int, reg uint8) (val uint16, err error) {
d.mutex.Lock()
defer d.mutex.Unlock()
if err := d.queryFunctionality(I2C_FUNC_SMBUS_READ_WORD_DATA, "read word data"); err != nil {
return 0, err
}
var data uint16 = 0xFFFE // set value for debugging purposes
err = d.smbusAccess(I2C_SMBUS_READ, reg, I2C_SMBUS_WORD_DATA, unsafe.Pointer(&data))
err = d.smbusAccess(address, I2C_SMBUS_READ, reg, I2C_SMBUS_WORD_DATA, unsafe.Pointer(&data))
return data, err
}
// ReadBlockData fills the given buffer with reads starting from the given register of an i2c device.
func (d *i2cDevice) ReadBlockData(reg uint8, data []byte) error {
func (d *i2cDevice) ReadBlockData(address int, reg uint8, data []byte) error {
d.mutex.Lock()
defer d.mutex.Unlock()
dataLen := len(data)
if dataLen > 32 {
return fmt.Errorf("Reading blocks larger than 32 bytes (%v) not supported", len(data))
@ -135,14 +154,14 @@ func (d *i2cDevice) ReadBlockData(reg uint8, data []byte) error {
if i2cDeviceDebug {
log.Printf("%s, use fallback\n", err.Error())
}
return d.readBlockDataFallback(reg, data)
return d.readBlockDataFallback(address, reg, data)
}
// set the first element with the data size
buf := make([]byte, dataLen+1)
buf[0] = byte(dataLen)
copy(buf[1:], data)
if err := d.smbusAccess(I2C_SMBUS_READ, reg, I2C_SMBUS_I2C_BLOCK_DATA, unsafe.Pointer(&buf[0])); err != nil {
if err := d.smbusAccess(address, I2C_SMBUS_READ, reg, I2C_SMBUS_I2C_BLOCK_DATA, unsafe.Pointer(&buf[0])); err != nil {
return err
}
// get data from buffer without first size element
@ -151,36 +170,48 @@ func (d *i2cDevice) ReadBlockData(reg uint8, data []byte) error {
}
// WriteByte writes the given byte value to the current register of an i2c device.
func (d *i2cDevice) WriteByte(val byte) error {
func (d *i2cDevice) WriteByte(address int, val byte) error {
d.mutex.Lock()
defer d.mutex.Unlock()
if err := d.queryFunctionality(I2C_FUNC_SMBUS_WRITE_BYTE, "write byte"); err != nil {
return err
}
return d.smbusAccess(I2C_SMBUS_WRITE, val, I2C_SMBUS_BYTE, nil)
return d.smbusAccess(address, I2C_SMBUS_WRITE, val, I2C_SMBUS_BYTE, nil)
}
// WriteByteData writes the given byte value to the given register of an i2c device.
func (d *i2cDevice) WriteByteData(reg uint8, val uint8) error {
func (d *i2cDevice) WriteByteData(address int, reg uint8, val uint8) error {
d.mutex.Lock()
defer d.mutex.Unlock()
if err := d.queryFunctionality(I2C_FUNC_SMBUS_WRITE_BYTE_DATA, "write byte data"); err != nil {
return err
}
var data = val
return d.smbusAccess(I2C_SMBUS_WRITE, reg, I2C_SMBUS_BYTE_DATA, unsafe.Pointer(&data))
return d.smbusAccess(address, I2C_SMBUS_WRITE, reg, I2C_SMBUS_BYTE_DATA, unsafe.Pointer(&data))
}
// WriteWordData writes the given 16 bit value starting from the given register of an i2c device.
func (d *i2cDevice) WriteWordData(reg uint8, val uint16) error {
func (d *i2cDevice) WriteWordData(address int, reg uint8, val uint16) error {
d.mutex.Lock()
defer d.mutex.Unlock()
if err := d.queryFunctionality(I2C_FUNC_SMBUS_WRITE_WORD_DATA, "write word data"); err != nil {
return err
}
var data = val
return d.smbusAccess(I2C_SMBUS_WRITE, reg, I2C_SMBUS_WORD_DATA, unsafe.Pointer(&data))
return d.smbusAccess(address, I2C_SMBUS_WRITE, reg, I2C_SMBUS_WORD_DATA, unsafe.Pointer(&data))
}
// WriteBlockData writes the given buffer starting from the given register of an i2c device.
func (d *i2cDevice) WriteBlockData(reg uint8, data []byte) error {
func (d *i2cDevice) WriteBlockData(address int, reg uint8, data []byte) error {
d.mutex.Lock()
defer d.mutex.Unlock()
dataLen := len(data)
if dataLen > 32 {
return fmt.Errorf("Writing blocks larger than 32 bytes (%v) not supported", len(data))
@ -190,7 +221,7 @@ func (d *i2cDevice) WriteBlockData(reg uint8, data []byte) error {
if i2cDeviceDebug {
log.Printf("%s, use fallback\n", err.Error())
}
return d.writeBlockDataFallback(reg, data)
return d.writeBlockDataFallback(address, reg, data)
}
// set the first element with the data size
@ -198,12 +229,56 @@ func (d *i2cDevice) WriteBlockData(reg uint8, data []byte) error {
buf[0] = byte(dataLen)
copy(buf[1:], data)
return d.smbusAccess(I2C_SMBUS_WRITE, reg, I2C_SMBUS_I2C_BLOCK_DATA, unsafe.Pointer(&buf[0]))
return d.smbusAccess(address, I2C_SMBUS_WRITE, reg, I2C_SMBUS_I2C_BLOCK_DATA, unsafe.Pointer(&buf[0]))
}
// WriteBytes writes the given buffer starting from the current register of an i2c device.
func (d *i2cDevice) WriteBytes(data []byte) error {
n, err := d.Write(data)
func (d *i2cDevice) WriteBytes(address int, data []byte) error {
d.mutex.Lock()
defer d.mutex.Unlock()
return d.writeBytes(address, data)
}
// Read implements direct I2C read operations.
func (d *i2cDevice) Read(address int, b []byte) (n int, err error) {
d.mutex.Lock()
defer d.mutex.Unlock()
return d.read(address, b)
}
// Write implements the io.ReadWriteCloser method by direct I2C write operations.
func (d *i2cDevice) Write(address int, b []byte) (n int, err error) {
d.mutex.Lock()
defer d.mutex.Unlock()
return d.write(address, b)
}
func (d *i2cDevice) readBlockDataFallback(address int, reg uint8, data []byte) error {
if err := d.writeBytes(address, []byte{reg}); err != nil {
return err
}
if err := d.readAndCheckCount(address, data); err != nil {
return err
}
return nil
}
func (d *i2cDevice) writeBlockDataFallback(address int, reg uint8, data []byte) error {
buf := make([]byte, len(data)+1)
copy(buf[1:], data)
buf[0] = reg
if err := d.writeBytes(address, buf); err != nil {
return err
}
return nil
}
func (d *i2cDevice) writeBytes(address int, data []byte) error {
n, err := d.write(address, data)
if err != nil {
return err
}
@ -213,67 +288,18 @@ func (d *i2cDevice) WriteBytes(data []byte) error {
return nil
}
// Read implements the io.ReadWriteCloser method by direct I2C read operations.
func (d *i2cDevice) Read(b []byte) (n int, err error) {
// lazy initialization
if d.file == nil {
if d.file, err = d.fs.openFile(d.location, os.O_RDWR, os.ModeExclusive); err != nil {
return 0, err
}
func (d *i2cDevice) write(address int, b []byte) (n int, err error) {
if err = d.setAddress(address); err != nil {
return 0, err
}
return d.file.Read(b)
}
// Write implements the io.ReadWriteCloser method by direct I2C write operations.
func (d *i2cDevice) Write(b []byte) (n int, err error) {
// lazy initialization
if d.file == nil {
if d.file, err = d.fs.openFile(d.location, os.O_RDWR, os.ModeExclusive); err != nil {
return 0, err
}
if err := d.openFileLazy("Write"); err != nil {
return 0, err
}
return d.file.Write(b)
}
func (d *i2cDevice) smbusAccess(readWrite byte, command byte, protocol uint32, dataStart unsafe.Pointer) error {
smbus := i2cSmbusIoctlData{
readWrite: readWrite,
command: command,
protocol: protocol,
data: dataStart, // the reflected value of unsafePointer equals uintptr(dataStart),
}
if err := d.syscallIoctl(I2C_SMBUS, unsafe.Pointer(&smbus), "SMBus access"); err != nil {
return err
}
return nil
}
func (d *i2cDevice) readBlockDataFallback(reg uint8, data []byte) error {
if err := d.WriteBytes([]byte{reg}); err != nil {
return err
}
if err := d.readAndCheckCount(data); err != nil {
return err
}
return nil
}
func (d *i2cDevice) writeBlockDataFallback(reg uint8, data []byte) error {
buf := make([]byte, len(data)+1)
copy(buf[1:], data)
buf[0] = reg
if err := d.WriteBytes(buf); err != nil {
return err
}
return nil
}
func (d *i2cDevice) readAndCheckCount(data []byte) error {
n, err := d.Read(data)
func (d *i2cDevice) readAndCheckCount(address int, data []byte) error {
n, err := d.read(address, data)
if err != nil {
return err
}
@ -283,6 +309,17 @@ func (d *i2cDevice) readAndCheckCount(data []byte) error {
return nil
}
func (d *i2cDevice) read(address int, b []byte) (n int, err error) {
if err = d.setAddress(address); err != nil {
return 0, err
}
if err := d.openFileLazy("Read"); err != nil {
return 0, err
}
return d.file.Read(b)
}
func (d *i2cDevice) queryFunctionality(requested uint64, sender string) error {
// lazy initialization
if d.funcs == 0 {
@ -298,12 +335,45 @@ func (d *i2cDevice) queryFunctionality(requested uint64, sender string) error {
return nil
}
func (d *i2cDevice) syscallIoctl(signal uintptr, payload unsafe.Pointer, sender string) (err error) {
// lazy initialization
if d.file == nil {
if d.file, err = d.fs.openFile(d.location, os.O_RDWR, os.ModeExclusive); err != nil {
return err
func (d *i2cDevice) smbusAccess(address int, readWrite byte, command byte, protocol uint32, dataStart unsafe.Pointer) error {
if err := d.setAddress(address); err != nil {
return err
}
smbus := i2cSmbusIoctlData{
readWrite: readWrite,
command: command,
protocol: protocol,
data: dataStart, // the reflected value of unsafePointer equals uintptr(dataStart),
}
sender := fmt.Sprintf("SMBus access r/w: %d, command: %d, protocol: %d, address: %d", readWrite, command, protocol, d.lastAddress)
if err := d.syscallIoctl(I2C_SMBUS, unsafe.Pointer(&smbus), sender); err != nil {
return err
}
return nil
}
// setAddress sets the address of the i2c device to use.
func (d *i2cDevice) setAddress(address int) error {
if d.lastAddress == address && !forceSetAddress {
if i2cDeviceDebug {
log.Printf("I2C address %d was already sent - skip", address)
}
return nil
}
// for go vet false positives, see: https://github.com/golang/go/issues/41205
if err := d.syscallIoctl(I2C_SLAVE, unsafe.Pointer(uintptr(byte(address))), "Setting address"); err != nil {
return err
}
d.lastAddress = address
return nil
}
func (d *i2cDevice) syscallIoctl(signal uintptr, payload unsafe.Pointer, sender string) (err error) {
if err := d.openFileLazy(sender); err != nil {
return err
}
if _, _, errno := d.sys.syscall(syscall.SYS_IOCTL, d.file, signal, payload); errno != 0 {
return fmt.Errorf("%s failed with syscall.Errno %v", sender, errno)
@ -311,3 +381,16 @@ func (d *i2cDevice) syscallIoctl(signal uintptr, payload unsafe.Pointer, sender
return nil
}
func (d *i2cDevice) openFileLazy(sender string) (err error) {
// lazy initialization
// note: "os.ModeExclusive" is undefined without create the file. This means for the existing character device,
// a second open will not return an error e.g. due to a busy resource, so most likely "os.ModeExclusive" is not really
// helpful and we drop it to the default "0" used by normal Open().
if d.file == nil {
if d.file, err = d.fs.openFile(d.location, os.O_RDWR, 0); err != nil {
return err
}
}
return nil
}

View File

@ -13,16 +13,38 @@ import (
const dev = "/dev/i2c-1"
func syscallFuncsImpl(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) {
if (trap == syscall.SYS_IOCTL) && (a2 == I2C_FUNCS) {
var funcPtr *uint64 = (*uint64)(unsafe.Pointer(a3))
*funcPtr = I2C_FUNC_SMBUS_READ_BYTE | I2C_FUNC_SMBUS_READ_BYTE_DATA |
I2C_FUNC_SMBUS_READ_WORD_DATA |
I2C_FUNC_SMBUS_WRITE_BYTE | I2C_FUNC_SMBUS_WRITE_BYTE_DATA |
I2C_FUNC_SMBUS_WRITE_WORD_DATA
func getSyscallFuncImpl(errorMask byte) func(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) {
// bit 0: error on function query
// bit 1: error on set address
// bit 2: error on command
return func(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) {
// function query
if (trap == syscall.SYS_IOCTL) && (a2 == I2C_FUNCS) {
if errorMask&0x01 == 0x01 {
return 0, 0, 1
}
var funcPtr *uint64 = (*uint64)(unsafe.Pointer(a3))
*funcPtr = I2C_FUNC_SMBUS_READ_BYTE | I2C_FUNC_SMBUS_READ_BYTE_DATA |
I2C_FUNC_SMBUS_READ_WORD_DATA |
I2C_FUNC_SMBUS_WRITE_BYTE | I2C_FUNC_SMBUS_WRITE_BYTE_DATA |
I2C_FUNC_SMBUS_WRITE_WORD_DATA
}
// set address
if (trap == syscall.SYS_IOCTL) && (a2 == I2C_SLAVE) {
if errorMask&0x02 == 0x02 {
return 0, 0, 1
}
}
// command
if (trap == syscall.SYS_IOCTL) && (a2 == I2C_SMBUS) {
if errorMask&0x04 == 0x04 {
return 0, 0, 1
}
}
// Let all operations succeed
return 0, 0, 0
}
// Let all operations succeed
return 0, 0, 0
}
func initTestI2cDeviceWithMockedSys() (*i2cDevice, *mockSyscall) {
@ -76,24 +98,14 @@ func TestClose(t *testing.T) {
gobottest.Assert(t, d.Close(), nil)
}
func TestSetAddress(t *testing.T) {
// arrange
d, msc := initTestI2cDeviceWithMockedSys()
// act
err := d.SetAddress(0xff)
// assert
gobottest.Assert(t, err, nil)
gobottest.Assert(t, msc.devAddress, uintptr(0xff))
}
func TestWriteRead(t *testing.T) {
// arrange
d, _ := initTestI2cDeviceWithMockedSys()
wbuf := []byte{0x01, 0x02, 0x03}
rbuf := make([]byte, 4)
// act
wn, werr := d.Write(wbuf)
rn, rerr := d.Read(rbuf)
wn, werr := d.Write(1, wbuf)
rn, rerr := d.Read(1, rbuf)
// assert
gobottest.Assert(t, werr, nil)
gobottest.Assert(t, rerr, nil)
@ -113,8 +125,8 @@ func TestReadByte(t *testing.T) {
},
"error_syscall": {
funcs: I2C_FUNC_SMBUS_READ_BYTE,
syscallImpl: func(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) { return 0, 0, 1 },
wantErr: "SMBus access failed with syscall.Errno operation not permitted",
syscallImpl: getSyscallFuncImpl(0x04),
wantErr: "SMBus access r/w: 1, command: 0, protocol: 1, address: 2 failed with syscall.Errno operation not permitted",
},
"error_not_supported": {
wantErr: "SMBus read byte not supported",
@ -129,7 +141,7 @@ func TestReadByte(t *testing.T) {
const want = byte(5)
msc.dataSlice = []byte{want}
// act
got, err := d.ReadByte()
got, err := d.ReadByte(2)
// assert
if tc.wantErr != "" {
gobottest.Assert(t, err.Error(), tc.wantErr)
@ -157,8 +169,8 @@ func TestReadByteData(t *testing.T) {
},
"error_syscall": {
funcs: I2C_FUNC_SMBUS_READ_BYTE_DATA,
syscallImpl: func(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) { return 0, 0, 1 },
wantErr: "SMBus access failed with syscall.Errno operation not permitted",
syscallImpl: getSyscallFuncImpl(0x04),
wantErr: "SMBus access r/w: 1, command: 1, protocol: 2, address: 3 failed with syscall.Errno operation not permitted",
},
"error_not_supported": {
wantErr: "SMBus read byte data not supported",
@ -176,7 +188,7 @@ func TestReadByteData(t *testing.T) {
)
msc.dataSlice = []byte{want}
// act
got, err := d.ReadByteData(reg)
got, err := d.ReadByteData(3, reg)
// assert
if tc.wantErr != "" {
gobottest.Assert(t, err.Error(), tc.wantErr)
@ -204,8 +216,8 @@ func TestReadWordData(t *testing.T) {
},
"error_syscall": {
funcs: I2C_FUNC_SMBUS_READ_WORD_DATA,
syscallImpl: func(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) { return 0, 0, 1 },
wantErr: "SMBus access failed with syscall.Errno operation not permitted",
syscallImpl: getSyscallFuncImpl(0x04),
wantErr: "SMBus access r/w: 1, command: 2, protocol: 3, address: 4 failed with syscall.Errno operation not permitted",
},
"error_not_supported": {
wantErr: "SMBus read word data not supported",
@ -226,7 +238,7 @@ func TestReadWordData(t *testing.T) {
// all common drivers read LSByte first
msc.dataSlice = []byte{lsbyte, msbyte}
// act
got, err := d.ReadWordData(reg)
got, err := d.ReadWordData(4, reg)
// assert
if tc.wantErr != "" {
gobottest.Assert(t, err.Error(), tc.wantErr)
@ -268,8 +280,8 @@ func TestReadBlockData(t *testing.T) {
},
"error_syscall": {
funcs: I2C_FUNC_SMBUS_READ_I2C_BLOCK,
syscallImpl: func(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) { return 0, 0, 1 },
wantErr: "SMBus access failed with syscall.Errno operation not permitted",
syscallImpl: getSyscallFuncImpl(0x04),
wantErr: "SMBus access r/w: 1, command: 3, protocol: 8, address: 5 failed with syscall.Errno operation not permitted",
},
"error_from_used_fallback_if_not_supported": {
wantErr: "Read 1 bytes from device by sysfs, expected 10",
@ -284,7 +296,7 @@ func TestReadBlockData(t *testing.T) {
msc.dataSlice = []byte{wantB0, wantB1, wantB2, wantB3, wantB4, wantB5, wantB6, wantB7, wantB8, wantB9}
buf := []byte{12, 23, 34, 45, 56, 67, 78, 89, 98, 87}
// act
err := d.ReadBlockData(reg, buf)
err := d.ReadBlockData(5, reg, buf)
// assert
if tc.wantErr != "" {
gobottest.Assert(t, err.Error(), tc.wantErr)
@ -313,8 +325,8 @@ func TestWriteByte(t *testing.T) {
},
"error_syscall": {
funcs: I2C_FUNC_SMBUS_WRITE_BYTE,
syscallImpl: func(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) { return 0, 0, 1 },
wantErr: "SMBus access failed with syscall.Errno operation not permitted",
syscallImpl: getSyscallFuncImpl(0x04),
wantErr: "SMBus access r/w: 0, command: 68, protocol: 1, address: 6 failed with syscall.Errno operation not permitted",
},
"error_not_supported": {
wantErr: "SMBus write byte not supported",
@ -328,7 +340,7 @@ func TestWriteByte(t *testing.T) {
d.funcs = tc.funcs
const val = byte(0x44)
// act
err := d.WriteByte(val)
err := d.WriteByte(6, val)
// assert
if tc.wantErr != "" {
gobottest.Assert(t, err.Error(), tc.wantErr)
@ -355,8 +367,8 @@ func TestWriteByteData(t *testing.T) {
},
"error_syscall": {
funcs: I2C_FUNC_SMBUS_WRITE_BYTE_DATA,
syscallImpl: func(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) { return 0, 0, 1 },
wantErr: "SMBus access failed with syscall.Errno operation not permitted",
syscallImpl: getSyscallFuncImpl(0x04),
wantErr: "SMBus access r/w: 0, command: 4, protocol: 2, address: 7 failed with syscall.Errno operation not permitted",
},
"error_not_supported": {
wantErr: "SMBus write byte data not supported",
@ -373,7 +385,7 @@ func TestWriteByteData(t *testing.T) {
val = byte(0x55)
)
// act
err := d.WriteByteData(reg, val)
err := d.WriteByteData(7, reg, val)
// assert
if tc.wantErr != "" {
gobottest.Assert(t, err.Error(), tc.wantErr)
@ -402,8 +414,8 @@ func TestWriteWordData(t *testing.T) {
},
"error_syscall": {
funcs: I2C_FUNC_SMBUS_WRITE_WORD_DATA,
syscallImpl: func(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) { return 0, 0, 1 },
wantErr: "SMBus access failed with syscall.Errno operation not permitted",
syscallImpl: getSyscallFuncImpl(0x04),
wantErr: "SMBus access r/w: 0, command: 5, protocol: 3, address: 8 failed with syscall.Errno operation not permitted",
},
"error_not_supported": {
wantErr: "SMBus write word data not supported",
@ -422,7 +434,7 @@ func TestWriteWordData(t *testing.T) {
wantMSByte = byte(0xD4)
)
// act
err := d.WriteWordData(reg, val)
err := d.WriteWordData(8, reg, val)
// assert
if tc.wantErr != "" {
gobottest.Assert(t, err.Error(), tc.wantErr)
@ -467,8 +479,8 @@ func TestWriteBlockData(t *testing.T) {
},
"error_syscall": {
funcs: I2C_FUNC_SMBUS_WRITE_I2C_BLOCK,
syscallImpl: func(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) { return 0, 0, 1 },
wantErr: "SMBus access failed with syscall.Errno operation not permitted",
syscallImpl: getSyscallFuncImpl(0x04),
wantErr: "SMBus access r/w: 0, command: 6, protocol: 8, address: 9 failed with syscall.Errno operation not permitted",
},
}
for name, tc := range tests {
@ -479,7 +491,7 @@ func TestWriteBlockData(t *testing.T) {
d.funcs = tc.funcs
data := []byte{b0, b1, b2, b3, b4, b5, b6, b7, b8, b9}
// act
err := d.WriteBlockData(reg, data)
err := d.WriteBlockData(9, reg, data)
// assert
if tc.wantErr != "" {
gobottest.Assert(t, err.Error(), tc.wantErr)
@ -502,12 +514,22 @@ func TestWriteBlockDataTooMuch(t *testing.T) {
// arrange
d, _ := initTestI2cDeviceWithMockedSys()
// act
err := d.WriteBlockData(0x01, make([]byte, 33))
err := d.WriteBlockData(10, 0x01, make([]byte, 33))
// assert
gobottest.Assert(t, err, errors.New("Writing blocks larger than 32 bytes (33) not supported"))
}
func Test_lazyInit(t *testing.T) {
func Test_setAddress(t *testing.T) {
// arrange
d, msc := initTestI2cDeviceWithMockedSys()
// act
err := d.setAddress(0xff)
// assert
gobottest.Assert(t, err, nil)
gobottest.Assert(t, msc.devAddress, uintptr(0xff))
}
func Test_queryFunctionality(t *testing.T) {
var tests = map[string]struct {
requested uint64
dev string
@ -519,18 +541,18 @@ func Test_lazyInit(t *testing.T) {
"ok": {
requested: I2C_FUNC_SMBUS_READ_BYTE,
dev: dev,
syscallImpl: syscallFuncsImpl,
syscallImpl: getSyscallFuncImpl(0x00),
wantFile: true,
wantFuncs: 0x7E0000,
},
"dev_null_error": {
dev: os.DevNull,
syscallImpl: syscallFuncsImpl,
syscallImpl: getSyscallFuncImpl(0x00),
wantErr: " : /dev/null: No such file.",
},
"query_funcs_error": {
dev: dev,
syscallImpl: func(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) { return 0, 0, 1 },
syscallImpl: getSyscallFuncImpl(0x01),
wantErr: "Querying functionality failed with syscall.Errno operation not permitted",
wantFile: true,
},