543 lines
30 KiB
Python
Executable File
543 lines
30 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
import ssl
|
|
import sys
|
|
import re
|
|
import json
|
|
import os.path
|
|
import argparse
|
|
import threading
|
|
from itertools import chain
|
|
from time import time, sleep, localtime, strftime
|
|
from collections import OrderedDict
|
|
from colorama import init as colorama_init
|
|
from colorama import Fore, Back, Style
|
|
from configparser import ConfigParser
|
|
from unidecode import unidecode
|
|
from miflora.miflora_poller import MiFloraPoller, MI_BATTERY, MI_CONDUCTIVITY, MI_LIGHT, MI_MOISTURE, MI_TEMPERATURE
|
|
from mithermometer.mithermometer_poller import MiThermometerPoller, MI_HUMIDITY
|
|
from btlewrap import BluepyBackend, BluetoothBackendException
|
|
from bluepy.btle import BTLEException
|
|
import paho.mqtt.client as mqtt
|
|
import sdnotify
|
|
from signal import signal, SIGPIPE, SIG_DFL
|
|
|
|
signal(SIGPIPE,SIG_DFL)
|
|
|
|
project_name = 'Xiaomi Mi Flora Plant Sensor MQTT Client/Daemon'
|
|
project_url = 'https://github.com/ThomDietrich/miflora-mqtt-daemon'
|
|
|
|
sensor_name_miflora = "Mi Flora"
|
|
sensor_type_miflora = "MiFlora"
|
|
sensor_name_mitempbt = "Mijia Bluetooth Temperature Smart Humidity"
|
|
sensor_type_mitempbt = "MiTempBt"
|
|
|
|
miflora_parameters = OrderedDict([
|
|
(MI_LIGHT, dict(name="LightIntensity", name_pretty='Sunlight Intensity', typeformat='%d', unit='lux', device_class="illuminance")),
|
|
(MI_TEMPERATURE, dict(name="AirTemperature", name_pretty='Air Temperature', typeformat='%.1f', unit='°C', device_class="temperature")),
|
|
(MI_MOISTURE, dict(name="SoilMoisture", name_pretty='Soil Moisture', typeformat='%d', unit='%', device_class="humidity")),
|
|
(MI_CONDUCTIVITY, dict(name="SoilConductivity", name_pretty='Soil Conductivity/Fertility', typeformat='%d', unit='µS/cm')),
|
|
(MI_BATTERY, dict(name="Battery", name_pretty='Sensor Battery Level', typeformat='%d', unit='%', device_class="battery"))
|
|
])
|
|
|
|
mitempbt_parameters = OrderedDict([
|
|
(MI_TEMPERATURE, dict(name="AirTemperature", name_pretty='Air Temperature', typeformat='%.1f', unit='°C', device_class="temperature")),
|
|
(MI_HUMIDITY, dict(name="Humidity", name_pretty='Air Moisture', typeformat='%d', unit='%', device_class="humidity")),
|
|
(MI_BATTERY, dict(name="Battery", name_pretty='Sensor Battery Level', typeformat='%d', unit='%', device_class="battery"))
|
|
])
|
|
|
|
if False:
|
|
# will be caught by python 2.7 to be illegal syntax
|
|
print('Sorry, this script requires a python3 runtime environment.', file=sys.stderr)
|
|
|
|
# Argparse
|
|
parser = argparse.ArgumentParser(description=project_name, epilog='For further details see: ' + project_url)
|
|
parser.add_argument('--gen-openhab', help='generate openHAB items based on configured sensors', action='store_true')
|
|
parser.add_argument('--config_dir', help='set directory where config.ini is located', default=sys.path[0])
|
|
parse_args = parser.parse_args()
|
|
|
|
# Intro
|
|
colorama_init()
|
|
print(Fore.GREEN + Style.BRIGHT)
|
|
print(project_name)
|
|
print('Source:', project_url)
|
|
print(Style.RESET_ALL)
|
|
|
|
# Systemd Service Notifications - https://github.com/bb4242/sdnotify
|
|
sd_notifier = sdnotify.SystemdNotifier()
|
|
|
|
# Logging function
|
|
def print_line(text, error = False, warning=False, sd_notify=False, console=True):
|
|
timestamp = strftime('%Y-%m-%d %H:%M:%S', localtime())
|
|
if console:
|
|
if error:
|
|
print(Fore.RED + Style.BRIGHT + '[{}] '.format(timestamp) + Style.RESET_ALL + '{}'.format(text) + Style.RESET_ALL, file=sys.stderr)
|
|
elif warning:
|
|
print(Fore.YELLOW + '[{}] '.format(timestamp) + Style.RESET_ALL + '{}'.format(text) + Style.RESET_ALL)
|
|
else:
|
|
print(Fore.GREEN + '[{}] '.format(timestamp) + Style.RESET_ALL + '{}'.format(text) + Style.RESET_ALL)
|
|
timestamp_sd = strftime('%b %d %H:%M:%S', localtime())
|
|
if sd_notify:
|
|
sd_notifier.notify('STATUS={} - {}.'.format(timestamp_sd, unidecode(text)))
|
|
|
|
# convert device type to human-readable name
|
|
def sensor_type_to_name(sensor_type):
|
|
return sensor_name_miflora if (sensor_type == sensor_type_miflora ) else sensor_name_mitempbt
|
|
|
|
# Identifier cleanup
|
|
def clean_identifier(name):
|
|
clean = name.strip()
|
|
for this, that in [[' ', '-'], ['ä', 'ae'], ['Ä', 'Ae'], ['ö', 'oe'], ['Ö', 'Oe'], ['ü', 'ue'], ['Ü', 'Ue'], ['ß', 'ss']]:
|
|
clean = clean.replace(this, that)
|
|
clean = unidecode(clean)
|
|
return clean
|
|
|
|
# Eclipse Paho callbacks - http://www.eclipse.org/paho/clients/python/docs/#callbacks
|
|
def on_connect(client, userdata, flags, rc):
|
|
if rc == 0:
|
|
print_line('MQTT connection established', console=True, sd_notify=True)
|
|
print()
|
|
else:
|
|
print_line('Connection error with result code {} - {}'.format(str(rc), mqtt.connack_string(rc)), error=True)
|
|
#kill main thread
|
|
os._exit(1)
|
|
|
|
|
|
def on_publish(client, userdata, mid):
|
|
#print_line('Data successfully published.')
|
|
pass
|
|
|
|
|
|
def sensors_to_openhab_items(sensor_type, sensors, sensor_params, reporting_mode):
|
|
sensor_type_name = sensor_type_to_name(sensor_type)
|
|
print_line('Generating openHAB items. Copy to your configuration and modify as needed...')
|
|
items = list()
|
|
items.append('// {}.items - Generated by miflora-mqtt-daemon.'.format(sensor_type.lower()))
|
|
items.append('// Adapt to your needs! Things you probably want to modify:')
|
|
items.append('// Room group names, icons,')
|
|
items.append('// "gAll", "broker", "UnknownRoom"')
|
|
items.append('')
|
|
items.append('// {} specific groups'.format(sensor_type_name))
|
|
items.append('Group g{} "All {} sensors and elements" (gAll)'.format(sensor_type, sensor_type_name))
|
|
for param, param_properties in sensor_params.items():
|
|
items.append('Group g{} "{} {} elements" (gAll, g{})'.format(param_properties['name'], sensor_type_name, param_properties['name_pretty'], sensor_type))
|
|
if reporting_mode == 'mqtt-json':
|
|
for [sensor_name, sensor] in sensors.items():
|
|
location = sensor['location_clean'] if sensor['location_clean'] else 'UnknownRoom'
|
|
items.append('\n// {} "{}" ({})'.format(sensor_type_name, sensor['name_pretty'], sensor['mac']))
|
|
items.append('Group g{}{} "{} Sensor {}" (g{}, g{})'.format(location, sensor_name, sensor_type_name, sensor['name_pretty'], sensor_type, location))
|
|
for [param, param_properties] in sensor_params.items():
|
|
basic = 'Number {}_{}_{}'.format(location, sensor_name, param_properties['name'])
|
|
label = '"{} {} {} [{} {}]"'.format(location, sensor['name_pretty'], param_properties['name_pretty'], param_properties['typeformat'], param_properties['unit'].replace('%', '%%'))
|
|
details = '<text> (g{}{}, g{})'.format(location, sensor_name, param_properties['name'])
|
|
channel = '{{mqtt="<[broker:{}/{}:state:JSONPATH($.{})]"}}'.format(base_topic, sensor_name, param)
|
|
items.append(' '.join([basic, label, details, channel]))
|
|
items.append('')
|
|
print('\n'.join(items))
|
|
#elif reporting_mode == 'mqtt-homie':
|
|
else:
|
|
raise IOError('Given reporting_mode not supported for the export to openHAB items')
|
|
|
|
# Init sensors from configuration files
|
|
def init_sensors(sensor_type, sensors):
|
|
sensor_type_name = sensor_type_to_name(sensor_type)
|
|
if sensor_type == sensor_type_miflora:
|
|
config_section = sensor_type_miflora
|
|
mac_regexp = "C4:7C:8D:[0-9A-F]{2}:[0-9A-F]{2}:[0-9A-F]{2}"
|
|
elif sensor_type == sensor_type_mitempbt:
|
|
config_section = sensor_type_mitempbt
|
|
mac_regexp = "4C:65:A8:[0-9A-F]{2}:[0-9A-F]{2}:[0-9A-F]{2}"
|
|
else:
|
|
print_line('Unknown device type: {}'.format(sensor_type), error=True, sd_notify=True)
|
|
sys.exit(1)
|
|
|
|
for [name, mac] in config[config_section].items():
|
|
if not re.match(mac_regexp, mac):
|
|
print_line('The MAC address "{}" seems to be in the wrong format. Please check your configuration'.format(mac), error=True, sd_notify=True)
|
|
sys.exit(1)
|
|
|
|
if '@' in name:
|
|
name_pretty, location_pretty = name.split('@')
|
|
else:
|
|
name_pretty, location_pretty = name, ''
|
|
name_clean = clean_identifier(name_pretty)
|
|
location_clean = clean_identifier(location_pretty)
|
|
|
|
sensor = dict()
|
|
print('Adding sensor to device list and testing connection ...')
|
|
print('Name: "{}"'.format(name_pretty))
|
|
#print_line('Attempting initial connection to Mi Flora sensor "{}" ({})'.format(name_pretty, mac), console=False, sd_notify=True)
|
|
|
|
if sensor_type == sensor_type_miflora:
|
|
sensor_poller = MiFloraPoller(mac=mac, backend=BluepyBackend, cache_timeout=miflora_cache_timeout, retries=3, adapter=used_adapter)
|
|
elif sensor_type == sensor_type_mitempbt:
|
|
sensor_poller = MiThermometerPoller(mac=mac, backend=BluepyBackend, cache_timeout=mitempbt_cache_timeout, retries=3, adapter=used_adapter)
|
|
|
|
sensor['poller'] = sensor_poller
|
|
sensor['name_pretty'] = name_pretty
|
|
sensor['mac'] = sensor_poller._mac
|
|
sensor['refresh'] = miflora_sleep_period if (sensor_type == sensor_type_miflora) else mitempbt_sleep_period
|
|
sensor['location_clean'] = location_clean
|
|
sensor['location_pretty'] = location_pretty
|
|
sensor['stats'] = {"count": 0, "success": 0, "failure": 0}
|
|
try:
|
|
sensor_poller.fill_cache()
|
|
sensor_poller.parameter_value(MI_BATTERY)
|
|
sensor['firmware'] = sensor_poller.firmware_version()
|
|
except (IOError, BluetoothBackendException, BTLEException, RuntimeError, BrokenPipeError):
|
|
print_line('Initial connection to {} sensor "{}" ({}) failed.'.format(sensor_type_name, name_pretty, mac), error=True, sd_notify=True)
|
|
else:
|
|
print('Internal name: "{}"'.format(name_clean))
|
|
print('Device name: "{}"'.format(sensor_poller.name()))
|
|
print('MAC address: {}'.format(sensor_poller._mac))
|
|
print('Firmware: {}'.format(sensor_poller.firmware_version()))
|
|
print_line('Initial connection to {} sensor "{}" ({}) successful'.format(sensor_type_name, name_pretty, mac), sd_notify=True)
|
|
print()
|
|
sensors[name_clean] = sensor
|
|
|
|
# Pool & publish information from sensors
|
|
def pool_sensors(sensor_type, sensors, parameters):
|
|
sensor_type_name = sensor_type_to_name(sensor_type)
|
|
for [sensor_name, sensor] in sensors.items():
|
|
data = dict()
|
|
attempts = 2
|
|
sensor['poller']._cache = None
|
|
sensor['poller']._last_read = None
|
|
sensor['stats']['count'] = sensor['stats']['count'] + 1
|
|
print_line('Retrieving data from {} sensor "{}" ...'.format(sensor_type_name, sensor['name_pretty']))
|
|
while attempts != 0 and not sensor['poller']._cache:
|
|
try:
|
|
sensor['poller'].fill_cache()
|
|
sensor['poller'].parameter_value(MI_BATTERY)
|
|
except (IOError, BluetoothBackendException, BTLEException, RuntimeError, BrokenPipeError) as e:
|
|
attempts = attempts - 1
|
|
if attempts > 0:
|
|
print_line('Retrying ...', warning = True)
|
|
if len(str(e))>0:
|
|
print_line('\tDue to: {}'.format(e), error=True)
|
|
sensor['poller']._cache = None
|
|
sensor['poller']._last_read = None
|
|
|
|
if not sensor['poller']._cache:
|
|
sensor['stats']['failure'] = sensor['stats']['failure'] + 1
|
|
print_line('Failed to retrieve data from {} sensor "{}" ({}), success rate: {:.0%}'.format(
|
|
sensor_type_name, sensor['name_pretty'], sensor['mac'], sensor['stats']['success']/sensor['stats']['count']
|
|
), error = True, sd_notify = True)
|
|
print()
|
|
continue
|
|
else:
|
|
sensor['stats']['success'] = sensor['stats']['success'] + 1
|
|
|
|
for param,_ in parameters.items():
|
|
data[param] = sensor['poller'].parameter_value(param)
|
|
print_line('Result: {}'.format(json.dumps(data)))
|
|
|
|
if reporting_mode == 'mqtt-json':
|
|
print_line('Publishing to MQTT topic "{}/{}"'.format(base_topic, sensor_name))
|
|
mqtt_client.publish('{}/{}'.format(base_topic, sensor_name), json.dumps(data))
|
|
sleep(0.5) # some slack for the publish roundtrip and callback function
|
|
elif reporting_mode == 'thingsboard-json':
|
|
print_line('Publishing to MQTT topic "{}" username "{}"'.format(base_topic, sensor_name))
|
|
mqtt_client.username_pw_set(sensor_name)
|
|
mqtt_client.reconnect()
|
|
sleep(1.0)
|
|
mqtt_client.publish('{}'.format(base_topic), json.dumps(data))
|
|
sleep(0.5) # some slack for the publish roundtrip and callback function
|
|
elif reporting_mode == 'homeassistant-mqtt':
|
|
print_line('Publishing to MQTT topic "{}/sensor/{}/state"'.format(base_topic, sensor_name).lower())
|
|
mqtt_client.publish('{}/sensor/{}/state'.format(base_topic, sensor_name).lower(), json.dumps(data), retain=True)
|
|
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, sensor_name))
|
|
for [param, value] in data.items():
|
|
mqtt_client.publish('{}/{}/{}/{}'.format(base_topic, device_id, sensor_name, param), value, 1, retain=False)
|
|
sleep(0.5) # some slack for the publish roundtrip and callback function
|
|
elif reporting_mode == 'mqtt-smarthome':
|
|
for [param, value] in data.items():
|
|
print_line('Publishing data to MQTT topic "{}/status/{}/{}"'.format(base_topic, sensor_name, param))
|
|
payload = dict()
|
|
payload['val'] = value
|
|
payload['ts'] = int(round(time() * 1000))
|
|
mqtt_client.publish('{}/status/{}/{}'.format(base_topic, sensor_name, param), json.dumps(payload), retain=True)
|
|
sleep(0.5) # some slack for the publish roundtrip and callback function
|
|
elif reporting_mode == 'wirenboard-mqtt':
|
|
for [param, value] in data.items():
|
|
print_line('Publishing data to MQTT topic "/devices/{}/controls/{}"'.format(sensor_name, param))
|
|
mqtt_client.publish('/devices/{}/controls/{}'.format(sensor_name, param), value, retain=True)
|
|
mqtt_client.publish('/devices/{}/controls/{}'.format(sensor_name, 'timestamp'), strftime('%Y-%m-%d %H:%M:%S', localtime()), retain=True)
|
|
sleep(0.5) # some slack for the publish roundtrip and callback function
|
|
elif reporting_mode == 'json':
|
|
data['timestamp'] = strftime('%Y-%m-%d %H:%M:%S', localtime())
|
|
data['name'] = sensor_name
|
|
data['name_pretty'] = sensor['name_pretty']
|
|
data['mac'] = sensor['mac']
|
|
data['firmware'] = sensor['firmware']
|
|
print('Data for "{}": {}'.format(sensor_name, json.dumps(data)))
|
|
else:
|
|
raise NameError('Unexpected reporting_mode.')
|
|
print()
|
|
|
|
class sensorPooler(threading.Thread):
|
|
def __init__(self, sensor_type, sensors, sensor_parameters, sleep_period, hciLock):
|
|
threading.Thread.__init__(self)
|
|
self.sensor_type = sensor_type
|
|
self.sensor_type_name = sensor_type_to_name(sensor_type)
|
|
self.sensors = sensors
|
|
self.sensor_parameters = sensor_parameters
|
|
self.sleep_period = sleep_period
|
|
self.hciLock = hciLock
|
|
self.daemon = True
|
|
self.start()
|
|
|
|
def run(self):
|
|
print_line('Worker for {} sensors started'.format(self.sensor_type_name), sd_notify=True)
|
|
# Sensor data retrieving and publishing
|
|
while True:
|
|
with self.hciLock:
|
|
pool_sensors(self.sensor_type, self.sensors, self.sensor_parameters)
|
|
|
|
if daemon_enabled:
|
|
print_line('Sleeping for {} ({} seconds) ...'.format(self.sensor_type_name, self.sleep_period))
|
|
print()
|
|
sleep(self.sleep_period)
|
|
else:
|
|
break
|
|
|
|
print_line('Execution finished for {}'.format(self.sensor_type_name), sd_notify=True)
|
|
print()
|
|
|
|
# Load configuration file
|
|
config_dir = parse_args.config_dir
|
|
|
|
config = ConfigParser(delimiters=('=', ))
|
|
config.optionxform = str
|
|
config.read([os.path.join(config_dir, 'config.ini.dist'), os.path.join(config_dir, 'config.ini')])
|
|
|
|
reporting_mode = config['General'].get('reporting_method', 'mqtt-json')
|
|
used_adapter = config['General'].get('adapter', 'hci0')
|
|
daemon_enabled = config['Daemon'].getboolean('enabled', True)
|
|
|
|
if reporting_mode == 'mqtt-homie':
|
|
default_base_topic = 'homie'
|
|
elif reporting_mode == 'homeassistant-mqtt':
|
|
default_base_topic = 'homeassistant'
|
|
elif reporting_mode == 'thingsboard-json':
|
|
default_base_topic = 'v1/devices/me/telemetry'
|
|
elif reporting_mode == 'wirenboard-mqtt':
|
|
default_base_topic = ''
|
|
else:
|
|
default_base_topic = 'misensor'
|
|
|
|
base_topic = config['MQTT'].get('base_topic', default_base_topic).lower()
|
|
device_id = config['MQTT'].get('homie_device_id', 'miflora-mqtt-daemon').lower()
|
|
miflora_sleep_period = config['Daemon'].getint('period_miflora', 300)
|
|
miflora_cache_timeout = miflora_sleep_period - 1
|
|
mitempbt_sleep_period = config['Daemon'].getint('period_mitempbt', 60)
|
|
mitempbt_cache_timeout = mitempbt_sleep_period - 1
|
|
|
|
# Check configuration
|
|
if reporting_mode not in ['mqtt-json', 'mqtt-homie', 'json', 'mqtt-smarthome', 'homeassistant-mqtt', 'thingsboard-json', 'wirenboard-mqtt']:
|
|
print_line('Configuration parameter reporting_mode set to an invalid value', error=True, sd_notify=True)
|
|
sys.exit(1)
|
|
if not config[sensor_type_miflora] and not config[sensor_type_mitempbt]:
|
|
print_line('No sensors found in configuration file "config.ini"', error=True, sd_notify=True)
|
|
sys.exit(1)
|
|
if reporting_mode == 'wirenboard-mqtt' and base_topic:
|
|
print_line('Parameter "base_topic" ignored for "reporting_method = wirenboard-mqtt"', warning=True, sd_notify=True)
|
|
|
|
|
|
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']:
|
|
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)
|
|
|
|
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.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
|
|
)
|
|
|
|
if config['MQTT'].get('username'):
|
|
mqtt_client.username_pw_set(config['MQTT'].get('username'), config['MQTT'].get('password', None))
|
|
try:
|
|
mqtt_client.connect(config['MQTT'].get('hostname', 'localhost'),
|
|
port=config['MQTT'].getint('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:
|
|
if reporting_mode == 'mqtt-smarthome':
|
|
mqtt_client.publish('{}/connected'.format(base_topic), payload='1', retain=True)
|
|
if reporting_mode != 'thingsboard-json':
|
|
mqtt_client.loop_start()
|
|
sleep(1.0) # some slack to establish the connection
|
|
|
|
sd_notifier.notify('READY=1')
|
|
|
|
# Initialize Mi sensors
|
|
mifloras = OrderedDict()
|
|
init_sensors(sensor_type_miflora, mifloras)
|
|
mitempbts = OrderedDict()
|
|
init_sensors(sensor_type_mitempbt, mitempbts)
|
|
|
|
# openHAB items generation
|
|
if parse_args.gen_openhab:
|
|
sensors_to_openhab_items(sensor_type_miflora, mifloras, miflora_parameters, reporting_mode)
|
|
sensors_to_openhab_items(sensor_type_mitempbt, mitempbts, mitempbt_parameters, reporting_mode)
|
|
sys.exit(0)
|
|
|
|
# Discovery Announcement
|
|
if reporting_mode == 'mqtt-json':
|
|
print_line('Announcing {}/{} devices to MQTT broker for auto-discovery ...'.format(sensor_name_miflora,sensor_name_mitempbt))
|
|
sensors_info = dict()
|
|
for [sensor_name, sensor] in chain(mifloras.items(),mitempbts.items()):
|
|
sensor_info = {key: value for key, value in sensor.items() if key not in ['poller', 'stats']}
|
|
sensor_info['topic'] = '{}/{}'.format(base_topic, sensor_name)
|
|
sensors_info[sensor_name] = sensor_info
|
|
mqtt_client.publish('{}/$announce'.format(base_topic), json.dumps(sensors_info), retain=True)
|
|
sleep(0.5) # some slack for the publish roundtrip and callback function
|
|
print()
|
|
elif reporting_mode == 'mqtt-homie':
|
|
print_line('Announcing {}/{} devices to MQTT broker for auto-discovery ...'.format(sensor_name_miflora,sensor_name_mitempbt))
|
|
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([sensor_name for [sensor_name, sensor] in chain(mifloras.items(), mitempbts.items())])
|
|
mqtt_client.publish('{}/{}/$nodes'.format(base_topic, device_id), nodes_list, 1, True)
|
|
|
|
for [sensor_name, sensor] in mifloras.items():
|
|
topic_path = '{}/{}/{}'.format(base_topic, device_id, sensor_name)
|
|
mqtt_client.publish('{}/$name'.format(topic_path), sensor['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)
|
|
|
|
for [sensor_name, sensor] in mitempbts.items():
|
|
topic_path = '{}/{}/{}'.format(base_topic, device_id, sensor_name)
|
|
mqtt_client.publish('{}/$name'.format(topic_path), sensor['name_pretty'], 1, True)
|
|
mqtt_client.publish('{}/$type'.format(topic_path), 'mitempbt', 1, True)
|
|
mqtt_client.publish('{}/$properties'.format(topic_path), 'battery,humidity,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('{}/humidity/$settable'.format(topic_path), 'false', 1, True)
|
|
mqtt_client.publish('{}/humidity/$unit'.format(topic_path), 'percent', 1, True)
|
|
mqtt_client.publish('{}/humidity/$datatype'.format(topic_path), 'int', 1, True)
|
|
mqtt_client.publish('{}/humidity/$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)
|
|
sleep(0.5) # some slack for the publish roundtrip and callback function
|
|
print()
|
|
elif reporting_mode == 'homeassistant-mqtt':
|
|
print_line('Announcing {}/{} devices to MQTT broker for auto-discovery ...'.format(sensor_name_miflora,sensor_name_mitempbt))
|
|
for [flora_name, flora] in mifloras.items():
|
|
topic_path = '{}/sensor/{}'.format(base_topic, flora_name)
|
|
base_payload = {
|
|
"state_topic": "{}/state".format(topic_path).lower()
|
|
}
|
|
for sensor, params in miflora_parameters.items():
|
|
payload = dict(base_payload.items())
|
|
payload['unit_of_measurement'] = params['unit']
|
|
payload['value_template'] = "{{ value_json.%s }}" % (sensor, )
|
|
payload['name'] = "{} {}".format(flora_name, sensor.title())
|
|
if 'device_class' in params:
|
|
payload['device_class'] = params['device_class']
|
|
mqtt_client.publish('{}/{}_{}/config'.format(topic_path, flora_name, sensor).lower(), json.dumps(payload), 1, True)
|
|
for [mitempbt_name, mitempbt] in mitempbts.items():
|
|
topic_path = '{}/sensor/{}'.format(base_topic, mitempbt_name)
|
|
base_payload = {
|
|
"state_topic": "{}/state".format(topic_path).lower()
|
|
}
|
|
for sensor, params in mitempbt_parameters.items():
|
|
payload = dict(base_payload.items())
|
|
payload['unit_of_measurement'] = params['unit']
|
|
payload['value_template'] = "{{ value_json.%s }}" % (sensor, )
|
|
payload['name'] = "{} {}".format(mitempbt_name, sensor.title())
|
|
if 'device_class' in params:
|
|
payload['device_class'] = params['device_class']
|
|
mqtt_client.publish('{}/{}_{}/config'.format(topic_path, mitempbt_name, sensor).lower(), json.dumps(payload), 1, True)
|
|
elif reporting_mode == 'wirenboard-mqtt':
|
|
print_line('Announcing {}/{} devices to MQTT broker for auto-discovery ...'.format(sensor_name_miflora, sensor_name_mitempbt))
|
|
for [flora_name, flora] in mifloras.items():
|
|
mqtt_client.publish('/devices/{}/meta/name'.format(flora_name), flora_name, 1, True)
|
|
topic_path = '/devices/{}/controls'.format(flora_name)
|
|
mqtt_client.publish('{}/battery/meta/type'.format(topic_path), 'value', 1, True)
|
|
mqtt_client.publish('{}/battery/meta/units'.format(topic_path), '%', 1, True)
|
|
mqtt_client.publish('{}/conductivity/meta/type'.format(topic_path), 'value', 1, True)
|
|
mqtt_client.publish('{}/conductivity/meta/units'.format(topic_path), 'µS/cm', 1, True)
|
|
mqtt_client.publish('{}/light/meta/type'.format(topic_path), 'value', 1, True)
|
|
mqtt_client.publish('{}/light/meta/units'.format(topic_path), 'lux', 1, True)
|
|
mqtt_client.publish('{}/moisture/meta/type'.format(topic_path), 'rel_humidity', 1, True)
|
|
mqtt_client.publish('{}/temperature/meta/type'.format(topic_path), 'temperature', 1, True)
|
|
mqtt_client.publish('{}/timestamp/meta/type'.format(topic_path), 'text', 1, True)
|
|
sleep(0.5) # some slack for the publish roundtrip and callback function
|
|
|
|
for [mitempbt_name, mitempbt] in mitempbts.items():
|
|
mqtt_client.publish('/devices/{}/meta/name'.format(mitempbt_name), mitempbt_name, 1, True)
|
|
topic_path = '/devices/{}/controls'.format(mitempbt_name)
|
|
mqtt_client.publish('{}/battery/meta/type'.format(topic_path), 'value', 1, True)
|
|
mqtt_client.publish('{}/battery/meta/units'.format(topic_path), '%', 1, True)
|
|
mqtt_client.publish('{}/humidity/meta/type'.format(topic_path), 'rel_humidity', 1, True)
|
|
mqtt_client.publish('{}/temperature/meta/type'.format(topic_path), 'temperature', 1, True)
|
|
mqtt_client.publish('{}/timestamp/meta/type'.format(topic_path), 'text', 1, True)
|
|
sleep(0.5) # some slack for the publish roundtrip and callback function
|
|
print()
|
|
|
|
print_line('Initialization complete, starting MQTT publish loop', console=False, sd_notify=True)
|
|
|
|
hciLock = threading.Lock()
|
|
threads = []
|
|
|
|
if len(mifloras) != 0:
|
|
threads.append(sensorPooler(sensor_type_miflora, mifloras, miflora_parameters, miflora_sleep_period, hciLock))
|
|
|
|
if len(mitempbts) != 0:
|
|
threads.append(sensorPooler(sensor_type_mitempbt, mitempbts, mitempbt_parameters, mitempbt_sleep_period, hciLock))
|
|
|
|
for thread in threads:
|
|
thread.join()
|
|
|
|
print ("Exiting Main Thread")
|
|
if reporting_mode == 'mqtt-json':
|
|
mqtt_client.disconnect()
|