Plugging arduino to wall switch

I wanted to use the existing wall switch to arduino as an input. I’ve got two switches and I just dont know how to code nor wire everything up. Suggestions? Here’s what I tried.

And the code:

#define NSWITCHES 2

int switch_iterator;
int switches[NSWITCHES] = {2,3};
int readingSwitch[NSWITCHES];
int previousSwitch[NSWITCHES];
int switchPin = 0;

void setup()
{
    Serial.begin(9600);
    for (switch_iterator = 0; switch_iterator < NSWITCHES; switch_iterator++) {
      pinMode(switches[switch_iterator], INPUT);
      previousSwitch[switch_iterator] = digitalRead(switches[switch_iterator]);
    }

}

void loop()
{
	if (wasSwitchToggled()) {
		Serial.print(switchPin);
	}
}

boolean wasSwitchToggled() {  
        boolean toggled = false;
        for (switch_iterator = 0; switch_iterator < NSWITCHES; switch_iterator++) {
                switchPin = switch_iterator;
                readingSwitch[switch_iterator] = digitalRead(switches[switch_iterator]);
                
                if (readingSwitch[switch_iterator] != previousSwitch[switch_iterator]) {
                  toggled = true;
                }
  
                previousSwitch[switch_iterator] = readingSwitch[switch_iterator];
        }
return toggled;
}

There are a few parts to this… A switch is a switch. It’s open or closed, no different with a wall switch.

Simplest way to wire a switch to an Arduino is from a digital input pin to ground. Enable the internal pull-up resistor, and you will read a 0 when the switch is closed:

pinMode(SWITCH, INPUT);
digitalWrite(SWITCH, 1);

Your schematic is nearly OK (pull-down resistors are a good thought to have) except that the resistors are too small (use 10k instead to reduce power consumption) and you did not connect the breadboard’s power+ground lines to the arduino, so your switches are entirely floating and will not work at all.

The next step is you need to do de-bouncing. You can do it analogue with an R and C, or you can do it in software: don’t recognise the switch as having changed state if it has already changed state in the last 20ms.

When you say “wall switch”, is it going to be in a wall? Is it going to be a significant distance from your arduino, or will the arduino be right behind the switch? If there is more than a couple of metres of cable then you need to worry about noise being induced in the long cable, and protecting the Arduino’s input pins. Have a look into opto-isolation, for example. Clearly you don’t want to be doing the noise+safety analysis at this point, so keep your switch cable to under a metre and well away from power lines.

Try making a Switch/Button abstraction and then instantiating lots of copies of it, rather than trying to make a vectorised switch with arrays of bits and arrays of previous states and other craziness like that. For example below:

/**
 * Debouncing of a single button to generate keypress events.
 */
class ButtonDebounce {
public:

    ButtonDebounce(char p, bool inv=true);

    /// inspect pin
    void scan();

    /// return current pin state
    bool getCurrentState() const;

    /// return true ONCE for each keydown
    bool hadPress();

private:
    char pin;
    bool state, event, invert;
    unsigned long changedat;

    // 20 ms must elapse since keyup before a new keydown will register
    static const unsigned long TIMEOUT=20000;
};

ButtonDebounce::ButtonDebounce(char p, bool inv)
    : pin(p), invert(inv)
{
    pinMode(pin, INPUT);
    digitalWrite(pin, 1);
    state=false;
    event=false;
    changedat=0;
}

void ButtonDebounce::scan()
{
    bool newstate=digitalRead(pin) ^ invert;

    if(newstate != state){
        unsigned long now=micros();

        // changed after long-enough time
        if(now - changedat > TIMEOUT){
            changedat=now;
            state=newstate;
            if(state)
                event=true;
        }
    }
}

bool ButtonDebounce::getCurrentState() const
{
    return state;
}

bool ButtonDebounce::hadPress()
{
    bool res=event;
    event=false;
    return res;
}

/// four input switches with debouncing, inverted inputs
const int SWITCH_COUNT=4;
ButtonDebounce switches[SWITCH_COUNT]={ ButtonDebounce(9, true), ButtonDebounce(10, true), ButtonDebounce(11, true), ButtonDebounce(12, true) };

