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
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,11 @@ The integration creates:
- **Fan switch** — Controls the exhaust fan independently of oil pumping. Use to accelerate scent distribution without running the diffuser.
- Button entities for run/save actions
- Number entities for work duration, pause duration, and polling interval
- Sensor entities for runtime and device statistics
- Sensor entities for runtime and device statistics:
- **Work Status**, **Work Remaining Time**, **Pause Remaining Time** — live cycle state.
- **Total Run Time** (hours) — the API's `runCount`, which accumulates seconds of work time.
- **Total Diffusion Time** (hours) — estimated as pump activations × current work duration.
- **On Count** / **Pump Count** — the raw API counters (long-term statistics enabled). The device pushes these upstream on its own schedule, so they typically update about once a day.

## Technical Notes

Expand Down
88 changes: 77 additions & 11 deletions custom_components/aromalink_ha_integration/sensor.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Sensor platform for Aroma-Link."""
import logging
from homeassistant.components.sensor import SensorEntity
from homeassistant.components.sensor import SensorEntity, SensorStateClass
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.const import UnitOfTime
Expand All @@ -23,8 +23,10 @@ async def async_setup_entry(hass, entry, async_add_entities):
entities.append(AromaLinkWorkRemainingTimeSensor(coordinator, entry, device_id, device_info["name"]))
entities.append(AromaLinkPauseRemainingTimeSensor(coordinator, entry, device_id, device_info["name"]))
entities.append(AromaLinkTotalRunTimeSensor(coordinator, entry, device_id, device_info["name"]))
entities.append(AromaLinkTotalDiffusionTimeSensor(coordinator, entry, device_id, device_info["name"]))
entities.append(AromaLinkOnCountSensor(coordinator, entry, device_id, device_info["name"]))
entities.append(AromaLinkPumpCountSensor(coordinator, entry, device_id, device_info["name"]))

async_add_entities(entities)

class AromaLinkSensorBase(CoordinatorEntity, SensorEntity):
Expand Down Expand Up @@ -196,17 +198,17 @@ def native_value(self):
# runCount accumulates seconds of work time; convert to hours.
return round(raw / 3600, 2)

class AromaLinkPumpCountSensor(AromaLinkSensorBase):
class AromaLinkTotalDiffusionTimeSensor(AromaLinkSensorBase):
"""Sensor showing total diffusion time in hours (airPumpCount * work_duration)."""

def __init__(self, coordinator, entry, device_id, device_name):
"""Initialize the pump count sensor."""
"""Initialize the total diffusion time sensor."""
super().__init__(
coordinator,
entry,
device_id,
device_name,
"Total Diffusion Time",
coordinator,
entry,
device_id,
device_name,
"Total Diffusion Time",
icon="mdi:spray-bottle",
unit=UnitOfTime.HOURS
)
Expand All @@ -224,15 +226,79 @@ def native_value(self):
)
if pump_count is None:
return None

work_duration = self.coordinator.work_duration or 0
if work_duration <= 0:
_LOGGER.debug(
"Omitting historical diffusion time for %s: invalid work_duration=%d",
self._device_id, work_duration
)
return None

# airPumpCount counts activations; multiply by work duration to get total diffusion time.
total_seconds = pump_count * work_duration
return round(total_seconds / 3600, 2)

class AromaLinkOnCountSensor(AromaLinkSensorBase):
"""Raw counter the API reports for run activity (issue #2).

Keeps the pre-2.x unique_id, name, and unit so existing entities and
recorded history continue seamlessly. When served by the web list API
the underlying field is runCount, which accumulates seconds of work
time rather than a number of activations (see docs/API.md).
"""

def __init__(self, coordinator, entry, device_id, device_name):
"""Initialize the on count sensor."""
super().__init__(
coordinator,
entry,
device_id,
device_name,
"On Count",
icon="mdi:counter",
unit="activations"
)
self._attr_state_class = SensorStateClass.TOTAL_INCREASING

@property
def native_value(self):
"""Return the on count value."""
return self._get_raw_count(
"onCount",
"runCount",
"on_count",
"run_count",
"openCount",
"open_count",
"startCount",
"start_count",
)

class AromaLinkPumpCountSensor(AromaLinkSensorBase):
"""Raw pump activation counter (airPumpCount, +1 per completed cycle)."""

def __init__(self, coordinator, entry, device_id, device_name):
"""Initialize the pump count sensor."""
super().__init__(
coordinator,
entry,
device_id,
device_name,
"Pump Count",
icon="mdi:shimmer",
unit="diffusions"
)
self._attr_state_class = SensorStateClass.TOTAL_INCREASING

@property
def native_value(self):
"""Return the pump count value."""
return self._get_raw_count(
"pumpCount",
"airPumpCount",
"pump_count",
"air_pump_count",
"pumpTimes",
"pump_times",
)
Loading