Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,9 @@ As soon as HomeAssistant is restarted, you can proceed with __component setup__.

## Supported device models

| Model | Version |
|----------------------------------|--------------------|
| `Refoss Smart Wi-Fi Switch, R11` | `all` |
| `Refoss Smart Wi-Fi Plug, P11S` | `all` |
| `Refoss Smart Wi-Fi Switch, R21` | `all` |
| Model | Version |
|-------------------------------------|--------------------|
| `Refoss Smart Wi-Fi Switch, R11` | `all` |
| `Refoss Smart Wi-Fi Plug, P11S` | `all` |
| `Refoss Smart Wi-Fi Switch, R21` | `all` |
| `Refoss Smart Energy Monito, EM06P` | `all` |
3 changes: 2 additions & 1 deletion custom_components/refoss_rpc/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,8 @@ def _async_device_event_handler(self, event_data: dict[str, Any]) -> None:
if event_type is None:
continue

if event_type in ("config_changed"):
RELOAD_EVENTS = {"config_changed", "emmerge_change", "cfg_change"}
if event_type in RELOAD_EVENTS:
LOGGER.info(
"Config for %s changed, reloading entry in %s seconds",
self.name,
Expand Down
5 changes: 2 additions & 3 deletions custom_components/refoss_rpc/cover.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,14 @@ def __init__(self, coordinator: RefossCoordinator, _id: int) -> None:
"""Initialize cover."""
super().__init__(coordinator, f"cover:{_id}")
self._id = _id
if self.status["pos_control"]:
if self.status["cali_state"] == "success":
self._attr_supported_features |= CoverEntityFeature.SET_POSITION

@property
def current_cover_position(self) -> int | None:
"""Position of the cover."""
if not self.status["pos_control"]:
if not self.status["cali_state"] or self.status["cali_state"] != "success":
return None

return cast(int, self.status["current_pos"])

@property
Expand Down
115 changes: 82 additions & 33 deletions custom_components/refoss_rpc/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
async_remove_refoss_entity,
get_refoss_entity_name,
get_refoss_key_instances,
merge_channel_get_status,
)


Expand All @@ -35,38 +36,51 @@ def async_setup_entry_refoss(
) -> None:
"""Set up entities for Refoss."""
coordinator = config_entry.runtime_data.coordinator
assert coordinator
if not coordinator.device.initialized:
# If the device is not initialized, return directly
if not coordinator or not coordinator.device.initialized:
return

entities = []
for sensor_id in sensors:
description = sensors[sensor_id]
key_instances = get_refoss_key_instances(
coordinator.device.status, description.key
)
device_status = coordinator.device.status
device_config = coordinator.device.config
mac = coordinator.mac
entities: list[Any] = []

for sensor_id, description in sensors.items():
key_instances = get_refoss_key_instances(device_status, description.key)

for key in key_instances:
# Filter non-existing sensors
if description.sub_key not in coordinator.device.status[
key
] and not description.supported(coordinator.device.status[key]):
key_status = device_status.get(key)
if key_status is None:
continue

# Filter out sensors that are not supported or do not match the configuration
if (
not key.startswith("emmerge:")
and description.sub_key not in key_status
and not description.supported(key_status)
):
continue

# Filter and remove entities that according to config/status
# should not create an entity
# Filter and remove entities that should not be created according to the configuration/status
if description.removal_condition and description.removal_condition(
coordinator.device.config, coordinator.device.status, key
device_config, device_status, key
):
domain = sensor_class.__module__.split(".")[-1]
unique_id = f"{coordinator.mac}-{key}-{sensor_id}"
try:
domain = sensor_class.__module__.split(".")[-1]
except AttributeError:
LOGGER.error(
"Failed to get module name from sensor_class for sensor_id %s and key %s",
sensor_id,
key,
)
continue
unique_id = f"{mac}-{key}-{sensor_id}"
async_remove_refoss_entity(hass, domain, unique_id)
else:
entities.append(sensor_class(coordinator, key, sensor_id, description))
if not entities:
return

async_add_entities(entities)
if entities:
async_add_entities(entities)


@dataclass(frozen=True, kw_only=True)
Expand Down Expand Up @@ -101,11 +115,13 @@ def available(self) -> bool:
return super().available and (coordinator.device.initialized)

@property
def status(self) -> dict:
def status(self) -> dict | None:
"""Device status by entity key."""
return cast(dict, self.coordinator.device.status[self.key])
device_status = self.coordinator.device.status.get(self.key)
if device_status is None:
LOGGER.debug("Device status not found for key: %s", self.key)
return device_status

