Danish LK wall switch



  • In this use case, I need to control my Philips Hue light. The issue with Philips Hue lights bulbs are that they need to be connected to mains always for controlling them. If the light bulbs are turned off by the wall switch, you cannot turn them on again remotely. You have to go to activate the wall switch again. I don’t want to accidentally turn off the power to the light bulbs, so I cannot control them. And I do not want to use the separate switches sold by Philips. I want to use my existing wall switches only.

    So, I have solved this issue in a combination with MySensors, Domoticz and the Philips Hue gateway. In this description I have only described the wall switch part.

    The obvious choice is to build a self-contained MySensor switch into the wall. Complete with a DC power converter and a relay. The issue here is the Danish wall switches produced by LK. They are more or less a standard in DK. They have a very nice look, but the casing behind the switch are small, and leave not much room for electronics.
    0_1528225003888_2ec9bff4-fbc7-49b2-b640-dffe97f245e8-billede.png
    0_1528225016188_cd0a43b8-86dc-4172-ab6e-2fbec0280b24-billede.png

    It would be impossible for me to create such a small self-contained relay unit, using standard components. Another issue is regulation and insurance. I am not quite sure if I would be covered, if the electronics – against all odds, should caught fire within the wall.
    So I decided to create a battery driven MySensor switch, that would fit nicely inside the LK housing, using the existing LK switch.

    I had these requirements for the switch:
    • Should fit inside the LK casing, using the existing LK switch
    • Long range
    • Low power consumption
    • Robust and self-healing
    • Easy to perform reset and re-programming
    • Follow regulations

    Small housing
    I made the PCB as small as I could, fitting it into the LK housing. As I develop and etch the PCB myself, I have to use single side print. All components are SMD to achieve the smallest possible footprint.
    0_1528225107566_33fb24ed-f00b-4f11-8f1d-97f85ea82dc0-billede.png
    0_1528225610486_638c8c84-3cb7-42eb-aae3-a3d39e2df21a-billede.png

    Long range
    I have made several experiments with NRF24L01+ in the house. In my case they were not a good choice. The range are simply not good enough, and installing them inside walls make it worse. I need these things to work – every single time.
    The choice was RFM69 using 433MHz. This frequency has an extreme good coverage, and I have not seen any lost packages using that radio.

    Low power
    To achieve low power for extending battery life, two main things can be addressed. Reduce the clock frequency of the Atmega328 and bring it to Sleep mode when not used.
    In my experiments I first used 1MHz as the clock frequency. But when I did some analysis of the data packages sent, I could see that there were data loss. It worked, but it was not reliable or stable. I then tried the 8MHz clock frequency. And here all data packages came through. So I ended up with an 8MHz bootloader.
    Also, I used a coin cell for a small foot print and for easy changing of battery. To achieve long life, I used the CR2477 type. It has the capacity of 1000mAh.
    0_1528225149200_3b3f98b5-9ae2-4965-9c81-a455b4037533-billede.png

    Self-healing
    As the switch would be installed behind the wall switch, it would also mean that easy access to it would not be possible. So it has to be a robust design that requires no maintenance. Sometimes electronics can fail without any obvious cause. So I decided to implement a Watch Dog to reset the processor if it should fail. That should catch most problems. I use the MAX6369 for that.

    Re-programming
    New versions of MySensors emerge now and then. Maybe I need to upgrade to a version later in time. And maybe I need to reprogram the switch for other functions – who knows. It could also be that I need to clear the memory of the switch for some reason. So an easy way of doing this would be required.
    When I program the switch, I use a USBasp programmer. I solder the wires to the same pads as where the RFM69 are placed. But as the RFM69 should not be applied with more than 3.3V, and the Atmega328 are programmed with a higher voltage, I need to remove the radio before programming. When the RFM69 first are soldered to the PCB, it is almost impossible to unmount. So I have made the PCB pads to just align with the radio, but not letting the copper parts touch each other. That way the radio can easily be de-soldered and the Atmega re-programmed.
    I have applied two switches for Reset and Memory purposes. The Reset switch just resets the Atmega. But when the Memory switch are pressed at the same time as the Reset switch (or the battery inserted), a small code that clear the memory are executed.
    0_1528225178578_fbf25073-a1de-4d28-9802-34c74869ac3d-billede.png

    Regulations
    I need to have the power outlet on the ceiling to always carry 240V. This is for the Philips Hue light bulbs to be connected all the time. In order to do that, I assemble and connect the 240V wires in the LK housing, so that the power is always on. Doing this inside the LK housing is the right way as to avoid breaking any rules or regulations regarding high voltage installation. And it is completely safe.
    As there is no way to do this assembly within the LK housing where the wall switch is, I drove the wires down into the second housing and assembled them there.
    0_1528225208687_24479755-2689-49c9-a453-ce381055349d-billede.png

    Conclusion
    Everything works like a charm. All requirements are fulfilled, and the solution even have been accepted by the wife. The reason I know this, is that there have been no complaints or sarcastic comments. It is a complete wife transparent solution. It just works.

    The Code

    // 8MHz Bootloader
    // Enable debug prints to serial monitor
    //#define MY_DEBUG
    //#define MY_BAUD_RATE 19200
    
    //#define MY_RADIO_NRF24
    #define MY_RADIO_RFM69
    #define MY_RFM69_FREQUENCY RFM69_433MHZ
    #define CHILD_ID_0 0
    #define CHILD_ID_1 1
    #define CLEAR_EEPROM_PIN 8  // Arduino Digital I/O pin for clear EEPROM
    
    #include <MySensors.h>
    #include <EEPROM.h>
    
    volatile int batteryTimer = 1440;
    volatile boolean ack = false;
    
    MyMessage msg1(CHILD_ID_0, V_LOCK_STATUS);
    MyMessage msg2(CHILD_ID_1, V_VOLTAGE);
    
    void before()
    {
      ///////////////////////////////// Clear EEPROM as the first thing, if butten is pressed /////////////////////////////////////////////////////
      pinMode(CLEAR_EEPROM_PIN, INPUT_PULLUP); // Setup the clear EEPROM button and activate internal pull-up
      int value = digitalRead(CLEAR_EEPROM_PIN);  // read input value
      if (value == LOW) {         // check if the input is LOW (button closed)
        for (int i = 0; i < 512; i++) {
          EEPROM.write(i, 0xff);
        }
      }
      ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    }
    
    void presentation()
    {
        sendSketchInfo("Wall switch - single", "2.2.0");
        wait(100);
        present(CHILD_ID_0, S_LOCK);
        wait(100);
        present(CHILD_ID_1, S_MULTIMETER);
        wait(100);
    }
    
    void setup()
    {
      pinMode(7, OUTPUT);  // WatchDog pin. Initialize digital pin 7 as an output.
      digitalWrite(7, LOW); // Set pin to Low
      pinMode(3, INPUT_PULLUP);// Interrupt pin. Configure wake up pin 3 as Interrupt input with pullup.
      
      transmitValue(digitalRead(3));
      wait(100);
      batterystatus();
      wait(100);
    }
    
    
    void loop()
    {
      int trigger = sleep(1, CHANGE, 50000); // Use Interrupt 1, wake up on 'Change' signal, sleep for 50 seconds or until interrupt. Returns interrupt number.
    
      if (trigger == 1) {  //Interrupt triggred by sensor;
          transmitValue(digitalRead(3));
          watchDog();
      } else { //Triggered by 50 seconds timer interrupt
        watchDog();
        batterystatus();
      }
    }
    
    ////////////////////////////////////////////////// WatchDog trigger - keep alive signal ////////////////////////////////////
    void watchDog()
    {
      digitalWrite(7, HIGH);
      wait(1); 
      digitalWrite(7, LOW);
    }
    
    ////////////////////////////////////////////////// Send battery status every 24 hours ////////////////////////////////////
    void batterystatus()
    {
      if (batteryTimer == 1440) {
        float batteryPcnt = readVcc();
        send(msg2.set(batteryPcnt, 3)); 
        batteryTimer = 0;
      }
      ++batteryTimer; 
    }
    
    ////////////////////////////////////////////////// Transmit session ///////////////////////////////////////////////////
    void transmitValue(int value)
    {
      ack = false;
      int retries = 0;
      while (ack == false) { // Keep sending until 'ack' are recieved from GW
        send(msg1.set(value), true);
        wait(150);
        if (retries == 5) { // But stop after 5 retries. This saves battery.
          ack = true;
        }
        ++retries;
      }
    }
    
    void receive(const MyMessage &message) {
      if (message.isAck()) {
        ack = true;
      }
    }
    
    
    float readVcc() {
      // Read 1.1V reference against AVcc
      // set the reference to Vcc and the measurement to the internal 1.1V reference
    #if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
      ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
    #elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
      ADMUX = _BV(MUX5) | _BV(MUX0);
    #elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
      ADMUX = _BV(MUX3) | _BV(MUX2);
    #else
      ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
    #endif
    
      delay(2); // Wait for Vref to settle
      ADCSRA |= _BV(ADSC); // Start conversion
      while (bit_is_set(ADCSRA, ADSC)); // measuring
    
      uint8_t low  = ADCL; // must read ADCL first - it then locks ADCH
      uint8_t high = ADCH; // unlocks both
    
      long result = (high << 8) | low;
      result = 1125300L / result; // Calculate Vcc (in mV); 1125300 = 1.1*1023*1000
      float battery = result / 1000.0;
      return battery; // Vcc in volts```

 

309
Online

8.0k
Users

8.8k
Topics

94.3k
Posts