diff --git a/.media/ha_integration.png b/.media/ha_integration.png new file mode 100644 index 0000000..2492504 Binary files /dev/null and b/.media/ha_integration.png differ 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 {