#include "hardware_rtc.hpp" #include HardwareRTC::HardwareRTC(TwoWire& wire): wire(wire), initialized(false) {} HardwareRTC::~HardwareRTC() {} void HardwareRTC::begin() { if(wire.getClock() == 0) { wire.begin(); wire.setClock(100000); // Set to 100kHz } initialized = true; LOG_DEBUG("RTC initialized"); setSystemTime(0); } bool HardwareRTC::setTime(time_t unixTimestamp) { if (!initialized) { LOG_WARN("RTC not initialized"); return false; } // Use time_t, which on ESP32 is 64-bit and safe for this conversion time_t t = unixTimestamp; struct tm timeStruct; gmtime_r(&t, &timeStruct); // Check if the year is within the DS1307's valid range (2000-2099) if (timeStruct.tm_year < 100 || timeStruct.tm_year > 199) { // Year is out of the 2000-2099 range that the DS1307 can store. LOG_ERROR("RTC setTime: Year %d is out of range (2000-2099)", timeStruct.tm_year + 1900); return false; } uint8_t buffer[7]; buffer[0] = decToBcd(timeStruct.tm_sec); buffer[1] = decToBcd(timeStruct.tm_min); buffer[2] = decToBcd(timeStruct.tm_hour); buffer[3] = decToBcd(timeStruct.tm_wday + 1); buffer[4] = decToBcd(timeStruct.tm_mday); buffer[5] = decToBcd(timeStruct.tm_mon + 1); // The DS1307 only stores years 00-99. We handle this by taking the year since 2000. buffer[6] = decToBcd(timeStruct.tm_year - 100); writeRegisters(buffer, 7); // set system time to match RTC setSystemTime(0); LOG_DEBUG("RTC setTime: %s", toDateString(unixTimestamp).c_str()); return true; } time_t HardwareRTC::getTime() { if (!initialized) return 0; uint8_t buffer[7]; readRegisters(buffer, 7); struct tm timeStruct = {0}; // Important: Initialize the struct to zero timeStruct.tm_sec = bcdToDec(buffer[0] & 0x7F); timeStruct.tm_min = bcdToDec(buffer[1]); timeStruct.tm_hour = bcdToDec(buffer[2] & 0x3F); timeStruct.tm_wday = bcdToDec(buffer[3]) - 1; timeStruct.tm_mday = bcdToDec(buffer[4]); timeStruct.tm_mon = bcdToDec(buffer[5]) - 1; // Assume all 2-digit years from the RTC are in the 21st century (2000-2099) timeStruct.tm_year = bcdToDec(buffer[6]) + 100; // Years since 1900 // mktime converts a local time struct to a time_t. // Since the RTC stores time without timezone info, we treat it as UTC. // timegm is the correct function for this, but mktime is often used // on embedded systems with the timezone set to UTC. time_t t = mktime(&timeStruct); LOG_DEBUG("RTC getTime: %s", toDateString(t).c_str()); // Cast the 64-bit time_t to uint64_t for the return type return static_cast(t); } void HardwareRTC::setSystemTime(int timezoneOffsetHours) { if (!initialized) { LOG_WARN("RTC not initialized"); return; } uint64_t unixTime = getTime(); if (unixTime == 0) return; unixTime += timezoneOffsetHours * 3600; LOG_DEBUG("RTC setSystemTime: %s", toDateString(unixTime).c_str()); struct timeval tv; // The tv_sec field is of type time_t, which is 64-bit on ESP32 tv.tv_sec = unixTime; tv.tv_usec = 0; settimeofday(&tv, NULL); } bool HardwareRTC::isRunning() { if (!initialized) return false; uint8_t buffer[1]; readRegisters(buffer, 1); return !(buffer[0] & 0x80); // CH bit is 0 when running } void HardwareRTC::setTimezone(const char* timezone) { setenv("TZ", timezone, 1); tzset(); } void HardwareRTC::update() { static uint32_t nextSync = 0; time_t now = millis(); if (now >= nextSync) { if(!isRunning()) { LOG_WARN("RTC is not running, skipping update"); return; } setSystemTime(0); nextSync = now + (15UL * 60UL * 1000UL); // every 15 minutes LOG_DEBUG("RTC update: System time synchronized with RTC"); } } String HardwareRTC::toDateString(time_t timestamp) { struct tm* timeinfo = gmtime(×tamp); char buffer[40]; strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", timeinfo); return String(buffer); } uint8_t HardwareRTC::bcdToDec(uint8_t bcd) { return ((bcd >> 4) * 10) + (bcd & 0x0F); } uint8_t HardwareRTC::decToBcd(uint8_t dec) { return ((dec / 10) << 4) | (dec % 10); } void HardwareRTC::readRegisters(uint8_t* buffer, uint8_t length) { Wire.beginTransmission(DS1307_ADDRESS); Wire.write(0x00); // start at register 0 Wire.endTransmission(); Wire.requestFrom(DS1307_ADDRESS, length); for (uint8_t i = 0; i < length; i++) { buffer[i] = Wire.read(); } } void HardwareRTC::writeRegisters(uint8_t* buffer, uint8_t length) { Wire.beginTransmission(DS1307_ADDRESS); Wire.write(0x00); // start at register 0 for (uint8_t i = 0; i < length; i++) { Wire.write(buffer[i]); } Wire.endTransmission(); }