I’ve managed to get this working for the Australian rebrand of the iSaver inverter:
Madimack INVERTER PLUS.
I ended up using UART directly since there are only 2 registers worth accessing: writing the RPM (3001) and reading the current RPM (2001). AFAIK the ESPHome modbus implementation is incompatible with this non-standard modbus protocol. Even when ignoring the invalid CRC being returned, the function code of 0xC3 echoed back by the inverter is interpreted as an error because of this check here: esphome/esphome/components/modbus/modbus.cpp at f2249848585e98e532ae3d71d1df0a9f483a5a5c · esphome/esphome · GitHub.
Here’s the YAML i’ve come up with. It’s far from perfect - it will block the ESP for 150ms each time it reads / writes and the code is hardcoded and duplicated, but it works well enough in this limited case. I wouldn’t run anything else on the same ESP though. It will also ensure any RPM set below 1200 turns the pump off.
# Set up UART for the TTL485 controller
uart:
tx_pin: GPIO17
rx_pin: GPIO16
baud_rate: 1200
stop_bits: 1
data_bits: 8
parity: NONE
id: modbus_uart
debug:
after:
delimiter: "\n"
output:
- platform: gpio
pin: GPIO15
id: modbus_write_enable
number:
- platform: template
name: "Pump RPM"
id: pump_rpm
min_value: 0
max_value: 2900
step: 1
update_interval: 10s
set_action:
then:
- lambda: |
id(modbus_write_enable).turn_on();
uint16_t rpm = (uint16_t)x;
if (rpm < 1200) {
rpm = 1;
}
uint8_t rpm0 = (rpm >> 8) & 0xFF;
uint8_t rpm1 = rpm & 0xFF;
uint8_t data[6] = {0xAA, 0xD0, 0x0B, 0xB9, rpm0, rpm1};
uint16_t crc = 0xFFFF;
for (int i = 0; i < 6; i++) {
crc ^= data[i];
for (int j = 0; j < 8; j++) {
if (crc & 0x0001) {
crc >>= 1;
crc ^= 0xA001;
} else {
crc >>= 1;
}
}
}
uint8_t crc0 = crc & 0xFF;
uint8_t crc1 = (crc >> 8) & 0xFF;
uint8_t packet[8] = {0xAA, 0xD0, 0x0B, 0xB9, rpm0, rpm1, crc0, crc1};
auto uart = id(modbus_uart);
uart->write_array(packet, 8);
uart->flush();
id(modbus_write_enable).turn_off();
lambda: |
id(modbus_write_enable).turn_on();
uint8_t data[6] = {0xAA, 0xC3, 0x07, 0xD1, 0x00, 0x00};
uint16_t crc = 0xFFFF;
for (int i = 0; i < 6; i++) {
crc ^= data[i];
for (int j = 0; j < 8; j++) {
if (crc & 0x0001) {
crc >>= 1;
crc ^= 0xA001;
} else {
crc >>= 1;
}
}
}
uint8_t crc0 = crc & 0xFF;
uint8_t crc1 = (crc >> 8) & 0xFF;
uint8_t packet[8] = {0xAA, 0xC3, 0x07, 0xD1, 0x00, 0x00, crc0, crc1};
auto uart = id(modbus_uart);
while(uart->available() > 0) {
uint8_t t;
uart->read_array(&t, 1);
}
uart->write_array(packet, 8);
uart->flush();
id(modbus_write_enable).turn_off();
uint32_t start_time = millis();
while (uart->available() < 8 && (millis() - start_time) < 200) {
delay(1);
}
if (uart->available() >= 7) {
uint8_t response[7];
uart->read_array(response, 7);
return (float)((uint16_t)response[5] << 8 | (uint16_t)response[6]);
} else {
return NAN;
}