mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00:00 
			
		
		
		
	RTC implementation of pcf8563 (#4998)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
		| @@ -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 | ||||
|   | ||||
							
								
								
									
										0
									
								
								esphome/components/pcf8563/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/pcf8563/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										109
									
								
								esphome/components/pcf8563/pcf8563.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								esphome/components/pcf8563/pcf8563.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
							
								
								
									
										124
									
								
								esphome/components/pcf8563/pcf8563.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								esphome/components/pcf8563/pcf8563.h
									
									
									
									
									
										Normal file
									
								
							| @@ -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<typename... Ts> class WriteAction : public Action<Ts...>, public Parented<PCF8563Component> { | ||||
|  public: | ||||
|   void play(Ts... x) override { this->parent_->write_time(); } | ||||
| }; | ||||
|  | ||||
| template<typename... Ts> class ReadAction : public Action<Ts...>, public Parented<PCF8563Component> { | ||||
|  public: | ||||
|   void play(Ts... x) override { this->parent_->read_time(); } | ||||
| }; | ||||
| }  // namespace pcf8563 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										62
									
								
								esphome/components/pcf8563/time.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								esphome/components/pcf8563/time.py
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||
| @@ -590,6 +590,7 @@ display: | ||||
|  | ||||
| time: | ||||
|   - platform: pcf85063 | ||||
|   - platform: pcf8563 | ||||
|  | ||||
| text_sensor: | ||||
|   - platform: ezo_pmp | ||||
|   | ||||
		Reference in New Issue
	
	Block a user