# pylint: disable-next=hass-missing-super-call
async def async_added_to_hass(self) -> None:
"""When entity is added to HASS."""
self.async_on_remove(self.coordinator.async_add_listener(self._update_callback))
Expand Down Expand Up @@ -164,18 +180,51 @@ def __init__(
self._last_value = None

@property
def sub_status(self) -> Any:
"""Device status by entity key."""
return self.status[self.entity_description.sub_key]
def sub_status(self) -> Any | None:
"""Get the sub - status of the device by entity key.

Returns the value corresponding to the sub - key in the device status.
If the device status is None or the sub - key does not exist, returns None.
"""
device_status = self.status
if device_status is None:
LOGGER.debug("Device status is None for entity %s", self.name)
return None
sub_key = self.entity_description.sub_key
sub_status = device_status.get(sub_key)
return sub_status

@property
def attribute_value(self) -> StateType:
"""Value of sensor."""
if self.entity_description.value is not None:
self._last_value = self.entity_description.value(
self.status.get(self.entity_description.sub_key), self._last_value
try:
if self.key.startswith("emmerge:"):
# Call the merge channel attributes function
return merge_channel_get_status(
self.coordinator.device.status,
self.key,
self.entity_description.sub_key,
)

# Reduce repeated calls and get the sub-status
sub_status = self.sub_status

if self.entity_description.value is not None:
# Call the custom value processing function
self._last_value = self.entity_description.value(
sub_status, self._last_value
)
else:
self._last_value = sub_status

return self._last_value
except Exception as e:
# Log the exception
LOGGER.error(
"Error getting attribute value for entity %s, key %s, attribute %s: %s",
self.name,
self.key,
self.attribute,
str(e),
)
else:
self._last_value = self.sub_status

return self._last_value
return None
2 changes: 1 addition & 1 deletion custom_components/refoss_rpc/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"loggers": ["aiorefoss"],
"quality_scale": "bronze",
"requirements": ["aiorefoss==1.0.1"],
"version": "1.0.3",
"version": "1.0.5",
"zeroconf": [
{
"type": "_http._tcp.local.",
Expand Down
171 changes: 171 additions & 0 deletions custom_components/refoss_rpc/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,177 @@ class RefossSensorDescription(RefossEntityDescription, SensorEntityDescription):
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
),
"em_power": RefossSensorDescription(
key="em",
sub_key="power",
name="Power",
native_unit_of_measurement=UnitOfPower.WATT,
value=lambda status, _: None if status is None else float(status),
suggested_display_precision=2,
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
),
"em_voltage": RefossSensorDescription(
key="em",
sub_key="voltage",
name="Voltage",
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
value=lambda status, _: None if status is None else float(status),
suggested_display_precision=2,
device_class=SensorDeviceClass.VOLTAGE,
state_class=SensorStateClass.MEASUREMENT,
),
"em_current": RefossSensorDescription(
key="em",
sub_key="current",
name="Current",
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
value=lambda status, _: None if status is None else float(status),
suggested_display_precision=2,
device_class=SensorDeviceClass.CURRENT,
state_class=SensorStateClass.MEASUREMENT,
),
"em_month_energy": RefossSensorDescription(
key="em",
sub_key="month_energy",
name="This Month Energy",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
value=lambda status, _: None if status is None else float(status),
suggested_display_precision=2,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
),
"em_month_ret_energy": RefossSensorDescription(
key="em",
sub_key="month_ret_energy",
name="This Month Return Energy",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
value=lambda status, _: None if status is None else float(status),
suggested_display_precision=2,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
),
"em_week_energy": RefossSensorDescription(
key="em",
sub_key="week_energy",
name="This Week Energy",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
value=lambda status, _: None if status is None else float(status),
suggested_display_precision=2,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
),
"em_week_ret_energy": RefossSensorDescription(
key="em",
sub_key="week_ret_energy",
name="This Week Retrun Energy",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
value=lambda status, _: None if status is None else float(status),
suggested_display_precision=2,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
),
"em_day_energy": RefossSensorDescription(
key="em",
sub_key="day_energy",
name="Today Energy",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
value=lambda status, _: None if status is None else float(status),
suggested_display_precision=2,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
),
"em_day_ret_energy": RefossSensorDescription(
key="em",
sub_key="day_ret_energy",
name="Today Return Energy",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
value=lambda status, _: None if status is None else float(status),
suggested_display_precision=2,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
),
"em_pf": RefossSensorDescription(
key="em",
sub_key="pf",
name="Power factor",
value=lambda status, _: None if status is None else float(status),
suggested_display_precision=2,
device_class=SensorDeviceClass.POWER_FACTOR,
state_class=SensorStateClass.MEASUREMENT,
),
"emmerge_power": RefossSensorDescription(
key="emmerge",
sub_key="power",
name="Power",
native_unit_of_measurement=UnitOfPower.WATT,
suggested_display_precision=2,
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
),
"emmerge_current": RefossSensorDescription(
key="emmerge",
sub_key="current",
name="Current",
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
suggested_display_precision=2,
device_class=SensorDeviceClass.CURRENT,
state_class=SensorStateClass.MEASUREMENT,
),
"emmerge_month_energy": RefossSensorDescription(
key="emmerge",
sub_key="month_energy",
name="This Month Energy",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
suggested_display_precision=2,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
),
"emmerge_month_ret_energy": RefossSensorDescription(
key="emmerge",
sub_key="month_ret_energy",
name="This Month Return Energy",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
suggested_display_precision=2,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
),
"emmerge_week_energy": RefossSensorDescription(
key="emmerge",
sub_key="week_energy",
name="This Week Energy",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
suggested_display_precision=2,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
),
"emmerge_week_ret_energy": RefossSensorDescription(
key="emmerge",
sub_key="week_ret_energy",
name="This Week Retrun Energy",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
suggested_display_precision=2,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
),
"emmerge_day_energy": RefossSensorDescription(
key="emmerge",
sub_key="day_energy",
name="Today Energy",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
suggested_display_precision=2,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
),
"emmerge_day_ret_energy": RefossSensorDescription(
key="emmerge",
sub_key="day_ret_energy",
name="Today Return Energy",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
suggested_display_precision=2,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
),
}


Expand Down
Loading