diff --git a/sonoff/user_config.h b/sonoff/user_config.h index 0ed7eba35..f37f8dc63 100644 --- a/sonoff/user_config.h +++ b/sonoff/user_config.h @@ -322,6 +322,8 @@ #define MTX_ADDRESS6 0x76 // [DisplayAddress6] I2C address of sixth 8x8 matrix module #define MTX_ADDRESS7 0x00 // [DisplayAddress7] I2C address of seventh 8x8 matrix module #define MTX_ADDRESS8 0x00 // [DisplayAddress8] I2C address of eigth 8x8 matrix module +// #define USE_DS3231 // Enable use DS3231 external RTC , usefall when you don't have avaliable WIFI. see docs in the source file (+1k2 code) +// #define USE_RTC_ADDR 0x68 //you can change the addrsss of the DS3231 RTC, default is 0x68, not mandatory fieled #endif // USE_I2C diff --git a/sonoff/xsns_33_ds3231.ino b/sonoff/xsns_33_ds3231.ino new file mode 100644 index 000000000..df18c73db --- /dev/null +++ b/sonoff/xsns_33_ds3231.ino @@ -0,0 +1,191 @@ +/* + xsns_33_ds3231.ino - ds3231 RTC chip, act like sensor support for Sonoff-Tasmota + + Copyright (C) 2018 Guy Elgabsi (guy.elg AT gmail.com) + + 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 + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#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 + \*********************************************************************************************/ + +//DS3232 I2C Address +#ifndef USE_RTC_ADDR + #define USE_RTC_ADDR 0x68 +#endif + +//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 + +//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 +boolean ds3231ReadStatus = false , ds3231WriteStatus = false; //flag, we want to wriet/write to DS3231 onlu once +boolean DS3231chipDetected; + + +/*----------------------------------------------------------------------* + Detect the DS3231 Chip + ----------------------------------------------------------------------*/ +boolean DS3231Detect() +{ + if (I2cValidRead(USE_RTC_ADDR, RTC_STATUS, 1)) + { + snprintf_P(log_data, sizeof(log_data), S_LOG_I2C_FOUND_AT, "DS3231", USE_RTC_ADDR); + AddLog(LOG_LEVEL_INFO); + return true; + } + else + { + snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_I2C "%s *NOT* " D_FOUND_AT " 0x%x"), "DS3231", USE_RTC_ADDR); + AddLog(LOG_LEVEL_INFO); + return false; + } +} + +/*----------------------------------------------------------------------* + BCD-to-Decimal conversion + ----------------------------------------------------------------------*/ +uint8_t bcd2dec(uint8_t n) +{ + return n - 6 * (n >> 4); +} + +/*----------------------------------------------------------------------* + Decimal-to-BCD conversion + ----------------------------------------------------------------------*/ +uint8_t dec2bcd(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() +{ + 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)); + return MakeTime(tm); +} +/*----------------------------------------------------------------------* + Get time as TIME_T and set the DS3231 time to this value + ----------------------------------------------------------------------*/ +void SetDS3231Time (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 +} + +/*********************************************************************************************\ + Interface + \*********************************************************************************************/ + +#define XSNS_33 + +boolean Xsns33(byte function) +{ + boolean result = false; + + if (i2c_flg) { + switch (function) { + case FUNC_INIT: + DS3231chipDetected = DS3231Detect(); + result = DS3231chipDetected; + break; + + case FUNC_EVERY_SECOND: + TIME_T tmpTime; + if (!ds3231ReadStatus && DS3231chipDetected && utc_time < 1451602800 ) { // We still did not sync with NTP (time not valid) , so, read time from DS3231 + ntp_force_sync = 1; //force to sync with ntp + utc_time = ReadFromDS3231(); //we read UTC TIME from DS3231 + // from this line, we just copy the function from "void RtcSecond()" at the support.ino ,line 2143 and above + // We need it to set rules etc. + BreakTime(utc_time, tmpTime); + if (utc_time < 1451602800 ) { + ds3231ReadStatus = true; //if time in DS3231 is valid, do not update again + } + RtcTime.year = tmpTime.year + 1970; + daylight_saving_time = RuleToTime(Settings.tflag[1], RtcTime.year); + standard_time = RuleToTime(Settings.tflag[0], RtcTime.year); + snprintf_P(log_data, sizeof(log_data), PSTR("Set time from DS3231 to RTC (" D_UTC_TIME ") %s, (" D_DST_TIME ") %s, (" D_STD_TIME ") %s"), + GetTime(0).c_str(), GetTime(2).c_str(), GetTime(3).c_str()); + AddLog(LOG_LEVEL_INFO); + if (local_time < 1451602800) { // 2016-01-01 + rules_flag.time_init = 1; + } else { + rules_flag.time_set = 1; + } + result = true; + } + else if (!ds3231WriteStatus && DS3231chipDetected && utc_time > 1451602800 && abs(utc_time - ReadFromDS3231()) > 60) {//if time is valid and is drift from RTC in more that 60 second + snprintf_P(log_data, sizeof(log_data), PSTR("Write Time TO DS3231 from NTP (" D_UTC_TIME ") %s, (" D_DST_TIME ") %s, (" D_STD_TIME ") %s"), + GetTime(0).c_str(), GetTime(2).c_str(), GetTime(3).c_str()); + AddLog(LOG_LEVEL_INFO); + SetDS3231Time (utc_time); //update the DS3231 time + ds3231WriteStatus = true; + } + else { + result = false; + } + break; + } + } + return result; +} + +#endif // USE_DS3231 +#endif // USE_I2C