feat(pm3py): validate PWM LED capability using device capabilities, raise on non-PWM LEDs
This commit is contained in:
56
pm3py/hw.py
56
pm3py/hw.py
@@ -8,8 +8,15 @@ from .transport import PM3Transport, PM3Response, PM3Error, ProgressCallback
|
||||
class HWCommands:
|
||||
"""Proxmark3 hardware commands."""
|
||||
|
||||
# PWM-capable LEDs by platform (bitmask)
|
||||
# PM3 Easy: A (green) and B (red) support PWM
|
||||
# RDV4: A (orange) and D (red2) support PWM
|
||||
_PWM_LEDS_EASY = 0x03 # A=0x01 | B=0x02
|
||||
_PWM_LEDS_RDV4 = 0x09 # A=0x01 | D=0x08
|
||||
|
||||
def __init__(self, transport: PM3Transport):
|
||||
self._t = transport
|
||||
self._is_rdv4: bool | None = None # cached after first capabilities() call
|
||||
|
||||
async def ping(self, length: int = 32) -> dict:
|
||||
"""Send ping and verify echo. Returns {success, length, data}."""
|
||||
@@ -97,6 +104,10 @@ class HWCommands:
|
||||
|
||||
ACTION_NAMES = {0: "off", 1: "on", 2: "toggle", 3: "pwm", 4: "pulse", 5: "fade", 6: "blink"}
|
||||
|
||||
# Validate PWM capability for PWM actions (brightness, pulse, fade)
|
||||
if action_code in (3, 4, 5):
|
||||
await self._check_pwm_capable(led_mask, ACTION_NAMES[action_code])
|
||||
|
||||
payload = struct.pack("<BBBHH", led_mask, action_code, brightness_val, speed, count)
|
||||
await self._t.send_ng(Cmd.LED_CONTROL, payload)
|
||||
return {
|
||||
@@ -123,6 +134,27 @@ class HWCommands:
|
||||
raise ValueError(f"Unknown LED: {token!r}. Use: {', '.join(LED_MAP.keys())}")
|
||||
return mask
|
||||
|
||||
async def _check_pwm_capable(self, led_mask: int, action: str) -> None:
|
||||
"""Raise if any LEDs in mask don't support PWM."""
|
||||
# Fetch capabilities if not cached
|
||||
if self._is_rdv4 is None:
|
||||
try:
|
||||
await self.capabilities()
|
||||
except PM3Error:
|
||||
return # can't validate, let it through
|
||||
|
||||
pwm_mask = self._PWM_LEDS_RDV4 if self._is_rdv4 else self._PWM_LEDS_EASY
|
||||
non_pwm = led_mask & ~pwm_mask
|
||||
if non_pwm:
|
||||
platform = "RDV4" if self._is_rdv4 else "PM3 Easy"
|
||||
LED_NAMES = {0x01: "A", 0x02: "B", 0x04: "C", 0x08: "D"}
|
||||
bad = [LED_NAMES[b] for b in (0x01, 0x02, 0x04, 0x08) if non_pwm & b]
|
||||
good = [LED_NAMES[b] for b in (0x01, 0x02, 0x04, 0x08) if pwm_mask & b]
|
||||
raise PM3Error(
|
||||
f"LED(s) {','.join(bad)} do not support {action} on {platform}. "
|
||||
f"PWM-capable LEDs: {','.join(good)}"
|
||||
)
|
||||
|
||||
async def dbg(self, level: int | None = None) -> dict:
|
||||
"""Get or set debug level. None=get, 0-4=set."""
|
||||
if level is None:
|
||||
@@ -162,15 +194,31 @@ class HWCommands:
|
||||
version = resp.data[0]
|
||||
baudrate = struct.unpack_from("<I", resp.data, 1)[0]
|
||||
bigbuf_size = struct.unpack_from("<I", resp.data, 5)[0]
|
||||
# Bit flags start at byte 9
|
||||
flags = 0
|
||||
|
||||
# Parse bit flags (bytes 9+)
|
||||
is_rdv4 = False
|
||||
compiled_with_lf = False
|
||||
compiled_with_iso14443a = False
|
||||
compiled_with_iso15693 = False
|
||||
if len(resp.data) > 12:
|
||||
is_rdv4 = bool(resp.data[12] & 0x02) # byte 12, bit 1
|
||||
if len(resp.data) > 9:
|
||||
flags = struct.unpack_from("<I", resp.data, 9)[0] if len(resp.data) >= 13 else resp.data[9]
|
||||
compiled_with_lf = bool(resp.data[9] & 0x80) # byte 9, bit 7
|
||||
if len(resp.data) > 10:
|
||||
compiled_with_iso14443a = bool(resp.data[10] & 0x40) # byte 10, bit 6
|
||||
if len(resp.data) > 11:
|
||||
compiled_with_iso15693 = bool(resp.data[11] & 0x01) # byte 11, bit 0
|
||||
|
||||
self._is_rdv4 = is_rdv4
|
||||
|
||||
return {
|
||||
"version": version,
|
||||
"baudrate": baudrate,
|
||||
"bigbuf_size": bigbuf_size,
|
||||
"raw_flags": flags,
|
||||
"is_rdv4": is_rdv4,
|
||||
"compiled_with_lf": compiled_with_lf,
|
||||
"compiled_with_iso14443a": compiled_with_iso14443a,
|
||||
"compiled_with_iso15693": compiled_with_iso15693,
|
||||
}
|
||||
|
||||
async def tune(self, on_progress: ProgressCallback = None) -> dict:
|
||||
|
||||
Reference in New Issue
Block a user