├── .github └── workflows │ └── arduino-checks.yml ├── .gitignore ├── LICENSE.md ├── README.md ├── docs ├── RF433.md ├── SC5272_protocol.md ├── cresta_protocol.md ├── ev1527_protocol.md └── scanner.md ├── examples ├── README.md ├── TempSensor │ └── TempSensor.ino ├── intertechno │ ├── README.md │ └── intertechno.ino ├── necIR │ └── necIR.ino ├── scanner │ ├── README.md │ └── scanner.ino └── testcodes │ └── testcodes.ino ├── library.properties └── src ├── RFCodes.h ├── SignalCollector.cpp ├── SignalCollector.h ├── SignalParser.cpp ├── SignalParser.h ├── debugout.h ├── ircodes.h └── protocols.h /.github/workflows/arduino-checks.yml: -------------------------------------------------------------------------------- 1 | name: Arduino Library Checks 2 | 3 | # Controls when the action will run. 4 | on: 5 | # Triggers the workflow on push or pull request events but only for the master branch 6 | push: 7 | branches: [develop,master] 8 | pull_request: 9 | branches: [develop,master] 10 | 11 | # Allows you to run this workflow manually from the Actions tab 12 | workflow_dispatch: 13 | 14 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 15 | jobs: 16 | # This defines a job for checking the Arduino library format specifications 17 | # see 18 | lint: 19 | name: check library format 20 | runs-on: ubuntu-latest 21 | continue-on-error: true 22 | 23 | steps: 24 | - uses: actions/checkout@v2 25 | 26 | # Arduino - lint 27 | - name: Arduino-lint 28 | uses: arduino/arduino-lint-action@v1 29 | with: 30 | library-manager: update 31 | verbose: false 32 | 33 | # These jobs are used to compile the examples fot the specific processor/board. 34 | # see 35 | compile-esp8266: 36 | name: compile esp8266 examples 37 | runs-on: ubuntu-latest 38 | continue-on-error: true 39 | 40 | steps: 41 | - uses: actions/checkout@v2 42 | 43 | # Runs Arduino Compiler 44 | # see https://github.com/arduino/compile-sketches 45 | #see https://github.com/arduino-libraries/Arduino_ConnectionHandler/blob/master/.github/workflows/compile-examples.yml 46 | - name: compile examples 47 | uses: arduino/compile-sketches@v1 48 | with: 49 | verbose: true 50 | platforms: | 51 | # Install ESP8266 platform via Boards Manager 52 | - name: esp8266:esp8266 53 | source-url: https://arduino.esp8266.com/stable/package_esp8266com_index.json 54 | version: 3.0.0 55 | fqbn: esp8266:esp8266:nodemcuv2 56 | sketch-paths: | 57 | - 'examples/intertechno' 58 | - 'examples/necIR' 59 | - 'examples/TempSensor' 60 | # - 'examples/scanner' 61 | 62 | # libraries: ${{ env.LIBRARIES }} 63 | # size-report-sketch: 'ConnectionHandlerDemo' 64 | # enable-size-deltas-report: 'true' 65 | # sketches-report-path: ${{ env.SKETCHES_REPORTS_PATH }} 66 | 67 | # compile-uno: 68 | # name: compile uno examples 69 | # runs-on: ubuntu-latest 70 | # continue-on-error: true 71 | 72 | # steps: 73 | # - uses: actions/checkout@v2 74 | 75 | # # Compile Examples for UNO 76 | # - name: compile examples 77 | # uses: arduino/compile-sketches@v1 78 | # with: 79 | # verbose: true 80 | # fqbn: arduino:avr:uno 81 | # sketch-paths: | 82 | # - 'examples/intertechno' 83 | 84 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _* 2 | .vscode -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # Software License Agreement (BSD 3-Clause Style License) 2 | 3 | See http://www.mathertel.de/License.aspx 4 | 5 | Copyright (c) 2005-2014 by Matthias Hertel, http://www.mathertel.de/ 6 | 7 | All rights reserved. 8 | 9 | Redistribution and use in source and binary forms, with or without modification, 10 | are permitted provided that the following conditions are met: 11 | 12 | * Redistributions of source code must retain the above copyright notice, this 13 | list of conditions and the following disclaimer. 14 | 15 | * Redistributions in binary form must reproduce the above copyright notice, 16 | this list of conditions and the following disclaimer in the documentation 17 | and/or other materials provided with the distribution. 18 | 19 | * Neither the name of the copyright owners nor the names of its contributors 20 | may be used to endorse or promote products derived from this software without 21 | specific prior written permission. 22 | 23 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 24 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 25 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 26 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 27 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 29 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 31 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 32 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RFCodes library 2 | 3 | This is a library that can encode and decode signals patterns that are used in the 433 MHz and IR technology. 4 | 5 | These signals use a carrier frequency (433MHz or 44kHz) that is switched on and off using a defined pattern. 6 | 7 | Each protocol often defined by a device manufacturer or a chip producing company consists of a series of pulses and pauses (codes) with a defined length that have a special semantic. 8 | 9 | There is a textual representation for sending and receiving a sequence by specifying the short name of the protocol and the characters specifying the code. For some protocols there is an algorithm defined that compiles the characters into a real value. 10 | So any code sequence corresponds to a textual representation like `it2 s_##___#____#_#__###_____#__#____x` 11 | 12 | ## Use the library 13 | 14 | Using the library requires the following steps: 15 | 16 | * A set of protocol definitions that you expect that they are used. 17 | * A pin where the inbound signal comes in (receiver) 18 | * A pin where the outbound signal can be send (sender) 19 | * A function that gets called when a code was decoded. 20 | 21 | For the receiver role the library uses a interrupt routine that gets called when ever a signal change on the pin has been detected. 22 | Some microprocessors support only specific pins with interrupts 23 | so please look up the documentation for **Arduino attachInterrupt()** function for the processor. 24 | 25 | Sending a protocol uses no interrupts but also should not be interrupted by another ISR routine. 26 | 27 | ## The Wiring 28 | 29 | A interrupt capable pin can be used to attach a receiver. e.g. D5 on a ESP8266 board. 30 | 31 | Another pin can be used to attach a transmitter e.g. D6 on a ESP8266 board. 32 | 33 | Both, receiver and transmitter must be connected to VCC and GND. 34 | Best option is to use variants that can be used with 3.3V when using a ESP8266. 35 | 36 | ```TXT 37 | 3.3V ---------------- 3.3V --------------- 3.3V 38 | | | | 39 | +-----+-----+ +-----+-----+ +----+------+ 40 | | RF433 | | ESP8266 | | RF433 | 41 | | receiver +----> D5-+ board +-D6 ---->+ sender | 42 | | module | | | | module | 43 | +-----+-----+ +-----+-----+ +----+------+ 44 | | | | 45 | GND ----------------- GND ---------------- GND 46 | ``` 47 | 48 | The receiver modules that can be used must detect the RF signal and produce a signal when the carrier frequency has been detected. The polarity of the signal is not relevant. 49 | The modules I used and found reliable are the type RXB8 and RXB12, both with a ceramic resonator. The RF-5V and XY-MK-5V modules were not reliable in my environment and setup. 50 | 51 | The sender modules must produce a carrier frequency on HIGH output. When not transmitting a code the output is LOW so other devices can use the carrier frequency on their own. I used several modules all with ceramic resonators (no adjustable air coils). They seem to be less critical. 52 | 53 | ## Examples 54 | 55 | The following examples sketches are available: 56 | 57 | * The [Intertechno](./examples/intertechno/README.md) example shows how to register and use 58 | the 2 protocols used by devices from intertechno. 59 | 60 | * The [TempSensor](./examples/TempSensor/README.md) example shows how to receive temperature+humidity 61 | from a cresta protocol based sensor. 62 | 63 | * The [necIR](./examples/necIR/README.md) example shows how to receive and send the Infrared NEC protocol. 64 | 65 | * The [Scanner](./examples/scanner/README.md) example can be used to collect code timings for further analysis. 66 | 67 | ## Protocol definitions 68 | 69 | Here are some hints on how to configure a protocol: 70 | 71 | In the protocol structure the (short) name of the protocol and some 72 | settings must be defined: 73 | 74 | * **name** - a short name of the protocol. 75 | * **minCodeLen** - the minimum length of a code sequence including all start, data and end codes. 76 | * **maxCodeLen** - the maximum length of a code sequence including all start, data and end codes. 77 | * **tolerance** - codes are not sent and not captured using very precise timings. The tolerance defines the percentage the timing may derive. 78 | * **sendRepeat** - When sending the code the sequence should be repeated as specified by the sendRepeat parameter. 79 | * **baseTime** - Many protocols use a base clock time. This should be specified in the baseTime parameter and the factors in the code. 80 | * **codes** - The list of codes in this protocol. 81 | 82 | In the code definitions the typical timing patterns are defined. 83 | 84 | * **type** - The code type defines the role of this code in the sequence. 85 | 86 | * A **start code** is defined to recognize that a protocol is send out as it is sent at the beginning only. 87 | This can be used to simply wait until such a unique timing can be found. This may be a code with a exceptional duration or a series of durations marking the start of a sequence. 88 | As the following codes often have shorter timings so a 89 | short pulse and a long pause is part of many protocols to detect other senders transmitting into the pause. A simple way of collision detection. 90 | 91 | * Multiple **data codes** are defined to represent data bits. 92 | For binary protocols one sequence stays for a set bit and another for a cleared bit but you can find also protocols with 3 data codes. Multiple of these codes in a row can then be used to build the protocol data. 93 | 94 | * A **end code** marks the end of a sequence. 95 | This is useful for protocols that have a variable length of data. 96 | A code defined with the END flag will always stop the current sequence detection. When the minimum length is not yet given the sequence is not taken as a valid code. 97 | 98 | * Codes with a **fixed length** are defined using the minimum and maximum length with the length of the sequence. Do use the END flag only when there is a special code defined marking the end. 99 | Some protocols just end after a number of codes. 100 | 101 | * **name** - The single character representing a code in the sequence. 102 | It must be unique within the protocol. 103 | 104 | * **durations** - The list of durations that represent the code. 105 | 106 | ### Protocol Example 107 | 108 | The code used by the SC5272 chip is named "sc5", has 3 data codes ('0', '1' and 'f') and a stop code ('S') defined. 109 | So the textual representation may be "[sc5 0000f0000fffS]" 110 | 111 | The chip can be used with different clock speeds so the baseTime can be adjusted to fit the speed of your device. 112 | 113 | ```CPP 114 | /** Definition of the protocol from SC5272 and similar chips with 32 - 46 data bits data */ 115 | SignalParser::Protocol sc5 = { 116 | "sc5", 117 | .minCodeLen = 1 + 12, 118 | .maxCodeLen = 1 + 12, 119 | 120 | .tolerance = 25, 121 | .sendRepeat = 3, 122 | .baseTime = 100, 123 | .codes = { 124 | {SignalParser::CodeType::ANYDATA, '0', {4, 12, 4, 12}}, 125 | {SignalParser::CodeType::ANYDATA, '1', {12, 4, 12, 4}}, 126 | {SignalParser::CodeType::ANYDATA, 'f', {4, 12, 12, 4}}, 127 | {SignalParser::CodeType::END, 'S', {4, 124}}}}; 128 | ``` 129 | 130 | This 3-state protocol is also found using the END code as a start code. When submitting multiple sequences in a row as it is usually done by senders and expected by receivers this protocol is partially equivalent to the `it1` protocol. 131 | 132 | ## Implementation 133 | 134 | There are 2 classes combined here: 135 | 136 | **SignalParser** 137 | 138 | The `SignalParser` is a general usable class that knows all about the timing of codes in the protocol 139 | and knows how to decode and encode them. 140 | 141 | This class can take pulse/gap durations give to the `parse()` method and uses the registered protocol definitions 142 | to detect a full protocol sequence using valid codes. 143 | This allows a flexible usage of the SignalParser to be combined with different signal sources and frequencies 144 | or use the class to test for codes in a given series of durations. (see Example testcodes.ino) 145 | 146 | Since the solutions of the manufacturers vary quiet a lot this library can be adapted to different protocols by registering the signal patterns of the protocols using the `load` method by passing a Protocol+Codes definition. 147 | 148 | Whenever a full sequence is detected from the given durations the callback function is used to pass the sequence over for further processing. 149 | 150 | ```CPP 151 | SignalParser sig; 152 | 153 | // load some protocols into the SignalParser 154 | sig.load(&RFCodes::it1); 155 | sig.load(&RFCodes::it2); 156 | 157 | // register the callback function. 158 | sig.attachCallback(receiveCode); 159 | ``` 160 | 161 | **SignalCollector** 162 | 163 | The `SignalCollector` class handles interrupt routines and the IO pins. 164 | Every time when receiving a signal change the duration since the previous change is collected into a buffer. 165 | 166 | The loop() function must be called from the main loop function to transfer the durations from the buffer into the parser. 167 | 168 | Sending a sequence is done by calling the send() function with the protocol name and the codes as a string. 169 | 170 | ```CPP 171 | SignalCollector col; 172 | 173 | // initialize the SignalCollector library 174 | col.init(&sig, D5, D6); // input at pin D5, output at pin D6 175 | 176 | // send a sequence 177 | col.send("it2 s_##___#____#_#__###_____#____#__x"); 178 | ``` 179 | 180 | ## See also 181 | 182 | * [About RF Protocols](/docs/rf433.md) 183 | * [Standard protocols](/docs/SC5272_protocol.md) 184 | * [EV1527 protocol](/docs/ev1527_protocol.md) 185 | * [intertechno protocols](/docs/intertechno_protocol.md) 186 | * [Cresta protocol for sensors](/docs/cresta_protocol.md) 187 | -------------------------------------------------------------------------------- /docs/RF433.md: -------------------------------------------------------------------------------- 1 | # About RF Protocols wit 433 MHz 2 | 3 | These are some comments from my experience and observations when using 433 MHz OOK / ASK transmitter and receivers and protocols. 4 | 5 | Actually 433.92MHz (almost 434MHz) is used. 6 | 7 | The simple devices using 433MHz signals use on/off patterns (OOK/ASK) to send a specific information over the air. 8 | Most protocols define code patterns for start and stop, LOW bits and HIGH bits but there are also protocols that use more than 2 code patterns. 9 | 10 | 11 | ## Signal decoding 12 | 13 | The implementation of the library is made up of 3 parts: 14 | 15 | * The SignalCollector uses an interrupt routine and produces a stream of numbers that correlate to the time between 2 signal changes. 16 | The timings are delivered to a static ring buffer so the interrupt routine can return as soon as possible. 17 | By calling the loop() function in the loop() function of the main sketch the signal timings are taken from the buffer and are passed to the SignalParser. 18 | 19 | * The SignalParser checks in every defined protocol wether the given durations match. 20 | This happens independently on every protocol. To safe computing time, only required protocols should be loaded. 21 | 22 | * When a complete protocol is found a callback function is called with the textual representation of the protocol. 23 | This may be by detecting a stop code or reaching the maximum length of the protocol. 24 | Decoding the transferred vales is implemented on he base of this value. 25 | 26 | * When receiving a timing that doesn't fit to any defined code the receiving is reset and will restart with a next starting code that is detected. 27 | 28 | 29 | ## Analyzing new codes 30 | 31 | Analyzing new codes is an it adventure in itself. The following steps have been useful for me: 32 | 33 | * Collect some timings using the `scanner.ino` example. This sketch collects many durations using the same method like the SignalParser and writes them to the Serial output. 34 | 35 | * Search the internet and the referenced sources for the protocol and device for any hints you can get. 36 | 37 | * Create a new protocol definition. Examples can be found in `protocols.h`. 38 | 39 | * Use the `testcodes.ino` example with the given durations and optonally enable the TRACE_MSG and INFO_MSG outputs i the SignalParser to see how the codes are parsed. 40 | 41 | 42 | ## Finding Codes and hints on the internet 43 | 44 | There are some good sources of protocol definitions available. Here are some links I found useful: 45 | 46 | * 47 | * 48 | * 49 | * 50 | * 51 | * 52 | * 53 | * 54 | * 55 | 56 | 57 | 58 | ## Why RF signals sometime fail... 59 | 60 | ### 1. Some noise from somewhere else 61 | 62 | Here is an example next to a `[it2 s_##___#____#_#__###_____#____#__x]` sequence. 63 | When receiving the 15. code the pause expected to have 1250 µsecs. was interrupted by another signal and ended therefore with about 409 µsecs. 64 | 65 | ```TXT 66 | s 283 2658 67 | _ 286 255 296 1269 68 | # 286 1269 287 267 69 | # 285 1268 289 264 70 | _ 285 249 289 1278 71 | _ 286 251 285 1282 72 | _ 289 249 287 1278 73 | # 288 1268 291 261 74 | _ 294 239 294 1287 75 | _ 287 252 286 1281 76 | _ 284 254 287 1278 77 | _ 290 251 286 1279 78 | # 285 1259 294 268 79 | _ 283 259 287 1272 80 | 291 409 81 | ``` 82 | 83 | This kind of disruption is stopping code recognition or will result in random codes received. 84 | Better no code than any false code. 85 | 86 | Especially in start/stop codes often a long pause is used that is sensitive for noise from somewhere else. 87 | 88 | 89 | ### 2. Automatic receiver signal amplification 90 | 91 | Under real conditions senders may be far away producing low signals or may be nearby producing high signals. 92 | Therefore many receivers have an automatic level adjustment for the sensitivity of the input. 93 | 94 | This results often in receiving much short pulse noise when no signal is actually sent because the receiving amplifier volumes gets higher and higher until any signal is found. When then a nearby sender is activated, the amplification is too high for some milliseconds until the automatic level is reduced to a usable amount. 95 | 96 | This results in missing the first timings of a sequence correctly. 97 | 98 | Repeating the same signal in the sender for multiple times or sending a extra start signal is a approbate method to level all receivers to the right level. 99 | 100 | 101 | ### 3. Low signals 102 | 103 | Use a good antenna with the right length. (17.3 cm) 104 | 105 | You can also find small helicals. They are not as good as a 1/4 wave length antennas but save space. 106 | 107 | You can find cheap antennas and outdoor antennas at eBay... 108 | 109 | 110 | ## TODO 111 | 112 | * Add more protocols 113 | * Port back to AVR. 114 | * Use debugging output not using printf to save memory on AVR cores. 115 | * Documentation on 116 | 117 | 118 | -------------------------------------------------------------------------------- /docs/SC5272_protocol.md: -------------------------------------------------------------------------------- 1 | # SC5272 Protocol 2 | 3 | The protocol that the SC5272 chip is one of the kind that accepts some address and data inputs and generates a 12-bit protocol 4 | using different ON/OFF signal patterns. 5 | 6 | This chip allows 3 different states per bit. 7 | 8 | From the datasheet you can see that the sync code is sent after the data bits and the whole protocol is sent 3 times in a row. 9 | Therefore other protocol definitions with a starting sync code may conflict. E.g. see intertechno-old protocol. 10 | 11 | There are also very similar chips like the PT2264 on the market that also send these kind of signals and you can find also derivations of this protocol. 12 | 13 | ## Protocol Timings 14 | 15 | From the manual you can find 4 different codes in use: 16 | 17 | | Code | timing ratio | 18 | | -------- | --------------- | 19 | | bit '0' | 4 : 12 : 4 : 12 | 20 | | bit '1' | 12 : 4 : 12 : 4 | 21 | | bit 'f' | 4 : 12 : 12 : 4 | 22 | | sync bit | 4: 124 | 23 | 24 | These rations need to be multiplied by a base timing that varies. 25 | 26 | The SC5272 protocol can be added to the signal parser by calling `sig.load(&RFCodes::sc5)` 27 | 28 | The baseTiming of 100 µ seconds is a good starting point but may be adjusted in the protocol definition. 29 | 30 | ## Examples 31 | 32 | [sc5 0000f0000fffS] 33 | [sc5 0000f0000ff0S] 34 | 35 | 36 | ## See also 37 | 38 | * SC5272 datasheet 39 | * PT2264 datasheet 40 | -------------------------------------------------------------------------------- /docs/cresta_protocol.md: -------------------------------------------------------------------------------- 1 | # Cresta protocol for sensors 2 | 3 | This protocol definition supports the manchester encoded protocol that is used in some temperature and humidity sensors using 433 MHz transmission. 4 | 5 | These sensors have a fixed starting byte that is transferred using 5 long timings in a row. 6 | The following timings are either one long (l) or two short timings (s) as defined in the manchester protocol. 7 | See the wikipedia article on how the manchester code is built in principle. 8 | 9 | From the cresta data in the manchester coding a 7 bytes are recorded even when the sensor will send more information and a checksum. 10 | More details on the protocol can be found in . 11 | 12 | 13 | ## How manchester code can be decoded based on fixed codes 14 | 15 | The recorded sequence begins with the data bit 0x75 transferred with the LSB first and a 0-bit between the bytes: `0 1-0-1-0-1-1-1-0 0`. 16 | This results in Huffman coding: `LH HL-LH-HL-LH-HL-HL-HL-LH LH` 17 | 18 | When two signals in a row with the same level will result in a long and others in short timings: 19 | * A `H-H` or `L-L` sequence is recorded as one long signals (l) 20 | * A `H-L` or `L-H` sequence is recorded as two short singals (s) 21 | 22 | The timings we record is `(s)l l l l l s s l s..`. 23 | This is well designed so we can wait until we see the 5 long signals in a row and than start capturing the l or s codes. 24 | Therefore signals from the cresta type sensors will start with `[cw Hssls` 25 | 26 | ```TXT 27 | data : 0 -------- 0x75 -------- 0 28 | data : 0 1 0 1 0 1 1 1 0 0 29 | level : LH HL LH HL LH HL HL HL LH LH 30 | times : sl -l -l -l -l -s ss sl -s ss 31 | code : -H--------------s--s--l--s--s 32 | ``` 33 | 34 | The package starts with a first short low level `s` that typically follows a pause so it will not be observed. 35 | The next 5 `l` in a row are combined into the start code `H` as a the start sequence when receivong a data package. 36 | 37 | The `cresta_decode` function takse the codes as an input and creates the data bytes by decoding into levels and bits. 38 | Then the temperature and humidity is takenfrom the data bits and printed to the log. 39 | 40 | 41 | ### Protocol Example with decoding 42 | 43 | [cw Hsslsllssllsssslllsslllsllllsllsllsslssslslsssslllslsslssss] 44 | data:9f 33 ce de 13 c2 12 45 | .temp: 21.3 °C 46 | .hum: 12 % 47 | 48 | ## See also 49 | 50 | * 51 | 52 | -------------------------------------------------------------------------------- /docs/ev1527_protocol.md: -------------------------------------------------------------------------------- 1 | # EV1527 Protocol 2 | 3 | The protocol that the EV1527 chip is one of the kind that has a (almost) unique ID as part of the sending sequence 4 | to avoid accepts conflicts with other implementations. 5 | 6 | The receivers must "learn" these IDs usually by using a specific button on the devices. 7 | 8 | From the datasheet you can see that the a start code and 24 data codes are sent. 9 | 10 | ## Protocol Timings 11 | 12 | From the manual you can find 3 different codes in use: 13 | 14 | | Code | timing ratio | 15 | | -----| --------------- | 16 | | 's' | 1 : 32 | 17 | | '0' | 1 : 3 | 18 | | '1' | 3 : 1 | 19 | 20 | A fully frame will consist in a 21 | 22 | * 's' preample 23 | * 10 digits that make a unique sender ID 24 | * 4 digits that vary by the input signal (button) used. 25 | 26 | These ratios need to be multiplied by a base timing that varies. 27 | 28 | The EV1527 protocol can be added to the signal parser by calling `sig.load(&RFCodes::ev1527);` 29 | 30 | The baseTiming of 320 µ seconds is a good starting point but may be adjusted in the protocol defintion. 31 | 32 | ## Examples 33 | 34 | [ev1527 s100100110110001001000001] 35 | [ev1527 s100100110110001001000010] 36 | 37 | ## See also 38 | 39 | * EV1527 datasheet 40 | -------------------------------------------------------------------------------- /docs/scanner.md: -------------------------------------------------------------------------------- 1 | # Scanner application 2 | 3 | The scanner project is a standalone sketch that can record received timings around a specific condition. 4 | 5 | I recommend to use a EPS8266 chip for scanning because it needs to record some 6 | 7 | 8 | ## Wiring 9 | 10 | The scanner project must be adjusted if you use other pins as the following: 11 | 12 | **startPin = D5** 13 | 14 | The D5 pin should be used with a momentary button that is pulling the signal to GND. 15 | 16 | 17 | **receiverPin = D7** 18 | 19 | The D7 pin is connected to the signal from the RF receiver. 20 | 21 | 22 | **ledPin = D4** 23 | 24 | The D4 pin is connected to the LED on the ESP-12 and is used to visualize the incomming signal. 25 | When using a e.g. nodeMCU alike board no wiring is requred. 26 | 27 | 28 | ## Recording 29 | 30 | Recording is started by pressing the momentary button connected to D5. 31 | 32 | A starting condition can be implemented to find a protocol candidate while recording. In the standard this condition is waiting for a long timing in the range of 6000 tp 12000 µsecs. 33 | 34 | Recording then is done in the following steps: 35 | 36 | 1. First a set of timings are recorded to be sure to fill the buffer. 37 | 2. All following timings are recorded while the starting condition is evaluated. 38 | 3. When the starting condition is true the following timings are recorded until the buffer is filled. 39 | 4. Recording is stopped and all timings are sent to the Serial output to be visible in the monitor and available to be analyzed. 40 | 41 | ## Recording example 42 | 43 | Here is example of a recording. The last timing in the first block was the starting condition. 44 | The first block is showing the timings before this point and the second block the timings past this point. 45 | 46 | RF Scanner... 47 | 48 | wait... 49 | check... 50 | collect... 51 | .............done. 52 | 139,9,33,3795,162,50,7,686,428,1259,1321,427,415,439,26,794,1329,79,8,73,35,178,482,1263,494,1262,432,1252,1343,359,454,1312, 53 | 447,1264,395,1288,1347,386,436,1254,1352,334,585,1174,1343,441,367,1305,1263,423,465,1250,1323,446,408,1272,19,27,1257,425,409,1297,450,1264, 54 | 478,7464,40,1419,37,60,40,4184,431,270,33,1014,494,27,741,419,492,912,93,35,59,153,1327,382,431,460,30,787,445,1253,484,1228, 55 | 1371,376,475,1247,439,1245,454,1315,1271,431,395,1273,1334,399,454,1268,594,24,700,407,482,1215,1328,423,451,1274,1288,386,472,1300,1255,415, 56 | 426,1285,476,1245,467,2557,42,10668,484,1235,1308,438,404,371,130,774,1340,380,427,971,10,326,415,1266,465,1248,378,24,40,16,918,361, 57 | 512,848,40,353,422,1268,33,8,428,1224,1351,393,452,1234,1324,458,413,1249,1296,437,418,1294,1289,497,387,1266,1143,551,441,1326,1272,393, 58 | 284,19,142,1334,285,8,48,1381,396,10274,44,2689,61,37,21,126,511,66,14,1142,1299,410,452,21,66,370,18,843,1247,412,481,1296, 59 | 433,1264,463,1220,1305,399,462,1259,469,1259,470,1228,2390,8,845,247,1284,513,22,32,300,1301,2104,658,1888,504,381,1275,205,8,1057,477, 60 | 324,29,64,1299,1133,685,118,26,183,1266,232,14,453,1009,206,28,104,46,95,13243,484,1239,1315,401,446,1327,1275,389,426,1276,462,1267, 61 | 461,1254,549,24,695,443,456,1256,475,1246,445,1333,413,19,794,430,458,1271,1302,393,359,23,88,1253,291,84,50,33,29,31,848,352, 62 | 196,30,226,1294,137,15,1115,443,404,1292,235,38,1032,408,454,1272,29,33,1273,447,134,8,204,1414,374,150,194,1032,340,9552,41,3698, 63 | 395,1275,1337,372,473,1274,1068,15,228,389,31,8,429,1233,456,1269,249,30,199,1284,1250,409,449,1273,485,1267,418,1114,1463,8,29,99, 64 | 189,85,457,1302,1260,410,581,1167,65,8,1209,413,449,1253,1350,406,490,1234,1298,381,134,132,141,1311,1338,377,430,1125,12,141,458,1279, 65 | 425,5892,46,69,73,875,35,1854,60,60,39,2063,59,2212,441,180,58,1009,1276,408,480,735,41,459,1355,416,483,829,64,281,482,1235, 66 | 509,1242,1319,377,466,191,57,1014,455,1245,299,9,182,1237,1280,180,30,75,37,64,507,1294,56,8,246,11,1020,354,418,64,292,27, 67 | 517,205,89,84,1157,576,455,1270,1327,368,473,1259,1154,28,81,455,472,1230,1323,422,438,1248,427,15,175,245,167,990,288,1231,130,7424, 68 | --- 69 | 266,4224,468,1302,1244,102,773,1264,1372,391,64,35,353,1341,57,12,207,1396,390,1303,70,18,1075,14,107,394,471,1424,28,9,235,1278, 70 | 472,1214,1322,301,519,1298,1300,436,409,1188,515,12,899,432,351,1318,1291,476,430,1286,1306,372,422,874,206,235,753,19,532,456,381,463, 71 | 96,838,291,402,20,908,437,7541,46,2059,15,1561,58,1966,454,404,95,364,37,46,72,250,1306,463,436,1046,50,18,58,84,1322,415, 72 | 451,847,28,346,455,807,37,438,64,91,251,1281,1320,259,53,96,433,172,17,985,25,179,425,24,38,1140,501,200,47,1005,35,18, 73 | 1251,359,488,1268,1321,31,30,51,44,224,445,972,51,288,677,8,557,420,537,1227,1286,383,468,1267,1355,387,35,27,404,1265,1283,376, 74 | 412,1303,487,1253,492,2058,26,1920,62,1600,45,3165,414,3710,54,175,473,1267,1325,399,448,1212,1371,385,472,1220,473,1239,489,1235,1344,370, 75 | 493,1226,510,1298,403,108,33,1120,661,141,509,373,480,1240,1344,384,437,1290,1304,403,455,1238,1308,414,470,1288,1280,443,433,1228,1320,461, 76 | 473,75,491,205,93,326,451,1301,427,8101,25,5133,524,1233,1289,433,438,1234,1296,414,484,1273,470,1221,468,436,90,724,1334,366,450,1317, 77 | 408,1283,482,1263,1278,386,485,1276,1287,446,433,1259,1303,410,477,1218,1270,551,356,1362,1248,422,417,1234,1346,383,466,1262,445,1254,452,13296, 78 | 32,19,361,1290,1339,377,481,1265,1284,411,67,19,384,1268,452,1220,453,1266,1364,394,48,16,118,1500,452,1317,475,1362,1179,368,486,1248, 79 | 1313,376,493,1241,1339,403,465,1205,1338,404,483,1234,1311,503,343,1293,124,11,1137,409,503,1300,359,1301,447,13277,476,1241,1313,406,482,1259, 80 | 1323,337,502,1241,464,1274,408,1267,1364,367,442,1267,112,21,382,1200,486,1270,1341,332,485,1272,1306,414,444,1229,1304,461,433,1252,65,13, 81 | 2391,30,835,182,63,10,892,712,379,1311,1308,395,461,1333,393,473,116,683,424,4189,203,5493,291,3150,365,1328,1309,395,452,1301,1271,433, 82 | 437,1302,411,1296,413,1308,1274,589,253,1261,423,1341,18,39,374,1231,1304,481,406,1286,22,29,1233,431,45,15,385,1268,1072,27,205,480, 83 | 308,1312,1326,401,438,1375,1192,468,438,1229,1320,379,501,1224,477,1240,475,13259,454,1286,1298,429,434,1245,29,11,1337,457,381,1215,477,1237, 84 | 442,1303,378,16,914,438,361,1311,455,1260,58,16,412,1218,1368,367,487,1252,1302,409,423,1261,1343,413,35,27,389,267,2309,379,501,610, 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | The following examples are provided: 4 | 5 | ## Intertechno 6 | 7 | This example shows how to receive and send the protocols used by the intertechno devices. 8 | Here 2 different protocols can be decoded using the same receiver. 9 | 10 | 11 | ## TempSensor 12 | 13 | This example is part of the RFCodes library showing how to receive temperature+humidity from a cresta based sensor. 14 | The protocol used can transport data and is using manchester coding. 15 | 16 | See [Cresta protocol](../docs/cresta_protocol.md) for more details. 17 | 18 | 19 | ## necIR 20 | 21 | This example is part of the RFCodes library showing how to receive IR protocols that are very similar to the 22 | RF protocols regarding the timings of the signals. 23 | The nec protocol uses a specific start sequence and shorter sequences for 0 and 1 bits. 24 | 25 | See for more details. 26 | 27 | 28 | ## Scanner 29 | 30 | This is a standalone sketch that can record received timings around a specific condition. 31 | 32 | See [Scanner documentation](../docs/scanner.md) for more details. 33 | 34 | The default condition is waiting for a long transmission time (6000-12000 µsecs). 35 | 36 | While recording all received timings the condition is evaluated and when the condition is detected some 512 more timings are recorded. 37 | Then all timings are send to Serial output to be visible in the monitor and can be analyzed. 38 | 39 | 40 | ## necIR 41 | 42 | This example shows how to receive and send the Infrared NEC protocol. 43 | 44 | https://github.com/crankyoldgit/IRremoteESP8266 45 | -------------------------------------------------------------------------------- /examples/TempSensor/TempSensor.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * @file TempSensor.ino 3 | * 4 | * @author Matthias Hertel (https://www.mathertel.de) 5 | * @copyright Copyright (c) by Matthias Hertel, https://www.mathertel.de. 6 | * This work is licensed under a BSD 3-Clause style license, see https://www.mathertel.de/License.aspx 7 | * 8 | * @brief Receive codes from a temperature and humidity sensor. 9 | * This file is part of the RFCodes library that implements receiving an sending RF and IR protocols. 10 | * 11 | * This example shows how to use a 433 MHz receiver to get and decode a protocol from a temperature and humidity sensor. 12 | * For more details see cresta_protocol.md in the docu folder. 13 | * 14 | * Wiring (ESP8266): 15 | * * a receiver can be attached with data to pin D5. 16 | 17 | * * 13.05.2020 created from receiver.ino 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | #include 25 | 26 | SignalParser sig; 27 | SignalCollector col; 28 | 29 | // ===== Cresta protocol decoding ===== 30 | 31 | // This function can be used to decode the Cresta Manchester protocol. 32 | 33 | void cresta_decode(const char *p) 34 | { 35 | uint8_t cresta_data[10]; // our device emits 10+2 data bytes, we only read the 10 and ignore the checksum 36 | int cresta_cnt = 0; // number of received bytes 37 | 38 | uint8_t cresta_byte = 0; // currenty byte value fom the stream 39 | uint8_t cresta_bits = 0; // next bit to receive. 0..7, 8 is the 0-bit used inbetween data baytes 40 | 41 | // simulate shifting in bits from header : 10101 42 | cresta_byte = 0x15; 43 | cresta_bits = 5; 44 | bool bit = 1; // last bit was HIGH 45 | 46 | while (*p) { 47 | if (*p == 'l') { 48 | bit = 1 - bit; 49 | } 50 | p++; 51 | 52 | if (cresta_bits < 8) { 53 | // shift bit into data byte 54 | cresta_byte |= bit << cresta_bits; 55 | cresta_bits++; 56 | } else { 57 | // shift completed byte into buffer 58 | // bit must be 0, not verified 59 | cresta_byte = cresta_byte ^ (cresta_byte << 1); // decode 60 | cresta_data[cresta_cnt++] = cresta_byte; 61 | // reset for next byte 62 | cresta_bits = 0; 63 | cresta_byte = 0; 64 | } 65 | } // while 66 | 67 | // print received data 68 | Serial.print("data:"); 69 | for (int i = 0; i < 7; i++) { 70 | if (cresta_data[i] < 16) Serial.print("0"); 71 | Serial.print(cresta_data[i], 16); 72 | Serial.print(" "); 73 | } // for 74 | Serial.println(); 75 | 76 | if (cresta_data[0] != 0x9f) { 77 | Serial.println("Bad data."); 78 | } else { 79 | // temperature 80 | int temp = 100 * (cresta_data[5] & 0x0f) + 10 * (cresta_data[4] >> 4) + (cresta_data[4] & 0x0f); 81 | Serial.print(" temp: "); 82 | Serial.print(temp / 10); 83 | Serial.print('.'); 84 | Serial.print(temp % 10); 85 | Serial.println(" °C"); 86 | 87 | // humidity 88 | int hum = 10 * (cresta_data[6] >> 4) + (cresta_data[6] & 0x0f); 89 | Serial.print(" hum: "); 90 | Serial.print(hum); 91 | Serial.println(" %"); 92 | } 93 | } // cresta_decode 94 | 95 | 96 | // This function will be called when a complete protcol was received. 97 | void receiveCode(const char *proto) 98 | { 99 | Serial.print("received ["); 100 | Serial.print(proto); 101 | Serial.println("]"); 102 | 103 | if (strncmp(proto, "cw ", 3) == 0) { 104 | cresta_decode(proto + 4); 105 | } 106 | } // receiveCode() 107 | 108 | 109 | void setup() 110 | { 111 | Serial.begin(115200); 112 | Serial.println("Cresta Protocol Receiver Example"); 113 | Serial.println(); 114 | 115 | // load the protocol into the SignalParser 116 | sig.load(&RFCodes::cw); 117 | 118 | // show all defined protocols 119 | sig.dumpTable(); 120 | 121 | // initialize the SignalCollector library 122 | col.init(&sig, D5, NO_PIN); // input at pin D5, no output 123 | 124 | sig.attachCallback(receiveCode); 125 | } // setup() 126 | 127 | 128 | void loop() 129 | { 130 | // process received bytes 131 | col.loop(); 132 | } // loop() 133 | 134 | // End. 135 | -------------------------------------------------------------------------------- /examples/intertechno/README.md: -------------------------------------------------------------------------------- 1 | # Example using Intertechno Protocols 2 | 3 | The RF devices from Intertechno use two different protocols 4 | so this example also shows how to register multiple protocols. 5 | 6 | Both protocols are described at some places in projects and are obviously also used by other brands. 7 | 8 | 9 | ## Intertechno 1 `it1` 10 | 11 | The older and simpler protocol uses a fixed length of 1+12 codes done with a 2 pulse start code and 4 pulse data codes. 12 | 13 | Setting the codes is done by specifying the unit through the first 5 codes and the ID in the following 5 codes. 14 | 2 codes can be used for the data and is often used for ON/OFF protocols using the last code. 15 | 16 | By the limited number of codes that can be configured on the devices 17 | there is a high risk that another installation in the neighborhood can interfere. 18 | 19 | ### Examples 20 | 21 | ```TXT 22 | [it1 B000110000000] unit: 4, key: 1 on 23 | [it1 B100010000001] unit: 1, key: 1 off 24 | [it1 B010010000001] unit: 2, key: 1 off 25 | [it1 B001010000001] unit: 3, key: 1 off 26 | [it1 B000110000001] unit: 4, key: 1 off 27 | ``` 28 | 29 | There are manual senders with Units 1-4 and others with units 0-15 so the actual bits may need to be interpreted differently. 30 | Just record the sequence of the key you press and see. 31 | 32 | 33 | ### Timings 34 | 35 | My observation is that the durations of the codes vary a lot during a sequence. 36 | 37 | Example: 38 | 39 | [it1 B001010000001] 40 | 41 | /* B */ 433, 13291, 42 | /* 0 */ 446, 1267, 1304, 419, 43 | /* 0 */ 439, 1276, 1299, 422, 44 | /* 1 */ 434, 1280, 437, 1281, 45 | /* 0 */ 436, 1281, 1293, 426, 46 | /* 1 */ 430, 1289, 427, 1287, 47 | /* 0 */ 429, 1291, 1283, 432, 48 | /* 0 */ 426, 1291, 1283, 441, 49 | /* 0 */ 416, 1298, 1277, 442, 50 | /* 0 */ 417, 1301, 1273, 447, 51 | /* 0 */ 413, 1301, 1271, 449, 52 | /* 0 */ 410, 1308, 1266, 459, 53 | /* 0 */ 399, 1316, 399, 1316, 54 | 55 | * The short durations vary from 399 to 459 (429) +/- 7% 56 | * The long durations vary from 1266 to 1316 (1291) +/- 2 % 57 | * The ratio is about 1:3 and the long impulse for the start code is 1:31. 58 | 59 | These timing rations resemble very much the protocol used in encoders like SC5272 60 | and when inspecting the timings it seam that often the first sequence in not properly started. 61 | 62 | Manual senders repeat as long as the button is pressed. When sent per library 4 codes in a row seems to fit. 63 | 64 | 65 | ## Intertechno 2 `it2` 66 | 67 | The newer and longer code supporting 32 and 36 data bits gives much more space for units but also takes longer for transmitting a single sequence. 68 | 69 | The devices have a build in fixed id using 26 codes. 70 | 71 | It uses a Manchester encoding scheme 72 | with start and stop sequences and a special behavior regarding the dimming of lights. 73 | 74 | The receiving units like switches and dimmers do have a learning mode and can accept commands from multiple sender IDs. 75 | 76 | ### Examples 77 | 78 | // ITT-1500, no switch, 3 button pairs and one button "all off" 79 | [it2 s_##__##__#__####____##__#__#____x] key 1 on 80 | [it2 s_##__##__#__####____##__#_______x] key 1 off 81 | [it2 s_##__##__#__####____##__#______#x] key 2 off 82 | [it2 s_##__##__#__####____##__#_#_____x] all off 83 | 84 | // ITLS-16 with switch for 4 units 85 | [it2 s_##___#____#_#__###_____#__#____x] unit:1, key 1 on 86 | [it2 s_##___#____#_#__###_____#_______x] unit:1, key 1 off 87 | [it2 s_##___#____#_#__###_____#__#_#__x] unit:2, key:1 on 88 | [it2 s_##___#____#_#__###_____#____#__x] unit:2, key:1 off 89 | 90 | 91 | ### ON/OFF Protocol Details 92 | 93 | The ON/OFF protocol is using a sequence of 34 codes: 94 | 95 | * Starting code of type 's'. 96 | * 26 data codes that represent the unique 26 bit id of the sender. 97 | * 1 data code used as a flag to address all receivers, any button. 98 | * 1 data code to switch the device on ('#') or off ('_') 99 | * 4 data codes specifying the pressed button / unit. 100 | * 1 Terminating code of type 'x'. 101 | 102 | 103 | ### Dimming Protocol Details 104 | 105 | In case of the dimming devices a specific dimming value can be set directly using a 38 code sequence. 106 | 107 | This code uses a 'D' code (shorter than the others) instead of on/off to indicate the 108 | transmission of a dim command. Before the terminating code 4 additional data codes are sent to 109 | specify the dim level: 110 | 111 | * Starting code of type 's'. 112 | * 26 data codes that represent the unique 26 bit id of the sender. 113 | * 1 data code used as a flag to address all receivers, any button. 114 | * 1 data code 'D' 115 | * 4 data codes specifying the pressed button / unit. 116 | * 4 data codes specifying the dim level. 117 | * 1 Terminating code of type 'x'. 118 | 119 | 120 | ## loading the protocols 121 | 122 | To activate the intertechno RF protocols you have to load them into the signal parser: 123 | 124 | signalParser.load(&RFCodes::it1); // loading Intertechno 1 125 | signalParser.load(&RFCodes::it2); // loading Intertechno 2 126 | 127 | 128 | ## See also 129 | 130 | * 131 | 132 | Descriptions and projects found on the internet: 133 | 134 | * (it1) 135 | * (it1) 136 | 137 | * (it2) 138 | * (it2) 139 | 140 | * 141 | 142 | -------------------------------------------------------------------------------- /examples/intertechno/intertechno.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * @file intertechno.ino 3 | * 4 | * @author Matthias Hertel (https://www.mathertel.de) 5 | * @copyright Copyright (c) by Matthias Hertel, https://www.mathertel.de. 6 | * This work is licensed under a BSD 3-Clause style license, see https://www.mathertel.de/License.aspx 7 | 8 | * @brief Receive and send codes using intertechno protocols. 9 | * This file is part of the RFCodes library that implements receiving an sending RF and IR protocols. 10 | * 11 | * This example shows how to receive and send codes from the intertechno devices. 12 | * The 2 different protocols used by this vendor can be decoded with the same receiver 13 | * by registering the 2 protocols. 14 | * 15 | * Some codes are defined in this sketch and can be sent out. 16 | * 17 | * Wiring (ESP8266): 18 | * * a receiver can be attached with data to pin D5. 19 | * * a transmitter can be attached with data to pin D6. 20 | * 21 | * Use the Serial Monitor to see the received codes and send the predefined codes. 22 | */ 23 | 24 | #include 25 | #include 26 | 27 | bool showRaw = false; 28 | 29 | SignalParser sig; 30 | SignalCollector col; 31 | 32 | // ===== Cresta protocol decoding ===== 33 | 34 | // This function can be used to decode the Cresta Manchester protocol. 35 | // See /docs/cresta_potocol.md for further details. 36 | 37 | void cresta_decode(const char *p) 38 | { 39 | uint8_t cresta_data[10]; // our device emits 10+2 data bytes, we only read the 10 and ignore the checksum 40 | int cresta_cnt = 0; // number of received bytes 41 | 42 | uint8_t cresta_byte = 0; // currenty byte value fom the stream 43 | uint8_t cresta_bits = 0; // next bit to receive. 0..7, 8 is the 0-bit used inbetween data baytes 44 | 45 | // simulate shifting in bits from header : 10101 46 | cresta_byte = 0x15; 47 | cresta_bits = 5; 48 | bool bit = 1; // last bit was HIGH 49 | 50 | while (*p) { 51 | if (*p == 'l') { 52 | bit = 1 - bit; 53 | } 54 | p++; 55 | 56 | if (cresta_bits < 8) { 57 | // shift bit into data byte 58 | cresta_byte |= bit << cresta_bits; 59 | cresta_bits++; 60 | } else { 61 | // shift completed byte into buffer 62 | // bit must be 0, not verified 63 | cresta_byte = cresta_byte ^ (cresta_byte << 1); // decode 64 | cresta_data[cresta_cnt++] = cresta_byte; 65 | // reset for next byte 66 | cresta_bits = 0; 67 | cresta_byte = 0; 68 | } 69 | } // while 70 | 71 | // print received data 72 | Serial.print("data:"); 73 | for (int i = 0; i < 7; i++) { 74 | Serial.printf("%02x ", cresta_data[i]); 75 | } // for 76 | Serial.println(); 77 | 78 | if (cresta_data[0] != 0x9f) { 79 | Serial.println("Bad data."); 80 | } else { 81 | // temperature 82 | int temp = 100 * (cresta_data[5] & 0x0f) + 10 * (cresta_data[4] >> 4) + (cresta_data[4] & 0x0f); 83 | Serial.printf(" temp: %d.%d °C\n", temp / 10, temp % 10); 84 | 85 | // humidity 86 | int hum = 10 * (cresta_data[6] >> 4) + (cresta_data[6] & 0x0f); 87 | Serial.printf(" hum : %d %%\n", hum); 88 | } 89 | 90 | } // cresta_decode 91 | 92 | 93 | // This function will be called when a complete protcol was received. 94 | void receiveCode(const char *proto) 95 | { 96 | SignalParser::CodeTime lastProbes[120 + 1]; // dividable by 8 is preferred. 97 | Serial.printf("received [%s]\n", proto); 98 | 99 | // analysing supporting callback 100 | if (showRaw) { 101 | col.getBufferData(lastProbes, 120 + 1); 102 | 103 | // dump probes 104 | SignalParser::CodeTime *p = lastProbes; 105 | int len = 0; 106 | while (len < 121 && *p) { 107 | if (len % 8 == 0) { 108 | Serial.printf("%3d: %5u,", len, *p); 109 | } else if (len % 8 == 7) { 110 | Serial.printf(" %5u,\n", *p); 111 | } else { 112 | Serial.printf(" %5u,", *p); 113 | } 114 | p++; 115 | len++; 116 | } // while 117 | Serial.println(); 118 | } // if 119 | 120 | if (strncmp(proto, "cw ", 3) == 0) { 121 | cresta_decode(proto + 4); 122 | } 123 | } // receiveCode() 124 | 125 | 126 | void setup() 127 | { 128 | delay(2000); 129 | Serial.begin(115200); 130 | Serial.println("RFCodes Intertechno Example"); 131 | Serial.println(); 132 | 133 | Serial.println( 134 | "Commands: 1-2(Send Code) D(ump Code Table) R(aw toggle)"); 135 | 136 | // load the protocols into the SignalParser 137 | sig.load(&RFCodes::it1); 138 | sig.load(&RFCodes::it2); 139 | sig.load(&RFCodes::sc5); 140 | sig.load(&RFCodes::cw); 141 | 142 | // show all defined protocols 143 | sig.dumpTable(); 144 | 145 | // initialize the SignalCollector library 146 | col.init(&sig, D5, D6); // input at pin D5, output at pin D6 147 | 148 | if (showRaw) 149 | Serial.println("Raw mode is enabled"); 150 | else 151 | Serial.println("Raw mode is disabled"); 152 | 153 | sig.attachCallback(receiveCode); 154 | } // setup() 155 | 156 | 157 | #define C1 "it2 s_##___#____#_#__###_____#__#_#__x" 158 | #define C2 "it2 s_##___#____#_#__###_____#____#__x" 159 | #define C3 "it1 B111101111000" 160 | #define C4 "it1 B111101111001" 161 | 162 | void loop() 163 | { 164 | unsigned long now = millis(); 165 | static unsigned long nextReport = now + 1000; 166 | 167 | if (Serial.available() > 0) { 168 | char cmd = Serial.read(); 169 | if (cmd == '1') { 170 | Serial.println("Sending (C1)..."); 171 | col.send(C1); 172 | 173 | } else if (cmd == '2') { 174 | Serial.println("Sending (C2)..."); 175 | col.send(C2); 176 | 177 | } else if (cmd == '3') { 178 | Serial.println("Sending (C3)..."); 179 | col.send(C3); 180 | 181 | } else if (cmd == '4') { 182 | Serial.println("Sending (C4)..."); 183 | col.send(C3); 184 | 185 | } else if (cmd == 'R') { 186 | showRaw = !showRaw; 187 | if (showRaw) 188 | Serial.println("Raw mode is enabled"); 189 | else 190 | Serial.println("Raw mode is disabled"); 191 | 192 | } else if (cmd == 'D') { 193 | sig.dumpTable(); 194 | 195 | } // if 196 | } // if 197 | 198 | 199 | SignalParser::CodeTime bufcnt = col.getBufferCount(); 200 | if ((now > nextReport) && (bufcnt > 40)) { 201 | Serial.print("buf_cnt:"); 202 | Serial.println(bufcnt); 203 | nextReport = now + 1000; 204 | } 205 | 206 | // process received bytes 207 | col.loop(); 208 | } // loop() 209 | -------------------------------------------------------------------------------- /examples/necIR/necIR.ino: -------------------------------------------------------------------------------- 1 | /* 2 | file: nectIR.ino 3 | This sample is part of the TabRF library showing how to receive and send 4 | Infrared codes. 5 | 6 | This example shows how the nec IR protocol can be decoded with a IR receiver. 7 | 8 | http://www.mathertel.de/Arduino/TabRFlibrary.aspx 9 | 10 | Use the Serial Monitor to see the received codes and send the predefined 11 | codes. 12 | 13 | 05.06.2020 created 14 | */ 15 | 16 | #include 17 | #include 18 | #include 19 | 20 | #include 21 | 22 | 23 | bool showRaw = false; 24 | 25 | SignalParser sig; 26 | SignalCollector tabRF; 27 | 28 | // This function will be called when a complete protcol was received. 29 | void receiveCode(const char *proto) 30 | { 31 | SignalParser::CodeTime lastProbes[152]; // dividable by 8 is preferred. 32 | // remember last code in a local variable 33 | Serial.printf("[%s]\n", proto); 34 | 35 | // analysing supporting callback 36 | if (showRaw && (*proto == '*')) { 37 | int n = atoi(proto + 1) + 1; 38 | tabRF.getBufferData(lastProbes, min(n, 150)); 39 | 40 | // dump probes 41 | SignalParser::CodeTime *p = lastProbes; 42 | int len = 0; 43 | while (len < 150 && *p) { 44 | if (len % 8 == 0) { 45 | Serial.printf("%3d: %5u,", len, *p); 46 | } else if (len % 8 == 7) { 47 | Serial.printf(" %5u,\n", *p); 48 | } else { 49 | Serial.printf(" %5u,", *p); 50 | } 51 | p++; 52 | len++; 53 | } // while 54 | Serial.println(); 55 | } // if 56 | } // receiveCode() 57 | 58 | void setup() 59 | { 60 | delay(2000); 61 | Serial.begin(115200); 62 | Serial.println("necIR Packet Receiver"); 63 | Serial.println(); 64 | 65 | // initialize the tabRF library 66 | tabRF.init(&sig, D7, NO_PIN, 16); // input at pin D7, no output 67 | 68 | 69 | sig.load(&IRCodes::nec); 70 | sig.dumpTable(); 71 | 72 | if (showRaw) 73 | Serial.println("Raw mode is enabled"); 74 | else 75 | Serial.println("Raw mode is disabled"); 76 | 77 | sig.attachCallback(receiveCode); 78 | } // setup() 79 | 80 | 81 | unsigned int maxBufCount = 0; 82 | 83 | void loop() 84 | { 85 | unsigned long now = millis(); 86 | static unsigned long nextReport = now + 1000; 87 | 88 | if (Serial.available() > 0) { 89 | char cmd = Serial.read(); 90 | if (cmd == '1') { 91 | Serial.println("Sending (C1)..."); 92 | 93 | } else if (cmd == '2') { 94 | Serial.println("Sending (C2)..."); 95 | 96 | } else if (cmd == 'T') { 97 | sig.dumpTable(); 98 | if (showRaw) 99 | Serial.println("Raw mode is enabled"); 100 | else 101 | Serial.println("Raw mode is disabled"); 102 | 103 | } else if (cmd == 'R') { 104 | showRaw = !showRaw; 105 | 106 | } // if 107 | } // if 108 | 109 | SignalParser::CodeTime bufcnt = tabRF.getBufferCount(); 110 | if ((now > nextReport) && (bufcnt > 40)) { 111 | Serial.print("buf_cnt:"); 112 | Serial.println(bufcnt); 113 | nextReport = now + 1000; 114 | } 115 | 116 | // process received bytes 117 | tabRF.loop(); 118 | } // loop() 119 | -------------------------------------------------------------------------------- /examples/scanner/README.md: -------------------------------------------------------------------------------- 1 | # RF Scanner Example 2 | 3 | With the RF Scanner you can collect some probes from a specific sender where you don't know the exact timings. 4 | 5 | See [RF Scanner documentation](/docs/scanner.md). 6 | -------------------------------------------------------------------------------- /examples/scanner/scanner.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * @file: scanner.ino 3 | * 4 | * This file is part of the RFCodes library that implements receiving an sending 5 | * RF and IR protocols. 6 | * 7 | * @copyright Copyright (c) by Matthias Hertel, https://www.mathertel.de. 8 | * 9 | * This work is licensed under a BSD 3-Clause style license, 10 | * https://www.mathertel.de/License.aspx. 11 | * 12 | * @brief 13 | * 14 | * This sample is part of the RFCodes library. 15 | * It can be used to collect some probes from a specific sender where you don't know the exact timings. 16 | * 17 | * More info at: http://www.mathertel.de/Arduino/ 18 | * 19 | * * 31.03.2022 created by Matthias Hertel 20 | */ 21 | 22 | #include 23 | 24 | #define BACKSIZE 512 25 | #define RECORDSIZE 512 26 | #define SAMPLESIZE (BACKSIZE + RECORDSIZE) 27 | 28 | static int startPin = D5; 29 | static int receiverPin = D7; 30 | static int ledPin = D4; 31 | 32 | static int interruptPin = 0; 33 | 34 | volatile int status; 35 | int lastStatus; 36 | 37 | #define STATUS_PAUSE 0x00 // do nothing 38 | #define STATUS_WAIT 0x01 // just fill the buffer for 512 39 | #define STATUS_CHECK 0x02 // check for starting condition 40 | #define STATUS_START 0x04 // record 41 | #define STATUS_ISRECORD (STATUS_WAIT | STATUS_CHECK | STATUS_START) 42 | #define STATUS_DONE 0x80 // done 43 | 44 | bool done = false; 45 | 46 | typedef uint16_t CodeTime; 47 | 48 | // last time the interrupt was called. 49 | volatile static unsigned long lastTime; 50 | 51 | 52 | // Ring buffer 53 | // A simple ring buffer is used to decouple interrupt routine. 54 | // Static variables are used to be known in the ISR 55 | static CodeTime *buf88; // allocated memory 56 | static volatile CodeTime *buf88_write; // write pointer 57 | static volatile CodeTime *buf88_read; // read pointer 58 | static CodeTime *buf88_end; // end of buffer+1 pointer for wrapping 59 | static volatile unsigned int buf88_cnt; // number of bytes in buffer 60 | 61 | 62 | // This handler is attached to the change interrupt. 63 | void IRAM_ATTR handleInterrupt() 64 | { 65 | unsigned long now = micros(); 66 | CodeTime t = (CodeTime)(now - lastTime); 67 | lastTime = now; 68 | 69 | if (status & STATUS_ISRECORD) { 70 | digitalWrite(ledPin, !digitalRead(receiverPin)); 71 | 72 | // record using the write pointer, read pointer is not valid. 73 | // count may exceed the buffer size 74 | *buf88_write++ = t; 75 | 76 | // reset pointer to the start when reaching end 77 | if (buf88_write == buf88_end) 78 | buf88_write = buf88; 79 | buf88_cnt++; 80 | 81 | if (status == STATUS_WAIT) { 82 | if (buf88_cnt >= BACKSIZE) { 83 | // start checking condition 84 | status = STATUS_CHECK; 85 | } 86 | 87 | } else if (status == STATUS_CHECK) { 88 | // the last timings will be in the ring buffer when looking backwards. 89 | 90 | // ===== CHANGE STARTING CONDITION HERE: 91 | 92 | if ((t > 8000) && (t < 10000)) { 93 | // if ((t > 80) && (t < 1400)) { 94 | // a long time is detected, possibly a sync code 95 | status = STATUS_START; 96 | buf88_read = buf88_write; 97 | buf88_cnt = 0; 98 | } // if 99 | 100 | } else if (status == STATUS_START) { 101 | // continue recording and count. 102 | 103 | if (buf88_cnt == RECORDSIZE) { 104 | status = STATUS_DONE; 105 | digitalWrite(ledPin, HIGH); // off 106 | } 107 | 108 | } // if 109 | } // if 110 | } // handleInterrupt 111 | 112 | /** dump the data from a table of timings that end with a 0 time. */ 113 | void dumpBuffer(CodeTime *raw, int size) 114 | { 115 | 116 | // dump timing probes 117 | CodeTime *p = raw; 118 | int len = 0; 119 | 120 | if (p < buf88) 121 | p += SAMPLESIZE; 122 | 123 | while (len < size) { 124 | Serial.print(*p); 125 | if (len % 32 != 31) { 126 | Serial.print(','); 127 | } else { 128 | Serial.println(','); 129 | } 130 | p++; 131 | len++; 132 | 133 | // reset pointer to the start when reaching end 134 | if (p == buf88_end) 135 | p = buf88; 136 | } // while 137 | } // dumpBuffer() 138 | 139 | 140 | void setup() 141 | { 142 | delay(3000); 143 | Serial.begin(115200); 144 | 145 | Serial.println("RF Scanner..."); 146 | 147 | status = STATUS_PAUSE; 148 | 149 | // setup ring buffer 150 | buf88 = (CodeTime *)malloc(SAMPLESIZE * sizeof(CodeTime)); // allocated memory 151 | buf88_end = buf88 + SAMPLESIZE; // end of buffer+1 pointer for wrapping 152 | buf88_write = buf88; 153 | buf88_read = buf88; 154 | buf88_cnt = 0; 155 | 156 | // setup interrupt routine 157 | interruptPin = digitalPinToInterrupt(receiverPin); 158 | attachInterrupt(interruptPin, handleInterrupt, CHANGE); 159 | 160 | pinMode(ledPin, OUTPUT); 161 | pinMode(startPin, INPUT_PULLUP); 162 | pinMode(receiverPin, INPUT_PULLUP); 163 | 164 | // show activity. 165 | for (int i = 0; i < 4; i++) { 166 | digitalWrite(ledPin, LOW); 167 | delay(200); 168 | digitalWrite(ledPin, HIGH); 169 | delay(400); 170 | } 171 | } // setup() 172 | 173 | 174 | void loop() 175 | { 176 | if (status == STATUS_PAUSE) { 177 | lastStatus = status; 178 | if (digitalRead(startPin) == LOW) { 179 | Serial.print("press..."); 180 | while (digitalRead(startPin) == HIGH) { 181 | delay(20); 182 | } 183 | Serial.println("done."); 184 | delay(200); 185 | lastTime = micros(); 186 | status = STATUS_WAIT; // Now interrupt will start recording any timings 187 | } 188 | 189 | } else if (status == STATUS_WAIT) { 190 | if (lastStatus != status) { 191 | Serial.println("wait..."); 192 | lastStatus = status; 193 | } 194 | 195 | } else if (status == STATUS_CHECK) { 196 | if (lastStatus != status) { 197 | Serial.println("check..."); 198 | lastStatus = status; 199 | } 200 | if (digitalRead(startPin) == LOW) { 201 | status = STATUS_START; 202 | buf88_read = buf88_write; 203 | buf88_cnt = 0; 204 | } 205 | 206 | } else if (status == STATUS_START) { 207 | if (lastStatus != status) { 208 | Serial.print("collect..."); 209 | lastStatus = status; 210 | } 211 | Serial.print('.'); // show some progress 212 | delay(30); 213 | 214 | } else if (status == STATUS_DONE) { 215 | // report timings 216 | Serial.println("done."); 217 | 218 | // print buffer before read 219 | 220 | dumpBuffer((CodeTime *)buf88_read - BACKSIZE, BACKSIZE); 221 | Serial.println("---"); 222 | dumpBuffer((CodeTime *)buf88_read, RECORDSIZE); 223 | Serial.println(); 224 | 225 | // done. 226 | status = STATUS_PAUSE; 227 | } 228 | } // loop() 229 | 230 | // End. 231 | -------------------------------------------------------------------------------- /examples/testcodes/testcodes.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * @file: testcodes.ino 3 | * 4 | * This file is part of the RFCodes library that implements receiving an sending 5 | * RF and IR protocols. 6 | * 7 | * @copyright Copyright (c) by Matthias Hertel, https://www.mathertel.de. 8 | * 9 | * This work is licensed under a BSD 3-Clause style license, 10 | * https://www.mathertel.de/License.aspx. 11 | * 12 | * @brief 13 | * 14 | * This sample is part of the RFCodes library. 15 | * It demonstrates testing the code recognition in the SignalParser class 16 | * by injecting timings and verifying that codes could be detected. 17 | * 18 | * Use the Serial Monitor to see the received codes and send the predefined codes. 19 | * 20 | * More info at: http://www.mathertel.de/Arduino/ 21 | * 22 | * * 31.03.2032 created by Matthias Hertel 23 | */ 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | #include 30 | 31 | bool showRaw = false; 32 | 33 | SignalParser sig; 34 | SignalCollector col; 35 | 36 | // this is the test data with some annotations: 37 | SignalParser::CodeTime testdata[] = { 38 | /* noise */ 13462, 70, 1433, 171, 232, 98, 1239, 337, 318, 52, 469, 182, 340, 432, 2860, 269, 4056, 108, 3290, 79, 2904, 260, 2870, 158, 7818, 75, 2047, 183, 520, 152, 161, 115, 114, 329, 340, 95, 4309, 153, 5210, 28, 2966, 273, 4856, 75, 955, 289, 333, 254, 433, 65, 129, 261, 609, 39 | 40 | 520, 328, 1380, 1209, 513, 346, 1386, 1168, 534, 346, 1365, 1213, 507, 339, 1375, 346, 1372, 359, 13353, 392, 1317, 262, 48, 128, 95, 722, 466, 375, 1337, 1242, 492, 375, 1334, 388, 1350, 363, 1331, 1244, 478, 383, 1329, 389, 1329, 388, 1325, 1249, 477, 382, 1364, 1213, 522, 338, 1338, 1237, 496, 361, 1341, 1233, 493, 369, 1331, 1252, 465, 388, 1331, 1244, 469, 392, 1327, 390, 1322, 41 | 42 | // submitting it1 3 times: 43 | 397, 13320, 44 | 427, 1288, 1280, 446, 410, 1306, 1269, 451, 403, 1313, 409, 1308, 407, 1308, 1270, 452, 406, 1306, 412, 1309, 409, 1303, 1273, 446, 411, 1308, 1278, 442, 405, 1308, 1263, 456, 409, 1308, 1272, 448, 409, 1306, 1268, 464, 397, 1309, 1271, 448, 404, 1311, 407, 1310, 45 | // find: [it1 B001010000001] 46 | 397, 13320, 47 | // find: [sc5 ff0f0ffffff0S] 48 | 427, 1288, 1280, 446, 410, 1306, 1269, 451, 403, 1313, 409, 1308, 407, 1308, 1270, 452, 406, 1306, 412, 1309, 409, 1303, 1273, 446, 411, 1308, 1278, 442, 405, 1308, 1263, 456, 409, 1308, 1272, 448, 409, 1306, 1268, 464, 397, 1309, 1271, 448, 404, 1311, 407, 1310, 49 | // find: [it1 B001010000001] 50 | 408, 13334, 51 | // find: [sc5 ff0f0ffffff0S] 52 | 427, 1288, 1280, 446, 410, 1306, 1269, 451, 403, 1313, 409, 1308, 407, 1308, 1270, 452, 406, 1306, 412, 1309, 409, 1303, 1273, 446, 411, 1308, 1278, 442, 405, 1308, 1263, 456, 409, 1308, 1272, 448, 409, 1306, 1268, 464, 397, 1309, 1271, 448, 404, 1311, 407, 1310, 53 | // find: [it1 B001010000001] 54 | 55 | /* noise */ 445, 80, 1296, 128, 56 | 57 | 443, 13281, 58 | 460, 1257, 1313, 403, 464, 1254, 1310, 412, 450, 1263, 1311, 410, 449, 1265, 449, 1269, 452, 1265, 454, 1265, 446, 1267, 1312, 408, 452, 1264, 1310, 409, 451, 1265, 1311, 410, 448, 1267, 1305, 417, 443, 1271, 1306, 411, 445, 1274, 1308, 410, 443, 1276, 442, 1271, 59 | // find: [it1 B000110000001] 60 | 61 | /* noise */ 70, 232, 62 | 63 | // ideal, valid sc5: [sc5 0001100000011S] 64 | 350, 1080, 350, 1080, 350, 1080, 350, 1080, 350, 1080, 350, 1080, 1080, 350, 1080, 350, 65 | 350, 1080, 350, 1080, 350, 1080, 350, 1080, 350, 1080, 350, 1080, 350, 1080, 350, 1080, 66 | 350, 1080, 350, 1080, 350, 1080, 350, 1080, 350, 1080, 350, 1080, 1080, 350, 1080, 350, 67 | 350, 10912, 68 | // find: [ 69 | 70 | /* noise */ 70, 1433, 171, 232, 98, 1239, 337, 318, 52, 955, 289, 333, 254, 433, 65, 71 | 72 | 327, 2760, 326, 246, 324, 1316, 332, 1312, 320, 249, 332, 1307, 323, 257, 328, 237, 329, 1318, 327, 239, 323, 1324, 323, 1309, 336, 246, 327, 1304, 336, 256, 313, 243, 327, 1331, 324, 246, 323, 1322, 322, 1314, 324, 254, 326, 242, 324, 1317, 330, 245, 323, 1323, 73 | 322, 1308, 325, 252, 323, 1315, 324, 258, 316, 1316, 328, 253, 320, 1313, 325, 262, 326, 244, 329, 1316, 325, 242, 330, 1322, 322, 240, 325, 1320, 326, 244, 325, 1328, 316, 1311, 330, 247, 326, 1310, 324, 257, 319, 251, 321, 1326, 318, 254, 315, 1340, 319, 1312, 74 | 319, 265, 320, 243, 320, 1321, 325, 246, 323, 1321, 325, 247, 317, 1329, 314, 254, 321, 1320, 324, 249, 316, 1329, 318, 249, 319, 1328, 320, 249, 314, 1318, 321, 8387, 75 | // find: [it2 s_##__##__#__####____##__#_______x] 76 | 77 | /* noise */ 70, 1433, 171, 232, 98, 1239, 337, 318, 52, 955, 289, 333, 254, 433, 65, 78 | 79 | 305, 80 | 327, 2760, 326, 246, 324, 1316, 332, 1312, 320, 249, 332, 1307, 323, 257, 328, 237, 329, 1318, 327, 239, 323, 1324, 323, 1309, 336, 246, 327, 1304, 336, 256, 313, 243, 327, 1331, 324, 246, 323, 1322, 322, 1314, 324, 254, 326, 242, 324, 1317, 330, 245, 323, 1323, 81 | 322, 1308, 325, 252, 323, 1315, 324, 258, 316, 1316, 328, 253, 320, 1313, 325, 262, 326, 244, 329, 1316, 325, 242, 330, 1322, 322, 240, 325, 1320, 326, 244, 325, 1328, 316, 1311, 330, 247, 326, 1310, 324, 257, 319, 251, 321, 1326, 318, 254, 315, 1340, 319, 1312, 82 | 319, 265, 320, 243, 320, 1321, 325, 246, 323, 1321, 325, 247, 317, 1329, 314, 254, 321, 1320, 324, 249, 316, 1329, 318, 249, 319, 1328, 314, 1318, 320, 249, 321, 8387, 83 | // find: [it2 s_##__##__#__####____##__#______#x] 84 | 85 | /* noise */ 589, 396, 595, 377, 1077, 878, 1086, 375, 55568, 86 | 87 | 1044, 918, 1021, 940, 1003, 88 | 462, 509, 470, 492, 980, 486, 487, 972, 987, 477, 506, 456, 525, 945, 1007, 456, 533, 436, 533, 440, 535, 448, 531, 925, 1023, 935, 539, 438, 531, 440, 1028, 931, 1014, 447, 535, 930, 1028, 924, 1027, 437, 548, 441, 526, 924, 1038, 431, 536, 439, 535, 447, 538, 89 | 926, 1019, 941, 1016, 439, 537, 445, 533, 927, 530, 446, 536, 438, 536, 443, 533, 451, 535, 440, 1015, 934, 1020, 930, 537, 443, 1028, 436, 530, 450, 532, 442, 535, 437, 537, 450, 530, 444, 535, 90 | // find: [cw Hsslsllssllsssslllsslllsllllssllsssllllsslsssssllllslssssss] 91 | 92 | /* noise */ 941, 1016, 439, 537, 93 | 94 | 0}; 95 | 96 | const char *testresult[] = { 97 | "it1 B001010000001", 98 | "sc5 ff0f0ffffff0S", 99 | "it1 B001010000001", 100 | "sc5 ff0f0ffffff0S", 101 | "it1 B001010000001", 102 | "it1 B000110000001", 103 | "sc5 000100000001S", 104 | "it2 s_##__##__#__####____##__#_______x", 105 | "it2 s_##__##__#__####____##__#______#x", 106 | "cw Hsslsllssllsssslllsslllsllllssllsssllllsslsssssllllslssssss", 107 | ""}; 108 | 109 | int nextResult; 110 | 111 | 112 | 113 | // This function will be called when a complete protcol was received. 114 | void receiveCode(const char *proto) 115 | { 116 | const char *expect = testresult[nextResult]; 117 | if ((*expect) && strcmp(expect, proto) == 0) { 118 | Serial.printf("\n ok : [%s]\n", proto); 119 | nextResult++; 120 | } else { 121 | Serial.printf("\n BAD: [%s]\n", proto); 122 | Serial.printf(" exp: [%s]\n", expect); 123 | } 124 | } // receiveCode() 125 | 126 | 127 | void setup() 128 | { 129 | delay(3000); // wait for serial monitor to be ready. 130 | Serial.begin(115200); 131 | Serial.println("RFCodes code recognition test..."); 132 | Serial.println(); 133 | 134 | Serial.println( 135 | "Commands: T(est data)"); 136 | 137 | // initialize the SignalCollecor library without hardware 138 | col.init(&sig, NO_PIN, NO_PIN); // no pins. for testing purpose. 139 | 140 | // load all codes that may be detected in the test data 141 | sig.load(&RFCodes::it1); 142 | sig.load(&RFCodes::it2); 143 | sig.load(&RFCodes::sc5); 144 | sig.load(&RFCodes::cw); 145 | 146 | // show loaded protocols 147 | sig.dumpTable(); 148 | 149 | sig.attachCallback(receiveCode); 150 | } // setup() 151 | 152 | 153 | void loop() 154 | { 155 | unsigned long now = millis(); 156 | static unsigned long nextReport = now + 1000; 157 | 158 | if (Serial.available() > 0) { 159 | char cmd = Serial.read(); 160 | if (cmd == '1') { 161 | 162 | } else if (cmd == 'T') { 163 | Serial.println("Sending Test Data..."); 164 | nextResult = 0; 165 | SignalParser::CodeTime *d = testdata; 166 | while (*d) { 167 | Serial.print(*d); 168 | Serial.print(','); 169 | col.injectTiming(*d++); // add timing 170 | col.loop(); // let it parse 171 | } // while 172 | 173 | } // if 174 | } // if 175 | 176 | // process received bytes 177 | col.loop(); 178 | } // loop() 179 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=RFCodes 2 | version=0.9.3 3 | author=Matthias Hertel 4 | maintainer=Matthias Hertel, https://www.mathertel.de 5 | sentence=RF 433 and IR signal encode and decode library. 6 | paragraph=This library that can be adapted to different protocols by specifying the timings of the codes. Can be used with 433 MHz receivers and senders. 7 | category=Device Control 8 | url=https://github.com/mathertel/rfcodes 9 | architectures=esp8266,esp32 10 | includes=RFCodes.h 11 | -------------------------------------------------------------------------------- /src/RFCodes.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file: RFCodes.h 3 | * This file is part of the RFCodes library that implements receiving an sending 4 | * RF and IR protocols. 5 | * 6 | * @brief 7 | * Include this file to enable using the library and it's protocol definitions. 8 | * 9 | * This work is licensed under a BSD 3-Clause style license, 10 | * https://www.mathertel.de/License.aspx. 11 | * 12 | * More information on http://www.mathertel.de/Arduino 13 | * 14 | * Changelog: 15 | * * 29.04.2018 created by Matthias Hertel 16 | * * 06.08.2018 const char send, allow for sending only. 17 | */ 18 | 19 | #include 20 | #include 21 | 22 | #include 23 | -------------------------------------------------------------------------------- /src/SignalCollector.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file SignalCollector.cpp 3 | * @brief 4 | * This file is part of the RFCodes library that implements receiving an sending 5 | * RF and IR protocols. 6 | * 7 | * This work is licensed under a BSD 3-Clause style license, 8 | * https://www.mathertel.de/License.aspx. 9 | * 10 | * More information on http://www.mathertel.de/Arduino 11 | * 12 | */ 13 | 14 | #include "SignalCollector.h" 15 | 16 | // ====== SignalCollector implemenation ===== 17 | 18 | /** 19 | * Initialize the receiving and sending modes and activate the IO pins 20 | * @param recvPin The IO pin to be used for receiving. Set to -1 to disable 21 | * receiving mode. 22 | * @param sendPin The IO pin to be used for sending. Set to -1 to disable 23 | * sending mode. 24 | */ 25 | void SignalCollector::init(SignalParser *sig, int recvPin, int sendPin, int trim) 26 | { 27 | TRACE_MSG("Initalizing tabRF hardware\n"); 28 | 29 | _sig = sig; 30 | 31 | // Receiving mode 32 | _recvPin = recvPin; 33 | if (recvPin >= 0) { 34 | // initialize interrupt service routine 35 | // See https://www.arduino.cc/reference/en/language/functions/external-interrupts/attachinterrupt/ 36 | _irNumber = digitalPinToInterrupt(recvPin); // will return -1 on wrong pin number. 37 | if (_irNumber < 0) { 38 | TRACE_MSG("Error: Receiving pin cannot be used"); 39 | _recvPin = -1; 40 | 41 | } else { 42 | pinMode(_recvPin, INPUT); 43 | attachInterrupt(_irNumber, signal_change_handler, CHANGE); 44 | } // if 45 | } 46 | 47 | // Sending mode 48 | _sendPin = sendPin; 49 | if (sendPin >= 0) { 50 | // initialize sending mode 51 | pinMode(_sendPin, OUTPUT); 52 | digitalWrite(_sendPin, LOW); 53 | } 54 | } // init() 55 | 56 | 57 | void strcpyProtname(char *target, const char *signal) 58 | { 59 | char *p = target; 60 | char *s = (char *)signal; 61 | int len = PROTNAME_LEN - 1; 62 | while (len && *s && (*s != ' ')) { 63 | *p++ = *s++; 64 | len--; 65 | } 66 | *p = NUL; 67 | } // strcpyProtname 68 | 69 | 70 | void SignalCollector::send(const char *signal) 71 | { 72 | SignalParser::CodeTime timings[256]; 73 | int level = LOW; // LOW level before starting. 74 | // INFO_MSG("send(%s)", signal); 75 | 76 | char protname[PROTNAME_LEN]; 77 | strcpyProtname(protname, (char *)signal); 78 | // INFO_MSG("send prot %s", protname); 79 | 80 | int repeat = _sig->getSendRepeat(protname); 81 | // INFO_MSG("send repeat %d", repeat); 82 | 83 | if ((repeat) && (_sendPin >= 0)) { 84 | // get timings of the code 85 | _sig->compose(signal, timings, sizeof(timings) / sizeof(SignalParser::CodeTime)); 86 | // dumpTimings(timings); 87 | 88 | while (repeat) { 89 | SignalParser::CodeTime *t = timings; 90 | 91 | if (_recvPin >= 0) { 92 | noInterrupts(); 93 | } 94 | 95 | while (*t) { 96 | level = !level; 97 | digitalWrite(_sendPin, level); 98 | delayMicroseconds(*t++); 99 | } // while 100 | 101 | if (_recvPin >= 0) { 102 | interrupts(); 103 | } 104 | 105 | repeat--; 106 | } // while 107 | 108 | // never leave active after sending. 109 | digitalWrite(_sendPin, LOW); 110 | 111 | } // if 112 | } // send() 113 | 114 | 115 | // process bytes from ring buffer 116 | void SignalCollector::loop() 117 | { 118 | while (SignalCollector::buf88_cnt > 0) { 119 | SignalParser::CodeTime t = *SignalCollector::buf88_read++; 120 | SignalCollector::buf88_cnt--; 121 | 122 | _sig->parse(t); 123 | 124 | // reset pointer to the start when reaching end 125 | if (SignalCollector::buf88_read == SignalCollector::buf88_end) 126 | SignalCollector::buf88_read = SignalCollector::buf88; 127 | yield(); 128 | } // while 129 | } // loop 130 | 131 | 132 | // ===== Insights and Debugging Helpers ===== 133 | 134 | 135 | /** Return the last received timings from the ring-buffer. */ 136 | void SignalCollector::getBufferData(SignalParser::CodeTime *buffer, int len) 137 | { 138 | len--; // keep space for final '0'; 139 | if (len > SC_BUFFERSIZE) 140 | len = SC_BUFFERSIZE; 141 | 142 | SignalParser::CodeTime *p = (SignalParser::CodeTime *)buf88_read - len; 143 | if (p < buf88) 144 | p += SC_BUFFERSIZE; 145 | 146 | // copy timings to buffer 147 | while (len) { 148 | *buffer++ = *p++; 149 | 150 | // reset pointer to the start when reaching end 151 | if (p == SignalCollector::buf88_end) 152 | p = SignalCollector::buf88; 153 | len--; 154 | } // if 155 | *buffer = 0; 156 | }; // getBufferData() 157 | 158 | 159 | /** dump the data from a table of timings that end with a 0 time. */ 160 | void SignalCollector::dumpTimings(SignalParser::CodeTime *raw) 161 | { 162 | // dump probes 163 | SignalParser::CodeTime *p = raw; 164 | int len = 0; 165 | while (p && *p) { 166 | if (len % 8 == 0) { 167 | RAW_MSG("%3d: %5u,", len, *p); 168 | } else if (len % 8 == 7) { 169 | RAW_MSG(" %5u,\n", *p); 170 | } else { 171 | RAW_MSG(" %5u,", *p); 172 | } 173 | p++; 174 | len++; 175 | } // while 176 | RAW_MSG("\n"); 177 | } // dumpTimings 178 | 179 | 180 | // static class stuff, to be accessible to the Interrupt service routines. 181 | 182 | // This handler is attached to the change interrupt. 183 | void IRAM_ATTR SignalCollector::signal_change_handler() 184 | { 185 | unsigned long now = micros(); 186 | SignalParser::CodeTime t = (SignalParser::CodeTime)(now - SignalCollector::lastTime); 187 | 188 | // // adjust the timing with the trim factor. 189 | // int level = digitalRead(_recvPin); 190 | // if (level) { 191 | // t += SignalCollector::_trim; // end of low 192 | // } else { 193 | // t -= SignalCollector::_trim; // end of high 194 | // } 195 | 196 | // write to ring buffer 197 | if (SignalCollector::buf88_cnt < SC_BUFFERSIZE) { 198 | *SignalCollector::ringWrite++ = t; 199 | buf88_cnt++; 200 | 201 | // reset pointer to the start when reaching end 202 | if (SignalCollector::ringWrite == SignalCollector::buf88_end) 203 | SignalCollector::ringWrite = SignalCollector::buf88; 204 | } // if 205 | 206 | lastTime = now; // micros(); 207 | } // signal_change_handler() 208 | 209 | 210 | // Inject a test timing into the ring buffer. 211 | void SignalCollector::injectTiming(SignalParser::CodeTime t) 212 | { 213 | // write to ring buffer 214 | if (SignalCollector::buf88_cnt < SC_BUFFERSIZE) { 215 | *SignalCollector::ringWrite++ = t; 216 | buf88_cnt++; 217 | 218 | // reset pointer to the start when reaching end 219 | if (SignalCollector::ringWrite == SignalCollector::buf88_end) 220 | SignalCollector::ringWrite = SignalCollector::buf88; 221 | } // if 222 | 223 | SignalCollector::lastTime = micros(); 224 | } // injectTiming() 225 | 226 | 227 | int SignalCollector::_recvPin = -1; 228 | 229 | // allocate and initialize the static class members. 230 | 231 | unsigned long SignalCollector::lastTime = 0; 232 | 233 | // allocate memory for ring buffer 234 | SignalParser::CodeTime *SignalCollector::buf88 = (SignalParser::CodeTime *)malloc(SC_BUFFERSIZE * sizeof(SignalParser::CodeTime)); 235 | 236 | // write pointer starts at start 237 | volatile SignalParser::CodeTime *SignalCollector::ringWrite = SignalCollector::buf88; 238 | 239 | // read pointer starts at start 240 | volatile SignalParser::CodeTime *SignalCollector::buf88_read = SignalCollector::buf88; 241 | 242 | // end of buffer + 1 pointer for wrapping 243 | SignalParser::CodeTime *SignalCollector::buf88_end = SignalCollector::buf88 + SC_BUFFERSIZE; 244 | 245 | volatile unsigned int SignalCollector::buf88_cnt = 0; // number of bytes in buffer 246 | 247 | // End. 248 | -------------------------------------------------------------------------------- /src/SignalCollector.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file: SignalCollector.h 3 | * @brief 4 | * This file is part of the RFCodes library that implements receiving an sending 5 | * RF and IR protocols. 6 | * 7 | * This work is licensed under a BSD 3-Clause style license, 8 | * https://www.mathertel.de/License.aspx. 9 | * 10 | * More information on http://www.mathertel.de/Arduino 11 | * 12 | * Changelog: 13 | * * 29.04.2018 created by Matthias Hertel 14 | * * 06.08.2018 const char send, allow for sending only. 15 | */ 16 | 17 | #ifndef TabRF_H_ 18 | #define TabRF_H_ 19 | 20 | #include 21 | 22 | #include "debugout.h" 23 | #include "SignalParser.h" 24 | 25 | #define NUL '\0' 26 | #define null 0 27 | 28 | #define NO_PIN (-1) 29 | 30 | #define TabRF_ERR(...) Serial.printf("Error: " __VA_ARGS__) 31 | 32 | #define SC_BUFFERSIZE 512 33 | 34 | // main class for the TabRF library 35 | class SignalCollector 36 | { 37 | public: 38 | /** 39 | * @brief Initialize receiving and sending pins and register 40 | * interrupt service routine. 41 | * @param recvPin 42 | * @param sendPin 43 | */ 44 | void init(SignalParser *sig, int recvPin, int sendPin, int trim = 0); 45 | 46 | // send out a new code 47 | void send(const char *code); 48 | 49 | void loop(); 50 | 51 | // ===== Insights and Debugging Helpers ===== 52 | 53 | // Return the number of buffered data in the ring buffer. 54 | // This may be used to find the ring buffer is too small or loop() needs to be 55 | // called more often. 56 | uint32_t getBufferCount() 57 | { 58 | return (buf88_cnt); 59 | }; 60 | 61 | /** Return the last received timings from the ring-buffer. 62 | * When the length is larger than the ring buffer the length is reduced. 63 | * There is always a 0 entry in the last timings. 64 | * @param buffer target timing buffer 65 | * @param len length of buffer 66 | */ 67 | void getBufferData(SignalParser::CodeTime *buffer, int len); 68 | 69 | /** dump the data from a table of timings that end with a 0 time. 70 | * @param raw pointer to raw timings data. 71 | */ 72 | void dumpTimings(SignalParser::CodeTime *raw); 73 | 74 | // Inject a test timing into the ring buffer. 75 | void injectTiming(SignalParser::CodeTime t); 76 | 77 | 78 | private: 79 | // Ring buffer 80 | // A simple ring buffer is used to decouple interrupt routine. 81 | // Static variables are used to be known in the ISR 82 | static SignalParser::CodeTime *buf88; // allocated memory 83 | static volatile SignalParser::CodeTime *ringWrite; // write pointer 84 | static volatile SignalParser::CodeTime *buf88_read; // read pointer 85 | static SignalParser::CodeTime *buf88_end; // end of buffer+1 pointer for wrapping 86 | static volatile unsigned int buf88_cnt; // number of bytes in buffer 87 | 88 | static unsigned long lastTime; // last time the interrupt was called. 89 | 90 | SignalParser *_sig; 91 | 92 | 93 | /** hardware related settings */ 94 | static int _recvPin; // IO Pin number for receiving signals. static, to be 95 | // known in the ISR 96 | int _sendPin; // IO Pin number for sendint signals. 97 | int _irNumber; // Interrupt number of receiver. 98 | static int _trim; // timming factor 99 | 100 | 101 | // ===== Interrupt service routine ===== 102 | 103 | // This handler is attached to the change interrupt. 104 | static void IRAM_ATTR signal_change_handler(); 105 | 106 | }; // class SignalCollector 107 | 108 | #endif // TabRF_H_ 109 | -------------------------------------------------------------------------------- /src/SignalParser.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file SignalParser.cpp 3 | * 4 | * This file is part of the RFCodes library that implements receiving an sending 5 | * RF and IR protocols. 6 | * 7 | * @copyright Copyright (c) by Matthias Hertel, https://www.mathertel.de. 8 | * 9 | * This work is licensed under a BSD 3-Clause style license, 10 | * https://www.mathertel.de/License.aspx. 11 | * 12 | * @brief 13 | * This signal parser recognizes patterns in timing code sequences that are 14 | * defined by declarative tables. 15 | * 16 | * Change History see SignalParser.h 17 | */ 18 | 19 | #include 20 | 21 | #include "SignalParser.h" 22 | 23 | 24 | // ===== private functions ===== 25 | 26 | 27 | /** find protocol by name */ 28 | SignalParser::Protocol *SignalParser::_findProt(char *name) { 29 | Protocol *p = nullptr; 30 | 31 | for (int n = 0; n < _protocolCount; n++) { 32 | p = _protocol[n]; 33 | if (strcmp(name, p->name) == 0) 34 | break; 35 | } 36 | return (p); 37 | } // _findProt() 38 | 39 | 40 | /** find code by name */ 41 | SignalParser::Code *SignalParser::_findCode(Protocol *p, char codeName) { 42 | Code *c = p->codes; 43 | int cnt = p->codeLength; 44 | 45 | while (c && cnt) { 46 | if (c->name == codeName) 47 | break; 48 | c++; 49 | cnt--; 50 | } 51 | return (cnt ? c : nullptr); 52 | } // _findCode() 53 | 54 | 55 | /** reset all codes in a protocol */ 56 | void SignalParser::_resetCodes(Protocol *p) { 57 | Code *c = p->codes; 58 | int cCnt = p->codeLength; 59 | while (c && cCnt) { 60 | c->valid = true; 61 | c->cnt = 0; 62 | c->total = 0; 63 | 64 | c++; 65 | cCnt--; 66 | } 67 | } // _resetCodes() 68 | 69 | 70 | /** reset the whole protocol to start capturing from scratch. */ 71 | void SignalParser::_resetProtocol(Protocol *p) { 72 | TRACE_MSG(" reset prot: %s", p->name); 73 | p->seqLen = 0; 74 | p->seq[0] = NUL; 75 | _resetCodes(p); 76 | _recalcProtocol(p, p->baseTime); 77 | } // _resetProtocol() 78 | 79 | 80 | /** use the callback function when registered using format */ 81 | void SignalParser::_useCallback(Protocol *p) { 82 | if (p && _callbackFunc) { 83 | String code; 84 | code += p->name; 85 | code += ' '; 86 | code += p->seq; 87 | _callbackFunc(code.c_str()); 88 | } 89 | } // _useCallback() 90 | 91 | 92 | /** check if the duration fits for the protocol */ 93 | void SignalParser::_parseProtocol(Protocol *p, CodeTime duration) { 94 | Code *c = p->codes; 95 | int cCnt = p->codeLength; 96 | bool anyValid = false; 97 | bool retryCandidate = false; 98 | 99 | while (c && cCnt) { 100 | 101 | if (c->valid) { 102 | // check if timing fits into this code 103 | int8_t i = c->cnt; 104 | CodeType type = c->type; 105 | bool matched = false; // until found that the new duration fits 106 | 107 | TRACE_MSG("check: %c", c->name); 108 | 109 | if ((p->seqLen == 0) && !(type & START)) { 110 | // codes other than start codes are nor acceptable as a first code in the sequence. 111 | // TRACE_MSG(" not start"); 112 | 113 | } else if ((p->seqLen > 0) && !(type & ANY)) { 114 | // codes other than data and end codes are nor acceptable during receiving. 115 | // TRACE_MSG(" not data"); 116 | 117 | } else if ((duration < c->minTime[i]) || (duration > c->maxTime[i])) { 118 | // This timing is not matching. 119 | // TRACE_MSG(" no fitting timing"); 120 | 121 | if ((i == 1) && (p->seqLen == 0)) { 122 | // reanalyze this duration as a first duration for starting. 123 | retryCandidate = true; 124 | // TRACE_MSG(" --retry"); 125 | } 126 | 127 | } else { 128 | matched = true; // this code matches 129 | c->total += duration; 130 | 131 | } // if 132 | 133 | // write back to code 134 | c->valid = matched; 135 | 136 | anyValid = anyValid || matched; 137 | 138 | if (retryCandidate) { 139 | // reset this code only and try again. 140 | TRACE_MSG(" start retry..."); 141 | _resetProtocol(p); 142 | 143 | 144 | } else if (matched) { 145 | // this timing is matching 146 | TRACE_MSG(" matched."); 147 | c->cnt = i = i + 1; 148 | 149 | if (i == c->timeLength) { 150 | // all timings received so add code-character. 151 | if (p->seqLen == 0) { 152 | TRACE_MSG("start: %s %d", p->name, c->total); 153 | int allTimes = 0; 154 | for (int tl = 0; tl < c->timeLength; tl++) { 155 | allTimes += c->time[tl]; 156 | } 157 | _recalcProtocol(p, c->total / allTimes); 158 | } 159 | 160 | p->seq[p->seqLen++] = c->name; 161 | p->seq[p->seqLen] = NUL; 162 | // DEBUG_ESP_PORT.print(c->name); 163 | TRACE_MSG(" add '%s'", p->seq); 164 | 165 | _resetCodes(p); // reset all codes but not the protocol 166 | 167 | if ((type == END) && (p->seqLen < p->minCodeLen)) { 168 | // End packet found but sequence was not started early enough 169 | TRACE_MSG(" end fragment: %s", p->seq); 170 | _resetProtocol(p); 171 | 172 | } else if ((type & END) && (p->seqLen >= p->minCodeLen)) { 173 | TRACE_MSG(" found-1: %s", p->seq); 174 | _useCallback(p); 175 | _resetProtocol(p); 176 | 177 | } else if ((p->seqLen == p->maxCodeLen)) { 178 | TRACE_MSG(" found-2: %s", p->seq); 179 | _useCallback(p); 180 | _resetProtocol(p); 181 | } 182 | break; // no more code checking in this protocol 183 | } // if 184 | } 185 | } // if (c->valid) 186 | 187 | if (retryCandidate) { 188 | // only loop once 189 | retryCandidate = false; 190 | } else { 191 | // next code 192 | c++; 193 | cCnt--; 194 | } 195 | } // while 196 | 197 | if (!anyValid) { 198 | TRACE_MSG(" no codes."); 199 | _resetProtocol(p); 200 | } 201 | } // _parseProtocol() 202 | 203 | 204 | // ===== public functions ===== 205 | 206 | 207 | /** attach a callback function that will get passed any new code. */ 208 | void SignalParser::attachCallback(CallbackFunction newFunction) { 209 | _callbackFunc = newFunction; 210 | } // attachCallback() 211 | 212 | 213 | // return the number of send repeats that should occure. 214 | int SignalParser::getSendRepeat(char *name) { 215 | Protocol *p = _findProt(name); 216 | return (p ? p->sendRepeat : 0); 217 | } 218 | 219 | /** parse a single duration. 220 | * @param duration check if this duration fits to any definitions. 221 | */ 222 | void SignalParser::parse(CodeTime duration) { 223 | TRACE_MSG("(%d)", duration); 224 | 225 | for (int n = 0; n < _protocolCount; n++) { 226 | _parseProtocol(_protocol[n], duration); 227 | } 228 | } // parse() 229 | 230 | 231 | /** compose the timings of a sequence by using the code table. 232 | * @param sequence textual representation using " ". 233 | */ 234 | void SignalParser::compose(const char *sequence, CodeTime *timings, int len) { 235 | char protname[PROTNAME_LEN]; 236 | 237 | char *s = strchr(sequence, ' '); 238 | 239 | if (s) { 240 | // extract protname 241 | { 242 | char *tar = protname; 243 | const char *src = sequence; 244 | while (*src && (*src != ' ')) { 245 | *tar++ = *src++; 246 | } 247 | *tar = NUL; 248 | } 249 | Protocol *p = _findProt(protname); 250 | 251 | s++; // to start of code characters 252 | 253 | if (p && timings) { 254 | while (*s && len) { 255 | Code *c = _findCode(p, *s); 256 | if (c) { 257 | for (int i = 0; i < c->timeLength; i++) { 258 | *timings++ = (c->minTime[i] + c->maxTime[i]) / 2; 259 | } // for 260 | } 261 | s++; 262 | len--; 263 | } 264 | *timings = 0; 265 | } // if 266 | } 267 | } // compose() 268 | 269 | 270 | void SignalParser::_recalcProtocol(Protocol *protocol, CodeTime baseTime) { 271 | // calc min and max and codesLength 272 | if (protocol->baseTime != baseTime) { 273 | TRACE_MSG("recalc %d", baseTime); 274 | } 275 | 276 | for (int cl = 0; cl < protocol->codeLength; cl++) { 277 | Code *c = &(protocol->codes[cl]); 278 | 279 | for (int tl = 0; tl < c->timeLength; tl++) { 280 | CodeTime t = baseTime * c->time[tl]; 281 | int radius = (t * protocol->tolerance) / 100; 282 | c->minTime[tl] = t - radius; 283 | c->maxTime[tl] = t + radius; 284 | } 285 | } 286 | } // _recalcProtocol() 287 | 288 | 289 | /** Load a protocol to be used. */ 290 | // @param otherBaseTime not in use yet. 291 | void SignalParser::load(Protocol *protocol, CodeTime otherBaseTime) { 292 | if (protocol) { 293 | TRACE_MSG("loading protocol %s", protocol->name); 294 | 295 | // get space for protocol definition 296 | if (_protocolCount >= _protocolAlloc) { 297 | _protocolAlloc += 8; 298 | TRACE_MSG("alloc %d", _protocolAlloc); 299 | _protocol = (Protocol **)realloc(_protocol, _protocolAlloc * sizeof(Protocol *)); 300 | } 301 | 302 | // fill last one. 303 | _protocol[_protocolCount] = protocol; 304 | TRACE_MSG("_p[%d]=%08x", _protocolCount, protocol); 305 | _protocolCount += 1; 306 | 307 | CodeTime baseTime = protocol->baseTime; 308 | 309 | // calc c->timeLength and p->codeLength 310 | int cl = 0; 311 | while ((cl < MAX_CODELENGTH) && (protocol->codes[cl].name)) { 312 | Code *c = &(protocol->codes[cl]); 313 | 314 | int tl = 0; 315 | while ((tl < MAX_TIMELENGTH) && (c->time[tl])) { 316 | tl++; 317 | } // while 318 | c->timeLength = tl; 319 | cl++; 320 | } // while 321 | protocol->codeLength = cl; // no need to specify codeLength 322 | 323 | _recalcProtocol(protocol, baseTime); 324 | _resetProtocol(protocol); 325 | 326 | for (int n = 0; n < _protocolCount; n++) { 327 | TRACE_MSG(" reg[%d] = %08x", n, _protocol[n]); 328 | } // for 329 | 330 | 331 | } // if 332 | } // load() 333 | 334 | // End. -------------------------------------------------------------------------------- /src/SignalParser.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file: SignalParser.h 3 | * 4 | * This file is part of the RFCodes library that implements receiving an sending 5 | * RF and IR protocols. 6 | * 7 | * @copyright Copyright (c) by Matthias Hertel, https://www.mathertel.de. 8 | * 9 | * This work is licensed under a BSD 3-Clause style license, 10 | * https://www.mathertel.de/License.aspx. 11 | * 12 | * @brief 13 | * This signal parser recognizes patterns in timing code sequences that are 14 | * defined by declarative tables. 15 | * 16 | * Changelog: 17 | * * 29.04.2018 created by Matthias Hertel 18 | * * 06.08.2018 const char send, allow for sending only. 19 | */ 20 | 21 | // .h 22 | 23 | // This signal parser recognizes patterns in timing code sequences that are 24 | // defined by declarative tables. 25 | 26 | // * Define the pattern using newProtocol and newCode. 27 | // * Register a callback function using attachCallback 28 | // * Pass timing code values into the parse function. 29 | 30 | // * 20.3.2021: parse every protocol independently 31 | 32 | 33 | #ifndef SignalParser_H_ 34 | #define SignalParser_H_ 35 | 36 | // #include 37 | // #include 38 | // #include 39 | 40 | #include "debugout.h" 41 | 42 | #define NUL '\0' 43 | 44 | #define MAX_TIMELENGTH 8 // maximal length of a code definition 45 | #define MAX_CODELENGTH 8 // maximal number of code definitions per protocol 46 | 47 | #define MAX_SEQUENCE_LENGTH 120 // maximal length of a code sequence 48 | #define MAX_TIMING_LENGTH (MAX_TIMELENGTH * MAX_SEQUENCE_LENGTH) // maximal number of timings in a sequence 49 | 50 | #define PROTNAME_LEN 12 // maximal protocol name len including ending '\0' 51 | 52 | class SignalParser { 53 | public: 54 | // ===== Type definitions ===== 55 | 56 | 57 | // use-cases of a defined code (start,data,end). 58 | typedef enum { 59 | START = 0x01, // A valid start code type. 60 | DATA = 0x02, // A code containing some information 61 | END = 0x04, // This code ends a sequence 62 | ANYDATA = (START | DATA), // A code with data can be used to start a sequence 63 | ANY = (DATA | END) // A code with data that can end the sequence 64 | } CodeType; 65 | 66 | // timings are using CodeTime datatypes meaning µsecs. 67 | typedef unsigned int CodeTime; 68 | 69 | // The Code structure is used to hold a specific timing sequence used in the protocol. 70 | // This Structure includes also the current state information while receiving the code. 71 | struct Code { 72 | CodeType type; // type of usage of code 73 | char name; // single character name for this code used for the message string. 74 | 75 | CodeTime time[MAX_TIMELENGTH]; // ideal time of the code part. 76 | 77 | // These members will be calculated: 78 | 79 | int timeLength; // number of timings for this code 80 | CodeTime total; // total time in this code 81 | 82 | CodeTime minTime[MAX_TIMELENGTH]; // average time of the code part. 83 | CodeTime maxTime[MAX_TIMELENGTH]; // average time of the code part. 84 | 85 | // these fields reflect the current status of the code. 86 | int cnt; // number of discovered timings. 87 | bool valid; // is true while discovering and the code is still possible. 88 | }; // struct Code 89 | 90 | 91 | // The Protocol structure is used to hold the basic settings for a protocol. 92 | struct Protocol { 93 | // These members must be initialized for load(): 94 | 95 | /** name of the protocol */ 96 | char name[PROTNAME_LEN]; 97 | 98 | /** minimal number of codes in a row required by the protocol. */ 99 | unsigned int minCodeLen; 100 | 101 | // maximum number of codes in a row defining a complete CodeSequence. 102 | unsigned int maxCodeLen; 103 | 104 | // tolerance of the timings in percent. 105 | unsigned int tolerance; 106 | 107 | // Number of repeats when sending. 108 | unsigned int sendRepeat; 109 | 110 | CodeTime baseTime; 111 | CodeTime realBase; 112 | 113 | Code codes[MAX_CODELENGTH]; 114 | 115 | // ===== These members are used while parsing: 116 | 117 | // Number of defined codes in this table 118 | int codeLength; 119 | char seq[MAX_SEQUENCE_LENGTH]; 120 | int seqLen; 121 | }; // struct Protocol 122 | 123 | 124 | // Callback when a code sequence was detected. 125 | typedef void (*CallbackFunction)(const char *code); 126 | 127 | 128 | // ===== Functions ===== 129 | 130 | private: 131 | // ===== class variables ===== 132 | 133 | /** Protocol table and related settings */ 134 | Protocol **_protocol; 135 | int _protocolAlloc = 0; 136 | int _protocolCount = 0; 137 | 138 | CallbackFunction _callbackFunc; 139 | 140 | /** find protocol by name */ 141 | Protocol *_findProt(char *name); 142 | 143 | /** find code by name */ 144 | Code *_findCode(Protocol *p, char codeName); 145 | 146 | /** reset all codes in a protocol */ 147 | void _resetCodes(Protocol *p); 148 | 149 | /** reset the whole protocol to start capturing from scratch. */ 150 | void _resetProtocol(Protocol *p); 151 | 152 | /** use the callback function when registered using format */ 153 | void _useCallback(Protocol *p); 154 | 155 | /** check if the duration fits for the protocol */ 156 | void _parseProtocol(Protocol *p, CodeTime duration); 157 | 158 | /** recalculate adjusted timings. */ 159 | void _recalcProtocol(Protocol *protocol, CodeTime baseTime); 160 | 161 | 162 | // ===== public functions ===== 163 | 164 | public: 165 | /** attach a callback function that will get passed any new code. */ 166 | void attachCallback(CallbackFunction newFunction); 167 | 168 | // return the number of send repeats that should occure. 169 | int getSendRepeat(char *name); 170 | 171 | /** parse a single duration. 172 | * @param duration check if this duration fits to any definitions. 173 | */ 174 | void parse(CodeTime duration); 175 | 176 | /** compose the timings of a sequence by using the code table. 177 | * @param sequence textual representation using " ". 178 | */ 179 | void compose(const char *sequence, CodeTime *timings, int len); 180 | 181 | /** Load a protocol to be used. */ 182 | void load(Protocol *protocol, CodeTime otherBaseTime = 0); 183 | 184 | // ===== debug helpers ===== 185 | 186 | /** Send a summary of the current code-table to the output. */ 187 | void dumpProtocol(Protocol *p) { 188 | TRACE_MSG("dump %08x", p); 189 | 190 | if (p) { 191 | // dump the Protocol characteristics 192 | RAW_MSG("Protocol '%s', min:%d max:%d tol:%02u rep:%d\n", 193 | p->name, p->minCodeLen, p->maxCodeLen, p->tolerance, 194 | p->sendRepeat); 195 | 196 | Code *c = p->codes; 197 | int cnt = p->codeLength; 198 | 199 | while (c && cnt) { 200 | RAW_MSG(" '%c' |", c->name); 201 | 202 | for (int n = 0; n < c->timeLength; n++) { 203 | RAW_MSG("%5d -%5d |", c->minTime[n], c->maxTime[n]); 204 | } // for 205 | RAW_MSG("\n"); 206 | 207 | c++; 208 | cnt--; 209 | } // while 210 | RAW_MSG("\n"); 211 | } // if 212 | } // dumpProtocol() 213 | 214 | /** Send a summary of the current code-table to the output. */ 215 | void dumpTable() { 216 | for (int n = 0; n < _protocolCount; n++) { 217 | Protocol *p = _protocol[n]; 218 | dumpProtocol(p); 219 | } // for 220 | } // dumpTable() 221 | }; // class 222 | 223 | #endif // SignalParser_H_ 224 | -------------------------------------------------------------------------------- /src/debugout.h: -------------------------------------------------------------------------------- 1 | // Debug output helper 2 | 3 | #ifndef DEBUGOUT_H_ 4 | #define DEBUGOUT_H_ 5 | 6 | // #define NODEBUG 7 | 8 | #if defined(NODEBUG) 9 | #define TRACE_MSG(...) 10 | #define ERROR_MSG(...) 11 | #define INFO_MSG(...) 12 | #define RAW_MSG(...) 13 | 14 | #elif defined(DEBUG_ESP_PORT) 15 | // ESP8266 way to specify the text output e.g. using Serial 16 | #define ERROR_MSG(...) { DEBUG_ESP_PORT.printf(__VA_ARGS__); DEBUG_ESP_PORT.println(); } 17 | #define INFO_MSG(...) { DEBUG_ESP_PORT.printf(__VA_ARGS__); DEBUG_ESP_PORT.println(); } 18 | #define TRACE_MSG(...) { DEBUG_ESP_PORT.printf(__VA_ARGS__); DEBUG_ESP_PORT.println(); } 19 | #define RAW_MSG(...) { DEBUG_ESP_PORT.printf(__VA_ARGS__); } 20 | 21 | #elif defined(ARDUINO_ARCH_AVR) 22 | #define ERROR_MSG(fmt, ...) 23 | #define INFO_MSG(fmt, ...) 24 | #define TRACE_MSG(fmt, ...) 25 | #define RAW_MSG(...) 26 | 27 | #else 28 | #define ERROR_MSG(fmt, ...) 29 | #define INFO_MSG(fmt, ...) 30 | #define TRACE_MSG(fmt, ...) 31 | #define RAW_MSG(...) 32 | 33 | // #include 34 | // // stdout version 35 | // #define ERROR_MSG(fmt, ...) fprintf(stderr, "[error] " fmt "\n", __VA_ARGS__); 36 | // #define INFO_MSG(fmt, ...) fprintf(stderr, "[info] " fmt "\n", __VA_ARGS__); 37 | // #define TRACE_MSG(fmt, ...) fprintf(stderr, "[trace] " fmt "\n", __VA_ARGS__); 38 | // #define RAW_MSG(...) fprintf(stderr, __VA_ARGS__); 39 | 40 | #endif 41 | 42 | #undef TRACE_MSG 43 | #define TRACE_MSG(...) 44 | 45 | 46 | #endif // DEBUGOUT_H_ 47 | -------------------------------------------------------------------------------- /src/ircodes.h: -------------------------------------------------------------------------------- 1 | // nec_ir_protocol.h 2 | 3 | // https://www.sbprojects.net/knowledge/ir/nec.php 4 | // https://techdocs.altium.com/display/FPGA/NEC+Infrared+Transmission+Protocol 5 | 6 | 7 | // typical recording: [nec N00000000 11110111 11010000 00101111] 8 | // 9193,4530, 9 | // 632,520,595,556,605,519,631,522,592,560,600,523,630,520,593,556, 10 | // 602,1670,593,1675,633,1665,604,1666,595,555,570,1700,593,1674,597,1699, 11 | // 569,1700,592,1675,598,554,591,1674,598,553,592,558,569,553,596,554, 12 | // 591,560,567,555,596,1696,568,555,596,1696,567,1699,591,1674,598,1696, 13 | 14 | // repeat code: 15 | // 9183,2258,592, 16 | 17 | // 558,591,1681,598,556,593,562,566,560,595,555,593,562,568,556,597,1703,571,556,596,1704,570,1705,595,1679,598,1703,570,15529,81,65070,9193, 18 | // 4530,632,520,595,556,605,519,631,522,592,560,600,523,630,520,593,556,602,1670,593,1675,633,1665,604,1666,595,555,570,1700,593,1674,597, 19 | // 1699,569,1700,592,1675,598,554,591,1674,598,553,592,558,569,553,596,554,591,560,567,555,596,1696,568,555,596,1696,567,1699,591,1674,598, 20 | // 1696,569,41398,9183,2258,592,52659,142,37089,139,60928,85,24742,9175,4559,570,557,597,554,595,558,569,556,596,556,594,557,571,555,597,556,593, 21 | // 1678,597,1703,571,1701,594,1678,598,554,594,1677,631,1668,567,1704,595,1676,633,1666,605,519,630,1668,605,518,631,520,592,559,603,520,630, 22 | // 522,593,557,604,1666,593,559,602,1667,594,1675,631,1668,604,1667,590,41502,9157,2260,598,37695,90,62909,9171,4558,571,556,596,554,593,558,570, 23 | // 556,597,554,593,559,568,556,597,555,594,1677,599,1700,570,1702,594,1676,597,557,591,1679,596,1701,570,1701,591,1679,597,1701,570,554,596, 24 | // 1701,570,554,596,556,591,559,567,556,595,555,592,560,566,1702,590,563,567,1700,592,1678,597,1699,570,1701,591,41501,9155,2263,598,25530,9171, 25 | // 4564,603,522,630,525,591,558,604,521,628,528,592,557,570,556,597,554,595,1678,631,1670,604,1669,594,1677,632,522,586,1685,632,1668,605, 26 | // 1668,595,1676,631,1670,603,520,632,1670,601,522,631,521,593,558,602,524,628,522,595,556,604,1668,593,559,604,1668,594,1678,630,1669,604, 27 | 28 | 29 | #ifndef SignalParser_IRCODES_H_ 30 | #define SignalParser_IRCODES_H_ 31 | 32 | #include "SignalParser.h" 33 | 34 | /** namespace for defining codes for the Arduino RFCodes library. */ 35 | namespace IRCodes 36 | { 37 | 38 | // Definition of the IR protocol used / defined by nec. 39 | // Timings from // https://www.sbprojects.net/knowledge/ir/nec.php 40 | 41 | SignalParser::Protocol nec = { 42 | "nec", 43 | .minCodeLen = 1, 44 | .maxCodeLen = 1 + 32, 45 | 46 | .tolerance = 20, 47 | .sendRepeat = 4, 48 | .baseTime = 560, 49 | .codes = { 50 | // starting sequence is /‾‾(9000)‾‾\__(4500)__/ 51 | {SignalParser::CodeType::START, 'N', {16, 8}}, 52 | 53 | // low signal is /‾\_/ 54 | {SignalParser::CodeType::DATA, '0', {1, 1}}, 55 | 56 | // high signal is /‾\___/ 57 | {SignalParser::CodeType::DATA, '1', {1, 3}}, 58 | 59 | // Repeat signal is /‾‾(9000)‾‾\__(2250)__/ 60 | {SignalParser::CodeType::DATA, 'R', {16, 4}}}}; 61 | 62 | } // namespace IRCodes 63 | 64 | #endif // SignalParser_IRCODES_H_ 65 | 66 | // End. 67 | -------------------------------------------------------------------------------- /src/protocols.h: -------------------------------------------------------------------------------- 1 | // protocols.h 2 | 3 | // This is a collection of protocol definitions used in the 433 MHz Band for remote controls and data transfers. 4 | 5 | #ifndef SignalParser_PROTOCOLS_H_ 6 | #define SignalParser_PROTOCOLS_H_ 7 | 8 | #include "SignalParser.h" 9 | 10 | /** namespace for defining codes for the Arduino RFCodes library. */ 11 | namespace RFCodes 12 | { 13 | 14 | /** Definition of the "older" intertechno protocol with fixed 12 bits of data */ 15 | SignalParser::Protocol it1 = { 16 | "it1", 17 | .minCodeLen = 1 + 12, 18 | .maxCodeLen = 1 + 12, 19 | 20 | .tolerance = 25, 21 | .sendRepeat = 4, 22 | .baseTime = 400, 23 | .codes = { 24 | {SignalParser::CodeType::START, 'B', {1, 31}}, 25 | {SignalParser::CodeType::DATA, '0', {1, 3, 3, 1}}, 26 | {SignalParser::CodeType::DATA, '1', {1, 3, 1, 3}}} 27 | 28 | }; 29 | 30 | 31 | /** Definition of the "newer" intertechno protocol with 32 - 46 data bits data */ 32 | SignalParser::Protocol it2 = { 33 | "it2", // .name = 34 | .minCodeLen = 34, 35 | .maxCodeLen = 48, 36 | 37 | .tolerance = 25, 38 | .sendRepeat = 10, 39 | .baseTime = 280, // base time in µsecs 40 | .codes = { 41 | {SignalParser::CodeType::START, 's', {1, 10}}, 42 | {SignalParser::CodeType::DATA, '_', {1, 1, 1, 5}}, 43 | {SignalParser::CodeType::DATA, '#', {1, 5, 1, 1}}, 44 | {SignalParser::CodeType::DATA, 'D', {1, 1, 1, 1}}, 45 | {SignalParser::CodeType::END, 'x', {1, 38}}} 46 | 47 | }; 48 | 49 | 50 | /** Definition of the protocol from SC5272 and similar chips with 32 - 46 data bits data */ 51 | SignalParser::Protocol sc5 = { 52 | "sc5", 53 | .minCodeLen = 1 + 12, 54 | .maxCodeLen = 1 + 12, 55 | 56 | .tolerance = 25, 57 | .sendRepeat = 3, 58 | .baseTime = 100, 59 | .codes = { 60 | {SignalParser::CodeType::ANYDATA, '0', {4, 12, 4, 12}}, 61 | {SignalParser::CodeType::ANYDATA, '1', {12, 4, 12, 4}}, 62 | {SignalParser::CodeType::ANYDATA, 'f', {4, 12, 12, 4}}, 63 | {SignalParser::CodeType::END, 'S', {4, 124}}}}; 64 | 65 | 66 | /** Definition of the protocol from ev1527 and similar chips with 20 address and 4 data bits. */ 67 | SignalParser::Protocol ev1527 = { 68 | "ev1527", 69 | .minCodeLen = 1 + 20 + 4, 70 | .maxCodeLen = 1 + 20 + 4, 71 | 72 | .tolerance = 25, 73 | .sendRepeat = 3, 74 | .baseTime = 320, 75 | .codes = { 76 | {SignalParser::CodeType::START, 's', {1, 31}}, 77 | {SignalParser::CodeType::DATA, '0', {1, 3}}, 78 | {SignalParser::CodeType::DATA, '1', {3, 1}} 79 | }}; 80 | 81 | 82 | /** register the cresta protocol with a length of 59 codes; used for sensor data transmissions. 83 | * See /docs/cresta_protocol.h */ 84 | SignalParser::Protocol cw = { 85 | "cw", 86 | .minCodeLen = 59, 87 | .maxCodeLen = 59, 88 | 89 | .tolerance = 16, 90 | .sendRepeat = 3, 91 | .baseTime = 500, 92 | .codes = { 93 | {SignalParser::CodeType::START, 'H', {2, 2, 2, 2, 2}}, 94 | {SignalParser::CodeType::DATA, 's', {1, 1}}, 95 | {SignalParser::CodeType::DATA, 'l', {2}}}}; 96 | 97 | } // namespace RFCodes 98 | 99 | #endif // SignalParser_PROTOCOLS_H_ 100 | 101 | // End. 102 | --------------------------------------------------------------------------------