diff --git a/README.md b/README.md index ce192c3..3c16790 100644 --- a/README.md +++ b/README.md @@ -22,16 +22,15 @@ Python gateway for the [custom firmware](https://github.com/atc1441/ATC_MiThermo - Make in runnable in a docker container (because only cool people are using docker) - Make docker image smaller. I mean shiiit 1GB D: should be possible to be under 500MB (It's now around 100MB) - Implement a loop for fetching the data every X seconds +- Storing temperature, humidity and battery state as json in a text file +- Can run on Raspberry Pi (3, 4, zero w) or any other Linux driven hardware which has BLE and WiFi support **TODOs:** -- [WIP] Can run on Raspberry Pi (3, 4, zero w) or any other Linux driven hardware which has BLE and WiFi support -- [WIP] Storing temperature, humidity and battery state as json in a text file +- [WIP] MQTT publishing with discovery for homeassistant - [TODO] Make a microPython version for using the raspberry pico w or any other microcontroller with BLE and WiFi support - [TODO] Collect data from multiple devices/gateways - [TODO] Command line tool for managing the devices - [TODO] Analyzing tool for making statistics -- [TODO] HomeAssistant integration -- [TODO] MQTT publishing - [TODO] Maybe... a webinterface. But I suck at web stuff, so I don't know. - [TODO] Implement other BLE Sensors diff --git a/example.env b/example.env index 74b7ca3..b4a3a56 100644 --- a/example.env +++ b/example.env @@ -7,4 +7,6 @@ API=true DEBUG=DEBUG MODE=1 LOOP=20 -TIMEOUT=20 \ No newline at end of file +TIMEOUT=20 +MQTT_IP= +MQTT_PORT=1883 \ No newline at end of file diff --git a/python/src/log_data.py b/python/src/log_data.py index d31b87f..69124be 100644 --- a/python/src/log_data.py +++ b/python/src/log_data.py @@ -7,6 +7,18 @@ from logger import get_logger logger = get_logger(__name__) +def generate_json(device:Device): + dev, data_obj, from_config = device + return { + "timestamp": data_obj.timestamp, + "temperature": data_obj.temperature, + "humidity": data_obj.humidity, + "battery_percent": data_obj.battery_percent, + "battery_volt": data_obj.battery_volt, + "rssi": dev.rssi + } + + def log_to_json(devices): workdir, filename = os.path.split(os.path.abspath(__file__)) @@ -29,15 +41,7 @@ def log_to_json(devices): file.write(json.dumps(new_file)) data = new_file - measurements = { - "timestamp": data_obj.timestamp, - "temperature": data_obj.temperature, - "humidity": data_obj.humidity, - "battery_percent": data_obj.battery_percent, - "battery_volt": data_obj.battery_volt, - "rssi": dev.rssi - } - data['measurements'].append(measurements) + data['measurements'].append(generate_json(device)) # logger.debug(measurements) @@ -48,6 +52,5 @@ def log_to_mongodb(data): pass -def log_to_mqtt(data): - pass + diff --git a/python/src/loop.py b/python/src/loop.py index b75c2bf..95626dd 100644 --- a/python/src/loop.py +++ b/python/src/loop.py @@ -1,13 +1,25 @@ from time import sleep from log_data import log_to_json +from mqtt import publish_home_assistant_device_config +from mqtt import publish_device_state +from devices import Device +from data_class import Data + from ble_discovery import start_discovery from logger import get_logger logger = get_logger(__name__) +def publish_data(devices): + log_to_json(devices) + for device in devices: + publish_home_assistant_device_config(device) + publish_device_state(device) + + def start_loop(interval=40, timeout=20): logger.info(f"Starting loop with interval {interval}s") while True: devices = start_discovery(timeout=timeout) - log_to_json(devices) + publish_data(devices) sleep(interval) diff --git a/python/src/main.py b/python/src/main.py index 72a9395..fc5b8af 100644 --- a/python/src/main.py +++ b/python/src/main.py @@ -26,20 +26,17 @@ logger.debug(f"VERSION: {os.getenv('VERSION')}") update_state = check_for_update() print_state(update_state) -try: - if DOCKER: - logger.info('Running in Docker') +if DOCKER: + logger.info('Running in Docker') - try:INTERVAL = int(interval) - except:pass + try:INTERVAL = int(interval) + except:pass - try:TIMEOUT = int(timeout) - except:pass + try:TIMEOUT = int(timeout) + except:pass - if interval is None: log_to_json(start_discovery(timeout=TIMEOUT)) - else:start_loop(INTERVAL, TIMEOUT) + if interval is None: log_to_json(start_discovery(timeout=TIMEOUT)) + else:start_loop(INTERVAL, TIMEOUT) - else: - start_loop(interval=40) -except Exception as err: - logger.error(err) +else: + start_loop(interval=40) diff --git a/python/src/mqtt.py b/python/src/mqtt.py index 2464945..ff13343 100644 --- a/python/src/mqtt.py +++ b/python/src/mqtt.py @@ -1,15 +1,105 @@ -import paho.mqtt.client as MQTT +import os +import json +import paho.mqtt.client as mqtt -topic_root = "/atc_mithermometer_gateway" +from devices import Device +from data_class import Data +from logger import get_logger -mqtt = MQTT.Client(mqtt.CallbackAPIVersion.VERSION2) -mqtt.connect("192.168.178.140", 1883, 60) +topic_root = "homeassistant" +logger = get_logger(__name__) +client = mqtt.Client(client_id='atc_mithermometer_gateway') +IP = os.getenv('MQTT_IP') +PORT = os.getenv('MQTT_PORT') +if IP and PORT: + client.connect(IP, int(PORT), 60) -def publish_measurement(mac): - topic = f"{topic_root}/measurements/{mac}" - topic_temp = f"{topic}/temperature" - topic_humid = f"{topic}/humidity" - topic_battery = f"{topic}/battery" +def generate_json(device:Device): + dev, data_obj, from_config = device + return { + "timestamp": data_obj.timestamp, + "temperature": data_obj.temperature, + "humidity": data_obj.humidity, + "battery_percent": data_obj.battery_percent, + "battery_volt": data_obj.battery_volt, + "rssi": dev.rssi + } - mqtt.publish(topic_temp, ) + +def generate_config_payloads(device:Device): + mac = '-'.join(device.mac.split(':')[3:]) + + state_topic = f"atc/device/{mac}/state" + + device_json = { + "name": f"ATC {device.name}", + "identifiers":[f"atc-{mac}"], + } + + config_temperature_payload = { + "name": f"{device.name} temperature", + "unique_id": f"atc-{mac}-temperature", + "state_topic": state_topic, + "unit_of_measurement": "°C", + "value_template": "{{ value_json.temperature}}", + "device": device_json + } + + config_humidity_payload = { + "name": f"{device.name} humidity", + "unique_id": f"atc-{mac}-humidity", + "state_topic": state_topic, + "unit_of_measurement": "%", + "value_template": "{{ value_json.humidity}}", + "device": device_json + } + + config_battery_percent_payload = { + "name": f"{device.name} battery", + "unique_id": f"atc-{mac}-battery-percent", + "state_topic": state_topic, + "unit_of_measurement": "%", + "value_template": "{{ value_json.battery_percent}}", + "device": device_json + } + + config_rssi_payload = { + "name": f"{device.name} rssi", + "unique_id": f"atc-{mac}-rssi", + "state_topic": state_topic, + "value_template": "{{ value_json.rssi}}", + "device": device_json + } + + return [config_temperature_payload, config_humidity_payload, config_battery_percent_payload, config_rssi_payload] + + +def publish_device_state(device:Device): + dev, data_obj, from_config = device + data_obj: Data + from_config: Device + mac = '-'.join(from_config.mac.split(':')[3:]) + state_topic = f"atc/device/{mac}/state" + payload = generate_json(device) + logger.info(f"Publishing {payload}") + client.publish(state_topic, json.dumps(payload)) + + +def publish_home_assistant_device_config(device:Device): + dev, data_obj, from_config = device + data_obj: Data + from_config: Device + mac = '-'.join(from_config.mac.split(':')[3:]) + topic = f"{topic_root}/sensor/atc-{mac}/config" + + temperature_payload, humidity_payload, battery_percent, rssi_payload = generate_config_payloads(from_config) + client.publish(f'{topic}-temperature/config', json.dumps(temperature_payload)) + client.publish(f'{topic}-humidity/config', json.dumps(humidity_payload)) + client.publish(f'{topic}-battery/config', json.dumps(battery_percent)) + client.publish(f'{topic}-rssi/config', json.dumps(rssi_payload)) + + # client.publish(f'{topic}-temperature/config', json.dumps({})) + # client.publish(f'{topic}-humidity/config', json.dumps({})) + # client.publish(f'{topic}-battery/config', json.dumps({})) + # client.publish(f'{topic}-rssi/config', json.dumps({}))