Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 96 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,12 +176,45 @@ Once connected, open `http://open-firenet.local` in a browser (or use the IP sho
| Endpoint | Method | Description |
|---|---|---|
| `/api/status` | GET | JSON: wifi, ip, ssid, provisioning, mainLoop, controls |
| `/api/sensors` | GET | JSON: sensor fields from POST_SENSORS (f0 = room temp ×10) |
| `/api/sensors` | GET | JSON: stove sensors + stove controls state + ESP32 system fields (see below) |
| `/api/controls` | GET | JSON: current setpoints (`onOff`, `operatingMode`, `heatingPower`, `tempRoomTarget`) |
| `/api/controls` | POST | Set controls — body: `onOff=1; operatingMode=1; heatingPower=50; tempRoomTarget=210;` |
| `/log` | GET | Plain-text log |
| `/update` | GET/POST | OTA firmware update page |
| `/reset-wifi` | GET | Erase WiFi credentials and reboot |
| `/restart` | GET | Reboot the ESP32 |

### `/api/sensors` response fields

All fields are always present; stove and controls fields are omitted until the first poll cycle completes.

**Stove sensors** (from `POST_SENSORS`):

| Field | Description |
|---|---|
| `f0` | Room temperature ×10 — e.g. `213` = 21.3 °C |
| `f1`–`f12` | Additional stove fields (see [PROTOCOL.md](PROTOCOL.md) for the full positional map) |

**Stove controls state** (from `POST_CONTROLS`, present once the main loop has run at least one poll cycle):

| Field | Description |
|---|---|
| `stoveOnOff` | Stove on/off as reported by stove (0 or 1) |
| `stoveOpMode` | Operating mode as reported by stove (0–3) |
| `stovePower` | Heating power % as reported by stove |
| `stoveTempTarget` | Temperature setpoint ×10 as reported by stove |

**ESP32 system fields** (always present):

| Field | Description |
|---|---|
| `uptime` | Seconds since boot |
| `firmware` | Firmware version string |
| `internalTemp` | ESP32 chip temperature in °C |
| `rssi` | WiFi signal strength in dBm (when connected) |
| `mac` | WiFi MAC address (when connected) |
| `ssid` | Connected SSID (when connected) |
| `ip` | Bridge IP address (when connected) |

### Controls format
```
Expand Down Expand Up @@ -238,28 +271,89 @@ Key facts:

## Home Assistant

Example REST sensor for room temperature:
The bridge exposes a local REST API — no cloud, no custom integration needed. Use `rest` sensors and `rest_command` in `configuration.yaml`.

### Sensors

```yaml
sensor:
# Room temperature from the stove (f0 is ×10)
- 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"
device_class: temperature
scan_interval: 30

# Stove on/off state as reported by the stove
- platform: rest
resource: http://open-firenet.local/api/sensors
name: Stove state
value_template: "{{ value_json.stoveOnOff }}"
scan_interval: 30

# Current temperature setpoint (×10 → °C)
- platform: rest
resource: http://open-firenet.local/api/sensors
name: Stove temperature setpoint
value_template: "{{ (value_json.stoveTempTarget | int / 10) | round(1) }}"
unit_of_measurement: "°C"
device_class: temperature
scan_interval: 30

# Heating power %
- platform: rest
resource: http://open-firenet.local/api/sensors
name: Stove heating power
value_template: "{{ value_json.stovePower }}"
unit_of_measurement: "%"
scan_interval: 30

# Bridge WiFi signal
- platform: rest
resource: http://open-firenet.local/api/sensors
name: Open Firenet RSSI
value_template: "{{ value_json.rssi }}"
unit_of_measurement: "dBm"
device_class: signal_strength
scan_interval: 60
```

### Commands

```yaml
rest_command:
rika_start:
url: "http://open-firenet.local/api/controls"
method: POST
payload: "onOff=1; operatingMode=1; heatingPower=50; tempRoomTarget=210;"
content_type: text/plain

rika_stop:
url: "http://open-firenet.local/api/controls"
method: POST
payload: "onOff=0; operatingMode=1; heatingPower=50; tempRoomTarget=210;"
content_type: text/plain

rika_set_temp:
url: "http://open-firenet.local/api/controls"
method: POST
# tempRoomTarget is ×10 — pass 210 for 21.0 °C
payload: "onOff=1; operatingMode=2; heatingPower=50; tempRoomTarget={{ (temp | float * 10) | int }};"
content_type: text/plain
```

### Automation example

```yaml
automation:
- alias: Turn off stove at night
trigger:
- platform: time
at: "22:00:00"
action:
- service: rest_command.rika_stop
```

---
Expand Down
46 changes: 36 additions & 10 deletions open-firenet/open-firenet.ino
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#define RIKA_PID 0x819A
#define NTP_SERVER "pool.ntp.org"
#define LOG_MAX_CHARS 32768
#define FIRMWARE_VERSION "1.0.0"

Preferences prefs;
USBCDC USBSerial;
Expand Down Expand Up @@ -394,13 +395,26 @@ void handleApiSensors() {
if (i) json += ",";
json += "\"" + sensors[i].key + "\":\"" + sensors[i].val + "\"";
}
// Append values received from the stove (POST_CONTROLS parsed positionally)
// Stove controls state (POST_CONTROLS)
if (stoveOnOff >= 0) {
if (sensorCount) json += ",";
json += "\"stoveOnOff\":\"" + String(stoveOnOff) + "\"";
json += ",\"stoveOpMode\":\"" + String(stoveOpMode) + "\"";
json += ",\"stovePower\":\"" + String(stovePower) + "\"";
json += ",\"stoveTempTarget\":\"" + String(stoveTempTarget) + "\"";
json += "\"stoveOnOff\":" + String(stoveOnOff);
json += ",\"stoveOpMode\":" + String(stoveOpMode);
json += ",\"stovePower\":" + String(stovePower);
json += ",\"stoveTempTarget\":" + String(stoveTempTarget);
}
// ESP32 system sensors
bool needComma = sensorCount || stoveOnOff >= 0;
if (needComma) json += ",";
json += "\"uptime\":" + String(millis() / 1000);
json += ",\"firmware\":\"" FIRMWARE_VERSION "\"";
float intTemp = temperatureRead();
json += ",\"internalTemp\":" + String(intTemp, 1);
if (WiFi.status() == WL_CONNECTED) {
json += ",\"mac\":\"" + WiFi.macAddress() + "\"";
json += ",\"rssi\":" + String(WiFi.RSSI());
json += ",\"ssid\":\"" + wifiSsid + "\"";
json += ",\"ip\":\"" + WiFi.localIP().toString() + "\"";
}
json += "}";
server.send(200, "application/json", json);
Expand Down Expand Up @@ -747,11 +761,23 @@ function sendCmd(e){
}
function fetchAll(){
fetch('/api/sensors').then(function(r){return r.json();}).then(function(d){
var rows='<table><tr><th>'+t('sensors')+'</th><th></th></tr>';
if(d.f0!==undefined){var v0=parseInt(d.f0);rows+='<tr><td>'+t('sensor_room')+'</td><td>'+(v0>0?(v0/10).toFixed(1)+'°C':'–')+'</td></tr>';}
for(var k in d){if(k==='f0')continue;var v=parseInt(d[k]);var disp=(v>0&&v<5000)?((v/10).toFixed(1)+'°C'):(d[k]);rows+='<tr><td>'+k+'</td><td>'+disp+'</td></tr>';}
var STOVE_SKIP={'rssi':1,'ip':1,'mac':1,'ssid':1,'firmware':1,'uptime':1,'internalTemp':1,'stoveOnOff':1,'stoveOpMode':1,'stovePower':1,'stoveTempTarget':1};
var rows='<table>';
// System sensors (always shown)
var uptime=d.uptime!==undefined?d.uptime:0;
var uh=Math.floor(uptime/3600),um=Math.floor((uptime%3600)/60),us=uptime%60;
var uptimeStr=(uh?uh+'h ':'')+(um?um+'m ':'')+us+'s';
rows+='<tr><td style="color:#aaa;font-size:11px;padding-top:10px" colspan="2">ESP32</td></tr>';
if(d.internalTemp!==undefined) rows+='<tr><td>Temp. chip</td><td>'+Number(d.internalTemp).toFixed(1)+'°C</td></tr>';
if(d.rssi!==undefined) rows+='<tr><td>WiFi RSSI</td><td>'+d.rssi+' dBm</td></tr>';
if(d.uptime!==undefined) rows+='<tr><td>Uptime</td><td>'+uptimeStr+'</td></tr>';
if(d.firmware!==undefined) rows+='<tr><td>Firmware</td><td>'+d.firmware+'</td></tr>';
// Stove sensors (f0, f1... from POST_SENSORS)
var stoveRows=0;
if(d.f0!==undefined){var v0=parseInt(d.f0);rows+='<tr><td style="color:#aaa;font-size:11px;padding-top:10px" colspan="2">'+t('sensors')+'</td></tr>';rows+='<tr><td>'+t('sensor_room')+'</td><td>'+(v0>0?(v0/10).toFixed(1)+'°C':'–')+'</td></tr>';stoveRows++;}
for(var k in d){if(k==='f0'||STOVE_SKIP[k])continue;var v=parseInt(d[k]);var disp=(v>0&&v<5000&&String(d[k]).indexOf('.')<0)?((v/10).toFixed(1)+'°C'):(d[k]);rows+='<tr><td>'+k+'</td><td>'+disp+'</td></tr>';stoveRows++;}
rows+='</table>';
document.getElementById('sensors').innerHTML=Object.keys(d).length?rows:'<em style="color:#ccc;font-size:13px">'+t('waiting')+'</em>';
document.getElementById('sensors').innerHTML=rows;
}).catch(function(){});
fetch('/api/controls').then(function(r){return r.json();}).then(function(s){
if(Date.now()>ignoreCtrlUntil){
Expand All @@ -760,7 +786,7 @@ function fetchAll(){
document.getElementById('ctrl').textContent=disp;
updateDisplay();
}
});
}).catch(function(){});
if(logOpen)fetchLog();
}
function copyLog(){var txt=document.getElementById('log').textContent;var el=document.createElement('textarea');el.value=txt;document.body.appendChild(el);el.select();document.execCommand('copy');document.body.removeChild(el);}
Expand Down