Compare commits

..

10 Commits

4 changed files with 51 additions and 20 deletions

View File

@ -2,8 +2,7 @@ ARG BUILD_FROM
FROM $BUILD_FROM FROM $BUILD_FROM
# Install requirements for add-on # Install requirements for add-on
RUN apk update && apk add --no-cache python3 py-pip RUN apk update && apk add --no-cache python3 py3-pip py3-websockets py3-requests
RUN python3 -m pip install websockets requests
WORKDIR /data WORKDIR /data

View File

@ -29,3 +29,4 @@ schema:
- sensor: str - sensor: str
thermostate: str thermostate: str
update_timeout: int update_timeout: int
log_level: "str?"

View File

@ -42,7 +42,7 @@ class FritzBox:
logging.debug(f"Calculate v2 challenge: {challenge}") logging.debug(f"Calculate v2 challenge: {challenge}")
chall_regex = re.compile( chall_regex = re.compile(
"2\$(?P<iter1>[0-9a-zA-Z]+)\$(?P<salt1>[0-9a-zA-Z]+)\$(?P<iter2>[0-9a-zA-Z]+)\$(?P<salt2>[0-9a-zA-Z]+)") r"2\$(?P<iter1>[0-9a-zA-Z]+)\$(?P<salt1>[0-9a-zA-Z]+)\$(?P<iter2>[0-9a-zA-Z]+)\$(?P<salt2>[0-9a-zA-Z]+)")
chall_parts = chall_regex.match(challenge).groupdict() chall_parts = chall_regex.match(challenge).groupdict()
salt1: bytes = bytes.fromhex(chall_parts["salt1"]) salt1: bytes = bytes.fromhex(chall_parts["salt1"])

View File

