fix(airconditioner): scale legacy ARTIK051 cumulativePower correctly (#193)

ARTIK051_KRAC_18K-generation boards report /energy/consumption/vs/0's
cumulativePower in centiwatt-hours, not the plain Wh every other AC board
family reports -- confirmed against the reporter's own SmartThings-app
reading (raw 117430000 vs. the app's authoritative 1,174.30 kWh is exactly
a /100000 factor, not the shared wh_to_kwh's /1000 alone). Split
common.ENERGY_METER into generic/legacy variants on the airconditioner
registry, discriminated by the existing is_legacy_board() check, so every
other AC family keeps the unmodified shared capability.
This commit is contained in:
Marc Billow committed 2026-07-29 13:04:33 +00:00
1 parent e8df8f7b0c
commit d83733a670
6 files changed
+340 -1

No files matched your search

@@ -7,6 +7,13 @@ this registry includes *common.UNIVERSAL but deliberately NOT common.POWER --
on/off is the climate entity's HVACMode.OFF / TURN_ON/OFF. See common.POWER's
own comment in capabilities/common.py for why it's excluded.
common.ENERGY_METER itself is also excluded from UNIVERSAL here, replaced by
the ENERGY_METER_GENERIC/ENERGY_METER_LEGACY pair -- the legacy ARTIK051 board
generation (issue #193) reports cumulativePower in a different unit than
every other AC family, so this registry needs two mutually-exclusive variants
of that one capability instead of the single shared one every other registry
uses unconditionally.
Reuses dishwasher.DIAGNOSIS for /diagnosis/vs/0.
"""
from ..capabilities import airconditioner, common, dishwasher, ignored
@@ -16,7 +23,9 @@ REGISTRY = DeviceRegistry(
name='airconditioner',
capabilities=_build([
*ignored.IGNORED,
*common.UNIVERSAL,
*[c for c in common.UNIVERSAL if c is not common.ENERGY_METER],
airconditioner.ENERGY_METER_GENERIC,
airconditioner.ENERGY_METER_LEGACY,
dishwasher.DIAGNOSIS,
airconditioner.CLIMATE,
airconditioner.AIR_PURIFY,
@@ -14,10 +14,13 @@ None of these caps may go into the global `ALL`/`CAPABILITIES`: `/mode/vs/0`,
different schema (see capabilities/__init__.py). They live only in the AC
by_type registry.
"""
from dataclasses import replace
from ..capability import Capability
from ..entities import (
BinarySensorDesc, ClimateDesc, NumberDesc, SelectDesc, SensorDesc, SwitchDesc,
)
from . import common
from .common import filter_usage_percent, normalize_temp_unit
from .laundry import option_write
@@ -296,6 +299,44 @@ def is_legacy_board(resources):
return HREF_AIRFLOW in resources and HREF_WIND_STRENGTH not in resources
# This legacy ARTIK051 board generation (issue #193, model AR12NXWXCWKNEU /
# ARTIK051_KRAC_18K) reports /energy/consumption/vs/0's cumulativePower in
# centiwatt-hours -- raw value 100x the plain Wh every other AC board family
# (and common.wh_to_kwh's assumed unit) reports. Confirmed against the
# reporter's own SmartThings-app reading: raw '117430000' vs the app's
# authoritative 1,174.30 kWh is exactly a /100000 factor (i.e. /100 on top of
# wh_to_kwh's own /1000), not wh_to_kwh's plain /1000 alone. No other field in
# ENERGY_METER's entities is present on this board's dump, so only
# 'energy_kwh' needs a replacement value_fn here; the rest pass through
# unchanged in case a future legacy dump ever reports them.
def _legacy_cumulative_power_kwh(v):
n = _int(v)
return round(n / 100000.0, 2) if n is not None else None
ENERGY_METER_LEGACY = Capability(
href=common.ENERGY_METER.href,
poll_tier=common.ENERGY_METER.poll_tier,
match_fn=lambda rep, resources: is_legacy_board(resources),
entities=tuple(
replace(e, value_fn=_legacy_cumulative_power_kwh)
if e.key == 'energy_kwh' else e
for e in common.ENERGY_METER.entities
),
)
# The non-legacy counterpart to ENERGY_METER_LEGACY above -- identical to
# common.ENERGY_METER, just excluding the legacy board generation so the two
# capabilities can share /energy/consumption/vs/0 in this registry without
# the 'multiple caps need a discriminator' build check tripping (common.
# ENERGY_METER itself has no match_fn, since every *other* registry includes
# it unconditionally and alone).
ENERGY_METER_GENERIC = replace(
common.ENERGY_METER,
match_fn=lambda rep, resources: not is_legacy_board(resources),
)
def _has_option_token(prefix):
return lambda rep, resources: (
is_legacy_board(resources) and _option_token(rep, prefix) is not None)