Watchdog not watchdogging?


  • Plugin Developer

    Last night the smart alarm clock I'm working on froze, and thus didn't wake me up.

    I quickly added a watchdog this morning (was on the to-do list). Then the radar motion sensor had a wire issue, and got disconnected and reconnected. The device froze again.

    No problem, I thought, the watchdog is probably already doing its thing.

    But it stayed frozen.

    Are there limits to the watchdog function of the Arduino Nano?



  • If you use the standard bootloader from Arduino the WD is dsabled, flash with optiboot


  • Plugin Developer

    Ah! So that's it!

    I'll look into it. Thanks for the tip!

    Is there any way to activate it without resorting to a different bootloader?


  • Hardware Contributor

    @alowhum

    It's not portable but for the AVR architecture you can use:

    void before() {
      wdt_disable();
      wdt_enable(WDTO_8S); // 8s timeout 
    }
    
    void loop() {
      wdt_reset();
    }
    

    Other watchdog options for the AVR architecture here.


  • Mod

    Be aware that some of the MySensors functions (sleep and according to below link also send) will change the watchdog settings, so you'll have to set back the watchdog after using these functions.

    Enhancement request: https://github.com/mysensors/MySensors/issues/1160

    EDIT: no, that's not what issue 1160 is about. 1160 is about setting up the watchdog so that sketch developers don't need to add custom code to enable the watchdog. Sorry for confusing everyone.


  • Plugin Developer

    @mfalkvidd Could you elaborate a bit on what MySensors does to the watchdog? Does it disable it? The above seems to imply I have to re-enable it after every send command? That sounds almost unbelievable.

    On github a quick search for "wdt_disable" in the code only reveals disabling it when sleep() is called?

    There I can understand its presence.

    The node mentioned in the first post already had an AVR watchdog enabled in the manner described. It doesn't use sleep. Still, it didn't seem to reboot. So maybe it's true, and I just haven't found the code in the search?


  • Mod

    @alowhum I don’t know unfortunately. But what you’re saying sounds reasonable.


  • Mod

    @alowhum I don't have easy access to the code atm, but, as I read it, the change request @mfalkvidd refers to suggests to add a wdt reset to the send() function. It doesn't say the current send() fiddles with the wdt.


  • Mod

    I've done some digging.

    Sending I_DEBUG message to the node will call hwCPUFrequency which does things to the watchdog, but it looks like the watchdog settings are saved and then restored afterwards.

    Sleep uses the watchdog to wake up. When the node is sleeping on timer, it needs to use the watchdog because all other clocks are stopped when sleeping so there is no other way to keep time.

    I guess it would be possible to save and restore the watchdog settings in hwPowerDown, just like in hwCPUFrequency? That should allow the user to set their own watchdog which is a good start.
    EDIT: MySensors already saves and restores the watchdog during sleep. Reference link.

    I guess it would be even better if it was possible to enable the watchdog when not sleeping, using a define (like suggested in the linked github issue). But I am not sure endless reboots (which is a risk with the watchdog enabled) is desirable, so that feature might need to be off by default. I also don't know if it is possible to detect the reset reason and handle boot differently (which might be desirable).

    All this is for AVR only. I am not sure about the other platforms supported by MySensors.

    And thanks Yveaux for clarifying he send() behavior. I was very confused by that statement in the github issue. I guess resetting the watchdog in wait() and any other long-running function would make sense. But why would send() take so long time that the watchdog might be tripped?


  • Mod

    @mfalkvidd I suppose the best place to kick the watchdog in the MySensors stack would be the doYield(), as it also keeps the esp8266 watchdog alive.


  • Mod



  • What about a hardware watchdog such as this 555 based watchdog. A hardware based watchdog seems a bit more reliable.
    http://upperbound.com/projects/555-watchdog-timer/



  • @dbemowsk said in Watchdog not watchdogging?:

    What about a hardware watchdog such as this 555 based watchdog. A hardware based watchdog seems a bit more reliable.
    http://upperbound.com/projects/555-watchdog-timer/

    the watchdog in AT328 is reliable, do you have documentation behind this comment that it's not?



  • @bjacobse I have just seen things in forums in the past like this where some users explain about the possibility of the microcontroller with the software based watchdog timer hanging to the point where the MC's internal watchdog hangs too. That was my basis for the comment.



  • This link is misleading which causes misunderstanding
    The ATmega328p have watchdog timer build in, which if enabled uses an internal RC oscillation, and this will for sure reset the MCU, if the timer isn't reset by the user program before time-out. it works rock stable...
    Something that can cause unreliability is that brown-out detecting of power voltage, will also cause a shutdown, and if the designer isn't aware of stable power voltage, then you get unstable MCU system...

    http://ww1.microchip.com/downloads/en/DeviceDoc/ATmega48A-PA-88A-PA-168A-PA-328-P-DS-DS40002061A.pdf

    Below snippet is from above ATmega328 spec:

    Please note there are 3 Operating modes:
    – Interrupt
    – System Reset
    – Interrupt and System Reset
    In Interrupt mode, the WDT gives an interrupt when the timer expires. This interrupt can be used to wake the
    device from sleep-modes, and also as a general system timer. One example is to limit the maximum time
    allowed for certain operations, giving an interrupt when the operation has run longer than expected.
    In System Reset mode, the WDT gives a reset when the timer expires. This is typically used to prevent system hang-up in case of runaway code. The third mode, Interrupt and System Reset mode, combines the other two modes by first giving an interrupt and then switch to System Reset mode. This mode will for instance allow a safe shutdown by saving critical parameters before a system reset.

    [EDIT] below removed as it was not correct, comment from mfalkvidd is correct, that mysensors is using WDT for wakup-call


  • Mod

    @bjacobse no that is not the reason.

    In sleep mode, MySensors uses the watchdog to wake up every 8 seconds to increment a counter and immediately go to sleep. This is the most power efficient way to sleep for a set time. Because no other clocks are active in sleep mode, it is impossible to keep time without using the watchdog.


  • Mod

    I have now checked the code for sleep, and the watchdog settings are saved and restored: https://github.com/mysensors/MySensors/blob/121648f34bb45ab0e21fc4b4835959d27b28a9c6/hal/architecture/AVR/MyHwAVR.cpp#L93

    So it seems like it should be possible for sketch developers to define their own watchdog, which will be active att all times except:

    • briefly after receiving an I_DEBUG message
    • during sleep()

    @alowhum would you mind posting your sketch?



  • @mfalkvidd
    This just shows how clever/smart the mysensors code are developed 🙂



  • This is an old issue related to bootloader problems on some nano/mega boards.
    Try this
    https://bigdanzblog.wordpress.com/2014/10/23/installing-the-optiboot-loader-on-an-arudino-nano-to-fix-the-watch-dog-timer-wdt-issue/



  • See this project, there have solution for hardware based watch dog, can be easily transform to be usable for Arduino.

    https://www.openhardware.io/view/636/Raspberry-PI-Hat


  • Plugin Developer

    @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.


  • Plugin Developer

    @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.
     *
     *
     */
    

  • Mod

    @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.


  • Mod

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


  • Plugin Developer

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



  • 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


  • Mod

    Does it work with a shorter time than 8S?



  • 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...


Log in to reply
 

Suggested Topics

  • 1
  • 1
  • 1
  • 1
  • 5
  • 6

31
Online

11.4k
Users

11.1k
Topics

112.6k
Posts