void loop()
{
  for(int i=0;i<SWITCH_COUNT;++i){
    switches[i].scan();

    if(switches[i].getCurrentState()){
      // switch i is ON
    }
    if(switches[i].hadPress()){
      // switch i was just pressed on (e.g. a pushbutton event)
      // probably not relevant for an on/off switch.
    } 
  }
}

edited to add: your wall switch should in no way ever be connected to the mains if it is also connected to the arduino. Don’t go near mains power unless you are qualified to do so because it is illegal+deadly.

Thank you very much for the elegant and fast answer.

Yesterday I was a little bit busy so I had to rush to make that fritzing. Cant make it work though: I’m wiring like this, but it doesn’t seem like it is working. As for the resistor, I’m using a 1k Ohm. Should I change it? The power led flickers a little bit when trying to push the switch.

When you say “wall switch”, is it going to be in a wall? Is it going to be a significant distance from your arduino, or will the arduino be right behind the switch? If there is more than a couple of metres of cable then you need to worry about noise being induced in the long cable, and protecting the Arduino’s input pins. Have a look into opto-isolation, for example. Clearly you don’t want to be doing the noise+safety analysis at this point, so keep your switch cable to under a metre and well away from power lines.

I think I’m working pretty much close to power lines, so I’ll have a look into opto-isolation, thanks.
Also, I’m going to change from relays to triacs: they dont make that click noise and can dimmer.

edited to add: your wall switch should in no way ever be connected to the mains if it is also connected to the arduino. Don’t go near mains power unless you are qualified to do so because it is illegal+deadly.

Thank you for the warning: the arduino is plugged only to the switch, and the power lines are connected to the relay.

#define NDEV 4
int pins[NDEV] = {5, 6, 7, 8};
boolean on[NDEV] = {false, false, false, false};
int switchPin = 0;
int led_iterator;

/**
 * Debouncing of a single button to generate keypress events.
 */
class ButtonDebounce {
public:

    ButtonDebounce(char p, bool inv=true);

    /// inspect pin
    void scan();

    /// return current pin state
    bool getCurrentState() const;

    /// return true ONCE for each keydown
    bool hadPress();

private:
    char pin;
    bool state, event, invert;
    unsigned long changedat;

    // 20 ms must elapse since keyup before a new keydown will register
    static const unsigned long TIMEOUT=20000;
};

ButtonDebounce::ButtonDebounce(char p, bool inv) : pin(p), invert(inv)
{
    pinMode(pin, INPUT);
    digitalWrite(pin, 1);
    state=false;
    event=false;
    changedat=0;
}

void ButtonDebounce::scan()
{
    bool newstate=digitalRead(pin) ^ invert;

    if(newstate != state){
        unsigned long now=micros();

        // changed after long-enough time
        if(now - changedat > TIMEOUT){
            changedat=now;
            state=newstate;
            if(state)
                event=true;
        }
    }
}

bool ButtonDebounce::getCurrentState() const
{
    return state;
}

bool ButtonDebounce::hadPress()
{
    bool res=event;
    event=false;
    return res;
}

/// four input switches with debouncing, inverted inputs
const int SWITCH_COUNT=2;
ButtonDebounce switches[SWITCH_COUNT]={ ButtonDebounce(2, true), ButtonDebounce(3, true) };

void setup()
{
    // multiple outputs - iterates through all NDEVices
    for(led_iterator = 0; led_iterator < NDEV; led_iterator++) {
      pinMode(pins[led_iterator], INPUT);
      digitalWrite(pins[switchPin], LOW);
    }
}

void loop()
{
	if (wasSwitchToggled()) {
		switchRelayState(true);
	}
}

void switchRelayState(boolean localTrigger)
{
    if (on[switchPin]) {
      digitalWrite(pins[switchPin], LOW);
      on[switchPin] = false;
    } else {
      digitalWrite(pins[switchPin], HIGH);
      on[switchPin] = true;
    }
    
    if (localTrigger) {
      delay(300); // delay to avoid interference
      sendStateMessage(on[switchPin]); // feedback to the mainframe
    }
}