@ -1,48 +1,70 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import asyncio import asyncio
import json
import logging
import os import os
import sys import sys
from fritzbox import FritzBox from fritzbox import FritzBox
from homeassistant import HomeAssistantAPI from homeassistant import HomeAssistantAPI
import logging
import json
sensor_mappings = {} sensor_mappings = {}
thermostate_mappings = {} thermostate_mappings = {}
async def handle_event(idx: int): async def handle_event(idx: int):
global ha, fb
logging.debug(f"Wait for events for {idx}") logging.debug(f"Wait for events for {idx}")
while event := await ha.events[idx].get(): while event := await ha.events[idx].get():
try: try:
entity_id = event["data"]["entity_id"] entity_id = event["data"]["entity_id"]
if entity_id in sensor_mappings.keys() or entity_id in thermostate_mappings.keys(): if (
entity_id in sensor_mappings.keys()
or entity_id in thermostate_mappings.keys()
):
state = await ha.get_device_state(entity_id) state = await ha.get_device_state(entity_id)
new_state = event["data"]["new_state"] new_state = event["data"]["new_state"]
logging.debug(f"received changed state from {entity_id}") logging.info(
if entity_id in thermostate_mappings.keys() and state["state"] != "unavailable": f"received changed state from {entity_id} {entity_id in thermostate_mappings.keys()} {state['state']} {entity_id in sensor_mappings.keys()}"
)
if (
entity_id in thermostate_mappings.keys()
and state["state"] != "unavailable"
):
therm_temp = new_state["attributes"]["current_temperature"] therm_temp = new_state["attributes"]["current_temperature"]
therm_name = new_state["attributes"]["friendly_name"] therm_name = new_state["attributes"]["friendly_name"]
sensor = thermostate_mappings[entity_id] sensor = thermostate_mappings[entity_id]
sensor_state = await ha.get_device_state(sensor) sensor_state = await ha.get_device_state(sensor)
sensor_temp = round(float(sensor_state["attributes"]["temperature"]) * 2) / 2 sensor_temp = round(float(sensor_state["state"]) * 2) / 2
logging.info(f"temps: {therm_temp} {sensor_temp}")
if therm_temp != sensor_temp: if therm_temp != sensor_temp:
logging.info(f"{therm_name}: {therm_temp}\n{sensor}: {sensor_state['attributes']['temperature']} ({sensor_temp})") logging.info(
f"{therm_name}: {therm_temp}\n{sensor}: {sensor_state['state']} ({sensor_temp})"
)
fb.correct_offset(therm_name, sensor_temp) fb.correct_offset(therm_name, sensor_temp)
elif entity_id in sensor_mappings.keys(): elif entity_id in sensor_mappings.keys():
sensor_temp = round(float(new_state["attributes"]["temperature"]) * 2) / 2 logging.info(f"here {sensor_mappings} {entity_id}")
logging.info(f"{new_state}")
sensor_temp = round(float(new_state["state"]) * 2) / 2
logging.info(f"entry: {sensor_mappings[entity_id]}")
for thermostate in sensor_mappings[entity_id]: for thermostate in sensor_mappings[entity_id]:
logging.info(thermostate)
therm_state = await ha.get_device_state(thermostate) therm_state = await ha.get_device_state(thermostate)
logging.info(f"{thermostate} {therm_state}")
if therm_state["state"] == "unavailable": if therm_state["state"] == "unavailable":
continue continue
therm_temp = float(therm_state["attributes"]["current_temperature"]) therm_temp = float(
therm_state["attributes"]["current_temperature"]
)
therm_name = therm_state["attributes"]["friendly_name"] therm_name = therm_state["attributes"]["friendly_name"]
logging.info(f"Temps: {therm_temp} {sensor_temp}")
if therm_temp != sensor_temp: if therm_temp != sensor_temp:
logging.info(f"{therm_name}: {therm_temp}\n{entity_id}: {new_state['attributes']['temperature']} ({sensor_temp})") logging.info(
f"{therm_name}: {therm_temp}\n{entity_id}: {new_state['state']} ({sensor_temp})"
)
fb.correct_offset(therm_name, sensor_temp) fb.correct_offset(therm_name, sensor_temp)
except KeyError: except KeyError:
pass pass
@ -59,23 +81,31 @@ async def init(ha: HomeAssistantAPI, fb: FritzBox):
await ha.wait_for_close() await ha.wait_for_close()
logging.info("Websocket closed, shutting down..") logging.info("Websocket closed, shutting down..")
async def main(): async def main():
config_path = sys.argv[1] config_path = sys.argv[1]
config = json.load(open(config_path)) config = json.load(open(config_path))
level = logging.INFO level = logging.INFO
if "log_level" in config: if "log_level" in config:
print(f"Setting log_level {config['log_level']}")
if config["log_level"] == "DEBUG": if config["log_level"] == "DEBUG":
level = logging.DEBUG level = logging.DEBUG
logging.basicConfig(level=level, format="[%(asctime)s] [%(levelname)s] %(message)s") logging.basicConfig(
level=level, format="[%(asctime)s] [%(levelname)s] %(message)s"
)
logging.debug(config) logging.debug(config)
global fb global fb
fb = FritzBox(url=config["fritzbox"]["url"], fb = FritzBox(
url=config["fritzbox"]["url"],
user=config["fritzbox"]["username"], user=config["fritzbox"]["username"],
password=config["fritzbox"]["password"], password=config["fritzbox"]["password"],
update_timeout=config["update_timeout"], update_timeout=config["update_timeout"],
dry_run=False) dry_run=False,
)
supervisor_url = "ws://supervisor/core/websocket" supervisor_url = "ws://supervisor/core/websocket"
if "SUPERVISOR_URL" in os.environ:
supervisor_url = os.environ["SUPERVISOR_URL"]
supervisor_token = os.environ["SUPERVISOR_TOKEN"] supervisor_token = os.environ["SUPERVISOR_TOKEN"]
global ha global ha
ha = HomeAssistantAPI(supervisor_token, supervisor_url) ha = HomeAssistantAPI(supervisor_token, supervisor_url)
@ -85,10 +115,11 @@ async def main():
sensor_mappings[mapping["sensor"]] = [] sensor_mappings[mapping["sensor"]] = []
sensor_mappings[mapping["sensor"]].append(mapping["thermostate"]) sensor_mappings[mapping["sensor"]].append(mapping["thermostate"])
thermostate_mappings[mapping["thermostate"]] = mapping["sensor"] thermostate_mappings[mapping["thermostate"]] = mapping["sensor"]
logging.debug(f"Mappings: {sensor_mappings} {thermostate_mappings}")
try: try:
await init(ha, fb) await init(ha, fb)
except KeyboardInterrupt: except KeyboardInterrupt:
pass pass
asyncio.run(main()) asyncio.run(main())