Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
/*
* SynchroClock.cpp
*
* Copyright 2017 Christopher B. Liebman
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*
* Created on: May 26, 2017
* Author: liebman
*/
#include "SynchroClock.h"
#include "SynchroClockVersion.h"
#include <FS.h>
#include <time.h>
#include <sys/time.h>
#include <lwip/apps/sntp.h>
#include <umm_malloc/umm_malloc.h>
//
// These are only used when debugging
//
//#define DISABLE_DEEP_SLEEP
//#define DISABLE_INITIAL_NTP
//#define DISABLE_INITIAL_SYNC
DLog& dlog = DLog::getLog();
Config config; // configuration persisted in the EEPROM
DeepSleepData dsd; // data persisted in the RTC memory
#if defined(LED_PIN)
FeedbackLED feedback(LED_PIN); // used to blink LED to indicate status
#endif
ESP8266WebServer HTTP(80); // used when debugging/stay awake mode
NTP ntp(&(dsd.ntp_runtime), &(config.ntp_persist), &saveConfig); // handles NTP communication & filtering
Clock clk(SYNC_PIN); // clock ticker, manages position of clock
DS3231 rtc; // real time clock on i2c interface
boolean save_config = false; // used by wifi manager when settings were updated.
boolean force_config = false; // reset handler sets this to force into config mode if button held
boolean stay_awake = false; // don't use deep sleep (from config mode option)
boolean url_update = false; // set true of we got an update url
char devicename[32];
char message[128]; // buffer for http return values
#ifdef USE_CERT_STORE
BearSSL::CertStore certStore;
#endif
boolean parseBoolean(const char* value)
{
if (!strcmp(value, "true") || !strcmp(value, "True") || !strcmp(value, "TRUE") || atoi(value))
{
return true;
}
return false;
}
uint8_t parseDuty(const char* value)
{
int i = atoi(value);
if (i < 1 || i > 100)
{
dlog.error(F("parseDuty"), F("invalid value %s: using 50 instead!"), value);
i = 50;
}
return (uint8_t) i;
}
int getValidOffset(String name)
{
int result = TimeUtils::parseOffset(HTTP.arg(name).c_str());
return result;
}
uint16_t getValidPosition(String name)
{
int result = TimeUtils::parsePosition(HTTP.arg(name).c_str());
return result;
}
uint8_t getValidDuration(String name)
{
uint8_t result = TimeUtils::parseSmallDuration(HTTP.arg(name).c_str());
return result;
}
uint8_t getValidDuty(String name)
{
uint8_t result = parseDuty(HTTP.arg(name).c_str());
return result;
}
boolean getValidBoolean(String name)
{
boolean result = parseBoolean(HTTP.arg(name).c_str());
return result;
}
uint8_t getValidByte(String name)
{
int i = atoi(HTTP.arg(name).c_str());
if (i < 0 || i > 255)
{
dlog.error(F("getValidByte"), F("invalid value %d: using 255 instead!"), i);
i = 255;
}
return (uint8_t) i;
}
void handleOffset()
{
if (HTTP.hasArg("set"))
{
config.tz_offset = getValidOffset("set");
dlog.info(F("handleOffset"), F("seconds offset:%d"), config.tz_offset);
}
HTTP.send(200, "text/plain", String(config.tz_offset) + "\n");
}
void handleAdjustment()
{
static PROGMEM const char TAG[] = "handleAdjustment";
uint16_t adj;
if (HTTP.hasArg("set"))
{
if (HTTP.arg("set").equalsIgnoreCase("auto"))
{
dlog.info(FPSTR(TAG), F("Auto Adjust!"));
setCLKfromRTC();
}
else
{
adj = getValidPosition("set");
dlog.info(FPSTR(TAG), F("setting adjustment:%u"), adj);
if (clk.writeAdjustment(adj))
{
dlog.error(FPSTR(TAG), F("failed to set adjustment!"));
}
}
}
if (clk.readAdjustment(&adj))
{
sprintf_P(message, PSTR("failed to read adjustment!\n"));
}
else
{
sprintf_P(message, PSTR("adjustment: %d\n"), adj);
}
HTTP.send(200, "text/plain", message);
}
void handlePosition()
{
static PROGMEM const char TAG[] = "handlePosition";
uint16_t pos;
if (HTTP.hasArg("set"))
{
pos = getValidPosition("set");
dlog.info(FPSTR(TAG), F("setting position:%u"), pos);
if (clk.writePosition(pos))
{
dlog.error(FPSTR(TAG), F("failed to set position!"));
}
}
if (clk.readPosition(&pos))
{
sprintf_P(message, PSTR("failed to read position!\n"));
}
else
{
int hours = pos / 3600;
int minutes = (pos - (hours * 3600)) / 60;
int seconds = pos - (hours * 3600) - (minutes * 60);
sprintf_P(message, PSTR("position: %d (%02d:%02d:%02d)\n"), pos, hours, minutes, seconds);
}
HTTP.send(200, "text/Plain", message);
}
void handleTPDuration()
{
static PROGMEM const char TAG[] = "handleTPDuration";
uint8_t value;
if (HTTP.hasArg("set"))
{
value = getValidDuration("set");
dlog.info(FPSTR(TAG), F("setting tp_duration:%u"), value);
if (clk.writeTPDuration(value))
{
dlog.error(FPSTR(TAG), F("failed to set TP duration!"));
}
}
if (clk.readTPDuration(&value))
{
sprintf_P(message, PSTR("failed to read TP duration\n"));
}
else
{
sprintf_P(message, PSTR("TP duration: %u\n"), value);
}
HTTP.send(200, "text/plain", message);
}
void handleTPDuty()
{
static PROGMEM const char TAG[] = "handleTPDuty";
uint8_t value;
if (HTTP.hasArg("set"))
{
value = getValidDuty("set");
dlog.info(FPSTR(TAG), F("setting tp_duty:%u"), value);
if (clk.writeTPDuty(value))
{
dlog.error(FPSTR(TAG), F("failed to set TP duty!"));
}
}
if (clk.readTPDuty(&value))
{
sprintf_P(message, PSTR("failed to read TP duty\n"));
}
else
{
sprintf_P(message, PSTR("TP duty: %u\n"), value);
}
HTTP.send(200, "text/plain", message);
}
void handleAPDuration()
{
static PROGMEM const char TAG[] = "handleAPDuration";
uint8_t value;
if (HTTP.hasArg("set"))
{
value = getValidDuration("set");
dlog.info(FPSTR(TAG), F("setting ap_duration:%u"), value);
if (clk.writeAPDuration(value))
{
dlog.error(FPSTR(TAG), F("failed to set AP duration"));
}
}
if (clk.readAPDuration(&value))
{
sprintf_P(message, PSTR("failed to read AP duration\n"));
}
else
{
sprintf_P(message, PSTR("AP duration: %u\n"), value);
}
HTTP.send(200, "text/plain", message);
}
void handleAPDuty()
{
static PROGMEM const char TAG[] = "handleAPDuty";
uint8_t value;
if (HTTP.hasArg("set"))
{
value = getValidDuty("set");
dlog.info(FPSTR(TAG), F("setting ap_duty:%u"), value);
if (clk.writeAPDuty(value))
{
dlog.error(FPSTR(TAG), F("failed to set AP duty!"));
}
}
if (clk.readAPDuty(&value))
{
sprintf_P(message, PSTR("failed to read AP duty\n"));
}
else
{
sprintf_P(message, PSTR("AP duty: %u\n"), value);
}
HTTP.send(200, "text/plain", message);
}
void handleAPDelay()
{
static PROGMEM const char TAG[] = "handleAPDelay";
uint8_t value;
if (HTTP.hasArg("set"))
{
value = getValidDuration("set");
dlog.info(FPSTR(TAG), F("setting ap_delay:%u"), value);
if (clk.writeAPDelay(value))
{
dlog.error(FPSTR(TAG), F("failed to set AP delay"));
}
}
if (clk.readAPDelay(&value))
{
sprintf_P(message, PSTR("failed to read AP delay\n"));
}
else
{
sprintf_P(message, PSTR("AP delay: %u\n"), value);
}
HTTP.send(200, "text/plain", message);
}
void handleAPStartDuration()
{
static PROGMEM const char TAG[] = "handleAPStartDuration";
uint8_t value;
if (HTTP.hasArg("set"))
{
value = getValidDuration("set");
dlog.info(FPSTR(TAG), F("setting ap_start:%u"), value);
if (clk.writeAPStartDuration(value))
{
dlog.error(FPSTR(TAG), F("failed to set AP start duration"));
}
}
if (clk.readAPStartDuration(&value))
{
sprintf_P(message, PSTR("failed to read AP start duration\n"));
}
else
{
sprintf_P(message, PSTR("AP start duration: %u\n"), value);
}
HTTP.send(200, "text/plain", message);
}
void handlePWMTop()
{
static PROGMEM const char TAG[] = "handlePWMTop";
uint8_t value;
if (HTTP.hasArg("set"))
{
value = getValidByte("set");
dlog.info(FPSTR(TAG), F("setting pwm_top:%u"), value);
if (clk.writePWMTop(value))
{
dlog.error(FPSTR(TAG), F("failed to set pwm_top!"));
}
}
if (clk.readPWMTop(&value))
{
sprintf_P(message, PSTR("failed to read pwm_top!\n"));
}
else
{
sprintf_P(message, PSTR("pwm_top: %u\n"), value);
}
HTTP.send(200, "text/plain", message);
}
void handleEnable()
{
boolean enable;
if (HTTP.hasArg("set"))
{
enable = getValidBoolean("set");
clk.setEnable(enable);
}
enable = clk.getEnable();
HTTP.send(200, "text/Plain", String(enable) + "\n");
}
void handleRTC()
{
static PROGMEM const char TAG[] = "handleRTC";
char message[64];
DS3231DateTime dt;
int err = rtc.readTime(dt);
if (!err)
{
dlog.info(FPSTR(TAG), F("handleRTC: RTC : %s (UTC)"), dt.string());
if (HTTP.hasArg("offset"))
{
double offset = strtod(HTTP.arg("offset").c_str(), NULL);
dlog.info(FPSTR(TAG), F("handleRTC: offset: %lf"), offset);
setRTCfromOffset(offset, true);
}
else if (HTTP.hasArg("sync") && getValidBoolean("sync"))
{
setCLKfromRTC();
}
uint16_t value = dt.getPosition(config.tz_offset);
int hours = value / 3600;
int minutes = (value - (hours * 3600)) / 60;
int seconds = value - (hours * 3600) - (minutes * 60);
sprintf_P(message, PSTR("%d (%02d:%02d:%02d)\n"), value, hours, minutes, seconds);
}
else
{
sprintf_P(message, PSTR("rtc.readTime() failed!\n"));
}
HTTP.send(200, "text/plain", message);
}
void handleNTP()
{
static PROGMEM const char TAG[] = "handleNTP";
char server[64];
strcpy(server, config.ntp_server);
boolean sync = false;
if (HTTP.hasArg("server"))
{
dlog.info(FPSTR(TAG), F("SERVER: %s"), HTTP.arg("server").c_str());
strncpy(server, HTTP.arg("server").c_str(), 63);
server[63] = 0;
}
if (HTTP.hasArg("sync"))
{
sync = getValidBoolean("sync");
}
double offset;
IPAddress address;
char message[64];
int code;
if (setRTCfromNTP(server, sync, &offset, &address))
{
code = 500;
snprintf_P(message, 64, PSTR("NTP Failed!\n"));
}
else
{
if (sync)
{
dlog.info(FPSTR(TAG), F("Syncing clock to RTC!"));
setCLKfromRTC();
}
code = 200;
snprintf_P(message, 64, PSTR("OFFSET: %0.6lf (%s)\n"), offset, address.toString().c_str());
}
dlog.info(FPSTR(TAG), F("%s"), message);
HTTP.send(code, "text/Plain", message);
return;
}
void handleWire()
{
int rtn = WireUtils.clearBus();
if (rtn != 0)
{
if (rtn == 1)
{
snprintf_P(message, 64, PSTR("SCL clock line held low\n"));
}
else if (rtn == 2)
{
snprintf_P(message, 64, PSTR("SCL clock line held low by slave clock stretch\n"));
}
else if (rtn == 3)
{
snprintf_P(message, 64, PSTR("SDA data line held low\n"));
}
else
{
snprintf_P(message, 64, PSTR("I2C bus error. Could not clear\n"));
}
}
else
{
snprintf_P(message, 64, PSTR("Recovered!\n"));
}
HTTP.send(200, "text/Plain", message);
}
void handleSave()
{
clk.saveConfig();
saveConfig();
HTTP.send(200, "text/plain", "Saved!\n");
}
void handleErase()
{
eraseConfig();
HTTP.send(200, "text/plain", "Erased!\n");
}
//
// update the timezone offset based on the current date/time
// return true if offset was updated
//
bool updateTZOffset()
{
static PROGMEM const char TAG[] = "updateTZOffset";
DS3231DateTime dt;
if (rtc.readTime(dt))
{
dlog.error(FPSTR(TAG), F("updateTZOffset: FAILED to read RTC"));
return false;
}
int new_offset = TimeUtils::computeUTCOffset(dt.getUnixTime(), config.tz_offset, config.tc, TIME_CHANGE_COUNT);
// if the time zone changed then save the new value and return true
if (config.tz_offset != new_offset)
{
dlog.info(FPSTR(TAG), F("time zone offset changed from %d to %d"), config.tz_offset, new_offset);
config.tz_offset = new_offset;
saveConfig();
return true;
}
return false;
}
void createWiFiParams(WiFiManager& wifi, std::vector<ConfigParamPtr> ¶ms)
{
static PROGMEM const char TAG[] = "wifiParams";
uint16_t pos = 0;
char pos_str[16];
clk.readPosition(&pos, 3);
int hours = pos / 3600;
int minutes = (pos - (hours * 3600)) / 60;
int seconds = pos - (hours * 3600) - (minutes * 60);
memset(pos_str, 0, sizeof(pos_str));
snprintf_P(pos_str, 15, PSTR("%02d:%02d:%02d"), hours, minutes, seconds);
uint8_t version = 0;
clk.readVersion(&version, 3);
snprintf_P(message, sizeof(message), PSTR("<p>ESP: %s ATTiny: %u</p>"), SYNCHRO_CLOCK_VERSION, version);
params.push_back(std::make_shared<ConfigParam>(wifi, message));
params.push_back(std::make_shared<ConfigParam>(wifi, "position", "Clock Position", pos_str, 10, [](const char* result)
{
if (strlen(result))
{
uint16_t position = TimeUtils::parsePosition(result);
dlog.info(FPSTR(TAG), F("setting position to %d"), position);
if (clk.writePosition(position))
{
dlog.error(FPSTR(TAG), F("failed to set initial position!"));
}
}
}));
params.push_back(std::make_shared<ConfigParam>(wifi, "ntp_server", "NTP Server", config.ntp_server, 32, [](const char* result)
{
strncpy(config.ntp_server, result, sizeof(config.ntp_server) - 1);
}));
params.push_back(std::make_shared<ConfigParam>(wifi, "<p>OTA Update</p>"));
params.push_back(std::make_shared<ConfigParam>(wifi, "ota_url", "OTA URL", "", 127, [](const char* result)
{
if (SPIFFS.begin())
{
dlog.warning(FPSTR(TAG), F("SPIFFS begin failed!!!"));
}
dlog.info(FPSTR(TAG), F("opening file for writing '%s'"), UPDATE_URL_FILENAME);
File uf = SPIFFS.open(UPDATE_URL_FILENAME, "w");
size_t urlsize = strlen(result);
size_t written = uf.write((uint8_t*)result, urlsize);
if (written != urlsize)
{
dlog.error(FPSTR(TAG), F("writing url size %d failed, only wrote %d"), urlsize, written);
}
uf.close();
url_update = true;
}));
params.push_back(std::make_shared<ConfigParam>(wifi, "<p>1st Time Change</p>"));
params.push_back(std::make_shared<ConfigParam>(wifi, "tc1_occurrence", "occurrence", config.tc[0].occurrence, 3, [](const char* result)
{
config.tc[0].occurrence = TimeUtils::parseOccurrence(result);
}));
params.push_back(std::make_shared<ConfigParam>(wifi, "tc1_day_of_week", "Day of Week (Sun=0)", config.tc[0].day_of_week, 2, [](const char* result)
{
config.tc[0].day_of_week = TimeUtils::parseDayOfWeek(result);
}));
params.push_back(std::make_shared<ConfigParam>(wifi, "tc1_day_offset", "day offset", config.tc[0].day_offset, 3, [](const char* result)
{
config.tc[0].day_offset = TimeUtils::parseOccurrence(result);
}));
params.push_back(std::make_shared<ConfigParam>(wifi, "tc1_month", "Month (Jan=1)", config.tc[0].month, 3, [](const char* result)
{
config.tc[0].month = TimeUtils::parseMonth(result);
}));
params.push_back(std::make_shared<ConfigParam>(wifi, "tc1_hour", "Hour (0-23)", config.tc[0].hour, 3, [](const char* result)
{
config.tc[0].hour = TimeUtils::parseHour(result);
}));
params.push_back(std::make_shared<ConfigParam>(wifi, "tc1_offset", "Time Offset", config.tc[0].tz_offset, 8, [](const char* result)
{
config.tc[0].tz_offset = TimeUtils::parseOffset(result);
}));
params.push_back(std::make_shared<ConfigParam>(wifi, "<p>2nd Time Change</p>"));
params.push_back(std::make_shared<ConfigParam>(wifi, "tc2_occurrence", "occurrence", config.tc[1].occurrence, 3, [](const char* result)
{
config.tc[1].occurrence = TimeUtils::parseOccurrence(result);
}));
params.push_back(std::make_shared<ConfigParam>(wifi, "tc2_day_of_week", "Day of Week (Sun=0)", config.tc[1].day_of_week, 2, [](const char* result)
{
config.tc[1].day_of_week = TimeUtils::parseDayOfWeek(result);
}));
params.push_back(std::make_shared<ConfigParam>(wifi, "tc2_day_offset", "day offset", config.tc[1].day_offset, 3, [](const char* result)
{
config.tc[1].day_offset = TimeUtils::parseOccurrence(result);
}));
params.push_back(std::make_shared<ConfigParam>(wifi, "tc2_month", "Month (Jan=1)", config.tc[1].month, 3, [](const char* result)
{
config.tc[1].month = TimeUtils::parseMonth(result);
}));
params.push_back(std::make_shared<ConfigParam>(wifi, "tc2_hour", "Hour (0-23)", config.tc[1].hour, 3, [](const char* result)
{
config.tc[1].hour = TimeUtils::parseHour(result);
}));
params.push_back(std::make_shared<ConfigParam>(wifi, "tc2_offset", "Time Offset", config.tc[1].tz_offset, 8, [](const char* result)
{
config.tc[1].tz_offset = TimeUtils::parseOffset(result);
}));
params.push_back(std::make_shared<ConfigParam>(wifi, "<p>Advanced Settings!</p>"));
params.push_back(std::make_shared<ConfigParam>(wifi, "stay_awake", "Stay Awake 'true'", "", 8, [](const char* result)
{
stay_awake = parseBoolean(result);
}));
params.push_back(std::make_shared<ConfigParam>(wifi, "sleep_duration", "Sleep", config.sleep_duration, 8, [](const char* result)
{
config.sleep_duration = atoi(result);
}));
uint8_t value;
clk.readTPDuration(&value);
params.push_back(std::make_shared<ConfigParam>(wifi, "tp_duration", "Tick Pulse", value, 8, [](const char* result)
{
uint8_t tp_duration = TimeUtils::parseSmallDuration(result);
clk.writeTPDuration(tp_duration);
}));
clk.readTPDuty(&value);
params.push_back(std::make_shared<ConfigParam>(wifi, "tp_duty", "Tick Pulse Duty", value, 8, [](const char* result)
{
uint8_t tp_duty = parseDuty(result);
clk.writeTPDuty(tp_duty);
}));
clk.readAPStartDuration(&value);
params.push_back(std::make_shared<ConfigParam>(wifi, "ap_start", "Adjust Start Pulse", value, 4, [](const char* result)
{
uint8_t ap_start = TimeUtils::parseSmallDuration(result);
clk.writeAPStartDuration(ap_start);
}));
clk.readAPDuration(&value);
params.push_back(std::make_shared<ConfigParam>(wifi, "ap_duration", "Adjust Pulse", value, 4, [](const char* result)
{
uint8_t ap_duration = TimeUtils::parseSmallDuration(result);
clk.writeAPDuration(ap_duration);
}));
clk.readAPDuty(&value);
params.push_back(std::make_shared<ConfigParam>(wifi, "ap_duty", "Adjust Pulse Duty", value, 8, [](const char* result)
{
uint8_t ap_duty = parseDuty(result);
clk.writeAPDuty(ap_duty);
}));
clk.readAPDelay(&value);
params.push_back(std::make_shared<ConfigParam>(wifi, "ap_delay", "Adjust Delay", value, 4, [](const char* result)
{
uint8_t ap_delay = TimeUtils::parseSmallDuration(result);
clk.writeAPDelay(ap_delay);
}));
params.push_back(std::make_shared<ConfigParam>(wifi, "syslog_host", "Syslog Host", config.syslog_host, 32, [](const char* result)
{
strncpy(config.syslog_host, result, sizeof(config.syslog_host) - 1);
}));
params.push_back(std::make_shared<ConfigParam>(wifi, "syslog_port", "Syslog Port", config.syslog_port, 6, [](const char* result)
{
config.syslog_port = atoi(result);
}));
params.push_back(std::make_shared<ConfigParam>(wifi, "clear_ntp_persist", "Clear NTP Persist 'true'", "", 8, [](const char* result)
{
boolean clearit = parseBoolean(result);
if (clearit)
{
memset(&config.ntp_persist, 0, sizeof(config.ntp_persist));
}
}));
}
//
// connect to wifi, open config portal first if required.
// returns true if connected, false if not
//
bool initWiFi()
{
static PROGMEM const char TAG[] = "initWiFi";
dlog.info(FPSTR(TAG), F("starting WiFi!"));
// setup wifi, blink let slow while connecting and fast if portal activated.
feedback.blink(FEEDBACK_LED_SLOW);
sntp_servermode_dhcp(0);
WiFiManager wm;
wm.setEnableConfigPortal(false); // don't automatically use the captive portal
wm.setDebugOutput(false);
wm.setConnectTimeout(CONNECTION_TIMEOUT);
wm.setSaveConfigCallback([]()
{
save_config = true;
});
wm.setAPCallback([](WiFiManager *)
{
dlog.info(FPSTR(TAG), F("config portal up!"));
feedback.blink(FEEDBACK_LED_FAST);
});
std::vector<ConfigParamPtr> params;
snprintf(devicename, sizeof(devicename), "SynchroClock:%08x", ESP.getChipId());
if (force_config)
{
createWiFiParams(wm, params);
dlog.info(FPSTR(TAG), F("params has %d items"), params.size());
wm.startConfigPortal(devicename, NULL);
delay(2000); // delay 2 seconds after portal for connect
}
else
{
wm.autoConnect(devicename, NULL);
}
WiFi.mode(WIFI_STA); // force station mode only (WifiManager issue work around)
feedback.off();
//
// if we are not connected then deep sleep and try again.
if (!WiFi.isConnected())
{
return false;
}
IPAddress ip = WiFi.localIP();
String mac = WiFi.macAddress();
dlog.info(FPSTR(TAG), F("Connected! IP address is: %u.%u.%u.%u MAC:'%s'"), ip[0], ip[1], ip[2], ip[3], mac.c_str());
//
// Config was set from captive portal!
//
if (save_config)
{
dlog.info(FPSTR(TAG), F("updating changed params"));
for(ConfigParamPtr p : params)
{
p->applyIfChanged();
}
clk.saveConfig();
saveConfig();
updateTZOffset();
}
dlog.debug(FPSTR(TAG), F("syslog settings: '%s:%d'"), config.syslog_host, config.syslog_port);
// Configure syslog logging if enabled
if (strlen(config.syslog_host) && config.syslog_port)
{
dlog.begin(new DLogSyslogWriter(config.syslog_host, config.syslog_port, devicename, SYNCHRO_CLOCK_VERSION));
dlog.info(FPSTR(TAG), F("starting syslog to '%s:%d'"), config.syslog_host, config.syslog_port);
delay(200); // delay so ARP usually finishes before we overrun the UDP transmit buffers (maybe?)
}
return true;
}
bool setSystemTime()
{
static PROGMEM const char TAG[] = "setSystemTime";
DS3231DateTime dt;
unsigned int tries = 10;
dlog.info(FPSTR(TAG), F("getting current date/time from RTC"));
while(rtc.readTime(dt))
{
--tries;
if (tries == 0)
{
return false;
}
dlog.error(FPSTR(TAG), F("FAILED to read RTC %d tries left!"), tries);
WireUtils.clearBus();
}
dlog.info(FPSTR(TAG), F("setting system time"));
struct timeval tv;
tv.tv_sec = dt.getUnixTime();
tv.tv_usec = 0;
int ret = settimeofday(&tv, nullptr);
if (ret != 0)
{
dlog.error(FPSTR(TAG), F("failed to set system time"));
return false;
}
return true;
}
void processOTA(bool enable_clock)
{
static PROGMEM const char TAG[] = "processOTA";
dlog.info(FPSTR(TAG), F("process any OTA update"));
dlog.info(FPSTR(TAG), F("free mem: %u"), ESP.getFreeHeap());
if (url_update)
{
dlog.info(FPSTR(TAG), F("setting clock enable: %s"), enable_clock ? "true" : "false");
clk.setEnable(enable_clock);
dlog.info(FPSTR(TAG), F("got a url update, use deep sleep to reset for a clean heap!"));
dlog.end();
dsd.sleep_delay_left = 0;
writeDeepSleepData();
ESP.deepSleep(300000, RF_DEFAULT); // short sleep
while(true); // should never get here
}
if (!SPIFFS.begin())
{
dlog.warning(FPSTR(TAG), F("SPIFFS begin failed!!!"));
}
File uf = SPIFFS.open(UPDATE_URL_FILENAME, "r");
if (uf)
{
dlog.info(FPSTR(TAG), F("opened update file '%s"), UPDATE_URL_FILENAME);
size_t urlsize = uf.size();
dlog.info(FPSTR(TAG), F("file size is %d, allocating buffer"), urlsize);
char* url = new char[urlsize+1];
std::fill(&url[0], &url[urlsize+1], 0);
size_t gotsize = uf.read((uint8_t*)url, urlsize);
if (gotsize != urlsize)
{
dlog.warning(FPSTR(TAG), F("failed to read %d bytes from url file, only got %d"), urlsize, gotsize);
}
uf.close();
dlog.info(FPSTR(TAG), F("deleting file '%s"), UPDATE_URL_FILENAME);
SPIFFS.remove(UPDATE_URL_FILENAME);
// need to set system time so that TLS validation can happen
setSystemTime();
dlog.info(FPSTR(TAG), F("checking for OTA from: '%s'"), url);
feedback.blink(FEEDBACK_LED_MEDIUM);
ESPhttpUpdate.rebootOnUpdate(false);
//ESPhttpUpdate.followRedirects(true);
t_httpUpdate_return ret = HTTP_UPDATE_FAILED;
WiFiClient *client = nullptr;
if (strncmp(url, "https:", 6) == 0)
{
#ifdef USE_CERT_STORE
int numCerts = certStore.initCertStore(SPIFFS, PSTR("/certs.idx"), PSTR("/certs.ar"));
dlog.info(FPSTR(TAG), F("Number of CA certs read: %d"), numCerts);
#endif
BearSSL::WiFiClientSecure *bear = new BearSSL::WiFiClientSecure();
#ifdef USE_CERT_STORE
// Integrate the cert store with this connection
dlog.info(FPSTR(TAG), F("adding cert store to connection"));
bear->setCertStore(&certStore);
#else
bear->setInsecure();
#endif
client = bear;
}
else
{
client = new WiFiClient;
}
dlog.info(FPSTR(TAG), F("free mem: %u"), ESP.getFreeHeap());
dlog.info(FPSTR(TAG), F("starting update with client: 0x%08x"), (unsigned int)client);
ret = ESPhttpUpdate.update(*client, url, SYNCHRO_CLOCK_VERSION);
String reason = ESPhttpUpdate.getLastErrorString();
switch(ret)
{
case HTTP_UPDATE_OK:
clk.setEnable(enable_clock);
dlog.info(FPSTR(TAG), F("OTA update OK! restarting..."));
dlog.end();
ESP.restart();
break;
case HTTP_UPDATE_FAILED:
dlog.info(FPSTR(TAG), F("OTA update failed! reason:'%s'"), reason.c_str());
break;
case HTTP_UPDATE_NO_UPDATES:
dlog.info(FPSTR(TAG), F("OTA no updates! reason:'%s'"), reason.c_str());
break;
default:
dlog.info(FPSTR(TAG), F("OTA update WTF? unexpected return code: %d reason: '%s'"), ret, reason.c_str());
break;
}
}
}
void factoryReset()
{
static PROGMEM const char TAG[] = "factoryReset";
dlog.info(FPSTR(TAG), F("activated!"));
dlog.info(FPSTR(TAG), F("sending factory reset to Clock..."));
feedback.blink(FEEDBACK_LED_SLOW);
while (clk.factoryReset() != 0)
{
dlog.error(FPSTR(TAG), F("can't talk with Clock Controller!"));
while (WireUtils.clearBus())
{
delay(10000);
dlog.info(FPSTR(TAG), F("lets try that again..."));
}
delay(10000);
}
feedback.off();
dlog.info(FPSTR(TAG), F("invalidating config..."));
EEPROM.begin(sizeof(EEConfig));
eraseConfig();
dlog.info(FPSTR(TAG), F("erase WiFi config..."));
ESP.eraseConfig();
dlog.info(FPSTR(TAG), F("waiting for power off!!!!"));
dlog.end();
//
// delay one second then restart!
//
delay(1000);
ESP.restart();
while(true)
{
}
}
//
// prefix log lines with current time
//
void dlogPrefix(DLogBuffer& buffer, DLogLevel level)
{
(void)level; // not used
uint32 usec = micros();
uint32 secs = usec / 1000000;
usec = usec - (secs * 1000000);
buffer.printf(F("%d.%06ld "), secs, usec);
}
void setup()
{
static PROGMEM const char TAG[] = "setup";
uint32_t free_mem = ESP.getFreeHeap();
Serial.begin(76800); // use the default baud rate that the ESPs SDK uses
dlog.begin(new DLogPrintWriter(Serial));
dlog.setPreFunc(&dlogPrefix);
#ifdef NTP_LOG_LEVEL
dlog.setLevel(F("NTP"), NTP_LOG_LEVEL);
#endif
dlog.info(FPSTR(TAG), F("Startup! SynchroClock version: %s"), SYNCHRO_CLOCK_VERSION);
dlog.info(FPSTR(TAG), F("ESP ChipId: 0x%08x (%u)"), ESP.getChipId(), ESP.getChipId());
dlog.info(FPSTR(TAG), F("free mem: %u"), free_mem);
#ifdef SHOW_RTC_SIZES
dlog.info(FPSTR(TAG), F("sizes: RTC: %d NTPRunTime: %d NTPSample: %d" ), sizeof(RTCDeepSleepData), sizeof(NTPRunTime), sizeof(NTPSample));
#endif
pinMode(SYNC_PIN, INPUT);
pinMode(CONFIG_PIN, INPUT);
feedback.off();
//
// lets read deep sleep data and see if we need to immed go back to sleep.
//
memset(&dsd, 0, sizeof(dsd));
readDeepSleepData();
Wire.begin();
Wire.setClockStretchLimit(CLOCK_STRETCH_LIMIT);
//
// initialize config to defaults then load.
memset(&config, 0, sizeof(config));
config.sleep_duration = DEFAULT_SLEEP_DURATION;
config.tz_offset = 0;
config.syslog_port = 514;
//
// make sure the device is available!
//
dlog.info(FPSTR(TAG), F("starting clock interface"));
while (clk.begin(3) != 0)
{
dlog.error(FPSTR(TAG), F("can't talk with Clock Controller!"));
delay(10000);
}
uint8_t version = 0;
while (clk.readVersion(&version, 3) != 0)
{
dlog.error(FPSTR(TAG), F("can't read clock version!"));
delay(10000);
}
dlog.info(FPSTR(TAG), F("I2CAnalogClock VERSION: %u"), version);
struct rst_info * reset_info = ESP.getResetInfoPtr();
dlog.info(FPSTR(TAG), F("Reset reason: %lu '%s'"), reset_info->reason, ESP.getResetReason().c_str());
uint8_t clk_reason; // restart reason of clk
while (clk.readResetReason(&clk_reason, 3) != 0)
{
dlog.error(FPSTR(TAG), F("can't read clock reset reason!"));
delay(10000);
}
dlog.info(FPSTR(TAG), F("I2CAnalogClock restart reason: 0x%02x"), clk_reason);
uint16_t pos;
while (clk.readPosition(&pos, 3))
{
dlog.error(FPSTR(TAG), F("can't read clock position!"));
delay(10000);
}
int hours = pos / 3600;
int minutes = (pos - (hours * 3600)) / 60;
int seconds = pos - (hours * 3600) - (minutes * 60);
dlog.info(FPSTR(TAG), F("clock position: %d (%02d:%02d:%02d)"), pos, hours, minutes, seconds);
bool clock_was_enabled = clk.getEnable();
dlog.info(FPSTR(TAG), F("clock interface started, enabled:%s"), clock_was_enabled ? "true" : "false");
// if the reset/config button is pressed then force config
if (digitalRead(CONFIG_PIN) == 0)
{
//
// If we wake with the reset button pressed and sleep_delay_left then the radio is off
// so set it to 0 and use a very short deepSleep to turn the radio back on.
//
if (dsd.sleep_delay_left != 0)
{
dlog.info(FPSTR(TAG), F("reset button pressed with radio off, short sleep to enable!"));
dlog.end();
dsd.sleep_delay_left = 0;
writeDeepSleepData();
ESP.deepSleep(300000, RF_DEFAULT); // short sleep to enable the radio!
}
time_t start = millis();
while (digitalRead(CONFIG_PIN) == 0)
{
time_t delta = millis() - start;
if (!force_config && delta > CONFIG_DELAY)
{
feedback.on();
dlog.info(FPSTR(TAG), F("reset button pressed, forcing config or factory reset!"));
force_config = true;
//
// If we force config because of the config button then we stop the clock.
//
clk.writeAdjustment(0);
clk.setEnable(false);
// reset the start and continue for factory reset delay
start += delta;
continue;
}
//
// if the button is held for FACTORY_RESET_DELAY milliseconds then factory reset!
//
if (delta > FACTORY_RESET_DELAY)
{
factoryReset();
}
delay(100);
}
}
bool enabled = clk.getEnable();
dlog.info(FPSTR(TAG), F("clock enable is:%u"), enabled);
strncpy_P(config.ntp_server, PSTR(DEFAULT_NTP_SERVER), sizeof(config.ntp_server) - 1);
config.ntp_server[sizeof(config.ntp_server) - 1] = 0;
// Default to disabled (all tz_offsets = 0)
config.tc[0].occurrence = DEFAULT_TC0_OCCUR;
config.tc[0].day_of_week = DEFAULT_TC0_DOW;
config.tc[0].day_offset = DEFAULT_TC0_DOFF;
config.tc[0].month = DEFAULT_TC0_MONTH;
config.tc[0].hour = DEFAULT_TC0_HOUR;
config.tc[0].tz_offset = DEFAULT_TC0_OFFSET;
config.tc[1].occurrence = DEFAULT_TC1_OCCUR;
config.tc[1].day_of_week = DEFAULT_TC1_DOW;
config.tc[1].day_offset = DEFAULT_TC1_DOFF;
config.tc[1].month = DEFAULT_TC1_MONTH;
config.tc[1].hour = DEFAULT_TC1_HOUR;
config.tc[1].tz_offset = DEFAULT_TC1_OFFSET;
dlog.debug(FPSTR(TAG), F("defaults: tz:%d ntp:%s logging: %s:%d"), config.tz_offset,
config.ntp_server, config.syslog_host, config.syslog_port);
dlog.debug(FPSTR(TAG), F("EEConfig size: %u"), sizeof(EEConfig));
//
// if the saved config was not good and the clock is not running
// then force config. If the clock is running then we trust that
// it has the correct position.
//
if (!loadConfig() && !enabled)
{
force_config = true;
}
#if defined(DEFAULT_LOG_ADDR) && defined(DEFAULT_LOG_PORT)
if (strnlen(config.syslog_host, 64) == 0)
{
strncpy(config.syslog_host, DEFAULT_LOG_ADDR, 64);
config.syslog_port = DEFAULT_LOG_PORT;
}
#endif
dlog.info(FPSTR(TAG), F("config: tz:%d ntp:%s logging: %s:%d"), config.tz_offset,
config.ntp_server, config.syslog_host, config.syslog_port);
dlog.info(FPSTR(TAG), F("starting RTC"));
while (rtc.begin())
{
dlog.error(FPSTR(TAG), F("RTC begin failed! Attempting recovery..."));
while (WireUtils.clearBus())
{
delay(10000);
dlog.info(FPSTR(TAG), F("lets try that again..."));
}
delay(1000);
}
bool clock_needs_sync = updateTZOffset();
#if defined(USE_DRIFT)
//
// apply drift to RTC
//
if (setRTCfromDrift() == 0)
{
clock_needs_sync = true;
}
#endif
dlog.info(FPSTR(TAG), F("sleep_delay_left: %lu"), dsd.sleep_delay_left);
if (dsd.sleep_delay_left != 0)
{
if (clock_needs_sync)
{
setCLKfromRTC();
}
sleepFor(dsd.sleep_delay_left);
}
#if !defined(DISABLE_DEEP_SLEEP)
if (!enabled)
{
force_config = true; // force the config portal to set the position if the clock is not running
}
#endif
if (!initWiFi())
{
//
// if we are are enabled and not connected then make sure that the clock is in-sync with
// the actual time - it could have just been powered on and failed to connect.....
//
if (enabled)
{
setCLKfromRTC();
}
dlog.error(FPSTR(TAG), F("failed to connect to wifi!"));
sleepFor(MAX_SLEEP_DURATION);
}
//
// Network started, log versions
//
dlog.info(FPSTR(TAG), F("SynchroClock VERSION: '%s'"), SYNCHRO_CLOCK_VERSION);
dlog.info(FPSTR(TAG), F("I2CAnalogClock VERSION: %u"), version);
dlog.info(FPSTR(TAG), F("ESP ChipId: 0x%08x (%u)"), ESP.getChipId(), ESP.getChipId());
processOTA(clock_was_enabled);
dlog.debug(FPSTR(TAG), F("###### rtc data size: %d"), sizeof(RTCDeepSleepData));
ntp.begin(NTP_PORT);
if (!enabled)
{
dlog.info(FPSTR(TAG), F("enabling clock"));
clk.setEnable(true);
}
else
{
dlog.info(FPSTR(TAG), F("clock is enabled, skipping init of RTC"));
}
#if !defined(DISABLE_INITIAL_NTP)
dlog.info(FPSTR(TAG), F("syncing RTC from NTP!"));
setRTCfromNTP(config.ntp_server, true, NULL, NULL);
#endif
#if !defined(DISABLE_INITIAL_SYNC)
dlog.info(FPSTR(TAG), F("syncing clock to RTC!"));
setCLKfromRTC();
#endif
#if defined(DISABLE_DEEP_SLEEP)
stay_awake = true;
#endif
if (!stay_awake)
{
#if defined(USE_NTP_POLL_ESTIMATE)
sleepFor(ntp.getPollInterval());
#else
sleepFor(config.sleep_duration);
#endif
}
dlog.info(FPSTR(TAG), F("starting HTTP"));
HTTP.on("/offset", HTTP_GET, handleOffset);
HTTP.on("/adjust", HTTP_GET, handleAdjustment);
HTTP.on("/position", HTTP_GET, handlePosition);
HTTP.on("/tp_duration", HTTP_GET, handleTPDuration);
HTTP.on("/tp_duty", HTTP_GET, handleTPDuty);
HTTP.on("/ap_duration", HTTP_GET, handleAPDuration);
HTTP.on("/ap_duty", HTTP_GET, handleAPDuty);
HTTP.on("/ap_delay", HTTP_GET, handleAPDelay);
HTTP.on("/enable", HTTP_GET, handleEnable);
HTTP.on("/rtc", HTTP_GET, handleRTC);
HTTP.on("/ntp", HTTP_GET, handleNTP);
HTTP.on("/wire", HTTP_GET, handleWire);
HTTP.on("/save", HTTP_GET, handleSave);
HTTP.on("/erase", HTTP_GET, handleErase);
HTTP.on("/ap_start", HTTP_GET, handleAPStartDuration);
HTTP.on("/pwm_top", HTTP_GET, handlePWMTop);
HTTP.begin();
}
void sleepFor(uint32_t sleep_duration)
{
static PROGMEM const char TAG[] = "sleepFor";
dlog.info(FPSTR(TAG), F("seconds: %u"), sleep_duration);
dsd.sleep_delay_left = sleep_duration;
sleep_duration = MAX_SLEEP_DURATION;
RFMode mode = RF_NO_CAL;
if (dsd.sleep_delay_left > MAX_SLEEP_DURATION)
{
dsd.sleep_delay_left = dsd.sleep_delay_left - MAX_SLEEP_DURATION;
mode = RF_DISABLED;
dlog.info(FPSTR(TAG), F("sleep_duration > max, mode=DISABLED sleep_delay_left=%lu"), dsd.sleep_delay_left);
}
else
{
sleep_duration = dsd.sleep_delay_left;
dsd.sleep_delay_left = 0;
dlog.info(FPSTR(TAG), F("delay less than max, mode=DEFAULT sleep_delay_left=%lu"), dsd.sleep_delay_left);
}
uint64_t sleep_us = (uint64_t)sleep_duration * 1000000L;
writeDeepSleepData();
dlog.info(FPSTR(TAG), F("Deep Sleep Time: %u"), sleep_duration);
dlog.end();
ESP.deepSleep(sleep_us, mode);
}
void loop()
{
if (stay_awake)
{
HTTP.handleClient();
}
delay(100);
}
int getEdgeSyncedTime(DS3231DateTime& dt, unsigned int retries)
{
static PROGMEM const char TAG[] = "getEdgeSyncedTime";
clk.waitForEdge(CLOCK_EDGE_FALLING);
while(retries-- > 0)
{
clk.waitForEdge(CLOCK_EDGE_FALLING);
// need 1ms delay! but why? noise?
delay(1);
if (rtc.readTime(dt) == 0)
{
return 0;
}
dlog.warning(FPSTR(TAG), F("failed to read from RTC, %d retries left"), retries);
WireUtils.clearBus();
}
return -1;
}
int setRTCfromOffset(double offset, bool sync)
{
static PROGMEM const char TAG[] = "setRTCfromOffset";
int32_t seconds = (int32_t)offset;
uint32_t msdelay = fabs((offset - (double)seconds) * 1000);
if (offset > 0)
{
seconds = seconds + 1; // +1 because we go to the next second
msdelay = 1000.0 - msdelay;
}
dlog.info(FPSTR(TAG), F("offset: %lf seconds: %d msdelay: %d sync: %s"),
offset, seconds, msdelay, sync ? "true" : "false");
DS3231DateTime dt;
if (getEdgeSyncedTime(dt, 3))
{
dlog.error(FPSTR(TAG), F("failed to read from RTC!"));
return ERROR_RTC;
}
// wait for where the next second should start
if (msdelay > 0 && msdelay < 1000)
{
delay(msdelay);
}
uint32_t old_time = dt.getUnixTime();
uint32_t new_time = old_time + seconds;
dt.setUnixTime(new_time);
if (sync)
{
if (rtc.writeTime(dt))
{
dlog.error(FPSTR(TAG), F("FAILED to set RTC: %s"), dt.string());
return ERROR_RTC;
}
}
//
// Update the TZ offset in case the timezone offset has changed based on new time.
//
delay(1); // DS3231 seems to need a short delay after write before we read the time back
updateTZOffset();
dlog.info(FPSTR(TAG), F("old_time: %d new_time: %d"), old_time, new_time);
dlog.info(FPSTR(TAG), F("RTC: %s"), dt.string());
return 0;
}
/*
* sync to a second boundary and return the current time in seconds
*/
int getTime(uint32_t *result)
{
clk.waitForEdge(CLOCK_EDGE_FALLING);
delay(2); // ATtiny85 at 1mhz has interrupts disabled for a bit after the falling edge
DS3231DateTime dt;
if (rtc.readTime(dt))
{
dlog.error(F("getTime"), F("failed to read from RTC!"));
return -1;
}
*result = dt.getUnixTime();
return 0;
}
int setRTCfromDrift()
{
static PROGMEM const char TAG[] = "setRTCfromDrift";
double offset;
if (ntp.getOffsetUsingDrift(&offset, &getTime))
{
dlog.error(FPSTR(TAG), F("failed, not adjusting for drift!"));
return -1;
}
dlog.info(FPSTR(TAG), F("********* DRIFT OFFSET: %lf"), offset);
int error = setRTCfromOffset(offset, true);
if (error)
{
return error;
}
dlog.debug(FPSTR(TAG), F("returning OK"));
return 0;
}
int setRTCfromNTP(const char* server, bool sync, double* result_offset, IPAddress* result_address)
{
static PROGMEM const char TAG[] = "setRTCfromNTP";
dlog.info(FPSTR(TAG), F("using server: %s"), server);
double offset;
if (ntp.getOffset(server, &offset, &getTime))
{
dlog.warning(FPSTR(TAG), F("NTP Failed!"));
return ERROR_NTP;
}
if (result_address)
{
*result_address = ntp.getAddress();
}
if (result_offset != NULL)
{
*result_offset = offset;
}
dlog.info(FPSTR(TAG), F("********* NTP OFFSET: %lf"), offset);
int error = setRTCfromOffset(offset, sync);
if (error)
{
return error;
}
dlog.debug(FPSTR(TAG), F("returning OK"));
return 0;
}
int setCLKfromRTC()
{
static PROGMEM const char TAG[] = "setCLKfromRTC";
// if there is already an adjustment in progress then stop it.
if (clk.writeAdjustment(0))
{
dlog.error(FPSTR(TAG), F("failed to clear adjustment!"));
return -1;
}
#if defined(USE_STOP_THE_CLOCK)
int delta;
uint16_t clock_pos;
uint16_t rtc_pos;
do
{
clk.waitForEdge(CLOCK_EDGE_FALLING);
delay(10);
if (clk.readPosition(&clock_pos))
{
dlog.error(FPSTR(TAG), F("failed to read position, ignoring"));
return -1;
}
dlog.info(FPSTR(TAG), F("clock position:%d"), clock_pos);
DS3231DateTime dt;
if (rtc.readTime(dt))
{
dlog.error(FPSTR(TAG), F("FAILED to read RTC"));
return -1;
}
rtc_pos = dt.getPosition(config.tz_offset);
dlog.info(FPSTR(TAG), F("RTC position:%d"), rtc_pos);
delta = rtc_pos - clock_pos;
if (delta < 0 && abs(delta) < STOP_THE_CLOCK_MAX)
{
int stop_for = abs(delta) + STOP_THE_CLOCK_EXTRA;
dlog.info(FPSTR(TAG), F("stop the clock delta is %d stopping for %d seconds"), delta, stop_for);
// stop the clock for delta seconds
clk.setEnable(false);
delay(stop_for * 1000);
clk.setEnable(true);
}
//
// if we stopped the clock then repeat block to re-read clock & RTC
//
} while (delta < 0 && abs(delta) < STOP_THE_CLOCK_MAX);
#else
clk.waitForEdge(CLOCK_EDGE_FALLING);
delay(10);
uint16_t clock_pos;
if (clk.readPosition(&clock_pos))
{
dlog.error(FPSTR(TAG), F("failed to read position, ignoring"));
return -1;
}
dlog.info(FPSTR(TAG), F("clock position:%d"), clock_pos);
DS3231DateTime dt;
if (rtc.readTime(dt))
{
dlog.error(FPSTR(TAG), F("FAILED to read RTC"));
return -1;
}
uint16_t rtc_pos = dt.getPosition(config.tz_offset);
dlog.info(FPSTR(TAG), F("RTC position:%d"), rtc_pos);
#endif
if (clock_pos != rtc_pos)
{
int adj = rtc_pos - clock_pos;
if (adj < 0)
{
adj += MAX_POSITION;
}
dlog.info(FPSTR(TAG), F("sending adjustment of %u"), adj);
clk.waitForEdge(CLOCK_EDGE_RISING);
if (clk.writeAdjustment(adj))
{
dlog.error(FPSTR(TAG), F("failed to set adjustment!"));
return -1;
}
}
return 0;
}
uint32_t calculateCRC32(const uint8_t *data, size_t length)
{
uint32_t crc = 0xffffffff;
while (length--)
{
uint8_t c = *data++;
for (uint32_t i = 0x80; i > 0; i >>= 1)
{
bool bit = crc & 0x80000000;
if (c & i)
{
bit = !bit;
}
crc <<= 1;
if (bit)
{
crc ^= 0x04c11db7;
}
}
}
return crc;
}
void initConfig()
{
if (EEPROM.length() != sizeof(EEConfig))
{
dlog.info(F("initConfig"), F("initializing EEPROM"));
EEPROM.begin(sizeof(EEConfig));
}
}
boolean loadConfig()
{
static PROGMEM const char TAG[] = "loadConfig";
EEConfig cfg;
initConfig();
// Read struct from EEPROM
dlog.debug(FPSTR(TAG), F("loading from EEPROM"));
unsigned int i;
uint8_t* p = (uint8_t*) &cfg;
for (i = 0; i < sizeof(cfg); ++i)
{
p[i] = EEPROM.read(i);
}
dlog.debug(FPSTR(TAG), F("checking CRC"));
uint32_t crcOfData = calculateCRC32(((uint8_t*) &cfg.data), sizeof(cfg.data));
dlog.trace(FPSTR(TAG), F("CRC32 of data: %08x"), crcOfData);
dlog.trace(FPSTR(TAG), F("CRC32 read from EEPROM: %08x"), cfg.crc);
if (crcOfData != cfg.crc)
{
dlog.warning(FPSTR(TAG), F("CRC32 in EEPROM memory doesn't match CRC32 of data. Data is probably invalid!"));
return false;
}
dlog.debug(FPSTR(TAG), F("CRC32 check ok, data is probably valid."));
memcpy(&config, &cfg.data, sizeof(config));
return true;
}
void saveConfig()
{
static PROGMEM const char TAG[] = "saveConfig";
EEConfig cfg;
initConfig();
memcpy(&cfg.data, &config, sizeof(cfg.data));
cfg.crc = calculateCRC32(((uint8_t*) &cfg.data), sizeof(cfg.data));
dlog.debug(FPSTR(TAG), F("caculated CRC: %08x"), cfg.crc);
dlog.info(FPSTR(TAG), F("Saving configuration to EEPROM!"));
unsigned int i;
uint8_t* p = (uint8_t*) &cfg;
for (i = 0; i < sizeof(cfg); ++i)
{
EEPROM.write(i, p[i]);
}
bool result = EEPROM.commit();
dlog.info(FPSTR(TAG), F("result: %s"), result ? "success" : "FAILURE");
}
void eraseConfig()
{
static PROGMEM const char TAG[] = "eraseConfig";
initConfig();
dlog.info(FPSTR(TAG), F("erasing...."));
for (unsigned int i = 0; i < sizeof(EEConfig); ++i)
{
EEPROM.write(i, 0xff);
}
dlog.info(FPSTR(TAG), F("committing...."));
bool result = EEPROM.commit();
dlog.info(FPSTR(TAG), F("result: %s"), result ? "success" : "FAILURE");
}
boolean readDeepSleepData()
{
static PROGMEM const char TAG[] = "readDeepSleepData";
RTCDeepSleepData rtcdsd;
dlog.info(FPSTR(TAG), F("loading deep sleep data from RTC Memory"));
if (!ESP.rtcUserMemoryRead(0, (uint32_t*) &rtcdsd, sizeof(rtcdsd)))
{
dlog.error(FPSTR(TAG), F("failed to read RTC Memory"));
return false;
}
uint32_t crcOfData = calculateCRC32(((uint8_t*) &rtcdsd.data), sizeof(rtcdsd.data));
if (crcOfData != rtcdsd.crc)
{
dlog.warning(FPSTR(TAG), F("CRC32 in RTC Memory doesn't match CRC32 of data. Data is probably invalid!"));
return false;
}
memcpy(&dsd, &rtcdsd.data, sizeof(dsd));
return true;
}
boolean writeDeepSleepData()
{
RTCDeepSleepData rtcdsd;
memcpy(&rtcdsd.data, &dsd, sizeof(rtcdsd.data));
rtcdsd.crc = calculateCRC32(((uint8_t*) &rtcdsd.data), sizeof(rtcdsd.data));
if (!ESP.rtcUserMemoryWrite(0, (uint32_t*) &rtcdsd, sizeof(rtcdsd)))
{
dlog.error(F("writeDeepSleepData"), F("failed to write RTC Memory"));
return false;
}
return true;
}
a20780224This file is pinned to the selected release. Download the original to open it in your design tool.