From 932168125cbcc0ee56343ea4cfd10b18612b2b5d Mon Sep 17 00:00:00 2001 From: DasMoorhuhn Date: Thu, 12 Sep 2024 01:04:25 +0200 Subject: [PATCH] add ha integration and added entity state api endpoint --- home_assistant_integration/__init__.py | 10 ++++ home_assistant_integration/api.py | 0 .../discover_gateways.py | 49 ++++++++++++++++++ home_assistant_integration/sensor.py | 51 +++++++++++++++++++ python/src/api_endpoints.py | 12 +++++ python/src/data_class.py | 25 +++++++++ 6 files changed, 147 insertions(+) create mode 100644 home_assistant_integration/__init__.py create mode 100644 home_assistant_integration/api.py create mode 100644 home_assistant_integration/discover_gateways.py create mode 100644 home_assistant_integration/sensor.py diff --git a/home_assistant_integration/__init__.py b/home_assistant_integration/__init__.py new file mode 100644 index 0000000..0a98898 --- /dev/null +++ b/home_assistant_integration/__init__.py @@ -0,0 +1,10 @@ +DOMAIN = "atc_mi_thermometer_gateway" + +from .discover_gateways import start_discovery_client + + +def setup(hass, config): + hass.states.set("hello_state.world", "Paulus") + + # Return boolean to indicate that initialization was successful. + return True diff --git a/home_assistant_integration/api.py b/home_assistant_integration/api.py new file mode 100644 index 0000000..e69de29 diff --git a/home_assistant_integration/discover_gateways.py b/home_assistant_integration/discover_gateways.py new file mode 100644 index 0000000..1f2afd7 --- /dev/null +++ b/home_assistant_integration/discover_gateways.py @@ -0,0 +1,49 @@ +from time import time +from socket import socket +from socket import AF_INET +from socket import SOCK_DGRAM +from socket import IPPROTO_UDP +from socket import SOL_SOCKET +from socket import SO_REUSEADDR +from socket import SO_BROADCAST + + +DISCOVERY_ACK = 'IP_DISCOVERY_ACK'.encode() # ACK for broadcast +DISCOVERY_OK = 'IP_DISCOVERY_OK'.encode() # Not used yet +DISCOVERY_RSP_GTW = 'IP_DISCOVERY_RSP_GTW'.encode() # RSP for gateway +DISCOVERY_RSP_MSH = 'IP_DISCOVERY_RSP_MSH'.encode() # RSP for mesh +DISCOVERY_TIMEOUT = 0.5 +SOCKET_TIMEOUT = 0.2 +PORT_SERVER = 9434 +PORT_CLIENT = 9435 + + +def start_discovery_client(): + sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP) + sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) + sock.setsockopt(SOL_SOCKET, SO_BROADCAST, 1) + sock.settimeout(SOCKET_TIMEOUT) + server_address = ('255.255.255.255', PORT_SERVER) + start_time_stamp = time() + delta = round(time() - start_time_stamp, 2) + discovered_devices = [] + + try: + sock.bind(('', PORT_CLIENT)) + + while delta <= DISCOVERY_TIMEOUT: + delta = round(time() - start_time_stamp, 2) + sock.sendto(DISCOVERY_ACK, server_address) + data, addr = sock.recvfrom(4096) + + if str(addr[0]) in discovered_devices: continue + if data == DISCOVERY_RSP_GTW: + discovered_devices.append(str(addr[0])) + + except Exception as err: + print(err) + + finally: + sock.close() + return discovered_devices[0] + diff --git a/home_assistant_integration/sensor.py b/home_assistant_integration/sensor.py new file mode 100644 index 0000000..d46e042 --- /dev/null +++ b/home_assistant_integration/sensor.py @@ -0,0 +1,51 @@ +"""Platform for light integration.""" +from __future__ import annotations + +import logging + +from .discover_gateways import start_discovery_client +import voluptuous as vol + +from pprint import pformat + +import homeassistant.helpers.config_validation as cv +from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import SensorEntity +from homeassistant.const import CONF_NAME, CONF_MAC +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.typing import ConfigType +from homeassistant.helpers.typing import DiscoveryInfoType + +_LOGGER = logging.getLogger("atc_mi_thermometer_gateway") + + +def setup_platform( + hass: HomeAssistant, + config: ConfigType, + add_entities: AddEntitiesCallback, + discovery_info: DiscoveryInfoType | None = None): + + _LOGGER.info(pformat(config)) + gateway = start_discovery_client() + + add_entities([Gateway(ip=gateway[0])]) + + +class Gateway(SensorEntity): + def __init__(self, ip:str): + self._ip = ip + self._online = False + self._discovered_devices = [] + + @property + def ip(self): + return self._ip + + @property + def online(self): + return self._online + + @property + def discovered_devices(self): + return self._discovered_devices diff --git a/python/src/api_endpoints.py b/python/src/api_endpoints.py index efc3732..5988a6e 100644 --- a/python/src/api_endpoints.py +++ b/python/src/api_endpoints.py @@ -1,3 +1,4 @@ +import json import os from flask import Flask from flask import jsonify @@ -6,6 +7,8 @@ from flask_cors import CORS from flask_cors import cross_origin from check_update import check_for_update +from data_class import LogEntry + class API: """ @@ -48,6 +51,15 @@ class API: workdir, filename = os.path.split(os.path.abspath(__file__)) return send_from_directory(f'{workdir}/data', path) + @self.app.route('/api/state/') + @cross_origin() + def serve_entity_state(path): + workdir, filename = os.path.split(os.path.abspath(__file__)) + state = json.load(open(f'{workdir}/data/{path}', mode='r')) + print(state['measurements']) + entity_state = LogEntry(data=state) + return jsonify(entity_state.to_json()) + @self.app.route('/charts') @cross_origin() def serve_index(): diff --git a/python/src/data_class.py b/python/src/data_class.py index 490c059..b4504b1 100644 --- a/python/src/data_class.py +++ b/python/src/data_class.py @@ -37,3 +37,28 @@ class Data: "battery_volt": self.battery_volt, "count": self.count, } + + +class LogEntry: + def __init__(self, data): + self.name = data['name'] + self.room = data['room'] + self.timestamp = data['measurements'][:-1]['timestamp'] + self.temperature = data['measurements'][:-1]['temperature'] + self.humidity = data['measurements'][:-1]['humidity'] + self.battery_percent = data['measurements'][:-1]['battery_percent'] + self.battery_volt = data['measurements'][:-1]['battery_volt'] + self.rssi = data['measurements'][:-1]['rssi'] + + def to_json(self): + return { + "name": self.name, + "room": self.room, + "timestamp": self.timestamp, + "temperature": self.temperature, + "humidity": self.humidity, + "battery_percent": self.battery_percent, + "battery_volt": self.battery_volt, + "rssi": self.rssi + } +