mirror of
https://github.com/andvikt/mega_hacs.git
synced 2025-12-11 17:14:28 +05:00
203 lines
7.9 KiB
Python
203 lines
7.9 KiB
Python
import asyncio
|
||
import logging
|
||
|
||
import typing
|
||
from collections import defaultdict
|
||
|
||
from aiohttp.web_request import Request
|
||
from aiohttp.web_response import Response
|
||
|
||
from homeassistant.helpers.template import Template
|
||
from homeassistant.components.http import HomeAssistantView
|
||
from homeassistant.core import HomeAssistant
|
||
from .const import EVENT_BINARY_SENSOR, CONF_RESPONSE_TEMPLATE
|
||
from .tools import make_ints
|
||
from . import hub as h
|
||
|
||
_LOGGER = logging.getLogger(__name__).getChild("http")
|
||
|
||
|
||
def is_ext(data: typing.Dict[str, typing.Any]):
|
||
for x in data:
|
||
if x.startswith("ext"):
|
||
return True
|
||
|
||
|
||
class MegaView(HomeAssistantView):
|
||
url = "/mega"
|
||
name = "mega"
|
||
requires_auth = False
|
||
|
||
def __init__(self, cfg: dict):
|
||
self._try = 0
|
||
self.protected = True
|
||
self.allowed_hosts = {"::1", "127.0.0.1"}
|
||
self.notified_attempts = defaultdict(lambda: False)
|
||
self.callbacks = defaultdict(lambda: defaultdict(list))
|
||
self.templates: typing.Dict[str, typing.Dict[str, Template]] = {
|
||
mid: {
|
||
pt: cfg[mid][pt][CONF_RESPONSE_TEMPLATE]
|
||
for pt in cfg[mid]
|
||
if isinstance(pt, int) and CONF_RESPONSE_TEMPLATE in cfg[mid][pt]
|
||
}
|
||
for mid in cfg
|
||
if isinstance(cfg[mid], dict)
|
||
}
|
||
_LOGGER.debug("templates: %s", self.templates)
|
||
self.hubs = {}
|
||
|
||
async def get(self, request: Request) -> Response:
|
||
_LOGGER.debug("request from %s %s", request.remote, request.headers)
|
||
hass: HomeAssistant = request.app["hass"]
|
||
if self.protected:
|
||
auth = False
|
||
for x in self.allowed_hosts:
|
||
if request.remote.startswith(x):
|
||
auth = True
|
||
break
|
||
if not auth:
|
||
msg = (
|
||
f"Non-authorised request from {request.remote} to `/mega`. "
|
||
f"If you want to accept requests from this host "
|
||
f"please add it to allowed hosts in `mega` UI-configuration"
|
||
)
|
||
if not self.notified_attempts[request.remote]:
|
||
await hass.services.async_call(
|
||
"persistent_notification",
|
||
"create",
|
||
{
|
||
"notification_id": request.remote,
|
||
"title": "Non-authorised request",
|
||
"message": msg,
|
||
},
|
||
)
|
||
_LOGGER.warning(msg)
|
||
return Response(status=401)
|
||
|
||
remote = request.headers.get("X-Real-IP", request.remote)
|
||
hub: "h.MegaD" = self.hubs.get(remote)
|
||
if hub is None and "mdid" in request.query:
|
||
hub = self.hubs.get(request.query["mdid"])
|
||
if hub is None:
|
||
_LOGGER.warning(
|
||
f'can not find mdid={request.query["mdid"]} in {list(self.hubs)}'
|
||
)
|
||
if hub is None and request.remote in ["::1", "127.0.0.1"]:
|
||
try:
|
||
hub = list(self.hubs.values())[0]
|
||
except IndexError:
|
||
_LOGGER.warning(
|
||
f'can not find mdid={request.query["mdid"]} in {list(self.hubs)}'
|
||
)
|
||
return Response(status=400)
|
||
elif hub is None:
|
||
return Response(status=400)
|
||
data = dict(request.query)
|
||
hass.bus.async_fire(
|
||
EVENT_BINARY_SENSOR,
|
||
data,
|
||
)
|
||
_LOGGER.debug(f"Request: %s from '%s'", data, request.remote)
|
||
make_ints(data)
|
||
if data.get("st") == "1":
|
||
hass.async_create_task(self.later_restore(hub))
|
||
return Response(status=200)
|
||
port = data.get("pt")
|
||
data = data.copy()
|
||
update_all = True
|
||
if "v" in data:
|
||
update_all = False
|
||
data["value"] = data.pop("v")
|
||
data["mega_id"] = hub.id
|
||
ret = "d" if hub.force_d else ""
|
||
if port is not None:
|
||
if is_ext(data):
|
||
# ret = '' # пока ответ всегда пустой, неясно какая будет реакция на непустой ответ
|
||
if port in hub.extenders:
|
||
pt_orig = port
|
||
else:
|
||
pt_orig = hub.ext_in.get(port, hub.ext_in.get(str(port)))
|
||
if pt_orig is None:
|
||
hub.lg.warning(
|
||
f"can not find extender for int port {port}, "
|
||
f"have ext_int: {hub.ext_in}, ext: {hub.extenders}"
|
||
)
|
||
return Response(status=200)
|
||
for e, v in data.items():
|
||
_data = data.copy()
|
||
if e.startswith("ext"):
|
||
idx = e[3:]
|
||
pt = (
|
||
f"{pt_orig}e{idx}"
|
||
if not hub.new_naming
|
||
else f"{int(pt_orig):02d}e{int(idx):02d}"
|
||
)
|
||
_data["pt_orig"] = pt_orig
|
||
_data["value"] = "ON" if v == "1" else "OFF"
|
||
_data["m"] = (
|
||
1 if _data[e] == "0" else 0
|
||
) # имитация поведения обычного входа, чтобы события обрабатывались аналогично
|
||
hub.values[pt] = _data
|
||
for cb in self.callbacks[hub.id][pt]:
|
||
cb(_data)
|
||
act = hub.ext_act.get(pt)
|
||
hub.lg.debug(
|
||
f"act on port {pt}: {act}, all acts are: {hub.ext_act}"
|
||
)
|
||
template: Template = self.templates.get(hub.id, {}).get(
|
||
port, hub.def_response
|
||
)
|
||
if template is not None:
|
||
template.hass = hass
|
||
ret = template.async_render(_data)
|
||
hub.lg.debug(f"response={ret}, template={template}")
|
||
if ret == "d" and act:
|
||
await hub.request(cmd=act.replace(":3", f":{v}"))
|
||
ret = "d" if hub.force_d else ""
|
||
else:
|
||
# elif port in hub.binary_sensors:
|
||
hub.values[port] = data
|
||
for cb in self.callbacks[hub.id][port]:
|
||
cb(data)
|
||
template: Template = self.templates.get(hub.id, {}).get(
|
||
port, hub.def_response
|
||
)
|
||
if template is not None:
|
||
template.hass = hass
|
||
ret = template.async_render(data)
|
||
if hub.update_all and update_all:
|
||
asyncio.create_task(self.later_update(hub))
|
||
_LOGGER.debug("response %s", ret)
|
||
Response(body="" if hub.fake_response else ret, content_type="text/plain")
|
||
|
||
if (
|
||
hub.fake_response
|
||
and "value" not in data
|
||
and "pt" in data
|
||
and port in hub.binary_sensors
|
||
):
|
||
if "d" in ret:
|
||
await hub.request(pt=port, cmd=ret)
|
||
else:
|
||
await hub.request(cmd=ret)
|
||
if not isinstance(ret, str):
|
||
return ""
|
||
return ret
|
||
|
||
async def later_restore(self, hub):
|
||
"""
|
||
Восстановление всех выходов с небольшой задержкой. Задержка нужна чтобы ответ прошел успешно
|
||
|
||
:param hub:
|
||
:return:
|
||
"""
|
||
await asyncio.sleep(0.2)
|
||
if hub.restore_on_restart:
|
||
await hub.restore_states()
|
||
await hub.reload()
|
||
|
||
async def later_update(self, hub):
|
||
await asyncio.sleep(1)
|
||
_LOGGER.debug("force update")
|
||
await hub.updater.async_refresh()
|