Stairs/src/main.triggers.h

344 lines
8.7 KiB
C
Raw Normal View History

WiFiUDP ntpUDP;
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()
{
if (!motionTriggerSettings->enabled())
return;
for (uint8_t i = 0; i < motionTriggerSettings->triggerCount(); i++)
{
MotionTrigger* trigger = motionTriggerSettings->trigger(i);
if (trigger->enabled)
pinMode(trigger->pin, INPUT);
}
}
void parseResponse()
{
if (response == nullptr || responseSize == 0)
return;
_dln("Timezone :: response:");
_dln(response);
char* data = response;
if (strncmp(data, "HTTP/1.", 7) != 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();
2018-01-19 15:12:23 +00:00
#ifdef MapsAPIViaProxyScript
String request = String("GET ") + TimezoneProxyScriptPath + "?location=" +
#else
String request = "GET /maps/api/timezone/json?location=" +
2018-01-19 15:12:23 +00:00
#endif
String(systemSettings->latitude(), 7) + "," + String(systemSettings->longitude(), 7) +
"8&timestamp=" +
String(timestamp);
if (systemSettings->mapsAPIKey() != nullptr)
request = request + "&key=" + systemSettings->mapsAPIKey();
2018-01-19 15:12:23 +00:00
_d("Timezone :: request: ");
_dln(request);
#ifdef MapsAPIViaProxyScript
request = request + " HTTP/1.0\r\nHost: " + TimezoneProxyScriptHost + "\r\n\r\n";
#else
request = request + " HTTP/1.0\r\nHost: maps.googleapis.com\r\n\r\n";
#endif
client->write(request.c_str());
}, nullptr);
_d("Timezone :: available heap: ");
_dln(ESP.getFreeHeap());
2018-01-19 15:12:23 +00:00
#ifdef MapsAPIViaProxyScript
if(!timezoneClient->connect(TimezoneProxyScriptHost, 80))
#else
if(!timezoneClient->connect("maps.googleapis.com", 443, true))
2018-01-19 15:12:23 +00:00
#endif
{
_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 &&
systemSettings->ntpServer() != nullptr && systemSettings->ntpInterval() > 0)
{
_dln("NTP :: initializing NTP client");
ntpClient = new NTPClient(ntpUDP, systemSettings->ntpServer(), 0, systemSettings->ntpInterval() * 60 * 1000);
ntpClient->begin();
}
// 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 || !hasTimezone || !timeTriggerSettings->enabled())
{
activeTimeTrigger = nullptr;
return;
}
if (timeTriggerSettingsChanged)
{
// Time trigger settings changed, activeTimeTrigger pointer is considered
// invalid, force recheck
timeTriggerSettingsChanged = false;
}
else if (currentTime - lastTimeTriggerChecked < 10000)
return;
lastTimeTriggerChecked = currentTime;
_dln("Triggers:: updating time trigger");
uint32_t epochTime = ntpClient->getEpochTime();
if (epochTime == 0)
{
activeTimeTrigger = nullptr;
_dln("Triggers:: time not synchronised yet");
return;
}
tmElements_t time;
breakTime(epochTime + timezoneOffset, time);
activeTimeTrigger = timeTriggerSettings->getActiveTrigger(time);
#ifdef SerialDebug
_d("Triggers:: active time trigger: ");
if (activeTimeTrigger != nullptr)
_dln(activeTimeTrigger->time);
else
_dln("null");
#endif
}
uint32_t activeMotionStart = 0;
uint16_t activeMotionBrightness = 0;
MotionDirection activeMotionDirection = Nondirectional;
bool lastMotion = false;
void updateMotionTrigger()
{
if (!motionTriggerSettings->enabled() || !motionTriggerSettings->triggerCount())
{
activeMotionStart = 0;
return;
}
for (uint8_t i = 0; i < motionTriggerSettings->triggerCount(); i++)
{
MotionTrigger* trigger = motionTriggerSettings->trigger(i);
if (trigger->enabled && digitalRead(trigger->pin) == HIGH)
{
if (activeMotionStart == 0)
{
activeMotionDirection = trigger->direction;
activeMotionBrightness = trigger->brightness;
}
activeMotionStart = currentTime;
}
}
if (currentTime - activeMotionStart >= motionTriggerSettings->delay())
activeMotionStart = 0;
}
void checkTriggers()
{
if (!timeTriggerSettings->enabled() && activeTimeTrigger == nullptr &&
!motionTriggerSettings->enabled() && activeMotionStart == 0)
return;
updateTimeTrigger();
updateMotionTrigger();
bool inTimeTrigger = timeTriggerSettings->enabled() &&
activeTimeTrigger != nullptr &&
activeTimeTrigger->brightness;
bool timeTriggerChanged = activeTimeTrigger != lastTimeTrigger;
lastTimeTrigger = activeTimeTrigger;
bool inMotionTrigger = (activeMotionStart > 0) && (!inTimeTrigger || motionTriggerSettings->enabledDuringTimeTrigger());
bool motionChanged = (activeMotionStart > 0) != lastMotion;
lastMotion = (activeMotionStart > 0);
if (!motionChanged && !timeTriggerChanged)
return;
if (motionChanged)
{
if (inMotionTrigger)
{
_dln("Triggers :: start motion trigger");
if (activeMotionDirection == Nondirectional || motionTriggerSettings->transitionTime() == 0)
stairs->setAll(activeMotionBrightness, motionTriggerSettings->transitionTime(), 0);
else
2018-01-31 20:03:08 +00:00
stairs->sweep(activeMotionBrightness, motionTriggerSettings->transitionTime(), activeMotionDirection == TopDown);
}
else
{
if (inTimeTrigger)
{
_dln("Triggers :: motion stopped, falling back to time trigger");
// Fall back to time trigger value
stairs->setAll(activeTimeTrigger->brightness, motionTriggerSettings->transitionTime(), 0);
}
else
{
_dln("Triggers :: motion stopped, turning off");
// No more motion, no active time trigger, turn off
stairs->setAll(0, motionTriggerSettings->transitionTime(), 0);
}
}
}
else if (timeTriggerChanged && !inMotionTrigger)
{
_dln("Triggers :: time trigger changed");
// Set to time trigger value
stairs->setAll(activeTimeTrigger->brightness, timeTriggerSettings->transitionTime(), 0);
}
}