Implemented timezone lookup

Very unstable at the moment, likely due to SSL's memory requirements combined with my lack of optimization
This commit is contained in:
Mark van Renswoude 2018-01-18 23:20:31 +01:00
parent d1126e70d3
commit d9d9b45956
17 changed files with 2499 additions and 2321 deletions

View File

@ -138,18 +138,18 @@ const uint8_t EmbeddedBundleCSS[] PROGMEM = {
0x7c,0xa6,0x30,0x1c,0xa6,0xe2,0xcf,0x16,0x49,0xdb,0xf3,0x71,0x71,0x7e,0xbd,0x3c,0x9e,0x14,0xa0,0x3d,
0x7d,0x80,0x56,0x2e,0x92,0xf8,0xa9,0xab,0xbe,0xf4,0x26,0x09,0x74,0x2e,0x7f,0x02,0x69,0x71,0xc2,0x42,
0x98,0xff,0xe5,0x24,0x17,0x54,0x17,0xd4,0xf5,0x64,0x54,0xdf,0xf1,0x12,0x08,0x4b,0xda,0x2c,0x60,0xbc,
0x20,0xd9,0x6a,0x13,0xe2,0xde,0x86,0xfd,0x05,0x81,0x37,0x62,0xb5,0xde,0x5f,0xae,0x5e,0x31,0xb7,0xfb,
0x35,0x2d,0xf9,0x09,0x7b,0x2b,0x86,0x2a,0x1b,0x4a,0xfd,0xf7,0x9d,0xb6,0xfa,0x19,0x44,0xcf,0xc2,0xf5,
0x20,0xdf,0xb8,0xb6,0xb9,0x54,0x71,0x74,0x27,0x46,0x12,0x2d,0x2c,0x74,0x92,0x20,0xb9,0xce,0x30,0x20,
0x67,0x8b,0xea,0x7b,0x8a,0x37,0x74,0xa3,0xd9,0x43,0x1f,0x5e,0x6f,0xc7,0x0b,0xf6,0xf9,0xda,0x0c,0xfb,
0x8c,0x9b,0x06,0x9d,0x70,0x37,0xbd,0x26,0x54,0xd0,0x6c,0x76,0xe6,0xe9,0x15,0x11,0x1c,0xfd,0x05,0x5d,
0x8c,0x4b,0xa6,0xdc,0xe8,0x11,0x2a,0x9f,0x3f,0x9a,0x70,0xc3,0xdf,0x96,0x18,0x8f,0x98,0xde,0xae,0x50,
0x81,0xf3,0xf9,0xf9,0x3f,0x79,0xbb,0x55,0xd1,0xd6,0xd8,0xdb,0x66,0xc4,0x06,0xff,0xb2,0x44,0x9e,0xa8,
0xc9,0x4c,0xb8,0xdf,0xca,0xe1,0x43,0x43,0x68,0xe8,0xee,0x1e,0x94,0x08,0x28,0x9b,0x8f,0x45,0xa5,0xb9,
0x39,0x29,0x48,0xcf,0x9d,0x9d,0x16,0x72,0xe6,0xf0,0xce,0x0a,0x40,0xcb,0xcc,0x46,0x11,0x8f,0xf5,0x72,
0x51,0xa3,0x9f,0x17,0x99,0x2b,0xe2,0x48,0x85,0x83,0x8a,0xb5,0x50,0x09,0x79,0x1b,0xf8,0x78,0x57,0x0b,
0x78,0xe3,0xf5,0x98,0x12,0x28,0x44,0x9d,0x21,0x71,0x5f,0xbf,0x43,0x63,0xc5,0x97,0xbd,0x76,0x7d,0xc3,
0x55,0xc2,0x0a,0x02,0x7b,0x30,0x4e,0xa7,0x7b,0x87,0xf6,0x82,0xf1,0x73,0x8c,0xde,0xc4,0xfa,0xc4,0xde,
0x07,0x29,0xff,0x03,0xbb,0xba,0x32,0x32,0x37,0x2e,0x00,0x00};
0x20,0xd9,0x6a,0x13,0xe2,0xde,0x86,0xfd,0x05,0x81,0x37,0x62,0xb5,0xde,0x5f,0xae,0x5e,0x71,0xb5,0x3b,
0x64,0x7c,0xb0,0xf1,0xae,0x39,0xc9,0x4f,0xd8,0x58,0x31,0x94,0xd8,0x50,0xe7,0xbf,0xef,0xa8,0xd5,0xb3,
0x17,0x0d,0x0b,0xd7,0x83,0x64,0xe3,0xda,0xe3,0x52,0x65,0xd1,0x1d,0x17,0x49,0xb4,0xb0,0xca,0x49,0x76,
0xe4,0x3a,0xc3,0x80,0x9c,0x2a,0xaa,0x2f,0x29,0xde,0x50,0x8c,0x66,0x03,0x7d,0x78,0xbd,0x1d,0xaf,0xd6,
0xe7,0x6b,0x33,0xec,0x33,0x6e,0x1a,0x74,0xc2,0xdd,0xf4,0x8e,0x50,0x41,0xb3,0xd9,0x81,0xa7,0x57,0x44,
0x70,0xf4,0x17,0x74,0x31,0x2e,0x99,0x72,0xa3,0xe7,0xa7,0x7c,0xf8,0x68,0x62,0x0d,0x7f,0x55,0x62,0x3c,
0x5f,0x7a,0xbb,0x42,0x05,0xce,0xe7,0x87,0xff,0xe4,0xd5,0x56,0x45,0x5b,0x63,0x63,0x9b,0x11,0x1b,0xfc,
0xcb,0x12,0x49,0xa2,0x26,0x2d,0xe1,0x4e,0x2b,0xc7,0x0e,0x0d,0xa1,0xa1,0xbb,0x78,0x50,0xc2,0x9f,0x6c,
0x3e,0x16,0x92,0xe6,0xe6,0xa4,0x20,0x3d,0x77,0x76,0x54,0xc8,0x69,0xc3,0x3b,0xd3,0x7f,0x2d,0x33,0x1b,
0x45,0x3c,0xd0,0xcb,0x15,0x8d,0x7e,0x5e,0x64,0xae,0x88,0x23,0x55,0x0d,0x2a,0xd6,0x42,0x19,0xe4,0x6d,
0xe0,0xe3,0x5d,0x2d,0xe0,0x8d,0x77,0x63,0x4a,0x94,0x10,0x45,0x86,0xc4,0x7d,0xfd,0x02,0x8d,0x55,0x5e,
0xf6,0xda,0xdd,0x0d,0x57,0x09,0xab,0x06,0xec,0xc1,0x38,0x9d,0xee,0x05,0xda,0x0b,0xc6,0xcf,0x31,0x7a,
0x13,0xeb,0x13,0x7b,0x1f,0xa4,0xfc,0x0f,0x98,0xb1,0x01,0x7f,0x34,0x2e,0x00,0x00};
#endif