boolean wasSwitchToggled() 
{
  for(int i=0;i<SWITCH_COUNT;++i){
    switches[i].scan();
    switchPin = i;
    
    if(switches[i].getCurrentState()){
      // switch toggled
      return true;
    }

  }
  return false;
}

Your current fritzing now has the switches short-circuited by plugging both ends into the same breadboard line. However since the power light flickers on a button-press:

  • clearly you have it wired differently than the diagram, and
  • I suspect your wiring is causing a short when the switch is closed.

1k is OK for a pull-up/down resistor but if you’re using device’s internal pullups you don’t need the resistor. Just put a switch between the pin and ground. Or if you really want the resistor, put the resistor to +5 and the switch to ground.

Your code is kind of hard to follow, but I suspect what’s happening is that it will try to toggle the relay output every 300ms while you hold the button down. Or if localTrigger=false, it will toggle the relay at a few hundred kHz. Note that if one switch is held down, the other is not scanned by your code.

Note the “invert” parameter to the ButtonDebounce constructor. Set it true if your switch is to ground, set it false if the switch is to +5.

I think you might want something more like the following, which has NDEV (momentary pushbutton, normally open) switches and outputs; each time you press a switch it will toggle the corresponding output pin.

#define NDEV 2
int pins[NDEV] = {5, 6};  // output pins
bool state[NDEV] = {false, false};
ButtonDebounce switches[NDEV]={ ButtonDebounce(2, true), ButtonDebounce(3, true) };

void setup()
{
  for(int i=0;i<NDEV;++i){
    digitalWrite(pins[i], state[i]);
    pinMode(pins[i], OUTPUT);
  }
}

void loop()
{
  for(int i=0;i<NDEV;++i){
    switches[i].scan();
    // look for switch events
    if(switches[i].hadPress()){
      state[i] = !state[i];
    }
    // once you have your communications framework going, you poll it here:
    if(comms_says_toggle[i]){
      state[i] = !state[i];
    }
    digitalWrite(pins[i], state[i]);
  }
}

The above assumes you want to use a communications channel (serial, wireless, whatever) to remotely change state and therefore the relays at any time, plus any press of the switch will cause the output to toggle too. So you get to control from both sources. With a slight modification (delete the “if(state)” line near the end of ButtonDebounce::scan), you will get a hadPress event for every switch state-change, which makes it useful with rocker switches instead of pushbuttons.
I still beg you to stay away from mains at your current skill level. No offense intended, but if you can’t get this to work on the very first try then you need to not be trying to interface with mains power. Triacs are a useful tool, but dimming with them using a micro is far, far more complex than this.
Can I suggest that you use this circuit to control some low voltage downlights? You can put the relay in the 12V line between the lighting transformer and the lights. You can dim DC lights pretty safely using PWM on a big MOSFET; look into a “buck converter”. A couple of capacitors, diode, inductor and a FET will get you a very powerful DC light dimmer. You can also dim low voltage AC lights with a triac - the technique you use will depend on what sort (magnetic or switchmode) of power supply your lights have.

Just put a switch between the pin and ground.

Yes sir.

Thanks for the OOP design advice, I don’t know why I didn’t think of this approach before.
The following code is from the new main. Still needs some working, but it’s getting shape

#include <VirtualWire.h>
#include <RFCode.h>
#include <Debounce.h>
#include <RelayDevice.h>


int switchPin = 0;
char msg[1]; // for sending messages
const int RELAY_COUNT = 2;
const int RX_MODULE_PIN = 11;
const int TX_MODULE_PIN = 4;

/** Dont construct things inside constructors - RFCode part is working, strangely*/
RelayDevice relays[RELAY_COUNT] = 
			{RelayDevice(5, RFCode('A', 'a', 'B', 'b'), 2, true), 
			 RelayDevice(6, RFCode('C', 'c', 'D', 'd'), 3, true)/*,
			 RelayDevice(7, ButtonDebounce(12, true), RFCode('E', 'e', 'F', 'f')),
			 RelayDevice(8, ButtonDebounce(13, true), RFCode('G', 'g', 'F', 'f'))*/};

