diff --git a/.experiment.py b/.experiment.py index 4419e7a..d3b0496 100644 --- a/.experiment.py +++ b/.experiment.py @@ -1,121 +1,3 @@ -from urllib.parse import urlparse, parse_qsl +import struct -from bs4 import BeautifulSoup - -page = ''' -Back
0x15 - T67XX
0x40 - HTU21D/PCA9685/HM3301
0x4a - MAX44009
- -''' - -from urllib.parse import parse_qsl, urlparse - -from bs4 import BeautifulSoup - -from homeassistant.const import ( - DEVICE_CLASS_HUMIDITY, - DEVICE_CLASS_TEMPERATURE, - DEVICE_CLASS_ILLUMINANCE, - DEVICE_CLASS_PRESSURE, -) - - -def parse_scan_page(page: str): - ret = [] - req = [] - page = BeautifulSoup(page, features="lxml") - for x in page.find_all('a'): - params = x.get('href') - if params is None: - continue - params = dict(parse_qsl(urlparse(params).query)) - if 'i2c_dev' in params: - dev = params['i2c_dev'] - classes = i2c_classes.get(dev, []) - for i, c in enumerate(classes): - if c is Skip: - continue - elif c is Request: - req.append(params) - continue - elif isinstance(c, tuple): - suffix, c = c - elif isinstance(c, str): - suffix = c - else: - suffix = '' - if 'addr' in params: - suffix += f"_{params['addr']}" if suffix else str(params['addr']) - if suffix: - _dev = f'{dev}_{suffix}' - else: - _dev = dev - params = params.copy() - if i > 0: - params['i2c_par'] = i - ret.append({ - 'id_suffix': _dev, - 'device_class': c, - 'params': params, - }) - req.append(params) - return req, ret - - -class Skip: - pass - - -class Request: - pass - - -i2c_classes = { - 'htu21d': [ - DEVICE_CLASS_HUMIDITY, - DEVICE_CLASS_TEMPERATURE, - ], - 'sht31': [ - DEVICE_CLASS_HUMIDITY, - DEVICE_CLASS_TEMPERATURE, - ], - 'max44009': [ - DEVICE_CLASS_ILLUMINANCE - ], - 'bh1750': [ - DEVICE_CLASS_ILLUMINANCE - ], - 'tsl2591': [ - DEVICE_CLASS_ILLUMINANCE - ], - 'bmp180': [ - DEVICE_CLASS_PRESSURE, - DEVICE_CLASS_TEMPERATURE, - ], - 'bmx280': [ - DEVICE_CLASS_PRESSURE, - DEVICE_CLASS_TEMPERATURE, - DEVICE_CLASS_HUMIDITY - ], - 'mlx90614': [ - Skip, - ('temp', DEVICE_CLASS_TEMPERATURE), - ('object', DEVICE_CLASS_TEMPERATURE), - ], - 'ptsensor': [ - Request, # запрос на измерение - DEVICE_CLASS_PRESSURE, - DEVICE_CLASS_TEMPERATURE, - ], - 'mcp9600': [ - DEVICE_CLASS_TEMPERATURE, # термопара - DEVICE_CLASS_TEMPERATURE, # сенсор встроенный в микросхему - ], - 't67xx': [ - None # для co2 нет класса в HA - ], - 'tmp117': [ - DEVICE_CLASS_TEMPERATURE, - ] -} - -print(parse_scan_page(page)) \ No newline at end of file +print(struct.unpack('!f', bytes.fromhex('435c028f'))[0]) \ No newline at end of file diff --git a/custom_components/mega/__init__.py b/custom_components/mega/__init__.py index ed25fee..33f5b0c 100644 --- a/custom_components/mega/__init__.py +++ b/custom_components/mega/__init__.py @@ -16,7 +16,7 @@ from homeassistant.components import mqtt from homeassistant.config_entries import ConfigEntry from .const import DOMAIN, CONF_INVERT, CONF_RELOAD, PLATFORMS, CONF_PORTS, CONF_CUSTOM, CONF_SKIP, CONF_PORT_TO_SCAN, \ CONF_MQTT_INPUTS, CONF_HTTP, CONF_RESPONSE_TEMPLATE, CONF_ACTION, CONF_GET_VALUE, CONF_ALLOW_HOSTS, \ - CONF_CONV_TEMPLATE, CONF_ALL, CONF_FORCE_D, CONF_DEF_RESPONSE, CONF_FORCE_I2C_SCAN + CONF_CONV_TEMPLATE, CONF_ALL, CONF_FORCE_D, CONF_DEF_RESPONSE, CONF_FORCE_I2C_SCAN, CONF_HEX_TO_FLOAT from .hub import MegaD from .config_flow import ConfigFlow from .http import MegaView @@ -47,6 +47,7 @@ CUSTOMIZE_PORT = { vol.Optional(CONF_CONV_TEMPLATE): cv.template, vol.Optional(CONF_VALUE_TEMPLATE): cv.template, vol.Optional(CONF_FORCE_I2C_SCAN): bool, + vol.Optional(CONF_HEX_TO_FLOAT): bool, } CUSTOMIZE_DS2413 = { vol.Optional(str.lower, description='адрес и индекс устройства'): CUSTOMIZE_PORT @@ -133,7 +134,7 @@ async def get_hub(hass, entry): break if _mqtt is None: raise Exception('mqtt not configured, please configure mqtt first') - hub = MegaD(hass, **data, mqtt=_mqtt, lg=_LOGGER, loop=asyncio.get_event_loop()) + hub = MegaD(hass, config=entry, **data, mqtt=_mqtt, lg=_LOGGER, loop=asyncio.get_event_loop()) hub.mqtt_id = await hub.get_mqtt_id() return hub @@ -177,14 +178,11 @@ async def updater(hass: HomeAssistant, entry: ConfigEntry): hub.poll_interval = entry.options[CONF_SCAN_INTERVAL] hub.port_to_scan = entry.options.get(CONF_PORT_TO_SCAN, 0) entry.data = entry.options - for platform in PLATFORMS: - await hass.config_entries.async_forward_entry_unload(entry, platform) - await async_remove_entry(hass, entry) - await async_setup_entry(hass, entry) + await hass.config_entries.async_reload(entry.entry_id) return True -async def async_remove_entry(hass, entry) -> None: +async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None: """Handle removal of an entry.""" id = entry.data.get('id', entry.entry_id) hub: MegaD = hass.data[DOMAIN].get(id) @@ -194,12 +192,17 @@ async def async_remove_entry(hass, entry) -> None: _hubs.pop(id, None) hass.data[DOMAIN].pop(id, None) hass.data[DOMAIN][CONF_ALL].pop(id, None) + for platform in PLATFORMS: + await hass.config_entries.async_forward_entry_unload(entry, platform) task: asyncio.Task = _POLL_TASKS.pop(id, None) if task is not None: task.cancel() if hub is None: return await hub.stop() + return True + +async_unload_entry = async_remove_entry async def async_migrate_entry(hass, config_entry: ConfigEntry): diff --git a/custom_components/mega/config_flow.py b/custom_components/mega/config_flow.py index e604048..a7e5a31 100644 --- a/custom_components/mega/config_flow.py +++ b/custom_components/mega/config_flow.py @@ -65,7 +65,7 @@ async def validate_input(hass: core.HomeAssistant, data): class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Handle a config flow for mega.""" - VERSION = 22 + VERSION = 23 CONNECTION_CLASS = config_entries.CONN_CLASS_ASSUMED async def async_step_user(self, user_input=None): @@ -130,8 +130,6 @@ class OptionsFlowHandler(config_entries.OptionsFlow): _LOGGER.debug(f'new config: %s', new) cfg = dict(self.config_entry.data) - for x in PLATFORMS: - cfg.pop(x, None) for x in REMOVE_CONFIG: cfg.pop(x, None) cfg.update(new) diff --git a/custom_components/mega/const.py b/custom_components/mega/const.py index b8430bd..d564266 100644 --- a/custom_components/mega/const.py +++ b/custom_components/mega/const.py @@ -36,6 +36,7 @@ CONF_CLICK_TIME = 'click_time' CONF_LONG_TIME = 'long_time' CONF_FORCE_I2C_SCAN = 'force_i2c_scan' CONF_UPDATE_TIME = 'update_time' +CONF_HEX_TO_FLOAT = 'hex_to_float' PLATFORMS = [ "light", "switch", diff --git a/custom_components/mega/http.py b/custom_components/mega/http.py index 4dba493..cf60481 100644 --- a/custom_components/mega/http.py +++ b/custom_components/mega/http.py @@ -77,7 +77,11 @@ class MegaView(HomeAssistantView): 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']: - hub = self.hubs.get('__def') + 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) @@ -87,8 +91,10 @@ class MegaView(HomeAssistantView): ) _LOGGER.debug(f"Request: %s from '%s'", data, request.remote) make_ints(data) - if data.get('st') == '1' and hub.restore_on_restart: - asyncio.create_task(self.later_restore(hub)) + if data.get('st') == '1': + hass.async_create_task(hub.reload()) + if hub.restore_on_restart: + hass.async_create_task(self.later_restore(hub)) return Response(status=200) port = data.get('pt') data = data.copy() @@ -163,5 +169,4 @@ class MegaView(HomeAssistantView): async def later_update(self, hub): await asyncio.sleep(1) _LOGGER.debug('force update') - await hub.updater.async_refresh() - + await hub.updater.async_refresh() \ No newline at end of file diff --git a/custom_components/mega/hub.py b/custom_components/mega/hub.py index 4ea0dbe..25ae7e4 100644 --- a/custom_components/mega/hub.py +++ b/custom_components/mega/hub.py @@ -10,6 +10,7 @@ import json from bs4 import BeautifulSoup from homeassistant.components import mqtt +from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_PRESSURE, DEVICE_CLASS_ILLUMINANCE, TEMP_CELSIUS, PERCENTAGE, LIGHT_LUX @@ -20,7 +21,8 @@ from .config_parser import parse_config, DS2413, MCP230, MCP230_OUT, MCP230_IN, from .const import ( TEMP, HUM, PRESS, LUX, PATT_SPLIT, DOMAIN, - CONF_HTTP, EVENT_BINARY_SENSOR, CONF_CUSTOM, CONF_FORCE_D, CONF_DEF_RESPONSE, PATT_FW, CONF_FORCE_I2C_SCAN + CONF_HTTP, EVENT_BINARY_SENSOR, CONF_CUSTOM, CONF_FORCE_D, CONF_DEF_RESPONSE, PATT_FW, CONF_FORCE_I2C_SCAN, + REMOVE_CONFIG ) from .entities import set_events_off, BaseMegaEntity, MegaOutPort from .exceptions import CannotConnect, NoPort @@ -63,6 +65,7 @@ class MegaD: def __init__( self, hass: HomeAssistant, + config: ConfigEntry, loop: asyncio.AbstractEventLoop, host: str, password: str, @@ -90,6 +93,7 @@ class MegaD: **kwargs, ): """Initialize.""" + self.config = config if mqtt_inputs is None or mqtt_inputs == 'None' or mqtt_inputs is False: self.http = hass.data.get(DOMAIN, {}).get(CONF_HTTP) if not self.http is None: @@ -619,4 +623,14 @@ class MegaD: await self.request( cf=7, stime=datetime.now().strftime('%H:%M:%S') - ) \ No newline at end of file + ) + + async def reload(self): + new = await self.get_config(nports=self.nports) + self.lg.debug(f'new config: %s', new) + cfg = dict(self.config.data) + for x in REMOVE_CONFIG: + cfg.pop(x, None) + cfg.update(new) + self.config.data = cfg + await self.hass.config_entries.async_reload(self.config.entry_id) diff --git a/custom_components/mega/manifest.json b/custom_components/mega/manifest.json index 2e58e32..2dd5efe 100644 --- a/custom_components/mega/manifest.json +++ b/custom_components/mega/manifest.json @@ -15,5 +15,5 @@ "@andvikt" ], "issue_tracker": "https://github.com/andvikt/mega_hacs/issues", - "version": "v0.6.1b1" + "version": "v0.6.3b1" } \ No newline at end of file diff --git a/custom_components/mega/sensor.py b/custom_components/mega/sensor.py index 69f86f8..d88bffa 100644 --- a/custom_components/mega/sensor.py +++ b/custom_components/mega/sensor.py @@ -1,6 +1,7 @@ """Platform for light integration.""" import logging import voluptuous as vol +import struct from homeassistant.components.sensor import ( PLATFORM_SCHEMA as SENSOR_SCHEMA, @@ -19,7 +20,7 @@ from homeassistant.const import ( from homeassistant.core import HomeAssistant from homeassistant.helpers.template import Template from .entities import MegaPushEntity -from .const import CONF_KEY, TEMP, HUM, W1, W1BUS, CONF_CONV_TEMPLATE +from .const import CONF_KEY, TEMP, HUM, W1, W1BUS, CONF_CONV_TEMPLATE, CONF_HEX_TO_FLOAT from .hub import MegaD import re @@ -114,7 +115,17 @@ class MegaI2C(MegaPushEntity): @property def state(self): # self.lg.debug(f'get % all states: %', self._params, self.mega.values) - return self.mega.values.get(self._params) + ret = self.mega.values.get(self._params) + if self.customize.get(CONF_HEX_TO_FLOAT): + try: + ret = struct.unpack('!f', bytes.fromhex('41973333'))[0] + except: + self.lg.warning(f'could not convert {ret} form hex to float') + tmpl: Template = self.customize.get(CONF_CONV_TEMPLATE, self.customize.get(CONF_VALUE_TEMPLATE)) + if tmpl is not None and self.hass is not None: + tmpl.hass = self.hass + ret = tmpl.async_render({'value': ret}) + return ret @property def device_class(self): @@ -197,6 +208,11 @@ class Mega1WSensor(MegaPushEntity): ret = str(ret) except: ret = None + if self.customize.get(CONF_HEX_TO_FLOAT): + try: + ret = struct.unpack('!f', bytes.fromhex(ret))[0] + except: + self.lg.warning(f'could not convert {ret} form hex to float') tmpl: Template = self.customize.get(CONF_CONV_TEMPLATE, self.customize.get(CONF_VALUE_TEMPLATE)) if tmpl is not None and self.hass is not None: tmpl.hass = self.hass