mqtt-flora.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. #!/usr/bin/env python3
  2. import sys
  3. import json
  4. import os.path
  5. from time import sleep, localtime, strftime
  6. from configparser import ConfigParser
  7. from miflora.miflora_poller import MiFloraPoller, MI_BATTERY, MI_CONDUCTIVITY, MI_LIGHT, MI_MOISTURE, MI_TEMPERATURE
  8. import paho.mqtt.client as mqtt
  9. import sdnotify
  10. parameters = [MI_BATTERY, MI_CONDUCTIVITY, MI_LIGHT, MI_MOISTURE, MI_TEMPERATURE]
  11. # Intro
  12. print('Xiaomi Mi Flora Plant Sensor MQTT Client/Daemon')
  13. print('Source: https://github.com/ThomDietrich/miflora-mqtt-daemon')
  14. print()
  15. # Systemd Service Notifications - https://github.com/bb4242/sdnotify
  16. sd_notifier = sdnotify.SystemdNotifier()
  17. # Eclipse Paho callbacks - http://www.eclipse.org/paho/clients/python/docs/#callbacks
  18. def on_connect(client, userdata, flags, rc):
  19. if rc == 0:
  20. print('Connected.\n')
  21. sd_notifier.notify('STATUS=MQTT connection established')
  22. else:
  23. print('Connection error with result code {} - {}'.format(str(rc), mqtt.connack_string(rc)), file=sys.stderr)
  24. #kill main thread
  25. os._exit(1)
  26. def on_publish(client, userdata, mid):
  27. print('Data successfully published!')
  28. # Load configuration file
  29. config = ConfigParser(delimiters=('=', ))
  30. config.optionxform = str
  31. config.read([os.path.join(sys.path[0], 'config.ini'), os.path.join(sys.path[0], 'config.local.ini')])
  32. reporting_mode = config['General'].get('reporting_method', 'mqtt-json')
  33. daemon_enabled = config['Daemon'].getboolean('enabled', True)
  34. topic_prefix = config['MQTT'].get('topic_prefix', 'miflora')
  35. sleep_period = config['Daemon'].getint('period', 300)
  36. #miflora_cache_timeout = config['MiFlora'].getint('cache_timeout', 600)
  37. miflora_cache_timeout = sleep_period - 1
  38. # Check configuration
  39. if not reporting_mode in ['mqtt-json', 'json']:
  40. print('Error. Configuration parameter reporting_mode set to an invalid value.', file=sys.stderr)
  41. sys.exit(1)
  42. if not config['Sensors']:
  43. print('Error. Please add at least one sensor to the configuration file "config.ini".', file=sys.stderr)
  44. print('Scan for available Miflora sensors with "sudo hcitool lescan".', file=sys.stderr)
  45. sys.exit(1)
  46. sd_notifier.notify('STATUS=Configuration accepted')
  47. # MQTT connection
  48. if reporting_mode == 'mqtt-json':
  49. print('Connecting to MQTT broker ...')
  50. mqtt_client = mqtt.Client()
  51. mqtt_client.on_connect = on_connect
  52. mqtt_client.on_publish = on_publish
  53. if config['MQTT'].get('username'):
  54. mqtt_client.username_pw_set(config['MQTT'].get('username'), config['MQTT'].get('password', None))
  55. try:
  56. mqtt_client.connect(config['MQTT'].get('hostname', 'localhost'),
  57. port=config['MQTT'].getint('port', 1883),
  58. keepalive=config['MQTT'].getint('keepalive', 60))
  59. except:
  60. print('Error. Please check your MQTT connection settings in the configuration file "config.ini".', file=sys.stderr)
  61. sys.exit(1)
  62. else:
  63. mqtt_client.loop_start()
  64. sleep(1) # some slack to establish the connection
  65. sd_notifier.notify('READY=1')
  66. # Initialize Mi Flora sensors
  67. flores = dict()
  68. for [name, mac] in config['Sensors'].items():
  69. print('Adding device from config to Mi Flora device list ...')
  70. print('Name: "{}"'.format(name))
  71. flora_poller = MiFloraPoller(mac=mac, cache_timeout=miflora_cache_timeout, retries=9)
  72. flora_poller.fill_cache()
  73. print('Device name: "{}"'.format(flora_poller.name()))
  74. print('MAC address: {}'.format(flora_poller._mac))
  75. print('Firmware: {}'.format(flora_poller.firmware_version()))
  76. print()
  77. flores[name] = flora_poller
  78. sd_notifier.notify('STATUS=Initialization complete, starting MQTT publish loop')
  79. # Sensor data retrieval and publication
  80. while True:
  81. for [flora_name, flora_poller] in flores.items():
  82. data = dict()
  83. for param in parameters:
  84. data[param] = flora_poller.parameter_value(param)
  85. timestamp = strftime('%Y-%m-%d %H:%M:%S', localtime())
  86. if reporting_mode == 'mqtt-json':
  87. print('[{}] Attempting to publishing to MQTT topic "{}/{}" ...\nData: {}'.format(timestamp, topic_prefix, flora_name, json.dumps(data)))
  88. mqtt_client.publish('{}/{}'.format(topic_prefix, flora_name), json.dumps(data))
  89. sleep(0.5) # some slack for the publish roundtrip and callback function
  90. print()
  91. elif reporting_mode == 'json':
  92. data['timestamp'] = timestamp
  93. data['name'] = flora_name
  94. data['mac'] = flora_poller._mac
  95. print('Data:', json.dumps(data))
  96. else:
  97. raise NameError('Unexpected reporting_mode.')
  98. sd_notifier.notify('STATUS={} - Status messages published'.format(strftime('%Y-%m-%d %H:%M:%S', localtime())))
  99. if daemon_enabled:
  100. print('Sleeping ({} seconds) ...'.format(sleep_period))
  101. sleep(sleep_period)
  102. print()
  103. else:
  104. if reporting_mode == 'mqtt-json':
  105. mqtt_client.disconnect()
  106. break