Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Two receivers + why static definitions? #63

Closed
bilogic opened this issue May 23, 2016 · 3 comments
Closed

Two receivers + why static definitions? #63

bilogic opened this issue May 23, 2016 · 3 comments

Comments

@bilogic
Copy link

bilogic commented May 23, 2016

@bilogic The class uses static definitions because Arduino interrupt handler callbacks sadly only can access static / global data; and interrupt handlers cannot easily be shared for different users. In particular, they have no void *refCon parameter, which many other callback based APIs offer.

So I am really curious to learn how you bypassed that problem. But please let's not put it onto this old, closed issue; instead, submit a pull request, and/or a new issue where you explain your changes.

@fingolfin

  1. Sorry, but i have no idea how to do a pull request without forking, so I'm just going to attach my code here
  2. The code is a proof of concept, not too dirty, but can be much cleaner
  3. I'm experienced in C/C++ but very new to Arduino
  4. Initially I wanted to go the way of non static, but after reviewing the code, it was much easier to just add another buffer and interrupt handler
  5. No offence, but I wasn't convinced about the "access static" data and did a test, you can find it as non_static in my code. Interrupt handler is able to read + write to it, is my test correct?
  6. Assuming my test is correct (aka interrupt handlers can access non static variables), I think the best way is to have 2 instances of rc-switch, 1 for each receiver, keeping as much code and variables static while the buffers non static
  7. My earlier encounter with static definitions on Arduino was to prevent memory fragmentation, this was certainly a valid consideration
  8. I'm not sure if 2 or more instances will lead to any memory fragmentation or other issues

Finally, thanks for this library, I having a fantastic time turning on/off electricity :)

/*
  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 / <first name>.<last name>(at)gmail(dot)com
  - Andreas Steinel / A.<lastname>(at)gmail(dot)com
  - Max Horn / max(at)quendi(dot)de
  - Robert ter Vehn / <first name>.<last name>(at)gmail(dot)com
  - Johann Richard / <first name>.<last name>(at)gmail(dot)com
  - Vlad Gheorghe / <first name>.<last name>(at)gmail(dot)com https://github.com/vgheo

  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"

/* Format for protocol definitions:
 * {pulselength, Sync bit, "0" bit, "1" bit}
 *
 * 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.
 */
static const RCSwitch::Protocol PROGMEM proto[] = {
    { 350, {  1, 31 }, {  1,  3 }, {  3,  1 } },    // protocol 1
    { 650, {  1, 10 }, {  1,  2 }, {  2,  1 } },    // protocol 2
    { 100, { 30, 71 }, {  4, 11 }, {  9,  6 } },    // protocol 3
    { 380, {  1,  6 }, {  1,  3 }, {  3,  1 } },    // protocol 4
    { 500, {  6, 14 }, {  1,  2 }, {  2,  1 } },    // protocol 5
};

static const int numProto = sizeof(proto) / sizeof(proto[0]);

int non_static = 1184;

#if not defined( RCSwitchDisableReceiving )
unsigned long RCSwitch::nReceivedValue = 0;
unsigned int RCSwitch::nReceivedBitlength = 0;
unsigned int RCSwitch::nReceivedDelay = 0;
unsigned int RCSwitch::nReceivedProtocol = 0;
int RCSwitch::nReceiveTolerance = 60;
const unsigned int RCSwitch::nSeparationLimit = 4600;
// 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::timings0[RCSWITCH_MAX_CHANGES];
unsigned int RCSwitch::timings1[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" ???
  }
  memcpy_P(&this->protocol, &proto[nProtocol-1], sizeof(Protocol));
}

/**
  * 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 sent.
 * A code word consists of 9 address bits, 3 data bits and one sync bit but
 * in our case only the first 8 address bits and the last 2 data bits were used.
 * A code bit can have 4 different states: "F" (floating), "0" (low), "1" (high), "S" (sync bit)
 *
 * +-----------------------------+-----------------------------+----------+----------+--------------+----------+
 * | 4 bits address              | 4 bits address              | 1 bit    | 1 bit    | 2 bits       | 1 bit    |
 * | switch group                | switch number               | not used | not used | on / off     | sync bit |
 * | 1=0FFF 2=F0FF 3=FF0F 4=FFF0 | 1=0FFF 2=F0FF 3=FF0F 4=FFF0 | F        | F        | on=FF off=F0 | S        |
 * +-----------------------------+-----------------------------+----------+----------+--------------+----------+
 *
 * @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]
 */
