mirror of
https://github.com/esphome/esphome.git
synced 2024-11-22 15:08:10 +01:00
Merge branch 'beta' into bump-2023.3.0
This commit is contained in:
commit
33339e3bd8
207 changed files with 6360 additions and 1672 deletions
5
.github/workflows/ci-docker.yml
vendored
5
.github/workflows/ci-docker.yml
vendored
|
@ -23,6 +23,11 @@ permissions:
|
||||||
contents: read
|
contents: read
|
||||||
packages: read
|
packages: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
# yamllint disable-line rule:line-length
|
||||||
|
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check-docker:
|
check-docker:
|
||||||
name: Build docker containers
|
name: Build docker containers
|
||||||
|
|
16
.github/workflows/ci.yml
vendored
16
.github/workflows/ci.yml
vendored
|
@ -7,6 +7,7 @@ on:
|
||||||
branches: [dev, beta, release]
|
branches: [dev, beta, release]
|
||||||
|
|
||||||
pull_request:
|
pull_request:
|
||||||
|
merge_group:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
@ -181,9 +182,22 @@ jobs:
|
||||||
|
|
||||||
- name: Run yamllint
|
- name: Run yamllint
|
||||||
if: matrix.id == 'yamllint'
|
if: matrix.id == 'yamllint'
|
||||||
uses: frenck/action-yamllint@v1.3.1
|
uses: frenck/action-yamllint@v1.4.0
|
||||||
|
|
||||||
- name: Suggested changes
|
- name: Suggested changes
|
||||||
run: script/ci-suggest-changes
|
run: script/ci-suggest-changes
|
||||||
# yamllint disable-line rule:line-length
|
# yamllint disable-line rule:line-length
|
||||||
if: always() && (matrix.id == 'clang-tidy' || matrix.id == 'clang-format' || matrix.id == 'lint-python')
|
if: always() && (matrix.id == 'clang-tidy' || matrix.id == 'clang-format' || matrix.id == 'lint-python')
|
||||||
|
|
||||||
|
ci-status:
|
||||||
|
name: CI Status
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [ci]
|
||||||
|
if: always()
|
||||||
|
steps:
|
||||||
|
- name: Successful deploy
|
||||||
|
if: ${{ !(contains(needs.*.result, 'failure')) }}
|
||||||
|
run: exit 0
|
||||||
|
- name: Failing deploy
|
||||||
|
if: ${{ contains(needs.*.result, 'failure') }}
|
||||||
|
run: exit 1
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
# See https://pre-commit.com/hooks.html for more hooks
|
# See https://pre-commit.com/hooks.html for more hooks
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/ambv/black
|
- repo: https://github.com/ambv/black
|
||||||
rev: 22.12.0
|
rev: 23.1.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
args:
|
args:
|
||||||
|
@ -27,7 +27,7 @@ repos:
|
||||||
- --branch=release
|
- --branch=release
|
||||||
- --branch=beta
|
- --branch=beta
|
||||||
- repo: https://github.com/asottile/pyupgrade
|
- repo: https://github.com/asottile/pyupgrade
|
||||||
rev: v3.3.0
|
rev: v3.3.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: pyupgrade
|
- id: pyupgrade
|
||||||
args: [--py39-plus]
|
args: [--py39-plus]
|
||||||
|
|
11
CODEOWNERS
11
CODEOWNERS
|
@ -11,6 +11,7 @@ esphome/*.py @esphome/core
|
||||||
esphome/core/* @esphome/core
|
esphome/core/* @esphome/core
|
||||||
|
|
||||||
# Integrations
|
# Integrations
|
||||||
|
esphome/components/absolute_humidity/* @DAVe3283
|
||||||
esphome/components/ac_dimmer/* @glmnet
|
esphome/components/ac_dimmer/* @glmnet
|
||||||
esphome/components/adc/* @esphome/core
|
esphome/components/adc/* @esphome/core
|
||||||
esphome/components/adc128s102/* @DeerMaximum
|
esphome/components/adc128s102/* @DeerMaximum
|
||||||
|
@ -24,6 +25,7 @@ esphome/components/analog_threshold/* @ianchi
|
||||||
esphome/components/animation/* @syndlex
|
esphome/components/animation/* @syndlex
|
||||||
esphome/components/anova/* @buxtronix
|
esphome/components/anova/* @buxtronix
|
||||||
esphome/components/api/* @OttoWinter
|
esphome/components/api/* @OttoWinter
|
||||||
|
esphome/components/as7341/* @mrgnr
|
||||||
esphome/components/async_tcp/* @OttoWinter
|
esphome/components/async_tcp/* @OttoWinter
|
||||||
esphome/components/atc_mithermometer/* @ahpohl
|
esphome/components/atc_mithermometer/* @ahpohl
|
||||||
esphome/components/b_parasite/* @rbaron
|
esphome/components/b_parasite/* @rbaron
|
||||||
|
@ -90,11 +92,13 @@ esphome/components/factory_reset/* @anatoly-savchenkov
|
||||||
esphome/components/fastled_base/* @OttoWinter
|
esphome/components/fastled_base/* @OttoWinter
|
||||||
esphome/components/feedback/* @ianchi
|
esphome/components/feedback/* @ianchi
|
||||||
esphome/components/fingerprint_grow/* @OnFreund @loongyh
|
esphome/components/fingerprint_grow/* @OnFreund @loongyh
|
||||||
|
esphome/components/fs3000/* @kahrendt
|
||||||
esphome/components/globals/* @esphome/core
|
esphome/components/globals/* @esphome/core
|
||||||
esphome/components/gpio/* @esphome/core
|
esphome/components/gpio/* @esphome/core
|
||||||
esphome/components/gps/* @coogle
|
esphome/components/gps/* @coogle
|
||||||
esphome/components/graph/* @synco
|
esphome/components/graph/* @synco
|
||||||
esphome/components/growatt_solar/* @leeuwte
|
esphome/components/growatt_solar/* @leeuwte
|
||||||
|
esphome/components/haier/* @Yarikx
|
||||||
esphome/components/havells_solar/* @sourabhjaiswal
|
esphome/components/havells_solar/* @sourabhjaiswal
|
||||||
esphome/components/hbridge/fan/* @WeekendWarrior
|
esphome/components/hbridge/fan/* @WeekendWarrior
|
||||||
esphome/components/hbridge/light/* @DotNetDann
|
esphome/components/hbridge/light/* @DotNetDann
|
||||||
|
@ -107,17 +111,20 @@ esphome/components/hte501/* @Stock-M
|
||||||
esphome/components/hydreon_rgxx/* @functionpointer
|
esphome/components/hydreon_rgxx/* @functionpointer
|
||||||
esphome/components/i2c/* @esphome/core
|
esphome/components/i2c/* @esphome/core
|
||||||
esphome/components/i2s_audio/* @jesserockz
|
esphome/components/i2s_audio/* @jesserockz
|
||||||
|
esphome/components/ili9xxx/* @nielsnl68
|
||||||
esphome/components/improv_base/* @esphome/core
|
esphome/components/improv_base/* @esphome/core
|
||||||
esphome/components/improv_serial/* @esphome/core
|
esphome/components/improv_serial/* @esphome/core
|
||||||
esphome/components/ina260/* @MrEditor97
|
esphome/components/ina260/* @MrEditor97
|
||||||
esphome/components/inkbird_ibsth1_mini/* @fkirill
|
esphome/components/inkbird_ibsth1_mini/* @fkirill
|
||||||
esphome/components/inkplate6/* @jesserockz
|
esphome/components/inkplate6/* @jesserockz
|
||||||
esphome/components/integration/* @OttoWinter
|
esphome/components/integration/* @OttoWinter
|
||||||
|
esphome/components/internal_temperature/* @Mat931
|
||||||
esphome/components/interval/* @esphome/core
|
esphome/components/interval/* @esphome/core
|
||||||
esphome/components/json/* @OttoWinter
|
esphome/components/json/* @OttoWinter
|
||||||
esphome/components/kalman_combinator/* @Cat-Ion
|
esphome/components/kalman_combinator/* @Cat-Ion
|
||||||
esphome/components/key_collector/* @ssieb
|
esphome/components/key_collector/* @ssieb
|
||||||
esphome/components/key_provider/* @ssieb
|
esphome/components/key_provider/* @ssieb
|
||||||
|
esphome/components/kuntze/* @ssieb
|
||||||
esphome/components/lcd_menu/* @numo68
|
esphome/components/lcd_menu/* @numo68
|
||||||
esphome/components/ld2410/* @sebcaps
|
esphome/components/ld2410/* @sebcaps
|
||||||
esphome/components/ledc/* @OttoWinter
|
esphome/components/ledc/* @OttoWinter
|
||||||
|
@ -160,8 +167,9 @@ esphome/components/modbus_controller/select/* @martgras @stegm
|
||||||
esphome/components/modbus_controller/sensor/* @martgras
|
esphome/components/modbus_controller/sensor/* @martgras
|
||||||
esphome/components/modbus_controller/switch/* @martgras
|
esphome/components/modbus_controller/switch/* @martgras
|
||||||
esphome/components/modbus_controller/text_sensor/* @martgras
|
esphome/components/modbus_controller/text_sensor/* @martgras
|
||||||
esphome/components/mopeka_ble/* @spbrogan
|
esphome/components/mopeka_ble/* @Fabian-Schmidt @spbrogan
|
||||||
esphome/components/mopeka_pro_check/* @spbrogan
|
esphome/components/mopeka_pro_check/* @spbrogan
|
||||||
|
esphome/components/mopeka_std_check/* @Fabian-Schmidt
|
||||||
esphome/components/mpl3115a2/* @kbickar
|
esphome/components/mpl3115a2/* @kbickar
|
||||||
esphome/components/mpu6886/* @fabaff
|
esphome/components/mpu6886/* @fabaff
|
||||||
esphome/components/network/* @esphome/core
|
esphome/components/network/* @esphome/core
|
||||||
|
@ -208,6 +216,7 @@ esphome/components/sdm_meter/* @jesserockz @polyfaces
|
||||||
esphome/components/sdp3x/* @Azimath
|
esphome/components/sdp3x/* @Azimath
|
||||||
esphome/components/selec_meter/* @sourabhjaiswal
|
esphome/components/selec_meter/* @sourabhjaiswal
|
||||||
esphome/components/select/* @esphome/core
|
esphome/components/select/* @esphome/core
|
||||||
|
esphome/components/sen21231/* @shreyaskarnik
|
||||||
esphome/components/sen5x/* @martgras
|
esphome/components/sen5x/* @martgras
|
||||||
esphome/components/sensirion_common/* @martgras
|
esphome/components/sensirion_common/* @martgras
|
||||||
esphome/components/sensor/* @esphome/core
|
esphome/components/sensor/* @esphome/core
|
||||||
|
|
|
@ -6,9 +6,9 @@
|
||||||
ARG BASEIMGTYPE=docker
|
ARG BASEIMGTYPE=docker
|
||||||
|
|
||||||
# https://github.com/hassio-addons/addon-debian-base/releases
|
# https://github.com/hassio-addons/addon-debian-base/releases
|
||||||
FROM ghcr.io/hassio-addons/debian-base:6.2.0 AS base-hassio
|
FROM ghcr.io/hassio-addons/debian-base:6.2.3 AS base-hassio
|
||||||
# https://hub.docker.com/_/debian?tab=tags&page=1&name=bullseye
|
# https://hub.docker.com/_/debian?tab=tags&page=1&name=bullseye
|
||||||
FROM debian:bullseye-20221024-slim AS base-docker
|
FROM debian:bullseye-20230208-slim AS base-docker
|
||||||
|
|
||||||
FROM base-${BASEIMGTYPE} AS base
|
FROM base-${BASEIMGTYPE} AS base
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ RUN \
|
||||||
python3-cryptography=3.3.2-1 \
|
python3-cryptography=3.3.2-1 \
|
||||||
iputils-ping=3:20210202-1 \
|
iputils-ping=3:20210202-1 \
|
||||||
git=1:2.30.2-1 \
|
git=1:2.30.2-1 \
|
||||||
curl=7.74.0-1.3+deb11u5 \
|
curl=7.74.0-1.3+deb11u7 \
|
||||||
openssh-client=1:8.4p1-5+deb11u1 \
|
openssh-client=1:8.4p1-5+deb11u1 \
|
||||||
&& rm -rf \
|
&& rm -rf \
|
||||||
/tmp/* \
|
/tmp/* \
|
||||||
|
@ -51,7 +51,7 @@ RUN \
|
||||||
# Ubuntu python3-pip is missing wheel
|
# Ubuntu python3-pip is missing wheel
|
||||||
pip3 install --no-cache-dir \
|
pip3 install --no-cache-dir \
|
||||||
wheel==0.37.1 \
|
wheel==0.37.1 \
|
||||||
platformio==6.1.5 \
|
platformio==6.1.6 \
|
||||||
# Change some platformio settings
|
# Change some platformio settings
|
||||||
&& platformio settings set enable_telemetry No \
|
&& platformio settings set enable_telemetry No \
|
||||||
&& platformio settings set check_platformio_interval 1000000 \
|
&& platformio settings set check_platformio_interval 1000000 \
|
||||||
|
|
|
@ -254,7 +254,11 @@ async def repeat_action_to_code(config, action_id, template_arg, args):
|
||||||
var = cg.new_Pvariable(action_id, template_arg)
|
var = cg.new_Pvariable(action_id, template_arg)
|
||||||
count_template = await cg.templatable(config[CONF_COUNT], args, cg.uint32)
|
count_template = await cg.templatable(config[CONF_COUNT], args, cg.uint32)
|
||||||
cg.add(var.set_count(count_template))
|
cg.add(var.set_count(count_template))
|
||||||
actions = await build_action_list(config[CONF_THEN], template_arg, args)
|
actions = await build_action_list(
|
||||||
|
config[CONF_THEN],
|
||||||
|
cg.TemplateArguments(cg.uint32, *template_arg.args),
|
||||||
|
[(cg.uint32, "iteration"), *args],
|
||||||
|
)
|
||||||
cg.add(var.add_then(actions))
|
cg.add(var.add_then(actions))
|
||||||
return var
|
return var
|
||||||
|
|
||||||
|
|
|
@ -47,6 +47,7 @@ from esphome.cpp_helpers import ( # noqa
|
||||||
build_registry_list,
|
build_registry_list,
|
||||||
extract_registry_entry_config,
|
extract_registry_entry_config,
|
||||||
register_parented,
|
register_parented,
|
||||||
|
past_safe_mode,
|
||||||
)
|
)
|
||||||
from esphome.cpp_types import ( # noqa
|
from esphome.cpp_types import ( # noqa
|
||||||
global_ns,
|
global_ns,
|
||||||
|
@ -63,6 +64,7 @@ from esphome.cpp_types import ( # noqa
|
||||||
uint16,
|
uint16,
|
||||||
uint32,
|
uint32,
|
||||||
uint64,
|
uint64,
|
||||||
|
int16,
|
||||||
int32,
|
int32,
|
||||||
int64,
|
int64,
|
||||||
size_t,
|
size_t,
|
||||||
|
|
1
esphome/components/absolute_humidity/__init__.py
Normal file
1
esphome/components/absolute_humidity/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
CODEOWNERS = ["@DAVe3283"]
|
182
esphome/components/absolute_humidity/absolute_humidity.cpp
Normal file
182
esphome/components/absolute_humidity/absolute_humidity.cpp
Normal file
|
@ -0,0 +1,182 @@
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
#include "absolute_humidity.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace absolute_humidity {
|
||||||
|
|
||||||
|
static const char *const TAG = "absolute_humidity.sensor";
|
||||||
|
|
||||||
|
void AbsoluteHumidityComponent::setup() {
|
||||||
|
ESP_LOGCONFIG(TAG, "Setting up absolute humidity '%s'...", this->get_name().c_str());
|
||||||
|
|
||||||
|
ESP_LOGD(TAG, " Added callback for temperature '%s'", this->temperature_sensor_->get_name().c_str());
|
||||||
|
this->temperature_sensor_->add_on_state_callback([this](float state) { this->temperature_callback_(state); });
|
||||||
|
if (this->temperature_sensor_->has_state()) {
|
||||||
|
this->temperature_callback_(this->temperature_sensor_->get_state());
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGD(TAG, " Added callback for relative humidity '%s'", this->humidity_sensor_->get_name().c_str());
|
||||||
|
this->humidity_sensor_->add_on_state_callback([this](float state) { this->humidity_callback_(state); });
|
||||||
|
if (this->humidity_sensor_->has_state()) {
|
||||||
|
this->humidity_callback_(this->humidity_sensor_->get_state());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AbsoluteHumidityComponent::dump_config() {
|
||||||
|
LOG_SENSOR("", "Absolute Humidity", this);
|
||||||
|
|
||||||
|
switch (this->equation_) {
|
||||||
|
case BUCK:
|
||||||
|
ESP_LOGCONFIG(TAG, "Saturation Vapor Pressure Equation: Buck");
|
||||||
|
break;
|
||||||
|
case TETENS:
|
||||||
|
ESP_LOGCONFIG(TAG, "Saturation Vapor Pressure Equation: Tetens");
|
||||||
|
break;
|
||||||
|
case WOBUS:
|
||||||
|
ESP_LOGCONFIG(TAG, "Saturation Vapor Pressure Equation: Wobus");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ESP_LOGE(TAG, "Invalid saturation vapor pressure equation selection!");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGCONFIG(TAG, "Sources");
|
||||||
|
ESP_LOGCONFIG(TAG, " Temperature: '%s'", this->temperature_sensor_->get_name().c_str());
|
||||||
|
ESP_LOGCONFIG(TAG, " Relative Humidity: '%s'", this->humidity_sensor_->get_name().c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
float AbsoluteHumidityComponent::get_setup_priority() const { return setup_priority::DATA; }
|
||||||
|
|
||||||
|
void AbsoluteHumidityComponent::loop() {
|
||||||
|
if (!this->next_update_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->next_update_ = false;
|
||||||
|
|
||||||
|
// Ensure we have source data
|
||||||
|
const bool no_temperature = std::isnan(this->temperature_);
|
||||||
|
const bool no_humidity = std::isnan(this->humidity_);
|
||||||
|
if (no_temperature || no_humidity) {
|
||||||
|
if (no_temperature) {
|
||||||
|
ESP_LOGW(TAG, "No valid state from temperature sensor!");
|
||||||
|
}
|
||||||
|
if (no_humidity) {
|
||||||
|
ESP_LOGW(TAG, "No valid state from temperature sensor!");
|
||||||
|
}
|
||||||
|
ESP_LOGW(TAG, "Unable to calculate absolute humidity.");
|
||||||
|
this->publish_state(NAN);
|
||||||
|
this->status_set_warning();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to desired units
|
||||||
|
const float temperature_c = this->temperature_;
|
||||||
|
const float temperature_k = temperature_c + 273.15;
|
||||||
|
const float hr = this->humidity_ / 100;
|
||||||
|
|
||||||
|
// Calculate saturation vapor pressure
|
||||||
|
float es;
|
||||||
|
switch (this->equation_) {
|
||||||
|
case BUCK:
|
||||||
|
es = es_buck(temperature_c);
|
||||||
|
break;
|
||||||
|
case TETENS:
|
||||||
|
es = es_tetens(temperature_c);
|
||||||
|
break;
|
||||||
|
case WOBUS:
|
||||||
|
es = es_wobus(temperature_c);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ESP_LOGE(TAG, "Invalid saturation vapor pressure equation selection!");
|
||||||
|
this->publish_state(NAN);
|
||||||
|
this->status_set_error();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ESP_LOGD(TAG, "Saturation vapor pressure %f kPa", es);
|
||||||
|
|
||||||
|
// Calculate absolute humidity
|
||||||
|
const float absolute_humidity = vapor_density(es, hr, temperature_k);
|
||||||
|
|
||||||
|
// Publish absolute humidity
|
||||||
|
ESP_LOGD(TAG, "Publishing absolute humidity %f g/m³", absolute_humidity);
|
||||||
|
this->status_clear_warning();
|
||||||
|
this->publish_state(absolute_humidity);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buck equation (https://en.wikipedia.org/wiki/Arden_Buck_equation)
|
||||||
|
// More accurate than Tetens in normal meteorologic conditions
|
||||||
|
float AbsoluteHumidityComponent::es_buck(float temperature_c) {
|
||||||
|
float a, b, c, d;
|
||||||
|
if (temperature_c >= 0) {
|
||||||
|
a = 0.61121;
|
||||||
|
b = 18.678;
|
||||||
|
c = 234.5;
|
||||||
|
d = 257.14;
|
||||||
|
} else {
|
||||||
|
a = 0.61115;
|
||||||
|
b = 18.678;
|
||||||
|
c = 233.7;
|
||||||
|
d = 279.82;
|
||||||
|
}
|
||||||
|
return a * expf((b - (temperature_c / c)) * (temperature_c / (d + temperature_c)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tetens equation (https://en.wikipedia.org/wiki/Tetens_equation)
|
||||||
|
float AbsoluteHumidityComponent::es_tetens(float temperature_c) {
|
||||||
|
float a, b;
|
||||||
|
if (temperature_c >= 0) {
|
||||||
|
a = 17.27;
|
||||||
|
b = 237.3;
|
||||||
|
} else {
|
||||||
|
a = 21.875;
|
||||||
|
b = 265.5;
|
||||||
|
}
|
||||||
|
return 0.61078 * expf((a * temperature_c) / (temperature_c + b));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wobus equation
|
||||||
|
// https://wahiduddin.net/calc/density_altitude.htm
|
||||||
|
// https://wahiduddin.net/calc/density_algorithms.htm
|
||||||
|
// Calculate the saturation vapor pressure (kPa)
|
||||||
|
float AbsoluteHumidityComponent::es_wobus(float t) {
|
||||||
|
// THIS FUNCTION RETURNS THE SATURATION VAPOR PRESSURE ESW (MILLIBARS)
|
||||||
|
// OVER LIQUID WATER GIVEN THE TEMPERATURE T (CELSIUS). THE POLYNOMIAL
|
||||||
|
// APPROXIMATION BELOW IS DUE TO HERMAN WOBUS, A MATHEMATICIAN WHO
|
||||||
|
// WORKED AT THE NAVY WEATHER RESEARCH FACILITY, NORFOLK, VIRGINIA,
|
||||||
|
// BUT WHO IS NOW RETIRED. THE COEFFICIENTS OF THE POLYNOMIAL WERE
|
||||||
|
// CHOSEN TO FIT THE VALUES IN TABLE 94 ON PP. 351-353 OF THE SMITH-
|
||||||
|
// SONIAN METEOROLOGICAL TABLES BY ROLAND LIST (6TH EDITION). THE
|
||||||
|
// APPROXIMATION IS VALID FOR -50 < T < 100C.
|
||||||
|
//
|
||||||
|
// Baker, Schlatter 17-MAY-1982 Original version.
|
||||||
|
|
||||||
|
const float c0 = +0.99999683e00;
|
||||||
|
const float c1 = -0.90826951e-02;
|
||||||
|
const float c2 = +0.78736169e-04;
|
||||||
|
const float c3 = -0.61117958e-06;
|
||||||
|
const float c4 = +0.43884187e-08;
|
||||||
|
const float c5 = -0.29883885e-10;
|
||||||
|
const float c6 = +0.21874425e-12;
|
||||||
|
const float c7 = -0.17892321e-14;
|
||||||
|
const float c8 = +0.11112018e-16;
|
||||||
|
const float c9 = -0.30994571e-19;
|
||||||
|
const float p = c0 + t * (c1 + t * (c2 + t * (c3 + t * (c4 + t * (c5 + t * (c6 + t * (c7 + t * (c8 + t * (c9)))))))));
|
||||||
|
return 0.61078 / pow(p, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
// From https://www.environmentalbiophysics.org/chalk-talk-how-to-calculate-absolute-humidity/
|
||||||
|
// H/T to https://esphome.io/cookbook/bme280_environment.html
|
||||||
|
// H/T to https://carnotcycle.wordpress.com/2012/08/04/how-to-convert-relative-humidity-to-absolute-humidity/
|
||||||
|
float AbsoluteHumidityComponent::vapor_density(float es, float hr, float ta) {
|
||||||
|
// es = saturated vapor pressure (kPa)
|
||||||
|
// hr = relative humidity [0-1]
|
||||||
|
// ta = absolute temperature (K)
|
||||||
|
|
||||||
|
const float ea = hr * es * 1000; // vapor pressure of the air (Pa)
|
||||||
|
const float mw = 18.01528; // molar mass of water (g⋅mol⁻¹)
|
||||||
|
const float r = 8.31446261815324; // molar gas constant (J⋅K⁻¹)
|
||||||
|
return (ea * mw) / (r * ta);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace absolute_humidity
|
||||||
|
} // namespace esphome
|
76
esphome/components/absolute_humidity/absolute_humidity.h
Normal file
76
esphome/components/absolute_humidity/absolute_humidity.h
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace absolute_humidity {
|
||||||
|
|
||||||
|
/// Enum listing all implemented saturation vapor pressure equations.
|
||||||
|
enum SaturationVaporPressureEquation {
|
||||||
|
BUCK,
|
||||||
|
TETENS,
|
||||||
|
WOBUS,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// This class implements calculation of absolute humidity from temperature and relative humidity.
|
||||||
|
class AbsoluteHumidityComponent : public sensor::Sensor, public Component {
|
||||||
|
public:
|
||||||
|
AbsoluteHumidityComponent() = default;
|
||||||
|
|
||||||
|
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { this->temperature_sensor_ = temperature_sensor; }
|
||||||
|
void set_humidity_sensor(sensor::Sensor *humidity_sensor) { this->humidity_sensor_ = humidity_sensor; }
|
||||||
|
void set_equation(SaturationVaporPressureEquation equation) { this->equation_ = equation; }
|
||||||
|
|
||||||
|
void setup() override;
|
||||||
|
void dump_config() override;
|
||||||
|
float get_setup_priority() const override;
|
||||||
|
void loop() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void temperature_callback_(float state) {
|
||||||
|
this->next_update_ = true;
|
||||||
|
this->temperature_ = state;
|
||||||
|
}
|
||||||
|
void humidity_callback_(float state) {
|
||||||
|
this->next_update_ = true;
|
||||||
|
this->humidity_ = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Buck equation for saturation vapor pressure in kPa.
|
||||||
|
*
|
||||||
|
* @param temperature_c Air temperature in °C.
|
||||||
|
*/
|
||||||
|
static float es_buck(float temperature_c);
|
||||||
|
/** Tetens equation for saturation vapor pressure in kPa.
|
||||||
|
*
|
||||||
|
* @param temperature_c Air temperature in °C.
|
||||||
|
*/
|
||||||
|
static float es_tetens(float temperature_c);
|
||||||
|
/** Wobus equation for saturation vapor pressure in kPa.
|
||||||
|
*
|
||||||
|
* @param temperature_c Air temperature in °C.
|
||||||
|
*/
|
||||||
|
static float es_wobus(float temperature_c);
|
||||||
|
|
||||||
|
/** Calculate vapor density (absolute humidity) in g/m³.
|
||||||
|
*
|
||||||
|
* @param es Saturation vapor pressure in kPa.
|
||||||
|
* @param hr Relative humidity 0 to 1.
|
||||||
|
* @param ta Absolute temperature in K.
|
||||||
|
* @param heater_duration The duration in ms that the heater should turn on for when measuring.
|
||||||
|
*/
|
||||||
|
static float vapor_density(float es, float hr, float ta);
|
||||||
|
|
||||||
|
sensor::Sensor *temperature_sensor_{nullptr};
|
||||||
|
sensor::Sensor *humidity_sensor_{nullptr};
|
||||||
|
|
||||||
|
bool next_update_{false};
|
||||||
|
|
||||||
|
float temperature_{NAN};
|
||||||
|
float humidity_{NAN};
|
||||||
|
SaturationVaporPressureEquation equation_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace absolute_humidity
|
||||||
|
} // namespace esphome
|
56
esphome/components/absolute_humidity/sensor.py
Normal file
56
esphome/components/absolute_humidity/sensor.py
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import sensor
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_HUMIDITY,
|
||||||
|
CONF_TEMPERATURE,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
CONF_EQUATION,
|
||||||
|
ICON_WATER,
|
||||||
|
UNIT_GRAMS_PER_CUBIC_METER,
|
||||||
|
)
|
||||||
|
|
||||||
|
absolute_humidity_ns = cg.esphome_ns.namespace("absolute_humidity")
|
||||||
|
AbsoluteHumidityComponent = absolute_humidity_ns.class_(
|
||||||
|
"AbsoluteHumidityComponent", sensor.Sensor, cg.Component
|
||||||
|
)
|
||||||
|
|
||||||
|
SaturationVaporPressureEquation = absolute_humidity_ns.enum(
|
||||||
|
"SaturationVaporPressureEquation"
|
||||||
|
)
|
||||||
|
EQUATION = {
|
||||||
|
"BUCK": SaturationVaporPressureEquation.BUCK,
|
||||||
|
"TETENS": SaturationVaporPressureEquation.TETENS,
|
||||||
|
"WOBUS": SaturationVaporPressureEquation.WOBUS,
|
||||||
|
}
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = (
|
||||||
|
sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_GRAMS_PER_CUBIC_METER,
|
||||||
|
icon=ICON_WATER,
|
||||||
|
accuracy_decimals=2,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
)
|
||||||
|
.extend(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(AbsoluteHumidityComponent),
|
||||||
|
cv.Required(CONF_TEMPERATURE): cv.use_id(sensor.Sensor),
|
||||||
|
cv.Required(CONF_HUMIDITY): cv.use_id(sensor.Sensor),
|
||||||
|
cv.Optional(CONF_EQUATION, default="WOBUS"): cv.enum(EQUATION, upper=True),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(cv.COMPONENT_SCHEMA)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
var = await sensor.new_sensor(config)
|
||||||
|
await cg.register_component(var, config)
|
||||||
|
|
||||||
|
temperature_sensor = await cg.get_variable(config[CONF_TEMPERATURE])
|
||||||
|
cg.add(var.set_temperature_sensor(temperature_sensor))
|
||||||
|
|
||||||
|
humidity_sensor = await cg.get_variable(config[CONF_HUMIDITY])
|
||||||
|
cg.add(var.set_humidity_sensor(humidity_sensor))
|
||||||
|
|
||||||
|
cg.add(var.set_equation(config[CONF_EQUATION]))
|
|
@ -16,13 +16,16 @@ ADC128S102Sensor = adc128s102_ns.class_(
|
||||||
)
|
)
|
||||||
CONF_ADC128S102_ID = "adc128s102_id"
|
CONF_ADC128S102_ID = "adc128s102_id"
|
||||||
|
|
||||||
CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend(
|
CONFIG_SCHEMA = (
|
||||||
|
sensor.sensor_schema(ADC128S102Sensor)
|
||||||
|
.extend(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(ADC128S102Sensor),
|
|
||||||
cv.GenerateID(CONF_ADC128S102_ID): cv.use_id(ADC128S102),
|
cv.GenerateID(CONF_ADC128S102_ID): cv.use_id(ADC128S102),
|
||||||
cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=7),
|
cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=7),
|
||||||
}
|
}
|
||||||
).extend(cv.polling_component_schema("60s"))
|
)
|
||||||
|
.extend(cv.polling_component_schema("60s"))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
|
|
|
@ -15,18 +15,24 @@ AnalogThresholdBinarySensor = analog_threshold_ns.class_(
|
||||||
CONF_UPPER = "upper"
|
CONF_UPPER = "upper"
|
||||||
CONF_LOWER = "lower"
|
CONF_LOWER = "lower"
|
||||||
|
|
||||||
CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend(
|
CONFIG_SCHEMA = (
|
||||||
|
binary_sensor.binary_sensor_schema(AnalogThresholdBinarySensor)
|
||||||
|
.extend(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(AnalogThresholdBinarySensor),
|
|
||||||
cv.Required(CONF_SENSOR_ID): cv.use_id(sensor.Sensor),
|
cv.Required(CONF_SENSOR_ID): cv.use_id(sensor.Sensor),
|
||||||
cv.Required(CONF_THRESHOLD): cv.Any(
|
cv.Required(CONF_THRESHOLD): cv.Any(
|
||||||
cv.float_,
|
cv.float_,
|
||||||
cv.Schema(
|
cv.Schema(
|
||||||
{cv.Required(CONF_UPPER): cv.float_, cv.Required(CONF_LOWER): cv.float_}
|
{
|
||||||
|
cv.Required(CONF_UPPER): cv.float_,
|
||||||
|
cv.Required(CONF_LOWER): cv.float_,
|
||||||
|
}
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
).extend(cv.COMPONENT_SCHEMA)
|
)
|
||||||
|
.extend(cv.COMPONENT_SCHEMA)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
|
|
|
@ -829,7 +829,7 @@ message ListEntitiesClimateResponse {
|
||||||
repeated ClimateMode supported_modes = 7;
|
repeated ClimateMode supported_modes = 7;
|
||||||
float visual_min_temperature = 8;
|
float visual_min_temperature = 8;
|
||||||
float visual_max_temperature = 9;
|
float visual_max_temperature = 9;
|
||||||
float visual_temperature_step = 10;
|
float visual_target_temperature_step = 10;
|
||||||
// for older peer versions - in new system this
|
// for older peer versions - in new system this
|
||||||
// is if CLIMATE_PRESET_AWAY exists is supported_presets
|
// is if CLIMATE_PRESET_AWAY exists is supported_presets
|
||||||
bool legacy_supports_away = 11;
|
bool legacy_supports_away = 11;
|
||||||
|
@ -842,6 +842,7 @@ message ListEntitiesClimateResponse {
|
||||||
bool disabled_by_default = 18;
|
bool disabled_by_default = 18;
|
||||||
string icon = 19;
|
string icon = 19;
|
||||||
EntityCategory entity_category = 20;
|
EntityCategory entity_category = 20;
|
||||||
|
float visual_current_temperature_step = 21;
|
||||||
}
|
}
|
||||||
message ClimateStateResponse {
|
message ClimateStateResponse {
|
||||||
option (id) = 47;
|
option (id) = 47;
|
||||||
|
@ -1338,3 +1339,23 @@ message BluetoothGATTNotifyResponse {
|
||||||
uint64 address = 1;
|
uint64 address = 1;
|
||||||
uint32 handle = 2;
|
uint32 handle = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message BluetoothDevicePairingResponse {
|
||||||
|
option (id) = 85;
|
||||||
|
option (source) = SOURCE_SERVER;
|
||||||
|
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||||
|
|
||||||
|
uint64 address = 1;
|
||||||
|
bool paired = 2;
|
||||||
|
int32 error = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message BluetoothDeviceUnpairingResponse {
|
||||||
|
option (id) = 86;
|
||||||
|
option (source) = SOURCE_SERVER;
|
||||||
|
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||||
|
|
||||||
|
uint64 address = 1;
|
||||||
|
bool success = 2;
|
||||||
|
int32 error = 3;
|
||||||
|
}
|
||||||
|
|
|
@ -548,7 +548,9 @@ bool APIConnection::send_climate_info(climate::Climate *climate) {
|
||||||
|
|
||||||
msg.visual_min_temperature = traits.get_visual_min_temperature();
|
msg.visual_min_temperature = traits.get_visual_min_temperature();
|
||||||
msg.visual_max_temperature = traits.get_visual_max_temperature();
|
msg.visual_max_temperature = traits.get_visual_max_temperature();
|
||||||
msg.visual_temperature_step = traits.get_visual_temperature_step();
|
msg.visual_target_temperature_step = traits.get_visual_target_temperature_step();
|
||||||
|
msg.visual_current_temperature_step = traits.get_visual_current_temperature_step();
|
||||||
|
|
||||||
msg.legacy_supports_away = traits.supports_preset(climate::CLIMATE_PRESET_AWAY);
|
msg.legacy_supports_away = traits.supports_preset(climate::CLIMATE_PRESET_AWAY);
|
||||||
msg.supports_action = traits.get_supports_action();
|
msg.supports_action = traits.get_supports_action();
|
||||||
|
|
||||||
|
@ -951,7 +953,7 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
|
||||||
resp.webserver_port = USE_WEBSERVER_PORT;
|
resp.webserver_port = USE_WEBSERVER_PORT;
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_BLUETOOTH_PROXY
|
#ifdef USE_BLUETOOTH_PROXY
|
||||||
resp.bluetooth_proxy_version = bluetooth_proxy::global_bluetooth_proxy->has_active() ? 3 : 1;
|
resp.bluetooth_proxy_version = bluetooth_proxy::global_bluetooth_proxy->has_active() ? 4 : 1;
|
||||||
#endif
|
#endif
|
||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3451,7 +3451,11 @@ bool ListEntitiesClimateResponse::decode_32bit(uint32_t field_id, Proto32Bit val
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
case 10: {
|
case 10: {
|
||||||
this->visual_temperature_step = value.as_float();
|
this->visual_target_temperature_step = value.as_float();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case 21: {
|
||||||
|
this->visual_current_temperature_step = value.as_float();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
@ -3470,7 +3474,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||||
}
|
}
|
||||||
buffer.encode_float(8, this->visual_min_temperature);
|
buffer.encode_float(8, this->visual_min_temperature);
|
||||||
buffer.encode_float(9, this->visual_max_temperature);
|
buffer.encode_float(9, this->visual_max_temperature);
|
||||||
buffer.encode_float(10, this->visual_temperature_step);
|
buffer.encode_float(10, this->visual_target_temperature_step);
|
||||||
buffer.encode_bool(11, this->legacy_supports_away);
|
buffer.encode_bool(11, this->legacy_supports_away);
|
||||||
buffer.encode_bool(12, this->supports_action);
|
buffer.encode_bool(12, this->supports_action);
|
||||||
for (auto &it : this->supported_fan_modes) {
|
for (auto &it : this->supported_fan_modes) {
|
||||||
|
@ -3491,6 +3495,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||||
buffer.encode_bool(18, this->disabled_by_default);
|
buffer.encode_bool(18, this->disabled_by_default);
|
||||||
buffer.encode_string(19, this->icon);
|
buffer.encode_string(19, this->icon);
|
||||||
buffer.encode_enum<enums::EntityCategory>(20, this->entity_category);
|
buffer.encode_enum<enums::EntityCategory>(20, this->entity_category);
|
||||||
|
buffer.encode_float(21, this->visual_current_temperature_step);
|
||||||
}
|
}
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
void ListEntitiesClimateResponse::dump_to(std::string &out) const {
|
void ListEntitiesClimateResponse::dump_to(std::string &out) const {
|
||||||
|
@ -3537,8 +3542,8 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const {
|
||||||
out.append(buffer);
|
out.append(buffer);
|
||||||
out.append("\n");
|
out.append("\n");
|
||||||
|
|
||||||
out.append(" visual_temperature_step: ");
|
out.append(" visual_target_temperature_step: ");
|
||||||
sprintf(buffer, "%g", this->visual_temperature_step);
|
sprintf(buffer, "%g", this->visual_target_temperature_step);
|
||||||
out.append(buffer);
|
out.append(buffer);
|
||||||
out.append("\n");
|
out.append("\n");
|
||||||
|
|
||||||
|
@ -3591,6 +3596,11 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const {
|
||||||
out.append(" entity_category: ");
|
out.append(" entity_category: ");
|
||||||
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
|
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
|
||||||
out.append("\n");
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" visual_current_temperature_step: ");
|
||||||
|
sprintf(buffer, "%g", this->visual_current_temperature_step);
|
||||||
|
out.append(buffer);
|
||||||
|
out.append("\n");
|
||||||
out.append("}");
|
out.append("}");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -5964,6 +5974,92 @@ void BluetoothGATTNotifyResponse::dump_to(std::string &out) const {
|
||||||
out.append("}");
|
out.append("}");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
bool BluetoothDevicePairingResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||||
|
switch (field_id) {
|
||||||
|
case 1: {
|
||||||
|
this->address = value.as_uint64();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case 2: {
|
||||||
|
this->paired = value.as_bool();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case 3: {
|
||||||
|
this->error = value.as_int32();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void BluetoothDevicePairingResponse::encode(ProtoWriteBuffer buffer) const {
|
||||||
|
buffer.encode_uint64(1, this->address);
|
||||||
|
buffer.encode_bool(2, this->paired);
|
||||||
|
buffer.encode_int32(3, this->error);
|
||||||
|
}
|
||||||
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
|
void BluetoothDevicePairingResponse::dump_to(std::string &out) const {
|
||||||
|
__attribute__((unused)) char buffer[64];
|
||||||
|
out.append("BluetoothDevicePairingResponse {\n");
|
||||||
|
out.append(" address: ");
|
||||||
|
sprintf(buffer, "%llu", this->address);
|
||||||
|
out.append(buffer);
|
||||||
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" paired: ");
|
||||||
|
out.append(YESNO(this->paired));
|
||||||
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" error: ");
|
||||||
|
sprintf(buffer, "%d", this->error);
|
||||||
|
out.append(buffer);
|
||||||
|
out.append("\n");
|
||||||
|
out.append("}");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
bool BluetoothDeviceUnpairingResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||||
|
switch (field_id) {
|
||||||
|
case 1: {
|
||||||
|
this->address = value.as_uint64();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case 2: {
|
||||||
|
this->success = value.as_bool();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case 3: {
|
||||||
|
this->error = value.as_int32();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void BluetoothDeviceUnpairingResponse::encode(ProtoWriteBuffer buffer) const {
|
||||||
|
buffer.encode_uint64(1, this->address);
|
||||||
|
buffer.encode_bool(2, this->success);
|
||||||
|
buffer.encode_int32(3, this->error);
|
||||||
|
}
|
||||||
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
|
void BluetoothDeviceUnpairingResponse::dump_to(std::string &out) const {
|
||||||
|
__attribute__((unused)) char buffer[64];
|
||||||
|
out.append("BluetoothDeviceUnpairingResponse {\n");
|
||||||
|
out.append(" address: ");
|
||||||
|
sprintf(buffer, "%llu", this->address);
|
||||||
|
out.append(buffer);
|
||||||
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" success: ");
|
||||||
|
out.append(YESNO(this->success));
|
||||||
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" error: ");
|
||||||
|
sprintf(buffer, "%d", this->error);
|
||||||
|
out.append(buffer);
|
||||||
|
out.append("\n");
|
||||||
|
out.append("}");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
} // namespace api
|
} // namespace api
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
|
@ -915,7 +915,7 @@ class ListEntitiesClimateResponse : public ProtoMessage {
|
||||||
std::vector<enums::ClimateMode> supported_modes{};
|
std::vector<enums::ClimateMode> supported_modes{};
|
||||||
float visual_min_temperature{0.0f};
|
float visual_min_temperature{0.0f};
|
||||||
float visual_max_temperature{0.0f};
|
float visual_max_temperature{0.0f};
|
||||||
float visual_temperature_step{0.0f};
|
float visual_target_temperature_step{0.0f};
|
||||||
bool legacy_supports_away{false};
|
bool legacy_supports_away{false};
|
||||||
bool supports_action{false};
|
bool supports_action{false};
|
||||||
std::vector<enums::ClimateFanMode> supported_fan_modes{};
|
std::vector<enums::ClimateFanMode> supported_fan_modes{};
|
||||||
|
@ -926,6 +926,7 @@ class ListEntitiesClimateResponse : public ProtoMessage {
|
||||||
bool disabled_by_default{false};
|
bool disabled_by_default{false};
|
||||||
std::string icon{};
|
std::string icon{};
|
||||||
enums::EntityCategory entity_category{};
|
enums::EntityCategory entity_category{};
|
||||||
|
float visual_current_temperature_step{0.0f};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
void dump_to(std::string &out) const override;
|
void dump_to(std::string &out) const override;
|
||||||
|
@ -1527,6 +1528,32 @@ class BluetoothGATTNotifyResponse : public ProtoMessage {
|
||||||
protected:
|
protected:
|
||||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||||
};
|
};
|
||||||
|
class BluetoothDevicePairingResponse : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
uint64_t address{0};
|
||||||
|
bool paired{false};
|
||||||
|
int32_t error{0};
|
||||||
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
|
void dump_to(std::string &out) const override;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||||
|
};
|
||||||
|
class BluetoothDeviceUnpairingResponse : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
uint64_t address{0};
|
||||||
|
bool success{false};
|
||||||
|
int32_t error{0};
|
||||||
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
|
void dump_to(std::string &out) const override;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace api
|
} // namespace api
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
|
@ -425,6 +425,22 @@ bool APIServerConnectionBase::send_bluetooth_gatt_notify_response(const Bluetoot
|
||||||
return this->send_message_<BluetoothGATTNotifyResponse>(msg, 84);
|
return this->send_message_<BluetoothGATTNotifyResponse>(msg, 84);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_BLUETOOTH_PROXY
|
||||||
|
bool APIServerConnectionBase::send_bluetooth_device_pairing_response(const BluetoothDevicePairingResponse &msg) {
|
||||||
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
|
ESP_LOGVV(TAG, "send_bluetooth_device_pairing_response: %s", msg.dump().c_str());
|
||||||
|
#endif
|
||||||
|
return this->send_message_<BluetoothDevicePairingResponse>(msg, 85);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef USE_BLUETOOTH_PROXY
|
||||||
|
bool APIServerConnectionBase::send_bluetooth_device_unpairing_response(const BluetoothDeviceUnpairingResponse &msg) {
|
||||||
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
|
ESP_LOGVV(TAG, "send_bluetooth_device_unpairing_response: %s", msg.dump().c_str());
|
||||||
|
#endif
|
||||||
|
return this->send_message_<BluetoothDeviceUnpairingResponse>(msg, 86);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {
|
bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {
|
||||||
switch (msg_type) {
|
switch (msg_type) {
|
||||||
case 1: {
|
case 1: {
|
||||||
|
|
|
@ -209,6 +209,12 @@ class APIServerConnectionBase : public ProtoService {
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_BLUETOOTH_PROXY
|
#ifdef USE_BLUETOOTH_PROXY
|
||||||
bool send_bluetooth_gatt_notify_response(const BluetoothGATTNotifyResponse &msg);
|
bool send_bluetooth_gatt_notify_response(const BluetoothGATTNotifyResponse &msg);
|
||||||
|
#endif
|
||||||
|
#ifdef USE_BLUETOOTH_PROXY
|
||||||
|
bool send_bluetooth_device_pairing_response(const BluetoothDevicePairingResponse &msg);
|
||||||
|
#endif
|
||||||
|
#ifdef USE_BLUETOOTH_PROXY
|
||||||
|
bool send_bluetooth_device_unpairing_response(const BluetoothDeviceUnpairingResponse &msg);
|
||||||
#endif
|
#endif
|
||||||
protected:
|
protected:
|
||||||
bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;
|
bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;
|
||||||
|
|
|
@ -309,6 +309,28 @@ void APIServer::send_bluetooth_device_connection(uint64_t address, bool connecte
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void APIServer::send_bluetooth_device_pairing(uint64_t address, bool paired, esp_err_t error) {
|
||||||
|
BluetoothDevicePairingResponse call;
|
||||||
|
call.address = address;
|
||||||
|
call.paired = paired;
|
||||||
|
call.error = error;
|
||||||
|
|
||||||
|
for (auto &client : this->clients_) {
|
||||||
|
client->send_bluetooth_device_pairing_response(call);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void APIServer::send_bluetooth_device_unpairing(uint64_t address, bool success, esp_err_t error) {
|
||||||
|
BluetoothDeviceUnpairingResponse call;
|
||||||
|
call.address = address;
|
||||||
|
call.success = success;
|
||||||
|
call.error = error;
|
||||||
|
|
||||||
|
for (auto &client : this->clients_) {
|
||||||
|
client->send_bluetooth_device_unpairing_response(call);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void APIServer::send_bluetooth_connections_free(uint8_t free, uint8_t limit) {
|
void APIServer::send_bluetooth_connections_free(uint8_t free, uint8_t limit) {
|
||||||
BluetoothConnectionsFreeResponse call;
|
BluetoothConnectionsFreeResponse call;
|
||||||
call.free = free;
|
call.free = free;
|
||||||
|
|
|
@ -78,6 +78,8 @@ class APIServer : public Component, public Controller {
|
||||||
#ifdef USE_BLUETOOTH_PROXY
|
#ifdef USE_BLUETOOTH_PROXY
|
||||||
void send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &call);
|
void send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &call);
|
||||||
void send_bluetooth_device_connection(uint64_t address, bool connected, uint16_t mtu = 0, esp_err_t error = ESP_OK);
|
void send_bluetooth_device_connection(uint64_t address, bool connected, uint16_t mtu = 0, esp_err_t error = ESP_OK);
|
||||||
|
void send_bluetooth_device_pairing(uint64_t address, bool paired, esp_err_t error = ESP_OK);
|
||||||
|
void send_bluetooth_device_unpairing(uint64_t address, bool success, esp_err_t error = ESP_OK);
|
||||||
void send_bluetooth_connections_free(uint8_t free, uint8_t limit);
|
void send_bluetooth_connections_free(uint8_t free, uint8_t limit);
|
||||||
void send_bluetooth_gatt_read_response(const BluetoothGATTReadResponse &call);
|
void send_bluetooth_gatt_read_response(const BluetoothGATTReadResponse &call);
|
||||||
void send_bluetooth_gatt_write_response(const BluetoothGATTWriteResponse &call);
|
void send_bluetooth_gatt_write_response(const BluetoothGATTWriteResponse &call);
|
||||||
|
|
0
esphome/components/as7341/__init__.py
Normal file
0
esphome/components/as7341/__init__.py
Normal file
271
esphome/components/as7341/as7341.cpp
Normal file
271
esphome/components/as7341/as7341.cpp
Normal file
|
@ -0,0 +1,271 @@
|
||||||
|
#include "as7341.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/hal.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace as7341 {
|
||||||
|
|
||||||
|
static const char *const TAG = "as7341";
|
||||||
|
|
||||||
|
void AS7341Component::setup() {
|
||||||
|
ESP_LOGCONFIG(TAG, "Setting up AS7341...");
|
||||||
|
LOG_I2C_DEVICE(this);
|
||||||
|
|
||||||
|
// Verify device ID
|
||||||
|
uint8_t id;
|
||||||
|
this->read_byte(AS7341_ID, &id);
|
||||||
|
ESP_LOGCONFIG(TAG, " Read ID: 0x%X", id);
|
||||||
|
if ((id & 0xFC) != (AS7341_CHIP_ID << 2)) {
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Power on (enter IDLE state)
|
||||||
|
if (!this->enable_power(true)) {
|
||||||
|
ESP_LOGE(TAG, " Power on failed!");
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set configuration
|
||||||
|
this->write_byte(AS7341_CONFIG, 0x00);
|
||||||
|
this->setup_atime(this->atime_);
|
||||||
|
this->setup_astep(this->astep_);
|
||||||
|
this->setup_gain(this->gain_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AS7341Component::dump_config() {
|
||||||
|
ESP_LOGCONFIG(TAG, "AS7341:");
|
||||||
|
LOG_I2C_DEVICE(this);
|
||||||
|
if (this->is_failed()) {
|
||||||
|
ESP_LOGE(TAG, "Communication with AS7341 failed!");
|
||||||
|
}
|
||||||
|
LOG_UPDATE_INTERVAL(this);
|
||||||
|
ESP_LOGCONFIG(TAG, " Gain: %u", get_gain());
|
||||||
|
ESP_LOGCONFIG(TAG, " ATIME: %u", get_atime());
|
||||||
|
ESP_LOGCONFIG(TAG, " ASTEP: %u", get_astep());
|
||||||
|
|
||||||
|
LOG_SENSOR(" ", "F1", this->f1_);
|
||||||
|
LOG_SENSOR(" ", "F2", this->f2_);
|
||||||
|
LOG_SENSOR(" ", "F3", this->f3_);
|
||||||
|
LOG_SENSOR(" ", "F4", this->f4_);
|
||||||
|
LOG_SENSOR(" ", "F5", this->f5_);
|
||||||
|
LOG_SENSOR(" ", "F6", this->f6_);
|
||||||
|
LOG_SENSOR(" ", "F7", this->f7_);
|
||||||
|
LOG_SENSOR(" ", "F8", this->f8_);
|
||||||
|
LOG_SENSOR(" ", "Clear", this->clear_);
|
||||||
|
LOG_SENSOR(" ", "NIR", this->nir_);
|
||||||
|
}
|
||||||
|
|
||||||
|
float AS7341Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||||
|
|
||||||
|
void AS7341Component::update() {
|
||||||
|
this->read_channels(this->channel_readings_);
|
||||||
|
|
||||||
|
if (this->f1_ != nullptr) {
|
||||||
|
this->f1_->publish_state(this->channel_readings_[0]);
|
||||||
|
}
|
||||||
|
if (this->f2_ != nullptr) {
|
||||||
|
this->f2_->publish_state(this->channel_readings_[1]);
|
||||||
|
}
|
||||||
|
if (this->f3_ != nullptr) {
|
||||||
|
this->f3_->publish_state(this->channel_readings_[2]);
|
||||||
|
}
|
||||||
|
if (this->f4_ != nullptr) {
|
||||||
|
this->f4_->publish_state(this->channel_readings_[3]);
|
||||||
|
}
|
||||||
|
if (this->f5_ != nullptr) {
|
||||||
|
this->f5_->publish_state(this->channel_readings_[6]);
|
||||||
|
}
|
||||||
|
if (this->f6_ != nullptr) {
|
||||||
|
this->f6_->publish_state(this->channel_readings_[7]);
|
||||||
|
}
|
||||||
|
if (this->f7_ != nullptr) {
|
||||||
|
this->f7_->publish_state(this->channel_readings_[8]);
|
||||||
|
}
|
||||||
|
if (this->f8_ != nullptr) {
|
||||||
|
this->f8_->publish_state(this->channel_readings_[9]);
|
||||||
|
}
|
||||||
|
if (this->clear_ != nullptr) {
|
||||||
|
this->clear_->publish_state(this->channel_readings_[10]);
|
||||||
|
}
|
||||||
|
if (this->nir_ != nullptr) {
|
||||||
|
this->nir_->publish_state(this->channel_readings_[11]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AS7341Gain AS7341Component::get_gain() {
|
||||||
|
uint8_t data;
|
||||||
|
this->read_byte(AS7341_CFG1, &data);
|
||||||
|
return (AS7341Gain) data;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t AS7341Component::get_atime() {
|
||||||
|
uint8_t data;
|
||||||
|
this->read_byte(AS7341_ATIME, &data);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t AS7341Component::get_astep() {
|
||||||
|
uint16_t data;
|
||||||
|
this->read_byte_16(AS7341_ASTEP, &data);
|
||||||
|
return this->swap_bytes(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AS7341Component::setup_gain(AS7341Gain gain) { return this->write_byte(AS7341_CFG1, gain); }
|
||||||
|
|
||||||
|
bool AS7341Component::setup_atime(uint8_t atime) { return this->write_byte(AS7341_ATIME, atime); }
|
||||||
|
|
||||||
|
bool AS7341Component::setup_astep(uint16_t astep) { return this->write_byte_16(AS7341_ASTEP, swap_bytes(astep)); }
|
||||||
|
|
||||||
|
bool AS7341Component::read_channels(uint16_t *data) {
|
||||||
|
this->set_smux_low_channels(true);
|
||||||
|
this->enable_spectral_measurement(true);
|
||||||
|
this->wait_for_data();
|
||||||
|
bool low_success = this->read_bytes_16(AS7341_CH0_DATA_L, data, 6);
|
||||||
|
|
||||||
|
this->set_smux_low_channels(false);
|
||||||
|
this->enable_spectral_measurement(true);
|
||||||
|
this->wait_for_data();
|
||||||
|
bool high_sucess = this->read_bytes_16(AS7341_CH0_DATA_L, &data[6], 6);
|
||||||
|
|
||||||
|
return low_success && high_sucess;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AS7341Component::set_smux_low_channels(bool enable) {
|
||||||
|
this->enable_spectral_measurement(false);
|
||||||
|
this->set_smux_command(AS7341_SMUX_CMD_WRITE);
|
||||||
|
|
||||||
|
if (enable) {
|
||||||
|
this->configure_smux_low_channels();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
this->configure_smux_high_channels();
|
||||||
|
}
|
||||||
|
this->enable_smux();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AS7341Component::set_smux_command(AS7341SmuxCommand command) {
|
||||||
|
uint8_t data = command << 3; // Write to bits 4:3 of the register
|
||||||
|
return this->write_byte(AS7341_CFG6, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AS7341Component::configure_smux_low_channels() {
|
||||||
|
// SMUX Config for F1,F2,F3,F4,NIR,Clear
|
||||||
|
this->write_byte(0x00, 0x30); // F3 left set to ADC2
|
||||||
|
this->write_byte(0x01, 0x01); // F1 left set to ADC0
|
||||||
|
this->write_byte(0x02, 0x00); // Reserved or disabled
|
||||||
|
this->write_byte(0x03, 0x00); // F8 left disabled
|
||||||
|
this->write_byte(0x04, 0x00); // F6 left disabled
|
||||||
|
this->write_byte(0x05, 0x42); // F4 left connected to ADC3/f2 left connected to ADC1
|
||||||
|
this->write_byte(0x06, 0x00); // F5 left disbled
|
||||||
|
this->write_byte(0x07, 0x00); // F7 left disbled
|
||||||
|
this->write_byte(0x08, 0x50); // CLEAR connected to ADC4
|
||||||
|
this->write_byte(0x09, 0x00); // F5 right disabled
|
||||||
|
this->write_byte(0x0A, 0x00); // F7 right disabled
|
||||||
|
this->write_byte(0x0B, 0x00); // Reserved or disabled
|
||||||
|
this->write_byte(0x0C, 0x20); // F2 right connected to ADC1
|
||||||
|
this->write_byte(0x0D, 0x04); // F4 right connected to ADC3
|
||||||
|
this->write_byte(0x0E, 0x00); // F6/F8 right disabled
|
||||||
|
this->write_byte(0x0F, 0x30); // F3 right connected to AD2
|
||||||
|
this->write_byte(0x10, 0x01); // F1 right connected to AD0
|
||||||
|
this->write_byte(0x11, 0x50); // CLEAR right connected to AD4
|
||||||
|
this->write_byte(0x12, 0x00); // Reserved or disabled
|
||||||
|
this->write_byte(0x13, 0x06); // NIR connected to ADC5
|
||||||
|
}
|
||||||
|
|
||||||
|
void AS7341Component::configure_smux_high_channels() {
|
||||||
|
// SMUX Config for F5,F6,F7,F8,NIR,Clear
|
||||||
|
this->write_byte(0x00, 0x00); // F3 left disable
|
||||||
|
this->write_byte(0x01, 0x00); // F1 left disable
|
||||||
|
this->write_byte(0x02, 0x00); // reserved/disable
|
||||||
|
this->write_byte(0x03, 0x40); // F8 left connected to ADC3
|
||||||
|
this->write_byte(0x04, 0x02); // F6 left connected to ADC1
|
||||||
|
this->write_byte(0x05, 0x00); // F4/ F2 disabled
|
||||||
|
this->write_byte(0x06, 0x10); // F5 left connected to ADC0
|
||||||
|
this->write_byte(0x07, 0x03); // F7 left connected to ADC2
|
||||||
|
this->write_byte(0x08, 0x50); // CLEAR Connected to ADC4
|
||||||
|
this->write_byte(0x09, 0x10); // F5 right connected to ADC0
|
||||||
|
this->write_byte(0x0A, 0x03); // F7 right connected to ADC2
|
||||||
|
this->write_byte(0x0B, 0x00); // Reserved or disabled
|
||||||
|
this->write_byte(0x0C, 0x00); // F2 right disabled
|
||||||
|
this->write_byte(0x0D, 0x00); // F4 right disabled
|
||||||
|
this->write_byte(0x0E, 0x24); // F8 right connected to ADC2/ F6 right connected to ADC1
|
||||||
|
this->write_byte(0x0F, 0x00); // F3 right disabled
|
||||||
|
this->write_byte(0x10, 0x00); // F1 right disabled
|
||||||
|
this->write_byte(0x11, 0x50); // CLEAR right connected to AD4
|
||||||
|
this->write_byte(0x12, 0x00); // Reserved or disabled
|
||||||
|
this->write_byte(0x13, 0x06); // NIR connected to ADC5
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AS7341Component::enable_smux() {
|
||||||
|
this->set_register_bit(AS7341_ENABLE, 4);
|
||||||
|
|
||||||
|
uint16_t timeout = 1000;
|
||||||
|
for (uint16_t time = 0; time < timeout; time++) {
|
||||||
|
// The SMUXEN bit is cleared once the SMUX operation is finished
|
||||||
|
bool smuxen = this->read_register_bit(AS7341_ENABLE, 4);
|
||||||
|
if (!smuxen) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
delay(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AS7341Component::wait_for_data() {
|
||||||
|
uint16_t timeout = 1000;
|
||||||
|
for (uint16_t time = 0; time < timeout; time++) {
|
||||||
|
if (this->is_data_ready()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
delay(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AS7341Component::is_data_ready() { return this->read_register_bit(AS7341_STATUS2, 6); }
|
||||||
|
|
||||||
|
bool AS7341Component::enable_power(bool enable) { return this->write_register_bit(AS7341_ENABLE, enable, 0); }
|
||||||
|
|
||||||
|
bool AS7341Component::enable_spectral_measurement(bool enable) {
|
||||||
|
return this->write_register_bit(AS7341_ENABLE, enable, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AS7341Component::read_register_bit(uint8_t address, uint8_t bit_position) {
|
||||||
|
uint8_t data;
|
||||||
|
this->read_byte(address, &data);
|
||||||
|
bool bit = (data & (1 << bit_position)) > 0;
|
||||||
|
return bit;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AS7341Component::write_register_bit(uint8_t address, bool value, uint8_t bit_position) {
|
||||||
|
if (value) {
|
||||||
|
return this->set_register_bit(address, bit_position);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this->clear_register_bit(address, bit_position);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AS7341Component::set_register_bit(uint8_t address, uint8_t bit_position) {
|
||||||
|
uint8_t data;
|
||||||
|
this->read_byte(address, &data);
|
||||||
|
data |= (1 << bit_position);
|
||||||
|
return this->write_byte(address, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AS7341Component::clear_register_bit(uint8_t address, uint8_t bit_position) {
|
||||||
|
uint8_t data;
|
||||||
|
this->read_byte(address, &data);
|
||||||
|
data &= ~(1 << bit_position);
|
||||||
|
return this->write_byte(address, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t AS7341Component::swap_bytes(uint16_t data) { return (data >> 8) | (data << 8); }
|
||||||
|
|
||||||
|
} // namespace as7341
|
||||||
|
} // namespace esphome
|
144
esphome/components/as7341/as7341.h
Normal file
144
esphome/components/as7341/as7341.h
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
#include "esphome/components/i2c/i2c.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace as7341 {
|
||||||
|
|
||||||
|
static const uint8_t AS7341_CHIP_ID = 0X09;
|
||||||
|
|
||||||
|
static const uint8_t AS7341_CONFIG = 0x70;
|
||||||
|
static const uint8_t AS7341_LED = 0x74;
|
||||||
|
|
||||||
|
static const uint8_t AS7341_ENABLE = 0x80;
|
||||||
|
static const uint8_t AS7341_ATIME = 0x81;
|
||||||
|
|
||||||
|
static const uint8_t AS7341_WTIME = 0x83;
|
||||||
|
|
||||||
|
static const uint8_t AS7341_AUXID = 0x90;
|
||||||
|
static const uint8_t AS7341_REVID = 0x91;
|
||||||
|
static const uint8_t AS7341_ID = 0x92;
|
||||||
|
static const uint8_t AS7341_STATUS = 0x93;
|
||||||
|
|
||||||
|
static const uint8_t AS7341_CH0_DATA_L = 0x95;
|
||||||
|
static const uint8_t AS7341_CH0_DATA_H = 0x96;
|
||||||
|
static const uint8_t AS7341_CH1_DATA_L = 0x97;
|
||||||
|
static const uint8_t AS7341_CH1_DATA_H = 0x98;
|
||||||
|
static const uint8_t AS7341_CH2_DATA_L = 0x99;
|
||||||
|
static const uint8_t AS7341_CH2_DATA_H = 0x9A;
|
||||||
|
static const uint8_t AS7341_CH3_DATA_L = 0x9B;
|
||||||
|
static const uint8_t AS7341_CH3_DATA_H = 0x9C;
|
||||||
|
static const uint8_t AS7341_CH4_DATA_L = 0x9D;
|
||||||
|
static const uint8_t AS7341_CH4_DATA_H = 0x9E;
|
||||||
|
static const uint8_t AS7341_CH5_DATA_L = 0x9F;
|
||||||
|
static const uint8_t AS7341_CH5_DATA_H = 0xA0;
|
||||||
|
|
||||||
|
static const uint8_t AS7341_STATUS2 = 0xA3;
|
||||||
|
|
||||||
|
static const uint8_t AS7341_CFG1 = 0xAA; ///< Controls ADC Gain
|
||||||
|
|
||||||
|
static const uint8_t AS7341_CFG6 = 0xAF; // Stores SMUX command
|
||||||
|
static const uint8_t AS7341_CFG9 = 0xB2; // Config for system interrupts (SMUX, Flicker detection)
|
||||||
|
|
||||||
|
static const uint8_t AS7341_ASTEP = 0xCA; // LSB
|
||||||
|
static const uint8_t AS7341_ASTEP_MSB = 0xCB; // MSB
|
||||||
|
|
||||||
|
enum AS7341AdcChannel {
|
||||||
|
AS7341_ADC_CHANNEL_0,
|
||||||
|
AS7341_ADC_CHANNEL_1,
|
||||||
|
AS7341_ADC_CHANNEL_2,
|
||||||
|
AS7341_ADC_CHANNEL_3,
|
||||||
|
AS7341_ADC_CHANNEL_4,
|
||||||
|
AS7341_ADC_CHANNEL_5,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum AS7341SmuxCommand {
|
||||||
|
AS7341_SMUX_CMD_ROM_RESET, ///< ROM code initialization of SMUX
|
||||||
|
AS7341_SMUX_CMD_READ, ///< Read SMUX configuration to RAM from SMUX chain
|
||||||
|
AS7341_SMUX_CMD_WRITE, ///< Write SMUX configuration from RAM to SMUX chain
|
||||||
|
};
|
||||||
|
|
||||||
|
enum AS7341Gain {
|
||||||
|
AS7341_GAIN_0_5X,
|
||||||
|
AS7341_GAIN_1X,
|
||||||
|
AS7341_GAIN_2X,
|
||||||
|
AS7341_GAIN_4X,
|
||||||
|
AS7341_GAIN_8X,
|
||||||
|
AS7341_GAIN_16X,
|
||||||
|
AS7341_GAIN_32X,
|
||||||
|
AS7341_GAIN_64X,
|
||||||
|
AS7341_GAIN_128X,
|
||||||
|
AS7341_GAIN_256X,
|
||||||
|
AS7341_GAIN_512X,
|
||||||
|
};
|
||||||
|
|
||||||
|
class AS7341Component : public PollingComponent, public i2c::I2CDevice {
|
||||||
|
public:
|
||||||
|
void setup() override;
|
||||||
|
void dump_config() override;
|
||||||
|
float get_setup_priority() const override;
|
||||||
|
void update() override;
|
||||||
|
|
||||||
|
void set_f1_sensor(sensor::Sensor *f1_sensor) { this->f1_ = f1_sensor; }
|
||||||
|
void set_f2_sensor(sensor::Sensor *f2_sensor) { f2_ = f2_sensor; }
|
||||||
|
void set_f3_sensor(sensor::Sensor *f3_sensor) { f3_ = f3_sensor; }
|
||||||
|
void set_f4_sensor(sensor::Sensor *f4_sensor) { f4_ = f4_sensor; }
|
||||||
|
void set_f5_sensor(sensor::Sensor *f5_sensor) { f5_ = f5_sensor; }
|
||||||
|
void set_f6_sensor(sensor::Sensor *f6_sensor) { f6_ = f6_sensor; }
|
||||||
|
void set_f7_sensor(sensor::Sensor *f7_sensor) { f7_ = f7_sensor; }
|
||||||
|
void set_f8_sensor(sensor::Sensor *f8_sensor) { f8_ = f8_sensor; }
|
||||||
|
void set_clear_sensor(sensor::Sensor *clear_sensor) { clear_ = clear_sensor; }
|
||||||
|
void set_nir_sensor(sensor::Sensor *nir_sensor) { nir_ = nir_sensor; }
|
||||||
|
|
||||||
|
void set_gain(AS7341Gain gain) { gain_ = gain; }
|
||||||
|
void set_atime(uint8_t atime) { atime_ = atime; }
|
||||||
|
void set_astep(uint16_t astep) { astep_ = astep; }
|
||||||
|
|
||||||
|
AS7341Gain get_gain();
|
||||||
|
uint8_t get_atime();
|
||||||
|
uint16_t get_astep();
|
||||||
|
bool setup_gain(AS7341Gain gain);
|
||||||
|
bool setup_atime(uint8_t atime);
|
||||||
|
bool setup_astep(uint16_t astep);
|
||||||
|
|
||||||
|
uint16_t read_channel(AS7341AdcChannel channel);
|
||||||
|
bool read_channels(uint16_t *data);
|
||||||
|
void set_smux_low_channels(bool enable);
|
||||||
|
bool set_smux_command(AS7341SmuxCommand command);
|
||||||
|
void configure_smux_low_channels();
|
||||||
|
void configure_smux_high_channels();
|
||||||
|
bool enable_smux();
|
||||||
|
|
||||||
|
bool wait_for_data();
|
||||||
|
bool is_data_ready();
|
||||||
|
bool enable_power(bool enable);
|
||||||
|
bool enable_spectral_measurement(bool enable);
|
||||||
|
|
||||||
|
bool read_register_bit(uint8_t address, uint8_t bit_position);
|
||||||
|
bool write_register_bit(uint8_t address, bool value, uint8_t bit_position);
|
||||||
|
bool set_register_bit(uint8_t address, uint8_t bit_position);
|
||||||
|
bool clear_register_bit(uint8_t address, uint8_t bit_position);
|
||||||
|
uint16_t swap_bytes(uint16_t data);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
sensor::Sensor *f1_{nullptr};
|
||||||
|
sensor::Sensor *f2_{nullptr};
|
||||||
|
sensor::Sensor *f3_{nullptr};
|
||||||
|
sensor::Sensor *f4_{nullptr};
|
||||||
|
sensor::Sensor *f5_{nullptr};
|
||||||
|
sensor::Sensor *f6_{nullptr};
|
||||||
|
sensor::Sensor *f7_{nullptr};
|
||||||
|
sensor::Sensor *f8_{nullptr};
|
||||||
|
sensor::Sensor *clear_{nullptr};
|
||||||
|
sensor::Sensor *nir_{nullptr};
|
||||||
|
|
||||||
|
uint16_t astep_;
|
||||||
|
AS7341Gain gain_;
|
||||||
|
uint8_t atime_;
|
||||||
|
uint16_t channel_readings_[12];
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace as7341
|
||||||
|
} // namespace esphome
|
112
esphome/components/as7341/sensor.py
Normal file
112
esphome/components/as7341/sensor.py
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import i2c, sensor
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_GAIN,
|
||||||
|
CONF_ID,
|
||||||
|
DEVICE_CLASS_ILLUMINANCE,
|
||||||
|
ICON_BRIGHTNESS_5,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
CODEOWNERS = ["@mrgnr"]
|
||||||
|
DEPENDENCIES = ["i2c"]
|
||||||
|
|
||||||
|
as7341_ns = cg.esphome_ns.namespace("as7341")
|
||||||
|
|
||||||
|
AS7341Component = as7341_ns.class_(
|
||||||
|
"AS7341Component", cg.PollingComponent, i2c.I2CDevice
|
||||||
|
)
|
||||||
|
|
||||||
|
CONF_ATIME = "atime"
|
||||||
|
CONF_ASTEP = "astep"
|
||||||
|
|
||||||
|
CONF_F1 = "f1"
|
||||||
|
CONF_F2 = "f2"
|
||||||
|
CONF_F3 = "f3"
|
||||||
|
CONF_F4 = "f4"
|
||||||
|
CONF_F5 = "f5"
|
||||||
|
CONF_F6 = "f6"
|
||||||
|
CONF_F7 = "f7"
|
||||||
|
CONF_F8 = "f8"
|
||||||
|
CONF_CLEAR = "clear"
|
||||||
|
CONF_NIR = "nir"
|
||||||
|
|
||||||
|
UNIT_COUNTS = "#"
|
||||||
|
|
||||||
|
AS7341_GAIN = as7341_ns.enum("AS7341Gain")
|
||||||
|
GAIN_OPTIONS = {
|
||||||
|
"X0.5": AS7341_GAIN.AS7341_GAIN_0_5X,
|
||||||
|
"X1": AS7341_GAIN.AS7341_GAIN_1X,
|
||||||
|
"X2": AS7341_GAIN.AS7341_GAIN_2X,
|
||||||
|
"X4": AS7341_GAIN.AS7341_GAIN_4X,
|
||||||
|
"X8": AS7341_GAIN.AS7341_GAIN_8X,
|
||||||
|
"X16": AS7341_GAIN.AS7341_GAIN_16X,
|
||||||
|
"X32": AS7341_GAIN.AS7341_GAIN_32X,
|
||||||
|
"X64": AS7341_GAIN.AS7341_GAIN_64X,
|
||||||
|
"X128": AS7341_GAIN.AS7341_GAIN_128X,
|
||||||
|
"X256": AS7341_GAIN.AS7341_GAIN_256X,
|
||||||
|
"X512": AS7341_GAIN.AS7341_GAIN_512X,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
SENSOR_SCHEMA = sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_COUNTS,
|
||||||
|
icon=ICON_BRIGHTNESS_5,
|
||||||
|
accuracy_decimals=0,
|
||||||
|
device_class=DEVICE_CLASS_ILLUMINANCE,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = (
|
||||||
|
cv.Schema(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(AS7341Component),
|
||||||
|
cv.Optional(CONF_F1): SENSOR_SCHEMA,
|
||||||
|
cv.Optional(CONF_F2): SENSOR_SCHEMA,
|
||||||
|
cv.Optional(CONF_F3): SENSOR_SCHEMA,
|
||||||
|
cv.Optional(CONF_F4): SENSOR_SCHEMA,
|
||||||
|
cv.Optional(CONF_F5): SENSOR_SCHEMA,
|
||||||
|
cv.Optional(CONF_F6): SENSOR_SCHEMA,
|
||||||
|
cv.Optional(CONF_F7): SENSOR_SCHEMA,
|
||||||
|
cv.Optional(CONF_F8): SENSOR_SCHEMA,
|
||||||
|
cv.Optional(CONF_CLEAR): SENSOR_SCHEMA,
|
||||||
|
cv.Optional(CONF_NIR): SENSOR_SCHEMA,
|
||||||
|
cv.Optional(CONF_GAIN, default="X8"): cv.enum(GAIN_OPTIONS),
|
||||||
|
cv.Optional(CONF_ATIME, default=29): cv.int_range(min=0, max=255),
|
||||||
|
cv.Optional(CONF_ASTEP, default=599): cv.int_range(min=0, max=65534),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(cv.polling_component_schema("60s"))
|
||||||
|
.extend(i2c.i2c_device_schema(0x39))
|
||||||
|
)
|
||||||
|
|
||||||
|
SENSORS = {
|
||||||
|
CONF_F1: "set_f1_sensor",
|
||||||
|
CONF_F2: "set_f2_sensor",
|
||||||
|
CONF_F3: "set_f3_sensor",
|
||||||
|
CONF_F4: "set_f4_sensor",
|
||||||
|
CONF_F5: "set_f5_sensor",
|
||||||
|
CONF_F6: "set_f6_sensor",
|
||||||
|
CONF_F7: "set_f7_sensor",
|
||||||
|
CONF_F8: "set_f8_sensor",
|
||||||
|
CONF_CLEAR: "set_clear_sensor",
|
||||||
|
CONF_NIR: "set_nir_sensor",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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_gain(config[CONF_GAIN]))
|
||||||
|
cg.add(var.set_atime(config[CONF_ATIME]))
|
||||||
|
cg.add(var.set_astep(config[CONF_ASTEP]))
|
||||||
|
|
||||||
|
for conf_id, set_sensor_func in SENSORS.items():
|
||||||
|
if conf_id in config:
|
||||||
|
sens = await sensor.new_sensor(config[conf_id])
|
||||||
|
cg.add(getattr(var, set_sensor_func)(sens))
|
|
@ -80,7 +80,7 @@ async def to_code(config):
|
||||||
|
|
||||||
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))
|
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))
|
||||||
|
|
||||||
for (config_key, setter) in [
|
for config_key, setter in [
|
||||||
(CONF_TEMPERATURE, var.set_temperature),
|
(CONF_TEMPERATURE, var.set_temperature),
|
||||||
(CONF_HUMIDITY, var.set_humidity),
|
(CONF_HUMIDITY, var.set_humidity),
|
||||||
(CONF_BATTERY_VOLTAGE, var.set_battery_voltage),
|
(CONF_BATTERY_VOLTAGE, var.set_battery_voltage),
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#ifdef USE_ESP32
|
||||||
|
|
||||||
#include "bedjet_hub.h"
|
#include "bedjet_hub.h"
|
||||||
#include "bedjet_child.h"
|
#include "bedjet_child.h"
|
||||||
#include "bedjet_const.h"
|
#include "bedjet_const.h"
|
||||||
|
@ -541,3 +543,5 @@ void BedJetHub::register_child(BedJetClient *obj) {
|
||||||
|
|
||||||
} // namespace bedjet
|
} // namespace bedjet
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#ifdef USE_ESP32
|
||||||
|
|
||||||
#include "esphome/components/ble_client/ble_client.h"
|
#include "esphome/components/ble_client/ble_client.h"
|
||||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||||
|
@ -14,8 +15,6 @@
|
||||||
#include "esphome/components/time/real_time_clock.h"
|
#include "esphome/components/time/real_time_clock.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_ESP32
|
|
||||||
|
|
||||||
#include <esp_gattc_api.h>
|
#include <esp_gattc_api.h>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
|
|
|
@ -27,13 +27,13 @@ from esphome.const import (
|
||||||
CONF_TIMING,
|
CONF_TIMING,
|
||||||
CONF_TRIGGER_ID,
|
CONF_TRIGGER_ID,
|
||||||
CONF_MQTT_ID,
|
CONF_MQTT_ID,
|
||||||
DEVICE_CLASS_EMPTY,
|
|
||||||
DEVICE_CLASS_BATTERY,
|
DEVICE_CLASS_BATTERY,
|
||||||
DEVICE_CLASS_BATTERY_CHARGING,
|
DEVICE_CLASS_BATTERY_CHARGING,
|
||||||
DEVICE_CLASS_CARBON_MONOXIDE,
|
DEVICE_CLASS_CARBON_MONOXIDE,
|
||||||
DEVICE_CLASS_COLD,
|
DEVICE_CLASS_COLD,
|
||||||
DEVICE_CLASS_CONNECTIVITY,
|
DEVICE_CLASS_CONNECTIVITY,
|
||||||
DEVICE_CLASS_DOOR,
|
DEVICE_CLASS_DOOR,
|
||||||
|
DEVICE_CLASS_EMPTY,
|
||||||
DEVICE_CLASS_GARAGE_DOOR,
|
DEVICE_CLASS_GARAGE_DOOR,
|
||||||
DEVICE_CLASS_GAS,
|
DEVICE_CLASS_GAS,
|
||||||
DEVICE_CLASS_HEAT,
|
DEVICE_CLASS_HEAT,
|
||||||
|
@ -62,13 +62,13 @@ from esphome.util import Registry
|
||||||
|
|
||||||
CODEOWNERS = ["@esphome/core"]
|
CODEOWNERS = ["@esphome/core"]
|
||||||
DEVICE_CLASSES = [
|
DEVICE_CLASSES = [
|
||||||
DEVICE_CLASS_EMPTY,
|
|
||||||
DEVICE_CLASS_BATTERY,
|
DEVICE_CLASS_BATTERY,
|
||||||
DEVICE_CLASS_BATTERY_CHARGING,
|
DEVICE_CLASS_BATTERY_CHARGING,
|
||||||
DEVICE_CLASS_CARBON_MONOXIDE,
|
DEVICE_CLASS_CARBON_MONOXIDE,
|
||||||
DEVICE_CLASS_COLD,
|
DEVICE_CLASS_COLD,
|
||||||
DEVICE_CLASS_CONNECTIVITY,
|
DEVICE_CLASS_CONNECTIVITY,
|
||||||
DEVICE_CLASS_DOOR,
|
DEVICE_CLASS_DOOR,
|
||||||
|
DEVICE_CLASS_EMPTY,
|
||||||
DEVICE_CLASS_GARAGE_DOOR,
|
DEVICE_CLASS_GARAGE_DOOR,
|
||||||
DEVICE_CLASS_GAS,
|
DEVICE_CLASS_GAS,
|
||||||
DEVICE_CLASS_HEAT,
|
DEVICE_CLASS_HEAT,
|
||||||
|
@ -393,28 +393,21 @@ def binary_sensor_schema(
|
||||||
entity_category: str = _UNDEF,
|
entity_category: str = _UNDEF,
|
||||||
device_class: str = _UNDEF,
|
device_class: str = _UNDEF,
|
||||||
) -> cv.Schema:
|
) -> cv.Schema:
|
||||||
schema = BINARY_SENSOR_SCHEMA
|
schema = {}
|
||||||
|
|
||||||
if class_ is not _UNDEF:
|
if class_ is not _UNDEF:
|
||||||
schema = schema.extend({cv.GenerateID(): cv.declare_id(class_)})
|
# Not cv.optional
|
||||||
if icon is not _UNDEF:
|
schema[cv.GenerateID()] = cv.declare_id(class_)
|
||||||
schema = schema.extend({cv.Optional(CONF_ICON, default=icon): cv.icon})
|
|
||||||
if entity_category is not _UNDEF:
|
for key, default, validator in [
|
||||||
schema = schema.extend(
|
(CONF_ICON, icon, cv.icon),
|
||||||
{
|
(CONF_ENTITY_CATEGORY, entity_category, cv.entity_category),
|
||||||
cv.Optional(
|
(CONF_DEVICE_CLASS, device_class, validate_device_class),
|
||||||
CONF_ENTITY_CATEGORY, default=entity_category
|
]:
|
||||||
): cv.entity_category
|
if default is not _UNDEF:
|
||||||
}
|
schema[cv.Optional(key, default=default)] = validator
|
||||||
)
|
|
||||||
if device_class is not _UNDEF:
|
return BINARY_SENSOR_SCHEMA.extend(schema)
|
||||||
schema = schema.extend(
|
|
||||||
{
|
|
||||||
cv.Optional(
|
|
||||||
CONF_DEVICE_CLASS, default=device_class
|
|
||||||
): validate_device_class
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return schema
|
|
||||||
|
|
||||||
|
|
||||||
async def setup_binary_sensor_core_(var, config):
|
async def setup_binary_sensor_core_(var, config):
|
||||||
|
|
|
@ -19,6 +19,15 @@ namespace binary_sensor {
|
||||||
} \
|
} \
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define SUB_BINARY_SENSOR(name) \
|
||||||
|
protected: \
|
||||||
|
binary_sensor::BinarySensor *name##_binary_sensor_{nullptr}; \
|
||||||
|
\
|
||||||
|
public: \
|
||||||
|
void set_##name##_binary_sensor(binary_sensor::BinarySensor *binary_sensor) { \
|
||||||
|
this->name##_binary_sensor_ = binary_sensor; \
|
||||||
|
}
|
||||||
|
|
||||||
/** Base class for all binary_sensor-type classes.
|
/** Base class for all binary_sensor-type classes.
|
||||||
*
|
*
|
||||||
* This class includes a callback that components such as MQTT can subscribe to for state changes.
|
* This class includes a callback that components such as MQTT can subscribe to for state changes.
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#ifdef USE_ESP32
|
||||||
|
|
||||||
#include "automation.h"
|
#include "automation.h"
|
||||||
|
|
||||||
#include <esp_bt_defs.h>
|
#include <esp_bt_defs.h>
|
||||||
|
@ -73,3 +75,5 @@ void BLEWriterClientNode::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
|
||||||
|
|
||||||
} // namespace ble_client
|
} // namespace ble_client
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef USE_ESP32
|
||||||
|
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "esphome/core/automation.h"
|
#include "esphome/core/automation.h"
|
||||||
#include "esphome/components/ble_client/ble_client.h"
|
#include "esphome/components/ble_client/ble_client.h"
|
||||||
|
|
||||||
#ifdef USE_ESP32
|
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace ble_client {
|
namespace ble_client {
|
||||||
class BLEClientConnectTrigger : public Trigger<>, public BLEClientNode {
|
class BLEClientConnectTrigger : public Trigger<>, public BLEClientNode {
|
||||||
|
|
|
@ -158,6 +158,25 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BluetoothConnection::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
|
||||||
|
BLEClientBase::gap_event_handler(event, param);
|
||||||
|
|
||||||
|
switch (event) {
|
||||||
|
case ESP_GAP_BLE_AUTH_CMPL_EVT:
|
||||||
|
if (memcmp(param->ble_security.auth_cmpl.bd_addr, this->remote_bda_, 6) != 0)
|
||||||
|
break;
|
||||||
|
if (param->ble_security.auth_cmpl.success) {
|
||||||
|
api::global_api_server->send_bluetooth_device_pairing(this->address_, true);
|
||||||
|
} else {
|
||||||
|
api::global_api_server->send_bluetooth_device_pairing(this->address_, false,
|
||||||
|
param->ble_security.auth_cmpl.fail_reason);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
esp_err_t BluetoothConnection::read_characteristic(uint16_t handle) {
|
esp_err_t BluetoothConnection::read_characteristic(uint16_t handle) {
|
||||||
if (!this->connected()) {
|
if (!this->connected()) {
|
||||||
ESP_LOGW(TAG, "[%d] [%s] Cannot read GATT characteristic, not connected.", this->connection_index_,
|
ESP_LOGW(TAG, "[%d] [%s] Cannot read GATT characteristic, not connected.", this->connection_index_,
|
||||||
|
|
|
@ -13,6 +13,7 @@ class BluetoothConnection : public esp32_ble_client::BLEClientBase {
|
||||||
public:
|
public:
|
||||||
bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||||
esp_ble_gattc_cb_param_t *param) override;
|
esp_ble_gattc_cb_param_t *param) override;
|
||||||
|
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override;
|
||||||
|
|
||||||
esp_err_t read_characteristic(uint16_t handle);
|
esp_err_t read_characteristic(uint16_t handle);
|
||||||
esp_err_t write_characteristic(uint16_t handle, const std::string &data, bool response);
|
esp_err_t write_characteristic(uint16_t handle, const std::string &data, bool response);
|
||||||
|
|
|
@ -257,12 +257,7 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest
|
||||||
ESP_LOGI(TAG, "[%d] [%s] Connecting v1", connection->get_connection_index(), connection->address_str().c_str());
|
ESP_LOGI(TAG, "[%d] [%s] Connecting v1", connection->get_connection_index(), connection->address_str().c_str());
|
||||||
}
|
}
|
||||||
if (msg.has_address_type) {
|
if (msg.has_address_type) {
|
||||||
connection->remote_bda_[0] = (msg.address >> 40) & 0xFF;
|
uint64_to_bd_addr(msg.address, connection->remote_bda_);
|
||||||
connection->remote_bda_[1] = (msg.address >> 32) & 0xFF;
|
|
||||||
connection->remote_bda_[2] = (msg.address >> 24) & 0xFF;
|
|
||||||
connection->remote_bda_[3] = (msg.address >> 16) & 0xFF;
|
|
||||||
connection->remote_bda_[4] = (msg.address >> 8) & 0xFF;
|
|
||||||
connection->remote_bda_[5] = (msg.address >> 0) & 0xFF;
|
|
||||||
connection->set_remote_addr_type(static_cast<esp_ble_addr_type_t>(msg.address_type));
|
connection->set_remote_addr_type(static_cast<esp_ble_addr_type_t>(msg.address_type));
|
||||||
connection->set_state(espbt::ClientState::DISCOVERED);
|
connection->set_state(espbt::ClientState::DISCOVERED);
|
||||||
} else {
|
} else {
|
||||||
|
@ -290,10 +285,28 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR:
|
case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR: {
|
||||||
case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR:
|
auto *connection = this->get_connection_(msg.address, false);
|
||||||
|
if (connection != nullptr) {
|
||||||
|
if (!connection->is_paired()) {
|
||||||
|
auto err = connection->pair();
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
api::global_api_server->send_bluetooth_device_pairing(msg.address, false, err);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
api::global_api_server->send_bluetooth_device_pairing(msg.address, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR: {
|
||||||
|
esp_bd_addr_t address;
|
||||||
|
uint64_to_bd_addr(msg.address, address);
|
||||||
|
esp_err_t ret = esp_ble_remove_bond_device(address);
|
||||||
|
api::global_api_server->send_bluetooth_device_unpairing(msg.address, ret == ESP_OK, ret);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void BluetoothProxy::bluetooth_gatt_read(const api::BluetoothGATTReadRequest &msg) {
|
void BluetoothProxy::bluetooth_gatt_read(const api::BluetoothGATTReadRequest &msg) {
|
||||||
|
|
|
@ -44,6 +44,15 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
|
||||||
int get_bluetooth_connections_free();
|
int get_bluetooth_connections_free();
|
||||||
int get_bluetooth_connections_limit() { return this->connections_.size(); }
|
int get_bluetooth_connections_limit() { return this->connections_.size(); }
|
||||||
|
|
||||||
|
static void uint64_to_bd_addr(uint64_t address, esp_bd_addr_t bd_addr) {
|
||||||
|
bd_addr[0] = (address >> 40) & 0xff;
|
||||||
|
bd_addr[1] = (address >> 32) & 0xff;
|
||||||
|
bd_addr[2] = (address >> 24) & 0xff;
|
||||||
|
bd_addr[3] = (address >> 16) & 0xff;
|
||||||
|
bd_addr[4] = (address >> 8) & 0xff;
|
||||||
|
bd_addr[5] = (address >> 0) & 0xff;
|
||||||
|
}
|
||||||
|
|
||||||
void set_active(bool active) { this->active_ = active; }
|
void set_active(bool active) { this->active_ = active; }
|
||||||
bool has_active() { return this->active_; }
|
bool has_active() { return this->active_; }
|
||||||
|
|
||||||
|
|
|
@ -117,18 +117,24 @@ void BME680Component::setup() {
|
||||||
this->calibration_.gh2 = cal2[12] << 8 | cal2[13];
|
this->calibration_.gh2 = cal2[12] << 8 | cal2[13];
|
||||||
this->calibration_.gh3 = cal2[15];
|
this->calibration_.gh3 = cal2[15];
|
||||||
|
|
||||||
if (!this->read_byte(0x02, &this->calibration_.res_heat_range)) {
|
uint8_t temp_var = 0;
|
||||||
|
if (!this->read_byte(0x02, &temp_var)) {
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!this->read_byte(0x00, &this->calibration_.res_heat_val)) {
|
this->calibration_.res_heat_range = ((temp_var & 0x30) / 16);
|
||||||
|
|
||||||
|
if (!this->read_byte(0x00, &temp_var)) {
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!this->read_byte(0x04, &this->calibration_.range_sw_err)) {
|
this->calibration_.res_heat_val = (int8_t) temp_var;
|
||||||
|
|
||||||
|
if (!this->read_byte(0x04, &temp_var)) {
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
this->calibration_.range_sw_err = ((int8_t) temp_var & (int8_t) 0xf0) / 16;
|
||||||
|
|
||||||
this->calibration_.ambient_temperature = 25; // prime ambient temperature
|
this->calibration_.ambient_temperature = 25; // prime ambient temperature
|
||||||
|
|
||||||
|
@ -181,7 +187,7 @@ void BME680Component::setup() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
gas0_control &= ~0b00001000;
|
gas0_control &= ~0b00001000;
|
||||||
gas0_control |= heat_off ? 0b100 : 0b000;
|
gas0_control |= heat_off << 3;
|
||||||
if (!this->write_byte(BME680_REGISTER_CONTROL_GAS0, gas0_control)) {
|
if (!this->write_byte(BME680_REGISTER_CONTROL_GAS0, gas0_control)) {
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
|
@ -249,12 +255,12 @@ uint8_t BME680Component::calc_heater_resistance_(uint16_t temperature) {
|
||||||
if (temperature > 400)
|
if (temperature > 400)
|
||||||
temperature = 400;
|
temperature = 400;
|
||||||
|
|
||||||
const uint8_t ambient_temperature = this->calibration_.ambient_temperature;
|
const int8_t ambient_temperature = this->calibration_.ambient_temperature;
|
||||||
const int8_t gh1 = this->calibration_.gh1;
|
const int8_t gh1 = this->calibration_.gh1;
|
||||||
const int16_t gh2 = this->calibration_.gh2;
|
const int16_t gh2 = this->calibration_.gh2;
|
||||||
const int8_t gh3 = this->calibration_.gh3;
|
const int8_t gh3 = this->calibration_.gh3;
|
||||||
const uint8_t res_heat_range = this->calibration_.res_heat_range;
|
const uint8_t res_heat_range = this->calibration_.res_heat_range;
|
||||||
const uint8_t res_heat_val = this->calibration_.res_heat_val;
|
const int8_t res_heat_val = this->calibration_.res_heat_val;
|
||||||
|
|
||||||
uint8_t heatr_res;
|
uint8_t heatr_res;
|
||||||
int32_t var1;
|
int32_t var1;
|
||||||
|
@ -293,35 +299,57 @@ uint8_t BME680Component::calc_heater_duration_(uint16_t duration) {
|
||||||
void BME680Component::read_data_() {
|
void BME680Component::read_data_() {
|
||||||
uint8_t data[15];
|
uint8_t data[15];
|
||||||
if (!this->read_bytes(BME680_REGISTER_FIELD0, data, 15)) {
|
if (!this->read_bytes(BME680_REGISTER_FIELD0, data, 15)) {
|
||||||
|
if (this->temperature_sensor_ != nullptr)
|
||||||
|
this->temperature_sensor_->publish_state(NAN);
|
||||||
|
if (this->pressure_sensor_ != nullptr)
|
||||||
|
this->pressure_sensor_->publish_state(NAN);
|
||||||
|
if (this->humidity_sensor_ != nullptr)
|
||||||
|
this->humidity_sensor_->publish_state(NAN);
|
||||||
|
if (this->gas_resistance_sensor_ != nullptr)
|
||||||
|
this->gas_resistance_sensor_->publish_state(NAN);
|
||||||
|
ESP_LOGW(TAG, "Communication with BME680 failed!");
|
||||||
this->status_set_warning();
|
this->status_set_warning();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
this->status_clear_warning();
|
||||||
|
|
||||||
uint32_t raw_temperature = (uint32_t(data[5]) << 12) | (uint32_t(data[6]) << 4) | (uint32_t(data[7]) >> 4);
|
uint32_t raw_temperature = (uint32_t(data[5]) << 12) | (uint32_t(data[6]) << 4) | (uint32_t(data[7]) >> 4);
|
||||||
uint32_t raw_pressure = (uint32_t(data[2]) << 12) | (uint32_t(data[3]) << 4) | (uint32_t(data[4]) >> 4);
|
uint32_t raw_pressure = (uint32_t(data[2]) << 12) | (uint32_t(data[3]) << 4) | (uint32_t(data[4]) >> 4);
|
||||||
uint32_t raw_humidity = (uint32_t(data[8]) << 8) | uint32_t(data[9]);
|
uint32_t raw_humidity = (uint32_t(data[8]) << 8) | uint32_t(data[9]);
|
||||||
uint16_t raw_gas = (uint16_t(data[13]) << 2) | (uint16_t(14) >> 6);
|
uint16_t raw_gas = (uint16_t)((uint32_t) data[13] * 4 | (((uint32_t) data[14]) / 64));
|
||||||
uint8_t gas_range = data[14] & 0x0F;
|
uint8_t gas_range = data[14] & 0x0F;
|
||||||
|
|
||||||
float temperature = this->calc_temperature_(raw_temperature);
|
float temperature = this->calc_temperature_(raw_temperature);
|
||||||
float pressure = this->calc_pressure_(raw_pressure);
|
float pressure = this->calc_pressure_(raw_pressure);
|
||||||
float humidity = this->calc_humidity_(raw_humidity);
|
float humidity = this->calc_humidity_(raw_humidity);
|
||||||
float gas_resistance = NAN;
|
float gas_resistance = this->calc_gas_resistance_(raw_gas, gas_range);
|
||||||
if (data[14] & 0x20) {
|
|
||||||
gas_resistance = this->calc_gas_resistance_(raw_gas, gas_range);
|
bool gas_valid = (data[14] >> 5) & 1;
|
||||||
}
|
bool heat_stable = (data[14] >> 4) & 1;
|
||||||
|
if (this->heater_temperature_ == 0 || this->heater_duration_ == 0)
|
||||||
|
heat_stable = true; // Allow reporting gas resistance when heater is disabled
|
||||||
|
|
||||||
ESP_LOGD(TAG, "Got temperature=%.1f°C pressure=%.1fhPa humidity=%.1f%% gas_resistance=%.1fΩ", temperature, pressure,
|
ESP_LOGD(TAG, "Got temperature=%.1f°C pressure=%.1fhPa humidity=%.1f%% gas_resistance=%.1fΩ", temperature, pressure,
|
||||||
humidity, gas_resistance);
|
humidity, gas_resistance);
|
||||||
|
if (!gas_valid)
|
||||||
|
ESP_LOGW(TAG, "Gas measurement unsuccessful, reading invalid!");
|
||||||
|
if (!heat_stable)
|
||||||
|
ESP_LOGW(TAG, "Heater unstable, reading invalid! (Normal for a few readings after a power cycle)");
|
||||||
|
|
||||||
if (this->temperature_sensor_ != nullptr)
|
if (this->temperature_sensor_ != nullptr)
|
||||||
this->temperature_sensor_->publish_state(temperature);
|
this->temperature_sensor_->publish_state(temperature);
|
||||||
if (this->pressure_sensor_ != nullptr)
|
if (this->pressure_sensor_ != nullptr)
|
||||||
this->pressure_sensor_->publish_state(pressure);
|
this->pressure_sensor_->publish_state(pressure);
|
||||||
if (this->humidity_sensor_ != nullptr)
|
if (this->humidity_sensor_ != nullptr)
|
||||||
this->humidity_sensor_->publish_state(humidity);
|
this->humidity_sensor_->publish_state(humidity);
|
||||||
if (this->gas_resistance_sensor_ != nullptr)
|
if (this->gas_resistance_sensor_ != nullptr) {
|
||||||
|
if (gas_valid && heat_stable) {
|
||||||
this->gas_resistance_sensor_->publish_state(gas_resistance);
|
this->gas_resistance_sensor_->publish_state(gas_resistance);
|
||||||
this->status_clear_warning();
|
} else {
|
||||||
|
this->status_set_warning();
|
||||||
|
this->gas_resistance_sensor_->publish_state(NAN);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
float BME680Component::calc_temperature_(uint32_t raw_temperature) {
|
float BME680Component::calc_temperature_(uint32_t raw_temperature) {
|
||||||
|
@ -428,20 +456,22 @@ float BME680Component::calc_humidity_(uint16_t raw_humidity) {
|
||||||
|
|
||||||
return calc_hum;
|
return calc_hum;
|
||||||
}
|
}
|
||||||
uint32_t BME680Component::calc_gas_resistance_(uint16_t raw_gas, uint8_t range) {
|
float BME680Component::calc_gas_resistance_(uint16_t raw_gas, uint8_t range) {
|
||||||
float calc_gas_res;
|
float calc_gas_res;
|
||||||
float var1 = 0;
|
float var1 = 0;
|
||||||
float var2 = 0;
|
float var2 = 0;
|
||||||
float var3 = 0;
|
float var3 = 0;
|
||||||
|
float raw_gas_f = raw_gas;
|
||||||
|
float range_f = 1U << range;
|
||||||
const float range_sw_err = this->calibration_.range_sw_err;
|
const float range_sw_err = this->calibration_.range_sw_err;
|
||||||
|
|
||||||
var1 = 1340.0f + (5.0f * range_sw_err);
|
var1 = 1340.0f + (5.0f * range_sw_err);
|
||||||
var2 = var1 * (1.0f + BME680_GAS_LOOKUP_TABLE_1[range] / 100.0f);
|
var2 = var1 * (1.0f + BME680_GAS_LOOKUP_TABLE_1[range] / 100.0f);
|
||||||
var3 = 1.0f + (BME680_GAS_LOOKUP_TABLE_2[range] / 100.0f);
|
var3 = 1.0f + (BME680_GAS_LOOKUP_TABLE_2[range] / 100.0f);
|
||||||
|
|
||||||
calc_gas_res = 1.0f / (var3 * 0.000000125f * float(1 << range) * (((float(raw_gas) - 512.0f) / var2) + 1.0f));
|
calc_gas_res = 1.0f / (var3 * 0.000000125f * range_f * (((raw_gas_f - 512.0f) / var2) + 1.0f));
|
||||||
|
|
||||||
return static_cast<uint32_t>(calc_gas_res);
|
return calc_gas_res;
|
||||||
}
|
}
|
||||||
uint32_t BME680Component::calc_meas_duration_() {
|
uint32_t BME680Component::calc_meas_duration_() {
|
||||||
uint32_t tph_dur; // Calculate in us
|
uint32_t tph_dur; // Calculate in us
|
||||||
|
|
|
@ -59,11 +59,11 @@ struct BME680CalibrationData {
|
||||||
int8_t gh3;
|
int8_t gh3;
|
||||||
|
|
||||||
uint8_t res_heat_range;
|
uint8_t res_heat_range;
|
||||||
uint8_t res_heat_val;
|
int8_t res_heat_val;
|
||||||
uint8_t range_sw_err;
|
int8_t range_sw_err;
|
||||||
|
|
||||||
float tfine;
|
float tfine;
|
||||||
uint8_t ambient_temperature;
|
int8_t ambient_temperature;
|
||||||
};
|
};
|
||||||
|
|
||||||
class BME680Component : public PollingComponent, public i2c::I2CDevice {
|
class BME680Component : public PollingComponent, public i2c::I2CDevice {
|
||||||
|
@ -117,7 +117,7 @@ class BME680Component : public PollingComponent, public i2c::I2CDevice {
|
||||||
/// Calculate the relative humidity in % using the provided raw ADC value.
|
/// Calculate the relative humidity in % using the provided raw ADC value.
|
||||||
float calc_humidity_(uint16_t raw_humidity);
|
float calc_humidity_(uint16_t raw_humidity);
|
||||||
/// Calculate the gas resistance in Ω using the provided raw ADC value.
|
/// Calculate the gas resistance in Ω using the provided raw ADC value.
|
||||||
uint32_t calc_gas_resistance_(uint16_t raw_gas, uint8_t range);
|
float calc_gas_resistance_(uint16_t raw_gas, uint8_t range);
|
||||||
/// Calculate how long the sensor will take until we can retrieve data.
|
/// Calculate how long the sensor will take until we can retrieve data.
|
||||||
uint32_t calc_meas_duration_();
|
uint32_t calc_meas_duration_();
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ from esphome.const import CONF_ID
|
||||||
CODEOWNERS = ["@trvrnrth"]
|
CODEOWNERS = ["@trvrnrth"]
|
||||||
DEPENDENCIES = ["i2c"]
|
DEPENDENCIES = ["i2c"]
|
||||||
AUTO_LOAD = ["sensor", "text_sensor"]
|
AUTO_LOAD = ["sensor", "text_sensor"]
|
||||||
|
MULTI_CONF = True
|
||||||
|
|
||||||
CONF_BME680_BSEC_ID = "bme680_bsec_id"
|
CONF_BME680_BSEC_ID = "bme680_bsec_id"
|
||||||
CONF_TEMPERATURE_OFFSET = "temperature_offset"
|
CONF_TEMPERATURE_OFFSET = "temperature_offset"
|
||||||
|
@ -54,6 +55,7 @@ async def to_code(config):
|
||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
await i2c.register_i2c_device(var, config)
|
await i2c.register_i2c_device(var, config)
|
||||||
|
|
||||||
|
cg.add(var.set_device_id(str(config[CONF_ID])))
|
||||||
cg.add(var.set_temperature_offset(config[CONF_TEMPERATURE_OFFSET]))
|
cg.add(var.set_temperature_offset(config[CONF_TEMPERATURE_OFFSET]))
|
||||||
cg.add(var.set_iaq_mode(config[CONF_IAQ_MODE]))
|
cg.add(var.set_iaq_mode(config[CONF_IAQ_MODE]))
|
||||||
cg.add(var.set_sample_rate(config[CONF_SAMPLE_RATE]))
|
cg.add(var.set_sample_rate(config[CONF_SAMPLE_RATE]))
|
||||||
|
|
|
@ -10,19 +10,24 @@ static const char *const TAG = "bme680_bsec.sensor";
|
||||||
|
|
||||||
static const std::string IAQ_ACCURACY_STATES[4] = {"Stabilizing", "Uncertain", "Calibrating", "Calibrated"};
|
static const std::string IAQ_ACCURACY_STATES[4] = {"Stabilizing", "Uncertain", "Calibrating", "Calibrated"};
|
||||||
|
|
||||||
BME680BSECComponent *BME680BSECComponent::instance; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
std::vector<BME680BSECComponent *>
|
||||||
|
BME680BSECComponent::instances; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||||
|
uint8_t BME680BSECComponent::work_buffer_[BSEC_MAX_WORKBUFFER_SIZE] = {0};
|
||||||
|
|
||||||
void BME680BSECComponent::setup() {
|
void BME680BSECComponent::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Setting up BME680 via BSEC...");
|
ESP_LOGCONFIG(TAG, "Setting up BME680(%s) via BSEC...", this->device_id_.c_str());
|
||||||
BME680BSECComponent::instance = this;
|
|
||||||
|
|
||||||
this->bsec_status_ = bsec_init();
|
uint8_t new_idx = BME680BSECComponent::instances.size();
|
||||||
if (this->bsec_status_ != BSEC_OK) {
|
BME680BSECComponent::instances.push_back(this);
|
||||||
this->mark_failed();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->bme680_.dev_id = this->address_;
|
this->bsec_state_data_valid_ = false;
|
||||||
|
|
||||||
|
// Initialize the bme680_ structure (passed-in to the bme680_* functions) and the BME680 device
|
||||||
|
this->bme680_.dev_id =
|
||||||
|
new_idx; // This is a "Place holder to store the id of the device structure" (see bme680_defs.h).
|
||||||
|
// This will be passed-in as first parameter to the next "read" and "write" function pointers.
|
||||||
|
// We currently use the index of the object in the BME680BSECComponent::instances vector to identify
|
||||||
|
// the different devices in the system.
|
||||||
this->bme680_.intf = BME680_I2C_INTF;
|
this->bme680_.intf = BME680_I2C_INTF;
|
||||||
this->bme680_.read = BME680BSECComponent::read_bytes_wrapper;
|
this->bme680_.read = BME680BSECComponent::read_bytes_wrapper;
|
||||||
this->bme680_.write = BME680BSECComponent::write_bytes_wrapper;
|
this->bme680_.write = BME680BSECComponent::write_bytes_wrapper;
|
||||||
|
@ -35,29 +40,30 @@ void BME680BSECComponent::setup() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->sample_rate_ == SAMPLE_RATE_ULP) {
|
// Initialize the BSEC library
|
||||||
const uint8_t bsec_config[] = {
|
if (this->reinit_bsec_lib_() != 0) {
|
||||||
#include "config/generic_33v_300s_28d/bsec_iaq.txt"
|
|
||||||
};
|
|
||||||
this->set_config_(bsec_config);
|
|
||||||
} else {
|
|
||||||
const uint8_t bsec_config[] = {
|
|
||||||
#include "config/generic_33v_3s_28d/bsec_iaq.txt"
|
|
||||||
};
|
|
||||||
this->set_config_(bsec_config);
|
|
||||||
}
|
|
||||||
this->update_subscription_();
|
|
||||||
if (this->bsec_status_ != BSEC_OK) {
|
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load the BSEC library state from storage
|
||||||
this->load_state_();
|
this->load_state_();
|
||||||
}
|
}
|
||||||
|
|
||||||
void BME680BSECComponent::set_config_(const uint8_t *config) {
|
void BME680BSECComponent::set_config_() {
|
||||||
uint8_t work_buffer[BSEC_MAX_WORKBUFFER_SIZE];
|
if (this->sample_rate_ == SAMPLE_RATE_ULP) {
|
||||||
this->bsec_status_ = bsec_set_configuration(config, BSEC_MAX_PROPERTY_BLOB_SIZE, work_buffer, sizeof(work_buffer));
|
const uint8_t config[] = {
|
||||||
|
#include "config/generic_33v_300s_28d/bsec_iaq.txt"
|
||||||
|
};
|
||||||
|
this->bsec_status_ =
|
||||||
|
bsec_set_configuration(config, BSEC_MAX_PROPERTY_BLOB_SIZE, this->work_buffer_, sizeof(this->work_buffer_));
|
||||||
|
} else {
|
||||||
|
const uint8_t config[] = {
|
||||||
|
#include "config/generic_33v_3s_28d/bsec_iaq.txt"
|
||||||
|
};
|
||||||
|
this->bsec_status_ =
|
||||||
|
bsec_set_configuration(config, BSEC_MAX_PROPERTY_BLOB_SIZE, this->work_buffer_, sizeof(this->work_buffer_));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
float BME680BSECComponent::calc_sensor_sample_rate_(SampleRate sample_rate) {
|
float BME680BSECComponent::calc_sensor_sample_rate_(SampleRate sample_rate) {
|
||||||
|
@ -118,10 +124,12 @@ void BME680BSECComponent::update_subscription_() {
|
||||||
uint8_t num_sensor_settings = BSEC_MAX_PHYSICAL_SENSOR;
|
uint8_t num_sensor_settings = BSEC_MAX_PHYSICAL_SENSOR;
|
||||||
this->bsec_status_ =
|
this->bsec_status_ =
|
||||||
bsec_update_subscription(virtual_sensors, num_virtual_sensors, sensor_settings, &num_sensor_settings);
|
bsec_update_subscription(virtual_sensors, num_virtual_sensors, sensor_settings, &num_sensor_settings);
|
||||||
|
ESP_LOGV(TAG, "%s: updating subscription for %d virtual sensors (out=%d sensors)", this->device_id_.c_str(),
|
||||||
|
num_virtual_sensors, num_sensor_settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BME680BSECComponent::dump_config() {
|
void BME680BSECComponent::dump_config() {
|
||||||
ESP_LOGCONFIG(TAG, "BME680 via BSEC:");
|
ESP_LOGCONFIG(TAG, "%s via BSEC:", this->device_id_.c_str());
|
||||||
|
|
||||||
bsec_version_t version;
|
bsec_version_t version;
|
||||||
bsec_get_version(&version);
|
bsec_get_version(&version);
|
||||||
|
@ -185,23 +193,31 @@ void BME680BSECComponent::run_() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ESP_LOGV(TAG, "Performing sensor run");
|
ESP_LOGV(TAG, "%s: Performing sensor run", this->device_id_.c_str());
|
||||||
|
|
||||||
bsec_bme_settings_t bme680_settings;
|
// Restore BSEC library state
|
||||||
this->bsec_status_ = bsec_sensor_control(curr_time_ns, &bme680_settings);
|
// The reinit_bsec_lib_ method is computationally expensive: it takes 1200÷2900 microseconds on a ESP32.
|
||||||
|
// It can be skipped entirely when there is only one device (since the BSEC library won't be shared)
|
||||||
|
if (BME680BSECComponent::instances.size() > 1) {
|
||||||
|
int res = this->reinit_bsec_lib_();
|
||||||
|
if (res != 0)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->bsec_status_ = bsec_sensor_control(curr_time_ns, &this->bme680_settings_);
|
||||||
if (this->bsec_status_ < BSEC_OK) {
|
if (this->bsec_status_ < BSEC_OK) {
|
||||||
ESP_LOGW(TAG, "Failed to fetch sensor control settings (BSEC Error Code %d)", this->bsec_status_);
|
ESP_LOGW(TAG, "Failed to fetch sensor control settings (BSEC Error Code %d)", this->bsec_status_);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this->next_call_ns_ = bme680_settings.next_call;
|
this->next_call_ns_ = this->bme680_settings_.next_call;
|
||||||
|
|
||||||
if (bme680_settings.trigger_measurement) {
|
if (this->bme680_settings_.trigger_measurement) {
|
||||||
this->bme680_.tph_sett.os_temp = bme680_settings.temperature_oversampling;
|
this->bme680_.tph_sett.os_temp = this->bme680_settings_.temperature_oversampling;
|
||||||
this->bme680_.tph_sett.os_pres = bme680_settings.pressure_oversampling;
|
this->bme680_.tph_sett.os_pres = this->bme680_settings_.pressure_oversampling;
|
||||||
this->bme680_.tph_sett.os_hum = bme680_settings.humidity_oversampling;
|
this->bme680_.tph_sett.os_hum = this->bme680_settings_.humidity_oversampling;
|
||||||
this->bme680_.gas_sett.run_gas = bme680_settings.run_gas;
|
this->bme680_.gas_sett.run_gas = this->bme680_settings_.run_gas;
|
||||||
this->bme680_.gas_sett.heatr_temp = bme680_settings.heater_temperature;
|
this->bme680_.gas_sett.heatr_temp = this->bme680_settings_.heater_temperature;
|
||||||
this->bme680_.gas_sett.heatr_dur = bme680_settings.heating_duration;
|
this->bme680_.gas_sett.heatr_dur = this->bme680_settings_.heating_duration;
|
||||||
this->bme680_.power_mode = BME680_FORCED_MODE;
|
this->bme680_.power_mode = BME680_FORCED_MODE;
|
||||||
uint16_t desired_settings = BME680_OST_SEL | BME680_OSP_SEL | BME680_OSH_SEL | BME680_GAS_SENSOR_SEL;
|
uint16_t desired_settings = BME680_OST_SEL | BME680_OSP_SEL | BME680_OSH_SEL | BME680_GAS_SENSOR_SEL;
|
||||||
this->bme680_status_ = bme680_set_sensor_settings(desired_settings, &this->bme680_);
|
this->bme680_status_ = bme680_set_sensor_settings(desired_settings, &this->bme680_);
|
||||||
|
@ -218,19 +234,26 @@ void BME680BSECComponent::run_() {
|
||||||
|
|
||||||
uint16_t meas_dur = 0;
|
uint16_t meas_dur = 0;
|
||||||
bme680_get_profile_dur(&meas_dur, &this->bme680_);
|
bme680_get_profile_dur(&meas_dur, &this->bme680_);
|
||||||
|
|
||||||
|
// Since we are about to go "out of scope" in the loop, take a snapshot of the state now so we can restore it later
|
||||||
|
// TODO: it would be interesting to see if this is really needed here, or if it's needed only after each
|
||||||
|
// bsec_do_steps() call
|
||||||
|
if (BME680BSECComponent::instances.size() > 1)
|
||||||
|
this->snapshot_state_();
|
||||||
|
|
||||||
ESP_LOGV(TAG, "Queueing read in %ums", meas_dur);
|
ESP_LOGV(TAG, "Queueing read in %ums", meas_dur);
|
||||||
this->set_timeout("read", meas_dur,
|
this->set_timeout("read", meas_dur, [this]() { this->read_(); });
|
||||||
[this, curr_time_ns, bme680_settings]() { this->read_(curr_time_ns, bme680_settings); });
|
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGV(TAG, "Measurement not required");
|
ESP_LOGV(TAG, "Measurement not required");
|
||||||
this->read_(curr_time_ns, bme680_settings);
|
this->read_();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void BME680BSECComponent::read_(int64_t trigger_time_ns, bsec_bme_settings_t bme680_settings) {
|
void BME680BSECComponent::read_() {
|
||||||
ESP_LOGV(TAG, "Reading data");
|
ESP_LOGV(TAG, "%s: Reading data", this->device_id_.c_str());
|
||||||
|
int64_t curr_time_ns = this->get_time_ns_();
|
||||||
|
|
||||||
if (bme680_settings.trigger_measurement) {
|
if (this->bme680_settings_.trigger_measurement) {
|
||||||
while (this->bme680_.power_mode != BME680_SLEEP_MODE) {
|
while (this->bme680_.power_mode != BME680_SLEEP_MODE) {
|
||||||
this->bme680_status_ = bme680_get_sensor_mode(&this->bme680_);
|
this->bme680_status_ = bme680_get_sensor_mode(&this->bme680_);
|
||||||
if (this->bme680_status_ != BME680_OK) {
|
if (this->bme680_status_ != BME680_OK) {
|
||||||
|
@ -239,7 +262,7 @@ void BME680BSECComponent::read_(int64_t trigger_time_ns, bsec_bme_settings_t bme
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!bme680_settings.process_data) {
|
if (!this->bme680_settings_.process_data) {
|
||||||
ESP_LOGV(TAG, "Data processing not required");
|
ESP_LOGV(TAG, "Data processing not required");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -259,35 +282,35 @@ void BME680BSECComponent::read_(int64_t trigger_time_ns, bsec_bme_settings_t bme
|
||||||
bsec_input_t inputs[BSEC_MAX_PHYSICAL_SENSOR]; // Temperature, Pressure, Humidity & Gas Resistance
|
bsec_input_t inputs[BSEC_MAX_PHYSICAL_SENSOR]; // Temperature, Pressure, Humidity & Gas Resistance
|
||||||
uint8_t num_inputs = 0;
|
uint8_t num_inputs = 0;
|
||||||
|
|
||||||
if (bme680_settings.process_data & BSEC_PROCESS_TEMPERATURE) {
|
if (this->bme680_settings_.process_data & BSEC_PROCESS_TEMPERATURE) {
|
||||||
inputs[num_inputs].sensor_id = BSEC_INPUT_TEMPERATURE;
|
inputs[num_inputs].sensor_id = BSEC_INPUT_TEMPERATURE;
|
||||||
inputs[num_inputs].signal = data.temperature / 100.0f;
|
inputs[num_inputs].signal = data.temperature / 100.0f;
|
||||||
inputs[num_inputs].time_stamp = trigger_time_ns;
|
inputs[num_inputs].time_stamp = curr_time_ns;
|
||||||
num_inputs++;
|
num_inputs++;
|
||||||
|
|
||||||
// Temperature offset from the real temperature due to external heat sources
|
// Temperature offset from the real temperature due to external heat sources
|
||||||
inputs[num_inputs].sensor_id = BSEC_INPUT_HEATSOURCE;
|
inputs[num_inputs].sensor_id = BSEC_INPUT_HEATSOURCE;
|
||||||
inputs[num_inputs].signal = this->temperature_offset_;
|
inputs[num_inputs].signal = this->temperature_offset_;
|
||||||
inputs[num_inputs].time_stamp = trigger_time_ns;
|
inputs[num_inputs].time_stamp = curr_time_ns;
|
||||||
num_inputs++;
|
num_inputs++;
|
||||||
}
|
}
|
||||||
if (bme680_settings.process_data & BSEC_PROCESS_HUMIDITY) {
|
if (this->bme680_settings_.process_data & BSEC_PROCESS_HUMIDITY) {
|
||||||
inputs[num_inputs].sensor_id = BSEC_INPUT_HUMIDITY;
|
inputs[num_inputs].sensor_id = BSEC_INPUT_HUMIDITY;
|
||||||
inputs[num_inputs].signal = data.humidity / 1000.0f;
|
inputs[num_inputs].signal = data.humidity / 1000.0f;
|
||||||
inputs[num_inputs].time_stamp = trigger_time_ns;
|
inputs[num_inputs].time_stamp = curr_time_ns;
|
||||||
num_inputs++;
|
num_inputs++;
|
||||||
}
|
}
|
||||||
if (bme680_settings.process_data & BSEC_PROCESS_PRESSURE) {
|
if (this->bme680_settings_.process_data & BSEC_PROCESS_PRESSURE) {
|
||||||
inputs[num_inputs].sensor_id = BSEC_INPUT_PRESSURE;
|
inputs[num_inputs].sensor_id = BSEC_INPUT_PRESSURE;
|
||||||
inputs[num_inputs].signal = data.pressure;
|
inputs[num_inputs].signal = data.pressure;
|
||||||
inputs[num_inputs].time_stamp = trigger_time_ns;
|
inputs[num_inputs].time_stamp = curr_time_ns;
|
||||||
num_inputs++;
|
num_inputs++;
|
||||||
}
|
}
|
||||||
if (bme680_settings.process_data & BSEC_PROCESS_GAS) {
|
if (this->bme680_settings_.process_data & BSEC_PROCESS_GAS) {
|
||||||
if (data.status & BME680_GASM_VALID_MSK) {
|
if (data.status & BME680_GASM_VALID_MSK) {
|
||||||
inputs[num_inputs].sensor_id = BSEC_INPUT_GASRESISTOR;
|
inputs[num_inputs].sensor_id = BSEC_INPUT_GASRESISTOR;
|
||||||
inputs[num_inputs].signal = data.gas_resistance;
|
inputs[num_inputs].signal = data.gas_resistance;
|
||||||
inputs[num_inputs].time_stamp = trigger_time_ns;
|
inputs[num_inputs].time_stamp = curr_time_ns;
|
||||||
num_inputs++;
|
num_inputs++;
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGD(TAG, "BME680 did not report gas data");
|
ESP_LOGD(TAG, "BME680 did not report gas data");
|
||||||
|
@ -298,6 +321,22 @@ void BME680BSECComponent::read_(int64_t trigger_time_ns, bsec_bme_settings_t bme
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Restore BSEC library state
|
||||||
|
// The reinit_bsec_lib_ method is computationally expensive: it takes 1200÷2900 microseconds on a ESP32.
|
||||||
|
// It can be skipped entirely when there is only one device (since the BSEC library won't be shared)
|
||||||
|
if (BME680BSECComponent::instances.size() > 1) {
|
||||||
|
int res = this->reinit_bsec_lib_();
|
||||||
|
if (res != 0)
|
||||||
|
return;
|
||||||
|
// Now that the BSEC library has been re-initialized, bsec_sensor_control *NEEDS* to be called in order to support
|
||||||
|
// multiple devices with a different set of enabled sensors (even if the bme680_settings_ data is not used)
|
||||||
|
this->bsec_status_ = bsec_sensor_control(curr_time_ns, &this->bme680_settings_);
|
||||||
|
if (this->bsec_status_ < BSEC_OK) {
|
||||||
|
ESP_LOGW(TAG, "Failed to fetch sensor control settings (BSEC Error Code %d)", this->bsec_status_);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bsec_output_t outputs[BSEC_NUMBER_OUTPUTS];
|
bsec_output_t outputs[BSEC_NUMBER_OUTPUTS];
|
||||||
uint8_t num_outputs = BSEC_NUMBER_OUTPUTS;
|
uint8_t num_outputs = BSEC_NUMBER_OUTPUTS;
|
||||||
this->bsec_status_ = bsec_do_steps(inputs, num_inputs, outputs, &num_outputs);
|
this->bsec_status_ = bsec_do_steps(inputs, num_inputs, outputs, &num_outputs);
|
||||||
|
@ -305,6 +344,13 @@ void BME680BSECComponent::read_(int64_t trigger_time_ns, bsec_bme_settings_t bme
|
||||||
ESP_LOGW(TAG, "BSEC failed to process signals (BSEC Error Code %d)", this->bsec_status_);
|
ESP_LOGW(TAG, "BSEC failed to process signals (BSEC Error Code %d)", this->bsec_status_);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
ESP_LOGV(TAG, "%s: after bsec_do_steps: num_inputs=%d num_outputs=%d", this->device_id_.c_str(), num_inputs,
|
||||||
|
num_outputs);
|
||||||
|
|
||||||
|
// Since we are about to go "out of scope" in the loop, take a snapshot of the state now so we can restore it later
|
||||||
|
if (BME680BSECComponent::instances.size() > 1)
|
||||||
|
this->snapshot_state_();
|
||||||
|
|
||||||
if (num_outputs < 1) {
|
if (num_outputs < 1) {
|
||||||
ESP_LOGD(TAG, "No signal outputs provided by BSEC");
|
ESP_LOGD(TAG, "No signal outputs provided by BSEC");
|
||||||
return;
|
return;
|
||||||
|
@ -314,7 +360,7 @@ void BME680BSECComponent::read_(int64_t trigger_time_ns, bsec_bme_settings_t bme
|
||||||
}
|
}
|
||||||
|
|
||||||
void BME680BSECComponent::publish_(const bsec_output_t *outputs, uint8_t num_outputs) {
|
void BME680BSECComponent::publish_(const bsec_output_t *outputs, uint8_t num_outputs) {
|
||||||
ESP_LOGV(TAG, "Queuing sensor state publish actions");
|
ESP_LOGV(TAG, "%s: Queuing sensor state publish actions", this->device_id_.c_str());
|
||||||
for (uint8_t i = 0; i < num_outputs; i++) {
|
for (uint8_t i = 0; i < num_outputs; i++) {
|
||||||
float signal = outputs[i].signal;
|
float signal = outputs[i].signal;
|
||||||
switch (outputs[i].sensor_id) {
|
switch (outputs[i].sensor_id) {
|
||||||
|
@ -376,12 +422,20 @@ void BME680BSECComponent::publish_sensor_(text_sensor::TextSensor *sensor, const
|
||||||
sensor->publish_state(value);
|
sensor->publish_state(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
int8_t BME680BSECComponent::read_bytes_wrapper(uint8_t address, uint8_t a_register, uint8_t *data, uint16_t len) {
|
// Communication function - read
|
||||||
return BME680BSECComponent::instance->read_bytes(a_register, data, len) ? 0 : -1;
|
// First parameter is the "dev_id" member of our "bme680_" object, which is passed-back here as-is
|
||||||
|
int8_t BME680BSECComponent::read_bytes_wrapper(uint8_t devid, uint8_t a_register, uint8_t *data, uint16_t len) {
|
||||||
|
BME680BSECComponent *inst = instances[devid];
|
||||||
|
// Use the I2CDevice::read_bytes method to perform the actual I2C register read
|
||||||
|
return inst->read_bytes(a_register, data, len) ? 0 : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int8_t BME680BSECComponent::write_bytes_wrapper(uint8_t address, uint8_t a_register, uint8_t *data, uint16_t len) {
|
// Communication function - write
|
||||||
return BME680BSECComponent::instance->write_bytes(a_register, data, len) ? 0 : -1;
|
// First parameter is the "dev_id" member of our "bme680_" object, which is passed-back here as-is
|
||||||
|
int8_t BME680BSECComponent::write_bytes_wrapper(uint8_t devid, uint8_t a_register, uint8_t *data, uint16_t len) {
|
||||||
|
BME680BSECComponent *inst = instances[devid];
|
||||||
|
// Use the I2CDevice::write_bytes method to perform the actual I2C register write
|
||||||
|
return inst->write_bytes(a_register, data, len) ? 0 : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BME680BSECComponent::delay_ms(uint32_t period) {
|
void BME680BSECComponent::delay_ms(uint32_t period) {
|
||||||
|
@ -389,41 +443,97 @@ void BME680BSECComponent::delay_ms(uint32_t period) {
|
||||||
delay(period);
|
delay(period);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fetch the BSEC library state and save it in the bsec_state_data_ member (volatile memory)
|
||||||
|
// Used to share the library when using more than one sensor
|
||||||
|
void BME680BSECComponent::snapshot_state_() {
|
||||||
|
uint32_t num_serialized_state = BSEC_MAX_STATE_BLOB_SIZE;
|
||||||
|
this->bsec_status_ = bsec_get_state(0, this->bsec_state_data_, BSEC_MAX_STATE_BLOB_SIZE, this->work_buffer_,
|
||||||
|
sizeof(this->work_buffer_), &num_serialized_state);
|
||||||
|
if (this->bsec_status_ != BSEC_OK) {
|
||||||
|
ESP_LOGW(TAG, "%s: Failed to fetch BSEC library state for snapshot (BSEC Error Code %d)", this->device_id_.c_str(),
|
||||||
|
this->bsec_status_);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->bsec_state_data_valid_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restores the BSEC library state from a snapshot in memory
|
||||||
|
// Used to share the library when using more than one sensor
|
||||||
|
void BME680BSECComponent::restore_state_() {
|
||||||
|
if (!this->bsec_state_data_valid_) {
|
||||||
|
ESP_LOGV(TAG, "%s: BSEC state data NOT valid, aborting restore_state_()", this->device_id_.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->bsec_status_ =
|
||||||
|
bsec_set_state(this->bsec_state_data_, BSEC_MAX_STATE_BLOB_SIZE, this->work_buffer_, sizeof(this->work_buffer_));
|
||||||
|
if (this->bsec_status_ != BSEC_OK) {
|
||||||
|
ESP_LOGW(TAG, "Failed to restore BSEC library state (BSEC Error Code %d)", this->bsec_status_);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int BME680BSECComponent::reinit_bsec_lib_() {
|
||||||
|
this->bsec_status_ = bsec_init();
|
||||||
|
if (this->bsec_status_ != BSEC_OK) {
|
||||||
|
this->mark_failed();
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->set_config_();
|
||||||
|
if (this->bsec_status_ != BSEC_OK) {
|
||||||
|
this->mark_failed();
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->restore_state_();
|
||||||
|
|
||||||
|
this->update_subscription_();
|
||||||
|
if (this->bsec_status_ != BSEC_OK) {
|
||||||
|
this->mark_failed();
|
||||||
|
return -3;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
void BME680BSECComponent::load_state_() {
|
void BME680BSECComponent::load_state_() {
|
||||||
uint32_t hash = fnv1_hash("bme680_bsec_state_" + to_string(this->address_));
|
uint32_t hash = fnv1_hash("bme680_bsec_state_" + this->device_id_);
|
||||||
this->bsec_state_ = global_preferences->make_preference<uint8_t[BSEC_MAX_STATE_BLOB_SIZE]>(hash, true);
|
this->bsec_state_ = global_preferences->make_preference<uint8_t[BSEC_MAX_STATE_BLOB_SIZE]>(hash, true);
|
||||||
|
|
||||||
uint8_t state[BSEC_MAX_STATE_BLOB_SIZE];
|
if (!this->bsec_state_.load(&this->bsec_state_data_)) {
|
||||||
if (this->bsec_state_.load(&state)) {
|
// No saved BSEC library state available
|
||||||
ESP_LOGV(TAG, "Loading state");
|
return;
|
||||||
uint8_t work_buffer[BSEC_MAX_WORKBUFFER_SIZE];
|
}
|
||||||
this->bsec_status_ = bsec_set_state(state, BSEC_MAX_STATE_BLOB_SIZE, work_buffer, sizeof(work_buffer));
|
|
||||||
|
ESP_LOGV(TAG, "%s: Loading BSEC library state", this->device_id_.c_str());
|
||||||
|
this->bsec_status_ =
|
||||||
|
bsec_set_state(this->bsec_state_data_, BSEC_MAX_STATE_BLOB_SIZE, this->work_buffer_, sizeof(this->work_buffer_));
|
||||||
if (this->bsec_status_ != BSEC_OK) {
|
if (this->bsec_status_ != BSEC_OK) {
|
||||||
ESP_LOGW(TAG, "Failed to load state (BSEC Error Code %d)", this->bsec_status_);
|
ESP_LOGW(TAG, "%s: Failed to load BSEC library state (BSEC Error Code %d)", this->device_id_.c_str(),
|
||||||
}
|
this->bsec_status_);
|
||||||
ESP_LOGI(TAG, "Loaded state");
|
return;
|
||||||
}
|
}
|
||||||
|
// All OK: set the BSEC state data as valid
|
||||||
|
this->bsec_state_data_valid_ = true;
|
||||||
|
ESP_LOGI(TAG, "%s: Loaded BSEC library state", this->device_id_.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
void BME680BSECComponent::save_state_(uint8_t accuracy) {
|
void BME680BSECComponent::save_state_(uint8_t accuracy) {
|
||||||
if (accuracy < 3 || (millis() - this->last_state_save_ms_ < this->state_save_interval_ms_)) {
|
if (accuracy < 3 || (millis() - this->last_state_save_ms_ < this->state_save_interval_ms_)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (BME680BSECComponent::instances.size() <= 1) {
|
||||||
ESP_LOGV(TAG, "Saving state");
|
// When a single device is in use, no snapshot is taken regularly so one is taken now
|
||||||
|
// On multiple devices, a snapshot is taken at every loop, so there is no need to take one here
|
||||||
uint8_t state[BSEC_MAX_STATE_BLOB_SIZE];
|
this->snapshot_state_();
|
||||||
uint8_t work_buffer[BSEC_MAX_STATE_BLOB_SIZE];
|
|
||||||
uint32_t num_serialized_state = BSEC_MAX_STATE_BLOB_SIZE;
|
|
||||||
|
|
||||||
this->bsec_status_ =
|
|
||||||
bsec_get_state(0, state, BSEC_MAX_STATE_BLOB_SIZE, work_buffer, BSEC_MAX_STATE_BLOB_SIZE, &num_serialized_state);
|
|
||||||
if (this->bsec_status_ != BSEC_OK) {
|
|
||||||
ESP_LOGW(TAG, "Failed fetch state for save (BSEC Error Code %d)", this->bsec_status_);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
if (!this->bsec_state_data_valid_)
|
||||||
|
return;
|
||||||
|
|
||||||
if (!this->bsec_state_.save(&state)) {
|
ESP_LOGV(TAG, "%s: Saving state", this->device_id_.c_str());
|
||||||
|
|
||||||
|
if (!this->bsec_state_.save(&this->bsec_state_data_)) {
|
||||||
ESP_LOGW(TAG, "Failed to save state");
|
ESP_LOGW(TAG, "Failed to save state");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,7 @@ enum SampleRate {
|
||||||
|
|
||||||
class BME680BSECComponent : public Component, public i2c::I2CDevice {
|
class BME680BSECComponent : public Component, public i2c::I2CDevice {
|
||||||
public:
|
public:
|
||||||
|
void set_device_id(const std::string &devid) { this->device_id_.assign(devid); }
|
||||||
void set_temperature_offset(float offset) { this->temperature_offset_ = offset; }
|
void set_temperature_offset(float offset) { this->temperature_offset_ = offset; }
|
||||||
void set_iaq_mode(IAQMode iaq_mode) { this->iaq_mode_ = iaq_mode; }
|
void set_iaq_mode(IAQMode iaq_mode) { this->iaq_mode_ = iaq_mode; }
|
||||||
void set_state_save_interval(uint32_t interval) { this->state_save_interval_ms_ = interval; }
|
void set_state_save_interval(uint32_t interval) { this->state_save_interval_ms_ = interval; }
|
||||||
|
@ -50,9 +51,9 @@ class BME680BSECComponent : public Component, public i2c::I2CDevice {
|
||||||
void set_co2_equivalent_sensor(sensor::Sensor *sensor) { this->co2_equivalent_sensor_ = sensor; }
|
void set_co2_equivalent_sensor(sensor::Sensor *sensor) { this->co2_equivalent_sensor_ = sensor; }
|
||||||
void set_breath_voc_equivalent_sensor(sensor::Sensor *sensor) { this->breath_voc_equivalent_sensor_ = sensor; }
|
void set_breath_voc_equivalent_sensor(sensor::Sensor *sensor) { this->breath_voc_equivalent_sensor_ = sensor; }
|
||||||
|
|
||||||
static BME680BSECComponent *instance;
|
static std::vector<BME680BSECComponent *> instances;
|
||||||
static int8_t read_bytes_wrapper(uint8_t address, uint8_t a_register, uint8_t *data, uint16_t len);
|
static int8_t read_bytes_wrapper(uint8_t devid, uint8_t a_register, uint8_t *data, uint16_t len);
|
||||||
static int8_t write_bytes_wrapper(uint8_t address, uint8_t a_register, uint8_t *data, uint16_t len);
|
static int8_t write_bytes_wrapper(uint8_t devid, uint8_t a_register, uint8_t *data, uint16_t len);
|
||||||
static void delay_ms(uint32_t period);
|
static void delay_ms(uint32_t period);
|
||||||
|
|
||||||
void setup() override;
|
void setup() override;
|
||||||
|
@ -61,23 +62,33 @@ class BME680BSECComponent : public Component, public i2c::I2CDevice {
|
||||||
void loop() override;
|
void loop() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void set_config_(const uint8_t *config);
|
void set_config_();
|
||||||
float calc_sensor_sample_rate_(SampleRate sample_rate);
|
float calc_sensor_sample_rate_(SampleRate sample_rate);
|
||||||
void update_subscription_();
|
void update_subscription_();
|
||||||
|
|
||||||
void run_();
|
void run_();
|
||||||
void read_(int64_t trigger_time_ns, bsec_bme_settings_t bme680_settings);
|
void read_();
|
||||||
void publish_(const bsec_output_t *outputs, uint8_t num_outputs);
|
void publish_(const bsec_output_t *outputs, uint8_t num_outputs);
|
||||||
int64_t get_time_ns_();
|
int64_t get_time_ns_();
|
||||||
|
|
||||||
void publish_sensor_(sensor::Sensor *sensor, float value, bool change_only = false);
|
void publish_sensor_(sensor::Sensor *sensor, float value, bool change_only = false);
|
||||||
void publish_sensor_(text_sensor::TextSensor *sensor, const std::string &value);
|
void publish_sensor_(text_sensor::TextSensor *sensor, const std::string &value);
|
||||||
|
|
||||||
void load_state_();
|
void snapshot_state_(); // Fetch the current BSEC library state and save it in the bsec_state_data_ member (volatile
|
||||||
void save_state_(uint8_t accuracy);
|
// memory)
|
||||||
|
void restore_state_(); // Push the state contained in the bsec_state_data_ member (volatile memory) to the BSEC
|
||||||
|
// library
|
||||||
|
int reinit_bsec_lib_(); // Prepare the BSEC library to be used again after this object returns active
|
||||||
|
// (as the library may have been used by other objects)
|
||||||
|
void load_state_(); // Initialize the ESP preferences object; retrieve the BSEC library state from the ESP
|
||||||
|
// preferences (storage); then save it in the bsec_state_data_ member (volatile memory) and
|
||||||
|
// push it to the BSEC library
|
||||||
|
void save_state_(
|
||||||
|
uint8_t accuracy); // Save the bsec_state_data_ member (volatile memory) to the ESP preferences (storage)
|
||||||
|
|
||||||
void queue_push_(std::function<void()> &&f) { this->queue_.push(std::move(f)); }
|
void queue_push_(std::function<void()> &&f) { this->queue_.push(std::move(f)); }
|
||||||
|
|
||||||
|
static uint8_t work_buffer_[BSEC_MAX_WORKBUFFER_SIZE];
|
||||||
struct bme680_dev bme680_;
|
struct bme680_dev bme680_;
|
||||||
bsec_library_return_t bsec_status_{BSEC_OK};
|
bsec_library_return_t bsec_status_{BSEC_OK};
|
||||||
int8_t bme680_status_{BME680_OK};
|
int8_t bme680_status_{BME680_OK};
|
||||||
|
@ -88,10 +99,14 @@ class BME680BSECComponent : public Component, public i2c::I2CDevice {
|
||||||
|
|
||||||
std::queue<std::function<void()>> queue_;
|
std::queue<std::function<void()>> queue_;
|
||||||
|
|
||||||
|
bool bsec_state_data_valid_;
|
||||||
|
uint8_t bsec_state_data_[BSEC_MAX_STATE_BLOB_SIZE]; // This is the current snapshot of the BSEC library state
|
||||||
ESPPreferenceObject bsec_state_;
|
ESPPreferenceObject bsec_state_;
|
||||||
uint32_t state_save_interval_ms_{21600000}; // 6 hours - 4 times a day
|
uint32_t state_save_interval_ms_{21600000}; // 6 hours - 4 times a day
|
||||||
uint32_t last_state_save_ms_ = 0;
|
uint32_t last_state_save_ms_ = 0;
|
||||||
|
bsec_bme_settings_t bme680_settings_;
|
||||||
|
|
||||||
|
std::string device_id_;
|
||||||
float temperature_offset_{0};
|
float temperature_offset_{0};
|
||||||
IAQMode iaq_mode_{IAQ_MODE_STATIC};
|
IAQMode iaq_mode_{IAQ_MODE_STATIC};
|
||||||
|
|
||||||
|
|
|
@ -11,16 +11,19 @@ from esphome.const import (
|
||||||
CONF_ON_PRESS,
|
CONF_ON_PRESS,
|
||||||
CONF_TRIGGER_ID,
|
CONF_TRIGGER_ID,
|
||||||
CONF_MQTT_ID,
|
CONF_MQTT_ID,
|
||||||
|
DEVICE_CLASS_EMPTY,
|
||||||
DEVICE_CLASS_RESTART,
|
DEVICE_CLASS_RESTART,
|
||||||
DEVICE_CLASS_UPDATE,
|
DEVICE_CLASS_UPDATE,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, coroutine_with_priority
|
from esphome.core import CORE, coroutine_with_priority
|
||||||
from esphome.cpp_helpers import setup_entity
|
from esphome.cpp_helpers import setup_entity
|
||||||
|
from esphome.cpp_generator import MockObjClass
|
||||||
|
|
||||||
CODEOWNERS = ["@esphome/core"]
|
CODEOWNERS = ["@esphome/core"]
|
||||||
IS_PLATFORM_COMPONENT = True
|
IS_PLATFORM_COMPONENT = True
|
||||||
|
|
||||||
DEVICE_CLASSES = [
|
DEVICE_CLASSES = [
|
||||||
|
DEVICE_CLASS_EMPTY,
|
||||||
DEVICE_CLASS_RESTART,
|
DEVICE_CLASS_RESTART,
|
||||||
DEVICE_CLASS_UPDATE,
|
DEVICE_CLASS_UPDATE,
|
||||||
]
|
]
|
||||||
|
@ -54,30 +57,23 @@ _UNDEF = object()
|
||||||
|
|
||||||
|
|
||||||
def button_schema(
|
def button_schema(
|
||||||
|
class_: MockObjClass,
|
||||||
|
*,
|
||||||
icon: str = _UNDEF,
|
icon: str = _UNDEF,
|
||||||
entity_category: str = _UNDEF,
|
entity_category: str = _UNDEF,
|
||||||
device_class: str = _UNDEF,
|
device_class: str = _UNDEF,
|
||||||
) -> cv.Schema:
|
) -> cv.Schema:
|
||||||
schema = BUTTON_SCHEMA
|
schema = {cv.GenerateID(): cv.declare_id(class_)}
|
||||||
if icon is not _UNDEF:
|
|
||||||
schema = schema.extend({cv.Optional(CONF_ICON, default=icon): cv.icon})
|
for key, default, validator in [
|
||||||
if entity_category is not _UNDEF:
|
(CONF_ICON, icon, cv.icon),
|
||||||
schema = schema.extend(
|
(CONF_ENTITY_CATEGORY, entity_category, cv.entity_category),
|
||||||
{
|
(CONF_DEVICE_CLASS, device_class, validate_device_class),
|
||||||
cv.Optional(
|
]:
|
||||||
CONF_ENTITY_CATEGORY, default=entity_category
|
if default is not _UNDEF:
|
||||||
): cv.entity_category
|
schema[cv.Optional(key, default=default)] = validator
|
||||||
}
|
|
||||||
)
|
return BUTTON_SCHEMA.extend(schema)
|
||||||
if device_class is not _UNDEF:
|
|
||||||
schema = schema.extend(
|
|
||||||
{
|
|
||||||
cv.Optional(
|
|
||||||
CONF_DEVICE_CLASS, default=device_class
|
|
||||||
): validate_device_class
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return schema
|
|
||||||
|
|
||||||
|
|
||||||
async def setup_button_core_(var, config):
|
async def setup_button_core_(var, config):
|
||||||
|
|
|
@ -15,6 +15,13 @@ namespace button {
|
||||||
} \
|
} \
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define SUB_BUTTON(name) \
|
||||||
|
protected: \
|
||||||
|
button::Button *name##_button_{nullptr}; \
|
||||||
|
\
|
||||||
|
public: \
|
||||||
|
void set_##name##_button(button::Button *button) { this->name##_button_ = button; }
|
||||||
|
|
||||||
/** Base class for all buttons.
|
/** Base class for all buttons.
|
||||||
*
|
*
|
||||||
* A button is just a momentary switch that does not have a state, only a trigger.
|
* A button is just a momentary switch that does not have a state, only a trigger.
|
||||||
|
|
|
@ -20,6 +20,7 @@ from esphome.const import (
|
||||||
CONF_MODE,
|
CONF_MODE,
|
||||||
CONF_MODE_COMMAND_TOPIC,
|
CONF_MODE_COMMAND_TOPIC,
|
||||||
CONF_MODE_STATE_TOPIC,
|
CONF_MODE_STATE_TOPIC,
|
||||||
|
CONF_ON_CONTROL,
|
||||||
CONF_ON_STATE,
|
CONF_ON_STATE,
|
||||||
CONF_PRESET,
|
CONF_PRESET,
|
||||||
CONF_PRESET_COMMAND_TOPIC,
|
CONF_PRESET_COMMAND_TOPIC,
|
||||||
|
@ -104,9 +105,40 @@ CLIMATE_SWING_MODES = {
|
||||||
|
|
||||||
validate_climate_swing_mode = cv.enum(CLIMATE_SWING_MODES, upper=True)
|
validate_climate_swing_mode = cv.enum(CLIMATE_SWING_MODES, upper=True)
|
||||||
|
|
||||||
|
CONF_CURRENT_TEMPERATURE = "current_temperature"
|
||||||
|
|
||||||
|
visual_temperature = cv.float_with_unit(
|
||||||
|
"visual_temperature", "(°C|° C|°|C|° K|° K|K|°F|° F|F)?"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def single_visual_temperature(value):
|
||||||
|
if isinstance(value, dict):
|
||||||
|
return value
|
||||||
|
|
||||||
|
value = visual_temperature(value)
|
||||||
|
return VISUAL_TEMPERATURE_STEP_SCHEMA(
|
||||||
|
{
|
||||||
|
CONF_TARGET_TEMPERATURE: value,
|
||||||
|
CONF_CURRENT_TEMPERATURE: value,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# Actions
|
# Actions
|
||||||
ControlAction = climate_ns.class_("ControlAction", automation.Action)
|
ControlAction = climate_ns.class_("ControlAction", automation.Action)
|
||||||
StateTrigger = climate_ns.class_("StateTrigger", automation.Trigger.template())
|
StateTrigger = climate_ns.class_("StateTrigger", automation.Trigger.template())
|
||||||
|
ControlTrigger = climate_ns.class_("ControlTrigger", automation.Trigger.template())
|
||||||
|
|
||||||
|
VISUAL_TEMPERATURE_STEP_SCHEMA = cv.Any(
|
||||||
|
single_visual_temperature,
|
||||||
|
cv.Schema(
|
||||||
|
{
|
||||||
|
cv.Required(CONF_TARGET_TEMPERATURE): visual_temperature,
|
||||||
|
cv.Required(CONF_CURRENT_TEMPERATURE): visual_temperature,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
|
CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
|
||||||
{
|
{
|
||||||
|
@ -116,9 +148,7 @@ CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).
|
||||||
{
|
{
|
||||||
cv.Optional(CONF_MIN_TEMPERATURE): cv.temperature,
|
cv.Optional(CONF_MIN_TEMPERATURE): cv.temperature,
|
||||||
cv.Optional(CONF_MAX_TEMPERATURE): cv.temperature,
|
cv.Optional(CONF_MAX_TEMPERATURE): cv.temperature,
|
||||||
cv.Optional(CONF_TEMPERATURE_STEP): cv.float_with_unit(
|
cv.Optional(CONF_TEMPERATURE_STEP): VISUAL_TEMPERATURE_STEP_SCHEMA,
|
||||||
"visual_temperature", "(°C|° C|°|C|° K|° K|K|°F|° F|F)?"
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_ACTION_STATE_TOPIC): cv.All(
|
cv.Optional(CONF_ACTION_STATE_TOPIC): cv.All(
|
||||||
|
@ -175,6 +205,11 @@ CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).
|
||||||
cv.Optional(CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC): cv.All(
|
cv.Optional(CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC): cv.All(
|
||||||
cv.requires_component("mqtt"), cv.publish_topic
|
cv.requires_component("mqtt"), cv.publish_topic
|
||||||
),
|
),
|
||||||
|
cv.Optional(CONF_ON_CONTROL): automation.validate_automation(
|
||||||
|
{
|
||||||
|
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ControlTrigger),
|
||||||
|
}
|
||||||
|
),
|
||||||
cv.Optional(CONF_ON_STATE): automation.validate_automation(
|
cv.Optional(CONF_ON_STATE): automation.validate_automation(
|
||||||
{
|
{
|
||||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger),
|
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger),
|
||||||
|
@ -193,7 +228,12 @@ async def setup_climate_core_(var, config):
|
||||||
if CONF_MAX_TEMPERATURE in visual:
|
if CONF_MAX_TEMPERATURE in visual:
|
||||||
cg.add(var.set_visual_max_temperature_override(visual[CONF_MAX_TEMPERATURE]))
|
cg.add(var.set_visual_max_temperature_override(visual[CONF_MAX_TEMPERATURE]))
|
||||||
if CONF_TEMPERATURE_STEP in visual:
|
if CONF_TEMPERATURE_STEP in visual:
|
||||||
cg.add(var.set_visual_temperature_step_override(visual[CONF_TEMPERATURE_STEP]))
|
cg.add(
|
||||||
|
var.set_visual_temperature_step_override(
|
||||||
|
visual[CONF_TEMPERATURE_STEP][CONF_TARGET_TEMPERATURE],
|
||||||
|
visual[CONF_TEMPERATURE_STEP][CONF_CURRENT_TEMPERATURE],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
if CONF_MQTT_ID in config:
|
if CONF_MQTT_ID in config:
|
||||||
mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var)
|
mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var)
|
||||||
|
|
|
@ -42,6 +42,13 @@ template<typename... Ts> class ControlAction : public Action<Ts...> {
|
||||||
Climate *climate_;
|
Climate *climate_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class ControlTrigger : public Trigger<> {
|
||||||
|
public:
|
||||||
|
ControlTrigger(Climate *climate) {
|
||||||
|
climate->add_on_control_callback([this]() { this->trigger(); });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
class StateTrigger : public Trigger<> {
|
class StateTrigger : public Trigger<> {
|
||||||
public:
|
public:
|
||||||
StateTrigger(Climate *climate) {
|
StateTrigger(Climate *climate) {
|
||||||
|
|
|
@ -44,6 +44,7 @@ void ClimateCall::perform() {
|
||||||
if (this->target_temperature_high_.has_value()) {
|
if (this->target_temperature_high_.has_value()) {
|
||||||
ESP_LOGD(TAG, " Target Temperature High: %.2f", *this->target_temperature_high_);
|
ESP_LOGD(TAG, " Target Temperature High: %.2f", *this->target_temperature_high_);
|
||||||
}
|
}
|
||||||
|
this->parent_->control_callback_.call();
|
||||||
this->parent_->control(*this);
|
this->parent_->control(*this);
|
||||||
}
|
}
|
||||||
void ClimateCall::validate_() {
|
void ClimateCall::validate_() {
|
||||||
|
@ -317,6 +318,10 @@ void Climate::add_on_state_callback(std::function<void()> &&callback) {
|
||||||
this->state_callback_.add(std::move(callback));
|
this->state_callback_.add(std::move(callback));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Climate::add_on_control_callback(std::function<void()> &&callback) {
|
||||||
|
this->control_callback_.add(std::move(callback));
|
||||||
|
}
|
||||||
|
|
||||||
// Random 32bit value; If this changes existing restore preferences are invalidated
|
// Random 32bit value; If this changes existing restore preferences are invalidated
|
||||||
static const uint32_t RESTORE_STATE_VERSION = 0x848EA6ADUL;
|
static const uint32_t RESTORE_STATE_VERSION = 0x848EA6ADUL;
|
||||||
|
|
||||||
|
@ -430,9 +435,11 @@ ClimateTraits Climate::get_traits() {
|
||||||
if (this->visual_max_temperature_override_.has_value()) {
|
if (this->visual_max_temperature_override_.has_value()) {
|
||||||
traits.set_visual_max_temperature(*this->visual_max_temperature_override_);
|
traits.set_visual_max_temperature(*this->visual_max_temperature_override_);
|
||||||
}
|
}
|
||||||
if (this->visual_temperature_step_override_.has_value()) {
|
if (this->visual_target_temperature_step_override_.has_value()) {
|
||||||
traits.set_visual_temperature_step(*this->visual_temperature_step_override_);
|
traits.set_visual_target_temperature_step(*this->visual_target_temperature_step_override_);
|
||||||
|
traits.set_visual_current_temperature_step(*this->visual_current_temperature_step_override_);
|
||||||
}
|
}
|
||||||
|
|
||||||
return traits;
|
return traits;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -442,8 +449,9 @@ void Climate::set_visual_min_temperature_override(float visual_min_temperature_o
|
||||||
void Climate::set_visual_max_temperature_override(float visual_max_temperature_override) {
|
void Climate::set_visual_max_temperature_override(float visual_max_temperature_override) {
|
||||||
this->visual_max_temperature_override_ = visual_max_temperature_override;
|
this->visual_max_temperature_override_ = visual_max_temperature_override;
|
||||||
}
|
}
|
||||||
void Climate::set_visual_temperature_step_override(float visual_temperature_step_override) {
|
void Climate::set_visual_temperature_step_override(float target, float current) {
|
||||||
this->visual_temperature_step_override_ = visual_temperature_step_override;
|
this->visual_target_temperature_step_override_ = target;
|
||||||
|
this->visual_current_temperature_step_override_ = current;
|
||||||
}
|
}
|
||||||
#pragma GCC diagnostic push
|
#pragma GCC diagnostic push
|
||||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||||
|
@ -541,7 +549,9 @@ void Climate::dump_traits_(const char *tag) {
|
||||||
ESP_LOGCONFIG(tag, " [x] Visual settings:");
|
ESP_LOGCONFIG(tag, " [x] Visual settings:");
|
||||||
ESP_LOGCONFIG(tag, " - Min: %.1f", traits.get_visual_min_temperature());
|
ESP_LOGCONFIG(tag, " - Min: %.1f", traits.get_visual_min_temperature());
|
||||||
ESP_LOGCONFIG(tag, " - Max: %.1f", traits.get_visual_max_temperature());
|
ESP_LOGCONFIG(tag, " - Max: %.1f", traits.get_visual_max_temperature());
|
||||||
ESP_LOGCONFIG(tag, " - Step: %.1f", traits.get_visual_temperature_step());
|
ESP_LOGCONFIG(tag, " - Step:");
|
||||||
|
ESP_LOGCONFIG(tag, " Target: %.1f", traits.get_visual_target_temperature_step());
|
||||||
|
ESP_LOGCONFIG(tag, " Current: %.1f", traits.get_visual_current_temperature_step());
|
||||||
if (traits.get_supports_current_temperature()) {
|
if (traits.get_supports_current_temperature()) {
|
||||||
ESP_LOGCONFIG(tag, " [x] Supports current temperature");
|
ESP_LOGCONFIG(tag, " [x] Supports current temperature");
|
||||||
}
|
}
|
||||||
|
|
|
@ -219,6 +219,14 @@ class Climate : public EntityBase {
|
||||||
*/
|
*/
|
||||||
void add_on_state_callback(std::function<void()> &&callback);
|
void add_on_state_callback(std::function<void()> &&callback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a callback for the climate device configuration; each time the configuration parameters of a climate device
|
||||||
|
* is updated (using perform() of a ClimateCall), this callback will be called, before any on_state callback.
|
||||||
|
*
|
||||||
|
* @param callback The callback to call.
|
||||||
|
*/
|
||||||
|
void add_on_control_callback(std::function<void()> &&callback);
|
||||||
|
|
||||||
/** Make a climate device control call, this is used to control the climate device, see the ClimateCall description
|
/** Make a climate device control call, this is used to control the climate device, see the ClimateCall description
|
||||||
* for more info.
|
* for more info.
|
||||||
* @return A new ClimateCall instance targeting this climate device.
|
* @return A new ClimateCall instance targeting this climate device.
|
||||||
|
@ -241,7 +249,7 @@ class Climate : public EntityBase {
|
||||||
|
|
||||||
void set_visual_min_temperature_override(float visual_min_temperature_override);
|
void set_visual_min_temperature_override(float visual_min_temperature_override);
|
||||||
void set_visual_max_temperature_override(float visual_max_temperature_override);
|
void set_visual_max_temperature_override(float visual_max_temperature_override);
|
||||||
void set_visual_temperature_step_override(float visual_temperature_step_override);
|
void set_visual_temperature_step_override(float target, float current);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
friend ClimateCall;
|
friend ClimateCall;
|
||||||
|
@ -285,10 +293,12 @@ class Climate : public EntityBase {
|
||||||
void dump_traits_(const char *tag);
|
void dump_traits_(const char *tag);
|
||||||
|
|
||||||
CallbackManager<void()> state_callback_{};
|
CallbackManager<void()> state_callback_{};
|
||||||
|
CallbackManager<void()> control_callback_{};
|
||||||
ESPPreferenceObject rtc_;
|
ESPPreferenceObject rtc_;
|
||||||
optional<float> visual_min_temperature_override_{};
|
optional<float> visual_min_temperature_override_{};
|
||||||
optional<float> visual_max_temperature_override_{};
|
optional<float> visual_max_temperature_override_{};
|
||||||
optional<float> visual_temperature_step_override_{};
|
optional<float> visual_target_temperature_step_override_{};
|
||||||
|
optional<float> visual_current_temperature_step_override_{};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace climate
|
} // namespace climate
|
||||||
|
|
|
@ -3,8 +3,12 @@
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace climate {
|
namespace climate {
|
||||||
|
|
||||||
int8_t ClimateTraits::get_temperature_accuracy_decimals() const {
|
int8_t ClimateTraits::get_target_temperature_accuracy_decimals() const {
|
||||||
return step_to_accuracy_decimals(this->visual_temperature_step_);
|
return step_to_accuracy_decimals(this->visual_target_temperature_step_);
|
||||||
|
}
|
||||||
|
|
||||||
|
int8_t ClimateTraits::get_current_temperature_accuracy_decimals() const {
|
||||||
|
return step_to_accuracy_decimals(this->visual_current_temperature_step_);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace climate
|
} // namespace climate
|
||||||
|
|
|
@ -147,9 +147,20 @@ class ClimateTraits {
|
||||||
void set_visual_min_temperature(float visual_min_temperature) { visual_min_temperature_ = visual_min_temperature; }
|
void set_visual_min_temperature(float visual_min_temperature) { visual_min_temperature_ = visual_min_temperature; }
|
||||||
float get_visual_max_temperature() const { return visual_max_temperature_; }
|
float get_visual_max_temperature() const { return visual_max_temperature_; }
|
||||||
void set_visual_max_temperature(float visual_max_temperature) { visual_max_temperature_ = visual_max_temperature; }
|
void set_visual_max_temperature(float visual_max_temperature) { visual_max_temperature_ = visual_max_temperature; }
|
||||||
float get_visual_temperature_step() const { return visual_temperature_step_; }
|
float get_visual_target_temperature_step() const { return visual_target_temperature_step_; }
|
||||||
int8_t get_temperature_accuracy_decimals() const;
|
float get_visual_current_temperature_step() const { return visual_current_temperature_step_; }
|
||||||
void set_visual_temperature_step(float temperature_step) { visual_temperature_step_ = temperature_step; }
|
void set_visual_target_temperature_step(float temperature_step) {
|
||||||
|
visual_target_temperature_step_ = temperature_step;
|
||||||
|
}
|
||||||
|
void set_visual_current_temperature_step(float temperature_step) {
|
||||||
|
visual_current_temperature_step_ = temperature_step;
|
||||||
|
}
|
||||||
|
void set_visual_temperature_step(float temperature_step) {
|
||||||
|
visual_target_temperature_step_ = temperature_step;
|
||||||
|
visual_current_temperature_step_ = temperature_step;
|
||||||
|
}
|
||||||
|
int8_t get_target_temperature_accuracy_decimals() const;
|
||||||
|
int8_t get_current_temperature_accuracy_decimals() const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void set_mode_support_(climate::ClimateMode mode, bool supported) {
|
void set_mode_support_(climate::ClimateMode mode, bool supported) {
|
||||||
|
@ -186,7 +197,8 @@ class ClimateTraits {
|
||||||
|
|
||||||
float visual_min_temperature_{10};
|
float visual_min_temperature_{10};
|
||||||
float visual_max_temperature_{30};
|
float visual_max_temperature_{30};
|
||||||
float visual_temperature_step_{0.1};
|
float visual_target_temperature_step_{0.1};
|
||||||
|
float visual_current_temperature_step_{0.1};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace climate
|
} // namespace climate
|
||||||
|
|
|
@ -10,8 +10,20 @@ CONF_RED_INT = "red_int"
|
||||||
CONF_GREEN_INT = "green_int"
|
CONF_GREEN_INT = "green_int"
|
||||||
CONF_BLUE_INT = "blue_int"
|
CONF_BLUE_INT = "blue_int"
|
||||||
CONF_WHITE_INT = "white_int"
|
CONF_WHITE_INT = "white_int"
|
||||||
|
CONF_HEX = "hex"
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.Schema(
|
|
||||||
|
def hex_color(value):
|
||||||
|
if len(value) != 6:
|
||||||
|
raise cv.Invalid("Color must have six digits")
|
||||||
|
try:
|
||||||
|
return (int(value[0:2], 16), int(value[2:4], 16), int(value[4:6], 16))
|
||||||
|
except ValueError as exc:
|
||||||
|
raise cv.Invalid("Color must be hexadecimal") from exc
|
||||||
|
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = cv.Any(
|
||||||
|
cv.Schema(
|
||||||
{
|
{
|
||||||
cv.Required(CONF_ID): cv.declare_id(ColorStruct),
|
cv.Required(CONF_ID): cv.declare_id(ColorStruct),
|
||||||
cv.Exclusive(CONF_RED, "red"): cv.percentage,
|
cv.Exclusive(CONF_RED, "red"): cv.percentage,
|
||||||
|
@ -23,10 +35,17 @@ CONFIG_SCHEMA = cv.Schema(
|
||||||
cv.Exclusive(CONF_WHITE, "white"): cv.percentage,
|
cv.Exclusive(CONF_WHITE, "white"): cv.percentage,
|
||||||
cv.Exclusive(CONF_WHITE_INT, "white"): cv.uint8_t,
|
cv.Exclusive(CONF_WHITE_INT, "white"): cv.uint8_t,
|
||||||
}
|
}
|
||||||
).extend(cv.COMPONENT_SCHEMA)
|
).extend(cv.COMPONENT_SCHEMA),
|
||||||
|
cv.Schema(
|
||||||
|
{
|
||||||
|
cv.Required(CONF_ID): cv.declare_id(ColorStruct),
|
||||||
|
cv.Required(CONF_HEX): hex_color,
|
||||||
|
}
|
||||||
|
).extend(cv.COMPONENT_SCHEMA),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
def from_rgbw(config):
|
||||||
r = 0
|
r = 0
|
||||||
if CONF_RED in config:
|
if CONF_RED in config:
|
||||||
r = int(config[CONF_RED] * 255)
|
r = int(config[CONF_RED] * 255)
|
||||||
|
@ -51,6 +70,16 @@ async def to_code(config):
|
||||||
elif CONF_WHITE_INT in config:
|
elif CONF_WHITE_INT in config:
|
||||||
w = config[CONF_WHITE_INT]
|
w = config[CONF_WHITE_INT]
|
||||||
|
|
||||||
|
return (r, g, b, w)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
if CONF_HEX in config:
|
||||||
|
r, g, b = config[CONF_HEX]
|
||||||
|
w = 0
|
||||||
|
else:
|
||||||
|
r, g, b, w = from_rgbw(config)
|
||||||
|
|
||||||
cg.new_variable(
|
cg.new_variable(
|
||||||
config[CONF_ID],
|
config[CONF_ID],
|
||||||
cg.StructInitializer(ColorStruct, ("r", r), ("g", g), ("b", b), ("w", w)),
|
cg.StructInitializer(ColorStruct, ("r", r), ("g", g), ("b", b), ("w", w)),
|
||||||
|
|
|
@ -16,10 +16,9 @@ CopyButton = copy_ns.class_("CopyButton", button.Button, cg.Component)
|
||||||
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = (
|
CONFIG_SCHEMA = (
|
||||||
button.button_schema()
|
button.button_schema(CopyButton)
|
||||||
.extend(
|
.extend(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(CopyButton),
|
|
||||||
cv.Required(CONF_SOURCE_ID): cv.use_id(button.Button),
|
cv.Required(CONF_SOURCE_ID): cv.use_id(button.Button),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -15,12 +15,15 @@ from .. import copy_ns
|
||||||
CopyNumber = copy_ns.class_("CopyNumber", number.Number, cg.Component)
|
CopyNumber = copy_ns.class_("CopyNumber", number.Number, cg.Component)
|
||||||
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = number.NUMBER_SCHEMA.extend(
|
CONFIG_SCHEMA = (
|
||||||
|
number.number_schema(CopyNumber)
|
||||||
|
.extend(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(CopyNumber),
|
|
||||||
cv.Required(CONF_SOURCE_ID): cv.use_id(number.Number),
|
cv.Required(CONF_SOURCE_ID): cv.use_id(number.Number),
|
||||||
}
|
}
|
||||||
).extend(cv.COMPONENT_SCHEMA)
|
)
|
||||||
|
.extend(cv.COMPONENT_SCHEMA)
|
||||||
|
)
|
||||||
|
|
||||||
FINAL_VALIDATE_SCHEMA = cv.All(
|
FINAL_VALIDATE_SCHEMA = cv.All(
|
||||||
inherit_property_from(CONF_ICON, CONF_SOURCE_ID),
|
inherit_property_from(CONF_ICON, CONF_SOURCE_ID),
|
||||||
|
|
|
@ -17,6 +17,17 @@ from esphome.const import (
|
||||||
CONF_STOP,
|
CONF_STOP,
|
||||||
CONF_MQTT_ID,
|
CONF_MQTT_ID,
|
||||||
CONF_TRIGGER_ID,
|
CONF_TRIGGER_ID,
|
||||||
|
DEVICE_CLASS_AWNING,
|
||||||
|
DEVICE_CLASS_BLIND,
|
||||||
|
DEVICE_CLASS_CURTAIN,
|
||||||
|
DEVICE_CLASS_DAMPER,
|
||||||
|
DEVICE_CLASS_DOOR,
|
||||||
|
DEVICE_CLASS_EMPTY,
|
||||||
|
DEVICE_CLASS_GARAGE,
|
||||||
|
DEVICE_CLASS_GATE,
|
||||||
|
DEVICE_CLASS_SHADE,
|
||||||
|
DEVICE_CLASS_SHUTTER,
|
||||||
|
DEVICE_CLASS_WINDOW,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, coroutine_with_priority
|
from esphome.core import CORE, coroutine_with_priority
|
||||||
from esphome.cpp_helpers import setup_entity
|
from esphome.cpp_helpers import setup_entity
|
||||||
|
@ -25,17 +36,17 @@ IS_PLATFORM_COMPONENT = True
|
||||||
|
|
||||||
CODEOWNERS = ["@esphome/core"]
|
CODEOWNERS = ["@esphome/core"]
|
||||||
DEVICE_CLASSES = [
|
DEVICE_CLASSES = [
|
||||||
"",
|
DEVICE_CLASS_AWNING,
|
||||||
"awning",
|
DEVICE_CLASS_BLIND,
|
||||||
"blind",
|
DEVICE_CLASS_CURTAIN,
|
||||||
"curtain",
|
DEVICE_CLASS_DAMPER,
|
||||||
"damper",
|
DEVICE_CLASS_DOOR,
|
||||||
"door",
|
DEVICE_CLASS_EMPTY,
|
||||||
"garage",
|
DEVICE_CLASS_GARAGE,
|
||||||
"gate",
|
DEVICE_CLASS_GATE,
|
||||||
"shade",
|
DEVICE_CLASS_SHADE,
|
||||||
"shutter",
|
DEVICE_CLASS_SHUTTER,
|
||||||
"window",
|
DEVICE_CLASS_WINDOW,
|
||||||
]
|
]
|
||||||
|
|
||||||
cover_ns = cg.esphome_ns.namespace("cover")
|
cover_ns = cg.esphome_ns.namespace("cover")
|
||||||
|
|
|
@ -10,7 +10,7 @@ CONFIG_SCHEMA = cv.Schema(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(CustomSensorConstructor),
|
cv.GenerateID(): cv.declare_id(CustomSensorConstructor),
|
||||||
cv.Required(CONF_LAMBDA): cv.returning_lambda,
|
cv.Required(CONF_LAMBDA): cv.returning_lambda,
|
||||||
cv.Required(CONF_SENSORS): cv.ensure_list(sensor.SENSOR_SCHEMA),
|
cv.Required(CONF_SENSORS): cv.ensure_list(sensor.sensor_schema()),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ from esphome.components.esp32.const import (
|
||||||
VARIANT_ESP32,
|
VARIANT_ESP32,
|
||||||
VARIANT_ESP32C3,
|
VARIANT_ESP32C3,
|
||||||
VARIANT_ESP32S2,
|
VARIANT_ESP32S2,
|
||||||
|
VARIANT_ESP32S3,
|
||||||
)
|
)
|
||||||
|
|
||||||
WAKEUP_PINS = {
|
WAKEUP_PINS = {
|
||||||
|
@ -69,6 +70,30 @@ WAKEUP_PINS = {
|
||||||
20,
|
20,
|
||||||
21,
|
21,
|
||||||
],
|
],
|
||||||
|
VARIANT_ESP32S3: [
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
3,
|
||||||
|
4,
|
||||||
|
5,
|
||||||
|
6,
|
||||||
|
7,
|
||||||
|
8,
|
||||||
|
9,
|
||||||
|
10,
|
||||||
|
11,
|
||||||
|
12,
|
||||||
|
13,
|
||||||
|
14,
|
||||||
|
15,
|
||||||
|
16,
|
||||||
|
17,
|
||||||
|
18,
|
||||||
|
19,
|
||||||
|
20,
|
||||||
|
21,
|
||||||
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -284,9 +284,10 @@ CONFIG_SCHEMA = cv.Schema(
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
): [
|
): [
|
||||||
number.NUMBER_SCHEMA.extend(cv.COMPONENT_SCHEMA).extend(
|
number.number_schema(DemoNumber)
|
||||||
|
.extend(cv.COMPONENT_SCHEMA)
|
||||||
|
.extend(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(DemoNumber),
|
|
||||||
cv.Required(CONF_TYPE): cv.enum(NUMBER_TYPES, int=True),
|
cv.Required(CONF_TYPE): cv.enum(NUMBER_TYPES, int=True),
|
||||||
cv.Required(CONF_MIN_VALUE): cv.float_,
|
cv.Required(CONF_MIN_VALUE): cv.float_,
|
||||||
cv.Required(CONF_MAX_VALUE): cv.float_,
|
cv.Required(CONF_MAX_VALUE): cv.float_,
|
||||||
|
|
|
@ -32,9 +32,11 @@ void Rect::extend(Rect rect) {
|
||||||
this->h = rect.h;
|
this->h = rect.h;
|
||||||
} else {
|
} else {
|
||||||
if (this->x > rect.x) {
|
if (this->x > rect.x) {
|
||||||
|
this->w = this->w + (this->x - rect.x);
|
||||||
this->x = rect.x;
|
this->x = rect.x;
|
||||||
}
|
}
|
||||||
if (this->y > rect.y) {
|
if (this->y > rect.y) {
|
||||||
|
this->h = this->h + (this->y - rect.y);
|
||||||
this->y = rect.y;
|
this->y = rect.y;
|
||||||
}
|
}
|
||||||
if (this->x2() < rect.x2()) {
|
if (this->x2() < rect.x2()) {
|
||||||
|
@ -49,29 +51,35 @@ void Rect::shrink(Rect rect) {
|
||||||
if (!this->inside(rect)) {
|
if (!this->inside(rect)) {
|
||||||
(*this) = Rect();
|
(*this) = Rect();
|
||||||
} else {
|
} else {
|
||||||
if (this->x < rect.x) {
|
|
||||||
this->x = rect.x;
|
|
||||||
}
|
|
||||||
if (this->y < rect.y) {
|
|
||||||
this->y = rect.y;
|
|
||||||
}
|
|
||||||
if (this->x2() > rect.x2()) {
|
if (this->x2() > rect.x2()) {
|
||||||
this->w = rect.x2() - this->x;
|
this->w = rect.x2() - this->x;
|
||||||
}
|
}
|
||||||
|
if (this->x < rect.x) {
|
||||||
|
this->w = this->w + (this->x - rect.x);
|
||||||
|
this->x = rect.x;
|
||||||
|
}
|
||||||
if (this->y2() > rect.y2()) {
|
if (this->y2() > rect.y2()) {
|
||||||
this->h = rect.y2() - this->y;
|
this->h = rect.y2() - this->y;
|
||||||
}
|
}
|
||||||
|
if (this->y < rect.y) {
|
||||||
|
this->h = this->h + (this->y - rect.y);
|
||||||
|
this->y = rect.y;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Rect::inside(int16_t x, int16_t y, bool absolute) { // NOLINT
|
bool Rect::equal(Rect rect) {
|
||||||
|
return (rect.x == this->x) && (rect.w == this->w) && (rect.y == this->y) && (rect.h == this->h);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Rect::inside(int16_t test_x, int16_t test_y, bool absolute) { // NOLINT
|
||||||
if (!this->is_set()) {
|
if (!this->is_set()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (absolute) {
|
if (absolute) {
|
||||||
return ((x >= 0) && (x <= this->w) && (y >= 0) && (y <= this->h));
|
return ((test_x >= this->x) && (test_x <= this->x2()) && (test_y >= this->y) && (test_y <= this->y2()));
|
||||||
} else {
|
} else {
|
||||||
return ((x >= this->x) && (x <= this->x2()) && (y >= this->y) && (y <= this->y2()));
|
return ((test_x >= 0) && (test_x <= this->w) && (test_y >= 0) && (test_y <= this->h));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,15 +88,16 @@ bool Rect::inside(Rect rect, bool absolute) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (absolute) {
|
if (absolute) {
|
||||||
return ((rect.x <= this->w) && (rect.w >= 0) && (rect.y <= this->h) && (rect.h >= 0));
|
|
||||||
} else {
|
|
||||||
return ((rect.x <= this->x2()) && (rect.x2() >= this->x) && (rect.y <= this->y2()) && (rect.y2() >= this->y));
|
return ((rect.x <= this->x2()) && (rect.x2() >= this->x) && (rect.y <= this->y2()) && (rect.y2() >= this->y));
|
||||||
|
} else {
|
||||||
|
return ((rect.x <= this->w) && (rect.w >= 0) && (rect.y <= this->h) && (rect.h >= 0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Rect::info(const std::string &prefix) {
|
void Rect::info(const std::string &prefix) {
|
||||||
if (this->is_set()) {
|
if (this->is_set()) {
|
||||||
ESP_LOGI(TAG, "%s [%3d,%3d,%3d,%3d]", prefix.c_str(), this->x, this->y, this->w, this->h);
|
ESP_LOGI(TAG, "%s [%3d,%3d,%3d,%3d] (%3d,%3d)", prefix.c_str(), this->x, this->y, this->w, this->h, this->x2(),
|
||||||
|
this->y2());
|
||||||
} else
|
} else
|
||||||
ESP_LOGI(TAG, "%s ** IS NOT SET **", prefix.c_str());
|
ESP_LOGI(TAG, "%s ** IS NOT SET **", prefix.c_str());
|
||||||
}
|
}
|
||||||
|
@ -603,10 +612,10 @@ void Font::measure(const char *str, int *width, int *x_offset, int *baseline, in
|
||||||
*x_offset = min_x;
|
*x_offset = min_x;
|
||||||
*width = x - min_x;
|
*width = x - min_x;
|
||||||
}
|
}
|
||||||
const std::vector<Glyph> &Font::get_glyphs() const { return this->glyphs_; }
|
|
||||||
Font::Font(const GlyphData *data, int data_nr, int baseline, int height) : baseline_(baseline), height_(height) {
|
Font::Font(const GlyphData *data, int data_nr, int baseline, int height) : baseline_(baseline), height_(height) {
|
||||||
|
glyphs_.reserve(data_nr);
|
||||||
for (int i = 0; i < data_nr; ++i)
|
for (int i = 0; i < data_nr; ++i)
|
||||||
glyphs_.emplace_back(data + i);
|
glyphs_.emplace_back(&data[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Image::get_pixel(int x, int y) const {
|
bool Image::get_pixel(int x, int y) const {
|
||||||
|
|
|
@ -120,8 +120,9 @@ class Rect {
|
||||||
void extend(Rect rect);
|
void extend(Rect rect);
|
||||||
void shrink(Rect rect);
|
void shrink(Rect rect);
|
||||||
|
|
||||||
bool inside(Rect rect, bool absolute = false);
|
bool inside(Rect rect, bool absolute = true);
|
||||||
bool inside(int16_t x, int16_t y, bool absolute = false);
|
bool inside(int16_t test_x, int16_t test_y, bool absolute = true);
|
||||||
|
bool equal(Rect rect);
|
||||||
void info(const std::string &prefix = "rect info:");
|
void info(const std::string &prefix = "rect info:");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -526,10 +527,10 @@ class Font {
|
||||||
inline int get_baseline() { return this->baseline_; }
|
inline int get_baseline() { return this->baseline_; }
|
||||||
inline int get_height() { return this->height_; }
|
inline int get_height() { return this->height_; }
|
||||||
|
|
||||||
const std::vector<Glyph> &get_glyphs() const;
|
const std::vector<Glyph, ExternalRAMAllocator<Glyph>> &get_glyphs() const { return glyphs_; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::vector<Glyph> glyphs_;
|
std::vector<Glyph, ExternalRAMAllocator<Glyph>> glyphs_;
|
||||||
int baseline_;
|
int baseline_;
|
||||||
int height_;
|
int height_;
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,29 +4,43 @@ from pathlib import Path
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from esphome.helpers import copy_file_if_changed, write_file_if_changed
|
from esphome.helpers import copy_file_if_changed, write_file_if_changed, mkdir_p
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_BOARD,
|
CONF_BOARD,
|
||||||
|
CONF_COMPONENTS,
|
||||||
CONF_FRAMEWORK,
|
CONF_FRAMEWORK,
|
||||||
|
CONF_NAME,
|
||||||
CONF_SOURCE,
|
CONF_SOURCE,
|
||||||
CONF_TYPE,
|
CONF_TYPE,
|
||||||
CONF_VARIANT,
|
CONF_VARIANT,
|
||||||
CONF_VERSION,
|
CONF_VERSION,
|
||||||
CONF_ADVANCED,
|
CONF_ADVANCED,
|
||||||
|
CONF_REFRESH,
|
||||||
|
CONF_PATH,
|
||||||
|
CONF_URL,
|
||||||
|
CONF_REF,
|
||||||
CONF_IGNORE_EFUSE_MAC_CRC,
|
CONF_IGNORE_EFUSE_MAC_CRC,
|
||||||
KEY_CORE,
|
KEY_CORE,
|
||||||
KEY_FRAMEWORK_VERSION,
|
KEY_FRAMEWORK_VERSION,
|
||||||
KEY_TARGET_FRAMEWORK,
|
KEY_TARGET_FRAMEWORK,
|
||||||
KEY_TARGET_PLATFORM,
|
KEY_TARGET_PLATFORM,
|
||||||
|
TYPE_GIT,
|
||||||
|
TYPE_LOCAL,
|
||||||
__version__,
|
__version__,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, HexInt
|
from esphome.core import CORE, HexInt, TimePeriod
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
|
from esphome import git
|
||||||
|
|
||||||
from .const import ( # noqa
|
from .const import ( # noqa
|
||||||
KEY_BOARD,
|
KEY_BOARD,
|
||||||
|
KEY_COMPONENTS,
|
||||||
KEY_ESP32,
|
KEY_ESP32,
|
||||||
|
KEY_PATH,
|
||||||
|
KEY_REF,
|
||||||
|
KEY_REFRESH,
|
||||||
|
KEY_REPO,
|
||||||
KEY_SDKCONFIG_OPTIONS,
|
KEY_SDKCONFIG_OPTIONS,
|
||||||
KEY_VARIANT,
|
KEY_VARIANT,
|
||||||
VARIANT_ESP32C3,
|
VARIANT_ESP32C3,
|
||||||
|
@ -51,6 +65,7 @@ def set_core_data(config):
|
||||||
if conf[CONF_TYPE] == FRAMEWORK_ESP_IDF:
|
if conf[CONF_TYPE] == FRAMEWORK_ESP_IDF:
|
||||||
CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "esp-idf"
|
CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "esp-idf"
|
||||||
CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS] = {}
|
CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS] = {}
|
||||||
|
CORE.data[KEY_ESP32][KEY_COMPONENTS] = {}
|
||||||
elif conf[CONF_TYPE] == FRAMEWORK_ARDUINO:
|
elif conf[CONF_TYPE] == FRAMEWORK_ARDUINO:
|
||||||
CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "arduino"
|
CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "arduino"
|
||||||
CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = cv.Version.parse(
|
CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = cv.Version.parse(
|
||||||
|
@ -104,6 +119,21 @@ def add_idf_sdkconfig_option(name: str, value: SdkconfigValueType):
|
||||||
CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS][name] = value
|
CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS][name] = value
|
||||||
|
|
||||||
|
|
||||||
|
def add_idf_component(
|
||||||
|
name: str, repo: str, ref: str = None, path: str = None, refresh: TimePeriod = None
|
||||||
|
):
|
||||||
|
"""Add an esp-idf component to the project."""
|
||||||
|
if not CORE.using_esp_idf:
|
||||||
|
raise ValueError("Not an esp-idf project")
|
||||||
|
if name not in CORE.data[KEY_ESP32][KEY_COMPONENTS]:
|
||||||
|
CORE.data[KEY_ESP32][KEY_COMPONENTS][name] = {
|
||||||
|
KEY_REPO: repo,
|
||||||
|
KEY_REF: ref,
|
||||||
|
KEY_PATH: path,
|
||||||
|
KEY_REFRESH: refresh,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def _format_framework_arduino_version(ver: cv.Version) -> str:
|
def _format_framework_arduino_version(ver: cv.Version) -> str:
|
||||||
# format the given arduino (https://github.com/espressif/arduino-esp32/releases) version to
|
# format the given arduino (https://github.com/espressif/arduino-esp32/releases) version to
|
||||||
# a PIO platformio/framework-arduinoespressif32 value
|
# a PIO platformio/framework-arduinoespressif32 value
|
||||||
|
@ -138,18 +168,18 @@ ARDUINO_PLATFORM_VERSION = cv.Version(5, 2, 0)
|
||||||
# The default/recommended esp-idf framework version
|
# The default/recommended esp-idf framework version
|
||||||
# - https://github.com/espressif/esp-idf/releases
|
# - https://github.com/espressif/esp-idf/releases
|
||||||
# - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf
|
# - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf
|
||||||
RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(4, 4, 2)
|
RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(4, 4, 4)
|
||||||
# The platformio/espressif32 version to use for esp-idf frameworks
|
# The platformio/espressif32 version to use for esp-idf frameworks
|
||||||
# - https://github.com/platformio/platform-espressif32/releases
|
# - https://github.com/platformio/platform-espressif32/releases
|
||||||
# - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32
|
# - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32
|
||||||
ESP_IDF_PLATFORM_VERSION = cv.Version(5, 2, 0)
|
ESP_IDF_PLATFORM_VERSION = cv.Version(5, 3, 0)
|
||||||
|
|
||||||
|
|
||||||
def _arduino_check_versions(value):
|
def _arduino_check_versions(value):
|
||||||
value = value.copy()
|
value = value.copy()
|
||||||
lookups = {
|
lookups = {
|
||||||
"dev": (cv.Version(2, 0, 5), "https://github.com/espressif/arduino-esp32.git"),
|
"dev": (cv.Version(2, 1, 0), "https://github.com/espressif/arduino-esp32.git"),
|
||||||
"latest": (cv.Version(2, 0, 5), None),
|
"latest": (cv.Version(2, 0, 7), None),
|
||||||
"recommended": (RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, None),
|
"recommended": (RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, None),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,8 +213,8 @@ def _arduino_check_versions(value):
|
||||||
def _esp_idf_check_versions(value):
|
def _esp_idf_check_versions(value):
|
||||||
value = value.copy()
|
value = value.copy()
|
||||||
lookups = {
|
lookups = {
|
||||||
"dev": (cv.Version(5, 0, 0), "https://github.com/espressif/esp-idf.git"),
|
"dev": (cv.Version(5, 1, 0), "https://github.com/espressif/esp-idf.git"),
|
||||||
"latest": (cv.Version(4, 4, 2), None),
|
"latest": (cv.Version(5, 0, 1), None),
|
||||||
"recommended": (RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION, None),
|
"recommended": (RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION, None),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -270,6 +300,18 @@ ESP_IDF_FRAMEWORK_SCHEMA = cv.All(
|
||||||
cv.Optional(CONF_IGNORE_EFUSE_MAC_CRC, default=False): cv.boolean,
|
cv.Optional(CONF_IGNORE_EFUSE_MAC_CRC, default=False): cv.boolean,
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
cv.Optional(CONF_COMPONENTS, default=[]): cv.ensure_list(
|
||||||
|
cv.Schema(
|
||||||
|
{
|
||||||
|
cv.Required(CONF_NAME): cv.string_strict,
|
||||||
|
cv.Required(CONF_SOURCE): cv.SOURCE_SCHEMA,
|
||||||
|
cv.Optional(CONF_PATH): cv.string,
|
||||||
|
cv.Optional(CONF_REFRESH, default="1d"): cv.All(
|
||||||
|
cv.string, cv.source_refresh
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
),
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
_esp_idf_check_versions,
|
_esp_idf_check_versions,
|
||||||
|
@ -372,6 +414,19 @@ async def to_code(config):
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
for component in conf[CONF_COMPONENTS]:
|
||||||
|
source = component[CONF_SOURCE]
|
||||||
|
if source[CONF_TYPE] == TYPE_GIT:
|
||||||
|
add_idf_component(
|
||||||
|
name=component[CONF_NAME],
|
||||||
|
repo=source[CONF_URL],
|
||||||
|
ref=source.get(CONF_REF),
|
||||||
|
path=component.get(CONF_PATH),
|
||||||
|
refresh=component[CONF_REFRESH],
|
||||||
|
)
|
||||||
|
elif source[CONF_TYPE] == TYPE_LOCAL:
|
||||||
|
_LOGGER.warning("Local components are not implemented yet.")
|
||||||
|
|
||||||
elif conf[CONF_TYPE] == FRAMEWORK_ARDUINO:
|
elif conf[CONF_TYPE] == FRAMEWORK_ARDUINO:
|
||||||
cg.add_platformio_option("framework", "arduino")
|
cg.add_platformio_option("framework", "arduino")
|
||||||
cg.add_build_flag("-DUSE_ARDUINO")
|
cg.add_build_flag("-DUSE_ARDUINO")
|
||||||
|
@ -468,6 +523,32 @@ def copy_files():
|
||||||
__version__,
|
__version__,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
shutil.rmtree(CORE.relative_build_path("components"), ignore_errors=True)
|
||||||
|
|
||||||
|
if CORE.data[KEY_ESP32][KEY_COMPONENTS]:
|
||||||
|
components: dict = CORE.data[KEY_ESP32][KEY_COMPONENTS]
|
||||||
|
|
||||||
|
for name, component in components.items():
|
||||||
|
repo_dir, _ = git.clone_or_update(
|
||||||
|
url=component[KEY_REPO],
|
||||||
|
ref=component[KEY_REF],
|
||||||
|
refresh=component[KEY_REFRESH],
|
||||||
|
domain="idf_components",
|
||||||
|
)
|
||||||
|
mkdir_p(CORE.relative_build_path("components"))
|
||||||
|
component_dir = repo_dir
|
||||||
|
if component[KEY_PATH] is not None:
|
||||||
|
component_dir = component_dir / component[KEY_PATH]
|
||||||
|
|
||||||
|
shutil.copytree(
|
||||||
|
component_dir,
|
||||||
|
CORE.relative_build_path(f"components/{name}"),
|
||||||
|
dirs_exist_ok=True,
|
||||||
|
ignore=shutil.ignore_patterns(".git", ".github"),
|
||||||
|
)
|
||||||
|
|
||||||
dir = os.path.dirname(__file__)
|
dir = os.path.dirname(__file__)
|
||||||
post_build_file = os.path.join(dir, "post_build.py.script")
|
post_build_file = os.path.join(dir, "post_build.py.script")
|
||||||
copy_file_if_changed(
|
copy_file_if_changed(
|
||||||
|
|
|
@ -4,6 +4,11 @@ KEY_ESP32 = "esp32"
|
||||||
KEY_BOARD = "board"
|
KEY_BOARD = "board"
|
||||||
KEY_VARIANT = "variant"
|
KEY_VARIANT = "variant"
|
||||||
KEY_SDKCONFIG_OPTIONS = "sdkconfig_options"
|
KEY_SDKCONFIG_OPTIONS = "sdkconfig_options"
|
||||||
|
KEY_COMPONENTS = "components"
|
||||||
|
KEY_REPO = "repo"
|
||||||
|
KEY_REF = "ref"
|
||||||
|
KEY_REFRESH = "refresh"
|
||||||
|
KEY_PATH = "path"
|
||||||
|
|
||||||
VARIANT_ESP32 = "ESP32"
|
VARIANT_ESP32 = "ESP32"
|
||||||
VARIANT_ESP32S2 = "ESP32S2"
|
VARIANT_ESP32S2 = "ESP32S2"
|
||||||
|
|
|
@ -1,15 +1,25 @@
|
||||||
# Source https://github.com/letscontrolit/ESPEasy/pull/3845#issuecomment-1005864664
|
# Source https://github.com/letscontrolit/ESPEasy/pull/3845#issuecomment-1005864664
|
||||||
|
|
||||||
import os
|
|
||||||
if os.environ.get("ESPHOME_USE_SUBPROCESS") is None:
|
|
||||||
import esptool
|
|
||||||
else:
|
|
||||||
import subprocess
|
|
||||||
from SCons.Script import ARGUMENTS
|
|
||||||
|
|
||||||
# pylint: disable=E0602
|
# pylint: disable=E0602
|
||||||
Import("env") # noqa
|
Import("env") # noqa
|
||||||
|
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
if os.environ.get("ESPHOME_USE_SUBPROCESS") is None:
|
||||||
|
try:
|
||||||
|
import esptool
|
||||||
|
except ImportError:
|
||||||
|
env.Execute("$PYTHONEXE -m pip install esptool")
|
||||||
|
else:
|
||||||
|
import subprocess
|
||||||
|
from SCons.Script import ARGUMENTS
|
||||||
|
|
||||||
|
# Copy over the default sdkconfig.
|
||||||
|
from os import path
|
||||||
|
if path.exists("./sdkconfig.defaults"):
|
||||||
|
os.makedirs(".temp", exist_ok=True)
|
||||||
|
shutil.copy("./sdkconfig.defaults", "./.temp/sdkconfig-esp32-idf")
|
||||||
|
|
||||||
def esp32_create_combined_bin(source, target, env):
|
def esp32_create_combined_bin(source, target, env):
|
||||||
verbose = bool(int(ARGUMENTS.get("PIOVERBOSE", "0")))
|
verbose = bool(int(ARGUMENTS.get("PIOVERBOSE", "0")))
|
||||||
|
|
|
@ -62,6 +62,7 @@ bool BLEClientBase::parse_device(const espbt::ESPBTDevice &device) {
|
||||||
void BLEClientBase::connect() {
|
void BLEClientBase::connect() {
|
||||||
ESP_LOGI(TAG, "[%d] [%s] 0x%02x Attempting BLE connection", this->connection_index_, this->address_str_.c_str(),
|
ESP_LOGI(TAG, "[%d] [%s] 0x%02x Attempting BLE connection", this->connection_index_, this->address_str_.c_str(),
|
||||||
this->remote_addr_type_);
|
this->remote_addr_type_);
|
||||||
|
this->paired_ = false;
|
||||||
auto ret = esp_ble_gattc_open(this->gattc_if_, this->remote_bda_, this->remote_addr_type_, true);
|
auto ret = esp_ble_gattc_open(this->gattc_if_, this->remote_bda_, this->remote_addr_type_, true);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_open error, status=%d", this->connection_index_, this->address_str_.c_str(),
|
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_open error, status=%d", this->connection_index_, this->address_str_.c_str(),
|
||||||
|
@ -72,6 +73,8 @@ void BLEClientBase::connect() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
esp_err_t BLEClientBase::pair() { return esp_ble_set_encryption(this->remote_bda_, ESP_BLE_SEC_ENCRYPT); }
|
||||||
|
|
||||||
void BLEClientBase::disconnect() {
|
void BLEClientBase::disconnect() {
|
||||||
if (this->state_ == espbt::ClientState::IDLE || this->state_ == espbt::ClientState::DISCONNECTING)
|
if (this->state_ == espbt::ClientState::IDLE || this->state_ == espbt::ClientState::DISCONNECTING)
|
||||||
return;
|
return;
|
||||||
|
@ -247,11 +250,15 @@ void BLEClientBase::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_
|
||||||
switch (event) {
|
switch (event) {
|
||||||
// This event is sent by the server when it requests security
|
// This event is sent by the server when it requests security
|
||||||
case ESP_GAP_BLE_SEC_REQ_EVT:
|
case ESP_GAP_BLE_SEC_REQ_EVT:
|
||||||
|
if (memcmp(param->ble_security.auth_cmpl.bd_addr, this->remote_bda_, 6) != 0)
|
||||||
|
break;
|
||||||
ESP_LOGV(TAG, "[%d] [%s] ESP_GAP_BLE_SEC_REQ_EVT %x", this->connection_index_, this->address_str_.c_str(), event);
|
ESP_LOGV(TAG, "[%d] [%s] ESP_GAP_BLE_SEC_REQ_EVT %x", this->connection_index_, this->address_str_.c_str(), event);
|
||||||
esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true);
|
esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true);
|
||||||
break;
|
break;
|
||||||
// This event is sent once authentication has completed
|
// This event is sent once authentication has completed
|
||||||
case ESP_GAP_BLE_AUTH_CMPL_EVT:
|
case ESP_GAP_BLE_AUTH_CMPL_EVT:
|
||||||
|
if (memcmp(param->ble_security.auth_cmpl.bd_addr, this->remote_bda_, 6) != 0)
|
||||||
|
break;
|
||||||
esp_bd_addr_t bd_addr;
|
esp_bd_addr_t bd_addr;
|
||||||
memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t));
|
memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t));
|
||||||
ESP_LOGI(TAG, "[%d] [%s] auth complete. remote BD_ADDR: %s", this->connection_index_, this->address_str_.c_str(),
|
ESP_LOGI(TAG, "[%d] [%s] auth complete. remote BD_ADDR: %s", this->connection_index_, this->address_str_.c_str(),
|
||||||
|
@ -260,6 +267,7 @@ void BLEClientBase::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_
|
||||||
ESP_LOGE(TAG, "[%d] [%s] auth fail reason = 0x%x", this->connection_index_, this->address_str_.c_str(),
|
ESP_LOGE(TAG, "[%d] [%s] auth fail reason = 0x%x", this->connection_index_, this->address_str_.c_str(),
|
||||||
param->ble_security.auth_cmpl.fail_reason);
|
param->ble_security.auth_cmpl.fail_reason);
|
||||||
} else {
|
} else {
|
||||||
|
this->paired_ = true;
|
||||||
ESP_LOGV(TAG, "[%d] [%s] auth success. address type = %d auth mode = %d", this->connection_index_,
|
ESP_LOGV(TAG, "[%d] [%s] auth success. address type = %d auth mode = %d", this->connection_index_,
|
||||||
this->address_str_.c_str(), param->ble_security.auth_cmpl.addr_type,
|
this->address_str_.c_str(), param->ble_security.auth_cmpl.addr_type,
|
||||||
param->ble_security.auth_cmpl.auth_mode);
|
param->ble_security.auth_cmpl.auth_mode);
|
||||||
|
|
|
@ -33,6 +33,7 @@ class BLEClientBase : public espbt::ESPBTClient, public Component {
|
||||||
esp_ble_gattc_cb_param_t *param) override;
|
esp_ble_gattc_cb_param_t *param) override;
|
||||||
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override;
|
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override;
|
||||||
void connect() override;
|
void connect() override;
|
||||||
|
esp_err_t pair();
|
||||||
void disconnect();
|
void disconnect();
|
||||||
void release_services();
|
void release_services();
|
||||||
|
|
||||||
|
@ -71,6 +72,7 @@ class BLEClientBase : public espbt::ESPBTClient, public Component {
|
||||||
void set_remote_addr_type(esp_ble_addr_type_t address_type) { this->remote_addr_type_ = address_type; }
|
void set_remote_addr_type(esp_ble_addr_type_t address_type) { this->remote_addr_type_ = address_type; }
|
||||||
uint16_t get_conn_id() const { return this->conn_id_; }
|
uint16_t get_conn_id() const { return this->conn_id_; }
|
||||||
uint64_t get_address() const { return this->address_; }
|
uint64_t get_address() const { return this->address_; }
|
||||||
|
bool is_paired() const { return this->paired_; }
|
||||||
|
|
||||||
uint8_t get_connection_index() const { return this->connection_index_; }
|
uint8_t get_connection_index() const { return this->connection_index_; }
|
||||||
|
|
||||||
|
@ -86,6 +88,7 @@ class BLEClientBase : public espbt::ESPBTClient, public Component {
|
||||||
uint8_t connection_index_;
|
uint8_t connection_index_;
|
||||||
int16_t service_count_{0};
|
int16_t service_count_{0};
|
||||||
uint16_t mtu_{23};
|
uint16_t mtu_{23};
|
||||||
|
bool paired_{false};
|
||||||
espbt::ConnectionType connection_type_{espbt::ConnectionType::V1};
|
espbt::ConnectionType connection_type_{espbt::ConnectionType::V1};
|
||||||
|
|
||||||
std::vector<BLEService *> services_;
|
std::vector<BLEService *> services_;
|
||||||
|
|
|
@ -53,6 +53,14 @@ void ESP32BLETracker::setup() {
|
||||||
ESP_LOGE(TAG, "BLE Tracker was marked failed by ESP32BLE");
|
ESP_LOGE(TAG, "BLE Tracker was marked failed by ESP32BLE");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
ExternalRAMAllocator<esp_ble_gap_cb_param_t::ble_scan_result_evt_param> allocator(
|
||||||
|
ExternalRAMAllocator<esp_ble_gap_cb_param_t::ble_scan_result_evt_param>::ALLOW_FAILURE);
|
||||||
|
this->scan_result_buffer_ = allocator.allocate(ESP32BLETracker::SCAN_RESULT_BUFFER_SIZE);
|
||||||
|
|
||||||
|
if (this->scan_result_buffer_ == nullptr) {
|
||||||
|
ESP_LOGE(TAG, "Could not allocate buffer for BLE Tracker!");
|
||||||
|
this->mark_failed();
|
||||||
|
}
|
||||||
|
|
||||||
global_esp32_ble_tracker = this;
|
global_esp32_ble_tracker = this;
|
||||||
this->scan_result_lock_ = xSemaphoreCreateMutex();
|
this->scan_result_lock_ = xSemaphoreCreateMutex();
|
||||||
|
@ -107,7 +115,7 @@ void ESP32BLETracker::loop() {
|
||||||
xSemaphoreTake(this->scan_result_lock_, 5L / portTICK_PERIOD_MS)) {
|
xSemaphoreTake(this->scan_result_lock_, 5L / portTICK_PERIOD_MS)) {
|
||||||
uint32_t index = this->scan_result_index_;
|
uint32_t index = this->scan_result_index_;
|
||||||
if (index) {
|
if (index) {
|
||||||
if (index >= 16) {
|
if (index >= ESP32BLETracker::SCAN_RESULT_BUFFER_SIZE) {
|
||||||
ESP_LOGW(TAG, "Too many BLE events to process. Some devices may not show up.");
|
ESP_LOGW(TAG, "Too many BLE events to process. Some devices may not show up.");
|
||||||
}
|
}
|
||||||
for (size_t i = 0; i < index; i++) {
|
for (size_t i = 0; i < index; i++) {
|
||||||
|
@ -322,7 +330,7 @@ void ESP32BLETracker::gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_
|
||||||
void ESP32BLETracker::gap_scan_result_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m) {
|
void ESP32BLETracker::gap_scan_result_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m) {
|
||||||
if (param.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT) {
|
if (param.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT) {
|
||||||
if (xSemaphoreTake(this->scan_result_lock_, 0L)) {
|
if (xSemaphoreTake(this->scan_result_lock_, 0L)) {
|
||||||
if (this->scan_result_index_ < 16) {
|
if (this->scan_result_index_ < ESP32BLETracker::SCAN_RESULT_BUFFER_SIZE) {
|
||||||
this->scan_result_buffer_[this->scan_result_index_++] = param;
|
this->scan_result_buffer_[this->scan_result_index_++] = param;
|
||||||
}
|
}
|
||||||
xSemaphoreGive(this->scan_result_lock_);
|
xSemaphoreGive(this->scan_result_lock_);
|
||||||
|
|
|
@ -101,7 +101,7 @@ class ESPBTDevice {
|
||||||
std::vector<int8_t> tx_powers_{};
|
std::vector<int8_t> tx_powers_{};
|
||||||
optional<uint16_t> appearance_{};
|
optional<uint16_t> appearance_{};
|
||||||
optional<uint8_t> ad_flag_{};
|
optional<uint8_t> ad_flag_{};
|
||||||
std::vector<ESPBTUUID> service_uuids_;
|
std::vector<ESPBTUUID> service_uuids_{};
|
||||||
std::vector<ServiceData> manufacturer_datas_{};
|
std::vector<ServiceData> manufacturer_datas_{};
|
||||||
std::vector<ServiceData> service_datas_{};
|
std::vector<ServiceData> service_datas_{};
|
||||||
esp_ble_gap_cb_param_t::ble_scan_result_evt_param scan_result_{};
|
esp_ble_gap_cb_param_t::ble_scan_result_evt_param scan_result_{};
|
||||||
|
@ -231,7 +231,12 @@ class ESP32BLETracker : public Component, public GAPEventHandler, public GATTcEv
|
||||||
SemaphoreHandle_t scan_result_lock_;
|
SemaphoreHandle_t scan_result_lock_;
|
||||||
SemaphoreHandle_t scan_end_lock_;
|
SemaphoreHandle_t scan_end_lock_;
|
||||||
size_t scan_result_index_{0};
|
size_t scan_result_index_{0};
|
||||||
esp_ble_gap_cb_param_t::ble_scan_result_evt_param scan_result_buffer_[16];
|
#if CONFIG_SPIRAM
|
||||||
|
const static u_int8_t SCAN_RESULT_BUFFER_SIZE = 32;
|
||||||
|
#else
|
||||||
|
const static u_int8_t SCAN_RESULT_BUFFER_SIZE = 16;
|
||||||
|
#endif // CONFIG_SPIRAM
|
||||||
|
esp_ble_gap_cb_param_t::ble_scan_result_evt_param *scan_result_buffer_;
|
||||||
esp_bt_status_t scan_start_failed_{ESP_BT_STATUS_SUCCESS};
|
esp_bt_status_t scan_start_failed_{ESP_BT_STATUS_SUCCESS};
|
||||||
esp_bt_status_t scan_set_param_failed_{ESP_BT_STATUS_SUCCESS};
|
esp_bt_status_t scan_set_param_failed_{ESP_BT_STATUS_SUCCESS};
|
||||||
};
|
};
|
||||||
|
|
|
@ -11,6 +11,7 @@ from esphome.const import (
|
||||||
CONF_VOLTAGE_ATTENUATION,
|
CONF_VOLTAGE_ATTENUATION,
|
||||||
)
|
)
|
||||||
from esphome.core import TimePeriod
|
from esphome.core import TimePeriod
|
||||||
|
from esphome.components import esp32
|
||||||
|
|
||||||
AUTO_LOAD = ["binary_sensor"]
|
AUTO_LOAD = ["binary_sensor"]
|
||||||
DEPENDENCIES = ["esp32"]
|
DEPENDENCIES = ["esp32"]
|
||||||
|
@ -50,7 +51,8 @@ VOLTAGE_ATTENUATION = {
|
||||||
"0V": cg.global_ns.TOUCH_HVOLT_ATTEN_0V,
|
"0V": cg.global_ns.TOUCH_HVOLT_ATTEN_0V,
|
||||||
}
|
}
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.Schema(
|
CONFIG_SCHEMA = cv.All(
|
||||||
|
cv.Schema(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(ESP32TouchComponent),
|
cv.GenerateID(): cv.declare_id(ESP32TouchComponent),
|
||||||
cv.Optional(CONF_SETUP_MODE, default=False): cv.boolean,
|
cv.Optional(CONF_SETUP_MODE, default=False): cv.boolean,
|
||||||
|
@ -73,7 +75,13 @@ CONFIG_SCHEMA = cv.Schema(
|
||||||
VOLTAGE_ATTENUATION
|
VOLTAGE_ATTENUATION
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
).extend(cv.COMPONENT_SCHEMA)
|
).extend(cv.COMPONENT_SCHEMA),
|
||||||
|
esp32.only_on_variant(
|
||||||
|
supported=[
|
||||||
|
esp32.const.VARIANT_ESP32,
|
||||||
|
]
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
|
from esphome.core import CORE
|
||||||
from esphome.components import binary_sensor
|
from esphome.components import binary_sensor
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_PIN,
|
CONF_PIN,
|
||||||
|
@ -7,6 +8,13 @@ from esphome.const import (
|
||||||
CONF_ID,
|
CONF_ID,
|
||||||
)
|
)
|
||||||
from esphome.components.esp32 import gpio
|
from esphome.components.esp32 import gpio
|
||||||
|
from esphome.components.esp32.const import (
|
||||||
|
KEY_ESP32,
|
||||||
|
KEY_VARIANT,
|
||||||
|
VARIANT_ESP32,
|
||||||
|
VARIANT_ESP32S2,
|
||||||
|
VARIANT_ESP32S3,
|
||||||
|
)
|
||||||
from . import esp32_touch_ns, ESP32TouchComponent
|
from . import esp32_touch_ns, ESP32TouchComponent
|
||||||
|
|
||||||
DEPENDENCIES = ["esp32_touch", "esp32"]
|
DEPENDENCIES = ["esp32_touch", "esp32"]
|
||||||
|
@ -15,6 +23,7 @@ CONF_ESP32_TOUCH_ID = "esp32_touch_id"
|
||||||
CONF_WAKEUP_THRESHOLD = "wakeup_threshold"
|
CONF_WAKEUP_THRESHOLD = "wakeup_threshold"
|
||||||
|
|
||||||
TOUCH_PADS = {
|
TOUCH_PADS = {
|
||||||
|
VARIANT_ESP32: {
|
||||||
4: cg.global_ns.TOUCH_PAD_NUM0,
|
4: cg.global_ns.TOUCH_PAD_NUM0,
|
||||||
0: cg.global_ns.TOUCH_PAD_NUM1,
|
0: cg.global_ns.TOUCH_PAD_NUM1,
|
||||||
2: cg.global_ns.TOUCH_PAD_NUM2,
|
2: cg.global_ns.TOUCH_PAD_NUM2,
|
||||||
|
@ -25,14 +34,52 @@ TOUCH_PADS = {
|
||||||
27: cg.global_ns.TOUCH_PAD_NUM7,
|
27: cg.global_ns.TOUCH_PAD_NUM7,
|
||||||
33: cg.global_ns.TOUCH_PAD_NUM8,
|
33: cg.global_ns.TOUCH_PAD_NUM8,
|
||||||
32: cg.global_ns.TOUCH_PAD_NUM9,
|
32: cg.global_ns.TOUCH_PAD_NUM9,
|
||||||
|
},
|
||||||
|
VARIANT_ESP32S2: {
|
||||||
|
1: cg.global_ns.TOUCH_PAD_NUM1,
|
||||||
|
2: cg.global_ns.TOUCH_PAD_NUM2,
|
||||||
|
3: cg.global_ns.TOUCH_PAD_NUM3,
|
||||||
|
4: cg.global_ns.TOUCH_PAD_NUM4,
|
||||||
|
5: cg.global_ns.TOUCH_PAD_NUM5,
|
||||||
|
6: cg.global_ns.TOUCH_PAD_NUM6,
|
||||||
|
7: cg.global_ns.TOUCH_PAD_NUM7,
|
||||||
|
8: cg.global_ns.TOUCH_PAD_NUM8,
|
||||||
|
9: cg.global_ns.TOUCH_PAD_NUM9,
|
||||||
|
10: cg.global_ns.TOUCH_PAD_NUM10,
|
||||||
|
11: cg.global_ns.TOUCH_PAD_NUM11,
|
||||||
|
12: cg.global_ns.TOUCH_PAD_NUM12,
|
||||||
|
13: cg.global_ns.TOUCH_PAD_NUM13,
|
||||||
|
14: cg.global_ns.TOUCH_PAD_NUM14,
|
||||||
|
},
|
||||||
|
VARIANT_ESP32S3: {
|
||||||
|
1: cg.global_ns.TOUCH_PAD_NUM1,
|
||||||
|
2: cg.global_ns.TOUCH_PAD_NUM2,
|
||||||
|
3: cg.global_ns.TOUCH_PAD_NUM3,
|
||||||
|
4: cg.global_ns.TOUCH_PAD_NUM4,
|
||||||
|
5: cg.global_ns.TOUCH_PAD_NUM5,
|
||||||
|
6: cg.global_ns.TOUCH_PAD_NUM6,
|
||||||
|
7: cg.global_ns.TOUCH_PAD_NUM7,
|
||||||
|
8: cg.global_ns.TOUCH_PAD_NUM8,
|
||||||
|
9: cg.global_ns.TOUCH_PAD_NUM9,
|
||||||
|
10: cg.global_ns.TOUCH_PAD_NUM10,
|
||||||
|
11: cg.global_ns.TOUCH_PAD_NUM11,
|
||||||
|
12: cg.global_ns.TOUCH_PAD_NUM12,
|
||||||
|
13: cg.global_ns.TOUCH_PAD_NUM13,
|
||||||
|
14: cg.global_ns.TOUCH_PAD_NUM14,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def validate_touch_pad(value):
|
def validate_touch_pad(value):
|
||||||
value = gpio.validate_gpio_pin(value)
|
value = gpio.validate_gpio_pin(value)
|
||||||
if value not in TOUCH_PADS:
|
variant = CORE.data[KEY_ESP32][KEY_VARIANT]
|
||||||
|
if variant not in TOUCH_PADS:
|
||||||
|
raise cv.Invalid(f"ESP32 variant {variant} does not support touch pads.")
|
||||||
|
|
||||||
|
pads = TOUCH_PADS[variant]
|
||||||
|
if value not in pads:
|
||||||
raise cv.Invalid(f"Pin {value} does not support touch pads.")
|
raise cv.Invalid(f"Pin {value} does not support touch pads.")
|
||||||
return value
|
return cv.enum(pads)(value)
|
||||||
|
|
||||||
|
|
||||||
ESP32TouchBinarySensor = esp32_touch_ns.class_(
|
ESP32TouchBinarySensor = esp32_touch_ns.class_(
|
||||||
|
@ -53,7 +100,7 @@ async def to_code(config):
|
||||||
hub = await cg.get_variable(config[CONF_ESP32_TOUCH_ID])
|
hub = await cg.get_variable(config[CONF_ESP32_TOUCH_ID])
|
||||||
var = cg.new_Pvariable(
|
var = cg.new_Pvariable(
|
||||||
config[CONF_ID],
|
config[CONF_ID],
|
||||||
TOUCH_PADS[config[CONF_PIN]],
|
config[CONF_PIN],
|
||||||
config[CONF_THRESHOLD],
|
config[CONF_THRESHOLD],
|
||||||
config[CONF_WAKEUP_THRESHOLD],
|
config[CONF_WAKEUP_THRESHOLD],
|
||||||
)
|
)
|
||||||
|
|
|
@ -240,7 +240,6 @@ async def to_code(config):
|
||||||
|
|
||||||
# Called by writer.py
|
# Called by writer.py
|
||||||
def copy_files():
|
def copy_files():
|
||||||
|
|
||||||
dir = os.path.dirname(__file__)
|
dir = os.path.dirname(__file__)
|
||||||
post_build_file = os.path.join(dir, "post_build.py.script")
|
post_build_file = os.path.join(dir, "post_build.py.script")
|
||||||
copy_file_if_changed(
|
copy_file_if_changed(
|
||||||
|
|
|
@ -36,12 +36,25 @@ ETHERNET_TYPES = {
|
||||||
"JL1101": EthernetType.ETHERNET_TYPE_JL1101,
|
"JL1101": EthernetType.ETHERNET_TYPE_JL1101,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
emac_rmii_clock_mode_t = cg.global_ns.enum("emac_rmii_clock_mode_t")
|
||||||
emac_rmii_clock_gpio_t = cg.global_ns.enum("emac_rmii_clock_gpio_t")
|
emac_rmii_clock_gpio_t = cg.global_ns.enum("emac_rmii_clock_gpio_t")
|
||||||
CLK_MODES = {
|
CLK_MODES = {
|
||||||
"GPIO0_IN": emac_rmii_clock_gpio_t.EMAC_CLK_IN_GPIO,
|
"GPIO0_IN": (
|
||||||
"GPIO0_OUT": emac_rmii_clock_gpio_t.EMAC_APPL_CLK_OUT_GPIO,
|
emac_rmii_clock_mode_t.EMAC_CLK_EXT_IN,
|
||||||
"GPIO16_OUT": emac_rmii_clock_gpio_t.EMAC_CLK_OUT_GPIO,
|
emac_rmii_clock_gpio_t.EMAC_CLK_IN_GPIO,
|
||||||
"GPIO17_OUT": emac_rmii_clock_gpio_t.EMAC_CLK_OUT_180_GPIO,
|
),
|
||||||
|
"GPIO0_OUT": (
|
||||||
|
emac_rmii_clock_mode_t.EMAC_CLK_OUT,
|
||||||
|
emac_rmii_clock_gpio_t.EMAC_APPL_CLK_OUT_GPIO,
|
||||||
|
),
|
||||||
|
"GPIO16_OUT": (
|
||||||
|
emac_rmii_clock_mode_t.EMAC_CLK_OUT,
|
||||||
|
emac_rmii_clock_gpio_t.EMAC_CLK_OUT_GPIO,
|
||||||
|
),
|
||||||
|
"GPIO17_OUT": (
|
||||||
|
emac_rmii_clock_mode_t.EMAC_CLK_OUT,
|
||||||
|
emac_rmii_clock_gpio_t.EMAC_CLK_OUT_180_GPIO,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -114,7 +127,7 @@ async def to_code(config):
|
||||||
cg.add(var.set_mdc_pin(config[CONF_MDC_PIN]))
|
cg.add(var.set_mdc_pin(config[CONF_MDC_PIN]))
|
||||||
cg.add(var.set_mdio_pin(config[CONF_MDIO_PIN]))
|
cg.add(var.set_mdio_pin(config[CONF_MDIO_PIN]))
|
||||||
cg.add(var.set_type(config[CONF_TYPE]))
|
cg.add(var.set_type(config[CONF_TYPE]))
|
||||||
cg.add(var.set_clk_mode(CLK_MODES[config[CONF_CLK_MODE]]))
|
cg.add(var.set_clk_mode(*CLK_MODES[config[CONF_CLK_MODE]]))
|
||||||
cg.add(var.set_use_address(config[CONF_USE_ADDRESS]))
|
cg.add(var.set_use_address(config[CONF_USE_ADDRESS]))
|
||||||
|
|
||||||
if CONF_POWER_PIN in config:
|
if CONF_POWER_PIN in config:
|
||||||
|
|
|
@ -43,13 +43,12 @@ void EthernetComponent::setup() {
|
||||||
eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG();
|
eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG();
|
||||||
|
|
||||||
phy_config.phy_addr = this->phy_addr_;
|
phy_config.phy_addr = this->phy_addr_;
|
||||||
if (this->power_pin_ != -1)
|
|
||||||
phy_config.reset_gpio_num = this->power_pin_;
|
phy_config.reset_gpio_num = this->power_pin_;
|
||||||
|
|
||||||
mac_config.smi_mdc_gpio_num = this->mdc_pin_;
|
mac_config.smi_mdc_gpio_num = this->mdc_pin_;
|
||||||
mac_config.smi_mdio_gpio_num = this->mdio_pin_;
|
mac_config.smi_mdio_gpio_num = this->mdio_pin_;
|
||||||
mac_config.clock_config.rmii.clock_mode = this->clk_mode_ == EMAC_CLK_IN_GPIO ? EMAC_CLK_EXT_IN : EMAC_CLK_OUT;
|
mac_config.clock_config.rmii.clock_mode = this->clk_mode_;
|
||||||
mac_config.clock_config.rmii.clock_gpio = this->clk_mode_;
|
mac_config.clock_config.rmii.clock_gpio = this->clk_gpio_;
|
||||||
|
|
||||||
esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&mac_config);
|
esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&mac_config);
|
||||||
|
|
||||||
|
@ -316,7 +315,10 @@ void EthernetComponent::set_power_pin(int power_pin) { this->power_pin_ = power_
|
||||||
void EthernetComponent::set_mdc_pin(uint8_t mdc_pin) { this->mdc_pin_ = mdc_pin; }
|
void EthernetComponent::set_mdc_pin(uint8_t mdc_pin) { this->mdc_pin_ = mdc_pin; }
|
||||||
void EthernetComponent::set_mdio_pin(uint8_t mdio_pin) { this->mdio_pin_ = mdio_pin; }
|
void EthernetComponent::set_mdio_pin(uint8_t mdio_pin) { this->mdio_pin_ = mdio_pin; }
|
||||||
void EthernetComponent::set_type(EthernetType type) { this->type_ = type; }
|
void EthernetComponent::set_type(EthernetType type) { this->type_ = type; }
|
||||||
void EthernetComponent::set_clk_mode(emac_rmii_clock_gpio_t clk_mode) { this->clk_mode_ = clk_mode; }
|
void EthernetComponent::set_clk_mode(emac_rmii_clock_mode_t clk_mode, emac_rmii_clock_gpio_t clk_gpio) {
|
||||||
|
this->clk_mode_ = clk_mode;
|
||||||
|
this->clk_gpio_ = clk_gpio;
|
||||||
|
}
|
||||||
void EthernetComponent::set_manual_ip(const ManualIP &manual_ip) { this->manual_ip_ = manual_ip; }
|
void EthernetComponent::set_manual_ip(const ManualIP &manual_ip) { this->manual_ip_ = manual_ip; }
|
||||||
|
|
||||||
std::string EthernetComponent::get_use_address() const {
|
std::string EthernetComponent::get_use_address() const {
|
||||||
|
|
|
@ -50,7 +50,7 @@ class EthernetComponent : public Component {
|
||||||
void set_mdc_pin(uint8_t mdc_pin);
|
void set_mdc_pin(uint8_t mdc_pin);
|
||||||
void set_mdio_pin(uint8_t mdio_pin);
|
void set_mdio_pin(uint8_t mdio_pin);
|
||||||
void set_type(EthernetType type);
|
void set_type(EthernetType type);
|
||||||
void set_clk_mode(emac_rmii_clock_gpio_t clk_mode);
|
void set_clk_mode(emac_rmii_clock_mode_t clk_mode, emac_rmii_clock_gpio_t clk_gpio);
|
||||||
void set_manual_ip(const ManualIP &manual_ip);
|
void set_manual_ip(const ManualIP &manual_ip);
|
||||||
|
|
||||||
network::IPAddress get_ip_address();
|
network::IPAddress get_ip_address();
|
||||||
|
@ -70,7 +70,8 @@ class EthernetComponent : public Component {
|
||||||
uint8_t mdc_pin_{23};
|
uint8_t mdc_pin_{23};
|
||||||
uint8_t mdio_pin_{18};
|
uint8_t mdio_pin_{18};
|
||||||
EthernetType type_{ETHERNET_TYPE_LAN8720};
|
EthernetType type_{ETHERNET_TYPE_LAN8720};
|
||||||
emac_rmii_clock_gpio_t clk_mode_{EMAC_CLK_IN_GPIO};
|
emac_rmii_clock_mode_t clk_mode_{EMAC_CLK_EXT_IN};
|
||||||
|
emac_rmii_clock_gpio_t clk_gpio_{EMAC_CLK_IN_GPIO};
|
||||||
optional<ManualIP> manual_ip_{};
|
optional<ManualIP> manual_ip_{};
|
||||||
|
|
||||||
bool started_{false};
|
bool started_{false};
|
||||||
|
|
|
@ -1,90 +1,32 @@
|
||||||
import re
|
|
||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
|
from esphome import git, loader
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_COMPONENTS,
|
CONF_COMPONENTS,
|
||||||
|
CONF_EXTERNAL_COMPONENTS,
|
||||||
|
CONF_PASSWORD,
|
||||||
|
CONF_PATH,
|
||||||
CONF_REF,
|
CONF_REF,
|
||||||
CONF_REFRESH,
|
CONF_REFRESH,
|
||||||
CONF_SOURCE,
|
CONF_SOURCE,
|
||||||
CONF_URL,
|
|
||||||
CONF_TYPE,
|
CONF_TYPE,
|
||||||
CONF_EXTERNAL_COMPONENTS,
|
CONF_URL,
|
||||||
CONF_PATH,
|
|
||||||
CONF_USERNAME,
|
CONF_USERNAME,
|
||||||
CONF_PASSWORD,
|
TYPE_GIT,
|
||||||
|
TYPE_LOCAL,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE
|
from esphome.core import CORE
|
||||||
from esphome import git, loader
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
DOMAIN = CONF_EXTERNAL_COMPONENTS
|
DOMAIN = CONF_EXTERNAL_COMPONENTS
|
||||||
|
|
||||||
TYPE_GIT = "git"
|
|
||||||
TYPE_LOCAL = "local"
|
|
||||||
|
|
||||||
|
|
||||||
GIT_SCHEMA = {
|
|
||||||
cv.Required(CONF_URL): cv.url,
|
|
||||||
cv.Optional(CONF_REF): cv.git_ref,
|
|
||||||
cv.Optional(CONF_USERNAME): cv.string,
|
|
||||||
cv.Optional(CONF_PASSWORD): cv.string,
|
|
||||||
}
|
|
||||||
LOCAL_SCHEMA = {
|
|
||||||
cv.Required(CONF_PATH): cv.directory,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def validate_source_shorthand(value):
|
|
||||||
if not isinstance(value, str):
|
|
||||||
raise cv.Invalid("Shorthand only for strings")
|
|
||||||
try:
|
|
||||||
return SOURCE_SCHEMA({CONF_TYPE: TYPE_LOCAL, CONF_PATH: value})
|
|
||||||
except cv.Invalid:
|
|
||||||
pass
|
|
||||||
# Regex for GitHub repo name with optional branch/tag
|
|
||||||
# Note: git allows other branch/tag names as well, but never seen them used before
|
|
||||||
m = re.match(
|
|
||||||
r"github://(?:([a-zA-Z0-9\-]+)/([a-zA-Z0-9\-\._]+)(?:@([a-zA-Z0-9\-_.\./]+))?|pr#([0-9]+))",
|
|
||||||
value,
|
|
||||||
)
|
|
||||||
if m is None:
|
|
||||||
raise cv.Invalid(
|
|
||||||
"Source is not a file system path, in expected github://username/name[@branch-or-tag] or github://pr#1234 format!"
|
|
||||||
)
|
|
||||||
if m.group(4):
|
|
||||||
conf = {
|
|
||||||
CONF_TYPE: TYPE_GIT,
|
|
||||||
CONF_URL: "https://github.com/esphome/esphome.git",
|
|
||||||
CONF_REF: f"pull/{m.group(4)}/head",
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
conf = {
|
|
||||||
CONF_TYPE: TYPE_GIT,
|
|
||||||
CONF_URL: f"https://github.com/{m.group(1)}/{m.group(2)}.git",
|
|
||||||
}
|
|
||||||
if m.group(3):
|
|
||||||
conf[CONF_REF] = m.group(3)
|
|
||||||
|
|
||||||
return SOURCE_SCHEMA(conf)
|
|
||||||
|
|
||||||
|
|
||||||
SOURCE_SCHEMA = cv.Any(
|
|
||||||
validate_source_shorthand,
|
|
||||||
cv.typed_schema(
|
|
||||||
{
|
|
||||||
TYPE_GIT: cv.Schema(GIT_SCHEMA),
|
|
||||||
TYPE_LOCAL: cv.Schema(LOCAL_SCHEMA),
|
|
||||||
}
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.ensure_list(
|
CONFIG_SCHEMA = cv.ensure_list(
|
||||||
{
|
{
|
||||||
cv.Required(CONF_SOURCE): SOURCE_SCHEMA,
|
cv.Required(CONF_SOURCE): cv.SOURCE_SCHEMA,
|
||||||
cv.Optional(CONF_REFRESH, default="1d"): cv.All(cv.string, cv.source_refresh),
|
cv.Optional(CONF_REFRESH, default="1d"): cv.All(cv.string, cv.source_refresh),
|
||||||
cv.Optional(CONF_COMPONENTS, default="all"): cv.Any(
|
cv.Optional(CONF_COMPONENTS, default="all"): cv.Any(
|
||||||
"all", cv.ensure_list(cv.string)
|
"all", cv.ensure_list(cv.string)
|
||||||
|
|
|
@ -41,9 +41,9 @@ DeviceInformationTrigger = ezo_ns.class_(
|
||||||
LedTrigger = ezo_ns.class_("LedTrigger", automation.Trigger.template(cg.bool_))
|
LedTrigger = ezo_ns.class_("LedTrigger", automation.Trigger.template(cg.bool_))
|
||||||
|
|
||||||
CONFIG_SCHEMA = (
|
CONFIG_SCHEMA = (
|
||||||
sensor.SENSOR_SCHEMA.extend(
|
sensor.sensor_schema(EZOSensor)
|
||||||
|
.extend(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(EZOSensor),
|
|
||||||
cv.Optional(CONF_ON_CUSTOM): automation.validate_automation(
|
cv.Optional(CONF_ON_CUSTOM): automation.validate_automation(
|
||||||
{
|
{
|
||||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CustomTrigger),
|
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CustomTrigger),
|
||||||
|
|
|
@ -13,15 +13,12 @@ FactoryResetButton = factory_reset_ns.class_(
|
||||||
"FactoryResetButton", button.Button, cg.Component
|
"FactoryResetButton", button.Button, cg.Component
|
||||||
)
|
)
|
||||||
|
|
||||||
CONFIG_SCHEMA = (
|
CONFIG_SCHEMA = button.button_schema(
|
||||||
button.button_schema(
|
FactoryResetButton,
|
||||||
device_class=DEVICE_CLASS_RESTART,
|
device_class=DEVICE_CLASS_RESTART,
|
||||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||||
icon=ICON_RESTART_ALERT,
|
icon=ICON_RESTART_ALERT,
|
||||||
)
|
).extend(cv.COMPONENT_SCHEMA)
|
||||||
.extend({cv.GenerateID(): cv.declare_id(FactoryResetButton)})
|
|
||||||
.extend(cv.COMPONENT_SCHEMA)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
|
|
0
esphome/components/fs3000/__init__.py
Normal file
0
esphome/components/fs3000/__init__.py
Normal file
107
esphome/components/fs3000/fs3000.cpp
Normal file
107
esphome/components/fs3000/fs3000.cpp
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
#include "fs3000.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace fs3000 {
|
||||||
|
|
||||||
|
static const char *const TAG = "fs3000";
|
||||||
|
|
||||||
|
void FS3000Component::setup() {
|
||||||
|
ESP_LOGCONFIG(TAG, "Setting up FS3000...");
|
||||||
|
|
||||||
|
if (model_ == FIVE) {
|
||||||
|
// datasheet gives 9 points to interpolate from for the 1005 model
|
||||||
|
static const uint16_t RAW_DATA_POINTS_1005[9] = {409, 915, 1522, 2066, 2523, 2908, 3256, 3572, 3686};
|
||||||
|
static const float MPS_DATA_POINTS_1005[9] = {0.0, 1.07, 2.01, 3.0, 3.97, 4.96, 5.98, 6.99, 7.23};
|
||||||
|
|
||||||
|
std::copy(RAW_DATA_POINTS_1005, RAW_DATA_POINTS_1005 + 9, this->raw_data_points_);
|
||||||
|
std::copy(MPS_DATA_POINTS_1005, MPS_DATA_POINTS_1005 + 9, this->mps_data_points_);
|
||||||
|
} else if (model_ == FIFTEEN) {
|
||||||
|
// datasheet gives 13 points to extrapolate from for the 1015 model
|
||||||
|
static const uint16_t RAW_DATA_POINTS_1015[13] = {409, 1203, 1597, 1908, 2187, 2400, 2629,
|
||||||
|
2801, 3006, 3178, 3309, 3563, 3686};
|
||||||
|
static const float MPS_DATA_POINTS_1015[13] = {0.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 13.0, 15.0};
|
||||||
|
|
||||||
|
std::copy(RAW_DATA_POINTS_1015, RAW_DATA_POINTS_1015 + 13, this->raw_data_points_);
|
||||||
|
std::copy(MPS_DATA_POINTS_1015, MPS_DATA_POINTS_1015 + 13, this->mps_data_points_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FS3000Component::update() {
|
||||||
|
// 5 bytes of data read from fs3000 sensor
|
||||||
|
// byte 1 - checksum
|
||||||
|
// byte 2 - (lower 4 bits) high byte of sensor reading
|
||||||
|
// byte 3 - (8 bits) low byte of sensor reading
|
||||||
|
// byte 4 - generic checksum data
|
||||||
|
// byte 5 - generic checksum data
|
||||||
|
|
||||||
|
uint8_t data[5];
|
||||||
|
|
||||||
|
if (!this->read_bytes_raw(data, 5)) {
|
||||||
|
this->status_set_warning();
|
||||||
|
ESP_LOGW(TAG, "Error reading data from FS3000");
|
||||||
|
this->publish_state(NAN);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// checksum passes if the modulo 256 sum of the five bytes is 0
|
||||||
|
uint8_t checksum = 0;
|
||||||
|
for (uint8_t i : data) {
|
||||||
|
checksum += i;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (checksum != 0) {
|
||||||
|
this->status_set_warning();
|
||||||
|
ESP_LOGW(TAG, "Checksum failure when reading from FS3000");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// raw value information is 12 bits
|
||||||
|
uint16_t raw_value = (data[1] << 8) | data[2];
|
||||||
|
ESP_LOGV(TAG, "Got raw reading=%i", raw_value);
|
||||||
|
|
||||||
|
// convert and publish the raw value into m/s using the table of data points in the datasheet
|
||||||
|
this->publish_state(fit_raw_(raw_value));
|
||||||
|
|
||||||
|
this->status_clear_warning();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FS3000Component::dump_config() {
|
||||||
|
ESP_LOGCONFIG(TAG, "FS3000:");
|
||||||
|
LOG_I2C_DEVICE(this);
|
||||||
|
LOG_UPDATE_INTERVAL(this);
|
||||||
|
LOG_SENSOR(" ", "Air Velocity", this);
|
||||||
|
}
|
||||||
|
|
||||||
|
float FS3000Component::fit_raw_(uint16_t raw_value) {
|
||||||
|
// converts a raw value read from the FS3000 into a speed in m/s based on the
|
||||||
|
// reference data points given in the datasheet
|
||||||
|
// fits raw reading using a linear interpolation between each data point
|
||||||
|
|
||||||
|
uint8_t end = 8; // assume model 1005, which has 9 data points
|
||||||
|
if (this->model_ == FIFTEEN)
|
||||||
|
end = 12; // model 1015 has 13 data points
|
||||||
|
|
||||||
|
if (raw_value <= this->raw_data_points_[0]) { // less than smallest data point returns first data point
|
||||||
|
return this->mps_data_points_[0];
|
||||||
|
} else if (raw_value >= this->raw_data_points_[end]) { // greater than largest data point returns max speed
|
||||||
|
return this->mps_data_points_[end];
|
||||||
|
} else {
|
||||||
|
uint8_t i = 0;
|
||||||
|
|
||||||
|
// determine between which data points does the reading fall, i-1 and i
|
||||||
|
while (raw_value > this->raw_data_points_[i]) {
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate the slope of the secant line between the two data points that surrounds the reading
|
||||||
|
float slope = (this->mps_data_points_[i] - this->mps_data_points_[i - 1]) /
|
||||||
|
(this->raw_data_points_[i] - this->raw_data_points_[i - 1]);
|
||||||
|
|
||||||
|
// return the interpolated value for the reading
|
||||||
|
return (float(raw_value - this->raw_data_points_[i - 1])) * slope + this->mps_data_points_[i - 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace fs3000
|
||||||
|
} // namespace esphome
|
35
esphome/components/fs3000/fs3000.h
Normal file
35
esphome/components/fs3000/fs3000.h
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
#include "esphome/components/i2c/i2c.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace fs3000 {
|
||||||
|
|
||||||
|
// FS3000 has two models, 1005 and 1015
|
||||||
|
// 1005 has a max speed detection of 7.23 m/s
|
||||||
|
// 1015 has a max speed detection of 15 m/s
|
||||||
|
enum FS3000Model { FIVE, FIFTEEN };
|
||||||
|
|
||||||
|
class FS3000Component : public PollingComponent, public i2c::I2CDevice, public sensor::Sensor {
|
||||||
|
public:
|
||||||
|
void setup() override;
|
||||||
|
void update() override;
|
||||||
|
|
||||||
|
void dump_config() override;
|
||||||
|
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||||
|
|
||||||
|
void set_model(FS3000Model model) { this->model_ = model; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
FS3000Model model_{};
|
||||||
|
|
||||||
|
uint16_t raw_data_points_[13];
|
||||||
|
float mps_data_points_[13];
|
||||||
|
|
||||||
|
float fit_raw_(uint16_t raw_value);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace fs3000
|
||||||
|
} // namespace esphome
|
50
esphome/components/fs3000/sensor.py
Normal file
50
esphome/components/fs3000/sensor.py
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
# initially based off of TMP117 component
|
||||||
|
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import i2c, sensor
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_MODEL,
|
||||||
|
DEVICE_CLASS_WIND_SPEED,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
)
|
||||||
|
|
||||||
|
DEPENDENCIES = ["i2c"]
|
||||||
|
CODEOWNERS = ["@kahrendt"]
|
||||||
|
|
||||||
|
fs3000_ns = cg.esphome_ns.namespace("fs3000")
|
||||||
|
|
||||||
|
FS3000Model = fs3000_ns.enum("MODEL")
|
||||||
|
FS3000_MODEL_OPTIONS = {
|
||||||
|
"1005": FS3000Model.FIVE,
|
||||||
|
"1015": FS3000Model.FIFTEEN,
|
||||||
|
}
|
||||||
|
|
||||||
|
FS3000Component = fs3000_ns.class_(
|
||||||
|
"FS3000Component", cg.PollingComponent, i2c.I2CDevice, sensor.Sensor
|
||||||
|
)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = (
|
||||||
|
sensor.sensor_schema(
|
||||||
|
FS3000Component,
|
||||||
|
unit_of_measurement="m/s",
|
||||||
|
accuracy_decimals=2,
|
||||||
|
device_class=DEVICE_CLASS_WIND_SPEED,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
)
|
||||||
|
.extend(
|
||||||
|
{
|
||||||
|
cv.Required(CONF_MODEL): cv.enum(FS3000_MODEL_OPTIONS, lower=True),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(cv.polling_component_schema("60s"))
|
||||||
|
.extend(i2c.i2c_device_schema(0x28))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
var = await sensor.new_sensor(config)
|
||||||
|
await cg.register_component(var, config)
|
||||||
|
await i2c.register_i2c_device(var, config)
|
||||||
|
|
||||||
|
cg.add(var.set_model(config[CONF_MODEL]))
|
1
esphome/components/haier/__init__.py
Normal file
1
esphome/components/haier/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
CODEOWNERS = ["@Yarikx"]
|
43
esphome/components/haier/climate.py
Normal file
43
esphome/components/haier/climate.py
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
from esphome.components import climate
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import uart
|
||||||
|
from esphome.components.climate import ClimateSwingMode
|
||||||
|
from esphome.const import CONF_ID, CONF_SUPPORTED_SWING_MODES
|
||||||
|
|
||||||
|
DEPENDENCIES = ["uart"]
|
||||||
|
|
||||||
|
haier_ns = cg.esphome_ns.namespace("haier")
|
||||||
|
HaierClimate = haier_ns.class_(
|
||||||
|
"HaierClimate", climate.Climate, cg.PollingComponent, uart.UARTDevice
|
||||||
|
)
|
||||||
|
|
||||||
|
ALLOWED_CLIMATE_SWING_MODES = {
|
||||||
|
"BOTH": ClimateSwingMode.CLIMATE_SWING_BOTH,
|
||||||
|
"VERTICAL": ClimateSwingMode.CLIMATE_SWING_VERTICAL,
|
||||||
|
"HORIZONTAL": ClimateSwingMode.CLIMATE_SWING_HORIZONTAL,
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_swing_modes = cv.enum(ALLOWED_CLIMATE_SWING_MODES, upper=True)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = cv.All(
|
||||||
|
climate.CLIMATE_SCHEMA.extend(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(HaierClimate),
|
||||||
|
cv.Optional(CONF_SUPPORTED_SWING_MODES): cv.ensure_list(
|
||||||
|
validate_swing_modes
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(cv.polling_component_schema("5s"))
|
||||||
|
.extend(uart.UART_DEVICE_SCHEMA),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
await cg.register_component(var, config)
|
||||||
|
await climate.register_climate(var, config)
|
||||||
|
await uart.register_uart_device(var, config)
|
||||||
|
if CONF_SUPPORTED_SWING_MODES in config:
|
||||||
|
cg.add(var.set_supported_swing_modes(config[CONF_SUPPORTED_SWING_MODES]))
|
302
esphome/components/haier/haier.cpp
Normal file
302
esphome/components/haier/haier.cpp
Normal file
|
@ -0,0 +1,302 @@
|
||||||
|
#include <cmath>
|
||||||
|
#include "haier.h"
|
||||||
|
#include "esphome/core/macros.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace haier {
|
||||||
|
|
||||||
|
static const char *const TAG = "haier";
|
||||||
|
|
||||||
|
static const uint8_t TEMPERATURE = 13;
|
||||||
|
static const uint8_t HUMIDITY = 15;
|
||||||
|
|
||||||
|
static const uint8_t MODE = 23;
|
||||||
|
|
||||||
|
static const uint8_t FAN_SPEED = 25;
|
||||||
|
|
||||||
|
static const uint8_t SWING = 27;
|
||||||
|
|
||||||
|
static const uint8_t POWER = 29;
|
||||||
|
static const uint8_t POWER_MASK = 1;
|
||||||
|
|
||||||
|
static const uint8_t SET_TEMPERATURE = 35;
|
||||||
|
static const uint8_t DECIMAL_MASK = (1 << 5);
|
||||||
|
|
||||||
|
static const uint8_t CRC = 36;
|
||||||
|
|
||||||
|
static const uint8_t COMFORT_PRESET_MASK = (1 << 3);
|
||||||
|
|
||||||
|
static const uint8_t MIN_VALID_TEMPERATURE = 16;
|
||||||
|
static const uint8_t MAX_VALID_TEMPERATURE = 50;
|
||||||
|
static const float TEMPERATURE_STEP = 0.5f;
|
||||||
|
|
||||||
|
static const uint8_t POLL_REQ[13] = {255, 255, 10, 0, 0, 0, 0, 0, 1, 1, 77, 1, 90};
|
||||||
|
static const uint8_t OFF_REQ[13] = {255, 255, 10, 0, 0, 0, 0, 0, 1, 1, 77, 3, 92};
|
||||||
|
|
||||||
|
void HaierClimate::dump_config() {
|
||||||
|
ESP_LOGCONFIG(TAG, "Haier:");
|
||||||
|
ESP_LOGCONFIG(TAG, " Update interval: %u", this->get_update_interval());
|
||||||
|
this->dump_traits_(TAG);
|
||||||
|
this->check_uart_settings(9600);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HaierClimate::loop() {
|
||||||
|
if (this->available() >= sizeof(this->data_)) {
|
||||||
|
this->read_array(this->data_, sizeof(this->data_));
|
||||||
|
if (this->data_[0] != 255 || this->data_[1] != 255)
|
||||||
|
return;
|
||||||
|
|
||||||
|
read_state_(this->data_, sizeof(this->data_));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HaierClimate::update() {
|
||||||
|
this->write_array(POLL_REQ, sizeof(POLL_REQ));
|
||||||
|
dump_message_("Poll sent", POLL_REQ, sizeof(POLL_REQ));
|
||||||
|
}
|
||||||
|
|
||||||
|
climate::ClimateTraits HaierClimate::traits() {
|
||||||
|
auto traits = climate::ClimateTraits();
|
||||||
|
|
||||||
|
traits.set_visual_min_temperature(MIN_VALID_TEMPERATURE);
|
||||||
|
traits.set_visual_max_temperature(MAX_VALID_TEMPERATURE);
|
||||||
|
traits.set_visual_temperature_step(TEMPERATURE_STEP);
|
||||||
|
|
||||||
|
traits.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_HEAT_COOL, climate::CLIMATE_MODE_COOL,
|
||||||
|
climate::CLIMATE_MODE_HEAT, climate::CLIMATE_MODE_FAN_ONLY, climate::CLIMATE_MODE_DRY});
|
||||||
|
|
||||||
|
traits.set_supported_fan_modes({
|
||||||
|
climate::CLIMATE_FAN_AUTO,
|
||||||
|
climate::CLIMATE_FAN_LOW,
|
||||||
|
climate::CLIMATE_FAN_MEDIUM,
|
||||||
|
climate::CLIMATE_FAN_HIGH,
|
||||||
|
});
|
||||||
|
|
||||||
|
traits.set_supported_swing_modes(this->supported_swing_modes_);
|
||||||
|
traits.set_supports_current_temperature(true);
|
||||||
|
traits.set_supports_two_point_target_temperature(false);
|
||||||
|
|
||||||
|
traits.add_supported_preset(climate::CLIMATE_PRESET_NONE);
|
||||||
|
traits.add_supported_preset(climate::CLIMATE_PRESET_COMFORT);
|
||||||
|
|
||||||
|
return traits;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HaierClimate::read_state_(const uint8_t *data, uint8_t size) {
|
||||||
|
dump_message_("Received state", data, size);
|
||||||
|
|
||||||
|
uint8_t check = data[CRC];
|
||||||
|
|
||||||
|
uint8_t crc = get_checksum_(data, size);
|
||||||
|
|
||||||
|
if (check != crc) {
|
||||||
|
ESP_LOGW(TAG, "Invalid checksum");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->current_temperature = data[TEMPERATURE];
|
||||||
|
|
||||||
|
this->target_temperature = data[SET_TEMPERATURE] + MIN_VALID_TEMPERATURE;
|
||||||
|
|
||||||
|
if (data[POWER] & DECIMAL_MASK) {
|
||||||
|
this->target_temperature += 0.5f;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (data[MODE]) {
|
||||||
|
case MODE_SMART:
|
||||||
|
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
|
||||||
|
break;
|
||||||
|
case MODE_COOL:
|
||||||
|
this->mode = climate::CLIMATE_MODE_COOL;
|
||||||
|
break;
|
||||||
|
case MODE_HEAT:
|
||||||
|
this->mode = climate::CLIMATE_MODE_HEAT;
|
||||||
|
break;
|
||||||
|
case MODE_ONLY_FAN:
|
||||||
|
this->mode = climate::CLIMATE_MODE_FAN_ONLY;
|
||||||
|
break;
|
||||||
|
case MODE_DRY:
|
||||||
|
this->mode = climate::CLIMATE_MODE_DRY;
|
||||||
|
break;
|
||||||
|
default: // other modes are unsupported
|
||||||
|
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (data[FAN_SPEED]) {
|
||||||
|
case FAN_AUTO:
|
||||||
|
this->fan_mode = climate::CLIMATE_FAN_AUTO;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FAN_MIN:
|
||||||
|
this->fan_mode = climate::CLIMATE_FAN_LOW;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FAN_MIDDLE:
|
||||||
|
this->fan_mode = climate::CLIMATE_FAN_MEDIUM;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FAN_MAX:
|
||||||
|
this->fan_mode = climate::CLIMATE_FAN_HIGH;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (data[SWING]) {
|
||||||
|
case SWING_OFF:
|
||||||
|
this->swing_mode = climate::CLIMATE_SWING_OFF;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SWING_VERTICAL:
|
||||||
|
this->swing_mode = climate::CLIMATE_SWING_VERTICAL;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SWING_HORIZONTAL:
|
||||||
|
this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SWING_BOTH:
|
||||||
|
this->swing_mode = climate::CLIMATE_SWING_BOTH;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data[POWER] & COMFORT_PRESET_MASK) {
|
||||||
|
this->preset = climate::CLIMATE_PRESET_COMFORT;
|
||||||
|
} else {
|
||||||
|
this->preset = climate::CLIMATE_PRESET_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((data[POWER] & POWER_MASK) == 0) {
|
||||||
|
this->mode = climate::CLIMATE_MODE_OFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->publish_state();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HaierClimate::control(const climate::ClimateCall &call) {
|
||||||
|
if (call.get_mode().has_value()) {
|
||||||
|
switch (call.get_mode().value()) {
|
||||||
|
case climate::CLIMATE_MODE_OFF:
|
||||||
|
send_data_(OFF_REQ, sizeof(OFF_REQ));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case climate::CLIMATE_MODE_HEAT_COOL:
|
||||||
|
case climate::CLIMATE_MODE_AUTO:
|
||||||
|
data_[POWER] |= POWER_MASK;
|
||||||
|
data_[MODE] = MODE_SMART;
|
||||||
|
break;
|
||||||
|
case climate::CLIMATE_MODE_HEAT:
|
||||||
|
data_[POWER] |= POWER_MASK;
|
||||||
|
data_[MODE] = MODE_HEAT;
|
||||||
|
break;
|
||||||
|
case climate::CLIMATE_MODE_COOL:
|
||||||
|
data_[POWER] |= POWER_MASK;
|
||||||
|
data_[MODE] = MODE_COOL;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case climate::CLIMATE_MODE_FAN_ONLY:
|
||||||
|
data_[POWER] |= POWER_MASK;
|
||||||
|
data_[MODE] = MODE_ONLY_FAN;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case climate::CLIMATE_MODE_DRY:
|
||||||
|
data_[POWER] |= POWER_MASK;
|
||||||
|
data_[MODE] = MODE_DRY;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (call.get_preset().has_value()) {
|
||||||
|
if (call.get_preset().value() == climate::CLIMATE_PRESET_COMFORT) {
|
||||||
|
data_[POWER] |= COMFORT_PRESET_MASK;
|
||||||
|
} else {
|
||||||
|
data_[POWER] &= ~COMFORT_PRESET_MASK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (call.get_target_temperature().has_value()) {
|
||||||
|
float target = call.get_target_temperature().value() - MIN_VALID_TEMPERATURE;
|
||||||
|
|
||||||
|
data_[SET_TEMPERATURE] = (uint8_t) target;
|
||||||
|
|
||||||
|
if ((int) target == std::lroundf(target)) {
|
||||||
|
data_[POWER] &= ~DECIMAL_MASK;
|
||||||
|
} else {
|
||||||
|
data_[POWER] |= DECIMAL_MASK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (call.get_fan_mode().has_value()) {
|
||||||
|
switch (call.get_fan_mode().value()) {
|
||||||
|
case climate::CLIMATE_FAN_AUTO:
|
||||||
|
data_[FAN_SPEED] = FAN_AUTO;
|
||||||
|
break;
|
||||||
|
case climate::CLIMATE_FAN_LOW:
|
||||||
|
data_[FAN_SPEED] = FAN_MIN;
|
||||||
|
break;
|
||||||
|
case climate::CLIMATE_FAN_MEDIUM:
|
||||||
|
data_[FAN_SPEED] = FAN_MIDDLE;
|
||||||
|
break;
|
||||||
|
case climate::CLIMATE_FAN_HIGH:
|
||||||
|
data_[FAN_SPEED] = FAN_MAX;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: // other modes are unsupported
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (call.get_swing_mode().has_value()) {
|
||||||
|
switch (call.get_swing_mode().value()) {
|
||||||
|
case climate::CLIMATE_SWING_OFF:
|
||||||
|
data_[SWING] = SWING_OFF;
|
||||||
|
break;
|
||||||
|
case climate::CLIMATE_SWING_VERTICAL:
|
||||||
|
data_[SWING] = SWING_VERTICAL;
|
||||||
|
break;
|
||||||
|
case climate::CLIMATE_SWING_HORIZONTAL:
|
||||||
|
data_[SWING] = SWING_HORIZONTAL;
|
||||||
|
break;
|
||||||
|
case climate::CLIMATE_SWING_BOTH:
|
||||||
|
data_[SWING] = SWING_BOTH;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parts of the message that must have specific values for "send" command.
|
||||||
|
// The meaning of those values is unknown at the moment.
|
||||||
|
data_[9] = 1;
|
||||||
|
data_[10] = 77;
|
||||||
|
data_[11] = 95;
|
||||||
|
data_[17] = 0;
|
||||||
|
|
||||||
|
// Compute checksum
|
||||||
|
uint8_t crc = get_checksum_(data_, sizeof(data_));
|
||||||
|
data_[CRC] = crc;
|
||||||
|
|
||||||
|
send_data_(data_, sizeof(data_));
|
||||||
|
}
|
||||||
|
|
||||||
|
void HaierClimate::send_data_(const uint8_t *message, uint8_t size) {
|
||||||
|
this->write_array(message, size);
|
||||||
|
|
||||||
|
dump_message_("Sent message", message, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HaierClimate::dump_message_(const char *title, const uint8_t *message, uint8_t size) {
|
||||||
|
ESP_LOGV(TAG, "%s:", title);
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
ESP_LOGV(TAG, " byte %02d - %d", i, message[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t HaierClimate::get_checksum_(const uint8_t *message, size_t size) {
|
||||||
|
uint8_t position = size - 1;
|
||||||
|
uint8_t crc = 0;
|
||||||
|
|
||||||
|
for (int i = 2; i < position; i++)
|
||||||
|
crc += message[i];
|
||||||
|
|
||||||
|
return crc;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace haier
|
||||||
|
} // namespace esphome
|
37
esphome/components/haier/haier.h
Normal file
37
esphome/components/haier/haier.h
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/components/climate/climate.h"
|
||||||
|
#include "esphome/components/uart/uart.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace haier {
|
||||||
|
|
||||||
|
enum Mode : uint8_t { MODE_SMART = 0, MODE_COOL = 1, MODE_HEAT = 2, MODE_ONLY_FAN = 3, MODE_DRY = 4 };
|
||||||
|
enum FanSpeed : uint8_t { FAN_MAX = 0, FAN_MIDDLE = 1, FAN_MIN = 2, FAN_AUTO = 3 };
|
||||||
|
enum SwingMode : uint8_t { SWING_OFF = 0, SWING_VERTICAL = 1, SWING_HORIZONTAL = 2, SWING_BOTH = 3 };
|
||||||
|
|
||||||
|
class HaierClimate : public climate::Climate, public uart::UARTDevice, public PollingComponent {
|
||||||
|
public:
|
||||||
|
void loop() override;
|
||||||
|
void update() override;
|
||||||
|
void dump_config() override;
|
||||||
|
void control(const climate::ClimateCall &call) override;
|
||||||
|
void set_supported_swing_modes(const std::set<climate::ClimateSwingMode> &modes) {
|
||||||
|
this->supported_swing_modes_ = modes;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
climate::ClimateTraits traits() override;
|
||||||
|
void read_state_(const uint8_t *data, uint8_t size);
|
||||||
|
void send_data_(const uint8_t *message, uint8_t size);
|
||||||
|
void dump_message_(const char *title, const uint8_t *message, uint8_t size);
|
||||||
|
uint8_t get_checksum_(const uint8_t *message, size_t size);
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint8_t data_[37];
|
||||||
|
std::set<climate::ClimateSwingMode> supported_swing_modes_{};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace haier
|
||||||
|
} // namespace esphome
|
|
@ -52,7 +52,6 @@ CONFIG_SCHEMA = (
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
|
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
await spi.register_spi_device(var, config)
|
await spi.register_spi_device(var, config)
|
||||||
|
|
|
@ -1,153 +1,5 @@
|
||||||
import esphome.codegen as cg
|
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome import core, pins
|
|
||||||
from esphome.components import display, spi
|
CONFIG_SCHEMA = cv.invalid(
|
||||||
from esphome.const import (
|
"The ili9341 platform component has been renamed to ili9xxx."
|
||||||
CONF_COLOR_PALETTE,
|
|
||||||
CONF_DC_PIN,
|
|
||||||
CONF_ID,
|
|
||||||
CONF_LAMBDA,
|
|
||||||
CONF_MODEL,
|
|
||||||
CONF_PAGES,
|
|
||||||
CONF_RAW_DATA_ID,
|
|
||||||
CONF_RESET_PIN,
|
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, HexInt
|
|
||||||
|
|
||||||
DEPENDENCIES = ["spi"]
|
|
||||||
|
|
||||||
CONF_COLOR_PALETTE_IMAGES = "color_palette_images"
|
|
||||||
CONF_LED_PIN = "led_pin"
|
|
||||||
|
|
||||||
ili9341_ns = cg.esphome_ns.namespace("ili9341")
|
|
||||||
ili9341 = ili9341_ns.class_(
|
|
||||||
"ILI9341Display", cg.PollingComponent, spi.SPIDevice, display.DisplayBuffer
|
|
||||||
)
|
|
||||||
ILI9341M5Stack = ili9341_ns.class_("ILI9341M5Stack", ili9341)
|
|
||||||
ILI9341TFT24 = ili9341_ns.class_("ILI9341TFT24", ili9341)
|
|
||||||
ILI9341TFT24R = ili9341_ns.class_("ILI9341TFT24R", ili9341)
|
|
||||||
|
|
||||||
ILI9341Model = ili9341_ns.enum("ILI9341Model")
|
|
||||||
ILI9341ColorMode = ili9341_ns.enum("ILI9341ColorMode")
|
|
||||||
|
|
||||||
MODELS = {
|
|
||||||
"M5STACK": ILI9341Model.M5STACK,
|
|
||||||
"TFT_2.4": ILI9341Model.TFT_24,
|
|
||||||
"TFT_2.4R": ILI9341Model.TFT_24R,
|
|
||||||
}
|
|
||||||
|
|
||||||
ILI9341_MODEL = cv.enum(MODELS, upper=True, space="_")
|
|
||||||
|
|
||||||
COLOR_PALETTE = cv.one_of("NONE", "GRAYSCALE", "IMAGE_ADAPTIVE")
|
|
||||||
|
|
||||||
|
|
||||||
def _validate(config):
|
|
||||||
if config.get(CONF_COLOR_PALETTE) == "IMAGE_ADAPTIVE" and not config.get(
|
|
||||||
CONF_COLOR_PALETTE_IMAGES
|
|
||||||
):
|
|
||||||
raise cv.Invalid(
|
|
||||||
"Color palette in IMAGE_ADAPTIVE mode requires at least one 'color_palette_images' entry to generate palette"
|
|
||||||
)
|
|
||||||
if (
|
|
||||||
config.get(CONF_COLOR_PALETTE_IMAGES)
|
|
||||||
and config.get(CONF_COLOR_PALETTE) != "IMAGE_ADAPTIVE"
|
|
||||||
):
|
|
||||||
raise cv.Invalid(
|
|
||||||
"Providing color palette images requires palette mode to be 'IMAGE_ADAPTIVE'"
|
|
||||||
)
|
|
||||||
return config
|
|
||||||
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.All(
|
|
||||||
display.FULL_DISPLAY_SCHEMA.extend(
|
|
||||||
{
|
|
||||||
cv.GenerateID(): cv.declare_id(ili9341),
|
|
||||||
cv.Required(CONF_MODEL): ILI9341_MODEL,
|
|
||||||
cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema,
|
|
||||||
cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
|
|
||||||
cv.Optional(CONF_LED_PIN): pins.gpio_output_pin_schema,
|
|
||||||
cv.Optional(CONF_COLOR_PALETTE, default="NONE"): COLOR_PALETTE,
|
|
||||||
cv.Optional(CONF_COLOR_PALETTE_IMAGES, default=[]): cv.ensure_list(
|
|
||||||
cv.file_
|
|
||||||
),
|
|
||||||
cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.extend(cv.polling_component_schema("1s"))
|
|
||||||
.extend(spi.spi_device_schema(False)),
|
|
||||||
cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA),
|
|
||||||
_validate,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
|
||||||
if config[CONF_MODEL] == "M5STACK":
|
|
||||||
lcd_type = ILI9341M5Stack
|
|
||||||
if config[CONF_MODEL] == "TFT_2.4":
|
|
||||||
lcd_type = ILI9341TFT24
|
|
||||||
if config[CONF_MODEL] == "TFT_2.4R":
|
|
||||||
lcd_type = ILI9341TFT24R
|
|
||||||
rhs = lcd_type.new()
|
|
||||||
var = cg.Pvariable(config[CONF_ID], rhs)
|
|
||||||
|
|
||||||
await cg.register_component(var, config)
|
|
||||||
await display.register_display(var, config)
|
|
||||||
await spi.register_spi_device(var, config)
|
|
||||||
cg.add(var.set_model(config[CONF_MODEL]))
|
|
||||||
dc = await cg.gpio_pin_expression(config[CONF_DC_PIN])
|
|
||||||
cg.add(var.set_dc_pin(dc))
|
|
||||||
|
|
||||||
if CONF_LAMBDA in config:
|
|
||||||
lambda_ = await cg.process_lambda(
|
|
||||||
config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void
|
|
||||||
)
|
|
||||||
cg.add(var.set_writer(lambda_))
|
|
||||||
if CONF_RESET_PIN in config:
|
|
||||||
reset = await cg.gpio_pin_expression(config[CONF_RESET_PIN])
|
|
||||||
cg.add(var.set_reset_pin(reset))
|
|
||||||
if CONF_LED_PIN in config:
|
|
||||||
led_pin = await cg.gpio_pin_expression(config[CONF_LED_PIN])
|
|
||||||
cg.add(var.set_led_pin(led_pin))
|
|
||||||
|
|
||||||
rhs = None
|
|
||||||
if config[CONF_COLOR_PALETTE] == "GRAYSCALE":
|
|
||||||
cg.add(var.set_buffer_color_mode(ILI9341ColorMode.BITS_8_INDEXED))
|
|
||||||
rhs = []
|
|
||||||
for x in range(256):
|
|
||||||
rhs.extend([HexInt(x), HexInt(x), HexInt(x)])
|
|
||||||
elif config[CONF_COLOR_PALETTE] == "IMAGE_ADAPTIVE":
|
|
||||||
cg.add(var.set_buffer_color_mode(ILI9341ColorMode.BITS_8_INDEXED))
|
|
||||||
from PIL import Image
|
|
||||||
|
|
||||||
def load_image(filename):
|
|
||||||
path = CORE.relative_config_path(filename)
|
|
||||||
try:
|
|
||||||
return Image.open(path)
|
|
||||||
except Exception as e:
|
|
||||||
raise core.EsphomeError(f"Could not load image file {path}: {e}")
|
|
||||||
|
|
||||||
# make a wide horizontal combined image.
|
|
||||||
images = [load_image(x) for x in config[CONF_COLOR_PALETTE_IMAGES]]
|
|
||||||
total_width = sum(i.width for i in images)
|
|
||||||
max_height = max(i.height for i in images)
|
|
||||||
|
|
||||||
ref_image = Image.new("RGB", (total_width, max_height))
|
|
||||||
x = 0
|
|
||||||
for i in images:
|
|
||||||
ref_image.paste(i, (x, 0))
|
|
||||||
x = x + i.width
|
|
||||||
|
|
||||||
# reduce the colors on combined image to 256.
|
|
||||||
converted = ref_image.convert("P", palette=Image.ADAPTIVE, colors=256)
|
|
||||||
# if you want to verify how the images look use
|
|
||||||
# ref_image.save("ref_in.png")
|
|
||||||
# converted.save("ref_out.png")
|
|
||||||
palette = converted.getpalette()
|
|
||||||
assert len(palette) == 256 * 3
|
|
||||||
rhs = palette
|
|
||||||
else:
|
|
||||||
cg.add(var.set_buffer_color_mode(ILI9341ColorMode.BITS_8))
|
|
||||||
|
|
||||||
if rhs is not None:
|
|
||||||
prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs)
|
|
||||||
cg.add(var.set_palette(prog_arr))
|
|
||||||
|
|
|
@ -1,83 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
namespace esphome {
|
|
||||||
namespace ili9341 {
|
|
||||||
|
|
||||||
// Color definitions
|
|
||||||
// clang-format off
|
|
||||||
static const uint8_t MADCTL_MY = 0x80; ///< Bit 7 Bottom to top
|
|
||||||
static const uint8_t MADCTL_MX = 0x40; ///< Bit 6 Right to left
|
|
||||||
static const uint8_t MADCTL_MV = 0x20; ///< Bit 5 Reverse Mode
|
|
||||||
static const uint8_t MADCTL_ML = 0x10; ///< Bit 4 LCD refresh Bottom to top
|
|
||||||
static const uint8_t MADCTL_RGB = 0x00; ///< Bit 3 Red-Green-Blue pixel order
|
|
||||||
static const uint8_t MADCTL_BGR = 0x08; ///< Bit 3 Blue-Green-Red pixel order
|
|
||||||
static const uint8_t MADCTL_MH = 0x04; ///< Bit 2 LCD refresh right to left
|
|
||||||
// clang-format on
|
|
||||||
|
|
||||||
static const uint16_t ILI9341_TFTWIDTH = 320; ///< ILI9341 max TFT width
|
|
||||||
static const uint16_t ILI9341_TFTHEIGHT = 240; ///< ILI9341 max TFT height
|
|
||||||
|
|
||||||
// All ILI9341 specific commands some are used by init()
|
|
||||||
static const uint8_t ILI9341_NOP = 0x00;
|
|
||||||
static const uint8_t ILI9341_SWRESET = 0x01;
|
|
||||||
static const uint8_t ILI9341_RDDID = 0x04;
|
|
||||||
static const uint8_t ILI9341_RDDST = 0x09;
|
|
||||||
|
|
||||||
static const uint8_t ILI9341_SLPIN = 0x10;
|
|
||||||
static const uint8_t ILI9341_SLPOUT = 0x11;
|
|
||||||
static const uint8_t ILI9341_PTLON = 0x12;
|
|
||||||
static const uint8_t ILI9341_NORON = 0x13;
|
|
||||||
|
|
||||||
static const uint8_t ILI9341_RDMODE = 0x0A;
|
|
||||||
static const uint8_t ILI9341_RDMADCTL = 0x0B;
|
|
||||||
static const uint8_t ILI9341_RDPIXFMT = 0x0C;
|
|
||||||
static const uint8_t ILI9341_RDIMGFMT = 0x0A;
|
|
||||||
static const uint8_t ILI9341_RDSELFDIAG = 0x0F;
|
|
||||||
|
|
||||||
static const uint8_t ILI9341_INVOFF = 0x20;
|
|
||||||
static const uint8_t ILI9341_INVON = 0x21;
|
|
||||||
static const uint8_t ILI9341_GAMMASET = 0x26;
|
|
||||||
static const uint8_t ILI9341_DISPOFF = 0x28;
|
|
||||||
static const uint8_t ILI9341_DISPON = 0x29;
|
|
||||||
|
|
||||||
static const uint8_t ILI9341_CASET = 0x2A;
|
|
||||||
static const uint8_t ILI9341_PASET = 0x2B;
|
|
||||||
static const uint8_t ILI9341_RAMWR = 0x2C;
|
|
||||||
static const uint8_t ILI9341_RAMRD = 0x2E;
|
|
||||||
|
|
||||||
static const uint8_t ILI9341_PTLAR = 0x30;
|
|
||||||
static const uint8_t ILI9341_VSCRDEF = 0x33;
|
|
||||||
static const uint8_t ILI9341_MADCTL = 0x36;
|
|
||||||
static const uint8_t ILI9341_VSCRSADD = 0x37;
|
|
||||||
static const uint8_t ILI9341_PIXFMT = 0x3A;
|
|
||||||
|
|
||||||
static const uint8_t ILI9341_WRDISBV = 0x51;
|
|
||||||
static const uint8_t ILI9341_RDDISBV = 0x52;
|
|
||||||
static const uint8_t ILI9341_WRCTRLD = 0x53;
|
|
||||||
|
|
||||||
static const uint8_t ILI9341_FRMCTR1 = 0xB1;
|
|
||||||
static const uint8_t ILI9341_FRMCTR2 = 0xB2;
|
|
||||||
static const uint8_t ILI9341_FRMCTR3 = 0xB3;
|
|
||||||
static const uint8_t ILI9341_INVCTR = 0xB4;
|
|
||||||
static const uint8_t ILI9341_DFUNCTR = 0xB6;
|
|
||||||
|
|
||||||
static const uint8_t ILI9341_PWCTR1 = 0xC0;
|
|
||||||
static const uint8_t ILI9341_PWCTR2 = 0xC1;
|
|
||||||
static const uint8_t ILI9341_PWCTR3 = 0xC2;
|
|
||||||
static const uint8_t ILI9341_PWCTR4 = 0xC3;
|
|
||||||
static const uint8_t ILI9341_PWCTR5 = 0xC4;
|
|
||||||
static const uint8_t ILI9341_VMCTR1 = 0xC5;
|
|
||||||
static const uint8_t ILI9341_VMCTR2 = 0xC7;
|
|
||||||
|
|
||||||
static const uint8_t ILI9341_RDID4 = 0xD3;
|
|
||||||
static const uint8_t ILI9341_RDINDEX = 0xD9;
|
|
||||||
static const uint8_t ILI9341_RDID1 = 0xDA;
|
|
||||||
static const uint8_t ILI9341_RDID2 = 0xDB;
|
|
||||||
static const uint8_t ILI9341_RDID3 = 0xDC;
|
|
||||||
static const uint8_t ILI9341_RDIDX = 0xDD; // TBC
|
|
||||||
|
|
||||||
static const uint8_t ILI9341_GMCTRP1 = 0xE0;
|
|
||||||
static const uint8_t ILI9341_GMCTRN1 = 0xE1;
|
|
||||||
|
|
||||||
} // namespace ili9341
|
|
||||||
} // namespace esphome
|
|
|
@ -1,308 +0,0 @@
|
||||||
#include "ili9341_display.h"
|
|
||||||
#include "esphome/core/log.h"
|
|
||||||
#include "esphome/core/application.h"
|
|
||||||
#include "esphome/core/helpers.h"
|
|
||||||
#include "esphome/core/hal.h"
|
|
||||||
|
|
||||||
namespace esphome {
|
|
||||||
namespace ili9341 {
|
|
||||||
|
|
||||||
static const char *const TAG = "ili9341";
|
|
||||||
|
|
||||||
void ILI9341Display::setup_pins_() {
|
|
||||||
this->dc_pin_->setup(); // OUTPUT
|
|
||||||
this->dc_pin_->digital_write(false);
|
|
||||||
if (this->reset_pin_ != nullptr) {
|
|
||||||
this->reset_pin_->setup(); // OUTPUT
|
|
||||||
this->reset_pin_->digital_write(true);
|
|
||||||
}
|
|
||||||
if (this->led_pin_ != nullptr) {
|
|
||||||
this->led_pin_->setup();
|
|
||||||
this->led_pin_->digital_write(true);
|
|
||||||
}
|
|
||||||
this->spi_setup();
|
|
||||||
|
|
||||||
this->reset_();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ILI9341Display::dump_config() {
|
|
||||||
LOG_DISPLAY("", "ili9341", this);
|
|
||||||
LOG_PIN(" Reset Pin: ", this->reset_pin_);
|
|
||||||
LOG_PIN(" DC Pin: ", this->dc_pin_);
|
|
||||||
LOG_PIN(" Busy Pin: ", this->busy_pin_);
|
|
||||||
LOG_UPDATE_INTERVAL(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
float ILI9341Display::get_setup_priority() const { return setup_priority::HARDWARE; }
|
|
||||||
|
|
||||||
void ILI9341Display::command(uint8_t value) {
|
|
||||||
this->start_command_();
|
|
||||||
this->write_byte(value);
|
|
||||||
this->end_command_();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ILI9341Display::reset_() {
|
|
||||||
if (this->reset_pin_ != nullptr) {
|
|
||||||
this->reset_pin_->digital_write(false);
|
|
||||||
delay(10);
|
|
||||||
this->reset_pin_->digital_write(true);
|
|
||||||
delay(10);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ILI9341Display::data(uint8_t value) {
|
|
||||||
this->start_data_();
|
|
||||||
this->write_byte(value);
|
|
||||||
this->end_data_();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ILI9341Display::send_command(uint8_t command_byte, const uint8_t *data_bytes, uint8_t num_data_bytes) {
|
|
||||||
this->command(command_byte); // Send the command byte
|
|
||||||
this->start_data_();
|
|
||||||
this->write_array(data_bytes, num_data_bytes);
|
|
||||||
this->end_data_();
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t ILI9341Display::read_command(uint8_t command_byte, uint8_t index) {
|
|
||||||
uint8_t data = 0x10 + index;
|
|
||||||
this->send_command(0xD9, &data, 1); // Set Index Register
|
|
||||||
uint8_t result;
|
|
||||||
this->start_command_();
|
|
||||||
this->write_byte(command_byte);
|
|
||||||
this->start_data_();
|
|
||||||
do {
|
|
||||||
result = this->read_byte();
|
|
||||||
} while (index--);
|
|
||||||
this->end_data_();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ILI9341Display::update() {
|
|
||||||
this->do_update_();
|
|
||||||
this->display_();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ILI9341Display::display_() {
|
|
||||||
// we will only update the changed window to the display
|
|
||||||
uint16_t w = this->x_high_ - this->x_low_ + 1;
|
|
||||||
uint16_t h = this->y_high_ - this->y_low_ + 1;
|
|
||||||
uint32_t start_pos = ((this->y_low_ * this->width_) + x_low_);
|
|
||||||
|
|
||||||
// check if something was displayed
|
|
||||||
if ((this->x_high_ < this->x_low_) || (this->y_high_ < this->y_low_)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
set_addr_window_(this->x_low_, this->y_low_, w, h);
|
|
||||||
|
|
||||||
ESP_LOGVV("ILI9341", "Start ILI9341Display::display_(xl:%d, xh:%d, yl:%d, yh:%d, w:%d, h:%d, start_pos:%d)",
|
|
||||||
this->x_low_, this->x_high_, this->y_low_, this->y_high_, w, h, start_pos);
|
|
||||||
|
|
||||||
this->start_data_();
|
|
||||||
for (uint16_t row = 0; row < h; row++) {
|
|
||||||
uint32_t pos = start_pos + (row * width_);
|
|
||||||
uint32_t rem = w;
|
|
||||||
|
|
||||||
while (rem > 0) {
|
|
||||||
uint32_t sz = buffer_to_transfer_(pos, rem);
|
|
||||||
this->write_array(transfer_buffer_, 2 * sz);
|
|
||||||
pos += sz;
|
|
||||||
rem -= sz;
|
|
||||||
App.feed_wdt();
|
|
||||||
}
|
|
||||||
App.feed_wdt();
|
|
||||||
}
|
|
||||||
this->end_data_();
|
|
||||||
|
|
||||||
// invalidate watermarks
|
|
||||||
this->x_low_ = this->width_;
|
|
||||||
this->y_low_ = this->height_;
|
|
||||||
this->x_high_ = 0;
|
|
||||||
this->y_high_ = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ILI9341Display::fill(Color color) {
|
|
||||||
uint8_t color332 = 0;
|
|
||||||
if (this->buffer_color_mode_ == BITS_8) {
|
|
||||||
color332 = display::ColorUtil::color_to_332(color);
|
|
||||||
} else { // if (this->buffer_color_mode_ == BITS_8_INDEXED)
|
|
||||||
color332 = display::ColorUtil::color_to_index8_palette888(color, this->palette_);
|
|
||||||
}
|
|
||||||
memset(this->buffer_, color332, this->get_buffer_length_());
|
|
||||||
this->x_low_ = 0;
|
|
||||||
this->y_low_ = 0;
|
|
||||||
this->x_high_ = this->get_width_internal() - 1;
|
|
||||||
this->y_high_ = this->get_height_internal() - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ILI9341Display::fill_internal_(uint8_t color) {
|
|
||||||
memset(transfer_buffer_, color, sizeof(transfer_buffer_));
|
|
||||||
|
|
||||||
uint32_t rem = (this->get_buffer_length_() * 2);
|
|
||||||
|
|
||||||
this->set_addr_window_(0, 0, this->get_width_internal(), this->get_height_internal());
|
|
||||||
this->start_data_();
|
|
||||||
|
|
||||||
while (rem > 0) {
|
|
||||||
size_t sz = rem <= sizeof(transfer_buffer_) ? rem : sizeof(transfer_buffer_);
|
|
||||||
this->write_array(transfer_buffer_, sz);
|
|
||||||
rem -= sz;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->end_data_();
|
|
||||||
|
|
||||||
memset(buffer_, color, this->get_buffer_length_());
|
|
||||||
}
|
|
||||||
|
|
||||||
void ILI9341Display::rotate_my_(uint8_t m) {
|
|
||||||
uint8_t rotation = m & 3; // can't be higher than 3
|
|
||||||
switch (rotation) {
|
|
||||||
case 0:
|
|
||||||
m = (MADCTL_MX | MADCTL_BGR);
|
|
||||||
// _width = ILI9341_TFTWIDTH;
|
|
||||||
// _height = ILI9341_TFTHEIGHT;
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
m = (MADCTL_MV | MADCTL_BGR);
|
|
||||||
// _width = ILI9341_TFTHEIGHT;
|
|
||||||
// _height = ILI9341_TFTWIDTH;
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
m = (MADCTL_MY | MADCTL_BGR);
|
|
||||||
// _width = ILI9341_TFTWIDTH;
|
|
||||||
// _height = ILI9341_TFTHEIGHT;
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
m = (MADCTL_MX | MADCTL_MY | MADCTL_MV | MADCTL_BGR);
|
|
||||||
// _width = ILI9341_TFTHEIGHT;
|
|
||||||
// _height = ILI9341_TFTWIDTH;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->command(ILI9341_MADCTL);
|
|
||||||
this->data(m);
|
|
||||||
}
|
|
||||||
|
|
||||||
void HOT ILI9341Display::draw_absolute_pixel_internal(int x, int y, Color color) {
|
|
||||||
if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
uint32_t pos = (y * width_) + x;
|
|
||||||
uint8_t new_color;
|
|
||||||
|
|
||||||
if (this->buffer_color_mode_ == BITS_8) {
|
|
||||||
new_color = display::ColorUtil::color_to_332(color, display::ColorOrder::COLOR_ORDER_RGB);
|
|
||||||
} else { // if (this->buffer_color_mode_ == BITS_8_INDEXED) {
|
|
||||||
new_color = display::ColorUtil::color_to_index8_palette888(color, this->palette_);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (buffer_[pos] != new_color) {
|
|
||||||
buffer_[pos] = new_color;
|
|
||||||
// low and high watermark may speed up drawing from buffer
|
|
||||||
this->x_low_ = (x < this->x_low_) ? x : this->x_low_;
|
|
||||||
this->y_low_ = (y < this->y_low_) ? y : this->y_low_;
|
|
||||||
this->x_high_ = (x > this->x_high_) ? x : this->x_high_;
|
|
||||||
this->y_high_ = (y > this->y_high_) ? y : this->y_high_;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// should return the total size: return this->get_width_internal() * this->get_height_internal() * 2 // 16bit color
|
|
||||||
// values per bit is huge
|
|
||||||
uint32_t ILI9341Display::get_buffer_length_() { return this->get_width_internal() * this->get_height_internal(); }
|
|
||||||
|
|
||||||
void ILI9341Display::start_command_() {
|
|
||||||
this->dc_pin_->digital_write(false);
|
|
||||||
this->enable();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ILI9341Display::end_command_() { this->disable(); }
|
|
||||||
void ILI9341Display::start_data_() {
|
|
||||||
this->dc_pin_->digital_write(true);
|
|
||||||
this->enable();
|
|
||||||
}
|
|
||||||
void ILI9341Display::end_data_() { this->disable(); }
|
|
||||||
|
|
||||||
void ILI9341Display::init_lcd_(const uint8_t *init_cmd) {
|
|
||||||
uint8_t cmd, x, num_args;
|
|
||||||
const uint8_t *addr = init_cmd;
|
|
||||||
while ((cmd = progmem_read_byte(addr++)) > 0) {
|
|
||||||
x = progmem_read_byte(addr++);
|
|
||||||
num_args = x & 0x7F;
|
|
||||||
send_command(cmd, addr, num_args);
|
|
||||||
addr += num_args;
|
|
||||||
if (x & 0x80)
|
|
||||||
delay(150); // NOLINT
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ILI9341Display::set_addr_window_(uint16_t x1, uint16_t y1, uint16_t w, uint16_t h) {
|
|
||||||
uint16_t x2 = (x1 + w - 1), y2 = (y1 + h - 1);
|
|
||||||
this->command(ILI9341_CASET); // Column address set
|
|
||||||
this->start_data_();
|
|
||||||
this->write_byte(x1 >> 8);
|
|
||||||
this->write_byte(x1);
|
|
||||||
this->write_byte(x2 >> 8);
|
|
||||||
this->write_byte(x2);
|
|
||||||
this->end_data_();
|
|
||||||
this->command(ILI9341_PASET); // Row address set
|
|
||||||
this->start_data_();
|
|
||||||
this->write_byte(y1 >> 8);
|
|
||||||
this->write_byte(y1);
|
|
||||||
this->write_byte(y2 >> 8);
|
|
||||||
this->write_byte(y2);
|
|
||||||
this->end_data_();
|
|
||||||
this->command(ILI9341_RAMWR); // Write to RAM
|
|
||||||
}
|
|
||||||
|
|
||||||
void ILI9341Display::invert_display_(bool invert) { this->command(invert ? ILI9341_INVON : ILI9341_INVOFF); }
|
|
||||||
|
|
||||||
int ILI9341Display::get_width_internal() { return this->width_; }
|
|
||||||
int ILI9341Display::get_height_internal() { return this->height_; }
|
|
||||||
|
|
||||||
uint32_t ILI9341Display::buffer_to_transfer_(uint32_t pos, uint32_t sz) {
|
|
||||||
uint8_t *src = buffer_ + pos;
|
|
||||||
uint8_t *dst = transfer_buffer_;
|
|
||||||
|
|
||||||
if (sz > sizeof(transfer_buffer_) / 2) {
|
|
||||||
sz = sizeof(transfer_buffer_) / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (uint32_t i = 0; i < sz; ++i) {
|
|
||||||
uint16_t color;
|
|
||||||
if (this->buffer_color_mode_ == BITS_8) {
|
|
||||||
color = display::ColorUtil::color_to_565(display::ColorUtil::rgb332_to_color(*src++));
|
|
||||||
} else { // if (this->buffer_color_mode == BITS_8_INDEXED) {
|
|
||||||
Color col = display::ColorUtil::index8_to_color_palette888(*src++, this->palette_);
|
|
||||||
color = display::ColorUtil::color_to_565(col);
|
|
||||||
}
|
|
||||||
*dst++ = (uint8_t)(color >> 8);
|
|
||||||
*dst++ = (uint8_t) color;
|
|
||||||
}
|
|
||||||
|
|
||||||
return sz;
|
|
||||||
}
|
|
||||||
|
|
||||||
// M5Stack display
|
|
||||||
void ILI9341M5Stack::initialize() {
|
|
||||||
this->init_lcd_(INITCMD_M5STACK);
|
|
||||||
this->width_ = 320;
|
|
||||||
this->height_ = 240;
|
|
||||||
this->invert_display_(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 24_TFT display
|
|
||||||
void ILI9341TFT24::initialize() {
|
|
||||||
this->init_lcd_(INITCMD_TFT);
|
|
||||||
this->width_ = 240;
|
|
||||||
this->height_ = 320;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 24_TFT rotated display
|
|
||||||
void ILI9341TFT24R::initialize() {
|
|
||||||
this->init_lcd_(INITCMD_TFT);
|
|
||||||
this->width_ = 320;
|
|
||||||
this->height_ = 240;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace ili9341
|
|
||||||
} // namespace esphome
|
|
|
@ -1,70 +0,0 @@
|
||||||
#pragma once
|
|
||||||
#include "esphome/core/helpers.h"
|
|
||||||
|
|
||||||
namespace esphome {
|
|
||||||
namespace ili9341 {
|
|
||||||
|
|
||||||
// clang-format off
|
|
||||||
static const uint8_t PROGMEM INITCMD_M5STACK[] = {
|
|
||||||
0xEF, 3, 0x03, 0x80, 0x02,
|
|
||||||
0xCF, 3, 0x00, 0xC1, 0x30,
|
|
||||||
0xED, 4, 0x64, 0x03, 0x12, 0x81,
|
|
||||||
0xE8, 3, 0x85, 0x00, 0x78,
|
|
||||||
0xCB, 5, 0x39, 0x2C, 0x00, 0x34, 0x02,
|
|
||||||
0xF7, 1, 0x20,
|
|
||||||
0xEA, 2, 0x00, 0x00,
|
|
||||||
ILI9341_PWCTR1 , 1, 0x23, // Power control VRH[5:0]
|
|
||||||
ILI9341_PWCTR2 , 1, 0x10, // Power control SAP[2:0];BT[3:0]
|
|
||||||
ILI9341_VMCTR1 , 2, 0x3e, 0x28, // VCM control
|
|
||||||
ILI9341_VMCTR2 , 1, 0x86, // VCM control2
|
|
||||||
ILI9341_MADCTL , 1, MADCTL_BGR, // Memory Access Control
|
|
||||||
ILI9341_VSCRSADD, 1, 0x00, // Vertical scroll zero
|
|
||||||
ILI9341_PIXFMT , 1, 0x55,
|
|
||||||
ILI9341_FRMCTR1 , 2, 0x00, 0x13,
|
|
||||||
ILI9341_DFUNCTR , 3, 0x08, 0x82, 0x27, // Display Function Control
|
|
||||||
0xF2, 1, 0x00, // 3Gamma Function Disable
|
|
||||||
ILI9341_GAMMASET , 1, 0x01, // Gamma curve selected
|
|
||||||
ILI9341_GMCTRP1 , 15, 0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, // Set Gamma
|
|
||||||
0x4E, 0xF1, 0x37, 0x07, 0x10, 0x03,
|
|
||||||
0x0E, 0x09, 0x00,
|
|
||||||
ILI9341_GMCTRN1 , 15, 0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, // Set Gamma
|
|
||||||
0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C,
|
|
||||||
0x31, 0x36, 0x0F,
|
|
||||||
ILI9341_SLPOUT , 0x80, // Exit Sleep
|
|
||||||
ILI9341_DISPON , 0x80, // Display on
|
|
||||||
0x00 // End of list
|
|
||||||
};
|
|
||||||
|
|
||||||
static const uint8_t PROGMEM INITCMD_TFT[] = {
|
|
||||||
0xEF, 3, 0x03, 0x80, 0x02,
|
|
||||||
0xCF, 3, 0x00, 0xC1, 0x30,
|
|
||||||
0xED, 4, 0x64, 0x03, 0x12, 0x81,
|
|
||||||
0xE8, 3, 0x85, 0x00, 0x78,
|
|
||||||
0xCB, 5, 0x39, 0x2C, 0x00, 0x34, 0x02,
|
|
||||||
0xF7, 1, 0x20,
|
|
||||||
0xEA, 2, 0x00, 0x00,
|
|
||||||
ILI9341_PWCTR1 , 1, 0x23, // Power control VRH[5:0]
|
|
||||||
ILI9341_PWCTR2 , 1, 0x10, // Power control SAP[2:0];BT[3:0]
|
|
||||||
ILI9341_VMCTR1 , 2, 0x3e, 0x28, // VCM control
|
|
||||||
ILI9341_VMCTR2 , 1, 0x86, // VCM control2
|
|
||||||
ILI9341_MADCTL , 1, 0x48, // Memory Access Control
|
|
||||||
ILI9341_VSCRSADD, 1, 0x00, // Vertical scroll zero
|
|
||||||
ILI9341_PIXFMT , 1, 0x55,
|
|
||||||
ILI9341_FRMCTR1 , 2, 0x00, 0x18,
|
|
||||||
ILI9341_DFUNCTR , 3, 0x08, 0x82, 0x27, // Display Function Control
|
|
||||||
0xF2, 1, 0x00, // 3Gamma Function Disable
|
|
||||||
ILI9341_GAMMASET , 1, 0x01, // Gamma curve selected
|
|
||||||
ILI9341_GMCTRP1 , 15, 0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, // Set Gamma
|
|
||||||
0x4E, 0xF1, 0x37, 0x07, 0x10, 0x03,
|
|
||||||
0x0E, 0x09, 0x00,
|
|
||||||
ILI9341_GMCTRN1 , 15, 0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, // Set Gamma
|
|
||||||
0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C,
|
|
||||||
0x31, 0x36, 0x0F,
|
|
||||||
ILI9341_SLPOUT , 0x80, // Exit Sleep
|
|
||||||
ILI9341_DISPON , 0x80, // Display on
|
|
||||||
0x00 // End of list
|
|
||||||
};
|
|
||||||
|
|
||||||
// clang-format on
|
|
||||||
} // namespace ili9341
|
|
||||||
} // namespace esphome
|
|
0
esphome/components/ili9xxx/__init__.py
Normal file
0
esphome/components/ili9xxx/__init__.py
Normal file
175
esphome/components/ili9xxx/display.py
Normal file
175
esphome/components/ili9xxx/display.py
Normal file
|
@ -0,0 +1,175 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome import core, pins
|
||||||
|
from esphome.components import display, spi
|
||||||
|
from esphome.core import CORE, HexInt
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_COLOR_PALETTE,
|
||||||
|
CONF_DC_PIN,
|
||||||
|
CONF_ID,
|
||||||
|
CONF_LAMBDA,
|
||||||
|
CONF_MODEL,
|
||||||
|
CONF_RAW_DATA_ID,
|
||||||
|
CONF_PAGES,
|
||||||
|
CONF_RESET_PIN,
|
||||||
|
CONF_DIMENSIONS,
|
||||||
|
)
|
||||||
|
|
||||||
|
DEPENDENCIES = ["spi"]
|
||||||
|
|
||||||
|
|
||||||
|
def AUTO_LOAD():
|
||||||
|
if CORE.is_esp32:
|
||||||
|
return ["psram"]
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
CODEOWNERS = ["@nielsnl68"]
|
||||||
|
|
||||||
|
ili9XXX_ns = cg.esphome_ns.namespace("ili9xxx")
|
||||||
|
ili9XXXSPI = ili9XXX_ns.class_(
|
||||||
|
"ILI9XXXDisplay", cg.PollingComponent, spi.SPIDevice, display.DisplayBuffer
|
||||||
|
)
|
||||||
|
|
||||||
|
ILI9XXXColorMode = ili9XXX_ns.enum("ILI9XXXColorMode")
|
||||||
|
|
||||||
|
MODELS = {
|
||||||
|
"M5STACK": ili9XXX_ns.class_("ILI9XXXM5Stack", ili9XXXSPI),
|
||||||
|
"M5CORE": ili9XXX_ns.class_("ILI9XXXM5CORE", ili9XXXSPI),
|
||||||
|
"TFT_2.4": ili9XXX_ns.class_("ILI9XXXILI9341", ili9XXXSPI),
|
||||||
|
"TFT_2.4R": ili9XXX_ns.class_("ILI9XXXILI9342", ili9XXXSPI),
|
||||||
|
"ILI9341": ili9XXX_ns.class_("ILI9XXXILI9341", ili9XXXSPI),
|
||||||
|
"ILI9342": ili9XXX_ns.class_("ILI9XXXILI9342", ili9XXXSPI),
|
||||||
|
"ILI9481": ili9XXX_ns.class_("ILI9XXXILI9481", ili9XXXSPI),
|
||||||
|
"ILI9486": ili9XXX_ns.class_("ILI9XXXILI9486", ili9XXXSPI),
|
||||||
|
"ILI9488": ili9XXX_ns.class_("ILI9XXXILI9488", ili9XXXSPI),
|
||||||
|
"ST7796": ili9XXX_ns.class_("ILI9XXXST7796", ili9XXXSPI),
|
||||||
|
}
|
||||||
|
|
||||||
|
COLOR_PALETTE = cv.one_of("NONE", "GRAYSCALE", "IMAGE_ADAPTIVE")
|
||||||
|
|
||||||
|
CONF_LED_PIN = "led_pin"
|
||||||
|
CONF_COLOR_PALETTE_IMAGES = "color_palette_images"
|
||||||
|
|
||||||
|
|
||||||
|
def _validate(config):
|
||||||
|
if config.get(CONF_COLOR_PALETTE) == "IMAGE_ADAPTIVE" and not config.get(
|
||||||
|
CONF_COLOR_PALETTE_IMAGES
|
||||||
|
):
|
||||||
|
raise cv.Invalid(
|
||||||
|
"Color palette in IMAGE_ADAPTIVE mode requires at least one 'color_palette_images' entry to generate palette"
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
config.get(CONF_COLOR_PALETTE_IMAGES)
|
||||||
|
and config.get(CONF_COLOR_PALETTE) != "IMAGE_ADAPTIVE"
|
||||||
|
):
|
||||||
|
raise cv.Invalid(
|
||||||
|
"Providing color palette images requires palette mode to be 'IMAGE_ADAPTIVE'"
|
||||||
|
)
|
||||||
|
if CORE.is_esp8266 and config.get(CONF_MODEL) not in [
|
||||||
|
"M5STACK",
|
||||||
|
"TFT_2.4",
|
||||||
|
"TFT_2.4R",
|
||||||
|
"ILI9341",
|
||||||
|
"ILI9342",
|
||||||
|
]:
|
||||||
|
raise cv.Invalid(
|
||||||
|
"Provided model can't run on ESP8266. Use an ESP32 with PSRAM onboard"
|
||||||
|
)
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = cv.All(
|
||||||
|
display.FULL_DISPLAY_SCHEMA.extend(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(ili9XXXSPI),
|
||||||
|
cv.Required(CONF_MODEL): cv.enum(MODELS, upper=True, space="_"),
|
||||||
|
cv.Optional(CONF_DIMENSIONS): cv.dimensions,
|
||||||
|
cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema,
|
||||||
|
cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
|
||||||
|
cv.Optional(CONF_LED_PIN): cv.invalid(
|
||||||
|
"This property is removed. To use the backlight use proper light component."
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_COLOR_PALETTE, default="NONE"): COLOR_PALETTE,
|
||||||
|
cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8),
|
||||||
|
cv.Optional(CONF_COLOR_PALETTE_IMAGES, default=[]): cv.ensure_list(
|
||||||
|
cv.file_
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(cv.polling_component_schema("1s"))
|
||||||
|
.extend(spi.spi_device_schema(False)),
|
||||||
|
cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA),
|
||||||
|
_validate,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
rhs = MODELS[config[CONF_MODEL]].new()
|
||||||
|
var = cg.Pvariable(config[CONF_ID], rhs)
|
||||||
|
|
||||||
|
await cg.register_component(var, config)
|
||||||
|
await display.register_display(var, config)
|
||||||
|
await spi.register_spi_device(var, config)
|
||||||
|
dc = await cg.gpio_pin_expression(config[CONF_DC_PIN])
|
||||||
|
cg.add(var.set_dc_pin(dc))
|
||||||
|
|
||||||
|
if CONF_LAMBDA in config:
|
||||||
|
lambda_ = await cg.process_lambda(
|
||||||
|
config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void
|
||||||
|
)
|
||||||
|
cg.add(var.set_writer(lambda_))
|
||||||
|
|
||||||
|
if CONF_RESET_PIN in config:
|
||||||
|
reset = await cg.gpio_pin_expression(config[CONF_RESET_PIN])
|
||||||
|
cg.add(var.set_reset_pin(reset))
|
||||||
|
|
||||||
|
if CONF_DIMENSIONS in config:
|
||||||
|
cg.add(
|
||||||
|
var.set_dimentions(config[CONF_DIMENSIONS][0], config[CONF_DIMENSIONS][1])
|
||||||
|
)
|
||||||
|
|
||||||
|
rhs = None
|
||||||
|
if config[CONF_COLOR_PALETTE] == "GRAYSCALE":
|
||||||
|
cg.add(var.set_buffer_color_mode(ILI9XXXColorMode.BITS_8_INDEXED))
|
||||||
|
rhs = []
|
||||||
|
for x in range(256):
|
||||||
|
rhs.extend([HexInt(x), HexInt(x), HexInt(x)])
|
||||||
|
prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs)
|
||||||
|
cg.add(var.set_palette(prog_arr))
|
||||||
|
elif config[CONF_COLOR_PALETTE] == "IMAGE_ADAPTIVE":
|
||||||
|
cg.add(var.set_buffer_color_mode(ILI9XXXColorMode.BITS_8_INDEXED))
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
def load_image(filename):
|
||||||
|
path = CORE.relative_config_path(filename)
|
||||||
|
try:
|
||||||
|
return Image.open(path)
|
||||||
|
except Exception as e:
|
||||||
|
raise core.EsphomeError(f"Could not load image file {path}: {e}")
|
||||||
|
|
||||||
|
# make a wide horizontal combined image.
|
||||||
|
images = [load_image(x) for x in config[CONF_COLOR_PALETTE_IMAGES]]
|
||||||
|
total_width = sum(i.width for i in images)
|
||||||
|
max_height = max(i.height for i in images)
|
||||||
|
|
||||||
|
ref_image = Image.new("RGB", (total_width, max_height))
|
||||||
|
x = 0
|
||||||
|
for i in images:
|
||||||
|
ref_image.paste(i, (x, 0))
|
||||||
|
x = x + i.width
|
||||||
|
|
||||||
|
# reduce the colors on combined image to 256.
|
||||||
|
converted = ref_image.convert("P", palette=Image.ADAPTIVE, colors=256)
|
||||||
|
# if you want to verify how the images look use
|
||||||
|
# ref_image.save("ref_in.png")
|
||||||
|
# converted.save("ref_out.png")
|
||||||
|
palette = converted.getpalette()
|
||||||
|
assert len(palette) == 256 * 3
|
||||||
|
rhs = palette
|
||||||
|
else:
|
||||||
|
cg.add(var.set_buffer_color_mode(ILI9XXXColorMode.BITS_16))
|
||||||
|
|
||||||
|
if rhs is not None:
|
||||||
|
prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs)
|
||||||
|
cg.add(var.set_palette(prog_arr))
|
96
esphome/components/ili9xxx/ili9xxx_defines.h
Normal file
96
esphome/components/ili9xxx/ili9xxx_defines.h
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace ili9xxx {
|
||||||
|
|
||||||
|
// Color definitions
|
||||||
|
// clang-format off
|
||||||
|
static const uint8_t MADCTL_MY = 0x80; ///< Bit 7 Bottom to top
|
||||||
|
static const uint8_t MADCTL_MX = 0x40; ///< Bit 6 Right to left
|
||||||
|
static const uint8_t MADCTL_MV = 0x20; ///< Bit 5 Reverse Mode
|
||||||
|
static const uint8_t MADCTL_ML = 0x10; ///< Bit 4 LCD refresh Bottom to top
|
||||||
|
static const uint8_t MADCTL_RGB = 0x00; ///< Bit 3 Red-Green-Blue pixel order
|
||||||
|
static const uint8_t MADCTL_BGR = 0x08; ///< Bit 3 Blue-Green-Red pixel order
|
||||||
|
static const uint8_t MADCTL_MH = 0x04; ///< Bit 2 LCD refresh right to left
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
// All ILI9XXX specific commands some are used by init()
|
||||||
|
static const uint8_t ILI9XXX_NOP = 0x00;
|
||||||
|
static const uint8_t ILI9XXX_SWRESET = 0x01;
|
||||||
|
static const uint8_t ILI9XXX_RDDID = 0x04;
|
||||||
|
static const uint8_t ILI9XXX_RDDST = 0x09;
|
||||||
|
|
||||||
|
static const uint8_t ILI9XXX_SLPIN = 0x10;
|
||||||
|
static const uint8_t ILI9XXX_SLPOUT = 0x11;
|
||||||
|
static const uint8_t ILI9XXX_PTLON = 0x12;
|
||||||
|
static const uint8_t ILI9XXX_NORON = 0x13;
|
||||||
|
|
||||||
|
static const uint8_t ILI9XXX_RDMODE = 0x0A;
|
||||||
|
static const uint8_t ILI9XXX_RDMADCTL = 0x0B;
|
||||||
|
static const uint8_t ILI9XXX_RDPIXFMT = 0x0C;
|
||||||
|
static const uint8_t ILI9XXX_RDIMGFMT = 0x0D;
|
||||||
|
static const uint8_t ILI9XXX_RDSELFDIAG = 0x0F;
|
||||||
|
|
||||||
|
static const uint8_t ILI9XXX_INVOFF = 0x20;
|
||||||
|
static const uint8_t ILI9XXX_INVON = 0x21;
|
||||||
|
static const uint8_t ILI9XXX_GAMMASET = 0x26;
|
||||||
|
static const uint8_t ILI9XXX_DISPOFF = 0x28;
|
||||||
|
static const uint8_t ILI9XXX_DISPON = 0x29;
|
||||||
|
|
||||||
|
static const uint8_t ILI9XXX_CASET = 0x2A;
|
||||||
|
static const uint8_t ILI9XXX_PASET = 0x2B;
|
||||||
|
static const uint8_t ILI9XXX_RAMWR = 0x2C;
|
||||||
|
static const uint8_t ILI9XXX_RAMRD = 0x2E;
|
||||||
|
|
||||||
|
static const uint8_t ILI9XXX_PTLAR = 0x30;
|
||||||
|
static const uint8_t ILI9XXX_VSCRDEF = 0x33;
|
||||||
|
static const uint8_t ILI9XXX_MADCTL = 0x36;
|
||||||
|
static const uint8_t ILI9XXX_VSCRSADD = 0x37;
|
||||||
|
static const uint8_t ILI9XXX_IDMOFF = 0x38;
|
||||||
|
static const uint8_t ILI9XXX_IDMON = 0x39;
|
||||||
|
static const uint8_t ILI9XXX_PIXFMT = 0x3A;
|
||||||
|
static const uint8_t ILI9XXX_COLMOD = 0x3A;
|
||||||
|
|
||||||
|
static const uint8_t ILI9XXX_GETSCANLINE = 0x45;
|
||||||
|
|
||||||
|
static const uint8_t ILI9XXX_WRDISBV = 0x51;
|
||||||
|
static const uint8_t ILI9XXX_RDDISBV = 0x52;
|
||||||
|
static const uint8_t ILI9XXX_WRCTRLD = 0x53;
|
||||||
|
|
||||||
|
static const uint8_t ILI9XXX_IFMODE = 0xB0;
|
||||||
|
static const uint8_t ILI9XXX_FRMCTR1 = 0xB1;
|
||||||
|
static const uint8_t ILI9XXX_FRMCTR2 = 0xB2;
|
||||||
|
static const uint8_t ILI9XXX_FRMCTR3 = 0xB3;
|
||||||
|
static const uint8_t ILI9XXX_INVCTR = 0xB4;
|
||||||
|
static const uint8_t ILI9XXX_DFUNCTR = 0xB6;
|
||||||
|
static const uint8_t ILI9XXX_ETMOD = 0xB7;
|
||||||
|
|
||||||
|
static const uint8_t ILI9XXX_PWCTR1 = 0xC0;
|
||||||
|
static const uint8_t ILI9XXX_PWCTR2 = 0xC1;
|
||||||
|
static const uint8_t ILI9XXX_PWCTR3 = 0xC2;
|
||||||
|
static const uint8_t ILI9XXX_PWCTR4 = 0xC3;
|
||||||
|
static const uint8_t ILI9XXX_PWCTR5 = 0xC4;
|
||||||
|
static const uint8_t ILI9XXX_VMCTR1 = 0xC5;
|
||||||
|
static const uint8_t ILI9XXX_IFCTR = 0xC6;
|
||||||
|
static const uint8_t ILI9XXX_VMCTR2 = 0xC7;
|
||||||
|
static const uint8_t ILI9XXX_GMCTR = 0xC8;
|
||||||
|
static const uint8_t ILI9XXX_SETEXTC = 0xC8;
|
||||||
|
|
||||||
|
static const uint8_t ILI9XXX_PWSET = 0xD0;
|
||||||
|
static const uint8_t ILI9XXX_VMCTR = 0xD1;
|
||||||
|
static const uint8_t ILI9XXX_PWSETN = 0xD2;
|
||||||
|
static const uint8_t ILI9XXX_RDID4 = 0xD3;
|
||||||
|
static const uint8_t ILI9XXX_RDINDEX = 0xD9;
|
||||||
|
static const uint8_t ILI9XXX_RDID1 = 0xDA;
|
||||||
|
static const uint8_t ILI9XXX_RDID2 = 0xDB;
|
||||||
|
static const uint8_t ILI9XXX_RDID3 = 0xDC;
|
||||||
|
static const uint8_t ILI9XXX_RDIDX = 0xDD; // TBC
|
||||||
|
|
||||||
|
static const uint8_t ILI9XXX_GMCTRP1 = 0xE0;
|
||||||
|
static const uint8_t ILI9XXX_GMCTRN1 = 0xE1;
|
||||||
|
|
||||||
|
static const uint8_t ILI9XXX_CSCON = 0xF0;
|
||||||
|
static const uint8_t ILI9XXX_ADJCTL3 = 0xF7;
|
||||||
|
|
||||||
|
} // namespace ili9xxx
|
||||||
|
} // namespace esphome
|
416
esphome/components/ili9xxx/ili9xxx_display.cpp
Normal file
416
esphome/components/ili9xxx/ili9xxx_display.cpp
Normal file
|
@ -0,0 +1,416 @@
|
||||||
|
#include "ili9xxx_display.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/application.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
#include "esphome/core/hal.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace ili9xxx {
|
||||||
|
|
||||||
|
static const char *const TAG = "ili9xxx";
|
||||||
|
|
||||||
|
void ILI9XXXDisplay::setup() {
|
||||||
|
this->setup_pins_();
|
||||||
|
this->initialize();
|
||||||
|
|
||||||
|
this->x_low_ = this->width_;
|
||||||
|
this->y_low_ = this->height_;
|
||||||
|
this->x_high_ = 0;
|
||||||
|
this->y_high_ = 0;
|
||||||
|
if (this->buffer_color_mode_ == BITS_16) {
|
||||||
|
this->init_internal_(this->get_buffer_length_() * 2);
|
||||||
|
if (this->buffer_ != nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->buffer_color_mode_ = BITS_8;
|
||||||
|
}
|
||||||
|
this->init_internal_(this->get_buffer_length_());
|
||||||
|
if (this->buffer_ == nullptr) {
|
||||||
|
this->mark_failed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ILI9XXXDisplay::setup_pins_() {
|
||||||
|
this->dc_pin_->setup(); // OUTPUT
|
||||||
|
this->dc_pin_->digital_write(false);
|
||||||
|
if (this->reset_pin_ != nullptr) {
|
||||||
|
this->reset_pin_->setup(); // OUTPUT
|
||||||
|
this->reset_pin_->digital_write(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
this->spi_setup();
|
||||||
|
|
||||||
|
this->reset_();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ILI9XXXDisplay::dump_config() {
|
||||||
|
LOG_DISPLAY("", "ili9xxx", this);
|
||||||
|
switch (this->buffer_color_mode_) {
|
||||||
|
case BITS_8_INDEXED:
|
||||||
|
ESP_LOGCONFIG(TAG, " Color mode: 8bit Indexed");
|
||||||
|
break;
|
||||||
|
case BITS_16:
|
||||||
|
ESP_LOGCONFIG(TAG, " Color mode: 16bit");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ESP_LOGCONFIG(TAG, " Color mode: 8bit 332 mode");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (this->is_18bitdisplay_) {
|
||||||
|
ESP_LOGCONFIG(TAG, " 18-Bit Mode: YES");
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_PIN(" Reset Pin: ", this->reset_pin_);
|
||||||
|
LOG_PIN(" DC Pin: ", this->dc_pin_);
|
||||||
|
LOG_PIN(" Busy Pin: ", this->busy_pin_);
|
||||||
|
|
||||||
|
if (this->is_failed()) {
|
||||||
|
ESP_LOGCONFIG(TAG, " => Failed to init Memory: YES!");
|
||||||
|
}
|
||||||
|
LOG_UPDATE_INTERVAL(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
float ILI9XXXDisplay::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||||
|
|
||||||
|
void ILI9XXXDisplay::fill(Color color) {
|
||||||
|
uint16_t new_color = 0;
|
||||||
|
this->x_low_ = 0;
|
||||||
|
this->y_low_ = 0;
|
||||||
|
this->x_high_ = this->get_width_internal() - 1;
|
||||||
|
this->y_high_ = this->get_height_internal() - 1;
|
||||||
|
switch (this->buffer_color_mode_) {
|
||||||
|
case BITS_8_INDEXED:
|
||||||
|
new_color = display::ColorUtil::color_to_index8_palette888(color, this->palette_);
|
||||||
|
break;
|
||||||
|
case BITS_16:
|
||||||
|
new_color = display::ColorUtil::color_to_565(color);
|
||||||
|
for (uint32_t i = 0; i < this->get_buffer_length_() * 2; i = i + 2) {
|
||||||
|
this->buffer_[i] = (uint8_t)(new_color >> 8);
|
||||||
|
this->buffer_[i + 1] = (uint8_t) new_color;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
new_color = display::ColorUtil::color_to_332(color, display::ColorOrder::COLOR_ORDER_RGB);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
memset(this->buffer_, (uint8_t) new_color, this->get_buffer_length_());
|
||||||
|
}
|
||||||
|
|
||||||
|
void HOT ILI9XXXDisplay::draw_absolute_pixel_internal(int x, int y, Color color) {
|
||||||
|
if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uint32_t pos = (y * width_) + x;
|
||||||
|
uint16_t new_color;
|
||||||
|
bool updated = false;
|
||||||
|
switch (this->buffer_color_mode_) {
|
||||||
|
case BITS_8_INDEXED:
|
||||||
|
new_color = display::ColorUtil::color_to_index8_palette888(color, this->palette_);
|
||||||
|
break;
|
||||||
|
case BITS_16:
|
||||||
|
pos = pos * 2;
|
||||||
|
new_color = display::ColorUtil::color_to_565(color, display::ColorOrder::COLOR_ORDER_RGB);
|
||||||
|
if (this->buffer_[pos] != (uint8_t)(new_color >> 8)) {
|
||||||
|
this->buffer_[pos] = (uint8_t)(new_color >> 8);
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
pos = pos + 1;
|
||||||
|
new_color = new_color & 0xFF;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
new_color = display::ColorUtil::color_to_332(color, display::ColorOrder::COLOR_ORDER_RGB);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->buffer_[pos] != new_color) {
|
||||||
|
this->buffer_[pos] = new_color;
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
if (updated) {
|
||||||
|
// low and high watermark may speed up drawing from buffer
|
||||||
|
this->x_low_ = (x < this->x_low_) ? x : this->x_low_;
|
||||||
|
this->y_low_ = (y < this->y_low_) ? y : this->y_low_;
|
||||||
|
this->x_high_ = (x > this->x_high_) ? x : this->x_high_;
|
||||||
|
this->y_high_ = (y > this->y_high_) ? y : this->y_high_;
|
||||||
|
// ESP_LOGVV(TAG, "=>>> pixel (x:%d, y:%d) (xl:%d, xh:%d, yl:%d, yh:%d", x, y, this->x_low_, this->x_high_,
|
||||||
|
// this->y_low_, this->y_high_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ILI9XXXDisplay::update() {
|
||||||
|
if (this->prossing_update_) {
|
||||||
|
this->need_update_ = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
do {
|
||||||
|
this->prossing_update_ = true;
|
||||||
|
this->need_update_ = false;
|
||||||
|
if (!this->need_update_) {
|
||||||
|
this->do_update_();
|
||||||
|
}
|
||||||
|
} while (this->need_update_);
|
||||||
|
this->prossing_update_ = false;
|
||||||
|
this->display_();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ILI9XXXDisplay::display_() {
|
||||||
|
// we will only update the changed window to the display
|
||||||
|
uint16_t w = this->x_high_ - this->x_low_ + 1; // NOLINT
|
||||||
|
uint16_t h = this->y_high_ - this->y_low_ + 1; // NOLINT
|
||||||
|
uint32_t start_pos = ((this->y_low_ * this->width_) + x_low_);
|
||||||
|
|
||||||
|
// check if something was displayed
|
||||||
|
if ((this->x_high_ < this->x_low_) || (this->y_high_ < this->y_low_)) {
|
||||||
|
ESP_LOGV(TAG, "Nothing to display");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
set_addr_window_(this->x_low_, this->y_low_, w, h);
|
||||||
|
|
||||||
|
ESP_LOGV(TAG,
|
||||||
|
"Start display(xlow:%d, ylow:%d, xhigh:%d, yhigh:%d, width:%d, "
|
||||||
|
"heigth:%d, start_pos:%d)",
|
||||||
|
this->x_low_, this->y_low_, this->x_high_, this->y_high_, w, h, start_pos);
|
||||||
|
|
||||||
|
this->start_data_();
|
||||||
|
for (uint16_t row = 0; row < h; row++) {
|
||||||
|
uint32_t pos = start_pos + (row * width_);
|
||||||
|
uint32_t rem = w;
|
||||||
|
|
||||||
|
while (rem > 0) {
|
||||||
|
uint32_t sz = std::min(rem, ILI9XXX_TRANSFER_BUFFER_SIZE);
|
||||||
|
// ESP_LOGVV(TAG, "Send to display(pos:%d, rem:%d, zs:%d)", pos, rem, sz);
|
||||||
|
buffer_to_transfer_(pos, sz);
|
||||||
|
if (this->is_18bitdisplay_) {
|
||||||
|
for (uint32_t i = 0; i < sz; ++i) {
|
||||||
|
uint16_t color_val = transfer_buffer_[i];
|
||||||
|
|
||||||
|
uint8_t red = color_val & 0x1F;
|
||||||
|
uint8_t green = (color_val & 0x7E0) >> 5;
|
||||||
|
uint8_t blue = (color_val & 0xF800) >> 11;
|
||||||
|
|
||||||
|
uint8_t pass_buff[3];
|
||||||
|
|
||||||
|
pass_buff[2] = (uint8_t)((red / 32.0) * 64) << 2;
|
||||||
|
pass_buff[1] = (uint8_t) green << 2;
|
||||||
|
pass_buff[0] = (uint8_t)((blue / 32.0) * 64) << 2;
|
||||||
|
|
||||||
|
this->write_array(pass_buff, sizeof(pass_buff));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this->write_array16(transfer_buffer_, sz);
|
||||||
|
}
|
||||||
|
pos += sz;
|
||||||
|
rem -= sz;
|
||||||
|
}
|
||||||
|
App.feed_wdt();
|
||||||
|
}
|
||||||
|
this->end_data_();
|
||||||
|
|
||||||
|
// invalidate watermarks
|
||||||
|
this->x_low_ = this->width_;
|
||||||
|
this->y_low_ = this->height_;
|
||||||
|
this->x_high_ = 0;
|
||||||
|
this->y_high_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t ILI9XXXDisplay::buffer_to_transfer_(uint32_t pos, uint32_t sz) {
|
||||||
|
for (uint32_t i = 0; i < sz; ++i) {
|
||||||
|
switch (this->buffer_color_mode_) {
|
||||||
|
case BITS_8_INDEXED:
|
||||||
|
transfer_buffer_[i] = display::ColorUtil::color_to_565(
|
||||||
|
display::ColorUtil::index8_to_color_palette888(this->buffer_[pos + i], this->palette_));
|
||||||
|
break;
|
||||||
|
case BITS_16:
|
||||||
|
transfer_buffer_[i] = ((uint16_t) this->buffer_[(pos + i) * 2] << 8) | this->buffer_[((pos + i) * 2) + 1];
|
||||||
|
continue;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
transfer_buffer_[i] =
|
||||||
|
display::ColorUtil::color_to_565(display::ColorUtil::rgb332_to_color(this->buffer_[pos + i]));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sz;
|
||||||
|
}
|
||||||
|
|
||||||
|
// should return the total size: return this->get_width_internal() * this->get_height_internal() * 2 // 16bit color
|
||||||
|
// values per bit is huge
|
||||||
|
uint32_t ILI9XXXDisplay::get_buffer_length_() { return this->get_width_internal() * this->get_height_internal(); }
|
||||||
|
|
||||||
|
void ILI9XXXDisplay::command(uint8_t value) {
|
||||||
|
this->start_command_();
|
||||||
|
this->write_byte(value);
|
||||||
|
this->end_command_();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ILI9XXXDisplay::data(uint8_t value) {
|
||||||
|
this->start_data_();
|
||||||
|
this->write_byte(value);
|
||||||
|
this->end_data_();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ILI9XXXDisplay::send_command(uint8_t command_byte, const uint8_t *data_bytes, uint8_t num_data_bytes) {
|
||||||
|
this->command(command_byte); // Send the command byte
|
||||||
|
this->start_data_();
|
||||||
|
this->write_array(data_bytes, num_data_bytes);
|
||||||
|
this->end_data_();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t ILI9XXXDisplay::read_command(uint8_t command_byte, uint8_t index) {
|
||||||
|
uint8_t data = 0x10 + index;
|
||||||
|
this->send_command(0xD9, &data, 1); // Set Index Register
|
||||||
|
uint8_t result;
|
||||||
|
this->start_command_();
|
||||||
|
this->write_byte(command_byte);
|
||||||
|
this->start_data_();
|
||||||
|
do {
|
||||||
|
result = this->read_byte();
|
||||||
|
} while (index--);
|
||||||
|
this->end_data_();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ILI9XXXDisplay::start_command_() {
|
||||||
|
this->dc_pin_->digital_write(false);
|
||||||
|
this->enable();
|
||||||
|
}
|
||||||
|
void ILI9XXXDisplay::start_data_() {
|
||||||
|
this->dc_pin_->digital_write(true);
|
||||||
|
this->enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ILI9XXXDisplay::end_command_() { this->disable(); }
|
||||||
|
void ILI9XXXDisplay::end_data_() { this->disable(); }
|
||||||
|
|
||||||
|
void ILI9XXXDisplay::reset_() {
|
||||||
|
if (this->reset_pin_ != nullptr) {
|
||||||
|
this->reset_pin_->digital_write(false);
|
||||||
|
delay(10);
|
||||||
|
this->reset_pin_->digital_write(true);
|
||||||
|
delay(10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ILI9XXXDisplay::init_lcd_(const uint8_t *init_cmd) {
|
||||||
|
uint8_t cmd, x, num_args;
|
||||||
|
const uint8_t *addr = init_cmd;
|
||||||
|
while ((cmd = progmem_read_byte(addr++)) > 0) {
|
||||||
|
x = progmem_read_byte(addr++);
|
||||||
|
num_args = x & 0x7F;
|
||||||
|
send_command(cmd, addr, num_args);
|
||||||
|
addr += num_args;
|
||||||
|
if (x & 0x80)
|
||||||
|
delay(150); // NOLINT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ILI9XXXDisplay::set_addr_window_(uint16_t x1, uint16_t y1, uint16_t w, uint16_t h) {
|
||||||
|
uint16_t x2 = (x1 + w - 1), y2 = (y1 + h - 1);
|
||||||
|
this->command(ILI9XXX_CASET); // Column address set
|
||||||
|
this->start_data_();
|
||||||
|
this->write_byte(x1 >> 8);
|
||||||
|
this->write_byte(x1);
|
||||||
|
this->write_byte(x2 >> 8);
|
||||||
|
this->write_byte(x2);
|
||||||
|
this->end_data_();
|
||||||
|
this->command(ILI9XXX_PASET); // Row address set
|
||||||
|
this->start_data_();
|
||||||
|
this->write_byte(y1 >> 8);
|
||||||
|
this->write_byte(y1);
|
||||||
|
this->write_byte(y2 >> 8);
|
||||||
|
this->write_byte(y2);
|
||||||
|
this->end_data_();
|
||||||
|
this->command(ILI9XXX_RAMWR); // Write to RAM
|
||||||
|
}
|
||||||
|
|
||||||
|
void ILI9XXXDisplay::invert_display_(bool invert) { this->command(invert ? ILI9XXX_INVON : ILI9XXX_INVOFF); }
|
||||||
|
|
||||||
|
int ILI9XXXDisplay::get_width_internal() { return this->width_; }
|
||||||
|
int ILI9XXXDisplay::get_height_internal() { return this->height_; }
|
||||||
|
|
||||||
|
// M5Stack display
|
||||||
|
void ILI9XXXM5Stack::initialize() {
|
||||||
|
this->init_lcd_(INITCMD_M5STACK);
|
||||||
|
if (this->width_ == 0)
|
||||||
|
this->width_ = 320;
|
||||||
|
if (this->height_ == 0)
|
||||||
|
this->height_ = 240;
|
||||||
|
this->invert_display_(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// M5CORE display // Based on the configuration settings of M5stact's M5GFX code.
|
||||||
|
void ILI9XXXM5CORE::initialize() {
|
||||||
|
this->init_lcd_(INITCMD_M5CORE);
|
||||||
|
if (this->width_ == 0)
|
||||||
|
this->width_ = 320;
|
||||||
|
if (this->height_ == 0)
|
||||||
|
this->height_ = 240;
|
||||||
|
this->invert_display_(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 24_TFT display
|
||||||
|
void ILI9XXXILI9341::initialize() {
|
||||||
|
this->init_lcd_(INITCMD_ILI9341);
|
||||||
|
if (this->width_ == 0)
|
||||||
|
this->width_ = 240;
|
||||||
|
if (this->height_ == 0)
|
||||||
|
this->height_ = 320;
|
||||||
|
}
|
||||||
|
// 24_TFT rotated display
|
||||||
|
void ILI9XXXILI9342::initialize() {
|
||||||
|
this->init_lcd_(INITCMD_ILI9341);
|
||||||
|
if (this->width_ == 0) {
|
||||||
|
this->width_ = 320;
|
||||||
|
}
|
||||||
|
if (this->height_ == 0) {
|
||||||
|
this->height_ = 240;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 35_TFT display
|
||||||
|
void ILI9XXXILI9481::initialize() {
|
||||||
|
this->init_lcd_(INITCMD_ILI9481);
|
||||||
|
if (this->width_ == 0) {
|
||||||
|
this->width_ = 480;
|
||||||
|
}
|
||||||
|
if (this->height_ == 0) {
|
||||||
|
this->height_ = 320;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 35_TFT display
|
||||||
|
void ILI9XXXILI9486::initialize() {
|
||||||
|
this->init_lcd_(INITCMD_ILI9486);
|
||||||
|
if (this->width_ == 0) {
|
||||||
|
this->width_ = 480;
|
||||||
|
}
|
||||||
|
if (this->height_ == 0) {
|
||||||
|
this->height_ = 320;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 40_TFT display
|
||||||
|
void ILI9XXXILI9488::initialize() {
|
||||||
|
this->init_lcd_(INITCMD_ILI9488);
|
||||||
|
if (this->width_ == 0) {
|
||||||
|
this->width_ = 480;
|
||||||
|
}
|
||||||
|
if (this->height_ == 0) {
|
||||||
|
this->height_ = 320;
|
||||||
|
}
|
||||||
|
this->is_18bitdisplay_ = true;
|
||||||
|
}
|
||||||
|
// 40_TFT display
|
||||||
|
void ILI9XXXST7796::initialize() {
|
||||||
|
this->init_lcd_(INITCMD_ST7796);
|
||||||
|
if (this->width_ == 0) {
|
||||||
|
this->width_ = 320;
|
||||||
|
}
|
||||||
|
if (this->height_ == 0) {
|
||||||
|
this->height_ = 480;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ili9xxx
|
||||||
|
} // namespace esphome
|
|
@ -1,27 +1,21 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "esphome/core/component.h"
|
|
||||||
#include "esphome/components/spi/spi.h"
|
#include "esphome/components/spi/spi.h"
|
||||||
#include "esphome/components/display/display_buffer.h"
|
#include "esphome/components/display/display_buffer.h"
|
||||||
#include "ili9341_defines.h"
|
#include "ili9xxx_defines.h"
|
||||||
#include "ili9341_init.h"
|
#include "ili9xxx_init.h"
|
||||||
#include "esphome/core/log.h"
|
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace ili9341 {
|
namespace ili9xxx {
|
||||||
|
|
||||||
enum ILI9341Model {
|
const uint32_t ILI9XXX_TRANSFER_BUFFER_SIZE = 64;
|
||||||
M5STACK = 0,
|
|
||||||
TFT_24,
|
enum ILI9XXXColorMode {
|
||||||
TFT_24R,
|
BITS_8 = 0x08,
|
||||||
|
BITS_8_INDEXED = 0x09,
|
||||||
|
BITS_16 = 0x10,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum ILI9341ColorMode {
|
class ILI9XXXDisplay : public PollingComponent,
|
||||||
BITS_8,
|
|
||||||
BITS_8_INDEXED,
|
|
||||||
};
|
|
||||||
|
|
||||||
class ILI9341Display : public PollingComponent,
|
|
||||||
public display::DisplayBuffer,
|
public display::DisplayBuffer,
|
||||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
|
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
|
||||||
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_40MHZ> {
|
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_40MHZ> {
|
||||||
|
@ -29,59 +23,46 @@ class ILI9341Display : public PollingComponent,
|
||||||
void set_dc_pin(GPIOPin *dc_pin) { dc_pin_ = dc_pin; }
|
void set_dc_pin(GPIOPin *dc_pin) { dc_pin_ = dc_pin; }
|
||||||
float get_setup_priority() const override;
|
float get_setup_priority() const override;
|
||||||
void set_reset_pin(GPIOPin *reset) { this->reset_pin_ = reset; }
|
void set_reset_pin(GPIOPin *reset) { this->reset_pin_ = reset; }
|
||||||
void set_led_pin(GPIOPin *led) { this->led_pin_ = led; }
|
|
||||||
void set_model(ILI9341Model model) { this->model_ = model; }
|
|
||||||
void set_palette(const uint8_t *palette) { this->palette_ = palette; }
|
void set_palette(const uint8_t *palette) { this->palette_ = palette; }
|
||||||
void set_buffer_color_mode(ILI9341ColorMode color_mode) { this->buffer_color_mode_ = color_mode; }
|
void set_buffer_color_mode(ILI9XXXColorMode color_mode) { this->buffer_color_mode_ = color_mode; }
|
||||||
|
void set_dimentions(int16_t width, int16_t height) {
|
||||||
|
this->height_ = height;
|
||||||
|
this->width_ = width;
|
||||||
|
}
|
||||||
void command(uint8_t value);
|
void command(uint8_t value);
|
||||||
void data(uint8_t value);
|
void data(uint8_t value);
|
||||||
void send_command(uint8_t command_byte, const uint8_t *data_bytes, uint8_t num_data_bytes);
|
void send_command(uint8_t command_byte, const uint8_t *data_bytes, uint8_t num_data_bytes);
|
||||||
uint8_t read_command(uint8_t command_byte, uint8_t index);
|
uint8_t read_command(uint8_t command_byte, uint8_t index);
|
||||||
virtual void initialize() = 0;
|
|
||||||
|
|
||||||
void update() override;
|
void update() override;
|
||||||
|
|
||||||
void fill(Color color) override;
|
void fill(Color color) override;
|
||||||
|
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
void setup() override {
|
void setup() override;
|
||||||
this->setup_pins_();
|
|
||||||
this->initialize();
|
|
||||||
|
|
||||||
this->x_low_ = this->width_;
|
|
||||||
this->y_low_ = this->height_;
|
|
||||||
this->x_high_ = 0;
|
|
||||||
this->y_high_ = 0;
|
|
||||||
|
|
||||||
this->init_internal_(this->get_buffer_length_());
|
|
||||||
this->fill_internal_(0x00);
|
|
||||||
}
|
|
||||||
|
|
||||||
display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_COLOR; }
|
display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_COLOR; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void draw_absolute_pixel_internal(int x, int y, Color color) override;
|
void draw_absolute_pixel_internal(int x, int y, Color color) override;
|
||||||
void setup_pins_();
|
void setup_pins_();
|
||||||
|
virtual void initialize() = 0;
|
||||||
|
|
||||||
|
void display_();
|
||||||
void init_lcd_(const uint8_t *init_cmd);
|
void init_lcd_(const uint8_t *init_cmd);
|
||||||
void set_addr_window_(uint16_t x, uint16_t y, uint16_t w, uint16_t h);
|
void set_addr_window_(uint16_t x, uint16_t y, uint16_t w, uint16_t h);
|
||||||
void invert_display_(bool invert);
|
void invert_display_(bool invert);
|
||||||
void reset_();
|
void reset_();
|
||||||
void fill_internal_(uint8_t color);
|
|
||||||
void display_();
|
|
||||||
void rotate_my_(uint8_t m);
|
|
||||||
|
|
||||||
ILI9341Model model_;
|
int16_t width_{0}; ///< Display width as modified by current rotation
|
||||||
int16_t width_{320}; ///< Display width as modified by current rotation
|
int16_t height_{0}; ///< Display height as modified by current rotation
|
||||||
int16_t height_{240}; ///< Display height as modified by current rotation
|
|
||||||
uint16_t x_low_{0};
|
uint16_t x_low_{0};
|
||||||
uint16_t y_low_{0};
|
uint16_t y_low_{0};
|
||||||
uint16_t x_high_{0};
|
uint16_t x_high_{0};
|
||||||
uint16_t y_high_{0};
|
uint16_t y_high_{0};
|
||||||
const uint8_t *palette_;
|
const uint8_t *palette_;
|
||||||
|
|
||||||
ILI9341ColorMode buffer_color_mode_{BITS_8};
|
ILI9XXXColorMode buffer_color_mode_{BITS_16};
|
||||||
|
|
||||||
uint32_t get_buffer_length_();
|
uint32_t get_buffer_length_();
|
||||||
int get_width_internal() override;
|
int get_width_internal() override;
|
||||||
|
@ -92,33 +73,66 @@ class ILI9341Display : public PollingComponent,
|
||||||
void start_data_();
|
void start_data_();
|
||||||
void end_data_();
|
void end_data_();
|
||||||
|
|
||||||
uint8_t transfer_buffer_[64];
|
uint16_t transfer_buffer_[ILI9XXX_TRANSFER_BUFFER_SIZE];
|
||||||
|
|
||||||
uint32_t buffer_to_transfer_(uint32_t pos, uint32_t sz);
|
uint32_t buffer_to_transfer_(uint32_t pos, uint32_t sz);
|
||||||
|
|
||||||
GPIOPin *reset_pin_{nullptr};
|
GPIOPin *reset_pin_{nullptr};
|
||||||
GPIOPin *led_pin_{nullptr};
|
GPIOPin *dc_pin_{nullptr};
|
||||||
GPIOPin *dc_pin_;
|
|
||||||
GPIOPin *busy_pin_{nullptr};
|
GPIOPin *busy_pin_{nullptr};
|
||||||
|
|
||||||
|
bool prossing_update_ = false;
|
||||||
|
bool need_update_ = false;
|
||||||
|
bool is_18bitdisplay_ = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
//----------- M5Stack display --------------
|
//----------- M5Stack display --------------
|
||||||
class ILI9341M5Stack : public ILI9341Display {
|
class ILI9XXXM5Stack : public ILI9XXXDisplay {
|
||||||
public:
|
protected:
|
||||||
void initialize() override;
|
void initialize() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
//----------- ILI9341_24_TFT display --------------
|
//----------- M5Stack display --------------
|
||||||
class ILI9341TFT24 : public ILI9341Display {
|
class ILI9XXXM5CORE : public ILI9XXXDisplay {
|
||||||
public:
|
protected:
|
||||||
void initialize() override;
|
void initialize() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
//----------- ILI9341_24_TFT rotated display --------------
|
//----------- ILI9XXX_24_TFT display --------------
|
||||||
class ILI9341TFT24R : public ILI9341Display {
|
class ILI9XXXILI9341 : public ILI9XXXDisplay {
|
||||||
public:
|
protected:
|
||||||
void initialize() override;
|
void initialize() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ili9341
|
//----------- ILI9XXX_24_TFT rotated display --------------
|
||||||
|
class ILI9XXXILI9342 : public ILI9XXXDisplay {
|
||||||
|
protected:
|
||||||
|
void initialize() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
//----------- ILI9XXX_??_TFT rotated display --------------
|
||||||
|
class ILI9XXXILI9481 : public ILI9XXXDisplay {
|
||||||
|
protected:
|
||||||
|
void initialize() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
//----------- ILI9XXX_35_TFT rotated display --------------
|
||||||
|
class ILI9XXXILI9486 : public ILI9XXXDisplay {
|
||||||
|
protected:
|
||||||
|
void initialize() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
//----------- ILI9XXX_35_TFT rotated display --------------
|
||||||
|
class ILI9XXXILI9488 : public ILI9XXXDisplay {
|
||||||
|
protected:
|
||||||
|
void initialize() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
//----------- ILI9XXX_35_TFT rotated display --------------
|
||||||
|
class ILI9XXXST7796 : public ILI9XXXDisplay {
|
||||||
|
protected:
|
||||||
|
void initialize() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ili9xxx
|
||||||
} // namespace esphome
|
} // namespace esphome
|
174
esphome/components/ili9xxx/ili9xxx_init.h
Normal file
174
esphome/components/ili9xxx/ili9xxx_init.h
Normal file
|
@ -0,0 +1,174 @@
|
||||||
|
#pragma once
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace ili9xxx {
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
static const uint8_t PROGMEM INITCMD_M5STACK[] = {
|
||||||
|
0xEF, 3, 0x03, 0x80, 0x02,
|
||||||
|
0xCF, 3, 0x00, 0xC1, 0x30,
|
||||||
|
0xED, 4, 0x64, 0x03, 0x12, 0x81,
|
||||||
|
0xE8, 3, 0x85, 0x00, 0x78,
|
||||||
|
0xCB, 5, 0x39, 0x2C, 0x00, 0x34, 0x02,
|
||||||
|
0xF7, 1, 0x20,
|
||||||
|
0xEA, 2, 0x00, 0x00,
|
||||||
|
ILI9XXX_PWCTR1 , 1, 0x23, // Power control VRH[5:0]
|
||||||
|
ILI9XXX_PWCTR2 , 1, 0x10, // Power control SAP[2:0];BT[3:0]
|
||||||
|
ILI9XXX_VMCTR1 , 2, 0x3e, 0x28, // VCM control
|
||||||
|
ILI9XXX_VMCTR2 , 1, 0x86, // VCM control2
|
||||||
|
ILI9XXX_MADCTL , 1, MADCTL_BGR, // Memory Access Control
|
||||||
|
ILI9XXX_VSCRSADD, 1, 0x00, // Vertical scroll zero
|
||||||
|
ILI9XXX_PIXFMT , 1, 0x55,
|
||||||
|
ILI9XXX_FRMCTR1 , 2, 0x00, 0x13,
|
||||||
|
ILI9XXX_DFUNCTR , 3, 0x08, 0x82, 0x27, // Display Function Control
|
||||||
|
0xF2, 1, 0x00, // 3Gamma Function Disable
|
||||||
|
ILI9XXX_GAMMASET , 1, 0x01, // Gamma curve selected
|
||||||
|
ILI9XXX_GMCTRP1 , 15, 0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, // Set Gamma
|
||||||
|
0x4E, 0xF1, 0x37, 0x07, 0x10, 0x03,
|
||||||
|
0x0E, 0x09, 0x00,
|
||||||
|
ILI9XXX_GMCTRN1 , 15, 0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, // Set Gamma
|
||||||
|
0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C,
|
||||||
|
0x31, 0x36, 0x0F,
|
||||||
|
ILI9XXX_SLPOUT , 0x80, // Exit Sleep
|
||||||
|
ILI9XXX_DISPON , 0x80, // Display on
|
||||||
|
0x00 // End of list
|
||||||
|
};
|
||||||
|
|
||||||
|
static const uint8_t PROGMEM INITCMD_M5CORE[] = {
|
||||||
|
ILI9XXX_SETEXTC, 3, 0xFF,0x93,0x42, // Turn on the external command
|
||||||
|
ILI9XXX_PWCTR1 , 2, 0x12, 0x12,
|
||||||
|
ILI9XXX_PWCTR2 , 1, 0x03,
|
||||||
|
ILI9XXX_VMCTR1 , 1, 0xF2,
|
||||||
|
ILI9XXX_IFMODE , 1, 0xE0,
|
||||||
|
0xF6 , 3, 0x01, 0x00, 0x00,
|
||||||
|
ILI9XXX_GMCTRP1,15, 0x00,0x0C,0x11,0x04,0x11,0x08,0x37,0x89,0x4C,0x06,0x0C,0x0A,0x2E,0x34,0x0F,
|
||||||
|
ILI9XXX_GMCTRN1,15, 0x00,0x0B,0x11,0x05,0x13,0x09,0x33,0x67,0x48,0x07,0x0E,0x0B,0x2E,0x33,0x0F,
|
||||||
|
ILI9XXX_DFUNCTR, 4, 0x08,0x82,0x1D,0x04,
|
||||||
|
ILI9XXX_IDMOFF , 0,
|
||||||
|
ILI9XXX_DISPON , 0x80, // Display on
|
||||||
|
ILI9XXX_SLPOUT , 0x80, // Exit Sleep
|
||||||
|
|
||||||
|
0x00 // End of list
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
static const uint8_t PROGMEM INITCMD_ILI9341[] = {
|
||||||
|
0xEF, 3, 0x03, 0x80, 0x02,
|
||||||
|
0xCF, 3, 0x00, 0xC1, 0x30,
|
||||||
|
0xED, 4, 0x64, 0x03, 0x12, 0x81,
|
||||||
|
0xE8, 3, 0x85, 0x00, 0x78,
|
||||||
|
0xCB, 5, 0x39, 0x2C, 0x00, 0x34, 0x02,
|
||||||
|
0xF7, 1, 0x20,
|
||||||
|
0xEA, 2, 0x00, 0x00,
|
||||||
|
ILI9XXX_PWCTR1 , 1, 0x23, // Power control VRH[5:0]
|
||||||
|
ILI9XXX_PWCTR2 , 1, 0x10, // Power control SAP[2:0];BT[3:0]
|
||||||
|
ILI9XXX_VMCTR1 , 2, 0x3e, 0x28, // VCM control
|
||||||
|
ILI9XXX_VMCTR2 , 1, 0x86, // VCM control2
|
||||||
|
ILI9XXX_MADCTL , 1, 0x48, // Memory Access Control
|
||||||
|
ILI9XXX_VSCRSADD, 1, 0x00, // Vertical scroll zero
|
||||||
|
ILI9XXX_PIXFMT , 1, 0x55,
|
||||||
|
ILI9XXX_FRMCTR1 , 2, 0x00, 0x18,
|
||||||
|
ILI9XXX_DFUNCTR , 3, 0x08, 0x82, 0x27, // Display Function Control
|
||||||
|
0xF2, 1, 0x00, // 3Gamma Function Disable
|
||||||
|
ILI9XXX_GAMMASET , 1, 0x01, // Gamma curve selected
|
||||||
|
ILI9XXX_GMCTRP1 , 15, 0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, // Set Gamma
|
||||||
|
0x4E, 0xF1, 0x37, 0x07, 0x10, 0x03,
|
||||||
|
0x0E, 0x09, 0x00,
|
||||||
|
ILI9XXX_GMCTRN1 , 15, 0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, // Set Gamma
|
||||||
|
0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C,
|
||||||
|
0x31, 0x36, 0x0F,
|
||||||
|
ILI9XXX_SLPOUT , 0x80, // Exit Sleep
|
||||||
|
ILI9XXX_DISPON , 0x80, // Display on
|
||||||
|
0x00 // End of list
|
||||||
|
};
|
||||||
|
|
||||||
|
static const uint8_t PROGMEM INITCMD_ILI9481[] = {
|
||||||
|
ILI9XXX_SLPOUT , 0x80, // Exit sleep mode
|
||||||
|
ILI9XXX_PWSET , 3, 0x07, 0x41, 0x1D,
|
||||||
|
ILI9XXX_VMCTR , 3, 0x00, 0x1C, 0x1F,
|
||||||
|
ILI9XXX_PWSETN , 2, 0x01, 0x11,
|
||||||
|
ILI9XXX_PWCTR1 , 5, 0x10, 0x3B, 0x00, 0x02, 0x11,
|
||||||
|
ILI9XXX_VMCTR1 , 1, 0x03,
|
||||||
|
ILI9XXX_IFCTR , 1, 0x83,
|
||||||
|
ILI9XXX_GMCTR ,12, 0x00, 0x26, 0x21, 0x00, 0x00, 0x1F, 0x65, 0x23, 0x77, 0x00, 0x0F, 0x00,
|
||||||
|
ILI9XXX_IFMODE , 1, 0x00, // CommandAccessProtect
|
||||||
|
0xE4 , 1, 0xA0,
|
||||||
|
ILI9XXX_CSCON , 1, 0x01,
|
||||||
|
ILI9XXX_DISPON, 0x80, // Set display on
|
||||||
|
0x00 // end
|
||||||
|
};
|
||||||
|
|
||||||
|
static const uint8_t PROGMEM INITCMD_ILI9486[] = {
|
||||||
|
ILI9XXX_SLPOUT, 0x80,
|
||||||
|
ILI9XXX_PIXFMT, 1, 0x55,
|
||||||
|
ILI9XXX_PWCTR3, 1, 0x44,
|
||||||
|
ILI9XXX_VMCTR1, 4, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
ILI9XXX_GMCTRP1, 15, 0x0f,0x1f,0x1c,0x0c,0x0f,0x08,0x48,0x98,0x37,0x0a,0x13,0x04,0x11,0x0d,0x00,
|
||||||
|
ILI9XXX_GMCTRN1, 15, 0x0f,0x32,0x2e,0x0b,0x0d,0x05,0x47,0x75,0x37,0x06,0x10,0x03,0x24,0x20,0x00,
|
||||||
|
ILI9XXX_INVOFF, 0x80,
|
||||||
|
ILI9XXX_MADCTL, 1, 0x48,
|
||||||
|
ILI9XXX_DISPON, 0x80,
|
||||||
|
|
||||||
|
// ILI9XXX_MADCTL, 1, MADCTL_BGR | MADCTL_MV, //hardware rotation
|
||||||
|
0x00 // End of list
|
||||||
|
};
|
||||||
|
|
||||||
|
static const uint8_t PROGMEM INITCMD_ILI9488[] = {
|
||||||
|
ILI9XXX_GMCTRP1,15, 0x00, 0x03, 0x09, 0x08, 0x16, 0x0A, 0x3F, 0x78, 0x4C, 0x09, 0x0A, 0x08, 0x16, 0x1A, 0x0F,
|
||||||
|
ILI9XXX_GMCTRN1,15, 0x00, 0x16, 0x19, 0x03, 0x0F, 0x05, 0x32, 0x45, 0x46, 0x04, 0x0E, 0x0D, 0x35, 0x37, 0x0F,
|
||||||
|
|
||||||
|
ILI9XXX_PWCTR1, 2, 0x17, 0x15, // VRH1 VRH2
|
||||||
|
ILI9XXX_PWCTR2, 1, 0x41, // VGH, VGL
|
||||||
|
ILI9XXX_VMCTR1, 3, 0x00, 0x12, 0x80, // nVM VCM_REG VCM_REG_EN
|
||||||
|
|
||||||
|
ILI9XXX_IFMODE, 1, 0x00,
|
||||||
|
ILI9XXX_FRMCTR1, 1, 0xA0, // Frame rate = 60Hz
|
||||||
|
ILI9XXX_INVCTR, 1, 0x02, // Display Inversion Control = 2dot
|
||||||
|
|
||||||
|
ILI9XXX_DFUNCTR, 2, 0x02, 0x02, // Nomal scan
|
||||||
|
|
||||||
|
0xE9, 1, 0x00, // Set Image Functio. Disable 24 bit data
|
||||||
|
|
||||||
|
ILI9XXX_ADJCTL3, 4, 0xA9, 0x51, 0x2C, 0x82, // Adjust Control 3
|
||||||
|
|
||||||
|
ILI9XXX_MADCTL, 1, 0x28,
|
||||||
|
//ILI9XXX_PIXFMT, 1, 0x55, // Interface Pixel Format = 16bit
|
||||||
|
ILI9XXX_PIXFMT, 1, 0x66, //ILI9488 only supports 18-bit pixel format in 4/3 wire SPI mode
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 5 frames
|
||||||
|
//ILI9XXX_ETMOD, 1, 0xC6, //
|
||||||
|
|
||||||
|
|
||||||
|
ILI9XXX_SLPOUT, 0x80, // Exit sleep mode
|
||||||
|
//ILI9XXX_INVON , 0,
|
||||||
|
ILI9XXX_DISPON, 0x80, // Set display on
|
||||||
|
0x00 // end
|
||||||
|
};
|
||||||
|
|
||||||
|
static const uint8_t PROGMEM INITCMD_ST7796[] = {
|
||||||
|
// This ST7796S initilization routine was copied from https://github.com/prenticedavid/Adafruit_ST7796S_kbv/blob/master/Adafruit_ST7796S_kbv.cpp
|
||||||
|
ILI9XXX_SWRESET, 0x80, // Soft reset, then delay 150 ms
|
||||||
|
ILI9XXX_CSCON, 1, 0xC3, // ?? Unlock Manufacturer
|
||||||
|
ILI9XXX_CSCON, 1, 0x96,
|
||||||
|
ILI9XXX_VMCTR1, 1, 0x1C, //VCOM Control 1 [1C]
|
||||||
|
ILI9XXX_MADCTL, 1, 0x48, //Memory Access [00]
|
||||||
|
ILI9XXX_PIXFMT, 1, 0x55, //565
|
||||||
|
ILI9XXX_IFMODE, 1, 0x80, //Interface [00]
|
||||||
|
ILI9XXX_INVCTR, 1, 0x01, //Inversion Control [01]
|
||||||
|
ILI9XXX_DFUNCTR, 3, 0x80, 0x02, 0x3B, // Display Function Control [80 02 3B] .kbv SS=1, NL=480
|
||||||
|
ILI9XXX_ETMOD, 1, 0xC6, //Entry Mode [06]
|
||||||
|
|
||||||
|
ILI9XXX_CSCON, 1, 0x69, //?? lock manufacturer commands
|
||||||
|
ILI9XXX_CSCON, 1, 0x3C, //
|
||||||
|
ILI9XXX_SLPOUT, 0x80, // Exit Sleep, then delay 150 ms
|
||||||
|
ILI9XXX_DISPON, 0x80, // Main screen turn on, delay 150 ms
|
||||||
|
0x00 // End of list
|
||||||
|
};
|
||||||
|
|
||||||
|
// clang-format on
|
||||||
|
} // namespace ili9xxx
|
||||||
|
} // namespace esphome
|
|
@ -48,9 +48,10 @@ def inherit_accuracy_decimals(decimals, config):
|
||||||
return decimals + 2
|
return decimals + 2
|
||||||
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend(
|
CONFIG_SCHEMA = (
|
||||||
|
sensor.sensor_schema(IntegrationSensor)
|
||||||
|
.extend(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(IntegrationSensor),
|
|
||||||
cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor),
|
cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor),
|
||||||
cv.Required(CONF_TIME_UNIT): cv.enum(INTEGRATION_TIMES, lower=True),
|
cv.Required(CONF_TIME_UNIT): cv.enum(INTEGRATION_TIMES, lower=True),
|
||||||
cv.Optional(CONF_INTEGRATION_METHOD, default="trapezoid"): cv.enum(
|
cv.Optional(CONF_INTEGRATION_METHOD, default="trapezoid"): cv.enum(
|
||||||
|
@ -61,7 +62,9 @@ CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend(
|
||||||
"min_save_interval was removed in 2022.8.0. Please use the `preferences` -> `flash_write_interval` to adjust."
|
"min_save_interval was removed in 2022.8.0. Please use the `preferences` -> `flash_write_interval` to adjust."
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
).extend(cv.COMPONENT_SCHEMA)
|
)
|
||||||
|
.extend(cv.COMPONENT_SCHEMA)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
FINAL_VALIDATE_SCHEMA = cv.All(
|
FINAL_VALIDATE_SCHEMA = cv.All(
|
||||||
|
|
1
esphome/components/internal_temperature/__init__.py
Normal file
1
esphome/components/internal_temperature/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
CODEOWNERS = ["@Mat931"]
|
|
@ -0,0 +1,58 @@
|
||||||
|
#include "internal_temperature.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
#ifdef USE_ESP32
|
||||||
|
#if defined(USE_ESP32_VARIANT_ESP32)
|
||||||
|
// there is no official API available on the original ESP32
|
||||||
|
extern "C" {
|
||||||
|
uint8_t temprature_sens_read();
|
||||||
|
}
|
||||||
|
#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
|
||||||
|
#include "driver/temp_sensor.h"
|
||||||
|
#endif // USE_ESP32_VARIANT
|
||||||
|
#endif // USE_ESP32
|
||||||
|
#ifdef USE_RP2040
|
||||||
|
#include "Arduino.h"
|
||||||
|
#endif // USE_RP2040
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace internal_temperature {
|
||||||
|
|
||||||
|
static const char *const TAG = "internal_temperature";
|
||||||
|
|
||||||
|
void InternalTemperatureSensor::update() {
|
||||||
|
float temperature = NAN;
|
||||||
|
bool success = false;
|
||||||
|
#ifdef USE_ESP32
|
||||||
|
#if defined(USE_ESP32_VARIANT_ESP32)
|
||||||
|
uint8_t raw = temprature_sens_read();
|
||||||
|
ESP_LOGV(TAG, "Raw temperature value: %d", raw);
|
||||||
|
temperature = (raw - 32) / 1.8f;
|
||||||
|
success = (raw != 128);
|
||||||
|
#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
|
||||||
|
temp_sensor_config_t tsens = TSENS_CONFIG_DEFAULT();
|
||||||
|
temp_sensor_set_config(tsens);
|
||||||
|
temp_sensor_start();
|
||||||
|
esp_err_t result = temp_sensor_read_celsius(&temperature);
|
||||||
|
temp_sensor_stop();
|
||||||
|
success = (result == ESP_OK);
|
||||||
|
#endif // USE_ESP32_VARIANT
|
||||||
|
#endif // USE_ESP32
|
||||||
|
#ifdef USE_RP2040
|
||||||
|
temperature = analogReadTemp();
|
||||||
|
success = (temperature != 0.0f);
|
||||||
|
#endif // USE_RP2040
|
||||||
|
if (success && std::isfinite(temperature)) {
|
||||||
|
this->publish_state(temperature);
|
||||||
|
} else {
|
||||||
|
ESP_LOGD(TAG, "Ignoring invalid temperature (success=%d, value=%.1f)", success, temperature);
|
||||||
|
if (!this->has_state()) {
|
||||||
|
this->publish_state(NAN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void InternalTemperatureSensor::dump_config() { LOG_SENSOR("", "Internal Temperature Sensor", this); }
|
||||||
|
|
||||||
|
} // namespace internal_temperature
|
||||||
|
} // namespace esphome
|
|
@ -0,0 +1,17 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace internal_temperature {
|
||||||
|
|
||||||
|
class InternalTemperatureSensor : public sensor::Sensor, public PollingComponent {
|
||||||
|
public:
|
||||||
|
void dump_config() override;
|
||||||
|
|
||||||
|
void update() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace internal_temperature
|
||||||
|
} // namespace esphome
|
31
esphome/components/internal_temperature/sensor.py
Normal file
31
esphome/components/internal_temperature/sensor.py
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import sensor
|
||||||
|
from esphome.const import (
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
UNIT_CELSIUS,
|
||||||
|
DEVICE_CLASS_TEMPERATURE,
|
||||||
|
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||||
|
)
|
||||||
|
|
||||||
|
internal_temperature_ns = cg.esphome_ns.namespace("internal_temperature")
|
||||||
|
InternalTemperatureSensor = internal_temperature_ns.class_(
|
||||||
|
"InternalTemperatureSensor", sensor.Sensor, cg.PollingComponent
|
||||||
|
)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = cv.All(
|
||||||
|
sensor.sensor_schema(
|
||||||
|
InternalTemperatureSensor,
|
||||||
|
unit_of_measurement=UNIT_CELSIUS,
|
||||||
|
accuracy_decimals=1,
|
||||||
|
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||||
|
).extend(cv.polling_component_schema("60s")),
|
||||||
|
cv.only_on(["esp32", "rp2040"]),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
var = await sensor.new_sensor(config)
|
||||||
|
await cg.register_component(var, config)
|
|
@ -23,9 +23,11 @@ CONF_PROCESS_STD_DEV = "process_std_dev"
|
||||||
CONF_STD_DEV = "std_dev"
|
CONF_STD_DEV = "std_dev"
|
||||||
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend(cv.COMPONENT_SCHEMA).extend(
|
CONFIG_SCHEMA = (
|
||||||
|
sensor.sensor_schema(KalmanCombinatorComponent)
|
||||||
|
.extend(cv.COMPONENT_SCHEMA)
|
||||||
|
.extend(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(KalmanCombinatorComponent),
|
|
||||||
cv.Required(CONF_PROCESS_STD_DEV): cv.positive_float,
|
cv.Required(CONF_PROCESS_STD_DEV): cv.positive_float,
|
||||||
cv.Required(CONF_SOURCES): cv.ensure_list(
|
cv.Required(CONF_SOURCES): cv.ensure_list(
|
||||||
cv.Schema(
|
cv.Schema(
|
||||||
|
@ -35,9 +37,10 @@ CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend(cv.COMPONENT_SCHEMA).extend(
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_STD_DEV): sensor.SENSOR_SCHEMA,
|
cv.Optional(CONF_STD_DEV): sensor.sensor_schema(),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# Inherit some sensor values from the first source, for both the state and the error value
|
# Inherit some sensor values from the first source, for both the state and the error value
|
||||||
properties_to_inherit = [
|
properties_to_inherit = [
|
||||||
|
|
0
esphome/components/kuntze/__init__.py
Normal file
0
esphome/components/kuntze/__init__.py
Normal file
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue