From d14d5063ebf75349e9d264ea64bbed1240c35d89 Mon Sep 17 00:00:00 2001 From: DRSDavidSoft <4673812+DRSDavidSoft@users.noreply.github.com> Date: Tue, 27 Oct 2026 00:02:11 +0330 Subject: [PATCH 1/5] fix: decode RF frames outside the UART-blocking interrupt --- .github/workflows/firmware.yml | 3 + BoardSupportSources.cpp | 3 + LocalLib/RCSwitch.cpp | 728 +++++++++++++++++++++ LocalLib/RCSwitch.h | 186 ++++++ PCController.ino | 4 + Tools/Firmware/tests/rf-deferred/Arduino.h | 23 + Tools/Firmware/tests/rf-deferred/main.cpp | 33 + Tools/Firmware/verify-rf-deferred.mjs | 14 + docs/RF-DEFERRED-DECODE.md | 30 + 9 files changed, 1024 insertions(+) create mode 100644 LocalLib/RCSwitch.cpp create mode 100644 LocalLib/RCSwitch.h create mode 100644 Tools/Firmware/tests/rf-deferred/Arduino.h create mode 100644 Tools/Firmware/tests/rf-deferred/main.cpp create mode 100644 Tools/Firmware/verify-rf-deferred.mjs create mode 100644 docs/RF-DEFERRED-DECODE.md diff --git a/.github/workflows/firmware.yml b/.github/workflows/firmware.yml index d36159664..ebfb253e4 100644 --- a/.github/workflows/firmware.yml +++ b/.github/workflows/firmware.yml @@ -101,6 +101,9 @@ jobs: --clean --version "$PCCONTROLLER_VERSION" + - name: "Verify RF decoding stays outside the interrupt handler" + run: node Tools/Firmware/verify-rf-deferred.mjs + - name: "Verify linked strip output waveform" run: >- node Tools/Firmware/verify-strip-waveform.mjs diff --git a/BoardSupportSources.cpp b/BoardSupportSources.cpp index 51209f473..27d5a401b 100644 --- a/BoardSupportSources.cpp +++ b/BoardSupportSources.cpp @@ -2,6 +2,9 @@ // Keep the LocalLib domain layout while building each implementation once. #include "ProjectConfig.h" +#if !defined(PCCONTROLLER_NATIVE) +#include "LocalLib/RCSwitch.cpp" +#endif #include "LocalLib/DallasTemperatureBus.cpp" #include "LocalLib/I2cLcd.cpp" #include "LocalLib/Keys.cpp" diff --git a/LocalLib/RCSwitch.cpp b/LocalLib/RCSwitch.cpp new file mode 100644 index 000000000..768dbb1c6 --- /dev/null +++ b/LocalLib/RCSwitch.cpp @@ -0,0 +1,728 @@ +// PCController: rc-switch 2.6.4, LGPL-2.1-or-later. Decode outside the ISR. +/* + RCSwitch - Arduino libary for remote control outlet switches + Copyright (c) 2011 Suat Özgür. All right reserved. + + Contributors: + - Andre Koehler / info(at)tomate-online(dot)de + - Gordeev Andrey Vladimirovich / gordeev(at)openpyro(dot)com + - Skineffect / http://forum.ardumote.com/viewtopic.php?f=2&t=46 + - Dominik Fischer / dom_fischer(at)web(dot)de + - Frank Oltmanns / .(at)gmail(dot)com + - Andreas Steinel / A.(at)gmail(dot)com + - Max Horn / max(at)quendi(dot)de + - Robert ter Vehn / .(at)gmail(dot)com + - Johann Richard / .(at)gmail(dot)com + - Vlad Gheorghe / .(at)gmail(dot)com https://github.com/vgheo + - Matias Cuenca-Acuna + + Project home: https://github.com/sui77/rc-switch/ + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "RCSwitch.h" + +#ifdef RaspberryPi + // PROGMEM and _P functions are for AVR based microprocessors, + // so we must normalize these for the ARM processor: + #define PROGMEM + #define memcpy_P(dest, src, num) memcpy((dest), (src), (num)) +#endif + +#if defined(ESP8266) + // interrupt handler and related code must be in RAM on ESP8266, + // according to issue #46. + #define RECEIVE_ATTR ICACHE_RAM_ATTR + #define VAR_ISR_ATTR +#elif defined(ESP32) + #define RECEIVE_ATTR IRAM_ATTR + #define VAR_ISR_ATTR DRAM_ATTR +#else + #define RECEIVE_ATTR + #define VAR_ISR_ATTR +#endif + + +/* Format for protocol definitions: + * {pulselength, Sync bit, "0" bit, "1" bit, invertedSignal} + * + * pulselength: pulse length in microseconds, e.g. 350 + * Sync bit: {1, 31} means 1 high pulse and 31 low pulses + * (perceived as a 31*pulselength long pulse, total length of sync bit is + * 32*pulselength microseconds), i.e: + * _ + * | |_______________________________ (don't count the vertical bars) + * "0" bit: waveform for a data bit of value "0", {1, 3} means 1 high pulse + * and 3 low pulses, total length (1+3)*pulselength, i.e: + * _ + * | |___ + * "1" bit: waveform for a data bit of value "1", e.g. {3,1}: + * ___ + * | |_ + * + * These are combined to form Tri-State bits when sending or receiving codes. + */ +#if defined(ESP8266) || defined(ESP32) +static const VAR_ISR_ATTR RCSwitch::Protocol proto[] = { +#else +static const RCSwitch::Protocol PROGMEM proto[] = { +#endif + { 350, { 1, 31 }, { 1, 3 }, { 3, 1 }, false }, // protocol 1 + { 650, { 1, 10 }, { 1, 2 }, { 2, 1 }, false }, // protocol 2 + { 100, { 30, 71 }, { 4, 11 }, { 9, 6 }, false }, // protocol 3 + { 380, { 1, 6 }, { 1, 3 }, { 3, 1 }, false }, // protocol 4 + { 500, { 6, 14 }, { 1, 2 }, { 2, 1 }, false }, // protocol 5 + { 450, { 23, 1 }, { 1, 2 }, { 2, 1 }, true }, // protocol 6 (HT6P20B) + { 150, { 2, 62 }, { 1, 6 }, { 6, 1 }, false }, // protocol 7 (HS2303-PT, i. e. used in AUKEY Remote) + { 200, { 3, 130}, { 7, 16 }, { 3, 16}, false}, // protocol 8 Conrad RS-200 RX + { 200, { 130, 7 }, { 16, 7 }, { 16, 3 }, true}, // protocol 9 Conrad RS-200 TX + { 365, { 18, 1 }, { 3, 1 }, { 1, 3 }, true }, // protocol 10 (1ByOne Doorbell) + { 270, { 36, 1 }, { 1, 2 }, { 2, 1 }, true }, // protocol 11 (HT12E) + { 320, { 36, 1 }, { 1, 2 }, { 2, 1 }, true } // protocol 12 (SM5212) +}; + +enum { + numProto = sizeof(proto) / sizeof(proto[0]) +}; + +#if not defined( RCSwitchDisableReceiving ) +volatile unsigned long RCSwitch::nReceivedValue = 0; +volatile unsigned int RCSwitch::nReceivedBitlength = 0; +volatile unsigned int RCSwitch::nReceivedDelay = 0; +volatile unsigned int RCSwitch::nReceivedProtocol = 0; +int RCSwitch::nReceiveTolerance = 60; +const unsigned int RCSwitch::nSeparationLimit = 4300; +// separationLimit: minimum microseconds between received codes, closer codes are ignored. +// according to discussion on issue #14 it might be more suitable to set the separation +// limit to the same time as the 'low' part of the sync signal for the current protocol. +unsigned int RCSwitch::timings[RCSWITCH_MAX_CHANGES]; +#endif + +RCSwitch::RCSwitch() { + this->nTransmitterPin = -1; + this->setRepeatTransmit(10); + this->setProtocol(1); + #if not defined( RCSwitchDisableReceiving ) + this->nReceiverInterrupt = -1; + this->setReceiveTolerance(60); + RCSwitch::nReceivedValue = 0; + #endif +} + +/** + * Sets the protocol to send. + */ +void RCSwitch::setProtocol(Protocol protocol) { + this->protocol = protocol; +} + +/** + * Sets the protocol to send, from a list of predefined protocols + */ +void RCSwitch::setProtocol(int nProtocol) { + if (nProtocol < 1 || nProtocol > numProto) { + nProtocol = 1; // TODO: trigger an error, e.g. "bad protocol" ??? + } +#if defined(ESP8266) || defined(ESP32) + this->protocol = proto[nProtocol-1]; +#else + memcpy_P(&this->protocol, &proto[nProtocol-1], sizeof(Protocol)); +#endif +} + +/** + * Sets the protocol to send with pulse length in microseconds. + */ +void RCSwitch::setProtocol(int nProtocol, int nPulseLength) { + setProtocol(nProtocol); + this->setPulseLength(nPulseLength); +} + + +/** + * Sets pulse length in microseconds + */ +void RCSwitch::setPulseLength(int nPulseLength) { + this->protocol.pulseLength = nPulseLength; +} + +/** + * Sets Repeat Transmits + */ +void RCSwitch::setRepeatTransmit(int nRepeatTransmit) { + this->nRepeatTransmit = nRepeatTransmit; +} + +/** + * Set Receiving Tolerance + */ +#if not defined( RCSwitchDisableReceiving ) +void RCSwitch::setReceiveTolerance(int nPercent) { + RCSwitch::nReceiveTolerance = nPercent; +} +#endif + + +/** + * Enable transmissions + * + * @param nTransmitterPin Arduino Pin to which the sender is connected to + */ +void RCSwitch::enableTransmit(int nTransmitterPin) { + this->nTransmitterPin = nTransmitterPin; + pinMode(this->nTransmitterPin, OUTPUT); +} + +/** + * Disable transmissions + */ +void RCSwitch::disableTransmit() { + this->nTransmitterPin = -1; +} + +/** + * Switch a remote switch on (Type D REV) + * + * @param sGroup Code of the switch group (A,B,C,D) + * @param nDevice Number of the switch itself (1..3) + */ +void RCSwitch::switchOn(char sGroup, int nDevice) { + this->sendTriState( this->getCodeWordD(sGroup, nDevice, true) ); +} + +/** + * Switch a remote switch off (Type D REV) + * + * @param sGroup Code of the switch group (A,B,C,D) + * @param nDevice Number of the switch itself (1..3) + */ +void RCSwitch::switchOff(char sGroup, int nDevice) { + this->sendTriState( this->getCodeWordD(sGroup, nDevice, false) ); +} + +/** + * Switch a remote switch on (Type C Intertechno) + * + * @param sFamily Familycode (a..f) + * @param nGroup Number of group (1..4) + * @param nDevice Number of device (1..4) + */ +void RCSwitch::switchOn(char sFamily, int nGroup, int nDevice) { + this->sendTriState( this->getCodeWordC(sFamily, nGroup, nDevice, true) ); +} + +/** + * Switch a remote switch off (Type C Intertechno) + * + * @param sFamily Familycode (a..f) + * @param nGroup Number of group (1..4) + * @param nDevice Number of device (1..4) + */ +void RCSwitch::switchOff(char sFamily, int nGroup, int nDevice) { + this->sendTriState( this->getCodeWordC(sFamily, nGroup, nDevice, false) ); +} + +/** + * Switch a remote switch on (Type B with two rotary/sliding switches) + * + * @param nAddressCode Number of the switch group (1..4) + * @param nChannelCode Number of the switch itself (1..4) + */ +void RCSwitch::switchOn(int nAddressCode, int nChannelCode) { + this->sendTriState( this->getCodeWordB(nAddressCode, nChannelCode, true) ); +} + +/** + * Switch a remote switch off (Type B with two rotary/sliding switches) + * + * @param nAddressCode Number of the switch group (1..4) + * @param nChannelCode Number of the switch itself (1..4) + */ +void RCSwitch::switchOff(int nAddressCode, int nChannelCode) { + this->sendTriState( this->getCodeWordB(nAddressCode, nChannelCode, false) ); +} + +/** + * Deprecated, use switchOn(const char* sGroup, const char* sDevice) instead! + * Switch a remote switch on (Type A with 10 pole DIP switches) + * + * @param sGroup Code of the switch group (refers to DIP switches 1..5 where "1" = on and "0" = off, if all DIP switches are on it's "11111") + * @param nChannelCode Number of the switch itself (1..5) + */ +void RCSwitch::switchOn(const char* sGroup, int nChannel) { + const char* code[6] = { "00000", "10000", "01000", "00100", "00010", "00001" }; + this->switchOn(sGroup, code[nChannel]); +} + +/** + * Deprecated, use switchOff(const char* sGroup, const char* sDevice) instead! + * Switch a remote switch off (Type A with 10 pole DIP switches) + * + * @param sGroup Code of the switch group (refers to DIP switches 1..5 where "1" = on and "0" = off, if all DIP switches are on it's "11111") + * @param nChannelCode Number of the switch itself (1..5) + */ +void RCSwitch::switchOff(const char* sGroup, int nChannel) { + const char* code[6] = { "00000", "10000", "01000", "00100", "00010", "00001" }; + this->switchOff(sGroup, code[nChannel]); +} + +/** + * Switch a remote switch on (Type A with 10 pole DIP switches) + * + * @param sGroup Code of the switch group (refers to DIP switches 1..5 where "1" = on and "0" = off, if all DIP switches are on it's "11111") + * @param sDevice Code of the switch device (refers to DIP switches 6..10 (A..E) where "1" = on and "0" = off, if all DIP switches are on it's "11111") + */ +void RCSwitch::switchOn(const char* sGroup, const char* sDevice) { + this->sendTriState( this->getCodeWordA(sGroup, sDevice, true) ); +} + +/** + * Switch a remote switch off (Type A with 10 pole DIP switches) + * + * @param sGroup Code of the switch group (refers to DIP switches 1..5 where "1" = on and "0" = off, if all DIP switches are on it's "11111") + * @param sDevice Code of the switch device (refers to DIP switches 6..10 (A..E) where "1" = on and "0" = off, if all DIP switches are on it's "11111") + */ +void RCSwitch::switchOff(const char* sGroup, const char* sDevice) { + this->sendTriState( this->getCodeWordA(sGroup, sDevice, false) ); +} + + +/** + * Returns a char[13], representing the code word to be send. + * + */ +char* RCSwitch::getCodeWordA(const char* sGroup, const char* sDevice, bool bStatus) { + static char sReturn[13]; + int nReturnPos = 0; + + for (int i = 0; i < 5; i++) { + sReturn[nReturnPos++] = (sGroup[i] == '0') ? 'F' : '0'; + } + + for (int i = 0; i < 5; i++) { + sReturn[nReturnPos++] = (sDevice[i] == '0') ? 'F' : '0'; + } + + sReturn[nReturnPos++] = bStatus ? '0' : 'F'; + sReturn[nReturnPos++] = bStatus ? 'F' : '0'; + + sReturn[nReturnPos] = '\0'; + return sReturn; +} + +/** + * Encoding for type B switches with two rotary/sliding switches. + * + * The code word is a tristate word and with following bit pattern: + * + * +-----------------------------+-----------------------------+----------+------------+ + * | 4 bits address | 4 bits address | 3 bits | 1 bit | + * | switch group | switch number | not used | on / off | + * | 1=0FFF 2=F0FF 3=FF0F 4=FFF0 | 1=0FFF 2=F0FF 3=FF0F 4=FFF0 | FFF | on=F off=0 | + * +-----------------------------+-----------------------------+----------+------------+ + * + * @param nAddressCode Number of the switch group (1..4) + * @param nChannelCode Number of the switch itself (1..4) + * @param bStatus Whether to switch on (true) or off (false) + * + * @return char[13], representing a tristate code word of length 12 + */ +char* RCSwitch::getCodeWordB(int nAddressCode, int nChannelCode, bool bStatus) { + static char sReturn[13]; + int nReturnPos = 0; + + if (nAddressCode < 1 || nAddressCode > 4 || nChannelCode < 1 || nChannelCode > 4) { + return 0; + } + + for (int i = 1; i <= 4; i++) { + sReturn[nReturnPos++] = (nAddressCode == i) ? '0' : 'F'; + } + + for (int i = 1; i <= 4; i++) { + sReturn[nReturnPos++] = (nChannelCode == i) ? '0' : 'F'; + } + + sReturn[nReturnPos++] = 'F'; + sReturn[nReturnPos++] = 'F'; + sReturn[nReturnPos++] = 'F'; + + sReturn[nReturnPos++] = bStatus ? 'F' : '0'; + + sReturn[nReturnPos] = '\0'; + return sReturn; +} + +/** + * Like getCodeWord (Type C = Intertechno) + */ +char* RCSwitch::getCodeWordC(char sFamily, int nGroup, int nDevice, bool bStatus) { + static char sReturn[13]; + int nReturnPos = 0; + + int nFamily = (int)sFamily - 'a'; + if ( nFamily < 0 || nFamily > 15 || nGroup < 1 || nGroup > 4 || nDevice < 1 || nDevice > 4) { + return 0; + } + + // encode the family into four bits + sReturn[nReturnPos++] = (nFamily & 1) ? 'F' : '0'; + sReturn[nReturnPos++] = (nFamily & 2) ? 'F' : '0'; + sReturn[nReturnPos++] = (nFamily & 4) ? 'F' : '0'; + sReturn[nReturnPos++] = (nFamily & 8) ? 'F' : '0'; + + // encode the device and group + sReturn[nReturnPos++] = ((nDevice-1) & 1) ? 'F' : '0'; + sReturn[nReturnPos++] = ((nDevice-1) & 2) ? 'F' : '0'; + sReturn[nReturnPos++] = ((nGroup-1) & 1) ? 'F' : '0'; + sReturn[nReturnPos++] = ((nGroup-1) & 2) ? 'F' : '0'; + + // encode the status code + sReturn[nReturnPos++] = '0'; + sReturn[nReturnPos++] = 'F'; + sReturn[nReturnPos++] = 'F'; + sReturn[nReturnPos++] = bStatus ? 'F' : '0'; + + sReturn[nReturnPos] = '\0'; + return sReturn; +} + +/** + * Encoding for the REV Switch Type + * + * The code word is a tristate word and with following bit pattern: + * + * +-----------------------------+-------------------+----------+--------------+ + * | 4 bits address | 3 bits address | 3 bits | 2 bits | + * | switch group | device number | not used | on / off | + * | A=1FFF B=F1FF C=FF1F D=FFF1 | 1=0FF 2=F0F 3=FF0 | 000 | on=10 off=01 | + * +-----------------------------+-------------------+----------+--------------+ + * + * Source: http://www.the-intruder.net/funksteckdosen-von-rev-uber-arduino-ansteuern/ + * + * @param sGroup Name of the switch group (A..D, resp. a..d) + * @param nDevice Number of the switch itself (1..3) + * @param bStatus Whether to switch on (true) or off (false) + * + * @return char[13], representing a tristate code word of length 12 + */ +char* RCSwitch::getCodeWordD(char sGroup, int nDevice, bool bStatus) { + static char sReturn[13]; + int nReturnPos = 0; + + // sGroup must be one of the letters in "abcdABCD" + int nGroup = (sGroup >= 'a') ? (int)sGroup - 'a' : (int)sGroup - 'A'; + if ( nGroup < 0 || nGroup > 3 || nDevice < 1 || nDevice > 3) { + return 0; + } + + for (int i = 0; i < 4; i++) { + sReturn[nReturnPos++] = (nGroup == i) ? '1' : 'F'; + } + + for (int i = 1; i <= 3; i++) { + sReturn[nReturnPos++] = (nDevice == i) ? '1' : 'F'; + } + + sReturn[nReturnPos++] = '0'; + sReturn[nReturnPos++] = '0'; + sReturn[nReturnPos++] = '0'; + + sReturn[nReturnPos++] = bStatus ? '1' : '0'; + sReturn[nReturnPos++] = bStatus ? '0' : '1'; + + sReturn[nReturnPos] = '\0'; + return sReturn; +} + +/** + * @param sCodeWord a tristate code word consisting of the letter 0, 1, F + */ +void RCSwitch::sendTriState(const char* sCodeWord) { + // turn the tristate code word into the corresponding bit pattern, then send it + unsigned long code = 0; + unsigned int length = 0; + for (const char* p = sCodeWord; *p; p++) { + code <<= 2L; + switch (*p) { + case '0': + // bit pattern 00 + break; + case 'F': + // bit pattern 01 + code |= 1L; + break; + case '1': + // bit pattern 11 + code |= 3L; + break; + } + length += 2; + } + this->send(code, length); +} + +/** + * @param sCodeWord a binary code word consisting of the letter 0, 1 + */ +void RCSwitch::send(const char* sCodeWord) { + // turn the tristate code word into the corresponding bit pattern, then send it + unsigned long code = 0; + unsigned int length = 0; + for (const char* p = sCodeWord; *p; p++) { + code <<= 1L; + if (*p != '0') + code |= 1L; + length++; + } + this->send(code, length); +} + +/** + * Transmit the first 'length' bits of the integer 'code'. The + * bits are sent from MSB to LSB, i.e., first the bit at position length-1, + * then the bit at position length-2, and so on, till finally the bit at position 0. + */ +void RCSwitch::send(unsigned long code, unsigned int length) { + if (this->nTransmitterPin == -1) + return; + +#if not defined( RCSwitchDisableReceiving ) + // make sure the receiver is disabled while we transmit + int nReceiverInterrupt_backup = nReceiverInterrupt; + if (nReceiverInterrupt_backup != -1) { + this->disableReceive(); + } +#endif + + for (int nRepeat = 0; nRepeat < nRepeatTransmit; nRepeat++) { + for (int i = length-1; i >= 0; i--) { + if (code & (1L << i)) + this->transmit(protocol.one); + else + this->transmit(protocol.zero); + } + this->transmit(protocol.syncFactor); + } + + // Disable transmit after sending (i.e., for inverted protocols) + digitalWrite(this->nTransmitterPin, LOW); + +#if not defined( RCSwitchDisableReceiving ) + // enable receiver again if we just disabled it + if (nReceiverInterrupt_backup != -1) { + this->enableReceive(nReceiverInterrupt_backup); + } +#endif +} + +/** + * Transmit a single high-low pulse. + */ +void RCSwitch::transmit(HighLow pulses) { + uint8_t firstLogicLevel = (this->protocol.invertedSignal) ? LOW : HIGH; + uint8_t secondLogicLevel = (this->protocol.invertedSignal) ? HIGH : LOW; + + digitalWrite(this->nTransmitterPin, firstLogicLevel); + delayMicroseconds( this->protocol.pulseLength * pulses.high); + digitalWrite(this->nTransmitterPin, secondLogicLevel); + delayMicroseconds( this->protocol.pulseLength * pulses.low); +} + + +#if not defined( RCSwitchDisableReceiving ) +/** + * Enable receiving data + */ +void RCSwitch::enableReceive(int interrupt) { + this->nReceiverInterrupt = interrupt; + this->enableReceive(); +} + +void RCSwitch::enableReceive() { + if (this->nReceiverInterrupt != -1) { + RCSwitch::pendingChanges = 0; + RCSwitch::nReceivedValue = 0; + RCSwitch::nReceivedBitlength = 0; +#if defined(RaspberryPi) // Raspberry Pi + wiringPiISR(this->nReceiverInterrupt, INT_EDGE_BOTH, &handleInterrupt); +#else // Arduino + attachInterrupt(this->nReceiverInterrupt, handleInterrupt, CHANGE); +#endif + } +} + +/** + * Disable receiving data + */ +void RCSwitch::disableReceive() { +#if not defined(RaspberryPi) // Arduino + detachInterrupt(this->nReceiverInterrupt); +#endif // For Raspberry Pi (wiringPi) you can't unregister the ISR + this->nReceiverInterrupt = -1; +} + +volatile uint8_t RCSwitch::pendingChanges = 0; + +bool RCSwitch::available() { + // The ISR leaves this single buffer untouched until decoding completes. + // UART interrupts remain enabled during division/protocol matching. + const uint8_t count = pendingChanges; + if (count != 0) { + for (unsigned int i = 1; i <= numProto; ++i) { + if (receiveProtocol(i, count)) break; + } + pendingChanges = 0; + } + return RCSwitch::nReceivedValue != 0; +} + +void RCSwitch::resetAvailable() { + RCSwitch::nReceivedValue = 0; +} + +unsigned long RCSwitch::getReceivedValue() { + return RCSwitch::nReceivedValue; +} + +unsigned int RCSwitch::getReceivedBitlength() { + return RCSwitch::nReceivedBitlength; +} + +unsigned int RCSwitch::getReceivedDelay() { + return RCSwitch::nReceivedDelay; +} + +unsigned int RCSwitch::getReceivedProtocol() { + return RCSwitch::nReceivedProtocol; +} + +unsigned int* RCSwitch::getReceivedRawdata() { + return RCSwitch::timings; +} + +/* helper function for the receiveProtocol method */ +static inline unsigned int diff(int A, int B) { + return abs(A - B); +} + +/** + * + */ +bool RECEIVE_ATTR RCSwitch::receiveProtocol(const int p, unsigned int changeCount) { +#if defined(ESP8266) || defined(ESP32) + const Protocol &pro = proto[p-1]; +#else + Protocol pro; + memcpy_P(&pro, &proto[p-1], sizeof(Protocol)); +#endif + + unsigned long code = 0; + //Assuming the longer pulse length is the pulse captured in timings[0] + const unsigned int syncLengthInPulses = ((pro.syncFactor.low) > (pro.syncFactor.high)) ? (pro.syncFactor.low) : (pro.syncFactor.high); + const unsigned int delay = RCSwitch::timings[0] / syncLengthInPulses; + const unsigned int delayTolerance = delay * RCSwitch::nReceiveTolerance / 100; + + /* For protocols that start low, the sync period looks like + * _________ + * _____________| |XXXXXXXXXXXX| + * + * |--1st dur--|-2nd dur-|-Start data-| + * + * The 3rd saved duration starts the data. + * + * For protocols that start high, the sync period looks like + * + * ______________ + * | |____________|XXXXXXXXXXXXX| + * + * |-filtered out-|--1st dur--|--Start data--| + * + * The 2nd saved duration starts the data + */ + const unsigned int firstDataTiming = (pro.invertedSignal) ? (2) : (1); + + for (unsigned int i = firstDataTiming; i < changeCount - 1; i += 2) { + code <<= 1; + if (diff(RCSwitch::timings[i], delay * pro.zero.high) < delayTolerance && + diff(RCSwitch::timings[i + 1], delay * pro.zero.low) < delayTolerance) { + // zero + } else if (diff(RCSwitch::timings[i], delay * pro.one.high) < delayTolerance && + diff(RCSwitch::timings[i + 1], delay * pro.one.low) < delayTolerance) { + // one + code |= 1; + } else { + // Failed + return false; + } + } + + if (changeCount > 7) { // ignore very short transmissions: no device sends them, so this must be noise + RCSwitch::nReceivedValue = code; + RCSwitch::nReceivedBitlength = (changeCount - 1) / 2; + RCSwitch::nReceivedDelay = delay; + RCSwitch::nReceivedProtocol = p; + return true; + } + + return false; +} + +void RECEIVE_ATTR RCSwitch::handleInterrupt() { + // Drop edges while the main loop owns the captured frame; the next long + // gap resynchronizes capture. Do not re-enable interrupts inside this ISR. + if (pendingChanges != 0) return; + + static unsigned int changeCount = 0; + static unsigned long lastTime = 0; + static unsigned int repeatCount = 0; + + const long time = micros(); + const unsigned int duration = time - lastTime; + + if (duration > RCSwitch::nSeparationLimit) { + // A long stretch without signal level change occurred. This could + // be the gap between two transmission. + if ((repeatCount==0) || (diff(duration, RCSwitch::timings[0]) < 200)) { + // This long signal is close in length to the long signal which + // started the previously recorded timings; this suggests that + // it may indeed by a a gap between two transmissions (we assume + // here that a sender will send the signal multiple times, + // with roughly the same gap between them). + repeatCount++; + if (repeatCount == 2) { + repeatCount = 0; + if (changeCount > 7) { + pendingChanges = static_cast(changeCount); + changeCount = 0; + lastTime = time; + return; + } + } + } + changeCount = 0; + } + + // detect overflow + if (changeCount >= RCSWITCH_MAX_CHANGES) { + changeCount = 0; + repeatCount = 0; + } + + RCSwitch::timings[changeCount++] = duration; + lastTime = time; +} +#endif diff --git a/LocalLib/RCSwitch.h b/LocalLib/RCSwitch.h new file mode 100644 index 000000000..f55e0cab3 --- /dev/null +++ b/LocalLib/RCSwitch.h @@ -0,0 +1,186 @@ +// PCController: rc-switch 2.6.4, LGPL-2.1-or-later. Decode outside the ISR. +/* + RCSwitch - Arduino libary for remote control outlet switches + Copyright (c) 2011 Suat Özgür. All right reserved. + + Contributors: + - Andre Koehler / info(at)tomate-online(dot)de + - Gordeev Andrey Vladimirovich / gordeev(at)openpyro(dot)com + - Skineffect / http://forum.ardumote.com/viewtopic.php?f=2&t=46 + - Dominik Fischer / dom_fischer(at)web(dot)de + - Frank Oltmanns / .(at)gmail(dot)com + - Max Horn / max(at)quendi(dot)de + - Robert ter Vehn / .(at)gmail(dot)com + + Project home: https://github.com/sui77/rc-switch/ + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#ifndef _RCSwitch_h +#define _RCSwitch_h + +#if defined(ARDUINO) && ARDUINO >= 100 + #include "Arduino.h" +#elif defined(ENERGIA) // LaunchPad, FraunchPad and StellarPad specific + #include "Energia.h" +#elif defined(RPI) // Raspberry Pi + #define RaspberryPi + + // Include libraries for RPi: + #include /* memcpy */ + #include /* abs */ + #include +#elif defined(SPARK) + #include "application.h" +#else + #include "WProgram.h" +#endif + +#include + + +// At least for the ATTiny X4/X5, receiving has to be disabled due to +// missing libm depencies (udivmodhi4) +#if defined( __AVR_ATtinyX5__ ) or defined ( __AVR_ATtinyX4__ ) +#define RCSwitchDisableReceiving +#endif + +// Number of maximum high/Low changes per packet. +// We can handle up to (unsigned long) => 32 bit * 2 H/L changes per bit + 2 for sync +#define RCSWITCH_MAX_CHANGES 67 + +class RCSwitch { + + public: + RCSwitch(); + + void switchOn(int nGroupNumber, int nSwitchNumber); + void switchOff(int nGroupNumber, int nSwitchNumber); + void switchOn(const char* sGroup, int nSwitchNumber); + void switchOff(const char* sGroup, int nSwitchNumber); + void switchOn(char sFamily, int nGroup, int nDevice); + void switchOff(char sFamily, int nGroup, int nDevice); + void switchOn(const char* sGroup, const char* sDevice); + void switchOff(const char* sGroup, const char* sDevice); + void switchOn(char sGroup, int nDevice); + void switchOff(char sGroup, int nDevice); + + void sendTriState(const char* sCodeWord); + void send(unsigned long code, unsigned int length); + void send(const char* sCodeWord); + + #if not defined( RCSwitchDisableReceiving ) + void enableReceive(int interrupt); + void enableReceive(); + void disableReceive(); + bool available(); + void resetAvailable(); + + unsigned long getReceivedValue(); + unsigned int getReceivedBitlength(); + unsigned int getReceivedDelay(); + unsigned int getReceivedProtocol(); + unsigned int* getReceivedRawdata(); + #endif + + void enableTransmit(int nTransmitterPin); + void disableTransmit(); + void setPulseLength(int nPulseLength); + void setRepeatTransmit(int nRepeatTransmit); + #if not defined( RCSwitchDisableReceiving ) + void setReceiveTolerance(int nPercent); + #endif + + /** + * Description of a single pule, which consists of a high signal + * whose duration is "high" times the base pulse length, followed + * by a low signal lasting "low" times the base pulse length. + * Thus, the pulse overall lasts (high+low)*pulseLength + */ + struct HighLow { + uint8_t high; + uint8_t low; + }; + + /** + * A "protocol" describes how zero and one bits are encoded into high/low + * pulses. + */ + struct Protocol { + /** base pulse length in microseconds, e.g. 350 */ + uint16_t pulseLength; + + HighLow syncFactor; + HighLow zero; + HighLow one; + + /** + * If true, interchange high and low logic levels in all transmissions. + * + * By default, RCSwitch assumes that any signals it sends or receives + * can be broken down into pulses which start with a high signal level, + * followed by a a low signal level. This is e.g. the case for the + * popular PT 2260 encoder chip, and thus many switches out there. + * + * But some devices do it the other way around, and start with a low + * signal level, followed by a high signal level, e.g. the HT6P20B. To + * accommodate this, one can set invertedSignal to true, which causes + * RCSwitch to change how it interprets any HighLow struct FOO: It will + * then assume transmissions start with a low signal lasting + * FOO.high*pulseLength microseconds, followed by a high signal lasting + * FOO.low*pulseLength microseconds. + */ + bool invertedSignal; + }; + + void setProtocol(Protocol protocol); + void setProtocol(int nProtocol); + void setProtocol(int nProtocol, int nPulseLength); + + private: + char* getCodeWordA(const char* sGroup, const char* sDevice, bool bStatus); + char* getCodeWordB(int nGroupNumber, int nSwitchNumber, bool bStatus); + char* getCodeWordC(char sFamily, int nGroup, int nDevice, bool bStatus); + char* getCodeWordD(char group, int nDevice, bool bStatus); + void transmit(HighLow pulses); + + #if not defined( RCSwitchDisableReceiving ) + static void handleInterrupt(); + static bool receiveProtocol(const int p, unsigned int changeCount); + int nReceiverInterrupt; + #endif + int nTransmitterPin; + int nRepeatTransmit; + + Protocol protocol; + + #if not defined( RCSwitchDisableReceiving ) + static int nReceiveTolerance; + volatile static unsigned long nReceivedValue; + volatile static unsigned int nReceivedBitlength; + volatile static unsigned int nReceivedDelay; + volatile static unsigned int nReceivedProtocol; + const static unsigned int nSeparationLimit; + /* + * timings[0] contains sync timing, followed by a number of bits + */ + static unsigned int timings[RCSWITCH_MAX_CHANGES]; + volatile static uint8_t pendingChanges; + #endif + + +}; + +#endif diff --git a/PCController.ino b/PCController.ino index 3912a8756..608be2a67 100644 --- a/PCController.ino +++ b/PCController.ino @@ -3,7 +3,11 @@ #include "ProjectConfig.h" +#if defined(PCCONTROLLER_NATIVE) #include +#else +#include "LocalLib/RCSwitch.h" +#endif #include "LocalLib/BoardPins.h" #include "LocalLib/DallasTemperatureBus.h" diff --git a/Tools/Firmware/tests/rf-deferred/Arduino.h b/Tools/Firmware/tests/rf-deferred/Arduino.h new file mode 100644 index 000000000..19d301250 --- /dev/null +++ b/Tools/Firmware/tests/rf-deferred/Arduino.h @@ -0,0 +1,23 @@ +#pragma once +#include +#include +#include +#include +#define PROGMEM +#define HIGH 1 +#define LOW 0 +#define OUTPUT 1 +#define CHANGE 1 +extern unsigned long fakeMicros; +extern bool inInterrupt; +extern void (*edgeHandler)(); +inline unsigned long micros() { return fakeMicros; } +inline void pinMode(int,int) {} +inline void digitalWrite(int,int) {} +inline void delayMicroseconds(unsigned int) {} +inline void attachInterrupt(int,void (*handler)(),int) { edgeHandler=handler; } +inline void detachInterrupt(int) { edgeHandler=nullptr; } +inline void* memcpy_P(void* destination,const void* source,size_t count) { + assert(!inInterrupt && "RF protocol decoding must never run in the ISR"); + return std::memcpy(destination,source,count); +} diff --git a/Tools/Firmware/tests/rf-deferred/main.cpp b/Tools/Firmware/tests/rf-deferred/main.cpp new file mode 100644 index 000000000..f2c4e7c60 --- /dev/null +++ b/Tools/Firmware/tests/rf-deferred/main.cpp @@ -0,0 +1,33 @@ +#include "Arduino.h" +#include "../../../../LocalLib/RCSwitch.h" +#include +unsigned long fakeMicros=0; +bool inInterrupt=false; +void (*edgeHandler)()=nullptr; +void edge(unsigned int duration) { + fakeMicros+=duration; assert(edgeHandler); inInterrupt=true; + edgeHandler(); inInterrupt=false; +} +void packet(unsigned int code) { + for(int bit=7;bit>=0;--bit) { + edge((code&(1< Date: Tue, 27 Oct 2026 00:02:28 +0330 Subject: [PATCH 2/5] style: normalize vendored RF source whitespace --- LocalLib/RCSwitch.cpp | 22 +++++++++++----------- LocalLib/RCSwitch.h | 14 +++++++------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/LocalLib/RCSwitch.cpp b/LocalLib/RCSwitch.cpp index 768dbb1c6..d92bccb3e 100644 --- a/LocalLib/RCSwitch.cpp +++ b/LocalLib/RCSwitch.cpp @@ -2,7 +2,7 @@ /* RCSwitch - Arduino libary for remote control outlet switches Copyright (c) 2011 Suat Özgür. All right reserved. - + Contributors: - Andre Koehler / info(at)tomate-online(dot)de - Gordeev Andrey Vladimirovich / gordeev(at)openpyro(dot)com @@ -14,8 +14,8 @@ - Robert ter Vehn / .(at)gmail(dot)com - Johann Richard / .(at)gmail(dot)com - Vlad Gheorghe / .(at)gmail(dot)com https://github.com/vgheo - - Matias Cuenca-Acuna - + - Matias Cuenca-Acuna + Project home: https://github.com/sui77/rc-switch/ This library is free software; you can redistribute it and/or @@ -58,7 +58,7 @@ /* Format for protocol definitions: * {pulselength, Sync bit, "0" bit, "1" bit, invertedSignal} - * + * * pulselength: pulse length in microseconds, e.g. 350 * Sync bit: {1, 31} means 1 high pulse and 31 low pulses * (perceived as a 31*pulselength long pulse, total length of sync bit is @@ -174,7 +174,7 @@ void RCSwitch::setReceiveTolerance(int nPercent) { RCSwitch::nReceiveTolerance = nPercent; } #endif - + /** * Enable transmissions @@ -377,7 +377,7 @@ char* RCSwitch::getCodeWordC(char sFamily, int nGroup, int nDevice, bool bStatus if ( nFamily < 0 || nFamily > 15 || nGroup < 1 || nGroup > 4 || nDevice < 1 || nDevice > 4) { return 0; } - + // encode the family into four bits sReturn[nReturnPos++] = (nFamily & 1) ? 'F' : '0'; sReturn[nReturnPos++] = (nFamily & 2) ? 'F' : '0'; @@ -413,7 +413,7 @@ char* RCSwitch::getCodeWordC(char sFamily, int nGroup, int nDevice, bool bStatus * * Source: http://www.the-intruder.net/funksteckdosen-von-rev-uber-arduino-ansteuern/ * - * @param sGroup Name of the switch group (A..D, resp. a..d) + * @param sGroup Name of the switch group (A..D, resp. a..d) * @param nDevice Number of the switch itself (1..3) * @param bStatus Whether to switch on (true) or off (false) * @@ -535,7 +535,7 @@ void RCSwitch::send(unsigned long code, unsigned int length) { void RCSwitch::transmit(HighLow pulses) { uint8_t firstLogicLevel = (this->protocol.invertedSignal) ? LOW : HIGH; uint8_t secondLogicLevel = (this->protocol.invertedSignal) ? HIGH : LOW; - + digitalWrite(this->nTransmitterPin, firstLogicLevel); delayMicroseconds( this->protocol.pulseLength * pulses.high); digitalWrite(this->nTransmitterPin, secondLogicLevel); @@ -635,7 +635,7 @@ bool RECEIVE_ATTR RCSwitch::receiveProtocol(const int p, unsigned int changeCoun const unsigned int syncLengthInPulses = ((pro.syncFactor.low) > (pro.syncFactor.high)) ? (pro.syncFactor.low) : (pro.syncFactor.high); const unsigned int delay = RCSwitch::timings[0] / syncLengthInPulses; const unsigned int delayTolerance = delay * RCSwitch::nReceiveTolerance / 100; - + /* For protocols that start low, the sync period looks like * _________ * _____________| |XXXXXXXXXXXX| @@ -715,7 +715,7 @@ void RECEIVE_ATTR RCSwitch::handleInterrupt() { } changeCount = 0; } - + // detect overflow if (changeCount >= RCSWITCH_MAX_CHANGES) { changeCount = 0; @@ -723,6 +723,6 @@ void RECEIVE_ATTR RCSwitch::handleInterrupt() { } RCSwitch::timings[changeCount++] = duration; - lastTime = time; + lastTime = time; } #endif diff --git a/LocalLib/RCSwitch.h b/LocalLib/RCSwitch.h index f55e0cab3..9986961c5 100644 --- a/LocalLib/RCSwitch.h +++ b/LocalLib/RCSwitch.h @@ -11,7 +11,7 @@ - Frank Oltmanns / .(at)gmail(dot)com - Max Horn / max(at)quendi(dot)de - Robert ter Vehn / .(at)gmail(dot)com - + Project home: https://github.com/sui77/rc-switch/ This library is free software; you can redistribute it and/or @@ -65,7 +65,7 @@ class RCSwitch { public: RCSwitch(); - + void switchOn(int nGroupNumber, int nSwitchNumber); void switchOff(int nGroupNumber, int nSwitchNumber); void switchOn(const char* sGroup, int nSwitchNumber); @@ -80,7 +80,7 @@ class RCSwitch { void sendTriState(const char* sCodeWord); void send(unsigned long code, unsigned int length); void send(const char* sCodeWord); - + #if not defined( RCSwitchDisableReceiving ) void enableReceive(int interrupt); void enableReceive(); @@ -94,7 +94,7 @@ class RCSwitch { unsigned int getReceivedProtocol(); unsigned int* getReceivedRawdata(); #endif - + void enableTransmit(int nTransmitterPin); void disableTransmit(); void setPulseLength(int nPulseLength); @@ -163,7 +163,7 @@ class RCSwitch { #endif int nTransmitterPin; int nRepeatTransmit; - + Protocol protocol; #if not defined( RCSwitchDisableReceiving ) @@ -173,14 +173,14 @@ class RCSwitch { volatile static unsigned int nReceivedDelay; volatile static unsigned int nReceivedProtocol; const static unsigned int nSeparationLimit; - /* + /* * timings[0] contains sync timing, followed by a number of bits */ static unsigned int timings[RCSWITCH_MAX_CHANGES]; volatile static uint8_t pendingChanges; #endif - + }; #endif From 4429e39cd91a60cb2e76885d71cd033016df8a76 Mon Sep 17 00:00:00 2001 From: DRSDavidSoft <4673812+DRSDavidSoft@users.noreply.github.com> Date: Tue, 27 Oct 2026 00:06:51 +0330 Subject: [PATCH 3/5] docs: record extended strip serial bench result --- docs/RF-DEFERRED-DECODE.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/RF-DEFERRED-DECODE.md b/docs/RF-DEFERRED-DECODE.md index 695e88460..16e1e6628 100644 --- a/docs/RF-DEFERRED-DECODE.md +++ b/docs/RF-DEFERRED-DECODE.md @@ -25,6 +25,11 @@ Bench investigation is ongoing: the original 38-pixel run rejected a staged packet after 498 acknowledged frames, increasing framing errors by one without CRC errors or resets. The first deferred-decoding run acknowledged 588 frames with zero framing/CRC errors, then hit a host request deadline. Longer serial and -host scheduling tests are required before claiming full resolution. Temporary +host scheduling tests are required before claiming full resolution. A subsequent +180-second, 38-pixel chase acknowledged 2,442 of 2,443 attempted frames, with no +framing errors, CRC errors, or resets. One HTTP client request exceeded its +five-second deadline; its physical outcome is unknown. Later animation frames +continued without replaying that request. This is evidence for improved UART +reception, not proof that all host latency issues are resolved. Temporary UART tracing is local bench instrumentation and must be removed before final production delivery. No physical RF remote acceptance is claimed by the fixture. From 1f2295723faff84b7b880798a33301447f3c7d6e Mon Sep 17 00:00:00 2001 From: DRSDavidSoft <4673812+DRSDavidSoft@users.noreply.github.com> Date: Tue, 27 Oct 2026 00:13:10 +0330 Subject: [PATCH 4/5] docs: checkpoint verified RF firmware and integration handoff --- .github/scripts/repository-check.mjs | 7 +++++++ docs/RF-DEFERRED-DECODE.md | 28 ++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/.github/scripts/repository-check.mjs b/.github/scripts/repository-check.mjs index 33b5b351e..29e3e534d 100644 --- a/.github/scripts/repository-check.mjs +++ b/.github/scripts/repository-check.mjs @@ -188,6 +188,13 @@ try { // the exception file- and value-specific so docs and all other source remain // subject to the normal external-repository privacy gate. const reviewedRepositoryFixtures = new Map([ + // Upstream attribution retained in the vendored LGPL RF library. + ["LocalLib/RCSwitch.cpp", new Set([ + ["https://github", ".com/sui77/rc-switch"].join(""), + ])], + ["LocalLib/RCSwitch.h", new Set([ + ["https://github", ".com/sui77/rc-switch"].join(""), + ])], ["Tools/Controller/README.md", new Set([ ["https://github", ".com/atomicdeploy/PCController"].join(""), ])], diff --git a/docs/RF-DEFERRED-DECODE.md b/docs/RF-DEFERRED-DECODE.md index 16e1e6628..313b62d96 100644 --- a/docs/RF-DEFERRED-DECODE.md +++ b/docs/RF-DEFERRED-DECODE.md @@ -33,3 +33,31 @@ continued without replaying that request. This is evidence for improved UART reception, not proof that all host latency issues are resolved. Temporary UART tracing is local bench instrumentation and must be removed before final production delivery. No physical RF remote acceptance is claimed by the fixture. + +## Bench checkpoint + +The temporary tracing was removed for firmware identity `5B86029E`. The final +build uses 32,334 flash bytes and 1,571 static SRAM bytes; the existing final-LTO +serial-response-plus-INT0 guard estimates 269 bytes of margin. The linked strip +waveform fixture passes all 256 byte values, byte boundaries and a 300-byte frame. +The programmer's immediate verify passed, but its separate readback initially +failed to synchronize. A read-only retry succeeded. Guarded recovery then verified +32,346 written bytes including the identity section, restored and verified EEPROM +settings, cleared its recovery marker and released the programming latch. + +Pealayer's individual-frame path was also exercised for 60 seconds before that +final upload: 166 frame requests were accepted, with 105 waits for its pending +operation to finish. No new board framing/CRC errors, resets or Pealayer clock +failures were observed. API acceptance is not an optical acknowledgement. This +path is suitable for manual diagnostics; smooth playback needs the host streaming +engine rather than frequent individual UI operations. + +The host connection and Pealayer automatic connection were restored after +recovery. A fresh 38-pixel configuration was acknowledged. Playback remains +paused and standalone chase/rainbow streaming is stopped so the separate playback +investigation owns effects. Long streaming validation on the final trace-free +image, the isolated host deadline, physical RF reception, synchronized playback, +the three effect demonstrations and seat stress acceptance remain open. The next +action is to reproduce the reported playback stop at its effect boundary with +the recovered host available, recording new error deltas rather than historical +maintenance disconnects. This checkpoint does not claim those acceptance gates. From cdbeac91061d0a4404235723fda729fb432ac6f2 Mon Sep 17 00:00:00 2001 From: DRSDavidSoft <4673812+DRSDavidSoft@users.noreply.github.com> Date: Tue, 27 Oct 2026 00:15:29 +0330 Subject: [PATCH 5/5] fix: bound RF indices to preserve optional profile flash budget --- LocalLib/RCSwitch.cpp | 13 +++++++------ LocalLib/RCSwitch.h | 2 +- Tools/Firmware/tests/rf-deferred/main.cpp | 17 +++++++++++++---- docs/RF-DEFERRED-DECODE.md | 9 +++++++++ 4 files changed, 30 insertions(+), 11 deletions(-) diff --git a/LocalLib/RCSwitch.cpp b/LocalLib/RCSwitch.cpp index d92bccb3e..a9929789e 100644 --- a/LocalLib/RCSwitch.cpp +++ b/LocalLib/RCSwitch.cpp @@ -582,7 +582,7 @@ bool RCSwitch::available() { // UART interrupts remain enabled during division/protocol matching. const uint8_t count = pendingChanges; if (count != 0) { - for (unsigned int i = 1; i <= numProto; ++i) { + for (uint8_t i = 1; i <= numProto; ++i) { if (receiveProtocol(i, count)) break; } pendingChanges = 0; @@ -622,7 +622,7 @@ static inline unsigned int diff(int A, int B) { /** * */ -bool RECEIVE_ATTR RCSwitch::receiveProtocol(const int p, unsigned int changeCount) { +bool RECEIVE_ATTR RCSwitch::receiveProtocol(uint8_t p, uint8_t changeCount) { #if defined(ESP8266) || defined(ESP32) const Protocol &pro = proto[p-1]; #else @@ -653,9 +653,9 @@ bool RECEIVE_ATTR RCSwitch::receiveProtocol(const int p, unsigned int changeCoun * * The 2nd saved duration starts the data */ - const unsigned int firstDataTiming = (pro.invertedSignal) ? (2) : (1); + const uint8_t firstDataTiming = (pro.invertedSignal) ? (2) : (1); - for (unsigned int i = firstDataTiming; i < changeCount - 1; i += 2) { + for (uint8_t i = firstDataTiming; i < changeCount - 1; i += 2) { code <<= 1; if (diff(RCSwitch::timings[i], delay * pro.zero.high) < delayTolerance && diff(RCSwitch::timings[i + 1], delay * pro.zero.low) < delayTolerance) { @@ -686,9 +686,10 @@ void RECEIVE_ATTR RCSwitch::handleInterrupt() { // gap resynchronizes capture. Do not re-enable interrupts inside this ISR. if (pendingChanges != 0) return; - static unsigned int changeCount = 0; + static_assert(RCSWITCH_MAX_CHANGES < 255, "RF pulse indices must fit one byte"); + static uint8_t changeCount = 0; static unsigned long lastTime = 0; - static unsigned int repeatCount = 0; + static uint8_t repeatCount = 0; const long time = micros(); const unsigned int duration = time - lastTime; diff --git a/LocalLib/RCSwitch.h b/LocalLib/RCSwitch.h index 9986961c5..c205c6616 100644 --- a/LocalLib/RCSwitch.h +++ b/LocalLib/RCSwitch.h @@ -158,7 +158,7 @@ class RCSwitch { #if not defined( RCSwitchDisableReceiving ) static void handleInterrupt(); - static bool receiveProtocol(const int p, unsigned int changeCount); + static bool receiveProtocol(uint8_t p, uint8_t changeCount); int nReceiverInterrupt; #endif int nTransmitterPin; diff --git a/Tools/Firmware/tests/rf-deferred/main.cpp b/Tools/Firmware/tests/rf-deferred/main.cpp index f2c4e7c60..0e1eb0938 100644 --- a/Tools/Firmware/tests/rf-deferred/main.cpp +++ b/Tools/Firmware/tests/rf-deferred/main.cpp @@ -8,10 +8,10 @@ void edge(unsigned int duration) { fakeMicros+=duration; assert(edgeHandler); inInterrupt=true; edgeHandler(); inInterrupt=false; } -void packet(unsigned int code) { - for(int bit=7;bit>=0;--bit) { - edge((code&(1<=0;--bit) { + edge((code&(1UL<