Skip to content

Commit f5264d3

Browse files
committed
Clean up PAC/Stanley CLI: remove debug command, accept ASCII IDs, handle unknown tag types gracefully
- Remove lf pac debug command (development-only) - Accept both 16-hex and 8-ASCII card ID formats with 7-bit validation - Add T55xx write command under lf pac write - Handle unknown TagSpecificType values in slot list without crashing - Auto-initialize slot data when setting tag type - Simplify pac_write_to_t55xx by removing unused key parameters
1 parent 64986b4 commit f5264d3

File tree

2 files changed

+52
-105
lines changed

2 files changed

+52
-105
lines changed

software/script/chameleon_cli_unit.py

Lines changed: 51 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -3959,108 +3959,29 @@ def on_exec(self, args: argparse.Namespace):
39593959
print(f" PAC/Stanley Card ID: {color_string((CG, card_id_ascii))} ({card_id.hex().upper()})")
39603960

39613961

3962-
@lf_pac.command('debug')
3963-
class LFPacDebug(ReaderRequiredUnit):
3964-
def args_parser(self) -> ArgumentParserNoExit:
3965-
parser = ArgumentParserNoExit()
3966-
parser.description = 'Capture raw ADC data and analyze NRZ signal for PAC debugging'
3967-
return parser
3968-
3969-
def on_exec(self, args: argparse.Namespace):
3970-
resp = self.cmd.adc_generic_read()
3971-
if resp is None:
3972-
print("ADC read failed")
3973-
return
3974-
3975-
data = list(resp)
3976-
min_val = min(data)
3977-
max_val = max(data)
3978-
3979-
print(f" Samples: {len(data)}")
3980-
print(f" ADC range: {min_val} - {max_val} (spread: {max_val - min_val})")
3981-
3982-
# Spike removal: clip values above 3x the minimum (LC ringing transients)
3983-
spike_cap = min_val * 3 if min_val > 0 else max_val
3984-
clipped = [min(v, spike_cap) for v in data]
3985-
c_min = min(clipped)
3986-
c_max = max(clipped)
3987-
c_thresh = (c_min + c_max) / 2
3988-
3989-
print(f" Spike cap: {spike_cap} (3x raw min {min_val})")
3990-
print(f" Clipped range: {c_min} - {c_max} (spread: {c_max - c_min})")
3991-
3992-
# Moving-average of clipped signal (32-sample window = 1 NRZ bit)
3993-
window = 32
3994-
if len(clipped) >= window:
3995-
filtered = []
3996-
s = sum(clipped[:window])
3997-
for i in range(window, len(clipped)):
3998-
filtered.append(s / window)
3999-
s += clipped[i] - clipped[i - window]
4000-
filtered.append(s / window)
4001-
f_min = min(filtered)
4002-
f_max = max(filtered)
4003-
f_thresh = (f_min + f_max) / 2
4004-
f_hyst = (f_max - f_min) / 4
4005-
print(f"\n Spike-free moving average (window={window}):")
4006-
print(f" Range: {f_min:.1f} - {f_max:.1f} (spread: {f_max - f_min:.1f})")
4007-
print(f" Threshold: {f_thresh:.1f}, Hysteresis: {f_hyst:.1f}")
4008-
4009-
# Binary decode with hysteresis
4010-
f_binary = []
4011-
state = filtered[0] > f_thresh
4012-
for v in filtered:
4013-
if v > f_thresh + f_hyst:
4014-
state = True
4015-
elif v < f_thresh - f_hyst:
4016-
state = False
4017-
f_binary.append(1 if state else 0)
4018-
4019-
f_trans = sum(1 for i in range(1, len(f_binary)) if f_binary[i] != f_binary[i - 1])
4020-
f_runs = []
4021-
cur = f_binary[0]
4022-
cnt = 1
4023-
for i in range(1, len(f_binary)):
4024-
if f_binary[i] == cur:
4025-
cnt += 1
4026-
else:
4027-
f_runs.append(cnt)
4028-
cur = f_binary[i]
4029-
cnt = 1
4030-
f_runs.append(cnt)
4031-
print(f" Transitions: {f_trans}")
4032-
print(f" Run lengths: {f_runs[:40]}{'...' if len(f_runs) > 40 else ''}")
4033-
run_bits = [f"{r / 32:.1f}" for r in f_runs[:40]]
4034-
print(f" Run bits: {run_bits}")
4035-
4036-
# Show bit-period averages (each = avg of 32 clipped samples)
4037-
print(f"\n Bit-period averages (32 samples each):")
4038-
bit_avgs = []
4039-
for i in range(0, len(clipped) - window + 1, window):
4040-
chunk = clipped[i:i + window]
4041-
bit_avgs.append(sum(chunk) / len(chunk))
4042-
bit_str = " ".join(f"{a:.0f}" for a in bit_avgs)
4043-
print(f" {bit_str}")
4044-
4045-
# Raw hex dump (first 512 bytes)
4046-
print(f"\n Raw ADC (8-bit, min={min_val} max={max_val}):")
4047-
for i in range(0, min(len(data), 512), 50):
4048-
chunk = data[i:i + 50]
4049-
hexpart = " ".join(f"{b:02x}" for b in chunk)
4050-
print(f" {i:04x} {hexpart}")
4051-
4052-
40533962
class LFPacIdArgsUnit(DeviceRequiredUnit):
40543963
@staticmethod
40553964
def add_card_arg(parser: ArgumentParserNoExit, required=False):
4056-
parser.add_argument("--id", type=str, required=required, help="PAC/Stanley card ID (8 ASCII hex chars)", metavar="<hex>")
3965+
parser.add_argument("--id", type=str, required=required,
3966+
help="PAC/Stanley card ID: 16 hex chars (e.g. 4141414141414141) or 8 ASCII chars (e.g. AAAAAAAA)",
3967+
metavar="<hex|ascii>")
40573968
return parser
40583969