char* RCSwitch::getCodeWordB(int nAddressCode, int nChannelCode, boolean bStatus) {
   int nReturnPos = 0;
   static char sReturn[13];

   const char* code[5] = { "FFFF", "0FFF", "F0FF", "FF0F", "FFF0" };
   if (nAddressCode < 1 || nAddressCode > 4 || nChannelCode < 1 || nChannelCode > 4) {
    return '\0';
   }
   for (int i = 0; i<4; i++) {
     sReturn[nReturnPos++] = code[nAddressCode][i];
   }

   for (int i = 0; i<4; i++) {
     sReturn[nReturnPos++] = code[nChannelCode][i];
   }

   sReturn[nReturnPos++] = 'F';
   sReturn[nReturnPos++] = 'F';
   sReturn[nReturnPos++] = 'F';

   if (bStatus) {
      sReturn[nReturnPos++] = 'F';
   } else {
      sReturn[nReturnPos++] = '0';
   }

   sReturn[nReturnPos] = '\0';

   return sReturn;
}

/**
 * Returns a char[13], representing the code word to be send.
 *
 */
char* RCSwitch::getCodeWordA(const char* sGroup, const char* sDevice, boolean bOn) {
    static char sDipSwitches[13];
    int i = 0;
    int j = 0;

    for (i = 0; i < 5; i++) {
        sDipSwitches[j++] = (sGroup[i] == '0') ? 'F' : '0';
    }

    for (i = 0; i < 5; i++) {
        sDipSwitches[j++] = (sDevice[i] == '0') ? 'F' : '0';
    }

    if (bOn) {
        sDipSwitches[j++] = '0';
        sDipSwitches[j++] = 'F';
    } else {
        sDipSwitches[j++] = 'F';
        sDipSwitches[j++] = '0';
    }

    sDipSwitches[j] = '\0';

    return sDipSwitches;
}

/**
 * Like getCodeWord (Type C = Intertechno)
 */
char* RCSwitch::getCodeWordC(char sFamily, int nGroup, int nDevice, boolean bStatus) {
  static char sReturn[13];
  int nReturnPos = 0;

  if ( (byte)sFamily < 97 || (byte)sFamily > 112 || nGroup < 1 || nGroup > 4 || nDevice < 1 || nDevice > 4) {
    return '\0';
  }

  const char* sDeviceGroupCode =  dec2binWcharfill(  (nDevice-1) + (nGroup-1)*4, 4, '0'  );
  const char familycode[16][5] = {
      "0000", "F000", "0F00", "FF00",
      "00F0", "F0F0", "0FF0", "FFF0",
      "000F", "F00F", "0F0F", "FF0F",
      "00FF", "F0FF", "0FFF", "FFFF"
      };
  for (int i = 0; i<4; i++) {
    sReturn[nReturnPos++] = familycode[ (int)sFamily - 97 ][i];
  }
  for (int i = 0; i<4; i++) {
    sReturn[nReturnPos++] = (sDeviceGroupCode[3-i] == '1' ? 'F' : '0');
  }
  sReturn[nReturnPos++] = '0';
  sReturn[nReturnPos++] = 'F';
  sReturn[nReturnPos++] = 'F';
  if (bStatus) {
    sReturn[nReturnPos++] = 'F';
  } else {
    sReturn[nReturnPos++] = '0';
  }
  sReturn[nReturnPos] = '\0';
  return sReturn;
}

/**
 * Decoding for the REV Switch Type
 *
 * Returns a char[13], representing the Tristate to be send.
 * A Code Word consists of 7 address bits and 5 command data bits.
 * A Code Bit can have 3 different states: "F" (floating), "0" (low), "1" (high)
 *
 * +-------------------------------+--------------------------------+-----------------------+
 * | 4 bits address (switch group) | 3 bits address (device number) | 5 bits (command data) |
 * | A=1FFF B=F1FF C=FF1F D=FFF1   | 1=0FFF 2=F0FF 3=FF0F 4=FFF0    | on=00010 off=00001    |
 * +-------------------------------+--------------------------------+-----------------------+
 *
 * 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]
 */

