From d8bacf9dfcc5e87ec785a626475f7c373d8a85a6 Mon Sep 17 00:00:00 2001 From: Luis Miranda <161006+luuuis@users.noreply.github.com> Date: Sun, 2 Jan 2022 22:53:53 +0000 Subject: [PATCH 1/3] chore: add short_mac() for reuse across the integration --- custom_components/wibeee/config_flow.py | 3 ++- custom_components/wibeee/util.py | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 custom_components/wibeee/util.py diff --git a/custom_components/wibeee/config_flow.py b/custom_components/wibeee/config_flow.py index 862970e38c..3adc701389 100644 --- a/custom_components/wibeee/config_flow.py +++ b/custom_components/wibeee/config_flow.py @@ -13,6 +13,7 @@ from .api import WibeeeAPI from .const import (DOMAIN, DEFAULT_SCAN_INTERVAL) +from .util import short_mac _LOGGER = logging.getLogger(__name__) @@ -28,7 +29,7 @@ async def validate_input(hass: HomeAssistant, user_input: dict) -> [str, str, di mac_addr = format_mac(device['mac_addr']) unique_id = mac_addr - name = f"Wibeee {mac_addr.replace(':', '')[-6:].upper()}" + name = f"Wibeee {short_mac(mac_addr)}" return name, unique_id, {CONF_HOST: user_input[CONF_HOST], } diff --git a/custom_components/wibeee/util.py b/custom_components/wibeee/util.py new file mode 100644 index 0000000000..de7167b1a0 --- /dev/null +++ b/custom_components/wibeee/util.py @@ -0,0 +1,3 @@ +def short_mac(mac_addr): + """Returns the last 6 chars of the MAC address for showing in UI.""" + return mac_addr.replace(':', '')[-6:].upper() From 85d5cfaefcb43187ebd9731716027dfb5e2ce54b Mon Sep 17 00:00:00 2001 From: Luis Miranda <161006+luuuis@users.noreply.github.com> Date: Sun, 2 Jan 2022 22:57:01 +0000 Subject: [PATCH 2/3] feat: implement "Devices" for the Wibeee energy monitor and each clamp --- custom_components/wibeee/sensor.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/custom_components/wibeee/sensor.py b/custom_components/wibeee/sensor.py index d8bd27dd1d..d37260bc02 100755 --- a/custom_components/wibeee/sensor.py +++ b/custom_components/wibeee/sensor.py @@ -40,12 +40,14 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.event import async_track_time_interval from homeassistant.util import slugify - +from homeassistant.helpers import device_registry as dr from .api import WibeeeAPI from .const import (DOMAIN, DEFAULT_SCAN_INTERVAL, DEFAULT_TIMEOUT) +from .util import short_mac _LOGGER = logging.getLogger(__name__) @@ -137,6 +139,7 @@ def __init__(self, device, xml_name: str, sensor_phase: str, sensor_type: str, s self._attr_unique_id = f"_{mac_addr}_{ha_name.lower()}_{sensor_phase}" self._attr_name = f"{device_name} {friendly_name} L{sensor_phase}" self._attr_should_poll = False + self._attr_device_info = _make_device_info(mac_addr, sensor_phase) self.entity_id = f"sensor.{entity_id}" # we don't want this derived from the name @callback @@ -154,3 +157,19 @@ def make_device_sensors(device, status) -> list['WibeeeSensor']: phase_values = [(key, value, key[4:].split("_", 1)) for key, value in status.items() if key.startswith('fase')] known_values = [(key, phase, stype, value) for (key, value, (phase, stype)) in phase_values] return [WibeeeSensor(device, key, phase, stype, value) for (key, phase, stype, value) in known_values if stype in SENSOR_TYPES] + + +def _make_device_info(mac_addr, sensor_phase) -> DeviceInfo: + is_phase = sensor_phase != '4' + + return DeviceInfo( + # identifiers and links + identifiers={(DOMAIN, f'{mac_addr}{"_L" + sensor_phase if is_phase else ""}')}, + via_device=(DOMAIN, f'{mac_addr}') if is_phase else None, + connections={(dr.CONNECTION_NETWORK_MAC, mac_addr)}, + + # and now for the humans :) + name=f'Wibeee {short_mac(mac_addr)} {" Line " + sensor_phase if is_phase else ""}', + model='Wibeee Current Clamp' if is_phase else 'Wibeee Consumption Analyzer', + manufacturer='Smilics', + ) From 7b5ec736511436b81b035dec01b3555ac3f9bd9d Mon Sep 17 00:00:00 2001 From: Luis Miranda <161006+luuuis@users.noreply.github.com> Date: Mon, 3 Jan 2022 00:29:40 +0000 Subject: [PATCH 3/3] feat: adds `sw_version`, `model` and `configuration_url` to Device info --- custom_components/wibeee/api.py | 13 +++++++-- custom_components/wibeee/config_flow.py | 2 +- custom_components/wibeee/sensor.py | 39 ++++++++++++++++++------- 3 files changed, 40 insertions(+), 14 deletions(-) diff --git a/custom_components/wibeee/api.py b/custom_components/wibeee/api.py index 306041eba8..7705206219 100644 --- a/custom_components/wibeee/api.py +++ b/custom_components/wibeee/api.py @@ -31,11 +31,18 @@ async def async_fetch_device_info(self, retries: int): devices = await self.async_fetch_url(f'http://{self.host}/services/user/devices.xml', retries) device_id = devices['devices']['id'] + var_names = ['macAddr', 'softVersion', 'model', 'ipAddr'] + var_ids = [f"{quote_plus(device_id)}.{name}" for name in var_names] + values = await self.async_fetch_url(f'http://{self.host}/services/user/values.xml?var={"&".join(var_ids)}', retries) + # macAddr11:11:11:11:11:11 - values = await self.async_fetch_url(f'http://{self.host}/services/user/values.xml?var={quote_plus(device_id)}.macAddr', retries) - mac_addr = values['values']['variable']['value'] + device_vars = {var['id']: var['value'] for var in values['values']['variable']} - return dict(id=device_id, mac_addr=mac_addr.replace(":", "").lower()) if device_id and mac_addr else None + return { + **device_vars, + 'macAddr': device_vars['macAddr'].replace(':', ''), + 'id': device_id, + } if len(device_vars) == len(var_names) else None async def async_fetch_url(self, url, retries: int = 0): async def fetch_with_retries(try_n): diff --git a/custom_components/wibeee/config_flow.py b/custom_components/wibeee/config_flow.py index 3adc701389..bfa7cd806f 100644 --- a/custom_components/wibeee/config_flow.py +++ b/custom_components/wibeee/config_flow.py @@ -27,7 +27,7 @@ async def validate_input(hass: HomeAssistant, user_input: dict) -> [str, str, di except Exception as e: raise NoDeviceInfo from e - mac_addr = format_mac(device['mac_addr']) + mac_addr = format_mac(device['macAddr']) unique_id = mac_addr name = f"Wibeee {short_mac(mac_addr)}" diff --git a/custom_components/wibeee/sensor.py b/custom_components/wibeee/sensor.py index d37260bc02..1b1ba101f0 100755 --- a/custom_components/wibeee/sensor.py +++ b/custom_components/wibeee/sensor.py @@ -44,7 +44,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.event import async_track_time_interval from homeassistant.util import slugify -from homeassistant.helpers import device_registry as dr + from .api import WibeeeAPI from .const import (DOMAIN, DEFAULT_SCAN_INTERVAL, DEFAULT_TIMEOUT) from .util import short_mac @@ -74,6 +74,20 @@ 'energia_reactiva_cap': ['Capacitive_Reactive_Energy', 'Capacitive Reactive Energy', 'VArCh', DEVICE_CLASS_ENERGY] } +KNOWN_MODELS = { + 'WBM': 'Wibeee 1Ph', + 'WBT': 'Wibeee 3Ph', + 'WTD': 'Wibeee 3Ph RN', + 'WX2': 'Wibeee MAX 2S', + 'WX3': 'Wibeee MAX 3S', + 'WXX': 'Wibeee MAX MS', + 'WBB': 'Wibeee BOX', + 'WB3': 'Wibeee BOX S3P', + 'W3P': 'Wibeee 3Ph 3W', + 'WGD': 'Wibeee GND', + 'WBP': 'Wibeee PLUG', +} + async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Import existing configuration from YAML.""" @@ -128,7 +142,7 @@ class WibeeeSensor(SensorEntity): def __init__(self, device, xml_name: str, sensor_phase: str, sensor_type: str, sensor_value): """Initialize the sensor.""" ha_name, friendly_name, unit, device_class = SENSOR_TYPES[sensor_type] - [device_name, mac_addr] = [device['id'], device['mac_addr']] + [device_name, mac_addr] = [device['id'], device['macAddr']] entity_id = slugify(f"{DOMAIN} {mac_addr} {friendly_name} L{sensor_phase}") self._xml_name = xml_name self._attr_native_unit_of_measurement = unit @@ -139,7 +153,7 @@ def __init__(self, device, xml_name: str, sensor_phase: str, sensor_type: str, s self._attr_unique_id = f"_{mac_addr}_{ha_name.lower()}_{sensor_phase}" self._attr_name = f"{device_name} {friendly_name} L{sensor_phase}" self._attr_should_poll = False - self._attr_device_info = _make_device_info(mac_addr, sensor_phase) + self._attr_device_info = _make_device_info(device, sensor_phase) self.entity_id = f"sensor.{entity_id}" # we don't want this derived from the name @callback @@ -159,17 +173,22 @@ def make_device_sensors(device, status) -> list['WibeeeSensor']: return [WibeeeSensor(device, key, phase, stype, value) for (key, phase, stype, value) in known_values if stype in SENSOR_TYPES] -def _make_device_info(mac_addr, sensor_phase) -> DeviceInfo: - is_phase = sensor_phase != '4' +def _make_device_info(device, sensor_phase) -> DeviceInfo: + mac_addr = device['macAddr'] + is_clamp = sensor_phase != '4' + + device_name = f'Wibeee {short_mac(mac_addr)}' + device_model = KNOWN_MODELS.get(device['model'], 'Wibeee Energy Meter') return DeviceInfo( # identifiers and links - identifiers={(DOMAIN, f'{mac_addr}{"_L" + sensor_phase if is_phase else ""}')}, - via_device=(DOMAIN, f'{mac_addr}') if is_phase else None, - connections={(dr.CONNECTION_NETWORK_MAC, mac_addr)}, + identifiers={(DOMAIN, f'{mac_addr}_L{sensor_phase}' if is_clamp else mac_addr)}, + via_device=(DOMAIN, f'{mac_addr}') if is_clamp else None, # and now for the humans :) - name=f'Wibeee {short_mac(mac_addr)} {" Line " + sensor_phase if is_phase else ""}', - model='Wibeee Current Clamp' if is_phase else 'Wibeee Consumption Analyzer', + name=device_name if not is_clamp else f"{device_name} Line {sensor_phase}", + model=device_model if not is_clamp else f'{device_model} Clamp', manufacturer='Smilics', + configuration_url=f"http://{device['ipAddr']}/" if not is_clamp else None, + sw_version=f"{device['softVersion']}" if not is_clamp else None, )