mirror of https://github.com/arendst/Tasmota.git
Support for I2C over Serial (#22444)
This commit is contained in:
parent
a571ca1db5
commit
2fd1c0b7fb
|
@ -22,7 +22,7 @@ All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
## [14.3.0.3] 20241031
|
## [14.3.0.3] 20241031
|
||||||
### Added
|
### Added
|
||||||
- Support for I2C over Serial, preliminary stub (#22388)
|
- Support for I2C over Serial
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- ESP32 Platform from 2024.10.30 to 2024.11.30, Framework (Arduino Core) from v3.1.0.241023 to v3.1.0.241030 and IDF to 5.3.1.241024 (#22384)
|
- ESP32 Platform from 2024.10.30 to 2024.11.30, Framework (Arduino Core) from v3.1.0.241023 to v3.1.0.241030 and IDF to 5.3.1.241024 (#22384)
|
||||||
|
|
|
@ -21,80 +21,233 @@
|
||||||
|
|
||||||
#define XDRV_76 76
|
#define XDRV_76 76
|
||||||
|
|
||||||
class TwoWireSerial;
|
#ifndef I2C_SERIAL_TIMEOUT
|
||||||
|
#define I2C_SERIAL_TIMEOUT 20 // number of millisecond to wait for a return from MCU
|
||||||
|
#endif // I2C_SERIAL_TIMEOUT
|
||||||
|
|
||||||
|
#ifndef I2C_SERIAL_INIT_TIMEOUT
|
||||||
|
#define I2C_SERIAL_INIT_TIMEOUT 2000 // number of millisecond to wait for the RP2040 to start
|
||||||
|
#endif // I2C_SERIAL_INIT_TIMEOUT
|
||||||
|
|
||||||
|
#ifndef I2C_SERIAL_BAUDRATE
|
||||||
|
#define I2C_SERIAL_BAUDRATE 115200 // good number to start from
|
||||||
|
#endif // I2C_SERIAL_BAUDRATE
|
||||||
|
|
||||||
|
class TwoWireSerial; // anticipated declaration of class
|
||||||
|
|
||||||
|
// Global structure to keep the global state
|
||||||
|
// Due to packed structure, it consumes only 8 bytes so we don't use a pointer here
|
||||||
struct {
|
struct {
|
||||||
bool active = false;
|
bool active = false; // is I2C_SERIAL feature active
|
||||||
uint8_t bus = 0;
|
uint8_t bus = 0; // which I2C bus number are we virtualizing: 0 or 1
|
||||||
uint8_t tx = 0;
|
uint8_t tx = 0; // GPIO for Serial Tx
|
||||||
uint8_t rx = 0;
|
uint8_t rx = 0; // GPIO for Serial Rx
|
||||||
TwoWireSerial *WireSerial = nullptr; // replacement object for TwoWire
|
TwoWireSerial *wire_serial = nullptr; // instance of the TwoWire instance to be used instead of `Wire` or `Wire1`
|
||||||
} i2c_serial;
|
} I2C_Serial;
|
||||||
|
|
||||||
|
// The class `TwoWireSerial` implements the minimal part of `TwoWire` to be used as a replacement for `Wire` or `Wire1`
|
||||||
class TwoWireSerial : public TwoWire {
|
class TwoWireSerial : public TwoWire {
|
||||||
protected:
|
protected:
|
||||||
uint8_t tx;
|
uint8_t tx; // GPIO for Serial Tx
|
||||||
uint8_t rx;
|
uint8_t rx; // GPIO for Serial Rx
|
||||||
TasmotaSerial *serial; // serial instance to communicate with SC18IM704
|
TasmotaSerial serial; // TasmotaSerial instance to communicate with SC18IM704
|
||||||
private:
|
uint8_t rx_buffer[I2C_BUFFER_LENGTH]; // statically allocated Rx buffer - size of 128 is more than enough here
|
||||||
public:
|
uint8_t tx_buffer[I2C_BUFFER_LENGTH]; // statically allocated Tx buffer - size of 128 is more than enough here
|
||||||
TwoWireSerial(uint8_t bus_num) : tx(-1), rx(-1), serial(nullptr), TwoWire(bus_num) {
|
size_t rx_index; // offset of cursor in rx_buffer
|
||||||
AddLog(LOG_LEVEL_DEBUG_MORE, "ISR: TwoWireSerial(%i)", bus_num);
|
size_t rx_length; // length of data in rx_buffer
|
||||||
};
|
size_t tx_length; // length of data in tx_buffer
|
||||||
// ~TwoWireSerial() {};
|
bool non_stop; // if `true` used for read after write or write after write
|
||||||
|
uint8_t tx_address; // I2C address for tx_buffer
|
||||||
|
|
||||||
bool setPins(int _tx, int _rx) {
|
public:
|
||||||
if (_tx >= 0 && _rx >= 0) {
|
// Constructor
|
||||||
tx = _tx;
|
// `bus_num` is still unclear whether it's actually needed
|
||||||
rx = _rx;
|
TwoWireSerial(uint8_t bus_num, uint8_t _tx, uint8_t _rx) :
|
||||||
|
tx(_tx),
|
||||||
|
rx(_rx),
|
||||||
|
serial(rx, tx), // TasmotaSerial constructor
|
||||||
|
TwoWire(bus_num) // parent class
|
||||||
|
{};
|
||||||
|
|
||||||
|
// Destructor
|
||||||
|
~TwoWireSerial() {
|
||||||
|
// nothing to do here:
|
||||||
|
// - TasmotaSerial destructor is implcitly called after this
|
||||||
|
// - buffers are statically allocated so we don't need to free them here
|
||||||
|
};
|
||||||
|
|
||||||
|
// bool begin() override {}; -- 'final' cannot be overriden -- don't use !!!
|
||||||
|
// bool begin(uint8_t address) override{}; -- 'final' cannot be overriden -- don't use !!!
|
||||||
|
bool end() override { return true; };
|
||||||
|
|
||||||
|
// Start UART
|
||||||
|
bool beginSerial() {
|
||||||
|
// AddLog(LOG_LEVEL_DEBUG_MORE, "ICR: beginSerial tx %i rx %i", tx, rx);
|
||||||
|
if (tx >= 0 && rx >= 0) {
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32S3
|
||||||
|
pinMode(tx, OUTPUT);
|
||||||
|
digitalWrite(tx, HIGH);
|
||||||
|
sleep(1);
|
||||||
|
#endif // CONFIG_IDF_TARGET_ESP32S3
|
||||||
|
serial.begin(I2C_SERIAL_BAUDRATE);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// virtual bool begin()
|
|
||||||
// virtual bool begin(uint8_t address) override{ Serial.printf(">>>>>>> begin\n"); return true; };
|
|
||||||
// virtual bool end() = 0;
|
|
||||||
bool beginSerial() {
|
|
||||||
AddLog(LOG_LEVEL_DEBUG_MORE, "ISR: beginSerial");
|
|
||||||
if (tx >= 0 && rx >= 0) {
|
|
||||||
serial = new TasmotaSerial(tx, rx);
|
|
||||||
if (serial) {
|
|
||||||
serial->begin(115200);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual bool setClock(uint32_t freq) override {
|
virtual bool setClock(uint32_t freq) override {
|
||||||
AddLog(LOG_LEVEL_DEBUG_MORE, "ISR: setClock(%i) -- ignored", freq);
|
// Ignore
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void beginTransmission(uint8_t address) override {
|
// Internal function to read I2C_STAT internal register and get the state of the last read or write
|
||||||
AddLog(LOG_LEVEL_DEBUG_MORE, "ISR: beginTransmission(0x%02X)", address);
|
int32_t read_i2c_stat(void) {
|
||||||
};
|
serial.flush();
|
||||||
virtual uint8_t endTransmission(bool stopBit) override {
|
serial.write('R');
|
||||||
AddLog(LOG_LEVEL_DEBUG_MORE, "ISR: endTransmission(%i)", stopBit);
|
serial.write(0x0A); // I2CStat
|
||||||
return 2;
|
serial.write('P');
|
||||||
}
|
int32_t r = serial.read();
|
||||||
virtual uint8_t endTransmission(void) override {
|
uint32_t wait_until = millis() + I2C_SERIAL_TIMEOUT;
|
||||||
AddLog(LOG_LEVEL_DEBUG_MORE, "ISR: endTransmission()");
|
while (r < 0 && !TimeReached(wait_until)) {
|
||||||
return 2;
|
delay(1);
|
||||||
|
r = serial.read();
|
||||||
|
}
|
||||||
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
// not used, but redefine to avoid any accidental call
|
// unused function, but override to NOP just in case
|
||||||
virtual size_t requestFrom(uint8_t address, size_t len, bool stopBit) override { return 0; };
|
void onReceive(void (*)(int)) override {};
|
||||||
virtual size_t requestFrom(uint8_t address, size_t len) override { return 0; };
|
void onRequest(void (*)(void)) override {};
|
||||||
|
|
||||||
|
void beginTransmission(uint8_t address) override {
|
||||||
|
non_stop = false;
|
||||||
|
tx_address = address;
|
||||||
|
tx_length = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
https://www.arduino.cc/reference/en/language/functions/communication/wire/endtransmission/
|
||||||
|
endTransmission() returns:
|
||||||
|
0: success.
|
||||||
|
1: data too long to fit in transmit buffer.
|
||||||
|
2: received NACK on transmit of address.
|
||||||
|
3: received NACK on transmit of data.
|
||||||
|
4: other error.
|
||||||
|
5: timeout
|
||||||
|
*/
|
||||||
|
uint8_t endTransmission(bool stopBit) override {
|
||||||
|
// AddLog(LOG_LEVEL_DEBUG_MORE, "ICR: endTransmission txAddress=%i txBuffer=%p bufferSize=%i txLength=%i _timeOutMillis=%i stopBit=%i", txAddress, txBuffer, bufferSize, txLength, _timeOutMillis, stopBit);
|
||||||
|
serial.flush();
|
||||||
|
serial.write('S'); // Start I2C
|
||||||
|
serial.write((tx_address << 1) + 0); // Address for Write
|
||||||
|
serial.write(tx_length); // length in bytes
|
||||||
|
for (int32_t i = 0; i < tx_length; i++) {
|
||||||
|
serial.write(tx_buffer[i]);
|
||||||
|
}
|
||||||
|
if (stopBit) {
|
||||||
|
serial.write('P'); // Stop
|
||||||
|
}
|
||||||
|
// Read I2CStat
|
||||||
|
int32_t r = read_i2c_stat();
|
||||||
|
// AddLog(LOG_LEVEL_DEBUG_MORE, "ICR: endTransmission i2c_stat=%i", r);
|
||||||
|
if (r < 0) { return 4; } // fatal error
|
||||||
|
r = (r & 0x0F); // keep only 4 low bits
|
||||||
|
if (r == 0) { return 0; } // OK
|
||||||
|
if (r == 1) { return 2; } // I2C_NACK_ON_ADDRESS
|
||||||
|
if (r == 2) { return 3; } // I2C_NACK_ON_DATA
|
||||||
|
if (r == 3) { return 5; } // I2C_TIME_OUT
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
// variant
|
||||||
|
uint8_t endTransmission() override {
|
||||||
|
return endTransmission(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// override `write` to use statically allocated buffers
|
||||||
|
size_t write(uint8_t data) override {
|
||||||
|
if (tx_length >= I2C_BUFFER_LENGTH) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
tx_buffer[tx_length++] = data;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t write(const uint8_t *data, size_t quantity) override {
|
||||||
|
for (size_t i = 0; i < quantity; ++i) {
|
||||||
|
if (!write(data[i])) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return quantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
int available() override {
|
||||||
|
int result = rx_length - rx_index;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
int read() override {
|
||||||
|
int value = -1;
|
||||||
|
if (rx_index < rx_length) {
|
||||||
|
value = rx_buffer[rx_index++];
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
int peek() override {
|
||||||
|
int value = -1;
|
||||||
|
if (rx_index < rx_length) {
|
||||||
|
value = rx_buffer[rx_index];
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void flush() {
|
||||||
|
rx_index = 0;
|
||||||
|
rx_length = 0;
|
||||||
|
tx_length = 0;
|
||||||
|
rxIndex = 0;
|
||||||
|
rxLength = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual size_t requestFrom(uint8_t address, size_t len, bool stopBit) override {
|
||||||
|
if (len > I2C_BUFFER_LENGTH) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
// AddLog(LOG_LEVEL_DEBUG, "ICR: address=0x%02X read_len=%i r=%02X", address, len);
|
||||||
|
serial.flush();
|
||||||
|
serial.write('S'); // Start I2C
|
||||||
|
serial.write((address << 1) + 1); // Address for Read
|
||||||
|
serial.write(len); // length in bytes
|
||||||
|
serial.write('P'); // Stop
|
||||||
|
|
||||||
|
rx_index = 0;
|
||||||
|
rx_length = 0;
|
||||||
|
for (int32_t read_len = 0; read_len < len; read_len++) {
|
||||||
|
int32_t r = serial.read();
|
||||||
|
uint32_t wait_until = millis() + I2C_SERIAL_TIMEOUT;
|
||||||
|
while (r < 0 && !TimeReached(wait_until)) {
|
||||||
|
delay(1);
|
||||||
|
r = serial.read();
|
||||||
|
}
|
||||||
|
if (r >= 0) {
|
||||||
|
rx_buffer[rx_length++] = r;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// AddLog(LOG_LEVEL_DEBUG_MORE, "ICR: requestFrom(addr=%i, len=%i, stop=%i) returned %i bytes", address, len, stopBit, rx_length);
|
||||||
|
return rx_length;
|
||||||
|
};
|
||||||
|
// Variant
|
||||||
|
size_t requestFrom(uint8_t address, size_t size) override {
|
||||||
|
return requestFrom(address, size, true);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// return the original Wire object or the I2C Serial object
|
// return the original Wire object or the I2C Serial object
|
||||||
TwoWire & I2CSerialGetWire(TwoWire & orig_wire, uint8_t bus) {
|
TwoWire & I2CSerialGetWire(TwoWire & orig_wire, uint8_t bus) {
|
||||||
if (i2c_serial.active && i2c_serial.bus == bus) {
|
if (I2C_Serial.active && I2C_Serial.wire_serial && I2C_Serial.bus == bus) {
|
||||||
AddLog(LOG_LEVEL_DEBUG, PSTR("I2C: Bus%d %p"), bus +1, i2c_serial.WireSerial);
|
return *I2C_Serial.wire_serial;
|
||||||
return *i2c_serial.WireSerial;
|
|
||||||
} else {
|
} else {
|
||||||
return orig_wire;
|
return orig_wire;
|
||||||
}
|
}
|
||||||
|
@ -105,11 +258,11 @@ TwoWire & I2CSerialGetWire(TwoWire & orig_wire, uint8_t bus) {
|
||||||
// - configure serial bus
|
// - configure serial bus
|
||||||
// - register serial bus with Tasmota
|
// - register serial bus with Tasmota
|
||||||
void I2CSerialInit(void) {
|
void I2CSerialInit(void) {
|
||||||
i2c_serial.active = false;
|
I2C_Serial.active = false;
|
||||||
// check if I2C serial is configured on some GPIOs
|
// check if I2C serial is configured on some GPIOs
|
||||||
for (uint32_t bus = 0; bus < MAX_I2C; bus++) {
|
for (uint32_t bus = 0; bus < MAX_I2C; bus++) {
|
||||||
if (PinUsed(GPIO_I2C_SER_TX, bus) && PinUsed(GPIO_I2C_SER_RX, bus)) {
|
if (PinUsed(GPIO_I2C_SER_TX, bus) && PinUsed(GPIO_I2C_SER_RX, bus)) {
|
||||||
if (i2c_serial.active) {
|
if (I2C_Serial.active) {
|
||||||
// Error: I2C Serial was already configured on bus 0, we don't accept a second one
|
// Error: I2C Serial was already configured on bus 0, we don't accept a second one
|
||||||
AddLog(LOG_LEVEL_ERROR, "I2C: I2C serial can be configured only on 1 bus");
|
AddLog(LOG_LEVEL_ERROR, "I2C: I2C serial can be configured only on 1 bus");
|
||||||
continue;
|
continue;
|
||||||
|
@ -119,26 +272,43 @@ void I2CSerialInit(void) {
|
||||||
AddLog(LOG_LEVEL_ERROR, "I2C: I2C serial failed on bus %i because SDA/SCL already configured", bus + 1);
|
AddLog(LOG_LEVEL_ERROR, "I2C: I2C serial failed on bus %i because SDA/SCL already configured", bus + 1);
|
||||||
} else {
|
} else {
|
||||||
// all good
|
// all good
|
||||||
i2c_serial.bus = bus;
|
I2C_Serial.bus = bus;
|
||||||
i2c_serial.tx = Pin(GPIO_I2C_SER_TX, bus);
|
I2C_Serial.tx = Pin(GPIO_I2C_SER_TX, bus);
|
||||||
i2c_serial.rx = Pin(GPIO_I2C_SER_RX, bus);
|
I2C_Serial.rx = Pin(GPIO_I2C_SER_RX, bus);
|
||||||
i2c_serial.active = true;
|
I2C_Serial.active = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// configure serial bus
|
// configure serial bus
|
||||||
if (i2c_serial.active) {
|
if (I2C_Serial.active) {
|
||||||
i2c_serial.WireSerial = new TwoWireSerial(1); // TODO is it ok to use UART 1 ?
|
I2C_Serial.wire_serial = new TwoWireSerial(1, I2C_Serial.tx, I2C_Serial.rx); // TODO is it ok to use UART 1 ?
|
||||||
i2c_serial.WireSerial->setPins(i2c_serial.tx, i2c_serial.rx);
|
if (I2C_Serial.wire_serial->beginSerial()) {
|
||||||
if (i2c_serial.WireSerial->beginSerial()) {
|
TasmotaGlobal.i2c_enabled[I2C_Serial.bus] = true; // enable at Tasmota level
|
||||||
TasmotaGlobal.i2c_enabled[i2c_serial.bus] = true; // enable at Tasmota level
|
AddLog(LOG_LEVEL_INFO, "I2C: I2C serial configured on GPIO TX %i / RX %i for bus %i", I2C_Serial.tx, I2C_Serial.rx, I2C_Serial.bus + 1);
|
||||||
AddLog(LOG_LEVEL_INFO, "I2C: I2C serial configured on GPIO TX %i / RX %i for bus %i", i2c_serial.tx, i2c_serial.rx, i2c_serial.bus + 1);
|
|
||||||
} else {
|
} else {
|
||||||
delete i2c_serial.WireSerial;
|
delete I2C_Serial.wire_serial;
|
||||||
i2c_serial.active = false;
|
I2C_Serial.wire_serial = nullptr;
|
||||||
|
I2C_Serial.active = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AddLog(LOG_LEVEL_DEBUG_MORE, "I2C: I2C serial active %i, bus %i, tx %i / rx %i, wire %p", i2c_serial.active, i2c_serial.bus + 1, i2c_serial.tx, i2c_serial.rx, i2c_serial.WireSerial);
|
// reading I2C_stat to check if connection is alive
|
||||||
|
if (I2C_Serial.active) {
|
||||||
|
int32_t r = -1; // result, or -1 of nothing was received
|
||||||
|
uint32_t wait_until_init = millis() + I2C_SERIAL_INIT_TIMEOUT;
|
||||||
|
while (r < 0 && !TimeReached(wait_until_init)) {
|
||||||
|
r = I2C_Serial.wire_serial->read_i2c_stat();
|
||||||
|
delay(10); // wait for 10ms before iterating
|
||||||
|
}
|
||||||
|
if (r < 0) {
|
||||||
|
AddLog(LOG_LEVEL_INFO, "I2C: I2C serial failed to communicate with target");
|
||||||
|
delete I2C_Serial.wire_serial;
|
||||||
|
I2C_Serial.wire_serial = nullptr;
|
||||||
|
I2C_Serial.active = false;
|
||||||
|
} else {
|
||||||
|
AddLog(LOG_LEVEL_DEBUG, "I2C: I2C serial initialized");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// AddLog(LOG_LEVEL_DEBUG_MORE, "I2C: I2C serial active %i, bus %i, tx %i / rx %i, wire %p", I2C_Serial.active, I2C_Serial.bus + 1, I2C_Serial.tx, I2C_Serial.rx, I2C_Serial.wire_serial);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*********************************************************************************************\
|
/*********************************************************************************************\
|
||||||
|
@ -150,7 +320,7 @@ bool Xdrv76(uint32_t function) {
|
||||||
|
|
||||||
if (FUNC_PRE_INIT == function) {
|
if (FUNC_PRE_INIT == function) {
|
||||||
I2CSerialInit();
|
I2CSerialInit();
|
||||||
} else if (i2c_serial.active) {
|
} else if (I2C_Serial.active) {
|
||||||
switch (function) {
|
switch (function) {
|
||||||
case FUNC_ACTIVE:
|
case FUNC_ACTIVE:
|
||||||
result = true;
|
result = true;
|
||||||
|
@ -161,3 +331,171 @@ bool Xdrv76(uint32_t function) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // USE_I2C_SERIAL
|
#endif // USE_I2C_SERIAL
|
||||||
|
|
||||||
|
|
||||||
|
/********************************************************************************\
|
||||||
|
|
||||||
|
# below is an example of Micropython code for Seedstudio SenseCap
|
||||||
|
# that allows to bridge the UART on GPIO 16/17 to I2C on GPIO 20/21
|
||||||
|
|
||||||
|
from machine import Pin, I2C
|
||||||
|
from machine import Pin
|
||||||
|
from machine import UART, Pin
|
||||||
|
import time
|
||||||
|
|
||||||
|
uart = UART(0, baudrate=115200, tx=Pin(16), rx=Pin(17), timeout=30000, timeout_char=50, txbuf=128, rxbuf=128)
|
||||||
|
print(f"CFG: UART initialized")
|
||||||
|
|
||||||
|
power_i2c = Pin(18, Pin.OUT) # create output pin on GPIO0
|
||||||
|
power_i2c.on() # set pin to "on" (high) level
|
||||||
|
|
||||||
|
i2c = I2C(0, scl=Pin(21), sda=Pin(20), freq=400_000, timeout=1000)
|
||||||
|
|
||||||
|
# print(f"I2C: scan {i2c.scan()}")
|
||||||
|
|
||||||
|
# i2c_stat:
|
||||||
|
# 0: no error
|
||||||
|
# 1: I2C_NACK_ON_ADDRESS
|
||||||
|
# 2: I2C_NACK_ON_DATA
|
||||||
|
# 3: I2C_TIME_OUT
|
||||||
|
i2c_stat = 0
|
||||||
|
def set_i2c_stat(v):
|
||||||
|
global i2c_stat
|
||||||
|
i2c_stat = v
|
||||||
|
|
||||||
|
def get_i2c_stat():
|
||||||
|
global i2c_stat
|
||||||
|
return i2c_stat
|
||||||
|
|
||||||
|
|
||||||
|
def ignore_until_P():
|
||||||
|
# read uart until none left or 'P' reached
|
||||||
|
# return last unprocessed char or None
|
||||||
|
while True:
|
||||||
|
c = uart.read(1)
|
||||||
|
if c is None:
|
||||||
|
return None # end of receive
|
||||||
|
if c == b'P':
|
||||||
|
cur_char = None
|
||||||
|
return None # end reached
|
||||||
|
|
||||||
|
def process_cmd_start():
|
||||||
|
# return last unprocessed char or None
|
||||||
|
addr_b = uart.read(1)
|
||||||
|
if addr_b is None: print("start: no address sent"); return None
|
||||||
|
addr = addr_b[0] >> 1
|
||||||
|
is_write = not bool(addr_b[0] & 1)
|
||||||
|
len_b = uart.read(1)
|
||||||
|
if len_b is None: print("start: no length sent"); return None
|
||||||
|
len_i = len_b[0]
|
||||||
|
cmd_next = None
|
||||||
|
# dispatch depending on READ or WRITE
|
||||||
|
if is_write:
|
||||||
|
payload_b = bytes()
|
||||||
|
if len_i > 0:
|
||||||
|
payload_b = uart.read(len_i)
|
||||||
|
if len(payload_b) < len_i:
|
||||||
|
print(f"start: payload {payload_b} too small, expected {len_i} bytes")
|
||||||
|
return None
|
||||||
|
stop_bit = False
|
||||||
|
cmd_next = uart.read(1)
|
||||||
|
if cmd_next == b'P':
|
||||||
|
stop_bit = True
|
||||||
|
try:
|
||||||
|
set_i2c_stat(0)
|
||||||
|
acks_count = i2c.writeto(addr, payload_b, stop_bit)
|
||||||
|
#print(f"{acks_count=} {len_i=}")
|
||||||
|
if acks_count < len_i:
|
||||||
|
set_i2c_stat(2)
|
||||||
|
else:
|
||||||
|
print(f"I2C: [0x{addr:02X}] W '{payload_b.hex()}'")
|
||||||
|
#print(f"{acks_count=} {len_i=} {get_i2c_stat()=}")
|
||||||
|
except Exception as error:
|
||||||
|
#print(f"{error=}")
|
||||||
|
set_i2c_stat(1) # I2C_NACK_ON_ADDRESS
|
||||||
|
# if 'S' is followed, return to main loop
|
||||||
|
if cmd_next == b'S':
|
||||||
|
return cmd_next
|
||||||
|
else:
|
||||||
|
# read
|
||||||
|
payload_b = b''
|
||||||
|
#print(f"read: [0x{addr:02X}] {len_i}")
|
||||||
|
try:
|
||||||
|
set_i2c_stat(0)
|
||||||
|
payload_b = i2c.readfrom(addr, len_i, True)
|
||||||
|
print(f"I2C: [0x{addr:02X}] R '{payload_b.hex()}' {len(payload_b)}/{len_i}")
|
||||||
|
uart.write(payload_b)
|
||||||
|
except Exception as error:
|
||||||
|
print(f"I2C: error while reading from 0x{addr:02X} len={len_i} error '{error}'")
|
||||||
|
set_i2c_stat(1) # I2C_NACK_ON_ADDRESS
|
||||||
|
return None
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def process_cmd_stop():
|
||||||
|
# return last unprocessed char or None
|
||||||
|
return None # do nothing
|
||||||
|
|
||||||
|
def process_cmd_read():
|
||||||
|
# return last unprocessed char or None
|
||||||
|
# we accept only 1 register for now
|
||||||
|
reg = uart.read(1)
|
||||||
|
if reg is None: print("read: no register sent"); return None
|
||||||
|
cmd_next = uart.read(1)
|
||||||
|
if cmd_next is None or cmd_next != b'P': print("read: unfinished command"); return None
|
||||||
|
#
|
||||||
|
reg = reg[0] # convert to number
|
||||||
|
if reg == 0x0A: # I2CStat
|
||||||
|
uart.write(int.to_bytes(get_i2c_stat() | 0xF0))
|
||||||
|
else:
|
||||||
|
uart.write(int.to_bytes(0x00))
|
||||||
|
return None
|
||||||
|
|
||||||
|
def process_cmd_write():
|
||||||
|
# return last unprocessed char or None
|
||||||
|
print("I2C: ignore 'W' commmand")
|
||||||
|
return ignore_until_P()
|
||||||
|
|
||||||
|
def process_cmd_version():
|
||||||
|
ignore_until_P()
|
||||||
|
uart.write(b'Tasmota I2C uart bridge 1.0\x00')
|
||||||
|
return None
|
||||||
|
|
||||||
|
def process_cmd_ignore():
|
||||||
|
# return last unprocessed char or None
|
||||||
|
return ignore_until_P()
|
||||||
|
|
||||||
|
def process_discard():
|
||||||
|
# discard all bytes in input
|
||||||
|
# return last unprocessed char or None
|
||||||
|
while uart.any() > 1:
|
||||||
|
uart.read(uart.any())
|
||||||
|
return None
|
||||||
|
|
||||||
|
def run():
|
||||||
|
cmd = None
|
||||||
|
while True:
|
||||||
|
if cmd is None and uart.any() > 0:
|
||||||
|
cmd = uart.read(1)
|
||||||
|
if cmd is None:
|
||||||
|
time.sleep(0.01)
|
||||||
|
else:
|
||||||
|
#print(f"SER: received cmd {cmd}")
|
||||||
|
if cmd == b'S':
|
||||||
|
cmd = process_cmd_start()
|
||||||
|
elif cmd == b'P':
|
||||||
|
cmd = process_cmd_stop()
|
||||||
|
elif cmd == b'R':
|
||||||
|
cmd = process_cmd_read()
|
||||||
|
elif cmd == b'W':
|
||||||
|
cmd = process_cmd_write()
|
||||||
|
elif cmd == b'V':
|
||||||
|
cmd = process_cmd_version()
|
||||||
|
elif cmd == b'I' or cmd == b'O' or cmd == b'Z':
|
||||||
|
cmd = process_cmd_ignore()
|
||||||
|
else:
|
||||||
|
cmd = process_discard()
|
||||||
|
|
||||||
|
run()
|
||||||
|
|
||||||
|
\********************************************************************************/
|
||||||
|
|
Loading…
Reference in New Issue