Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6611f85958 | ||
|
|
8470853fb6 | ||
|
|
e942c822a6 | ||
|
|
4a376a98aa | ||
|
|
bc9d4f4cf5 | ||
|
|
10f2aac0bd | ||
|
|
0cc9d8e511 | ||
|
|
b586277031 | ||
|
|
2a0a9f32ee | ||
|
|
8dadea6796 | ||
|
|
0715eb0829 |
No files matched your search
@@ -41,6 +41,7 @@ from .registry.capabilities.airconditioner import (
|
||||
HREF_TEMP_CURRENT as TEMP_CURRENT_HREF,
|
||||
HREF_TEMP_DESIRED as TEMP_DESIRED_HREF,
|
||||
HREF_TEMP_CONTROL as TEMP_CONTROL_HREF,
|
||||
HREF_TEMPS_VS as TEMPS_VS_HREF,
|
||||
HREF_WIND_STRENGTH as WIND_STRENGTH_HREF,
|
||||
HREF_WIND_DIRECTION as WIND_DIRECTION_HREF,
|
||||
HREF_CONVENIENT as CONVENIENT_HREF,
|
||||
@@ -125,6 +126,22 @@ def _num(value):
|
||||
return None
|
||||
|
||||
|
||||
def _temps_vs_item(rep: dict) -> dict:
|
||||
"""First item of the vendor `/temperatures/vs/0` items[] array.
|
||||
|
||||
Newer AC firmware (Tizen Lite, oneUiVersion "7.0 Air conditioner", e.g.
|
||||
model TP1X_DA-AC-RAC-01011) does NOT expose the OCF-standard
|
||||
/temperature/current/0 + /temperature/desired/0 pair; it reports current
|
||||
and target under a single `/temperatures/vs/0` resource whose
|
||||
`x.com.samsung.da.items[0]` carries current/desired/minimum/maximum/
|
||||
increment/unit. Returns {} when absent, so callers fall through cleanly.
|
||||
"""
|
||||
items = rep.get('x.com.samsung.da.items')
|
||||
if isinstance(items, (list, tuple)) and items and isinstance(items[0], dict):
|
||||
return items[0]
|
||||
return {}
|
||||
|
||||
|
||||
class LocalThingsClimate(LocalThingsEntity, ClimateEntity):
|
||||
"""Composite climate entity for a Samsung air conditioner."""
|
||||
|
||||
@@ -172,24 +189,41 @@ class LocalThingsClimate(LocalThingsEntity, ClimateEntity):
|
||||
|
||||
# -- temperature --------------------------------------------------------
|
||||
|
||||
def _temps_vs(self) -> dict:
|
||||
"""Vendor `/temperatures/vs/0` items[0] (empty {} when absent)."""
|
||||
return _temps_vs_item(self._rep(TEMPS_VS_HREF))
|
||||
|
||||
@property
|
||||
def temperature_unit(self) -> str:
|
||||
raw = self._rep(TEMP_DESIRED_HREF).get('units')
|
||||
if raw is None:
|
||||
raw = self._temps_vs().get('x.com.samsung.da.unit')
|
||||
return (UnitOfTemperature.FAHRENHEIT
|
||||
if normalize_temp_unit(raw, '°C') == '°F'
|
||||
else UnitOfTemperature.CELSIUS)
|
||||
|
||||
@property
|
||||
def current_temperature(self):
|
||||
return _num(self._rep(TEMP_CURRENT_HREF).get('temperature'))
|
||||
v = _num(self._rep(TEMP_CURRENT_HREF).get('temperature'))
|
||||
if v is None:
|
||||
v = _num(self._temps_vs().get('x.com.samsung.da.current'))
|
||||
return v
|
||||
|
||||
@property
|
||||
def target_temperature(self):
|
||||
return _num(self._rep(TEMP_DESIRED_HREF).get('temperature'))
|
||||
v = _num(self._rep(TEMP_DESIRED_HREF).get('temperature'))
|
||||
if v is None:
|
||||
v = _num(self._temps_vs().get('x.com.samsung.da.desired'))
|
||||
return v
|
||||
|
||||
def _range(self) -> list | None:
|
||||
r = self._rep(TEMP_DESIRED_HREF).get('range')
|
||||
return r if (isinstance(r, (list, tuple)) and len(r) == 2) else None
|
||||
if isinstance(r, (list, tuple)) and len(r) == 2:
|
||||
return r
|
||||
item = self._temps_vs()
|
||||
lo = _num(item.get('x.com.samsung.da.minimum'))
|
||||
hi = _num(item.get('x.com.samsung.da.maximum'))
|
||||
return [lo, hi] if (lo is not None and hi is not None) else None
|
||||
|
||||
@property
|
||||
def min_temp(self) -> float:
|
||||
@@ -203,7 +237,10 @@ class LocalThingsClimate(LocalThingsEntity, ClimateEntity):
|
||||
|
||||
@property
|
||||
def target_temperature_step(self) -> float:
|
||||
return _num(self._rep(TEMP_CONTROL_HREF).get('increment')) or 1.0
|
||||
return (_num(self._rep(TEMP_CONTROL_HREF).get('increment'))
|
||||
or _num(self._rep(TEMP_CONTROL_HREF).get('x.com.samsung.da.increment'))
|
||||
or _num(self._temps_vs().get('x.com.samsung.da.increment'))
|
||||
or 1.0)
|
||||
|
||||
# -- hvac mode ----------------------------------------------------------
|
||||
|
||||
|
||||
@@ -12,5 +12,5 @@
|
||||
"pyOpenSSL>=23.0",
|
||||
"smartthings-local>=0.1.0"
|
||||
],
|
||||
"version": "0.8.0"
|
||||
"version": "0.9.0"
|
||||
}
|
||||
@@ -96,4 +96,9 @@ def for_device_by_model(model_num: str, description: str) -> Optional[DeviceRegi
|
||||
# a modelNum carrying the '_PRAC_' (Package Room Air Conditioner) token.
|
||||
if key is None and '_PRAC_' in (model_num or ''):
|
||||
key = 'airconditioner'
|
||||
# Older/simpler RAC boards (e.g. TP2X_RAC_20K, issue #37) use the plain
|
||||
# '_RAC_' token instead -- distinct from '_PRAC_' above (no overlap: the
|
||||
# 'P' sits between the underscore and 'RAC' in that token).
|
||||
if key is None and '_RAC_' in (model_num or ''):
|
||||
key = 'airconditioner'
|
||||
return _REGISTRY_BY_KEY.get(key) if key else None
|
||||
@@ -19,11 +19,15 @@ REGISTRY = DeviceRegistry(
|
||||
common.ALARMS,
|
||||
common.ENERGY_METER,
|
||||
fridge.FIRMWARE_UPDATE,
|
||||
fridge.SELF_CHECK,
|
||||
dishwasher.DIAGNOSIS,
|
||||
airconditioner.CLIMATE,
|
||||
airconditioner.AIR_PURIFY,
|
||||
airconditioner.AUTO_CLEAN,
|
||||
airconditioner.AIR_FILTER,
|
||||
airconditioner.DISPLAY_LIGHT,
|
||||
airconditioner.MUTE_ONCE,
|
||||
airconditioner.CURRENT_LIMIT,
|
||||
*airconditioner.COVERAGE,
|
||||
]),
|
||||
)
|
||||
@@ -20,6 +20,7 @@ REGISTRY = DeviceRegistry(
|
||||
dishwasher.DIAGNOSIS,
|
||||
fridge.ICEMAKER_NIGHTTIME,
|
||||
fridge.FLEX_ZONE,
|
||||
fridge.AI_ENERGY_LEVEL,
|
||||
fridge.REFRIGERATION,
|
||||
fridge.AUTOFILL,
|
||||
fridge.WELCOME_LIGHTING,
|
||||
|
||||
@@ -15,7 +15,7 @@ different schema (see capabilities/__init__.py). They live only in the AC
|
||||
by_type registry.
|
||||
"""
|
||||
from ..capability import Capability
|
||||
from ..entities import ClimateDesc, SensorDesc, SwitchDesc
|
||||
from ..entities import BinarySensorDesc, ClimateDesc, SensorDesc, SwitchDesc
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Canonical AC resource hrefs. The climate entity (climate.py) binds the
|
||||
@@ -34,10 +34,12 @@ HREF_TEMP_CONTROL = '/temperature/control/vs/0' # target_temperature_step
|
||||
HREF_WIND_STRENGTH = '/wind/strength/vs/0' # fan_mode
|
||||
HREF_WIND_DIRECTION = '/wind/direction/vs/0' # swing_mode
|
||||
HREF_CONVENIENT = '/mode/convenient/vs/0' # preset_mode
|
||||
HREF_TEMPS_VS = '/temperatures/vs/0' # vendor temp fallback (items[] array)
|
||||
|
||||
CLIMATE_CONSUMED_HREFS = [
|
||||
HREF_POWER, HREF_POWER_VS, HREF_TEMP_CURRENT, HREF_TEMP_DESIRED,
|
||||
HREF_TEMP_CONTROL, HREF_WIND_STRENGTH, HREF_WIND_DIRECTION, HREF_CONVENIENT,
|
||||
HREF_TEMP_CONTROL, HREF_TEMPS_VS, HREF_WIND_STRENGTH, HREF_WIND_DIRECTION,
|
||||
HREF_CONVENIENT,
|
||||
]
|
||||
|
||||
|
||||
@@ -146,6 +148,57 @@ AIR_FILTER = Capability(
|
||||
),
|
||||
)
|
||||
|
||||
DISPLAY_LIGHT = Capability(
|
||||
href='/light/vs/0',
|
||||
poll_tier='cold',
|
||||
entities=(
|
||||
SwitchDesc(key='display_light', field='mode',
|
||||
name='Display light', icon='mdi:led-on',
|
||||
entity_category='config',
|
||||
value_fn=lambda v: v == 'On',
|
||||
write_fn=lambda p, rep, href=None: (
|
||||
['light', 'vs', '0'],
|
||||
{'mode': 'On' if p == 'On' else 'Off'})),
|
||||
),
|
||||
)
|
||||
|
||||
# Confirmed against issue #38's dump (TP1X_DA-AC-RAC-01001_0000): a single
|
||||
# boolean field, no vendor prefix, mirroring the On/Off convention used
|
||||
# throughout the rest of this API.
|
||||
MUTE_ONCE = Capability(
|
||||
href='/option/muteonce/vs/0',
|
||||
poll_tier='warm',
|
||||
entities=(
|
||||
SwitchDesc(key='mute_once', field='muteonce',
|
||||
name='Mute once', icon='mdi:volume-mute',
|
||||
entity_category='config',
|
||||
value_fn=lambda v: v == 'On',
|
||||
write_fn=lambda p, rep, href=None: (
|
||||
['option', 'muteonce', 'vs', '0'],
|
||||
{'muteonce': 'On' if p == 'On' else 'Off'})),
|
||||
),
|
||||
)
|
||||
|
||||
# Circuit-breaker current-limit setting (issue #38, TP1X board): `operation`
|
||||
# toggles the limiter and `modes` picks a level out of `supportedModes`
|
||||
# (seen as '3'..'9'). No vendor field-name prefix and no unit/label in the
|
||||
# dump to confirm what the levels mean (amps vs. an abstract tier) --
|
||||
# exposed read-only per the 'don't guess' rule rather than risking an
|
||||
# unverified write to live HVAC hardware.
|
||||
CURRENT_LIMIT = Capability(
|
||||
href='/electriccurrent/vs/0',
|
||||
poll_tier='cold',
|
||||
entities=(
|
||||
BinarySensorDesc(key='current_limit_enabled', field='operation',
|
||||
name='Current limit enabled', icon='mdi:current-ac',
|
||||
entity_category='diagnostic',
|
||||
value_fn=lambda v: v == 'On'),
|
||||
SensorDesc(key='current_limit_level', field='modes',
|
||||
name='Current limit level', icon='mdi:current-ac',
|
||||
entity_category='diagnostic'),
|
||||
),
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# AC-scoped coverage: the CLIMATE_CONSUMED_HREFS above (read by the climate
|
||||
# entity) plus vendor duplicates / all-zero-ambiguous / plumbing resources.
|
||||
@@ -155,8 +208,6 @@ AIR_FILTER = Capability(
|
||||
# bound so discover() reports no coverage gap.
|
||||
# ---------------------------------------------------------------------------
|
||||
_AC_IGNORED = [
|
||||
# Vendor superset that duplicates the OCF /temperature/current+desired pair.
|
||||
'/temperatures/vs/0',
|
||||
# All-zero and ambiguously encoded on this model (2-value arrays); the
|
||||
# 'don't guess' rule -- leave unmodeled rather than invent entities.
|
||||
'/sensors/vs/0',
|
||||
@@ -164,6 +215,23 @@ _AC_IGNORED = [
|
||||
'/humidity/vs/0',
|
||||
# Presence-personalization plumbing (empty item list here).
|
||||
'/personality/presence/vs/0',
|
||||
# --- TP1X/TP2X-class housekeeping / opaque blobs. These carry no
|
||||
# user-actionable state or no documented write contract, so per the
|
||||
# 'don't guess' rule they are ignored rather than modeled.
|
||||
# /option/muteonce/vs/0 and /selfcheck/vs/0 are deliberately NOT here --
|
||||
# see MUTE_ONCE and fridge.SELF_CHECK above/in the by_type registry,
|
||||
# both of which have a confirmed, cleanly modelable contract.
|
||||
'/airlevelcheck/vs/0', # periodic air-quality sensing scheduler plumbing
|
||||
'/aisleep/vs/0', # AI-sleep feedback state (no actionable control)
|
||||
'/availablecontrolsets/vs/0', # opaque hex-encoded control-set bitmap
|
||||
'/da/softreset/vs/0', # soft-reset trigger plumbing
|
||||
'/keepnormalstate/vs/0', # internal keep-normal flag
|
||||
'/mds/absencemonitoring/vs/0', # motion-detection sensor plumbing (empty here)
|
||||
'/mds/absencestate/vs/0', # motion-detection state (empty here)
|
||||
'/remotedatacontrol/vs/0', # remote data-control session status
|
||||
'/remotetemperature/vs/0', # external temp-sensor feed (unset on this unit)
|
||||
'/reserverulesets/vs/0', # opaque hex-encoded schedule reservation blob
|
||||
'/welcome/temperature/vs/0', # welcome-cooling plumbing
|
||||
]
|
||||
|
||||
# Built as bare no-entity caps; folded into the AC registry (not global).
|
||||
|
||||
@@ -242,6 +242,14 @@ SELF_CHECK = Capability(
|
||||
SensorDesc(key='selfcheck_result', field='x.com.samsung.da.result',
|
||||
name='Self-check result', icon='mdi:clipboard-check-outline',
|
||||
entity_category='diagnostic'),
|
||||
# List of error codes from the last self-check; joined for display.
|
||||
# Not every fridge reports the field, hence the exists_fn.
|
||||
SensorDesc(key='selfcheck_error', field='x.com.samsung.da.error',
|
||||
name='Self-check error', icon='mdi:alert-circle-outline',
|
||||
entity_category='diagnostic',
|
||||
exists_fn=lambda rep, resources: (
|
||||
not rep or 'x.com.samsung.da.error' in rep),
|
||||
value_fn=lambda v: (', '.join(v) if v else None) if isinstance(v, list) else v),
|
||||
ButtonDesc(key='selfcheck_start', field='', name='Start self-check',
|
||||
payload='Start', icon='mdi:play-circle-outline',
|
||||
entity_category='diagnostic',
|
||||
@@ -580,6 +588,33 @@ FLEX_ZONE = Capability(
|
||||
),
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# AI energy-saving level
|
||||
#
|
||||
# supportedAiLevel is a single-entry list ('1' only) on some hardware, where
|
||||
# a select would offer no real choice — gated to >1 supported level.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
AI_ENERGY_LEVEL = Capability(
|
||||
href='/energy/ailevel/vs/0',
|
||||
poll_tier='cold',
|
||||
entities=(
|
||||
# Only levels '1'/'2' have translated labels in strings.json/translations;
|
||||
# a higher supportedAiLevel entry renders as its raw value until added.
|
||||
SelectDesc(key='ai_energy_level', field='aiLevel',
|
||||
name='AI energy level', icon='mdi:leaf',
|
||||
translation_key='ai_energy_level',
|
||||
entity_category='config',
|
||||
options_field='supportedAiLevel',
|
||||
exists_fn=lambda rep, resources: (
|
||||
not rep or (
|
||||
isinstance((sl := rep.get('supportedAiLevel')), (list, tuple))
|
||||
and len(sl) > 1)),
|
||||
write_fn=lambda p, rep, href=None: (
|
||||
['energy', 'ailevel', 'vs', '0'], {'aiLevel': p})),
|
||||
),
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Firmware update
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@@ -85,14 +85,9 @@ IGNORED: list[Capability] = [
|
||||
# Warranty/service-plan enrollment status — every field reads "Unknown"
|
||||
# on hardware not enrolled in a Samsung Care+ style program.
|
||||
Capability(href='/dginformation/vs/0'),
|
||||
# AI energy-saving level; only one value ('1') is ever in
|
||||
# supportedAiLevel on hardware seen so far, so there's no real choice
|
||||
# to expose. Revisit if a device surfaces more than one supported level.
|
||||
Capability(href='/energy/ailevel/vs/0'),
|
||||
# OCF-native vacation-mode flag (fridge). Only one value ('RVACATION_OFF')
|
||||
# has ever been seen in `modes` (issue #7's dump), same "no real choice
|
||||
# to expose yet" situation as /energy/ailevel/vs/0 above. Revisit if a
|
||||
# device surfaces it toggled on.
|
||||
# has ever been seen in `modes` (issue #7's dump) -- no real choice to
|
||||
# expose yet. Revisit if a device surfaces it toggled on.
|
||||
Capability(href='/mode/0'),
|
||||
# Opaque integer with no supportedModes/options list to interpret it
|
||||
# against — meaning unclear from the raw resource alone.
|
||||
|
||||
@@ -143,6 +143,12 @@
|
||||
"180": "3 hr",
|
||||
"240": "4 hr"
|
||||
}
|
||||
},
|
||||
"ai_energy_level": {
|
||||
"state": {
|
||||
"1": "Level 1",
|
||||
"2": "Level 2"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
|
||||
@@ -143,6 +143,12 @@
|
||||
"180": "3 hr",
|
||||
"240": "4 hr"
|
||||
}
|
||||
},
|
||||
"ai_energy_level": {
|
||||
"state": {
|
||||
"1": "Level 1",
|
||||
"2": "Level 2"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
|
||||
@@ -0,0 +1,595 @@
|
||||
{
|
||||
"meta": {
|
||||
"note": "Samsung Room A/C, model TP1X_DA-AC-RAC-01011 (oneUiVersion '7.0 Air conditioner', Tizen Lite). Scrubbed real /device/0 dump. Adds temperature via /temperatures/vs/0 + display light. Issue #17 (newer model class).",
|
||||
"issue": 17,
|
||||
"model": "TP1X_DA-AC-RAC-01011",
|
||||
"oneUiVersion": "7.0 Air conditioner"
|
||||
},
|
||||
"device0": [
|
||||
{
|
||||
"rt": [
|
||||
"x.com.samsung.devcol",
|
||||
"oic.wk.col"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.ll",
|
||||
"oic.if.b"
|
||||
]
|
||||
},
|
||||
{
|
||||
"href": "/airlevelcheck/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.periodicSensingInterval": "1800",
|
||||
"x.com.samsung.da.sensingState": "NonProcessing",
|
||||
"x.com.samsung.da.lastSensingTime": "1784508715",
|
||||
"x.com.samsung.da.lastSensingLevel": "Kr1",
|
||||
"x.com.samsung.da.periodicSensingSkipStatus": "Off",
|
||||
"x.com.samsung.da.periodicSensingSkipTime": "00000000",
|
||||
"x.com.samsung.da.periodicSensingActivationState": "On",
|
||||
"x.com.samsung.da.autoExeState": "Alarm",
|
||||
"x.com.samsung.da.supportedAutoExeState": [
|
||||
"Airpurify",
|
||||
"Alarm",
|
||||
"Sensing"
|
||||
],
|
||||
"x.com.samsung.da.startSensingOnce": "Off",
|
||||
"x.com.samsung.da.autoExeSetting": "Off",
|
||||
"x.com.samsung.da.supportedAutoExeSetting": [
|
||||
"On",
|
||||
"Off"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/aisleep/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.displayNightMode": "Off",
|
||||
"x.com.samsung.da.elapsedTime": "0",
|
||||
"x.com.samsung.da.requestFeedback": "Off",
|
||||
"x.com.samsung.da.resultFeedback": 0,
|
||||
"x.com.samsung.da.statusFeedback": "Idle",
|
||||
"x.com.samsung.da.sleepTime": "14002200"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/alarms/vs/0",
|
||||
"rep": {
|
||||
"rt": [
|
||||
"x.com.samsung.da.alarms"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.a"
|
||||
],
|
||||
"x.com.samsung.da.items": [
|
||||
{
|
||||
"x.com.samsung.da.id": "0",
|
||||
"x.com.samsung.da.alarmType": "Device",
|
||||
"x.com.samsung.da.code": "ErrorCode_OFF",
|
||||
"x.com.samsung.da.triggeredTime": "2026-07-22T07:13:19",
|
||||
"x.com.samsung.da.state": "Deleted"
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "1",
|
||||
"x.com.samsung.da.alarmType": "Device",
|
||||
"x.com.samsung.da.code": "FilterAlarm_OFF",
|
||||
"x.com.samsung.da.triggeredTime": "2026-07-22T07:13:19",
|
||||
"x.com.samsung.da.state": "Deleted"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/availablecontrolsets/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.sets": "000000A0012C016102490F0F0000",
|
||||
"x.com.samsung.da.id": "RAC",
|
||||
"x.com.samsung.da.version": "1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/configuration/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.region": "0000000000",
|
||||
"x.com.samsung.da.airconOptionList": [
|
||||
"PRODUCT_GLOBAL",
|
||||
"AI_GLOBAL_HEATPUMP_4.0",
|
||||
"AI_3.0",
|
||||
"Auto_To_AI",
|
||||
"AI_Heat",
|
||||
"ENERGY_2.0",
|
||||
"HOMECARE_WIZARD_V2",
|
||||
"DR",
|
||||
"SingleCommand_1"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/da/softreset/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.softwarereset": "false"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/drlc/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.start": "1970-01-01T00:00:00Z",
|
||||
"x.com.samsung.da.override": "Off",
|
||||
"x.com.samsung.da.durationminutes": "0",
|
||||
"x.com.samsung.da.drlcLevel": "0",
|
||||
"x.com.samsung.da.realSaving": "Off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/energy/consumption/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.cumulativeDate": "1784704371",
|
||||
"x.com.samsung.da.cumulativeSavedPower": "-1",
|
||||
"x.com.samsung.da.cumulativePower": "75025",
|
||||
"x.com.samsung.da.cumulativeUnit": "Wh",
|
||||
"x.com.samsung.da.instantaneousPowerUnit": "W",
|
||||
"x.com.samsung.da.cumulativePowerType": "total"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/file/information/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.timeoffset": "+02:00",
|
||||
"x.com.samsung.supprtedtype": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/filter/airdustfilter/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.filterUsageResolution": "1",
|
||||
"x.com.samsung.da.filterCapacity": "500",
|
||||
"x.com.samsung.da.filterCapacityUnit": "Hour",
|
||||
"x.com.samsung.da.filterResetType": [
|
||||
"replaceable",
|
||||
"washable"
|
||||
],
|
||||
"x.com.samsung.da.filterStatus": "normal",
|
||||
"x.com.samsung.da.filterUsage": "42",
|
||||
"x.com.samsung.da.filterDesiredUsage": "500",
|
||||
"x.com.samsung.da.supportedFilterDesiredUsage": [
|
||||
"180",
|
||||
"300",
|
||||
"500",
|
||||
"700"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/humidity/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.humidity": "0",
|
||||
"x.com.samsung.da.fivepercentHumidity": "53"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/information/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.otnDUID": "**REDACTED**",
|
||||
"x.com.samsung.da.modelNum": "TP1X_DA-AC-RAC-01011_0000|10275643|60010523001911F58E0049320092E900",
|
||||
"x.com.samsung.da.description": "TP1X_DA-AC-RAC-01011_0000",
|
||||
"x.com.samsung.da.serialNum": "**REDACTED**",
|
||||
"x.com.samsung.da.serialNumOption": "**REDACTED**",
|
||||
"x.com.samsung.da.items": [
|
||||
{
|
||||
"x.com.samsung.da.id": "0",
|
||||
"x.com.samsung.da.description": "Version",
|
||||
"x.com.samsung.da.type": "Software",
|
||||
"x.com.samsung.da.number": "02762A260401",
|
||||
"x.com.samsung.da.newVersionAvailable": "0"
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "1",
|
||||
"x.com.samsung.da.description": "Version",
|
||||
"x.com.samsung.da.type": "Firmware",
|
||||
"x.com.samsung.da.newVersionAvailable": "0",
|
||||
"x.com.samsung.da.number": "02756C25082500,FFFFFFFFFFFFFF"
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "2",
|
||||
"x.com.samsung.da.description": "Version",
|
||||
"x.com.samsung.da.type": "Firmware",
|
||||
"x.com.samsung.da.newVersionAvailable": "0",
|
||||
"x.com.samsung.da.number": "02669A24092600,02636A10001200"
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "3",
|
||||
"x.com.samsung.da.description": "Version",
|
||||
"x.com.samsung.da.type": "Firmware",
|
||||
"x.com.samsung.da.newVersionAvailable": "0",
|
||||
"x.com.samsung.da.number": "02672A10001000,FFFFFFFFFFFFFF"
|
||||
}
|
||||
],
|
||||
"x.com.samsung.da.diagDumpType": "file",
|
||||
"x.com.samsung.da.diagEndPoint": "SSM",
|
||||
"x.com.samsung.da.diagLogType": [
|
||||
"errCode",
|
||||
"dump"
|
||||
],
|
||||
"x.com.samsung.da.diagMnid": "0AJT",
|
||||
"x.com.samsung.da.diagSetupid": "AR4",
|
||||
"x.com.samsung.da.diagProtocolType": "BLE_OCF",
|
||||
"x.com.samsung.da.diagMinVersion": "3.0",
|
||||
"x.com.samsung.da.diagTsId": "DA01"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/keepnormalstate/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.keepnormal": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/light/vs/0",
|
||||
"rep": {
|
||||
"mode": "On",
|
||||
"supportedModes": [
|
||||
"On",
|
||||
"Off"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/mds/absencemonitoring/vs/0",
|
||||
"rep": {}
|
||||
},
|
||||
{
|
||||
"href": "/mds/absencestate/vs/0",
|
||||
"rep": {}
|
||||
},
|
||||
{
|
||||
"href": "/mode/convenient/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.modes": "Off",
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"Off",
|
||||
"Sleep",
|
||||
"Quiet",
|
||||
"Smart",
|
||||
"Speed",
|
||||
"MotionIndirect",
|
||||
"Nano",
|
||||
"NanoSleep",
|
||||
"DryComfort"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/mode/vs/0",
|
||||
"rep": {
|
||||
"rt": [
|
||||
"x.com.samsung.da.mode"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.a"
|
||||
],
|
||||
"x.com.samsung.da.modes": [
|
||||
"Heat"
|
||||
],
|
||||
"workingMode": "Heat",
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"Auto",
|
||||
"Cool",
|
||||
"Dry",
|
||||
"Heat",
|
||||
"Wind"
|
||||
],
|
||||
"x.com.samsung.da.options": [
|
||||
"OptionCode_56378",
|
||||
"Sleep_6",
|
||||
"ArtificialWorking_Off",
|
||||
"Off",
|
||||
"OutdoorTemp_68",
|
||||
"OutdoorConnection_Connected"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/option/airpurify/vs/0",
|
||||
"rep": {
|
||||
"rt": [
|
||||
"x.com.samsung.da.option.airpurify"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.a"
|
||||
],
|
||||
"x.com.samsung.da.modes": "Off",
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"On",
|
||||
"Off"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/option/autoclean/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.status": "Stop",
|
||||
"x.com.samsung.da.supportedStatus": [
|
||||
"Start",
|
||||
"Stop"
|
||||
],
|
||||
"x.com.samsung.da.progress": "0",
|
||||
"x.com.samsung.da.settingStatus": "On",
|
||||
"x.com.samsung.da.supportedSettingStatus": [
|
||||
"On",
|
||||
"Off"
|
||||
],
|
||||
"defaultSettingStatus": "On"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/option/muteonce/vs/0",
|
||||
"rep": {
|
||||
"muteonce": "Off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/personality/presence/vs/0",
|
||||
"rep": {}
|
||||
},
|
||||
{
|
||||
"href": "/power/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.power": "Off",
|
||||
"operationNumber": "39",
|
||||
"displayCondition": "Enable"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/realtimenotiforclient/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.timeforshortnoti": "0",
|
||||
"x.com.samsung.da.longnotisubscription": "true",
|
||||
"x.com.samsung.da.periodicnotisubscription": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/remotedatacontrol/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.status": "Off",
|
||||
"x.com.samsung.da.connectionStatus": "Disconnected"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/remotetemperature/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.temperature": "",
|
||||
"x.com.samsung.da.unit": "",
|
||||
"x.com.samsung.da.error": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/reserverulesets/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.sets": "3EFFFFFFFF101E121EFFFF101EFFFF000001001F0001001F001F0000009CACFFFF0000",
|
||||
"x.com.samsung.da.id": "RAC",
|
||||
"x.com.samsung.da.version": "1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/selfcheck/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.start": "Cancel",
|
||||
"x.com.samsung.da.status": "Ready",
|
||||
"x.com.samsung.da.progress": "0",
|
||||
"x.com.samsung.da.result": "NA",
|
||||
"x.com.samsung.da.supportedActions": [
|
||||
"Start",
|
||||
"Cancel"
|
||||
],
|
||||
"x.com.samsung.da.error": [
|
||||
"DA_ERROR_NONE"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/sensors/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.cleanLevel": "1",
|
||||
"x.com.samsung.da.items": [
|
||||
{
|
||||
"x.com.samsung.da.id": "0",
|
||||
"x.com.samsung.da.description": "Sensor for CleanLevel",
|
||||
"x.com.samsung.da.type": "CleanLevel",
|
||||
"x.com.samsung.da.value": [
|
||||
"1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "2",
|
||||
"x.com.samsung.da.description": "Sensor for Dust",
|
||||
"x.com.samsung.da.type": "Dust",
|
||||
"x.com.samsung.da.value": [
|
||||
"0",
|
||||
"0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "3",
|
||||
"x.com.samsung.da.description": "Sensor for FineDust",
|
||||
"x.com.samsung.da.type": "FineDust",
|
||||
"x.com.samsung.da.value": [
|
||||
"0",
|
||||
"0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "4",
|
||||
"x.com.samsung.da.description": "Sensor for SuperFineDust",
|
||||
"x.com.samsung.da.type": "SuperFineDust",
|
||||
"x.com.samsung.da.value": [
|
||||
"0",
|
||||
"0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/temperature/control/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.increment": "1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/temperatures/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.items": [
|
||||
{
|
||||
"x.com.samsung.da.id": "0",
|
||||
"x.com.samsung.da.description": "Temperature",
|
||||
"x.com.samsung.da.current": "17.0",
|
||||
"x.com.samsung.da.desired": "25.0",
|
||||
"x.com.samsung.da.minimum": "16",
|
||||
"x.com.samsung.da.maximum": "30",
|
||||
"x.com.samsung.da.increment": "1",
|
||||
"x.com.samsung.da.unit": "Celsius"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/welcome/temperature/vs/0",
|
||||
"rep": {
|
||||
"operatingStatus": "None",
|
||||
"requestId": "0000"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/wind/direction/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.modes": "Fix",
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"Fix",
|
||||
"Up_And_Low",
|
||||
"Left_And_Right",
|
||||
"All"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/wind/strength/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.modes": "0",
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"0",
|
||||
"1",
|
||||
"2",
|
||||
"3",
|
||||
"4"
|
||||
],
|
||||
"x.com.samsung.da.modesName": [
|
||||
"Auto",
|
||||
"Low",
|
||||
"Mid",
|
||||
"High",
|
||||
"Turbo"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/otninformation/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.target": "",
|
||||
"x.com.samsung.da.newVersionAvailable": "false",
|
||||
"otnStatus": "None",
|
||||
"flashingProgress": "0",
|
||||
"otnCompleteDate": "noHistory",
|
||||
"scheduledTime": "None",
|
||||
"swVersionInfo": {
|
||||
"platform": "Tizen Lite",
|
||||
"oneUiVersion": "7.0 Air conditioner",
|
||||
"osVersion": "4.0"
|
||||
},
|
||||
"otnList": [
|
||||
{
|
||||
"type": "WIFI",
|
||||
"modelId": "ARA-KR-TP1-25-ARXX00",
|
||||
"versions": [
|
||||
"11260401"
|
||||
],
|
||||
"visVersion": "260401"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/timezone/vs/0",
|
||||
"rep": {
|
||||
"timezoneid": "Africa/Johannesburg",
|
||||
"offset": "+02:00",
|
||||
"DST": "OFF"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/connectionconfig/vs/0",
|
||||
"rep": {
|
||||
"autoReconnectionMinVersion": "1.0",
|
||||
"autoReconnection": "true",
|
||||
"autoReconnectionProtocolType": [
|
||||
"helper_hotspot",
|
||||
"ble_ocf"
|
||||
],
|
||||
"supportedWiFiAuthType": [
|
||||
"OPEN",
|
||||
"WEP",
|
||||
"WPA-PSK",
|
||||
"WPA2-PSK",
|
||||
"SAE"
|
||||
],
|
||||
"supportedWiFiCryptoType": [
|
||||
"TKIP",
|
||||
"AES",
|
||||
"WEP-64",
|
||||
"WEP-128"
|
||||
],
|
||||
"supportedWiFiFreq": [
|
||||
"2.4G"
|
||||
],
|
||||
"calmConnectionCare": {
|
||||
"version": "1.0",
|
||||
"role": [
|
||||
"things"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/wirelessinfo/vs/0",
|
||||
"rep": {
|
||||
"macaddressWiFi": "**REDACTED**",
|
||||
"macaddressBLE": "**REDACTED**",
|
||||
"connectedApSsid": "**REDACTED**"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/quickcontrol/info/vs/0",
|
||||
"rep": {
|
||||
"supportedVersion": "1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/dginformation/vs/0",
|
||||
"rep": {
|
||||
"enrolmentstatus": "Unknown",
|
||||
"devicestate": "Unknown",
|
||||
"lockstatus": "Normal",
|
||||
"nextduedate": "",
|
||||
"workingminutes": 0,
|
||||
"paymentinfo": {
|
||||
"emiplan": "Unknown",
|
||||
"currency": "Unknown",
|
||||
"totalemi": 0,
|
||||
"totalemipaid": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
+591
@@ -0,0 +1,591 @@
|
||||
{
|
||||
"meta": {
|
||||
"model": "TP1X_DA-AC-RAC-01001_0000",
|
||||
"device_type": "airconditioner",
|
||||
"source": "issue #38 diagnostics (scrubbed)",
|
||||
"note": "Newer TP1X RAC board (oneUiVersion '7.0 Air conditioner') with a fuller resource set than the ARTIK051/TP2X boards: display light, self-check, mute-once, and a circuit-breaker current-limit setting."
|
||||
},
|
||||
"device0": [
|
||||
{
|
||||
"rt": [
|
||||
"x.com.samsung.devcol",
|
||||
"oic.wk.col"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.ll",
|
||||
"oic.if.b"
|
||||
]
|
||||
},
|
||||
{
|
||||
"href": "/aisleep/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.displayNightMode": "Off",
|
||||
"x.com.samsung.da.elapsedTime": "0",
|
||||
"x.com.samsung.da.requestFeedback": "Off",
|
||||
"x.com.samsung.da.resultFeedback": 0,
|
||||
"x.com.samsung.da.statusFeedback": "Idle",
|
||||
"x.com.samsung.da.sleepTime": "14002200"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/alarms/vs/0",
|
||||
"rep": {
|
||||
"rt": [
|
||||
"x.com.samsung.da.alarms"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.a"
|
||||
],
|
||||
"x.com.samsung.da.items": [
|
||||
{
|
||||
"x.com.samsung.da.id": "0",
|
||||
"x.com.samsung.da.alarmType": "Device",
|
||||
"x.com.samsung.da.code": "ErrorCode_OFF",
|
||||
"x.com.samsung.da.triggeredTime": "2026-07-22T12:58:00",
|
||||
"x.com.samsung.da.state": "Deleted"
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "1",
|
||||
"x.com.samsung.da.alarmType": "Device",
|
||||
"x.com.samsung.da.code": "FilterAlarm_OFF",
|
||||
"x.com.samsung.da.triggeredTime": "2026-07-22T12:58:00",
|
||||
"x.com.samsung.da.state": "Deleted"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/availablecontrolsets/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.sets": "AEB700A0012C01610248040F0000",
|
||||
"x.com.samsung.da.id": "RAC",
|
||||
"x.com.samsung.da.version": "1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/configuration/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.region": "0000000000",
|
||||
"x.com.samsung.da.airconOptionList": [
|
||||
"PRODUCT_GLOBAL",
|
||||
"AI_GLOBAL_HEATPUMP_4.0",
|
||||
"AI_3.0",
|
||||
"Auto_To_AI",
|
||||
"AI_Heat",
|
||||
"ENERGY_2.0",
|
||||
"HOMECARE_WIZARD_V2",
|
||||
"DR",
|
||||
"SingleCommand_1"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/connectionconfig/vs/0",
|
||||
"rep": {
|
||||
"autoReconnectionMinVersion": "1.0",
|
||||
"autoReconnection": "true",
|
||||
"autoReconnectionProtocolType": [
|
||||
"helper_hotspot",
|
||||
"ble_ocf"
|
||||
],
|
||||
"supportedWiFiAuthType": [
|
||||
"OPEN",
|
||||
"WEP",
|
||||
"WPA-PSK",
|
||||
"WPA2-PSK",
|
||||
"SAE"
|
||||
],
|
||||
"supportedWiFiCryptoType": [
|
||||
"TKIP",
|
||||
"AES",
|
||||
"WEP-64",
|
||||
"WEP-128"
|
||||
],
|
||||
"supportedWiFiFreq": [
|
||||
"2.4G"
|
||||
],
|
||||
"calmConnectionCare": {
|
||||
"version": "1.0",
|
||||
"role": [
|
||||
"things"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/da/softreset/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.softwarereset": "false"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/dginformation/vs/0",
|
||||
"rep": {
|
||||
"enrolmentstatus": "Unknown",
|
||||
"devicestate": "Unknown",
|
||||
"lockstatus": "Unknown",
|
||||
"nextduedate": "",
|
||||
"workingminutes": 0,
|
||||
"paymentinfo": {
|
||||
"emiplan": "Unknown",
|
||||
"currency": "Unknown",
|
||||
"totalemi": 0,
|
||||
"totalemipaid": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/drlc/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.start": "2026-07-22T10:59:14Z",
|
||||
"x.com.samsung.da.override": "Off",
|
||||
"x.com.samsung.da.durationminutes": "1148",
|
||||
"x.com.samsung.da.drlcLevel": "1",
|
||||
"x.com.samsung.da.realSaving": "Off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/electriccurrent/vs/0",
|
||||
"rep": {
|
||||
"operation": "Off",
|
||||
"modes": "3",
|
||||
"supportedModes": [
|
||||
"3",
|
||||
"4",
|
||||
"5",
|
||||
"6",
|
||||
"7",
|
||||
"8",
|
||||
"9"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/energy/consumption/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.cumulativeDate": "1784725078",
|
||||
"x.com.samsung.da.cumulativeSavedPower": "-1",
|
||||
"x.com.samsung.da.cumulativePower": "160922",
|
||||
"x.com.samsung.da.cumulativeUnit": "Wh",
|
||||
"x.com.samsung.da.instantaneousPowerUnit": "W",
|
||||
"x.com.samsung.da.cumulativePowerType": "total"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/file/information/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.timeoffset": "+03:00",
|
||||
"x.com.samsung.supprtedtype": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/filter/airdustfilter/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.filterUsageResolution": "1",
|
||||
"x.com.samsung.da.filterCapacity": "500",
|
||||
"x.com.samsung.da.filterCapacityUnit": "Hour",
|
||||
"x.com.samsung.da.filterResetType": [
|
||||
"replaceable",
|
||||
"washable"
|
||||
],
|
||||
"x.com.samsung.da.filterStatus": "normal",
|
||||
"x.com.samsung.da.filterUsage": "30",
|
||||
"x.com.samsung.da.filterDesiredUsage": "500",
|
||||
"x.com.samsung.da.supportedFilterDesiredUsage": [
|
||||
"180",
|
||||
"300",
|
||||
"500",
|
||||
"700"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/information/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.otnDUID": "**REDACTED**",
|
||||
"x.com.samsung.da.modelNum": "TP1X_DA-AC-RAC-01001_0000|10267841|60010532001911000E0041220090A000",
|
||||
"x.com.samsung.da.description": "TP1X_DA-AC-RAC-01001_0000",
|
||||
"x.com.samsung.da.serialNum": "**REDACTED**",
|
||||
"x.com.samsung.da.serialNumOption": "**REDACTED**",
|
||||
"x.com.samsung.da.items": [
|
||||
{
|
||||
"x.com.samsung.da.id": "0",
|
||||
"x.com.samsung.da.description": "Version",
|
||||
"x.com.samsung.da.type": "Software",
|
||||
"x.com.samsung.da.number": "02762A260401",
|
||||
"x.com.samsung.da.newVersionAvailable": "0"
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "1",
|
||||
"x.com.samsung.da.description": "Version",
|
||||
"x.com.samsung.da.type": "Firmware",
|
||||
"x.com.samsung.da.newVersionAvailable": "0",
|
||||
"x.com.samsung.da.number": "02678A24092500,FFFFFFFFFFFFFF"
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "2",
|
||||
"x.com.samsung.da.description": "Version",
|
||||
"x.com.samsung.da.type": "Firmware",
|
||||
"x.com.samsung.da.newVersionAvailable": "0",
|
||||
"x.com.samsung.da.number": "02669A24092600,02636A10001200"
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "3",
|
||||
"x.com.samsung.da.description": "Version",
|
||||
"x.com.samsung.da.type": "Firmware",
|
||||
"x.com.samsung.da.newVersionAvailable": "0",
|
||||
"x.com.samsung.da.number": "02672A10001000,FFFFFFFFFFFFFF"
|
||||
}
|
||||
],
|
||||
"x.com.samsung.da.diagDumpType": "file",
|
||||
"x.com.samsung.da.diagEndPoint": "SSM",
|
||||
"x.com.samsung.da.diagLogType": [
|
||||
"errCode",
|
||||
"dump"
|
||||
],
|
||||
"x.com.samsung.da.diagMnid": "0AJT",
|
||||
"x.com.samsung.da.diagSetupid": "AR2",
|
||||
"x.com.samsung.da.diagProtocolType": "BLE_OCF",
|
||||
"x.com.samsung.da.diagMinVersion": "3.0",
|
||||
"x.com.samsung.da.diagTsId": "DA01"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/keepnormalstate/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.keepnormal": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/light/vs/0",
|
||||
"rep": {
|
||||
"mode": "On",
|
||||
"supportedModes": [
|
||||
"On",
|
||||
"Off"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/mds/absencemonitoring/vs/0",
|
||||
"rep": {}
|
||||
},
|
||||
{
|
||||
"href": "/mds/absencestate/vs/0",
|
||||
"rep": {}
|
||||
},
|
||||
{
|
||||
"href": "/mode/convenient/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.modes": "Off",
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"Off",
|
||||
"Sleep",
|
||||
"Quiet",
|
||||
"Smart",
|
||||
"Speed"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/mode/vs/0",
|
||||
"rep": {
|
||||
"rt": [
|
||||
"x.com.samsung.da.mode"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.a"
|
||||
],
|
||||
"x.com.samsung.da.modes": [
|
||||
"Cool"
|
||||
],
|
||||
"workingMode": "Cool",
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"Auto",
|
||||
"Cool",
|
||||
"Dry",
|
||||
"Fan",
|
||||
"Heat"
|
||||
],
|
||||
"x.com.samsung.da.options": [
|
||||
"OptionCode_54328",
|
||||
"Sleep_0",
|
||||
"ArtificialWorking_Off",
|
||||
"ComfortAICooling_Off",
|
||||
"AiTempChanged_Off",
|
||||
"AiTemp_240",
|
||||
"Volume_100",
|
||||
"StopAutoClean_Idle",
|
||||
"DiagnosisAI_Off",
|
||||
"ProgressDiagnosisAI_0",
|
||||
"ResultDiagnosisAI_Normal",
|
||||
"ExtendOptionCode_230024",
|
||||
"UpdateAllow_NotAllowed",
|
||||
"DurationOn_120",
|
||||
"OutdoorTemp_92",
|
||||
"CoolCapa_50",
|
||||
"WarmCapa_60",
|
||||
"Autoclean_Off",
|
||||
"WelcomeCoolingState_Off",
|
||||
"WelcomeHeatingState_Off",
|
||||
"OutdoorConnection_Connected"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/option/airpurify/vs/0",
|
||||
"rep": {
|
||||
"rt": [
|
||||
"x.com.samsung.da.option.airpurify"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.a"
|
||||
],
|
||||
"x.com.samsung.da.modes": "Off",
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"On",
|
||||
"Off"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/option/autoclean/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.status": "Stop",
|
||||
"x.com.samsung.da.supportedStatus": [
|
||||
"Start",
|
||||
"Stop"
|
||||
],
|
||||
"x.com.samsung.da.progress": "0",
|
||||
"x.com.samsung.da.settingStatus": "Off",
|
||||
"x.com.samsung.da.supportedSettingStatus": [
|
||||
"On",
|
||||
"Off"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/option/muteonce/vs/0",
|
||||
"rep": {
|
||||
"muteonce": "Off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/otninformation/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.target": "",
|
||||
"x.com.samsung.da.newVersionAvailable": "false",
|
||||
"otnStatus": "None",
|
||||
"flashingProgress": "0",
|
||||
"otnCompleteDate": "noHistory",
|
||||
"scheduledTime": "None",
|
||||
"swVersionInfo": {
|
||||
"platform": "Tizen Lite",
|
||||
"oneUiVersion": "7.0 Air conditioner",
|
||||
"osVersion": "4.0"
|
||||
},
|
||||
"otnList": [
|
||||
{
|
||||
"type": "WIFI",
|
||||
"modelId": "ARA-KR-TP1-25-ARXX00",
|
||||
"versions": [
|
||||
"11260401"
|
||||
],
|
||||
"visVersion": "260401"
|
||||
},
|
||||
{
|
||||
"type": "Micom",
|
||||
"modelId": "845210267841FFFFFFFF",
|
||||
"versions": [
|
||||
"24092500",
|
||||
"FFFFFFFF"
|
||||
],
|
||||
"visVersion": "240925"
|
||||
},
|
||||
{
|
||||
"type": "Micom",
|
||||
"modelId": "045210267841FFFFFFFF",
|
||||
"versions": [
|
||||
"24092500",
|
||||
"FFFFFFFF"
|
||||
],
|
||||
"visVersion": "240925"
|
||||
},
|
||||
{
|
||||
"type": "Micom",
|
||||
"modelId": "04521026694110263641",
|
||||
"versions": [
|
||||
"24092600",
|
||||
"10001200"
|
||||
],
|
||||
"visVersion": "240926"
|
||||
},
|
||||
{
|
||||
"type": "Micom",
|
||||
"modelId": "FGCAN3",
|
||||
"versions": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"visVersion": "999999"
|
||||
},
|
||||
{
|
||||
"type": "Micom",
|
||||
"modelId": "845210266941FFFFFFFF",
|
||||
"versions": [
|
||||
"24092600",
|
||||
"FFFFFFFF"
|
||||
],
|
||||
"visVersion": "240926"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/personality/presence/vs/0",
|
||||
"rep": {}
|
||||
},
|
||||
{
|
||||
"href": "/power/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.power": "On",
|
||||
"operationNumber": "9"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/quickcontrol/info/vs/0",
|
||||
"rep": {
|
||||
"supportedVersion": "1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/realtimenotiforclient/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.timeforshortnoti": "0",
|
||||
"x.com.samsung.da.longnotisubscription": "false",
|
||||
"x.com.samsung.da.periodicnotisubscription": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/remotedatacontrol/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.status": "Off",
|
||||
"x.com.samsung.da.connectionStatus": "Disconnected"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/remotetemperature/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.temperature": "",
|
||||
"x.com.samsung.da.unit": "",
|
||||
"x.com.samsung.da.error": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/reserverulesets/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.sets": "AEFFFFFFFF101E121EFFFF101EFFFF000001001F00010000001F0000000000FFFF1E00",
|
||||
"x.com.samsung.da.id": "RAC",
|
||||
"x.com.samsung.da.version": "1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/selfcheck/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.start": "Cancel",
|
||||
"x.com.samsung.da.status": "Ready",
|
||||
"x.com.samsung.da.progress": "0",
|
||||
"x.com.samsung.da.result": "NA",
|
||||
"x.com.samsung.da.supportedActions": [
|
||||
"Start",
|
||||
"Cancel"
|
||||
],
|
||||
"x.com.samsung.da.error": [
|
||||
"DA_ERROR_NONE"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/temperature/control/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.increment": "1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/temperatures/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.items": [
|
||||
{
|
||||
"x.com.samsung.da.id": "0",
|
||||
"x.com.samsung.da.description": "Temperature",
|
||||
"x.com.samsung.da.current": "26.0",
|
||||
"x.com.samsung.da.desired": "22.0",
|
||||
"x.com.samsung.da.minimum": "16",
|
||||
"x.com.samsung.da.maximum": "30",
|
||||
"x.com.samsung.da.increment": "1",
|
||||
"x.com.samsung.da.unit": "Celsius"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/timezone/vs/0",
|
||||
"rep": {
|
||||
"timezoneid": "Asia/Amman",
|
||||
"offset": "+03:00",
|
||||
"DST": "OFF"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/welcome/temperature/vs/0",
|
||||
"rep": {
|
||||
"operatingStatus": "None",
|
||||
"requestId": "0000"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/wind/direction/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.modes": "All",
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"Fix",
|
||||
"Up_And_Low",
|
||||
"Left_And_Right",
|
||||
"All"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/wind/strength/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.modes": "1",
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"0",
|
||||
"1",
|
||||
"2",
|
||||
"3",
|
||||
"4"
|
||||
],
|
||||
"x.com.samsung.da.modesName": [
|
||||
"Auto",
|
||||
"Low",
|
||||
"Mid",
|
||||
"High",
|
||||
"Turbo"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/wirelessinfo/vs/0",
|
||||
"rep": {
|
||||
"macaddressWiFi": "**REDACTED**",
|
||||
"macaddressBLE": "**REDACTED**",
|
||||
"connectedApSsid": "**REDACTED**"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,380 @@
|
||||
{
|
||||
"meta": {
|
||||
"model": "TP2X_RAC_20K",
|
||||
"device_type": "airconditioner",
|
||||
"source": "issue #37 diagnostics (scrubbed)",
|
||||
"note": "Older/simpler RAC board reporting no oneUiVersion -- resolved via the '_RAC_' modelNum token fallback in for_device_by_model (unlike issue #38's TP1X board, which self-reports oneUiVersion)."
|
||||
},
|
||||
"device0": [
|
||||
{
|
||||
"rt": [
|
||||
"x.com.samsung.devcol",
|
||||
"oic.wk.col"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.ll",
|
||||
"oic.if.b"
|
||||
]
|
||||
},
|
||||
{
|
||||
"href": "/alarms/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.items": [
|
||||
{
|
||||
"x.com.samsung.da.id": "0",
|
||||
"x.com.samsung.da.description": "Alarm",
|
||||
"x.com.samsung.da.alarmType": "Device",
|
||||
"x.com.samsung.da.code": "ErrorCode_OFF",
|
||||
"x.com.samsung.da.triggeredTime": "2026-07-22T12:42:46",
|
||||
"x.com.samsung.da.state": "Deleted"
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "1",
|
||||
"x.com.samsung.da.description": "Alarm",
|
||||
"x.com.samsung.da.alarmType": "Device",
|
||||
"x.com.samsung.da.code": "FilterAlarm_OFF",
|
||||
"x.com.samsung.da.triggeredTime": "2026-07-22T12:42:46",
|
||||
"x.com.samsung.da.state": "Deleted"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/availablecontrolsets/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.sets": "000000A0012C01610249040F0000",
|
||||
"x.com.samsung.da.id": "RAC",
|
||||
"x.com.samsung.da.version": "1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/configuration/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.region": "0000000000",
|
||||
"x.com.samsung.da.airconOptionList": [
|
||||
"SingleCommand_1",
|
||||
"HOMECARE_WIZARD_V2",
|
||||
"PRODUCT_GLOBAL",
|
||||
"AI_RAC_GLOBAL_HEATPUMP_3.0",
|
||||
"AI_3.0",
|
||||
"Auto_To_AI",
|
||||
"AI_Heat"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/energy/consumption/0",
|
||||
"rep": {
|
||||
"power": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/energy/consumption/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.instantaneousPower": "0.000000",
|
||||
"x.com.samsung.da.cumulativePower": "717060",
|
||||
"x.com.samsung.da.cumulativeUnit": "Wh",
|
||||
"x.com.samsung.da.cumulativePowerType": "total",
|
||||
"x.com.samsung.da.instantaneousPowerUnit": "W"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/file/information/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.timeoffset": "+03:00",
|
||||
"x.com.samsung.supprtedtype": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/filter/airdustfilter/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.filterUsage": "39",
|
||||
"x.com.samsung.da.filterUsageResolution": "1",
|
||||
"x.com.samsung.da.filterDesiredUsage": "500",
|
||||
"x.com.samsung.da.filterStatus": "normal",
|
||||
"x.com.samsung.da.filterCapacity": "500",
|
||||
"x.com.samsung.da.filterCapacityUnit": "Hour",
|
||||
"x.com.samsung.da.filterResetType": [
|
||||
"replaceable",
|
||||
"washable"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/humidity/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.humidity": "0",
|
||||
"x.com.samsung.da.fivepercentHumidity": "43"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/information/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.modelNum": "TP2X_RAC_20K|10229641|60010523001511014600003200800000",
|
||||
"x.com.samsung.da.description": "TP2X_RAC_20K",
|
||||
"x.com.samsung.da.serialNum": "**REDACTED**",
|
||||
"x.com.samsung.da.otnDUID": "**REDACTED**",
|
||||
"x.com.samsung.da.diagProtocolType": "WIFI_HTTPS",
|
||||
"x.com.samsung.da.diagLogType": [
|
||||
"errCode",
|
||||
"dump"
|
||||
],
|
||||
"x.com.samsung.da.diagDumpType": "file",
|
||||
"x.com.samsung.da.diagEndPoint": "SSM",
|
||||
"x.com.samsung.da.diagMnid": "0AJT",
|
||||
"x.com.samsung.da.diagSetupid": "010",
|
||||
"x.com.samsung.da.diagMinVersion": "1.0",
|
||||
"x.com.samsung.da.serialNumOption": "**REDACTED**",
|
||||
"x.com.samsung.da.items": [
|
||||
{
|
||||
"x.com.samsung.da.id": "0",
|
||||
"x.com.samsung.da.description": "Version",
|
||||
"x.com.samsung.da.type": "Software",
|
||||
"x.com.samsung.da.number": "02273A260324",
|
||||
"x.com.samsung.da.newVersionAvailable": "0"
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "1",
|
||||
"x.com.samsung.da.description": "Version",
|
||||
"x.com.samsung.da.type": "Firmware",
|
||||
"x.com.samsung.da.number": "102296A23012700",
|
||||
"x.com.samsung.da.newVersionAvailable": "0"
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "2",
|
||||
"x.com.samsung.da.description": "Version",
|
||||
"x.com.samsung.da.type": "Firmware",
|
||||
"x.com.samsung.da.newVersionAvailable": "0"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/keepnormalstate/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.keepnormal": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/mode/convenient/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.modes": "Off",
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"Off",
|
||||
"Sleep",
|
||||
"Quiet",
|
||||
"Smart",
|
||||
"Speed",
|
||||
"MotionIndirect",
|
||||
"MotionDirect",
|
||||
"Nano",
|
||||
"NanoSleep"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/mode/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"Auto",
|
||||
"Cool",
|
||||
"Dry",
|
||||
"Wind",
|
||||
"Heat",
|
||||
"HOMECARE_WIZARD_V2"
|
||||
],
|
||||
"x.com.samsung.da.modes": [
|
||||
"Cool"
|
||||
],
|
||||
"x.com.samsung.da.options": [
|
||||
"Sleep_16",
|
||||
"ArtificialWorking_Off",
|
||||
"ComfortAICooling_Off",
|
||||
"MotionState_Normal",
|
||||
"AiTempChanged_Off",
|
||||
"AiTemp_240",
|
||||
"OutdoorTemp_85",
|
||||
"CoolCapa_35",
|
||||
"WarmCapa_35",
|
||||
"Light_Off",
|
||||
"Volume_100",
|
||||
"StopAutoClean_Idle",
|
||||
"Autoclean_Off",
|
||||
"DiagnosisAI_Off",
|
||||
"ProgressDiagnosisAI_1",
|
||||
"ResultDiagnosisAI_Normal",
|
||||
"MotionMode_PowerSave",
|
||||
"OptionCode_56376",
|
||||
"ExtendOptionCode_230157",
|
||||
"RacInfo_None",
|
||||
"UpdateAllow_NotAllowed",
|
||||
"DurationOn_50",
|
||||
"WelcomeCoolingState_Off"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/option/autoclean/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.status": "Stop",
|
||||
"x.com.samsung.da.settingStatus": "Off",
|
||||
"x.com.samsung.da.progress": "0",
|
||||
"x.com.samsung.da.supportedStatus": [
|
||||
"Start",
|
||||
"Stop"
|
||||
],
|
||||
"x.com.samsung.da.supportedSettingStatus": [
|
||||
"On",
|
||||
"Off"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/option/muteonce/vs/0",
|
||||
"rep": {
|
||||
"muteonce": "Off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/otninformation/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.target": "",
|
||||
"x.com.samsung.da.newVersionAvailable": "false",
|
||||
"x.com.samsung.da.newVersionNo": "00000000",
|
||||
"x.com.samsung.da.currentVersionInfo": "00000000",
|
||||
"otnStatus": "None",
|
||||
"flashingProgress": "",
|
||||
"otnTarget": "main",
|
||||
"otnCompleteDate": "noHistory",
|
||||
"otnList": [
|
||||
{
|
||||
"type": "WIFI",
|
||||
"modelId": "TP2X_RAC_20K",
|
||||
"versions": [
|
||||
"11260324"
|
||||
],
|
||||
"visVersion": "260324"
|
||||
},
|
||||
{
|
||||
"type": "Micom",
|
||||
"modelId": "045210229641FFFFFFFF",
|
||||
"versions": [
|
||||
"23012700",
|
||||
"FFFFFFFF"
|
||||
],
|
||||
"visVersion": "230127"
|
||||
},
|
||||
{
|
||||
"type": "Micom",
|
||||
"modelId": "04521022954110229941",
|
||||
"versions": [
|
||||
"21092700",
|
||||
"10000300"
|
||||
],
|
||||
"visVersion": "210927"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/personality/presence/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.items": [
|
||||
{
|
||||
"x.com.samsung.da.id": "",
|
||||
"x.com.samsung.da.deviceId": "**REDACTED**",
|
||||
"x.com.samsung.da.value": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/power/0",
|
||||
"rep": {
|
||||
"value": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/power/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.power": "Off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/realtimenotiforclient/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.timeforshortnoti": "0",
|
||||
"x.com.samsung.da.longnotisubscription": "false",
|
||||
"x.com.samsung.da.periodicnotisubscription": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/sensors/vs/0",
|
||||
"rep": {}
|
||||
},
|
||||
{
|
||||
"href": "/temperature/control/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.increment": "1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/temperatures/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.items": [
|
||||
{
|
||||
"x.com.samsung.da.id": "0",
|
||||
"x.com.samsung.da.description": "Temperature",
|
||||
"x.com.samsung.da.desired": "22.0",
|
||||
"x.com.samsung.da.current": "26.0",
|
||||
"x.com.samsung.da.maximum": "30",
|
||||
"x.com.samsung.da.minimum": "16",
|
||||
"x.com.samsung.da.increment": "1.0",
|
||||
"x.com.samsung.da.unit": "Celsius"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/timezone/vs/0",
|
||||
"rep": {
|
||||
"timezoneid": "Asia/Amman",
|
||||
"offset": "+03:00",
|
||||
"DST": "OFF"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/wind/direction/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.modes": "Left_And_Right",
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"Fix",
|
||||
"All",
|
||||
"Up_And_Low",
|
||||
"Left_And_Right"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/wind/strength/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.modes": "2",
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"0",
|
||||
"1",
|
||||
"2",
|
||||
"3",
|
||||
"4"
|
||||
],
|
||||
"x.com.samsung.da.modesName": [
|
||||
"Auto",
|
||||
"Low",
|
||||
"Mid",
|
||||
"High",
|
||||
"Turbo"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"state_keys": [
|
||||
"air_filter_status",
|
||||
"air_filter_usage",
|
||||
"air_purify",
|
||||
"alarm_code",
|
||||
"auto_clean",
|
||||
"climate",
|
||||
"display_light",
|
||||
"energy_kwh",
|
||||
"energy_saved_kwh",
|
||||
"firmware_update",
|
||||
"mute_once",
|
||||
"selfcheck_error",
|
||||
"selfcheck_result",
|
||||
"selfcheck_status"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"state_keys": [
|
||||
"air_filter_status",
|
||||
"air_filter_usage",
|
||||
"air_purify",
|
||||
"alarm_code",
|
||||
"auto_clean",
|
||||
"climate",
|
||||
"current_limit_enabled",
|
||||
"current_limit_level",
|
||||
"display_light",
|
||||
"energy_kwh",
|
||||
"energy_saved_kwh",
|
||||
"firmware_update",
|
||||
"mute_once",
|
||||
"selfcheck_error",
|
||||
"selfcheck_result",
|
||||
"selfcheck_status"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"state_keys": [
|
||||
"air_filter_status",
|
||||
"air_filter_usage",
|
||||
"alarm_code",
|
||||
"auto_clean",
|
||||
"climate",
|
||||
"energy_kwh",
|
||||
"firmware_update",
|
||||
"mute_once",
|
||||
"power_watts"
|
||||
]
|
||||
}
|
||||
+4
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"state_keys": [
|
||||
"ai_energy_level",
|
||||
"alarm_code",
|
||||
"auto_door_opener",
|
||||
"autofill",
|
||||
@@ -39,11 +40,13 @@
|
||||
"rapid_freezing",
|
||||
"rapid_fridge",
|
||||
"sabbath_mode",
|
||||
"selfcheck_error",
|
||||
"selfcheck_result",
|
||||
"selfcheck_status",
|
||||
"welcome_lighting"
|
||||
],
|
||||
"discovery_unique_ids": [
|
||||
"samsung_refrigerator_ai_energy_level",
|
||||
"samsung_refrigerator_alarm_code",
|
||||
"samsung_refrigerator_auto_door_opener",
|
||||
"samsung_refrigerator_autofill",
|
||||
@@ -83,6 +86,7 @@
|
||||
"samsung_refrigerator_rapid_freezing",
|
||||
"samsung_refrigerator_rapid_fridge",
|
||||
"samsung_refrigerator_sabbath_mode",
|
||||
"samsung_refrigerator_selfcheck_error",
|
||||
"samsung_refrigerator_selfcheck_result",
|
||||
"samsung_refrigerator_selfcheck_start",
|
||||
"samsung_refrigerator_selfcheck_status",
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
"rapid_freezing",
|
||||
"rapid_fridge",
|
||||
"sabbath_mode",
|
||||
"selfcheck_error",
|
||||
"selfcheck_result",
|
||||
"selfcheck_status"
|
||||
]
|
||||
|
||||
+2
-1
@@ -279,7 +279,8 @@
|
||||
"rep": {
|
||||
"aiLevel": "1",
|
||||
"supportedAiLevel": [
|
||||
"1"
|
||||
"1",
|
||||
"2"
|
||||
],
|
||||
"href": "/energy/ailevel/vs/0"
|
||||
}
|
||||
|
||||
@@ -3,8 +3,10 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from unittest.mock import patch
|
||||
|
||||
import cbor2
|
||||
import pytest
|
||||
from pytest_homeassistant_custom_component.common import MockConfigEntry
|
||||
|
||||
@@ -150,12 +152,26 @@ class FakeObserveSession:
|
||||
self.subscribed: list[str] = []
|
||||
self.fail_hrefs: set[str] = set()
|
||||
self.closed = False
|
||||
# When set to a rep dict, subscribe() immediately delivers that rep
|
||||
# as an OBSERVE notification for the href — what a real device does
|
||||
# when it answers a subscription with the current representation.
|
||||
# `try_enter_observe_mode` clears its notified set before it
|
||||
# subscribes, so this is the only way to deliver a notify that
|
||||
# reliably counts: it lands synchronously via on_notification,
|
||||
# after that clear and before the post-sleep fraction check — a
|
||||
# notify raced in from another thread can be wiped by the clear
|
||||
# (or arrive after the check) depending on scheduling. Set to
|
||||
# None to model a device that answers subscriptions but never
|
||||
# notifies.
|
||||
self.notify_on_subscribe: dict[str, Any] | None = None
|
||||
|
||||
def subscribe(self, path_segs):
|
||||
href = '/' + '/'.join(path_segs)
|
||||
if href in self.fail_hrefs:
|
||||
raise ConnectionError("subscribe failed")
|
||||
self.subscribed.append(href)
|
||||
if self.notify_on_subscribe is not None and self.on_notification is not None:
|
||||
self.on_notification(href, cbor2.dumps(self.notify_on_subscribe))
|
||||
return b'\x01'
|
||||
|
||||
def refresh_observes(self, paths):
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
"""Tests for the LocalThingsCoordinator."""
|
||||
from __future__ import annotations
|
||||
|
||||
import threading
|
||||
import time
|
||||
from datetime import timedelta
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
@@ -260,21 +258,18 @@ async def test_enters_observe_mode_when_hot_warm_hrefs_notify(
|
||||
coordinator: LocalThingsCoordinator = hass.data[DOMAIN][mock_entry.entry_id]
|
||||
hrefs = coordinator._hot_hrefs + coordinator._warm_hrefs
|
||||
|
||||
# try_enter_observe_mode clears prior notifications as soon as it
|
||||
# starts, so notifies must land *during* its grace-period sleep (same
|
||||
# pattern test_observe.py uses), not before the call.
|
||||
def _notify_during_grace_period():
|
||||
time.sleep(0.005)
|
||||
for href in hrefs:
|
||||
fake.on_notification(href, cbor2.dumps({'notified': True}))
|
||||
|
||||
notifier = threading.Thread(target=_notify_during_grace_period, daemon=True)
|
||||
notifier.start()
|
||||
# try_enter_observe_mode clears prior notifications before it
|
||||
# subscribes, then checks the notified fraction after the grace-period
|
||||
# sleep — a notify counts as long as it arrives in that window, not
|
||||
# specifically during the sleep itself. The fake session delivers one
|
||||
# synchronously from subscribe() for every href it subscribes to (see
|
||||
# FakeObserveSession.notify_on_subscribe in conftest.py), which lands
|
||||
# it in that window by construction rather than by timing.
|
||||
fake.notify_on_subscribe = {'notified': True}
|
||||
entered = await hass.async_add_executor_job(
|
||||
coordinator._observe.try_enter_observe_mode,
|
||||
fake, hrefs, 0.02, 0.8,
|
||||
)
|
||||
notifier.join()
|
||||
|
||||
assert entered is True
|
||||
assert coordinator._observe.mode == MODE_OBSERVE
|
||||
@@ -300,18 +295,11 @@ async def test_reconnect_while_observe_mode_downgrades_to_poll(
|
||||
|
||||
# Get the coordinator into observe mode the same way
|
||||
# test_enters_observe_mode_when_hot_warm_hrefs_notify does.
|
||||
def _notify_during_grace_period():
|
||||
time.sleep(0.005)
|
||||
for href in hrefs:
|
||||
fake.on_notification(href, cbor2.dumps({'notified': True}))
|
||||
|
||||
notifier = threading.Thread(target=_notify_during_grace_period, daemon=True)
|
||||
notifier.start()
|
||||
fake.notify_on_subscribe = {'notified': True}
|
||||
entered = await hass.async_add_executor_job(
|
||||
coordinator._observe.try_enter_observe_mode,
|
||||
fake, hrefs, 0.02, 0.8,
|
||||
)
|
||||
notifier.join()
|
||||
assert entered is True
|
||||
assert coordinator.observe_mode == MODE_OBSERVE
|
||||
|
||||
@@ -324,12 +312,12 @@ async def test_reconnect_while_observe_mode_downgrades_to_poll(
|
||||
|
||||
# Simulate the existing "poll failed, reconnecting" branch: _poll_once
|
||||
# fails once (triggering the reconnect/backoff path), then succeeds.
|
||||
# No fresh notifies are supplied for the immediate resubscribe attempt
|
||||
# this now triggers, so its grace period (shortened for tests by the
|
||||
# `_fast_coordinator_timers` autouse fixture — see conftest.py) times
|
||||
# out and mode stays 'poll' — this test only asserts the tear-down half of
|
||||
# the fix; test_reconnect_from_observe_mode_resubscribes_immediately
|
||||
# covers the successful-immediate-resubscribe half.
|
||||
# The device stops notifying on subscribe, so the immediate resubscribe
|
||||
# attempt this now triggers gets nothing back and mode stays 'poll' —
|
||||
# this test only asserts the tear-down half of the fix;
|
||||
# test_reconnect_from_observe_mode_resubscribes_immediately covers the
|
||||
# successful-immediate-resubscribe half.
|
||||
fake.notify_on_subscribe = None
|
||||
with (
|
||||
patch(
|
||||
'custom_components.localthings.coordinator.LocalThingsCoordinator._poll_once',
|
||||
@@ -362,18 +350,11 @@ async def test_poll_timeout_skips_reconnect_when_push_is_healthy(
|
||||
coordinator: LocalThingsCoordinator = hass.data[DOMAIN][mock_entry.entry_id]
|
||||
hrefs = coordinator._hot_hrefs + coordinator._warm_hrefs
|
||||
|
||||
def _notify_during_grace_period():
|
||||
time.sleep(0.005)
|
||||
for href in hrefs:
|
||||
fake.on_notification(href, cbor2.dumps({'notified': True}))
|
||||
|
||||
notifier = threading.Thread(target=_notify_during_grace_period, daemon=True)
|
||||
notifier.start()
|
||||
fake.notify_on_subscribe = {'notified': True}
|
||||
entered = await hass.async_add_executor_job(
|
||||
coordinator._observe.try_enter_observe_mode,
|
||||
fake, hrefs, 0.02, 0.8,
|
||||
)
|
||||
notifier.join()
|
||||
assert entered is True
|
||||
assert coordinator.observe_mode == MODE_OBSERVE
|
||||
|
||||
@@ -408,18 +389,11 @@ async def test_poll_timeout_reconnects_after_consecutive_limit_even_with_push(
|
||||
coordinator: LocalThingsCoordinator = hass.data[DOMAIN][mock_entry.entry_id]
|
||||
hrefs = coordinator._hot_hrefs + coordinator._warm_hrefs
|
||||
|
||||
def _notify_during_grace_period():
|
||||
time.sleep(0.005)
|
||||
for href in hrefs:
|
||||
fake.on_notification(href, cbor2.dumps({'notified': True}))
|
||||
|
||||
notifier = threading.Thread(target=_notify_during_grace_period, daemon=True)
|
||||
notifier.start()
|
||||
fake.notify_on_subscribe = {'notified': True}
|
||||
entered = await hass.async_add_executor_job(
|
||||
coordinator._observe.try_enter_observe_mode,
|
||||
fake, hrefs, 0.02, 0.8,
|
||||
)
|
||||
notifier.join()
|
||||
assert entered is True
|
||||
assert coordinator.observe_mode == MODE_OBSERVE
|
||||
|
||||
@@ -441,7 +415,10 @@ async def test_poll_timeout_reconnects_after_consecutive_limit_even_with_push(
|
||||
assert coordinator.observe_mode == MODE_OBSERVE
|
||||
|
||||
# The final consecutive timeout crosses the limit and triggers the
|
||||
# existing reconnect path, which then succeeds and downgrades.
|
||||
# existing reconnect path, which then succeeds and downgrades. The
|
||||
# device stops notifying on subscribe, so the resubscribe attempt that
|
||||
# follows the reconnect finds nothing and mode stays 'poll'.
|
||||
fake.notify_on_subscribe = None
|
||||
with (
|
||||
patch(
|
||||
'custom_components.localthings.coordinator.LocalThingsCoordinator._poll_once',
|
||||
@@ -473,18 +450,11 @@ async def test_poll_timeout_counter_resets_when_push_is_healthy_again(
|
||||
coordinator: LocalThingsCoordinator = hass.data[DOMAIN][mock_entry.entry_id]
|
||||
hrefs = coordinator._hot_hrefs + coordinator._warm_hrefs
|
||||
|
||||
def _notify_during_grace_period():
|
||||
time.sleep(0.005)
|
||||
for href in hrefs:
|
||||
fake.on_notification(href, cbor2.dumps({'notified': True}))
|
||||
|
||||
notifier = threading.Thread(target=_notify_during_grace_period, daemon=True)
|
||||
notifier.start()
|
||||
fake.notify_on_subscribe = {'notified': True}
|
||||
entered = await hass.async_add_executor_job(
|
||||
coordinator._observe.try_enter_observe_mode,
|
||||
fake, hrefs, 0.02, 0.8,
|
||||
)
|
||||
notifier.join()
|
||||
assert entered is True
|
||||
assert coordinator.observe_mode == MODE_OBSERVE
|
||||
|
||||
@@ -526,21 +496,11 @@ async def test_reconnect_from_observe_mode_resubscribes_immediately(
|
||||
coordinator: LocalThingsCoordinator = hass.data[DOMAIN][mock_entry.entry_id]
|
||||
hrefs = coordinator._hot_hrefs + coordinator._warm_hrefs
|
||||
|
||||
def _notify(delay: float = 0.005) -> threading.Thread:
|
||||
def _run():
|
||||
time.sleep(delay)
|
||||
for href in hrefs:
|
||||
fake.on_notification(href, cbor2.dumps({'notified': True}))
|
||||
t = threading.Thread(target=_run, daemon=True)
|
||||
t.start()
|
||||
return t
|
||||
|
||||
notifier = _notify()
|
||||
fake.notify_on_subscribe = {'notified': True}
|
||||
entered = await hass.async_add_executor_job(
|
||||
coordinator._observe.try_enter_observe_mode,
|
||||
fake, hrefs, 0.02, 0.8,
|
||||
)
|
||||
notifier.join()
|
||||
assert entered is True
|
||||
assert coordinator.observe_mode == MODE_OBSERVE
|
||||
|
||||
@@ -563,10 +523,8 @@ async def test_reconnect_from_observe_mode_resubscribes_immediately(
|
||||
new=AsyncMock(),
|
||||
),
|
||||
):
|
||||
notifier = _notify()
|
||||
await coordinator.async_request_refresh()
|
||||
await hass.async_block_till_done()
|
||||
notifier.join()
|
||||
|
||||
assert coordinator.observe_mode == MODE_OBSERVE
|
||||
|
||||
@@ -587,21 +545,11 @@ async def test_sweep_mismatch_never_downgrades_a_live_observe_session(
|
||||
hrefs = coordinator._hot_hrefs + coordinator._warm_hrefs
|
||||
assert len(hrefs) >= 2
|
||||
|
||||
def _notify(delay: float = 0.005) -> threading.Thread:
|
||||
def _run():
|
||||
time.sleep(delay)
|
||||
for href in hrefs:
|
||||
fake.on_notification(href, cbor2.dumps({'notified': True}))
|
||||
t = threading.Thread(target=_run, daemon=True)
|
||||
t.start()
|
||||
return t
|
||||
|
||||
notifier = _notify()
|
||||
fake.notify_on_subscribe = {'notified': True}
|
||||
entered = await hass.async_add_executor_job(
|
||||
coordinator._observe.try_enter_observe_mode,
|
||||
fake, hrefs, 0.02, 0.8,
|
||||
)
|
||||
notifier.join()
|
||||
assert entered is True
|
||||
assert coordinator.observe_mode == MODE_OBSERVE
|
||||
|
||||
@@ -637,21 +585,11 @@ async def test_sweep_mismatch_forces_subpolls_on_a_live_observe_session(
|
||||
hrefs = coordinator._hot_hrefs + coordinator._warm_hrefs
|
||||
assert len(hrefs) >= 2
|
||||
|
||||
def _notify(delay: float = 0.005) -> threading.Thread:
|
||||
def _run():
|
||||
time.sleep(delay)
|
||||
for href in hrefs:
|
||||
fake.on_notification(href, cbor2.dumps({'notified': True}))
|
||||
t = threading.Thread(target=_run, daemon=True)
|
||||
t.start()
|
||||
return t
|
||||
|
||||
notifier = _notify()
|
||||
fake.notify_on_subscribe = {'notified': True}
|
||||
entered = await hass.async_add_executor_job(
|
||||
coordinator._observe.try_enter_observe_mode,
|
||||
fake, hrefs, 0.02, 0.8,
|
||||
)
|
||||
notifier.join()
|
||||
assert entered is True
|
||||
assert coordinator.observe_mode == MODE_OBSERVE
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ climate entity itself lives in climate.py (imports homeassistant) and is not
|
||||
importable here -- consistent with how the other HA platform files are untested.
|
||||
"""
|
||||
from custom_components.localthings.registry.adapter import flatten
|
||||
from custom_components.localthings.registry.by_type import for_device_by_model
|
||||
from custom_components.localthings.registry.by_type import for_device, for_device_by_model
|
||||
from custom_components.localthings.registry.capabilities import airconditioner
|
||||
from custom_components.localthings.registry.discovery import discover
|
||||
from custom_components.localthings.registry.entities import ClimateDesc
|
||||
@@ -23,6 +23,22 @@ def _ac():
|
||||
return reg, resources
|
||||
|
||||
|
||||
def _resolve(name):
|
||||
"""Mirror the coordinator's detection order: oneUiVersion first, modelNum
|
||||
fallback second (needed for issue #37's board, which reports neither
|
||||
oneUiVersion nor a '_PRAC_' modelNum token)."""
|
||||
resources = _load_device(name)
|
||||
otn = resources.get('/otninformation/vs/0', {})
|
||||
one_ui = otn.get('swVersionInfo', {}).get('oneUiVersion', '')
|
||||
info = resources['/information/vs/0']
|
||||
reg = for_device(one_ui) if one_ui else None
|
||||
if reg is None:
|
||||
reg = for_device_by_model(
|
||||
info['x.com.samsung.da.modelNum'], info['x.com.samsung.da.description'],
|
||||
)
|
||||
return reg, resources
|
||||
|
||||
|
||||
def _bound():
|
||||
reg, resources = _ac()
|
||||
return discover(resources, reg.capabilities, reg.pattern_capabilities), resources
|
||||
@@ -106,3 +122,114 @@ def test_climate_consumed_hrefs_declared_as_coverage():
|
||||
caps = reg.capabilities.get(href)
|
||||
assert caps, href
|
||||
assert all(c.entities == () for c in caps), href
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# TP1X_DA-AC-RAC-01011 (oneUiVersion "7.0 Air conditioner", Tizen Lite) -- a
|
||||
# newer model class than the ARTIK051_PRAC dump above. It has no OCF-standard
|
||||
# /temperature/current+desired pair (temperature lives on the vendor
|
||||
# /temperatures/vs/0 items[] resource), exposes a /light/vs/0 display light, and
|
||||
# carries extra vendor housekeeping hrefs. Issue #17 for this class (PR #36).
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _ac_tp1x():
|
||||
resources = _load_device('airconditioner_tp1x_da_ac_rac_01011')
|
||||
one_ui = resources['/otninformation/vs/0']['swVersionInfo']['oneUiVersion']
|
||||
return for_device(one_ui), resources
|
||||
|
||||
|
||||
def test_tp1x_resolves_to_airconditioner_registry():
|
||||
reg, _ = _ac_tp1x()
|
||||
assert reg is not None and reg.name == 'airconditioner'
|
||||
|
||||
|
||||
def test_tp1x_no_unbound_hrefs():
|
||||
"""Every resource in the TP1X dump binds or is covered -- including
|
||||
/temperatures/vs/0, /light/vs/0 and the housekeeping hrefs absent from
|
||||
the ARTIK051 dump. Clears the coverage-gap repair."""
|
||||
reg, resources = _ac_tp1x()
|
||||
unbound = []
|
||||
discover(resources, reg.capabilities, reg.pattern_capabilities, log=unbound.append)
|
||||
assert unbound == []
|
||||
|
||||
|
||||
def test_tp1x_display_light_switch_present():
|
||||
"""/light/vs/0 (mode On/Off) surfaces as the display-light switch."""
|
||||
reg, resources = _ac_tp1x()
|
||||
state = flatten(
|
||||
discover(resources, reg.capabilities, reg.pattern_capabilities), resources)
|
||||
assert state.get('display_light') is True # device reports mode == 'On'
|
||||
|
||||
|
||||
def test_tp1x_vendor_temperature_and_light_covered():
|
||||
"""The vendor temperature resource (read by the climate entity) and the
|
||||
display-light resource both resolve in the registry -- no gap."""
|
||||
reg, _ = _ac_tp1x()
|
||||
assert reg.capabilities.get('/temperatures/vs/0'), '/temperatures/vs/0'
|
||||
assert reg.capabilities.get('/light/vs/0'), '/light/vs/0'
|
||||
|
||||
|
||||
def test_tp1x_climate_entity_is_bound():
|
||||
"""The composite climate entity still binds the primary /mode/vs/0."""
|
||||
reg, resources = _ac_tp1x()
|
||||
bound = discover(resources, reg.capabilities, reg.pattern_capabilities)
|
||||
climate = [b for b in bound if isinstance(b.desc, ClimateDesc)]
|
||||
assert len(climate) == 1 and climate[0].href == '/mode/vs/0'
|
||||
|
||||
|
||||
def test_tp2x_rac_20k_model_resolves_via_model_fallback():
|
||||
"""TP2X_RAC_20K (issue #37) reports no oneUiVersion and no '_PRAC_' token
|
||||
-- resolved via the '_RAC_' modelNum fallback added for this device."""
|
||||
reg, _ = _resolve('airconditioner_tp2x_rac_20k')
|
||||
assert reg is not None and reg.name == 'airconditioner'
|
||||
|
||||
|
||||
def test_tp2x_rac_20k_no_unbound_hrefs():
|
||||
reg, resources = _resolve('airconditioner_tp2x_rac_20k')
|
||||
unbound = []
|
||||
discover(resources, reg.capabilities, reg.pattern_capabilities, log=unbound.append)
|
||||
assert unbound == []
|
||||
|
||||
|
||||
def test_tp1x_rac_model_resolves_via_one_ui_version():
|
||||
"""TP1X_DA-AC-RAC-01001_0000 (issue #38) self-reports oneUiVersion
|
||||
'7.0 Air conditioner' -- resolved via for_device(), not the modelNum
|
||||
fallback."""
|
||||
reg, _ = _resolve('airconditioner_tp1x_rac')
|
||||
assert reg is not None and reg.name == 'airconditioner'
|
||||
|
||||
|
||||
def test_tp1x_rac_no_unbound_hrefs():
|
||||
reg, resources = _resolve('airconditioner_tp1x_rac')
|
||||
unbound = []
|
||||
discover(resources, reg.capabilities, reg.pattern_capabilities, log=unbound.append)
|
||||
assert unbound == []
|
||||
|
||||
|
||||
def test_tp1x_rac_expected_state_keys_present():
|
||||
reg, resources = _resolve('airconditioner_tp1x_rac')
|
||||
bound = discover(resources, reg.capabilities, reg.pattern_capabilities)
|
||||
state = flatten(bound, resources)
|
||||
for key in ('climate', 'display_light', 'mute_once', 'selfcheck_status',
|
||||
'selfcheck_result', 'current_limit_enabled', 'current_limit_level'):
|
||||
assert key in state, key
|
||||
|
||||
|
||||
def test_mute_once_write_target():
|
||||
write = airconditioner.MUTE_ONCE.entities[0].write_fn
|
||||
assert write('On', {}) == (['option', 'muteonce', 'vs', '0'], {'muteonce': 'On'})
|
||||
assert write('Off', {}) == (['option', 'muteonce', 'vs', '0'], {'muteonce': 'Off'})
|
||||
|
||||
|
||||
def test_display_light_write_target():
|
||||
write = airconditioner.DISPLAY_LIGHT.entities[0].write_fn
|
||||
assert write('On', {}) == (['light', 'vs', '0'], {'mode': 'On'})
|
||||
assert write('Off', {}) == (['light', 'vs', '0'], {'mode': 'Off'})
|
||||
|
||||
|
||||
def test_current_limit_is_read_only():
|
||||
"""Meaning/write contract for the current-limit levels isn't confirmed
|
||||
from the dump alone -- exposed as read-only diagnostic sensors rather
|
||||
than a guessed writable control."""
|
||||
for desc in airconditioner.CURRENT_LIMIT.entities:
|
||||
assert getattr(desc, 'write_fn', None) is None
|
||||
@@ -0,0 +1,38 @@
|
||||
"""Tests for the /temperatures/vs/0 fallback helper in climate.py (PR #36).
|
||||
|
||||
`_temps_vs_item` is a pure module-level function (plain dict in, plain dict
|
||||
out) with no coordinator/entity dependency, so -- like select.py's `_display`
|
||||
in test_select_display.py -- it's testable directly even though the
|
||||
LocalThingsClimate entity itself needs a coordinator/hass fixture to
|
||||
exercise. This covers the actual fallback-selection logic that the
|
||||
registry-level "no unbound hrefs" tests in test_airconditioner_capabilities.py
|
||||
don't reach: TP1X-class firmware has no OCF-standard
|
||||
/temperature/current+desired pair, so current_temperature/target_temperature
|
||||
would silently be None without this fallback.
|
||||
"""
|
||||
from custom_components.localthings.climate import _temps_vs_item
|
||||
|
||||
|
||||
def test_temps_vs_item_returns_first_item():
|
||||
rep = {
|
||||
'x.com.samsung.da.items': [
|
||||
{'x.com.samsung.da.current': '17.0', 'x.com.samsung.da.desired': '25.0'},
|
||||
],
|
||||
}
|
||||
assert _temps_vs_item(rep) == {
|
||||
'x.com.samsung.da.current': '17.0', 'x.com.samsung.da.desired': '25.0',
|
||||
}
|
||||
|
||||
|
||||
def test_temps_vs_item_empty_when_items_missing():
|
||||
"""OCF-pair devices don't populate /temperatures/vs/0's items at all --
|
||||
callers must fall through to the OCF hrefs cleanly, not raise."""
|
||||
assert _temps_vs_item({}) == {}
|
||||
|
||||
|
||||
def test_temps_vs_item_empty_when_items_is_empty_list():
|
||||
assert _temps_vs_item({'x.com.samsung.da.items': []}) == {}
|
||||
|
||||
|
||||
def test_temps_vs_item_empty_when_items_entry_is_not_a_dict():
|
||||
assert _temps_vs_item({'x.com.samsung.da.items': ['not-a-dict']}) == {}
|
||||
@@ -171,6 +171,7 @@ class TestTp1xNativeDuplicateResources:
|
||||
assert 'ice_maker_enabled' not in state
|
||||
assert state['icemaker_one_enabled'] is True
|
||||
assert state['icemaker_two_enabled'] is True
|
||||
assert state['selfcheck_error'] == 'DA_ERROR_NONE'
|
||||
|
||||
|
||||
class TestPantryZone:
|
||||
@@ -228,3 +229,99 @@ class TestArtik051AndTp2xFixturesHaveCompleteCoverage:
|
||||
assert unbound == []
|
||||
state = flatten(bound, resources)
|
||||
assert state['flex_zone_mode'] == 'CV_FDR_BEVERAGE'
|
||||
|
||||
|
||||
class TestSelfCheckError:
|
||||
"""Self-check diagnostic error list -- surfaced on hardware that reports
|
||||
x.com.samsung.da.error, joined into a single display string."""
|
||||
|
||||
def _desc(self):
|
||||
return next(e for e in fridge.SELF_CHECK.entities if e.key == 'selfcheck_error')
|
||||
|
||||
def test_exists_when_field_present(self):
|
||||
desc = self._desc()
|
||||
assert desc.exists_fn({'x.com.samsung.da.error': ['DA_ERROR_NONE']}, {}) is True
|
||||
|
||||
def test_does_not_exist_when_field_absent(self):
|
||||
desc = self._desc()
|
||||
assert desc.exists_fn({'x.com.samsung.da.status': 'Ready'}, {}) is False
|
||||
|
||||
def test_exists_for_empty_stub_rep(self):
|
||||
"""An empty {} rep is /device/0's not-yet-fetched-stub carve-out --
|
||||
must be included-for-now, same as ENERGY_METER's fields."""
|
||||
desc = self._desc()
|
||||
assert desc.exists_fn({}, {}) is True
|
||||
|
||||
def test_value_joins_list(self):
|
||||
desc = self._desc()
|
||||
assert desc.value_fn(['E1', 'E2']) == 'E1, E2'
|
||||
|
||||
def test_value_passes_through_scalar(self):
|
||||
desc = self._desc()
|
||||
assert desc.value_fn('DA_ERROR_NONE') == 'DA_ERROR_NONE'
|
||||
|
||||
def test_value_none_for_empty_list(self):
|
||||
"""An empty error list means no value to show -- None (unknown),
|
||||
not an empty string."""
|
||||
desc = self._desc()
|
||||
assert desc.value_fn([]) is None
|
||||
|
||||
|
||||
class TestAiEnergyLevel:
|
||||
"""AI energy-saving level select on /energy/ailevel/vs/0 -- only
|
||||
exposed when the device actually offers more than one level; hardware
|
||||
that only ever reports a single supported level keeps this ignored
|
||||
(see capabilities/ignored.py)."""
|
||||
|
||||
def test_href(self):
|
||||
assert fridge.AI_ENERGY_LEVEL.href == '/energy/ailevel/vs/0'
|
||||
|
||||
def test_hidden_with_single_supported_level(self):
|
||||
desc = fridge.AI_ENERGY_LEVEL.entities[0]
|
||||
assert desc.exists_fn({'aiLevel': '1', 'supportedAiLevel': ['1']}, {}) is False
|
||||
|
||||
def test_shown_with_multiple_supported_levels(self):
|
||||
desc = fridge.AI_ENERGY_LEVEL.entities[0]
|
||||
assert desc.exists_fn(
|
||||
{'aiLevel': '1', 'supportedAiLevel': ['1', '2']}, {}) is True
|
||||
|
||||
def test_exists_for_empty_stub_rep(self):
|
||||
"""An empty {} rep is /device/0's not-yet-fetched-stub carve-out --
|
||||
must be included-for-now, same as ENERGY_METER's fields."""
|
||||
desc = fridge.AI_ENERGY_LEVEL.entities[0]
|
||||
assert desc.exists_fn({}, {}) is True
|
||||
|
||||
def test_hidden_when_supported_level_is_non_list_scalar(self):
|
||||
"""A stray scalar (e.g. a string) must not be len()-checked as if it
|
||||
were a list -- a 5-char string would otherwise wrongly pass `> 1`."""
|
||||
desc = fridge.AI_ENERGY_LEVEL.entities[0]
|
||||
assert desc.exists_fn(
|
||||
{'aiLevel': '1', 'supportedAiLevel': '12'}, {}) is False
|
||||
|
||||
def test_write(self):
|
||||
desc = fridge.AI_ENERGY_LEVEL.entities[0]
|
||||
path, body = desc.write_fn('2', {})
|
||||
assert path == ['energy', 'ailevel', 'vs', '0']
|
||||
assert body == {'aiLevel': '2'}
|
||||
|
||||
def test_synthetic_fixture_has_complete_coverage(self):
|
||||
"""refrigerator_device.json's supportedAiLevel was extended to two
|
||||
entries (['1', '2']) specifically to exercise this select -- the
|
||||
real captured TP1X_REF_21K_US dump only ever reports one entry, so
|
||||
this capability is synthetic-fixture-only for now."""
|
||||
from custom_components.localthings.registry.adapter import flatten
|
||||
from custom_components.localthings.registry.by_type import refrigerator
|
||||
from custom_components.localthings.registry.discovery import discover
|
||||
from tests.conftest import _load_device
|
||||
|
||||
resources = _load_device('refrigerator')
|
||||
unbound = []
|
||||
bound = discover(
|
||||
resources,
|
||||
refrigerator.REGISTRY.capabilities,
|
||||
refrigerator.REGISTRY.pattern_capabilities,
|
||||
log=unbound.append,
|
||||
)
|
||||
assert unbound == []
|
||||
state = flatten(bound, resources)
|
||||
assert state['ai_energy_level'] == '1'
|
||||
@@ -153,6 +153,52 @@ def test_registry_reproduces_golden_state_keys_for_tp2x_ref_20k():
|
||||
)
|
||||
|
||||
|
||||
def test_registry_reproduces_golden_state_keys_for_ac_tp1x_da_ac_rac_01011():
|
||||
"""Newer AC firmware (Tizen Lite, oneUiVersion "7.0 Air conditioner"; model
|
||||
TP1X_DA-AC-RAC-01011) reports temperature via the vendor /temperatures/vs/0
|
||||
items[] resource and adds a /light/vs/0 display light, with extra vendor
|
||||
housekeeping hrefs -- issue #17 for this model class (PR #36)."""
|
||||
from tests.conftest import _load_device
|
||||
resources = _load_device('airconditioner_tp1x_da_ac_rac_01011')
|
||||
golden = json.loads(
|
||||
(GOLDEN / 'airconditioner_tp1x_da_ac_rac_01011.json').read_text()
|
||||
)
|
||||
state_keys = _new_state_keys('airconditioner_tp1x_da_ac_rac_01011', resources)
|
||||
assert set(state_keys) == set(golden['state_keys']), (
|
||||
f"state_keys mismatch:\n"
|
||||
f" extra: {sorted(set(state_keys) - set(golden['state_keys']))}\n"
|
||||
f" missing: {sorted(set(golden['state_keys']) - set(state_keys))}"
|
||||
)
|
||||
|
||||
|
||||
def test_registry_reproduces_golden_state_keys_for_tp2x_rac_20k():
|
||||
"""TP2X_RAC_20K (issue #37) -- reports no oneUiVersion; resolved via the
|
||||
'_RAC_' modelNum token fallback in for_device_by_model."""
|
||||
from tests.conftest import _load_device
|
||||
resources = _load_device('airconditioner_tp2x_rac_20k')
|
||||
golden = json.loads((GOLDEN / 'airconditioner_tp2x_rac_20k.json').read_text())
|
||||
state_keys = _new_state_keys('airconditioner_tp2x_rac_20k', resources)
|
||||
assert set(state_keys) == set(golden['state_keys']), (
|
||||
f"state_keys mismatch:\n"
|
||||
f" extra: {sorted(set(state_keys) - set(golden['state_keys']))}\n"
|
||||
f" missing: {sorted(set(golden['state_keys']) - set(state_keys))}"
|
||||
)
|
||||
|
||||
|
||||
def test_registry_reproduces_golden_state_keys_for_tp1x_rac():
|
||||
"""TP1X_DA-AC-RAC-01001_0000 (issue #38) -- fuller RAC board with display
|
||||
light, self-check, mute-once, and a current-limit setting."""
|
||||
from tests.conftest import _load_device
|
||||
resources = _load_device('airconditioner_tp1x_rac')
|
||||
golden = json.loads((GOLDEN / 'airconditioner_tp1x_rac.json').read_text())
|
||||
state_keys = _new_state_keys('airconditioner_tp1x_rac', resources)
|
||||
assert set(state_keys) == set(golden['state_keys']), (
|
||||
f"state_keys mismatch:\n"
|
||||
f" extra: {sorted(set(state_keys) - set(golden['state_keys']))}\n"
|
||||
f" missing: {sorted(set(golden['state_keys']) - set(state_keys))}"
|
||||
)
|
||||
|
||||
|
||||
def test_resources_from_batch_preferred_over_flat():
|
||||
from tests.conftest import _resources_from_dump
|
||||
dump = {
|
||||
|
||||
Reference in new issue
Block a user