Open-firenet: Local USB-CDC gateway for RIKA pellet stoves

Hi everyone,

Like many of you, I'm not a big fan of cloud-dependent appliances, especially when it comes to home heating. My RIKA pellet stove required their proprietary "Firenet 2.0" USB dongle to connect to their servers, with no official local API available.

To get around this, I spent some time reverse-engineering the official firmware and the USB communication. I've developed an open-source alternative called open-firenet.

It’s a custom firmware designed for an ESP32-S3 board. You just plug the ESP32 directly into the stove's USB port using a standard USB-A cable. The board emulates the original CDC device, tricks the stove into thinking it's the official dongle, and takes over the communication.

What it does:

  • 100% Local: No cloud account, no internet connection required. It works entirely on your LAN.
  • REST API: Exposes endpoints to get sensors (room temperature, etc.) and send controls (turn on/off, change operating modes, adjust target temperature or heating power).
  • Web Interface: Includes a lightweight web UI for manual testing and OTA firmware updates.
  • Native Provisioning: You can still use the stove's built-in screen to set up your WiFi credentials; the ESP32 catches them over the USB link.

How to integrate with Home Assistant:
For now, it works out of the box using standard rest sensors and rest_command in your configuration. Here is a quick example:


sensor:
  - platform: rest
    resource: http://open-firenet.local/api/sensors
    name: Stove room temperature
    value_template: "{{ (value_json.f0 | int / 10) | round(1) }}"
    unit_of_measurement: "°C"

rest_command:
  rika_start:
    url: "http://open-firenet.local/api/controls?cmd=onOff%3D1%3B+operatingMode%3D1%3B+heatingPower%3D50%3B+tempRoomTarget%3D210%3B"

