3KUdelta / ESP32_ServoClock_MST

4 digit clock using servos to handle the segments
MIT License
5 stars 0 forks source link

12 Hour Clock Question / Null 1st Digit #4

Closed BzowK closed 3 years ago

BzowK commented 3 years ago

Hey!

Had to put project aside for a while, but finished it up recently and love it. Thanks a lot for taking time to code the way you did. I did have an issue, a question, & a suggestion I've been meaning to post about, though. Any assistance would be greatly appreciated...

The Issue Time Correctly Sets to a 12 Hour clock if powered on in PM hours. If powered on in AM hours, it shows 24 hour clock after noon. Resetting power shows time with 12 hour clock again as it should. In other words, if powered on in the AM, once it's 1:00pm, it shows 13:00. Cycling the power (correctly) shows it is 1:00

I have the below line at the end of the get_NTP_time function but seemingly only runs it once when powered on. Looks to me like it should be in the loop, but no coding expert :)

if (hour(now()) >=12) adjustTime(-43200); // 43200 seconds = 12 hours

...Entire code below...

The Question Since I use a 12 hour clock, I obviously only use the first digit / 8 servos for the occasional "1". I've tried editing the code to make the first digit display nothing (all servos in off position) instead of a "0" when the time not "11" or "12", but have so far been unsuccessful. Would this be an easy change or no?

The Suggestion My clock required a lot of fine-tuning which obviously requires removing from wall, disconnecting the power from the servo boards (I put a switch in as suggested), etc. I instead added in code to use OTA to perform flashes which works great. If you'd like to add (or not), I've attached my current sketch.

Any suggestions would be appreciated - Thanks!

#include <Wire.h>
#include <Adafruit_PWMServoDriver.h>  
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>    
#include <EasyNTPClient.h> 
#include <TimeLib.h>    
#include <ESP8266mDNS.h>
#include <ArduinoOTA.h>

// WiFi
#define STASSID "YourWifiSSID"  // Insert wifi SSID here
#define STAPSK  "WifiPassword"   // Insert wifi password here
const char* ssid     = STASSID;
const char* password = STAPSK;

// NTP
#define NTP_SERVER   "ch.pool.ntp.org"
#define TZ           -6  
#define TZ_SEC       ((TZ)*3600)  
#define UTC_OFFSET + TZ

Adafruit_PWMServoDriver servoHours = Adafruit_PWMServoDriver(0x40);   //first PCA9685 address
Adafruit_PWMServoDriver servoMinutes = Adafruit_PWMServoDriver(0x41); //second PCA9685 address, solder blob on A0

WiFiUDP udp;
EasyNTPClient ntpClient(udp, NTP_SERVER, TZ_SEC);  //calls NTP for current timezone; summertime_EU() does the rest

//*** declaration of functions ***************************************************

void capturetime();
void showtime(byte m [4], int w);
void showdigit(byte i, byte digit, int w);
void go_online();
void get_NTP_time();
boolean summertime_EU(int year, byte month, byte day, byte hour, byte tzHours);

//*********************************************************************************

const int enablepin = 0;                  // ESP32 pin connected to the OE pin at PCA9685, would also work without
const int servopulse [2] {210, 400};      // pulse of servos at low positions and high position

const byte mapchar [12][7] = { //for each number, position of every segment
  {1, 1, 1, 0, 1, 1, 1}, // zero          //   0
  {0, 0, 1, 0, 0, 1, 0}, // one           // 1   2
  {1, 0, 1, 1, 1, 0, 1}, // two           //   3
  {1, 0, 1, 1, 0, 1, 1}, // three         // 4   5
  {0, 1, 1, 1, 0, 1, 0}, // four          //   6
  {1, 1, 0, 1, 0, 1, 1}, // five
  {1, 1, 0, 1, 1, 1, 1}, // six
  {1, 0, 1, 0, 0, 1, 0}, // seven
  {1, 1, 1, 1, 1, 1, 1}, // eight
  {1, 1, 1, 1, 0, 1, 1}, // nine
  {0, 0, 0, 0, 0, 0, 0}, // null (all down)
  {1, 1, 0, 1, 1, 0, 1}  // E for error indication
};