40593970
def before_exec(self, args: argparse.Namespace):
40603971
if not super().before_exec(args):
40613972
return False
4062-
if args.id is not None and not re.match(r"^[a-fA-F0-9]{16}$", args.id):
4063-
raise ArgsParserError("ID must include 16 HEX symbols (8 bytes)")
3973+
if args.id is not None:
3974+
if re.match(r"^[a-fA-F0-9]{16}$", args.id):
3975+
pass # already hex
3976+
elif len(args.id) == 8:
3977+
# treat as 8 ASCII characters, convert to hex
3978+
args.id = args.id.encode('ascii').hex()
3979+
else:
3980+
raise ArgsParserError("ID must be 16 hex chars (8 bytes) or 8 ASCII characters")
3981+
# PAC uses 7-bit UART frames; MSB of each byte is not encoded
3982+
id_bytes = bytes.fromhex(args.id)
3983+
if any(b > 0x7F for b in id_bytes):
3984+
raise ArgsParserError("PAC card IDs are 7-bit ASCII only (each byte must be 0x00-0x7F)")
40643985
return True
40653986

40663987
def args_parser(self) -> ArgumentParserNoExit:
@@ -4069,6 +3990,19 @@ def args_parser(self) -> ArgumentParserNoExit:
40693990
def on_exec(self, args: argparse.Namespace):
40703991
raise NotImplementedError("Please implement this")
40713992

3993+
@lf_pac.command('write')
3994+
class LFPacWriteT55xx(LFPacIdArgsUnit, ReaderRequiredUnit):
3995+
def args_parser(self) -> ArgumentParserNoExit:
3996+
parser = ArgumentParserNoExit()
3997+
parser.description = 'Write PAC/Stanley id to T55xx'
3998+
return self.add_card_arg(parser, required=True)
3999+
4000+
def on_exec(self, args: argparse.Namespace):
4001+
id_bytes = bytes.fromhex(args.id)
4002+
self.cmd.pac_write_to_t55xx(id_bytes)
4003+
id_ascii = ''.join(chr(b) if 0x20 <= b < 0x7f else '.' for b in id_bytes)
4004+
print(f" - PAC/Stanley ID write done: {id_ascii} ({args.id.upper()})")
4005+
40724006
@lf_pac.command('econfig')
40734007
class LFPacEconfig(SlotIndexArgsAndGoUnit, LFPacIdArgsUnit):
40744008
def args_parser(self) -> ArgumentParserNoExit:
@@ -4182,8 +4116,20 @@ def on_exec(self, args: argparse.Namespace):
41824116
for slot in SlotNumber:
41834117
fwslot = SlotNumber.to_fw(slot)
41844118
status = f"({color_string((CG, 'active'))})" if slot == selected else ""
4185-
hf_tag_type = TagSpecificType(slotinfo[fwslot]['hf'])
4186-
lf_tag_type = TagSpecificType(slotinfo[fwslot]['lf'])
4119+
try:
4120+
hf_tag_type = TagSpecificType(slotinfo[fwslot]['hf'])
4121+
except ValueError:
4122+
hf_tag_type = TagSpecificType.UNDEFINED
4123+
hf_unknown = slotinfo[fwslot]['hf']
4124+
else:
4125+
hf_unknown = None
4126+
try:
4127+
lf_tag_type = TagSpecificType(slotinfo[fwslot]['lf'])
4128+
except ValueError:
4129+
lf_tag_type = TagSpecificType.UNDEFINED
4130+
lf_unknown = slotinfo[fwslot]['lf']
4131+
else:
4132+
lf_unknown = None
41874133
print(f' - {f"Slot {slot}:":{4+maxnamelength+1}} {status}')
41884134