void setup()
{
	/** Initializing wireless communications */
	vw_setup(2000);	                  // Bits per sec
	vw_set_ptt_inverted(true);        // Required for DR3100
	vw_set_rx_pin(RX_MODULE_PIN);
	vw_set_tx_pin(TX_MODULE_PIN);
	vw_rx_start();
}

void loop()
{
	for(int i = 0; i < RELAY_COUNT; i++){
		relays[i].getSwitch().scan();

		if(relays[i].getSwitch().hadPress()){ // look for switch events
			relays[i].switchState(); // switch states
			
			delay(300); // delay to avoid interference
			feedbackStateMessage(i); // feedback to the mainframe
		}
	}

	if (wasMessageReceived()) {
		relays[switchPin].switchState();
	}
}

boolean wasMessageReceived()
{
	uint8_t buf[VW_MAX_MESSAGE_LEN] = "";
	uint8_t buflen = VW_MAX_MESSAGE_LEN;

	if (vw_get_message(buf, &buflen)) { // if received message
		for(int i = 0; i < RELAY_COUNT; i++) { 
			switchPin = i;
			boolean rightString = true;
			if (strlen((char*)buf) == 1) { //one char to id message
				if (relays[i].getState() && (uint8_t)relays[i].getRFCode().getOffCode() != buf[0]) { // ON and code is not OFF
					rightString = false;
				} else if ((uint8_t)relays[i].getRFCode().getOnCode() != buf[0]) { // OFF and code is not ON
					rightString = false;
				}
			}
			if (rightString) { // if it checked all letters and it's ok
				return true;
			}
		} // iterates through all relays
	} // if message received
	return false;
}

void feedbackStateMessage(int i)
{
	vw_rx_stop(); // stop listening so that it can send messages
	
	if (relays[i].getState()) { // feedbacks that the appliance is on HIGH
		msg[0] = relays[i].getRFCode().getFeedbackOnCode();
	} else {  // feedbacks that the appliance is on LOW
		msg[0] = relays[i].getRFCode().getFeedbackOffCode();
	}
	vw_send((uint8_t *)msg, strlen(msg)); // send the message
	vw_wait_tx(); // wait until all message is gone
	
	vw_rx_start(); // start listening so that it can receive messages
	vw_wait_rx(); // wait until rx receives a message
}

The code for the RelayDevice is the following

#include "Arduino.h"
#include <RFCode.h>
#include <Debounce.h>
#include "RelayDevice.h"

RelayDevice::RelayDevice(int p, RFCode code, int b, bool inv) : pin(p), _toggle_switch(b, inv) {
    pinMode(pin, OUTPUT);
    digitalWrite(pin, LOW);
    _code = code;
    state = false;
}
ButtonDebounce RelayDevice::getSwitch() {
    return _toggle_switch;
}
boolean RelayDevice::getState() {
    return state;
}
RFCode RelayDevice::getRFCode() {
    return _code;
}
void RelayDevice::switchState() {
    Serial.print("SwitchME");
    state = !state;
    digitalWrite(pin, state);
}

Today I’m going to test it. Was having some Java programmer issues: not being able to pass objects as parameters (not using pointers) is troublesome.

Thanks for the constant feedback. I’m working with a prototype, so not going to mains until everything is robust.
Wanted to try some other solutions on how to switch AC appliances, and RF is not working out for me - sure it’s cheap - but since I’m going to have lots of IoT appliances, the noise/interference will destroy me.

lol you don't need to call me sir, I'm not a knight.

Anyway, your RelayDevice::getSwitch won't work because it returns a copy of the switch object. You need to return it by reference (note the ampersand):

ButtonDebounce &RelayDevice::getSwitch()
{
  return switch;
}

Because you were making a copy, any methods called would be on the copy. So the event flag would be cleared only on the copy and therefore hadKeypress() will always return true.

The same syntax applies when passing parameters to a function, they can go by reference and not be copied. The only real trap with references is that you must never return a reference to a local variable in a function because it won't exist after the function returns.

An even better approach would be to put the contents of your main loop into a RelayDevice::poll() function that does all of the necessary logic. Classes are meant to be complete abstractions and therefore should contain in their methods everything relevant to their operation.

PS I would suggest separately testing the inputs and outputs, eg displaying the current switch state to the LED (D13) and writing a trivial test program that toggles the relay once a second, just so you can make sure your circuits are right before worrying about the code.

