Phase 4 enhancements: WebSocket auth, HTTPS UI, plugin hooks, build fixes
Security:
- Add token-based WebSocket authentication (closes critical security gap)
- In-memory token store with 24h TTL (token_store.py)
- POST /api/auth/token exchanges Basic Auth for WS token
- GET /api/auth/status public endpoint for auth check
- WebSocket validates token query param, rejects with close code 4401
- Frontend LoginPrompt modal for credential entry
- WebSocket manager handles full auth flow with auth_required state
- No-op when AUTH_ENABLED=false (preserves existing behavior)
HTTPS:
- Wire HTTPS toggle in Settings UI (POST /api/system/ssl/toggle)
- Add certificate regeneration button
- Display SSL info (expiration, SANs, SHA256 fingerprint)
Plugins:
- Wire trigger_hook("pm3_command") in PM3 service
- Wire trigger_hook("update_check") in update manager
Build/Infrastructure:
- Enable NetworkManager in pi-gen AP setup stage
- Add HF booster board detection patch for Proxmark3
- Update LED PWM control patch
- Fix BLE adapter, UPS drivers, WiFi manager improvements
- Update HTTPS support stage script
Documentation:
- Update PROJECT_STATUS.md and IMPLEMENTATION_PRIORITIES.md
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -391,6 +391,9 @@ EOF
|
||||
|
||||
systemctl enable dangerous-pi-ap.service
|
||||
|
||||
# Ensure NetworkManager is enabled (pi-gen may not carry the default)
|
||||
systemctl enable NetworkManager
|
||||
|
||||
# Ensure hostapd and dnsmasq start AFTER network is configured
|
||||
mkdir -p /etc/systemd/system/hostapd.service.d
|
||||
cat > /etc/systemd/system/hostapd.service.d/override.conf << 'EOF'
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
# Remove this file to enable PiSugar installation
|
||||
# PiSugar support is optional and disabled by default
|
||||
21
pi-gen/stageDangerousPi/05-https-support/00-run-chroot.sh
Normal file → Executable file
21
pi-gen/stageDangerousPi/05-https-support/00-run-chroot.sh
Normal file → Executable file
@@ -1,24 +1,17 @@
|
||||
#!/bin/bash -e
|
||||
# Configure HTTPS support for Dangerous Pi
|
||||
# Installs SSL certificate generation and nginx configuration switching
|
||||
# Sets up SSL certificate generation and nginx configuration switching
|
||||
# Note: Scripts and configs are already in /opt/dangerous-pi/ from stage 03
|
||||
|
||||
echo "=== Setting up HTTPS support ==="
|
||||
|
||||
# Create SSL directory
|
||||
# Create SSL directory for certificates
|
||||
mkdir -p /opt/dangerous-pi/ssl
|
||||
mkdir -p /opt/dangerous-pi/nginx
|
||||
|
||||
# Install certificate generation script
|
||||
echo "Installing SSL certificate generation script..."
|
||||
install -m 755 /opt/dangerous-pi/scripts/generate-ssl-cert.sh /opt/dangerous-pi/scripts/
|
||||
|
||||
# Install nginx configuration switcher
|
||||
echo "Installing nginx configuration switcher..."
|
||||
install -m 755 /opt/dangerous-pi/scripts/configure-nginx.sh /opt/dangerous-pi/scripts/
|
||||
|
||||
# Copy HTTPS nginx configuration
|
||||
echo "Installing HTTPS nginx configuration..."
|
||||
cp /opt/dangerous-pi/nginx/dangerous-pi-https.conf /opt/dangerous-pi/nginx/
|
||||
# Set executable permissions on scripts (already copied by stage 03)
|
||||
echo "Setting script permissions..."
|
||||
chmod 755 /opt/dangerous-pi/scripts/generate-ssl-cert.sh
|
||||
chmod 755 /opt/dangerous-pi/scripts/configure-nginx.sh
|
||||
|
||||
# Install systemd service for first-boot certificate generation
|
||||
echo "Installing SSL certificate generation service..."
|
||||
|
||||
86
pi-gen/stagePM3/01-proxmark3/hf-booster-detection.patch
Normal file
86
pi-gen/stagePM3/01-proxmark3/hf-booster-detection.patch
Normal file
@@ -0,0 +1,86 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Dangerous Pi <dangerous-pi@users.noreply.github.com>
|
||||
Date: Tue, 11 Feb 2026 00:00:00 +0000
|
||||
Subject: [PATCH] client: detect booster board / LC tank loading during hw tune
|
||||
|
||||
When a booster board or other LC tank circuit is installed on a PM3
|
||||
Easy (non-RDV4), the HF antenna voltage during `hw tune` drops into
|
||||
the 2550-2750 mV range. Previously this was reported as "unusable"
|
||||
with no Q-factor calculation. This change detects the loaded state
|
||||
and warns the user about the likely cause.
|
||||
|
||||
The detection only fires on non-RDV4 devices since the voltage range
|
||||
is calibrated for the PM3 Easy voltage divider (11:1, vdd_other=5400).
|
||||
On RDV4 (42.67:1 divider), the same mV reading has different meaning.
|
||||
---
|
||||
client/src/cmdhw.c | 27 ++++++++++++++++++++++-----
|
||||
1 file changed, 22 insertions(+), 5 deletions(-)
|
||||
|
||||
diff --git a/client/src/cmdhw.c b/client/src/cmdhw.c
|
||||
--- a/client/src/cmdhw.c
|
||||
+++ b/client/src/cmdhw.c
|
||||
@@ -909,6 +909,8 @@
|
||||
#define LF_MARGINAL_V 10000
|
||||
#define HF_UNUSABLE_V 3000
|
||||
#define HF_MARGINAL_V 5000
|
||||
+#define HF_BOOSTER_LOW 2550 // lower bound of HF booster/LC tank loading range (mV)
|
||||
+#define HF_BOOSTER_HIGH 2750 // upper bound of HF booster/LC tank loading range (mV)
|
||||
#define ANTENNA_ERROR 1.00 // current algo has 3% error margin.
|
||||
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
@@ -1055,6 +1057,10 @@
|
||||
|
||||
memset(judgement, 0, sizeof(judgement));
|
||||
|
||||
+ bool hf_booster_detected = (package->v_hf >= HF_BOOSTER_LOW
|
||||
+ && package->v_hf <= HF_BOOSTER_HIGH
|
||||
+ && !IfPm3Rdv4Fw());
|
||||
+
|
||||
PrintAndLogEx(SUCCESS, "");
|
||||
PrintAndLogEx(SUCCESS, "Approx. Q factor measurement");
|
||||
|
||||
@@ -1062,16 +1068,24 @@
|
||||
// Q measure with Vlr=Q*(2*Vdd/pi)
|
||||
double hfq = (double)package->v_hf * 3.14 / 2 / vdd;
|
||||
PrintAndLogEx(SUCCESS, "Peak voltage.......... " _YELLOW_("%.1lf"), hfq);
|
||||
- }
|
||||
-
|
||||
- if (package->v_hf < HF_UNUSABLE_V)
|
||||
+ } else if (hf_booster_detected) {
|
||||
+ PrintAndLogEx(SUCCESS, "Your HF antenna measurement shows");
|
||||
+ PrintAndLogEx(SUCCESS, "low voltage that is consistent");
|
||||
+ PrintAndLogEx(SUCCESS, "with the installation of a booster");
|
||||
+ PrintAndLogEx(SUCCESS, "board. If you do not have a");
|
||||
+ PrintAndLogEx(SUCCESS, "booster board installed, either");
|
||||
+ PrintAndLogEx(SUCCESS, "your antenna is malfunctioning or");
|
||||
+ PrintAndLogEx(SUCCESS, "you have a tag on the HF antenna.");
|
||||
+ }
|
||||
+
|
||||
+ if (hf_booster_detected)
|
||||
+ snprintf(judgement, sizeof(judgement), _YELLOW_("loaded"));
|
||||
+ else if (package->v_hf < HF_UNUSABLE_V)
|
||||
snprintf(judgement, sizeof(judgement), _RED_("unusable"));
|
||||
else if (package->v_hf < HF_MARGINAL_V)
|
||||
snprintf(judgement, sizeof(judgement), _YELLOW_("marginal"));
|
||||
else
|
||||
snprintf(judgement, sizeof(judgement), _GREEN_("ok"));
|
||||
|
||||
- PrintAndLogEx((package->v_hf < HF_UNUSABLE_V) ? WARNING : SUCCESS, "HF antenna ( %s )", judgement);
|
||||
+ PrintAndLogEx((package->v_hf < HF_UNUSABLE_V && !hf_booster_detected) ? WARNING : SUCCESS, "HF antenna ( %s )", judgement);
|
||||
+
|
||||
+ if (hf_booster_detected) {
|
||||
+ PrintAndLogEx(NORMAL, "");
|
||||
+ }
|
||||
|
||||
// graph LF measurements
|
||||
// even here, these values has 3% error.
|
||||
@@ -1103,6 +1117,9 @@
|
||||
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
PrintAndLogEx(INFO, "Q factor must be measured without tag on the antenna");
|
||||
+ if (hf_booster_detected) {
|
||||
+ PrintAndLogEx(INFO, "Booster board detection range: %.2f - %.2f V", HF_BOOSTER_LOW / 1000.0, HF_BOOSTER_HIGH / 1000.0);
|
||||
+ }
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
@@ -9,6 +9,11 @@ index 88ce070..cbeb7d2 100644
|
||||
+ case CMD_LED_CONTROL: {
|
||||
+ payload_led_control_t *led_data = (payload_led_control_t *)packet->data.asBytes;
|
||||
+
|
||||
+ // Check for extended struct (8 bytes) vs legacy (3 bytes)
|
||||
+ bool extended = (packet->length >= 8);
|
||||
+ uint16_t speed_ms = extended ? led_data->speed_ms : 500;
|
||||
+ uint16_t count = extended ? led_data->count : 5;
|
||||
+
|
||||
+ if (led_data->action == 0) {
|
||||
+ // Turn off
|
||||
+ if (led_data->led & LED_A) {
|
||||
@@ -65,6 +70,31 @@ index 88ce070..cbeb7d2 100644
|
||||
+ reply_ng(CMD_LED_CONTROL, PM3_EINVARG, NULL, 0);
|
||||
+ break;
|
||||
+ }
|
||||
+
|
||||
+ } else if (led_data->action == 4) {
|
||||
+ // PULSE effect (PWM LEDs only)
|
||||
+ if (led_data->led & ~(LED_A | LED_B)) {
|
||||
+ reply_ng(CMD_LED_CONTROL, PM3_EINVARG, NULL, 0);
|
||||
+ break;
|
||||
+ }
|
||||
+ led_effect_pulse(led_data->led, speed_ms, count);
|
||||
+
|
||||
+ } else if (led_data->action == 5) {
|
||||
+ // FADE effect (PWM LEDs only)
|
||||
+ if (led_data->led & ~(LED_A | LED_B)) {
|
||||
+ reply_ng(CMD_LED_CONTROL, PM3_EINVARG, NULL, 0);
|
||||
+ break;
|
||||
+ }
|
||||
+ led_effect_fade(led_data->led, speed_ms);
|
||||
+
|
||||
+ } else if (led_data->action == 6) {
|
||||
+ // BLINK effect (all LEDs supported)
|
||||
+ led_effect_blink(led_data->led, speed_ms, count);
|
||||
+
|
||||
+ } else {
|
||||
+ // Unknown action
|
||||
+ reply_ng(CMD_LED_CONTROL, PM3_EINVARG, NULL, 0);
|
||||
+ break;
|
||||
+ }
|
||||
+
|
||||
+ reply_ng(CMD_LED_CONTROL, PM3_SUCCESS, NULL, 0);
|
||||
@@ -224,6 +254,157 @@ index 0df8935..6d8246f 100644
|
||||
+ pwm2_initialized = false;
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+// ============================================================================
|
||||
+// LED Effect Functions (firmware-side animations)
|
||||
+// ============================================================================
|
||||
+
|
||||
+static volatile bool led_effect_stop_requested = false;
|
||||
+
|
||||
+void led_effects_stop(void) {
|
||||
+ led_effect_stop_requested = true;
|
||||
+}
|
||||
+
|
||||
+// Check if effect should stop (button press, USB data, or stop flag)
|
||||
+static bool should_stop_effect(void) {
|
||||
+ WDT_HIT();
|
||||
+ return led_effect_stop_requested || BUTTON_PRESS() || data_available();
|
||||
+}
|
||||
+
|
||||
+// PULSE: Fade in and out continuously (PWM LEDs only: A, B)
|
||||
+void led_effect_pulse(uint8_t led_mask, uint16_t speed_ms, uint16_t count) {
|
||||
+ led_effect_stop_requested = false;
|
||||
+
|
||||
+ // Only LED_A and LED_B support PWM
|
||||
+ uint8_t pwm_mask = led_mask & (LED_A | LED_B);
|
||||
+ if (pwm_mask == 0) return;
|
||||
+
|
||||
+ // Default speed if 0
|
||||
+ if (speed_ms == 0) speed_ms = 500;
|
||||
+
|
||||
+ // Calculate steps: 10ms per step, speed_ms/2 per half-cycle
|
||||
+ uint16_t step_delay = 10;
|
||||
+ uint16_t steps = speed_ms / step_delay / 2;
|
||||
+ if (steps < 1) steps = 1;
|
||||
+ if (steps > 50) steps = 50;
|
||||
+
|
||||
+ uint16_t cycles_done = 0;
|
||||
+ bool infinite = (count == 0);
|
||||
+
|
||||
+ while (infinite || cycles_done < count) {
|
||||
+ // Fade in (0 -> 100)
|
||||
+ for (uint16_t i = 0; i <= steps; i++) {
|
||||
+ if (should_stop_effect()) goto pulse_cleanup;
|
||||
+ uint8_t brightness = (i * 100) / steps;
|
||||
+ if (pwm_mask & LED_A) led_set_pwm_brightness(LED_A, brightness);
|
||||
+ if (pwm_mask & LED_B) led_set_pwm_brightness(LED_B, brightness);
|
||||
+ SpinDelay(step_delay);
|
||||
+ }
|
||||
+
|
||||
+ // Fade out (100 -> 0)
|
||||
+ for (uint16_t i = steps; i > 0; i--) {
|
||||
+ if (should_stop_effect()) goto pulse_cleanup;
|
||||
+ uint8_t brightness = (i * 100) / steps;
|
||||
+ if (pwm_mask & LED_A) led_set_pwm_brightness(LED_A, brightness);
|
||||
+ if (pwm_mask & LED_B) led_set_pwm_brightness(LED_B, brightness);
|
||||
+ SpinDelay(step_delay);
|
||||
+ }
|
||||
+
|
||||
+ cycles_done++;
|
||||
+ }
|
||||
+
|
||||
+pulse_cleanup:
|
||||
+ // Disable PWM and turn off LEDs
|
||||
+ if (pwm_mask & LED_A) { led_pwm_disable(LED_A); LED_A_OFF(); }
|
||||
+ if (pwm_mask & LED_B) { led_pwm_disable(LED_B); LED_B_OFF(); }
|
||||
+ led_effect_stop_requested = false;
|
||||
+}
|
||||
+
|
||||
+// FADE: Full brightness then fade out once (PWM LEDs only: A, B)
|
||||
+void led_effect_fade(uint8_t led_mask, uint16_t speed_ms) {
|
||||
+ led_effect_stop_requested = false;
|
||||
+
|
||||
+ uint8_t pwm_mask = led_mask & (LED_A | LED_B);
|
||||
+ if (pwm_mask == 0) return;
|
||||
+
|
||||
+ if (speed_ms == 0) speed_ms = 500;
|
||||
+
|
||||
+ uint16_t step_delay = 10;
|
||||
+ uint16_t steps = speed_ms / step_delay;
|
||||
+ if (steps < 1) steps = 1;
|
||||
+ if (steps > 100) steps = 100;
|
||||
+
|
||||
+ // Start at full brightness
|
||||
+ if (pwm_mask & LED_A) led_set_pwm_brightness(LED_A, 100);
|
||||
+ if (pwm_mask & LED_B) led_set_pwm_brightness(LED_B, 100);
|
||||
+
|
||||
+ // Fade out
|
||||
+ for (uint16_t i = steps; i > 0; i--) {
|
||||
+ if (should_stop_effect()) goto fade_cleanup;
|
||||
+ uint8_t brightness = (i * 100) / steps;
|
||||
+ if (pwm_mask & LED_A) led_set_pwm_brightness(LED_A, brightness);
|
||||
+ if (pwm_mask & LED_B) led_set_pwm_brightness(LED_B, brightness);
|
||||
+ SpinDelay(step_delay);
|
||||
+ }
|
||||
+
|
||||
+fade_cleanup:
|
||||
+ if (pwm_mask & LED_A) { led_pwm_disable(LED_A); LED_A_OFF(); }
|
||||
+ if (pwm_mask & LED_B) { led_pwm_disable(LED_B); LED_B_OFF(); }
|
||||
+ led_effect_stop_requested = false;
|
||||
+}
|
||||
+
|
||||
+// BLINK: Alternating on/off (works with ALL LEDs including C, D)
|
||||
+void led_effect_blink(uint8_t led_mask, uint16_t speed_ms, uint16_t count) {
|
||||
+ led_effect_stop_requested = false;
|
||||
+
|
||||
+ if (led_mask == 0) return;
|
||||
+ if (speed_ms == 0) speed_ms = 500;
|
||||
+
|
||||
+ // Disable any PWM first (use GPIO toggling)
|
||||
+ if (led_mask & LED_A) led_pwm_disable(LED_A);
|
||||
+ if (led_mask & LED_B) led_pwm_disable(LED_B);
|
||||
+
|
||||
+ uint16_t half_cycle = speed_ms / 2;
|
||||
+ if (half_cycle > 1390) half_cycle = 1390; // SpinDelay limit
|
||||
+
|
||||
+ uint16_t cycles_done = 0;
|
||||
+ bool infinite = (count == 0);
|
||||
+
|
||||
+ // Start with LEDs off
|
||||
+ if (led_mask & LED_A) LED_A_OFF();
|
||||
+ if (led_mask & LED_B) LED_B_OFF();
|
||||
+ if (led_mask & LED_C) LED_C_OFF();
|
||||
+ if (led_mask & LED_D) LED_D_OFF();
|
||||
+
|
||||
+ while (infinite || cycles_done < count) {
|
||||
+ if (should_stop_effect()) break;
|
||||
+
|
||||
+ // Toggle on
|
||||
+ if (led_mask & LED_A) LED_A_ON();
|
||||
+ if (led_mask & LED_B) LED_B_ON();
|
||||
+ if (led_mask & LED_C) LED_C_ON();
|
||||
+ if (led_mask & LED_D) LED_D_ON();
|
||||
+
|
||||
+ SpinDelay(half_cycle);
|
||||
+ if (should_stop_effect()) break;
|
||||
+
|
||||
+ // Toggle off
|
||||
+ if (led_mask & LED_A) LED_A_OFF();
|
||||
+ if (led_mask & LED_B) LED_B_OFF();
|
||||
+ if (led_mask & LED_C) LED_C_OFF();
|
||||
+ if (led_mask & LED_D) LED_D_OFF();
|
||||
+
|
||||
+ SpinDelay(half_cycle);
|
||||
+ cycles_done++;
|
||||
+ }
|
||||
+
|
||||
+ // Ensure LEDs are off when done
|
||||
+ if (led_mask & LED_A) LED_A_OFF();
|
||||
+ if (led_mask & LED_B) LED_B_OFF();
|
||||
+ if (led_mask & LED_C) LED_C_OFF();
|
||||
+ if (led_mask & LED_D) LED_D_OFF();
|
||||
+ led_effect_stop_requested = false;
|
||||
+}
|
||||
diff --git a/armsrc/util.h b/armsrc/util.h
|
||||
index fd99ac7..d558dda 100644
|
||||
--- a/armsrc/util.h
|
||||
@@ -234,6 +415,12 @@ index fd99ac7..d558dda 100644
|
||||
|
||||
+void led_set_pwm_brightness(uint8_t led, uint8_t brightness);
|
||||
+void led_pwm_disable(uint8_t led);
|
||||
+
|
||||
+// LED effect functions (firmware-side animations)
|
||||
+void led_effect_pulse(uint8_t led_mask, uint16_t speed_ms, uint16_t count);
|
||||
+void led_effect_fade(uint8_t led_mask, uint16_t speed_ms);
|
||||
+void led_effect_blink(uint8_t led_mask, uint16_t speed_ms, uint16_t count);
|
||||
+void led_effects_stop(void);
|
||||
+
|
||||
#endif
|
||||
diff --git a/client/src/cmdhw.c b/client/src/cmdhw.c
|
||||
@@ -247,13 +434,18 @@ index 9bbdc98..ad877b0 100644
|
||||
+static int CmdLed(const char *Cmd) {
|
||||
+ CLIParserContext *ctx;
|
||||
+ CLIParserInit(&ctx, "hw led",
|
||||
+ "Control Proxmark3 LEDs with optional PWM brightness for LED A and B",
|
||||
+ "hw led --led a --on --> Turn on LED A\n"
|
||||
+ "hw led --led b --off --> Turn off LED B\n"
|
||||
+ "hw led --led a,b --toggle --> Toggle LEDs A and B\n"
|
||||
+ "hw led --led a --brightness 50 --> Set LED A to 50%% brightness (PWM)\n"
|
||||
+ "hw led --led b --brightness 75 --> Set LED B to 75%% brightness (PWM)\n"
|
||||
+ "hw led --led a,b --brightness 30 --> Set both LEDs A and B to 30%% brightness");
|
||||
+ "Control Proxmark3 LEDs with optional PWM brightness and effects.\n"
|
||||
+ "LED A (green) and B (blue) support PWM effects (pulse, fade, brightness).\n"
|
||||
+ "All LEDs support blink effect.",
|
||||
+ "hw led --led a --on --> Turn on LED A\n"
|
||||
+ "hw led --led b --off --> Turn off LED B\n"
|
||||
+ "hw led --led a,b --toggle --> Toggle LEDs A and B\n"
|
||||
+ "hw led --led a --brightness 50 --> Set LED A to 50%% brightness (PWM)\n"
|
||||
+ "hw led --led a --pulse --> Pulse LED A (5 cycles, 500ms)\n"
|
||||
+ "hw led --led a --pulse --count 10 --speed 300 --> Pulse 10 times, 300ms cycle\n"
|
||||
+ "hw led --led b --fade --> Fade LED B out once\n"
|
||||
+ "hw led --led a,b,c,d --blink --> Blink all LEDs\n"
|
||||
+ "hw led --led c,d --blink --count 20 --speed 200 --> Fast blink non-PWM LEDs");
|
||||
+
|
||||
+ void *argtable[] = {
|
||||
+ arg_param_begin,
|
||||
@@ -262,6 +454,11 @@ index 9bbdc98..ad877b0 100644
|
||||
+ arg_lit0(NULL, "off", "Turn LED off"),
|
||||
+ arg_lit0(NULL, "toggle", "Toggle LED"),
|
||||
+ arg_int0(NULL, "brightness", "<0-100>", "LED brightness percentage (PWM, LED A and B only)"),
|
||||
+ arg_lit0(NULL, "pulse", "Pulse effect - fade in/out (PWM LEDs only)"),
|
||||
+ arg_lit0(NULL, "fade", "Fade effect - fade out from full (PWM LEDs only)"),
|
||||
+ arg_lit0(NULL, "blink", "Blink effect - on/off alternating (all LEDs)"),
|
||||
+ arg_int0(NULL, "speed", "<ms>", "Effect cycle time in milliseconds (default 500)"),
|
||||
+ arg_int0(NULL, "count", "<n>", "Number of effect cycles (default 5, 0=infinite)"),
|
||||
+ arg_param_end
|
||||
+ };
|
||||
+ CLIExecWithReturn(ctx, Cmd, argtable, false);
|
||||
@@ -273,11 +470,17 @@ index 9bbdc98..ad877b0 100644
|
||||
+ bool off = arg_get_lit(ctx, 3);
|
||||
+ bool toggle = arg_get_lit(ctx, 4);
|
||||
+ int brightness = arg_get_int_def(ctx, 5, -1);
|
||||
+ bool pulse = arg_get_lit(ctx, 6);
|
||||
+ bool fade = arg_get_lit(ctx, 7);
|
||||
+ bool blink = arg_get_lit(ctx, 8);
|
||||
+ int speed = arg_get_int_def(ctx, 9, 500);
|
||||
+ int count = arg_get_int_def(ctx, 10, 5);
|
||||
+ CLIParserFree(ctx);
|
||||
+
|
||||
+ // Validate only one action
|
||||
+ if ((on + off + toggle + (brightness >= 0)) != 1) {
|
||||
+ PrintAndLogEx(ERR, "Specify exactly one action: --on, --off, --toggle, or --brightness");
|
||||
+ // Count how many actions specified
|
||||
+ int action_count = on + off + toggle + (brightness >= 0) + pulse + fade + blink;
|
||||
+ if (action_count != 1) {
|
||||
+ PrintAndLogEx(ERR, "Specify exactly one action: --on, --off, --toggle, --brightness, --pulse, --fade, or --blink");
|
||||
+ return PM3_EINVARG;
|
||||
+ }
|
||||
+
|
||||
@@ -285,18 +488,21 @@ index 9bbdc98..ad877b0 100644
|
||||
+ uint8_t led_mask = 0;
|
||||
+ char *token = strtok(led_str, ",");
|
||||
+ while (token != NULL) {
|
||||
+ if (strcmp(token, "a") == 0 || strcmp(token, "A") == 0) {
|
||||
+ // Trim leading whitespace
|
||||
+ while (*token == ' ') token++;
|
||||
+
|
||||
+ if (strcasecmp(token, "a") == 0) {
|
||||
+ led_mask |= 1; // LED_A
|
||||
+ } else if (strcmp(token, "b") == 0 || strcmp(token, "B") == 0) {
|
||||
+ } else if (strcasecmp(token, "b") == 0) {
|
||||
+ led_mask |= 2; // LED_B
|
||||
+ } else if (strcmp(token, "c") == 0 || strcmp(token, "C") == 0) {
|
||||
+ } else if (strcasecmp(token, "c") == 0) {
|
||||
+ led_mask |= 4; // LED_C
|
||||
+ } else if (strcmp(token, "d") == 0 || strcmp(token, "D") == 0) {
|
||||
+ } else if (strcasecmp(token, "d") == 0) {
|
||||
+ led_mask |= 8; // LED_D
|
||||
+ } else if (strcmp(token, "all") == 0 || strcmp(token, "ALL") == 0) {
|
||||
+ } else if (strcasecmp(token, "all") == 0) {
|
||||
+ led_mask = 15; // All LEDs
|
||||
+ } else {
|
||||
+ PrintAndLogEx(ERR, "Invalid LED: %s (use a, b, c, d, or all)", token);
|
||||
+ PrintAndLogEx(ERR, "Invalid LED: '%s' (use a, b, c, d, or all)", token);
|
||||
+ return PM3_EINVARG;
|
||||
+ }
|
||||
+ token = strtok(NULL, ",");
|
||||
@@ -307,49 +513,108 @@ index 9bbdc98..ad877b0 100644
|
||||
+ return PM3_EINVARG;
|
||||
+ }
|
||||
+
|
||||
+ // Validate brightness
|
||||
+ if (brightness >= 0) {
|
||||
+ if (brightness > 100) {
|
||||
+ PrintAndLogEx(ERR, "Brightness must be 0-100");
|
||||
+ return PM3_EINVARG;
|
||||
+ }
|
||||
+ // Check if only LED_A and/or LED_B selected for PWM
|
||||
+ if (led_mask & ~0x03) { // If any bit other than LED_A or LED_B is set
|
||||
+ PrintAndLogEx(ERR, "PWM brightness only supported for LED A and B");
|
||||
+ return PM3_EINVARG;
|
||||
+ }
|
||||
+ // Validate PWM-only actions (brightness, pulse, fade)
|
||||
+ if ((brightness >= 0 || pulse || fade) && (led_mask & ~0x03)) {
|
||||
+ PrintAndLogEx(ERR, "PWM effects (pulse, fade, brightness) only supported for LED A and B");
|
||||
+ return PM3_EINVARG;
|
||||
+ }
|
||||
+
|
||||
+ // Build payload
|
||||
+ // Validate brightness range
|
||||
+ if (brightness > 100) {
|
||||
+ PrintAndLogEx(ERR, "Brightness must be 0-100");
|
||||
+ return PM3_EINVARG;
|
||||
+ }
|
||||
+
|
||||
+ // Validate speed range
|
||||
+ if (speed < 50 || speed > 10000) {
|
||||
+ PrintAndLogEx(ERR, "Speed must be between 50 and 10000 ms");
|
||||
+ return PM3_EINVARG;
|
||||
+ }
|
||||
+
|
||||
+ // Validate count
|
||||
+ if (count < 0 || count > 65535) {
|
||||
+ PrintAndLogEx(ERR, "Count must be 0-65535 (0 = infinite)");
|
||||
+ return PM3_EINVARG;
|
||||
+ }
|
||||
+
|
||||
+ // Build extended payload (8 bytes)
|
||||
+ struct {
|
||||
+ uint8_t led;
|
||||
+ uint8_t action;
|
||||
+ uint8_t brightness;
|
||||
+ uint8_t reserved;
|
||||
+ uint16_t speed_ms;
|
||||
+ uint16_t count;
|
||||
+ } PACKED payload;
|
||||
+
|
||||
+ payload.led = led_mask;
|
||||
+ if (on) {
|
||||
+ payload.action = 1;
|
||||
+ } else if (off) {
|
||||
+ payload.brightness = (brightness >= 0) ? brightness : 100;
|
||||
+ payload.reserved = 0;
|
||||
+ payload.speed_ms = speed;
|
||||
+ payload.count = count;
|
||||
+
|
||||
+ // Determine action code
|
||||
+ if (off) {
|
||||
+ payload.action = 0;
|
||||
+ } else if (on) {
|
||||
+ payload.action = 1;
|
||||
+ } else if (toggle) {
|
||||
+ payload.action = 2;
|
||||
+ } else {
|
||||
+ payload.action = 3; // PWM
|
||||
+ } else if (brightness >= 0) {
|
||||
+ payload.action = 3;
|
||||
+ } else if (pulse) {
|
||||
+ payload.action = 4;
|
||||
+ } else if (fade) {
|
||||
+ payload.action = 5;
|
||||
+ } else if (blink) {
|
||||
+ payload.action = 6;
|
||||
+ }
|
||||
+
|
||||
+ // Print feedback for effects
|
||||
+ if (pulse || fade || blink) {
|
||||
+ const char *effect_name = pulse ? "pulse" : (fade ? "fade" : "blink");
|
||||
+ PrintAndLogEx(INFO, "LED %s: LEDs=%s%s%s%s, speed=%dms, count=%d",
|
||||
+ effect_name,
|
||||
+ (led_mask & 1) ? "A" : "",
|
||||
+ (led_mask & 2) ? "B" : "",
|
||||
+ (led_mask & 4) ? "C" : "",
|
||||
+ (led_mask & 8) ? "D" : "",
|
||||
+ payload.speed_ms,
|
||||
+ payload.count);
|
||||
+
|
||||
+ if (count == 0) {
|
||||
+ PrintAndLogEx(INFO, "Running indefinitely. Press button on device or send any command to stop.");
|
||||
+ }
|
||||
+ }
|
||||
+ payload.brightness = (brightness >= 0) ? brightness : 0;
|
||||
+
|
||||
+ clearCommandBuffer();
|
||||
+ SendCommandNG(CMD_LED_CONTROL, (uint8_t *)&payload, sizeof(payload));
|
||||
+ PacketResponseNG resp;
|
||||
+ if (WaitForResponseTimeout(CMD_LED_CONTROL, &resp, 1000) == false) {
|
||||
+ PrintAndLogEx(WARNING, "command execution time out");
|
||||
+ return PM3_ETIMEOUT;
|
||||
+
|
||||
+ // Calculate timeout based on effect duration
|
||||
+ uint32_t timeout = 2000;
|
||||
+ if (pulse || blink) {
|
||||
+ if (count == 0) {
|
||||
+ timeout = 0; // No wait for infinite effects
|
||||
+ } else {
|
||||
+ timeout = (count * speed) + 2000;
|
||||
+ if (timeout > 120000) timeout = 120000; // Max 2 minutes
|
||||
+ }
|
||||
+ } else if (fade) {
|
||||
+ timeout = speed + 2000;
|
||||
+ }
|
||||
+ if (resp.status != PM3_SUCCESS) {
|
||||
+ PrintAndLogEx(ERR, "LED control command failed");
|
||||
+ return resp.status;
|
||||
+
|
||||
+ if (timeout > 0) {
|
||||
+ PacketResponseNG resp;
|
||||
+ if (WaitForResponseTimeout(CMD_LED_CONTROL, &resp, timeout) == false) {
|
||||
+ PrintAndLogEx(WARNING, "command execution time out");
|
||||
+ return PM3_ETIMEOUT;
|
||||
+ }
|
||||
+ if (resp.status != PM3_SUCCESS) {
|
||||
+ PrintAndLogEx(ERR, "LED control command failed");
|
||||
+ return resp.status;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ return PM3_SUCCESS;
|
||||
+}
|
||||
+
|
||||
@@ -360,7 +625,7 @@ index 9bbdc98..ad877b0 100644
|
||||
{"connect", CmdConnect, AlwaysAvailable, "Connect to the device via serial port"},
|
||||
{"dbg", CmdDbg, IfPm3Present, "Set device side debug level"},
|
||||
{"fpgaoff", CmdFPGAOff, IfPm3Present, "Turn off FPGA on device"},
|
||||
+ {"led", CmdLed, IfPm3Present, "Control LEDs with optional PWM brightness"},
|
||||
+ {"led", CmdLed, IfPm3Present, "Control LEDs with PWM brightness and effects"},
|
||||
{"lcd", CmdLCD, IfPm3Lcd, "Send command/data to LCD"},
|
||||
{"lcdreset", CmdLCDReset, IfPm3Lcd, "Hardware reset LCD"},
|
||||
{"ping", CmdPing, IfPm3Present, "Test if the Proxmark3 is responsive"},
|
||||
@@ -459,10 +724,14 @@ index 860dfa9..e1551b5 100644
|
||||
} PACKED smart_card_raw_t;
|
||||
|
||||
+// For CMD_LED_CONTROL
|
||||
+// Actions: 0=off, 1=on, 2=toggle, 3=pwm, 4=pulse, 5=fade, 6=blink
|
||||
+typedef struct {
|
||||
+ uint8_t led; // LED bitmask: LED_A=1, LED_B=2, LED_C=4, LED_D=8
|
||||
+ uint8_t action; // 0=off, 1=on, 2=toggle, 3=pwm
|
||||
+ uint8_t brightness; // 0-100 (for action=3 PWM mode only)
|
||||
+ uint8_t action; // 0=off, 1=on, 2=toggle, 3=pwm, 4=pulse, 5=fade, 6=blink
|
||||
+ uint8_t brightness; // 0-100 (for PWM-based actions)
|
||||
+ uint8_t reserved; // Reserved for alignment
|
||||
+ uint16_t speed_ms; // Effect cycle time in ms (default 500)
|
||||
+ uint16_t count; // Number of effect cycles (0=infinite)
|
||||
+} PACKED payload_led_control_t;
|
||||
+
|
||||
|
||||
|
||||
Reference in New Issue
Block a user