diff --git a/README.md b/README.md index 7f1715f..1a0c366 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ The Smart MAIC integration listens for energy data from the device via MQTT prot Tested with: * [Розумний лічильник електроенергії c WiFi D101, однофазний, стандартна версія](https://store.smart-maic.com/ua/p684214708-umnyj-schetchik-elektroenergii.html) +* [Розумний лічильник електроенергії c WiFi D103, трифазний, розширена версія](https://store.smart-maic.com/ua/p679987290-umnyj-schetchik-elektroenergii.html) ## Highlights diff --git a/custom_components/smart_maic/config_flow.py b/custom_components/smart_maic/config_flow.py index 86d772b..6d71497 100644 --- a/custom_components/smart_maic/config_flow.py +++ b/custom_components/smart_maic/config_flow.py @@ -45,6 +45,10 @@ async def validate_input(hass: HomeAssistant, data: dict) -> dict[str, Any]: smart_maic = SmartMaic(data) coordinator = SmartMaicCoordinator(smart_maic, hass) + config = await coordinator.async_get_config() + if not config["serv"]: + raise AbortFlow("mqtt_unconfigured") + config = await coordinator.async_set_mqtt_config() additional = { DEVICE_ID: config["about"][DEVICE_ID]["value"], diff --git a/custom_components/smart_maic/coordinator.py b/custom_components/smart_maic/coordinator.py index e4bdd24..419ca44 100644 --- a/custom_components/smart_maic/coordinator.py +++ b/custom_components/smart_maic/coordinator.py @@ -28,6 +28,14 @@ def __init__(self, smart_maic: SmartMaic, hass: HomeAssistant) -> None: name=DOMAIN, ) + def _get_config(self) -> None: + """Get Smart MAIC config.""" + return self._smart_maic.set_mqtt_config() + + async def async_get_config(self) -> None: + """Get Smart MAIC config.""" + return await self.hass.async_add_executor_job(self._get_config) + def _set_mqtt_config(self) -> None: """Set Smart MAIC MQTT config.""" return self._smart_maic.set_mqtt_config() diff --git a/custom_components/smart_maic/entity.py b/custom_components/smart_maic/entity.py index a8dbb12..a968a03 100644 --- a/custom_components/smart_maic/entity.py +++ b/custom_components/smart_maic/entity.py @@ -57,3 +57,11 @@ def device_info(self) -> DeviceInfo: manufacturer="Smart MAIC", model=self._entry.data[DEVICE_TYPE], ) + + @property + def name(self) -> str: + """Return the name of the entity.""" + original = super().name + key = self.entity_description.key + suffix = f" {key[-1]}" if key[-1] in ["1", "2", "3"] else "" + return f"{original}{suffix}" diff --git a/custom_components/smart_maic/number.py b/custom_components/smart_maic/number.py index 7364130..dc3ddb1 100644 --- a/custom_components/smart_maic/number.py +++ b/custom_components/smart_maic/number.py @@ -25,16 +25,27 @@ from .entity import SmartMaicEntity +def phase_descriptions(index="") -> dict[str, NumberEntityDescription]: + """Generate entity descriptions for a given phase""" + return { + f"Wh{index}": NumberEntityDescription( + key=f"Wh{index}", + translation_key="consumption", + device_class=NumberDeviceClass.ENERGY, + mode=NumberMode.BOX, + native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, + native_min_value=0, + native_max_value=sys.maxsize, + entity_registry_enabled_default=False, + ), + } + + ENTITY_DESCRIPTIONS: dict[str, NumberEntityDescription] = { - "Wh": NumberEntityDescription( - key="Wh", - translation_key="consumption", - device_class=NumberDeviceClass.ENERGY, - mode=NumberMode.BOX, - native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, - native_min_value=0, - native_max_value=sys.maxsize, - ), + **phase_descriptions(""), + **phase_descriptions("1"), + **phase_descriptions("2"), + **phase_descriptions("3"), } diff --git a/custom_components/smart_maic/sensor.py b/custom_components/smart_maic/sensor.py index 7fbff52..9fd7d3e 100644 --- a/custom_components/smart_maic/sensor.py +++ b/custom_components/smart_maic/sensor.py @@ -30,18 +30,91 @@ from .coordinator import SmartMaicCoordinator from .entity import SmartMaicEntity + +def phase_descriptions(index="") -> dict[str, SensorEntityDescription]: + """Generate entity descriptions for a given phase""" + return { + f"V{index}": SensorEntityDescription( + key=f"V{index}", + translation_key="voltage", + device_class=SensorDeviceClass.VOLTAGE, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfElectricPotential.VOLT, + suggested_display_precision=2, + ), + f"A{index}": SensorEntityDescription( + key=f"A{index}", + translation_key="current", + device_class=SensorDeviceClass.CURRENT, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfElectricCurrent.AMPERE, + suggested_display_precision=2, + ), + f"W{index}": SensorEntityDescription( + key=f"W{index}", + translation_key="power", + device_class=SensorDeviceClass.POWER, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfPower.WATT, + suggested_display_precision=0, + ), + f"rW{index}": SensorEntityDescription( + key=f"rW{index}", + translation_key="return_power", + device_class=SensorDeviceClass.POWER, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfPower.WATT, + suggested_display_precision=0, + entity_registry_enabled_default=False, + ), + f"Wh{index}": SensorEntityDescription( + key=f"Wh{index}", + translation_key="consumption", + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL_INCREASING, + native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, + suggested_display_precision=0, + ), + f"rWh{index}": SensorEntityDescription( + key=f"rWh{index}", + translation_key="return", + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL, + native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, + suggested_display_precision=0, + entity_registry_enabled_default=False, + ), + f"PF{index}": SensorEntityDescription( + key=f"PF{index}", + translation_key="power_factor", + device_class=SensorDeviceClass.POWER_FACTOR, + state_class=SensorStateClass.MEASUREMENT, + suggested_display_precision=2, + ), + } + + ENTITY_DESCRIPTIONS: dict[str, SensorEntityDescription] = { - "V": SensorEntityDescription( - key="V", - translation_key="voltage", - device_class=SensorDeviceClass.VOLTAGE, + **phase_descriptions(""), + **phase_descriptions("1"), + **phase_descriptions("2"), + **phase_descriptions("3"), + "Temp": SensorEntityDescription( + key="Temp", + translation_key="device_temperature", + device_class=SensorDeviceClass.TEMPERATURE, state_class=SensorStateClass.MEASUREMENT, - native_unit_of_measurement=UnitOfElectricPotential.VOLT, - suggested_display_precision=2, + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + suggested_display_precision=0, ), +} + +# NOTE: dict keys here match API response +# But we align "key" values with single phase for consistency +TOTAL_ENTITY_DESCRIPTIONS: dict[str, SensorEntityDescription] = { "A": SensorEntityDescription( key="A", - translation_key="current", + translation_key="total_current", device_class=SensorDeviceClass.CURRENT, state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=UnitOfElectricCurrent.AMPERE, @@ -49,34 +122,37 @@ ), "W": SensorEntityDescription( key="W", - translation_key="power", + translation_key="total_power", device_class=SensorDeviceClass.POWER, state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=UnitOfPower.WATT, suggested_display_precision=0, ), - "Wh": SensorEntityDescription( + "rW": SensorEntityDescription( + key="rW", + translation_key="total_return_power", + device_class=SensorDeviceClass.POWER, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfPower.WATT, + suggested_display_precision=0, + entity_registry_enabled_default=False, + ), + "TWh": SensorEntityDescription( key="Wh", - translation_key="consumption", + translation_key="total_consumption", device_class=SensorDeviceClass.ENERGY, state_class=SensorStateClass.TOTAL_INCREASING, native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, suggested_display_precision=0, ), - "PF": SensorEntityDescription( - key="PF", - translation_key="power_factor", - device_class=SensorDeviceClass.POWER_FACTOR, - state_class=SensorStateClass.MEASUREMENT, - suggested_display_precision=2, - ), - "Temp": SensorEntityDescription( - key="Temp", - translation_key="device_temperature", - device_class=SensorDeviceClass.TEMPERATURE, - state_class=SensorStateClass.MEASUREMENT, - native_unit_of_measurement=UnitOfTemperature.CELSIUS, + "rTWh": SensorEntityDescription( + key="rWh", + translation_key="total_return", + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL, + native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, suggested_display_precision=0, + entity_registry_enabled_default=False, ), } @@ -95,6 +171,16 @@ async def async_setup_entry( ] ) + # NOTE: check if we're dealing with 3-phase device + if coordinator.data.get("A1"): + async_add_entities( + [ + SmartMaicTotalSensor(hass, coordinator, entry, description) + for ent in TOTAL_ENTITY_DESCRIPTIONS + if (description := TOTAL_ENTITY_DESCRIPTIONS.get(ent)) + ] + ) + class SmartMaicSensor(SmartMaicEntity, SensorEntity): """Representation of the Smart MAIC sensor.""" @@ -104,3 +190,15 @@ def native_value(self) -> StateType: """Return the state of the sensor.""" value = self.coordinator.data[self.entity_description.key] return cast(StateType, value) + + +class SmartMaicTotalSensor(SmartMaicEntity, SensorEntity): + """Representation of the Smart MAIC total sensor.""" + + @property + def native_value(self) -> StateType: + """Return the state of the sensor.""" + base_key = self.entity_description.key + data = self.coordinator.data + value = data[f"{base_key}1"] + data[f"{base_key}2"] + data[f"{base_key}3"] + return cast(StateType, value) diff --git a/custom_components/smart_maic/smart_maic.py b/custom_components/smart_maic/smart_maic.py index 94eb3d2..03e8ce4 100644 --- a/custom_components/smart_maic/smart_maic.py +++ b/custom_components/smart_maic/smart_maic.py @@ -49,7 +49,7 @@ def set_mqtt_config(self) -> dict[str, Any]: uname=config["uname"], **{"pass": config["pass"]}, mqtt_on=1, - mqttint=60, + mqttint=5, separat=2, prefix=f"{PREFIX}/", ) diff --git a/custom_components/smart_maic/translations/en.json b/custom_components/smart_maic/translations/en.json index 8df19c0..e694717 100644 --- a/custom_components/smart_maic/translations/en.json +++ b/custom_components/smart_maic/translations/en.json @@ -7,13 +7,14 @@ "pin": "PIN password", "device_name": "Name for the device in HA" }, - "description": "Please set up MQTT host/user/pass on the device before adding this integration" + "description": "Please set up MQTT on the device before adding this integration" } }, "error": { "already_configured": "Device is already configured", "already_in_progress": "Device configuration is in progress", "mqtt_unavailable": "MQTT is not available", + "mqtt_unconfigured": "Please configure MQTT host/port/credentials on the device and retry setup", "cannot_connect": "Failed to connect", "unknown": "Unknown error" } @@ -34,14 +35,35 @@ "power": { "name": "Power" }, + "return_power": { + "name": "Return power" + }, "consumption": { "name": "Consumption" }, + "return": { + "name": "Return" + }, "power_factor": { "name": "Power factor" }, "device_temperature": { "name": "Device temperature" + }, + "total_current": { + "name": "Total current" + }, + "total_power": { + "name": "Total power" + }, + "total_return_power": { + "name": "Total return power" + }, + "total_consumption": { + "name": "Total consumption" + }, + "total_return": { + "name": "Total return" } }, "switch": {