feat(lf): AWID FSK decode (parity-validated Wiegand)
protocols.decode_awid: FSK2a (no Manchester) via the ported detectAWID path, then removeParity — the 88 bits after the preamble are 22 groups of 3 data + 1 odd-parity bit, so stripping parity gives 66 bits whose first byte is the format length (26/34/36/37/50) and the rest the facility/card fields (cmdlfawid.c index map). The 22 parity checks stand in for AWID's missing CRC, so it can't false-positive. Added to the identify decoder chain. Tests: AWID-26 round-trip across fc/card, parity gate rejects noise/EM4100. Full suite green. LF stack now covers EM4100, HID, AWID, FDX-B + T55xx config. Indala (PSK) queued — the one path needing real PSK phase-tracking (DetectPSKClock/pskRawDemod), best tuned against a hardware capture. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -164,6 +164,47 @@ class TestHID:
|
||||
assert r["protocol"] == "HID Prox" and r["card_number"] == 9999
|
||||
|
||||
|
||||
# --- AWID (FSK, no Manchester) ----------------------------------------------------------------
|
||||
|
||||
def _awid_env(fc, cn, fmt_len=26, repeats=4, **kw):
|
||||
"""A faithful AWID envelope: 66-bit Wiegand (fmtLen + fc + card) re-parity'd into 88 bits
|
||||
(3 data + 1 odd parity per group), preambled, FSK2a-modulated (one symbol per bit)."""
|
||||
(fc_off, fc_w), (cn_off, cn_w) = protocols._AWID_FORMATS[fmt_len]
|
||||
data = [0] * 66
|
||||
for k in range(8):
|
||||
data[k] = (fmt_len >> (7 - k)) & 1
|
||||
for k in range(fc_w):
|
||||
data[fc_off + k] = (fc >> (fc_w - 1 - k)) & 1
|
||||
for k in range(cn_w):
|
||||
data[cn_off + k] = (cn >> (cn_w - 1 - k)) & 1
|
||||
parity88 = [] # re-insert odd parity every 3 bits
|
||||
for i in range(0, 66, 3):
|
||||
g = data[i:i + 3]
|
||||
parity88 += g + [1 - (sum(g) & 1)] # odd parity of the 3 data bits
|
||||
frame = fsk.AWID_PREAMBLE + parity88 # 8 + 88 = 96 bits
|
||||
return _fsk_env(frame, repeats=repeats, **kw)
|
||||
|
||||
|
||||
class TestAWID:
|
||||
def test_awid26_roundtrip(self):
|
||||
r = protocols.decode_awid(_awid_env(12, 34567))
|
||||
assert r and r["protocol"] == "AWID" and r["format"] == "AWID-26"
|
||||
assert r["facility_code"] == 12 and r["card_number"] == 34567
|
||||
|
||||
def test_awid_various(self):
|
||||
for fc, cn in ((0, 1), (255, 65535), (200, 9999)):
|
||||
r = protocols.decode_awid(_awid_env(fc, cn))
|
||||
assert r and r["facility_code"] == fc and r["card_number"] == cn, f"{fc}/{cn}"
|
||||
|
||||
def test_parity_gate_rejects(self):
|
||||
assert protocols.decode_awid(bytes([128] * 6000)) is None
|
||||
assert protocols.decode_awid(_em4100_env(0x0102030405)) is None
|
||||
|
||||
def test_demod_samples_picks_awid(self):
|
||||
r = demod_samples(_awid_env(7, 4242))
|
||||
assert r["protocol"] == "AWID" and r["card_number"] == 4242
|
||||
|
||||
|
||||
# --- FDX-B (biphase) --------------------------------------------------------------------------
|
||||
|
||||
def _fdxb_bits(country, national, animal=0):
|
||||
|
||||
Reference in New Issue
Block a user