Skip to content

Commit

Permalink
Update to last version
Browse files Browse the repository at this point in the history
  • Loading branch information
ajtudela committed May 12, 2024
1 parent de35e88 commit 66c3c2d
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 17 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# Changelog

See GitHub release notes: https://github.com/graham33/hass-smartbox/releases
See GitHub release notes: https://github.com/ajtudela/hass-smartbox/releases
2 changes: 1 addition & 1 deletion custom_components/smartbox/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
)
from .model import get_devices, is_supported_node

__version__ = "2.0.0-beta.1"
__version__ = "2.0.0-beta.2"

_LOGGER = logging.getLogger(__name__)

Expand Down
8 changes: 4 additions & 4 deletions custom_components/smartbox/manifest.json
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
{
"domain": "smartbox",
"name": "Smartbox",
"documentation": "https://github.com/graham33/hass-smartbox",
"issue_tracker": "https://github.com/graham33/hass-smartbox/issues",
"documentation": "https://github.com/ajtudela/hass-smartbox",
"issue_tracker": "https://github.com/ajtudela/hass-smartbox/issues",
"dependencies": [],
"version": "2.0.0-beta.1",
"version": "2.0.0-beta.2",
"config_flow": false,
"codeowners": [
"@graham33"
],
"requirements": [
"smartbox==2.0.0b1"
"smartbox==2.0.0b2"
],
"iot_class": "cloud_polling"
}
121 changes: 112 additions & 9 deletions custom_components/smartbox/sensor.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
from datetime import datetime, timedelta
from homeassistant.const import (
ATTR_LOCKED,
DEVICE_CLASS_BATTERY,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_ENERGY,
DEVICE_CLASS_POWER,
DEVICE_CLASS_POWER_FACTOR,
DEVICE_CLASS_TEMPERATURE,
ENERGY_WATT_HOUR,
PERCENTAGE,
POWER_WATT,
)
from homeassistant.components.sensor import (
SensorEntity,
STATE_CLASS_MEASUREMENT,
SensorStateClass,
)
from homeassistant.core import HomeAssistant
import logging
Expand All @@ -18,6 +22,7 @@
from .const import (
DOMAIN,
HEATER_NODE_TYPE_ACM,
HEATER_NODE_TYPE_HTR,
HEATER_NODE_TYPE_HTR_MOD,
SMARTBOX_NODES,
)
Expand Down Expand Up @@ -55,6 +60,25 @@ async def async_setup_platform(
],
True,
)
# Duty Cycle and Energy
# Only nodes of type 'htr' seem to report the duty cycle, which is needed
# to compute energy consumption
async_add_entities(
[
DutyCycleSensor(node)
for node in hass.data[DOMAIN][SMARTBOX_NODES]
if node.node_type == HEATER_NODE_TYPE_HTR
],
True,
)
async_add_entities(
[
EnergySensor(node)
for node in hass.data[DOMAIN][SMARTBOX_NODES]
if node.node_type == HEATER_NODE_TYPE_HTR
],
True,
)
# Charge Level
async_add_entities(
[
Expand All @@ -73,6 +97,8 @@ def __init__(self, node: Union[SmartboxNode, MagicMock]) -> None:
self._node = node
self._status: Dict[str, Any] = {}
self._available = False # unavailable until we get an update
self._last_update: Optional[datetime] = None
self._time_since_last_update: Optional[timedelta] = None
_LOGGER.debug(f"Created node {self.name} unique_id={self.unique_id}")

@property
Expand All @@ -91,15 +117,25 @@ async def async_update(self) -> None:
# update our status
self._status = new_status
self._available = True
update_time = datetime.now()
if self._last_update is not None:
self._time_since_last_update = update_time - self._last_update
self._last_update = update_time
else:
self._available = False
self._last_update = None
self._time_since_last_update = None

@property
def time_since_last_update(self) -> Optional[timedelta]:
return self._time_since_last_update


class TemperatureSensor(SmartboxSensorBase):
"""Smartbox heater temperature sensor"""

device_class = DEVICE_CLASS_TEMPERATURE
state_class = STATE_CLASS_MEASUREMENT
state_class = SensorStateClass.MEASUREMENT

def __init__(self, node: Union[SmartboxNode, MagicMock]) -> None:
super().__init__(node)
Expand All @@ -122,11 +158,18 @@ def native_unit_of_measurement(self) -> str:


class PowerSensor(SmartboxSensorBase):
"""Smartbox heater power sensor"""
"""Smartbox heater power sensor
Note: this represents the power the heater is drawing *when heating*; the
heater is not always active over the entire period since the last update,
even when 'active' is true. The duty cycle sensor indicates how much it
was active. To measure energy consumption, use the corresponding energy
sensor.
"""

device_class = DEVICE_CLASS_POWER
native_unit_of_measurement = POWER_WATT
state_class = STATE_CLASS_MEASUREMENT
state_class = SensorStateClass.MEASUREMENT

def __init__(self, node: Union[SmartboxNode, MagicMock]) -> None:
super().__init__(node)
Expand All @@ -141,22 +184,82 @@ def unique_id(self) -> str:

@property
def native_value(self) -> float:
# TODO: is this correct? The heater seems to report power usage all the
# time otherwise, which doesn't make sense and doesn't tally with the
# graphs in the vendor app UI
return (
self._status["power"]
if is_heating(self._node.node_type, self._status)
else 0
)


class DutyCycleSensor(SmartboxSensorBase):
"""Smartbox heater duty cycle sensor
Represents the duty cycle for the heater.
"""

device_class = DEVICE_CLASS_POWER_FACTOR
native_unit_of_measurement = PERCENTAGE
state_class = SensorStateClass.MEASUREMENT

def __init__(self, node: Union[SmartboxNode, MagicMock]) -> None:
super().__init__(node)

@property
def name(self) -> str:
return f"{self._node.name} Duty Cycle"

@property
def unique_id(self) -> str:
return f"{self._node.node_id}_duty_cycle"

@property
def native_value(self) -> float:
return self._status["duty"]


class EnergySensor(SmartboxSensorBase):
"""Smartbox heater energy sensor
Represents the energy consumed by the heater.
"""

device_class = DEVICE_CLASS_ENERGY
native_unit_of_measurement = ENERGY_WATT_HOUR
state_class = SensorStateClass.TOTAL

def __init__(self, node: Union[SmartboxNode, MagicMock]) -> None:
super().__init__(node)

@property
def name(self) -> str:
return f"{self._node.name} Energy"

@property
def unique_id(self) -> str:
return f"{self._node.node_id}_energy"

@property
def native_value(self) -> float | None:
time_since_last_update = self.time_since_last_update
if time_since_last_update is not None:
return (
float(self._status["power"])
* float(self._status["duty"])
/ 100
* time_since_last_update.seconds
/ 60
/ 60
)
else:
return None


class ChargeLevelSensor(SmartboxSensorBase):
"""Smartbox storage heater charge level sensor"""

device_class = DEVICE_CLASS_BATTERY
native_unit_of_measurement = PERCENTAGE
state_class = STATE_CLASS_MEASUREMENT
state_class = SensorStateClass.MEASUREMENT

def __init__(self, node: Union[SmartboxNode, MagicMock]) -> None:
super().__init__(node)
Expand Down
2 changes: 1 addition & 1 deletion custom_components/smartbox/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@

SetupDict = Dict[str, Union[bool, float, str, FactoryOptionsDict]]

StatusDict = Dict[str, Union[bool, float, str]]
StatusDict = Dict[str, Union[bool, int, float, str]]
2 changes: 1 addition & 1 deletion requirements_common.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
smartbox==2.0.0b1
smartbox==2.0.0b2

0 comments on commit 66c3c2d

Please sign in to comment.