Merge pull request #2691 from esphome/bump-2021.11.0b1

2021.11.0b1
This commit is contained in:
Jesse Hills 2021-11-11 11:05:45 +13:00 committed by GitHub
commit 9f4519210f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
217 changed files with 5271 additions and 1297 deletions

View file

@ -16,6 +16,7 @@ Quick description and explanation of changes
## Test Environment ## Test Environment
- [ ] ESP32 - [ ] ESP32
- [ ] ESP32 IDF
- [ ] ESP8266 - [ ] ESP8266
## Example entry for `config.yaml`: ## Example entry for `config.yaml`:

View file

@ -1,7 +0,0 @@
comment: >-
https://github.com/esphome/esphome/issues/430
issueConfigs:
- content:
- "OTHERWISE THE ISSUE WILL BE CLOSED AUTOMATICALLY"
caseInsensitive: false

36
.github/lock.yml vendored
View file

@ -1,36 +0,0 @@
# Configuration for Lock Threads - https://github.com/dessant/lock-threads
# Number of days of inactivity before a closed issue or pull request is locked
daysUntilLock: 7
# Skip issues and pull requests created before a given timestamp. Timestamp must
# follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable
skipCreatedBefore: false
# Issues and pull requests with these labels will be ignored. Set to `[]` to disable
exemptLabels:
- keep-open
# Label to add before locking, such as `outdated`. Set to `false` to disable
lockLabel: false
# Comment to post before locking. Set to `false` to disable
lockComment: false
# Assign `resolved` as the reason for locking. Set to `false` to disable
setLockReason: false
# Limit to only `issues` or `pulls`
# only: issues
# Optionally, specify configuration settings just for `issues` or `pulls`
# issues:
# exemptLabels:
# - help-wanted
# lockLabel: outdated
# pulls:
# daysUntilLock: 30
# Repository to extend settings from
# _extends: repo

View file

@ -17,6 +17,10 @@ on:
- 'requirements*.txt' - 'requirements*.txt'
- 'platformio.ini' - 'platformio.ini'
permissions:
contents: read
packages: read
jobs: jobs:
check-docker: check-docker:
name: Build docker containers name: Build docker containers

View file

@ -8,6 +8,9 @@ on:
pull_request: pull_request:
permissions:
contents: read
jobs: jobs:
ci: ci:
name: ${{ matrix.name }} name: ${{ matrix.name }}

View file

@ -9,13 +9,19 @@ permissions:
issues: write issues: write
pull-requests: write pull-requests: write
concurrency:
group: lock
jobs: jobs:
lock: lock:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: dessant/lock-threads@v2 - uses: dessant/lock-threads@v3
with: with:
github-token: ${{ github.token }} pr-inactive-days: "1"
pr-lock-inactive-days: "1"
pr-lock-reason: "" pr-lock-reason: ""
process-only: prs exclude-any-pr-labels: keep-open
issue-inactive-days: "7"
issue-lock-reason: ""
exclude-any-issue-labels: keep-open

View file

@ -7,6 +7,9 @@ on:
schedule: schedule:
- cron: "0 2 * * *" - cron: "0 2 * * *"
permissions:
contents: read
jobs: jobs:
init: init:
name: Initialize build name: Initialize build
@ -52,6 +55,9 @@ jobs:
deploy-docker: deploy-docker:
name: Build and publish docker containers name: Build and publish docker containers
if: github.repository == 'esphome/esphome' if: github.repository == 'esphome/esphome'
permissions:
contents: read
packages: write
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [init] needs: [init]
strategy: strategy:
@ -93,6 +99,9 @@ jobs:
deploy-docker-manifest: deploy-docker-manifest:
if: github.repository == 'esphome/esphome' if: github.repository == 'esphome/esphome'
permissions:
contents: read
packages: write
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [init, deploy-docker] needs: [init, deploy-docker]
strategy: strategy:

View file

@ -9,13 +9,15 @@ permissions:
issues: write issues: write
pull-requests: write pull-requests: write
concurrency:
group: lock
jobs: jobs:
stale: stale:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/stale@v4 - uses: actions/stale@v4
with: with:
repo-token: ${{ github.token }}
days-before-pr-stale: 90 days-before-pr-stale: 90
days-before-pr-close: 7 days-before-pr-close: 7
days-before-issue-stale: -1 days-before-issue-stale: -1
@ -28,3 +30,19 @@ jobs:
pull request has been automatically marked as stale because of that pull request has been automatically marked as stale because of that
and will be closed if no further activity occurs within 7 days. and will be closed if no further activity occurs within 7 days.
Thank you for your contributions. Thank you for your contributions.
# Use stale to automatically close issues with a reference to the issue tracker
close-issues:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v4
with:
days-before-pr-stale: -1
days-before-pr-close: -1
days-before-issue-stale: 1
days-before-issue-close: 1
remove-stale-when-updated: true
stale-issue-label: "stale"
exempt-issue-labels: "not-stale"
stale-issue-message: >
https://github.com/esphome/esphome/issues/430

View file

@ -31,6 +31,7 @@ esphome/components/binary_sensor/* @esphome/core
esphome/components/ble_client/* @buxtronix esphome/components/ble_client/* @buxtronix
esphome/components/bme680_bsec/* @trvrnrth esphome/components/bme680_bsec/* @trvrnrth
esphome/components/canbus/* @danielschramm @mvturnho esphome/components/canbus/* @danielschramm @mvturnho
esphome/components/cap1188/* @MrEditor97
esphome/components/captive_portal/* @OttoWinter esphome/components/captive_portal/* @OttoWinter
esphome/components/ccs811/* @habbie esphome/components/ccs811/* @habbie
esphome/components/climate/* @esphome/core esphome/components/climate/* @esphome/core
@ -39,6 +40,7 @@ esphome/components/color_temperature/* @jesserockz
esphome/components/coolix/* @glmnet esphome/components/coolix/* @glmnet
esphome/components/cover/* @esphome/core esphome/components/cover/* @esphome/core
esphome/components/cs5460a/* @balrog-kun esphome/components/cs5460a/* @balrog-kun
esphome/components/cse7761/* @berfenger
esphome/components/ct_clamp/* @jesserockz esphome/components/ct_clamp/* @jesserockz
esphome/components/current_based/* @djwmarcx esphome/components/current_based/* @djwmarcx
esphome/components/daly_bms/* @s1lvi0 esphome/components/daly_bms/* @s1lvi0
@ -51,6 +53,7 @@ esphome/components/dsmr/* @glmnet @zuidwijk
esphome/components/esp32/* @esphome/core esphome/components/esp32/* @esphome/core
esphome/components/esp32_ble/* @jesserockz esphome/components/esp32_ble/* @jesserockz
esphome/components/esp32_ble_server/* @jesserockz esphome/components/esp32_ble_server/* @jesserockz
esphome/components/esp32_camera_web_server/* @ayufan
esphome/components/esp32_improv/* @jesserockz esphome/components/esp32_improv/* @jesserockz
esphome/components/esp8266/* @esphome/core esphome/components/esp8266/* @esphome/core
esphome/components/exposure_notifications/* @OttoWinter esphome/components/exposure_notifications/* @OttoWinter
@ -70,6 +73,7 @@ esphome/components/homeassistant/* @OttoWinter
esphome/components/hrxl_maxsonar_wr/* @netmikey esphome/components/hrxl_maxsonar_wr/* @netmikey
esphome/components/i2c/* @esphome/core esphome/components/i2c/* @esphome/core
esphome/components/improv/* @jesserockz esphome/components/improv/* @jesserockz
esphome/components/improv_serial/* @esphome/core
esphome/components/inkbird_ibsth1_mini/* @fkirill esphome/components/inkbird_ibsth1_mini/* @fkirill
esphome/components/inkplate6/* @jesserockz esphome/components/inkplate6/* @jesserockz
esphome/components/integration/* @OttoWinter esphome/components/integration/* @OttoWinter

View file

@ -5,12 +5,12 @@
# One of "docker", "hassio" # One of "docker", "hassio"
ARG BASEIMGTYPE=docker ARG BASEIMGTYPE=docker
FROM ghcr.io/hassio-addons/debian-base/amd64:5.1.0 AS base-hassio-amd64 FROM ghcr.io/hassio-addons/debian-base/amd64:5.1.1 AS base-hassio-amd64
FROM ghcr.io/hassio-addons/debian-base/aarch64:5.1.0 AS base-hassio-arm64 FROM ghcr.io/hassio-addons/debian-base/aarch64:5.1.1 AS base-hassio-arm64
FROM ghcr.io/hassio-addons/debian-base/armv7:5.1.0 AS base-hassio-armv7 FROM ghcr.io/hassio-addons/debian-base/armv7:5.1.1 AS base-hassio-armv7
FROM debian:bullseye-20210902-slim AS base-docker-amd64 FROM debian:bullseye-20211011-slim AS base-docker-amd64
FROM debian:bullseye-20210902-slim AS base-docker-arm64 FROM debian:bullseye-20211011-slim AS base-docker-arm64
FROM debian:bullseye-20210902-slim AS base-docker-armv7 FROM debian:bullseye-20211011-slim AS base-docker-armv7
# Use TARGETARCH/TARGETVARIANT defined by docker # Use TARGETARCH/TARGETVARIANT defined by docker
# https://docs.docker.com/engine/reference/builder/#automatic-platform-args-in-the-global-scope # https://docs.docker.com/engine/reference/builder/#automatic-platform-args-in-the-global-scope
@ -43,7 +43,7 @@ RUN \
# Ubuntu python3-pip is missing wheel # Ubuntu python3-pip is missing wheel
pip3 install --no-cache-dir \ pip3 install --no-cache-dir \
wheel==0.36.2 \ wheel==0.36.2 \
platformio==5.2.1 \ platformio==5.2.2 \
# Change some platformio settings # Change some platformio settings
&& platformio settings set enable_telemetry No \ && platformio settings set enable_telemetry No \
&& platformio settings set check_libraries_interval 1000000 \ && platformio settings set check_libraries_interval 1000000 \

View file

@ -3,6 +3,7 @@ import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_AUTOMATION_ID, CONF_AUTOMATION_ID,
CONF_CONDITION, CONF_CONDITION,
CONF_COUNT,
CONF_ELSE, CONF_ELSE,
CONF_ID, CONF_ID,
CONF_THEN, CONF_THEN,
@ -66,6 +67,7 @@ DelayAction = cg.esphome_ns.class_("DelayAction", Action, cg.Component)
LambdaAction = cg.esphome_ns.class_("LambdaAction", Action) LambdaAction = cg.esphome_ns.class_("LambdaAction", Action)
IfAction = cg.esphome_ns.class_("IfAction", Action) IfAction = cg.esphome_ns.class_("IfAction", Action)
WhileAction = cg.esphome_ns.class_("WhileAction", Action) WhileAction = cg.esphome_ns.class_("WhileAction", Action)
RepeatAction = cg.esphome_ns.class_("RepeatAction", Action)
WaitUntilAction = cg.esphome_ns.class_("WaitUntilAction", Action, cg.Component) WaitUntilAction = cg.esphome_ns.class_("WaitUntilAction", Action, cg.Component)
UpdateComponentAction = cg.esphome_ns.class_("UpdateComponentAction", Action) UpdateComponentAction = cg.esphome_ns.class_("UpdateComponentAction", Action)
Automation = cg.esphome_ns.class_("Automation") Automation = cg.esphome_ns.class_("Automation")
@ -241,6 +243,25 @@ async def while_action_to_code(config, action_id, template_arg, args):
return var return var
@register_action(
"repeat",
RepeatAction,
cv.Schema(
{
cv.Required(CONF_COUNT): cv.templatable(cv.positive_not_null_int),
cv.Required(CONF_THEN): validate_action_list,
}
),
)
async def repeat_action_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
count_template = await cg.templatable(config[CONF_COUNT], args, cg.uint32)
cg.add(var.set_count(count_template))
actions = await build_action_list(config[CONF_THEN], template_arg, args)
cg.add(var.add_then(actions))
return var
def validate_wait_until(value): def validate_wait_until(value):
schema = cv.Schema( schema = cv.Schema(
{ {

View file

@ -81,4 +81,5 @@ from esphome.cpp_types import ( # noqa
GPIOPin, GPIOPin,
InternalGPIOPin, InternalGPIOPin,
gpio_Flags, gpio_Flags,
EntityCategory,
) )

View file

@ -1,5 +1,6 @@
#include "adc_sensor.h" #include "adc_sensor.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/helpers.h"
#ifdef USE_ESP8266 #ifdef USE_ESP8266
#ifdef USE_ADC_SENSOR_VCC #ifdef USE_ADC_SENSOR_VCC
@ -15,50 +16,6 @@ namespace adc {
static const char *const TAG = "adc"; static const char *const TAG = "adc";
#ifdef USE_ESP32
void ADCSensor::set_attenuation(adc_atten_t attenuation) { this->attenuation_ = attenuation; }
inline adc1_channel_t gpio_to_adc1(uint8_t pin) {
#if CONFIG_IDF_TARGET_ESP32
switch (pin) {
case 36:
return ADC1_CHANNEL_0;
case 37:
return ADC1_CHANNEL_1;
case 38:
return ADC1_CHANNEL_2;
case 39:
return ADC1_CHANNEL_3;
case 32:
return ADC1_CHANNEL_4;
case 33:
return ADC1_CHANNEL_5;
case 34:
return ADC1_CHANNEL_6;
case 35:
return ADC1_CHANNEL_7;
default:
return ADC1_CHANNEL_MAX;
}
#elif CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32H2
switch (pin) {
case 0:
return ADC1_CHANNEL_0;
case 1:
return ADC1_CHANNEL_1;
case 2:
return ADC1_CHANNEL_2;
case 3:
return ADC1_CHANNEL_3;
case 4:
return ADC1_CHANNEL_4;
default:
return ADC1_CHANNEL_MAX;
}
#endif
}
#endif
void ADCSensor::setup() { void ADCSensor::setup() {
ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str()); ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str());
#ifndef USE_ADC_SENSOR_VCC #ifndef USE_ADC_SENSOR_VCC
@ -66,13 +23,36 @@ void ADCSensor::setup() {
#endif #endif
#ifdef USE_ESP32 #ifdef USE_ESP32
adc1_config_channel_atten(gpio_to_adc1(pin_->get_pin()), attenuation_);
adc1_config_width(ADC_WIDTH_BIT_12); adc1_config_width(ADC_WIDTH_BIT_12);
#if !CONFIG_IDF_TARGET_ESP32C3 && !CONFIG_IDF_TARGET_ESP32H2 if (!autorange_) {
adc_gpio_init(ADC_UNIT_1, (adc_channel_t) gpio_to_adc1(pin_->get_pin())); adc1_config_channel_atten(channel_, attenuation_);
#endif }
// load characteristics for each attenuation
for (int i = 0; i < (int) ADC_ATTEN_MAX; i++) {
auto cal_value = esp_adc_cal_characterize(ADC_UNIT_1, (adc_atten_t) i, ADC_WIDTH_BIT_12,
1100, // default vref
&cal_characteristics_[i]);
switch (cal_value) {
case ESP_ADC_CAL_VAL_EFUSE_VREF:
ESP_LOGV(TAG, "Using eFuse Vref for calibration");
break;
case ESP_ADC_CAL_VAL_EFUSE_TP:
ESP_LOGV(TAG, "Using two-point eFuse Vref for calibration");
break;
case ESP_ADC_CAL_VAL_DEFAULT_VREF:
default:
break;
}
}
// adc_gpio_init doesn't exist on ESP32-C3 or ESP32-H2
#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32H2)
adc_gpio_init(ADC_UNIT_1, (adc_channel_t) channel_);
#endif #endif
#endif // USE_ESP32
} }
void ADCSensor::dump_config() { void ADCSensor::dump_config() {
LOG_SENSOR("", "ADC Sensor", this); LOG_SENSOR("", "ADC Sensor", this);
#ifdef USE_ESP8266 #ifdef USE_ESP8266
@ -81,9 +61,13 @@ void ADCSensor::dump_config() {
#else #else
LOG_PIN(" Pin: ", pin_); LOG_PIN(" Pin: ", pin_);
#endif #endif
#endif #endif // USE_ESP8266
#ifdef USE_ESP32 #ifdef USE_ESP32
LOG_PIN(" Pin: ", pin_); LOG_PIN(" Pin: ", pin_);
if (autorange_)
ESP_LOGCONFIG(TAG, " Attenuation: auto");
else
switch (this->attenuation_) { switch (this->attenuation_) {
case ADC_ATTEN_DB_0: case ADC_ATTEN_DB_0:
ESP_LOGCONFIG(TAG, " Attenuation: 0db (max 1.1V)"); ESP_LOGCONFIG(TAG, " Attenuation: 0db (max 1.1V)");
@ -100,65 +84,84 @@ void ADCSensor::dump_config() {
default: // This is to satisfy the unused ADC_ATTEN_MAX default: // This is to satisfy the unused ADC_ATTEN_MAX
break; break;
} }
#endif #endif // USE_ESP32
LOG_UPDATE_INTERVAL(this); LOG_UPDATE_INTERVAL(this);
} }
float ADCSensor::get_setup_priority() const { return setup_priority::DATA; } float ADCSensor::get_setup_priority() const { return setup_priority::DATA; }
void ADCSensor::update() { void ADCSensor::update() {
float value_v = this->sample(); float value_v = this->sample();
ESP_LOGD(TAG, "'%s': Got voltage=%.2fV", this->get_name().c_str(), value_v); ESP_LOGD(TAG, "'%s': Got voltage=%.4fV", this->get_name().c_str(), value_v);
this->publish_state(value_v); this->publish_state(value_v);
} }
float ADCSensor::sample() {
#ifdef USE_ESP32
int raw = adc1_get_raw(gpio_to_adc1(pin_->get_pin()));
float value_v = raw / 4095.0f;
#if CONFIG_IDF_TARGET_ESP32
switch (this->attenuation_) {
case ADC_ATTEN_DB_0:
value_v *= 1.1;
break;
case ADC_ATTEN_DB_2_5:
value_v *= 1.5;
break;
case ADC_ATTEN_DB_6:
value_v *= 2.2;
break;
case ADC_ATTEN_DB_11:
value_v *= 3.9;
break;
default: // This is to satisfy the unused ADC_ATTEN_MAX
break;
}
#elif CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32H2
switch (this->attenuation_) {
case ADC_ATTEN_DB_0:
value_v *= 0.84;
break;
case ADC_ATTEN_DB_2_5:
value_v *= 1.13;
break;
case ADC_ATTEN_DB_6:
value_v *= 1.56;
break;
case ADC_ATTEN_DB_11:
value_v *= 3.0;
break;
default: // This is to satisfy the unused ADC_ATTEN_MAX
break;
}
#endif
return value_v;
#endif
#ifdef USE_ESP8266 #ifdef USE_ESP8266
float ADCSensor::sample() {
#ifdef USE_ADC_SENSOR_VCC #ifdef USE_ADC_SENSOR_VCC
return ESP.getVcc() / 1024.0f; // NOLINT(readability-static-accessed-through-instance) int raw = ESP.getVcc(); // NOLINT(readability-static-accessed-through-instance)
#else #else
return analogRead(this->pin_->get_pin()) / 1024.0f; // NOLINT int raw = analogRead(this->pin_->get_pin()); // NOLINT
#endif
#endif #endif
if (output_raw_) {
return raw;
}
return raw / 1024.0f;
} }
#endif
#ifdef USE_ESP32
float ADCSensor::sample() {
if (!autorange_) {
int raw = adc1_get_raw(channel_);
if (raw == -1) {
return NAN;
}
if (output_raw_) {
return raw;
}
uint32_t mv = esp_adc_cal_raw_to_voltage(raw, &cal_characteristics_[(int) attenuation_]);
return mv / 1000.0f;
}
int raw11, raw6 = 4095, raw2 = 4095, raw0 = 4095;
adc1_config_channel_atten(channel_, ADC_ATTEN_DB_11);
raw11 = adc1_get_raw(channel_);
if (raw11 < 4095) {
adc1_config_channel_atten(channel_, ADC_ATTEN_DB_6);
raw6 = adc1_get_raw(channel_);
if (raw6 < 4095) {
adc1_config_channel_atten(channel_, ADC_ATTEN_DB_2_5);
raw2 = adc1_get_raw(channel_);
if (raw2 < 4095) {
adc1_config_channel_atten(channel_, ADC_ATTEN_DB_0);
raw0 = adc1_get_raw(channel_);
}
}
}
if (raw0 == -1 || raw2 == -1 || raw6 == -1 || raw11 == -1) {
return NAN;
}
uint32_t mv11 = esp_adc_cal_raw_to_voltage(raw11, &cal_characteristics_[(int) ADC_ATTEN_DB_11]);
uint32_t mv6 = esp_adc_cal_raw_to_voltage(raw6, &cal_characteristics_[(int) ADC_ATTEN_DB_6]);
uint32_t mv2 = esp_adc_cal_raw_to_voltage(raw2, &cal_characteristics_[(int) ADC_ATTEN_DB_2_5]);
uint32_t mv0 = esp_adc_cal_raw_to_voltage(raw0, &cal_characteristics_[(int) ADC_ATTEN_DB_0]);
// Contribution of each value, in range 0-2048
uint32_t c11 = std::min(raw11, 2048);
uint32_t c6 = 2048 - std::abs(raw6 - 2048);
uint32_t c2 = 2048 - std::abs(raw2 - 2048);
uint32_t c0 = std::min(4095 - raw0, 2048);
// max theoretical csum value is 2048*4 = 8192
uint32_t csum = c11 + c6 + c2 + c0;
// each mv is max 3900; so max value is 3900*2048*4, fits in unsigned
uint32_t mv_scaled = (mv11 * c11) + (mv6 * c6) + (mv2 * c2) + (mv0 * c0);
return mv_scaled / (float) (csum * 1000U);
}
#endif // USE_ESP32
#ifdef USE_ESP8266 #ifdef USE_ESP8266
std::string ADCSensor::unique_id() { return get_mac_address() + "-adc"; } std::string ADCSensor::unique_id() { return get_mac_address() + "-adc"; }
#endif #endif

View file

@ -8,6 +8,7 @@
#ifdef USE_ESP32 #ifdef USE_ESP32
#include "driver/adc.h" #include "driver/adc.h"
#include <esp_adc_cal.h>
#endif #endif
namespace esphome { namespace esphome {
@ -17,7 +18,9 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
public: public:
#ifdef USE_ESP32 #ifdef USE_ESP32
/// Set the attenuation for this pin. Only available on the ESP32. /// Set the attenuation for this pin. Only available on the ESP32.
void set_attenuation(adc_atten_t attenuation); void set_attenuation(adc_atten_t attenuation) { attenuation_ = attenuation; }
void set_channel(adc1_channel_t channel) { channel_ = channel; }
void set_autorange(bool autorange) { autorange_ = autorange; }
#endif #endif
/// Update adc values. /// Update adc values.
@ -28,6 +31,7 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
/// `HARDWARE_LATE` setup priority. /// `HARDWARE_LATE` setup priority.
float get_setup_priority() const override; float get_setup_priority() const override;
void set_pin(InternalGPIOPin *pin) { this->pin_ = pin; } void set_pin(InternalGPIOPin *pin) { this->pin_ = pin; }
void set_output_raw(bool output_raw) { output_raw_ = output_raw; }
float sample() override; float sample() override;
#ifdef USE_ESP8266 #ifdef USE_ESP8266
@ -36,9 +40,13 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
protected: protected:
InternalGPIOPin *pin_; InternalGPIOPin *pin_;
bool output_raw_{false};
#ifdef USE_ESP32 #ifdef USE_ESP32
adc_atten_t attenuation_{ADC_ATTEN_DB_0}; adc_atten_t attenuation_{ADC_ATTEN_DB_0};
adc1_channel_t channel_{};
bool autorange_{false};
esp_adc_cal_characteristics_t cal_characteristics_[(int) ADC_ATTEN_MAX] = {};
#endif #endif
}; };

View file

@ -4,14 +4,24 @@ from esphome import pins
from esphome.components import sensor, voltage_sampler from esphome.components import sensor, voltage_sampler
from esphome.const import ( from esphome.const import (
CONF_ATTENUATION, CONF_ATTENUATION,
CONF_RAW,
CONF_ID, CONF_ID,
CONF_INPUT, CONF_INPUT,
CONF_NUMBER,
CONF_PIN, CONF_PIN,
DEVICE_CLASS_VOLTAGE, DEVICE_CLASS_VOLTAGE,
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
UNIT_VOLT, UNIT_VOLT,
) )
from esphome.core import CORE from esphome.core import CORE
from esphome.components.esp32 import get_esp32_variant
from esphome.components.esp32.const import (
VARIANT_ESP32,
VARIANT_ESP32C3,
VARIANT_ESP32H2,
VARIANT_ESP32S2,
VARIANT_ESP32S3,
)
AUTO_LOAD = ["voltage_sampler"] AUTO_LOAD = ["voltage_sampler"]
@ -21,6 +31,62 @@ ATTENUATION_MODES = {
"2.5db": cg.global_ns.ADC_ATTEN_DB_2_5, "2.5db": cg.global_ns.ADC_ATTEN_DB_2_5,
"6db": cg.global_ns.ADC_ATTEN_DB_6, "6db": cg.global_ns.ADC_ATTEN_DB_6,
"11db": cg.global_ns.ADC_ATTEN_DB_11, "11db": cg.global_ns.ADC_ATTEN_DB_11,
"auto": "auto",
}
adc1_channel_t = cg.global_ns.enum("adc1_channel_t")
# From https://github.com/espressif/esp-idf/blob/master/components/driver/include/driver/adc_common.h
# pin to adc1 channel mapping
ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
VARIANT_ESP32: {
36: adc1_channel_t.ADC1_CHANNEL_0,
37: adc1_channel_t.ADC1_CHANNEL_1,
38: adc1_channel_t.ADC1_CHANNEL_2,
39: adc1_channel_t.ADC1_CHANNEL_3,
32: adc1_channel_t.ADC1_CHANNEL_4,
33: adc1_channel_t.ADC1_CHANNEL_5,
34: adc1_channel_t.ADC1_CHANNEL_6,
35: adc1_channel_t.ADC1_CHANNEL_7,
},
VARIANT_ESP32S2: {
1: adc1_channel_t.ADC1_CHANNEL_0,
2: adc1_channel_t.ADC1_CHANNEL_1,
3: adc1_channel_t.ADC1_CHANNEL_2,
4: adc1_channel_t.ADC1_CHANNEL_3,
5: adc1_channel_t.ADC1_CHANNEL_4,
6: adc1_channel_t.ADC1_CHANNEL_5,
7: adc1_channel_t.ADC1_CHANNEL_6,
8: adc1_channel_t.ADC1_CHANNEL_7,
9: adc1_channel_t.ADC1_CHANNEL_8,
10: adc1_channel_t.ADC1_CHANNEL_9,
},
VARIANT_ESP32S3: {
1: adc1_channel_t.ADC1_CHANNEL_0,
2: adc1_channel_t.ADC1_CHANNEL_1,
3: adc1_channel_t.ADC1_CHANNEL_2,
4: adc1_channel_t.ADC1_CHANNEL_3,
5: adc1_channel_t.ADC1_CHANNEL_4,
6: adc1_channel_t.ADC1_CHANNEL_5,
7: adc1_channel_t.ADC1_CHANNEL_6,
8: adc1_channel_t.ADC1_CHANNEL_7,
9: adc1_channel_t.ADC1_CHANNEL_8,
10: adc1_channel_t.ADC1_CHANNEL_9,
},
VARIANT_ESP32C3: {
0: adc1_channel_t.ADC1_CHANNEL_0,
1: adc1_channel_t.ADC1_CHANNEL_1,
2: adc1_channel_t.ADC1_CHANNEL_2,
3: adc1_channel_t.ADC1_CHANNEL_3,
4: adc1_channel_t.ADC1_CHANNEL_4,
},
VARIANT_ESP32H2: {
0: adc1_channel_t.ADC1_CHANNEL_0,
1: adc1_channel_t.ADC1_CHANNEL_1,
2: adc1_channel_t.ADC1_CHANNEL_2,
3: adc1_channel_t.ADC1_CHANNEL_3,
4: adc1_channel_t.ADC1_CHANNEL_4,
},
} }
@ -29,15 +95,16 @@ def validate_adc_pin(value):
return cv.only_on_esp8266("VCC") return cv.only_on_esp8266("VCC")
if CORE.is_esp32: if CORE.is_esp32:
from esphome.components.esp32 import is_esp32c3
value = pins.internal_gpio_input_pin_number(value) value = pins.internal_gpio_input_pin_number(value)
if is_esp32c3(): variant = get_esp32_variant()
if not (0 <= value <= 4): # ADC1 if variant not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL:
raise cv.Invalid("ESP32-C3: Only pins 0 though 4 support ADC.") raise cv.Invalid(f"This ESP32 variant ({variant}) is not supported")
elif not (32 <= value <= 39): # ADC1
raise cv.Invalid("ESP32: Only pins 32 though 39 support ADC.") if value not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant]:
elif CORE.is_esp8266: raise cv.Invalid(f"{variant} doesn't support ADC on this pin")
return pins.internal_gpio_input_pin_schema(value)
if CORE.is_esp8266:
from esphome.components.esp8266.gpio import CONF_ANALOG from esphome.components.esp8266.gpio import CONF_ANALOG
value = pins.internal_gpio_pin_number({CONF_ANALOG: True, CONF_INPUT: True})( value = pins.internal_gpio_pin_number({CONF_ANALOG: True, CONF_INPUT: True})(
@ -49,10 +116,14 @@ def validate_adc_pin(value):
return pins.gpio_pin_schema( return pins.gpio_pin_schema(
{CONF_ANALOG: True, CONF_INPUT: True}, internal=True {CONF_ANALOG: True, CONF_INPUT: True}, internal=True
)(value) )(value)
else:
raise NotImplementedError raise NotImplementedError
return pins.internal_gpio_input_pin_schema(value)
def validate_config(config):
if config[CONF_RAW] and config.get(CONF_ATTENUATION, None) == "auto":
raise cv.Invalid("Automatic attenuation cannot be used when raw output is set.")
return config
adc_ns = cg.esphome_ns.namespace("adc") adc_ns = cg.esphome_ns.namespace("adc")
@ -60,7 +131,7 @@ ADCSensor = adc_ns.class_(
"ADCSensor", sensor.Sensor, cg.PollingComponent, voltage_sampler.VoltageSampler "ADCSensor", sensor.Sensor, cg.PollingComponent, voltage_sampler.VoltageSampler
) )
CONFIG_SCHEMA = ( CONFIG_SCHEMA = cv.All(
sensor.sensor_schema( sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT, unit_of_measurement=UNIT_VOLT,
accuracy_decimals=2, accuracy_decimals=2,
@ -71,12 +142,14 @@ CONFIG_SCHEMA = (
{ {
cv.GenerateID(): cv.declare_id(ADCSensor), cv.GenerateID(): cv.declare_id(ADCSensor),
cv.Required(CONF_PIN): validate_adc_pin, cv.Required(CONF_PIN): validate_adc_pin,
cv.Optional(CONF_RAW, default=False): cv.boolean,
cv.SplitDefault(CONF_ATTENUATION, esp32="0db"): cv.All( cv.SplitDefault(CONF_ATTENUATION, esp32="0db"): cv.All(
cv.only_on_esp32, cv.enum(ATTENUATION_MODES, lower=True) cv.only_on_esp32, cv.enum(ATTENUATION_MODES, lower=True)
), ),
} }
) )
.extend(cv.polling_component_schema("60s")) .extend(cv.polling_component_schema("60s")),
validate_config,
) )
@ -91,5 +164,17 @@ async def to_code(config):
pin = await cg.gpio_pin_expression(config[CONF_PIN]) pin = await cg.gpio_pin_expression(config[CONF_PIN])
cg.add(var.set_pin(pin)) cg.add(var.set_pin(pin))
if CONF_RAW in config:
cg.add(var.set_output_raw(config[CONF_RAW]))
if CONF_ATTENUATION in config: if CONF_ATTENUATION in config:
if config[CONF_ATTENUATION] == "auto":
cg.add(var.set_autorange(cg.global_ns.true))
else:
cg.add(var.set_attenuation(config[CONF_ATTENUATION])) cg.add(var.set_attenuation(config[CONF_ATTENUATION]))
if CORE.is_esp32:
variant = get_esp32_variant()
pin_num = config[CONF_PIN][CONF_NUMBER]
chan = ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant][pin_num]
cg.add(var.set_channel(chan))

View file

@ -73,7 +73,7 @@ void AHT10Component::update() {
bool success = false; bool success = false;
for (int i = 0; i < AHT10_ATTEMPTS; ++i) { for (int i = 0; i < AHT10_ATTEMPTS; ++i) {
ESP_LOGVV(TAG, "Attempt %d at %6u", i, millis()); ESP_LOGVV(TAG, "Attempt %d at %6u", i, millis());
delay_microseconds_accurate(4); delayMicroseconds(4);
uint8_t reg = 0; uint8_t reg = 0;
if (this->write(&reg, 1) != i2c::ERROR_OK) { if (this->write(&reg, 1) != i2c::ERROR_OK) {

View file

@ -4,7 +4,8 @@ from esphome.components import sensor, ble_client
from esphome.const import ( from esphome.const import (
CONF_ID, CONF_ID,
CONF_BATTERY_LEVEL, CONF_BATTERY_LEVEL,
ICON_BATTERY, DEVICE_CLASS_BATTERY,
ENTITY_CATEGORY_DIAGNOSTIC,
CONF_ILLUMINANCE, CONF_ILLUMINANCE,
ICON_BRIGHTNESS_5, ICON_BRIGHTNESS_5,
UNIT_PERCENT, UNIT_PERCENT,
@ -20,10 +21,15 @@ CONFIG_SCHEMA = (
{ {
cv.GenerateID(): cv.declare_id(Am43), cv.GenerateID(): cv.declare_id(Am43),
cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(
UNIT_PERCENT, ICON_BATTERY, 0 unit_of_measurement=UNIT_PERCENT,
device_class=DEVICE_CLASS_BATTERY,
accuracy_decimals=0,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
), ),
cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema( cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema(
UNIT_PERCENT, ICON_BRIGHTNESS_5, 0 unit_of_measurement=UNIT_PERCENT,
icon=ICON_BRIGHTNESS_5,
accuracy_decimals=0,
), ),
} }
) )

View file

@ -103,21 +103,21 @@ void AnovaCodec::decode(const uint8_t *data, uint16_t length) {
break; break;
} }
case READ_TARGET_TEMPERATURE: { case READ_TARGET_TEMPERATURE: {
this->target_temp_ = strtof(this->buf_, nullptr); this->target_temp_ = parse_number<float>(this->buf_, sizeof(this->buf_)).value_or(0.0f);
if (this->fahrenheit_) if (this->fahrenheit_)
this->target_temp_ = ftoc(this->target_temp_); this->target_temp_ = ftoc(this->target_temp_);
this->has_target_temp_ = true; this->has_target_temp_ = true;
break; break;
} }
case SET_TARGET_TEMPERATURE: { case SET_TARGET_TEMPERATURE: {
this->target_temp_ = strtof(this->buf_, nullptr); this->target_temp_ = parse_number<float>(this->buf_, sizeof(this->buf_)).value_or(0.0f);
if (this->fahrenheit_) if (this->fahrenheit_)
this->target_temp_ = ftoc(this->target_temp_); this->target_temp_ = ftoc(this->target_temp_);
this->has_target_temp_ = true; this->has_target_temp_ = true;
break; break;
} }
case READ_CURRENT_TEMPERATURE: { case READ_CURRENT_TEMPERATURE: {
this->current_temp_ = strtof(this->buf_, nullptr); this->current_temp_ = parse_number<float>(this->buf_, sizeof(this->buf_)).value_or(0.0f);
if (this->fahrenheit_) if (this->fahrenheit_)
this->current_temp_ = ftoc(this->current_temp_); this->current_temp_ = ftoc(this->current_temp_);
this->has_current_temp_ = true; this->has_current_temp_ = true;

View file

@ -182,6 +182,8 @@ message DeviceInfoResponse {
// The esphome project details if set // The esphome project details if set
string project_name = 8; string project_name = 8;
string project_version = 9; string project_version = 9;
uint32 webserver_port = 10;
} }
message ListEntitiesRequest { message ListEntitiesRequest {
@ -201,6 +203,14 @@ message SubscribeStatesRequest {
// Empty // Empty
} }
// ==================== COMMON =====================
enum EntityCategory {
ENTITY_CATEGORY_NONE = 0;
ENTITY_CATEGORY_CONFIG = 1;
ENTITY_CATEGORY_DIAGNOSTIC = 2;
}
// ==================== BINARY SENSOR ==================== // ==================== BINARY SENSOR ====================
message ListEntitiesBinarySensorResponse { message ListEntitiesBinarySensorResponse {
option (id) = 12; option (id) = 12;
@ -216,6 +226,7 @@ message ListEntitiesBinarySensorResponse {
bool is_status_binary_sensor = 6; bool is_status_binary_sensor = 6;
bool disabled_by_default = 7; bool disabled_by_default = 7;
string icon = 8; string icon = 8;
EntityCategory entity_category = 9;
} }
message BinarySensorStateResponse { message BinarySensorStateResponse {
option (id) = 21; option (id) = 21;
@ -247,6 +258,7 @@ message ListEntitiesCoverResponse {
string device_class = 8; string device_class = 8;
bool disabled_by_default = 9; bool disabled_by_default = 9;
string icon = 10; string icon = 10;
EntityCategory entity_category = 11;
} }
enum LegacyCoverState { enum LegacyCoverState {
@ -316,6 +328,7 @@ message ListEntitiesFanResponse {
int32 supported_speed_count = 8; int32 supported_speed_count = 8;
bool disabled_by_default = 9; bool disabled_by_default = 9;
string icon = 10; string icon = 10;
EntityCategory entity_category = 11;
} }
enum FanSpeed { enum FanSpeed {
FAN_SPEED_LOW = 0; FAN_SPEED_LOW = 0;
@ -392,6 +405,7 @@ message ListEntitiesLightResponse {
repeated string effects = 11; repeated string effects = 11;
bool disabled_by_default = 13; bool disabled_by_default = 13;
string icon = 14; string icon = 14;
EntityCategory entity_category = 15;
} }
message LightStateResponse { message LightStateResponse {
option (id) = 24; option (id) = 24;
@ -480,6 +494,7 @@ message ListEntitiesSensorResponse {
// Last reset type removed in 2021.9.0 // Last reset type removed in 2021.9.0
SensorLastResetType legacy_last_reset_type = 11; SensorLastResetType legacy_last_reset_type = 11;
bool disabled_by_default = 12; bool disabled_by_default = 12;
EntityCategory entity_category = 13;
} }
message SensorStateResponse { message SensorStateResponse {
option (id) = 25; option (id) = 25;
@ -508,6 +523,7 @@ message ListEntitiesSwitchResponse {
string icon = 5; string icon = 5;
bool assumed_state = 6; bool assumed_state = 6;
bool disabled_by_default = 7; bool disabled_by_default = 7;
EntityCategory entity_category = 8;
} }
message SwitchStateResponse { message SwitchStateResponse {
option (id) = 26; option (id) = 26;
@ -541,6 +557,7 @@ message ListEntitiesTextSensorResponse {
string icon = 5; string icon = 5;
bool disabled_by_default = 6; bool disabled_by_default = 6;
EntityCategory entity_category = 7;
} }
message TextSensorStateResponse { message TextSensorStateResponse {
option (id) = 27; option (id) = 27;
@ -701,6 +718,8 @@ message ListEntitiesCameraResponse {
string name = 3; string name = 3;
string unique_id = 4; string unique_id = 4;
bool disabled_by_default = 5; bool disabled_by_default = 5;
string icon = 6;
EntityCategory entity_category = 7;
} }
message CameraImageResponse { message CameraImageResponse {
@ -795,6 +814,7 @@ message ListEntitiesClimateResponse {
repeated string supported_custom_presets = 17; repeated string supported_custom_presets = 17;
bool disabled_by_default = 18; bool disabled_by_default = 18;
string icon = 19; string icon = 19;
EntityCategory entity_category = 20;
} }
message ClimateStateResponse { message ClimateStateResponse {
option (id) = 47; option (id) = 47;
@ -863,6 +883,7 @@ message ListEntitiesNumberResponse {
float max_value = 7; float max_value = 7;
float step = 8; float step = 8;
bool disabled_by_default = 9; bool disabled_by_default = 9;
EntityCategory entity_category = 10;
} }
message NumberStateResponse { message NumberStateResponse {
option (id) = 50; option (id) = 50;
@ -900,6 +921,7 @@ message ListEntitiesSelectResponse {
string icon = 5; string icon = 5;
repeated string options = 6; repeated string options = 6;
bool disabled_by_default = 7; bool disabled_by_default = 7;
EntityCategory entity_category = 8;
} }
message SelectStateResponse { message SelectStateResponse {
option (id) = 53; option (id) = 53;

View file

@ -184,6 +184,7 @@ bool APIConnection::send_binary_sensor_info(binary_sensor::BinarySensor *binary_
msg.is_status_binary_sensor = binary_sensor->is_status_binary_sensor(); msg.is_status_binary_sensor = binary_sensor->is_status_binary_sensor();
msg.disabled_by_default = binary_sensor->is_disabled_by_default(); msg.disabled_by_default = binary_sensor->is_disabled_by_default();
msg.icon = binary_sensor->get_icon(); msg.icon = binary_sensor->get_icon();
msg.entity_category = static_cast<enums::EntityCategory>(binary_sensor->get_entity_category());
return this->send_list_entities_binary_sensor_response(msg); return this->send_list_entities_binary_sensor_response(msg);
} }
#endif #endif
@ -217,6 +218,7 @@ bool APIConnection::send_cover_info(cover::Cover *cover) {
msg.device_class = cover->get_device_class(); msg.device_class = cover->get_device_class();
msg.disabled_by_default = cover->is_disabled_by_default(); msg.disabled_by_default = cover->is_disabled_by_default();
msg.icon = cover->get_icon(); msg.icon = cover->get_icon();
msg.entity_category = static_cast<enums::EntityCategory>(cover->get_entity_category());
return this->send_list_entities_cover_response(msg); return this->send_list_entities_cover_response(msg);
} }
void APIConnection::cover_command(const CoverCommandRequest &msg) { void APIConnection::cover_command(const CoverCommandRequest &msg) {
@ -283,6 +285,7 @@ bool APIConnection::send_fan_info(fan::FanState *fan) {
msg.supported_speed_count = traits.supported_speed_count(); msg.supported_speed_count = traits.supported_speed_count();
msg.disabled_by_default = fan->is_disabled_by_default(); msg.disabled_by_default = fan->is_disabled_by_default();
msg.icon = fan->get_icon(); msg.icon = fan->get_icon();
msg.entity_category = static_cast<enums::EntityCategory>(fan->get_entity_category());
return this->send_list_entities_fan_response(msg); return this->send_list_entities_fan_response(msg);
} }
void APIConnection::fan_command(const FanCommandRequest &msg) { void APIConnection::fan_command(const FanCommandRequest &msg) {
@ -346,6 +349,7 @@ bool APIConnection::send_light_info(light::LightState *light) {
msg.disabled_by_default = light->is_disabled_by_default(); msg.disabled_by_default = light->is_disabled_by_default();
msg.icon = light->get_icon(); msg.icon = light->get_icon();
msg.entity_category = static_cast<enums::EntityCategory>(light->get_entity_category());
for (auto mode : traits.get_supported_color_modes()) for (auto mode : traits.get_supported_color_modes())
msg.supported_color_modes.push_back(static_cast<enums::ColorMode>(mode)); msg.supported_color_modes.push_back(static_cast<enums::ColorMode>(mode));
@ -432,7 +436,7 @@ bool APIConnection::send_sensor_info(sensor::Sensor *sensor) {
msg.device_class = sensor->get_device_class(); msg.device_class = sensor->get_device_class();
msg.state_class = static_cast<enums::SensorStateClass>(sensor->get_state_class()); msg.state_class = static_cast<enums::SensorStateClass>(sensor->get_state_class());
msg.disabled_by_default = sensor->is_disabled_by_default(); msg.disabled_by_default = sensor->is_disabled_by_default();
msg.entity_category = static_cast<enums::EntityCategory>(sensor->get_entity_category());
return this->send_list_entities_sensor_response(msg); return this->send_list_entities_sensor_response(msg);
} }
#endif #endif
@ -456,6 +460,7 @@ bool APIConnection::send_switch_info(switch_::Switch *a_switch) {
msg.icon = a_switch->get_icon(); msg.icon = a_switch->get_icon();
msg.assumed_state = a_switch->assumed_state(); msg.assumed_state = a_switch->assumed_state();
msg.disabled_by_default = a_switch->is_disabled_by_default(); msg.disabled_by_default = a_switch->is_disabled_by_default();
msg.entity_category = static_cast<enums::EntityCategory>(a_switch->get_entity_category());
return this->send_list_entities_switch_response(msg); return this->send_list_entities_switch_response(msg);
} }
void APIConnection::switch_command(const SwitchCommandRequest &msg) { void APIConnection::switch_command(const SwitchCommandRequest &msg) {
@ -491,6 +496,7 @@ bool APIConnection::send_text_sensor_info(text_sensor::TextSensor *text_sensor)
msg.unique_id = get_default_unique_id("text_sensor", text_sensor); msg.unique_id = get_default_unique_id("text_sensor", text_sensor);
msg.icon = text_sensor->get_icon(); msg.icon = text_sensor->get_icon();
msg.disabled_by_default = text_sensor->is_disabled_by_default(); msg.disabled_by_default = text_sensor->is_disabled_by_default();
msg.entity_category = static_cast<enums::EntityCategory>(text_sensor->get_entity_category());
return this->send_list_entities_text_sensor_response(msg); return this->send_list_entities_text_sensor_response(msg);
} }
#endif #endif
@ -537,6 +543,7 @@ bool APIConnection::send_climate_info(climate::Climate *climate) {
msg.disabled_by_default = climate->is_disabled_by_default(); msg.disabled_by_default = climate->is_disabled_by_default();
msg.icon = climate->get_icon(); msg.icon = climate->get_icon();
msg.entity_category = static_cast<enums::EntityCategory>(climate->get_entity_category());
msg.supports_current_temperature = traits.get_supports_current_temperature(); msg.supports_current_temperature = traits.get_supports_current_temperature();
msg.supports_two_point_target_temperature = traits.get_supports_two_point_target_temperature(); msg.supports_two_point_target_temperature = traits.get_supports_two_point_target_temperature();
@ -611,6 +618,7 @@ bool APIConnection::send_number_info(number::Number *number) {
msg.unique_id = get_default_unique_id("number", number); msg.unique_id = get_default_unique_id("number", number);
msg.icon = number->get_icon(); msg.icon = number->get_icon();
msg.disabled_by_default = number->is_disabled_by_default(); msg.disabled_by_default = number->is_disabled_by_default();
msg.entity_category = static_cast<enums::EntityCategory>(number->get_entity_category());
msg.min_value = number->traits.get_min_value(); msg.min_value = number->traits.get_min_value();
msg.max_value = number->traits.get_max_value(); msg.max_value = number->traits.get_max_value();
@ -648,6 +656,7 @@ bool APIConnection::send_select_info(select::Select *select) {
msg.unique_id = get_default_unique_id("select", select); msg.unique_id = get_default_unique_id("select", select);
msg.icon = select->get_icon(); msg.icon = select->get_icon();
msg.disabled_by_default = select->is_disabled_by_default(); msg.disabled_by_default = select->is_disabled_by_default();
msg.entity_category = static_cast<enums::EntityCategory>(select->get_entity_category());
for (const auto &option : select->traits.get_options()) for (const auto &option : select->traits.get_options())
msg.options.push_back(option); msg.options.push_back(option);
@ -680,6 +689,8 @@ bool APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) {
msg.name = camera->get_name(); msg.name = camera->get_name();
msg.unique_id = get_default_unique_id("camera", camera); msg.unique_id = get_default_unique_id("camera", camera);
msg.disabled_by_default = camera->is_disabled_by_default(); msg.disabled_by_default = camera->is_disabled_by_default();
msg.icon = camera->get_icon();
msg.entity_category = static_cast<enums::EntityCategory>(camera->get_entity_category());
return this->send_list_entities_camera_response(msg); return this->send_list_entities_camera_response(msg);
} }
void APIConnection::camera_image(const CameraImageRequest &msg) { void APIConnection::camera_image(const CameraImageRequest &msg) {
@ -758,6 +769,9 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
#ifdef ESPHOME_PROJECT_NAME #ifdef ESPHOME_PROJECT_NAME
resp.project_name = ESPHOME_PROJECT_NAME; resp.project_name = ESPHOME_PROJECT_NAME;
resp.project_version = ESPHOME_PROJECT_VERSION; resp.project_version = ESPHOME_PROJECT_VERSION;
#endif
#ifdef USE_WEBSERVER
resp.webserver_port = WEBSERVER_PORT;
#endif #endif
return resp; return resp;
} }

View file

@ -6,6 +6,18 @@
namespace esphome { namespace esphome {
namespace api { namespace api {
template<> const char *proto_enum_to_string<enums::EntityCategory>(enums::EntityCategory value) {
switch (value) {
case enums::ENTITY_CATEGORY_NONE:
return "ENTITY_CATEGORY_NONE";
case enums::ENTITY_CATEGORY_CONFIG:
return "ENTITY_CATEGORY_CONFIG";
case enums::ENTITY_CATEGORY_DIAGNOSTIC:
return "ENTITY_CATEGORY_DIAGNOSTIC";
default:
return "UNKNOWN";
}
}
template<> const char *proto_enum_to_string<enums::LegacyCoverState>(enums::LegacyCoverState value) { template<> const char *proto_enum_to_string<enums::LegacyCoverState>(enums::LegacyCoverState value) {
switch (value) { switch (value) {
case enums::LEGACY_COVER_STATE_OPEN: case enums::LEGACY_COVER_STATE_OPEN:
@ -396,6 +408,10 @@ bool DeviceInfoResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
this->has_deep_sleep = value.as_bool(); this->has_deep_sleep = value.as_bool();
return true; return true;
} }
case 10: {
this->webserver_port = value.as_uint32();
return true;
}
default: default:
return false; return false;
} }
@ -444,6 +460,7 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(7, this->has_deep_sleep); buffer.encode_bool(7, this->has_deep_sleep);
buffer.encode_string(8, this->project_name); buffer.encode_string(8, this->project_name);
buffer.encode_string(9, this->project_version); buffer.encode_string(9, this->project_version);
buffer.encode_uint32(10, this->webserver_port);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void DeviceInfoResponse::dump_to(std::string &out) const { void DeviceInfoResponse::dump_to(std::string &out) const {
@ -484,6 +501,11 @@ void DeviceInfoResponse::dump_to(std::string &out) const {
out.append(" project_version: "); out.append(" project_version: ");
out.append("'").append(this->project_version).append("'"); out.append("'").append(this->project_version).append("'");
out.append("\n"); out.append("\n");
out.append(" webserver_port: ");
sprintf(buffer, "%u", this->webserver_port);
out.append(buffer);
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@ -509,6 +531,10 @@ bool ListEntitiesBinarySensorResponse::decode_varint(uint32_t field_id, ProtoVar
this->disabled_by_default = value.as_bool(); this->disabled_by_default = value.as_bool();
return true; return true;
} }
case 9: {
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
default: default:
return false; return false;
} }
@ -558,6 +584,7 @@ void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(6, this->is_status_binary_sensor); buffer.encode_bool(6, this->is_status_binary_sensor);
buffer.encode_bool(7, this->disabled_by_default); buffer.encode_bool(7, this->disabled_by_default);
buffer.encode_string(8, this->icon); buffer.encode_string(8, this->icon);
buffer.encode_enum<enums::EntityCategory>(9, this->entity_category);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const { void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const {
@ -595,6 +622,10 @@ void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const {
out.append(" icon: "); out.append(" icon: ");
out.append("'").append(this->icon).append("'"); out.append("'").append(this->icon).append("'");
out.append("\n"); out.append("\n");
out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@ -664,6 +695,10 @@ bool ListEntitiesCoverResponse::decode_varint(uint32_t field_id, ProtoVarInt val
this->disabled_by_default = value.as_bool(); this->disabled_by_default = value.as_bool();
return true; return true;
} }
case 11: {
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
default: default:
return false; return false;
} }
@ -715,6 +750,7 @@ void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(8, this->device_class); buffer.encode_string(8, this->device_class);
buffer.encode_bool(9, this->disabled_by_default); buffer.encode_bool(9, this->disabled_by_default);
buffer.encode_string(10, this->icon); buffer.encode_string(10, this->icon);
buffer.encode_enum<enums::EntityCategory>(11, this->entity_category);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesCoverResponse::dump_to(std::string &out) const { void ListEntitiesCoverResponse::dump_to(std::string &out) const {
@ -760,6 +796,10 @@ void ListEntitiesCoverResponse::dump_to(std::string &out) const {
out.append(" icon: "); out.append(" icon: ");
out.append("'").append(this->icon).append("'"); out.append("'").append(this->icon).append("'");
out.append("\n"); out.append("\n");
out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@ -948,6 +988,10 @@ bool ListEntitiesFanResponse::decode_varint(uint32_t field_id, ProtoVarInt value
this->disabled_by_default = value.as_bool(); this->disabled_by_default = value.as_bool();
return true; return true;
} }
case 11: {
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
default: default:
return false; return false;
} }
@ -995,6 +1039,7 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_int32(8, this->supported_speed_count); buffer.encode_int32(8, this->supported_speed_count);
buffer.encode_bool(9, this->disabled_by_default); buffer.encode_bool(9, this->disabled_by_default);
buffer.encode_string(10, this->icon); buffer.encode_string(10, this->icon);
buffer.encode_enum<enums::EntityCategory>(11, this->entity_category);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesFanResponse::dump_to(std::string &out) const { void ListEntitiesFanResponse::dump_to(std::string &out) const {
@ -1041,6 +1086,10 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const {
out.append(" icon: "); out.append(" icon: ");
out.append("'").append(this->icon).append("'"); out.append("'").append(this->icon).append("'");
out.append("\n"); out.append("\n");
out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@ -1267,6 +1316,10 @@ bool ListEntitiesLightResponse::decode_varint(uint32_t field_id, ProtoVarInt val
this->disabled_by_default = value.as_bool(); this->disabled_by_default = value.as_bool();
return true; return true;
} }
case 15: {
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
default: default:
return false; return false;
} }
@ -1334,6 +1387,7 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const {
} }
buffer.encode_bool(13, this->disabled_by_default); buffer.encode_bool(13, this->disabled_by_default);
buffer.encode_string(14, this->icon); buffer.encode_string(14, this->icon);
buffer.encode_enum<enums::EntityCategory>(15, this->entity_category);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesLightResponse::dump_to(std::string &out) const { void ListEntitiesLightResponse::dump_to(std::string &out) const {
@ -1401,6 +1455,10 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const {
out.append(" icon: "); out.append(" icon: ");
out.append("'").append(this->icon).append("'"); out.append("'").append(this->icon).append("'");
out.append("\n"); out.append("\n");
out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@ -1860,6 +1918,10 @@ bool ListEntitiesSensorResponse::decode_varint(uint32_t field_id, ProtoVarInt va
this->disabled_by_default = value.as_bool(); this->disabled_by_default = value.as_bool();
return true; return true;
} }
case 13: {
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
default: default:
return false; return false;
} }
@ -1917,6 +1979,7 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_enum<enums::SensorStateClass>(10, this->state_class); buffer.encode_enum<enums::SensorStateClass>(10, this->state_class);
buffer.encode_enum<enums::SensorLastResetType>(11, this->legacy_last_reset_type); buffer.encode_enum<enums::SensorLastResetType>(11, this->legacy_last_reset_type);
buffer.encode_bool(12, this->disabled_by_default); buffer.encode_bool(12, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(13, this->entity_category);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesSensorResponse::dump_to(std::string &out) const { void ListEntitiesSensorResponse::dump_to(std::string &out) const {
@ -1971,6 +2034,10 @@ void ListEntitiesSensorResponse::dump_to(std::string &out) const {
out.append(" disabled_by_default: "); out.append(" disabled_by_default: ");
out.append(YESNO(this->disabled_by_default)); out.append(YESNO(this->disabled_by_default));
out.append("\n"); out.append("\n");
out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@ -2033,6 +2100,10 @@ bool ListEntitiesSwitchResponse::decode_varint(uint32_t field_id, ProtoVarInt va
this->disabled_by_default = value.as_bool(); this->disabled_by_default = value.as_bool();
return true; return true;
} }
case 8: {
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
default: default:
return false; return false;
} }
@ -2077,6 +2148,7 @@ void ListEntitiesSwitchResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(5, this->icon); buffer.encode_string(5, this->icon);
buffer.encode_bool(6, this->assumed_state); buffer.encode_bool(6, this->assumed_state);
buffer.encode_bool(7, this->disabled_by_default); buffer.encode_bool(7, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(8, this->entity_category);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesSwitchResponse::dump_to(std::string &out) const { void ListEntitiesSwitchResponse::dump_to(std::string &out) const {
@ -2110,6 +2182,10 @@ void ListEntitiesSwitchResponse::dump_to(std::string &out) const {
out.append(" disabled_by_default: "); out.append(" disabled_by_default: ");
out.append(YESNO(this->disabled_by_default)); out.append(YESNO(this->disabled_by_default));
out.append("\n"); out.append("\n");
out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@ -2197,6 +2273,10 @@ bool ListEntitiesTextSensorResponse::decode_varint(uint32_t field_id, ProtoVarIn
this->disabled_by_default = value.as_bool(); this->disabled_by_default = value.as_bool();
return true; return true;
} }
case 7: {
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
default: default:
return false; return false;
} }
@ -2240,6 +2320,7 @@ void ListEntitiesTextSensorResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(4, this->unique_id); buffer.encode_string(4, this->unique_id);
buffer.encode_string(5, this->icon); buffer.encode_string(5, this->icon);
buffer.encode_bool(6, this->disabled_by_default); buffer.encode_bool(6, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesTextSensorResponse::dump_to(std::string &out) const { void ListEntitiesTextSensorResponse::dump_to(std::string &out) const {
@ -2269,6 +2350,10 @@ void ListEntitiesTextSensorResponse::dump_to(std::string &out) const {
out.append(" disabled_by_default: "); out.append(" disabled_by_default: ");
out.append(YESNO(this->disabled_by_default)); out.append(YESNO(this->disabled_by_default));
out.append("\n"); out.append("\n");
out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@ -2892,6 +2977,10 @@ bool ListEntitiesCameraResponse::decode_varint(uint32_t field_id, ProtoVarInt va
this->disabled_by_default = value.as_bool(); this->disabled_by_default = value.as_bool();
return true; return true;
} }
case 7: {
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
default: default:
return false; return false;
} }
@ -2910,6 +2999,10 @@ bool ListEntitiesCameraResponse::decode_length(uint32_t field_id, ProtoLengthDel
this->unique_id = value.as_string(); this->unique_id = value.as_string();
return true; return true;
} }
case 6: {
this->icon = value.as_string();
return true;
}
default: default:
return false; return false;
} }
@ -2930,6 +3023,8 @@ void ListEntitiesCameraResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(3, this->name); buffer.encode_string(3, this->name);
buffer.encode_string(4, this->unique_id); buffer.encode_string(4, this->unique_id);
buffer.encode_bool(5, this->disabled_by_default); buffer.encode_bool(5, this->disabled_by_default);
buffer.encode_string(6, this->icon);
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesCameraResponse::dump_to(std::string &out) const { void ListEntitiesCameraResponse::dump_to(std::string &out) const {
@ -2955,6 +3050,14 @@ void ListEntitiesCameraResponse::dump_to(std::string &out) const {
out.append(" disabled_by_default: "); out.append(" disabled_by_default: ");
out.append(YESNO(this->disabled_by_default)); out.append(YESNO(this->disabled_by_default));
out.append("\n"); out.append("\n");
out.append(" icon: ");
out.append("'").append(this->icon).append("'");
out.append("\n");
out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@ -3082,6 +3185,10 @@ bool ListEntitiesClimateResponse::decode_varint(uint32_t field_id, ProtoVarInt v
this->disabled_by_default = value.as_bool(); this->disabled_by_default = value.as_bool();
return true; return true;
} }
case 20: {
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
default: default:
return false; return false;
} }
@ -3170,6 +3277,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const {
} }
buffer.encode_bool(18, this->disabled_by_default); buffer.encode_bool(18, this->disabled_by_default);
buffer.encode_string(19, this->icon); buffer.encode_string(19, this->icon);
buffer.encode_enum<enums::EntityCategory>(20, this->entity_category);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesClimateResponse::dump_to(std::string &out) const { void ListEntitiesClimateResponse::dump_to(std::string &out) const {
@ -3266,6 +3374,10 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const {
out.append(" icon: "); out.append(" icon: ");
out.append("'").append(this->icon).append("'"); out.append("'").append(this->icon).append("'");
out.append("\n"); out.append("\n");
out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@ -3642,6 +3754,10 @@ bool ListEntitiesNumberResponse::decode_varint(uint32_t field_id, ProtoVarInt va
this->disabled_by_default = value.as_bool(); this->disabled_by_default = value.as_bool();
return true; return true;
} }
case 10: {
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
default: default:
return false; return false;
} }
@ -3700,6 +3816,7 @@ void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_float(7, this->max_value); buffer.encode_float(7, this->max_value);
buffer.encode_float(8, this->step); buffer.encode_float(8, this->step);
buffer.encode_bool(9, this->disabled_by_default); buffer.encode_bool(9, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(10, this->entity_category);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesNumberResponse::dump_to(std::string &out) const { void ListEntitiesNumberResponse::dump_to(std::string &out) const {
@ -3744,6 +3861,10 @@ void ListEntitiesNumberResponse::dump_to(std::string &out) const {
out.append(" disabled_by_default: "); out.append(" disabled_by_default: ");
out.append(YESNO(this->disabled_by_default)); out.append(YESNO(this->disabled_by_default));
out.append("\n"); out.append("\n");
out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@ -3836,6 +3957,10 @@ bool ListEntitiesSelectResponse::decode_varint(uint32_t field_id, ProtoVarInt va
this->disabled_by_default = value.as_bool(); this->disabled_by_default = value.as_bool();
return true; return true;
} }
case 8: {
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
default: default:
return false; return false;
} }
@ -3886,6 +4011,7 @@ void ListEntitiesSelectResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(6, it, true); buffer.encode_string(6, it, true);
} }
buffer.encode_bool(7, this->disabled_by_default); buffer.encode_bool(7, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(8, this->entity_category);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesSelectResponse::dump_to(std::string &out) const { void ListEntitiesSelectResponse::dump_to(std::string &out) const {
@ -3921,6 +4047,10 @@ void ListEntitiesSelectResponse::dump_to(std::string &out) const {
out.append(" disabled_by_default: "); out.append(" disabled_by_default: ");
out.append(YESNO(this->disabled_by_default)); out.append(YESNO(this->disabled_by_default));
out.append("\n"); out.append("\n");
out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif

View file

@ -9,6 +9,11 @@ namespace api {
namespace enums { namespace enums {
enum EntityCategory : uint32_t {
ENTITY_CATEGORY_NONE = 0,
ENTITY_CATEGORY_CONFIG = 1,
ENTITY_CATEGORY_DIAGNOSTIC = 2,
};
enum LegacyCoverState : uint32_t { enum LegacyCoverState : uint32_t {
LEGACY_COVER_STATE_OPEN = 0, LEGACY_COVER_STATE_OPEN = 0,
LEGACY_COVER_STATE_CLOSED = 1, LEGACY_COVER_STATE_CLOSED = 1,
@ -224,6 +229,7 @@ class DeviceInfoResponse : public ProtoMessage {
bool has_deep_sleep{false}; bool has_deep_sleep{false};
std::string project_name{}; std::string project_name{};
std::string project_version{}; std::string project_version{};
uint32_t webserver_port{0};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
@ -270,6 +276,7 @@ class ListEntitiesBinarySensorResponse : public ProtoMessage {
bool is_status_binary_sensor{false}; bool is_status_binary_sensor{false};
bool disabled_by_default{false}; bool disabled_by_default{false};
std::string icon{}; std::string icon{};
enums::EntityCategory entity_category{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
@ -306,6 +313,7 @@ class ListEntitiesCoverResponse : public ProtoMessage {
std::string device_class{}; std::string device_class{};
bool disabled_by_default{false}; bool disabled_by_default{false};
std::string icon{}; std::string icon{};
enums::EntityCategory entity_category{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
@ -363,6 +371,7 @@ class ListEntitiesFanResponse : public ProtoMessage {
int32_t supported_speed_count{0}; int32_t supported_speed_count{0};
bool disabled_by_default{false}; bool disabled_by_default{false};
std::string icon{}; std::string icon{};
enums::EntityCategory entity_category{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
@ -428,6 +437,7 @@ class ListEntitiesLightResponse : public ProtoMessage {
std::vector<std::string> effects{}; std::vector<std::string> effects{};
bool disabled_by_default{false}; bool disabled_by_default{false};
std::string icon{}; std::string icon{};
enums::EntityCategory entity_category{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
@ -516,6 +526,7 @@ class ListEntitiesSensorResponse : public ProtoMessage {
enums::SensorStateClass state_class{}; enums::SensorStateClass state_class{};
enums::SensorLastResetType legacy_last_reset_type{}; enums::SensorLastResetType legacy_last_reset_type{};
bool disabled_by_default{false}; bool disabled_by_default{false};
enums::EntityCategory entity_category{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
@ -549,6 +560,7 @@ class ListEntitiesSwitchResponse : public ProtoMessage {
std::string icon{}; std::string icon{};
bool assumed_state{false}; bool assumed_state{false};
bool disabled_by_default{false}; bool disabled_by_default{false};
enums::EntityCategory entity_category{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
@ -593,6 +605,7 @@ class ListEntitiesTextSensorResponse : public ProtoMessage {
std::string unique_id{}; std::string unique_id{};
std::string icon{}; std::string icon{};
bool disabled_by_default{false}; bool disabled_by_default{false};
enums::EntityCategory entity_category{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
@ -803,6 +816,8 @@ class ListEntitiesCameraResponse : public ProtoMessage {
std::string name{}; std::string name{};
std::string unique_id{}; std::string unique_id{};
bool disabled_by_default{false}; bool disabled_by_default{false};
std::string icon{};
enums::EntityCategory entity_category{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
@ -861,6 +876,7 @@ class ListEntitiesClimateResponse : public ProtoMessage {
std::vector<std::string> supported_custom_presets{}; std::vector<std::string> supported_custom_presets{};
bool disabled_by_default{false}; bool disabled_by_default{false};
std::string icon{}; std::string icon{};
enums::EntityCategory entity_category{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
@ -940,6 +956,7 @@ class ListEntitiesNumberResponse : public ProtoMessage {
float max_value{0.0f}; float max_value{0.0f};
float step{0.0f}; float step{0.0f};
bool disabled_by_default{false}; bool disabled_by_default{false};
enums::EntityCategory entity_category{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
@ -985,6 +1002,7 @@ class ListEntitiesSelectResponse : public ProtoMessage {
std::string icon{}; std::string icon{};
std::vector<std::string> options{}; std::vector<std::string> options{};
bool disabled_by_default{false}; bool disabled_by_default{false};
enums::EntityCategory entity_category{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;

View file

@ -77,7 +77,7 @@ void APIServer::setup() {
this->last_connected_ = millis(); this->last_connected_ = millis();
#ifdef USE_ESP32_CAMERA #ifdef USE_ESP32_CAMERA
if (esp32_camera::global_esp32_camera != nullptr) { if (esp32_camera::global_esp32_camera != nullptr && !esp32_camera::global_esp32_camera->is_internal()) {
esp32_camera::global_esp32_camera->add_image_callback( esp32_camera::global_esp32_camera->add_image_callback(
[this](const std::shared_ptr<esp32_camera::CameraImage> &image) { [this](const std::shared_ptr<esp32_camera::CameraImage> &image) {
for (auto &c : this->clients_) for (auto &c : this->clients_)

View file

@ -23,7 +23,6 @@ async def async_run_logs(config, address):
_LOGGER.info("Starting log output from %s using esphome API", address) _LOGGER.info("Starting log output from %s using esphome API", address)
zc = zeroconf.Zeroconf() zc = zeroconf.Zeroconf()
cli = APIClient( cli = APIClient(
asyncio.get_event_loop(),
address, address,
port, port,
password, password,

View file

@ -8,6 +8,18 @@
namespace esphome { namespace esphome {
namespace api { namespace api {
template<typename... X> class TemplatableStringValue : public TemplatableValue<std::string, X...> {
public:
TemplatableStringValue() : TemplatableValue<std::string, X...>() {}
template<typename F, enable_if_t<!is_callable<F, X...>::value, int> = 0>
TemplatableStringValue(F value) : TemplatableValue<std::string, X...>(value) {}
template<typename F, enable_if_t<is_callable<F, X...>::value, int> = 0>
TemplatableStringValue(F f)
: TemplatableValue<std::string, X...>([f](X... x) -> std::string { return to_string(f(x...)); }) {}
};
template<typename... Ts> class TemplatableKeyValuePair { template<typename... Ts> class TemplatableKeyValuePair {
public: public:
template<typename T> TemplatableKeyValuePair(std::string key, T value) : key(std::move(key)), value(value) {} template<typename T> TemplatableKeyValuePair(std::string key, T value) : key(std::move(key)), value(value) {}
@ -19,7 +31,8 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
public: public:
explicit HomeAssistantServiceCallAction(APIServer *parent, bool is_event) : parent_(parent), is_event_(is_event) {} explicit HomeAssistantServiceCallAction(APIServer *parent, bool is_event) : parent_(parent), is_event_(is_event) {}
TEMPLATABLE_STRING_VALUE(service); template<typename T> void set_service(T service) { this->service_ = service; }
template<typename T> void add_data(std::string key, T value) { template<typename T> void add_data(std::string key, T value) {
this->data_.push_back(TemplatableKeyValuePair<Ts...>(key, value)); this->data_.push_back(TemplatableKeyValuePair<Ts...>(key, value));
} }
@ -58,6 +71,7 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
protected: protected:
APIServer *parent_; APIServer *parent_;
bool is_event_; bool is_event_;
TemplatableStringValue<Ts...> service_{};
std::vector<TemplatableKeyValuePair<Ts...>> data_; std::vector<TemplatableKeyValuePair<Ts...>> data_;
std::vector<TemplatableKeyValuePair<Ts...>> data_template_; std::vector<TemplatableKeyValuePair<Ts...>> data_template_;
std::vector<TemplatableKeyValuePair<Ts...>> variables_; std::vector<TemplatableKeyValuePair<Ts...>> variables_;

View file

@ -12,6 +12,7 @@ from esphome.const import (
DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_VOLTAGE, DEVICE_CLASS_VOLTAGE,
ENTITY_CATEGORY_DIAGNOSTIC,
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS, UNIT_CELSIUS,
UNIT_PERCENT, UNIT_PERCENT,
@ -49,12 +50,14 @@ CONFIG_SCHEMA = (
accuracy_decimals=0, accuracy_decimals=0,
device_class=DEVICE_CLASS_BATTERY, device_class=DEVICE_CLASS_BATTERY,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
), ),
cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema( cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT, unit_of_measurement=UNIT_VOLT,
accuracy_decimals=3, accuracy_decimals=3,
device_class=DEVICE_CLASS_VOLTAGE, device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
), ),
} }
) )

View file

@ -17,6 +17,7 @@ from esphome.const import (
DEVICE_CLASS_POWER_FACTOR, DEVICE_CLASS_POWER_FACTOR,
DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_VOLTAGE, DEVICE_CLASS_VOLTAGE,
ENTITY_CATEGORY_DIAGNOSTIC,
ICON_LIGHTBULB, ICON_LIGHTBULB,
ICON_CURRENT_AC, ICON_CURRENT_AC,
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
@ -125,6 +126,7 @@ CONFIG_SCHEMA = (
accuracy_decimals=1, accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE, device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
), ),
cv.Required(CONF_LINE_FREQUENCY): cv.enum(LINE_FREQS, upper=True), cv.Required(CONF_LINE_FREQUENCY): cv.enum(LINE_FREQS, upper=True),
cv.Optional(CONF_CURRENT_PHASES, default="3"): cv.enum( cv.Optional(CONF_CURRENT_PHASES, default="3"): cv.enum(

View file

@ -13,6 +13,7 @@ from esphome.const import (
DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_ILLUMINANCE,
DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_VOLTAGE, DEVICE_CLASS_VOLTAGE,
ENTITY_CATEGORY_DIAGNOSTIC,
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS, UNIT_CELSIUS,
UNIT_LUX, UNIT_LUX,
@ -51,6 +52,7 @@ CONFIG_SCHEMA = (
accuracy_decimals=3, accuracy_decimals=3,
device_class=DEVICE_CLASS_VOLTAGE, device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
), ),
cv.Optional(CONF_MOISTURE): sensor.sensor_schema( cv.Optional(CONF_MOISTURE): sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT, unit_of_measurement=UNIT_PERCENT,

View file

@ -79,6 +79,9 @@ void BH1750Sensor::read_data_() {
float lx = float(raw_value) / 1.2f; float lx = float(raw_value) / 1.2f;
lx *= 69.0f / this->measurement_duration_; lx *= 69.0f / this->measurement_duration_;
if (this->resolution_ == BH1750_RESOLUTION_0P5_LX) {
lx /= 2.0f;
}
ESP_LOGD(TAG, "'%s': Got illuminance=%.1flx", this->get_name().c_str(), lx); ESP_LOGD(TAG, "'%s': Got illuminance=%.1flx", this->get_name().c_str(), lx);
this->publish_state(lx); this->publish_state(lx);
this->status_clear_warning(); this->status_clear_warning();

View file

@ -313,6 +313,7 @@ def validate_multi_click_timing(value):
device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_") device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
BINARY_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( BINARY_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend(
{ {
cv.GenerateID(): cv.declare_id(BinarySensor), cv.GenerateID(): cv.declare_id(BinarySensor),

View file

@ -2,7 +2,6 @@ import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.components import i2c from esphome.components import i2c
from esphome.const import CONF_ID from esphome.const import CONF_ID
from esphome.core import CORE
CODEOWNERS = ["@trvrnrth"] CODEOWNERS = ["@trvrnrth"]
DEPENDENCIES = ["i2c"] DEPENDENCIES = ["i2c"]
@ -62,7 +61,6 @@ async def to_code(config):
var.set_state_save_interval(config[CONF_STATE_SAVE_INTERVAL].total_milliseconds) var.set_state_save_interval(config[CONF_STATE_SAVE_INTERVAL].total_milliseconds)
) )
if CORE.is_esp32:
# Although this component does not use SPI, the BSEC library requires the SPI library # Although this component does not use SPI, the BSEC library requires the SPI library
cg.add_library("SPI", None) cg.add_library("SPI", None)

View file

@ -0,0 +1,45 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c
from esphome.const import CONF_ID, CONF_RESET_PIN
from esphome import pins
CONF_TOUCH_THRESHOLD = "touch_threshold"
CONF_ALLOW_MULTIPLE_TOUCHES = "allow_multiple_touches"
DEPENDENCIES = ["i2c"]
AUTO_LOAD = ["binary_sensor", "output"]
CODEOWNERS = ["@MrEditor97"]
cap1188_ns = cg.esphome_ns.namespace("cap1188")
CONF_CAP1188_ID = "cap1188_id"
CAP1188Component = cap1188_ns.class_("CAP1188Component", cg.Component, i2c.I2CDevice)
MULTI_CONF = True
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(CAP1188Component),
cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
cv.Optional(CONF_TOUCH_THRESHOLD, default=0x20): cv.int_range(
min=0x01, max=0x80
),
cv.Optional(CONF_ALLOW_MULTIPLE_TOUCHES, default=False): cv.boolean,
}
)
.extend(cv.COMPONENT_SCHEMA)
.extend(i2c.i2c_device_schema(0x29))
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
cg.add(var.set_touch_threshold(config[CONF_TOUCH_THRESHOLD]))
cg.add(var.set_allow_multiple_touches(config[CONF_ALLOW_MULTIPLE_TOUCHES]))
if CONF_RESET_PIN in config:
pin = await cg.gpio_pin_expression(config[CONF_RESET_PIN])
cg.add(var.set_reset_pin(pin))
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)

View file

@ -0,0 +1,25 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import binary_sensor
from esphome.const import CONF_CHANNEL, CONF_ID
from . import cap1188_ns, CAP1188Component, CONF_CAP1188_ID
DEPENDENCIES = ["cap1188"]
CAP1188Channel = cap1188_ns.class_("CAP1188Channel", binary_sensor.BinarySensor)
CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(CAP1188Channel),
cv.GenerateID(CONF_CAP1188_ID): cv.use_id(CAP1188Component),
cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=7),
}
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await binary_sensor.register_binary_sensor(var, config)
hub = await cg.get_variable(config[CONF_CAP1188_ID])
cg.add(var.set_channel(config[CONF_CHANNEL]))
cg.add(hub.register_channel(var))

View file

@ -0,0 +1,88 @@
#include "cap1188.h"
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
namespace esphome {
namespace cap1188 {
static const char *const TAG = "cap1188";
void CAP1188Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up CAP1188...");
// Reset device using the reset pin
if (this->reset_pin_ != nullptr) {
this->reset_pin_->setup();
this->reset_pin_->digital_write(false);
delay(100); // NOLINT
this->reset_pin_->digital_write(true);
delay(100); // NOLINT
this->reset_pin_->digital_write(false);
delay(100); // NOLINT
}
// Check if CAP1188 is actually connected
this->read_byte(CAP1188_PRODUCT_ID, &this->cap1188_product_id_);
this->read_byte(CAP1188_MANUFACTURE_ID, &this->cap1188_manufacture_id_);
this->read_byte(CAP1188_REVISION, &this->cap1188_revision_);
if ((this->cap1188_product_id_ != 0x50) || (this->cap1188_manufacture_id_ != 0x5D)) {
this->error_code_ = COMMUNICATION_FAILED;
this->mark_failed();
return;
}
// Set sensitivity
uint8_t sensitivity = 0;
this->read_byte(CAP1188_SENSITVITY, &sensitivity);
sensitivity = sensitivity & 0x0f;
this->write_byte(CAP1188_SENSITVITY, sensitivity | this->touch_threshold_);
// Allow multiple touches
this->write_byte(CAP1188_MULTI_TOUCH, this->allow_multiple_touches_);
// Have LEDs follow touches
this->write_byte(CAP1188_LED_LINK, 0xFF);
// Speed up a bit
this->write_byte(CAP1188_STAND_BY_CONFIGURATION, 0x30);
}
void CAP1188Component::dump_config() {
ESP_LOGCONFIG(TAG, "CAP1188:");
LOG_I2C_DEVICE(this);
LOG_PIN(" Reset Pin: ", this->reset_pin_);
ESP_LOGCONFIG(TAG, " Product ID: 0x%x", this->cap1188_product_id_);
ESP_LOGCONFIG(TAG, " Manufacture ID: 0x%x", this->cap1188_manufacture_id_);
ESP_LOGCONFIG(TAG, " Revision ID: 0x%x", this->cap1188_revision_);
switch (this->error_code_) {
case COMMUNICATION_FAILED:
ESP_LOGE(TAG, "Product ID or Manufacture ID of the connected device does not match a known CAP1188.");
break;
case NONE:
default:
break;
}
}
void CAP1188Component::loop() {
uint8_t touched = 0;
this->read_register(CAP1188_SENSOR_INPUT_STATUS, &touched, 1);
if (touched) {
uint8_t data = 0;
this->read_register(CAP1188_MAIN, &data, 1);
data = data & ~CAP1188_MAIN_INT;
this->write_register(CAP1188_MAIN, &data, 2);
}
for (auto *channel : this->channels_) {
channel->process(touched);
}
}
} // namespace cap1188
} // namespace esphome

View file

@ -0,0 +1,68 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/output/binary_output.h"
#include "esphome/components/binary_sensor/binary_sensor.h"
namespace esphome {
namespace cap1188 {
enum {
CAP1188_I2CADDR = 0x29,
CAP1188_SENSOR_INPUT_STATUS = 0x3,
CAP1188_MULTI_TOUCH = 0x2A,
CAP1188_LED_LINK = 0x72,
CAP1188_PRODUCT_ID = 0xFD,
CAP1188_MANUFACTURE_ID = 0xFE,
CAP1188_STAND_BY_CONFIGURATION = 0x41,
CAP1188_REVISION = 0xFF,
CAP1188_MAIN = 0x00,
CAP1188_MAIN_INT = 0x01,
CAP1188_LEDPOL = 0x73,
CAP1188_INTERUPT_REPEAT = 0x28,
CAP1188_SENSITVITY = 0x1f,
};
class CAP1188Channel : public binary_sensor::BinarySensor {
public:
void set_channel(uint8_t channel) { channel_ = channel; }
void process(uint8_t data) { this->publish_state(static_cast<bool>(data & (1 << this->channel_))); }
protected:
uint8_t channel_{0};
};
class CAP1188Component : public Component, public i2c::I2CDevice {
public:
void register_channel(CAP1188Channel *channel) { this->channels_.push_back(channel); }
void set_touch_threshold(uint8_t touch_threshold) { this->touch_threshold_ = touch_threshold; };
void set_allow_multiple_touches(bool allow_multiple_touches) {
this->allow_multiple_touches_ = allow_multiple_touches ? 0x41 : 0x80;
};
void set_reset_pin(GPIOPin *reset_pin) { this->reset_pin_ = reset_pin; }
void setup() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
void loop() override;
protected:
std::vector<CAP1188Channel *> channels_{};
uint8_t touch_threshold_{0x20};
uint8_t allow_multiple_touches_{0x80};
GPIOPin *reset_pin_{nullptr};
uint8_t cap1188_product_id_{0};
uint8_t cap1188_manufacture_id_{0};
uint8_t cap1188_revision_{0};
enum ErrorCode {
NONE = 0,
COMMUNICATION_FAILED,
} error_code_{NONE};
};
} // namespace cap1188
} // namespace esphome

View file

@ -36,3 +36,5 @@ async def to_code(config):
if CORE.is_esp32: if CORE.is_esp32:
cg.add_library("DNSServer", None) cg.add_library("DNSServer", None)
cg.add_library("WiFi", None) cg.add_library("WiFi", None)
if CORE.is_esp8266:
cg.add_library("DNSServer", None)

View file

@ -67,6 +67,7 @@ void CaptivePortal::handle_wifisave(AsyncWebServerRequest *request) {
ESP_LOGI(TAG, " SSID='%s'", ssid.c_str()); ESP_LOGI(TAG, " SSID='%s'", ssid.c_str());
ESP_LOGI(TAG, " Password=" LOG_SECRET("'%s'"), psk.c_str()); ESP_LOGI(TAG, " Password=" LOG_SECRET("'%s'"), psk.c_str());
wifi::global_wifi_component->save_wifi_sta(ssid, psk); wifi::global_wifi_component->save_wifi_sta(ssid, psk);
wifi::global_wifi_component->start_scanning();
request->redirect("/?save=true"); request->redirect("/?save=true");
} }

View file

@ -52,7 +52,7 @@ void CCS811Component::setup() {
if (this->baseline_.has_value()) { if (this->baseline_.has_value()) {
// baseline available, write to sensor // baseline available, write to sensor
this->write_bytes(0x11, decode_uint16(*this->baseline_)); this->write_bytes(0x11, decode_value(*this->baseline_));
} }
auto hardware_version_data = this->read_bytes<1>(0x21); auto hardware_version_data = this->read_bytes<1>(0x21);

View file

View file

@ -0,0 +1,244 @@
#include "cse7761.h"
#include "esphome/core/log.h"
namespace esphome {
namespace cse7761 {
static const char *const TAG = "cse7761";
/*********************************************************************************************\
* CSE7761 - Energy (Sonoff Dual R3 Pow v1.x)
*
* Based on Tasmota source code
* See https://github.com/arendst/Tasmota/discussions/10793
* https://github.com/arendst/Tasmota/blob/development/tasmota/xnrg_19_cse7761.ino
\*********************************************************************************************/
static const int CSE7761_UREF = 42563; // RmsUc
static const int CSE7761_IREF = 52241; // RmsIAC
static const int CSE7761_PREF = 44513; // PowerPAC
static const uint8_t CSE7761_REG_SYSCON = 0x00; // (2) System Control Register (0x0A04)
static const uint8_t CSE7761_REG_EMUCON = 0x01; // (2) Metering control register (0x0000)
static const uint8_t CSE7761_REG_EMUCON2 = 0x13; // (2) Metering control register 2 (0x0001)
static const uint8_t CSE7761_REG_PULSE1SEL = 0x1D; // (2) Pin function output select register (0x3210)
static const uint8_t CSE7761_REG_RMSIA = 0x24; // (3) The effective value of channel A current (0x000000)
static const uint8_t CSE7761_REG_RMSIB = 0x25; // (3) The effective value of channel B current (0x000000)
static const uint8_t CSE7761_REG_RMSU = 0x26; // (3) Voltage RMS (0x000000)
static const uint8_t CSE7761_REG_POWERPA = 0x2C; // (4) Channel A active power, update rate 27.2Hz (0x00000000)
static const uint8_t CSE7761_REG_POWERPB = 0x2D; // (4) Channel B active power, update rate 27.2Hz (0x00000000)
static const uint8_t CSE7761_REG_SYSSTATUS = 0x43; // (1) System status register
static const uint8_t CSE7761_REG_COEFFCHKSUM = 0x6F; // (2) Coefficient checksum
static const uint8_t CSE7761_REG_RMSIAC = 0x70; // (2) Channel A effective current conversion coefficient
static const uint8_t CSE7761_SPECIAL_COMMAND = 0xEA; // Start special command
static const uint8_t CSE7761_CMD_RESET = 0x96; // Reset command, after receiving the command, the chip resets
static const uint8_t CSE7761_CMD_CLOSE_WRITE = 0xDC; // Close write operation
static const uint8_t CSE7761_CMD_ENABLE_WRITE = 0xE5; // Enable write operation
enum CSE7761 { RMS_IAC, RMS_IBC, RMS_UC, POWER_PAC, POWER_PBC, POWER_SC, ENERGY_AC, ENERGY_BC };
void CSE7761Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up CSE7761...");
this->write_(CSE7761_SPECIAL_COMMAND, CSE7761_CMD_RESET);
uint16_t syscon = this->read_(0x00, 2); // Default 0x0A04
if ((0x0A04 == syscon) && this->chip_init_()) {
this->write_(CSE7761_SPECIAL_COMMAND, CSE7761_CMD_CLOSE_WRITE);
ESP_LOGD(TAG, "CSE7761 found");
this->data_.ready = true;
} else {
this->mark_failed();
}
}
void CSE7761Component::dump_config() {
ESP_LOGCONFIG(TAG, "CSE7761:");
if (this->is_failed()) {
ESP_LOGE(TAG, "Communication with CSE7761 failed!");
}
LOG_UPDATE_INTERVAL(this);
this->check_uart_settings(38400, 1, uart::UART_CONFIG_PARITY_EVEN, 8);
}
float CSE7761Component::get_setup_priority() const { return setup_priority::DATA; }
void CSE7761Component::update() {
if (this->data_.ready) {
this->get_data_();
}
}
void CSE7761Component::write_(uint8_t reg, uint16_t data) {
uint8_t buffer[5];
buffer[0] = 0xA5;
buffer[1] = reg;
uint32_t len = 2;
if (data) {
if (data < 0xFF) {
buffer[2] = data & 0xFF;
len = 3;
} else {
buffer[2] = (data >> 8) & 0xFF;
buffer[3] = data & 0xFF;
len = 4;
}
uint8_t crc = 0;
for (uint32_t i = 0; i < len; i++) {
crc += buffer[i];
}
buffer[len] = ~crc;
len++;
}
this->write_array(buffer, len);
}
bool CSE7761Component::read_once_(uint8_t reg, uint8_t size, uint32_t *value) {
while (this->available()) {
this->read();
}
this->write_(reg, 0);
uint8_t buffer[8] = {0};
uint32_t rcvd = 0;
for (uint32_t i = 0; i <= size; i++) {
int value = this->read();
if (value > -1 && rcvd < sizeof(buffer) - 1) {
buffer[rcvd++] = value;
}
}
if (!rcvd) {
ESP_LOGD(TAG, "Received 0 bytes for register %hhu", reg);
return false;
}
rcvd--;
uint32_t result = 0;
// CRC check
uint8_t crc = 0xA5 + reg;
for (uint32_t i = 0; i < rcvd; i++) {
result = (result << 8) | buffer[i];
crc += buffer[i];
}
crc = ~crc;
if (crc != buffer[rcvd]) {
return false;
}
*value = result;
return true;
}
uint32_t CSE7761Component::read_(uint8_t reg, uint8_t size) {
bool result = false; // Start loop
uint8_t retry = 3; // Retry up to three times
uint32_t value = 0; // Default no value
while (!result && retry > 0) {
retry--;
if (this->read_once_(reg, size, &value))
return value;
}
ESP_LOGE(TAG, "Reading register %hhu failed!", reg);
return value;
}
uint32_t CSE7761Component::coefficient_by_unit_(uint32_t unit) {
switch (unit) {
case RMS_UC:
return 0x400000 * 100 / this->data_.coefficient[RMS_UC];
case RMS_IAC:
return (0x800000 * 100 / this->data_.coefficient[RMS_IAC]) * 10; // Stay within 32 bits
case POWER_PAC:
return 0x80000000 / this->data_.coefficient[POWER_PAC];
}
return 0;
}
bool CSE7761Component::chip_init_() {
uint16_t calc_chksum = 0xFFFF;
for (uint32_t i = 0; i < 8; i++) {
this->data_.coefficient[i] = this->read_(CSE7761_REG_RMSIAC + i, 2);
calc_chksum += this->data_.coefficient[i];
}
calc_chksum = ~calc_chksum;
uint16_t coeff_chksum = this->read_(CSE7761_REG_COEFFCHKSUM, 2);
if ((calc_chksum != coeff_chksum) || (!calc_chksum)) {
ESP_LOGD(TAG, "Default calibration");
this->data_.coefficient[RMS_IAC] = CSE7761_IREF;
this->data_.coefficient[RMS_UC] = CSE7761_UREF;
this->data_.coefficient[POWER_PAC] = CSE7761_PREF;
}
this->write_(CSE7761_SPECIAL_COMMAND, CSE7761_CMD_ENABLE_WRITE);
uint8_t sys_status = this->read_(CSE7761_REG_SYSSTATUS, 1);
if (sys_status & 0x10) { // Write enable to protected registers (WREN)
this->write_(CSE7761_REG_SYSCON | 0x80, 0xFF04);
this->write_(CSE7761_REG_EMUCON | 0x80, 0x1183);
this->write_(CSE7761_REG_EMUCON2 | 0x80, 0x0FC1);
this->write_(CSE7761_REG_PULSE1SEL | 0x80, 0x3290);
} else {
ESP_LOGD(TAG, "Write failed at chip_init");
return false;
}
return true;
}
void CSE7761Component::get_data_() {
// The effective value of current and voltage Rms is a 24-bit signed number,
// the highest bit is 0 for valid data,
// and when the highest bit is 1, the reading will be processed as zero
// The active power parameter PowerA/B is in twos complement format, 32-bit
// data, the highest bit is Sign bit.
uint32_t value = this->read_(CSE7761_REG_RMSU, 3);
this->data_.voltage_rms = (value >= 0x800000) ? 0 : value;
value = this->read_(CSE7761_REG_RMSIA, 3);
this->data_.current_rms[0] = ((value >= 0x800000) || (value < 1600)) ? 0 : value; // No load threshold of 10mA
value = this->read_(CSE7761_REG_POWERPA, 4);
this->data_.active_power[0] = (0 == this->data_.current_rms[0]) ? 0 : ((uint32_t) abs((int) value));
value = this->read_(CSE7761_REG_RMSIB, 3);
this->data_.current_rms[1] = ((value >= 0x800000) || (value < 1600)) ? 0 : value; // No load threshold of 10mA
value = this->read_(CSE7761_REG_POWERPB, 4);
this->data_.active_power[1] = (0 == this->data_.current_rms[1]) ? 0 : ((uint32_t) abs((int) value));
// convert values and publish to sensors
float voltage = (float) this->data_.voltage_rms / this->coefficient_by_unit_(RMS_UC);
if (this->voltage_sensor_ != nullptr) {
this->voltage_sensor_->publish_state(voltage);
}
for (uint32_t channel = 0; channel < 2; channel++) {
// Active power = PowerPA * PowerPAC * 1000 / 0x80000000
float active_power = (float) this->data_.active_power[channel] / this->coefficient_by_unit_(POWER_PAC); // W
float amps = (float) this->data_.current_rms[channel] / this->coefficient_by_unit_(RMS_IAC); // A
ESP_LOGD(TAG, "Channel %d power %f W, current %f A", channel + 1, active_power, amps);
if (channel == 0) {
if (this->power_sensor_1_ != nullptr) {
this->power_sensor_1_->publish_state(active_power);
}
if (this->current_sensor_1_ != nullptr) {
this->current_sensor_1_->publish_state(amps);
}
} else if (channel == 1) {
if (this->power_sensor_2_ != nullptr) {
this->power_sensor_2_->publish_state(active_power);
}
if (this->current_sensor_2_ != nullptr) {
this->current_sensor_2_->publish_state(amps);
}
}
}
}
} // namespace cse7761
} // namespace esphome

View file

@ -0,0 +1,52 @@
#pragma once
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/uart/uart.h"
#include "esphome/core/component.h"
namespace esphome {
namespace cse7761 {
struct CSE7761DataStruct {
uint32_t frequency = 0;
uint32_t voltage_rms = 0;
uint32_t current_rms[2] = {0};
uint32_t energy[2] = {0};
uint32_t active_power[2] = {0};
uint16_t coefficient[8] = {0};
uint8_t energy_update = 0;
bool ready = false;
};
/// This class implements support for the CSE7761 UART power sensor.
class CSE7761Component : public PollingComponent, public uart::UARTDevice {
public:
void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
void set_active_power_1_sensor(sensor::Sensor *power_sensor_1) { power_sensor_1_ = power_sensor_1; }
void set_current_1_sensor(sensor::Sensor *current_sensor_1) { current_sensor_1_ = current_sensor_1; }
void set_active_power_2_sensor(sensor::Sensor *power_sensor_2) { power_sensor_2_ = power_sensor_2; }
void set_current_2_sensor(sensor::Sensor *current_sensor_2) { current_sensor_2_ = current_sensor_2; }
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
void update() override;
protected:
// Sensors
sensor::Sensor *voltage_sensor_{nullptr};
sensor::Sensor *power_sensor_1_{nullptr};
sensor::Sensor *current_sensor_1_{nullptr};
sensor::Sensor *power_sensor_2_{nullptr};
sensor::Sensor *current_sensor_2_{nullptr};
CSE7761DataStruct data_;
void write_(uint8_t reg, uint16_t data);
bool read_once_(uint8_t reg, uint8_t size, uint32_t *value);
uint32_t read_(uint8_t reg, uint8_t size);
uint32_t coefficient_by_unit_(uint32_t unit);
bool chip_init_();
void get_data_();
};
} // namespace cse7761
} // namespace esphome