char* RCSwitch::getCodeWordD(char sGroup, int nDevice, boolean bStatus){
    static char sReturn[13];
    int nReturnPos = 0;

    // Building 4 bits address
    // (Potential problem if dec2binWcharfill not returning correct string)
    char *sGroupCode;
    switch(sGroup){
        case 'a':
        case 'A':
            sGroupCode = dec2binWcharfill(8, 4, 'F'); break;
        case 'b':
        case 'B':
            sGroupCode = dec2binWcharfill(4, 4, 'F'); break;
        case 'c':
        case 'C':
            sGroupCode = dec2binWcharfill(2, 4, 'F'); break;
        case 'd':
        case 'D':
            sGroupCode = dec2binWcharfill(1, 4, 'F'); break;
        default:
            return '\0';
    }

    for (int i = 0; i<4; i++) {
        sReturn[nReturnPos++] = sGroupCode[i];
    }


    // Building 3 bits address
    // (Potential problem if dec2binWcharfill not returning correct string)
    char *sDevice;
    switch(nDevice) {
        case 1:
            sDevice = dec2binWcharfill(4, 3, 'F'); break;
        case 2:
            sDevice = dec2binWcharfill(2, 3, 'F'); break;
        case 3:
            sDevice = dec2binWcharfill(1, 3, 'F'); break;
        default:
            return '\0';
    }

    for (int i = 0; i<3; i++)
        sReturn[nReturnPos++] = sDevice[i];

    // fill up rest with zeros
    for (int i = 0; i<5; i++)
        sReturn[nReturnPos++] = '0';

    // encode on or off
    if (bStatus)
        sReturn[10] = '1';
    else
        sReturn[11] = '1';

    // last position terminate string
    sReturn[12] = '\0';
    return sReturn;

}

/**
 * @param sCodeWord   /^[10FS]*$/  -> see getCodeWord
 */
void RCSwitch::sendTriState(const char* sCodeWord) {
  for (int nRepeat=0; nRepeat<nRepeatTransmit; nRepeat++) {
    int i = 0;
    while (sCodeWord[i] != '\0') {
      switch(sCodeWord[i]) {
        case '0':
          this->sendT0();
        break;
        case 'F':
          this->sendTF();
        break;
        case '1':
          this->sendT1();
        break;
      }
      i++;
    }
    this->sendSync();
  }
}

void RCSwitch::send(unsigned long code, unsigned int length) {
  this->send( this->dec2binWcharfill(code, length, '0') );
}

void RCSwitch::send(const char* sCodeWord) {
  for (int nRepeat=0; nRepeat<nRepeatTransmit; nRepeat++) {
    int i = 0;
    while (sCodeWord[i] != '\0') {
      switch(sCodeWord[i]) {
        case '0':
          this->send0();
        break;
        case '1':
          this->send1();
        break;
      }
      i++;
    }
    this->sendSync();
  }
}

void RCSwitch::transmit(int nHighPulses, int nLowPulses) {
    if (this->nTransmitterPin != -1) {
        #if not defined( RCSwitchDisableReceiving )
        int nReceiverInterrupt_backup = nReceiverInterrupt;
        if (nReceiverInterrupt_backup != -1) {
            this->disableReceive();
        }
        #endif
        digitalWrite(this->nTransmitterPin, HIGH);
        delayMicroseconds( this->protocol.pulseLength * nHighPulses);
        digitalWrite(this->nTransmitterPin, LOW);
        delayMicroseconds( this->protocol.pulseLength * nLowPulses);

        #if not defined( RCSwitchDisableReceiving )
        if (nReceiverInterrupt_backup != -1) {
            this->enableReceive(nReceiverInterrupt_backup);
        }
        #endif
    }
}

void RCSwitch::transmit(HighLow pulses) {
    transmit(pulses.high, pulses.low);
}

/**
 * Sends a "0" Bit
 *                       _
 * Waveform Protocol 1: | |___
 *                       _
 * Waveform Protocol 2: | |__
 */
void RCSwitch::send0() {
    this->transmit(protocol.zero);
}

/**
 * Sends a "1" Bit
 *                       ___
 * Waveform Protocol 1: |   |_
 *                       __
 * Waveform Protocol 2: |  |_
 */
void RCSwitch::send1() {
    this->transmit(protocol.one);
}


/**
 * Sends a Tri-State "0" Bit
 *            _     _
 * Waveform: | |___| |___
 */
void RCSwitch::sendT0() {
  this->send0();
  this->send0();
}

/**
 * Sends a Tri-State "1" Bit
 *            ___   ___
 * Waveform: |   |_|   |_
 */
