feature-requests
feature-requests copied to clipboard
Support for DS3231 (I2C RTC)
Describe the problem you have/What new integration you would like
I understand supporting this should not be a priority over other useful sensors, but having support for some kind of reliable I2C RTC chip would be nice in certain situations, especially if the ESPHome node is placed at a location where the wifi signal strength is poor. Please describe your use case for this integration and alternatives you've tried:
Without a bulky external antenna, GPS modules don't work that well in some buildings, sometimes it won't even get a fix at all. Additional context
Hope to get support
This could go very nice along with a ntp server component https://github.com/esphome/feature-requests/issues/296 :tada:
hopefully there will be support for ds1307 too
Sometimes electricity breaks in our location and then restores, but internet connection restoring in a long time after usually, so our ESP modules cannot understand the time for light controlling logic
Just been looking for ways to have backup programming for my boiler and an RTC was a requirement - would love to see it!
Me too. If there is no wi fi, I would like to show the correct time on the display.
hopefully there will be support for ds1307 too
I have finally started working on that again: https://github.com/esphome/esphome/pull/1441
The DS3231 register set is so similar to the one of the DS1307, that the DS1307 component (esphome/esphome#1441) should already work with a DS3231. I could not test this, however, because I don't have such a device at hand; and additional features such as temperature readings and alarms are obviously not supported.
I could definitely also provide a dedicated driver component for the DS3231 if I had one to test with. :-) https://www.amazon.de/hz/wishlist/ls/IKJQ9RPIUL6J
https://github.com/esphome/esphome/pull/1441 is merged in 1.16.0b2.
@badbadc0ffee I need to find my DS3231 somewhere in the drawer so I can test it with your component! Thank's already for your work :+1:
Any update on DS3231 support for ESP32? Or if someone can show us how ti write our own code porting from conventional Arduino IDE based approach to get DS3231 integrated into ESPHOME.
I would like if I can wake ESP by external clock to do its job and then turn it off completely. It is not conceptually correct to have ESP sleeping and all supporting circuitry powered up if no job is needed. Everything should be powered down until its time comes.
@jbrepogmailcom I think you misunderstand. It is perfectly reasonable to leave the rest of the external support circuitry powered up when calling a routine that is documented to put the processor to sleep. Switching off power to the entire board is beyond the scope of the the deep sleep routine. You could probably build a basic switch from a couple of transistors, or use a dedicated power management IC (eg. AXP192)
Hello, we are probably talking about different things. I meant to switch off both circuitry and ESP32 by DS3231 during the deep sleep, not before. And after wakeup, to reprogram DS3231 for nex sleep. However, I learned in the meanwhile that DS3231 takes about 100µA during sleep, while ESP32 with RTC on takes 10µA in deep sleep and 5µA in hibernation mode. So it is better to use ESP32 itself to manage sleep time and turn on/off supporting electronics.
A bit late to the discussion, but the DS1307 component in ESPHome seems to work just fine with the DS3231 module. I have it attached to a NodeMCU 32s board and it's ticking along nicely.
Edit: I'm getting two devices at the i2c scan. I'm, ofc, using 0x68. I have no idea what the other is tbh.
[22:17:45][C][i2c:028]: I2C Bus:
[22:17:45][C][i2c:029]: SDA Pin: GPIO21
[22:17:45][C][i2c:030]: SCL Pin: GPIO22
[22:17:45][C][i2c:031]: Frequency: 50000 Hz
[22:17:45][I][i2c:033]: Scanning i2c bus for active devices...
[22:17:45][I][i2c:040]: Found i2c device at address 0x57
[22:17:45][I][i2c:040]: Found i2c device at address 0x68
[22:17:45][C][ds1307:022]: DS1307:
[22:17:45][C][ds1307:023]: Address: 0x68
Edit: I'm getting two devices at the i2c scan. I'm, ofc, using 0x68. I have no idea what the other is tbh.
The other is likely 32K of eeprom (AT24C32X), which I know can have an address of 0x57 and appears on modules like this:

