chore: import local project into Gitea
This commit is contained in:
5
iceman_web_ui_esp32/CMakeLists.txt
Normal file
5
iceman_web_ui_esp32/CMakeLists.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
|
||||
project(proxarch_esp_idf)
|
||||
16
iceman_web_ui_esp32/main/CMakeLists.txt
Normal file
16
iceman_web_ui_esp32/main/CMakeLists.txt
Normal file
@@ -0,0 +1,16 @@
|
||||
|
||||
idf_component_register(SRCS "proxarch_main.c"
|
||||
INCLUDE_DIRS ".")
|
||||
|
||||
# Required for esp_http_server, esp_wifi, nvs_flash, esp_netif, esp_event, esp_log, freertos, lwip
|
||||
set(requires esp_http_server esp_wifi nvs_flash esp_netif esp_event esp_log freertos lwip)
|
||||
|
||||
# Required for TinyUSB
|
||||
list(APPEND requires tinyusb)
|
||||
|
||||
# If you use SPIFFS for web files later, add:
|
||||
# list(APPEND requires spiffs)
|
||||
|
||||
target_link_libraries(${COMPONENT_TARGET} PRIVATE
|
||||
${requires}
|
||||
)
|
||||
708
iceman_web_ui_esp32/main/proxarch_main.c
Normal file
708
iceman_web_ui_esp32/main/proxarch_main.c
Normal file
@@ -0,0 +1,708 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/event_groups.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_wifi.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_log.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "esp_netif.h"
|
||||
#include "esp_http_server.h"
|
||||
#include "esp_vfs.h" // For esp_vfs_spiffs_register if using SPIFFS
|
||||
|
||||
// For TinyUSB
|
||||
#include "tinyusb.h"
|
||||
#include "tusb_cdc_acm.h"
|
||||
#include "sdkconfig.h" // Required for Kconfig options
|
||||
|
||||
// WiFi credentials - REPLACE WITH YOURS
|
||||
#define WIFI_SSID "proxarch"
|
||||
#define WIFI_PASS "password"
|
||||
#define WIFI_MAXIMUM_RETRY 5
|
||||
|
||||
// Proxmark3 Communication
|
||||
#define PM3_CDC_ITF ITF_NUM_CDC_0 // CDC Interface number for PM3
|
||||
#define PM3_RESPONSE_BUFFER_SIZE 2048
|
||||
#define PM3_PROMPT "proxmark3>"
|
||||
#define PM3_CMD_TIMEOUT_MS 10000 // 10 seconds
|
||||
|
||||
static char pm3_rx_buffer[PM3_RESPONSE_BUFFER_SIZE];
|
||||
static uint32_t pm3_rx_buffer_len = 0;
|
||||
static bool pm3_connected = false;
|
||||
static bool pm3_command_in_progress = false;
|
||||
static httpd_ws_client_t* ws_clients[CONFIG_LWIP_MAX_SOCKETS - 4]; // Max clients for WebSocket
|
||||
static size_t ws_client_count = 0;
|
||||
|
||||
// FreeRTOS event group to signal when we are connected
|
||||
static EventGroupHandle_t s_wifi_event_group;
|
||||
#define WIFI_CONNECTED_BIT BIT0
|
||||
#define WIFI_FAIL_BIT BIT1
|
||||
static int s_retry_num = 0;
|
||||
|
||||
static const char *TAG = "PROXARCH";
|
||||
|
||||
// --- Forward Declarations ---
|
||||
static void wifi_event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data);
|
||||
void wifi_init_sta(void);
|
||||
static esp_err_t http_get_handler(httpd_req_t *req);
|
||||
static esp_err_t ws_handler(httpd_req_t *req);
|
||||
static void send_pm3_command(const char* command);
|
||||
static void send_ws_message_to_all(const char* message, size_t len);
|
||||
static void send_ws_pm3_status_update(void);
|
||||
static void tinyusb_cdc_rx_callback(int itf, cdcacm_event_t *event);
|
||||
static void tinyusb_cdc_line_state_changed_callback(int itf, cdcacm_event_t *event);
|
||||
|
||||
// --- Embedded Web Page ---
|
||||
const char index_html_start[] = R"rawliteral(
|
||||
<!DOCTYPE HTML><html>
|
||||
<head>
|
||||
<title>Proxarch ESP-IDF</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; background-color: #1e1e1e; color: #d4d4d4; margin: 0; padding: 20px; display: flex; flex-direction: column; align-items: center; }
|
||||
h1 { color: #569cd6; }
|
||||
.container { background-color: #252526; padding: 20px; border-radius: 8px; box-shadow: 0 0 10px rgba(0,0,0,0.5); width: 90%; max-width: 800px; }
|
||||
.button-group { margin-bottom: 20px; display: flex; flex-wrap: wrap; gap: 10px; justify-content: center;}
|
||||
button { background-color: #0e639c; color: white; border: none; padding: 10px 15px; text-align: center; text-decoration: none; display: inline-block; font-size: 16px; border-radius: 4px; cursor: pointer; transition: background-color 0.3s; }
|
||||
button:hover { background-color: #1177bb; }
|
||||
button:active { background-color: #0c5586; }
|
||||
#output { background-color: #1e1e1e; color: #d4d4d4; border: 1px solid #333; padding: 10px; height: 400px; overflow-y: scroll; white-space: pre-wrap; font-family: 'Courier New', Courier, monospace; margin-top: 20px; border-radius: 4px; }
|
||||
.status { margin-bottom:15px; font-size:0.9em; text-align:center; }
|
||||
#pm3Status { font-weight: bold; }
|
||||
#wifiStatus { font-weight: bold; }
|
||||
.loader { border: 5px solid #f3f3f3; border-top: 5px solid #569cd6; border-radius: 50%; width: 30px; height: 30px; animation: spin 1s linear infinite; margin: 10px auto; display: none; }
|
||||
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Proxarch ESP-IDF Interface</h1>
|
||||
<div class="container">
|
||||
<div class="status">
|
||||
WiFi Status: <span id="wifiStatus">Connecting...</span> | Proxmark3 Status: <span id="pm3Status">Disconnected</span>
|
||||
</div>
|
||||
<div class="button-group">
|
||||
<button onclick="sendCommand('hw version')">Get Version</button>
|
||||
<button onclick="sendCommand('hw tune')">Tune Antennas</button>
|
||||
<button onclick="sendCommand('lf search')">LF Search</button>
|
||||
<button onclick="sendCommand('hf search')">HF Search</button>
|
||||
<button onclick="sendCommand('data buffclear')">Clear Buffer</button>
|
||||
</div>
|
||||
<div id="loader" class="loader"></div>
|
||||
<h3>Proxmark3 Output:</h3>
|
||||
<div id="output">Waiting for commands...</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var gateway = `ws://${window.location.hostname}/ws`;
|
||||
var websocket;
|
||||
var outputDiv = document.getElementById('output');
|
||||
var loaderDiv = document.getElementById('loader');
|
||||
var pm3StatusSpan = document.getElementById('pm3Status');
|
||||
var wifiStatusSpan = document.getElementById('wifiStatus');
|
||||
|
||||
window.addEventListener('load', onLoad);
|
||||
|
||||
function onLoad(event) {
|
||||
initWebSocket();
|
||||
// WiFi status will be updated by server push or initial message
|
||||
}
|
||||
|
||||
function initWebSocket() {
|
||||
console.log('Trying to open a WebSocket connection...');
|
||||
websocket = new WebSocket(gateway);
|
||||
websocket.onopen = onOpen;
|
||||
websocket.onclose = onClose;
|
||||
websocket.onmessage = onMessage;
|
||||
}
|
||||
|
||||
function onOpen(event) {
|
||||
console.log('Connection opened');
|
||||
websocket.send(JSON.stringify({type: "client_hello"}));
|
||||
updateWifiStatus(true, window.location.hostname);
|
||||
}
|
||||
|
||||
function onClose(event) {
|
||||
console.log('Connection closed');
|
||||
pm3StatusSpan.textContent = "Disconnected (WS)";
|
||||
pm3StatusSpan.style.color = "#ff6347"; // Red
|
||||
updateWifiStatus(false);
|
||||
setTimeout(initWebSocket, 2000); // Try to reconnect
|
||||
}
|
||||
|
||||
function escapeHtml(unsafe) {
|
||||
return unsafe
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'");
|
||||
}
|
||||
|
||||
function onMessage(event) {
|
||||
// console.log(event.data);
|
||||
var data;
|
||||
try {
|
||||
data = JSON.parse(event.data);
|
||||
} catch (e) {
|
||||
console.error("Error parsing WebSocket JSON:", e);
|
||||
// If not JSON, treat as raw string for output (e.g. direct PM3 data)
|
||||
outputDiv.innerHTML += escapeHtml(event.data) + '\\n';
|
||||
outputDiv.scrollTop = outputDiv.scrollHeight;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if(data.type === 'pm3_status') {
|
||||
if(data.connected) {
|
||||
pm3StatusSpan.textContent = "Connected";
|
||||
pm3StatusSpan.style.color = "#32cd32"; // Green
|
||||
} else {
|
||||
pm3StatusSpan.textContent = "Disconnected";
|
||||
pm3StatusSpan.style.color = "#ff6347"; // Red
|
||||
}
|
||||
} else if (data.type === 'wifi_status') {
|
||||
updateWifiStatus(data.connected, data.ip);
|
||||
} else if (data.type === 'pm3_response_start') {
|
||||
outputDiv.innerHTML = ''; // Clear previous output
|
||||
loaderDiv.style.display = 'block';
|
||||
} else if (data.type === 'pm3_response_chunk') {
|
||||
outputDiv.innerHTML += escapeHtml(data.chunk);
|
||||
outputDiv.scrollTop = outputDiv.scrollHeight; // Auto-scroll
|
||||
} else if (data.type === 'pm3_response_end') {
|
||||
loaderDiv.style.display = 'none';
|
||||
outputDiv.scrollTop = outputDiv.scrollHeight;
|
||||
} else if (data.type === 'pm3_error' || data.type === 'error') {
|
||||
outputDiv.innerHTML += `<span style="color: #ff6347;">ERROR: ${escapeHtml(data.message)}</span>\\n`;
|
||||
loaderDiv.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function sendCommand(command) {
|
||||
if (websocket.readyState === WebSocket.OPEN) {
|
||||
// outputDiv.innerHTML = ''; // Clear previous output on new command - handled by response_start
|
||||
loaderDiv.style.display = 'block';
|
||||
console.log("Sending command: " + command);
|
||||
websocket.send(JSON.stringify({type: "pm3_command", cmd: command}));
|
||||
} else {
|
||||
console.log("WebSocket is not open. Cannot send command.");
|
||||
outputDiv.innerHTML = "WebSocket connection is closed. Please wait or refresh.";
|
||||
}
|
||||
}
|
||||
|
||||
function updateWifiStatus(connected, ipAddress = '') {
|
||||
if(connected) {
|
||||
wifiStatusSpan.textContent = `Connected (${ipAddress})`;
|
||||
wifiStatusSpan.style.color = "#32cd32"; // Green
|
||||
} else {
|
||||
wifiStatusSpan.textContent = "Disconnected";
|
||||
wifiStatusSpan.style.color = "#ff6347"; // Red
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
)rawliteral";
|
||||
|
||||
|
||||
// --- WiFi Initialization ---
|
||||
static void wifi_event_handler(void* arg, esp_event_base_t event_base,
|
||||
int32_t event_id, void* event_data) {
|
||||
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
|
||||
esp_wifi_connect();
|
||||
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
|
||||
if (s_retry_num < WIFI_MAXIMUM_RETRY) {
|
||||
esp_wifi_connect();
|
||||
s_retry_num++;
|
||||
ESP_LOGI(TAG, "Retrying to connect to the AP");
|
||||
} else {
|
||||
xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
|
||||
// Send WiFi status update via WebSocket if possible
|
||||
char ws_msg[100];
|
||||
snprintf(ws_msg, sizeof(ws_msg), "{\"type\":\"wifi_status\", \"connected\":false, \"ip\":\"N/A\"}");
|
||||
send_ws_message_to_all(ws_msg, strlen(ws_msg));
|
||||
}
|
||||
ESP_LOGI(TAG,"Connect to the AP fail");
|
||||
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
|
||||
ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
|
||||
ESP_LOGI(TAG, "Got IP:" IPSTR, IP2STR(&event->ip_info.ip));
|
||||
s_retry_num = 0;
|
||||
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
|
||||
// Send WiFi status update via WebSocket
|
||||
char ws_msg[100];
|
||||
snprintf(ws_msg, sizeof(ws_msg), "{\"type\":\"wifi_status\", \"connected\":true, \"ip\":\"" IPSTR "\"}", IP2STR(&event->ip_info.ip));
|
||||
send_ws_message_to_all(ws_msg, strlen(ws_msg));
|
||||
}
|
||||
}
|
||||
|
||||
void wifi_init_sta(void) {
|
||||
s_wifi_event_group = xEventGroupCreate();
|
||||
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
esp_netif_create_default_wifi_sta();
|
||||
|
||||
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
|
||||
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
|
||||
|
||||
esp_event_handler_instance_t instance_any_id;
|
||||
esp_event_handler_instance_t instance_got_ip;
|
||||
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
|
||||
ESP_EVENT_ANY_ID,
|
||||
&wifi_event_handler,
|
||||
NULL,
|
||||
&instance_any_id));
|
||||
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
|
||||
IP_EVENT_STA_GOT_IP,
|
||||
&wifi_event_handler,
|
||||
NULL,
|
||||
&instance_got_ip));
|
||||
|
||||
wifi_config_t wifi_config = {
|
||||
.sta = {
|
||||
.ssid = WIFI_SSID,
|
||||
.password = WIFI_PASS,
|
||||
.threshold.authmode = WIFI_AUTH_WPA2_PSK, // Adjust if needed
|
||||
},
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) );
|
||||
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config) );
|
||||
ESP_ERROR_CHECK(esp_wifi_start() );
|
||||
|
||||
ESP_LOGI(TAG, "wifi_init_sta finished.");
|
||||
|
||||
EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
|
||||
WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
|
||||
pdFALSE,
|
||||
pdFALSE,
|
||||
portMAX_DELAY);
|
||||
|
||||
if (bits & WIFI_CONNECTED_BIT) {
|
||||
ESP_LOGI(TAG, "Connected to AP SSID:%s", WIFI_SSID);
|
||||
} else if (bits & WIFI_FAIL_BIT) {
|
||||
ESP_LOGI(TAG, "Failed to connect to SSID:%s", WIFI_SSID);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "UNEXPECTED WIFI EVENT");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// --- TinyUSB CDC Host Callbacks & Task ---
|
||||
void tud_mount_cb(void) {
|
||||
ESP_LOGI(TAG, "TinyUSB Device MOUNTED");
|
||||
}
|
||||
|
||||
void tud_umount_cb(void) {
|
||||
ESP_LOGI(TAG, "TinyUSB Device UNMOUNTED");
|
||||
pm3_connected = false;
|
||||
pm3_command_in_progress = false;
|
||||
send_ws_pm3_status_update();
|
||||
}
|
||||
|
||||
// Invoked when device is suspended
|
||||
void tud_suspend_cb(bool remote_wakeup_en) {
|
||||
(void) remote_wakeup_en;
|
||||
ESP_LOGI(TAG, "TinyUSB Device Suspended");
|
||||
}
|
||||
|
||||
// Invoked when device is resumed
|
||||
void tud_resume_cb(void) {
|
||||
ESP_LOGI(TAG, "TinyUSB Device Resumed");
|
||||
}
|
||||
|
||||
// Callback invoked when data is received from CDC host
|
||||
static void tinyusb_cdc_rx_callback(int itf, cdcacm_event_t *event) {
|
||||
size_t rx_size = 0;
|
||||
esp_err_t ret = tud_cdc_n_read(itf, pm3_rx_buffer + pm3_rx_buffer_len, PM3_RESPONSE_BUFFER_SIZE - pm3_rx_buffer_len -1); // -1 for null terminator
|
||||
if (ret == ESP_OK) {
|
||||
rx_size = tud_cdc_n_get_available(itf); // This might not be what we want, tud_cdc_n_read should return bytes read
|
||||
// For now, let's assume ret is the size or use a fixed size from a buffer.
|
||||
// A better way is to use the actual bytes read from tud_cdc_n_read if it returns that.
|
||||
// For now, we'll rely on a different mechanism to know how much was read.
|
||||
// This callback might be tricky with TinyUSB's API for host.
|
||||
// Let's simplify: assume we read into a global buffer and process it elsewhere.
|
||||
}
|
||||
// This callback structure is more for CDC *Device* mode.
|
||||
// For CDC *Host* mode, we typically poll tud_cdc_n_available() and call tud_cdc_n_read() in a task.
|
||||
// Let's adjust the approach. The main loop will handle reads.
|
||||
}
|
||||
|
||||
|
||||
// Callback invoked when line state change (DTR/RTS)
|
||||
static void tinyusb_cdc_line_state_changed_callback(int itf, cdcacm_event_t *event) {
|
||||
ESP_LOGI(TAG, "TinyUSB CDC ITF %d Line State Changed: DTR: %d, RTS: %d",
|
||||
itf, event->line_state_changed_data.dtr, event->line_state_changed_data.rts);
|
||||
if (event->line_state_changed_data.dtr && event->line_state_changed_data.rts) {
|
||||
// This is often a signal that the serial device is ready on the other side.
|
||||
// However, for Proxmark3, simply being connected is the main indicator.
|
||||
// We can set pm3_connected here if the device is enumerated.
|
||||
if (tud_cdc_n_mounted(itf)) {
|
||||
ESP_LOGI(TAG, "Proxmark3 (CDC ITF %d) is likely connected and ready.", itf);
|
||||
pm3_connected = true;
|
||||
pm3_rx_buffer_len = 0; // Clear buffer on (re)connect
|
||||
memset(pm3_rx_buffer, 0, PM3_RESPONSE_BUFFER_SIZE);
|
||||
send_ws_pm3_status_update();
|
||||
}
|
||||
} else {
|
||||
// pm3_connected = false; // Handle disconnection if DTR/RTS go low after being high
|
||||
// send_ws_pm3_status_update();
|
||||
}
|
||||
}
|
||||
|
||||
// Invoked when a new CDC Host device is mounted
|
||||
void tud_cdc_mount_cb(uint8_t itf) {
|
||||
ESP_LOGI(TAG, "TinyUSB CDC Host ITF %d MOUNTED", itf);
|
||||
// You might want to check VID/PID here if you have multiple CDC devices
|
||||
// For now, assume any CDC device is the Proxmark3
|
||||
// The line_state_changed_callback is often a better place to confirm "readiness"
|
||||
// but we can set a preliminary connected status here.
|
||||
cdc_line_coding_t line_coding;
|
||||
tud_cdc_n_get_line_coding(itf, &line_coding);
|
||||
ESP_LOGI(TAG, "Line Coding: %d bps, %d stop, %d parity, %d data", line_coding.bit_rate, line_coding.stop_bits, line_coding.parity, line_coding.data_bits);
|
||||
|
||||
// Set DTR/RTS to indicate to the device that we are ready
|
||||
tud_cdc_n_set_control_line_state(itf, true, true);
|
||||
ESP_LOGI(TAG, "Set DTR & RTS for ITF %d", itf);
|
||||
|
||||
// At this point, the device is mounted. The line_state_changed_callback will confirm DTR/RTS.
|
||||
// We can consider the PM3 connected here if we don't rely strictly on DTR/RTS from PM3.
|
||||
pm3_connected = true;
|
||||
pm3_rx_buffer_len = 0;
|
||||
memset(pm3_rx_buffer, 0, PM3_RESPONSE_BUFFER_SIZE);
|
||||
send_ws_pm3_status_update();
|
||||
}
|
||||
|
||||
// Invoked when a CDC Host device is unmounted
|
||||
void tud_cdc_umount_cb(uint8_t itf) {
|
||||
ESP_LOGI(TAG, "TinyUSB CDC Host ITF %d UNMOUNTED", itf);
|
||||
pm3_connected = false;
|
||||
pm3_command_in_progress = false;
|
||||
send_ws_pm3_status_update();
|
||||
}
|
||||
|
||||
|
||||
// Task to handle TinyUSB host events and PM3 communication
|
||||
static void tusb_host_task(void *param) {
|
||||
ESP_LOGI(TAG, "TinyUSB Host task started");
|
||||
tusb_init(); // Initialize TinyUSB stack
|
||||
|
||||
while (1) {
|
||||
tuh_task(); // TinyUSB host task runner
|
||||
|
||||
if (pm3_connected && pm3_command_in_progress) {
|
||||
if (tud_cdc_n_available(PM3_CDC_ITF)) {
|
||||
uint32_t count = tud_cdc_n_read(PM3_CDC_ITF,
|
||||
(uint8_t*)pm3_rx_buffer + pm3_rx_buffer_len,
|
||||
PM3_RESPONSE_BUFFER_SIZE - pm3_rx_buffer_len - 1);
|
||||
if (count > 0) {
|
||||
pm3_rx_buffer_len += count;
|
||||
pm3_rx_buffer[pm3_rx_buffer_len] = '\0'; // Null-terminate
|
||||
|
||||
// Send chunk to WebSocket
|
||||
char chunk_json[PM3_RESPONSE_BUFFER_SIZE + 50]; // Extra space for JSON overhead
|
||||
snprintf(chunk_json, sizeof(chunk_json), "{\"type\":\"pm3_response_chunk\", \"chunk\":\"%.*s\"}", (int)count, pm3_rx_buffer + (pm3_rx_buffer_len - count) );
|
||||
|
||||
// Need to escape the chunk before putting into JSON
|
||||
// For simplicity, this is omitted here but is CRITICAL for robust JSON.
|
||||
// The JS side has escapeHtml, but C side needs to escape for JSON string.
|
||||
// Let's assume for now the data is mostly ASCII and doesn't break JSON.
|
||||
// A proper implementation would use a JSON library or manual escaping.
|
||||
// For now, let's send the raw new data.
|
||||
char temp_chunk_buffer[count + 1];
|
||||
memcpy(temp_chunk_buffer, pm3_rx_buffer + (pm3_rx_buffer_len - count), count);
|
||||
temp_chunk_buffer[count] = '\0';
|
||||
|
||||
// Proper escaping for JSON (simplified)
|
||||
char escaped_chunk_buffer[count * 2 + 1]; // Worst case if all chars need escaping
|
||||
int j = 0;
|
||||
for(int i=0; i < count; i++) {
|
||||
char ch = temp_chunk_buffer[i];
|
||||
if (ch == '"' || ch == '\\' || ch == '/') { escaped_chunk_buffer[j++] = '\\'; escaped_chunk_buffer[j++] = ch; }
|
||||
else if (ch == '\b') { escaped_chunk_buffer[j++] = '\\'; escaped_chunk_buffer[j++] = 'b'; }
|
||||
else if (ch == '\f') { escaped_chunk_buffer[j++] = '\\'; escaped_chunk_buffer[j++] = 'f'; }
|
||||
else if (ch == '\n') { escaped_chunk_buffer[j++] = '\\'; escaped_chunk_buffer[j++] = 'n'; }
|
||||
else if (ch == '\r') { escaped_chunk_buffer[j++] = '\\'; escaped_chunk_buffer[j++] = 'r'; }
|
||||
else if (ch == '\t') { escaped_chunk_buffer[j++] = '\\'; escaped_chunk_buffer[j++] = 't'; }
|
||||
else if (ch < 32) { /* skip control chars or use \uXXXX */ }
|
||||
else { escaped_chunk_buffer[j++] = ch; }
|
||||
}
|
||||
escaped_chunk_buffer[j] = '\0';
|
||||
|
||||
snprintf(chunk_json, sizeof(chunk_json), "{\"type\":\"pm3_response_chunk\", \"chunk\":\"%s\"}", escaped_chunk_buffer);
|
||||
send_ws_message_to_all(chunk_json, strlen(chunk_json));
|
||||
|
||||
|
||||
// Check for prompt
|
||||
if (pm3_rx_buffer_len >= strlen(PM3_PROMPT)) {
|
||||
if (strstr(pm3_rx_buffer, PM3_PROMPT) != NULL) {
|
||||
ESP_LOGI(TAG, "PM3 Prompt detected. Command finished.");
|
||||
pm3_command_in_progress = false;
|
||||
pm3_rx_buffer_len = 0; // Clear buffer for next command
|
||||
memset(pm3_rx_buffer, 0, PM3_RESPONSE_BUFFER_SIZE);
|
||||
send_ws_message_to_all("{\"type\":\"pm3_response_end\"}", strlen("{\"type\":\"pm3_response_end\"}"));
|
||||
}
|
||||
}
|
||||
// Buffer full without prompt? Error or very long output.
|
||||
if (pm3_rx_buffer_len >= PM3_RESPONSE_BUFFER_SIZE -1) {
|
||||
ESP_LOGW(TAG, "PM3 RX Buffer full without prompt!");
|
||||
// Send what we have and signal end/error
|
||||
pm3_command_in_progress = false;
|
||||
pm3_rx_buffer_len = 0;
|
||||
memset(pm3_rx_buffer, 0, PM3_RESPONSE_BUFFER_SIZE);
|
||||
send_ws_message_to_all("{\"type\":\"pm3_error\", \"message\":\"RX buffer full\"}", strlen("{\"type\":\"pm3_error\", \"message\":\"RX buffer full\"}"));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Check for timeout if command is in progress
|
||||
// This needs a timer associated with when the command was sent.
|
||||
// For simplicity, this is omitted but important for robustness.
|
||||
}
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(10)); // Yield for other tasks
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// --- HTTP Server Handlers ---
|
||||
static esp_err_t http_get_handler(httpd_req_t *req) {
|
||||
httpd_resp_set_type(req, "text/html");
|
||||
httpd_resp_send(req, index_html_start, HTTPD_RESP_USE_STRLEN);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void send_ws_message_to_all(const char* message, size_t len) {
|
||||
for (size_t i = 0; i < ws_client_count; ++i) {
|
||||
httpd_ws_client_t *client = ws_clients[i];
|
||||
if (client) {
|
||||
httpd_ws_frame_t ws_pkt;
|
||||
memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
|
||||
ws_pkt.payload = (uint8_t*)message;
|
||||
ws_pkt.len = len;
|
||||
ws_pkt.type = HTTPD_WS_TYPE_TEXT;
|
||||
httpd_ws_send_frame_async(client->hd, client->fd, &ws_pkt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void send_ws_pm3_status_update(void) {
|
||||
char status_msg[100];
|
||||
snprintf(status_msg, sizeof(status_msg), "{\"type\":\"pm3_status\", \"connected\":%s}", pm3_connected ? "true" : "false");
|
||||
send_ws_message_to_all(status_msg, strlen(status_msg));
|
||||
}
|
||||
|
||||
|
||||
static esp_err_t ws_handler(httpd_req_t *req) {
|
||||
if (req->method == HTTP_GET) {
|
||||
// Check if this client is already tracked
|
||||
bool client_exists = false;
|
||||
for(size_t i = 0; i < ws_client_count; ++i) {
|
||||
if(ws_clients[i] && ws_clients[i]->fd == req->handle->fd) {
|
||||
client_exists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!client_exists && ws_client_count < (CONFIG_LWIP_MAX_SOCKETS - 4)) {
|
||||
// Find an empty slot or add to the end
|
||||
size_t new_client_idx = ws_client_count;
|
||||
for(size_t i = 0; i < ws_client_count; ++i) { // Reuse slot if any client disconnected
|
||||
if(ws_clients[i] == NULL) {
|
||||
new_client_idx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
httpd_ws_client_t *client = calloc(1, sizeof(httpd_ws_client_t));
|
||||
if (!client) {
|
||||
ESP_LOGE(TAG, "Failed to allocate memory for new WS client");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
client->hd = req->handle;
|
||||
client->fd = httpd_req_to_sockfd(req); // Get socket descriptor
|
||||
|
||||
if (new_client_idx == ws_client_count) { // Adding new client
|
||||
ws_clients[ws_client_count++] = client;
|
||||
} else { // Reusing slot
|
||||
ws_clients[new_client_idx] = client;
|
||||
}
|
||||
ESP_LOGI(TAG, "New WebSocket client connected, fd=%d, total_clients=%d", client->fd, ws_client_count);
|
||||
// Send initial status
|
||||
send_ws_pm3_status_update(); // Send PM3 status
|
||||
// Send WiFi status (already done by wifi_event_handler, but good for new client)
|
||||
char ws_msg[100];
|
||||
if ( (xEventGroupGetBits(s_wifi_event_group) & WIFI_CONNECTED_BIT) ) {
|
||||
esp_netif_ip_info_t ip_info;
|
||||
esp_netif_get_ip_info(esp_netif_get_handle_from_ifkey("WIFI_STA_DEF"), &ip_info);
|
||||
snprintf(ws_msg, sizeof(ws_msg), "{\"type\":\"wifi_status\", \"connected\":true, \"ip\":\"" IPSTR "\"}", IP2STR(&ip_info.ip));
|
||||
} else {
|
||||
snprintf(ws_msg, sizeof(ws_msg), "{\"type\":\"wifi_status\", \"connected\":false, \"ip\":\"N/A\"}");
|
||||
}
|
||||
httpd_ws_frame_t pkt;
|
||||
memset(&pkt, 0, sizeof(httpd_ws_frame_t));
|
||||
pkt.payload = (uint8_t*)ws_msg;
|
||||
pkt.len = strlen(ws_msg);
|
||||
pkt.type = HTTPD_WS_TYPE_TEXT;
|
||||
httpd_ws_send_frame_async(client->hd, client->fd, &pkt);
|
||||
|
||||
return ESP_OK; // Keep connection open for WebSocket
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Max WebSocket clients reached or client already exists.");
|
||||
return ESP_FAIL; // Or send 429 Too Many Requests
|
||||
}
|
||||
}
|
||||
|
||||
// Handle WebSocket data frames
|
||||
uint8_t buf[128];
|
||||
httpd_ws_frame_t ws_pkt;
|
||||
memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
|
||||
ws_pkt.payload = buf;
|
||||
ws_pkt.type = HTTPD_WS_TYPE_TEXT; // Expecting text
|
||||
esp_err_t ret = httpd_ws_recv_frame(req, &ws_pkt, sizeof(buf) -1); // -1 for null terminator
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "httpd_ws_recv_frame failed with %d", ret);
|
||||
// Client disconnected or error
|
||||
for(size_t i = 0; i < ws_client_count; ++i) {
|
||||
if(ws_clients[i] && ws_clients[i]->fd == httpd_req_to_sockfd(req)) {
|
||||
ESP_LOGI(TAG, "WebSocket client disconnected, fd=%d", ws_clients[i]->fd);
|
||||
free(ws_clients[i]);
|
||||
ws_clients[i] = NULL;
|
||||
// To properly manage the array, shift elements or mark as NULL and reuse
|
||||
// For simplicity, just NULLing. A robust solution would compact the array.
|
||||
// If it was the last element, decrement count
|
||||
if (i == ws_client_count -1) ws_client_count--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (ws_pkt.len > 0 && ws_pkt.type == HTTPD_WS_TYPE_TEXT) {
|
||||
buf[ws_pkt.len] = '\0'; // Null-terminate
|
||||
ESP_LOGI(TAG, "Got WS packet with message: %s", buf);
|
||||
|
||||
// Basic JSON parsing: {"type":"pm3_command", "cmd":"hw version"}
|
||||
// Using simple strstr for this example. A JSON library (e.g. cJSON) is recommended for robust parsing.
|
||||
char* type_str = strstr((char*)buf, "\"type\":\"pm3_command\"");
|
||||
if (type_str) {
|
||||
char* cmd_start_str = strstr((char*)buf, "\"cmd\":\"");
|
||||
if (cmd_start_str) {
|
||||
cmd_start_str += strlen("\"cmd\":\""); // Move pointer to start of command
|
||||
char* cmd_end_str = strchr(cmd_start_str, '"');
|
||||
if (cmd_end_str) {
|
||||
int cmd_len = cmd_end_str - cmd_start_str;
|
||||
if (cmd_len > 0 && cmd_len < 100) { // Basic sanity check for command length
|
||||
char command[101];
|
||||
strncpy(command, cmd_start_str, cmd_len);
|
||||
command[cmd_len] = '\0';
|
||||
ESP_LOGI(TAG, "Parsed PM3 command: %s", command);
|
||||
send_pm3_command(command);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (strstr((char*)buf, "\"type\":\"client_hello\"")) {
|
||||
ESP_LOGI(TAG, "Client says hello!");
|
||||
// Already sent status on connect, can send ack if needed
|
||||
}
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
|
||||
static const httpd_uri_t uri_get = {
|
||||
.uri = "/",
|
||||
.method = HTTP_GET,
|
||||
.handler = http_get_handler,
|
||||
.user_ctx = NULL
|
||||
};
|
||||
|
||||
static const httpd_uri_t ws = {
|
||||
.uri = "/ws",
|
||||
.method = HTTP_GET,
|
||||
.handler = ws_handler,
|
||||
.user_ctx = NULL,
|
||||
.is_websocket = true
|
||||
};
|
||||
|
||||
static httpd_handle_t start_webserver(void) {
|
||||
httpd_handle_t server = NULL;
|
||||
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
|
||||
config.max_open_sockets = 7; // Default is 7. Need at least 1 for HTTP, 1 for WS.
|
||||
config.lru_purge_enable = true;
|
||||
|
||||
ESP_LOGI(TAG, "Starting httpd server on port: '%d'", config.server_port);
|
||||
if (httpd_start(&server, &config) == ESP_OK) {
|
||||
ESP_LOGI(TAG, "Registering URI handlers");
|
||||
httpd_register_uri_handler(server, &uri_get);
|
||||
httpd_register_uri_handler(server, &ws);
|
||||
return server;
|
||||
}
|
||||
ESP_LOGI(TAG, "Error starting server!");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// --- Proxmark3 Command Logic ---
|
||||
void send_pm3_command(const char* command) {
|
||||
if (!pm3_connected) {
|
||||
ESP_LOGE(TAG, "Cannot send command: Proxmark3 not connected.");
|
||||
send_ws_message_to_all("{\"type\":\"pm3_error\", \"message\":\"Proxmark3 not connected\"}", strlen("{\"type\":\"pm3_error\", \"message\":\"Proxmark3 not connected\"}"));
|
||||
return;
|
||||
}
|
||||
if (pm3_command_in_progress) {
|
||||
ESP_LOGW(TAG, "Cannot send command: Another command is already in progress.");
|
||||
send_ws_message_to_all("{\"type\":\"pm3_error\", \"message\":\"Command already in progress\"}", strlen("{\"type\":\"pm3_error\", \"message\":\"Command already in progress\"}"));
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Sending to PM3: %s", command);
|
||||
pm3_command_in_progress = true;
|
||||
pm3_rx_buffer_len = 0; // Clear RX buffer
|
||||
memset(pm3_rx_buffer, 0, PM3_RESPONSE_BUFFER_SIZE);
|
||||
|
||||
send_ws_message_to_all("{\"type\":\"pm3_response_start\"}", strlen("{\"type\":\"pm3_response_start\"}"));
|
||||
|
||||
// Ensure command ends with newline for Proxmark3
|
||||
char full_command[128];
|
||||
snprintf(full_command, sizeof(full_command), "%s\n", command);
|
||||
|
||||
if (tud_cdc_n_connected(PM3_CDC_ITF)) {
|
||||
tud_cdc_n_write_str(PM3_CDC_ITF, full_command);
|
||||
tud_cdc_n_write_flush(PM3_CDC_ITF);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "PM3 CDC not connected for writing.");
|
||||
pm3_command_in_progress = false; // Reset flag
|
||||
send_ws_message_to_all("{\"type\":\"pm3_error\", \"message\":\"Proxmark3 CDC write error\"}", strlen("{\"type\":\"pm3_error\", \"message\":\"Proxmark3 CDC write error\"}"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// --- Main Application ---
|
||||
void app_main(void) {
|
||||
esp_err_t ret = nvs_flash_init();
|
||||
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
ret = nvs_flash_init();
|
||||
}
|
||||
ESP_ERROR_CHECK(ret);
|
||||
|
||||
ESP_LOGI(TAG, "ESP_WIFI_MODE_STA");
|
||||
wifi_init_sta();
|
||||
|
||||
// Initialize TinyUSB stack for Host mode
|
||||
// This is now done in the tusb_host_task
|
||||
// tusb_init();
|
||||
// ESP_LOGI(TAG, "TinyUSB tud_init() done.");
|
||||
|
||||
// Create a task for TinyUSB host stack and PM3 comms
|
||||
// Stack size for TinyUSB host might need adjustment.
|
||||
// USB operations, especially with potential string processing, can be stack intensive.
|
||||
// Let's increase it from the default.
|
||||
xTaskCreate(tusb_host_task, "tusb_host_task", 4096 * 2, NULL, 5, NULL);
|
||||
ESP_LOGI(TAG, "tusb_host_task created.");
|
||||
|
||||
// Start Webserver
|
||||
start_webserver();
|
||||
|
||||
ESP_LOGI(TAG, "Proxarch ESP-IDF Initialized.");
|
||||
}
|
||||
Reference in New Issue
Block a user