From d869bb53d569099856319c61f3782ca51c78bd2d Mon Sep 17 00:00:00 2001 From: cjkas Date: Sat, 24 Oct 2026 10:56:54 +0200 Subject: [PATCH] test: record protocol 2 fixtures from a device hello, the schema and a whole sync as the device sent them, recorded with pytest test/e2e --record. Remote addresses, rolling codes, names, the server id, hostname and SSID are replaced with fake values while recording, since with an address and its rolling code anyone could forge frames for a real installation's shades. Co-Authored-By: Claude Opus 5.5 --- test/e2e/test_sync.py | 36 +- test/fixtures/proto2/hello.json | 8 + test/fixtures/proto2/schema.json | 510 +++++++++++++++ test/fixtures/proto2/sync.json | 1041 ++++++++++++++++++++++++++++++ 4 files changed, 1593 insertions(+), 2 deletions(-) create mode 100644 test/fixtures/proto2/hello.json create mode 100644 test/fixtures/proto2/schema.json create mode 100644 test/fixtures/proto2/sync.json diff --git a/test/e2e/test_sync.py b/test/e2e/test_sync.py index 5ce6841..6c30ec7 100644 --- a/test/e2e/test_sync.py +++ b/test/e2e/test_sync.py @@ -100,8 +100,36 @@ def test_resync_sends_it_all_again(connect, schema): assert "T" not in f["d"] +# What a recording must not keep from a real installation: the remote +# addresses and rolling codes would let anyone forge frames for its shades. +SCRUBBED = {"remoteAddress", "lastRollingCode", "sourceAddress", "startingAddress", "repeaters", + "name", "serverId", "hostname", "ssid"} + + +def scrub(value, schema, counter): + """Fake values of the same kind in place of anything identifying.""" + if isinstance(value, list): + return [scrub(v, schema, counter) for v in value] + if not isinstance(value, dict): + return value + out = {} + for key, v in value.items(): + name = schema.fields.get(key, (key,))[0] + if name in SCRUBBED: + counter[0] += 1 + if isinstance(v, list): + v = [0x100000 + counter[0] * 16 + i for i in range(len(v))] + elif isinstance(v, str): + v = "%s %u" % (name, counter[0]) + else: + v = 0x100000 + counter[0] + out[key] = scrub(v, schema, counter) + return out + + def test_record_fixtures(connect, recording): - """With --record, saves hello and a whole first sync for the other suites.""" + """With --record, saves hello and a whole first sync for the other suites, + with the installation's identifying values replaced.""" if not recording: return c = connect() @@ -111,8 +139,12 @@ def test_record_fixtures(connect, recording): hello = json.loads(c.raw[0]) with open(os.path.join(FIXTURES, "hello.json"), "w", encoding="utf-8") as f: json.dump(hello, f, indent=1) + counter = [0] + frames = [json.loads(r) for r in c.raw[1:]] + for frame in frames: + frame["d"] = scrub(frame["d"], schema, counter) with open(os.path.join(FIXTURES, "sync.json"), "w", encoding="utf-8") as f: - json.dump([json.loads(r) for r in c.raw[1:]], f, indent=1) + json.dump(frames, f, indent=1) expanded = { "hash": hello["d"]["sh"], "types": {str(k): v for k, v in sorted(schema.types.items())}, diff --git a/test/fixtures/proto2/hello.json b/test/fixtures/proto2/hello.json new file mode 100644 index 0000000..3235c0a --- /dev/null +++ b/test/fixtures/proto2/hello.json @@ -0,0 +1,8 @@ +{ + "v": 2, + "t": 1, + "d": { + "fw": "v3.0.14", + "sh": "8198c564" + } +} \ No newline at end of file diff --git a/test/fixtures/proto2/schema.json b/test/fixtures/proto2/schema.json new file mode 100644 index 0000000..86a3c0f --- /dev/null +++ b/test/fixtures/proto2/schema.json @@ -0,0 +1,510 @@ +{ + "hash": "8198c564", + "types": { + "1": "hello", + "2": "reply", + "3": "next", + "4": "sync", + "5": "syncEnd", + "6": "subscribe", + "7": "unsubscribe", + "128": "shadeState", + "129": "shade", + "130": "shadeRemoved", + "131": "group", + "132": "groupState", + "133": "groupRemoved", + "134": "room", + "135": "roomRemoved", + "136": "roomOrder", + "137": "shadeOrder", + "138": "groupOrder", + "139": "shadeCommand", + "140": "memory", + "141": "wifi", + "142": "ethernet", + "143": "frame", + "144": "frequencyScan", + "145": "repeaters" + }, + "fields": { + "D": { + "name": "downTime", + "kind": "i", + "default": 0 + }, + "E": { + "name": "errors", + "kind": "l", + "default": 0 + }, + "F": { + "name": "fields", + "kind": "l", + "default": 0 + }, + "G": { + "name": "groups", + "kind": "l", + "default": 0 + }, + "M": { + "name": "myTiltPos", + "kind": "i", + "default": -1 + }, + "Q": { + "name": "feeds", + "kind": "l", + "default": 0 + }, + "R": { + "name": "rooms", + "kind": "l", + "default": 0 + }, + "S": { + "name": "shades", + "kind": "l", + "default": 0 + }, + "T": { + "name": "types", + "kind": "l", + "default": 0 + }, + "U": { + "name": "upTime", + "kind": "i", + "default": 0 + }, + "X": { + "name": "repeaters", + "kind": "l", + "default": 0 + }, + "Y": { + "name": "tiltTime", + "kind": "i", + "default": 0 + }, + "Z": { + "name": "status", + "kind": "o", + "default": 0 + }, + "a": { + "name": "remoteAddress", + "kind": "i", + "default": 0 + }, + "bl": { + "name": "bitLength", + "kind": "i", + "default": 56 + }, + "bt": { + "name": "bits", + "kind": "i", + "default": 0 + }, + "c": { + "name": "lastRollingCode", + "kind": "i", + "default": 0 + }, + "ch": { + "name": "channel", + "kind": "i", + "default": 0 + }, + "cm": { + "name": "command", + "kind": "s", + "default": 0 + }, + "co": { + "name": "connected", + "kind": "b", + "default": 0 + }, + "cp": { + "name": "chipModel", + "kind": "s", + "default": 0 + }, + "ct": { + "name": "connType", + "kind": "s", + "default": 0 + }, + "d": { + "name": "direction", + "kind": "i", + "default": 0 + }, + "e": { + "name": "code", + "kind": "i", + "default": 0 + }, + "ek": { + "name": "encKey", + "kind": "i", + "default": 0 + }, + "f": { + "name": "flags", + "kind": "i", + "default": 0 + }, + "fc": { + "name": "flipCommands", + "kind": "b", + "default": 0 + }, + "fd": { + "name": "fullDuplex", + "kind": "b", + "default": 0 + }, + "fe": { + "name": "feed", + "kind": "i", + "default": 0 + }, + "fp": { + "name": "flipPosition", + "kind": "b", + "default": 0 + }, + "fq": { + "name": "frequency", + "kind": "n", + "default": 0 + }, + "fr": { + "name": "freeHeap", + "kind": "i", + "default": 0 + }, + "fw": { + "name": "fw", + "kind": "s", + "default": 0 + }, + "g": { + "name": "groupId", + "kind": "i", + "default": 0 + }, + "gd": { + "name": "gpioDown", + "kind": "i", + "default": 0 + }, + "gl": { + "name": "gpioLLTrigger", + "kind": "b", + "default": 0 + }, + "gm": { + "name": "gpioMy", + "kind": "i", + "default": 0 + }, + "gu": { + "name": "gpioUp", + "kind": "i", + "default": 0 + }, + "h": { + "name": "header", + "kind": "o", + "default": 0 + }, + "hn": { + "name": "hostname", + "kind": "s", + "default": 0 + }, + "hs": { + "name": "hwsync", + "kind": "i", + "default": 0 + }, + "i": { + "name": "paired", + "kind": "b", + "default": 0 + }, + "ig": { + "name": "inGroup", + "kind": "b", + "default": 0 + }, + "is": { + "name": "ids", + "kind": "l", + "default": 0 + }, + "k": { + "name": "ok", + "kind": "b", + "default": 0 + }, + "li": { + "name": "light", + "kind": "b", + "default": 0 + }, + "lk": { + "name": "linkedRemotes", + "kind": "l", + "default": 0 + }, + "lp": { + "name": "loopRate", + "kind": "i", + "default": 0 + }, + "ls": { + "name": "linkedShades", + "kind": "l", + "default": 0 + }, + "lx": { + "name": "loopMaxUs", + "kind": "i", + "default": 0 + }, + "m": { + "name": "myPos", + "kind": "i", + "default": -1 + }, + "md": { + "name": "model", + "kind": "s", + "default": 0 + }, + "mn": { + "name": "minHeap", + "kind": "i", + "default": 0 + }, + "mx": { + "name": "maxHeap", + "kind": "i", + "default": 0 + }, + "n": { + "name": "name", + "kind": "s", + "default": 0 + }, + "o": { + "name": "sortOrder", + "kind": "i", + "default": 0 + }, + "p": { + "name": "position", + "kind": "i", + "default": 0 + }, + "pr": { + "name": "proto", + "kind": "i", + "default": 0 + }, + "pu": { + "name": "pulses", + "kind": "l", + "default": 0 + }, + "q": { + "name": "tiltPosition", + "kind": "i", + "default": 0 + }, + "r": { + "name": "roomId", + "kind": "i", + "default": 0 + }, + "rp": { + "name": "repeats", + "kind": "i", + "default": 1 + }, + "rs": { + "name": "rssi", + "kind": "i", + "default": 0 + }, + "rw": { + "name": "rawCmd", + "kind": "i", + "default": 0 + }, + "s": { + "name": "shadeId", + "kind": "i", + "default": 0 + }, + "sa": { + "name": "startingAddress", + "kind": "i", + "default": 0 + }, + "sc": { + "name": "scanning", + "kind": "b", + "default": 0 + }, + "sd": { + "name": "ssid", + "kind": "s", + "default": 0 + }, + "sg": { + "name": "strength", + "kind": "i", + "default": 0 + }, + "sh": { + "name": "schemaHash", + "kind": "s", + "default": 0 + }, + "sm": { + "name": "simMy", + "kind": "b", + "default": 0 + }, + "so": { + "name": "source", + "kind": "s", + "default": 0 + }, + "sp": { + "name": "speed", + "kind": "i", + "default": 0 + }, + "sr": { + "name": "sourceAddress", + "kind": "i", + "default": 0 + }, + "ss": { + "name": "sunSensor", + "kind": "b", + "default": 0 + }, + "st": { + "name": "shadeType", + "kind": "i", + "default": 0 + }, + "sv": { + "name": "serverId", + "kind": "s", + "default": 0 + }, + "t": { + "name": "target", + "kind": "i", + "default": 0 + }, + "tf": { + "name": "testFreq", + "kind": "n", + "default": 0 + }, + "to": { + "name": "totalHeap", + "kind": "i", + "default": 0 + }, + "tr": { + "name": "testRssi", + "kind": "i", + "default": 0 + }, + "u": { + "name": "tiltTarget", + "kind": "i", + "default": 0 + }, + "up": { + "name": "uptime", + "kind": "i", + "default": 0 + }, + "va": { + "name": "valid", + "kind": "b", + "default": 0 + }, + "w": { + "name": "tiltDirection", + "kind": "i", + "default": 0 + }, + "x": { + "name": "desc", + "kind": "s", + "default": 0 + }, + "xg": { + "name": "maxGroups", + "kind": "i", + "default": 0 + }, + "xl": { + "name": "maxLinkedRemotes", + "kind": "i", + "default": 0 + }, + "xm": { + "name": "maxGroupedShades", + "kind": "i", + "default": 0 + }, + "xr": { + "name": "maxRooms", + "kind": "i", + "default": 0 + }, + "xs": { + "name": "maxShades", + "kind": "i", + "default": 0 + }, + "y": { + "name": "tiltType", + "kind": "i", + "default": 0 + }, + "z": { + "name": "stepSize", + "kind": "i", + "default": 100 + } + }, + "errors": { + "1": "badRequest", + "2": "unknownOp", + "3": "unauthorized", + "4": "notFound", + "5": "busy", + "6": "radioUnavailable", + "7": "internal" + }, + "feeds": { + "1": "frames", + "2": "frequencyScan" + } +} \ No newline at end of file diff --git a/test/fixtures/proto2/sync.json b/test/fixtures/proto2/sync.json new file mode 100644 index 0000000..64cb1a6 --- /dev/null +++ b/test/fixtures/proto2/sync.json @@ -0,0 +1,1041 @@ +[ + { + "v": 2, + "t": 4, + "d": { + "T": [ + [ + 1, + "hello" + ], + [ + 2, + "reply" + ], + [ + 3, + "next" + ], + [ + 4, + "sync" + ], + [ + 5, + "syncEnd" + ], + [ + 6, + "subscribe" + ], + [ + 7, + "unsubscribe" + ], + [ + 128, + "shadeState" + ], + [ + 129, + "shade" + ], + [ + 130, + "shadeRemoved" + ], + [ + 131, + "group" + ], + [ + 132, + "groupState" + ], + [ + 133, + "groupRemoved" + ], + [ + 134, + "room" + ], + [ + 135, + "roomRemoved" + ], + [ + 136, + "roomOrder" + ], + [ + 137, + "shadeOrder" + ], + [ + 138, + "groupOrder" + ], + [ + 139, + "shadeCommand" + ], + [ + 140, + "memory" + ], + [ + 141, + "wifi" + ], + [ + 142, + "ethernet" + ], + [ + 143, + "frame" + ], + [ + 144, + "frequencyScan" + ], + [ + 145, + "repeaters" + ] + ], + "F": [ + [ + "T", + "types", + "l", + 0 + ], + [ + "F", + "fields", + "l", + 0 + ], + [ + "E", + "errors", + "l", + 0 + ], + [ + "Q", + "feeds", + "l", + 0 + ], + [ + "h", + "header", + "o", + 0 + ], + [ + "R", + "rooms", + "l", + 0 + ], + [ + "S", + "shades", + "l", + 0 + ], + [ + "G", + "groups", + "l", + 0 + ], + [ + "X", + "repeaters", + "l", + 0 + ], + [ + "Z", + "status", + "o", + 0 + ], + [ + "fw", + "fw", + "s", + 0 + ], + [ + "sh", + "schemaHash", + "s", + 0 + ], + [ + "k", + "ok", + "b", + 0 + ], + [ + "e", + "code", + "i", + 0 + ], + [ + "x", + "desc", + "s", + 0 + ], + [ + "fe", + "feed", + "i", + 0 + ], + [ + "is", + "ids", + "l", + 0 + ], + [ + "s", + "shadeId", + "i", + 0 + ], + [ + "g", + "groupId", + "i", + 0 + ], + [ + "r", + "roomId", + "i", + 0 + ], + [ + "n", + "name", + "s", + 0 + ], + [ + "a", + "remoteAddress", + "i", + 0 + ], + [ + "c", + "lastRollingCode", + "i", + 0 + ], + [ + "o", + "sortOrder", + "i", + 0 + ], + [ + "f", + "flags", + "i", + 0 + ], + [ + "p", + "position", + "i", + 0 + ], + [ + "t", + "target", + "i", + 0 + ], + [ + "d", + "direction", + "i", + 0 + ], + [ + "m", + "myPos", + "i", + -1 + ], + [ + "y", + "tiltType", + "i", + 0 + ], + [ + "q", + "tiltPosition", + "i", + 0 + ], + [ + "u", + "tiltTarget", + "i", + 0 + ], + [ + "w", + "tiltDirection", + "i", + 0 + ], + [ + "M", + "myTiltPos", + "i", + -1 + ], + [ + "U", + "upTime", + "i", + 0 + ], + [ + "D", + "downTime", + "i", + 0 + ], + [ + "Y", + "tiltTime", + "i", + 0 + ], + [ + "z", + "stepSize", + "i", + 100 + ], + [ + "i", + "paired", + "b", + 0 + ], + [ + "st", + "shadeType", + "i", + 0 + ], + [ + "bl", + "bitLength", + "i", + 56 + ], + [ + "pr", + "proto", + "i", + 0 + ], + [ + "fc", + "flipCommands", + "b", + 0 + ], + [ + "fp", + "flipPosition", + "b", + 0 + ], + [ + "ig", + "inGroup", + "b", + 0 + ], + [ + "ss", + "sunSensor", + "b", + 0 + ], + [ + "li", + "light", + "b", + 0 + ], + [ + "sm", + "simMy", + "b", + 0 + ], + [ + "rp", + "repeats", + "i", + 1 + ], + [ + "gu", + "gpioUp", + "i", + 0 + ], + [ + "gd", + "gpioDown", + "i", + 0 + ], + [ + "gm", + "gpioMy", + "i", + 0 + ], + [ + "gl", + "gpioLLTrigger", + "b", + 0 + ], + [ + "lk", + "linkedRemotes", + "l", + 0 + ], + [ + "ls", + "linkedShades", + "l", + 0 + ], + [ + "cm", + "command", + "s", + 0 + ], + [ + "so", + "source", + "s", + 0 + ], + [ + "sr", + "sourceAddress", + "i", + 0 + ], + [ + "xr", + "maxRooms", + "i", + 0 + ], + [ + "xs", + "maxShades", + "i", + 0 + ], + [ + "xg", + "maxGroups", + "i", + 0 + ], + [ + "xm", + "maxGroupedShades", + "i", + 0 + ], + [ + "xl", + "maxLinkedRemotes", + "i", + 0 + ], + [ + "sa", + "startingAddress", + "i", + 0 + ], + [ + "sv", + "serverId", + "s", + 0 + ], + [ + "hn", + "hostname", + "s", + 0 + ], + [ + "md", + "model", + "s", + 0 + ], + [ + "cp", + "chipModel", + "s", + 0 + ], + [ + "ct", + "connType", + "s", + 0 + ], + [ + "fr", + "freeHeap", + "i", + 0 + ], + [ + "mx", + "maxHeap", + "i", + 0 + ], + [ + "mn", + "minHeap", + "i", + 0 + ], + [ + "to", + "totalHeap", + "i", + 0 + ], + [ + "up", + "uptime", + "i", + 0 + ], + [ + "lp", + "loopRate", + "i", + 0 + ], + [ + "lx", + "loopMaxUs", + "i", + 0 + ], + [ + "sd", + "ssid", + "s", + 0 + ], + [ + "sg", + "strength", + "i", + 0 + ], + [ + "ch", + "channel", + "i", + 0 + ], + [ + "co", + "connected", + "b", + 0 + ], + [ + "sp", + "speed", + "i", + 0 + ], + [ + "fd", + "fullDuplex", + "b", + 0 + ], + [ + "ek", + "encKey", + "i", + 0 + ], + [ + "rw", + "rawCmd", + "i", + 0 + ], + [ + "rs", + "rssi", + "i", + 0 + ], + [ + "bt", + "bits", + "i", + 0 + ], + [ + "va", + "valid", + "b", + 0 + ], + [ + "hs", + "hwsync", + "i", + 0 + ], + [ + "pu", + "pulses", + "l", + 0 + ], + [ + "sc", + "scanning", + "b", + 0 + ], + [ + "tf", + "testFreq", + "n", + 0 + ], + [ + "tr", + "testRssi", + "i", + 0 + ], + [ + "fq", + "frequency", + "n", + 0 + ] + ], + "E": [ + [ + 1, + "badRequest" + ], + [ + 2, + "unknownOp" + ], + [ + 3, + "unauthorized" + ], + [ + 4, + "notFound" + ], + [ + 5, + "busy" + ], + [ + 6, + "radioUnavailable" + ], + [ + 7, + "internal" + ] + ], + "Q": [ + [ + 1, + "frames" + ], + [ + 2, + "frequencyScan" + ] + ], + "h": { + "xr": 16, + "xs": 32, + "xg": 16, + "xm": 32, + "xl": 7, + "sa": 1048577, + "bl": 80, + "pr": 1, + "sv": "serverId 2", + "hn": "hostname 3", + "md": "ESPSomfyRTS", + "fw": "v3.0.14", + "ct": "Wifi" + }, + "S": [ + { + "s": 1, + "c": 1048580, + "n": "name 5", + "a": 1048582, + "U": 18000, + "D": 19000, + "Y": 7000, + "i": 1, + "bl": 80, + "pr": 1, + "fc": 1, + "ig": 1, + "o": 1, + "lk": [ + { + "a": 1048583, + "c": 1048584 + }, + { + "a": 1048585, + "c": 1048586 + } + ] + }, + { + "s": 2, + "c": 1048587, + "n": "name 12", + "a": 1048589, + "U": 18000, + "D": 19000, + "Y": 7000, + "i": 1, + "bl": 80, + "pr": 1, + "fc": 1, + "ig": 1, + "o": 2, + "lk": [ + { + "a": 1048590, + "c": 1048591 + }, + { + "a": 1048592, + "c": 1048593 + } + ] + }, + { + "s": 3, + "c": 1048594, + "n": "name 19", + "a": 1048596, + "U": 18000, + "D": 19000, + "Y": 7000, + "i": 1, + "bl": 80, + "pr": 1, + "fc": 1, + "ig": 1, + "o": 3, + "lk": [ + { + "a": 1048597, + "c": 1048598 + }, + { + "a": 1048599, + "c": 1048600 + } + ] + }, + { + "s": 4, + "c": 1048601, + "n": "name 26", + "a": 1048603, + "U": 18000, + "D": 19000, + "Y": 7000, + "i": 1, + "bl": 80, + "pr": 1, + "fc": 1, + "ig": 1, + "o": 4, + "lk": [ + { + "a": 1048604, + "c": 1048605 + }, + { + "a": 1048606, + "c": 1048607 + } + ] + }, + { + "s": 5, + "c": 1048608, + "n": "name 33", + "a": 1048610, + "U": 18000, + "D": 19000, + "Y": 7000, + "i": 1, + "bl": 80, + "pr": 1, + "fc": 1, + "ig": 1, + "o": 5, + "lk": [ + { + "a": 1048611, + "c": 1048612 + }, + { + "a": 1048613, + "c": 1048614 + } + ] + }, + { + "s": 6, + "c": 1048615, + "n": "name 40", + "a": 1048617, + "U": 25000, + "D": 25000, + "Y": 7000, + "i": 1, + "bl": 80, + "pr": 1, + "fc": 1, + "ig": 1, + "o": 6, + "lk": [ + { + "a": 1048618, + "c": 1048619 + }, + { + "a": 1048620, + "c": 1048621 + } + ] + }, + { + "s": 7, + "c": 1048622, + "n": "name 47", + "a": 1048624, + "U": 25000, + "D": 25000, + "Y": 7000, + "i": 1, + "bl": 80, + "pr": 1, + "fc": 1, + "ig": 1, + "o": 7, + "lk": [ + { + "a": 1048625, + "c": 1048626 + }, + { + "a": 1048627, + "c": 1048628 + } + ] + } + ] + } + }, + { + "v": 2, + "t": 4, + "d": { + "S": [ + { + "s": 8, + "c": 1048629, + "n": "name 54", + "a": 1048631, + "U": 18000, + "D": 19000, + "Y": 7000, + "i": 1, + "bl": 80, + "pr": 1, + "fc": 1, + "ig": 1, + "o": 8, + "lk": [ + { + "a": 1048632, + "c": 1048633 + }, + { + "a": 1048634, + "c": 1048635 + } + ] + }, + { + "s": 9, + "c": 1048636, + "n": "name 61", + "a": 1048638, + "U": 18000, + "D": 19000, + "Y": 7000, + "i": 1, + "bl": 80, + "pr": 1, + "fc": 1, + "ig": 1, + "o": 9, + "lk": [ + { + "a": 1048639, + "c": 1048640 + }, + { + "a": 1048641, + "c": 1048642 + } + ] + }, + { + "s": 10, + "c": 1048643, + "n": "name 68", + "a": 1048645, + "U": 18000, + "D": 19000, + "Y": 7000, + "i": 1, + "bl": 80, + "pr": 1, + "fc": 1, + "ig": 1, + "o": 10, + "lk": [ + { + "a": 1048646, + "c": 1048647 + }, + { + "a": 1048648, + "c": 1048649 + } + ] + } + ], + "G": [ + { + "g": 1, + "n": "name 74", + "a": 1048651, + "c": 1048652, + "bl": 80, + "pr": 1, + "fc": 1, + "o": 1, + "ls": [ + 5, + 7, + 6, + 8, + 9, + 10, + 4, + 1, + 2, + 3 + ] + }, + { + "g": 2, + "n": "name 77", + "a": 1048654, + "c": 1048655, + "bl": 80, + "pr": 1, + "fc": 1, + "o": 2, + "ls": [ + 7, + 6, + 5 + ] + } + ], + "Z": { + "fr": 94956, + "mx": 53236, + "mn": 45904, + "to": 266884, + "up": 672549, + "lp": 998, + "lx": 13409, + "sd": "ssid 80", + "sg": -68, + "ch": 6 + } + } + }, + { + "v": 2, + "t": 5, + "d": {} + } +] \ No newline at end of file