Writing to and reading from it is not currently supported by esphome.
That seems very likely, as that's the exact module I have :-)
I currently have no use for the eeprom on the module, so it's not really something I would wish esphome to focus on anyhow.
Thanks for the information!
Just to throw my two cents in. For my ESPHome sensors, I'm hoping to replicate the work found https://www.raspberrypi.com/news/build-low-power-clock-controlled-devices/ to greatly prolong the battery life on my sensors. It seems like this "sleep" method allows for zero current to the DS321 (via VCC) or ESP32 and very, very low current for the P-Channel Mosfet switch (< 1mA). The DS3231 runs from the coin cell while waiting for the next alarm. When that alarm happens, the DS3231 switches the mosfet, the ESP32 circuit is powered on. The ESP32 can then do it's business, set a next alarm, and when it clears the current DS3231 alarm the ESP32 circuit is shutdown (completely), along with the VCC input on the DS3231 (so it is only running from the coin-cell until it triggers the next alarm, turning the ESP32 circuit back on).
It seems like several others have more or less the same goal. Has anyone made any progress?
I first will probably just do it as an Arduino or MicroPython app and publish to Home Assistant via MQTT (via node-red probably). I'd love to then transfer what I've learned to be able to do this with ESPHome, but we'll see how far I get on that front. It's currently not clear to me how I'd want to implement this in ESPHome. Maybe as an alternative to deep sleep or via lambda scripts?
The unit I ordered for testing is https://www.amazon.com/gp/product/B07WJZ7NKM - I've read you specifically should prefer the DS3231SN versions of the chip.
https://esphome.io/components/time.html?#ds1307-time-source
https://esphome.io/components/time.html?#ds1307-time-source
That's an interesting device for reading/writing time. The ds3231 could enable doing a lot more. See my description above.
My goal is to be able to use the ds3231 to be able to be able to power down circuit including the esp32 for stretches of time and it can trigger turning the circuit back on to do work - and the cycle repeats.
I have a Norvi NORVI-ENET-AE06-R DIN mount ESP32 that uses a DS3231, I found that the built-in ds1307 component seems to works fine for RTC only purposes.
okay guys got it figured out with esp32-c3 super mini anyhow. see my esphome.yaml files attached one for setting the time, the other for reading the time. ds3231-esphome-scripts.zip
htaf@mriczo:~/liberit/kyut/ds3231-rtc$ cat ds3231-set-time.yaml
esphome:
name: ds3231-set-time
esp32:
board: esp32-c3-devkitm-1
framework:
type: arduino
i2c:
sda: GPIO20
scl: GPIO21
scan: true
logger:
wifi:
output_power: 8.5dB
networks:
- ssid: "yourWifiNetwork"
password: "yourPassword"
time:
- platform: sntp
id: sntp_time
timezone: America/Toronto
custom_component:
- lambda: |-
class DS3231Component : public PollingComponent {
public:
DS3231Component(esphome::time::RealTimeClock *rtc) : PollingComponent(10000), rtc_(rtc) {}
void setup() override {
if (!Wire.requestFrom(0x68, 1)) {
ESP_LOGE("DS3231", "RTC not found at I2C address 0x68");
return;
}
ESP_LOGI("DS3231", "RTC found at I2C address 0x68");
auto now = this->rtc_->now();
if (now.is_valid()) {
set_time(now.year, now.month, now.day_of_month, now.hour, now.minute, now.second, now.day_of_week);
ESP_LOGI("DS3231", "RTC time set to %04d-%02d-%02d %02d:%02d:%02d",
now.year, now.month, now.day_of_month, now.hour, now.minute, now.second);
} else {
ESP_LOGE("DS3231", "SNTP time is not valid; cannot set RTC");
}
}
void update() override {
auto time = get_time();
if (time.year > 0) {
ESP_LOGI("DS3231", "Current RTC Time: %04d-%02d-%02d %02d:%02d:%02d",
time.year, time.month, time.day, time.hour, time.minute, time.second);
} else {
ESP_LOGE("DS3231", "Failed to read time from RTC");
}
}
void set_time(int year, int month, int day, int hour, int minute, int second, int day_of_week) {
Wire.beginTransmission(0x68);
Wire.write(0); // Start at register 0
Wire.write(dec_to_bcd(second)); // Seconds
Wire.write(dec_to_bcd(minute)); // Minutes
Wire.write(dec_to_bcd(hour)); // Hours
Wire.write(dec_to_bcd(day_of_week)); // Day of the week
Wire.write(dec_to_bcd(day)); // Day of the month
Wire.write(dec_to_bcd(month)); // Month
Wire.write(dec_to_bcd(year - 2000)); // Year
Wire.endTransmission();
}
struct Time {
int year;
int month;
int day;
int hour;
int minute;
int second;
int day_of_week;
};
Time get_time() {
Wire.beginTransmission(0x68);
Wire.write(0); // Start at register 0
Wire.endTransmission();
if (Wire.requestFrom(0x68, 7) != 7) {
ESP_LOGE("DS3231", "Failed to read time registers");
return Time{0, 0, 0, 0, 0, 0, 0};
}
uint8_t second = bcd_to_dec(Wire.read());
uint8_t minute = bcd_to_dec(Wire.read());
uint8_t hour = bcd_to_dec(Wire.read());
uint8_t day_of_week = bcd_to_dec(Wire.read());
uint8_t day = bcd_to_dec(Wire.read());
uint8_t month = bcd_to_dec(Wire.read());
uint16_t year = bcd_to_dec(Wire.read()) + 2000;
return Time{year, month, day, hour, minute, second, day_of_week};
}
private:
esphome::time::RealTimeClock *rtc_;
uint8_t dec_to_bcd(int val) {
return ((val / 10 * 16) + (val % 10));
}
int bcd_to_dec(uint8_t val) {
return ((val / 16 * 10) + (val % 16));
}
};
auto my_rtc = new DS3231Component(id(sntp_time));
App.register_component(my_rtc);
return {};
htaf@mriczo:~/liberit/kyut/ds3231-rtc$
htaf@mriczo:~/liberit/kyut/ds3231-rtc$ cat ds3231-read-time.yaml
esphome:
name: ds3231-read-time
platform: ESP32
board: esp32-c3-devkitm-1
i2c:
sda: GPIO20 # Adjust to your setup
scl: GPIO21
scan: true
logger:
# Use the DS1307 platform to interact with the DS3231 RTC
time:
- platform: ds1307
id: rtc_time
timezone: America/Toronto
on_time_sync:
then:
- logger.log: "Time synchronized with RTC."
- lambda: |-
auto now = id(rtc_time).now();
ESP_LOGI("RTC", "Current time: %04d-%02d-%02d %02d:%02d:%02d",
now.year, now.month, now.day_of_month, now.hour, now.minute, now.second);
interval:
- interval: 5s
then:
- lambda: |-
auto now = id(rtc_time).now();
ESP_LOGI("Time", "Current time: %04d-%02d-%02d %02d:%02d:%02d",
now.year, now.month, now.day_of_month, now.hour, now.minute, now.second);
htaf@mriczo:~/liberit/kyut/ds3231-rtc$
oh and hey you guys wanted the alarm via SQW pin, here is how to set it up, for every minute on the first second, check the set_alarm() code if you need a different interval:
esphome:
name: ds3231-alarm-test
platform: ESP32
board: esp32-c3-devkitm-1
i2c:
sda: GPIO20 # Adjust to your wiring
scl: GPIO21
scan: true
logger:
binary_sensor:
- platform: gpio
pin:
number: GPIO9 # Pin connected to SQW/INT from DS3231
mode: INPUT_PULLUP
name: "DS3231 Alarm Trigger"
filters:
- delayed_off: 10ms # Debounce to avoid false positives
on_press:
- logger.log: "Alarm Detected!" # Log when interrupt occurs
custom_component:
- lambda: |-
class DS3231Alarm : public Component {
public:
void setup() override {
if (!Wire.requestFrom(0x68, 1)) {
ESP_LOGE("DS3231", "RTC not found at I2C address 0x68");
return;
}
ESP_LOGI("DS3231", "RTC found at I2C address 0x68");
// Set up the alarm to trigger every 6 seconds
set_alarm();
}
void set_alarm() {
Wire.beginTransmission(0x68);
Wire.write(0x07); // Start at Alarm1 seconds register
Wire.write(0x01); // A1M1=0: Match on first second
Wire.write(0x80); // A1M2=1: Match any minute
Wire.write(0x80); // A1M3=1: Match any hour
Wire.write(0x80); // A1M4=1: Match any day
Wire.endTransmission();
// Enable Alarm1 interrupt and disable square wave
Wire.beginTransmission(0x68);
Wire.write(0x0E); // Control register
Wire.write(0x05); // INTCN=1 (enable interrupt), A1IE=1 (enable Alarm1)
Wire.endTransmission();
ESP_LOGI("DS3231", "Alarm set to trigger every 6 seconds.");
}
void loop() override {
// Check if Alarm1 triggered by reading the status register
Wire.beginTransmission(0x68);
Wire.write(0x0F); // Status register
Wire.endTransmission();
Wire.requestFrom(0x68, 1);
uint8_t status = Wire.read();
if (status & 0x01) { // Alarm1 flag (A1F) is set
ESP_LOGI("DS3231", "Alarm Triggered!");
// Clear the Alarm1 flag
Wire.beginTransmission(0x68);
Wire.write(0x0F); // Status register
Wire.write(status & ~0x01); // Clear A1F
Wire.endTransmission();
}
}
};
auto my_alarm = new DS3231Alarm();
App.register_component(my_alarm);
return {};
@elspru thanks for the example, does this work with an esp32-s3 and esp-idf framework? i have this config:
substitutions:
device_name: "v"
friendly_name: "V"
ip_address: 192.168.188.190
device_description: "Project v. - IP: ${ip_address}"
esphome:
name: ${device_name}
comment: ${device_description}
friendly_name: ${friendly_name}
min_version: 2024.11.0
name_add_mac_suffix: false
libraries:
# a library bundled with Arduino
- Wire
esp32:
board: esp32-s3-devkitc-1
framework:
type: esp-idf
# Enable logging
logger:
# Enable Home Assistant API
api:
reboot_timeout: 0s
encryption:
key: !secret api_encryption_key
# Allow Over-The-Air updates
ota:
- platform: esphome
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
# Optional manual IP
manual_ip:
static_ip: ${ip_address}
gateway: 192.168.188.1
subnet: 255.255.255.0
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: ${device_name}-FH
password: !secret ap_password
i2c:
sda: GPIO48
scl: GPIO47
scan: true
id: bus_a
binary_sensor:
- platform: gpio
pin:
number: GPIO9 # Pin connected to SQW/INT from DS3231
mode: INPUT_PULLUP
name: "DS3231 Alarm Trigger"
filters:
- delayed_off: 10ms # Debounce to avoid false positives
on_press:
- logger.log: "Alarm Detected!" # Log when interrupt occurs
custom_component:
- lambda: |-
class DS3231Alarm : public Component {
public:
void setup() override {
if (!Wire.requestFrom(0x68, 1)) {
ESP_LOGE("DS3231", "RTC not found at I2C address 0x68");
return;
}
ESP_LOGI("DS3231", "RTC found at I2C address 0x68");
// Set up the alarm to trigger every 6 seconds
set_alarm();
}
void set_alarm() {
Wire.beginTransmission(0x68);
Wire.write(0x07); // Start at Alarm1 seconds register
Wire.write(0x01); // A1M1=0: Match on first second
Wire.write(0x80); // A1M2=1: Match any minute
Wire.write(0x80); // A1M3=1: Match any hour
Wire.write(0x80); // A1M4=1: Match any day
Wire.endTransmission();
// Enable Alarm1 interrupt and disable square wave
Wire.beginTransmission(0x68);
Wire.write(0x0E); // Control register
Wire.write(0x05); // INTCN=1 (enable interrupt), A1IE=1 (enable Alarm1)
Wire.endTransmission();
ESP_LOGI("DS3231", "Alarm set to trigger every 6 seconds.");
}
void loop() override {
// Check if Alarm1 triggered by reading the status register
Wire.beginTransmission(0x68);
Wire.write(0x0F); // Status register
Wire.endTransmission();
Wire.requestFrom(0x68, 1);
uint8_t status = Wire.read();
if (status & 0x01) { // Alarm1 flag (A1F) is set
ESP_LOGI("DS3231", "Alarm Triggered!");
// Clear the Alarm1 flag
Wire.beginTransmission(0x68);
Wire.write(0x0F); // Status register
Wire.write(status & ~0x01); // Clear A1F
Wire.endTransmission();
}
}
};
auto my_alarm = new DS3231Alarm();
App.register_component(my_alarm);
return {};
and when i compile the program i get this error:
Library Manager: Installing Wire
Warning! Could not find the package with 'Wire' requirements for your system 'linux_x86_64'
Library Manager: Installing Wire
Warning! Could not find the package with 'Wire' requirements for your system 'linux_x86_64'
Dependency Graph
........
........
Compiling .pioenvs/vaso-smart/console/linenoise/linenoise.c.o
/root/config/vaso-smart.yaml: In member function 'virtual void setup()::<lambda()>::DS3231Alarm::setup()':
/root/config/vaso-smart.yaml:116:16: error: 'Wire' was not declared in this scope
116 | if (!Wire.requestFrom(0x68, 1)) {
| ^~~~
/root/config/vaso-smart.yaml: In member function 'void setup()::<lambda()>::DS3231Alarm::set_alarm()':
/root/config/vaso-smart.yaml:127:11: error: 'Wire' was not declared in this scope
127 | Wire.beginTransmission(0x68);
| ^~~~
/root/config/vaso-smart.yaml: In member function 'virtual void setup()::<lambda()>::DS3231Alarm::loop()':
/root/config/vaso-smart.yaml:147:11: error: 'Wire' was not declared in this scope
147 | Wire.beginTransmission(0x68);
| ^~~~
Compiling .pioenvs/vaso-smart/console/argtable3/arg_cmd.c.o
Thanks!
Hell0 i have the config
globals:
- id: last_on_relayone_timestamp
type: std::string
restore_value: true
initial_value: '"Never ON"'
- id: last_off_relayone_timestamp
type: std::string
restore_value: true
initial_value: '"Never OFF"'
- id: last_on_relaytwo_timestamp
type: std::string
restore_value: true
initial_value: '"Never ON"'
- id: last_off_relaytwo_timestamp
type: std::string
restore_value: true
initial_value: '"Never OFF"'
- id: last_on_relaythree_timestamp
type: std::string
restore_value: true
initial_value: '"Never ON"'
- id: last_off_relaythree_timestamp
type: std::string
restore_value: true
initial_value: '"Never OFF"'
- id: last_on_relayfour_timestamp
type: std::string
restore_value: true
initial_value: '"Never ON"'
- id: last_off_relayfour_timestamp
type: std::string
restore_value: true
initial_value: '"Never OFF"'
substitutions:
# Default name
name: "wateringdevice"
# Default friendly name
friendly_name: "wateringdevice"
# Allows ESP device to be automatically linked to an 'Area' in Home Assistant. Typically used for areas such as 'Lounge Room', 'Kitchen' etc
room: ""
# Description as appears in ESPHome & top of webserver page
device_description: "wateringdevice"
# Project Name
project_name: "wateringdevice"
# Projection version denotes the release version of the yaml file, allowing checking of deployed vs latest version
project_version: "v1.0.3"
# Restore the relay (GPO switch) upon reboot to state:
relay1_restore_mode: RESTORE_DEFAULT_OFF
relay2_restore_mode: RESTORE_DEFAULT_OFF
relay3_restore_mode: RESTORE_DEFAULT_OFF
relay4_restore_mode: RESTORE_DEFAULT_OFF
# Define a domain for this device to use. i.e. iot.home.lan (so device will appear as athom-smart-plug-v2.iot.home.lan in DNS/DHCP logs)
dns_domain: ".local"
# Set timezone of the smart plug. Useful if the plug is in a location different to the HA server. Can be entered in unix Country/Area format (i.e. "Australia/Sydney")
timezone: "Europe/Berlin"
# Set the duration between the sntp service polling ntp.org servers for an update
sntp_update_interval: 6h
# Network time servers for your region, enter from lowest to highest priority. To use local servers update as per zones or countries at: https://www.ntppool.org/zone/@
sntp_server_1: "0.pool.ntp.org"
sntp_server_2: "1.pool.ntp.org"
sntp_server_3: "2.pool.ntp.org"
# Enables faster network connections, with last connected SSID being connected to and no full scan for SSID being undertaken
wifi_fast_connect: "false"
# Define logging level: NONE, ERROR, WARN, INFO, DEBUG (Default), VERBOSE, VERY_VERBOSE
log_level: "DEBUG"
# Enable or disable the use of IPv6 networking on the device
ipv6_enable: "false"
esphome:
name: wateringdevice
on_boot:
- priority: 90
then:
esp32:
board: esp32dev
framework:
type: arduino
# Enable logging
logger:
# Enable Home Assistant API
#api:
# password: ""
ota:
password: ""
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
output_power: 15
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: $devicename
password: !secret ap_password
web_server:
port: 80
captive_portal:
output:
- platform: gpio
pin: GPIO23
id: led
- platform: gpio
pin: GPIO32
id: relay_pin_1
- platform: gpio
pin: GPIO33
id: relay_pin_2
- platform: gpio
pin: GPIO25
id: relay_pin_3
- platform: gpio
pin: GPIO26
id: relay_pin_4
# Define the switches based on the relay pins
switch:
- platform: output
id: relay_1
name: "Relay 1"
output: relay_pin_1
on_turn_on:
- lambda: |-
if (id(sntp_time).now().is_valid()) {
auto now = id(sntp_time).now();
char buffer[32];
snprintf(buffer, sizeof(buffer), "%04d-%02d-%02d %02d:%02d:%02d",
now.year, now.month, now.day_of_month,
now.hour, now.minute, now.second);
id(last_on_relayone_timestamp) = std::string(buffer);
id(last_on_relayone_text_sensor).publish_state(id(last_on_relayone_timestamp));
} else {
id(last_on_relayone_timestamp) = "Time invalid";
id(last_on_relayone_text_sensor).publish_state("Time invalid");
}
on_turn_off:
- lambda: |-
if (id(sntp_time).now().is_valid()) {
auto now = id(sntp_time).now();
char buffer[32];
snprintf(buffer, sizeof(buffer), "%04d-%02d-%02d %02d:%02d:%02d",
now.year, now.month, now.day_of_month,
now.hour, now.minute, now.second);
id(last_off_relayone_timestamp) = std::string(buffer);
id(last_off_relayone_text_sensor).publish_state(id(last_off_relayone_timestamp));
} else {
id(last_off_relayone_timestamp) = "Time invalid";
id(last_off_relayone_text_sensor).publish_state("Time invalid");
}
- platform: output
id: relay_2
name: "Relay 2"
output: relay_pin_2
on_turn_on:
- lambda: |-
if (id(sntp_time).now().is_valid()) {
auto now = id(sntp_time).now();
char buffer[32];
snprintf(buffer, sizeof(buffer), "%04d-%02d-%02d %02d:%02d:%02d",
now.year, now.month, now.day_of_month,
now.hour, now.minute, now.second);
id(last_on_relaytwo_timestamp) = std::string(buffer);
id(last_on_relaytwo_text_sensor).publish_state(id(last_on_relaytwo_timestamp));
} else {
id(last_on_relaytwo_timestamp) = "Time invalid";
id(last_on_relaytwo_text_sensor).publish_state("Time invalid");
}
on_turn_off:
- lambda: |-
if (id(sntp_time).now().is_valid()) {
auto now = id(sntp_time).now();
char buffer[32];
snprintf(buffer, sizeof(buffer), "%04d-%02d-%02d %02d:%02d:%02d",
now.year, now.month, now.day_of_month,
now.hour, now.minute, now.second);
id(last_off_relaytwo_timestamp) = std::string(buffer);
id(last_off_relaytwo_text_sensor).publish_state(id(last_off_relaytwo_timestamp));
} else {
id(last_off_relaytwo_timestamp) = "Time invalid";
id(last_off_relaytwo_text_sensor).publish_state("Time invalid");
}
- platform: output
id: relay_3
name: "Relay 3"
output: relay_pin_3
on_turn_on:
- lambda: |-
if (id(sntp_time).now().is_valid()) {
auto now = id(sntp_time).now();
char buffer[32];
snprintf(buffer, sizeof(buffer), "%04d-%02d-%02d %02d:%02d:%02d",
now.year, now.month, now.day_of_month,
now.hour, now.minute, now.second);
id(last_on_relaythree_timestamp) = std::string(buffer);
id(last_on_relaythree_text_sensor).publish_state(id(last_on_relaythree_timestamp));
} else {
id(last_on_relaythree_timestamp) = "Time invalid";
id(last_on_relaythree_text_sensor).publish_state("Time invalid");
}
on_turn_off:
- lambda: |-
if (id(sntp_time).now().is_valid()) {
auto now = id(sntp_time).now();
char buffer[32];
snprintf(buffer, sizeof(buffer), "%04d-%02d-%02d %02d:%02d:%02d",
now.year, now.month, now.day_of_month,
now.hour, now.minute, now.second);
id(last_off_relaythree_timestamp) = std::string(buffer);
id(last_off_relaythree_text_sensor).publish_state(id(last_off_relaythree_timestamp));
} else {
id(last_off_relaythree_timestamp) = "Time invalid";
id(last_off_relaythree_text_sensor).publish_state("Time invalid");
}
- platform: output
id: relay_4
name: "Relay 4"
output: relay_pin_4
on_turn_on:
- lambda: |-
if (id(sntp_time).now().is_valid()) {
auto now = id(sntp_time).now();
char buffer[32];
snprintf(buffer, sizeof(buffer), "%04d-%02d-%02d %02d:%02d:%02d",
now.year, now.month, now.day_of_month,
now.hour, now.minute, now.second);
id(last_on_relayfour_timestamp) = std::string(buffer);
id(last_on_relayfour_text_sensor).publish_state(id(last_on_relayfour_timestamp));
} else {
id(last_on_relayfour_timestamp) = "Time invalid";
id(last_on_relayfour_text_sensor).publish_state("Time invalid");
}
on_turn_off:
- lambda: |-
if (id(sntp_time).now().is_valid()) {
auto now = id(sntp_time).now();
char buffer[32];
snprintf(buffer, sizeof(buffer), "%04d-%02d-%02d %02d:%02d:%02d",
now.year, now.month, now.day_of_month,
now.hour, now.minute, now.second);
id(last_off_relayfour_timestamp) = std::string(buffer);
id(last_off_relayfour_text_sensor).publish_state(id(last_off_relayfour_timestamp));
} else {
id(last_off_relayfour_timestamp) = "Time invalid";
id(last_off_relayfour_text_sensor).publish_state("Time invalid");
}
debug:
update_interval: 60s
interval:
- interval: 1000ms
then:
- output.turn_on: led
- delay: 500ms
- output.turn_off: led
custom_component:
- id: tss
lambda: |-
class DS3231Component : public PollingComponent {
public:
DS3231Component(esphome::time::RealTimeClock *rtc) : PollingComponent(10000), rtc_(rtc) {}
void setup() override {
if (!Wire.requestFrom(0x68, 1)) {
ESP_LOGE("DS3231", "RTC not found at I2C address 0x68");
return;
}
ESP_LOGI("DS3231", "RTC found at I2C address 0x68");
auto now = this->rtc_->now();
if (now.is_valid()) {
set_time(now.year, now.month, now.day_of_month, now.hour, now.minute, now.second, now.day_of_week);
ESP_LOGI("DS3231", "RTC time set to %04d-%02d-%02d %02d:%02d:%02d",
now.year, now.month, now.day_of_month, now.hour, now.minute, now.second);
} else {
ESP_LOGE("DS3231", "SNTP time is not valid; cannot set RTC");
}
}
void update() override {
auto time = get_time();
if (time.year > 0) {
ESP_LOGI("DS3231", "Current RTC Time: %04d-%02d-%02d %02d:%02d:%02d",
time.year, time.month, time.day, time.hour, time.minute, time.second);
} else {
ESP_LOGE("DS3231", "Failed to read time from RTC");
}
}
void set_time(int year, int month, int day, int hour, int minute, int second, int day_of_week) {
Wire.beginTransmission(0x68);
Wire.write(0); // Start at register 0
Wire.write(dec_to_bcd(second)); // Seconds
Wire.write(dec_to_bcd(minute)); // Minutes
Wire.write(dec_to_bcd(hour)); // Hours
Wire.write(dec_to_bcd(day_of_week)); // Day of the week
Wire.write(dec_to_bcd(day)); // Day of the month
Wire.write(dec_to_bcd(month)); // Month
Wire.write(dec_to_bcd(year - 2000)); // Year
Wire.endTransmission();
}
struct Time {
int year;
int month;
int day;
int hour;
int minute;
int second;
int day_of_week;
};
Time get_time() {
Wire.beginTransmission(0x68);
Wire.write(0); // Start at register 0
Wire.endTransmission();
if (Wire.requestFrom(0x68, 7) != 7) {
ESP_LOGE("DS3231", "Failed to read time registers");
return Time{0, 0, 0, 0, 0, 0, 0};
}
uint8_t second = bcd_to_dec(Wire.read());
uint8_t minute = bcd_to_dec(Wire.read());
uint8_t hour = bcd_to_dec(Wire.read());
uint8_t day_of_week = bcd_to_dec(Wire.read());
uint8_t day = bcd_to_dec(Wire.read());
uint8_t month = bcd_to_dec(Wire.read());
uint16_t year = bcd_to_dec(Wire.read()) + 2000;
return Time{year, month, day, hour, minute, second, day_of_week};
}
private:
esphome::time::RealTimeClock *rtc_;
uint8_t dec_to_bcd(int val) {
return ((val / 10 * 16) + (val % 10));
}
int bcd_to_dec(uint8_t val) {
return ((val / 16 * 10) + (val % 16));
}
};
auto my_rtc = new DS3231Component(id(sntp_time));
App.register_component(my_rtc);
return {my_rtc};
text_sensor:
- platform: debug
device:
name: "Device Info"
reset_reason:
name: "Reset Reason"
- platform: wifi_info
ip_address:
name: "IP Address"
entity_category: diagnostic
ssid:
name: "Connected SSID"
entity_category: diagnostic
mac_address:
name: "Mac Address"
entity_category: diagnostic
- platform: template
name: "Last Relay one ON Time"
id: last_on_relayone_text_sensor
lambda: |-
return id(last_on_relayone_timestamp);
- platform: template
name: "Last Relay one OFF Time"
id: last_off_relayone_text_sensor
lambda: |-
return id(last_off_relayone_timestamp);
- platform: template
name: "Last Relay two ON Time"
id: last_on_relaytwo_text_sensor
lambda: |-
return id(last_on_relaytwo_timestamp);
- platform: template
name: "Last Relay two OFF Time"
id: last_off_relaytwo_text_sensor
lambda: |-
return id(last_off_relaytwo_timestamp);
- platform: template
name: "Last Relay three ON Time"
id: last_on_relaythree_text_sensor
lambda: |-
return id(last_on_relaythree_timestamp);
- platform: template
name: "Last Relay three OFF Time"
id: last_off_relaythree_text_sensor
lambda: |-
return id(last_off_relaythree_timestamp);
- platform: template
name: "Last Relay four ON Time"
id: last_on_relayfour_text_sensor
lambda: |-
return id(last_on_relayfour_timestamp);
- platform: template
name: "Last Relay four OFF Time"
id: last_off_relayfour_text_sensor
lambda: |-
return id(last_off_relayfour_timestamp);
# Creates a sensor showing when the device was last restarted
- platform: template
name: 'Last Restart'
id: device_last_restart
icon: mdi:clock
entity_category: diagnostic
# device_class: timestamp
# Creates a sensor of the uptime of the device, in formatted days, hours, minutes and seconds
- platform: template
name: "Uptime"
entity_category: diagnostic
lambda: |-
int seconds = (id(uptime_sensor).state);
int days = seconds / (24 * 3600);
seconds = seconds % (24 * 3600);
int hours = seconds / 3600;
seconds = seconds % 3600;
int minutes = seconds / 60;
seconds = seconds % 60;
if ( days > 3650 ) {
return { "Starting up" };
} else if ( days ) {
return { (String(days) +"d " + String(hours) +"h " + String(minutes) +"m "+ String(seconds) +"s").c_str() };
} else if ( hours ) {
return { (String(hours) +"h " + String(minutes) +"m "+ String(seconds) +"s").c_str() };
} else if ( minutes ) {
return { (String(minutes) +"m "+ String(seconds) +"s").c_str() };
} else {
return { (String(seconds) +"s").c_str() };
}
icon: mdi:clock-start
time:
- platform: sntp
id: sntp_time
# Define the timezone of the device
timezone: "${timezone}"
# Change sync interval from default 5min to 6 hours (or as set in substitutions)
update_interval: ${sntp_update_interval}
# Set specific sntp servers to use
servers:
- "${sntp_server_1}"
- "${sntp_server_2}"
- "${sntp_server_3}"
# Publish the time the device was last restarted
on_time_sync:
then:
- lambda: |-
struct Time {
int year;
int month;
int day;
int hour;
int minute;
int second;
int day_of_week;
};
Time r = static_cast< DS3231Component*> (id(tss).get_component(0))->get_time();
# Update last restart time, but only once.
- if:
condition:
lambda: 'return id(device_last_restart).state == "";'
then:
- text_sensor.template.publish:
id: device_last_restart
state: !lambda 'return id(sntp_time).now().strftime("%a %d %b %Y - %I:%M:%S %p");'
binary_sensor:
- platform: status
name: "Status"
entity_category: diagnostic
sensor:
- platform: uptime
name: "Uptime Sensor"
id: uptime_sensor
entity_category: diagnostic
internal: true
- platform: wifi_signal
name: "WiFi Signal dB"
id: wifi_signal_db
update_interval: 60s
entity_category: "diagnostic"
- platform: copy
source_id: wifi_signal_db
name: "WiFi Signal Percent"
filters:
- lambda: return min(max(2 * (x + 100.0), 0.0), 100.0);
unit_of_measurement: "Signal %"
entity_category: "diagnostic"
device_class: ""
button:
- platform: restart
name: "Restart"
entity_category: config
- platform: factory_reset
name: "Factory Reset"
id: Reset
entity_category: config
- platform: safe_mode
name: "Safe Mode"
internal: false
entity_category: config
- platform: template
name: "Relais2 fuer x% Sekunden"
on_press:
- switch.turn_on: relay_2
- delay: !lambda |-
int delay_ms = (int(id(relais_two_zeit_prozent).state) * 600);
// 100% * 600ms = 60s max
return delay_ms;
- switch.turn_off: relay_2
number:
- platform: template
name: "Relais2 Zeit (%)"
id: relais_two_zeit_prozent
min_value: 0
max_value: 100
step: 1
optimistic: true
mode: slider
i2c:
sda: GPIO21 # Passe diese Pins an dein Setup an
scl: GPIO22
scan: true # Zeigt I²C-Geräte im Log
but I get this error
watering.yaml:501:30: error: 'DS3231Component' does not name a type watering.yaml:501:45: error: expected '>' before '*' token watering.yaml:501:45: error: expected '(' before '*' token watering.yaml:501:46: error: expected primary-expression before '>' token watering.yaml:501:72: error: 'class esphome::Component' has no member named 'get_time'; did you mean 'set_timeout'? watering.yaml:501:82: error: expected ')' before ';' token *** [.pioenvs/wateringdevice/src/main.cpp.o] Error 1
how do i call the function correctly?
As custom components are not a possibility anymore and I needed a to have my DS3231 fully work with for porting my led matrix project to esphome, I converted the rtc sync and get time to an external component, you just have to add this to your yaml:
external_components:
- source: github://zubrick/zmatrix-esphome@main
components: [ ds3231 ]
refresh: 10min
ds3231:
id: ds3231_time
time_id: sntp_time
update_interval: 240min
time:
- platform: sntp
id: sntp_time
timezone: "Europe/Zurich"
the update_interval parameter lets you decide how often you want to try syncing the rtc to the ntp (or any other time source you may use)
you can then call id(ds3231_id).get_time() in your lambda to get the rtc time returned as the Time structure
Sorry, I didn't implement the alarm part, I don't have any use for that