Isaver pool pump speed control MODBUS => Waveshare USB to RS485

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;
      }
3 Likes