Files
qianbian/tests/test_protocol.py
T

157 lines
6.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""协议编解码测试 — 测试向量来自 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_rxtx_channel_by_firmware_family(self):
assert P.select_rxtx_uuid("HM42_AIO_V1.0.9") == P.RXTX_CHAR_UUID_LEGACY
assert P.select_rxtx_uuid("hm42_aio_v2") == P.RXTX_CHAR_UUID_LEGACY
assert P.select_rxtx_uuid("PP_da14585_4.2") == P.RXTX_CHAR_UUID
assert P.select_rxtx_uuid(None) == P.RXTX_CHAR_UUID
def test_complete_image_planes_by_firmware_family(self):
assert P.requires_complete_image_planes("HM42_AIO_V1.0.9") is True
assert P.requires_complete_image_planes("hm42_aio_v2") is True
assert P.requires_complete_image_planes("PP_da14585_4.2") is False
assert P.requires_complete_image_planes(None) is False
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"
)