├── .gitignore ├── .vscode ├── extensions.json └── settings.json ├── LICENSE ├── custom_partition.csv ├── gateKeeper.code-workspace ├── images └── my751j2xvw2e1.jpeg ├── include └── README ├── lib └── README ├── platformio.ini ├── readme.md ├── src ├── FlipperSubFile.cpp ├── FlipperSubFile.h ├── RCSwitch.cpp ├── RCSwitch.h ├── main.cpp ├── main.h └── modules │ ├── CC1101 │ ├── CC1101.cpp │ └── CC1101.h │ └── SerialCom │ ├── serialCom.cpp │ └── serialCom.h └── test └── README /.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .vscode/.browse.c_cpp.db* 3 | .vscode/c_cpp_properties.json 4 | .vscode/launch.json 5 | .vscode/ipch 6 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "platformio.platformio-ide" 6 | ], 7 | "unwantedRecommendations": [ 8 | "ms-vscode.cpptools-extension-pack" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "idf.portWin": "COM10", 3 | "files.associations": { 4 | "array": "cpp", 5 | "deque": "cpp", 6 | "string": "cpp", 7 | "unordered_map": "cpp", 8 | "unordered_set": "cpp", 9 | "vector": "cpp", 10 | "string_view": "cpp", 11 | "initializer_list": "cpp", 12 | "*.tpp": "cpp", 13 | "random": "cpp" 14 | } 15 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | # Nucleus License (GPL-inspired) 2 | 3 | **Nucleus One / Nucleus ESP32 License** 4 | Version 1.0, November 2024 5 | 6 | **Copyright (c) 2024, Petr Bělohoubek** 7 | 8 | This software is licensed under terms that allow for modification, sharing, and contribution, with certain restrictions on commercial redistribution. 9 | 10 | ### 1. Permissions 11 | You are allowed to use, modify, and distribute copies of this software, with or without modification, under the following conditions: 12 | 13 | - **Open Access**: You may freely use this software for personal, educational, and internal business purposes. 14 | - **Modifications and Contributions**: You may publish modified versions or contribute changes back to the original project, as long as attribution is given to the original author and contributors. 15 | 16 | ### 2. Restrictions on Commercial Redistribution and Resale 17 | This software is provided free of charge for use, but with the following restrictions: 18 | 19 | - **Prohibition on Commercial Sale**: You may not sell, license, or commercially distribute this software, modified or unmodified, without explicit permission from Petr Bělohoubek. 20 | - **Cloning and Productization Restriction**: You may not incorporate this software into another product intended for direct commercial resale without obtaining a separate commercial license from Petr Bělohoubek. 21 | 22 | ### 3. Permitted Use for Gifts and Non-Commercial Sharing 23 | You are encouraged to: 24 | - Use this software in personal projects, including commercial internal operations, provided it is not directly resold. 25 | - Share modifications as gifts or non-commercial contributions without charge, maintaining the terms of this license. 26 | 27 | ### 4. Contribution and Attribution Requirements 28 | - Any derivative works based on this software must retain this license and provide attribution to the original project, Nucleus One / Nucleus ESP32. 29 | - Publicly shared modifications must include this license and be available for further sharing under these same terms. 30 | 31 | ### 5. Contact for Commercial Licensing 32 | Entities interested in commercial distribution or incorporation into products for sale should contact Petr Bělohoubek at Petr.Belohoubekm89@gmail.com for commercial licensing. 33 | 34 | ### Disclaimer 35 | 36 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 37 | -------------------------------------------------------------------------------- /custom_partition.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, 0x300000, 5 | spiffs, data, spiffs, 0x310000, 0xe0000, 6 | coredump, data, coredump, 0x3f0000, 0x10000, -------------------------------------------------------------------------------- /gateKeeper.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "name": "gateKeeper", 5 | "path": "." 6 | } 7 | ], 8 | "settings": { 9 | "files.associations": { 10 | "array": "cpp", 11 | "deque": "cpp", 12 | "string": "cpp", 13 | "unordered_map": "cpp", 14 | "unordered_set": "cpp", 15 | "vector": "cpp", 16 | "string_view": "cpp", 17 | "initializer_list": "cpp", 18 | "*.tcc": "cpp", 19 | "system_error": "cpp", 20 | "random": "cpp", 21 | "fstream": "cpp", 22 | "iostream": "cpp", 23 | "istream": "cpp", 24 | "limits": "cpp", 25 | "new": "cpp", 26 | "ostream": "cpp", 27 | "ratio": "cpp", 28 | "stdexcept": "cpp", 29 | "streambuf": "cpp", 30 | "functional": "cpp", 31 | "map": "cpp", 32 | "sstream": "cpp" 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /images/my751j2xvw2e1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GthiN89/NucleusGateKeeper/ce5439fba29623bee4c882dbf8c9fd515d405c48/images/my751j2xvw2e1.jpeg -------------------------------------------------------------------------------- /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 40 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | monitor_speed = 115200 16 | board_build.mcu = esp32 17 | board_build.f_cpu = 80000000L 18 | build_flags = 19 | -D CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ=80 20 | board_build.partitions = custom_partition.csv 21 | lib_deps = 22 | https://github.com/LSatan/SmartRC-CC1101-Driver-Lib.git 23 | https://github.com/vshymanskyy/TinyGSM 24 | plerup/EspSoftwareSerial @ ^8.2.0 25 | mbed-seeed/BluetoothSerial @ 0.0.0+sha.f56002898ee8 26 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Nucleus Gate Keeper: Sub-GHz Code Capture Device 2 | 3 | ![Build Status](https://img.shields.io/github/actions/workflow/status/GthiN89/NucleusGateKeeper/ci.yml?branch=main) 4 | ![GitHub stars](https://img.shields.io/github/stars/GthiN89/NucleusGateKeeper?style=social) 5 | 6 | Nucleus Gate Keeper is a specialized, compact, and cost-effective device designed for **autonomously capturing Sub-GHz RF codes** used in remote-controlled systems, such as electric gates and other Sub-GHz-controlled devices. It is a side project born out of the Nucleus ESP32 codebase, leveraging much of its functionality to ensure rapid development and deployment. 7 | 8 | The device is compatible with **Flipper Zero** Sub-GHz `.sub` file format, allowing seamless integration with Flipper workflows for replay or further analysis. 9 | 10 | ## 📋 Project Overview 11 | 12 | Nucleus Gate Keeper simplifies the process of capturing RF codes without spending extensive time near the target device. It uses affordable and readily available hardware, focusing on **autonomous operation** and ease of use. 13 | 14 | **Status**: In Development 15 | The device can autonomously receive and save codes, supports basic settings via serial or Bluetooth serial, and recognizes around 30 known protocols. While AM protocols work fine, FM protocols are currently buggy. 16 | 17 | --- 18 | 19 | ## 🚀 Features Implemented 20 | 21 | ### 🛡️ Autonomous Code Capture 22 | - **Autonomous RF Code Capture**: The device independently detects and saves Sub-GHz signals without manual intervention. 23 | - **SD Card Integration**: Captured codes are stored on an SD card for easy retrieval and analysis. 24 | - **Flipper Zero Compatibility**: Captured codes are saved in `.sub` format, compatible with Flipper Zero for replay or analysis. 25 | - **Protocol Recognition**: Supports around 30 known Sub-GHz protocols. When a known protocol is detected, it is recorded and visible in the filename. 26 | - **Basic Settings Management**: Allows users to adjust basic settings through USB Serial or Bluetooth Serial connections. 27 | 28 | ### 📡 Communication 29 | - **Serial and Bluetooth Serial**: Provides dual interfaces for configuring the device and accessing captured data. 30 | 31 | ### 📂 Known Protocols Handling 32 | - **AM Protocols**: Fully functional and reliable. 33 | - **FM Protocols**: Currently buggy and under refinement. 34 | 35 | --- 36 | 37 | ## 🛠️ Hardware 38 | 39 | ### 📦 Components 40 | - **ESP32**: A versatile microcontroller for processing and communication. 41 | - **CC1101 Transceiver**: Captures Sub-GHz signals. 42 | - **SD Card Reader**: Provides onboard storage for captured codes. 43 | - **Battery**: Powers the device for autonomous operation. 44 | 45 | ### 🔧 Why This Setup? 46 | - **Proven Technology**: Leverages the established Nucleus ESP32 codebase for stable functionality. 47 | - **Affordability**: Keeps costs low, making the device disposable if necessary. 48 | - **Flipper Zero Integration**: Ensures seamless compatibility with widely used hacking tools. 49 | - **Compact Design**: Ensures portability and ease of deployment. 50 | 51 | --- 52 | 53 | ## 🔍 Current Functionalities 54 | 55 | - **Autonomous Code Capture**: Detects and logs RF codes without manual intervention. 56 | - **SD Card Support**: Saves RF codes directly to an SD card for retrieval and analysis. 57 | - **Flipper Zero-Compatible `.sub` Files**: Codes are saved in a format ready for use with Flipper Zero. 58 | - **Protocol Detection and Naming**: Recognizes around 30 known protocols. Detected protocols are reflected in the filename for easy identification. 59 | - **Basic Settings Over Serial/Bluetooth Serial**: Users can configure device settings through USB Serial or Bluetooth Serial interfaces. 60 | 61 | **Note**: FM protocols are currently buggy and may not function reliably. AM protocols are fully operational. 62 | 63 | --- 64 | 65 | ## 🛤️ Planned Features 66 | 67 | - **GSM/GPRS Communication**: Real-time updates and location tracking. 68 | - **Magnetic Sensor Integration**: Correlate RF codes with gate movements. 69 | - **Advanced RF Decoding**: Improved Sub-GHz protocol support and replay capabilities. 70 | - **Broader Compatibility**: Support for additional Sub-GHz-controlled devices. 71 | 72 | --- 73 | 74 | ## ⏰ Timing and Transmission Quality 75 | 76 | The current system is focused on reliable code capture. Future updates will improve RF decoding precision and add replay functionality. 77 | 78 | --- 79 | 80 | ## 📖 Usage Instructions 81 | 82 | 1. **Setup**: Insert an SD card and ensure the battery is charged. 83 | 2. **Deployment**: Place the device near the gate or Sub-GHz-controlled device. 84 | 3. **Monitoring**: The device autonomously captures and logs RF codes. 85 | 4. **Data Retrieval**: Collect the SD card to access captured RF codes in Flipper Zero-compatible `.sub` format. 86 | 5. **Configuration**: Adjust basic settings via USB Serial or Bluetooth Serial interfaces as needed. 87 | 88 | --- 89 | 90 | ## ⚠️ Limitations 91 | 92 | - **FM Protocols**: Currently buggy and may not capture FM signals reliably. 93 | - **Communication and Magnetic Sensor Features**: Under development. 94 | - **Protocol Support**: Currently focused on around 30 Sub-GHz protocols; expansion is planned. 95 | 96 | --- 97 | 98 | ## 🤝 Contribute or Stay Updated 99 | 100 | This project is open-source, and contributions are welcome! Whether it's optimizing the code, suggesting features, or helping with testing, your input is valuable. Follow updates and access source code at [https://github.com/GthiN89/NucleusGateKeeper](https://github.com/GthiN89/NucleusGateKeeper). 101 | 102 | Happy hacking! 103 | 104 | --- 105 | 106 | ## 📝 Additional Notes 107 | 108 | - **Protocol Identification**: When a known protocol is detected, the filename will include the protocol name for easy identification (e.g., `capture_AM650.sub`). 109 | - **Serial and Bluetooth Serial Configuration**: Ensure that your Bluetooth device is paired with the ESP32 using the configured PIN for secure communication. 110 | - **Security Enhancements**: Future updates may include more robust security features for Bluetooth communication. 111 | 112 | --- -------------------------------------------------------------------------------- /src/FlipperSubFile.cpp: -------------------------------------------------------------------------------- 1 | #include "FlipperSubFile.h" 2 | #include 3 | 4 | 5 | // Initialize the preset mapping (if this is not done elsewhere) 6 | const std::map FlipperSubFile::presetMapping = { 7 | {AM270, "FuriHalSubGhzPresetOok270Async"}, 8 | {AM650, "FuriHalSubGhzPresetOok650Async"}, 9 | {FM238, "FuriHalSubGhzPreset2FSKDev238Async"}, 10 | {FM476, "FuriHalSubGhzPreset2FSKDev476Async"}, 11 | {CUSTOM, "FuriHalSubGhzPresetCustom"} 12 | }; 13 | 14 | void FlipperSubFile::generateRaw( 15 | File& file, 16 | CC1101_PRESET presetName, 17 | const std::vector& customPresetData, 18 | String& samples, 19 | float frequency 20 | ) { 21 | if (!file) { 22 | Serial.println("Error: File is not open."); 23 | return; 24 | } 25 | writeHeader(file, frequency); 26 | writePresetInfo(file, presetName, customPresetData); 27 | writeRawProtocolData(file, samples); 28 | } 29 | 30 | void FlipperSubFile::writeHeader(File& file, float frequency) { 31 | file.println("Filetype: Flipper SubGhz RAW File"); 32 | file.println("Version: 1"); 33 | file.print("Frequency: "); 34 | file.print(frequency * 1e6, 0); 35 | file.println(); 36 | } 37 | 38 | void FlipperSubFile::writePresetInfo(File& file, CC1101_PRESET presetName, const std::vector& customPresetData) { 39 | file.print("Preset: "); 40 | file.println(getPresetName(presetName).c_str()); 41 | 42 | if (presetName == CC1101_PRESET::CUSTOM) { 43 | file.println("Custom_preset_module: CC1101"); 44 | file.print("Custom_preset_data: "); 45 | for (size_t i = 0; i < customPresetData.size(); ++i) { 46 | char hexStr[3]; 47 | sprintf(hexStr, "%02X", customPresetData[i]); 48 | file.print(hexStr); 49 | if (i < customPresetData.size() - 1) { 50 | file.print(" "); 51 | } 52 | } 53 | file.println(); 54 | } 55 | } 56 | 57 | void FlipperSubFile::writeRawProtocolData(File& file, String& samples) { 58 | file.println("Protocol: RAW"); 59 | file.print("RAW_Data: "); 60 | 61 | std::string sample; 62 | int wordCount = 0; 63 | 64 | std::string cppString(samples.c_str()); 65 | std::istringstream stream(cppString); 66 | 67 | while (std::getline(stream, sample, ' ')) { 68 | if (wordCount > 0 && wordCount % 512 == 0) { 69 | file.println(); 70 | file.print("RAW_Data: "); 71 | } 72 | file.print(sample.c_str()); 73 | file.print(' '); 74 | wordCount++; 75 | } 76 | file.println(); 77 | } 78 | 79 | std::string FlipperSubFile::getPresetName(CC1101_PRESET preset) { 80 | auto it = presetMapping.find(preset); 81 | return (it != presetMapping.end()) ? it->second : "FuriHalSubGhzPresetCustom"; 82 | } 83 | -------------------------------------------------------------------------------- /src/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 "main.h" 9 | #include "FlipperSubFile.h" 10 | 11 | 12 | class FlipperSubFile { 13 | public: 14 | /** 15 | * Generate a Flipper SubGhz RAW file. 16 | * @param file Reference to the SD card file to write data to. 17 | * @param presetName The CC1101 preset to be used (e.g., CUSTOM). 18 | * @param customPresetData Custom data if the preset is set to CUSTOM. 19 | * @param samples Raw signal data as a string of samples. 20 | * @param frequency The frequency of the signal in MHz. 21 | */ 22 | void generateRaw( 23 | File& file, 24 | CC1101_PRESET presetName, 25 | const std::vector& customPresetData, 26 | String& samples, 27 | float frequency 28 | ); 29 | 30 | private: 31 | /** 32 | * Writes the header information to the file. 33 | * @param file Reference to the SD card file. 34 | * @param frequency The frequency of the signal in MHz. 35 | */ 36 | void writeHeader(File& file, float frequency); 37 | 38 | /** 39 | * Writes preset information to the file. 40 | * @param file Reference to the SD card file. 41 | * @param presetName The CC1101 preset being used. 42 | * @param customPresetData Custom data for CUSTOM preset. 43 | */ 44 | void writePresetInfo(File& file, CC1101_PRESET presetName, const std::vector& customPresetData); 45 | 46 | /** 47 | * Writes the raw protocol data to the file. 48 | * @param file Reference to the SD card file. 49 | * @param samples String containing the raw signal samples. 50 | */ 51 | void writeRawProtocolData(File& file, String& samples); 52 | 53 | /** 54 | * Retrieves the name of the preset as a string. 55 | * @param preset The preset enum value. 56 | * @return A string representing the preset name. 57 | */ 58 | std::string getPresetName(CC1101_PRESET preset); 59 | 60 | // Mapping from CC1101 preset enums to their string representations 61 | static const std::map presetMapping; 62 | }; 63 | 64 | #endif // FLIPPER_SUB_FILE_H 65 | -------------------------------------------------------------------------------- /src/RCSwitch.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | RCSwitch - Arduino libary for remote control outlet switches 3 | Copyright (c) 2011 Suat Özgür. All right reserved. 4 | 5 | Contributors: 6 | - Andre Koehler / info(at)tomate-online(dot)de 7 | - Gordeev Andrey Vladimirovich / gordeev(at)openpyro(dot)com 8 | - Skineffect / http://forum.ardumote.com/viewtopic.php?f=2&t=46 9 | - Dominik Fischer / dom_fischer(at)web(dot)de 10 | - Frank Oltmanns / .(at)gmail(dot)com 11 | - Andreas Steinel / A.(at)gmail(dot)com 12 | - Max Horn / max(at)quendi(dot)de 13 | - Robert ter Vehn / .(at)gmail(dot)com 14 | - Johann Richard / .(at)gmail(dot)com 15 | - Vlad Gheorghe / .(at)gmail(dot)com https://github.com/vgheo 16 | 17 | Project home: https://github.com/sui77/rc-switch/ 18 | 19 | This library is free software; you can redistribute it and/or 20 | modify it under the terms of the GNU Lesser General Public 21 | License as published by the Free Software Foundation; either 22 | version 2.1 of the License, or (at your option) any later version. 23 | 24 | This library is distributed in the hope that it will be useful, 25 | but WITHOUT ANY WARRANTY; without even the implied warranty of 26 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 27 | Lesser General Public License for more details. 28 | 29 | You should have received a copy of the GNU Lesser General Public 30 | License along with this library; if not, write to the Free Software 31 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 32 | */ 33 | //#define KeeLoq_NLF 0x3A5C742EUL 34 | #define KeeLoq_NLF 0x3A5C742E 35 | 36 | #include "RCSwitch.h" 37 | 38 | #ifdef RaspberryPi 39 | // PROGMEM and _P functions are for AVR based microprocessors, 40 | // so we must normalize these for the ARM processor: 41 | #define PROGMEM 42 | #define memcpy_P(dest, src, num) memcpy((dest), (src), (num)) 43 | #endif 44 | 45 | #if defined(ESP8266) 46 | // interrupt handler and related code must be in RAM on ESP8266, 47 | // according to issue #46. 48 | #define RECEIVE_ATTR ICACHE_RAM_ATTR 49 | #define VAR_ISR_ATTR 50 | #elif defined(ESP32) 51 | #define RECEIVE_ATTR IRAM_ATTR 52 | #define VAR_ISR_ATTR DRAM_ATTR 53 | #else 54 | #define RECEIVE_ATTR 55 | #define VAR_ISR_ATTR 56 | #endif 57 | 58 | 59 | /* Protocol description format 60 | * 61 | * { 62 | * Pulse length, 63 | * 64 | * PreambleFactor, 65 | * Preamble {high,low}, 66 | * 67 | * HeaderFactor, 68 | * Header {high,low}, 69 | * 70 | * "0" bit {high,low}, 71 | * "1" bit {high,low}, 72 | * 73 | * Inverted Signal, 74 | * Guard time 75 | * } 76 | * 77 | * Pulse length: pulse duration (Te) in microseconds, 78 | * for example 350 79 | * PreambleFactor: Number of high and low states to send 80 | * (One pulse = 2 states, in orther words, number of pulses is 81 | * ceil(PreambleFactor/2).) 82 | * Preamble: Pulse shape which defines a preamble bit. 83 | * Sent ceil(PreambleFactor/2) times. 84 | * For example, {1, 2} with factor 3 would send 85 | * _ _ 86 | * | |__| |__ (each horizontal bar has a duration of Te, 87 | * vertical bars are ignored) 88 | * HeaderFactor: Number of times to send the header pulse. 89 | * Header: Pulse shape which defines a header (or "sync"/"clock") pulse. 90 | * {1, 31} means one pulse of duration 1 Te high and 31 Te low 91 | * _ 92 | * | |_______________________________ (don't count the vertical bars) 93 | * 94 | * "0" bit: pulse shape defining a data bit, which is a logical "0" 95 | * {1, 3} means 1 pulse duration Te high level and 3 low 96 | * _ 97 | * | |___ 98 | * 99 | * "1" bit: pulse shape that defines the data bit, which is a logical "1" 100 | * {3, 1} means 3 pulses with a duration of Te high level and 1 low 101 | * ___ 102 | * | |_ 103 | * 104 | * (note: to form the state bit Z (Tri-State bit), two codes are combined) 105 | * 106 | * Inverted Signal: Signal inversion - if true the signal is inverted 107 | * replacing high to low in a transmitted / received packet 108 | * Guard time: Separation time between two retries. It will be followed by the 109 | * next preamble of the next packet. In number of Te. 110 | * e.g. 39 pulses of duration Te low level 111 | */ 112 | 113 | // #if defined(ESP8266) || defined(ESP32) 114 | // static const VAR_ISR_ATTR RCSwitch::Protocol proto[] = { 115 | // #else 116 | static const RCSwitch::Protocol PROGMEM proto[] = { 117 | // #endif 118 | { 350, 0, { 0, 0 }, 1, { 1, 31 }, { 1, 3 }, { 3, 1 }, false, 0 }, // 01 (Princeton, PT-2240) 119 | { 650, 0, { 0, 0 }, 1, { 1, 10 }, { 1, 2 }, { 2, 1 }, false, 0 }, // 02 120 | { 100, 0, { 0, 0 }, 1, { 30, 71 }, { 4, 11 }, { 9, 6 }, false, 0 }, // 03 121 | { 380, 0, { 0, 0 }, 1, { 1, 6 }, { 1, 3 }, { 3, 1 }, false, 0 }, // 04 122 | { 500, 0, { 0, 0 }, 1, { 6, 14 }, { 1, 2 }, { 2, 1 }, false, 0 }, // 05 123 | { 450, 0, { 0, 0 }, 1, { 23, 1 }, { 1, 2 }, { 2, 1 }, true, 0 }, // 06 (HT6P20B) 124 | { 150, 0, { 0, 0 }, 1, { 2, 62 }, { 1, 6 }, { 6, 1 }, false, 0 }, // 07 (HS2303-PT, i. e. used in AUKEY Remote) 125 | { 320, 0, { 0, 0 }, 1, { 36, 1 }, { 1, 2 }, { 2, 1 }, true, 0 }, // 08 (Came 12bit, HT12E) 126 | { 700, 0, { 0, 0 }, 1, { 32, 1 }, { 1, 2 }, { 2, 1 }, true, 0 }, // 09 (Nice_Flo 12bit) 127 | { 420, 0, { 0, 0 }, 1, { 60, 6 }, { 1, 2 }, { 2, 1 }, true, 0 }, // 10 (V2 phoenix) 128 | { 500, 2, { 3, 3 }, 0, { 0, 0 }, { 1, 2 }, { 2, 1 }, false, 37 }, // 11 (Nice_FloR-S 52bit) 129 | { 400, 23, { 1, 1 }, 1, { 0, 9 }, { 2, 1 }, { 1, 2 }, false, 39 }, // 12 (Keeloq 64/66) 130 | { 300, 6, { 2, 2 }, 3, { 8, 3 }, { 2, 2 }, { 3, 3 }, false, 0 }, // 13 test (CFM) 131 | { 250, 12, { 4, 4 }, 0, { 0, 0 }, { 1, 1 }, { 2, 2 }, false, 0 }, // 14 test (StarLine) 132 | { 500, 0, { 0, 0 }, 0, { 100, 1 }, { 1, 2 }, { 2, 1 }, false, 35 }, // 15 133 | 134 | { 361, 0, { 0, 0 }, 1, { 52, 1 }, { 1, 3 }, { 3, 1 }, true, 0 }, // 16 (Einhell) 135 | { 500, 0, { 0, 0 }, 1, { 1, 23 }, { 1, 2 }, { 2, 1 }, false, 0 }, // 17 (InterTechno PAR-1000) 136 | { 180, 0, { 0, 0 }, 1, { 1, 15 }, { 1, 1 }, { 1, 8 }, false, 0 }, // 18 (Intertechno ITT-1500) 137 | { 350, 0, { 0, 0 }, 1, { 1, 2 }, { 0, 2 }, { 3, 2 }, false, 0 }, // 19 (Murcury) 138 | { 150, 0, { 0, 0 }, 1, { 34, 3 }, { 1, 3 }, { 3, 1 }, false, 0 }, // 20 (AC114) 139 | { 360, 0, { 0, 0 }, 1, { 13, 4 }, { 1, 2 }, { 2, 1 }, false, 0 }, // 21 (DC250) 140 | { 650, 0, { 0, 0 }, 1, { 1, 10 }, { 1, 2 }, { 2, 1 }, true, 0 }, // 22 (Mandolyn/Lidl TR-502MSV/RC-402/RC-402DX) 141 | { 641, 0, { 0, 0 }, 1, { 115, 1 }, { 1, 2 }, { 2, 1 }, true, 0 }, // 23 (Lidl TR-502MSV/RC-402 - Flavien) 142 | { 620, 0, { 0, 0 }, 1, { 0, 64 }, { 0, 1 }, { 1, 0 }, false, 0 }, // 24 (Lidl TR-502MSV/RC701) 143 | { 560, 0, { 0, 0 }, 1, { 16, 8 }, { 1, 1 }, { 1, 3 }, false, 0 }, // 25 (NEC) 144 | { 385, 0, { 0, 0 }, 1, { 1, 17 }, { 1, 2 }, { 2, 1 }, false, 0 }, // 26 (Arlec RC210) 145 | { 188, 0, { 0, 0 }, 1, { 1, 31 }, { 1, 3 }, { 3, 1 }, false, 0 }, // 27 (Zap, FHT-7901) 146 | 147 | { 700, 1, { 0, 1 }, 1, { 116, 0 }, { 1, 2 }, { 2, 1 }, true, 0 }, // 28 (Quigg GT-7000) from @Tho85 https://github.com/sui77/rc-switch/pull/115 148 | { 220, 0, { 0, 0 }, 1, { 1, 46 }, { 1, 6 }, { 1, 1 }, false, 2 }, // 29 (NEXA) 149 | { 260, 0, { 0, 0 }, 1, { 1, 8 }, { 1, 4 }, { 4, 1 }, true, 0 }, // 30 (Anima) 150 | 151 | { 400, 0, { 0, 0 }, 1, { 1, 1 }, { 1, 2 }, { 2, 1 }, false, 43 }, // 31 (Mertik Maxitrol G6R-H4T1) 152 | { 365, 0, { 0, 0 }, 1, { 18, 1 }, { 3, 1 }, { 1, 3 }, true, 0 }, // 32 (1ByOne Doorbell) from @Fatbeard https://github.com/sui77/rc-switch/pull/277 153 | { 340, 0, { 0, 0 }, 1, { 14, 4 }, { 1, 2 }, { 2, 1 }, false, 0 }, // 33 (Dooya Control DC2708L) 154 | { 120, 0, { 0, 0 }, 1, { 1, 28 }, { 1, 3 }, { 3, 1 }, false, 0 }, // 34 DIGOO SD10 - so as to use this protocol RCSWITCH_SEPARATION_LIMIT must be set to 2600 155 | { 20, 0, { 0, 0 }, 1, { 239, 78 }, {20, 35 }, {35, 20}, false, 10000},// 35 Dooya 5-Channel blinds remote DC1603 156 | { 250, 0, { 0, 0 }, 1, { 18, 6 }, { 1, 3 }, { 3, 1 }, false, 0 }, // 36 Dooya remote DC2700AC for Dooya DT82TV curtains motor 157 | { 200, 0, { 0, 0 }, 0, { 0, 0 }, { 1, 3 }, { 3, 1} , false, 20}, // 37 DEWENWILS Power Strip 158 | 159 | { 500, 0, { 0, 0 }, 1, { 0, 7 }, { 1, 2 }, { 1, 4}, false, 0 }, // 38 (Nexus weather, 36 bit) 160 | //{ 20, 16, { 14, 30 }, 1, { 510, 30 }, { 14, 30 }, { 30, 14 }, false, 230 } // 39 (Louvolite) with premable 161 | }; 162 | 163 | enum { 164 | numProto = sizeof(proto) / sizeof(proto[0]) 165 | }; 166 | 167 | #if not defined( RCSwitchDisableReceiving ) 168 | volatile unsigned long long RCSwitch::nReceivedValue = 0; 169 | volatile unsigned long long RCSwitch::nReceiveProtocolMask; 170 | volatile unsigned int RCSwitch::nReceivedBitlength = 0; 171 | volatile unsigned int RCSwitch::nReceivedDelay = 0; 172 | volatile unsigned int RCSwitch::nReceivedProtocol = 0; 173 | int RCSwitch::nReceiveTolerance = 60; 174 | const unsigned int RCSwitch::nSeparationLimit = RCSWITCH_SEPARATION_LIMIT; 175 | unsigned int RCSwitch::timings[RCSWITCH_MAX_CHANGES]; 176 | unsigned int RCSwitch::buftimings[4]; 177 | #endif 178 | 179 | RCSwitch::RCSwitch() { 180 | this->nTransmitterPin = -1; 181 | this->setRepeatTransmit(5); 182 | this->setProtocol(1); 183 | #if not defined( RCSwitchDisableReceiving ) 184 | this->nReceiverInterrupt = -1; 185 | this->setReceiveTolerance(60); 186 | RCSwitch::nReceivedValue = 0; 187 | RCSwitch::nReceiveProtocolMask = (1ULL << numProto)-1; //pow(2,numProto)-1; 188 | #endif 189 | } 190 | 191 | uint8_t RCSwitch::getNumProtos() { 192 | return numProto; 193 | } 194 | /** 195 | * Sets the protocol to send. 196 | */ 197 | void RCSwitch::setProtocol(Protocol protocol) { 198 | this->protocol = protocol; 199 | } 200 | 201 | /** 202 | * Sets the protocol to send, from a list of predefined protocols 203 | */ 204 | void RCSwitch::setProtocol(int nProtocol) { 205 | if (nProtocol < 1 || nProtocol > numProto) { 206 | nProtocol = 1; // TODO: trigger an error, e.g. "bad protocol" ??? 207 | } 208 | #if defined(ESP8266) || defined(ESP32) 209 | this->protocol = proto[nProtocol-1]; 210 | #else 211 | memcpy_P(&this->protocol, &proto[nProtocol-1], sizeof(Protocol)); 212 | #endif 213 | } 214 | 215 | /** 216 | * Sets the protocol to send with pulse length in microseconds. 217 | */ 218 | void RCSwitch::setProtocol(int nProtocol, int nPulseLength) { 219 | setProtocol(nProtocol); 220 | this->setPulseLength(nPulseLength); 221 | } 222 | 223 | 224 | /** 225 | * Sets pulse length in microseconds 226 | */ 227 | void RCSwitch::setPulseLength(int nPulseLength) { 228 | this->protocol.pulseLength = nPulseLength; 229 | } 230 | 231 | /** 232 | * Sets Repeat Transmits 233 | */ 234 | void RCSwitch::setRepeatTransmit(int nRepeatTransmit) { 235 | this->nRepeatTransmit = nRepeatTransmit; 236 | } 237 | 238 | /** 239 | * Set Receiving Tolerance 240 | */ 241 | #if not defined( RCSwitchDisableReceiving ) 242 | void RCSwitch::setReceiveTolerance(int nPercent) { 243 | RCSwitch::nReceiveTolerance = nPercent; 244 | } 245 | 246 | void RCSwitch::setReceiveProtocolMask(unsigned long long mask) { 247 | RCSwitch::nReceiveProtocolMask = mask; 248 | } 249 | #endif 250 | 251 | 252 | /** 253 | * Enable transmissions 254 | * 255 | * @param nTransmitterPin Arduino Pin to which the sender is connected to 256 | */ 257 | void RCSwitch::enableTransmit(int nTransmitterPin) { 258 | this->nTransmitterPin = nTransmitterPin; 259 | pinMode(this->nTransmitterPin, OUTPUT); 260 | } 261 | 262 | /** 263 | * Disable transmissions 264 | */ 265 | void RCSwitch::disableTransmit() { 266 | this->nTransmitterPin = -1; 267 | } 268 | 269 | /** 270 | * Switch a remote switch on (Type D REV) 271 | * 272 | * @param sGroup Code of the switch group (A,B,C,D) 273 | * @param nDevice Number of the switch itself (1..3) 274 | */ 275 | void RCSwitch::switchOn(char sGroup, int nDevice) { 276 | this->sendTriState( this->getCodeWordD(sGroup, nDevice, true) ); 277 | } 278 | 279 | /** 280 | * Switch a remote switch off (Type D REV) 281 | * 282 | * @param sGroup Code of the switch group (A,B,C,D) 283 | * @param nDevice Number of the switch itself (1..3) 284 | */ 285 | void RCSwitch::switchOff(char sGroup, int nDevice) { 286 | this->sendTriState( this->getCodeWordD(sGroup, nDevice, false) ); 287 | } 288 | 289 | /** 290 | * Switch a remote switch on (Type C Intertechno) 291 | * 292 | * @param sFamily Familycode (a..f) 293 | * @param nGroup Number of group (1..4) 294 | * @param nDevice Number of device (1..4) 295 | */ 296 | void RCSwitch::switchOn(char sFamily, int nGroup, int nDevice) { 297 | this->sendTriState( this->getCodeWordC(sFamily, nGroup, nDevice, true) ); 298 | } 299 | 300 | /** 301 | * Switch a remote switch off (Type C Intertechno) 302 | * 303 | * @param sFamily Familycode (a..f) 304 | * @param nGroup Number of group (1..4) 305 | * @param nDevice Number of device (1..4) 306 | */ 307 | void RCSwitch::switchOff(char sFamily, int nGroup, int nDevice) { 308 | this->sendTriState( this->getCodeWordC(sFamily, nGroup, nDevice, false) ); 309 | } 310 | 311 | /** 312 | * Switch a remote switch on (Type B with two rotary/sliding switches) 313 | * 314 | * @param nAddressCode Number of the switch group (1..4) 315 | * @param nChannelCode Number of the switch itself (1..4) 316 | */ 317 | void RCSwitch::switchOn(int nAddressCode, int nChannelCode) { 318 | this->sendTriState( this->getCodeWordB(nAddressCode, nChannelCode, true) ); 319 | } 320 | 321 | /** 322 | * Switch a remote switch off (Type B with two rotary/sliding switches) 323 | * 324 | * @param nAddressCode Number of the switch group (1..4) 325 | * @param nChannelCode Number of the switch itself (1..4) 326 | */ 327 | void RCSwitch::switchOff(int nAddressCode, int nChannelCode) { 328 | this->sendTriState( this->getCodeWordB(nAddressCode, nChannelCode, false) ); 329 | } 330 | 331 | /** 332 | * Deprecated, use switchOn(const char* sGroup, const char* sDevice) instead! 333 | * Switch a remote switch on (Type A with 10 pole DIP switches) 334 | * 335 | * @param sGroup Code of the switch group (refers to DIP switches 1..5 where "1" = on and "0" = off, if all DIP switches are on it's "11111") 336 | * @param nChannelCode Number of the switch itself (1..5) 337 | */ 338 | void RCSwitch::switchOn(const char* sGroup, int nChannel) { 339 | const char* code[6] = { "00000", "10000", "01000", "00100", "00010", "00001" }; 340 | this->switchOn(sGroup, code[nChannel]); 341 | } 342 | 343 | /** 344 | * Deprecated, use switchOff(const char* sGroup, const char* sDevice) instead! 345 | * Switch a remote switch off (Type A with 10 pole DIP switches) 346 | * 347 | * @param sGroup Code of the switch group (refers to DIP switches 1..5 where "1" = on and "0" = off, if all DIP switches are on it's "11111") 348 | * @param nChannelCode Number of the switch itself (1..5) 349 | */ 350 | void RCSwitch::switchOff(const char* sGroup, int nChannel) { 351 | const char* code[6] = { "00000", "10000", "01000", "00100", "00010", "00001" }; 352 | this->switchOff(sGroup, code[nChannel]); 353 | } 354 | 355 | /** 356 | * Switch a remote switch on (Type A with 10 pole DIP switches) 357 | * 358 | * @param sGroup Code of the switch group (refers to DIP switches 1..5 where "1" = on and "0" = off, if all DIP switches are on it's "11111") 359 | * @param sDevice Code of the switch device (refers to DIP switches 6..10 (A..E) where "1" = on and "0" = off, if all DIP switches are on it's "11111") 360 | */ 361 | void RCSwitch::switchOn(const char* sGroup, const char* sDevice) { 362 | this->sendTriState( this->getCodeWordA(sGroup, sDevice, true) ); 363 | } 364 | 365 | /** 366 | * Switch a remote switch off (Type A with 10 pole DIP switches) 367 | * 368 | * @param sGroup Code of the switch group (refers to DIP switches 1..5 where "1" = on and "0" = off, if all DIP switches are on it's "11111") 369 | * @param sDevice Code of the switch device (refers to DIP switches 6..10 (A..E) where "1" = on and "0" = off, if all DIP switches are on it's "11111") 370 | */ 371 | void RCSwitch::switchOff(const char* sGroup, const char* sDevice) { 372 | this->sendTriState( this->getCodeWordA(sGroup, sDevice, false) ); 373 | } 374 | 375 | 376 | /** 377 | * Returns a char[13], representing the code word to be send. 378 | * 379 | */ 380 | char* RCSwitch::getCodeWordA(const char* sGroup, const char* sDevice, bool bStatus) { 381 | static char sReturn[13]; 382 | int nReturnPos = 0; 383 | 384 | for (int i = 0; i < 5; i++) { 385 | sReturn[nReturnPos++] = (sGroup[i] == '0') ? 'F' : '0'; 386 | } 387 | 388 | for (int i = 0; i < 5; i++) { 389 | sReturn[nReturnPos++] = (sDevice[i] == '0') ? 'F' : '0'; 390 | } 391 | 392 | sReturn[nReturnPos++] = bStatus ? '0' : 'F'; 393 | sReturn[nReturnPos++] = bStatus ? 'F' : '0'; 394 | 395 | sReturn[nReturnPos] = '\0'; 396 | return sReturn; 397 | } 398 | 399 | /** 400 | * Encoding for type B switches with two rotary/sliding switches. 401 | * 402 | * The code word is a tristate word and with following bit pattern: 403 | * 404 | * +-----------------------------+-----------------------------+----------+------------+ 405 | * | 4 bits address | 4 bits address | 3 bits | 1 bit | 406 | * | switch group | switch number | not used | on / off | 407 | * | 1=0FFF 2=F0FF 3=FF0F 4=FFF0 | 1=0FFF 2=F0FF 3=FF0F 4=FFF0 | FFF | on=F off=0 | 408 | * +-----------------------------+-----------------------------+----------+------------+ 409 | * 410 | * @param nAddressCode Number of the switch group (1..4) 411 | * @param nChannelCode Number of the switch itself (1..4) 412 | * @param bStatus Whether to switch on (true) or off (false) 413 | * 414 | * @return char[13], representing a tristate code word of length 12 415 | */ 416 | char* RCSwitch::getCodeWordB(int nAddressCode, int nChannelCode, bool bStatus) { 417 | static char sReturn[13]; 418 | int nReturnPos = 0; 419 | 420 | if (nAddressCode < 1 || nAddressCode > 4 || nChannelCode < 1 || nChannelCode > 4) { 421 | return 0; 422 | } 423 | 424 | for (int i = 1; i <= 4; i++) { 425 | sReturn[nReturnPos++] = (nAddressCode == i) ? '0' : 'F'; 426 | } 427 | 428 | for (int i = 1; i <= 4; i++) { 429 | sReturn[nReturnPos++] = (nChannelCode == i) ? '0' : 'F'; 430 | } 431 | 432 | sReturn[nReturnPos++] = 'F'; 433 | sReturn[nReturnPos++] = 'F'; 434 | sReturn[nReturnPos++] = 'F'; 435 | 436 | sReturn[nReturnPos++] = bStatus ? 'F' : '0'; 437 | 438 | sReturn[nReturnPos] = '\0'; 439 | return sReturn; 440 | } 441 | 442 | /** 443 | * Like getCodeWord (Type C = Intertechno) 444 | */ 445 | char* RCSwitch::getCodeWordC(char sFamily, int nGroup, int nDevice, bool bStatus) { 446 | static char sReturn[13]; 447 | int nReturnPos = 0; 448 | 449 | int nFamily = (int)sFamily - 'a'; 450 | if ( nFamily < 0 || nFamily > 15 || nGroup < 1 || nGroup > 4 || nDevice < 1 || nDevice > 4) { 451 | return 0; 452 | } 453 | 454 | // encode the family into four bits 455 | sReturn[nReturnPos++] = (nFamily & 1) ? 'F' : '0'; 456 | sReturn[nReturnPos++] = (nFamily & 2) ? 'F' : '0'; 457 | sReturn[nReturnPos++] = (nFamily & 4) ? 'F' : '0'; 458 | sReturn[nReturnPos++] = (nFamily & 8) ? 'F' : '0'; 459 | 460 | // encode the device and group 461 | sReturn[nReturnPos++] = ((nDevice-1) & 1) ? 'F' : '0'; 462 | sReturn[nReturnPos++] = ((nDevice-1) & 2) ? 'F' : '0'; 463 | sReturn[nReturnPos++] = ((nGroup-1) & 1) ? 'F' : '0'; 464 | sReturn[nReturnPos++] = ((nGroup-1) & 2) ? 'F' : '0'; 465 | 466 | // encode the status code 467 | sReturn[nReturnPos++] = '0'; 468 | sReturn[nReturnPos++] = 'F'; 469 | sReturn[nReturnPos++] = 'F'; 470 | sReturn[nReturnPos++] = bStatus ? 'F' : '0'; 471 | 472 | sReturn[nReturnPos] = '\0'; 473 | return sReturn; 474 | } 475 | 476 | /** 477 | * Encoding for the REV Switch Type 478 | * 479 | * The code word is a tristate word and with following bit pattern: 480 | * 481 | * +-----------------------------+-------------------+----------+--------------+ 482 | * | 4 bits address | 3 bits address | 3 bits | 2 bits | 483 | * | switch group | device number | not used | on / off | 484 | * | A=1FFF B=F1FF C=FF1F D=FFF1 | 1=0FF 2=F0F 3=FF0 | 000 | on=10 off=01 | 485 | * +-----------------------------+-------------------+----------+--------------+ 486 | * 487 | * Source: http://www.the-intruder.net/funksteckdosen-von-rev-uber-arduino-ansteuern/ 488 | * 489 | * @param sGroup Name of the switch group (A..D, resp. a..d) 490 | * @param nDevice Number of the switch itself (1..3) 491 | * @param bStatus Whether to switch on (true) or off (false) 492 | * 493 | * @return char[13], representing a tristate code word of length 12 494 | */ 495 | char* RCSwitch::getCodeWordD(char sGroup, int nDevice, bool bStatus) { 496 | static char sReturn[13]; 497 | int nReturnPos = 0; 498 | 499 | // sGroup must be one of the letters in "abcdABCD" 500 | int nGroup = (sGroup >= 'a') ? (int)sGroup - 'a' : (int)sGroup - 'A'; 501 | if ( nGroup < 0 || nGroup > 3 || nDevice < 1 || nDevice > 3) { 502 | return 0; 503 | } 504 | 505 | for (int i = 0; i < 4; i++) { 506 | sReturn[nReturnPos++] = (nGroup == i) ? '1' : 'F'; 507 | } 508 | 509 | for (int i = 1; i <= 3; i++) { 510 | sReturn[nReturnPos++] = (nDevice == i) ? '1' : 'F'; 511 | } 512 | 513 | sReturn[nReturnPos++] = '0'; 514 | sReturn[nReturnPos++] = '0'; 515 | sReturn[nReturnPos++] = '0'; 516 | 517 | sReturn[nReturnPos++] = bStatus ? '1' : '0'; 518 | sReturn[nReturnPos++] = bStatus ? '0' : '1'; 519 | 520 | sReturn[nReturnPos] = '\0'; 521 | return sReturn; 522 | } 523 | 524 | /** 525 | * @param sCodeWord a tristate code word consisting of the letter 0, 1, F 526 | */ 527 | void RCSwitch::sendTriState(const char* sCodeWord) { 528 | // turn the tristate code word into the corresponding bit pattern, then send it 529 | unsigned long long code = 0; 530 | unsigned int length = 0; 531 | for (const char* p = sCodeWord; *p; p++) { 532 | code <<= 2L; 533 | switch (*p) { 534 | case '0': 535 | // bit pattern 00 536 | break; 537 | case 'F': 538 | // bit pattern 01 539 | code |= 1ULL; 540 | break; 541 | case '1': 542 | // bit pattern 11 543 | code |= 3ULL; 544 | break; 545 | } 546 | length += 2; 547 | } 548 | this->send(code, length); 549 | } 550 | 551 | /** 552 | * @param duration no. of microseconds to delay 553 | */ 554 | static inline void safeDelayMicroseconds(unsigned long duration) { 555 | #if defined(ESP8266) || defined(ESP32) 556 | if (duration > 10000) { 557 | // if delay > 10 milliseconds, use yield() to avoid wdt reset 558 | unsigned long start = micros(); 559 | while ((micros() - start) < duration) { 560 | yield(); 561 | } 562 | } 563 | else { 564 | delayMicroseconds(duration); 565 | } 566 | #else 567 | delayMicroseconds(duration); 568 | #endif 569 | } 570 | 571 | /** 572 | * @param sCodeWord a binary code word consisting of the letter 0, 1 573 | */ 574 | void RCSwitch::send(const char* sCodeWord) { 575 | // turn the tristate code word into the corresponding bit pattern, then send it 576 | unsigned long long code = 0; 577 | unsigned int length = 0; 578 | for (const char* p = sCodeWord; *p; p++) { 579 | code <<= 1ULL; 580 | if (*p != '0') 581 | code |= 1ULL; 582 | length++; 583 | } 584 | this->send(code, length); 585 | } 586 | 587 | /** 588 | * Transmit the first 'length' bits of the integer 'code'. The 589 | * bits are sent from MSB to LSB, i.e., first the bit at position length-1, 590 | * then the bit at position length-2, and so on, till finally the bit at position 0. 591 | */ 592 | void RCSwitch::send(uint64_t code, unsigned int length) { 593 | if (this->nTransmitterPin == -1) 594 | return; 595 | 596 | #if not defined( RCSwitchDisableReceiving ) 597 | // make sure the receiver is disabled while we transmit 598 | int nReceiverInterrupt_backup = nReceiverInterrupt; 599 | if (nReceiverInterrupt_backup != -1) { 600 | this->disableReceive(); 601 | } 602 | #endif 603 | 604 | // repeat sending the packet nRepeatTransmit times 605 | for (int nRepeat = 0; nRepeat < nRepeatTransmit; nRepeat++) { 606 | // send the preamble 607 | for (int i = 0; i < ((protocol.PreambleFactor / 2) + (protocol.PreambleFactor %2 )); i++) { 608 | this->transmit({protocol.Preamble.high, protocol.Preamble.low}); 609 | } 610 | // send the header 611 | if (protocol.HeaderFactor > 0) { 612 | for (int i = 0; i < protocol.HeaderFactor; i++) { 613 | this->transmit(protocol.Header); 614 | } 615 | } 616 | // send the code 617 | for (int i = length - 1; i >= 0; i--) { 618 | if (code & (1ULL << i)) 619 | this->transmit(protocol.one); 620 | else 621 | this->transmit(protocol.zero); 622 | } 623 | // for kilok, there should be a duration of 66, and 64 significant data codes are stored 624 | // send two more bits for even count 625 | if (length == 64) { 626 | if (nRepeat == 0) { 627 | this->transmit(protocol.zero); 628 | this->transmit(protocol.zero); 629 | } else { 630 | this->transmit(protocol.one); 631 | this->transmit(protocol.one); 632 | } 633 | } 634 | // Set the guard Time 635 | if (protocol.Guard > 0) { 636 | digitalWrite(this->nTransmitterPin, LOW); 637 | safeDelayMicroseconds(this->protocol.pulseLength * protocol.Guard); 638 | } 639 | } 640 | 641 | // Disable transmit after sending (i.e., for inverted protocols) 642 | digitalWrite(this->nTransmitterPin, LOW); 643 | 644 | #if not defined( RCSwitchDisableReceiving ) 645 | // enable receiver again if we just disabled it 646 | if (nReceiverInterrupt_backup != -1) { 647 | this->enableReceive(nReceiverInterrupt_backup); 648 | } 649 | #endif 650 | } 651 | 652 | /** 653 | * Transmit a single high-low pulse. 654 | */ 655 | void RCSwitch::transmit(HighLow pulses) { 656 | uint8_t firstLogicLevel = (this->protocol.invertedSignal) ? LOW : HIGH; 657 | uint8_t secondLogicLevel = (this->protocol.invertedSignal) ? HIGH : LOW; 658 | 659 | if (pulses.high > 0) { 660 | digitalWrite(this->nTransmitterPin, firstLogicLevel); 661 | delayMicroseconds( this->protocol.pulseLength * pulses.high); 662 | } 663 | if (pulses.low > 0) { 664 | digitalWrite(this->nTransmitterPin, secondLogicLevel); 665 | delayMicroseconds( this->protocol.pulseLength * pulses.low); 666 | } 667 | } 668 | 669 | 670 | #if not defined( RCSwitchDisableReceiving ) 671 | /** 672 | * Enable receiving data 673 | */ 674 | void RCSwitch::enableReceive(int interrupt) { 675 | this->nReceiverInterrupt = interrupt; 676 | this->enableReceive(); 677 | } 678 | 679 | void RCSwitch::enableReceive() { 680 | if (this->nReceiverInterrupt != -1) { 681 | RCSwitch::nReceivedValue = 0; 682 | RCSwitch::nReceivedBitlength = 0; 683 | #if defined(RaspberryPi) // Raspberry Pi 684 | wiringPiISR(this->nReceiverInterrupt, INT_EDGE_BOTH, &handleInterrupt); 685 | #else // Arduino 686 | attachInterrupt(this->nReceiverInterrupt, handleInterrupt, CHANGE); 687 | #endif 688 | } 689 | } 690 | 691 | /** 692 | * Disable receiving data 693 | */ 694 | void RCSwitch::disableReceive() { 695 | #if not defined(RaspberryPi) // Arduino 696 | detachInterrupt(this->nReceiverInterrupt); 697 | #endif // For Raspberry Pi (wiringPi) you can't unregister the ISR 698 | this->nReceiverInterrupt = -1; 699 | } 700 | 701 | bool RCSwitch::available() { 702 | return RCSwitch::nReceivedValue != 0; 703 | } 704 | 705 | void RCSwitch::resetAvailable() { 706 | RCSwitch::nReceivedValue = 0; 707 | } 708 | 709 | unsigned long long RCSwitch::getReceivedValue() { 710 | return RCSwitch::nReceivedValue; 711 | } 712 | 713 | unsigned int RCSwitch::getReceivedBitlength() { 714 | return RCSwitch::nReceivedBitlength; 715 | } 716 | 717 | unsigned int RCSwitch::getReceivedDelay() { 718 | return RCSwitch::nReceivedDelay; 719 | } 720 | 721 | unsigned int RCSwitch::getReceivedProtocol() { 722 | return RCSwitch::nReceivedProtocol; 723 | } 724 | 725 | unsigned int* RCSwitch::getReceivedRawdata() { 726 | return RCSwitch::timings; 727 | } 728 | 729 | /* helper function for the receiveProtocol method */ 730 | static inline unsigned int diff(int A, int B) { 731 | return abs(A - B); 732 | } 733 | 734 | /** 735 | * 736 | */ 737 | bool RCSwitch::receiveProtocol(const int p, unsigned int changeCount) { 738 | #if defined(ESP8266) || defined(ESP32) 739 | const Protocol &pro = proto[p-1]; 740 | #else 741 | Protocol pro; 742 | memcpy_P(&pro, &proto[p-1], sizeof(Protocol)); 743 | #endif 744 | 745 | unsigned long long code = 0; 746 | unsigned int FirstTiming = 0; 747 | if (pro.PreambleFactor > 0) { 748 | FirstTiming = pro.PreambleFactor + 1; 749 | } 750 | unsigned int BeginData = 0; 751 | if (pro.HeaderFactor > 0) { 752 | BeginData = (pro.invertedSignal) ? (2) : (1); 753 | // Header pulse count correction for more than one 754 | if (pro.HeaderFactor > 1) { 755 | BeginData += (pro.HeaderFactor - 1) * 2; 756 | } 757 | } 758 | //Assuming the longer pulse length is the pulse captured in timings[FirstTiming] 759 | // берем наибольшее значение из Header 760 | const unsigned int syncLengthInPulses = ((pro.Header.low) > (pro.Header.high)) ? (pro.Header.low) : (pro.Header.high); 761 | // определяем длительность Te как длительность первого импульса header деленную на количество импульсов в нем 762 | // или как длительность импульса preamble деленную на количество Te в нем 763 | unsigned int sdelay = 0; 764 | if (syncLengthInPulses > 0) { 765 | sdelay = RCSwitch::timings[FirstTiming] / syncLengthInPulses; 766 | } else if (pro.PreambleFactor > 0) { 767 | sdelay = RCSwitch::timings[FirstTiming-2] / pro.PreambleFactor; 768 | } 769 | const unsigned int delay = sdelay; 770 | // nReceiveTolerance = 60 771 | // допустимое отклонение длительностей импульсов на 60 % 772 | const unsigned int delayTolerance = delay * RCSwitch::nReceiveTolerance / 100; 773 | 774 | // 0 - sync перед preamble или data 775 | // BeginData - сдвиг на 1 или 2 от sync к preamble/data 776 | // FirstTiming - сдвиг на preamble к header 777 | // firstDataTiming первый импульс data 778 | // bitChangeCount - количество импульсов в data 779 | 780 | /* For protocols that start low, the sync period looks like 781 | * _________ 782 | * _____________| |XXXXXXXXXXXX| 783 | * 784 | * |--1st dur--|-2nd dur-|-Start data-| 785 | * 786 | * The 3rd saved duration starts the data. 787 | * 788 | * For protocols that start high, the sync period looks like 789 | * 790 | * ______________ 791 | * | |____________|XXXXXXXXXXXXX| 792 | * 793 | * |-filtered out-|--1st dur--|--Start data--| 794 | * 795 | * The 2nd saved duration starts the data 796 | */ 797 | // если invertedSignal=false, то сигнал начинается с 1 элемента массива (высокий уровень) 798 | // если invertedSignal=true, то сигнал начинается со 2 элемента массива (низкий уровень) 799 | // добавляем поправку на Преамбулу и Хедер 800 | const unsigned int firstDataTiming = BeginData + FirstTiming; 801 | unsigned int bitChangeCount = changeCount - firstDataTiming - 1 + pro.invertedSignal; 802 | if (bitChangeCount > 128) { 803 | bitChangeCount = 128; 804 | } 805 | 806 | for (unsigned int i = firstDataTiming; i < firstDataTiming + bitChangeCount; i += 2) { 807 | code <<= 1; 808 | if (diff(RCSwitch::timings[i], delay * pro.zero.high) < delayTolerance && 809 | diff(RCSwitch::timings[i + 1], delay * pro.zero.low) < delayTolerance) { 810 | // zero 811 | } else if (diff(RCSwitch::timings[i], delay * pro.one.high) < delayTolerance && 812 | diff(RCSwitch::timings[i + 1], delay * pro.one.low) < delayTolerance) { 813 | // one 814 | code |= 1; 815 | } else { 816 | // Failed 817 | return false; 818 | } 819 | } 820 | 821 | if (bitChangeCount > 14) { // ignore very short transmissions: no device sends them, so this must be noise 822 | RCSwitch::nReceivedValue = code; 823 | RCSwitch::nReceivedBitlength = bitChangeCount / 2; 824 | RCSwitch::nReceivedDelay = delay; 825 | RCSwitch::nReceivedProtocol = p; 826 | return true; 827 | } 828 | 829 | return false; 830 | } 831 | 832 | void RECEIVE_ATTR RCSwitch::handleInterrupt() { 833 | 834 | static unsigned int changeCount = 0; 835 | static unsigned long lastTime = 0; 836 | static byte repeatCount = 0; 837 | 838 | const long time = micros(); 839 | const unsigned int duration = time - lastTime; 840 | 841 | RCSwitch::buftimings[3]=RCSwitch::buftimings[2]; 842 | RCSwitch::buftimings[2]=RCSwitch::buftimings[1]; 843 | RCSwitch::buftimings[1]=RCSwitch::buftimings[0]; 844 | RCSwitch::buftimings[0]=duration; 845 | 846 | if (duration > RCSwitch::nSeparationLimit || 847 | changeCount == 156 || 848 | (diff(RCSwitch::buftimings[3], RCSwitch::buftimings[2]) < 50 && 849 | diff(RCSwitch::buftimings[2], RCSwitch::buftimings[1]) < 50 && 850 | changeCount > 25)) { 851 | // принят длинный импульс продолжительностью более nSeparationLimit (4300) 852 | // A long stretch without signal level change occurred. This could 853 | // be the gap between two transmission. 854 | if (diff(duration, RCSwitch::timings[0]) < 400 || 855 | changeCount == 156 || 856 | (diff(RCSwitch::buftimings[3], RCSwitch::timings[1]) < 50 && 857 | diff(RCSwitch::buftimings[2], RCSwitch::timings[2]) < 50 && 858 | diff(RCSwitch::buftimings[1], RCSwitch::timings[3]) < 50 && 859 | changeCount > 25)) { 860 | // если его длительность отличается от первого импульса, 861 | // который приняли раньше, менее чем на +-200 (исходно 200) 862 | // то считаем это повторным пакетом и игнорируем его 863 | // This long signal is close in length to the long signal which 864 | // started the previously recorded timings; this suggests that 865 | // it may indeed by a a gap between two transmissions (we assume 866 | // here that a sender will send the signal multiple times, 867 | // with roughly the same gap between them). 868 | 869 | // количество повторных пакетов 870 | repeatCount++; 871 | // при приеме второго повторного начинаем анализ принятого первым 872 | if (repeatCount == 1) { 873 | unsigned long long thismask = 1; 874 | for(unsigned int i = 1; i <= numProto; i++) { 875 | if (RCSwitch::nReceiveProtocolMask & thismask) { 876 | if (receiveProtocol(i, changeCount)) { 877 | // receive succeeded for protocol i 878 | break; 879 | } 880 | } 881 | thismask <<= 1; 882 | } 883 | // очищаем количество повторных пакетов 884 | repeatCount = 0; 885 | } 886 | } 887 | // дительность отличается более чем на +-200 от первого 888 | // принятого ранее, очищаем счетчик для приема нового пакета 889 | changeCount = 0; 890 | if (diff(RCSwitch::buftimings[3], RCSwitch::buftimings[2]) < 50 && 891 | diff(RCSwitch::buftimings[2], RCSwitch::buftimings[1]) < 50) { 892 | RCSwitch::timings[1]=RCSwitch::buftimings[3]; 893 | RCSwitch::timings[2]=RCSwitch::buftimings[2]; 894 | RCSwitch::timings[3]=RCSwitch::buftimings[1]; 895 | changeCount = 4; 896 | } 897 | } 898 | 899 | // detect overflow 900 | if (changeCount >= RCSWITCH_MAX_CHANGES) { 901 | changeCount = 0; 902 | repeatCount = 0; 903 | } 904 | 905 | // заносим в массив длительность очередного принятого импульса 906 | if (changeCount > 0 && duration < 100) { // игнорируем шумовые всплески менее 100 мкс 907 | RCSwitch::timings[changeCount-1] += duration; 908 | } else { 909 | RCSwitch::timings[changeCount++] = duration; 910 | } 911 | lastTime = time; 912 | } 913 | #endif 914 | 915 | /** 916 | * Initilize Keeloq 917 | */ 918 | Keeloq::Keeloq() { 919 | _keyHigh = 0; 920 | _keyLow = 0; 921 | } 922 | 923 | /** 924 | * Set Keeloq 64 bit cypher key 925 | */ 926 | void Keeloq::SetKey(unsigned long keyHigh, unsigned long keyLow) { 927 | _keyHigh = keyHigh; 928 | _keyLow = keyLow; 929 | } 930 | 931 | /** 932 | * Get Key data 933 | */ 934 | unsigned long Keeloq::GetKey(bool HighLow) { 935 | if (HighLow) { 936 | return _keyHigh; 937 | } 938 | return _keyLow; 939 | } 940 | 941 | /** 942 | * Encrypt Keeloq 32 bit data 943 | */ 944 | unsigned long Keeloq::Encrypt(unsigned long data) { 945 | unsigned long x = data; 946 | unsigned long r; 947 | int keyBitNo, index; 948 | unsigned long keyBitVal,bitVal; 949 | 950 | for (r = 0; r < 528; r++) { 951 | keyBitNo = r & 63; 952 | if (keyBitNo < 32) { 953 | keyBitVal = bitRead(_keyLow,keyBitNo); 954 | } else { 955 | keyBitVal = bitRead(_keyHigh, keyBitNo - 32); 956 | } 957 | index = 1 * bitRead(x, 1) + 2 * bitRead(x, 9) + 4 * bitRead(x, 20) + 8 * bitRead(x, 26) + 16 * bitRead(x, 31); 958 | bitVal = bitRead(x, 0) ^ bitRead(x, 16) ^ bitRead(KeeLoq_NLF, index) ^ keyBitVal; 959 | x = (x >> 1) ^ bitVal << 31; 960 | } 961 | return x; 962 | } 963 | 964 | /** 965 | * Decrypt Keeloq 32 bit data 966 | */ 967 | unsigned long Keeloq::Decrypt(unsigned long data) { 968 | unsigned long x = data; 969 | unsigned long r; 970 | int keyBitNo, index; 971 | unsigned long keyBitVal,bitVal; 972 | 973 | for (r = 0; r < 528; r++) { 974 | keyBitNo = (15-r) & 63; 975 | if(keyBitNo < 32) { 976 | keyBitVal = bitRead(_keyLow,keyBitNo); 977 | } else { 978 | keyBitVal = bitRead(_keyHigh, keyBitNo - 32); 979 | } 980 | index = 1 * bitRead(x, 0) + 2 * bitRead(x, 8) + 4 * bitRead(x, 19) + 8 * bitRead(x, 25) + 16 * bitRead(x, 30); 981 | bitVal = bitRead(x, 31) ^ bitRead(x, 15) ^ bitRead(KeeLoq_NLF, index) ^ keyBitVal; 982 | x = (x << 1) ^ bitVal; 983 | } 984 | return x; 985 | } 986 | 987 | /** 988 | * Set Normal Learning Keeloq key 989 | */ 990 | void Keeloq::NormLearn(unsigned long FixSN) { 991 | unsigned long tmpFixSN; 992 | // заготовки для формируемого ключа 993 | unsigned long NewkeyHigh; 994 | unsigned long NewkeyLow; 995 | 996 | tmpFixSN = FixSN & 0x0FFFFFFF; 997 | tmpFixSN |= 0x20000000; 998 | NewkeyLow = Decrypt(tmpFixSN); 999 | tmpFixSN = FixSN & 0x0FFFFFFF; 1000 | tmpFixSN |= 0x60000000; 1001 | NewkeyHigh = Decrypt(tmpFixSN); 1002 | _keyHigh = NewkeyHigh; 1003 | _keyLow = NewkeyLow; 1004 | } 1005 | 1006 | /** 1007 | * Reflect a 32 bit package 1008 | */ 1009 | unsigned long Keeloq::ReflectPack(unsigned long PackSrc) { 1010 | unsigned long long PackOut = 0; 1011 | for (byte i = 0; i < 32; i++) { 1012 | PackOut = PackOut << 1; 1013 | if (PackSrc & 1) { 1014 | PackOut = PackOut | 1; 1015 | } 1016 | PackSrc = PackSrc >> 1; 1017 | } 1018 | return PackOut; 1019 | } 1020 | -------------------------------------------------------------------------------- /src/RCSwitch.h: -------------------------------------------------------------------------------- 1 | /* 2 | RCSwitch - Arduino libary for remote control outlet switches 3 | Copyright (c) 2011 Suat Özgür. All right reserved. 4 | 5 | Contributors: 6 | - Andre Koehler / info(at)tomate-online(dot)de 7 | - Gordeev Andrey Vladimirovich / gordeev(at)openpyro(dot)com 8 | - Skineffect / http://forum.ardumote.com/viewtopic.php?f=2&t=46 9 | - Dominik Fischer / dom_fischer(at)web(dot)de 10 | - Frank Oltmanns / .(at)gmail(dot)com 11 | - Max Horn / max(at)quendi(dot)de 12 | - Robert ter Vehn / .(at)gmail(dot)com 13 | 14 | Project home: https://github.com/sui77/rc-switch/ 15 | 16 | This library is free software; you can redistribute it and/or 17 | modify it under the terms of the GNU Lesser General Public 18 | License as published by the Free Software Foundation; either 19 | version 2.1 of the License, or (at your option) any later version. 20 | 21 | This library is distributed in the hope that it will be useful, 22 | but WITHOUT ANY WARRANTY; without even the implied warranty of 23 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 24 | Lesser General Public License for more details. 25 | 26 | You should have received a copy of the GNU Lesser General Public 27 | License along with this library; if not, write to the Free Software 28 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 29 | */ 30 | #ifndef _RCSwitch_h 31 | #define _RCSwitch_h 32 | 33 | #if defined(ARDUINO) && ARDUINO >= 100 34 | #include "Arduino.h" 35 | #elif defined(ENERGIA) // LaunchPad, FraunchPad and StellarPad specific 36 | #include "Energia.h" 37 | #elif defined(RPI) // Raspberry Pi 38 | #define RaspberryPi 39 | 40 | // Include libraries for RPi: 41 | #include /* memcpy */ 42 | #include /* abs */ 43 | #include 44 | #elif defined(SPARK) 45 | #include "application.h" 46 | #else 47 | // #include "WProgram.h" 48 | #endif 49 | 50 | #include 51 | 52 | 53 | // At least for the ATTiny X4/X5, receiving has to be disabled due to 54 | // missing libm depencies (udivmodhi4) 55 | #if defined( __AVR_ATtinyX5__ ) or defined ( __AVR_ATtinyX4__ ) 56 | #define RCSwitchDisableReceiving 57 | #endif 58 | 59 | // Number of maximum high/Low changes per packet. 60 | // We can handle up to (unsigned long) => 32 bit * 2 H/L changes per bit + 2 for sync 61 | // Для keeloq нужно увеличить RCSWITCH_MAX_CHANGES до 23+1+66*2+1=157 62 | #define RCSWITCH_MAX_CHANGES 157 // default 67 63 | 64 | // separationLimit: minimum microseconds between received codes, closer codes are ignored. 65 | // according to discussion on issue #14 it might be more suitable to set the separation 66 | // limit to the same time as the 'low' part of the sync signal for the current protocol. 67 | // should be set to the minimum value of pulselength * the sync signal 68 | #define RCSWITCH_SEPARATION_LIMIT 4100 69 | 70 | class RCSwitch { 71 | 72 | public: 73 | RCSwitch(); 74 | static unsigned int timings[RCSWITCH_MAX_CHANGES]; 75 | void switchOn(int nGroupNumber, int nSwitchNumber); 76 | void switchOff(int nGroupNumber, int nSwitchNumber); 77 | void switchOn(const char* sGroup, int nSwitchNumber); 78 | void switchOff(const char* sGroup, int nSwitchNumber); 79 | void switchOn(char sFamily, int nGroup, int nDevice); 80 | void switchOff(char sFamily, int nGroup, int nDevice); 81 | void switchOn(const char* sGroup, const char* sDevice); 82 | void switchOff(const char* sGroup, const char* sDevice); 83 | void switchOn(char sGroup, int nDevice); 84 | void switchOff(char sGroup, int nDevice); 85 | 86 | void sendTriState(const char* sCodeWord); 87 | void send(uint64_t code, unsigned int length); 88 | void send(const char* sCodeWord); 89 | 90 | #if not defined( RCSwitchDisableReceiving ) 91 | void enableReceive(int interrupt); 92 | void enableReceive(); 93 | void disableReceive(); 94 | bool available(); 95 | void resetAvailable(); 96 | 97 | unsigned long long getReceivedValue(); 98 | unsigned int getReceivedBitlength(); 99 | unsigned int getReceivedDelay(); 100 | unsigned int getReceivedProtocol(); 101 | unsigned int* getReceivedRawdata(); 102 | uint8_t getNumProtos(); 103 | #endif 104 | 105 | void enableTransmit(int nTransmitterPin); 106 | void disableTransmit(); 107 | void setPulseLength(int nPulseLength); 108 | void setRepeatTransmit(int nRepeatTransmit); 109 | void sendraw(const char* sFilename); 110 | #if not defined( RCSwitchDisableReceiving ) 111 | void setReceiveTolerance(int nPercent); 112 | void setReceiveProtocolMask(unsigned long long mask); 113 | void setReceiveUsingProtocolTiming(bool useProtocolTiming); 114 | void setReceiveUnknownProtocol(bool showUnknownProtocol); 115 | 116 | #endif 117 | 118 | /** 119 | * Description of a single pule, which consists of a high signal 120 | * whose duration is "high" times the base pulse length, followed 121 | * by a low signal lasting "low" times the base pulse length. 122 | * Thus, the pulse overall lasts (high+low)*pulseLength 123 | */ 124 | struct HighLow { 125 | uint8_t high; 126 | uint8_t low; 127 | }; 128 | 129 | /** 130 | * A "protocol" describes how zero and one bits are encoded into high/low 131 | * pulses. 132 | */ 133 | struct Protocol { 134 | /** base pulse length in microseconds, e.g. 350 */ 135 | uint16_t pulseLength; 136 | uint8_t PreambleFactor; 137 | HighLow Preamble; 138 | uint8_t HeaderFactor; 139 | HighLow Header; 140 | 141 | HighLow zero; 142 | HighLow one; 143 | 144 | /** 145 | * If true, interchange high and low logic levels in all transmissions. 146 | * 147 | * By default, RCSwitch assumes that any signals it sends or receives 148 | * can be broken down into pulses which start with a high signal level, 149 | * followed by a a low signal level. This is e.g. the case for the 150 | * popular PT 2260 encoder chip, and thus many switches out there. 151 | * 152 | * But some devices do it the other way around, and start with a low 153 | * signal level, followed by a high signal level, e.g. the HT6P20B. To 154 | * accommodate this, one can set invertedSignal to true, which causes 155 | * RCSwitch to change how it interprets any HighLow struct FOO: It will 156 | * then assume transmissions start with a low signal lasting 157 | * FOO.high*pulseLength microseconds, followed by a high signal lasting 158 | * FOO.low*pulseLength microseconds. 159 | */ 160 | bool invertedSignal; 161 | uint16_t Guard; 162 | }; 163 | 164 | void setProtocol(Protocol protocol); 165 | void setProtocol(int nProtocol); 166 | void setProtocol(int nProtocol, int nPulseLength); 167 | static bool receiveProtocol(const int p, unsigned int changeCount); 168 | 169 | private: 170 | char* getCodeWordA(const char* sGroup, const char* sDevice, bool bStatus); 171 | char* getCodeWordB(int nGroupNumber, int nSwitchNumber, bool bStatus); 172 | char* getCodeWordC(char sFamily, int nGroup, int nDevice, bool bStatus); 173 | char* getCodeWordD(char group, int nDevice, bool bStatus); 174 | void transmit(HighLow pulses); 175 | 176 | #if not defined( RCSwitchDisableReceiving ) 177 | static void handleInterrupt(); 178 | static void acceptUnknownProtocol(unsigned int changeCount); 179 | int nReceiverInterrupt; 180 | #endif 181 | int nTransmitterPin; 182 | int nRepeatTransmit; 183 | Protocol protocol; 184 | 185 | #if not defined( RCSwitchDisableReceiving ) 186 | static int nReceiveTolerance; 187 | static bool receiveUsingProtocolTiming; 188 | static bool receiveUnknownProtocol; 189 | volatile static unsigned long long nReceivedValue; 190 | volatile static unsigned long long nReceiveProtocolMask; 191 | volatile static unsigned int nReceivedBitlength; 192 | volatile static unsigned int nReceivedDelay; 193 | volatile static unsigned int nReceivedProtocol; 194 | const static unsigned int nSeparationLimit; 195 | /* 196 | * timings[0] contains sync timing, followed by a number of bits 197 | */ 198 | 199 | // буфер длительностей последних четырех пакетов, [0] - последний 200 | static unsigned int buftimings[4]; 201 | #endif 202 | 203 | 204 | }; 205 | 206 | //// 207 | class Keeloq { 208 | public: 209 | Keeloq(); 210 | void SetKey(unsigned long keyHigh, unsigned long keyLow); 211 | unsigned long GetKey(bool HighLow); 212 | unsigned long Encrypt(unsigned long data); 213 | unsigned long Decrypt(unsigned long data); 214 | void NormLearn(unsigned long FixSN); 215 | unsigned long ReflectPack(unsigned long PackSrc); 216 | private: 217 | unsigned long _keyHigh; 218 | unsigned long _keyLow; 219 | }; 220 | 221 | #endif 222 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "ELECHOUSE_CC1101_SRC_DRV.h" 6 | #include "main.h" 7 | #include "modules/CC1101/CC1101.h" 8 | #include 9 | #include "modules/SerialCom/serialCom.h" 10 | 11 | 12 | #define TINY_GSM_MODEM_SIM800 13 | 14 | HardwareSerial SerialGSM(1); // RX, TX 15 | // Define SPI pins 16 | #define SD_MOSI 23 17 | #define SD_MISO 19 18 | #define SD_CLK 18 19 | #define SD_CS 5 20 | 21 | // State enumeration 22 | 23 | 24 | // Range to attempt to autobaud 25 | // NOTE: DO NOT AUTOBAUD in production code. Once you've established 26 | // communication, set a fixed baud rate using modem.setBaud(#). 27 | #define GSM_AUTOBAUD_MIN 9600 28 | #define GSM_AUTOBAUD_MAX 57600 29 | 30 | #define TINY_GSM_TEST_GPRS true 31 | #define TINY_GSM_TEST_WIFI false 32 | #define TINY_GSM_TEST_TCP true 33 | #define TINY_GSM_TEST_SSL true 34 | #define TINY_GSM_TEST_CALL true 35 | #define TINY_GSM_TEST_SMS true 36 | #define TINY_GSM_TEST_USSD true 37 | #define TINY_GSM_TEST_BATTERY true 38 | #define TINY_GSM_TEST_TEMPERATURE true 39 | #define TINY_GSM_TEST_GSM_LOCATION true 40 | #define TINY_GSM_TEST_GPS true 41 | #define TINY_GSM_TEST_NTP true 42 | #define TINY_GSM_TEST_TIME true 43 | // disconnect and power down modem after tests 44 | #define TINY_GSM_POWERDOWN true 45 | #include 46 | 47 | 48 | TinyGsm modem(SerialGSM); 49 | 50 | KEEPER_STATE gateKeeperState; 51 | CC1101_CLASS CC1101; 52 | CC1101_PRESET C1101preset; 53 | serialCom mySerialCom; 54 | 55 | void testFileOperations() { 56 | // Check if the test file exists 57 | if (SD.exists("/test.txt")) { 58 | Serial.println(F("File exists: /test.txt")); 59 | } else { 60 | Serial.println(F("File does not exist: /test.txt")); 61 | } 62 | 63 | // Create and write to the file 64 | Serial.println(F("Creating and writing to /test.txt...")); 65 | File file = SD.open("/test.txt", FILE_WRITE); 66 | if (file) { 67 | file.println("Hello, SD card!"); 68 | file.println("Testing file write."); 69 | file.close(); 70 | Serial.println(F("File written successfully.")); 71 | } else { 72 | Serial.println(F("Failed to create /test.txt.")); 73 | } 74 | 75 | // Read the file 76 | Serial.println(F("Reading /test.txt...")); 77 | file = SD.open("/test.txt", FILE_READ); 78 | if (file) { 79 | while (file.available()) { 80 | Serial.write(file.read()); 81 | } 82 | file.close(); 83 | Serial.println(F("\nFile read successfully.")); 84 | } else { 85 | Serial.println(F("Failed to open /test.txt for reading.")); 86 | } 87 | 88 | // Delete the file 89 | Serial.println(F("Deleting /test.txt...")); 90 | if (SD.remove("/test.txt")) { 91 | Serial.println(F("/test.txt deleted successfully.")); 92 | } else { 93 | Serial.println(F("Failed to delete /test.txt.")); 94 | } 95 | 96 | // Test creating a directory 97 | Serial.println(F("Creating directory /testdir...")); 98 | if (SD.mkdir("/testdir")) { 99 | Serial.println(F("Directory /testdir created successfully.")); 100 | } else { 101 | Serial.println(F("Failed to create directory /testdir.")); 102 | } 103 | 104 | // Test removing a directory 105 | Serial.println(F("Removing directory /testdir...")); 106 | if (SD.rmdir("/testdir")) { 107 | Serial.println(F("Directory /testdir removed successfully.")); 108 | } else { 109 | Serial.println(F("Failed to remove directory /testdir.")); 110 | } 111 | 112 | if (SD.exists("/ReceivedCodes")) { 113 | Serial.println(F("Folder for saving codes exists.")); 114 | } else if (SD.mkdir("/ReceivedCodes")) { 115 | Serial.println(F("Folder for saving codes created.")); 116 | } else { 117 | gateKeeperState = STATE_SDCARD_EROOR; 118 | return; 119 | } 120 | SD.end(); 121 | } 122 | 123 | void setup() { 124 | // Initialize Serial Monitor 125 | Serial.begin(115200); 126 | mySerialCom.begin(115200, "ESP32_BT"); 127 | while (!Serial) { 128 | delay(10); // Wait for Serial to initialize 129 | } 130 | 131 | // Configure SPI pins 132 | SPI.begin(SD_CLK, SD_MISO, SD_MOSI, SD_CS); 133 | 134 | Serial.println(F("Initializing SD card...")); 135 | 136 | // Try initializing the SD card 137 | if (!SD.begin(SD_CS)) { 138 | Serial.println(F("SD card initialization failed!")); 139 | return; 140 | } 141 | 142 | Serial.println(F("SD card initialized successfully.")); 143 | 144 | testFileOperations(); // Optional for testing SD card 145 | 146 | bool CC1101Init = CC1101.init(); 147 | if (CC1101Init) { 148 | gateKeeperState = STATE_INIT_SUCESS; 149 | delay(20); 150 | CC1101.setFrequency(433.92); 151 | delay(20); 152 | CC1101.setCC1101Preset(AM650); 153 | delay(20); 154 | CC1101.enableReceiver(); 155 | delay(20); 156 | gateKeeperState = STATE_CAPTURE; 157 | delay(20); 158 | Serial.println("Gate keeper initialization successful."); 159 | } else { 160 | gateKeeperState = STATE_INIT_FAIL; 161 | Serial.println(F("CC1101 initialization failed.")); 162 | } 163 | 164 | } 165 | 166 | void loop() { 167 | if (gateKeeperState == STATE_INIT_SUCESS) { 168 | CC1101.enableReceiver(); 169 | delay(20); 170 | gateKeeperState = STATE_CAPTURE; 171 | } 172 | 173 | if (gateKeeperState == STATE_CAPTURE) { 174 | 175 | // Serial.print(digitalRead(CC1101_CCGDO0A)); 176 | 177 | if (CC1101.CheckReceived()) { 178 | Serial.println(F("Signal received.")); 179 | CC1101.signalanalyse(); 180 | gateKeeperState = STATE_INIT_SUCESS;; 181 | } 182 | } 183 | 184 | 185 | 186 | if (gateKeeperState == STATE_SDCARD_EROOR) { 187 | Serial.println(F("SD card error detected. Restarting initialization...")); 188 | gateKeeperState = STATE_INIT_FAIL; 189 | delay(5000); 190 | setup(); // Re-run setup to retry 191 | } 192 | 193 | mySerialCom.update(); 194 | } 195 | 196 | 197 | -------------------------------------------------------------------------------- /src/main.h: -------------------------------------------------------------------------------- 1 | #ifndef MAIN_H 2 | #define MAIN_H 3 | 4 | //---------------------------------------------------------------------------// 5 | //-----------------------------ENUMBS----------------------------------------// 6 | //---------------------------------------------------------------------------// 7 | 8 | // Gate keeper state mashine 9 | enum KEEPER_STATE { 10 | STATE_INIT, 11 | STATE_INIT_FAIL, 12 | STATE_INIT_SUCESS, 13 | STATE_SDCARD_EROOR, 14 | STATE_IDLE, 15 | STATE_CAPTURE, 16 | STATE_CAPTURE_COMPLETE, 17 | STATE_SIGNAL_ANALYZER, 18 | STATE_SIGNAL_ANALYZER_COMPLETE, 19 | STATE_FILE_OPERATION, 20 | STATE_READY_TO_ACTION 21 | }; 22 | 23 | //gate keeper state, start on initialization 24 | extern KEEPER_STATE gateKeeperState; 25 | 26 | 27 | 28 | enum CC1101_PRESET { 29 | AM650, 30 | AM270, 31 | FM238, 32 | FM476, 33 | FM95, 34 | FSK12k, 35 | FM15k, 36 | FSK25k, 37 | FSK31k, 38 | PAGER, 39 | HND1, 40 | HND2, 41 | CUSTOM 42 | }; 43 | extern CC1101_PRESET C1101preset; 44 | 45 | #endif -------------------------------------------------------------------------------- /src/modules/CC1101/CC1101.cpp: -------------------------------------------------------------------------------- 1 | #include "CC1101.h" 2 | #include "main.h" 3 | #include "SD.h" 4 | #include "FlipperSubFile.h" 5 | 6 | #define SD_MOSI 23 7 | #define SD_MISO 19 8 | #define SD_CLK 18 9 | #define SD_CS 5 10 | 11 | //outher class variables *not feel right, will do differentely in future 12 | 13 | bool receiverEnabled; 14 | static unsigned long lastTime = 0; 15 | int sample[SAMPLE_SIZE]; 16 | int error_toleranz = 200; 17 | int samplecount = 0; 18 | int receiverGPIO; 19 | 20 | float CC1101_DRATE = 3.79372;; 21 | float CC1101_RX_BW = 650.00;; 22 | float CC1101_DEVIATION = 47.60; 23 | int CC1101_PKT_FORMAT = 0; 24 | int CC1101_SYNC = 2; 25 | float CC1101_FREQ = 433.92; 26 | int CC1101_MODULATION = 2; 27 | float CC1101_MHZ = 433.92; 28 | 29 | bool CC1101_is_initialized = false; 30 | bool CC1101_recieve_is_running = false; 31 | bool CC1101_transmit_is_running = false; 32 | bool CC1101_isiddle = true; 33 | bool CC1101_interup_attached = false; 34 | 35 | String decodedProtocol = ""; 36 | bool isDecoded = false; 37 | 38 | 39 | 40 | 41 | 42 | 43 | //////////////////// 44 | //////Protocols///// 45 | //////////////////// 46 | 47 | String protDecode[]={ 48 | "Unknown", 49 | "01 Princeton, PT-2240", 50 | "02 AT-Motor?", 51 | "03", 52 | "04", 53 | "05", 54 | "06 HT6P20B", 55 | "07 HS2303-PT, i. e. used in AUKEY Remote", 56 | "08 Came 12bit, HT12E", 57 | "09 Nice_Flo 12bit", 58 | "10 V2 phoenix", 59 | "11 Nice_FloR-S 52bit", 60 | "12 Keeloq 64/66 falseok", 61 | "13 test CFM", 62 | "14 test StarLine", 63 | "15", 64 | "16 Einhell", 65 | "17 InterTechno PAR-1000", 66 | "18 Intertechno ITT-1500", 67 | "19 Murcury", 68 | "20 AC114", 69 | "21 DC250", 70 | "22 Mandolyn/Lidl TR-502MSV/RC-402/RC-402DX", 71 | "23 Lidl TR-502MSV/RC-402 - Flavien", 72 | "24 Lidl TR-502MSV/RC701", 73 | "25 NEC", 74 | "26 Arlec RC210", 75 | "27 Zap, FHT-7901", 76 | "28", // github.com/sui77/rc-switch/pull/115 77 | "29 NEXA", 78 | "30 Anima", 79 | "31 Mertik Maxitrol G6R-H4T1", 80 | "32", //github.com/sui77/rc-switch/pull/277 81 | "33 Dooya Control DC2708L", 82 | "34 DIGOO SD10 ", //so as to use this protocol RCSWITCH_SEPARATION_LIMIT must be set to 2600 83 | "35 Dooya 5-Channel blinds remote DC1603", 84 | "36 DC2700AC", //Dooya remote DC2700AC for Dooya DT82TV curtains motor 85 | "37 DEWENWILS Power Strip", 86 | "38 Nexus weather, 36 bit", 87 | "39 Louvolite with premable" 88 | }; 89 | 90 | const char* CC1101_CLASS::presetToString(CC1101_PRESET preset) { 91 | switch (preset) { 92 | case AM650: return "AM650"; 93 | case AM270: return "AM270"; 94 | case FM238: return "FM238"; 95 | case FM476: return "FM476"; 96 | case FM95: return "FM95"; 97 | case FSK12k: return "FSK12k"; 98 | case FM15k: return "FM15k"; 99 | case FSK25k: return "FSK25k"; 100 | case FSK31k: return "FSK31k"; 101 | case PAGER: return "PAGER"; 102 | case HND1: return "HND1"; 103 | case HND2: return "HND2"; 104 | default: return "Unknown"; 105 | } 106 | } 107 | 108 | CC1101_PRESET CC1101_CLASS::convert_str_to_enum(const char * selected_str) { 109 | if (strcmp(selected_str, "AM650") == 0) return AM650; 110 | else if (strcmp(selected_str, "AM270") == 0) return AM270; 111 | else if (strcmp(selected_str, "FM238") == 0) return FM238; 112 | else if (strcmp(selected_str, "FM476") == 0) return FM476; 113 | else if (strcmp(selected_str, "FM95") == 0) return FM95; 114 | else if (strcmp(selected_str, "FSK12k") == 0) return FSK12k; 115 | else if (strcmp(selected_str, "FM15k") == 0) return FM15k; 116 | else if (strcmp(selected_str, "FSK25k") == 0) return FSK25k; 117 | else if (strcmp(selected_str, "FSK31k") == 0) return FSK31k; 118 | else if (strcmp(selected_str, "PAGER") == 0) return PAGER; 119 | else if (strcmp(selected_str, "HND1") == 0) return HND1; 120 | else if (strcmp(selected_str, "HND2") == 0) return HND2; 121 | else return CUSTOM; // Default to CUSTOM if no match is found 122 | } 123 | 124 | 125 | String CC1101_CLASS::getPresetSettingsString() { 126 | 127 | return String(presetToString(C1101preset)); 128 | } 129 | 130 | String CC1101_CLASS::getFrequeSettingsString() { 131 | return String(CC1101_FREQ); 132 | } 133 | 134 | 135 | void IRAM_ATTR InterruptHandler() 136 | { 137 | 138 | if (!receiverEnabled) 139 | { 140 | return; 141 | } 142 | 143 | const long time = micros(); 144 | const unsigned int duration = time - lastTime; 145 | 146 | if (duration > 100000) 147 | { 148 | samplecount = 0; 149 | } 150 | 151 | if (duration >= 100) 152 | { 153 | sample[samplecount++] = duration; 154 | } 155 | 156 | if (samplecount >= SAMPLE_SIZE) 157 | { 158 | return; 159 | } 160 | 161 | if (CC1101_MODULATION == 0) 162 | { 163 | if (samplecount == 1 && digitalRead(receiverGPIO) != HIGH) 164 | { 165 | samplecount = 0; 166 | } 167 | } 168 | 169 | lastTime = time; 170 | } 171 | 172 | bool CC1101_CLASS::CheckReceived() 173 | { 174 | if (samplecount >= minsample && micros() - lastTime > 100000) 175 | { 176 | receiverEnabled = false; 177 | return 1; 178 | } 179 | else 180 | { 181 | return 0; 182 | } 183 | } 184 | 185 | bool CC1101_CLASS::init() 186 | { 187 | ELECHOUSE_cc1101.setSpiPin(CC1101_SCLK, CC1101_MISO, CC1101_MOSI, CC1101_CS); 188 | ELECHOUSE_cc1101.Init(); 189 | 190 | if (ELECHOUSE_cc1101.getCC1101()) 191 | { 192 | ELECHOUSE_cc1101.setGDO0(CC1101_CCGDO0A); 193 | ELECHOUSE_cc1101.setSidle(); 194 | CC1101_isiddle = true; 195 | CC1101_is_initialized = true; 196 | return true; 197 | } 198 | else 199 | { 200 | ELECHOUSE_cc1101.setSidle(); 201 | CC1101_isiddle = true; 202 | return false; 203 | } 204 | 205 | } 206 | 207 | void CC1101_CLASS::setCC1101Preset(CC1101_PRESET preset) { 208 | C1101preset = preset; 209 | delay(20); 210 | } 211 | 212 | bool CC1101_CLASS::isValidPreset(CC1101_PRESET preset) { 213 | switch (preset) { 214 | case AM650: 215 | case AM270: 216 | case FM238: 217 | case FM476: 218 | case FM95: 219 | case FSK12k: 220 | case FM15k: 221 | case FSK25k: 222 | case FSK31k: 223 | case PAGER: 224 | case HND1: 225 | case HND2: 226 | case CUSTOM: 227 | return true; 228 | default: 229 | return false; 230 | } 231 | } 232 | 233 | void CC1101_CLASS::setCC1101DCcorrection(int v) { 234 | ELECHOUSE_cc1101.setDcFilterOff(v); 235 | } 236 | 237 | void CC1101_CLASS::loadPreset(CC1101_PRESET preset) { 238 | switch (preset) { 239 | case AM650: 240 | CC1101_MODULATION = 2; 241 | CC1101_DRATE = 3.79372; 242 | CC1101_RX_BW = 650.00; 243 | CC1101_DEVIATION = 1.58; 244 | break; 245 | case AM270: 246 | CC1101_MODULATION = 2; 247 | CC1101_DRATE = 3.79372; 248 | CC1101_RX_BW = 270.833333; 249 | CC1101_DEVIATION = 1.58; 250 | break; 251 | case FM238: 252 | CC1101_MODULATION = 0; 253 | CC1101_DRATE = 4.79794; 254 | CC1101_RX_BW = 270.833333; 255 | CC1101_DEVIATION = 2.380371; 256 | break; 257 | case FM476: 258 | CC1101_MODULATION = 0; 259 | CC1101_DRATE = 4.79794; 260 | CC1101_RX_BW = 270.833333; 261 | CC1101_DEVIATION = 47.60742; 262 | break; 263 | case FM95: 264 | CC1101_MODULATION = 0; 265 | CC1101_DRATE = 4.798; 266 | CC1101_RX_BW = 270; 267 | CC1101_DEVIATION = 9.521; 268 | CC1101_SYNC = 6; 269 | break; 270 | case FM15k: 271 | CC1101_MODULATION = 0; 272 | CC1101_DRATE = 3.794; 273 | CC1101_RX_BW = 135; 274 | CC1101_DEVIATION = 15.869; 275 | CC1101_SYNC = 7; 276 | break; 277 | case FSK12k: 278 | CC1101_MODULATION = 0; 279 | CC1101_DRATE = 12.69; 280 | CC1101_RX_BW = 200; 281 | CC1101_DEVIATION = 12.69; 282 | break; 283 | case FSK25k: 284 | CC1101_MODULATION = 0; 285 | CC1101_DRATE = 25.39; 286 | CC1101_RX_BW = 200; 287 | CC1101_DEVIATION = 25.39; 288 | break; 289 | case FSK31k: 290 | CC1101_MODULATION = 0; 291 | CC1101_DRATE = 31.73; 292 | CC1101_RX_BW = 200; 293 | CC1101_DEVIATION = 31.73; 294 | break; 295 | case PAGER: 296 | CC1101_MODULATION = 0; 297 | CC1101_DRATE = 0.625; 298 | CC1101_RX_BW = 270; 299 | CC1101_DEVIATION = 5.157; 300 | CC1101_SYNC = 6; 301 | break; 302 | case HND1: 303 | CC1101_MODULATION = 0; 304 | CC1101_DRATE = 37.04; 305 | CC1101_RX_BW = 250; 306 | CC1101_DEVIATION = 30; 307 | CC1101_SYNC = 6; 308 | break; 309 | case HND2: 310 | CC1101_MODULATION = 0; 311 | CC1101_DRATE = 15.357; 312 | CC1101_RX_BW = 67; 313 | CC1101_DEVIATION = 15.869; 314 | CC1101_SYNC = 7; 315 | break; 316 | default: 317 | Serial.println(CC1101_MODULATION); 318 | break; 319 | } 320 | Serial.print("preset loaded"); 321 | delay(20); 322 | } 323 | 324 | void CC1101_CLASS::enableReceiver() 325 | { 326 | if (!CC1101_is_initialized) { 327 | CC1101_CLASS::init(); 328 | //CC1101_CLASS::loadPreset(); 329 | } 330 | 331 | 332 | 333 | 334 | memset(sample, 0, sizeof(SAMPLE_SIZE)); 335 | samplecount = 0; 336 | 337 | if (CC1101_MODULATION == 2) 338 | { 339 | ELECHOUSE_cc1101.setDcFilterOff(0); 340 | } 341 | 342 | if (CC1101_MODULATION == 0) 343 | { 344 | ELECHOUSE_cc1101.setDcFilterOff(1); 345 | } 346 | 347 | ELECHOUSE_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 = 30/32 sync word bits detected. 4 = No preamble/sync, carrier-sense above threshold. 5 = 15/16 + carrier-sense above threshold. 6 = 16/16 + carrier-sense above threshold. 7 = 30/32 + carrier-sense above threshold. 348 | ELECHOUSE_cc1101.setPktFormat(3); // Format of RX and TX data. 0 = Normal mode, use FIFOs for RX and TX. 349 | // 1 = Synchronous serial mode, Data in on GDO0 and data out on either of the GDOx pins. 350 | // 2 = Random TX mode; sends random data using PN9 generator. Used for test. Works as normal mode, setting 0 (00), in RX. 351 | // 3 = Asynchronous serial mode, Data in on GDO0 and data out on either of the GDOx pins. 352 | // ELECHOUSE_cc1101.setSyncMode(3); 353 | ELECHOUSE_cc1101.setModulation(CC1101_MODULATION); // set modulation mode. 0 = 2-FSK, 1 = GFSK, 2 = ASK/OOK, 3 = 4-FSK, 4 = MSK. 354 | ELECHOUSE_cc1101.setMHZ(CC1101_MHZ); // Here you can set your basic frequency. The lib calculates the frequency automatically (default = 433.92).The cc1101 can: 300-348 MHZ, 387-464MHZ and 779-928MHZ. Read More info from datasheet. 355 | ELECHOUSE_cc1101.setDeviation(CC1101_DEVIATION); 356 | ELECHOUSE_cc1101.setDRate(CC1101_DRATE); // Set the Data Rate in kBaud. Value from 0.02 to 1621.83. Default is 99.97 kBaud! 357 | ELECHOUSE_cc1101.setRxBW(CC1101_RX_BW); // Set the Receive Bandwidth in kHz. Value from 58.03 to 812.50. Default is 812.50 kHz. 358 | pinMode(CC1101_CCGDO0A, INPUT); 359 | receiverGPIO = digitalPinToInterrupt(CC1101_CCGDO0A); 360 | ELECHOUSE_cc1101.SetRx(); 361 | ///////////////////////////// 362 | receiverEnabled = true; 363 | ////////////////////////////// 364 | attachInterrupt(CC1101_CCGDO0A, InterruptHandler, CHANGE); 365 | Serial.print("interrupt active"); 366 | 367 | } 368 | 369 | void CC1101_CLASS::setFrequency(float freq) 370 | { 371 | CC1101_MHZ = freq; 372 | ELECHOUSE_cc1101.setMHZ(CC1101_MHZ); 373 | } 374 | 375 | 376 | void CC1101_CLASS::signalanalyse(){ 377 | #define signalstorage 10 378 | 379 | int signalanz=0; 380 | int timingdelay[signalstorage]; 381 | float pulse[signalstorage]; 382 | long signaltimings[signalstorage*2]; 383 | int signaltimingscount[signalstorage]; 384 | long signaltimingssum[signalstorage]; 385 | long signalsum=0; 386 | 387 | for (int i = 0; isignaltimings[p*2-1]){ 406 | signaltimings[p*2]=sample[i]; 407 | } 408 | } 409 | } 410 | 411 | for (int i = 1; isignaltimings[p*2+1]){ 413 | signaltimings[p*2+1]=sample[i]; 414 | } 415 | } 416 | 417 | for (int i = 1; i=signaltimings[p*2] && sample[i]<=signaltimings[p*2+1]){ 419 | signaltimingscount[p]++; 420 | signaltimingssum[p]+=sample[i]; 421 | } 422 | } 423 | } 424 | 425 | int firstsample = signaltimings[0]; 426 | 427 | signalanz=signalstorage; 428 | for (int i = 0; i=5){calculate+=1;} 469 | if (calculate>0){ 470 | if (lastbin==0){ 471 | lastbin=1; 472 | }else{ 473 | lastbin=0; 474 | } 475 | } 476 | } 477 | Serial.println(); 478 | Serial.print("Samples/Symbol: "); 479 | Serial.println(timingdelay[0]); 480 | Serial.println(); 481 | 482 | smoothcount=0; 483 | for (int i=1; i=5){calculate+=1;} 489 | if (calculate>0){ 490 | samplesmooth[smoothcount] = calculate*timingdelay[0]; 491 | smoothcount++; 492 | } 493 | } 494 | 495 | uint16_t pulseTrain[SAMPLE_SIZE]; 496 | size_t length = smoothcount; 497 | 498 | for (size_t i = 0; i < smoothcount; i++) { 499 | pulseTrain[i] = static_cast(samplesmooth[i]); 500 | } 501 | 502 | 503 | String rawString = ""; 504 | Serial.println(""); 505 | for (int i = 0; i < smoothcount; i++) { 506 | rawString += (i > 0 ? (i % 2 == 1 ? " -" : " ") : ""); 507 | rawString += samplesmooth[i]; 508 | Serial.print(samplesmooth[i]); 509 | Serial.print(", "); 510 | } 511 | 512 | 513 | decodeProtocol(pulseTrain, length); 514 | 515 | delay(100); 516 | Serial.println(rawString); 517 | delay(100); 518 | SD.begin(SD_CS); 519 | FlipperSubFile subFile; 520 | 521 | 522 | 523 | 524 | String filename = generateFilename(CC1101_MHZ, CC1101_MODULATION, CC1101_RX_BW); 525 | String fullPath; 526 | 527 | if(isDecoded) { 528 | fullPath = "/ReceivedCodes/" + decodedProtocol + filename; 529 | } else { 530 | fullPath = "/ReceivedCodes/" + filename; 531 | } 532 | 533 | File outputFile = SD.open(fullPath, FILE_WRITE); 534 | if (outputFile) { 535 | std::vector customPresetData; 536 | if (C1101preset == CUSTOM) { 537 | customPresetData.insert(customPresetData.end(), { 538 | CC1101_MDMCFG4, ELECHOUSE_cc1101.SpiReadReg(CC1101_MDMCFG4), 539 | CC1101_MDMCFG3, ELECHOUSE_cc1101.SpiReadReg(CC1101_MDMCFG3), 540 | CC1101_MDMCFG2, ELECHOUSE_cc1101.SpiReadReg(CC1101_MDMCFG2), 541 | CC1101_DEVIATN, ELECHOUSE_cc1101.SpiReadReg(CC1101_DEVIATN), 542 | CC1101_FREND0, ELECHOUSE_cc1101.SpiReadReg(CC1101_FREND0), 543 | 0x00, 0x00 544 | }); 545 | 546 | std::array paTable; 547 | ELECHOUSE_cc1101.SpiReadBurstReg(0x3E, paTable.data(), paTable.size()); 548 | customPresetData.insert(customPresetData.end(), paTable.begin(), paTable.end()); 549 | } 550 | subFile.generateRaw(outputFile, C1101preset, customPresetData, rawString, CC1101_MHZ); 551 | outputFile.close(); 552 | SD.end(); 553 | gateKeeperState = STATE_INIT_SUCESS; 554 | } 555 | } 556 | 557 | void CC1101_CLASS::decodeProtocol(uint16_t *pulseTrain, size_t length) { 558 | if (length == 0) { 559 | Serial.println("No pulses to decode."); 560 | return; 561 | } 562 | 563 | bool foundSeparator = false; 564 | size_t startIndex = 0, endIndex = 0; 565 | 566 | // Separate single code by identifing of pause 567 | for (size_t i = 0; i < length; i++) { 568 | if (pulseTrain[i] > 5000) { 569 | if (!foundSeparator) { 570 | foundSeparator = true; 571 | startIndex = i + 1; 572 | } else { 573 | endIndex = i; 574 | break; 575 | } 576 | } 577 | } 578 | 579 | if (foundSeparator && endIndex > startIndex) { 580 | size_t newLength = endIndex - startIndex; 581 | for (size_t i = 0; i < newLength; i++) { 582 | pulseTrain[i] = pulseTrain[startIndex + i]; 583 | } 584 | length = newLength; 585 | } else { 586 | length = 0; 587 | } 588 | 589 | // Enable RC Switch decoding 590 | mySwitch.enableReceive(); 591 | 592 | // Ensure the pulse train length is within RC Switch's limits 593 | if (length > RCSWITCH_MAX_CHANGES) { 594 | Serial.println("Error: Pulse train too long for RC Switch."); 595 | return; 596 | } 597 | 598 | // Populate RC Switch's static timings array 599 | for (size_t i = 0; i < length; i++) { 600 | mySwitch.timings[i] = pulseTrain[i]; 601 | } 602 | bool knownProtocol = false; 603 | // Attempt to decode with RC Switch 604 | for (int protocol = 1; protocol <= mySwitch.getNumProtos(); protocol++) { 605 | if (mySwitch.receiveProtocol(protocol, length)) { 606 | knownProtocol = true; 607 | isDecoded = true; 608 | decodedProtocol = protDecode[protocol]; 609 | unsigned long long receivedValue = mySwitch.getReceivedValue(); 610 | Serial.println("Decoded Signal:"); 611 | Serial.print("Protocol: "); 612 | Serial.println(protDecode[protocol]); 613 | Serial.print("Value: "); 614 | Serial.println(receivedValue); 615 | Serial.print("Bit Length: "); 616 | Serial.println(mySwitch.getReceivedBitlength()); 617 | Serial.print("Pulse Length: "); 618 | Serial.println(mySwitch.getReceivedDelay()); 619 | 620 | 621 | if (receivedValue == 0) { 622 | Serial.println("Unknown encoding."); 623 | } else { 624 | 625 | // Print the value in binary 626 | Serial.print("Binary: "); 627 | for (int i = mySwitch.getReceivedBitlength() - 1; i >= 0; i--) { 628 | Serial.print((receivedValue >> i) & 1); 629 | } 630 | 631 | char hexBuffer[20]; 632 | snprintf(hexBuffer, sizeof(hexBuffer), "0x%lX", receivedValue); // Convert to hex 633 | Serial.println("\nHex: "); 634 | Serial.println(hexBuffer); 635 | 636 | 637 | if (receivedValue <= 0x7F) { 638 | char asciiBuffer[2] = {0}; 639 | asciiBuffer[0] = static_cast(receivedValue); // Convert to character 640 | Serial.print("ASCII: '"); 641 | Serial.write(asciiBuffer[0]); // Debug in Serial 642 | Serial.println("'"); 643 | } 644 | // Reset for the next signal 645 | mySwitch.resetAvailable(); 646 | return; 647 | } 648 | } 649 | } 650 | 651 | if(!knownProtocol) { 652 | if(!knownProtocol) { 653 | 654 | Serial.println("Unknown protocol."); 655 | 656 | String binaryResult = ""; // To store the resulting binary string 657 | unsigned long decimalValue = 0; // To accumulate the decimal value 658 | 659 | // Generate binary string and calculate decimal value 660 | for (int i = 0; i < sizeof(pulseTrain) / sizeof(pulseTrain[0]); i++) { 661 | int absValue = abs(pulseTrain[i]); 662 | int repeats = absValue / pulseTrain[0]; // Determine the number of repetitions 663 | 664 | // Append '1' or '0' based on the sign of the timing 665 | for (int j = 0; j < repeats; j++) { 666 | char bit = (pulseTrain[i] > 0) ? '1' : '0'; 667 | binaryResult += bit; 668 | decimalValue = (decimalValue << 1) | (bit - '0'); // Update decimal value 669 | } 670 | } 671 | 672 | // Convert decimal to hexadecimal 673 | char hexBuffer[20]; 674 | snprintf(hexBuffer, sizeof(hexBuffer), "0x%lX", decimalValue); 675 | 676 | // Output results 677 | Serial.println("Binary Representation: " + binaryResult); 678 | Serial.println("Decimal: " + String(decimalValue)); 679 | Serial.println("Hex: " + String(hexBuffer)); 680 | 681 | // Check if the value is within ASCII range and output the character 682 | if (decimalValue <= 0x7F) { 683 | char asciiBuffer[2] = {0}; 684 | asciiBuffer[0] = static_cast(decimalValue); 685 | Serial.print("ASCII: '"); 686 | Serial.write(asciiBuffer[0]); // Print ASCII character 687 | Serial.println("'"); 688 | } 689 | 690 | // Convert binary result to ASCII stream (if possible) 691 | String asciiStream = ""; 692 | for (size_t i = 0; i < binaryResult.length(); i += 8) { 693 | String byteStr = binaryResult.substring(i, i + 8); 694 | if (byteStr.length() == 8) { // Ensure it's a complete byte 695 | char asciiChar = static_cast(strtol(byteStr.c_str(), nullptr, 2)); 696 | if (asciiChar >= 0x20 && asciiChar <= 0x7E) { // Check if printable ASCII 697 | asciiStream += asciiChar; 698 | } else { 699 | asciiStream += '.'; // Replace non-printable with a placeholder 700 | } 701 | } 702 | } 703 | 704 | // Output ASCII stream if any 705 | if (asciiStream.length() > 0) { 706 | Serial.println("ASCII Stream: '" + asciiStream + "'"); 707 | } else { 708 | Serial.println("No valid ASCII characters in the binary sequence."); 709 | } 710 | } 711 | } 712 | } 713 | 714 | 715 | String CC1101_CLASS::generateFilename(float frequency, int modulation, float bandwidth) 716 | { 717 | char filenameBuffer[32]; 718 | 719 | sprintf(filenameBuffer, "%d_%s_%s.sub", static_cast(frequency * 100), presetToString(C1101preset), 720 | generateRandomString(8).c_str()); 721 | 722 | return String(filenameBuffer); 723 | } 724 | 725 | 726 | 727 | String CC1101_CLASS::generateRandomString(int length) 728 | { 729 | std::srand(static_cast(std::time(nullptr))); 730 | 731 | const std::string characters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; 732 | 733 | std::stringstream ss; 734 | for (int i = 0; i < length; ++i) { 735 | int randomIndex = std::rand() % characters.size(); 736 | char randomChar = characters[randomIndex]; 737 | ss << randomChar; 738 | } 739 | 740 | return String(ss.str().c_str()); 741 | } -------------------------------------------------------------------------------- /src/modules/CC1101/CC1101.h: -------------------------------------------------------------------------------- 1 | #ifndef C1101_H 2 | #define C1101_H 3 | 4 | 5 | #include "SPI.h" 6 | #include "ELECHOUSE_CC1101_SRC_DRV.h" 7 | #include "RCSwitch.h" 8 | #include "main.h" 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #define SAMPLE_SIZE 512 16 | 17 | // Pin configuration for CC1101 18 | #define CC1101_CS 14 // Chip Select 19 | #define CC1101_MOSI 13 // Master Out Slave In 20 | #define CC1101_MISO 12 // Master In Slave Out 21 | #define CC1101_SCLK 27 // Serial Clock 22 | 23 | #define CC1101_CCGDO0A 33 // GDO0 24 | 25 | 26 | 27 | //---------------------------------------------------------------------------// 28 | //-----------------------------ENUMBS----------------------------------------// 29 | //---------------------------------------------------------------------------// 30 | 31 | // C1101 presets 32 | 33 | 34 | 35 | 36 | // C1101 state mashine 37 | 38 | // Current State 39 | extern uint8_t C1101CurrentState; 40 | 41 | //////////////////// 42 | ////////FLAGS/////// 43 | //////////////////// 44 | 45 | extern bool CC1101_init; 46 | extern bool CC1101_is_initialized; 47 | extern bool CC1101_recieve_is_running; 48 | extern bool CC1101_transmit_is_running; 49 | extern bool CC1101_isiddle; 50 | extern bool CC1101_interup_attached; 51 | 52 | extern bool receiverEnabled; 53 | 54 | //////////////////// 55 | /////non class////// 56 | //////variables///// 57 | //////////////////// 58 | 59 | //Interrupt data 60 | 61 | extern int sample[SAMPLE_SIZE]; 62 | extern int error_toleranz; 63 | extern int samplecount; 64 | extern int receiverGPIO; 65 | 66 | //AM650 is default preset 433.92 is default frequency 67 | extern float CC1101_DRATE; 68 | extern float CC1101_RX_BW; 69 | extern float CC1101_DEVIATION; 70 | extern int CC1101_PKT_FORMAT; 71 | extern int CC1101_SYNC; 72 | extern float CC1101_FREQ; 73 | extern int CC1101_MODULATION; 74 | extern float CC1101_MHZ; 75 | extern String rawString; 76 | class CC1101_CLASS { 77 | public: 78 | 79 | 80 | //methods 81 | bool init(); 82 | void setCC1101Preset(CC1101_PRESET preset); 83 | void enableReceiver(); 84 | void signalanalyse(); 85 | bool CheckReceived(void); 86 | void setFrequency(float freq); 87 | String generateFilename(float frequency, int modulation, float bandwidth); 88 | String generateRandomString(int length); 89 | String getPresetSettingsString(); 90 | String getFrequeSettingsString(); 91 | const char* presetToString(CC1101_PRESET preset); 92 | CC1101_PRESET convert_str_to_enum(const char * selected_str); 93 | bool isValidPreset(CC1101_PRESET preset); 94 | void loadPreset(CC1101_PRESET preset); 95 | void setCC1101DCcorrection(int v); 96 | 97 | 98 | 99 | private: 100 | 101 | //classes 102 | SPIClass CC1101SPI; 103 | RCSwitch mySwitch; 104 | 105 | //variables 106 | int smoothcount; 107 | unsigned long samplesmooth[SAMPLE_SIZE]; 108 | String rawString = ""; 109 | int minsample = 30; 110 | 111 | //methods 112 | 113 | void decodeProtocol(uint16_t *pulseTrain, size_t length); 114 | 115 | 116 | }; 117 | 118 | #endif -------------------------------------------------------------------------------- /src/modules/SerialCom/serialCom.cpp: -------------------------------------------------------------------------------- 1 | #include "serialCom.h" 2 | #include "modules/CC1101/CC1101.h" 3 | 4 | serialCom::serialCom() { 5 | // Constructor body (if needed) 6 | } 7 | 8 | void serialCom::begin(long baudRate, const String& btDeviceName) { 9 | Serial.begin(baudRate); 10 | SerialBT.begin(btDeviceName); 11 | Serial.println("ESP32 Command Interface Started"); 12 | } 13 | 14 | void serialCom::update() { 15 | static String inputBuffer = ""; // Buffer for user input 16 | const char* promptColor = "\033[32m"; // Green color 17 | const char* resetColor = "\033[0m"; // Reset to default 18 | // Process USB Serial input 19 | while (Serial.available()) { 20 | char received = Serial.read(); 21 | 22 | if (received == '\b' && !inputBuffer.isEmpty()) { // Handle backspace 23 | inputBuffer.remove(inputBuffer.length() - 1); 24 | Serial.print("\b \b"); 25 | } else if (received == '\r' || received == '\n') { // Process on Enter 26 | Serial.println(); 27 | if (!inputBuffer.isEmpty()) { 28 | processInput(inputBuffer, Serial); 29 | inputBuffer = ""; 30 | } 31 | Serial.print(promptColor); 32 | Serial.print("GateKeeper> "); 33 | Serial.print(resetColor); 34 | } else if (isPrintable(received)) { // Echo printable characters 35 | inputBuffer += received; 36 | Serial.print(received); 37 | } 38 | } 39 | 40 | // Process Bluetooth Serial input 41 | while (SerialBT.available()) { 42 | char received = SerialBT.read(); 43 | 44 | if (received == '\b' && !inputBuffer.isEmpty()) { 45 | inputBuffer.remove(inputBuffer.length() - 1); 46 | SerialBT.print("\b \b"); 47 | } else if (received == '\r' || received == '\n') { 48 | SerialBT.println(); 49 | if (!inputBuffer.isEmpty()) { 50 | processInput(inputBuffer, SerialBT); 51 | inputBuffer = ""; 52 | } 53 | SerialBT.print(promptColor); 54 | SerialBT.print("GateKeeper> "); 55 | SerialBT.print(resetColor); 56 | } else if (isPrintable(received)) { 57 | inputBuffer += received; 58 | SerialBT.print(received); 59 | } 60 | } 61 | } 62 | 63 | 64 | void serialCom::processInput(const String& input, Stream& serialPort) { 65 | String trimmedInput = input; 66 | trimmedInput.trim(); 67 | if (trimmedInput.length() == 0) return; 68 | 69 | // Split input into command name and arguments 70 | int spaceIndex = trimmedInput.indexOf(' '); 71 | String commandName = (spaceIndex == -1) ? trimmedInput : trimmedInput.substring(0, spaceIndex); 72 | String args = (spaceIndex == -1) ? "" : trimmedInput.substring(spaceIndex + 1); 73 | 74 | // Get the command code and execute the command 75 | CommandCode cmdCode = getCommandCode(commandName); 76 | executeCommand(cmdCode, commandName, args, serialPort); // Pass commandName instead of args 77 | } 78 | 79 | 80 | CommandCode serialCom::getCommandCode(const String& commandName) { 81 | if (commandName.equalsIgnoreCase("wifi")) return CMD_WIFI; 82 | else if (commandName.equalsIgnoreCase("time")) return CMD_TIME; 83 | else if (commandName.equalsIgnoreCase("gsm")) return CMD_GSM; 84 | else if (commandName.equalsIgnoreCase("cc1101")) return CMD_CC1101; 85 | else if (commandName.equalsIgnoreCase("help")) return CMD_HELP; 86 | else return CMD_UNKNOWN; 87 | } 88 | 89 | OperationCode serialCom::getOperationCode(const String& operationStr) { 90 | if (operationStr.equals("-r")) return OP_READ; 91 | else if (operationStr.equals("-w")) return OP_WRITE; 92 | else return OP_UNKNOWN_OP; 93 | } 94 | 95 | void serialCom::executeCommand(CommandCode cmdCode, const String& commandName, const String& args, Stream& serialPort) { 96 | switch (cmdCode) { 97 | case CMD_WIFI: { 98 | // Extract operation and options 99 | int spaceIndex = args.indexOf(' '); 100 | String operationStr = (spaceIndex == -1) ? args : args.substring(0, spaceIndex); 101 | String options = (spaceIndex == -1) ? "" : args.substring(spaceIndex + 1); 102 | OperationCode opCode = getOperationCode(operationStr); 103 | handleWiFiCommand(opCode, options, serialPort); 104 | break; 105 | } 106 | case CMD_TIME: { 107 | int spaceIndex = args.indexOf(' '); 108 | String operationStr = (spaceIndex == -1) ? args : args.substring(0, spaceIndex); 109 | String options = (spaceIndex == -1) ? "" : args.substring(spaceIndex + 1); 110 | OperationCode opCode = getOperationCode(operationStr); 111 | handleTimeCommand(opCode, options, serialPort); 112 | break; 113 | } 114 | case CMD_GSM: { 115 | int spaceIndex = args.indexOf(' '); 116 | String operationStr = (spaceIndex == -1) ? args : args.substring(0, spaceIndex); 117 | String options = (spaceIndex == -1) ? "" : args.substring(spaceIndex + 1); 118 | OperationCode opCode = getOperationCode(operationStr); 119 | handleGSMCommand(opCode, options, serialPort); 120 | break; 121 | } 122 | case CMD_CC1101: { 123 | int spaceIndex = args.indexOf(' '); 124 | String operationStr = (spaceIndex == -1) ? args : args.substring(0, spaceIndex); 125 | String options = (spaceIndex == -1) ? "" : args.substring(spaceIndex + 1); 126 | OperationCode opCode = getOperationCode(operationStr); 127 | handleCC1101Command(opCode, options, serialPort); 128 | break; 129 | } 130 | case CMD_HELP: 131 | showHelp(serialPort); 132 | break; 133 | default: 134 | serialPort.println("Unknown command: " + commandName); 135 | serialPort.println("Type 'help' to see the list of available commands."); 136 | break; 137 | } 138 | } 139 | 140 | void serialCom::handleWiFiCommand(OperationCode opCode, const String& options, Stream& serialPort) { 141 | switch (opCode) { 142 | case OP_READ: 143 | // Read Wi-Fi settings 144 | serialPort.println("***Not Implemented yet **Current Wi-Fi Settings:"); 145 | serialPort.println("SSID: " + WiFi.SSID()); 146 | serialPort.println("Password: " + WiFi.psk()); 147 | break; 148 | case OP_WRITE: { 149 | // Write Wi-Fi settings 150 | String option1, value1, option2, value2; 151 | parseOptions(options, option1, value1, option2, value2); 152 | 153 | bool updated = false; 154 | if (option1 == "--ssid") { 155 | String newSSID = value1; 156 | // Implement actual Wi-Fi SSID change logic here 157 | // Example: Reconnect with new SSID 158 | WiFi.disconnect(); 159 | WiFi.begin(newSSID.c_str(), WiFi.psk().c_str()); 160 | serialPort.println("***Not Implemented yet **Wi-Fi SSID updated to: " + newSSID); 161 | updated = true; 162 | } 163 | if (option1 == "--password") { 164 | String newPassword = value1; 165 | // Implement actual Wi-Fi password change logic here 166 | WiFi.disconnect(); 167 | WiFi.begin(WiFi.SSID().c_str(), newPassword.c_str()); 168 | serialPort.println("***Not Implemented yet **Wi-Fi Password updated."); 169 | updated = true; 170 | } 171 | if (option2 == "--ssid") { 172 | String newSSID = value2; 173 | // Implement actual Wi-Fi SSID change logic here 174 | WiFi.disconnect(); 175 | WiFi.begin(newSSID.c_str(), WiFi.psk().c_str()); 176 | serialPort.println("***Not Implemented yet **Wi-Fi SSID updated to: " + newSSID); 177 | updated = true; 178 | } 179 | if (option2 == "--password") { 180 | String newPassword = value2; 181 | // Implement actual Wi-Fi password change logic here 182 | WiFi.disconnect(); 183 | WiFi.begin(WiFi.SSID().c_str(), newPassword.c_str()); 184 | serialPort.println("***Not Implemented yet **Wi-Fi Password updated."); 185 | updated = true; 186 | } 187 | if (!updated) { 188 | serialPort.println("***Not Implemented yet **No valid Wi-Fi options provided. Use '--ssid=' and/or '--password='."); 189 | } 190 | break; 191 | } 192 | default: 193 | serialPort.println("***Not Implemented yet **Invalid Wi-Fi operation. Use '-r' to read or '-w' to write."); 194 | break; 195 | } 196 | } 197 | 198 | void serialCom::handleTimeCommand(OperationCode opCode, const String& options, Stream& serialPort) { 199 | switch (opCode) { 200 | case OP_READ: 201 | // Read system time 202 | serialPort.println("***Not Implemented yet **Current system time (ms since boot): " + String(millis())); 203 | break; 204 | case OP_WRITE: { 205 | // Write system time 206 | String option1, value1, option2, value2; 207 | parseOptions(options, option1, value1, option2, value2); 208 | 209 | if (option1 == "--value") { 210 | String timestampStr = value1; 211 | // Implement actual time setting logic here if using an RTC or NTP 212 | // Example placeholder: 213 | serialPort.println("System time updated to: " + timestampStr); 214 | } 215 | if (option2 == "--value") { 216 | String timestampStr = value2; 217 | // Implement actual time setting logic here 218 | serialPort.println("***Not Implemented yet **System time updated to: " + timestampStr); 219 | } 220 | if (option1 != "--value" && option2 != "--value") { 221 | serialPort.println("***Not Implemented yet **No time value provided. Use '--value='."); 222 | } 223 | break; 224 | } 225 | default: 226 | serialPort.println("***Not Implemented yet **Invalid Time operation. Use '-r' to read or '-w' to write."); 227 | break; 228 | } 229 | } 230 | 231 | void serialCom::handleGSMCommand(OperationCode opCode, const String& options, Stream& serialPort) { 232 | switch (opCode) { 233 | case OP_READ: 234 | // Read GSM module status 235 | serialPort.println("***Not Implemented yet **Current GSM module status: ..."); 236 | break; 237 | case OP_WRITE: { 238 | // Write GSM settings 239 | String option1, value1, option2, value2; 240 | parseOptions(options, option1, value1, option2, value2); 241 | 242 | bool updated = false; 243 | if (option1 == "--apn") { 244 | String newAPN = value1; 245 | // Implement actual GSM APN change logic here 246 | serialPort.println("***Not Implemented yet **GSM APN updated to: " + newAPN); 247 | updated = true; 248 | } 249 | if (option2 == "--apn") { 250 | String newAPN = value2; 251 | // Implement actual GSM APN change logic here 252 | serialPort.println("***Not Implemented yet **GSM APN updated to: " + newAPN); 253 | updated = true; 254 | } 255 | if (!updated) { 256 | serialPort.println("***Not Implemented yet **No APN provided. Use '--apn='."); 257 | } 258 | break; 259 | } 260 | default: 261 | serialPort.println("***Not Implemented yet **Invalid GSM operation. Use '-r' to read or '-w' to write."); 262 | break; 263 | } 264 | } 265 | 266 | void serialCom::handleCC1101Command(OperationCode opCode, const String& options, Stream& serialPort) { 267 | CC1101_CLASS CC1101; 268 | switch (opCode) { 269 | case OP_READ: 270 | // Read CC1101 settings 271 | serialPort.println("Current CC1101 settings:"); 272 | serialPort.println("Preset:"); 273 | serialPort.print(CC1101.getPresetSettingsString()); 274 | serialPort.println("\nFreque:"); 275 | serialPort.print(CC1101.getFrequeSettingsString()); 276 | serialPort.println("State:"); 277 | serialPort.print(getStateString(gateKeeperState)); 278 | break; 279 | case OP_WRITE: { 280 | gateKeeperState == STATE_IDLE; 281 | // Write CC1101 settings 282 | String option1, value1, option2, value2; 283 | parseOptions(options, option1, value1, option2, value2); 284 | 285 | bool updated = false; 286 | // Function to check if a frequency is within the valid ranges 287 | auto isValidFrequency = [](float freq) -> bool { 288 | return (freq >= 300.0 && freq <= 348.0) || 289 | (freq >= 387.0 && freq <= 464.0) || 290 | (freq >= 779.0 && freq <= 928.0); 291 | }; 292 | 293 | // Handle option1 294 | if (option1 == "--freq") { 295 | float freq = value1.toFloat(); 296 | if (isValidFrequency(freq)) { 297 | CC1101.setFrequency(freq); 298 | serialPort.println("CC1101 frequency set to: " + String(freq)); 299 | updated = true; 300 | } else { 301 | serialPort.println("Invalid frequency. Allowed ranges are 300-348, 387-464, 779-928 MHz."); 302 | } 303 | } else if (option1 == "--preset") { 304 | String newPreset = value1; 305 | CC1101_PRESET presetEnum = CC1101.convert_str_to_enum(newPreset.c_str()); 306 | if (CC1101.isValidPreset(presetEnum)) { 307 | CC1101.setCC1101Preset(presetEnum); 308 | CC1101.loadPreset(presetEnum); 309 | CC1101.enableReceiver(); 310 | serialPort.println("CC1101 preset set to: " + newPreset); 311 | updated = true; 312 | delay(20); 313 | } else { 314 | serialPort.println("Invalid preset. Use 'help' to see available presets."); 315 | } 316 | } else if (option1 == "--DC") { 317 | String settings = value1; 318 | if (settings.toInt() == 1 || settings.toInt() == 1) { 319 | CC1101.setCC1101DCcorrection(settings.toInt()); 320 | serialPort.println("CC1101 DC correction off is: " + settings); 321 | updated = true; 322 | } else { 323 | serialPort.println("Invalid preset. Use 'help' to see available presets."); 324 | } 325 | } 326 | 327 | // Handle option2 (if present) 328 | if (option2 == "--freq") { 329 | float freq = value2.toFloat(); 330 | if (isValidFrequency(freq)) { 331 | CC1101.setFrequency(freq); 332 | serialPort.println("CC1101 frequency set to: " + String(freq)); 333 | updated = true; 334 | } else { 335 | serialPort.println("Invalid frequency. Allowed ranges are 300-348, 387-464, 779-928 MHz."); 336 | } 337 | } else if (option2 == "--preset") { 338 | String newPreset = value2; 339 | CC1101_PRESET presetEnum = CC1101.convert_str_to_enum(newPreset.c_str()); 340 | if (CC1101.isValidPreset(presetEnum)) { 341 | CC1101.setCC1101Preset(presetEnum); 342 | CC1101.loadPreset(presetEnum); 343 | CC1101.enableReceiver(); 344 | serialPort.println("CC1101 preset set to: " + newPreset); 345 | updated = true; 346 | delay(20); 347 | } else { 348 | serialPort.println("Invalid preset. Use 'help' to see available presets."); 349 | } 350 | } 351 | 352 | 353 | if (!updated) { 354 | serialPort.println("No frequency or preset provided. Use '--freq=' or '--preset='."); 355 | } 356 | break; 357 | gateKeeperState == STATE_INIT_SUCESS; 358 | } 359 | default: 360 | serialPort.println("Invalid CC1101 operation. Use '-r' to read or '-w' to write."); 361 | break; 362 | } 363 | } 364 | 365 | String serialCom::getStateString(KEEPER_STATE state) { 366 | switch(state) { 367 | case STATE_INIT: 368 | return "STATE_INIT"; 369 | case STATE_INIT_FAIL: 370 | return "STATE_INIT_FAIL"; 371 | case STATE_INIT_SUCESS: 372 | return "STATE_INIT_SUCESS"; 373 | case STATE_SDCARD_EROOR: 374 | return "STATE_SDCARD_EROOR"; 375 | case STATE_IDLE: 376 | return "STATE_IDLE"; 377 | case STATE_CAPTURE: 378 | return "STATE_CAPTURE"; 379 | case STATE_CAPTURE_COMPLETE: 380 | return "STATE_CAPTURE_COMPLETE"; 381 | case STATE_SIGNAL_ANALYZER: 382 | return "STATE_SIGNAL_ANALYZER"; 383 | case STATE_SIGNAL_ANALYZER_COMPLETE: 384 | return "STATE_SIGNAL_ANALYZER_COMPLETE"; 385 | case STATE_FILE_OPERATION: 386 | return "STATE_FILE_OPERATION"; 387 | case STATE_READY_TO_ACTION: 388 | return "STATE_READY_TO_ACTION"; 389 | default: 390 | return "UNKNOWN_STATE"; 391 | } 392 | } 393 | 394 | void serialCom::showHelp(Stream& serialPort) { 395 | serialPort.println("Available commands:"); 396 | serialPort.println("wifi -r : Read Wi-Fi settings"); 397 | serialPort.println("wifi -w --ssid= : Set Wi-Fi SSID"); 398 | serialPort.println("wifi -w --password= : Set Wi-Fi password"); 399 | serialPort.println("time -r : Read system time"); 400 | serialPort.println("time -w --value= : Set system time"); 401 | serialPort.println("gsm -r : Read GSM module status"); 402 | serialPort.println("gsm -w --apn= : Set GSM APN"); 403 | serialPort.println("cc1101 -r : Read CC1101 settings"); 404 | serialPort.println("cc1101 -w --freq= : Set CC1101 frequency"); 405 | serialPort.println("cc1101 -w --preset= : Set CC1101 preset"); 406 | serialPort.println("Available presets:"); 407 | serialPort.println(" AM650"); 408 | serialPort.println(" AM270"); 409 | serialPort.println(" FM238"); 410 | serialPort.println(" FM476"); 411 | serialPort.println(" FM95"); 412 | serialPort.println(" FSK12k"); 413 | serialPort.println(" FM15k"); 414 | serialPort.println(" FSK25k"); 415 | serialPort.println(" FSK31k"); 416 | serialPort.println(" PAGER"); 417 | serialPort.println(" HND1"); 418 | serialPort.println(" HND2"); 419 | serialPort.println(" CUSTOM"); 420 | serialPort.println("cc1101 -w --DC=<0/1> : Set CC1101 DC Correction OFF/ON"); 421 | serialPort.println("help : Show this help message"); 422 | } 423 | 424 | void serialCom::parseOptions(const String& optionsStr, String& option1, String& value1, String& option2, String& value2) { 425 | // Initialize output parameters 426 | option1 = ""; 427 | value1 = ""; 428 | option2 = ""; 429 | value2 = ""; 430 | 431 | int firstOptionIndex = optionsStr.indexOf("--"); 432 | if (firstOptionIndex == -1) return; 433 | 434 | int secondOptionIndex = optionsStr.indexOf("--", firstOptionIndex + 2); 435 | String firstOptionStr = (secondOptionIndex == -1) ? optionsStr.substring(firstOptionIndex) : optionsStr.substring(firstOptionIndex, secondOptionIndex); 436 | String secondOptionStr = (secondOptionIndex == -1) ? "" : optionsStr.substring(secondOptionIndex); 437 | 438 | // Parse first option 439 | int equalIndex1 = firstOptionStr.indexOf('='); 440 | if (equalIndex1 != -1) { 441 | option1 = firstOptionStr.substring(0, equalIndex1); 442 | option1.trim(); // Modify in-place 443 | 444 | value1 = firstOptionStr.substring(equalIndex1 + 1); 445 | value1.trim(); // Modify in-place 446 | } 447 | 448 | // Parse second option if present 449 | if (secondOptionStr.length() > 0) { 450 | int equalIndex2 = secondOptionStr.indexOf('='); 451 | if (equalIndex2 != -1) { 452 | option2 = secondOptionStr.substring(0, equalIndex2); 453 | option2.trim(); // Modify in-place 454 | 455 | value2 = secondOptionStr.substring(equalIndex2 + 1); 456 | value2.trim(); // Modify in-place 457 | } 458 | } 459 | } 460 | -------------------------------------------------------------------------------- /src/modules/SerialCom/serialCom.h: -------------------------------------------------------------------------------- 1 | #ifndef SERIALCOM_H 2 | #define SERIALCOM_H 3 | 4 | #include 5 | #include 6 | #include "BluetoothSerial.h" 7 | #include "main.h" 8 | 9 | // Define command codes using enum for better type safety 10 | enum CommandCode { 11 | CMD_WIFI = 1, 12 | CMD_TIME, 13 | CMD_GSM, 14 | CMD_CC1101, 15 | CMD_HELP, 16 | CMD_UNKNOWN = -1 17 | }; 18 | 19 | // Define operation codes 20 | enum OperationCode { 21 | OP_READ = 1, 22 | OP_WRITE, 23 | OP_UNKNOWN_OP = -1 24 | }; 25 | 26 | class serialCom { 27 | public: 28 | serialCom(); 29 | void begin(long baudRate, const String& btDeviceName); 30 | void update(); 31 | 32 | private: 33 | BluetoothSerial SerialBT; 34 | 35 | // Command processing 36 | void processInput(const String& input, Stream& serialPort); 37 | void executeCommand(CommandCode cmdCode, const String& commandName, const String& args, Stream& serialPort); 38 | 39 | // Command handlers 40 | void handleWiFiCommand(OperationCode opCode, const String& options, Stream& serialPort); 41 | void handleTimeCommand(OperationCode opCode, const String& options, Stream& serialPort); 42 | void handleGSMCommand(OperationCode opCode, const String& options, Stream& serialPort); 43 | void handleCC1101Command(OperationCode opCode, const String& options, Stream& serialPort); 44 | void showHelp(Stream& serialPort); 45 | 46 | // Utility functions 47 | CommandCode getCommandCode(const String& commandName); 48 | OperationCode getOperationCode(const String& operationStr); 49 | void parseOptions(const String& optionsStr, String& option1, String& value1, String& option2, String& value2); 50 | String getStateString(KEEPER_STATE state); 51 | }; 52 | 53 | #endif // SERIALCOM_H 54 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------