now works with fb 7360
This commit is contained in:
parent
ce59adccf0
commit
924eb57b34
@ -3,7 +3,7 @@ FROM $BUILD_FROM
|
||||
|
||||
# Install requirements for add-on
|
||||
RUN apk update && apk add --no-cache python3 py-pip
|
||||
RUN python3 -m pip install websockets requests
|
||||
RUN python3 -m pip install websockets requests beautifulsoup4
|
||||
|
||||
WORKDIR /data
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
name: "Fritz!Box Temperature Sync Dev"
|
||||
description: "Sync Fritz!DECT thermostat temperatures with other sensors in Home Assistant"
|
||||
version: "0.4.3"
|
||||
version: "0.5.0"
|
||||
startup: "application"
|
||||
stage: "stable"
|
||||
slug: "fritz_temp_sync_dev"
|
||||
@ -16,6 +16,8 @@ options:
|
||||
fritzbox:
|
||||
url: "http://fritz.box"
|
||||
password: null
|
||||
verify_ssl: true
|
||||
old_fb: false
|
||||
mappings:
|
||||
- sensor: null
|
||||
thermostate: null
|
||||
@ -25,6 +27,8 @@ schema:
|
||||
url: url
|
||||
username: "str?"
|
||||
password: str
|
||||
verify_ssl: bool
|
||||
old_fb: bool
|
||||
mappings:
|
||||
- sensor: str
|
||||
thermostate: str
|
||||
|
@ -11,15 +11,19 @@ from datetime import datetime, timedelta
|
||||
from typing import Optional, Dict, List
|
||||
|
||||
import requests
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
from device import Device
|
||||
|
||||
|
||||
class FritzBox:
|
||||
def __init__(self, url: str, password: str, update_timeout: int, user: str = None, dry_run: bool = False) -> None:
|
||||
def __init__(self, url: str, password: str, update_timeout: int, user: str = None, dry_run: bool = False,
|
||||
verify_ssl: bool = True, old_fb: bool = False) -> None:
|
||||
self._endpoints = {
|
||||
"login": "login_sid.lua?version=2",
|
||||
"logout": "index.lua",
|
||||
"data": "data.lua"
|
||||
"data": "data.lua",
|
||||
"device_details": "net/home_auto_hkr_edit.lua"
|
||||
}
|
||||
self.url: str = url
|
||||
self.dry_run: bool = dry_run
|
||||
@ -30,6 +34,12 @@ class FritzBox:
|
||||
self.update_timeout: int = update_timeout
|
||||
self.update_time: Dict[str, datetime] = {}
|
||||
self.hold_connection: Optional[Task] = None
|
||||
self.verify_ssl = verify_ssl
|
||||
self.old_fb = old_fb
|
||||
|
||||
if not verify_ssl:
|
||||
import urllib3
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
|
||||
async def hold_connection_alive(self) -> None:
|
||||
while True:
|
||||
@ -72,7 +82,7 @@ class FritzBox:
|
||||
"xhrId": "first",
|
||||
"noMenuRef": 1
|
||||
}
|
||||
r = self.session.post(f"{self.url}/{self._endpoints['data']}", data=data)
|
||||
r = self.session.post(f"{self.url}/{self._endpoints['data']}", data=data, verify=self.verify_ssl)
|
||||
if len(r.history) > 0:
|
||||
if not self.login():
|
||||
logging.error("Failed to login to Fritz!Box")
|
||||
@ -82,7 +92,7 @@ class FritzBox:
|
||||
def login(self) -> bool:
|
||||
logging.info(f"Login user {self.user} to Fritz!Box")
|
||||
challenge = None
|
||||
r = self.session.get(f"{self.url}/{self._endpoints['login']}")
|
||||
r = self.session.get(f"{self.url}/{self._endpoints['login']}", verify=self.verify_ssl)
|
||||
xml = ET.fromstring(r.text)
|
||||
for elem in xml:
|
||||
if elem.tag == "SID":
|
||||
@ -106,7 +116,7 @@ class FritzBox:
|
||||
"response": response
|
||||
}
|
||||
|
||||
r = self.session.post(f"{self.url}/{self._endpoints['login']}", data=data)
|
||||
r = self.session.post(f"{self.url}/{self._endpoints['login']}", data=data, verify=self.verify_ssl)
|
||||
logging.debug(r.text)
|
||||
xml = ET.fromstring(r.text)
|
||||
for elem in xml:
|
||||
@ -125,12 +135,69 @@ class FritzBox:
|
||||
"sid": self.sid,
|
||||
"logout": 1,
|
||||
"no_sidrenew": ""}
|
||||
r = self.session.post(f"{self.url}/{self._endpoints['logout']}", data=data)
|
||||
r = self.session.post(f"{self.url}/{self._endpoints['logout']}", data=data, verify=self.verify_ssl)
|
||||
if self.hold_connection is not None:
|
||||
self.hold_connection.cancel()
|
||||
|
||||
return r.status_code == 200
|
||||
|
||||
def list_devices_old(self) -> Optional[List[Union[Device, Dict]]]:
|
||||
data = {
|
||||
"xhr": 1,
|
||||
"sid": self.sid,
|
||||
"lang": "de",
|
||||
"page": "sh",
|
||||
"xhrId": "all"
|
||||
}
|
||||
r = self.session.post(f"{self.url}/{self._endpoints['data']}", data=data, verify=self.verify_ssl)
|
||||
logging.debug(r.text[:100])
|
||||
if len(r.history) > 0:
|
||||
if self.login():
|
||||
r = self.session.post(f"{self.url}/{self._endpoints['data']}", data=data, verify=self.verify_ssl)
|
||||
else:
|
||||
return None
|
||||
|
||||
devices_overview_raw = BeautifulSoup(r.text, "html.parser")
|
||||
table_rows = devices_overview_raw.find(id="uiSmarthomeTables").table
|
||||
|
||||
devices_raw = []
|
||||
|
||||
for r in table_rows.find_all("tr"):
|
||||
name = r.findAll("td", {'class': ['name', 'cut_overflow']})
|
||||
button = r.findAll("td", {'class': 'btncolumn'})
|
||||
temperature = r.findAll("td", {'class': 'temperature'})
|
||||
|
||||
if name is None or len(name) < 1:
|
||||
continue
|
||||
if button is None or len(button) < 1:
|
||||
continue
|
||||
if temperature is None or len(temperature) < 1:
|
||||
continue
|
||||
|
||||
name = name[0].string
|
||||
id = int(button[0].button["value"])
|
||||
temperature = float(temperature[0].string.split(" ")[0].replace(",", "."))
|
||||
|
||||
request_data = {
|
||||
"device": id,
|
||||
"sid": self.sid,
|
||||
"xhr": 1
|
||||
}
|
||||
|
||||
r = self.session.get(f"{self.url}/{self._endpoints['device_details']}", params=request_data,
|
||||
verify=self.verify_ssl)
|
||||
device_content_raw = BeautifulSoup(r.text, "html.parser")
|
||||
offset = float(device_content_raw.find("input", {"type": "hidden", "name": "Offset"})["value"])
|
||||
devices_raw.append({
|
||||
"name": name,
|
||||
"display_name": name,
|
||||
"id": id,
|
||||
"temperature": temperature,
|
||||
"offset": offset
|
||||
})
|
||||
|
||||
return devices_raw
|
||||
|
||||
def list_devices(self) -> Optional[List[Device]]:
|
||||
data = {
|
||||
"xhr": 1,
|
||||
@ -139,11 +206,11 @@ class FritzBox:
|
||||
"page": "sh_dev",
|
||||
"xhrId": "all"
|
||||
}
|
||||
r = self.session.post(f"{self.url}/{self._endpoints['data']}", data=data)
|
||||
r = self.session.post(f"{self.url}/{self._endpoints['data']}", data=data, verify=self.verify_ssl)
|
||||
logging.debug(r.text[:100])
|
||||
if len(r.history) > 0:
|
||||
if self.login():
|
||||
r = self.session.post(f"{self.url}/{self._endpoints['data']}", data=data)
|
||||
r = self.session.post(f"{self.url}/{self._endpoints['data']}", data=data, verify=self.verify_ssl)
|
||||
else:
|
||||
return None
|
||||
devices: List[Device] = []
|
||||
@ -156,12 +223,18 @@ class FritzBox:
|
||||
if idx is None and name is None:
|
||||
logging.debug("No id or name given")
|
||||
return None
|
||||
|
||||
devices = self.list_devices()
|
||||
if self.old_fb:
|
||||
devices = self.list_devices_old()
|
||||
else:
|
||||
devices = self.list_devices()
|
||||
device = None
|
||||
for device in devices:
|
||||
if device.id == idx or device.display_name == name:
|
||||
break
|
||||
if isinstance(device, dict):
|
||||
if device["id"] == idx or device["display_name"] == name:
|
||||
break
|
||||
else:
|
||||
if device.id == idx or device.display_name == name:
|
||||
break
|
||||
device = None
|
||||
|
||||
if device is None:
|
||||
@ -174,27 +247,49 @@ class FritzBox:
|
||||
if self.dry_run:
|
||||
logging.warning("No updates in dry-run-mode")
|
||||
return
|
||||
data = {
|
||||
"xhr": 1,
|
||||
"sid": self.sid,
|
||||
"lang": "de",
|
||||
"device": device.id,
|
||||
"page": "home_auto_hkr_edit"
|
||||
}
|
||||
self.session.post(f"{self.url}/{self._endpoints['data']}", data=data)
|
||||
|
||||
data = {
|
||||
"xhr": 1,
|
||||
"sid": self.sid,
|
||||
"lang": "de",
|
||||
"view": "",
|
||||
"back_to_page": "sh_dev",
|
||||
"apply": "",
|
||||
"oldpage": "/net/home_auto_hkr_edit.lua"
|
||||
}
|
||||
data.update(device.to_web_data())
|
||||
old_type = isinstance(device, dict)
|
||||
if old_type:
|
||||
device_id = device["id"]
|
||||
else:
|
||||
device_id = device.id
|
||||
|
||||
self.session.post(f"{self.url}/{self._endpoints['data']}", data=data)
|
||||
if not old_type:
|
||||
data = {
|
||||
"xhr": 1,
|
||||
"sid": self.sid,
|
||||
"lang": "de",
|
||||
"device": device_id,
|
||||
"page": "home_auto_hkr_edit"
|
||||
}
|
||||
self.session.post(f"{self.url}/{self._endpoints['data']}", data=data, verify=self.verify_ssl)
|
||||
|
||||
data = {
|
||||
"xhr": 1,
|
||||
"sid": self.sid,
|
||||
"lang": "de",
|
||||
"view": "",
|
||||
"back_to_page": "sh_dev",
|
||||
"apply": "",
|
||||
"oldpage": "/net/home_auto_hkr_edit.lua"
|
||||
}
|
||||
data.update(device.to_web_data())
|
||||
|
||||
else:
|
||||
data = {
|
||||
"xhr": 1,
|
||||
"sid": self.sid,
|
||||
"lang": "de",
|
||||
"no_sidrenew": "",
|
||||
"device": device_id,
|
||||
"view": "",
|
||||
"back_to_page": "/net/home_auto_overview.lua",
|
||||
"ule_device_name": device["name"],
|
||||
"Offset": device["offset"],
|
||||
"apply": "",
|
||||
"oldpage": "/net/home_auto_hkr_edit.lua",
|
||||
}
|
||||
self.session.post(f"{self.url}/{self._endpoints['data']}", data=data, verify=self.verify_ssl)
|
||||
|
||||
def correct_offset(self, device_name: str, real_temp: float):
|
||||
elapsed = None
|
||||
@ -206,8 +301,17 @@ class FritzBox:
|
||||
device: Optional[Device] = self.get_device_data(name=device_name)
|
||||
if device is None:
|
||||
return
|
||||
new_offset = device.get_offset() + real_temp - device.get_temperature()
|
||||
logging.info(f"Update offset from {device.get_offset()} to {new_offset}")
|
||||
device.set_offset(new_offset)
|
||||
|
||||
if self.old_fb:
|
||||
new_offset = device["offset"] + real_temp - device["temperature"]
|
||||
logging.info(f"Update offset from {device['offset']} to {new_offset}")
|
||||
else:
|
||||
new_offset = device.get_offset() + real_temp - device.get_temperature()
|
||||
logging.info(f"Update offset from {device.get_offset()} to {new_offset}")
|
||||
|
||||
if self.old_fb:
|
||||
device["offset"] = new_offset
|
||||
else:
|
||||
device.set_offset(new_offset)
|
||||
self.set_offset(device)
|
||||
self.update_time[device.display_name] = datetime.now()
|
||||
|
@ -30,7 +30,8 @@ async def handle_event(idx: int):
|
||||
sensor_state = await ha.get_device_state(sensor)
|
||||
sensor_temp = round(float(sensor_state["attributes"]["temperature"]) * 2) / 2
|
||||
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_temp} ({sensor_temp})")
|
||||
fb.correct_offset(therm_name, sensor_temp)
|
||||
|
||||
elif entity_id in sensor_mappings.keys():
|
||||
@ -41,8 +42,9 @@ async def handle_event(idx: int):
|
||||
continue
|
||||
therm_temp = float(therm_state["attributes"]["current_temperature"])
|
||||
therm_name = therm_state["attributes"]["friendly_name"]
|
||||
if therm_temp != sensor_temp:
|
||||
logging.info(f"{therm_name}: {therm_temp}\n{entity_id}: {new_state['attributes']['temperature']} ({sensor_temp})")
|
||||
if therm_temp != sensor_temp or True:
|
||||
logging.info(
|
||||
f"{therm_name}: {therm_temp}\n{entity_id}: {new_state_temp} ({sensor_temp})")
|
||||
fb.correct_offset(therm_name, sensor_temp)
|
||||
except KeyError:
|
||||
pass
|
||||
@ -59,6 +61,7 @@ async def init(ha: HomeAssistantAPI, fb: FritzBox):
|
||||
await ha.wait_for_close()
|
||||
logging.info("Websocket closed, shutting down..")
|
||||
|
||||
|
||||
async def main():
|
||||
config_path = sys.argv[1]
|
||||
config = json.load(open(config_path))
|
||||
@ -71,12 +74,14 @@ async def main():
|
||||
|
||||
global fb
|
||||
fb = FritzBox(url=config["fritzbox"]["url"],
|
||||
user=config["fritzbox"]["username"],
|
||||
password=config["fritzbox"]["password"],
|
||||
update_timeout=config["update_timeout"],
|
||||
dry_run=False)
|
||||
supervisor_url = "ws://supervisor/core/websocket"
|
||||
supervisor_token = os.environ["SUPERVISOR_TOKEN"]
|
||||
user=config["fritzbox"]["username"],
|
||||
password=config["fritzbox"]["password"],
|
||||
update_timeout=config["update_timeout"],
|
||||
dry_run=False,
|
||||
verify_ssl=config["fritzbox"]["verify_ssl"],
|
||||
old_fb=config["fritzbox"]["old_fb"])
|
||||
supervisor_url = "ws://192.168.176.2:8123/api/websocket"
|
||||
supervisor_token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiIzZWZhZThhYjBhNTE0MDBhYmNjNzE4Yzc3YjE5MzNkNiIsImlhdCI6MTY3MDE1MTQxMCwiZXhwIjoxOTg1NTExNDEwfQ.0_PlrqiUtEjKhsKzID7xpyQGunWlbO5cr1EPtnzO5tE"
|
||||
global ha
|
||||
ha = HomeAssistantAPI(supervisor_token, supervisor_url)
|
||||
|
||||
@ -91,4 +96,5 @@ async def main():
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
|
||||
asyncio.run(main())
|
||||
|
Loading…
x
Reference in New Issue
Block a user