Skip to content
  • MySensors
  • OpenHardware.io
  • Categories
  • Recent
  • Tags
  • Popular
Skins
  • Light
  • Brite
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dark
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • Default (No Skin)
  • No Skin
Collapse
Brand Logo
  1. Home
  2. Development
  3. Watchdog not watchdogging?

Watchdog not watchdogging?

Scheduled Pinned Locked Moved Development
28 Posts 10 Posters 3.2k Views 10 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • alowhumA Offline
    alowhumA Offline
    alowhum
    Plugin Developer
    wrote on last edited by
    #21

    @tiana Thanks, but I'm trying to avoid adding more hardware as I am creating nodes that I want beginners to be able to make easily.

    1 Reply Last reply
    0
    • alowhumA Offline
      alowhumA Offline
      alowhum
      Plugin Developer
      wrote on last edited by
      #22

      @mfalkvidd Here's the sketch you requested. Work in progress..

      /*
       * 
       * GENTLE ALARM CLOCK
       * 
       * This is a smart alarm clock: 
       * - It takes into account your sleep cycle and tries to wake you up at an opportune moment in that cycle.
       *   It does this in the 30 minutes before the alarm time you set. So if you set 8AM, it will find the best moment in the 7:30AM till 8AM window.
       * - Additionally, it has wake-up light functinality to slowly wake you up. If that doesn't do it, it will sound an audio alarm.
       * 
       * Hardware:
       * - Arduino Nano
       * - Arduino Nano wireless expansion board
       * - NRF24 radio (to connect to MySensors controller)
       * - OLED screen
       * - Rotary encoder knob (KY-040)
       * - Motion sensor (radar type works well, but you can also use PIR)
       * - Bright LED. It should support setting varying brightness levels.
       * - Buzzer (could also trigger an MP3 player, or a voice recorder - have fun!)
       * 
       * 
       * How it works:
       * - It gets the clock time from the controller.
       * - You can set the alarm time (and enable or disable the alarm) with the rotary knob.
       * 
       * - Each minute it detects and sends the total seconds of movement during the previous minute.
       *   You can use a motion sensor of your choice for this. Point it at your sleepingplace in the bed.
       * 
       * 
       * SETTINGS */ 
      
      byte motionThreshold = 8;                   // If the total motion count over the past 5 minutes is this or higher, then the alarm will start ringing.
      
      // Do you want encrypted communication? If you do, then all devices in your network need to use the same password.
      //#define MY_ENCRYPTION_SIMPLE_PASSWD "changeme"
      
      
       /* 
       * 
       * 
       * 
       * 
       * 
       * TO-DO
       * - Decide on a measurement value to send to the controller. "custom" would make sense, but may not be as widely supported by controllers.
       * - Toggle the alarm on-off status from the controller. This allows the workday logic to move to the controller.
       * - Turn off the screen (or show less data on it) during the night. It lower screen brightness somehow.
       * - Fix bug where audio alarm can stay on despite rotating the knob to turn it off.
       * - Check if internal clock is somewhat accurate.
       * - Make rotating knob work better. Maybe use a library..
       * - Add snooze?
       * 
       * NICE-TO-HAVE
       * - Calculate the work days (mon-fri) on-device, and offer a toggle to only sound the alarm on those days.
       * - Maybe add another separate button for enabling/disabling the alarm.
       * - make the minute loop counter work by adding up 60 loops of a second. But might make the clock very imprecise.
       * - Sensitivity dial in controller
       */
      
      
      
      //
      // SETTINGS
      //
      
      // Enable and select the attached radio type
      #define MY_RADIO_RF24                               // This is a common and simple radio used with MySensors. Downside is that it uses the same frequency space as WiFi.
      //#define MY_RADIO_NRF5_ESB                         // This is a new type of device that is arduino and radio all in one. Currently not suitable for beginners yet.
      //#define MY_RADIO_RFM69                            // This is an open source radio on the 433mhz frequency. Great range and built-in encryption, but more expensive and little more difficult to connect.
      //#define MY_RADIO_RFM95                            // This is a LoRaWan radio, which can have a range of 10km.
      
      // MySensors: Choose your desired radio power level. High and max power can cause issues on cheap Chinese NRF24 radios.
      //#define MY_RF24_PA_LEVEL RF24_PA_MIN
      //#define MY_RF24_PA_LEVEL RF24_PA_LOW
      #define MY_RF24_PA_LEVEL RF24_PA_HIGH
      //#define MY_RF24_PA_LEVEL RF24_PA_MAX
      
      // Mysensors security
      #define MY_SIGNING_SOFT_RANDOMSEED_PIN A7         // Setting a pin to pickup random electromagnetic noise helps make encryption more secure.
      
      // Mysensors advanced settings
      #define MY_TRANSPORT_WAIT_READY_MS 10000            // Try connecting for 10 seconds. Otherwise just continue.
      //#define MY_RF24_CHANNEL 100                       // In EU the default channel 76 overlaps with wifi, so you could try using channel 100. But you will have to set this up on every device, and also on the controller.
      //#define MY_RF24_DATARATE RF24_1MBPS               // Slower datarate makes the network more stable?
      //#define MY_NODE_ID 10                             // Giving a node a manual ID can in rare cases fix connection issues.
      //#define MY_PARENT_NODE_ID 0                       // Fixating the ID of the gatewaynode can in rare cases fix connection issues.
      //#define MY_PARENT_NODE_IS_STATIC                  // Used together with setting the parent node ID. Daking the controller ID static can in rare cases fix connection issues.
      #define MY_SPLASH_SCREEN_DISABLED                   // Saves a little memory.
      //#define MY_DISABLE_RAM_ROUTING_TABLE_FEATURE      // Saves a little memory.
      
      // Enable MySensors debug output to the serial monitor, so you can check if the radio is working ok.
      //#define MY_DEBUG 
      
      // MySensors devices form a mesh network by passing along messages for each other. Do you want this node to also be a repeater?
      #define MY_REPEATER_FEATURE                         // Add or remove the two slashes at the beginning of this line to select if you want this sensor to act as a repeater for other sensors. If this node is on battery power, you probably shouldn't enable this.
      
      #define ONE_SECOND 1000                             // How many milliseconds does a second last?
      #define LOOPDURATION 60000                          // The main loop runs every x milliseconds. It's like a minute counter on a clock.
      //#define MEASUREMENT_INTERVAL 5                    // After a number of loops we start again.
      
      
      // LIBRARIES (in the Arduino IDE go to Sketch -> Include Library -> Manage Libraries to add these if you don't have them installed yet.)
      #include <MySensors.h>                              // MySensors library                  
      
      #define HAS_DISPLAY                                 // Remove this line if you are not using an OLED screen on the node.
      
      #ifdef HAS_DISPLAY
        #define INCLUDE_SCROLLING 0
        #define OLED_I2C_ADDRESS 0x3C
        #include <SSD1306Ascii.h>                         // Simple drivers for the OLED screen.
        #include <SSD1306AsciiAvrI2c.h>
        SSD1306AsciiAvrI2c oled;
      #endif
      
      // Clock variables
      byte hours = 0;
      byte minutes = 0;
      uint32_t unixTime = 0;
      
      // The lines below may be useful for a future feature.
      // leap year calulator expects year argument as years offset from 1970
      //#define LEAP_YEAR(Y)     ( ((1970+(Y))>0) && !((1970+(Y))%4) && ( ((1970+(Y))%100) || !((1970+(Y))%400) ) )
      //static  const uint8_t monthDays[]={31,28,31,30,31,30,31,31,30,31,30,31}; // API starts months from 1, this array starts from 0
      
      
      // Alarm variables
      #define SADEH_MOTION_THRESHOLD 2                    // How many movements a minute will we consider as enough to officially count as 'light sleep'
      boolean alarmSet = false;                           // Has the user enabled the alarm?
      boolean alarmSearching = false;                     // Set to true if we are in the 30 minutes before the deadline. We are waiting for the best exact moment to wake up the user now.
      boolean alarmRinging = false;                       // Wake up! Set to true if the opportune moment has been found. Light (and later audio) should be on now.
      byte alarmHours = 0;
      byte alarmMinutes = 0;
      byte displayHours = 0;
      byte displayMinutes = 0;
      
      // Speaker
      #define SPEAKER_PIN 7                               // The pin where the speaker is connected.
      
      // Rotary encoder knob
      #define ROTARY_CLK_PIN A0                           // Connected to CLK on the KY-040 rotary encoder
      #define ROTARY_DT_PIN A1                            // Connected to DT on the KY-040 rotary encoder
      #define ROTARY_SWITCH_PIN A2                        // Connected to SW on the KY-040 rotary encoder
      int previousRotaryValue;                            // The previous value read from the rotary encoder, to compare against.
      boolean rotarySwitchPressed = 0;                    // The state of the push button on the rotary encoder.
      boolean lastKnobDirection = 1;                      // 0 = Counter clockwise, 1 = clockwise.
      
      // Sadeh algorithm variables
      byte consecutiveSleepMinutesRadar = 0;
      boolean detectedREM = false;
      byte minutesSinceREM = 0;
      
      
      // Motion sensor details
      #define MOTION_SENSOR_PIN 3                         // On what pin is the radar sensor connected?
      
      
      // LED details
      #define LED_PIN 4
      #define LED_PWM_LENGTH 50                         // MICROseconds that each PWN up-and-down phase lasts. You may have too fine-tune this for your LED.
      int brightness = 0;
      byte brightnessPercentage = 0;
      
      // Mysensors settings.
      #define RADIO_DELAY 100                             // Milliseconds between sending radio signals. This keeps the radio happy.
      #define CHILD_ID_STATUS 0                           // Child ID of the sensor
      #define CHILD_ID_MOTION_SENSOR 1                    // Child ID of the sensor
      #define CHILD_ID_SET_ALARM 2                        // Allows the alarm to be turned on or off from the controller.
      #define CHILD_ID_RINGING 3                          // Allows the controller to set other devices in the room to turn on when the alarm clock is ringing.
      #define CHILD_ID_SENSITIVITY 4                    // Set the movement threshold from the controller interface.
      
      
      MyMessage statusMessage(CHILD_ID_STATUS,V_TEXT);    // Sets up the message format that we'll be sending to the MySensors gateway later. The first part is the ID of the specific sensor module on this node. The second part tells the gateway what kind of data to expect.
      MyMessage motionMessage(CHILD_ID_MOTION_SENSOR, V_TEMP);  // Sets up the message format that we'll be sending to the MySensors gateway later.
      MyMessage dimmerMessage(CHILD_ID_RINGING, V_PERCENTAGE);// Create a dimmer that can be used on the controller to set the value of, for example. another lamp.
      MyMessage relayMessage(CHILD_ID_SET_ALARM, V_STATUS); // Allow the controller to enable or disable the alarm
      
      
      void presentation()
      {
        // send the sketch version information to the gateway and Controller
        sendSketchInfo(F("Gentle alarm clock"), F("1.6")); wait(RADIO_DELAY);
          
        // Register all child sensors with the gateway
        present(CHILD_ID_STATUS, S_INFO, "Status"); wait(RADIO_DELAY);          // General status of the device, as well as the sleep phase
        present(CHILD_ID_MOTION_SENSOR, S_TEMP, "Motions"); wait(RADIO_DELAY);  // Total motion count for the past five minutes
        present(CHILD_ID_RINGING, S_DIMMER, "Dimmer"); wait(RADIO_DELAY);       // The level of the built-in wake-up LED is also mirrored to the controller. That way you could perhaps use some automation to set another lamp in the room to also slowly rise in brightness.
        present(CHILD_ID_SET_ALARM, S_BINARY, "Alarm"); wait(RADIO_DELAY);      // Allow the controller to turn the alarm on or off.
        present(CHILD_ID_SENSITIVITY, S_DIMMER, "Sensitivity"); wait(RADIO_DELAY);      // Set the motion count threshold that will trigger alarm.
      }
      
      
      void setup() 
      {
        // Output updates over the serial port
        Serial.begin(115200);
        while (!Serial) {}                                // Is this really necessary?
        Serial.println(F("Hello world!"));
      
        pinMode(MOTION_SENSOR_PIN, INPUT);                // Set motion sensor pin as input
       
        // LED
        pinMode(LED_PIN, OUTPUT);                         // Set the LED pin as output
        analogWrite(LED_PIN, 0);                          // Let's test the LED light
        wait(2000);
        analogWrite(LED_PIN, 255);
        wait(2000);
        analogWrite(LED_PIN, 0);
      
      
        // rotary encoder knob
        pinMode (ROTARY_CLK_PIN,INPUT);                   // Rotary encoder clock pin
        pinMode (ROTARY_DT_PIN,INPUT);                    // Rotary encoder data pin
        pinMode(ROTARY_SWITCH_PIN,INPUT_PULLUP);          // Rotary encoder switch pin
        previousRotaryValue = digitalRead(ROTARY_CLK_PIN); 
        rotarySwitchPressed = digitalRead(ROTARY_SWITCH_PIN);
        
      #ifdef HAS_DISPLAY
        // Start the display (if there is one)
        oled.begin(&Adafruit128x64, OLED_I2C_ADDRESS);
        oled.setFont(Adafruit5x7);
        
        oled.ssd1306WriteCmd(SSD1306_DISPLAYON);
        oled.setScroll(false);
        oled.setCursor(0,0);
        oled.print(F("ALARM CLOCK"));
      #endif
      
        // Check if there is a network connection
        if(isTransportReady()){
          Serial.println(F("Connected to gateway!"));
      
          send(statusMessage.setSensor(CHILD_ID_STATUS).set( F("Hello world") )); wait(RADIO_DELAY);
          send(dimmerMessage.set(0)); wait(RADIO_DELAY);  // Reset the dimmer level to 0.
          requestTime(); wait(RADIO_DELAY);               // Request the current time from the controller.
          
          //Serial.print(F("Time: ")); Serial.println(controllerTime);
      #ifdef HAS_DISPLAY
          // Show connection icon on the display
          oled.setCursor(90,0);
          oled.print(F("W"));
      #endif
        }else{
          Serial.println(F("! NOCONNECTION"));
      #ifdef HAS_DISPLAY    
          oled.setCursor(90,0);
          oled.print(F(" "));
      #endif    
        }
      
      
        // Get last known preferences from onboard storage
        alarmHours = loadState(1);                       // To what hour was the alarm set?
        if(alarmHours > 24){alarmHours = 8;}
        
        alarmMinutes = loadState(2);                     // To which minure was the alarm set?
        if(alarmMinutes > 55){alarmMinutes = 0;}
        
        alarmSet = loadState(3);                         // Was the alarm set to on or off?
        if(alarmSet > 1){alarmSet = 1;}
        
        if(loadState(4) != 255){
          motionThreshold = loadState(4);                // What is the desired sensitivity
        }
        
        wdt_enable(WDTO_8S);                              // Starts the watchdog timer. If it is not reset once every few seconds, then the entire device will automatically restart.                                
      }
      
      
      void loop()
      {
      
      
      /*  the main loop has four levels:
       *  - Continuously:
       *  - - Check if the rotary knob has been turned.
       *  - Flickering: this runs at the rate of 1000 times per second. 
       *  - - It is used to PWM the LED.
       *  - Flutter: this runs once a second. It does things like 
       *  - - Increase the LED brightness once the alarm is in ringing mode, and 
       *  - - Check if the motion sensor is in its active state.
       *  - Heartbeat: this runs once a minute. 
       *  - - It takes note of the total movement over the last minute, and sends along the data.
       *  - - It checks if it's time to wake up the user. 
       *  - - It also updates the minute counter on the display
       *  
       */
      
        // Main loop variables
        static unsigned long lastLoopTime = 0;            // Holds the last time the main loop ran.
        static int loopCounter = 0;                       // Count how many heartbeat loops (minutes) have passed.
      
        // Creating variables to track sleep.
        static byte motionCounter = 0;                    // The movement count for the last minute.
        static byte movementsList[5];                     // An arrray (list) that stores the last 5 motionCounter values.
        static int motionTotal = 0;                       // Total movement count for the past 5 minutes
      
      
      
        // Rotary knob
        static int rotaryValue;
        static boolean takeStep = 0;
        rotaryValue = digitalRead(ROTARY_CLK_PIN);
        if (rotaryValue != previousRotaryValue){          // Check if the rotary encoder knob is rotating
          alarmRinging = false;                           // In case the alarm was ringing, it should be turned off now.
          noTone(SPEAKER_PIN);                            // In case the alarm was still making sound somehow, turn it off.
          takeStep = !takeStep;                           // On every loop though this gets changed into its opposite. So 0 -> 1 -> 0 -> 1 etc
          if(takeStep){                                   // If we have already ignored a step, then go further.
            // Check in which direction it's rotating.
            if (digitalRead(ROTARY_DT_PIN) != rotaryValue) {
              Serial.println(F("Counterclockwise"));
      
              if(lastKnobDirection == 0){                 // Rotating left rapidly decreases the alarm time.
                if(alarmMinutes <= 30){
                  alarmMinutes = 30;
                  if(alarmHours == 0){alarmHours = 23;}else{alarmHours--;}
                }
                if(alarmMinutes >= 30){
                  alarmMinutes = 30;
                }
                Serial.print(F("New alarm hours: ")); Serial.println(alarmHours); 
                lastKnobDirection = 1;
              }
              lastKnobDirection = 0;
            } else {                                      // Rotating right slowly increases the alarm time.
              Serial.println (F("Clockwise"));
              alarmMinutes = alarmMinutes + 5;
              if(alarmMinutes > 55){
                alarmMinutes = 0; 
                alarmHours = alarmHours + 1;
                if(alarmHours > 23){alarmHours = 0;}
              }
              Serial.print(F("New alarm: ")); Serial.print(alarmHours); Serial.print(F(":")); Serial.println(alarmMinutes);   
              lastKnobDirection = 1;
            }
          }
      #ifdef HAS_DISPLAY
        updateClockDisplay();
      #endif     
        }
        previousRotaryValue = rotaryValue;
      
      
        // Check if the rotary knob button is being pressed.
        // boolean switchPosition = digitalRead(ROTARY_SWITCH_PIN);
        if (digitalRead(ROTARY_SWITCH_PIN) == 1 && rotarySwitchPressed == 0){
          rotarySwitchPressed = 1;
          Serial.println(F("Button pressed"));
      
          if(alarmSearching || alarmRinging){
            turnOffRinging();
          }else{
            alarmSet = !alarmSet;                         // If the alarm is not ringing, then pressing the button turns the alarm setting on or off completely.
          }
      #ifdef HAS_DISPLAY
          updateClockDisplay();
      #endif
          wait(50);
        } else if (digitalRead(ROTARY_SWITCH_PIN) == 0){
          rotarySwitchPressed = 0;
        }
      
      
       #ifdef HAS_DISPLAY  
        oled.set1X();
        oled.setCursor(110,0);                            // In the top-right corner..
        if(!alarmRinging){                                // If the alarm is not going off, show the motion count for this minute.
          oled.print(motionCounter); oled.println(F("   "));
        }else if (brightness < 255){                      // Otherwise, show the brightness level of the LED (at least intil it reaches 255).
          oled.setCursor(104,0);
          oled.print(F("*")); oled.print(brightnessPercentage); oled.println(F("   "));
        }else{
          oled.setCursor(104,0);
          oled.println(F("    "));
        }
        
       #endif
      
      
      
        //
        // FLICKERING
        // Software pulse width modulation (PWM) for the high brightness LED, using modulo.
        //
      
        if(alarmRinging == true){
      
          unsigned int pulsy = map(brightness,0,255,0,LED_PWM_LENGTH); // Depending on the intended brightness level, the LED should be on during a proportianl period of the PWM time.
      
          if(micros() % LED_PWM_LENGTH < pulsy ){
            digitalWrite(LED_PIN, 1);
            //Serial.print(F("|")); // For debugging
          }else{
            digitalWrite(LED_PIN, 0);
            //Serial.print(F(".")); // For debugging
          }
        }else{
          digitalWrite(LED_PIN, 0);  
        }
         
      
        //
        // FLUTTER
        // Runs every second. By counting how often this loop has run (and resetting that counter back to zero after a number of loops), it becomes possible to schedule all kinds of things without using a lot of memory.
        //
      
        static boolean loopDone = false;                        // used to make sure the 'once every millisecond' things only run once every millisecond (or 2.. sometimes the millis() function skips a millisecond.);
      
        // Allow the next loop to only run once. This entire construction saves memory by not using a long to store the last time the loop ran.
        if( (millis() % ONE_SECOND) > ONE_SECOND - 4 && loopDone == true ) {
          loopDone = false;  
        }
      
        // Main loop to time actions.
        if( (millis() % ONE_SECOND) < 4 && loopDone == false ) { // this module's approach to measuring the passage of time saves a tiny bit of memory.
          loopDone = true;
          
          
          //if (millis() - lastLoopTime > ONE_SECOND) {
          //lastLoopTime = millis(); // this variable is now already used by the minute counter.
      
          wdt_reset(); // Reset the watchdog timer. If the device crashes, then the watchdog won't be reset, and this will in turn cause it to reset the entire device.
      
          // Check if the movement sensor is seeing movement.
          boolean motionState = digitalRead(MOTION_SENSOR_PIN);
          if (motionState == HIGH) {
            motionCounter++;
            Serial.print(F("~"));
            Serial.print(motionCounter);
          }
      
          // WAKE UP! Every second we make the LED a little brighter.
          if(alarmRinging == true){
            brightness++;
            brightnessPercentage = map(brightness, 0, 255, 0, 100); 
            send(dimmerMessage.set(brightnessPercentage));
            Serial.print(F("Sending dimmer brightness: *")); Serial.println(brightness);
          }else{
            brightness = 0;
          }
      
          // WAKE UP! Make noise.
          if(alarmRinging == true && brightness > 255 && brightness < 386){
            Serial.print(F("**")); Serial.println(brightness);
            if (brightness % 2 == 0){ // Makes noise every other second.
              tone(SPEAKER_PIN, 1000);
            }else{
              noTone(SPEAKER_PIN);  
            }
          }
      
          // WAKE UP! The user should be awake by now.
          if(brightness == 765){                           // Turn of the light and the alarm
            //alarmSearching = false;
            turnOffRinging();
          }
      
        }
      
      
      
      
      
      
        //
        // HEARTBEAT
        // Runs every minute. By counting how often this loop has run (and resetting that counter back to zero after a number of loops), it becomes possible to schedule all kinds of things without using a lot of memory.
        //
      
      
        if (millis() - lastLoopTime >= LOOPDURATION) {
          lastLoopTime = millis();
          loopCounter++;
          if(loopCounter > 5){
            Serial.print(F("loopCounter ")); Serial.println(loopCounter);  
            loopCounter = 1;
            requestTime();
      
            Serial.print(F("Minutes since REM phase: ")); Serial.println(minutesSinceREM);
          }
      
          // Fun but incomplete stuff to detect REM phases
          if(detectedREM && minutesSinceREM < 250){
            minutesSinceREM++;
          }
      
          // Update the clock
          unixTime += 60;                                 // Add a minute to the clock. Maybe adding 59 is better?
          breakUpTime(unixTime);                          // Turn the unix time into human-readable time
          updateClockDisplay();
      
      
          // Save the alarm details. (They will only be overwritten if they have changed).
          saveState(1, alarmHours);
          saveState(2, alarmMinutes);
          saveState(3, alarmSet);
          saveState(4, motionThreshold);
      
      
          // ALARM ACTIVE CHECK - Should we start the wake up procedure?
          if(alarmSet == true && hours == alarmHours && minutes == alarmMinutes){
            Serial.println(F("ALARM SET - Starting wake up procedure: alarm now in searching phase"));
            alarmSearching = true;  // Here we enable searching for the right moment to wake up.
            send(statusMessage.setSensor(CHILD_ID_STATUS).set( F("Starting wake up") )); wait(RADIO_DELAY);
          }
      
      
          // If there was movement for 5 minutes, then the user is sleeping lightly, and its now time to WAKE UP!
          // if(alarmSet == true && alarmSearching == true && movementsList[1] >= SADEH_MOTION_THRESHOLD && movementsList[2] >= SADEH_MOTION_THRESHOLD && movementsList[3] >= SADEH_MOTION_THRESHOLD &&  movementsList[4] >= SADEH_MOTION_THRESHOLD && movementsList[5] >= SADEH_MOTION_THRESHOLD){
          if(alarmSet == true && alarmSearching == true && motionTotal >= motionThreshold){ // if there was a high movement count in the last 5 minutes, then the user is in a light sleep moment. 
            alarmSearching = false;                       // We are no longer looking for the right moment to ring the alarm, since we just found it.
            alarmRinging = true;                          // Time to really ring the alarm!
          }
      
          // If we are in the alarm active phase, but there was not a good moment to wake up the user, then at the end just sound the alarm at the defined alarm time. Like a normal alarmclock.
          if(alarmSet == true && alarmSearching == true && hours == displayHours && minutes == displayMinutes){
            alarmRinging = true;
            // maybe set the brightness high here too?
          }
      
      
          // SADEH algorithm. All this is not really required.
          static byte consecutiveSleepMinutesMotion = 0;
          static byte consecutiveAwakeMinutesMotion = 0;
          if(motionCounter < SADEH_MOTION_THRESHOLD){
            consecutiveAwakeMinutesMotion = 0;
            if(consecutiveSleepMinutesMotion < 250){
              consecutiveSleepMinutesMotion++;
            }else if (consecutiveSleepMinutesMotion == 15){
              detectedREM = true;
              send(statusMessage.setSensor(CHILD_ID_STATUS).set( F("DEEP SLEEP") )); wait(RADIO_DELAY);
            }
          }else{
            consecutiveSleepMinutesMotion = 0;
            if(consecutiveAwakeMinutesMotion < 250){
              consecutiveAwakeMinutesMotion++;
            }
            if(consecutiveAwakeMinutesMotion == 5){
              
              if(detectedREM && minutesSinceREM > 60){
                Serial.println(F("Light sleep after REM"));
                send(statusMessage.setSensor(CHILD_ID_STATUS).set( F("Light sleep after rem") )); wait(RADIO_DELAY);
                minutesSinceREM = 0; //  we found the light sleep phase in a good time segment after the rem phase. So reset this counter.
              }else{
                Serial.println(F("Light sleep"));
                send(statusMessage.setSensor(CHILD_ID_STATUS).set( F("Light sleep") )); wait(RADIO_DELAY);
              }
              
              // Definitely moved away from the REM phase, so reset those variables:
              consecutiveSleepMinutesMotion = 0;
              detectedREM = false;
            }    
          }
      
      
          movementsList[loopCounter] = motionCounter;
          Serial.print(F("Loop number and motion sensor movements: ")); Serial.print(loopCounter); Serial.print(F(" -> ")); Serial.println(movementsList[loopCounter]);
      
          motionTotal = movementsList[1] + movementsList[2] + movementsList[3] + movementsList[4] + movementsList[5];
      
          Serial.print(F("Sending motion total:")); Serial.println(motionTotal);
          send(motionMessage.set(motionTotal)); wait(RADIO_DELAY);
          
          // We ask the server to acknowledge that it has received the data. It it doesn't, remove the connection icon.
          if( send(motionMessage.set(motionTotal)) ){ // was ),1) ){
            Serial.println(F("Connection is ok"));
      #ifdef HAS_DISPLAY
            // add W icon
            oled.set1X();
            oled.setCursor(90,0);
            oled.print(F("W"));
      #endif
          }else {
            Serial.println(F("Connection lost"));
      #ifdef HAS_DISPLAY
            // remove W icon
            oled.set1X();
            oled.setCursor(90,0);
            oled.print(F(" "));
      #endif          
          }
      
          // every loop (minute) the movement counter is reset.
          motionCounter = 0;
       
        }
      }
      
      void turnOffRinging()
      {
        if(brightness != 0){
          Serial.println(F("Send: resetting dimmer to 0"));
          send(dimmerMessage.set(0));  
        }
        brightness = 0;
        alarmRinging = false;                         // If the alarm is ringing, then pressing the button stops the ringing. The alarm will still be set for the next day.
        alarmSearching = false;                       // Turning the alarm during the searching phase means we should no longer look for a moment to ring the alarm.
        noTone(SPEAKER_PIN);
      }
      
      
      void receive(const MyMessage &message)
      {
        Serial.print("__Incoming change for child: ");
        Serial.println(message.sensor);
        if (message.type==V_STATUS && message.sensor == CHILD_ID_SET_ALARM) { // Toggle of alarm on or off
          Serial.println(F("__RECEIVED ALARM TOGGLE"));
          // Change alarm state
          alarmSet = message.getBool()?1:0;
          if(alarmSet == false && (alarmSearching || alarmRinging)){
            turnOffRinging();
          }
          updateClockDisplay();
          // Write some debug info
        }
        if (message.type == V_PERCENTAGE && message.sensor == 4) { // If it's the desired sensitivity level
          //  Retrieve the power or dim level from the incoming request message
          int receivedSensitivity = atoi( message.data );
          motionThreshold = byte(receivedSensitivity);
          saveState(4, motionThreshold);
          Serial.print(F("Requested motion threshold is "));
          Serial.println( motionThreshold );
      
        }  
      }
      
      
      void receiveTime(unsigned long controllerTime) {
        Serial.print(F("Received time: ")); Serial.println(controllerTime);
        unixTime = controllerTime;
        breakUpTime(unixTime);
      #ifdef HAS_DISPLAY
        updateClockDisplay();                             // Update the hours on the display
      #endif
      }
      
      #ifdef HAS_DISPLAY
      void updateClockDisplay()                           // Update clock time display
      {
          oled.set2X();                                   // Switch to large font size
          oled.setCursor(30,2);
          if(hours < 10){ oled.print(F(" ")); }
          oled.print(hours); 
          oled.print(F(":")); 
          if(minutes < 10){ oled.print(F("0")); }
          oled.print(minutes);
      
          // This is important: the display shows the alarm time as being 30 minutes later than the internal alarm time. This is done for more easy programming, since we mostly case about the moment 30 minutes before the alarm should go off.
          displayHours = alarmHours;
          if(alarmMinutes >= 30){displayHours++;}
          if(displayHours > 23){displayHours = 0;}
      
          displayMinutes = (alarmMinutes + 30) % 60;
      
          //update alarm time display    
          oled.setCursor(17,5); 
          if(!alarmSet){
            oled.print(F("           ")); 
            oled.set1X();
            oled.setCursor(25,5);
          }
          if(displayHours < 10){oled.print(F(" "));}
          oled.print(displayHours);
          //oled.print(hours); // Used for debugging
          oled.print(F(":")); 
          if(displayMinutes < 10){ oled.print(F("0")); }
          oled.print(displayMinutes);
          //oled.print(minutes); // Used for debugging
          if(alarmSet && !alarmSearching){ oled.print(F(" SET")); } // Alarm set, but we're not in the 30 minuts before the deadline yet.
          else if(alarmSet && alarmSearching){ oled.print(F(" ON!")); } // We're in the 30 minutes before the user wants to wake up at the latest.
          else if(alarmSet && alarmRinging){ oled.print(F(" !!!")); } // NOW IS THE BEST TIME TO WAKE UP! WAKE UP!
          
      }
      
      #endif
      
      void breakUpTime(uint32_t timeInput){
      // Break the given time_t into time components.
      // This is a more compact version of the C library localtime function
      // Note that year is offset from 1970!
      
        uint32_t time;
      
        time = (uint32_t)timeInput;
        uint32_t Second = time % 60;
        time /= 60; // now it is minutes
        minutes = time % 60;
        time /= 60; // now it is hours
        hours = time % 24;
        time /= 24; // now it is days
        //int Wday = ((time + 4) % 7) + 1;                  // Which day of the week is it. Sunday is day 1 
        
        Serial.print(F("Received time: "));
        Serial.print(hours);
        Serial.print(F(":"));
        Serial.println(minutes);
      
        /* possible todo: learn what the work-days are (mon-fri), and create a toggle to only be active on work days.
        year = 0;  
        days = 0;
        while((unsigned)(days += (LEAP_YEAR(year) ? 366 : 365)) <= time) {
          year++;
        }
        //byte Year = year; // year is offset from 1970 
        
        days -= LEAP_YEAR(year) ? 366 : 365;
        time  -= days; // now it is days in this year, starting at 0
        
        days=0;
        month=0;
        monthLength=0;
        for (month=0; month<12; month++) {
          if (month==1) { // february
            if (LEAP_YEAR(year)) {
              monthLength=29;
            } else {
              monthLength=28;
            }
          } else {
            monthLength = monthDays[month];
          }
          
          if (time >= monthLength) {
            time -= monthLength;
          } else {
              break;
          }
        }
        byte Month = month + 1;  // jan is month 1  
        byte Day = time + 1;     // day of month
        */
      }
      
      
      /**
       * The MySensors Arduino library handles the wireless radio link and protocol
       * between your home built sensors/actuators and HA controller of choice.
       * The sensors forms a self healing radio network with optional repeaters. Each
       * repeater and gateway builds a routing tables in EEPROM which keeps track of the
       * network topology allowing messages to be routed to nodes.
       *
       * Created by Hreenrik Ekblad <henrik.ekblad@mysensors.org>
       * Copyright (C) 2013-2015 Sensnology AB
       * Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
       *
       * Documentation: http://www.mysensors.org
       * Support Forum: http://forum.mysensors.org
       *
       * This program is free software; you can redistribute it and/or
       * modify it under the terms of the GNU General Public License
       * version 2 as published by the Free Software Foundation.
       *
       *
       */
      
      mfalkviddM 1 Reply Last reply
      0
      • alowhumA alowhum

        @mfalkvidd Here's the sketch you requested. Work in progress..

        /*
         * 
         * GENTLE ALARM CLOCK
         * 
         * This is a smart alarm clock: 
         * - It takes into account your sleep cycle and tries to wake you up at an opportune moment in that cycle.
         *   It does this in the 30 minutes before the alarm time you set. So if you set 8AM, it will find the best moment in the 7:30AM till 8AM window.
         * - Additionally, it has wake-up light functinality to slowly wake you up. If that doesn't do it, it will sound an audio alarm.
         * 
         * Hardware:
         * - Arduino Nano
         * - Arduino Nano wireless expansion board
         * - NRF24 radio (to connect to MySensors controller)
         * - OLED screen
         * - Rotary encoder knob (KY-040)
         * - Motion sensor (radar type works well, but you can also use PIR)
         * - Bright LED. It should support setting varying brightness levels.
         * - Buzzer (could also trigger an MP3 player, or a voice recorder - have fun!)
         * 
         * 
         * How it works:
         * - It gets the clock time from the controller.
         * - You can set the alarm time (and enable or disable the alarm) with the rotary knob.
         * 
         * - Each minute it detects and sends the total seconds of movement during the previous minute.
         *   You can use a motion sensor of your choice for this. Point it at your sleepingplace in the bed.
         * 
         * 
         * SETTINGS */ 
        
        byte motionThreshold = 8;                   // If the total motion count over the past 5 minutes is this or higher, then the alarm will start ringing.
        
        // Do you want encrypted communication? If you do, then all devices in your network need to use the same password.
        //#define MY_ENCRYPTION_SIMPLE_PASSWD "changeme"
        
        
         /* 
         * 
         * 
         * 
         * 
         * 
         * TO-DO
         * - Decide on a measurement value to send to the controller. "custom" would make sense, but may not be as widely supported by controllers.
         * - Toggle the alarm on-off status from the controller. This allows the workday logic to move to the controller.
         * - Turn off the screen (or show less data on it) during the night. It lower screen brightness somehow.
         * - Fix bug where audio alarm can stay on despite rotating the knob to turn it off.
         * - Check if internal clock is somewhat accurate.
         * - Make rotating knob work better. Maybe use a library..
         * - Add snooze?
         * 
         * NICE-TO-HAVE
         * - Calculate the work days (mon-fri) on-device, and offer a toggle to only sound the alarm on those days.
         * - Maybe add another separate button for enabling/disabling the alarm.
         * - make the minute loop counter work by adding up 60 loops of a second. But might make the clock very imprecise.
         * - Sensitivity dial in controller
         */
        
        
        
        //
        // SETTINGS
        //
        
        // Enable and select the attached radio type
        #define MY_RADIO_RF24                               // This is a common and simple radio used with MySensors. Downside is that it uses the same frequency space as WiFi.
        //#define MY_RADIO_NRF5_ESB                         // This is a new type of device that is arduino and radio all in one. Currently not suitable for beginners yet.
        //#define MY_RADIO_RFM69                            // This is an open source radio on the 433mhz frequency. Great range and built-in encryption, but more expensive and little more difficult to connect.
        //#define MY_RADIO_RFM95                            // This is a LoRaWan radio, which can have a range of 10km.
        
        // MySensors: Choose your desired radio power level. High and max power can cause issues on cheap Chinese NRF24 radios.
        //#define MY_RF24_PA_LEVEL RF24_PA_MIN
        //#define MY_RF24_PA_LEVEL RF24_PA_LOW
        #define MY_RF24_PA_LEVEL RF24_PA_HIGH
        //#define MY_RF24_PA_LEVEL RF24_PA_MAX
        
        // Mysensors security
        #define MY_SIGNING_SOFT_RANDOMSEED_PIN A7         // Setting a pin to pickup random electromagnetic noise helps make encryption more secure.
        
        // Mysensors advanced settings
        #define MY_TRANSPORT_WAIT_READY_MS 10000            // Try connecting for 10 seconds. Otherwise just continue.
        //#define MY_RF24_CHANNEL 100                       // In EU the default channel 76 overlaps with wifi, so you could try using channel 100. But you will have to set this up on every device, and also on the controller.
        //#define MY_RF24_DATARATE RF24_1MBPS               // Slower datarate makes the network more stable?
        //#define MY_NODE_ID 10                             // Giving a node a manual ID can in rare cases fix connection issues.
        //#define MY_PARENT_NODE_ID 0                       // Fixating the ID of the gatewaynode can in rare cases fix connection issues.
        //#define MY_PARENT_NODE_IS_STATIC                  // Used together with setting the parent node ID. Daking the controller ID static can in rare cases fix connection issues.
        #define MY_SPLASH_SCREEN_DISABLED                   // Saves a little memory.
        //#define MY_DISABLE_RAM_ROUTING_TABLE_FEATURE      // Saves a little memory.
        
        // Enable MySensors debug output to the serial monitor, so you can check if the radio is working ok.
        //#define MY_DEBUG 
        
        // MySensors devices form a mesh network by passing along messages for each other. Do you want this node to also be a repeater?
        #define MY_REPEATER_FEATURE                         // Add or remove the two slashes at the beginning of this line to select if you want this sensor to act as a repeater for other sensors. If this node is on battery power, you probably shouldn't enable this.
        
        #define ONE_SECOND 1000                             // How many milliseconds does a second last?
        #define LOOPDURATION 60000                          // The main loop runs every x milliseconds. It's like a minute counter on a clock.
        //#define MEASUREMENT_INTERVAL 5                    // After a number of loops we start again.
        
        
        // LIBRARIES (in the Arduino IDE go to Sketch -> Include Library -> Manage Libraries to add these if you don't have them installed yet.)
        #include <MySensors.h>                              // MySensors library                  
        
        #define HAS_DISPLAY                                 // Remove this line if you are not using an OLED screen on the node.
        
        #ifdef HAS_DISPLAY
          #define INCLUDE_SCROLLING 0
          #define OLED_I2C_ADDRESS 0x3C
          #include <SSD1306Ascii.h>                         // Simple drivers for the OLED screen.
          #include <SSD1306AsciiAvrI2c.h>
          SSD1306AsciiAvrI2c oled;
        #endif
        
        // Clock variables
        byte hours = 0;
        byte minutes = 0;
        uint32_t unixTime = 0;
        
        // The lines below may be useful for a future feature.
        // leap year calulator expects year argument as years offset from 1970
        //#define LEAP_YEAR(Y)     ( ((1970+(Y))>0) && !((1970+(Y))%4) && ( ((1970+(Y))%100) || !((1970+(Y))%400) ) )
        //static  const uint8_t monthDays[]={31,28,31,30,31,30,31,31,30,31,30,31}; // API starts months from 1, this array starts from 0
        
        
        // Alarm variables
        #define SADEH_MOTION_THRESHOLD 2                    // How many movements a minute will we consider as enough to officially count as 'light sleep'
        boolean alarmSet = false;                           // Has the user enabled the alarm?
        boolean alarmSearching = false;                     // Set to true if we are in the 30 minutes before the deadline. We are waiting for the best exact moment to wake up the user now.
        boolean alarmRinging = false;                       // Wake up! Set to true if the opportune moment has been found. Light (and later audio) should be on now.
        byte alarmHours = 0;
        byte alarmMinutes = 0;
        byte displayHours = 0;
        byte displayMinutes = 0;
        
        // Speaker
        #define SPEAKER_PIN 7                               // The pin where the speaker is connected.
        
        // Rotary encoder knob
        #define ROTARY_CLK_PIN A0                           // Connected to CLK on the KY-040 rotary encoder
        #define ROTARY_DT_PIN A1                            // Connected to DT on the KY-040 rotary encoder
        #define ROTARY_SWITCH_PIN A2                        // Connected to SW on the KY-040 rotary encoder
        int previousRotaryValue;                            // The previous value read from the rotary encoder, to compare against.
        boolean rotarySwitchPressed = 0;                    // The state of the push button on the rotary encoder.
        boolean lastKnobDirection = 1;                      // 0 = Counter clockwise, 1 = clockwise.
        
        // Sadeh algorithm variables
        byte consecutiveSleepMinutesRadar = 0;
        boolean detectedREM = false;
        byte minutesSinceREM = 0;
        
        
        // Motion sensor details
        #define MOTION_SENSOR_PIN 3                         // On what pin is the radar sensor connected?
        
        
        // LED details
        #define LED_PIN 4
        #define LED_PWM_LENGTH 50                         // MICROseconds that each PWN up-and-down phase lasts. You may have too fine-tune this for your LED.
        int brightness = 0;
        byte brightnessPercentage = 0;
        
        // Mysensors settings.
        #define RADIO_DELAY 100                             // Milliseconds between sending radio signals. This keeps the radio happy.
        #define CHILD_ID_STATUS 0                           // Child ID of the sensor
        #define CHILD_ID_MOTION_SENSOR 1                    // Child ID of the sensor
        #define CHILD_ID_SET_ALARM 2                        // Allows the alarm to be turned on or off from the controller.
        #define CHILD_ID_RINGING 3                          // Allows the controller to set other devices in the room to turn on when the alarm clock is ringing.
        #define CHILD_ID_SENSITIVITY 4                    // Set the movement threshold from the controller interface.
        
        
        MyMessage statusMessage(CHILD_ID_STATUS,V_TEXT);    // Sets up the message format that we'll be sending to the MySensors gateway later. The first part is the ID of the specific sensor module on this node. The second part tells the gateway what kind of data to expect.
        MyMessage motionMessage(CHILD_ID_MOTION_SENSOR, V_TEMP);  // Sets up the message format that we'll be sending to the MySensors gateway later.
        MyMessage dimmerMessage(CHILD_ID_RINGING, V_PERCENTAGE);// Create a dimmer that can be used on the controller to set the value of, for example. another lamp.
        MyMessage relayMessage(CHILD_ID_SET_ALARM, V_STATUS); // Allow the controller to enable or disable the alarm
        
        
        void presentation()
        {
          // send the sketch version information to the gateway and Controller
          sendSketchInfo(F("Gentle alarm clock"), F("1.6")); wait(RADIO_DELAY);
            
          // Register all child sensors with the gateway
          present(CHILD_ID_STATUS, S_INFO, "Status"); wait(RADIO_DELAY);          // General status of the device, as well as the sleep phase
          present(CHILD_ID_MOTION_SENSOR, S_TEMP, "Motions"); wait(RADIO_DELAY);  // Total motion count for the past five minutes
          present(CHILD_ID_RINGING, S_DIMMER, "Dimmer"); wait(RADIO_DELAY);       // The level of the built-in wake-up LED is also mirrored to the controller. That way you could perhaps use some automation to set another lamp in the room to also slowly rise in brightness.
          present(CHILD_ID_SET_ALARM, S_BINARY, "Alarm"); wait(RADIO_DELAY);      // Allow the controller to turn the alarm on or off.
          present(CHILD_ID_SENSITIVITY, S_DIMMER, "Sensitivity"); wait(RADIO_DELAY);      // Set the motion count threshold that will trigger alarm.
        }
        
        
        void setup() 
        {
          // Output updates over the serial port
          Serial.begin(115200);
          while (!Serial) {}                                // Is this really necessary?
          Serial.println(F("Hello world!"));
        
          pinMode(MOTION_SENSOR_PIN, INPUT);                // Set motion sensor pin as input
         
          // LED
          pinMode(LED_PIN, OUTPUT);                         // Set the LED pin as output
          analogWrite(LED_PIN, 0);                          // Let's test the LED light
          wait(2000);
          analogWrite(LED_PIN, 255);
          wait(2000);
          analogWrite(LED_PIN, 0);
        
        
          // rotary encoder knob
          pinMode (ROTARY_CLK_PIN,INPUT);                   // Rotary encoder clock pin
          pinMode (ROTARY_DT_PIN,INPUT);                    // Rotary encoder data pin
          pinMode(ROTARY_SWITCH_PIN,INPUT_PULLUP);          // Rotary encoder switch pin
          previousRotaryValue = digitalRead(ROTARY_CLK_PIN); 
          rotarySwitchPressed = digitalRead(ROTARY_SWITCH_PIN);
          
        #ifdef HAS_DISPLAY
          // Start the display (if there is one)
          oled.begin(&Adafruit128x64, OLED_I2C_ADDRESS);
          oled.setFont(Adafruit5x7);
          
          oled.ssd1306WriteCmd(SSD1306_DISPLAYON);
          oled.setScroll(false);
          oled.setCursor(0,0);
          oled.print(F("ALARM CLOCK"));
        #endif
        
          // Check if there is a network connection
          if(isTransportReady()){
            Serial.println(F("Connected to gateway!"));
        
            send(statusMessage.setSensor(CHILD_ID_STATUS).set( F("Hello world") )); wait(RADIO_DELAY);
            send(dimmerMessage.set(0)); wait(RADIO_DELAY);  // Reset the dimmer level to 0.
            requestTime(); wait(RADIO_DELAY);               // Request the current time from the controller.
            
            //Serial.print(F("Time: ")); Serial.println(controllerTime);
        #ifdef HAS_DISPLAY
            // Show connection icon on the display
            oled.setCursor(90,0);
            oled.print(F("W"));
        #endif
          }else{
            Serial.println(F("! NOCONNECTION"));
        #ifdef HAS_DISPLAY    
            oled.setCursor(90,0);
            oled.print(F(" "));
        #endif    
          }
        
        
          // Get last known preferences from onboard storage
          alarmHours = loadState(1);                       // To what hour was the alarm set?
          if(alarmHours > 24){alarmHours = 8;}
          
          alarmMinutes = loadState(2);                     // To which minure was the alarm set?
          if(alarmMinutes > 55){alarmMinutes = 0;}
          
          alarmSet = loadState(3);                         // Was the alarm set to on or off?
          if(alarmSet > 1){alarmSet = 1;}
          
          if(loadState(4) != 255){
            motionThreshold = loadState(4);                // What is the desired sensitivity
          }
          
          wdt_enable(WDTO_8S);                              // Starts the watchdog timer. If it is not reset once every few seconds, then the entire device will automatically restart.                                
        }
        
        
        void loop()
        {
        
        
        /*  the main loop has four levels:
         *  - Continuously:
         *  - - Check if the rotary knob has been turned.
         *  - Flickering: this runs at the rate of 1000 times per second. 
         *  - - It is used to PWM the LED.
         *  - Flutter: this runs once a second. It does things like 
         *  - - Increase the LED brightness once the alarm is in ringing mode, and 
         *  - - Check if the motion sensor is in its active state.
         *  - Heartbeat: this runs once a minute. 
         *  - - It takes note of the total movement over the last minute, and sends along the data.
         *  - - It checks if it's time to wake up the user. 
         *  - - It also updates the minute counter on the display
         *  
         */
        
          // Main loop variables
          static unsigned long lastLoopTime = 0;            // Holds the last time the main loop ran.
          static int loopCounter = 0;                       // Count how many heartbeat loops (minutes) have passed.
        
          // Creating variables to track sleep.
          static byte motionCounter = 0;                    // The movement count for the last minute.
          static byte movementsList[5];                     // An arrray (list) that stores the last 5 motionCounter values.
          static int motionTotal = 0;                       // Total movement count for the past 5 minutes
        
        
        
          // Rotary knob
          static int rotaryValue;
          static boolean takeStep = 0;
          rotaryValue = digitalRead(ROTARY_CLK_PIN);
          if (rotaryValue != previousRotaryValue){          // Check if the rotary encoder knob is rotating
            alarmRinging = false;                           // In case the alarm was ringing, it should be turned off now.
            noTone(SPEAKER_PIN);                            // In case the alarm was still making sound somehow, turn it off.
            takeStep = !takeStep;                           // On every loop though this gets changed into its opposite. So 0 -> 1 -> 0 -> 1 etc
            if(takeStep){                                   // If we have already ignored a step, then go further.
              // Check in which direction it's rotating.
              if (digitalRead(ROTARY_DT_PIN) != rotaryValue) {
                Serial.println(F("Counterclockwise"));
        
                if(lastKnobDirection == 0){                 // Rotating left rapidly decreases the alarm time.
                  if(alarmMinutes <= 30){
                    alarmMinutes = 30;
                    if(alarmHours == 0){alarmHours = 23;}else{alarmHours--;}
                  }
                  if(alarmMinutes >= 30){
                    alarmMinutes = 30;
                  }
                  Serial.print(F("New alarm hours: ")); Serial.println(alarmHours); 
                  lastKnobDirection = 1;
                }
                lastKnobDirection = 0;
              } else {                                      // Rotating right slowly increases the alarm time.
                Serial.println (F("Clockwise"));
                alarmMinutes = alarmMinutes + 5;
                if(alarmMinutes > 55){
                  alarmMinutes = 0; 
                  alarmHours = alarmHours + 1;
                  if(alarmHours > 23){alarmHours = 0;}
                }
                Serial.print(F("New alarm: ")); Serial.print(alarmHours); Serial.print(F(":")); Serial.println(alarmMinutes);   
                lastKnobDirection = 1;
              }
            }
        #ifdef HAS_DISPLAY
          updateClockDisplay();
        #endif     
          }
          previousRotaryValue = rotaryValue;
        
        
          // Check if the rotary knob button is being pressed.
          // boolean switchPosition = digitalRead(ROTARY_SWITCH_PIN);
          if (digitalRead(ROTARY_SWITCH_PIN) == 1 && rotarySwitchPressed == 0){
            rotarySwitchPressed = 1;
            Serial.println(F("Button pressed"));
        
            if(alarmSearching || alarmRinging){
              turnOffRinging();
            }else{
              alarmSet = !alarmSet;                         // If the alarm is not ringing, then pressing the button turns the alarm setting on or off completely.
            }
        #ifdef HAS_DISPLAY
            updateClockDisplay();
        #endif
            wait(50);
          } else if (digitalRead(ROTARY_SWITCH_PIN) == 0){
            rotarySwitchPressed = 0;
          }
        
        
         #ifdef HAS_DISPLAY  
          oled.set1X();
          oled.setCursor(110,0);                            // In the top-right corner..
          if(!alarmRinging){                                // If the alarm is not going off, show the motion count for this minute.
            oled.print(motionCounter); oled.println(F("   "));
          }else if (brightness < 255){                      // Otherwise, show the brightness level of the LED (at least intil it reaches 255).
            oled.setCursor(104,0);
            oled.print(F("*")); oled.print(brightnessPercentage); oled.println(F("   "));
          }else{
            oled.setCursor(104,0);
            oled.println(F("    "));
          }
          
         #endif
        
        
        
          //
          // FLICKERING
          // Software pulse width modulation (PWM) for the high brightness LED, using modulo.
          //
        
          if(alarmRinging == true){
        
            unsigned int pulsy = map(brightness,0,255,0,LED_PWM_LENGTH); // Depending on the intended brightness level, the LED should be on during a proportianl period of the PWM time.
        
            if(micros() % LED_PWM_LENGTH < pulsy ){
              digitalWrite(LED_PIN, 1);
              //Serial.print(F("|")); // For debugging
            }else{
              digitalWrite(LED_PIN, 0);
              //Serial.print(F(".")); // For debugging
            }
          }else{
            digitalWrite(LED_PIN, 0);  
          }
           
        
          //
          // FLUTTER
          // Runs every second. By counting how often this loop has run (and resetting that counter back to zero after a number of loops), it becomes possible to schedule all kinds of things without using a lot of memory.
          //
        
          static boolean loopDone = false;                        // used to make sure the 'once every millisecond' things only run once every millisecond (or 2.. sometimes the millis() function skips a millisecond.);
        
          // Allow the next loop to only run once. This entire construction saves memory by not using a long to store the last time the loop ran.
          if( (millis() % ONE_SECOND) > ONE_SECOND - 4 && loopDone == true ) {
            loopDone = false;  
          }
        
          // Main loop to time actions.
          if( (millis() % ONE_SECOND) < 4 && loopDone == false ) { // this module's approach to measuring the passage of time saves a tiny bit of memory.
            loopDone = true;
            
            
            //if (millis() - lastLoopTime > ONE_SECOND) {
            //lastLoopTime = millis(); // this variable is now already used by the minute counter.
        
            wdt_reset(); // Reset the watchdog timer. If the device crashes, then the watchdog won't be reset, and this will in turn cause it to reset the entire device.
        
            // Check if the movement sensor is seeing movement.
            boolean motionState = digitalRead(MOTION_SENSOR_PIN);
            if (motionState == HIGH) {
              motionCounter++;
              Serial.print(F("~"));
              Serial.print(motionCounter);
            }
        
            // WAKE UP! Every second we make the LED a little brighter.
            if(alarmRinging == true){
              brightness++;
              brightnessPercentage = map(brightness, 0, 255, 0, 100); 
              send(dimmerMessage.set(brightnessPercentage));
              Serial.print(F("Sending dimmer brightness: *")); Serial.println(brightness);
            }else{
              brightness = 0;
            }
        
            // WAKE UP! Make noise.
            if(alarmRinging == true && brightness > 255 && brightness < 386){
              Serial.print(F("**")); Serial.println(brightness);
              if (brightness % 2 == 0){ // Makes noise every other second.
                tone(SPEAKER_PIN, 1000);
              }else{
                noTone(SPEAKER_PIN);  
              }
            }
        
            // WAKE UP! The user should be awake by now.
            if(brightness == 765){                           // Turn of the light and the alarm
              //alarmSearching = false;
              turnOffRinging();
            }
        
          }
        
        
        
        
        
        
          //
          // HEARTBEAT
          // Runs every minute. By counting how often this loop has run (and resetting that counter back to zero after a number of loops), it becomes possible to schedule all kinds of things without using a lot of memory.
          //
        
        
          if (millis() - lastLoopTime >= LOOPDURATION) {
            lastLoopTime = millis();
            loopCounter++;
            if(loopCounter > 5){
              Serial.print(F("loopCounter ")); Serial.println(loopCounter);  
              loopCounter = 1;
              requestTime();
        
              Serial.print(F("Minutes since REM phase: ")); Serial.println(minutesSinceREM);
            }
        
            // Fun but incomplete stuff to detect REM phases
            if(detectedREM && minutesSinceREM < 250){
              minutesSinceREM++;
            }
        
            // Update the clock
            unixTime += 60;                                 // Add a minute to the clock. Maybe adding 59 is better?
            breakUpTime(unixTime);                          // Turn the unix time into human-readable time
            updateClockDisplay();
        
        
            // Save the alarm details. (They will only be overwritten if they have changed).
            saveState(1, alarmHours);
            saveState(2, alarmMinutes);
            saveState(3, alarmSet);
            saveState(4, motionThreshold);
        
        
            // ALARM ACTIVE CHECK - Should we start the wake up procedure?
            if(alarmSet == true && hours == alarmHours && minutes == alarmMinutes){
              Serial.println(F("ALARM SET - Starting wake up procedure: alarm now in searching phase"));
              alarmSearching = true;  // Here we enable searching for the right moment to wake up.
              send(statusMessage.setSensor(CHILD_ID_STATUS).set( F("Starting wake up") )); wait(RADIO_DELAY);
            }
        
        
            // If there was movement for 5 minutes, then the user is sleeping lightly, and its now time to WAKE UP!
            // if(alarmSet == true && alarmSearching == true && movementsList[1] >= SADEH_MOTION_THRESHOLD && movementsList[2] >= SADEH_MOTION_THRESHOLD && movementsList[3] >= SADEH_MOTION_THRESHOLD &&  movementsList[4] >= SADEH_MOTION_THRESHOLD && movementsList[5] >= SADEH_MOTION_THRESHOLD){
            if(alarmSet == true && alarmSearching == true && motionTotal >= motionThreshold){ // if there was a high movement count in the last 5 minutes, then the user is in a light sleep moment. 
              alarmSearching = false;                       // We are no longer looking for the right moment to ring the alarm, since we just found it.
              alarmRinging = true;                          // Time to really ring the alarm!
            }
        
            // If we are in the alarm active phase, but there was not a good moment to wake up the user, then at the end just sound the alarm at the defined alarm time. Like a normal alarmclock.
            if(alarmSet == true && alarmSearching == true && hours == displayHours && minutes == displayMinutes){
              alarmRinging = true;
              // maybe set the brightness high here too?
            }
        
        
            // SADEH algorithm. All this is not really required.
            static byte consecutiveSleepMinutesMotion = 0;
            static byte consecutiveAwakeMinutesMotion = 0;
            if(motionCounter < SADEH_MOTION_THRESHOLD){
              consecutiveAwakeMinutesMotion = 0;
              if(consecutiveSleepMinutesMotion < 250){
                consecutiveSleepMinutesMotion++;
              }else if (consecutiveSleepMinutesMotion == 15){
                detectedREM = true;
                send(statusMessage.setSensor(CHILD_ID_STATUS).set( F("DEEP SLEEP") )); wait(RADIO_DELAY);
              }
            }else{
              consecutiveSleepMinutesMotion = 0;
              if(consecutiveAwakeMinutesMotion < 250){
                consecutiveAwakeMinutesMotion++;
              }
              if(consecutiveAwakeMinutesMotion == 5){
                
                if(detectedREM && minutesSinceREM > 60){
                  Serial.println(F("Light sleep after REM"));
                  send(statusMessage.setSensor(CHILD_ID_STATUS).set( F("Light sleep after rem") )); wait(RADIO_DELAY);
                  minutesSinceREM = 0; //  we found the light sleep phase in a good time segment after the rem phase. So reset this counter.
                }else{
                  Serial.println(F("Light sleep"));
                  send(statusMessage.setSensor(CHILD_ID_STATUS).set( F("Light sleep") )); wait(RADIO_DELAY);
                }
                
                // Definitely moved away from the REM phase, so reset those variables:
                consecutiveSleepMinutesMotion = 0;
                detectedREM = false;
              }    
            }
        
        
            movementsList[loopCounter] = motionCounter;
            Serial.print(F("Loop number and motion sensor movements: ")); Serial.print(loopCounter); Serial.print(F(" -> ")); Serial.println(movementsList[loopCounter]);
        
            motionTotal = movementsList[1] + movementsList[2] + movementsList[3] + movementsList[4] + movementsList[5];
        
            Serial.print(F("Sending motion total:")); Serial.println(motionTotal);
            send(motionMessage.set(motionTotal)); wait(RADIO_DELAY);
            
            // We ask the server to acknowledge that it has received the data. It it doesn't, remove the connection icon.
            if( send(motionMessage.set(motionTotal)) ){ // was ),1) ){
              Serial.println(F("Connection is ok"));
        #ifdef HAS_DISPLAY
              // add W icon
              oled.set1X();
              oled.setCursor(90,0);
              oled.print(F("W"));
        #endif
            }else {
              Serial.println(F("Connection lost"));
        #ifdef HAS_DISPLAY
              // remove W icon
              oled.set1X();
              oled.setCursor(90,0);
              oled.print(F(" "));
        #endif          
            }
        
            // every loop (minute) the movement counter is reset.
            motionCounter = 0;
         
          }
        }
        
        void turnOffRinging()
        {
          if(brightness != 0){
            Serial.println(F("Send: resetting dimmer to 0"));
            send(dimmerMessage.set(0));  
          }
          brightness = 0;
          alarmRinging = false;                         // If the alarm is ringing, then pressing the button stops the ringing. The alarm will still be set for the next day.
          alarmSearching = false;                       // Turning the alarm during the searching phase means we should no longer look for a moment to ring the alarm.
          noTone(SPEAKER_PIN);
        }
        
        
        void receive(const MyMessage &message)
        {
          Serial.print("__Incoming change for child: ");
          Serial.println(message.sensor);
          if (message.type==V_STATUS && message.sensor == CHILD_ID_SET_ALARM) { // Toggle of alarm on or off
            Serial.println(F("__RECEIVED ALARM TOGGLE"));
            // Change alarm state
            alarmSet = message.getBool()?1:0;
            if(alarmSet == false && (alarmSearching || alarmRinging)){
              turnOffRinging();
            }
            updateClockDisplay();
            // Write some debug info
          }
          if (message.type == V_PERCENTAGE && message.sensor == 4) { // If it's the desired sensitivity level
            //  Retrieve the power or dim level from the incoming request message
            int receivedSensitivity = atoi( message.data );
            motionThreshold = byte(receivedSensitivity);
            saveState(4, motionThreshold);
            Serial.print(F("Requested motion threshold is "));
            Serial.println( motionThreshold );
        
          }  
        }
        
        
        void receiveTime(unsigned long controllerTime) {
          Serial.print(F("Received time: ")); Serial.println(controllerTime);
          unixTime = controllerTime;
          breakUpTime(unixTime);
        #ifdef HAS_DISPLAY
          updateClockDisplay();                             // Update the hours on the display
        #endif
        }
        
        #ifdef HAS_DISPLAY
        void updateClockDisplay()                           // Update clock time display
        {
            oled.set2X();                                   // Switch to large font size
            oled.setCursor(30,2);
            if(hours < 10){ oled.print(F(" ")); }
            oled.print(hours); 
            oled.print(F(":")); 
            if(minutes < 10){ oled.print(F("0")); }
            oled.print(minutes);
        
            // This is important: the display shows the alarm time as being 30 minutes later than the internal alarm time. This is done for more easy programming, since we mostly case about the moment 30 minutes before the alarm should go off.
            displayHours = alarmHours;
            if(alarmMinutes >= 30){displayHours++;}
            if(displayHours > 23){displayHours = 0;}
        
            displayMinutes = (alarmMinutes + 30) % 60;
        
            //update alarm time display    
            oled.setCursor(17,5); 
            if(!alarmSet){
              oled.print(F("           ")); 
              oled.set1X();
              oled.setCursor(25,5);
            }
            if(displayHours < 10){oled.print(F(" "));}
            oled.print(displayHours);
            //oled.print(hours); // Used for debugging
            oled.print(F(":")); 
            if(displayMinutes < 10){ oled.print(F("0")); }
            oled.print(displayMinutes);
            //oled.print(minutes); // Used for debugging
            if(alarmSet && !alarmSearching){ oled.print(F(" SET")); } // Alarm set, but we're not in the 30 minuts before the deadline yet.
            else if(alarmSet && alarmSearching){ oled.print(F(" ON!")); } // We're in the 30 minutes before the user wants to wake up at the latest.
            else if(alarmSet && alarmRinging){ oled.print(F(" !!!")); } // NOW IS THE BEST TIME TO WAKE UP! WAKE UP!
            
        }
        
        #endif
        
        void breakUpTime(uint32_t timeInput){
        // Break the given time_t into time components.
        // This is a more compact version of the C library localtime function
        // Note that year is offset from 1970!
        
          uint32_t time;
        
          time = (uint32_t)timeInput;
          uint32_t Second = time % 60;
          time /= 60; // now it is minutes
          minutes = time % 60;
          time /= 60; // now it is hours
          hours = time % 24;
          time /= 24; // now it is days
          //int Wday = ((time + 4) % 7) + 1;                  // Which day of the week is it. Sunday is day 1 
          
          Serial.print(F("Received time: "));
          Serial.print(hours);
          Serial.print(F(":"));
          Serial.println(minutes);
        
          /* possible todo: learn what the work-days are (mon-fri), and create a toggle to only be active on work days.
          year = 0;  
          days = 0;
          while((unsigned)(days += (LEAP_YEAR(year) ? 366 : 365)) <= time) {
            year++;
          }
          //byte Year = year; // year is offset from 1970 
          
          days -= LEAP_YEAR(year) ? 366 : 365;
          time  -= days; // now it is days in this year, starting at 0
          
          days=0;
          month=0;
          monthLength=0;
          for (month=0; month<12; month++) {
            if (month==1) { // february
              if (LEAP_YEAR(year)) {
                monthLength=29;
              } else {
                monthLength=28;
              }
            } else {
              monthLength = monthDays[month];
            }
            
            if (time >= monthLength) {
              time -= monthLength;
            } else {
                break;
            }
          }
          byte Month = month + 1;  // jan is month 1  
          byte Day = time + 1;     // day of month
          */
        }
        
        
        /**
         * The MySensors Arduino library handles the wireless radio link and protocol
         * between your home built sensors/actuators and HA controller of choice.
         * The sensors forms a self healing radio network with optional repeaters. Each
         * repeater and gateway builds a routing tables in EEPROM which keeps track of the
         * network topology allowing messages to be routed to nodes.
         *
         * Created by Hreenrik Ekblad <henrik.ekblad@mysensors.org>
         * Copyright (C) 2013-2015 Sensnology AB
         * Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
         *
         * Documentation: http://www.mysensors.org
         * Support Forum: http://forum.mysensors.org
         *
         * This program is free software; you can redistribute it and/or
         * modify it under the terms of the GNU General Public License
         * version 2 as published by the Free Software Foundation.
         *
         *
         */
        
        mfalkviddM Offline
        mfalkviddM Offline
        mfalkvidd
        Mod
        wrote on last edited by
        #23

        @alowhum thanks. I don't see anything obvious that would mess up the watchdog.

        I would try to get a debug log from where it crashes, to see if there are any clues.

        1 Reply Last reply
        1
        • gohanG Offline
          gohanG Offline
          gohan
          Mod
          wrote on last edited by
          #24

          does the arduino LED flashes bright and fast when it crashes?

          1 Reply Last reply
          0
          • alowhumA Offline
            alowhumA Offline
            alowhum
            Plugin Developer
            wrote on last edited by
            #25

            @gohan It froze again today, but I didn't check for that. I will next time.

            1 Reply Last reply
            0
            • J Offline
              J Offline
              Jan Kadlec
              wrote on last edited by
              #26

              I can confirm that 8s user watchdog is not rebooting CPU as expected, as WDT is used on internal library stuff - that I actually do not need at all - and there is no way how to just exclude that code from compiling in ;-(
              I ended with using delay(10000) to force WDT to reboot the CPU..
              Is there some sample code to use user WDT?
              I

              J 1 Reply Last reply
              0
              • gohanG Offline
                gohanG Offline
                gohan
                Mod
                wrote on last edited by
                #27

                Does it work with a shorter time than 8S?

                1 Reply Last reply
                0
                • J Jan Kadlec

                  I can confirm that 8s user watchdog is not rebooting CPU as expected, as WDT is used on internal library stuff - that I actually do not need at all - and there is no way how to just exclude that code from compiling in ;-(
                  I ended with using delay(10000) to force WDT to reboot the CPU..
                  Is there some sample code to use user WDT?
                  I

                  J Offline
                  J Offline
                  Jan Kadlec
                  wrote on last edited by
                  #28

                  I've manualy removed the call for watchdog reset from function

                  void doYield(void)
                  

                  and I can see my user watchdog timer working again...

                  This will probably affect some deep sleep and waking processes - that I do not care about in my cases...

                  I used RadioHead library before, which is much more library in sense of composable part and I was very happy with it's long term stability...

                  Now I'm trying MySensors and it's quite unstable comparing to RadioHead... If it had software signing, I'd definitely use RadioHead...

                  Let's see if enabling user WDT will fix the unstabilities...

                  1 Reply Last reply
                  0
                  Reply
                  • Reply as topic
                  Log in to reply
                  • Oldest to Newest
                  • Newest to Oldest
                  • Most Votes


                  10

                  Online

                  11.7k

                  Users

                  11.2k

                  Topics

                  113.1k

                  Posts


                  Copyright 2025 TBD   |   Forum Guidelines   |   Privacy Policy   |   Terms of Service
                  • Login

                  • Don't have an account? Register

                  • Login or register to search.
                  • First post
                    Last post
                  0
                  • MySensors
                  • OpenHardware.io
                  • Categories
                  • Recent
                  • Tags
                  • Popular