Caninos_sdk não suporta endereço interno na I2C

Tenho os seguintes códigos no Labrador e no Arduino:

Labrador:

import caninos_sdk as k9
import time

labrador = k9.Labrador()
labrador.i2c.add_device("indefinido", 127)


def mudarEndereco(novo, nome):
	print("Mudando endereço")
	bd = bytes([138, novo])
	labrador.indefinido.write(bd)
	labrador.i2c.add_device(nome, novo)
	print("Endereço alterado")





s = time.time()
mudarEndereco(0, "sensordelinha")

while True:
	resposta = labrador.sensordelinha.read(1)
	print (str(time.time() - s)+" - "+str(resposta))
	time.sleep(0.1)

Arduino: (Parte do código foi omitida pela irrelevância)

byte address = 0x7F;
byte mode;
char *modeData;

enum regAddr{
  READ_SENSORS = 0X00,
  I2C_SLAVE_DEVICE_ADDRESS = 0x8A,
};

void readData(){
  byte len = Wire.available();
  modeData = malloc(len);
  for(int i=0; i<len; i++){
    modeData[i]=Wire.read();
  }
}

void onI2CReceive(){
  
  debugLED();

  if(!Wire.available()){
    onI2CRequest();
    return;
  }

  Serial.println("Receiving I2C data");
  Serial.print("Data length: ");
  Serial.println(Wire.available());

  byte command = Wire.read();
  Serial.print("Command: ");
  Serial.println(command);
  readData();
  
  switch(command){
    case READ_SENSORS:
      mode = command;
    return;
    
    case I2C_SLAVE_DEVICE_ADDRESS:
      address = modeData[0];
      Serial.print("Changing address to ");
      Serial.println(address);

      Wire.end();
      Wire.begin(address);

      freeData();
    return;
  }

}

A idéia é que o Arduino sirva como um dispositivo slave e o Labrador master, o Arduino tem um endereço inicial 0x7F, e receberá um comando (0x8A) do Labrador com o novo endereço dele (0). Isso significa que o Labrador envia 2 bytes para o Arduino.
Só que o Arduino recebe 3 bytes, com a seguinte linha: 0x00 0x8A 0x00
Então o Arduino imprime isso:

01:21:04.876 → Receiving I2C data
01:21:04.876 → Data length: 3
01:21:04.876 → Command: 0

Verificando nessa parte do código da caninos_sdk

    def write(self, data):
        return self.libi2c_device.write(0x0, data)

Vemos que ele envia 2 parâmetros para a libi2c_device. Que nessa linha vemos que os parâmetros são iaddr e buf.

if (!PyArg_ParseTuple(args, "Is#::write", &iaddr, &buf, &size)) {

Que manda para a write_handle, que manda para a i2c_ioctl_write

ssize_t i2c_ioctl_write(const I2CDevice *device, unsigned int iaddr, const void *buf, size_t len)
{
    ssize_t remain = len;
    size_t size = 0, cnt = 0;
    const unsigned char *buffer = buf;
    unsigned char delay = GET_I2C_DELAY(device->delay);
    unsigned short flags = GET_I2C_FLAGS(device->tenbit, device->flags);

    struct i2c_msg ioctl_msg;
    struct i2c_rdwr_ioctl_data ioctl_data;
    unsigned char tmp_buf[PAGE_MAX_BYTES + INT_ADDR_MAX_BYTES];

    while (remain > 0) {

        size = GET_WRITE_SIZE(iaddr % device->page_bytes, remain, device->page_bytes);

        /* Convert i2c internal address */
        memset(tmp_buf, 0, sizeof(tmp_buf));
        i2c_iaddr_convert(iaddr, device->iaddr_bytes, tmp_buf);

        /* Connect write data after device internal address */
        memcpy(tmp_buf + device->iaddr_bytes, buffer, size);

        /* Fill kernel ioctl i2c_msg */
        memset(&ioctl_msg, 0, sizeof(ioctl_msg));
        memset(&ioctl_data, 0, sizeof(ioctl_data));

        ioctl_msg.len	=	device->iaddr_bytes + size;
        ioctl_msg.addr	=	device->addr;
        ioctl_msg.buf	=	tmp_buf;
        ioctl_msg.flags	=	flags;

        ioctl_data.nmsgs =	1;
        ioctl_data.msgs	=	&ioctl_msg;

        if (ioctl(device->bus, I2C_RDWR, (unsigned long)&ioctl_data) == -1) {

            perror("Ioctl write i2c error:");
            return -1;
        }

        /* XXX: Must have a little time delay */
        i2c_delay(delay);

        cnt += size;
        iaddr += size;
        buffer += size;
        remain -= size;
    }

    return cnt;
}

Se prestarmos atenção nessa linha:

        /* Connect write data after device internal address */
        memcpy(tmp_buf + device->iaddr_bytes, buffer, size);

Vemos que ele junta o endereço interno com os dados. Sendo o endereço interno 0x00.
Se essa linha for o problema, significa que o problema pode não ser na caninos_sdk em si, a raíz do problema está na pylibi2c mesmo… Certo?

Certo?

ERRADO kkkkkkkkkk

Inicialmente pensei que fosse algum bug, mas na verdade, é um recurso que falta na biblioteca do caninos_sdk.
O endereço interno (iaddr) é o que identifica o endereço de registro que você quer gravar ou acessar o dado, que usamos para realizar certas funções, exatamente como o de alterar seu endereço no barramento, como o que quero implementar. A biblioteca pylibi2c nos permite selecionar o endereço interno, mas a caninos_sdk não, deixando ela sempre na 0x00.
Então quando tento indicar o endereço do registro no próprio buffer pelo caninos_sdk, o pylibi2c fala: “nonononono, você quer o endereço 0x00 e o 0x8A é só um dado”, porque ele já tem o seu jeito de indicar que é passando no parâmetro dele.

A mesma coisa funciona na função de leitura…

Solução é ou implementar um parâmetro para definir o endereço do registro no write() e no read() do caninos_sdk, e com o poder do Python, quando o parâmetro não for especificado quando chamar a função, manter 0x00 como padrão. Ou só usar o pylibi2c.

Vou fazer uma pull request em breve.

1 Curtida