DIY Integrations
How to feed rbamp readings into popular self-hosted automation
systems. For each platform there is a minimal working ESP-IDF project
and the matching configuration on the platform side.
Cloud / commercial integrations (AWS IoT, Azure, GCP, InfluxDB Cloud) are in 08 · Cloud integrations.
| Platform | Transport | Auto-discovery | IDF components |
|---|---|---|---|
| Home Assistant | MQTT | yes (HA MQTT Discovery) | espressif/esp_mqtt_client |
| ESPHome | Native API + MQTT | yes (YAML config) | (an alternative framework — see below) |
| Node-RED | MQTT (or HTTP) | manual flow | espressif/esp_mqtt_client |
| OpenHAB | MQTT (or REST) | manual .things |
espressif/esp_mqtt_client |
| Domoticz | MQTT (auto) or HTTP | yes (MQTT plugin) | esp_mqtt_client or esp_http_client |
| InfluxDB OSS + Grafana | HTTPS line-protocol | no | esp_http_client |
Ready-made projects for the two main scenarios are in
examples/:examples/mqtt_publisher/(a per-channel MQTT publisher) andexamples/ha_discovery/(HA Auto-discovery on top of mqtt_publisher).
Home Assistant — MQTT Auto-discovery
HA MQTT Discovery automatically creates the device and sensors when the ESP32 publishes config topics. No YAML edits in HA are needed.
ESP-IDF project (rbamp + WiFi STA + esp_mqtt_client)
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/i2c_master.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_wifi.h"
#include "mqtt_client.h"
#include "nvs_flash.h"
#include "rbamp.h"
#define DEVICE_ID "rbamp_main"
#define DEVICE_NAME "Mains rbAmp"
#define MQTT_URI "mqtt://192.168.1.10:1883"
static const char *TAG = "ha_discovery";
static esp_mqtt_client_handle_t mqtt;
static void publish_discovery_sensor(const char *key, const char *friendly,
const char *unit, const char *device_class,
const char *state_class) {
char topic[128], payload[512];
snprintf(topic, sizeof(topic),
"homeassistant/sensor/%s/%s/config", DEVICE_ID, key);
int n = snprintf(payload, sizeof(payload),
"{"
"\"name\":\"%s %s\","
"\"unique_id\":\"%s_%s\","
"\"state_topic\":\"rbamp/%s/state\","
"\"value_template\":\"{{ value_json.%s }}\","
"\"state_class\":\"%s\","
"\"device\":{"
"\"identifiers\":[\"%s\"],"
"\"name\":\"%s\","
"\"manufacturer\":\"rbAmp\","
"\"model\":\"UI*\""
"}",
DEVICE_NAME, friendly,
DEVICE_ID, key,
DEVICE_ID, key, state_class,
DEVICE_ID, DEVICE_NAME);
if (unit) n += snprintf(payload+n, sizeof(payload)-n,
",\"unit_of_measurement\":\"%s\"", unit);
if (device_class) n += snprintf(payload+n, sizeof(payload)-n,
",\"device_class\":\"%s\"", device_class);
snprintf(payload+n, sizeof(payload)-n, "}");
esp_mqtt_client_publish(mqtt, topic, payload, 0, /*qos*/1, /*retain*/1);
}
static void publish_discovery_all(void) {
publish_discovery_sensor("voltage", "Voltage", "V", "voltage", "measurement");
publish_discovery_sensor("current", "Current", "A", "current", "measurement");
publish_discovery_sensor("power", "Power", "W", "power", "measurement");
publish_discovery_sensor("energy", "Energy", "Wh", "energy", "total_increasing");
publish_discovery_sensor("frequency", "Frequency", "Hz", "frequency", "measurement");
publish_discovery_sensor("power_factor", "Power Factor", NULL, "power_factor", "measurement");
}
static void mqtt_event_handler(void *handler_args, esp_event_base_t base,
int32_t event_id, void *event_data) {
if (event_id == MQTT_EVENT_CONNECTED) {
publish_discovery_all(); /* discovery publication on every connect */
}
}
void app_main(void) {
/* ...nvs_flash_init + esp_netif_init + esp_event_loop_create_default +
* esp_wifi_init/start + waiting for IP_EVENT_STA_GOT_IP... */
/* (the pattern from 10 · Troubleshooting, section "Watchdog timeout on WiFi") */
/* I²C bus + rbAmp handle */
i2c_master_bus_handle_t bus = NULL;
/* ...i2c_new_master_bus(...)... */
rbamp_handle_t dev = NULL;
ESP_ERROR_CHECK(rbamp_new(bus, 0x50, &dev)); /* new — never fails
* with valid arguments */
/* rbamp_begin may return NACK / VERSION on bus bounce or on a
* cold module boot. A soft retry is better than an ESP_ERROR_CHECK abort —
* in production the network stack must survive a temporary loss of the slave. */
while (rbamp_begin(dev) != ESP_OK) {
ESP_LOGE(TAG, "rbamp_begin: %s — retry in 1 s",
rbamp_err_to_str(rbamp_last_error(dev)));
vTaskDelay(pdMS_TO_TICKS(1000));
}
/* set_sensor_class + set_ct_model — both block for ~700 ms
* (a CMD_SAVE_GAINS flash write). Log failures, but do not abort —
* on v1.0/v1.1 firmware set_sensor_class is effectively a no-op
* (the register exists but the firmware does not use it), on v1.2+
* it is a mandatory prerequisite for set_ct_model. */
esp_err_t err;
if ((err = rbamp_set_sensor_class(dev, RBAMP_SENSOR_SCT013)) != ESP_OK) {
ESP_LOGE(TAG, "set_sensor_class: %s", rbamp_err_to_str(err));
}
if ((err = rbamp_set_ct_model(dev, 3)) != ESP_OK) {
ESP_LOGE(TAG, "set_ct_model: %s", rbamp_err_to_str(err));
}
/* MQTT client */
const esp_mqtt_client_config_t mqtt_cfg = {
.broker.address.uri = MQTT_URI,
.credentials.client_id = DEVICE_ID,
.session.keepalive = 60,
.buffer.size = 1024, /* enough for one discovery payload */
};
mqtt = esp_mqtt_client_init(&mqtt_cfg);
esp_mqtt_client_register_event(mqtt, MQTT_EVENT_CONNECTED,
mqtt_event_handler, NULL);
esp_mqtt_client_start(mqtt);
/* State-publish loop — once a minute */
while (1) {
vTaskDelay(pdMS_TO_TICKS(60000));
rbamp_period_snapshot_t snap;
if (rbamp_read_period_snapshot(dev, &snap, 50, false) != ESP_OK ||
!snap.valid) continue;
float u, i, pf, freq;
rbamp_read_voltage(dev, 0, &u);
rbamp_read_current(dev, 0, &i);
rbamp_read_power_factor(dev, 0, &pf);
rbamp_read_frequency(dev, &freq);
char state[384];
snprintf(state, sizeof(state),
"{\"voltage\":%.1f,\"current\":%.3f,\"power\":%.1f,"
"\"energy\":%.3f,\"frequency\":%.1f,\"power_factor\":%.3f}",
(double)u, (double)i, (double)snap.avg_p[0],
rbamp_energy_wh(dev, 0), (double)freq, (double)pf);
char topic[64];
snprintf(topic, sizeof(topic), "rbamp/%s/state", DEVICE_ID);
esp_mqtt_client_publish(mqtt, topic, state, 0, /*qos*/0, /*retain*/0);
}
}The full project is at examples/ha_discovery/.
The result in HA
A few seconds after the first publication, HA automatically creates a
"Mains rbAmp" device with 6 sensors (Voltage, Current, Power, Energy,
Frequency, Power Factor). The Energy sensor has
state_class: total_increasing and the correct device_class — the
HA Energy Dashboard accepts it as a consumption source.
To remove the device from HA later, publish an empty payload to
homeassistant/sensor/.../config (the retained flag clears the record).
Multi-channel UI3
Repeat the publish_discovery_sensor() calls with suffixed keys for
channels 1 and 2:
publish_discovery_sensor("current_1", "Current 1", "A", "current", "measurement");
publish_discovery_sensor("power_1", "Power 1", "W", "power", "measurement");
publish_discovery_sensor("energy_1", "Energy 1", "Wh", "energy", "total_increasing");
/* ...the same for _2 */And extend the state JSON with the "current_1", "power_1",
"energy_1" fields, populated from rbamp_read_current(dev, 1, &i) /
snap.avg_p[1] / rbamp_energy_wh(dev, 1).
ESPHome — an alternative via external_components
If you use ESPHome (rather than bare ESP-IDF), there is a dedicated
rbamp external component that replaces this component entirely and
does its own HA integration through the ESPHome native API:
external_components:
- source: github://rbamp/rbamp-esphome
components: [rbamp]
refresh: 0s
i2c:
sda: 21
scl: 22
frequency: 50kHz # see 10 · Troubleshooting about baseline mitigation
sensor:
- platform: rbamp
address: 0x50
update_interval: 60s
voltage:
name: "rbAmp Voltage"
current:
name: "rbAmp Current"
power:
name: "rbAmp Power"
energy:
name: "rbAmp Energy"
frequency:
name: "rbAmp Frequency"
power_factor:
name: "rbAmp Power Factor"ESPHome integrates natively with HA through the ESPHome API (not MQTT) — the device appears in HA automatically after flashing.
The rbamp-esphome component lives in a separate repository — see the
main rbAmp index for links. Use
ESPHome instead of this component; mixing ESPHome and bare ESP-IDF
in one project is usually not worthwhile.
Node-RED
Subscribe to the ESP32's MQTT topic in a flow:
[
{
"id": "rbamp_in",
"type": "mqtt in",
"topic": "rbamp/main/state",
"qos": "0",
"datatype": "json"
},
{
"id": "rbamp_chart",
"type": "ui_chart",
"label": "Mains Power",
"chartType": "line",
"ymin": "0",
"ymax": "5000"
},
{
"id": "extract_power",
"type": "function",
"func": "msg.payload = msg.payload.power; return msg;"
}
]Connect rbamp_in → extract_power → rbamp_chart and you get a
real-time power chart. Do the same for energy / voltage / PF.
If Node-RED runs on the same Pi as the MQTT broker, set the host to
localhost. For remote brokers, use 192.168.X.Y:1883 plus
credentials if the broker requires auth.
The ESP32 side is the same sketch as in the Home Assistant section above; the JSON payload is shared and only the consumer differs.
OpenHAB
OpenHAB 4.x + the MQTT binding:
# things/rbamp.things
Bridge mqtt:broker:local "MQTT Broker" [ host="192.168.1.10", port=1883 ] {
Thing topic rbamp_main "rbAmp Main" {
Channels:
Type number : voltage "Voltage" [ stateTopic="rbamp/main/state", transformationPattern="JSONPATH:$.voltage" ]
Type number : current "Current" [ stateTopic="rbamp/main/state", transformationPattern="JSONPATH:$.current" ]
Type number : power "Power" [ stateTopic="rbamp/main/state", transformationPattern="JSONPATH:$.power" ]
Type number : energy "Energy" [ stateTopic="rbamp/main/state", transformationPattern="JSONPATH:$.energy" ]
}
}# items/rbamp.items
Number:ElectricPotential rbAmp_Voltage "Voltage [%.1f V]" <energy> { channel="mqtt:topic:local:rbamp_main:voltage" }
Number:ElectricCurrent rbAmp_Current "Current [%.3f A]" <energy> { channel="mqtt:topic:local:rbamp_main:current" }
Number:Power rbAmp_Power "Power [%.1f W]" <energy> { channel="mqtt:topic:local:rbamp_main:power" }
Number:Energy rbAmp_Energy "Energy [%.3f Wh]" <energy> { channel="mqtt:topic:local:rbamp_main:energy" }The ESP32 side is the same project as in the Home Assistant section above. The JSON payload is shared and only the consumer differs.
Domoticz
The MQTT Auto-discovery plugin in Domoticz understands the same
homeassistant/... discovery topics. Enable the plugin in the Domoticz
settings, and the ESP32 project from the HA section above will start
registering a device in Domoticz automatically, just as it does in HA.
An alternative is the native Domoticz HTTP API via esp_http_client:
#include "esp_http_client.h"
#include "esp_log.h"
static const char *TAG = "domoticz";
static void publish_to_domoticz(int idx, float power, double e_wh) {
char url[256];
/* Energy in Wh, Power in W — Domoticz format: "POWER;ENERGY_WH" */
snprintf(url, sizeof(url),
"http://192.168.1.20:8080/json.htm?type=command¶m=udevice"
"&idx=%d&svalue=%.1f;%.0f", idx, (double)power, e_wh);
esp_http_client_config_t cfg = {
.url = url,
.method = HTTP_METHOD_GET,
.timeout_ms = 5000,
};
esp_http_client_handle_t client = esp_http_client_init(&cfg);
esp_err_t err = esp_http_client_perform(client);
int status = esp_http_client_get_status_code(client);
esp_http_client_cleanup(client);
if (err != ESP_OK || status != 200) {
ESP_LOGW(TAG, "domoticz HTTP %d (err %s)", status, esp_err_to_name(err));
}
}
/* In the 60-second loop: */
publish_to_domoticz(123 /* your idx */, snap.avg_p[0], rbamp_energy_wh(dev, 0));Create a device in the Domoticz UI of type General → kWh (incremental counter), get its idx, and hard-code it into the project.
InfluxDB OSS + Grafana
Write line-protocol points to InfluxDB directly from the ESP32 via
esp_http_client:
#include "esp_http_client.h"
#include "esp_log.h"
#define INFLUX_HOST "192.168.1.30:8086"
#define INFLUX_ORG "homelab"
#define INFLUX_BKT "energy"
#define INFLUX_TOKEN "your-token-here"
static const char *TAG = "influx";
static void push_influx(float u, float p, double e_wh) {
char url[256], body[256];
snprintf(url, sizeof(url),
"http://" INFLUX_HOST "/api/v2/write?org=%s&bucket=%s&precision=s",
INFLUX_ORG, INFLUX_BKT);
snprintf(body, sizeof(body),
"rbamp,device=main voltage=%.1f,power=%.1f,energy=%.3f",
(double)u, (double)p, e_wh);
esp_http_client_config_t cfg = {
.url = url,
.method = HTTP_METHOD_POST,
.timeout_ms = 5000,
};
esp_http_client_handle_t client = esp_http_client_init(&cfg);
esp_http_client_set_header(client, "Authorization", "Token " INFLUX_TOKEN);
esp_http_client_set_header(client, "Content-Type", "text/plain");
esp_http_client_set_post_field(client, body, strlen(body));
esp_err_t err = esp_http_client_perform(client);
int status = esp_http_client_get_status_code(client);
esp_http_client_cleanup(client);
if (err != ESP_OK || status != 204) {
ESP_LOGW(TAG, "influx HTTP %d (err %s)", status, esp_err_to_name(err));
}
}
/* In the 60-second loop: */
rbamp_period_snapshot_t snap;
if (rbamp_read_period_snapshot(dev, &snap, 50, false) == ESP_OK && snap.valid) {
float u;
rbamp_read_voltage(dev, 0, &u);
push_influx(u, snap.avg_p[0], rbamp_energy_wh(dev, 0));
}In Grafana, add an InfluxDB data source, then a panel with a Flux query:
from(bucket: "energy")
|> range(start: -24h)
|> filter(fn: (r) => r._measurement == "rbamp" and r.device == "main")
|> filter(fn: (r) => r._field == "power")For a long-running soak deployment, additionally push a diagnostic
payload to a separate measurement carrying the heap level (via
esp_get_free_heap_size()) and counters of successful / failed period
snapshots — this lets you watch the health of the bus + WiFi on the
same chart as the energy data:
snprintf(body, sizeof(body),
"rbamp_diag,device=main heap_free=%u,snap_ok=%lu,snap_fail=%lu",
(unsigned)esp_get_free_heap_size(),
(unsigned long)snap_ok_count,
(unsigned long)snap_fail_count);
/* ...push to InfluxDB... */Note: the component's internal retry/sanity counters are not exposed in the public API (see 10 · Troubleshooting, "About retry/sanity counters"). Count
snap_ok_count/snap_fail_countyourself on the application side from the return values ofrbamp_read_period_snapshot().
Multi-platform — fan-out from a single ESP32
If you need HA Auto-discovery, InfluxDB, and Node-RED all at once,
publish the state JSON once to MQTT and let each consumer subscribe to
rbamp/+/state. The ESP32 talks only to the MQTT broker — the broker
itself fans out to the subscribers. Do not push from the ESP32 into N
HTTP endpoints directly: that couples the device to specific consumers.
For a very fast stream (5 Hz RT), run a sidecar Python script on the Pi
that hosts the broker — subscribe to the fast topic, decimate, and
publish to the slow topics. The ESP32's I²C loop (a FreeRTOS task with
rbamp_read_*() calls) should stay focused, free of HTTP overhead.
Links
- 06 · Examples — the base projects that these
integrations are built on (including
mqtt_publisherandha_discovery) - 08 · Cloud integrations — AWS IoT / Azure / GCP / InfluxDB Cloud / generic webhook
- 10 · Troubleshooting — patterns for MQTT disconnect, the WiFi event handler, the TLS heap budget
← Examples | Contents | Cloud Integrations →
Source & issues: rb-amp/rbamp-esp-idf · this page in the repo: docs/07_diy_integrations.md