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)

2 Likes

I am about to give this a go. That remote doesn’t seem to be terribly intuitive, or at least mine isn’t. How far was your equipment from your ESP, I am a minmum of 8-10 meters

I went one step further and ended up building an MQTT integration for my IRIS Pool Lighting:

In reverse engineering my remote with SDR, I can confirm that my observations were mostly the same as @viraptor’s, noting though that the trailing 2 bytes for my remote identifier were instead 0x2D, 0x21, and that the checksum was reproducible by subtracting the “command” byte from 0xAD.

From what I could tell the 0x01 byte after the remote ID is related to the remote’s mode button, of which there are three modes according to the doc found here: https://static1.squarespace.com/static/57871ee2f7e0ab31aff6d617/t/68af95804fe441061f7bc6bd/1756337536456/RM-3H+iRIS+HANDSET_INSTALL+MANUAL_20250827_Digital.pdf, that said I did not bother capturing / confirming this as I only have the pool lights myself.

With this now in place I am finally using my pool lights, switching them on after sunset each night, along with the garden lights around the pool :slight_smile: