ha integration
This commit is contained in:
parent
932168125c
commit
ed44da8427
BIN
.media/ha_integration.png
Normal file
BIN
.media/ha_integration.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
@ -38,6 +38,7 @@ Python gateway for the [custom firmware](https://github.com/atc1441/ATC_MiThermo
|
|||||||
**Current State**
|
**Current State**
|
||||||
|
|
||||||

|

|
||||||
|

|
||||||
|
|
||||||
## Getting started
|
## Getting started
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -3,21 +3,23 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from .discover_gateways import start_discovery_client
|
from api import *
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from pprint import pformat
|
|
||||||
|
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.components.sensor import PLATFORM_SCHEMA
|
from homeassistant.components.sensor import PLATFORM_SCHEMA
|
||||||
from homeassistant.components.sensor import SensorEntity
|
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.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.typing import ConfigType
|
from homeassistant.helpers.typing import ConfigType
|
||||||
from homeassistant.helpers.typing import DiscoveryInfoType
|
from homeassistant.helpers.typing import DiscoveryInfoType
|
||||||
|
|
||||||
_LOGGER = logging.getLogger("atc_mi_thermometer_gateway")
|
_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(
|
def setup_platform(
|
||||||
@ -26,26 +28,55 @@ def setup_platform(
|
|||||||
add_entities: AddEntitiesCallback,
|
add_entities: AddEntitiesCallback,
|
||||||
discovery_info: DiscoveryInfoType | None = None):
|
discovery_info: DiscoveryInfoType | None = None):
|
||||||
|
|
||||||
_LOGGER.info(pformat(config))
|
devices = get_deices(gateway=config[CONF_HOST])
|
||||||
gateway = start_discovery_client()
|
for device in devices:
|
||||||
|
device_state = get_state(gateway=config[CONF_HOST], device=device)
|
||||||
add_entities([Gateway(ip=gateway[0])])
|
add_entities([MiThermometer(mac=device, state=device_state)])
|
||||||
|
|
||||||
|
|
||||||
class Gateway(SensorEntity):
|
class MiThermometer(SensorEntity):
|
||||||
def __init__(self, ip:str):
|
def __init__(self, mac:str, state:EntityState):
|
||||||
self._ip = ip
|
self._mac = mac
|
||||||
self._online = False
|
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
|
@property
|
||||||
def ip(self):
|
def mac(self):
|
||||||
return self._ip
|
return self._mac
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def online(self):
|
def online(self):
|
||||||
return self._online
|
return self._online
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def discovered_devices(self):
|
def get_name(self):
|
||||||
return self._discovered_devices
|
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
|
||||||
|
|||||||
@ -24,11 +24,8 @@ class API:
|
|||||||
@self.app.route('/api')
|
@self.app.route('/api')
|
||||||
@cross_origin()
|
@cross_origin()
|
||||||
def serve_root():
|
def serve_root():
|
||||||
workdir, filename = os.path.split(os.path.abspath(__file__))
|
|
||||||
update = check_for_update()
|
update = check_for_update()
|
||||||
files = os.listdir(f'{workdir}/data')
|
|
||||||
for file in files:
|
|
||||||
if not file.endswith('.json'): files.remove(file)
|
|
||||||
root_dict = {
|
root_dict = {
|
||||||
"version": {
|
"version": {
|
||||||
"version": os.getenv('VERSION'),
|
"version": os.getenv('VERSION'),
|
||||||
@ -40,7 +37,7 @@ class API:
|
|||||||
"name": os.getenv('NAME'),
|
"name": os.getenv('NAME'),
|
||||||
"info": {
|
"info": {
|
||||||
"files_size_sum": self.get_file_size(),
|
"files_size_sum": self.get_file_size(),
|
||||||
"files": files
|
"devices": self.get_files()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return jsonify(root_dict)
|
return jsonify(root_dict)
|
||||||
@ -49,14 +46,15 @@ class API:
|
|||||||
@cross_origin()
|
@cross_origin()
|
||||||
def serve_json(path):
|
def serve_json(path):
|
||||||
workdir, filename = os.path.split(os.path.abspath(__file__))
|
workdir, filename = os.path.split(os.path.abspath(__file__))
|
||||||
|
path += '.json'
|
||||||
return send_from_directory(f'{workdir}/data', path)
|
return send_from_directory(f'{workdir}/data', path)
|
||||||
|
|
||||||
@self.app.route('/api/state/<path:path>')
|
@self.app.route('/api/state/<path:path>')
|
||||||
@cross_origin()
|
@cross_origin()
|
||||||
def serve_entity_state(path):
|
def serve_entity_state(path):
|
||||||
workdir, filename = os.path.split(os.path.abspath(__file__))
|
workdir, filename = os.path.split(os.path.abspath(__file__))
|
||||||
|
path += '.json'
|
||||||
state = json.load(open(f'{workdir}/data/{path}', mode='r'))
|
state = json.load(open(f'{workdir}/data/{path}', mode='r'))
|
||||||
print(state['measurements'])
|
|
||||||
entity_state = LogEntry(data=state)
|
entity_state = LogEntry(data=state)
|
||||||
return jsonify(entity_state.to_json())
|
return jsonify(entity_state.to_json())
|
||||||
|
|
||||||
@ -74,6 +72,15 @@ class API:
|
|||||||
if file.endswith('.json'): sizes += os.path.getsize(f'{workdir}/data/{file}')
|
if file.endswith('.json'): sizes += os.path.getsize(f'{workdir}/data/{file}')
|
||||||
return sizes
|
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 = API()
|
||||||
api.app.run(host='0.0.0.0', port=8000)
|
api.app.run(host='0.0.0.0', port=8000)
|
||||||
|
|||||||
@ -43,12 +43,12 @@ class LogEntry:
|
|||||||
def __init__(self, data):
|
def __init__(self, data):
|
||||||
self.name = data['name']
|
self.name = data['name']
|
||||||
self.room = data['room']
|
self.room = data['room']
|
||||||
self.timestamp = data['measurements'][:-1]['timestamp']
|
self.timestamp = data['measurements'][-1]['timestamp']
|
||||||
self.temperature = data['measurements'][:-1]['temperature']
|
self.temperature = data['measurements'][-1]['temperature']
|
||||||
self.humidity = data['measurements'][:-1]['humidity']
|
self.humidity = data['measurements'][-1]['humidity']
|
||||||
self.battery_percent = data['measurements'][:-1]['battery_percent']
|
self.battery_percent = data['measurements'][-1]['battery_percent']
|
||||||
self.battery_volt = data['measurements'][:-1]['battery_volt']
|
self.battery_volt = data['measurements'][-1]['battery_volt']
|
||||||
self.rssi = data['measurements'][:-1]['rssi']
|
self.rssi = data['measurements'][-1]['rssi']
|
||||||
|
|
||||||
def to_json(self):
|
def to_json(self):
|
||||||
return {
|
return {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user