mirror of
https://github.com/Senape3000/EvilCrowRF-V2.git
synced 2026-03-30 13:45:39 +00:00
Introduce CPU telemetry and configurable CPU temperature offset, add hardware-button replay support and SD root pathType, and update app/firmware plumbing. Key changes: - Added schematics PDFs to docs. - Protocol/firmware: BinaryStatus extended with cpuTempDeciC, core0Mhz, core1Mhz; settings payload and sync now include cpuTempOffsetDeciC. HwButton config command supports an extended payload for replay file (pathType + path). - ConfigManager: persist button replay path/type and cpuTempOffsetDeciC, clamp/range checks, build/save logic and default values updated. - ButtonCommands: executeAction now receives buttonId; handleButtonConfig accepts optional replay file info; ReplayLast implemented to queue CC1101 transmissions using configured path/type. - FileCommands: support pathType 5 (SD root) and treat root-based storages appropriately; only create SD directories for dedicated SD folders. - StateCommands: sendSettingsSync updated to 10-byte payload and include cpu temp offset in logs. - Mobile app (Flutter): localization for CPU temp offset; BLE provider persists and exposes cpuTempOffsetDeciC and device telemetry (cpuTempC, coreMHz); supports pathType 5 (SD root) and loads temp offset preference; settings provider persists button replay paths/types and exposes setters; firmware protocol helper builds extended hw button command; FilesScreen supports pickMode, SD root selection and enforces allowed extensions when picking; Debug screen adds slider/card to adjust CPU temp offset; NrfScreen uses nrfNotify() for targeted UI updates and visual tweaks; spectrum painter maxLevel adjusted to 100. These changes enable local replay-from-file via hardware buttons, expose CPU temperature telemetry and offset tuning to the mobile app, and add SD root handling across firmware and app.
263 lines
9.9 KiB
C++
263 lines
9.9 KiB
C++
/**
|
|
* @file ButtonCommands.h
|
|
* @brief BLE command handler for hardware button configuration + polling.
|
|
*
|
|
* Command IDs:
|
|
* 0x40 = HW_BUTTON_CONFIG — Set action for a physical button
|
|
* Payload: [buttonId: 1|2][actionId: 0-6]
|
|
*
|
|
* Button actions (HwButtonAction enum):
|
|
* 0 = None
|
|
* 1 = Toggle NRF Jammer
|
|
* 2 = Toggle SubGhz Recording
|
|
* 3 = Replay Last Signal
|
|
* 4 = Toggle LED
|
|
* 5 = Deep Sleep
|
|
* 6 = Reboot
|
|
*
|
|
* GPIO34 (BUTTON1) and GPIO35 (BUTTON2) are input-only pins on ESP32.
|
|
* The polling function checkButtons() should be called from loop().
|
|
*/
|
|
|
|
#ifndef BUTTON_COMMANDS_H
|
|
#define BUTTON_COMMANDS_H
|
|
|
|
#include <Arduino.h>
|
|
#include "config.h"
|
|
#include "core/ble/CommandHandler.h"
|
|
#include "core/ble/ClientsManager.h"
|
|
#include "BinaryMessages.h"
|
|
#include "ConfigManager.h"
|
|
#include "core/device_controls/DeviceControls.h"
|
|
#include "modules/nrf/NrfJammer.h"
|
|
#include "modules/CC1101_driver/CC1101_Worker.h"
|
|
#include "esp_log.h"
|
|
|
|
/// Available actions for hardware buttons.
|
|
/// Must match the Flutter HwButtonAction enum order.
|
|
enum class HwButtonAction : uint8_t {
|
|
None = 0,
|
|
ToggleJammer = 1,
|
|
ToggleRecording = 2,
|
|
ReplayLast = 3,
|
|
ToggleLed = 4,
|
|
DeepSleep = 5,
|
|
Reboot = 6,
|
|
ACTION_COUNT = 7,
|
|
};
|
|
|
|
class ButtonCommands {
|
|
public:
|
|
static void registerCommands(CommandHandler& handler) {
|
|
handler.registerCommand(0x40, handleButtonConfig);
|
|
// Load persisted button actions from flash config
|
|
loadFromConfig();
|
|
ESP_LOGI("ButtonCmd", "HW Button commands registered (0x40), btn1=%d btn2=%d",
|
|
(int)button1Action, (int)button2Action);
|
|
}
|
|
|
|
/// Load button actions from ConfigManager (call after ConfigManager::loadSettings)
|
|
static void loadFromConfig() {
|
|
uint8_t a1 = ConfigManager::settings.button1Action;
|
|
uint8_t a2 = ConfigManager::settings.button2Action;
|
|
if (a1 < (uint8_t)HwButtonAction::ACTION_COUNT)
|
|
button1Action = static_cast<HwButtonAction>(a1);
|
|
if (a2 < (uint8_t)HwButtonAction::ACTION_COUNT)
|
|
button2Action = static_cast<HwButtonAction>(a2);
|
|
}
|
|
|
|
/// Call from loop() — polls buttons with debounce and executes assigned action.
|
|
static void checkButtons() {
|
|
static unsigned long lastBtn1Press = 0;
|
|
static unsigned long lastBtn2Press = 0;
|
|
static bool btn1WasPressed = false;
|
|
static bool btn2WasPressed = false;
|
|
const unsigned long debounceMs = 300;
|
|
unsigned long now = millis();
|
|
|
|
// BUTTON1 (GPIO34) — active LOW
|
|
bool btn1Pressed = (digitalRead(BUTTON1) == LOW);
|
|
if (btn1Pressed && !btn1WasPressed && (now - lastBtn1Press > debounceMs)) {
|
|
lastBtn1Press = now;
|
|
executeAction(button1Action, 1);
|
|
}
|
|
btn1WasPressed = btn1Pressed;
|
|
|
|
// BUTTON2 (GPIO35) — active LOW
|
|
bool btn2Pressed = (digitalRead(BUTTON2) == LOW);
|
|
if (btn2Pressed && !btn2WasPressed && (now - lastBtn2Press > debounceMs)) {
|
|
lastBtn2Press = now;
|
|
executeAction(button2Action, 2);
|
|
}
|
|
btn2WasPressed = btn2Pressed;
|
|
}
|
|
|
|
private:
|
|
static inline HwButtonAction button1Action = HwButtonAction::None;
|
|
static inline HwButtonAction button2Action = HwButtonAction::None;
|
|
|
|
/// Handle 0x40: Set button action
|
|
/// Payload basic: [buttonId (1|2)][actionId (0-6)]
|
|
/// Payload extended: [buttonId][actionId][pathType][pathLen][path...]
|
|
static bool handleButtonConfig(const uint8_t* data, size_t len) {
|
|
if (len < 2) {
|
|
ESP_LOGW("ButtonCmd", "Payload too short (need 2 bytes)");
|
|
return false;
|
|
}
|
|
|
|
uint8_t buttonId = data[0];
|
|
uint8_t actionId = data[1];
|
|
|
|
if (buttonId < 1 || buttonId > 2) {
|
|
ESP_LOGW("ButtonCmd", "Invalid button ID: %u (must be 1 or 2)", buttonId);
|
|
return false;
|
|
}
|
|
if (actionId >= (uint8_t)HwButtonAction::ACTION_COUNT) {
|
|
ESP_LOGW("ButtonCmd", "Invalid action ID: %u (max %u)",
|
|
actionId, (uint8_t)HwButtonAction::ACTION_COUNT - 1);
|
|
return false;
|
|
}
|
|
|
|
HwButtonAction action = static_cast<HwButtonAction>(actionId);
|
|
|
|
if (buttonId == 1) {
|
|
button1Action = action;
|
|
ConfigManager::settings.button1Action = actionId;
|
|
} else {
|
|
button2Action = action;
|
|
ConfigManager::settings.button2Action = actionId;
|
|
}
|
|
|
|
// Optional replay file configuration
|
|
if (len >= 4) {
|
|
uint8_t pathType = data[2];
|
|
uint8_t pathLen = data[3];
|
|
if (pathType > 5) {
|
|
ESP_LOGW("ButtonCmd", "Invalid replay pathType: %u", pathType);
|
|
return false;
|
|
}
|
|
if (pathLen > MAX_BUTTON_SIGNAL_PATH_LEN) {
|
|
ESP_LOGW("ButtonCmd", "Replay path too long: %u", pathLen);
|
|
return false;
|
|
}
|
|
if (len < (size_t)(4 + pathLen)) {
|
|
ESP_LOGW("ButtonCmd", "Replay payload truncated");
|
|
return false;
|
|
}
|
|
|
|
if (buttonId == 1) {
|
|
ConfigManager::settings.button1SignalPathType = pathType;
|
|
memset(ConfigManager::settings.button1SignalPath, 0, sizeof(ConfigManager::settings.button1SignalPath));
|
|
if (pathLen > 0) {
|
|
memcpy(ConfigManager::settings.button1SignalPath, data + 4, pathLen);
|
|
ConfigManager::settings.button1SignalPath[pathLen] = '\0';
|
|
}
|
|
} else {
|
|
ConfigManager::settings.button2SignalPathType = pathType;
|
|
memset(ConfigManager::settings.button2SignalPath, 0, sizeof(ConfigManager::settings.button2SignalPath));
|
|
if (pathLen > 0) {
|
|
memcpy(ConfigManager::settings.button2SignalPath, data + 4, pathLen);
|
|
ConfigManager::settings.button2SignalPath[pathLen] = '\0';
|
|
}
|
|
}
|
|
}
|
|
|
|
// Persist to flash so the action survives reboot
|
|
ConfigManager::saveSettings();
|
|
ESP_LOGI("ButtonCmd", "Button %u -> action %u (saved to flash)", buttonId, actionId);
|
|
|
|
// Send confirmation
|
|
uint8_t resp[] = { MSG_COMMAND_SUCCESS, buttonId, actionId };
|
|
ClientsManager::getInstance().notifyAllBinary(
|
|
NotificationType::SettingsSync, resp, sizeof(resp));
|
|
|
|
return true;
|
|
}
|
|
|
|
/// Execute the action assigned to a button.
|
|
static void executeAction(HwButtonAction action, uint8_t buttonId) {
|
|
switch (action) {
|
|
case HwButtonAction::None:
|
|
break;
|
|
|
|
case HwButtonAction::ToggleJammer:
|
|
if (NrfJammer::isRunning()) {
|
|
NrfJammer::stop();
|
|
ESP_LOGI("ButtonCmd", "Jammer stopped via button");
|
|
} else {
|
|
// Start with default mode (Full spectrum sweep)
|
|
NrfJammer::start(NRF_JAM_FULL);
|
|
ESP_LOGI("ButtonCmd", "Jammer started via button");
|
|
}
|
|
break;
|
|
|
|
case HwButtonAction::ToggleRecording:
|
|
// TODO: Implement recording toggle when recorder module
|
|
// exposes a static start/stop interface.
|
|
ESP_LOGI("ButtonCmd", "Toggle recording — not yet implemented");
|
|
DeviceControls::ledBlink(2, 100);
|
|
break;
|
|
|
|
case HwButtonAction::ReplayLast:
|
|
{
|
|
const char* configuredPath = (buttonId == 1)
|
|
? ConfigManager::settings.button1SignalPath
|
|
: ConfigManager::settings.button2SignalPath;
|
|
int configuredPathType = (buttonId == 1)
|
|
? ConfigManager::settings.button1SignalPathType
|
|
: ConfigManager::settings.button2SignalPathType;
|
|
|
|
if (configuredPath[0] == '\0') {
|
|
ESP_LOGW("ButtonCmd", "Replay requested but no .sub file configured for button %u", buttonId);
|
|
DeviceControls::ledBlink(3, 80);
|
|
break;
|
|
}
|
|
|
|
int module = CC1101Worker::findFirstIdleModule();
|
|
if (module < 0) {
|
|
ESP_LOGW("ButtonCmd", "No idle CC1101 module available for replay");
|
|
DeviceControls::ledBlink(4, 60);
|
|
break;
|
|
}
|
|
|
|
bool queued = CC1101Worker::transmit(module, std::string(configuredPath), 1, configuredPathType);
|
|
if (queued) {
|
|
ESP_LOGI("ButtonCmd", "Replay queued from button %u: module=%d pathType=%d path=%s",
|
|
buttonId, module, configuredPathType, configuredPath);
|
|
} else {
|
|
ESP_LOGE("ButtonCmd", "Replay queue failed from button %u", buttonId);
|
|
DeviceControls::ledBlink(4, 60);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case HwButtonAction::ToggleLed:
|
|
{
|
|
static bool ledState = false;
|
|
ledState = !ledState;
|
|
digitalWrite(LED, ledState ? HIGH : LOW);
|
|
ESP_LOGI("ButtonCmd", "LED toggled %s", ledState ? "ON" : "OFF");
|
|
}
|
|
break;
|
|
|
|
case HwButtonAction::DeepSleep:
|
|
ESP_LOGI("ButtonCmd", "Entering deep sleep via button");
|
|
DeviceControls::ledBlink(5, 150);
|
|
DeviceControls::goDeepSleep();
|
|
break;
|
|
|
|
case HwButtonAction::Reboot:
|
|
ESP_LOGI("ButtonCmd", "Rebooting via button");
|
|
DeviceControls::ledBlink(3, 100);
|
|
vTaskDelay(pdMS_TO_TICKS(200));
|
|
esp_restart();
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
#endif // BUTTON_COMMANDS_H
|