Skip to content

Commit

Permalink
Support 3 phase device
Browse files Browse the repository at this point in the history
  • Loading branch information
krasnoukhov committed Nov 8, 2023
1 parent 43a7ad5 commit 8407fbe
Show file tree
Hide file tree
Showing 8 changed files with 186 additions and 34 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 4 additions & 0 deletions custom_components/smart_maic/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
Expand Down
8 changes: 8 additions & 0 deletions custom_components/smart_maic/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
8 changes: 8 additions & 0 deletions custom_components/smart_maic/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}"
29 changes: 20 additions & 9 deletions custom_components/smart_maic/number.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
}


Expand Down
144 changes: 121 additions & 23 deletions custom_components/smart_maic/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,53 +30,129 @@
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,
suggested_display_precision=2,
),
"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,
),
}

Expand All @@ -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."""
Expand All @@ -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)
2 changes: 1 addition & 1 deletion custom_components/smart_maic/smart_maic.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}/",
)
Expand Down
24 changes: 23 additions & 1 deletion custom_components/smart_maic/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
Expand All @@ -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": {
Expand Down

0 comments on commit 8407fbe

Please sign in to comment.