void RCSwitch::sendT1() {
  this->send1();
  this->send1();
}

/**
 * Sends a Tri-State "F" Bit
 *            _     ___
 * Waveform: | |___|   |_
 */
void RCSwitch::sendTF() {
  this->send0();
  this->send1();
}

/**
 * Sends a "Sync" Bit
 *                       _
 * Waveform Protocol 1: | |_______________________________
 *                       _
 * Waveform Protocol 2: | |__________
 */
void RCSwitch::sendSync() {
    this->transmit(protocol.syncFactor);
}

#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::nReceivedValue = 0;
    RCSwitch::nReceivedBitlength = 0;
#if defined(RaspberryPi) // Raspberry Pi
    wiringPiISR(this->nReceiverInterrupt, INT_EDGE_BOTH, &handleInterrupt1);
#else // Arduino
    // attachInterrupt(this->nReceiverInterrupt, handleInterrupt, CHANGE);
    attachInterrupt(0, handleInterrupt0, CHANGE);
    attachInterrupt(1, handleInterrupt1, CHANGE);
#endif
  }
}

/**
 * Disable receiving data
 */
void RCSwitch::disableReceive() {
#if not defined(RaspberryPi) // Arduino
  // detachInterrupt(this->nReceiverInterrupt);
  detachInterrupt(0);
  detachInterrupt(1);
#endif // For Raspberry Pi (wiringPi) you can't unregister the ISR
  this->nReceiverInterrupt = -1;
}

