Repository navigation
feat: Mirror system and air quality events to Home Assistant via MQTT - #50
netscout2001 wants to merge 11 commits into
Conversation
|
Added PressureHistory events (3h/24h delta ready, gap fills, stale resets) and boot screens released event from UiBootFlow to the same MQTT events sensor. |
Additional event sources addedExtended the initial implementation with two more modules so the HA Events sensor
|
| Message | Trigger |
|---|---|
Charts history: invalid header, reset |
Persisted blob has wrong magic/version |
Charts history: invalid index/count, reset |
Stored index or count out of range |
Charts history: stale, reset |
Stored history too old to be useful (load) |
Charts history: stale, reset |
History became stale during runtime |
Charts history: epoch moved backwards, reset |
System clock jumped backwards |
src/modules/NetworkManager.cpp
Added MqttPublishSystemEvent() for the 3 network-level warnings that are
actionable from HA (low-level internal retries intentionally excluded to avoid spam):
| Message | Trigger |
|---|---|
WiFi connect failed |
All connection retries exhausted, entering error state |
WiFi AP failed to start |
softAP() returned false during AP mode setup |
mDNS start failed |
MDNS.begin() returned false — device unreachable via .local |
All new call sites follow the same pattern as the initial implementation —
no new methods, no new topics, no Discovery changes required.
Additional events added — boot sequence and sensor init now fully coveredExtended the implementation with 9 more info-level events across 6 files so the
|
| Message | Trigger |
|---|---|
WiFi connected, IP: <ip> |
STA connection established |
mDNS responder started: <hostname>.local |
mDNS started successfully |
src/modules/MqttManager.cpp
| Message | Trigger |
|---|---|
MQTT connected |
Every successful broker connect / reconnect |
src/modules/TimeManager.cpp
| Message | Trigger |
|---|---|
NTP sync start (tz=<timezone>) |
NTP sync initiated after WiFi connect |
src/modules/SensorManager.cpp
| Message | Trigger |
|---|---|
BMP580/581 OK / BMP3xx OK / DPS310 OK |
Whichever pressure sensor is detected |
SEN0466 CO OK at 0x<addr> |
CO sensor detected and started |
src/modules/PressureHistory.cpp
| Message | Trigger |
|---|---|
Pressure history restored |
Stored pressure history successfully loaded from flash |
src/modules/ChartsHistory.cpp
| Message | Trigger |
|---|---|
Charts history restored |
Stored chart history successfully loaded from flash |
No new methods, topics, or Discovery changes. All call sites in files compiled
by the native test runner remain guarded with #ifdef ARDUINO or
#ifndef UNIT_TEST.
docs: update Events entity documentation with complete event reference
…/project_aura into feature/mqtt-events
|
Resolved merge conflicts after upstream refactored the MQTT client from PubSubClient to the native ESP-IDF Changes required to adapt our implementation:
All our event publishing methods ( |
|
Fixed native test build failure in ChartsHistory.cpp and PressureHistory.cpp — the #include "modules/MqttManager.h" and all MqttPublishSystemEvent() call sites were missing #ifndef UNIT_TEST guards, causing WiFi.h: No such file or directory errors in the native test environment. All calls are now consistently guarded, matching the pattern already used in StorageManager.cpp and TimeManager.cpp. ========================================================================================================= SUMMARY ========================================================================================================= native_test test_bmp3xx_probe PASSED 00:00:02.307 |
|
@21cncstudio you had a chance to review the PR? |
netscout2001
left a comment
There was a problem hiding this comment.
PR ready for implementation.
|
Thanks for the PR. I like the overall direction, and mirroring runtime events into Home Assistant definitely makes sense for this project. I had a first look and there are a few things I want to review more carefully before we decide how to move forward, mainly around early boot events being lost before MQTT is connected, and some mismatch between the events sensor discovery/docs and the actual HA behavior. The implementation also touches quite a few modules, so I want to go through it properly instead of giving rushed feedback. I’ll take a closer, more concrete look on Monday and then come back with specific notes on what should be changed before merge. For now, please leave the PR as it is and don’t update anything yet. |
|
Thanks a lot for the idea and for putting time into this PR. I ended up taking a slightly different approach on my side so it fits better with the current MQTT/Home Assistant setup and the direction I want for the project, so I’m not going to merge this one as-is. But your PR was definitely helpful and pushed this feature in the right direction. Really appreciate the contribution and the effort here. |
|
Hi @netscout2001, Thank you again for the work you put into this PR! I ended up implementing the Home Assistant MQTT event stream with a different internal design so it would fit the current Aura architecture, but your proposal and implementation were genuinely helpful and pushed the feature in the right direction. The resulting feature was added in 4e14540. I’m now improving how Project Aura records community contributions. The project has an Individual Contributor License Agreement, and I’d appreciate it if you could read the current CLA. If you’re happy with it, please post the following exact text as a separate comment containing only this sentence:
Separately from the CLA, I’d like to record this PR in the contributor history and clearly acknowledge your role in bringing the Home Assistant event-stream idea to Aura. Thanks again for the idea and all the detailed implementation work! |
Summary
This PR adds a new
sensorentity to the existing MQTT + Home Assistant integration that mirrors all significant runtime events to HA in real time — matching what the web dashboard Events tab already shows locally.No new dependencies, no new configuration options, no breaking changes to existing entities or topics.
Why a
sensorentity instead ofevent?The HA
eventplatform (introduced in HA 2023.8) is designed for discrete, fire-and-forget signals. While it correctly models the intent, it has one significant UX limitation: the HA logbook and history graph display every state change as the generic string "Hat ein Ereignis erkannt" ("An event was detected"), regardless of the payload content. The actualmessage,category, andevent_typeattributes are only visible when drilling into the entity's detail page.Using the
sensorplatform with"value_template": "{{ value_json.message }}"makes the human-readable message text — e.g. "CO2 worsened to bad: 1480 ppm" or "NTP sync completed, local time=2026-03-19 09:41:28" — the sensor's state value. This means:event_type,category,value, andunitviatrigger.to_state.attributes.logbook.logautomations are needed in HA.The payload format is identical regardless of platform type, so switching back to
eventin a future firmware version would only require changing the discovery component string and adding anevent_typesarray.New MQTT topic
Air quality event payload:
System event payload:
event_typeis eithersystem_info,system_warning, or a<metric>_<band>string (e.g.co2_bad,pm25_critical).Changed files
src/modules/MqttManager.hpublishEvent(metric_key, band, value, unit, message)— publishes an air quality band-change event to<base_topic>/events.publishSystemEvent(category, severity, message)— publishes a system log event to<base_topic>/events.publishDiscoveryEvent()declaration — now registers asensorentity instead of anevententity.MqttPublishEvent(...)— callable from any module without creating a circular header dependency withMqttManager.h.MqttPublishSystemEvent(...)— same rationale.src/modules/MqttManager.cpppublishDiscoveryEvent()(rewritten)"event"to"sensor".event_typesarray (not applicable to sensor platform)."value_template": "{{ value_json.message }}"so the human-readable message is used as the sensor state."icon": "mdi:bell-alert".publishDiscovery()(extended)event/<id>_air_events/configto remove the intermediateevent-type entity from HA device registry on first connect after firmware update.event/<id>_events/configfor the same reason.publishEvent()(new)event_type,message,value, andunitto<base_topic>/eventswithretain=false.append_json_escaped()for the message field to handle any special characters that could appear in log strings.publishSystemEvent()(new)event_type,message, andcategoryto<base_topic>/eventswithretain=false.severitystring"warning"→event_type"system_warning", anything else →"system_info".MqttPublishEvent()/MqttPublishSystemEvent()(new free functions)MqttManagerthrough the existingg_mqttsingleton set inMqttManager::begin().SensorManagerandMqttManagerrun on the same Arduino loop task (Core 1), so no mutex is required.src/modules/SensorManager.cppInclude
#include "modules/MqttManager.h"guarded with#ifdef ARDUINOto prevent compilation failure in the native host test environment (which has noWiFi.h).log_air_metric_transition()(extended)mqtt_keyparameter (e.g."co2","pm25") used to build theevent_typestring together with the band name.messagebuffer that assembles the same human-readable string already written to the serial log, e.g. "CO2 worsened to bad: 1480 ppm".LOGW/LOGIcalls with a singlesnprintfintomessagefollowed by the log call, so the serial log and the MQTT payload always contain identical text.MqttPublishEvent()call guarded with#ifdef ARDUINOat each band-transition point (initial non-good reading, worsening, improving, and recovery to Good).log_soft_warnings()(extended)log_air_metric_transition()calls updated to pass the newmqtt_keyargument:"co2","co","pm05","pm1","pm25","pm4","pm10","hcho","voc","nox".SensorManager::begin()(extended)MqttPublishSystemEvent()guarded with#ifdef ARDUINOat:"Pressure sensor not found"→system_warning"SFA30 init failed"→system_warning"SFA30 OK"→system_infoSensorManager::poll()(extended)MqttPublishSystemEvent()guarded with#ifdef ARDUINOat:"SEN66 not found (x/y)"→system_warning(uses same snprintf buffer as the existingLOGWcall, ensuring identical text)"SEN66 start attempts exhausted, stop probing until reboot"→system_warning"SEN66 OK"(recovery after fault) →system_infosrc/modules/StorageManager.cppInclude
#include "modules/MqttManager.h"inside#ifndef UNIT_TESTblock so the native unit test build is unaffected.StorageManager::begin()(extended)MqttPublishSystemEvent()inside#ifndef UNIT_TESTat:"LittleFS mount failed"→system_warning"restored last good config"→system_warning"last good config missing, factory reset"→system_warning"factory reset requested"→system_warningStorageManager::poll()(extended)MqttPublishSystemEvent()inside#ifndef UNIT_TESTat:"config committed as last known good"→system_info"last known good commit failed"→system_warningsrc/modules/TimeManager.cppInclude
#include "modules/MqttManager.h"inside#ifndef UNIT_TESTblock.TimeManageris compiled in the native test environment;MqttManageris not available there.TimeManager::initRtc()(extended)MqttPublishSystemEvent()inside#ifndef UNIT_TESTat:"<RTC label> init failed"→system_warningTimeManager::ntpPoll()(extended)MqttPublishSystemEvent()inside#ifndef UNIT_TESTat:"NTP sync completed, local time=<timestamp>"→system_info(uses samebufas the existingLOGIcall)"NTP sync timeout after <N> ms"→system_warningTimeManager::noteRtcReadSuccess()(extended)MqttPublishSystemEvent()inside#ifndef UNIT_TESTat:"<RTC label> communication restored"→system_infoTimeManager::noteRtcReadFailure()(extended)MqttPublishSystemEvent()inside#ifndef UNIT_TESTat:"<RTC label> read failed repeatedly"→system_warningTimeManager::applyRtcBatteryLowState()(extended)MqttPublishSystemEvent()inside#ifndef UNIT_TESTat:"RTC battery low"→system_warning"RTC battery status OK"→system_infosrc/modules/FanControl.cppInclude
#include "modules/MqttManager.h".FanControlhas no native test build, so no guard is needed.FanControl::begin()(extended)MqttPublishSystemEvent()at:"DAC ready"→system_infoFanControl::poll()(extended)MqttPublishSystemEvent()at:"DAC recovered"→system_infoFanControl::handleDacFault()(extended)MqttPublishSystemEvent()at:"DAC error: <reason>"→system_warning(uses same snprintf buffer to match the existingLOGWtext exactly)Native test build
All
MqttManager.hincludes andMqttPublishEvent()/MqttPublishSystemEvent()call sites that exist in files compiled by the native test runner (SensorManager.cpp,StorageManager.cpp,TimeManager.cpp) are guarded with either#ifdef ARDUINOor#ifndef UNIT_TEST. The native test suite passes unchanged.Backward compatibility
<base_topic>/eventstopic is only published when a relevant event occurs; it is never retained, so subscribers that were not connected at the time of an event will not receive it retroactively.MqttPublishEvent()/MqttPublishSystemEvent()calls are silent no-ops.Home Assistant result
After flashing, a new entity
sensor.<device_name>_eventsappears automatically via MQTT Discovery. No manual YAML configuration is required.The HA logbook shows entries like:
Example HA automation trigger using the full attribute payload:
Checklist