arduino-libraries / RTCZero

RTC Library for SAMD21 based boards
http://arduino.cc/en/Reference/RTC
GNU Lesser General Public License v2.1
78 stars 78 forks source link

Four Digit Year #17

Closed recoilnetworks closed 7 years ago

recoilnetworks commented 8 years ago

I am new to Arduino however, I have developed in other languages. Right now I am having an issue with your library. For the life of me, I cannot seem to get the year to be four digits. Here is what I am doing, in a nutshell:

  1. Make HTTP request to server to get current date/time (JSON response)
  2. Parse JSON response and set date/time

Everything works except my year is "32" instead of "2016" as I expect it and I know the year is correct in my JSON object. Here is my current sketch:

#include <ArduinoJson.h>
#include <ArduinoHttpClient.h>
#include <RTCZero.h>
#include <WiFi101.h>
#include <WiFiClient.h>
#include <WiFiUdp.h>
#include <SPI.h>

// Wireless Network SSID
char ssid[] = "";

// Wireless Network Password
char pass[] = "";

// Wireless Network Key Index Number (WEP ONLY)
int keyIndex = 0;

// Wireless Idle Status
int wifiStatus = WL_IDLE_STATUS;

// LED Pin
int ledpin = 6;

// Number of milliseconds to wait without receiving any data before we give up
const int kNetworkTimeout = 30*1000;

// Number of milliseconds to wait if no data is available before trying again
const int kNetworkDelay = 1000;

// Current Real Time Clock
RTCZero rtc;

void setup() {
  // Let's wait and give the console time to catch up
  delay(5000);

  // Initialize serial communication
  Serial.print("Start Serial...");
  Serial.begin(9600);
  Serial.println("OK");

  // Set the LED Pin Mode
  pinMode(ledpin, OUTPUT);

  // Make sure LED is OFF
  digitalWrite(ledpin, LOW);

  // Check for the presence of the WiFi Shield
  Serial.print("Checking for WiFi101 Shield...");

  if (WiFi.status() == WL_NO_SHIELD) {
    Serial.println("NOT PRESENT");

    return;
  }

  Serial.println("Found");

  // Attempt to connect to Wireless Network
  while (wifiStatus != WL_CONNECTED) {
    // Print the Wireless Network Name
    Serial.print("Connecting to Wireless Network...");

    // Connect to WPA/WPA2 network. Change this line if using open or WEP network
    wifiStatus = WiFi.begin(ssid, pass);

    // Wait 10 seconds for connection
    delay(10000);
  }

  if (wifiStatus == WL_CONNECTED) {
    Serial.println("OK");

    printWirelessStatus();
    setCurrentDateTime();
  } else {
    Serial.println("Failed");
  }
}

void loop() {
  // Print date...
  char dateBuffer[12];

  Serial.print("Current Year: ");
  Serial.println(rtc.getYear());

  //sprintf(dateBuffer, "%02u-%02u-%04d %02u:%02u:%02u", rtc.getDay(), rtc.getMonth(), rtc.getYear(), rtc.getHours(), rtc.getMinutes(), rtc.getSeconds());
  //Serial.print(dateBuffer);

  //printf("%03d", rtc.getDay());
  //Serial.print("/");
  //printf("%03d", rtc.getMonth());
  //Serial.print("/");
  //printf("%03d", rtc.getYear());
  //Serial.print(" ");
  // ...and time
  //printf("%03d", rtc.getHours());
  //Serial.print(":");
  //printf("%03d", rtc.getMinutes());
  //Serial.print(":");
  //printf("%03d", rtc.getSeconds());
  Serial.println();

  delay(1000);
}

void printWirelessStatus() {
  // Turn LED ON to show we are connected
  digitalWrite(ledpin, HIGH);

  // Print the SSID of the Wireless Network
  Serial.print("Connected To: ");
  Serial.println(WiFi.SSID());

  // Print your WiFi Shield's IP Address
  IPAddress ip = WiFi.localIP();
  Serial.print("IP Address: ");
  Serial.println(ip);

  // Print the Received Signal Strength
  long rssi = WiFi.RSSI();
  Serial.print("Signal Strength (RSSI):");
  Serial.print(rssi);
  Serial.println(" dBm");
}

void setCurrentDateTime() {
  Serial.print("Attempting to fetch and set current date & time...");

  WiFiClient wifiClient;
  HttpClient http(wifiClient, "");

  int response = 0;

  response = http.get("");

  if (response == 0) {
    response = http.responseStatusCode();

    if (response == 200) {
      int bodyLen = http.contentLength();

      unsigned long timeoutStart = millis();

      String json;

      while ((http.connected() || http.available()) && (!http.endOfBodyReached()) && ((millis() - timeoutStart) < kNetworkTimeout)) {
          if (http.available()) {
              json = http.responseBody();

              // Parse Response
              StaticJsonBuffer<256> jsonBuffer;
              JsonObject& timestamp = jsonBuffer.parseObject(json);

              byte month = timestamp["timestamp"]["month"];
              byte day = timestamp["timestamp"]["day"];
              byte year = timestamp["timestamp"]["year"];
              byte hour = timestamp["timestamp"]["hour"];
              byte minute = timestamp["timestamp"]["minute"];
              byte second = timestamp["timestamp"]["second"];

              rtc.begin();
              rtc.setTime(hour, minute, second);
              rtc.setDate(day, month, year);

              timeoutStart = millis();

              Serial.println("OK");
          } else {
              delay(kNetworkDelay);
          }
      }
    } else {    
      Serial.print("HTTP Response Failed: ");
      Serial.println(response);
    }
  } else {
    Serial.print("HTTP Connect Failed: ");
    Serial.println(response);
  }

  http.stop();
}
sergiuburian commented 8 years ago

