├── .gitignore ├── sdkconfig.defaults ├── custom_partitions.csv ├── src ├── ControllerAdapter.cpp ├── Cc1101Mode.cpp ├── DeviceControls.cpp ├── Detector.cpp ├── ClientsManager.cpp ├── Cc1101Control.cpp ├── Transmitter.cpp ├── Recorder.cpp ├── ModuleCc1101.cpp ├── WebAdapter.cpp ├── main.cpp ├── Actions.cpp ├── SerialAdapter.cpp └── ServiceMode.cpp ├── lib ├── utility │ └── compatibility.h ├── subghz │ ├── AllProtocols.h │ ├── protocols │ │ ├── Raw.h │ │ ├── BinRAW.h │ │ ├── Princeton.h │ │ ├── Raw.cpp │ │ ├── BinRAW.cpp │ │ └── Princeton.cpp │ ├── PulsePayload.h │ ├── SubGhzProtocol.h │ └── SubGhzProtocol.cpp ├── helpers │ ├── StringHelpers.h │ └── StringHelpers.cpp ├── generators │ ├── FlipperSubFile.h │ └── FlipperSubFile.cpp ├── README └── cc1101 │ └── CC1101_Radio.h ├── include ├── ServiceMode.h ├── DeviceControls.h ├── ControllerAdapter.h ├── Transmitter.h ├── WebAdapter.h ├── Actions.h ├── Cc1101Control.h ├── Cc1101Mode.h ├── README ├── Detector.h ├── config.h ├── Recorder.h ├── SerialAdapter.h ├── ClientsManager.h ├── ModuleCc1101.h ├── ConfigManager.h ├── FilesManager.h ├── SubFileParser.h └── DeviceTasks.h ├── test └── README ├── platformio.ini ├── .clang-format ├── README.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .vscode -------------------------------------------------------------------------------- /sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | # Enable core dumps 2 | CONFIG_ESP32_ENABLE_COREDUMP_TO_FLASH=y 3 | CONFIG_ESP32_COREDUMP_DATA_FORMAT_ELF=y -------------------------------------------------------------------------------- /custom_partitions.csv: -------------------------------------------------------------------------------- 1 | # Name, Type, SubType, Offset, Size, Flags 2 | nvs, data, nvs, 0x9000, 0x5000, 3 | otadata, data, ota, 0xe000, 0x2000, 4 | app0, app, ota_0, 0x10000, 0x1D0000, 5 | app1, app, ota_1, 0x1E0000,0x1D0000, 6 | spiffs, data, spiffs, 0x3B0000,0x50000, -------------------------------------------------------------------------------- /src/ControllerAdapter.cpp: -------------------------------------------------------------------------------- 1 | #include "ControllerAdapter.h" 2 | 3 | // Initialize the static member 4 | QueueHandle_t ControllerAdapter::xTaskQueue = nullptr; 5 | 6 | void ControllerAdapter::initializeQueue() 7 | { 8 | if (xTaskQueue == nullptr) { 9 | xTaskQueue = xQueueCreate(5, sizeof(QueueItem*)); 10 | } 11 | } -------------------------------------------------------------------------------- /lib/utility/compatibility.h: -------------------------------------------------------------------------------- 1 | #ifndef Compatibility_h 2 | #define Compatibility_h 3 | 4 | #include 5 | 6 | namespace std { 7 | template 8 | std::unique_ptr make_unique(Args&&... args) 9 | { 10 | return std::unique_ptr(new T(std::forward(args)...)); 11 | } 12 | } // namespace std 13 | 14 | #endif -------------------------------------------------------------------------------- /include/ServiceMode.h: -------------------------------------------------------------------------------- 1 | #ifndef Service_Mode_h 2 | #define Service_Mode_h 3 | 4 | #include 5 | #include 6 | #include 7 | #include "SPIFFS.h" 8 | #include 9 | #include 10 | #include "Update.h" 11 | #include "ConfigManager.h" 12 | 13 | class ServiceMode { 14 | private: 15 | static void serveWebPage(); 16 | static void initWifi(); 17 | public: 18 | static void serviceModeStart(); 19 | }; 20 | 21 | 22 | #endif -------------------------------------------------------------------------------- /test/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for PlatformIO Test Runner and project tests. 3 | 4 | Unit Testing is a software testing method by which individual units of 5 | source code, sets of one or more MCU program modules together with associated 6 | control data, usage procedures, and operating procedures, are tested to 7 | determine whether they are fit for use. Unit testing finds problems early 8 | in the development cycle. 9 | 10 | More information about PlatformIO Unit Testing: 11 | - https://docs.platformio.org/en/latest/advanced/unit-testing/index.html 12 | -------------------------------------------------------------------------------- /include/DeviceControls.h: -------------------------------------------------------------------------------- 1 | #ifndef Device_Controls_h 2 | #define Device_Controls_h 3 | 4 | #include 5 | #include "config.h" 6 | #include "ModuleCc1101.h" 7 | #include "ConfigManager.h" 8 | 9 | const int BLINK_ON_TIME = 100; 10 | const int BLINK_OFF_TIME = 10000; 11 | 12 | class DeviceControls { 13 | private: 14 | static unsigned long blinkTime; 15 | 16 | public: 17 | static void setup(); 18 | static void onLoadPowerManagement(); 19 | static void goDeepSleep(); 20 | static void ledBlink(int count, int pause); 21 | static void poweronBlink(); 22 | static void onLoadServiceMode(); 23 | }; 24 | 25 | #endif -------------------------------------------------------------------------------- /lib/subghz/AllProtocols.h: -------------------------------------------------------------------------------- 1 | #ifndef All_Protocols_h 2 | #define All_Protocols_h 3 | 4 | #include "protocols/Princeton.h" 5 | #include "protocols/Raw.h" 6 | #include "protocols/BinRAW.h" 7 | 8 | namespace { 9 | struct RegisterAllProtocols { 10 | RegisterAllProtocols() { 11 | SubGhzProtocol::registerProtocol("Princeton", createPrincetonProtocol); 12 | SubGhzProtocol::registerProtocol("RAW", createRawProtocol); 13 | SubGhzProtocol::registerProtocol("BinRAW", createBinRAWProtocol); 14 | } 15 | }; 16 | 17 | static RegisterAllProtocols registerAllProtocols; 18 | } 19 | 20 | #endif // All_Protocols_h 21 | -------------------------------------------------------------------------------- /lib/subghz/protocols/Raw.h: -------------------------------------------------------------------------------- 1 | #ifndef Raw_Protocol_h 2 | #define Raw_Protocol_h 3 | 4 | #include "SubGhzProtocol.h" 5 | #include "compatibility.h" 6 | #include 7 | 8 | class RawProtocol : public SubGhzProtocol { 9 | public: 10 | bool parse(File &file) override; 11 | std::vector> getPulseData() const override; 12 | uint32_t getRepeatCount() const override; 13 | std::string serialize() const override; 14 | 15 | private: 16 | mutable std::vector> pulseData; 17 | }; 18 | 19 | // Factory function 20 | std::unique_ptr createRawProtocol(); 21 | 22 | #endif // Raw_Protocol_h 23 | -------------------------------------------------------------------------------- /lib/helpers/StringHelpers.h: -------------------------------------------------------------------------------- 1 | #ifndef STRING_HELPERS_H 2 | #define STRING_HELPERS_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace helpers { 14 | namespace string { 15 | 16 | String toLowerCase(const String &str); 17 | std::string toLowerCase(const std::string &str); 18 | std::string toStdString(const String &str); 19 | bool endsWith(const std::string& str, const std::string& suffix); 20 | String toArduinoString(const std::string &str); 21 | String generateRandomString(int length); 22 | std::string escapeJson(const std::string &input); 23 | } 24 | } 25 | 26 | #endif // STRING_HELPERS_H -------------------------------------------------------------------------------- /include/ControllerAdapter.h: -------------------------------------------------------------------------------- 1 | #ifndef ControllerAdapter_h 2 | #define ControllerAdapter_h 3 | 4 | #include "DeviceTasks.h" 5 | #include 6 | #include 7 | #include 8 | 9 | class ControllerAdapter 10 | { 11 | public: 12 | static void initializeQueue(); 13 | virtual void notify(String type, std::string message) = 0; 14 | virtual String getName() = 0; 15 | static QueueHandle_t xTaskQueue; 16 | 17 | template 18 | bool sendTask(T&& task) { 19 | QueueItem* item = new QueueItem(std::move(task)); 20 | 21 | if (xQueueSend(xTaskQueue, &item, portMAX_DELAY) != pdPASS) { 22 | delete item; 23 | // Handle queue full situation 24 | return false; 25 | } 26 | return true; 27 | } 28 | }; 29 | 30 | #endif -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [env:esp32dev] 12 | platform = espressif32 13 | board = esp32dev 14 | framework = arduino 15 | lib_deps = 16 | bblanchon/ArduinoJson@^7.0.4 17 | esphome/ESPAsyncWebServer-esphome@^3.2.2 18 | spacehuhn/SimpleCLI@^1.1.4 19 | 20 | #Log Levels 0 - none, 1 - error, 2 - warning, 3 - info, 4 - debug, 5 - verbose 21 | build_flags = -DCORE_DEBUG_LEVEL=0 22 | 23 | ; monitor_filters = esp32_exception_decoder 24 | ; build_type = debug -------------------------------------------------------------------------------- /src/Cc1101Mode.cpp: -------------------------------------------------------------------------------- 1 | #include "Cc1101Mode.h" 2 | 3 | Cc1101Mode::Cc1101Mode() 4 | { 5 | } 6 | 7 | Cc1101Mode::Cc1101Mode(OperationMode mode, CallbackFunction onMode) 8 | { 9 | setup(mode, onMode); 10 | } 11 | 12 | void Cc1101Mode::setup(OperationMode mode, CallbackFunction onMode) 13 | { 14 | this->mode = mode; 15 | this->onMode = onMode; 16 | } 17 | 18 | bool Cc1101Mode::isMode(OperationMode mode) 19 | { 20 | return mode == this->mode; 21 | } 22 | 23 | OperationMode Cc1101Mode::getMode() const 24 | { 25 | return mode; 26 | } 27 | 28 | void Cc1101Mode::setMode(OperationMode mode) 29 | { 30 | this->mode = mode; 31 | } 32 | 33 | void Cc1101Mode::onModeProcess(void *taskParameters) 34 | { 35 | ModeTaskParameters *parameters = static_cast(taskParameters); 36 | parameters->mode.onMode(parameters->module); 37 | } -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | Language: Cpp 2 | BasedOnStyle: Google 3 | IndentWidth: 4 4 | UseTab: Never 5 | TabWidth: 8 6 | ColumnLimit: 180 7 | PointerAlignment: Right 8 | AccessModifierOffset: -2 9 | BreakBeforeBraces: Custom 10 | BraceWrapping: 11 | AfterEnum: true 12 | AfterStruct: true 13 | AfterClass: true 14 | SplitEmptyFunction: true 15 | AfterControlStatement: false 16 | AfterNamespace: false 17 | AfterFunction: true 18 | AfterUnion: true 19 | AfterExternBlock: false 20 | BeforeCatch: false 21 | BeforeElse: false 22 | SplitEmptyRecord: true 23 | SplitEmptyNamespace: true 24 | DerivePointerAlignment: true 25 | AllowShortFunctionsOnASingleLine: Empty 26 | AllowShortIfStatementsOnASingleLine: false 27 | AllowShortLoopsOnASingleLine: true 28 | AllowShortBlocksOnASingleLine: false 29 | AlwaysBreakAfterDefinitionReturnType: None 30 | AlwaysBreakTemplateDeclarations: true -------------------------------------------------------------------------------- /lib/generators/FlipperSubFile.h: -------------------------------------------------------------------------------- 1 | #ifndef FLIPPER_SUB_FILE_H 2 | #define FLIPPER_SUB_FILE_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | class FlipperSubFile { 11 | public: 12 | static void generateRaw( 13 | File& file, 14 | const std::string& presetName, 15 | const std::vector& customPresetData, 16 | std::stringstream& samples, 17 | float frequency 18 | ); 19 | 20 | private: 21 | static const std::map presetMapping; 22 | 23 | static void writeHeader(File& file, float frequency); 24 | static void writePresetInfo(File& file, const std::string& presetName, const std::vector& customPresetData); 25 | static void writeRawProtocolData(File& file, std::stringstream& samples); 26 | static std::string getPresetName(const std::string& preset); 27 | }; 28 | 29 | #endif // FLIPPER_SUB_FILE_H -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Disclaimer 2 | 3 | This project is intended solely for testing and educational purposes. The code provided in this repository is offered "as-is," without warranty of any kind. 4 | 5 | ### Important Notes: 6 | - **No Liability**: The author(s) of this project assume no responsibility for any damages, misuse, or legal repercussions that may arise from the use of this code in any environment, including but not limited to production, commercial, or operational settings. 7 | - **Testing Only**: This code is designed and intended to be used only in controlled testing environments. It is not recommended or supported for use in live or production environments. 8 | - **User Responsibility**: Users of this code are solely responsible for ensuring that its use complies with applicable laws, regulations, and best practices. 9 | 10 | By using or downloading this code, you agree to these terms and acknowledge that the authors will not be held liable for any misuse. 11 | -------------------------------------------------------------------------------- /include/Transmitter.h: -------------------------------------------------------------------------------- 1 | #ifndef Transmitter_h 2 | #define Transmitter_h 3 | 4 | #include "config.h" 5 | #include 6 | #include "ModuleCc1101.h" 7 | #include 8 | #include 9 | #include "FilesManager.h" 10 | #include "SubFileParser.h" 11 | #include "PulsePayload.h" 12 | 13 | class Transmitter 14 | { 15 | private: 16 | std::vector getCountOfOnOffBits(const std::string& bits); 17 | boolean transmitRawData(const std::vector &rawData, int module); 18 | public: 19 | boolean transmitBinary(float frequency, int pulseDuration, const std::string& bits, int module, int modulation, float deviation, int repeatCount, int wait); 20 | boolean transmitRaw(int module, float frequency, int modulation, float deviation, std::string& data, int repeat); 21 | bool transmitSub(const std::string& filename, int module, int repeat); 22 | boolean transmitData(PulsePayload& payload, int module); 23 | 24 | }; 25 | 26 | #endif // Transmitter_h -------------------------------------------------------------------------------- /lib/subghz/protocols/BinRAW.h: -------------------------------------------------------------------------------- 1 | #ifndef BinRAW_Protocol_h 2 | #define BinRAW_Protocol_h 3 | 4 | #include "SubGhzProtocol.h" 5 | #include "compatibility.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | class BinRAWProtocol : public SubGhzProtocol { 12 | public: 13 | bool parse(File &file) override; 14 | std::vector> getPulseData() const override; // Marked as const 15 | uint32_t getRepeatCount() const override; // Marked as const 16 | std::string serialize() const override; // Marked as const 17 | 18 | private: 19 | mutable std::vector> pulseData; // Mutable to allow modification in const context 20 | void generatePulseData(const std::vector& rawData, uint32_t bitRaw, uint32_t te) const; // Marked as const 21 | 22 | uint32_t bitCount = 0; 23 | uint32_t te = 0; 24 | }; 25 | 26 | // Factory function 27 | std::unique_ptr createBinRAWProtocol(); 28 | 29 | #endif // BinRAW_Protocol_h 30 | -------------------------------------------------------------------------------- /lib/subghz/protocols/Princeton.h: -------------------------------------------------------------------------------- 1 | #ifndef Princeton_Protocol_h 2 | #define Princeton_Protocol_h 3 | 4 | #include "SubGhzProtocol.h" 5 | #include "compatibility.h" 6 | #include 7 | 8 | #define PRINCETON_GUARD_TIME_DEFALUT 30 //GUARD_TIME = PRINCETON_GUARD_TIME_DEFALUT * TE 9 | // Guard Time value should be between 15 -> 72 otherwise default value will be used 10 | 11 | class PrincetonProtocol : public SubGhzProtocol { 12 | public: 13 | bool parse(File &file) override; 14 | std::vector> getPulseData() const override; 15 | uint32_t getRepeatCount() const override; 16 | std::string serialize() const override; 17 | 18 | private: 19 | uint64_t key = 0; 20 | uint32_t te = 0; 21 | uint32_t repeat = 0; 22 | uint16_t bit_count = 0; 23 | uint32_t guard_time = PRINCETON_GUARD_TIME_DEFALUT; 24 | mutable std::vector> pulseData; 25 | void generatePulseData() const; 26 | }; 27 | 28 | // Factory function 29 | std::unique_ptr createPrincetonProtocol(); 30 | 31 | #endif // Princeton_Protocol_h 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 tutejshy-bit 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /include/WebAdapter.h: -------------------------------------------------------------------------------- 1 | #ifndef WebAdapter_h 2 | #define WebAdapter_h 3 | 4 | #include "SD.h" 5 | #include "config.h" 6 | #include 7 | #include 8 | #include 9 | #include "ControllerAdapter.h" 10 | #include "ModuleCc1101.h" 11 | #include "FilesManager.h" 12 | #include "Update.h" 13 | 14 | class WebAdapter: public ControllerAdapter 15 | { 16 | public: 17 | WebAdapter(); 18 | 19 | void notify(String type, std::string message) override; 20 | void begin(SDFS sd); 21 | void initStatic(SDFS sd); 22 | 23 | String getName() override { 24 | return "webAdapter"; 25 | } 26 | private: 27 | bool staticServed = false; 28 | 29 | AsyncWebServer* server; 30 | AsyncEventSource* events; 31 | 32 | void handleDetectSignal(); 33 | void handleGetState() ; 34 | void handleIdle(); 35 | void handleRecordSignal(); 36 | void handleTransmitSignal(); 37 | void handleTransmitFromFile(); 38 | void handleFilesManagement(); 39 | void handleFileUpload(); 40 | void onEventsConnect(); 41 | bool handleMissingParams(AsyncWebServerRequest* request, const std::vector& requiredParams); 42 | }; 43 | 44 | extern WebAdapter webAdapter; 45 | 46 | #endif // WebAdapter_h -------------------------------------------------------------------------------- /lib/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link into executable file. 4 | 5 | The source code of each library should be placed in an own separate directory 6 | ("lib/your_library_name/[here are source files]"). 7 | 8 | For example, see a structure of the following two libraries `Foo` and `Bar`: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- README --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | and a contents of `src/main.c`: 31 | ``` 32 | #include 33 | #include 34 | 35 | int main (void) 36 | { 37 | ... 38 | } 39 | 40 | ``` 41 | 42 | PlatformIO Library Dependency Finder will find automatically dependent 43 | libraries scanning project source files. 44 | 45 | More information about PlatformIO Library Dependency Finder 46 | - https://docs.platformio.org/page/librarymanager/ldf.html 47 | -------------------------------------------------------------------------------- /lib/subghz/PulsePayload.h: -------------------------------------------------------------------------------- 1 | #ifndef PULSE_PAYLOAD_H 2 | #define PULSE_PAYLOAD_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | class PulsePayload { 10 | public: 11 | PulsePayload() : repeatCount(0), currentIndex(0), currentRepeat(0) {} 12 | PulsePayload(const std::vector>& pulses, uint32_t repeatCount) 13 | : pulses(pulses), repeatCount(repeatCount), currentIndex(0), currentRepeat(0) {} 14 | 15 | bool next(uint32_t& duration, bool& pinState) { 16 | if (currentRepeat >= repeatCount || pulses.empty()) { 17 | return false; 18 | } 19 | 20 | duration = pulses[currentIndex].first; 21 | pinState = pulses[currentIndex].second; 22 | 23 | currentIndex++; 24 | if (currentIndex >= pulses.size()) { 25 | currentIndex = 0; 26 | currentRepeat++; 27 | taskYIELD(); 28 | if (currentRepeat >= repeatCount) { 29 | return false; 30 | } 31 | } 32 | 33 | return true; 34 | } 35 | 36 | private: 37 | std::vector> pulses; 38 | uint32_t repeatCount; 39 | size_t currentIndex; 40 | uint32_t currentRepeat; 41 | }; 42 | 43 | 44 | #endif // PULSE_PAYLOAD_H 45 | -------------------------------------------------------------------------------- /include/Actions.h: -------------------------------------------------------------------------------- 1 | #ifndef Actions_h 2 | #define Actions_h 3 | 4 | #include "ClientsManager.h" 5 | #include "Detector.h" 6 | #include "Cc1101Mode.h" 7 | #include "Cc1101Control.h" 8 | #include "FilesManager.h" 9 | #include "ModuleCc1101.h" 10 | #include "Recorder.h" 11 | #include "StringHelpers.h" 12 | #include "DeviceTasks.h" 13 | #include "Transmitter.h" 14 | #include 15 | #include 16 | 17 | String generateFilename(float frequency, int modulation, float bandwidth); 18 | 19 | namespace Handler { 20 | void signalRecordedHandler(bool saved, std::string filename); 21 | void signalDetectedHandler(DetectedSignal detectedSignal); 22 | void onStateChange(int module, OperationMode mode, OperationMode previousMode); 23 | } 24 | 25 | namespace Action { 26 | void detectSignal(Device::TaskDetectSignal &task); 27 | void recordSignal(Device::TaskRecord &task); 28 | void transmitFromFile(Device::TaskTransmission &task); 29 | void transmitSignal(Device::TaskTransmission &task); 30 | void idle(Device::TaskIdle &task); 31 | void getCurrentState(Device::TaskGetState &task); 32 | void fileOperator(Device::TaskFilesManager &task); 33 | void fileUpload(Device::TaskFileUpload &task); 34 | } 35 | 36 | extern Cc1101Mode deviceModes[]; 37 | extern Transmitter transmitter; 38 | extern Recorder recorder; 39 | extern Detector detector; 40 | 41 | #endif -------------------------------------------------------------------------------- /include/Cc1101Control.h: -------------------------------------------------------------------------------- 1 | #ifndef cc1101_control_h 2 | #define cc1101_control_h 3 | 4 | #include 5 | #include 6 | #include "Cc1101Mode.h" 7 | #include "config.h" 8 | 9 | class Cc1101Control 10 | { 11 | private: 12 | int moduleNumber; 13 | int registeredModesCount; 14 | OperationMode previousMode; 15 | OperationMode currentMode; 16 | Cc1101Mode* registeredModes; 17 | public: 18 | QueueHandle_t eventQueue; 19 | SemaphoreHandle_t stateSemaphore; 20 | TaskHandle_t recordTaskHandle; 21 | TaskHandle_t detectTaskHandle; 22 | 23 | Cc1101Control(); 24 | Cc1101Control(int module, Cc1101Mode modesToRegister[], OperationMode *mode); 25 | Cc1101Control(int module, Cc1101Mode modesToRegister[], OperationMode operationMode); 26 | 27 | bool hasMode(OperationMode operationMode) const; 28 | int getModule() const; 29 | Cc1101Mode getMode(OperationMode operationMode) const; 30 | Cc1101Mode getCurrentMode() const; 31 | Cc1101Mode getPreviousMode() const; 32 | bool isPreviousMode(OperationMode mode) const; 33 | bool isCurrentMode(OperationMode mode) const; 34 | 35 | void init(int module, Cc1101Mode modesToRegister[], OperationMode operationMode); 36 | void switchMode(OperationMode mode); 37 | }; 38 | 39 | extern Cc1101Control cc1101Control[CC1101_NUM_MODULES]; 40 | 41 | #endif -------------------------------------------------------------------------------- /lib/subghz/protocols/Raw.cpp: -------------------------------------------------------------------------------- 1 | #include "Raw.h" 2 | #include // for debugging 3 | 4 | bool RawProtocol::parse(File &file) { 5 | std::string line; 6 | while (file.available()) { 7 | line = file.readStringUntil('\n').c_str(); 8 | if (line.find("RAW_Data:") != std::string::npos) { 9 | std::istringstream iss(line.substr(line.find("RAW_Data:") + 9)); 10 | int32_t duration; 11 | while (iss >> duration) { 12 | if (duration != 0) { 13 | bool pinState = (duration > 0); 14 | pulseData.emplace_back(abs(duration), pinState); 15 | } 16 | } 17 | } 18 | } 19 | return true; 20 | } 21 | 22 | std::vector> RawProtocol::getPulseData() const { 23 | return pulseData; 24 | } 25 | 26 | uint32_t RawProtocol::getRepeatCount() const { 27 | return 1; 28 | } 29 | 30 | std::string RawProtocol::serialize() const { 31 | std::ostringstream oss; 32 | oss << "RAW_Data:"; 33 | for (const auto& pulse : pulseData) { 34 | int32_t duration = pulse.second ? static_cast(pulse.first) : -static_cast(pulse.first); 35 | oss << " " << duration; 36 | } 37 | return oss.str(); 38 | } 39 | 40 | std::unique_ptr createRawProtocol() { 41 | return std::make_unique(); 42 | } 43 | -------------------------------------------------------------------------------- /include/Cc1101Mode.h: -------------------------------------------------------------------------------- 1 | #ifndef Cc1101_Mode_h 2 | #define Cc1101_Mode_h 3 | 4 | #include 5 | 6 | typedef void (*CallbackFunction)(int); 7 | 8 | enum class OperationMode 9 | { 10 | Idle, 11 | DetectSignal, 12 | RecordSignal, 13 | TransmitSignal, 14 | Unknown 15 | }; 16 | 17 | enum class OperationEvent 18 | { 19 | GoIdle, 20 | StartRecord, 21 | StartDetect 22 | }; 23 | 24 | inline const char* modeToString(OperationMode v) 25 | { 26 | switch (v) 27 | { 28 | case OperationMode::Idle: return "Idle"; 29 | case OperationMode::DetectSignal: return "DetectSignal"; 30 | case OperationMode::RecordSignal: return "RecordSignal"; 31 | case OperationMode::TransmitSignal: return "TransmitSignal"; 32 | default: return "Unknown"; 33 | } 34 | } 35 | 36 | class Cc1101Mode 37 | { 38 | friend class Cc1101Control; 39 | 40 | public: 41 | Cc1101Mode(); 42 | Cc1101Mode(OperationMode mode, CallbackFunction onState = nullptr); 43 | 44 | void setup(OperationMode mode, CallbackFunction onState = nullptr); 45 | void setMode(OperationMode mode); 46 | 47 | static void onModeProcess(void *taskParameters); 48 | 49 | OperationMode getMode() const; 50 | bool isMode(OperationMode mode); 51 | 52 | CallbackFunction onMode = nullptr; 53 | protected: 54 | OperationMode mode; 55 | }; 56 | 57 | struct ModeTaskParameters 58 | { 59 | int module; 60 | Cc1101Mode mode; 61 | }; 62 | 63 | #endif -------------------------------------------------------------------------------- /lib/subghz/SubGhzProtocol.h: -------------------------------------------------------------------------------- 1 | #ifndef SUB_GHZ_PROTOCOL_H 2 | #define SUB_GHZ_PROTOCOL_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | // Base SubGhzProtocol class 15 | class SubGhzProtocol { 16 | public: 17 | virtual ~SubGhzProtocol() {} 18 | virtual bool parse(File &file) = 0; 19 | virtual std::vector> getPulseData() const = 0; // Marked as const 20 | virtual uint32_t getRepeatCount() const = 0; // Marked as const 21 | virtual std::string serialize() const = 0; // Marked as const 22 | 23 | static SubGhzProtocol* create(const std::string &name); 24 | static void registerProtocol(const std::string &name, std::unique_ptr (*creator)()); 25 | bool readHexKey(const std::string &value, uint64_t &result); 26 | bool readUint32Decimal(const std::string &value, uint32_t &result); 27 | }; 28 | 29 | // Registry to manage protocols 30 | class SubGhzProtocolRegistry { 31 | public: 32 | static SubGhzProtocolRegistry& instance(); 33 | void registerProtocol(const std::string &name, std::unique_ptr (*creator)()); 34 | SubGhzProtocol* create(const std::string &name); 35 | void printRegisteredProtocols() const; 36 | 37 | private: 38 | std::unordered_map (*)(void)> registry; 39 | }; 40 | 41 | #endif // SUB_GHZ_PROTOCOL_H 42 | -------------------------------------------------------------------------------- /include/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project header files. 3 | 4 | A header file is a file containing C declarations and macro definitions 5 | to be shared between several project source files. You request the use of a 6 | header file in your project source file (C, C++, etc) located in `src` folder 7 | by including it, with the C preprocessing directive `#include'. 8 | 9 | ```src/main.c 10 | 11 | #include "header.h" 12 | 13 | int main (void) 14 | { 15 | ... 16 | } 17 | ``` 18 | 19 | Including a header file produces the same results as copying the header file 20 | into each source file that needs it. Such copying would be time-consuming 21 | and error-prone. With a header file, the related declarations appear 22 | in only one place. If they need to be changed, they can be changed in one 23 | place, and programs that include the header file will automatically use the 24 | new version when next recompiled. The header file eliminates the labor of 25 | finding and changing all the copies as well as the risk that a failure to 26 | find one copy will result in inconsistencies within a program. 27 | 28 | In C, the usual convention is to give header files names that end with `.h'. 29 | It is most portable to use only letters, digits, dashes, and underscores in 30 | header file names, and at most one dot. 31 | 32 | Read more about using header files in official GCC documentation: 33 | 34 | * Include Syntax 35 | * Include Operation 36 | * Once-Only Headers 37 | * Computed Includes 38 | 39 | https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html -------------------------------------------------------------------------------- /include/Detector.h: -------------------------------------------------------------------------------- 1 | #ifndef Detector_h 2 | #define Detector_h 3 | 4 | #include "config.h" 5 | #include "ModuleCc1101.h" 6 | #include 7 | #include 8 | #include 9 | 10 | // SINGAL DETECTION 11 | #define SIGNAL_DETECTION_FREQUENCIES_LENGTH 19 12 | #define DELAY_AFTER_SIGNAL_FOUND 300 13 | 14 | class DetectedSignal { 15 | public: 16 | int rssi; 17 | float frequency; 18 | int module; 19 | bool isBackgroundScanner; 20 | 21 | std::string toJson() const { 22 | std::stringstream jsonString; 23 | jsonString << "{\"module\":\"" << module << "\","; 24 | jsonString << "\"frequency\":\"" << std::fixed << std::setprecision(8) << frequency << "\","; 25 | jsonString << "\"rssi\":\"" << rssi << "\"}"; 26 | return jsonString.str(); 27 | } 28 | }; 29 | 30 | typedef void (*DetectorCallback)(DetectedSignal detectedSignal); 31 | 32 | class Detector 33 | { 34 | private: 35 | DetectorCallback detectorCallback; 36 | static float signalDetectionFrequencies[]; 37 | int signalDetectionMinRssi = -50; 38 | void detectSignal(int module, int minRssi); 39 | public: 40 | Detector(DetectorCallback detectorCallback); 41 | static void start(int module); 42 | static void stop(int module); 43 | static void process(int module); 44 | int getSignalDetectionMinRssi() { return signalDetectionMinRssi; } 45 | void setSignalDetectionMinRssi(int signalDetectionMinRssi) { this->signalDetectionMinRssi = signalDetectionMinRssi; } 46 | }; 47 | 48 | extern Detector detector; 49 | 50 | #endif // Detector_h -------------------------------------------------------------------------------- /include/config.h: -------------------------------------------------------------------------------- 1 | #define DEST_FS_USES_SD 2 | 3 | #define CC1101_NUM_MODULES 2 4 | 5 | #define samplesize 2000 6 | 7 | #if defined(ESP8266) 8 | #define RECEIVE_ATTR ICACHE_RAM_ATTR 9 | #elif defined(ESP32) 10 | #define RECEIVE_ATTR IRAM_ATTR 11 | #else 12 | #define RECEIVE_ATTR 13 | #endif 14 | 15 | #define FILES_RECORDS_PATH "/DATA/RECORDS" 16 | 17 | #define MIN_SAMPLE 30 18 | #define MIN_PULSE_DURATION 50 19 | #define MAX_SIGNAL_DURATION 100000 20 | #define BUFFER_MAX_SIZE 2000 21 | #define FORMAT_ON_FAIL true 22 | 23 | #define FZ_SUB_MAX_SIZE 4096 // should be suficient 24 | #define MAX_LINE_SIZE 4096 25 | #define JSON_DOC_SIZE 4096 26 | 27 | #define SERIAL_BAUDRATE 115200 28 | #define DELAY_BETWEEN_RETRANSMISSIONS 200 29 | 30 | #define WEB_SERVER_PORT 80 31 | 32 | /* I/O */ 33 | // SPI devices 34 | #define SD_SCLK 18 35 | #define SD_MISO 19 36 | #define SD_MOSI 23 37 | #define SD_SS 22 38 | 39 | #define CC1101_SCK 14 40 | #define CC1101_MISO 12 41 | #define CC1101_MOSI 13 42 | #define CC1101_SS0 5 43 | #define CC1101_SS1 27 44 | #define MOD0_GDO0 2 45 | #define MOD0_GDO2 4 46 | #define MOD1_GDO0 25 47 | #define MOD1_GDO2 26 48 | 49 | // Buttons and led 50 | #define LED 32 51 | #define BUTTON1 34 52 | #define BUTTON2 35 53 | 54 | /* HTTP response codes */ 55 | #define HTTP_OK 200 56 | #define HTTP_CREATED 201 57 | #define HTTP_NO_CONTENT 204 58 | #define HTTP_BAD_REQUEST 400 59 | #define HTTP_UNAUTHORIZED 401 60 | #define HTTP_FORBIDDEN 403 61 | #define HTTP_NOT_FOUND 404 62 | #define HTTP_SERVER_ERROR 500 63 | #define HTTP_NOT_IMPLEMENTED 501 64 | 65 | // Tasks params 66 | #define NOTIFICATIONS_QUEUE 10 -------------------------------------------------------------------------------- /src/DeviceControls.cpp: -------------------------------------------------------------------------------- 1 | #include "DeviceControls.h" 2 | 3 | unsigned long DeviceControls::blinkTime = 0; 4 | 5 | void DeviceControls::setup() 6 | { 7 | pinMode(LED, OUTPUT); 8 | pinMode(BUTTON1, INPUT); 9 | pinMode(BUTTON2, INPUT); 10 | } 11 | 12 | void DeviceControls::onLoadPowerManagement() 13 | { 14 | if (digitalRead(BUTTON2) != LOW && digitalRead(BUTTON1) == LOW) { 15 | if (ConfigManager::isSleepMode()) { 16 | goDeepSleep(); 17 | } 18 | } 19 | 20 | if (digitalRead(BUTTON2) == LOW && digitalRead(BUTTON1) == HIGH) { 21 | if (!ConfigManager::isSleepMode()) { 22 | ConfigManager::setSleepMode(1); 23 | goDeepSleep(); 24 | } else { 25 | ConfigManager::setSleepMode(0); 26 | } 27 | } 28 | } 29 | 30 | void DeviceControls::onLoadServiceMode() 31 | { 32 | if (digitalRead(BUTTON1) == LOW && digitalRead(BUTTON2) == LOW) { 33 | if (!ConfigManager::isServiceMode()) { 34 | ConfigManager::setServiceMode(1); 35 | } else { 36 | ConfigManager::setServiceMode(0); 37 | } 38 | } 39 | } 40 | 41 | void DeviceControls::goDeepSleep() 42 | { 43 | for (int i = 0; i < CC1101_NUM_MODULES; i++) { 44 | moduleCC1101State[i].goSleep(); 45 | } 46 | ledBlink(5, 150); 47 | esp_deep_sleep_start(); 48 | } 49 | 50 | void DeviceControls::ledBlink(int count, int pause) 51 | { 52 | for (int i = 0; i < count; i++) { 53 | digitalWrite(LED, HIGH); 54 | delay(pause); 55 | digitalWrite(LED, LOW); 56 | delay(pause); 57 | } 58 | } 59 | 60 | void DeviceControls::poweronBlink() 61 | { 62 | if (millis() - blinkTime > BLINK_OFF_TIME) { 63 | digitalWrite(LED, LOW); 64 | } 65 | if (millis() - blinkTime > BLINK_OFF_TIME + BLINK_ON_TIME) { 66 | digitalWrite(LED, HIGH); 67 | blinkTime = millis(); 68 | } 69 | } -------------------------------------------------------------------------------- /include/Recorder.h: -------------------------------------------------------------------------------- 1 | #ifndef Recorder_h 2 | #define Recorder_h 3 | 4 | #include "config.h" 5 | #include "ModuleCc1101.h" 6 | #include 7 | #include 8 | #include "StringHelpers.h" 9 | #include "FilesManager.h" 10 | #include 11 | #include "FlipperSubFile.h" 12 | 13 | 14 | typedef void (*RecorderCallback)(bool saved, std::string filename); 15 | 16 | // Receive data 17 | typedef struct 18 | { 19 | std::vector samples; 20 | volatile unsigned long lastReceiveTime = 0; 21 | } ReceivedSamples; 22 | 23 | ReceivedSamples &getReceivedData(int module); 24 | 25 | typedef struct 26 | { 27 | float frequency = 433.92; 28 | int modulation = 2; 29 | float deviation = 0; 30 | float rxBandwidth = 650; 31 | float dataRate = 0; 32 | std::string preset; 33 | } RecordConfig; 34 | 35 | class Recorder 36 | { 37 | private: 38 | RecordConfig recordConfig[CC1101_NUM_MODULES]; 39 | 40 | RecorderCallback recorderCallback; 41 | void clearReceivedSamples(int module); 42 | static void receiveSample(int module); 43 | 44 | // unsigned int findPulseDuration(const std::vector& samples); 45 | // void demodulateAndSmooth(const std::vector& samples, RecordedSignal& recordedSignal); 46 | 47 | 48 | public: 49 | Recorder(RecorderCallback recorderCallback); 50 | static void start(int module); 51 | static void stop(int module); 52 | static void process(int module); 53 | static void init(); 54 | static std::string generateFilename(int module, float frequency, int modulation, float bandwidth); 55 | void setRecordConfig(int module, float frequency, int modulation, float deviation, float rxBandwidth, float dataRate, std::string preset); 56 | 57 | static void addModuleReceiver(int module); 58 | static void removeModuleReceiver(int module); 59 | 60 | static portMUX_TYPE muxes[CC1101_NUM_MODULES]; 61 | static void IRAM_ATTR receiver(void* arg); 62 | 63 | }; 64 | 65 | extern Recorder recorder; 66 | 67 | #endif // Recorder_h -------------------------------------------------------------------------------- /src/Detector.cpp: -------------------------------------------------------------------------------- 1 | #include "Detector.h" 2 | 3 | float Detector::signalDetectionFrequencies[] = {300.00, 303.87, 304.25, 310.00, 315.00, 318.00, 390.00, 418.00, 433.07, 4 | 433.92, 434.42, 434.77, 438.90, 868.35, 868.865, 868.95, 915.00, 925.00}; 5 | 6 | Detector::Detector(DetectorCallback detectorCallback) 7 | { 8 | this->detectorCallback = detectorCallback; 9 | } 10 | 11 | void Detector::start(int module) 12 | { 13 | moduleCC1101State[module].setReceiveConfig(Detector::signalDetectionFrequencies[SIGNAL_DETECTION_FREQUENCIES_LENGTH - 1], false, MODULATION_ASK_OOK, 256, 0, 512).initConfig(); 14 | } 15 | 16 | void Detector::stop(int module) 17 | { 18 | moduleCC1101State[module].setSidle(); 19 | moduleCC1101State[module].unlock(); 20 | } 21 | 22 | void Detector::process(int module) 23 | { 24 | Detector::start(module); 25 | while (true) { 26 | if (ulTaskNotifyTake(pdTRUE, 0) == 1) { 27 | break; 28 | } 29 | detector.detectSignal(module, detector.signalDetectionMinRssi); 30 | vTaskDelay(pdMS_TO_TICKS(10)); // 31 | } 32 | Detector::stop(module); 33 | vTaskDelete(NULL); // Delete the task itself 34 | } 35 | 36 | void Detector::detectSignal(int module, int minRssi) 37 | { 38 | int detectedRssi = -100; 39 | float detectedFrequency = 0.0; 40 | 41 | int tries = 0; 42 | while (tries < 2) { 43 | for (float fMhz : Detector::signalDetectionFrequencies) { 44 | moduleCC1101State[module].changeFrequency(fMhz); 45 | vTaskDelay(pdMS_TO_TICKS(1)); 46 | int rssi = moduleCC1101State[module].getRssi(); 47 | 48 | if (rssi >= detectedRssi) { 49 | detectedRssi = rssi; 50 | detectedFrequency = fMhz; 51 | } 52 | } 53 | if (detectedRssi >= minRssi) { 54 | detectorCallback(DetectedSignal{detectedRssi, detectedFrequency, module}); 55 | vTaskDelay(pdMS_TO_TICKS(400)); 56 | return; 57 | } 58 | tries++; 59 | } 60 | } -------------------------------------------------------------------------------- /src/ClientsManager.cpp: -------------------------------------------------------------------------------- 1 | #include "ClientsManager.h" 2 | 3 | ClientsManager& ClientsManager::getInstance() { 4 | static ClientsManager instance; 5 | return instance; 6 | } 7 | 8 | ClientsManager::ClientsManager() : clientsNotificationQueue(nullptr) {} 9 | 10 | 11 | ClientsManager::~ClientsManager() { 12 | if (clientsNotificationQueue != nullptr) { 13 | vQueueDelete(clientsNotificationQueue); 14 | } 15 | } 16 | 17 | void ClientsManager::initializeQueue(size_t queueSize) { 18 | if (clientsNotificationQueue == nullptr) { 19 | clientsNotificationQueue = xQueueCreate(queueSize, sizeof(Notification)); 20 | } 21 | } 22 | 23 | void ClientsManager::addAdapter(ControllerAdapter* adapter) 24 | { 25 | adapters[adapter->getName().c_str()] = adapter; 26 | } 27 | 28 | void ClientsManager::removeAdapter(const std::string& name) 29 | { 30 | adapters.erase(name); 31 | } 32 | 33 | void ClientsManager::notifyAll(NotificationType type, std::string message) 34 | { 35 | String typeName = NotificationTypeToString(type); 36 | for (const auto& pair : adapters) { 37 | pair.second->notify(typeName, message); 38 | } 39 | } 40 | 41 | void ClientsManager::notifyByName(const std::string& name, NotificationType type, std::string message) 42 | { 43 | String typeName = NotificationTypeToString(type); 44 | if (adapters.find(name) != adapters.end()) { 45 | adapters[name]->notify(typeName, message); 46 | } 47 | } 48 | 49 | bool ClientsManager::enqueueMessage(NotificationType type, const std::string& message) 50 | { 51 | if (clientsNotificationQueue == nullptr) { 52 | return false; 53 | } 54 | 55 | Notification notification = {type, message}; 56 | if (xQueueSend(clientsNotificationQueue, ¬ification, portMAX_DELAY) != pdPASS) { 57 | return false; 58 | } 59 | 60 | return true; 61 | } 62 | 63 | void ClientsManager::processMessageQueue(void *taskParameters) { 64 | Notification notification; 65 | 66 | while (true) { 67 | if (xQueueReceive(ClientsManager::getInstance().clientsNotificationQueue, ¬ification, portMAX_DELAY)) { 68 | ClientsManager::getInstance().notifyAll(notification.type, notification.message); 69 | } 70 | vTaskDelay(pdMS_TO_TICKS(10)); 71 | } 72 | } -------------------------------------------------------------------------------- /lib/helpers/StringHelpers.cpp: -------------------------------------------------------------------------------- 1 | #include "StringHelpers.h" 2 | 3 | namespace helpers { 4 | namespace string { 5 | 6 | std::string toLowerCase(const std::string &str) 7 | { 8 | std::string lowerStr = str; 9 | std::transform(lowerStr.begin(), lowerStr.end(), lowerStr.begin(), ::tolower); 10 | return lowerStr; 11 | } 12 | 13 | String toLowerCase(const String &str) 14 | { 15 | String lowerStr = str; 16 | lowerStr.toLowerCase(); 17 | return lowerStr; 18 | } 19 | 20 | std::string toStdString(const String &str) 21 | { 22 | return std::string(str.c_str()); 23 | } 24 | 25 | bool endsWith(const std::string& str, const std::string& suffix) 26 | { 27 | if (suffix.size() > str.size()) return false; 28 | return std::equal(suffix.rbegin(), suffix.rend(), str.rbegin()); 29 | } 30 | 31 | String toArduinoString(const std::string &str) 32 | { 33 | return String(str.c_str()); 34 | } 35 | 36 | std::string escapeJson(const std::string &input) { 37 | std::ostringstream escaped; 38 | for (char c : input) { 39 | switch (c) { 40 | case '"': escaped << "\\\""; break; 41 | case '\\': escaped << "\\\\"; break; 42 | case '\b': escaped << "\\b"; break; 43 | case '\f': escaped << "\\f"; break; 44 | case '\n': escaped << "\\n"; break; 45 | case '\r': escaped << "\\r"; break; 46 | case '\t': escaped << "\\t"; break; 47 | default: 48 | if ('\x00' <= c && c <= '\x1f') { 49 | escaped << "\\u" << std::hex << std::setw(4) << std::setfill('0') << (int)c; 50 | } else { 51 | escaped << c; 52 | } 53 | } 54 | } 55 | return escaped.str(); 56 | } 57 | 58 | String generateRandomString(int length) 59 | { 60 | std::srand(static_cast(std::time(nullptr))); 61 | 62 | const std::string characters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; 63 | 64 | std::stringstream ss; 65 | for (int i = 0; i < length; ++i) { 66 | int randomIndex = std::rand() % characters.size(); 67 | char randomChar = characters[randomIndex]; 68 | ss << randomChar; 69 | } 70 | 71 | return String(ss.str().c_str()); 72 | } 73 | } // namespace string 74 | } // namespace helpers -------------------------------------------------------------------------------- /include/SerialAdapter.h: -------------------------------------------------------------------------------- 1 | #ifndef SerialAdapter_h 2 | #define SerialAdapter_h 3 | 4 | #define COMMAND_BUFFER_LENGTH 512 5 | 6 | #include "ControllerAdapter.h" 7 | #include "ModuleCc1101.h" 8 | #include "HardwareSerial.h" 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include "SimpleCLI.h" 14 | 15 | class SerialAdapter: public ControllerAdapter 16 | { 17 | private: 18 | SerialAdapter() : length(0), seenCR(false), simpleCli(nullptr), serialQueue(NULL) {} // Private constructor for singleton 19 | SerialAdapter(const SerialAdapter&) = delete; 20 | SerialAdapter& operator=(const SerialAdapter&) = delete; 21 | 22 | void processCharacter(char data); 23 | 24 | void registerDetectSignalCommand(SimpleCLI& cli); 25 | void registerRecordSignalCommand(SimpleCLI& cli); 26 | void registerTransmitSignalCommand(SimpleCLI& cli); 27 | void registerGetStateCommand(SimpleCLI& cli); 28 | void registerIdleCommand(SimpleCLI& cli); 29 | void registerFilesListCommand(SimpleCLI& cli); 30 | void registerFileUploadCommand(SimpleCLI& cli); 31 | 32 | static void errorCallback(cmd_error* e); 33 | static void commandDetectSignal(cmd* commandPointer); 34 | static void commandGetState(cmd* commandPointer); 35 | static void commandIdle(cmd* commandPointer); 36 | static void commandRecordSignal(cmd* commandPointer); 37 | static void commandTransmitSignal(cmd* commandPointer); 38 | static void commandGetFilesList(cmd* commandPointer); 39 | static void commandFileUpload(cmd *commandPointer); 40 | static void response(String); 41 | static void error(String); 42 | static void writeResponseMessage(String message); 43 | static bool handleMissingArguments(const std::vector &arguments); 44 | 45 | SimpleCLI* simpleCli; 46 | char buffer[COMMAND_BUFFER_LENGTH]; 47 | int length = 0; 48 | bool seenCR = false; 49 | 50 | QueueHandle_t serialQueue; 51 | static SerialAdapter* instance; 52 | 53 | String getName() override { 54 | return "serial"; 55 | } 56 | public: 57 | static SerialAdapter& getInstance() { 58 | static SerialAdapter instance; 59 | return instance; 60 | } 61 | 62 | void begin(); 63 | static void onSerialData(); 64 | void setup(SimpleCLI& cli); 65 | void processQueue(); 66 | 67 | void notify(String type, std::string message) override; 68 | }; 69 | 70 | #endif -------------------------------------------------------------------------------- /lib/subghz/SubGhzProtocol.cpp: -------------------------------------------------------------------------------- 1 | #include "SubGhzProtocol.h" 2 | #include 3 | 4 | SubGhzProtocolRegistry& SubGhzProtocolRegistry::instance() { 5 | static SubGhzProtocolRegistry instance; 6 | return instance; 7 | } 8 | 9 | void SubGhzProtocolRegistry::registerProtocol(const std::string &name, std::unique_ptr (*creator)()) { 10 | registry[name] = creator; 11 | } 12 | 13 | SubGhzProtocol* SubGhzProtocolRegistry::create(const std::string &name) { 14 | auto it = registry.find(name); 15 | if (it != registry.end()) { 16 | return it->second().release(); 17 | } 18 | return nullptr; 19 | } 20 | 21 | SubGhzProtocol* SubGhzProtocol::create(const std::string &name) { 22 | return SubGhzProtocolRegistry::instance().create(name); 23 | } 24 | 25 | void SubGhzProtocol::registerProtocol(const std::string &name, std::unique_ptr (*creator)()) { 26 | SubGhzProtocolRegistry::instance().registerProtocol(name, creator); 27 | } 28 | 29 | void SubGhzProtocolRegistry::printRegisteredProtocols() const { 30 | Serial.println("Registered protocols:"); 31 | for (const auto& entry : registry) { 32 | Serial.println(entry.first.c_str()); 33 | } 34 | } 35 | 36 | bool hexCharToUint8(char high, char low, uint8_t &value) { 37 | auto hexValue = [](char c) -> uint8_t { 38 | if (c >= '0' && c <= '9') return c - '0'; 39 | if (c >= 'A' && c <= 'F') return c - 'A' + 10; 40 | if (c >= 'a' && c <= 'f') return c - 'a' + 10; 41 | return 0; 42 | }; 43 | 44 | value = (hexValue(high) << 4) | hexValue(low); 45 | return true; 46 | } 47 | 48 | bool SubGhzProtocol::readHexKey(const std::string &value, uint64_t &result) { 49 | const char* hexStr = value.c_str(); 50 | hexStr += strspn(hexStr, " \t"); // Skip any spaces or tabs 51 | 52 | uint8_t byteValue; 53 | result = 0; 54 | 55 | while (*hexStr && *(hexStr + 1)) { 56 | if (hexCharToUint8(*hexStr, *(hexStr + 1), byteValue)) { 57 | result = (result << 8) | byteValue; 58 | } 59 | hexStr += 2; 60 | hexStr += strspn(hexStr, " \t"); // Skip any spaces or tabs between hex pairs 61 | } 62 | 63 | return true; 64 | } 65 | 66 | bool SubGhzProtocol::readUint32Decimal(const std::string &value, uint32_t &result) { 67 | const char* valueStr = value.c_str(); 68 | valueStr += strspn(valueStr, " \t"); // Skip any spaces or tabs 69 | 70 | result = strtoul(valueStr, nullptr, 10); 71 | return true; 72 | } -------------------------------------------------------------------------------- /include/ClientsManager.h: -------------------------------------------------------------------------------- 1 | #ifndef ClientsManager_h 2 | #define ClientsManager_h 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "ControllerAdapter.h" 9 | 10 | enum class NotificationType 11 | { 12 | SignalDetected, 13 | SignalRecorded, 14 | SignalRecordError, 15 | SignalSent, 16 | SignalSendingError, 17 | State, 18 | ModeSwitch, 19 | FileSystem, 20 | FileUpload, 21 | Unknown 22 | }; 23 | 24 | inline const String NotificationTypeToString(NotificationType v) 25 | { 26 | switch (v) { 27 | case NotificationType::SignalDetected: 28 | return "SignalDetected"; 29 | case NotificationType::SignalRecorded: 30 | return "SignalRecorded"; 31 | case NotificationType::SignalRecordError: 32 | return "SignalRecordError"; 33 | case NotificationType::SignalSent: 34 | return "SignalSent"; 35 | case NotificationType::SignalSendingError: 36 | return "SignalSendingError"; 37 | case NotificationType::ModeSwitch: 38 | return "ModeSwitch"; 39 | case NotificationType::FileSystem: 40 | return "FileSystem"; 41 | case NotificationType::FileUpload: 42 | return "FileUpload"; 43 | case NotificationType::State: 44 | return "State"; 45 | default: 46 | return "Unknown"; 47 | } 48 | } 49 | 50 | struct Notification 51 | { 52 | NotificationType type; 53 | std::string message; 54 | 55 | Notification() : type(NotificationType::Unknown), message("") {} 56 | Notification(NotificationType t, std::string m) : type(t), message(m) {} 57 | }; 58 | 59 | class ClientsManager 60 | { 61 | public: 62 | static ClientsManager& getInstance(); 63 | 64 | void addAdapter(ControllerAdapter* adapter); 65 | void removeAdapter(const std::string& name); 66 | void notifyAll(NotificationType type, std::string message); 67 | void notifyByName(const std::string& name, NotificationType type, std::string message); 68 | void initializeQueue(size_t queueSize); 69 | 70 | bool enqueueMessage(NotificationType, const std::string& message); 71 | static void processMessageQueue(void *taskParameters); 72 | 73 | private: 74 | ClientsManager(); 75 | ~ClientsManager(); 76 | ClientsManager(const ClientsManager&) = delete; 77 | ClientsManager& operator=(const ClientsManager&) = delete; 78 | QueueHandle_t clientsNotificationQueue; 79 | std::map adapters; 80 | }; 81 | 82 | #endif // Clients_h -------------------------------------------------------------------------------- /lib/generators/FlipperSubFile.cpp: -------------------------------------------------------------------------------- 1 | #include "FlipperSubFile.h" 2 | 3 | const std::map FlipperSubFile::presetMapping = { 4 | {"Ook270", "FuriHalSubGhzPresetOok270Async"}, 5 | {"Ook650", "FuriHalSubGhzPresetOok650Async"}, 6 | {"2FSKDev238", "FuriHalSubGhzPreset2FSKDev238Async"}, 7 | {"2FSKDev476", "FuriHalSubGhzPreset2FSKDev476Async"}, 8 | {"Custom", "FuriHalSubGhzPresetCustom"} 9 | }; 10 | 11 | void FlipperSubFile::generateRaw( 12 | File& file, 13 | const std::string& presetName, 14 | const std::vector& customPresetData, 15 | std::stringstream& samples, 16 | float frequency 17 | ) { 18 | // Write the header, preset info, and protocol data 19 | writeHeader(file, frequency); 20 | writePresetInfo(file, presetName, customPresetData); 21 | writeRawProtocolData(file, samples); 22 | } 23 | 24 | void FlipperSubFile::writeHeader(File& file, float frequency) { 25 | file.println("Filetype: Flipper SubGhz RAW File"); 26 | file.println("Version: 1"); 27 | file.print("Frequency: "); 28 | file.print(frequency * 1e6, 0); 29 | file.println(); 30 | } 31 | 32 | void FlipperSubFile::writePresetInfo(File& file, const std::string& presetName, const std::vector& customPresetData) { 33 | file.print("Preset: "); 34 | file.println(getPresetName(presetName).c_str()); 35 | 36 | if (presetName == "Custom") { 37 | file.println("Custom_preset_module: CC1101"); 38 | file.print("Custom_preset_data: "); 39 | for (size_t i = 0; i < customPresetData.size(); ++i) { 40 | char hexStr[3]; 41 | sprintf(hexStr, "%02X", customPresetData[i]); 42 | file.print(hexStr); 43 | if (i < customPresetData.size() - 1) { 44 | file.print(" "); 45 | } 46 | } 47 | file.println(); 48 | } 49 | } 50 | 51 | void FlipperSubFile::writeRawProtocolData(File& file, std::stringstream& samples) { 52 | file.println("Protocol: RAW"); 53 | file.print("RAW_Data: "); 54 | 55 | std::string sample; 56 | int wordCount = 0; 57 | 58 | while (getline(samples, sample, ' ')) { 59 | if (wordCount > 0 && wordCount % 512 == 0) { 60 | file.println(); 61 | file.print("RAW_Data: "); 62 | } 63 | file.print(sample.c_str()); 64 | file.print(' '); 65 | wordCount++; 66 | } 67 | 68 | file.println(); 69 | } 70 | 71 | std::string FlipperSubFile::getPresetName(const std::string& preset) { 72 | auto it = presetMapping.find(preset); 73 | if (it != presetMapping.end()) { 74 | return it->second; 75 | } else { 76 | return "FuriHalSubGhzPresetCustom"; 77 | } 78 | } -------------------------------------------------------------------------------- /src/Cc1101Control.cpp: -------------------------------------------------------------------------------- 1 | #include "Cc1101Control.h" 2 | 3 | Cc1101Control cc1101Control[CC1101_NUM_MODULES]; 4 | 5 | Cc1101Control::Cc1101Control() : moduleNumber(0), registeredModes(nullptr), registeredModesCount(0), 6 | currentMode(OperationMode::Unknown), previousMode(OperationMode::Unknown), 7 | eventQueue(nullptr), stateSemaphore(nullptr) {} 8 | 9 | Cc1101Control::Cc1101Control(int module, Cc1101Mode modesToRegister[], OperationMode *mode) 10 | { 11 | if (mode == NULL) return; 12 | Cc1101Control(module, modesToRegister, *mode); 13 | } 14 | 15 | Cc1101Control::Cc1101Control(int module, Cc1101Mode modesToRegister[], OperationMode operationMode) 16 | { 17 | init(module, modesToRegister, operationMode); 18 | } 19 | 20 | bool Cc1101Control::hasMode(OperationMode operationMode) const 21 | { 22 | for (int i = 0; i < registeredModesCount; i++) 23 | { 24 | if (registeredModes[i].mode == operationMode) 25 | return true; 26 | } 27 | return false; 28 | } 29 | 30 | int Cc1101Control::getModule() const 31 | { 32 | return moduleNumber; 33 | } 34 | 35 | Cc1101Mode Cc1101Control::getMode(OperationMode operationMode) const 36 | { 37 | for (int i = 0; i < registeredModesCount; i++) 38 | { 39 | if (registeredModes[i].mode == operationMode) 40 | return registeredModes[i]; 41 | } 42 | 43 | return Cc1101Mode(OperationMode::Unknown); 44 | } 45 | 46 | Cc1101Mode Cc1101Control::getCurrentMode() const 47 | { 48 | return getMode(currentMode); 49 | } 50 | 51 | Cc1101Mode Cc1101Control::getPreviousMode() const 52 | { 53 | return getMode(previousMode); 54 | } 55 | 56 | bool Cc1101Control::isPreviousMode(OperationMode mode) const 57 | { 58 | return mode == previousMode; 59 | } 60 | 61 | bool Cc1101Control::isCurrentMode(OperationMode mode) const 62 | { 63 | return mode == currentMode; 64 | } 65 | 66 | void Cc1101Control::init(int module, Cc1101Mode modesToRegister[], OperationMode operationMode) 67 | { 68 | moduleNumber = module; 69 | registeredModes = modesToRegister; 70 | registeredModesCount = sizeof(registeredModes); 71 | if (!hasMode(operationMode)) 72 | return; 73 | 74 | previousMode = operationMode; 75 | currentMode = operationMode; 76 | eventQueue = xQueueCreate(10, sizeof(OperationMode)); 77 | stateSemaphore = xSemaphoreCreateMutex(); 78 | } 79 | 80 | void Cc1101Control::switchMode(OperationMode operationMode) 81 | { 82 | if (currentMode == operationMode || !hasMode(operationMode)) 83 | return; 84 | 85 | previousMode = currentMode; 86 | Cc1101Mode newMode = getMode(operationMode); 87 | 88 | if (newMode.getMode() == OperationMode::Unknown) { 89 | return; 90 | } 91 | 92 | currentMode = operationMode; 93 | 94 | xQueueSend(eventQueue, &operationMode, portMAX_DELAY); 95 | } -------------------------------------------------------------------------------- /lib/subghz/protocols/BinRAW.cpp: -------------------------------------------------------------------------------- 1 | #include "BinRAW.h" 2 | #include 3 | 4 | bool BinRAWProtocol::parse(File &file) { 5 | pulseData.clear(); 6 | std::string line; 7 | uint32_t bitRaw = 0; 8 | std::vector rawData; 9 | 10 | while (file.available()) { 11 | line = file.readStringUntil('\n').c_str(); 12 | 13 | if (line.find("Bit:") != std::string::npos) { 14 | std::istringstream iss(line.substr(line.find("Bit:") + 4)); 15 | iss >> bitCount; 16 | } else if (line.find("TE:") != std::string::npos) { 17 | std::istringstream iss(line.substr(line.find("TE:") + 3)); 18 | iss >> te; 19 | } else if (line.find("Bit_RAW:") != std::string::npos) { 20 | std::istringstream iss(line.substr(line.find("Bit_RAW:") + 8)); 21 | iss >> bitRaw; 22 | } else if (line.find("Data_RAW:") != std::string::npos) { 23 | std::istringstream iss(line.substr(line.find("Data_RAW:") + 9)); 24 | uint32_t byte; 25 | while (iss >> std::hex >> byte) { 26 | rawData.push_back(static_cast(byte)); 27 | } 28 | generatePulseData(rawData, bitRaw, te); 29 | rawData.clear(); 30 | } 31 | } 32 | return true; 33 | } 34 | 35 | std::vector> BinRAWProtocol::getPulseData() const { 36 | return pulseData; 37 | } 38 | 39 | uint32_t BinRAWProtocol::getRepeatCount() const { 40 | return 1; 41 | } 42 | 43 | std::string BinRAWProtocol::serialize() const { 44 | std::ostringstream oss; 45 | oss << "Protocol: BinRAW\n"; 46 | oss << "Bit: " << bitCount << "\n"; 47 | oss << "TE: " << te << "\n"; 48 | oss << "Bit_RAW: " << pulseData.size() / 2 << "\n"; // Assuming pulseData is always even 49 | oss << "Data_RAW:"; 50 | for (const auto& pulse : pulseData) { 51 | int32_t duration = pulse.second ? static_cast(pulse.first) : -static_cast(pulse.first); 52 | oss << " " << std::hex << duration; 53 | } 54 | return oss.str(); 55 | } 56 | 57 | void BinRAWProtocol::generatePulseData(const std::vector& rawData, uint32_t bitRaw, uint32_t te) const { 58 | bool currentState = false; 59 | uint32_t currentDuration = 0; 60 | 61 | for (uint32_t i = 0; i < bitRaw; ++i) { 62 | bool bit = (rawData[i / 8] >> (7 - (i % 8))) & 0x01; 63 | if (bit == currentState) { 64 | currentDuration += te; 65 | } else { 66 | if (currentDuration > 0) { 67 | pulseData.emplace_back(currentDuration, currentState); 68 | } 69 | currentState = bit; 70 | currentDuration = te; 71 | } 72 | } 73 | // Add the last pulse if it exists 74 | if (currentDuration > 0) { 75 | pulseData.emplace_back(currentDuration, currentState); 76 | } 77 | } 78 | 79 | std::unique_ptr createBinRAWProtocol() { 80 | return std::make_unique(); 81 | } 82 | -------------------------------------------------------------------------------- /include/ModuleCc1101.h: -------------------------------------------------------------------------------- 1 | #ifndef ModuleCc1101State_h 2 | #define ModuleCc1101State_h 3 | 4 | typedef unsigned char byte; 5 | 6 | #include 7 | 8 | #include "CC1101_Radio.h" 9 | #include "config.h" 10 | #include // For std::copy 11 | #include // For std::array 12 | #include "esp_log.h" 13 | 14 | // CC1101 Settings 15 | #define MODULE_1 0 16 | #define MODULE_2 1 17 | 18 | // Modulation types 19 | #define MODULATION_2_FSK 0 20 | #define MODULATION_ASK_OOK 2 21 | 22 | // Available modes 23 | #define MODE_TRANSMIT 1 24 | #define MODE_RECEIVE 0 25 | 26 | typedef struct 27 | { 28 | // float deviation = 1.58; 29 | // float frequency = 433.92; 30 | // int modulation = 2; 31 | // bool dcFilterOff = true; 32 | // float rxBandwidth = 650; 33 | // float dataRate = 3.79372; 34 | // bool transmitMode = false; 35 | float deviation; 36 | float frequency; 37 | int modulation; 38 | bool dcFilterOff; 39 | float rxBandwidth; 40 | float dataRate; 41 | bool transmitMode; 42 | bool initialized = false; 43 | } CC1101ModuleConfig; 44 | 45 | class ModuleCc1101 46 | { 47 | private: 48 | CC1101ModuleConfig config; 49 | CC1101ModuleConfig tmpConfig; 50 | byte id; 51 | byte inputPin; 52 | byte outputPin; 53 | SemaphoreHandle_t stateChangeSemaphore; 54 | static SemaphoreHandle_t rwSemaphore; 55 | 56 | public: 57 | /* 58 | * SPI (Serial Peripheral Interface) pins 59 | * sck - Serial Clock 60 | * miso - Master In Slave Out 61 | * mosi - Master Out Slave In 62 | * ss - Slave Select 63 | * ip - Input pin 64 | * op - Output pin 65 | * module - select cc1101 module 0 or 1 66 | */ 67 | ModuleCc1101(byte sck, byte miso, byte mosi, byte ss, byte io, byte op, byte module); 68 | 69 | ModuleCc1101 backupConfig(); 70 | ModuleCc1101 restoreConfig(); 71 | ModuleCc1101 setConfig(int mode, float frequency, bool dcFilterOff, int modulation, float rxBandwidth, float deviation, float dataRate); 72 | ModuleCc1101 setConfig(CC1101ModuleConfig config); 73 | ModuleCc1101 setReceiveConfig(float frequency, bool dcFilterOff, int modulation, float rxBandwidth, float deviation, float dataRate); 74 | ModuleCc1101 changeFrequency(float frequency); 75 | ModuleCc1101 setTransmitConfig(float frequency, int modulation, float deviation); 76 | ModuleCc1101 initConfig(); 77 | void applySubConfiguration(const uint8_t *byteArray, int length); 78 | void setTx(float frequency); 79 | void sendData(byte *txBuffer, byte size); 80 | 81 | CC1101ModuleConfig getCurrentConfig(); 82 | int getModulation(); 83 | byte getId(); 84 | void init(); 85 | void reset(); 86 | byte getInputPin(); 87 | byte getOutputPin(); 88 | int getRssi(); 89 | byte getLqi(); 90 | void setSidle(); 91 | void goSleep(); 92 | SemaphoreHandle_t getStateChangeSemaphore(); 93 | void unlock(); 94 | byte getRegisterValue(byte address); 95 | std::array getPATableValues(); 96 | void readAllConfigRegisters(byte *buffer, byte num); 97 | float getFrequency(); 98 | }; 99 | 100 | extern ModuleCc1101 moduleCC1101State[]; 101 | 102 | #endif -------------------------------------------------------------------------------- /include/ConfigManager.h: -------------------------------------------------------------------------------- 1 | #ifndef ConfigManager_h 2 | #define ConfigManager_h 3 | 4 | #include 5 | 6 | class ConfigManager 7 | { 8 | public: 9 | static void createDefaultConfig() 10 | { 11 | if (!SPIFFS.exists("/config.txt")) { 12 | File configFile = SPIFFS.open("/config.txt", FILE_WRITE); 13 | if (configFile) { 14 | configFile.println("wifi_mode=ap"); 15 | configFile.println("ssid=Tut RF"); 16 | configFile.println("password=123456789"); 17 | configFile.println("serial_baud_rate=115200"); 18 | configFile.close(); 19 | } 20 | } 21 | } 22 | 23 | static void resetConfigToDefault() 24 | { 25 | SPIFFS.remove("/config.txt"); 26 | createDefaultConfig(); 27 | } 28 | 29 | static bool saveConfig(const String& config) 30 | { 31 | File configFile = SPIFFS.open("/config.txt", FILE_WRITE); 32 | if (configFile) { 33 | configFile.print(config); 34 | configFile.close(); 35 | return true; 36 | } 37 | return false; 38 | } 39 | 40 | static String getPlainConfig() 41 | { 42 | String configData = ""; 43 | if (SPIFFS.exists("/config.txt")) { 44 | File configFile = SPIFFS.open("/config.txt", "r"); 45 | if (configFile) { 46 | while (configFile.available()) { 47 | configData += configFile.readStringUntil('\n'); // Read each line 48 | configData += '\n'; // Ensure each line ends with a newline character 49 | } 50 | configFile.close(); 51 | } 52 | } 53 | return configData; 54 | } 55 | 56 | static String getConfigParam(const String& param) 57 | { 58 | String config = getPlainConfig(); 59 | int paramIndex = config.indexOf(param + "="); 60 | if (paramIndex != -1) { 61 | int endIndex = config.indexOf("\n", paramIndex); 62 | if (endIndex == -1) { // If no newline is found, this is the last line 63 | endIndex = config.length(); 64 | } 65 | String value = config.substring(paramIndex + param.length() + 1, endIndex); 66 | value.trim(); 67 | return value; 68 | } 69 | return ""; 70 | } 71 | 72 | static bool setFlag(const char* path, bool value) 73 | { 74 | if (value) { 75 | File flagFile = SPIFFS.open(path, FILE_WRITE); 76 | if (flagFile) { 77 | flagFile.close(); 78 | return true; 79 | } 80 | } else { 81 | return SPIFFS.remove(path); 82 | } 83 | return false; 84 | } 85 | 86 | static bool isFlagSet(const char* path) 87 | { 88 | return SPIFFS.exists(path); 89 | } 90 | 91 | static void setSleepMode(bool value) 92 | { 93 | setFlag("/sleep_mode.flag", value); 94 | } 95 | 96 | static void setServiceMode(bool value) 97 | { 98 | setFlag("/service_mode.flag", value); 99 | } 100 | 101 | static bool isSleepMode() 102 | { 103 | return isFlagSet("/sleep_mode.flag"); 104 | } 105 | 106 | static bool isServiceMode() 107 | { 108 | return isFlagSet("/service_mode.flag"); 109 | } 110 | }; 111 | 112 | #endif // ConfigManager_h 113 | -------------------------------------------------------------------------------- /lib/subghz/protocols/Princeton.cpp: -------------------------------------------------------------------------------- 1 | #include "Princeton.h" 2 | 3 | std::pair level_duration_make(bool level, uint32_t duration) { 4 | return std::make_pair(duration, level); 5 | } 6 | 7 | bool PrincetonProtocol::parse(File &file) { 8 | char buffer[256]; 9 | while (file.available()) { 10 | int len = file.readBytesUntil('\n', buffer, sizeof(buffer)); 11 | buffer[len] = '\0'; // Null-terminate the buffer 12 | std::string line(buffer); 13 | 14 | std::istringstream iss(line); 15 | std::string key, value; 16 | if (std::getline(iss, key, ':') && std::getline(iss, value)) { 17 | key = key.substr(0, key.find_first_of(" \t")); // Trim key 18 | value = value.substr(value.find_first_not_of(" \t")); // Trim value 19 | 20 | if (key == "Key") { 21 | uint64_t parsedKey; 22 | if (!readHexKey(value, parsedKey)) { 23 | return false; 24 | } 25 | this->key = parsedKey; 26 | } else if (key == "TE") { 27 | uint32_t te; 28 | if (!readUint32Decimal(value, te)) { 29 | return false; 30 | } 31 | this->te = te; 32 | } else if (key == "Repeat") { 33 | uint32_t repeat; 34 | if (!readUint32Decimal(value, repeat)) { 35 | return false; 36 | } 37 | this->repeat = repeat; 38 | } else if (key == "Bit") { 39 | uint32_t bit; 40 | if (!readUint32Decimal(value, bit)) { 41 | return false; 42 | } 43 | this->bit_count = (uint16_t)bit; 44 | } else if (key == "Guard_time") { 45 | uint32_t g_time; 46 | if (readUint32Decimal(value, g_time)) { 47 | if ((g_time >= 15) && (g_time <= 72)) { 48 | 49 | this->guard_time = g_time; 50 | } 51 | } 52 | } 53 | } 54 | } 55 | 56 | return (this->key != 0 && this->te != 0 && this->repeat != 0 && this->bit_count != 0); 57 | } 58 | 59 | std::vector> PrincetonProtocol::getPulseData() const { 60 | if (pulseData.empty()) { 61 | generatePulseData(); 62 | } 63 | return pulseData; 64 | } 65 | 66 | void PrincetonProtocol::generatePulseData() const { 67 | pulseData.clear(); 68 | 69 | size_t index = 0; 70 | size_t payloadSize = (bit_count * 2) + 2; 71 | pulseData.reserve(payloadSize); 72 | 73 | for (int i = bit_count - 1; i >= 0; --i) { 74 | bool bit = (key >> i) & 0x01; 75 | if (bit) { 76 | pulseData.push_back(level_duration_make(true, te * 3)); 77 | pulseData.push_back(level_duration_make(false, te)); 78 | } else { 79 | pulseData.push_back(level_duration_make(true, te)); 80 | pulseData.push_back(level_duration_make(false, te * 3)); 81 | } 82 | } 83 | 84 | // Send Stop bit 85 | pulseData.push_back(level_duration_make(true, te)); 86 | // Send PT_GUARD_TIME 87 | pulseData.push_back(level_duration_make(false, te * guard_time)); 88 | } 89 | 90 | std::string PrincetonProtocol::serialize() const { 91 | std::ostringstream oss; 92 | oss << "Bit: " << bit_count << "\r\n"; 93 | oss << "Key: " << std::hex << key << "\r\n"; 94 | oss << "TE: " << te << "\r\n"; 95 | oss << "Guard_time" << guard_time << "\r\n"; 96 | oss << "Repeat: " << repeat << "\n"; 97 | return oss.str(); 98 | } 99 | 100 | uint32_t PrincetonProtocol::getRepeatCount() const { 101 | return repeat; 102 | } 103 | 104 | std::unique_ptr createPrincetonProtocol() { 105 | return std::make_unique(); 106 | } -------------------------------------------------------------------------------- /include/FilesManager.h: -------------------------------------------------------------------------------- 1 | #ifndef FilesManager_h 2 | #define FilesManager_h 3 | 4 | #include 5 | 6 | #include "SD.h" 7 | 8 | class FilesManager 9 | { 10 | public: 11 | FilesManager() {} 12 | 13 | File open(std::string path, const char* mode) 14 | { 15 | return SD.open(path.c_str(), mode); 16 | } 17 | 18 | std::string listFiles(const std::string& path) 19 | { 20 | File dir = SD.open(path.c_str()); 21 | std::ostringstream fileList; 22 | fileList << "["; 23 | bool firstFile = true; 24 | while (File entry = dir.openNextFile()) { 25 | if (!firstFile) { 26 | fileList << ","; 27 | } 28 | 29 | std::string fullPath(entry.name()); 30 | std::string filename = fullPath.substr(fullPath.find_last_of('/') + 1); 31 | if (!entry.isDirectory()) { 32 | fileList << "{\"name\": \"" << filename << "\", \"size\": " << entry.size() << ", \"date\": \"" << entry.getLastWrite() << "\", \"type\": \"file\"}"; 33 | } else { 34 | fileList << "{\"name\": \"" << filename << "\", \"type\": \"directory\"}"; 35 | } 36 | firstFile = false; 37 | entry.close(); 38 | } 39 | fileList << "]"; 40 | dir.close(); 41 | return fileList.str(); 42 | } 43 | std::string listAllFiles(const std::string& path) 44 | { 45 | File dir = SD.open(path.c_str()); 46 | std::string filesList = listFilesRecursive(dir); 47 | dir.close(); 48 | return filesList; 49 | } 50 | 51 | std::string listFilesRecursive(File dir) 52 | { 53 | std::ostringstream fileList; 54 | fileList << "["; 55 | bool firstFile = true; 56 | while (File entry = dir.openNextFile()) { 57 | if (!firstFile) { 58 | fileList << ","; 59 | } 60 | 61 | std::string fullPath(entry.name()); 62 | std::string filename = fullPath.substr(fullPath.find_last_of('/') + 1); 63 | if (!entry.isDirectory()) { 64 | fileList << "{\"name\": \"" << filename << "\", \"size\": " << entry.size() << ", \"date\": \"" << entry.getLastWrite() << "\", \"type\": \"file\"}"; 65 | } else { 66 | fileList << "{\"name\": \"" << filename << "\", \"type\": \"directory\", \"contains\": " << listFilesRecursive(entry) << "}"; 67 | } 68 | firstFile = false; 69 | entry.close(); 70 | } 71 | fileList << "]"; 72 | return fileList.str(); 73 | } 74 | 75 | std::string getFile(const std::string& filename) 76 | { 77 | 78 | File file = SD.open(String("/DATA/RECORDS/") + filename.c_str(), FILE_READ); 79 | if (!file) { 80 | return "File not found"; 81 | } 82 | 83 | std::ostringstream fileContent; 84 | while (file.available()) { 85 | fileContent << static_cast(file.read()); 86 | } 87 | file.close(); 88 | return fileContent.str(); 89 | } 90 | 91 | bool createDirectory(const std::string& dirPath) 92 | { 93 | String path = String(dirPath.c_str()); 94 | if (!SD.exists(path)) { 95 | return SD.mkdir(path); 96 | } 97 | return false; 98 | } 99 | 100 | bool remove(const std::string& path) 101 | { 102 | if (!SD.exists(path.c_str())) { 103 | return false; 104 | } 105 | File file = SD.open(path.c_str()); 106 | if (file.isDirectory()) { 107 | file.close(); 108 | return SD.rmdir(path.c_str()); 109 | } 110 | file.close(); 111 | return SD.remove(path.c_str()); 112 | } 113 | 114 | bool rename(const std::string& from, const std::string& to) 115 | { 116 | if (!SD.exists(from.c_str()) || SD.exists(to.c_str())) { 117 | return false; 118 | } 119 | return SD.rename(from.c_str(), to.c_str()); 120 | } 121 | }; 122 | 123 | extern FilesManager filesManager; 124 | 125 | #endif -------------------------------------------------------------------------------- /src/Transmitter.cpp: -------------------------------------------------------------------------------- 1 | #include "Transmitter.h" 2 | 3 | FilesManager fm; 4 | 5 | std::vector Transmitter::getCountOfOnOffBits(const std::string &bits) 6 | { 7 | std::vector counts; 8 | char currentBit = bits[0]; 9 | int currentCount = 1; 10 | 11 | for (size_t i = 1; i < bits.size(); i++) { 12 | if (bits[i] == currentBit) { 13 | currentCount++; 14 | } else { 15 | counts.push_back(currentCount); 16 | currentBit = bits[i]; 17 | currentCount = 1; 18 | } 19 | } 20 | 21 | counts.push_back(currentCount); 22 | return counts; 23 | } 24 | 25 | boolean Transmitter::transmitBinary(float frequency, int pulseDuration, const std::string &bits, int module, int modulation, float deviation, int repeatCount, int wait) 26 | { 27 | moduleCC1101State[module].backupConfig().setTransmitConfig(frequency, modulation, deviation).init(); 28 | std::vector countOfOnOffBits = getCountOfOnOffBits(bits); 29 | 30 | for (int r = 0; r < repeatCount; r++) { 31 | for (int i = 0; i < countOfOnOffBits.size(); i++) { 32 | digitalWrite(moduleCC1101State[module].getOutputPin(), i % 2 == 0 ? HIGH : LOW); 33 | delayMicroseconds(countOfOnOffBits[i] * pulseDuration); 34 | } 35 | delay(wait); 36 | } 37 | 38 | moduleCC1101State[module].restoreConfig(); 39 | moduleCC1101State[module].setSidle(); 40 | 41 | return true; 42 | } 43 | 44 | boolean Transmitter::transmitRaw(int module, float frequency, int modulation, float deviation, std::string& data, int repeat) 45 | { 46 | std::vector samples; 47 | std::istringstream stream(data.c_str()); 48 | int sample; 49 | 50 | while (stream >> sample) { 51 | samples.push_back(sample); 52 | } 53 | 54 | moduleCC1101State[module].backupConfig().setTransmitConfig(frequency, modulation, deviation).initConfig(); 55 | for (int r = 0; r < repeat; r++) { 56 | transmitRawData(samples, module); 57 | delay(1); 58 | } 59 | 60 | moduleCC1101State[module].restoreConfig(); 61 | moduleCC1101State[module].setSidle(); 62 | 63 | return true; 64 | } 65 | 66 | bool Transmitter::transmitSub(const std::string& filename, int module, int repeat) 67 | { 68 | std::string fullPath = std::string(FILES_RECORDS_PATH) + "/" + filename; 69 | 70 | File file = fm.open(fullPath.c_str(), FILE_READ); 71 | if (!file) { 72 | return false; 73 | } 74 | SubFileParser parser(file); 75 | parser.parseFile(); 76 | file.close(); 77 | 78 | if (!parser.isModuleCc1101()) { 79 | return false; 80 | } 81 | 82 | PulsePayload payload; 83 | if (!parser.getPayload(payload)) { 84 | // Serial.println("Unsupported protocol or failed to create payload"); 85 | return false; 86 | } 87 | 88 | moduleCC1101State[module].setTx(parser.header.frequency / 1000000.0); 89 | moduleCC1101State[module].applySubConfiguration(parser.moduleParams, 128); 90 | delay(10); 91 | boolean signalTransmitted = false; 92 | signalTransmitted = transmitData(payload, module); 93 | // parser.displayInfo(); 94 | parser.clearMemory(); 95 | 96 | moduleCC1101State[module].restoreConfig().setSidle(); 97 | return signalTransmitted; 98 | } 99 | 100 | boolean Transmitter::transmitRawData(const std::vector &rawData, int module) 101 | { 102 | if (rawData.empty()) { 103 | return false; 104 | } 105 | 106 | for (const auto &rawValue : rawData) { 107 | if (rawValue != 0) { 108 | digitalWrite(moduleCC1101State[module].getOutputPin(), (rawValue > 0)); 109 | delayMicroseconds(abs(rawValue)); 110 | } 111 | } 112 | 113 | return true; 114 | } 115 | 116 | boolean Transmitter::transmitData(PulsePayload &payload, int module) 117 | { 118 | uint32_t duration; 119 | bool pinState; 120 | // std::vector> timingData; 121 | 122 | // unsigned long previousMicros = micros(); 123 | 124 | while (payload.next(duration, pinState)) { 125 | // unsigned long startMicros = micros(); 126 | 127 | digitalWrite(moduleCC1101State[module].getOutputPin(), pinState); 128 | delayMicroseconds(duration); 129 | 130 | // unsigned long endMicros = micros(); 131 | // unsigned long elapsedTime = endMicros - previousMicros; 132 | // previousMicros = endMicros; 133 | 134 | // timingData.emplace_back(duration, elapsedTime); 135 | taskYIELD(); 136 | } 137 | 138 | // for (const auto& data : timingData) { 139 | // Serial.print("Duration: "); 140 | // Serial.print(data.first); 141 | // Serial.print(" - Elapsed Time: "); 142 | // Serial.println(data.second); 143 | // } 144 | 145 | return true; 146 | } -------------------------------------------------------------------------------- /src/Recorder.cpp: -------------------------------------------------------------------------------- 1 | #include "Recorder.h" 2 | 3 | portMUX_TYPE Recorder::muxes[CC1101_NUM_MODULES]; 4 | ReceivedSamples receivedSamples[CC1101_NUM_MODULES]; 5 | 6 | ReceivedSamples &getReceivedData(int module) 7 | { 8 | return receivedSamples[module]; 9 | } 10 | 11 | void Recorder::removeModuleReceiver(int module) 12 | { 13 | detachInterrupt(digitalPinToInterrupt(moduleCC1101State[module].getInputPin())); 14 | } 15 | 16 | void IRAM_ATTR Recorder::receiver(void* arg) 17 | { 18 | int module = reinterpret_cast(arg); 19 | receiveSample(module); 20 | } 21 | 22 | void Recorder::receiveSample(int module) 23 | { 24 | const long time = micros(); 25 | portENTER_CRITICAL(&Recorder::muxes[module]); 26 | ReceivedSamples &data = getReceivedData(module); 27 | 28 | if (data.lastReceiveTime == 0) { 29 | data.lastReceiveTime = time; 30 | portEXIT_CRITICAL(&Recorder::muxes[module]); 31 | return; 32 | } 33 | 34 | const unsigned int duration = time - data.lastReceiveTime; 35 | data.lastReceiveTime = time; 36 | 37 | if (duration > MAX_SIGNAL_DURATION) { 38 | data.samples.clear(); 39 | portEXIT_CRITICAL(&Recorder::muxes[module]); 40 | return; 41 | } 42 | 43 | if (duration >= MIN_PULSE_DURATION) { 44 | data.samples.push_back(duration); 45 | } 46 | 47 | portEXIT_CRITICAL(&Recorder::muxes[module]); 48 | 49 | if (data.samples.size() >= samplesize) { 50 | removeModuleReceiver(module); 51 | } 52 | } 53 | 54 | void Recorder::addModuleReceiver(int module) 55 | { 56 | attachInterruptArg(moduleCC1101State[module].getInputPin(), receiver, reinterpret_cast(module), CHANGE); 57 | } 58 | 59 | std::string Recorder::generateFilename(int module, float frequency, int modulation, float bandwidth) 60 | { 61 | char filenameBuffer[100]; 62 | 63 | sprintf(filenameBuffer, "m%d_%d_%s_%d_%s.sub", module, static_cast(frequency * 100), modulation == MODULATION_ASK_OOK ? "AM" : "FM", static_cast(bandwidth), 64 | helpers::string::generateRandomString(8).c_str()); 65 | 66 | return std::string(filenameBuffer); 67 | } 68 | 69 | Recorder::Recorder(RecorderCallback recorderCallback) 70 | { 71 | this->recorderCallback = recorderCallback; 72 | } 73 | 74 | void Recorder::init() 75 | { 76 | for (int i = 0; i < CC1101_NUM_MODULES; ++i) { 77 | Recorder::muxes[i] = portMUX_INITIALIZER_UNLOCKED; 78 | } 79 | } 80 | 81 | void Recorder::start(int module) 82 | { 83 | moduleCC1101State[module] 84 | .setReceiveConfig( 85 | recorder.recordConfig[module].frequency, 86 | recorder.recordConfig[module].modulation == MODULATION_2_FSK ? true : false, 87 | recorder.recordConfig[module].modulation, 88 | recorder.recordConfig[module].rxBandwidth, 89 | recorder.recordConfig[module].deviation, 90 | recorder.recordConfig[module].dataRate 91 | ).initConfig(); 92 | 93 | recorder.clearReceivedSamples(module); 94 | addModuleReceiver(module); 95 | } 96 | 97 | void Recorder::stop(int module) 98 | { 99 | removeModuleReceiver(module); 100 | moduleCC1101State[module].setSidle(); 101 | moduleCC1101State[module].unlock(); 102 | } 103 | 104 | void Recorder::process(int module) 105 | { 106 | Recorder::start(module); 107 | 108 | while (true) { 109 | if (ulTaskNotifyTake(pdTRUE, 0) == 1) { 110 | break; 111 | } 112 | 113 | std::vector samplesCopy; 114 | unsigned long lastReceiveTime; 115 | 116 | portENTER_CRITICAL_ISR(&Recorder::muxes[module]); 117 | ReceivedSamples &data = getReceivedData(module); 118 | samplesCopy = data.samples; 119 | lastReceiveTime = data.lastReceiveTime; 120 | portEXIT_CRITICAL_ISR(&Recorder::muxes[module]); 121 | 122 | size_t samplecount = samplesCopy.size(); 123 | 124 | if (samplecount >= MIN_SAMPLE && micros() - lastReceiveTime > MAX_SIGNAL_DURATION) { 125 | removeModuleReceiver(module); 126 | std::stringstream rawSignal; 127 | 128 | for (int i = 0; i < samplecount; i++) { 129 | rawSignal << (i > 0 ? (i % 2 == 1 ? " -" : " ") : ""); 130 | rawSignal << samplesCopy[i]; 131 | } 132 | 133 | RecordConfig& config = recorder.recordConfig[module]; 134 | std::string filename = Recorder::generateFilename(module, config.frequency, config.modulation, config.rxBandwidth); 135 | File outputFile; 136 | std::string fullPath = "/DATA/RECORDS/" + filename; 137 | outputFile = SD.open(fullPath.c_str(), FILE_WRITE); 138 | if (outputFile) { 139 | const std::string preset = config.preset; 140 | std::vector customPresetData; 141 | if (preset == "Custom") { 142 | ModuleCc1101& m = moduleCC1101State[module]; 143 | customPresetData.insert(customPresetData.end(), { 144 | CC1101_MDMCFG4, m.getRegisterValue(CC1101_MDMCFG4), 145 | CC1101_MDMCFG3, m.getRegisterValue(CC1101_MDMCFG3), 146 | CC1101_MDMCFG2, m.getRegisterValue(CC1101_MDMCFG2), 147 | CC1101_DEVIATN, m.getRegisterValue(CC1101_DEVIATN), 148 | CC1101_FREND0, m.getRegisterValue(CC1101_FREND0), 149 | 0x00, 0x00 150 | }); 151 | 152 | std::array paTable = m.getPATableValues(); 153 | customPresetData.insert(customPresetData.end(), paTable.begin(), paTable.end()); 154 | } 155 | FlipperSubFile::generateRaw(outputFile, preset.empty() ? "Custom" : preset, customPresetData, rawSignal, config.frequency); 156 | outputFile.close(); 157 | } else { 158 | // @todo: Log/send error 159 | } 160 | recorder.recorderCallback(true, filename); 161 | 162 | recorder.clearReceivedSamples(module); 163 | addModuleReceiver(module); 164 | } 165 | 166 | vTaskDelay(pdMS_TO_TICKS(100)); 167 | } 168 | 169 | Recorder::stop(module); 170 | recorder.clearReceivedSamples(module); // Clear samples AFTER detaching interrupt handler 171 | vTaskDelete(NULL); // Delete the task itself 172 | } 173 | 174 | void Recorder::clearReceivedSamples(int module) 175 | { 176 | portENTER_CRITICAL_ISR(&Recorder::muxes[module]); 177 | getReceivedData(module).samples.clear(); 178 | // getReceivedData(module).samplesmooth.clear(); 179 | getReceivedData(module).lastReceiveTime = 0; 180 | portEXIT_CRITICAL(&Recorder::muxes[module]); 181 | } 182 | 183 | void Recorder::setRecordConfig(int module, float frequency, int modulation, float deviation, float rxBandwidth, float dataRate, std::string preset) 184 | { 185 | recordConfig[module].preset = preset; 186 | recordConfig[module].frequency = frequency; 187 | recordConfig[module].modulation = modulation; 188 | recordConfig[module].deviation = deviation; 189 | recordConfig[module].rxBandwidth = rxBandwidth; 190 | recordConfig[module].dataRate = dataRate; 191 | } 192 | 193 | // unsigned int Recorder::findPulseDuration(const std::vector &samples) 194 | // { 195 | // unsigned long minDuration = *std::min_element(samples.begin(), samples.end()); 196 | // unsigned long maxDuration = minDuration; 197 | 198 | // for (unsigned long sample : samples) { 199 | // if (sample <= minDuration + MIN_PULSE_DURATION) { 200 | // maxDuration = std::max(maxDuration, sample); 201 | // } 202 | // } 203 | 204 | // return round((minDuration + maxDuration) / 2); 205 | // } 206 | 207 | // void Recorder::demodulateAndSmooth(const std::vector &samples, RecordedSignal &recordedSignal) 208 | // { 209 | // unsigned int pulseDuration = findPulseDuration(samples); 210 | 211 | // std::stringstream demodulated; 212 | // std::stringstream smoothed; 213 | // bool lastbin = false; 214 | 215 | // for (int i = 0; i < samples.size(); i++) { 216 | // int bitCount = static_cast(round(static_cast(samples[i]) / pulseDuration)); 217 | // if (bitCount > 0) { 218 | // lastbin = !lastbin; 219 | // demodulated << std::string(bitCount, lastbin ? '1' : '0'); 220 | // } 221 | // smoothed << (i > 0 ? (i % 2 == 1 ? " -" : " ") : "") << (bitCount * pulseDuration); 222 | // } 223 | // recordedSignal.pulseDuration = pulseDuration; 224 | // recordedSignal.binary = String(demodulated.str().c_str()); 225 | // recordedSignal.smoothed = String(smoothed.str().c_str()); 226 | // return; 227 | // } 228 | -------------------------------------------------------------------------------- /lib/cc1101/CC1101_Radio.h: -------------------------------------------------------------------------------- 1 | /* 2 | Based on ELECHOUSE_CC1101.cpp - CC1101 module library 3 | */ 4 | #ifndef CC1101_Radio_h 5 | #define CC1101_Radio_h 6 | 7 | #include 8 | 9 | //***************************************CC1101 define**************************************************// 10 | // CC1101 CONFIG REGSITER 11 | #define CC1101_IOCFG2 0x00 // GDO2 output pin configuration 12 | #define CC1101_IOCFG1 0x01 // GDO1 output pin configuration 13 | #define CC1101_IOCFG0 0x02 // GDO0 output pin configuration 14 | #define CC1101_FIFOTHR 0x03 // RX FIFO and TX FIFO thresholds 15 | #define CC1101_SYNC1 0x04 // Sync word, high INT8U 16 | #define CC1101_SYNC0 0x05 // Sync word, low INT8U 17 | #define CC1101_PKTLEN 0x06 // Packet length 18 | #define CC1101_PKTCTRL1 0x07 // Packet automation control 19 | #define CC1101_PKTCTRL0 0x08 // Packet automation control 20 | #define CC1101_ADDR 0x09 // Device address 21 | #define CC1101_CHANNR 0x0A // Channel number 22 | #define CC1101_FSCTRL1 0x0B // Frequency synthesizer control 23 | #define CC1101_FSCTRL0 0x0C // Frequency synthesizer control 24 | #define CC1101_FREQ2 0x0D // Frequency control word, high INT8U 25 | #define CC1101_FREQ1 0x0E // Frequency control word, middle INT8U 26 | #define CC1101_FREQ0 0x0F // Frequency control word, low INT8U 27 | #define CC1101_MDMCFG4 0x10 // Modem configuration 28 | #define CC1101_MDMCFG3 0x11 // Modem configuration 29 | #define CC1101_MDMCFG2 0x12 // Modem configuration 30 | #define CC1101_MDMCFG1 0x13 // Modem configuration 31 | #define CC1101_MDMCFG0 0x14 // Modem configuration 32 | #define CC1101_DEVIATN 0x15 // Modem deviation setting 33 | #define CC1101_MCSM2 0x16 // Main Radio Control State Machine configuration 34 | #define CC1101_MCSM1 0x17 // Main Radio Control State Machine configuration 35 | #define CC1101_MCSM0 0x18 // Main Radio Control State Machine configuration 36 | #define CC1101_FOCCFG 0x19 // Frequency Offset Compensation configuration 37 | #define CC1101_BSCFG 0x1A // Bit Synchronization configuration 38 | #define CC1101_AGCCTRL2 0x1B // AGC control 39 | #define CC1101_AGCCTRL1 0x1C // AGC control 40 | #define CC1101_AGCCTRL0 0x1D // AGC control 41 | #define CC1101_WOREVT1 0x1E // High INT8U Event 0 timeout 42 | #define CC1101_WOREVT0 0x1F // Low INT8U Event 0 timeout 43 | #define CC1101_WORCTRL 0x20 // Wake On Radio control 44 | #define CC1101_FREND1 0x21 // Front end RX configuration 45 | #define CC1101_FREND0 0x22 // Front end TX configuration 46 | #define CC1101_FSCAL3 0x23 // Frequency synthesizer calibration 47 | #define CC1101_FSCAL2 0x24 // Frequency synthesizer calibration 48 | #define CC1101_FSCAL1 0x25 // Frequency synthesizer calibration 49 | #define CC1101_FSCAL0 0x26 // Frequency synthesizer calibration 50 | #define CC1101_RCCTRL1 0x27 // RC oscillator configuration 51 | #define CC1101_RCCTRL0 0x28 // RC oscillator configuration 52 | #define CC1101_FSTEST 0x29 // Frequency synthesizer calibration control 53 | #define CC1101_PTEST 0x2A // Production test 54 | #define CC1101_AGCTEST 0x2B // AGC test 55 | #define CC1101_TEST2 0x2C // Various test settings 56 | #define CC1101_TEST1 0x2D // Various test settings 57 | #define CC1101_TEST0 0x2E // Various test settings 58 | 59 | //CC1101 Strobe commands 60 | #define CC1101_SRES 0x30 // Reset chip. 61 | #define CC1101_SFSTXON 0x31 // Enable and calibrate frequency synthesizer (if MCSM0.FS_AUTOCAL=1). 62 | // If in RX/TX: Go to a wait state where only the synthesizer is 63 | // running (for quick RX / TX turnaround). 64 | #define CC1101_SXOFF 0x32 // Turn off crystal oscillator. 65 | #define CC1101_SCAL 0x33 // Calibrate frequency synthesizer and turn it off 66 | // (enables quick start). 67 | #define CC1101_SRX 0x34 // Enable RX. Perform calibration first if coming from IDLE and 68 | // MCSM0.FS_AUTOCAL=1. 69 | #define CC1101_STX 0x35 // In IDLE state: Enable TX. Perform calibration first if 70 | // MCSM0.FS_AUTOCAL=1. If in RX state and CCA is enabled: 71 | // Only go to TX if channel is clear. 72 | #define CC1101_SIDLE 0x36 // Exit RX / TX, turn off frequency synthesizer and exit 73 | // Wake-On-Radio mode if applicable. 74 | #define CC1101_SAFC 0x37 // Perform AFC adjustment of the frequency synthesizer 75 | #define CC1101_SWOR 0x38 // Start automatic RX polling sequence (Wake-on-Radio) 76 | #define CC1101_SPWD 0x39 // Enter power down mode when CSn goes high. 77 | #define CC1101_SFRX 0x3A // Flush the RX FIFO buffer. 78 | #define CC1101_SFTX 0x3B // Flush the TX FIFO buffer. 79 | #define CC1101_SWORRST 0x3C // Reset real time clock. 80 | #define CC1101_SNOP 0x3D // No operation. May be used to pad strobe commands to two 81 | // INT8Us for simpler software. 82 | //CC1101 STATUS REGSITER 83 | #define CC1101_PARTNUM 0x30 84 | #define CC1101_VERSION 0x31 85 | #define CC1101_FREQEST 0x32 86 | #define CC1101_LQI 0x33 87 | #define CC1101_RSSI 0x34 88 | #define CC1101_MARCSTATE 0x35 89 | #define CC1101_WORTIME1 0x36 90 | #define CC1101_WORTIME0 0x37 91 | #define CC1101_PKTSTATUS 0x38 92 | #define CC1101_VCO_VC_DAC 0x39 93 | #define CC1101_TXBYTES 0x3A 94 | #define CC1101_RXBYTES 0x3B 95 | 96 | //CC1101 PATABLE,TXFIFO,RXFIFO 97 | #define CC1101_PATABLE 0x3E 98 | #define CC1101_TXFIFO 0x3F 99 | #define CC1101_RXFIFO 0x3F 100 | 101 | //************************************* class **************************************************// 102 | class CC1101_Radio 103 | { 104 | private: 105 | void SpiStart(void); 106 | void SpiEnd(void); 107 | void GDO_Set (void); 108 | void GDO0_Set (void); 109 | void setSpi(void); 110 | void Calibrate(void); 111 | void Split_PKTCTRL0(void); 112 | void Split_PKTCTRL1(void); 113 | void Split_MDMCFG1(void); 114 | void Split_MDMCFG2(void); 115 | void Split_MDMCFG4(void); 116 | public: 117 | void Init(void); 118 | void Reset (void); 119 | void RegConfigSettings(void); 120 | byte SpiReadStatus(byte addr); 121 | void setSpiPin(byte sck, byte miso, byte mosi, byte ss); 122 | void addSpiPin(byte sck, byte miso, byte mosi, byte ss, byte modul); 123 | void setGDO(byte gdo0, byte gdo2); 124 | void setGDO0(byte gdo0); 125 | void addGDO(byte gdo0, byte gdo2, byte modul); 126 | void addGDO0(byte gdo0, byte modul); 127 | void setModul(byte modul); 128 | void setCCMode(bool s); 129 | void setModulation(byte m); 130 | void setPA(int p); 131 | void setMHZ(float mhz); 132 | void setChannel(byte chnl); 133 | void setChsp(float f); 134 | void setRxBW(float f); 135 | void setDRate(float d); 136 | void setDeviation(float d); 137 | void SetTx(void); 138 | void SetRx(void); 139 | void SetTx(float mhz); 140 | void SetRx(float mhz); 141 | int getRssi(void); 142 | byte getLqi(void); 143 | void setSres(void); 144 | void setSidle(void); 145 | void goSleep(void); 146 | void SendData(byte *txBuffer, byte size); 147 | void SendData(char *txchar); 148 | void SendData(byte *txBuffer, byte size, int t); 149 | void SendData(char *txchar, int t); 150 | byte CheckReceiveFlag(void); 151 | byte ReceiveData(byte *rxBuffer); 152 | bool CheckCRC(void); 153 | void SpiStrobe(byte strobe); 154 | void SpiWriteReg(byte addr, byte value); 155 | void SpiWriteBurstReg(byte addr, byte *buffer, byte num); 156 | byte SpiReadReg(byte addr); 157 | void SpiReadBurstReg(byte addr, byte *buffer, byte num); 158 | void setClb(byte b, byte s, byte e); 159 | bool getCC1101(void); 160 | byte getMode(void); 161 | void setSyncWord(byte sh, byte sl); 162 | void setAddr(byte v); 163 | void setWhiteData(bool v); 164 | void setPktFormat(byte v); 165 | void setCrc(bool v); 166 | void setLengthConfig(byte v); 167 | void setPacketLength(byte v); 168 | void setDcFilterOff(bool v); 169 | void setManchester(bool v); 170 | void setSyncMode(byte v); 171 | void setFEC(bool v); 172 | void setPRE(byte v); 173 | void setPQT(byte v); 174 | void setCRC_AF(bool v); 175 | void setAppendStatus(bool v); 176 | void setAdrChk(byte v); 177 | bool CheckRxFifo(int t); 178 | byte getState(); 179 | float getFrequency(); 180 | void selectModule(byte module); 181 | }; 182 | 183 | extern CC1101_Radio cc1101; 184 | 185 | #endif 186 | -------------------------------------------------------------------------------- /src/ModuleCc1101.cpp: -------------------------------------------------------------------------------- 1 | #include "ModuleCc1101.h" 2 | 3 | SemaphoreHandle_t ModuleCc1101::rwSemaphore = xSemaphoreCreateMutex(); 4 | 5 | static const char* TAG = "Cc1101Config"; 6 | 7 | ModuleCc1101 moduleCC1101State[] = {ModuleCc1101(CC1101_SCK, CC1101_MISO, CC1101_MOSI, CC1101_SS0, MOD0_GDO2, MOD0_GDO0, MODULE_1), 8 | ModuleCc1101(CC1101_SCK, CC1101_MISO, CC1101_MOSI, CC1101_SS1, MOD1_GDO2, MOD1_GDO0, MODULE_2)}; 9 | 10 | ModuleCc1101::ModuleCc1101(byte sck, byte miso, byte mosi, byte ss, byte ip, byte op, byte module) 11 | { 12 | stateChangeSemaphore = xSemaphoreCreateBinary(); 13 | cc1101.addSpiPin(sck, miso, mosi, ss, module); 14 | cc1101.addGDO(op, ip, module); 15 | inputPin = ip; 16 | outputPin = op; 17 | id = module; 18 | } 19 | 20 | SemaphoreHandle_t ModuleCc1101::getStateChangeSemaphore() 21 | { 22 | return stateChangeSemaphore; 23 | } 24 | 25 | void ModuleCc1101::unlock() 26 | { 27 | xSemaphoreGive(stateChangeSemaphore); 28 | } 29 | 30 | ModuleCc1101 ModuleCc1101::backupConfig() 31 | { 32 | tmpConfig = config; 33 | return *this; 34 | } 35 | 36 | ModuleCc1101 ModuleCc1101::restoreConfig() 37 | { 38 | config = tmpConfig; 39 | return *this; 40 | } 41 | 42 | ModuleCc1101 ModuleCc1101::setConfig(int mode, float frequency, bool dcFilterOff, int modulation, float rxBandwidth, float deviation, float dataRate) 43 | { 44 | config.transmitMode = mode == MODE_TRANSMIT; 45 | config.frequency = frequency; 46 | config.deviation = deviation; 47 | config.modulation = modulation; 48 | config.dcFilterOff = dcFilterOff; 49 | config.rxBandwidth = rxBandwidth; 50 | config.dataRate = dataRate; 51 | 52 | return *this; 53 | } 54 | 55 | ModuleCc1101 ModuleCc1101::setConfig(CC1101ModuleConfig config) 56 | { 57 | this->config = config; 58 | return *this; 59 | } 60 | 61 | ModuleCc1101 ModuleCc1101::setReceiveConfig(float frequency, bool dcFilterOff, int modulation, float rxBandwidth, float deviation, float dataRate) 62 | { 63 | if (config.frequency == frequency && config.dcFilterOff == dcFilterOff && config.modulation == modulation && config.rxBandwidth == rxBandwidth && 64 | config.deviation == deviation && config.dataRate == dataRate) { 65 | ESP_LOGD(TAG, "Config not changed"); 66 | 67 | return *this; 68 | } 69 | 70 | ESP_LOGD(TAG, "Config changed"); 71 | config.transmitMode = false; 72 | config.frequency = frequency; 73 | config.deviation = deviation; 74 | config.modulation = modulation; 75 | config.dcFilterOff = dcFilterOff; 76 | config.rxBandwidth = rxBandwidth; 77 | config.dataRate = dataRate; 78 | return *this; 79 | } 80 | 81 | ModuleCc1101 ModuleCc1101::changeFrequency(float frequency) 82 | { 83 | xSemaphoreTake(rwSemaphore, portMAX_DELAY); 84 | config.frequency = frequency; 85 | cc1101.setModul(id); 86 | cc1101.setSidle(); 87 | cc1101.setMHZ(frequency); 88 | cc1101.SetRx(); 89 | cc1101.setDRate(config.dataRate); 90 | cc1101.setRxBW(config.rxBandwidth); 91 | xSemaphoreGive(rwSemaphore); 92 | return *this; 93 | } 94 | 95 | ModuleCc1101 ModuleCc1101::setTransmitConfig(float frequency, int modulation, float deviation) 96 | { 97 | config.transmitMode = true; 98 | config.frequency = frequency; 99 | config.deviation = deviation; 100 | config.modulation = modulation; 101 | return *this; 102 | } 103 | 104 | CC1101ModuleConfig ModuleCc1101::getCurrentConfig() 105 | { 106 | return config; 107 | } 108 | 109 | byte ModuleCc1101::getId() 110 | { 111 | return id; 112 | } 113 | 114 | int ModuleCc1101::getModulation() 115 | { 116 | return config.modulation; 117 | } 118 | 119 | void ModuleCc1101::init() 120 | { 121 | xSemaphoreTake(rwSemaphore, portMAX_DELAY); 122 | cc1101.setModul(id); 123 | cc1101.Init(); 124 | xSemaphoreGive(rwSemaphore); 125 | } 126 | 127 | ModuleCc1101 ModuleCc1101::initConfig() 128 | { 129 | xSemaphoreTake(rwSemaphore, portMAX_DELAY); 130 | cc1101.setModul(id); 131 | cc1101.setModulation(config.modulation); // set modulation mode. 0 = 2-FSK, 1 = GFSK, 2 = ASK/OOK, 3 = 4-FSK, 4 = MSK. 132 | cc1101.setDeviation(config.deviation); // Set the Frequency deviation in kHz. Value from 1.58 to 380.85. Default is 47.60 kHz. 133 | cc1101.setMHZ(config.frequency); 134 | 135 | if (config.transmitMode) { 136 | cc1101.SetTx(); 137 | } else { 138 | cc1101.SetRx(); 139 | delay(1); 140 | cc1101.setDcFilterOff(config.dcFilterOff); 141 | cc1101.setSyncMode(0); // Combined sync-word qualifier mode. 0 = No preamble/sync. 1 = 16 sync word bits detected. 2 = 16/16 sync word bits detected. 3 = 142 | // 30/32 sync word bits detected. 4 = No preamble/sync, carrier-sense above threshold. 5 = 15/16 + carrier-sense above threshold. 6 143 | // = 16/16 + carrier-sense above threshold. 7 = 30/32 + carrier-sense above threshold. 144 | cc1101.setPktFormat(3); // Format of RX and TX data. 0 = Normal mode, use FIFOs for RX and TX. 1 = Synchronous serial mode, Data in on GDO0 and data out on 145 | // either of the GDOx pins. 2 = Random TX mode; sends random data using PN9 generator. Used for t. Works as normal mode, setting 0 146 | // (00), in RX. 3 = Asynchronous serial mode, Data in on GDO0 and data out on either of the GDOx pins. 147 | cc1101.setDRate(config.dataRate); 148 | cc1101.setRxBW(config.rxBandwidth); 149 | } 150 | xSemaphoreGive(rwSemaphore); 151 | 152 | return *this; 153 | } 154 | 155 | void ModuleCc1101::setTx(float frequency) 156 | { 157 | xSemaphoreTake(rwSemaphore, portMAX_DELAY); 158 | cc1101.setModul(id); 159 | cc1101.setSidle(); 160 | cc1101.Init(); 161 | cc1101.setMHZ(frequency); 162 | cc1101.SetTx(); 163 | xSemaphoreGive(rwSemaphore); 164 | } 165 | 166 | void ModuleCc1101::applySubConfiguration(const uint8_t *byteArray, int length) 167 | { 168 | int index = 0; 169 | 170 | xSemaphoreTake(rwSemaphore, portMAX_DELAY); 171 | while (index < length) { 172 | uint8_t addr = byteArray[index++]; 173 | uint8_t value = byteArray[index++]; 174 | 175 | if (addr == 0x00 && value == 0x00) { 176 | break; 177 | } 178 | cc1101.SpiWriteReg(addr, value); 179 | } 180 | 181 | std::array paValue; 182 | std::copy(byteArray + index, byteArray + index + paValue.size(), paValue.begin()); 183 | cc1101.SpiWriteBurstReg(CC1101_PATABLE, paValue.data(), paValue.size()); 184 | xSemaphoreGive(rwSemaphore); 185 | } 186 | 187 | byte ModuleCc1101::getInputPin() 188 | { 189 | return inputPin; 190 | } 191 | 192 | byte ModuleCc1101::getOutputPin() 193 | { 194 | return outputPin; 195 | } 196 | 197 | int ModuleCc1101::getRssi() 198 | { 199 | xSemaphoreTake(rwSemaphore, portMAX_DELAY); 200 | cc1101.setModul(id); 201 | int rssi = cc1101.getRssi(); 202 | xSemaphoreGive(rwSemaphore); 203 | return rssi; 204 | } 205 | 206 | byte ModuleCc1101::getLqi() 207 | { 208 | xSemaphoreTake(rwSemaphore, portMAX_DELAY); 209 | cc1101.setModul(id); 210 | byte lqi = cc1101.getLqi(); 211 | xSemaphoreGive(rwSemaphore); 212 | return lqi; 213 | } 214 | 215 | void ModuleCc1101::setSidle() 216 | { 217 | xSemaphoreTake(rwSemaphore, portMAX_DELAY); 218 | cc1101.setModul(id); 219 | cc1101.setSidle(); 220 | xSemaphoreGive(rwSemaphore); 221 | } 222 | 223 | void ModuleCc1101::reset() 224 | { 225 | xSemaphoreTake(rwSemaphore, portMAX_DELAY); 226 | cc1101.setModul(id); 227 | cc1101.setSres(); 228 | xSemaphoreGive(rwSemaphore); 229 | } 230 | 231 | void ModuleCc1101::goSleep() 232 | { 233 | xSemaphoreTake(rwSemaphore, portMAX_DELAY); 234 | cc1101.setModul(id); 235 | cc1101.goSleep(); 236 | xSemaphoreGive(rwSemaphore); 237 | } 238 | 239 | void ModuleCc1101::sendData(byte *txBuffer, byte size) 240 | { 241 | xSemaphoreTake(rwSemaphore, portMAX_DELAY); 242 | cc1101.setModul(id); 243 | cc1101.SendData(txBuffer, size); 244 | xSemaphoreGive(rwSemaphore); 245 | } 246 | 247 | byte ModuleCc1101::getRegisterValue(byte address) 248 | { 249 | xSemaphoreTake(rwSemaphore, portMAX_DELAY); 250 | cc1101.setModul(id); 251 | byte value = cc1101.SpiReadReg(address); 252 | xSemaphoreGive(rwSemaphore); 253 | return value; 254 | } 255 | 256 | std::array ModuleCc1101::getPATableValues() 257 | { 258 | xSemaphoreTake(rwSemaphore, portMAX_DELAY); 259 | std::array paTable; 260 | cc1101.setModul(id); 261 | cc1101.SpiReadBurstReg(0x3E, paTable.data(), paTable.size()); 262 | xSemaphoreGive(rwSemaphore); 263 | return paTable; 264 | } 265 | 266 | void ModuleCc1101::readAllConfigRegisters(byte *buffer, byte num) 267 | { 268 | xSemaphoreTake(rwSemaphore, portMAX_DELAY); 269 | cc1101.selectModule(id); 270 | cc1101.SpiReadBurstReg(0x00, buffer, num); // 0x00 is the start address for configuration registers 271 | xSemaphoreGive(rwSemaphore); 272 | } 273 | 274 | float ModuleCc1101::getFrequency() 275 | { 276 | float fq; 277 | xSemaphoreTake(rwSemaphore, portMAX_DELAY); 278 | cc1101.setModul(id); 279 | fq = cc1101.getFrequency(); // 0x00 is the start address for configuration registers 280 | xSemaphoreGive(rwSemaphore); 281 | return fq; 282 | } -------------------------------------------------------------------------------- /include/SubFileParser.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include // For std::find 5 | #include // For std::array 6 | #include // For std::string 7 | #include 8 | #include 9 | #include "SubGhzProtocol.h" 10 | #include "protocols/Princeton.h" 11 | #include "PulsePayload.h" 12 | 13 | const uint8_t subghz_device_cc1101_preset_ook_270khz_async_regs[] = {0x02, 0x0D, 0x03, 0x07, 0x08, 0x32, 0x0B, 0x06, 0x14, 0x00, 0x13, 0x00, 0x12, 0x30, 0x11, 14 | 0x32, 0x10, 0x17, 0x18, 0x18, 0x19, 0x18, 0x1D, 0x91, 0x1C, 0x00, 0x1B, 0x07, 0x20, 0xFB, 15 | 0x22, 0x11, 0x21, 0xB6, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 16 | 17 | const uint8_t subghz_device_cc1101_preset_ook_650khz_async_regs[] = {0x02, 0x0D, 0x03, 0x07, 0x08, 0x32, 0x0B, 0x06, 0x14, 0x00, 0x13, 0x00, 0x12, 0x30, 0x11, 18 | 0x32, 0x10, 0x17, 0x18, 0x18, 0x19, 0x18, 0x1D, 0x91, 0x1C, 0x00, 0x1B, 0x07, 0x20, 0xFB, 19 | 0x22, 0x11, 0x21, 0xB6, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 20 | 21 | const uint8_t subghz_device_cc1101_preset_2fsk_dev2_38khz_async_regs[] = {0x02, 0x0D, 0x03, 0x07, 0x08, 0x32, 0x0B, 0x06, 0x14, 0x00, 0x13, 0x00, 0x12, 0x30, 0x11, 22 | 0x32, 0x10, 0x17, 0x18, 0x18, 0x19, 0x18, 0x1D, 0x91, 0x1C, 0x00, 0x1B, 0x07, 0x20, 0xFB, 23 | 0x22, 0x11, 0x21, 0xB6, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 24 | 25 | const uint8_t subghz_device_cc1101_preset_2fsk_dev47_6khz_async_regs[] = {0x02, 0x0D, 0x03, 0x07, 0x08, 0x32, 0x0B, 0x06, 0x14, 0x00, 0x13, 0x00, 0x12, 0x30, 0x11, 26 | 0x32, 0x10, 0x17, 0x18, 0x18, 0x19, 0x18, 0x1D, 0x91, 0x1C, 0x00, 0x1B, 0x07, 0x20, 0xFB, 27 | 0x22, 0x11, 0x21, 0xB6, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 28 | 29 | const uint8_t subghz_device_cc1101_preset_msk_99_97kb_async_regs[] = {0x02, 0x0D, 0x03, 0x07, 0x08, 0x32, 0x0B, 0x06, 0x14, 0x00, 0x13, 0x00, 0x12, 0x30, 0x11, 30 | 0x32, 0x10, 0x17, 0x18, 0x18, 0x19, 0x18, 0x1D, 0x91, 0x1C, 0x00, 0x1B, 0x07, 0x20, 0xFB, 31 | 0x22, 0x11, 0x21, 0xB6, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 32 | 33 | const uint8_t subghz_device_cc1101_preset_gfsk_9_99kb_async_regs[] = {0x02, 0x0D, 0x03, 0x07, 0x08, 0x32, 0x0B, 0x06, 0x14, 0x00, 0x13, 0x00, 0x12, 0x30, 0x11, 34 | 0x32, 0x10, 0x17, 0x18, 0x18, 0x19, 0x18, 0x1D, 0x91, 0x1C, 0x00, 0x1B, 0x07, 0x20, 0xFB, 35 | 0x22, 0x11, 0x21, 0xB6, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 36 | 37 | struct SubFileHeader 38 | { 39 | String filetype; 40 | uint32_t version; 41 | uint32_t frequency; 42 | }; 43 | 44 | struct SubFilePreset 45 | { 46 | String preset; 47 | String customPresetModule; 48 | String customPresetData; 49 | }; 50 | 51 | struct SubFileData 52 | { 53 | String protocol; 54 | std::vector rawData; 55 | uint32_t bitLength; 56 | String key; 57 | uint32_t te; 58 | }; 59 | 60 | const std::array cc1101Presets = { 61 | "FuriHalSubGhzPresetOok270Async", "FuriHalSubGhzPresetOok650Async", "FuriHalSubGhzPreset2FSKDev238Async", "FuriHalSubGhzPreset2FSKDev476Async" 62 | }; 63 | 64 | class SubFileParser 65 | { 66 | public: 67 | SubFileHeader header; 68 | SubFilePreset preset; 69 | SubFileData data; 70 | uint8_t moduleParams[128]; 71 | 72 | SubFileParser(File file) : file(file) 73 | { 74 | clearMemory(); 75 | } 76 | 77 | void displayInfo() 78 | { 79 | Serial.println("Filetype: " + header.filetype); 80 | Serial.println("Version: " + String(header.version)); 81 | Serial.println("Frequency: " + String(header.frequency)); 82 | Serial.println("Preset: " + preset.preset); 83 | if (!preset.customPresetModule.isEmpty()) { 84 | Serial.println("Custom Preset Module: " + preset.customPresetModule); 85 | } 86 | if (!preset.customPresetData.isEmpty()) { 87 | Serial.println("Custom Preset Data: " + preset.customPresetData); 88 | } 89 | Serial.println("Protocol: " + data.protocol); 90 | 91 | printModuleParams(); 92 | } 93 | 94 | void clearMemory() 95 | { 96 | // Clear dynamically allocated memory 97 | data.rawData.clear(); 98 | memset(moduleParams, 0, sizeof(moduleParams)); 99 | } 100 | 101 | bool isModuleCc1101() 102 | { 103 | return (preset.preset == "FuriHalSubGhzPresetCustom" && preset.customPresetModule == "CC1101") || inCc1101Presets(); 104 | } 105 | 106 | bool parseFile() { 107 | readFile(); 108 | 109 | if (!file) { 110 | return false; 111 | } 112 | 113 | if (data.protocol.isEmpty()) { 114 | return false; 115 | } 116 | Serial.println(data.protocol.c_str()); 117 | protocol.reset(SubGhzProtocol::create(data.protocol.c_str())); 118 | 119 | if (!protocol) { 120 | Serial.println("no protocol found"); 121 | SubGhzProtocolRegistry::instance().printRegisteredProtocols(); 122 | return false; 123 | } 124 | file.seek(0); 125 | bool result = protocol->parse(file); 126 | 127 | return result; 128 | } 129 | 130 | bool getPayload(PulsePayload &payload) const { 131 | if (!protocol) { 132 | return false; 133 | } 134 | const auto& pulseData = protocol->getPulseData(); 135 | // Serial.println(protocol->serialize().c_str()); 136 | payload = PulsePayload(pulseData, protocol->getRepeatCount()); 137 | 138 | return true; 139 | } 140 | 141 | private: 142 | File file; 143 | std::unique_ptr protocol; 144 | 145 | void readFile() 146 | { 147 | if (!file) { 148 | return; 149 | } 150 | 151 | while (file.available()) { 152 | String line = file.readStringUntil('\n'); 153 | if (line.endsWith("\r")) { 154 | line.remove(line.length() - 1); 155 | } 156 | parseLine(line); 157 | } 158 | 159 | handlePreset(); 160 | } 161 | 162 | void parseLine(const String &line) 163 | { 164 | if (line.startsWith("Filetype:")) { 165 | header.filetype = parseValue(line); 166 | } else if (line.startsWith("Version:")) { 167 | header.version = parseValue(line).toInt(); 168 | } else if (line.startsWith("Frequency:")) { 169 | header.frequency = parseValue(line).toInt(); 170 | } else if (line.startsWith("Preset:")) { 171 | preset.preset = parseValue(line); 172 | } else if (line.startsWith("Custom_preset_module:")) { 173 | preset.customPresetModule = parseValue(line); 174 | } else if (line.startsWith("Custom_preset_data:")) { 175 | preset.customPresetData = parseValue(line); 176 | } else if (line.startsWith("Protocol:")) { 177 | data.protocol = parseValue(line); 178 | } else { 179 | return; 180 | } 181 | } 182 | 183 | String parseValue(const String &line) 184 | { 185 | int pos = line.indexOf(':'); 186 | if (pos == -1) { 187 | return ""; 188 | } 189 | String val = line.substring(pos + 1); 190 | val.trim(); 191 | return val; 192 | } 193 | 194 | void handlePreset() 195 | { 196 | if (preset.preset == "FuriHalSubGhzPresetCustom") { 197 | parseCustomPresetData(preset.customPresetData); 198 | } else { 199 | const uint8_t *byteArray = getByteArrayForPreset(preset.preset); 200 | if (byteArray != nullptr) { 201 | for (int i = 0; i < 44; i++) { 202 | moduleParams[i] = byteArray[i]; 203 | } 204 | } 205 | } 206 | } 207 | 208 | void parseRawDataLine(const String &rawValues) 209 | { 210 | int start = 0; 211 | while (start < rawValues.length()) { 212 | int spaceIndex = rawValues.indexOf(' ', start); 213 | if (spaceIndex == -1) { 214 | spaceIndex = rawValues.length(); 215 | } 216 | 217 | String numberStr = rawValues.substring(start, spaceIndex); 218 | int value = numberStr.toInt(); 219 | data.rawData.push_back(value); 220 | 221 | start = spaceIndex + 1; 222 | } 223 | } 224 | 225 | const uint8_t *getByteArrayForPreset(const String &preset) 226 | { 227 | if (preset == "FuriHalSubGhzPresetOok270Async") { 228 | return subghz_device_cc1101_preset_ook_270khz_async_regs; 229 | } else if (preset == "FuriHalSubGhzPresetOok650Async") { 230 | return subghz_device_cc1101_preset_ook_650khz_async_regs; 231 | } else if (preset == "FuriHalSubGhzPreset2FSKDev238Async") { 232 | return subghz_device_cc1101_preset_2fsk_dev2_38khz_async_regs; 233 | } else if (preset == "FuriHalSubGhzPreset2FSKDev476Async") { 234 | return subghz_device_cc1101_preset_2fsk_dev47_6khz_async_regs; 235 | } 236 | 237 | return nullptr; 238 | } 239 | 240 | bool inCc1101Presets() 241 | { 242 | return std::find(cc1101Presets.begin(), cc1101Presets.end(), preset.preset.c_str()) != cc1101Presets.end(); 243 | } 244 | 245 | void parseCustomPresetData(const String &customData) 246 | { 247 | int length = countBytes(customData); 248 | 249 | int startIndex = 0; 250 | int endIndex = customData.indexOf(' ', startIndex); 251 | int index = 0; 252 | 253 | while (endIndex > 0) { 254 | String byteStr = customData.substring(startIndex, endIndex); 255 | moduleParams[index++] = strtol(byteStr.c_str(), NULL, 16); 256 | startIndex = endIndex + 1; 257 | endIndex = customData.indexOf(' ', startIndex); 258 | } 259 | 260 | // Get the last byte 261 | String byteStr = customData.substring(startIndex); 262 | moduleParams[index++] = strtol(byteStr.c_str(), NULL, 16); 263 | } 264 | 265 | int countBytes(const String &data) 266 | { 267 | int count = 0; 268 | int startIndex = 0; 269 | int endIndex = data.indexOf(' ', startIndex); 270 | 271 | while (endIndex > 0) { 272 | count++; 273 | startIndex = endIndex + 1; 274 | endIndex = data.indexOf(' ', startIndex); 275 | } 276 | 277 | // Count the last byte 278 | count++; 279 | return count; 280 | } 281 | 282 | void printModuleParams() 283 | { 284 | size_t size = sizeof(moduleParams); 285 | Serial.println("Module Params:"); 286 | for (size_t i = 0; i < size; ++i) { 287 | if (i % 16 == 0) { // Print 16 bytes per line 288 | Serial.println(); 289 | } 290 | if (moduleParams[i] < 0x10) { 291 | Serial.print("0"); // Add leading zero for single digit hex values 292 | } 293 | Serial.print(moduleParams[i], HEX); 294 | Serial.print(" "); 295 | } 296 | Serial.println(); 297 | } 298 | }; -------------------------------------------------------------------------------- /src/WebAdapter.cpp: -------------------------------------------------------------------------------- 1 | #include "WebAdapter.h" 2 | 3 | WebAdapter webAdapter; 4 | 5 | WebAdapter::WebAdapter() : server(nullptr), events(nullptr) {} 6 | 7 | String getContentType(const String& path) { 8 | if (path.endsWith(".html.gz")) return "text/html"; 9 | else if (path.endsWith(".css.gz")) return "text/css"; 10 | else if (path.endsWith(".js.gz")) return "application/javascript"; 11 | else if (path.endsWith(".png")) return "image/png"; 12 | else if (path.endsWith(".jpg")) return "image/jpeg"; 13 | else if (path.endsWith(".ico")) return "image/x-icon"; 14 | else if (path.endsWith(".xml")) return "text/xml"; 15 | else if (path.endsWith(".pdf")) return "application/pdf"; 16 | else if (path.endsWith(".zip")) return "application/zip"; 17 | else if (path.endsWith(".woff2.gz")) return "font/woff2"; // Added woff2 MIME type 18 | return "text/plain"; 19 | } 20 | 21 | void WebAdapter::begin(SDFS sd) 22 | { 23 | server = new AsyncWebServer(WEB_SERVER_PORT); 24 | events = new AsyncEventSource("/events"); 25 | events->onConnect([this](AsyncEventSourceClient *client) { onEventsConnect(); }); 26 | 27 | server->addHandler(events); 28 | DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*"); 29 | DefaultHeaders::Instance().addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); 30 | DefaultHeaders::Instance().addHeader("Access-Control-Allow-Headers", "Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With"); 31 | 32 | server->begin(); 33 | 34 | handleGetState(); 35 | handleDetectSignal(); 36 | handleIdle(); 37 | handleRecordSignal(); 38 | handleTransmitSignal(); 39 | handleTransmitFromFile(); 40 | handleFilesManagement(); 41 | handleFileUpload(); 42 | 43 | server->onNotFound([&sd](AsyncWebServerRequest *request) { 44 | String path = request->url(); 45 | String pathWithGz = path + ".gz"; 46 | 47 | if (sd.exists(pathWithGz)) { 48 | AsyncWebServerResponse *response = request->beginResponse(SD, pathWithGz, getContentType(path)); 49 | response->addHeader("Content-Encoding", "gzip"); 50 | response->addHeader("Cache-Control", "max-age=86400"); 51 | request->send(response); 52 | } else if (sd.exists(path)) { 53 | request->send(sd, path, getContentType(path)); 54 | } else { 55 | request->send(404, "text/plain", "File Not Found"); 56 | } 57 | }); 58 | } 59 | 60 | void WebAdapter::initStatic(SDFS sd) 61 | { 62 | if (!staticServed) { 63 | server->serveStatic("/", sd, "/HTML").setDefaultFile("index.html").setCacheControl("max-age=86400"); 64 | staticServed = true; 65 | } 66 | } 67 | 68 | void WebAdapter::handleGetState() 69 | { 70 | server->on("/getstate", HTTP_GET, [this](AsyncWebServerRequest *request) { 71 | Device::TaskGetState task(true); 72 | request->send(HTTP_NO_CONTENT, "text/json", ""); 73 | sendTask(std::move(task)); 74 | }); 75 | } 76 | 77 | void WebAdapter::handleDetectSignal() 78 | { 79 | server->on("/detect", HTTP_GET, [this](AsyncWebServerRequest *request) { 80 | Device::TaskDetectSignalBuilder taskBuilder; 81 | 82 | if (request->hasArg("minrssi")) 83 | taskBuilder.setMinRssi(request->arg("minrssi").toInt()); 84 | if (request->hasArg("module")) 85 | taskBuilder.setModule(request->arg("module").toInt() == 2 ? MODULE_2 : MODULE_1); 86 | 87 | Device::TaskDetectSignal task = taskBuilder.build(); 88 | request->send(HTTP_NO_CONTENT, "text/json", ""); 89 | sendTask(std::move(task)); 90 | }); 91 | } 92 | 93 | void WebAdapter::handleIdle() 94 | { 95 | server->on("/idle", HTTP_GET, [this](AsyncWebServerRequest *request) { 96 | request->send(HTTP_NO_CONTENT, "text/json", ""); 97 | int module = MODULE_1; 98 | if (request->hasArg("module")) { 99 | module = request->arg("module").toInt() == 2 ? MODULE_2 : MODULE_1; 100 | } 101 | Device::TaskIdle task(module); 102 | sendTask(std::move(task)); 103 | }); 104 | } 105 | 106 | void WebAdapter::handleRecordSignal() 107 | { 108 | server->on("/record", HTTP_GET, [this](AsyncWebServerRequest *request) { 109 | std::vector requiredParams = {"frequency"}; 110 | if (!this->handleMissingParams(request, requiredParams)) { 111 | return; 112 | } 113 | 114 | Device::TaskRecordBuilder taskBuilder(request->arg("frequency").toFloat()); 115 | 116 | if (request->hasArg("module")) 117 | taskBuilder.setModule(request->arg("module").toInt() == 2 ? MODULE_2 : MODULE_1); 118 | 119 | if (request->hasArg("preset")) { 120 | taskBuilder.setPreset(std::string(request->arg("preset").c_str())); 121 | } else { 122 | if (request->hasArg("modulation")) 123 | taskBuilder.setModulation(request->arg("modulation").toInt()); 124 | if (request->hasArg("deviation")) 125 | taskBuilder.setDeviation(request->arg("deviation").toFloat()); 126 | if (request->hasArg("rxbandwidth")) 127 | taskBuilder.setRxBandwidth(request->arg("rxbandwidth").toFloat()); 128 | if (request->hasArg("datarate")) 129 | taskBuilder.setDataRate(request->arg("datarate").toFloat()); 130 | } 131 | 132 | request->send(HTTP_NO_CONTENT, "text/json", ""); 133 | Device::TaskRecord task = taskBuilder.build(); 134 | sendTask(std::move(task)); 135 | }); 136 | } 137 | 138 | void WebAdapter::handleTransmitSignal() 139 | { 140 | server->on("/transmit/signal", HTTP_GET, [this](AsyncWebServerRequest *request) { 141 | std::vector requiredParams = {"frequency", "type", "data"}; 142 | if (!this->handleMissingParams(request, requiredParams)) { 143 | return; 144 | } 145 | 146 | Device::TaskTransmissionBuilder *taskBuilder = nullptr; 147 | 148 | if (request->arg("type") == "raw") { 149 | taskBuilder = new Device::TaskTransmissionBuilder(Device::TransmissionType::Raw); 150 | } 151 | 152 | if (taskBuilder != nullptr) { 153 | taskBuilder->setFrequency(request->arg("frequency").toFloat()); 154 | taskBuilder->setData(request->arg("data").c_str()); 155 | if (request->hasArg("module")) { 156 | taskBuilder->setModule(request->arg("module").toInt() == 2 ? MODULE_2 : MODULE_1); 157 | } else { 158 | taskBuilder->setModule(MODULE_1); 159 | } 160 | if (request->hasArg("preset")) { 161 | taskBuilder->setPreset(request->arg("preset").c_str()); 162 | } else { 163 | if (request->hasArg("modulation")) 164 | taskBuilder->setModulation(request->arg("modulation").toInt()); 165 | if (request->hasArg("deviation")) 166 | taskBuilder->setDeviation(request->arg("deviation").toFloat()); 167 | } 168 | 169 | if (request->hasArg("repeat")) 170 | taskBuilder->setRepeat(request->arg("repeat").toInt()); 171 | 172 | Device::TaskTransmission task = taskBuilder->build(); 173 | 174 | request->send(HTTP_NO_CONTENT, "text/json", ""); 175 | sendTask(std::move(task)); 176 | } else { 177 | request->send(HTTP_BAD_REQUEST, "text/json", "Invalid transmission type"); 178 | } 179 | }); 180 | } 181 | 182 | void WebAdapter::handleTransmitFromFile() 183 | { 184 | server->on("/transmit/file", HTTP_GET, [this](AsyncWebServerRequest *request) { 185 | std::vector requiredParams = {"file"}; 186 | if (!this->handleMissingParams(request, requiredParams)) { 187 | return; 188 | } 189 | 190 | Device::TaskTransmission task = Device::TaskTransmissionBuilder(Device::TransmissionType::File).setFilename(request->arg("file").c_str()).build(); 191 | request->send(HTTP_NO_CONTENT, "text/json", ""); 192 | sendTask(std::move(task)); 193 | }); 194 | } 195 | 196 | void WebAdapter::handleFilesManagement() 197 | { 198 | server->on("/file/list", HTTP_GET, [this](AsyncWebServerRequest *request) { 199 | Device::TaskFilesManager task(Device::TaskFilesManagerAction::List); 200 | if (request->hasArg("path")) 201 | task.path = request->arg("path").c_str(); 202 | 203 | request->send(HTTP_NO_CONTENT, "text/json", ""); 204 | sendTask(std::move(task)); 205 | }); 206 | 207 | server->on("/file/get", HTTP_GET, [this](AsyncWebServerRequest *request) { 208 | std::vector requiredParams = {"file"}; 209 | if (!this->handleMissingParams(request, requiredParams)) { 210 | return; 211 | } 212 | Device::TaskFilesManager task = Device::TaskFilesManager(Device::TaskFilesManagerAction::Load, request->arg("file").c_str()); 213 | request->send(HTTP_NO_CONTENT, "text/json", ""); 214 | sendTask(std::move(task)); 215 | }); 216 | 217 | server->on("/file/create-directory", HTTP_GET, [this](AsyncWebServerRequest *request) { 218 | std::vector requiredParams = {"path"}; 219 | if (!this->handleMissingParams(request, requiredParams)) { 220 | return; 221 | } 222 | Device::TaskFilesManager task = Device::TaskFilesManager(Device::TaskFilesManagerAction::CreateDirectory, request->arg("path").c_str()); 223 | request->send(HTTP_NO_CONTENT, "text/json", ""); 224 | sendTask(task); 225 | }); 226 | 227 | server->on("/file/delete", HTTP_GET, [this](AsyncWebServerRequest *request) { 228 | std::vector requiredParams = {"path"}; 229 | if (!this->handleMissingParams(request, requiredParams)) { 230 | return; 231 | } 232 | Device::TaskFilesManager task = Device::TaskFilesManager(Device::TaskFilesManagerAction::Delete, request->arg("path").c_str()); 233 | request->send(HTTP_NO_CONTENT, "text/json", ""); 234 | sendTask(task); 235 | }); 236 | 237 | server->on("/file/rename", HTTP_GET, [this](AsyncWebServerRequest *request) { 238 | std::vector requiredParams = {"from", "to"}; 239 | if (!this->handleMissingParams(request, requiredParams)) { 240 | return; 241 | } 242 | Device::TaskFilesManager task = Device::TaskFilesManager(Device::TaskFilesManagerAction::Rename, request->arg("from").c_str(), request->arg("to").c_str()); 243 | request->send(HTTP_NO_CONTENT, "text/json", ""); 244 | sendTask(task); 245 | }); 246 | } 247 | 248 | void WebAdapter::handleFileUpload() 249 | { 250 | server->on( 251 | "/upload", HTTP_POST, [](AsyncWebServerRequest *request) {}, 252 | [this](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { 253 | Device::TaskFileUpload task(filename.c_str(), Device::FileUploadType::File, index, data, len, final); 254 | request->send(HTTP_NO_CONTENT, "text/json", ""); 255 | sendTask(std::move(task)); 256 | }); 257 | } 258 | 259 | void WebAdapter::notify(String type, std::string message) 260 | { 261 | String response; 262 | if (!message.empty()) { 263 | if (message[0] != '{' && message[0] != '[') { 264 | response = "{\"type\":\"" + type + "\", \"data\":\"" + String(message.c_str()) + "\"}"; 265 | } else { 266 | response = "{\"type\":\"" + type + "\", \"data\":" + String(message.c_str()) + "}"; 267 | } 268 | events->send(response.c_str(), "message", millis()); 269 | } else { 270 | // Serial.println("message is empty"); 271 | } 272 | } 273 | 274 | void WebAdapter::onEventsConnect() 275 | { 276 | notify("DeviceStatus", "connected"); 277 | } 278 | 279 | bool WebAdapter::handleMissingParams(AsyncWebServerRequest *request, const std::vector ¶ms) 280 | { 281 | std::vector missingParams; 282 | for (const auto ¶m : params) { 283 | if (!request->hasArg(param.c_str())) { 284 | missingParams.push_back(param); 285 | } 286 | } 287 | 288 | if (!missingParams.empty()) { 289 | String errorMessage = "{\"error\":\"Missing parameters: "; 290 | for (size_t i = 0; i < missingParams.size(); ++i) { 291 | errorMessage += missingParams[i]; 292 | if (i < missingParams.size() - 1) { 293 | errorMessage += ", "; 294 | } 295 | } 296 | errorMessage += "\"}"; 297 | request->send(HTTP_OK, "text/json", errorMessage); 298 | return false; 299 | } 300 | 301 | return true; 302 | } -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include "Actions.h" 14 | #include "AllProtocols.h" 15 | #include "ClientsManager.h" 16 | #include "ConfigManager.h" 17 | #include "DeviceControls.h" 18 | #include "FS.h" 19 | #include "SD.h" 20 | #include "SPI.h" 21 | #include "SerialAdapter.h" 22 | #include "ServiceMode.h" 23 | #include "WebAdapter.h" 24 | #include "config.h" 25 | #include "esp_log.h" 26 | 27 | static const char* TAG = "Setup"; 28 | 29 | // Constants 30 | const int WIFI_AP_STARTED_BIT = BIT0; 31 | const int MAX_RETRIES = 5; 32 | 33 | // Global variables 34 | EventGroupHandle_t wifiEventGroup; 35 | SemaphoreHandle_t wifiEventMutex; 36 | bool webAdapterStarted = false; 37 | void onWiFiEvent(WiFiEvent_t event); 38 | 39 | SimpleCLI cli; 40 | SPIClass sdspi(VSPI); 41 | 42 | bool webAdapterStared = false; 43 | 44 | // Wifi parameters 45 | const int wifi_channel = 12; 46 | 47 | // Device settings 48 | struct DeviceConfig 49 | { 50 | bool powerBlink; 51 | } deviceConfig; 52 | 53 | void cc1101StateTask(void* parameters) 54 | { 55 | Cc1101Control* cc1101Control = (Cc1101Control*)parameters; 56 | if (cc1101Control == nullptr) { 57 | ESP_LOGE(TAG, "cc1101Control is nullptr"); 58 | vTaskDelete(nullptr); 59 | } 60 | 61 | OperationMode event; 62 | ModeTaskParameters taskParameters; 63 | 64 | while (true) { 65 | if (xQueueReceive(cc1101Control->eventQueue, &event, portMAX_DELAY) == pdPASS) { 66 | if (cc1101Control->stateSemaphore == nullptr) { 67 | ESP_LOGE(TAG, "stateSemaphore is nullptr"); 68 | vTaskDelete(nullptr); 69 | } 70 | xSemaphoreTake(cc1101Control->stateSemaphore, portMAX_DELAY); 71 | 72 | if (cc1101Control->isPreviousMode(OperationMode::RecordSignal) && cc1101Control->recordTaskHandle != nullptr) { 73 | xTaskNotifyGive(cc1101Control->recordTaskHandle); 74 | if (xSemaphoreTake(moduleCC1101State[cc1101Control->getModule()].getStateChangeSemaphore(), portMAX_DELAY) == pdTRUE) { 75 | cc1101Control->recordTaskHandle = nullptr; 76 | } 77 | } else if (cc1101Control->isPreviousMode(OperationMode::DetectSignal) && cc1101Control->detectTaskHandle != nullptr) { 78 | xTaskNotifyGive(cc1101Control->detectTaskHandle); 79 | if (xSemaphoreTake(moduleCC1101State[cc1101Control->getModule()].getStateChangeSemaphore(), portMAX_DELAY) == pdTRUE) { 80 | cc1101Control->detectTaskHandle = nullptr; 81 | } 82 | } 83 | 84 | ModeTaskParameters* taskParameters = new ModeTaskParameters{.module = cc1101Control->getModule(), .mode = cc1101Control->getCurrentMode()}; 85 | if (taskParameters == nullptr) { 86 | ESP_LOGE(TAG, "Failed to allocate memory for taskParameters"); 87 | vTaskDelete(nullptr); 88 | } 89 | Cc1101Mode prevMode = cc1101Control->getPreviousMode(); 90 | Cc1101Mode currentMode = cc1101Control->getCurrentMode(); 91 | 92 | switch (event) { 93 | case OperationMode::RecordSignal: 94 | if (xTaskCreate(cc1101Control->getCurrentMode().onModeProcess, "RecordTask", 4096, taskParameters, 1, &cc1101Control->recordTaskHandle) != pdPASS) { 95 | ESP_LOGE(TAG, "Failed to create RecordTask"); 96 | delete taskParameters; 97 | } 98 | break; 99 | 100 | case OperationMode::DetectSignal: 101 | if (xTaskCreate(cc1101Control->getCurrentMode().onModeProcess, "DetectTask", 4096, taskParameters, 1, &cc1101Control->detectTaskHandle) != pdPASS) { 102 | ESP_LOGE(TAG, "Failed to create DetectTask"); 103 | delete taskParameters; 104 | } 105 | break; 106 | 107 | case OperationMode::Idle: 108 | // Stop the active task 109 | break; 110 | 111 | default: 112 | delete taskParameters; // Clean up if no task was created 113 | break; 114 | } 115 | 116 | Handler::onStateChange(cc1101Control->getModule(), currentMode.getMode(), prevMode.getMode()); 117 | 118 | xSemaphoreGive(cc1101Control->stateSemaphore); 119 | } 120 | 121 | vTaskDelay(pdMS_TO_TICKS(10)); 122 | } 123 | } 124 | 125 | void listenSerial(void* parameter) 126 | { 127 | while (true) { 128 | SerialAdapter::getInstance().processQueue(); 129 | vTaskDelay(pdMS_TO_TICKS(10)); 130 | } 131 | } 132 | 133 | void taskProcessor(void* pvParameters) 134 | { 135 | if (ControllerAdapter::xTaskQueue == nullptr) { 136 | ESP_LOGE(TAG, "Task queue not found"); 137 | vTaskDelete(nullptr); // Remove task 138 | } 139 | QueueItem* item; 140 | while (true) { 141 | if (xQueueReceive(ControllerAdapter::xTaskQueue, &item, portMAX_DELAY)) { 142 | switch (item->type) { 143 | case Device::TaskType::Transmission: { 144 | Device::TaskTransmission& task = item->transmissionTask; 145 | Action::transmitSignal(task); 146 | } break; 147 | case Device::TaskType::Record: { 148 | Device::TaskRecord& task = item->recordTask; 149 | Action::recordSignal(task); 150 | } break; 151 | case Device::TaskType::DetectSignal: { 152 | Device::TaskDetectSignal& task = item->detectSignalTask; 153 | Action::detectSignal(task); 154 | } break; 155 | case Device::TaskType::FilesManager: { 156 | Device::TaskFilesManager& task = item->filesManagerTask; 157 | Action::fileOperator(task); 158 | } break; 159 | case Device::TaskType::FileUpload: { 160 | Device::TaskFileUpload& task = item->fileUploadTask; 161 | Action::fileUpload(task); 162 | } break; 163 | case Device::TaskType::GetState: { 164 | Device::TaskGetState& task = item->getStateTask; 165 | Action::getCurrentState(task); 166 | } break; 167 | case Device::TaskType::Idle: { 168 | Device::TaskIdle& task = item->idleTask; 169 | Action::idle(task); 170 | } break; 171 | default: 172 | break; 173 | } 174 | } 175 | vTaskDelay(pdMS_TO_TICKS(10)); 176 | } 177 | } 178 | 179 | void onWiFiEvent(WiFiEvent_t event) 180 | { 181 | if (wifiEventGroup == NULL || wifiEventMutex == NULL) { 182 | ESP_LOGE(TAG, "WiFi event group or mutex is NULL"); 183 | return; 184 | } 185 | 186 | if (xSemaphoreTake(wifiEventMutex, portMAX_DELAY) == pdTRUE) { 187 | switch (event) { 188 | case ARDUINO_EVENT_WIFI_AP_START: 189 | xEventGroupSetBits(wifiEventGroup, WIFI_AP_STARTED_BIT); 190 | break; 191 | case ARDUINO_EVENT_WIFI_STA_CONNECTED: 192 | case ARDUINO_EVENT_WIFI_AP_STACONNECTED: 193 | // webAdapter.initStatic(SD); 194 | break; 195 | default: 196 | break; 197 | } 198 | xSemaphoreGive(wifiEventMutex); 199 | } else { 200 | ESP_LOGE(TAG, "Failed to take mutex"); 201 | } 202 | } 203 | 204 | void setup() 205 | { 206 | ESP_LOGD(TAG, "Starting SPIFFS"); 207 | if (!SPIFFS.begin(false)) { 208 | ESP_LOGE(TAG, "SPIFFS mount failed!"); 209 | return; 210 | } 211 | 212 | String baudRate = ConfigManager::getConfigParam("serial_baud_rate"); 213 | Serial.begin(baudRate.isEmpty() ? SERIAL_BAUDRATE : baudRate.toInt()); 214 | 215 | ConfigManager::createDefaultConfig(); 216 | 217 | DeviceControls::setup(); 218 | DeviceControls::onLoadPowerManagement(); 219 | DeviceControls::onLoadServiceMode(); 220 | 221 | if (ConfigManager::isServiceMode()) { 222 | ServiceMode::serviceModeStart(); 223 | return; 224 | } 225 | 226 | ESP_LOGD(TAG, "Starting setup..."); 227 | 228 | sdspi.begin(SD_SCLK, SD_MISO, SD_MOSI, SD_SS); 229 | if (!SD.begin(SD_SS, sdspi)) { 230 | ESP_LOGE(TAG, "Card Mount Failed"); 231 | return; 232 | } 233 | 234 | ESP_LOGD(TAG, "SD card initialized."); 235 | 236 | ControllerAdapter::initializeQueue(); 237 | 238 | ESP_LOGD(TAG, "Device controls setup completed."); 239 | 240 | for (int i = 0; i < CC1101_NUM_MODULES; i++) { 241 | ESP_LOGD(TAG, "Initializing CC1101 module #%d\n", i); 242 | moduleCC1101State[i].init(); 243 | ESP_LOGD(TAG, "Initializing CC1101 module #%d end \n", i); 244 | cc1101Control[i].init(i, deviceModes, OperationMode::Idle); 245 | ESP_LOGD(TAG, "CC1101 module #%d initialized.\n", i); 246 | } 247 | 248 | deviceConfig.powerBlink = true; 249 | 250 | Recorder::init(); 251 | ESP_LOGD(TAG, "Recorder initialized."); 252 | 253 | for (int i = 0; i < CC1101_NUM_MODULES; i++) { 254 | char taskName[20]; 255 | snprintf(taskName, sizeof(taskName), "cc1101StateTask%d", i); 256 | if (cc1101Control[i].eventQueue == nullptr || cc1101Control[i].stateSemaphore == nullptr) { 257 | ESP_LOGE(TAG, "Error: Failed to initialize cc1101Control members"); 258 | return; 259 | } 260 | xTaskCreate(cc1101StateTask, taskName, 2048, &cc1101Control[i], 1, NULL); 261 | ESP_LOGD(TAG, "Task %s created.\n", taskName); 262 | } 263 | 264 | xTaskCreate(taskProcessor, "TaskProcessor", 25600, NULL, 1, NULL); 265 | ESP_LOGD(TAG, "TaskProcessor task created."); 266 | 267 | ClientsManager& clients = ClientsManager::getInstance(); 268 | clients.initializeQueue(NOTIFICATIONS_QUEUE); 269 | ESP_LOGD(TAG, "ClientsManager initialized."); 270 | 271 | xTaskCreate(ClientsManager::processMessageQueue, "SendNotifications", 2048, NULL, 1, NULL); 272 | ESP_LOGD(TAG, "SendNotifications task created."); 273 | 274 | SerialAdapter& serialAdapter = SerialAdapter::getInstance(); 275 | serialAdapter.setup(cli); 276 | serialAdapter.begin(); 277 | clients.addAdapter(&serialAdapter); 278 | ESP_LOGD(TAG, "Serial adapter initialized and added to clients."); 279 | 280 | xTaskCreate(listenSerial, "ListenSerial", 4096, NULL, 1, NULL); 281 | ESP_LOGD(TAG, "ListenSerial task created."); 282 | 283 | wifiEventGroup = xEventGroupCreate(); 284 | if (wifiEventGroup == NULL) { 285 | ESP_LOGE(TAG, "Failed to create wifi event group"); 286 | return; 287 | } 288 | 289 | wifiEventMutex = xSemaphoreCreateMutex(); 290 | if (wifiEventMutex == NULL) { 291 | ESP_LOGE(TAG, "Failed to create wifi event mutex"); 292 | return; 293 | } 294 | 295 | WiFi.onEvent(onWiFiEvent); 296 | 297 | String ssid = ConfigManager::getConfigParam("ssid"); 298 | String password = ConfigManager::getConfigParam("password"); 299 | 300 | if (ConfigManager::getConfigParam("wifi_mode") == "client") { 301 | WiFi.mode(WIFI_STA); 302 | WiFi.begin(ssid, password); 303 | 304 | ESP_LOGI(TAG, "Connecting to Wi-Fi ssid: \"%s\" password: \"%s\"", ssid, password); 305 | while (WiFi.status() != WL_CONNECTED) { 306 | delay(1000); 307 | ESP_LOGI(TAG, "."); 308 | } 309 | ESP_LOGI(TAG, "Connected to the Wi-Fi network!"); 310 | ESP_LOGI(TAG, "IP Address: %s", WiFi.localIP().toString()); 311 | 312 | webAdapter.begin(SD); 313 | clients.addAdapter(&webAdapter); 314 | } else { 315 | WiFi.mode(WIFI_AP); 316 | WiFi.setSleep(false); 317 | 318 | int retryCount = 0; 319 | while (retryCount < MAX_RETRIES) { 320 | Serial.println(ssid); 321 | Serial.println(password); 322 | WiFi.softAP(ssid, password, wifi_channel, 8); 323 | EventBits_t bits = xEventGroupWaitBits(wifiEventGroup, WIFI_AP_STARTED_BIT, pdFALSE, pdTRUE, pdMS_TO_TICKS(10000)); 324 | if (bits & WIFI_AP_STARTED_BIT) { 325 | ESP_LOGD(TAG, "WiFi AP successfully started"); 326 | break; 327 | } else { 328 | ESP_LOGE(TAG, "Failed to start WiFi AP, retrying..."); 329 | retryCount++; 330 | } 331 | } 332 | 333 | if (retryCount == MAX_RETRIES) { 334 | ESP_LOGE(TAG, "Failed to start WiFi AP after maximum retries"); 335 | } else { 336 | webAdapter.begin(SD); 337 | clients.addAdapter(&webAdapter); 338 | } 339 | } 340 | 341 | webAdapter.initStatic(SD); 342 | ESP_LOGD(TAG, "Starting scheduler..."); 343 | vTaskStartScheduler(); 344 | } 345 | 346 | void loop() 347 | { 348 | if (deviceConfig.powerBlink) { 349 | DeviceControls::poweronBlink(); 350 | } 351 | } 352 | -------------------------------------------------------------------------------- /src/Actions.cpp: -------------------------------------------------------------------------------- 1 | #include "Actions.h" 2 | 3 | FilesManager filesManager; 4 | 5 | Transmitter transmitter; 6 | Recorder recorder(Handler::signalRecordedHandler); 7 | Detector detector(Handler::signalDetectedHandler); 8 | 9 | ClientsManager& clients = ClientsManager::getInstance(); 10 | 11 | Cc1101Mode deviceModes[] = {Cc1101Mode(OperationMode::Idle, nullptr), 12 | Cc1101Mode(OperationMode::DetectSignal, Detector::process), 13 | Cc1101Mode(OperationMode::RecordSignal, Recorder::process)}; 14 | 15 | 16 | namespace Handler { 17 | void signalRecordedHandler(bool saved, std::string filename) 18 | { 19 | if (saved) { 20 | clients.enqueueMessage(NotificationType::SignalRecorded, ("{\"filename\":\"" + filename + "\"}").c_str()); 21 | } else { 22 | clients.enqueueMessage(NotificationType::FileSystem, ("{\"error\":\"Failed to open the file for writing: " + filename + "\"}").c_str()); 23 | } 24 | } 25 | 26 | void signalDetectedHandler(DetectedSignal detectedSignal) 27 | { 28 | clients.enqueueMessage(NotificationType::SignalDetected, detectedSignal.toJson()); 29 | } 30 | 31 | void onStateChange(int module, OperationMode mode, OperationMode previousMode) 32 | { 33 | std::stringstream response; 34 | 35 | // Construct the JSON string 36 | response << "{\"module\":\"" << module << "\"," 37 | << "\"mode\":\"" << modeToString(mode) << "\"," 38 | << "\"previousMode\":\"" << modeToString(previousMode) << "\"}"; 39 | clients.enqueueMessage(NotificationType::ModeSwitch, response.str().c_str()); 40 | } 41 | } // namespace Handler 42 | 43 | namespace Action { 44 | 45 | void detectSignal(Device::TaskDetectSignal &task) 46 | { 47 | int minRssi = task.minRssi ? *task.minRssi : -65; 48 | detector.setSignalDetectionMinRssi(minRssi); 49 | int module = task.module ? *task.module : MODULE_1; 50 | cc1101Control[module].switchMode(OperationMode::DetectSignal); 51 | } 52 | 53 | void recordSignal(Device::TaskRecord &task) 54 | { 55 | std::ostringstream message; 56 | std::ostringstream errorMessage; 57 | 58 | int module = task.module ? *task.module : MODULE_1; 59 | 60 | if (task.config.preset) { 61 | const std::string& preset = *task.config.preset; 62 | if (preset == "Ook270") { 63 | recorder.setRecordConfig(module, task.config.frequency, MODULATION_ASK_OOK, 2.380371, 270.833333, 3.79372, "Ook270"); 64 | } else if (preset == "Ook650") { 65 | recorder.setRecordConfig(module, task.config.frequency, MODULATION_ASK_OOK, 2.380371, 650, 3.79372, "Ook650"); 66 | } else if (preset == "2FSKDev238") { 67 | recorder.setRecordConfig(module, task.config.frequency, MODULATION_2_FSK, 2.380371, 270.833333, 4.79794, "2FSKDev238"); 68 | } else if (preset == "2FSKDev476") { 69 | recorder.setRecordConfig(module, task.config.frequency, MODULATION_2_FSK, 47.60742, 270.833333, 4.79794, "2FSKDev476"); 70 | } else { 71 | errorMessage << "{\"error\":\"Can not apply record configuration. Unsupported preset " << preset << "\"}"; 72 | } 73 | } else { 74 | int modulation = task.config.modulation ? *task.config.modulation : MODULATION_ASK_OOK; 75 | float bandwidth = task.config.rxBandwidth ? *task.config.rxBandwidth : 650; 76 | float deviation = task.config.deviation ? *task.config.deviation : 47.60742; 77 | float dataRate = task.config.dataRate ? *task.config.dataRate : 4.79794; 78 | recorder.setRecordConfig(module, task.config.frequency, modulation, deviation, bandwidth, dataRate, "Custom"); 79 | } 80 | 81 | if (errorMessage.str().empty()) { 82 | cc1101Control[module].switchMode(OperationMode::RecordSignal); 83 | } else { 84 | clients.enqueueMessage(NotificationType::SignalRecordError, errorMessage.str()); 85 | } 86 | } 87 | 88 | void transmitFromFile(Device::TaskTransmission &task) 89 | { 90 | std::ostringstream message; 91 | std::ostringstream errorMessage; 92 | 93 | boolean signalTransmitted = false; 94 | 95 | if (!task.filename) { 96 | errorMessage << "{\"error\":\"No filename provided\"}"; 97 | } else if (task.transmissionType == Device::TransmissionType::File) { 98 | const std::string filename = *task.filename; 99 | std::string lowerFilename = helpers::string::toLowerCase(filename); 100 | if (helpers::string::endsWith(filename, ".sub")) { 101 | signalTransmitted = transmitter.transmitSub(filename, task.module, task.repeat ? *task.repeat : 1); 102 | if (signalTransmitted) { 103 | message << "{\"file\":\"" << lowerFilename << "\"}"; 104 | } else { 105 | errorMessage << "{\"error\":\"Failed to transmit signal from file\",\"file\":\"" << filename << "\"}"; 106 | } 107 | } else { 108 | errorMessage << "{\"error\":\"Unsupported file format " << filename << "\"}"; 109 | } 110 | } 111 | 112 | if (!errorMessage.str().empty()) { 113 | clients.enqueueMessage(NotificationType::SignalSendingError, errorMessage.str()); 114 | } 115 | 116 | if (!message.str().empty()) { 117 | clients.enqueueMessage(NotificationType::SignalSent, message.str()); 118 | } 119 | } 120 | 121 | void transmitSignal(Device::TaskTransmission &task) 122 | { 123 | if (task.transmissionType == Device::TransmissionType::File) { 124 | transmitFromFile(task); 125 | return; 126 | } 127 | 128 | std::ostringstream message; 129 | std::ostringstream errorMessage; 130 | boolean signalTransmitted = false; 131 | 132 | int modulation = MODULATION_ASK_OOK; 133 | float deviation = 0; 134 | 135 | if (task.config.preset) { 136 | const std::string& preset = *task.config.preset; 137 | if (preset == "Ook270" || preset == "Ook650") { 138 | modulation = MODULATION_ASK_OOK; 139 | } else if (preset == "2FSKDev238") { 140 | modulation = MODULATION_2_FSK; 141 | deviation = 2.380371; 142 | } else if (preset == "2FSKDev476") { 143 | modulation = MODULATION_2_FSK; 144 | deviation = 47.60742; 145 | } else { 146 | errorMessage << "{\"error\":\"Can not apply transmission configuration. Unsupported preset " << preset << "\"}"; 147 | } 148 | } else { 149 | if (task.config.modulation) { 150 | modulation = *task.config.modulation; 151 | } 152 | if (task.config.deviation) { 153 | deviation = *task.config.deviation; 154 | } 155 | } 156 | 157 | if (errorMessage.str().empty()) { 158 | if (task.transmissionType == Device::TransmissionType::Raw) { 159 | signalTransmitted = transmitter.transmitRaw(task.module, *task.config.frequency, modulation, deviation, *task.data, task.repeat ? *task.repeat : 1); 160 | if (signalTransmitted) { 161 | message << "{\"message\":\"Signal transmitted\"}"; 162 | } else { 163 | errorMessage << "{\"error\":\"Failed to transmit signal\"}"; 164 | } 165 | } else { 166 | errorMessage << "{\"error\":\"Unsupported transmission type\"}"; 167 | } 168 | } 169 | 170 | if (!errorMessage.str().empty()) { 171 | clients.enqueueMessage(NotificationType::SignalSendingError, errorMessage.str()); 172 | } 173 | 174 | if (!message.str().empty()) { 175 | clients.enqueueMessage(NotificationType::SignalSent, message.str()); 176 | } 177 | } 178 | 179 | void idle(Device::TaskIdle &task) 180 | { 181 | cc1101Control[task.module].switchMode(OperationMode::Idle); 182 | } 183 | 184 | void getCurrentState(Device::TaskGetState &task) 185 | { 186 | std::stringstream response; 187 | response << "{"; 188 | 189 | // Add device information 190 | response << "\"device\":{"; 191 | response << "\"freeHeap\":" << ESP.getFreeHeap(); 192 | response << "},"; 193 | 194 | // Add CC1101 modules information 195 | response << "\"cc1101\":["; 196 | for (int i = 0; i < CC1101_NUM_MODULES; i++) { 197 | const byte numSettingsRegisters = 0x2E; // Number of configuration registers as per datasheet 198 | std::vector buffer(numSettingsRegisters); // Using std::vector for better memory management 199 | std::fill(buffer.begin(), buffer.end(), 0); 200 | moduleCC1101State[i].readAllConfigRegisters(buffer.data(), numSettingsRegisters); 201 | 202 | response << "{"; 203 | response << "\"id\":" << i << ","; 204 | 205 | response << "\"settings\":\""; 206 | for (byte j = 0; j <= numSettingsRegisters; ++j) { 207 | response << std::hex << std::uppercase 208 | << std::setw(2) << std::setfill('0') << (int)(j) << " " 209 | << std::setw(2) << std::setfill('0') << (int)buffer[j]; 210 | if (j < numSettingsRegisters) { 211 | response << " "; 212 | } 213 | } 214 | response << "\","; 215 | 216 | response << "\"mode\":\"" << modeToString(cc1101Control[i].getCurrentMode().getMode()) << "\""; 217 | response << "}"; 218 | if (i < CC1101_NUM_MODULES - 1) { 219 | response << ","; 220 | } 221 | } 222 | response << "]}"; 223 | 224 | clients.enqueueMessage(NotificationType::State, response.str()); 225 | } 226 | 227 | void fileOperator(Device::TaskFilesManager &task) 228 | { 229 | std::string fullPath = FILES_RECORDS_PATH; 230 | std::ostringstream result; 231 | std::string fileContent; 232 | std::string path = task.path; 233 | std::string pathTo = task.pathTo; 234 | 235 | switch (task.actionType) { 236 | case Device::TaskFilesManagerAction::List: 237 | if (!path.empty()) { 238 | fullPath += std::string("/") + path; 239 | } 240 | result << "{\"action\":\"list\",\"files\":" << filesManager.listAllFiles(fullPath) << "}"; 241 | clients.enqueueMessage(NotificationType::FileSystem, result.str()); 242 | break; 243 | 244 | case Device::TaskFilesManagerAction::Delete: 245 | if (path.empty()) { 246 | result << "{\"action\":\"delete\",\"error\":\"Path is not provided to delete\"}"; 247 | clients.enqueueMessage(NotificationType::FileSystem, result.str()); 248 | return; 249 | } 250 | if (filesManager.remove(std::string(FILES_RECORDS_PATH) + "/" + path)) { 251 | result << "{\"action\":\"delete\",\"success\":true,\"path\":\"" << path << "\"}"; 252 | } else { 253 | result << "{\"action\":\"delete\",\"error\":\"Failed to delete file\",\"path\":\"" << path << "\"}"; 254 | } 255 | clients.enqueueMessage(NotificationType::FileSystem, result.str()); 256 | break; 257 | 258 | case Device::TaskFilesManagerAction::Rename: 259 | if (path.empty() || pathTo.empty()) { 260 | result << "{\"action\":\"rename\",\"error\":\"Path from or to is not provided for rename\"}"; 261 | clients.enqueueMessage(NotificationType::FileSystem, result.str()); 262 | return; 263 | } 264 | if (filesManager.rename(std::string(FILES_RECORDS_PATH) + "/" + path, std::string(FILES_RECORDS_PATH) + "/" + pathTo)) { 265 | result << "{\"action\":\"rename\",\"success\":true,\"from\":\"" << path << "\",\"to\":\"" << pathTo << "\"}"; 266 | } else { 267 | result << "{\"action\":\"rename\",\"error\":\"Failed to rename file\",\"from\":\"" << path << "\",\"to\":\"" << pathTo << "\"}"; 268 | } 269 | clients.enqueueMessage(NotificationType::FileSystem, result.str()); 270 | break; 271 | 272 | case Device::TaskFilesManagerAction::CreateDirectory: 273 | if (path.empty()) { 274 | result << "{\"action\":\"create-directory\",\"error\":\"Path is not provided to create directory\"}"; 275 | clients.enqueueMessage(NotificationType::FileSystem, result.str()); 276 | return; 277 | } 278 | if (filesManager.createDirectory(std::string(FILES_RECORDS_PATH) + "/" + path)) { 279 | result << "{\"action\":\"create-directory\",\"success\":true,\"path\":\"" << path << "\"}"; 280 | } else { 281 | result << "{\"action\":\"create-directory\",\"error\":\"Failed to create directory\",\"path\":\"" << path << "\"}"; 282 | } 283 | clients.enqueueMessage(NotificationType::FileSystem, result.str()); 284 | break; 285 | 286 | case Device::TaskFilesManagerAction::Load: 287 | if (path.empty()) { 288 | result << "{\"action\":\"load\",\"error\":\"File is not provided\"}"; 289 | clients.enqueueMessage(NotificationType::FileSystem, result.str()); 290 | return; 291 | } 292 | fileContent = filesManager.getFile(path); 293 | result << "{\"action\":\"load\",\"path\":\"" << path << "\",\"success\":true,\"content\":\"" << helpers::string::escapeJson(fileContent) << "\"}"; 294 | clients.enqueueMessage(NotificationType::FileSystem, result.str()); 295 | break; 296 | 297 | default: 298 | result << "{\"error\":\"Unknown action\"}"; 299 | clients.enqueueMessage(NotificationType::FileSystem, result.str()); 300 | break; 301 | } 302 | } 303 | 304 | void fileUpload(Device::TaskFileUpload &task) 305 | { 306 | std::string path; 307 | 308 | switch (task.uploadType) { 309 | case Device::FileUploadType::File: 310 | path = std::string(FILES_RECORDS_PATH); 311 | break; 312 | 313 | default: 314 | clients.enqueueMessage(NotificationType::FileUpload, "{\"error\": \"Unsupported upload type\"}"); 315 | return; 316 | } 317 | 318 | File outputFile; 319 | path = path + "/" + task.filename; 320 | 321 | outputFile = filesManager.open(path, !task.index ? FILE_WRITE : FILE_APPEND); 322 | 323 | if (!outputFile) { 324 | clients.enqueueMessage(NotificationType::FileUpload, "{\"error\": \"Failed to open the file for writing\"}"); 325 | return; 326 | } 327 | 328 | if (outputFile.write(task.data.data(), task.data.size()) != task.data.size()) { 329 | outputFile.close(); 330 | clients.enqueueMessage(NotificationType::FileUpload, "{\"error\": \"Failed to write to the file\"}"); 331 | return; 332 | } 333 | 334 | outputFile.close(); 335 | 336 | if (task.final) { 337 | clients.enqueueMessage(NotificationType::FileUpload, "{\"success\": true, \"filename\": \"" + task.filename + "\"}"); 338 | return; 339 | } 340 | 341 | return; 342 | } 343 | 344 | } // namespace Action -------------------------------------------------------------------------------- /include/DeviceTasks.h: -------------------------------------------------------------------------------- 1 | #ifndef Tasks_h 2 | #define Tasks_h 3 | 4 | #include 5 | #include "compatibility.h" 6 | #include 7 | #include "Arduino.h" 8 | 9 | namespace Device { 10 | 11 | enum class TaskType { 12 | Transmission, 13 | Record, 14 | DetectSignal, 15 | FilesManager, 16 | FileUpload, 17 | GetState, 18 | Idle 19 | }; 20 | 21 | struct TaskBase { 22 | TaskType type; 23 | TaskBase(TaskType t) : type(t) {} 24 | }; 25 | 26 | // ==================================== 27 | // Task Transmission 28 | // ==================================== 29 | enum class TransmissionType 30 | { 31 | Raw, 32 | File, 33 | Binary 34 | }; 35 | 36 | struct TransmissionConfig 37 | { 38 | std::unique_ptr frequency; 39 | std::unique_ptr modulation; 40 | std::unique_ptr deviation; 41 | std::unique_ptr preset; 42 | 43 | TransmissionConfig() = default; 44 | TransmissionConfig(std::unique_ptr freq, std::unique_ptr mod, std::unique_ptr dev, std::unique_ptr pre) 45 | : frequency(std::move(freq)), modulation(std::move(mod)), deviation(std::move(dev)), preset(std::move(pre)) 46 | { 47 | } 48 | }; 49 | 50 | struct TaskTransmission: public TaskBase 51 | { 52 | TransmissionType transmissionType; 53 | std::unique_ptr filename; 54 | int module = 0; 55 | std::unique_ptr repeat; 56 | std::unique_ptr data; 57 | TransmissionConfig config; 58 | 59 | TaskTransmission(TransmissionType t) : TaskBase(TaskType::Transmission), transmissionType(t), config() { 60 | } 61 | 62 | // Delete copy constructor and assignment operator to prevent accidental copying 63 | TaskTransmission(const TaskTransmission&) = delete; 64 | TaskTransmission& operator=(const TaskTransmission&) = delete; 65 | 66 | // Provide move constructor and move assignment operator 67 | TaskTransmission(TaskTransmission&& other) noexcept = default; 68 | TaskTransmission& operator=(TaskTransmission&& other) noexcept = default; 69 | }; 70 | 71 | class TaskTransmissionBuilder 72 | { 73 | private: 74 | TaskTransmission task; 75 | 76 | public: 77 | TaskTransmissionBuilder(TransmissionType t) : task(t) {} 78 | 79 | TaskTransmissionBuilder& setFilename(std::string fname) 80 | { 81 | task.filename = std::make_unique(fname); 82 | return *this; 83 | } 84 | 85 | TaskTransmissionBuilder& setModule(int mod) 86 | { 87 | task.module = mod; 88 | return *this; 89 | } 90 | 91 | TaskTransmissionBuilder& setRepeat(int rep) 92 | { 93 | task.repeat = std::make_unique(rep); 94 | return *this; 95 | } 96 | 97 | TaskTransmissionBuilder& setFrequency(float freq) 98 | { 99 | task.config.frequency = std::make_unique(freq); 100 | return *this; 101 | } 102 | 103 | TaskTransmissionBuilder& setModulation(int mod) 104 | { 105 | task.config.modulation = std::make_unique(mod); 106 | return *this; 107 | } 108 | 109 | TaskTransmissionBuilder& setDeviation(float dev) 110 | { 111 | task.config.deviation = std::make_unique(dev); 112 | return *this; 113 | } 114 | 115 | TaskTransmissionBuilder& setPreset(std::string pre) 116 | { 117 | task.config.preset = std::make_unique(std::move(pre)); 118 | return *this; 119 | } 120 | 121 | TaskTransmissionBuilder& setData(std::string data) 122 | { 123 | task.data = std::make_unique(std::move(data)); 124 | return *this; 125 | } 126 | 127 | TaskTransmission build() 128 | { 129 | return std::move(task); 130 | } 131 | }; 132 | 133 | // ==================================== 134 | // Task Record 135 | // ==================================== 136 | struct RecordConfig 137 | { 138 | float frequency; 139 | std::unique_ptr modulation; 140 | std::unique_ptr deviation; 141 | std::unique_ptr rxBandwidth; 142 | std::unique_ptr dataRate; 143 | std::unique_ptr preset; 144 | 145 | RecordConfig() = default; 146 | }; 147 | 148 | struct TaskRecord: public TaskBase 149 | { 150 | std::unique_ptr module; 151 | RecordConfig config; 152 | 153 | TaskRecord(float freq) : TaskBase(TaskType::Record), config() 154 | { 155 | config.frequency = freq; 156 | } 157 | 158 | // Delete copy constructor and assignment operator to prevent accidental copying 159 | TaskRecord(const TaskRecord&) = delete; 160 | TaskRecord& operator=(const TaskRecord&) = delete; 161 | 162 | // Provide move constructor and move assignment operator 163 | TaskRecord(TaskRecord&& other) noexcept = default; 164 | TaskRecord& operator=(TaskRecord&& other) noexcept = default; 165 | }; 166 | 167 | class TaskRecordBuilder 168 | { 169 | private: 170 | TaskRecord task; 171 | 172 | public: 173 | TaskRecordBuilder(float frequency) : task(frequency) {} 174 | 175 | TaskRecordBuilder& setModulation(int mod) 176 | { 177 | task.config.modulation = std::make_unique(mod); 178 | return *this; 179 | } 180 | 181 | TaskRecordBuilder& setDeviation(float dev) 182 | { 183 | task.config.deviation = std::make_unique(dev); 184 | return *this; 185 | } 186 | 187 | TaskRecordBuilder& setRxBandwidth(float rxBW) 188 | { 189 | task.config.rxBandwidth = std::make_unique(rxBW); 190 | return *this; 191 | } 192 | 193 | TaskRecordBuilder& setDataRate(float dRate) 194 | { 195 | task.config.dataRate = std::make_unique(dRate); 196 | return *this; 197 | } 198 | 199 | TaskRecordBuilder& setPreset(std::string pre) 200 | { 201 | task.config.preset = std::make_unique(pre); 202 | return *this; 203 | } 204 | 205 | TaskRecordBuilder& setModule(int mod) 206 | { 207 | task.module = std::make_unique(mod); 208 | return *this; 209 | } 210 | 211 | TaskRecord build() 212 | { 213 | return std::move(task); 214 | } 215 | }; 216 | 217 | // ==================================== 218 | // Task FilesManager 219 | // ==================================== 220 | enum class TaskFilesManagerAction 221 | { 222 | Unknown, 223 | List, 224 | Load, 225 | CreateDirectory, 226 | Delete, 227 | Rename 228 | }; 229 | 230 | struct TaskFilesManager: public TaskBase 231 | { 232 | TaskFilesManagerAction actionType; 233 | std::string path; 234 | std::string pathTo; 235 | 236 | TaskFilesManager(TaskFilesManagerAction t, std::string p = "", std::string pt = "") 237 | : TaskBase(TaskType::FilesManager), actionType(t), path(p), pathTo(pt) {} 238 | 239 | // Delete copy constructor and assignment operator to prevent accidental copying 240 | TaskFilesManager(const TaskFilesManager&) = delete; 241 | TaskFilesManager& operator=(const TaskFilesManager&) = delete; 242 | 243 | // Provide move constructor and move assignment operator 244 | TaskFilesManager(TaskFilesManager&& other) noexcept = default; 245 | TaskFilesManager& operator=(TaskFilesManager&& other) noexcept = default; 246 | }; 247 | 248 | // ==================================== 249 | // Task FileUpload 250 | // ==================================== 251 | 252 | enum class FileUploadType 253 | { 254 | File, 255 | Firmware 256 | }; 257 | 258 | struct TaskFileUpload: public TaskBase 259 | { 260 | std::string filename; 261 | FileUploadType uploadType; 262 | size_t index; 263 | std::vector data; 264 | size_t len; 265 | bool final; 266 | 267 | TaskFileUpload(std::string filename, FileUploadType uploadType, size_t index = 0, uint8_t* data = nullptr, size_t len = 0, bool final = false) 268 | : TaskBase(TaskType::FileUpload), filename(filename), uploadType(uploadType), index(index), data(data, data + len), len(len), final(final) {} 269 | }; 270 | 271 | // ==================================== 272 | // Task DetectSignal 273 | // ==================================== 274 | 275 | struct TaskDetectSignal: public TaskBase 276 | { 277 | public: 278 | std::unique_ptr module; 279 | std::unique_ptr minRssi; 280 | std::unique_ptr background; 281 | 282 | TaskDetectSignal() : TaskBase(TaskType::DetectSignal) {}; 283 | 284 | // Delete copy constructor and assignment operator to prevent accidental copying 285 | TaskDetectSignal(const TaskDetectSignal&) = delete; 286 | TaskDetectSignal& operator=(const TaskDetectSignal&) = delete; 287 | 288 | // Provide move constructor and move assignment operator 289 | TaskDetectSignal(TaskDetectSignal&& other) noexcept = default; 290 | TaskDetectSignal& operator=(TaskDetectSignal&& other) noexcept = default; 291 | }; 292 | 293 | class TaskDetectSignalBuilder 294 | { 295 | private: 296 | TaskDetectSignal task; 297 | 298 | public: 299 | // Default constructor 300 | TaskDetectSignalBuilder() = default; 301 | 302 | TaskDetectSignalBuilder& setModule(int module) 303 | { 304 | task.module = std::make_unique(module); 305 | return *this; 306 | } 307 | 308 | TaskDetectSignalBuilder& setMinRssi(int minRssi) 309 | { 310 | task.minRssi = std::make_unique(minRssi); 311 | return *this; 312 | } 313 | 314 | TaskDetectSignal build() 315 | { 316 | return std::move(task); 317 | } 318 | }; 319 | 320 | // ==================================== 321 | // Task GetState 322 | // ==================================== 323 | 324 | struct TaskGetState: public TaskBase 325 | { 326 | public: 327 | bool full; 328 | 329 | TaskGetState(bool full) : TaskBase(TaskType::GetState), full(full) {} 330 | }; 331 | 332 | // ==================================== 333 | // Task Idle 334 | // ==================================== 335 | 336 | struct TaskIdle: public TaskBase 337 | { 338 | public: 339 | int module; 340 | 341 | TaskIdle(int module) : TaskBase(TaskType::Idle), module(module) {} 342 | }; 343 | 344 | } // namespace Device 345 | 346 | struct QueueItem { 347 | Device::TaskType type; 348 | union { 349 | Device::TaskTransmission transmissionTask; 350 | Device::TaskRecord recordTask; 351 | Device::TaskDetectSignal detectSignalTask; 352 | Device::TaskFilesManager filesManagerTask; 353 | Device::TaskFileUpload fileUploadTask; 354 | Device::TaskGetState getStateTask; 355 | Device::TaskIdle idleTask; 356 | }; 357 | 358 | // Default constructor 359 | QueueItem() : type(Device::TaskType::Idle) { 360 | new (&idleTask) Device::TaskIdle(0); 361 | } 362 | 363 | // Constructors for each task type 364 | QueueItem(Device::TaskTransmission&& task) : type(Device::TaskType::Transmission), transmissionTask(std::move(task)) {} 365 | QueueItem(Device::TaskRecord&& task) : type(Device::TaskType::Record), recordTask(std::move(task)) {} 366 | QueueItem(Device::TaskDetectSignal&& task) : type(Device::TaskType::DetectSignal), detectSignalTask(std::move(task)) {} 367 | QueueItem(Device::TaskFilesManager&& task) : type(Device::TaskType::FilesManager), filesManagerTask(std::move(task)) {} 368 | QueueItem(Device::TaskFileUpload&& task) : type(Device::TaskType::FileUpload), fileUploadTask(std::move(task)) {} 369 | QueueItem(Device::TaskGetState&& task) : type(Device::TaskType::GetState), getStateTask(std::move(task)) {} 370 | QueueItem(Device::TaskIdle&& task) : type(Device::TaskType::Idle), idleTask(std::move(task)) {} 371 | 372 | // Destructor 373 | ~QueueItem() { 374 | switch (type) { 375 | case Device::TaskType::Transmission: 376 | transmissionTask.~TaskTransmission(); 377 | break; 378 | case Device::TaskType::Record: 379 | recordTask.~TaskRecord(); 380 | break; 381 | case Device::TaskType::DetectSignal: 382 | detectSignalTask.~TaskDetectSignal(); 383 | break; 384 | case Device::TaskType::FilesManager: 385 | filesManagerTask.~TaskFilesManager(); 386 | break; 387 | case Device::TaskType::FileUpload: 388 | fileUploadTask.~TaskFileUpload(); 389 | break; 390 | case Device::TaskType::GetState: 391 | getStateTask.~TaskGetState(); 392 | break; 393 | case Device::TaskType::Idle: 394 | idleTask.~TaskIdle(); 395 | break; 396 | default: 397 | break; 398 | } 399 | } 400 | 401 | // Disable copy constructor and copy assignment operator 402 | QueueItem(const QueueItem&) = delete; 403 | QueueItem& operator=(const QueueItem&) = delete; 404 | 405 | // Move constructor 406 | QueueItem(QueueItem&& other) noexcept : type(other.type) { 407 | switch (type) { 408 | case Device::TaskType::Transmission: 409 | new (&transmissionTask) Device::TaskTransmission(std::move(other.transmissionTask)); 410 | break; 411 | case Device::TaskType::Record: 412 | new (&recordTask) Device::TaskRecord(std::move(other.recordTask)); 413 | break; 414 | case Device::TaskType::DetectSignal: 415 | new (&detectSignalTask) Device::TaskDetectSignal(std::move(other.detectSignalTask)); 416 | break; 417 | case Device::TaskType::FilesManager: 418 | new (&filesManagerTask) Device::TaskFilesManager(std::move(other.filesManagerTask)); 419 | break; 420 | case Device::TaskType::FileUpload: 421 | new (&fileUploadTask) Device::TaskFileUpload(std::move(other.fileUploadTask)); 422 | break; 423 | case Device::TaskType::GetState: 424 | new (&getStateTask) Device::TaskGetState(std::move(other.getStateTask)); 425 | break; 426 | case Device::TaskType::Idle: 427 | new (&idleTask) Device::TaskIdle(std::move(other.idleTask)); 428 | break; 429 | default: 430 | break; 431 | } 432 | } 433 | 434 | // Move assignment operator 435 | QueueItem& operator=(QueueItem&& other) noexcept { 436 | if (this != &other) { 437 | this->~QueueItem(); 438 | type = other.type; 439 | switch (type) { 440 | case Device::TaskType::Transmission: 441 | new (&transmissionTask) Device::TaskTransmission(std::move(other.transmissionTask)); 442 | break; 443 | case Device::TaskType::Record: 444 | new (&recordTask) Device::TaskRecord(std::move(other.recordTask)); 445 | break; 446 | case Device::TaskType::DetectSignal: 447 | new (&detectSignalTask) Device::TaskDetectSignal(std::move(other.detectSignalTask)); 448 | break; 449 | case Device::TaskType::FilesManager: 450 | new (&filesManagerTask) Device::TaskFilesManager(std::move(other.filesManagerTask)); 451 | break; 452 | case Device::TaskType::FileUpload: 453 | new (&fileUploadTask) Device::TaskFileUpload(std::move(other.fileUploadTask)); 454 | break; 455 | case Device::TaskType::GetState: 456 | new (&getStateTask) Device::TaskGetState(std::move(other.getStateTask)); 457 | break; 458 | case Device::TaskType::Idle: 459 | new (&idleTask) Device::TaskIdle(std::move(other.idleTask)); 460 | break; 461 | default: 462 | break; 463 | } 464 | } 465 | return *this; 466 | } 467 | }; 468 | 469 | #endif // Tasks_h 470 | -------------------------------------------------------------------------------- /src/SerialAdapter.cpp: -------------------------------------------------------------------------------- 1 | #include "SerialAdapter.h" 2 | 3 | void SerialAdapter::begin() 4 | { 5 | if (serialQueue == nullptr) { 6 | serialQueue = xQueueCreate(10, COMMAND_BUFFER_LENGTH * sizeof(char)); 7 | } 8 | Serial.onReceive(onSerialData); 9 | } 10 | 11 | void SerialAdapter::onSerialData() 12 | { 13 | BaseType_t xHigherPriorityTaskWoken = pdFALSE; 14 | while (Serial.available()) { 15 | char data = Serial.read(); 16 | SerialAdapter::getInstance().processCharacter(data); 17 | } 18 | portYIELD_FROM_ISR(xHigherPriorityTaskWoken); 19 | } 20 | 21 | void SerialAdapter::processCharacter(char data) 22 | { 23 | if (data == '\r') { 24 | seenCR = true; 25 | } else if (data == '\n') { 26 | if (seenCR) { // Check if '\r' was seen before '\n' 27 | buffer[length] = '\0'; 28 | if (length > 0) { 29 | xQueueSendFromISR(serialQueue, buffer, NULL); 30 | memset(buffer, 0, COMMAND_BUFFER_LENGTH); 31 | } 32 | length = 0; 33 | } else { 34 | // If '\r' was not seen before '\n', treat '\n' as normal character 35 | if (length < COMMAND_BUFFER_LENGTH) { 36 | buffer[length++] = data; 37 | } 38 | } 39 | seenCR = false; // Reset the state variable 40 | } else { 41 | if (length < COMMAND_BUFFER_LENGTH) { 42 | buffer[length++] = data; 43 | } 44 | seenCR = false; 45 | } 46 | } 47 | 48 | void SerialAdapter::processQueue() 49 | { 50 | char commandBuffer[COMMAND_BUFFER_LENGTH]; 51 | while (xQueueReceive(serialQueue, &commandBuffer, portMAX_DELAY)) { 52 | this->simpleCli->parse(commandBuffer); 53 | } 54 | } 55 | 56 | //================== 57 | // Detect Signal 58 | //================== 59 | void SerialAdapter::registerDetectSignalCommand(SimpleCLI& cli) 60 | { 61 | Command detectCommand = cli.addCmd("detect", commandDetectSignal); 62 | detectCommand.addArg("r,minrssi", ""); 63 | detectCommand.addArg("m,module", ""); 64 | } 65 | 66 | void SerialAdapter::commandDetectSignal(cmd* commandPointer) 67 | { 68 | Command cmd(commandPointer); 69 | Argument minRssiArg = cmd.getArg("r"); 70 | Argument moduleArg = cmd.getArg("m"); 71 | 72 | Device::TaskDetectSignalBuilder taskBuilder; 73 | 74 | if (minRssiArg.isSet()) 75 | taskBuilder.setMinRssi(minRssiArg.getValue().toInt()); 76 | if (moduleArg.isSet()) { 77 | int module = moduleArg.getValue().toInt(); 78 | taskBuilder.setModule(module == 2 ? MODULE_2 : MODULE_1); 79 | } 80 | 81 | Device::TaskDetectSignal task = taskBuilder.build(); 82 | SerialAdapter::getInstance().sendTask(std::move(task)); 83 | } 84 | 85 | //================== 86 | // Idle 87 | //================== 88 | 89 | void SerialAdapter::registerIdleCommand(SimpleCLI& cli) 90 | { 91 | Command idleCommand = cli.addCmd("idle", commandIdle); 92 | idleCommand.addArg("m,module", ""); 93 | } 94 | 95 | void SerialAdapter::commandIdle(cmd* commandPointer) 96 | { 97 | Command cmd(commandPointer); 98 | Argument moduleArg = cmd.getArg("m"); 99 | int module = MODULE_1; 100 | if (moduleArg.isSet()) { 101 | module = moduleArg.getValue().toInt() == 2 ? MODULE_2 : MODULE_1; 102 | } 103 | Device::TaskIdle task(module); 104 | SerialAdapter::getInstance().sendTask(std::move(task)); 105 | } 106 | 107 | //================== 108 | // Get State 109 | //================== 110 | 111 | void SerialAdapter::commandGetState(cmd* commandPointer) 112 | { 113 | Command cmd(commandPointer); 114 | Device::TaskGetState task(false); 115 | SerialAdapter::getInstance().sendTask(std::move(task)); 116 | } 117 | 118 | void SerialAdapter::registerGetStateCommand(SimpleCLI& cli) 119 | { 120 | cli.addCmd("state", commandGetState); 121 | } 122 | 123 | //================== 124 | // File Management 125 | //================== 126 | void SerialAdapter::registerFilesListCommand(SimpleCLI& cli) 127 | { 128 | Command cmd = cli.addCmd("files", commandGetFilesList); 129 | cmd.addPosArg("action", "list"); 130 | cmd.addPosArg("arg1", ""); 131 | cmd.addPosArg("arg2", ""); 132 | } 133 | 134 | void SerialAdapter::commandGetFilesList(cmd* commandPointer) 135 | { 136 | Command cmd(commandPointer); 137 | Argument actionArg = cmd.getArg("action"); 138 | Argument arg1Arg = cmd.getArg("arg1"); 139 | Argument arg2Arg = cmd.getArg("arg2"); 140 | 141 | std::unordered_map action = {{"list", Device::TaskFilesManagerAction::List}, 142 | {"delete", Device::TaskFilesManagerAction::Delete}, 143 | {"rename", Device::TaskFilesManagerAction::Rename}, 144 | {"create-directory", Device::TaskFilesManagerAction::CreateDirectory}, 145 | {"get", Device::TaskFilesManagerAction::Load}}; 146 | 147 | Device::TaskFilesManagerAction actionType = Device::TaskFilesManagerAction::Unknown; 148 | for (const auto& pair : action) { 149 | if (pair.first == actionArg.getValue().c_str()) { 150 | actionType = pair.second; 151 | break; 152 | } 153 | } 154 | 155 | if (actionType == Device::TaskFilesManagerAction::Unknown) { 156 | error("{\"error\": \"Unknown action\"}"); 157 | return; 158 | } 159 | 160 | Device::TaskFilesManager task(actionType); 161 | 162 | switch (actionType) { 163 | case Device::TaskFilesManagerAction::List: 164 | if (arg1Arg.isSet()) { 165 | task.path = arg1Arg.getValue().c_str(); 166 | } 167 | break; 168 | case Device::TaskFilesManagerAction::Delete: 169 | if (!arg1Arg.isSet()) { 170 | error("\"error\": \"Missing path for deletion\"}"); 171 | return; 172 | } 173 | task.path = arg1Arg.getValue().c_str(); 174 | break; 175 | case Device::TaskFilesManagerAction::Rename: 176 | if (!arg1Arg.isSet() || !arg2Arg.isSet()) { 177 | error("\"error\": \"To rename file you need provide paths 'rename /path/from /path/to'\"}"); 178 | return; 179 | } 180 | task.path = arg1Arg.getValue().c_str(); 181 | task.pathTo = arg2Arg.getValue().c_str(); 182 | break; 183 | case Device::TaskFilesManagerAction::CreateDirectory: 184 | if (!arg1Arg.isSet()) { 185 | error("\"error\": \"Missing path for directory creation\"}"); 186 | return; 187 | } 188 | task.path = arg1Arg.getValue().c_str(); 189 | break; 190 | case Device::TaskFilesManagerAction::Load: 191 | if (!arg1Arg.isSet()) { 192 | error("\"error\": \"Missing path to file to get it's content\"}"); 193 | return; 194 | } 195 | task.path = arg1Arg.getValue().c_str(); 196 | break; 197 | } 198 | 199 | SerialAdapter::getInstance().sendTask(std::move(task)); 200 | } 201 | 202 | //================== 203 | // File Upload 204 | //================== 205 | void SerialAdapter::registerFileUploadCommand(SimpleCLI& cli) 206 | { 207 | Command cmd = cli.addCmd("upload", commandFileUpload); 208 | cmd.addArg("n,name", ""); 209 | cmd.addArg("t,type", "signal"); 210 | cmd.addPosArg("data", ""); 211 | } 212 | 213 | void SerialAdapter::commandFileUpload(cmd* commandPointer) 214 | { 215 | Command cmd(commandPointer); 216 | Argument nameArg = cmd.getArg("n"); 217 | Argument typeArg = cmd.getArg("t"); 218 | Argument dataArg = cmd.getArg("data"); 219 | 220 | String dataString = dataArg.getValue(); 221 | size_t length = dataString.length(); 222 | 223 | std::vector dataRaw(dataString.begin(), dataString.end()); 224 | dataRaw.push_back('\0'); 225 | 226 | Device::FileUploadType type; 227 | 228 | if (typeArg.isSet()) { 229 | type = typeArg.getValue().c_str() == "firmware" ? Device::FileUploadType::Firmware : Device::FileUploadType::File; 230 | } 231 | 232 | Device::TaskFileUpload task(nameArg.getValue().c_str(), type, 0, dataRaw.data(), length, true); 233 | SerialAdapter::getInstance().sendTask(std::move(task)); 234 | } 235 | 236 | //================== 237 | // Record Signal 238 | //================== 239 | void SerialAdapter::registerRecordSignalCommand(SimpleCLI& cli) 240 | { 241 | Command cmd = cli.addCmd("record", commandRecordSignal); 242 | cmd.addArg("m,modulation", ""); 243 | cmd.addArg("f,frequency"); 244 | cmd.addArg("d,deviation", ""); 245 | cmd.addArg("b,bandwidth", ""); 246 | cmd.addArg("r,datarate", ""); 247 | cmd.addArg("a,module", ""); 248 | cmd.addArg("p,preset", ""); 249 | } 250 | 251 | void SerialAdapter::commandRecordSignal(cmd* commandPointer) 252 | { 253 | Command cmd(commandPointer); 254 | Argument frequencyArg = cmd.getArg("f"); 255 | 256 | std::vector requiredParams = {frequencyArg}; 257 | if (!handleMissingArguments(requiredParams)) { 258 | return; 259 | } 260 | 261 | Argument presetArg = cmd.getArg("p"); 262 | Argument moduleArg = cmd.getArg("a"); 263 | Argument dataRateArg = cmd.getArg("r"); 264 | Argument deviationArg = cmd.getArg("d"); 265 | Argument bandwidthArg = cmd.getArg("b"); 266 | Argument modulationArg = cmd.getArg("m"); 267 | 268 | Device::TaskRecordBuilder taskBuilder(frequencyArg.getValue().toFloat()); 269 | 270 | if (presetArg.isSet()) { 271 | taskBuilder.setPreset(presetArg.getValue().c_str()); 272 | } else { 273 | if (deviationArg.isSet()) 274 | taskBuilder.setDeviation(deviationArg.getValue().toFloat()); 275 | if (bandwidthArg.isSet()) 276 | taskBuilder.setRxBandwidth(bandwidthArg.getValue().toFloat()); 277 | if (dataRateArg.isSet()) 278 | taskBuilder.setDataRate(dataRateArg.getValue().toFloat()); 279 | if (modulationArg.isSet()) { 280 | if (modulationArg.getValue() == "ook") { 281 | taskBuilder.setModulation(MODULATION_ASK_OOK); 282 | } else if (modulationArg.getValue() == "2fsk") { 283 | taskBuilder.setModulation(MODULATION_2_FSK); 284 | } 285 | } 286 | } 287 | 288 | if (moduleArg.isSet()) { 289 | if (moduleArg.getValue().toInt() == 1) { 290 | taskBuilder.setModule(MODULE_1); 291 | } else if (moduleArg.getValue().toInt() == 2) { 292 | taskBuilder.setModule(MODULE_2); 293 | } else { 294 | taskBuilder.setModule(MODULE_1); 295 | } 296 | } 297 | 298 | Device::TaskRecord task = taskBuilder.build(); 299 | SerialAdapter::getInstance().sendTask(std::move(task)); 300 | ; 301 | } 302 | 303 | //================== 304 | // Transmit Signal 305 | //================== 306 | void SerialAdapter::registerTransmitSignalCommand(SimpleCLI& cli) 307 | { 308 | Command cmd = cli.addCmd("transmit", commandTransmitSignal); 309 | cmd.addArg("t,type"); 310 | cmd.addArg("f,frequency", ""); 311 | cmd.addArg("d,data", ""); 312 | cmd.addArg("p,preset", ""); 313 | cmd.addArg("a,module", ""); 314 | cmd.addArg("d,deviation", ""); 315 | cmd.addArg("m,modulation", ""); 316 | cmd.addArg("s,filename", ""); 317 | cmd.addArg("r,repeat", "1"); 318 | } 319 | 320 | void SerialAdapter::commandTransmitSignal(cmd* commandPointer) 321 | { 322 | Command cmd(commandPointer); 323 | Argument typeArg = cmd.getArg("t"); 324 | Argument moduleArg = cmd.getArg("a"); 325 | Argument frequencyArg = cmd.getArg("f"); 326 | Argument modulationArg = cmd.getArg("m"); 327 | Argument dataArg = cmd.getArg("d"); 328 | Argument presetArg = cmd.getArg("p"); 329 | Argument deviationArg = cmd.getArg("d"); 330 | Argument repeatArg = cmd.getArg("r"); 331 | Argument filenameArg = cmd.getArg("s"); 332 | 333 | std::vector requiredParams; 334 | Device::TransmissionType type = Device::TransmissionType::Raw; 335 | 336 | if (typeArg.isSet()) { 337 | if (typeArg.getValue() == "file") { 338 | type = Device::TransmissionType::File; 339 | requiredParams.push_back(filenameArg); 340 | } else if (typeArg.getValue() == "binary") { 341 | type = Device::TransmissionType::Binary; 342 | } else if (typeArg.getValue() == "raw") { 343 | type = Device::TransmissionType::Raw; 344 | requiredParams.push_back(frequencyArg); 345 | if (!presetArg.isSet()) { 346 | requiredParams.push_back(dataArg); 347 | } 348 | } else { 349 | error("\"error\": \"Unsupported transmission type\"}"); 350 | } 351 | } 352 | 353 | if (!handleMissingArguments(requiredParams)) { 354 | return; 355 | } 356 | 357 | Device::TaskTransmissionBuilder taskBuilder(type); 358 | 359 | taskBuilder.setFrequency(frequencyArg.getValue().toFloat()); 360 | 361 | if (moduleArg.isSet()) 362 | taskBuilder.setModule(moduleArg.getValue().toInt() == 2 ? MODULE_2 : MODULE_1); 363 | if (modulationArg.isSet()) 364 | taskBuilder.setModulation(modulationArg.getValue().toInt()); 365 | if (dataArg.isSet()) 366 | taskBuilder.setData(dataArg.getValue().c_str()); 367 | if (presetArg.isSet()) 368 | taskBuilder.setPreset(presetArg.getValue().c_str()); 369 | if (deviationArg.isSet()) 370 | taskBuilder.setDeviation(deviationArg.getValue().toFloat()); 371 | if (repeatArg.isSet()) 372 | taskBuilder.setRepeat(repeatArg.getValue().toInt()); 373 | if (filenameArg.isSet()) 374 | taskBuilder.setFilename(filenameArg.getValue().c_str()); 375 | 376 | Device::TaskTransmission task = taskBuilder.build(); 377 | SerialAdapter::getInstance().sendTask(std::move(task)); 378 | ; 379 | } 380 | 381 | void SerialAdapter::setup(SimpleCLI& cli) 382 | { 383 | this->simpleCli = &cli; 384 | cli.setOnError(errorCallback); 385 | registerDetectSignalCommand(cli); 386 | registerRecordSignalCommand(cli); 387 | registerTransmitSignalCommand(cli); 388 | registerGetStateCommand(cli); 389 | registerIdleCommand(cli); 390 | registerFilesListCommand(cli); 391 | registerFileUploadCommand(cli); 392 | } 393 | 394 | void SerialAdapter::errorCallback(cmd_error* e) 395 | { 396 | CommandError cmdError(e); 397 | error(cmdError.toString().c_str()); 398 | } 399 | 400 | void SerialAdapter::notify(String type, std::string message) 401 | { 402 | if (!message.empty()) { 403 | String response; 404 | if (message[0] != '{' && message[0] != '[') { 405 | response = "Event: {\"type\":\"" + type + "\",\"data\":\"" + String(message.c_str()) + "\"}"; 406 | } else { 407 | response = "Event: {\"type\":\"" + type + "\",\"data\":" + String(message.c_str()) + "}"; 408 | } 409 | writeResponseMessage(response); 410 | } else { 411 | Serial.println("message is empty"); 412 | } 413 | } 414 | 415 | void SerialAdapter::response(String message) 416 | { 417 | String response; 418 | response = "Response: " + message; 419 | writeResponseMessage(response); 420 | } 421 | 422 | void SerialAdapter::error(String message) 423 | { 424 | String response; 425 | response = "Error: " + message; 426 | writeResponseMessage(response); 427 | } 428 | 429 | void SerialAdapter::writeResponseMessage(String message) 430 | { 431 | Serial.println(message); 432 | } 433 | 434 | bool SerialAdapter::handleMissingArguments(const std::vector& arguments) 435 | { 436 | std::vector missingParams; 437 | for (const auto& argument : arguments) { 438 | if (!argument.isSet()) { 439 | missingParams.push_back(argument.getName()); 440 | } 441 | } 442 | 443 | if (!missingParams.empty()) { 444 | String errorMessage = "{\"error\":\"Missing required arguments: "; 445 | for (size_t i = 0; i < missingParams.size(); ++i) { 446 | errorMessage += missingParams[i]; 447 | if (i < missingParams.size() - 1) { 448 | errorMessage += ", "; 449 | } 450 | } 451 | errorMessage += "\"}"; 452 | error(errorMessage); 453 | return false; 454 | } 455 | 456 | return true; 457 | } -------------------------------------------------------------------------------- /src/ServiceMode.cpp: -------------------------------------------------------------------------------- 1 | #include "ServiceMode.h" 2 | 3 | AsyncWebServer server(80); 4 | AsyncWebSocket ws("/ws"); 5 | int wsClientCount = 0; 6 | 7 | size_t totalSize = 0; 8 | size_t receivedSize = 0; 9 | bool otaStarted = false; 10 | unsigned long lastProgressUpdate = 0; 11 | static std::vector accumulatedData; 12 | static size_t expectedChunkSize = 1024; 13 | 14 | void ServiceMode::initWifi() 15 | { 16 | const char *ssid = "Tutejshy AP"; 17 | const char *password = "123456789"; 18 | WiFi.mode(WIFI_AP); 19 | WiFi.softAP(ssid, password, 12, 0, 1); 20 | 21 | IPAddress local_IP(192, 168, 4, 1); 22 | IPAddress gateway(192, 168, 4, 1); 23 | IPAddress subnet(255, 255, 255, 0); 24 | WiFi.softAPConfig(local_IP, gateway, subnet); 25 | } 26 | 27 | void sendWsMessage(AsyncWebSocketClient *client, const String &message) 28 | { 29 | if (client) { 30 | client->text(message); 31 | } 32 | } 33 | 34 | void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) 35 | { 36 | if (type == WS_EVT_CONNECT) { 37 | wsClientCount++; 38 | } else if (type == WS_EVT_DISCONNECT) { 39 | wsClientCount--; 40 | 41 | otaStarted = false; 42 | accumulatedData.clear(); 43 | Update.abort(); 44 | 45 | } else if (type == WS_EVT_DATA) { 46 | AwsFrameInfo *info = (AwsFrameInfo *)arg; 47 | if (info->opcode == WS_TEXT) { 48 | String message = String((char *)data).substring(0, len); 49 | if (message.startsWith("start_ota:")) { 50 | totalSize = message.substring(10).toInt(); 51 | receivedSize = 0; 52 | otaStarted = true; 53 | 54 | if (!Update.begin(UPDATE_SIZE_UNKNOWN)) { 55 | Serial.println("OTA Begin Failed"); 56 | sendWsMessage(client, "ota_error:begin_failed"); 57 | } else { 58 | Serial.printf("OTA Started, expecting %u bytes\n", totalSize); 59 | sendWsMessage(client, "ota_ready_for_chunk"); 60 | } 61 | } else if (message.startsWith("cancel_ota")) { 62 | Update.abort(); 63 | otaStarted = false; 64 | Serial.println("OTA Update Canceled"); 65 | sendWsMessage(client, "ota_canceled"); 66 | } else if (message == "ota_end") { 67 | if (receivedSize + accumulatedData.size() == totalSize) { 68 | size_t chunkSize = accumulatedData.size(); 69 | if (Update.write(accumulatedData.data(), chunkSize) == chunkSize) { 70 | receivedSize += chunkSize; 71 | accumulatedData.clear(); 72 | 73 | if (Update.end(true)) { 74 | Serial.println("OTA Success, Rebooting..."); 75 | sendWsMessage(client, "ota_success"); 76 | delay(1000); 77 | ESP.restart(); 78 | } else { 79 | Update.printError(Serial); 80 | sendWsMessage(client, "ota_error:end_failed"); 81 | } 82 | } else { 83 | Update.printError(Serial); 84 | sendWsMessage(client, "ota_error:write_failed"); 85 | } 86 | } else { 87 | sendWsMessage(client, "ota_error:size_mismatch"); 88 | } 89 | } 90 | } else if (info->opcode == WS_BINARY && otaStarted) { 91 | accumulatedData.insert(accumulatedData.end(), data, data + len); 92 | 93 | if (accumulatedData.size() >= expectedChunkSize) { 94 | size_t chunkSize = accumulatedData.size(); 95 | if (Update.write(accumulatedData.data(), chunkSize) == chunkSize) { 96 | receivedSize += chunkSize; 97 | accumulatedData.clear(); 98 | 99 | unsigned long currentTime = millis(); 100 | float progress = (receivedSize * 100.0) / totalSize; 101 | if (currentTime - lastProgressUpdate >= 1000 || receivedSize == totalSize) { 102 | lastProgressUpdate = currentTime; 103 | Serial.printf("Received %u/%u bytes\n", receivedSize, totalSize); 104 | sendWsMessage(client, "progress:" + String(progress, 1)); 105 | } 106 | 107 | sendWsMessage(client, "ota_ready_for_chunk"); 108 | } else { 109 | Update.printError(Serial); 110 | sendWsMessage(client, "ota_error:write_failed"); 111 | } 112 | } 113 | } 114 | } 115 | } 116 | 117 | void ServiceMode::serveWebPage() 118 | { 119 | static const uint8_t serverIndex[] PROGMEM = { 120 | 31, 139, 8, 0, 131, 243, 237, 102, 0, 255, 157, 89, 11, 115, 219, 54, 151, 253, 43, 12, 221, 36, 226, 70, 162, 37, 59, 78, 92, 42, 116, 55, 205, 151, 121 | 204, 238, 76, 51, 205, 196, 233, 116, 102, 189, 94, 7, 34, 65, 9, 13, 69, 112, 1, 208, 178, 171, 240, 191, 247, 92, 0, 148, 40, 249, 177, 153, 149, 70, 122 | 18, 31, 184, 175, 115, 159, 160, 222, 60, 201, 101, 102, 110, 107, 30, 44, 204, 178, 60, 123, 67, 223, 65, 201, 170, 121, 26, 242, 42, 196, 57, 103, 249, 217, 123 | 155, 37, 55, 44, 200, 22, 76, 105, 110, 210, 176, 49, 197, 232, 20, 247, 236, 213, 138, 45, 121, 26, 94, 11, 190, 170, 165, 50, 97, 144, 201, 202, 240, 10, 124 | 171, 86, 34, 55, 139, 52, 231, 215, 34, 227, 35, 123, 50, 20, 149, 48, 130, 149, 35, 157, 177, 146, 167, 19, 176, 208, 230, 182, 228, 103, 255, 182, 46, 64, 125 | 53, 42, 216, 82, 148, 183, 201, 71, 89, 177, 76, 14, 151, 178, 146, 186, 102, 25, 111, 23, 147, 117, 38, 75, 169, 146, 131, 217, 9, 63, 61, 57, 158, 218, 126 | 213, 90, 252, 205, 147, 227, 113, 125, 51, 53, 252, 198, 140, 88, 41, 230, 85, 146, 65, 52, 87, 237, 226, 120, 189, 100, 106, 46, 170, 209, 76, 26, 35, 151, 127 | 201, 184, 157, 201, 252, 118, 61, 99, 217, 183, 185, 146, 77, 149, 39, 7, 19, 70, 239, 169, 91, 151, 140, 167, 53, 203, 115, 81, 205, 113, 228, 133, 21, 69, 128 | 209, 198, 222, 28, 176, 187, 113, 70, 36, 167, 99, 146, 233, 201, 142, 112, 28, 176, 198, 200, 45, 121, 64, 215, 166, 51, 121, 67, 26, 210, 149, 153, 84, 57, 129 | 87, 208, 228, 166, 141, 121, 46, 140, 84, 107, 199, 105, 50, 30, 63, 157, 238, 168, 116, 68, 239, 158, 124, 176, 33, 218, 100, 2, 41, 90, 150, 34, 15, 14, 130 | 198, 227, 177, 99, 190, 96, 185, 92, 65, 220, 132, 84, 176, 95, 71, 248, 208, 253, 227, 141, 50, 143, 169, 82, 138, 138, 175, 115, 161, 235, 146, 221, 38, 69, 131 | 201, 111, 166, 127, 53, 218, 136, 226, 118, 228, 109, 78, 44, 250, 163, 25, 55, 43, 206, 171, 169, 5, 120, 36, 12, 95, 106, 187, 124, 164, 13, 83, 102, 35, 132 | 202, 106, 48, 110, 227, 154, 41, 182, 220, 243, 151, 55, 247, 100, 207, 89, 37, 47, 76, 27, 95, 103, 107, 203, 14, 32, 172, 146, 201, 116, 71, 35, 123, 35, 133 | 23, 138, 103, 70, 72, 120, 87, 150, 205, 178, 2, 9, 43, 27, 142, 104, 170, 27, 115, 65, 177, 155, 22, 162, 228, 151, 253, 11, 53, 211, 122, 5, 99, 119, 134 | 46, 146, 232, 203, 161, 230, 37, 216, 249, 31, 199, 234, 65, 127, 184, 16, 121, 212, 31, 222, 196, 62, 12, 15, 32, 190, 175, 175, 151, 122, 100, 97, 89, 112, 135 | 49, 95, 152, 228, 248, 4, 199, 29, 175, 215, 222, 177, 119, 40, 147, 132, 126, 70, 206, 4, 9, 238, 13, 130, 188, 186, 47, 186, 119, 253, 224, 181, 31, 255, 136 | 32, 195, 100, 33, 175, 185, 218, 97, 235, 25, 121, 182, 157, 144, 70, 105, 156, 214, 82, 216, 228, 139, 141, 168, 187, 0, 120, 149, 205, 114, 158, 245, 18, 118, 137 | 130, 32, 245, 201, 51, 50, 178, 78, 200, 222, 45, 250, 150, 54, 22, 21, 188, 34, 242, 142, 7, 73, 60, 57, 238, 235, 236, 212, 219, 241, 173, 67, 116, 87, 138 | 147, 190, 28, 202, 132, 123, 88, 252, 48, 102, 119, 61, 222, 247, 216, 182, 58, 28, 83, 117, 184, 71, 208, 15, 98, 137, 252, 153, 101, 187, 89, 217, 207, 59, 139 | 87, 222, 250, 118, 217, 248, 0, 81, 63, 132, 189, 102, 46, 249, 55, 2, 145, 214, 86, 80, 158, 231, 222, 168, 145, 98, 185, 104, 180, 139, 89, 82, 175, 40, 140 | 145, 130, 11, 145, 231, 72, 247, 90, 106, 20, 107, 4, 129, 226, 37, 51, 226, 154, 147, 148, 194, 139, 25, 119, 50, 246, 82, 166, 147, 225, 77, 51, 138, 85, 141 | 158, 139, 165, 11, 226, 35, 13, 54, 117, 87, 156, 169, 0, 56, 233, 247, 212, 247, 149, 147, 240, 26, 245, 110, 181, 128, 249, 35, 91, 142, 146, 74, 174, 20, 142 | 171, 219, 216, 169, 185, 129, 170, 146, 21, 52, 220, 199, 238, 241, 138, 182, 31, 30, 32, 15, 238, 9, 145, 173, 115, 95, 158, 62, 109, 15, 26, 189, 163, 62, 143 | 21, 255, 246, 64, 150, 235, 13, 96, 133, 184, 225, 249, 148, 184, 142, 167, 118, 9, 12, 184, 227, 156, 253, 90, 163, 230, 51, 54, 24, 15, 233, 29, 159, 68, 144 | 211, 191, 71, 162, 202, 249, 13, 45, 67, 63, 114, 81, 93, 73, 42, 156, 112, 17, 207, 219, 131, 172, 24, 218, 80, 185, 227, 166, 62, 233, 164, 253, 247, 37, 145 | 58, 14, 11, 6, 91, 27, 94, 189, 130, 169, 209, 218, 149, 127, 91, 95, 9, 207, 132, 190, 238, 192, 181, 45, 243, 190, 174, 15, 169, 90, 247, 210, 181, 125, 146 | 115, 232, 58, 248, 155, 67, 55, 40, 80, 151, 197, 208, 48, 57, 59, 231, 138, 26, 127, 240, 81, 230, 28, 55, 39, 103, 111, 114, 113, 29, 100, 37, 234, 114, 147 | 26, 122, 254, 232, 255, 22, 238, 192, 194, 29, 58, 188, 195, 64, 228, 105, 168, 155, 48, 176, 165, 57, 13, 255, 168, 115, 102, 120, 240, 65, 168, 229, 138, 41, 148 | 14, 162, 67, 176, 186, 143, 95, 224, 98, 194, 113, 40, 52, 77, 47, 199, 103, 29, 93, 240, 71, 93, 74, 150, 67, 151, 227, 93, 177, 84, 57, 60, 73, 17, 149 | 6, 44, 203, 120, 141, 233, 37, 158, 9, 140, 63, 193, 131, 10, 54, 88, 187, 81, 144, 24, 247, 21, 236, 169, 6, 31, 237, 168, 85, 207, 246, 239, 239, 159, 150 | 23, 27, 11, 239, 216, 89, 215, 225, 217, 248, 233, 206, 221, 135, 244, 203, 160, 159, 167, 234, 196, 123, 117, 223, 177, 42, 227, 229, 70, 93, 224, 66, 248, 62, 151 | 2, 171, 195, 241, 61, 38, 151, 224, 156, 27, 131, 206, 164, 29, 138, 180, 148, 100, 185, 161, 102, 35, 207, 159, 222, 195, 112, 150, 61, 226, 114, 149, 109, 84, 152 | 252, 204, 49, 103, 6, 239, 100, 85, 136, 249, 163, 78, 208, 251, 81, 210, 41, 120, 15, 134, 68, 32, 203, 125, 84, 186, 21, 58, 83, 162, 54, 103, 48, 89, 153 | 67, 82, 122, 17, 162, 144, 135, 195, 112, 114, 100, 127, 142, 94, 218, 159, 151, 152, 253, 240, 243, 243, 43, 119, 239, 103, 119, 243, 248, 212, 221, 61, 121, 237, 154 | 174, 191, 126, 121, 122, 106, 239, 79, 78, 60, 245, 241, 216, 173, 64, 171, 199, 43, 188, 68, 185, 35, 49, 101, 138, 92, 42, 196, 213, 18, 57, 146, 172, 209, 155 | 249, 146, 240, 79, 241, 65, 4, 116, 30, 100, 172, 10, 102, 60, 224, 194, 44, 184, 10, 158, 179, 250, 121, 80, 72, 21, 188, 69, 124, 106, 29, 124, 162, 14, 156 | 23, 224, 252, 121, 86, 10, 248, 232, 121, 28, 14, 109, 211, 4, 8, 9, 79, 207, 46, 66, 86, 67, 160, 187, 25, 94, 162, 165, 102, 101, 147, 115, 61, 224, 157 | 209, 144, 112, 76, 66, 215, 235, 195, 161, 172, 169, 124, 232, 100, 143, 162, 29, 106, 45, 114, 175, 213, 249, 249, 127, 254, 43, 144, 69, 0, 85, 2, 171, 97, 158 | 133, 10, 42, 213, 183, 61, 161, 60, 46, 121, 53, 55, 139, 179, 177, 151, 65, 3, 87, 216, 14, 187, 97, 204, 115, 251, 136, 42, 67, 166, 49, 32, 192, 25, 159 | 142, 79, 237, 214, 130, 101, 104, 109, 250, 33, 150, 233, 233, 46, 79, 205, 21, 118, 18, 87, 51, 214, 228, 87, 138, 22, 59, 222, 191, 226, 60, 160, 115, 11, 160 | 150, 91, 4, 60, 177, 239, 64, 68, 238, 112, 54, 63, 128, 137, 105, 91, 239, 170, 44, 5, 201, 186, 68, 84, 86, 105, 213, 148, 229, 84, 20, 3, 162, 1, 161 | 40, 60, 77, 81, 175, 140, 66, 216, 133, 17, 102, 22, 23, 64, 60, 38, 155, 222, 154, 193, 56, 162, 251, 7, 225, 247, 239, 135, 255, 115, 193, 70, 127, 191, 162 | 29, 253, 215, 120, 244, 243, 213, 232, 242, 197, 79, 135, 177, 225, 218, 64, 252, 180, 74, 177, 11, 107, 150, 64, 62, 254, 223, 134, 171, 219, 115, 63, 136, 13, 163 | 204, 179, 103, 61, 78, 79, 44, 167, 95, 190, 30, 252, 180, 230, 237, 215, 132, 71, 45, 47, 53, 15, 160, 11, 190, 32, 151, 50, 27, 26, 253, 199, 151, 143, 164 | 191, 189, 47, 57, 177, 139, 214, 85, 202, 91, 197, 77, 163, 170, 53, 47, 147, 106, 136, 184, 231, 131, 104, 13, 154, 42, 170, 98, 155, 12, 191, 9, 109, 98, 165 | 204, 156, 131, 46, 41, 162, 169, 163, 128, 195, 133, 6, 214, 11, 185, 186, 143, 68, 241, 37, 166, 134, 135, 168, 100, 53, 224, 67, 40, 224, 201, 192, 255, 253, 166 | 53, 52, 34, 74, 94, 113, 101, 111, 238, 82, 144, 107, 129, 70, 71, 65, 167, 168, 1, 118, 55, 201, 119, 87, 102, 26, 94, 235, 49, 183, 77, 232, 130, 95, 167 | 166, 102, 119, 29, 132, 190, 35, 117, 123, 92, 119, 45, 6, 246, 59, 4, 206, 164, 135, 105, 188, 201, 251, 100, 153, 226, 8, 43, 162, 240, 151, 179, 193, 198, 168 | 161, 238, 158, 247, 7, 150, 68, 80, 171, 174, 121, 69, 194, 55, 224, 216, 11, 239, 22, 162, 196, 213, 152, 151, 223, 191, 239, 73, 64, 32, 82, 240, 229, 233, 169 | 218, 29, 232, 161, 24, 178, 116, 60, 84, 233, 100, 124, 244, 114, 40, 211, 241, 180, 104, 42, 187, 75, 10, 42, 248, 202, 133, 161, 76, 179, 65, 87, 142, 35, 170 | 176, 157, 74, 4, 61, 176, 167, 0, 73, 195, 112, 250, 251, 236, 47, 218, 3, 65, 47, 37, 144, 7, 121, 20, 35, 107, 222, 179, 108, 49, 24, 92, 240, 97, 171 | 117, 25, 33, 230, 187, 128, 206, 6, 145, 55, 101, 16, 162, 98, 130, 223, 6, 220, 208, 206, 6, 184, 226, 29, 232, 52, 164, 20, 41, 225, 147, 216, 214, 109, 172 | 202, 17, 151, 93, 209, 90, 239, 48, 235, 46, 247, 248, 217, 154, 142, 120, 178, 228, 62, 25, 55, 170, 81, 34, 222, 167, 148, 91, 183, 213, 130, 236, 213, 30, 173 | 217, 1, 66, 141, 178, 4, 106, 84, 145, 137, 157, 72, 158, 167, 70, 53, 188, 117, 89, 180, 167, 149, 109, 57, 247, 41, 21, 83, 236, 133, 100, 19, 170, 165, 174 | 45, 70, 17, 196, 240, 210, 237, 36, 211, 202, 157, 160, 202, 48, 244, 48, 55, 53, 33, 3, 157, 194, 226, 81, 20, 81, 192, 58, 237, 29, 110, 162, 142, 124, 175 | 249, 97, 143, 18, 94, 103, 116, 234, 12, 37, 233, 155, 19, 65, 39, 158, 133, 122, 148, 5, 77, 131, 91, 38, 8, 137, 45, 19, 102, 153, 232, 24, 249, 28, 176 | 206, 202, 6, 101, 116, 64, 113, 145, 83, 186, 109, 13, 159, 94, 35, 33, 233, 116, 104, 133, 182, 17, 98, 173, 31, 213, 202, 93, 109, 55, 81, 74, 69, 8, 177 | 209, 137, 142, 28, 197, 174, 192, 56, 211, 67, 164, 124, 69, 251, 125, 42, 167, 83, 157, 86, 124, 21, 252, 201, 103, 231, 50, 251, 198, 113, 119, 165, 147, 195, 178 | 67, 116, 222, 120, 242, 234, 52, 126, 25, 79, 14, 87, 218, 58, 0, 15, 139, 32, 44, 181, 170, 129, 45, 166, 191, 142, 237, 20, 167, 24, 39, 35, 218, 49, 179 | 64, 232, 180, 25, 68, 173, 37, 200, 74, 169, 57, 76, 192, 206, 65, 41, 169, 28, 45, 30, 76, 41, 136, 249, 151, 125, 108, 21, 8, 29, 96, 234, 14, 216, 180 | 53, 19, 37, 155, 149, 60, 14, 62, 81, 203, 226, 129, 81, 183, 1, 155, 51, 81, 197, 144, 222, 201, 243, 2, 58, 121, 94, 252, 70, 222, 18, 141, 155, 205, 181 | 185, 237, 36, 219, 54, 65, 145, 226, 93, 132, 82, 77, 125, 201, 14, 220, 250, 79, 52, 126, 202, 37, 170, 16, 128, 201, 176, 43, 184, 46, 191, 189, 66, 26, 182 | 92, 101, 139, 166, 250, 22, 70, 209, 186, 0, 239, 174, 5, 96, 85, 173, 228, 92, 65, 74, 66, 247, 28, 79, 141, 71, 33, 120, 106, 247, 1, 35, 170, 129, 183 | 95, 177, 55, 18, 48, 15, 11, 46, 38, 151, 209, 180, 30, 232, 29, 6, 36, 70, 55, 118, 196, 32, 22, 245, 0, 83, 126, 132, 29, 168, 197, 228, 247, 47, 184 | 111, 253, 176, 136, 209, 108, 89, 35, 201, 249, 147, 224, 51, 159, 73, 73, 206, 138, 99, 130, 130, 140, 221, 227, 103, 193, 37, 110, 158, 205, 123, 58, 79, 130, 185 | 240, 197, 29, 109, 238, 33, 198, 20, 132, 97, 149, 231, 61, 250, 190, 26, 254, 110, 39, 185, 221, 134, 23, 144, 241, 8, 240, 244, 35, 51, 139, 120, 41, 170, 186 | 129, 26, 178, 145, 116, 229, 201, 216, 192, 250, 128, 217, 255, 51, 96, 197, 158, 218, 245, 118, 17, 235, 18, 142, 31, 200, 161, 124, 1, 240, 13, 220, 70, 195, 187 | 125, 218, 241, 5, 87, 141, 242, 97, 115, 4, 72, 55, 37, 10, 139, 124, 129, 246, 4, 149, 229, 89, 202, 54, 183, 157, 233, 21, 20, 71, 233, 166, 181, 44, 188 | 127, 171, 223, 42, 197, 110, 127, 109, 138, 2, 237, 175, 234, 229, 2, 116, 95, 11, 55, 87, 160, 168, 79, 169, 154, 11, 77, 59, 40, 174, 72, 187, 219, 15, 189 | 82, 189, 35, 151, 167, 5, 3, 60, 240, 26, 156, 130, 40, 195, 140, 223, 15, 58, 108, 37, 250, 167, 54, 179, 220, 169, 207, 44, 232, 178, 17, 89, 83, 247, 190 | 193, 42, 218, 212, 119, 69, 205, 110, 236, 194, 33, 127, 17, 62, 117, 81, 141, 173, 250, 166, 158, 218, 139, 237, 188, 23, 223, 84, 16, 48, 46, 102, 223, 64, 191 | 178, 213, 167, 119, 149, 18, 10, 62, 40, 176, 211, 24, 132, 111, 177, 213, 184, 149, 77, 160, 27, 127, 176, 98, 152, 96, 141, 164, 65, 151, 246, 35, 52, 91, 192 | 210, 74, 187, 39, 105, 172, 127, 127, 9, 163, 103, 207, 58, 60, 221, 178, 43, 192, 138, 88, 32, 105, 216, 129, 221, 145, 182, 113, 57, 165, 36, 221, 71, 15, 193 | 160, 186, 31, 211, 38, 79, 251, 89, 18, 77, 96, 28, 249, 230, 234, 99, 202, 103, 181, 235, 11, 1, 219, 42, 66, 116, 62, 197, 45, 188, 219, 146, 210, 43, 194 | 92, 83, 36, 170, 147, 112, 49, 190, 164, 210, 183, 65, 153, 50, 24, 158, 229, 112, 43, 143, 233, 249, 23, 10, 129, 51, 199, 38, 57, 89, 147, 132, 47, 216, 195 | 22, 61, 199, 112, 235, 38, 224, 189, 46, 184, 65, 219, 11, 23, 198, 212, 123, 181, 111, 206, 205, 149, 5, 24, 165, 50, 6, 128, 152, 184, 104, 100, 182, 14, 196 | 139, 182, 87, 214, 57, 73, 119, 185, 246, 223, 212, 33, 21, 207, 27, 4, 185, 29, 161, 58, 208, 46, 170, 161, 198, 252, 212, 173, 75, 97, 52, 165, 33, 28, 197 | 16, 241, 11, 12, 99, 74, 44, 7, 17, 85, 124, 119, 212, 77, 39, 188, 29, 174, 97, 49, 18, 163, 141, 46, 224, 36, 168, 26, 94, 14, 136, 163, 4, 114, 198 | 54, 247, 123, 230, 80, 147, 48, 195, 106, 91, 156, 108, 163, 235, 198, 114, 228, 148, 237, 37, 78, 242, 206, 220, 101, 228, 124, 94, 218, 182, 108, 23, 135, 195, 199 | 39, 122, 163, 130, 222, 242, 135, 98, 221, 4, 182, 55, 219, 160, 159, 109, 173, 190, 128, 14, 189, 225, 134, 58, 255, 87, 247, 224, 135, 170, 241, 200, 53, 237, 200 | 240, 167, 181, 105, 195, 203, 175, 93, 16, 233, 78, 107, 1, 16, 42, 64, 236, 71, 186, 115, 49, 67, 247, 156, 111, 58, 117, 181, 181, 71, 111, 236, 121, 212, 201 | 20, 248, 191, 67, 243, 217, 51, 230, 167, 117, 66, 150, 198, 147, 168, 165, 88, 167, 246, 178, 31, 235, 208, 233, 9, 12, 190, 63, 148, 51, 169, 232, 169, 185, 202 | 77, 171, 5, 158, 56, 225, 33, 226, 2, 19, 15, 2, 154, 151, 57, 118, 89, 221, 88, 128, 237, 12, 234, 224, 31, 159, 127, 59, 231, 76, 101, 139, 79, 100, 203 | 186, 254, 63, 6, 67, 67, 216, 117, 67, 171, 13, 34, 132, 252, 131, 65, 138, 49, 168, 11, 210, 225, 26, 127, 25, 45, 100, 158, 132, 159, 126, 63, 255, 18, 204 | 14, 233, 153, 17, 246, 124, 201, 154, 154, 62, 205, 249, 163, 47, 52, 87, 37, 216, 138, 34, 10, 17, 76, 240, 233, 33, 30, 80, 173, 86, 35, 200, 95, 142, 205 | 26, 133, 12, 206, 176, 59, 206, 177, 3, 164, 71, 77, 9, 2, 83, 158, 219, 189, 23, 5, 224, 54, 226, 29, 24, 60, 150, 223, 126, 9, 187, 103, 2, 129, 206 | 102, 215, 64, 192, 247, 183, 2, 213, 246, 54, 134, 172, 15, 232, 237, 184, 140, 74, 68, 247, 81, 3, 220, 106, 130, 200, 50, 125, 48, 170, 109, 214, 226, 169, 207 | 197, 255, 163, 230, 161, 117, 80, 15, 130, 111, 28, 50, 13, 182, 172, 20, 191, 184, 149, 243, 130, 161, 173, 108, 244, 176, 69, 16, 53, 161, 87, 85, 104, 51, 208 | 246, 48, 222, 150, 247, 221, 178, 64, 225, 66, 120, 108, 250, 168, 123, 188, 210, 73, 94, 48, 141, 61, 57, 175, 58, 213, 238, 42, 210, 1, 98, 135, 101, 207, 209 | 99, 139, 157, 35, 219, 177, 6, 4, 109, 31, 62, 40, 177, 131, 160, 31, 5, 28, 41, 137, 216, 37, 79, 168, 173, 68, 244, 132, 209, 61, 154, 121, 115, 232, 210 | 30, 46, 30, 218, 63, 42, 255, 1, 138, 96, 215, 90, 184, 28, 0, 0}; 211 | 212 | DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*"); 213 | DefaultHeaders::Instance().addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); 214 | DefaultHeaders::Instance().addHeader("Access-Control-Allow-Headers", "Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With"); 215 | 216 | server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { 217 | AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", serverIndex, sizeof(serverIndex)); 218 | response->addHeader("Content-Encoding", "gzip"); 219 | request->send(response); 220 | }); 221 | 222 | server.on("/reset_config", HTTP_GET, [](AsyncWebServerRequest *request) { 223 | ConfigManager::resetConfigToDefault(); 224 | request->send(204, "text/plain", ""); 225 | }); 226 | 227 | server.on("/get_config", HTTP_GET, [](AsyncWebServerRequest *request) { 228 | String configData = ConfigManager::getPlainConfig(); 229 | 230 | if (configData.isEmpty()) { 231 | request->send(500, "text/plain", "Failed to read config"); 232 | } else { 233 | request->send(200, "text/plain", configData); 234 | } 235 | }); 236 | 237 | server.on("/set_config", HTTP_POST, [](AsyncWebServerRequest *request) { 238 | String configData = ""; 239 | 240 | int params = request->params(); 241 | for (int i = 0; i < params; i++) { 242 | AsyncWebParameter *p = request->getParam(i); 243 | configData += p->name() + "=" + p->value() + "\n"; 244 | } 245 | 246 | if (ConfigManager::saveConfig(configData)) { 247 | request->send(200, "text/plain", "Settings saved successfully."); 248 | } else { 249 | request->send(500, "text/plain", "Failed to save settings."); 250 | } 251 | }); 252 | 253 | ws.onEvent(onWsEvent); 254 | server.addHandler(&ws); 255 | 256 | server.begin(); 257 | } 258 | 259 | void ServiceMode::serviceModeStart() 260 | { 261 | Serial.println("Starting Service mode"); 262 | 263 | Serial.println("Starting Wi-Fi Access Point"); 264 | initWifi(); 265 | Serial.println(WiFi.softAPIP()); 266 | 267 | Serial.println("Starting Web Server"); 268 | serveWebPage(); 269 | Serial.println("Service mode on"); 270 | } --------------------------------------------------------------------------------