bool RCSwitch::available() {
  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 various receiveProtocol methods */
static inline unsigned int diff(int A, int B) {
    return abs(A - B);
}

/**
 *
 */
bool RCSwitch::receiveProtocol(const int p, unsigned int changeCount, unsigned int interrupt) {

    Protocol pro;
    memcpy_P(&pro, &proto[p-1], sizeof(Protocol));

    unsigned int *timings;

    switch (interrupt)
    {
        case 0: timings = RCSwitch::timings0; break;
        case 1: timings = RCSwitch::timings1; break;
    }

    unsigned long code = 0;
    const unsigned int delay = timings[0] / pro.syncFactor.low;
    const unsigned int delayTolerance = delay * RCSwitch::nReceiveTolerance / 100;

    for (unsigned int i = 1; i < changeCount; i += 2) {
        code <<= 1;
        if (diff(timings[i], delay * pro.zero.high) < delayTolerance &&
            diff(timings[i + 1], delay * pro.zero.low) < delayTolerance) {
            // zero
        } else if (diff(timings[i], delay * pro.one.high) < delayTolerance &&
                   diff(timings[i + 1], delay * pro.one.low) < delayTolerance) {
            // one
            code |= 1;
        } else {
            // Failed
            return false;
        }
    }

    if (changeCount > 6) {    // ignore < 4bit values as there are no devices sending 4bit values => noise
        RCSwitch::nReceivedValue = code;
        RCSwitch::nReceivedBitlength = changeCount / 2;
        RCSwitch::nReceivedDelay = delay;
        RCSwitch::nReceivedProtocol = p;
    }

    return true;
}

void RCSwitch::handleInterrupt0() {

  static unsigned int duration;
  static unsigned int changeCount;
  static unsigned long lastTime;
  static unsigned int repeatCount;

  Serial.print("non_static: ");
  Serial.println(non_static);

  non_static = 1234;

  long time = micros();
  duration = time - lastTime;

  if (duration > RCSwitch::nSeparationLimit && diff(duration, RCSwitch::timings0[0]) < 200) {
    repeatCount++;
    changeCount--;
    if (repeatCount == 2) {
    for(unsigned int i = 1; i <= numProto; i++ ) {
        if (receiveProtocol(i, changeCount, 0)) {
            // receive succeeded for protocol i
            break;
        }
    }
      repeatCount = 0;
    }
    changeCount = 0;
  } else if (duration > RCSwitch::nSeparationLimit) {
    changeCount = 0;
  }

  if (changeCount >= RCSWITCH_MAX_CHANGES) {
    changeCount = 0;
    repeatCount = 0;
  }
  RCSwitch::timings0[changeCount++] = duration;
  lastTime = time;
}

void RCSwitch::handleInterrupt1() {

  static unsigned int duration;
  static unsigned int changeCount;
  static unsigned long lastTime;
  static unsigned int repeatCount;

  // Serial.println("interrupting 1");

  long time = micros();
  duration = time - lastTime;

  if (duration > RCSwitch::nSeparationLimit && diff(duration, RCSwitch::timings1[0]) < 200) {
    repeatCount++;
    changeCount--;
    if (repeatCount == 2) {
    for(unsigned int i = 1; i <= numProto; i++ ) {
        if (receiveProtocol(i, changeCount, 1)) {
            // receive succeeded for protocol i
            break;
        }
    }
      repeatCount = 0;
    }
    changeCount = 0;
  } else if (duration > RCSwitch::nSeparationLimit) {
    changeCount = 0;
  }

  if (changeCount >= RCSWITCH_MAX_CHANGES) {
    changeCount = 0;
    repeatCount = 0;
  }
  RCSwitch::timings1[changeCount++] = duration;
  lastTime = time;
}
#endif

/**
  * Turns a decimal value to its binary representation
  */
char* RCSwitch::dec2binWcharfill(unsigned long dec, unsigned int bitLength, char fill) {
  static char bin[32];

  bin[bitLength] = '\0';
  while (bitLength > 0) {
    bitLength--;
    bin[bitLength] = (dec & 1) ? '1' : fill;
    dec >>= 1;
  }

  return bin;
}
/*
  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 / <first name>.<last name>(at)gmail(dot)com
  - Max Horn / max(at)quendi(dot)de
  - Robert ter Vehn / <first name>.<last name>(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
    // PROGMEM och _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))
    // Include libraries for RPi:
    #include <string.h> /* memcpy */
    #include <stdlib.h> /* abs */
    #include <stddef.h> /* NULL */
    #include <wiringPi.h>
    #include <stdint.h>
    #define CHANGE 1
    // The following typedefs are needed to be able to compile RCSwitch.cpp
    // with the RPi C++ compiler (g++)
    #ifdef __cplusplus
        extern "C"{
    #endif
        typedef uint8_t boolean;
        typedef uint8_t byte;
    #ifdef __cplusplus
    }
    #endif
    // Last line within Raspberry Pi block
#else
    #include "WProgram.h"
#endif


// 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* Code);
    void send(unsigned long Code, unsigned int length);
    void send(const char* Code);

    #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

    struct HighLow {
        byte high;
        byte low;
    };

    struct Protocol {
        int pulseLength;
        HighLow syncFactor;
        HighLow zero;
        HighLow one;
    };

    void setProtocol(Protocol protocol);
    void setProtocol(int nProtocol);
    void setProtocol(int nProtocol, int nPulseLength);

  private:
    char* getCodeWordB(int nGroupNumber, int nSwitchNumber, boolean bStatus);
    char* getCodeWordA(const char* sGroup, int nSwitchNumber, boolean bStatus);
    char* getCodeWordA(const char* sGroup, const char* sDevice, boolean bStatus);
    char* getCodeWordC(char sFamily, int nGroup, int nDevice, boolean bStatus);
    char* getCodeWordD(char group, int nDevice, boolean bStatus);
    void sendT0();
    void sendT1();
    void sendTF();
    void send0();
    void send1();
    void sendSync();
    void transmit(int nHighPulses, int nLowPulses);
    void transmit(HighLow pulses);

    static char* dec2binWcharfill(unsigned long dec, unsigned int length, char fill);

    #if not defined( RCSwitchDisableReceiving )
    static void handleInterrupt0();
    static void handleInterrupt1();
    static bool receiveProtocol(const int p, unsigned int changeCount, unsigned int interrupt);
    int nReceiverInterrupt;
    #endif
    int nTransmitterPin;
    int nRepeatTransmit;

    Protocol protocol;

    #if not defined( RCSwitchDisableReceiving )
    static int nReceiveTolerance;
    static unsigned long nReceivedValue;
    static unsigned int nReceivedBitlength;
    static unsigned int nReceivedDelay;
    static unsigned int nReceivedProtocol;
    const static unsigned int nSeparationLimit;
    /*
     * timings[0] contains sync timing, followed by a number of bits
     */
    static unsigned int timings0[RCSWITCH_MAX_CHANGES];
    static unsigned int timings1[RCSWITCH_MAX_CHANGES];
    #endif


};

#endif
@fingolfin
Copy link
Collaborator

fingolfin commented May 23, 2016