File diff suppressed because it is too large Load Diff

View File

@ -29,4 +29,16 @@ void assignChar(char** field, const char* newValue)
}
else
*field = nullptr;
}
bool sameStr(const char* value1, const char* value2)
{
if ((value1 == nullptr) != (value2 == nullptr))
return true;
if (value1 == nullptr)
return false;
return strcmp(value1, value2) == 0;
}

View File

@ -10,5 +10,6 @@
#include <stdint.h>
void assignChar(char** field, const char* newValue);
bool sameStr(const char* value1, const char* value2);
#endif

View File

@ -21,6 +21,8 @@ static const char* MotionTriggerSettingsFile = "/motiontriggers.json";
static const char* DefaultAPSSIDPrefix = "Stairs-";
static const char* DefaultNTPServer = "pool.ntp.org";
// Timeout when in AP + station mode (otherwise trying to connect
// to the STA will block the AP)
static const uint32_t StationModeTimeout = 30000;
@ -28,4 +30,8 @@ static const uint32_t StationModeTimeout = 30000;
static const uint16_t APButtonHoldTime = 5000;
// Only used if the timezone has not been succesfully retrieved yet, otherwise
// the configurable NTP interval is used
static const uint32_t TimezoneRetryInterval = 60000;
#endif

View File

@ -29,5 +29,7 @@ bool shouldReboot = false;
uint32_t currentTime;
NTPClient* ntpClient = nullptr;
bool hasTimezone = false;
uint32_t timezoneOffset = 0;
IPAddress emptyIP(0, 0, 0, 0);

View File

@ -41,6 +41,8 @@ extern bool shouldReboot;
extern uint32_t currentTime;
extern NTPClient* ntpClient;
extern bool hasTimezone;
extern uint32_t timezoneOffset;
extern IPAddress emptyIP;

View File

