Przeglądaj źródła

Upgraded to homie 3.0 convention and one connection per device for homie (#119)

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

* Cleanup
Sebastian Chrostek 4 lat temu
rodzic
commit
7a9a7aa902
2 zmienionych plików z 93 dodań i 40 usunięć
  1. 0 3
      config.ini.dist
  2. 93 37
      miflora-mqtt-daemon.py

+ 0 - 3
config.ini.dist

@@ -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

+ 93 - 37
miflora-mqtt-daemon.py

@@ -115,7 +115,6 @@ else:
     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
 
@@ -133,15 +132,13 @@ if reporting_mode == 'wirenboard-mqtt' and base_topic:
 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)
 
@@ -235,40 +232,95 @@ if reporting_mode == 'mqtt-json':
     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':
@@ -339,6 +391,8 @@ while True:
 
         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)
@@ -367,9 +421,11 @@ while True:
             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():