145 lines
5.9 KiB
Python
145 lines
5.9 KiB
Python
"""协议编解码测试 — 测试向量来自 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"
|
||
)
|