41894135
# HF
@@ -4192,12 +4138,14 @@ def on_exec(self, args: argparse.Namespace):
41924138
print(f' HF: '
41934139
f'{slotnames[fwslot]["hf"]["name"]:{field_length}}', end='')
41944140
print(status, end='')
4195-
if hf_tag_type != TagSpecificType.UNDEFINED:
4141+
if hf_unknown is not None:
4142+
print(color_string((CR, f"Unknown ({hf_unknown})")))
4143+
elif hf_tag_type != TagSpecificType.UNDEFINED:
41964144
color = CY if enabled[fwslot]['hf'] else C0
41974145
print(color_string((color, hf_tag_type)))
41984146
else:
41994147
print("undef")
4200-
if (not args.short) and enabled[fwslot]['hf'] and hf_tag_type != TagSpecificType.UNDEFINED:
4148+
if (not args.short) and enabled[fwslot]['hf'] and hf_tag_type != TagSpecificType.UNDEFINED and hf_unknown is None:
42014149
if current != slot:
42024150
self.cmd.set_active_slot(slot)
42034151
current = slot
@@ -4247,12 +4195,14 @@ def on_exec(self, args: argparse.Namespace):
42474195
print(f' LF: '
42484196
f'{slotnames[fwslot]["lf"]["name"]:{field_length}}', end='')
42494197
print(status, end='')
4250-
if lf_tag_type != TagSpecificType.UNDEFINED:
4198+
if lf_unknown is not None:
4199+
print(color_string((CR, f"Unknown ({lf_unknown})")))
4200+
elif lf_tag_type != TagSpecificType.UNDEFINED:
42514201
color = CY if enabled[fwslot]['lf'] else C0
42524202
print(color_string((color, lf_tag_type)))
42534203
else:
42544204
print("undef")
4255-
if (not args.short) and enabled[fwslot]['lf'] and lf_tag_type != TagSpecificType.UNDEFINED:
4205+
if (not args.short) and enabled[fwslot]['lf'] and lf_tag_type != TagSpecificType.UNDEFINED and lf_unknown is None:
42564206
if current != slot:
42574207
self.cmd.set_active_slot(slot)
42584208
current = slot
@@ -4310,6 +4260,7 @@ def on_exec(self, args: argparse.Namespace):
43104260
else:
43114261
slot_num = SlotNumber.from_fw(self.cmd.get_active_slot())
43124262
self.cmd.set_slot_tag_type(slot_num, tag_type)
4263+
self.cmd.set_slot_data_default(slot_num, tag_type)
43134264
print(f' - Set slot {slot_num} tag type success.')
43144265

43154266

software/script/chameleon_cmd.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -547,17 +547,13 @@ def pac_scan(self):
547547
return resp
548548

549549
@expect_response(Status.LF_TAG_OK)
550-
def pac_write_to_t55xx(self, id_bytes: bytes, new_key: bytes = b'\x00\x00\x00\x00', old_keys: list = None):
550+
def pac_write_to_t55xx(self, id_bytes: bytes):
551551
"""
552552
Write PAC/Stanley card data to a T55XX tag.
553553
554554
:param id_bytes: 8-byte ASCII card ID
555-
:param new_key: new password (4 bytes)
556-
:param old_keys: list of old passwords to try (each 4 bytes)
557555
:return:
558556
"""
559-
if old_keys is None:
560-
old_keys = [b'\x00\x00\x00\x00']
561557
if len(id_bytes) != 8:
562558
raise ValueError("The id bytes length must equal 8")
563559
data = struct.pack(f'!8s4s{4*len(old_keys)}s', id_bytes, new_key, b''.join(old_keys))

0 commit comments

Comments
 (0)