Skip to content

Commit

Permalink
0.7.4: timezoneapi.io -> timezoned.rop.nl
Browse files Browse the repository at this point in the history
also added EthernetShield example
  • Loading branch information
ropg committed Sep 25, 2018
1 parent a2187f2 commit 4b1ffe5
Show file tree
Hide file tree
Showing 7 changed files with 194 additions and 105 deletions.
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

 

> **Newsflash**: *To find the timezone information, ezTime originally used timezoneapi.io, which could be used for free for up to 50 queries a day. They have changed this policy and forced the use of https, both breaking ezTime. As of version 0.7.4, ezTime makes use of its very own online timezone lookup daemon, removing a dependency on some third party that might change their policy just like timezoneapi did. Please see details for [*`setLocation`*](#setlocation) because the interface changed a little. You can now also do GeoIP lookups for automatic local time setting (only in countries which do not span multiple timezones).*
## A brief history of ezTime

I was working on [M5ez](https://github.com/ropg/M5ez), an interface library to easily make cool-looking programs for the "[M5Stack](http://m5stack.com/)" ESP32 hardware. The status bar of M5ez needed to display the time. That was all, I swear. I figured I would use [Time](https://github.com/PaulStoffregen/Time), Michael Margolis' and Paul Stoffregen's library to do time things on Arduino. Then I needed to sync that to an NTP server, so I figured I would use [NTPclient](https://github.com/arduino-libraries/NTPClient), one of the existing NTP client libraries. And then I wanted it to show the local time, so I would need some way for the user to set an offset between UTC and local time.
Expand Down Expand Up @@ -368,7 +370,11 @@ Provide the offset from UTC in minutes at the indicated time (or now if you do n

`boolsetLocation(String location = "")`    — **MUST** be prefixed with name of a timezone

With `setLocation` you can provide a string to do an internet lookup for a timezone. If the string contains a forward slash, the string is taken to be on Olsen timezone name, like `Europe/Berlin`. If it does not, it is parsed as a free form address, for which the system will try to find a timezone. You can enter "Paris" and get the info for "Europe/Paris", or enter "Paris, Texas" and get the timezone info for "America/Chicago", which is the Central Time timezone that Texas is in. After the information is retrieved, it is loaded in the current timezone, and cached if a cache is set (see below). `setLocation` will return `false` (Setting either `NO_NETWORK`, `CONNECT_FAILED` or `DATA_NOT_FOUND`) if it cannot find the information online.
With `setLocation` you can provide a string to do an internet lookup for a timezone. The string can either be an Olsen timezone name, like `Europe/Berlin` (case-sensitive). ([Here](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) is a complete list of these names.) Or it can be a two-letter country code for any country that does not span multiple timezones, like `NL` or `DE` (but not `US`). After the information is retrieved, it is loaded in the current timezone, and cached if a cache is set (see below). `setLocation` will return `false` (Setting either `NO_NETWORK`, `DATA_NOT_FOUND` or `SERVER_ERROR`) if it cannot get timezone information.

If you provide no location ( `YourTZ.setLocation()` ), ezTime will attempt to do a GeoIP lookup fo find the country associated with your IP-address. If that is a country that has a single timezone, that timezone will be loaded, otherwise a `SERVER_ERROR` ("Country Spans Multiple Timezones") will result.

In the case of `SERVER_ERROR`, `errorString()` returns the error from the server, which might be "Country Spans Multiple Timezones", "Country Not Found", "GeoIP Lookup Failed" or "Timezone Not Found".

 

Expand Down
88 changes: 88 additions & 0 deletions examples/EthernetShield/EthernetShield.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* Note: to use an ethernet shield, You must also set #define EZTIME_ETHERNET in $sketch_dir/libraries/ezTime/src/ezTime.h
*
* Also note that all ezTime examples can be used with an Ethernet shield if you just replace the beginning of the sketch
* with the beginning of this one.
*/

#include <ezTime.h>

#include <Ethernet.h>

// Enter a MAC address for your controller below. (Or use address below, just make sure it's unique on your network)
// Newer Ethernet shields have a MAC address printed on a sticker on the shield
#define MAC_ADDRESS { 0xBA, 0xDB, 0xAD, 0xC0, 0xFF, 0xEE }

void setup() {

// Open serial communications and wait for port to open:
Serial.begin(115200);
while (!Serial) { ; } // wait for serial port to connect. Needed for native USB port only
Serial.println();

// You can use Ethernet.init(pin) to configure the CS pin
//Ethernet.init(10); // Most Arduino shields (default if unspecified)
//Ethernet.init(5); // MKR ETH shield
//Ethernet.init(0); // Teensy 2.0
//Ethernet.init(20); // Teensy++ 2.0
//Ethernet.init(15); // ESP8266 with Adafruit Featherwing Ethernet
//Ethernet.init(33); // ESP32 with Adafruit Featherwing Ethernet

Serial.print(F("Ethernet connection ... "));
byte mac [] = MAC_ADDRESS;
if (Ethernet.begin(mac) == 0) {
Serial.println(F("failed. (Reset to retry.)"));
while (true) { ; }; // Hang
} else {
Serial.print(F("got DHCP IP: "));
Serial.println(Ethernet.localIP());
}
// give the Ethernet shield a second to initialize:
delay(1000);

// OK, we're online... So the part above here is what you swap in before the waitForSync() in the other examples...


// Wait for ezTime to get its time synchronized
waitForSync();

Serial.println();
Serial.println("UTC: " + UTC.dateTime());

Timezone myTZ;

// Provide official timezone names
// https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
myTZ.setLocation(F("Pacific/Auckland"));
Serial.print(F("New Zealand: "));
Serial.println(myTZ.dateTime());

// Or country codes for countries that do not span multiple timezones
myTZ.setLocation(F("de"));
Serial.print(F("Germany: "));
Serial.println(myTZ.dateTime());

// See if local time can be obtained (does not work in countries that span multiple timezones)
Serial.print(F("Local (GeoIP): "));
if (myTZ.setLocation()) {
Serial.println(myTZ.dateTime());
} else {
Serial.println(errorString());
}

Serial.println();
Serial.println(F("Now ezTime will show an NTP sync every 60 seconds"));

// Set NTP polling interval to 60 seconds. Way too often, but good for demonstration purposes.
setInterval(60);

// Make ezTime show us what it is doing
setDebug(INFO);

}

void loop() {

events();

}
20 changes: 17 additions & 3 deletions examples/Timezones/Timezones.ino
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,24 @@ void setup() {

Timezone myTZ;

// Anything with a slash in it is interpreted as an official timezone name
// Provide official timezone names
// https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
myTZ.setLocation("Pacific/Auckland");
Serial.println("Auckland: " + myTZ.dateTime());
myTZ.setLocation(F("Pacific/Auckland"));
Serial.print(F("New Zealand: "));
Serial.println(myTZ.dateTime());

// Or country codes for countries that do not span multiple timezones
mtTZ.setLocation(F("de"));
Serial.print(F("Germany: "));
Serial.println(myTZ.dateTime());

// See if local time can be obtained (does not work in countries that span multiple timezones)
Serial.print(F("Local (GeoIP): "));
if (myTZ.setLocation()) {
Serial.println(myTZ.dateTime());
} else {
Serial.println(errorString());
}

}

Expand Down
6 changes: 3 additions & 3 deletions library.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@
"keywords": "time date ntp timezone events milliseconds",
"authors": {
"name": "Rop Gonggrijp",
"url": "https://github.com/ropg"
"url": "https://github.com/ropg",
"maintainer": true
},
"repository": {
"type": "git",
"url": "https://github.com/ropg/ezTime"
},
"version": "0.7.3",
"version": "0.7.4",
"framework": "arduino",
"platforms": "*"
"platforms": "*",
"build": {
"libArchive": false
}
Expand Down
2 changes: 1 addition & 1 deletion library.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name=ezTime
version=0.7.3
version=0.7.4
author=Rop Gonggrijp
maintainer=Rop Gonggrijp
sentence=ezTime - pronounced "Easy Time" - is a very easy to use Arduino time and date library that provides NTP network time lookups, extensive timezone support, formatted time and date strings, user events, millisecond precision and more.
Expand Down
135 changes: 55 additions & 80 deletions src/ezTime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <EthernetUdp.h>
#else
#include <WiFi.h>
#include <WiFiUdp.h>
#endif
#endif

Expand Down Expand Up @@ -62,6 +63,7 @@ const uint8_t monthDays[]={31,28,31,30,31,30,31,31,30,31,30,31}; // API starts m
namespace {

ezError_t _last_error = NO_ERROR;
String _server_error = "";
ezDebugLevel_t _debug_level = NONE;
Print *_debug_device = (Print *)&Serial;
ezEvent_t _events[MAX_EVENTS];
Expand Down Expand Up @@ -123,6 +125,7 @@ String errorString(const ezError_t err /* = LAST_ERROR */) {
case NO_CACHE_SET: return F("No cache set");
case CACHE_TOO_SMALL: return F("Cache too small");
case TOO_MANY_EVENTS: return F("Too many events");
case SERVER_ERROR: return _server_error;
default: return F("Unkown error");
}
}
Expand Down Expand Up @@ -458,16 +461,14 @@ bool minuteChanged() {
info(F(" ... "));

#ifndef EZTIME_ETHERNET
#ifndef ARDUINO_SAMD_MKR1000
if (!WiFi.isConnected()) { error(NO_NETWORK); return false; }
#endif
if (WiFi.status() != WL_CONNECTED) { error(NO_NETWORK); return false; }
WiFiUDP udp;
#else
EthernetUDP udp;
#endif

udp.flush();
udp.begin(NTP_LOCAL_TIME_PORT);
udp.begin(NTP_LOCAL_PORT);

// Send NTP packet
byte buffer[NTP_PACKET_SIZE];
Expand Down Expand Up @@ -526,11 +527,12 @@ bool minuteChanged() {

unsigned long start = millis();

#if !defined(EZTIME_ETHERNET) && !defined(ARDUINO_SAMD_MKR1000)
if (!WiFi.isConnected()) {
#if !defined(EZTIME_ETHERNET)
if (WiFi.status() != WL_CONNECTED) {
info(F("Waiting for WiFi ... "));
while (!WiFi.isConnected()) {
while (WiFi.status() != WL_CONNECTED) {
if ( timeout && (millis() - start) / 1000 > timeout ) { error(TIMEOUT); return false;};
events();
delay(25);
}
infoln(F("connected"));
Expand Down Expand Up @@ -797,92 +799,65 @@ String Timezone::getPosix() { return _posix; }

#ifdef EZTIME_NETWORK_ENABLE

bool Timezone::setLocation(const String location /* = "" */) {
bool Timezone::setLocation(const String location /* = "GeoIP" */) {

info(F("Timezone lookup for: "));
infoln(location);
if (_locked_to_UTC) { error(LOCKED_TO_UTC); return false; }

#if !defined(EZTIME_ETHERNET) && !defined(ARDUINO_SAMD_MKR1000)
if (!WiFi.isConnected()) { error(NO_NETWORK); return false; }
#endif

String path;
if (location.indexOf("/") != -1) {
path = F("/api/timezone/?"); path += urlEncode(location);
} else if (location != "") {
path = F("/api/address/?"); path += urlEncode(location);
} else {
path = F("/api/ip");
}

#ifndef EZTIME_ETHERNET
WiFiClient client;
if (WiFi.status() != WL_CONNECTED) { error(NO_NETWORK); return false; }
WiFiUDP udp;
#else
EthernetClient client;
#endif

if (!client.connect("timezoneapi.io", 80)) { error(CONNECT_FAILED); return false; }

client.print(F("GET "));
client.print(path);
client.println(F(" HTTP/1.1"));
client.println(F("Host: timezoneapi.io"));
client.println(F("Connection: close"));
client.println();
client.setTimeout(3000);
EthernetUDP udp;
#endif

udp.flush();
udp.begin(TIMEZONED_LOCAL_PORT);

debug(F("Sent request for http://timezoneapi.io")); debugln(path);
debugln(F("Reply from server:\r\n"));
udp.beginPacket(TIMEZONED_REMOTE_HOST, TIMEZONED_REMOTE_PORT);
udp.write((const uint8_t*)location.c_str(), location.length());
udp.endPacket();

// This "JSON parser" (bwahaha!) fits in the small memory of the AVRs
String tzinfo = "";
String needle = "\"id\":\"";
uint8_t search_state = 0;
uint8_t char_found = 0;
uint32_t start = millis();
while ( search_state < 4 && millis() - start < TIMEZONEAPI_TIMEOUT) {
if (client.available()) {
char c = client.read();
debug(c);
if (c == needle.charAt(char_found)) {
char_found++;
if (char_found == needle.length()) {
search_state++;
c = 0;
}
} else {
char_found = 0;
}
if (search_state == 1 || search_state == 3) {
if (c == '"') {
search_state++;
if (search_state == 2) {
needle = "\"tz_string\":\"";
tzinfo += ' ';
}
} else if (c && c != '\\') {
tzinfo += c;
}
}
// Wait for packet or return false with timed out
unsigned long started = millis();
uint16_t packetsize = 0;
while (!udp.parsePacket()) {
delay (1);
if (millis() - started > TIMEZONED_TIMEOUT) {
udp.stop();
error(TIMEOUT);
udp.stop();
return false;
}
}
debugln(F("\r\n\r\n"));
if (search_state != 4 || tzinfo == "") { error(DATA_NOT_FOUND); return false; }

infoln(F("success."));
info(F("Found: ")); infoln(tzinfo);

_olsen = tzinfo.substring(0, tzinfo.indexOf(' '));
_posix = tzinfo.substring(tzinfo.indexOf(' ') + 1);

#if defined(EZTIME_CACHE_EEPROM) || defined(EZTIME_CACHE_NVS)
writeCache(tzinfo); // caution, byref to save memory, tzinfo mangled afterwards
#endif

return true;
// Stick result in String recv
String recv;
recv.reserve(60);
while (udp.available()) recv += (char)udp.read();
udp.stop();
if (recv.substring(0,6) == "ERROR ") {
_server_error = recv.substring(6);
error (SERVER_ERROR);
return false;
}
if (recv.substring(0,3) == "OK ") {
_olsen = recv.substring(3, recv.indexOf(" ", 4));
_posix = recv.substring(recv.indexOf(" ", 4) + 1);
infoln(F("success."));
info(F(" Olsen: ")); infoln(_olsen);
info(F(" Posix: ")); infoln(_posix);
#if defined(EZTIME_CACHE_EEPROM) || defined(EZTIME_CACHE_NVS)
String tzinfo = _olsen + " " + _posix;
writeCache(tzinfo); // caution, byref to save memory, tzinfo mangled afterwards
#endif
return true;
}
error (DATA_NOT_FOUND);
return false;
}


String Timezone::getOlsen() {
return _olsen;
}
Expand Down
Loading

0 comments on commit 4b1ffe5

Please sign in to comment.