From 26a396f8da41b69e00901666707b650397136768 Mon Sep 17 00:00:00 2001 From: Scott Wallace Date: Wed, 16 Jun 2021 07:00:43 +0100 Subject: [PATCH] Initial commit --- .gitignore | 3 ++ .gitmodules | 3 ++ Dockerfile | 24 +++++++++++++ bluetooth_utils | 1 + docker-compose.yaml | 18 ++++++++++ entrypoint.sh | 6 ++++ get_data.py | 87 +++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 2 ++ 8 files changed, 144 insertions(+) create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 Dockerfile create mode 160000 bluetooth_utils create mode 100644 docker-compose.yaml create mode 100644 entrypoint.sh create mode 100644 get_data.py create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2e5f17d --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.pyenv/ +.vscode/ +__pycache__/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..c5a502f --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "bluetooth_utils"] + path = bluetooth_utils + url = https://github.com/colin-guyon/py-bluetooth-utils.git diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..8e4a891 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,24 @@ +FROM python:3.8-slim-buster + +# Keeps Python from generating .pyc files in the container +ENV PYTHONDONTWRITEBYTECODE 1 + +# Turns off buffering for easier container logging +ENV PYTHONUNBUFFERED 1 + +RUN apt update +RUN apt install -y gcc libbluetooth-dev bluez bluetooth +RUN apt clean + +ADD requirements.txt . +RUN python -m pip install -r requirements.txt + +RUN useradd -d /app -m appuser +WORKDIR /app +USER appuser + +COPY get_data.py /app +COPY bluetooth_utils /app/bluetooth_utils +COPY entrypoint.sh /app + +ENTRYPOINT ["/bin/bash", "/app/entrypoint.sh"] diff --git a/bluetooth_utils b/bluetooth_utils new file mode 160000 index 0000000..416a892 --- /dev/null +++ b/bluetooth_utils @@ -0,0 +1 @@ +Subproject commit 416a8925682d6ee4ad607936d897646a3a83a331 diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..e9df607 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,18 @@ +--- +version: "3" +services: + xiaomi2mqtt: + image: xiaomi2mqtt:latest + container_name: xiaomi2mqtt + user: root:root + environment: + - TZ=Europe/London + - MQTT_HOST=mqtt.lan + devices: + - /dev/bus/usb + network_mode: host + cap_add: + - SYS_ADMIN + - CAP_SYSLOG + - NET_ADMIN + restart: unless-stopped diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100644 index 0000000..9b3eaea --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +service dbus start +service bluetooth start + +python get_data.py ${@} diff --git a/get_data.py b/get_data.py new file mode 100644 index 0000000..844cc3c --- /dev/null +++ b/get_data.py @@ -0,0 +1,87 @@ +""" +Read Xiaomi LYWSD03MMC advertised packets and send them to MQTT +""" +import os +from datetime import datetime + +import bluetooth._bluetooth as bluez +import paho.mqtt.publish as publish + +from bluetooth_utils.bluetooth_utils import (disable_le_scan, enable_le_scan, + parse_le_advertising_events, + raw_packet_to_str, toggle_device) + + +def send_to_mqtt(data: dict): + """ + Send data from LYWSD03MMC to MQTT + """ + msgs = [ + { + 'topic': f'sensors/home/{data["mac"]}/temperature', + 'payload': data['temperature'], + }, + { + 'topic': f'sensors/home/{data["mac"]}/humidity', + 'payload': data['humidity'], + }, + { + 'topic': f'sensors/home/{data["mac"]}/battery', + 'payload': data['battery'], + }, + ] + publish.multiple(msgs, hostname=os.environ.get('MQTT_HOST', 'mqtt')) + + +def le_advertise_packet_handler(mac: str, _, data: bytes, __): + """ + Handle new Xiaomi LYWSD03MMC BTLE advertise packet + """ + data_str = raw_packet_to_str(data) + # Check for ATC preamble + if data_str[0:2] == '11' and data_str[6:10] == '1a18': + temp = int(data_str[22:26], 16) / 10 + hum = int(data_str[26:28], 16) + batt = int(data_str[28:30], 16) + send_to_mqtt( + { + 'timestamp': datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + 'mac': mac, + 'temperature': temp, + 'humidity': hum, + 'battery': batt, + } + ) + + +if __name__ == '__main__': + + def main(): + """ + Main program + """ + + # Use 0 for hci0 + dev_id = 0 + toggle_device(dev_id, True) + + try: + sock = bluez.hci_open_dev(dev_id) # pylint: disable=c-extension-no-member + except: + print(f'Cannot open bluetooth device {dev_id}') + raise + + # Set filter to "True" to see only one packet per device + enable_le_scan(sock, filter_duplicates=False) + + try: + # Called on new LE packet + parse_le_advertising_events( + sock, handler=le_advertise_packet_handler, debug=False + ) + # Scan until Ctrl-C + except KeyboardInterrupt: + disable_le_scan(sock) + + +main() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..eed4c9f --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +paho-mqtt +pybluez