From aa371c22a4a64d22e5ee75b47f8ab3d893a40439 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Odd=20Str=C3=A5b=C3=B8?= <oddstr13@openshell.no>
Date: Thu, 17 Aug 2023 00:21:56 +0200
Subject: [PATCH] Add pcf85063.start_timer action

---
 esphome/components/pcf85063/pcf85063.cpp | 36 +++++++++++++++++++++++
 esphome/components/pcf85063/pcf85063.h   | 14 +++++++++
 esphome/components/pcf85063/time.py      | 37 +++++++++++++++++++++++-
 3 files changed, 86 insertions(+), 1 deletion(-)

diff --git a/esphome/components/pcf85063/pcf85063.cpp b/esphome/components/pcf85063/pcf85063.cpp
index 941278d..da7d82d 100644
--- a/esphome/components/pcf85063/pcf85063.cpp
+++ b/esphome/components/pcf85063/pcf85063.cpp
@@ -91,6 +91,42 @@ uint8_t PCF85063Component::read_nvram() {
   return this->pcf85063_.reg.nvram;
 }
 
+/*
+  TIMER_CLOCK_MINUTE      60    15300     1m     4h15m
+  TIMER_CLOCK_SECOND       1      255     1s     4m15s
+*/
+bool PCF85063Component::set_timer_interval(uint16_t interval_seconds) {
+  if (interval_seconds < 256) {
+    pcf85063_.reg.timer_clock_frequency = TIMER_CLOCK_SECOND;
+    pcf85063_.reg.timer_value = interval_seconds;
+    pcf85063_.reg.timer_enable = true;
+    return this->write_timer_();
+  } else if (interval_seconds > 15300) {
+    ESP_LOGE(TAG, "Specified interval is longer than max allowed, clamping to 4h15m.");
+    interval_seconds = 15300;
+  }
+  div_t dm = div(interval_seconds, 60);
+  if (dm.rem) {
+    ESP_LOGI(TAG, "Interval out of seconds range, rounding down to closest whole minute.");
+  }
+  
+  pcf85063_.reg.timer_clock_frequency = TIMER_CLOCK_MINUTE;
+  pcf85063_.reg.timer_value = dm.quot;
+  pcf85063_.reg.timer_enable = true;
+  return this->write_timer_();
+}
+
+bool PCF85063Component::write_timer_() {
+  if (!this->write_bytes(0x10, &this->pcf85063_.raw[0x03], 2)) {
+    ESP_LOGE(TAG, "Can't write I2C data.");
+    return false;
+  }
+  ESP_LOGD(TAG, "Write timer %s %0u CLOCK=%0u IR=%s %0u",
+           ONOFF(pcf85063_.reg.timer_enable), pcf85063_.reg.timer_value, pcf85063_.reg.timer_clock_frequency,
+           ONOFF(pcf85063_.reg.timer_interrupt_enable), pcf85063_.reg.timer_interrupt_mode);
+  return true;
+}
+
 bool PCF85063Component::read_rtc_() {
   if (!this->read_bytes(0, this->pcf85063_.raw, sizeof(this->pcf85063_.raw))) {
     ESP_LOGE(TAG, "Can't read I2C data.");
diff --git a/esphome/components/pcf85063/pcf85063.h b/esphome/components/pcf85063/pcf85063.h
index 3d3589c..48e830d 100644
--- a/esphome/components/pcf85063/pcf85063.h
+++ b/esphome/components/pcf85063/pcf85063.h
@@ -30,7 +30,11 @@ class PCF85063Component : public time::RealTimeClock, public i2c::I2CDevice {
   void write_nvram(uint8_t);
   uint8_t read_nvram();
 
+  bool set_timer_interval(uint16_t interval);
+
+
  protected:
+  bool write_timer_();
   bool read_rtc_();
   bool write_rtc_();
   union PCF85063Reg {
@@ -135,6 +139,16 @@ class PCF85063Component : public time::RealTimeClock, public i2c::I2CDevice {
   } pcf85063_;
 };
 
+template<typename... Ts> class StartTimerAction : public Action<Ts...>, public Parented<PCF85063Component> {
+ public:
+  TEMPLATABLE_VALUE(uint16_t, timer_seconds);
+  void play(Ts... x) override {
+    this->parent_->pcf85063_.reg.timer_interrupt_enable = true;
+    this->parent_->pcf85063_.reg.timer_interrupt_mode = TIMER_INTERRUPT_MODE_FLAG;
+    this->parent_->set_timer_interval(this->timer_seconds_.value(x...));
+  }
+};
+
 template<typename... Ts> class WriteAction : public Action<Ts...>, public Parented<PCF85063Component> {
  public:
   void play(Ts... x) override { this->parent_->write_time(); }
diff --git a/esphome/components/pcf85063/time.py b/esphome/components/pcf85063/time.py
index 67ec230..43a9a06 100644
--- a/esphome/components/pcf85063/time.py
+++ b/esphome/components/pcf85063/time.py
@@ -2,7 +2,7 @@ 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
+from esphome.const import CONF_ID, CONF_INTERVAL
 
 
 CODEOWNERS = ["@brogon"]
@@ -13,6 +13,7 @@ PCF85063Component = pcf85063_ns.class_(
 )
 WriteAction = pcf85063_ns.class_("WriteAction", automation.Action)
 ReadAction = pcf85063_ns.class_("ReadAction", automation.Action)
+StartTimerAction = pcf85063_ns.class_("StartTimerAction", automation.Action)
 
 
 CONFIG_SCHEMA = time.TIME_SCHEMA.extend(
@@ -52,6 +53,40 @@ async def pcf85063_read_time_to_code(config, action_id, template_arg, args):
     return var
 
 
+def validate_timer_seconds(value):
+    value: cv.TimePeriodSeconds = cv.positive_time_period_seconds(value)
+    min_interval = cv.TimePeriod(seconds=1)
+    max_interval = cv.TimePeriod(minutes=255)
+
+    if value < min_interval:
+        raise cv.Invalid(
+            f"This timer interval is not possible, please choose a larger interval (at least {min_interval})"
+        )
+    if value > max_interval:
+        raise cv.Invalid(
+            f"This timer interval is not possible, please choose a lower interval (at most {max_interval})"
+        )
+    return value
+
+@automation.register_action(
+    "pcf85063.start_timer",
+    StartTimerAction,
+    cv.Schema(
+        {
+            cv.GenerateID(): cv.use_id(PCF85063Component),
+            cv.Required(CONF_INTERVAL): cv.templatable(validate_timer_seconds),
+        }
+    ),
+)
+async def pcf85063_start_timer_to_code(config, action_id, template_arg, args):
+    var = cg.new_Pvariable(action_id, template_arg)
+
+    template_ = await cg.templatable(config[CONF_INTERVAL], args, cv.TimePeriodSeconds)
+    cg.add(var.start_timer(template_))
+
+    await cg.register_parented(var, config[CONF_ID])
+    return var
+
 async def to_code(config):
     var = cg.new_Pvariable(config[CONF_ID])