@ -9,7 +9,9 @@
//#include <ESPAsyncTCP.h>
#include <WiFiUDP.h>
#include <ESPAsyncWebServer.h>
#include <ESPAsyncTCP.h>
#include <TimeLib.h>
#include <ArduinoJson.h>
extern "C" {
#include <user_interface.h>

View File

@ -63,6 +63,9 @@ void updateDebugStatus()
_d(day(time)); _d("-"); _d(month(time)); _d("-"); _d(year(time)); _d(" ");
_d(hour(time)); _d(":"); _d(minute(time)); _d(":"); _dln(second(time));
_d("Status :: offset: ");
_dln(timezoneOffset);
}
}

View File

@ -4,6 +4,13 @@ uint32_t lastTimeTriggerChecked = 0;
TimeTrigger* lastTimeTrigger = nullptr;
TimeTrigger* activeTimeTrigger = nullptr;
uint32_t lastTimezoneUpdate = 0;
AsyncClient* timezoneClient = nullptr;
char* response = nullptr;
uint16_t responseSize = 0;
static const uint16_t ResponseMaxSize = 1024;
void initMotionPins()
{
@ -19,6 +26,136 @@ void initMotionPins()
}
void parseResponse()
{
if (response == nullptr || responseSize == 0)
return;
_dln("Timezone :: response:");
_dln(response);
char* data = response;
if (strncmp(data, "HTTP/1.0 ", 9) != 0)
{
_dln("Timezone :: not an HTTP response");
return;
}
data += 9;
if (strncmp(data, "200", 3) != 0)
{
_dln("Timezone :: invalid HTTP status code");
return;
}
data = strstr(data, "\r\n\r\n");
if (data == nullptr)
{
_dln("Timezone :: end of HTTP headers not found");
return;
}
data += 4;
DynamicJsonBuffer jsonBuffer(JSON_OBJECT_SIZE(5) + 200);
JsonObject& root = jsonBuffer.parseObject(data);
if (!sameStr(root["status"], "OK"))
{
_dln("Timezone :: invalid status in response");
return;
}
timezoneOffset = root["rawOffset"];
hasTimezone = true;
}
void updateTimezone()
{
if (timezoneClient != nullptr)
return;
timezoneClient = new AsyncClient();
if (!timezoneClient)
return;
timezoneClient->onError([](void* arg, AsyncClient* client, int error)
{
_d("Timezone :: error ");
_dln(error);
timezoneClient = nullptr;
delete client;
lastTimezoneUpdate = currentTime;
}, nullptr);
timezoneClient->onConnect([](void* arg, AsyncClient* client)
{
response = (char*)malloc(ResponseMaxSize + 1);
responseSize = 0;
timezoneClient->onError(nullptr, nullptr);
client->onDisconnect([](void * arg, AsyncClient * c)
{
timezoneClient = nullptr;
delete c;
lastTimezoneUpdate = currentTime;
parseResponse();
free(response);
response = nullptr;
}, nullptr);
client->onData([](void* arg, AsyncClient* c, void* data, size_t len)
{
uint16_t copyLen = responseSize == ResponseMaxSize ? 0 :
responseSize + len > ResponseMaxSize ? ResponseMaxSize - responseSize :
len;
if (copyLen > 0)
{
memcpy(response + responseSize, data, copyLen);
responseSize += copyLen;
response[responseSize] = 0;
}
}, nullptr);
uint32_t timestamp = ntpClient->getEpochTime();
String request = "GET /maps/api/timezone/json?location=" +
String(systemSettings->latitude(), 7) + "," + String(systemSettings->longitude(), 7) +
"8&timestamp=" +
String(timestamp);
_d("Timezone :: request: ");
_dln(request);
if (systemSettings->mapsAPIKey() != nullptr)
request = request + "&key=" + systemSettings->mapsAPIKey();
request = request + " HTTP/1.0\r\nHost: maps.googleapis.com\r\n\r\n";
client->write(request.c_str());
}, nullptr);
_d("Timezone :: available heap: ");
_dln(ESP.getFreeHeap());
if(!timezoneClient->connect("maps.googleapis.com", 443, true))
{
_dln("Timezone :: failed to connect to host");
AsyncClient * client = timezoneClient;
timezoneClient = nullptr;
delete client;
lastTimezoneUpdate = currentTime;
}
}
void updateNTPClient()
{
if (ntpClient == nullptr && WiFi.status() == WL_CONNECTED &&
@ -31,14 +168,30 @@ void updateNTPClient()
}
if (ntpClient != nullptr)
// Only update if we're not in the middle of a transition, as it will block
// the loop until the NTP server responds or times out (up to a second)
if (ntpClient != nullptr && !stairs->inTransition())
{
ntpClient->update();
// Lat/lng 0,0 is off the African coast, I think we can safely assume nobody
// will have WiFi enabled stair lighting at that location.
if (timezoneClient == nullptr && systemSettings->latitude() && systemSettings->longitude())
{
uint32_t interval = hasTimezone ? systemSettings->ntpInterval() * 60 * 1000 : TimezoneRetryInterval;
if (lastTimezoneUpdate == 0 || currentTime - lastTimezoneUpdate > interval)
{
updateTimezone();
lastTimezoneUpdate = currentTime;
}
}
}
}
void updateTimeTrigger()
{
if (ntpClient == nullptr || !timeTriggerSettings->enabled())
if (ntpClient == nullptr || !hasTimezone || !timeTriggerSettings->enabled())
{
activeTimeTrigger = nullptr;
return;
@ -65,10 +218,8 @@ void updateTimeTrigger()
return;
}
// TODO apply timezone offset
tmElements_t time;
breakTime(epochTime, time);
breakTime(epochTime + timezoneOffset, time);
activeTimeTrigger = timeTriggerSettings->getActiveTrigger(time);

View File

@ -65,11 +65,11 @@ bool ConnectionSettings::fromJson(char* data, bool* changed)
if (!(jsonAccessPoint || jsonStation))
jsonAccessPoint = true;
if ((jsonHostname != hostname()) ||
if ((!sameStr(jsonHostname, hostname())) ||
(jsonAccessPoint != flag(AccessPoint)) ||
(jsonStation != flag(StationMode)) ||
(jsonSSID != ssid()) ||
(jsonPassword != password()) ||
(!sameStr(jsonSSID, ssid())) ||
(!sameStr(jsonPassword, password())) ||
(jsonDHCP != flag(DHCP)) ||
(jsonIP != ip()) ||
(jsonSubnetMask != subnetMask()) ||

View File

@ -68,6 +68,9 @@ bool SystemSettings::fromJson(char* data, bool* changed)
uint16_t jsonPWMDriverFrequency = root["pwmFrequency"];
const char* jsonMapAPIKey = root["mapsAPIKey"];
if (jsonNTPServer == nullptr) jsonNTPServer = DefaultNTPServer;
if (jsonNTPInterval == 0) jsonNTPInterval = 5;
if (jsonPinLEDAP == 0) jsonPinLEDAP = pinLEDAP();
if (jsonPinLEDSTA == 0) jsonPinLEDSTA = pinLEDSTA();
if (jsonPinAPButton == 0) jsonPinAPButton = pinAPButton();
@ -85,10 +88,10 @@ bool SystemSettings::fromJson(char* data, bool* changed)
(jsonPinPWMDriverSCL != pinPWMDriverSCL()) ||
(jsonPWMDriverAddress != pwmDriverAddress()) ||
(jsonPWMDriverFrequency != pwmDriverFrequency()) ||
(jsonMapAPIKey != mapsAPIKey()) ||
(!sameStr(jsonMapAPIKey, mapsAPIKey())) ||
(jsonLat != latitude()) ||
(jsonLng != longitude()) ||
(jsonNTPServer != ntpServer()) ||
(!sameStr(jsonNTPServer, ntpServer())) ||
(jsonNTPInterval != ntpInterval()))
{
latitude(jsonLat);

View File

@ -17,7 +17,7 @@
class SystemSettings : public AbstractJsonSettings
{
private:
char* mNTPServer = nullptr;
char* mNTPServer;
uint32_t mNTPInterval = 5;
double mLatitude = 0;
@ -39,6 +39,12 @@ class SystemSettings : public AbstractJsonSettings
virtual const char* getDebugPrefix() { return "SystemSettings"; };
public:
SystemSettings()
{
assignChar(&mNTPServer, DefaultNTPServer);
}
void toJson(Print &print);
bool fromJson(char* data, bool* changed);

View File

@ -34,6 +34,8 @@ class Stairs
void init(PCA9685* pwmDriver);
void tick();
bool inTransition() { return mTick; }
uint8_t get(uint8_t step, bool target = true);
void set(uint8_t step, uint8_t brightness, uint16_t transitionTime = 0, uint16_t startTime = 0);
void setAll(uint8_t brightness, uint16_t transitionTime = 0, uint16_t startTime = 0);

2
web/dist/bundle.css vendored

File diff suppressed because one or more lines are too long

2
web/dist/bundle.js vendored

File diff suppressed because one or more lines are too long

View File

@ -293,7 +293,7 @@ input[disabled]
.notificationContainer
{
position: absolute;
position: fixed;
top: 2rem;
z-index: 666;