Spa Electrics IRIS / RM3 wireless control

Based on reverse engineering the wireless signal for one of these devices: iRIS Lighting Controller — Spa Electrics

The transmission parameters for mine are:

Frequency: 916.02
Modulation: FSK (with terrible filtering - there’s extreme wideband noise)
Frequency separation: 155KHz
Data rate: 9.6KHz
Preamble: 32 bits (0xaa, 0xaa, 0xaa, 0xaa)

The packet from my remote looks like this:

0x2d, 0xd4, 0x69, 0x88, 0x01, 0x27, 0x03, 0xe3

And seems to be split into (mostly guessing, I have only a single remote - would love to see more captures):

0x2d, 0xd4, 0x69, 0x88 - remote identifier
0x01 - unknown, but probably light number since you can drive 2
0x27 - colour (in this case white)
0x03 - unknown
0xe3 - checksum

The checksum is not CRC, but some simple arithmetic - I’d love to get more samples from other remotes to compare. It looks like it’s just 0x100-(sum(packet)%0x100)… which is stupid for a checksum, but :person_shrugging:

The colours are:

0x27 - white
0x26 - aqua
0x21 - blue
0x22 - pink
0x23 - red
0x24 - yellow
0x25 - green
0x11 - off
0x31, 0x32, 0x33, 0x34 - presets 1-4

Using radiolib with ESP32 HAL and RF69 type radio, you can drive them like this:

script:
  - id: setup_radiolib
    then:
      - lambda: |-
          radio.reset();
          int16_t state = radio.begin(
            916.02,
            9.6,
            155.0,
            RADIOLIB_RF69_DEFAULT_RXBW,
            RADIOLIB_RF69_DEFAULT_POWER,
            0);
          state = radio.disableAES();
          state = radio.packetMode();
          state = radio.setOOK(false);
          state = radio.setOutputPower(20, true);
          state = radio.disableAddressFiltering();
          state = radio.disableSyncWordFiltering();
          state = radio.setCrcFiltering(0);

button:
  - platform: template
    name: Pool Light white
    on_press:
      - lambda: |-
          uint8_t data[] = {
            0xaa, 0xaa, 0xaa, 0xaa, 0x2d, 0xd4, 0x69, 0x88, 0x1, 0x27, 0x3, 0xe3,
          };
          radio.transmit(data, sizeof(data), 0);
          radio.standby();

(I need a lot of power to reach the pool - please turn down your power if possible)