Thanks. All in all, I see you did not thing special here, so I am no longer surprised. Rather, you simply replaced the existing hard-coded limitation of a single receiver by another hard-coded limitation (exactly two receivers). That is of course not hard to achieve. The difficulties I was alluding to rather are in making the code such that you can simply make one, two or three RCSwitch instances in order to use one, two or three receivers.

Anyway, let me reply to some specific points:

  1. Sorry, but i have no idea how to do a pull request without forking, so I'm just going to attach my code here

Yes, you need to make a fork in order to create a pull request. That's a feature, not a bug :-). It also makes it possible to properly review your changes, as opposed to just having to look at a big blob of code.

  1. The code is a proof of concept, not too dirty, but can be much cleaner
  2. I'm experienced in C/C++ but very new to Arduino

Then you should know that code duplication should be avoided ;-). Your code changes is doing exactly that, by duplicating the interrupt handler code.

  1. Initially I wanted to go the way of non static, but after reviewing the code, it was much easier to just add another buffer and interrupt handler
  2. No offence, but I wasn't convinced about the "access static" data and did a test, you can find it as non_static in my code. Interrupt handler is able to read + write to it, is my test correct?

Sorry, I guess I unclear in what I wrote: First off, note that static has two meanings in C/C++: the one I am referring to is the use of static variables inside a function; the example with non_static you give is about the second meaning, which restricts the visibility of a global variable to a file. So, there is no contradiction here.

Secondly, my point simply was that interrupt handlers can only access global/static variables (and of course anything reachable from that).

Now, it would easily be possible to turn the static variables inside the interrupt handler into e.g. globals. But the problem I was alluding to is that there is no good way to write a single interrupt handler function, and then use that on multiple interrupts. In a sense, you precisely confirm this, as your code duplicates the interrupt handler function.

This is a problem for a library, which now has to restrict itself to a hard coded limit of the number of interrupts it supports (currentl 1 in the case of RCSwitch). (Your patch raises that limit to 2, but it still is hard coded).

  1. Assuming my test is correct (aka interrupt handlers can access non static variables), I think the best way is to have 2 instances of rc-switch, 1 for each receiver, keeping as much code and variables static while the buffers non static
  2. My earlier encounter with static definitions on Arduino was to prevent memory fragmentation, this was certainly a valid consideration
  3. I'm not sure if 2 or more instances will lead to any memory fragmentation or other issues

I perceive two primary problems with your code:

  1. It forces a second buffer onto everybody, even people who don't need it (and who need the memory for other things).
  2. It duplicates the interrupt handler code.

Problem 1 could e.g. be addressed by a compile time switch, such as an optional #define USE_SECOND_RECEIVE_HANDLER at the top of RCSwitch.h, to enable/disable compilation of this feature. Or perhaps even better: #define MAX_NUM_OF_RECEIVERS 2 and then allow setting it to 0 (= disable receiver completly), 1 (= current behavior), 2 (your patch), and possibly even higher.

Problem 2 could be solved by factoring out the state of the interrupt handler into a struct, and the code into a separate function. This then also makes it easy to support more than two receivers, if desired. Here is a rought sketch of how that could look like:

struct InterruptHandlerState {
  unsigned int changeCount;
  unsigned long lastTime;
  unsigned int repeatCount;
};

void RCSwitch::handleInterruptGeneric(InterruptHandlerState &state, unsigned int *timings) {
  ...
}

#if MAX_NUM_OF_RECEIVERS >= 1
void RECEIVE_ATTR RCSwitch::handleInterrupt0() {
  static InterruptHandlerState state;
  handleInterruptGeneric(state0, RCSwitch::timings0);
}
#endif

#if MAX_NUM_OF_RECEIVERS >= 2
void RECEIVE_ATTR RCSwitch::handleInterrupt1() {
  static InterruptHandlerState state;
  handleInterruptGeneric(state, RCSwitch::timings1);
}
#endif

@bilogic
Copy link
Author

bilogic commented May 23, 2016

@fingolfin
You are right, I retested by placing non_static in the RCSwitch.h in order to use this->non_static and it doesn't compile anymore. I know the code is not clean, but it was the quickest way to meet my use case and anyone who needs exactly two receivers. :)

@bilogic bilogic closed this as completed May 23, 2016
@fingolfin
Copy link
Collaborator

Of course! Everybody does that, me certainly included :-). Thank you for your input, though, it made me think about this again: While there may be no "perfect" solution, one could still add a working solution which at least helps people who need two receivers; and if done right, it should not "harm" people who need no or just a single receiver.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants