Merge branch 'dev' into add-graphical-layout-system

This commit is contained in:
Michael Davidson 2024-01-17 21:16:08 +11:00 committed by GitHub
commit d51e54b878
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
55 changed files with 1298 additions and 252 deletions

View file

@ -228,7 +228,7 @@ esphome/components/nextion/binary_sensor/* @senexcrenshaw
esphome/components/nextion/sensor/* @senexcrenshaw
esphome/components/nextion/switch/* @senexcrenshaw
esphome/components/nextion/text_sensor/* @senexcrenshaw
esphome/components/nfc/* @jesserockz
esphome/components/nfc/* @jesserockz @kbx81
esphome/components/noblex/* @AGalfra
esphome/components/number/* @esphome/core
esphome/components/ota/* @esphome/core
@ -363,6 +363,7 @@ esphome/components/ufire_ec/* @pvizeli
esphome/components/ufire_ise/* @pvizeli
esphome/components/ultrasonic/* @OttoWinter
esphome/components/vbus/* @ssieb
esphome/components/veml3235/* @kbx81
esphome/components/version/* @esphome/core
esphome/components/voice_assistant/* @jesserockz
esphome/components/wake_on_lan/* @willwill2will54

View file

@ -18,6 +18,7 @@ from esphome.const import (
UNIT_KILOWATT_HOURS,
UNIT_VOLT,
UNIT_WATT,
STATE_CLASS_TOTAL_INCREASING,
)
DEPENDENCIES = ["uart"]
@ -54,6 +55,7 @@ CONFIG_SCHEMA = (
unit_of_measurement=UNIT_KILOWATT_HOURS,
accuracy_decimals=0,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
cv.Optional(CONF_INTERNAL_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,

View file

@ -19,6 +19,7 @@ from esphome.const import (
UNIT_VOLT,
UNIT_WATT,
UNIT_HERTZ,
STATE_CLASS_TOTAL_INCREASING,
)
DEPENDENCIES = ["uart"]
@ -52,6 +53,7 @@ CONFIG_SCHEMA = (
unit_of_measurement=UNIT_KILOWATT_HOURS,
accuracy_decimals=0,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
cv.Optional(CONF_FREQUENCY): sensor.sensor_schema(
unit_of_measurement=UNIT_HERTZ,

View file

@ -113,8 +113,9 @@ void CSE7766Component::parse_data_() {
bool have_voltage = adj & 0x40;
if (have_voltage) {
// voltage cycle of serial port outputted is a complete cycle;
this->voltage_acc_ += voltage_calib / float(voltage_cycle);
this->voltage_counts_ += 1;
float voltage = voltage_calib / float(voltage_cycle);
if (this->voltage_sensor_ != nullptr)
this->voltage_sensor_->publish_state(voltage);
}
bool have_power = adj & 0x10;
@ -126,8 +127,8 @@ void CSE7766Component::parse_data_() {
if (!power_cycle_exceeds_range) {
power = power_calib / float(power_cycle);
}
this->power_acc_ += power;
this->power_counts_ += 1;
if (this->power_sensor_ != nullptr)
this->power_sensor_->publish_state(power);
uint32_t difference;
if (this->cf_pulses_last_ == 0) {
@ -141,7 +142,10 @@ void CSE7766Component::parse_data_() {
}
this->cf_pulses_last_ = cf_pulses;
this->energy_total_ += difference * float(power_calib) / 1000000.0f / 3600.0f;
this->energy_total_counts_ += 1;
if (this->energy_sensor_ != nullptr)
this->energy_sensor_->publish_state(this->energy_total_);
} else if ((this->energy_sensor_ != nullptr) && !this->energy_sensor_->has_state()) {
this->energy_sensor_->publish_state(0);
}
if (adj & 0x20) {
@ -150,42 +154,13 @@ void CSE7766Component::parse_data_() {
if (have_voltage && !have_power) {
// Testing has shown that when we have voltage and current but not power, that means the power is 0.
// We report a power of 0, which in turn means we should report a current of 0.
this->power_counts_ += 1;
if (this->power_sensor_ != nullptr)
this->power_sensor_->publish_state(0);
} else if (power != 0.0f) {
current = current_calib / float(current_cycle);
}
this->current_acc_ += current;
this->current_counts_ += 1;
}
}
void CSE7766Component::update() {
const auto publish_state = [](const char *name, sensor::Sensor *sensor, float &acc, uint32_t &counts) {
if (counts != 0) {
const auto avg = acc / counts;
ESP_LOGV(TAG, "Got %s_acc=%.2f %s_counts=%" PRIu32 " %s=%.1f", name, acc, name, counts, name, avg);
if (sensor != nullptr) {
sensor->publish_state(avg);
}
acc = 0.0f;
counts = 0;
}
};
publish_state("voltage", this->voltage_sensor_, this->voltage_acc_, this->voltage_counts_);
publish_state("current", this->current_sensor_, this->current_acc_, this->current_counts_);
publish_state("power", this->power_sensor_, this->power_acc_, this->power_counts_);
if (this->energy_total_counts_ != 0) {
ESP_LOGV(TAG, "Got energy_total=%.2f energy_total_counts=%" PRIu32, this->energy_total_,
this->energy_total_counts_);
if (this->energy_sensor_ != nullptr) {
this->energy_sensor_->publish_state(this->energy_total_);
}
this->energy_total_counts_ = 0;
if (this->current_sensor_ != nullptr)
this->current_sensor_->publish_state(current);
}
}
@ -196,7 +171,6 @@ uint32_t CSE7766Component::get_24_bit_uint_(uint8_t start_index) {
void CSE7766Component::dump_config() {
ESP_LOGCONFIG(TAG, "CSE7766:");
LOG_UPDATE_INTERVAL(this);
LOG_SENSOR(" ", "Voltage", this->voltage_sensor_);
LOG_SENSOR(" ", "Current", this->current_sensor_);
LOG_SENSOR(" ", "Power", this->power_sensor_);

View file

@ -7,7 +7,7 @@
namespace esphome {
namespace cse7766 {
class CSE7766Component : public PollingComponent, public uart::UARTDevice {
class CSE7766Component : public Component, public uart::UARTDevice {
public:
void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_sensor; }
@ -16,7 +16,6 @@ class CSE7766Component : public PollingComponent, public uart::UARTDevice {
void loop() override;
float get_setup_priority() const override;
void update() override;
void dump_config() override;
protected:
@ -31,16 +30,8 @@ class CSE7766Component : public PollingComponent, public uart::UARTDevice {
sensor::Sensor *current_sensor_{nullptr};
sensor::Sensor *power_sensor_{nullptr};
sensor::Sensor *energy_sensor_{nullptr};
float voltage_acc_{0.0f};
float current_acc_{0.0f};
float power_acc_{0.0f};
float energy_total_{0.0f};
uint32_t cf_pulses_last_{0};
uint32_t voltage_counts_{0};
uint32_t current_counts_{0};
uint32_t power_counts_{0};
// Setting this to 1 means it will always publish 0 once at startup
uint32_t energy_total_counts_{1};
};
} // namespace cse7766

View file

@ -22,43 +22,37 @@ from esphome.const import (
DEPENDENCIES = ["uart"]
cse7766_ns = cg.esphome_ns.namespace("cse7766")
CSE7766Component = cse7766_ns.class_(
"CSE7766Component", cg.PollingComponent, uart.UARTDevice
)
CSE7766Component = cse7766_ns.class_("CSE7766Component", cg.Component, uart.UARTDevice)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(CSE7766Component),
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=1,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_CURRENT): sensor.sensor_schema(
unit_of_measurement=UNIT_AMPERE,
accuracy_decimals=2,
device_class=DEVICE_CLASS_CURRENT,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_POWER): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT,
accuracy_decimals=1,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_ENERGY): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT_HOURS,
accuracy_decimals=3,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
}
)
.extend(cv.polling_component_schema("60s"))
.extend(uart.UART_DEVICE_SCHEMA)
)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(CSE7766Component),
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=1,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_CURRENT): sensor.sensor_schema(
unit_of_measurement=UNIT_AMPERE,
accuracy_decimals=2,
device_class=DEVICE_CLASS_CURRENT,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_POWER): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT,
accuracy_decimals=1,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_ENERGY): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT_HOURS,
accuracy_decimals=3,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
}
).extend(uart.UART_DEVICE_SCHEMA)
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
"cse7766", baud_rate=4800, require_rx=True
)

View file

@ -217,8 +217,12 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r
uint16_t raw_humidity = (uint16_t(data[0] & 0xFF) << 8) | (data[1] & 0xFF);
uint16_t raw_temperature = (uint16_t(data[2] & 0xFF) << 8) | (data[3] & 0xFF);
if (this->model_ != DHT_MODEL_DHT22_TYPE2 && (raw_temperature & 0x8000) != 0)
raw_temperature = ~(raw_temperature & 0x7FFF);
if (raw_temperature & 0x8000) {
if (!(raw_temperature & 0x4000))
raw_temperature = ~(raw_temperature & 0x7FFF);
} else if (raw_temperature & 0x800) {
raw_temperature |= 0xf000;
}
if (raw_temperature == 1 && raw_humidity == 10) {
if (report_errors) {

View file

@ -145,7 +145,7 @@ async def display_page_show_to_code(config, action_id, template_arg, args):
DisplayPageShowNextAction,
maybe_simple_id(
{
cv.Required(CONF_ID): cv.templatable(cv.use_id(DisplayBuffer)),
cv.Required(CONF_ID): cv.templatable(cv.use_id(Display)),
}
),
)
@ -159,7 +159,7 @@ async def display_page_show_next_to_code(config, action_id, template_arg, args):
DisplayPageShowPrevAction,
maybe_simple_id(
{
cv.Required(CONF_ID): cv.templatable(cv.use_id(DisplayBuffer)),
cv.Required(CONF_ID): cv.templatable(cv.use_id(Display)),
}
),
)
@ -173,7 +173,7 @@ async def display_page_show_previous_to_code(config, action_id, template_arg, ar
DisplayIsDisplayingPageCondition,
cv.maybe_simple_value(
{
cv.GenerateID(CONF_ID): cv.use_id(DisplayBuffer),
cv.GenerateID(CONF_ID): cv.use_id(Display),
cv.Required(CONF_PAGE_ID): cv.use_id(DisplayPage),
},
key=CONF_PAGE_ID,

View file

@ -141,6 +141,122 @@ void Display::filled_circle(int center_x, int center_y, int radius, Color color)
}
} while (dx <= 0);
}
void HOT Display::triangle(int x1, int y1, int x2, int y2, int x3, int y3, Color color) {
this->line(x1, y1, x2, y2);
this->line(x1, y1, x3, y3);
this->line(x2, y2, x3, y3);
}
void Display::sort_triangle_points_by_y_(int *x1, int *y1, int *x2, int *y2, int *x3, int *y3) {
if (*y1 > *y2) {
int x_temp = *x1, y_temp = *y1;
*x1 = *x2, *y1 = *y2;
*x2 = x_temp, *y2 = y_temp;
}
if (*y1 > *y3) {
int x_temp = *x1, y_temp = *y1;
*x1 = *x3, *y1 = *y3;
*x3 = x_temp, *y3 = y_temp;
}
if (*y2 > *y3) {
int x_temp = *x2, y_temp = *y2;
*x2 = *x3, *y2 = *y3;
*x3 = x_temp, *y3 = y_temp;
}
}
void Display::filled_flat_side_triangle_(int x1, int y1, int x2, int y2, int x3, int y3, Color color) {
// y2 must be equal to y3 (same horizontal line)
// Initialize Bresenham's algorithm for side 1
int s1_current_x = x1;
int s1_current_y = y1;
bool s1_axis_swap = false;
int s1_dx = abs(x2 - x1);
int s1_dy = abs(y2 - y1);
int s1_sign_x = ((x2 - x1) >= 0) ? 1 : -1;
int s1_sign_y = ((y2 - y1) >= 0) ? 1 : -1;
if (s1_dy > s1_dx) { // swap values
int tmp = s1_dx;
s1_dx = s1_dy;
s1_dy = tmp;
s1_axis_swap = true;
}
int s1_error = 2 * s1_dy - s1_dx;
// Initialize Bresenham's algorithm for side 2
int s2_current_x = x1;
int s2_current_y = y1;
bool s2_axis_swap = false;
int s2_dx = abs(x3 - x1);
int s2_dy = abs(y3 - y1);
int s2_sign_x = ((x3 - x1) >= 0) ? 1 : -1;
int s2_sign_y = ((y3 - y1) >= 0) ? 1 : -1;
if (s2_dy > s2_dx) { // swap values
int tmp = s2_dx;
s2_dx = s2_dy;
s2_dy = tmp;
s2_axis_swap = true;
}
int s2_error = 2 * s2_dy - s2_dx;
// Iterate on side 1 and allow side 2 to be processed to match the advance of the y-axis.
for (int i = 0; i <= s1_dx; i++) {
if (s1_current_x <= s2_current_x) {
this->horizontal_line(s1_current_x, s1_current_y, s2_current_x - s1_current_x + 1, color);
} else {
this->horizontal_line(s2_current_x, s2_current_y, s1_current_x - s2_current_x + 1, color);
}
// Bresenham's #1
// Side 1 s1_current_x and s1_current_y calculation
while (s1_error >= 0) {
if (s1_axis_swap) {
s1_current_x += s1_sign_x;
} else {
s1_current_y += s1_sign_y;
}
s1_error = s1_error - 2 * s1_dx;
}
if (s1_axis_swap) {
s1_current_y += s1_sign_y;
} else {
s1_current_x += s1_sign_x;
}
s1_error = s1_error + 2 * s1_dy;
// Bresenham's #2
// Side 2 s2_current_x and s2_current_y calculation
while (s2_current_y != s1_current_y) {
while (s2_error >= 0) {
if (s2_axis_swap) {
s2_current_x += s2_sign_x;
} else {
s2_current_y += s2_sign_y;
}
s2_error = s2_error - 2 * s2_dx;
}
if (s2_axis_swap) {
s2_current_y += s2_sign_y;
} else {
s2_current_x += s2_sign_x;
}
s2_error = s2_error + 2 * s2_dy;
}
}
}
void Display::filled_triangle(int x1, int y1, int x2, int y2, int x3, int y3, Color color) {
// Sort the three points by y-coordinate ascending, so [x1,y1] is the topmost point
this->sort_triangle_points_by_y_(&x1, &y1, &x2, &y2, &x3, &y3);
if (y2 == y3) { // Check for special case of a bottom-flat triangle
this->filled_flat_side_triangle_(x1, y1, x2, y2, x3, y3, color);
} else if (y1 == y2) { // Check for special case of a top-flat triangle
this->filled_flat_side_triangle_(x3, y3, x1, y1, x2, y2, color);
} else { // General case: split the no-flat-side triangle in a top-flat triangle and bottom-flat triangle
int x_temp = (int) (x1 + ((float) (y2 - y1) / (float) (y3 - y1)) * (x3 - x1)), y_temp = y2;
this->filled_flat_side_triangle_(x1, y1, x2, y2, x_temp, y_temp, color);
this->filled_flat_side_triangle_(x3, y3, x2, y2, x_temp, y_temp, color);
}
}
void Display::print(int x, int y, BaseFont *font, Color color, TextAlign align, const char *text) {
int x_start, y_start;

View file

@ -242,6 +242,12 @@ class Display : public PollingComponent {
/// Fill a circle centered around [center_x,center_y] with the radius radius with the given color.
void filled_circle(int center_x, int center_y, int radius, Color color = COLOR_ON);
/// Draw the outline of a triangle contained between the points [x1,y1], [x2,y2] and [x3,y3] with the given color.
void triangle(int x1, int y1, int x2, int y2, int x3, int y3, Color color = COLOR_ON);
/// Fill a triangle contained between the points [x1,y1], [x2,y2] and [x3,y3] with the given color.
void filled_triangle(int x1, int y1, int x2, int y2, int x3, int y3, Color color = COLOR_ON);
/** Print `text` with the anchor point at [x,y] with `font`.
*
* @param x The x coordinate of the text alignment anchor point.
@ -579,6 +585,15 @@ class Display : public PollingComponent {
void do_update_();
void clear_clipping_();
/**
* This method fills a triangle using only integer variables by using a
* modified bresenham algorithm.
* It is mandatory that [x2,y2] and [x3,y3] lie on the same horizontal line,
* so y2 must be equal to y3.
*/
void filled_flat_side_triangle_(int x1, int y1, int x2, int y2, int x3, int y3, Color color);
void sort_triangle_points_by_y_(int *x1, int *y1, int *x2, int *y2, int *x3, int *y3);
DisplayRotation rotation_{DISPLAY_ROTATION_0_DEGREES};
optional<display_writer_t> writer_{};
DisplayPage *page_{nullptr};

View file

@ -13,6 +13,8 @@ namespace esp32_rmt_led_strip {
static const char *const TAG = "esp32_rmt_led_strip";
static const uint32_t RMT_CLK_FREQ = 80000000;
static const uint8_t RMT_CLK_DIV = 2;
void ESP32RMTLEDStripLightOutput::setup() {
@ -65,7 +67,7 @@ void ESP32RMTLEDStripLightOutput::setup() {
void ESP32RMTLEDStripLightOutput::set_led_params(uint32_t bit0_high, uint32_t bit0_low, uint32_t bit1_high,
uint32_t bit1_low) {
float ratio = (float) APB_CLK_FREQ / RMT_CLK_DIV / 1e09f;
float ratio = (float) RMT_CLK_FREQ / RMT_CLK_DIV / 1e09f;
// 0-bit
this->bit0_.duration0 = (uint32_t) (ratio * bit0_high);

View file

@ -13,8 +13,10 @@ from esphome.const import (
CONF_ON_ENROLLMENT_DONE,
CONF_ON_ENROLLMENT_FAILED,
CONF_ON_ENROLLMENT_SCAN,
CONF_ON_FINGER_SCAN_START,
CONF_ON_FINGER_SCAN_MATCHED,
CONF_ON_FINGER_SCAN_UNMATCHED,
CONF_ON_FINGER_SCAN_MISPLACED,
CONF_ON_FINGER_SCAN_INVALID,
CONF_PASSWORD,
CONF_SENSING_PIN,
@ -35,6 +37,10 @@ FingerprintGrowComponent = fingerprint_grow_ns.class_(
"FingerprintGrowComponent", cg.PollingComponent, uart.UARTDevice
)
FingerScanStartTrigger = fingerprint_grow_ns.class_(
"FingerScanStartTrigger", automation.Trigger.template()
)
FingerScanMatchedTrigger = fingerprint_grow_ns.class_(
"FingerScanMatchedTrigger", automation.Trigger.template(cg.uint16, cg.uint16)
)
@ -43,6 +49,10 @@ FingerScanUnmatchedTrigger = fingerprint_grow_ns.class_(
"FingerScanUnmatchedTrigger", automation.Trigger.template()
)
FingerScanMisplacedTrigger = fingerprint_grow_ns.class_(
"FingerScanMisplacedTrigger", automation.Trigger.template()
)
FingerScanInvalidTrigger = fingerprint_grow_ns.class_(
"FingerScanInvalidTrigger", automation.Trigger.template()
)
@ -99,6 +109,13 @@ CONFIG_SCHEMA = (
cv.Optional(CONF_SENSING_PIN): pins.gpio_input_pin_schema,
cv.Optional(CONF_PASSWORD): cv.uint32_t,
cv.Optional(CONF_NEW_PASSWORD): cv.uint32_t,
cv.Optional(CONF_ON_FINGER_SCAN_START): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
FingerScanStartTrigger
),
}
),
cv.Optional(CONF_ON_FINGER_SCAN_MATCHED): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
@ -113,6 +130,13 @@ CONFIG_SCHEMA = (
),
}
),
cv.Optional(CONF_ON_FINGER_SCAN_MISPLACED): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
FingerScanMisplacedTrigger
),
}
),
cv.Optional(CONF_ON_FINGER_SCAN_INVALID): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
@ -164,6 +188,10 @@ async def to_code(config):
sensing_pin = await cg.gpio_pin_expression(config[CONF_SENSING_PIN])
cg.add(var.set_sensing_pin(sensing_pin))
for conf in config.get(CONF_ON_FINGER_SCAN_START, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_FINGER_SCAN_MATCHED, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(
@ -174,6 +202,10 @@ async def to_code(config):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_FINGER_SCAN_MISPLACED, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_FINGER_SCAN_INVALID, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)

View file

@ -15,16 +15,18 @@ void FingerprintGrowComponent::update() {
return;
}
if (this->sensing_pin_ != nullptr) {
if (this->has_sensing_pin_) {
if (this->sensing_pin_->digital_read()) {
ESP_LOGV(TAG, "No touch sensing");
this->waiting_removal_ = false;
return;
} else if (!this->waiting_removal_) {
this->finger_scan_start_callback_.call();
}
}
if (this->waiting_removal_) {
if (this->scan_image_(1) == NO_FINGER) {
if ((!this->has_sensing_pin_) && (this->scan_image_(1) == NO_FINGER)) {
ESP_LOGD(TAG, "Finger removed");
this->waiting_removal_ = false;
}
@ -51,6 +53,7 @@ void FingerprintGrowComponent::update() {
void FingerprintGrowComponent::setup() {
ESP_LOGCONFIG(TAG, "Setting up Grow Fingerprint Reader...");
this->has_sensing_pin_ = (this->sensing_pin_ != nullptr);
if (this->check_password_()) {
if (this->new_password_ != -1) {
if (this->set_password_())
@ -91,7 +94,7 @@ void FingerprintGrowComponent::finish_enrollment(uint8_t result) {
}
void FingerprintGrowComponent::scan_and_match_() {
if (this->sensing_pin_ != nullptr) {
if (this->has_sensing_pin_) {
ESP_LOGD(TAG, "Scan and match");
} else {
ESP_LOGV(TAG, "Scan and match");
@ -122,33 +125,38 @@ void FingerprintGrowComponent::scan_and_match_() {
}
uint8_t FingerprintGrowComponent::scan_image_(uint8_t buffer) {
if (this->sensing_pin_ != nullptr) {
if (this->has_sensing_pin_) {
ESP_LOGD(TAG, "Getting image %d", buffer);
} else {
ESP_LOGV(TAG, "Getting image %d", buffer);
}
this->data_ = {GET_IMAGE};
switch (this->send_command_()) {
uint8_t send_result = this->send_command_();
switch (send_result) {
case OK:
break;
case NO_FINGER:
if (this->sensing_pin_ != nullptr) {
ESP_LOGD(TAG, "No finger");
this->finger_scan_invalid_callback_.call();
if (this->has_sensing_pin_) {
this->waiting_removal_ = true;
ESP_LOGD(TAG, "Finger Misplaced");
this->finger_scan_misplaced_callback_.call();
} else {
ESP_LOGV(TAG, "No finger");
}
return this->data_[0];
return send_result;
case IMAGE_FAIL:
ESP_LOGE(TAG, "Imaging error");
this->finger_scan_invalid_callback_.call();
return send_result;
default:
return this->data_[0];
ESP_LOGD(TAG, "Unknown Scan Error: %d", send_result);
return send_result;
}
ESP_LOGD(TAG, "Processing image %d", buffer);
this->data_ = {IMAGE_2_TZ, buffer};
switch (this->send_command_()) {
send_result = this->send_command_();
switch (send_result) {
case OK:
ESP_LOGI(TAG, "Processed image %d", buffer);
break;
@ -162,7 +170,7 @@ uint8_t FingerprintGrowComponent::scan_image_(uint8_t buffer) {
this->finger_scan_invalid_callback_.call();
break;
}
return this->data_[0];
return send_result;
}
uint8_t FingerprintGrowComponent::save_fingerprint_() {
@ -225,10 +233,11 @@ bool FingerprintGrowComponent::get_parameters_() {
ESP_LOGD(TAG, "Getting parameters");
this->data_ = {READ_SYS_PARAM};
if (this->send_command_() == OK) {
ESP_LOGD(TAG, "Got parameters");
if (this->status_sensor_ != nullptr) {
ESP_LOGD(TAG, "Got parameters"); // Bear in mind data_[0] is the transfer status,
if (this->status_sensor_ != nullptr) { // the parameters table start at data_[1]
this->status_sensor_->publish_state(((uint16_t) this->data_[1] << 8) | this->data_[2]);
}
this->system_identifier_code_ = ((uint16_t) this->data_[3] << 8) | this->data_[4];
this->capacity_ = ((uint16_t) this->data_[5] << 8) | this->data_[6];
if (this->capacity_sensor_ != nullptr) {
this->capacity_sensor_->publish_state(this->capacity_);
@ -430,13 +439,22 @@ uint8_t FingerprintGrowComponent::send_command_() {
void FingerprintGrowComponent::dump_config() {
ESP_LOGCONFIG(TAG, "GROW_FINGERPRINT_READER:");
ESP_LOGCONFIG(TAG, " System Identifier Code: 0x%.4X", this->system_identifier_code_);
ESP_LOGCONFIG(TAG, " Touch Sensing Pin: %s",
this->has_sensing_pin_ ? this->sensing_pin_->dump_summary().c_str() : "None");
LOG_UPDATE_INTERVAL(this);
LOG_SENSOR(" ", "Fingerprint Count", this->fingerprint_count_sensor_);
ESP_LOGCONFIG(TAG, " Current Value: %d", (uint16_t) this->fingerprint_count_sensor_->get_state());
LOG_SENSOR(" ", "Status", this->status_sensor_);
ESP_LOGCONFIG(TAG, " Current Value: %d", (uint8_t) this->status_sensor_->get_state());
LOG_SENSOR(" ", "Capacity", this->capacity_sensor_);
ESP_LOGCONFIG(TAG, " Current Value: %d", (uint16_t) this->capacity_sensor_->get_state());
LOG_SENSOR(" ", "Security Level", this->security_level_sensor_);
ESP_LOGCONFIG(TAG, " Current Value: %d", (uint8_t) this->security_level_sensor_->get_state());
LOG_SENSOR(" ", "Last Finger ID", this->last_finger_id_sensor_);
ESP_LOGCONFIG(TAG, " Current Value: %d", (uint32_t) this->last_finger_id_sensor_->get_state());
LOG_SENSOR(" ", "Last Confidence", this->last_confidence_sensor_);
ESP_LOGCONFIG(TAG, " Current Value: %d", (uint32_t) this->last_confidence_sensor_->get_state());
}
} // namespace fingerprint_grow

View file

@ -118,12 +118,18 @@ class FingerprintGrowComponent : public PollingComponent, public uart::UARTDevic
void set_enrolling_binary_sensor(binary_sensor::BinarySensor *enrolling_binary_sensor) {
this->enrolling_binary_sensor_ = enrolling_binary_sensor;
}
void add_on_finger_scan_start_callback(std::function<void()> callback) {
this->finger_scan_start_callback_.add(std::move(callback));
}
void add_on_finger_scan_matched_callback(std::function<void(uint16_t, uint16_t)> callback) {
this->finger_scan_matched_callback_.add(std::move(callback));
}
void add_on_finger_scan_unmatched_callback(std::function<void()> callback) {
this->finger_scan_unmatched_callback_.add(std::move(callback));
}
void add_on_finger_scan_misplaced_callback(std::function<void()> callback) {
this->finger_scan_misplaced_callback_.add(std::move(callback));
}
void add_on_finger_scan_invalid_callback(std::function<void()> callback) {
this->finger_scan_invalid_callback_.add(std::move(callback));
}
@ -166,8 +172,10 @@ class FingerprintGrowComponent : public PollingComponent, public uart::UARTDevic
uint16_t enrollment_slot_ = ENROLLMENT_SLOT_UNUSED;
uint8_t enrollment_buffers_ = 5;
bool waiting_removal_ = false;
bool has_sensing_pin_ = false;
uint32_t last_aura_led_control_ = 0;
uint16_t last_aura_led_duration_ = 0;
uint16_t system_identifier_code_ = 0;
sensor::Sensor *fingerprint_count_sensor_{nullptr};
sensor::Sensor *status_sensor_{nullptr};
sensor::Sensor *capacity_sensor_{nullptr};
@ -176,13 +184,22 @@ class FingerprintGrowComponent : public PollingComponent, public uart::UARTDevic
sensor::Sensor *last_confidence_sensor_{nullptr};
binary_sensor::BinarySensor *enrolling_binary_sensor_{nullptr};
CallbackManager<void()> finger_scan_invalid_callback_;
CallbackManager<void()> finger_scan_start_callback_;
CallbackManager<void(uint16_t, uint16_t)> finger_scan_matched_callback_;
CallbackManager<void()> finger_scan_unmatched_callback_;
CallbackManager<void()> finger_scan_misplaced_callback_;
CallbackManager<void(uint8_t, uint16_t)> enrollment_scan_callback_;
CallbackManager<void(uint16_t)> enrollment_done_callback_;
CallbackManager<void(uint16_t)> enrollment_failed_callback_;
};
class FingerScanStartTrigger : public Trigger<> {
public:
explicit FingerScanStartTrigger(FingerprintGrowComponent *parent) {
parent->add_on_finger_scan_start_callback([this]() { this->trigger(); });
}
};
class FingerScanMatchedTrigger : public Trigger<uint16_t, uint16_t> {
public:
explicit FingerScanMatchedTrigger(FingerprintGrowComponent *parent) {
@ -198,6 +215,13 @@ class FingerScanUnmatchedTrigger : public Trigger<> {
}
};
class FingerScanMisplacedTrigger : public Trigger<> {
public:
explicit FingerScanMisplacedTrigger(FingerprintGrowComponent *parent) {
parent->add_on_finger_scan_misplaced_callback([this]() { this->trigger(); });
}
};
class FingerScanInvalidTrigger : public Trigger<> {
public:
explicit FingerScanInvalidTrigger(FingerprintGrowComponent *parent) {

View file

@ -67,13 +67,13 @@ def validate_pillow_installed(value):
except ImportError as err:
raise cv.Invalid(
"Please install the pillow python package to use this feature. "
'(pip install "pillow==10.1.0")'
'(pip install "pillow==10.2.0")'
) from err
if version.parse(PIL.__version__) != version.parse("10.1.0"):
if version.parse(PIL.__version__) != version.parse("10.2.0"):
raise cv.Invalid(
"Please update your pillow installation to 10.1.0. "
'(pip install "pillow==10.1.0")'
"Please update your pillow installation to 10.2.0. "
'(pip install "pillow==10.2.0")'
)
return value

View file

@ -13,14 +13,14 @@
namespace esphome {
namespace ft63x6 {
static const uint8_t FT63X6_ADDR_TOUCH_COUNT = 0x02;
static const uint8_t FT63X6_ADDR_TOUCH1_ID = 0x05;
static const uint8_t FT63X6_ADDR_TOUCH1_STATE = 0x03;
static const uint8_t FT63X6_ADDR_TOUCH1_X = 0x03;
static const uint8_t FT63X6_ADDR_TOUCH1_ID = 0x05;
static const uint8_t FT63X6_ADDR_TOUCH1_Y = 0x05;
static const uint8_t FT63X6_ADDR_TOUCH2_ID = 0x0B;
static const uint8_t FT63X6_ADDR_TOUCH2_STATE = 0x09;
static const uint8_t FT63X6_ADDR_TOUCH2_X = 0x09;
static const uint8_t FT63X6_ADDR_TOUCH2_ID = 0x0B;
static const uint8_t FT63X6_ADDR_TOUCH2_Y = 0x0B;
static const char *const TAG = "FT63X6Touchscreen";
@ -40,26 +40,11 @@ void FT63X6Touchscreen::setup() {
this->hard_reset_();
// Get touch resolution
this->x_raw_max_ = 320;
this->y_raw_max_ = 480;
}
void FT63X6Touchscreen::update_touches() {
int touch_count = this->read_touch_count_();
if (touch_count == 0) {
return;
if (this->x_raw_max_ == this->x_raw_min_) {
this->x_raw_max_ = 320;
}
uint8_t touch_id = this->read_touch_id_(FT63X6_ADDR_TOUCH1_ID); // id1 = 0 or 1
int16_t x = this->read_touch_coordinate_(FT63X6_ADDR_TOUCH1_X);
int16_t y = this->read_touch_coordinate_(FT63X6_ADDR_TOUCH1_Y);
this->add_raw_touch_position_(touch_id, x, y);
if (touch_count >= 2) {
touch_id = this->read_touch_id_(FT63X6_ADDR_TOUCH2_ID); // id2 = 0 or 1(~id1 & 0x01)
x = this->read_touch_coordinate_(FT63X6_ADDR_TOUCH2_X);
y = this->read_touch_coordinate_(FT63X6_ADDR_TOUCH2_Y);
this->add_raw_touch_position_(touch_id, x, y);
if (this->y_raw_max_ == this->y_raw_min_) {
this->y_raw_max_ = 480;
}
}
@ -76,23 +61,31 @@ void FT63X6Touchscreen::dump_config() {
LOG_I2C_DEVICE(this);
LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_);
LOG_PIN(" Reset Pin: ", this->reset_pin_);
LOG_UPDATE_INTERVAL(this);
}
uint8_t FT63X6Touchscreen::read_touch_count_() { return this->read_byte_(FT63X6_ADDR_TOUCH_COUNT); }
void FT63X6Touchscreen::update_touches() {
uint8_t data[15];
uint16_t touch_id, x, y;
// Touch functions
uint16_t FT63X6Touchscreen::read_touch_coordinate_(uint8_t coordinate) {
uint8_t read_buf[2];
read_buf[0] = this->read_byte_(coordinate);
read_buf[1] = this->read_byte_(coordinate + 1);
return ((read_buf[0] & 0x0f) << 8) | read_buf[1];
}
uint8_t FT63X6Touchscreen::read_touch_id_(uint8_t id_address) { return this->read_byte_(id_address) >> 4; }
if (!this->read_bytes(0x00, (uint8_t *) data, 15)) {
ESP_LOGE(TAG, "Failed to read touch data");
this->skip_update_ = true;
return;
}
uint8_t FT63X6Touchscreen::read_byte_(uint8_t addr) {
uint8_t byte = 0;
this->read_byte(addr, &byte);
return byte;
if (((data[FT63X6_ADDR_TOUCH1_STATE] >> 6) & 0x01) == 0) {
touch_id = data[FT63X6_ADDR_TOUCH1_ID] >> 4; // id1 = 0 or 1
x = encode_uint16(data[FT63X6_ADDR_TOUCH1_X] & 0x0F, data[FT63X6_ADDR_TOUCH1_X + 1]);
y = encode_uint16(data[FT63X6_ADDR_TOUCH1_Y] & 0x0F, data[FT63X6_ADDR_TOUCH1_Y + 1]);
this->add_raw_touch_position_(touch_id, x, y);
}
if (((data[FT63X6_ADDR_TOUCH2_STATE] >> 6) & 0x01) == 0) {
touch_id = data[FT63X6_ADDR_TOUCH2_ID] >> 4; // id1 = 0 or 1
x = encode_uint16(data[FT63X6_ADDR_TOUCH2_X] & 0x0F, data[FT63X6_ADDR_TOUCH2_X + 1]);
y = encode_uint16(data[FT63X6_ADDR_TOUCH2_Y] & 0x0F, data[FT63X6_ADDR_TOUCH2_Y + 1]);
this->add_raw_touch_position_(touch_id, x, y);
}
}
} // namespace ft63x6

View file

@ -61,6 +61,7 @@ VALUE_POSITION_TYPE = {
"BELOW": ValuePositionType.VALUE_POSITION_TYPE_BELOW,
}
CONF_CONTINUOUS = "continuous"
GRAPH_TRACE_SCHEMA = cv.Schema(
{
@ -70,6 +71,7 @@ GRAPH_TRACE_SCHEMA = cv.Schema(
cv.Optional(CONF_LINE_THICKNESS): cv.positive_int,
cv.Optional(CONF_LINE_TYPE): cv.enum(LINE_TYPE, upper=True),
cv.Optional(CONF_COLOR): cv.use_id(color.ColorStruct),
cv.Optional(CONF_CONTINUOUS): cv.boolean,
}
)
@ -186,6 +188,8 @@ async def to_code(config):
if CONF_COLOR in trace:
c = await cg.get_variable(trace[CONF_COLOR])
cg.add(tr.set_line_color(c))
if CONF_CONTINUOUS in trace:
cg.add(tr.set_continuous(trace[CONF_CONTINUOUS]))
cg.add(var.add_trace(tr))
# Add legend
if CONF_LEGEND in config:

View file

@ -165,17 +165,42 @@ void Graph::draw(Display *buff, uint16_t x_offset, uint16_t y_offset, Color colo
for (auto *trace : traces_) {
Color c = trace->get_line_color();
uint16_t thick = trace->get_line_thickness();
bool continuous = trace->get_continuous();
bool has_prev = false;
bool prev_b = false;
int16_t prev_y = 0;
for (uint32_t i = 0; i < this->width_; i++) {
float v = (trace->get_tracedata()->get_value(i) - ymin) / yrange;
if (!std::isnan(v) && (thick > 0)) {
int16_t x = this->width_ - 1 - i;
uint8_t b = (i % (thick * LineType::PATTERN_LENGTH)) / thick;
if (((uint8_t) trace->get_line_type() & (1 << b)) == (1 << b)) {
int16_t y = (int16_t) roundf((this->height_ - 1) * (1.0 - v)) - thick / 2;
for (uint16_t t = 0; t < thick; t++) {
buff->draw_pixel_at(x_offset + x, y_offset + y + t, c);
int16_t x = this->width_ - 1 - i + x_offset;
uint8_t bit = 1 << ((i % (thick * LineType::PATTERN_LENGTH)) / thick);
bool b = (trace->get_line_type() & bit) == bit;
if (b) {
int16_t y = (int16_t) roundf((this->height_ - 1) * (1.0 - v)) - thick / 2 + y_offset;
if (!continuous || !has_prev || !prev_b || (abs(y - prev_y) <= thick)) {
for (uint16_t t = 0; t < thick; t++) {
buff->draw_pixel_at(x, y + t, c);
}
} else {
int16_t mid_y = (y + prev_y + thick) / 2;
if (y > prev_y) {
for (uint16_t t = prev_y + thick; t <= mid_y; t++)
buff->draw_pixel_at(x + 1, t, c);
for (uint16_t t = mid_y + 1; t < y + thick; t++)
buff->draw_pixel_at(x, t, c);
} else {
for (uint16_t t = prev_y - 1; t >= mid_y; t--)
buff->draw_pixel_at(x + 1, t, c);
for (uint16_t t = mid_y - 1; t >= y; t--)
buff->draw_pixel_at(x, t, c);
}
}
prev_y = y;
}
prev_b = b;
has_prev = true;
} else {
has_prev = false;
}
}
}

View file

@ -116,6 +116,8 @@ class GraphTrace {
void set_line_type(enum LineType val) { this->line_type_ = val; }
Color get_line_color() { return this->line_color_; }
void set_line_color(Color val) { this->line_color_ = val; }
bool get_continuous() { return this->continuous_; }
void set_continuous(bool continuous) { this->continuous_ = continuous; }
std::string get_name() { return name_; }
const HistoryData *get_tracedata() { return &data_; }
@ -125,6 +127,7 @@ class GraphTrace {
uint8_t line_thickness_{3};
enum LineType line_type_ { LINE_TYPE_SOLID };
Color line_color_{COLOR_ON};
bool continuous_{false};
HistoryData data_;
friend Graph;

View file

@ -52,7 +52,7 @@ optional<uint8_t> ImprovSerialComponent::read_byte_() {
size_t available;
uart_get_buffered_data_len(this->uart_num_, &available);
if (available) {
uart_read_bytes(this->uart_num_, &data, 1, 20 / portTICK_PERIOD_MS);
uart_read_bytes(this->uart_num_, &data, 1, 0);
byte = data;
}
}
@ -71,7 +71,7 @@ optional<uint8_t> ImprovSerialComponent::read_byte_() {
#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S3)
case logger::UART_SELECTION_USB_SERIAL_JTAG: {
if (usb_serial_jtag_read_bytes((char *) &data, 1, 20 / portTICK_PERIOD_MS)) {
if (usb_serial_jtag_read_bytes((char *) &data, 1, 0)) {
byte = data;
}
break;

View file

@ -55,6 +55,9 @@ void Inkplate6::setup() {
this->wakeup_pin_->digital_write(false);
}
/**
* Allocate buffers. May be called after setup to re-initialise if e.g. greyscale is changed.
*/
void Inkplate6::initialize_() {
ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
ExternalRAMAllocator<uint32_t> allocator32(ExternalRAMAllocator<uint32_t>::ALLOW_FAILURE);

View file

@ -68,8 +68,9 @@ class Inkplate6 : public display::DisplayBuffer, public i2c::I2CDevice {
void set_greyscale(bool greyscale) {
this->greyscale_ = greyscale;
this->initialize_();
this->block_partial_ = true;
if (this->is_ready())
this->initialize_();
}
void set_partial_updating(bool partial_updating) { this->partial_updating_ = partial_updating; }
void set_full_update_every(uint32_t full_update_every) { this->full_update_every_ = full_update_every; }

View file

@ -1,12 +1,13 @@
from esphome import automation
import esphome.codegen as cg
CODEOWNERS = ["@jesserockz"]
CODEOWNERS = ["@jesserockz", "@kbx81"]
nfc_ns = cg.esphome_ns.namespace("nfc")
Nfcc = nfc_ns.class_("Nfcc")
NfcTag = nfc_ns.class_("NfcTag")
NfcTagListener = nfc_ns.class_("NfcTagListener")
NfcOnTagTrigger = nfc_ns.class_(
"NfcOnTagTrigger", automation.Trigger.template(cg.std_string, NfcTag)
)

View file

@ -0,0 +1,72 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import binary_sensor
from esphome.const import CONF_UID
from esphome.core import HexInt
from .. import nfc_ns, Nfcc, NfcTagListener
DEPENDENCIES = ["nfc"]
CONF_NDEF_CONTAINS = "ndef_contains"
CONF_NFCC_ID = "nfcc_id"
CONF_TAG_ID = "tag_id"
NfcTagBinarySensor = nfc_ns.class_(
"NfcTagBinarySensor",
binary_sensor.BinarySensor,
cg.Component,
NfcTagListener,
cg.Parented.template(Nfcc),
)
def validate_uid(value):
value = cv.string_strict(value)
for x in value.split("-"):
if len(x) != 2:
raise cv.Invalid(
"Each part (separated by '-') of the UID must be two characters "
"long."
)
try:
x = int(x, 16)
except ValueError as err:
raise cv.Invalid(
"Valid characters for parts of a UID are 0123456789ABCDEF."
) from err
if x < 0 or x > 255:
raise cv.Invalid(
"Valid values for UID parts (separated by '-') are 00 to FF"
)
return value
CONFIG_SCHEMA = cv.All(
binary_sensor.binary_sensor_schema(NfcTagBinarySensor)
.extend(
{
cv.GenerateID(CONF_NFCC_ID): cv.use_id(Nfcc),
cv.Optional(CONF_NDEF_CONTAINS): cv.string,
cv.Optional(CONF_TAG_ID): cv.string,
cv.Optional(CONF_UID): validate_uid,
}
)
.extend(cv.COMPONENT_SCHEMA),
cv.has_exactly_one_key(CONF_NDEF_CONTAINS, CONF_TAG_ID, CONF_UID),
)
async def to_code(config):
var = await binary_sensor.new_binary_sensor(config)
await cg.register_component(var, config)
await cg.register_parented(var, config[CONF_NFCC_ID])
hub = await cg.get_variable(config[CONF_NFCC_ID])
cg.add(hub.register_listener(var))
if CONF_NDEF_CONTAINS in config:
cg.add(var.set_ndef_match_string(config[CONF_NDEF_CONTAINS]))
if CONF_TAG_ID in config:
cg.add(var.set_tag_name(config[CONF_TAG_ID]))
elif CONF_UID in config:
addr = [HexInt(int(x, 16)) for x in config[CONF_UID].split("-")]
cg.add(var.set_uid(addr))

View file

@ -0,0 +1,114 @@
#include "binary_sensor.h"
#include "../nfc_helpers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace nfc {
static const char *const TAG = "nfc.binary_sensor";
void NfcTagBinarySensor::setup() {
this->parent_->register_listener(this);
this->publish_initial_state(false);
}
void NfcTagBinarySensor::dump_config() {
std::string match_str = "name";
LOG_BINARY_SENSOR("", "NFC Tag Binary Sensor", this);
if (!this->match_string_.empty()) {
if (!this->match_tag_name_) {
match_str = "contains";
}
ESP_LOGCONFIG(TAG, " Tag %s: %s", match_str.c_str(), this->match_string_.c_str());
return;
}
if (!this->uid_.empty()) {
ESP_LOGCONFIG(TAG, " Tag UID: %s", format_bytes(this->uid_).c_str());
}
}
void NfcTagBinarySensor::set_ndef_match_string(const std::string &str) {
this->match_string_ = str;
this->match_tag_name_ = false;
}
void NfcTagBinarySensor::set_tag_name(const std::string &str) {
this->match_string_ = str;
this->match_tag_name_ = true;
}
void NfcTagBinarySensor::set_uid(const std::vector<uint8_t> &uid) { this->uid_ = uid; }
bool NfcTagBinarySensor::tag_match_ndef_string(const std::shared_ptr<NdefMessage> &msg) {
for (const auto &record : msg->get_records()) {
if (record->get_payload().find(this->match_string_) != std::string::npos) {
return true;
}
}
return false;
}
bool NfcTagBinarySensor::tag_match_tag_name(const std::shared_ptr<NdefMessage> &msg) {
for (const auto &record : msg->get_records()) {
if (record->get_payload().find(HA_TAG_ID_PREFIX) != std::string::npos) {
auto rec_substr = record->get_payload().substr(sizeof(HA_TAG_ID_PREFIX) - 1);
if (rec_substr.find(this->match_string_) != std::string::npos) {
return true;
}
}
}
return false;
}
bool NfcTagBinarySensor::tag_match_uid(const std::vector<uint8_t> &data) {
if (data.size() != this->uid_.size()) {
return false;
}
for (size_t i = 0; i < data.size(); i++) {
if (data[i] != this->uid_[i]) {
return false;
}
}
return true;
}
void NfcTagBinarySensor::tag_off(NfcTag &tag) {
if (!this->match_string_.empty() && tag.has_ndef_message()) {
if (this->match_tag_name_) {
if (this->tag_match_tag_name(tag.get_ndef_message())) {
this->publish_state(false);
}
} else {
if (this->tag_match_ndef_string(tag.get_ndef_message())) {
this->publish_state(false);
}
}
return;
}
if (!this->uid_.empty() && this->tag_match_uid(tag.get_uid())) {
this->publish_state(false);
}
}
void NfcTagBinarySensor::tag_on(NfcTag &tag) {
if (!this->match_string_.empty() && tag.has_ndef_message()) {
if (this->match_tag_name_) {
if (this->tag_match_tag_name(tag.get_ndef_message())) {
this->publish_state(true);
}
} else {
if (this->tag_match_ndef_string(tag.get_ndef_message())) {
this->publish_state(true);
}
}
return;
}
if (!this->uid_.empty() && this->tag_match_uid(tag.get_uid())) {
this->publish_state(true);
}
}
} // namespace nfc
} // namespace esphome

View file

@ -0,0 +1,38 @@
#pragma once
#include "esphome/components/binary_sensor/binary_sensor.h"
#include "esphome/components/nfc/nfc.h"
#include "esphome/components/nfc/nfc_tag.h"
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace nfc {
class NfcTagBinarySensor : public binary_sensor::BinarySensor,
public Component,
public NfcTagListener,
public Parented<Nfcc> {
public:
void setup() override;
void dump_config() override;
void set_ndef_match_string(const std::string &str);
void set_tag_name(const std::string &str);
void set_uid(const std::vector<uint8_t> &uid);
bool tag_match_ndef_string(const std::shared_ptr<NdefMessage> &msg);
bool tag_match_tag_name(const std::shared_ptr<NdefMessage> &msg);
bool tag_match_uid(const std::vector<uint8_t> &data);
void tag_off(NfcTag &tag) override;
void tag_on(NfcTag &tag) override;
protected:
bool match_tag_name_{false};
std::string match_string_;
std::vector<uint8_t> uid_;
};
} // namespace nfc
} // namespace esphome

View file

@ -66,5 +66,19 @@ bool mifare_classic_is_trailer_block(uint8_t block_num);
uint32_t get_mifare_ultralight_buffer_size(uint32_t message_length);
class NfcTagListener {
public:
virtual void tag_off(NfcTag &tag) {}
virtual void tag_on(NfcTag &tag) {}
};
class Nfcc {
public:
void register_listener(NfcTagListener *listener) { this->tag_listeners_.push_back(listener); }
protected:
std::vector<NfcTagListener *> tag_listeners_;
};
} // namespace nfc
} // namespace esphome

View file

@ -195,7 +195,7 @@ void PMSX003Component::send_command_(uint8_t cmd, uint16_t data) {
void PMSX003Component::parse_data_() {
switch (this->type_) {
case PMSX003_TYPE_5003ST: {
float temperature = this->get_16_bit_uint_(30) / 10.0f;
float temperature = (int16_t) this->get_16_bit_uint_(30) / 10.0f;
float humidity = this->get_16_bit_uint_(32) / 10.0f;
ESP_LOGD(TAG, "Got Temperature: %.1f°C, Humidity: %.1f%%", temperature, humidity);
@ -279,7 +279,7 @@ void PMSX003Component::parse_data_() {
// Note the pm particles 50um & 100um are not returned,
// as PMS5003T uses those data values for temperature and humidity.
float temperature = this->get_16_bit_uint_(24) / 10.0f;
float temperature = (int16_t) this->get_16_bit_uint_(24) / 10.0f;
float humidity = this->get_16_bit_uint_(26) / 10.0f;
ESP_LOGD(TAG,

View file

@ -34,7 +34,7 @@ CONF_TAG_TTL = "tag_ttl"
CONF_VEN_PIN = "ven_pin"
pn7150_ns = cg.esphome_ns.namespace("pn7150")
PN7150 = pn7150_ns.class_("PN7150", cg.Component)
PN7150 = pn7150_ns.class_("PN7150", nfc.Nfcc, cg.Component)
EmulationOffAction = pn7150_ns.class_("EmulationOffAction", automation.Action)
EmulationOnAction = pn7150_ns.class_("EmulationOnAction", automation.Action)

View file

@ -566,6 +566,9 @@ void PN7150::erase_tag_(const uint8_t tag_index) {
for (auto *trigger : this->triggers_ontagremoved_) {
trigger->process(this->discovered_endpoint_[tag_index].tag);
}
for (auto *listener : this->tag_listeners_) {
listener->tag_off(*this->discovered_endpoint_[tag_index].tag);
}
ESP_LOGI(TAG, "Tag %s removed", nfc::format_uid(this->discovered_endpoint_[tag_index].tag->get_uid()).c_str());
this->discovered_endpoint_.erase(this->discovered_endpoint_.begin() + tag_index);
}
@ -881,6 +884,9 @@ void PN7150::process_rf_intf_activated_oid_(nfc::NciMessage &rx) { // an endpoi
for (auto *trigger : this->triggers_ontag_) {
trigger->process(working_endpoint.tag);
}
for (auto *listener : this->tag_listeners_) {
listener->tag_on(*working_endpoint.tag);
}
working_endpoint.trig_called = true;
break;
}

View file

@ -142,7 +142,7 @@ struct DiscoveredEndpoint {
bool trig_called;
};
class PN7150 : public Component {
class PN7150 : public nfc::Nfcc, public Component {
public:
void setup() override;
void dump_config() override;

View file

@ -36,7 +36,7 @@ CONF_VEN_PIN = "ven_pin"
CONF_WKUP_REQ_PIN = "wkup_req_pin"
pn7160_ns = cg.esphome_ns.namespace("pn7160")
PN7160 = pn7160_ns.class_("PN7160", cg.Component)
PN7160 = pn7160_ns.class_("PN7160", nfc.Nfcc, cg.Component)
EmulationOffAction = pn7160_ns.class_("EmulationOffAction", automation.Action)
EmulationOnAction = pn7160_ns.class_("EmulationOnAction", automation.Action)

View file

@ -591,6 +591,9 @@ void PN7160::erase_tag_(const uint8_t tag_index) {
for (auto *trigger : this->triggers_ontagremoved_) {
trigger->process(this->discovered_endpoint_[tag_index].tag);
}
for (auto *listener : this->tag_listeners_) {
listener->tag_off(*this->discovered_endpoint_[tag_index].tag);
}
ESP_LOGI(TAG, "Tag %s removed", nfc::format_uid(this->discovered_endpoint_[tag_index].tag->get_uid()).c_str());
this->discovered_endpoint_.erase(this->discovered_endpoint_.begin() + tag_index);
}
@ -905,6 +908,9 @@ void PN7160::process_rf_intf_activated_oid_(nfc::NciMessage &rx) { // an endpoi
for (auto *trigger : this->triggers_ontag_) {
trigger->process(working_endpoint.tag);
}
for (auto *listener : this->tag_listeners_) {
listener->tag_on(*working_endpoint.tag);
}
working_endpoint.trig_called = true;
break;
}

View file

@ -157,7 +157,7 @@ struct DiscoveredEndpoint {
bool trig_called;
};
class PN7160 : public Component {
class PN7160 : public nfc::Nfcc, public Component {
public:
void setup() override;
void dump_config() override;

View file

@ -352,7 +352,7 @@ void SEN5XComponent::update() {
float humidity = measurements[4] / 100.0;
if (measurements[4] == 0xFFFF)
humidity = NAN;
float temperature = measurements[5] / 200.0;
float temperature = (int16_t) measurements[5] / 200.0;
if (measurements[5] == 0xFFFF)
temperature = NAN;
float voc = measurements[6] / 10.0;

View file

@ -86,6 +86,13 @@ class BSDSocketImpl : public Socket {
}
int listen(int backlog) override { return ::listen(fd_, backlog); }
ssize_t read(void *buf, size_t len) override { return ::read(fd_, buf, len); }
ssize_t recvfrom(void *buf, size_t len, sockaddr *addr, socklen_t *addr_len) override {
#if defined(USE_ESP32)
return ::recvfrom(this->fd_, buf, len, 0, addr, addr_len);
#else
return ::lwip_recvfrom(this->fd_, buf, len, 0, addr, addr_len);
#endif
}
ssize_t readv(const struct iovec *iov, int iovcnt) override {
#if defined(USE_ESP32)
return ::lwip_readv(fd_, iov, iovcnt);

View file

@ -31,6 +31,9 @@ class Socket {
virtual int setsockopt(int level, int optname, const void *optval, socklen_t optlen) = 0;
virtual int listen(int backlog) = 0;
virtual ssize_t read(void *buf, size_t len) = 0;
#ifdef USE_SOCKET_IMPL_BSD_SOCKETS
virtual ssize_t recvfrom(void *buf, size_t len, sockaddr *addr, socklen_t *addr_len) = 0;
#endif
virtual ssize_t readv(const struct iovec *iov, int iovcnt) = 0;
virtual ssize_t write(const void *buf, size_t len) = 0;
virtual ssize_t writev(const struct iovec *iov, int iovcnt) = 0;

View file

View file

@ -0,0 +1,84 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.const import (
CONF_GAIN,
CONF_INTEGRATION_TIME,
DEVICE_CLASS_ILLUMINANCE,
STATE_CLASS_MEASUREMENT,
UNIT_LUX,
)
CODEOWNERS = ["@kbx81"]
DEPENDENCIES = ["i2c"]
CONF_AUTO_GAIN = "auto_gain"
CONF_AUTO_GAIN_THRESHOLD_HIGH = "auto_gain_threshold_high"
CONF_AUTO_GAIN_THRESHOLD_LOW = "auto_gain_threshold_low"
CONF_DIGITAL_GAIN = "digital_gain"
veml3235_ns = cg.esphome_ns.namespace("veml3235")
VEML3235Sensor = veml3235_ns.class_(
"VEML3235Sensor", sensor.Sensor, cg.PollingComponent, i2c.I2CDevice
)
VEML3235IntegrationTime = veml3235_ns.enum("VEML3235IntegrationTime")
VEML3235_INTEGRATION_TIMES = {
"50ms": VEML3235IntegrationTime.VEML3235_INTEGRATION_TIME_50MS,
"100ms": VEML3235IntegrationTime.VEML3235_INTEGRATION_TIME_100MS,
"200ms": VEML3235IntegrationTime.VEML3235_INTEGRATION_TIME_200MS,
"400ms": VEML3235IntegrationTime.VEML3235_INTEGRATION_TIME_400MS,
"800ms": VEML3235IntegrationTime.VEML3235_INTEGRATION_TIME_800MS,
}
VEML3235ComponentDigitalGain = veml3235_ns.enum("VEML3235ComponentDigitalGain")
DIGITAL_GAINS = {
"1X": VEML3235ComponentDigitalGain.VEML3235_DIGITAL_GAIN_1X,
"2X": VEML3235ComponentDigitalGain.VEML3235_DIGITAL_GAIN_2X,
}
VEML3235ComponentGain = veml3235_ns.enum("VEML3235ComponentGain")
GAINS = {
"1X": VEML3235ComponentGain.VEML3235_GAIN_1X,
"2X": VEML3235ComponentGain.VEML3235_GAIN_2X,
"4X": VEML3235ComponentGain.VEML3235_GAIN_4X,
"AUTO": VEML3235ComponentGain.VEML3235_GAIN_AUTO,
}
CONFIG_SCHEMA = (
sensor.sensor_schema(
VEML3235Sensor,
unit_of_measurement=UNIT_LUX,
accuracy_decimals=1,
device_class=DEVICE_CLASS_ILLUMINANCE,
state_class=STATE_CLASS_MEASUREMENT,
)
.extend(
{
cv.Optional(CONF_DIGITAL_GAIN, default="1X"): cv.enum(
DIGITAL_GAINS, upper=True
),
cv.Optional(CONF_AUTO_GAIN, default=True): cv.boolean,
cv.Optional(CONF_AUTO_GAIN_THRESHOLD_HIGH, default="90%"): cv.percentage,
cv.Optional(CONF_AUTO_GAIN_THRESHOLD_LOW, default="20%"): cv.percentage,
cv.Optional(CONF_GAIN, default="1X"): cv.enum(GAINS, upper=True),
cv.Optional(CONF_INTEGRATION_TIME, default="50ms"): cv.All(
cv.positive_time_period_milliseconds,
cv.enum(VEML3235_INTEGRATION_TIMES, lower=True),
),
}
)
.extend(cv.polling_component_schema("60s"))
.extend(i2c.i2c_device_schema(0x10))
)
async def to_code(config):
var = await sensor.new_sensor(config)
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
cg.add(var.set_auto_gain(config[CONF_AUTO_GAIN]))
cg.add(var.set_auto_gain_threshold_high(config[CONF_AUTO_GAIN_THRESHOLD_HIGH]))
cg.add(var.set_auto_gain_threshold_low(config[CONF_AUTO_GAIN_THRESHOLD_LOW]))
cg.add(var.set_digital_gain(DIGITAL_GAINS[config[CONF_DIGITAL_GAIN]]))
cg.add(var.set_gain(GAINS[config[CONF_GAIN]]))
cg.add(var.set_integration_time(config[CONF_INTEGRATION_TIME]))

View file

@ -0,0 +1,230 @@
#include "veml3235.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace veml3235 {
static const char *const TAG = "veml3235.sensor";
void VEML3235Sensor::setup() {
uint8_t device_id[] = {0, 0};
ESP_LOGCONFIG(TAG, "Setting up VEML3235 '%s'...", this->name_.c_str());
if (!this->refresh_config_reg()) {
ESP_LOGE(TAG, "Unable to write configuration");
this->mark_failed();
return;
}
if ((this->write(&ID_REG, 1, false) != i2c::ERROR_OK) || !this->read_bytes_raw(device_id, 2)) {
ESP_LOGE(TAG, "Unable to read ID");
this->mark_failed();
return;
} else if (device_id[0] != DEVICE_ID) {
ESP_LOGE(TAG, "Incorrect device ID - expected 0x%.2x, read 0x%.2x", DEVICE_ID, device_id[0]);
this->mark_failed();
return;
}
}
bool VEML3235Sensor::refresh_config_reg(bool force_on) {
uint16_t data = this->power_on_ || force_on ? 0 : SHUTDOWN_BITS;
data |= (uint16_t(this->integration_time_ << CONFIG_REG_IT_BIT));
data |= (uint16_t(this->digital_gain_ << CONFIG_REG_DG_BIT));
data |= (uint16_t(this->gain_ << CONFIG_REG_G_BIT));
data |= 0x1; // mandatory 1 here per RM
ESP_LOGVV(TAG, "Writing 0x%.4x to register 0x%.2x", data, CONFIG_REG);
return this->write_byte_16(CONFIG_REG, data);
}
float VEML3235Sensor::read_lx_() {
if (!this->power_on_) { // if off, turn on
if (!this->refresh_config_reg(true)) {
ESP_LOGW(TAG, "Turning on failed");
this->status_set_warning();
return NAN;
}
delay(4); // from RM: a wait time of 4 ms should be observed before the first measurement is picked up, to allow
// for a correct start of the signal processor and oscillator
}
uint8_t als_regs[] = {0, 0};
if ((this->write(&ALS_REG, 1, false) != i2c::ERROR_OK) || !this->read_bytes_raw(als_regs, 2)) {
this->status_set_warning();
return NAN;
}
this->status_clear_warning();
float als_raw_value_multiplier = LUX_MULTIPLIER_BASE;
uint16_t als_raw_value = encode_uint16(als_regs[1], als_regs[0]);
// determine multiplier value based on gains and integration time
if (this->digital_gain_ == VEML3235_DIGITAL_GAIN_1X) {
als_raw_value_multiplier *= 2;
}
switch (this->gain_) {
case VEML3235_GAIN_1X:
als_raw_value_multiplier *= 4;
break;
case VEML3235_GAIN_2X:
als_raw_value_multiplier *= 2;
break;
default:
break;
}
switch (this->integration_time_) {
case VEML3235_INTEGRATION_TIME_50MS:
als_raw_value_multiplier *= 16;
break;
case VEML3235_INTEGRATION_TIME_100MS:
als_raw_value_multiplier *= 8;
break;
case VEML3235_INTEGRATION_TIME_200MS:
als_raw_value_multiplier *= 4;
break;
case VEML3235_INTEGRATION_TIME_400MS:
als_raw_value_multiplier *= 2;
break;
default:
break;
}
// finally, determine and return the actual lux value
float lx = float(als_raw_value) * als_raw_value_multiplier;
ESP_LOGVV(TAG, "'%s': ALS raw = %u, multiplier = %.5f", this->get_name().c_str(), als_raw_value,
als_raw_value_multiplier);
ESP_LOGD(TAG, "'%s': Illuminance = %.4flx", this->get_name().c_str(), lx);
if (!this->power_on_) { // turn off if required
if (!this->refresh_config_reg()) {
ESP_LOGW(TAG, "Turning off failed");
this->status_set_warning();
}
}
if (this->auto_gain_) {
this->adjust_gain_(als_raw_value);
}
return lx;
}
void VEML3235Sensor::adjust_gain_(const uint16_t als_raw_value) {
if ((als_raw_value > UINT16_MAX * this->auto_gain_threshold_low_) &&
(als_raw_value < UINT16_MAX * this->auto_gain_threshold_high_)) {
return;
}
if (als_raw_value >= UINT16_MAX * 0.9) { // over-saturated, reset all gains and start over
this->digital_gain_ = VEML3235_DIGITAL_GAIN_1X;
this->gain_ = VEML3235_GAIN_1X;
this->integration_time_ = VEML3235_INTEGRATION_TIME_50MS;
this->refresh_config_reg();
return;
}
if (this->gain_ != VEML3235_GAIN_4X) { // increase gain if possible
switch (this->gain_) {
case VEML3235_GAIN_1X:
this->gain_ = VEML3235_GAIN_2X;
break;
case VEML3235_GAIN_2X:
this->gain_ = VEML3235_GAIN_4X;
break;
default:
break;
}
this->refresh_config_reg();
return;
}
// gain is maxed out; reset it and try to increase digital gain
if (this->digital_gain_ != VEML3235_DIGITAL_GAIN_2X) { // increase digital gain if possible
this->digital_gain_ = VEML3235_DIGITAL_GAIN_2X;
this->gain_ = VEML3235_GAIN_1X;
this->refresh_config_reg();
return;
}
// digital gain is maxed out; reset it and try to increase integration time
if (this->integration_time_ != VEML3235_INTEGRATION_TIME_800MS) { // increase integration time if possible
switch (this->integration_time_) {
case VEML3235_INTEGRATION_TIME_50MS:
this->integration_time_ = VEML3235_INTEGRATION_TIME_100MS;
break;
case VEML3235_INTEGRATION_TIME_100MS:
this->integration_time_ = VEML3235_INTEGRATION_TIME_200MS;
break;
case VEML3235_INTEGRATION_TIME_200MS:
this->integration_time_ = VEML3235_INTEGRATION_TIME_400MS;
break;
case VEML3235_INTEGRATION_TIME_400MS:
this->integration_time_ = VEML3235_INTEGRATION_TIME_800MS;
break;
default:
break;
}
this->digital_gain_ = VEML3235_DIGITAL_GAIN_1X;
this->gain_ = VEML3235_GAIN_1X;
this->refresh_config_reg();
return;
}
}
void VEML3235Sensor::dump_config() {
uint8_t digital_gain = 1;
uint8_t gain = 1;
uint16_t integration_time = 0;
if (this->digital_gain_ == VEML3235_DIGITAL_GAIN_2X) {
digital_gain = 2;
}
switch (this->gain_) {
case VEML3235_GAIN_2X:
gain = 2;
break;
case VEML3235_GAIN_4X:
gain = 4;
break;
default:
break;
}
switch (this->integration_time_) {
case VEML3235_INTEGRATION_TIME_50MS:
integration_time = 50;
break;
case VEML3235_INTEGRATION_TIME_100MS:
integration_time = 100;
break;
case VEML3235_INTEGRATION_TIME_200MS:
integration_time = 200;
break;
case VEML3235_INTEGRATION_TIME_400MS:
integration_time = 400;
break;
case VEML3235_INTEGRATION_TIME_800MS:
integration_time = 800;
break;
default:
break;
}
LOG_SENSOR("", "VEML3235", this);
LOG_I2C_DEVICE(this);
if (this->is_failed()) {
ESP_LOGE(TAG, "Communication failed");
}
LOG_UPDATE_INTERVAL(this);
ESP_LOGCONFIG(TAG, " Auto-gain enabled: %s", YESNO(this->auto_gain_));
if (this->auto_gain_) {
ESP_LOGCONFIG(TAG, " Auto-gain upper threshold: %f%%", this->auto_gain_threshold_high_ * 100.0);
ESP_LOGCONFIG(TAG, " Auto-gain lower threshold: %f%%", this->auto_gain_threshold_low_ * 100.0);
ESP_LOGCONFIG(TAG, " Values below will be used as initial values only");
}
ESP_LOGCONFIG(TAG, " Digital gain: %uX", digital_gain);
ESP_LOGCONFIG(TAG, " Gain: %uX", gain);
ESP_LOGCONFIG(TAG, " Integration time: %ums", integration_time);
}
} // namespace veml3235
} // namespace esphome

View file

@ -0,0 +1,109 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
namespace esphome {
namespace veml3235 {
// Register IDs/locations
//
static const uint8_t CONFIG_REG = 0x00;
static const uint8_t W_REG = 0x04;
static const uint8_t ALS_REG = 0x05;
static const uint8_t ID_REG = 0x09;
// Bit offsets within CONFIG_REG
//
static const uint8_t CONFIG_REG_IT_BIT = 12;
static const uint8_t CONFIG_REG_DG_BIT = 5;
static const uint8_t CONFIG_REG_G_BIT = 3;
// Other important constants
//
static const uint8_t DEVICE_ID = 0x35;
static const uint16_t SHUTDOWN_BITS = 0x0018;
// Base multiplier value for lux computation
//
static const float LUX_MULTIPLIER_BASE = 0.00213;
// Enum for conversion/integration time settings for the VEML3235.
//
// Specific values of the enum constants are register values taken from the VEML3235 datasheet.
// Longer times mean more accurate results, but will take more energy/more time.
//
enum VEML3235ComponentIntegrationTime {
VEML3235_INTEGRATION_TIME_50MS = 0b000,
VEML3235_INTEGRATION_TIME_100MS = 0b001,
VEML3235_INTEGRATION_TIME_200MS = 0b010,
VEML3235_INTEGRATION_TIME_400MS = 0b011,
VEML3235_INTEGRATION_TIME_800MS = 0b100,
};
// Enum for digital gain settings for the VEML3235.
// Higher values are better for low light situations, but can increase noise.
//
enum VEML3235ComponentDigitalGain {
VEML3235_DIGITAL_GAIN_1X = 0b0,
VEML3235_DIGITAL_GAIN_2X = 0b1,
};
// Enum for gain settings for the VEML3235.
// Higher values are better for low light situations, but can increase noise.
//
enum VEML3235ComponentGain {
VEML3235_GAIN_1X = 0b00,
VEML3235_GAIN_2X = 0b01,
VEML3235_GAIN_4X = 0b11,
};
class VEML3235Sensor : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice {
public:
void setup() override;
void dump_config() override;
void update() override { this->publish_state(this->read_lx_()); }
float get_setup_priority() const override { return setup_priority::DATA; }
// Used by ESPHome framework. Does NOT actually set the value on the device.
void set_auto_gain(bool auto_gain) { this->auto_gain_ = auto_gain; }
void set_auto_gain_threshold_high(float auto_gain_threshold_high) {
this->auto_gain_threshold_high_ = auto_gain_threshold_high;
}
void set_auto_gain_threshold_low(float auto_gain_threshold_low) {
this->auto_gain_threshold_low_ = auto_gain_threshold_low;
}
void set_power_on(bool power_on) { this->power_on_ = power_on; }
void set_digital_gain(VEML3235ComponentDigitalGain digital_gain) { this->digital_gain_ = digital_gain; }
void set_gain(VEML3235ComponentGain gain) { this->gain_ = gain; }
void set_integration_time(VEML3235ComponentIntegrationTime integration_time) {
this->integration_time_ = integration_time;
}
bool auto_gain() { return this->auto_gain_; }
float auto_gain_threshold_high() { return this->auto_gain_threshold_high_; }
float auto_gain_threshold_low() { return this->auto_gain_threshold_low_; }
VEML3235ComponentDigitalGain digital_gain() { return this->digital_gain_; }
VEML3235ComponentGain gain() { return this->gain_; }
VEML3235ComponentIntegrationTime integration_time() { return this->integration_time_; }
// Updates the configuration register on the device
bool refresh_config_reg(bool force_on = false);
protected:
float read_lx_();
void adjust_gain_(uint16_t als_raw_value);
bool auto_gain_{true};
bool power_on_{true};
float auto_gain_threshold_high_{0.9};
float auto_gain_threshold_low_{0.2};
VEML3235ComponentDigitalGain digital_gain_{VEML3235_DIGITAL_GAIN_1X};
VEML3235ComponentGain gain_{VEML3235_GAIN_1X};
VEML3235ComponentIntegrationTime integration_time_{VEML3235_INTEGRATION_TIME_50MS};
};
} // namespace veml3235
} // namespace esphome

View file

@ -86,14 +86,14 @@ void VoiceAssistant::setup() {
#ifdef USE_ESP_ADF
this->vad_instance_ = vad_create(VAD_MODE_4);
#endif
this->ring_buffer_ = rb_create(BUFFER_SIZE, sizeof(int16_t));
this->ring_buffer_ = RingBuffer::create(BUFFER_SIZE * sizeof(int16_t));
if (this->ring_buffer_ == nullptr) {
ESP_LOGW(TAG, "Could not allocate ring buffer");
this->mark_failed();
return;
}
#endif
ExternalRAMAllocator<uint8_t> send_allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
this->send_buffer_ = send_allocator.allocate(SEND_BUFFER_SIZE);
@ -112,14 +112,8 @@ int VoiceAssistant::read_microphone_() {
memset(this->input_buffer_, 0, INPUT_BUFFER_SIZE * sizeof(int16_t));
return 0;
}
#ifdef USE_ESP_ADF
// Write audio into ring buffer
int available = rb_bytes_available(this->ring_buffer_);
if (available < bytes_read) {
rb_read(this->ring_buffer_, nullptr, bytes_read - available, 0);
}
rb_write(this->ring_buffer_, (char *) this->input_buffer_, bytes_read, 0);
#endif
this->ring_buffer_->write((void *) this->input_buffer_, bytes_read);
} else {
ESP_LOGD(TAG, "microphone not running");
}
@ -141,9 +135,9 @@ void VoiceAssistant::loop() {
switch (this->state_) {
case State::IDLE: {
if (this->continuous_ && this->desired_state_ == State::IDLE) {
this->ring_buffer_->reset();
#ifdef USE_ESP_ADF
if (this->use_wake_word_) {
rb_reset(this->ring_buffer_);
this->set_state_(State::START_MICROPHONE, State::WAIT_FOR_VAD);
} else
#endif
@ -236,19 +230,13 @@ void VoiceAssistant::loop() {
break; // State changed when udp server port received
}
case State::STREAMING_MICROPHONE: {
size_t bytes_read = this->read_microphone_();
#ifdef USE_ESP_ADF
if (rb_bytes_filled(this->ring_buffer_) >= SEND_BUFFER_SIZE) {
rb_read(this->ring_buffer_, (char *) this->send_buffer_, SEND_BUFFER_SIZE, 0);
this->read_microphone_();
if (this->ring_buffer_->available() >= SEND_BUFFER_SIZE) {
this->ring_buffer_->read((void *) this->send_buffer_, SEND_BUFFER_SIZE, 0);
this->socket_->sendto(this->send_buffer_, SEND_BUFFER_SIZE, 0, (struct sockaddr *) &this->dest_addr_,
sizeof(this->dest_addr_));
}
#else
if (bytes_read > 0) {
this->socket_->sendto(this->input_buffer_, bytes_read, 0, (struct sockaddr *) &this->dest_addr_,
sizeof(this->dest_addr_));
}
#endif
break;
}
case State::STOP_MICROPHONE: {
@ -473,9 +461,9 @@ void VoiceAssistant::request_start(bool continuous, bool silence_detection) {
if (this->state_ == State::IDLE) {
this->continuous_ = continuous;
this->silence_detection_ = silence_detection;
this->ring_buffer_->reset();
#ifdef USE_ESP_ADF
if (this->use_wake_word_) {
rb_reset(this->ring_buffer_);
this->set_state_(State::START_MICROPHONE, State::WAIT_FOR_VAD);
} else
#endif
@ -618,9 +606,9 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) {
case api::enums::VOICE_ASSISTANT_RUN_END: {
ESP_LOGD(TAG, "Assist Pipeline ended");
if (this->state_ == State::STREAMING_MICROPHONE) {
this->ring_buffer_->reset();
#ifdef USE_ESP_ADF
if (this->use_wake_word_) {
rb_reset(this->ring_buffer_);
// No need to stop the microphone since we didn't use the speaker
this->set_state_(State::WAIT_FOR_VAD, State::WAITING_FOR_VAD);
} else

View file

@ -7,6 +7,7 @@
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include "esphome/core/ring_buffer.h"
#include "esphome/components/api/api_connection.h"
#include "esphome/components/api/api_pb2.h"
@ -21,7 +22,6 @@
#ifdef USE_ESP_ADF
#include <esp_vad.h>
#include <ringbuf.h>
#endif
namespace esphome {
@ -177,10 +177,10 @@ class VoiceAssistant : public Component {
#ifdef USE_ESP_ADF
vad_handle_t vad_instance_;
ringbuf_handle_t ring_buffer_;
uint8_t vad_threshold_{5};
uint8_t vad_counter_{0};
#endif
std::unique_ptr<RingBuffer> ring_buffer_;
bool use_wake_word_;
uint8_t noise_suppression_level_;

View file

@ -2004,15 +2004,19 @@ def suppress_invalid():
pass
GIT_SCHEMA = {
Required(CONF_URL): url,
Optional(CONF_REF): git_ref,
Optional(CONF_USERNAME): string,
Optional(CONF_PASSWORD): string,
}
LOCAL_SCHEMA = {
Required(CONF_PATH): directory,
}
GIT_SCHEMA = Schema(
{
Required(CONF_URL): url,
Optional(CONF_REF): git_ref,
Optional(CONF_USERNAME): string,
Optional(CONF_PASSWORD): string,
}
)
LOCAL_SCHEMA = Schema(
{
Required(CONF_PATH): directory,
}
)
def validate_source_shorthand(value):
@ -2053,8 +2057,8 @@ SOURCE_SCHEMA = Any(
validate_source_shorthand,
typed_schema(
{
TYPE_GIT: Schema(GIT_SCHEMA),
TYPE_LOCAL: Schema(LOCAL_SCHEMA),
TYPE_GIT: GIT_SCHEMA,
TYPE_LOCAL: LOCAL_SCHEMA,
}
),
)

View file

@ -510,6 +510,8 @@ CONF_ON_ENROLLMENT_FAILED = "on_enrollment_failed"
CONF_ON_ENROLLMENT_SCAN = "on_enrollment_scan"
CONF_ON_FINGER_SCAN_INVALID = "on_finger_scan_invalid"
CONF_ON_FINGER_SCAN_MATCHED = "on_finger_scan_matched"
CONF_ON_FINGER_SCAN_MISPLACED = "on_finger_scan_misplaced"
CONF_ON_FINGER_SCAN_START = "on_finger_scan_start"
CONF_ON_FINGER_SCAN_UNMATCHED = "on_finger_scan_unmatched"
CONF_ON_JSON_MESSAGE = "on_json_message"
CONF_ON_LOCK = "on_lock"

View file

@ -0,0 +1,49 @@
#include "ring_buffer.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#ifdef USE_ESP32
#include "helpers.h"
namespace esphome {
static const char *const TAG = "ring_buffer";
std::unique_ptr<RingBuffer> RingBuffer::create(size_t len) {
std::unique_ptr<RingBuffer> rb = make_unique<RingBuffer>();
ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
rb->storage_ = allocator.allocate(len);
if (rb->storage_ == nullptr) {
return nullptr;
}
rb->handle_ = xStreamBufferCreateStatic(len, 0, rb->storage_, &rb->structure_);
return rb;
}
size_t RingBuffer::read(void *data, size_t size, TickType_t ticks_to_wait) {
return xStreamBufferReceive(this->handle_, data, size, ticks_to_wait);
}
size_t RingBuffer::write(void *data, size_t len) {
size_t free = this->free();
if (free < len) {
size_t needed = len - free;
uint8_t discard[needed];
xStreamBufferReceive(this->handle_, discard, needed, 0);
}
return xStreamBufferSend(this->handle_, data, len, 0);
}
size_t RingBuffer::available() const { return xStreamBufferBytesAvailable(this->handle_); }
size_t RingBuffer::free() const { return xStreamBufferSpacesAvailable(this->handle_); }
BaseType_t RingBuffer::reset() { return xStreamBufferReset(this->handle_); }
} // namespace esphome
#endif

View file

@ -0,0 +1,34 @@
#pragma once
#ifdef USE_ESP32
#include <freertos/FreeRTOS.h>
#include <freertos/stream_buffer.h>
#include <cinttypes>
#include <memory>
namespace esphome {
class RingBuffer {
public:
size_t read(void *data, size_t size, TickType_t ticks_to_wait = 0);
size_t write(void *data, size_t len);
size_t available() const;
size_t free() const;
BaseType_t reset();
static std::unique_ptr<RingBuffer> create(size_t len);
protected:
StreamBufferHandle_t handle_;
StaticStreamBuffer_t structure_;
uint8_t *storage_;
};
} // namespace esphome
#endif

View file

@ -1,36 +1,37 @@
from __future__ import annotations
import fnmatch
import functools
import inspect
import logging
import math
import os
import uuid
from typing import Any
import yaml
import yaml.constructor
from yaml import SafeLoader as PurePythonLoader
try:
from yaml import CSafeLoader as FastestAvailableSafeLoader
except ImportError:
FastestAvailableSafeLoader = PurePythonLoader
from esphome import core
from esphome.config_helpers import read_config_file, Extend, Remove
from esphome.config_helpers import Extend, Remove, read_config_file
from esphome.core import (
CORE,
DocumentRange,
EsphomeError,
IPAddress,
Lambda,
MACAddress,
TimePeriod,
DocumentRange,
CORE,
)
from esphome.helpers import add_class_to_obj
from esphome.util import OrderedDict, filter_yaml_files
try:
from yaml import CSafeLoader as FastestAvailableSafeLoader
except ImportError:
from yaml import ( # type: ignore[assignment]
SafeLoader as FastestAvailableSafeLoader,
)
_LOGGER = logging.getLogger(__name__)
# Mostly copied from Home Assistant because that code works fine and
@ -97,7 +98,7 @@ def _add_data_ref(fn):
return wrapped
class ESPHomeLoader(FastestAvailableSafeLoader):
class ESPHomeLoaderMixin:
"""Loader class that keeps track of line numbers."""
@_add_data_ref
@ -282,8 +283,8 @@ class ESPHomeLoader(FastestAvailableSafeLoader):
return file, vars
def substitute_vars(config, vars):
from esphome.const import CONF_SUBSTITUTIONS, CONF_DEFAULTS
from esphome.components import substitutions
from esphome.const import CONF_DEFAULTS, CONF_SUBSTITUTIONS
org_subs = None
result = config
@ -375,50 +376,64 @@ class ESPHomeLoader(FastestAvailableSafeLoader):
return Remove(str(node.value))
ESPHomeLoader.add_constructor("tag:yaml.org,2002:int", ESPHomeLoader.construct_yaml_int)
ESPHomeLoader.add_constructor(
"tag:yaml.org,2002:float", ESPHomeLoader.construct_yaml_float
)
ESPHomeLoader.add_constructor(
"tag:yaml.org,2002:binary", ESPHomeLoader.construct_yaml_binary
)
ESPHomeLoader.add_constructor(
"tag:yaml.org,2002:omap", ESPHomeLoader.construct_yaml_omap
)
ESPHomeLoader.add_constructor("tag:yaml.org,2002:str", ESPHomeLoader.construct_yaml_str)
ESPHomeLoader.add_constructor("tag:yaml.org,2002:seq", ESPHomeLoader.construct_yaml_seq)
ESPHomeLoader.add_constructor("tag:yaml.org,2002:map", ESPHomeLoader.construct_yaml_map)
ESPHomeLoader.add_constructor("!env_var", ESPHomeLoader.construct_env_var)
ESPHomeLoader.add_constructor("!secret", ESPHomeLoader.construct_secret)
ESPHomeLoader.add_constructor("!include", ESPHomeLoader.construct_include)
ESPHomeLoader.add_constructor(
"!include_dir_list", ESPHomeLoader.construct_include_dir_list
)
ESPHomeLoader.add_constructor(
"!include_dir_merge_list", ESPHomeLoader.construct_include_dir_merge_list
)
ESPHomeLoader.add_constructor(
"!include_dir_named", ESPHomeLoader.construct_include_dir_named
)
ESPHomeLoader.add_constructor(
"!include_dir_merge_named", ESPHomeLoader.construct_include_dir_merge_named
)
ESPHomeLoader.add_constructor("!lambda", ESPHomeLoader.construct_lambda)
ESPHomeLoader.add_constructor("!force", ESPHomeLoader.construct_force)
ESPHomeLoader.add_constructor("!extend", ESPHomeLoader.construct_extend)
ESPHomeLoader.add_constructor("!remove", ESPHomeLoader.construct_remove)
class ESPHomeLoader(ESPHomeLoaderMixin, FastestAvailableSafeLoader):
"""Loader class that keeps track of line numbers."""
def load_yaml(fname, clear_secrets=True):
class ESPHomePurePythonLoader(ESPHomeLoaderMixin, PurePythonLoader):
"""Loader class that keeps track of line numbers."""
for _loader in (ESPHomeLoader, ESPHomePurePythonLoader):
_loader.add_constructor("tag:yaml.org,2002:int", _loader.construct_yaml_int)
_loader.add_constructor("tag:yaml.org,2002:float", _loader.construct_yaml_float)
_loader.add_constructor("tag:yaml.org,2002:binary", _loader.construct_yaml_binary)
_loader.add_constructor("tag:yaml.org,2002:omap", _loader.construct_yaml_omap)
_loader.add_constructor("tag:yaml.org,2002:str", _loader.construct_yaml_str)
_loader.add_constructor("tag:yaml.org,2002:seq", _loader.construct_yaml_seq)
_loader.add_constructor("tag:yaml.org,2002:map", _loader.construct_yaml_map)
_loader.add_constructor("!env_var", _loader.construct_env_var)
_loader.add_constructor("!secret", _loader.construct_secret)
_loader.add_constructor("!include", _loader.construct_include)
_loader.add_constructor("!include_dir_list", _loader.construct_include_dir_list)
_loader.add_constructor(
"!include_dir_merge_list", _loader.construct_include_dir_merge_list
)
_loader.add_constructor("!include_dir_named", _loader.construct_include_dir_named)
_loader.add_constructor(
"!include_dir_merge_named", _loader.construct_include_dir_merge_named
)
_loader.add_constructor("!lambda", _loader.construct_lambda)
_loader.add_constructor("!force", _loader.construct_force)
_loader.add_constructor("!extend", _loader.construct_extend)
_loader.add_constructor("!remove", _loader.construct_remove)
def load_yaml(fname: str, clear_secrets: bool = True) -> Any:
if clear_secrets:
_SECRET_VALUES.clear()
_SECRET_CACHE.clear()
return _load_yaml_internal(fname)
def _load_yaml_internal(fname):
def _load_yaml_internal(fname: str) -> Any:
"""Load a YAML file."""
content = read_config_file(fname)
loader = ESPHomeLoader(content)
try:
return _load_yaml_internal_with_type(ESPHomeLoader, fname, content)
except EsphomeError:
# Loading failed, so we now load with the Python loader which has more
# readable exceptions
return _load_yaml_internal_with_type(ESPHomePurePythonLoader, fname, content)
def _load_yaml_internal_with_type(
loader_type: type[ESPHomeLoader] | type[ESPHomePurePythonLoader],
fname: str,
content: str,
) -> Any:
"""Load a YAML file."""
loader = loader_type(content)
loader.name = fname
try:
return loader.get_single_data() or OrderedDict()

View file

@ -1,3 +1,3 @@
pillow==10.1.0
pillow==10.2.0
cairosvg==2.7.1
cryptography==41.0.4

View file

@ -4,10 +4,13 @@
set -e
cd "$(dirname "$0")/.."
location="venv/bin/activate"
if [ ! -n "$DEVCONTAINER" ] && [ ! -n "$VIRTUAL_ENV" ] && [ ! "$ESPHOME_NO_VENV" ]; then
python3 -m venv venv
source venv/bin/activate
if [ -f venv/Scripts/activate ]; then
location="venv/Scripts/activate"
fi
source $location;
fi
# Avoid unsafe git error when running inside devcontainer
@ -25,4 +28,4 @@ script/platformio_install_deps.py platformio.ini --libraries --tools --platforms
echo
echo
echo "Virtual environment created; source venv/bin/activate to use it"
echo "Virtual environment created. Run 'source $location' to use it."

View file

@ -1370,6 +1370,16 @@ sensor:
name: tsl2591 calculated_lux
id: tsl2591_cl
i2c_id: i2c_bus
- platform: veml3235
id: veml3235_sensor
name: VEML3235 Light Sensor
i2c_id: i2c_bus
auto_gain: true
auto_gain_threshold_high: 90%
auto_gain_threshold_low: 15%
digital_gain: 1X
gain: 1X
integration_time: 50ms
- platform: tee501
name: Office Temperature 3
address: 0x48
@ -2028,6 +2038,18 @@ binary_sensor:
- platform: dfrobot_sen0395
id: mmwave_detected_uart
dfrobot_sen0395_id: mmwave
- platform: nfc
nfcc_id: nfcc_pn7160_i2c
ndef_contains: pulse
name: MFC Tag 1
- platform: nfc
nfcc_id: nfcc_pn7160_i2c
tag_id: pulse
name: MFC Tag 2
- platform: nfc
nfcc_id: nfcc_pn7160_i2c
uid: 59-FC-AB-15
name: MFC Tag 3
pca9685:
frequency: 500
@ -3461,6 +3483,7 @@ pn532_i2c:
i2c_id: i2c_bus
pn7150_i2c:
id: nfcc_pn7150_i2c
i2c_id: i2c_bus
irq_pin:
allow_other_uses: true

View file

@ -1258,6 +1258,9 @@ fingerprint_grow:
number: 4
password: 0x12FE37DC
new_password: 0xA65B9840
on_finger_scan_start:
- homeassistant.event:
event: esphome.${device_name}_fingerprint_grow_finger_scan_start
on_finger_scan_invalid:
- homeassistant.event:
event: esphome.${device_name}_fingerprint_grow_finger_scan_invalid
@ -1270,6 +1273,9 @@ fingerprint_grow:
on_finger_scan_unmatched:
- homeassistant.event:
event: esphome.${device_name}_fingerprint_grow_finger_scan_unmatched
on_finger_scan_misplaced:
- homeassistant.event:
event: esphome.${device_name}_fingerprint_grow_finger_scan_misplaced
on_enrollment_scan:
- homeassistant.event:
event: esphome.${device_name}_fingerprint_grow_enrollment_scan

View file

@ -0,0 +1,18 @@
---
substitutions:
name: original
wifi: !include
file: includes/broken_included.yaml.txt
vars:
name: my_custom_ssid
esphome:
# should be substituted as 'original',
# not overwritten by vars in the !include above
name: ${name}
name_add_mac_suffix: true
platform: esp8266
board: !include {file: includes/scalar.yaml, vars: {var1: nodemcu}}
libraries: !include {file: includes/list.yaml, vars: {var1: Wire}}

View file

@ -0,0 +1,5 @@
---
# yamllint disable-line
ssid: ${name}
# yamllint disable-line
fdf: error

View file

@ -1,5 +1,6 @@
from esphome import yaml_util
from esphome.components import substitutions
from esphome.core import EsphomeError
def test_include_with_vars(fixture_path):
@ -11,3 +12,13 @@ def test_include_with_vars(fixture_path):
assert actual["esphome"]["libraries"][0] == "Wire"
assert actual["esphome"]["board"] == "nodemcu"
assert actual["wifi"]["ssid"] == "my_custom_ssid"
def test_loading_a_broken_yaml_file(fixture_path):
"""Ensure we fallback to pure python to give good errors."""
yaml_file = fixture_path / "yaml_util" / "broken_includetest.yaml"
try:
yaml_util.load_yaml(yaml_file)
except EsphomeError as err:
assert "broken_included.yaml" in str(err)