Okay, I’m trying to poll it from the RelayDevice class itself but I’m not getting the hang of this operator.

RelayDevice::RelayDevice(int p, RFCode code, int b, bool inv) : pin(p), _toggle_switch(b, inv) {
    pinMode(pin, OUTPUT);
    digitalWrite(pin, LOW);
    _code = code;
    state = false;
}
ButtonDebounce &RelayDevice::getSwitch() {
    return _toggle_switch;
}
boolean RelayDevice::getState() {
    return state;
}
RFCode RelayDevice::getRFCode() {
    return _code;
}
void RelayDevice::switchState() {
    state = !state;
    digitalWrite(pin, state);
}
boolean RelayDevice::poll() {
    _toggle_switch.scan();
    if (_toggle_switch.hadPress()) {
        switchState();
        return true;
    }
    return false;
}

I’m trying to

	for(int i = 0; i < RELAY_COUNT; i++){
                if (relays[i].poll()) {
			Serial.print("switched");
			delay(300); // delay to avoid interference
			feedbackStateMessage(i); // feedback to the mainframe
                }
	}

but I dont think I’ve initialized ButtonDebounce, since output wont work…
Going to test it separately
edit: mistake I made
edit2: tested only the output and it’s not working out… the output changes only when I change the state of the switch twice with this code

boolean state = false;
ButtonDebounce button1(2, true);
void setup()
{
pinMode(5, OUTPUT);
}

void loop()
{
  button1.scan();
  if (button1.hadPress()) {
      digitalWrite(5, state);
          state = !state;

  }

See my earlier post about removing the "if(state)" bit. That line of code in ButtonDebounce::scan will set event boolean only when the button turns on, therefore hadKeypress() returns true on button-down. If you remove that line, event will be set for every (debounced) state change on the switch which is what I think you want.

And in your test code above, the contents if the if() are backwards. The output will be set only on the next loop iteration.

Thanks. The input is working now.

Need to make the output work now...

RelayDevice relays[RELAY_COUNT] = 
            {RelayDevice(5, RFCode('A', 'a', 'B', 'b'), 2, true), 
             RelayDevice(6, RFCode('C', 'c', 'D', 'd'), 3, true)};

isn't working.

i don't think this kind of initialization is really storaging the instance of ButtonDebounce on _toggle_switch...

Constructor

RelayDevice::RelayDevice(int p, RFCode code, int b, bool inv) : pin(p), _toggle_switch(b, inv) {
    pinMode(pin, OUTPUT);
    digitalWrite(pin, LOW);
    _code = code;
    state = false;
}

Class

class RelayDevice {
public:
    RelayDevice(int p, RFCode code, int b, bool inv=true);
    boolean getState();
    RFCode getRFCode();
    void switchState();
    boolean poll();
    ButtonDebounce _toggle_switch;

private:
    int pin;
    RFCode _code;
    bool state;
};

As a matter of good object design, the ButtonDebounce should be private in the RelayDevice, but that won't break it.

Your constructor syntax looks mostly right, though you have constructor calls out of order (constructors must be called in the same order as the members are declared in the class) but again, that shouldn't break it.

What do you mean "isn't working" ? Does it compile? Did you initialise Serial properly in your setup? Use the LED pin for debugging purposes to make sure that certain parts of your code are being reached, e.g. copying the switch-state to the LED inside RelayDevice::poll() will prove to you whether the switch is being polled, etc, even if Serial misbehaves.

I don't think you need the 300ms delay either. The only purpose it could serve is to prevent the serial output overflowing if you pressed the button a few hundred times a second, and the debounce code will prevent that.

Thanks for all the feedback!

It was a anoying lock from

//vw_wait_rx(); // wait until rx receives a message (NO! this locks the arduino, so no more loop iterations)!!!

It was making me crazy! Anyways, it's now working. Thanks a lot! No! I'm being serious, you saved me from hours of 'debugging'.

The arduino pullup is not enought though... Going to isolate it well, as you advised The 300ms delay is so I cause no radio frequency interference in other devices - this device is working in half-duplex, so I must listen, stop, send, listen.