mirror of
https://github.com/esphome/esphome.git
synced 2024-12-26 15:34:53 +01:00
Merge branch 'dev' into optolink
This commit is contained in:
commit
2d67ae6d51
42 changed files with 1630 additions and 214 deletions
|
@ -27,7 +27,7 @@ repos:
|
|||
- --branch=release
|
||||
- --branch=beta
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v3.9.0
|
||||
rev: v3.10.1
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: [--py39-plus]
|
||||
|
|
|
@ -49,6 +49,7 @@ esphome/components/ble_client/* @buxtronix
|
|||
esphome/components/bluetooth_proxy/* @jesserockz
|
||||
esphome/components/bme680_bsec/* @trvrnrth
|
||||
esphome/components/bmp3xx/* @martgras
|
||||
esphome/components/bmp581/* @kahrendt
|
||||
esphome/components/bp1658cj/* @Cossid
|
||||
esphome/components/bp5758d/* @Cossid
|
||||
esphome/components/button/* @esphome/core
|
||||
|
|
|
@ -31,6 +31,7 @@ ClearedTrigger = alarm_control_panel_ns.class_(
|
|||
)
|
||||
ArmAwayAction = alarm_control_panel_ns.class_("ArmAwayAction", automation.Action)
|
||||
ArmHomeAction = alarm_control_panel_ns.class_("ArmHomeAction", automation.Action)
|
||||
ArmNightAction = alarm_control_panel_ns.class_("ArmNightAction", automation.Action)
|
||||
DisarmAction = alarm_control_panel_ns.class_("DisarmAction", automation.Action)
|
||||
PendingAction = alarm_control_panel_ns.class_("PendingAction", automation.Action)
|
||||
TriggeredAction = alarm_control_panel_ns.class_("TriggeredAction", automation.Action)
|
||||
|
@ -117,6 +118,18 @@ async def alarm_action_arm_home_to_code(config, action_id, template_arg, args):
|
|||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"alarm_control_panel.arm_night", ArmNightAction, ALARM_CONTROL_PANEL_ACTION_SCHEMA
|
||||
)
|
||||
async def alarm_action_arm_night_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
if CONF_CODE in config:
|
||||
templatable_ = await cg.templatable(config[CONF_CODE], args, cg.std_string)
|
||||
cg.add(var.set_code(templatable_))
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"alarm_control_panel.disarm", DisarmAction, ALARM_CONTROL_PANEL_ACTION_SCHEMA
|
||||
)
|
||||
|
|
|
@ -85,6 +85,11 @@ void AlarmControlPanelCall::validate_() {
|
|||
this->state_.reset();
|
||||
return;
|
||||
}
|
||||
if (state == ACP_STATE_ARMED_NIGHT && (this->parent_->get_supported_features() & ACP_FEAT_ARM_NIGHT) == 0) {
|
||||
ESP_LOGW(TAG, "Cannot arm night when not supported");
|
||||
this->state_.reset();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ const LogString *alarm_control_panel_state_to_string(AlarmControlPanelState stat
|
|||
case ACP_STATE_ARMED_AWAY:
|
||||
return LOG_STR("ARMED_AWAY");
|
||||
case ACP_STATE_ARMED_NIGHT:
|
||||
return LOG_STR("NIGHT");
|
||||
return LOG_STR("ARMED_NIGHT");
|
||||
case ACP_STATE_ARMED_VACATION:
|
||||
return LOG_STR("ARMED_VACATION");
|
||||
case ACP_STATE_ARMED_CUSTOM_BYPASS:
|
||||
|
|
|
@ -67,6 +67,26 @@ template<typename... Ts> class ArmHomeAction : public Action<Ts...> {
|
|||
AlarmControlPanel *alarm_control_panel_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class ArmNightAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit ArmNightAction(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) {}
|
||||
|
||||
TEMPLATABLE_VALUE(std::string, code)
|
||||
|
||||
void play(Ts... x) override {
|
||||
auto call = this->alarm_control_panel_->make_call();
|
||||
auto code = this->code_.optional_value(x...);
|
||||
if (code.has_value()) {
|
||||
call.set_code(code.value());
|
||||
}
|
||||
call.arm_night();
|
||||
call.perform();
|
||||
}
|
||||
|
||||
protected:
|
||||
AlarmControlPanel *alarm_control_panel_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class DisarmAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit DisarmAction(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) {}
|
||||
|
|
0
esphome/components/bmp581/__init__.py
Normal file
0
esphome/components/bmp581/__init__.py
Normal file
596
esphome/components/bmp581/bmp581.cpp
Normal file
596
esphome/components/bmp581/bmp581.cpp
Normal file
|
@ -0,0 +1,596 @@
|
|||
/*
|
||||
* Adds support for Bosch's BMP581 high accuracy pressure and temperature sensor
|
||||
* - Component structure based on ESPHome's BMP3XX component (as of March, 2023)
|
||||
* - Implementation is easier as the sensor itself automatically compensates pressure for the temperature
|
||||
* - Temperature and pressure data is converted via simple divison operations in this component
|
||||
* - IIR filter level can independently be applied to temperature and pressure measurements
|
||||
* - Bosch's BMP5-Sensor-API was consulted to verify that sensor configuration is done correctly
|
||||
* - Copyright (c) 2022 Bosch Sensortec Gmbh, SPDX-License-Identifier: BSD-3-Clause
|
||||
* - This component uses forced power mode only so measurements are synchronized by the host
|
||||
* - All datasheet page references refer to Bosch Document Number BST-BMP581-DS004-04 (revision number 1.4)
|
||||
*/
|
||||
|
||||
#include "bmp581.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace bmp581 {
|
||||
|
||||
static const char *const TAG = "bmp581";
|
||||
|
||||
static const LogString *oversampling_to_str(Oversampling oversampling) {
|
||||
switch (oversampling) {
|
||||
case Oversampling::OVERSAMPLING_NONE:
|
||||
return LOG_STR("None");
|
||||
case Oversampling::OVERSAMPLING_X2:
|
||||
return LOG_STR("2x");
|
||||
case Oversampling::OVERSAMPLING_X4:
|
||||
return LOG_STR("4x");
|
||||
case Oversampling::OVERSAMPLING_X8:
|
||||
return LOG_STR("8x");
|
||||
case Oversampling::OVERSAMPLING_X16:
|
||||
return LOG_STR("16x");
|
||||
case Oversampling::OVERSAMPLING_X32:
|
||||
return LOG_STR("32x");
|
||||
case Oversampling::OVERSAMPLING_X64:
|
||||
return LOG_STR("64x");
|
||||
case Oversampling::OVERSAMPLING_X128:
|
||||
return LOG_STR("128x");
|
||||
default:
|
||||
return LOG_STR("");
|
||||
}
|
||||
}
|
||||
|
||||
static const LogString *iir_filter_to_str(IIRFilter filter) {
|
||||
switch (filter) {
|
||||
case IIRFilter::IIR_FILTER_OFF:
|
||||
return LOG_STR("OFF");
|
||||
case IIRFilter::IIR_FILTER_2:
|
||||
return LOG_STR("2x");
|
||||
case IIRFilter::IIR_FILTER_4:
|
||||
return LOG_STR("4x");
|
||||
case IIRFilter::IIR_FILTER_8:
|
||||
return LOG_STR("8x");
|
||||
case IIRFilter::IIR_FILTER_16:
|
||||
return LOG_STR("16x");
|
||||
case IIRFilter::IIR_FILTER_32:
|
||||
return LOG_STR("32x");
|
||||
case IIRFilter::IIR_FILTER_64:
|
||||
return LOG_STR("64x");
|
||||
case IIRFilter::IIR_FILTER_128:
|
||||
return LOG_STR("128x");
|
||||
default:
|
||||
return LOG_STR("");
|
||||
}
|
||||
}
|
||||
|
||||
void BMP581Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "BMP581:");
|
||||
|
||||
switch (this->error_code_) {
|
||||
case NONE:
|
||||
break;
|
||||
case ERROR_COMMUNICATION_FAILED:
|
||||
ESP_LOGE(TAG, " Communication with BMP581 failed!");
|
||||
break;
|
||||
case ERROR_WRONG_CHIP_ID:
|
||||
ESP_LOGE(TAG, " BMP581 has wrong chip ID - please verify you are using a BMP 581");
|
||||
break;
|
||||
case ERROR_SENSOR_RESET:
|
||||
ESP_LOGE(TAG, " BMP581 failed to reset");
|
||||
break;
|
||||
case ERROR_SENSOR_STATUS:
|
||||
ESP_LOGE(TAG, " BMP581 sensor status failed, there were NVM problems");
|
||||
break;
|
||||
case ERROR_PRIME_IIR_FAILED:
|
||||
ESP_LOGE(TAG, " BMP581's IIR Filter failed to prime with an initial measurement");
|
||||
break;
|
||||
default:
|
||||
ESP_LOGE(TAG, " BMP581 error code %d", (int) this->error_code_);
|
||||
break;
|
||||
}
|
||||
|
||||
LOG_I2C_DEVICE(this);
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
|
||||
ESP_LOGCONFIG(TAG, " Measurement conversion time: %ums", this->conversion_time_);
|
||||
|
||||
if (this->temperature_sensor_) {
|
||||
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
|
||||
ESP_LOGCONFIG(TAG, " IIR Filter: %s", LOG_STR_ARG(iir_filter_to_str(this->iir_temperature_level_)));
|
||||
ESP_LOGCONFIG(TAG, " Oversampling: %s", LOG_STR_ARG(oversampling_to_str(this->temperature_oversampling_)));
|
||||
}
|
||||
|
||||
if (this->pressure_sensor_) {
|
||||
LOG_SENSOR(" ", "Pressure", this->pressure_sensor_);
|
||||
ESP_LOGCONFIG(TAG, " IIR Filter: %s", LOG_STR_ARG(iir_filter_to_str(this->iir_pressure_level_)));
|
||||
ESP_LOGCONFIG(TAG, " Oversampling: %s", LOG_STR_ARG(oversampling_to_str(this->pressure_oversampling_)));
|
||||
}
|
||||
}
|
||||
|
||||
void BMP581Component::setup() {
|
||||
/*
|
||||
* Setup goes through several stages, which follows the post-power-up procedure (page 18 of datasheet) and then sets
|
||||
* configured options
|
||||
* 1) Soft reboot
|
||||
* 2) Verify ASIC chip ID matches BMP581
|
||||
* 3) Verify sensor status (check if NVM is okay)
|
||||
* 4) Enable data ready interrupt
|
||||
* 5) Write oversampling settings and set internal configuration values
|
||||
* 6) Configure and prime IIR Filter(s), if enabled
|
||||
*/
|
||||
|
||||
this->error_code_ = NONE;
|
||||
ESP_LOGCONFIG(TAG, "Setting up BMP581...");
|
||||
|
||||
////////////////////
|
||||
// 1) Soft reboot //
|
||||
////////////////////
|
||||
|
||||
// Power-On-Reboot bit is asserted if sensor successfully reset
|
||||
if (!this->reset_()) {
|
||||
ESP_LOGE(TAG, "BMP581 failed to reset");
|
||||
|
||||
this->error_code_ = ERROR_SENSOR_RESET;
|
||||
this->mark_failed();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////
|
||||
// 2) Verify ASIC chip ID matches BMP581 //
|
||||
///////////////////////////////////////////
|
||||
|
||||
uint8_t chip_id;
|
||||
|
||||
// read chip id from sensor
|
||||
if (!this->read_byte(BMP581_CHIP_ID, &chip_id)) {
|
||||
ESP_LOGE(TAG, "Failed to read chip id");
|
||||
|
||||
this->error_code_ = ERROR_COMMUNICATION_FAILED;
|
||||
this->mark_failed();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// verify id
|
||||
if (chip_id != BMP581_ASIC_ID) {
|
||||
ESP_LOGE(TAG, "Unknown chip ID, is this a BMP581?");
|
||||
|
||||
this->error_code_ = ERROR_WRONG_CHIP_ID;
|
||||
this->mark_failed();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////
|
||||
// 3) Verify sensor status (check if NVM is okay) //
|
||||
////////////////////////////////////////////////////
|
||||
|
||||
if (!this->read_byte(BMP581_STATUS, &this->status_.reg)) {
|
||||
ESP_LOGE(TAG, "Failed to read status register");
|
||||
|
||||
this->error_code_ = ERROR_COMMUNICATION_FAILED;
|
||||
this->mark_failed();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// verify status_nvm_rdy bit (it is asserted if boot was successful)
|
||||
if (!(this->status_.bit.status_nvm_rdy)) {
|
||||
ESP_LOGE(TAG, "NVM not ready after boot");
|
||||
|
||||
this->error_code_ = ERROR_SENSOR_STATUS;
|
||||
this->mark_failed();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// verify status_nvm_err bit (it is asserted if an error is detected)
|
||||
if (this->status_.bit.status_nvm_err) {
|
||||
ESP_LOGE(TAG, "NVM error detected on boot");
|
||||
|
||||
this->error_code_ = ERROR_SENSOR_STATUS;
|
||||
this->mark_failed();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
////////////////////////////////////
|
||||
// 4) Enable data ready interrupt //
|
||||
////////////////////////////////////
|
||||
|
||||
// enable the data ready interrupt source
|
||||
if (!this->write_interrupt_source_settings_(true)) {
|
||||
ESP_LOGE(TAG, "Failed to write interrupt source register");
|
||||
|
||||
this->error_code_ = ERROR_COMMUNICATION_FAILED;
|
||||
this->mark_failed();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// 5) Write oversampling settings and set internal configuration values //
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// configure pressure readings, if sensor is defined
|
||||
// otherwise, disable pressure oversampling
|
||||
if (this->pressure_sensor_) {
|
||||
this->osr_config_.bit.press_en = true;
|
||||
} else {
|
||||
this->pressure_oversampling_ = OVERSAMPLING_NONE;
|
||||
}
|
||||
|
||||
// write oversampling settings
|
||||
if (!this->write_oversampling_settings_(this->temperature_oversampling_, this->pressure_oversampling_)) {
|
||||
ESP_LOGE(TAG, "Failed to write oversampling register");
|
||||
|
||||
this->error_code_ = ERROR_COMMUNICATION_FAILED;
|
||||
this->mark_failed();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// set output data rate to 4 Hz=0x19 (page 65 of datasheet)
|
||||
// - ?shouldn't? matter as this component only uses FORCED_MODE - datasheet is ambiguous
|
||||
// - If in NORMAL_MODE or NONSTOP_MODE, then this would still allow deep standby to save power
|
||||
// - will be written to BMP581 at next requested measurement
|
||||
this->odr_config_.bit.odr = 0x19;
|
||||
|
||||
///////////////////////////////////////////////////////
|
||||
/// 6) Configure and prime IIR Filter(s), if enabled //
|
||||
///////////////////////////////////////////////////////
|
||||
|
||||
if ((this->iir_temperature_level_ != IIR_FILTER_OFF) || (this->iir_pressure_level_ != IIR_FILTER_OFF)) {
|
||||
if (!this->write_iir_settings_(this->iir_temperature_level_, this->iir_pressure_level_)) {
|
||||
ESP_LOGE(TAG, "Failed to write IIR configuration registers");
|
||||
|
||||
this->error_code_ = ERROR_COMMUNICATION_FAILED;
|
||||
this->mark_failed();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this->prime_iir_filter_()) {
|
||||
ESP_LOGE(TAG, "Failed to prime the IIR filter with an intiial measurement");
|
||||
|
||||
this->error_code_ = ERROR_PRIME_IIR_FAILED;
|
||||
this->mark_failed();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BMP581Component::update() {
|
||||
/*
|
||||
* Each update goes through several stages
|
||||
* 0) Verify either a temperature or pressure sensor is defined before proceeding
|
||||
* 1) Request a measurement
|
||||
* 2) Wait for measurement to finish (based on oversampling rates)
|
||||
* 3) Read data registers for temperature and pressure, if applicable
|
||||
* 4) Publish measurements to sensor(s), if applicable
|
||||
*/
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
// 0) Verify either a temperature or pressure sensor is defined before proceeding //
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
if ((!this->temperature_sensor_) && (!this->pressure_sensor_)) {
|
||||
return;
|
||||
}
|
||||
|
||||
//////////////////////////////
|
||||
// 1) Request a measurement //
|
||||
//////////////////////////////
|
||||
|
||||
ESP_LOGVV(TAG, "Requesting a measurement from sensor");
|
||||
|
||||
if (!this->start_measurement_()) {
|
||||
ESP_LOGW(TAG, "Failed to request forced measurement of sensor");
|
||||
this->status_set_warning();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// 2) Wait for measurement to finish (based on oversampling rates) //
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
ESP_LOGVV(TAG, "Measurement is expected to take %d ms to complete", this->conversion_time_);
|
||||
|
||||
this->set_timeout("measurement", this->conversion_time_, [this]() {
|
||||
float temperature = 0.0;
|
||||
float pressure = 0.0;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// 3) Read data registers for temperature and pressure, if applicable //
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
if (this->pressure_sensor_) {
|
||||
if (!this->read_temperature_and_pressure_(temperature, pressure)) {
|
||||
ESP_LOGW(TAG, "Failed to read temperature and pressure measurements, skipping update");
|
||||
this->status_set_warning();
|
||||
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (!this->read_temperature_(temperature)) {
|
||||
ESP_LOGW(TAG, "Failed to read temperature measurement, skipping update");
|
||||
this->status_set_warning();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////
|
||||
// 4) Publish measurements to sensor(s), if applicable //
|
||||
/////////////////////////////////////////////////////////
|
||||
|
||||
if (this->temperature_sensor_) {
|
||||
this->temperature_sensor_->publish_state(temperature);
|
||||
}
|
||||
|
||||
if (this->pressure_sensor_) {
|
||||
this->pressure_sensor_->publish_state(pressure);
|
||||
}
|
||||
|
||||
this->status_clear_warning();
|
||||
});
|
||||
}
|
||||
|
||||
bool BMP581Component::check_data_readiness_() {
|
||||
// - verifies component is not internally in standby mode
|
||||
// - reads interrupt status register
|
||||
// - checks if data ready bit is asserted
|
||||
// - If true, then internally sets component to standby mode if in forced mode
|
||||
// - returns data readiness state
|
||||
|
||||
if (this->odr_config_.bit.pwr_mode == STANDBY_MODE) {
|
||||
ESP_LOGD(TAG, "Data is not ready, sensor is in standby mode");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t status;
|
||||
|
||||
if (!this->read_byte(BMP581_INT_STATUS, &status)) {
|
||||
ESP_LOGE(TAG, "Failed to read interrupt status register");
|
||||
return false;
|
||||
}
|
||||
|
||||
this->int_status_.reg = status;
|
||||
|
||||
if (this->int_status_.bit.drdy_data_reg) {
|
||||
// If in forced mode, then set internal record of the power mode to STANDBY_MODE
|
||||
// - sensor automatically returns to standby mode after completing a forced measurement
|
||||
if (this->odr_config_.bit.pwr_mode == FORCED_MODE) {
|
||||
this->odr_config_.bit.pwr_mode = STANDBY_MODE;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BMP581Component::prime_iir_filter_() {
|
||||
// - temporarily disables oversampling for a fast initial measurement; avoids slowing down ESPHome's startup process
|
||||
// - enables IIR filter flushing with forced measurements
|
||||
// - forces a measurement; flushing the IIR filter and priming it with a current value
|
||||
// - disables IIR filter flushing with forced measurements
|
||||
// - reverts to internally configured oversampling rates
|
||||
// - returns success of all register writes/priming
|
||||
|
||||
// store current internal oversampling settings to revert to after priming
|
||||
Oversampling current_temperature_oversampling = (Oversampling) this->osr_config_.bit.osr_t;
|
||||
Oversampling current_pressure_oversampling = (Oversampling) this->osr_config_.bit.osr_p;
|
||||
|
||||
// temporarily disables oversampling for temperature and pressure for a fast priming measurement
|
||||
if (!this->write_oversampling_settings_(OVERSAMPLING_NONE, OVERSAMPLING_NONE)) {
|
||||
ESP_LOGE(TAG, "Failed to write oversampling register");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// flush the IIR filter with forced measurements (we will only flush once)
|
||||
this->dsp_config_.bit.iir_flush_forced_en = true;
|
||||
if (!this->write_byte(BMP581_DSP, this->dsp_config_.reg)) {
|
||||
ESP_LOGE(TAG, "Failed to write IIR source register");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// forces an intial measurement
|
||||
// - this measurements flushes the IIR filter reflecting written DSP settings
|
||||
// - flushing with this initial reading avoids having the internal previous data aquisition being 0, which
|
||||
// (I)nfinitely affects future values
|
||||
if (!this->start_measurement_()) {
|
||||
ESP_LOGE(TAG, "Failed to request a forced measurement");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// wait for priming measurement to complete
|
||||
// - with oversampling disabled, the conversion time for a single measurement for pressure and temperature is
|
||||
// ceilf(1.05*(1.0+1.0)) = 3ms
|
||||
// - see page 12 of datasheet for details
|
||||
delay(3);
|
||||
|
||||
if (!this->check_data_readiness_()) {
|
||||
ESP_LOGE(TAG, "IIR priming measurement was not ready");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// disable IIR filter flushings on future forced measurements
|
||||
this->dsp_config_.bit.iir_flush_forced_en = false;
|
||||
if (!this->write_byte(BMP581_DSP, this->dsp_config_.reg)) {
|
||||
ESP_LOGE(TAG, "Failed to write IIR source register");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// revert oversampling rates to original settings
|
||||
return this->write_oversampling_settings_(current_temperature_oversampling, current_pressure_oversampling);
|
||||
}
|
||||
|
||||
bool BMP581Component::read_temperature_(float &temperature) {
|
||||
// - verifies data is ready to be read
|
||||
// - reads in 3 bytes of temperature data
|
||||
// - returns whether successful, where the the variable parameter contains
|
||||
// - the measured temperature (in degrees Celsius)
|
||||
|
||||
if (!this->check_data_readiness_()) {
|
||||
ESP_LOGW(TAG, "Data from sensor isn't ready, skipping this update");
|
||||
this->status_set_warning();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t data[3];
|
||||
if (!this->read_bytes(BMP581_MEASUREMENT_DATA, &data[0], 3)) {
|
||||
ESP_LOGW(TAG, "Failed to read sensor's measurement data");
|
||||
this->status_set_warning();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// temperature MSB is in data[2], LSB is in data[1], XLSB in data[0]
|
||||
int32_t raw_temp = (int32_t) data[2] << 16 | (int32_t) data[1] << 8 | (int32_t) data[0];
|
||||
temperature = (float) (raw_temp / 65536.0); // convert measurement to degrees Celsius (page 22 of datasheet)
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BMP581Component::read_temperature_and_pressure_(float &temperature, float &pressure) {
|
||||
// - verifies data is ready to be read
|
||||
// - reads in 6 bytes of temperature data (3 for temeperature, 3 for pressure)
|
||||
// - returns whether successful, where the variable parameters contain
|
||||
// - the measured temperature (in degrees Celsius)
|
||||
// - the measured pressure (in Pa)
|
||||
|
||||
if (!this->check_data_readiness_()) {
|
||||
ESP_LOGW(TAG, "Data from sensor isn't ready, skipping this update");
|
||||
this->status_set_warning();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t data[6];
|
||||
if (!this->read_bytes(BMP581_MEASUREMENT_DATA, &data[0], 6)) {
|
||||
ESP_LOGW(TAG, "Failed to read sensor's measurement data");
|
||||
this->status_set_warning();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// temperature MSB is in data[2], LSB is in data[1], XLSB in data[0]
|
||||
int32_t raw_temp = (int32_t) data[2] << 16 | (int32_t) data[1] << 8 | (int32_t) data[0];
|
||||
temperature = (float) (raw_temp / 65536.0); // convert measurement to degrees Celsius (page 22 of datasheet)
|
||||
|
||||
// pressure MSB is in data[5], LSB is in data[4], XLSB in data[3]
|
||||
int32_t raw_press = (int32_t) data[5] << 16 | (int32_t) data[4] << 8 | (int32_t) data[3];
|
||||
pressure = (float) (raw_press / 64.0); // Divide by 2^6=64 for Pa (page 22 of datasheet)
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BMP581Component::reset_() {
|
||||
// - writes reset command to the command register
|
||||
// - waits for sensor to complete reset
|
||||
// - returns the Power-On-Reboot interrupt status, which is asserted if successful
|
||||
|
||||
// writes reset command to BMP's command register
|
||||
if (!this->write_byte(BMP581_COMMAND, RESET_COMMAND)) {
|
||||
ESP_LOGE(TAG, "Failed to write reset command");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// t_{soft_res} = 2ms (page 11 of datasheet); time it takes to enter standby mode
|
||||
// - round up to 3 ms
|
||||
delay(3);
|
||||
|
||||
// read interrupt status register
|
||||
if (!this->read_byte(BMP581_INT_STATUS, &this->int_status_.reg)) {
|
||||
ESP_LOGE(TAG, "Failed to read interrupt status register");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Power-On-Reboot bit is asserted if sensor successfully reset
|
||||
return this->int_status_.bit.por;
|
||||
}
|
||||
|
||||
bool BMP581Component::start_measurement_() {
|
||||
// - only pushes the sensor into FORCED_MODE for a reading if already in STANDBY_MODE
|
||||
// - returns whether a measurement is in progress or has been initiated
|
||||
|
||||
if (this->odr_config_.bit.pwr_mode == STANDBY_MODE) {
|
||||
return this->write_power_mode_(FORCED_MODE);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool BMP581Component::write_iir_settings_(IIRFilter temperature_iir, IIRFilter pressure_iir) {
|
||||
// - ensures data registers store filtered values
|
||||
// - sets IIR filter levels on sensor
|
||||
// - matches other default settings on sensor
|
||||
// - writes configuration to the two relevant registers
|
||||
// - returns success or failure of write to the registers
|
||||
|
||||
// If the temperature/pressure IIR filter is configured, then ensure data registers store the filtered measurement
|
||||
this->dsp_config_.bit.shdw_sel_iir_t = (temperature_iir != IIR_FILTER_OFF);
|
||||
this->dsp_config_.bit.shdw_sel_iir_p = (pressure_iir != IIR_FILTER_OFF);
|
||||
|
||||
// set temperature and pressure IIR filter level to configured values
|
||||
this->iir_config_.bit.set_iir_t = temperature_iir;
|
||||
this->iir_config_.bit.set_iir_p = pressure_iir;
|
||||
|
||||
// enable pressure and temperature compensation (page 61 of datasheet)
|
||||
// - ?only relevant if IIR filter is applied?; the datasheet is ambiguous
|
||||
// - matches BMP's default setting
|
||||
this->dsp_config_.bit.comp_pt_en = 0x3;
|
||||
|
||||
// BMP581_DSP register and BMP581_DSP_IIR registers are successive
|
||||
// - allows us to write the IIR configuration with one command to both registers
|
||||
uint8_t register_data[2] = {this->dsp_config_.reg, this->iir_config_.reg};
|
||||
return this->write_bytes(BMP581_DSP, register_data, sizeof(register_data));
|
||||
}
|
||||
|
||||
bool BMP581Component::write_interrupt_source_settings_(bool data_ready_enable) {
|
||||
// - updates component's internal setting
|
||||
// - returns success or failure of write to interrupt source register
|
||||
|
||||
this->int_source_.bit.drdy_data_reg_en = data_ready_enable;
|
||||
|
||||
// write interrupt source register
|
||||
return this->write_byte(BMP581_INT_SOURCE, this->int_source_.reg);
|
||||
}
|
||||
|
||||
bool BMP581Component::write_oversampling_settings_(Oversampling temperature_oversampling,
|
||||
Oversampling pressure_oversampling) {
|
||||
// - updates component's internal setting
|
||||
// - returns success or failure of write to Over-Sampling Rate register
|
||||
|
||||
this->osr_config_.bit.osr_t = temperature_oversampling;
|
||||
this->osr_config_.bit.osr_p = pressure_oversampling;
|
||||
|
||||
return this->write_byte(BMP581_OSR, this->osr_config_.reg);
|
||||
}
|
||||
|
||||
bool BMP581Component::write_power_mode_(OperationMode mode) {
|
||||
// - updates the component's internal power mode
|
||||
// - returns success or failure of write to Output Data Rate register
|
||||
|
||||
this->odr_config_.bit.pwr_mode = mode;
|
||||
|
||||
// write odr register
|
||||
return this->write_byte(BMP581_ODR, this->odr_config_.reg);
|
||||
}
|
||||
|
||||
} // namespace bmp581
|
||||
} // namespace esphome
|
222
esphome/components/bmp581/bmp581.h
Normal file
222
esphome/components/bmp581/bmp581.h
Normal file
|
@ -0,0 +1,222 @@
|
|||
// All datasheet page references refer to Bosch Document Number BST-BMP581-DS004-04 (revision number 1.4)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace bmp581 {
|
||||
|
||||
static const uint8_t BMP581_ASIC_ID = 0x50; // BMP581's ASIC chip ID (page 51 of datasheet)
|
||||
static const uint8_t RESET_COMMAND = 0xB6; // Soft reset command
|
||||
|
||||
// BMP581 Register Addresses
|
||||
enum {
|
||||
BMP581_CHIP_ID = 0x01, // read chip ID
|
||||
BMP581_INT_SOURCE = 0x15, // write interrupt sources
|
||||
BMP581_MEASUREMENT_DATA =
|
||||
0x1D, // read measurement registers, 0x1D-0x1F are temperature XLSB to MSB and 0x20-0x22 are pressure XLSB to MSB
|
||||
BMP581_INT_STATUS = 0x27, // read interrupt statuses
|
||||
BMP581_STATUS = 0x28, // read sensor status
|
||||
BMP581_DSP = 0x30, // write sensor configuration
|
||||
BMP581_DSP_IIR = 0x31, // write IIR filter configuration
|
||||
BMP581_OSR = 0x36, // write oversampling configuration
|
||||
BMP581_ODR = 0x37, // write data rate and power mode configuration
|
||||
BMP581_COMMAND = 0x7E // write sensor command
|
||||
};
|
||||
|
||||
// BMP581 Power mode operations
|
||||
enum OperationMode {
|
||||
STANDBY_MODE = 0x0, // no active readings
|
||||
NORMAL_MODE = 0x1, // read continuously at ODR configured rate and standby between
|
||||
FORCED_MODE = 0x2, // read sensor once (only reading mode used by this component)
|
||||
NONSTOP_MODE = 0x3 // read continuously with no standby
|
||||
};
|
||||
|
||||
// Temperature and pressure sensors can be oversampled to reduce noise
|
||||
enum Oversampling {
|
||||
OVERSAMPLING_NONE = 0x0,
|
||||
OVERSAMPLING_X2 = 0x1,
|
||||
OVERSAMPLING_X4 = 0x2,
|
||||
OVERSAMPLING_X8 = 0x3,
|
||||
OVERSAMPLING_X16 = 0x4,
|
||||
OVERSAMPLING_X32 = 0x5,
|
||||
OVERSAMPLING_X64 = 0x6,
|
||||
OVERSAMPLING_X128 = 0x7
|
||||
};
|
||||
|
||||
// Infinite Impulse Response filter reduces noise caused by ambient disturbances
|
||||
enum IIRFilter {
|
||||
IIR_FILTER_OFF = 0x0,
|
||||
IIR_FILTER_2 = 0x1,
|
||||
IIR_FILTER_4 = 0x2,
|
||||
IIR_FILTER_8 = 0x3,
|
||||
IIR_FILTER_16 = 0x4,
|
||||
IIR_FILTER_32 = 0x5,
|
||||
IIR_FILTER_64 = 0x6,
|
||||
IIR_FILTER_128 = 0x7
|
||||
};
|
||||
|
||||
class BMP581Component : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
void setup() override;
|
||||
void update() override;
|
||||
|
||||
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { this->temperature_sensor_ = temperature_sensor; }
|
||||
void set_pressure_sensor(sensor::Sensor *pressure_sensor) { this->pressure_sensor_ = pressure_sensor; }
|
||||
|
||||
void set_temperature_oversampling_config(Oversampling temperature_oversampling) {
|
||||
this->temperature_oversampling_ = temperature_oversampling;
|
||||
}
|
||||
void set_pressure_oversampling_config(Oversampling pressure_oversampling) {
|
||||
this->pressure_oversampling_ = pressure_oversampling;
|
||||
}
|
||||
|
||||
void set_temperature_iir_filter_config(IIRFilter iir_temperature_level) {
|
||||
this->iir_temperature_level_ = iir_temperature_level;
|
||||
}
|
||||
void set_pressure_iir_filter_config(IIRFilter iir_pressure_level) { this->iir_pressure_level_ = iir_pressure_level; }
|
||||
|
||||
void set_conversion_time(uint8_t conversion_time) { this->conversion_time_ = conversion_time; }
|
||||
|
||||
protected:
|
||||
sensor::Sensor *temperature_sensor_{nullptr};
|
||||
sensor::Sensor *pressure_sensor_{nullptr};
|
||||
|
||||
Oversampling temperature_oversampling_;
|
||||
Oversampling pressure_oversampling_;
|
||||
|
||||
IIRFilter iir_temperature_level_;
|
||||
IIRFilter iir_pressure_level_;
|
||||
|
||||
// Stores the sensors conversion time needed for a measurement based on oversampling settings and datasheet (page 12)
|
||||
// Computed in Python during codegen
|
||||
uint8_t conversion_time_;
|
||||
|
||||
// Checks if the BMP581 has measurement data ready by checking the sensor's interrupts
|
||||
bool check_data_readiness_();
|
||||
|
||||
// Flushes the IIR filter and primes an initial reading
|
||||
bool prime_iir_filter_();
|
||||
|
||||
// Reads temperature data from sensor and converts data to measurement in degrees Celsius
|
||||
bool read_temperature_(float &temperature);
|
||||
// Reads temperature and pressure data from sensor and converts data to measurements in degrees Celsius and Pa
|
||||
bool read_temperature_and_pressure_(float &temperature, float &pressure);
|
||||
|
||||
// Soft resets the BMP581
|
||||
bool reset_();
|
||||
|
||||
// Initiates a measurement on sensor by switching to FORCED_MODE
|
||||
bool start_measurement_();
|
||||
|
||||
// Writes the IIR filter configuration to the DSP and DSP_IIR registers
|
||||
bool write_iir_settings_(IIRFilter temperature_iir, IIRFilter pressure_iir);
|
||||
|
||||
// Writes whether to enable the data ready interrupt to the interrupt source register
|
||||
bool write_interrupt_source_settings_(bool data_ready_enable);
|
||||
|
||||
// Writes the oversampling settings to the OSR register
|
||||
bool write_oversampling_settings_(Oversampling temperature_oversampling, Oversampling pressure_oversampling);
|
||||
|
||||
// Sets the power mode on the BMP581 by writing to the ODR register
|
||||
bool write_power_mode_(OperationMode mode);
|
||||
|
||||
enum ErrorCode {
|
||||
NONE = 0,
|
||||
ERROR_COMMUNICATION_FAILED,
|
||||
ERROR_WRONG_CHIP_ID,
|
||||
ERROR_SENSOR_STATUS,
|
||||
ERROR_SENSOR_RESET,
|
||||
ERROR_PRIME_IIR_FAILED
|
||||
} error_code_{NONE};
|
||||
|
||||
// BMP581's interrupt source register (address 0x15) to configure which interrupts are enabled (page 54 of datasheet)
|
||||
union {
|
||||
struct {
|
||||
uint8_t drdy_data_reg_en : 1; // Data ready interrupt enable
|
||||
uint8_t fifo_full_en : 1; // FIFO full interrupt enable
|
||||
uint8_t fifo_ths_en : 1; // FIFO threshold/watermark interrupt enable
|
||||
uint8_t oor_p_en : 1; // Pressure data out-of-range interrupt enable
|
||||
} bit;
|
||||
uint8_t reg;
|
||||
} int_source_ = {.reg = 0};
|
||||
|
||||
// BMP581's interrupt status register (address 0x27) to determine ensor's current state (page 58 of datasheet)
|
||||
union {
|
||||
struct {
|
||||
uint8_t drdy_data_reg : 1; // Data ready
|
||||
uint8_t fifo_full : 1; // FIFO full
|
||||
uint8_t fifo_ths : 1; // FIFO fhreshold/watermark
|
||||
uint8_t oor_p : 1; // Pressure data out-of-range
|
||||
uint8_t por : 1; // Power-On-Reset complete
|
||||
} bit;
|
||||
uint8_t reg;
|
||||
} int_status_ = {.reg = 0};
|
||||
|
||||
// BMP581's status register (address 0x28) to determine if sensor has setup correctly (page 58 of datasheet)
|
||||
union {
|
||||
struct {
|
||||
uint8_t status_core_rdy : 1;
|
||||
uint8_t status_nvm_rdy : 1; // asserted if NVM is ready of operations
|
||||
uint8_t status_nvm_err : 1; // asserted if NVM error
|
||||
uint8_t status_nvm_cmd_err : 1; // asserted if boot command error
|
||||
uint8_t status_boot_err_corrected : 1; // asserted if a boot error has been corrected
|
||||
uint8_t : 2;
|
||||
uint8_t st_crack_pass : 1; // asserted if crack check has executed without detecting a crack
|
||||
} bit;
|
||||
uint8_t reg;
|
||||
} status_ = {.reg = 0};
|
||||
|
||||
// BMP581's dsp register (address 0x30) to configure data registers iir selection (page 61 of datasheet)
|
||||
union {
|
||||
struct {
|
||||
uint8_t comp_pt_en : 2; // enable temperature and pressure compensation
|
||||
uint8_t iir_flush_forced_en : 1; // IIR filter is flushed in forced mode
|
||||
uint8_t shdw_sel_iir_t : 1; // temperature data register value selected before or after iir
|
||||
uint8_t fifo_sel_iir_t : 1; // FIFO temperature data register value secected before or after iir
|
||||
uint8_t shdw_sel_iir_p : 1; // pressure data register value selected before or after iir
|
||||
uint8_t fifo_sel_iir_p : 1; // FIFO pressure data register value selected before or after iir
|
||||
uint8_t oor_sel_iir_p : 1; // pressure out-of-range value selected before or after iir
|
||||
} bit;
|
||||
uint8_t reg;
|
||||
} dsp_config_ = {.reg = 0};
|
||||
|
||||
// BMP581's iir register (address 0x31) to configure iir filtering(page 62 of datasheet)
|
||||
union {
|
||||
struct {
|
||||
uint8_t set_iir_t : 3; // Temperature IIR filter coefficient
|
||||
uint8_t set_iir_p : 3; // Pressure IIR filter coefficient
|
||||
} bit;
|
||||
uint8_t reg;
|
||||
} iir_config_ = {.reg = 0};
|
||||
|
||||
// BMP581's OSR register (address 0x36) to configure Over-Sampling Rates (page 64 of datasheet)
|
||||
union {
|
||||
struct {
|
||||
uint8_t osr_t : 3; // Temperature oversampling
|
||||
uint8_t osr_p : 3; // Pressure oversampling
|
||||
uint8_t press_en : 1; // Enables pressure measurement
|
||||
} bit;
|
||||
uint8_t reg;
|
||||
} osr_config_ = {.reg = 0};
|
||||
|
||||
// BMP581's odr register (address 0x37) to configure output data rate and power mode (page 64 of datasheet)
|
||||
union {
|
||||
struct {
|
||||
uint8_t pwr_mode : 2; // power mode of sensor
|
||||
uint8_t odr : 5; // output data rate
|
||||
uint8_t deep_dis : 1; // deep standby disabled if asserted
|
||||
} bit;
|
||||
uint8_t reg;
|
||||
} odr_config_ = {.reg = 0};
|
||||
};
|
||||
|
||||
} // namespace bmp581
|
||||
} // namespace esphome
|
163
esphome/components/bmp581/sensor.py
Normal file
163
esphome/components/bmp581/sensor.py
Normal file
|
@ -0,0 +1,163 @@
|
|||
import math
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_IIR_FILTER,
|
||||
CONF_OVERSAMPLING,
|
||||
CONF_PRESSURE,
|
||||
CONF_TEMPERATURE,
|
||||
DEVICE_CLASS_ATMOSPHERIC_PRESSURE,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_PASCAL,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@kahrendt"]
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
bmp581_ns = cg.esphome_ns.namespace("bmp581")
|
||||
|
||||
Oversampling = bmp581_ns.enum("Oversampling")
|
||||
OVERSAMPLING_OPTIONS = {
|
||||
"NONE": Oversampling.OVERSAMPLING_NONE,
|
||||
"2X": Oversampling.OVERSAMPLING_X2,
|
||||
"4X": Oversampling.OVERSAMPLING_X4,
|
||||
"8X": Oversampling.OVERSAMPLING_X8,
|
||||
"16X": Oversampling.OVERSAMPLING_X16,
|
||||
"32X": Oversampling.OVERSAMPLING_X32,
|
||||
"64X": Oversampling.OVERSAMPLING_X64,
|
||||
"128X": Oversampling.OVERSAMPLING_X128,
|
||||
}
|
||||
|
||||
IIRFilter = bmp581_ns.enum("IIRFilter")
|
||||
IIR_FILTER_OPTIONS = {
|
||||
"OFF": IIRFilter.IIR_FILTER_OFF,
|
||||
"2X": IIRFilter.IIR_FILTER_2,
|
||||
"4X": IIRFilter.IIR_FILTER_4,
|
||||
"8X": IIRFilter.IIR_FILTER_8,
|
||||
"16X": IIRFilter.IIR_FILTER_16,
|
||||
"32X": IIRFilter.IIR_FILTER_32,
|
||||
"64X": IIRFilter.IIR_FILTER_64,
|
||||
"128X": IIRFilter.IIR_FILTER_128,
|
||||
}
|
||||
|
||||
BMP581Component = bmp581_ns.class_(
|
||||
"BMP581Component", cg.PollingComponent, i2c.I2CDevice
|
||||
)
|
||||
|
||||
|
||||
def compute_measurement_conversion_time(config):
|
||||
# - adds up sensor conversion time based on temperature and pressure oversampling rates given in datasheet
|
||||
# - returns a rounded up time in ms
|
||||
|
||||
# Page 12 of datasheet
|
||||
PRESSURE_OVERSAMPLING_CONVERSION_TIMES = {
|
||||
"NONE": 1.0,
|
||||
"2X": 1.7,
|
||||
"4X": 2.9,
|
||||
"8X": 5.4,
|
||||
"16X": 10.4,
|
||||
"32X": 20.4,
|
||||
"64X": 40.4,
|
||||
"128X": 80.4,
|
||||
}
|
||||
|
||||
# Page 12 of datasheet
|
||||
TEMPERATURE_OVERSAMPLING_CONVERSION_TIMES = {
|
||||
"NONE": 1.0,
|
||||
"2X": 1.1,
|
||||
"4X": 1.5,
|
||||
"8X": 2.1,
|
||||
"16X": 3.3,
|
||||
"32X": 5.8,
|
||||
"64X": 10.8,
|
||||
"128X": 20.8,
|
||||
}
|
||||
|
||||
pressure_conversion_time = (
|
||||
0.0 # No conversion time necessary without a pressure sensor
|
||||
)
|
||||
if pressure_config := config.get(CONF_PRESSURE):
|
||||
pressure_conversion_time = PRESSURE_OVERSAMPLING_CONVERSION_TIMES[
|
||||
pressure_config.get(CONF_OVERSAMPLING)
|
||||
]
|
||||
|
||||
temperature_conversion_time = (
|
||||
1.0 # BMP581 always samples the temperature even if only reading pressure
|
||||
)
|
||||
if temperature_config := config.get(CONF_TEMPERATURE):
|
||||
temperature_conversion_time = TEMPERATURE_OVERSAMPLING_CONVERSION_TIMES[
|
||||
temperature_config.get(CONF_OVERSAMPLING)
|
||||
]
|
||||
|
||||
# Datasheet indicates a 5% possible error in each conversion time listed
|
||||
return math.ceil(1.05 * (pressure_conversion_time + temperature_conversion_time))
|
||||
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BMP581Component),
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
).extend(
|
||||
{
|
||||
cv.Optional(CONF_OVERSAMPLING, default="NONE"): cv.enum(
|
||||
OVERSAMPLING_OPTIONS, upper=True
|
||||
),
|
||||
cv.Optional(CONF_IIR_FILTER, default="OFF"): cv.enum(
|
||||
IIR_FILTER_OPTIONS, upper=True
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PASCAL,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_ATMOSPHERIC_PRESSURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
).extend(
|
||||
{
|
||||
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
|
||||
OVERSAMPLING_OPTIONS, upper=True
|
||||
),
|
||||
cv.Optional(CONF_IIR_FILTER, default="OFF"): cv.enum(
|
||||
IIR_FILTER_OPTIONS, upper=True
|
||||
),
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(i2c.i2c_device_schema(0x46))
|
||||
)
|
||||
|
||||
|
||||
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)
|
||||
if temperature_config := config.get(CONF_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(temperature_config)
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
cg.add(
|
||||
var.set_temperature_oversampling_config(
|
||||
temperature_config[CONF_OVERSAMPLING]
|
||||
)
|
||||
)
|
||||
cg.add(
|
||||
var.set_temperature_iir_filter_config(temperature_config[CONF_IIR_FILTER])
|
||||
)
|
||||
|
||||
if pressure_config := config.get(CONF_PRESSURE):
|
||||
sens = await sensor.new_sensor(pressure_config)
|
||||
cg.add(var.set_pressure_sensor(sens))
|
||||
cg.add(var.set_pressure_oversampling_config(pressure_config[CONF_OVERSAMPLING]))
|
||||
cg.add(var.set_pressure_iir_filter_config(pressure_config[CONF_IIR_FILTER]))
|
||||
|
||||
cg.add(var.set_conversion_time(compute_measurement_conversion_time(config)))
|
|
@ -72,3 +72,4 @@ async def to_code(config):
|
|||
|
||||
if CORE.using_esp_idf:
|
||||
add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True)
|
||||
add_idf_sdkconfig_option("CONFIG_BT_BLE_42_FEATURES_SUPPORTED", True)
|
||||
|
|
|
@ -15,8 +15,19 @@ static const char *const TAG = "i2c.idf";
|
|||
|
||||
void IDFI2CBus::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up I2C bus...");
|
||||
static i2c_port_t next_port = 0;
|
||||
port_ = next_port++;
|
||||
static i2c_port_t next_port = I2C_NUM_0;
|
||||
port_ = next_port;
|
||||
#if I2C_NUM_MAX > 1
|
||||
next_port = (next_port == I2C_NUM_0) ? I2C_NUM_1 : I2C_NUM_MAX;
|
||||
#else
|
||||
next_port = I2C_NUM_MAX;
|
||||
#endif
|
||||
|
||||
if (port_ == I2C_NUM_MAX) {
|
||||
ESP_LOGE(TAG, "Too many I2C buses configured");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
recover_();
|
||||
|
||||
|
|
|
@ -16,14 +16,6 @@ static const char *const TAG = "i2s_audio.microphone";
|
|||
|
||||
void I2SAudioMicrophone::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up I2S Audio Microphone...");
|
||||
ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
|
||||
this->buffer_ = allocator.allocate(BUFFER_SIZE);
|
||||
if (this->buffer_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed to allocate buffer!");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
#if SOC_I2S_SUPPORTS_ADC
|
||||
if (this->adc_) {
|
||||
if (this->parent_->get_port() != I2S_NUM_0) {
|
||||
|
@ -110,37 +102,38 @@ void I2SAudioMicrophone::stop_() {
|
|||
this->high_freq_.stop();
|
||||
}
|
||||
|
||||
void I2SAudioMicrophone::read_() {
|
||||
size_t I2SAudioMicrophone::read(int16_t *buf, size_t len) {
|
||||
size_t bytes_read = 0;
|
||||
esp_err_t err =
|
||||
i2s_read(this->parent_->get_port(), this->buffer_, BUFFER_SIZE, &bytes_read, (100 / portTICK_PERIOD_MS));
|
||||
esp_err_t err = i2s_read(this->parent_->get_port(), buf, len, &bytes_read, (100 / portTICK_PERIOD_MS));
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Error reading from I2S microphone: %s", esp_err_to_name(err));
|
||||
this->status_set_warning();
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
this->status_clear_warning();
|
||||
|
||||
std::vector<int16_t> samples;
|
||||
size_t samples_read = 0;
|
||||
if (this->bits_per_sample_ == I2S_BITS_PER_SAMPLE_16BIT) {
|
||||
samples_read = bytes_read / sizeof(int16_t);
|
||||
} else if (this->bits_per_sample_ == I2S_BITS_PER_SAMPLE_32BIT) {
|
||||
samples_read = bytes_read / sizeof(int32_t);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Unsupported bits per sample: %d", this->bits_per_sample_);
|
||||
return;
|
||||
}
|
||||
samples.resize(samples_read);
|
||||
if (this->bits_per_sample_ == I2S_BITS_PER_SAMPLE_16BIT) {
|
||||
memcpy(samples.data(), this->buffer_, bytes_read);
|
||||
return bytes_read;
|
||||
} else if (this->bits_per_sample_ == I2S_BITS_PER_SAMPLE_32BIT) {
|
||||
std::vector<int16_t> samples;
|
||||
size_t samples_read = bytes_read / sizeof(int32_t);
|
||||
samples.resize(samples_read);
|
||||
for (size_t i = 0; i < samples_read; i++) {
|
||||
int32_t temp = reinterpret_cast<int32_t *>(this->buffer_)[i] >> 14;
|
||||
int32_t temp = reinterpret_cast<int32_t *>(buf)[i] >> 14;
|
||||
samples[i] = clamp<int16_t>(temp, INT16_MIN, INT16_MAX);
|
||||
}
|
||||
memcpy(buf, samples.data(), samples_read * sizeof(int16_t));
|
||||
return samples_read * sizeof(int16_t);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Unsupported bits per sample: %d", this->bits_per_sample_);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void I2SAudioMicrophone::read_() {
|
||||
std::vector<int16_t> samples;
|
||||
samples.resize(BUFFER_SIZE);
|
||||
size_t bytes_read = this->read(samples.data(), BUFFER_SIZE / sizeof(int16_t));
|
||||
samples.resize(bytes_read / sizeof(int16_t));
|
||||
this->data_callbacks_.call(samples);
|
||||
}
|
||||
|
||||
|
@ -152,7 +145,9 @@ void I2SAudioMicrophone::loop() {
|
|||
this->start_();
|
||||
break;
|
||||
case microphone::STATE_RUNNING:
|
||||
this->read_();
|
||||
if (this->data_callbacks_.size() > 0) {
|
||||
this->read_();
|
||||
}
|
||||
break;
|
||||
case microphone::STATE_STOPPING:
|
||||
this->stop_();
|
||||
|
|
|
@ -21,6 +21,8 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub
|
|||
void set_din_pin(int8_t pin) { this->din_pin_ = pin; }
|
||||
void set_pdm(bool pdm) { this->pdm_ = pdm; }
|
||||
|
||||
size_t read(int16_t *buf, size_t len) override;
|
||||
|
||||
#if SOC_I2S_SUPPORTS_ADC
|
||||
void set_adc_channel(adc1_channel_t channel) {
|
||||
this->adc_channel_ = channel;
|
||||
|
@ -42,7 +44,6 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub
|
|||
bool adc_{false};
|
||||
#endif
|
||||
bool pdm_{false};
|
||||
uint8_t *buffer_;
|
||||
i2s_channel_fmt_t channel_;
|
||||
i2s_bits_per_sample_t bits_per_sample_;
|
||||
|
||||
|
|
0
esphome/components/kmeteriso/__init__.py
Normal file
0
esphome/components/kmeteriso/__init__.py
Normal file
82
esphome/components/kmeteriso/kmeteriso.cpp
Normal file
82
esphome/components/kmeteriso/kmeteriso.cpp
Normal file
|
@ -0,0 +1,82 @@
|
|||
#include "kmeteriso.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace kmeteriso {
|
||||
|
||||
static const char *const TAG = "kmeteriso.sensor";
|
||||
|
||||
static const uint8_t KMETER_ERROR_STATUS_REG = 0x20;
|
||||
static const uint8_t KMETER_TEMP_VAL_REG = 0x00;
|
||||
static const uint8_t KMETER_INTERNAL_TEMP_VAL_REG = 0x10;
|
||||
static const uint8_t KMETER_FIRMWARE_VERSION_REG = 0xFE;
|
||||
|
||||
void KMeterISOComponent::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up KMeterISO...");
|
||||
this->error_code_ = NONE;
|
||||
|
||||
// Mark as not failed before initializing. Some devices will turn off sensors to save on batteries
|
||||
// and when they come back on, the COMPONENT_STATE_FAILED bit must be unset on the component.
|
||||
if ((this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_FAILED) {
|
||||
this->component_state_ &= ~COMPONENT_STATE_MASK;
|
||||
this->component_state_ |= COMPONENT_STATE_CONSTRUCTION;
|
||||
}
|
||||
|
||||
auto err = this->bus_->writev(this->address_, nullptr, 0);
|
||||
if (err == esphome::i2c::ERROR_OK) {
|
||||
ESP_LOGCONFIG(TAG, "Could write to the address %d.", this->address_);
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, "Could not write to the address %d.", this->address_);
|
||||
this->error_code_ = COMMUNICATION_FAILED;
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t read_buf[4] = {1};
|
||||
if (!this->read_bytes(KMETER_ERROR_STATUS_REG, read_buf, 1)) {
|
||||
ESP_LOGCONFIG(TAG, "Could not read from the device.");
|
||||
this->error_code_ = COMMUNICATION_FAILED;
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
if (read_buf[0] != 0) {
|
||||
ESP_LOGCONFIG(TAG, "The device is not ready.");
|
||||
this->error_code_ = STATUS_FAILED;
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, "The device was successfully setup.");
|
||||
}
|
||||
|
||||
float KMeterISOComponent::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
void KMeterISOComponent::update() {
|
||||
uint8_t read_buf[4];
|
||||
|
||||
if (this->temperature_sensor_ != nullptr) {
|
||||
if (!this->read_bytes(KMETER_TEMP_VAL_REG, read_buf, 4)) {
|
||||
ESP_LOGW(TAG, "Error reading temperature.");
|
||||
} else {
|
||||
int32_t temp = encode_uint32(read_buf[3], read_buf[2], read_buf[1], read_buf[0]);
|
||||
float temp_f = temp / 100.0;
|
||||
ESP_LOGV(TAG, "Got temperature=%.2f °C", temp_f);
|
||||
this->temperature_sensor_->publish_state(temp_f);
|
||||
}
|
||||
}
|
||||
|
||||
if (this->internal_temperature_sensor_ != nullptr) {
|
||||
if (!this->read_bytes(KMETER_INTERNAL_TEMP_VAL_REG, read_buf, 4)) {
|
||||
ESP_LOGW(TAG, "Error reading internal temperature.");
|
||||
return;
|
||||
} else {
|
||||
int32_t internal_temp = encode_uint32(read_buf[3], read_buf[2], read_buf[1], read_buf[0]);
|
||||
float internal_temp_f = internal_temp / 100.0;
|
||||
ESP_LOGV(TAG, "Got internal temperature=%.2f °C", internal_temp_f);
|
||||
this->internal_temperature_sensor_->publish_state(internal_temp_f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace kmeteriso
|
||||
} // namespace esphome
|
34
esphome/components/kmeteriso/kmeteriso.h
Normal file
34
esphome/components/kmeteriso/kmeteriso.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/i2c/i2c_bus.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace kmeteriso {
|
||||
|
||||
/// This class implements support for the KMeterISO thermocouple sensor.
|
||||
class KMeterISOComponent : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
void set_temperature_sensor(sensor::Sensor *t) { this->temperature_sensor_ = t; }
|
||||
void set_internal_temperature_sensor(sensor::Sensor *t) { this->internal_temperature_sensor_ = t; }
|
||||
|
||||
// ========== INTERNAL METHODS ==========
|
||||
// (In most use cases you won't need these)
|
||||
void setup() override;
|
||||
float get_setup_priority() const override;
|
||||
void update() override;
|
||||
|
||||
protected:
|
||||
sensor::Sensor *temperature_sensor_{nullptr};
|
||||
sensor::Sensor *internal_temperature_sensor_{nullptr};
|
||||
enum ErrorCode {
|
||||
NONE = 0,
|
||||
COMMUNICATION_FAILED,
|
||||
STATUS_FAILED,
|
||||
} error_code_{NONE};
|
||||
};
|
||||
|
||||
} // namespace kmeteriso
|
||||
} // namespace esphome
|
55
esphome/components/kmeteriso/sensor.py
Normal file
55
esphome/components/kmeteriso/sensor.py
Normal file
|
@ -0,0 +1,55 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_TEMPERATURE,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
)
|
||||
|
||||
CONF_INTERNAL_TEMPERATURE = "internal_temperature"
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
kmeteriso_ns = cg.esphome_ns.namespace("kmeteriso")
|
||||
|
||||
KMeterISOComponent = kmeteriso_ns.class_(
|
||||
"KMeterISOComponent", cg.PollingComponent, i2c.I2CDevice
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(KMeterISOComponent),
|
||||
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,
|
||||
),
|
||||
cv.Optional(CONF_INTERNAL_TEMPERATURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(i2c.i2c_device_schema(0x66))
|
||||
)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
if temperature_config := config.get(CONF_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(temperature_config)
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
if internal_temperature_config := config.get(CONF_INTERNAL_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(internal_temperature_config)
|
||||
cg.add(var.set_internal_temperature_sensor(sens))
|
|
@ -17,7 +17,11 @@
|
|||
#define CLOCK_FREQUENCY 40e6f
|
||||
#endif
|
||||
#else
|
||||
#ifdef SOC_LEDC_SUPPORT_APB_CLOCK
|
||||
#define DEFAULT_CLK LEDC_USE_APB_CLK
|
||||
#else
|
||||
#define DEFAULT_CLK LEDC_AUTO_CLK
|
||||
#endif
|
||||
#endif
|
||||
|
||||
static const uint8_t SETUP_ATTEMPT_COUNT_MAX = 5;
|
||||
|
|
|
@ -20,6 +20,7 @@ class Microphone {
|
|||
void add_data_callback(std::function<void(const std::vector<int16_t> &)> &&data_callback) {
|
||||
this->data_callbacks_.add(std::move(data_callback));
|
||||
}
|
||||
virtual size_t read(int16_t *buf, size_t len) = 0;
|
||||
|
||||
bool is_running() const { return this->state_ == STATE_RUNNING; }
|
||||
bool is_stopped() const { return this->state_ == STATE_STOPPED; }
|
||||
|
|
|
@ -271,8 +271,8 @@ def exp_mqtt_message(config):
|
|||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
# Add required libraries for arduino
|
||||
if CORE.using_arduino:
|
||||
# Add required libraries for ESP8266
|
||||
if CORE.is_esp8266:
|
||||
# https://github.com/OttoWinter/async-mqtt-client/blob/master/library.json
|
||||
cg.add_library("ottowinter/AsyncMqttClient-esphome", "0.8.6")
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#ifdef USE_ESP_IDF
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <string>
|
||||
#include "mqtt_backend_idf.h"
|
||||
#include "mqtt_backend_esp32.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
|
@ -10,7 +10,7 @@ namespace mqtt {
|
|||
|
||||
static const char *const TAG = "mqtt.idf";
|
||||
|
||||
bool MQTTBackendIDF::initialize_() {
|
||||
bool MQTTBackendESP32::initialize_() {
|
||||
#if ESP_IDF_VERSION_MAJOR < 5
|
||||
mqtt_cfg_.user_context = (void *) this;
|
||||
mqtt_cfg_.buffer_size = MQTT_BUFFER_SIZE;
|
||||
|
@ -95,7 +95,7 @@ bool MQTTBackendIDF::initialize_() {
|
|||
}
|
||||
}
|
||||
|
||||
void MQTTBackendIDF::loop() {
|
||||
void MQTTBackendESP32::loop() {
|
||||
// process new events
|
||||
// handle only 1 message per loop iteration
|
||||
if (!mqtt_events_.empty()) {
|
||||
|
@ -105,7 +105,7 @@ void MQTTBackendIDF::loop() {
|
|||
}
|
||||
}
|
||||
|
||||
void MQTTBackendIDF::mqtt_event_handler_(const Event &event) {
|
||||
void MQTTBackendESP32::mqtt_event_handler_(const Event &event) {
|
||||
ESP_LOGV(TAG, "Event dispatched from event loop event_id=%d", event.event_id);
|
||||
switch (event.event_id) {
|
||||
case MQTT_EVENT_BEFORE_CONNECT:
|
||||
|
@ -166,8 +166,9 @@ void MQTTBackendIDF::mqtt_event_handler_(const Event &event) {
|
|||
}
|
||||
|
||||
/// static - Dispatch event to instance method
|
||||
void MQTTBackendIDF::mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) {
|
||||
MQTTBackendIDF *instance = static_cast<MQTTBackendIDF *>(handler_args);
|
||||
void MQTTBackendESP32::mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id,
|
||||
void *event_data) {
|
||||
MQTTBackendESP32 *instance = static_cast<MQTTBackendESP32 *>(handler_args);
|
||||
// queue event to decouple processing
|
||||
if (instance) {
|
||||
auto event = *static_cast<esp_mqtt_event_t *>(event_data);
|
||||
|
@ -177,4 +178,4 @@ void MQTTBackendIDF::mqtt_event_handler(void *handler_args, esp_event_base_t bas
|
|||
|
||||
} // namespace mqtt
|
||||
} // namespace esphome
|
||||
#endif // USE_ESP_IDF
|
||||
#endif // USE_ESP32
|
|
@ -1,6 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <string>
|
||||
#include <queue>
|
||||
|
@ -41,7 +41,7 @@ struct Event {
|
|||
error_handle(*event.error_handle) {}
|
||||
};
|
||||
|
||||
class MQTTBackendIDF final : public MQTTBackend {
|
||||
class MQTTBackendESP32 final : public MQTTBackend {
|
||||
public:
|
||||
static const size_t MQTT_BUFFER_SIZE = 4096;
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
#ifdef USE_ESP8266
|
||||
|
||||
#include "mqtt_backend.h"
|
||||
#include <AsyncMqttClient.h>
|
||||
|
@ -8,7 +8,7 @@
|
|||
namespace esphome {
|
||||
namespace mqtt {
|
||||
|
||||
class MQTTBackendArduino final : public MQTTBackend {
|
||||
class MQTTBackendESP8266 final : public MQTTBackend {
|
||||
public:
|
||||
void set_keep_alive(uint16_t keep_alive) final { mqtt_client_.setKeepAlive(keep_alive); }
|
||||
void set_client_id(const char *client_id) final { mqtt_client_.setClientId(client_id); }
|
||||
|
@ -71,4 +71,4 @@ class MQTTBackendArduino final : public MQTTBackend {
|
|||
} // namespace mqtt
|
||||
} // namespace esphome
|
||||
|
||||
#endif // defined(USE_ARDUINO)
|
||||
#endif // defined(USE_ESP8266)
|
|
@ -556,8 +556,8 @@ static bool topic_match(const char *message, const char *subscription) {
|
|||
}
|
||||
|
||||
void MQTTClientComponent::on_message(const std::string &topic, const std::string &payload) {
|
||||
#ifdef USE_ARDUINO
|
||||
// on Arduino, this is called in lwIP/AsyncTCP task; some components do not like running
|
||||
#ifdef USE_ESP8266
|
||||
// on ESP8266, this is called in lwIP/AsyncTCP task; some components do not like running
|
||||
// from a different task.
|
||||
this->defer([this, topic, payload]() {
|
||||
#endif
|
||||
|
@ -565,7 +565,7 @@ void MQTTClientComponent::on_message(const std::string &topic, const std::string
|
|||
if (topic_match(topic.c_str(), subscription.topic.c_str()))
|
||||
subscription.callback(topic, payload);
|
||||
}
|
||||
#ifdef USE_ARDUINO
|
||||
#ifdef USE_ESP8266
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -9,10 +9,10 @@
|
|||
#include "esphome/core/log.h"
|
||||
#include "esphome/components/json/json_util.h"
|
||||
#include "esphome/components/network/ip_address.h"
|
||||
#if defined(USE_ESP_IDF)
|
||||
#include "mqtt_backend_idf.h"
|
||||
#elif defined(USE_ARDUINO)
|
||||
#include "mqtt_backend_arduino.h"
|
||||
#if defined(USE_ESP32)
|
||||
#include "mqtt_backend_esp32.h"
|
||||
#elif defined(USE_ESP8266)
|
||||
#include "mqtt_backend_esp8266.h"
|
||||
#endif
|
||||
#include "lwip/ip_addr.h"
|
||||
|
||||
|
@ -142,7 +142,7 @@ class MQTTClientComponent : public Component {
|
|||
*/
|
||||
void add_ssl_fingerprint(const std::array<uint8_t, SHA1_SIZE> &fingerprint);
|
||||
#endif
|
||||
#ifdef USE_ESP_IDF
|
||||
#ifdef USE_ESP32
|
||||
void set_ca_certificate(const char *cert) { this->mqtt_backend_.set_ca_certificate(cert); }
|
||||
void set_skip_cert_cn_check(bool skip_check) { this->mqtt_backend_.set_skip_cert_cn_check(skip_check); }
|
||||
#endif
|
||||
|
@ -296,10 +296,10 @@ class MQTTClientComponent : public Component {
|
|||
int log_level_{ESPHOME_LOG_LEVEL};
|
||||
|
||||
std::vector<MQTTSubscription> subscriptions_;
|
||||
#if defined(USE_ESP_IDF)
|
||||
MQTTBackendIDF mqtt_backend_;
|
||||
#elif defined(USE_ARDUINO)
|
||||
MQTTBackendArduino mqtt_backend_;
|
||||
#if defined(USE_ESP32)
|
||||
MQTTBackendESP32 mqtt_backend_;
|
||||
#elif defined(USE_ESP8266)
|
||||
MQTTBackendESP8266 mqtt_backend_;
|
||||
#endif
|
||||
|
||||
MQTTClientState state_{MQTT_CLIENT_DISCONNECTED};
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include "pulse_meter_sensor.h"
|
||||
#include <utility>
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
|
@ -9,66 +10,58 @@ static const char *const TAG = "pulse_meter";
|
|||
void PulseMeterSensor::setup() {
|
||||
this->pin_->setup();
|
||||
this->isr_pin_ = pin_->to_isr();
|
||||
this->pin_->attach_interrupt(PulseMeterSensor::gpio_intr, this, gpio::INTERRUPT_ANY_EDGE);
|
||||
|
||||
this->last_detected_edge_us_ = 0;
|
||||
this->last_valid_edge_us_ = 0;
|
||||
this->pulse_width_us_ = 0;
|
||||
this->sensor_is_high_ = this->isr_pin_.digital_read();
|
||||
this->has_valid_edge_ = false;
|
||||
this->pending_state_change_ = NONE;
|
||||
if (this->filter_mode_ == FILTER_EDGE) {
|
||||
this->pin_->attach_interrupt(PulseMeterSensor::edge_intr, this, gpio::INTERRUPT_RISING_EDGE);
|
||||
} else if (this->filter_mode_ == FILTER_PULSE) {
|
||||
this->pin_->attach_interrupt(PulseMeterSensor::pulse_intr, this, gpio::INTERRUPT_ANY_EDGE);
|
||||
}
|
||||
}
|
||||
// In PULSE mode we set a flag (pending_state_change_) for every interrupt
|
||||
// that constitutes a state change. In the loop() method we check if a time
|
||||
// interval greater than the internal_filter time has passed without any
|
||||
// interrupts.
|
||||
|
||||
void PulseMeterSensor::loop() {
|
||||
// Get a snapshot of the needed volatile sensor values, to make sure they are not
|
||||
// modified by the ISR while we are in the loop() method. If they are changed
|
||||
// after we the variable "now" has been set, overflow will occur in the
|
||||
// subsequent arithmetic
|
||||
const bool has_valid_edge = this->has_valid_edge_;
|
||||
const uint32_t last_detected_edge_us = this->last_detected_edge_us_;
|
||||
const uint32_t last_valid_edge_us = this->last_valid_edge_us_;
|
||||
// Get the current time after the snapshot of saved times
|
||||
const uint32_t now = micros();
|
||||
// Reset the count in get before we pass it back to the ISR as set
|
||||
this->get_->count_ = 0;
|
||||
|
||||
this->handle_state_change_(now, last_detected_edge_us, last_valid_edge_us, has_valid_edge);
|
||||
// Swap out set and get to get the latest state from the ISR
|
||||
// The ISR could interrupt on any of these lines and the results would be consistent
|
||||
auto *temp = this->set_;
|
||||
this->set_ = this->get_;
|
||||
this->get_ = temp;
|
||||
|
||||
// If we've exceeded our timeout interval without receiving any pulses, assume 0 pulses/min until
|
||||
// we get at least two valid pulses.
|
||||
const uint32_t time_since_valid_edge_us = now - last_detected_edge_us;
|
||||
if ((has_valid_edge) && (time_since_valid_edge_us > this->timeout_us_)) {
|
||||
ESP_LOGD(TAG, "No pulse detected for %us, assuming 0 pulses/min", time_since_valid_edge_us / 1000000);
|
||||
|
||||
this->last_valid_edge_us_ = 0;
|
||||
this->pulse_width_us_ = 0;
|
||||
this->has_valid_edge_ = false;
|
||||
this->last_detected_edge_us_ = 0;
|
||||
}
|
||||
|
||||
// We quantize our pulse widths to 1 ms to avoid unnecessary jitter
|
||||
const uint32_t pulse_width_ms = this->pulse_width_us_ / 1000;
|
||||
if (this->pulse_width_dedupe_.next(pulse_width_ms)) {
|
||||
if (pulse_width_ms == 0) {
|
||||
// Treat 0 pulse width as 0 pulses/min (normally because we've not detected any pulses for a while)
|
||||
this->publish_state(0);
|
||||
} else {
|
||||
// Calculate pulses/min from the pulse width in ms
|
||||
this->publish_state((60.0f * 1000.0f) / pulse_width_ms);
|
||||
}
|
||||
}
|
||||
|
||||
if (this->total_sensor_ != nullptr) {
|
||||
const uint32_t total = this->total_pulses_;
|
||||
if (this->total_dedupe_.next(total)) {
|
||||
// Check if we detected a pulse this loop
|
||||
if (this->get_->count_ > 0) {
|
||||
// Keep a running total of pulses if a total sensor is configured
|
||||
if (this->total_sensor_ != nullptr) {
|
||||
this->total_pulses_ += this->get_->count_;
|
||||
const uint32_t total = this->total_pulses_;
|
||||
this->total_sensor_->publish_state(total);
|
||||
}
|
||||
|
||||
// We need to detect at least two edges to have a valid pulse width
|
||||
if (!this->initialized_) {
|
||||
this->initialized_ = true;
|
||||
} else {
|
||||
uint32_t delta_us = this->get_->last_detected_edge_us_ - this->last_processed_edge_us_;
|
||||
float pulse_width_us = delta_us / float(this->get_->count_);
|
||||
this->publish_state((60.0f * 1000000.0f) / pulse_width_us);
|
||||
}
|
||||
|
||||
this->last_processed_edge_us_ = this->get_->last_detected_edge_us_;
|
||||
}
|
||||
// No detected edges this loop
|
||||
else {
|
||||
const uint32_t now = micros();
|
||||
const uint32_t time_since_valid_edge_us = now - this->last_processed_edge_us_;
|
||||
|
||||
if (this->initialized_ && time_since_valid_edge_us > this->timeout_us_) {
|
||||
ESP_LOGD(TAG, "No pulse detected for %us, assuming 0 pulses/min", time_since_valid_edge_us / 1000000);
|
||||
this->initialized_ = false;
|
||||
this->publish_state(0.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PulseMeterSensor::set_total_pulses(uint32_t pulses) { this->total_pulses_ = pulses; }
|
||||
float PulseMeterSensor::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
void PulseMeterSensor::dump_config() {
|
||||
LOG_SENSOR("", "Pulse Meter", this);
|
||||
|
@ -81,96 +74,49 @@ void PulseMeterSensor::dump_config() {
|
|||
ESP_LOGCONFIG(TAG, " Assuming 0 pulses/min after not receiving a pulse for %us", this->timeout_us_ / 1000000);
|
||||
}
|
||||
|
||||
void IRAM_ATTR PulseMeterSensor::gpio_intr(PulseMeterSensor *sensor) {
|
||||
void IRAM_ATTR PulseMeterSensor::edge_intr(PulseMeterSensor *sensor) {
|
||||
// This is an interrupt handler - we can't call any virtual method from this method
|
||||
// Get the current time before we do anything else so the measurements are consistent
|
||||
const uint32_t now = micros();
|
||||
|
||||
if ((now - sensor->last_edge_candidate_us_) >= sensor->filter_us_) {
|
||||
sensor->last_edge_candidate_us_ = now;
|
||||
sensor->set_->last_detected_edge_us_ = now;
|
||||
sensor->set_->count_++;
|
||||
}
|
||||
}
|
||||
|
||||
void IRAM_ATTR PulseMeterSensor::pulse_intr(PulseMeterSensor *sensor) {
|
||||
// This is an interrupt handler - we can't call any virtual method from this method
|
||||
// Get the current time before we do anything else so the measurements are consistent
|
||||
const uint32_t now = micros();
|
||||
const bool pin_val = sensor->isr_pin_.digital_read();
|
||||
|
||||
if (sensor->filter_mode_ == FILTER_EDGE) {
|
||||
// We only look at rising edges
|
||||
if (!pin_val) {
|
||||
return;
|
||||
// A pulse occurred faster than we can detect
|
||||
if (sensor->last_pin_val_ == pin_val) {
|
||||
// If we haven't reached the filter length yet we need to reset our last_intr_ to now
|
||||
// otherwise we can consider this noise as the "pulse" was certainly less than filter_us_
|
||||
if (now - sensor->last_intr_ < sensor->filter_us_) {
|
||||
sensor->last_intr_ = now;
|
||||
}
|
||||
// Check to see if we should filter this edge out
|
||||
if ((now - sensor->last_detected_edge_us_) >= sensor->filter_us_) {
|
||||
// Don't measure the first valid pulse (we need at least two pulses to measure the width)
|
||||
if (sensor->has_valid_edge_) {
|
||||
sensor->pulse_width_us_ = (now - sensor->last_valid_edge_us_);
|
||||
}
|
||||
sensor->total_pulses_++;
|
||||
sensor->last_valid_edge_us_ = now;
|
||||
sensor->has_valid_edge_ = true;
|
||||
}
|
||||
sensor->last_detected_edge_us_ = now;
|
||||
} else {
|
||||
// Filter Mode is PULSE
|
||||
const uint32_t delta_t_us = now - sensor->last_detected_edge_us_;
|
||||
// We need to check if we have missed to handle a state change in the
|
||||
// loop() function. This can happen when the filter_us value is less than
|
||||
// the loop() interval, which is ~50-60ms
|
||||
// The section below is essentially a modified repeat of the
|
||||
// handle_state_change method. Ideally i would refactor and call the
|
||||
// method here as well. However functions called in ISRs need to meet
|
||||
// strict criteria and I don't think the methos would meet them.
|
||||
if (sensor->pending_state_change_ != NONE && (delta_t_us > sensor->filter_us_)) {
|
||||
// We have missed to handle a state change in the loop function.
|
||||
sensor->sensor_is_high_ = sensor->pending_state_change_ == TO_HIGH;
|
||||
if (sensor->sensor_is_high_) {
|
||||
// We need to handle a pulse that would have been missed by the loop function
|
||||
sensor->total_pulses_++;
|
||||
if (sensor->has_valid_edge_) {
|
||||
sensor->pulse_width_us_ = sensor->last_detected_edge_us_ - sensor->last_valid_edge_us_;
|
||||
sensor->has_valid_edge_ = true;
|
||||
sensor->last_valid_edge_us_ = sensor->last_detected_edge_us_;
|
||||
}
|
||||
// Check if the last interrupt was long enough in the past
|
||||
if (now - sensor->last_intr_ > sensor->filter_us_) {
|
||||
// High pulse of filter length now falling (therefore last_intr_ was the rising edge)
|
||||
if (!sensor->in_pulse_ && sensor->last_pin_val_) {
|
||||
sensor->last_edge_candidate_us_ = sensor->last_intr_;
|
||||
sensor->in_pulse_ = true;
|
||||
}
|
||||
} // End of checking for and handling of change in state
|
||||
|
||||
// Ignore false edges that may be caused by bouncing and exit the ISR ASAP
|
||||
if (pin_val == sensor->sensor_is_high_) {
|
||||
sensor->pending_state_change_ = NONE;
|
||||
return;
|
||||
}
|
||||
sensor->pending_state_change_ = pin_val ? TO_HIGH : TO_LOW;
|
||||
sensor->last_detected_edge_us_ = now;
|
||||
}
|
||||
}
|
||||
|
||||
void PulseMeterSensor::handle_state_change_(uint32_t now, uint32_t last_detected_edge_us, uint32_t last_valid_edge_us,
|
||||
bool has_valid_edge) {
|
||||
if (this->pending_state_change_ == NONE) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bool pin_val = this->isr_pin_.digital_read();
|
||||
if (pin_val == this->sensor_is_high_) {
|
||||
// Most likely caused by high frequency bouncing. Theoretically we should
|
||||
// expect interrupts of alternating state. Here we are registering an
|
||||
// interrupt with no change in state. Another interrupt will likely trigger
|
||||
// just after this one and have an alternate state.
|
||||
this->pending_state_change_ = NONE;
|
||||
return;
|
||||
}
|
||||
|
||||
if ((now - last_detected_edge_us) > this->filter_us_) {
|
||||
this->sensor_is_high_ = pin_val;
|
||||
ESP_LOGVV(TAG, "State is now %s", pin_val ? "high" : "low");
|
||||
|
||||
// Increment with valid rising edges only
|
||||
if (pin_val) {
|
||||
this->total_pulses_++;
|
||||
ESP_LOGVV(TAG, "Incremented pulses to %u", this->total_pulses_);
|
||||
|
||||
if (has_valid_edge) {
|
||||
this->pulse_width_us_ = last_detected_edge_us - last_valid_edge_us;
|
||||
ESP_LOGVV(TAG, "Set pulse width to %u", this->pulse_width_us_);
|
||||
// Low pulse of filter length now rising (therefore last_intr_ was the falling edge)
|
||||
else if (sensor->in_pulse_ && !sensor->last_pin_val_) {
|
||||
sensor->set_->last_detected_edge_us_ = sensor->last_edge_candidate_us_;
|
||||
sensor->set_->count_++;
|
||||
sensor->in_pulse_ = false;
|
||||
}
|
||||
this->has_valid_edge_ = true;
|
||||
this->last_valid_edge_us_ = last_detected_edge_us;
|
||||
ESP_LOGVV(TAG, "last_valid_edge_us_ is now %u", this->last_valid_edge_us_);
|
||||
}
|
||||
this->pending_state_change_ = NONE;
|
||||
|
||||
sensor->last_intr_ = now;
|
||||
sensor->last_pin_val_ = pin_val;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,41 +17,50 @@ class PulseMeterSensor : public sensor::Sensor, public Component {
|
|||
|
||||
void set_pin(InternalGPIOPin *pin) { this->pin_ = pin; }
|
||||
void set_filter_us(uint32_t filter) { this->filter_us_ = filter; }
|
||||
void set_filter_mode(InternalFilterMode mode) { this->filter_mode_ = mode; }
|
||||
void set_timeout_us(uint32_t timeout) { this->timeout_us_ = timeout; }
|
||||
void set_total_sensor(sensor::Sensor *sensor) { this->total_sensor_ = sensor; }
|
||||
|
||||
void set_total_pulses(uint32_t pulses);
|
||||
void set_filter_mode(InternalFilterMode mode) { this->filter_mode_ = mode; }
|
||||
void set_total_pulses(uint32_t pulses) { this->total_pulses_ = pulses; }
|
||||
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
float get_setup_priority() const override;
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
enum StateChange { TO_LOW = 0, TO_HIGH, NONE };
|
||||
|
||||
static void gpio_intr(PulseMeterSensor *sensor);
|
||||
void handle_state_change_(uint32_t now, uint32_t last_detected_edge_us, uint32_t last_valid_edge_us,
|
||||
bool has_valid_edge);
|
||||
static void edge_intr(PulseMeterSensor *sensor);
|
||||
static void pulse_intr(PulseMeterSensor *sensor);
|
||||
|
||||
InternalGPIOPin *pin_{nullptr};
|
||||
ISRInternalGPIOPin isr_pin_;
|
||||
uint32_t filter_us_ = 0;
|
||||
uint32_t timeout_us_ = 1000000UL * 60UL * 5UL;
|
||||
sensor::Sensor *total_sensor_{nullptr};
|
||||
InternalFilterMode filter_mode_{FILTER_EDGE};
|
||||
|
||||
Deduplicator<uint32_t> pulse_width_dedupe_;
|
||||
Deduplicator<uint32_t> total_dedupe_;
|
||||
// Variables used in the loop
|
||||
bool initialized_ = false;
|
||||
uint32_t total_pulses_ = 0;
|
||||
uint32_t last_processed_edge_us_ = 0;
|
||||
|
||||
volatile uint32_t last_detected_edge_us_ = 0;
|
||||
volatile uint32_t last_valid_edge_us_ = 0;
|
||||
volatile uint32_t pulse_width_us_ = 0;
|
||||
volatile uint32_t total_pulses_ = 0;
|
||||
volatile bool sensor_is_high_ = false;
|
||||
volatile bool has_valid_edge_ = false;
|
||||
volatile StateChange pending_state_change_{NONE};
|
||||
// This struct (and the two pointers) are used to pass data between the ISR and loop.
|
||||
// These two pointers are exchanged each loop.
|
||||
// Therefore you can't use data in the pointer to loop receives to set values in the pointer to loop sends.
|
||||
// As a result it's easiest if you only use these pointers to send data from the ISR to the loop.
|
||||
// (except for resetting the values)
|
||||
struct State {
|
||||
uint32_t last_detected_edge_us_ = 0;
|
||||
uint32_t count_ = 0;
|
||||
};
|
||||
State state_[2];
|
||||
volatile State *set_ = state_;
|
||||
volatile State *get_ = state_ + 1;
|
||||
|
||||
// Only use these variables in the ISR
|
||||
ISRInternalGPIOPin isr_pin_;
|
||||
uint32_t last_edge_candidate_us_ = 0;
|
||||
uint32_t last_intr_ = 0;
|
||||
bool in_pulse_ = false;
|
||||
bool last_pin_val_ = false;
|
||||
};
|
||||
|
||||
} // namespace pulse_meter
|
||||
|
|
|
@ -16,18 +16,22 @@ CODEOWNERS = ["@grahambrown11"]
|
|||
|
||||
CONF_CODES = "codes"
|
||||
CONF_BYPASS_ARMED_HOME = "bypass_armed_home"
|
||||
CONF_BYPASS_ARMED_NIGHT = "bypass_armed_night"
|
||||
CONF_REQUIRES_CODE_TO_ARM = "requires_code_to_arm"
|
||||
CONF_ARMING_HOME_TIME = "arming_home_time"
|
||||
CONF_ARMING_NIGHT_TIME = "arming_night_time"
|
||||
CONF_ARMING_AWAY_TIME = "arming_away_time"
|
||||
CONF_PENDING_TIME = "pending_time"
|
||||
CONF_TRIGGER_TIME = "trigger_time"
|
||||
|
||||
FLAG_NORMAL = "normal"
|
||||
FLAG_BYPASS_ARMED_HOME = "bypass_armed_home"
|
||||
FLAG_BYPASS_ARMED_NIGHT = "bypass_armed_night"
|
||||
|
||||
BinarySensorFlags = {
|
||||
FLAG_NORMAL: 1 << 0,
|
||||
FLAG_BYPASS_ARMED_HOME: 1 << 1,
|
||||
FLAG_BYPASS_ARMED_NIGHT: 1 << 2,
|
||||
}
|
||||
|
||||
TemplateAlarmControlPanel = template_ns.class_(
|
||||
|
@ -55,6 +59,7 @@ TEMPLATE_ALARM_CONTROL_PANEL_BINARY_SENSOR_SCHEMA = cv.maybe_simple_value(
|
|||
{
|
||||
cv.Required(CONF_INPUT): cv.use_id(binary_sensor.BinarySensor),
|
||||
cv.Optional(CONF_BYPASS_ARMED_HOME, default=False): cv.boolean,
|
||||
cv.Optional(CONF_BYPASS_ARMED_NIGHT, default=False): cv.boolean,
|
||||
},
|
||||
key=CONF_INPUT,
|
||||
)
|
||||
|
@ -66,6 +71,7 @@ TEMPLATE_ALARM_CONTROL_PANEL_SCHEMA = (
|
|||
cv.Optional(CONF_CODES): cv.ensure_list(cv.string_strict),
|
||||
cv.Optional(CONF_REQUIRES_CODE_TO_ARM): cv.boolean,
|
||||
cv.Optional(CONF_ARMING_HOME_TIME): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_ARMING_NIGHT_TIME): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(
|
||||
CONF_ARMING_AWAY_TIME, default="0s"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
|
@ -110,14 +116,23 @@ async def to_code(config):
|
|||
cg.add(var.set_arming_home_time(config[CONF_ARMING_HOME_TIME]))
|
||||
supports_arm_home = True
|
||||
|
||||
supports_arm_night = False
|
||||
if CONF_ARMING_NIGHT_TIME in config:
|
||||
cg.add(var.set_arming_night_time(config[CONF_ARMING_NIGHT_TIME]))
|
||||
supports_arm_night = True
|
||||
|
||||
for sensor in config.get(CONF_BINARY_SENSORS, []):
|
||||
bs = await cg.get_variable(sensor[CONF_INPUT])
|
||||
flags = BinarySensorFlags[FLAG_NORMAL]
|
||||
if sensor[CONF_BYPASS_ARMED_HOME]:
|
||||
flags |= BinarySensorFlags[FLAG_BYPASS_ARMED_HOME]
|
||||
supports_arm_home = True
|
||||
if sensor[CONF_BYPASS_ARMED_NIGHT]:
|
||||
flags |= BinarySensorFlags[FLAG_BYPASS_ARMED_NIGHT]
|
||||
supports_arm_night = True
|
||||
cg.add(var.add_sensor(bs, flags))
|
||||
|
||||
cg.add(var.set_supports_arm_home(supports_arm_home))
|
||||
cg.add(var.set_supports_arm_night(supports_arm_night))
|
||||
|
||||
cg.add(var.set_restore_mode(config[CONF_RESTORE_MODE]))
|
||||
|
|
|
@ -29,6 +29,8 @@ void TemplateAlarmControlPanel::dump_config() {
|
|||
ESP_LOGCONFIG(TAG, " Arming Away Time: %us", (this->arming_away_time_ / 1000));
|
||||
if (this->arming_home_time_ != 0)
|
||||
ESP_LOGCONFIG(TAG, " Arming Home Time: %us", (this->arming_home_time_ / 1000));
|
||||
if (this->arming_night_time_ != 0)
|
||||
ESP_LOGCONFIG(TAG, " Arming Night Time: %us", (this->arming_night_time_ / 1000));
|
||||
ESP_LOGCONFIG(TAG, " Pending Time: %us", (this->pending_time_ / 1000));
|
||||
ESP_LOGCONFIG(TAG, " Trigger Time: %us", (this->trigger_time_ / 1000));
|
||||
ESP_LOGCONFIG(TAG, " Supported Features: %u", this->get_supported_features());
|
||||
|
@ -38,6 +40,8 @@ void TemplateAlarmControlPanel::dump_config() {
|
|||
ESP_LOGCONFIG(TAG, " Name: %s", sensor_pair.first->get_name().c_str());
|
||||
ESP_LOGCONFIG(TAG, " Armed home bypass: %s",
|
||||
TRUEFALSE(sensor_pair.second & BINARY_SENSOR_MODE_BYPASS_ARMED_HOME));
|
||||
ESP_LOGCONFIG(TAG, " Armed night bypass: %s",
|
||||
TRUEFALSE(sensor_pair.second & BINARY_SENSOR_MODE_BYPASS_ARMED_NIGHT));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
@ -69,6 +73,9 @@ void TemplateAlarmControlPanel::loop() {
|
|||
if (this->desired_state_ == ACP_STATE_ARMED_HOME) {
|
||||
delay = this->arming_home_time_;
|
||||
}
|
||||
if (this->desired_state_ == ACP_STATE_ARMED_NIGHT) {
|
||||
delay = this->arming_night_time_;
|
||||
}
|
||||
if ((millis() - this->last_update_) > delay) {
|
||||
this->publish_state(this->desired_state_);
|
||||
}
|
||||
|
@ -95,6 +102,10 @@ void TemplateAlarmControlPanel::loop() {
|
|||
(sensor_pair.second & BINARY_SENSOR_MODE_BYPASS_ARMED_HOME)) {
|
||||
continue;
|
||||
}
|
||||
if (this->current_state_ == ACP_STATE_ARMED_NIGHT &&
|
||||
(sensor_pair.second & BINARY_SENSOR_MODE_BYPASS_ARMED_NIGHT)) {
|
||||
continue;
|
||||
}
|
||||
trigger = true;
|
||||
break;
|
||||
}
|
||||
|
@ -129,6 +140,9 @@ uint32_t TemplateAlarmControlPanel::get_supported_features() const {
|
|||
if (this->supports_arm_home_) {
|
||||
features |= ACP_FEAT_ARM_HOME;
|
||||
}
|
||||
if (this->supports_arm_night_) {
|
||||
features |= ACP_FEAT_ARM_NIGHT;
|
||||
}
|
||||
return features;
|
||||
}
|
||||
|
||||
|
@ -158,6 +172,8 @@ void TemplateAlarmControlPanel::control(const AlarmControlPanelCall &call) {
|
|||
this->arm_(call.get_code(), ACP_STATE_ARMED_AWAY, this->arming_away_time_);
|
||||
} else if (call.get_state() == ACP_STATE_ARMED_HOME) {
|
||||
this->arm_(call.get_code(), ACP_STATE_ARMED_HOME, this->arming_home_time_);
|
||||
} else if (call.get_state() == ACP_STATE_ARMED_NIGHT) {
|
||||
this->arm_(call.get_code(), ACP_STATE_ARMED_NIGHT, this->arming_night_time_);
|
||||
} else if (call.get_state() == ACP_STATE_DISARMED) {
|
||||
if (!this->is_code_valid_(call.get_code())) {
|
||||
ESP_LOGW(TAG, "Not disarming code doesn't match");
|
||||
|
|
|
@ -19,6 +19,7 @@ namespace template_ {
|
|||
enum BinarySensorFlags : uint16_t {
|
||||
BINARY_SENSOR_MODE_NORMAL = 1 << 0,
|
||||
BINARY_SENSOR_MODE_BYPASS_ARMED_HOME = 1 << 1,
|
||||
BINARY_SENSOR_MODE_BYPASS_ARMED_NIGHT = 1 << 2,
|
||||
};
|
||||
#endif
|
||||
|
||||
|
@ -71,6 +72,12 @@ class TemplateAlarmControlPanel : public alarm_control_panel::AlarmControlPanel,
|
|||
*/
|
||||
void set_arming_home_time(uint32_t time) { this->arming_home_time_ = time; }
|
||||
|
||||
/** set the delay before arming night
|
||||
*
|
||||
* @param time The milliseconds
|
||||
*/
|
||||
void set_arming_night_time(uint32_t time) { this->arming_night_time_ = time; }
|
||||
|
||||
/** set the delay before triggering
|
||||
*
|
||||
* @param time The milliseconds
|
||||
|
@ -85,6 +92,8 @@ class TemplateAlarmControlPanel : public alarm_control_panel::AlarmControlPanel,
|
|||
|
||||
void set_supports_arm_home(bool supports_arm_home) { supports_arm_home_ = supports_arm_home; }
|
||||
|
||||
void set_supports_arm_night(bool supports_arm_night) { supports_arm_night_ = supports_arm_night; }
|
||||
|
||||
protected:
|
||||
void control(const alarm_control_panel::AlarmControlPanelCall &call) override;
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
|
@ -97,6 +106,8 @@ class TemplateAlarmControlPanel : public alarm_control_panel::AlarmControlPanel,
|
|||
uint32_t arming_away_time_;
|
||||
// the arming home delay
|
||||
uint32_t arming_home_time_{0};
|
||||
// the arming night delay
|
||||
uint32_t arming_night_time_{0};
|
||||
// the trigger delay
|
||||
uint32_t pending_time_;
|
||||
// the time in trigger
|
||||
|
@ -106,6 +117,7 @@ class TemplateAlarmControlPanel : public alarm_control_panel::AlarmControlPanel,
|
|||
// requires a code to arm
|
||||
bool requires_code_to_arm_ = false;
|
||||
bool supports_arm_home_ = false;
|
||||
bool supports_arm_night_ = false;
|
||||
// check if the code is valid
|
||||
bool is_code_valid_(optional<std::string> code);
|
||||
|
||||
|
|
|
@ -51,6 +51,9 @@ WaveshareEPaper7P5InBC = waveshare_epaper_ns.class_(
|
|||
WaveshareEPaper7P5InBV2 = waveshare_epaper_ns.class_(
|
||||
"WaveshareEPaper7P5InBV2", WaveshareEPaper
|
||||
)
|
||||
WaveshareEPaper7P5InBV3 = waveshare_epaper_ns.class_(
|
||||
"WaveshareEPaper7P5InBV3", WaveshareEPaper
|
||||
)
|
||||
WaveshareEPaper7P5InV2 = waveshare_epaper_ns.class_(
|
||||
"WaveshareEPaper7P5InV2", WaveshareEPaper
|
||||
)
|
||||
|
@ -87,6 +90,7 @@ MODELS = {
|
|||
"5.83inv2": ("b", WaveshareEPaper5P8InV2),
|
||||
"7.50in": ("b", WaveshareEPaper7P5In),
|
||||
"7.50in-bv2": ("b", WaveshareEPaper7P5InBV2),
|
||||
"7.50in-bv3": ("b", WaveshareEPaper7P5InBV3),
|
||||
"7.50in-bc": ("b", WaveshareEPaper7P5InBC),
|
||||
"7.50inv2": ("b", WaveshareEPaper7P5InV2),
|
||||
"7.50inv2alt": ("b", WaveshareEPaper7P5InV2alt),
|
||||
|
|
|
@ -1328,6 +1328,157 @@ void WaveshareEPaper7P5InBV2::dump_config() {
|
|||
LOG_PIN(" Busy Pin: ", this->busy_pin_);
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
}
|
||||
|
||||
bool WaveshareEPaper7P5InBV3::wait_until_idle_() {
|
||||
if (this->busy_pin_ == nullptr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const uint32_t start = millis();
|
||||
while (this->busy_pin_->digital_read()) {
|
||||
this->command(0x71);
|
||||
if (millis() - start > this->idle_timeout_()) {
|
||||
ESP_LOGI(TAG, "Timeout while displaying image!");
|
||||
return false;
|
||||
}
|
||||
delay(10);
|
||||
}
|
||||
delay(200); // NOLINT
|
||||
return true;
|
||||
};
|
||||
void WaveshareEPaper7P5InBV3::initialize() {
|
||||
this->reset_();
|
||||
|
||||
// COMMAND POWER SETTING
|
||||
this->command(0x01);
|
||||
|
||||
// 1-0=11: internal power
|
||||
this->data(0x07);
|
||||
this->data(0x17); // VGH&VGL
|
||||
this->data(0x3F); // VSH
|
||||
this->data(0x26); // VSL
|
||||
this->data(0x11); // VSHR
|
||||
|
||||
// VCOM DC Setting
|
||||
this->command(0x82);
|
||||
this->data(0x24); // VCOM
|
||||
|
||||
// Booster Setting
|
||||
this->command(0x06);
|
||||
this->data(0x27);
|
||||
this->data(0x27);
|
||||
this->data(0x2F);
|
||||
this->data(0x17);
|
||||
|
||||
// POWER ON
|
||||
this->command(0x04);
|
||||
|
||||
delay(100); // NOLINT
|
||||
this->wait_until_idle_();
|
||||
// COMMAND PANEL SETTING
|
||||
this->command(0x00);
|
||||
this->data(0x3F); // KW-3f KWR-2F BWROTP 0f BWOTP 1f
|
||||
|
||||
// COMMAND RESOLUTION SETTING
|
||||
this->command(0x61);
|
||||
this->data(0x03); // source 800
|
||||
this->data(0x20);
|
||||
this->data(0x01); // gate 480
|
||||
this->data(0xE0);
|
||||
// COMMAND ...?
|
||||
this->command(0x15);
|
||||
this->data(0x00);
|
||||
// COMMAND VCOM AND DATA INTERVAL SETTING
|
||||
this->command(0x50);
|
||||
this->data(0x10);
|
||||
this->data(0x00);
|
||||
// COMMAND TCON SETTING
|
||||
this->command(0x60);
|
||||
this->data(0x22);
|
||||
// Resolution setting
|
||||
this->command(0x65);
|
||||
this->data(0x00);
|
||||
this->data(0x00); // 800*480
|
||||
this->data(0x00);
|
||||
this->data(0x00);
|
||||
|
||||
this->wait_until_idle_();
|
||||
|
||||
uint8_t lut_vcom_7_i_n5_v2[] = {
|
||||
0x0, 0xF, 0xF, 0x0, 0x0, 0x1, 0x0, 0xF, 0x1, 0xF, 0x1, 0x2, 0x0, 0xF, 0xF, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
};
|
||||
|
||||
uint8_t lut_ww_7_i_n5_v2[] = {
|
||||
0x10, 0xF, 0xF, 0x0, 0x0, 0x1, 0x84, 0xF, 0x1, 0xF, 0x1, 0x2, 0x20, 0xF, 0xF, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
};
|
||||
|
||||
uint8_t lut_bw_7_i_n5_v2[] = {
|
||||
0x10, 0xF, 0xF, 0x0, 0x0, 0x1, 0x84, 0xF, 0x1, 0xF, 0x1, 0x2, 0x20, 0xF, 0xF, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
};
|
||||
|
||||
uint8_t lut_wb_7_i_n5_v2[] = {
|
||||
0x80, 0xF, 0xF, 0x0, 0x0, 0x3, 0x84, 0xF, 0x1, 0xF, 0x1, 0x4, 0x40, 0xF, 0xF, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
};
|
||||
|
||||
uint8_t lut_bb_7_i_n5_v2[] = {
|
||||
0x80, 0xF, 0xF, 0x0, 0x0, 0x1, 0x84, 0xF, 0x1, 0xF, 0x1, 0x2, 0x40, 0xF, 0xF, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
};
|
||||
|
||||
uint8_t count;
|
||||
this->command(0x20); // VCOM
|
||||
for (count = 0; count < 42; count++)
|
||||
this->data(lut_vcom_7_i_n5_v2[count]);
|
||||
|
||||
this->command(0x21); // LUTBW
|
||||
for (count = 0; count < 42; count++)
|
||||
this->data(lut_ww_7_i_n5_v2[count]);
|
||||
|
||||
this->command(0x22); // LUTBW
|
||||
for (count = 0; count < 42; count++)
|
||||
this->data(lut_bw_7_i_n5_v2[count]);
|
||||
|
||||
this->command(0x23); // LUTWB
|
||||
for (count = 0; count < 42; count++)
|
||||
this->data(lut_wb_7_i_n5_v2[count]);
|
||||
|
||||
this->command(0x24); // LUTBB
|
||||
for (count = 0; count < 42; count++)
|
||||
this->data(lut_bb_7_i_n5_v2[count]);
|
||||
|
||||
this->command(0x10);
|
||||
for (uint32_t i = 0; i < 800 * 480 / 8; i++) {
|
||||
this->data(0xFF);
|
||||
}
|
||||
};
|
||||
void HOT WaveshareEPaper7P5InBV3::display() {
|
||||
uint32_t buf_len = this->get_buffer_length_();
|
||||
|
||||
this->command(0x13); // Start Transmission
|
||||
delay(2);
|
||||
for (uint32_t i = 0; i < buf_len; i++) {
|
||||
this->data(~(this->buffer_[i]));
|
||||
}
|
||||
|
||||
this->command(0x12); // Display Refresh
|
||||
delay(100); // NOLINT
|
||||
this->wait_until_idle_();
|
||||
}
|
||||
int WaveshareEPaper7P5InBV3::get_width_internal() { return 800; }
|
||||
int WaveshareEPaper7P5InBV3::get_height_internal() { return 480; }
|
||||
void WaveshareEPaper7P5InBV3::dump_config() {
|
||||
LOG_DISPLAY("", "Waveshare E-Paper", this);
|
||||
ESP_LOGCONFIG(TAG, " Model: 7.5in-bv3");
|
||||
LOG_PIN(" Reset Pin: ", this->reset_pin_);
|
||||
LOG_PIN(" DC Pin: ", this->dc_pin_);
|
||||
LOG_PIN(" Busy Pin: ", this->busy_pin_);
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
}
|
||||
|
||||
void WaveshareEPaper7P5In::initialize() {
|
||||
// COMMAND POWER SETTING
|
||||
this->command(0x01);
|
||||
|
|
|
@ -413,6 +413,40 @@ class WaveshareEPaper7P5InBV2 : public WaveshareEPaper {
|
|||
int get_height_internal() override;
|
||||
};
|
||||
|
||||
class WaveshareEPaper7P5InBV3 : public WaveshareEPaper {
|
||||
public:
|
||||
bool wait_until_idle_();
|
||||
|
||||
void initialize() override;
|
||||
|
||||
void display() override;
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
void deep_sleep() override {
|
||||
this->command(0x02); // Power off
|
||||
this->wait_until_idle_();
|
||||
this->command(0x07); // Deep sleep
|
||||
this->data(0xA5);
|
||||
}
|
||||
|
||||
protected:
|
||||
int get_width_internal() override;
|
||||
|
||||
int get_height_internal() override;
|
||||
|
||||
void reset_() {
|
||||
if (this->reset_pin_ != nullptr) {
|
||||
this->reset_pin_->digital_write(true);
|
||||
delay(200); // NOLINT
|
||||
this->reset_pin_->digital_write(false);
|
||||
delay(5);
|
||||
this->reset_pin_->digital_write(true);
|
||||
delay(200); // NOLINT
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
class WaveshareEPaper7P5InBC : public WaveshareEPaper {
|
||||
public:
|
||||
void initialize() override;
|
||||
|
|
|
@ -103,6 +103,11 @@
|
|||
|
||||
#endif
|
||||
|
||||
#ifdef USE_RP2040
|
||||
#define USE_ARDUINO_VERSION_CODE VERSION_CODE(3, 3, 0)
|
||||
#define USE_SOCKET_IMPL_LWIP_TCP
|
||||
#endif
|
||||
|
||||
#ifdef USE_HOST
|
||||
#define USE_SOCKET_IMPL_BSD_SOCKETS
|
||||
#endif
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
#include "esp32/rom/crc.h"
|
||||
#endif
|
||||
|
||||
#ifdef USE_ESP32_IGNORE_EFUSE_MAC_CRC
|
||||
#if defined(CONFIG_SOC_IEEE802154_SUPPORTED) || defined(USE_ESP32_IGNORE_EFUSE_MAC_CRC)
|
||||
#include "esp_efuse.h"
|
||||
#include "esp_efuse_table.h"
|
||||
#endif
|
||||
|
@ -540,7 +540,9 @@ bool HighFrequencyLoopRequester::is_high_frequency() { return num_requests > 0;
|
|||
|
||||
void get_mac_address_raw(uint8_t *mac) { // NOLINT(readability-non-const-parameter)
|
||||
#if defined(USE_ESP32)
|
||||
#if defined(USE_ESP32_IGNORE_EFUSE_MAC_CRC)
|
||||
#if defined(CONFIG_SOC_IEEE802154_SUPPORTED) || defined(USE_ESP32_IGNORE_EFUSE_MAC_CRC)
|
||||
// When CONFIG_SOC_IEEE802154_SUPPORTED is defined, esp_efuse_mac_get_default
|
||||
// returns the 802.15.4 EUI-64 address. Read directly from eFuse instead.
|
||||
// On some devices, the MAC address that is burnt into EFuse does not
|
||||
// match the CRC that goes along with it. For those devices, this
|
||||
// work-around reads and uses the MAC address as-is from EFuse,
|
||||
|
|
|
@ -57,7 +57,6 @@ lib_deps =
|
|||
${common.lib_deps}
|
||||
SPI ; spi (Arduino built-in)
|
||||
Wire ; i2c (Arduino built-int)
|
||||
ottowinter/AsyncMqttClient-esphome@0.8.6 ; mqtt
|
||||
esphome/ESPAsyncWebServer-esphome@2.1.0 ; web_server_base
|
||||
fastled/FastLED@3.3.2 ; fastled_base
|
||||
mikalhart/TinyGPSPlus@1.0.2 ; gps
|
||||
|
@ -90,6 +89,7 @@ lib_deps =
|
|||
${common:arduino.lib_deps}
|
||||
ESP8266WiFi ; wifi (Arduino built-in)
|
||||
Update ; ota (Arduino built-in)
|
||||
ottowinter/AsyncMqttClient-esphome@0.8.6 ; mqtt
|
||||
esphome/ESPAsyncTCP-esphome@1.2.3 ; async_tcp
|
||||
ESP8266HTTPClient ; http_request (Arduino built-in)
|
||||
ESP8266mDNS ; mdns (Arduino built-in)
|
||||
|
|
|
@ -6,7 +6,7 @@ tornado==6.3.2
|
|||
tzlocal==5.0.1 # from time
|
||||
tzdata>=2021.1 # from time
|
||||
pyserial==3.5
|
||||
platformio==6.1.7 # When updating platformio, also update Dockerfile
|
||||
platformio==6.1.9 # When updating platformio, also update Dockerfile
|
||||
esptool==4.6.2
|
||||
click==8.1.6
|
||||
esphome-dashboard==20230711.0
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
pylint==2.17.5
|
||||
flake8==6.0.0 # also change in .pre-commit-config.yaml when updating
|
||||
black==23.7.0 # also change in .pre-commit-config.yaml when updating
|
||||
pyupgrade==3.9.0 # also change in .pre-commit-config.yaml when updating
|
||||
pyupgrade==3.10.1 # also change in .pre-commit-config.yaml when updating
|
||||
pre-commit
|
||||
|
||||
# Unit tests
|
||||
pytest==7.4.0
|
||||
pytest-cov==4.1.0
|
||||
pytest-mock==3.11.1
|
||||
pytest-asyncio==0.21.0
|
||||
pytest-asyncio==0.21.1
|
||||
asyncmock==0.4.2
|
||||
hypothesis==5.49.0
|
||||
|
||||
|
|
|
@ -72,7 +72,7 @@ def clang_options(idedata):
|
|||
# copy compiler flags, except those clang doesn't understand.
|
||||
cmd.extend(
|
||||
flag
|
||||
for flag in idedata["cxx_flags"].split(" ")
|
||||
for flag in idedata["cxx_flags"]
|
||||
if flag
|
||||
not in (
|
||||
"-free",
|
||||
|
|
|
@ -800,6 +800,13 @@ sensor:
|
|||
name: INA3221 Channel 1 Shunt Voltage
|
||||
update_interval: 15s
|
||||
i2c_id: i2c_bus
|
||||
- platform: kmeteriso
|
||||
temperature:
|
||||
name: Outside Temperature
|
||||
internal_temperature:
|
||||
name: Internal Ttemperature
|
||||
update_interval: 15s
|
||||
i2c_id: i2c_bus
|
||||
- platform: kalman_combinator
|
||||
name: Kalman-filtered temperature
|
||||
process_std_dev: 0.00139
|
||||
|
@ -1363,6 +1370,14 @@ sensor:
|
|||
name: "Distance"
|
||||
update_interval: 60s
|
||||
i2c_id: i2c_bus
|
||||
- platform: bmp581
|
||||
i2c_id: i2c_bus
|
||||
temperature:
|
||||
name: "BMP581 Temperature"
|
||||
iir_filter: 2x
|
||||
pressure:
|
||||
name: "BMP581 Pressure"
|
||||
oversampling: 128x
|
||||
|
||||
esp32_touch:
|
||||
setup_mode: false
|
||||
|
|
|
@ -1194,12 +1194,14 @@ alarm_control_panel:
|
|||
- "1234"
|
||||
requires_code_to_arm: true
|
||||
arming_home_time: 1s
|
||||
arming_night_time: 1s
|
||||
arming_away_time: 15s
|
||||
pending_time: 15s
|
||||
trigger_time: 30s
|
||||
binary_sensors:
|
||||
- input: bin1
|
||||
bypass_armed_home: true
|
||||
bypass_armed_night: true
|
||||
on_state:
|
||||
then:
|
||||
- lambda: !lambda |-
|
||||
|
|
Loading…
Reference in a new issue