laundry: cover a device that advertises cloud cycles it has never loaded
A survey of every laundry diagnostics dump attached to an issue turned up 14 devices, 4 of which carry cloud-course tokens. Two were already known; the two new ones are both useful, and one contradicts something the investigation write-up asserted. A DW5000C dishwasher (issues #113/#123) advertises four downloaded programs and carries no payload token for any of them. That is a shape the corpus didn't have: the feature is not washer-only (DA_DW, not DA_WM), and a device can name programs whose payloads have never been observed. The existing code already handles it correctly -- nothing learnable, nothing offered, gap still counted for the Repairs issue -- so this adds the fixture, golden, and tests that keep it that way. A second WW5000C (issues #259/#343, firmware _B048) holds the same saved program as the first one's captured "Towels", and the two payloads differ at exactly one byte: byte 3, 04 against 06. Everything else -- id, slot, all four varying tag values, the whole tail -- is identical. So byte 3 is neither a per-board constant nor a property of the program, and the doc's claim that it is always 04 on this board was wrong. That is also the strongest argument yet for learning payloads per device: a catalog keyed on program id would have shipped one unit's byte 3 to the other. Nothing changes in the implementation as a result -- it never had a catalog -- but the reasoning is now backed by evidence rather than caution. Also recorded: both WW5000C units advertise the byte-identical slot list despite different firmware, so the program set looks factory- or region-assigned rather than user-curated; and a sentinel's byte 2 equals the selected course on one dump but not the other, so it stays unused.
This commit is contained in:
@@ -7,15 +7,29 @@ records the byte-level work behind it, including a decode that fit one
|
||||
device perfectly and collapsed on the second — the reason nothing in the
|
||||
shipped code interprets a program payload at all.
|
||||
|
||||
Two real devices carry these tokens:
|
||||
Four devices in the corpus carry these tokens (a survey of every laundry
|
||||
diagnostics dump attached to an issue turned up 14 devices; the other 10 have
|
||||
no cloud tokens at all, so this is a minority feature):
|
||||
|
||||
| dump | model | slots advertised | blob width |
|
||||
| --- | --- | --- | --- |
|
||||
| `washer_ww5000c_cloud` | WW5000C, `DA_WM_TP1_21_COMMON`, Table_02 | 9 | 20 bytes |
|
||||
| `washer_wa55a7700av` | WA55A7700AV, `DA_WM_TP1_21_COMMON`, Table_02 | 2 | 16 bytes |
|
||||
| dump | model | slots advertised | payloads seen | blob width |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `washer_ww5000c_cloud` | WW5000C `_B06C`, `DA_WM_TP1_21_COMMON`, Table_02 | 9 | 2 | 20 bytes |
|
||||
| `washer_wa55a7700av` | WA55A7700AV, `DA_WM_TP1_21_COMMON`, Table_02 | 2 | 1 | 16 bytes |
|
||||
| `dishwasher_dw5000c_cloud` | DW5000C, `DA_DW_TP1_21_COMMON` | 4 | **0** | — |
|
||||
| (not fixtured) | WW5000C `_B048`, issues #259/#343, Table_02 | 9 | 1 | 20 bytes |
|
||||
|
||||
Both are Table_02. Note that already: same course table, different
|
||||
everything else.
|
||||
Three things follow immediately from that table:
|
||||
|
||||
- **It isn't washer-only.** The DW5000C is a `DA_DW_` dishwasher.
|
||||
- **A device can advertise programs it has never loaded.** The DW5000C names
|
||||
four slots and carries no `CloudCourse_`/`OneTimeCloudCourse_` token at
|
||||
all. Nothing about it is learnable until its owner runs one, which is
|
||||
exactly the situation the Repairs issue exists to explain.
|
||||
- **Both WW5000C units advertise the byte-identical slot list**
|
||||
(`0A5C286B2D0C55301A`, same nine slots in the same order) despite different
|
||||
firmware builds. Either the set is a factory/regional default rather than
|
||||
something each owner curates, or the two dumps share an owner — unresolved,
|
||||
but worth knowing before assuming a user picked their own programs.
|
||||
|
||||
## The three tokens
|
||||
|
||||
@@ -136,11 +150,44 @@ WA55 00 | 1C59 | 05 | 49:16 4D:11 4A:22 4C:20 | 37:F0 | AC:
|
||||
- Then a fixed tail, terminated on both by an `AC:<value>` pair. The entire
|
||||
width difference is two trailing pairs the WA55 doesn't carry.
|
||||
|
||||
The tail is not program data. Across all nine WW5000C programs, byte 3 is
|
||||
always `04` and bytes 12–19 are byte-identical — every trailing pair carries
|
||||
value `F0` except the `AC` terminator, and `35` vs `37` looks like a board or
|
||||
profile marker rather than a field. (Byte 3 is not a field count: the board
|
||||
with the *higher* value has *fewer* pairs.)
|
||||
The tail is not program data. Across all nine WW5000C programs bytes 12–19
|
||||
are byte-identical — every trailing pair carries value `F0` except the `AC`
|
||||
terminator, and `35` vs `37` looks like a board or profile marker rather than
|
||||
a field. (It is not a field count either: the board with the *higher* leading
|
||||
byte has *fewer* pairs.)
|
||||
|
||||
### Byte 3 is not part of a program's identity
|
||||
|
||||
Worth its own heading, because it is the single strongest argument against
|
||||
ever shipping a table of payloads. The second WW5000C (issues #259/#343,
|
||||
firmware `_B048`) has its saved `CloudCourse` set to the same program as the
|
||||
first one's "Towels" capture — and the two payloads differ at exactly one
|
||||
byte:
|
||||
|
||||
```
|
||||
_B06C "Towels" 00 04 0A 04 49 40 4D 13 4A B8 4C 00 35 F0 04 F0 05 F0 AC 00
|
||||
_B048 CloudCourse 00 04 0A 06 49 40 4D 13 4A B8 4C 00 35 F0 04 F0 05 F0 AC 00
|
||||
^^
|
||||
```
|
||||
|
||||
Same program id, same slot, same values on all four varying tags, same tail.
|
||||
Only byte 3 moves, `04` → `06`. So it is neither a per-board constant (both
|
||||
are WW5000C) nor a property of the program (identical in every other
|
||||
respect) — most likely a download revision or sequence counter.
|
||||
|
||||
A hardcoded catalog keyed on program id would therefore have shipped one
|
||||
unit's byte 3 to the other unit. Whether the appliance would reject that, or
|
||||
accept it and do something unintended, is untested and does not need to be:
|
||||
every payload is learned from the device it will be replayed to.
|
||||
|
||||
### Sentinels
|
||||
|
||||
The `FFFF` "nothing loaded" payload takes its board's own width (16 bytes on
|
||||
the WA55, 20 on the WW5000C `_B048`) and always carries byte 3 = `00`. Its
|
||||
byte 2 is *not* reliably meaningful: it equals the currently selected course
|
||||
on the WA55 (`01`, on `Course_01`) and does not on the `_B048` (`1B`, on
|
||||
`Course_1C`). Nothing keys off it — a sentinel is rejected on its `FFFF`
|
||||
prefix, and its byte 2 is not an advertised slot in either dump anyway.
|
||||
|
||||
So the payload is tag/value, not fixed offsets — but knowing the grammar
|
||||
doesn't recover the values. The same four tags carry non-overlapping ranges
|
||||
@@ -194,5 +241,20 @@ sensors this decode would have enabled were dropped for the same reason.
|
||||
Care for "Baby care intensive", `30` Cloudy Day for "Cloudy heaven") and
|
||||
sometimes doesn't (`21` Colors for "Sports"). Probably a base-course
|
||||
reference; not reliable enough to use.
|
||||
3. A third device with downloaded programs would settle the tag/value
|
||||
reading of the payload.
|
||||
3. What does byte 3 count? It moves between two units holding the identical
|
||||
program (`04` vs `06`) and is `00` on every sentinel. A revision or
|
||||
download counter is the obvious guess; a dump taken before and after
|
||||
re-downloading the same program would confirm it.
|
||||
4. **The value encoding is still open, and a third *washer* won't
|
||||
necessarily settle it.** The survey found one, but its saved program is a
|
||||
duplicate of one already captured, so it adds no new tag values. What is
|
||||
actually needed is a dump from a board whose `/washer/vs/0` speaks in
|
||||
named levels (Cold/Warm/Hot, Low/High) *while that unit is sitting on a
|
||||
downloaded program* — then the payload's `49`/`4A` values can be read
|
||||
against settings that describe the same program. The WA55 is such a
|
||||
board but was captured on a local course, which is why it can't be used
|
||||
(see the trap above).
|
||||
5. The DW5000C's four slots (`8E 8D 8F 02`) are in the same numeric range as
|
||||
dishwasher course codes (its selected course is `86`), unlike the
|
||||
washers' slots. One payload from that machine would show whether slot ids
|
||||
are drawn from the course-code space on some boards.
|
||||
|
||||
+297
@@ -0,0 +1,297 @@
|
||||
{
|
||||
"device0": [
|
||||
{
|
||||
"rt": [
|
||||
"x.com.samsung.devcol",
|
||||
"oic.wk.col"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.ll",
|
||||
"oic.if.b"
|
||||
]
|
||||
},
|
||||
{
|
||||
"href": "/realtimenotiforclient/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.timeforshortnoti": "0",
|
||||
"x.com.samsung.da.periodicnotisubscription": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"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": "DishA_Disable",
|
||||
"x.com.samsung.da.triggeredTime": "2022-07-28T13:45:57",
|
||||
"x.com.samsung.da.state": "Deleted"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/diagnosis/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.diagnosisStart": "Ready"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/energy/consumption/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.cumulativeSavedPower": "0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/energy/consumption/0",
|
||||
"rep": {}
|
||||
},
|
||||
{
|
||||
"href": "/course/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"HOMECARE_WIZARD_V2"
|
||||
],
|
||||
"x.com.samsung.da.options": [
|
||||
"DeviceType_0812",
|
||||
"UpdateAllow_NotAllowed",
|
||||
"CourseDefaultTimeSet_008B008E009A006D003C000A006600A50059009D",
|
||||
"CourseDefaultTempSet_3C41363F41413737373700003C3C464649493C3C",
|
||||
"CourseDefaultFahrenheitSet_8C95819195958383838300008C8C9E9EA3A38C8C",
|
||||
"Course_86",
|
||||
"DetergentOnce_0",
|
||||
"DetergentLeft_0",
|
||||
"DetergentBase_0",
|
||||
"DetergentAlarm_Off",
|
||||
"DetergentType_0",
|
||||
"DetergentTotal_0",
|
||||
"ProgressTimeSet_420622820456B20384",
|
||||
"SendToDevice_Off",
|
||||
"GMT_F2",
|
||||
"SavingModeCondition_01010207828485868E8D020300",
|
||||
"SavingMode_Off",
|
||||
"DownloadCourseList_8F",
|
||||
"StormWashZone_Off",
|
||||
"AutoDoorRelease_On",
|
||||
"Sound_On",
|
||||
"WaterLevelSet_050404050302010205010400",
|
||||
"CloudExtraCourse_8E8D8F02",
|
||||
"EnergyLevelSet_050403050202010305040300",
|
||||
"UsagesDB_ok",
|
||||
"EnergyKW_396",
|
||||
"DrumCleanLog_Empty",
|
||||
"TimeSync_NotSupported"
|
||||
],
|
||||
"x.com.samsung.da.supportedOptions": [
|
||||
"482830CB002C002D00283830CB002C002D00284830CB002C002D00285830CB000C000D00086830CB002C002D002908308B000C000D0008E830CB000C000D0008D830CB002C002D0028F8308B000C000D00002830CB002C002D002"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/power/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.power": "On"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/power/0",
|
||||
"rep": {
|
||||
"value": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/kidslock/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.kidsLock": "Ready"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/kidslock/0",
|
||||
"rep": {
|
||||
"value": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/operational/state/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.state": "Run",
|
||||
"x.com.samsung.da.remainingTime": "00:44:00",
|
||||
"x.com.samsung.da.progressPercentage": "28",
|
||||
"x.com.samsung.da.delayStartTime": "00:00:00",
|
||||
"x.com.samsung.da.progress": "Wash",
|
||||
"x.com.samsung.da.supportedProgress": [
|
||||
"None",
|
||||
"Predrain",
|
||||
"Wash",
|
||||
"Rinse",
|
||||
"Drying",
|
||||
"Finish"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/operational/state/0",
|
||||
"rep": {
|
||||
"currentMachineState": "**REDACTED**",
|
||||
"machineStates": "**REDACTED**",
|
||||
"jobStates": [
|
||||
"None",
|
||||
"Predrain",
|
||||
"Wash",
|
||||
"Rinse",
|
||||
"Drying",
|
||||
"Finish"
|
||||
],
|
||||
"currentJobState": "Wash",
|
||||
"remainingTime": "00:44:00",
|
||||
"progressPercentage": "28"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/information/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.modelNum": "DA_DW_TP1_21_COMMON|30010741|40000200001711004981000000200000",
|
||||
"x.com.samsung.da.description": "DA_DW_TP1_21_COMMON_DW5000C/DD92-0010741_0001",
|
||||
"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": "WD0",
|
||||
"x.com.samsung.da.diagMinVersion": "1.0",
|
||||
"x.com.samsung.da.items": [
|
||||
{
|
||||
"x.com.samsung.da.id": "0",
|
||||
"x.com.samsung.da.description": "DA_DW_TP1_21_COMMON|30010741|40000200001711004981000000200000",
|
||||
"x.com.samsung.da.type": "Software",
|
||||
"x.com.samsung.da.number": "00081A230213(A214)",
|
||||
"x.com.samsung.da.newVersionAvailable": "0"
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "1",
|
||||
"x.com.samsung.da.description": "Firmware_1_DB_30010741230602142FFFFFFFFFFFFFFFFFFFFFFFFFFE(081230010741FFFFFFFF_30000000)(FileDown:0)(Type:0)",
|
||||
"x.com.samsung.da.type": "Firmware",
|
||||
"x.com.samsung.da.number": "23060214,FFFFFFFF",
|
||||
"x.com.samsung.da.newVersionAvailable": "0"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/file/information/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.timeoffset": "+00:00"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/wm/editcourse/vs/0",
|
||||
"rep": {}
|
||||
},
|
||||
{
|
||||
"href": "/wm/setinfo/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.isModelSettingWithoutSC": "false",
|
||||
"x.com.samsung.da.isModelSettingPowerOnOff": "false"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/dishwasher/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.highTemperatureDry": "Off",
|
||||
"x.com.samsung.da.sanitize": "Off",
|
||||
"x.com.samsung.da.selectedZone": "ON_ON",
|
||||
"x.com.samsung.da.rinseLevel": "0",
|
||||
"x.com.samsung.da.supportedSelectedZone": [
|
||||
"OFF_ON",
|
||||
"ON_ON"
|
||||
],
|
||||
"x.com.samsung.da.supportedSanitize": [
|
||||
"Off",
|
||||
"On"
|
||||
],
|
||||
"x.com.samsung.da.supportedHighTemperatureDry": [
|
||||
"Off",
|
||||
"On"
|
||||
],
|
||||
"x.com.samsung.da.supportedRinseLevel": [
|
||||
"0",
|
||||
"1",
|
||||
"2",
|
||||
"3",
|
||||
"4",
|
||||
"5",
|
||||
"6"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/otninformation/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.target": "Micom",
|
||||
"x.com.samsung.da.newVersionAvailable": "false"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/remotectrl/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.remoteControlEnabled": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/remotectrl/0",
|
||||
"rep": {
|
||||
"value": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/configuration/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.region": "0000000000",
|
||||
"x.com.samsung.da.countryCode": "CA"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/filter/waterfilter/vs/0",
|
||||
"rep": {}
|
||||
},
|
||||
{
|
||||
"href": "/drlc/0",
|
||||
"rep": {
|
||||
"DRLevel": 0,
|
||||
"start": "0000-00-00T00:00:00Z",
|
||||
"duration": 0,
|
||||
"override": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/drlc/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.drlcLevel": "0",
|
||||
"x.com.samsung.da.durationminutes": "0",
|
||||
"x.com.samsung.da.start": "0000-00-00T00:00:00Z",
|
||||
"x.com.samsung.da.override": "Off",
|
||||
"x.com.samsung.da.realSaving": "Off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/water/consumption/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.cumulativeWater": "2662000"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/wm/submode/vs/0",
|
||||
"rep": {
|
||||
"setTemperatureUnit": "F"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"state_keys": [
|
||||
"alarm_code",
|
||||
"auto_release_dry",
|
||||
"child_lock",
|
||||
"completion_minutes",
|
||||
"cycle",
|
||||
"cycle_active",
|
||||
"delay_start_hours",
|
||||
"diagnosis_status",
|
||||
"energy_saved_kwh",
|
||||
"filter_status",
|
||||
"filter_usage",
|
||||
"finish_time",
|
||||
"firmware_update",
|
||||
"heated_dry",
|
||||
"machine_state",
|
||||
"power_switch",
|
||||
"progress",
|
||||
"progress_percentage",
|
||||
"remote_control",
|
||||
"sanitize",
|
||||
"storm_wash",
|
||||
"water_liters"
|
||||
]
|
||||
}
|
||||
@@ -122,6 +122,59 @@ class TestRealDumps:
|
||||
store.observe(rep)
|
||||
assert store.download_candidates() == []
|
||||
|
||||
def test_a_dishwasher_advertises_programs_it_has_never_loaded(self):
|
||||
"""DW5000C (issues #113/#123): CloudExtraCourse_ names four slots
|
||||
with no CloudCourse_/OneTimeCloudCourse_ token anywhere in the array.
|
||||
|
||||
Two things at once -- the feature is not washer-only (DA_DW, not
|
||||
DA_WM), and a device can advertise programs whose payloads have never
|
||||
been observed. Nothing is learnable here, so nothing is offerable,
|
||||
but the gap is still countable and still worth telling the user
|
||||
about."""
|
||||
rep = _load_device("dishwasher_dw5000c_cloud")["/course/vs/0"]
|
||||
assert cloudcourse.advertised_slots(rep) == ["8E", "8D", "8F", "02"]
|
||||
assert cloudcourse.supports_cloud_courses(rep) is True
|
||||
|
||||
store = cloudcourse.CloudCourses()
|
||||
assert store.observe(rep) is False
|
||||
assert store.view() == {}
|
||||
assert cloudcourse.undiscovered(rep, store.snapshot()) == ["8E", "8D", "8F", "02"]
|
||||
|
||||
def test_byte_three_is_not_part_of_a_programs_identity(self):
|
||||
"""Two WW5000C units on different firmware (issue #342's _B06C and
|
||||
issues #259/#343's _B048) report the same program -- same id, same
|
||||
slot, same field values, same tail -- differing only at byte 3.
|
||||
|
||||
This is the evidence against a shipped catalog of payloads: keyed on
|
||||
program id, one unit's capture would have carried the other unit's
|
||||
byte 3. Both are recorded and replayed per device instead, so
|
||||
whatever byte 3 tracks never has to be understood."""
|
||||
b06c = "00040A0449404D134AB84C0035F004F005F0AC00"
|
||||
b048 = "00040A0649404D134AB84C0035F004F005F0AC00"
|
||||
assert b06c != b048
|
||||
# Same program by every identifier the device gives us.
|
||||
assert cloudcourse.slot_of(b06c) == cloudcourse.slot_of(b048) == "0A"
|
||||
assert b06c[:6] == b048[:6]
|
||||
assert b06c[8:] == b048[8:]
|
||||
# Learned separately per device, and each replays what it saw.
|
||||
for blob in (b06c, b048):
|
||||
store = cloudcourse.CloudCourses()
|
||||
store.observe(_rep(["CloudExtraCourse_0A", f"CloudCourse_{blob}"]))
|
||||
assert store.blob("0A") == blob
|
||||
|
||||
def test_a_sentinel_slot_byte_is_not_the_current_course(self):
|
||||
"""The two sentinels in the corpus disagree about this -- WA55's byte
|
||||
2 happens to equal its selected course, the WW5000C _B048's does not
|
||||
(1B against Course_1C). Nothing keys off it: a sentinel is rejected
|
||||
on its FFFF prefix, and its byte 2 is not an advertised slot either
|
||||
way."""
|
||||
wa55 = "FFFF010049004D004A804C0037F0AC00"
|
||||
b048 = "FFFF1B0049004D004A804C0035F004F005F0AC00"
|
||||
assert cloudcourse.slot_of(wa55) is None
|
||||
assert cloudcourse.slot_of(b048) is None
|
||||
# Different board widths, same sentinel rule.
|
||||
assert len(wa55) != len(b048)
|
||||
|
||||
def test_undiscovered_counts_against_what_the_device_advertises(self):
|
||||
rep = _load_device("washer_ww5000c_cloud")["/course/vs/0"]
|
||||
store = cloudcourse.CloudCourses()
|
||||
|
||||
@@ -1519,3 +1519,23 @@ def test_registry_reproduces_golden_state_keys_for_washer_ww5000c_cloud():
|
||||
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_dishwasher_dw5000c_cloud():
|
||||
"""A DW5000C dishwasher (issues #113/#123) that advertises four
|
||||
downloaded programs it has never loaded -- CloudExtraCourse_ with no
|
||||
CloudCourse_/OneTimeCloudCourse_ payload alongside it (issue #342).
|
||||
|
||||
Proves the cloud-cycle machinery is not washer-only and, more usefully,
|
||||
that a device can advertise programs whose payloads have never been
|
||||
seen. It adds no entity of its own either way."""
|
||||
from tests.conftest import _load_device
|
||||
|
||||
resources = _load_device("dishwasher_dw5000c_cloud")
|
||||
golden = json.loads((GOLDEN / "dishwasher_dw5000c_cloud.json").read_text())
|
||||
state_keys = _new_state_keys("dishwasher_dw5000c_cloud", 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))}"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user