"""协议编解码测试 — 测试向量来自 docs/protocol.md §8(证据:analysis/web/protocol-raw.md)。""" import datetime import pytest from ppclock import protocol as P class TestImageBlocks: def test_first_block_header_bw_full(self): """400x300 bw 平面首块 → 03 ff 0000 + 240B(向量#2)""" plane = bytes(15000) blocks = list(P.image_blocks(plane, small=False, red=False)) assert blocks[0][:4] == bytes([0x03, 0xFF, 0x00, 0x00]) assert len(blocks[0]) == 244 def test_block_size_and_count(self): """15000B / 240B = 62.5 → 63 块;末块 120B+4""" blocks = list(P.image_blocks(bytes(15000), small=False, red=False)) assert len(blocks) == 63 assert len(blocks[-1]) == 4 + 120 def test_offsets_big_endian_sequential(self): blocks = list(P.image_blocks(bytes(15000), small=False, red=False)) assert blocks[1][2:4] == (240).to_bytes(2, "big") assert blocks[2][2:4] == (480).to_bytes(2, "big") def test_red_plane_code_00(self): blocks = list(P.image_blocks(bytes(15000), small=False, red=True)) assert blocks[0][1] == 0x00 def test_small_image_cod_04(self): """3500B/9000B → cod=04(向量#3)""" assert list(P.image_blocks(bytes(3500), small=True, red=False))[0][0] == 0x04 assert list(P.image_blocks(bytes(9000), small=True, red=False))[0][0] == 0x04 def test_full_image_cod_03(self): assert list(P.image_blocks(bytes(15000), small=False, red=False))[0][0] == 0x03 def test_end_frames(self): assert P.end_frame(small=False) == bytes([0x01]) assert P.end_frame(small=True) == bytes([0xAA]) class TestSetTime: def test_layout(self): """dd + unix(4B BE) + year(2B BE) + month + day + week(向量#1) 2026-07-24 15:30:00 本地(UTC+8) 周五 → unix 字段=本地墙钟按UTC计""" dt = datetime.datetime(2026, 7, 24, 15, 30, 0) frame = P.build_set_time(dt) assert frame[0] == 0xDD assert len(frame) == 10 expected_unix = int( dt.replace(tzinfo=datetime.timezone.utc).timestamp() ) # 本地墙钟按 UTC 计 assert frame[1:5] == expected_unix.to_bytes(4, "big") assert frame[5:7] == (2026).to_bytes(2, "big") assert frame[7] == 7 assert frame[8] == 24 assert frame[9] == 5 # 周五,周一=1 class TestCommandFrames: def test_parking(self): """停车牌 "12" → EF1122(向量#4)""" assert P.parking_frame("12") == bytes([0xEF, 0x11, 0x22]) assert P.parking_frame("13800138000") == bytes( [0xEF] + [int(f"{d}{d}", 16) for d in "13800138000"] ) def test_rotation_interval(self): """轮播 5 分钟 → e9010501;5 小时 → e9010500(向量#5)""" assert P.rotation_interval_frame(5, "min") == bytes.fromhex("e9010501") assert P.rotation_interval_frame(5, "hour") == bytes.fromhex("e9010500") def test_slot_select(self): """槽位3(索引2) → EA02(向量#6)""" assert P.slot_select_frame(2) == bytes([0xEA, 0x02]) with pytest.raises(ValueError): P.slot_select_frame(4) def test_rotation_count(self): """轮播数量4 → EA0003(向量#6)""" assert P.rotation_count_frame(4) == bytes.fromhex("EA0003") assert P.rotation_count_frame(1) == bytes.fromhex("EA0000") with pytest.raises(ValueError): P.rotation_count_frame(5) def test_countdown_date_clock(self): """时钟倒计时 2026-12-31 → ff00 + '20''26' BCD + 12 + 31""" frame = P.countdown_date_frame(datetime.date(2026, 12, 31), "clock") assert frame == bytes([0xFF, 0x00, 0x20, 0x26, 0x12, 0x31]) def test_countdown_date_calendar(self): frame = P.countdown_date_frame(datetime.date(2026, 1, 5), "calendar") assert frame == bytes([0xFF, 0xFF, 0x20, 0x26, 0x01, 0x05]) def test_countdown_off(self): assert P.countdown_off_frames() == [bytes([0xFA]), bytes([0xE2])] def test_activation(self): code = "A1B2" * 8 # 32 hex assert P.activation_frame(code) == bytes([0xEF]) + bytes.fromhex(code) def test_request_device_id(self): """EF 前缀区分:EFEF=取ID(向量#7)""" assert P.request_id_frame() == bytes([0xEF, 0xEF]) def test_lut(self): assert P.lut_frame(0x05) == bytes([0xE6, 0x05]) with pytest.raises(ValueError): P.lut_frame(0x00) def test_sleep(self): assert P.sleep_frame(True, 23, 7) == bytes([0xFB, 0x01, 0x23, 0x07]) assert P.sleep_frame(False, 23, 7) == bytes([0xFB, 0x00, 0x23, 0x07]) def test_modes(self): assert P.mode_frame("clock1") == bytes.fromhex("e10201") assert P.mode_frame("clock3") == bytes.fromhex("e10203") assert P.mode_frame("calendar1") == bytes.fromhex("e10103") assert P.mode_frame("calendar3") == bytes.fromhex("e10105") assert P.mode_frame("image0") == bytes.fromhex("e10000") assert P.mode_frame("image3") == bytes.fromhex("e10003") assert P.mode_frame("tricolor") == bytes.fromhex("2300") assert P.mode_frame("mono") == bytes.fromhex("2301") with pytest.raises(ValueError): P.mode_frame("nope") def test_toggles(self): assert P.simple_command("hour_format") == bytes([0x14]) assert P.simple_command("clock_color") == bytes([0x15]) assert P.simple_command("invert") == bytes([0xE3]) assert P.simple_command("font") == bytes([0xE4]) assert P.simple_command("rotate180") == bytes([0xE5]) assert P.simple_command("commit") == bytes([0xE2]) def test_clear_screen(self): assert P.clear_frames(0x00) == [bytes([0x00, 0x00]), bytes([0x01])] def test_wifi(self): assert P.wifi_frame("MyAP", "pw123", "101010100") == ( b"WIFI_SSID:MyAP;WIFI_PASS:pw123;CITY:101010100" )