Alarm Clock



  • Hi !
    I am having a go myself at the alarm clock project !!
    Nokia 5110 screen
    Rotary encoder
    No RTC 🙂
    LiIon18650 cell

    So far, I managed to get time from controller via requestTime()
    Then display it on node screen

    Now i would like the node to send a message to the controller : goal is to check in PaperUI the alarm time that has been set on the clock
    Later on i would like to be able to set/unset alarm from PaperUI
    And even later on i would like to be able to set alarm from PaperUI as well as from alarm clock

    Thanks a lot for your input and comments
    I am having so much fun with my sensors !!!

    0_1498933565366_FullSizeRender.jpg



  • Guys

    Making progress, mainly on sketch-side of it.

    Now on the hardware side of it : i would like to power the clock via a single 18650 cell (2000mah from old laptop)

    So i need a regulation unit, as LiIon cell will run from 4.2V down to 2.3V (cut-off from protection board)

    NRF, arduino nano and 5110 screen need 3.3V supply
    NRF and arduino will draw up to 30mA when transmitting
    5110 screen backlight will draw 50mA, logic seems to draw nearly nothing...
    So that would be a grand total of less than 100mA

    That converter would need to step-down as well as step-up depending on "petrol level" of the cell...
    Does it even exist ??

    @NeverDie suggested this elsewhere on the forum... http://www.ti.com/product/TPS82740B/description&lpos=Middle_Container&lid=Alternative_Devices

    Or shall i simply connect the LiIon cell straight to arduino board (raw pin) and let it power the screen and radio (3.3 pin) ?? As it seems the on-board regulator can handle upto 150mA... smell of burning chips... ? 😆

    Would it fit the bill ? Any suggestions ?

    Thanks a lot !



  • And here is my code up-to now :

    What it does on node's side

    • displays time, date, and alarm if set (if not, displays "alarm Off")
    • updates itself once a week by asking controller
    • list menus to :
      • set alarm on or off
      • set alarm time (hour and minute)
      • change screen contrast

    What is does on openHab

    • turns on and off alarm via a switch (item #1 - lock sensor)

    • displays time of alarm and when it has been updated (item #0 - text)

    I have ordered a WTV020-SD-16P module so that my little guys can wake-up to the sound of "the Final Countdown" !
    http://www.ebay.com/itm/U-disk-audio-player-TF-SD-card-voice-module-MP3-Sound-WTV020-SD-16P-Arduino/310629013078?ssPageName=STRK%3AMEBIDX%3AIT&_trksid=p2057872.m2749.l2649

    And last on the list, being able to change alarm time via paperUI and iPhone app

    Thanks for your comments (be gentle on arduino sketch 😉 )

    /*******Interrupt-based Rotary Encoder Sketch*******
    by Simon Merrett, based on insight from Oleg Mazurov, Nick Gammon, rt, Steve Spence */
    
    //#define MY_DEBUG          // Enable debug prints
    #define MY_RADIO_NRF24   // Enable and select radio type attached
    
    #define MY_PARENT_ID 0
    #define MY_NODE_ID 50
    
    #include <SPI.h>
    #include <Adafruit_GFX.h>
    #include <Adafruit_PCD8544.h>
    #include <MySensors.h>
    #include <Time.h>
    
    #define ALARM_ID 0                      // Id of TimeOfAlarm value
    #define ACTIV_ID 1                      // Id of ActivateAlarm switch
    
    static int pinA = 2; // Our first hardware interrupt pin is digital pin 2
    static int pinB = 3; // Our second hardware interrupt pin is digital pin 3
    static int enSW = 4; // Rotary switch push-button
    
    volatile byte aFlag = 0; // let's us know when we're expecting a rising edge on pinA to signal that the encoder has arrived at a detent
    volatile byte bFlag = 0; // let's us know when we're expecting a rising edge on pinB to signal that the encoder has arrived at a detent (opposite direction to when aFlag is set)
    volatile uint16_t encoderPos = 0; //this variable stores our current value of encoder position. Change to int or uin16_t instead of byte if you want to record a larger range than 0-255
    volatile uint16_t oldEncPos = 0; //stores the last encoder position value so we can compare to the current reading and see if it has changed (so we know when to print to the serial monitor)
    volatile byte reading = 0; //somewhere to store the direct values we read from our interrupt pins before checking to see if we have moved a whole detent
    
    // Software SPI (slower updates, more flexible pin options):
    // pin 7 - Serial clock out (SCLK)
    // pin 6 - Serial data out (DIN)
    // pin 5 - Data/Command select (D/C)
    // pin 4 - LCD chip select (CS)
    // pin 8 - LCD reset (RST)
    Adafruit_PCD8544 display = Adafruit_PCD8544(A4, A3, A2, A1, A0);
    
    //Declare the Menus you need.
    char menu[][10] = {"Activer", "Regler", "Ecran", "Volume", "Retour"};
    byte itemCount = 4;
    int itemSelected;
    byte runOnceAWeek = 1;   //allow for time update once a week
    
    char dataBuffer[20];  // for sprintf function
    bool alarmStatus;     // Alarm set/unset status
    int alarmHour = 0;
    int alarmMin = 0;
    int contrast = 50;
    int backlightFlag = 0;
    int backlightCountdown = 10000;   // switch off backlight after XX millisec
    int loopTime = 0;
    int prevMillis = 0;
    
    MyMessage  textMsg(ALARM_ID, V_TEXT);      // Initialize clock messages
    MyMessage alarmMsg(ACTIV_ID, V_LOCK_STATUS);    // Initialize switch messages
    
    void presentation()  {
      sendSketchInfo("Loulou's Clock", "08.07.2017");
      present(ALARM_ID, S_INFO);
      present(ACTIV_ID, S_LOCK);
    }
    
    void setup() {
      pinMode(A5, OUTPUT);     //5110 screen backlight
      digitalWrite(A5, LOW);
      display.begin();
      pinMode(enSW, INPUT_PULLUP);
      pinMode(pinA, INPUT_PULLUP);
      pinMode(pinB, INPUT_PULLUP);
      attachInterrupt(0, PinA, RISING);
      attachInterrupt(1, PinB, RISING);
      Serial.begin(115200);
      display.setContrast(contrast);
      display.display();
      display.clearDisplay();
      display.setTextColor(BLACK);
    
      int clockCounter = 0;
      while (timeStatus() == timeNotSet && clockCounter < 60) {
        requestTime();
        clockCounter++;
        wait(1000);
        /*
          Serial.print("état reception:");
          Serial.println(timeStatus());
        */
        if (clockCounter > 16) {
          /*
            Serial.print(F("**Failed Clock**"));
            Serial.print(F("*Syncronization*"));
          */
          break;
        }
      }
      alarmStatus = loadState(0);    // Read last lock status from eeprom
      setAlarmState(alarmStatus, true); // Now set the last known state and send it to controller
      sendTime();
    }
    
    void loop() {
      /*** synchronise clock node with controller from time to time... ***/
      if (weekday() == 5 && runOnceAWeek == 1) {
        requestTime();
        runOnceAWeek = 0;
      }
      if (weekday() != 5)
        runOnceAWeek = 1;
    
      /*** switch off backlight function ***/
      loopTime = millis() - prevMillis;
      prevMillis = millis();
      backlightFlag = backlightFlag + loopTime;
      if (backlightFlag > backlightCountdown) {
        digitalWrite(A5, HIGH);
        backlightFlag = backlightCountdown + 1;   // to avoid overflow
      }
      else
        digitalWrite(A5, LOW);
    
      /*** Show time on the default Screen ***/
      display.clearDisplay();
      display.setTextSize(2);
      display.setCursor(14, 2);
      sprintf(dataBuffer, "%02u:%02u ", hour() + 2, minute());
      display.print(dataBuffer);
      display.display();
      display.setTextSize(1);
      display.setCursor(14, 22);
      sprintf(dataBuffer, "%02u-%02u-%04u ", day(), month(), year());
      display.print(dataBuffer);
      display.display();
    
      if (alarmStatus == 0) {       //Show whether alarm is on or off
        display.setCursor(14, 35);
        sprintf(dataBuffer, "Alarme OFF");
      }
      else {
        display.setCursor(7, 35);
        char alarmText[7] = "Alarme";
        sprintf(dataBuffer, "%s %02u:%02u", alarmText, alarmHour, alarmMin);
      }
      display.print(dataBuffer);
      display.setTextSize(1);
      display.display();
    
      /*** Enter the settings menu if select Switch is pressed ***/
      if (digitalRead(enSW) == 0) {
        while (digitalRead(enSW) == 0); //wait till switch is released.
        encoderPos = 0;
        digitalWrite(A5, LOW);          // switch backlight ON when entering menu
        switch (encoderPos) {           //Enter main program
          case 0: display.clearDisplay();
            display.setTextSize(2);
            display.setCursor(0, 10);
            display.println(menu[encoderPos]);
            display.display();
            while (digitalRead(enSW)) {
              encoderPos = constrain(encoderPos, 0, itemCount);
              display.clearDisplay();
              display.setCursor(0, 0);
              display.println(menu[encoderPos]);
              if (encoderPos < 0)
                encoderPos = itemCount;
              if  (encoderPos > itemCount)
                encoderPos = 0;
              display.display();
            }
            while (digitalRead(enSW) == 0);
            itemSelected = encoderPos;
          default: break;
        }
        switch (itemSelected) {
          case 0: display.clearDisplay();      //ACTIVATE ALARME
            display.setTextSize(2);
            display.setCursor(0, 10);
            display.println(menu[itemSelected]);
            display.display();
            encoderPos = alarmStatus;
            while (digitalRead(enSW)) {
              encoderPos = constrain(encoderPos, 0, 1);
              display.clearDisplay();
              display.setCursor(0, 0);
              display.print(menu[itemSelected]);
              display.setCursor(16, 26);
              display.print(encoderPos);
              if (encoderPos == 0)  {
                sprintf(dataBuffer, "OFF");
              }
              else  {
                sprintf(dataBuffer, "ON");
              }
              display.setCursor(45, 26);
              display.print(dataBuffer);
              display.display();
            }
            if (encoderPos != alarmStatus)  {
              alarmStatus = encoderPos;
              send(alarmMsg.set(alarmStatus));
            }
            while (digitalRead(enSW) == 0);
            break;
    
          case 1: display.clearDisplay();     //SET ALARME TIME
            display.setTextSize(2);
            display.setCursor(0, 10);
            display.print(menu[itemSelected]);
            display.display();
            encoderPos = alarmHour;
            while (digitalRead(enSW)) {       //SET ALARME HOUR
              encoderPos = constrain(encoderPos, 00, 23);
              display.clearDisplay();
              display.setCursor(0, 0);
              display.print(menu[itemSelected]);
              display.setCursor(10, 26);       //HOUR LOCATION
              display.print(encoderPos);
              if (encoderPos < 0)
                encoderPos = 23;
              if  (encoderPos > 23)
                encoderPos = 0;
              display.setCursor(35, 26);       // ":" LOCATION
              display.print(":");
              display.setCursor(50, 26);       //MINUTE LOCATION
              display.print(alarmMin);
              display.display();
            }
            if (encoderPos != alarmHour)  {
              alarmHour = encoderPos;
              sendTime();
            }
            encoderPos = alarmMin;
            while (digitalRead(enSW)) {        //SET ALARME MINUTE
              encoderPos = constrain(encoderPos, 00, 59);
              display.clearDisplay();
              display.setCursor(0, 0);
              display.print(menu[itemSelected]);
              display.setCursor(10, 26);       //HOUR LOCATION
              display.print(alarmHour);
              display.setCursor(35, 26);       //":" LOCATION
              display.print(":");
              display.setCursor(50, 26);       //MINUTE LOCATION
              display.print(encoderPos);
              if (encoderPos < 0)
                encoderPos = 59;
              if  (encoderPos > 59)
                encoderPos = 0;
              display.display();
            }
            if (encoderPos != alarmMin)  {
              alarmMin = encoderPos;
              sendTime();
            }
            while (digitalRead(enSW) == 0);
            break;
    
          case 2: display.clearDisplay();        //CONTRAST
            display.setTextSize(2);
            display.setCursor(0, 10);
            display.print(menu[itemSelected]);
            display.display();
            encoderPos = contrast;
            while (digitalRead(enSW)) {
              encoderPos = constrain(encoderPos, 0, 100);
              display.clearDisplay();
              display.setCursor(0, 0);
              display.print(menu[itemSelected]);
              display.setCursor(16, 16);
              display.print(encoderPos);
              if (encoderPos < 0)
                encoderPos = 100;
              if  (encoderPos > 100)
                encoderPos = 0;
              display.setContrast(encoderPos);
              display.display();
            }
            if (encoderPos != contrast)
              contrast = encoderPos;
            while (digitalRead(enSW) == 0);
            break;
          default: break;
        }
      }
    }
    
    
    /*** send alarm time to controller ****/
    void sendTime()  {
      sprintf(dataBuffer, "%02u:%02u ", alarmHour, alarmMin);
      send(textMsg.set(dataBuffer));
      wait(1000);
    }
    
    /*** Activate alarm ***/
    void setAlarmState(bool state, bool doSend) {
      if (doSend)
        send(alarmMsg.set(state));
      saveState(0, state);
      alarmStatus = state;
    }
    
    /*** receive alarme state from controller ***/
    void receive(const MyMessage & message) {
      if (message.type == V_LOCK_STATUS) {
        setAlarmState(message.getBool(), false);      // Change relay state
      }
    }
    
    /*** receive a new time value ***/
    void receiveTime(unsigned long controllerTime) {
      setTime(controllerTime);
    }
    
    /*** rotary encoder interrupt function ***/
    void PinA() {
      cli(); //stop interrupts happening before we read pin values
      reading = PIND & 0xC; // read all eight pin values then strip away all but pinA and pinB's values
      if (reading == B00001100 && aFlag) { //check that we have both pins at detent (HIGH) and that we are expecting detent on this pin's rising edge
        encoderPos --; //decrement the encoder's position count
        bFlag = 0; //reset flags for the next turn
        aFlag = 0; //reset flags for the next turn
        backlightFlag = 0;
      }
      else if (reading == B00000100) bFlag = 1; //signal that we're expecting pinB to signal the transition to detent from free rotation
      sei(); //restart interrupts
    }
    
    
    void PinB() {
      cli(); //stop interrupts happening before we read pin values
      reading = PIND & 0xC; //read all eight pin values then strip away all but pinA and pinB's values
      if (reading == B00001100 && bFlag) { //check that we have both pins at detent (HIGH) and that we are expecting detent on this pin's rising edge
        encoderPos ++; //increment the encoder's position count
        bFlag = 0; //reset flags for the next turn
        aFlag = 0; //reset flags for the next turn
        backlightFlag = 0;
      }
      else if (reading == B00001000) aFlag = 1; //signal that we're expecting pinA to signal the transition to detent from free rotation
      sei(); //restart interrupts
    }```


  • Guys

    Funny discovery tonight :
    "alarm time" value in PaperUI can be set by clicking on it !! It is not a read-only value, it can also BE SET !!!

    0_1500492214317_Capture du 2017-07-19 21-23-03.png

    Now the big question is: how can my node retreive that new value ?

    21:17:04.816 [DEBUG] [rsAbstractConnection$MySensorsReader] - 50;255;3;0;21;0
    21:17:20.291 [INFO ] [smarthome.event.ItemCommandEvent    ] - Item 'bedAlarm01' received command 21:12
    21:17:20.296 [DEBUG] [rs.internal.gateway.MySensorsGateway] - Node 50 found in gateway
    21:17:20.298 [DEBUG] [rs.internal.gateway.MySensorsGateway] - Child 0 found in node 50
    21:17:20.299 [DEBUG] [rsAbstractConnection$MySensorsWriter] - Sending to MySensors: 50;0;1;0;47;21:12
    

    Looks like gateway sends a text message (47) towards node... so sketch needs to make a difference between "lock" messages and "alarm time" messages, right ?

    /*** receive alarme state and time from controller ***/
    void receive(const MyMessage & message) {
      if (message.type == V_LOCK_STATUS) {
        setAlarmState(message.getBool(), false);
      }
      if (message.type == V_TEXT) {
        do (
             use strtok_r(message.data, ";", &p) to extract payload
             somehow extract hour and minute from that payload
             store them in int alarmHour int and alarmMin
      }
    }
    

    A clue to get me on the way ?

    Thanks a lot for your input 🙂



  • This post is deleted!


  • Guys,

    I need some help por favor 🙂

    I am fighting against payload! The picture (a few posts above) shows that i can modify two values (seen under "Heure de réveil")

    As i validate the new value, the gateway sends a message to the node

    Sending to MySensors: 50;0;1;0;47;21:12
    

    Which is great, that's a new feature to me 🙂

    Now on the node side : I have included

    void receive(const MyMessage & message) {
      if (message.type == V_TEXT)
        Serial.print(message.data);
       // or Serial.print(message.getString());  // works fine too
    }
    

    Serial monitor of node shows

    21:12
    

    Lovely jobly sounds good

    I am now facing two major problems to me as a noob:

    • what is the format of "message.data" ? string? whenever i try to play with it i get some "invalid conversion" from IDE...
    • how to split that "message.data" into two integers (alarmHour and alarmMin) ?

    Thanks for your help ! That should be the final shout for help as this project is nearly achieved...



  • Can someone please comment on this DIY mess (it's a mix of bits and pieces found here and there)

    void receive(const MyMessage & message) {
      if (message.type == V_LOCK_STATUS)
        setAlarmState(message.getBool(), false);
      if (message.type == V_TEXT) {
        char *alarmTime =  message.data;
        char* token = strtok(alarmTime, ":");
        alarmHour = atoi(token);
        token= strtok(NULL, "");
        alarmMin = atoi(token);
      }
    

    It works the way I need it but i guess it's a dirty way of getting there... your eyes might start bleeding 😄 i get this from compiler :

    warning: invalid conversion from 'const char*' to 'char*' [-fpermissive]
         char *alarmTime =  message.data;
    

    Any suggestion to make it neater?


  • Mod

    @ben999 message.data is const char* which means its contents can not (or at least should not) be modified.
    Then you set the alarmTime pointer to point to the same message.data, which means that if you modify the contents of alarmTime you will also modify the contents of message.data (they are not two copies of the same thing, they are the same thing)
    The definition of strtok is

    char *strtok(char *restrict source, const char *restrict delimiters);
    

    which means the first parameter must allow modification (no const), while the second does not need modification.

    The man page of strtok also says this (my emphasis):

    The strtok function returns a pointer to the beginning of each subsequent token in the string, after replacing the separator character itself with a null character. When no more tokens remain, a null pointer is returned.

    which explains why the parameter must allow modification.
    That's why you can't do

    const char *alarmTime =  message.data;
    

    I am not a c expert, but I think you should do this instead:

    void receive(const MyMessage & message) {
      if (message.type == V_LOCK_STATUS) setAlarmState(message.getBool(), false);
      if (message.type == V_TEXT) {
        char alarmTime[MAX_MESSAGE_LENGTH];
        strncpy(alarmTime, message.data, MAX_MESSAGE_LENGTH);
        char* token = strtok(alarmTime, ":");
        alarmHour = atoi(token);
        token = strtok(NULL, "");
        alarmMin = atoi(token);
      }
    }
    

    MAX_MESSAGE_LENGTH is provided by the MySensors library.



  • @mfalkvidd thank you very much for your detailed explanation !

    It does make a lot of sense

    And it compiles perfectly

    Thanks again for taking the time to correct and explain 🙂



  • Full sketch
    Some mods to come:

    • stop alarm while ringing
    • snoze
    //#define MY_DEBUG          // Enable debug prints
    #define MY_RADIO_NRF24   // Enable and select radio type attached
    
    #define MY_PARENT_ID 0
    #define MY_NODE_ID 50
    
    #include <SPI.h>
    #include <Adafruit_GFX.h>
    #include <Adafruit_PCD8544.h>
    #include <MySensors.h>
    #include <Time.h>
    #include <Wtv020sd16p.h>
    
    #define ALARM_ID 0                      // Id of TimeOfAlarm value
    #define ACTIV_ID 1                      // Id of ActivateAlarm switch
    
    int resetPin = 5;  // The pin number of the reset pin.
    int clockPin = 6;  // The pin number of the clock pin.
    int dataPin = 7;  // The pin number of the data pin.
    int busyPin = 8;  // The pin number of the busy pin.
    
    static int pinA = 2; // Our first hardware interrupt pin is digital pin 2
    static int pinB = 3; // Our second hardware interrupt pin is digital pin 3
    static int enSW = 4; // Rotary switch push-button
    
    volatile byte aFlag = 0; // let's us know when we're expecting a rising edge on pinA to signal that the encoder has arrived at a detent
    volatile byte bFlag = 0; // let's us know when we're expecting a rising edge on pinB to signal that the encoder has arrived at a detent (opposite direction to when aFlag is set)
    volatile uint16_t encoderPos = 0; //this variable stores our current value of encoder position. Change to int or uin16_t instead of byte if you want to record a larger range than 0-255
    volatile uint16_t oldEncPos = 0; //stores the last encoder position value so we can compare to the current reading and see if it has changed (so we know when to print to the serial monitor)
    volatile byte reading = 0; //somewhere to store the direct values we read from our interrupt pins before checking to see if we have moved a whole detent
    
    // Software SPI (slower updates, more flexible pin options):
    // pin 7 - Serial clock out (SCLK)
    // pin 6 - Serial data out (DIN)
    // pin 5 - Data/Command select (D/C)
    // pin 4 - LCD chip select (CS)
    // pin 8 - LCD reset (RST)
    Adafruit_PCD8544 display = Adafruit_PCD8544(A4, A3, A2, A1, A0);
    
    
    // Create an instance of the Wtv020sd16p class.
    // 1st parameter: Reset pin number.
    // 2nd parameter: Clock pin number.
    // 3rd parameter: Data pin number.
    // 4th parameter: Busy pin number.
    Wtv020sd16p wtv020sd16p(resetPin, clockPin, dataPin, busyPin);
    
    //Declare the Menus you need.
    char menu[][20] = {"ActiverAlarme", "Regler Alarme" , "Contra-ste", "Volume",
                       "MusiqueAlarme", "EcouterMusique", "Retour"
                      };
    byte itemCount = 6;
    int itemSelected;
    byte runOnceAWeek = 1;   //allow for time update once a week
    
    char dataBuffer[20];  // for sprintf function
    bool alarmStatus;     // Alarm set/unset status
    int alarmHour = 0;
    int alarmMin = 0;
    int alarmTune = 1;
    unsigned int alarmVolume = 5;
    int contrast = 50;
    int backlightFlag = 0;
    int backlightCountdown = 10000;   // switch off backlight after XX millisec
    int loopTime = 0;
    int prevMillis = 0;
    byte smallFont = 1;
    byte bigFont = 2;
    
    MyMessage  textMsg(ALARM_ID, V_TEXT);      // Initialize clock messages
    MyMessage alarmMsg(ACTIV_ID, V_LOCK_STATUS);    // Initialize switch messages
    
    void presentation()  {
      sendSketchInfo("Loulou's Clock", "25.07.2017");
      present(ALARM_ID, S_INFO);
      present(ACTIV_ID, S_LOCK);
    }
    
    void setup() {
      wtv020sd16p.reset();     //MP3 player
      delay(1000);
      wtv020sd16p.setVolume(alarmVolume);
      pinMode(A5, OUTPUT);     //5110 screen backlight
      digitalWrite(A5, LOW);
      display.begin();
      pinMode(enSW, INPUT_PULLUP);
      pinMode(pinA, INPUT_PULLUP);
      pinMode(pinB, INPUT_PULLUP);
      attachInterrupt(0, PinA, RISING);
      attachInterrupt(1, PinB, RISING);
      Serial.begin(115200);
      display.setContrast(contrast);
      display.display();
      display.clearDisplay();
      display.setTextColor(BLACK);
    
      int clockCounter = 0;
      while (timeStatus() == timeNotSet && clockCounter < 60) {
        requestTime();
        clockCounter++;
        wait(1000);
        /*
          Serial.print("état reception:");
          Serial.println(timeStatus());
        */
        if (clockCounter > 16) {
          /*
            Serial.print(F("**Failed Clock**"));
            Serial.print(F("*Syncronization*"));
          */
          break;
        }
      }
      alarmStatus = loadState(0);    // Read last lock status from eeprom
      setAlarmState(alarmStatus, true); // Now set the last known state and send it to controller
      sendTime();
    }
    
    void loop() {
      /*** ALARM !!! ***/
      while (hour() + 2 == alarmHour && minute() == alarmMin) {
        digitalWrite(A5, LOW);          // switch backlight ON when entering menu
        display.clearDisplay();
        display.setTextSize(1);
        display.setCursor(2, 2);
        display.print("Debout Axou");
        display.display();
        display.setCursor(2, 20);
        display.print("Faut y aller");
        display.display();
        display.setCursor(2, 40);
        sprintf(dataBuffer, "Il est %02u:%02u ", hour() + 2, minute());
        display.print(dataBuffer);
        display.display();
        wtv020sd16p.playVoice(alarmTune - 1);
      }
    
      /*** synchronise clock node with controller from time to time... ***/
      if (weekday() == 5 && runOnceAWeek == 1) {
        requestTime();
        runOnceAWeek = 0;
      }
      if (weekday() != 5)
        runOnceAWeek = 1;
    
      /*** switch off backlight function ***/
      loopTime = millis() - prevMillis;
      prevMillis = millis();
      backlightFlag = backlightFlag + loopTime;
      if (backlightFlag > backlightCountdown) {
        digitalWrite(A5, HIGH);
        backlightFlag = backlightCountdown + 1;   // to avoid overflow
      }
      else
        digitalWrite(A5, LOW);
    
      /*** Show time on the default Screen ***/
      display.clearDisplay();
      display.setTextSize(2);
      display.setCursor(14, 2);
      sprintf(dataBuffer, "%02u:%02u ", hour() + 2, minute());
      display.print(dataBuffer);
      //display.display();
      display.setTextSize(1);
      display.setCursor(14, 22);
      sprintf(dataBuffer, "%02u-%02u-%04u ", day(), month(), year());
      display.print(dataBuffer);
      display.display();
    
      if (alarmStatus == 0) {       //Show whether alarm is on or off
        display.setCursor(14, 35);
        sprintf(dataBuffer, "Alarme OFF");
      }
      else {
        display.setCursor(7, 35);
        char alarmText[7] = "Alarme";
        sprintf(dataBuffer, "%s %02u:%02u", alarmText, alarmHour, alarmMin);
      }
      display.print(dataBuffer);
      display.display();
    
      /*** Enter the settings menu if select Switch is pressed ***/
      if (digitalRead(enSW) == 0) {
        while (digitalRead(enSW) == 0); //wait till switch is released.
        encoderPos = 0;
        digitalWrite(A5, LOW);          // switch backlight ON when entering menu
        switch (encoderPos) {           //Enter main program
          case 0: display.clearDisplay();
            display.setTextSize(2);
            display.setCursor(0, 10);
            display.println(menu[encoderPos]);
            display.display();
            while (digitalRead(enSW)) {
              encoderPos = constrain(encoderPos, 0, itemCount);
              display.clearDisplay();
              display.setCursor(0, 0);
              display.println(menu[encoderPos]);
              if (encoderPos < 0)
                encoderPos = itemCount;
              if  (encoderPos > itemCount)
                encoderPos = 0;
              display.display();
            }
            while (digitalRead(enSW) == 0);
            itemSelected = encoderPos;
          default: break;
        }
        switch (itemSelected) {
          case 0: display.clearDisplay();      //ACTIVATE ALARME
            display.setTextSize(bigFont);
            display.setCursor(0, 10);
            display.println(menu[itemSelected]);
            display.display();
            encoderPos = alarmStatus;
            while (digitalRead(enSW)) {
              encoderPos = constrain(encoderPos, 0, 1);
              display.clearDisplay();
              display.setCursor(0, 0);
              display.print(menu[itemSelected]);
              display.setCursor(16, 34);
              display.print(encoderPos);
              if (encoderPos == 0)  {
                sprintf(dataBuffer, "OFF");
              }
              else  {
                sprintf(dataBuffer, "ON");
              }
              display.setCursor(45, 34);
              display.print(dataBuffer);
              display.display();
            }
            if (encoderPos != alarmStatus)  {
              alarmStatus = encoderPos;
              send(alarmMsg.set(alarmStatus));
            }
            while (digitalRead(enSW) == 0);
            break;
    
          case 1: display.clearDisplay();     //SET ALARME TIME
            display.setTextSize(bigFont);
            display.setCursor(0, 10);
            display.print(menu[itemSelected]);
            display.display();
            encoderPos = alarmHour;
            while (digitalRead(enSW)) {       //SET ALARME HOUR
              encoderPos = constrain(encoderPos, 00, 23);
              display.clearDisplay();
              display.setCursor(0, 0);
              display.print(menu[itemSelected]);
              display.setCursor(10, 34);       //HOUR LOCATION
              display.print(encoderPos);
              if (encoderPos < 0)
                encoderPos = 23;
              if  (encoderPos > 23)
                encoderPos = 0;
              display.setCursor(35, 34);       // ":" LOCATION
              display.print(":");
              display.setCursor(50, 34);       //MINUTE LOCATION
              display.print(alarmMin);
              display.display();
            }
            if (encoderPos != alarmHour)  {
              alarmHour = encoderPos;
              sendTime();
            }
            encoderPos = alarmMin;
            while (digitalRead(enSW)) {        //SET ALARME MINUTE
              encoderPos = constrain(encoderPos, 00, 59);
              display.clearDisplay();
              display.setCursor(0, 0);
              display.print(menu[itemSelected]);
              display.setCursor(10, 34);       //HOUR LOCATION
              display.print(alarmHour);
              display.setCursor(35, 34);       //":" LOCATION
              display.print(":");
              display.setCursor(50, 34);       //MINUTE LOCATION
              display.print(encoderPos);
              if (encoderPos < 0)
                encoderPos = 59;
              if  (encoderPos > 59)
                encoderPos = 0;
              display.display();
            }
            if (encoderPos != alarmMin)  {
              alarmMin = encoderPos;
              sendTime();
            }
            while (digitalRead(enSW) == 0);
            break;
    
          case 2: display.clearDisplay();        //CONTRAST
            display.setTextSize(bigFont);
            display.setCursor(0, 10);
            display.print(menu[itemSelected]);
            display.display();
            encoderPos = contrast;
            while (digitalRead(enSW)) {
              encoderPos = constrain(encoderPos, 0, 100);
              display.clearDisplay();
              display.setCursor(0, 0);
              display.print(menu[itemSelected]);
              display.setCursor(16, 16);
              display.print(encoderPos);
              if (encoderPos < 0)
                encoderPos = 100;
              if  (encoderPos > 100)
                encoderPos = 0;
              display.setContrast(encoderPos);
              display.display();
            }
            if (encoderPos != contrast)
              contrast = encoderPos;
            while (digitalRead(enSW) == 0);
            break;
    
          case 3: display.clearDisplay();        //VOLUME
            display.setTextSize(smallFont);
            display.setCursor(0, 10);
            display.print(menu[itemSelected]);
            display.display();
            encoderPos = alarmVolume;
            while (digitalRead(enSW)) {
              encoderPos = constrain(encoderPos, 0, 7);
              display.clearDisplay();
              display.setCursor(0, 0);
              display.print(menu[itemSelected]);
              display.setCursor(16, 34);
              display.print(encoderPos);
              if (encoderPos < 0)
                encoderPos = 7;
              if  (encoderPos > 7)
                encoderPos = 0;
              display.display();
              wtv020sd16p.setVolume(encoderPos);
            }
            alarmVolume = encoderPos;
            while (digitalRead(enSW) == 0);
            break;
    
          case 4: display.clearDisplay();        //ALARM TUNE
            display.setTextSize(bigFont);
            display.setCursor(0, 10);
            display.print(menu[itemSelected]);
            display.display();
            encoderPos = alarmTune;
            while (digitalRead(enSW)) {
              encoderPos = constrain(encoderPos, 1, 10);
              display.clearDisplay();
              display.setCursor(0, 0);
              display.print(menu[itemSelected]);
              display.setCursor(16, 34);
              display.print(encoderPos);
              if (encoderPos < 1)
                encoderPos = 10;
              if  (encoderPos > 10)
                encoderPos = 0;
              display.display();
            }
            alarmTune = encoderPos;
            while (digitalRead(enSW) == 0);
            break;
    
          case 5: display.clearDisplay();        //PLAYER
            display.setTextSize(bigFont);
            display.setCursor(0, 10);
            display.print(menu[itemSelected]);
            display.display();
            encoderPos = 0;
            while (digitalRead(enSW)) {
              encoderPos = constrain(encoderPos, 0, 10);
              display.clearDisplay();
              display.setCursor(0, 0);
              display.print(menu[itemSelected]);
              display.setCursor(16, 34);
              display.print(encoderPos);
              if (encoderPos < 0)
                encoderPos = 10;
              if  (encoderPos > 10)
                encoderPos = 0;
              display.display();
            }
            if (encoderPos == 0)
              wtv020sd16p.stopVoice();
            else
              wtv020sd16p.playVoice(encoderPos - 1);
            while (digitalRead(enSW) == 0);
            break;
    
          default: break;
        }
      }
    }
    
    /*** receive a new time value ***/
    void receiveTime(unsigned long controllerTime) {
      setTime(controllerTime);
    }
    
    /*** send alarm time to controller ****/
    void sendTime()  {
      sprintf(dataBuffer, "%02u:%02u ", alarmHour, alarmMin);
      send(textMsg.set(dataBuffer));
      wait(1000);
    }
    
    /*** Activate alarm ***/
    void setAlarmState(bool state, bool doSend) {
      if (doSend)
        send(alarmMsg.set(state));
      saveState(0, state);
      alarmStatus = state;
    }
    
    /*** receive alarme state from controller ***/
    void receive(const MyMessage & message) {
      if (message.type == V_LOCK_STATUS)
        setAlarmState(message.getBool(), false);
      if (message.type == V_TEXT) {
        char alarmTime[MAX_MESSAGE_LENGTH];
        strncpy(alarmTime, message.data, MAX_MESSAGE_LENGTH);
        char* token = strtok(alarmTime, ":");
        alarmHour = atoi(token);
        token = strtok(NULL, "");
        alarmMin = atoi(token);
      }
    
    }
    
    /*** rotary encoder interrupt function ***/
    void PinA() {
      cli(); //stop interrupts happening before we read pin values
      reading = PIND & 0xC; // read all eight pin values then strip away all but pinA and pinB's values
      if (reading == B00001100 && aFlag) { //check that we have both pins at detent (HIGH) and that we are expecting detent on this pin's rising edge
        encoderPos --; //decrement the encoder's position count
        bFlag = 0; //reset flags for the next turn
        aFlag = 0; //reset flags for the next turn
        backlightFlag = 0;
      }
      else if (reading == B00000100) bFlag = 1; //signal that we're expecting pinB to signal the transition to detent from free rotation
      sei(); //restart interrupts
    }
    
    
    void PinB() {
      cli(); //stop interrupts happening before we read pin values
      reading = PIND & 0xC; //read all eight pin values then strip away all but pinA and pinB's values
      if (reading == B00001100 && bFlag) { //check that we have both pins at detent (HIGH) and that we are expecting detent on this pin's rising edge
        encoderPos ++; //increment the encoder's position count
        bFlag = 0; //reset flags for the next turn
        aFlag = 0; //reset flags for the next turn
        backlightFlag = 0;
      }
      else if (reading == B00001000) aFlag = 1; //signal that we're expecting pinA to signal the transition to detent from free rotation
      sei(); //restart interrupts
    }```


  • Nice project would love to see the finished product as I want to try it as well. Can you post all the details once you get it do Easy?

    Thanks and good luck



  • Hi I need your help, I created this clock and complied the sketch, looked like it worked but when adding it to very I get the error message that D_LcdText1.xml is not available. Where do I find that and how do i add it to the Vera Plus?

    Excellent project and can't wait to have it up and running.


  • Hero Member

    @Newzwaver you can find the needed files here


Log in to reply
 

577
Online

6.7k
Users

7.6k
Topics

80.6k
Posts

Looks like your connection to MySensors Forum was lost, please wait while we try to reconnect.