Hi,

Please post your JSON response. I am using this RTCZero lib initialized with the NTP Time via FONA/GPRS and it works (2 digit year). The year is kept internal as a 2 digit offset from 2000. So if you set it to 16, it would work. Check also the line byte year = timestamp["timestamp"]["year"];. The year is a byte, so if JSON response contains a 4 digit number for year, it gets corrupted by the conversion.

Also please be aware that getting all time attrs in the loop() is expensive; it will cost you with an M0 board at 48 MHz around 40 - 50 ms. If you need to loop more frequent than 15 times in a sec, you should consider using the Time library here http://www.pjrc.com/teensy/td_libs_Time.html (or download via your Library Manager in IDE). This caches the time and counts it based on millis() and syncs it periodically (as you schedule) with a time provider (can be your JSON service, or the RTC Zero timer) in order to compensate millis() drift. And that library will give you the time in 4 digit. Its only drawback is that is C and not C++ ... :( ... but that is more of a subjective argument :)

Cheers,

Sergiu

recoilnetworks commented 8 years ago

Thank you for your response. The JSON string that you asked for is below. I don't plan on running the timer every second. I just received my sensors today. My plan is to setup the sensor to report their data back to my server via a JSON post every 5 minutes. With this post, I would also like to send the date and time using the format YYYY-MM-DD H:I:S (2016-10-18 - 02:20:00). I figured that my byte year = timestamp["timestamp"]["year"]; was probably wrong so how can I fix it? I really do need the four digit year, so what is your suggestion?

{"error":false,"timestamp":{"month":"10","day":"19","year":"2016","hour":"01","minute":"03","second":"13"}}

sergiuburian commented 8 years ago

Hi,

There are a couple of things you could do:

  1. Add/Substract 2000 to/from the RTCZero year when you send/receive the JSON with time in it. I guess that is the fastest way. Like uint8_t year = uint8_t (int(timestamp["timestamp"]["year"])-2000); when you receive the JSON request and timestamp["timestamp"]["year"] = rtc.getYear()+2000; when you prepare the JSON to be sent.
  2. I am not sure whether you only capture sensor data when you post it, i.e. every 5 mins, or you capture it more often and only post a bunch of values every 5 mins. If the first is the case, you could probably just not send the timestamp and calculate it server-side.
  3. Use the Time lib I have mentioned and sync it let's say hourly with your JSON service time.

I hope it helps,

Sergiu

recoilnetworks commented 8 years ago

Yeah, so the problem with this is that when it hits the year 2100 it will fail. I understand these boards are meant for prototyping and what not, but why would you write something that you know will fail eventually?

sergiuburian commented 8 years ago

You have high hopes wrt life expectancy, man :) :) Where did you find that medicine ;) ???

Joke aside, it will NOT fail if you pass 2100. Actually the uint8_t return type of the getYear() allows you to go up to 255 (8 bit unsigned), which will cover you up to the year 2255. By that time, the mankind will have forgotten the micro-controllers :), so it is rather a theoretical problem. If you are still unhappy, please use the other library that I have mentioned.

And another comment: I did neither write nor contributed to this library (yet). I am a user, trying to give back help to the community, as I got from the community who wrote this library. And as always is the case with open source: if you have improvement ideas, please pull the source, do the extension and give it back to the community :)

Cheers,

Sergiu

GabrielNotman commented 7 years ago

@recoilnetworks @sergiuburian While the function returns an 8bit value, the Year part of the CLOCK register is only 6bit. It will rollover in 2064.

See page 292: www.atmel.com/Images/Atmel-42181-SAM-D21_Datasheet.pdf

sergiuburian commented 7 years ago

Hi, thanks for the update and hint. Looking at the specs, it is important to take a leap year as a base, it does not have to be 2000. So, the code will have to be written in a way that, when it reaches 2061 - which is after a leap year - the base is changed and moved to 2060, year register is updated to 1, any you are OK for the next 64-4=60 years. I guess this (setting the clock at boot time somehow) cannot be avoided anyway, since devices/RTC will be rebooted (at least to change the battery) during 60 years :) (Adafruit Feather will not even give you an RTC battery). Thanks,

Sergiu

agdl commented 7 years ago

I'm closing this issue since we are ok for the next 47 years. please reopen it in 2064 :)