Skip to content

Commit

Permalink
Upgraded to homie 3.0 convention and one connection per device for ho…
Browse files Browse the repository at this point in the history
…mie (#119)

* Upgraded to homie 3.0 convention and one connection per device for homie

* Cleanup
  • Loading branch information
chrostek authored Mar 15, 2021
1 parent db1edfa commit 7a9a7aa
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 40 deletions.
3 changes: 0 additions & 3 deletions config.ini.dist
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,6 @@
#base_topic = v1/devices/me/telemetry # Default for: thingsboard-json
#base_topic = # Default for: wirenboard-mqtt

# Homie specific: The device ID for this daemon instance (Default: miflora-mqtt-daemon)
#homie_device_id = miflora-mqtt-daemon

# The MQTT broker authentification credentials (Default: no authentication)
# Will also read from MQTT_USERNAME and MQTT_PASSWORD environment variables
#username = user
Expand Down
130 changes: 93 additions & 37 deletions miflora-mqtt-daemon.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,6 @@ def on_publish(client, userdata, mid):
default_base_topic = 'miflora'

base_topic = config['MQTT'].get('base_topic', default_base_topic).lower()
device_id = config['MQTT'].get('homie_device_id', 'miflora-mqtt-daemon').lower()
sleep_period = config['Daemon'].getint('period', 300)
miflora_cache_timeout = sleep_period - 1

Expand All @@ -133,15 +132,13 @@ def on_publish(client, userdata, mid):
print_line('Configuration accepted', console=False, sd_notify=True)

# MQTT connection
if reporting_mode in ['mqtt-json', 'mqtt-homie', 'mqtt-smarthome', 'homeassistant-mqtt', 'thingsboard-json', 'wirenboard-mqtt']:
if reporting_mode in ['mqtt-json', 'mqtt-smarthome', 'homeassistant-mqtt', 'thingsboard-json', 'wirenboard-mqtt']:
print_line('Connecting to MQTT broker ...')
mqtt_client = mqtt.Client()
mqtt_client.on_connect = on_connect
mqtt_client.on_publish = on_publish
if reporting_mode == 'mqtt-json':
mqtt_client.will_set('{}/$announce'.format(base_topic), payload='{}', retain=True)
elif reporting_mode == 'mqtt-homie':
mqtt_client.will_set('{}/{}/$online'.format(base_topic, device_id), payload='false', retain=True)
elif reporting_mode == 'mqtt-smarthome':
mqtt_client.will_set('{}/connected'.format(base_topic), payload='0', retain=True)

Expand Down Expand Up @@ -235,40 +232,95 @@ def on_publish(client, userdata, mid):
sleep(0.5) # some slack for the publish roundtrip and callback function
print()
elif reporting_mode == 'mqtt-homie':
mqtt_client = OrderedDict()
print_line('Announcing Mi Flora devices to MQTT broker for auto-discovery ...')
mqtt_client.publish('{}/{}/$homie'.format(base_topic, device_id), '2.1.0-alpha', 1, True)
mqtt_client.publish('{}/{}/$online'.format(base_topic, device_id), 'true', 1, True)
mqtt_client.publish('{}/{}/$name'.format(base_topic, device_id), device_id, 1, True)
mqtt_client.publish('{}/{}/$fw/version'.format(base_topic, device_id), flora['firmware'], 1, True)

nodes_list = ','.join([flora_name for [flora_name, flora] in flores.items()])
mqtt_client.publish('{}/{}/$nodes'.format(base_topic, device_id), nodes_list, 1, True)

for [flora_name, flora] in flores.items():
topic_path = '{}/{}/{}'.format(base_topic, device_id, flora_name)
mqtt_client.publish('{}/$name'.format(topic_path), flora['name_pretty'], 1, True)
mqtt_client.publish('{}/$type'.format(topic_path), 'miflora', 1, True)
mqtt_client.publish('{}/$properties'.format(topic_path), 'battery,conductivity,light,moisture,temperature', 1, True)
mqtt_client.publish('{}/battery/$settable'.format(topic_path), 'false', 1, True)
mqtt_client.publish('{}/battery/$unit'.format(topic_path), 'percent', 1, True)
mqtt_client.publish('{}/battery/$datatype'.format(topic_path), 'int', 1, True)
mqtt_client.publish('{}/battery/$range'.format(topic_path), '0:100', 1, True)
mqtt_client.publish('{}/conductivity/$settable'.format(topic_path), 'false', 1, True)
mqtt_client.publish('{}/conductivity/$unit'.format(topic_path), 'µS/cm', 1, True)
mqtt_client.publish('{}/conductivity/$datatype'.format(topic_path), 'int', 1, True)
mqtt_client.publish('{}/conductivity/$range'.format(topic_path), '0:*', 1, True)
mqtt_client.publish('{}/light/$settable'.format(topic_path), 'false', 1, True)
mqtt_client.publish('{}/light/$unit'.format(topic_path), 'lux', 1, True)
mqtt_client.publish('{}/light/$datatype'.format(topic_path), 'int', 1, True)
mqtt_client.publish('{}/light/$range'.format(topic_path), '0:50000', 1, True)
mqtt_client.publish('{}/moisture/$settable'.format(topic_path), 'false', 1, True)
mqtt_client.publish('{}/moisture/$unit'.format(topic_path), 'percent', 1, True)
mqtt_client.publish('{}/moisture/$datatype'.format(topic_path), 'int', 1, True)
mqtt_client.publish('{}/moisture/$range'.format(topic_path), '0:100', 1, True)
mqtt_client.publish('{}/temperature/$settable'.format(topic_path), 'false', 1, True)
mqtt_client.publish('{}/temperature/$unit'.format(topic_path), '°C', 1, True)
mqtt_client.publish('{}/temperature/$datatype'.format(topic_path), 'float', 1, True)
mqtt_client.publish('{}/temperature/$range'.format(topic_path), '*', 1, True)
print_line('Connecting to MQTT broker for "{}" ...'.format(flora['name_pretty']))
mqtt_client[flora_name.lower()] = mqtt.Client(flora_name.lower())
mqtt_client[flora_name.lower()].on_connect = on_connect
mqtt_client[flora_name.lower()].on_publish = on_publish
mqtt_client[flora_name.lower()].will_set('{}/{}/$state'.format(base_topic, flora_name.lower()), payload='disconnected', retain=True)

if config['MQTT'].getboolean('tls', False):
# According to the docs, setting PROTOCOL_SSLv23 "Selects the highest protocol version
# that both the client and server support. Despite the name, this option can select
# “TLS” protocols as well as “SSL”" - so this seems like a resonable default
mqtt_client[flora_name.lower()].tls_set(
ca_certs=config['MQTT'].get('tls_ca_cert', None),
keyfile=config['MQTT'].get('tls_keyfile', None),
certfile=config['MQTT'].get('tls_certfile', None),
tls_version=ssl.PROTOCOL_SSLv23
)

mqtt_username = os.environ.get("MQTT_USERNAME", config['MQTT'].get('username'))
mqtt_password = os.environ.get("MQTT_PASSWORD", config['MQTT'].get('password', None))

if mqtt_username:
mqtt_client[flora_name.lower()].username_pw_set(mqtt_username, mqtt_password)
try:
mqtt_client[flora_name.lower()].connect(os.environ.get('MQTT_HOSTNAME', config['MQTT'].get('hostname', 'localhost')),
port=int(os.environ.get('MQTT_PORT', config['MQTT'].get('port', '1883'))),
keepalive=config['MQTT'].getint('keepalive', 60))
except:
print_line('MQTT connection error. Please check your settings in the configuration file "config.ini"', error=True, sd_notify=True)
sys.exit(1)
else:
mqtt_client[flora_name.lower()].loop_start()
sleep(1.0) # some slack to establish the connection

topic_path = '{}/{}'.format(base_topic, flora_name.lower())

mqtt_client[flora_name.lower()].publish('{}/$homie'.format(topic_path), '3.0', 1, True)
mqtt_client[flora_name.lower()].publish('{}/$name'.format(topic_path), flora['name_pretty'], 1, True)
mqtt_client[flora_name.lower()].publish('{}/$state'.format(topic_path), 'ready', 1, True)
mqtt_client[flora_name.lower()].publish('{}/$mac'.format(topic_path), flora['mac'], 1, True)
mqtt_client[flora_name.lower()].publish('{}/$stats'.format(topic_path), 'interval,timestamp', 1, True)
mqtt_client[flora_name.lower()].publish('{}/$stats/interval'.format(topic_path), flora['refresh'], 1, True)
mqtt_client[flora_name.lower()].publish('{}/$stats/timestamp'.format(topic_path), strftime('%Y-%m-%dT%H:%M:%S%z', localtime()), 1, True)
mqtt_client[flora_name.lower()].publish('{}/$fw/name'.format(topic_path), 'miflora-firmware', 1, True)
mqtt_client[flora_name.lower()].publish('{}/$fw/version'.format(topic_path), flora['firmware'], 1, True)
mqtt_client[flora_name.lower()].publish('{}/$nodes'.format(topic_path), 'sensor', 1, True)

sensor_path = '{}/sensor'.format(topic_path)

mqtt_client[flora_name.lower()].publish('{}/$name'.format(sensor_path), 'miflora', 1, True)
mqtt_client[flora_name.lower()].publish('{}/$properties'.format(sensor_path), 'battery,conductivity,light,moisture,temperature', 1, True)

mqtt_client[flora_name.lower()].publish('{}/battery/$name'.format(sensor_path), 'battery', 1, True)
mqtt_client[flora_name.lower()].publish('{}/battery/$settable'.format(sensor_path), 'false', 1, True)
mqtt_client[flora_name.lower()].publish('{}/battery/$unit'.format(sensor_path), '%', 1, True)
mqtt_client[flora_name.lower()].publish('{}/battery/$datatype'.format(sensor_path), 'integer', 1, True)
mqtt_client[flora_name.lower()].publish('{}/battery/$format'.format(sensor_path), '0:100', 1, True)
mqtt_client[flora_name.lower()].publish('{}/battery/$retained'.format(sensor_path), 'true', 1, True)

mqtt_client[flora_name.lower()].publish('{}/conductivity/$name'.format(sensor_path), 'conductivity', 1, True)
mqtt_client[flora_name.lower()].publish('{}/conductivity/$settable'.format(sensor_path), 'false', 1, True)
mqtt_client[flora_name.lower()].publish('{}/conductivity/$unit'.format(sensor_path), 'µS/cm', 1, True)
mqtt_client[flora_name.lower()].publish('{}/conductivity/$datatype'.format(sensor_path), 'integer', 1, True)
mqtt_client[flora_name.lower()].publish('{}/conductivity/$format'.format(sensor_path), '0:*', 1, True)
mqtt_client[flora_name.lower()].publish('{}/conductivity/$retained'.format(sensor_path), 'true', 1, True)

mqtt_client[flora_name.lower()].publish('{}/light/$name'.format(sensor_path), 'light', 1, True)
mqtt_client[flora_name.lower()].publish('{}/light/$settable'.format(sensor_path), 'false', 1, True)
mqtt_client[flora_name.lower()].publish('{}/light/$unit'.format(sensor_path), 'lux', 1, True)
mqtt_client[flora_name.lower()].publish('{}/light/$datatype'.format(sensor_path), 'integer', 1, True)
mqtt_client[flora_name.lower()].publish('{}/light/$format'.format(sensor_path), '0:50000', 1, True)
mqtt_client[flora_name.lower()].publish('{}/light/$retained'.format(sensor_path), 'true', 1, True)

mqtt_client[flora_name.lower()].publish('{}/moisture/$name'.format(sensor_path), 'moisture', 1, True)
mqtt_client[flora_name.lower()].publish('{}/moisture/$settable'.format(sensor_path), 'false', 1, True)
mqtt_client[flora_name.lower()].publish('{}/moisture/$unit'.format(sensor_path), '%', 1, True)
mqtt_client[flora_name.lower()].publish('{}/moisture/$datatype'.format(sensor_path), 'integer', 1, True)
mqtt_client[flora_name.lower()].publish('{}/moisture/$format'.format(sensor_path), '0:100', 1, True)
mqtt_client[flora_name.lower()].publish('{}/moisture/$retained'.format(sensor_path), 'true', 1, True)

mqtt_client[flora_name.lower()].publish('{}/temperature/$name'.format(sensor_path), 'temperature', 1, True)
mqtt_client[flora_name.lower()].publish('{}/temperature/$settable'.format(sensor_path), 'false', 1, True)
mqtt_client[flora_name.lower()].publish('{}/temperature/$unit'.format(sensor_path), '°C', 1, True)
mqtt_client[flora_name.lower()].publish('{}/temperature/$datatype'.format(sensor_path), 'float', 1, True)
mqtt_client[flora_name.lower()].publish('{}/temperature/$format'.format(sensor_path), '*', 1, True)
mqtt_client[flora_name.lower()].publish('{}/temperature/$retained'.format(sensor_path), 'true', 1, True)
sleep(0.5) # some slack for the publish roundtrip and callback function
print()
elif reporting_mode == 'homeassistant-mqtt':
Expand Down Expand Up @@ -339,6 +391,8 @@ def on_publish(client, userdata, mid):

if not flora['poller']._cache:
flora['stats']['failure'] += 1
if reporting_mode == 'mqtt-homie':
mqtt_client[flora_name.lower()].publish('{}/{}/$state'.format(base_topic, flora_name.lower()), 'disconnected', 1, True)
print_line('Failed to retrieve data from Mi Flora sensor "{}" ({}), success rate: {:.0%}'.format(
flora['name_pretty'], flora['mac'], flora['stats']['success']/flora['stats']['count']
), error = True, sd_notify = True)
Expand Down Expand Up @@ -367,9 +421,11 @@ def on_publish(client, userdata, mid):
mqtt_client.publish('{}/sensor/{}/state'.format(base_topic, flora_name.lower()), json.dumps(data))
sleep(0.5) # some slack for the publish roundtrip and callback function
elif reporting_mode == 'mqtt-homie':
print_line('Publishing data to MQTT base topic "{}/{}/{}"'.format(base_topic, device_id, flora_name))
print_line('Publishing data to MQTT base topic "{}/{}"'.format(base_topic, flora_name.lower()))
mqtt_client[flora_name.lower()].publish('{}/{}/$state'.format(base_topic, flora_name.lower()), 'ready', 1, True)
for [param, value] in data.items():
mqtt_client.publish('{}/{}/{}/{}'.format(base_topic, device_id, flora_name, param), value, 1, True)
mqtt_client[flora_name.lower()].publish('{}/{}/sensor/{}'.format(base_topic, flora_name.lower(), param), value, 1, True)
mqtt_client[flora_name.lower()].publish('{}/{}/$stats/timestamp'.format(base_topic, flora_name.lower()), strftime('%Y-%m-%dT%H:%M:%S%z', localtime()), 1, True)
sleep(0.5) # some slack for the publish roundtrip and callback function
elif reporting_mode == 'mqtt-smarthome':
for [param, value] in data.items():
Expand Down

0 comments on commit 7a9a7aa

Please sign in to comment.