mirror of
https://github.com/esphome/esphome.git
synced 2024-11-27 17:27:59 +01:00
DS3232 RTC crystal support + test of functionality
* Basic RTC function (like DS1307) * Two hardware-based alarms / Stable 1Hz oscillator * NVRAM storage for numeric variables (limited experimental support) * Internal temperature sensor support
This commit is contained in:
parent
5e9741f51c
commit
4e14a0b8c0
8 changed files with 2291 additions and 0 deletions
0
esphome/components/ds3232/__init__.py
Normal file
0
esphome/components/ds3232/__init__.py
Normal file
660
esphome/components/ds3232/ds3232.cpp
Normal file
660
esphome/components/ds3232/ds3232.cpp
Normal file
|
@ -0,0 +1,660 @@
|
|||
#include "ds3232.h"
|
||||
#include "ds3232_alarm.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
// Datasheet:
|
||||
// - https://www.analog.com/media/en/technical-documentation/data-sheets/ds3232m.pdf
|
||||
|
||||
namespace esphome {
|
||||
namespace ds3232 {
|
||||
|
||||
namespace ds3232alarms {} // namespace ds3232alarms
|
||||
|
||||
static const char *const TAG = "ds3232";
|
||||
|
||||
static const char *const DOW_TEXT = "day of week";
|
||||
static const char *const DOM_TEXT = "day of month";
|
||||
|
||||
/// Factor to calculate temperature of DS3232
|
||||
static const float TEMPERATURE_FACTOR = 0.25;
|
||||
|
||||
static const uint8_t I2C_REG_RTC = 0x00;
|
||||
static const uint8_t I2C_REG_ALARM_1 = 0x07;
|
||||
static const uint8_t I2C_REG_ALARM_2 = 0x0B;
|
||||
static const uint8_t I2C_REG_CONTROL = 0x0E;
|
||||
static const uint8_t I2C_REG_STATUS = 0x0F;
|
||||
static const uint8_t I2C_REG_AGING = 0x10;
|
||||
static const uint8_t I2C_REG_TEMPERATURE = 0x11;
|
||||
static const uint8_t I2C_REG_START = I2C_REG_RTC;
|
||||
|
||||
void DS3232Component::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up DS3232...");
|
||||
this->power_state_.next(DS3232PowerState::UNKNOWN);
|
||||
|
||||
if (!this->read_data_()) {
|
||||
ESP_LOGE(TAG, "Unable to read data from crystal.");
|
||||
this->mark_failed();
|
||||
} else {
|
||||
ESP_LOGV(TAG, "Checking for oscillator state.");
|
||||
this->reinit_osf_();
|
||||
}
|
||||
|
||||
this->nvram_available_ = this->read_bytes(SVC_NVRAM_ADDRESS, this->nvram_info_.raw, sizeof(this->nvram_info_.raw));
|
||||
|
||||
if (!this->nvram_available_) {
|
||||
ESP_LOGE(TAG, "NVRAM: Unable to access NVRAM. Marking NVRAM functionality as failed.");
|
||||
this->nvram_state_ = DS3232NVRAMState::FAIL;
|
||||
this->variable_fail_callback_.call();
|
||||
} else {
|
||||
if ((this->nvram_info_.info.magix_1 == ds3232::MAGIX_CONTROL_1) &&
|
||||
(this->nvram_info_.info.magix_2 == ds3232::MAGIX_CONTROL_2)) {
|
||||
ESP_LOGI(TAG, "NVRAM usage has been detected.");
|
||||
if (!this->nvram_info_.info.is_initialized) {
|
||||
ESP_LOGI(TAG, "NVRAM will be written with initial values.");
|
||||
this->plan_reset_nvram_();
|
||||
} else {
|
||||
ESP_LOGI(TAG, "Found NVRAM. Data version: %.2u.%.1u", this->nvram_info_.info.maj_version,
|
||||
this->nvram_info_.info.min_version);
|
||||
if ((this->nvram_info_.info.maj_version * 10 + this->nvram_info_.info.min_version) >
|
||||
ds3232::NVRAM_DATA_VERSION) {
|
||||
ESP_LOGW(TAG,
|
||||
"Supported version (%.2u.%.1u) lower than stored version. Behaviour could be unpredictable. "
|
||||
"Consider to reset NVRAM.",
|
||||
ds3232::NVRAM_DATA_MAJ_VERSION, ds3232::NVRAM_DATA_MIN_VERSION);
|
||||
}
|
||||
this->nvram_state_ = DS3232NVRAMState::OK;
|
||||
}
|
||||
} else {
|
||||
ESP_LOGI(TAG,
|
||||
"Non-complaint data in NVRAM has been detected. NVRAM will be initialized. This will take some time.");
|
||||
this->plan_clear_nvram_();
|
||||
};
|
||||
ESP_LOGD(TAG, "DS3232 NVRAM initialization complete");
|
||||
this->late_startup_ = false;
|
||||
}
|
||||
|
||||
// Workaround for states when alarms fired when ESPHome device was unpowered but DS3232 was online.
|
||||
// There is a problem with A1F / A2F registers - INT signal will not be issued when this registers already set to 1.
|
||||
// So there is a need to reset them to 0 on startup.
|
||||
if (this->reg_data_.reg.alarm_1_match || this->reg_data_.reg.alarm_2_match) {
|
||||
if (this->fire_alarms_on_startup_) {
|
||||
process_alarms_();
|
||||
} else {
|
||||
reg_data_.reg.alarm_1_match = false;
|
||||
reg_data_.reg.alarm_2_match = false;
|
||||
this->write_bytes(I2C_REG_STATUS, reg_data_.raw_blocks.status_raw, sizeof(reg_data_.raw_blocks.status_raw));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void DS3232Component::update() {
|
||||
if(this->late_startup_)
|
||||
return;
|
||||
this->read_time();
|
||||
this->read_temperature_();
|
||||
}
|
||||
|
||||
// Need to initialize afted i2c bus.
|
||||
// I don't know why but on my ESP32 board i2c bus initialized and
|
||||
// ready for connection too late. As a result sometimes
|
||||
// component marked as failed.
|
||||
float DS3232Component::get_setup_priority() const { return setup_priority::DATA - 200; }
|
||||
|
||||
void DS3232Component::reinit_osf_() {
|
||||
this->read_data_();
|
||||
if(!this->reg_data_.reg.osf_bit)
|
||||
return;
|
||||
ESP_LOGD(TAG, "Found disabled oscillator. Restarting it.");
|
||||
this->reg_data_.reg.osf_bit = false;
|
||||
if(!this->write_byte(I2C_REG_STATUS, this->reg_data_.raw_blocks.status_raw[0]))
|
||||
{
|
||||
ESP_LOGE(TAG, "Unable to restart oscillator.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void DS3232Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "DS3232");
|
||||
LOG_PIN(" Interrupt / Heartbeat pin: ", this->int_pin_);
|
||||
LOG_PIN(" Reset / Power pin: ", this->rst_pin_);
|
||||
LOG_I2C_DEVICE(this);
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, "Communication with DS3232 failed!");
|
||||
} else {
|
||||
this->read_data_();
|
||||
ESP_LOGCONFIG(TAG, " Timezone: '%s'", this->timezone_.c_str());
|
||||
ESP_LOGCONFIG(TAG, " Internal config from chip:");
|
||||
ESP_LOGCONFIG(TAG, " Ocsillation active: %s", TRUEFALSE(!this->reg_data_.reg.osf_bit));
|
||||
ESP_LOGCONFIG(TAG, " Heartbeat mode: %s", ONOFF(!this->reg_data_.reg.int_enable));
|
||||
ESP_LOGCONFIG(TAG, " Alarms mode: %s", ONOFF(this->reg_data_.reg.int_enable));
|
||||
ds3232_alarm::DS3232Alarm alarm;
|
||||
alarm = this->get_alarm_one();
|
||||
if (alarm.enabled) {
|
||||
ESP_LOGCONFIG(TAG, " Alarm 1: %s", this->reg_data_.reg.int_enable ? "ACTIVE" : "SUSPENDED");
|
||||
ESP_LOGCONFIG(TAG, " Start on %s", alarm.to_string().c_str());
|
||||
ESP_LOGCONFIG(TAG, " Matched: %s", TRUEFALSE(this->reg_data_.reg.alarm_1_match));
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, " Alarm 1: INACTIVE");
|
||||
};
|
||||
alarm = this->get_alarm_two();
|
||||
if (alarm.enabled) {
|
||||
ESP_LOGCONFIG(TAG, " Alarm 2: %s", this->reg_data_.reg.int_enable ? "ACTIVE" : "SUSPENDED");
|
||||
ESP_LOGCONFIG(TAG, " Start on %s", alarm.to_string().c_str());
|
||||
ESP_LOGCONFIG(TAG, " Matched: %s", TRUEFALSE(this->reg_data_.reg.alarm_2_match));
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, " Alarm 2: INACTIVE");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DS3232Component::loop() {
|
||||
auto pow_pin_state = DS3232PowerState::UNKNOWN;
|
||||
if (this->rst_pin_ != nullptr) {
|
||||
pow_pin_state = this->rst_pin_->digital_read() ? DS3232PowerState::ONLINE : DS3232PowerState::BATTERY;
|
||||
}
|
||||
if (this->power_state_.next(pow_pin_state)) {
|
||||
ESP_LOGI(TAG, "Power source changed: %u", pow_pin_state);
|
||||
this->power_state_callback_.call(pow_pin_state);
|
||||
}
|
||||
if (this->int_pin_ != nullptr) {
|
||||
auto pin_state = this->int_pin_->digital_read();
|
||||
if ((this->pin_state_ != pin_state) && !pin_state) {
|
||||
ESP_LOGD(TAG, "Got active low on INT pin.");
|
||||
this->process_interrupt_();
|
||||
}
|
||||
this->pin_state_ = pin_state;
|
||||
}
|
||||
switch (this->nvram_state_)
|
||||
{
|
||||
case DS3232NVRAMState::NEED_INITIALIZATION:
|
||||
if(this->busy_) {
|
||||
ESP_LOGI(TAG, "Performing planned NVRAM full reset...");
|
||||
clear_nvram_();
|
||||
}
|
||||
break;
|
||||
case DS3232NVRAMState::NEED_RESET:
|
||||
if(this->busy_) {
|
||||
ESP_LOGI(TAG, "Performing planned NVRAM factory reset...");
|
||||
this->nvram_state_ = DS3232NVRAMState::OK;
|
||||
this->variable_init_callback_.call();
|
||||
if(this->nvram_state_ != DS3232NVRAMState::OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "NVRAM: Failed to reset to factory defaults.");
|
||||
}
|
||||
this->busy_ = false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void DS3232Component::plan_clear_nvram_() {
|
||||
this->busy_ = true;
|
||||
this->nvram_state_ = DS3232NVRAMState::NEED_INITIALIZATION;
|
||||
}
|
||||
|
||||
void DS3232Component::plan_reset_nvram_() {
|
||||
this->busy_ = true;
|
||||
this->nvram_state_ = DS3232NVRAMState::NEED_RESET;
|
||||
}
|
||||
|
||||
void DS3232Component::clear_nvram_() {
|
||||
const uint8_t write_len = 8;
|
||||
|
||||
this->nvram_state_ = DS3232NVRAMState::INITIALIZATION;
|
||||
ESP_LOGD(TAG, "NVRAM: Begin to clear memory.");
|
||||
uint8_t total_size = MAX_NVRAM_ADDRESS - MIN_NVRAM_ADDRESS + 1;
|
||||
ESP_LOGD(TAG, "NVRAM: Total size to clear: %.2d byte(s)", total_size);
|
||||
uint8_t step = 0;
|
||||
bool state = true;
|
||||
std::vector<uint8_t> zeroes;
|
||||
zeroes.resize(write_len_, 0);
|
||||
|
||||
while (step < total_size)
|
||||
{
|
||||
bool res = false;
|
||||
if((total_size - step) < write_len) {
|
||||
res_ = this->write_bytes(MIN_NVRAM_ADDRESS + step_, zeroes.data(), total_size - step_);
|
||||
step += (total_size - step);
|
||||
} else {
|
||||
res_ = this->write_bytes(MIN_NVRAM_ADDRESS + step_, zeroes);
|
||||
step += write_len;
|
||||
}
|
||||
ESP_LOGV(TAG, "NVRAM: Trying to set %#02x register to 0x00: %s", MIN_NVRAM_ADDRESS + step, YESNO(res));
|
||||
state &= res;
|
||||
}
|
||||
|
||||
if(!state)
|
||||
{
|
||||
ESP_LOGE(TAG, "NVRAM: Unable to clear memory. Marking as failed.");
|
||||
this->nvram_state_ = DS3232NVRAMState::FAIL;
|
||||
} else {
|
||||
ESP_LOGD(TAG, "NVRAM: Memory has been cleared. Writing service information.");
|
||||
this->nvram_info_.info.magix_1 = MAGIX_CONTROL_1;
|
||||
this->nvram_info_.info.magix_2 = MAGIX_CONTROL_2;
|
||||
this->nvram_info_.info.is_initialized = false;
|
||||
this->nvram_info_.info.maj_version = NVRAM_DATA_MAJ_VERSION;
|
||||
this->nvram_info_.info.min_version = NVRAM_DATA_MIN_VERSION;
|
||||
if(!this->write_bytes(SVC_NVRAM_ADDRESS, this->nvram_info_.raw, sizeof(this->nvram_info_.raw)))
|
||||
{
|
||||
ESP_LOGE(TAG, "NVRAM: Unable to write service NVRAM information. Marking as failed.");
|
||||
this->nvram_state_ = DS3232NVRAMState::FAIL;
|
||||
}
|
||||
ESP_LOGD(TAG, "NVRAM: Initializing variables with initial values.");
|
||||
this->variable_init_callback_.call();
|
||||
ESP_LOGD(TAG, "NVRAM: Variables has been initialized. Saving state.");
|
||||
|
||||
this->nvram_info_.info.is_initialized = true;
|
||||
if(!this->write_bytes(SVC_NVRAM_ADDRESS, this->nvram_info_.raw, sizeof(this->nvram_info_.raw)))
|
||||
{
|
||||
ESP_LOGE(TAG, "NVRAM: Unable to write service NVRAM information. Marking as failed.");
|
||||
this->nvram_state_ = DS3232NVRAMState::FAIL;
|
||||
}
|
||||
this->nvram_state_ = DS3232NVRAMState::OK;
|
||||
ESP_LOGI(TAG, "NVRAM has been cleared.");
|
||||
}
|
||||
this->busy_ = false;
|
||||
}
|
||||
|
||||
bool DS3232Component::validate_mem_(uint8_t reg_id, uint8_t size, bool ignore_empty) {
|
||||
bool validator = true;
|
||||
validator &= (ignore_empty || this->nvram_available_);
|
||||
validator &= reg_id >= ds3232::MIN_NVRAM_ADDRESS;
|
||||
validator &= (reg_id + size - 1) <= ds3232::MAX_NVRAM_ADDRESS;
|
||||
return validator;
|
||||
}
|
||||
|
||||
void DS3232Component::reset_memory() {
|
||||
if(this->nvram_state_ == DS3232NVRAMState::INITIALIZATION) {
|
||||
ESP_LOGW(TAG, "NVRAM: Another memory reset process in progress already.");
|
||||
return;
|
||||
}
|
||||
ESP_LOGI(TAG, "Resetting NVRAM has been requested.");
|
||||
this->plan_clear_nvram_();
|
||||
}
|
||||
|
||||
void DS3232Component::reset_to_factory() {
|
||||
if(this->nvram_state_ == DS3232NVRAMState::INITIALIZATION) {
|
||||
ESP_LOGW(TAG, "NVRAM: Another memory reset process in progress already.");
|
||||
return;
|
||||
}
|
||||
ESP_LOGI(TAG, "Resetting config variables to their initial values.");
|
||||
this->plan_reset_nvram_();
|
||||
}
|
||||
|
||||
bool DS3232Component::read_memory(uint8_t reg_id, std::vector<uint8_t> &data) {
|
||||
if(this->nvram_state_ == DS3232NVRAMState::INITIALIZATION) {
|
||||
ESP_LOGW(TAG, "NVRAM: Memory reset process in progress. Try later.");
|
||||
return false;
|
||||
}
|
||||
if(data.size() == 0) {
|
||||
ESP_LOGW(TAG, "NVRAM: Nothing to write to memory.");
|
||||
return true;
|
||||
}
|
||||
if (!this->validate_mem_(reg_id, data.size())) {
|
||||
ESP_LOGE(TAG, "Invalid NVRAM memory mapping.");
|
||||
return false;
|
||||
} else {
|
||||
if(!this->read_bytes(reg_id, data.data(), data.size())) {
|
||||
ESP_LOGE(TAG, "NVRAM: Unable to read from %#02x register with size %u.", reg_id, data.size());
|
||||
this->nvram_state_ = DS3232NVRAMState::FAIL;
|
||||
return false;
|
||||
};
|
||||
ESP_LOGD(TAG, "NVRAM: Value read from %#02x register with size %u.", reg_id, data.size());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool DS3232Component::write_memory(const uint8_t reg_id, const std::vector<uint8_t> &data)
|
||||
{
|
||||
if(data.size() == 0) {
|
||||
ESP_LOGW(TAG, "NVRAM: Nothing to write to memory.");
|
||||
return true;
|
||||
}
|
||||
|
||||
if(this->nvram_state_ == DS3232NVRAMState::INITIALIZATION) {
|
||||
ESP_LOGW(TAG, "NVRAM: Memory reset process in progress. Try later.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!this->validate_mem_(reg_id, data.size(), true))
|
||||
{
|
||||
ESP_LOGE(TAG, "Invalid NVRAM memory mapping.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!this->write_bytes(reg_id, data))
|
||||
{
|
||||
ESP_LOGE(TAG, "NVRAM: Unable to write to %#02x register with size %u.", reg_id, data.size());
|
||||
this->nvram_state_ = DS3232NVRAMState::FAIL;
|
||||
return false;
|
||||
} else {
|
||||
ESP_LOGD(TAG, "NVRAM: Value written to %#02x register with size %u.", reg_id, data.size());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void DS3232Component::process_interrupt_() {
|
||||
if (this->read_data_()) {
|
||||
if (!reg_data_.reg.int_enable) {
|
||||
ESP_LOGD(TAG, "1Hz Heartbeat fired.");
|
||||
this->heartbeat_callback_.call();
|
||||
} else {
|
||||
process_alarms_();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DS3232Component::process_alarms_() {
|
||||
auto firing_time = this->reg_to_time_();
|
||||
if (reg_data_.reg.alarm_1_enable && reg_data_.reg.alarm_1_match) {
|
||||
reg_data_.reg.alarm_1_match = false;
|
||||
ESP_LOGI(TAG, "Alarm 1 has been fired.");
|
||||
this->alarm_one_callback_.call(firing_time);
|
||||
};
|
||||
if (reg_data_.reg.alarm_2_enable && reg_data_.reg.alarm_2_match) {
|
||||
reg_data_.reg.alarm_2_match = false;
|
||||
ESP_LOGI(TAG, "Alarm 2 has been fired.");
|
||||
this->alarm_two_callback_.call(firing_time);
|
||||
};
|
||||
this->write_bytes(I2C_REG_STATUS, reg_data_.raw_blocks.status_raw, sizeof(reg_data_.raw_blocks.status_raw));
|
||||
}
|
||||
|
||||
|
||||
/// @brief Reads i2c registers from chip
|
||||
/// @return True - if data has been read; otherwise - false
|
||||
bool DS3232Component::read_data_() {
|
||||
if (!this->read_bytes(I2C_REG_START, this->reg_data_.raw, sizeof(this->reg_data_.raw))) {
|
||||
ESP_LOGE(TAG, "Unable to read I2C data.");
|
||||
return false;
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG,
|
||||
"Read registers:\nRTC: %0u%0u:%0u%0u:%0u%0u 2%0u%0u%0u-%0u%0u-%0u%0u\nAlarm 1 (%s): 0b%0u%0u%0u%0u "
|
||||
"%0u%0u:%0u%0u:%0u%0u / %0u%0u (as %s)\nAlarm 2 (%s): 0b%0u%0u%0u0 %0u%0u:%0u%0u:00 / %0u%0u (as "
|
||||
"%s)\nTemperature: (%d + 0.25*%0u) °C\nEOSC:%s BBSQW:%s CONV:%s INTCN:%s\nOSCF:%s BB32KHZ:%s EN32KHZ:%s "
|
||||
"BSY:%s A2F:%0u A1F:%0u",
|
||||
reg_data_.reg.hour_10, reg_data_.reg.hour, reg_data_.reg.minute_10, reg_data_.reg.minute,
|
||||
reg_data_.reg.second_10, reg_data_.reg.second, reg_data_.reg.century, reg_data_.reg.year_10,
|
||||
reg_data_.reg.year, reg_data_.reg.month_10, reg_data_.reg.month, reg_data_.reg.day_10, reg_data_.reg.day,
|
||||
ONOFF(reg_data_.reg.alarm_1_enable), reg_data_.reg.alarm_1_mode_4, reg_data_.reg.alarm_1_mode_3,
|
||||
reg_data_.reg.alarm_1_mode_2, reg_data_.reg.alarm_1_mode_1, reg_data_.reg.alarm_1_hour_10,
|
||||
reg_data_.reg.alarm_1_hour, reg_data_.reg.alarm_1_minute_10, reg_data_.reg.alarm_1_minute,
|
||||
reg_data_.reg.alarm_1_second_10, reg_data_.reg.alarm_1_second, reg_data_.reg.alarm_1_day_10,
|
||||
reg_data_.reg.alarm_1_day, (reg_data_.reg.alarm_1_use_day_of_week ? DOW_TEXT : DOM_TEXT),
|
||||
ONOFF(reg_data_.reg.alarm_2_enable), reg_data_.reg.alarm_2_mode_4, reg_data_.reg.alarm_2_mode_3,
|
||||
reg_data_.reg.alarm_2_mode_2, reg_data_.reg.alarm_2_hour_10, reg_data_.reg.alarm_2_hour,
|
||||
reg_data_.reg.alarm_2_minute_10, reg_data_.reg.alarm_2_minute, reg_data_.reg.alarm_2_day_10,
|
||||
reg_data_.reg.alarm_2_day, (reg_data_.reg.alarm_2_use_day_of_week ? DOW_TEXT : DOM_TEXT),
|
||||
reg_data_.reg.temperature_integral, reg_data_.reg.temperature_fractional, ONOFF(reg_data_.reg.EOSC),
|
||||
ONOFF(reg_data_.reg.enable_battery_square_signal), ONOFF(reg_data_.reg.convert_temp),
|
||||
ONOFF(reg_data_.reg.int_enable), ONOFF(reg_data_.reg.osf_bit),
|
||||
ONOFF(reg_data_.reg.enable_hf_output_on_battery_mode), ONOFF(reg_data_.reg.enable_hf_output),
|
||||
ONOFF(reg_data_.reg.device_busy), reg_data_.reg.alarm_2_match, reg_data_.reg.alarm_1_match);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// @brief Reads temperature from internal chip sensor and updates sensor component on data change
|
||||
void DS3232Component::read_temperature_() {
|
||||
float temperature = reg_data_.reg.temperature_integral + TEMPERATURE_FACTOR * reg_data_.reg.temperature_fractional;
|
||||
ESP_LOGD(TAG, "Got temperature=%.2f°C", temperature);
|
||||
if (this->temperature_sensor_ != nullptr) {
|
||||
if (this->temperature_state_.next(temperature)) {
|
||||
this->temperature_sensor_->publish_state(temperature);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/// @brief Creates ESPTime struct from registers data
|
||||
/// @return ESPTime struct that contains time stored in RTC registers.
|
||||
ESPTime DS3232Component::reg_to_time_() {
|
||||
return ESPTime{
|
||||
.second = uint8_t(reg_data_.reg.second + 10 * reg_data_.reg.second_10),
|
||||
.minute = uint8_t(reg_data_.reg.minute + 10u * reg_data_.reg.minute_10),
|
||||
.hour = uint8_t(reg_data_.reg.hour + 10u * reg_data_.reg.hour_10),
|
||||
.day_of_week = uint8_t(reg_data_.reg.weekday),
|
||||
.day_of_month = uint8_t(reg_data_.reg.day + 10u * reg_data_.reg.day_10),
|
||||
.day_of_year = 1, // ignored by recalc_timestamp_utc(false)
|
||||
.month = uint8_t(reg_data_.reg.month + 10u * reg_data_.reg.month_10),
|
||||
.year = uint16_t(reg_data_.reg.year + 10u * reg_data_.reg.year_10 + 100u * reg_data_.reg.century + 2000)};
|
||||
}
|
||||
|
||||
/// @brief Reads time from chip
|
||||
void DS3232Component::read_time() {
|
||||
if (!this->read_data_()) {
|
||||
return;
|
||||
}
|
||||
if (reg_data_.reg.osf_bit) {
|
||||
ESP_LOGW(TAG, "RTC halted, not syncing to system clock.");
|
||||
return;
|
||||
}
|
||||
if (reg_data_.reg.device_busy) {
|
||||
ESP_LOGW(TAG, "RTC busy, not syncing to system clock.");
|
||||
return;
|
||||
}
|
||||
ESPTime current_rtc_time = this->reg_to_time_();
|
||||
current_rtc_time.recalc_timestamp_utc(false);
|
||||
if (!current_rtc_time.is_valid()) {
|
||||
ESP_LOGE(TAG, "Invalid RTC time, not syncing to system clock.");
|
||||
return;
|
||||
}
|
||||
time::RealTimeClock::synchronize_epoch_(current_rtc_time.timestamp);
|
||||
}
|
||||
|
||||
/// @brief Writes current time to chip
|
||||
void DS3232Component::write_time() {
|
||||
auto now = time::RealTimeClock::utcnow();
|
||||
if (!now.is_valid()) {
|
||||
ESP_LOGE(TAG, "Invalid system time, not syncing to RTC.");
|
||||
return;
|
||||
}
|
||||
reg_data_.reg.year = (now.year >= 2100) ? ((now.year - 2100) % 10) : ((now.year - 2000) % 10);
|
||||
reg_data_.reg.year_10 = ((now.year >= 2100) ? ((now.year - 2100) / 10) : ((now.year - 2000) / 10)) % 10;
|
||||
reg_data_.reg.century = (now.year >= 2100) ? 1 : 0;
|
||||
reg_data_.reg.month = now.month % 10;
|
||||
reg_data_.reg.month_10 = now.month / 10;
|
||||
reg_data_.reg.day = now.day_of_month % 10;
|
||||
reg_data_.reg.day_10 = now.day_of_month / 10;
|
||||
reg_data_.reg.weekday = now.day_of_week;
|
||||
reg_data_.reg.hour = now.hour % 10;
|
||||
reg_data_.reg.hour_10 = now.hour / 10;
|
||||
reg_data_.reg.minute = now.minute % 10;
|
||||
reg_data_.reg.minute_10 = now.minute / 10;
|
||||
reg_data_.reg.second = now.second % 10;
|
||||
reg_data_.reg.second_10 = now.second / 10;
|
||||
reg_data_.reg.osf_bit = true;
|
||||
reg_data_.reg.EOSC = false;
|
||||
|
||||
this->write_bytes(I2C_REG_RTC, reg_data_.raw_blocks.rtc_raw, sizeof(reg_data_.raw_blocks.rtc_raw));
|
||||
this->write_bytes(I2C_REG_CONTROL, reg_data_.raw_blocks.control_raw, sizeof(reg_data_.raw_blocks.control_raw));
|
||||
this->write_bytes(I2C_REG_STATUS, reg_data_.raw_blocks.status_raw, sizeof(reg_data_.raw_blocks.status_raw));
|
||||
}
|
||||
|
||||
/// @brief Gets alarm one state from chip
|
||||
/// @return A DS3232Alarm struct that contains detailed information about alarm
|
||||
ds3232_alarm::DS3232Alarm DS3232Component::get_alarm_one() {
|
||||
if (!this->read_data_()) {
|
||||
return {};
|
||||
}
|
||||
ds3232_alarm::DS3232Alarm alarm = {};
|
||||
alarm.enabled = reg_data_.reg.alarm_1_enable;
|
||||
alarm.seconds_supported = true;
|
||||
alarm.mode = ds3232_alarm::DS3232Alarm::alarm_mode(
|
||||
reg_data_.reg.alarm_1_mode_1,
|
||||
reg_data_.reg.alarm_1_mode_2,
|
||||
reg_data_.reg.alarm_1_mode_3,
|
||||
reg_data_.reg.alarm_1_mode_4,
|
||||
reg_data_.reg.alarm_1_use_day_of_week);
|
||||
alarm.second = reg_data_.reg.alarm_1_second + 10u * reg_data_.reg.alarm_1_second_10;
|
||||
alarm.minute = reg_data_.reg.alarm_1_minute + 10u * reg_data_.reg.alarm_1_minute_10;
|
||||
alarm.hour = reg_data_.reg.alarm_1_hour + 10u * reg_data_.reg.alarm_1_hour_10;
|
||||
alarm.day_of_week = reg_data_.reg.alarm_1_day;
|
||||
alarm.day_of_month = reg_data_.reg.alarm_1_day + 10u * reg_data_.reg.alarm_1_day_10;
|
||||
alarm.is_fired = reg_data_.reg.alarm_1_match;
|
||||
if (!alarm.is_valid()) {
|
||||
ESP_LOGW(TAG, "Stored 1st alarm data is invalid.");
|
||||
}
|
||||
return alarm;
|
||||
}
|
||||
|
||||
/// @brief Gets alarm two state from chip
|
||||
/// @return A DS3232Alarm struct that contains detailed information about alarm
|
||||
ds3232_alarm::DS3232Alarm DS3232Component::get_alarm_two() {
|
||||
if (!this->read_data_()) {
|
||||
return {};
|
||||
}
|
||||
ds3232_alarm::DS3232Alarm alarm = {};
|
||||
alarm.enabled = reg_data_.reg.alarm_2_enable;
|
||||
alarm.seconds_supported = false;
|
||||
alarm.mode = ds3232_alarm::DS3232Alarm::alarm_mode(
|
||||
false,
|
||||
reg_data_.reg.alarm_2_mode_2,
|
||||
reg_data_.reg.alarm_2_mode_3,
|
||||
reg_data_.reg.alarm_2_mode_4,
|
||||
reg_data_.reg.alarm_2_use_day_of_week);
|
||||
alarm.second = 0;
|
||||
alarm.minute = reg_data_.reg.alarm_2_minute + 10u * reg_data_.reg.alarm_2_minute_10;
|
||||
alarm.hour = reg_data_.reg.alarm_2_hour + 10u * reg_data_.reg.alarm_2_hour_10;
|
||||
alarm.day_of_week = reg_data_.reg.alarm_2_day;
|
||||
alarm.day_of_month = reg_data_.reg.alarm_2_day + 10u * reg_data_.reg.alarm_2_day_10;
|
||||
alarm.is_fired = reg_data_.reg.alarm_2_match;
|
||||
if (!alarm.is_valid()) {
|
||||
ESP_LOGW(TAG, "Stored 2nd alarm data is invalid.");
|
||||
}
|
||||
return alarm;
|
||||
}
|
||||
|
||||
/// @brief Resets alarm one data to initial settings and disables it.
|
||||
void DS3232Component::clear_alarm_one() {
|
||||
ds3232_alarm::DS3232Alarm alarm = this->get_alarm_one();
|
||||
alarm.enabled = false;
|
||||
alarm.second = 0;
|
||||
alarm.minute = 0;
|
||||
alarm.hour = 0;
|
||||
alarm.day_of_month = 1;
|
||||
alarm.day_of_week = 1;
|
||||
alarm.mode = ds3232_alarm::DS3232Alarm::alarm_mode(ds3232_alarm::AlarmMode::EVERY_TIME);
|
||||
this->set_alarm_one(alarm);
|
||||
}
|
||||
|
||||
/// @brief Resets alarm two data to initial settings and disables it.
|
||||
void DS3232Component::clear_alarm_two() {
|
||||
ds3232_alarm::DS3232Alarm alarm = this->get_alarm_two();
|
||||
alarm.enabled = false;
|
||||
alarm.second = 0;
|
||||
alarm.minute = 0;
|
||||
alarm.hour = 0;
|
||||
alarm.day_of_month = 1;
|
||||
alarm.day_of_week = 1;
|
||||
alarm.mode = ds3232_alarm::DS3232Alarm::alarm_mode(ds3232_alarm::AlarmMode::EVERY_TIME);
|
||||
this->set_alarm_two(alarm);
|
||||
}
|
||||
|
||||
/// @brief Sets the 1st alarm
|
||||
/// @param alarm Struct that contains detailed information about alarm to set.
|
||||
void DS3232Component::set_alarm_one(const ds3232_alarm::DS3232Alarm &alarm) {
|
||||
if (!alarm.is_valid()) {
|
||||
ESP_LOGW(TAG, "Unable to set alarm one to invalid state.");
|
||||
return;
|
||||
}
|
||||
if (!this->read_data_()) {
|
||||
return;
|
||||
}
|
||||
reg_data_.reg.alarm_1_enable = alarm.enabled;
|
||||
reg_data_.reg.alarm_1_second = alarm.second % 10;
|
||||
reg_data_.reg.alarm_1_second_10 = alarm.second / 10;
|
||||
reg_data_.reg.alarm_1_minute = alarm.minute % 10;
|
||||
reg_data_.reg.alarm_1_minute_10 = alarm.minute / 10;
|
||||
reg_data_.reg.alarm_1_hour = alarm.hour % 10;
|
||||
reg_data_.reg.alarm_1_hour_10 = alarm.hour / 10;
|
||||
reg_data_.reg.alarm_1_day = alarm.mode.bits.use_weekdays ? alarm.day_of_week : alarm.day_of_month % 10;
|
||||
reg_data_.reg.alarm_1_day_10 = alarm.mode.bits.use_weekdays ? 0 : alarm.day_of_month / 10;
|
||||
reg_data_.reg.alarm_1_mode_1 = alarm.mode.bits.match_seconds;
|
||||
reg_data_.reg.alarm_1_mode_2 = alarm.mode.bits.match_minutes;
|
||||
reg_data_.reg.alarm_1_mode_3 = alarm.mode.bits.match_hours;
|
||||
reg_data_.reg.alarm_1_mode_4 = alarm.mode.bits.match_days;
|
||||
reg_data_.reg.alarm_1_use_day_of_week = alarm.mode.bits.use_weekdays;
|
||||
reg_data_.reg.alarm_2_match = false;
|
||||
reg_data_.reg.int_enable =
|
||||
(reg_data_.reg.alarm_1_enable || reg_data_.reg.alarm_2_enable) && !this->produce_stable_frequency_;
|
||||
|
||||
if (!this->write_bytes(I2C_REG_ALARM_1, reg_data_.raw_blocks.alarm_1_raw, sizeof(reg_data_.raw_blocks.alarm_1_raw))) {
|
||||
return;
|
||||
}
|
||||
this->write_bytes(I2C_REG_CONTROL, reg_data_.raw_blocks.control_raw, sizeof(reg_data_.raw_blocks.control_raw));
|
||||
this->write_bytes(I2C_REG_STATUS, reg_data_.raw_blocks.status_raw, sizeof(reg_data_.raw_blocks.status_raw));
|
||||
}
|
||||
|
||||
/// @brief Sets the 2nd alarm
|
||||
/// @param alarm Struct that contains detailed information about alarm to set.
|
||||
void DS3232Component::set_alarm_two(const ds3232_alarm::DS3232Alarm &alarm) {
|
||||
if (!alarm.is_valid()) {
|
||||
ESP_LOGW(TAG, "Unable to set alarm one to invalid state.");
|
||||
return;
|
||||
}
|
||||
if (!this->read_data_()) {
|
||||
return;
|
||||
}
|
||||
reg_data_.reg.alarm_2_enable = alarm.enabled;
|
||||
reg_data_.reg.alarm_2_minute = alarm.minute % 10;
|
||||
reg_data_.reg.alarm_2_minute_10 = alarm.minute / 10;
|
||||
reg_data_.reg.alarm_2_hour = alarm.hour % 10;
|
||||
reg_data_.reg.alarm_2_hour_10 = alarm.hour / 10;
|
||||
reg_data_.reg.alarm_2_day = alarm.mode.bits.use_weekdays ? alarm.day_of_week : alarm.day_of_month % 10;
|
||||
reg_data_.reg.alarm_2_day_10 = alarm.mode.bits.use_weekdays ? 0 : alarm.day_of_month / 10;
|
||||
// reg_data_.reg.alarm_2_mode_1 = alarm.mode.bits.match_seconds; - there is no mode_1 flag for alarm 2.
|
||||
reg_data_.reg.alarm_2_mode_2 = alarm.mode.bits.match_minutes;
|
||||
reg_data_.reg.alarm_2_mode_3 = alarm.mode.bits.match_hours;
|
||||
reg_data_.reg.alarm_2_mode_4 = alarm.mode.bits.match_days;
|
||||
reg_data_.reg.alarm_2_use_day_of_week = alarm.mode.bits.use_weekdays;
|
||||
reg_data_.reg.alarm_2_match = false;
|
||||
reg_data_.reg.int_enable =
|
||||
(reg_data_.reg.alarm_1_enable || reg_data_.reg.alarm_2_enable) && !this->produce_stable_frequency_;
|
||||
|
||||
if (!this->write_bytes(I2C_REG_ALARM_2, reg_data_.raw_blocks.alarm_2_raw, sizeof(reg_data_.raw_blocks.alarm_2_raw))) {
|
||||
return;
|
||||
}
|
||||
this->write_bytes(I2C_REG_CONTROL, reg_data_.raw_blocks.control_raw, sizeof(reg_data_.raw_blocks.control_raw));
|
||||
this->write_bytes(I2C_REG_STATUS, reg_data_.raw_blocks.status_raw, sizeof(reg_data_.raw_blocks.status_raw));
|
||||
}
|
||||
|
||||
/// @brief Gets heartbeat state
|
||||
/// @return If true then 1Hz signal output on INT pin enabled.
|
||||
/// If false then INT pin goes low on alarm firing event.
|
||||
bool DS3232Component::is_heartbeat_enabled() {
|
||||
if (!this->read_data_()) {
|
||||
return false;
|
||||
}
|
||||
return !reg_data_.reg.int_enable;
|
||||
}
|
||||
|
||||
/// @brief Enables 1Hz output and disables alarms.
|
||||
void DS3232Component::enable_heartbeat() {
|
||||
if (!this->read_data_()) {
|
||||
return;
|
||||
}
|
||||
reg_data_.reg.int_enable = false;
|
||||
reg_data_.reg.alarm_1_match = false;
|
||||
reg_data_.reg.alarm_2_match = false;
|
||||
this->write_bytes(I2C_REG_STATUS, reg_data_.raw_blocks.status_raw, sizeof(reg_data_.raw_blocks.status_raw));
|
||||
this->write_bytes(I2C_REG_CONTROL, reg_data_.raw_blocks.control_raw, sizeof(reg_data_.raw_blocks.control_raw));
|
||||
this->produce_stable_frequency_ = true;
|
||||
}
|
||||
|
||||
/// @brief Enables alarms functionality and disables 1Hz output.
|
||||
void DS3232Component::enable_alarms() {
|
||||
if (!this->read_data_()) {
|
||||
return;
|
||||
}
|
||||
reg_data_.reg.int_enable = true;
|
||||
reg_data_.reg.alarm_1_match = false;
|
||||
reg_data_.reg.alarm_2_match = false;
|
||||
this->write_bytes(I2C_REG_STATUS, reg_data_.raw_blocks.status_raw, sizeof(reg_data_.raw_blocks.status_raw));
|
||||
this->write_bytes(I2C_REG_CONTROL, reg_data_.raw_blocks.control_raw, sizeof(reg_data_.raw_blocks.control_raw));
|
||||
this->produce_stable_frequency_ = false;
|
||||
}
|
||||
|
||||
} // namespace ds3232
|
||||
} // namespace esphome
|
352
esphome/components/ds3232/ds3232.h
Normal file
352
esphome/components/ds3232/ds3232.h
Normal file
|
@ -0,0 +1,352 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/time/real_time_clock.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "ds3232_alarm.h"
|
||||
namespace esphome {
|
||||
namespace ds3232 {
|
||||
|
||||
/// @brief Power state of DS3232 chip
|
||||
enum DS3232PowerState {
|
||||
/// @brief Power state is unknown, i.e. rst_pin is not connected.
|
||||
UNKNOWN = 0b00,
|
||||
|
||||
/// @brief Vcc >= Vbattery, i.e. power is online.
|
||||
ONLINE = 0b01,
|
||||
|
||||
/// @brief Vbat >= Vcc, i.e. no supplied power provided, battery is used.
|
||||
BATTERY = 0b10
|
||||
};
|
||||
|
||||
|
||||
/// @brief State of NVRAM functionality
|
||||
enum DS3232NVRAMState {
|
||||
/// @brief State is unknown
|
||||
UNCERTAIN = 0b0000,
|
||||
|
||||
/// @brief NVRAM should be initialized before usage.
|
||||
NEED_INITIALIZATION = 0b0001,
|
||||
|
||||
/// @brief NVRAM initialization process in progress.
|
||||
INITIALIZATION = 0b0010,
|
||||
|
||||
/// @brief NVRAM should be initialized with variables default values.
|
||||
NEED_RESET = 0b0100,
|
||||
|
||||
/// @brief NVRAM is in normal state.
|
||||
OK = 0b1000,
|
||||
|
||||
/// @brief NVRAM found but its state is faulty.
|
||||
FAIL = 0b1111
|
||||
};
|
||||
|
||||
/// @brief Basic register to store service information
|
||||
static const uint8_t SVC_NVRAM_ADDRESS = 0x14;
|
||||
|
||||
/// @brief Minimal number of NVRAM register available to user variables.
|
||||
static const uint8_t MIN_NVRAM_ADDRESS = 0x20;
|
||||
|
||||
/// @brief Maximal number of NVRAM register available to user variables.
|
||||
static const uint8_t MAX_NVRAM_ADDRESS = 0xFF;
|
||||
|
||||
/// @brief Magic value 1 to control validity of NVRAM.
|
||||
static const uint8_t MAGIX_CONTROL_1 = 0b00000011;
|
||||
/// @brief Magic value 2 to control validity of NVRAM.
|
||||
static const uint8_t MAGIX_CONTROL_2 = 0b00101101;
|
||||
|
||||
/// @brief Major version of NVRAM storage logic
|
||||
static const uint8_t NVRAM_DATA_MAJ_VERSION = 0;
|
||||
/// @brief Minor version of NVRAM storage logic
|
||||
static const uint8_t NVRAM_DATA_MIN_VERSION = 1;
|
||||
/// @brief NVRAM storage version
|
||||
static const uint8_t NVRAM_DATA_VERSION = NVRAM_DATA_MAJ_VERSION * 10 + NVRAM_DATA_MIN_VERSION;
|
||||
|
||||
/// @brief Component that implements ds3232mz+ crystal capabilities
|
||||
class DS3232Component : public time::RealTimeClock, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
void update() override;
|
||||
void dump_config() override;
|
||||
void loop() override;
|
||||
float get_setup_priority() const override;
|
||||
/// @brief Gets current state of NVRAM functionality.
|
||||
/// @return One of values from DS3232NVRAMState
|
||||
/// @see DS3232NVRAMState
|
||||
DS3232NVRAMState get_nvram_state() { return this->busy_ ? DS3232NVRAMState::UNCERTAIN : this->nvram_state_; }
|
||||
void read_time(); // Action
|
||||
void write_time(); // Action
|
||||
bool is_heartbeat_enabled(); // Condition
|
||||
void enable_heartbeat(); // Action
|
||||
void enable_alarms(); // Action
|
||||
/// @brief Perform full memory reinitialization. All available
|
||||
/// memory registers will be set to zeroes.
|
||||
/// This could help if memory structure is corrupted or faulty.
|
||||
void reset_memory(); // Action
|
||||
/// @brief Sets all 'configuration' variables to their initial values.
|
||||
void reset_to_factory(); // Action
|
||||
void set_alarm_one(const ds3232_alarm::DS3232Alarm &alarm); // Action
|
||||
void set_alarm_two(const ds3232_alarm::DS3232Alarm &alarm); // Action
|
||||
ds3232_alarm::DS3232Alarm get_alarm_one();
|
||||
ds3232_alarm::DS3232Alarm get_alarm_two();
|
||||
void clear_alarm_one();
|
||||
void clear_alarm_two();
|
||||
/// @brief Gets power source state.
|
||||
/// @return One of values from DS3232PowerState
|
||||
DS3232PowerState get_power_state();
|
||||
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }
|
||||
void set_interrupt_pin(GPIOPin *pin) { int_pin_ = pin; }
|
||||
void set_reset_pin(GPIOPin *pin) { rst_pin_ = pin; }
|
||||
void set_fire_alarms_on_startup(bool value) { fire_alarms_on_startup_ = value; }
|
||||
void add_on_alarm_one_callback(std::function<void(ESPTime)> &&func) {
|
||||
this->alarm_one_callback_.add(std::move(func));
|
||||
}
|
||||
void add_on_alarm_two_callback(std::function<void(ESPTime)> &&func) {
|
||||
this->alarm_two_callback_.add(std::move(func));
|
||||
}
|
||||
void add_on_heartbeat_callback(std::function<void()> &&func) { this->heartbeat_callback_.add(std::move(func)); }
|
||||
void add_on_power_change_callback(std::function<void(DS3232PowerState)> &&func) {
|
||||
this->power_state_callback_.add(std::move(func));
|
||||
}
|
||||
void add_on_variable_init_required_callback(std::function<void()> &&func) { this->variable_init_callback_.add(std::move(func)); }
|
||||
void add_on_variable_fail_callback(std::function<void()> &&func) { this->variable_fail_callback_.add(std::move(func)); }
|
||||
bool read_memory(uint8_t reg_id, std::vector<uint8_t> &data);
|
||||
bool write_memory(uint8_t reg_id, const std::vector<uint8_t> &data);
|
||||
bool is_valid_nvram(const uint8_t reg_id, uint8_t size) { return this->validate_mem_(reg_id, size, true); }
|
||||
|
||||
protected:
|
||||
void reinit_osf_();
|
||||
bool read_data_();
|
||||
void clear_nvram_();
|
||||
void plan_clear_nvram_();
|
||||
void plan_reset_nvram_();
|
||||
bool fire_alarms_on_startup_{false};
|
||||
void read_temperature_();
|
||||
void process_interrupt_();
|
||||
void process_alarms_();
|
||||
bool pin_state_{false};
|
||||
bool alarm_1_firing_{false};
|
||||
bool alarm_2_firing_{false};
|
||||
bool nvram_available_{false};
|
||||
bool late_startup_{true};
|
||||
DS3232NVRAMState nvram_state_{DS3232NVRAMState::UNCERTAIN};
|
||||
bool validate_mem_(uint8_t reg_id, uint8_t size, bool ignore_empty = false);
|
||||
ESPTime reg_to_time_();
|
||||
bool produce_stable_frequency_{false};
|
||||
bool busy_{false};
|
||||
sensor::Sensor *temperature_sensor_{nullptr};
|
||||
GPIOPin *int_pin_{nullptr};
|
||||
GPIOPin *rst_pin_{nullptr};
|
||||
CallbackManager<void(ESPTime)> alarm_one_callback_{};
|
||||
CallbackManager<void(ESPTime)> alarm_two_callback_{};
|
||||
CallbackManager<void()> heartbeat_callback_{};
|
||||
CallbackManager<void(DS3232PowerState)> power_state_callback_{};
|
||||
CallbackManager<void()> variable_init_callback_{};
|
||||
CallbackManager<void()> variable_fail_callback_{};
|
||||
Deduplicator<DS3232PowerState> power_state_;
|
||||
Deduplicator<float> temperature_state_;
|
||||
|
||||
union DS3232NVRAMData {
|
||||
struct {
|
||||
uint8_t magix_1 : 2; //Always should be 0b11;
|
||||
uint8_t magix_2 : 6; //Always should be 'E' char.
|
||||
bool is_initialized : 1; //If set to 1 then it means that nvram has initialized values.
|
||||
uint8_t min_version : 3;
|
||||
uint8_t maj_version : 4;
|
||||
} info;
|
||||
|
||||
mutable uint8_t raw[sizeof(info)];
|
||||
} nvram_info_;
|
||||
|
||||
union DS3232RegData {
|
||||
struct {
|
||||
// RTC regs (0x00)
|
||||
uint8_t second : 4;
|
||||
uint8_t second_10 : 3;
|
||||
uint8_t unused_0 : 1;
|
||||
|
||||
uint8_t minute : 4;
|
||||
uint8_t minute_10 : 3;
|
||||
uint8_t unused_1 : 1;
|
||||
|
||||
uint8_t hour : 4;
|
||||
uint8_t hour_10 : 2;
|
||||
uint8_t unused_2 : 2; // Always store in 24h mode
|
||||
|
||||
uint8_t weekday : 3;
|
||||
uint8_t unused_3 : 5;
|
||||
|
||||
uint8_t day : 4;
|
||||
uint8_t day_10 : 2;
|
||||
uint8_t unused_4 : 2;
|
||||
|
||||
uint8_t month : 4;
|
||||
uint8_t month_10 : 1;
|
||||
uint8_t unused_5 : 2;
|
||||
uint8_t century : 1;
|
||||
|
||||
uint8_t year : 4;
|
||||
uint8_t year_10 : 4;
|
||||
|
||||
// Alarm 1 regs (0x07)
|
||||
uint8_t alarm_1_second : 4;
|
||||
uint8_t alarm_1_second_10 : 3;
|
||||
bool alarm_1_mode_1 : 1;
|
||||
|
||||
uint8_t alarm_1_minute : 4;
|
||||
uint8_t alarm_1_minute_10 : 3;
|
||||
bool alarm_1_mode_2 : 1;
|
||||
|
||||
uint8_t alarm_1_hour : 4;
|
||||
uint8_t alarm_1_hour_10 : 2;
|
||||
uint8_t unused_6 : 1; // Always store in 24h mode
|
||||
bool alarm_1_mode_3 : 1;
|
||||
|
||||
uint8_t alarm_1_day : 4;
|
||||
uint8_t alarm_1_day_10 : 2;
|
||||
bool alarm_1_use_day_of_week : 1;
|
||||
bool alarm_1_mode_4 : 1;
|
||||
|
||||
// Alarm 2 regs (0x0B)
|
||||
uint8_t alarm_2_minute : 4;
|
||||
uint8_t alarm_2_minute_10 : 3;
|
||||
bool alarm_2_mode_2 : 1;
|
||||
|
||||
uint8_t alarm_2_hour : 4;
|
||||
uint8_t alarm_2_hour_10 : 2;
|
||||
uint8_t unused_7 : 1; // Always store in 24h mode
|
||||
bool alarm_2_mode_3 : 1;
|
||||
|
||||
uint8_t alarm_2_day : 4;
|
||||
uint8_t alarm_2_day_10 : 2;
|
||||
bool alarm_2_use_day_of_week : 1;
|
||||
bool alarm_2_mode_4 : 1;
|
||||
|
||||
// Control register (0x0E)
|
||||
bool alarm_1_enable : 1;
|
||||
bool alarm_2_enable : 1;
|
||||
bool int_enable : 1;
|
||||
uint8_t unused_8 : 2;
|
||||
bool convert_temp : 1;
|
||||
bool enable_battery_square_signal : 1;
|
||||
bool EOSC : 1;
|
||||
|
||||
// Status register (0x0F)
|
||||
bool alarm_1_match : 1;
|
||||
bool alarm_2_match : 1;
|
||||
bool device_busy : 1;
|
||||
bool enable_hf_output : 1;
|
||||
uint8_t unused_9 : 2;
|
||||
bool enable_hf_output_on_battery_mode : 1;
|
||||
bool osf_bit : 1;
|
||||
|
||||
// Aging register (0x10)
|
||||
uint8_t unused_10 : 8;
|
||||
|
||||
// Temperature data (read-only, 0x11)
|
||||
uint8_t temperature_integral : 8;
|
||||
|
||||
uint8_t unused_11 : 6;
|
||||
uint8_t temperature_fractional : 2;
|
||||
|
||||
} reg;
|
||||
mutable struct {
|
||||
mutable uint8_t rtc_raw[7];
|
||||
mutable uint8_t alarm_1_raw[4];
|
||||
mutable uint8_t alarm_2_raw[3];
|
||||
mutable uint8_t control_raw[1];
|
||||
mutable uint8_t status_raw[1];
|
||||
mutable uint8_t aging_raw[1];
|
||||
mutable uint8_t temperature_raw[2];
|
||||
} raw_blocks;
|
||||
mutable uint8_t raw[sizeof(reg)];
|
||||
} reg_data_;
|
||||
};
|
||||
|
||||
class HeartbeatEvent : public Trigger<> {
|
||||
public:
|
||||
explicit HeartbeatEvent(DS3232Component *parent) {
|
||||
parent->add_on_heartbeat_callback([this]() { this->trigger(); });
|
||||
}
|
||||
};
|
||||
|
||||
class AlarmFiredEvent : public Trigger<ESPTime> {
|
||||
public:
|
||||
explicit AlarmFiredEvent(DS3232Component *parent, bool is_first = true) {
|
||||
if (is_first) {
|
||||
parent->add_on_alarm_one_callback([this](ESPTime fire_time) { this->trigger(fire_time); });
|
||||
} else {
|
||||
parent->add_on_alarm_two_callback([this](ESPTime fire_time) { this->trigger(fire_time); });
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
class PowerStateEvent : public Trigger<DS3232PowerState> {
|
||||
public:
|
||||
explicit PowerStateEvent(DS3232Component *parent) {
|
||||
parent->add_on_power_change_callback([this](DS3232PowerState power_state) { this->trigger(power_state); });
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts> class FactoryResetNVRAMAction : public Action<Ts...>, public Parented<DS3232Component> {
|
||||
public:
|
||||
void play(Ts... x) override { this->parent_->reset_to_factory(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class EraseNVRAMAction : public Action<Ts...>, public Parented<DS3232Component> {
|
||||
public:
|
||||
void play(Ts... x) override { this->parent_->reset_memory(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class WriteAction : public Action<Ts...>, public Parented<DS3232Component> {
|
||||
public:
|
||||
void play(Ts... x) override { this->parent_->write_time(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class ReadAction : public Action<Ts...>, public Parented<DS3232Component> {
|
||||
public:
|
||||
void play(Ts... x) override { this->parent_->read_time(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class EnableHeartbeatAction : public Action<Ts...>, public Parented<DS3232Component> {
|
||||
public:
|
||||
void play(Ts... x) override { this->parent_->enable_heartbeat(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class EnableAlarmsAction : public Action<Ts...>, public Parented<DS3232Component> {
|
||||
public:
|
||||
void play(Ts... x) override { this->parent_->enable_alarms(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetAlarmAction : public Action<Ts...> {
|
||||
public:
|
||||
SetAlarmAction(DS3232Component *parent) : parent_(parent) {}
|
||||
TEMPLATABLE_VALUE(ds3232_alarm::DS3232Alarm, alarm_data)
|
||||
TEMPLATABLE_VALUE(uint8_t, alarm_id)
|
||||
void play(Ts... x) {
|
||||
if (!alarm_data_.has_value())
|
||||
return;
|
||||
switch (alarm_id_.value(x...)) {
|
||||
case 1:
|
||||
this->parent_->set_alarm_one(alarm_data_.value(x...));
|
||||
case 2:
|
||||
this->parent_->set_alarm_two(alarm_data_.value(x...));
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
DS3232Component *parent_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class IsHeartbeatEnabledCondition : public Condition<Ts...>, public Parented<DS3232Component> {
|
||||
public:
|
||||
bool check(Ts... x) override { return this->parent_->is_heartbeat_enabled(); }
|
||||
};
|
||||
|
||||
} // namespace ds3232
|
||||
} // namespace esphome
|
168
esphome/components/ds3232/ds3232_alarm.cpp
Normal file
168
esphome/components/ds3232/ds3232_alarm.cpp
Normal file
|
@ -0,0 +1,168 @@
|
|||
#include "ds3232_alarm.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <array>
|
||||
|
||||
namespace esphome {
|
||||
namespace ds3232 {
|
||||
namespace ds3232_alarm {
|
||||
|
||||
static const char *const TAG = "ds3232_alarm";
|
||||
|
||||
static const char *const DAYS_OF_WEEK[] = {"", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
|
||||
|
||||
static const char *const INVALID = "invalid";
|
||||
static const char *const FORMAT_STRING_EVERY_TIME_S = "every second";
|
||||
static const char *const FORMAT_STRING_EVERY_TIME_M = "every minute";
|
||||
static const char *const FORMAT_STRING_EVERY_MINUTE = "every minute on %.2u%s second";
|
||||
static const char *const FORMAT_STRING_EVERY_HOUR = "every hour on %.2u:%.2u";
|
||||
static const char *const FORMAT_STRING_EVERY_DAY = "every day on %.2u:%.2u:%.2u";
|
||||
static const char *const FORMAT_STRING_EVERY_WEEK = "every %s on %.2u:%.2u:%.2u";
|
||||
static const char *const FORMAT_STRING_EVERY_MONTH = "every %u%s day of month on %.2u:%.2u:%.2u";
|
||||
static const char *const FORMAT_STRING_INVALID = INVALID;
|
||||
|
||||
// Possible output formatted strings for DS3232Alarm::to_string():
|
||||
// every 31st day of month on 23:59:59 35 symbols
|
||||
// every Mon on 23:59:59 21 symbols
|
||||
// every day on 23:59:59 21 symbols
|
||||
// every hour on 59:59 19 symbols
|
||||
// every minute on 59th second 27 symbols
|
||||
// every minute 12 symbols
|
||||
// every second 12 symbols
|
||||
// disabled 8 symbols
|
||||
// So, the recommended formatted_string length initialization: 128 symbols (35 * 3 (105) growed to 2^x(128))
|
||||
static const size_t DS3232_ALARM_FORMAT_STRING_LENGTH = 128;
|
||||
|
||||
/// Converts alarm match bits to mode
|
||||
// static AlarmMode bits_to_mode(bool use_week, bool match_day, bool match_hour, bool match_min, bool match_sec = false) {
|
||||
// uint8_t mode_bits =
|
||||
// 0x00001 * match_sec + 0x00010 * match_min + 0x00100 * match_hour + 0x01000 * match_day + 0x10000 * use_week;
|
||||
// return static_cast<AlarmMode>(mode_bits);
|
||||
// };
|
||||
|
||||
DS3232AlarmMode DS3232Alarm::alarm_mode(const AlarmMode mode) {
|
||||
DS3232AlarmMode am = {};
|
||||
am.alarm_mode = mode;
|
||||
return am;
|
||||
};
|
||||
|
||||
DS3232AlarmMode DS3232Alarm::alarm_mode(const bool bit_seconds, const bool bit_minutes, const bool bit_hours,
|
||||
const bool bit_days, const bool use_weekdays) {
|
||||
bool weekdays_bit = use_weekdays && !(bit_seconds || bit_minutes || bit_hours);
|
||||
uint8_t mode_bits = 0b00001 * bit_seconds + 0b00010 * bit_minutes + 0b00100 * bit_hours + 0b01000 * bit_days +
|
||||
0b10000 * weekdays_bit;
|
||||
ESP_LOGVV(TAG, "Converted alarm mode from %.1u, %.1u, %.1u, %.1u, %.1u to bits %.2u",
|
||||
bit_seconds, bit_minutes, bit_hours, bit_days, use_weekdays, mode_bits);
|
||||
return DS3232Alarm::alarm_mode(static_cast<AlarmMode>(mode_bits));
|
||||
};
|
||||
|
||||
bool DS3232Alarm::is_valid() const {
|
||||
bool result = true;
|
||||
result &= this->seconds_supported ? (this->second >= 0 && this->second < 60) : true;
|
||||
result &= this->minute >= 0 && this->minute < 60;
|
||||
result &= this->hour >= 0 && this->hour < 24;
|
||||
result &= this->day_of_week > 0 && this->day_of_week <= 7;
|
||||
result &= this->day_of_month > 0 && this->day_of_month <= 31;
|
||||
return result;
|
||||
}
|
||||
|
||||
DS3232Alarm DS3232Alarm::create(const bool is_enabled, const DS3232AlarmMode mode, const bool use_weekdays,
|
||||
const uint8_t day, const uint8_t hour, const uint8_t minute,
|
||||
const uint8_t second, const bool is_fired,
|
||||
const bool is_seconds_supported)
|
||||
{
|
||||
DS3232Alarm alarm = {};
|
||||
// .enabled = is_enabled,
|
||||
// .seconds_supported = is_seconds_supported,
|
||||
// .mode = mode,
|
||||
// .second = second,
|
||||
// .minute = minute,
|
||||
// .hour = hour,
|
||||
// .day_of_week = (use_weekdays ? day : 1),
|
||||
// .day_of_month = (use_weekdays ? 1 : day),
|
||||
// .is_fired = is_fired
|
||||
// };
|
||||
ESP_LOGVV(TAG, "Initializing new instance of alarm:");
|
||||
alarm.enabled = is_enabled;
|
||||
ESP_LOGVV(TAG, " Alarm state: %s", ONOFF(alarm.enabled));
|
||||
alarm.seconds_supported = is_seconds_supported;
|
||||
ESP_LOGVV(TAG, " Seconds supported: %s", YESNO(alarm.seconds_supported));
|
||||
alarm.mode = mode;
|
||||
ESP_LOGVV(TAG, " Mode set to: %.2u (%.2u)", alarm.mode, mode);
|
||||
alarm.second = second;
|
||||
ESP_LOGVV(TAG, " Seconds set to: %.2u (%.2u)", alarm.second, second);
|
||||
alarm.minute = minute;
|
||||
ESP_LOGVV(TAG, " Minutes set to: %.2u (%.2u)", alarm.minute, minute);
|
||||
alarm.hour = hour;
|
||||
ESP_LOGVV(TAG, " Hours set to: %.2u (%.2u)", alarm.hour, hour);
|
||||
alarm.day_of_week = use_weekdays ? day : 1;
|
||||
ESP_LOGVV(TAG, " Day of month set to: %.2u (%.2u)", alarm.day_of_week, use_weekdays ? day : 1);
|
||||
alarm.day_of_month = use_weekdays ? 1 : day;
|
||||
ESP_LOGVV(TAG, " Day of week set to: %.2u (%.2u)", alarm.day_of_month, use_weekdays ? 1 : day);
|
||||
alarm.is_fired = is_fired;
|
||||
ESP_LOGVV(TAG, " Alarm is fired", YESNO(alarm.is_fired));
|
||||
return alarm;
|
||||
}
|
||||
|
||||
std::string DS3232Alarm::to_string() const {
|
||||
if (!this->is_valid())
|
||||
return std::string(INVALID);
|
||||
char formatted_string[DS3232_ALARM_FORMAT_STRING_LENGTH];
|
||||
size_t len;
|
||||
switch (this->mode.alarm_mode) {
|
||||
case AlarmMode::EVERY_TIME:
|
||||
case 31:
|
||||
return std::string(this->seconds_supported ? FORMAT_STRING_EVERY_TIME_S : FORMAT_STRING_EVERY_TIME_M);
|
||||
case AlarmMode::MATCH_SECONDS:
|
||||
case 30:
|
||||
if (!this->seconds_supported)
|
||||
return std::string(FORMAT_STRING_EVERY_TIME_M);
|
||||
len = snprintf(formatted_string, sizeof(formatted_string), FORMAT_STRING_EVERY_MINUTE, this->second,
|
||||
ORDINAL_SUFFIX(this->second));
|
||||
if (len < 0) {
|
||||
ESP_LOGE(TAG, "Unable to format alarm data.");
|
||||
return std::string(INVALID);
|
||||
}
|
||||
return std::string(formatted_string);
|
||||
case AlarmMode::MATCH_MINUTES_SECONDS:
|
||||
case 28:
|
||||
len =
|
||||
snprintf(formatted_string, sizeof(formatted_string), FORMAT_STRING_EVERY_HOUR, this->minute, this->second);
|
||||
if (len < 0) {
|
||||
ESP_LOGE(TAG, "Unable to format alarm data.");
|
||||
return std::string(INVALID);
|
||||
}
|
||||
return std::string(formatted_string);
|
||||
case AlarmMode::MATCH_TIME:
|
||||
case 24:
|
||||
len = snprintf(formatted_string, sizeof(formatted_string), FORMAT_STRING_EVERY_DAY, this->hour, this->minute,
|
||||
this->second);
|
||||
if (len < 0) {
|
||||
ESP_LOGE(TAG, "Unable to format alarm data.");
|
||||
return std::string(INVALID);
|
||||
}
|
||||
return std::string(formatted_string);
|
||||
case AlarmMode::MATCH_DAY_OF_WEEK_AND_TIME:
|
||||
len = snprintf(formatted_string, sizeof(formatted_string), FORMAT_STRING_EVERY_WEEK,
|
||||
DAYS_OF_WEEK[this->day_of_week], this->hour, this->minute, this->second);
|
||||
if (len < 0) {
|
||||
ESP_LOGE(TAG, "Unable to format alarm data.");
|
||||
return std::string(INVALID);
|
||||
}
|
||||
return std::string(formatted_string);
|
||||
case AlarmMode::MATCH_DATE_AND_TIME:
|
||||
len = snprintf(formatted_string, sizeof(formatted_string), FORMAT_STRING_EVERY_MONTH, this->day_of_month,
|
||||
ORDINAL_SUFFIX(this->day_of_month), this->hour, this->minute, this->second);
|
||||
if (len < 0) {
|
||||
ESP_LOGE(TAG, "Unable to format alarm data.");
|
||||
return std::string(INVALID);
|
||||
}
|
||||
return std::string(formatted_string);
|
||||
default:
|
||||
return INVALID;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ds3232_alarm
|
||||
} // namespace ds3232
|
||||
} // namespace esphome
|
112
esphome/components/ds3232/ds3232_alarm.h
Normal file
112
esphome/components/ds3232/ds3232_alarm.h
Normal file
|
@ -0,0 +1,112 @@
|
|||
#pragma once
|
||||
#include <string>
|
||||
|
||||
namespace esphome {
|
||||
namespace ds3232 {
|
||||
namespace ds3232_alarm {
|
||||
|
||||
#define ORDINAL_SUFFIX(NUMBER) \
|
||||
((((NUMBER) % 10) == 1) && (NUMBER) != 11) ? "st" \
|
||||
: (((((NUMBER) % 10) == 2) && (NUMBER) != 12) ? "nd" \
|
||||
: (((((NUMBER) % 10) == 3) && (NUMBER) != 13)) ? "rd" \
|
||||
: "th")
|
||||
|
||||
/// @brief Mode of the alarm
|
||||
enum AlarmMode {
|
||||
/// Fire alarm once a second for
|
||||
/// alarm one and once per minute for
|
||||
/// alarm two.
|
||||
EVERY_TIME = 0b01111,
|
||||
|
||||
/// Alarm when seconds match
|
||||
/// If seconds are not supported by alarm then
|
||||
/// it will be fired on every 00 seconds i.e. every minute.
|
||||
MATCH_SECONDS = 0b01110,
|
||||
|
||||
/// Alarm when minutes and optionally seconds match
|
||||
MATCH_MINUTES_SECONDS = 0b01100,
|
||||
|
||||
/// Alarm when hours, minutes, and optionally seconds match
|
||||
MATCH_TIME = 0b01000,
|
||||
|
||||
/// Alarm when date, hours, minutes, and optionally seconds match
|
||||
MATCH_DATE_AND_TIME = 0b00000,
|
||||
|
||||
/// Alarm when day, hours, minutes, and optionally seconds match
|
||||
MATCH_DAY_OF_WEEK_AND_TIME = 0b10000,
|
||||
};
|
||||
|
||||
/// static AlarmMode bits_to_mode(bool use_week, bool match_day, bool match_hour, bool match_min, bool match_sec =
|
||||
/// false);
|
||||
|
||||
/// Mode of alarm (firing pattern)
|
||||
union DS3232AlarmMode {
|
||||
mutable struct {
|
||||
bool match_seconds : 1;
|
||||
bool match_minutes : 1;
|
||||
bool match_hours : 1;
|
||||
bool match_days : 1;
|
||||
bool use_weekdays : 1;
|
||||
uint8_t unused : 3;
|
||||
} bits;
|
||||
mutable AlarmMode alarm_mode;
|
||||
mutable uint8_t raw;
|
||||
};
|
||||
|
||||
|
||||
/// @brief Alarm description
|
||||
struct DS3232Alarm {
|
||||
/// Indicates whether this alarm is enabled or not
|
||||
bool enabled = false;
|
||||
|
||||
/// Determines whether seconds matching is supported
|
||||
/// by alarm. Default is true.
|
||||
bool seconds_supported = true;
|
||||
|
||||
/// Mode of alarm (firing pattern)
|
||||
DS3232AlarmMode mode = alarm_mode(AlarmMode::MATCH_DATE_AND_TIME);
|
||||
|
||||
/// On which second alarm should be fired
|
||||
uint8_t second = 0;
|
||||
|
||||
/// On which minute alarm should be fired
|
||||
uint8_t minute = 0;
|
||||
|
||||
/// On which hour alarm should be fired
|
||||
uint8_t hour = 0;
|
||||
|
||||
/// On which day of week (1 is Sunday, 7 is Saturday) alarm should be fired
|
||||
uint8_t day_of_week = 1;
|
||||
|
||||
/// On which day of month alarm should be fired.
|
||||
uint8_t day_of_month = 1;
|
||||
|
||||
/// Indicates whether alarm has been fired
|
||||
bool is_fired = false;
|
||||
|
||||
/// Checks whether alarm configuration is valid.
|
||||
/// @return true is alarm contains valid configuration; otherwise - false.
|
||||
bool is_valid() const;
|
||||
|
||||
/// Formats alarm into human-readable text.
|
||||
/// @return Textual description of alarm.
|
||||
std::string to_string() const;
|
||||
|
||||
/// @brief Sets target alarm mode
|
||||
/// @param target_mode Mode of alarm to set
|
||||
void set_mode(AlarmMode target_mode) { mode.alarm_mode = target_mode; }
|
||||
|
||||
static DS3232AlarmMode alarm_mode(AlarmMode mode);
|
||||
static DS3232AlarmMode alarm_mode(bool bit_seconds = false, bool bit_minutes = false,
|
||||
bool bit_hours = false, bool bit_days = false,
|
||||
bool use_weekdays = false);
|
||||
|
||||
static DS3232Alarm create(bool is_enabled, DS3232AlarmMode mode, bool use_weekdays = true,
|
||||
uint8_t day = 1, uint8_t hour = 0, uint8_t minute = 0,
|
||||
uint8_t second = 0, bool is_fired = false,
|
||||
bool is_seconds_supported = false);
|
||||
};
|
||||
|
||||
} // namespace ds3232_alarm
|
||||
} // namespace ds3232
|
||||
} // namespace esphome
|
198
esphome/components/ds3232/ds3232_variables.h
Normal file
198
esphome/components/ds3232/ds3232_variables.h
Normal file
|
@ -0,0 +1,198 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include "ds3232.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/components/logger/logger.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ds3232 {
|
||||
namespace ds3232_variables {
|
||||
|
||||
static const char *const TAG = "ds3232_nvram";
|
||||
|
||||
template<typename T> class DS3232Variable : public Component {
|
||||
public:
|
||||
using value_type = T;
|
||||
|
||||
explicit DS3232Variable(DS3232Component *parent, const uint8_t reg_id) {
|
||||
reg_ = reg_id;
|
||||
parent_ = parent;
|
||||
}
|
||||
|
||||
explicit DS3232Variable(DS3232Component *parent, const uint8_t reg_id, T initial_value) : initial_value_(initial_value) {
|
||||
reg_ = reg_id;
|
||||
parent_ = parent;
|
||||
}
|
||||
|
||||
/// @brief Access to variable value.
|
||||
/// @return Variable
|
||||
T &value() { return this->value_; }
|
||||
|
||||
void setup() override {
|
||||
if (this->parent_ == nullptr) {
|
||||
this->mark_failed_();
|
||||
return;
|
||||
}
|
||||
|
||||
this->parent_->add_on_variable_init_required_callback([=]() { this->set_to_default_(); });
|
||||
this->parent_->add_on_variable_fail_callback([=]() { this->mark_failed_(); });
|
||||
|
||||
if (!this->parent_->is_valid_nvram(this->reg_, sizeof(T))) {
|
||||
this->mark_failed_();
|
||||
return;
|
||||
}
|
||||
|
||||
DS3232NVRAMState nvram_state_ = this->parent_->get_nvram_state();
|
||||
|
||||
while(nvram_state_ != DS3232NVRAMState::OK)
|
||||
{
|
||||
if(nvram_state_ == DS3232NVRAMState::FAIL)
|
||||
{
|
||||
this->log_error_("Unable to init with failed NVRAM.");
|
||||
this->mark_failed();
|
||||
break;
|
||||
}
|
||||
if(nvram_state_ == DS3232NVRAMState::NEED_RESET)
|
||||
{
|
||||
this->set_to_default_();
|
||||
}
|
||||
delay_microseconds_safe(10);
|
||||
nvram_state_ = this->parent_->get_nvram_state();
|
||||
}
|
||||
|
||||
if(!this->try_read_()) {
|
||||
this->mark_failed_();
|
||||
} else {
|
||||
this->is_initialized_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
float get_setup_priority() const override { return this->parent_->get_setup_priority() - 0.5f; } //Ensure that variables will be initialized after ds3232 component.
|
||||
|
||||
void loop() override {
|
||||
if(!this->try_write_())
|
||||
this->mark_failed_();
|
||||
}
|
||||
|
||||
void on_shutdown() override { this->try_write_(); }
|
||||
|
||||
bool is_initialized() { return this->is_initialized_; }
|
||||
|
||||
protected:
|
||||
uint8_t reg_;
|
||||
T initial_value_{};
|
||||
T value_{};
|
||||
T prev_value_{};
|
||||
bool need_init_{false};
|
||||
bool need_to_wait_{false};
|
||||
bool is_initialized_{false};
|
||||
std::va_list llist_{};
|
||||
|
||||
void log_error_(const char* log_str) {
|
||||
#ifdef USE_LOGGER
|
||||
auto *log = logger::global_logger;
|
||||
if (log == nullptr)
|
||||
return;
|
||||
|
||||
log->log_vprintf_(ESPHOME_LOG_LEVEL_ERROR, TAG, __LINE__, log_str, this->llist_);
|
||||
#endif
|
||||
}
|
||||
|
||||
void mark_failed_() {
|
||||
this->need_to_wait_ = false;
|
||||
this->need_init_ = false;
|
||||
this->is_initialized_ = false;
|
||||
this->mark_failed();
|
||||
}
|
||||
|
||||
void set_to_default_() {
|
||||
std::vector<uint8_t> buffer;
|
||||
buffer.resize(sizeof(T));
|
||||
memcpy(buffer.data(), &this->initial_value_, sizeof(T));
|
||||
if(!this->parent_->write_memory(this->reg_, buffer))
|
||||
{
|
||||
this->mark_failed_();
|
||||
} else {
|
||||
memcpy(&this->value_, &this->initial_value_, sizeof(T));
|
||||
memcpy(&this->prev_value_, &this->initial_value_, sizeof(T));
|
||||
};
|
||||
}
|
||||
|
||||
bool try_write_() {
|
||||
if (this->parent_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (this->parent_->get_nvram_state() == ds3232::DS3232NVRAMState::FAIL)
|
||||
return false;
|
||||
|
||||
int diff = memcmp(&this->value_, &this->prev_value_, sizeof(T));
|
||||
if (diff != 0) {
|
||||
std::vector<uint8_t> buffer;
|
||||
buffer.resize(sizeof(T));
|
||||
memcpy(buffer.data(), &this->value_, sizeof(T));
|
||||
if (!this->parent_->write_memory(this->reg_, buffer)) {
|
||||
this->mark_failed_();
|
||||
this->log_error_("Unable to write new value to NVRAM.");
|
||||
return false;
|
||||
} else {
|
||||
memcpy(&this->prev_value_, &this->value_, sizeof(T));
|
||||
return true;
|
||||
};
|
||||
} else {
|
||||
return true;
|
||||
};
|
||||
return false;
|
||||
}
|
||||
|
||||
bool try_read_() {
|
||||
if (this->parent_ == nullptr) {
|
||||
this->log_error_("No crystal defined. Read from NVRAM failed.");
|
||||
return false;
|
||||
}
|
||||
if (this->parent_->get_nvram_state() != ds3232::DS3232NVRAMState::OK)
|
||||
{
|
||||
this->log_error_("Invalid NVRAM state. Unable to read data.");
|
||||
return false;
|
||||
}
|
||||
std::vector<uint8_t> buffer;
|
||||
buffer.resize(sizeof(T));
|
||||
if (this->parent_->read_memory(this->reg_, buffer)) {
|
||||
memcpy(&this->value_, buffer.data(), buffer.size());
|
||||
memcpy(&this->prev_value_, &this->value_, sizeof(T));
|
||||
return true;
|
||||
}
|
||||
this->log_error_("Failed to read data.");
|
||||
return false;
|
||||
}
|
||||
|
||||
DS3232Component *parent_{nullptr};
|
||||
};
|
||||
|
||||
template<typename T> T &id(DS3232Variable<T> *value) {
|
||||
value->try_read_();
|
||||
return value->value();
|
||||
}
|
||||
|
||||
template<class C, typename... Ts> class DS3232VariableSetAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit DS3232VariableSetAction(C *parent) : parent_(parent) {}
|
||||
|
||||
using T = typename C::value_type;
|
||||
|
||||
TEMPLATABLE_VALUE(T, value);
|
||||
|
||||
void play(Ts... x) override {
|
||||
this->parent_->value() = this->value_.value(x...);
|
||||
}
|
||||
|
||||
protected:
|
||||
C *parent_;
|
||||
};
|
||||
|
||||
} // namespace ds3232_variables
|
||||
} // namespace ds3232
|
||||
} // namespace esphome
|
647
esphome/components/ds3232/time.py
Normal file
647
esphome/components/ds3232/time.py
Normal file
|
@ -0,0 +1,647 @@
|
|||
import re
|
||||
from esphome import config_validation as cv, automation, pins
|
||||
from esphome import codegen as cg
|
||||
from esphome.components import i2c, time, sensor
|
||||
from esphome.const import (
|
||||
CONF_TEMPERATURE,
|
||||
CONF_ID,
|
||||
CONF_ACTIVE,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_HOUR,
|
||||
CONF_MINUTE,
|
||||
CONF_SECOND,
|
||||
CONF_INTERRUPT_PIN,
|
||||
CONF_RESET_PIN,
|
||||
CONF_TIME,
|
||||
CONF_MODE,
|
||||
CONF_TYPE,
|
||||
CONF_VALUE,
|
||||
CONF_INITIAL_VALUE,
|
||||
CONF_SIZE,
|
||||
UNIT_CELSIUS,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
)
|
||||
from esphome.core import coroutine_with_priority
|
||||
|
||||
CODEOWNERS = ["@ViKozh"]
|
||||
DEPENDENCIES = ["i2c"]
|
||||
AUTO_LOAD = ["sensor"]
|
||||
# Fix for ESPTime wrong location
|
||||
ESPTime = cg.esphome_ns.struct("ESPTime")
|
||||
|
||||
ds3232_ns = cg.esphome_ns.namespace("ds3232")
|
||||
DS3232Component = ds3232_ns.class_("DS3232Component", time.RealTimeClock, i2c.I2CDevice)
|
||||
DS3232PowerState = ds3232_ns.enum("DS3232PowerState")
|
||||
|
||||
ReadAction = ds3232_ns.class_("ReadAction", automation.Action)
|
||||
WriteAction = ds3232_ns.class_("WriteAction", automation.Action)
|
||||
EnableHeartbeatAction = ds3232_ns.class_("EnableHeartbeatAction", automation.Action)
|
||||
EnableAlarmsAction = ds3232_ns.class_("EnableAlarmsAction", automation.Action)
|
||||
SetAlarmAction = ds3232_ns.class_("SetAlarmAction", automation.Action)
|
||||
FactoryResetAction = ds3232_ns.class_("FactoryResetNVRAMAction", automation.Action)
|
||||
EraseMemoryAction = ds3232_ns.class_("EraseNVRAMAction", automation.Action)
|
||||
|
||||
PowerStateEventTrigger = ds3232_ns.class_(
|
||||
"PowerStateEvent",
|
||||
automation.Trigger.template(DS3232PowerState),
|
||||
)
|
||||
HeartBeatEventTrigger = ds3232_ns.class_(
|
||||
"HeartbeatEvent", automation.Trigger.template()
|
||||
)
|
||||
AlarmFiredEventTrigger = ds3232_ns.class_(
|
||||
"AlarmFiredEvent",
|
||||
automation.Trigger.template(ESPTime),
|
||||
)
|
||||
|
||||
IsHeartbeatEnabledCondition = ds3232_ns.class_(
|
||||
"IsHeartbeatEnabledCondition", automation.Condition
|
||||
)
|
||||
|
||||
ds3232_alarm_ns = ds3232_ns.namespace("ds3232_alarm")
|
||||
DS3232Alarm = ds3232_alarm_ns.struct("DS3232Alarm")
|
||||
DS3232AlarmMode = ds3232_alarm_ns.enum("AlarmMode")
|
||||
|
||||
ds3232_variables_ns = ds3232_ns.namespace("ds3232_variables")
|
||||
DS3232Variable = ds3232_variables_ns.class_("DS3232Variable", cg.Component)
|
||||
DS3232VariableSetAction = ds3232_variables_ns.class_(
|
||||
"DS3232VariableSetAction", automation.Action
|
||||
)
|
||||
|
||||
ALARM_MODES = {
|
||||
"EVERY_TIME": DS3232AlarmMode.EVERY_TIME,
|
||||
"MATCH_SECONDS": DS3232AlarmMode.MATCH_SECONDS,
|
||||
"MATCH_MINUTES_SECONDS": DS3232AlarmMode.MATCH_MINUTES_SECONDS,
|
||||
"MATCH_TIME": DS3232AlarmMode.MATCH_TIME,
|
||||
"MATCH_DATE_AND_TIME": DS3232AlarmMode.MATCH_DATE_AND_TIME,
|
||||
"MATCH_DAY_OF_WEEK_AND_TIME": DS3232AlarmMode.MATCH_DAY_OF_WEEK_AND_TIME,
|
||||
}
|
||||
|
||||
WEEKDAYS = {
|
||||
"SUN": 1,
|
||||
"SUNDAY": 1,
|
||||
"MON": 2,
|
||||
"MONDAY": 2,
|
||||
"TUE": 3,
|
||||
"TUESDAY": 3,
|
||||
"WEDNESDAY": 4,
|
||||
"WED": 4,
|
||||
"THURSDAY": 5,
|
||||
"THU": 5,
|
||||
"FRIDAY": 6,
|
||||
"FRI": 6,
|
||||
"SATURDAY": 7,
|
||||
"SAT": 7,
|
||||
}
|
||||
|
||||
PERSISTENT_VARIABLE_TYPES = {
|
||||
"BOOL": ["bool", 1],
|
||||
"UINT": ["uint32_t", 4],
|
||||
"UINT64": ["uint64_t", 8],
|
||||
"UINT32": ["uint32_t", 4],
|
||||
"UINT16": ["uint16_t", 2],
|
||||
"UINT8": ["uint8_t", 1],
|
||||
"INT": ["int32_t", 4],
|
||||
"INT64": ["int64_t", 8],
|
||||
"INT32": ["int32_t", 4],
|
||||
"INT16": ["int16_t", 2],
|
||||
"INT8": ["int8_t", 1],
|
||||
"FLOAT": ["float", 4],
|
||||
"DOUBLE": ["double", 8],
|
||||
}
|
||||
|
||||
CONF_USE_HEARTBEAT = "use_heartbeat"
|
||||
CONF_FIRE_ALARMS_ON_STARTUP = "fire_alarms_on_startup"
|
||||
CONF_FIRST_ALARM = "first_alarm"
|
||||
CONF_SECOND_ALARM = "second_alarm"
|
||||
CONF_ON_POWER_CHANGE = "on_power_change"
|
||||
CONF_ON_HEARTBEAT = "on_heartbeat"
|
||||
CONF_ON_FIRST_ALARM = "on_first_alarm"
|
||||
CONF_ON_SECOND_ALARM = "on_second_alarm"
|
||||
CONF_TIME_PATTERN = "time_pattern"
|
||||
CONF_DAY_OF_MONTH = "day_of_month"
|
||||
CONF_DAY_OF_WEEK = "day_of_week"
|
||||
CONF_REGISTER = "register"
|
||||
CONF_PERSISTENT_STORAGE = "persistent_storage"
|
||||
CONF_CPP_TYPE = "cpp_type"
|
||||
|
||||
MIN_REGISTER_VALUE = 0x16
|
||||
MAX_REGISTER_VALUE = 0xFF
|
||||
|
||||
|
||||
def validate_variable_definition_(conf):
|
||||
size_ = int(PERSISTENT_VARIABLE_TYPES[str.upper(conf[CONF_TYPE])][1])
|
||||
end_reg = int(MAX_REGISTER_VALUE) - (size_ - 1)
|
||||
if int(MIN_REGISTER_VALUE) <= int(conf[CONF_REGISTER]) <= end_reg:
|
||||
conf[CONF_CPP_TYPE] = PERSISTENT_VARIABLE_TYPES[str.upper(conf[CONF_TYPE])][0]
|
||||
conf[CONF_SIZE] = size_
|
||||
else:
|
||||
raise cv.Invalid(
|
||||
f"Unable to allocate memory DS3232 in NVRAM for variable {conf[CONF_ID]}"
|
||||
)
|
||||
return conf
|
||||
|
||||
|
||||
def validate_persistent_storage_(conf):
|
||||
memory_map = {x: True for x in range(MIN_REGISTER_VALUE, MAX_REGISTER_VALUE)}
|
||||
for var_conf in conf:
|
||||
start_reg = int(var_conf[CONF_REGISTER])
|
||||
end_reg = start_reg + int(var_conf[CONF_SIZE]) - 1
|
||||
subrange = range(start_reg, end_reg)
|
||||
if all(memory_map[x] for x in subrange):
|
||||
for x in subrange:
|
||||
memory_map[x] = False
|
||||
else:
|
||||
raise cv.Invalid(
|
||||
f"Overlapping DS3232 NVRAM addresses found: {hex(start_reg)} to {hex(end_reg)}"
|
||||
)
|
||||
return conf
|
||||
|
||||
|
||||
PERSISTENT_VARIABLE_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.declare_id(DS3232Variable),
|
||||
cv.Required(CONF_TYPE): cv.enum(PERSISTENT_VARIABLE_TYPES, upper=True),
|
||||
cv.Required(CONF_REGISTER): cv.hex_int_range(
|
||||
min=MIN_REGISTER_VALUE, max=MAX_REGISTER_VALUE
|
||||
),
|
||||
cv.Optional(CONF_INITIAL_VALUE): cv.string_strict,
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
validate_variable_definition_,
|
||||
)
|
||||
|
||||
VALIDATE_ALARM_REGEX_ = r"^((?P<weekday>\w{3,})|(?P<day>(\d{1,2})|\*)){0,1}\s+(?P<hour>(\d{1,2})|\*):(?P<minute>(\d{1,2})|\*):(?P<second>(\d{1,2})|\*)$"
|
||||
ALARM_REGEX_PLACEHOLDER_ = "*"
|
||||
|
||||
|
||||
def validate_alarm_(conf):
|
||||
value_seconds = 0
|
||||
value_minutes = 0
|
||||
value_hours = 0
|
||||
value_days_of_week = 1
|
||||
value_days_of_month = 1
|
||||
if CONF_TIME in conf:
|
||||
value_seconds = conf[CONF_TIME][CONF_SECOND]
|
||||
value_minutes = conf[CONF_TIME][CONF_MINUTE]
|
||||
value_hours = conf[CONF_TIME][CONF_HOUR]
|
||||
if CONF_DAY_OF_WEEK in conf[CONF_TIME]:
|
||||
value_days_of_week = WEEKDAYS.get(
|
||||
str.upper(conf[CONF_TIME][CONF_DAY_OF_WEEK])
|
||||
)
|
||||
if CONF_DAY_OF_MONTH in conf[CONF_TIME]:
|
||||
value_days_of_month = conf[CONF_TIME][CONF_DAY_OF_MONTH]
|
||||
elif CONF_TIME_PATTERN in conf:
|
||||
parse_result = re.fullmatch(
|
||||
VALIDATE_ALARM_REGEX_,
|
||||
conf[CONF_TIME_PATTERN],
|
||||
flags=re.IGNORECASE | re.UNICODE,
|
||||
)
|
||||
if parse_result:
|
||||
values = parse_result.groupdict()
|
||||
sec = values.get("second", ALARM_REGEX_PLACEHOLDER_)
|
||||
if sec != ALARM_REGEX_PLACEHOLDER_ and sec is not None:
|
||||
value_seconds = int(sec)
|
||||
min = values.get("minute", ALARM_REGEX_PLACEHOLDER_)
|
||||
if min != ALARM_REGEX_PLACEHOLDER_ and min is not None:
|
||||
value_minutes = int(min)
|
||||
hr = values.get("hour", ALARM_REGEX_PLACEHOLDER_)
|
||||
if hr != ALARM_REGEX_PLACEHOLDER_ and hr is not None:
|
||||
value_hours = int(hr)
|
||||
day_m = values.get("day", ALARM_REGEX_PLACEHOLDER_)
|
||||
if day_m != ALARM_REGEX_PLACEHOLDER_ and day_m is not None:
|
||||
value_days_of_month = int(day_m)
|
||||
weekday = values.get("weekday", ALARM_REGEX_PLACEHOLDER_)
|
||||
if weekday != ALARM_REGEX_PLACEHOLDER_ and weekday is not None:
|
||||
value_days_of_week = WEEKDAYS.get(str.upper(weekday))
|
||||
else:
|
||||
raise cv.Invalid(
|
||||
"Invalid alarm match pattern. See documentation for reference."
|
||||
)
|
||||
elif conf[CONF_MODE] != DS3232AlarmMode.EVERY_TIME:
|
||||
raise cv.Invalid(
|
||||
"Alarms without time settings only possible with EVERY_TIME mode."
|
||||
)
|
||||
|
||||
if not (0 <= value_seconds <= 59):
|
||||
raise cv.Invalid("Seconds should be in range between 0 and 59.")
|
||||
if not (0 <= value_minutes <= 59):
|
||||
raise cv.Invalid("Minutes should be in range between 0 and 59.")
|
||||
if not (0 <= value_hours <= 59):
|
||||
raise cv.Invalid("Hours should be in range between 0 and 23.")
|
||||
if not (1 <= value_days_of_month <= 31):
|
||||
raise cv.Invalid("Day of month should be in range from 1 to 31.")
|
||||
if not (1 <= value_days_of_week <= 7):
|
||||
raise cv.Invalid(
|
||||
"Day of week should be in range from 1 (Sunday) to 7 (Saturday)."
|
||||
)
|
||||
|
||||
conf[CONF_SECOND] = value_seconds
|
||||
conf[CONF_MINUTE] = value_minutes
|
||||
conf[CONF_HOUR] = value_hours
|
||||
conf[CONF_DAY_OF_WEEK] = value_days_of_week
|
||||
conf[CONF_DAY_OF_MONTH] = value_days_of_month
|
||||
return conf
|
||||
|
||||
|
||||
def validate_(conf):
|
||||
if CONF_INTERRUPT_PIN not in conf:
|
||||
if CONF_ON_FIRST_ALARM in conf:
|
||||
raise cv.Invalid(
|
||||
f"'{CONF_INTERRUPT_PIN}' should be defined and properly connected to use '{CONF_ON_FIRST_ALARM}' trigger."
|
||||
)
|
||||
if CONF_ON_SECOND_ALARM in conf:
|
||||
raise cv.Invalid(
|
||||
f"'{CONF_INTERRUPT_PIN}' should be defined and properly connected to use '{CONF_ON_SECOND_ALARM}' trigger."
|
||||
)
|
||||
if CONF_ON_HEARTBEAT in conf:
|
||||
raise cv.Invalid(
|
||||
f"'{CONF_INTERRUPT_PIN}' should be defined and properly connected to use '{CONF_ON_HEARTBEAT}' trigger."
|
||||
)
|
||||
if CONF_RESET_PIN not in conf:
|
||||
if CONF_ON_POWER_CHANGE in conf:
|
||||
raise cv.Invalid(
|
||||
f"'{CONF_RESET_PIN}' should be defined and properly connected to use '{CONF_ON_POWER_CHANGE}' trigger."
|
||||
)
|
||||
return conf
|
||||
|
||||
|
||||
alarm_day_err_msg_ = "Alarm cannot be set both for day of month and day of week."
|
||||
ALARM_TIME_PATTERN_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_SECOND, default=0): cv.Range(
|
||||
min=0, min_included=True, max=59, max_included=True
|
||||
),
|
||||
cv.Optional(CONF_MINUTE, default=0): cv.Range(
|
||||
min=0, min_included=True, max=59, max_included=True
|
||||
),
|
||||
cv.Optional(CONF_HOUR, default=0): cv.Range(
|
||||
min=0, min_included=True, max=23, max_included=True
|
||||
),
|
||||
cv.Exclusive(CONF_DAY_OF_WEEK, "alarm_day", msg=alarm_day_err_msg_): cv.enum(
|
||||
WEEKDAYS, upper=True
|
||||
),
|
||||
cv.Exclusive(CONF_DAY_OF_MONTH, "alarm_day", msg=alarm_day_err_msg_): cv.Range(
|
||||
min=1, min_included=True, max=31, max_included=True
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
alarm_pattern_err_msg_ = "Alarm should be specified either by time match patterns or time elements. See docs for examples."
|
||||
ALARM_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_MODE): cv.enum(ALARM_MODES, upper=True),
|
||||
cv.Optional(CONF_ACTIVE, default=True): cv.boolean,
|
||||
cv.Exclusive(
|
||||
CONF_TIME_PATTERN, "alarm_pattern", msg=alarm_pattern_err_msg_
|
||||
): cv.string_strict,
|
||||
cv.Exclusive(
|
||||
CONF_TIME, "alarm_pattern", msg=alarm_pattern_err_msg_
|
||||
): ALARM_TIME_PATTERN_SCHEMA,
|
||||
}
|
||||
)
|
||||
|
||||
DS3232_ALARM_SCHEMA = cv.All(ALARM_SCHEMA, validate_alarm_)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
time.TIME_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(DS3232Component),
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_USE_HEARTBEAT, default=True): cv.boolean,
|
||||
cv.Optional(CONF_INTERRUPT_PIN): pins.gpio_input_pullup_pin_schema,
|
||||
cv.Optional(CONF_RESET_PIN): pins.gpio_input_pin_schema,
|
||||
cv.Optional(CONF_FIRE_ALARMS_ON_STARTUP, default=True): cv.boolean,
|
||||
cv.Optional(CONF_FIRST_ALARM): DS3232_ALARM_SCHEMA,
|
||||
cv.Optional(CONF_SECOND_ALARM): DS3232_ALARM_SCHEMA,
|
||||
cv.Optional(CONF_ON_HEARTBEAT): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
HeartBeatEventTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_FIRST_ALARM): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
AlarmFiredEventTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_SECOND_ALARM): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
AlarmFiredEventTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_POWER_CHANGE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
PowerStateEventTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_PERSISTENT_STORAGE, default={}): cv.All(
|
||||
[PERSISTENT_VARIABLE_SCHEMA],
|
||||
cv.ensure_list(),
|
||||
validate_persistent_storage_,
|
||||
),
|
||||
}
|
||||
).extend(i2c.i2c_device_schema(0x68)),
|
||||
validate_,
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"ds3232.factory_reset",
|
||||
FactoryResetAction,
|
||||
automation.maybe_simple_id(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(DS3232Component),
|
||||
}
|
||||
),
|
||||
)
|
||||
async def ds3232_factory_reset(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(
|
||||
"ds3232.erase_memory",
|
||||
EraseMemoryAction,
|
||||
automation.maybe_simple_id(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(DS3232Component),
|
||||
}
|
||||
),
|
||||
)
|
||||
async def ds3232_erase_memory(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(
|
||||
"ds3232.write_time",
|
||||
WriteAction,
|
||||
automation.maybe_simple_id(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(DS3232Component),
|
||||
}
|
||||
),
|
||||
)
|
||||
async def ds3232_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(
|
||||
"ds3232.read_time",
|
||||
ReadAction,
|
||||
automation.maybe_simple_id(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(DS3232Component),
|
||||
}
|
||||
),
|
||||
)
|
||||
async def ds3232_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
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"ds3232.enable_heartbeat",
|
||||
EnableHeartbeatAction,
|
||||
automation.maybe_simple_id(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(DS3232Component),
|
||||
}
|
||||
),
|
||||
)
|
||||
async def ds3232_enable_heartbeat_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(
|
||||
"ds3232.enable_alarms",
|
||||
EnableAlarmsAction,
|
||||
automation.maybe_simple_id(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(DS3232Component),
|
||||
}
|
||||
),
|
||||
)
|
||||
async def ds3232_enable_alarms_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_condition(
|
||||
"ds3232.is_heartbeat_enabled",
|
||||
IsHeartbeatEnabledCondition,
|
||||
automation.maybe_simple_id({cv.GenerateID(): cv.use_id(DS3232Component)}),
|
||||
)
|
||||
async def ds3232_is_hb_enabled_to_code(config, condition_id, template_arg, args):
|
||||
var = cg.new_Pvariable(condition_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
return var
|
||||
|
||||
|
||||
async def alarm(config, is_second_alarm=False):
|
||||
is_enabled = cg.bool_(config[CONF_ACTIVE])
|
||||
is_seconds_supported = cg.bool_(not is_second_alarm)
|
||||
is_fired = cg.bool_(False)
|
||||
use_weekdays = None
|
||||
day = None
|
||||
hour = cg.uint8(config.get(CONF_HOUR, 0))
|
||||
minute = cg.uint8(config.get(CONF_MINUTE, 0))
|
||||
second = cg.uint8(config.get(CONF_SECOND, 0))
|
||||
cur_op = DS3232Alarm.op
|
||||
DS3232Alarm.op = "::"
|
||||
mode = DS3232Alarm.alarm_mode(
|
||||
ALARM_MODES[config.get(CONF_MODE, "MATCH_DAY_OF_WEEK_AND_TIME")]
|
||||
)
|
||||
DS3232Alarm.op = cur_op
|
||||
if config[CONF_MODE] == DS3232AlarmMode.MATCH_DAY_OF_WEEK_AND_TIME:
|
||||
use_weekdays = cg.bool_(True)
|
||||
day = cg.uint8(config.get(CONF_DAY_OF_WEEK, 1))
|
||||
else:
|
||||
use_weekdays = cg.bool_(False)
|
||||
day = cg.uint8(config.get(CONF_DAY_OF_MONTH, 1))
|
||||
|
||||
cur_op = DS3232Alarm.op
|
||||
DS3232Alarm.op = "::"
|
||||
expr = DS3232Alarm.create(
|
||||
is_enabled,
|
||||
mode,
|
||||
use_weekdays,
|
||||
day,
|
||||
hour,
|
||||
minute,
|
||||
second,
|
||||
is_fired,
|
||||
is_seconds_supported,
|
||||
)
|
||||
DS3232Alarm.op = cur_op
|
||||
return expr
|
||||
|
||||
|
||||
SET_ALARM_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(DS3232Component),
|
||||
}
|
||||
).extend(ALARM_SCHEMA)
|
||||
|
||||
SET_ALARM_VALID_SCHEMA = cv.All(
|
||||
SET_ALARM_SCHEMA,
|
||||
validate_alarm_,
|
||||
)
|
||||
|
||||
|
||||
# Run with low priority so that namespaces are registered first
|
||||
@coroutine_with_priority(-100.0)
|
||||
async def variable_to_code(conf, parent_id):
|
||||
var_type = cg.RawExpression(PERSISTENT_VARIABLE_TYPES[conf[CONF_TYPE]][0])
|
||||
paren = await cg.get_variable(parent_id)
|
||||
template_args = cg.TemplateArguments(var_type)
|
||||
register = conf[CONF_REGISTER]
|
||||
res_type = DS3232Variable.template(template_args)
|
||||
var_instance = None
|
||||
if CONF_INITIAL_VALUE in conf:
|
||||
var_instance = DS3232Variable.new(
|
||||
template_args, paren, register, cg.RawExpression(conf[CONF_INITIAL_VALUE])
|
||||
)
|
||||
else:
|
||||
var_instance = DS3232Variable.new(template_args, paren, register)
|
||||
var_persistent = cg.Pvariable(conf[CONF_ID], var_instance, res_type)
|
||||
await cg.register_component(var_persistent, conf)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"ds3232.set_alarm_one", SetAlarmAction, SET_ALARM_VALID_SCHEMA
|
||||
)
|
||||
async def ds3232_set_alarm_one(config, action_id, template_args, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_args, paren)
|
||||
cg.add(var.set_alarm_id(1))
|
||||
alarm_ = await alarm(config)
|
||||
cg.add(var.set_alarm_data(alarm_))
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"ds3232.set_alarm_two", SetAlarmAction, SET_ALARM_VALID_SCHEMA
|
||||
)
|
||||
async def ds3232_set_alarm_two(config, action_id, template_args, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_args, paren)
|
||||
cg.add(var.set_alarm_id(2))
|
||||
alarm_ = await alarm(config, True)
|
||||
cg.add(var.set_alarm_data(alarm_))
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"ds3232_nvram.set",
|
||||
DS3232VariableSetAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(DS3232Variable),
|
||||
cv.Required(CONF_VALUE): cv.templatable(cv.string_strict),
|
||||
}
|
||||
),
|
||||
)
|
||||
async def ds3232_nvram_set_to_code(config, action_id, template_arg, args):
|
||||
full_id, parent = await cg.get_variable_with_full_id(config[CONF_ID])
|
||||
template_arg = cg.TemplateArguments(full_id.type, *template_arg)
|
||||
variable = cg.new_Pvariable(action_id, template_arg, parent)
|
||||
templat = await cg.templatable(
|
||||
config[CONF_VALUE], args, None, to_exp=cg.RawExpression
|
||||
)
|
||||
cg.add(variable.set_value(templat))
|
||||
return variable
|
||||
|
||||
|
||||
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)
|
||||
|
||||
for variable in config[CONF_PERSISTENT_STORAGE]:
|
||||
await variable_to_code(variable, config[CONF_ID])
|
||||
|
||||
if CONF_TEMPERATURE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
|
||||
if CONF_FIRE_ALARMS_ON_STARTUP in config:
|
||||
cg.add(var.set_fire_alarms_on_startup(config[CONF_FIRE_ALARMS_ON_STARTUP]))
|
||||
|
||||
if CONF_INTERRUPT_PIN in config:
|
||||
int_pin = await cg.gpio_pin_expression(config[CONF_INTERRUPT_PIN])
|
||||
cg.add(var.set_interrupt_pin(int_pin))
|
||||
|
||||
for conf in config.get(CONF_ON_FIRST_ALARM, []):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], paren, True)
|
||||
await automation.build_automation(
|
||||
trigger,
|
||||
[
|
||||
(ESPTime, "fire_time"),
|
||||
],
|
||||
conf,
|
||||
)
|
||||
|
||||
for conf in config.get(CONF_ON_SECOND_ALARM, []):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], paren, False)
|
||||
await automation.build_automation(
|
||||
trigger,
|
||||
[
|
||||
(ESPTime, "fire_time"),
|
||||
],
|
||||
conf,
|
||||
)
|
||||
|
||||
if CONF_FIRST_ALARM in config:
|
||||
cg.add(
|
||||
var.set_alarm_one(
|
||||
await alarm(config[CONF_FIRST_ALARM], is_second_alarm=False)
|
||||
)
|
||||
)
|
||||
|
||||
if CONF_SECOND_ALARM in config:
|
||||
cg.add(
|
||||
var.set_alarm_two(
|
||||
await alarm(config[CONF_SECOND_ALARM], is_second_alarm=True)
|
||||
)
|
||||
)
|
||||
|
||||
for conf in config.get(CONF_ON_HEARTBEAT, []):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], paren)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
|
||||
for conf in config.get(CONF_ON_POWER_CHANGE, []):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], paren)
|
||||
await automation.build_automation(
|
||||
trigger, [(DS3232PowerState, "power_state")], conf
|
||||
)
|
154
tests/test1.2.yaml
Normal file
154
tests/test1.2.yaml
Normal file
|
@ -0,0 +1,154 @@
|
|||
---
|
||||
substitutions:
|
||||
devicename: test1_2
|
||||
sensorname: my
|
||||
textname: template
|
||||
roomname: living_room
|
||||
|
||||
esphome:
|
||||
name: test1_2
|
||||
name_add_mac_suffix: true
|
||||
platform: ESP32
|
||||
board: nodemcu-32s
|
||||
build_path: build/test1
|
||||
|
||||
i2c:
|
||||
sda: GPIO21
|
||||
scl: GPIO22
|
||||
id: i2c_bus
|
||||
frequency: 400kHz
|
||||
|
||||
logger:
|
||||
level: VERBOSE
|
||||
|
||||
api:
|
||||
|
||||
wifi:
|
||||
ssid: "ssid"
|
||||
|
||||
time:
|
||||
- platform: ds3232
|
||||
id: ds3232_chip
|
||||
i2c_id: i2c_bus
|
||||
temperature:
|
||||
name: "Chip Temperature"
|
||||
use_heartbeat: false
|
||||
interrupt_pin:
|
||||
number: GPIO18
|
||||
mode:
|
||||
input: true
|
||||
pullup: true
|
||||
reset_pin: GPIO23
|
||||
fire_alarms_on_startup: true
|
||||
first_alarm:
|
||||
mode: MATCH_DAY_OF_WEEK_AND_TIME
|
||||
time_pattern: "Wed 12:04:23"
|
||||
second_alarm:
|
||||
mode: MATCH_TIME
|
||||
time:
|
||||
minute: 10
|
||||
hour: 21
|
||||
on_first_alarm:
|
||||
then:
|
||||
- lambda: |-
|
||||
if(!id(counter_one)->is_initialized()) {
|
||||
ESP_LOGD("TEST", "NVRAM not yet ready...");
|
||||
return;
|
||||
};
|
||||
int i = id(counter_one)->value();
|
||||
i++;
|
||||
id(test_snsr).publish_state(i);
|
||||
id(counter_one)->value() = i;
|
||||
on_heartbeat:
|
||||
then:
|
||||
- lambda: |-
|
||||
ESP_LOGD("TEST", "1Hz heartbeat signal");
|
||||
on_second_alarm:
|
||||
then:
|
||||
- logger.log: "Alarm 2 has been fired"
|
||||
- lambda: |-
|
||||
id(has_ever_fired)->value() = true;
|
||||
# on_power_change:
|
||||
# then:
|
||||
# - lambda: |-
|
||||
# id(counter_one)->value() = id(counter_one)->value() + 1;
|
||||
# - if:
|
||||
# condition:
|
||||
# lambda: |-
|
||||
# return id(has_ever_fired)->value();
|
||||
# then:
|
||||
# - ds3232.enable_heartbeat: ds3232_chip
|
||||
# else:
|
||||
# - ds3232.enable_alarms: ds3232_chip
|
||||
persistent_storage:
|
||||
- id: counter_one
|
||||
type: uint64
|
||||
register: 0x21
|
||||
initial_value: '3548'
|
||||
- id: has_ever_fired
|
||||
type: bool
|
||||
register: 0x20
|
||||
|
||||
sensor:
|
||||
- platform: template
|
||||
name: "Test"
|
||||
id: test_snsr
|
||||
- platform: template
|
||||
name: "NVRAM Counter"
|
||||
lambda: |-
|
||||
if(id(counter_one)->is_initialized())
|
||||
return id(counter_one)->value();
|
||||
else
|
||||
return NAN;
|
||||
update_interval: 5s
|
||||
- platform: template
|
||||
name: "Has Ever Fired"
|
||||
update_interval: 5s
|
||||
lambda: |-
|
||||
if(id(has_ever_fired)->is_initialized())
|
||||
return id(has_ever_fired)->value();
|
||||
else
|
||||
return NAN;
|
||||
|
||||
button:
|
||||
- platform: template
|
||||
name: Reset variables
|
||||
on_press:
|
||||
then:
|
||||
- ds3232_nvram.set:
|
||||
id: has_ever_fired
|
||||
value: 'false'
|
||||
- ds3232_nvram.set:
|
||||
id: counter_one
|
||||
value: '5555555'
|
||||
- platform: template
|
||||
name: Factory Reset
|
||||
on_press:
|
||||
then:
|
||||
- ds3232.factory_reset
|
||||
- platform: template
|
||||
name: Toggle DS3232 mode
|
||||
on_press:
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
ds3232.is_heartbeat_enabled: ds3232_chip
|
||||
then:
|
||||
- ds3232.enable_alarms: ds3232_chip
|
||||
else:
|
||||
- ds3232.enable_heartbeat: ds3232_chip
|
||||
- platform: template
|
||||
name: Set New Alarm
|
||||
on_press:
|
||||
- ds3232.set_alarm_one:
|
||||
mode: MATCH_SECONDS
|
||||
active: true
|
||||
time_pattern: "* *:*:10"
|
||||
- ds3232.set_alarm_two:
|
||||
mode: MATCH_MINUTES_SECONDS
|
||||
active: true
|
||||
time:
|
||||
minute: 15
|
||||
- ds3232_nvram.set:
|
||||
id: has_ever_fired
|
||||
value: 'false'
|
Loading…
Reference in a new issue