diff --git a/CODEOWNERS b/CODEOWNERS index 68a8eb6d18..82612fbd4a 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -201,6 +201,7 @@ esphome/components/output/* @esphome/core esphome/components/pca6416a/* @Mat931 esphome/components/pca9554/* @hwstar esphome/components/pcf85063/* @brogon +esphome/components/pcf8563/* @KoenBreeman esphome/components/pid/* @OttoWinter esphome/components/pipsolar/* @andreashergert1984 esphome/components/pm1006/* @habbie diff --git a/esphome/components/pcf8563/__init__.py b/esphome/components/pcf8563/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/pcf8563/pcf8563.cpp b/esphome/components/pcf8563/pcf8563.cpp new file mode 100644 index 0000000000..f2a82735c5 --- /dev/null +++ b/esphome/components/pcf8563/pcf8563.cpp @@ -0,0 +1,109 @@ +#include "pcf8563.h" +#include "esphome/core/log.h" + +// Datasheet: +// - https://nl.mouser.com/datasheet/2/302/PCF8563-1127619.pdf + +namespace esphome { +namespace pcf8563 { + +static const char *const TAG = "PCF8563"; + +void PCF8563Component::setup() { + ESP_LOGCONFIG(TAG, "Setting up PCF8563..."); + if (!this->read_rtc_()) { + this->mark_failed(); + } +} + +void PCF8563Component::update() { this->read_time(); } + +void PCF8563Component::dump_config() { + ESP_LOGCONFIG(TAG, "PCF8563:"); + LOG_I2C_DEVICE(this); + if (this->is_failed()) { + ESP_LOGE(TAG, "Communication with PCF8563 failed!"); + } + ESP_LOGCONFIG(TAG, " Timezone: '%s'", this->timezone_.c_str()); +} + +float PCF8563Component::get_setup_priority() const { return setup_priority::DATA; } + +void PCF8563Component::read_time() { + if (!this->read_rtc_()) { + return; + } + if (pcf8563_.reg.stop) { + ESP_LOGW(TAG, "RTC halted, not syncing to system clock."); + return; + } + ESPTime rtc_time{ + .second = uint8_t(pcf8563_.reg.second + 10 * pcf8563_.reg.second_10), + .minute = uint8_t(pcf8563_.reg.minute + 10u * pcf8563_.reg.minute_10), + .hour = uint8_t(pcf8563_.reg.hour + 10u * pcf8563_.reg.hour_10), + .day_of_week = uint8_t(pcf8563_.reg.weekday), + .day_of_month = uint8_t(pcf8563_.reg.day + 10u * pcf8563_.reg.day_10), + .day_of_year = 1, // ignored by recalc_timestamp_utc(false) + .month = uint8_t(pcf8563_.reg.month + 10u * pcf8563_.reg.month_10), + .year = uint16_t(pcf8563_.reg.year + 10u * pcf8563_.reg.year_10 + 2000), + .is_dst = false, // not used + .timestamp = 0, // overwritten by recalc_timestamp_utc(false) + }; + 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 PCF8563Component::write_time() { + auto now = time::RealTimeClock::utcnow(); + if (!now.is_valid()) { + ESP_LOGE(TAG, "Invalid system time, not syncing to RTC."); + return; + } + pcf8563_.reg.year = (now.year - 2000) % 10; + pcf8563_.reg.year_10 = (now.year - 2000) / 10 % 10; + pcf8563_.reg.month = now.month % 10; + pcf8563_.reg.month_10 = now.month / 10; + pcf8563_.reg.day = now.day_of_month % 10; + pcf8563_.reg.day_10 = now.day_of_month / 10; + pcf8563_.reg.weekday = now.day_of_week; + pcf8563_.reg.hour = now.hour % 10; + pcf8563_.reg.hour_10 = now.hour / 10; + pcf8563_.reg.minute = now.minute % 10; + pcf8563_.reg.minute_10 = now.minute / 10; + pcf8563_.reg.second = now.second % 10; + pcf8563_.reg.second_10 = now.second / 10; + pcf8563_.reg.stop = false; + + this->write_rtc_(); +} + +bool PCF8563Component::read_rtc_() { + if (!this->read_bytes(0, this->pcf8563_.raw, sizeof(this->pcf8563_.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 STOP:%s CLKOUT:%0u", pcf8563_.reg.hour_10, + pcf8563_.reg.hour, pcf8563_.reg.minute_10, pcf8563_.reg.minute, pcf8563_.reg.second_10, pcf8563_.reg.second, + pcf8563_.reg.year_10, pcf8563_.reg.year, pcf8563_.reg.month_10, pcf8563_.reg.month, pcf8563_.reg.day_10, + pcf8563_.reg.day, ONOFF(!pcf8563_.reg.stop), pcf8563_.reg.clkout_enabled); + + return true; +} + +bool PCF8563Component::write_rtc_() { + if (!this->write_bytes(0, this->pcf8563_.raw, sizeof(this->pcf8563_.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", pcf8563_.reg.hour_10, + pcf8563_.reg.hour, pcf8563_.reg.minute_10, pcf8563_.reg.minute, pcf8563_.reg.second_10, pcf8563_.reg.second, + pcf8563_.reg.year_10, pcf8563_.reg.year, pcf8563_.reg.month_10, pcf8563_.reg.month, pcf8563_.reg.day_10, + pcf8563_.reg.day, ONOFF(!pcf8563_.reg.stop), pcf8563_.reg.clkout_enabled); + return true; +} +} // namespace pcf8563 +} // namespace esphome diff --git a/esphome/components/pcf8563/pcf8563.h b/esphome/components/pcf8563/pcf8563.h new file mode 100644 index 0000000000..b6832efe72 --- /dev/null +++ b/esphome/components/pcf8563/pcf8563.h @@ -0,0 +1,124 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/i2c/i2c.h" +#include "esphome/components/time/real_time_clock.h" + +namespace esphome { +namespace pcf8563 { + +class PCF8563Component : 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 PCF8563Reg { + struct { + // Control_1 register + bool : 3; + bool power_on_reset : 1; + bool : 1; + bool stop : 1; + bool : 1; + bool ext_test : 1; + + // Control_2 register + bool time_int : 1; + bool alarm_int : 1; + bool timer_flag : 1; + bool alarm_flag : 1; + bool timer_int_timer_pulse : 1; + bool : 3; + + // Seconds register + uint8_t second : 4; + uint8_t second_10 : 3; + bool clock_int : 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 : 2; + uint8_t century : 1; + + // Years register + uint8_t year : 4; + uint8_t year_10 : 4; + + // Minute Alarm register + uint8_t minute_alarm : 4; + uint8_t minute_alarm_10 : 3; + bool minute_alarm_enabled : 1; + + // Hour Alarm register + uint8_t hour_alarm : 4; + uint8_t hour_alarm_10 : 2; + uint8_t : 1; + bool hour_alarm_enabled : 1; + + // Day Alarm register + uint8_t day_alarm : 4; + uint8_t day_alarm_10 : 2; + uint8_t : 1; + bool day_alarm_enabled : 1; + + // Weekday Alarm register + uint8_t weekday_alarm : 3; + uint8_t : 4; + bool weekday_alarm_enabled : 1; + + // CLKout control register + uint8_t frequency_output : 2; + uint8_t : 5; + uint8_t clkout_enabled : 1; + + // Timer control register + uint8_t timer_source_frequency : 2; + uint8_t : 5; + uint8_t timer_enabled : 1; + + // Timer register + uint8_t countdown_period : 8; + + } reg; + mutable uint8_t raw[sizeof(reg)]; + } pcf8563_; +}; + +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 pcf8563 +} // namespace esphome diff --git a/esphome/components/pcf8563/time.py b/esphome/components/pcf8563/time.py new file mode 100644 index 0000000000..2e6456a72d --- /dev/null +++ b/esphome/components/pcf8563/time.py @@ -0,0 +1,62 @@ +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 = ["@KoenBreeman"] + +DEPENDENCIES = ["i2c"] + + +pcf8563_ns = cg.esphome_ns.namespace("pcf8563") +pcf8563Component = pcf8563_ns.class_( + "PCF8563Component", time.RealTimeClock, i2c.I2CDevice +) +WriteAction = pcf8563_ns.class_("WriteAction", automation.Action) +ReadAction = pcf8563_ns.class_("ReadAction", automation.Action) + + +CONFIG_SCHEMA = time.TIME_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(pcf8563Component), + } +).extend(i2c.i2c_device_schema(0xA3)) + + +@automation.register_action( + "pcf8563.write_time", + WriteAction, + automation.maybe_simple_id( + { + cv.GenerateID(): cv.use_id(pcf8563Component), + } + ), +) +async def pcf8563_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( + "pcf8563.read_time", + ReadAction, + automation.maybe_simple_id( + { + cv.GenerateID(): cv.use_id(pcf8563Component), + } + ), +) +async def pcf8563_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) diff --git a/tests/test5.yaml b/tests/test5.yaml index cb4b559b06..a2530d799a 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -590,6 +590,7 @@ display: time: - platform: pcf85063 + - platform: pcf8563 text_sensor: - platform: ezo_pmp