View file

@ -0,0 +1,90 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, uart
from esphome.const import (
CONF_ID,
CONF_VOLTAGE,
DEVICE_CLASS_CURRENT,
DEVICE_CLASS_POWER,
DEVICE_CLASS_VOLTAGE,
STATE_CLASS_MEASUREMENT,
UNIT_VOLT,
UNIT_AMPERE,
UNIT_WATT,
)
CODEOWNERS = ["@berfenger"]
DEPENDENCIES = ["uart"]
cse7761_ns = cg.esphome_ns.namespace("cse7761")
CSE7761Component = cse7761_ns.class_(
"CSE7761Component", cg.PollingComponent, uart.UARTDevice
)
CONF_CURRENT_1 = "current_1"
CONF_CURRENT_2 = "current_2"
CONF_ACTIVE_POWER_1 = "active_power_1"
CONF_ACTIVE_POWER_2 = "active_power_2"
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(CSE7761Component),
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_1): sensor.sensor_schema(
unit_of_measurement=UNIT_AMPERE,
accuracy_decimals=2,
device_class=DEVICE_CLASS_CURRENT,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_CURRENT_2): sensor.sensor_schema(
unit_of_measurement=UNIT_AMPERE,
accuracy_decimals=2,
device_class=DEVICE_CLASS_CURRENT,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_ACTIVE_POWER_1): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT,
accuracy_decimals=1,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_ACTIVE_POWER_2): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT,
accuracy_decimals=1,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
}
)
.extend(cv.polling_component_schema("60s"))
.extend(uart.UART_DEVICE_SCHEMA)
)
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
"cse7761", baud_rate=38400, require_rx=True, require_tx=True
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
for key in [
CONF_VOLTAGE,
CONF_CURRENT_1,
CONF_CURRENT_2,
CONF_ACTIVE_POWER_1,
CONF_ACTIVE_POWER_2,
]:
if key not in config:
continue
conf = config[key]
sens = await sensor.new_sensor(conf)
cg.add(getattr(var, f"set_{key}_sensor")(sens))

View file

@ -6,26 +6,20 @@ from esphome.const import CONF_ID, CONF_PIN
MULTI_CONF = True MULTI_CONF = True
AUTO_LOAD = ["sensor"] AUTO_LOAD = ["sensor"]
CONF_ONE_WIRE_ID = "one_wire_id"
dallas_ns = cg.esphome_ns.namespace("dallas") dallas_ns = cg.esphome_ns.namespace("dallas")
DallasComponent = dallas_ns.class_("DallasComponent", cg.PollingComponent) DallasComponent = dallas_ns.class_("DallasComponent", cg.PollingComponent)
ESPOneWire = dallas_ns.class_("ESPOneWire")
CONFIG_SCHEMA = cv.All( CONFIG_SCHEMA = cv.Schema(
cv.Schema(
{ {
cv.GenerateID(): cv.declare_id(DallasComponent), cv.GenerateID(): cv.declare_id(DallasComponent),
cv.GenerateID(CONF_ONE_WIRE_ID): cv.declare_id(ESPOneWire),
cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema, cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema,
} }
).extend(cv.polling_component_schema("60s")), ).extend(cv.polling_component_schema("60s"))
# pin_mode call logs in esp-idf, but InterruptLock is active -> crash
cv.only_with_arduino,
)
async def to_code(config): async def to_code(config):
pin = await cg.gpio_pin_expression(config[CONF_PIN]) var = cg.new_Pvariable(config[CONF_ID])
one_wire = cg.new_Pvariable(config[CONF_ONE_WIRE_ID], pin)
var = cg.new_Pvariable(config[CONF_ID], one_wire)
await cg.register_component(var, config) await cg.register_component(var, config)
pin = await cg.gpio_pin_expression(config[CONF_PIN])
cg.add(var.set_pin(pin))

View file

