miflora-mqtt-daemon.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  1. #!/usr/bin/env python3
  2. import ssl
  3. import sys
  4. import re
  5. import json
  6. import os.path
  7. import os
  8. import argparse
  9. from time import time, sleep, localtime, strftime
  10. from collections import OrderedDict
  11. from colorama import init as colorama_init
  12. from colorama import Fore, Back, Style
  13. from configparser import ConfigParser
  14. from unidecode import unidecode
  15. from miflora.miflora_poller import MiFloraPoller, MI_BATTERY, MI_CONDUCTIVITY, MI_LIGHT, MI_MOISTURE, MI_TEMPERATURE
  16. from btlewrap import BluepyBackend, GatttoolBackend, BluetoothBackendException
  17. import paho.mqtt.client as mqtt
  18. import sdnotify
  19. project_name = 'Xiaomi Mi Flora Plant Sensor MQTT Client/Daemon'
  20. project_url = 'https://github.com/ThomDietrich/miflora-mqtt-daemon'
  21. parameters = OrderedDict([
  22. (MI_LIGHT, dict(name="LightIntensity", name_pretty='Sunlight Intensity', typeformat='%d', unit='lux', device_class="illuminance")),
  23. (MI_TEMPERATURE, dict(name="AirTemperature", name_pretty='Air Temperature', typeformat='%.1f', unit='°C', device_class="temperature")),
  24. (MI_MOISTURE, dict(name="SoilMoisture", name_pretty='Soil Moisture', typeformat='%d', unit='%', device_class="humidity")),
  25. (MI_CONDUCTIVITY, dict(name="SoilConductivity", name_pretty='Soil Conductivity/Fertility', typeformat='%d', unit='µS/cm')),
  26. (MI_BATTERY, dict(name="Battery", name_pretty='Sensor Battery Level', typeformat='%d', unit='%', device_class="battery"))
  27. ])
  28. if False:
  29. # will be caught by python 2.7 to be illegal syntax
  30. print('Sorry, this script requires a python3 runtime environemt.', file=sys.stderr)
  31. # Argparse
  32. parser = argparse.ArgumentParser(description=project_name, epilog='For further details see: ' + project_url)
  33. parser.add_argument('--gen-openhab', help='generate openHAB items based on configured sensors', action='store_true')
  34. parser.add_argument('--config_dir', help='set directory where config.ini is located', default=sys.path[0])
  35. parse_args = parser.parse_args()
  36. # Intro
  37. colorama_init()
  38. print(Fore.GREEN + Style.BRIGHT)
  39. print(project_name)
  40. print('Source:', project_url)
  41. print(Style.RESET_ALL)
  42. # Systemd Service Notifications - https://github.com/bb4242/sdnotify
  43. sd_notifier = sdnotify.SystemdNotifier()
  44. # Logging function
  45. def print_line(text, error = False, warning=False, sd_notify=False, console=True):
  46. timestamp = strftime('%Y-%m-%d %H:%M:%S', localtime())
  47. if console:
  48. if error:
  49. print(Fore.RED + Style.BRIGHT + '[{}] '.format(timestamp) + Style.RESET_ALL + '{}'.format(text) + Style.RESET_ALL, file=sys.stderr)
  50. elif warning:
  51. print(Fore.YELLOW + '[{}] '.format(timestamp) + Style.RESET_ALL + '{}'.format(text) + Style.RESET_ALL)
  52. else:
  53. print(Fore.GREEN + '[{}] '.format(timestamp) + Style.RESET_ALL + '{}'.format(text) + Style.RESET_ALL)
  54. timestamp_sd = strftime('%b %d %H:%M:%S', localtime())
  55. if sd_notify:
  56. sd_notifier.notify('STATUS={} - {}.'.format(timestamp_sd, unidecode(text)))
  57. # Identifier cleanup
  58. def clean_identifier(name):
  59. clean = name.strip()
  60. for this, that in [[' ', '-'], ['ä', 'ae'], ['Ä', 'Ae'], ['ö', 'oe'], ['Ö', 'Oe'], ['ü', 'ue'], ['Ü', 'Ue'], ['ß', 'ss']]:
  61. clean = clean.replace(this, that)
  62. clean = unidecode(clean)
  63. return clean
  64. # Eclipse Paho callbacks - http://www.eclipse.org/paho/clients/python/docs/#callbacks
  65. def on_connect(client, userdata, flags, rc):
  66. if rc == 0:
  67. print_line('MQTT connection established', console=True, sd_notify=True)
  68. print()
  69. else:
  70. print_line('Connection error with result code {} - {}'.format(str(rc), mqtt.connack_string(rc)), error=True)
  71. #kill main thread
  72. os._exit(1)
  73. def on_publish(client, userdata, mid):
  74. #print_line('Data successfully published.')
  75. pass
  76. def flores_to_openhab_items(flores, reporting_mode):
  77. print_line('Generating openHAB items. Copy to your configuration and modify as needed...')
  78. items = list()
  79. items.append('// miflora.items - Generated by miflora-mqtt-daemon.')
  80. items.append('// Adapt to your needs! Things you probably want to modify:')
  81. items.append('// Room group names, icons,')
  82. items.append('// "gAll", "broker", "UnknownRoom"')
  83. items.append('')
  84. items.append('// Mi Flora specific groups')
  85. items.append('Group gMiFlora "All Mi Flora sensors and elements" (gAll)')
  86. for param, param_properties in parameters.items():
  87. items.append('Group g{} "Mi Flora {} elements" (gAll, gMiFlora)'.format(param_properties['name'], param_properties['name_pretty']))
  88. if reporting_mode == 'mqtt-json':
  89. for [flora_name, flora] in flores.items():
  90. location = flora['location_clean'] if flora['location_clean'] else 'UnknownRoom'
  91. items.append('\n// Mi Flora "{}" ({})'.format(flora['name_pretty'], flora['mac']))
  92. items.append('Group g{}{} "Mi Flora Sensor {}" (gMiFlora, g{})'.format(location, flora_name, flora['name_pretty'], location))
  93. for [param, param_properties] in parameters.items():
  94. basic = 'Number {}_{}_{}'.format(location, flora_name, param_properties['name'])
  95. label = '"{} {} {} [{} {}]"'.format(location, flora['name_pretty'], param_properties['name_pretty'], param_properties['typeformat'], param_properties['unit'].replace('%', '%%'))
  96. details = '<text> (g{}{}, g{})'.format(location, flora_name, param_properties['name'])
  97. channel = '{{mqtt="<[broker:{}/{}:state:JSONPATH($.{})]"}}'.format(base_topic, flora_name, param)
  98. items.append(' '.join([basic, label, details, channel]))
  99. items.append('')
  100. print('\n'.join(items))
  101. #elif reporting_mode == 'mqtt-homie':
  102. else:
  103. raise IOError('Given reporting_mode not supported for the export to openHAB items')
  104. # Load configuration file
  105. config_dir = parse_args.config_dir
  106. config = ConfigParser(delimiters=('=', ), inline_comment_prefixes=('#'))
  107. config.optionxform = str
  108. try:
  109. with open(os.path.join(config_dir, 'config.ini')) as config_file:
  110. config.readfp(config_file)
  111. except IOError:
  112. print_line('No configuration file "config.ini"', error=True, sd_notify=True)
  113. sys.exit(1)
  114. reporting_mode = config['General'].get('reporting_method', 'mqtt-json')
  115. used_adapter = config['General'].get('adapter', 'hci0')
  116. daemon_enabled = config['Daemon'].getboolean('enabled', True)
  117. if reporting_mode == 'mqtt-homie':
  118. default_base_topic = 'homie'
  119. elif reporting_mode == 'homeassistant-mqtt':
  120. default_base_topic = 'homeassistant'
  121. elif reporting_mode == 'thingsboard-json':
  122. default_base_topic = 'v1/devices/me/telemetry'
  123. elif reporting_mode == 'wirenboard-mqtt':
  124. default_base_topic = ''
  125. else:
  126. default_base_topic = 'miflora'
  127. base_topic = config['MQTT'].get('base_topic', default_base_topic).lower()
  128. device_id = config['MQTT'].get('homie_device_id', 'miflora-mqtt-daemon').lower()
  129. sleep_period = config['Daemon'].getint('period', 300)
  130. miflora_cache_timeout = sleep_period - 1
  131. # Check configuration
  132. if reporting_mode not in ['mqtt-json', 'mqtt-homie', 'json', 'mqtt-smarthome', 'homeassistant-mqtt', 'thingsboard-json', 'wirenboard-mqtt']:
  133. print_line('Configuration parameter reporting_mode set to an invalid value', error=True, sd_notify=True)
  134. sys.exit(1)
  135. if not config['Sensors']:
  136. print_line('No sensors found in configuration file "config.ini"', error=True, sd_notify=True)
  137. sys.exit(1)
  138. if reporting_mode == 'wirenboard-mqtt' and base_topic:
  139. print_line('Parameter "base_topic" ignored for "reporting_method = wirenboard-mqtt"', warning=True, sd_notify=True)
  140. print_line('Configuration accepted', console=False, sd_notify=True)
  141. # MQTT connection
  142. if reporting_mode in ['mqtt-json', 'mqtt-homie', 'mqtt-smarthome', 'homeassistant-mqtt', 'thingsboard-json', 'wirenboard-mqtt']:
  143. print_line('Connecting to MQTT broker ...')
  144. mqtt_client = mqtt.Client()
  145. mqtt_client.on_connect = on_connect
  146. mqtt_client.on_publish = on_publish
  147. if reporting_mode == 'mqtt-json':
  148. mqtt_client.will_set('{}/$announce'.format(base_topic), payload='{}', retain=True)
  149. elif reporting_mode == 'mqtt-homie':
  150. mqtt_client.will_set('{}/{}/$online'.format(base_topic, device_id), payload='false', retain=True)
  151. elif reporting_mode == 'mqtt-smarthome':
  152. mqtt_client.will_set('{}/connected'.format(base_topic), payload='0', retain=True)
  153. if config['MQTT'].getboolean('tls', False):
  154. # According to the docs, setting PROTOCOL_SSLv23 "Selects the highest protocol version
  155. # that both the client and server support. Despite the name, this option can select
  156. # “TLS” protocols as well as “SSL”" - so this seems like a resonable default
  157. mqtt_client.tls_set(
  158. ca_certs=config['MQTT'].get('tls_ca_cert', None),
  159. keyfile=config['MQTT'].get('tls_keyfile', None),
  160. certfile=config['MQTT'].get('tls_certfile', None),
  161. tls_version=ssl.PROTOCOL_SSLv23
  162. )
  163. mqtt_username = os.environ.get("MQTT_USERNAME", config['MQTT'].get('username'))
  164. mqtt_password = os.environ.get("MQTT_PASSWORD", config['MQTT'].get('password', None))
  165. if mqtt_username:
  166. mqtt_client.username_pw_set(mqtt_username, mqtt_password)
  167. try:
  168. mqtt_client.connect(os.environ.get('MQTT_HOSTNAME', config['MQTT'].get('hostname', 'localhost')),
  169. port=int(os.environ.get('MQTT_PORT', config['MQTT'].get('port', '1883'))),
  170. keepalive=config['MQTT'].getint('keepalive', 60))
  171. except:
  172. print_line('MQTT connection error. Please check your settings in the configuration file "config.ini"', error=True, sd_notify=True)
  173. sys.exit(1)
  174. else:
  175. if reporting_mode == 'mqtt-smarthome':
  176. mqtt_client.publish('{}/connected'.format(base_topic), payload='1', retain=True)
  177. if reporting_mode != 'thingsboard-json':
  178. mqtt_client.loop_start()
  179. sleep(1.0) # some slack to establish the connection
  180. sd_notifier.notify('READY=1')
  181. # Initialize Mi Flora sensors
  182. flores = OrderedDict()
  183. for [name, mac] in config['Sensors'].items():
  184. if not re.match("[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}", mac.lower()):
  185. print_line('The MAC address "{}" seems to be in the wrong format. Please check your configuration'.format(mac), error=True, sd_notify=True)
  186. sys.exit(1)
  187. if '@' in name:
  188. name_pretty, location_pretty = name.split('@')
  189. else:
  190. name_pretty, location_pretty = name, ''
  191. name_clean = clean_identifier(name_pretty)
  192. location_clean = clean_identifier(location_pretty)
  193. flora = dict()
  194. print('Adding sensor to device list and testing connection ...')
  195. print('Name: "{}"'.format(name_pretty))
  196. #print_line('Attempting initial connection to Mi Flora sensor "{}" ({})'.format(name_pretty, mac), console=False, sd_notify=True)
  197. flora_poller = MiFloraPoller(mac=mac, backend=BluepyBackend, cache_timeout=miflora_cache_timeout, retries=3, adapter=used_adapter)
  198. flora['poller'] = flora_poller
  199. flora['name_pretty'] = name_pretty
  200. flora['mac'] = flora_poller._mac
  201. flora['refresh'] = sleep_period
  202. flora['location_clean'] = location_clean
  203. flora['location_pretty'] = location_pretty
  204. flora['stats'] = {"count": 0, "success": 0, "failure": 0}
  205. try:
  206. flora_poller.fill_cache()
  207. flora_poller.parameter_value(MI_LIGHT)
  208. flora['firmware'] = flora_poller.firmware_version()
  209. except (IOError, BluetoothBackendException):
  210. print_line('Initial connection to Mi Flora sensor "{}" ({}) failed.'.format(name_pretty, mac), error=True, sd_notify=True)
  211. else:
  212. print('Internal name: "{}"'.format(name_clean))
  213. print('Device name: "{}"'.format(flora_poller.name()))
  214. print('MAC address: {}'.format(flora_poller._mac))
  215. print('Firmware: {}'.format(flora_poller.firmware_version()))
  216. print_line('Initial connection to Mi Flora sensor "{}" ({}) successful'.format(name_pretty, mac), sd_notify=True)
  217. if int(flora_poller.firmware_version().replace(".", "")) < 319:
  218. print_line('Mi Flora sensor with a firmware version before 3.1.9 is not supported. Please update now.'.format(name_pretty, mac), error=True, sd_notify=True)
  219. print()
  220. flores[name_clean] = flora
  221. # openHAB items generation
  222. if parse_args.gen_openhab:
  223. flores_to_openhab_items(flores, reporting_mode)
  224. sys.exit(0)
  225. # Discovery Announcement
  226. if reporting_mode == 'mqtt-json':
  227. print_line('Announcing Mi Flora devices to MQTT broker for auto-discovery ...')
  228. flores_info = dict()
  229. for [flora_name, flora] in flores.items():
  230. flora_info = {key: value for key, value in flora.items() if key not in ['poller', 'stats']}
  231. flora_info['topic'] = '{}/{}'.format(base_topic, flora_name)
  232. flores_info[flora_name] = flora_info
  233. mqtt_client.publish('{}/$announce'.format(base_topic), json.dumps(flores_info), retain=True)
  234. sleep(0.5) # some slack for the publish roundtrip and callback function
  235. print()
  236. elif reporting_mode == 'mqtt-homie':
  237. print_line('Announcing Mi Flora devices to MQTT broker for auto-discovery ...')
  238. mqtt_client.publish('{}/{}/$homie'.format(base_topic, device_id), '2.1.0-alpha', 1, True)
  239. mqtt_client.publish('{}/{}/$online'.format(base_topic, device_id), 'true', 1, True)
  240. mqtt_client.publish('{}/{}/$name'.format(base_topic, device_id), device_id, 1, True)
  241. mqtt_client.publish('{}/{}/$fw/version'.format(base_topic, device_id), flora['firmware'], 1, True)
  242. nodes_list = ','.join([flora_name for [flora_name, flora] in flores.items()])
  243. mqtt_client.publish('{}/{}/$nodes'.format(base_topic, device_id), nodes_list, 1, True)
  244. for [flora_name, flora] in flores.items():
  245. topic_path = '{}/{}/{}'.format(base_topic, device_id, flora_name)
  246. mqtt_client.publish('{}/$name'.format(topic_path), flora['name_pretty'], 1, True)
  247. mqtt_client.publish('{}/$type'.format(topic_path), 'miflora', 1, True)
  248. mqtt_client.publish('{}/$properties'.format(topic_path), 'battery,conductivity,light,moisture,temperature', 1, True)
  249. mqtt_client.publish('{}/battery/$settable'.format(topic_path), 'false', 1, True)
  250. mqtt_client.publish('{}/battery/$unit'.format(topic_path), 'percent', 1, True)
  251. mqtt_client.publish('{}/battery/$datatype'.format(topic_path), 'int', 1, True)
  252. mqtt_client.publish('{}/battery/$range'.format(topic_path), '0:100', 1, True)
  253. mqtt_client.publish('{}/conductivity/$settable'.format(topic_path), 'false', 1, True)
  254. mqtt_client.publish('{}/conductivity/$unit'.format(topic_path), 'µS/cm', 1, True)
  255. mqtt_client.publish('{}/conductivity/$datatype'.format(topic_path), 'int', 1, True)
  256. mqtt_client.publish('{}/conductivity/$range'.format(topic_path), '0:*', 1, True)
  257. mqtt_client.publish('{}/light/$settable'.format(topic_path), 'false', 1, True)
  258. mqtt_client.publish('{}/light/$unit'.format(topic_path), 'lux', 1, True)
  259. mqtt_client.publish('{}/light/$datatype'.format(topic_path), 'int', 1, True)
  260. mqtt_client.publish('{}/light/$range'.format(topic_path), '0:50000', 1, True)
  261. mqtt_client.publish('{}/moisture/$settable'.format(topic_path), 'false', 1, True)
  262. mqtt_client.publish('{}/moisture/$unit'.format(topic_path), 'percent', 1, True)
  263. mqtt_client.publish('{}/moisture/$datatype'.format(topic_path), 'int', 1, True)
  264. mqtt_client.publish('{}/moisture/$range'.format(topic_path), '0:100', 1, True)
  265. mqtt_client.publish('{}/temperature/$settable'.format(topic_path), 'false', 1, True)
  266. mqtt_client.publish('{}/temperature/$unit'.format(topic_path), '°C', 1, True)
  267. mqtt_client.publish('{}/temperature/$datatype'.format(topic_path), 'float', 1, True)
  268. mqtt_client.publish('{}/temperature/$range'.format(topic_path), '*', 1, True)
  269. sleep(0.5) # some slack for the publish roundtrip and callback function
  270. print()
  271. elif reporting_mode == 'homeassistant-mqtt':
  272. print_line('Announcing Mi Flora devices to MQTT broker for auto-discovery ...')
  273. for [flora_name, flora] in flores.items():
  274. state_topic = '{}/sensor/{}'.format(base_topic, flora_name.lower())
  275. for [sensor, params] in parameters.items():
  276. discovery_topic = 'homeassistant/sensor/{}_{}/config'.format(flora_name, sensor).lower()
  277. payload = OrderedDict()
  278. payload['name'] = "{} {}".format(flora_name, sensor.title())
  279. payload['unique_id'] = "{}-{}".format(flora['mac'].lower().replace(":", ""), sensor)
  280. payload['unit_of_measurement'] = params['unit']
  281. if 'device_class' in params:
  282. payload['device_class'] = params['device_class']
  283. payload['state_topic'] = state_topic
  284. payload['value_template'] = "{{{{ value_json.{} }}}}".format(sensor)
  285. payload['device'] = {
  286. 'identifiers' : ["MiFlora{}".format(flora['mac'].lower().replace(":", ""))],
  287. 'connections' : [["mac", flora['mac'].lower()]],
  288. 'manufacturer' : 'Xiaomi',
  289. 'name' : flora_name,
  290. 'model' : 'MiFlora Plant Sensor (HHCCJCY01)',
  291. 'sw_version': flora['firmware']
  292. }
  293. mqtt_client.publish(discovery_topic, json.dumps(payload), 1, True)
  294. elif reporting_mode == 'wirenboard-mqtt':
  295. print_line('Announcing Mi Flora devices to MQTT broker for auto-discovery ...')
  296. for [flora_name, flora] in flores.items():
  297. mqtt_client.publish('/devices/{}/meta/name'.format(flora_name), flora_name, 1, True)
  298. topic_path = '/devices/{}/controls'.format(flora_name)
  299. mqtt_client.publish('{}/battery/meta/type'.format(topic_path), 'value', 1, True)
  300. mqtt_client.publish('{}/battery/meta/units'.format(topic_path), '%', 1, True)
  301. mqtt_client.publish('{}/conductivity/meta/type'.format(topic_path), 'value', 1, True)
  302. mqtt_client.publish('{}/conductivity/meta/units'.format(topic_path), 'µS/cm', 1, True)
  303. mqtt_client.publish('{}/light/meta/type'.format(topic_path), 'value', 1, True)
  304. mqtt_client.publish('{}/light/meta/units'.format(topic_path), 'lux', 1, True)
  305. mqtt_client.publish('{}/moisture/meta/type'.format(topic_path), 'rel_humidity', 1, True)
  306. mqtt_client.publish('{}/temperature/meta/type'.format(topic_path), 'temperature', 1, True)
  307. mqtt_client.publish('{}/timestamp/meta/type'.format(topic_path), 'text', 1, True)
  308. sleep(0.5) # some slack for the publish roundtrip and callback function
  309. print()
  310. print_line('Initialization complete, starting MQTT publish loop', console=False, sd_notify=True)
  311. # Sensor data retrieval and publication
  312. while True:
  313. for [flora_name, flora] in flores.items():
  314. data = dict()
  315. attempts = 2
  316. flora['poller']._cache = None
  317. flora['poller']._last_read = None
  318. flora['stats']['count'] = flora['stats']['count'] + 1
  319. print_line('Retrieving data from sensor "{}" ...'.format(flora['name_pretty']))
  320. while attempts != 0 and not flora['poller']._cache:
  321. try:
  322. flora['poller'].fill_cache()
  323. flora['poller'].parameter_value(MI_LIGHT)
  324. except (IOError, BluetoothBackendException):
  325. attempts = attempts - 1
  326. if attempts > 0:
  327. print_line('Retrying ...', warning = True)
  328. flora['poller']._cache = None
  329. flora['poller']._last_read = None
  330. if not flora['poller']._cache:
  331. flora['stats']['failure'] = flora['stats']['failure'] + 1
  332. print_line('Failed to retrieve data from Mi Flora sensor "{}" ({}), success rate: {:.0%}'.format(
  333. flora['name_pretty'], flora['mac'], flora['stats']['success']/flora['stats']['count']
  334. ), error = True, sd_notify = True)
  335. print()
  336. continue
  337. else:
  338. flora['stats']['success'] = flora['stats']['success'] + 1
  339. for param,_ in parameters.items():
  340. data[param] = flora['poller'].parameter_value(param)
  341. print_line('Result: {}'.format(json.dumps(data)))
  342. if reporting_mode == 'mqtt-json':
  343. print_line('Publishing to MQTT topic "{}/{}"'.format(base_topic, flora_name))
  344. mqtt_client.publish('{}/{}'.format(base_topic, flora_name), json.dumps(data))
  345. sleep(0.5) # some slack for the publish roundtrip and callback function
  346. elif reporting_mode == 'thingsboard-json':
  347. print_line('Publishing to MQTT topic "{}" username "{}"'.format(base_topic, flora_name))
  348. mqtt_client.username_pw_set(flora_name)
  349. mqtt_client.reconnect()
  350. sleep(1.0)
  351. mqtt_client.publish('{}'.format(base_topic), json.dumps(data))
  352. sleep(0.5) # some slack for the publish roundtrip and callback function
  353. elif reporting_mode == 'homeassistant-mqtt':
  354. print_line('Publishing to MQTT topic "{}/sensor/{}"'.format(base_topic, flora_name.lower()))
  355. mqtt_client.publish('{}/sensor/{}'.format(base_topic, flora_name.lower()), json.dumps(data))
  356. sleep(0.5) # some slack for the publish roundtrip and callback function
  357. elif reporting_mode == 'mqtt-homie':
  358. print_line('Publishing data to MQTT base topic "{}/{}/{}"'.format(base_topic, device_id, flora_name))
  359. for [param, value] in data.items():
  360. mqtt_client.publish('{}/{}/{}/{}'.format(base_topic, device_id, flora_name, param), value, 1, True)
  361. sleep(0.5) # some slack for the publish roundtrip and callback function
  362. elif reporting_mode == 'mqtt-smarthome':
  363. for [param, value] in data.items():
  364. print_line('Publishing data to MQTT topic "{}/status/{}/{}"'.format(base_topic, flora_name, param))
  365. payload = dict()
  366. payload['val'] = value
  367. payload['ts'] = int(round(time() * 1000))
  368. mqtt_client.publish('{}/status/{}/{}'.format(base_topic, flora_name, param), json.dumps(payload), retain=True)
  369. sleep(0.5) # some slack for the publish roundtrip and callback function
  370. elif reporting_mode == 'wirenboard-mqtt':
  371. for [param, value] in data.items():
  372. print_line('Publishing data to MQTT topic "/devices/{}/controls/{}"'.format(flora_name, param))
  373. mqtt_client.publish('/devices/{}/controls/{}'.format(flora_name, param), value, retain=True)
  374. mqtt_client.publish('/devices/{}/controls/{}'.format(flora_name, 'timestamp'), strftime('%Y-%m-%d %H:%M:%S', localtime()), retain=True)
  375. sleep(0.5) # some slack for the publish roundtrip and callback function
  376. elif reporting_mode == 'json':
  377. data['timestamp'] = strftime('%Y-%m-%d %H:%M:%S', localtime())
  378. data['name'] = flora_name
  379. data['name_pretty'] = flora['name_pretty']
  380. data['mac'] = flora['mac']
  381. data['firmware'] = flora['firmware']
  382. print('Data for "{}": {}'.format(flora_name, json.dumps(data)))
  383. else:
  384. raise NameError('Unexpected reporting_mode.')
  385. print()
  386. print_line('Status messages published', console=False, sd_notify=True)
  387. if daemon_enabled:
  388. print_line('Sleeping ({} seconds) ...'.format(sleep_period))
  389. sleep(sleep_period)
  390. print()
  391. else:
  392. print_line('Execution finished in non-daemon-mode', sd_notify=True)
  393. if reporting_mode == 'mqtt-json':
  394. mqtt_client.disconnect()
  395. break