const byte servoreverse [4] [7] = {      // one identifies wservos that work reverse, zero is normal direction
  {1, 1, 0, 0, 0, 1, 1},
  {1, 1, 0, 0, 0, 1, 1},
  {1, 1, 0, 0, 0, 1, 1},
  {1, 1, 0, 0, 0, 1, 1}
};
const int servofinetune [4] [7] = {      // pulse to add to servopulse for fine tunning of each individual servo
  //positive increase adjusts to the right
 // 0, 1, 2, 3, 4, 5, 6
  {0, 0, 30, 20, 35, -30, 10},
 //7, 8, 9, 10, 11, 12, 13
  {-5, 40, 40, 25, 60, 5, 30},
 //14, 15, 16, 17, 18, 19, 20
  {-20, 40, 40, 55, 65, .5, -15},
 //21, 22, 23, 24, 25, 26, 27
  {-35, 30, 35, 15, 35, 10, 15},
};
byte allon [4] = {8, 8, 8, 8};           // all digits on
byte alloff [4] = {10, 10, 10, 10};      // all digits off for startup
byte momentdisplay [4];                  // time shown in display
byte moment [4];                         // to store actual time in 4 digits
unsigned long timestamp;                 // timestamp to measure time past since last update on NTP server

void setup() {
  delay(500);                            // give time after boot
  Serial.begin(115200);
  Serial.println();
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);
  pinMode(enablepin, OUTPUT);            // this is the pin for OE in the PCA (enable/disable servos LOW/HIGH)
  servoHours.begin();
  servoHours.setPWMFreq(50);             // set frequncy for first PCA
  servoMinutes.begin();
  servoMinutes.setPWMFreq(50);           // set frequency for second PCA
  digitalWrite(enablepin, LOW);          // enable the servos
  delay(200);
  showtime(allon, 220);                  // initializing all servos
  showtime(alloff, 220);
  go_online();                           // connect to WiFi
  get_NTP_time();                        // get precise time from time server

  ArduinoOTA.setHostname("ServoClock");
  ArduinoOTA.onStart([]() {
    String type;
    if (ArduinoOTA.getCommand() == U_FLASH)
      type = "sketch";
    else // U_SPIFFS
      type = "filesystem";

    // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end()
    Serial.println("Start updating " + type);
  });
  ArduinoOTA.onEnd([]() {
    Serial.println("\nEnd");
    ESP.restart();
  });
  ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
    Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
  });
  ArduinoOTA.onError([](ota_error_t error) {
    Serial.printf("Error[%u]: ", error);
    if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
    else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
    else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
    else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
    else if (error == OTA_END_ERROR) Serial.println("End Failed");
  });
  ArduinoOTA.begin();
//  Serial.println("Ready");
//  Serial.print("IP address: ");
//  Serial.println(WiFi.localIP());
}

void loop() {
  capturetime();                         // assign time to digits
  showtime(moment, 220);                 // to the main routine that shows the time in our digital clock
  if ((now() - timestamp) > 43200) {     // 43200 = 12 hours
    get_NTP_time();                      // get all 12 hours a time update from NTP Server --> avoiding a constant read from time server
  }
  ArduinoOTA.handle();
}

void capturetime() {
  moment[0] = hour(now()) / 10;               // find out first digit from hour
  moment[1] = hour(now()) - moment[0] * 10;   // find out second digit from hour
  moment[2] = minute(now()) / 10;             // find our first digit from minute
  moment[3] = minute(now()) - moment[2] * 10; // find out second digit from minute
  if ((hour(now()) < 1)) { // if hour is "0", displays a "12"
    moment[0] = 1;               
    moment[1] = 2;   
  }
// Serial.println();
Serial.println(moment[0]);
Serial.println(moment[1]);
Serial.println(moment[2]);
Serial.println(moment[3]);
Serial.println();
Serial.println(hour(now()));
//Serial.println(minute(now()));
}

void showtime(byte m [4], int w) {
  int d = 275; // delay needed to stop the servos moving before disabling them
  // parameters: matrix with the digits to show
  if (memcmp(m, momentdisplay, 4) != 0) { // if time displayed is different to time expected to be shown
    for (int i = 0; i < 4; i++) { // for every single position, show only if different to actually shown
      if (m[i] != momentdisplay[i]) {
        showdigit(i, m[i], w);
        momentdisplay [i] = m[i]; // take note of the shown digit
      }
    }
    delay(d); // give time to the servos to stop moving
  }
  }

void showdigit(byte i, byte digit, int w) { // parameters: i = digit position (0,1,2,3), digit = number, w = delay avoiding high power draw
  int pulse;
  byte servonum, a, segmentposition;

  for (byte j = 0; j < 7; j++) { // show the 7 segments
    servonum = j + (8 * ((i == 1 || i == 3))); // add 8 if position is 1 or 3 because 1 ands 3 digits start on pin 8 of the PCA
    segmentposition = mapchar[digit] [j]; // segment should be low or high?
    if (servoreverse [i] [j] == 0) {
      pulse = servopulse[segmentposition]; // for a normal (not reverse) servo assign pulse low or high
    }
    else {
      pulse = servopulse[!segmentposition]; // if servo works reverse, assign contrary
    }
    pulse = pulse + servofinetune [i] [j]; // add the fine tunning to the servo

    switch (i) { // switch for both PCA controllers
      case 0: case 1:
        servoHours.setPWM(servonum, 0, pulse);
        break;
      case 2: case 3:
        servoMinutes.setPWM(servonum, 0, pulse);
        break;
    }
    delay(w); // delay in between movement of contiguous segments, so we can do waves oleeeeeeee.
  }
}

