Merge pull request #3204 from esphome/bump-2022.2.0

2022.2.0
This commit is contained in:
Jesse Hills 2022-02-16 21:12:45 +13:00 committed by GitHub
commit 81b2fd78f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
425 changed files with 8696 additions and 3039 deletions

View File

@ -68,8 +68,6 @@ Checks: >-
-modernize-use-nodiscard,
-mpi-*,
-objc-*,
-readability-braces-around-statements,
-readability-const-return-type,
-readability-convert-member-functions-to-static,
-readability-else-after-return,
-readability-function-cognitive-complexity,
@ -77,10 +75,6 @@ Checks: >-
-readability-isolate-declaration,
-readability-magic-numbers,
-readability-make-member-function-const,
-readability-named-parameter,
-readability-qualified-auto,
-readability-redundant-access-specifiers,
-readability-redundant-member-init,
-readability-redundant-string-init,
-readability-uppercase-literal-suffix,
-readability-use-anyofallof,
@ -114,6 +108,8 @@ CheckOptions:
value: 'make_unique'
- key: modernize-make-unique.MakeSmartPtrFunctionHeader
value: 'esphome/core/helpers.h'
- key: readability-braces-around-statements.ShortStatementLines
value: 2
- key: readability-identifier-naming.LocalVariableCase
value: 'lower_case'
- key: readability-identifier-naming.ClassCase
@ -160,3 +156,5 @@ CheckOptions:
value: 'lower_case'
- key: readability-identifier-naming.VirtualMethodSuffix
value: ''
- key: readability-qualified-auto.AddConstToQualified
value: 0

View File

@ -137,18 +137,18 @@ jobs:
--build-type "${{ matrix.build_type }}" \
manifest
deploy-hassio-repo:
deploy-ha-addon-repo:
if: github.repository == 'esphome/esphome' && github.event_name == 'release'
runs-on: ubuntu-latest
needs: [deploy-docker]
steps:
- env:
TOKEN: ${{ secrets.DEPLOY_HASSIO_TOKEN }}
TOKEN: ${{ secrets.DEPLOY_HA_ADDON_REPO_TOKEN }}
run: |
TAG="${GITHUB_REF#refs/tags/}"
curl \
-u ":$TOKEN" \
-X POST \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/esphome/hassio/actions/workflows/bump-version.yml/dispatches \
https://api.github.com/repos/esphome/home-assistant-addon/actions/workflows/bump-version.yml/dispatches \
-d "{\"ref\":\"main\",\"inputs\":{\"version\":\"$TAG\"}}"

View File

@ -3,4 +3,4 @@ ports:
onOpen: open-preview
tasks:
- before: pyenv local $(pyenv version | grep '^3\.' | cut -d ' ' -f 1) && script/setup
command: python -m esphome config dashboard
command: python -m esphome dashboard config

View File

@ -2,7 +2,7 @@
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/ambv/black
rev: 20.8b1
rev: 22.1.0
hooks:
- id: black
args:
@ -10,7 +10,7 @@ repos:
- --quiet
files: ^((esphome|script|tests)/.+)?[^/]+\.py$
- repo: https://gitlab.com/pycqa/flake8
rev: 3.8.4
rev: 4.0.1
hooks:
- id: flake8
additional_dependencies:

View File

@ -54,6 +54,7 @@ esphome/components/dfplayer/* @glmnet
esphome/components/dht/* @OttoWinter
esphome/components/ds1307/* @badbadc0ffee
esphome/components/dsmr/* @glmnet @zuidwijk
esphome/components/ektf2232/* @jesserockz
esphome/components/esp32/* @esphome/core
esphome/components/esp32_ble/* @jesserockz
esphome/components/esp32_ble_server/* @jesserockz
@ -88,9 +89,12 @@ esphome/components/json/* @OttoWinter
esphome/components/kalman_combinator/* @Cat-Ion
esphome/components/ledc/* @OttoWinter
esphome/components/light/* @esphome/core
esphome/components/lilygo_t5_47/touchscreen/* @jesserockz
esphome/components/lock/* @esphome/core
esphome/components/logger/* @esphome/core
esphome/components/ltr390/* @sjtrny
esphome/components/max7219digit/* @rspaargaren
esphome/components/max9611/* @mckaymatthew
esphome/components/mcp23008/* @jesserockz
esphome/components/mcp23017/* @jesserockz
esphome/components/mcp23s08/* @SenexCrenshaw @jesserockz
@ -107,10 +111,12 @@ esphome/components/mdns/* @esphome/core
esphome/components/midea/* @dudanov
esphome/components/midea_ir/* @dudanov
esphome/components/mitsubishi/* @RubyBailey
esphome/components/mlx90393/* @functionpointer
esphome/components/modbus_controller/* @martgras
esphome/components/modbus_controller/binary_sensor/* @martgras
esphome/components/modbus_controller/number/* @martgras
esphome/components/modbus_controller/output/* @martgras
esphome/components/modbus_controller/select/* @martgras @stegm
esphome/components/modbus_controller/sensor/* @martgras
esphome/components/modbus_controller/switch/* @martgras
esphome/components/modbus_controller/text_sensor/* @martgras
@ -136,6 +142,9 @@ esphome/components/preferences/* @esphome/core
esphome/components/psram/* @esphome/core
esphome/components/pulse_meter/* @stevebaxter
esphome/components/pvvx_mithermometer/* @pasiz
esphome/components/qr_code/* @wjtje
esphome/components/radon_eye_ble/* @jeffeb3
esphome/components/radon_eye_rd200/* @jeffeb3
esphome/components/rc522/* @glmnet
esphome/components/rc522_i2c/* @glmnet
esphome/components/rc522_spi/* @glmnet
@ -187,6 +196,7 @@ esphome/components/tmp102/* @timsavage
esphome/components/tmp117/* @Azimath
esphome/components/tof10120/* @wstrzalka
esphome/components/toshiba/* @kbx81
esphome/components/touchscreen/* @jesserockz
esphome/components/tsl2591/* @wjcarpenter
esphome/components/tuya/binary_sensor/* @jesserockz
esphome/components/tuya/climate/* @jesserockz
@ -197,8 +207,10 @@ esphome/components/tuya/text_sensor/* @dentra
esphome/components/uart/* @esphome/core
esphome/components/ultrasonic/* @OttoWinter
esphome/components/version/* @esphome/core
esphome/components/wake_on_lan/* @willwill2will54
esphome/components/web_server_base/* @OttoWinter
esphome/components/whirlpool/* @glmnet
esphome/components/xiaomi_lywsd03mmc/* @ahpohl
esphome/components/xiaomi_mhoc303/* @drug123
esphome/components/xiaomi_mhoc401/* @vevsvevs
esphome/components/xpt2046/* @numo68

View File

@ -8,9 +8,9 @@ ARG BASEIMGTYPE=docker
FROM ghcr.io/hassio-addons/debian-base/amd64:5.2.3 AS base-hassio-amd64
FROM ghcr.io/hassio-addons/debian-base/aarch64:5.2.3 AS base-hassio-arm64
FROM ghcr.io/hassio-addons/debian-base/armv7:5.2.3 AS base-hassio-armv7
FROM debian:bullseye-20211220-slim AS base-docker-amd64
FROM debian:bullseye-20211220-slim AS base-docker-arm64
FROM debian:bullseye-20211220-slim AS base-docker-armv7
FROM debian:bullseye-20220125-slim AS base-docker-amd64
FROM debian:bullseye-20220125-slim AS base-docker-arm64
FROM debian:bullseye-20220125-slim AS base-docker-armv7
# Use TARGETARCH/TARGETVARIANT defined by docker
# https://docs.docker.com/engine/reference/builder/#automatic-platform-args-in-the-global-scope
@ -23,7 +23,7 @@ RUN \
python3=3.9.2-3 \
python3-pip=20.3.4-4 \
python3-setuptools=52.0.0-4 \
python3-pil=8.1.2+dfsg-0.3 \
python3-pil=8.1.2+dfsg-0.3+deb11u1 \
python3-cryptography=3.3.2-1 \
iputils-ping=3:20210202-1 \
git=1:2.30.2-1 \
@ -102,7 +102,7 @@ RUN \
ARG BUILD_VERSION=dev
# Copy root filesystem
COPY docker/hassio-rootfs/ /
COPY docker/ha-addon-rootfs/ /
# First install requirements to leverage caching when requirements don't change
COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini /

View File

@ -10,7 +10,7 @@ server {
ssl_certificate_key /ssl/%%keyfile%%;
# Clear Hass.io Ingress header
proxy_set_header X-Hassio-Ingress "";
proxy_set_header X-HA-Ingress "";
# Redirect http requests to https on the same port.
# https://rageagainstshell.com/2016/11/redirect-http-to-https-on-the-same-port-in-nginx/

View File

@ -4,7 +4,7 @@ server {
include /etc/nginx/includes/server_params.conf;
include /etc/nginx/includes/proxy_params.conf;
# Clear Hass.io Ingress header
proxy_set_header X-Hassio-Ingress "";
proxy_set_header X-HA-Ingress "";
location / {
proxy_pass http://esphome;

View File

@ -3,8 +3,8 @@ server {
include /etc/nginx/includes/server_params.conf;
include /etc/nginx/includes/proxy_params.conf;
# Set Hass.io Ingress header
proxy_set_header X-Hassio-Ingress "YES";
# Set Home Assistant Ingress header
proxy_set_header X-HA-Ingress "YES";
location / {
# Only allow from Hass.io supervisor

View File

@ -4,7 +4,7 @@
# Runs the ESPHome dashboard
# ==============================================================================
export ESPHOME_IS_HASSIO=true
export ESPHOME_IS_HA_ADDON=true
if bashio::config.true 'leave_front_door_open'; then
export DISABLE_HA_AUTHENTICATION=true
@ -32,4 +32,4 @@ export PLATFORMIO_CACHE_DIR="${pio_cache_base}/cache"
export PLATFORMIO_GLOBALLIB_DIR=/piolibs
bashio::log.info "Starting ESPHome dashboard..."
exec esphome dashboard /config/esphome --socket /var/run/esphome.sock --hassio
exec esphome dashboard /config/esphome --socket /var/run/esphome.sock --ha-addon

View File

@ -661,7 +661,7 @@ def parse_args(argv):
"--open-ui", help="Open the dashboard UI in a browser.", action="store_true"
)
parser_dashboard.add_argument(
"--hassio", help=argparse.SUPPRESS, action="store_true"
"--ha-addon", help=argparse.SUPPRESS, action="store_true"
)
parser_dashboard.add_argument(
"--socket", help="Make the dashboard serve under a unix socket", type=str

View File

@ -63,6 +63,7 @@ from esphome.cpp_types import ( # noqa
uint32,
uint64,
int32,
int64,
const_char_ptr,
NAN,
esphome_ns,
@ -81,4 +82,5 @@ from esphome.cpp_types import ( # noqa
InternalGPIOPin,
gpio_Flags,
EntityCategory,
Parented,
)

View File

@ -52,10 +52,10 @@ uint32_t IRAM_ATTR HOT AcDimmerDataStore::timer_intr(uint32_t now) {
this->gate_pin.digital_write(false);
}
if (time_since_zc < this->enable_time_us)
if (time_since_zc < this->enable_time_us) {
// Next event is enable, return time until that event
return this->enable_time_us - time_since_zc;
else if (time_since_zc < disable_time_us) {
} else if (time_since_zc < disable_time_us) {
// Next event is disable, return time until that event
return this->disable_time_us - time_since_zc;
}
@ -74,9 +74,10 @@ uint32_t IRAM_ATTR HOT timer_interrupt() {
uint32_t min_dt_us = 1000;
uint32_t now = micros();
for (auto *dimmer : all_dimmers) {
if (dimmer == nullptr)
if (dimmer == nullptr) {
// no more dimmers
break;
}
uint32_t res = dimmer->timer_intr(now);
if (res != 0 && res < min_dt_us)
min_dt_us = res;
@ -212,12 +213,13 @@ void AcDimmer::dump_config() {
LOG_PIN(" Zero-Cross Pin: ", this->zero_cross_pin_);
ESP_LOGCONFIG(TAG, " Min Power: %.1f%%", this->store_.min_power / 10.0f);
ESP_LOGCONFIG(TAG, " Init with half cycle: %s", YESNO(this->init_with_half_cycle_));
if (method_ == DIM_METHOD_LEADING_PULSE)
if (method_ == DIM_METHOD_LEADING_PULSE) {
ESP_LOGCONFIG(TAG, " Method: leading pulse");
else if (method_ == DIM_METHOD_LEADING)
} else if (method_ == DIM_METHOD_LEADING) {
ESP_LOGCONFIG(TAG, " Method: leading");
else
} else {
ESP_LOGCONFIG(TAG, " Method: trailing");
}
LOG_FLOAT_OUTPUT(this);
ESP_LOGV(TAG, " Estimated Frequency: %.3fHz", 1e6f / this->store_.cycle_time_us / 2);

View File

@ -13,7 +13,6 @@ class AdalightLightEffect : public light::AddressableLightEffect, public uart::U
public:
AdalightLightEffect(const std::string &name);
public:
void start() override;
void stop() override;
void apply(light::AddressableLight &it, const Color &current_color) override;
@ -30,7 +29,6 @@ class AdalightLightEffect : public light::AddressableLightEffect, public uart::U
void blank_all_leds_(light::AddressableLight &it);
Frame parse_frame_(light::AddressableLight &it);
protected:
uint32_t last_ack_{0};
uint32_t last_byte_{0};
uint32_t last_reset_{0};

View File

@ -15,6 +15,11 @@ namespace esphome {
namespace adc {
static const char *const TAG = "adc";
// 13 bits for S3 / 12 bit for all other esp32 variants
// create a const to avoid the repated cast to enum
#ifdef USE_ESP32
static const adc_bits_width_t ADC_WIDTH_MAX_SOC_BITS = static_cast<adc_bits_width_t>(ADC_WIDTH_MAX - 1);
#endif
void ADCSensor::setup() {
ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str());
@ -23,14 +28,14 @@ void ADCSensor::setup() {
#endif
#ifdef USE_ESP32
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_width(ADC_WIDTH_MAX_SOC_BITS);
if (!autorange_) {
adc1_config_channel_atten(channel_, attenuation_);
}
// 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,
auto cal_value = esp_adc_cal_characterize(ADC_UNIT_1, (adc_atten_t) i, ADC_WIDTH_MAX_SOC_BITS,
1100, // default vref
&cal_characteristics_[i]);
switch (cal_value) {
@ -65,9 +70,9 @@ void ADCSensor::dump_config() {
#ifdef USE_ESP32
LOG_PIN(" Pin: ", pin_);
if (autorange_)
if (autorange_) {
ESP_LOGCONFIG(TAG, " Attenuation: auto");
else
} else {
switch (this->attenuation_) {
case ADC_ATTEN_DB_0:
ESP_LOGCONFIG(TAG, " Attenuation: 0db (max 1.1V)");
@ -84,6 +89,7 @@ void ADCSensor::dump_config() {
default: // This is to satisfy the unused ADC_ATTEN_MAX
break;
}
}
#endif // USE_ESP32
LOG_UPDATE_INTERVAL(this);
}

View File

@ -24,7 +24,7 @@ void AirthingsWaveMini::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt
case ESP_GATTC_SEARCH_CMPL_EVT: {
this->handle_ = 0;
auto chr = this->parent()->get_characteristic(service_uuid_, sensors_data_characteristic_uuid_);
auto *chr = this->parent()->get_characteristic(service_uuid_, sensors_data_characteristic_uuid_);
if (chr == nullptr) {
ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", service_uuid_.to_string().c_str(),
sensors_data_characteristic_uuid_.to_string().c_str());
@ -56,7 +56,7 @@ void AirthingsWaveMini::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt
}
void AirthingsWaveMini::read_sensors_(uint8_t *raw_value, uint16_t value_len) {
auto value = (WaveMiniReadings *) raw_value;
auto *value = (WaveMiniReadings *) raw_value;
if (sizeof(WaveMiniReadings) <= value_len) {
this->humidity_sensor_->publish_state(value->humidity / 100.0f);

View File

@ -24,7 +24,7 @@ void AirthingsWavePlus::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt
case ESP_GATTC_SEARCH_CMPL_EVT: {
this->handle_ = 0;
auto chr = this->parent()->get_characteristic(service_uuid_, sensors_data_characteristic_uuid_);
auto *chr = this->parent()->get_characteristic(service_uuid_, sensors_data_characteristic_uuid_);
if (chr == nullptr) {
ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", service_uuid_.to_string().c_str(),
sensors_data_characteristic_uuid_.to_string().c_str());
@ -56,7 +56,7 @@ void AirthingsWavePlus::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt
}
void AirthingsWavePlus::read_sensors_(uint8_t *raw_value, uint16_t value_len) {
auto value = (WavePlusReadings *) raw_value;
auto *value = (WavePlusReadings *) raw_value;
if (sizeof(WavePlusReadings) <= value_len) {
ESP_LOGD(TAG, "version = %d", value->version);

View File

@ -19,12 +19,14 @@ uint16_t crc_16(uint8_t *ptr, uint8_t length) {
//------------------------------
while (length--) {
crc ^= *ptr++;
for (i = 0; i < 8; i++)
for (i = 0; i < 8; i++) {
if ((crc & 0x01) != 0) {
crc >>= 1;
crc ^= 0xA001;
} else
} else {
crc >>= 1;
}
}
}
return crc;
}

View File

@ -39,7 +39,7 @@ void Am43::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_i
break;
}
case ESP_GATTC_SEARCH_CMPL_EVT: {
auto chr = this->parent_->get_characteristic(AM43_SERVICE_UUID, AM43_CHARACTERISTIC_UUID);
auto *chr = this->parent_->get_characteristic(AM43_SERVICE_UUID, AM43_CHARACTERISTIC_UUID);
if (chr == nullptr) {
if (this->parent_->get_characteristic(AM43_TUYA_SERVICE_UUID, AM43_TUYA_CHARACTERISTIC_UUID) != nullptr) {
ESP_LOGE(TAG, "[%s] Detected a Tuya AM43 which is not supported, sorry.",
@ -75,13 +75,14 @@ void Am43::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_i
if (this->current_sensor_ > 0) {
if (this->illuminance_ != nullptr) {
auto packet = this->encoder_->get_light_level_request();
auto *packet = this->encoder_->get_light_level_request();
auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_,
packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP,
ESP_GATT_AUTH_REQ_NONE);
if (status)
if (status) {
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(),
status);
}
}
this->current_sensor_ = 0;
}
@ -99,7 +100,7 @@ void Am43::update() {
}
if (this->current_sensor_ == 0) {
if (this->battery_ != nullptr) {
auto packet = this->encoder_->get_battery_level_request();
auto *packet = this->encoder_->get_battery_level_request();
auto status =
esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length,
packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);

View File

@ -5,7 +5,7 @@ from esphome.const import CONF_ID, CONF_PIN
CODEOWNERS = ["@buxtronix"]
DEPENDENCIES = ["ble_client"]
AUTO_LOAD = ["am43"]
AUTO_LOAD = ["am43", "sensor"]
CONF_INVERT_POSITION = "invert_position"

View File

@ -25,15 +25,16 @@ void Am43Component::setup() {
void Am43Component::loop() {
if (this->node_state == espbt::ClientState::ESTABLISHED && !this->logged_in_) {
auto packet = this->encoder_->get_send_pin_request(this->pin_);
auto *packet = this->encoder_->get_send_pin_request(this->pin_);
auto status =
esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length,
packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
ESP_LOGI(TAG, "[%s] Logging into AM43", this->get_name().c_str());
if (status)
if (status) {
ESP_LOGW(TAG, "[%s] Error writing set_pin to device, error = %d", this->get_name().c_str(), status);
else
} else {
this->logged_in_ = true;
}
}
}
@ -51,7 +52,7 @@ void Am43Component::control(const CoverCall &call) {
return;
}
if (call.get_stop()) {
auto packet = this->encoder_->get_stop_request();
auto *packet = this->encoder_->get_stop_request();
auto status =
esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length,
packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
@ -63,7 +64,7 @@ void Am43Component::control(const CoverCall &call) {
if (this->invert_position_)
pos = 1 - pos;
auto packet = this->encoder_->get_set_position_request(100 - (uint8_t)(pos * 100));
auto *packet = this->encoder_->get_set_position_request(100 - (uint8_t)(pos * 100));
auto status =
esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length,
packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
@ -80,7 +81,7 @@ void Am43Component::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
break;
}
case ESP_GATTC_SEARCH_CMPL_EVT: {
auto chr = this->parent_->get_characteristic(AM43_SERVICE_UUID, AM43_CHARACTERISTIC_UUID);
auto *chr = this->parent_->get_characteristic(AM43_SERVICE_UUID, AM43_CHARACTERISTIC_UUID);
if (chr == nullptr) {
if (this->parent_->get_characteristic(AM43_TUYA_SERVICE_UUID, AM43_TUYA_CHARACTERISTIC_UUID) != nullptr) {
ESP_LOGE(TAG, "[%s] Detected a Tuya AM43 which is not supported, sorry.", this->get_name().c_str());
@ -120,7 +121,7 @@ void Am43Component::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
if (this->decoder_->has_pin_response()) {
if (this->decoder_->pin_ok_) {
ESP_LOGI(TAG, "[%s] AM43 pin accepted.", this->get_name().c_str());
auto packet = this->encoder_->get_position_request();
auto *packet = this->encoder_->get_position_request();
auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_,
packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP,
ESP_GATT_AUTH_REQ_NONE);

View File

@ -40,7 +40,7 @@ void Anova::control(const ClimateCall &call) {
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
}
if (call.get_target_temperature().has_value()) {
auto pkt = this->codec_->get_set_target_temp_request(*call.get_target_temperature());
auto *pkt = this->codec_->get_set_target_temp_request(*call.get_target_temperature());
auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_,
pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
if (status)
@ -57,7 +57,7 @@ void Anova::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_
break;
}
case ESP_GATTC_SEARCH_CMPL_EVT: {
auto chr = this->parent_->get_characteristic(ANOVA_SERVICE_UUID, ANOVA_CHARACTERISTIC_UUID);
auto *chr = this->parent_->get_characteristic(ANOVA_SERVICE_UUID, ANOVA_CHARACTERISTIC_UUID);
if (chr == nullptr) {
ESP_LOGW(TAG, "[%s] No control service found at device, not an Anova..?", this->get_name().c_str());
ESP_LOGW(TAG, "[%s] Note, this component does not currently support Anova Nano.", this->get_name().c_str());
@ -114,9 +114,10 @@ void Anova::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_
auto status =
esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, pkt->length,
pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
if (status)
if (status) {
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(),
status);
}
}
}
break;
@ -133,7 +134,7 @@ void Anova::update() {
return;
if (this->current_request_ < 2) {
auto pkt = this->codec_->get_read_device_status_request();
auto *pkt = this->codec_->get_read_device_status_request();
if (this->current_request_ == 0)
this->codec_->get_set_unit_request(this->fahrenheit_ ? 'f' : 'c');
auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_,

View File

@ -36,7 +36,7 @@ class Anova : public climate::Climate, public esphome::ble_client::BLEClientNode
traits.set_visual_temperature_step(0.1);
return traits;
}
void set_unit_of_measurement(const char *);
void set_unit_of_measurement(const char *unit);
protected:
std::unique_ptr<AnovaCodec> codec_;

View File

@ -225,9 +225,10 @@ void APDS9960::read_gesture_data_() {
uint8_t fifo_level;
APDS9960_WARNING_CHECK(this->read_byte(0xAE, &fifo_level), "Reading FIFO level failed.");
if (fifo_level == 0)
if (fifo_level == 0) {
// no data to process
return;
}
APDS9960_WARNING_CHECK(fifo_level <= 32, "FIFO level has invalid value.")

View File

@ -41,6 +41,7 @@ service APIConnection {
rpc number_command (NumberCommandRequest) returns (void) {}
rpc select_command (SelectCommandRequest) returns (void) {}
rpc button_command (ButtonCommandRequest) returns (void) {}
rpc lock_command (LockCommandRequest) returns (void) {}
}
@ -95,6 +96,9 @@ message HelloResponse {
// and only exists for debugging/logging purposes.
// For example "ESPHome v1.10.0 on ESP8266"
string server_info = 3;
// The name of the server (App.get_name())
string name = 4;
}
// Message sent at the beginning of each connection to authenticate the client
@ -525,6 +529,7 @@ message ListEntitiesSwitchResponse {
bool assumed_state = 6;
bool disabled_by_default = 7;
EntityCategory entity_category = 8;
string device_class = 9;
}
message SwitchStateResponse {
option (id) = 26;
@ -953,6 +958,63 @@ message SelectCommandRequest {
string state = 2;
}
// ==================== LOCK ====================
enum LockState {
LOCK_STATE_NONE = 0;
LOCK_STATE_LOCKED = 1;
LOCK_STATE_UNLOCKED = 2;
LOCK_STATE_JAMMED = 3;
LOCK_STATE_LOCKING = 4;
LOCK_STATE_UNLOCKING = 5;
}
enum LockCommand {
LOCK_UNLOCK = 0;
LOCK_LOCK = 1;
LOCK_OPEN = 2;
}
message ListEntitiesLockResponse {
option (id) = 58;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_LOCK";
string object_id = 1;
fixed32 key = 2;
string name = 3;
string unique_id = 4;
string icon = 5;
bool disabled_by_default = 6;
EntityCategory entity_category = 7;
bool assumed_state = 8;
bool supports_open = 9;
bool requires_code = 10;
# Not yet implemented:
string code_format = 11;
}
message LockStateResponse {
option (id) = 59;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_LOCK";
option (no_delay) = true;
fixed32 key = 1;
LockState state = 2;
}
message LockCommandRequest {
option (id) = 60;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_LOCK";
option (no_delay) = true;
fixed32 key = 1;
LockCommand command = 2;
# Not yet implemented:
bool has_code = 3;
string code = 4;
}
// ==================== BUTTON ====================
message ListEntitiesButtonResponse {
option (id) = 61;
@ -977,3 +1039,4 @@ message ButtonCommandRequest {
fixed32 key = 1;
}

View File

@ -255,7 +255,7 @@ void APIConnection::cover_command(const CoverCommandRequest &msg) {
// Shut-up about usage of deprecated speed_level_to_enum/speed_enum_to_level functions for a bit.
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
bool APIConnection::send_fan_state(fan::FanState *fan) {
bool APIConnection::send_fan_state(fan::Fan *fan) {
if (!this->state_subscription_)
return false;
@ -273,7 +273,7 @@ bool APIConnection::send_fan_state(fan::FanState *fan) {
resp.direction = static_cast<enums::FanDirection>(fan->direction);
return this->send_fan_state_response(resp);
}
bool APIConnection::send_fan_info(fan::FanState *fan) {
bool APIConnection::send_fan_info(fan::Fan *fan) {
auto traits = fan->get_traits();
ListEntitiesFanResponse msg;
msg.key = fan->get_object_id_hash();
@ -290,7 +290,7 @@ bool APIConnection::send_fan_info(fan::FanState *fan) {
return this->send_list_entities_fan_response(msg);
}
void APIConnection::fan_command(const FanCommandRequest &msg) {
fan::FanState *fan = App.get_fan_by_key(msg.key);
fan::Fan *fan = App.get_fan_by_key(msg.key);
if (fan == nullptr)
return;
@ -462,6 +462,7 @@ bool APIConnection::send_switch_info(switch_::Switch *a_switch) {
msg.assumed_state = a_switch->assumed_state();
msg.disabled_by_default = a_switch->is_disabled_by_default();
msg.entity_category = static_cast<enums::EntityCategory>(a_switch->get_entity_category());
msg.device_class = a_switch->get_device_class();
return this->send_list_entities_switch_response(msg);
}
void APIConnection::switch_command(const SwitchCommandRequest &msg) {
@ -469,10 +470,11 @@ void APIConnection::switch_command(const SwitchCommandRequest &msg) {
if (a_switch == nullptr)
return;
if (msg.state)
if (msg.state) {
a_switch->turn_on();
else
} else {
a_switch->turn_off();
}
}
#endif
@ -699,6 +701,49 @@ void APIConnection::button_command(const ButtonCommandRequest &msg) {
}
#endif
#ifdef USE_LOCK
bool APIConnection::send_lock_state(lock::Lock *a_lock, lock::LockState state) {
if (!this->state_subscription_)
return false;
LockStateResponse resp{};
resp.key = a_lock->get_object_id_hash();
resp.state = static_cast<enums::LockState>(state);
return this->send_lock_state_response(resp);
}
bool APIConnection::send_lock_info(lock::Lock *a_lock) {
ListEntitiesLockResponse msg;
msg.key = a_lock->get_object_id_hash();
msg.object_id = a_lock->get_object_id();
msg.name = a_lock->get_name();
msg.unique_id = get_default_unique_id("lock", a_lock);
msg.icon = a_lock->get_icon();
msg.assumed_state = a_lock->traits.get_assumed_state();
msg.disabled_by_default = a_lock->is_disabled_by_default();
msg.entity_category = static_cast<enums::EntityCategory>(a_lock->get_entity_category());
msg.supports_open = a_lock->traits.get_supports_open();
msg.requires_code = a_lock->traits.get_requires_code();
return this->send_list_entities_lock_response(msg);
}
void APIConnection::lock_command(const LockCommandRequest &msg) {
lock::Lock *a_lock = App.get_lock_by_key(msg.key);
if (a_lock == nullptr)
return;
switch (msg.command) {
case enums::LOCK_UNLOCK:
a_lock->unlock();
break;
case enums::LOCK_LOCK:
a_lock->lock();
break;
case enums::LOCK_OPEN:
a_lock->open();
break;
}
}
#endif
#ifdef USE_ESP32_CAMERA
void APIConnection::send_camera_state(std::shared_ptr<esp32_camera::CameraImage> image) {
if (!this->state_subscription_)
@ -766,6 +811,8 @@ HelloResponse APIConnection::hello(const HelloRequest &msg) {
resp.api_version_major = 1;
resp.api_version_minor = 6;
resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")";
resp.name = App.get_name();
this->connection_state_ = ConnectionState::CONNECTED;
return resp;
}
@ -803,15 +850,16 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
resp.project_version = ESPHOME_PROJECT_VERSION;
#endif
#ifdef USE_WEBSERVER
resp.webserver_port = WEBSERVER_PORT;
resp.webserver_port = USE_WEBSERVER_PORT;
#endif
return resp;
}
void APIConnection::on_home_assistant_state_response(const HomeAssistantStateResponse &msg) {
for (auto &it : this->parent_->get_state_subs())
for (auto &it : this->parent_->get_state_subs()) {
if (it.entity_id == msg.entity_id && it.attribute.value() == msg.attribute) {
it.callback(msg.state);
}
}
}
void APIConnection::execute_service(const ExecuteServiceRequest &msg) {
bool found = false;

View File

@ -32,8 +32,8 @@ class APIConnection : public APIServerConnection {
void cover_command(const CoverCommandRequest &msg) override;
#endif
#ifdef USE_FAN
bool send_fan_state(fan::FanState *fan);
bool send_fan_info(fan::FanState *fan);
bool send_fan_state(fan::Fan *fan);
bool send_fan_info(fan::Fan *fan);
void fan_command(const FanCommandRequest &msg) override;
#endif
#ifdef USE_LIGHT
@ -77,6 +77,11 @@ class APIConnection : public APIServerConnection {
#ifdef USE_BUTTON
bool send_button_info(button::Button *button);
void button_command(const ButtonCommandRequest &msg) override;
#endif
#ifdef USE_LOCK
bool send_lock_state(lock::Lock *a_lock, lock::LockState state);
bool send_lock_info(lock::Lock *a_lock);
void lock_command(const LockCommandRequest &msg) override;
#endif
bool send_log_message(int level, const char *tag, const char *line);
void send_homeassistant_service_call(const HomeassistantServiceResponse &call) {

View File

@ -3,6 +3,7 @@
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#include "esphome/core/application.h"
#include "proto.h"
#include <cstring>
@ -302,9 +303,16 @@ APIError APINoiseFrameHelper::state_action_() {
}
if (state_ == State::SERVER_HELLO) {
// send server hello
uint8_t msg[1];
msg[0] = 0x01; // chosen proto
aerr = write_frame_(msg, 1);
std::vector<uint8_t> msg;
// chosen proto
msg.push_back(0x01);
// node name, terminated by null byte
const std::string &name = App.get_name();
const uint8_t *name_ptr = reinterpret_cast<const uint8_t *>(name.c_str());
msg.insert(msg.end(), name_ptr, name_ptr + name.size() + 1);
aerr = write_frame_(msg.data(), msg.size());
if (aerr != APIError::OK)
return aerr;

View File

@ -278,6 +278,36 @@ template<> const char *proto_enum_to_string<enums::NumberMode>(enums::NumberMode
return "UNKNOWN";
}
}
template<> const char *proto_enum_to_string<enums::LockState>(enums::LockState value) {
switch (value) {
case enums::LOCK_STATE_NONE:
return "LOCK_STATE_NONE";
case enums::LOCK_STATE_LOCKED:
return "LOCK_STATE_LOCKED";
case enums::LOCK_STATE_UNLOCKED:
return "LOCK_STATE_UNLOCKED";
case enums::LOCK_STATE_JAMMED:
return "LOCK_STATE_JAMMED";
case enums::LOCK_STATE_LOCKING:
return "LOCK_STATE_LOCKING";
case enums::LOCK_STATE_UNLOCKING:
return "LOCK_STATE_UNLOCKING";
default:
return "UNKNOWN";
}
}
template<> const char *proto_enum_to_string<enums::LockCommand>(enums::LockCommand value) {
switch (value) {
case enums::LOCK_UNLOCK:
return "LOCK_UNLOCK";
case enums::LOCK_LOCK:
return "LOCK_LOCK";
case enums::LOCK_OPEN:
return "LOCK_OPEN";
default:
return "UNKNOWN";
}
}
bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
@ -319,6 +349,10 @@ bool HelloResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value)
this->server_info = value.as_string();
return true;
}
case 4: {
this->name = value.as_string();
return true;
}
default:
return false;
}
@ -327,6 +361,7 @@ void HelloResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint32(1, this->api_version_major);
buffer.encode_uint32(2, this->api_version_minor);
buffer.encode_string(3, this->server_info);
buffer.encode_string(4, this->name);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void HelloResponse::dump_to(std::string &out) const {
@ -345,6 +380,10 @@ void HelloResponse::dump_to(std::string &out) const {
out.append(" server_info: ");
out.append("'").append(this->server_info).append("'");
out.append("\n");
out.append(" name: ");
out.append("'").append(this->name).append("'");
out.append("\n");
out.append("}");
}
#endif
@ -2138,6 +2177,10 @@ bool ListEntitiesSwitchResponse::decode_length(uint32_t field_id, ProtoLengthDel
this->icon = value.as_string();
return true;
}
case 9: {
this->device_class = value.as_string();
return true;
}
default:
return false;
}
@ -2161,6 +2204,7 @@ void ListEntitiesSwitchResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(6, this->assumed_state);
buffer.encode_bool(7, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(8, this->entity_category);
buffer.encode_string(9, this->device_class);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesSwitchResponse::dump_to(std::string &out) const {
@ -2198,6 +2242,10 @@ void ListEntitiesSwitchResponse::dump_to(std::string &out) const {
out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append(" device_class: ");
out.append("'").append(this->device_class).append("'");
out.append("\n");
out.append("}");
}
#endif
@ -4177,6 +4225,234 @@ void SelectCommandRequest::dump_to(std::string &out) const {
out.append("}");
}
#endif
bool ListEntitiesLockResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 6: {
this->disabled_by_default = value.as_bool();
return true;
}
case 7: {
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
case 8: {
this->assumed_state = value.as_bool();
return true;
}
case 9: {
this->supports_open = value.as_bool();
return true;
}
case 10: {
this->requires_code = value.as_bool();
return true;
}
default:
return false;
}
}
bool ListEntitiesLockResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
this->object_id = value.as_string();
return true;
}
case 3: {
this->name = value.as_string();
return true;
}
case 4: {
this->unique_id = value.as_string();
return true;
}
case 5: {
this->icon = value.as_string();
return true;
}
case 11: {
this->code_format = value.as_string();
return true;
}
default:
return false;
}
}
bool ListEntitiesLockResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
switch (field_id) {
case 2: {
this->key = value.as_fixed32();
return true;
}
default:
return false;
}
}
void ListEntitiesLockResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.encode_string(3, this->name);
buffer.encode_string(4, this->unique_id);
buffer.encode_string(5, this->icon);
buffer.encode_bool(6, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
buffer.encode_bool(8, this->assumed_state);
buffer.encode_bool(9, this->supports_open);
buffer.encode_bool(10, this->requires_code);
buffer.encode_string(11, this->code_format);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesLockResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("ListEntitiesLockResponse {\n");
out.append(" object_id: ");
out.append("'").append(this->object_id).append("'");
out.append("\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
out.append(buffer);
out.append("\n");
out.append(" name: ");
out.append("'").append(this->name).append("'");
out.append("\n");
out.append(" unique_id: ");
out.append("'").append(this->unique_id).append("'");
out.append("\n");
out.append(" icon: ");
out.append("'").append(this->icon).append("'");
out.append("\n");
out.append(" disabled_by_default: ");
out.append(YESNO(this->disabled_by_default));
out.append("\n");
out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append(" assumed_state: ");
out.append(YESNO(this->assumed_state));
out.append("\n");
out.append(" supports_open: ");
out.append(YESNO(this->supports_open));
out.append("\n");
out.append(" requires_code: ");
out.append(YESNO(this->requires_code));
out.append("\n");
out.append(" code_format: ");
out.append("'").append(this->code_format).append("'");
out.append("\n");
out.append("}");
}
#endif
bool LockStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 2: {
this->state = value.as_enum<enums::LockState>();
return true;
}
default:
return false;
}
}
bool LockStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
switch (field_id) {
case 1: {
this->key = value.as_fixed32();
return true;
}
default:
return false;
}
}
void LockStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_fixed32(1, this->key);
buffer.encode_enum<enums::LockState>(2, this->state);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void LockStateResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("LockStateResponse {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
out.append(buffer);
out.append("\n");
out.append(" state: ");
out.append(proto_enum_to_string<enums::LockState>(this->state));
out.append("\n");
out.append("}");
}
#endif
bool LockCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 2: {
this->command = value.as_enum<enums::LockCommand>();
return true;
}
case 3: {
this->has_code = value.as_bool();
return true;
}
default:
return false;
}
}
bool LockCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 4: {
this->code = value.as_string();
return true;
}
default:
return false;
}
}
bool LockCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
switch (field_id) {
case 1: {
this->key = value.as_fixed32();
return true;
}
default:
return false;
}
}
void LockCommandRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_fixed32(1, this->key);
buffer.encode_enum<enums::LockCommand>(2, this->command);
buffer.encode_bool(3, this->has_code);
buffer.encode_string(4, this->code);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void LockCommandRequest::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("LockCommandRequest {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
out.append(buffer);
out.append("\n");
out.append(" command: ");
out.append(proto_enum_to_string<enums::LockCommand>(this->command));
out.append("\n");
out.append(" has_code: ");
out.append(YESNO(this->has_code));
out.append("\n");
out.append(" code: ");
out.append("'").append(this->code).append("'");
out.append("\n");
out.append("}");
}
#endif
bool ListEntitiesButtonResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 6: {
@ -4239,7 +4515,7 @@ void ListEntitiesButtonResponse::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesButtonResponse::dump_to(std::string &out) const {
char buffer[64];
__attribute__((unused)) char buffer[64];
out.append("ListEntitiesButtonResponse {\n");
out.append(" object_id: ");
out.append("'").append(this->object_id).append("'");
@ -4289,7 +4565,7 @@ bool ButtonCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
void ButtonCommandRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); }
#ifdef HAS_PROTO_MESSAGE_DUMP
void ButtonCommandRequest::dump_to(std::string &out) const {
char buffer[64];
__attribute__((unused)) char buffer[64];
out.append("ButtonCommandRequest {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);

View File

@ -128,6 +128,19 @@ enum NumberMode : uint32_t {
NUMBER_MODE_BOX = 1,
NUMBER_MODE_SLIDER = 2,
};
enum LockState : uint32_t {
LOCK_STATE_NONE = 0,
LOCK_STATE_LOCKED = 1,
LOCK_STATE_UNLOCKED = 2,
LOCK_STATE_JAMMED = 3,
LOCK_STATE_LOCKING = 4,
LOCK_STATE_UNLOCKING = 5,
};
enum LockCommand : uint32_t {
LOCK_UNLOCK = 0,
LOCK_LOCK = 1,
LOCK_OPEN = 2,
};
} // namespace enums
@ -147,6 +160,7 @@ class HelloResponse : public ProtoMessage {
uint32_t api_version_major{0};
uint32_t api_version_minor{0};
std::string server_info{};
std::string name{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
@ -566,6 +580,7 @@ class ListEntitiesSwitchResponse : public ProtoMessage {
bool assumed_state{false};
bool disabled_by_default{false};
enums::EntityCategory entity_category{};
std::string device_class{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
@ -1048,6 +1063,58 @@ class SelectCommandRequest : public ProtoMessage {
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
};
class ListEntitiesLockResponse : public ProtoMessage {
public:
std::string object_id{};
uint32_t key{0};
std::string name{};
std::string unique_id{};
std::string icon{};
bool disabled_by_default{false};
enums::EntityCategory entity_category{};
bool assumed_state{false};
bool supports_open{false};
bool requires_code{false};
std::string code_format{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class LockStateResponse : public ProtoMessage {
public:
uint32_t key{0};
enums::LockState state{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class LockCommandRequest : public ProtoMessage {
public:
uint32_t key{0};
enums::LockCommand command{};
bool has_code{false};
std::string code{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class ListEntitiesButtonResponse : public ProtoMessage {
public:
std::string object_id{};

View File

@ -282,6 +282,24 @@ bool APIServerConnectionBase::send_select_state_response(const SelectStateRespon
#endif
#ifdef USE_SELECT
#endif
#ifdef USE_LOCK
bool APIServerConnectionBase::send_list_entities_lock_response(const ListEntitiesLockResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_list_entities_lock_response: %s", msg.dump().c_str());
#endif
return this->send_message_<ListEntitiesLockResponse>(msg, 58);
}
#endif
#ifdef USE_LOCK
bool APIServerConnectionBase::send_lock_state_response(const LockStateResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_lock_state_response: %s", msg.dump().c_str());
#endif
return this->send_message_<LockStateResponse>(msg, 59);
}
#endif
#ifdef USE_LOCK
#endif
#ifdef USE_BUTTON
bool APIServerConnectionBase::send_list_entities_button_response(const ListEntitiesButtonResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
@ -523,6 +541,17 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
ESP_LOGVV(TAG, "on_select_command_request: %s", msg.dump().c_str());
#endif
this->on_select_command_request(msg);
#endif
break;
}
case 60: {
#ifdef USE_LOCK
LockCommandRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_lock_command_request: %s", msg.dump().c_str());
#endif
this->on_lock_command_request(msg);
#endif
break;
}
@ -771,6 +800,19 @@ void APIServerConnection::on_button_command_request(const ButtonCommandRequest &
this->button_command(msg);
}
#endif
#ifdef USE_LOCK
void APIServerConnection::on_lock_command_request(const LockCommandRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
this->lock_command(msg);
}
#endif
} // namespace api
} // namespace esphome

View File

@ -130,6 +130,15 @@ class APIServerConnectionBase : public ProtoService {
#ifdef USE_SELECT
virtual void on_select_command_request(const SelectCommandRequest &value){};
#endif
#ifdef USE_LOCK
bool send_list_entities_lock_response(const ListEntitiesLockResponse &msg);
#endif
#ifdef USE_LOCK
bool send_lock_state_response(const LockStateResponse &msg);
#endif
#ifdef USE_LOCK
virtual void on_lock_command_request(const LockCommandRequest &value){};
#endif
#ifdef USE_BUTTON
bool send_list_entities_button_response(const ListEntitiesButtonResponse &msg);
#endif
@ -180,6 +189,9 @@ class APIServerConnection : public APIServerConnectionBase {
#endif
#ifdef USE_BUTTON
virtual void button_command(const ButtonCommandRequest &msg) = 0;
#endif
#ifdef USE_LOCK
virtual void lock_command(const LockCommandRequest &msg) = 0;
#endif
protected:
void on_hello_request(const HelloRequest &msg) override;
@ -221,6 +233,9 @@ class APIServerConnection : public APIServerConnectionBase {
#ifdef USE_BUTTON
void on_button_command_request(const ButtonCommandRequest &msg) override;
#endif
#ifdef USE_LOCK
void on_lock_command_request(const LockCommandRequest &msg) override;
#endif
};
} // namespace api

View File

@ -24,7 +24,7 @@ static const char *const TAG = "api";
void APIServer::setup() {
ESP_LOGCONFIG(TAG, "Setting up Home Assistant API server...");
this->setup_controller();
socket_ = socket::socket(AF_INET, SOCK_STREAM, 0);
socket_ = socket::socket_ip(SOCK_STREAM, 0);
if (socket_ == nullptr) {
ESP_LOGW(TAG, "Could not create socket.");
this->mark_failed();
@ -43,13 +43,16 @@ void APIServer::setup() {
return;
}
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_addr.s_addr = ESPHOME_INADDR_ANY;
server.sin_port = htons(this->port_);
struct sockaddr_storage server;
err = socket_->bind((struct sockaddr *) &server, sizeof(server));
socklen_t sl = socket::set_sockaddr_any((struct sockaddr *) &server, sizeof(server), htons(this->port_));
if (sl == 0) {
ESP_LOGW(TAG, "Socket unable to set sockaddr: errno %d", errno);
this->mark_failed();
return;
}
err = socket_->bind((struct sockaddr *) &server, sl);
if (err != 0) {
ESP_LOGW(TAG, "Socket unable to bind: errno %d", errno);
this->mark_failed();
@ -80,9 +83,10 @@ void APIServer::setup() {
if (esp32_camera::global_esp32_camera != nullptr && !esp32_camera::global_esp32_camera->is_internal()) {
esp32_camera::global_esp32_camera->add_image_callback(
[this](const std::shared_ptr<esp32_camera::CameraImage> &image) {
for (auto &c : this->clients_)
for (auto &c : this->clients_) {
if (!c->remove_)
c->send_camera_state(image);
}
});
}
#endif
@ -188,7 +192,7 @@ void APIServer::on_cover_update(cover::Cover *obj) {
#endif
#ifdef USE_FAN
void APIServer::on_fan_update(fan::FanState *obj) {
void APIServer::on_fan_update(fan::Fan *obj) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
@ -259,6 +263,15 @@ void APIServer::on_select_update(select::Select *obj, const std::string &state)
}
#endif
#ifdef USE_LOCK
void APIServer::on_lock_update(lock::Lock *obj) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_lock_state(obj, obj->state);
}
#endif
float APIServer::get_setup_priority() const { return setup_priority::AFTER_WIFI; }
void APIServer::set_port(uint16_t port) { this->port_ = port; }
APIServer *global_api_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)

View File

@ -44,7 +44,7 @@ class APIServer : public Component, public Controller {
void on_cover_update(cover::Cover *obj) override;
#endif
#ifdef USE_FAN
void on_fan_update(fan::FanState *obj) override;
void on_fan_update(fan::Fan *obj) override;
#endif
#ifdef USE_LIGHT
void on_light_update(light::LightState *obj) override;
@ -66,6 +66,9 @@ class APIServer : public Component, public Controller {
#endif
#ifdef USE_SELECT
void on_select_update(select::Select *obj, const std::string &state) override;
#endif
#ifdef USE_LOCK
void on_lock_update(lock::Lock *obj) override;
#endif
void send_homeassistant_service_call(const HomeassistantServiceResponse &call);
void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); }

View File

@ -16,7 +16,7 @@ bool ListEntitiesIterator::on_binary_sensor(binary_sensor::BinarySensor *binary_
bool ListEntitiesIterator::on_cover(cover::Cover *cover) { return this->client_->send_cover_info(cover); }
#endif
#ifdef USE_FAN
bool ListEntitiesIterator::on_fan(fan::FanState *fan) { return this->client_->send_fan_info(fan); }
bool ListEntitiesIterator::on_fan(fan::Fan *fan) { return this->client_->send_fan_info(fan); }
#endif
#ifdef USE_LIGHT
bool ListEntitiesIterator::on_light(light::LightState *light) { return this->client_->send_light_info(light); }
@ -35,6 +35,9 @@ bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor)
return this->client_->send_text_sensor_info(text_sensor);
}
#endif
#ifdef USE_LOCK
bool ListEntitiesIterator::on_lock(lock::Lock *a_lock) { return this->client_->send_lock_info(a_lock); }
#endif
bool ListEntitiesIterator::on_end() { return this->client_->send_list_info_done(); }
ListEntitiesIterator::ListEntitiesIterator(APIServer *server, APIConnection *client)

View File

@ -19,7 +19,7 @@ class ListEntitiesIterator : public ComponentIterator {
bool on_cover(cover::Cover *cover) override;
#endif
#ifdef USE_FAN
bool on_fan(fan::FanState *fan) override;
bool on_fan(fan::Fan *fan) override;
#endif
#ifdef USE_LIGHT
bool on_light(light::LightState *light) override;
@ -48,6 +48,9 @@ class ListEntitiesIterator : public ComponentIterator {
#endif
#ifdef USE_SELECT
bool on_select(select::Select *select) override;
#endif
#ifdef USE_LOCK
bool on_lock(lock::Lock *a_lock) override;
#endif
bool on_end() override;

View File

@ -55,17 +55,19 @@ class ProtoVarInt {
}
int32_t as_sint32() const {
// with ZigZag encoding
if (this->value_ & 1)
if (this->value_ & 1) {
return static_cast<int32_t>(~(this->value_ >> 1));
else
} else {
return static_cast<int32_t>(this->value_ >> 1);
}
}
int64_t as_sint64() const {
// with ZigZag encoding
if (this->value_ & 1)
if (this->value_ & 1) {
return static_cast<int64_t>(~(this->value_ >> 1));
else
} else {
return static_cast<int64_t>(this->value_ >> 1);
}
}
void encode(std::vector<uint8_t> &out) {
uint32_t val = this->value_;
@ -220,10 +222,11 @@ class ProtoWriteBuffer {
}
void encode_sint32(uint32_t field_id, int32_t value, bool force = false) {
uint32_t uvalue;
if (value < 0)
if (value < 0) {
uvalue = ~(value << 1);
else
} else {
uvalue = value << 1;
}
this->encode_uint32(field_id, uvalue, force);
}
template<class C> void encode_message(uint32_t field_id, const C &value, bool force = false) {

View File

@ -14,7 +14,7 @@ bool InitialStateIterator::on_binary_sensor(binary_sensor::BinarySensor *binary_
bool InitialStateIterator::on_cover(cover::Cover *cover) { return this->client_->send_cover_state(cover); }
#endif
#ifdef USE_FAN
bool InitialStateIterator::on_fan(fan::FanState *fan) { return this->client_->send_fan_state(fan); }
bool InitialStateIterator::on_fan(fan::Fan *fan) { return this->client_->send_fan_state(fan); }
#endif
#ifdef USE_LIGHT
bool InitialStateIterator::on_light(light::LightState *light) { return this->client_->send_light_state(light); }
@ -47,6 +47,9 @@ bool InitialStateIterator::on_select(select::Select *select) {
return this->client_->send_select_state(select, select->state);
}
#endif
#ifdef USE_LOCK
bool InitialStateIterator::on_lock(lock::Lock *a_lock) { return this->client_->send_lock_state(a_lock, a_lock->state); }
#endif
InitialStateIterator::InitialStateIterator(APIServer *server, APIConnection *client)
: ComponentIterator(server), client_(client) {}

View File

@ -20,7 +20,7 @@ class InitialStateIterator : public ComponentIterator {
bool on_cover(cover::Cover *cover) override;
#endif
#ifdef USE_FAN
bool on_fan(fan::FanState *fan) override;
bool on_fan(fan::Fan *fan) override;
#endif
#ifdef USE_LIGHT
bool on_light(light::LightState *light) override;
@ -45,6 +45,9 @@ class InitialStateIterator : public ComponentIterator {
#endif
#ifdef USE_SELECT
bool on_select(select::Select *select) override;
#endif
#ifdef USE_LOCK
bool on_lock(lock::Lock *a_lock) override;
#endif
protected:
APIConnection *client_;

View File

@ -52,7 +52,7 @@ template<typename... Ts> class UserServiceBase : public UserServiceDescriptor {
protected:
virtual void execute(Ts... x) = 0;
template<int... S> void execute_(std::vector<ExecuteServiceArgument> args, seq<S...>) {
template<int... S> void execute_(std::vector<ExecuteServiceArgument> args, seq<S...> type) {
this->execute((get_execute_arg_value<Ts>(args[S]))...);
}

View File

@ -212,6 +212,21 @@ void ComponentIterator::advance() {
}
}
break;
#endif
#ifdef USE_LOCK
case IteratorState::LOCK:
if (this->at_ >= App.get_locks().size()) {
advance_platform = true;
} else {
auto *a_lock = App.get_locks()[this->at_];
if (a_lock->is_internal()) {
success = true;
break;
} else {
success = this->on_lock(a_lock);
}
}
break;
#endif
case IteratorState::MAX:
if (this->on_end()) {

View File

@ -27,7 +27,7 @@ class ComponentIterator {
virtual bool on_cover(cover::Cover *cover) = 0;
#endif
#ifdef USE_FAN
virtual bool on_fan(fan::FanState *fan) = 0;
virtual bool on_fan(fan::Fan *fan) = 0;
#endif
#ifdef USE_LIGHT
virtual bool on_light(light::LightState *light) = 0;
@ -56,6 +56,9 @@ class ComponentIterator {
#endif
#ifdef USE_SELECT
virtual bool on_select(select::Select *select) = 0;
#endif
#ifdef USE_LOCK
virtual bool on_lock(lock::Lock *a_lock) = 0;
#endif
virtual bool on_end();
@ -99,6 +102,9 @@ class ComponentIterator {
#endif
#ifdef USE_SELECT
SELECT,
#endif
#ifdef USE_LOCK
LOCK,
#endif
MAX,
} state_{IteratorState::NONE};

View File

@ -58,10 +58,11 @@ void AS3935Component::loop() {
void AS3935Component::write_indoor(bool indoor) {
ESP_LOGV(TAG, "Setting indoor to %d", indoor);
if (indoor)
if (indoor) {
this->write_register(AFE_GAIN, GAIN_MASK, INDOOR, 1);
else
} else {
this->write_register(AFE_GAIN, GAIN_MASK, OUTDOOR, 1);
}
}
// REG0x01, bits[3:0], manufacturer default: 0010 (2).
// This setting determines the threshold for events that trigger the

View File

@ -97,7 +97,7 @@ void BalluClimate::transmit_state() {
// Send code
auto transmit = this->transmitter_->transmit();
auto data = transmit.get_data();
auto *data = transmit.get_data();
data->set_carrier_frequency(38000);
@ -130,10 +130,10 @@ bool BalluClimate::on_receive(remote_base::RemoteReceiveData data) {
for (int i = 0; i < BALLU_STATE_LENGTH; i++) {
// Read bit
for (int j = 0; j < 8; j++) {
if (data.expect_item(BALLU_BIT_MARK, BALLU_ONE_SPACE))
if (data.expect_item(BALLU_BIT_MARK, BALLU_ONE_SPACE)) {
remote_state[i] |= 1 << j;
else if (!data.expect_item(BALLU_BIT_MARK, BALLU_ZERO_SPACE)) {
} else if (!data.expect_item(BALLU_BIT_MARK, BALLU_ZERO_SPACE)) {
ESP_LOGV(TAG, "Byte %d bit %d fail", i, j);
return false;
}

View File

@ -21,12 +21,13 @@ void BangBangClimate::setup() {
restore->to_call(this).perform();
} else {
// restore from defaults, change_away handles those for us
if (supports_cool_ && supports_heat_)
if (supports_cool_ && supports_heat_) {
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
else if (supports_cool_)
} else if (supports_cool_) {
this->mode = climate::CLIMATE_MODE_COOL;
else if (supports_heat_)
} else if (supports_heat_) {
this->mode = climate::CLIMATE_MODE_HEAT;
}
this->change_away_(false);
}
}
@ -56,11 +57,12 @@ climate::ClimateTraits BangBangClimate::traits() {
if (supports_cool_ && supports_heat_)
traits.add_supported_mode(climate::CLIMATE_MODE_HEAT_COOL);
traits.set_supports_two_point_target_temperature(true);
if (supports_away_)
if (supports_away_) {
traits.set_supported_presets({
climate::CLIMATE_PRESET_HOME,
climate::CLIMATE_PRESET_AWAY,
});
}
traits.set_supports_action(true);
return traits;
}
@ -82,17 +84,19 @@ void BangBangClimate::compute_state_() {
if (too_cold) {
// too cold -> enable heating if possible and enabled, else idle
if (this->supports_heat_ &&
(this->mode == climate::CLIMATE_MODE_HEAT_COOL || this->mode == climate::CLIMATE_MODE_HEAT))
(this->mode == climate::CLIMATE_MODE_HEAT_COOL || this->mode == climate::CLIMATE_MODE_HEAT)) {
target_action = climate::CLIMATE_ACTION_HEATING;
else
} else {
target_action = climate::CLIMATE_ACTION_IDLE;
}
} else if (too_hot) {
// too hot -> enable cooling if possible and enabled, else idle
if (this->supports_cool_ &&
(this->mode == climate::CLIMATE_MODE_HEAT_COOL || this->mode == climate::CLIMATE_MODE_COOL))
(this->mode == climate::CLIMATE_MODE_HEAT_COOL || this->mode == climate::CLIMATE_MODE_COOL)) {
target_action = climate::CLIMATE_ACTION_COOLING;
else
} else {
target_action = climate::CLIMATE_ACTION_IDLE;
}
} else {
// neither too hot nor too cold -> in range
if (this->supports_cool_ && this->supports_heat_ && this->mode == climate::CLIMATE_MODE_HEAT_COOL) {
@ -107,9 +111,10 @@ void BangBangClimate::compute_state_() {
this->switch_to_action_(target_action);
}
void BangBangClimate::switch_to_action_(climate::ClimateAction action) {
if (action == this->action)
if (action == this->action) {
// already in target mode
return;
}
if ((action == climate::CLIMATE_ACTION_OFF && this->action == climate::CLIMATE_ACTION_IDLE) ||
(action == climate::CLIMATE_ACTION_IDLE && this->action == climate::CLIMATE_ACTION_OFF)) {

View File

@ -9,7 +9,7 @@ from esphome.const import (
)
from .. import binary_ns
BinaryFan = binary_ns.class_("BinaryFan", cg.Component)
BinaryFan = binary_ns.class_("BinaryFan", fan.Fan, cg.Component)
CONFIG_SCHEMA = fan.FAN_SCHEMA.extend(
{
@ -24,9 +24,8 @@ CONFIG_SCHEMA = fan.FAN_SCHEMA.extend(
async def to_code(config):
var = cg.new_Pvariable(config[CONF_OUTPUT_ID])
await cg.register_component(var, config)
await fan.register_fan(var, config)
fan_ = await fan.create_fan_state(config)
cg.add(var.set_fan(fan_))
output_ = await cg.get_variable(config[CONF_OUTPUT])
cg.add(var.set_output(output_))

View File

@ -6,59 +6,35 @@ namespace binary {
static const char *const TAG = "binary.fan";
void binary::BinaryFan::dump_config() {
ESP_LOGCONFIG(TAG, "Fan '%s':", this->fan_->get_name().c_str());
if (this->fan_->get_traits().supports_oscillation()) {
ESP_LOGCONFIG(TAG, " Oscillation: YES");
}
if (this->fan_->get_traits().supports_direction()) {
ESP_LOGCONFIG(TAG, " Direction: YES");
}
}
void BinaryFan::setup() {
auto traits = fan::FanTraits(this->oscillating_ != nullptr, false, this->direction_ != nullptr, 0);
this->fan_->set_traits(traits);
this->fan_->add_on_state_callback([this]() { this->next_update_ = true; });
}
void BinaryFan::loop() {
if (!this->next_update_) {
return;
}
this->next_update_ = false;
{
bool enable = this->fan_->state;
if (enable)
this->output_->turn_on();
else
this->output_->turn_off();
ESP_LOGD(TAG, "Setting binary state: %s", ONOFF(enable));
}
if (this->oscillating_ != nullptr) {
bool enable = this->fan_->oscillating;
if (enable) {
this->oscillating_->turn_on();
} else {
this->oscillating_->turn_off();
}
ESP_LOGD(TAG, "Setting oscillation: %s", ONOFF(enable));
}
if (this->direction_ != nullptr) {
bool enable = this->fan_->direction == fan::FAN_DIRECTION_REVERSE;
if (enable) {
this->direction_->turn_on();
} else {
this->direction_->turn_off();
}
ESP_LOGD(TAG, "Setting reverse direction: %s", ONOFF(enable));
auto restore = this->restore_state_();
if (restore.has_value()) {
restore->apply(*this);
this->write_state_();
}
}
void BinaryFan::dump_config() { LOG_FAN("", "Binary Fan", this); }
fan::FanTraits BinaryFan::get_traits() {
return fan::FanTraits(this->oscillating_ != nullptr, false, this->direction_ != nullptr, 0);
}
void BinaryFan::control(const fan::FanCall &call) {
if (call.get_state().has_value())
this->state = *call.get_state();
if (call.get_oscillating().has_value())
this->oscillating = *call.get_oscillating();
if (call.get_direction().has_value())
this->direction = *call.get_direction();
// We need a higher priority than the FanState component to make sure that the traits are set
// when that component sets itself up.
float BinaryFan::get_setup_priority() const { return fan_->get_setup_priority() + 1.0f; }
this->write_state_();
this->publish_state();
}
void BinaryFan::write_state_() {
this->output_->set_state(this->state);
if (this->oscillating_ != nullptr)
this->oscillating_->set_state(this->oscillating);
if (this->direction_ != nullptr)
this->direction_->set_state(this->direction == fan::FanDirection::REVERSE);
}
} // namespace binary
} // namespace esphome

View File

@ -2,28 +2,29 @@
#include "esphome/core/component.h"
#include "esphome/components/output/binary_output.h"
#include "esphome/components/fan/fan_state.h"
#include "esphome/components/fan/fan.h"
namespace esphome {
namespace binary {
class BinaryFan : public Component {
class BinaryFan : public Component, public fan::Fan {
public:
void set_fan(fan::FanState *fan) { fan_ = fan; }
void set_output(output::BinaryOutput *output) { output_ = output; }
void setup() override;
void loop() override;
void dump_config() override;
float get_setup_priority() const override;
void set_output(output::BinaryOutput *output) { this->output_ = output; }
void set_oscillating(output::BinaryOutput *oscillating) { this->oscillating_ = oscillating; }
void set_direction(output::BinaryOutput *direction) { this->direction_ = direction; }
fan::FanTraits get_traits() override;
protected:
fan::FanState *fan_;
void control(const fan::FanCall &call) override;
void write_state_();
output::BinaryOutput *output_;
output::BinaryOutput *oscillating_{nullptr};
output::BinaryOutput *direction_{nullptr};
bool next_update_{true};
};
} // namespace binary

View File

@ -65,7 +65,7 @@ void BL0940::update() {
}
void BL0940::setup() {
for (auto i : BL0940_INIT) {
for (auto *i : BL0940_INIT) {
this->write_array(i, 6);
delay(1);
}

View File

@ -167,7 +167,7 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es
break;
}
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
auto descr = this->get_config_descriptor(param->reg_for_notify.handle);
auto *descr = this->get_config_descriptor(param->reg_for_notify.handle);
if (descr == nullptr) {
ESP_LOGW(TAG, "No descriptor found for notify of handle 0x%x", param->reg_for_notify.handle);
break;
@ -252,16 +252,17 @@ float BLEClient::parse_char_value(uint8_t *value, uint16_t length) {
}
BLEService *BLEClient::get_service(espbt::ESPBTUUID uuid) {
for (auto svc : this->services_)
for (auto *svc : this->services_) {
if (svc->uuid == uuid)
return svc;
}
return nullptr;
}
BLEService *BLEClient::get_service(uint16_t uuid) { return this->get_service(espbt::ESPBTUUID::from_uint16(uuid)); }
BLECharacteristic *BLEClient::get_characteristic(espbt::ESPBTUUID service, espbt::ESPBTUUID chr) {
auto svc = this->get_service(service);
auto *svc = this->get_service(service);
if (svc == nullptr)
return nullptr;
return svc->get_characteristic(chr);
@ -272,19 +273,24 @@ BLECharacteristic *BLEClient::get_characteristic(uint16_t service, uint16_t chr)
}
BLEDescriptor *BLEClient::get_config_descriptor(uint16_t handle) {
for (auto &svc : this->services_)
for (auto &chr : svc->characteristics)
if (chr->handle == handle)
for (auto &desc : chr->descriptors)
for (auto &svc : this->services_) {
for (auto &chr : svc->characteristics) {
if (chr->handle == handle) {
for (auto &desc : chr->descriptors) {
if (desc->uuid == espbt::ESPBTUUID::from_uint16(0x2902))
return desc;
}
}
}
}
return nullptr;
}
BLECharacteristic *BLEService::get_characteristic(espbt::ESPBTUUID uuid) {
for (auto &chr : this->characteristics)
for (auto &chr : this->characteristics) {
if (chr->uuid == uuid)
return chr;
}
return nullptr;
}
@ -293,10 +299,10 @@ BLECharacteristic *BLEService::get_characteristic(uint16_t uuid) {
}
BLEDescriptor *BLEClient::get_descriptor(espbt::ESPBTUUID service, espbt::ESPBTUUID chr, espbt::ESPBTUUID descr) {
auto svc = this->get_service(service);
auto *svc = this->get_service(service);
if (svc == nullptr)
return nullptr;
auto ch = svc->get_characteristic(chr);
auto *ch = svc->get_characteristic(chr);
if (ch == nullptr)
return nullptr;
return ch->get_descriptor(descr);
@ -379,24 +385,29 @@ void BLECharacteristic::parse_descriptors() {
}
BLEDescriptor *BLECharacteristic::get_descriptor(espbt::ESPBTUUID uuid) {
for (auto &desc : this->descriptors)
for (auto &desc : this->descriptors) {
if (desc->uuid == uuid)
return desc;
}
return nullptr;
}
BLEDescriptor *BLECharacteristic::get_descriptor(uint16_t uuid) {
return this->get_descriptor(espbt::ESPBTUUID::from_uint16(uuid));
}
void BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size) {
auto client = this->service->client;
void BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size, esp_gatt_write_type_t write_type) {
auto *client = this->service->client;
auto status = esp_ble_gattc_write_char(client->gattc_if, client->conn_id, this->handle, new_val_size, new_val,
ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
write_type, ESP_GATT_AUTH_REQ_NONE);
if (status) {
ESP_LOGW(TAG, "Error sending write value to BLE gattc server, status=%d", status);
}
}
void BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size) {
write_value(new_val, new_val_size, ESP_GATT_WRITE_TYPE_NO_RSP);
}
} // namespace ble_client
} // namespace esphome

View File

@ -60,6 +60,7 @@ class BLECharacteristic {
BLEDescriptor *get_descriptor(espbt::ESPBTUUID uuid);
BLEDescriptor *get_descriptor(uint16_t uuid);
void write_value(uint8_t *new_val, int16_t new_val_size);
void write_value(uint8_t *new_val, int16_t new_val_size, esp_gatt_write_type_t write_type);
BLEService *service;
};
@ -126,9 +127,10 @@ class BLEClient : public espbt::ESPBTClient, public Component {
bool all_nodes_established_() {
if (this->state() != espbt::ClientState::ESTABLISHED)
return false;
for (auto &node : nodes_)
for (auto &node : nodes_) {
if (node->node_state != espbt::ClientState::ESTABLISHED)
return false;
}
return true;
}

View File

@ -1,13 +1,14 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import output, ble_client, esp32_ble_tracker
from esphome.components import ble_client, esp32_ble_tracker, output
from esphome.const import CONF_ID, CONF_SERVICE_UUID
from .. import ble_client_ns
from .. import ble_client_ns
DEPENDENCIES = ["ble_client"]
CONF_CHARACTERISTIC_UUID = "characteristic_uuid"
CONF_REQUIRE_RESPONSE = "require_response"
BLEBinaryOutput = ble_client_ns.class_(
"BLEBinaryOutput", output.BinaryOutput, ble_client.BLEClientNode, cg.Component
@ -19,6 +20,7 @@ CONFIG_SCHEMA = cv.All(
cv.Required(CONF_ID): cv.declare_id(BLEBinaryOutput),
cv.Required(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid,
cv.Required(CONF_CHARACTERISTIC_UUID): esp32_ble_tracker.bt_uuid,
cv.Optional(CONF_REQUIRE_RESPONSE, default=False): cv.boolean,
}
)
.extend(cv.COMPONENT_SCHEMA)
@ -61,7 +63,7 @@ def to_code(config):
config[CONF_CHARACTERISTIC_UUID]
)
cg.add(var.set_char_uuid128(uuid128))
cg.add(var.set_require_response(config[CONF_REQUIRE_RESPONSE]))
yield output.register_output(var, config)
yield ble_client.register_ble_node(var, config)
yield cg.register_component(var, config)

View File

@ -32,7 +32,7 @@ void BLEBinaryOutput::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_i
break;
}
auto chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_);
auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_);
if (chr == nullptr) {
ESP_LOGW(TAG, "[%s] Characteristic not found.", this->char_uuid_.to_string().c_str());
break;
@ -54,7 +54,7 @@ void BLEBinaryOutput::write_state(bool state) {
return;
}
auto chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_);
auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_);
if (chr == nullptr) {
ESP_LOGW(TAG, "[%s] Characteristic not found. State update can not be written.",
this->char_uuid_.to_string().c_str());
@ -63,7 +63,11 @@ void BLEBinaryOutput::write_state(bool state) {
uint8_t state_as_uint = (uint8_t) state;
ESP_LOGV(TAG, "[%s] Write State: %d", this->char_uuid_.to_string().c_str(), state_as_uint);
chr->write_value(&state_as_uint, sizeof(state_as_uint));
if (this->require_response_) {
chr->write_value(&state_as_uint, sizeof(state_as_uint), ESP_GATT_WRITE_TYPE_RSP);
} else {
chr->write_value(&state_as_uint, sizeof(state_as_uint), ESP_GATT_WRITE_TYPE_NO_RSP);
}
}
} // namespace ble_client

View File

@ -25,9 +25,11 @@ class BLEBinaryOutput : public output::BinaryOutput, public BLEClientNode, publi
void set_char_uuid128(uint8_t *uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) override;
void set_require_response(bool response) { this->require_response_ = response; }
protected:
void write_state(bool state) override;
bool require_response_;
espbt::ESPBTUUID service_uuid_;
espbt::ESPBTUUID char_uuid_;
espbt::ClientState client_state_;

View File

@ -43,7 +43,7 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
}
case ESP_GATTC_SEARCH_CMPL_EVT: {
this->handle = 0;
auto chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_);
auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_);
if (chr == nullptr) {
this->status_set_warning();
this->publish_state(NAN);
@ -53,7 +53,7 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
}
this->handle = chr->handle;
if (this->descr_uuid_.get_uuid().len > 0) {
auto descr = chr->get_descriptor(this->descr_uuid_);
auto *descr = chr->get_descriptor(this->descr_uuid_);
if (descr == nullptr) {
this->status_set_warning();
this->publish_state(NAN);

View File

@ -1,7 +1,6 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import text_sensor, esp32_ble_tracker
from esphome.const import CONF_ID
DEPENDENCIES = ["esp32_ble_tracker"]
@ -14,18 +13,13 @@ BLEScanner = ble_scanner_ns.class_(
)
CONFIG_SCHEMA = cv.All(
text_sensor.TEXT_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(BLEScanner),
}
)
text_sensor.text_sensor_schema(klass=BLEScanner)
.extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
.extend(cv.COMPONENT_SCHEMA)
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
var = await text_sensor.new_text_sensor(config)
await cg.register_component(var, config)
await esp32_ble_tracker.register_ble_device(var, config)
await text_sensor.register_text_sensor(var, config)

View File

@ -214,9 +214,10 @@ void BME280Component::update() {
float BME280Component::read_temperature_(const uint8_t *data, int32_t *t_fine) {
int32_t adc = ((data[3] & 0xFF) << 16) | ((data[4] & 0xFF) << 8) | (data[5] & 0xFF);
adc >>= 4;
if (adc == 0x80000)
if (adc == 0x80000) {
// temperature was disabled
return NAN;
}
const int32_t t1 = this->calibration_.t1;
const int32_t t2 = this->calibration_.t2;
@ -233,9 +234,10 @@ float BME280Component::read_temperature_(const uint8_t *data, int32_t *t_fine) {
float BME280Component::read_pressure_(const uint8_t *data, int32_t t_fine) {
int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF);
adc >>= 4;
if (adc == 0x80000)
if (adc == 0x80000) {
// pressure was disabled
return NAN;
}
const int64_t p1 = this->calibration_.p1;
const int64_t p2 = this->calibration_.p2;
const int64_t p3 = this->calibration_.p3;

View File

@ -95,7 +95,7 @@ void BME680Component::setup() {
this->calibration_.t3 = cal1[3];
this->calibration_.h1 = cal2[2] << 4 | (cal2[1] & 0x0F);
this->calibration_.h2 = cal2[0] << 4 | cal2[1];
this->calibration_.h2 = cal2[0] << 4 | cal2[1] >> 4;
this->calibration_.h3 = cal2[3];
this->calibration_.h4 = cal2[4];
this->calibration_.h5 = cal2[5];
@ -420,10 +420,11 @@ float BME680Component::calc_humidity_(uint16_t raw_humidity) {
calc_hum = var2 + (var3 + var4 * temp_comp) * var2 * var2;
if (calc_hum > 100.0f)
if (calc_hum > 100.0f) {
calc_hum = 100.0f;
else if (calc_hum < 0.0f)
} else if (calc_hum < 0.0f) {
calc_hum = 0.0f;
}
return calc_hum;
}

View File

@ -1,7 +1,6 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import text_sensor
from esphome.const import CONF_ID, CONF_ICON
from . import BME680BSECComponent, CONF_BME680_BSEC_ID
DEPENDENCIES = ["bme680_bsec"]
@ -14,11 +13,8 @@ TYPES = [CONF_IAQ_ACCURACY]
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_BME680_BSEC_ID): cv.use_id(BME680BSECComponent),
cv.Optional(CONF_IAQ_ACCURACY): text_sensor.TEXT_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
cv.Optional(CONF_ICON, default=ICON_ACCURACY): cv.icon,
}
cv.Optional(CONF_IAQ_ACCURACY): text_sensor.text_sensor_schema(
icon=ICON_ACCURACY
),
}
)
@ -27,8 +23,7 @@ CONFIG_SCHEMA = cv.Schema(
async def setup_conf(config, key, hub):
if key in config:
conf = config[key]
sens = cg.new_Pvariable(conf[CONF_ID])
await text_sensor.register_text_sensor(sens, conf)
sens = await text_sensor.new_text_sensor(conf)
cg.add(getattr(hub, f"set_{key}_text_sensor")(sens))

View File

@ -161,9 +161,10 @@ float BMP280Component::read_temperature_(int32_t *t_fine) {
return NAN;
int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF);
adc >>= 4;
if (adc == 0x80000)
if (adc == 0x80000) {
// temperature was disabled
return NAN;
}
const int32_t t1 = this->calibration_.t1;
const int32_t t2 = this->calibration_.t2;
@ -183,9 +184,10 @@ float BMP280Component::read_pressure_(int32_t t_fine) {
return NAN;
int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF);
adc >>= 4;
if (adc == 0x80000)
if (adc == 0x80000) {
// pressure was disabled
return NAN;
}
const int64_t p1 = this->calibration_.p1;
const int64_t p2 = this->calibration_.p2;
const int64_t p3 = this->calibration_.p3;

View File

@ -75,7 +75,7 @@ void Canbus::loop() {
}
// fire all triggers
for (auto trigger : this->triggers_) {
for (auto *trigger : this->triggers_) {
if ((trigger->can_id_ == can_message.can_id) && (trigger->use_extended_id_ == can_message.use_extended_id)) {
trigger->trigger(data);
}

View File

@ -39,14 +39,15 @@ void CCS811Component::setup() {
// set MEAS_MODE (page 5)
uint8_t meas_mode = 0;
uint32_t interval = this->get_update_interval();
if (interval >= 60 * 1000)
if (interval >= 60 * 1000) {
meas_mode = 3 << 4; // sensor takes a reading every 60 seconds
else if (interval >= 10 * 1000)
} else if (interval >= 10 * 1000) {
meas_mode = 2 << 4; // sensor takes a reading every 10 seconds
else if (interval >= 1 * 1000)
} else if (interval >= 1 * 1000) {
meas_mode = 1 << 4; // sensor takes a reading every second
else
} else {
meas_mode = 4 << 4; // sensor takes a reading every 250ms
}
CHECKED_IO(this->write_byte(0x01, meas_mode))

View File

@ -2,7 +2,6 @@ import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor, text_sensor
from esphome.const import (
CONF_ICON,
CONF_ID,
ICON_RADIATOR,
ICON_RESTART,
@ -47,11 +46,8 @@ CONFIG_SCHEMA = (
device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_VERSION): text_sensor.TEXT_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
cv.Optional(CONF_ICON, default=ICON_RESTART): cv.icon,
}
cv.Optional(CONF_VERSION): text_sensor.text_sensor_schema(
icon=ICON_RESTART
),
cv.Optional(CONF_BASELINE): cv.hex_uint16_t,
cv.Optional(CONF_TEMPERATURE): cv.use_id(sensor.Sensor),
@ -74,8 +70,7 @@ async def to_code(config):
cg.add(var.set_tvoc(sens))
if CONF_VERSION in config:
sens = cg.new_Pvariable(config[CONF_VERSION][CONF_ID])
await text_sensor.register_text_sensor(sens, config[CONF_VERSION])
sens = await text_sensor.new_text_sensor(config[CONF_VERSION])
cg.add(var.set_version(sens))
if CONF_BASELINE in config:

View File

@ -6,8 +6,9 @@ from esphome.const import (
CONF_ID,
)
CODEOWNERS = ["@asoehlke"]
AUTO_LOAD = ["sensor", "voltage_sampler"]
CODEOWNERS = ["@asoehlke"]
MULTI_CONF = True
cd74hc4067_ns = cg.esphome_ns.namespace("cd74hc4067")

View File

@ -65,7 +65,7 @@ class ClimateTraits {
ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead", "v1.20")
void set_supports_dry_mode(bool supports_dry_mode) { set_mode_support_(CLIMATE_MODE_DRY, supports_dry_mode); }
bool supports_mode(ClimateMode mode) const { return supported_modes_.count(mode); }
const std::set<ClimateMode> get_supported_modes() const { return supported_modes_; }
std::set<ClimateMode> get_supported_modes() const { return supported_modes_; }
void set_supports_action(bool supports_action) { supports_action_ = supports_action; }
bool get_supports_action() const { return supports_action_; }
@ -93,7 +93,7 @@ class ClimateTraits {
void set_supports_fan_mode_diffuse(bool supported) { set_fan_mode_support_(CLIMATE_FAN_DIFFUSE, supported); }
bool supports_fan_mode(ClimateFanMode fan_mode) const { return supported_fan_modes_.count(fan_mode); }
bool get_supports_fan_modes() const { return !supported_fan_modes_.empty() || !supported_custom_fan_modes_.empty(); }
const std::set<ClimateFanMode> get_supported_fan_modes() const { return supported_fan_modes_; }
std::set<ClimateFanMode> get_supported_fan_modes() const { return supported_fan_modes_; }
void set_supported_custom_fan_modes(std::set<std::string> supported_custom_fan_modes) {
supported_custom_fan_modes_ = std::move(supported_custom_fan_modes);
@ -141,7 +141,7 @@ class ClimateTraits {
}
bool supports_swing_mode(ClimateSwingMode swing_mode) const { return supported_swing_modes_.count(swing_mode); }
bool get_supports_swing_modes() const { return !supported_swing_modes_.empty(); }
const std::set<ClimateSwingMode> get_supported_swing_modes() { return supported_swing_modes_; }
std::set<ClimateSwingMode> get_supported_swing_modes() { return supported_swing_modes_; }
float get_visual_min_temperature() const { return visual_min_temperature_; }
void set_visual_min_temperature(float visual_min_temperature) { visual_min_temperature_ = visual_min_temperature; }

View File

@ -137,11 +137,11 @@ bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) {
this->swing_mode =
this->swing_mode == climate::CLIMATE_SWING_OFF ? climate::CLIMATE_SWING_VERTICAL : climate::CLIMATE_SWING_OFF;
} else {
if ((remote_state & COMMAND_MASK) == COMMAND_AUTO)
if ((remote_state & COMMAND_MASK) == COMMAND_AUTO) {
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
else if ((remote_state & COMMAND_MASK) == COMMAND_DRY_FAN)
} else if ((remote_state & COMMAND_MASK) == COMMAND_DRY_FAN) {
this->mode = climate::CLIMATE_MODE_DRY;
else if ((remote_state & COMMAND_MASK) == COMMAND_HEAT) {
} else if ((remote_state & COMMAND_MASK) == COMMAND_HEAT) {
this->mode = climate::CLIMATE_MODE_HEAT;
} else {
this->mode = climate::CLIMATE_MODE_COOL;
@ -156,14 +156,15 @@ bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) {
this->fan_mode = climate::CLIMATE_FAN_AUTO;
} else if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_HEAT ||
this->mode == climate::CLIMATE_MODE_DRY) {
if ((remote_state & FAN_MASK) == FAN_AUTO)
if ((remote_state & FAN_MASK) == FAN_AUTO) {
this->fan_mode = climate::CLIMATE_FAN_AUTO;
else if ((remote_state & FAN_MASK) == FAN_MIN)
} else if ((remote_state & FAN_MASK) == FAN_MIN) {
this->fan_mode = climate::CLIMATE_FAN_LOW;
else if ((remote_state & FAN_MASK) == FAN_MED)
} else if ((remote_state & FAN_MASK) == FAN_MED) {
this->fan_mode = climate::CLIMATE_FAN_MEDIUM;
else if ((remote_state & FAN_MASK) == FAN_MAX)
} else if ((remote_state & FAN_MASK) == FAN_MAX) {
this->fan_mode = climate::CLIMATE_FAN_HIGH;
}
}
}
this->publish_state();
@ -175,7 +176,7 @@ void LgIrClimate::transmit_(uint32_t value) {
ESP_LOGD(TAG, "Sending climate_lg_ir code: 0x%02X", value);
auto transmit = this->transmitter_->transmit();
auto data = transmit.get_data();
auto *data = transmit.get_data();
data->set_carrier_frequency(38000);
data->reserve(2 + BITS * 2u);

View File

@ -104,7 +104,7 @@ void CoolixClimate::transmit_state() {
ESP_LOGV(TAG, "Sending coolix code: 0x%06X", remote_state);
auto transmit = this->transmitter_->transmit();
auto data = transmit.get_data();
auto *data = transmit.get_data();
remote_base::CoolixProtocol().encode(data, remote_state);
transmit.perform();
}
@ -125,34 +125,37 @@ bool CoolixClimate::on_coolix(climate::Climate *parent, remote_base::RemoteRecei
parent->swing_mode =
parent->swing_mode == climate::CLIMATE_SWING_OFF ? climate::CLIMATE_SWING_VERTICAL : climate::CLIMATE_SWING_OFF;
} else {
if ((remote_state & COOLIX_MODE_MASK) == COOLIX_HEAT)
if ((remote_state & COOLIX_MODE_MASK) == COOLIX_HEAT) {
parent->mode = climate::CLIMATE_MODE_HEAT;
else if ((remote_state & COOLIX_MODE_MASK) == COOLIX_AUTO)
} else if ((remote_state & COOLIX_MODE_MASK) == COOLIX_AUTO) {
parent->mode = climate::CLIMATE_MODE_HEAT_COOL;
else if ((remote_state & COOLIX_MODE_MASK) == COOLIX_DRY_FAN) {
if ((remote_state & COOLIX_FAN_MASK) == COOLIX_FAN_MODE_AUTO_DRY)
} else if ((remote_state & COOLIX_MODE_MASK) == COOLIX_DRY_FAN) {
if ((remote_state & COOLIX_FAN_MASK) == COOLIX_FAN_MODE_AUTO_DRY) {
parent->mode = climate::CLIMATE_MODE_DRY;
else
} else {
parent->mode = climate::CLIMATE_MODE_FAN_ONLY;
}
} else
parent->mode = climate::CLIMATE_MODE_COOL;
// Fan Speed
if ((remote_state & COOLIX_FAN_AUTO) == COOLIX_FAN_AUTO || parent->mode == climate::CLIMATE_MODE_HEAT_COOL ||
parent->mode == climate::CLIMATE_MODE_DRY)
parent->mode == climate::CLIMATE_MODE_DRY) {
parent->fan_mode = climate::CLIMATE_FAN_AUTO;
else if ((remote_state & COOLIX_FAN_MIN) == COOLIX_FAN_MIN)
} else if ((remote_state & COOLIX_FAN_MIN) == COOLIX_FAN_MIN) {
parent->fan_mode = climate::CLIMATE_FAN_LOW;
else if ((remote_state & COOLIX_FAN_MED) == COOLIX_FAN_MED)
} else if ((remote_state & COOLIX_FAN_MED) == COOLIX_FAN_MED) {
parent->fan_mode = climate::CLIMATE_FAN_MEDIUM;
else if ((remote_state & COOLIX_FAN_MAX) == COOLIX_FAN_MAX)
} else if ((remote_state & COOLIX_FAN_MAX) == COOLIX_FAN_MAX) {
parent->fan_mode = climate::CLIMATE_FAN_HIGH;
}
// Temperature
uint8_t temperature_code = remote_state & COOLIX_TEMP_MASK;
for (uint8_t i = 0; i < COOLIX_TEMP_RANGE; i++)
for (uint8_t i = 0; i < COOLIX_TEMP_RANGE; i++) {
if (COOLIX_TEMP_MAP[i] == temperature_code)
parent->target_temperature = i + COOLIX_TEMP_MIN;
}
}
parent->publish_state();

View File

@ -7,6 +7,7 @@ from esphome.const import (
CONF_ID,
CONF_DEVICE_CLASS,
CONF_STATE,
CONF_ON_OPEN,
CONF_POSITION,
CONF_POSITION_COMMAND_TOPIC,
CONF_POSITION_STATE_TOPIC,
@ -74,7 +75,6 @@ CoverClosedTrigger = cover_ns.class_(
"CoverClosedTrigger", automation.Trigger.template()
)
CONF_ON_OPEN = "on_open"
CONF_ON_CLOSED = "on_closed"
COVER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(

View File

@ -49,8 +49,8 @@ def validate_config(config):
if current_gain == 0.0 or voltage_gain == 0.0:
raise cv.Invalid("The gains can't be zero")
max_energy = (0.25 * 0.25 / 3600 / (2 ** -4)) / (voltage_gain * current_gain)
min_energy = (0.25 * 0.25 / 3600 / (2 ** 18)) / (voltage_gain * current_gain)
max_energy = (0.25 * 0.25 / 3600 / (2**-4)) / (voltage_gain * current_gain)
min_energy = (0.25 * 0.25 / 3600 / (2**18)) / (voltage_gain * current_gain)
mech_min_energy = (0.25 * 0.25 / 3600 / 7.8) / (voltage_gain * current_gain)
if pulse_energy < min_energy or pulse_energy > max_energy:
raise cv.Invalid(

View File

@ -11,11 +11,7 @@ CONFIG_SCHEMA = cv.Schema(
cv.GenerateID(): cv.declare_id(CustomTextSensorConstructor),
cv.Required(CONF_LAMBDA): cv.returning_lambda,
cv.Required(CONF_TEXT_SENSORS): cv.ensure_list(
text_sensor.TEXT_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
}
)
text_sensor.text_sensor_schema()
),
}
)

View File

@ -23,7 +23,7 @@ void DaikinClimate::transmit_state() {
}
auto transmit = this->transmitter_->transmit();
auto data = transmit.get_data();
auto *data = transmit.get_data();
data->set_carrier_frequency(DAIKIN_IR_FREQUENCY);
data->mark(DAIKIN_HEADER_MARK);
@ -175,14 +175,15 @@ bool DaikinClimate::parse_state_frame_(const uint8_t frame[]) {
}
uint8_t fan_mode = frame[8];
uint8_t swing_mode = frame[9];
if (fan_mode & 0xF && swing_mode & 0xF)
if (fan_mode & 0xF && swing_mode & 0xF) {
this->swing_mode = climate::CLIMATE_SWING_BOTH;
else if (fan_mode & 0xF)
} else if (fan_mode & 0xF) {
this->swing_mode = climate::CLIMATE_SWING_VERTICAL;
else if (swing_mode & 0xF)
} else if (swing_mode & 0xF) {
this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL;
else
} else {
this->swing_mode = climate::CLIMATE_SWING_OFF;
}
switch (fan_mode & 0xF0) {
case DAIKIN_FAN_1:
case DAIKIN_FAN_2:
@ -212,9 +213,9 @@ bool DaikinClimate::on_receive(remote_base::RemoteReceiveData data) {
for (uint8_t pos = 0; pos < DAIKIN_STATE_FRAME_SIZE; pos++) {
uint8_t byte = 0;
for (int8_t bit = 0; bit < 8; bit++) {
if (data.expect_item(DAIKIN_BIT_MARK, DAIKIN_ONE_SPACE))
if (data.expect_item(DAIKIN_BIT_MARK, DAIKIN_ONE_SPACE)) {
byte |= 1 << bit;
else if (!data.expect_item(DAIKIN_BIT_MARK, DAIKIN_ZERO_SPACE)) {
} else if (!data.expect_item(DAIKIN_BIT_MARK, DAIKIN_ZERO_SPACE)) {
return false;
}
}

View File

@ -52,7 +52,7 @@ void DallasComponent::setup() {
this->found_sensors_.push_back(address);
}
for (auto sensor : this->sensors_) {
for (auto *sensor : this->sensors_) {
if (sensor->get_index().has_value()) {
if (*sensor->get_index() >= this->found_sensors_.size()) {
this->status_set_error();

View File

@ -145,9 +145,10 @@ uint64_t ESPOneWire::search() {
// read its complement
bool cmp_id_bit = this->read_bit();
if (id_bit && cmp_id_bit)
if (id_bit && cmp_id_bit) {
// No devices participating in search
break;
}
bool branch;
@ -170,12 +171,13 @@ uint64_t ESPOneWire::search() {
}
}
if (branch)
if (branch) {
// set bit
this->rom_number8_()[rom_byte_number] |= rom_byte_mask;
else
} else {
// clear bit
this->rom_number8_()[rom_byte_number] &= ~rom_byte_mask;
}
// choose/announce branch
this->write_bit(branch);
@ -190,9 +192,10 @@ uint64_t ESPOneWire::search() {
if (id_bit_number >= 65) {
this->last_discrepancy_ = last_zero;
if (this->last_discrepancy_ == 0)
if (this->last_discrepancy_ == 0) {
// we're at root and have no choices left, so this was the last one.
this->last_device_flag_ = true;
}
search_result = true;
}

View File

@ -1,7 +1,7 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import text_sensor
from esphome.const import CONF_ICON, CONF_ID, CONF_STATUS
from esphome.const import CONF_STATUS
from . import DalyBmsComponent, CONF_BMS_DALY_ID
ICON_CAR_BATTERY = "mdi:car-battery"
@ -14,11 +14,8 @@ CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(CONF_BMS_DALY_ID): cv.use_id(DalyBmsComponent),
cv.Optional(CONF_STATUS): text_sensor.TEXT_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
cv.Optional(CONF_ICON, default=ICON_CAR_BATTERY): cv.icon,
}
cv.Optional(CONF_STATUS): text_sensor.text_sensor_schema(
icon=ICON_CAR_BATTERY
),
}
).extend(cv.COMPONENT_SCHEMA)
@ -28,8 +25,7 @@ CONFIG_SCHEMA = cv.All(
async def setup_conf(config, key, hub):
if key in config:
conf = config[key]
sens = cg.new_Pvariable(conf[CONF_ID])
await text_sensor.register_text_sensor(sens, conf)
sens = await text_sensor.new_text_sensor(conf)
cg.add(getattr(hub, f"set_{key}_text_sensor")(sens))

View File

@ -1,17 +1,42 @@
import esphome.config_validation as cv
import esphome.codegen as cg
from esphome.const import CONF_ID
from esphome.const import (
CONF_ID,
CONF_DEVICE,
CONF_FREE,
CONF_FRAGMENTATION,
CONF_BLOCK,
CONF_LOOP_TIME,
)
CODEOWNERS = ["@OttoWinter"]
DEPENDENCIES = ["logger"]
CONF_DEBUG_ID = "debug_id"
debug_ns = cg.esphome_ns.namespace("debug")
DebugComponent = debug_ns.class_("DebugComponent", cg.Component)
DebugComponent = debug_ns.class_("DebugComponent", cg.PollingComponent)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(DebugComponent),
cv.Optional(CONF_DEVICE): cv.invalid(
"The 'device' option has been moved to the 'debug' text_sensor component"
),
cv.Optional(CONF_FREE): cv.invalid(
"The 'free' option has been moved to the 'debug' sensor component"
),
cv.Optional(CONF_BLOCK): cv.invalid(
"The 'block' option has been moved to the 'debug' sensor component"
),
cv.Optional(CONF_FRAGMENTATION): cv.invalid(
"The 'fragmentation' option has been moved to the 'debug' sensor component"
),
cv.Optional(CONF_LOOP_TIME): cv.invalid(
"The 'loop_time' option has been moved to the 'debug' sensor component"
),
}
).extend(cv.COMPONENT_SCHEMA)
).extend(cv.polling_component_schema("60s"))
async def to_code(config):

View File

@ -1,21 +1,23 @@
#include "debug_component.h"
#include <algorithm>
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#include "esphome/core/defines.h"
#include "esphome/core/version.h"
#ifdef USE_ESP_IDF
#ifdef USE_ESP32
#include <esp_heap_caps.h>
#include <esp_system.h>
#endif
#ifdef USE_ESP32
#if ESP_IDF_VERSION_MAJOR >= 4
#include <esp32/rom/rtc.h>
#else
#include <rom/rtc.h>
#endif
#endif
#endif // USE_ESP32
#ifdef USE_ARDUINO
#include <Esp.h>
@ -26,19 +28,40 @@ namespace debug {
static const char *const TAG = "debug";
static uint32_t get_free_heap() {
#if defined(USE_ESP8266)
return ESP.getFreeHeap(); // NOLINT(readability-static-accessed-through-instance)
#elif defined(USE_ESP32)
return heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
#endif
}
void DebugComponent::dump_config() {
std::string device_info;
device_info.reserve(256);
#ifndef ESPHOME_LOG_HAS_DEBUG
ESP_LOGE(TAG, "Debug Component requires debug log level!");
this->status_set_error();
return;
#endif
ESP_LOGCONFIG(TAG, "Debug component:");
#ifdef USE_TEXT_SENSOR
LOG_TEXT_SENSOR(" ", "Device info", this->device_info_);
#endif // USE_TEXT_SENSOR
#ifdef USE_SENSOR
LOG_SENSOR(" ", "Free space on heap", this->free_sensor_);
LOG_SENSOR(" ", "Largest free heap block", this->block_sensor_);
#if defined(USE_ESP8266) && ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2)
LOG_SENSOR(" ", "Heap fragmentation", this->fragmentation_sensor_);
#endif // defined(USE_ESP8266) && ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2)
#endif // USE_SENSOR
ESP_LOGD(TAG, "ESPHome version %s", ESPHOME_VERSION);
#ifdef USE_ARDUINO
this->free_heap_ = ESP.getFreeHeap(); // NOLINT(readability-static-accessed-through-instance)
#elif defined(USE_ESP_IDF)
this->free_heap_ = heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
#endif
device_info += ESPHOME_VERSION;
this->free_heap_ = get_free_heap();
ESP_LOGD(TAG, "Free Heap Size: %u bytes", this->free_heap_);
#ifdef USE_ARDUINO
@ -67,9 +90,12 @@ void DebugComponent::dump_config() {
default:
flash_mode = "UNKNOWN";
}
// NOLINTNEXTLINE(readability-static-accessed-through-instance)
ESP_LOGD(TAG, "Flash Chip: Size=%ukB Speed=%uMHz Mode=%s", ESP.getFlashChipSize() / 1024,
ESP.getFlashChipSpeed() / 1000000, flash_mode);
ESP_LOGD(TAG, "Flash Chip: Size=%ukB Speed=%uMHz Mode=%s",
ESP.getFlashChipSize() / 1024, // NOLINT
ESP.getFlashChipSpeed() / 1000000, flash_mode); // NOLINT
device_info += "|Flash: " + to_string(ESP.getFlashChipSize() / 1024) + // NOLINT
"kB Speed:" + to_string(ESP.getFlashChipSpeed() / 1000000) + "MHz Mode:"; // NOLINT
device_info += flash_mode;
#endif // USE_ARDUINO
#ifdef USE_ESP32
@ -104,10 +130,21 @@ void DebugComponent::dump_config() {
features += "Other:" + format_hex(info.features);
ESP_LOGD(TAG, "Chip: Model=%s, Features=%s Cores=%u, Revision=%u", model, features.c_str(), info.cores,
info.revision);
device_info += "|Chip: ";
device_info += model;
device_info += " Features:";
device_info += features;
device_info += " Cores:" + to_string(info.cores);
device_info += " Revision:" + to_string(info.revision);
ESP_LOGD(TAG, "ESP-IDF Version: %s", esp_get_idf_version());
device_info += "|ESP-IDF: ";
device_info += esp_get_idf_version();
ESP_LOGD(TAG, "EFuse MAC: %s", get_mac_address_pretty().c_str());
std::string mac = get_mac_address_pretty();
ESP_LOGD(TAG, "EFuse MAC: %s", mac.c_str());
device_info += "|EFuse MAC: ";
device_info += mac;
const char *reset_reason;
switch (rtc_get_reset_reason(0)) {
@ -160,6 +197,8 @@ void DebugComponent::dump_config() {
reset_reason = "Unknown Reset Reason";
}
ESP_LOGD(TAG, "Reset Reason: %s", reset_reason);
device_info += "|Reset: ";
device_info += reset_reason;
const char *wakeup_reason;
switch (rtc_get_wakeup_cause()) {
@ -203,6 +242,8 @@ void DebugComponent::dump_config() {
wakeup_reason = "Unknown";
}
ESP_LOGD(TAG, "Wakeup Reason: %s", wakeup_reason);
device_info += "|Wakeup: ";
device_info += wakeup_reason;
#endif
#if defined(USE_ESP8266) && !defined(CLANG_TIDY)
@ -214,20 +255,81 @@ void DebugComponent::dump_config() {
ESP_LOGD(TAG, "Flash Chip ID=0x%08X", ESP.getFlashChipId());
ESP_LOGD(TAG, "Reset Reason: %s", ESP.getResetReason().c_str());
ESP_LOGD(TAG, "Reset Info: %s", ESP.getResetInfo().c_str());
device_info += "|Chip: 0x" + format_hex(ESP.getChipId());
device_info += "|SDK: ";
device_info += ESP.getSdkVersion();
device_info += "|Core: ";
device_info += ESP.getCoreVersion().c_str();
device_info += "|Boot: ";
device_info += to_string(ESP.getBootVersion());
device_info += "|Mode: " + to_string(ESP.getBootMode());
device_info += "|CPU: " + to_string(ESP.getCpuFreqMHz());
device_info += "|Flash: 0x" + format_hex(ESP.getFlashChipId());
device_info += "|Reset: ";
device_info += ESP.getResetReason().c_str();
device_info += "|";
device_info += ESP.getResetInfo().c_str();
#endif
#ifdef USE_TEXT_SENSOR
if (this->device_info_ != nullptr) {
if (device_info.length() > 255)
device_info.resize(255);
this->device_info_->publish_state(device_info);
}
#endif // USE_TEXT_SENSOR
}
void DebugComponent::loop() {
#ifdef USE_ARDUINO
uint32_t new_free_heap = ESP.getFreeHeap(); // NOLINT(readability-static-accessed-through-instance)
#elif defined(USE_ESP_IDF)
uint32_t new_free_heap = heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
#endif
// log when free heap space has halved
uint32_t new_free_heap = get_free_heap();
if (new_free_heap < this->free_heap_ / 2) {
this->free_heap_ = new_free_heap;
ESP_LOGD(TAG, "Free Heap Size: %u bytes", this->free_heap_);
this->status_momentary_warning("heap", 1000);
}
#ifdef USE_SENSOR
// calculate loop time - from last call to this one
if (this->loop_time_sensor_ != nullptr) {
uint32_t now = millis();
uint32_t loop_time = now - this->last_loop_timetag_;
this->max_loop_time_ = std::max(this->max_loop_time_, loop_time);
this->last_loop_timetag_ = now;
}
#endif // USE_SENSOR
}
void DebugComponent::update() {
#ifdef USE_SENSOR
if (this->free_sensor_ != nullptr) {
this->free_sensor_->publish_state(get_free_heap());
}
if (this->block_sensor_ != nullptr) {
#if defined(USE_ESP8266)
// NOLINTNEXTLINE(readability-static-accessed-through-instance)
this->block_sensor_->publish_state(ESP.getMaxFreeBlockSize());
#elif defined(USE_ESP32)
this->block_sensor_->publish_state(heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL));
#endif
}
#if defined(USE_ESP8266) && ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2)
if (this->fragmentation_sensor_ != nullptr) {
// NOLINTNEXTLINE(readability-static-accessed-through-instance)
this->fragmentation_sensor_->publish_state(ESP.getHeapFragmentation());
}
#endif
if (this->loop_time_sensor_ != nullptr) {
this->loop_time_sensor_->publish_state(this->max_loop_time_);
this->max_loop_time_ = 0;
}
#endif // USE_SENSOR
}
float DebugComponent::get_setup_priority() const { return setup_priority::LATE; }
} // namespace debug

View File

@ -1,18 +1,56 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/defines.h"
#include "esphome/core/macros.h"
#include "esphome/core/helpers.h"
#ifdef USE_SENSOR
#include "esphome/components/sensor/sensor.h"
#endif
#ifdef USE_TEXT_SENSOR
#include "esphome/components/text_sensor/text_sensor.h"
#endif
namespace esphome {
namespace debug {
class DebugComponent : public Component {
class DebugComponent : public PollingComponent {
public:
void loop() override;
void update() override;
float get_setup_priority() const override;
void dump_config() override;
#ifdef USE_TEXT_SENSOR
void set_device_info_sensor(text_sensor::TextSensor *device_info) { device_info_ = device_info; }
#endif // USE_TEXT_SENSOR
#ifdef USE_SENSOR
void set_free_sensor(sensor::Sensor *free_sensor) { free_sensor_ = free_sensor; }
void set_block_sensor(sensor::Sensor *block_sensor) { block_sensor_ = block_sensor; }
#if defined(USE_ESP8266) && ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2)
void set_fragmentation_sensor(sensor::Sensor *fragmentation_sensor) { fragmentation_sensor_ = fragmentation_sensor; }
#endif
void set_loop_time_sensor(sensor::Sensor *loop_time_sensor) { loop_time_sensor_ = loop_time_sensor; }
#endif // USE_SENSOR
protected:
uint32_t free_heap_{};
#ifdef USE_SENSOR
uint32_t last_loop_timetag_{0};
uint32_t max_loop_time_{0};
sensor::Sensor *free_sensor_{nullptr};
sensor::Sensor *block_sensor_{nullptr};
#if defined(USE_ESP8266) && ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2)
sensor::Sensor *fragmentation_sensor_{nullptr};
#endif
sensor::Sensor *loop_time_sensor_{nullptr};
#endif // USE_SENSOR
#ifdef USE_TEXT_SENSOR
text_sensor::TextSensor *device_info_{nullptr};
#endif // USE_TEXT_SENSOR
};
} // namespace debug

View File

@ -0,0 +1,49 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor
from esphome.const import (
CONF_FREE,
CONF_FRAGMENTATION,
CONF_BLOCK,
CONF_LOOP_TIME,
UNIT_MILLISECOND,
UNIT_PERCENT,
UNIT_BYTES,
ICON_COUNTER,
ICON_TIMER,
)
from . import CONF_DEBUG_ID, DebugComponent
DEPENDENCIES = ["debug"]
CONFIG_SCHEMA = {
cv.GenerateID(CONF_DEBUG_ID): cv.use_id(DebugComponent),
cv.Optional(CONF_FREE): sensor.sensor_schema(UNIT_BYTES, ICON_COUNTER, 0),
cv.Optional(CONF_BLOCK): sensor.sensor_schema(UNIT_BYTES, ICON_COUNTER, 0),
cv.Optional(CONF_FRAGMENTATION): cv.All(
cv.only_on_esp8266,
cv.require_framework_version(esp8266_arduino=cv.Version(2, 5, 2)),
sensor.sensor_schema(UNIT_PERCENT, ICON_COUNTER, 1),
),
cv.Optional(CONF_LOOP_TIME): sensor.sensor_schema(UNIT_MILLISECOND, ICON_TIMER, 0),
}
async def to_code(config):
debug_component = await cg.get_variable(config[CONF_DEBUG_ID])
if CONF_FREE in config:
sens = await sensor.new_sensor(config[CONF_FREE])
cg.add(debug_component.set_free_sensor(sens))
if CONF_BLOCK in config:
sens = await sensor.new_sensor(config[CONF_BLOCK])
cg.add(debug_component.set_block_sensor(sens))
if CONF_FRAGMENTATION in config:
sens = await sensor.new_sensor(config[CONF_FRAGMENTATION])
cg.add(debug_component.set_fragmentation_sensor(sens))
if CONF_LOOP_TIME in config:
sens = await sensor.new_sensor(config[CONF_LOOP_TIME])
cg.add(debug_component.set_loop_time_sensor(sens))

View File

@ -0,0 +1,24 @@
from esphome.components import text_sensor
import esphome.config_validation as cv
import esphome.codegen as cg
from esphome.const import CONF_DEVICE
from . import CONF_DEBUG_ID, DebugComponent
DEPENDENCIES = ["debug"]
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_DEBUG_ID): cv.use_id(DebugComponent),
cv.Optional(CONF_DEVICE): text_sensor.text_sensor_schema(),
}
)
async def to_code(config):
debug_component = await cg.get_variable(config[CONF_DEBUG_ID])
if CONF_DEVICE in config:
sens = await text_sensor.new_text_sensor(config[CONF_DEVICE])
cg.add(debug_component.set_device_info_sensor(sens))

View File

@ -37,12 +37,10 @@ from esphome.const import (
DEVICE_CLASS_TEMPERATURE,
ICON_BLUETOOTH,
ICON_BLUR,
ICON_EMPTY,
ICON_THERMOMETER,
STATE_CLASS_MEASUREMENT,
STATE_CLASS_TOTAL_INCREASING,
UNIT_CELSIUS,
UNIT_EMPTY,
UNIT_PERCENT,
UNIT_WATT_HOURS,
)
@ -67,7 +65,7 @@ DemoClimate = demo_ns.class_("DemoClimate", climate.Climate, cg.Component)
DemoClimateType = demo_ns.enum("DemoClimateType", is_class=True)
DemoCover = demo_ns.class_("DemoCover", cover.Cover, cg.Component)
DemoCoverType = demo_ns.enum("DemoCoverType", is_class=True)
DemoFan = demo_ns.class_("DemoFan", cg.Component)
DemoFan = demo_ns.class_("DemoFan", fan.Fan, cg.Component)
DemoFanType = demo_ns.enum("DemoFanType", is_class=True)
DemoLight = demo_ns.class_("DemoLight", light.LightOutput, cg.Component)
DemoLightType = demo_ns.enum("DemoLightType", is_class=True)
@ -339,7 +337,7 @@ CONFIG_SCHEMA = cv.Schema(
},
],
): [
sensor.sensor_schema(UNIT_EMPTY, ICON_EMPTY, 0)
sensor.sensor_schema(accuracy_decimals=0)
.extend(cv.polling_component_schema("60s"))
.extend(
{
@ -378,12 +376,8 @@ CONFIG_SCHEMA = cv.Schema(
},
],
): [
text_sensor.TEXT_SENSOR_SCHEMA.extend(
text_sensor.text_sensor_schema(klass=DemoTextSensor).extend(
cv.polling_component_schema("60s")
).extend(
{
cv.GenerateID(): cv.declare_id(DemoTextSensor),
}
)
],
}
@ -411,8 +405,7 @@ async def to_code(config):
for conf in config[CONF_FANS]:
var = cg.new_Pvariable(conf[CONF_OUTPUT_ID])
await cg.register_component(var, conf)
fan_ = await fan.create_fan_state(conf)
cg.add(var.set_fan(fan_))
await fan.register_fan(var, conf)
cg.add(var.set_type(conf[CONF_TYPE]))
for conf in config[CONF_LIGHTS]:
@ -444,6 +437,5 @@ async def to_code(config):
await switch.register_switch(var, conf)
for conf in config[CONF_TEXT_SENSORS]:
var = cg.new_Pvariable(conf[CONF_ID])
var = await text_sensor.new_text_sensor(conf)
await cg.register_component(var, conf)
await text_sensor.register_text_sensor(var, conf)

View File

@ -1,7 +1,7 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/fan/fan_state.h"
#include "esphome/components/fan/fan.h"
namespace esphome {
namespace demo {
@ -13,11 +13,10 @@ enum class DemoFanType {
TYPE_4,
};
class DemoFan : public Component {
class DemoFan : public fan::Fan, public Component {
public:
void set_type(DemoFanType type) { type_ = type; }
void set_fan(fan::FanState *fan) { fan_ = fan; }
void setup() override {
fan::FanTraits get_traits() override {
fan::FanTraits traits{};
// oscillation
@ -43,10 +42,23 @@ class DemoFan : public Component {
break;
}
this->fan_->set_traits(traits);
return traits;
}
protected:
void control(const fan::FanCall &call) override {
if (call.get_state().has_value())
this->state = *call.get_state();
if (call.get_oscillating().has_value())
this->oscillating = *call.get_oscillating();
if (call.get_speed().has_value())
this->speed = *call.get_speed();
if (call.get_direction().has_value())
this->direction = *call.get_direction();
this->publish_state();
}
fan::FanState *fan_;
DemoFanType type_;
};

View File

@ -114,10 +114,11 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r
// Wait for rising edge
while (!this->pin_->digital_read()) {
if (micros() - start_time > 90) {
if (i < 0)
if (i < 0) {
error_code = 1;
else
} else {
error_code = 2;
}
break;
}
}
@ -130,10 +131,11 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r
// Wait for falling edge
while (this->pin_->digital_read()) {
if ((end_time = micros()) - start_time > 90) {
if (i < 0)
if (i < 0) {
error_code = 3;
else
} else {
error_code = 4;
}
break;
}
}

View File

@ -176,9 +176,10 @@ void DisplayBuffer::print(int x, int y, Font *font, Color color, TextAlign align
ESP_LOGW(TAG, "Encountered character without representation in font: '%c'", text[i]);
if (!font->get_glyphs().empty()) {
uint8_t glyph_width = font->get_glyphs()[0].glyph_data_->width;
for (int glyph_x = 0; glyph_x < glyph_width; glyph_x++)
for (int glyph_x = 0; glyph_x < glyph_width; glyph_x++) {
for (int glyph_y = 0; glyph_y < height; glyph_y++)
this->draw_pixel_at(glyph_x + x_at, glyph_y + y_start, color);
}
x_at += glyph_width;
}
@ -233,6 +234,14 @@ void DisplayBuffer::image(int x, int y, Image *image, Color color_on, Color colo
}
}
break;
case IMAGE_TYPE_TRANSPARENT_BINARY:
for (int img_x = 0; img_x < image->get_width(); img_x++) {
for (int img_y = 0; img_y < image->get_height(); img_y++) {
if (image->get_pixel(img_x, img_y))
this->draw_pixel_at(x + img_x, y + img_y, color_on);
}
}
break;
}
}
@ -243,6 +252,12 @@ void DisplayBuffer::legend(int x, int y, graph::Graph *graph, Color color_on) {
}
#endif // USE_GRAPH
#ifdef USE_QR_CODE
void DisplayBuffer::qr_code(int x, int y, qr_code::QrCode *qr_code, Color color_on, int scale) {
qr_code->draw(this, x, y, color_on, scale);
}
#endif // USE_QR_CODE
void DisplayBuffer::get_text_bounds(int x, int y, const char *text, Font *font, TextAlign align, int *x1, int *y1,
int *width, int *height) {
int x_offset, baseline;
@ -417,10 +432,11 @@ int Font::match_next_glyph(const char *str, int *match_length) {
int hi = this->glyphs_.size() - 1;
while (lo != hi) {
int mid = (lo + hi + 1) / 2;
if (this->glyphs_[mid].compare_to(str))
if (this->glyphs_[mid].compare_to(str)) {
lo = mid;
else
} else {
hi = mid - 1;
}
}
*match_length = this->glyphs_[lo].match_length(str);
if (*match_length <= 0)
@ -446,10 +462,11 @@ void Font::measure(const char *str, int *width, int *x_offset, int *baseline, in
}
const Glyph &glyph = this->glyphs_[glyph_n];
if (!has_char)
if (!has_char) {
min_x = glyph.glyph_data_->offset_x;
else
} else {
min_x = std::min(min_x, x + glyph.glyph_data_->offset_x);
}
x += glyph.glyph_data_->width + glyph.glyph_data_->offset_x;
i += match_length;

View File

@ -14,6 +14,10 @@
#include "esphome/components/graph/graph.h"
#endif
#ifdef USE_QR_CODE
#include "esphome/components/qr_code/qr_code.h"
#endif
namespace esphome {
namespace display {
@ -73,7 +77,12 @@ extern const Color COLOR_OFF;
/// Turn the pixel ON.
extern const Color COLOR_ON;
enum ImageType { IMAGE_TYPE_BINARY = 0, IMAGE_TYPE_GRAYSCALE = 1, IMAGE_TYPE_RGB24 = 2 };
enum ImageType {
IMAGE_TYPE_BINARY = 0,
IMAGE_TYPE_GRAYSCALE = 1,
IMAGE_TYPE_RGB24 = 2,
IMAGE_TYPE_TRANSPARENT_BINARY = 3,
};
enum DisplayRotation {
DISPLAY_ROTATION_0_DEGREES = 0,
@ -302,6 +311,17 @@ class DisplayBuffer {
void legend(int x, int y, graph::Graph *graph, Color color_on = COLOR_ON);
#endif // USE_GRAPH
#ifdef USE_QR_CODE
/** Draw the `qr_code` with the top-left corner at [x,y] to the screen.
*
* @param x The x coordinate of the upper left corner.
* @param y The y coordinate of the upper left corner.
* @param qr_code The qr_code to draw
* @param color_on The color to replace in binary images for the on bits.
*/
void qr_code(int x, int y, qr_code::QrCode *qr_code, Color color_on = COLOR_ON, int scale = 1);
#endif
/** Get the text bounds of the given string.
*
* @param x The x coordinate to place the string at, can be 0 if only interested in dimensions.
@ -336,15 +356,15 @@ class DisplayBuffer {
// Internal method to set display auto clearing.
void set_auto_clear(bool auto_clear_enabled) { this->auto_clear_enabled_ = auto_clear_enabled; }
virtual int get_height_internal() = 0;
virtual int get_width_internal() = 0;
DisplayRotation get_rotation() const { return this->rotation_; }
protected:
void vprintf_(int x, int y, Font *font, Color color, TextAlign align, const char *format, va_list arg);
virtual void draw_absolute_pixel_internal(int x, int y, Color color) = 0;
virtual int get_height_internal() = 0;
virtual int get_width_internal() = 0;
void init_internal_(uint32_t buffer_length);
void do_update_();

View File

@ -1,9 +1,7 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import text_sensor
from esphome.const import (
CONF_ID,
)
from . import Dsmr, CONF_DSMR_ID
AUTO_LOAD = ["dsmr"]
@ -11,71 +9,19 @@ AUTO_LOAD = ["dsmr"]
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_DSMR_ID): cv.use_id(Dsmr),
cv.Optional("identification"): text_sensor.TEXT_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
}
),
cv.Optional("p1_version"): text_sensor.TEXT_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
}
),
cv.Optional("p1_version_be"): text_sensor.TEXT_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
}
),
cv.Optional("timestamp"): text_sensor.TEXT_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
}
),
cv.Optional("electricity_tariff"): text_sensor.TEXT_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
}
),
cv.Optional("electricity_failure_log"): text_sensor.TEXT_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
}
),
cv.Optional("message_short"): text_sensor.TEXT_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
}
),
cv.Optional("message_long"): text_sensor.TEXT_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
}
),
cv.Optional("gas_equipment_id"): text_sensor.TEXT_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
}
),
cv.Optional("thermal_equipment_id"): text_sensor.TEXT_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
}
),
cv.Optional("water_equipment_id"): text_sensor.TEXT_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
}
),
cv.Optional("sub_equipment_id"): text_sensor.TEXT_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
}
),
cv.Optional("gas_delivered_text"): text_sensor.TEXT_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
}
),
cv.Optional("identification"): text_sensor.text_sensor_schema(),
cv.Optional("p1_version"): text_sensor.text_sensor_schema(),
cv.Optional("p1_version_be"): text_sensor.text_sensor_schema(),
cv.Optional("timestamp"): text_sensor.text_sensor_schema(),
cv.Optional("electricity_tariff"): text_sensor.text_sensor_schema(),
cv.Optional("electricity_failure_log"): text_sensor.text_sensor_schema(),
cv.Optional("message_short"): text_sensor.text_sensor_schema(),
cv.Optional("message_long"): text_sensor.text_sensor_schema(),
cv.Optional("gas_equipment_id"): text_sensor.text_sensor_schema(),
cv.Optional("thermal_equipment_id"): text_sensor.text_sensor_schema(),
cv.Optional("water_equipment_id"): text_sensor.text_sensor_schema(),
cv.Optional("sub_equipment_id"): text_sensor.text_sensor_schema(),
cv.Optional("gas_delivered_text"): text_sensor.text_sensor_schema(),
}
).extend(cv.COMPONENT_SCHEMA)
@ -89,8 +35,7 @@ async def to_code(config):
continue
id = conf.get("id")
if id and id.type == text_sensor.TextSensor:
var = cg.new_Pvariable(conf[CONF_ID])
await text_sensor.register_text_sensor(var, conf)
var = await text_sensor.new_text_sensor(conf)
cg.add(getattr(hub, f"set_{key}")(var))
text_sensors.append(f"F({key})")

View File

@ -97,7 +97,7 @@ bool E131Component::process_(int universe, const E131Packet &packet) {
ESP_LOGV(TAG, "Received E1.31 packet for %d universe, with %d bytes", universe, packet.count);
for (auto light_effect : light_effects_) {
for (auto *light_effect : light_effects_) {
handled = light_effect->process_(universe, packet) || handled;
}

View File

@ -33,11 +33,9 @@ class E131Component : public esphome::Component {
void loop() override;
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
public:
void add_effect(E131AddressableLightEffect *light_effect);
void remove_effect(E131AddressableLightEffect *light_effect);
public:
void set_method(E131ListenMethod listen_method) { this->listen_method_ = listen_method; }
protected:
@ -47,7 +45,6 @@ class E131Component : public esphome::Component {
void join_(int universe);
void leave_(int universe);
protected:
E131ListenMethod listen_method_{E131_MULTICAST};
std::unique_ptr<UDP> udp_;
std::set<E131AddressableLightEffect *> light_effects_;

View File

@ -47,7 +47,7 @@ void E131AddressableLightEffect::apply(light::AddressableLight &it, const Color
}
bool E131AddressableLightEffect::process_(int universe, const E131Packet &packet) {
auto it = get_addressable_();
auto *it = get_addressable_();
// check if this is our universe and data are valid
if (universe < first_universe_ || universe > get_last_universe())
@ -57,7 +57,7 @@ bool E131AddressableLightEffect::process_(int universe, const E131Packet &packet
// limit amount of lights per universe and received
int output_end =
std::min(it->size(), std::min(output_offset + get_lights_per_universe(), output_offset + packet.count - 1));
auto input_data = packet.values + 1;
auto *input_data = packet.values + 1;
ESP_LOGV(TAG, "Applying data for '%s' on %d universe, for %d-%d.", get_name().c_str(), universe, output_offset,
output_end);

View File

@ -17,19 +17,16 @@ class E131AddressableLightEffect : public light::AddressableLightEffect {
public:
E131AddressableLightEffect(const std::string &name);
public:
void start() override;
void stop() override;
void apply(light::AddressableLight &it, const Color &current_color) override;
public:
int get_data_per_universe() const;
int get_lights_per_universe() const;
int get_first_universe() const;
int get_last_universe() const;
int get_universe_count() const;
public:
void set_first_universe(int universe) { this->first_universe_ = universe; }
void set_channels(E131LightChannels channels) { this->channels_ = channels; }
void set_e131(E131Component *e131) { this->e131_ = e131; }
@ -37,7 +34,6 @@ class E131AddressableLightEffect : public light::AddressableLightEffect {
protected:
bool process_(int universe, const E131Packet &packet);
protected:
int first_universe_{0};
int last_universe_{0};
E131LightChannels channels_{E131_RGB};

View File

@ -116,7 +116,7 @@ bool E131Component::packet_(const std::vector<uint8_t> &data, int &universe, E13
if (data.size() < E131_MIN_PACKET_SIZE)
return false;
auto sbuff = reinterpret_cast<const E131RawPacket *>(&data[0]);
auto *sbuff = reinterpret_cast<const E131RawPacket *>(&data[0]);
if (memcmp(sbuff->acn_id, ACN_ID, sizeof(sbuff->acn_id)) != 0)
return false;

View File

View File

@ -0,0 +1,166 @@
#include "ektf2232.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace ektf2232 {
static const char *const TAG = "ektf2232";
static const uint8_t SOFT_RESET_CMD[4] = {0x77, 0x77, 0x77, 0x77};
static const uint8_t HELLO[4] = {0x55, 0x55, 0x55, 0x55};
static const uint8_t GET_X_RES[4] = {0x53, 0x60, 0x00, 0x00};
static const uint8_t GET_Y_RES[4] = {0x53, 0x63, 0x00, 0x00};
static const uint8_t GET_POWER_STATE_CMD[4] = {0x53, 0x50, 0x00, 0x01};
void EKTF2232TouchscreenStore::gpio_intr(EKTF2232TouchscreenStore *store) { store->touch = true; }
void EKTF2232Touchscreen::setup() {
ESP_LOGCONFIG(TAG, "Setting up EKT2232 Touchscreen...");
this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
this->interrupt_pin_->setup();
this->store_.pin = this->interrupt_pin_->to_isr();
this->interrupt_pin_->attach_interrupt(EKTF2232TouchscreenStore::gpio_intr, &this->store_,
gpio::INTERRUPT_FALLING_EDGE);
this->rts_pin_->setup();
this->hard_reset_();
if (!this->soft_reset_()) {
ESP_LOGE(TAG, "Failed to soft reset EKT2232!");
this->interrupt_pin_->detach_interrupt();
this->mark_failed();
return;
}
// Get touch resolution
uint8_t received[4];
this->write(GET_X_RES, 4);
if (this->read(received, 4)) {
ESP_LOGE(TAG, "Failed to read X resolution!");
this->interrupt_pin_->detach_interrupt();
this->mark_failed();
return;
}
this->x_resolution_ = ((received[2])) | ((received[3] & 0xf0) << 4);
this->write(GET_Y_RES, 4);
if (this->read(received, 4)) {
ESP_LOGE(TAG, "Failed to read Y resolution!");
this->interrupt_pin_->detach_interrupt();
this->mark_failed();
return;
}
this->y_resolution_ = ((received[2])) | ((received[3] & 0xf0) << 4);
this->store_.touch = false;
this->set_power_state(true);
}
void EKTF2232Touchscreen::loop() {
if (!this->store_.touch)
return;
this->store_.touch = false;
uint8_t touch_count = 0;
std::vector<TouchPoint> touches;
uint8_t raw[8];
this->read(raw, 8);
for (int i = 0; i < 8; i++) {
if (raw[7] & (1 << i))
touch_count++;
}
if (touch_count == 0) {
for (auto *listener : this->touch_listeners_)
listener->release();
return;
}
touch_count = std::min<uint8_t>(touch_count, 2);
ESP_LOGV(TAG, "Touch count: %d", touch_count);
for (int i = 0; i < touch_count; i++) {
uint8_t *d = raw + 1 + (i * 3);
uint32_t raw_x = (d[0] & 0xF0) << 4 | d[1];
uint32_t raw_y = (d[0] & 0x0F) << 8 | d[2];
raw_x = raw_x * this->display_height_ - 1;
raw_y = raw_y * this->display_width_ - 1;
TouchPoint tp;
switch (this->rotation_) {
case ROTATE_0_DEGREES:
tp.y = raw_x / this->x_resolution_;
tp.x = this->display_width_ - 1 - (raw_y / this->y_resolution_);
break;
case ROTATE_90_DEGREES:
tp.x = raw_x / this->x_resolution_;
tp.y = raw_y / this->y_resolution_;
break;
case ROTATE_180_DEGREES:
tp.y = this->display_height_ - 1 - (raw_x / this->x_resolution_);
tp.x = raw_y / this->y_resolution_;
break;
case ROTATE_270_DEGREES:
tp.x = this->display_height_ - 1 - (raw_x / this->x_resolution_);
tp.y = this->display_width_ - 1 - (raw_y / this->y_resolution_);
break;
}
this->defer([this, tp]() { this->send_touch_(tp); });
}
}
void EKTF2232Touchscreen::set_power_state(bool enable) {
uint8_t data[] = {0x54, 0x50, 0x00, 0x01};
data[1] |= (enable << 3);
this->write(data, 4);
}
bool EKTF2232Touchscreen::get_power_state() {
uint8_t received[4];
this->write(GET_POWER_STATE_CMD, 4);
this->store_.touch = false;
this->read(received, 4);
return (received[1] >> 3) & 1;
}
void EKTF2232Touchscreen::hard_reset_() {
this->rts_pin_->digital_write(false);
delay(15);
this->rts_pin_->digital_write(true);
delay(15);
}
bool EKTF2232Touchscreen::soft_reset_() {
auto err = this->write(SOFT_RESET_CMD, 4);
if (err != i2c::ERROR_OK)
return false;
uint8_t received[4];
uint16_t timeout = 1000;
while (!this->store_.touch && timeout > 0) {
delay(1);
timeout--;
}
if (timeout > 0)
this->store_.touch = true;
this->read(received, 4);
this->store_.touch = false;
return !memcmp(received, HELLO, 4);
}
void EKTF2232Touchscreen::dump_config() {
ESP_LOGCONFIG(TAG, "EKT2232 Touchscreen:");
LOG_I2C_DEVICE(this);
LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_);
LOG_PIN(" RTS Pin: ", this->rts_pin_);
}
} // namespace ektf2232
} // namespace esphome

View File

@ -0,0 +1,45 @@
#pragma once
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/touchscreen/touchscreen.h"
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
namespace esphome {
namespace ektf2232 {
struct EKTF2232TouchscreenStore {
volatile bool touch;
ISRInternalGPIOPin pin;
static void gpio_intr(EKTF2232TouchscreenStore *store);
};
using namespace touchscreen;
class EKTF2232Touchscreen : public Touchscreen, public Component, public i2c::I2CDevice {
public:
void setup() override;
void loop() override;
void dump_config() override;
void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; }
void set_rts_pin(GPIOPin *pin) { this->rts_pin_ = pin; }
void set_power_state(bool enable);
bool get_power_state();
protected:
void hard_reset_();
bool soft_reset_();
InternalGPIOPin *interrupt_pin_;
GPIOPin *rts_pin_;
EKTF2232TouchscreenStore store_;
uint16_t x_resolution_;
uint16_t y_resolution_;
};
} // namespace ektf2232
} // namespace esphome

View File

@ -0,0 +1,48 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins
from esphome.components import i2c, touchscreen
from esphome.const import CONF_ID
CODEOWNERS = ["@jesserockz"]
DEPENDENCIES = ["i2c"]
ektf2232_ns = cg.esphome_ns.namespace("ektf2232")
EKTF2232Touchscreen = ektf2232_ns.class_(
"EKTF2232Touchscreen",
touchscreen.Touchscreen,
cg.Component,
i2c.I2CDevice,
)
CONF_EKTF2232_ID = "ektf2232_id"
CONF_INTERRUPT_PIN = "interrupt_pin"
CONF_RTS_PIN = "rts_pin"
CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(EKTF2232Touchscreen),
cv.Required(CONF_INTERRUPT_PIN): cv.All(
pins.internal_gpio_input_pin_schema
),
cv.Required(CONF_RTS_PIN): pins.gpio_output_pin_schema,
}
)
.extend(i2c.i2c_device_schema(0x15))
.extend(cv.COMPONENT_SCHEMA)
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
await touchscreen.register_touchscreen(var, config)
interrupt_pin = await cg.gpio_pin_expression(config[CONF_INTERRUPT_PIN])
cg.add(var.set_interrupt_pin(interrupt_pin))
rts_pin = await cg.gpio_pin_expression(config[CONF_RTS_PIN])
cg.add(var.set_rts_pin(rts_pin))

View File

@ -18,6 +18,7 @@ from esphome.const import (
KEY_FRAMEWORK_VERSION,
KEY_TARGET_FRAMEWORK,
KEY_TARGET_PLATFORM,
__version__,
)
from esphome.core import CORE, HexInt
import esphome.config_validation as cv
@ -106,7 +107,6 @@ def _format_framework_espidf_version(ver: cv.Version) -> str:
# The new version needs to be thoroughly validated before changing the
# recommended version as otherwise a bunch of devices could be bricked
# * For all constants below, update platformio.ini (in this repo)
# and platformio.ini/platformio-lint.ini in the esphome-docker-base repository
# The default/recommended arduino framework version
# - https://github.com/espressif/arduino-esp32/releases
@ -115,16 +115,16 @@ RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(1, 0, 6)
# The platformio/espressif32 version to use for arduino frameworks
# - https://github.com/platformio/platform-espressif32/releases
# - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32
ARDUINO_PLATFORM_VERSION = cv.Version(3, 3, 2)
ARDUINO_PLATFORM_VERSION = cv.Version(3, 5, 0)
# The default/recommended esp-idf framework version
# - https://github.com/espressif/esp-idf/releases
# - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf
RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(4, 3, 0)
RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(4, 3, 2)
# The platformio/espressif32 version to use for esp-idf frameworks
# - https://github.com/platformio/platform-espressif32/releases
# - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32
ESP_IDF_PLATFORM_VERSION = cv.Version(3, 3, 2)
ESP_IDF_PLATFORM_VERSION = cv.Version(3, 5, 0)
def _arduino_check_versions(value):
@ -165,8 +165,8 @@ def _arduino_check_versions(value):
def _esp_idf_check_versions(value):
value = value.copy()
lookups = {
"dev": (cv.Version(4, 3, 1), "https://github.com/espressif/esp-idf.git"),
"latest": (cv.Version(4, 3, 0), None),
"dev": (cv.Version(5, 0, 0), "https://github.com/espressif/esp-idf.git"),
"latest": (cv.Version(4, 3, 2), None),
"recommended": (RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION, None),
}
@ -293,6 +293,8 @@ async def to_code(config):
cg.add_platformio_option("lib_ldf_mode", "off")
framework_ver: cv.Version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION]
conf = config[CONF_FRAMEWORK]
cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION])
@ -335,6 +337,13 @@ async def to_code(config):
"CONFIG_ESP32_PHY_CALIBRATION_AND_DATA_STORAGE", False
)
cg.add_define(
"USE_ESP_IDF_VERSION_CODE",
cg.RawExpression(
f"VERSION_CODE({framework_ver.major}, {framework_ver.minor}, {framework_ver.patch})"
),
)
elif conf[CONF_TYPE] == FRAMEWORK_ARDUINO:
cg.add_platformio_option("framework", "arduino")
cg.add_build_flag("-DUSE_ARDUINO")
@ -346,6 +355,13 @@ async def to_code(config):
cg.add_platformio_option("board_build.partitions", "partitions.csv")
cg.add_define(
"USE_ARDUINO_VERSION_CODE",
cg.RawExpression(
f"VERSION_CODE({framework_ver.major}, {framework_ver.minor}, {framework_ver.patch})"
),
)
ARDUINO_PARTITIONS_CSV = """\
nvs, data, nvs, 0x009000, 0x005000,
@ -415,6 +431,14 @@ def copy_files():
CORE.relative_build_path("partitions.csv"),
IDF_PARTITIONS_CSV,
)
# IDF build scripts look for version string to put in the build.
# However, if the build path does not have an initialized git repo,
# and no version.txt file exists, the CMake script fails for some setups.
# Fix by manually pasting a version.txt file, containing the ESPHome version
write_file_if_changed(
CORE.relative_build_path("version.txt"),
__version__,
)
dir = os.path.dirname(__file__)
post_build_file = os.path.join(dir, "post_build.py.script")

View File

@ -1,13 +1,16 @@
# Source https://github.com/letscontrolit/ESPEasy/pull/3845#issuecomment-1005864664
import esptool
from SCons.Script import ARGUMENTS
# pylint: disable=E0602
Import("env") # noqa
def esp32_create_combined_bin(source, target, env):
print("Generating combined binary for serial flashing")
verbose = bool(int(ARGUMENTS.get("PIOVERBOSE", "0")))
if verbose:
print("Generating combined binary for serial flashing")
app_offset = 0x10000
new_file_name = env.subst("$BUILD_DIR/${PROGNAME}-factory.bin")
@ -24,18 +27,21 @@ def esp32_create_combined_bin(source, target, env):
"--flash_size",
flash_size,
]
print(" Offset | File")
if verbose:
print(" Offset | File")
for section in sections:
sect_adr, sect_file = section.split(" ", 1)
print(f" - {sect_adr} | {sect_file}")
if verbose:
print(f" - {sect_adr} | {sect_file}")
cmd += [sect_adr, sect_file]
print(f" - {hex(app_offset)} | {firmware_name}")
cmd += [hex(app_offset), firmware_name]
print()
print(f"Using esptool.py arguments: {' '.join(cmd)}")
print()
if verbose:
print(f" - {hex(app_offset)} | {firmware_name}")
print()
print(f"Using esptool.py arguments: {' '.join(cmd)}")
print()
esptool.main(cmd)

View File

@ -42,6 +42,11 @@ void BLEAdvertising::remove_service_uuid(ESPBTUUID uuid) {
this->advertising_uuids_.end());
}
void BLEAdvertising::set_manufacturer_data(uint8_t *data, uint16_t size) {
this->advertising_data_.p_manufacturer_data = data;
this->advertising_data_.manufacturer_len = size;
}
void BLEAdvertising::start() {
int num_services = this->advertising_uuids_.size();
if (num_services == 0) {

View File

@ -20,6 +20,7 @@ class BLEAdvertising {
void remove_service_uuid(ESPBTUUID uuid);
void set_scan_response(bool scan_response) { this->scan_response_ = scan_response; }
void set_min_preferred_interval(uint16_t interval) { this->advertising_data_.min_interval = interval; }
void set_manufacturer_data(uint8_t *data, uint16_t size);
void start();
void stop();

View File

@ -147,40 +147,46 @@ bool BLECharacteristic::is_failed() {
}
void BLECharacteristic::set_broadcast_property(bool value) {
if (value)
if (value) {
this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_BROADCAST);
else
} else {
this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_BROADCAST);
}
}
void BLECharacteristic::set_indicate_property(bool value) {
if (value)
if (value) {
this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_INDICATE);
else
} else {
this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_INDICATE);
}
}
void BLECharacteristic::set_notify_property(bool value) {
if (value)
if (value) {
this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_NOTIFY);
else
} else {
this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_NOTIFY);
}
}
void BLECharacteristic::set_read_property(bool value) {
if (value)
if (value) {
this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_READ);
else
} else {
this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_READ);
}
}
void BLECharacteristic::set_write_property(bool value) {
if (value)
if (value) {
this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_WRITE);
else
} else {
this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_WRITE);
}
}
void BLECharacteristic::set_write_no_response_property(bool value) {
if (value)
if (value) {
this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_WRITE_NR);
else
} else {
this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_WRITE_NR);
}
}
void BLECharacteristic::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,

View File

@ -18,9 +18,10 @@ BLEService::~BLEService() {
}
BLECharacteristic *BLEService::get_characteristic(ESPBTUUID uuid) {
for (auto *chr : this->characteristics_)
for (auto *chr : this->characteristics_) {
if (chr->get_uuid() == uuid)
return chr;
}
return nullptr;
}

View File

@ -58,11 +58,12 @@ void ESP32BLETracker::setup() {
void ESP32BLETracker::loop() {
BLEEvent *ble_event = this->ble_events_.pop();
while (ble_event != nullptr) {
if (ble_event->type_)
if (ble_event->type_) {
this->real_gattc_event_handler_(ble_event->event_.gattc.gattc_event, ble_event->event_.gattc.gattc_if,
&ble_event->event_.gattc.gattc_param);
else
} else {
this->real_gap_event_handler_(ble_event->event_.gap.gap_event, &ble_event->event_.gap.gap_param);
}
delete ble_event; // NOLINT(cppcoreguidelines-owning-memory)
ble_event = this->ble_events_.pop();
}
@ -89,11 +90,12 @@ void ESP32BLETracker::loop() {
device.parse_scan_rst(this->scan_result_buffer_[i]);
bool found = false;
for (auto *listener : this->listeners_)
for (auto *listener : this->listeners_) {
if (listener->parse_device(device))
found = true;
}
for (auto *client : this->clients_)
for (auto *client : this->clients_) {
if (client->parse_device(device)) {
found = true;
if (client->state() == ClientState::DISCOVERED) {
@ -103,6 +105,7 @@ void ESP32BLETracker::loop() {
}
}
}
}
if (!found) {
this->print_bt_device_info(device);

View File

@ -23,6 +23,7 @@ AUTO_LOAD = ["psram"]
esp32_camera_ns = cg.esphome_ns.namespace("esp32_camera")
ESP32Camera = esp32_camera_ns.class_("ESP32Camera", cg.PollingComponent, cg.EntityBase)
ESP32CameraFrameSize = esp32_camera_ns.enum("ESP32CameraFrameSize")
FRAME_SIZES = {
"160X120": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_160X120,
@ -46,30 +47,76 @@ FRAME_SIZES = {
"1600X1200": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_1600X1200,
"UXGA": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_1600X1200,
}
ESP32GainControlMode = esp32_camera_ns.enum("ESP32GainControlMode")
ENUM_GAIN_CONTROL_MODE = {
"MANUAL": ESP32GainControlMode.ESP32_GC_MODE_MANU,
"AUTO": ESP32GainControlMode.ESP32_GC_MODE_AUTO,
}
ESP32AgcGainCeiling = esp32_camera_ns.enum("ESP32AgcGainCeiling")
ENUM_GAIN_CEILING = {
"2X": ESP32AgcGainCeiling.ESP32_GAINCEILING_2X,
"4X": ESP32AgcGainCeiling.ESP32_GAINCEILING_4X,
"8X": ESP32AgcGainCeiling.ESP32_GAINCEILING_8X,
"16X": ESP32AgcGainCeiling.ESP32_GAINCEILING_16X,
"32X": ESP32AgcGainCeiling.ESP32_GAINCEILING_32X,
"64X": ESP32AgcGainCeiling.ESP32_GAINCEILING_64X,
"128X": ESP32AgcGainCeiling.ESP32_GAINCEILING_128X,
}
ESP32WhiteBalanceMode = esp32_camera_ns.enum("ESP32WhiteBalanceMode")
ENUM_WB_MODE = {
"AUTO": ESP32WhiteBalanceMode.ESP32_WB_MODE_AUTO,
"SUNNY": ESP32WhiteBalanceMode.ESP32_WB_MODE_SUNNY,
"CLOUDY": ESP32WhiteBalanceMode.ESP32_WB_MODE_CLOUDY,
"OFFICE": ESP32WhiteBalanceMode.ESP32_WB_MODE_OFFICE,
"HOME": ESP32WhiteBalanceMode.ESP32_WB_MODE_HOME,
}
ESP32SpecialEffect = esp32_camera_ns.enum("ESP32SpecialEffect")
ENUM_SPECIAL_EFFECT = {
"NONE": ESP32SpecialEffect.ESP32_SPECIAL_EFFECT_NONE,
"NEGATIVE": ESP32SpecialEffect.ESP32_SPECIAL_EFFECT_NEGATIVE,
"GRAYSCALE": ESP32SpecialEffect.ESP32_SPECIAL_EFFECT_GRAYSCALE,
"RED_TINT": ESP32SpecialEffect.ESP32_SPECIAL_EFFECT_RED_TINT,
"GREEN_TINT": ESP32SpecialEffect.ESP32_SPECIAL_EFFECT_GREEN_TINT,
"BLUE_TINT": ESP32SpecialEffect.ESP32_SPECIAL_EFFECT_BLUE_TINT,
"SEPIA": ESP32SpecialEffect.ESP32_SPECIAL_EFFECT_SEPIA,
}
# pin assignment
CONF_VSYNC_PIN = "vsync_pin"
CONF_HREF_PIN = "href_pin"
CONF_PIXEL_CLOCK_PIN = "pixel_clock_pin"
CONF_EXTERNAL_CLOCK = "external_clock"
CONF_I2C_PINS = "i2c_pins"
CONF_POWER_DOWN_PIN = "power_down_pin"
CONF_MAX_FRAMERATE = "max_framerate"
CONF_IDLE_FRAMERATE = "idle_framerate"
# image
CONF_JPEG_QUALITY = "jpeg_quality"
CONF_VERTICAL_FLIP = "vertical_flip"
CONF_HORIZONTAL_MIRROR = "horizontal_mirror"
CONF_SATURATION = "saturation"
CONF_SPECIAL_EFFECT = "special_effect"
# exposure
CONF_AEC_MODE = "aec_mode"
CONF_AEC2 = "aec2"
CONF_AE_LEVEL = "ae_level"
CONF_AEC_VALUE = "aec_value"
CONF_SATURATION = "saturation"
# gains
CONF_AGC_MODE = "agc_mode"
CONF_AGC_VALUE = "agc_value"
CONF_AGC_GAIN_CEILING = "agc_gain_ceiling"
# white balance
CONF_WB_MODE = "wb_mode"
# test pattern
CONF_TEST_PATTERN = "test_pattern"
# framerates
CONF_MAX_FRAMERATE = "max_framerate"
CONF_IDLE_FRAMERATE = "idle_framerate"
camera_range_param = cv.int_range(min=-2, max=2)
CONFIG_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(ESP32Camera),
# pin assignment
cv.Required(CONF_DATA_PINS): cv.All(
[pins.internal_gpio_input_pin_number], cv.Length(min=8, max=8)
),
@ -92,12 +139,7 @@ CONFIG_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(
),
cv.Optional(CONF_RESET_PIN): pins.internal_gpio_output_pin_number,
cv.Optional(CONF_POWER_DOWN_PIN): pins.internal_gpio_output_pin_number,
cv.Optional(CONF_MAX_FRAMERATE, default="10 fps"): cv.All(
cv.framerate, cv.Range(min=0, min_included=False, max=60)
),
cv.Optional(CONF_IDLE_FRAMERATE, default="0.1 fps"): cv.All(
cv.framerate, cv.Range(min=0, max=1)
),
# image
cv.Optional(CONF_RESOLUTION, default="640X480"): cv.enum(
FRAME_SIZES, upper=True
),
@ -107,29 +149,66 @@ CONFIG_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(
cv.Optional(CONF_SATURATION, default=0): camera_range_param,
cv.Optional(CONF_VERTICAL_FLIP, default=True): cv.boolean,
cv.Optional(CONF_HORIZONTAL_MIRROR, default=True): cv.boolean,
cv.Optional(CONF_SPECIAL_EFFECT, default="NONE"): cv.enum(
ENUM_SPECIAL_EFFECT, upper=True
),
# exposure
cv.Optional(CONF_AGC_MODE, default="AUTO"): cv.enum(
ENUM_GAIN_CONTROL_MODE, upper=True
),
cv.Optional(CONF_AEC2, default=False): cv.boolean,
cv.Optional(CONF_AE_LEVEL, default=0): camera_range_param,
cv.Optional(CONF_AEC_VALUE, default=300): cv.int_range(min=0, max=1200),
# gains
cv.Optional(CONF_AEC_MODE, default="AUTO"): cv.enum(
ENUM_GAIN_CONTROL_MODE, upper=True
),
cv.Optional(CONF_AGC_VALUE, default=0): cv.int_range(min=0, max=30),
cv.Optional(CONF_AGC_GAIN_CEILING, default="2X"): cv.enum(
ENUM_GAIN_CEILING, upper=True
),
# white balance
cv.Optional(CONF_WB_MODE, default="AUTO"): cv.enum(ENUM_WB_MODE, upper=True),
# test pattern
cv.Optional(CONF_TEST_PATTERN, default=False): cv.boolean,
# framerates
cv.Optional(CONF_MAX_FRAMERATE, default="10 fps"): cv.All(
cv.framerate, cv.Range(min=0, min_included=False, max=60)
),
cv.Optional(CONF_IDLE_FRAMERATE, default="0.1 fps"): cv.All(
cv.framerate, cv.Range(min=0, max=1)
),
}
).extend(cv.COMPONENT_SCHEMA)
SETTERS = {
# pin assignment
CONF_DATA_PINS: "set_data_pins",
CONF_VSYNC_PIN: "set_vsync_pin",
CONF_HREF_PIN: "set_href_pin",
CONF_PIXEL_CLOCK_PIN: "set_pixel_clock_pin",
CONF_RESET_PIN: "set_reset_pin",
CONF_POWER_DOWN_PIN: "set_power_down_pin",
# image
CONF_JPEG_QUALITY: "set_jpeg_quality",
CONF_VERTICAL_FLIP: "set_vertical_flip",
CONF_HORIZONTAL_MIRROR: "set_horizontal_mirror",
CONF_AEC2: "set_aec2",
CONF_AE_LEVEL: "set_ae_level",
CONF_AEC_VALUE: "set_aec_value",
CONF_CONTRAST: "set_contrast",
CONF_BRIGHTNESS: "set_brightness",
CONF_SATURATION: "set_saturation",
CONF_SPECIAL_EFFECT: "set_special_effect",
# exposure
CONF_AEC_MODE: "set_aec_mode",
CONF_AEC2: "set_aec2",
CONF_AE_LEVEL: "set_ae_level",
CONF_AEC_VALUE: "set_aec_value",
# gains
CONF_AGC_MODE: "set_agc_mode",
CONF_AGC_VALUE: "set_agc_value",
CONF_AGC_GAIN_CEILING: "set_agc_gain_ceiling",
# white balance
CONF_WB_MODE: "set_wb_mode",
# test pattern
CONF_TEST_PATTERN: "set_test_pattern",
}

View File

@ -11,10 +11,14 @@ namespace esp32_camera {
static const char *const TAG = "esp32_camera";
/* ---------------- public API (derivated) ---------------- */
void ESP32Camera::setup() {
global_esp32_camera = this;
/* initialize time to now */
this->last_update_ = millis();
/* initialize camera */
esp_err_t err = esp_camera_init(&this->config_);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_camera_init failed: %s", esp_err_to_name(err));
@ -23,16 +27,10 @@ void ESP32Camera::setup() {
return;
}
sensor_t *s = esp_camera_sensor_get();
s->set_vflip(s, this->vertical_flip_);
s->set_hmirror(s, this->horizontal_mirror_);
s->set_aec2(s, this->aec2_); // 0 = disable , 1 = enable
s->set_ae_level(s, this->ae_level_); // -2 to 2
s->set_aec_value(s, this->aec_value_); // 0 to 1200
s->set_contrast(s, this->contrast_);
s->set_brightness(s, this->brightness_);
s->set_saturation(s, this->saturation_);
s->set_colorbar(s, this->test_pattern_);
/* initialize camera parameters */
this->update_camera_parameters();
/* initialize RTOS */
this->framebuffer_get_queue_ = xQueueCreate(1, sizeof(camera_fb_t *));
this->framebuffer_return_queue_ = xQueueCreate(1, sizeof(camera_fb_t *));
xTaskCreatePinnedToCore(&ESP32Camera::framebuffer_task,
@ -44,6 +42,7 @@ void ESP32Camera::setup() {
1 // core
);
}
void ESP32Camera::dump_config() {
auto conf = this->config_;
ESP_LOGCONFIG(TAG, "ESP32 Camera:");
@ -106,17 +105,17 @@ void ESP32Camera::dump_config() {
ESP_LOGCONFIG(TAG, " Saturation: %d", st.saturation);
ESP_LOGCONFIG(TAG, " Vertical Flip: %s", ONOFF(st.vflip));
ESP_LOGCONFIG(TAG, " Horizontal Mirror: %s", ONOFF(st.hmirror));
// ESP_LOGCONFIG(TAG, " Special Effect: %u", st.special_effect);
// ESP_LOGCONFIG(TAG, " White Balance Mode: %u", st.wb_mode);
ESP_LOGCONFIG(TAG, " Special Effect: %u", st.special_effect);
ESP_LOGCONFIG(TAG, " White Balance Mode: %u", st.wb_mode);
// ESP_LOGCONFIG(TAG, " Auto White Balance: %u", st.awb);
// ESP_LOGCONFIG(TAG, " Auto White Balance Gain: %u", st.awb_gain);
// ESP_LOGCONFIG(TAG, " Auto Exposure Control: %u", st.aec);
ESP_LOGCONFIG(TAG, " Auto Exposure Control: %u", st.aec);
ESP_LOGCONFIG(TAG, " Auto Exposure Control 2: %u", st.aec2);
ESP_LOGCONFIG(TAG, " Auto Exposure Level: %d", st.ae_level);
ESP_LOGCONFIG(TAG, " Auto Exposure Value: %u", st.aec_value);
// ESP_LOGCONFIG(TAG, " AGC: %u", st.agc);
// ESP_LOGCONFIG(TAG, " AGC Gain: %u", st.agc_gain);
// ESP_LOGCONFIG(TAG, " Gain Ceiling: %u", st.gainceiling);
ESP_LOGCONFIG(TAG, " AGC: %u", st.agc);
ESP_LOGCONFIG(TAG, " AGC Gain: %u", st.agc_gain);
ESP_LOGCONFIG(TAG, " Gain Ceiling: %u", st.gainceiling);
// ESP_LOGCONFIG(TAG, " BPC: %u", st.bpc);
// ESP_LOGCONFIG(TAG, " WPC: %u", st.wpc);
// ESP_LOGCONFIG(TAG, " RAW_GMA: %u", st.raw_gma);
@ -124,6 +123,7 @@ void ESP32Camera::dump_config() {
// ESP_LOGCONFIG(TAG, " DCW: %u", st.dcw);
ESP_LOGCONFIG(TAG, " Test Pattern: %s", YESNO(st.colorbar));
}
void ESP32Camera::loop() {
// check if we can return the image
if (this->can_return_image_()) {
@ -170,15 +170,10 @@ void ESP32Camera::loop() {
this->last_update_ = now;
this->single_requesters_ = 0;
}
void ESP32Camera::framebuffer_task(void *pv) {
while (true) {
camera_fb_t *framebuffer = esp_camera_fb_get();
xQueueSend(global_esp32_camera->framebuffer_get_queue_, &framebuffer, portMAX_DELAY);
// return is no-op for config with 1 fb
xQueueReceive(global_esp32_camera->framebuffer_return_queue_, &framebuffer, portMAX_DELAY);
esp_camera_fb_return(framebuffer);
}
}
float ESP32Camera::get_setup_priority() const { return setup_priority::DATA; }
/* ---------------- constructors ---------------- */
ESP32Camera::ESP32Camera(const std::string &name) : EntityBase(name) {
this->config_.pin_pwdn = -1;
this->config_.pin_reset = -1;
@ -193,6 +188,9 @@ ESP32Camera::ESP32Camera(const std::string &name) : EntityBase(name) {
global_esp32_camera = this;
}
ESP32Camera::ESP32Camera() : ESP32Camera("") {}
/* ---------------- setters ---------------- */
/* set pin assignment */
void ESP32Camera::set_data_pins(std::array<uint8_t, 8> pins) {
this->config_.pin_d0 = pins[0];
this->config_.pin_d1 = pins[1];
@ -214,6 +212,10 @@ void ESP32Camera::set_i2c_pins(uint8_t sda, uint8_t scl) {
this->config_.pin_sscb_sda = sda;
this->config_.pin_sscb_scl = scl;
}
void ESP32Camera::set_reset_pin(uint8_t pin) { this->config_.pin_reset = pin; }
void ESP32Camera::set_power_down_pin(uint8_t pin) { this->config_.pin_pwdn = pin; }
/* set image parameters */
void ESP32Camera::set_frame_size(ESP32CameraFrameSize size) {
switch (size) {
case ESP32_CAMERA_SIZE_160X120:
@ -249,36 +251,81 @@ void ESP32Camera::set_frame_size(ESP32CameraFrameSize size) {
}
}
void ESP32Camera::set_jpeg_quality(uint8_t quality) { this->config_.jpeg_quality = quality; }
void ESP32Camera::set_reset_pin(uint8_t pin) { this->config_.pin_reset = pin; }
void ESP32Camera::set_power_down_pin(uint8_t pin) { this->config_.pin_pwdn = pin; }
void ESP32Camera::add_image_callback(std::function<void(std::shared_ptr<CameraImage>)> &&f) {
this->new_image_callback_.add(std::move(f));
}
void ESP32Camera::set_vertical_flip(bool vertical_flip) { this->vertical_flip_ = vertical_flip; }
void ESP32Camera::set_horizontal_mirror(bool horizontal_mirror) { this->horizontal_mirror_ = horizontal_mirror; }
void ESP32Camera::set_aec2(bool aec2) { this->aec2_ = aec2; }
void ESP32Camera::set_ae_level(int ae_level) { this->ae_level_ = ae_level; }
void ESP32Camera::set_aec_value(uint32_t aec_value) { this->aec_value_ = aec_value; }
void ESP32Camera::set_contrast(int contrast) { this->contrast_ = contrast; }
void ESP32Camera::set_brightness(int brightness) { this->brightness_ = brightness; }
void ESP32Camera::set_saturation(int saturation) { this->saturation_ = saturation; }
float ESP32Camera::get_setup_priority() const { return setup_priority::DATA; }
uint32_t ESP32Camera::hash_base() { return 3010542557UL; }
void ESP32Camera::request_image(CameraRequester requester) { this->single_requesters_ |= 1 << requester; }
void ESP32Camera::start_stream(CameraRequester requester) { this->stream_requesters_ |= 1 << requester; }
void ESP32Camera::stop_stream(CameraRequester requester) { this->stream_requesters_ &= ~(1 << requester); }
bool ESP32Camera::has_requested_image_() const { return this->single_requesters_ || this->stream_requesters_; }
bool ESP32Camera::can_return_image_() const { return this->current_image_.use_count() == 1; }
void ESP32Camera::set_special_effect(ESP32SpecialEffect effect) { this->special_effect_ = effect; }
/* set exposure parameters */
void ESP32Camera::set_aec_mode(ESP32GainControlMode mode) { this->aec_mode_ = mode; }
void ESP32Camera::set_aec2(bool aec2) { this->aec2_ = aec2; }
void ESP32Camera::set_ae_level(int ae_level) { this->ae_level_ = ae_level; }
void ESP32Camera::set_aec_value(uint32_t aec_value) { this->aec_value_ = aec_value; }
/* set gains parameters */
void ESP32Camera::set_agc_mode(ESP32GainControlMode mode) { this->agc_mode_ = mode; }
void ESP32Camera::set_agc_value(uint8_t agc_value) { this->agc_value_ = agc_value; }
void ESP32Camera::set_agc_gain_ceiling(ESP32AgcGainCeiling gain_ceiling) { this->agc_gain_ceiling_ = gain_ceiling; }
/* set white balance */
void ESP32Camera::set_wb_mode(ESP32WhiteBalanceMode mode) { this->wb_mode_ = mode; }
/* set test mode */
void ESP32Camera::set_test_pattern(bool test_pattern) { this->test_pattern_ = test_pattern; }
/* set fps */
void ESP32Camera::set_max_update_interval(uint32_t max_update_interval) {
this->max_update_interval_ = max_update_interval;
}
void ESP32Camera::set_idle_update_interval(uint32_t idle_update_interval) {
this->idle_update_interval_ = idle_update_interval;
}
void ESP32Camera::set_test_pattern(bool test_pattern) { this->test_pattern_ = test_pattern; }
/* ---------------- public API (specific) ---------------- */
void ESP32Camera::add_image_callback(std::function<void(std::shared_ptr<CameraImage>)> &&f) {
this->new_image_callback_.add(std::move(f));
}
void ESP32Camera::start_stream(CameraRequester requester) { this->stream_requesters_ |= (1U << requester); }
void ESP32Camera::stop_stream(CameraRequester requester) { this->stream_requesters_ &= ~(1U << requester); }
void ESP32Camera::request_image(CameraRequester requester) { this->single_requesters_ |= (1U << requester); }
void ESP32Camera::update_camera_parameters() {
sensor_t *s = esp_camera_sensor_get();
/* update image */
s->set_vflip(s, this->vertical_flip_);
s->set_hmirror(s, this->horizontal_mirror_);
s->set_contrast(s, this->contrast_);
s->set_brightness(s, this->brightness_);
s->set_saturation(s, this->saturation_);
s->set_special_effect(s, (int) this->special_effect_); // 0 to 6
/* update exposure */
s->set_exposure_ctrl(s, (bool) this->aec_mode_);
s->set_aec2(s, this->aec2_); // 0 = disable , 1 = enable
s->set_ae_level(s, this->ae_level_); // -2 to 2
s->set_aec_value(s, this->aec_value_); // 0 to 1200
/* update gains */
s->set_gain_ctrl(s, (bool) this->agc_mode_);
s->set_agc_gain(s, (int) this->agc_value_); // 0 to 30
s->set_gainceiling(s, (gainceiling_t) this->agc_gain_ceiling_);
/* update white balance mode */
s->set_wb_mode(s, (int) this->wb_mode_); // 0 to 4
/* update test patern */
s->set_colorbar(s, this->test_pattern_);
}
/* ---------------- Internal methods ---------------- */
uint32_t ESP32Camera::hash_base() { return 3010542557UL; }
bool ESP32Camera::has_requested_image_() const { return this->single_requesters_ || this->stream_requesters_; }
bool ESP32Camera::can_return_image_() const { return this->current_image_.use_count() == 1; }
void ESP32Camera::framebuffer_task(void *pv) {
while (true) {
camera_fb_t *framebuffer = esp_camera_fb_get();
xQueueSend(global_esp32_camera->framebuffer_get_queue_, &framebuffer, portMAX_DELAY);
// return is no-op for config with 1 fb
xQueueReceive(global_esp32_camera->framebuffer_return_queue_, &framebuffer, portMAX_DELAY);
esp_camera_fb_return(framebuffer);
}
}
ESP32Camera *global_esp32_camera; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
/* ---------------- CameraImageReader class ---------------- */
void CameraImageReader::set_image(std::shared_ptr<CameraImage> image) {
this->image_ = std::move(image);
this->offset_ = 0;
@ -293,13 +340,15 @@ void CameraImageReader::return_image() { this->image_.reset(); }
void CameraImageReader::consume_data(size_t consumed) { this->offset_ += consumed; }
uint8_t *CameraImageReader::peek_data_buffer() { return this->image_->get_data_buffer() + this->offset_; }
/* ---------------- CameraImage class ---------------- */
CameraImage::CameraImage(camera_fb_t *buffer, uint8_t requesters) : buffer_(buffer), requesters_(requesters) {}
camera_fb_t *CameraImage::get_raw_buffer() { return this->buffer_; }
uint8_t *CameraImage::get_data_buffer() { return this->buffer_->buf; }
size_t CameraImage::get_data_length() { return this->buffer_->len; }
bool CameraImage::was_requested_by(CameraRequester requester) const {
return (this->requesters_ & (1 << requester)) != 0;
}
CameraImage::CameraImage(camera_fb_t *buffer, uint8_t requesters) : buffer_(buffer), requesters_(requesters) {}
} // namespace esp32_camera
} // namespace esphome

View File

@ -14,34 +14,9 @@ namespace esp32_camera {
class ESP32Camera;
/* ---------------- enum classes ---------------- */
enum CameraRequester { IDLE, API_REQUESTER, WEB_REQUESTER };
class CameraImage {
public:
CameraImage(camera_fb_t *buffer, uint8_t requester);
camera_fb_t *get_raw_buffer();
uint8_t *get_data_buffer();
size_t get_data_length();
bool was_requested_by(CameraRequester requester) const;
protected:
camera_fb_t *buffer_;
uint8_t requesters_;
};
class CameraImageReader {
public:
void set_image(std::shared_ptr<CameraImage> image);
size_t available() const;
uint8_t *peek_data_buffer();
void consume_data(size_t consumed);
void return_image();
protected:
std::shared_ptr<CameraImage> image_;
size_t offset_{0};
};
enum ESP32CameraFrameSize {
ESP32_CAMERA_SIZE_160X120, // QQVGA
ESP32_CAMERA_SIZE_176X144, // QCIF
@ -55,57 +30,155 @@ enum ESP32CameraFrameSize {
ESP32_CAMERA_SIZE_1600X1200, // UXGA
};
enum ESP32AgcGainCeiling {
ESP32_GAINCEILING_2X = GAINCEILING_2X,
ESP32_GAINCEILING_4X = GAINCEILING_4X,
ESP32_GAINCEILING_8X = GAINCEILING_8X,
ESP32_GAINCEILING_16X = GAINCEILING_16X,
ESP32_GAINCEILING_32X = GAINCEILING_32X,
ESP32_GAINCEILING_64X = GAINCEILING_64X,
ESP32_GAINCEILING_128X = GAINCEILING_128X,
};
enum ESP32GainControlMode {
ESP32_GC_MODE_MANU = false,
ESP32_GC_MODE_AUTO = true,
};
enum ESP32WhiteBalanceMode {
ESP32_WB_MODE_AUTO = 0U,
ESP32_WB_MODE_SUNNY = 1U,
ESP32_WB_MODE_CLOUDY = 2U,
ESP32_WB_MODE_OFFICE = 3U,
ESP32_WB_MODE_HOME = 4U,
};
enum ESP32SpecialEffect {
ESP32_SPECIAL_EFFECT_NONE = 0U,
ESP32_SPECIAL_EFFECT_NEGATIVE = 1U,
ESP32_SPECIAL_EFFECT_GRAYSCALE = 2U,
ESP32_SPECIAL_EFFECT_RED_TINT = 3U,
ESP32_SPECIAL_EFFECT_GREEN_TINT = 4U,
ESP32_SPECIAL_EFFECT_BLUE_TINT = 5U,
ESP32_SPECIAL_EFFECT_SEPIA = 6U,
};
/* ---------------- CameraImage class ---------------- */
class CameraImage {
public:
CameraImage(camera_fb_t *buffer, uint8_t requester);
camera_fb_t *get_raw_buffer();
uint8_t *get_data_buffer();
size_t get_data_length();
bool was_requested_by(CameraRequester requester) const;
protected:
camera_fb_t *buffer_;
uint8_t requesters_;
};
/* ---------------- CameraImageReader class ---------------- */
class CameraImageReader {
public:
void set_image(std::shared_ptr<CameraImage> image);
size_t available() const;
uint8_t *peek_data_buffer();
void consume_data(size_t consumed);
void return_image();
protected:
std::shared_ptr<CameraImage> image_;
size_t offset_{0};
};
/* ---------------- ESP32Camera class ---------------- */
class ESP32Camera : public Component, public EntityBase {
public:
ESP32Camera(const std::string &name);
ESP32Camera();
/* setters */
/* -- pin assignment */
void set_data_pins(std::array<uint8_t, 8> pins);
void set_vsync_pin(uint8_t pin);
void set_href_pin(uint8_t pin);
void set_pixel_clock_pin(uint8_t pin);
void set_external_clock(uint8_t pin, uint32_t frequency);
void set_i2c_pins(uint8_t sda, uint8_t scl);
void set_frame_size(ESP32CameraFrameSize size);
void set_jpeg_quality(uint8_t quality);
void set_reset_pin(uint8_t pin);
void set_power_down_pin(uint8_t pin);
/* -- image */
void set_frame_size(ESP32CameraFrameSize size);
void set_jpeg_quality(uint8_t quality);
void set_vertical_flip(bool vertical_flip);
void set_horizontal_mirror(bool horizontal_mirror);
void set_aec2(bool aec2);
void set_ae_level(int ae_level);
void set_aec_value(uint32_t aec_value);
void set_contrast(int contrast);
void set_brightness(int brightness);
void set_saturation(int saturation);
void set_special_effect(ESP32SpecialEffect effect);
/* -- exposure */
void set_aec_mode(ESP32GainControlMode mode);
void set_aec2(bool aec2);
void set_ae_level(int ae_level);
void set_aec_value(uint32_t aec_value);
/* -- gains */
void set_agc_mode(ESP32GainControlMode mode);
void set_agc_value(uint8_t agc_value);
void set_agc_gain_ceiling(ESP32AgcGainCeiling gain_ceiling);
/* -- white balance */
void set_wb_mode(ESP32WhiteBalanceMode mode);
/* -- test */
void set_test_pattern(bool test_pattern);
/* -- framerates */
void set_max_update_interval(uint32_t max_update_interval);
void set_idle_update_interval(uint32_t idle_update_interval);
void set_test_pattern(bool test_pattern);
/* public API (derivated) */
void setup() override;
void loop() override;
void dump_config() override;
void add_image_callback(std::function<void(std::shared_ptr<CameraImage>)> &&f);
float get_setup_priority() const override;
/* public API (specific) */
void add_image_callback(std::function<void(std::shared_ptr<CameraImage>)> &&f);
void start_stream(CameraRequester requester);
void stop_stream(CameraRequester requester);
void request_image(CameraRequester requester);
void update_camera_parameters();
protected:
/* internal methods */
uint32_t hash_base() override;
bool has_requested_image_() const;
bool can_return_image_() const;
static void framebuffer_task(void *pv);
/* attributes */
/* camera configuration */
camera_config_t config_{};
/* -- image */
bool vertical_flip_{true};
bool horizontal_mirror_{true};
bool aec2_{false};
int ae_level_{0};
uint32_t aec_value_{300};
int contrast_{0};
int brightness_{0};
int saturation_{0};
ESP32SpecialEffect special_effect_{ESP32_SPECIAL_EFFECT_NONE};
/* -- exposure */
ESP32GainControlMode aec_mode_{ESP32_GC_MODE_AUTO};
bool aec2_{false};
int ae_level_{0};
uint32_t aec_value_{300};
/* -- gains */
ESP32GainControlMode agc_mode_{ESP32_GC_MODE_AUTO};
uint8_t agc_value_{0};
ESP32AgcGainCeiling agc_gain_ceiling_{ESP32_GAINCEILING_2X};
/* -- white balance */
ESP32WhiteBalanceMode wb_mode_{ESP32_WB_MODE_AUTO};
/* -- Test */
bool test_pattern_{false};
/* -- framerates */
uint32_t max_update_interval_{1000};
uint32_t idle_update_interval_{15000};
esp_err_t init_error_{ESP_OK};
std::shared_ptr<CameraImage> current_image_;
@ -114,8 +187,7 @@ class ESP32Camera : public Component, public EntityBase {
QueueHandle_t framebuffer_get_queue_;
QueueHandle_t framebuffer_return_queue_;
CallbackManager<void(std::shared_ptr<CameraImage>)> new_image_callback_;
uint32_t max_update_interval_{1000};
uint32_t idle_update_interval_{15000};
uint32_t last_idle_request_{0};
uint32_t last_update_{0};
};

View File

@ -87,10 +87,11 @@ void CameraWebServer::on_shutdown() {
void CameraWebServer::dump_config() {
ESP_LOGCONFIG(TAG, "ESP32 Camera Web Server:");
ESP_LOGCONFIG(TAG, " Port: %d", this->port_);
if (this->mode_ == STREAM)
if (this->mode_ == STREAM) {
ESP_LOGCONFIG(TAG, " Mode: stream");
else
} else {
ESP_LOGCONFIG(TAG, " Mode: snapshot");
}
if (this->is_failed()) {
ESP_LOGE(TAG, " Setup Failed");

View File

@ -36,7 +36,6 @@ class CameraWebServer : public Component {
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_;

View File

@ -56,7 +56,7 @@ async def to_code(config):
cg.add(ble_server.register_service_component(var))
cg.add_define("USE_IMPROV")
cg.add_library("esphome/Improv", "1.0.0")
cg.add_library("esphome/Improv", "1.2.1")
cg.add(var.set_identify_duration(config[CONF_IDENTIFY_DURATION]))
cg.add(var.set_authorized_duration(config[CONF_AUTHORIZED_DURATION]))

View File

@ -128,7 +128,7 @@ void ESP32ImprovComponent::loop() {
std::vector<std::string> urls = {ESPHOME_MY_LINK};
#ifdef USE_WEBSERVER
auto ip = wifi::global_wifi_component->wifi_sta_ip();
std::string webserver_url = "http://" + ip.str() + ":" + to_string(WEBSERVER_PORT);
std::string webserver_url = "http://" + ip.str() + ":" + to_string(USE_WEBSERVER_PORT);
urls.push_back(webserver_url);
#endif
std::vector<uint8_t> data = improv::build_rpc_response(improv::WIFI_SETTINGS, urls);

View File

@ -162,7 +162,7 @@ void ESP32TouchComponent::on_shutdown() {
}
ESP32TouchBinarySensor::ESP32TouchBinarySensor(touch_pad_t touch_pad, uint16_t threshold, uint16_t wakeup_threshold)
: BinarySensor(), touch_pad_(touch_pad), threshold_(threshold), wakeup_threshold_(wakeup_threshold) {}
: touch_pad_(touch_pad), threshold_(threshold), wakeup_threshold_(wakeup_threshold) {}
} // namespace esp32_touch
} // namespace esphome

View File

@ -61,7 +61,7 @@ def _format_framework_arduino_version(ver: cv.Version) -> str:
# The default/recommended arduino framework version
# - https://github.com/esp8266/Arduino/releases
# - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-arduinoespressif8266
RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(2, 7, 4)
RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(3, 0, 2)
# The platformio/espressif8266 version to use for arduino 2 framework versions
# - https://github.com/platformio/platform-espressif8266/releases
# - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif8266
@ -166,6 +166,7 @@ async def to_code(config):
cg.add_platformio_option("framework", "arduino")
cg.add_build_flag("-DUSE_ARDUINO")
cg.add_build_flag("-DUSE_ESP8266_FRAMEWORK_ARDUINO")
cg.add_build_flag("-Wno-nonnull-compare")
cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION])
cg.add_platformio_option(
"platform_packages",
@ -198,10 +199,15 @@ async def to_code(config):
cg.add_platformio_option("board_build.flash_mode", config[CONF_BOARD_FLASH_MODE])
ver: cv.Version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION]
cg.add_define(
"USE_ARDUINO_VERSION_CODE",
cg.RawExpression(f"VERSION_CODE({ver.major}, {ver.minor}, {ver.patch})"),
)
if config[CONF_BOARD] in ESP8266_FLASH_SIZES:
flash_size = ESP8266_FLASH_SIZES[config[CONF_BOARD]]
ld_scripts = ESP8266_LD_SCRIPTS[flash_size]
ver = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION]
if ver <= cv.Version(2, 3, 0):
# No ld script support

View File

@ -1,4 +1,4 @@
FLASH_SIZE_1_MB = 2 ** 20
FLASH_SIZE_1_MB = 2**20
FLASH_SIZE_512_KB = FLASH_SIZE_1_MB // 2
FLASH_SIZE_2_MB = 2 * FLASH_SIZE_1_MB
FLASH_SIZE_4_MB = 4 * FLASH_SIZE_1_MB

View File

@ -98,16 +98,18 @@ static bool load_from_flash(size_t offset, uint32_t *data, size_t len) {
}
static bool save_to_rtc(size_t offset, const uint32_t *data, size_t len) {
for (uint32_t i = 0; i < len; i++)
for (uint32_t i = 0; i < len; i++) {
if (!esp_rtc_user_mem_write(offset + i, data[i]))
return false;
}
return true;
}
static bool load_from_rtc(size_t offset, uint32_t *data, size_t len) {
for (uint32_t i = 0; i < len; i++)
for (uint32_t i = 0; i < len; i++) {
if (!esp_rtc_user_mem_read(offset + i, &data[i]))
return false;
}
return true;
}

View File

@ -2,13 +2,10 @@
#include "esp8266_pwm.h"
#include "esphome/core/macros.h"
#include "esphome/core/defines.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
#if defined(USE_ESP8266) && ARDUINO_VERSION_CODE < VERSION_CODE(2, 4, 0)
#error ESP8266 PWM requires at least arduino_version 2.4.0
#endif
#include <core_esp8266_waveform.h>
namespace esphome {

View File

@ -23,15 +23,20 @@ ESP8266PWM = esp8266_pwm_ns.class_("ESP8266PWM", output.FloatOutput, cg.Componen
SetFrequencyAction = esp8266_pwm_ns.class_("SetFrequencyAction", automation.Action)
validate_frequency = cv.All(cv.frequency, cv.Range(min=1.0e-6))
CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend(
{
cv.Required(CONF_ID): cv.declare_id(ESP8266PWM),
cv.Required(CONF_PIN): cv.All(
pins.internal_gpio_output_pin_schema, valid_pwm_pin
),
cv.Optional(CONF_FREQUENCY, default="1kHz"): validate_frequency,
}
).extend(cv.COMPONENT_SCHEMA)
CONFIG_SCHEMA = cv.All(
output.FLOAT_OUTPUT_SCHEMA.extend(
{
cv.Required(CONF_ID): cv.declare_id(ESP8266PWM),
cv.Required(CONF_PIN): cv.All(
pins.internal_gpio_output_pin_schema, valid_pwm_pin
),
cv.Optional(CONF_FREQUENCY, default="1kHz"): validate_frequency,
}
).extend(cv.COMPONENT_SCHEMA),
cv.require_framework_version(
esp8266_arduino=cv.Version(2, 4, 0),
),
)
async def to_code(config):

View File

@ -75,9 +75,10 @@ void EZOSensor::loop() {
return;
// some sensors return multiple comma-separated values, terminate string after first one
for (size_t i = 1; i < sizeof(buf) - 1; i++)
for (size_t i = 1; i < sizeof(buf) - 1; i++) {
if (buf[i] == ',')
buf[i] = '\0';
}
float val = parse_number<float>((char *) &buf[1]).value_or(0);
this->publish_state(val);

View File

@ -19,6 +19,7 @@ from esphome.const import (
CONF_ON_TURN_ON,
CONF_TRIGGER_ID,
CONF_DIRECTION,
CONF_RESTORE_MODE,
)
from esphome.core import CORE, coroutine_with_priority
from esphome.cpp_helpers import setup_entity
@ -26,13 +27,24 @@ from esphome.cpp_helpers import setup_entity
IS_PLATFORM_COMPONENT = True
fan_ns = cg.esphome_ns.namespace("fan")
FanState = fan_ns.class_("FanState", cg.EntityBase, cg.Component)
MakeFan = cg.Application.struct("MakeFan")
Fan = fan_ns.class_("Fan", cg.EntityBase)
FanState = fan_ns.class_("Fan", Fan, cg.Component)
FanDirection = fan_ns.enum("FanDirection")
FanDirection = fan_ns.enum("FanDirection", is_class=True)
FAN_DIRECTION_ENUM = {
"FORWARD": FanDirection.FAN_DIRECTION_FORWARD,
"REVERSE": FanDirection.FAN_DIRECTION_REVERSE,
"FORWARD": FanDirection.FORWARD,
"REVERSE": FanDirection.REVERSE,
}
FanRestoreMode = fan_ns.enum("FanRestoreMode", is_class=True)
RESTORE_MODES = {
"NO_RESTORE": FanRestoreMode.NO_RESTORE,
"ALWAYS_OFF": FanRestoreMode.ALWAYS_OFF,
"ALWAYS_ON": FanRestoreMode.ALWAYS_ON,
"RESTORE_DEFAULT_OFF": FanRestoreMode.RESTORE_DEFAULT_OFF,
"RESTORE_DEFAULT_ON": FanRestoreMode.RESTORE_DEFAULT_ON,
"RESTORE_INVERTED_DEFAULT_OFF": FanRestoreMode.RESTORE_INVERTED_DEFAULT_OFF,
"RESTORE_INVERTED_DEFAULT_ON": FanRestoreMode.RESTORE_INVERTED_DEFAULT_ON,
}
# Actions
@ -50,7 +62,10 @@ FanIsOffCondition = fan_ns.class_("FanIsOffCondition", automation.Condition.temp
FAN_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
{
cv.GenerateID(): cv.declare_id(FanState),
cv.GenerateID(): cv.declare_id(Fan),
cv.Optional(CONF_RESTORE_MODE, default="RESTORE_DEFAULT_OFF"): cv.enum(
RESTORE_MODES, upper=True, space="_"
),
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTFanComponent),
cv.Optional(CONF_OSCILLATION_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
@ -92,6 +107,8 @@ FAN_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).exte
async def setup_fan_core_(var, config):
await setup_entity(var, config)
cg.add(var.set_restore_mode(config[CONF_RESTORE_MODE]))
if CONF_MQTT_ID in config:
mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var)
await mqtt.register_mqtt_component(mqtt_, config)
@ -142,19 +159,19 @@ async def register_fan(var, config):
if not CORE.has_id(config[CONF_ID]):
var = cg.Pvariable(config[CONF_ID], var)
cg.add(cg.App.register_fan(var))
await cg.register_component(var, config)
await setup_fan_core_(var, config)
async def create_fan_state(config):
var = cg.new_Pvariable(config[CONF_ID])
await register_fan(var, config)
await cg.register_component(var, config)
return var
FAN_ACTION_SCHEMA = maybe_simple_id(
{
cv.Required(CONF_ID): cv.use_id(FanState),
cv.Required(CONF_ID): cv.use_id(Fan),
}
)
@ -176,7 +193,7 @@ async def fan_turn_off_to_code(config, action_id, template_arg, args):
TurnOnAction,
maybe_simple_id(
{
cv.Required(CONF_ID): cv.use_id(FanState),
cv.Required(CONF_ID): cv.use_id(Fan),
cv.Optional(CONF_OSCILLATING): cv.templatable(cv.boolean),
cv.Optional(CONF_SPEED): cv.templatable(cv.int_range(1)),
cv.Optional(CONF_DIRECTION): cv.templatable(
@ -211,7 +228,7 @@ async def fan_cycle_speed_to_code(config, action_id, template_arg, args):
FanIsOnCondition,
automation.maybe_simple_id(
{
cv.Required(CONF_ID): cv.use_id(FanState),
cv.Required(CONF_ID): cv.use_id(Fan),
}
),
)
@ -220,7 +237,7 @@ async def fan_cycle_speed_to_code(config, action_id, template_arg, args):
FanIsOffCondition,
automation.maybe_simple_id(
{
cv.Required(CONF_ID): cv.use_id(FanState),
cv.Required(CONF_ID): cv.use_id(Fan),
}
),
)

View File

@ -1,10 +0,0 @@
#include "automation.h"
#include "esphome/core/log.h"
namespace esphome {
namespace fan {
static const char *const TAG = "fan.automation";
} // namespace fan
} // namespace esphome

View File

@ -9,7 +9,7 @@ namespace fan {
template<typename... Ts> class TurnOnAction : public Action<Ts...> {
public:
explicit TurnOnAction(FanState *state) : state_(state) {}
explicit TurnOnAction(Fan *state) : state_(state) {}
TEMPLATABLE_VALUE(bool, oscillating)
TEMPLATABLE_VALUE(int, speed)
@ -29,30 +29,30 @@ template<typename... Ts> class TurnOnAction : public Action<Ts...> {
call.perform();
}
FanState *state_;
Fan *state_;
};
template<typename... Ts> class TurnOffAction : public Action<Ts...> {
public:
explicit TurnOffAction(FanState *state) : state_(state) {}
explicit TurnOffAction(Fan *state) : state_(state) {}
void play(Ts... x) override { this->state_->turn_off().perform(); }
FanState *state_;
Fan *state_;
};
template<typename... Ts> class ToggleAction : public Action<Ts...> {
public:
explicit ToggleAction(FanState *state) : state_(state) {}
explicit ToggleAction(Fan *state) : state_(state) {}
void play(Ts... x) override { this->state_->toggle().perform(); }
FanState *state_;
Fan *state_;
};
template<typename... Ts> class CycleSpeedAction : public Action<Ts...> {
public:
explicit CycleSpeedAction(FanState *state) : state_(state) {}
explicit CycleSpeedAction(Fan *state) : state_(state) {}
void play(Ts... x) override {
// check to see if fan supports speeds and is on
@ -83,29 +83,29 @@ template<typename... Ts> class CycleSpeedAction : public Action<Ts...> {
}
}
FanState *state_;
Fan *state_;
};
template<typename... Ts> class FanIsOnCondition : public Condition<Ts...> {
public:
explicit FanIsOnCondition(FanState *state) : state_(state) {}
explicit FanIsOnCondition(Fan *state) : state_(state) {}
bool check(Ts... x) override { return this->state_->state; }
protected:
FanState *state_;
Fan *state_;
};
template<typename... Ts> class FanIsOffCondition : public Condition<Ts...> {
public:
explicit FanIsOffCondition(FanState *state) : state_(state) {}
explicit FanIsOffCondition(Fan *state) : state_(state) {}
bool check(Ts... x) override { return !this->state_->state; }
protected:
FanState *state_;
Fan *state_;
};
class FanTurnOnTrigger : public Trigger<> {
public:
FanTurnOnTrigger(FanState *state) {
FanTurnOnTrigger(Fan *state) {
state->add_on_state_callback([this, state]() {
auto is_on = state->state;
auto should_trigger = is_on && !this->last_on_;
@ -123,7 +123,7 @@ class FanTurnOnTrigger : public Trigger<> {
class FanTurnOffTrigger : public Trigger<> {
public:
FanTurnOffTrigger(FanState *state) {
FanTurnOffTrigger(Fan *state) {
state->add_on_state_callback([this, state]() {
auto is_on = state->state;
auto should_trigger = !is_on && this->last_on_;
@ -141,7 +141,7 @@ class FanTurnOffTrigger : public Trigger<> {
class FanSpeedSetTrigger : public Trigger<> {
public:
FanSpeedSetTrigger(FanState *state) {
FanSpeedSetTrigger(Fan *state) {
state->add_on_state_callback([this, state]() {
auto speed = state->speed;
auto should_trigger = speed != !this->last_speed_;

View File

@ -0,0 +1,175 @@
#include "fan.h"
#include "fan_helpers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace fan {
static const char *const TAG = "fan";
const LogString *fan_direction_to_string(FanDirection direction) {
switch (direction) {
case FanDirection::FORWARD:
return LOG_STR("FORWARD");
case FanDirection::REVERSE:
return LOG_STR("REVERSE");
default:
return LOG_STR("UNKNOWN");
}
}
void FanCall::perform() {
ESP_LOGD(TAG, "'%s' - Setting:", this->parent_.get_name().c_str());
this->validate_();
if (this->binary_state_.has_value())
ESP_LOGD(TAG, " State: %s", ONOFF(*this->binary_state_));
if (this->oscillating_.has_value())
ESP_LOGD(TAG, " Oscillating: %s", YESNO(*this->oscillating_));
if (this->speed_.has_value())
ESP_LOGD(TAG, " Speed: %d", *this->speed_);
if (this->direction_.has_value())
ESP_LOGD(TAG, " Direction: %s", LOG_STR_ARG(fan_direction_to_string(*this->direction_)));
this->parent_.control(*this);
}
void FanCall::validate_() {
auto traits = this->parent_.get_traits();
if (this->speed_.has_value())
this->speed_ = clamp(*this->speed_, 1, traits.supported_speed_count());
if (this->binary_state_.has_value() && *this->binary_state_) {
// when turning on, if current speed is zero, set speed to 100%
if (traits.supports_speed() && !this->parent_.state && this->parent_.speed == 0) {
this->speed_ = traits.supported_speed_count();
}
}
if (this->oscillating_.has_value() && !traits.supports_oscillation()) {
ESP_LOGW(TAG, "'%s' - This fan does not support oscillation!", this->parent_.get_name().c_str());
this->oscillating_.reset();
}
if (this->speed_.has_value() && !traits.supports_speed()) {
ESP_LOGW(TAG, "'%s' - This fan does not support speeds!", this->parent_.get_name().c_str());
this->speed_.reset();
}
if (this->direction_.has_value() && !traits.supports_direction()) {
ESP_LOGW(TAG, "'%s' - This fan does not support directions!", this->parent_.get_name().c_str());
this->direction_.reset();
}
}
// This whole method is deprecated, don't warn about usage of deprecated methods inside of it.
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
FanCall &FanCall::set_speed(const char *legacy_speed) {
const auto supported_speed_count = this->parent_.get_traits().supported_speed_count();
if (strcasecmp(legacy_speed, "low") == 0) {
this->set_speed(fan::speed_enum_to_level(FAN_SPEED_LOW, supported_speed_count));
} else if (strcasecmp(legacy_speed, "medium") == 0) {
this->set_speed(fan::speed_enum_to_level(FAN_SPEED_MEDIUM, supported_speed_count));
} else if (strcasecmp(legacy_speed, "high") == 0) {
this->set_speed(fan::speed_enum_to_level(FAN_SPEED_HIGH, supported_speed_count));
}
return *this;
}
#pragma GCC diagnostic pop
FanCall FanRestoreState::to_call(Fan &fan) {
auto call = fan.make_call();
call.set_state(this->state);
call.set_oscillating(this->oscillating);
call.set_speed(this->speed);
call.set_direction(this->direction);
return call;
}
void FanRestoreState::apply(Fan &fan) {
fan.state = this->state;
fan.oscillating = this->oscillating;
fan.speed = this->speed;
fan.direction = this->direction;
fan.publish_state();
}
Fan::Fan() : EntityBase("") {}
Fan::Fan(const std::string &name) : EntityBase(name) {}
FanCall Fan::turn_on() { return this->make_call().set_state(true); }
FanCall Fan::turn_off() { return this->make_call().set_state(false); }
FanCall Fan::toggle() { return this->make_call().set_state(!this->state); }
FanCall Fan::make_call() { return FanCall(*this); }
void Fan::add_on_state_callback(std::function<void()> &&callback) { this->state_callback_.add(std::move(callback)); }
void Fan::publish_state() {
auto traits = this->get_traits();
ESP_LOGD(TAG, "'%s' - Sending state:", this->name_.c_str());
ESP_LOGD(TAG, " State: %s", ONOFF(this->state));
if (traits.supports_speed())
ESP_LOGD(TAG, " Speed: %d", this->speed);
if (traits.supports_oscillation())
ESP_LOGD(TAG, " Oscillating: %s", YESNO(this->oscillating));
if (traits.supports_direction())
ESP_LOGD(TAG, " Direction: %s", LOG_STR_ARG(fan_direction_to_string(this->direction)));
this->state_callback_.call();
this->save_state_();
}
// Random 32-bit value, change this every time the layout of the FanRestoreState struct changes.
constexpr uint32_t RESTORE_STATE_VERSION = 0x71700ABA;
optional<FanRestoreState> Fan::restore_state_() {
FanRestoreState recovered{};
this->rtc_ = global_preferences->make_preference<FanRestoreState>(this->get_object_id_hash() ^ RESTORE_STATE_VERSION);
bool restored = this->rtc_.load(&recovered);
switch (this->restore_mode_) {
case FanRestoreMode::NO_RESTORE:
return {};
case FanRestoreMode::ALWAYS_OFF:
recovered.state = false;
return recovered;
case FanRestoreMode::ALWAYS_ON:
recovered.state = true;
return recovered;
case FanRestoreMode::RESTORE_DEFAULT_OFF:
recovered.state = restored ? recovered.state : false;
return recovered;
case FanRestoreMode::RESTORE_DEFAULT_ON:
recovered.state = restored ? recovered.state : true;
return recovered;
case FanRestoreMode::RESTORE_INVERTED_DEFAULT_OFF:
recovered.state = restored ? !recovered.state : false;
return recovered;
case FanRestoreMode::RESTORE_INVERTED_DEFAULT_ON:
recovered.state = restored ? !recovered.state : true;
return recovered;
}
return {};
}
void Fan::save_state_() {
FanRestoreState state{};
state.state = this->state;
state.oscillating = this->oscillating;
state.speed = this->speed;
state.direction = this->direction;
this->rtc_.save(&state);
}
void Fan::dump_traits_(const char *tag, const char *prefix) {
if (this->get_traits().supports_speed()) {
ESP_LOGCONFIG(tag, "%s Speed: YES", prefix);
ESP_LOGCONFIG(tag, "%s Speed count: %d", prefix, this->get_traits().supported_speed_count());
}
if (this->get_traits().supports_oscillation())
ESP_LOGCONFIG(tag, "%s Oscillation: YES", prefix);
if (this->get_traits().supports_direction())
ESP_LOGCONFIG(tag, "%s Direction: YES", prefix);
}
uint32_t Fan::hash_base() { return 418001110UL; }
} // namespace fan
} // namespace esphome

View File

@ -0,0 +1,154 @@
#pragma once
#include "esphome/core/entity_base.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "esphome/core/optional.h"
#include "esphome/core/preferences.h"
#include "fan_traits.h"
namespace esphome {
namespace fan {
#define LOG_FAN(prefix, type, obj) \
if ((obj) != nullptr) { \
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
(obj)->dump_traits_(TAG, prefix); \
}
/// Simple enum to represent the speed of a fan. - DEPRECATED - Will be deleted soon
enum ESPDEPRECATED("FanSpeed is deprecated.", "2021.9") FanSpeed {
FAN_SPEED_LOW = 0, ///< The fan is running on low speed.
FAN_SPEED_MEDIUM = 1, ///< The fan is running on medium speed.
FAN_SPEED_HIGH = 2 ///< The fan is running on high/full speed.
};
/// Simple enum to represent the direction of a fan.
enum class FanDirection { FORWARD = 0, REVERSE = 1 };
/// Restore mode of a fan.
enum class FanRestoreMode {
NO_RESTORE,
ALWAYS_OFF,
ALWAYS_ON,
RESTORE_DEFAULT_OFF,
RESTORE_DEFAULT_ON,
RESTORE_INVERTED_DEFAULT_OFF,
RESTORE_INVERTED_DEFAULT_ON,
};
const LogString *fan_direction_to_string(FanDirection direction);
class Fan;
class FanCall {
public:
explicit FanCall(Fan &parent) : parent_(parent) {}
FanCall &set_state(bool binary_state) {
this->binary_state_ = binary_state;
return *this;
}
FanCall &set_state(optional<bool> binary_state) {
this->binary_state_ = binary_state;
return *this;
}
optional<bool> get_state() const { return this->binary_state_; }
FanCall &set_oscillating(bool oscillating) {
this->oscillating_ = oscillating;
return *this;
}
FanCall &set_oscillating(optional<bool> oscillating) {
this->oscillating_ = oscillating;
return *this;
}
optional<bool> get_oscillating() const { return this->oscillating_; }
FanCall &set_speed(int speed) {
this->speed_ = speed;
return *this;
}
ESPDEPRECATED("set_speed() with string argument is deprecated, use integer argument instead.", "2021.9")
FanCall &set_speed(const char *legacy_speed);
optional<int> get_speed() const { return this->speed_; }
FanCall &set_direction(FanDirection direction) {
this->direction_ = direction;
return *this;
}
FanCall &set_direction(optional<FanDirection> direction) {
this->direction_ = direction;
return *this;
}
optional<FanDirection> get_direction() const { return this->direction_; }
void perform();
protected:
void validate_();
Fan &parent_;
optional<bool> binary_state_;
optional<bool> oscillating_;
optional<int> speed_;
optional<FanDirection> direction_{};
};
struct FanRestoreState {
bool state;
int speed;
bool oscillating;
FanDirection direction;
/// Convert this struct to a fan call that can be performed.
FanCall to_call(Fan &fan);
/// Apply these settings to the fan.
void apply(Fan &fan);
} __attribute__((packed));
class Fan : public EntityBase {
public:
Fan();
/// Construct the fan with name.
explicit Fan(const std::string &name);
/// The current on/off state of the fan.
bool state{false};
/// The current oscillation state of the fan.
bool oscillating{false};
/// The current fan speed level
int speed{0};
/// The current direction of the fan
FanDirection direction{FanDirection::FORWARD};
FanCall turn_on();
FanCall turn_off();
FanCall toggle();
FanCall make_call();
/// Register a callback that will be called each time the state changes.
void add_on_state_callback(std::function<void()> &&callback);
void publish_state();
virtual FanTraits get_traits() = 0;
/// Set the restore mode of this fan.
void set_restore_mode(FanRestoreMode restore_mode) { this->restore_mode_ = restore_mode; }
protected:
friend FanCall;
virtual void control(const FanCall &call) = 0;
optional<FanRestoreState> restore_state_();
void save_state_();
void dump_traits_(const char *tag, const char *prefix);
uint32_t hash_base() override;
CallbackManager<void()> state_callback_{};
ESPPreferenceObject rtc_;
FanRestoreMode restore_mode_;
};
} // namespace fan
} // namespace esphome

View File

@ -1,5 +1,6 @@
#pragma once
#include "fan_state.h"
#include "fan.h"
namespace esphome {
namespace fan {

View File

@ -1,85 +1,16 @@
#include "fan_state.h"
#include "fan_helpers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace fan {
static const char *const TAG = "fan";
const FanTraits &FanState::get_traits() const { return this->traits_; }
void FanState::set_traits(const FanTraits &traits) { this->traits_ = traits; }
void FanState::add_on_state_callback(std::function<void()> &&callback) {
this->state_callback_.add(std::move(callback));
}
FanState::FanState(const std::string &name) : EntityBase(name) {}
FanStateCall FanState::turn_on() { return this->make_call().set_state(true); }
FanStateCall FanState::turn_off() { return this->make_call().set_state(false); }
FanStateCall FanState::toggle() { return this->make_call().set_state(!this->state); }
FanStateCall FanState::make_call() { return FanStateCall(this); }
struct FanStateRTCState {
bool state;
int speed;
bool oscillating;
FanDirection direction;
};
void FanState::setup() {
this->rtc_ = global_preferences->make_preference<FanStateRTCState>(this->get_object_id_hash());
FanStateRTCState recovered{};
if (!this->rtc_.load(&recovered))
return;
auto call = this->make_call();
call.set_state(recovered.state);
call.set_speed(recovered.speed);
call.set_oscillating(recovered.oscillating);
call.set_direction(recovered.direction);
call.perform();
auto restore = this->restore_state_();
if (restore)
restore->to_call(*this).perform();
}
float FanState::get_setup_priority() const { return setup_priority::DATA - 1.0f; }
uint32_t FanState::hash_base() { return 418001110UL; }
void FanStateCall::perform() const {
if (this->binary_state_.has_value()) {
this->state_->state = *this->binary_state_;
}
if (this->oscillating_.has_value()) {
this->state_->oscillating = *this->oscillating_;
}
if (this->direction_.has_value()) {
this->state_->direction = *this->direction_;
}
if (this->speed_.has_value()) {
const int speed_count = this->state_->get_traits().supported_speed_count();
this->state_->speed = clamp(*this->speed_, 1, speed_count);
}
FanStateRTCState saved{};
saved.state = this->state_->state;
saved.speed = this->state_->speed;
saved.oscillating = this->state_->oscillating;
saved.direction = this->state_->direction;
this->state_->rtc_.save(&saved);
this->state_->state_callback_.call();
}
// This whole method is deprecated, don't warn about usage of deprecated methods inside of it.
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
FanStateCall &FanStateCall::set_speed(const char *legacy_speed) {
const auto supported_speed_count = this->state_->get_traits().supported_speed_count();
if (strcasecmp(legacy_speed, "low") == 0) {
this->set_speed(fan::speed_enum_to_level(FAN_SPEED_LOW, supported_speed_count));
} else if (strcasecmp(legacy_speed, "medium") == 0) {
this->set_speed(fan::speed_enum_to_level(FAN_SPEED_MEDIUM, supported_speed_count));
} else if (strcasecmp(legacy_speed, "high") == 0) {
this->set_speed(fan::speed_enum_to_level(FAN_SPEED_HIGH, supported_speed_count));
}
return *this;
}
} // namespace fan
} // namespace esphome

View File

@ -1,111 +1,34 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/entity_base.h"
#include "esphome/core/helpers.h"
#include "esphome/core/preferences.h"
#include "esphome/core/log.h"
#include "fan_traits.h"
#include "fan.h"
namespace esphome {
namespace fan {
/// Simple enum to represent the speed of a fan. - DEPRECATED - Will be deleted soon
enum ESPDEPRECATED("FanSpeed is deprecated.", "2021.9") FanSpeed {
FAN_SPEED_LOW = 0, ///< The fan is running on low speed.
FAN_SPEED_MEDIUM = 1, ///< The fan is running on medium speed.
FAN_SPEED_HIGH = 2 ///< The fan is running on high/full speed.
enum ESPDEPRECATED("LegacyFanDirection members are deprecated, use FanDirection instead.",
"2022.2") LegacyFanDirection {
FAN_DIRECTION_FORWARD = 0,
FAN_DIRECTION_REVERSE = 1
};
/// Simple enum to represent the direction of a fan
enum FanDirection { FAN_DIRECTION_FORWARD = 0, FAN_DIRECTION_REVERSE = 1 };
class FanState;
class FanStateCall {
public:
explicit FanStateCall(FanState *state) : state_(state) {}
FanStateCall &set_state(bool binary_state) {
this->binary_state_ = binary_state;
return *this;
}
FanStateCall &set_state(optional<bool> binary_state) {
this->binary_state_ = binary_state;
return *this;
}
FanStateCall &set_oscillating(bool oscillating) {
this->oscillating_ = oscillating;
return *this;
}
FanStateCall &set_oscillating(optional<bool> oscillating) {
this->oscillating_ = oscillating;
return *this;
}
FanStateCall &set_speed(int speed) {
this->speed_ = speed;
return *this;
}
ESPDEPRECATED("set_speed() with string argument is deprecated, use integer argument instead.", "2021.9")
FanStateCall &set_speed(const char *legacy_speed);
FanStateCall &set_direction(FanDirection direction) {
this->direction_ = direction;
return *this;
}
FanStateCall &set_direction(optional<FanDirection> direction) {
this->direction_ = direction;
return *this;
}
void perform() const;
protected:
FanState *const state_;
optional<bool> binary_state_;
optional<bool> oscillating_;
optional<int> speed_;
optional<FanDirection> direction_{};
};
class FanState : public EntityBase, public Component {
class ESPDEPRECATED("FanState is deprecated, use Fan instead.", "2022.2") FanState : public Fan, public Component {
public:
FanState() = default;
/// Construct the fan state with name.
explicit FanState(const std::string &name);
explicit FanState(const std::string &name) : Fan(name) {}
/// Register a callback that will be called each time the state changes.
void add_on_state_callback(std::function<void()> &&callback);
/// Get the traits of this fan (i.e. what features it supports).
const FanTraits &get_traits() const;
/// Get the traits of this fan.
FanTraits get_traits() override { return this->traits_; }
/// Set the traits of this fan (i.e. what features it supports).
void set_traits(const FanTraits &traits);
/// The current ON/OFF state of the fan.
bool state{false};
/// The current oscillation state of the fan.
bool oscillating{false};
/// The current fan speed level
int speed{};
/// The current direction of the fan
FanDirection direction{FAN_DIRECTION_FORWARD};
FanStateCall turn_on();
FanStateCall turn_off();
FanStateCall toggle();
FanStateCall make_call();
void set_traits(const FanTraits &traits) { this->traits_ = traits; }
void setup() override;
float get_setup_priority() const override;
protected:
friend FanStateCall;
uint32_t hash_base() override;
void control(const FanCall &call) override { this->publish_state(); }
FanTraits traits_{};
CallbackManager<void()> state_callback_{};
ESPPreferenceObject rtc_;
};
} // namespace fan

View File

@ -49,7 +49,12 @@ CONFIG_SCHEMA = cv.All(
}
),
_validate,
cv.only_with_arduino,
cv.require_framework_version(
esp8266_arduino=cv.Version(2, 7, 4),
esp32_arduino=cv.Version(99, 0, 0),
max_version=True,
extra_message="Please see note on documentation for FastLED",
),
)

View File

@ -33,7 +33,12 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_DATA_RATE): cv.frequency,
}
),
cv.only_with_arduino,
cv.require_framework_version(
esp8266_arduino=cv.Version(2, 7, 4),
esp32_arduino=cv.Version(99, 0, 0),
max_version=True,
extra_message="Please see note on documentation for FastLED",
),
)

View File

@ -80,6 +80,10 @@ AURA_LED_COLORS = {
"RED": AuraLEDColor.RED,
"BLUE": AuraLEDColor.BLUE,
"PURPLE": AuraLEDColor.PURPLE,
"GREEN": AuraLEDColor.GREEN,
"YELLOW": AuraLEDColor.YELLOW,
"CYAN": AuraLEDColor.CYAN,
"WHITE": AuraLEDColor.WHITE,
}
validate_aura_led_colors = cv.enum(AURA_LED_COLORS, upper=True)

View File

@ -278,10 +278,11 @@ void FingerprintGrowComponent::delete_all_fingerprints() {
void FingerprintGrowComponent::led_control(bool state) {
ESP_LOGD(TAG, "Setting LED");
if (state)
if (state) {
this->data_ = {LED_ON};
else
} else {
this->data_ = {LED_OFF};
}
switch (this->send_command_()) {
case OK:
ESP_LOGD(TAG, "LED set");

View File

@ -76,6 +76,10 @@ enum GrowAuraLEDColor {
RED = 0x01,
BLUE = 0x02,
PURPLE = 0x03,
GREEN = 0x04,
YELLOW = 0x05,
CYAN = 0x06,
WHITE = 0x07,
};
class FingerprintGrowComponent : public PollingComponent, public uart::UARTDevice {

View File

@ -207,7 +207,7 @@ void FujitsuGeneralClimate::transmit_(uint8_t const *message, uint8_t length) {
ESP_LOGV(TAG, "Transmit message length %d", length);
auto transmit = this->transmitter_->transmit();
auto data = transmit.get_data();
auto *data = transmit.get_data();
data->set_carrier_frequency(FUJITSU_GENERAL_CARRIER_FREQUENCY);

View File

@ -33,16 +33,18 @@ void GPIOSwitch::setup() {
}
// write state before setup
if (initial_state)
if (initial_state) {
this->turn_on();
else
} else {
this->turn_off();
}
this->pin_->setup();
// write after setup again for other IOs
if (initial_state)
if (initial_state) {
this->turn_on();
else
} else {
this->turn_off();
}
}
void GPIOSwitch::dump_config() {
LOG_SWITCH("", "GPIO Switch", this);

View File

@ -115,7 +115,7 @@ class GraphTrace {
void set_line_type(enum LineType val) { this->line_type_ = val; }
Color get_line_color() { return this->line_color_; }
void set_line_color(Color val) { this->line_color_ = val; }
const std::string get_name() { return name_; }
std::string get_name() { return name_; }
const HistoryData *get_tracedata() { return &data_; }
protected:

View File

@ -17,7 +17,7 @@ from .. import hbridge_ns
CODEOWNERS = ["@WeekendWarrior"]
HBridgeFan = hbridge_ns.class_("HBridgeFan", fan.FanState)
HBridgeFan = hbridge_ns.class_("HBridgeFan", cg.Component, fan.Fan)
DecayMode = hbridge_ns.enum("DecayMode")
DECAY_MODE_OPTIONS = {
@ -59,6 +59,7 @@ async def to_code(config):
config[CONF_SPEED_COUNT],
config[CONF_DECAY_MODE],
)
await cg.register_component(var, config)
await fan.register_fan(var, config)
pin_a_ = await cg.get_variable(config[CONF_PIN_A])
cg.add(var.set_pin_a(pin_a_))

View File

@ -22,47 +22,49 @@ void HBridgeFan::set_hbridge_levels_(float a_level, float b_level, float enable)
ESP_LOGD(TAG, "Setting speed: a: %.2f, b: %.2f, enable: %.2f", a_level, b_level, enable);
}
fan::FanStateCall HBridgeFan::brake() {
fan::FanCall HBridgeFan::brake() {
ESP_LOGD(TAG, "Braking");
(this->enable_ == nullptr) ? this->set_hbridge_levels_(1.0f, 1.0f) : this->set_hbridge_levels_(1.0f, 1.0f, 1.0f);
return this->make_call().set_state(false);
}
void HBridgeFan::setup() {
auto restore = this->restore_state_();
if (restore.has_value()) {
restore->apply(*this);
this->write_state_();
}
}
void HBridgeFan::dump_config() {
ESP_LOGCONFIG(TAG, "Fan '%s':", this->get_name().c_str());
if (this->get_traits().supports_oscillation()) {
ESP_LOGCONFIG(TAG, " Oscillation: YES");
}
if (this->get_traits().supports_direction()) {
ESP_LOGCONFIG(TAG, " Direction: YES");
}
LOG_FAN("", "H-Bridge Fan", this);
if (this->decay_mode_ == DECAY_MODE_SLOW) {
ESP_LOGCONFIG(TAG, " Decay Mode: Slow");
} else {
ESP_LOGCONFIG(TAG, " Decay Mode: Fast");
}
}
void HBridgeFan::setup() {
auto traits = fan::FanTraits(this->oscillating_ != nullptr, true, true, this->speed_count_);
this->set_traits(traits);
this->add_on_state_callback([this]() { this->next_update_ = true; });
fan::FanTraits HBridgeFan::get_traits() {
return fan::FanTraits(this->oscillating_ != nullptr, true, true, this->speed_count_);
}
void HBridgeFan::loop() {
if (!this->next_update_) {
return;
}
this->next_update_ = false;
void HBridgeFan::control(const fan::FanCall &call) {
if (call.get_state().has_value())
this->state = *call.get_state();
if (call.get_speed().has_value())
this->speed = *call.get_speed();
if (call.get_oscillating().has_value())
this->oscillating = *call.get_oscillating();
if (call.get_direction().has_value())
this->direction = *call.get_direction();
float speed = 0.0f;
if (this->state) {
speed = static_cast<float>(this->speed) / static_cast<float>(this->speed_count_);
}
this->write_state_();
this->publish_state();
}
void HBridgeFan::write_state_() {
float speed = this->state ? static_cast<float>(this->speed) / static_cast<float>(this->speed_count_) : 0.0f;
if (speed == 0.0f) { // off means idle
(this->enable_ == nullptr) ? this->set_hbridge_levels_(speed, speed)
: this->set_hbridge_levels_(speed, speed, speed);
return;
}
if (this->direction == fan::FAN_DIRECTION_FORWARD) {
} else if (this->direction == fan::FanDirection::FORWARD) {
if (this->decay_mode_ == DECAY_MODE_SLOW) {
(this->enable_ == nullptr) ? this->set_hbridge_levels_(1.0f - speed, 1.0f)
: this->set_hbridge_levels_(1.0f - speed, 1.0f, 1.0f);
@ -79,6 +81,9 @@ void HBridgeFan::loop() {
: this->set_hbridge_levels_(1.0f, 0.0f, speed);
}
}
if (this->oscillating_ != nullptr)
this->oscillating_->set_state(this->oscillating);
}
} // namespace hbridge

View File

@ -3,7 +3,7 @@
#include "esphome/core/automation.h"
#include "esphome/components/output/binary_output.h"
#include "esphome/components/output/float_output.h"
#include "esphome/components/fan/fan_state.h"
#include "esphome/components/fan/fan.h"
namespace esphome {
namespace hbridge {
@ -13,7 +13,7 @@ enum DecayMode {
DECAY_MODE_FAST = 1,
};
class HBridgeFan : public fan::FanState {
class HBridgeFan : public Component, public fan::Fan {
public:
HBridgeFan(int speed_count, DecayMode decay_mode) : speed_count_(speed_count), decay_mode_(decay_mode) {}
@ -22,25 +22,22 @@ class HBridgeFan : public fan::FanState {
void set_enable_pin(output::FloatOutput *enable) { enable_ = enable; }
void setup() override;
void loop() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::HARDWARE; }
fan::FanTraits get_traits() override;
fan::FanStateCall brake();
int get_speed_count() { return this->speed_count_; }
// update Hbridge without a triggered FanState change, eg. for acceleration/deceleration ramping
void internal_update() { this->next_update_ = true; }
fan::FanCall brake();
protected:
output::FloatOutput *pin_a_;
output::FloatOutput *pin_b_;
output::FloatOutput *enable_{nullptr};
output::BinaryOutput *oscillating_{nullptr};
bool next_update_{true};
int speed_count_{};
DecayMode decay_mode_{DECAY_MODE_SLOW};
void control(const fan::FanCall &call) override;
void write_state_();
void set_hbridge_levels_(float a_level, float b_level);
void set_hbridge_levels_(float a_level, float b_level, float enable);
};

View File

@ -6,20 +6,20 @@ namespace esphome {
namespace heatpumpir {
void IRSenderESPHome::setFrequency(int frequency) { // NOLINT(readability-identifier-naming)
auto data = transmit_.get_data();
auto *data = transmit_.get_data();
data->set_carrier_frequency(1000 * frequency);
}
// Send an IR 'mark' symbol, i.e. transmitter ON
void IRSenderESPHome::mark(int mark_length) {
auto data = transmit_.get_data();
auto *data = transmit_.get_data();
data->mark(mark_length);
}
// Send an IR 'space' symbol, i.e. transmitter OFF
void IRSenderESPHome::space(int space_length) {
if (space_length) {
auto data = transmit_.get_data();
auto *data = transmit_.get_data();
data->space(space_length);
} else {
transmit_.perform();

View File

@ -19,10 +19,11 @@ void set_bits(uint8_t *const dst, const uint8_t offset, const uint8_t nbits, con
void set_bit(uint8_t *const data, const uint8_t position, const bool on) {
uint8_t mask = 1 << position;
if (on)
if (on) {
*data |= mask;
else
} else {
*data &= ~mask;
}
}
uint8_t *invert_byte_pairs(uint8_t *ptr, const uint16_t length) {
@ -69,10 +70,11 @@ void HitachiClimate::set_temp_(uint8_t celsius, bool set_previous) {
temp = std::min(celsius, HITACHI_AC344_TEMP_MAX);
temp = std::max(temp, HITACHI_AC344_TEMP_MIN);
set_bits(&remote_state_[HITACHI_AC344_TEMP_BYTE], HITACHI_AC344_TEMP_OFFSET, HITACHI_AC344_TEMP_SIZE, temp);
if (previous_temp_ > temp)
if (previous_temp_ > temp) {
set_button_(HITACHI_AC344_BUTTON_TEMP_DOWN);
else if (previous_temp_ < temp)
} else if (previous_temp_ < temp) {
set_button_(HITACHI_AC344_BUTTON_TEMP_UP);
}
if (set_previous)
previous_temp_ = temp;
}
@ -110,11 +112,12 @@ void HitachiClimate::set_fan_(uint8_t speed) {
void HitachiClimate::set_swing_v_toggle_(bool on) {
uint8_t button = get_button_(); // Get the current button value.
if (on)
button = HITACHI_AC344_BUTTON_SWINGV; // Set the button to SwingV.
else if (button == HITACHI_AC344_BUTTON_SWINGV) // Asked to unset it
if (on) {
button = HITACHI_AC344_BUTTON_SWINGV; // Set the button to SwingV.
} else if (button == HITACHI_AC344_BUTTON_SWINGV) { // Asked to unset it
// It was set previous, so use Power as a default
button = HITACHI_AC344_BUTTON_POWER;
}
set_button_(button);
}
@ -211,7 +214,7 @@ void HitachiClimate::transmit_state() {
invert_byte_pairs(remote_state_ + 3, HITACHI_AC344_STATE_LENGTH - 3);
auto transmit = this->transmitter_->transmit();
auto data = transmit.get_data();
auto *data = transmit.get_data();
data->set_carrier_frequency(HITACHI_AC344_FREQ);
uint8_t repeat = 0;
@ -320,9 +323,9 @@ bool HitachiClimate::on_receive(remote_base::RemoteReceiveData data) {
for (uint8_t pos = 0; pos < HITACHI_AC344_STATE_LENGTH; pos++) {
// Read bit
for (int8_t bit = 0; bit < 8; bit++) {
if (data.expect_item(HITACHI_AC344_BIT_MARK, HITACHI_AC344_ONE_SPACE))
if (data.expect_item(HITACHI_AC344_BIT_MARK, HITACHI_AC344_ONE_SPACE)) {
recv_state[pos] |= 1 << bit;
else if (!data.expect_item(HITACHI_AC344_BIT_MARK, HITACHI_AC344_ZERO_SPACE)) {
} else if (!data.expect_item(HITACHI_AC344_BIT_MARK, HITACHI_AC344_ZERO_SPACE)) {
ESP_LOGVV(TAG, "Byte %d bit %d fail", pos, bit);
return false;
}

View File

@ -19,10 +19,11 @@ void set_bits(uint8_t *const dst, const uint8_t offset, const uint8_t nbits, con
void set_bit(uint8_t *const data, const uint8_t position, const bool on) {
uint8_t mask = 1 << position;
if (on)
if (on) {
*data |= mask;
else
} else {
*data &= ~mask;
}
}
uint8_t *invert_byte_pairs(uint8_t *ptr, const uint16_t length) {
@ -69,10 +70,11 @@ void HitachiClimate::set_temp_(uint8_t celsius, bool set_previous) {
temp = std::min(celsius, HITACHI_AC424_TEMP_MAX);
temp = std::max(temp, HITACHI_AC424_TEMP_MIN);
set_bits(&remote_state_[HITACHI_AC424_TEMP_BYTE], HITACHI_AC424_TEMP_OFFSET, HITACHI_AC424_TEMP_SIZE, temp);
if (previous_temp_ > temp)
if (previous_temp_ > temp) {
set_button_(HITACHI_AC424_BUTTON_TEMP_DOWN);
else if (previous_temp_ < temp)
} else if (previous_temp_ < temp) {
set_button_(HITACHI_AC424_BUTTON_TEMP_UP);
}
if (set_previous)
previous_temp_ = temp;
}
@ -110,11 +112,12 @@ void HitachiClimate::set_fan_(uint8_t speed) {
void HitachiClimate::set_swing_v_toggle_(bool on) {
uint8_t button = get_button_(); // Get the current button value.
if (on)
button = HITACHI_AC424_BUTTON_SWINGV; // Set the button to SwingV.
else if (button == HITACHI_AC424_BUTTON_SWINGV) // Asked to unset it
if (on) {
button = HITACHI_AC424_BUTTON_SWINGV; // Set the button to SwingV.
} else if (button == HITACHI_AC424_BUTTON_SWINGV) { // Asked to unset it
// It was set previous, so use Power as a default
button = HITACHI_AC424_BUTTON_POWER;
}
set_button_(button);
}
@ -212,7 +215,7 @@ void HitachiClimate::transmit_state() {
invert_byte_pairs(remote_state_ + 3, HITACHI_AC424_STATE_LENGTH - 3);
auto transmit = this->transmitter_->transmit();
auto data = transmit.get_data();
auto *data = transmit.get_data();
data->set_carrier_frequency(HITACHI_AC424_FREQ);
uint8_t repeat = 0;
@ -321,9 +324,9 @@ bool HitachiClimate::on_receive(remote_base::RemoteReceiveData data) {
for (uint8_t pos = 0; pos < HITACHI_AC424_STATE_LENGTH; pos++) {
// Read bit
for (int8_t bit = 0; bit < 8; bit++) {
if (data.expect_item(HITACHI_AC424_BIT_MARK, HITACHI_AC424_ONE_SPACE))
if (data.expect_item(HITACHI_AC424_BIT_MARK, HITACHI_AC424_ONE_SPACE)) {
recv_state[pos] |= 1 << bit;
else if (!data.expect_item(HITACHI_AC424_BIT_MARK, HITACHI_AC424_ZERO_SPACE)) {
} else if (!data.expect_item(HITACHI_AC424_BIT_MARK, HITACHI_AC424_ZERO_SPACE)) {
ESP_LOGVV(TAG, "Byte %d bit %d fail", pos, bit);
return false;
}

View File

@ -44,8 +44,8 @@ class HM3301Component : public PollingComponent, public i2c::I2CDevice {
AQICalculatorType aqi_calc_type_;
AQICalculatorFactory aqi_calculator_factory_ = AQICalculatorFactory();
bool validate_checksum_(const uint8_t *);
uint16_t get_sensor_value_(const uint8_t *, uint8_t);
bool validate_checksum_(const uint8_t *data);
uint16_t get_sensor_value_(const uint8_t *data, uint8_t i);
};
} // namespace hm3301

View File

@ -25,10 +25,11 @@ void HomeassistantBinarySensor::setup() {
} else {
ESP_LOGD(TAG, "'%s': Got state %s", this->entity_id_.c_str(), ONOFF(new_state));
}
if (this->initial_)
if (this->initial_) {
this->publish_initial_state(new_state);
else
} else {
this->publish_state(new_state);
}
break;
}
this->initial_ = false;

View File

@ -1,7 +1,8 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import text_sensor
from esphome.const import CONF_ATTRIBUTE, CONF_ENTITY_ID, CONF_ID
from esphome.const import CONF_ATTRIBUTE, CONF_ENTITY_ID
from .. import homeassistant_ns
DEPENDENCIES = ["api"]
@ -10,7 +11,7 @@ HomeassistantTextSensor = homeassistant_ns.class_(
"HomeassistantTextSensor", text_sensor.TextSensor, cg.Component
)
CONFIG_SCHEMA = text_sensor.TEXT_SENSOR_SCHEMA.extend(
CONFIG_SCHEMA = text_sensor.text_sensor_schema().extend(
{
cv.GenerateID(): cv.declare_id(HomeassistantTextSensor),
cv.Required(CONF_ENTITY_ID): cv.entity_id,
@ -20,9 +21,8 @@ CONFIG_SCHEMA = text_sensor.TEXT_SENSOR_SCHEMA.extend(
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
var = await text_sensor.new_text_sensor(config)
await cg.register_component(var, config)
await text_sensor.register_text_sensor(var, config)
cg.add(var.set_entity_id(config[CONF_ENTITY_ID]))
if CONF_ATTRIBUTE in config:

View File

@ -31,6 +31,8 @@ CONF_BODY = "body"
CONF_JSON = "json"
CONF_VERIFY_SSL = "verify_ssl"
CONF_ON_RESPONSE = "on_response"
CONF_FOLLOW_REDIRECTS = "follow_redirects"
CONF_REDIRECT_LIMIT = "redirect_limit"
def validate_url(value):
@ -71,6 +73,8 @@ CONFIG_SCHEMA = cv.All(
{
cv.GenerateID(): cv.declare_id(HttpRequestComponent),
cv.Optional(CONF_USERAGENT, "ESPHome"): cv.string,
cv.Optional(CONF_FOLLOW_REDIRECTS, True): cv.boolean,
cv.Optional(CONF_REDIRECT_LIMIT, 3): cv.int_,
cv.Optional(
CONF_TIMEOUT, default="5s"
): cv.positive_time_period_milliseconds,
@ -90,6 +94,9 @@ async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
cg.add(var.set_timeout(config[CONF_TIMEOUT]))
cg.add(var.set_useragent(config[CONF_USERAGENT]))
cg.add(var.set_follow_redirects(config[CONF_FOLLOW_REDIRECTS]))
cg.add(var.set_redirect_limit(config[CONF_REDIRECT_LIMIT]))
if CORE.is_esp8266 and not config[CONF_ESP8266_DISABLE_SSL_SUPPORT]:
cg.add_define("USE_HTTP_REQUEST_ESP8266_HTTPS")

View File

@ -1,7 +1,7 @@
#ifdef USE_ARDUINO
#include "http_request.h"
#include "esphome/core/macros.h"
#include "esphome/core/defines.h"
#include "esphome/core/log.h"
#include "esphome/components/network/util.h"
@ -14,6 +14,8 @@ void HttpRequestComponent::dump_config() {
ESP_LOGCONFIG(TAG, "HTTP Request:");
ESP_LOGCONFIG(TAG, " Timeout: %ums", this->timeout_);
ESP_LOGCONFIG(TAG, " User-Agent: %s", this->useragent_);
ESP_LOGCONFIG(TAG, " Follow Redirects: %d", this->follow_redirects_);
ESP_LOGCONFIG(TAG, " Redirect limit: %d", this->redirect_limit_);
}
void HttpRequestComponent::set_url(std::string url) {
@ -38,18 +40,21 @@ void HttpRequestComponent::send(const std::vector<HttpRequestResponseTrigger *>
bool begin_status = false;
const String url = this->url_.c_str();
#ifdef USE_ESP32
#if defined(USE_ESP32) || (defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0))
#if defined(USE_ESP32) || USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0)
if (this->follow_redirects_) {
this->client_.setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS);
} else {
this->client_.setFollowRedirects(HTTPC_DISABLE_FOLLOW_REDIRECTS);
}
#else
this->client_.setFollowRedirects(this->follow_redirects_);
#endif
this->client_.setRedirectLimit(this->redirect_limit_);
#endif
#if defined(USE_ESP32)
begin_status = this->client_.begin(url);
#endif
#ifdef USE_ESP8266
#if ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0)
this->client_.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
#elif ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0)
this->client_.setFollowRedirects(true);
#endif
#if ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0)
this->client_.setRedirectLimit(3);
#endif
#elif defined(USE_ESP8266)
begin_status = this->client_.begin(*this->get_wifi_client_(), url);
#endif

View File

@ -40,6 +40,8 @@ class HttpRequestComponent : public Component {
void set_method(const char *method) { this->method_ = method; }
void set_useragent(const char *useragent) { this->useragent_ = useragent; }
void set_timeout(uint16_t timeout) { this->timeout_ = timeout; }
void set_follow_redirects(bool follow_redirects) { this->follow_redirects_ = follow_redirects; }
void set_redirect_limit(uint16_t limit) { this->redirect_limit_ = limit; }
void set_body(const std::string &body) { this->body_ = body; }
void set_headers(std::list<Header> headers) { this->headers_ = std::move(headers); }
void send(const std::vector<HttpRequestResponseTrigger *> &response_triggers);
@ -53,6 +55,8 @@ class HttpRequestComponent : public Component {
const char *method_;
const char *useragent_{nullptr};
bool secure_;
bool follow_redirects_;
uint16_t redirect_limit_;
uint16_t timeout_{5000};
std::string body_;
std::list<Header> headers_;

View File

@ -16,10 +16,11 @@ void ArduinoI2CBus::setup() {
#ifdef USE_ESP32
static uint8_t next_bus_num = 0;
if (next_bus_num == 0)
if (next_bus_num == 0) {
wire_ = &Wire;
else
} else {
wire_ = new TwoWire(next_bus_num); // NOLINT(cppcoreguidelines-owning-memory)
}
next_bus_num++;
#else
wire_ = &Wire; // NOLINT(cppcoreguidelines-prefer-member-initializer)
@ -55,10 +56,11 @@ void ArduinoI2CBus::dump_config() {
ESP_LOGI(TAG, "Found no i2c devices!");
} else {
for (const auto &s : scan_results_) {
if (s.second)
if (s.second) {
ESP_LOGI(TAG, "Found i2c device at address 0x%02X", s.first);
else
} else {
ESP_LOGE(TAG, "Unknown error at address 0x%02X", s.first);
}
}
}
}

View File

@ -65,10 +65,11 @@ void IDFI2CBus::dump_config() {
ESP_LOGI(TAG, "Found no i2c devices!");
} else {
for (const auto &s : scan_results_) {
if (s.second)
if (s.second) {
ESP_LOGI(TAG, "Found i2c device at address 0x%02X", s.first);
else
} else {
ESP_LOGE(TAG, "Unknown error at address 0x%02X", s.first);
}
}
}
}

View File

@ -24,6 +24,7 @@ IMAGE_TYPE = {
"BINARY": ImageType.IMAGE_TYPE_BINARY,
"GRAYSCALE": ImageType.IMAGE_TYPE_GRAYSCALE,
"RGB24": ImageType.IMAGE_TYPE_RGB24,
"TRANSPARENT_BINARY": ImageType.IMAGE_TYPE_TRANSPARENT_BINARY,
}
Image_ = display.display_ns.class_("Image")
@ -99,6 +100,17 @@ async def to_code(config):
pos = x + y * width8
data[pos // 8] |= 0x80 >> (pos % 8)
elif config[CONF_TYPE] == "TRANSPARENT_BINARY":
image = image.convert("RGBA")
width8 = ((width + 7) // 8) * 8
data = [0 for _ in range(height * width8 // 8)]
for y in range(height):
for x in range(width):
if not image.getpixel((x, y))[3]:
continue
pos = x + y * width8
data[pos // 8] |= 0x80 >> (pos % 8)
rhs = [HexInt(x) for x in data]
prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs)
cg.new_Pvariable(

View File

@ -30,4 +30,4 @@ 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)
cg.add_library("esphome/Improv", "1.0.0")
cg.add_library("esphome/Improv", "1.2.1")

View File

@ -24,6 +24,8 @@ void ImprovSerialComponent::setup() {
if (wifi::global_wifi_component->has_sta()) {
this->state_ = improv::STATE_PROVISIONED;
} else {
wifi::global_wifi_component->start_scanning();
}
}
@ -95,7 +97,7 @@ std::vector<uint8_t> ImprovSerialComponent::build_rpc_settings_response_(improv:
std::vector<std::string> urls;
#ifdef USE_WEBSERVER
auto ip = wifi::global_wifi_component->wifi_sta_ip();
std::string webserver_url = "http://" + ip.str() + ":" + to_string(WEBSERVER_PORT);
std::string webserver_url = "http://" + ip.str() + ":" + to_string(USE_WEBSERVER_PORT);
urls.push_back(webserver_url);
#endif
std::vector<uint8_t> data = improv::build_rpc_response(command, urls, false);
@ -111,58 +113,15 @@ std::vector<uint8_t> ImprovSerialComponent::build_version_info_() {
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);
ESP_LOGV(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)
return true;
if (at == 8 + data_len + 1) {
uint8_t checksum = 0x00;
for (size_t i = 0; i < at; i++)
checksum += raw[i];
if (checksum != byte) {
ESP_LOGW(TAG, "Error decoding Improv payload");
this->set_error_(improv::ERROR_INVALID_RPC);
return false;
}
if (type == TYPE_RPC) {
this->set_error_(improv::ERROR_NONE);
auto command = improv::parse_improv_data(&raw[9], data_len, false);
return this->parse_improv_payload_(command);
}
}
// If we got here then the command coming is is improv, but not an RPC command
return false;
return improv::parse_improv_serial_byte(
at, byte, raw, [this](improv::ImprovCommand command) -> bool { return this->parse_improv_payload_(command); },
[this](improv::Error error) -> void {
ESP_LOGW(TAG, "Error decoding Improv payload");
this->set_error_(error);
});
}
bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command) {
@ -195,6 +154,27 @@ bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command
this->send_response_(info);
return true;
}
case improv::GET_WIFI_NETWORKS: {
std::vector<std::string> networks;
auto results = wifi::global_wifi_component->get_scan_result();
for (auto &scan : results) {
if (scan.get_is_hidden())
continue;
const std::string &ssid = scan.get_ssid();
if (std::find(networks.begin(), networks.end(), ssid) != networks.end())
continue;
// Send each ssid separately to avoid overflowing the buffer
std::vector<uint8_t> data = improv::build_rpc_response(
improv::GET_WIFI_NETWORKS, {ssid, str_sprintf("%d", scan.get_rssi()), YESNO(scan.get_with_auth())}, false);
this->send_response_(data);
networks.push_back(ssid);
}
// Send empty response to signify the end of the list.
std::vector<uint8_t> data =
improv::build_rpc_response(improv::GET_WIFI_NETWORKS, std::vector<std::string>{}, false);
this->send_response_(data);
return true;
}
default: {
ESP_LOGW(TAG, "Unknown Improv payload");
this->set_error_(improv::ERROR_UNKNOWN_RPC);

View File

@ -32,7 +32,7 @@ class ImprovSerialComponent : public Component {
void loop() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::HARDWARE; }
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
protected:
bool parse_improv_serial_byte_(uint8_t byte);

View File

@ -47,6 +47,7 @@ InkplateModel = inkplate6_ns.enum("InkplateModel")
MODELS = {
"inkplate_6": InkplateModel.INKPLATE_6,
"inkplate_10": InkplateModel.INKPLATE_10,
"inkplate_6_plus": InkplateModel.INKPLATE_6_PLUS,
}
CONFIG_SCHEMA = cv.All(

View File

@ -13,6 +13,11 @@ namespace inkplate6 {
static const char *const TAG = "inkplate";
void Inkplate6::setup() {
for (uint32_t i = 0; i < 256; i++) {
this->pin_lut_[i] = ((i & 0b00000011) << 4) | (((i & 0b00001100) >> 2) << 18) | (((i & 0b00010000) >> 4) << 23) |
(((i & 0b11100000) >> 5) << 25);
}
this->initialize_();
this->vcom_pin_->setup();
@ -38,11 +43,21 @@ void Inkplate6::setup() {
this->display_data_6_pin_->setup();
this->display_data_7_pin_->setup();
this->clean();
this->display();
this->wakeup_pin_->digital_write(true);
delay(1);
this->write_bytes(0x09, {
0b00011011, // Power up seq.
0b00000000, // Power up delay (3mS per rail)
0b00011011, // Power down seq.
0b00000000, // Power down delay (6mS per rail)
});
delay(1);
this->wakeup_pin_->digital_write(false);
}
void Inkplate6::initialize_() {
ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
ExternalRAMAllocator<uint32_t> allocator32(ExternalRAMAllocator<uint32_t>::ALLOW_FAILURE);
uint32_t buffer_size = this->get_buffer_length_();
if (buffer_size == 0)
return;
@ -53,6 +68,10 @@ void Inkplate6::initialize_() {
allocator.deallocate(this->partial_buffer_2_, buffer_size * 2);
if (this->buffer_ != nullptr)
allocator.deallocate(this->buffer_, buffer_size);
if (this->glut_ != nullptr)
allocator32.deallocate(this->glut_, 256 * (this->model_ == INKPLATE_6_PLUS ? 9 : 8));
if (this->glut2_ != nullptr)
allocator32.deallocate(this->glut2_, 256 * (this->model_ == INKPLATE_6_PLUS ? 9 : 8));
this->buffer_ = allocator.allocate(buffer_size);
if (this->buffer_ == nullptr) {
@ -60,7 +79,34 @@ void Inkplate6::initialize_() {
this->mark_failed();
return;
}
if (!this->greyscale_) {
if (this->greyscale_) {
uint8_t glut_size = (this->model_ == INKPLATE_6_PLUS ? 9 : 8);
this->glut_ = allocator32.allocate(256 * glut_size);
if (this->glut_ == nullptr) {
ESP_LOGE(TAG, "Could not allocate glut!");
this->mark_failed();
return;
}
this->glut2_ = allocator32.allocate(256 * glut_size);
if (this->glut2_ == nullptr) {
ESP_LOGE(TAG, "Could not allocate glut2!");
this->mark_failed();
return;
}
for (int i = 0; i < glut_size; i++) {
for (uint32_t j = 0; j < 256; j++) {
uint8_t z = (waveform3Bit[j & 0x07][i] << 2) | (waveform3Bit[(j >> 4) & 0x07][i]);
this->glut_[i * 256 + j] = ((z & 0b00000011) << 4) | (((z & 0b00001100) >> 2) << 18) |
(((z & 0b00010000) >> 4) << 23) | (((z & 0b11100000) >> 5) << 25);
z = ((waveform3Bit[j & 0x07][i] << 2) | (waveform3Bit[(j >> 4) & 0x07][i])) << 4;
this->glut2_[i * 256 + j] = ((z & 0b00000011) << 4) | (((z & 0b00001100) >> 2) << 18) |
(((z & 0b00010000) >> 4) << 23) | (((z & 0b11100000) >> 5) << 25);
}
}
} else {
this->partial_buffer_ = allocator.allocate(buffer_size);
if (this->partial_buffer_ == nullptr) {
ESP_LOGE(TAG, "Could not allocate partial buffer for display!");
@ -73,13 +119,16 @@ void Inkplate6::initialize_() {
this->mark_failed();
return;
}
memset(this->partial_buffer_, 0, buffer_size);
memset(this->partial_buffer_2_, 0, buffer_size * 2);
}
memset(this->buffer_, 0, buffer_size);
}
float Inkplate6::get_setup_priority() const { return setup_priority::PROCESSOR; }
size_t Inkplate6::get_buffer_length_() {
if (this->greyscale_) {
return size_t(this->get_width_internal()) * size_t(this->get_height_internal()) / 2u;
@ -87,6 +136,7 @@ size_t Inkplate6::get_buffer_length_() {
return size_t(this->get_width_internal()) * size_t(this->get_height_internal()) / 8u;
}
}
void Inkplate6::update() {
this->do_update_();
@ -96,6 +146,7 @@ void Inkplate6::update() {
this->display();
}
void HOT Inkplate6::draw_absolute_pixel_internal(int x, int y, Color color) {
if (x >= this->get_width_internal() || y >= this->get_height_internal() || x < 0 || y < 0)
return;
@ -121,6 +172,7 @@ void HOT Inkplate6::draw_absolute_pixel_internal(int x, int y, Color color) {
this->partial_buffer_[pos] = (~pixelMaskLUT[x_sub] & current) | (color.is_on() ? 0 : pixelMaskLUT[x_sub]);
}
}
void Inkplate6::dump_config() {
LOG_DISPLAY("", "Inkplate", this);
ESP_LOGCONFIG(TAG, " Greyscale: %s", YESNO(this->greyscale_));
@ -150,44 +202,51 @@ void Inkplate6::dump_config() {
LOG_UPDATE_INTERVAL(this);
}
void Inkplate6::eink_off_() {
ESP_LOGV(TAG, "Eink off called");
if (panel_on_ == 0)
if (!panel_on_)
return;
panel_on_ = 0;
this->gmod_pin_->digital_write(false);
panel_on_ = false;
this->oe_pin_->digital_write(false);
this->gmod_pin_->digital_write(false);
GPIO.out &= ~(get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()) | (1 << this->le_pin_->get_pin()));
GPIO.out &= ~(this->get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()) | (1 << this->le_pin_->get_pin()));
this->ckv_pin_->digital_write(false);
this->sph_pin_->digital_write(false);
this->spv_pin_->digital_write(false);
this->powerup_pin_->digital_write(false);
this->wakeup_pin_->digital_write(false);
this->vcom_pin_->digital_write(false);
this->write_byte(0x01, 0x6F); // Put TPS65186 into standby mode
delay(100); // NOLINT
this->write_byte(0x01, 0x4f); // Disable 3V3 to the panel
if (this->model_ != INKPLATE_6_PLUS)
this->wakeup_pin_->digital_write(false);
pins_z_state_();
}
void Inkplate6::eink_on_() {
ESP_LOGV(TAG, "Eink on called");
if (panel_on_ == 1)
if (panel_on_)
return;
panel_on_ = 1;
pins_as_outputs_();
this->panel_on_ = true;
this->pins_as_outputs_();
this->wakeup_pin_->digital_write(true);
this->powerup_pin_->digital_write(true);
this->vcom_pin_->digital_write(true);
this->write_byte(0x01, 0x3F);
delay(40);
this->write_byte(0x0D, 0x80);
delay(2);
this->read_register(0x00, nullptr, 0);
this->write_byte(0x01, 0b00101111); // Enable all rails
delay(1);
this->write_byte(0x01, 0b10101111); // Switch TPS65186 into active mode
this->le_pin_->digital_write(false);
this->oe_pin_->digital_write(false);
@ -196,8 +255,33 @@ void Inkplate6::eink_on_() {
this->gmod_pin_->digital_write(true);
this->spv_pin_->digital_write(true);
this->ckv_pin_->digital_write(false);
this->oe_pin_->digital_write(false);
uint32_t timer = millis();
do {
delay(1);
} while (!this->read_power_status_() && ((millis() - timer) < 250));
if ((millis() - timer) >= 250) {
ESP_LOGW(TAG, "Power supply not detected");
this->wakeup_pin_->digital_write(false);
this->vcom_pin_->digital_write(false);
this->powerup_pin_->digital_write(false);
this->panel_on_ = false;
return;
}
this->oe_pin_->digital_write(true);
}
bool Inkplate6::read_power_status_() {
uint8_t data;
auto err = this->read_register(0x0F, &data, 1);
if (err == i2c::ERROR_OK) {
return data == 0b11111010;
}
return false;
}
void Inkplate6::fill(Color color) {
ESP_LOGV(TAG, "Fill called");
uint32_t start_time = millis();
@ -212,6 +296,7 @@ void Inkplate6::fill(Color color) {
ESP_LOGV(TAG, "Fill finished (%ums)", millis() - start_time);
}
void Inkplate6::display() {
ESP_LOGV(TAG, "Display called");
uint32_t start_time = millis();
@ -227,201 +312,185 @@ void Inkplate6::display() {
}
ESP_LOGV(TAG, "Display finished (full) (%ums)", millis() - start_time);
}
void Inkplate6::display1b_() {
ESP_LOGV(TAG, "Display1b called");
uint32_t start_time = millis();
memcpy(this->buffer_, this->partial_buffer_, this->get_buffer_length_());
uint32_t send;
uint8_t data;
uint8_t buffer_value;
const uint8_t *buffer_ptr;
eink_on_();
clean_fast_(0, 1);
clean_fast_(1, 5);
clean_fast_(2, 1);
clean_fast_(0, 5);
clean_fast_(2, 1);
clean_fast_(1, 12);
clean_fast_(2, 1);
clean_fast_(0, 11);
if (this->model_ == INKPLATE_6_PLUS) {
clean_fast_(0, 1);
clean_fast_(1, 15);
clean_fast_(2, 1);
clean_fast_(0, 5);
clean_fast_(2, 1);
clean_fast_(1, 15);
} else {
clean_fast_(0, 1);
clean_fast_(1, 21);
clean_fast_(2, 1);
clean_fast_(0, 12);
clean_fast_(2, 1);
clean_fast_(1, 21);
clean_fast_(2, 1);
clean_fast_(0, 12);
}
uint32_t clock = (1 << this->cl_pin_->get_pin());
uint32_t data_mask = this->get_data_pin_mask_();
ESP_LOGV(TAG, "Display1b start loops (%ums)", millis() - start_time);
for (int k = 0; k < 3; k++) {
for (int k = 0; k < 4; k++) {
buffer_ptr = &this->buffer_[this->get_buffer_length_() - 1];
vscan_start_();
for (int i = 0; i < this->get_height_internal(); i++) {
for (int i = 0, im = this->get_height_internal(); i < im; i++) {
buffer_value = *(buffer_ptr--);
data = LUTB[(buffer_value >> 4) & 0x0F];
send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) |
(((data & 0b11100000) >> 5) << 25);
hscan_start_(send);
data = LUTB[buffer_value & 0x0F];
send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) |
(((data & 0b11100000) >> 5) << 25) | clock;
GPIO.out_w1ts = send;
GPIO.out_w1tc = send;
data = this->model_ == INKPLATE_6_PLUS ? LUTW[(~buffer_value >> 4) & 0x0F] : LUTB[(buffer_value >> 4) & 0x0F];
hscan_start_(this->pin_lut_[data]);
data = this->model_ == INKPLATE_6_PLUS ? LUTW[(~buffer_value) & 0x0F] : LUTB[buffer_value & 0x0F];
GPIO.out_w1ts = this->pin_lut_[data] | clock;
GPIO.out_w1tc = data_mask | clock;
for (int j = 0, jm = (this->get_width_internal() / 8) - 1; j < jm; j++) {
buffer_value = *(buffer_ptr--);
data = LUTB[(buffer_value >> 4) & 0x0F];
send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) |
(((data & 0b11100000) >> 5) << 25) | clock;
GPIO.out_w1ts = send;
GPIO.out_w1tc = send;
data = LUTB[buffer_value & 0x0F];
send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) |
(((data & 0b11100000) >> 5) << 25) | clock;
GPIO.out_w1ts = send;
GPIO.out_w1tc = send;
data = this->model_ == INKPLATE_6_PLUS ? LUTW[(~buffer_value >> 4) & 0x0F] : LUTB[(buffer_value >> 4) & 0x0F];
GPIO.out_w1ts = this->pin_lut_[data] | clock;
GPIO.out_w1tc = data_mask | clock;
data = this->model_ == INKPLATE_6_PLUS ? LUTW[(~buffer_value) & 0x0F] : LUTB[buffer_value & 0x0F];
GPIO.out_w1ts = this->pin_lut_[data] | clock;
GPIO.out_w1tc = data_mask | clock;
}
GPIO.out_w1ts = send;
GPIO.out_w1tc = get_data_pin_mask_() | clock;
GPIO.out_w1ts = clock;
GPIO.out_w1tc = data_mask | clock;
vscan_end_();
}
delayMicroseconds(230);
}
ESP_LOGV(TAG, "Display1b first loop x %d (%ums)", 3, millis() - start_time);
ESP_LOGV(TAG, "Display1b first loop x %d (%ums)", 4, millis() - start_time);
buffer_ptr = &this->buffer_[this->get_buffer_length_() - 1];
vscan_start_();
for (int i = 0; i < this->get_height_internal(); i++) {
for (int i = 0, im = this->get_height_internal(); i < im; i++) {
buffer_value = *(buffer_ptr--);
data = LUT2[(buffer_value >> 4) & 0x0F];
send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) |
(((data & 0b11100000) >> 5) << 25);
hscan_start_(send);
data = LUT2[buffer_value & 0x0F];
send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) |
(((data & 0b11100000) >> 5) << 25) | clock;
GPIO.out_w1ts = send;
GPIO.out_w1tc = send;
data = this->model_ == INKPLATE_6_PLUS ? LUTB[(buffer_value >> 4) & 0x0F] : LUT2[(buffer_value >> 4) & 0x0F];
hscan_start_(this->pin_lut_[data] | clock);
data = this->model_ == INKPLATE_6_PLUS ? LUTB[buffer_value & 0x0F] : LUT2[buffer_value & 0x0F];
GPIO.out_w1ts = this->pin_lut_[data] | clock;
GPIO.out_w1tc = data_mask | clock;
for (int j = 0, jm = (this->get_width_internal() / 8) - 1; j < jm; j++) {
buffer_value = *(buffer_ptr--);
data = LUT2[(buffer_value >> 4) & 0x0F];
send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) |
(((data & 0b11100000) >> 5) << 25) | clock;
GPIO.out_w1ts = send;
GPIO.out_w1tc = send;
data = LUT2[buffer_value & 0x0F];
send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) |
(((data & 0b11100000) >> 5) << 25) | clock;
GPIO.out_w1ts = send;
GPIO.out_w1tc = send;
data = this->model_ == INKPLATE_6_PLUS ? LUTB[(buffer_value >> 4) & 0x0F] : LUT2[(buffer_value >> 4) & 0x0F];
GPIO.out_w1ts = this->pin_lut_[data] | clock;
GPIO.out_w1tc = data_mask | clock;
data = this->model_ == INKPLATE_6_PLUS ? LUTB[buffer_value & 0x0F] : LUT2[buffer_value & 0x0F];
GPIO.out_w1ts = this->pin_lut_[data] | clock;
GPIO.out_w1tc = data_mask | clock;
}
GPIO.out_w1ts = send;
GPIO.out_w1tc = get_data_pin_mask_() | clock;
GPIO.out_w1ts = clock;
GPIO.out_w1tc = data_mask | clock;
vscan_end_();
}
delayMicroseconds(230);
ESP_LOGV(TAG, "Display1b second loop (%ums)", millis() - start_time);
vscan_start_();
for (int i = 0; i < this->get_height_internal(); i++) {
data = 0b00000000;
send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) |
(((data & 0b11100000) >> 5) << 25);
hscan_start_(send);
send |= clock;
GPIO.out_w1ts = send;
GPIO.out_w1tc = send;
for (int j = 0; j < (this->get_width_internal() / 8) - 1; j++) {
GPIO.out_w1ts = send;
GPIO.out_w1tc = send;
GPIO.out_w1ts = send;
GPIO.out_w1tc = send;
if (this->model_ == INKPLATE_6_PLUS) {
clean_fast_(2, 2);
clean_fast_(3, 1);
} else {
uint32_t send = this->pin_lut_[0];
vscan_start_();
for (int i = 0, im = this->get_height_internal(); i < im; i++) {
hscan_start_(send);
GPIO.out_w1ts = send | clock;
GPIO.out_w1tc = data_mask | clock;
for (int j = 0, jm = (this->get_width_internal() / 8) - 1; j < jm; j++) {
GPIO.out_w1ts = send | clock;
GPIO.out_w1tc = data_mask | clock;
GPIO.out_w1ts = send | clock;
GPIO.out_w1tc = data_mask | clock;
}
GPIO.out_w1ts = send | clock;
GPIO.out_w1tc = data_mask | clock;
vscan_end_();
}
GPIO.out_w1ts = clock;
GPIO.out_w1tc = get_data_pin_mask_() | clock;
vscan_end_();
delayMicroseconds(230);
ESP_LOGV(TAG, "Display1b third loop (%ums)", millis() - start_time);
}
delayMicroseconds(230);
ESP_LOGV(TAG, "Display1b third loop (%ums)", millis() - start_time);
vscan_start_();
eink_off_();
this->block_partial_ = false;
this->partial_updates_ = 0;
ESP_LOGV(TAG, "Display1b finished (%ums)", millis() - start_time);
}
void Inkplate6::display3b_() {
ESP_LOGV(TAG, "Display3b called");
uint32_t start_time = millis();
eink_on_();
clean_fast_(0, 1);
clean_fast_(1, 12);
clean_fast_(2, 1);
clean_fast_(0, 11);
clean_fast_(2, 1);
clean_fast_(1, 12);
clean_fast_(2, 1);
clean_fast_(0, 11);
if (this->model_ == INKPLATE_6_PLUS) {
clean_fast_(0, 1);
clean_fast_(1, 15);
clean_fast_(2, 1);
clean_fast_(0, 5);
clean_fast_(2, 1);
clean_fast_(1, 15);
} else {
clean_fast_(0, 1);
clean_fast_(1, 21);
clean_fast_(2, 1);
clean_fast_(0, 12);
clean_fast_(2, 1);
clean_fast_(1, 21);
clean_fast_(2, 1);
clean_fast_(0, 12);
}
uint32_t clock = (1 << this->cl_pin_->get_pin());
for (int k = 0; k < 8; k++) {
const uint8_t *buffer_ptr = &this->buffer_[this->get_buffer_length_() - 1];
uint32_t send;
uint8_t pix1;
uint8_t pix2;
uint8_t pix3;
uint8_t pix4;
uint8_t pixel;
uint8_t pixel2;
uint32_t data_mask = this->get_data_pin_mask_();
uint32_t pos;
uint32_t data;
uint8_t glut_size = this->model_ == INKPLATE_6_PLUS ? 9 : 8;
for (int k = 0; k < glut_size; k++) {
pos = this->get_buffer_length_();
vscan_start_();
for (int i = 0; i < this->get_height_internal(); i++) {
pix1 = (*buffer_ptr--);
pix2 = (*buffer_ptr--);
pix3 = (*buffer_ptr--);
pix4 = (*buffer_ptr--);
pixel = (waveform3Bit[pix1 & 0x07][k] << 6) | (waveform3Bit[(pix1 >> 4) & 0x07][k] << 4) |
(waveform3Bit[pix2 & 0x07][k] << 2) | (waveform3Bit[(pix2 >> 4) & 0x07][k] << 0);
pixel2 = (waveform3Bit[pix3 & 0x07][k] << 6) | (waveform3Bit[(pix3 >> 4) & 0x07][k] << 4) |
(waveform3Bit[pix4 & 0x07][k] << 2) | (waveform3Bit[(pix4 >> 4) & 0x07][k] << 0);
data = this->glut2_[k * 256 + this->buffer_[--pos]];
data |= this->glut_[k * 256 + this->buffer_[--pos]];
hscan_start_(data);
data = this->glut2_[k * 256 + this->buffer_[--pos]];
data |= this->glut_[k * 256 + this->buffer_[--pos]];
GPIO.out_w1ts = data | clock;
GPIO.out_w1tc = data_mask | clock;
send = ((pixel & 0b00000011) << 4) | (((pixel & 0b00001100) >> 2) << 18) | (((pixel & 0b00010000) >> 4) << 23) |
(((pixel & 0b11100000) >> 5) << 25);
hscan_start_(send);
send = ((pixel2 & 0b00000011) << 4) | (((pixel2 & 0b00001100) >> 2) << 18) |
(((pixel2 & 0b00010000) >> 4) << 23) | (((pixel2 & 0b11100000) >> 5) << 25) | clock;
GPIO.out_w1ts = send;
GPIO.out_w1tc = send;
for (int j = 0, jm = (this->get_width_internal() / 8) - 1; j < jm; j++) {
pix1 = (*buffer_ptr--);
pix2 = (*buffer_ptr--);
pix3 = (*buffer_ptr--);
pix4 = (*buffer_ptr--);
pixel = (waveform3Bit[pix1 & 0x07][k] << 6) | (waveform3Bit[(pix1 >> 4) & 0x07][k] << 4) |
(waveform3Bit[pix2 & 0x07][k] << 2) | (waveform3Bit[(pix2 >> 4) & 0x07][k] << 0);
pixel2 = (waveform3Bit[pix3 & 0x07][k] << 6) | (waveform3Bit[(pix3 >> 4) & 0x07][k] << 4) |
(waveform3Bit[pix4 & 0x07][k] << 2) | (waveform3Bit[(pix4 >> 4) & 0x07][k] << 0);
send = ((pixel & 0b00000011) << 4) | (((pixel & 0b00001100) >> 2) << 18) | (((pixel & 0b00010000) >> 4) << 23) |
(((pixel & 0b11100000) >> 5) << 25) | clock;
GPIO.out_w1ts = send;
GPIO.out_w1tc = send;
send = ((pixel2 & 0b00000011) << 4) | (((pixel2 & 0b00001100) >> 2) << 18) |
(((pixel2 & 0b00010000) >> 4) << 23) | (((pixel2 & 0b11100000) >> 5) << 25) | clock;
GPIO.out_w1ts = send;
GPIO.out_w1tc = send;
for (int j = 0; j < (this->get_width_internal() / 8) - 1; j++) {
data = this->glut2_[k * 256 + this->buffer_[--pos]];
data |= this->glut_[k * 256 + this->buffer_[--pos]];
GPIO.out_w1ts = data | clock;
GPIO.out_w1tc = data_mask | clock;
data = this->glut2_[k * 256 + this->buffer_[--pos]];
data |= this->glut_[k * 256 + this->buffer_[--pos]];
GPIO.out_w1ts = data | clock;
GPIO.out_w1tc = data_mask | clock;
}
GPIO.out_w1ts = send;
GPIO.out_w1tc = get_data_pin_mask_() | clock;
GPIO.out_w1ts = clock;
GPIO.out_w1tc = data_mask | clock;
vscan_end_();
}
delayMicroseconds(230);
}
clean_fast_(2, 1);
clean_fast_(3, 1);
vscan_start_();
eink_off_();
ESP_LOGV(TAG, "Display3b finished (%ums)", millis() - start_time);
}
bool Inkplate6::partial_update_() {
ESP_LOGV(TAG, "Partial update called");
uint32_t start_time = millis();
@ -432,16 +501,15 @@ bool Inkplate6::partial_update_() {
this->partial_updates_++;
uint16_t pos = this->get_buffer_length_() - 1;
uint32_t send;
uint32_t pos = this->get_buffer_length_() - 1;
uint8_t data;
uint8_t diffw, diffb;
uint32_t n = (this->get_buffer_length_() * 2) - 1;
for (int i = 0, im = this->get_height_internal(); i < im; i++) {
for (int j = 0, jm = (this->get_width_internal() / 8); j < jm; j++) {
diffw = (this->buffer_[pos] ^ this->partial_buffer_[pos]) & ~(this->partial_buffer_[pos]);
diffb = (this->buffer_[pos] ^ this->partial_buffer_[pos]) & this->partial_buffer_[pos];
diffw = this->buffer_[pos] & ~(this->partial_buffer_[pos]);
diffb = ~(this->buffer_[pos]) & this->partial_buffer_[pos];
pos--;
this->partial_buffer_2_[n--] = LUTW[diffw >> 4] & LUTB[diffb >> 4];
this->partial_buffer_2_[n--] = LUTW[diffw & 0x0F] & LUTB[diffb & 0x0F];
@ -451,23 +519,20 @@ bool Inkplate6::partial_update_() {
eink_on_();
uint32_t clock = (1 << this->cl_pin_->get_pin());
uint32_t data_mask = this->get_data_pin_mask_();
for (int k = 0; k < 5; k++) {
vscan_start_();
const uint8_t *data_ptr = &this->partial_buffer_2_[(this->get_buffer_length_() * 2) - 1];
for (int i = 0; i < this->get_height_internal(); i++) {
data = *(data_ptr--);
send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) |
(((data & 0b11100000) >> 5) << 25);
hscan_start_(send);
hscan_start_(this->pin_lut_[data]);
for (int j = 0, jm = (this->get_width_internal() / 4) - 1; j < jm; j++) {
data = *(data_ptr--);
send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) |
(((data & 0b11100000) >> 5) << 25) | clock;
GPIO.out_w1ts = send;
GPIO.out_w1tc = send;
GPIO.out_w1ts = this->pin_lut_[data] | clock;
GPIO.out_w1tc = data_mask | clock;
}
GPIO.out_w1ts = send;
GPIO.out_w1tc = get_data_pin_mask_() | clock;
GPIO.out_w1ts = clock;
GPIO.out_w1tc = data_mask | clock;
vscan_end_();
}
delayMicroseconds(230);
@ -482,6 +547,7 @@ bool Inkplate6::partial_update_() {
ESP_LOGV(TAG, "Partial update finished (%ums)", millis() - start_time);
return true;
}
void Inkplate6::vscan_start_() {
this->ckv_pin_->digital_write(true);
delayMicroseconds(7);
@ -505,30 +571,23 @@ void Inkplate6::vscan_start_() {
delayMicroseconds(0);
this->ckv_pin_->digital_write(true);
}
void Inkplate6::vscan_write_() {
this->ckv_pin_->digital_write(false);
this->le_pin_->digital_write(true);
this->le_pin_->digital_write(false);
delayMicroseconds(0);
void Inkplate6::hscan_start_(uint32_t d) {
uint8_t clock = (1 << this->cl_pin_->get_pin());
this->sph_pin_->digital_write(false);
this->cl_pin_->digital_write(true);
this->cl_pin_->digital_write(false);
GPIO.out_w1ts = d | clock;
GPIO.out_w1tc = this->get_data_pin_mask_() | clock;
this->sph_pin_->digital_write(true);
this->ckv_pin_->digital_write(true);
}
void Inkplate6::hscan_start_(uint32_t d) {
this->sph_pin_->digital_write(false);
GPIO.out_w1ts = (d) | (1 << this->cl_pin_->get_pin());
GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin());
this->sph_pin_->digital_write(true);
}
void Inkplate6::vscan_end_() {
this->ckv_pin_->digital_write(false);
this->le_pin_->digital_write(true);
this->le_pin_->digital_write(false);
delayMicroseconds(1);
this->ckv_pin_->digital_write(true);
delayMicroseconds(0);
}
void Inkplate6::clean() {
ESP_LOGV(TAG, "Clean called");
uint32_t start_time = millis();
@ -542,20 +601,22 @@ void Inkplate6::clean() {
clean_fast_(1, 10); // White to White
ESP_LOGV(TAG, "Clean finished (%ums)", millis() - start_time);
}
void Inkplate6::clean_fast_(uint8_t c, uint8_t rep) {
ESP_LOGV(TAG, "Clean fast called with: (%d, %d)", c, rep);
uint32_t start_time = millis();
eink_on_();
uint8_t data = 0;
if (c == 0) // White
if (c == 0) { // White
data = 0b10101010;
else if (c == 1) // Black
} else if (c == 1) { // Black
data = 0b01010101;
else if (c == 2) // Discharge
} else if (c == 2) { // Discharge
data = 0b00000000;
else if (c == 3) // Skip
} else if (c == 3) { // Skip
data = 0b11111111;
}
uint32_t send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) |
(((data & 0b11100000) >> 5) << 25);
@ -567,14 +628,14 @@ void Inkplate6::clean_fast_(uint8_t c, uint8_t rep) {
hscan_start_(send);
GPIO.out_w1ts = send | clock;
GPIO.out_w1tc = clock;
for (int j = 0, jm = this->get_width_internal() / 8; j < jm; j++) {
for (int j = 0; j < (this->get_width_internal() / 8) - 1; j++) {
GPIO.out_w1ts = clock;
GPIO.out_w1tc = clock;
GPIO.out_w1ts = clock;
GPIO.out_w1tc = clock;
}
GPIO.out_w1ts = clock;
GPIO.out_w1tc = get_data_pin_mask_() | clock;
GPIO.out_w1ts = send | clock;
GPIO.out_w1tc = clock;
vscan_end_();
}
delayMicroseconds(230);
@ -582,7 +643,10 @@ void Inkplate6::clean_fast_(uint8_t c, uint8_t rep) {
}
ESP_LOGV(TAG, "Clean fast finished (%ums)", millis() - start_time);
}
void Inkplate6::pins_z_state_() {
this->cl_pin_->pin_mode(gpio::FLAG_INPUT);
this->le_pin_->pin_mode(gpio::FLAG_INPUT);
this->ckv_pin_->pin_mode(gpio::FLAG_INPUT);
this->sph_pin_->pin_mode(gpio::FLAG_INPUT);
@ -599,7 +663,10 @@ void Inkplate6::pins_z_state_() {
this->display_data_6_pin_->pin_mode(gpio::FLAG_INPUT);
this->display_data_7_pin_->pin_mode(gpio::FLAG_INPUT);
}
void Inkplate6::pins_as_outputs_() {
this->cl_pin_->pin_mode(gpio::FLAG_OUTPUT);
this->le_pin_->pin_mode(gpio::FLAG_OUTPUT);
this->ckv_pin_->pin_mode(gpio::FLAG_OUTPUT);
this->sph_pin_->pin_mode(gpio::FLAG_OUTPUT);

View File

@ -13,32 +13,28 @@ namespace inkplate6 {
enum InkplateModel : uint8_t {
INKPLATE_6 = 0,
INKPLATE_10 = 1,
INKPLATE_6_PLUS = 2,
};
class Inkplate6 : public PollingComponent, public display::DisplayBuffer, public i2c::I2CDevice {
public:
const uint8_t LUT2[16] = {0b10101010, 0b10101001, 0b10100110, 0b10100101, 0b10011010, 0b10011001,
0b10010110, 0b10010101, 0b01101010, 0b01101001, 0b01100110, 0b01100101,
0b01011010, 0b01011001, 0b01010110, 0b01010101};
const uint8_t LUTW[16] = {0b11111111, 0b11111110, 0b11111011, 0b11111010, 0b11101111, 0b11101110,
0b11101011, 0b11101010, 0b10111111, 0b10111110, 0b10111011, 0b10111010,
0b10101111, 0b10101110, 0b10101011, 0b10101010};
const uint8_t LUTB[16] = {0b11111111, 0b11111101, 0b11110111, 0b11110101, 0b11011111, 0b11011101,
0b11010111, 0b11010101, 0b01111111, 0b01111101, 0b01110111, 0b01110101,
0b01011111, 0b01011101, 0b01010111, 0b01010101};
const uint8_t pixelMaskLUT[8] = {0b00000001, 0b00000010, 0b00000100, 0b00001000,
0b00010000, 0b00100000, 0b01000000, 0b10000000};
const uint8_t pixelMaskGLUT[2] = {0b00001111, 0b11110000};
const uint8_t waveform3Bit[8][8] = {{0, 0, 0, 0, 1, 1, 1, 0}, {1, 2, 2, 2, 1, 1, 1, 0}, {0, 1, 2, 1, 1, 2, 1, 0},
{0, 2, 1, 2, 1, 2, 1, 0}, {0, 0, 0, 1, 1, 1, 2, 0}, {2, 1, 1, 1, 2, 1, 2, 0},
const uint8_t LUT2[16] = {0xAA, 0xA9, 0xA6, 0xA5, 0x9A, 0x99, 0x96, 0x95,
0x6A, 0x69, 0x66, 0x65, 0x5A, 0x59, 0x56, 0x55};
const uint8_t LUTW[16] = {0xFF, 0xFE, 0xFB, 0xFA, 0xEF, 0xEE, 0xEB, 0xEA,
0xBF, 0xBE, 0xBB, 0xBA, 0xAF, 0xAE, 0xAB, 0xAA};
const uint8_t LUTB[16] = {0xFF, 0xFD, 0xF7, 0xF5, 0xDF, 0xDD, 0xD7, 0xD5,
0x7F, 0x7D, 0x77, 0x75, 0x5F, 0x5D, 0x57, 0x55};
const uint8_t pixelMaskLUT[8] = {0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80};
const uint8_t pixelMaskGLUT[2] = {0x0F, 0xF0};
const uint8_t waveform3Bit[8][8] = {{0, 1, 1, 0, 0, 1, 1, 0}, {0, 1, 2, 1, 1, 2, 1, 0}, {1, 1, 1, 2, 2, 1, 0, 0},
{0, 0, 0, 1, 1, 1, 2, 0}, {2, 1, 1, 1, 2, 1, 2, 0}, {2, 2, 1, 1, 2, 1, 2, 0},
{1, 1, 1, 2, 1, 2, 2, 0}, {0, 0, 0, 0, 0, 0, 2, 0}};
const uint32_t waveform[50] = {
0x00000008, 0x00000008, 0x00200408, 0x80281888, 0x60a81898, 0x60a8a8a8, 0x60a8a8a8, 0x6068a868, 0x6868a868,
0x6868a868, 0x68686868, 0x6a686868, 0x5a686868, 0x5a686868, 0x5a586a68, 0x5a5a6a68, 0x5a5a6a68, 0x55566a68,
0x55565a64, 0x55555654, 0x55555556, 0x55555556, 0x55555556, 0x55555516, 0x55555596, 0x15555595, 0x95955595,
0x95959595, 0x95949495, 0x94949495, 0x94949495, 0xa4949494, 0x9494a4a4, 0x84a49494, 0x84948484, 0x84848484,
0x84848484, 0x84848484, 0xa5a48484, 0xa9a4a4a8, 0xa9a8a8a8, 0xa5a9a9a4, 0xa5a5a5a4, 0xa1a5a5a1, 0xa9a9a9a9,
0xa9a9a9a9, 0xa9a9a9a9, 0xa9a9a9a9, 0x15151515, 0x11111111};
const uint8_t waveform3Bit6Plus[8][9] = {{0, 0, 0, 0, 0, 2, 1, 1, 0}, {0, 0, 2, 1, 1, 1, 2, 1, 0},
{0, 2, 2, 2, 1, 1, 2, 1, 0}, {0, 0, 2, 2, 2, 1, 2, 1, 0},
{0, 0, 0, 0, 2, 2, 2, 1, 0}, {0, 0, 2, 1, 2, 1, 1, 2, 0},
{0, 0, 2, 2, 2, 1, 1, 2, 0}, {0, 0, 0, 0, 2, 2, 2, 2, 0}};
void set_greyscale(bool greyscale) {
this->greyscale_ = greyscale;
@ -88,6 +84,8 @@ class Inkplate6 : public PollingComponent, public display::DisplayBuffer, public
bool get_partial_updating() { return this->partial_updating_; }
uint8_t get_temperature() { return this->temperature_; }
void block_partial() { this->block_partial_ = true; }
protected:
void draw_absolute_pixel_internal(int x, int y, Color color) override;
void display1b_();
@ -99,28 +97,34 @@ class Inkplate6 : public PollingComponent, public display::DisplayBuffer, public
void hscan_start_(uint32_t d);
void vscan_end_();
void vscan_start_();
void vscan_write_();
void eink_off_();
void eink_on_();
bool read_power_status_();
void setup_pins_();
void pins_z_state_();
void pins_as_outputs_();
int get_width_internal() override {
if (this->model_ == INKPLATE_6)
if (this->model_ == INKPLATE_6) {
return 800;
else if (this->model_ == INKPLATE_10)
} else if (this->model_ == INKPLATE_10) {
return 1200;
} else if (this->model_ == INKPLATE_6_PLUS) {
return 1024;
}
return 0;
}
int get_height_internal() override {
if (this->model_ == INKPLATE_6)
if (this->model_ == INKPLATE_6) {
return 600;
else if (this->model_ == INKPLATE_10)
} else if (this->model_ == INKPLATE_10) {
return 825;
} else if (this->model_ == INKPLATE_6_PLUS) {
return 758;
}
return 0;
}
@ -139,16 +143,20 @@ class Inkplate6 : public PollingComponent, public display::DisplayBuffer, public
return data;
}
uint8_t panel_on_ = 0;
bool panel_on_{false};
uint8_t temperature_;
uint8_t *partial_buffer_{nullptr};
uint8_t *partial_buffer_2_{nullptr};
uint32_t *glut_{nullptr};
uint32_t *glut2_{nullptr};
uint32_t pin_lut_[256];
uint32_t full_update_every_;
uint32_t partial_updates_{0};
bool block_partial_;
bool block_partial_{true};
bool greyscale_;
bool partial_updating_;

View File

@ -28,7 +28,7 @@ void KalmanCombinatorComponent::add_source(Sensor *sensor, std::function<float(f
}
void KalmanCombinatorComponent::add_source(Sensor *sensor, float stddev) {
this->add_source(sensor, std::function<float(float)>{[stddev](float) -> float { return stddev; }});
this->add_source(sensor, std::function<float(float)>{[stddev](float x) -> float { return stddev; }});
}
void KalmanCombinatorComponent::update_variance_() {

View File

@ -13,12 +13,12 @@ DEPENDENCIES = ["esp32"]
def calc_max_frequency(bit_depth):
return 80e6 / (2 ** bit_depth)
return 80e6 / (2**bit_depth)
def calc_min_frequency(bit_depth):
max_div_num = ((2 ** 20) - 1) / 256.0
return 80e6 / (max_div_num * (2 ** bit_depth))
max_div_num = ((2**20) - 1) / 256.0
return 80e6 / (max_div_num * (2**bit_depth))
def validate_frequency(value):

View File

@ -102,21 +102,24 @@ class RandomLightEffect : public LightEffect {
class LambdaLightEffect : public LightEffect {
public:
LambdaLightEffect(const std::string &name, std::function<void()> f, uint32_t update_interval)
LambdaLightEffect(const std::string &name, std::function<void(bool initial_run)> f, uint32_t update_interval)
: LightEffect(name), f_(std::move(f)), update_interval_(update_interval) {}
void start() override { this->initial_run_ = true; }
void apply() override {
const uint32_t now = millis();
if (now - this->last_run_ >= this->update_interval_) {
this->last_run_ = now;
this->f_();
this->f_(this->initial_run_);
this->initial_run_ = false;
}
}
protected:
std::function<void()> f_;
std::function<void(bool initial_run)> f_;
uint32_t update_interval_;
uint32_t last_run_{0};
bool initial_run_;
};
class AutomationLightEffect : public LightEffect {

View File

@ -141,7 +141,9 @@ def register_addressable_effect(
},
)
async def lambda_effect_to_code(config, effect_id):
lambda_ = await cg.process_lambda(config[CONF_LAMBDA], [], return_type=cg.void)
lambda_ = await cg.process_lambda(
config[CONF_LAMBDA], [(bool, "initial_run")], return_type=cg.void
)
return cg.new_Pvariable(
effect_id, config[CONF_NAME], lambda_, config[CONF_UPDATE_INTERVAL]
)

View File

@ -98,10 +98,11 @@ void LightCall::perform() {
// EFFECT
auto effect = this->effect_;
const char *effect_s;
if (effect == 0u)
if (effect == 0u) {
effect_s = "None";
else
} else {
effect_s = this->parent_->effects_[*this->effect_ - 1]->get_name().c_str();
}
if (this->publish_) {
ESP_LOGD(TAG, " Effect: '%s'", effect_s);
@ -445,9 +446,10 @@ std::set<ColorMode> LightCall::get_suitable_color_modes_() {
};
auto key = KEY(has_white, has_ct, has_cwww, has_rgb);
for (auto &item : lookup_table)
for (auto &item : lookup_table) {
if (std::get<0>(item) == key)
return std::get<1>(item);
}
// This happens if there are conflicting flags given.
return {};

View File

@ -145,10 +145,11 @@ void LightState::publish_state() { this->remote_values_callback_.call(); }
LightOutput *LightState::get_output() const { return this->output_; }
std::string LightState::get_effect_name() {
if (this->active_effect_index_ > 0)
if (this->active_effect_index_ > 0) {
return this->effects_[this->active_effect_index_ - 1]->get_name();
else
} else {
return "None";
}
}
void LightState::add_new_remote_values_callback(std::function<void()> &&send_callback) {
@ -219,10 +220,11 @@ void LightState::start_effect_(uint32_t effect_index) {
effect->start_internal();
}
LightEffect *LightState::get_active_effect_() {
if (this->active_effect_index_ == 0)
if (this->active_effect_index_ == 0) {
return nullptr;
else
} else {
return this->effects_[this->active_effect_index_ - 1];
}
}
void LightState::stop_effect_() {
auto *effect = this->get_active_effect_();

View File

@ -0,0 +1,3 @@
import esphome.codegen as cg
lilygo_t5_47_ns = cg.esphome_ns.namespace("lilygo_t5_47")

View File

@ -0,0 +1,45 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins
from esphome.components import i2c, touchscreen
from esphome.const import CONF_ID
from .. import lilygo_t5_47_ns
CODEOWNERS = ["@jesserockz"]
DEPENDENCIES = ["i2c"]
LilygoT547Touchscreen = lilygo_t5_47_ns.class_(
"LilygoT547Touchscreen",
touchscreen.Touchscreen,
cg.Component,
i2c.I2CDevice,
)
CONF_LILYGO_T5_47_TOUCHSCREEN_ID = "lilygo_t5_47_touchscreen_id"
CONF_INTERRUPT_PIN = "interrupt_pin"
CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(LilygoT547Touchscreen),
cv.Required(CONF_INTERRUPT_PIN): cv.All(
pins.internal_gpio_input_pin_schema
),
}
)
.extend(i2c.i2c_device_schema(0x5A))
.extend(cv.COMPONENT_SCHEMA)
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
await touchscreen.register_touchscreen(var, config)
interrupt_pin = await cg.gpio_pin_expression(config[CONF_INTERRUPT_PIN])
cg.add(var.set_interrupt_pin(interrupt_pin))

View File

@ -0,0 +1,141 @@
#include "lilygo_t5_47_touchscreen.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace lilygo_t5_47 {
static const char *const TAG = "lilygo_t5_47.touchscreen";
static const uint8_t POWER_REGISTER = 0xD6;
static const uint8_t TOUCH_REGISTER = 0xD0;
static const uint8_t WAKEUP_CMD[1] = {0x06};
static const uint8_t READ_FLAGS[1] = {0x00};
static const uint8_t CLEAR_FLAGS[2] = {0x00, 0xAB};
static const uint8_t READ_TOUCH[1] = {0x07};
#define ERROR_CHECK(err) \
if ((err) != i2c::ERROR_OK) { \
ESP_LOGE(TAG, "Failed to communicate!"); \
this->status_set_warning(); \
return; \
}
void Store::gpio_intr(Store *store) { store->touch = true; }
void LilygoT547Touchscreen::setup() {
ESP_LOGCONFIG(TAG, "Setting up Lilygo T5 4.7 Touchscreen...");
this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
this->interrupt_pin_->setup();
this->store_.pin = this->interrupt_pin_->to_isr();
this->interrupt_pin_->attach_interrupt(Store::gpio_intr, &this->store_, gpio::INTERRUPT_FALLING_EDGE);
if (this->write(nullptr, 0) != i2c::ERROR_OK) {
ESP_LOGE(TAG, "Failed to communicate!");
this->interrupt_pin_->detach_interrupt();
this->mark_failed();
return;
}
this->write_register(POWER_REGISTER, WAKEUP_CMD, 1);
}
void LilygoT547Touchscreen::loop() {
if (!this->store_.touch) {
for (auto *listener : this->touch_listeners_)
listener->release();
return;
}
this->store_.touch = false;
uint8_t point = 0;
uint8_t buffer[40] = {0};
uint32_t sum_l = 0, sum_h = 0;
i2c::ErrorCode err;
err = this->write_register(TOUCH_REGISTER, READ_FLAGS, 1);
ERROR_CHECK(err);
err = this->read(buffer, 7);
ERROR_CHECK(err);
if (buffer[0] == 0xAB) {
this->write_register(TOUCH_REGISTER, CLEAR_FLAGS, 2);
return;
}
point = buffer[5] & 0xF;
if (point == 0) {
for (auto *listener : this->touch_listeners_)
listener->release();
return;
} else if (point == 1) {
err = this->write_register(TOUCH_REGISTER, READ_TOUCH, 1);
ERROR_CHECK(err);
err = this->read(&buffer[5], 2);
ERROR_CHECK(err);
sum_l = buffer[5] << 8 | buffer[6];
} else if (point > 1) {
err = this->write_register(TOUCH_REGISTER, READ_TOUCH, 1);
ERROR_CHECK(err);
err = this->read(&buffer[5], 5 * (point - 1) + 3);
ERROR_CHECK(err);
sum_l = buffer[5 * point + 1] << 8 | buffer[5 * point + 2];
}
this->write_register(TOUCH_REGISTER, CLEAR_FLAGS, 2);
for (int i = 0; i < 5 * point; i++)
sum_h += buffer[i];
if (sum_l != sum_h)
point = 0;
if (point) {
uint8_t offset;
for (int i = 0; i < point; i++) {
if (i == 0) {
offset = 0;
} else {
offset = 4;
}
TouchPoint tp;
tp.id = (buffer[i * 5 + offset] >> 4) & 0x0F;
tp.state = buffer[i * 5 + offset] & 0x0F;
if (tp.state == 0x06)
tp.state = 0x07;
tp.y = (uint16_t)((buffer[i * 5 + 1 + offset] << 4) | ((buffer[i * 5 + 3 + offset] >> 4) & 0x0F));
tp.x = (uint16_t)((buffer[i * 5 + 2 + offset] << 4) | (buffer[i * 5 + 3 + offset] & 0x0F));
this->defer([this, tp]() { this->send_touch_(tp); });
}
} else {
TouchPoint tp;
tp.id = (buffer[0] >> 4) & 0x0F;
tp.state = 0x06;
tp.y = (uint16_t)((buffer[0 * 5 + 1] << 4) | ((buffer[0 * 5 + 3] >> 4) & 0x0F));
tp.x = (uint16_t)((buffer[0 * 5 + 2] << 4) | (buffer[0 * 5 + 3] & 0x0F));
this->defer([this, tp]() { this->send_touch_(tp); });
}
this->status_clear_warning();
}
void LilygoT547Touchscreen::dump_config() {
ESP_LOGCONFIG(TAG, "Lilygo T5 47 Touchscreen:");
LOG_I2C_DEVICE(this);
LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_);
}
} // namespace lilygo_t5_47
} // namespace esphome

View File

@ -0,0 +1,35 @@
#pragma once
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/touchscreen/touchscreen.h"
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
namespace esphome {
namespace lilygo_t5_47 {
struct Store {
volatile bool touch;
ISRInternalGPIOPin pin;
static void gpio_intr(Store *store);
};
using namespace touchscreen;
class LilygoT547Touchscreen : public Touchscreen, public Component, public i2c::I2CDevice {
public:
void setup() override;
void loop() override;
void dump_config() override;
void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; }
protected:
InternalGPIOPin *interrupt_pin_;
Store store_;
};
} // namespace lilygo_t5_47
} // namespace esphome

View File

@ -0,0 +1,102 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.automation import Condition, maybe_simple_id
from esphome.components import mqtt
from esphome.const import (
CONF_ID,
CONF_ON_LOCK,
CONF_ON_UNLOCK,
CONF_TRIGGER_ID,
CONF_MQTT_ID,
)
from esphome.core import CORE, coroutine_with_priority
from esphome.cpp_helpers import setup_entity
CODEOWNERS = ["@esphome/core"]
IS_PLATFORM_COMPONENT = True
lock_ns = cg.esphome_ns.namespace("lock")
Lock = lock_ns.class_("Lock", cg.EntityBase)
LockPtr = Lock.operator("ptr")
LockCall = lock_ns.class_("LockCall")
UnlockAction = lock_ns.class_("UnlockAction", automation.Action)
LockAction = lock_ns.class_("LockAction", automation.Action)
OpenAction = lock_ns.class_("OpenAction", automation.Action)
LockPublishAction = lock_ns.class_("LockPublishAction", automation.Action)
LockCondition = lock_ns.class_("LockCondition", Condition)
LockLockTrigger = lock_ns.class_("LockLockTrigger", automation.Trigger.template())
LockUnlockTrigger = lock_ns.class_("LockUnlockTrigger", automation.Trigger.template())
LOCK_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
{
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTLockComponent),
cv.Optional(CONF_ON_LOCK): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LockLockTrigger),
}
),
cv.Optional(CONF_ON_UNLOCK): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LockUnlockTrigger),
}
),
}
)
async def setup_lock_core_(var, config):
await setup_entity(var, config)
for conf in config.get(CONF_ON_LOCK, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_UNLOCK, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
if CONF_MQTT_ID in config:
mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var)
await mqtt.register_mqtt_component(mqtt_, config)
async def register_lock(var, config):
if not CORE.has_id(config[CONF_ID]):
var = cg.Pvariable(config[CONF_ID], var)
cg.add(cg.App.register_lock(var))
await setup_lock_core_(var, config)
LOCK_ACTION_SCHEMA = maybe_simple_id(
{
cv.Required(CONF_ID): cv.use_id(Lock),
}
)
@automation.register_action("lock.unlock", UnlockAction, LOCK_ACTION_SCHEMA)
@automation.register_action("lock.lock", LockAction, LOCK_ACTION_SCHEMA)
@automation.register_action("lock.open", OpenAction, LOCK_ACTION_SCHEMA)
async def lock_action_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)
@automation.register_condition("lock.is_locked", LockCondition, LOCK_ACTION_SCHEMA)
async def lock_is_on_to_code(config, condition_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(condition_id, template_arg, paren, True)
@automation.register_condition("lock.is_unlocked", LockCondition, LOCK_ACTION_SCHEMA)
async def lock_is_off_to_code(config, condition_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(condition_id, template_arg, paren, False)
@coroutine_with_priority(100.0)
async def to_code(config):
cg.add_global(lock_ns.using)
cg.add_define("USE_LOCK")

View File

@ -0,0 +1,87 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/automation.h"
#include "esphome/components/lock/lock.h"
namespace esphome {
namespace lock {
template<typename... Ts> class LockAction : public Action<Ts...> {
public:
explicit LockAction(Lock *a_lock) : lock_(a_lock) {}
void play(Ts... x) override { this->lock_->lock(); }
protected:
Lock *lock_;
};
template<typename... Ts> class UnlockAction : public Action<Ts...> {
public:
explicit UnlockAction(Lock *a_lock) : lock_(a_lock) {}
void play(Ts... x) override { this->lock_->unlock(); }
protected:
Lock *lock_;
};
template<typename... Ts> class OpenAction : public Action<Ts...> {
public:
explicit OpenAction(Lock *a_lock) : lock_(a_lock) {}
void play(Ts... x) override { this->lock_->open(); }
protected:
Lock *lock_;
};
template<typename... Ts> class LockCondition : public Condition<Ts...> {
public:
LockCondition(Lock *parent, bool state) : parent_(parent), state_(state) {}
bool check(Ts... x) override {
auto check_state = this->state_ ? LockState::LOCK_STATE_LOCKED : LockState::LOCK_STATE_UNLOCKED;
return this->parent_->state == check_state;
}
protected:
Lock *parent_;
bool state_;
};
class LockLockTrigger : public Trigger<> {
public:
LockLockTrigger(Lock *a_lock) {
a_lock->add_on_state_callback([this, a_lock]() {
if (a_lock->state == LockState::LOCK_STATE_LOCKED) {
this->trigger();
}
});
}
};
class LockUnlockTrigger : public Trigger<> {
public:
LockUnlockTrigger(Lock *a_lock) {
a_lock->add_on_state_callback([this, a_lock]() {
if (a_lock->state == LockState::LOCK_STATE_UNLOCKED) {
this->trigger();
}
});
}
};
template<typename... Ts> class LockPublishAction : public Action<Ts...> {
public:
LockPublishAction(Lock *a_lock) : lock_(a_lock) {}
TEMPLATABLE_VALUE(LockState, state)
void play(Ts... x) override { this->lock_->publish_state(this->state_.value(x...)); }
protected:
Lock *lock_;
};
} // namespace lock
} // namespace esphome

View File

@ -0,0 +1,109 @@
#include "lock.h"
#include "esphome/core/log.h"
namespace esphome {
namespace lock {
static const char *const TAG = "lock";
const char *lock_state_to_string(LockState state) {
switch (state) {
case LOCK_STATE_LOCKED:
return "LOCKED";
case LOCK_STATE_UNLOCKED:
return "UNLOCKED";
case LOCK_STATE_JAMMED:
return "JAMMED";
case LOCK_STATE_LOCKING:
return "LOCKING";
case LOCK_STATE_UNLOCKING:
return "UNLOCKING";
case LOCK_STATE_NONE:
default:
return "UNKNOWN";
}
}
Lock::Lock(const std::string &name) : EntityBase(name), state(LOCK_STATE_NONE) {}
Lock::Lock() : Lock("") {}
LockCall Lock::make_call() { return LockCall(this); }
void Lock::lock() {
auto call = this->make_call();
call.set_state(LOCK_STATE_LOCKED);
this->control(call);
}
void Lock::unlock() {
auto call = this->make_call();
call.set_state(LOCK_STATE_UNLOCKED);
this->control(call);
}
void Lock::open() {
if (traits.get_supports_open()) {
ESP_LOGD(TAG, "'%s' Opening.", this->get_name().c_str());
this->open_latch();
} else {
ESP_LOGW(TAG, "'%s' Does not support Open.", this->get_name().c_str());
}
}
void Lock::publish_state(LockState state) {
if (!this->publish_dedup_.next(state))
return;
this->state = state;
this->rtc_.save(&this->state);
ESP_LOGD(TAG, "'%s': Sending state %s", this->name_.c_str(), lock_state_to_string(state));
this->state_callback_.call();
}
void Lock::add_on_state_callback(std::function<void()> &&callback) { this->state_callback_.add(std::move(callback)); }
uint32_t Lock::hash_base() { return 856245656UL; }
void LockCall::perform() {
ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str());
this->validate_();
if (this->state_.has_value()) {
const char *state_s = lock_state_to_string(*this->state_);
ESP_LOGD(TAG, " State: %s", state_s);
}
this->parent_->control(*this);
}
void LockCall::validate_() {
if (this->state_.has_value()) {
auto state = *this->state_;
if (!this->parent_->traits.supports_state(state)) {
ESP_LOGW(TAG, " State %s is not supported by this device!", lock_state_to_string(*this->state_));
this->state_.reset();
}
}
}
LockCall &LockCall::set_state(LockState state) {
this->state_ = state;
return *this;
}
LockCall &LockCall::set_state(optional<LockState> state) {
this->state_ = state;
return *this;
}
LockCall &LockCall::set_state(const std::string &state) {
if (str_equals_case_insensitive(state, "LOCKED")) {
this->set_state(LOCK_STATE_LOCKED);
} else if (str_equals_case_insensitive(state, "UNLOCKED")) {
this->set_state(LOCK_STATE_UNLOCKED);
} else if (str_equals_case_insensitive(state, "JAMMED")) {
this->set_state(LOCK_STATE_JAMMED);
} else if (str_equals_case_insensitive(state, "LOCKING")) {
this->set_state(LOCK_STATE_LOCKING);
} else if (str_equals_case_insensitive(state, "UNLOCKING")) {
this->set_state(LOCK_STATE_UNLOCKING);
} else if (str_equals_case_insensitive(state, "NONE")) {
this->set_state(LOCK_STATE_NONE);
} else {
ESP_LOGW(TAG, "'%s' - Unrecognized state %s", this->parent_->get_name().c_str(), state.c_str());
}
return *this;
}
const optional<LockState> &LockCall::get_state() const { return this->state_; }
} // namespace lock
} // namespace esphome

View File

@ -0,0 +1,178 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/entity_base.h"
#include "esphome/core/preferences.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include <set>
namespace esphome {
namespace lock {
class Lock;
#define LOG_LOCK(prefix, type, obj) \
if ((obj) != nullptr) { \
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
if (!(obj)->get_icon().empty()) { \
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \
} \
if ((obj)->traits.get_assumed_state()) { \
ESP_LOGCONFIG(TAG, "%s Assumed State: YES", prefix); \
} \
}
/// Enum for all states a lock can be in.
enum LockState : uint8_t {
LOCK_STATE_NONE = 0,
LOCK_STATE_LOCKED = 1,
LOCK_STATE_UNLOCKED = 2,
LOCK_STATE_JAMMED = 3,
LOCK_STATE_LOCKING = 4,
LOCK_STATE_UNLOCKING = 5
};
const char *lock_state_to_string(LockState state);
class LockTraits {
public:
LockTraits() = default;
bool get_supports_open() const { return this->supports_open_; }
void set_supports_open(bool supports_open) { this->supports_open_ = supports_open; }
bool get_requires_code() const { return this->requires_code_; }
void set_requires_code(bool requires_code) { this->requires_code_ = requires_code; }
bool get_assumed_state() const { return this->assumed_state_; }
void set_assumed_state(bool assumed_state) { this->assumed_state_ = assumed_state; }
bool supports_state(LockState state) const { return supported_states_.count(state); }
std::set<LockState> get_supported_states() const { return supported_states_; }
void set_supported_states(std::set<LockState> states) { supported_states_ = std::move(states); }
void add_supported_state(LockState state) { supported_states_.insert(state); }
protected:
bool supports_open_{false};
bool requires_code_{false};
bool assumed_state_{false};
std::set<LockState> supported_states_ = {LOCK_STATE_NONE, LOCK_STATE_LOCKED, LOCK_STATE_UNLOCKED};
};
/** This class is used to encode all control actions on a lock device.
*
* It is supposed to be used by all code that wishes to control a lock device (mqtt, api, lambda etc).
* Create an instance of this class by calling `id(lock_device).make_call();`. Then set all attributes
* with the `set_x` methods. Finally, to apply the changes call `.perform();`.
*
* The integration that implements the lock device receives this instance with the `control` method.
* It should check all the properties it implements and apply them as needed. It should do so by
* getting all properties it controls with the getter methods in this class. If the optional value is
* set (check with `.has_value()`) that means the user wants to control this property. Get the value
* of the optional with the star operator (`*call.get_state()`) and apply it.
*/
class LockCall {
public:
LockCall(Lock *parent) : parent_(parent) {}
/// Set the state of the lock device.
LockCall &set_state(LockState state);
/// Set the state of the lock device.
LockCall &set_state(optional<LockState> state);
/// Set the state of the lock device based on a string.
LockCall &set_state(const std::string &state);
void perform();
const optional<LockState> &get_state() const;
protected:
void validate_();
Lock *const parent_;
optional<LockState> state_;
};
/** Base class for all locks.
*
* A lock is basically a switch with a combination of a binary sensor (for reporting lock values)
* and a write_state method that writes a state to the hardware. Locks can also have an "open"
* method to unlatch.
*
* For integrations: Integrations must implement the method control().
* Control will be called with the arguments supplied by the user and should be used
* to control all values of the lock.
*/
class Lock : public EntityBase {
public:
explicit Lock();
explicit Lock(const std::string &name);
/** Make a lock device control call, this is used to control the lock device, see the LockCall description
* for more info.
* @return A new LockCall instance targeting this lock device.
*/
LockCall make_call();
/** Publish a state to the front-end from the back-end.
*
* Then the internal value member is set and finally the callbacks are called.
*
* @param state The new state.
*/
void publish_state(LockState state);
/// The current reported state of the lock.
LockState state{LOCK_STATE_NONE};
LockTraits traits;
/** Turn this lock on. This is called by the front-end.
*
* For implementing locks, please override control.
*/
void lock();
/** Turn this lock off. This is called by the front-end.
*
* For implementing locks, please override control.
*/
void unlock();
/** Open (unlatch) this lock. This is called by the front-end.
*
* For implementing locks, please override control.
*/
void open();
/** Set callback for state changes.
*
* @param callback The void(bool) callback.
*/
void add_on_state_callback(std::function<void()> &&callback);
protected:
friend LockCall;
/** Perform the open latch action with hardware. This method is optional to implement
* when creating a new lock.
*
* In the implementation of this method, it is recommended you also call
* publish_state with "unlock" to acknowledge that the state was written to the hardware.
*/
virtual void open_latch() { unlock(); };
/** Control the lock device, this is a virtual method that each lock integration must implement.
*
* See more info in LockCall. The integration should check all of its values in this method and
* set them accordingly. At the end of the call, the integration must call `publish_state()` to
* notify the frontend of a changed state.
*
* @param call The LockCall instance encoding all attribute changes.
*/
virtual void control(const LockCall &call) = 0;
uint32_t hash_base() override;
CallbackManager<void()> state_callback_{};
Deduplicator<LockState> publish_dedup_;
ESPPreferenceObject rtc_;
};
} // namespace lock
} // namespace esphome

View File

@ -143,11 +143,13 @@ void MAX7219Component::dump_config() {
void MAX7219Component::display() {
for (uint8_t i = 0; i < 8; i++) {
this->enable();
for (uint8_t j = 0; j < this->num_chips_; j++)
if (reverse_)
for (uint8_t j = 0; j < this->num_chips_; j++) {
if (reverse_) {
this->send_byte_(8 - i, buffer_[(num_chips_ - j - 1) * 8 + i]);
else
} else {
this->send_byte_(8 - i, buffer_[j * 8 + i]);
}
}
this->disable();
}
}

View File

@ -253,10 +253,11 @@ void MAX7219Component::send_char(uint8_t chip, uint8_t data) {
void MAX7219Component::send64pixels(uint8_t chip, const uint8_t pixels[8]) {
for (uint8_t col = 0; col < 8; col++) { // RUN THIS LOOP 8 times until column is 7
this->enable(); // start sending by enabling SPI
for (uint8_t i = 0; i < chip; i++) // send extra NOPs to push the pixels out to extra displays
for (uint8_t i = 0; i < chip; i++) { // send extra NOPs to push the pixels out to extra displays
this->send_byte_(MAX7219_REGISTER_NOOP,
MAX7219_REGISTER_NOOP); // run this loop unit the matching chip is reached
uint8_t b = 0; // rotate pixels 90 degrees -- set byte to 0
}
uint8_t b = 0; // rotate pixels 90 degrees -- set byte to 0
if (this->orientation_ == 0) {
for (uint8_t i = 0; i < 8; i++) {
// run this loop 8 times for all the pixels[8] received

View File

@ -0,0 +1 @@
CODEOWNERS = ["@mckaymatthew"]

View File

@ -0,0 +1,93 @@
#include "max9611.h"
#include "esphome/core/log.h"
#include "esphome/components/i2c/i2c_bus.h"
namespace esphome {
namespace max9611 {
using namespace esphome::i2c;
// Sign extend
// http://graphics.stanford.edu/~seander/bithacks.html#FixedSignExtend
template<typename T, unsigned B> inline T signextend(const T x) {
struct {
T x : B;
} s;
return s.x = x;
}
// Map the gain register to in uV/LSB
float gain_to_lsb(MAX9611Multiplexer gain) {
float lsb = 0.0;
if (gain == MAX9611_MULTIPLEXER_CSA_GAIN1) {
lsb = 107.50;
} else if (gain == MAX9611_MULTIPLEXER_CSA_GAIN4) {
lsb = 26.88;
} else if (gain == MAX9611_MULTIPLEXER_CSA_GAIN8) {
lsb = 13.44;
}
return lsb;
}
static const char *const TAG = "max9611";
static const uint8_t SETUP_DELAY = 4; // Wait 2 integration periods.
static const float VOUT_LSB = 14.0 / 1000.0; // 14mV/LSB
static const float TEMP_LSB = 0.48; // 0.48C/LSB
static const float MICRO_VOLTS_PER_VOLT = 1000000.0;
void MAX9611Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up max9611...");
// Perform dummy-read
uint8_t value;
this->read(&value, 1);
// Configuration Stage.
// First send an integration request with the specified gain
const uint8_t setup_dat[] = {CONTROL_REGISTER_1_ADRR, static_cast<uint8_t>(gain_)};
// Then send a request that samples all channels as fast as possible, using the last provided gain
const uint8_t fast_mode_dat[] = {CONTROL_REGISTER_1_ADRR, MAX9611Multiplexer::MAX9611_MULTIPLEXER_FAST_MODE};
if (this->write(reinterpret_cast<const uint8_t *>(&setup_dat), sizeof(setup_dat)) != ErrorCode::ERROR_OK) {
ESP_LOGE(TAG, "Failed to setup Max9611 during GAIN SET");
return;
}
delay(SETUP_DELAY);
if (this->write(reinterpret_cast<const uint8_t *>(&fast_mode_dat), sizeof(fast_mode_dat)) != ErrorCode::ERROR_OK) {
ESP_LOGE(TAG, "Failed to setup Max9611 during FAST MODE SET");
return;
}
}
void MAX9611Component::dump_config() {
ESP_LOGCONFIG(TAG, "Dump Config max9611...");
ESP_LOGCONFIG(TAG, " CSA Gain Register: %x", gain_);
LOG_I2C_DEVICE(this);
}
void MAX9611Component::update() {
// Setup read from 0x0 register base
const uint8_t reg_base = 0x0;
const ErrorCode write_result = this->write(&reg_base, 1);
// Just read the entire register map in a bulk read, faster than individually querying register.
const ErrorCode read_result = this->read(register_map_, sizeof(register_map_));
if (write_result != ErrorCode::ERROR_OK || read_result != ErrorCode::ERROR_OK) {
ESP_LOGW(TAG, "MAX9611 Update FAILED!");
return;
}
uint16_t csa_register = ((register_map_[CSA_DATA_BYTE_MSB_ADRR] << 8) | (register_map_[CSA_DATA_BYTE_LSB_ADRR])) >> 4;
uint16_t rs_register = ((register_map_[RS_DATA_BYTE_MSB_ADRR] << 8) | (register_map_[RS_DATA_BYTE_LSB_ADRR])) >> 4;
uint16_t t_register = ((register_map_[TEMP_DATA_BYTE_MSB_ADRR] << 8) | (register_map_[TEMP_DATA_BYTE_LSB_ADRR])) >> 7;
float voltage = rs_register * VOUT_LSB;
float shunt_voltage = (csa_register * gain_to_lsb(gain_)) / MICRO_VOLTS_PER_VOLT;
float temp = signextend<signed int, 9>(t_register) * TEMP_LSB;
float amps = shunt_voltage / current_resistor_;
float watts = amps * voltage;
if (voltage_sensor_ != nullptr) {
voltage_sensor_->publish_state(voltage);
}
if (current_sensor_ != nullptr) {
current_sensor_->publish_state(amps);
}
if (watt_sensor_ != nullptr) {
watt_sensor_->publish_state(watts);
}
if (temperature_sensor_ != nullptr) {
temperature_sensor_->publish_state(temp);
}
ESP_LOGD(TAG, "V: %f, A: %f, W: %f, Deg C: %f", voltage, amps, watts, temp);
}
} // namespace max9611
} // namespace esphome

View File

@ -0,0 +1,62 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/core/hal.h"
namespace esphome {
namespace max9611 {
enum MAX9611Multiplexer {
MAX9611_MULTIPLEXER_CSA_GAIN1 = 0b000,
MAX9611_MULTIPLEXER_CSA_GAIN4 = 0b001,
MAX9611_MULTIPLEXER_CSA_GAIN8 = 0b010,
MAX9611_MULTIPLEXER_RS = 0b011,
MAX9611_MULTIPLEXER_OUT = 0b100,
MAX9611_MULTIPLEXER_SET = 0b101,
MAX9611_MULTIPLEXER_TEMP = 0b110,
MAX9611_MULTIPLEXER_FAST_MODE = 0b111,
};
enum MAX9611RegisterMap {
CSA_DATA_BYTE_MSB_ADRR = 0x00,
CSA_DATA_BYTE_LSB_ADRR = 0x01,
RS_DATA_BYTE_MSB_ADRR = 0x02,
RS_DATA_BYTE_LSB_ADRR = 0x03,
OUT_DATA_BYTE_MSB_ADRR = 0x04, // Unused Op-Amp
OUT_DATA_BYTE_LSB_ADRR = 0x05, // Unused Op-Amp
SET_DATA_BYTE_MSB_ADRR = 0x06, // Unused Op-Amp
SET_DATA_BYTE_LSB_ADRR = 0x07, // Unused Op-Amp
TEMP_DATA_BYTE_MSB_ADRR = 0x08,
TEMP_DATA_BYTE_LSB_ADRR = 0x09,
CONTROL_REGISTER_1_ADRR = 0x0A,
CONTROL_REGISTER_2_ADRR = 0x0B,
};
class MAX9611Component : public PollingComponent, public i2c::I2CDevice {
public:
void setup() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
void update() override;
void set_voltage_sensor(sensor::Sensor *vs) { voltage_sensor_ = vs; }
void set_current_sensor(sensor::Sensor *cs) { current_sensor_ = cs; }
void set_watt_sensor(sensor::Sensor *ws) { watt_sensor_ = ws; }
void set_temp_sensor(sensor::Sensor *ts) { temperature_sensor_ = ts; }
void set_current_resistor(float r) { current_resistor_ = r; }
void set_gain(MAX9611Multiplexer g) { gain_ = g; }
protected:
sensor::Sensor *voltage_sensor_{nullptr};
sensor::Sensor *current_sensor_{nullptr};
sensor::Sensor *watt_sensor_{nullptr};
sensor::Sensor *temperature_sensor_{nullptr};
float current_resistor_;
uint8_t register_map_[0x0C];
MAX9611Multiplexer gain_;
};
} // namespace max9611
} // namespace esphome

View File

@ -0,0 +1,92 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.const import (
CONF_ID,
CONF_SHUNT_RESISTANCE,
CONF_GAIN,
CONF_VOLTAGE,
CONF_CURRENT,
CONF_POWER,
CONF_TEMPERATURE,
UNIT_VOLT,
UNIT_AMPERE,
UNIT_WATT,
UNIT_CELSIUS,
DEVICE_CLASS_VOLTAGE,
DEVICE_CLASS_CURRENT,
DEVICE_CLASS_POWER,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
)
DEPENDENCIES = ["i2c"]
max9611_ns = cg.esphome_ns.namespace("max9611")
max9611Gain = max9611_ns.enum("MAX9611Multiplexer")
MAX9611_GAIN = {
"8X": max9611Gain.MAX9611_MULTIPLEXER_CSA_GAIN8,
"4X": max9611Gain.MAX9611_MULTIPLEXER_CSA_GAIN4,
"1X": max9611Gain.MAX9611_MULTIPLEXER_CSA_GAIN1,
}
MAX9611Component = max9611_ns.class_(
"MAX9611Component", cg.PollingComponent, i2c.I2CDevice
)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(MAX9611Component),
cv.Required(CONF_SHUNT_RESISTANCE): cv.resistance,
cv.Required(CONF_GAIN): cv.enum(MAX9611_GAIN, upper=True),
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=2,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_CURRENT): sensor.sensor_schema(
unit_of_measurement=UNIT_AMPERE,
accuracy_decimals=2,
device_class=DEVICE_CLASS_CURRENT,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_POWER): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT,
accuracy_decimals=2,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=2,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
),
}
)
.extend(cv.polling_component_schema("60s"))
.extend(i2c.i2c_device_schema(0x70))
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
cg.add(var.set_current_resistor(config[CONF_SHUNT_RESISTANCE]))
cg.add(var.set_gain(config[CONF_GAIN]))
if CONF_VOLTAGE in config:
conf = config[CONF_VOLTAGE]
sens = await sensor.new_sensor(conf)
cg.add(var.set_voltage_sensor(sens))
if CONF_CURRENT in config:
conf = config[CONF_CURRENT]
sens = await sensor.new_sensor(conf)
cg.add(var.set_current_sensor(sens))
if CONF_POWER in config:
conf = config[CONF_POWER]
sens = await sensor.new_sensor(conf)
cg.add(var.set_watt_sensor(sens))
if CONF_TEMPERATURE in config:
conf = config[CONF_TEMPERATURE]
sens = await sensor.new_sensor(conf)
cg.add(var.set_temp_sensor(sens))

View File

@ -62,10 +62,11 @@ void MCP23016::update_reg_(uint8_t pin, bool pin_value, uint8_t reg_addr) {
this->read_reg_(reg_addr, &reg_value);
}
if (pin_value)
if (pin_value) {
reg_value |= 1 << bit;
else
} else {
reg_value &= ~(1 << bit);
}
this->write_reg_(reg_addr, reg_value);

View File

@ -67,10 +67,11 @@ void MCP23X08Base::update_reg(uint8_t pin, bool pin_value, uint8_t reg_addr) {
this->read_reg(reg_addr, &reg_value);
}
if (pin_value)
if (pin_value) {
reg_value |= 1 << bit;
else
} else {
reg_value &= ~(1 << bit);
}
this->write_reg(reg_addr, reg_value);

View File

@ -70,10 +70,11 @@ void MCP23X17Base::update_reg(uint8_t pin, bool pin_value, uint8_t reg_addr) {
this->read_reg(reg_addr, &reg_value);
}
if (pin_value)
if (pin_value) {
reg_value |= 1 << bit;
else
} else {
reg_value &= ~(1 << bit);
}
this->write_reg(reg_addr, reg_value);

View File

@ -6,6 +6,7 @@
namespace esphome {
namespace md5 {
#ifdef USE_ARDUINO
void MD5Digest::init() {
memset(this->digest_, 0, 16);
MD5Init(&this->ctx_);
@ -14,6 +15,18 @@ void MD5Digest::init() {
void MD5Digest::add(const uint8_t *data, size_t len) { MD5Update(&this->ctx_, data, len); }
void MD5Digest::calculate() { MD5Final(this->digest_, &this->ctx_); }
#endif // USE_ARDUINO
#ifdef USE_ESP_IDF
void MD5Digest::init() {
memset(this->digest_, 0, 16);
esp_rom_md5_init(&this->ctx_);
}
void MD5Digest::add(const uint8_t *data, size_t len) { esp_rom_md5_update(&this->ctx_, data, len); }
void MD5Digest::calculate() { esp_rom_md5_final(this->digest_, &this->ctx_); }
#endif // USE_ESP_IDF
void MD5Digest::get_bytes(uint8_t *output) { memcpy(output, this->digest_, 16); }

View File

@ -3,8 +3,8 @@
#include "esphome/core/defines.h"
#ifdef USE_ESP_IDF
#include "esp32/rom/md5_hash.h"
#define MD5_CTX_TYPE MD5Context
#include "esp_rom_md5.h"
#define MD5_CTX_TYPE md5_context_t
#endif
#if defined(USE_ARDUINO) && defined(USE_ESP32)

View File

@ -16,8 +16,8 @@ namespace mdns {
static const char *const TAG = "mdns";
#ifndef WEBSERVER_PORT
#define WEBSERVER_PORT 80 // NOLINT
#ifndef USE_WEBSERVER_PORT
#define USE_WEBSERVER_PORT 80 // NOLINT
#endif
void MDNSComponent::compile_records_() {
@ -63,7 +63,7 @@ void MDNSComponent::compile_records_() {
MDNSService service{};
service.service_type = "_prometheus-http";
service.proto = "_tcp";
service.port = WEBSERVER_PORT;
service.port = USE_WEBSERVER_PORT;
this->services_.push_back(service);
}
#endif
@ -74,7 +74,7 @@ void MDNSComponent::compile_records_() {
MDNSService service{};
service.service_type = "_http";
service.proto = "_tcp";
service.port = WEBSERVER_PORT;
service.port = USE_WEBSERVER_PORT;
service.txt_records.push_back({"version", ESPHOME_VERSION});
this->services_.push_back(service);
}

View File

@ -20,11 +20,11 @@ void MDNSComponent::setup() {
// part of the wire protocol to have an underscore, and for example ESP-IDF
// expects the underscore to be there, the ESP8266 implementation always adds
// the underscore itself.
auto proto = service.proto.c_str();
auto *proto = service.proto.c_str();
while (*proto == '_') {
proto++;
}
auto service_type = service.service_type.c_str();
auto *service_type = service.service_type.c_str();
while (*service_type == '_') {
service_type++;
}

View File

@ -59,14 +59,16 @@ void AirConditioner::control(const ClimateCall &call) {
ctrl.swingMode = Converters::to_midea_swing_mode(call.get_swing_mode().value());
if (call.get_mode().has_value())
ctrl.mode = Converters::to_midea_mode(call.get_mode().value());
if (call.get_preset().has_value())
if (call.get_preset().has_value()) {
ctrl.preset = Converters::to_midea_preset(call.get_preset().value());
else if (call.get_custom_preset().has_value())
} else if (call.get_custom_preset().has_value()) {
ctrl.preset = Converters::to_midea_preset(call.get_custom_preset().value());
if (call.get_fan_mode().has_value())
}
if (call.get_fan_mode().has_value()) {
ctrl.fanMode = Converters::to_midea_fan_mode(call.get_fan_mode().value());
else if (call.get_custom_fan_mode().has_value())
} else if (call.get_custom_fan_mode().has_value()) {
ctrl.fanMode = Converters::to_midea_fan_mode(call.get_custom_fan_mode().value());
}
this->base_.control(ctrl);
}

View File

@ -172,10 +172,11 @@ bool MideaIR::on_midea_(const MideaData &data) {
this->target_temperature = status.get_temp();
this->mode = status.get_mode();
this->fan_mode = status.get_fan_mode();
if (status.get_sleep_preset())
if (status.get_sleep_preset()) {
this->preset = climate::CLIMATE_PRESET_SLEEP;
else if (this->preset == climate::CLIMATE_PRESET_SLEEP)
} else if (this->preset == climate::CLIMATE_PRESET_SLEEP) {
this->preset = climate::CLIMATE_PRESET_NONE;
}
this->publish_state();
return true;
}

View File

@ -54,7 +54,7 @@ void MitsubishiClimate::transmit_state() {
}
auto transmit = this->transmitter_->transmit();
auto data = transmit.get_data();
auto *data = transmit.get_data();
data->set_carrier_frequency(38000);
// repeat twice
@ -63,12 +63,13 @@ void MitsubishiClimate::transmit_state() {
data->mark(MITSUBISHI_HEADER_MARK);
data->space(MITSUBISHI_HEADER_SPACE);
// Data
for (uint8_t i : remote_state)
for (uint8_t i : remote_state) {
for (uint8_t j = 0; j < 8; j++) {
data->mark(MITSUBISHI_BIT_MARK);
bool bit = i & (1 << j);
data->space(bit ? MITSUBISHI_ONE_SPACE : MITSUBISHI_ZERO_SPACE);
}
}
// Footer
if (r == 0) {
data->mark(MITSUBISHI_BIT_MARK);

View File

@ -0,0 +1 @@
CODEOWNERS = ["@functionpointer"]

View File

@ -0,0 +1,135 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.const import (
CONF_ID,
UNIT_MICROTESLA,
UNIT_CELSIUS,
STATE_CLASS_MEASUREMENT,
ICON_MAGNET,
ICON_THERMOMETER,
CONF_GAIN,
CONF_RESOLUTION,
CONF_OVERSAMPLING,
CONF_FILTER,
CONF_TEMPERATURE,
)
from esphome import pins
CODEOWNERS = ["@functionpointer"]
DEPENDENCIES = ["i2c"]
mlx90393_ns = cg.esphome_ns.namespace("mlx90393")
MLX90393Component = mlx90393_ns.class_(
"MLX90393Cls", cg.PollingComponent, i2c.I2CDevice
)
GAIN = {
"1X": 7,
"1_33X": 6,
"1_67X": 5,
"2X": 4,
"2_5X": 3,
"3X": 2,
"4X": 1,
"5X": 0,
}
RESOLUTION = {
"16BIT": 0,
"17BIT": 1,
"18BIT": 2,
"19BIT": 3,
}
CONF_X_AXIS = "x_axis"
CONF_Y_AXIS = "y_axis"
CONF_Z_AXIS = "z_axis"
CONF_DRDY_PIN = "drdy_pin"
def mlx90393_axis_schema(default_resolution: str):
return sensor.sensor_schema(
unit_of_measurement=UNIT_MICROTESLA,
accuracy_decimals=0,
icon=ICON_MAGNET,
state_class=STATE_CLASS_MEASUREMENT,
).extend(
cv.Schema(
{
cv.Optional(CONF_RESOLUTION, default=default_resolution): cv.enum(
RESOLUTION, upper=True, space="_"
)
}
)
)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(MLX90393Component),
cv.Optional(CONF_GAIN, default="2_5X"): cv.enum(
GAIN, upper=True, space="_"
),
cv.Optional(CONF_DRDY_PIN): pins.gpio_input_pin_schema,
cv.Optional(CONF_OVERSAMPLING, default=2): cv.int_range(min=0, max=3),
cv.Optional(CONF_FILTER, default=6): cv.int_range(min=0, max=7),
cv.Optional(CONF_X_AXIS): mlx90393_axis_schema("19BIT"),
cv.Optional(CONF_Y_AXIS): mlx90393_axis_schema("19BIT"),
cv.Optional(CONF_Z_AXIS): mlx90393_axis_schema("16BIT"),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=1,
icon=ICON_THERMOMETER,
state_class=STATE_CLASS_MEASUREMENT,
).extend(
cv.Schema(
{
cv.Optional(CONF_OVERSAMPLING, default=0): cv.int_range(
min=0, max=3
),
}
)
),
},
)
.extend(cv.polling_component_schema("60s"))
.extend(i2c.i2c_device_schema(0x0C))
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
if CONF_DRDY_PIN in config:
pin = await cg.gpio_pin_expression(config[CONF_DRDY_PIN])
cg.add(var.set_drdy_pin(pin))
cg.add(var.set_gain(GAIN[config[CONF_GAIN]]))
cg.add(var.set_oversampling(config[CONF_OVERSAMPLING]))
cg.add(var.set_filter(config[CONF_FILTER]))
if CONF_X_AXIS in config:
sens = await sensor.new_sensor(config[CONF_X_AXIS])
cg.add(var.set_x_sensor(sens))
cg.add(var.set_resolution(0, RESOLUTION[config[CONF_X_AXIS][CONF_RESOLUTION]]))
if CONF_Y_AXIS in config:
sens = await sensor.new_sensor(config[CONF_Y_AXIS])
cg.add(var.set_y_sensor(sens))
cg.add(var.set_resolution(1, RESOLUTION[config[CONF_Y_AXIS][CONF_RESOLUTION]]))
if CONF_Z_AXIS in config:
sens = await sensor.new_sensor(config[CONF_Z_AXIS])
cg.add(var.set_z_sensor(sens))
cg.add(var.set_resolution(2, RESOLUTION[config[CONF_Z_AXIS][CONF_RESOLUTION]]))
if CONF_TEMPERATURE in config:
sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
cg.add(var.set_t_sensor(sens))
cg.add(var.set_t_oversampling(config[CONF_TEMPERATURE][CONF_OVERSAMPLING]))
if CONF_DRDY_PIN in config:
pin = await cg.gpio_pin_expression(config[CONF_DRDY_PIN])
cg.add(var.set_drdy_gpio(pin))
cg.add_library("functionpointer/arduino-MLX90393", "1.0.0")

View File

@ -0,0 +1,91 @@
#include "sensor_mlx90393.h"
#include "esphome/core/log.h"
namespace esphome {
namespace mlx90393 {
static const char *const TAG = "mlx90393";
bool MLX90393Cls::transceive(const uint8_t *request, size_t request_size, uint8_t *response, size_t response_size) {
i2c::ErrorCode e = this->write(request, request_size);
if (e != i2c::ErrorCode::ERROR_OK) {
return false;
}
e = this->read(response, response_size);
return e == i2c::ErrorCode::ERROR_OK;
}
bool MLX90393Cls::has_drdy_pin() { return this->drdy_pin_ != nullptr; }
bool MLX90393Cls::read_drdy_pin() {
if (this->drdy_pin_ == nullptr) {
return false;
} else {
return this->drdy_pin_->digital_read();
}
}
void MLX90393Cls::sleep_millis(uint32_t millis) { delay(millis); }
void MLX90393Cls::sleep_micros(uint32_t micros) { delayMicroseconds(micros); }
void MLX90393Cls::setup() {
ESP_LOGCONFIG(TAG, "Setting up MLX90393...");
// note the two arguments A0 and A1 which are used to construct an i2c address
// we can hard-code these because we never actually use the constructed address
// see the transceive function above, which uses the address from I2CComponent
this->mlx_.begin_with_hal(this, 0, 0);
this->mlx_.setGainSel(this->gain_);
this->mlx_.setResolution(this->resolutions_[0], this->resolutions_[1], this->resolutions_[2]);
this->mlx_.setOverSampling(this->oversampling_);
this->mlx_.setDigitalFiltering(this->filter_);
this->mlx_.setTemperatureOverSampling(this->temperature_oversampling_);
}
void MLX90393Cls::dump_config() {
ESP_LOGCONFIG(TAG, "MLX90393:");
LOG_I2C_DEVICE(this);
if (this->is_failed()) {
ESP_LOGE(TAG, "Communication with MLX90393 failed!");
return;
}
LOG_UPDATE_INTERVAL(this);
LOG_SENSOR(" ", "X Axis", this->x_sensor_);
LOG_SENSOR(" ", "Y Axis", this->y_sensor_);
LOG_SENSOR(" ", "Z Axis", this->z_sensor_);
LOG_SENSOR(" ", "Temperature", this->t_sensor_);
}
float MLX90393Cls::get_setup_priority() const { return setup_priority::DATA; }
void MLX90393Cls::update() {
MLX90393::txyz data;
if (this->mlx_.readData(data) == MLX90393::STATUS_OK) {
ESP_LOGD(TAG, "received %f %f %f", data.x, data.y, data.z);
if (this->x_sensor_ != nullptr) {
this->x_sensor_->publish_state(data.x);
}
if (this->y_sensor_ != nullptr) {
this->y_sensor_->publish_state(data.y);
}
if (this->z_sensor_ != nullptr) {
this->z_sensor_->publish_state(data.z);
}
if (this->t_sensor_ != nullptr) {
this->t_sensor_->publish_state(data.t);
}
this->status_clear_warning();
} else {
ESP_LOGE(TAG, "failed to read data");
this->status_set_warning();
}
}
} // namespace mlx90393
} // namespace esphome

View File

@ -0,0 +1,59 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/core/hal.h"
#include <MLX90393.h>
#include <MLX90393Hal.h>
namespace esphome {
namespace mlx90393 {
class MLX90393Cls : public PollingComponent, public i2c::I2CDevice, public MLX90393Hal {
public:
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
void update() override;
void set_drdy_gpio(GPIOPin *pin) { drdy_pin_ = pin; }
void set_x_sensor(sensor::Sensor *x_sensor) { x_sensor_ = x_sensor; }
void set_y_sensor(sensor::Sensor *y_sensor) { y_sensor_ = y_sensor; }
void set_z_sensor(sensor::Sensor *z_sensor) { z_sensor_ = z_sensor; }
void set_t_sensor(sensor::Sensor *t_sensor) { t_sensor_ = t_sensor; }
void set_oversampling(uint8_t osr) { oversampling_ = osr; }
void set_t_oversampling(uint8_t osr2) { temperature_oversampling_ = osr2; }
void set_resolution(uint8_t xyz, uint8_t res) { resolutions_[xyz] = res; }
void set_filter(uint8_t filter) { filter_ = filter; }
void set_gain(uint8_t gain_sel) { gain_ = gain_sel; }
// overrides for MLX library
// disable lint because it keeps suggesting const uint8_t *response.
// this->read() writes data into response, so it can't be const
bool transceive(const uint8_t *request, size_t request_size, uint8_t *response,
size_t response_size) override; // NOLINT
bool has_drdy_pin() override;
bool read_drdy_pin() override;
void sleep_millis(uint32_t millis) override;
void sleep_micros(uint32_t micros) override;
protected:
MLX90393 mlx_;
sensor::Sensor *x_sensor_{nullptr};
sensor::Sensor *y_sensor_{nullptr};
sensor::Sensor *z_sensor_{nullptr};
sensor::Sensor *t_sensor_{nullptr};
uint8_t gain_;
uint8_t oversampling_;
uint8_t temperature_oversampling_ = 0;
uint8_t filter_;
uint8_t resolutions_[3] = {0};
GPIOPin *drdy_pin_ = nullptr;
};
} // namespace mlx90393
} // namespace esphome

View File

@ -10,8 +10,7 @@ namespace modbus_controller {
class ModbusBinarySensor : public Component, public binary_sensor::BinarySensor, public SensorItem {
public:
ModbusBinarySensor(ModbusRegisterType register_type, uint16_t start_address, uint8_t offset, uint32_t bitmask,
uint8_t skip_updates, bool force_new_range)
: Component(), binary_sensor::BinarySensor() {
uint8_t skip_updates, bool force_new_range) {
this->register_type = register_type;
this->start_address = start_address;
this->offset = offset;
@ -20,10 +19,11 @@ class ModbusBinarySensor : public Component, public binary_sensor::BinarySensor,
this->skip_updates = skip_updates;
this->force_new_range = force_new_range;
if (register_type == ModbusRegisterType::COIL || register_type == ModbusRegisterType::DISCRETE_INPUT)
if (register_type == ModbusRegisterType::COIL || register_type == ModbusRegisterType::DISCRETE_INPUT) {
this->register_count = offset + 1;
else
} else {
this->register_count = 1;
}
}
void parse_and_publish(const std::vector<uint8_t> &data) override;

View File

@ -99,7 +99,7 @@ void ModbusController::on_register_data(ModbusRegisterType register_type, uint16
// loop through all sensors with the same start address
auto sensors = find_sensors_(register_type, start_address);
for (auto sensor : sensors) {
for (auto *sensor : sensors) {
sensor->parse_and_publish(data);
}
}
@ -110,7 +110,8 @@ void ModbusController::queue_command(const ModbusCommandItem &command) {
for (auto &item : command_queue_) {
if (item->register_address == command.register_address && item->register_count == command.register_count &&
item->register_type == command.register_type && item->function_code == command.function_code) {
ESP_LOGW(TAG, "Duplicate modbus command found");
ESP_LOGW(TAG, "Duplicate modbus command found: type=0x%x address=%u count=%u",
static_cast<uint8_t>(command.register_type), command.register_address, command.register_count);
// update the payload of the queued command
// replaces a previous command
item->payload = command.payload;
@ -360,8 +361,9 @@ ModbusCommandItem ModbusCommandItem::create_write_multiple_command(ModbusControl
modbusdevice->on_write_register_response(cmd.register_type, start_address, data);
};
for (auto v : values) {
cmd.payload.push_back((v / 256) & 0xFF);
cmd.payload.push_back(v & 0xFF);
auto decoded_value = decode_value(v);
cmd.payload.push_back(decoded_value[0]);
cmd.payload.push_back(decoded_value[1]);
}
return cmd;
}
@ -416,7 +418,7 @@ ModbusCommandItem ModbusCommandItem::create_write_multiple_coils(ModbusControlle
}
ModbusCommandItem ModbusCommandItem::create_write_single_command(ModbusController *modbusdevice, uint16_t start_address,
int16_t value) {
uint16_t value) {
ModbusCommandItem cmd;
cmd.modbusdevice = modbusdevice;
cmd.register_type = ModbusRegisterType::HOLDING;
@ -427,8 +429,10 @@ ModbusCommandItem ModbusCommandItem::create_write_single_command(ModbusControlle
const std::vector<uint8_t> &data) {
modbusdevice->on_write_register_response(cmd.register_type, start_address, data);
};
cmd.payload.push_back((value / 256) & 0xFF);
cmd.payload.push_back((value % 256) & 0xFF);
auto decoded_value = decode_value(value);
cmd.payload.push_back(decoded_value[0]);
cmd.payload.push_back(decoded_value[1]);
return cmd;
}
@ -440,7 +444,7 @@ ModbusCommandItem ModbusCommandItem::create_custom_command(
cmd.modbusdevice = modbusdevice;
cmd.function_code = ModbusFunctionCode::CUSTOM;
if (handler == nullptr) {
cmd.on_data_func = [](ModbusRegisterType, uint16_t, const std::vector<uint8_t> &data) {
cmd.on_data_func = [](ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data) {
ESP_LOGI(TAG, "Custom Command sent");
};
} else {
@ -463,84 +467,70 @@ bool ModbusCommandItem::send() {
return true;
}
std::vector<uint16_t> float_to_payload(float value, SensorValueType value_type) {
union {
float float_value;
uint32_t raw;
} raw_to_float;
std::vector<uint16_t> data;
int32_t val;
void number_to_payload(std::vector<uint16_t> &data, int64_t value, SensorValueType value_type) {
switch (value_type) {
case SensorValueType::U_WORD:
case SensorValueType::S_WORD:
// cast truncates the float do some rounding here
data.push_back(lroundf(value) & 0xFFFF);
data.push_back(value & 0xFFFF);
break;
case SensorValueType::U_DWORD:
case SensorValueType::S_DWORD:
val = lroundf(value);
data.push_back((val & 0xFFFF0000) >> 16);
data.push_back(val & 0xFFFF);
case SensorValueType::FP32:
case SensorValueType::FP32_R:
data.push_back((value & 0xFFFF0000) >> 16);
data.push_back(value & 0xFFFF);
break;
case SensorValueType::U_DWORD_R:
case SensorValueType::S_DWORD_R:
val = lroundf(value);
data.push_back(val & 0xFFFF);
data.push_back((val & 0xFFFF0000) >> 16);
data.push_back(value & 0xFFFF);
data.push_back((value & 0xFFFF0000) >> 16);
break;
case SensorValueType::FP32:
raw_to_float.float_value = value;
data.push_back((raw_to_float.raw & 0xFFFF0000) >> 16);
data.push_back(raw_to_float.raw & 0xFFFF);
case SensorValueType::U_QWORD:
case SensorValueType::S_QWORD:
data.push_back((value & 0xFFFF000000000000) >> 48);
data.push_back((value & 0xFFFF00000000) >> 32);
data.push_back((value & 0xFFFF0000) >> 16);
data.push_back(value & 0xFFFF);
break;
case SensorValueType::FP32_R:
raw_to_float.float_value = value;
data.push_back(raw_to_float.raw & 0xFFFF);
data.push_back((raw_to_float.raw & 0xFFFF0000) >> 16);
case SensorValueType::U_QWORD_R:
case SensorValueType::S_QWORD_R:
data.push_back(value & 0xFFFF);
data.push_back((value & 0xFFFF0000) >> 16);
data.push_back((value & 0xFFFF00000000) >> 32);
data.push_back((value & 0xFFFF000000000000) >> 48);
break;
default:
ESP_LOGE(TAG, "Invalid data type for modbus float to payload conversation");
ESP_LOGE(TAG, "Invalid data type for modbus number to payload conversation: %d",
static_cast<uint16_t>(value_type));
break;
}
return data;
}
float payload_to_float(const std::vector<uint8_t> &data, SensorValueType sensor_value_type, uint8_t offset,
uint32_t bitmask) {
union {
float float_value;
uint32_t raw;
} raw_to_float;
int64_t payload_to_number(const std::vector<uint8_t> &data, SensorValueType sensor_value_type, uint8_t offset,
uint32_t bitmask) {
int64_t value = 0; // int64_t because it can hold signed and unsigned 32 bits
float result = NAN;
switch (sensor_value_type) {
case SensorValueType::U_WORD:
value = mask_and_shift_by_rightbit(get_data<uint16_t>(data, offset), bitmask); // default is 0xFFFF ;
result = static_cast<float>(value);
break;
case SensorValueType::U_DWORD:
case SensorValueType::FP32:
value = get_data<uint32_t>(data, offset);
value = mask_and_shift_by_rightbit((uint32_t) value, bitmask);
result = static_cast<float>(value);
break;
case SensorValueType::U_DWORD_R:
case SensorValueType::FP32_R:
value = get_data<uint32_t>(data, offset);
value = static_cast<uint32_t>(value & 0xFFFF) << 16 | (value & 0xFFFF0000) >> 16;
value = mask_and_shift_by_rightbit((uint32_t) value, bitmask);
result = static_cast<float>(value);
break;
case SensorValueType::S_WORD:
value = mask_and_shift_by_rightbit(get_data<int16_t>(data, offset),
bitmask); // default is 0xFFFF ;
result = static_cast<float>(value);
break;
case SensorValueType::S_DWORD:
value = mask_and_shift_by_rightbit(get_data<int32_t>(data, offset), bitmask);
result = static_cast<float>(value);
break;
case SensorValueType::S_DWORD_R: {
value = get_data<uint32_t>(data, offset);
@ -549,18 +539,14 @@ float payload_to_float(const std::vector<uint8_t> &data, SensorValueType sensor_
uint32_t sign_bit = (value & 0x8000) << 16;
value = mask_and_shift_by_rightbit(
static_cast<int32_t>(((value & 0x7FFF) << 16 | (value & 0xFFFF0000) >> 16) | sign_bit), bitmask);
result = static_cast<float>(value);
} break;
case SensorValueType::U_QWORD:
// Ignore bitmask for U_QWORD
value = get_data<uint64_t>(data, offset);
result = static_cast<float>(value);
break;
case SensorValueType::S_QWORD:
// Ignore bitmask for S_QWORD
value = get_data<int64_t>(data, offset);
result = static_cast<float>(value);
break;
case SensorValueType::U_QWORD_R:
// Ignore bitmask for U_QWORD
@ -568,32 +554,16 @@ float payload_to_float(const std::vector<uint8_t> &data, SensorValueType sensor_
value = static_cast<uint64_t>(value & 0xFFFF) << 48 | (value & 0xFFFF000000000000) >> 48 |
static_cast<uint64_t>(value & 0xFFFF0000) << 32 | (value & 0x0000FFFF00000000) >> 32 |
static_cast<uint64_t>(value & 0xFFFF00000000) << 16 | (value & 0x00000000FFFF0000) >> 16;
result = static_cast<float>(value);
break;
case SensorValueType::S_QWORD_R:
// Ignore bitmask for S_QWORD
value = get_data<int64_t>(data, offset);
result = static_cast<float>(value);
break;
case SensorValueType::FP32:
raw_to_float.raw = get_data<uint32_t>(data, offset);
ESP_LOGD(TAG, "FP32 = 0x%08X => %f", raw_to_float.raw, raw_to_float.float_value);
result = raw_to_float.float_value;
break;
case SensorValueType::FP32_R: {
auto tmp = get_data<uint32_t>(data, offset);
raw_to_float.raw = static_cast<uint32_t>(tmp & 0xFFFF) << 16 | (tmp & 0xFFFF0000) >> 16;
ESP_LOGD(TAG, "FP32_R = 0x%08X => %f", raw_to_float.raw, raw_to_float.float_value);
result = raw_to_float.float_value;
} break;
case SensorValueType::RAW:
result = NAN;
break;
default:
break;
}
return result;
return value;
}
} // namespace modbus_controller

View File

@ -195,7 +195,7 @@ inline bool coil_from_vector(int coil, const std::vector<uint8_t> &data) {
*/
template<typename N> N mask_and_shift_by_rightbit(N data, uint32_t mask) {
auto result = (mask & data);
if (result == 0) {
if (result == 0 || mask == 0xFFFFFFFF) {
return result;
}
for (size_t pos = 0; pos < sizeof(N) << 3; pos++) {
@ -205,22 +205,23 @@ template<typename N> N mask_and_shift_by_rightbit(N data, uint32_t mask) {
return 0;
}
/** convert float value to vector<uint16_t> suitable for sending
* @param value float value to cconvert
/** Convert float value to vector<uint16_t> suitable for sending
* @param data target for payload
* @param value float value to convert
* @param value_type defines if 16/32 or FP32 is used
* @return vector containing the modbus register words in correct order
*/
std::vector<uint16_t> float_to_payload(float value, SensorValueType value_type);
void number_to_payload(std::vector<uint16_t> &data, int64_t value, SensorValueType value_type);
/** convert vector<uint8_t> response payload to float
* @param value float value to cconvert
/** Convert vector<uint8_t> response payload to number.
* @param data payload with the data to convert
* @param sensor_value_type defines if 16/32/64 bits or FP32 is used
* @param offset offset to the data in data
* @param bitmask bitmask used for masking and shifting
* @return float version of the input
* @return 64-bit number of the payload
*/
float payload_to_float(const std::vector<uint8_t> &data, SensorValueType sensor_value_type, uint8_t offset,
uint32_t bitmask);
int64_t payload_to_number(const std::vector<uint8_t> &data, SensorValueType sensor_value_type, uint8_t offset,
uint32_t bitmask);
class ModbusController;
@ -230,10 +231,11 @@ class SensorItem {
void set_custom_data(const std::vector<uint8_t> &data) { custom_data = data; }
size_t virtual get_register_size() const {
if (register_type == ModbusRegisterType::COIL || register_type == ModbusRegisterType::DISCRETE_INPUT)
if (register_type == ModbusRegisterType::COIL || register_type == ModbusRegisterType::DISCRETE_INPUT) {
return 1;
else // if CONF_RESPONSE_BYTES is used override the default
} else { // if CONF_RESPONSE_BYTES is used override the default
return response_bytes > 0 ? response_bytes : register_count * 2;
}
}
// Override register size for modbus devices not using 1 register for one dword
void set_register_size(uint8_t register_size) { response_bytes = register_size; }
@ -347,11 +349,11 @@ class ModbusCommandItem {
* @param modbusdevice pointer to the device to execute the command
* @param start_address modbus address of the first register to read
* @param register_count number of registers to read
* @param values uint16_t array to be written to the registers
* @param value uint16_t single register value to write
* @return ModbusCommandItem with the prepared command
*/
static ModbusCommandItem create_write_single_command(ModbusController *modbusdevice, uint16_t start_address,
int16_t value);
uint16_t value);
/** Create modbus write single registers command
* Function 05 (05hex) Write Single Coil
* @param modbusdevice pointer to the device to execute the command
@ -393,7 +395,7 @@ class ModbusCommandItem {
class ModbusController : public PollingComponent, public modbus::ModbusDevice {
public:
ModbusController(uint16_t throttle = 0) : modbus::ModbusDevice(), command_throttle_(throttle){};
ModbusController(uint16_t throttle = 0) : command_throttle_(throttle){};
void dump_config() override;
void loop() override;
void setup() override;
@ -445,13 +447,36 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice {
uint16_t command_throttle_;
};
/** convert vector<uint8_t> response payload to float
* @param value float value to cconvert
/** Convert vector<uint8_t> response payload to float.
* @param data payload with data
* @param item SensorItem object
* @return float version of the input
* @return float value of data
*/
inline float payload_to_float(const std::vector<uint8_t> &data, const SensorItem &item) {
return payload_to_float(data, item.sensor_value_type, item.offset, item.bitmask);
int64_t number = payload_to_number(data, item.sensor_value_type, item.offset, item.bitmask);
float float_value;
if (item.sensor_value_type == SensorValueType::FP32 || item.sensor_value_type == SensorValueType::FP32_R) {
float_value = bit_cast<float>(static_cast<uint32_t>(number));
} else {
float_value = static_cast<float>(number);
}
return float_value;
}
inline std::vector<uint16_t> float_to_payload(float value, SensorValueType value_type) {
int64_t val;
if (value_type == SensorValueType::FP32 || value_type == SensorValueType::FP32_R) {
val = bit_cast<uint32_t>(value);
} else {
val = llroundf(value);
}
std::vector<uint16_t> data;
number_to_payload(data, val, value_type);
return data;
}
} // namespace modbus_controller

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