From e152d8ffe041fbc49a84409885da0bd211d05f18 Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Tue, 1 Mar 2022 14:53:13 +0100 Subject: [PATCH] Refactor DS3231 driver --- tasmota/support_rtc.ino | 3 +- tasmota/tasmota.h | 2 +- tasmota/xsns_33_ds3231.ino | 262 +++++++++++++++++-------------------- 3 files changed, 124 insertions(+), 143 deletions(-) diff --git a/tasmota/support_rtc.ino b/tasmota/support_rtc.ino index 13244fc2b..ce2ce2482 100644 --- a/tasmota/support_rtc.ino +++ b/tasmota/support_rtc.ino @@ -474,7 +474,8 @@ void RtcSecond(void) void RtcSync(const char* source) { Rtc.time_synced = true; RtcSecond(); - AddLog(LOG_LEVEL_INFO, PSTR("RTC: Synced by %s"), source); + AddLog(LOG_LEVEL_DEBUG, PSTR("RTC: Synced by %s"), source); + XsnsCall(FUNC_TIME_SYNCED); } void RtcSetTime(uint32_t epoch) { diff --git a/tasmota/tasmota.h b/tasmota/tasmota.h index c7e7939e8..8650f9a42 100644 --- a/tasmota/tasmota.h +++ b/tasmota/tasmota.h @@ -376,7 +376,7 @@ enum XsnsFunctions {FUNC_SETTINGS_OVERRIDE, FUNC_PIN_STATE, FUNC_MODULE_INIT, FU FUNC_ENERGY_EVERY_SECOND, FUNC_ENERGY_RESET, FUNC_RULES_PROCESS, FUNC_TELEPERIOD_RULES_PROCESS, FUNC_SERIAL, FUNC_FREE_MEM, FUNC_BUTTON_PRESSED, FUNC_BUTTON_MULTI_PRESSED, FUNC_WEB_ADD_BUTTON, FUNC_WEB_ADD_CONSOLE_BUTTON, FUNC_WEB_ADD_MANAGEMENT_BUTTON, FUNC_WEB_ADD_MAIN_BUTTON, - FUNC_WEB_GET_ARG, FUNC_WEB_ADD_HANDLER, FUNC_SET_CHANNELS, FUNC_SET_SCHEME, FUNC_HOTPLUG_SCAN, + FUNC_WEB_GET_ARG, FUNC_WEB_ADD_HANDLER, FUNC_SET_CHANNELS, FUNC_SET_SCHEME, FUNC_HOTPLUG_SCAN, FUNC_TIME_SYNCED, FUNC_DEVICE_GROUP_ITEM }; enum AddressConfigSteps { ADDR_IDLE, ADDR_RECEIVE, ADDR_SEND }; diff --git a/tasmota/xsns_33_ds3231.ino b/tasmota/xsns_33_ds3231.ino index 0e5780699..f5c12a907 100644 --- a/tasmota/xsns_33_ds3231.ino +++ b/tasmota/xsns_33_ds3231.ino @@ -1,7 +1,7 @@ /* - xsns_33_ds3231.ino - ds3231 RTC chip, act like sensor support for Tasmota + xsns_33_ds3231.ino - DS3231/DS1307 RTC chip support for Tasmota - Copyright (C) 2021 Guy Elgabsi (guy.elg AT gmail.com) + Copyright (C) 2021 Guy Elgabsi (guy.elg AT gmail.com) and Theo Arends This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -20,161 +20,124 @@ #ifdef USE_I2C #ifdef USE_DS3231 /*********************************************************************************************\ - DS3231 - its a accurate RTC that used in the SONOFF for get time when you not have internet connection - This is minimal library that use only for read/write time ! - We store UTC time in the DS3231 , so we can use the standart functions. - HOWTO Use : first time, you must to have internet connection (use your mobile phone or try in other location). - once you have ntp connection , the DS3231 internal clock will be updated automatically. - you can now power off the device, from now and on the time is stored in the module and will - be restored when the is no connection to NTP. - Source: Guy Elgabsi with special thanks to Jack Christensen - - I2C Address: 0x68 + * DS1307 and DS3231 support + * + * DS3231 - An accurate RTC that used for get time when you do not have internet connection. + * We store UTC time in the DS3231, so we can use the standart functions. + * HOWTO Use : Initially the time needs to be set into the DS3231 either by + * - hand using command TIME + * - internet using NTP + * - GPS using UBX driver + * Once stored the time will be automaticaly updated by the DS2331 after power on + * Source: Guy Elgabsi with special thanks to Jack Christensen + * + * I2C Address: 0x68 \*********************************************************************************************/ #define XSNS_33 33 -#define XI2C_26 26 // See I2CDEVICES.md +#define XI2C_26 26 // See I2CDEVICES.md -#ifndef USE_GPS // USE_GPS provides it's own (better) NTP server so skip this one +#ifndef USE_RTC_ADDR +#define USE_RTC_ADDR 0x68 // DS3231 I2C Address +#endif + +#ifndef USE_GPS // USE_GPS provides it's own (better) NTP server so skip this one #define DS3231_NTP_SERVER #endif -//DS3232 I2C Address -#ifndef USE_RTC_ADDR -#define USE_RTC_ADDR 0x68 -#endif +// DS3231 Register Addresses +#define DS3231_SECONDS 0x00 +#define DS3231_MINUTES 0x01 +#define DS3231_HOURS 0x02 +#define DS3231_DAY 0x03 +#define DS3231_DATE 0x04 +#define DS3231_MONTH 0x05 +#define DS3231_YEAR 0x06 +#define DS3231_CONTROL 0x0E +#define DS3231_STATUS 0x0F -//DS3232 Register Addresses -#define RTC_SECONDS 0x00 -#define RTC_MINUTES 0x01 -#define RTC_HOURS 0x02 -#define RTC_DAY 0x03 -#define RTC_DATE 0x04 -#define RTC_MONTH 0x05 -#define RTC_YEAR 0x06 -#define RTC_CONTROL 0x0E -#define RTC_STATUS 0x0F -//Control register bits -#define OSF 7 -#define EOSC 7 -#define BBSQW 6 -#define CONV 5 -#define RS2 4 -#define RS1 3 -#define INTCN 2 +// Control register bits +#define DS3231_OSF 7 +#define DS3231_EOSC 7 +#define DS3231_BBSQW 6 +#define DS3231_CONV 5 +#define DS3231_RS2 4 +#define DS3231_RS1 3 +#define DS3231_INTCN 2 //Other -#define HR1224 6 //Hours register 12 or 24 hour mode (24 hour mode==0) -#define CENTURY 7 //Century bit in Month register -#define DYDT 6 //Day/Date flag bit in alarm Day/Date registers +#define DS3231_HR1224 6 // Hours register 12 or 24 hour mode (24 hour mode==0) +#define DS3231_CENTURY 7 // Century bit in Month register +#define DS3231_DYDT 6 // Day/Date flag bit in alarm Day/Date registers -bool ds3231ReadStatus = false; -bool ds3231WriteStatus = false; //flag, we want to read/write to DS3231 only once -bool DS3231chipDetected = false; +bool ds3231_detected = false; /*********************************************************************************************/ -#ifdef DS3231_NTP_SERVER -#include "NTPServer.h" -#include "NTPPacket.h" - -#define D_CMND_NTP "NTP" - -const char S_JSON_NTP_COMMAND_NVALUE[] PROGMEM = "{\"" D_CMND_NTP "%s\":%d}"; - -const char kRTCTypes[] PROGMEM = "NTP"; - -#define NTP_MILLIS_OFFSET 50 - -NtpServer timeServer(PortUdp); - -struct NTP_t { - struct { - uint32_t init:1; - uint32_t runningNTP:1; - } mode; -} NTP; -#endif // DS3231_NTP_SERVER - -/*----------------------------------------------------------------------*\ - * Detect the DS3231 Chip -\*----------------------------------------------------------------------*/ -void DS3231Detect(void) { - if (!I2cSetDevice(USE_RTC_ADDR)) { return; } - - if (I2cValidRead(USE_RTC_ADDR, RTC_STATUS, 1)) { - I2cSetActiveFound(USE_RTC_ADDR, "DS3231"); - DS3231chipDetected = true; - } -} - -/*----------------------------------------------------------------------*\ - * BCD-to-Decimal conversion -\*----------------------------------------------------------------------*/ -uint8_t bcd2dec(uint8_t n) { +uint8_t DS3231bcd2dec(uint8_t n) { return n - 6 * (n >> 4); } -/*----------------------------------------------------------------------*\ - * Decimal-to-BCD conversion -/*----------------------------------------------------------------------*/ -uint8_t dec2bcd(uint8_t n) { +uint8_t DS3231dec2bcd(uint8_t n) { return n + 6 * (n / 10); } -/*----------------------------------------------------------------------*\ +/*-------------------------------------------------------------------------------------------*\ * Read time from DS3231 and return the epoch time (second since 1-1-1970 00:00) -\*----------------------------------------------------------------------*/ -uint32_t ReadFromDS3231(void) { +\*-------------------------------------------------------------------------------------------*/ +uint32_t DS3231ReadTime(void) { TIME_T tm; - tm.second = bcd2dec(I2cRead8(USE_RTC_ADDR, RTC_SECONDS)); - tm.minute = bcd2dec(I2cRead8(USE_RTC_ADDR, RTC_MINUTES)); - tm.hour = bcd2dec(I2cRead8(USE_RTC_ADDR, RTC_HOURS) & ~_BV(HR1224)); //assumes 24hr clock - tm.day_of_week = I2cRead8(USE_RTC_ADDR, RTC_DAY); - tm.day_of_month = bcd2dec(I2cRead8(USE_RTC_ADDR, RTC_DATE)); - tm.month = bcd2dec(I2cRead8(USE_RTC_ADDR, RTC_MONTH) & ~_BV(CENTURY)); //don't use the Century bit - tm.year = bcd2dec(I2cRead8(USE_RTC_ADDR, RTC_YEAR)); + tm.second = DS3231bcd2dec(I2cRead8(USE_RTC_ADDR, DS3231_SECONDS)); + tm.minute = DS3231bcd2dec(I2cRead8(USE_RTC_ADDR, DS3231_MINUTES)); + tm.hour = DS3231bcd2dec(I2cRead8(USE_RTC_ADDR, DS3231_HOURS) & ~_BV(DS3231_HR1224)); // Assumes 24hr clock + tm.day_of_week = I2cRead8(USE_RTC_ADDR, DS3231_DAY); + tm.day_of_month = DS3231bcd2dec(I2cRead8(USE_RTC_ADDR, DS3231_DATE)); + tm.month = DS3231bcd2dec(I2cRead8(USE_RTC_ADDR, DS3231_MONTH) & ~_BV(DS3231_CENTURY)); // Don't use the Century bit + tm.year = DS3231bcd2dec(I2cRead8(USE_RTC_ADDR, DS3231_YEAR)); return MakeTime(tm); } -/*----------------------------------------------------------------------*\ +/*-------------------------------------------------------------------------------------------*\ * Get time as TIME_T and set the DS3231 time to this value -/*----------------------------------------------------------------------*/ -void SetDS3231Time (uint32_t epoch_time) { +\*-------------------------------------------------------------------------------------------*/ +void DS3231SetTime (uint32_t epoch_time) { TIME_T tm; BreakTime(epoch_time, tm); - I2cWrite8(USE_RTC_ADDR, RTC_SECONDS, dec2bcd(tm.second)); - I2cWrite8(USE_RTC_ADDR, RTC_MINUTES, dec2bcd(tm.minute)); - I2cWrite8(USE_RTC_ADDR, RTC_HOURS, dec2bcd(tm.hour)); - I2cWrite8(USE_RTC_ADDR, RTC_DAY, tm.day_of_week); - I2cWrite8(USE_RTC_ADDR, RTC_DATE, dec2bcd(tm.day_of_month)); - I2cWrite8(USE_RTC_ADDR, RTC_MONTH, dec2bcd(tm.month)); - I2cWrite8(USE_RTC_ADDR, RTC_YEAR, dec2bcd(tm.year)); - I2cWrite8(USE_RTC_ADDR, RTC_STATUS, I2cRead8(USE_RTC_ADDR, RTC_STATUS) & ~_BV(OSF)); // Clear the Oscillator Stop Flag + I2cWrite8(USE_RTC_ADDR, DS3231_SECONDS, DS3231dec2bcd(tm.second)); + I2cWrite8(USE_RTC_ADDR, DS3231_MINUTES, DS3231dec2bcd(tm.minute)); + I2cWrite8(USE_RTC_ADDR, DS3231_HOURS, DS3231dec2bcd(tm.hour)); + I2cWrite8(USE_RTC_ADDR, DS3231_DAY, tm.day_of_week); + I2cWrite8(USE_RTC_ADDR, DS3231_DATE, DS3231dec2bcd(tm.day_of_month)); + I2cWrite8(USE_RTC_ADDR, DS3231_MONTH, DS3231dec2bcd(tm.month)); + I2cWrite8(USE_RTC_ADDR, DS3231_YEAR, DS3231dec2bcd(tm.year)); + I2cWrite8(USE_RTC_ADDR, DS3231_STATUS, I2cRead8(USE_RTC_ADDR, DS3231_STATUS) & ~_BV(DS3231_OSF)); // Clear the Oscillator Stop Flag } -void DS3231EverySecond(void) { - if (!ds3231ReadStatus && (Rtc.utc_time < START_VALID_TIME)) { // We still did not sync with NTP (time not valid) , so, read time from DS3231 - uint32_t ds3231_time = ReadFromDS3231(); // Read UTC TIME from DS3231 - if (ds3231_time > START_VALID_TIME) { - Rtc.utc_time = ds3231_time; - RtcSync("DS3231"); -// Rtc.user_time_entry = true; // Stop NTP sync and DS3231 time write - ds3231ReadStatus = true; // As time in DS3231 is valid, do not update again +/*********************************************************************************************/ + +void DS3231Detect(void) { + if (!I2cSetDevice(USE_RTC_ADDR)) { return; } + + if (I2cValidRead(USE_RTC_ADDR, DS3231_STATUS, 1)) { + I2cSetActiveFound(USE_RTC_ADDR, "DS3231"); + ds3231_detected = true; + + if (Rtc.utc_time < START_VALID_TIME) { // We still did not sync with NTP/GPS (time not valid), so read time from DS3231 + uint32_t ds3231_time = DS3231ReadTime(); // Read UTC TIME from DS3231 + if (ds3231_time > START_VALID_TIME) { + Rtc.utc_time = ds3231_time; + RtcSync("DS3231"); + } } } - else if ((!ds3231WriteStatus || (!(TasmotaGlobal.uptime % 3600))) && // After restart or every hour - (Rtc.utc_time > START_VALID_TIME) && // Valid UTC time - (abs((int32_t)(Rtc.utc_time - ReadFromDS3231())) > 4)) { // Time has drifted from RTC more than 4 seconds - SetDS3231Time(Rtc.utc_time); // Update the DS3231 time +} + +void DS3231TimeSynced(void) { + if ((Rtc.utc_time > START_VALID_TIME) && // Valid UTC time + (abs((int32_t)(Rtc.utc_time - DS3231ReadTime())) > 2)) { // Time has drifted from RTC more than 2 seconds + DS3231SetTime(Rtc.utc_time); // Update the DS3231 time AddLog(LOG_LEVEL_DEBUG, PSTR("DS3: Re-synced (" D_UTC_TIME ") %s"), GetDateAndTime(DT_UTC).c_str()); - ds3231WriteStatus = true; } -#ifdef DS3231_NTP_SERVER - if (NTP.mode.runningNTP) { - timeServer.processOneRequest(Rtc.utc_time, NTP_MILLIS_OFFSET); - } -#endif // DS3231_NTP_SERVER } #ifdef DS3231_NTP_SERVER @@ -182,25 +145,39 @@ void DS3231EverySecond(void) { * NTP functions \*********************************************************************************************/ -void NTPSelectMode(uint16_t mode) { - switch(mode){ - case 0: - NTP.mode.runningNTP = false; - break; - case 1: - if (timeServer.beginListening()) { - NTP.mode.runningNTP = true; - } - break; +#include "NTPServer.h" +#include "NTPPacket.h" + +#define NTP_MILLIS_OFFSET 50 + +NtpServer DS3231timeServer(PortUdp); + +bool ds3231_running_NTP = false; + +void DS3231EverySecond(void) { + if (ds3231_running_NTP) { + DS3231timeServer.processOneRequest(Rtc.utc_time, NTP_MILLIS_OFFSET); } } -bool NTPCmd(void) { +/*********************************************************************************************\ + * Supported commands for Sensor33: + * + * Sensor33 0 - NTP server off (default) + * Sensor33 1 - NTP server on +\*********************************************************************************************/ + +bool DS3231NTPCmd(void) { bool serviced = true; - if (XdrvMailbox.data_len > 0) { - NTPSelectMode(XdrvMailbox.payload); - Response_P(S_JSON_NTP_COMMAND_NVALUE, XdrvMailbox.command, XdrvMailbox.payload); + + if (XdrvMailbox.payload >= 0) { + ds3231_running_NTP = 0; + if ((XdrvMailbox.payload &1) && DS3231timeServer.beginListening()) { + ds3231_running_NTP = 1; + } } + Response_P(PSTR("{\"Sensor33\":{\"NTPServer\":\"%s\"}}"), GetStateText(ds3231_running_NTP)); + return serviced; } #endif // DS3231_NTP_SERVER @@ -217,17 +194,20 @@ bool Xsns33(uint8_t function) { if (FUNC_INIT == function) { DS3231Detect(); } - else if (DS3231chipDetected) { + else if (ds3231_detected) { switch (function) { #ifdef DS3231_NTP_SERVER + case FUNC_EVERY_SECOND: + DS3231EverySecond(); + break; case FUNC_COMMAND_SENSOR: if (XSNS_33 == XdrvMailbox.index) { - result = NTPCmd(); + result = DS3231NTPCmd(); } break; #endif // DS3231_NTP_SERVER - case FUNC_EVERY_SECOND: - DS3231EverySecond(); + case FUNC_TIME_SYNCED: + DS3231TimeSynced(); break; } }