From ed44da84279ab0f73a6ac471800314bd2b44fa1b Mon Sep 17 00:00:00 2001 From: DasMoorhuhn Date: Thu, 12 Sep 2024 03:21:40 +0200 Subject: [PATCH] ha integration --- .media/ha_integration.png | Bin 0 -> 12386 bytes README.md | 1 + home_assistant_integration/api.py | 66 +++++++++++++++++++++++++++ home_assistant_integration/sensor.py | 63 ++++++++++++++++++------- python/src/api_endpoints.py | 19 +++++--- python/src/data_class.py | 12 ++--- 6 files changed, 133 insertions(+), 28 deletions(-) create mode 100644 .media/ha_integration.png diff --git a/.media/ha_integration.png b/.media/ha_integration.png new file mode 100644 index 0000000000000000000000000000000000000000..2492504f4591d82e83277efec9cc6a68d5295d0f GIT binary patch literal 12386 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 literal 0 HcmV?d00001 diff --git a/README.md b/README.md index ff7c4ee..ce192c3 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ Python gateway for the [custom firmware](https://github.com/atc1441/ATC_MiThermo **Current State** ![](.media/demo.gif) +![](.media/ha_integration.pngatc) ## Getting started diff --git a/home_assistant_integration/api.py b/home_assistant_integration/api.py index e69de29..ace1a76 100644 --- a/home_assistant_integration/api.py +++ b/home_assistant_integration/api.py @@ -0,0 +1,66 @@ +import json + +from requests import api +from dataclasses import dataclass + + +@dataclass +class EntityState: + timestamp: str + name:str + room:str + temperature:float + humidity:int + battery_percent:int + battery_volt:float + rssi:int + + +def test_api(gateway): + request = f'http://{gateway}:8000/api' + response = api.get(request) + if not response.ok: return False + response_json = json.loads(response.text) + version = response_json['version']['version'] + return True + + +def get_state(gateway, device) -> EntityState | None: + request = f'http://{gateway}:8000/api/state/{device}' + response = api.get(request) + if not response.ok: return None + response_json = json.loads(response.text) + return EntityState(response_json['timestamp'], + response_json['name'], + response_json['room'], + response_json['temperature'], + response_json['humidity'], + response_json['battery_percent'], + response_json['battery_volt'], + response_json['rssi']) + + +def get_deices(gateway) -> list | None: + request = f'http://{gateway}:8000/api' + response = api.get(request) + if not response.ok: return None + response_json = json.loads(response.text) + return response_json['info']['devices'] + + +def get_device(gateway, device) -> str | None: + devices = get_deices(gateway) + if device in devices: + index = devices.index(device) + return devices[index] + return None + + +def get_entity_state(entity_mac, gateway): + device = get_device(gateway, entity_mac) + if device is None: return None + entity_state = get_state(gateway, device) + return entity_state + + + diff --git a/home_assistant_integration/sensor.py b/home_assistant_integration/sensor.py index d46e042..c326c69 100644 --- a/home_assistant_integration/sensor.py +++ b/home_assistant_integration/sensor.py @@ -3,21 +3,23 @@ from __future__ import annotations import logging -from .discover_gateways import start_discovery_client +from api import * 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.const import CONF_NAME, CONF_HOST 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") +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Optional(CONF_NAME): cv.string, + vol.Required(CONF_HOST): cv.string, +}) def setup_platform( @@ -26,26 +28,55 @@ def setup_platform( add_entities: AddEntitiesCallback, discovery_info: DiscoveryInfoType | None = None): - _LOGGER.info(pformat(config)) - gateway = start_discovery_client() - - add_entities([Gateway(ip=gateway[0])]) + devices = get_deices(gateway=config[CONF_HOST]) + for device in devices: + device_state = get_state(gateway=config[CONF_HOST], device=device) + add_entities([MiThermometer(mac=device, state=device_state)]) -class Gateway(SensorEntity): - def __init__(self, ip:str): - self._ip = ip +class MiThermometer(SensorEntity): + def __init__(self, mac:str, state:EntityState): + self._mac = mac self._online = False - self._discovered_devices = [] + self._last_update = "" + self._temperature = state.temperature + self._humidity = state.temperature + self._battery_percentage = state.battery_percent + self._rssi = state.rssi + self._name = state.name + self._room = state.room @property - def ip(self): - return self._ip + def mac(self): + return self._mac @property def online(self): return self._online @property - def discovered_devices(self): - return self._discovered_devices + def get_name(self): + return self._name + + @property + def get_room(self): + return self._room + + @property + def get_temperature(self): + return self._temperature + + @property + def get_humidity(self): + return self._humidity + + @property + def get_battery_percentage(self): + return self._battery_percentage + + @property + def get_rssi(self): + return self._rssi + + def update(self) -> None: + pass diff --git a/python/src/api_endpoints.py b/python/src/api_endpoints.py index 5988a6e..47f874b 100644 --- a/python/src/api_endpoints.py +++ b/python/src/api_endpoints.py @@ -24,11 +24,8 @@ class API: @self.app.route('/api') @cross_origin() def serve_root(): - workdir, filename = os.path.split(os.path.abspath(__file__)) update = check_for_update() - files = os.listdir(f'{workdir}/data') - for file in files: - if not file.endswith('.json'): files.remove(file) + root_dict = { "version": { "version": os.getenv('VERSION'), @@ -40,7 +37,7 @@ class API: "name": os.getenv('NAME'), "info": { "files_size_sum": self.get_file_size(), - "files": files + "devices": self.get_files() } } return jsonify(root_dict) @@ -49,14 +46,15 @@ class API: @cross_origin() def serve_json(path): workdir, filename = os.path.split(os.path.abspath(__file__)) + path += '.json' 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__)) + path += '.json' state = json.load(open(f'{workdir}/data/{path}', mode='r')) - print(state['measurements']) entity_state = LogEntry(data=state) return jsonify(entity_state.to_json()) @@ -74,6 +72,15 @@ class API: if file.endswith('.json'): sizes += os.path.getsize(f'{workdir}/data/{file}') return sizes + def get_files(self): + workdir, filename = os.path.split(os.path.abspath(__file__)) + files = os.listdir(f'{workdir}/data') + files_list = [] + for file in files: + if file.endswith('.json'): + files_list.append(file.replace('.json', '')) + return files_list + api = API() api.app.run(host='0.0.0.0', port=8000) diff --git a/python/src/data_class.py b/python/src/data_class.py index b4504b1..6374b8a 100644 --- a/python/src/data_class.py +++ b/python/src/data_class.py @@ -43,12 +43,12 @@ 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'] + 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 {