Merge branch 'dev' into hbridge-switch

This commit is contained in:
David Woodhouse 2024-10-02 14:37:22 +01:00 committed by GitHub
commit 184713afd0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
37 changed files with 535 additions and 218 deletions

View file

@ -65,7 +65,7 @@ jobs:
pip3 install build pip3 install build
python3 -m build python3 -m build
- name: Publish - name: Publish
uses: pypa/gh-action-pypi-publish@v1.10.1 uses: pypa/gh-action-pypi-publish@v1.10.2
deploy-docker: deploy-docker:
name: Build ESPHome ${{ matrix.platform }} name: Build ESPHome ${{ matrix.platform }}

View file

@ -86,7 +86,7 @@ esphome/components/cap1188/* @mreditor97
esphome/components/captive_portal/* @OttoWinter esphome/components/captive_portal/* @OttoWinter
esphome/components/ccs811/* @habbie esphome/components/ccs811/* @habbie
esphome/components/cd74hc4067/* @asoehlke esphome/components/cd74hc4067/* @asoehlke
esphome/components/ch422g/* @jesterret esphome/components/ch422g/* @clydebarrow @jesterret
esphome/components/climate/* @esphome/core esphome/components/climate/* @esphome/core
esphome/components/climate_ir/* @glmnet esphome/components/climate_ir/* @glmnet
esphome/components/color_temperature/* @jesserockz esphome/components/color_temperature/* @jesserockz
@ -152,6 +152,7 @@ esphome/components/ft63x6/* @gpambrozio
esphome/components/gcja5/* @gcormier esphome/components/gcja5/* @gcormier
esphome/components/gdk101/* @Szewcson esphome/components/gdk101/* @Szewcson
esphome/components/globals/* @esphome/core esphome/components/globals/* @esphome/core
esphome/components/gp2y1010au0f/* @zry98
esphome/components/gp8403/* @jesserockz esphome/components/gp8403/* @jesserockz
esphome/components/gpio/* @esphome/core esphome/components/gpio/* @esphome/core
esphome/components/gpio/one_wire/* @ssieb esphome/components/gpio/one_wire/* @ssieb

View file

@ -7,3 +7,5 @@
For issues, please go to [the issue tracker](https://github.com/esphome/issues/issues). For issues, please go to [the issue tracker](https://github.com/esphome/issues/issues).
For feature requests, please see [feature requests](https://github.com/esphome/feature-requests/issues). For feature requests, please see [feature requests](https://github.com/esphome/feature-requests/issues).
[![ESPHome - A project from the Open Home Foundation](https://www.openhomefoundation.org/badges/esphome.png)](https://www.openhomefoundation.org/)

View file

@ -1,18 +1,20 @@
from esphome import pins from esphome import pins
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import i2c from esphome.components import i2c
from esphome.components.i2c import I2CBus
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_I2C_ID,
CONF_ID, CONF_ID,
CONF_INPUT, CONF_INPUT,
CONF_INVERTED, CONF_INVERTED,
CONF_MODE, CONF_MODE,
CONF_NUMBER, CONF_NUMBER,
CONF_OPEN_DRAIN,
CONF_OUTPUT, CONF_OUTPUT,
CONF_RESTORE_VALUE,
) )
CODEOWNERS = ["@jesterret"] CODEOWNERS = ["@jesterret", "@clydebarrow"]
DEPENDENCIES = ["i2c"] DEPENDENCIES = ["i2c"]
MULTI_CONF = True MULTI_CONF = True
ch422g_ns = cg.esphome_ns.namespace("ch422g") ch422g_ns = cg.esphome_ns.namespace("ch422g")
@ -23,29 +25,36 @@ CH422GGPIOPin = ch422g_ns.class_(
) )
CONF_CH422G = "ch422g" CONF_CH422G = "ch422g"
CONFIG_SCHEMA = (
cv.Schema( # Note that no address is configurable - each register in the CH422G has a dedicated i2c address
CONFIG_SCHEMA = cv.Schema(
{ {
cv.Required(CONF_ID): cv.declare_id(CH422GComponent), cv.GenerateID(CONF_ID): cv.declare_id(CH422GComponent),
cv.Optional(CONF_RESTORE_VALUE, default=False): cv.boolean, cv.GenerateID(CONF_I2C_ID): cv.use_id(I2CBus),
} }
) ).extend(cv.COMPONENT_SCHEMA)
.extend(cv.COMPONENT_SCHEMA)
.extend(i2c.i2c_device_schema(0x24))
)
async def to_code(config): async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])
cg.add(var.set_restore_value(config[CONF_RESTORE_VALUE]))
await cg.register_component(var, config) await cg.register_component(var, config)
await i2c.register_i2c_device(var, config) # Can't use register_i2c_device because there is no CONF_ADDRESS
parent = await cg.get_variable(config[CONF_I2C_ID])
cg.add(var.set_i2c_bus(parent))
# This is used as a final validation step so that modes have been fully transformed.
def pin_mode_check(pin_config, _):
if pin_config[CONF_MODE][CONF_INPUT] and pin_config[CONF_NUMBER] >= 8:
raise cv.Invalid("CH422G only supports input on pins 0-7")
if pin_config[CONF_MODE][CONF_OPEN_DRAIN] and pin_config[CONF_NUMBER] < 8:
raise cv.Invalid("CH422G only supports open drain output on pins 8-11")
CH422G_PIN_SCHEMA = pins.gpio_base_schema( CH422G_PIN_SCHEMA = pins.gpio_base_schema(
CH422GGPIOPin, CH422GGPIOPin,
cv.int_range(min=0, max=7), cv.int_range(min=0, max=11),
modes=[CONF_INPUT, CONF_OUTPUT], modes=[CONF_INPUT, CONF_OUTPUT, CONF_OPEN_DRAIN],
).extend( ).extend(
{ {
cv.Required(CONF_CH422G): cv.use_id(CH422GComponent), cv.Required(CONF_CH422G): cv.use_id(CH422GComponent),
@ -53,7 +62,7 @@ CH422G_PIN_SCHEMA = pins.gpio_base_schema(
) )
@pins.PIN_SCHEMA_REGISTRY.register(CONF_CH422G, CH422G_PIN_SCHEMA) @pins.PIN_SCHEMA_REGISTRY.register(CONF_CH422G, CH422G_PIN_SCHEMA, pin_mode_check)
async def ch422g_pin_to_code(config): async def ch422g_pin_to_code(config):
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])
parent = await cg.get_variable(config[CONF_CH422G]) parent = await cg.get_variable(config[CONF_CH422G])

View file

@ -4,33 +4,33 @@
namespace esphome { namespace esphome {
namespace ch422g { namespace ch422g {
const uint8_t CH422G_REG_IN = 0x26; static const uint8_t CH422G_REG_MODE = 0x24;
const uint8_t CH422G_REG_OUT = 0x38; static const uint8_t CH422G_MODE_OUTPUT = 0x01; // enables output mode on 0-7
const uint8_t OUT_REG_DEFAULT_VAL = 0xdf; static const uint8_t CH422G_MODE_OPEN_DRAIN = 0x04; // enables open drain mode on 8-11
static const uint8_t CH422G_REG_IN = 0x26; // read reg for input bits
static const uint8_t CH422G_REG_OUT = 0x38; // write reg for output bits 0-7
static const uint8_t CH422G_REG_OUT_UPPER = 0x23; // write reg for output bits 8-11
static const char *const TAG = "ch422g"; static const char *const TAG = "ch422g";
void CH422GComponent::setup() { void CH422GComponent::setup() {
ESP_LOGCONFIG(TAG, "Setting up CH422G..."); ESP_LOGCONFIG(TAG, "Setting up CH422G...");
// Test to see if device exists // set outputs before mode
if (!this->read_inputs_()) { this->write_outputs_();
// Set mode and check for errors
if (!this->set_mode_(this->mode_value_) || !this->read_inputs_()) {
ESP_LOGE(TAG, "CH422G not detected at 0x%02X", this->address_); ESP_LOGE(TAG, "CH422G not detected at 0x%02X", this->address_);
this->mark_failed(); this->mark_failed();
return; return;
} }
// restore defaults over whatever got saved on last boot ESP_LOGCONFIG(TAG, "Initialization complete. Warning: %d, Error: %d", this->status_has_warning(),
if (!this->restore_value_) {
this->write_output_(OUT_REG_DEFAULT_VAL);
}
ESP_LOGD(TAG, "Initialization complete. Warning: %d, Error: %d", this->status_has_warning(),
this->status_has_error()); this->status_has_error());
} }
void CH422GComponent::loop() { void CH422GComponent::loop() {
// Clear all the previously read flags. // Clear all the previously read flags.
this->pin_read_cache_ = 0x00; this->pin_read_flags_ = 0x00;
} }
void CH422GComponent::dump_config() { void CH422GComponent::dump_config() {
@ -41,82 +41,99 @@ void CH422GComponent::dump_config() {
} }
} }
// ch422g doesn't have any flag support (needs docs?) void CH422GComponent::pin_mode(uint8_t pin, gpio::Flags flags) {
void CH422GComponent::pin_mode(uint8_t pin, gpio::Flags flags) {} if (pin < 8) {
if (flags & gpio::FLAG_OUTPUT) {
this->mode_value_ |= CH422G_MODE_OUTPUT;
}
} else {
if (flags & gpio::FLAG_OPEN_DRAIN) {
this->mode_value_ |= CH422G_MODE_OPEN_DRAIN;
}
}
}
bool CH422GComponent::digital_read(uint8_t pin) { bool CH422GComponent::digital_read(uint8_t pin) {
if (this->pin_read_cache_ == 0 || this->pin_read_cache_ & (1 << pin)) { if (this->pin_read_flags_ == 0 || this->pin_read_flags_ & (1 << pin)) {
// Read values on first access or in case it's being read again in the same loop // Read values on first access or in case it's being read again in the same loop
this->read_inputs_(); this->read_inputs_();
} }
this->pin_read_cache_ |= (1 << pin); this->pin_read_flags_ |= (1 << pin);
return this->state_mask_ & (1 << pin); return (this->input_bits_ & (1 << pin)) != 0;
} }
void CH422GComponent::digital_write(uint8_t pin, bool value) { void CH422GComponent::digital_write(uint8_t pin, bool value) {
if (value) { if (value) {
this->write_output_(this->state_mask_ | (1 << pin)); this->output_bits_ |= (1 << pin);
} else { } else {
this->write_output_(this->state_mask_ & ~(1 << pin)); this->output_bits_ &= ~(1 << pin);
} }
this->write_outputs_();
} }
bool CH422GComponent::read_inputs_() { bool CH422GComponent::read_inputs_() {
if (this->is_failed()) { if (this->is_failed()) {
return false; return false;
} }
uint8_t result;
uint8_t temp = 0; // reading inputs requires the chip to be in input mode, possibly temporarily.
if ((this->last_error_ = this->read(&temp, 1)) != esphome::i2c::ERROR_OK) { if (this->mode_value_ & CH422G_MODE_OUTPUT) {
this->status_set_warning(str_sprintf("read_inputs_(): I2C I/O error: %d", (int) this->last_error_).c_str()); this->set_mode_(this->mode_value_ & ~CH422G_MODE_OUTPUT);
return false; result = this->read_reg_(CH422G_REG_IN);
this->set_mode_(this->mode_value_);
} else {
result = this->read_reg_(CH422G_REG_IN);
} }
this->input_bits_ = result;
uint8_t output = 0;
if ((this->last_error_ = this->bus_->read(CH422G_REG_IN, &output, 1)) != esphome::i2c::ERROR_OK) {
this->status_set_warning(str_sprintf("read_inputs_(): I2C I/O error: %d", (int) this->last_error_).c_str());
return false;
}
this->state_mask_ = output;
this->status_clear_warning(); this->status_clear_warning();
return true; return true;
} }
bool CH422GComponent::write_output_(uint8_t value) { // Write a register. Can't use the standard write_byte() method because there is no single pre-configured i2c address.
const uint8_t temp = 1; bool CH422GComponent::write_reg_(uint8_t reg, uint8_t value) {
if ((this->last_error_ = this->write(&temp, 1, false)) != esphome::i2c::ERROR_OK) { auto err = this->bus_->write(reg, &value, 1);
this->status_set_warning(str_sprintf("write_output_(): I2C I/O error: %d", (int) this->last_error_).c_str()); if (err != i2c::ERROR_OK) {
this->status_set_warning(str_sprintf("write failed for register 0x%X, error %d", reg, err).c_str());
return false; return false;
} }
uint8_t write_mask = value;
if ((this->last_error_ = this->bus_->write(CH422G_REG_OUT, &write_mask, 1)) != esphome::i2c::ERROR_OK) {
this->status_set_warning(
str_sprintf("write_output_(): I2C I/O error: %d for write_mask: %d", (int) this->last_error_, (int) write_mask)
.c_str());
return false;
}
this->state_mask_ = value;
this->status_clear_warning(); this->status_clear_warning();
return true; return true;
} }
uint8_t CH422GComponent::read_reg_(uint8_t reg) {
uint8_t value;
auto err = this->bus_->read(reg, &value, 1);
if (err != i2c::ERROR_OK) {
this->status_set_warning(str_sprintf("read failed for register 0x%X, error %d", reg, err).c_str());
return 0;
}
this->status_clear_warning();
return value;
}
bool CH422GComponent::set_mode_(uint8_t mode) { return this->write_reg_(CH422G_REG_MODE, mode); }
bool CH422GComponent::write_outputs_() {
return this->write_reg_(CH422G_REG_OUT, static_cast<uint8_t>(this->output_bits_)) &&
this->write_reg_(CH422G_REG_OUT_UPPER, static_cast<uint8_t>(this->output_bits_ >> 8));
}
float CH422GComponent::get_setup_priority() const { return setup_priority::IO; } float CH422GComponent::get_setup_priority() const { return setup_priority::IO; }
// Run our loop() method very early in the loop, so that we cache read values // Run our loop() method very early in the loop, so that we cache read values
// before other components call our digital_read() method. // before other components call our digital_read() method.
float CH422GComponent::get_loop_priority() const { return 9.0f; } // Just after WIFI float CH422GComponent::get_loop_priority() const { return 9.0f; } // Just after WIFI
void CH422GGPIOPin::setup() { pin_mode(flags_); }
void CH422GGPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); } void CH422GGPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); }
bool CH422GGPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; } bool CH422GGPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) ^ this->inverted_; }
void CH422GGPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } void CH422GGPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value ^ this->inverted_); }
std::string CH422GGPIOPin::dump_summary() const { return str_sprintf("EXIO%u via CH422G", pin_); } std::string CH422GGPIOPin::dump_summary() const { return str_sprintf("EXIO%u via CH422G", pin_); }
void CH422GGPIOPin::set_flags(gpio::Flags flags) {
flags_ = flags;
this->parent_->pin_mode(this->pin_, flags);
}
} // namespace ch422g } // namespace ch422g
} // namespace esphome } // namespace esphome

View file

@ -23,32 +23,30 @@ class CH422GComponent : public Component, public i2c::I2CDevice {
void pin_mode(uint8_t pin, gpio::Flags flags); void pin_mode(uint8_t pin, gpio::Flags flags);
float get_setup_priority() const override; float get_setup_priority() const override;
float get_loop_priority() const override; float get_loop_priority() const override;
void dump_config() override; void dump_config() override;
void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; }
protected: protected:
bool write_reg_(uint8_t reg, uint8_t value);
uint8_t read_reg_(uint8_t reg);
bool set_mode_(uint8_t mode);
bool read_inputs_(); bool read_inputs_();
bool write_outputs_();
bool write_output_(uint8_t value);
/// The mask to write as output state - 1 means HIGH, 0 means LOW /// The mask to write as output state - 1 means HIGH, 0 means LOW
uint8_t state_mask_{0x00}; uint16_t output_bits_{0x00};
/// Flags to check if read previously during this loop /// Flags to check if read previously during this loop
uint8_t pin_read_cache_ = {0x00}; uint8_t pin_read_flags_ = {0x00};
/// Storage for last I2C error seen /// Copy of last read values
esphome::i2c::ErrorCode last_error_; uint8_t input_bits_ = {0x00};
/// Whether we want to override stored values on expander /// Copy of the mode value
bool restore_value_{false}; uint8_t mode_value_{};
}; };
/// Helper class to expose a CH422G pin as an internal input GPIO pin. /// Helper class to expose a CH422G pin as a GPIO pin.
class CH422GGPIOPin : public GPIOPin { class CH422GGPIOPin : public GPIOPin {
public: public:
void setup() override; void setup() override{};
void pin_mode(gpio::Flags flags) override; void pin_mode(gpio::Flags flags) override;
bool digital_read() override; bool digital_read() override;
void digital_write(bool value) override; void digital_write(bool value) override;
@ -57,13 +55,13 @@ class CH422GGPIOPin : public GPIOPin {
void set_parent(CH422GComponent *parent) { parent_ = parent; } void set_parent(CH422GComponent *parent) { parent_ = parent; }
void set_pin(uint8_t pin) { pin_ = pin; } void set_pin(uint8_t pin) { pin_ = pin; }
void set_inverted(bool inverted) { inverted_ = inverted; } void set_inverted(bool inverted) { inverted_ = inverted; }
void set_flags(gpio::Flags flags) { flags_ = flags; } void set_flags(gpio::Flags flags);
protected: protected:
CH422GComponent *parent_; CH422GComponent *parent_{};
uint8_t pin_; uint8_t pin_{};
bool inverted_; bool inverted_{};
gpio::Flags flags_; gpio::Flags flags_{};
}; };
} // namespace ch422g } // namespace ch422g

View file

@ -26,7 +26,6 @@ from esphome.cpp_generator import MockObjClass
from esphome.cpp_helpers import setup_entity from esphome.cpp_helpers import setup_entity
CODEOWNERS = ["@rfdarter", "@jesserockz"] CODEOWNERS = ["@rfdarter", "@jesserockz"]
DEPENDENCIES = ["time"]
IS_PLATFORM_COMPONENT = True IS_PLATFORM_COMPONENT = True
@ -62,20 +61,28 @@ DATETIME_MODES = [
] ]
_DATETIME_SCHEMA = ( def _validate_time_present(config):
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA) config = config.copy()
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA) if CONF_ON_TIME in config and CONF_TIME_ID not in config:
.extend( time_id = cv.use_id(time.RealTimeClock)(None)
config[CONF_TIME_ID] = time_id
return config
_DATETIME_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(
web_server.WEBSERVER_SORTING_SCHEMA,
cv.MQTT_COMMAND_COMPONENT_SCHEMA,
cv.Schema(
{ {
cv.Optional(CONF_ON_VALUE): automation.validate_automation( cv.Optional(CONF_ON_VALUE): automation.validate_automation(
{ {
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DateTimeStateTrigger), cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DateTimeStateTrigger),
} }
), ),
cv.GenerateID(CONF_TIME_ID): cv.use_id(time.RealTimeClock), cv.Optional(CONF_TIME_ID): cv.use_id(time.RealTimeClock),
} }
) ),
) ).add_extra(_validate_time_present)
def date_schema(class_: MockObjClass) -> cv.Schema: def date_schema(class_: MockObjClass) -> cv.Schema:
@ -138,6 +145,7 @@ async def setup_datetime_core_(var, config):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [(cg.ESPTime, "x")], conf) await automation.build_automation(trigger, [(cg.ESPTime, "x")], conf)
if CONF_TIME_ID in config:
rtc = await cg.get_variable(config[CONF_TIME_ID]) rtc = await cg.get_variable(config[CONF_TIME_ID])
cg.add(var.set_rtc(rtc)) cg.add(var.set_rtc(rtc))

View file

@ -4,8 +4,9 @@
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/entity_base.h" #include "esphome/core/entity_base.h"
#include "esphome/core/time.h" #include "esphome/core/time.h"
#ifdef USE_TIME
#include "esphome/components/time/real_time_clock.h" #include "esphome/components/time/real_time_clock.h"
#endif
namespace esphome { namespace esphome {
namespace datetime { namespace datetime {
@ -19,23 +20,29 @@ class DateTimeBase : public EntityBase {
void add_on_state_callback(std::function<void()> &&callback) { this->state_callback_.add(std::move(callback)); } void add_on_state_callback(std::function<void()> &&callback) { this->state_callback_.add(std::move(callback)); }
#ifdef USE_TIME
void set_rtc(time::RealTimeClock *rtc) { this->rtc_ = rtc; } void set_rtc(time::RealTimeClock *rtc) { this->rtc_ = rtc; }
time::RealTimeClock *get_rtc() const { return this->rtc_; } time::RealTimeClock *get_rtc() const { return this->rtc_; }
#endif
protected: protected:
CallbackManager<void()> state_callback_; CallbackManager<void()> state_callback_;
#ifdef USE_TIME
time::RealTimeClock *rtc_; time::RealTimeClock *rtc_;
#endif
bool has_state_{false}; bool has_state_{false};
}; };
#ifdef USE_TIME
class DateTimeStateTrigger : public Trigger<ESPTime> { class DateTimeStateTrigger : public Trigger<ESPTime> {
public: public:
explicit DateTimeStateTrigger(DateTimeBase *parent) { explicit DateTimeStateTrigger(DateTimeBase *parent) {
parent->add_on_state_callback([this, parent]() { this->trigger(parent->state_as_esptime()); }); parent->add_on_state_callback([this, parent]() { this->trigger(parent->state_as_esptime()); });
} }
}; };
#endif
} // namespace datetime } // namespace datetime
} // namespace esphome } // namespace esphome

View file

@ -192,6 +192,7 @@ void DateTimeEntityRestoreState::apply(DateTimeEntity *time) {
time->publish_state(); time->publish_state();
} }
#ifdef USE_TIME
static const int MAX_TIMESTAMP_DRIFT = 900; // how far can the clock drift before we consider static const int MAX_TIMESTAMP_DRIFT = 900; // how far can the clock drift before we consider
// there has been a drastic time synchronization // there has been a drastic time synchronization
@ -245,6 +246,7 @@ bool OnDateTimeTrigger::matches_(const ESPTime &time) const {
time.day_of_month == this->parent_->day && time.hour == this->parent_->hour && time.day_of_month == this->parent_->day && time.hour == this->parent_->hour &&
time.minute == this->parent_->minute && time.second == this->parent_->second; time.minute == this->parent_->minute && time.second == this->parent_->second;
} }
#endif
} // namespace datetime } // namespace datetime
} // namespace esphome } // namespace esphome

View file

@ -134,6 +134,7 @@ template<typename... Ts> class DateTimeSetAction : public Action<Ts...>, public
} }
}; };
#ifdef USE_TIME
class OnDateTimeTrigger : public Trigger<>, public Component, public Parented<DateTimeEntity> { class OnDateTimeTrigger : public Trigger<>, public Component, public Parented<DateTimeEntity> {
public: public:
void loop() override; void loop() override;
@ -143,6 +144,7 @@ class OnDateTimeTrigger : public Trigger<>, public Component, public Parented<Da
optional<ESPTime> last_check_; optional<ESPTime> last_check_;
}; };
#endif
} // namespace datetime } // namespace datetime
} // namespace esphome } // namespace esphome

View file

@ -94,6 +94,7 @@ void TimeEntityRestoreState::apply(TimeEntity *time) {
time->publish_state(); time->publish_state();
} }
#ifdef USE_TIME
static const int MAX_TIMESTAMP_DRIFT = 900; // how far can the clock drift before we consider static const int MAX_TIMESTAMP_DRIFT = 900; // how far can the clock drift before we consider
// there has been a drastic time synchronization // there has been a drastic time synchronization
@ -145,6 +146,7 @@ bool OnTimeTrigger::matches_(const ESPTime &time) const {
return time.is_valid() && time.hour == this->parent_->hour && time.minute == this->parent_->minute && return time.is_valid() && time.hour == this->parent_->hour && time.minute == this->parent_->minute &&
time.second == this->parent_->second; time.second == this->parent_->second;
} }
#endif
} // namespace datetime } // namespace datetime
} // namespace esphome } // namespace esphome

View file

@ -113,6 +113,7 @@ template<typename... Ts> class TimeSetAction : public Action<Ts...>, public Pare
} }
}; };
#ifdef USE_TIME
class OnTimeTrigger : public Trigger<>, public Component, public Parented<TimeEntity> { class OnTimeTrigger : public Trigger<>, public Component, public Parented<TimeEntity> {
public: public:
void loop() override; void loop() override;
@ -122,6 +123,7 @@ class OnTimeTrigger : public Trigger<>, public Component, public Parented<TimeEn
optional<ESPTime> last_check_; optional<ESPTime> last_check_;
}; };
#endif
} // namespace datetime } // namespace datetime
} // namespace esphome } // namespace esphome

View file

@ -13,6 +13,7 @@ from esphome.const import (
CONF_COMPONENTS, CONF_COMPONENTS,
CONF_ESPHOME, CONF_ESPHOME,
CONF_FRAMEWORK, CONF_FRAMEWORK,
CONF_IGNORE_EFUSE_CUSTOM_MAC,
CONF_IGNORE_EFUSE_MAC_CRC, CONF_IGNORE_EFUSE_MAC_CRC,
CONF_NAME, CONF_NAME,
CONF_PATH, CONF_PATH,
@ -401,6 +402,9 @@ ESP_IDF_FRAMEWORK_SCHEMA = cv.All(
}, },
cv.Optional(CONF_ADVANCED, default={}): cv.Schema( cv.Optional(CONF_ADVANCED, default={}): cv.Schema(
{ {
cv.Optional(
CONF_IGNORE_EFUSE_CUSTOM_MAC, default=False
): cv.boolean,
cv.Optional(CONF_IGNORE_EFUSE_MAC_CRC, default=False): cv.boolean, cv.Optional(CONF_IGNORE_EFUSE_MAC_CRC, default=False): cv.boolean,
} }
), ),
@ -526,6 +530,8 @@ async def to_code(config):
for name, value in conf[CONF_SDKCONFIG_OPTIONS].items(): for name, value in conf[CONF_SDKCONFIG_OPTIONS].items():
add_idf_sdkconfig_option(name, RawSdkconfigValue(value)) add_idf_sdkconfig_option(name, RawSdkconfigValue(value))
if conf[CONF_ADVANCED][CONF_IGNORE_EFUSE_CUSTOM_MAC]:
cg.add_define("USE_ESP32_IGNORE_EFUSE_CUSTOM_MAC")
if conf[CONF_ADVANCED][CONF_IGNORE_EFUSE_MAC_CRC]: if conf[CONF_ADVANCED][CONF_IGNORE_EFUSE_MAC_CRC]:
cg.add_define("USE_ESP32_IGNORE_EFUSE_MAC_CRC") cg.add_define("USE_ESP32_IGNORE_EFUSE_MAC_CRC")
if (framework_ver.major, framework_ver.minor) >= (4, 4): if (framework_ver.major, framework_ver.minor) >= (4, 4):

View file

@ -0,0 +1,67 @@
#include "gp2y1010au0f.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#include <cinttypes>
namespace esphome {
namespace gp2y1010au0f {
static const char *const TAG = "gp2y1010au0f";
static const float MIN_VOLTAGE = 0.0f;
static const float MAX_VOLTAGE = 4.0f;
void GP2Y1010AU0FSensor::dump_config() {
LOG_SENSOR("", "Sharp GP2Y1010AU0F PM2.5 Sensor", this);
ESP_LOGCONFIG(TAG, " Sampling duration: %" PRId32 " ms", this->sample_duration_);
ESP_LOGCONFIG(TAG, " ADC voltage multiplier: %.3f", this->voltage_multiplier_);
LOG_UPDATE_INTERVAL(this);
}
void GP2Y1010AU0FSensor::update() {
is_sampling_ = true;
this->set_timeout("read", this->sample_duration_, [this]() {
this->is_sampling_ = false;
if (this->num_samples_ == 0)
return;
float mean = this->sample_sum_ / float(this->num_samples_);
ESP_LOGD(TAG, "ADC read voltage: %.3f V (mean from %" PRId32 " samples)", mean, this->num_samples_);
// PM2.5 calculation
// ref: https://www.howmuchsnow.com/arduino/airquality/
int16_t pm_2_5_value = 170 * mean;
this->publish_state(pm_2_5_value);
});
// reset readings
this->num_samples_ = 0;
this->sample_sum_ = 0.0f;
}
void GP2Y1010AU0FSensor::loop() {
if (!this->is_sampling_)
return;
// enable the internal IR LED
this->led_output_->turn_on();
// wait for the sensor to stabilize
delayMicroseconds(this->sample_wait_before_);
// perform a single sample
float read_voltage = this->source_->sample();
// disable the internal IR LED
this->led_output_->turn_off();
if (std::isnan(read_voltage))
return;
read_voltage = read_voltage * this->voltage_multiplier_ - this->voltage_offset_;
if (read_voltage < MIN_VOLTAGE || read_voltage > MAX_VOLTAGE)
return;
this->num_samples_++;
this->sample_sum_ += read_voltage;
}
} // namespace gp2y1010au0f
} // namespace esphome

View file

@ -0,0 +1,52 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/voltage_sampler/voltage_sampler.h"
#include "esphome/components/output/binary_output.h"
namespace esphome {
namespace gp2y1010au0f {
class GP2Y1010AU0FSensor : public sensor::Sensor, public PollingComponent {
public:
void update() override;
void loop() override;
void dump_config() override;
float get_setup_priority() const override {
// after the base sensor has been initialized
return setup_priority::DATA - 1.0f;
}
void set_adc_source(voltage_sampler::VoltageSampler *source) { source_ = source; }
void set_voltage_refs(float offset, float multiplier) {
this->voltage_offset_ = offset;
this->voltage_multiplier_ = multiplier;
}
void set_led_output(output::BinaryOutput *output) { led_output_ = output; }
protected:
// duration in ms of the sampling phase
uint32_t sample_duration_ = 100;
// duration in us of the wait before sampling
// ref: https://global.sharp/products/device/lineup/data/pdf/datasheet/gp2y1010au_appl_e.pdf
uint32_t sample_wait_before_ = 280;
// duration in us of the wait after sampling
// it seems no need to delay on purpose since one ADC sampling takes longer than that (300-400 us on ESP8266)
// uint32_t sample_wait_after_ = 40;
// the sampling source to read voltage from
voltage_sampler::VoltageSampler *source_;
// ADC voltage reading offset
float voltage_offset_ = 0.0f;
// ADC voltage reading multiplier
float voltage_multiplier_ = 1.0f;
// the binary output to control the sampling LED
output::BinaryOutput *led_output_;
float sample_sum_ = 0.0f;
uint32_t num_samples_ = 0;
bool is_sampling_ = false;
};
} // namespace gp2y1010au0f
} // namespace esphome

View file

@ -0,0 +1,61 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, voltage_sampler, output
from esphome.const import (
CONF_SENSOR,
CONF_OUTPUT,
DEVICE_CLASS_PM25,
STATE_CLASS_MEASUREMENT,
UNIT_MICROGRAMS_PER_CUBIC_METER,
ICON_CHEMICAL_WEAPON,
)
DEPENDENCIES = ["output"]
AUTO_LOAD = ["voltage_sampler"]
CODEOWNERS = ["@zry98"]
CONF_ADC_VOLTAGE_OFFSET = "adc_voltage_offset"
CONF_ADC_VOLTAGE_MULTIPLIER = "adc_voltage_multiplier"
gp2y1010au0f_ns = cg.esphome_ns.namespace("gp2y1010au0f")
GP2Y1010AU0FSensor = gp2y1010au0f_ns.class_(
"GP2Y1010AU0FSensor", sensor.Sensor, cg.PollingComponent
)
CONFIG_SCHEMA = (
sensor.sensor_schema(
GP2Y1010AU0FSensor,
unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER,
accuracy_decimals=0,
device_class=DEVICE_CLASS_PM25,
state_class=STATE_CLASS_MEASUREMENT,
icon=ICON_CHEMICAL_WEAPON,
)
.extend(
{
cv.Required(CONF_SENSOR): cv.use_id(voltage_sampler.VoltageSampler),
cv.Optional(CONF_ADC_VOLTAGE_OFFSET, default=0.0): cv.float_,
cv.Optional(CONF_ADC_VOLTAGE_MULTIPLIER, default=1.0): cv.float_,
cv.Required(CONF_OUTPUT): cv.use_id(output.BinaryOutput),
}
)
.extend(cv.polling_component_schema("60s"))
)
async def to_code(config):
var = await sensor.new_sensor(config)
await cg.register_component(var, config)
# the ADC sensor to read voltage from
adc_sensor = await cg.get_variable(config[CONF_SENSOR])
cg.add(var.set_adc_source(adc_sensor))
cg.add(
var.set_voltage_refs(
config[CONF_ADC_VOLTAGE_OFFSET], config[CONF_ADC_VOLTAGE_MULTIPLIER]
)
)
# the binary output to control the module's internal IR LED
led_output = await cg.get_variable(config[CONF_OUTPUT])
cg.add(var.set_led_output(led_output))

View file

@ -1,10 +1,14 @@
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import i2c, sensor
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.components import sensor, i2c
from esphome.const import ( from esphome.const import (
CONF_AMMONIA,
CONF_CARBON_MONOXIDE,
CONF_ETHANOL,
CONF_HYDROGEN,
CONF_ID, CONF_ID,
CONF_METHANE,
CONF_NITROGEN_DIOXIDE,
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
UNIT_PARTS_PER_MILLION, UNIT_PARTS_PER_MILLION,
) )
@ -12,13 +16,6 @@ from esphome.const import (
CODEOWNERS = ["@jesserockz"] CODEOWNERS = ["@jesserockz"]
DEPENDENCIES = ["i2c"] DEPENDENCIES = ["i2c"]
CONF_CARBON_MONOXIDE = "carbon_monoxide"
CONF_NITROGEN_DIOXIDE = "nitrogen_dioxide"
CONF_METHANE = "methane"
CONF_ETHANOL = "ethanol"
CONF_HYDROGEN = "hydrogen"
CONF_AMMONIA = "ammonia"
mics_4514_ns = cg.esphome_ns.namespace("mics_4514") mics_4514_ns = cg.esphome_ns.namespace("mics_4514")
MICS4514Component = mics_4514_ns.class_( MICS4514Component = mics_4514_ns.class_(
@ -31,6 +28,7 @@ SENSORS = [
CONF_ETHANOL, CONF_ETHANOL,
CONF_HYDROGEN, CONF_HYDROGEN,
CONF_AMMONIA, CONF_AMMONIA,
CONF_NITROGEN_DIOXIDE,
] ]
common_sensor_schema = sensor.sensor_schema( common_sensor_schema = sensor.sensor_schema(
@ -40,16 +38,7 @@ common_sensor_schema = sensor.sensor_schema(
) )
CONFIG_SCHEMA = ( CONFIG_SCHEMA = (
cv.Schema( cv.Schema({cv.GenerateID(): cv.declare_id(MICS4514Component)})
{
cv.GenerateID(): cv.declare_id(MICS4514Component),
cv.Optional(CONF_NITROGEN_DIOXIDE): sensor.sensor_schema(
unit_of_measurement=UNIT_PARTS_PER_MILLION,
state_class=STATE_CLASS_MEASUREMENT,
accuracy_decimals=2,
),
}
)
.extend({cv.Optional(sensor_type): common_sensor_schema for sensor_type in SENSORS}) .extend({cv.Optional(sensor_type): common_sensor_schema for sensor_type in SENSORS})
.extend(i2c.i2c_device_schema(0x75)) .extend(i2c.i2c_device_schema(0x75))
.extend(cv.polling_component_schema("60s")) .extend(cv.polling_component_schema("60s"))
@ -62,10 +51,6 @@ async def to_code(config):
await i2c.register_i2c_device(var, config) await i2c.register_i2c_device(var, config)
for sensor_type in SENSORS: for sensor_type in SENSORS:
if sensor_type in config: if sensor_config := config.get(sensor_type):
sens = await sensor.new_sensor(config[sensor_type]) sens = await sensor.new_sensor(sensor_config)
cg.add(getattr(var, f"set_{sensor_type}_sensor")(sens)) cg.add(getattr(var, f"set_{sensor_type}_sensor")(sens))
if CONF_NITROGEN_DIOXIDE in config:
sens = await sensor.new_sensor(config[CONF_NITROGEN_DIOXIDE])
cg.add(var.set_nitrogen_dioxide_sensor(sens))

View file

@ -11,6 +11,7 @@ from esphome.const import (
CONF_BIRTH_MESSAGE, CONF_BIRTH_MESSAGE,
CONF_BROKER, CONF_BROKER,
CONF_CERTIFICATE_AUTHORITY, CONF_CERTIFICATE_AUTHORITY,
CONF_CLEAN_SESSION,
CONF_CLIENT_CERTIFICATE, CONF_CLIENT_CERTIFICATE,
CONF_CLIENT_CERTIFICATE_KEY, CONF_CLIENT_CERTIFICATE_KEY,
CONF_CLIENT_ID, CONF_CLIENT_ID,
@ -209,6 +210,7 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_PORT, default=1883): cv.port, cv.Optional(CONF_PORT, default=1883): cv.port,
cv.Optional(CONF_USERNAME, default=""): cv.string, cv.Optional(CONF_USERNAME, default=""): cv.string,
cv.Optional(CONF_PASSWORD, default=""): cv.string, cv.Optional(CONF_PASSWORD, default=""): cv.string,
cv.Optional(CONF_CLEAN_SESSION, default=False): cv.boolean,
cv.Optional(CONF_CLIENT_ID): cv.string, cv.Optional(CONF_CLIENT_ID): cv.string,
cv.SplitDefault(CONF_IDF_SEND_ASYNC, esp32_idf=False): cv.All( cv.SplitDefault(CONF_IDF_SEND_ASYNC, esp32_idf=False): cv.All(
cv.boolean, cv.only_with_esp_idf cv.boolean, cv.only_with_esp_idf
@ -325,6 +327,7 @@ async def to_code(config):
cg.add(var.set_broker_port(config[CONF_PORT])) cg.add(var.set_broker_port(config[CONF_PORT]))
cg.add(var.set_username(config[CONF_USERNAME])) cg.add(var.set_username(config[CONF_USERNAME]))
cg.add(var.set_password(config[CONF_PASSWORD])) cg.add(var.set_password(config[CONF_PASSWORD]))
cg.add(var.set_clean_session(config[CONF_CLEAN_SESSION]))
if CONF_CLIENT_ID in config: if CONF_CLIENT_ID in config:
cg.add(var.set_client_id(config[CONF_CLIENT_ID])) cg.add(var.set_client_id(config[CONF_CLIENT_ID]))

View file

@ -147,6 +147,7 @@ void MQTTClientComponent::dump_config() {
this->ip_.str().c_str()); this->ip_.str().c_str());
ESP_LOGCONFIG(TAG, " Username: " LOG_SECRET("'%s'"), this->credentials_.username.c_str()); ESP_LOGCONFIG(TAG, " Username: " LOG_SECRET("'%s'"), this->credentials_.username.c_str());
ESP_LOGCONFIG(TAG, " Client ID: " LOG_SECRET("'%s'"), this->credentials_.client_id.c_str()); ESP_LOGCONFIG(TAG, " Client ID: " LOG_SECRET("'%s'"), this->credentials_.client_id.c_str());
ESP_LOGCONFIG(TAG, " Clean Session: %s", YESNO(this->credentials_.clean_session));
if (this->is_discovery_ip_enabled()) { if (this->is_discovery_ip_enabled()) {
ESP_LOGCONFIG(TAG, " Discovery IP enabled"); ESP_LOGCONFIG(TAG, " Discovery IP enabled");
} }
@ -246,6 +247,7 @@ void MQTTClientComponent::start_connect_() {
this->mqtt_backend_.disconnect(); this->mqtt_backend_.disconnect();
this->mqtt_backend_.set_client_id(this->credentials_.client_id.c_str()); this->mqtt_backend_.set_client_id(this->credentials_.client_id.c_str());
this->mqtt_backend_.set_clean_session(this->credentials_.clean_session);
const char *username = nullptr; const char *username = nullptr;
if (!this->credentials_.username.empty()) if (!this->credentials_.username.empty())
username = this->credentials_.username.c_str(); username = this->credentials_.username.c_str();

View file

@ -51,6 +51,7 @@ struct MQTTCredentials {
std::string username; std::string username;
std::string password; std::string password;
std::string client_id; ///< The client ID. Will automatically be truncated to 23 characters. std::string client_id; ///< The client ID. Will automatically be truncated to 23 characters.
bool clean_session; ///< Whether the session will be cleaned or remembered between connects.
}; };
/// Simple data struct for Home Assistant component availability. /// Simple data struct for Home Assistant component availability.
@ -254,6 +255,7 @@ class MQTTClientComponent : public Component {
void set_username(const std::string &username) { this->credentials_.username = username; } void set_username(const std::string &username) { this->credentials_.username = username; }
void set_password(const std::string &password) { this->credentials_.password = password; } void set_password(const std::string &password) { this->credentials_.password = password; }
void set_client_id(const std::string &client_id) { this->credentials_.client_id = client_id; } void set_client_id(const std::string &client_id) { this->credentials_.client_id = client_id; }
void set_clean_session(const bool &clean_session) { this->credentials_.clean_session = clean_session; }
void set_on_connect(mqtt_on_connect_callback_t &&callback); void set_on_connect(mqtt_on_connect_callback_t &&callback);
void set_on_disconnect(mqtt_on_disconnect_callback_t &&callback); void set_on_disconnect(mqtt_on_disconnect_callback_t &&callback);

View file

@ -71,6 +71,14 @@ def _format_framework_arduino_version(ver: cv.Version) -> str:
# return f"~1.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" # return f"~1.{ver.major}{ver.minor:02d}{ver.patch:02d}.0"
def _parse_platform_version(value):
value = cv.string(value)
if value.startswith("http"):
return value
return f"https://github.com/maxgerhardt/platform-raspberrypi.git#{value}"
# NOTE: Keep this in mind when updating the recommended version: # NOTE: Keep this in mind when updating the recommended version:
# * The new version needs to be thoroughly validated before changing the # * The new version needs to be thoroughly validated before changing the
# recommended version as otherwise a bunch of devices could be bricked # recommended version as otherwise a bunch of devices could be bricked
@ -82,10 +90,9 @@ def _format_framework_arduino_version(ver: cv.Version) -> str:
# - https://api.registry.platformio.org/v3/packages/earlephilhower/tool/framework-arduinopico # - https://api.registry.platformio.org/v3/packages/earlephilhower/tool/framework-arduinopico
RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(3, 9, 4) RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(3, 9, 4)
# The platformio/raspberrypi version to use for arduino frameworks # The raspberrypi platform version to use for arduino frameworks
# - https://github.com/platformio/platform-raspberrypi/releases # - https://github.com/maxgerhardt/platform-raspberrypi/tags
# - https://api.registry.platformio.org/v3/packages/platformio/platform/raspberrypi RECOMMENDED_ARDUINO_PLATFORM_VERSION = "v1.2.0-gcc12"
ARDUINO_PLATFORM_VERSION = cv.Version(1, 13, 0)
def _arduino_check_versions(value): def _arduino_check_versions(value):
@ -111,7 +118,8 @@ def _arduino_check_versions(value):
value[CONF_SOURCE] = source or _format_framework_arduino_version(version) value[CONF_SOURCE] = source or _format_framework_arduino_version(version)
value[CONF_PLATFORM_VERSION] = value.get( value[CONF_PLATFORM_VERSION] = value.get(
CONF_PLATFORM_VERSION, _parse_platform_version(str(ARDUINO_PLATFORM_VERSION)) CONF_PLATFORM_VERSION,
_parse_platform_version(RECOMMENDED_ARDUINO_PLATFORM_VERSION),
) )
if version != RECOMMENDED_ARDUINO_FRAMEWORK_VERSION: if version != RECOMMENDED_ARDUINO_FRAMEWORK_VERSION:
@ -122,15 +130,6 @@ def _arduino_check_versions(value):
return value return value
def _parse_platform_version(value):
try:
# if platform version is a valid version constraint, prefix the default package
cv.platformio_version_constraint(value)
return f"platformio/raspberrypi@{value}"
except cv.Invalid:
return value
ARDUINO_FRAMEWORK_SCHEMA = cv.All( ARDUINO_FRAMEWORK_SCHEMA = cv.All(
cv.Schema( cv.Schema(
{ {

View file

@ -2,6 +2,7 @@
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/hal.h" #include "esphome/core/hal.h"
#include <algorithm> #include <algorithm>
#include "esphome/core/helpers.h"
namespace esphome { namespace esphome {
namespace tcs34725 { namespace tcs34725 {
@ -14,10 +15,7 @@ static const uint8_t TCS34725_REGISTER_ID = TCS34725_COMMAND_BIT | 0x12;
static const uint8_t TCS34725_REGISTER_ATIME = TCS34725_COMMAND_BIT | 0x01; static const uint8_t TCS34725_REGISTER_ATIME = TCS34725_COMMAND_BIT | 0x01;
static const uint8_t TCS34725_REGISTER_CONTROL = TCS34725_COMMAND_BIT | 0x0F; static const uint8_t TCS34725_REGISTER_CONTROL = TCS34725_COMMAND_BIT | 0x0F;
static const uint8_t TCS34725_REGISTER_ENABLE = TCS34725_COMMAND_BIT | 0x00; static const uint8_t TCS34725_REGISTER_ENABLE = TCS34725_COMMAND_BIT | 0x00;
static const uint8_t TCS34725_REGISTER_CDATAL = TCS34725_COMMAND_BIT | 0x14; static const uint8_t TCS34725_REGISTER_CRGBDATAL = TCS34725_COMMAND_BIT | 0x14;
static const uint8_t TCS34725_REGISTER_RDATAL = TCS34725_COMMAND_BIT | 0x16;
static const uint8_t TCS34725_REGISTER_GDATAL = TCS34725_COMMAND_BIT | 0x18;
static const uint8_t TCS34725_REGISTER_BDATAL = TCS34725_COMMAND_BIT | 0x1A;
void TCS34725Component::setup() { void TCS34725Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up TCS34725..."); ESP_LOGCONFIG(TAG, "Setting up TCS34725...");
@ -75,12 +73,10 @@ float TCS34725Component::get_setup_priority() const { return setup_priority::DAT
* @return Color temperature in degrees Kelvin * @return Color temperature in degrees Kelvin
*/ */
void TCS34725Component::calculate_temperature_and_lux_(uint16_t r, uint16_t g, uint16_t b, uint16_t c) { void TCS34725Component::calculate_temperature_and_lux_(uint16_t r, uint16_t g, uint16_t b, uint16_t c) {
float r2, g2, b2; /* RGB values minus IR component */
float sat; /* Digital saturation level */ float sat; /* Digital saturation level */
float ir; /* Inferred IR content */
this->illuminance_ = 0; // Assign 0 value before calculation this->illuminance_ = NAN;
this->color_temperature_ = 0; this->color_temperature_ = NAN;
const float ga = this->glass_attenuation_; // Glass Attenuation Factor const float ga = this->glass_attenuation_; // Glass Attenuation Factor
static const float DF = 310.f; // Device Factor static const float DF = 310.f; // Device Factor
@ -89,6 +85,9 @@ void TCS34725Component::calculate_temperature_and_lux_(uint16_t r, uint16_t g, u
static const float B_COEF = -0.444f; // static const float B_COEF = -0.444f; //
static const float CT_COEF = 3810.f; // Color Temperature Coefficient static const float CT_COEF = 3810.f; // Color Temperature Coefficient
static const float CT_OFFSET = 1391.f; // Color Temperatuer Offset static const float CT_OFFSET = 1391.f; // Color Temperatuer Offset
static const float MAX_ILLUMINANCE = 100000.0f; // Cap illuminance at 100,000 lux
static const float MAX_COLOR_TEMPERATURE = 15000.0f; // Maximum expected color temperature in Kelvin
static const float MIN_COLOR_TEMPERATURE = 1000.0f; // Maximum reasonable color temperature in Kelvin
if (c == 0) { if (c == 0) {
return; return;
@ -139,69 +138,66 @@ void TCS34725Component::calculate_temperature_and_lux_(uint16_t r, uint16_t g, u
if (c >= sat) { if (c >= sat) {
if (this->integration_time_auto_) { if (this->integration_time_auto_) {
ESP_LOGI(TAG, "Saturation too high, sample discarded, autogain ongoing"); ESP_LOGI(TAG, "Saturation too high, sample discarded, autogain ongoing");
return;
} else { } else {
ESP_LOGW(
TAG,
"Saturation too high, sample with saturation %.1f and clear %d treat values carefully or use grey filter",
sat, c);
}
}
/* AMS RGB sensors have no IR channel, so the IR content must be */
/* calculated indirectly. */
ir = ((r + g + b) > c) ? (r + g + b - c) / 2 : 0;
/* Remove the IR component from the raw RGB values */
r2 = r - ir;
g2 = g - ir;
b2 = b - ir;
// discarding super low values? not recemmonded, and avoided by using auto gain.
if (r2 == 0) {
// legacy code
if (!this->integration_time_auto_) {
ESP_LOGW(TAG, ESP_LOGW(TAG,
"No light detected on red channel, switch to auto gain or adjust timing, values will be unreliable"); "Saturation too high, sample with saturation %.1f and clear %d lux/color temperature cannot reliably "
"calculated, reduce integration/gain or use a grey filter.",
sat, c);
return; return;
} }
} }
// Lux Calculation (DN40 3.2) // Lux Calculation (DN40 3.2)
float g1 = R_COEF * r2 + G_COEF * g2 + B_COEF * b2; float g1 = R_COEF * (float) r + G_COEF * (float) g + B_COEF * (float) b;
float cpl = (this->integration_time_ * this->gain_) / (ga * DF); float cpl = (this->integration_time_ * this->gain_) / (ga * DF);
this->illuminance_ = g1 / cpl;
this->illuminance_ = std::max(g1 / cpl, 0.0f);
if (this->illuminance_ > MAX_ILLUMINANCE) {
ESP_LOGW(TAG, "Calculated illuminance greater than limit (%f), setting to NAN", this->illuminance_);
this->illuminance_ = NAN;
return;
}
if (r == 0) {
ESP_LOGW(TAG, "Red channel is zero, cannot compute color temperature");
return;
}
// Color Temperature Calculation (DN40) // Color Temperature Calculation (DN40)
/* A simple method of measuring color temp is to use the ratio of blue */ /* A simple method of measuring color temp is to use the ratio of blue */
/* to red light, taking IR cancellation into account. */ /* to red light. */
this->color_temperature_ = (CT_COEF * b2) / /** Color temp coefficient. */
r2 + this->color_temperature_ = (CT_COEF * (float) b) / (float) r + CT_OFFSET;
CT_OFFSET; /** Color temp offset. */
// Ensure the color temperature stays within reasonable bounds
if (this->color_temperature_ < MIN_COLOR_TEMPERATURE) {
ESP_LOGW(TAG, "Calculated color temperature value too low (%f), setting to NAN", this->color_temperature_);
this->color_temperature_ = NAN;
} else if (this->color_temperature_ > MAX_COLOR_TEMPERATURE) {
ESP_LOGW(TAG, "Calculated color temperature value too high (%f), setting to NAN", this->color_temperature_);
this->color_temperature_ = NAN;
}
} }
void TCS34725Component::update() { void TCS34725Component::update() {
uint16_t raw_c; uint8_t data[8]; // Buffer to hold the 8 bytes (2 bytes for each of the 4 channels)
uint16_t raw_r;
uint16_t raw_g;
uint16_t raw_b;
if (this->read_data_register_(TCS34725_REGISTER_CDATAL, raw_c) != i2c::ERROR_OK) { // Perform burst
this->status_set_warning(); if (this->read_register(TCS34725_REGISTER_CRGBDATAL, data, 8) != i2c::ERROR_OK) {
return;
}
if (this->read_data_register_(TCS34725_REGISTER_RDATAL, raw_r) != i2c::ERROR_OK) {
this->status_set_warning();
return;
}
if (this->read_data_register_(TCS34725_REGISTER_GDATAL, raw_g) != i2c::ERROR_OK) {
this->status_set_warning();
return;
}
if (this->read_data_register_(TCS34725_REGISTER_BDATAL, raw_b) != i2c::ERROR_OK) {
this->status_set_warning(); this->status_set_warning();
ESP_LOGW(TAG, "Error reading TCS34725 sensor data");
return; return;
} }
// Extract the data
uint16_t raw_c = encode_uint16(data[1], data[0]); // Clear channel
uint16_t raw_r = encode_uint16(data[3], data[2]); // Red channel
uint16_t raw_g = encode_uint16(data[5], data[4]); // Green channel
uint16_t raw_b = encode_uint16(data[7], data[6]); // Blue channel
ESP_LOGV(TAG, "Raw values clear=%d red=%d green=%d blue=%d", raw_c, raw_r, raw_g, raw_b); ESP_LOGV(TAG, "Raw values clear=%d red=%d green=%d blue=%d", raw_c, raw_r, raw_g, raw_b);
float channel_c; float channel_c;

View file

@ -130,11 +130,16 @@ void event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, voi
} }
void WiFiComponent::wifi_pre_setup_() { void WiFiComponent::wifi_pre_setup_() {
#ifdef USE_ESP32_IGNORE_EFUSE_MAC_CRC
uint8_t mac[6]; uint8_t mac[6];
#ifdef USE_ESP32_IGNORE_EFUSE_MAC_CRC
get_mac_address_raw(mac); get_mac_address_raw(mac);
set_mac_address(mac); set_mac_address(mac);
ESP_LOGV(TAG, "Use EFuse MAC without checking CRC: %s", get_mac_address_pretty().c_str()); ESP_LOGV(TAG, "Use EFuse MAC without checking CRC: %s", get_mac_address_pretty().c_str());
#else
if (has_custom_mac_address()) {
get_mac_address_raw(mac);
set_mac_address(mac);
}
#endif #endif
esp_err_t err = esp_netif_init(); esp_err_t err = esp_netif_init();
if (err != ERR_OK) { if (err != ERR_OK) {

View file

@ -53,6 +53,7 @@ CONF_ALLOW_OTHER_USES = "allow_other_uses"
CONF_ALPHA = "alpha" CONF_ALPHA = "alpha"
CONF_ALTITUDE = "altitude" CONF_ALTITUDE = "altitude"
CONF_AMBIENT_LIGHT = "ambient_light" CONF_AMBIENT_LIGHT = "ambient_light"
CONF_AMMONIA = "ammonia"
CONF_ANALOG = "analog" CONF_ANALOG = "analog"
CONF_AND = "and" CONF_AND = "and"
CONF_ANGLE = "angle" CONF_ANGLE = "angle"
@ -110,6 +111,7 @@ CONF_CALIBRATE_LINEAR = "calibrate_linear"
CONF_CALIBRATION = "calibration" CONF_CALIBRATION = "calibration"
CONF_CAPACITANCE = "capacitance" CONF_CAPACITANCE = "capacitance"
CONF_CAPACITY = "capacity" CONF_CAPACITY = "capacity"
CONF_CARBON_MONOXIDE = "carbon_monoxide"
CONF_CARRIER_DUTY_PERCENT = "carrier_duty_percent" CONF_CARRIER_DUTY_PERCENT = "carrier_duty_percent"
CONF_CARRIER_FREQUENCY = "carrier_frequency" CONF_CARRIER_FREQUENCY = "carrier_frequency"
CONF_CERTIFICATE = "certificate" CONF_CERTIFICATE = "certificate"
@ -120,6 +122,7 @@ CONF_CHANNELS = "channels"
CONF_CHARACTERISTIC_UUID = "characteristic_uuid" CONF_CHARACTERISTIC_UUID = "characteristic_uuid"
CONF_CHECK = "check" CONF_CHECK = "check"
CONF_CHIPSET = "chipset" CONF_CHIPSET = "chipset"
CONF_CLEAN_SESSION = "clean_session"
CONF_CLEAR_IMPEDANCE = "clear_impedance" CONF_CLEAR_IMPEDANCE = "clear_impedance"
CONF_CLIENT_CERTIFICATE = "client_certificate" CONF_CLIENT_CERTIFICATE = "client_certificate"
CONF_CLIENT_CERTIFICATE_KEY = "client_certificate_key" CONF_CLIENT_CERTIFICATE_KEY = "client_certificate_key"
@ -262,6 +265,7 @@ CONF_ENUM_DATAPOINT = "enum_datapoint"
CONF_EQUATION = "equation" CONF_EQUATION = "equation"
CONF_ESP8266_DISABLE_SSL_SUPPORT = "esp8266_disable_ssl_support" CONF_ESP8266_DISABLE_SSL_SUPPORT = "esp8266_disable_ssl_support"
CONF_ESPHOME = "esphome" CONF_ESPHOME = "esphome"
CONF_ETHANOL = "ethanol"
CONF_ETHERNET = "ethernet" CONF_ETHERNET = "ethernet"
CONF_EVENT = "event" CONF_EVENT = "event"
CONF_EVENT_TYPE = "event_type" CONF_EVENT_TYPE = "event_type"
@ -360,6 +364,7 @@ CONF_HOURS = "hours"
CONF_HSYNC_PIN = "hsync_pin" CONF_HSYNC_PIN = "hsync_pin"
CONF_HUMIDITY = "humidity" CONF_HUMIDITY = "humidity"
CONF_HUMIDITY_SENSOR = "humidity_sensor" CONF_HUMIDITY_SENSOR = "humidity_sensor"
CONF_HYDROGEN = "hydrogen"
CONF_HYSTERESIS = "hysteresis" CONF_HYSTERESIS = "hysteresis"
CONF_I2C = "i2c" CONF_I2C = "i2c"
CONF_I2C_ID = "i2c_id" CONF_I2C_ID = "i2c_id"
@ -375,6 +380,7 @@ CONF_IDLE_ACTION = "idle_action"
CONF_IDLE_LEVEL = "idle_level" CONF_IDLE_LEVEL = "idle_level"
CONF_IDLE_TIME = "idle_time" CONF_IDLE_TIME = "idle_time"
CONF_IF = "if" CONF_IF = "if"
CONF_IGNORE_EFUSE_CUSTOM_MAC = "ignore_efuse_custom_mac"
CONF_IGNORE_EFUSE_MAC_CRC = "ignore_efuse_mac_crc" CONF_IGNORE_EFUSE_MAC_CRC = "ignore_efuse_mac_crc"
CONF_IGNORE_OUT_OF_RANGE = "ignore_out_of_range" CONF_IGNORE_OUT_OF_RANGE = "ignore_out_of_range"
CONF_IGNORE_PIN_VALIDATION_ERROR = "ignore_pin_validation_error" CONF_IGNORE_PIN_VALIDATION_ERROR = "ignore_pin_validation_error"
@ -475,6 +481,7 @@ CONF_MEDIA_PLAYER = "media_player"
CONF_MEDIUM = "medium" CONF_MEDIUM = "medium"
CONF_MEMORY_BLOCKS = "memory_blocks" CONF_MEMORY_BLOCKS = "memory_blocks"
CONF_MESSAGE = "message" CONF_MESSAGE = "message"
CONF_METHANE = "methane"
CONF_METHOD = "method" CONF_METHOD = "method"
CONF_MICROPHONE = "microphone" CONF_MICROPHONE = "microphone"
CONF_MIN_BRIGHTNESS = "min_brightness" CONF_MIN_BRIGHTNESS = "min_brightness"
@ -521,6 +528,7 @@ CONF_NBITS = "nbits"
CONF_NEC = "nec" CONF_NEC = "nec"
CONF_NETWORKS = "networks" CONF_NETWORKS = "networks"
CONF_NEW_PASSWORD = "new_password" CONF_NEW_PASSWORD = "new_password"
CONF_NITROGEN_DIOXIDE = "nitrogen_dioxide"
CONF_NOISE_LEVEL = "noise_level" CONF_NOISE_LEVEL = "noise_level"
CONF_NUM_ATTEMPTS = "num_attempts" CONF_NUM_ATTEMPTS = "num_attempts"
CONF_NUM_CHANNELS = "num_channels" CONF_NUM_CHANNELS = "num_channels"

View file

@ -44,9 +44,7 @@
#endif #endif
#ifdef USE_ESP32 #ifdef USE_ESP32
#include "esp32/rom/crc.h" #include "esp32/rom/crc.h"
#endif
#if defined(CONFIG_SOC_IEEE802154_SUPPORTED) || defined(USE_ESP32_IGNORE_EFUSE_MAC_CRC)
#include "esp_efuse.h" #include "esp_efuse.h"
#include "esp_efuse_table.h" #include "esp_efuse_table.h"
#endif #endif
@ -671,9 +669,17 @@ void get_mac_address_raw(uint8_t *mac) { // NOLINT(readability-non-const-parame
// match the CRC that goes along with it. For those devices, this // match the CRC that goes along with it. For those devices, this
// work-around reads and uses the MAC address as-is from EFuse, // work-around reads and uses the MAC address as-is from EFuse,
// without doing the CRC check. // without doing the CRC check.
if (has_custom_mac_address()) {
esp_efuse_read_field_blob(ESP_EFUSE_MAC_CUSTOM, mac, 48);
} else {
esp_efuse_read_field_blob(ESP_EFUSE_MAC_FACTORY, mac, 48); esp_efuse_read_field_blob(ESP_EFUSE_MAC_FACTORY, mac, 48);
}
#else #else
if (has_custom_mac_address()) {
esp_efuse_mac_get_custom(mac);
} else {
esp_efuse_mac_get_default(mac); esp_efuse_mac_get_default(mac);
}
#endif #endif
#elif defined(USE_ESP8266) #elif defined(USE_ESP8266)
wifi_get_macaddr(STATION_IF, mac); wifi_get_macaddr(STATION_IF, mac);
@ -685,20 +691,54 @@ void get_mac_address_raw(uint8_t *mac) { // NOLINT(readability-non-const-parame
// this should be an error, but that messes with CI checks. #error No mac address method defined // this should be an error, but that messes with CI checks. #error No mac address method defined
#endif #endif
} }
std::string get_mac_address() { std::string get_mac_address() {
uint8_t mac[6]; uint8_t mac[6];
get_mac_address_raw(mac); get_mac_address_raw(mac);
return str_snprintf("%02x%02x%02x%02x%02x%02x", 12, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); return str_snprintf("%02x%02x%02x%02x%02x%02x", 12, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
} }
std::string get_mac_address_pretty() { std::string get_mac_address_pretty() {
uint8_t mac[6]; uint8_t mac[6];
get_mac_address_raw(mac); get_mac_address_raw(mac);
return str_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); return str_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
} }
#ifdef USE_ESP32 #ifdef USE_ESP32
void set_mac_address(uint8_t *mac) { esp_base_mac_addr_set(mac); } void set_mac_address(uint8_t *mac) { esp_base_mac_addr_set(mac); }
#endif #endif
bool has_custom_mac_address() {
#if defined(USE_ESP32) && !defined(USE_ESP32_IGNORE_EFUSE_CUSTOM_MAC)
uint8_t mac[6];
// do not use 'esp_efuse_mac_get_custom(mac)' because it drops an error in the logs whenever it fails
#ifndef USE_ESP32_VARIANT_ESP32
return (esp_efuse_read_field_blob(ESP_EFUSE_USER_DATA_MAC_CUSTOM, mac, 48) == ESP_OK) && mac_address_is_valid(mac);
#else
return (esp_efuse_read_field_blob(ESP_EFUSE_MAC_CUSTOM, mac, 48) == ESP_OK) && mac_address_is_valid(mac);
#endif
#else
return false;
#endif
}
bool mac_address_is_valid(const uint8_t *mac) {
bool is_all_zeros = true;
bool is_all_ones = true;
for (uint8_t i = 0; i < 6; i++) {
if (mac[i] != 0) {
is_all_zeros = false;
}
}
for (uint8_t i = 0; i < 6; i++) {
if (mac[i] != 0xFF) {
is_all_ones = false;
}
}
return !(is_all_zeros || is_all_ones);
}
void delay_microseconds_safe(uint32_t us) { // avoids CPU locks that could trigger WDT or affect WiFi/BT stability void delay_microseconds_safe(uint32_t us) { // avoids CPU locks that could trigger WDT or affect WiFi/BT stability
uint32_t start = micros(); uint32_t start = micros();

View file

@ -635,6 +635,14 @@ std::string get_mac_address_pretty();
void set_mac_address(uint8_t *mac); void set_mac_address(uint8_t *mac);
#endif #endif
/// Check if a custom MAC address is set (ESP32 & variants)
/// @return True if a custom MAC address is set (ESP32 & variants), else false
bool has_custom_mac_address();
/// Check if the MAC address is not all zeros or all ones
/// @return True if MAC is valid, else false
bool mac_address_is_valid(const uint8_t *mac);
/// Delay for the given amount of microseconds, possibly yielding to other processes during the wait. /// Delay for the given amount of microseconds, possibly yielding to other processes during the wait.
void delay_microseconds_safe(uint32_t us); void delay_microseconds_safe(uint32_t us);

View file

@ -11,16 +11,26 @@ namespace esphome {
static const char *const TAG = "ring_buffer"; static const char *const TAG = "ring_buffer";
RingBuffer::~RingBuffer() {
if (this->handle_ != nullptr) {
vStreamBufferDelete(this->handle_);
ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
allocator.deallocate(this->storage_, this->size_);
}
}
std::unique_ptr<RingBuffer> RingBuffer::create(size_t len) { std::unique_ptr<RingBuffer> RingBuffer::create(size_t len) {
std::unique_ptr<RingBuffer> rb = make_unique<RingBuffer>(); std::unique_ptr<RingBuffer> rb = make_unique<RingBuffer>();
rb->size_ = len + 1;
ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE); ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
rb->storage_ = allocator.allocate(len + 1); rb->storage_ = allocator.allocate(rb->size_);
if (rb->storage_ == nullptr) { if (rb->storage_ == nullptr) {
return nullptr; return nullptr;
} }
rb->handle_ = xStreamBufferCreateStatic(len + 1, 1, rb->storage_, &rb->structure_); rb->handle_ = xStreamBufferCreateStatic(rb->size_, 1, rb->storage_, &rb->structure_);
ESP_LOGD(TAG, "Created ring buffer with size %u", len); ESP_LOGD(TAG, "Created ring buffer with size %u", len);
return rb; return rb;
} }

View file

@ -12,6 +12,8 @@ namespace esphome {
class RingBuffer { class RingBuffer {
public: public:
~RingBuffer();
/** /**
* @brief Reads from the ring buffer, waiting up to a specified number of ticks if necessary. * @brief Reads from the ring buffer, waiting up to a specified number of ticks if necessary.
* *
@ -83,6 +85,7 @@ class RingBuffer {
StreamBufferHandle_t handle_; StreamBufferHandle_t handle_;
StaticStreamBuffer_t structure_; StaticStreamBuffer_t structure_;
uint8_t *storage_; uint8_t *storage_;
size_t size_{0};
}; };
} // namespace esphome } // namespace esphome

View file

@ -226,4 +226,6 @@ class _Schema(vol.Schema):
if isinstance(schema, vol.Schema): if isinstance(schema, vol.Schema):
schema = schema.schema schema = schema.schema
ret = super().extend(schema, extra=extra) ret = super().extend(schema, extra=extra)
return _Schema(ret.schema, extra=ret.extra, extra_schemas=self._extra_schemas) return _Schema(
ret.schema, extra=ret.extra, extra_schemas=self._extra_schemas.copy()
)

View file

@ -90,9 +90,6 @@ esp32:
RP2040_CONFIG = """ RP2040_CONFIG = """
rp2040: rp2040:
board: {board} board: {board}
framework:
# Required until https://github.com/platformio/platform-raspberrypi/pull/36 is merged
platform_version: https://github.com/maxgerhardt/platform-raspberrypi.git
""" """
BK72XX_CONFIG = """ BK72XX_CONFIG = """

View file

@ -165,7 +165,7 @@ platform_packages =
extends = common:arduino extends = common:arduino
board_build.filesystem_size = 0.5m board_build.filesystem_size = 0.5m
platform = https://github.com/maxgerhardt/platform-raspberrypi.git platform = https://github.com/maxgerhardt/platform-raspberrypi.git#v1.2.0-gcc12
platform_packages = platform_packages =
; earlephilhower/framework-arduinopico@~1.20602.0 ; Cannot use the platformio package until old releases stop getting deleted ; earlephilhower/framework-arduinopico@~1.20602.0 ; Cannot use the platformio package until old releases stop getting deleted
earlephilhower/framework-arduinopico@https://github.com/earlephilhower/arduino-pico/releases/download/3.9.4/rp2040-3.9.4.zip earlephilhower/framework-arduinopico@https://github.com/earlephilhower/arduino-pico/releases/download/3.9.4/rp2040-3.9.4.zip

View file

@ -98,7 +98,7 @@ def clang_options(idedata):
cmd.extend(["-isystem", directory]) cmd.extend(["-isystem", directory])
# add library include directories using -isystem to suppress their errors # add library include directories using -isystem to suppress their errors
for directory in sorted(set(idedata["includes"]["build"])): for directory in set(idedata["includes"]["build"]):
# skip our own directories, we add those later # skip our own directories, we add those later
if ( if (
not directory.startswith(f"{root_path}/") not directory.startswith(f"{root_path}/")

View file

@ -1,6 +1,5 @@
ch422g: ch422g:
- id: ch422g_hub - id: ch422g_hub
address: 0x24
binary_sensor: binary_sensor:
- platform: gpio - platform: gpio
@ -11,10 +10,18 @@ binary_sensor:
number: 1 number: 1
mode: INPUT mode: INPUT
inverted: true inverted: true
output:
- platform: gpio - platform: gpio
id: ch422g_output id: ch422_out_0
pin: pin:
ch422g: ch422g_hub ch422g: ch422g_hub
number: 0 number: 0
mode: OUTPUT mode: OUTPUT
inverted: false inverted: false
- platform: gpio
id: ch422_out_11
pin:
ch422g: ch422g_hub
number: 11
mode: OUTPUT_OPEN_DRAIN
inverted: true

View file

@ -0,0 +1,16 @@
sensor:
- platform: adc
pin: GPIO36
id: adc_sensor
- platform: gp2y1010au0f
sensor: adc_sensor
name: Dust Sensor
adc_voltage_offset: 0.2
adc_voltage_multiplier: 3.3
output: dust_sensor_led
output:
- platform: gpio
id: dust_sensor_led
pin: GPIO32

View file

@ -10,6 +10,7 @@ mqtt:
port: 1883 port: 1883
username: debug username: debug
password: debug password: debug
clean_session: True
client_id: someclient client_id: someclient
use_abbreviations: false use_abbreviations: false
discovery: true discovery: true

View file

@ -4,9 +4,6 @@ esphome:
rp2040: rp2040:
board: rpipicow board: rpipicow
framework:
# Waiting for https://github.com/platformio/platform-raspberrypi/pull/36
platform_version: https://github.com/maxgerhardt/platform-raspberrypi.git
logger: logger:
level: VERY_VERBOSE level: VERY_VERBOSE