Hardware requirements:
You need a specific ESP32-S3 development board that exposes the native USB OTG pins on a USB-A socket (non-S3 variants won't work as they lack native USB device mode support).

The project is fully open-source, licensed under AGPL-3.0, and I've also documented the wire protocol in a PROTOCOL.md file inside the repository for those interested in the technical details.

You can find the code, hardware details, and flashing instructions here: Open Firenet

Feedback, pull requests, or questions from other RIKA owners are welcome!

Update: Native Home Assistant Integration available via HACS!

Following the initial release of open-firenet, I've published a dedicated Home Assistant custom component that goes beyond the REST sensor/command approach.

What it provides:

  • A proper climate entity — on/off, target temperature, preset modes (manual / auto / comfort), heating power
  • Current room temperature displayed directly on the thermostat card (read from the stove's sensor f0)
  • Dynamic sensors for all values reported by the stove
  • A connectivity binary sensor (bridge ↔ stove link)
  • Config flow UI — no YAML needed, just enter the IP of your open-firenet bridge
  • Optimistic UI updates — the interface reflects changes immediately

Installation via HACS:

  1. HACS → ⋮ → Custom repositories
  2. Add GitHub - openfirenet/open-firenet-ha: Open Firenet — Home Assistant integration for Rika pellet stoves via the open-firenet WiFi bridge · GitHub — category: Integration
  3. Search Open Firenet → Download → Restart HA
  4. Settings → Devices & Services → Add Integration → Open Firenet

Repository: GitHub - openfirenet/open-firenet-ha: Open Firenet — Home Assistant integration for Rika pellet stoves via the open-firenet WiFi bridge · GitHub

Feedback and PRs welcome!

Hi,

Thank you very much for your work, Amazing Job!

I was just looking for a solution to replace my Firenet Dongle for my RIKA PARO. And I am just starting to play with ESP32s.
I gave your project a try, but went through quite a few obstacles.

My ESP32 S3 is a XIAO Version and not a DEV Kit. I had to set the WIFI Credentials in the .ino code, and modify flash as SET WIFI or even serial feedback weren't working for me .

After getting a local server interface, it appears that the Handshake phase is failing, The conclusion may be related to your last comment in the PROTOCOL.md : " Firenet v2 stoves (symbol_current == 2) use GET_FIRENET_STATUS / POST_FIRENET_STATUS instead of the CDC device status commands — not implemented here."

[1.8] --- OPEN FIRENET ---
[3.2] WiFi MAC: E8:3D:C1:FC:**:**
[4.9] NVS: SSID="SFR_**** id=0000000
[4.9] WiFi WPA2/WPA3 connect (background): "SFR_****"
[9.2] Bridge ready. Waiting for stove...
[10.0] --- alive loop=0 prov=0 wifi=0 wst=6 ---
[10.8] WiFi STA_CONNECTED (association OK)
[13.0] WiFi GOT_IP: 192.168.1.39
[13.0] NTP init
[13.0] mDNS: http://open-firenet.local
[13.0] HTTP server started
[13.0] WiFi connected: 192.168.1.39 RSSI=-55dBm
[20.0] --- alive loop=0 prov=0 wifi=1 wst=3 ---
[30.0] --- alive loop=0 prov=0 wifi=1 wst=3 ---
[40.0] --- alive loop=0 prov=0 wifi=1 wst=3 ---
[50.0] --- alive loop=0 prov=0 wifi=1 wst=3 ---
[60.0] --- alive loop=0 prov=0 wifi=1 wst=3 ---
[70.0] --- alive loop=0 prov=0 wifi=1 wst=3 ---
[80.0] --- alive loop=0 prov=0 wifi=1 wst=3 ---
[90.0] --- alive loop=0 prov=0 wifi=1 wst=3 ---
[100.0] --- alive loop=0 prov=0 wifi=1 wst=3 ---
[110.0] --- alive loop=0 prov=0 wifi=1 wst=3 ---
[120.0] --- alive loop=0 prov=0 wifi=1 wst=3 ---
[130.0] --- alive loop=0 prov=0 wifi=1 wst=3 ---
[140.0] --- alive loop=0 prov=0 wifi=1 wst=3 ---

My stove firmware is 2.25 - updating seems quite a pain, but if it can solve the issue.
I don’t have much experience and use AI to improve and learn.

I can issue a more complete report of the complete process I went through ,if it can help to come with a more universal solution.

Thank you again.

Hi,

Thank you for your feedback!

Regarding the issue with the XIAO ESP32-S3, the lack of serial output and the SET command not responding is almost certainly due to the USB configuration. In the Arduino IDE, make sure you have selected "USB CDC On Boot: Enabled" in the Tools menu before flashing the board.

Without this option enabled, the ESP32-S3 won't redirect the serial traffic to the USB port, which breaks both the log display and the initial setup.

Once the USB handshake is properly established and the stove recognizes the gateway, you should see the Wi-Fi symbol light up on the stove's screen and should be able to configure Wifi credentials through the stove screen.

Let me know if enabling this option fixes the provisioning!

Hi, thanks for this great project and for the new HACS integration!

I successfully flashed two ESP32-S3 boards (Arduino Nano ESP32, native USB) for two stoves: a Rika Animo Arrondo and a Rika Animo Pure. WiFi provisioning (via the stove screen), connection, and controls (on/off, target temperature via POST_CONTROLS) all work correctly on both bridges, and both are now integrated in HA via the new HACS component (climate entity + controls working fine).

However, the stove never responds to GET_SENSORS=0; — no POST_SENSORS frame is ever received, even after letting the bridge run for a long time (checked via the /log endpoint and raw serial). Room temperature stays empty in HA.

I noticed this note in PROTOCOL.md:

“Firenet v2 stoves (symbol_current == 2) use GET_FIRENET_STATUS / POST_FIRENET_STATUS instead of the CDC device status commands — not implemented here.”

Could my Animo stoves be using this v2 variant? Happy to help gather protocol data (enabling a debug flag, capturing raw USB traffic, etc.) if that helps add support for it.

Here’s a representative excerpt of one poll cycle, showing GET_SENSORS sent with no stove response, while POST_CONTROLS echoes back normally in the same cycle:

[7395.8] <<< GET_NETWORKS_FINISHED
[7396.0] >>> GET_CONTROLS=1; onOff=12201; operatingMode=1; heatingPower=2; tempRoomTarget=50; / =180;
[7396.1] >>> GET_REVISION=0; revision=12201; frequency=30; 
[7396.1] >>> GET_SENSORS=0; 
[7396.2] <<< POST_CONTROLS=0; onOff=12201
[7396.2] <<< =180
[7396.2] --- Stove controls: onOff=-1 opMode=-1 power=-1 target=180 (=18.0°C)

No POST_SENSORS line ever appears in the log, on either of the two Animo units, across dozens of poll cycles. Let me know if there’s anything else I can capture to help debug this — happy to test a patch if you point me in the right direction.

Hi,

I was looking for a local solution for months, really happy to run that search again today !
I’ve look inside the protocol.md file but could not see any mention of multair air fan fields. I have two of those on my stove and need to control them (both state and value) to replace my stick. Did you know if adding controls for them would be possible ? Can I help in some way to get them supported ?

My ESP32 S3 is a XIAO Version and not a DEV Kit. I had to set the WIFI Credentials in the .ino code, and modify flash as SET WIFI or even serial feedback weren’t working for me .

I had to do the same thing first, then I found out that USB Mode need to be set to USB-OTG (TinyUSB).

Thanks !

Re: loop=0 forever (same symptom as bigbrother222, firmware 2.25)**

Hi, following up on bigbrother222’s report above — I’m seeing the exact same symptom on a RIKA stove, firmware 2.27**, so this doesn’t seem isolated to one firmware version. Sharing what I’ve dug up in case it’s useful, and happy to share raw logs/firmware if it helps.

Setup: ESP32-S3 (generic N16R8 dev board), official open-firenet.ino from this repo, ESP32 Arduino core 3.3.11, USB Mode: USB-OTG (TinyUSB), USB CDC On Boot: Enabled. WiFi/bridge/web UI/REST API all confirmed fully functional (/api/status responds correctly). The problem is specifically the USB-CDC handshake with the stove never completing — mainLoopActive stays false forever, stove shows error UW29.

Byte-level finding: what the log shows as <<< 3 on reset is actually two raw bytes, not one — 0x16 (SYN) immediately followed by ASCII '3' (0x33), no separator. This repeats every ~68-110ms (varies with our reply latency — see below) indefinitely. I don’t have visibility into whether this is meaningful protocol data or a lower-level USB polling artifact.

Transport-level checks (ruled out silent packet loss):

  • tud_cdc_n_connected(0) (DTR) is 0 throughout — but tud_cdc_n_write_available(0) recovers to full (64) after every write, meaning our bytes ARE being physically transmitted and accepted at the USB transport level regardless of DTR state.
  • Retry cadence is reactive to our reply speed: with a 50ms artificial delay before replying, the stove’s retry interval was ~109-114ms; with no delay, it dropped to ~68-70ms (removing 50ms delay matches the ~40ms cadence drop, accounting for processing overhead). This confirms the stove is processing something from us, not running on a fully independent timer.

Six reply-content strategies tested, all with identical (failing) results:
0. Plain GET_CDCDEVICE3_VERSION=0; BL=999; APP=201; REV=12201; DT=3; + \n (baseline, current repo behavior)

  1. Same + \r\n instead of \n
  2. Echo raw SYN byte (0x16) alone before the version string
  3. Echo SYN + ASCII ACK (0x16 0x06) before the version string
  4. Echo back exactly the 2 bytes received, before the version string
  5. Withhold the version string entirely, reply with only a single ACK byte (0x06)

All six produce byte-for-byte identical stove behavior — same 16 33 probe repeating at a cadence that only depends on our reply speed, never on content. This strongly suggests the blocker isn’t in what we send back at this stage, but something else (maybe an earlier init step we’re missing, or something specific to firmware ≥2.25/2.27 that diverges from whatever stove/firmware this was originally reverse-engineered against).

Given bigbrother222 is on 2.25 and I’m on 2.27, this doesn’t look like a narrow one-version edge case — might be worth flagging as affecting a broader firmware range than initially assumed.

Planning to attempt a logic-analyzer capture (D+/D- tap) of an official Firenet dongle talking to the same stove if I can get hold of one, to see the actual expected exchange. Will post results here if I get a usable capture. In the meantime, happy to share the full firmware diff or raw logs if that’s useful for anyone else hitting this.

Hello guys , i just got it working with all the sensor Datas , this is what i get 188540 RX 50 4F 53 54 5F 53 45 4E 53 4F 52 53 3D 30 3B 20 0A 3D 30 3B 20 0A 3D 32 ... 208 B POST_SENSORS=0; .=0; .=21; .=0; .=0; .=0; .=0; .=1; .=0; .=0; .=0; .=0; .=0; .=0; .=50; .=50; .=1; .=0; .=0; .=1; .=0; .=1; .=1; .=1; .=1; .=0; .=1; .=1; .=33; .=70; .=70; .=70; .=1; .=2; .=0; .=0; .=1; .=30; 188540 RX 20 0A 3D 31 3B 20 0A 3D 32 32 39 3B 20 0A 3D 32 32 39 3B 20 0A 3D 32 30 ... 132 B .=1; .=229; .=229; .=201; .=160; .=160; .=999; .=58512; .=53502; .=12201; .=1582; .=41939; .=2025; .=-152; .=505; .=10412; .=1572;

Stove i have is the Rika Paro with version 2.29

Not all Values Are checked because i dont want to start the Stove at summer :smiley:

Pos Feld Wert
f0 inputRoomTemperature ( Shifted value this is Flame Temperature ) 21
f1 inputFlameTemperature( Shifted value i dont know why ) 0
f2 statusError 0
f3 statusSubError 0
f4 statusWarning 0
f5 statusService 1
f6 outputDischargeMotor 0
f7 outputDischargeCurrent 0
f8 outputIDFan( Working Value ) 0
f9 outputIDFanTarget( Working Value ) 0
f10 outputInsertionMotor 0
f11 outputInsertionCurrent 0
f12 outputAirFlaps ( Working Value ) 50
f13 outputAirFlapsTargetPosition( Working Value ) 50
f14 outputBurnBackFlapMagnet 1
f15 outputGridMotor 0
f16 outputIgnition 0
f17 inputUpperTemperatureLimiter 1
f18 inputPressureSwitch 0
f19 inputPressureSensor 1
f20 inputGridContact 1
f21 inputDoor 1
f22 inputCover 1
f23 inputExternalRequest 0
f24 inputBurnBackFlapSwitch 1
f25 inputFlueGasFlapSwitch 1
f26 inputBoardTemperature 33
f27 inputCurrentStage 70
f28 inputTargetStagePID 70
f29 inputCurrentStagePID 70
f30 statusMainState 1
f31 statusSubState 2
f32 statusWifiStrength 0
f33 parameterEcoModePossible 0
f34 parameterFabricationNumber 1
f35 parameterStoveTypeNumber 30
f36 parameterLanguageNumber 1
f37 parameterVersionMainBoard 229
f38 parameterVersionTFT 229
f39 parameterVersionWiFi 201
f40 parameterVersionMainBoardBootLoader 160
f41 parameterVersionTFTBootLoader 160
f42 parameterVersionWiFiBootLoader 999
f43 parameterVersionMainBoardSub 58512
f44 parameterVersionTFTSub 53502
f45 parameterVersionWiFiSub 12201
f46 parameterRuntimePellets( Correct value ) 1582
f47 parameterRuntimeLogs( The value / 60) Stove display 698 41939
f48 parameterFeedRateTotal(PelletUsageKG Correct Value) 2025
f49 parameterFeedRateService(PelletusageuntilCleaning Correct value) -152
f50 parameterServiceCountdownKg 505
f51 parameterServiceCountdownTime 10412

Hello @Grassin,
I happen to have an Arduino Nano ESP32 and a Rika Animo Arrondo at home.
I just did a quick test and it was working fine on my side:

[1340.7] >>> GET_SENSORS=0; 
[1340.8] RAW: [POST_CONTROLS=0; onOff=12201; ]
[1340.8] <<< POST_CONTROLS=0; onOff=12201
[1340.8] --- CONTROLS: POST_CONTROLS=0; onOff=12201
[1340.8] RAW: [=180; ]
[1340.8] <<< =180
[1340.8] --- Stove controls: onOff=-1 opMode=-1 power=-1 target=180 (=18.0°C)
[1342.8] RAW: [POST_SENSORS=0; =254; =18; =0; =0; =0; =0; =0; =0; =0; =0; =0; =0; =0; =0; =0; =0; =0; =0; =1; =0; =2; =1; =1; =1; =1; =1; =1; =32; =70; =70; =70; =1; =0; =0; =0; =2; =8; =3; =229; =229; =201; =160; =160; =999; =58512; =53404; =12201; =1392; =0; =1930; =423; =277; =0; =627; =24; =4; =0; =7; =92; =0; =0; =0; =0; =0; =0; =0; =0; =0; =0; =0; =0; =0; =0; =0; =0; =0; =0; =1; =0; =0; =0; =360; =700; =0; =0; =0; =0; =0; ]
[1342.8] <<< POST_SENSORS=0; =254; =18; =0; =0; =0; =0; =0; =0; =0; =0; =0; =0; =0; =0; =0; =0; =0; =0; =1; =0; =2; =1; =1; =1; =1; =1; =1; =32; =70; =70; =70; =1; =0; =0; =0; =2; =8; =3; =229; =229; =201; =160; =160; =999; =58512; =53404; =12201; =1392; =0; =1930; =423; =277; =0; =627; =24; =4; =0; =7; =92; =0; =0; =0; =0; =0; =0; =0; =0; =0; =0; =0; =0; =0; =0; =0; =0; =0; =0; =1; =0; =0; =0; =360; =700; =0; =0; =0; =0; =0
[1342.8] --- SENSORS: f0=254; f1=18; f2=0; f3=0; f4=0; f5=0; f6=0; f7=0; f8=0; f9=0; f10=0; f11=0; f12=0; f13=0; f14=0; f15=0; f16=0; f17=0; f18=1; f19=0; f20=2; f21=1; f22=1; f23=1; f24=1; f25=1; f26=1; f27=32; f28=70; f29=70; f30=70; f31=1;

I have the following firmware on my stove: V2.29

RIKA firmware 2.28 – USB CDC handshake stops at 0x33 when emulating FireNet 2.0 with ESP32-S3

Hi everyone,

I am currently trying to replace the original RIKA FireNet 2.0 USB-WiFi stick with an ESP32-S3 using its native USB CDC interface.

The ESP32-S3 is programmed with Arduino and is configured to emulate the USB device as closely as possible:

  • VID: 0x303A
  • PID: 0x819A
  • USB product name: RIKA FireNet 2.0 USB-WiFi Stick
  • Native USB CDC / TinyUSB
  • WiFi, web server and mDNS are running on the ESP32 at the same time

I am using the Open-FireNet project and its documented protocol as the starting point:

openfirenet/open-firenet on GitHub

My RIKA stove is running firmware 2.28.

What works

The USB connection itself works.

The RIKA sends:

0x16

The ESP32 responds with the exact Open-FireNet version string:

GET_CDCDEVICE3_VERSION=0; BL=999; APP=201; REV=12201; DT=3;\n

The ESP32 confirms that all 60 bytes are successfully written to the USB CDC endpoint.

For example:

RX 0x16
*** 0x16 DETECTED ***
*** WAITING 50 ms ***
*** SENDING GET_CDCDEVICE3_VERSION ***
TX: GET_CDCDEVICE3_VERSION=0; BL=999; APP=201; REV=12201; DT=3;
USB TX RESULT=60/60 bytes
*** VERSION RESPONSE COMPLETE ***

So the basic USB CDC communication is definitely working.

The problem

Immediately after the version response, the RIKA sends:

0x33

ASCII '3'.

Then, about 16–17 ms later, it sends another:

0x16

The ESP32 again sends the version string, and the same sequence repeats indefinitely.

A typical sequence is:

RIKA                    ESP32-S3
 |                         |
 |-------- 0x16 ---------->|
 |                         |
 |<------- VERSION --------|
 |                         |
 |-------- 0x33 ---------->|
 |                         |
 |        ~16 ms           |
 |                         |
 |-------- 0x16 ---------->|
 |                         |
 |<------- VERSION --------|
 |                         |
 |-------- 0x33 ---------->|
 |                         |
 |          ...

The timing is surprisingly consistent:

0x16 → version response:       ~50 ms intentional delay
version → 0x33:                ~76–77 ms
0x33 → next 0x16:              ~16–17 ms

This repeats continuously.

The expected:

GET_CDCDEVICE_VERSION_FINISHED

is never received.

Things I have already tested

1. No response from the ESP32

I first tested what happens if the ESP32 does not respond to the RIKA at all.

The RIKA still sends:

0x16
0x33

So 0x33 is not caused by an ESP32 response.

2. Different response timing

I also tried changing the point at which the version response is sent, including waiting for the 0x16 / 0x33 sequence.

No change.

3. Echoing / responding to 0x33

I tried different responses to 0x33, including echoing it.

This did not advance the handshake.

4. Binary data capture

At one point I observed a sequence beginning with:

02 30 02 02 02 02 ...

and eventually ending with:

03

I therefore added a binary frame capture to the ESP32 firmware.

However, with the reproducible 0x16 → VERSION → 0x33 → 0x16 loop, this binary sequence does not appear consistently.

So I do not want to assume yet that 0x02 ... 0x03 is actually a protocol frame.

5. ACK 0x06

I am currently testing the hypothesis that 0x33 might require an ACK (0x06).

However, my first implementation of that test accidentally changed the working USB initialization, so that test is not considered valid yet. I will repeat it using the exact same USB code as the working implementation and only add:

0x33 → 0x06

An interesting firmware-version clue

There are reports from the Open-FireNet / Home Assistant community of very similar handshake problems with RIKA firmware versions around 2.25 / 2.27.

Interestingly, other users have reported successful communication with RIKA devices running firmware 2.29.

This makes me wonder whether there was a protocol or initialization change somewhere between these firmware versions.

My device is specifically running 2.28, so I am very interested in any information about changes introduced in this firmware generation.

Community discussion:

Open-FireNet USB CDC Gateway discussion on Home Assistant Community

What I am trying to understand

At this point I am mainly looking for information about what the RIKA firmware expects after:

GET_CDCDEVICE3_VERSION

Specifically:

  1. What exactly does 0x33 mean in the RIKA USB protocol?
  2. Is 0x33 an ACK/request/status byte, or something else?
  3. Is there an additional response expected from the FireNet stick?
  4. Does firmware 2.28 use a different initialization sequence than older Open-FireNet implementations?
  5. Are there known protocol differences between RIKA firmware 2.27, 2.28 and 2.29?
  6. Does the FireNet 2.0 protocol use GET_FIRENET_STATUS instead of the older GET_CDCDEVICE_STATUS sequence on these firmware versions?
  7. Could the missing part be at the USB CDC control level rather than the CDC data endpoint?

In particular, I am wondering whether we are missing something such as:

  • DTR / RTS state
  • SET_CONTROL_LINE_STATE
  • CDC line coding
  • USB control requests
  • endpoint state
  • a firmware-specific initialization command

rather than simply missing another ASCII response.

Current ESP32 implementation

The ESP32 is using native USB CDC/TinyUSB, with the actual transmission performed using:

tud_cdc_n_write_available(0);
tud_cdc_n_write(0, ...);
tud_cdc_n_write_flush(0);

The version response is exactly 60 bytes and the USB write reports:

60/60 bytes

The ESP32 also runs a normal WiFi connection, web server and mDNS service, so the USB implementation needs to coexist with those functions.

Why I am posting here

I have reached the point where simply trying different responses to 0x33 does not seem like the best approach anymore.

The behavior is extremely deterministic, which makes me suspect that there is a specific protocol or USB-state transition missing.

If anyone has:

  • a RIKA firmware 2.28 device,
  • a working FireNet 2.0 implementation,
  • USB captures from an original RIKA FireNet stick,
  • information about firmware 2.28 protocol changes,
  • or experience with the 2.27 → 2.28 → 2.29 transition,

I would really appreciate any pointers.

Even a raw USB capture of the original FireNet stick during the initial handshake would be extremely helpful.

Thanks!