@ -31,12 +31,11 @@ uint16_t DallasTemperatureSensor::millis_to_wait_for_conversion() const {
void DallasComponent::setup() { void DallasComponent::setup() {
ESP_LOGCONFIG(TAG, "Setting up DallasComponent..."); ESP_LOGCONFIG(TAG, "Setting up DallasComponent...");
yield(); pin_->setup();
one_wire_ = new ESPOneWire(pin_); // NOLINT(cppcoreguidelines-owning-memory)
std::vector<uint64_t> raw_sensors; std::vector<uint64_t> raw_sensors;
{
InterruptLock lock;
raw_sensors = this->one_wire_->search_vec(); raw_sensors = this->one_wire_->search_vec();
}
for (auto &address : raw_sensors) { for (auto &address : raw_sensors) {
std::string s = uint64_to_string(address); std::string s = uint64_to_string(address);
@ -70,7 +69,7 @@ void DallasComponent::setup() {
} }
void DallasComponent::dump_config() { void DallasComponent::dump_config() {
ESP_LOGCONFIG(TAG, "DallasComponent:"); ESP_LOGCONFIG(TAG, "DallasComponent:");
LOG_PIN(" Pin: ", this->one_wire_->get_pin()); LOG_PIN(" Pin: ", this->pin_);
LOG_UPDATE_INTERVAL(this); LOG_UPDATE_INTERVAL(this);
if (this->found_sensors_.empty()) { if (this->found_sensors_.empty()) {
@ -102,8 +101,6 @@ void DallasComponent::update() {
this->status_clear_warning(); this->status_clear_warning();
bool result; bool result;
{
InterruptLock lock;
if (!this->one_wire_->reset()) { if (!this->one_wire_->reset()) {
result = false; result = false;
} else { } else {
@ -111,7 +108,6 @@ void DallasComponent::update() {
this->one_wire_->skip(); this->one_wire_->skip();
this->one_wire_->write8(DALLAS_COMMAND_START_CONVERSION); this->one_wire_->write8(DALLAS_COMMAND_START_CONVERSION);
} }
}
if (!result) { if (!result) {
ESP_LOGE(TAG, "Requesting conversion failed"); ESP_LOGE(TAG, "Requesting conversion failed");
@ -121,11 +117,7 @@ void DallasComponent::update() {
for (auto *sensor : this->sensors_) { for (auto *sensor : this->sensors_) {
this->set_timeout(sensor->get_address_name(), sensor->millis_to_wait_for_conversion(), [this, sensor] { this->set_timeout(sensor->get_address_name(), sensor->millis_to_wait_for_conversion(), [this, sensor] {
bool res; bool res = sensor->read_scratch_pad();
{
InterruptLock lock;
res = sensor->read_scratch_pad();
}
if (!res) { if (!res) {
ESP_LOGW(TAG, "'%s' - Resetting bus for read failed!", sensor->get_name().c_str()); ESP_LOGW(TAG, "'%s' - Resetting bus for read failed!", sensor->get_name().c_str());
@ -146,7 +138,6 @@ void DallasComponent::update() {
}); });
} }
} }
DallasComponent::DallasComponent(ESPOneWire *one_wire) : one_wire_(one_wire) {}
void DallasTemperatureSensor::set_address(uint64_t address) { this->address_ = address; } void DallasTemperatureSensor::set_address(uint64_t address) { this->address_ = address; }
uint8_t DallasTemperatureSensor::get_resolution() const { return this->resolution_; } uint8_t DallasTemperatureSensor::get_resolution() const { return this->resolution_; }
@ -162,7 +153,7 @@ const std::string &DallasTemperatureSensor::get_address_name() {
return this->address_name_; return this->address_name_;
} }
bool IRAM_ATTR DallasTemperatureSensor::read_scratch_pad() { bool IRAM_ATTR DallasTemperatureSensor::read_scratch_pad() {
ESPOneWire *wire = this->parent_->one_wire_; auto *wire = this->parent_->one_wire_;
if (!wire->reset()) { if (!wire->reset()) {
return false; return false;
} }
@ -176,11 +167,7 @@ bool IRAM_ATTR DallasTemperatureSensor::read_scratch_pad() {
return true; return true;
} }
bool DallasTemperatureSensor::setup_sensor() { bool DallasTemperatureSensor::setup_sensor() {
bool r; bool r = this->read_scratch_pad();
{
InterruptLock lock;
r = this->read_scratch_pad();
}
if (!r) { if (!r) {
ESP_LOGE(TAG, "Reading scratchpad failed: reset"); ESP_LOGE(TAG, "Reading scratchpad failed: reset");
@ -214,9 +201,7 @@ bool DallasTemperatureSensor::setup_sensor() {
break; break;
} }
ESPOneWire *wire = this->parent_->one_wire_; auto *wire = this->parent_->one_wire_;
{
InterruptLock lock;
if (wire->reset()) { if (wire->reset()) {
wire->select(this->address_); wire->select(this->address_);
wire->write8(DALLAS_COMMAND_WRITE_SCRATCH_PAD); wire->write8(DALLAS_COMMAND_WRITE_SCRATCH_PAD);
@ -229,7 +214,6 @@ bool DallasTemperatureSensor::setup_sensor() {
wire->select(this->address_); wire->select(this->address_);
wire->write8(0x48); wire->write8(0x48);
} }
}
delay(20); // allow it to finish operation delay(20); // allow it to finish operation
wire->reset(); wire->reset();

View file

@ -11,8 +11,7 @@ class DallasTemperatureSensor;
class DallasComponent : public PollingComponent { class DallasComponent : public PollingComponent {
public: public:
explicit DallasComponent(ESPOneWire *one_wire); void set_pin(InternalGPIOPin *pin) { pin_ = pin; }
void register_sensor(DallasTemperatureSensor *sensor); void register_sensor(DallasTemperatureSensor *sensor);
void setup() override; void setup() override;
@ -24,6 +23,7 @@ class DallasComponent : public PollingComponent {
protected: protected:
friend DallasTemperatureSensor; friend DallasTemperatureSensor;
InternalGPIOPin *pin_;
ESPOneWire *one_wire_; ESPOneWire *one_wire_;
std::vector<DallasTemperatureSensor *> sensors_; std::vector<DallasTemperatureSensor *> sensors_;
std::vector<uint64_t> found_sensors_; std::vector<uint64_t> found_sensors_;

View file

@ -10,115 +10,123 @@ static const char *const TAG = "dallas.one_wire";
const uint8_t ONE_WIRE_ROM_SELECT = 0x55; const uint8_t ONE_WIRE_ROM_SELECT = 0x55;
const int ONE_WIRE_ROM_SEARCH = 0xF0; const int ONE_WIRE_ROM_SEARCH = 0xF0;
ESPOneWire::ESPOneWire(GPIOPin *pin) : pin_(pin) {} ESPOneWire::ESPOneWire(InternalGPIOPin *pin) { pin_ = pin->to_isr(); }
bool HOT IRAM_ATTR ESPOneWire::reset() { bool HOT IRAM_ATTR ESPOneWire::reset() {
uint8_t retries = 125; // See reset here:
// https://www.maximintegrated.com/en/design/technical-documents/app-notes/1/126.html
InterruptLock lock;
// Wait for communication to clear // Wait for communication to clear (delay G)
this->pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
uint8_t retries = 125;
do { do {
if (--retries == 0) if (--retries == 0)
return false; return false;
delayMicroseconds(2); delayMicroseconds(2);
} while (!this->pin_->digital_read()); } while (!pin_.digital_read());
// Send 480µs LOW TX reset pulse // Send 480µs LOW TX reset pulse (drive bus low, delay H)
this->pin_->pin_mode(gpio::FLAG_OUTPUT); pin_.pin_mode(gpio::FLAG_OUTPUT);
this->pin_->digital_write(false); pin_.digital_write(false);
delayMicroseconds(480); delayMicroseconds(480);
// Switch into RX mode, letting the pin float // Release the bus, delay I
this->pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
// after 15µs-60µs wait time, responder pulls low for 60µs-240µs
// let's have 70µs just in case
delayMicroseconds(70); delayMicroseconds(70);
bool r = !this->pin_->digital_read(); // sample bus, 0=device(s) present, 1=no device present
bool r = !pin_.digital_read();
// delay J
delayMicroseconds(410); delayMicroseconds(410);
return r; return r;
} }
void HOT IRAM_ATTR ESPOneWire::write_bit(bool bit) { void HOT IRAM_ATTR ESPOneWire::write_bit(bool bit) {
// Initiate write/read by pulling low. // See write 1/0 bit here:
this->pin_->pin_mode(gpio::FLAG_OUTPUT); // https://www.maximintegrated.com/en/design/technical-documents/app-notes/1/126.html
this->pin_->digital_write(false); InterruptLock lock;
// bus sampled within 15µs and 60µs after pulling LOW. // drive bus low
if (bit) { pin_.pin_mode(gpio::FLAG_OUTPUT);
// pull high/release within 15µs pin_.digital_write(false);
delayMicroseconds(10);
this->pin_->digital_write(true); uint32_t delay0 = bit ? 10 : 65;
// in total minimum of 60µs long uint32_t delay1 = bit ? 55 : 5;
delayMicroseconds(55);
} else { // delay A/C
// continue pulling LOW for at least 60µs delayMicroseconds(delay0);
delayMicroseconds(65); // release bus
this->pin_->digital_write(true); pin_.digital_write(true);
// grace period, 1µs recovery time // delay B/D
delayMicroseconds(5); delayMicroseconds(delay1);
}
} }
bool HOT IRAM_ATTR ESPOneWire::read_bit() { bool HOT IRAM_ATTR ESPOneWire::read_bit() {
// Initiate read slot by pulling LOW for at least 1µs // See read bit here:
this->pin_->pin_mode(gpio::FLAG_OUTPUT); // https://www.maximintegrated.com/en/design/technical-documents/app-notes/1/126.html
this->pin_->digital_write(false); InterruptLock lock;
// drive bus low, delay A
pin_.pin_mode(gpio::FLAG_OUTPUT);
pin_.digital_write(false);
delayMicroseconds(3); delayMicroseconds(3);
// release bus, we have to sample within 15µs of pulling low // release bus, delay E
this->pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
delayMicroseconds(10); delayMicroseconds(10);
bool r = this->pin_->digital_read(); // sample bus to read bit from peer
// read time slot at least 60µs long + 1µs recovery time between slots bool r = pin_.digital_read();
// delay F
delayMicroseconds(53); delayMicroseconds(53);
return r; return r;
} }
void IRAM_ATTR ESPOneWire::write8(uint8_t val) { void ESPOneWire::write8(uint8_t val) {
for (uint8_t i = 0; i < 8; i++) { for (uint8_t i = 0; i < 8; i++) {
this->write_bit(bool((1u << i) & val)); this->write_bit(bool((1u << i) & val));
} }
} }
void IRAM_ATTR ESPOneWire::write64(uint64_t val) { void ESPOneWire::write64(uint64_t val) {
for (uint8_t i = 0; i < 64; i++) { for (uint8_t i = 0; i < 64; i++) {
this->write_bit(bool((1ULL << i) & val)); this->write_bit(bool((1ULL << i) & val));
} }
} }
uint8_t IRAM_ATTR ESPOneWire::read8() { uint8_t ESPOneWire::read8() {
uint8_t ret = 0; uint8_t ret = 0;
for (uint8_t i = 0; i < 8; i++) { for (uint8_t i = 0; i < 8; i++) {
ret |= (uint8_t(this->read_bit()) << i); ret |= (uint8_t(this->read_bit()) << i);
} }
return ret; return ret;
} }
uint64_t IRAM_ATTR ESPOneWire::read64() { uint64_t ESPOneWire::read64() {
uint64_t ret = 0; uint64_t ret = 0;
for (uint8_t i = 0; i < 8; i++) { for (uint8_t i = 0; i < 8; i++) {
ret |= (uint64_t(this->read_bit()) << i); ret |= (uint64_t(this->read_bit()) << i);
} }
return ret; return ret;
} }
void IRAM_ATTR ESPOneWire::select(uint64_t address) { void ESPOneWire::select(uint64_t address) {
this->write8(ONE_WIRE_ROM_SELECT); this->write8(ONE_WIRE_ROM_SELECT);
this->write64(address); this->write64(address);
} }
void IRAM_ATTR ESPOneWire::reset_search() { void ESPOneWire::reset_search() {
this->last_discrepancy_ = 0; this->last_discrepancy_ = 0;
this->last_device_flag_ = false; this->last_device_flag_ = false;
this->last_family_discrepancy_ = 0; this->last_family_discrepancy_ = 0;
this->rom_number_ = 0; this->rom_number_ = 0;
} }
uint64_t HOT IRAM_ATTR ESPOneWire::search() { uint64_t ESPOneWire::search() {
if (this->last_device_flag_) { if (this->last_device_flag_) {
return 0u; return 0u;
} }
if (!this->reset()) { if (!this->reset()) {
// Reset failed // Reset failed or no devices present
this->reset_search(); this->reset_search();
return 0u; return 0u;
} }
@ -196,7 +204,7 @@ uint64_t HOT IRAM_ATTR ESPOneWire::search() {
return this->rom_number_; return this->rom_number_;
} }
std::vector<uint64_t> IRAM_ATTR ESPOneWire::search_vec() { std::vector<uint64_t> ESPOneWire::search_vec() {
std::vector<uint64_t> res; std::vector<uint64_t> res;
this->reset_search(); this->reset_search();
@ -206,10 +214,9 @@ std::vector<uint64_t> IRAM_ATTR ESPOneWire::search_vec() {
return res; return res;
} }
void IRAM_ATTR ESPOneWire::skip() { void ESPOneWire::skip() {
this->write8(0xCC); // skip ROM this->write8(0xCC); // skip ROM
} }
GPIOPin *ESPOneWire::get_pin() { return this->pin_; }
uint8_t IRAM_ATTR *ESPOneWire::rom_number8_() { return reinterpret_cast<uint8_t *>(&this->rom_number_); } uint8_t IRAM_ATTR *ESPOneWire::rom_number8_() { return reinterpret_cast<uint8_t *>(&this->rom_number_); }

View file

@ -1,6 +1,5 @@
#pragma once #pragma once
#include "esphome/core/component.h"
#include "esphome/core/hal.h" #include "esphome/core/hal.h"
#include <vector> #include <vector>
@ -12,7 +11,7 @@ extern const int ONE_WIRE_ROM_SEARCH;
class ESPOneWire { class ESPOneWire {
public: public:
explicit ESPOneWire(GPIOPin *pin); explicit ESPOneWire(InternalGPIOPin *pin);
/** Reset the bus, should be done before all write operations. /** Reset the bus, should be done before all write operations.
* *
@ -55,13 +54,11 @@ class ESPOneWire {
/// Helper that wraps search in a std::vector. /// Helper that wraps search in a std::vector.
std::vector<uint64_t> search_vec(); std::vector<uint64_t> search_vec();
GPIOPin *get_pin();
protected: protected:
/// Helper to get the internal 64-bit unsigned rom number as a 8-bit integer pointer. /// Helper to get the internal 64-bit unsigned rom number as a 8-bit integer pointer.
inline uint8_t *rom_number8_(); inline uint8_t *rom_number8_();
GPIOPin *pin_; ISRInternalGPIOPin pin_;
uint8_t last_discrepancy_{0}; uint8_t last_discrepancy_{0};
uint8_t last_family_discrepancy_{0}; uint8_t last_family_discrepancy_{0};
bool last_device_flag_{false}; bool last_device_flag_{false};

View file

@ -29,6 +29,14 @@ CONFIG_SCHEMA = cv.Schema(
} }
) )
WIFI_MESSAGE = """
# Do not forget to add your own wifi configuration before installing this configuration
# wifi:
# ssid: !secret wifi_ssid
# password: !secret wifi_password
"""
async def to_code(config): async def to_code(config):
cg.add_define("USE_DASHBOARD_IMPORT") cg.add_define("USE_DASHBOARD_IMPORT")
@ -41,5 +49,12 @@ def import_config(path: str, name: str, project_name: str, import_url: str) -> N
if p.exists(): if p.exists():
raise FileExistsError raise FileExistsError
config = {"substitutions": {"name": name}, "packages": {project_name: import_url}} config = {
p.write_text(dump(config), encoding="utf8") "substitutions": {"name": name},
"packages": {project_name: import_url},
"esphome": {"name_add_mac_suffix": False},
}
p.write_text(
dump(config) + WIFI_MESSAGE,
encoding="utf8",
)

View file

@ -78,8 +78,9 @@ void DeepSleepComponent::begin_sleep(bool manual) {
esp_sleep_enable_timer_wakeup(*this->sleep_duration_); esp_sleep_enable_timer_wakeup(*this->sleep_duration_);
if (this->wakeup_pin_ != nullptr) { if (this->wakeup_pin_ != nullptr) {
bool level = this->wakeup_pin_->is_inverted(); bool level = this->wakeup_pin_->is_inverted();
if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && this->wakeup_pin_->digital_read()) if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && !this->wakeup_pin_->digital_read()) {
level = !level; level = !level;
}
esp_sleep_enable_ext0_wakeup(gpio_num_t(this->wakeup_pin_->get_pin()), level); esp_sleep_enable_ext0_wakeup(gpio_num_t(this->wakeup_pin_->get_pin()), level);
} }
if (this->ext1_wakeup_.has_value()) { if (this->ext1_wakeup_.has_value()) {

View file

@ -3,6 +3,7 @@ import esphome.config_validation as cv
from esphome import core, automation from esphome import core, automation
from esphome.automation import maybe_simple_id from esphome.automation import maybe_simple_id
from esphome.const import ( from esphome.const import (
CONF_AUTO_CLEAR_ENABLED,
CONF_ID, CONF_ID,
CONF_LAMBDA, CONF_LAMBDA,
CONF_PAGES, CONF_PAGES,
@ -79,6 +80,7 @@ FULL_DISPLAY_SCHEMA = BASIC_DISPLAY_SCHEMA.extend(
cv.Optional(CONF_TO): cv.use_id(DisplayPage), cv.Optional(CONF_TO): cv.use_id(DisplayPage),
} }
), ),
cv.Optional(CONF_AUTO_CLEAR_ENABLED, default=True): cv.boolean,
} }
) )
@ -86,6 +88,10 @@ FULL_DISPLAY_SCHEMA = BASIC_DISPLAY_SCHEMA.extend(
async def setup_display_core_(var, config): async def setup_display_core_(var, config):
if CONF_ROTATION in config: if CONF_ROTATION in config:
cg.add(var.set_rotation(DISPLAY_ROTATIONS[config[CONF_ROTATION]])) cg.add(var.set_rotation(DISPLAY_ROTATIONS[config[CONF_ROTATION]]))
if CONF_AUTO_CLEAR_ENABLED in config:
cg.add(var.set_auto_clear(config[CONF_AUTO_CLEAR_ENABLED]))
if CONF_PAGES in config: if CONF_PAGES in config:
pages = [] pages = []
for conf in config[CONF_PAGES]: for conf in config[CONF_PAGES]:

View file

@ -336,7 +336,9 @@ void DisplayBuffer::show_page(DisplayPage *page) {
void DisplayBuffer::show_next_page() { this->page_->show_next(); } void DisplayBuffer::show_next_page() { this->page_->show_next(); }
void DisplayBuffer::show_prev_page() { this->page_->show_prev(); } void DisplayBuffer::show_prev_page() { this->page_->show_prev(); }
void DisplayBuffer::do_update_() { void DisplayBuffer::do_update_() {
if (this->auto_clear_enabled_) {
this->clear(); this->clear();
}
if (this->page_ != nullptr) { if (this->page_ != nullptr) {
this->page_->get_writer()(*this); this->page_->get_writer()(*this);
} else if (this->writer_.has_value()) { } else if (this->writer_.has_value()) {

View file

@ -333,6 +333,9 @@ class DisplayBuffer {
/// Internal method to set the display rotation with. /// Internal method to set the display rotation with.
void set_rotation(DisplayRotation rotation); void set_rotation(DisplayRotation rotation);
// Internal method to set display auto clearing.
void set_auto_clear(bool auto_clear_enabled) { this->auto_clear_enabled_ = auto_clear_enabled; }
protected: protected:
void vprintf_(int x, int y, Font *font, Color color, TextAlign align, const char *format, va_list arg); void vprintf_(int x, int y, Font *font, Color color, TextAlign align, const char *format, va_list arg);
@ -352,6 +355,7 @@ class DisplayBuffer {
DisplayPage *page_{nullptr}; DisplayPage *page_{nullptr};
DisplayPage *previous_page_{nullptr}; DisplayPage *previous_page_{nullptr};
std::vector<DisplayOnPageChangeTrigger *> on_page_change_triggers_; std::vector<DisplayOnPageChangeTrigger *> on_page_change_triggers_;
bool auto_clear_enabled_{true};
}; };
class DisplayPage { class DisplayPage {

View file

@ -19,14 +19,30 @@ void Dsmr::loop() {
this->receive_encrypted_(); this->receive_encrypted_();
} }
bool Dsmr::available_within_timeout_() {
uint8_t tries = READ_TIMEOUT_MS / 5;
while (tries--) {
delay(5);
if (available()) {
return true;
}
}
return false;
}
void Dsmr::receive_telegram_() { void Dsmr::receive_telegram_() {
int count = MAX_BYTES_PER_LOOP; while (true) {
while (available() && count-- > 0) { if (!available()) {
if (!header_found_ || !available_within_timeout_()) {
return;
}
}
const char c = read(); const char c = read();
// Find a new telegram header, i.e. forward slash. // Find a new telegram header, i.e. forward slash.
if (c == '/') { if (c == '/') {
ESP_LOGV(TAG, "Header found"); ESP_LOGV(TAG, "Header of telegram found");
header_found_ = true; header_found_ = true;
footer_found_ = false; footer_found_ = false;
telegram_len_ = 0; telegram_len_ = 0;
@ -38,7 +54,7 @@ void Dsmr::receive_telegram_() {
if (telegram_len_ >= MAX_TELEGRAM_LENGTH) { if (telegram_len_ >= MAX_TELEGRAM_LENGTH) {
header_found_ = false; header_found_ = false;
footer_found_ = false; footer_found_ = false;
ESP_LOGE(TAG, "Error: Message larger than buffer"); ESP_LOGE(TAG, "Error: telegram larger than buffer (%d bytes)", MAX_TELEGRAM_LENGTH);
return; return;
} }
@ -54,15 +70,16 @@ void Dsmr::receive_telegram_() {
// Check for a footer, i.e. exlamation mark, followed by a hex checksum. // Check for a footer, i.e. exlamation mark, followed by a hex checksum.
if (c == '!') { if (c == '!') {
ESP_LOGV(TAG, "Footer found"); ESP_LOGV(TAG, "Footer of telegram found");
footer_found_ = true; footer_found_ = true;
continue; continue;
} }
// Check for the end of the hex checksum, i.e. a newline. // Check for the end of the hex checksum, i.e. a newline.
if (footer_found_ && c == '\n') { if (footer_found_ && c == '\n') {
header_found_ = false;
// Parse the telegram and publish sensor values. // Parse the telegram and publish sensor values.
if (parse_telegram()) parse_telegram();
header_found_ = false;
return; return;
} }
} }
@ -72,41 +89,46 @@ void Dsmr::receive_encrypted_() {
// Encrypted buffer // Encrypted buffer
uint8_t buffer[MAX_TELEGRAM_LENGTH]; uint8_t buffer[MAX_TELEGRAM_LENGTH];
size_t buffer_length = 0; size_t buffer_length = 0;
size_t packet_size = 0; size_t packet_size = 0;
while (available()) {
while (true) {
if (!available()) {
if (!header_found_) {
return;
}
if (!available_within_timeout_()) {
ESP_LOGW(TAG, "Timeout while reading data for encrypted telegram");
return;
}
}
const char c = read(); const char c = read();
// Find a new telegram start byte.
if (!header_found_) { if (!header_found_) {
if ((uint8_t) c == 0xdb) { if ((uint8_t) c != 0xDB) {
ESP_LOGV(TAG, "Start byte 0xDB found"); continue;
}
ESP_LOGV(TAG, "Start byte 0xDB of encrypted telegram found");
header_found_ = true; header_found_ = true;
} }
}
// Sanity check // Check for buffer overflow.
if (!header_found_ || buffer_length >= MAX_TELEGRAM_LENGTH) { if (buffer_length >= MAX_TELEGRAM_LENGTH) {
if (buffer_length == 0) { header_found_ = false;
ESP_LOGE(TAG, "First byte of encrypted telegram should be 0xDB, aborting."); ESP_LOGE(TAG, "Error: encrypted telegram larger than buffer (%d bytes)", MAX_TELEGRAM_LENGTH);
} else {
ESP_LOGW(TAG, "Unexpected data");
}
this->status_momentary_warning("unexpected_data");
this->flush();
while (available())
read();
return; return;
} }
buffer[buffer_length++] = c; buffer[buffer_length++] = c;
if (packet_size == 0 && buffer_length > 20) { if (packet_size == 0 && buffer_length > 20) {
// Complete header + a few bytes of data // Complete header + data bytes
packet_size = buffer[11] << 8 | buffer[12]; packet_size = 13 + (buffer[11] << 8 | buffer[12]);
ESP_LOGV(TAG, "Encrypted telegram size: %d bytes", packet_size);
} }
if (buffer_length == packet_size + 13 && packet_size > 0) { if (buffer_length == packet_size && packet_size > 0) {
ESP_LOGV(TAG, "Encrypted data: %d bytes", buffer_length); ESP_LOGV(TAG, "End of encrypted telegram found");
GCM<AES128> *gcmaes128{new GCM<AES128>()}; GCM<AES128> *gcmaes128{new GCM<AES128>()};
gcmaes128->setKey(this->decryption_key_.data(), gcmaes128->keySize()); gcmaes128->setKey(this->decryption_key_.data(), gcmaes128->keySize());
// the iv is 8 bytes of the system title + 4 bytes frame counter // the iv is 8 bytes of the system title + 4 bytes frame counter
@ -123,28 +145,21 @@ void Dsmr::receive_encrypted_() {
delete gcmaes128; // NOLINT(cppcoreguidelines-owning-memory) delete gcmaes128; // NOLINT(cppcoreguidelines-owning-memory)
telegram_len_ = strnlen(this->telegram_, sizeof(this->telegram_)); telegram_len_ = strnlen(this->telegram_, sizeof(this->telegram_));
ESP_LOGV(TAG, "Decrypted data length: %d", telegram_len_); ESP_LOGV(TAG, "Decrypted telegram size: %d bytes", telegram_len_);
ESP_LOGVV(TAG, "Decrypted data %s", this->telegram_); ESP_LOGVV(TAG, "Decrypted telegram: %s", this->telegram_);
parse_telegram(); parse_telegram();
header_found_ = false;
telegram_len_ = 0; telegram_len_ = 0;
return; return;
} }
if (!available()) {
// baud rate is 115200 for encrypted data, this means a few byte should arrive every time
// program runs faster than buffer loading then available() might return false in the middle
delay(4); // Wait for data
}
}
if (buffer_length > 0) {
ESP_LOGW(TAG, "Timeout while waiting for encrypted data or invalid data received.");
} }
} }
bool Dsmr::parse_telegram() { bool Dsmr::parse_telegram() {
MyData data; MyData data;
ESP_LOGV(TAG, "Trying to parse"); ESP_LOGV(TAG, "Trying to parse telegram");
::dsmr::ParseResult<void> res = ::dsmr::ParseResult<void> res =
::dsmr::P1Parser::parse(&data, telegram_, telegram_len_, false, ::dsmr::P1Parser::parse(&data, telegram_, telegram_len_, false,
this->crc_check_); // Parse telegram according to data definition. Ignore unknown values. this->crc_check_); // Parse telegram according to data definition. Ignore unknown values.
@ -161,7 +176,7 @@ bool Dsmr::parse_telegram() {
} }
void Dsmr::dump_config() { void Dsmr::dump_config() {
ESP_LOGCONFIG(TAG, "dsmr:"); ESP_LOGCONFIG(TAG, "DSMR:");
#define DSMR_LOG_SENSOR(s) LOG_SENSOR(" ", #s, this->s_##s##_); #define DSMR_LOG_SENSOR(s) LOG_SENSOR(" ", #s, this->s_##s##_);
DSMR_SENSOR_LIST(DSMR_LOG_SENSOR, ) DSMR_SENSOR_LIST(DSMR_LOG_SENSOR, )
@ -178,12 +193,12 @@ void Dsmr::set_decryption_key(const std::string &decryption_key) {
} }
if (decryption_key.length() != 32) { if (decryption_key.length() != 32) {
ESP_LOGE(TAG, "Error, decryption key must be 32 character long."); ESP_LOGE(TAG, "Error, decryption key must be 32 character long");
return; return;
} }
this->decryption_key_.clear(); this->decryption_key_.clear();
ESP_LOGI(TAG, "Decryption key is set."); ESP_LOGI(TAG, "Decryption key is set");
// Verbose level prints decryption key // Verbose level prints decryption key
ESP_LOGV(TAG, "Using decryption key: %s", decryption_key.c_str()); ESP_LOGV(TAG, "Using decryption key: %s", decryption_key.c_str());

View file

@ -17,8 +17,7 @@ namespace esphome {
namespace dsmr { namespace dsmr {
static constexpr uint32_t MAX_TELEGRAM_LENGTH = 1500; static constexpr uint32_t MAX_TELEGRAM_LENGTH = 1500;
static constexpr uint32_t MAX_BYTES_PER_LOOP = 50; static constexpr uint32_t READ_TIMEOUT_MS = 200;
static constexpr uint32_t POLL_TIMEOUT = 1000;
using namespace ::dsmr::fields; using namespace ::dsmr::fields;
@ -86,6 +85,17 @@ class Dsmr : public Component, public uart::UARTDevice {
void receive_telegram_(); void receive_telegram_();
void receive_encrypted_(); void receive_encrypted_();
/// Wait for UART data to become available within the read timeout.
///
/// The smart meter might provide data in chunks, causing available() to
/// return 0. When we're already reading a telegram, then we don't return
/// right away (to handle further data in an upcoming loop) but wait a
/// little while using this method to see if more data are incoming.
/// By not returning, we prevent other components from taking so much
/// time that the UART RX buffer overflows and bytes of the telegram get
/// lost in the process.
bool available_within_timeout_();
// Telegram buffer // Telegram buffer
char telegram_[MAX_TELEGRAM_LENGTH]; char telegram_[MAX_TELEGRAM_LENGTH];
int telegram_len_{0}; int telegram_len_{0};

View file

@ -12,7 +12,6 @@ void DutyCycleSensor::setup() {
this->pin_->setup(); this->pin_->setup();
this->store_.pin = this->pin_->to_isr(); this->store_.pin = this->pin_->to_isr();
this->store_.last_level = this->pin_->digital_read(); this->store_.last_level = this->pin_->digital_read();
this->last_update_ = micros();
this->store_.last_interrupt = micros(); this->store_.last_interrupt = micros();
this->pin_->attach_interrupt(DutyCycleSensorStore::gpio_intr, &this->store_, gpio::INTERRUPT_ANY_EDGE); this->pin_->attach_interrupt(DutyCycleSensorStore::gpio_intr, &this->store_, gpio::INTERRUPT_ANY_EDGE);
@ -24,6 +23,7 @@ void DutyCycleSensor::dump_config() {
} }
void DutyCycleSensor::update() { void DutyCycleSensor::update() {
const uint32_t now = micros(); const uint32_t now = micros();
if (this->last_update_ != 0) {
const bool level = this->store_.last_level; const bool level = this->store_.last_level;
const uint32_t last_interrupt = this->store_.last_interrupt; const uint32_t last_interrupt = this->store_.last_interrupt;
uint32_t on_time = this->store_.on_time; uint32_t on_time = this->store_.on_time;
@ -36,7 +36,7 @@ void DutyCycleSensor::update() {
const float value = (on_time / total_time) * 100.0f; const float value = (on_time / total_time) * 100.0f;
ESP_LOGD(TAG, "'%s' Got duty cycle=%.1f%%", this->get_name().c_str(), value); ESP_LOGD(TAG, "'%s' Got duty cycle=%.1f%%", this->get_name().c_str(), value);
this->publish_state(value); this->publish_state(value);
}
this->store_.on_time = 0; this->store_.on_time = 0;
this->store_.last_interrupt = now; this->store_.last_interrupt = now;
this->last_update_ = now; this->last_update_ = now;

View file

@ -30,7 +30,7 @@ class DutyCycleSensor : public sensor::Sensor, public PollingComponent {
InternalGPIOPin *pin_; InternalGPIOPin *pin_;
DutyCycleSensorStore store_{}; DutyCycleSensorStore store_{};
uint32_t last_update_; uint32_t last_update_{0};
}; };
} // namespace duty_cycle } // namespace duty_cycle

View file

@ -28,6 +28,7 @@ from .const import ( # noqa
KEY_SDKCONFIG_OPTIONS, KEY_SDKCONFIG_OPTIONS,
KEY_VARIANT, KEY_VARIANT,
VARIANT_ESP32C3, VARIANT_ESP32C3,
VARIANT_FRIENDLY,
VARIANTS, VARIANTS,
) )
from .boards import BOARD_TO_VARIANT from .boards import BOARD_TO_VARIANT
@ -147,8 +148,9 @@ def _arduino_check_versions(value):
value[CONF_VERSION] = str(version) value[CONF_VERSION] = str(version)
value[CONF_SOURCE] = source or _format_framework_arduino_version(version) value[CONF_SOURCE] = source or _format_framework_arduino_version(version)
platform_version = value.get(CONF_PLATFORM_VERSION, ARDUINO_PLATFORM_VERSION) value[CONF_PLATFORM_VERSION] = value.get(
value[CONF_PLATFORM_VERSION] = str(platform_version) CONF_PLATFORM_VERSION, _parse_platform_version(str(ARDUINO_PLATFORM_VERSION))
)
if version != RECOMMENDED_ARDUINO_FRAMEWORK_VERSION: if version != RECOMMENDED_ARDUINO_FRAMEWORK_VERSION:
_LOGGER.warning( _LOGGER.warning(
@ -184,8 +186,9 @@ def _esp_idf_check_versions(value):
value[CONF_VERSION] = str(version) value[CONF_VERSION] = str(version)
value[CONF_SOURCE] = source or _format_framework_espidf_version(version) value[CONF_SOURCE] = source or _format_framework_espidf_version(version)
platform_version = value.get(CONF_PLATFORM_VERSION, ESP_IDF_PLATFORM_VERSION) value[CONF_PLATFORM_VERSION] = value.get(
value[CONF_PLATFORM_VERSION] = str(platform_version) CONF_PLATFORM_VERSION, _parse_platform_version(str(ESP_IDF_PLATFORM_VERSION))
)
if version != RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION: if version != RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION:
_LOGGER.warning( _LOGGER.warning(
@ -196,6 +199,15 @@ def _esp_idf_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/espressif32 @ {value}"
except cv.Invalid:
return value
def _detect_variant(value): def _detect_variant(value):
if CONF_VARIANT not in value: if CONF_VARIANT not in value:
board = value[CONF_BOARD] board = value[CONF_BOARD]
@ -218,7 +230,7 @@ ARDUINO_FRAMEWORK_SCHEMA = cv.All(
{ {
cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict, cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict,
cv.Optional(CONF_SOURCE): cv.string_strict, cv.Optional(CONF_SOURCE): cv.string_strict,
cv.Optional(CONF_PLATFORM_VERSION): cv.string_strict, cv.Optional(CONF_PLATFORM_VERSION): _parse_platform_version,
} }
), ),
_arduino_check_versions, _arduino_check_versions,
@ -230,7 +242,7 @@ ESP_IDF_FRAMEWORK_SCHEMA = cv.All(
{ {
cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict, cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict,
cv.Optional(CONF_SOURCE): cv.string_strict, cv.Optional(CONF_SOURCE): cv.string_strict,
cv.Optional(CONF_PLATFORM_VERSION): cv.string_strict, cv.Optional(CONF_PLATFORM_VERSION): _parse_platform_version,
cv.Optional(CONF_SDKCONFIG_OPTIONS, default={}): { cv.Optional(CONF_SDKCONFIG_OPTIONS, default={}): {
cv.string_strict: cv.string_strict cv.string_strict: cv.string_strict
}, },
@ -276,14 +288,14 @@ async def to_code(config):
cg.add_build_flag("-DUSE_ESP32") cg.add_build_flag("-DUSE_ESP32")
cg.add_define("ESPHOME_BOARD", config[CONF_BOARD]) cg.add_define("ESPHOME_BOARD", config[CONF_BOARD])
cg.add_build_flag(f"-DUSE_ESP32_VARIANT_{config[CONF_VARIANT]}") cg.add_build_flag(f"-DUSE_ESP32_VARIANT_{config[CONF_VARIANT]}")
cg.add_define("ESPHOME_VARIANT", VARIANT_FRIENDLY[config[CONF_VARIANT]])
cg.add_platformio_option("lib_ldf_mode", "off") cg.add_platformio_option("lib_ldf_mode", "off")
conf = config[CONF_FRAMEWORK] conf = config[CONF_FRAMEWORK]
cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION])
if conf[CONF_TYPE] == FRAMEWORK_ESP_IDF: if conf[CONF_TYPE] == FRAMEWORK_ESP_IDF:
cg.add_platformio_option(
"platform", f"espressif32 @ {conf[CONF_PLATFORM_VERSION]}"
)
cg.add_platformio_option("framework", "espidf") cg.add_platformio_option("framework", "espidf")
cg.add_build_flag("-DUSE_ESP_IDF") cg.add_build_flag("-DUSE_ESP_IDF")
cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ESP_IDF") cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ESP_IDF")
@ -299,6 +311,8 @@ async def to_code(config):
) )
add_idf_sdkconfig_option("CONFIG_COMPILER_OPTIMIZATION_DEFAULT", False) add_idf_sdkconfig_option("CONFIG_COMPILER_OPTIMIZATION_DEFAULT", False)
add_idf_sdkconfig_option("CONFIG_COMPILER_OPTIMIZATION_SIZE", True) add_idf_sdkconfig_option("CONFIG_COMPILER_OPTIMIZATION_SIZE", True)
# Increase freertos tick speed from 100Hz to 1kHz so that delay() resolution is 1ms
add_idf_sdkconfig_option("CONFIG_FREERTOS_HZ", 1000)
cg.add_platformio_option("board_build.partitions", "partitions.csv") cg.add_platformio_option("board_build.partitions", "partitions.csv")
@ -312,9 +326,6 @@ async def to_code(config):
) )
elif conf[CONF_TYPE] == FRAMEWORK_ARDUINO: elif conf[CONF_TYPE] == FRAMEWORK_ARDUINO:
cg.add_platformio_option(
"platform", f"espressif32 @ {conf[CONF_PLATFORM_VERSION]}"
)
cg.add_platformio_option("framework", "arduino") cg.add_platformio_option("framework", "arduino")
cg.add_build_flag("-DUSE_ARDUINO") cg.add_build_flag("-DUSE_ARDUINO")
cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ARDUINO") cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ARDUINO")

View file

@ -18,4 +18,12 @@ VARIANTS = [
VARIANT_ESP32H2, VARIANT_ESP32H2,
] ]
VARIANT_FRIENDLY = {
VARIANT_ESP32: "ESP32",
VARIANT_ESP32S2: "ESP32-S2",
VARIANT_ESP32S3: "ESP32-S3",
VARIANT_ESP32C3: "ESP32-C3",
VARIANT_ESP32H2: "ESP32-H2",
}
esp32_ns = cg.esphome_ns.namespace("esp32") esp32_ns = cg.esphome_ns.namespace("esp32")

View file

@ -21,11 +21,7 @@ void IRAM_ATTR HOT yield() { vPortYield(); }
uint32_t IRAM_ATTR HOT millis() { return (uint32_t)(esp_timer_get_time() / 1000ULL); } uint32_t IRAM_ATTR HOT millis() { return (uint32_t)(esp_timer_get_time() / 1000ULL); }
void IRAM_ATTR HOT delay(uint32_t ms) { vTaskDelay(ms / portTICK_PERIOD_MS); } void IRAM_ATTR HOT delay(uint32_t ms) { vTaskDelay(ms / portTICK_PERIOD_MS); }
uint32_t IRAM_ATTR HOT micros() { return (uint32_t) esp_timer_get_time(); } uint32_t IRAM_ATTR HOT micros() { return (uint32_t) esp_timer_get_time(); }
void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { delay_microseconds_safe(us); }
auto start = (uint64_t) esp_timer_get_time();
while (((uint64_t) esp_timer_get_time()) - start < us)
;
}
void arch_restart() { void arch_restart() {
esp_restart(); esp_restart();
// restart() doesn't always end execution // restart() doesn't always end execution

View file

@ -1,4 +1,5 @@
import logging from dataclasses import dataclass
from typing import Any
from esphome.const import ( from esphome.const import (
CONF_ID, CONF_ID,
@ -17,10 +18,24 @@ import esphome.config_validation as cv
import esphome.codegen as cg import esphome.codegen as cg
from . import boards from . import boards
from .const import KEY_BOARD, KEY_ESP32, esp32_ns from .const import (
KEY_BOARD,
KEY_ESP32,
KEY_VARIANT,
VARIANT_ESP32,
VARIANT_ESP32C3,
VARIANT_ESP32S2,
VARIANT_ESP32S3,
VARIANT_ESP32H2,
esp32_ns,
)
_LOGGER = logging.getLogger(__name__) from .gpio_esp32 import esp32_validate_gpio_pin, esp32_validate_supports
from .gpio_esp32_s2 import esp32_s2_validate_gpio_pin, esp32_s2_validate_supports
from .gpio_esp32_c3 import esp32_c3_validate_gpio_pin, esp32_c3_validate_supports
from .gpio_esp32_s3 import esp32_s3_validate_gpio_pin, esp32_s3_validate_supports
from .gpio_esp32_h2 import esp32_h2_validate_gpio_pin, esp32_h2_validate_supports
IDFInternalGPIOPin = esp32_ns.class_("IDFInternalGPIOPin", cg.InternalGPIOPin) IDFInternalGPIOPin = esp32_ns.class_("IDFInternalGPIOPin", cg.InternalGPIOPin)
@ -59,65 +74,61 @@ def _translate_pin(value):
return _lookup_pin(value) return _lookup_pin(value)
_ESP_SDIO_PINS = { @dataclass
6: "Flash Clock", class ESP32ValidationFunctions:
7: "Flash Data 0", pin_validation: Any
8: "Flash Data 1", usage_validation: Any
11: "Flash Command",
_esp32_validations = {
VARIANT_ESP32: ESP32ValidationFunctions(
pin_validation=esp32_validate_gpio_pin, usage_validation=esp32_validate_supports
),
VARIANT_ESP32S2: ESP32ValidationFunctions(
pin_validation=esp32_s2_validate_gpio_pin,
usage_validation=esp32_s2_validate_supports,
),
VARIANT_ESP32C3: ESP32ValidationFunctions(
pin_validation=esp32_c3_validate_gpio_pin,
usage_validation=esp32_c3_validate_supports,
),
VARIANT_ESP32S3: ESP32ValidationFunctions(
pin_validation=esp32_s3_validate_gpio_pin,
usage_validation=esp32_s3_validate_supports,
),
VARIANT_ESP32H2: ESP32ValidationFunctions(
pin_validation=esp32_h2_validate_gpio_pin,
usage_validation=esp32_h2_validate_supports,
),
} }
def validate_gpio_pin(value): def validate_gpio_pin(value):
value = _translate_pin(value) value = _translate_pin(value)
if value < 0 or value > 39: variant = CORE.data[KEY_ESP32][KEY_VARIANT]
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-39)") if variant not in _esp32_validations:
if value in _ESP_SDIO_PINS: raise cv.Invalid("Unsupported ESP32 variant {variant}")
raise cv.Invalid(
f"This pin cannot be used on ESP32s and is already used by the flash interface (function: {_ESP_SDIO_PINS[value]})" return _esp32_validations[variant].pin_validation(value)
)
if 9 <= value <= 10:
_LOGGER.warning(
"Pin %s (9-10) might already be used by the "
"flash interface in QUAD IO flash mode.",
value,
)
if value in (20, 24, 28, 29, 30, 31):
# These pins are not exposed in GPIO mux (reason unknown)
# but they're missing from IO_MUX list in datasheet
raise cv.Invalid(f"The pin GPIO{value} is not usable on ESP32s.")
return value
def validate_supports(value): def validate_supports(value):
num = value[CONF_NUMBER]
mode = value[CONF_MODE] mode = value[CONF_MODE]
is_input = mode[CONF_INPUT] is_input = mode[CONF_INPUT]
is_output = mode[CONF_OUTPUT] is_output = mode[CONF_OUTPUT]
is_open_drain = mode[CONF_OPEN_DRAIN] is_open_drain = mode[CONF_OPEN_DRAIN]
is_pullup = mode[CONF_PULLUP] is_pullup = mode[CONF_PULLUP]
is_pulldown = mode[CONF_PULLDOWN] is_pulldown = mode[CONF_PULLDOWN]
variant = CORE.data[KEY_ESP32][KEY_VARIANT]
if variant not in _esp32_validations:
raise cv.Invalid("Unsupported ESP32 variant {variant}")
if is_input:
# All ESP32 pins support input mode
pass
if is_output and 34 <= num <= 39:
raise cv.Invalid(
f"GPIO{num} (34-39) does not support output pin mode.",
[CONF_MODE, CONF_OUTPUT],
)
if is_open_drain and not is_output: if is_open_drain and not is_output:
raise cv.Invalid( raise cv.Invalid(
"Open-drain only works with output mode", [CONF_MODE, CONF_OPEN_DRAIN] "Open-drain only works with output mode", [CONF_MODE, CONF_OPEN_DRAIN]
) )
if is_pullup and 34 <= num <= 39:
raise cv.Invalid(
f"GPIO{num} (34-39) does not support pullups.", [CONF_MODE, CONF_PULLUP]
)
if is_pulldown and 34 <= num <= 39:
raise cv.Invalid(
f"GPIO{num} (34-39) does not support pulldowns.", [CONF_MODE, CONF_PULLDOWN]
)
value = _esp32_validations[variant].usage_validation(value)
if CORE.using_arduino: if CORE.using_arduino:
# (input, output, open_drain, pullup, pulldown) # (input, output, open_drain, pullup, pulldown)
supported_modes = { supported_modes = {
@ -138,7 +149,6 @@ def validate_supports(value):
"This pin mode is not supported on ESP32 for arduino frameworks", "This pin mode is not supported on ESP32 for arduino frameworks",
[CONF_MODE], [CONF_MODE],
) )
return value return value

View file

@ -9,6 +9,22 @@ namespace esp32 {
static const char *const TAG = "esp32"; static const char *const TAG = "esp32";
static int IRAM_ATTR flags_to_mode(gpio::Flags flags) {
if (flags == gpio::FLAG_INPUT) {
return INPUT;
} else if (flags == gpio::FLAG_OUTPUT) {
return OUTPUT;
} else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) {
return INPUT_PULLUP;
} else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLDOWN)) {
return INPUT_PULLDOWN;
} else if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) {
return OUTPUT_OPEN_DRAIN;
} else {
return 0;
}
}
struct ISRPinArg { struct ISRPinArg {
uint8_t pin; uint8_t pin;
bool inverted; bool inverted;
@ -43,22 +59,9 @@ void ArduinoInternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, g
attachInterruptArg(pin_, func, arg, arduino_mode); attachInterruptArg(pin_, func, arg, arduino_mode);
} }
void ArduinoInternalGPIOPin::pin_mode(gpio::Flags flags) { void ArduinoInternalGPIOPin::pin_mode(gpio::Flags flags) {
uint8_t mode; pinMode(pin_, flags_to_mode(flags)); // NOLINT
if (flags == gpio::FLAG_INPUT) {
mode = INPUT;
} else if (flags == gpio::FLAG_OUTPUT) {
mode = OUTPUT;
} else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) {
mode = INPUT_PULLUP;
} else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLDOWN)) {
mode = INPUT_PULLDOWN;
} else if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) {
mode = OUTPUT_OPEN_DRAIN;
} else {
return;
}
pinMode(pin_, mode); // NOLINT
} }
std::string ArduinoInternalGPIOPin::dump_summary() const { std::string ArduinoInternalGPIOPin::dump_summary() const {
@ -101,6 +104,10 @@ void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() {
} }
#endif #endif
} }
void IRAM_ATTR ISRInternalGPIOPin::pin_mode(gpio::Flags flags) {
auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
pinMode(arg->pin, flags_to_mode(flags)); // NOLINT
}
} // namespace esphome } // namespace esphome

View file

@ -0,0 +1,77 @@
import logging
from esphome.const import (
CONF_INPUT,
CONF_MODE,
CONF_NUMBER,
CONF_OUTPUT,
CONF_PULLDOWN,
CONF_PULLUP,
)
import esphome.config_validation as cv
_ESP_SDIO_PINS = {
6: "Flash Clock",
7: "Flash Data 0",
8: "Flash Data 1",
11: "Flash Command",
}
_ESP32_STRAPPING_PINS = {0, 2, 4, 15}
_LOGGER = logging.getLogger(__name__)
def esp32_validate_gpio_pin(value):
if value < 0 or value > 39:
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-39)")
if value in _ESP_SDIO_PINS:
raise cv.Invalid(
f"This pin cannot be used on ESP32s and is already used by the flash interface (function: {_ESP_SDIO_PINS[value]})"
)
if 9 <= value <= 10:
_LOGGER.warning(
"Pin %s (9-10) might already be used by the "
"flash interface in QUAD IO flash mode.",
value,
)
if value in _ESP32_STRAPPING_PINS:
_LOGGER.warning(
"GPIO%d is a Strapping PIN and should be avoided.\n"
"Attaching external pullup/down resistors to strapping pins can cause unexpected failures.\n"
"See https://esphome.io/guides/faq.html#why-am-i-getting-a-warning-about-strapping-pins",
value,
)
if value in (20, 24, 28, 29, 30, 31):
# These pins are not exposed in GPIO mux (reason unknown)
# but they're missing from IO_MUX list in datasheet
raise cv.Invalid(f"The pin GPIO{value} is not usable on ESP32s.")
return value
def esp32_validate_supports(value):
num = value[CONF_NUMBER]
mode = value[CONF_MODE]
is_input = mode[CONF_INPUT]
is_output = mode[CONF_OUTPUT]
is_pullup = mode[CONF_PULLUP]
is_pulldown = mode[CONF_PULLDOWN]
if is_input:
# All ESP32 pins support input mode
pass
if is_output and 34 <= num <= 39:
raise cv.Invalid(
f"GPIO{num} (34-39) does not support output pin mode.",
[CONF_MODE, CONF_OUTPUT],
)
if is_pullup and 34 <= num <= 39:
raise cv.Invalid(
f"GPIO{num} (34-39) does not support pullups.", [CONF_MODE, CONF_PULLUP]
)
if is_pulldown and 34 <= num <= 39:
raise cv.Invalid(
f"GPIO{num} (34-39) does not support pulldowns.", [CONF_MODE, CONF_PULLDOWN]
)
return value

View file

@ -0,0 +1,53 @@
import logging
from esphome.const import (
CONF_INPUT,
CONF_MODE,
CONF_NUMBER,
)
import esphome.config_validation as cv
_ESP32C3_SPI_PSRAM_PINS = {
12: "SPIHD",
13: "SPIWP",
14: "SPICS0",
15: "SPICLK",
16: "SPID",
17: "SPIQ",
}
_ESP32C3_STRAPPING_PINS = {2, 8, 9}
_LOGGER = logging.getLogger(__name__)
def esp32_c3_validate_gpio_pin(value):
if value < 0 or value > 21:
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-21)")
if value in _ESP32C3_SPI_PSRAM_PINS:
raise cv.Invalid(
f"This pin cannot be used on ESP32-C3s and is already used by the SPI/PSRAM interface (function: {_ESP32C3_SPI_PSRAM_PINS[value]})"
)
if value in _ESP32C3_STRAPPING_PINS:
_LOGGER.warning(
"GPIO%d is a Strapping PIN and should be avoided.\n"
"Attaching external pullup/down resistors to strapping pins can cause unexpected failures.\n"
"See https://esphome.io/guides/faq.html#why-am-i-getting-a-warning-about-strapping-pins",
value,
)
return value
def esp32_c3_validate_supports(value):
num = value[CONF_NUMBER]
mode = value[CONF_MODE]
is_input = mode[CONF_INPUT]
if num < 0 or num > 21:
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-21)")
if is_input:
# All ESP32 pins support input mode
pass
return value

View file

@ -0,0 +1,11 @@
import esphome.config_validation as cv
def esp32_h2_validate_gpio_pin(value):
# ESP32-H2 not yet supported
raise cv.Invalid("ESP32-H2 isn't supported yet")
def esp32_h2_validate_supports(value):
# ESP32-H2 not yet supported
raise cv.Invalid("ESP32-H2 isn't supported yet")

View file

@ -0,0 +1,80 @@
import logging
from esphome.const import (
CONF_INPUT,
CONF_MODE,
CONF_NUMBER,
CONF_OUTPUT,
CONF_PULLDOWN,
CONF_PULLUP,
)
import esphome.config_validation as cv
_ESP32S2_SPI_PSRAM_PINS = {
26: "SPICS1",
27: "SPIHD",
28: "SPIWP",
29: "SPICS0",
30: "SPICLK",
31: "SPIQ",
32: "SPID",
}
_ESP32S2_STRAPPING_PINS = {0, 45, 46}
_LOGGER = logging.getLogger(__name__)
def esp32_s2_validate_gpio_pin(value):
if value < 0 or value > 46:
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-46)")
if value in _ESP32S2_SPI_PSRAM_PINS:
raise cv.Invalid(
f"This pin cannot be used on ESP32-S2s and is already used by the SPI/PSRAM interface (function: {_ESP32S2_SPI_PSRAM_PINS[value]})"
)
if value in _ESP32S2_STRAPPING_PINS:
_LOGGER.warning(
"GPIO%d is a Strapping PIN and should be avoided.\n"
"Attaching external pullup/down resistors to strapping pins can cause unexpected failures.\n"
"See https://esphome.io/guides/faq.html#why-am-i-getting-a-warning-about-strapping-pins",
value,
)
if value in (22, 23, 24, 25):
# These pins are not exposed in GPIO mux (reason unknown)
# but they're missing from IO_MUX list in datasheet
raise cv.Invalid(f"The pin GPIO{value} is not usable on ESP32-S2s.")
return value
def esp32_s2_validate_supports(value):
num = value[CONF_NUMBER]
mode = value[CONF_MODE]
is_input = mode[CONF_INPUT]
is_output = mode[CONF_OUTPUT]
is_pullup = mode[CONF_PULLUP]
is_pulldown = mode[CONF_PULLDOWN]
if num < 0 or num > 46:
raise cv.Invalid(f"Invalid pin number: {num} (must be 0-46)")
if is_input:
# All ESP32 pins support input mode
pass
if is_output and num == 46:
raise cv.Invalid(
f"GPIO{num} does not support output pin mode.",
[CONF_MODE, CONF_OUTPUT],
)
if is_pullup and num == 46:
raise cv.Invalid(
f"GPIO{num} does not support pullups.", [CONF_MODE, CONF_PULLUP]
)
if is_pulldown and num == 46:
raise cv.Invalid(
f"GPIO{num} does not support pulldowns.", [CONF_MODE, CONF_PULLDOWN]
)
return value

View file

@ -0,0 +1,74 @@
import logging
from esphome.const import (
CONF_INPUT,
CONF_MODE,
CONF_NUMBER,
)
import esphome.config_validation as cv
_ESP_32S3_SPI_PSRAM_PINS = {
26: "SPICS1",
27: "SPIHD",
28: "SPIWP",
29: "SPICS0",
30: "SPICLK",
31: "SPIQ",
32: "SPID",
}
_ESP_32_ESP32_S3R8_PSRAM_PINS = {
33: "SPIIO4",
34: "SPIIO5",
35: "SPIIO6",
36: "SPIIO7",
37: "SPIDQS",
}
_ESP_32S3_STRAPPING_PINS = {0, 3, 45, 46}
_LOGGER = logging.getLogger(__name__)
def esp32_s3_validate_gpio_pin(value):
if value < 0 or value > 48:
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-46)")
if value in _ESP_32S3_SPI_PSRAM_PINS:
raise cv.Invalid(
f"This pin cannot be used on ESP32-S3s and is already used by the SPI/PSRAM interface(function: {_ESP_32S3_SPI_PSRAM_PINS[value]})"
)
if value in _ESP_32_ESP32_S3R8_PSRAM_PINS:
_LOGGER.warning(
"GPIO%d is used by the PSRAM interface on ESP32-S3R8 / ESP32-S3R8V and should be avoided on these models",
value,
)
if value in _ESP_32S3_STRAPPING_PINS:
_LOGGER.warning(
"GPIO%d is a Strapping PIN and should be avoided.\n"
"Attaching external pullup/down resistors to strapping pins can cause unexpected failures.\n"
"See https://esphome.io/guides/faq.html#why-am-i-getting-a-warning-about-strapping-pins",
value,
)
if value in (22, 23, 24, 25):
# These pins are not exposed in GPIO mux (reason unknown)
# but they're missing from IO_MUX list in datasheet
raise cv.Invalid(f"The pin GPIO{value} is not usable on ESP32-S3s.")
return value
def esp32_s3_validate_supports(value):
num = value[CONF_NUMBER]
mode = value[CONF_MODE]
is_input = mode[CONF_INPUT]
if num < 0 or num > 48:
raise cv.Invalid(f"Invalid pin number: {num} (must be 0-46)")
if is_input:
# All ESP32 pins support input mode
pass
return value

View file

@ -10,38 +10,7 @@ static const char *const TAG = "esp32";
bool IDFInternalGPIOPin::isr_service_installed = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) bool IDFInternalGPIOPin::isr_service_installed = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
struct ISRPinArg { static gpio_mode_t IRAM_ATTR flags_to_mode(gpio::Flags flags) {
gpio_num_t pin;
bool inverted;
};
ISRInternalGPIOPin IDFInternalGPIOPin::to_isr() const {
auto *arg = new ISRPinArg{}; // NOLINT(cppcoreguidelines-owning-memory)
arg->pin = pin_;
arg->inverted = inverted_;
return ISRInternalGPIOPin((void *) arg);
}
void IDFInternalGPIOPin::setup() {
pin_mode(flags_);
gpio_set_drive_capability(pin_, drive_strength_);
}
void IDFInternalGPIOPin::pin_mode(gpio::Flags flags) {
gpio_config_t conf{};
conf.pin_bit_mask = 1ULL << static_cast<uint32_t>(pin_);
conf.mode = flags_to_mode(flags);
conf.pull_up_en = flags & gpio::FLAG_PULLUP ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE;
conf.pull_down_en = flags & gpio::FLAG_PULLDOWN ? GPIO_PULLDOWN_ENABLE : GPIO_PULLDOWN_DISABLE;
conf.intr_type = GPIO_INTR_DISABLE;
gpio_config(&conf);
}
bool IDFInternalGPIOPin::digital_read() { return bool(gpio_get_level(pin_)) != inverted_; }
void IDFInternalGPIOPin::digital_write(bool value) { gpio_set_level(pin_, value != inverted_ ? 1 : 0); }
gpio_mode_t IDFInternalGPIOPin::flags_to_mode(gpio::Flags flags) {
flags = (gpio::Flags)(flags & ~(gpio::FLAG_PULLUP | gpio::FLAG_PULLDOWN)); flags = (gpio::Flags)(flags & ~(gpio::FLAG_PULLUP | gpio::FLAG_PULLDOWN));
if (flags == gpio::FLAG_NONE) { if (flags == gpio::FLAG_NONE) {
return GPIO_MODE_DISABLE; return GPIO_MODE_DISABLE;
@ -61,6 +30,18 @@ gpio_mode_t IDFInternalGPIOPin::flags_to_mode(gpio::Flags flags) {
} }
} }
struct ISRPinArg {
gpio_num_t pin;
bool inverted;
};
ISRInternalGPIOPin IDFInternalGPIOPin::to_isr() const {
auto *arg = new ISRPinArg{}; // NOLINT(cppcoreguidelines-owning-memory)
arg->pin = pin_;
arg->inverted = inverted_;
return ISRInternalGPIOPin((void *) arg);
}
void IDFInternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const { void IDFInternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const {
gpio_int_type_t idf_type = GPIO_INTR_ANYEDGE; gpio_int_type_t idf_type = GPIO_INTR_ANYEDGE;
switch (type) { switch (type) {
@ -99,6 +80,35 @@ std::string IDFInternalGPIOPin::dump_summary() const {
return buffer; return buffer;
} }
void IDFInternalGPIOPin::setup() {
gpio_config_t conf{};
conf.pin_bit_mask = 1ULL << static_cast<uint32_t>(pin_);
conf.mode = flags_to_mode(flags_);
conf.pull_up_en = flags_ & gpio::FLAG_PULLUP ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE;
conf.pull_down_en = flags_ & gpio::FLAG_PULLDOWN ? GPIO_PULLDOWN_ENABLE : GPIO_PULLDOWN_DISABLE;
conf.intr_type = GPIO_INTR_DISABLE;
gpio_config(&conf);
gpio_set_drive_capability(pin_, drive_strength_);
}
void IDFInternalGPIOPin::pin_mode(gpio::Flags flags) {
// can't call gpio_config here because that logs in esp-idf which may cause issues
gpio_set_direction(pin_, flags_to_mode(flags));
gpio_pull_mode_t pull_mode = GPIO_FLOATING;
if (flags & (gpio::FLAG_PULLUP | gpio::FLAG_PULLDOWN)) {
pull_mode = GPIO_PULLUP_PULLDOWN;
} else if (flags & gpio::FLAG_PULLUP) {
pull_mode = GPIO_PULLUP_ONLY;
} else if (flags & gpio::FLAG_PULLDOWN) {
pull_mode = GPIO_PULLDOWN_ONLY;
}
gpio_set_pull_mode(pin_, pull_mode);
}
bool IDFInternalGPIOPin::digital_read() { return bool(gpio_get_level(pin_)) != inverted_; }
void IDFInternalGPIOPin::digital_write(bool value) { gpio_set_level(pin_, value != inverted_ ? 1 : 0); }
void IDFInternalGPIOPin::detach_interrupt() const { gpio_intr_disable(pin_); }
} // namespace esp32 } // namespace esp32
using namespace esp32; using namespace esp32;
@ -114,6 +124,19 @@ void IRAM_ATTR ISRInternalGPIOPin::digital_write(bool value) {
void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() { void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() {
// not supported // not supported
} }
void IRAM_ATTR ISRInternalGPIOPin::pin_mode(gpio::Flags flags) {
auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
gpio_set_direction(arg->pin, flags_to_mode(flags));
gpio_pull_mode_t pull_mode = GPIO_FLOATING;
if (flags & (gpio::FLAG_PULLUP | gpio::FLAG_PULLDOWN)) {
pull_mode = GPIO_PULLUP_PULLDOWN;
} else if (flags & gpio::FLAG_PULLUP) {
pull_mode = GPIO_PULLUP_ONLY;
} else if (flags & gpio::FLAG_PULLDOWN) {
pull_mode = GPIO_PULLDOWN_ONLY;
}
gpio_set_pull_mode(arg->pin, pull_mode);
}
} // namespace esphome } // namespace esphome

View file

@ -18,13 +18,12 @@ class IDFInternalGPIOPin : public InternalGPIOPin {
bool digital_read() override; bool digital_read() override;
void digital_write(bool value) override; void digital_write(bool value) override;
std::string dump_summary() const override; std::string dump_summary() const override;
void detach_interrupt() const override { gpio_intr_disable(pin_); } void detach_interrupt() const override;
ISRInternalGPIOPin to_isr() const override; ISRInternalGPIOPin to_isr() const override;
uint8_t get_pin() const override { return (uint8_t) pin_; } uint8_t get_pin() const override { return (uint8_t) pin_; }
bool is_inverted() const override { return inverted_; } bool is_inverted() const override { return inverted_; }
protected: protected:
static gpio_mode_t flags_to_mode(gpio::Flags flags);
void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override; void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override;
gpio_num_t pin_; gpio_num_t pin_;

View file

@ -18,7 +18,6 @@ from esphome.core import CORE
from esphome.components.esp32 import add_idf_sdkconfig_option from esphome.components.esp32 import add_idf_sdkconfig_option
DEPENDENCIES = ["esp32"] DEPENDENCIES = ["esp32"]
AUTO_LOAD = ["xiaomi_ble", "ruuvi_ble"]
CONF_ESP32_BLE_ID = "esp32_ble_id" CONF_ESP32_BLE_ID = "esp32_ble_id"
CONF_SCAN_PARAMETERS = "scan_parameters" CONF_SCAN_PARAMETERS = "scan_parameters"

View file

@ -2,10 +2,8 @@ import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome import pins from esphome import pins
from esphome.const import ( from esphome.const import (
CONF_DISABLED_BY_DEFAULT,
CONF_FREQUENCY, CONF_FREQUENCY,
CONF_ID, CONF_ID,
CONF_NAME,
CONF_PIN, CONF_PIN,
CONF_SCL, CONF_SCL,
CONF_SDA, CONF_SDA,
@ -17,8 +15,9 @@ from esphome.const import (
) )
from esphome.core import CORE from esphome.core import CORE
from esphome.components.esp32 import add_idf_sdkconfig_option from esphome.components.esp32 import add_idf_sdkconfig_option
from esphome.cpp_helpers import setup_entity
DEPENDENCIES = ["esp32", "api"] DEPENDENCIES = ["esp32"]
esp32_camera_ns = cg.esphome_ns.namespace("esp32_camera") esp32_camera_ns = cg.esphome_ns.namespace("esp32_camera")
ESP32Camera = esp32_camera_ns.class_("ESP32Camera", cg.PollingComponent, cg.EntityBase) ESP32Camera = esp32_camera_ns.class_("ESP32Camera", cg.PollingComponent, cg.EntityBase)
@ -63,11 +62,9 @@ CONF_TEST_PATTERN = "test_pattern"
camera_range_param = cv.int_range(min=-2, max=2) camera_range_param = cv.int_range(min=-2, max=2)
CONFIG_SCHEMA = cv.Schema( CONFIG_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(
{ {
cv.GenerateID(): cv.declare_id(ESP32Camera), cv.GenerateID(): cv.declare_id(ESP32Camera),
cv.Required(CONF_NAME): cv.string,
cv.Optional(CONF_DISABLED_BY_DEFAULT, default=False): cv.boolean,
cv.Required(CONF_DATA_PINS): cv.All( cv.Required(CONF_DATA_PINS): cv.All(
[pins.internal_gpio_input_pin_number], cv.Length(min=8, max=8) [pins.internal_gpio_input_pin_number], cv.Length(min=8, max=8)
), ),
@ -127,8 +124,8 @@ SETTERS = {
async def to_code(config): async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID], config[CONF_NAME]) var = cg.new_Pvariable(config[CONF_ID])
cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT])) await setup_entity(var, config)
await cg.register_component(var, config) await cg.register_component(var, config)
for key, setter in SETTERS.items(): for key, setter in SETTERS.items():

View file

@ -45,6 +45,7 @@ void ESP32Camera::dump_config() {
auto conf = this->config_; auto conf = this->config_;
ESP_LOGCONFIG(TAG, "ESP32 Camera:"); ESP_LOGCONFIG(TAG, "ESP32 Camera:");
ESP_LOGCONFIG(TAG, " Name: %s", this->name_.c_str()); ESP_LOGCONFIG(TAG, " Name: %s", this->name_.c_str());
ESP_LOGCONFIG(TAG, " Internal: %s", YESNO(this->internal_));
#ifdef USE_ARDUINO #ifdef USE_ARDUINO
ESP_LOGCONFIG(TAG, " Board Has PSRAM: %s", YESNO(psramFound())); ESP_LOGCONFIG(TAG, " Board Has PSRAM: %s", YESNO(psramFound()));
#endif // USE_ARDUINO #endif // USE_ARDUINO
@ -185,6 +186,7 @@ ESP32Camera::ESP32Camera(const std::string &name) : EntityBase(name) {
global_esp32_camera = this; global_esp32_camera = this;
} }
ESP32Camera::ESP32Camera() : ESP32Camera("") {}
void ESP32Camera::set_data_pins(std::array<uint8_t, 8> pins) { void ESP32Camera::set_data_pins(std::array<uint8_t, 8> pins) {
this->config_.pin_d0 = pins[0]; this->config_.pin_d0 = pins[0];
this->config_.pin_d1 = pins[1]; this->config_.pin_d1 = pins[1];

View file

@ -54,6 +54,7 @@ enum ESP32CameraFrameSize {
class ESP32Camera : public Component, public EntityBase { class ESP32Camera : public Component, public EntityBase {
public: public:
ESP32Camera(const std::string &name); ESP32Camera(const std::string &name);
ESP32Camera();
void set_data_pins(std::array<uint8_t, 8> pins); void set_data_pins(std::array<uint8_t, 8> pins);
void set_vsync_pin(uint8_t pin); void set_vsync_pin(uint8_t pin);
void set_href_pin(uint8_t pin); void set_href_pin(uint8_t pin);

View file

@ -0,0 +1,28 @@
import esphome.config_validation as cv
import esphome.codegen as cg
from esphome.const import CONF_ID, CONF_PORT, CONF_MODE
CODEOWNERS = ["@ayufan"]
DEPENDENCIES = ["esp32_camera"]
MULTI_CONF = True
esp32_camera_web_server_ns = cg.esphome_ns.namespace("esp32_camera_web_server")
CameraWebServer = esp32_camera_web_server_ns.class_("CameraWebServer", cg.Component)
Mode = esp32_camera_web_server_ns.enum("Mode")
MODES = {"STREAM": Mode.STREAM, "SNAPSHOT": Mode.SNAPSHOT}
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(CameraWebServer),
cv.Required(CONF_PORT): cv.port,
cv.Required(CONF_MODE): cv.enum(MODES, upper=True),
},
).extend(cv.COMPONENT_SCHEMA)
async def to_code(config):
server = cg.new_Pvariable(config[CONF_ID])
cg.add(server.set_port(config[CONF_PORT]))
cg.add(server.set_mode(config[CONF_MODE]))
await cg.register_component(server, config)

View file

@ -0,0 +1,240 @@
#ifdef USE_ESP32
#include "camera_web_server.h"
#include "esphome/core/application.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "esphome/core/util.h"
#include <cstdlib>
#include <esp_http_server.h>
#include <utility>
namespace esphome {
namespace esp32_camera_web_server {
static const int IMAGE_REQUEST_TIMEOUT = 2000;
static const char *const TAG = "esp32_camera_web_server";
#define PART_BOUNDARY "123456789000000000000987654321"
#define CONTENT_TYPE "image/jpeg"
#define CONTENT_LENGTH "Content-Length"
static const char *const STREAM_HEADER =
"HTTP/1.1 200\r\nAccess-Control-Allow-Origin: *\r\nContent-Type: multipart/x-mixed-replace;boundary=" PART_BOUNDARY
"\r\n";
static const char *const STREAM_500 = "HTTP/1.1 500\r\nContent-Type: text/plain\r\n\r\nNo frames send.\r\n";
static const char *const STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n";
static const char *const STREAM_PART = "Content-Type: " CONTENT_TYPE "\r\n" CONTENT_LENGTH ": %u\r\n\r\n";
CameraWebServer::CameraWebServer() {}
CameraWebServer::~CameraWebServer() {}
void CameraWebServer::setup() {
if (!esp32_camera::global_esp32_camera || esp32_camera::global_esp32_camera->is_failed()) {
this->mark_failed();
return;
}
this->semaphore_ = xSemaphoreCreateBinary();
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
config.server_port = this->port_;
config.ctrl_port = this->port_;
config.max_open_sockets = 1;
config.backlog_conn = 2;
if (httpd_start(&this->httpd_, &config) != ESP_OK) {
mark_failed();
return;
}
httpd_uri_t uri = {
.uri = "/",
.method = HTTP_GET,
.handler = [](struct httpd_req *req) { return ((CameraWebServer *) req->user_ctx)->handler_(req); },
.user_ctx = this};
httpd_register_uri_handler(this->httpd_, &uri);
esp32_camera::global_esp32_camera->add_image_callback([this](std::shared_ptr<esp32_camera::CameraImage> image) {
if (this->running_) {
this->image_ = std::move(image);
xSemaphoreGive(this->semaphore_);
}
});
}
void CameraWebServer::on_shutdown() {
this->running_ = false;
this->image_ = nullptr;
httpd_stop(this->httpd_);
this->httpd_ = nullptr;
vSemaphoreDelete(this->semaphore_);
this->semaphore_ = nullptr;
}
void CameraWebServer::dump_config() {
ESP_LOGCONFIG(TAG, "ESP32 Camera Web Server:");
ESP_LOGCONFIG(TAG, " Port: %d", this->port_);
if (this->mode_ == STREAM)
ESP_LOGCONFIG(TAG, " Mode: stream");
else
ESP_LOGCONFIG(TAG, " Mode: snapshot");
if (this->is_failed()) {
ESP_LOGE(TAG, " Setup Failed");
}
}
float CameraWebServer::get_setup_priority() const { return setup_priority::LATE; }
void CameraWebServer::loop() {
if (!this->running_) {
this->image_ = nullptr;
}
}
std::shared_ptr<esphome::esp32_camera::CameraImage> CameraWebServer::wait_for_image_() {
std::shared_ptr<esphome::esp32_camera::CameraImage> image;
image.swap(this->image_);
if (!image) {
// retry as we might still be fetching image
xSemaphoreTake(this->semaphore_, IMAGE_REQUEST_TIMEOUT / portTICK_PERIOD_MS);
image.swap(this->image_);
}
return image;
}
esp_err_t CameraWebServer::handler_(struct httpd_req *req) {
esp_err_t res = ESP_FAIL;
this->image_ = nullptr;
this->running_ = true;
switch (this->mode_) {
case STREAM:
res = this->streaming_handler_(req);
break;
case SNAPSHOT:
res = this->snapshot_handler_(req);
break;
}
this->running_ = false;
this->image_ = nullptr;
return res;
}
static esp_err_t httpd_send_all(httpd_req_t *r, const char *buf, size_t buf_len) {
int ret;
while (buf_len > 0) {
ret = httpd_send(r, buf, buf_len);
if (ret < 0) {
return ESP_FAIL;
}
buf += ret;
buf_len -= ret;
}
return ESP_OK;
}
esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) {
esp_err_t res = ESP_OK;
char part_buf[64];
// This manually constructs HTTP response to avoid chunked encoding
// which is not supported by some clients
res = httpd_send_all(req, STREAM_HEADER, strlen(STREAM_HEADER));
if (res != ESP_OK) {
ESP_LOGW(TAG, "STREAM: failed to set HTTP header");
return res;
}
uint32_t last_frame = millis();
uint32_t frames = 0;
while (res == ESP_OK && this->running_) {
if (esp32_camera::global_esp32_camera != nullptr) {
esp32_camera::global_esp32_camera->request_stream();
}
auto image = this->wait_for_image_();
if (!image) {
ESP_LOGW(TAG, "STREAM: failed to acquire frame");
res = ESP_FAIL;
}
if (res == ESP_OK) {
res = httpd_send_all(req, STREAM_BOUNDARY, strlen(STREAM_BOUNDARY));
}
if (res == ESP_OK) {
size_t hlen = snprintf(part_buf, 64, STREAM_PART, image->get_data_length());
res = httpd_send_all(req, part_buf, hlen);
}
if (res == ESP_OK) {
res = httpd_send_all(req, (const char *) image->get_data_buffer(), image->get_data_length());
}
if (res == ESP_OK) {
frames++;
int64_t frame_time = millis() - last_frame;
last_frame = millis();
ESP_LOGD(TAG, "MJPG: %uB %ums (%.1ffps)", (uint32_t) image->get_data_length(), (uint32_t) frame_time,
1000.0 / (uint32_t) frame_time);
}
}
if (!frames) {
res = httpd_send_all(req, STREAM_500, strlen(STREAM_500));
}
ESP_LOGI(TAG, "STREAM: closed. Frames: %u", frames);
return res;
}
esp_err_t CameraWebServer::snapshot_handler_(struct httpd_req *req) {
esp_err_t res = ESP_OK;
if (esp32_camera::global_esp32_camera != nullptr) {
esp32_camera::global_esp32_camera->request_image();
}
auto image = this->wait_for_image_();
if (!image) {
ESP_LOGW(TAG, "SNAPSHOT: failed to acquire frame");
httpd_resp_send_500(req);
res = ESP_FAIL;
return res;
}
res = httpd_resp_set_type(req, CONTENT_TYPE);
if (res != ESP_OK) {
ESP_LOGW(TAG, "SNAPSHOT: failed to set HTTP response type");
return res;
}
httpd_resp_set_hdr(req, "Content-Disposition", "inline; filename=capture.jpg");
if (res == ESP_OK) {
res = httpd_resp_set_hdr(req, CONTENT_LENGTH, esphome::to_string(image->get_data_length()).c_str());
}
if (res == ESP_OK) {
res = httpd_resp_send(req, (const char *) image->get_data_buffer(), image->get_data_length());
}
return res;
}
} // namespace esp32_camera_web_server
} // namespace esphome
#endif // USE_ESP32

View file

@ -0,0 +1,51 @@
#pragma once
#ifdef USE_ESP32
#include <freertos/FreeRTOS.h>
#include <freertos/semphr.h>
#include "esphome/components/esp32_camera/esp32_camera.h"
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include "esphome/core/preferences.h"
struct httpd_req;
namespace esphome {
namespace esp32_camera_web_server {
enum Mode { STREAM, SNAPSHOT };
class CameraWebServer : public Component {
public:
CameraWebServer();
~CameraWebServer();
void setup() override;
void on_shutdown() override;
void dump_config() override;
float get_setup_priority() const override;
void set_port(uint16_t port) { this->port_ = port; }
void set_mode(Mode mode) { this->mode_ = mode; }
void loop() override;
protected:
std::shared_ptr<esphome::esp32_camera::CameraImage> wait_for_image_();
esp_err_t handler_(struct httpd_req *req);
esp_err_t streaming_handler_(struct httpd_req *req);
esp_err_t snapshot_handler_(struct httpd_req *req);
protected:
uint16_t port_{0};
void *httpd_{nullptr};
SemaphoreHandle_t semaphore_;
std::shared_ptr<esphome::esp32_camera::CameraImage> image_;
bool running_{false};
Mode mode_{STREAM};
};
} // namespace esp32_camera_web_server
} // namespace esphome
#endif // USE_ESP32

View file

@ -93,12 +93,12 @@ def _arduino_check_versions(value):
platform_version = value.get(CONF_PLATFORM_VERSION) platform_version = value.get(CONF_PLATFORM_VERSION)
if platform_version is None: if platform_version is None:
if version >= cv.Version(3, 0, 0): if version >= cv.Version(3, 0, 0):
platform_version = ARDUINO_3_PLATFORM_VERSION platform_version = _parse_platform_version(str(ARDUINO_3_PLATFORM_VERSION))
elif version >= cv.Version(2, 5, 0): elif version >= cv.Version(2, 5, 0):
platform_version = ARDUINO_2_PLATFORM_VERSION platform_version = _parse_platform_version(str(ARDUINO_2_PLATFORM_VERSION))
else: else:
platform_version = cv.Version(1, 8, 0) platform_version = _parse_platform_version(str(cv.Version(1, 8, 0)))
value[CONF_PLATFORM_VERSION] = str(platform_version) value[CONF_PLATFORM_VERSION] = platform_version
if version != RECOMMENDED_ARDUINO_FRAMEWORK_VERSION: if version != RECOMMENDED_ARDUINO_FRAMEWORK_VERSION:
_LOGGER.warning( _LOGGER.warning(
@ -109,13 +109,22 @@ 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/espressif8266 @ {value}"
except cv.Invalid:
return value
CONF_PLATFORM_VERSION = "platform_version" CONF_PLATFORM_VERSION = "platform_version"
ARDUINO_FRAMEWORK_SCHEMA = cv.All( ARDUINO_FRAMEWORK_SCHEMA = cv.All(
cv.Schema( cv.Schema(
{ {
cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict, cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict,
cv.Optional(CONF_SOURCE): cv.string_strict, cv.Optional(CONF_SOURCE): cv.string_strict,
cv.Optional(CONF_PLATFORM_VERSION): cv.string_strict, cv.Optional(CONF_PLATFORM_VERSION): _parse_platform_version,
} }
), ),
_arduino_check_versions, _arduino_check_versions,
@ -142,21 +151,22 @@ CONFIG_SCHEMA = cv.All(
async def to_code(config): async def to_code(config):
cg.add(esp8266_ns.setup_preferences()) cg.add(esp8266_ns.setup_preferences())
cg.add_platformio_option("lib_ldf_mode", "off")
cg.add_platformio_option("board", config[CONF_BOARD]) cg.add_platformio_option("board", config[CONF_BOARD])
cg.add_build_flag("-DUSE_ESP8266") cg.add_build_flag("-DUSE_ESP8266")
cg.add_define("ESPHOME_BOARD", config[CONF_BOARD]) cg.add_define("ESPHOME_BOARD", config[CONF_BOARD])
cg.add_define("ESPHOME_VARIANT", "ESP8266")
conf = config[CONF_FRAMEWORK] conf = config[CONF_FRAMEWORK]
cg.add_platformio_option("framework", "arduino") cg.add_platformio_option("framework", "arduino")
cg.add_build_flag("-DUSE_ARDUINO") cg.add_build_flag("-DUSE_ARDUINO")
cg.add_build_flag("-DUSE_ESP8266_FRAMEWORK_ARDUINO") cg.add_build_flag("-DUSE_ESP8266_FRAMEWORK_ARDUINO")
cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION])
cg.add_platformio_option( cg.add_platformio_option(
"platform_packages", "platform_packages",
[f"platformio/framework-arduinoespressif8266 @ {conf[CONF_SOURCE]}"], [f"platformio/framework-arduinoespressif8266 @ {conf[CONF_SOURCE]}"],
) )
cg.add_platformio_option(
"platform", f"platformio/espressif8266 @ {conf[CONF_PLATFORM_VERSION]}"
)
# Default for platformio is LWIP2_LOW_MEMORY with: # Default for platformio is LWIP2_LOW_MEMORY with:
# - MSS=536 # - MSS=536

View file

@ -12,7 +12,7 @@ void IRAM_ATTR HOT yield() { ::yield(); }
uint32_t IRAM_ATTR HOT millis() { return ::millis(); } uint32_t IRAM_ATTR HOT millis() { return ::millis(); }
void IRAM_ATTR HOT delay(uint32_t ms) { ::delay(ms); } void IRAM_ATTR HOT delay(uint32_t ms) { ::delay(ms); }
uint32_t IRAM_ATTR HOT micros() { return ::micros(); } uint32_t IRAM_ATTR HOT micros() { return ::micros(); }
void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { ::delayMicroseconds(us); } void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { delay_microseconds_safe(us); }
void arch_restart() { void arch_restart() {
ESP.restart(); // NOLINT(readability-static-accessed-through-instance) ESP.restart(); // NOLINT(readability-static-accessed-through-instance)
// restart() doesn't always end execution // restart() doesn't always end execution

View file

@ -8,6 +8,29 @@ namespace esp8266 {
static const char *const TAG = "esp8266"; static const char *const TAG = "esp8266";
static int IRAM_ATTR flags_to_mode(gpio::Flags flags, uint8_t pin) {
if (flags == gpio::FLAG_INPUT) {
return INPUT;
} else if (flags == gpio::FLAG_OUTPUT) {
return OUTPUT;
} else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) {
if (pin == 16) {
// GPIO16 doesn't have a pullup, so pinMode would fail.
// However, sometimes this method is called with pullup mode anyway
// for example from dallas one_wire. For those cases convert this
// to a INPUT mode.
return INPUT;
}
return INPUT_PULLUP;
} else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLDOWN)) {
return INPUT_PULLDOWN_16;
} else if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) {
return OUTPUT_OPEN_DRAIN;
} else {
return 0;
}
}
struct ISRPinArg { struct ISRPinArg {
uint8_t pin; uint8_t pin;
bool inverted; bool inverted;
@ -43,28 +66,7 @@ void ESP8266GPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::Int
attachInterruptArg(pin_, func, arg, arduino_mode); attachInterruptArg(pin_, func, arg, arduino_mode);
} }
void ESP8266GPIOPin::pin_mode(gpio::Flags flags) { void ESP8266GPIOPin::pin_mode(gpio::Flags flags) {
uint8_t mode; pinMode(pin_, flags_to_mode(flags, pin_)); // NOLINT
if (flags == gpio::FLAG_INPUT) {
mode = INPUT;
} else if (flags == gpio::FLAG_OUTPUT) {
mode = OUTPUT;
} else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) {
mode = INPUT_PULLUP;
if (pin_ == 16) {
// GPIO16 doesn't have a pullup, so pinMode would fail.
// However, sometimes this method is called with pullup mode anyway
// for example from dallas one_wire. For those cases convert this
// to a INPUT mode.
mode = INPUT;
}
} else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLDOWN)) {
mode = INPUT_PULLDOWN_16;
} else if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) {
mode = OUTPUT_OPEN_DRAIN;
} else {
return;
}
pinMode(pin_, mode); // NOLINT
} }
std::string ESP8266GPIOPin::dump_summary() const { std::string ESP8266GPIOPin::dump_summary() const {
@ -97,6 +99,10 @@ void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() {
auto *arg = reinterpret_cast<ISRPinArg *>(arg_); auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, 1UL << arg->pin); GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, 1UL << arg->pin);
} }
void IRAM_ATTR ISRInternalGPIOPin::pin_mode(gpio::Flags flags) {
auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
pinMode(arg->pin, flags_to_mode(flags, arg->pin)); // NOLINT
}
} // namespace esphome } // namespace esphome

View file

@ -168,7 +168,9 @@ void EthernetComponent::start_connect_() {
esp_err_t err; esp_err_t err;
err = tcpip_adapter_set_hostname(TCPIP_ADAPTER_IF_ETH, App.get_name().c_str()); err = tcpip_adapter_set_hostname(TCPIP_ADAPTER_IF_ETH, App.get_name().c_str());
ESPHL_ERROR_CHECK(err, "ETH set hostname error"); if (err != ERR_OK) {
ESP_LOGW(TAG, "tcpip_adapter_set_hostname failed: %s", esp_err_to_name(err));
}
tcpip_adapter_ip_info_t info; tcpip_adapter_ip_info_t info;
if (this->manual_ip_.has_value()) { if (this->manual_ip_.has_value()) {

View file

@ -43,19 +43,27 @@ def validate_source_shorthand(value):
# Regex for GitHub repo name with optional branch/tag # Regex for GitHub repo name with optional branch/tag
# Note: git allows other branch/tag names as well, but never seen them used before # Note: git allows other branch/tag names as well, but never seen them used before
m = re.match( m = re.match(
r"github://([a-zA-Z0-9\-]+)/([a-zA-Z0-9\-\._]+)(?:@([a-zA-Z0-9\-_.\./]+))?", r"github://(?:([a-zA-Z0-9\-]+)/([a-zA-Z0-9\-\._]+)(?:@([a-zA-Z0-9\-_.\./]+))?|pr#([0-9]+))",
value, value,
) )
if m is None: if m is None:
raise cv.Invalid( raise cv.Invalid(
"Source is not a file system path or in expected github://username/name[@branch-or-tag] format!" "Source is not a file system path, in expected github://username/name[@branch-or-tag] or github://pr#1234 format!"
) )
if m.group(4):
conf = {
CONF_TYPE: TYPE_GIT,
CONF_URL: "https://github.com/esphome/esphome.git",
CONF_REF: f"pull/{m.group(4)}/head",
}
else:
conf = { conf = {
CONF_TYPE: TYPE_GIT, CONF_TYPE: TYPE_GIT,
CONF_URL: f"https://github.com/{m.group(1)}/{m.group(2)}.git", CONF_URL: f"https://github.com/{m.group(1)}/{m.group(2)}.git",
} }
if m.group(3): if m.group(3):
conf[CONF_REF] = m.group(3) conf[CONF_REF] = m.group(3)
return SOURCE_SCHEMA(conf) return SOURCE_SCHEMA(conf)

View file

@ -74,7 +74,7 @@ void EZOSensor::loop() {
if (buf[0] != 1) if (buf[0] != 1)
return; return;
float val = strtof((char *) &buf[1], nullptr); float val = parse_number<float>((char *) &buf[1], sizeof(buf) - 1).value_or(0);
this->publish_state(val); this->publish_state(val);
} }

View file

@ -8,6 +8,7 @@ from esphome.const import (
CONF_LAST_FINGER_ID, CONF_LAST_FINGER_ID,
CONF_SECURITY_LEVEL, CONF_SECURITY_LEVEL,
CONF_STATUS, CONF_STATUS,
ENTITY_CATEGORY_DIAGNOSTIC,
ICON_ACCOUNT, ICON_ACCOUNT,
ICON_ACCOUNT_CHECK, ICON_ACCOUNT_CHECK,
ICON_DATABASE, ICON_DATABASE,
@ -26,30 +27,36 @@ CONFIG_SCHEMA = cv.Schema(
icon=ICON_FINGERPRINT, icon=ICON_FINGERPRINT,
accuracy_decimals=0, accuracy_decimals=0,
state_class=STATE_CLASS_NONE, state_class=STATE_CLASS_NONE,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
), ),
cv.Optional(CONF_STATUS): sensor.sensor_schema( cv.Optional(CONF_STATUS): sensor.sensor_schema(
accuracy_decimals=0, accuracy_decimals=0,
state_class=STATE_CLASS_NONE, state_class=STATE_CLASS_NONE,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
), ),
cv.Optional(CONF_CAPACITY): sensor.sensor_schema( cv.Optional(CONF_CAPACITY): sensor.sensor_schema(
icon=ICON_DATABASE, icon=ICON_DATABASE,
accuracy_decimals=0, accuracy_decimals=0,
state_class=STATE_CLASS_NONE, state_class=STATE_CLASS_NONE,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
), ),
cv.Optional(CONF_SECURITY_LEVEL): sensor.sensor_schema( cv.Optional(CONF_SECURITY_LEVEL): sensor.sensor_schema(
icon=ICON_SECURITY, icon=ICON_SECURITY,
accuracy_decimals=0, accuracy_decimals=0,
state_class=STATE_CLASS_NONE, state_class=STATE_CLASS_NONE,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
), ),
cv.Optional(CONF_LAST_FINGER_ID): sensor.sensor_schema( cv.Optional(CONF_LAST_FINGER_ID): sensor.sensor_schema(
icon=ICON_ACCOUNT, icon=ICON_ACCOUNT,
accuracy_decimals=0, accuracy_decimals=0,
state_class=STATE_CLASS_NONE, state_class=STATE_CLASS_NONE,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
), ),
cv.Optional(CONF_LAST_CONFIDENCE): sensor.sensor_schema( cv.Optional(CONF_LAST_CONFIDENCE): sensor.sensor_schema(
icon=ICON_ACCOUNT_CHECK, icon=ICON_ACCOUNT_CHECK,
accuracy_decimals=0, accuracy_decimals=0,
state_class=STATE_CLASS_NONE, state_class=STATE_CLASS_NONE,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
), ),
} }
) )

View file

@ -10,7 +10,7 @@ static const char *const TAG = "homeassistant.sensor";
void HomeassistantSensor::setup() { void HomeassistantSensor::setup() {
api::global_api_server->subscribe_home_assistant_state( api::global_api_server->subscribe_home_assistant_state(
this->entity_id_, this->attribute_, [this](const std::string &state) { this->entity_id_, this->attribute_, [this](const std::string &state) {
auto val = parse_float(state); auto val = parse_number<float>(state);
if (!val.has_value()) { if (!val.has_value()) {
ESP_LOGW(TAG, "Can't convert '%s' to number!", state.c_str()); ESP_LOGW(TAG, "Can't convert '%s' to number!", state.c_str());
this->publish_state(NAN); this->publish_state(NAN);

View file

@ -44,7 +44,7 @@ void HrxlMaxsonarWrComponent::check_buffer_() {
if (this->buffer_.length() == MAX_DATA_LENGTH_BYTES && this->buffer_[0] == 'R' && if (this->buffer_.length() == MAX_DATA_LENGTH_BYTES && this->buffer_[0] == 'R' &&
this->buffer_.back() == static_cast<char>(ASCII_CR)) { this->buffer_.back() == static_cast<char>(ASCII_CR)) {
int millimeters = strtol(this->buffer_.substr(1, MAX_DATA_LENGTH_BYTES - 2).c_str(), nullptr, 10); int millimeters = parse_number<int>(this->buffer_.substr(1, MAX_DATA_LENGTH_BYTES - 2)).value_or(0);
float meters = float(millimeters) / 1000.0; float meters = float(millimeters) / 1000.0;
ESP_LOGV(TAG, "Distance from sensor: %d mm, %f m", millimeters, meters); ESP_LOGV(TAG, "Distance from sensor: %d mm, %f m", millimeters, meters);
this->publish_state(meters); this->publish_state(meters);

View file

@ -96,6 +96,8 @@ async def to_code(config):
if CORE.is_esp32: if CORE.is_esp32:
cg.add_library("WiFiClientSecure", None) cg.add_library("WiFiClientSecure", None)
cg.add_library("HTTPClient", None) cg.add_library("HTTPClient", None)
if CORE.is_esp8266:
cg.add_library("ESP8266HTTPClient", None)
await cg.register_component(var, config) await cg.register_component(var, config)

View file

@ -9,8 +9,8 @@ static const char *const TAG = "htu21d";
static const uint8_t HTU21D_ADDRESS = 0x40; static const uint8_t HTU21D_ADDRESS = 0x40;
static const uint8_t HTU21D_REGISTER_RESET = 0xFE; static const uint8_t HTU21D_REGISTER_RESET = 0xFE;
static const uint8_t HTU21D_REGISTER_TEMPERATURE = 0xE3; static const uint8_t HTU21D_REGISTER_TEMPERATURE = 0xF3;
static const uint8_t HTU21D_REGISTER_HUMIDITY = 0xE5; static const uint8_t HTU21D_REGISTER_HUMIDITY = 0xF5;
static const uint8_t HTU21D_REGISTER_STATUS = 0xE7; static const uint8_t HTU21D_REGISTER_STATUS = 0xE7;
void HTU21DComponent::setup() { void HTU21DComponent::setup() {

View file

@ -1,6 +1,7 @@
#pragma once #pragma once
#include "i2c_bus.h" #include "i2c_bus.h"
#include "esphome/core/helpers.h"
#include "esphome/core/optional.h" #include "esphome/core/optional.h"
#include <array> #include <array>
#include <vector> #include <vector>
@ -32,16 +33,8 @@ class I2CRegister {
// like ntohs/htons but without including networking headers. // like ntohs/htons but without including networking headers.
// ("i2c" byte order is big-endian) // ("i2c" byte order is big-endian)
inline uint16_t i2ctohs(uint16_t i2cshort) { inline uint16_t i2ctohs(uint16_t i2cshort) { return convert_big_endian(i2cshort); }
union { inline uint16_t htoi2cs(uint16_t hostshort) { return convert_big_endian(hostshort); }
uint16_t x;
uint8_t y[2];
} conv;
conv.x = i2cshort;
return ((uint16_t) conv.y[0] << 8) | ((uint16_t) conv.y[1] << 0);
}
inline uint16_t htoi2cs(uint16_t hostshort) { return i2ctohs(hostshort); }
class I2CDevice { class I2CDevice {
public: public:

View file

@ -7,11 +7,13 @@ ImprovCommand parse_improv_data(const std::vector<uint8_t> &data) {
} }
ImprovCommand parse_improv_data(const uint8_t *data, size_t length) { ImprovCommand parse_improv_data(const uint8_t *data, size_t length) {
ImprovCommand improv_command;
Command command = (Command) data[0]; Command command = (Command) data[0];
uint8_t data_length = data[1]; uint8_t data_length = data[1];
if (data_length != length - 3) { if (data_length != length - 3) {
return {.command = UNKNOWN}; improv_command.command = UNKNOWN;
return improv_command;
} }
uint8_t checksum = data[length - 1]; uint8_t checksum = data[length - 1];
@ -22,7 +24,8 @@ ImprovCommand parse_improv_data(const uint8_t *data, size_t length) {
} }
if ((uint8_t) calculated_checksum != checksum) { if ((uint8_t) calculated_checksum != checksum) {
return {.command = BAD_CHECKSUM}; improv_command.command = BAD_CHECKSUM;
return improv_command;
} }
if (command == WIFI_SETTINGS) { if (command == WIFI_SETTINGS) {
@ -39,9 +42,8 @@ ImprovCommand parse_improv_data(const uint8_t *data, size_t length) {
return {.command = command, .ssid = ssid, .password = password}; return {.command = command, .ssid = ssid, .password = password};
} }
return { improv_command.command = command;
.command = command, return improv_command;
};
} }
std::vector<uint8_t> build_rpc_response(Command command, const std::vector<std::string> &datum) { std::vector<uint8_t> build_rpc_response(Command command, const std::vector<std::string> &datum) {

View file

@ -1,8 +1,8 @@
#pragma once #pragma once
#ifdef USE_ARDUINO #ifdef ARDUINO
#include "WString.h" #include "WString.h"
#endif // USE_ARDUINO #endif // ARDUINO
#include <cstdint> #include <cstdint>
#include <string> #include <string>
@ -38,6 +38,8 @@ enum Command : uint8_t {
UNKNOWN = 0x00, UNKNOWN = 0x00,
WIFI_SETTINGS = 0x01, WIFI_SETTINGS = 0x01,
IDENTIFY = 0x02, IDENTIFY = 0x02,
GET_CURRENT_STATE = 0x02,
GET_DEVICE_INFO = 0x03,
BAD_CHECKSUM = 0xFF, BAD_CHECKSUM = 0xFF,
}; };
@ -53,8 +55,8 @@ ImprovCommand parse_improv_data(const std::vector<uint8_t> &data);
ImprovCommand parse_improv_data(const uint8_t *data, size_t length); ImprovCommand parse_improv_data(const uint8_t *data, size_t length);
std::vector<uint8_t> build_rpc_response(Command command, const std::vector<std::string> &datum); std::vector<uint8_t> build_rpc_response(Command command, const std::vector<std::string> &datum);
#ifdef USE_ARDUINO #ifdef ARDUINO
std::vector<uint8_t> build_rpc_response(Command command, const std::vector<String> &datum); std::vector<uint8_t> build_rpc_response(Command command, const std::vector<String> &datum);
#endif // USE_ARDUINO #endif // ARDUINO
} // namespace improv } // namespace improv

View file

@ -0,0 +1,33 @@
from esphome.const import CONF_BAUD_RATE, CONF_ID, CONF_LOGGER
import esphome.codegen as cg
import esphome.config_validation as cv
import esphome.final_validate as fv
CODEOWNERS = ["@esphome/core"]
DEPENDENCIES = ["logger", "wifi"]
AUTO_LOAD = ["improv"]
improv_serial_ns = cg.esphome_ns.namespace("improv_serial")
ImprovSerialComponent = improv_serial_ns.class_("ImprovSerialComponent", cg.Component)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(ImprovSerialComponent),
}
).extend(cv.COMPONENT_SCHEMA)
def validate_logger_baud_rate(config):
logger_conf = fv.full_config.get()[CONF_LOGGER]
if logger_conf[CONF_BAUD_RATE] == 0:
raise cv.Invalid("improv_serial requires the logger baud_rate to be not 0")
return config
FINAL_VALIDATE_SCHEMA = validate_logger_baud_rate
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)

View file

@ -0,0 +1,250 @@
#include "improv_serial_component.h"
#include "esphome/core/application.h"
#include "esphome/core/defines.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#include "esphome/core/version.h"
#include "esphome/components/logger/logger.h"
namespace esphome {
namespace improv_serial {
static const char *const TAG = "improv_serial";
void ImprovSerialComponent::setup() {
global_improv_serial_component = this;
#ifdef USE_ARDUINO
this->hw_serial_ = logger::global_logger->get_hw_serial();
#endif
#ifdef USE_ESP_IDF
this->uart_num_ = logger::global_logger->get_uart_num();
#endif
if (wifi::global_wifi_component->has_sta()) {
this->state_ = improv::STATE_PROVISIONED;
}
}
void ImprovSerialComponent::dump_config() { ESP_LOGCONFIG(TAG, "Improv Serial:"); }
int ImprovSerialComponent::available_() {
#ifdef USE_ARDUINO
return this->hw_serial_->available();
#endif
#ifdef USE_ESP_IDF
size_t available;
uart_get_buffered_data_len(this->uart_num_, &available);
return available;
#endif
}
uint8_t ImprovSerialComponent::read_byte_() {
uint8_t data;
#ifdef USE_ARDUINO
this->hw_serial_->readBytes(&data, 1);
#endif
#ifdef USE_ESP_IDF
uart_read_bytes(this->uart_num_, &data, 1, 20 / portTICK_RATE_MS);
#endif
return data;
}
void ImprovSerialComponent::write_data_(std::vector<uint8_t> &data) {
data.push_back('\n');
#ifdef USE_ARDUINO
this->hw_serial_->write(data.data(), data.size());
#endif
#ifdef USE_ESP_IDF
uart_write_bytes(this->uart_num_, data.data(), data.size());
#endif
}
void ImprovSerialComponent::loop() {
const uint32_t now = millis();
if (now - this->last_read_byte_ > 50) {
this->rx_buffer_.clear();
this->last_read_byte_ = now;
}
while (this->available_()) {
uint8_t byte = this->read_byte_();
if (this->parse_improv_serial_byte_(byte)) {
this->last_read_byte_ = now;
} else {
this->rx_buffer_.clear();
}
}
if (this->state_ == improv::STATE_PROVISIONING) {
if (wifi::global_wifi_component->is_connected()) {
wifi::global_wifi_component->save_wifi_sta(this->connecting_sta_.get_ssid(),
this->connecting_sta_.get_password());
this->connecting_sta_ = {};
this->cancel_timeout("wifi-connect-timeout");
this->set_state_(improv::STATE_PROVISIONED);
std::vector<uint8_t> url = this->build_rpc_settings_response_(improv::WIFI_SETTINGS);
this->send_response_(url);
}
}
}
std::vector<uint8_t> ImprovSerialComponent::build_rpc_settings_response_(improv::Command command) {
std::string url = "https://my.home-assistant.io/redirect/config_flow_start?domain=esphome";
std::vector<std::string> urls = {url};
#ifdef USE_WEBSERVER
auto ip = wifi::global_wifi_component->wifi_sta_ip();
std::string webserver_url = "http://" + ip.str() + ":" + to_string(WEBSERVER_PORT);
urls.push_back(webserver_url);
#endif
std::vector<uint8_t> data = improv::build_rpc_response(command, urls);
return data;
}
std::vector<uint8_t> ImprovSerialComponent::build_version_info_() {
std::vector<std::string> infos = {"ESPHome", ESPHOME_VERSION, ESPHOME_VARIANT, App.get_name()};
std::vector<uint8_t> data = improv::build_rpc_response(improv::GET_DEVICE_INFO, infos);
return data;
};
bool ImprovSerialComponent::parse_improv_serial_byte_(uint8_t byte) {
size_t at = this->rx_buffer_.size();
this->rx_buffer_.push_back(byte);
ESP_LOGD(TAG, "Improv Serial byte: 0x%02X", byte);
const uint8_t *raw = &this->rx_buffer_[0];
if (at == 0)
return byte == 'I';
if (at == 1)
return byte == 'M';
if (at == 2)
return byte == 'P';
if (at == 3)
return byte == 'R';
if (at == 4)
return byte == 'O';
if (at == 5)
return byte == 'V';
if (at == 6)
return byte == IMPROV_SERIAL_VERSION;
if (at == 7)
return true;
uint8_t type = raw[7];
if (at == 8)
return true;
uint8_t data_len = raw[8];
if (at < 8 + data_len)
return true;
if (at == 8 + data_len) {
if (type == TYPE_RPC) {
this->set_error_(improv::ERROR_NONE);
auto command = improv::parse_improv_data(&raw[9], data_len);
return this->parse_improv_payload_(command);
}
}
return true;
}
bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command) {
switch (command.command) {
case improv::BAD_CHECKSUM:
ESP_LOGW(TAG, "Error decoding Improv payload");
this->set_error_(improv::ERROR_INVALID_RPC);
return false;
case improv::WIFI_SETTINGS: {
wifi::WiFiAP sta{};
sta.set_ssid(command.ssid);
sta.set_password(command.password);
this->connecting_sta_ = sta;
wifi::global_wifi_component->set_sta(sta);
wifi::global_wifi_component->start_scanning();
this->set_state_(improv::STATE_PROVISIONING);
ESP_LOGD(TAG, "Received Improv wifi settings ssid=%s, password=" LOG_SECRET("%s"), command.ssid.c_str(),
command.password.c_str());
auto f = std::bind(&ImprovSerialComponent::on_wifi_connect_timeout_, this);
this->set_timeout("wifi-connect-timeout", 30000, f);
return true;
}
case improv::GET_CURRENT_STATE:
this->set_state_(this->state_);
if (this->state_ == improv::STATE_PROVISIONED) {
std::vector<uint8_t> url = this->build_rpc_settings_response_(improv::GET_CURRENT_STATE);
this->send_response_(url);
}
return true;
case improv::GET_DEVICE_INFO: {
std::vector<uint8_t> info = this->build_version_info_();
this->send_response_(info);
return true;
}
default: {
ESP_LOGW(TAG, "Unknown Improv payload");
this->set_error_(improv::ERROR_UNKNOWN_RPC);
return false;
}
}
}
void ImprovSerialComponent::set_state_(improv::State state) {
this->state_ = state;
std::vector<uint8_t> data = {'I', 'M', 'P', 'R', 'O', 'V'};
data.resize(11);
data[6] = IMPROV_SERIAL_VERSION;
data[7] = TYPE_CURRENT_STATE;
data[8] = 1;
data[9] = state;
uint8_t checksum = 0x00;
for (uint8_t d : data)
checksum += d;
data[10] = checksum;
this->write_data_(data);
}
void ImprovSerialComponent::set_error_(improv::Error error) {
std::vector<uint8_t> data = {'I', 'M', 'P', 'R', 'O', 'V'};
data.resize(11);
data[6] = IMPROV_SERIAL_VERSION;
data[7] = TYPE_ERROR_STATE;
data[8] = 1;
data[9] = error;
uint8_t checksum = 0x00;
for (uint8_t d : data)
checksum += d;
data[10] = checksum;
this->write_data_(data);
}
void ImprovSerialComponent::send_response_(std::vector<uint8_t> &response) {
std::vector<uint8_t> data = {'I', 'M', 'P', 'R', 'O', 'V'};
data.resize(9);
data[6] = IMPROV_SERIAL_VERSION;
data[7] = TYPE_RPC_RESPONSE;
data[8] = response.size();
data.insert(data.end(), response.begin(), response.end());
this->write_data_(data);
}
void ImprovSerialComponent::on_wifi_connect_timeout_() {
this->set_error_(improv::ERROR_UNABLE_TO_CONNECT);
this->set_state_(improv::STATE_AUTHORIZED);
ESP_LOGW(TAG, "Timed out trying to connect to given WiFi network");
wifi::global_wifi_component->clear_sta();
}
ImprovSerialComponent *global_improv_serial_component = // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
} // namespace improv_serial
} // namespace esphome

View file

@ -0,0 +1,69 @@
#pragma once
#include "esphome/components/improv/improv.h"
#include "esphome/components/wifi/wifi_component.h"
#include "esphome/core/component.h"
#include "esphome/core/defines.h"
#include "esphome/core/helpers.h"
#ifdef USE_ARDUINO
#include <HardwareSerial.h>
#endif
#ifdef USE_ESP_IDF
#include <driver/uart.h>
#endif
namespace esphome {
namespace improv_serial {
enum ImprovSerialType : uint8_t {
TYPE_CURRENT_STATE = 0x01,
TYPE_ERROR_STATE = 0x02,
TYPE_RPC = 0x03,
TYPE_RPC_RESPONSE = 0x04
};
static const uint8_t IMPROV_SERIAL_VERSION = 1;
class ImprovSerialComponent : public Component {
public:
void setup() override;
void loop() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::HARDWARE; }
protected:
bool parse_improv_serial_byte_(uint8_t byte);
bool parse_improv_payload_(improv::ImprovCommand &command);
void set_state_(improv::State state);
void set_error_(improv::Error error);
void send_response_(std::vector<uint8_t> &response);
void on_wifi_connect_timeout_();
std::vector<uint8_t> build_rpc_settings_response_(improv::Command command);
std::vector<uint8_t> build_version_info_();
int available_();
uint8_t read_byte_();
void write_data_(std::vector<uint8_t> &data);
#ifdef USE_ARDUINO
HardwareSerial *hw_serial_{nullptr};
#endif
#ifdef USE_ESP_IDF
uart_port_t uart_num_;
#endif
std::vector<uint8_t> rx_buffer_;
uint32_t last_read_byte_{0};
wifi::WiFiAP connecting_sta_;
improv::State state_{improv::STATE_AUTHORIZED};
};
extern ImprovSerialComponent
*global_improv_serial_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
} // namespace improv_serial
} // namespace esphome

View file

@ -9,6 +9,7 @@ from esphome.const import (
DEVICE_CLASS_BATTERY, DEVICE_CLASS_BATTERY,
DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_TEMPERATURE,
ENTITY_CATEGORY_DIAGNOSTIC,
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS, UNIT_CELSIUS,
UNIT_PERCENT, UNIT_PERCENT,
@ -53,6 +54,7 @@ CONFIG_SCHEMA = (
accuracy_decimals=0, accuracy_decimals=0,
device_class=DEVICE_CLASS_BATTERY, device_class=DEVICE_CLASS_BATTERY,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
), ),
} }
) )

View file

@ -221,7 +221,7 @@ UARTSelection Logger::get_uart() const { return this->uart_; }
void Logger::add_on_log_callback(std::function<void(int, const char *, const char *)> &&callback) { void Logger::add_on_log_callback(std::function<void(int, const char *, const char *)> &&callback) {
this->log_callback_.add(std::move(callback)); this->log_callback_.add(std::move(callback));
} }
float Logger::get_setup_priority() const { return setup_priority::HARDWARE - 1.0f; } float Logger::get_setup_priority() const { return setup_priority::BUS + 500.0f; }
const char *const LOG_LEVELS[] = {"NONE", "ERROR", "WARN", "INFO", "CONFIG", "DEBUG", "VERBOSE", "VERY_VERBOSE"}; const char *const LOG_LEVELS[] = {"NONE", "ERROR", "WARN", "INFO", "CONFIG", "DEBUG", "VERBOSE", "VERY_VERBOSE"};
#ifdef USE_ESP32 #ifdef USE_ESP32
const char *const UART_SELECTIONS[] = {"UART0", "UART1", "UART2"}; const char *const UART_SELECTIONS[] = {"UART0", "UART1", "UART2"};

View file

@ -13,10 +13,20 @@ CONF_SCROLL_DELAY = "scroll_delay"
CONF_SCROLL_ENABLE = "scroll_enable" CONF_SCROLL_ENABLE = "scroll_enable"
CONF_SCROLL_MODE = "scroll_mode" CONF_SCROLL_MODE = "scroll_mode"
CONF_REVERSE_ENABLE = "reverse_enable" CONF_REVERSE_ENABLE = "reverse_enable"
CONF_NUM_CHIP_LINES = "num_chip_lines"
CONF_CHIP_LINES_STYLE = "chip_lines_style"
integration_ns = cg.esphome_ns.namespace("max7219digit")
ChipLinesStyle = integration_ns.enum("ChipLinesStyle")
CHIP_LINES_STYLE = {
"ZIGZAG": ChipLinesStyle.ZIGZAG,
"SNAKE": ChipLinesStyle.SNAKE,
}
ScrollMode = integration_ns.enum("ScrollMode")
SCROLL_MODES = { SCROLL_MODES = {
"CONTINUOUS": 0, "CONTINUOUS": ScrollMode.CONTINUOUS,
"STOP": 1, "STOP": ScrollMode.STOP,
} }
CHIP_MODES = { CHIP_MODES = {
@ -37,6 +47,10 @@ CONFIG_SCHEMA = (
{ {
cv.GenerateID(): cv.declare_id(MAX7219Component), cv.GenerateID(): cv.declare_id(MAX7219Component),
cv.Optional(CONF_NUM_CHIPS, default=4): cv.int_range(min=1, max=255), cv.Optional(CONF_NUM_CHIPS, default=4): cv.int_range(min=1, max=255),
cv.Optional(CONF_NUM_CHIP_LINES, default=1): cv.int_range(min=1, max=255),
cv.Optional(CONF_CHIP_LINES_STYLE, default="SNAKE"): cv.enum(
CHIP_LINES_STYLE, upper=True
),
cv.Optional(CONF_INTENSITY, default=15): cv.int_range(min=0, max=15), cv.Optional(CONF_INTENSITY, default=15): cv.int_range(min=0, max=15),
cv.Optional(CONF_ROTATE_CHIP, default="0"): cv.enum(CHIP_MODES, upper=True), cv.Optional(CONF_ROTATE_CHIP, default="0"): cv.enum(CHIP_MODES, upper=True),
cv.Optional(CONF_SCROLL_MODE, default="CONTINUOUS"): cv.enum( cv.Optional(CONF_SCROLL_MODE, default="CONTINUOUS"): cv.enum(
@ -67,6 +81,8 @@ async def to_code(config):
await display.register_display(var, config) await display.register_display(var, config)
cg.add(var.set_num_chips(config[CONF_NUM_CHIPS])) cg.add(var.set_num_chips(config[CONF_NUM_CHIPS]))
cg.add(var.set_num_chip_lines(config[CONF_NUM_CHIP_LINES]))
cg.add(var.set_chip_lines_style(config[CONF_CHIP_LINES_STYLE]))
cg.add(var.set_intensity(config[CONF_INTENSITY])) cg.add(var.set_intensity(config[CONF_INTENSITY]))
cg.add(var.set_chip_orientation(config[CONF_ROTATE_CHIP])) cg.add(var.set_chip_orientation(config[CONF_ROTATE_CHIP]))
cg.add(var.set_scroll_speed(config[CONF_SCROLL_SPEED])) cg.add(var.set_scroll_speed(config[CONF_SCROLL_SPEED]))

View file

@ -26,9 +26,12 @@ void MAX7219Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up MAX7219_DIGITS..."); ESP_LOGCONFIG(TAG, "Setting up MAX7219_DIGITS...");
this->spi_setup(); this->spi_setup();
this->stepsleft_ = 0; this->stepsleft_ = 0;
this->max_displaybuffer_.reserve(500); // Create base space to write buffer for (int chip_line = 0; chip_line < this->num_chip_lines_; chip_line++) {
std::vector<uint8_t> vec(1);
this->max_displaybuffer_.push_back(vec);
// Initialize buffer with 0 for display so all non written pixels are blank // Initialize buffer with 0 for display so all non written pixels are blank
this->max_displaybuffer_.resize(this->num_chips_ * 8, 0); this->max_displaybuffer_[chip_line].resize(get_width_internal(), 0);
}
// let's assume the user has all 8 digits connected, only important in daisy chained setups anyway // let's assume the user has all 8 digits connected, only important in daisy chained setups anyway
this->send_to_all_(MAX7219_REGISTER_SCAN_LIMIT, 7); this->send_to_all_(MAX7219_REGISTER_SCAN_LIMIT, 7);
// let's use our own ASCII -> led pattern encoding // let's use our own ASCII -> led pattern encoding
@ -46,6 +49,8 @@ void MAX7219Component::setup() {
void MAX7219Component::dump_config() { void MAX7219Component::dump_config() {
ESP_LOGCONFIG(TAG, "MAX7219DIGIT:"); ESP_LOGCONFIG(TAG, "MAX7219DIGIT:");
ESP_LOGCONFIG(TAG, " Number of Chips: %u", this->num_chips_); ESP_LOGCONFIG(TAG, " Number of Chips: %u", this->num_chips_);
ESP_LOGCONFIG(TAG, " Number of Chips Lines: %u", this->num_chip_lines_);
ESP_LOGCONFIG(TAG, " Chips Lines Style : %u", this->chip_lines_style_);
ESP_LOGCONFIG(TAG, " Intensity: %u", this->intensity_); ESP_LOGCONFIG(TAG, " Intensity: %u", this->intensity_);
ESP_LOGCONFIG(TAG, " Scroll Mode: %u", this->scroll_mode_); ESP_LOGCONFIG(TAG, " Scroll Mode: %u", this->scroll_mode_);
ESP_LOGCONFIG(TAG, " Scroll Speed: %u", this->scroll_speed_); ESP_LOGCONFIG(TAG, " Scroll Speed: %u", this->scroll_speed_);
@ -59,19 +64,19 @@ void MAX7219Component::loop() {
uint32_t now = millis(); uint32_t now = millis();
// check if the buffer has shrunk past the current position since last update // check if the buffer has shrunk past the current position since last update
if ((this->max_displaybuffer_.size() >= this->old_buffer_size_ + 3) || if ((this->max_displaybuffer_[0].size() >= this->old_buffer_size_ + 3) ||
(this->max_displaybuffer_.size() <= this->old_buffer_size_ - 3)) { (this->max_displaybuffer_[0].size() <= this->old_buffer_size_ - 3)) {
this->stepsleft_ = 0; this->stepsleft_ = 0;
this->display(); this->display();
this->old_buffer_size_ = this->max_displaybuffer_.size(); this->old_buffer_size_ = this->max_displaybuffer_[0].size();
} }
// Reset the counter back to 0 when full string has been displayed. // Reset the counter back to 0 when full string has been displayed.
if (this->stepsleft_ > this->max_displaybuffer_.size()) if (this->stepsleft_ > this->max_displaybuffer_[0].size())
this->stepsleft_ = 0; this->stepsleft_ = 0;
// Return if there is no need to scroll or scroll is off // Return if there is no need to scroll or scroll is off
if (!this->scroll_ || (this->max_displaybuffer_.size() <= this->num_chips_ * 8)) { if (!this->scroll_ || (this->max_displaybuffer_[0].size() <= get_width_internal())) {
this->display(); this->display();
return; return;
} }
@ -82,8 +87,8 @@ void MAX7219Component::loop() {
} }
// Dwell time at end of string in case of stop at end // Dwell time at end of string in case of stop at end
if (this->scroll_mode_ == 1) { if (this->scroll_mode_ == ScrollMode::STOP) {
if (this->stepsleft_ >= this->max_displaybuffer_.size() - this->num_chips_ * 8 + 1) { if (this->stepsleft_ >= this->max_displaybuffer_[0].size() - get_width_internal() + 1) {
if (now - this->last_scroll_ >= this->scroll_dwell_) { if (now - this->last_scroll_ >= this->scroll_dwell_) {
this->stepsleft_ = 0; this->stepsleft_ = 0;
this->last_scroll_ = now; this->last_scroll_ = now;
@ -107,30 +112,53 @@ void MAX7219Component::display() {
// Run this routine for the rows of every chip 8x row 0 top to 7 bottom // Run this routine for the rows of every chip 8x row 0 top to 7 bottom
// Fill the pixel parameter with display data // Fill the pixel parameter with display data
// Send the data to the chip // Send the data to the chip
for (uint8_t i = 0; i < this->num_chips_; i++) { for (uint8_t chip = 0; chip < this->num_chips_ / this->num_chip_lines_; chip++) {
for (uint8_t chip_line = 0; chip_line < this->num_chip_lines_; chip_line++) {
for (uint8_t j = 0; j < 8; j++) { for (uint8_t j = 0; j < 8; j++) {
if (this->reverse_) { bool reverse =
pixels[j] = this->max_displaybuffer_[(this->num_chips_ - i - 1) * 8 + j]; chip_line % 2 != 0 && this->chip_lines_style_ == ChipLinesStyle::SNAKE ? !this->reverse_ : this->reverse_;
if (reverse) {
pixels[j] =
this->max_displaybuffer_[chip_line][(this->num_chips_ / this->num_chip_lines_ - chip - 1) * 8 + j];
} else { } else {
pixels[j] = this->max_displaybuffer_[i * 8 + j]; pixels[j] = this->max_displaybuffer_[chip_line][chip * 8 + j];
} }
} }
this->send64pixels(i, pixels); if (chip_line % 2 != 0 && this->chip_lines_style_ == ChipLinesStyle::SNAKE)
this->orientation_ = orientation_180_();
this->send64pixels(chip_line * this->num_chips_ / this->num_chip_lines_ + chip, pixels);
if (chip_line % 2 != 0 && this->chip_lines_style_ == ChipLinesStyle::SNAKE)
this->orientation_ = orientation_180_();
}
}
}
uint8_t MAX7219Component::orientation_180_() {
switch (this->orientation_) {
case 0:
return 2;
case 1:
return 3;
case 2:
return 0;
case 3:
return 1;
default:
return 0;
} }
} }
int MAX7219Component::get_height_internal() { int MAX7219Component::get_height_internal() {
return 8; // TO BE DONE -> STACK TWO DISPLAYS ON TOP OF EACH OTHER return 8 * this->num_chip_lines_; // TO BE DONE -> CREATE Virtual size of screen and scroll
// TO BE DONE -> CREATE Virtual size of screen and scroll
} }
int MAX7219Component::get_width_internal() { return this->num_chips_ * 8; } int MAX7219Component::get_width_internal() { return this->num_chips_ / this->num_chip_lines_ * 8; }
size_t MAX7219Component::get_buffer_length_() { return this->num_chips_ * 8; }
void HOT MAX7219Component::draw_absolute_pixel_internal(int x, int y, Color color) { void HOT MAX7219Component::draw_absolute_pixel_internal(int x, int y, Color color) {
if (x + 1 > this->max_displaybuffer_.size()) { // Extend the display buffer in case required if (x + 1 > this->max_displaybuffer_[0].size()) { // Extend the display buffer in case required
this->max_displaybuffer_.resize(x + 1, this->bckgrnd_); for (int chip_line = 0; chip_line < this->num_chip_lines_; chip_line++) {
this->max_displaybuffer_[chip_line].resize(x + 1, this->bckgrnd_);
}
} }
if ((y >= this->get_height_internal()) || (y < 0) || (x < 0)) // If pixel is outside display then dont draw if ((y >= this->get_height_internal()) || (y < 0) || (x < 0)) // If pixel is outside display then dont draw
@ -140,9 +168,9 @@ void HOT MAX7219Component::draw_absolute_pixel_internal(int x, int y, Color colo
uint8_t subpos = y; // Y is starting at 0 top left uint8_t subpos = y; // Y is starting at 0 top left
if (color.is_on()) { if (color.is_on()) {
this->max_displaybuffer_[pos] |= (1 << subpos); this->max_displaybuffer_[subpos / 8][pos] |= (1 << subpos % 8);
} else { } else {
this->max_displaybuffer_[pos] &= ~(1 << subpos); this->max_displaybuffer_[subpos / 8][pos] &= ~(1 << subpos % 8);
} }
} }
@ -158,8 +186,10 @@ void MAX7219Component::send_to_all_(uint8_t a_register, uint8_t data) {
} }
void MAX7219Component::update() { void MAX7219Component::update() {
this->update_ = true; this->update_ = true;
this->max_displaybuffer_.clear(); for (int chip_line = 0; chip_line < this->num_chip_lines_; chip_line++) {
this->max_displaybuffer_.resize(this->num_chips_ * 8, this->bckgrnd_); this->max_displaybuffer_[chip_line].clear();
this->max_displaybuffer_[chip_line].resize(get_width_internal(), this->bckgrnd_);
}
if (this->writer_local_.has_value()) // insert Labda function if available if (this->writer_local_.has_value()) // insert Labda function if available
(*this->writer_local_)(*this); (*this->writer_local_)(*this);
} }
@ -175,7 +205,7 @@ void MAX7219Component::turn_on_off(bool on_off) {
} }
} }
void MAX7219Component::scroll(bool on_off, uint8_t mode, uint16_t speed, uint16_t delay, uint16_t dwell) { void MAX7219Component::scroll(bool on_off, ScrollMode mode, uint16_t speed, uint16_t delay, uint16_t dwell) {
this->set_scroll(on_off); this->set_scroll(on_off);
this->set_scroll_mode(mode); this->set_scroll_mode(mode);
this->set_scroll_speed(speed); this->set_scroll_speed(speed);
@ -183,7 +213,7 @@ void MAX7219Component::scroll(bool on_off, uint8_t mode, uint16_t speed, uint16_
this->set_scroll_delay(delay); this->set_scroll_delay(delay);
} }
void MAX7219Component::scroll(bool on_off, uint8_t mode) { void MAX7219Component::scroll(bool on_off, ScrollMode mode) {
this->set_scroll(on_off); this->set_scroll(on_off);
this->set_scroll_mode(mode); this->set_scroll_mode(mode);
} }
@ -196,24 +226,26 @@ void MAX7219Component::intensity(uint8_t intensity) {
void MAX7219Component::scroll(bool on_off) { this->set_scroll(on_off); } void MAX7219Component::scroll(bool on_off) { this->set_scroll(on_off); }
void MAX7219Component::scroll_left() { void MAX7219Component::scroll_left() {
for (int chip_line = 0; chip_line < this->num_chip_lines_; chip_line++) {
if (this->update_) { if (this->update_) {
this->max_displaybuffer_.push_back(this->bckgrnd_); this->max_displaybuffer_[chip_line].push_back(this->bckgrnd_);
for (uint16_t i = 0; i < this->stepsleft_; i++) { for (uint16_t i = 0; i < this->stepsleft_; i++) {
this->max_displaybuffer_.push_back(this->max_displaybuffer_.front()); this->max_displaybuffer_[chip_line].push_back(this->max_displaybuffer_[chip_line].front());
this->max_displaybuffer_.erase(this->max_displaybuffer_.begin()); this->max_displaybuffer_[chip_line].erase(this->max_displaybuffer_[chip_line].begin());
this->update_ = false;
} }
} else { } else {
this->max_displaybuffer_.push_back(this->max_displaybuffer_.front()); this->max_displaybuffer_[chip_line].push_back(this->max_displaybuffer_[chip_line].front());
this->max_displaybuffer_.erase(this->max_displaybuffer_.begin()); this->max_displaybuffer_[chip_line].erase(this->max_displaybuffer_[chip_line].begin());
} }
}
this->update_ = false;
this->stepsleft_++; this->stepsleft_++;
} }
void MAX7219Component::send_char(uint8_t chip, uint8_t data) { void MAX7219Component::send_char(uint8_t chip, uint8_t data) {
// get this character from PROGMEM // get this character from PROGMEM
for (uint8_t i = 0; i < 8; i++) for (uint8_t i = 0; i < 8; i++)
this->max_displaybuffer_[chip * 8 + i] = progmem_read_byte(&MAX7219_DOT_MATRIX_FONT[data][i]); this->max_displaybuffer_[0][chip * 8 + i] = progmem_read_byte(&MAX7219_DOT_MATRIX_FONT[data][i]);
} // end of send_char } // end of send_char
// send one character (data) to position (chip) // send one character (data) to position (chip)

View file

@ -12,6 +12,16 @@
namespace esphome { namespace esphome {
namespace max7219digit { namespace max7219digit {
enum ChipLinesStyle {
ZIGZAG = 0,
SNAKE,
};
enum ScrollMode {
CONTINUOUS = 0,
STOP,
};
class MAX7219Component; class MAX7219Component;
using max7219_writer_t = std::function<void(MAX7219Component &)>; using max7219_writer_t = std::function<void(MAX7219Component &)>;
@ -46,20 +56,22 @@ class MAX7219Component : public PollingComponent,
void set_intensity(uint8_t intensity) { this->intensity_ = intensity; }; void set_intensity(uint8_t intensity) { this->intensity_ = intensity; };
void set_num_chips(uint8_t num_chips) { this->num_chips_ = num_chips; }; void set_num_chips(uint8_t num_chips) { this->num_chips_ = num_chips; };
void set_num_chip_lines(uint8_t num_chip_lines) { this->num_chip_lines_ = num_chip_lines; };
void set_chip_lines_style(ChipLinesStyle chip_lines_style) { this->chip_lines_style_ = chip_lines_style; };
void set_chip_orientation(uint8_t rotate) { this->orientation_ = rotate; }; void set_chip_orientation(uint8_t rotate) { this->orientation_ = rotate; };
void set_scroll_speed(uint16_t speed) { this->scroll_speed_ = speed; }; void set_scroll_speed(uint16_t speed) { this->scroll_speed_ = speed; };
void set_scroll_dwell(uint16_t dwell) { this->scroll_dwell_ = dwell; }; void set_scroll_dwell(uint16_t dwell) { this->scroll_dwell_ = dwell; };
void set_scroll_delay(uint16_t delay) { this->scroll_delay_ = delay; }; void set_scroll_delay(uint16_t delay) { this->scroll_delay_ = delay; };
void set_scroll(bool on_off) { this->scroll_ = on_off; }; void set_scroll(bool on_off) { this->scroll_ = on_off; };
void set_scroll_mode(uint8_t mode) { this->scroll_mode_ = mode; }; void set_scroll_mode(ScrollMode mode) { this->scroll_mode_ = mode; };
void set_reverse(bool on_off) { this->reverse_ = on_off; }; void set_reverse(bool on_off) { this->reverse_ = on_off; };
void send_char(uint8_t chip, uint8_t data); void send_char(uint8_t chip, uint8_t data);
void send64pixels(uint8_t chip, const uint8_t pixels[8]); void send64pixels(uint8_t chip, const uint8_t pixels[8]);
void scroll_left(); void scroll_left();
void scroll(bool on_off, uint8_t mode, uint16_t speed, uint16_t delay, uint16_t dwell); void scroll(bool on_off, ScrollMode mode, uint16_t speed, uint16_t delay, uint16_t dwell);
void scroll(bool on_off, uint8_t mode); void scroll(bool on_off, ScrollMode mode);
void scroll(bool on_off); void scroll(bool on_off);
void intensity(uint8_t intensity); void intensity(uint8_t intensity);
@ -84,9 +96,12 @@ class MAX7219Component : public PollingComponent,
protected: protected:
void send_byte_(uint8_t a_register, uint8_t data); void send_byte_(uint8_t a_register, uint8_t data);
void send_to_all_(uint8_t a_register, uint8_t data); void send_to_all_(uint8_t a_register, uint8_t data);
uint8_t orientation_180_();
uint8_t intensity_; /// Intensity of the display from 0 to 15 (most) uint8_t intensity_; /// Intensity of the display from 0 to 15 (most)
uint8_t num_chips_; uint8_t num_chips_;
uint8_t num_chip_lines_;
ChipLinesStyle chip_lines_style_;
bool scroll_; bool scroll_;
bool reverse_; bool reverse_;
bool update_{false}; bool update_{false};
@ -94,11 +109,11 @@ class MAX7219Component : public PollingComponent,
uint16_t scroll_delay_; uint16_t scroll_delay_;
uint16_t scroll_dwell_; uint16_t scroll_dwell_;
uint16_t old_buffer_size_ = 0; uint16_t old_buffer_size_ = 0;
uint8_t scroll_mode_; ScrollMode scroll_mode_;
bool invert_ = false; bool invert_ = false;
uint8_t orientation_; uint8_t orientation_;
uint8_t bckgrnd_ = 0x0; uint8_t bckgrnd_ = 0x0;
std::vector<uint8_t> max_displaybuffer_; std::vector<std::vector<uint8_t>> max_displaybuffer_;
uint32_t last_scroll_ = 0; uint32_t last_scroll_ = 0;
uint16_t stepsleft_; uint16_t stepsleft_;
size_t get_buffer_length_(); size_t get_buffer_length_();

View file

@ -2,6 +2,7 @@
#include "esphome/core/defines.h" #include "esphome/core/defines.h"
#include "esphome/core/version.h" #include "esphome/core/version.h"
#include "esphome/core/application.h" #include "esphome/core/application.h"
#include "esphome/core/log.h"
#ifdef USE_API #ifdef USE_API
#include "esphome/components/api/api_server.h" #include "esphome/components/api/api_server.h"
@ -13,13 +14,16 @@
namespace esphome { namespace esphome {
namespace mdns { namespace mdns {
static const char *const TAG = "mdns";
#ifndef WEBSERVER_PORT #ifndef WEBSERVER_PORT
#define WEBSERVER_PORT 80 // NOLINT #define WEBSERVER_PORT 80 // NOLINT
#endif #endif
std::vector<MDNSService> MDNSComponent::compile_services_() { void MDNSComponent::compile_records_() {
std::vector<MDNSService> res; this->hostname_ = App.get_name();
this->services_.clear();
#ifdef USE_API #ifdef USE_API
if (api::global_api_server != nullptr) { if (api::global_api_server != nullptr) {
MDNSService service{}; MDNSService service{};
@ -50,7 +54,7 @@ std::vector<MDNSService> MDNSComponent::compile_services_() {
service.txt_records.push_back({"package_import_url", dashboard_import::get_package_import_url()}); service.txt_records.push_back({"package_import_url", dashboard_import::get_package_import_url()});
#endif #endif
res.push_back(service); this->services_.push_back(service);
} }
#endif // USE_API #endif // USE_API
@ -60,11 +64,11 @@ std::vector<MDNSService> MDNSComponent::compile_services_() {
service.service_type = "_prometheus-http"; service.service_type = "_prometheus-http";
service.proto = "_tcp"; service.proto = "_tcp";
service.port = WEBSERVER_PORT; service.port = WEBSERVER_PORT;
res.push_back(service); this->services_.push_back(service);
} }
#endif #endif
if (res.empty()) { if (this->services_.empty()) {
// Publish "http" service if not using native API // Publish "http" service if not using native API
// This is just to have *some* mDNS service so that .local resolution works // This is just to have *some* mDNS service so that .local resolution works
MDNSService service{}; MDNSService service{};
@ -72,11 +76,21 @@ std::vector<MDNSService> MDNSComponent::compile_services_() {
service.proto = "_tcp"; service.proto = "_tcp";
service.port = WEBSERVER_PORT; service.port = WEBSERVER_PORT;
service.txt_records.push_back({"version", ESPHOME_VERSION}); service.txt_records.push_back({"version", ESPHOME_VERSION});
res.push_back(service); this->services_.push_back(service);
}
}
void MDNSComponent::dump_config() {
ESP_LOGCONFIG(TAG, "mDNS:");
ESP_LOGCONFIG(TAG, " Hostname: %s", this->hostname_.c_str());
ESP_LOGV(TAG, " Services:");
for (const auto &service : this->services_) {
ESP_LOGV(TAG, " - %s, %s, %d", service.service_type.c_str(), service.proto.c_str(), service.port);
for (const auto &record : service.txt_records) {
ESP_LOGV(TAG, " TXT: %s = %s", record.key.c_str(), record.value.c_str());
}
} }
return res;
} }
std::string MDNSComponent::compile_hostname_() { return App.get_name(); }
} // namespace mdns } // namespace mdns
} // namespace esphome } // namespace esphome

View file

@ -26,6 +26,7 @@ struct MDNSService {
class MDNSComponent : public Component { class MDNSComponent : public Component {
public: public:
void setup() override; void setup() override;
void dump_config() override;
#if defined(USE_ESP8266) && defined(USE_ARDUINO) #if defined(USE_ESP8266) && defined(USE_ARDUINO)
void loop() override; void loop() override;
@ -33,8 +34,9 @@ class MDNSComponent : public Component {
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
protected: protected:
std::vector<MDNSService> compile_services_(); std::vector<MDNSService> services_{};
std::string compile_hostname_(); std::string hostname_;
void compile_records_();
}; };
} // namespace mdns } // namespace mdns

View file

@ -7,13 +7,12 @@
namespace esphome { namespace esphome {
namespace mdns { namespace mdns {
static const char *const TAG = "mdns";
void MDNSComponent::setup() { void MDNSComponent::setup() {
MDNS.begin(compile_hostname_().c_str()); this->compile_records_();
auto services = compile_services_(); MDNS.begin(this->hostname_.c_str());
for (const auto &service : services) {
for (const auto &service : this->services_) {
MDNS.addService(service.service_type.c_str(), service.proto.c_str(), service.port); MDNS.addService(service.service_type.c_str(), service.proto.c_str(), service.port);
for (const auto &record : service.txt_records) { for (const auto &record : service.txt_records) {
MDNS.addServiceTxt(service.service_type.c_str(), service.proto.c_str(), record.key.c_str(), record.value.c_str()); MDNS.addServiceTxt(service.service_type.c_str(), service.proto.c_str(), record.key.c_str(), record.value.c_str());

Some files were not shown because too many files have changed in this diff Show more