mirror of
https://github.com/esphome/esphome.git
synced 2026-01-10 04:00:51 -07:00
[bm8563] Add bm8563 component (#11616)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
This commit is contained in:
@@ -72,6 +72,7 @@ esphome/components/bl0942/* @dbuezas @dwmw2
|
||||
esphome/components/ble_client/* @buxtronix @clydebarrow
|
||||
esphome/components/ble_nus/* @tomaszduda23
|
||||
esphome/components/bluetooth_proxy/* @bdraco @jesserockz
|
||||
esphome/components/bm8563/* @abmantis
|
||||
esphome/components/bme280_base/* @esphome/core
|
||||
esphome/components/bme280_spi/* @apbodrov
|
||||
esphome/components/bme680_bsec/* @trvrnrth
|
||||
|
||||
1
esphome/components/bm8563/__init__.py
Normal file
1
esphome/components/bm8563/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
CODEOWNERS = ["@abmantis"]
|
||||
198
esphome/components/bm8563/bm8563.cpp
Normal file
198
esphome/components/bm8563/bm8563.cpp
Normal file
@@ -0,0 +1,198 @@
|
||||
#include "bm8563.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::bm8563 {
|
||||
|
||||
static const char *const TAG = "bm8563";
|
||||
|
||||
static constexpr uint8_t CONTROL_STATUS_1_REG = 0x00;
|
||||
static constexpr uint8_t CONTROL_STATUS_2_REG = 0x01;
|
||||
static constexpr uint8_t TIME_FIRST_REG = 0x02; // Time uses reg 2, 3, 4
|
||||
static constexpr uint8_t DATE_FIRST_REG = 0x05; // Date uses reg 5, 6, 7, 8
|
||||
static constexpr uint8_t TIMER_CONTROL_REG = 0x0E;
|
||||
static constexpr uint8_t TIMER_VALUE_REG = 0x0F;
|
||||
static constexpr uint8_t CLOCK_1_HZ = 0x82;
|
||||
static constexpr uint8_t CLOCK_1_60_HZ = 0x83;
|
||||
// Maximum duration: 255 minutes (at 1/60 Hz) = 15300 seconds
|
||||
static constexpr uint32_t MAX_TIMER_DURATION_S = 255 * 60;
|
||||
|
||||
void BM8563::setup() {
|
||||
if (!this->write_byte_16(CONTROL_STATUS_1_REG, 0)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void BM8563::update() { this->read_time(); }
|
||||
|
||||
void BM8563::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "BM8563:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
|
||||
}
|
||||
}
|
||||
|
||||
void BM8563::start_timer(uint32_t duration_s) {
|
||||
this->clear_irq_();
|
||||
this->set_timer_irq_(duration_s);
|
||||
}
|
||||
|
||||
void BM8563::write_time() {
|
||||
auto now = time::RealTimeClock::utcnow();
|
||||
if (!now.is_valid()) {
|
||||
ESP_LOGE(TAG, "Invalid system time, not syncing to RTC.");
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Writing time: %i-%i-%i %i, %i:%i:%i", now.year, now.month, now.day_of_month, now.day_of_week, now.hour,
|
||||
now.minute, now.second);
|
||||
|
||||
this->set_time_(now);
|
||||
this->set_date_(now);
|
||||
}
|
||||
|
||||
void BM8563::read_time() {
|
||||
ESPTime rtc_time;
|
||||
this->get_time_(rtc_time);
|
||||
this->get_date_(rtc_time);
|
||||
rtc_time.day_of_year = 1; // unused by recalc_timestamp_utc, but needs to be valid
|
||||
ESP_LOGD(TAG, "Read time: %i-%i-%i %i, %i:%i:%i", rtc_time.year, rtc_time.month, rtc_time.day_of_month,
|
||||
rtc_time.day_of_week, rtc_time.hour, rtc_time.minute, rtc_time.second);
|
||||
|
||||
rtc_time.recalc_timestamp_utc(false);
|
||||
if (!rtc_time.is_valid()) {
|
||||
ESP_LOGE(TAG, "Invalid RTC time, not syncing to system clock.");
|
||||
return;
|
||||
}
|
||||
time::RealTimeClock::synchronize_epoch_(rtc_time.timestamp);
|
||||
}
|
||||
|
||||
uint8_t BM8563::bcd2_to_byte_(uint8_t value) {
|
||||
const uint8_t tmp = ((value & 0xF0) >> 0x4) * 10;
|
||||
return tmp + (value & 0x0F);
|
||||
}
|
||||
|
||||
uint8_t BM8563::byte_to_bcd2_(uint8_t value) {
|
||||
const uint8_t bcdhigh = value / 10;
|
||||
value -= bcdhigh * 10;
|
||||
return (bcdhigh << 4) | value;
|
||||
}
|
||||
|
||||
void BM8563::get_time_(ESPTime &time) {
|
||||
uint8_t buf[3] = {0};
|
||||
this->read_register(TIME_FIRST_REG, buf, 3);
|
||||
|
||||
time.second = this->bcd2_to_byte_(buf[0] & 0x7f);
|
||||
time.minute = this->bcd2_to_byte_(buf[1] & 0x7f);
|
||||
time.hour = this->bcd2_to_byte_(buf[2] & 0x3f);
|
||||
}
|
||||
|
||||
void BM8563::set_time_(const ESPTime &time) {
|
||||
uint8_t buf[3] = {this->byte_to_bcd2_(time.second), this->byte_to_bcd2_(time.minute), this->byte_to_bcd2_(time.hour)};
|
||||
this->write_register_(TIME_FIRST_REG, buf, 3);
|
||||
}
|
||||
|
||||
void BM8563::get_date_(ESPTime &time) {
|
||||
uint8_t buf[4] = {0};
|
||||
this->read_register(DATE_FIRST_REG, buf, sizeof(buf));
|
||||
|
||||
time.day_of_month = this->bcd2_to_byte_(buf[0] & 0x3f);
|
||||
time.day_of_week = this->bcd2_to_byte_(buf[1] & 0x07);
|
||||
time.month = this->bcd2_to_byte_(buf[2] & 0x1f);
|
||||
|
||||
uint8_t year_byte = this->bcd2_to_byte_(buf[3] & 0xff);
|
||||
|
||||
if (buf[2] & 0x80) {
|
||||
time.year = 1900 + year_byte;
|
||||
} else {
|
||||
time.year = 2000 + year_byte;
|
||||
}
|
||||
}
|
||||
|
||||
void BM8563::set_date_(const ESPTime &time) {
|
||||
uint8_t buf[4] = {
|
||||
this->byte_to_bcd2_(time.day_of_month),
|
||||
this->byte_to_bcd2_(time.day_of_week),
|
||||
this->byte_to_bcd2_(time.month),
|
||||
this->byte_to_bcd2_(time.year % 100),
|
||||
};
|
||||
|
||||
if (time.year < 2000) {
|
||||
buf[2] = buf[2] | 0x80;
|
||||
}
|
||||
|
||||
this->write_register_(DATE_FIRST_REG, buf, 4);
|
||||
}
|
||||
|
||||
void BM8563::write_byte_(uint8_t reg, uint8_t value) {
|
||||
if (!this->write_byte(reg, value)) {
|
||||
ESP_LOGE(TAG, "Failed to write byte 0x%02X with value 0x%02X", reg, value);
|
||||
}
|
||||
}
|
||||
|
||||
void BM8563::write_register_(uint8_t reg, const uint8_t *data, size_t len) {
|
||||
if (auto error = this->write_register(reg, data, len); error != i2c::ErrorCode::NO_ERROR) {
|
||||
ESP_LOGE(TAG, "Failed to write register 0x%02X with %zu bytes", reg, len);
|
||||
}
|
||||
}
|
||||
|
||||
optional<uint8_t> BM8563::read_register_(uint8_t reg) {
|
||||
uint8_t data;
|
||||
if (auto error = this->read_register(reg, &data, 1); error != i2c::ErrorCode::NO_ERROR) {
|
||||
ESP_LOGE(TAG, "Failed to read register 0x%02X", reg);
|
||||
return {};
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
void BM8563::set_timer_irq_(uint32_t duration_s) {
|
||||
ESP_LOGI(TAG, "Timer Duration: %u s", duration_s);
|
||||
|
||||
if (duration_s > MAX_TIMER_DURATION_S) {
|
||||
ESP_LOGW(TAG, "Timer duration %u s exceeds maximum %u s", duration_s, MAX_TIMER_DURATION_S);
|
||||
return;
|
||||
}
|
||||
|
||||
if (duration_s > 255) {
|
||||
uint8_t duration_minutes = duration_s / 60;
|
||||
this->write_byte_(TIMER_VALUE_REG, duration_minutes);
|
||||
this->write_byte_(TIMER_CONTROL_REG, CLOCK_1_60_HZ);
|
||||
} else {
|
||||
this->write_byte_(TIMER_VALUE_REG, duration_s);
|
||||
this->write_byte_(TIMER_CONTROL_REG, CLOCK_1_HZ);
|
||||
}
|
||||
|
||||
auto maybe_ctrl_status_2 = this->read_register_(CONTROL_STATUS_2_REG);
|
||||
if (!maybe_ctrl_status_2.has_value()) {
|
||||
ESP_LOGE(TAG, "Failed to read CONTROL_STATUS_2_REG");
|
||||
return;
|
||||
}
|
||||
uint8_t ctrl_status_2_reg_value = maybe_ctrl_status_2.value();
|
||||
ctrl_status_2_reg_value |= (1 << 0);
|
||||
ctrl_status_2_reg_value &= ~(1 << 7);
|
||||
this->write_byte_(CONTROL_STATUS_2_REG, ctrl_status_2_reg_value);
|
||||
}
|
||||
|
||||
void BM8563::clear_irq_() {
|
||||
auto maybe_data = this->read_register_(CONTROL_STATUS_2_REG);
|
||||
if (!maybe_data.has_value()) {
|
||||
ESP_LOGE(TAG, "Failed to read CONTROL_STATUS_2_REG");
|
||||
return;
|
||||
}
|
||||
uint8_t data = maybe_data.value();
|
||||
this->write_byte_(CONTROL_STATUS_2_REG, data & 0xf3);
|
||||
}
|
||||
|
||||
void BM8563::disable_irq_() {
|
||||
this->clear_irq_();
|
||||
auto maybe_data = this->read_register_(CONTROL_STATUS_2_REG);
|
||||
if (!maybe_data.has_value()) {
|
||||
ESP_LOGE(TAG, "Failed to read CONTROL_STATUS_2_REG");
|
||||
return;
|
||||
}
|
||||
uint8_t data = maybe_data.value();
|
||||
this->write_byte_(CONTROL_STATUS_2_REG, data & 0xfc);
|
||||
}
|
||||
|
||||
} // namespace esphome::bm8563
|
||||
57
esphome/components/bm8563/bm8563.h
Normal file
57
esphome/components/bm8563/bm8563.h
Normal file
@@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/time/real_time_clock.h"
|
||||
|
||||
namespace esphome::bm8563 {
|
||||
|
||||
class BM8563 : public time::RealTimeClock, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
void update() override;
|
||||
void dump_config() override;
|
||||
|
||||
void write_time();
|
||||
void read_time();
|
||||
void start_timer(uint32_t duration_s);
|
||||
|
||||
private:
|
||||
void get_time_(ESPTime &time);
|
||||
void get_date_(ESPTime &time);
|
||||
|
||||
void set_time_(const ESPTime &time);
|
||||
void set_date_(const ESPTime &time);
|
||||
|
||||
void set_timer_irq_(uint32_t duration_s);
|
||||
void clear_irq_();
|
||||
void disable_irq_();
|
||||
|
||||
void write_byte_(uint8_t reg, uint8_t value);
|
||||
void write_register_(uint8_t reg, const uint8_t *data, size_t len);
|
||||
optional<uint8_t> read_register_(uint8_t reg);
|
||||
|
||||
uint8_t bcd2_to_byte_(uint8_t value);
|
||||
uint8_t byte_to_bcd2_(uint8_t value);
|
||||
};
|
||||
|
||||
template<typename... Ts> class WriteAction : public Action<Ts...>, public Parented<BM8563> {
|
||||
public:
|
||||
void play(const Ts &...x) override { this->parent_->write_time(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class ReadAction : public Action<Ts...>, public Parented<BM8563> {
|
||||
public:
|
||||
void play(const Ts &...x) override { this->parent_->read_time(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class TimerAction : public Action<Ts...>, public Parented<BM8563> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(uint32_t, duration)
|
||||
|
||||
void play(const Ts &...x) override {
|
||||
auto duration = this->duration_.value(x...);
|
||||
this->parent_->start_timer(duration);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace esphome::bm8563
|
||||
80
esphome/components/bm8563/time.py
Normal file
80
esphome/components/bm8563/time.py
Normal file
@@ -0,0 +1,80 @@
|
||||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import i2c, time
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_DURATION, CONF_ID
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
I2C_ADDR = 0x51
|
||||
|
||||
bm8563_ns = cg.esphome_ns.namespace("bm8563")
|
||||
BM8563 = bm8563_ns.class_("BM8563", time.RealTimeClock, i2c.I2CDevice)
|
||||
WriteAction = bm8563_ns.class_("WriteAction", automation.Action)
|
||||
ReadAction = bm8563_ns.class_("ReadAction", automation.Action)
|
||||
TimerAction = bm8563_ns.class_("TimerAction", automation.Action)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
time.TIME_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BM8563),
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
.extend(i2c.i2c_device_schema(I2C_ADDR))
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"bm8563.write_time",
|
||||
WriteAction,
|
||||
automation.maybe_simple_id(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(BM8563),
|
||||
}
|
||||
),
|
||||
)
|
||||
async def bm8563_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(
|
||||
"bm8563.start_timer",
|
||||
TimerAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(BM8563),
|
||||
cv.Required(CONF_DURATION): cv.templatable(cv.positive_time_period_seconds),
|
||||
}
|
||||
),
|
||||
)
|
||||
async def bm8563_start_timer_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
template_ = await cg.templatable(config[CONF_DURATION], args, cg.uint32)
|
||||
cg.add(var.set_duration(template_))
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"bm8563.read_time",
|
||||
ReadAction,
|
||||
automation.maybe_simple_id(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(BM8563),
|
||||
}
|
||||
),
|
||||
)
|
||||
async def bm8563_read_time_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
return var
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
await time.register_time(var, config)
|
||||
10
tests/components/bm8563/common.yaml
Normal file
10
tests/components/bm8563/common.yaml
Normal file
@@ -0,0 +1,10 @@
|
||||
esphome:
|
||||
on_boot:
|
||||
- bm8563.read_time
|
||||
- bm8563.write_time
|
||||
- bm8563.start_timer:
|
||||
duration: 300s
|
||||
|
||||
time:
|
||||
- platform: bm8563
|
||||
i2c_id: i2c_bus
|
||||
4
tests/components/bm8563/test.esp32-ard.yaml
Normal file
4
tests/components/bm8563/test.esp32-ard.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/esp32-ard.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
4
tests/components/bm8563/test.esp32-idf.yaml
Normal file
4
tests/components/bm8563/test.esp32-idf.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/esp32-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
4
tests/components/bm8563/test.esp8266-ard.yaml
Normal file
4
tests/components/bm8563/test.esp8266-ard.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/esp8266-ard.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
4
tests/components/bm8563/test.rp2040-ard.yaml
Normal file
4
tests/components/bm8563/test.rp2040-ard.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/rp2040-ard.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
Reference in New Issue
Block a user