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 alowhum
    #6

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

    mfalkviddM YveauxY 2 Replies Last reply
    0
    • alowhumA alowhum

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

      mfalkviddM Offline
      mfalkviddM Offline
      mfalkvidd
      Mod
      wrote on last edited by
      #7

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

      1 Reply Last reply
      0
      • alowhumA alowhum

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

        YveauxY Offline
        YveauxY Offline
        Yveaux
        Mod
        wrote on last edited by
        #8

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

        http://yveaux.blogspot.nl

        1 Reply Last reply
        0
        • mfalkviddM Offline
          mfalkviddM Offline
          mfalkvidd
          Mod
          wrote on last edited by mfalkvidd
          #9

          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?

          YveauxY 1 Reply Last reply
          0
          • mfalkviddM mfalkvidd

            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?

            YveauxY Offline
            YveauxY Offline
            Yveaux
            Mod
            wrote on last edited by
            #10

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

            http://yveaux.blogspot.nl

            mfalkviddM 1 Reply Last reply
            1
            • YveauxY Yveaux

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

              mfalkviddM Offline
              mfalkviddM Offline
              mfalkvidd
              Mod
              wrote on last edited by mfalkvidd
              #11

              @yveaux seems like it is supposed to kick the watchdog already:

              @remark Internally it will call yield, kick the watchdog and update led states.

              https://github.com/mysensors/MySensors/blob/06bdb991b6012c3cc9a2306a09b4a4b92105e9f0/core/MySensorsCore.h#L261

              https://github.com/mysensors/MySensors/blob/06bdb991b6012c3cc9a2306a09b4a4b92105e9f0/core/MySensorsCore.cpp#L551 calls hwWatchdogReset which, on avr, resets the watchdog https://github.com/mysensors/MySensors/blob/bde7dadca6c50d52cc21dadd5ee6d3623be5f3c6/hal/architecture/AVR/MyHwAVR.h#L62

              1 Reply Last reply
              1
              • dbemowskD Offline
                dbemowskD Offline
                dbemowsk
                wrote on last edited by
                #12

                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/

                Vera Plus running UI7 with MySensors, Sonoffs and 1-Wire devices
                Visit my website for more Bits, Bytes and Ramblings from me: http://dan.bemowski.info/

                bjacobseB 1 Reply Last reply
                0
                • dbemowskD dbemowsk

                  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/

                  bjacobseB Offline
                  bjacobseB Offline
                  bjacobse
                  wrote on last edited by
                  #13

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

                  dbemowskD 1 Reply Last reply
                  0
                  • bjacobseB bjacobse

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

                    dbemowskD Offline
                    dbemowskD Offline
                    dbemowsk
                    wrote on last edited by
                    #14

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

                    Vera Plus running UI7 with MySensors, Sonoffs and 1-Wire devices
                    Visit my website for more Bits, Bytes and Ramblings from me: http://dan.bemowski.info/

                    1 Reply Last reply
                    0
                    • bjacobseB Offline
                      bjacobseB Offline
                      bjacobse
                      wrote on last edited by bjacobse
                      #15

                      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

                      mfalkviddM 1 Reply Last reply
                      0
                      • bjacobseB bjacobse

                        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

                        mfalkviddM Offline
                        mfalkviddM Offline
                        mfalkvidd
                        Mod
                        wrote on last edited by
                        #16

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

                        1 Reply Last reply
                        0
                        • mfalkviddM Offline
                          mfalkviddM Offline
                          mfalkvidd
                          Mod
                          wrote on last edited by mfalkvidd
                          #17

                          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?

                          bjacobseB 1 Reply Last reply
                          0
                          • mfalkviddM mfalkvidd

                            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?

                            bjacobseB Offline
                            bjacobseB Offline
                            bjacobse
                            wrote on last edited by bjacobse
                            #18

                            @mfalkvidd
                            This just shows how clever/smart the mysensors code are developed :-)

                            1 Reply Last reply
                            1
                            • M Offline
                              M Offline
                              mgaman
                              wrote on last edited by
                              #19

                              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/

                              1 Reply Last reply
                              0
                              • tianaT Offline
                                tianaT Offline
                                tiana
                                wrote on last edited by
                                #20

                                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

                                1 Reply Last reply
                                0
                                • 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
                                          Reply
                                          • Reply as topic
                                          Log in to reply
                                          • Oldest to Newest
                                          • Newest to Oldest
                                          • Most Votes


                                          9

                                          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