Tijdelijke checkin, issues met opslaan van gamepad sensitivty

This commit is contained in:
koenieeee
2026-02-15 23:41:53 +01:00
parent cd5c4f4a59
commit e0ce5db2de
4 changed files with 493 additions and 145 deletions
+3 -3
View File
@@ -48,7 +48,7 @@ public:
void setMouseSensitivity(float sensitivity);
void setGamepadSensitivityValue(float sensitivity);
void setGamepadDeadzoneValue(float deadzone);
void setGamepadInvertY(bool invert) { settings.gamepad_invert_y = invert; }
void setGamepadInvertY(bool invert);
// Keyboard functions (spell to key mapping)
void sendKeyPress(uint8_t keycode, uint8_t modifiers = 0);
@@ -97,7 +97,7 @@ public:
const uint8_t *getSpellKeycodes() const { return settings.spell_keycodes; }
const uint8_t *getSpellGamepadButtons() const { return settings.spell_gamepad_buttons; }
bool getInvertMouseY() const { return settings.invert_mouse_y; }
void setInvertMouseY(bool invert) { settings.invert_mouse_y = invert; }
void setInvertMouseY(bool invert);
private:
bool initialized;
@@ -120,6 +120,6 @@ private:
// Helper functions
void sendMouseReport(int8_t x, int8_t y, int8_t wheel, uint8_t buttons);
void sendKeyboardReport(uint8_t modifiers, uint8_t keycode);
void sendGamepadReport(int8_t lx, int8_t ly, int8_t rx, int8_t ry, uint16_t buttons, uint8_t hat);
void sendGamepadReport(int8_t lx, int8_t ly, int8_t rx, int8_t ry, uint8_t lt, uint8_t rt, uint16_t buttons, uint8_t hat);
uint8_t getKeycodeForSpell(const char *spell_name);
};
+58 -4
View File
@@ -1054,15 +1054,48 @@ void WandBLEClient::updateAHRS(const IMUSample &sample)
{
float dx = pos.x - last_mouse_pos.x;
float dy = pos.y - last_mouse_pos.y;
float original_dy = dy; // Store original for logging
#if USE_USB_HID_DEVICE
HIDMode current_mode = usbHID.getHidMode();
if (current_mode == HID_MODE_MOUSE)
{
dy = usbHID.getInvertMouseY() ? -dy : dy;
bool invert = usbHID.getInvertMouseY();
dy = invert ? -dy : dy;
// Log occasionally to debug axis inversion (every 100 samples)
static int debug_counter = 0;
if (++debug_counter >= 100)
{
debug_counter = 0;
ESP_LOGI(TAG, "🖱️ MOUSE mode | Y: original=%.3f, invert=%s, final=%.3f (up wand should give original=%s)",
original_dy, invert ? "INVERTED" : "NORMAL", dy,
original_dy > 0 ? "POSITIVE" : original_dy < 0 ? "NEGATIVE"
: "ZERO");
}
}
else if (current_mode == HID_MODE_GAMEPAD)
{
dy = usbHID.getGamepadInvertY() ? -dy : dy;
bool invert = usbHID.getGamepadInvertY();
dy = invert ? -dy : dy;
// Log occasionally to debug axis inversion (every 100 samples)
static int gpad_debug_counter = 0;
if (++gpad_debug_counter >= 100)
{
gpad_debug_counter = 0;
ESP_LOGI(TAG, "🎮 GAMEPAD mode | Y: original=%.3f, invert=%s, final=%.3f",
original_dy, invert ? "INVERTED" : "NORMAL", dy);
}
}
else
{
// KEYBOARD or DISABLED mode - still process for WebSocket
static int other_debug_counter = 0;
if (++other_debug_counter >= 100)
{
other_debug_counter = 0;
ESP_LOGI(TAG, "⌨️ OTHER mode (%d) | No axis inversion applied", current_mode);
}
}
#else
dy = -dy; // Default: inverted
@@ -1113,15 +1146,36 @@ void WandBLEClient::updateAHRS(const IMUSample &sample)
{
float dx = pos.x - last_mouse_pos.x;
float dy = pos.y - last_mouse_pos.y;
float original_dy = dy; // Store original for logging
#if USE_USB_HID_DEVICE
HIDMode current_mode = usbHID.getHidMode();
if (current_mode == HID_MODE_MOUSE)
{
dy = usbHID.getInvertMouseY() ? -dy : dy;
bool invert = usbHID.getInvertMouseY();
dy = invert ? -dy : dy;
// Log occasionally to debug axis inversion (every 200 samples for websocket path)
static int ws_debug_counter = 0;
if (++ws_debug_counter >= 200)
{
ws_debug_counter = 0;
ESP_LOGI(TAG, "🖱️ Mouse Y (WS): original=%.3f, invert=%s, final=%.3f",
original_dy, invert ? "true" : "false", dy);
}
}
else if (current_mode == HID_MODE_GAMEPAD)
{
dy = usbHID.getGamepadInvertY() ? -dy : dy;
bool invert = usbHID.getGamepadInvertY();
dy = invert ? -dy : dy;
// Log occasionally to debug axis inversion (every 200 samples for websocket path)
static int ws_gpad_debug_counter = 0;
if (++ws_gpad_debug_counter >= 200)
{
ws_gpad_debug_counter = 0;
ESP_LOGI(TAG, "🎮 Gamepad Y (WS): original=%.3f, invert=%s, final=%.3f",
original_dy, invert ? "true" : "false", dy);
}
}
#else
dy = -dy; // Default: inverted
+255 -90
View File
@@ -76,32 +76,57 @@ static const uint8_t hid_report_descriptor[] = {
0x81, 0x00, // Input (Data, Array) - Key array
0xC0, // End Collection (Application)
// Gamepad Report (Report ID 3)
0x05, 0x01, // Usage Page (Generic Desktop)
0x09, 0x05, // Usage (Gamepad)
0xA1, 0x01, // Collection (Application)
0x85, 0x03, // Report ID (3)
0x05, 0x01, // Usage Page (Generic Desktop)
0x09, 0x30, // Usage (X)
0x09, 0x31, // Usage (Y)
0x09, 0x33, // Usage (Rx)
0x09, 0x34, // Usage (Ry)
0x15, 0x81, // Logical Minimum (-127)
0x25, 0x7F, // Logical Maximum (127)
0x75, 0x08, // Report Size (8)
0x95, 0x04, // Report Count (4)
0x81, 0x02, // Input (Data, Variable, Absolute)
0x05, 0x09, // Usage Page (Buttons)
0x19, 0x01, // Usage Minimum (Button 1)
0x29, 0x0A, // Usage Maximum (Button 10)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1)
0x75, 0x01, // Report Size (1)
0x95, 0x0A, // Report Count (10)
0x81, 0x02, // Input (Data, Variable, Absolute)
0x75, 0x01, // Report Size (1)
0x95, 0x06, // Report Count (6)
0x81, 0x01, // Input (Constant) - padding
// Gamepad Report (Report ID 3) - Full Xbox-Compatible Controller
// Compatible with games like Hogwarts Legacy that require complete gamepad support
0x05, 0x01, // Usage Page (Generic Desktop)
0x09, 0x05, // Usage (Gamepad)
0xA1, 0x01, // Collection (Application)
0x85, 0x03, // Report ID (3)
// Left Stick (X, Y)
0x05, 0x01, // Usage Page (Generic Desktop)
0x09, 0x30, // Usage (X)
0x09, 0x31, // Usage (Y)
0x15, 0x81, // Logical Minimum (-127)
0x25, 0x7F, // Logical Maximum (127)
0x75, 0x08, // Report Size (8)
0x95, 0x02, // Report Count (2)
0x81, 0x02, // Input (Data, Variable, Absolute)
// Right Stick (Rx, Ry)
0x09, 0x33, // Usage (Rx)
0x09, 0x34, // Usage (Ry)
0x15, 0x81, // Logical Minimum (-127)
0x25, 0x7F, // Logical Maximum (127)
0x75, 0x08, // Report Size (8)
0x95, 0x02, // Report Count (2)
0x81, 0x02, // Input (Data, Variable, Absolute)
// Triggers (LT, RT) - Z axis
0x09, 0x32, // Usage (Z) - Left Trigger
0x09, 0x35, // Usage (Rz) - Right Trigger
0x15, 0x00, // Logical Minimum (0)
0x25, 0xFF, // Logical Maximum (255)
0x75, 0x08, // Report Size (8)
0x95, 0x02, // Report Count (2)
0x81, 0x02, // Input (Data, Variable, Absolute)
// Buttons (14 buttons: A, B, X, Y, LB, RB, Back, Start, LS, RS, + 4 extra)
0x05, 0x09, // Usage Page (Buttons)
0x19, 0x01, // Usage Minimum (Button 1)
0x29, 0x0E, // Usage Maximum (Button 14)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1)
0x75, 0x01, // Report Size (1)
0x95, 0x0E, // Report Count (14)
0x81, 0x02, // Input (Data, Variable, Absolute)
// Padding (2 bits to make 16 bits = 2 bytes)
0x75, 0x01, // Report Size (1)
0x95, 0x02, // Report Count (2)
0x81, 0x01, // Input (Constant)
// D-Pad (Hat Switch)
0x05, 0x01, // Usage Page (Generic Desktop)
0x09, 0x39, // Usage (Hat switch)
0x15, 0x00, // Logical Minimum (0)
@@ -112,10 +137,13 @@ static const uint8_t hid_report_descriptor[] = {
0x75, 0x04, // Report Size (4)
0x95, 0x01, // Report Count (1)
0x81, 0x42, // Input (Data, Variable, Absolute, Null State)
0x75, 0x04, // Report Size (4)
0x95, 0x01, // Report Count (1)
0x81, 0x01, // Input (Constant) - padding
0xC0 // End Collection (Application)
// Padding (4 bits to complete the byte)
0x75, 0x04, // Report Size (4)
0x95, 0x01, // Report Count (1)
0x81, 0x01, // Input (Constant)
0xC0 // End Collection (Application)
};
// USB Descriptors for Composite HID + CDC
@@ -331,13 +359,13 @@ USBHIDManager::USBHIDManager()
{
// Initialize default settings
settings.mouse_sensitivity = 1.0f;
settings.invert_mouse_y = true; // Default: inverted (typical UI behavior)
settings.invert_mouse_y = false; // Default: natural (wand UP = cursor UP, since IMU gives negative for up)
settings.mouse_enabled = true; // Default: enabled
settings.keyboard_enabled = true; // Default: enabled
settings.hid_mode = HID_MODE_MOUSE;
settings.gamepad_sensitivity = 1.0f;
settings.gamepad_deadzone = 0.05f;
settings.gamepad_invert_y = true;
settings.gamepad_invert_y = false; // Default: natural (wand UP = stick UP)
// Initialize all spell keycodes to 0 (disabled)
memset(settings.spell_keycodes, 0, sizeof(settings.spell_keycodes));
memset(settings.spell_gamepad_buttons, 0, sizeof(settings.spell_gamepad_buttons));
@@ -387,6 +415,16 @@ bool USBHIDManager::begin()
#endif
initialized = true;
// Log final loaded settings for verification
ESP_LOGI(TAG, "📋 Final settings after initialization:");
ESP_LOGI(TAG, " Mouse: sensitivity=%.2f, invert_y=%s",
settings.mouse_sensitivity, settings.invert_mouse_y ? "true" : "false");
ESP_LOGI(TAG, " Gamepad: sensitivity=%.2f, deadzone=%.2f, invert_y=%s",
settings.gamepad_sensitivity, settings.gamepad_deadzone, settings.gamepad_invert_y ? "true" : "false");
ESP_LOGI(TAG, " HID mode=%u, mouse_enabled=%d, keyboard_enabled=%d",
settings.hid_mode, settings.mouse_enabled, settings.keyboard_enabled);
ESP_LOGI(TAG, "USB HID initialized successfully");
return true;
#else
@@ -460,11 +498,8 @@ void USBHIDManager::updateGamepadFromGesture(float delta_x, float delta_y)
float scale = settings.gamepad_sensitivity;
// Apply invert Y-axis setting
if (settings.gamepad_invert_y)
{
delta_y = -delta_y;
}
// Note: Inversion is already applied in ble_client.cpp before calling this function
// Do NOT apply inversion here again to avoid double-inversion
int16_t temp_x = (int16_t)(delta_x * scale);
int16_t temp_y = (int16_t)(delta_y * scale);
@@ -484,19 +519,19 @@ void USBHIDManager::updateGamepadFromGesture(float delta_x, float delta_y)
if (gamepad_ly > -deadzone && gamepad_ly < deadzone)
gamepad_ly = 0;
// Use left stick only; right stick centered, hat neutral.
sendGamepadReport(gamepad_lx, gamepad_ly, gamepad_rx, gamepad_ry, gamepad_buttons, 0x0F);
// Use left stick only; right stick centered, triggers off, hat neutral.
sendGamepadReport(gamepad_lx, gamepad_ly, gamepad_rx, gamepad_ry, 0, 0, gamepad_buttons, 0x0F);
#endif
}
void USBHIDManager::setGamepadButtons(uint16_t buttons)
{
gamepad_buttons = buttons & 0x03FF; // 10 buttons max
gamepad_buttons = buttons & 0x3FFF; // 14 buttons max
#if USE_USB_HID_DEVICE
if (initialized && getHidMode() == HID_MODE_GAMEPAD)
{
sendGamepadReport(gamepad_lx, gamepad_ly, gamepad_rx, gamepad_ry, gamepad_buttons, 0x0F);
sendGamepadReport(gamepad_lx, gamepad_ly, gamepad_rx, gamepad_ry, 0, 0, gamepad_buttons, 0x0F);
}
#endif
}
@@ -526,8 +561,13 @@ void USBHIDManager::setMouseSensitivity(float sensitivity)
void USBHIDManager::sendKeyPress(uint8_t keycode, uint8_t modifiers)
{
#if USE_USB_HID_DEVICE
if (!initialized || !keyboard_enabled || getHidMode() != HID_MODE_KEYBOARD)
// Allow keyboard input in both MOUSE and KEYBOARD modes (for spell hotkeys + mouse movement)
if (!initialized || !keyboard_enabled || (getHidMode() != HID_MODE_KEYBOARD && getHidMode() != HID_MODE_MOUSE))
{
ESP_LOGW(TAG, "⚠️ sendKeyPress blocked: init=%d, kbd_en=%d, mode=%d",
initialized, keyboard_enabled, getHidMode());
return;
}
sendKeyboardReport(modifiers, keycode);
#endif
@@ -536,7 +576,8 @@ void USBHIDManager::sendKeyPress(uint8_t keycode, uint8_t modifiers)
void USBHIDManager::sendKeyRelease()
{
#if USE_USB_HID_DEVICE
if (!initialized || !keyboard_enabled || getHidMode() != HID_MODE_KEYBOARD)
// Allow keyboard input in both MOUSE and KEYBOARD modes (for spell hotkeys + mouse movement)
if (!initialized || !keyboard_enabled || (getHidMode() != HID_MODE_KEYBOARD && getHidMode() != HID_MODE_MOUSE))
return;
sendKeyboardReport(0, 0);
@@ -627,7 +668,7 @@ void USBHIDManager::setHidMode(HIDMode mode)
{
case HID_MODE_MOUSE:
mouse_enabled = true;
keyboard_enabled = false;
keyboard_enabled = true; // Enable keyboard for spell hotkeys in mouse mode
break;
case HID_MODE_KEYBOARD:
mouse_enabled = false;
@@ -645,7 +686,7 @@ void USBHIDManager::setHidMode(HIDMode mode)
}
settings.mouse_enabled = mouse_enabled;
settings.keyboard_enabled = keyboard_enabled;
ESP_LOGI(TAG, "HID mode set to %u", settings.hid_mode);
ESP_LOGI(TAG, "HID mode set to %u (mouse=%d, keyboard=%d)", settings.hid_mode, mouse_enabled, keyboard_enabled);
}
void USBHIDManager::sendMouseReport(int8_t x, int8_t y, int8_t wheel, uint8_t buttons)
@@ -664,20 +705,24 @@ void USBHIDManager::sendMouseReport(int8_t x, int8_t y, int8_t wheel, uint8_t bu
#endif
}
void USBHIDManager::sendGamepadReport(int8_t lx, int8_t ly, int8_t rx, int8_t ry, uint16_t buttons, uint8_t hat)
void USBHIDManager::sendGamepadReport(int8_t lx, int8_t ly, int8_t rx, int8_t ry, uint8_t lt, uint8_t rt, uint16_t buttons, uint8_t hat)
{
#if USE_USB_HID_DEVICE
if (!tud_hid_ready())
return;
uint8_t report[7];
report[0] = (uint8_t)lx;
report[1] = (uint8_t)ly;
report[2] = (uint8_t)rx;
report[3] = (uint8_t)ry;
report[4] = (uint8_t)(buttons & 0xFF);
report[5] = (uint8_t)((buttons >> 8) & 0x03);
report[6] = (uint8_t)(hat & 0x0F);
// Full Xbox-compatible gamepad: 9 bytes total (per HID descriptor)
// Left stick (2), right stick (2), triggers (2), buttons (2), hat (1)
uint8_t report[9];
report[0] = (uint8_t)lx; // Left stick X
report[1] = (uint8_t)ly; // Left stick Y
report[2] = (uint8_t)rx; // Right stick X
report[3] = (uint8_t)ry; // Right stick Y
report[4] = lt; // Left trigger (Z axis)
report[5] = rt; // Right trigger (Rz axis)
report[6] = (uint8_t)(buttons & 0xFF); // Buttons 1-8
report[7] = (uint8_t)((buttons >> 8) & 0x3F); // Buttons 9-14 (6 bits) + 2 bits padding
report[8] = (uint8_t)(hat & 0x0F); // D-pad hat (4 bits) + 4 bits padding
tud_hid_report(3, report, sizeof(report)); // Report ID 3 = Gamepad
#endif
@@ -796,6 +841,7 @@ uint8_t USBHIDManager::getSpellKeycode(const char *spell_name) const
bool USBHIDManager::loadSettings()
{
ESP_LOGI(TAG, "📂 Loading settings from NVS...");
nvs_handle_t nvs_handle;
esp_err_t err = nvs_open("usb_hid", NVS_READONLY, &nvs_handle);
if (err != ESP_OK)
@@ -810,10 +856,12 @@ bool USBHIDManager::loadSettings()
if (err == ESP_OK)
{
settings.mouse_sensitivity = (float)sens_10x / 10.0f;
ESP_LOGI(TAG, "✓ Loaded mouse_sensitivity from NVS: %.2f (raw: %d)", settings.mouse_sensitivity, sens_10x);
}
else
{
settings.mouse_sensitivity = 1.0f;
ESP_LOGI(TAG, "⚠ Mouse sensitivity not found in NVS, using default: 1.0");
}
// Load gamepad sensitivity (stored as uint8_t: value * 10)
@@ -822,10 +870,12 @@ bool USBHIDManager::loadSettings()
if (err == ESP_OK)
{
settings.gamepad_sensitivity = (float)gpad_sens_10x / 10.0f;
ESP_LOGI(TAG, "✓ Loaded gamepad_sensitivity from NVS: %.2f (raw: %d)", settings.gamepad_sensitivity, gpad_sens_10x);
}
else
{
settings.gamepad_sensitivity = 1.0f;
ESP_LOGI(TAG, "⚠ Gamepad sensitivity not found in NVS, using default: 1.0");
}
// Load gamepad deadzone (stored as uint8_t: value * 100)
@@ -834,21 +884,46 @@ bool USBHIDManager::loadSettings()
if (err == ESP_OK)
{
settings.gamepad_deadzone = (float)gpad_deadzone_100 / 100.0f;
ESP_LOGI(TAG, "✓ Loaded gamepad_deadzone from NVS: %.2f (raw: %d)", settings.gamepad_deadzone, gpad_deadzone_100);
}
else
{
settings.gamepad_deadzone = 0.05f;
ESP_LOGI(TAG, "⚠ Gamepad deadzone not found in NVS, using default: 0.05");
}
// Load gamepad invert_y
uint8_t gpad_invert_y = 1; // Default: inverted
uint8_t gpad_invert_y = 0; // Default: natural (non-inverted)
err = nvs_get_u8(nvs_handle, "gamepad_invert_y", &gpad_invert_y);
settings.gamepad_invert_y = (gpad_invert_y != 0);
if (err == ESP_OK)
{
settings.gamepad_invert_y = (gpad_invert_y != 0);
ESP_LOGI(TAG, "✓ Loaded gamepad_invert_y from NVS: %s", settings.gamepad_invert_y ? "true" : "false");
}
else
{
settings.gamepad_invert_y = false;
ESP_LOGI(TAG, "⚠ Gamepad invert_y not found in NVS, using default: false (natural)");
}
// Load invert_mouse_y setting
uint8_t invert_y = 1; // Default: inverted (typical UI behavior)
uint8_t invert_y = 0; // Default: natural (non-inverted)
err = nvs_get_u8(nvs_handle, "invert_mouse_y", &invert_y);
settings.invert_mouse_y = (invert_y != 0);
if (err == ESP_OK)
{
settings.invert_mouse_y = (invert_y != 0);
ESP_LOGI(TAG, "✓ Loaded invert_mouse_y from NVS: %s", settings.invert_mouse_y ? "true" : "false");
}
else
{
settings.invert_mouse_y = false;
ESP_LOGI(TAG, "⚠ Invert_mouse_y not found in NVS, using default: false (natural)");
}
// Log current state for debugging
ESP_LOGI(TAG, "🎯 Current axis inversion settings: mouse_y=%s, gamepad_y=%s",
settings.invert_mouse_y ? "INVERTED" : "NORMAL",
settings.gamepad_invert_y ? "INVERTED" : "NORMAL");
// Load mouse_enabled
uint8_t mouse_en = 1; // Default: enabled
@@ -885,30 +960,56 @@ bool USBHIDManager::loadSettings()
setHidMode(static_cast<HIDMode>(hid_mode));
// Load spell keycodes (73 spells)
ESP_LOGI(TAG, "Loading spell keycodes from NVS...");
int non_zero_count = 0;
for (int i = 0; i < 73; i++)
{
char key[16];
snprintf(key, sizeof(key), "spell%d", i);
uint8_t old_value = settings.spell_keycodes[i];
nvs_get_u8(nvs_handle, key, &settings.spell_keycodes[i]);
// If not found, spell_keycodes[i] remains 0 (disabled)
}
// Load spell gamepad button mappings (73 spells)
for (int i = 0; i < 73; i++)
{
char key[20];
snprintf(key, sizeof(key), "gpad_spell%d", i);
nvs_get_u8(nvs_handle, key, &settings.spell_gamepad_buttons[i]);
// If not found, spell_gamepad_buttons[i] remains 0 (disabled)
if (settings.spell_keycodes[i] != 0)
{
non_zero_count++;
if (settings.spell_keycodes[i] != old_value)
{
extern const char *SPELL_NAMES[73];
ESP_LOGI(TAG, " Spell[%d]='%s' loaded: 0x%02X (%d)",
i, SPELL_NAMES[i], settings.spell_keycodes[i], settings.spell_keycodes[i]);
}
}
}
ESP_LOGI(TAG, "Loaded %d non-zero spell mappings", non_zero_count);
nvs_close(nvs_handle);
// Open gamepad namespace for gamepad spell mappings
err = nvs_open("gamepad", NVS_READONLY, &nvs_handle);
if (err == ESP_OK)
{
// Load spell gamepad button mappings (73 spells)
for (int i = 0; i < 73; i++)
{
char key[20];
snprintf(key, sizeof(key), "gpad_spell%d", i);
nvs_get_u8(nvs_handle, key, &settings.spell_gamepad_buttons[i]);
// If not found, spell_gamepad_buttons[i] remains 0 (disabled)
}
nvs_close(nvs_handle);
}
ESP_LOGI(TAG, "USB HID settings loaded from NVS");
return true;
}
bool USBHIDManager::saveSettings()
{
ESP_LOGI(TAG, "🔍 saveSettings() called - checking current settings struct values:");
ESP_LOGI(TAG, " gamepad_sensitivity=%.2f, gamepad_deadzone=%.2f, gamepad_invert_y=%s",
settings.gamepad_sensitivity, settings.gamepad_deadzone, settings.gamepad_invert_y ? "true" : "false");
ESP_LOGI(TAG, " mouse_sensitivity=%.2f, invert_mouse_y=%s",
settings.mouse_sensitivity, settings.invert_mouse_y ? "true" : "false");
nvs_handle_t nvs_handle;
esp_err_t err = nvs_open("usb_hid", NVS_READWRITE, &nvs_handle);
if (err != ESP_OK)
@@ -921,19 +1022,9 @@ bool USBHIDManager::saveSettings()
uint8_t sens_10x = (uint8_t)(settings.mouse_sensitivity * 10.0f);
nvs_set_u8(nvs_handle, "mouse_sens_10x", sens_10x);
// Save gamepad sensitivity (as 10x value to store as uint8)
uint8_t gpad_sens_10x = (uint8_t)(settings.gamepad_sensitivity * 10.0f);
nvs_set_u8(nvs_handle, "gamepad_sens_10x", gpad_sens_10x);
// Save gamepad deadzone (as 100x value to store as uint8)
uint8_t gpad_deadzone_100 = (uint8_t)(settings.gamepad_deadzone * 100.0f);
nvs_set_u8(nvs_handle, "gamepad_deadzone_100", gpad_deadzone_100);
// Save gamepad invert_y
nvs_set_u8(nvs_handle, "gamepad_invert_y", settings.gamepad_invert_y ? 1 : 0);
// Save invert_mouse_y setting
nvs_set_u8(nvs_handle, "invert_mouse_y", settings.invert_mouse_y ? 1 : 0);
ESP_LOGI(TAG, "💾 Saved invert_mouse_y to NVS: %s", settings.invert_mouse_y ? "true" : "false");
// Save mouse_enabled
nvs_set_u8(nvs_handle, "mouse_enabled", settings.mouse_enabled ? 1 : 0);
@@ -945,14 +1036,58 @@ bool USBHIDManager::saveSettings()
nvs_set_u8(nvs_handle, "hid_mode", settings.hid_mode);
// Save spell keycodes (73 spells)
ESP_LOGI(TAG, "Saving spell keycodes to NVS...");
int saved_count = 0;
for (int i = 0; i < 73; i++)
{
char key[16];
if (settings.spell_keycodes[i] != 0)
{
extern const char *SPELL_NAMES[73];
ESP_LOGI(TAG, " Saving spell[%d]='%s' = 0x%02X (%d)",
i, SPELL_NAMES[i], settings.spell_keycodes[i], settings.spell_keycodes[i]);
saved_count++;
}
snprintf(key, sizeof(key), "spell%d", i);
nvs_set_u8(nvs_handle, key, settings.spell_keycodes[i]);
}
ESP_LOGI(TAG, "Saved %d non-zero spell mappings to NVS", saved_count);
// Commit usb_hid namespace
err = nvs_commit(nvs_handle);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to commit usb_hid NVS settings");
nvs_close(nvs_handle);
return false;
}
nvs_close(nvs_handle);
ESP_LOGI(TAG, "✅ Saved usb_hid settings to NVS");
// Open gamepad namespace for gamepad-specific settings
err = nvs_open("gamepad", NVS_READWRITE, &nvs_handle);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to open NVS namespace 'gamepad'");
return false;
}
// Save gamepad sensitivity (as 10x value to store as uint8)
uint8_t gpad_sens_10x = (uint8_t)(settings.gamepad_sensitivity * 10.0f);
nvs_set_u8(nvs_handle, "gamepad_sens_10x", gpad_sens_10x);
ESP_LOGI(TAG, "💾 Saved gamepad_sensitivity to NVS: %.2f (raw: %d)", settings.gamepad_sensitivity, gpad_sens_10x);
// Save gamepad deadzone (as 100x value to store as uint8)
uint8_t gpad_deadzone_100 = (uint8_t)(settings.gamepad_deadzone * 100.0f);
nvs_set_u8(nvs_handle, "gamepad_deadzone_100", gpad_deadzone_100);
ESP_LOGI(TAG, "💾 Saved gamepad_deadzone to NVS: %.2f (raw: %d)", settings.gamepad_deadzone, gpad_deadzone_100);
// Save gamepad invert_y
nvs_set_u8(nvs_handle, "gamepad_invert_y", settings.gamepad_invert_y ? 1 : 0);
ESP_LOGI(TAG, "💾 Saved gamepad_invert_y to NVS: %s", settings.gamepad_invert_y ? "true" : "false");
// Save spell gamepad button mappings (73 spells)
ESP_LOGI(TAG, "Saving gamepad spell button mappings to NVS...");
for (int i = 0; i < 73; i++)
{
char key[20];
@@ -960,16 +1095,28 @@ bool USBHIDManager::saveSettings()
nvs_set_u8(nvs_handle, key, settings.spell_gamepad_buttons[i]);
}
// Commit gamepad namespace
err = nvs_commit(nvs_handle);
nvs_close(nvs_handle);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to commit NVS settings");
ESP_LOGE(TAG, "Failed to commit gamepad NVS settings");
nvs_close(nvs_handle);
return false;
}
ESP_LOGI(TAG, "USB HID settings saved to NVS");
// Verify gamepad values were actually written
uint8_t verify_gpad_sens = 0;
uint8_t verify_gpad_deadzone = 0;
uint8_t verify_gpad_invert = 0;
nvs_get_u8(nvs_handle, "gamepad_sens_10x", &verify_gpad_sens);
nvs_get_u8(nvs_handle, "gamepad_deadzone_100", &verify_gpad_deadzone);
nvs_get_u8(nvs_handle, "gamepad_invert_y", &verify_gpad_invert);
ESP_LOGI(TAG, "✅ Verified gamepad NVS write: sens=%d (%.1fx), deadzone=%d (%.2f), invert=%d",
verify_gpad_sens, verify_gpad_sens / 10.0f, verify_gpad_deadzone, verify_gpad_deadzone / 100.0f, verify_gpad_invert);
nvs_close(nvs_handle);
ESP_LOGI(TAG, "✅ All USB HID settings saved to NVS");
return true;
}
@@ -977,11 +1124,15 @@ bool USBHIDManager::resetSettings()
{
// Reset to defaults
settings.mouse_sensitivity = 1.0f;
settings.invert_mouse_y = true; // Default: inverted (typical UI behavior)
settings.invert_mouse_y = false; // Default: natural (wand UP = cursor UP)
settings.mouse_enabled = true; // Default: enabled
settings.keyboard_enabled = true; // Default: enabled
settings.hid_mode = HID_MODE_MOUSE;
settings.gamepad_sensitivity = 1.0f;
settings.gamepad_deadzone = 0.05f;
settings.gamepad_invert_y = false; // Default: natural (wand UP = stick UP)
memset(settings.spell_keycodes, 0, sizeof(settings.spell_keycodes));
memset(settings.spell_gamepad_buttons, 0, sizeof(settings.spell_gamepad_buttons));
mouse_sensitivity = 1.0f;
setHidMode(HID_MODE_MOUSE);
@@ -1025,7 +1176,7 @@ void USBHIDManager::setGamepadSensitivityValue(float sensitivity)
sensitivity = 5.0f;
settings.gamepad_sensitivity = sensitivity;
ESP_LOGI(TAG, "Gamepad sensitivity set to %.2f", sensitivity);
ESP_LOGI(TAG, "🎮 Gamepad sensitivity set to %.2f (will be saved on settings save)", sensitivity);
}
void USBHIDManager::setGamepadDeadzoneValue(float deadzone)
@@ -1039,6 +1190,20 @@ void USBHIDManager::setGamepadDeadzoneValue(float deadzone)
ESP_LOGI(TAG, "Gamepad dead zone set to %.2f", deadzone);
}
void USBHIDManager::setInvertMouseY(bool invert)
{
settings.invert_mouse_y = invert;
ESP_LOGI(TAG, "🔄 Mouse Y-axis invert set to: %s (wand UP -> cursor %s)",
invert ? "true (INVERTED)" : "false (NORMAL)",
invert ? "DOWN" : "UP");
}
void USBHIDManager::setGamepadInvertY(bool invert)
{
settings.gamepad_invert_y = invert;
ESP_LOGI(TAG, "🔄 Gamepad Y-axis invert set to: %s", invert ? "true (INVERTED)" : "false (NORMAL)");
}
void USBHIDManager::setSpellGamepadButton(const char *spell_name, uint8_t button)
{
if (!spell_name)
@@ -1083,16 +1248,16 @@ void USBHIDManager::sendSpellGamepadForSpell(const char *spell_name)
return;
uint8_t button = getSpellGamepadButton(spell_name);
if (button == 0 || button > 10)
if (button == 0 || button > 14)
return;
uint16_t mask = (uint16_t)(1U << (button - 1));
uint16_t previous = gamepad_buttons;
gamepad_buttons = (previous | mask) & 0x03FF;
sendGamepadReport(gamepad_lx, gamepad_ly, gamepad_rx, gamepad_ry, gamepad_buttons, 0x0F);
gamepad_buttons = (previous | mask) & 0x3FFF;
sendGamepadReport(gamepad_lx, gamepad_ly, gamepad_rx, gamepad_ry, 0, 0, gamepad_buttons, 0x0F);
vTaskDelay(pdMS_TO_TICKS(50));
gamepad_buttons = previous & 0x03FF;
sendGamepadReport(gamepad_lx, gamepad_ly, gamepad_rx, gamepad_ry, gamepad_buttons, 0x0F);
gamepad_buttons = previous & 0x3FFF;
sendGamepadReport(gamepad_lx, gamepad_ly, gamepad_rx, gamepad_ry, 0, 0, gamepad_buttons, 0x0F);
#endif
}
+177 -48
View File
@@ -454,6 +454,16 @@ static const char index_html[] = R"rawliteral(
<h3> Spell & Mouse Settings</h3>
<div class="settings-grid">
<div>
<div style="font-size: 0.9em; color: #4CAF50; margin-bottom: 15px; padding: 10px; background: rgba(76, 175, 80, 0.1); border-left: 3px solid #4CAF50; border-radius: 4px;">
<strong>📝 How Spell-to-Key Mapping Works:</strong><br>
1. Perform a gesture/spell with your wand<br>
2. The device detects which spell you cast<br>
3. The assigned keyboard key is sent to your computer<br>
4. Works in both <strong>Mouse</strong> and <strong>Keyboard</strong> HID modes!<br>
<br>
💡 <strong>Example:</strong> Map "Lumos" to key "F" Cast Lumos "F" key pressed<br>
Perfect for gaming hotkeys, productivity shortcuts, etc!
</div>
<h4 style="margin: 0 0 10px 0; color: #4CAF50;">Spell Mappings (Full Keyboard)</h4>
<input type="text" id="spell-filter" class="spell-mapping-search" placeholder="Filter spells..." oninput="filterSpellMappings()">
<div class="spell-mappings-container">
@@ -483,19 +493,31 @@ static const char index_html[] = R"rawliteral(
<div style="margin: 10px 0;">
<label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
<input type="checkbox" id="invert-mouse-y" style="width: 18px; height: 18px;">
<span>Invert Y-Axis (wand up = cursor up)</span>
<span>Invert Mouse Y-Axis</span>
</label>
<div style="font-size: 0.8em; color: #888; margin-top: 5px;">Checked = inverted (typical), Unchecked = natural</div>
<div style="font-size: 0.8em; color: #888; margin-top: 5px;">
📍 Only applies in <strong>Mouse</strong> mode<br>
<strong>If cursor moves BACKWARDS:</strong><br>
UNCHECKED (default) = wand UP moves cursor UP (natural)<br>
CHECKED = wand UP moves cursor DOWN (inverted)<br>
💡 Click "Reset Settings" below if checkbox doesn't work
</div>
</div>
<div style="margin: 10px 0; border-top: 1px solid #444; padding-top: 10px;">
<label style="display: block; margin-bottom: 5px;">HID Mode:</label>
<select id="hid-mode" style="width: 100%; padding: 8px; border-radius: 4px; background: #111; color: #eee; border: 1px solid #444;">
<option value="0">Mouse</option>
<option value="1">Keyboard</option>
<option value="2">Gamepad</option>
<option value="3">Disabled</option>
<option value="0">Mouse - Wand controls cursor + spell keys</option>
<option value="1">Keyboard - Spell keys only</option>
<option value="2">Gamepad - Wand controls joystick + spell buttons</option>
<option value="3">Disabled - No HID output</option>
</select>
<div style="font-size: 0.8em; color: #888; margin-top: 5px;">Only one mode can be active at a time</div>
<div style="font-size: 0.8em; color: #4CAF50; margin-top: 8px; padding: 8px; background: rgba(76, 175, 80, 0.1); border-left: 3px solid #4CAF50; border-radius: 4px;">
<strong> Mixed Mode Tip:</strong><br>
<strong>Mouse mode:</strong> Wand movement controls cursor AND detected spells send keyboard keys!<br>
<strong>Keyboard mode:</strong> Only spell detection sends keys (no mouse movement)<br>
<strong>Gamepad mode:</strong> Wand controls joystick AND spells trigger gamepad buttons<br>
This lets you aim with the wand while casting spells as hotkeys!
</div>
</div>
<div style="margin: 10px 0; border-top: 1px solid #444; padding-top: 10px;">
<label style="display: block; margin-bottom: 5px;">Gamepad Sensitivity:</label>
@@ -516,6 +538,10 @@ static const char index_html[] = R"rawliteral(
<input type="checkbox" id="invert-gamepad-y" style="width: 18px; height: 18px;">
<span>Invert Gamepad Y-Axis</span>
</label>
<div style="font-size: 0.8em; color: #888; margin-top: 5px;">
🎮 Only applies in <strong>Gamepad</strong> mode<br>
Toggle if wand movement feels backwards in-game
</div>
</div>
</div>
<div style="background: #222; padding: 10px; border-radius: 5px; margin-top: 10px;">
@@ -1611,13 +1637,19 @@ static const char index_html[] = R"rawliteral(
// Load and save settings with spell mappings
function saveSettings() {
const invertMouseY = document.getElementById('invert-mouse-y').checked;
console.log('💾 Saving settings - invert_mouse_y checkbox state:', invertMouseY);
const invertGamepadY = document.getElementById('invert-gamepad-y').checked;
console.log('💾 Saving settings - gamepad_invert_y checkbox state:', invertGamepadY);
const settings = {
mouse_sensitivity: parseFloat(document.getElementById('mouse-sensitivity').value),
invert_mouse_y: document.getElementById('invert-mouse-y').checked,
invert_mouse_y: invertMouseY,
hid_mode: parseInt(document.getElementById('hid-mode').value),
gamepad_sensitivity: parseFloat(document.getElementById('gamepad-sensitivity').value),
gamepad_deadzone: parseFloat(document.getElementById('gamepad-deadzone').value),
gamepad_invert_y: document.getElementById('invert-gamepad-y').checked,
gamepad_invert_y: invertGamepadY,
ha_mqtt_enabled: document.getElementById('ha-mqtt-enabled').checked,
mqtt_broker: document.getElementById('mqtt-broker').value,
mqtt_username: document.getElementById('mqtt-username').value,
@@ -1629,7 +1661,11 @@ static const char index_html[] = R"rawliteral(
// Collect all spell keycode mappings
for (let i = 0; i < SPELL_NAMES.length; i++) {
const select = document.getElementById(`spell_${i}`);
settings.spells.push(parseInt(select.value));
const keycode = parseInt(select.value);
settings.spells.push(keycode);
if (keycode !== 0) {
console.log(`Spell[${i}] '${SPELL_NAMES[i]}': keycode=${keycode} (0x${keycode.toString(16).toUpperCase()})`);
}
}
// Collect all spell gamepad button mappings
@@ -1659,15 +1695,23 @@ static const char index_html[] = R"rawliteral(
.then(response => response.json())
.then(data => {
console.log('Settings loaded:', data);
console.log('🔍 Received invert_mouse_y from backend:', data.invert_mouse_y);
console.log('🔍 Received gamepad_invert_y from backend:', data.gamepad_invert_y);
document.getElementById('mouse-sensitivity').value = data.mouse_sensitivity || 1.0;
document.getElementById('sens-value').textContent = (data.mouse_sensitivity || 1.0).toFixed(1) + 'x';
document.getElementById('invert-mouse-y').checked = data.invert_mouse_y !== false;
document.getElementById('invert-mouse-y').checked = (data.invert_mouse_y === true);
console.log(' Set invert-mouse-y checkbox to:', document.getElementById('invert-mouse-y').checked);
document.getElementById('hid-mode').value = (data.hid_mode !== undefined) ? data.hid_mode : 0;
document.getElementById('gamepad-sensitivity').value = data.gamepad_sensitivity || 1.0;
document.getElementById('gpad-sens-value').textContent = (data.gamepad_sensitivity || 1.0).toFixed(1) + 'x';
document.getElementById('gamepad-deadzone').value = (data.gamepad_deadzone !== undefined) ? data.gamepad_deadzone : 0.05;
document.getElementById('gpad-deadzone-value').textContent = ((data.gamepad_deadzone !== undefined) ? data.gamepad_deadzone : 0.05).toFixed(2);
document.getElementById('invert-gamepad-y').checked = data.gamepad_invert_y !== false;
document.getElementById('invert-gamepad-y').checked = (data.gamepad_invert_y === true);
console.log(' Set invert-gamepad-y checkbox to:', document.getElementById('invert-gamepad-y').checked);
document.getElementById('ha-mqtt-enabled').checked = data.ha_mqtt_enabled !== false;
document.getElementById('mqtt-broker').value = data.mqtt_broker || '';
document.getElementById('mqtt-username').value = data.mqtt_username || '';
@@ -1675,12 +1719,18 @@ static const char index_html[] = R"rawliteral(
// Load spell keycodes
if (data.spells && data.spells.length === SPELL_NAMES.length) {
let loaded_count = 0;
for (let i = 0; i < data.spells.length; i++) {
const select = document.getElementById(`spell_${i}`);
if (select) {
select.value = data.spells[i];
if (data.spells[i] !== 0) {
loaded_count++;
console.log(`Loading spell[${i}] '${SPELL_NAMES[i]}': keycode=${data.spells[i]} (0x${data.spells[i].toString(16).toUpperCase().padStart(2, '0')})`);
}
}
}
console.log(` Loaded ${loaded_count} spell-to-key mappings`);
}
if (data.gamepad_spells && data.gamepad_spells.length === SPELL_NAMES.length) {
for (let i = 0; i < data.gamepad_spells.length; i++) {
@@ -1704,23 +1754,11 @@ static const char index_html[] = R"rawliteral(
.then(response => response.json())
.then(data => {
console.log('Settings reset:', data);
// Reset all spell mappings to 0 (disabled)
for (let i = 0; i < SPELL_NAMES.length; i++) {
const select = document.getElementById(`spell_${i}`);
if (select) select.value = 0;
}
for (let i = 0; i < SPELL_NAMES.length; i++) {
const select = document.getElementById(`gpad_spell_${i}`);
if (select) select.value = 0;
}
document.getElementById('mouse-sensitivity').value = 1.0;
document.getElementById('sens-value').textContent = '1.0x';
document.getElementById('gamepad-sensitivity').value = 1.0;
document.getElementById('gpad-sens-value').textContent = '1.0x';
document.getElementById('gamepad-deadzone').value = 0.05;
document.getElementById('gpad-deadzone-value').textContent = '0.05';
document.getElementById('invert-gamepad-y').checked = true;
showToast('Settings reset to defaults!', 'success');
showToast('Settings reset to defaults! Reloading...', 'success');
// Reload settings from backend to ensure UI is synced
setTimeout(() => {
loadSettings();
}, 500);
})
.catch(error => {
showToast('Failed to reset settings', 'error');
@@ -3012,14 +3050,27 @@ esp_err_t WebServer::settings_get_handler(httpd_req_t *req)
int offset = 0;
size_t buffer_size = 4096;
float mouse_sens = usbHID.getMouseSensitivity();
bool mouse_invert = usbHID.getInvertMouseY();
HIDMode hid_mode = usbHID.getHidMode();
float gamepad_sens = usbHID.getGamepadSensitivity();
float gamepad_deadzone = usbHID.getGamepadDeadzone();
bool gamepad_invert = usbHID.getGamepadInvertY();
ESP_LOGI(TAG, "📤 Sending settings to UI:");
ESP_LOGI(TAG, " mouse_sensitivity=%.2f, invert_mouse_y=%s", mouse_sens, mouse_invert ? "true" : "false");
ESP_LOGI(TAG, " hid_mode=%u", static_cast<unsigned>(hid_mode));
ESP_LOGI(TAG, " gamepad_sensitivity=%.2f, gamepad_deadzone=%.2f, gamepad_invert_y=%s",
gamepad_sens, gamepad_deadzone, gamepad_invert ? "true" : "false");
offset += snprintf(buffer + offset, buffer_size - offset,
"{\"mouse_sensitivity\": %.2f, \"invert_mouse_y\": %s, \"hid_mode\": %u, \"gamepad_sensitivity\": %.2f, \"gamepad_deadzone\": %.2f, \"gamepad_invert_y\": %s, \"spells\": [",
usbHID.getMouseSensitivity(),
usbHID.getInvertMouseY() ? "true" : "false",
static_cast<unsigned>(usbHID.getHidMode()),
usbHID.getGamepadSensitivity(),
usbHID.getGamepadDeadzone(),
usbHID.getGamepadInvertY() ? "true" : "false");
mouse_sens,
mouse_invert ? "true" : "false",
static_cast<unsigned>(hid_mode),
gamepad_sens,
gamepad_deadzone,
gamepad_invert ? "true" : "false");
const uint8_t *spell_keycodes = usbHID.getSpellKeycodes();
for (int i = 0; i < 73; i++)
@@ -3231,7 +3282,7 @@ esp_err_t WebServer::settings_save_handler(httpd_req_t *req)
char *mouse_ptr = strstr(buffer, "\"mouse_sensitivity\"");
if (mouse_ptr)
{
sscanf(mouse_ptr, "\"mouse_sensitivity\": %f", &mouse_sens);
sscanf(mouse_ptr, "\"mouse_sensitivity\" : %f", &mouse_sens);
usbHID.setMouseSensitivityValue(mouse_sens);
}
@@ -3239,8 +3290,23 @@ esp_err_t WebServer::settings_save_handler(httpd_req_t *req)
char *invert_ptr = strstr(buffer, "\"invert_mouse_y\"");
if (invert_ptr)
{
bool invert = (strstr(invert_ptr, "true") != NULL);
usbHID.setInvertMouseY(invert);
// Find the colon after the key, then check the value
char *value_ptr = strchr(invert_ptr, ':');
if (value_ptr)
{
value_ptr++; // Skip colon
while (*value_ptr == ' ' || *value_ptr == '\t')
value_ptr++; // Skip whitespace
bool invert = (strncmp(value_ptr, "true", 4) == 0);
ESP_LOGI(TAG, "🔍 Parsing invert_mouse_y: JSON substring='%.50s...', parsed_value=%s",
invert_ptr, invert ? "true" : "false");
usbHID.setInvertMouseY(invert);
ESP_LOGI(TAG, "✅ Called setInvertMouseY(%s)", invert ? "true" : "false");
}
}
else
{
ESP_LOGW(TAG, "⚠️ invert_mouse_y not found in JSON!");
}
// Parse HID mode
@@ -3248,7 +3314,7 @@ esp_err_t WebServer::settings_save_handler(httpd_req_t *req)
if (hid_mode_ptr)
{
int hid_mode = HID_MODE_MOUSE;
sscanf(hid_mode_ptr, "\"hid_mode\": %d", &hid_mode);
sscanf(hid_mode_ptr, "\"hid_mode\" : %d", &hid_mode);
usbHID.setHidMode(static_cast<HIDMode>(hid_mode));
}
@@ -3257,7 +3323,8 @@ esp_err_t WebServer::settings_save_handler(httpd_req_t *req)
if (gpad_sens_ptr)
{
float gpad_sens = 1.0f;
sscanf(gpad_sens_ptr, "\"gamepad_sensitivity\": %f", &gpad_sens);
sscanf(gpad_sens_ptr, "\"gamepad_sensitivity\" : %f", &gpad_sens);
ESP_LOGI(TAG, "📝 Parsed gamepad_sensitivity: %.2f", gpad_sens);
usbHID.setGamepadSensitivityValue(gpad_sens);
}
@@ -3266,7 +3333,8 @@ esp_err_t WebServer::settings_save_handler(httpd_req_t *req)
if (gpad_deadzone_ptr)
{
float gpad_deadzone = 0.05f;
sscanf(gpad_deadzone_ptr, "\"gamepad_deadzone\": %f", &gpad_deadzone);
sscanf(gpad_deadzone_ptr, "\"gamepad_deadzone\" : %f", &gpad_deadzone);
ESP_LOGI(TAG, "📝 Parsed gamepad_deadzone: %.2f", gpad_deadzone);
usbHID.setGamepadDeadzoneValue(gpad_deadzone);
}
@@ -3274,15 +3342,37 @@ esp_err_t WebServer::settings_save_handler(httpd_req_t *req)
char *gpad_invert_ptr = strstr(buffer, "\"gamepad_invert_y\"");
if (gpad_invert_ptr)
{
bool invert = (strstr(gpad_invert_ptr, "true") != NULL);
// Find the colon after the key, then check the value
char *value_ptr = strchr(gpad_invert_ptr, ':');
bool invert = false;
if (value_ptr)
{
value_ptr++; // Skip colon
while (*value_ptr == ' ' || *value_ptr == '\t')
value_ptr++; // Skip whitespace
invert = (strncmp(value_ptr, "true", 4) == 0);
ESP_LOGI(TAG, "🔍 Parsing gamepad_invert_y: JSON substring='%.50s...', parsed_value=%s",
gpad_invert_ptr, invert ? "true" : "false");
}
usbHID.setGamepadInvertY(invert);
ESP_LOGI(TAG, "✅ Called setGamepadInvertY(%s)", invert ? "true" : "false");
}
// Parse HA MQTT enabled
char *ha_mqtt_ptr = strstr(buffer, "\"ha_mqtt_enabled\"");
if (ha_mqtt_ptr)
{
bool enabled = (strstr(ha_mqtt_ptr, "true") != NULL);
// Find the colon after the key, then check the value
char *value_ptr = strchr(ha_mqtt_ptr, ':');
bool enabled = false;
if (value_ptr)
{
value_ptr++; // Skip colon
while (*value_ptr == ' ' || *value_ptr == '\t')
value_ptr++; // Skip whitespace
enabled = (strncmp(value_ptr, "true", 4) == 0);
}
nvs_handle_t nvs_handle;
esp_err_t err = nvs_open("storage", NVS_READWRITE, &nvs_handle);
if (err == ESP_OK)
@@ -3411,6 +3501,8 @@ esp_err_t WebServer::settings_save_handler(httpd_req_t *req)
int matched = sscanf(parse_ptr, "%d", &keycode);
if (matched == 1)
{
ESP_LOGI(TAG, "Setting spell[%d]='%s' to keycode=0x%02X (%d)",
spell_idx, SPELL_NAMES[spell_idx], keycode, keycode);
usbHID.setSpellKeycode(SPELL_NAMES[spell_idx], (uint8_t)keycode);
spell_idx++;
// Skip to next comma or bracket
@@ -3493,7 +3585,16 @@ esp_err_t WebServer::settings_save_handler(httpd_req_t *req)
char *ha_mqtt_ptr = strstr(buffer, "\"ha_mqtt_enabled\"");
if (ha_mqtt_ptr)
{
bool enabled = (strstr(ha_mqtt_ptr, "true") != NULL);
// Find the colon after the key, then check the value
char *value_ptr = strchr(ha_mqtt_ptr, ':');
bool enabled = false;
if (value_ptr)
{
value_ptr++; // Skip colon
while (*value_ptr == ' ' || *value_ptr == '\t')
value_ptr++; // Skip whitespace
enabled = (strncmp(value_ptr, "true", 4) == 0);
}
nvs_handle_t nvs_handle;
esp_err_t err = nvs_open("storage", NVS_READWRITE, &nvs_handle);
if (err == ESP_OK)
@@ -3973,7 +4074,20 @@ esp_err_t WebServer::hotspot_settings_handler(httpd_req_t *req)
ESP_LOGI(TAG, "Received hotspot settings: %s", buffer);
// Parse settings
bool enabled = (strstr(buffer, "\"enabled\":true") != NULL);
char *enabled_ptr = strstr(buffer, "\"enabled\"");
bool enabled = false;
if (enabled_ptr)
{
char *value_ptr = strchr(enabled_ptr, ':');
if (value_ptr)
{
value_ptr++;
while (*value_ptr == ' ' || *value_ptr == '\t')
value_ptr++;
enabled = (strncmp(value_ptr, "true", 4) == 0);
}
}
char ssid[32] = {0};
char password[64] = {0};
int channel = 1;
@@ -4023,7 +4137,7 @@ esp_err_t WebServer::hotspot_settings_handler(httpd_req_t *req)
char *channel_ptr = strstr(buffer, "\"channel\":");
if (channel_ptr)
{
sscanf(channel_ptr, "\"channel\":%d", &channel);
sscanf(channel_ptr, "\"channel\" : %d", &channel);
}
// Save hotspot settings to NVS
@@ -4147,9 +4261,24 @@ esp_err_t WebServer::system_wifi_mode_handler(httpd_req_t *req)
// Parse JSON to extract mode
// Expected: {"mode":"client"} or {"mode":"ap"}
bool force_ap = false;
if (strstr(buf, "\"ap\"") != nullptr)
char *mode_ptr = strstr(buf, "\"mode\"");
if (mode_ptr)
{
force_ap = true;
char *value_ptr = strchr(mode_ptr, ':');
if (value_ptr)
{
value_ptr++;
while (*value_ptr == ' ' || *value_ptr == '\t')
value_ptr++;
if (*value_ptr == '\"')
{
value_ptr++;
if (strncmp(value_ptr, "ap", 2) == 0)
{
force_ap = true;
}
}
}
}
ESP_LOGI(TAG, "Setting force_ap_mode to %d", force_ap ? 1 : 0);