void go_online() {
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  Serial.print("---> Connecting to WiFi ");
  int i = 0;
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    i++;
    if (i > 20) {
      Serial.println("Could not connect to WiFi!");
      Serial.println("Doing a reset now and retry a connection from scratch.");
      showdigit(0, 11, 100);                  // E
      showdigit(2, 0, 100);                   // 0
      showdigit(3, 1, 100);                   // 1 : shows Error 01, could not connect to WiFi
      delay(5000);                            // wait 5 secs and then do a retry
      showtime(alloff, 220);
      go_online();                            // try again
    }
    Serial.print(".");
  }
  Serial.println("Wifi connected ok.");
} //end go_online

void(* resetFunc) (void) = 0; //declare reset function @ address 0

void get_NTP_time() {
  Serial.println("---> Now reading time from NTP Server");

  if (WiFi.status() != WL_CONNECTED) {
    Serial.println("WiFi not connected! Doing that now again.");
    go_online();
  }
  int i = 0;
  while (!ntpClient.getUnixTime()) {
    delay(500);
    i++;
    if (i > 20) {
      Serial.println("Could not connect to NTP server!");
      Serial.println("Doing a reset now and retry a connection from scratch.");
      showdigit(0, 11, 100);                  // E
      showdigit(2, 0, 100);                   // 0
      showdigit(3, 2, 100);                   // 2 : shows Error 02, could not connect to NTP server
      delay(5000);                            // wait 5 secs and then do a reset
      resetFunc();                            // doing a full reset to retry
    }
    Serial.print(".");
  }

  setTime(ntpClient.getUnixTime());           // get UNIX timestamp (seconds from 1.1.1970 on)

  if (summertime_EU(year(now()), month(now()), day(now()), hour(now()), 1)) {
    adjustTime(3600);                         // adding one hour
  }
  timestamp = now();
  Serial.println("Time obtained!");
  if (hour(now()) >=12) adjustTime(-43200); // 43200 seconds = 12 hours
} // end get_NTP_time()

boolean summertime_EU(int year, byte month, byte day, byte hour, byte tzHours)
// European Daylight Savings Time calculation by "jurs" for German Arduino Forum
// input parameters: "normal time" for year, month, day, hour and tzHours (0=UTC, 1=MEZ)
// return value: returns true during Daylight Saving Time, false otherwise
{
  if (month < 3 || month > 10) return false; // keine Sommerzeit in Jan, Feb, Nov, Dez
  if (month > 3 && month < 10) return true;  // Sommerzeit in Apr, Mai, Jun, Jul, Aug, Sep
  if (month == 3 && (hour + 24 * day) >= (1 + tzHours + 24 * (31 - (5 * year / 4 + 4) % 7)) || month == 10 && (hour + 24 * day) < (1 + tzHours + 24 * (31 - (5 * year / 4 + 1) % 7)))
    return true;
  else
    return false;
} // end of summertime_EU()
3KUdelta commented 3 years ago

Hi, let's see if I can help. For your issue: just MOVE the line if (hour(now()) >=12) adjustTime(-43200); // 43200 seconds = 12 hours as the first instruction in function void capturetime(). Like this:

void capturetime() {
if (hour(now()) >=12) adjustTime(-43200); // 43200 seconds = 12 hours
moment[0] = hour(now()) / 10;               // find out first digit from hour

This should do it. Unfortunately, I cannot test on my installation.

For your question:

I don't get this:

  if ((hour(now()) < 1)) { // if hour is "0", displays a "12"
    moment[0] = 1;               
    moment[1] = 2;   
  }

exchange it with these lines an the leading zero should dissapear

if (hour(now()) < 10) {  // if leading zero
moment[0] = 10;  // 10 is defined as all servos down
}

For your suggestion:

I like the idea of the OTA approach. Please use the "Fork" function on the top right in GitHub to generate your own fork to share an maintain you own code.

Hope this works. Marc

BzowK commented 3 years ago

Thanks so much for your reply and assistance!

I'll give the code a shot tomorrow and let you know the results. Will start a fork as well in case it helps anyone else. Thanks again for the help!

BzowK commented 3 years ago

Both solutions worked like a champ - Thanks again!!

3KUdelta commented 3 years ago

You're welcome.