From a6deae8b326864d52fb0e5064f93e3b1575292d1 Mon Sep 17 00:00:00 2001 From: brogon Date: Fri, 23 Dec 2022 00:33:31 +0100 Subject: [PATCH] PCF85063 RTC chip (#3873) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/pcf85063/__init__.py | 0 esphome/components/pcf85063/pcf85063.cpp | 105 +++++++++++++++++++++++ esphome/components/pcf85063/pcf85063.h | 96 +++++++++++++++++++++ esphome/components/pcf85063/time.py | 60 +++++++++++++ 4 files changed, 261 insertions(+) create mode 100644 esphome/components/pcf85063/__init__.py create mode 100644 esphome/components/pcf85063/pcf85063.cpp create mode 100644 esphome/components/pcf85063/pcf85063.h create mode 100644 esphome/components/pcf85063/time.py diff --git a/esphome/components/pcf85063/__init__.py b/esphome/components/pcf85063/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/esphome/components/pcf85063/pcf85063.cpp b/esphome/components/pcf85063/pcf85063.cpp new file mode 100644 index 0000000..c6a8624 --- /dev/null +++ b/esphome/components/pcf85063/pcf85063.cpp @@ -0,0 +1,105 @@ +#include "pcf85063.h" +#include "esphome/core/log.h" + +// Datasheet: +// - https://datasheets.maximintegrated.com/en/ds/DS1307.pdf + +namespace esphome { +namespace pcf85063 { + +static const char *const TAG = "pcf85063"; + +void PCF85063Component::setup() { + ESP_LOGCONFIG(TAG, "Setting up PCF85063..."); + if (!this->read_rtc_()) { + this->mark_failed(); + } +} + +void PCF85063Component::update() { this->read_time(); } + +void PCF85063Component::dump_config() { + ESP_LOGCONFIG(TAG, "PCF85063:"); + LOG_I2C_DEVICE(this); + if (this->is_failed()) { + ESP_LOGE(TAG, "Communication with PCF85063 failed!"); + } + ESP_LOGCONFIG(TAG, " Timezone: '%s'", this->timezone_.c_str()); +} + +float PCF85063Component::get_setup_priority() const { return setup_priority::DATA; } + +void PCF85063Component::read_time() { + if (!this->read_rtc_()) { + return; + } + if (pcf85063_.reg.osc_stop) { + ESP_LOGW(TAG, "RTC halted, not syncing to system clock."); + return; + } + time::ESPTime rtc_time{.second = uint8_t(pcf85063_.reg.second + 10 * pcf85063_.reg.second_10), + .minute = uint8_t(pcf85063_.reg.minute + 10u * pcf85063_.reg.minute_10), + .hour = uint8_t(pcf85063_.reg.hour + 10u * pcf85063_.reg.hour_10), + .day_of_week = uint8_t(pcf85063_.reg.weekday), + .day_of_month = uint8_t(pcf85063_.reg.day + 10u * pcf85063_.reg.day_10), + .day_of_year = 1, // ignored by recalc_timestamp_utc(false) + .month = uint8_t(pcf85063_.reg.month + 10u * pcf85063_.reg.month_10), + .year = uint16_t(pcf85063_.reg.year + 10u * pcf85063_.reg.year_10 + 2000)}; + rtc_time.recalc_timestamp_utc(false); + if (!rtc_time.is_valid()) { + ESP_LOGE(TAG, "Invalid RTC time, not syncing to system clock."); + return; + } + time::RealTimeClock::synchronize_epoch_(rtc_time.timestamp); +} + +void PCF85063Component::write_time() { + auto now = time::RealTimeClock::utcnow(); + if (!now.is_valid()) { + ESP_LOGE(TAG, "Invalid system time, not syncing to RTC."); + return; + } + pcf85063_.reg.year = (now.year - 2000) % 10; + pcf85063_.reg.year_10 = (now.year - 2000) / 10 % 10; + pcf85063_.reg.month = now.month % 10; + pcf85063_.reg.month_10 = now.month / 10; + pcf85063_.reg.day = now.day_of_month % 10; + pcf85063_.reg.day_10 = now.day_of_month / 10; + pcf85063_.reg.weekday = now.day_of_week; + pcf85063_.reg.hour = now.hour % 10; + pcf85063_.reg.hour_10 = now.hour / 10; + pcf85063_.reg.minute = now.minute % 10; + pcf85063_.reg.minute_10 = now.minute / 10; + pcf85063_.reg.second = now.second % 10; + pcf85063_.reg.second_10 = now.second / 10; + pcf85063_.reg.osc_stop = false; + + this->write_rtc_(); +} + +bool PCF85063Component::read_rtc_() { + if (!this->read_bytes(0, this->pcf85063_.raw, sizeof(this->pcf85063_.raw))) { + ESP_LOGE(TAG, "Can't read I2C data."); + return false; + } + ESP_LOGD(TAG, "Read %0u%0u:%0u%0u:%0u%0u 20%0u%0u-%0u%0u-%0u%0u OSC:%s CLKOUT:%0u", pcf85063_.reg.hour_10, + pcf85063_.reg.hour, pcf85063_.reg.minute_10, pcf85063_.reg.minute, pcf85063_.reg.second_10, + pcf85063_.reg.second, pcf85063_.reg.year_10, pcf85063_.reg.year, pcf85063_.reg.month_10, pcf85063_.reg.month, + pcf85063_.reg.day_10, pcf85063_.reg.day, ONOFF(!pcf85063_.reg.osc_stop), pcf85063_.reg.clkout_control); + + return true; +} + +bool PCF85063Component::write_rtc_() { + if (!this->write_bytes(0, this->pcf85063_.raw, sizeof(this->pcf85063_.raw))) { + ESP_LOGE(TAG, "Can't write I2C data."); + return false; + } + ESP_LOGD(TAG, "Write %0u%0u:%0u%0u:%0u%0u 20%0u%0u-%0u%0u-%0u%0u OSC:%s CLKOUT:%0u", pcf85063_.reg.hour_10, + pcf85063_.reg.hour, pcf85063_.reg.minute_10, pcf85063_.reg.minute, pcf85063_.reg.second_10, + pcf85063_.reg.second, pcf85063_.reg.year_10, pcf85063_.reg.year, pcf85063_.reg.month_10, pcf85063_.reg.month, + pcf85063_.reg.day_10, pcf85063_.reg.day, ONOFF(!pcf85063_.reg.osc_stop), pcf85063_.reg.clkout_control); + return true; +} +} // namespace pcf85063 +} // namespace esphome diff --git a/esphome/components/pcf85063/pcf85063.h b/esphome/components/pcf85063/pcf85063.h new file mode 100644 index 0000000..1a3fd70 --- /dev/null +++ b/esphome/components/pcf85063/pcf85063.h @@ -0,0 +1,96 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/i2c/i2c.h" +#include "esphome/components/time/real_time_clock.h" + +namespace esphome { +namespace pcf85063 { + +class PCF85063Component : public time::RealTimeClock, public i2c::I2CDevice { + public: + void setup() override; + void update() override; + void dump_config() override; + float get_setup_priority() const override; + void read_time(); + void write_time(); + + protected: + bool read_rtc_(); + bool write_rtc_(); + union PCF85063Reg { + struct { + // Control_1 register + bool cap_12pf : 1; + bool am_pm : 1; + bool correction_int_enable : 1; + bool : 1; + bool soft_reset : 1; + bool stop : 1; + bool : 1; + bool ext_test : 1; + + // Control_2 register + uint8_t clkout_control : 3; + bool timer_flag : 1; + bool halfminute_int : 1; + bool minute_int : 1; + bool alarm_flag : 1; + bool alarm_int : 1; + + // Offset register + uint8_t offset : 7; + bool coarse_mode : 1; + + // nvRAM register + uint8_t nvram : 8; + + // Seconds register + uint8_t second : 4; + uint8_t second_10 : 3; + bool osc_stop : 1; + + // Minutes register + uint8_t minute : 4; + uint8_t minute_10 : 3; + uint8_t : 1; + + // Hours register + uint8_t hour : 4; + uint8_t hour_10 : 2; + uint8_t : 2; + + // Days register + uint8_t day : 4; + uint8_t day_10 : 2; + uint8_t : 2; + + // Weekdays register + uint8_t weekday : 3; + uint8_t unused_3 : 5; + + // Months register + uint8_t month : 4; + uint8_t month_10 : 1; + uint8_t : 3; + + // Years register + uint8_t year : 4; + uint8_t year_10 : 4; + } reg; + mutable uint8_t raw[sizeof(reg)]; + } pcf85063_; +}; + +template class WriteAction : public Action, public Parented { + public: + void play(Ts... x) override { this->parent_->write_time(); } +}; + +template class ReadAction : public Action, public Parented { + public: + void play(Ts... x) override { this->parent_->read_time(); } +}; +} // namespace pcf85063 +} // namespace esphome diff --git a/esphome/components/pcf85063/time.py b/esphome/components/pcf85063/time.py new file mode 100644 index 0000000..67ec230 --- /dev/null +++ b/esphome/components/pcf85063/time.py @@ -0,0 +1,60 @@ +import esphome.config_validation as cv +import esphome.codegen as cg +from esphome import automation +from esphome.components import i2c, time +from esphome.const import CONF_ID + + +CODEOWNERS = ["@brogon"] +DEPENDENCIES = ["i2c"] +pcf85063_ns = cg.esphome_ns.namespace("pcf85063") +PCF85063Component = pcf85063_ns.class_( + "PCF85063Component", time.RealTimeClock, i2c.I2CDevice +) +WriteAction = pcf85063_ns.class_("WriteAction", automation.Action) +ReadAction = pcf85063_ns.class_("ReadAction", automation.Action) + + +CONFIG_SCHEMA = time.TIME_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(PCF85063Component), + } +).extend(i2c.i2c_device_schema(0x51)) + + +@automation.register_action( + "pcf85063.write_time", + WriteAction, + cv.Schema( + { + cv.GenerateID(): cv.use_id(PCF85063Component), + } + ), +) +async def pcf85063_write_time_to_code(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + return var + + +@automation.register_action( + "pcf85063.read_time", + ReadAction, + automation.maybe_simple_id( + { + cv.GenerateID(): cv.use_id(PCF85063Component), + } + ), +) +async def pcf85063_read_time_to_code(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + return var + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + await time.register_time(var, config)