Motion controlled dimmable LED porch light

  • Hi all,

    Just wanted to show you all my motion-activated LED Porch Light. Lots of 12v LED lights on here, not so many mains AC-powered bulb projects, so I thought I'd show you mine. Sketch is a standard motion sensor interrupt sketch alongside a dimming sketch, I'm sure I got it from the forums. Initially I wanted it to perform local control of the lamp via the PIR but then decided I wanted the light to come on low (33%) from dusk till dawn, and then raise to 100% when it detects motion. Because of that I've left the control logic to Vera. Below about 30% the bulb shows an obvious unstable flicker so I don't go there! Dimming and ramp up/down is beautifully smooth.

    It consists of:

    Arduino Nano
    NRF24L01 radio
    HLK-PM01 240v AC>5v DC
    HC-SR501 (PIR)
    240v AC Phase control dimming circuit:
    4w dimmable filament LED bulb
    Wall box with blanking plate


    Vera/mysensors motion controlled dimmable LED porch light – 00:39
    — Patrick Blinkhorne

  • MySensors Evangelist

    @pjblink thanks for sharing. This is exactly something I can use at my parents house. At the moment I use the security camera for motion detection 😉

  • No problem, happy I could have been of some help!

    What's nice is that mysensors triggers so fast that it grabs a security cam image from before the camera has caught up!! Excuse the driveway...


  • @pjblink what a great project perfect for my needs.

    Can you post the sketch please
    Thanks Doug

  • @5546dug Hi Doug,

    Here you go. Annoyingly my PIR seems to have died after a week or so. Hoping it's a faulty unit rather than anything to do with the circuit. I've ordered another, and will see how it holds up. It seems i can trigger it by touching the unit in places, but the PIR sensor itself is dead and not detecting anything.

    AC Light Control
    Uses up and down buttons to set levels
    makes use of a timer interrupt to set the level of dimming
    #include <SPI.h>
    #include <MySensor.h>  
    #include <TimerOne.h>
    #define SN "AC LED Porch Dimmer Control"
    #define SV "1.3"
    #define NODE_ID 30  //change to a number to assign a specific ID
    #define FADE_DELAY 20  // Delay in ms for each percentage fade up/down (10ms = 1s full-range dim)
    #define MOTION_CHILD 1  //ID of the motion sensor child
    #define MOTION_PIN  2  // Arduino pin tied to trigger pin on the motion sensor.
    volatile int i=0;               // Variable to use as a counter of dimming steps. It is volatile since it is passed between interrupts
    volatile boolean zero_cross=0;  // Flag to indicate we have crossed zero
    int AC_pin = 7;                 // Output to Opto Triac
    int freqStep = 75;              // This is the delay-per-brightness step in microseconds. It allows for 128 steps
                                    // If using 60 Hz grid frequency set this to 65
    MySensor gw;
    static int currentLevel = 128;  // Current dim level...
    uint8_t fadeLevel = 128; //used to store the fade level when using the buttons
    //motion sensor
    uint8_t lastMotion = 0;
    unsigned long previousMillis = 0; // last time update //see for more details on this
    unsigned long motionDelay = 10000; // interval at which to keep motion sensor trippped (milliseconds).  Used to prevent too frequent updates to Vera. 
    boolean metric = true; 
    MyMessage dimmerMsg(AC_pin, V_DIMMER);
    MyMessage lightMsg(AC_pin, V_LIGHT);
    MyMessage motionMsg(MOTION_CHILD, V_TRIPPED);
    void setup() {  // Begin setup
      Serial.println( SN ); 
      gw.begin( incomingMessage,  NODE_ID, true);
      // Register the LED Dimmable Light with the gateway
      gw.present( 6, S_DIMMER );
      gw.sendSketchInfo(SN, SV);
      // Pull the gateway's current dim level - restore light level upon sendor node power-up
      gw.request( 6, V_DIMMER );
      gw.present(MOTION_CHILD, S_MOTION);
      //Setup AC PIN
      pinMode(AC_pin, OUTPUT);                          // Set the Triac pin as output
      attachInterrupt(1, zero_cross_detect, RISING);    // Attach an Interupt to Pin 2 (interupt 0) for Zero Cross Detection
      Timer1.initialize(freqStep);                      // Initialize TimerOne library for the freq we need
      Timer1.attachInterrupt(dim_check, freqStep);      // Go to dim_check procedure every 75 uS (50Hz)  or 65 uS (60Hz)
      // Use the TimerOne Library to attach an interrupt
    void zero_cross_detect() {    
      zero_cross = true;               // set flag for dim_check function that a zero cross has occured
      i=0;                             // stepcounter to 0.... as we start a new cycle
      digitalWrite(AC_pin, LOW);
    // Turn on the TRIAC at the appropriate time
    // We arrive here every 75 (65) uS
    // First check if a flag has been set
    // Then check if the counter 'i' has reached the dimming level
    // if so.... switch on the TRIAC and reset the counter
    void dim_check() {                   
      if(zero_cross == true) {              
        if(i>=fadeLevel) {                     
          digitalWrite(AC_pin, HIGH);  // turn on light       
          i=0;  // reset time step counter                         
          zero_cross=false;    // reset zero cross detection flag
        else {
          i++;  // increment time step counter                     
    void loop() {  
      //motion sensor code
      unsigned long currentMillis = millis();
         if(currentMillis - previousMillis > motionDelay){
          uint8_t motionDetect = digitalRead(MOTION_PIN);
            if(motionDetect != lastMotion){
    //        Serial.print("motionDetect Value: ");
    //        Serial.println(motionDetect);
              gw.send(motionMsg.set(motionDetect));  // Send tripped value to gw
              if(motionDetect == 1){
              previousMillis = currentMillis;  //"Tripped" delay 
              previousMillis = currentMillis - motionDelay + 1000; //"Not tripped" delay for 1 second to stop rapid "not tripped" and "tripped" updates to Vera
          lastMotion = motionDetect; 
    void incomingMessage(const MyMessage &message) {
      if (message.type == V_LIGHT || message.type == V_DIMMER) {
        //  Retrieve the power or dim level from the incoming request message
        int requestedLevel = atoi( );
        // Adjust incoming level if this is a V_LIGHT variable update [0 == off, 1 == on]
        requestedLevel *= ( message.type == V_LIGHT ? 100 : 1 );
        // Clip incoming level to valid range of 0 to 100
        requestedLevel = requestedLevel > 100 ? 100 : requestedLevel;
        requestedLevel = requestedLevel < 0   ? 0   : requestedLevel;
        float percent_level;
        percent_level = 128 - (requestedLevel * 1.28);
        fadeToLevel( percent_level );
        Serial.print( "Changing level to " );
        Serial.print( requestedLevel );
        Serial.print( ", from " ); 
        Serial.println( currentLevel );
        // Inform the gateway of the current DimmableLED's SwitchPower1 and LoadLevelStatus value...
        //gw.send(lightMsg.set(currentLevel > 0 ? 1 : 0));
        // hek comment: Is this really nessesary?
        // gw.send( dimmerMsg.set(requestedLevel) );
     *  This method provides a graceful fade up/down effect
    void fadeToLevel( int toLevel ) {
     Serial.print("currentLevel Value: ");
     Serial.print("toLevel Value: ");
      int delta = ( currentLevel - toLevel ) < 0 ? 1 : -1;
      Serial.print("delta Value: ");
      while ( currentLevel != toLevel ) {
        currentLevel += delta;
     fadeLevel= ((int)currentLevel);
        delay( FADE_DELAY );
        //fadeLevel = toLevel;

  • thanks @pjblink , I get lots of errors just wondering before I do anything, was this sketch wrote for 1.5or 2.0?
    this is probably my issue as I still run 1.4.1
    but thought I would run it past you first.

    thx doug

  • @pjblink :
    Did you use any fuses between the 240v and the 220 > 5v module?
    Could you post a picture of the backside of your perfboard? I'm interested in how you wired everything up 🙂

Log in to reply

Suggested Topics