Skip to content

Commit 337a432

Browse files
authored
Esphome (#50)
* feat: add ESPHome support
1 parent 1ea9e55 commit 337a432

File tree

4 files changed

+65
-2
lines changed

4 files changed

+65
-2
lines changed

README.md

+15-2
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,25 @@ mqtt_zigbee_availability{topic="zigbee2mqtt_garage"} 1.0
7171
mqtt_temperature{topic="zigbee2mqtt_garage"} 1.0
7272
```
7373

74-
Note: the metric name mqtt_state is not kept to reduce collision risks as it is too common.
74+
Note: the metric name mqtt_state is not kept reducing collision risks as it is too common.
7575

7676
### Zwavejs2Mqtt
7777

7878
This exporter also supports Zwavejs2Mqtt metrics, preferably using "named topics" (see [official documentation](https://zwave-js.github.io/zwavejs2mqtt/#/usage/setup?id=gateway)).
7979

80-
To setup this, you need to specify the topic prefix used by Zwavejs2Mqtt in `ZWAVE_TOPIC_PREFIX` the environment variable (default being "zwave/").
80+
To set up this, you need to specify the topic prefix used by Zwavejs2Mqtt in `ZWAVE_TOPIC_PREFIX` the environment variable (default being "zwave/").
81+
82+
### ESPHome
83+
84+
ESPHome is supported only when using the default `state_topic`: "<TOPIC_PREFIX>/<COMPONENT_TYPE>/<COMPONENT_NAME>/state". (see [official documentation](https://esphome.io/components/mqtt.html#mqtt-component-base-configuration)).
85+
86+
To set up this, you need to specify the topic prefix list used by ESPHome in `ESPHOME_TOPIC_PREFIXES` the environment variable (default being "", so disabled).
87+
88+
This is a list so you can simply set one or more topic prefixes, the separator being a comma.
89+
90+
Example: `ESPHOME_TOPIC_PREFIXES="esphome-weather-indoor,esphome-weather-outdoor"`
91+
92+
If all of your ESPHome topics share a same prefix, you can simply put the common part. In the above example, `"esphome"` will match all topic starting by "esphome".
8193

8294
### Configuration
8395

@@ -102,6 +114,7 @@ The list of parameters are:
102114
* `TOPIC_LABEL`: Define the Prometheus label for the topic, example temperature{topic="device1"} (default: topic)
103115
* `ZIGBEE2MQTT_AVAILABILITY`: Normalize sensor name for device availability metric added by Zigbee2MQTT (default: False)
104116
* `ZWAVE_TOPIC_PREFIX`: MQTT topic used for Zwavejs2Mqtt messages (default: zwave/)
117+
* `ESPHOME_TOPIC_PREFIXES`: MQTT topic used for Zwavejs2Mqtt messages (default: "")
105118

106119
### Deployment
107120

mqtt_exporter/main.py

+26
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,30 @@ def _normalize_zwave2mqtt_format(topic, payload):
209209
return topic, payload_dict
210210

211211

212+
def _normalize_esphome_format(topic, payload):
213+
"""Normalize esphome format.
214+
215+
Example:
216+
esphome/sensor/temperature/state
217+
218+
Only supports default state_topic:
219+
<topic_prefix>/<component_type>/<component_name>/state
220+
"""
221+
info = topic.split("/")
222+
223+
topic = f"{info[0].lower()}/{info[1].lower()}"
224+
payload_dict = {info[-2]: payload}
225+
return topic, payload_dict
226+
227+
228+
def _is_esphome_topic(topic):
229+
for prefix in settings.ESPHOME_TOPIC_PREFIXES:
230+
if prefix and topic.startswith(prefix):
231+
return True
232+
233+
return False
234+
235+
212236
def _parse_message(raw_topic, raw_payload):
213237
"""Parse topic and payload to have exposable information."""
214238
# parse MQTT payload
@@ -223,6 +247,8 @@ def _parse_message(raw_topic, raw_payload):
223247

224248
if raw_topic.startswith(settings.ZWAVE_TOPIC_PREFIX):
225249
topic, payload = _normalize_zwave2mqtt_format(raw_topic, payload)
250+
elif _is_esphome_topic(raw_topic):
251+
topic, payload = _normalize_esphome_format(raw_topic, payload)
226252
elif not isinstance(payload, dict):
227253
topic, payload = _normalize_name_in_topic_msg(raw_topic, payload)
228254
else:

mqtt_exporter/settings.py

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
TOPIC = os.getenv("MQTT_TOPIC", "#")
77
IGNORED_TOPICS = os.getenv("MQTT_IGNORED_TOPICS", "").split(",")
88
ZWAVE_TOPIC_PREFIX = os.getenv("ZWAVE_TOPIC_PREFIX", "zwave/")
9+
ESPHOME_TOPIC_PREFIXES = os.getenv("ESPHOME_TOPIC_PREFIXES", "").split(",")
910

1011
ZIGBEE2MQTT_AVAILABILITY = os.getenv("ZIGBEE2MQTT_AVAILABILITY", "False") == "True"
1112
LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO")

tests/functional/test_parse_message.py

+23
Original file line numberDiff line numberDiff line change
@@ -140,3 +140,26 @@ def test_parse_message__zwave_js__payload_not_dict():
140140
parsed_topic, parsed_payload = _parse_message(topic, payload)
141141
assert parsed_topic == "zwave_BackRoom_Multisensor_sensor_multilevel_endpoint_0_Air_temperature"
142142
assert parsed_payload == {}
143+
144+
145+
def test__parse_message__esphome_style():
146+
"""Test message parsing with espthome style.
147+
148+
Same format for SONOFF sensors.
149+
"""
150+
settings.ESPHOME_TOPIC_PREFIXES = ["esp", "ESP"]
151+
topic = "esphome/outdoor/sensor/temperature/state"
152+
payload = "20.0"
153+
154+
parsed_topic, parsed_payload = _parse_message(topic, payload)
155+
156+
assert parsed_topic == "esphome_outdoor"
157+
assert parsed_payload == {"temperature": 20.0}
158+
159+
topic = "ESPHOME/indoor/sensor/temperature/state"
160+
payload = "22.0"
161+
162+
parsed_topic, parsed_payload = _parse_message(topic, payload)
163+
164+
assert parsed_topic == "esphome_indoor"
165+
assert parsed_payload == {"temperature": 22.0}

0 commit comments

Comments
 (0)