Merge branch 'dev' into bump-2021.12.0b1

This commit is contained in:
Jesse Hills 2021-12-02 19:38:44 +13:00
commit 36b355eb82
No known key found for this signature in database
GPG key ID: BEAAE804EFD8E83A
241 changed files with 3268 additions and 1414 deletions

View file

@ -5,11 +5,8 @@ Checks: >-
-altera-*, -altera-*,
-android-*, -android-*,
-boost-*, -boost-*,
-bugprone-branch-clone,
-bugprone-easily-swappable-parameters,
-bugprone-narrowing-conversions, -bugprone-narrowing-conversions,
-bugprone-signed-char-misuse, -bugprone-signed-char-misuse,
-bugprone-too-small-loop-variable,
-cert-dcl50-cpp, -cert-dcl50-cpp,
-cert-err58-cpp, -cert-err58-cpp,
-cert-oop57-cpp, -cert-oop57-cpp,
@ -19,12 +16,10 @@ Checks: >-
-clang-diagnostic-delete-abstract-non-virtual-dtor, -clang-diagnostic-delete-abstract-non-virtual-dtor,
-clang-diagnostic-delete-non-abstract-non-virtual-dtor, -clang-diagnostic-delete-non-abstract-non-virtual-dtor,
-clang-diagnostic-shadow-field, -clang-diagnostic-shadow-field,
-clang-diagnostic-sign-compare,
-clang-diagnostic-unused-variable,
-clang-diagnostic-unused-const-variable, -clang-diagnostic-unused-const-variable,
-clang-diagnostic-unused-parameter,
-concurrency-*, -concurrency-*,
-cppcoreguidelines-avoid-c-arrays, -cppcoreguidelines-avoid-c-arrays,
-cppcoreguidelines-avoid-goto,
-cppcoreguidelines-avoid-magic-numbers, -cppcoreguidelines-avoid-magic-numbers,
-cppcoreguidelines-init-variables, -cppcoreguidelines-init-variables,
-cppcoreguidelines-macro-usage, -cppcoreguidelines-macro-usage,
@ -41,7 +36,6 @@ Checks: >-
-cppcoreguidelines-pro-type-union-access, -cppcoreguidelines-pro-type-union-access,
-cppcoreguidelines-pro-type-vararg, -cppcoreguidelines-pro-type-vararg,
-cppcoreguidelines-special-member-functions, -cppcoreguidelines-special-member-functions,
-fuchsia-default-arguments,
-fuchsia-multiple-inheritance, -fuchsia-multiple-inheritance,
-fuchsia-overloaded-operator, -fuchsia-overloaded-operator,
-fuchsia-statically-constructed-objects, -fuchsia-statically-constructed-objects,
@ -51,6 +45,7 @@ Checks: >-
-google-explicit-constructor, -google-explicit-constructor,
-google-readability-braces-around-statements, -google-readability-braces-around-statements,
-google-readability-casting, -google-readability-casting,
-google-readability-namespace-comments,
-google-readability-todo, -google-readability-todo,
-google-runtime-references, -google-runtime-references,
-hicpp-*, -hicpp-*,
@ -97,9 +92,11 @@ CheckOptions:
value: '1' value: '1'
- key: google-readability-function-size.StatementThreshold - key: google-readability-function-size.StatementThreshold
value: '800' value: '800'
- key: google-readability-namespace-comments.ShortNamespaceLines - key: google-runtime-int.TypeSuffix
value: '_t'
- key: llvm-namespace-comment.ShortNamespaceLines
value: '10' value: '10'
- key: google-readability-namespace-comments.SpacesBeforeComments - key: llvm-namespace-comment.SpacesBeforeComments
value: '2' value: '2'
- key: modernize-loop-convert.MaxCopySize - key: modernize-loop-convert.MaxCopySize
value: '16' value: '16'

View file

@ -1,5 +1,3 @@
# THESE JOBS ARE COPIED IN release.yml and release-dev.yml
# PLEASE ALSO UPDATE THOSE FILES WHEN CHANGING LINES HERE
name: CI name: CI
on: on:
@ -11,6 +9,10 @@ on:
permissions: permissions:
contents: read contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs: jobs:
ci: ci:
name: ${{ matrix.name }} name: ${{ matrix.name }}
@ -34,7 +36,7 @@ jobs:
- id: test - id: test
file: tests/test3.yaml file: tests/test3.yaml
name: Test tests/test3.yaml name: Test tests/test3.yaml
pio_cache_key: test1 pio_cache_key: test3
- id: test - id: test
file: tests/test4.yaml file: tests/test4.yaml
name: Test tests/test4.yaml name: Test tests/test4.yaml
@ -80,18 +82,23 @@ jobs:
with: with:
python-version: '3.7' python-version: '3.7'
- name: Cache pip modules - name: Cache virtualenv
uses: actions/cache@v2 uses: actions/cache@v2
with: with:
path: ~/.cache/pip path: .venv
key: pip-${{ steps.python.outputs.python-version }}-${{ hashFiles('requirements*.txt') }} key: venv-${{ steps.python.outputs.python-version }}-${{ hashFiles('requirements*.txt') }}
restore-keys: | restore-keys: |
pip-${{ steps.python.outputs.python-version }}- venv-${{ steps.python.outputs.python-version }}-
- name: Set up python environment - name: Set up virtualenv
run: | run: |
pip3 install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt python -m venv .venv
pip3 install -e . source .venv/bin/activate
pip install -U pip
pip install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt
pip install -e .
echo "$GITHUB_WORKSPACE/.venv/bin" >> $GITHUB_PATH
echo "VIRTUAL_ENV=$GITHUB_WORKSPACE/.venv" >> $GITHUB_ENV
# Use per check platformio cache because checks use different parts # Use per check platformio cache because checks use different parts
- name: Cache platformio - name: Cache platformio

View file

@ -4,7 +4,7 @@
"owner": "ci-custom", "owner": "ci-custom",
"pattern": [ "pattern": [
{ {
"regexp": "^ERROR (.*):(\\d+):(\\d+) - (.*)$", "regexp": "^(.*):(\\d+):(\\d+):\\s+lint:\\s+(.*)$",
"file": 1, "file": 1,
"line": 2, "line": 2,
"column": 3, "column": 3,

View file

@ -5,7 +5,7 @@
"severity": "error", "severity": "error",
"pattern": [ "pattern": [
{ {
"regexp": "^(.*):(\\d+):(\\d+):\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$", "regexp": "^src/(.*):(\\d+):(\\d+):\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$",
"file": 1, "file": 1,
"line": 2, "line": 2,
"column": 3, "column": 3,

View file

@ -1,11 +1,22 @@
{ {
"problemMatcher": [ "problemMatcher": [
{
"owner": "black",
"severity": "error",
"pattern": [
{
"regexp": "^(.*): (Please format this file with the black formatter)",
"file": 1,
"message": 2
}
]
},
{ {
"owner": "flake8", "owner": "flake8",
"severity": "error", "severity": "error",
"pattern": [ "pattern": [
{ {
"regexp": "^(.*):(\\d+) - ([EFCDNW]\\d{3}.*)$", "regexp": "^(.*):(\\d+): ([EFCDNW]\\d{3}.*)$",
"file": 1, "file": 1,
"line": 2, "line": 2,
"message": 3 "message": 3
@ -17,7 +28,7 @@
"severity": "error", "severity": "error",
"pattern": [ "pattern": [
{ {
"regexp": "^(.*):(\\d+) - (\\[[EFCRW]\\d{4}\\(.*\\),.*\\].*)$", "regexp": "^(.*):(\\d+): (\\[[EFCRW]\\d{4}\\(.*\\),.*\\].*)$",
"file": 1, "file": 1,
"line": 2, "line": 2,
"message": 3 "message": 3

View file

@ -30,6 +30,7 @@ esphome/components/bang_bang/* @OttoWinter
esphome/components/binary_sensor/* @esphome/core esphome/components/binary_sensor/* @esphome/core
esphome/components/ble_client/* @buxtronix esphome/components/ble_client/* @buxtronix
esphome/components/bme680_bsec/* @trvrnrth esphome/components/bme680_bsec/* @trvrnrth
esphome/components/button/* @esphome/core
esphome/components/canbus/* @danielschramm @mvturnho esphome/components/canbus/* @danielschramm @mvturnho
esphome/components/cap1188/* @MrEditor97 esphome/components/cap1188/* @MrEditor97
esphome/components/captive_portal/* @OttoWinter esphome/components/captive_portal/* @OttoWinter
@ -72,7 +73,6 @@ esphome/components/hitachi_ac424/* @sourabhjaiswal
esphome/components/homeassistant/* @OttoWinter esphome/components/homeassistant/* @OttoWinter
esphome/components/hrxl_maxsonar_wr/* @netmikey esphome/components/hrxl_maxsonar_wr/* @netmikey
esphome/components/i2c/* @esphome/core esphome/components/i2c/* @esphome/core
esphome/components/improv/* @jesserockz
esphome/components/improv_serial/* @esphome/core esphome/components/improv_serial/* @esphome/core
esphome/components/inkbird_ibsth1_mini/* @fkirill esphome/components/inkbird_ibsth1_mini/* @fkirill
esphome/components/inkplate6/* @jesserockz esphome/components/inkplate6/* @jesserockz
@ -181,6 +181,7 @@ esphome/components/tuya/binary_sensor/* @jesserockz
esphome/components/tuya/climate/* @jesserockz esphome/components/tuya/climate/* @jesserockz
esphome/components/tuya/sensor/* @jesserockz esphome/components/tuya/sensor/* @jesserockz
esphome/components/tuya/switch/* @jesserockz esphome/components/tuya/switch/* @jesserockz
esphome/components/tuya/text_sensor/* @dentra
esphome/components/uart/* @esphome/core esphome/components/uart/* @esphome/core
esphome/components/ultrasonic/* @OttoWinter esphome/components/ultrasonic/* @OttoWinter
esphome/components/version/* @esphome/core esphome/components/version/* @esphome/core

View file

@ -147,9 +147,9 @@ RUN \
/var/{cache,log}/* \ /var/{cache,log}/* \
/var/lib/apt/lists/* /var/lib/apt/lists/*
COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini / COPY requirements.txt requirements_optional.txt requirements_test.txt docker/platformio_install_deps.py platformio.ini /
RUN \ RUN \
pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \ pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt -r /requirements_test.txt \
&& /platformio_install_deps.py /platformio.ini && /platformio_install_deps.py /platformio.ini
VOLUME ["/esphome"] VOLUME ["/esphome"]

View file

@ -18,6 +18,7 @@ from esphome.const import (
CONF_PORT, CONF_PORT,
CONF_ESPHOME, CONF_ESPHOME,
CONF_PLATFORMIO_OPTIONS, CONF_PLATFORMIO_OPTIONS,
SECRETS_FILES,
) )
from esphome.core import CORE, EsphomeError, coroutine from esphome.core import CORE, EsphomeError, coroutine
from esphome.helpers import indent from esphome.helpers import indent
@ -200,8 +201,7 @@ def upload_using_esptool(config, port):
firmware_offset = "0x10000" if CORE.is_esp32 else "0x0" firmware_offset = "0x10000" if CORE.is_esp32 else "0x0"
flash_images = [ flash_images = [
platformio_api.FlashImage( platformio_api.FlashImage(
path=idedata.firmware_bin_path, path=idedata.firmware_bin_path, offset=firmware_offset
offset=firmware_offset,
), ),
*idedata.extra_flash_images, *idedata.extra_flash_images,
] ]
@ -607,10 +607,7 @@ def parse_args(argv):
"wizard", "wizard",
help="A helpful setup wizard that will guide you through setting up ESPHome.", help="A helpful setup wizard that will guide you through setting up ESPHome.",
) )
parser_wizard.add_argument( parser_wizard.add_argument("configuration", help="Your YAML configuration file.")
"configuration",
help="Your YAML configuration file.",
)
parser_fingerprint = subparsers.add_parser( parser_fingerprint = subparsers.add_parser(
"mqtt-fingerprint", help="Get the SSL fingerprint from a MQTT broker." "mqtt-fingerprint", help="Get the SSL fingerprint from a MQTT broker."
@ -632,8 +629,7 @@ def parse_args(argv):
"dashboard", help="Create a simple web server for a dashboard." "dashboard", help="Create a simple web server for a dashboard."
) )
parser_dashboard.add_argument( parser_dashboard.add_argument(
"configuration", "configuration", help="Your YAML configuration file directory."
help="Your YAML configuration file directory.",
) )
parser_dashboard.add_argument( parser_dashboard.add_argument(
"--port", "--port",
@ -641,6 +637,12 @@ def parse_args(argv):
type=int, type=int,
default=6052, default=6052,
) )
parser_dashboard.add_argument(
"--address",
help="The address to bind to.",
type=str,
default="0.0.0.0",
)
parser_dashboard.add_argument( parser_dashboard.add_argument(
"--username", "--username",
help="The optional username to require for authentication.", help="The optional username to require for authentication.",
@ -789,6 +791,10 @@ def run_esphome(argv):
return 1 return 1
for conf_path in args.configuration: for conf_path in args.configuration:
if any(os.path.basename(conf_path) == x for x in SECRETS_FILES):
_LOGGER.warning("Skipping secrets file %s", conf_path)
continue
CORE.config_path = conf_path CORE.config_path = conf_path
CORE.dashboard = args.dashboard CORE.dashboard = args.dashboard

View file

@ -25,7 +25,7 @@ void AdalightLightEffect::stop() {
AddressableLightEffect::stop(); AddressableLightEffect::stop();
} }
int AdalightLightEffect::get_frame_size_(int led_count) const { unsigned int AdalightLightEffect::get_frame_size_(int led_count) const {
// 3 bytes: Ada // 3 bytes: Ada
// 2 bytes: LED count // 2 bytes: LED count
// 1 byte: checksum // 1 byte: checksum

View file

@ -25,7 +25,7 @@ class AdalightLightEffect : public light::AddressableLightEffect, public uart::U
CONSUMED, CONSUMED,
}; };
int get_frame_size_(int led_count) const; unsigned int get_frame_size_(int led_count) const;
void reset_frame_(light::AddressableLight &it); void reset_frame_(light::AddressableLight &it);
void blank_all_leds_(light::AddressableLight &it); void blank_all_leds_(light::AddressableLight &it);
Frame parse_frame_(light::AddressableLight &it); Frame parse_frame_(light::AddressableLight &it);

View file

@ -73,13 +73,6 @@ void AHT10Component::update() {
bool success = false; bool success = false;
for (int i = 0; i < AHT10_ATTEMPTS; ++i) { for (int i = 0; i < AHT10_ATTEMPTS; ++i) {
ESP_LOGVV(TAG, "Attempt %d at %6u", i, millis()); ESP_LOGVV(TAG, "Attempt %d at %6u", i, millis());
delayMicroseconds(4);
uint8_t reg = 0;
if (this->write(&reg, 1) != i2c::ERROR_OK) {
ESP_LOGD(TAG, "Communication with AHT10 failed, waiting...");
continue;
}
delay(delay_ms); delay(delay_ms);
if (this->read(data, 6) != i2c::ERROR_OK) { if (this->read(data, 6) != i2c::ERROR_OK) {
ESP_LOGD(TAG, "Communication with AHT10 failed, waiting..."); ESP_LOGD(TAG, "Communication with AHT10 failed, waiting...");
@ -117,12 +110,12 @@ void AHT10Component::update() {
uint32_t raw_temperature = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5]; uint32_t raw_temperature = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5];
uint32_t raw_humidity = ((data[1] << 16) | (data[2] << 8) | data[3]) >> 4; uint32_t raw_humidity = ((data[1] << 16) | (data[2] << 8) | data[3]) >> 4;
float temperature = ((200.0 * (float) raw_temperature) / 1048576.0) - 50.0; float temperature = ((200.0f * (float) raw_temperature) / 1048576.0f) - 50.0f;
float humidity; float humidity;
if (raw_humidity == 0) { // unrealistic value if (raw_humidity == 0) { // unrealistic value
humidity = NAN; humidity = NAN;
} else { } else {
humidity = (float) raw_humidity * 100.0 / 1048576.0; humidity = (float) raw_humidity * 100.0f / 1048576.0f;
} }
if (this->temperature_sensor_ != nullptr) { if (this->temperature_sensor_ != nullptr) {

View file

@ -38,9 +38,9 @@ void AM2320Component::update() {
return; return;
} }
float temperature = (((data[4] & 0x7F) << 8) + data[5]) / 10.0; float temperature = (((data[4] & 0x7F) << 8) + data[5]) / 10.0f;
temperature = (data[4] & 0x80) ? -temperature : temperature; temperature = (data[4] & 0x80) ? -temperature : temperature;
float humidity = ((data[2] << 8) + data[3]) / 10.0; float humidity = ((data[2] << 8) + data[3]) / 10.0f;
ESP_LOGD(TAG, "Got temperature=%.1f°C humidity=%.1f%%", temperature, humidity); ESP_LOGD(TAG, "Got temperature=%.1f°C humidity=%.1f%%", temperature, humidity);
if (this->temperature_sensor_ != nullptr) if (this->temperature_sensor_ != nullptr)

View file

@ -44,8 +44,9 @@ async def to_code(config):
width, height = image.size width, height = image.size
frames = image.n_frames frames = image.n_frames
if CONF_RESIZE in config: if CONF_RESIZE in config:
image.thumbnail(config[CONF_RESIZE]) new_width_max, new_height_max = config[CONF_RESIZE]
width, height = image.size ratio = min(new_width_max / width, new_height_max / height)
width, height = int(width * ratio), int(height * ratio)
else: else:
if width > 500 or height > 500: if width > 500 or height > 500:
_LOGGER.warning( _LOGGER.warning(
@ -59,10 +60,12 @@ async def to_code(config):
for frameIndex in range(frames): for frameIndex in range(frames):
image.seek(frameIndex) image.seek(frameIndex)
frame = image.convert("L", dither=Image.NONE) frame = image.convert("L", dither=Image.NONE)
if CONF_RESIZE in config:
frame = frame.resize([width, height])
pixels = list(frame.getdata()) pixels = list(frame.getdata())
if len(pixels) != height * width: if len(pixels) != height * width:
raise core.EsphomeError( raise core.EsphomeError(
f"Unexpected number of pixels in frame {frameIndex}: {len(pixels)} != {height*width}" f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height*width})"
) )
for pix in pixels: for pix in pixels:
data[pos] = pix data[pos] = pix
@ -76,10 +79,12 @@ async def to_code(config):
if CONF_RESIZE in config: if CONF_RESIZE in config:
image.thumbnail(config[CONF_RESIZE]) image.thumbnail(config[CONF_RESIZE])
frame = image.convert("RGB") frame = image.convert("RGB")
if CONF_RESIZE in config:
frame = frame.resize([width, height])
pixels = list(frame.getdata()) pixels = list(frame.getdata())
if len(pixels) != height * width: if len(pixels) != height * width:
raise core.EsphomeError( raise core.EsphomeError(
f"Unexpected number of pixels in frame {frameIndex}: {len(pixels)} != {height*width}" f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height*width})"
) )
for pix in pixels: for pix in pixels:
data[pos] = pix[0] data[pos] = pix[0]
@ -95,6 +100,8 @@ async def to_code(config):
for frameIndex in range(frames): for frameIndex in range(frames):
image.seek(frameIndex) image.seek(frameIndex)
frame = image.convert("1", dither=Image.NONE) frame = image.convert("1", dither=Image.NONE)
if CONF_RESIZE in config:
frame = frame.resize([width, height])
for y in range(height): for y in range(height):
for x in range(width): for x in range(width):
if frame.getpixel((x, y)): if frame.getpixel((x, y)):

View file

@ -30,7 +30,7 @@ class Anova : public climate::Climate, public esphome::ble_client::BLEClientNode
climate::ClimateTraits traits() override { climate::ClimateTraits traits() override {
auto traits = climate::ClimateTraits(); auto traits = climate::ClimateTraits();
traits.set_supports_current_temperature(true); traits.set_supports_current_temperature(true);
traits.set_supports_heat_mode(true); traits.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::ClimateMode::CLIMATE_MODE_HEAT});
traits.set_visual_min_temperature(25.0); traits.set_visual_min_temperature(25.0);
traits.set_visual_max_temperature(100.0); traits.set_visual_max_temperature(100.0);
traits.set_visual_temperature_step(0.1); traits.set_visual_temperature_step(0.1);

View file

@ -103,13 +103,7 @@ void AnovaCodec::decode(const uint8_t *data, uint16_t length) {
} }
break; break;
} }
case READ_TARGET_TEMPERATURE: { case READ_TARGET_TEMPERATURE:
this->target_temp_ = parse_number<float>(str_until(buf, '\r')).value_or(0.0f);
if (this->fahrenheit_)
this->target_temp_ = ftoc(this->target_temp_);
this->has_target_temp_ = true;
break;
}
case SET_TARGET_TEMPERATURE: { case SET_TARGET_TEMPERATURE: {
this->target_temp_ = parse_number<float>(str_until(buf, '\r')).value_or(0.0f); this->target_temp_ = parse_number<float>(str_until(buf, '\r')).value_or(0.0f);
if (this->fahrenheit_) if (this->fahrenheit_)

View file

@ -40,6 +40,7 @@ service APIConnection {
rpc climate_command (ClimateCommandRequest) returns (void) {} rpc climate_command (ClimateCommandRequest) returns (void) {}
rpc number_command (NumberCommandRequest) returns (void) {} rpc number_command (NumberCommandRequest) returns (void) {}
rpc select_command (SelectCommandRequest) returns (void) {} rpc select_command (SelectCommandRequest) returns (void) {}
rpc button_command (ButtonCommandRequest) returns (void) {}
} }
@ -868,6 +869,11 @@ message ClimateCommandRequest {
} }
// ==================== NUMBER ==================== // ==================== NUMBER ====================
enum NumberMode {
NUMBER_MODE_AUTO = 0;
NUMBER_MODE_BOX = 1;
NUMBER_MODE_SLIDER = 2;
}
message ListEntitiesNumberResponse { message ListEntitiesNumberResponse {
option (id) = 49; option (id) = 49;
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
@ -884,6 +890,8 @@ message ListEntitiesNumberResponse {
float step = 8; float step = 8;
bool disabled_by_default = 9; bool disabled_by_default = 9;
EntityCategory entity_category = 10; EntityCategory entity_category = 10;
string unit_of_measurement = 11;
NumberMode mode = 12;
} }
message NumberStateResponse { message NumberStateResponse {
option (id) = 50; option (id) = 50;
@ -944,3 +952,28 @@ message SelectCommandRequest {
fixed32 key = 1; fixed32 key = 1;
string state = 2; string state = 2;
} }
// ==================== BUTTON ====================
message ListEntitiesButtonResponse {
option (id) = 61;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_BUTTON";
string object_id = 1;
fixed32 key = 2;
string name = 3;
string unique_id = 4;
string icon = 5;
bool disabled_by_default = 6;
EntityCategory entity_category = 7;
string device_class = 8;
}
message ButtonCommandRequest {
option (id) = 62;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_BUTTON";
option (no_delay) = true;
fixed32 key = 1;
}

View file

@ -132,7 +132,7 @@ void APIConnection::loop() {
if (state_subs_at_ != -1) { if (state_subs_at_ != -1) {
const auto &subs = this->parent_->get_state_subs(); const auto &subs = this->parent_->get_state_subs();
if (state_subs_at_ >= subs.size()) { if (state_subs_at_ >= (int) subs.size()) {
state_subs_at_ = -1; state_subs_at_ = -1;
} else { } else {
auto &it = subs[state_subs_at_]; auto &it = subs[state_subs_at_];
@ -619,6 +619,8 @@ bool APIConnection::send_number_info(number::Number *number) {
msg.icon = number->get_icon(); msg.icon = number->get_icon();
msg.disabled_by_default = number->is_disabled_by_default(); msg.disabled_by_default = number->is_disabled_by_default();
msg.entity_category = static_cast<enums::EntityCategory>(number->get_entity_category()); msg.entity_category = static_cast<enums::EntityCategory>(number->get_entity_category());
msg.unit_of_measurement = number->traits.get_unit_of_measurement();
msg.mode = static_cast<enums::NumberMode>(number->traits.get_mode());
msg.min_value = number->traits.get_min_value(); msg.min_value = number->traits.get_min_value();
msg.max_value = number->traits.get_max_value(); msg.max_value = number->traits.get_max_value();
@ -674,6 +676,28 @@ void APIConnection::select_command(const SelectCommandRequest &msg) {
} }
#endif #endif
#ifdef USE_BUTTON
bool APIConnection::send_button_info(button::Button *button) {
ListEntitiesButtonResponse msg;
msg.key = button->get_object_id_hash();
msg.object_id = button->get_object_id();
msg.name = button->get_name();
msg.unique_id = get_default_unique_id("button", button);
msg.icon = button->get_icon();
msg.disabled_by_default = button->is_disabled_by_default();
msg.entity_category = static_cast<enums::EntityCategory>(button->get_entity_category());
msg.device_class = button->get_device_class();
return this->send_list_entities_button_response(msg);
}
void APIConnection::button_command(const ButtonCommandRequest &msg) {
button::Button *button = App.get_button_by_key(msg.key);
if (button == nullptr)
return;
button->press();
}
#endif
#ifdef USE_ESP32_CAMERA #ifdef USE_ESP32_CAMERA
void APIConnection::send_camera_state(std::shared_ptr<esp32_camera::CameraImage> image) { void APIConnection::send_camera_state(std::shared_ptr<esp32_camera::CameraImage> image) {
if (!this->state_subscription_) if (!this->state_subscription_)

View file

@ -73,6 +73,10 @@ class APIConnection : public APIServerConnection {
bool send_select_state(select::Select *select, std::string state); bool send_select_state(select::Select *select, std::string state);
bool send_select_info(select::Select *select); bool send_select_info(select::Select *select);
void select_command(const SelectCommandRequest &msg) override; void select_command(const SelectCommandRequest &msg) override;
#endif
#ifdef USE_BUTTON
bool send_button_info(button::Button *button);
void button_command(const ButtonCommandRequest &msg) override;
#endif #endif
bool send_log_message(int level, const char *tag, const char *line); bool send_log_message(int level, const char *tag, const char *line);
void send_homeassistant_service_call(const HomeassistantServiceResponse &call) { void send_homeassistant_service_call(const HomeassistantServiceResponse &call) {

View file

@ -174,9 +174,6 @@ APIError APINoiseFrameHelper::loop() {
* errno API_ERROR_HANDSHAKE_PACKET_LEN: Packet too big for this phase. * errno API_ERROR_HANDSHAKE_PACKET_LEN: Packet too big for this phase.
*/ */
APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) { APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
int err;
APIError aerr;
if (frame == nullptr) { if (frame == nullptr) {
HELPER_LOG("Bad argument for try_read_frame_"); HELPER_LOG("Bad argument for try_read_frame_");
return APIError::BAD_ARG; return APIError::BAD_ARG;
@ -200,7 +197,7 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
return APIError::CONNECTION_CLOSED; return APIError::CONNECTION_CLOSED;
} }
rx_header_buf_len_ += received; rx_header_buf_len_ += received;
if (received != to_read) { if ((size_t) received != to_read) {
// not a full read // not a full read
return APIError::WOULD_BLOCK; return APIError::WOULD_BLOCK;
} }
@ -247,7 +244,7 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
return APIError::CONNECTION_CLOSED; return APIError::CONNECTION_CLOSED;
} }
rx_buf_len_ += received; rx_buf_len_ += received;
if (received != to_read) { if ((size_t) received != to_read) {
// not all read // not all read
return APIError::WOULD_BLOCK; return APIError::WOULD_BLOCK;
} }
@ -544,7 +541,6 @@ APIError APINoiseFrameHelper::try_send_tx_buf_() {
APIError APINoiseFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) { APIError APINoiseFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) {
if (iovcnt == 0) if (iovcnt == 0)
return APIError::OK; return APIError::OK;
int err;
APIError aerr; APIError aerr;
size_t total_write_len = 0; size_t total_write_len = 0;
@ -584,7 +580,7 @@ APIError APINoiseFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) {
state_ = State::FAILED; state_ = State::FAILED;
HELPER_LOG("Socket write failed with errno %d", errno); HELPER_LOG("Socket write failed with errno %d", errno);
return APIError::SOCKET_WRITE_FAILED; return APIError::SOCKET_WRITE_FAILED;
} else if (sent != total_write_len) { } else if ((size_t) sent != total_write_len) {
// partially sent, add end to tx_buf // partially sent, add end to tx_buf
size_t to_consume = sent; size_t to_consume = sent;
for (int i = 0; i < iovcnt; i++) { for (int i = 0; i < iovcnt; i++) {
@ -778,9 +774,6 @@ APIError APIPlaintextFrameHelper::loop() {
* error API_ERROR_BAD_INDICATOR: Bad indicator byte at start of frame. * error API_ERROR_BAD_INDICATOR: Bad indicator byte at start of frame.
*/ */
APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) { APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
int err;
APIError aerr;
if (frame == nullptr) { if (frame == nullptr) {
HELPER_LOG("Bad argument for try_read_frame_"); HELPER_LOG("Bad argument for try_read_frame_");
return APIError::BAD_ARG; return APIError::BAD_ARG;
@ -854,7 +847,7 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
return APIError::CONNECTION_CLOSED; return APIError::CONNECTION_CLOSED;
} }
rx_buf_len_ += received; rx_buf_len_ += received;
if (received != to_read) { if ((size_t) received != to_read) {
// not all read // not all read
return APIError::WOULD_BLOCK; return APIError::WOULD_BLOCK;
} }
@ -874,7 +867,6 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
} }
APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) { APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
int err;
APIError aerr; APIError aerr;
if (state_ != State::DATA) { if (state_ != State::DATA) {
@ -894,9 +886,6 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
} }
bool APIPlaintextFrameHelper::can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); } bool APIPlaintextFrameHelper::can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); }
APIError APIPlaintextFrameHelper::write_packet(uint16_t type, const uint8_t *payload, size_t payload_len) { APIError APIPlaintextFrameHelper::write_packet(uint16_t type, const uint8_t *payload, size_t payload_len) {
int err;
APIError aerr;
if (state_ != State::DATA) { if (state_ != State::DATA) {
return APIError::BAD_STATE; return APIError::BAD_STATE;
} }
@ -940,7 +929,6 @@ APIError APIPlaintextFrameHelper::try_send_tx_buf_() {
APIError APIPlaintextFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) { APIError APIPlaintextFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) {
if (iovcnt == 0) if (iovcnt == 0)
return APIError::OK; return APIError::OK;
int err;
APIError aerr; APIError aerr;
size_t total_write_len = 0; size_t total_write_len = 0;
@ -980,7 +968,7 @@ APIError APIPlaintextFrameHelper::write_raw_(const struct iovec *iov, int iovcnt
state_ = State::FAILED; state_ = State::FAILED;
HELPER_LOG("Socket write failed with errno %d", errno); HELPER_LOG("Socket write failed with errno %d", errno);
return APIError::SOCKET_WRITE_FAILED; return APIError::SOCKET_WRITE_FAILED;
} else if (sent != total_write_len) { } else if ((size_t) sent != total_write_len) {
// partially sent, add end to tx_buf // partially sent, add end to tx_buf
size_t to_consume = sent; size_t to_consume = sent;
for (int i = 0; i < iovcnt; i++) { for (int i = 0; i < iovcnt; i++) {

View file

@ -266,6 +266,18 @@ template<> const char *proto_enum_to_string<enums::ClimatePreset>(enums::Climate
return "UNKNOWN"; return "UNKNOWN";
} }
} }
template<> const char *proto_enum_to_string<enums::NumberMode>(enums::NumberMode value) {
switch (value) {
case enums::NUMBER_MODE_AUTO:
return "NUMBER_MODE_AUTO";
case enums::NUMBER_MODE_BOX:
return "NUMBER_MODE_BOX";
case enums::NUMBER_MODE_SLIDER:
return "NUMBER_MODE_SLIDER";
default:
return "UNKNOWN";
}
}
bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) { switch (field_id) {
case 1: { case 1: {
@ -279,7 +291,7 @@ bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value)
void HelloRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->client_info); } void HelloRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->client_info); }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void HelloRequest::dump_to(std::string &out) const { void HelloRequest::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("HelloRequest {\n"); out.append("HelloRequest {\n");
out.append(" client_info: "); out.append(" client_info: ");
out.append("'").append(this->client_info).append("'"); out.append("'").append(this->client_info).append("'");
@ -318,7 +330,7 @@ void HelloResponse::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void HelloResponse::dump_to(std::string &out) const { void HelloResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("HelloResponse {\n"); out.append("HelloResponse {\n");
out.append(" api_version_major: "); out.append(" api_version_major: ");
sprintf(buffer, "%u", this->api_version_major); sprintf(buffer, "%u", this->api_version_major);
@ -349,7 +361,7 @@ bool ConnectRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value
void ConnectRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->password); } void ConnectRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->password); }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ConnectRequest::dump_to(std::string &out) const { void ConnectRequest::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("ConnectRequest {\n"); out.append("ConnectRequest {\n");
out.append(" password: "); out.append(" password: ");
out.append("'").append(this->password).append("'"); out.append("'").append(this->password).append("'");
@ -370,7 +382,7 @@ bool ConnectResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
void ConnectResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->invalid_password); } void ConnectResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->invalid_password); }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ConnectResponse::dump_to(std::string &out) const { void ConnectResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("ConnectResponse {\n"); out.append("ConnectResponse {\n");
out.append(" invalid_password: "); out.append(" invalid_password: ");
out.append(YESNO(this->invalid_password)); out.append(YESNO(this->invalid_password));
@ -464,7 +476,7 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void DeviceInfoResponse::dump_to(std::string &out) const { void DeviceInfoResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("DeviceInfoResponse {\n"); out.append("DeviceInfoResponse {\n");
out.append(" uses_password: "); out.append(" uses_password: ");
out.append(YESNO(this->uses_password)); out.append(YESNO(this->uses_password));
@ -588,7 +600,7 @@ void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const { void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("ListEntitiesBinarySensorResponse {\n"); out.append("ListEntitiesBinarySensorResponse {\n");
out.append(" object_id: "); out.append(" object_id: ");
out.append("'").append(this->object_id).append("'"); out.append("'").append(this->object_id).append("'");
@ -660,7 +672,7 @@ void BinarySensorStateResponse::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void BinarySensorStateResponse::dump_to(std::string &out) const { void BinarySensorStateResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("BinarySensorStateResponse {\n"); out.append("BinarySensorStateResponse {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%u", this->key);
@ -754,7 +766,7 @@ void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesCoverResponse::dump_to(std::string &out) const { void ListEntitiesCoverResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("ListEntitiesCoverResponse {\n"); out.append("ListEntitiesCoverResponse {\n");
out.append(" object_id: "); out.append(" object_id: ");
out.append("'").append(this->object_id).append("'"); out.append("'").append(this->object_id).append("'");
@ -844,7 +856,7 @@ void CoverStateResponse::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void CoverStateResponse::dump_to(std::string &out) const { void CoverStateResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("CoverStateResponse {\n"); out.append("CoverStateResponse {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%u", this->key);
@ -927,7 +939,7 @@ void CoverCommandRequest::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void CoverCommandRequest::dump_to(std::string &out) const { void CoverCommandRequest::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("CoverCommandRequest {\n"); out.append("CoverCommandRequest {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%u", this->key);
@ -1043,7 +1055,7 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesFanResponse::dump_to(std::string &out) const { void ListEntitiesFanResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("ListEntitiesFanResponse {\n"); out.append("ListEntitiesFanResponse {\n");
out.append(" object_id: "); out.append(" object_id: ");
out.append("'").append(this->object_id).append("'"); out.append("'").append(this->object_id).append("'");
@ -1139,7 +1151,7 @@ void FanStateResponse::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void FanStateResponse::dump_to(std::string &out) const { void FanStateResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("FanStateResponse {\n"); out.append("FanStateResponse {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%u", this->key);
@ -1240,7 +1252,7 @@ void FanCommandRequest::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void FanCommandRequest::dump_to(std::string &out) const { void FanCommandRequest::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("FanCommandRequest {\n"); out.append("FanCommandRequest {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%u", this->key);
@ -1391,7 +1403,7 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesLightResponse::dump_to(std::string &out) const { void ListEntitiesLightResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("ListEntitiesLightResponse {\n"); out.append("ListEntitiesLightResponse {\n");
out.append(" object_id: "); out.append(" object_id: ");
out.append("'").append(this->object_id).append("'"); out.append("'").append(this->object_id).append("'");
@ -1549,7 +1561,7 @@ void LightStateResponse::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void LightStateResponse::dump_to(std::string &out) const { void LightStateResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("LightStateResponse {\n"); out.append("LightStateResponse {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%u", this->key);
@ -1772,7 +1784,7 @@ void LightCommandRequest::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void LightCommandRequest::dump_to(std::string &out) const { void LightCommandRequest::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("LightCommandRequest {\n"); out.append("LightCommandRequest {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%u", this->key);
@ -1983,7 +1995,7 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesSensorResponse::dump_to(std::string &out) const { void ListEntitiesSensorResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("ListEntitiesSensorResponse {\n"); out.append("ListEntitiesSensorResponse {\n");
out.append(" object_id: "); out.append(" object_id: ");
out.append("'").append(this->object_id).append("'"); out.append("'").append(this->object_id).append("'");
@ -2072,7 +2084,7 @@ void SensorStateResponse::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void SensorStateResponse::dump_to(std::string &out) const { void SensorStateResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("SensorStateResponse {\n"); out.append("SensorStateResponse {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%u", this->key);
@ -2152,7 +2164,7 @@ void ListEntitiesSwitchResponse::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesSwitchResponse::dump_to(std::string &out) const { void ListEntitiesSwitchResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("ListEntitiesSwitchResponse {\n"); out.append("ListEntitiesSwitchResponse {\n");
out.append(" object_id: "); out.append(" object_id: ");
out.append("'").append(this->object_id).append("'"); out.append("'").append(this->object_id).append("'");
@ -2215,7 +2227,7 @@ void SwitchStateResponse::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void SwitchStateResponse::dump_to(std::string &out) const { void SwitchStateResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("SwitchStateResponse {\n"); out.append("SwitchStateResponse {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%u", this->key);
@ -2254,7 +2266,7 @@ void SwitchCommandRequest::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void SwitchCommandRequest::dump_to(std::string &out) const { void SwitchCommandRequest::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("SwitchCommandRequest {\n"); out.append("SwitchCommandRequest {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%u", this->key);
@ -2324,7 +2336,7 @@ void ListEntitiesTextSensorResponse::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesTextSensorResponse::dump_to(std::string &out) const { void ListEntitiesTextSensorResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("ListEntitiesTextSensorResponse {\n"); out.append("ListEntitiesTextSensorResponse {\n");
out.append(" object_id: "); out.append(" object_id: ");
out.append("'").append(this->object_id).append("'"); out.append("'").append(this->object_id).append("'");
@ -2394,7 +2406,7 @@ void TextSensorStateResponse::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void TextSensorStateResponse::dump_to(std::string &out) const { void TextSensorStateResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("TextSensorStateResponse {\n"); out.append("TextSensorStateResponse {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%u", this->key);
@ -2431,7 +2443,7 @@ void SubscribeLogsRequest::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void SubscribeLogsRequest::dump_to(std::string &out) const { void SubscribeLogsRequest::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("SubscribeLogsRequest {\n"); out.append("SubscribeLogsRequest {\n");
out.append(" level: "); out.append(" level: ");
out.append(proto_enum_to_string<enums::LogLevel>(this->level)); out.append(proto_enum_to_string<enums::LogLevel>(this->level));
@ -2474,7 +2486,7 @@ void SubscribeLogsResponse::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void SubscribeLogsResponse::dump_to(std::string &out) const { void SubscribeLogsResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("SubscribeLogsResponse {\n"); out.append("SubscribeLogsResponse {\n");
out.append(" level: "); out.append(" level: ");
out.append(proto_enum_to_string<enums::LogLevel>(this->level)); out.append(proto_enum_to_string<enums::LogLevel>(this->level));
@ -2516,7 +2528,7 @@ void HomeassistantServiceMap::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void HomeassistantServiceMap::dump_to(std::string &out) const { void HomeassistantServiceMap::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("HomeassistantServiceMap {\n"); out.append("HomeassistantServiceMap {\n");
out.append(" key: "); out.append(" key: ");
out.append("'").append(this->key).append("'"); out.append("'").append(this->key).append("'");
@ -2575,7 +2587,7 @@ void HomeassistantServiceResponse::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void HomeassistantServiceResponse::dump_to(std::string &out) const { void HomeassistantServiceResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("HomeassistantServiceResponse {\n"); out.append("HomeassistantServiceResponse {\n");
out.append(" service: "); out.append(" service: ");
out.append("'").append(this->service).append("'"); out.append("'").append(this->service).append("'");
@ -2631,7 +2643,7 @@ void SubscribeHomeAssistantStateResponse::encode(ProtoWriteBuffer buffer) const
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void SubscribeHomeAssistantStateResponse::dump_to(std::string &out) const { void SubscribeHomeAssistantStateResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("SubscribeHomeAssistantStateResponse {\n"); out.append("SubscribeHomeAssistantStateResponse {\n");
out.append(" entity_id: "); out.append(" entity_id: ");
out.append("'").append(this->entity_id).append("'"); out.append("'").append(this->entity_id).append("'");
@ -2668,7 +2680,7 @@ void HomeAssistantStateResponse::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void HomeAssistantStateResponse::dump_to(std::string &out) const { void HomeAssistantStateResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("HomeAssistantStateResponse {\n"); out.append("HomeAssistantStateResponse {\n");
out.append(" entity_id: "); out.append(" entity_id: ");
out.append("'").append(this->entity_id).append("'"); out.append("'").append(this->entity_id).append("'");
@ -2701,7 +2713,7 @@ bool GetTimeResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
void GetTimeResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->epoch_seconds); } void GetTimeResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->epoch_seconds); }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void GetTimeResponse::dump_to(std::string &out) const { void GetTimeResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("GetTimeResponse {\n"); out.append("GetTimeResponse {\n");
out.append(" epoch_seconds: "); out.append(" epoch_seconds: ");
sprintf(buffer, "%u", this->epoch_seconds); sprintf(buffer, "%u", this->epoch_seconds);
@ -2736,7 +2748,7 @@ void ListEntitiesServicesArgument::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesServicesArgument::dump_to(std::string &out) const { void ListEntitiesServicesArgument::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("ListEntitiesServicesArgument {\n"); out.append("ListEntitiesServicesArgument {\n");
out.append(" name: "); out.append(" name: ");
out.append("'").append(this->name).append("'"); out.append("'").append(this->name).append("'");
@ -2781,7 +2793,7 @@ void ListEntitiesServicesResponse::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesServicesResponse::dump_to(std::string &out) const { void ListEntitiesServicesResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("ListEntitiesServicesResponse {\n"); out.append("ListEntitiesServicesResponse {\n");
out.append(" name: "); out.append(" name: ");
out.append("'").append(this->name).append("'"); out.append("'").append(this->name).append("'");
@ -2875,7 +2887,7 @@ void ExecuteServiceArgument::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ExecuteServiceArgument::dump_to(std::string &out) const { void ExecuteServiceArgument::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("ExecuteServiceArgument {\n"); out.append("ExecuteServiceArgument {\n");
out.append(" bool_: "); out.append(" bool_: ");
out.append(YESNO(this->bool_)); out.append(YESNO(this->bool_));
@ -2956,7 +2968,7 @@ void ExecuteServiceRequest::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ExecuteServiceRequest::dump_to(std::string &out) const { void ExecuteServiceRequest::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("ExecuteServiceRequest {\n"); out.append("ExecuteServiceRequest {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%u", this->key);
@ -3028,7 +3040,7 @@ void ListEntitiesCameraResponse::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesCameraResponse::dump_to(std::string &out) const { void ListEntitiesCameraResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("ListEntitiesCameraResponse {\n"); out.append("ListEntitiesCameraResponse {\n");
out.append(" object_id: "); out.append(" object_id: ");
out.append("'").append(this->object_id).append("'"); out.append("'").append(this->object_id).append("'");
@ -3098,7 +3110,7 @@ void CameraImageResponse::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void CameraImageResponse::dump_to(std::string &out) const { void CameraImageResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("CameraImageResponse {\n"); out.append("CameraImageResponse {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%u", this->key);
@ -3135,7 +3147,7 @@ void CameraImageRequest::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void CameraImageRequest::dump_to(std::string &out) const { void CameraImageRequest::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("CameraImageRequest {\n"); out.append("CameraImageRequest {\n");
out.append(" single: "); out.append(" single: ");
out.append(YESNO(this->single)); out.append(YESNO(this->single));
@ -3281,7 +3293,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const {
} }
#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 {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("ListEntitiesClimateResponse {\n"); out.append("ListEntitiesClimateResponse {\n");
out.append(" object_id: "); out.append(" object_id: ");
out.append("'").append(this->object_id).append("'"); out.append("'").append(this->object_id).append("'");
@ -3468,7 +3480,7 @@ void ClimateStateResponse::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ClimateStateResponse::dump_to(std::string &out) const { void ClimateStateResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("ClimateStateResponse {\n"); out.append("ClimateStateResponse {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%u", this->key);
@ -3656,7 +3668,7 @@ void ClimateCommandRequest::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ClimateCommandRequest::dump_to(std::string &out) const { void ClimateCommandRequest::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("ClimateCommandRequest {\n"); out.append("ClimateCommandRequest {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%u", this->key);
@ -3758,6 +3770,10 @@ bool ListEntitiesNumberResponse::decode_varint(uint32_t field_id, ProtoVarInt va
this->entity_category = value.as_enum<enums::EntityCategory>(); this->entity_category = value.as_enum<enums::EntityCategory>();
return true; return true;
} }
case 12: {
this->mode = value.as_enum<enums::NumberMode>();
return true;
}
default: default:
return false; return false;
} }
@ -3780,6 +3796,10 @@ bool ListEntitiesNumberResponse::decode_length(uint32_t field_id, ProtoLengthDel
this->icon = value.as_string(); this->icon = value.as_string();
return true; return true;
} }
case 11: {
this->unit_of_measurement = value.as_string();
return true;
}
default: default:
return false; return false;
} }
@ -3817,10 +3837,12 @@ void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_float(8, this->step); buffer.encode_float(8, this->step);
buffer.encode_bool(9, this->disabled_by_default); buffer.encode_bool(9, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(10, this->entity_category); buffer.encode_enum<enums::EntityCategory>(10, this->entity_category);
buffer.encode_string(11, this->unit_of_measurement);
buffer.encode_enum<enums::NumberMode>(12, this->mode);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesNumberResponse::dump_to(std::string &out) const { void ListEntitiesNumberResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("ListEntitiesNumberResponse {\n"); out.append("ListEntitiesNumberResponse {\n");
out.append(" object_id: "); out.append(" object_id: ");
out.append("'").append(this->object_id).append("'"); out.append("'").append(this->object_id).append("'");
@ -3865,6 +3887,14 @@ void ListEntitiesNumberResponse::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(" unit_of_measurement: ");
out.append("'").append(this->unit_of_measurement).append("'");
out.append("\n");
out.append(" mode: ");
out.append(proto_enum_to_string<enums::NumberMode>(this->mode));
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@ -3899,7 +3929,7 @@ void NumberStateResponse::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void NumberStateResponse::dump_to(std::string &out) const { void NumberStateResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("NumberStateResponse {\n"); out.append("NumberStateResponse {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%u", this->key);
@ -3937,7 +3967,7 @@ void NumberCommandRequest::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void NumberCommandRequest::dump_to(std::string &out) const { void NumberCommandRequest::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("NumberCommandRequest {\n"); out.append("NumberCommandRequest {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%u", this->key);
@ -4015,7 +4045,7 @@ void ListEntitiesSelectResponse::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesSelectResponse::dump_to(std::string &out) const { void ListEntitiesSelectResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("ListEntitiesSelectResponse {\n"); out.append("ListEntitiesSelectResponse {\n");
out.append(" object_id: "); out.append(" object_id: ");
out.append("'").append(this->object_id).append("'"); out.append("'").append(this->object_id).append("'");
@ -4091,7 +4121,7 @@ void SelectStateResponse::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void SelectStateResponse::dump_to(std::string &out) const { void SelectStateResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("SelectStateResponse {\n"); out.append("SelectStateResponse {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%u", this->key);
@ -4134,7 +4164,7 @@ void SelectCommandRequest::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void SelectCommandRequest::dump_to(std::string &out) const { void SelectCommandRequest::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("SelectCommandRequest {\n"); out.append("SelectCommandRequest {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%u", this->key);
@ -4147,6 +4177,127 @@ void SelectCommandRequest::dump_to(std::string &out) const {
out.append("}"); out.append("}");
} }
#endif #endif
bool ListEntitiesButtonResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 6: {
this->disabled_by_default = value.as_bool();
return true;
}
case 7: {
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
default:
return false;
}
}
bool ListEntitiesButtonResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
this->object_id = value.as_string();
return true;
}
case 3: {
this->name = value.as_string();
return true;
}
case 4: {
this->unique_id = value.as_string();
return true;
}
case 5: {
this->icon = value.as_string();
return true;
}
case 8: {
this->device_class = value.as_string();
return true;
}
default:
return false;
}
}
bool ListEntitiesButtonResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
switch (field_id) {
case 2: {
this->key = value.as_fixed32();
return true;
}
default:
return false;
}
}
void ListEntitiesButtonResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.encode_string(3, this->name);
buffer.encode_string(4, this->unique_id);
buffer.encode_string(5, this->icon);
buffer.encode_bool(6, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
buffer.encode_string(8, this->device_class);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesButtonResponse::dump_to(std::string &out) const {
char buffer[64];
out.append("ListEntitiesButtonResponse {\n");
out.append(" object_id: ");
out.append("'").append(this->object_id).append("'");
out.append("\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
out.append(buffer);
out.append("\n");
out.append(" name: ");
out.append("'").append(this->name).append("'");
out.append("\n");
out.append(" unique_id: ");
out.append("'").append(this->unique_id).append("'");
out.append("\n");
out.append(" icon: ");
out.append("'").append(this->icon).append("'");
out.append("\n");
out.append(" disabled_by_default: ");
out.append(YESNO(this->disabled_by_default));
out.append("\n");
out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append(" device_class: ");
out.append("'").append(this->device_class).append("'");
out.append("\n");
out.append("}");
}
#endif
bool ButtonCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
switch (field_id) {
case 1: {
this->key = value.as_fixed32();
return true;
}
default:
return false;
}
}
void ButtonCommandRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); }
#ifdef HAS_PROTO_MESSAGE_DUMP
void ButtonCommandRequest::dump_to(std::string &out) const {
char buffer[64];
out.append("ButtonCommandRequest {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
out.append(buffer);
out.append("\n");
out.append("}");
}
#endif
} // namespace api } // namespace api
} // namespace esphome } // namespace esphome

View file

@ -123,6 +123,11 @@ enum ClimatePreset : uint32_t {
CLIMATE_PRESET_SLEEP = 6, CLIMATE_PRESET_SLEEP = 6,
CLIMATE_PRESET_ACTIVITY = 7, CLIMATE_PRESET_ACTIVITY = 7,
}; };
enum NumberMode : uint32_t {
NUMBER_MODE_AUTO = 0,
NUMBER_MODE_BOX = 1,
NUMBER_MODE_SLIDER = 2,
};
} // namespace enums } // namespace enums
@ -957,6 +962,8 @@ class ListEntitiesNumberResponse : public ProtoMessage {
float step{0.0f}; float step{0.0f};
bool disabled_by_default{false}; bool disabled_by_default{false};
enums::EntityCategory entity_category{}; enums::EntityCategory entity_category{};
std::string unit_of_measurement{};
enums::NumberMode mode{};
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;
@ -1041,6 +1048,37 @@ class SelectCommandRequest : public ProtoMessage {
bool decode_32bit(uint32_t field_id, Proto32Bit value) override; bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
}; };
class ListEntitiesButtonResponse : public ProtoMessage {
public:
std::string object_id{};
uint32_t key{0};
std::string name{};
std::string unique_id{};
std::string icon{};
bool disabled_by_default{false};
enums::EntityCategory entity_category{};
std::string device_class{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class ButtonCommandRequest : public ProtoMessage {
public:
uint32_t key{0};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
};
} // namespace api } // namespace api
} // namespace esphome } // namespace esphome

View file

@ -282,6 +282,16 @@ bool APIServerConnectionBase::send_select_state_response(const SelectStateRespon
#endif #endif
#ifdef USE_SELECT #ifdef USE_SELECT
#endif #endif
#ifdef USE_BUTTON
bool APIServerConnectionBase::send_list_entities_button_response(const ListEntitiesButtonResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_list_entities_button_response: %s", msg.dump().c_str());
#endif
return this->send_message_<ListEntitiesButtonResponse>(msg, 61);
}
#endif
#ifdef USE_BUTTON
#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: {
@ -513,6 +523,17 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
ESP_LOGVV(TAG, "on_select_command_request: %s", msg.dump().c_str()); ESP_LOGVV(TAG, "on_select_command_request: %s", msg.dump().c_str());
#endif #endif
this->on_select_command_request(msg); this->on_select_command_request(msg);
#endif
break;
}
case 62: {
#ifdef USE_BUTTON
ButtonCommandRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_button_command_request: %s", msg.dump().c_str());
#endif
this->on_button_command_request(msg);
#endif #endif
break; break;
} }
@ -737,6 +758,19 @@ void APIServerConnection::on_select_command_request(const SelectCommandRequest &
this->select_command(msg); this->select_command(msg);
} }
#endif #endif
#ifdef USE_BUTTON
void APIServerConnection::on_button_command_request(const ButtonCommandRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
this->button_command(msg);
}
#endif
} // namespace api } // namespace api
} // namespace esphome } // namespace esphome

View file

@ -129,6 +129,12 @@ class APIServerConnectionBase : public ProtoService {
#endif #endif
#ifdef USE_SELECT #ifdef USE_SELECT
virtual void on_select_command_request(const SelectCommandRequest &value){}; virtual void on_select_command_request(const SelectCommandRequest &value){};
#endif
#ifdef USE_BUTTON
bool send_list_entities_button_response(const ListEntitiesButtonResponse &msg);
#endif
#ifdef USE_BUTTON
virtual void on_button_command_request(const ButtonCommandRequest &value){};
#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;
@ -171,6 +177,9 @@ class APIServerConnection : public APIServerConnectionBase {
#endif #endif
#ifdef USE_SELECT #ifdef USE_SELECT
virtual void select_command(const SelectCommandRequest &msg) = 0; virtual void select_command(const SelectCommandRequest &msg) = 0;
#endif
#ifdef USE_BUTTON
virtual void button_command(const ButtonCommandRequest &msg) = 0;
#endif #endif
protected: protected:
void on_hello_request(const HelloRequest &msg) override; void on_hello_request(const HelloRequest &msg) override;
@ -209,6 +218,9 @@ class APIServerConnection : public APIServerConnectionBase {
#ifdef USE_SELECT #ifdef USE_SELECT
void on_select_command_request(const SelectCommandRequest &msg) override; void on_select_command_request(const SelectCommandRequest &msg) override;
#endif #endif
#ifdef USE_BUTTON
void on_button_command_request(const ButtonCommandRequest &msg) override;
#endif
}; };
} // namespace api } // namespace api

View file

@ -27,6 +27,9 @@ bool ListEntitiesIterator::on_sensor(sensor::Sensor *sensor) { return this->clie
#ifdef USE_SWITCH #ifdef USE_SWITCH
bool ListEntitiesIterator::on_switch(switch_::Switch *a_switch) { return this->client_->send_switch_info(a_switch); } bool ListEntitiesIterator::on_switch(switch_::Switch *a_switch) { return this->client_->send_switch_info(a_switch); }
#endif #endif
#ifdef USE_BUTTON
bool ListEntitiesIterator::on_button(button::Button *button) { return this->client_->send_button_info(button); }
#endif
#ifdef USE_TEXT_SENSOR #ifdef USE_TEXT_SENSOR
bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) { bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) {
return this->client_->send_text_sensor_info(text_sensor); return this->client_->send_text_sensor_info(text_sensor);

View file

@ -30,6 +30,9 @@ class ListEntitiesIterator : public ComponentIterator {
#ifdef USE_SWITCH #ifdef USE_SWITCH
bool on_switch(switch_::Switch *a_switch) override; bool on_switch(switch_::Switch *a_switch) override;
#endif #endif
#ifdef USE_BUTTON
bool on_button(button::Button *button) override;
#endif
#ifdef USE_TEXT_SENSOR #ifdef USE_TEXT_SENSOR
bool on_text_sensor(text_sensor::TextSensor *text_sensor) override; bool on_text_sensor(text_sensor::TextSensor *text_sensor) override;
#endif #endif

View file

@ -31,6 +31,9 @@ class InitialStateIterator : public ComponentIterator {
#ifdef USE_SWITCH #ifdef USE_SWITCH
bool on_switch(switch_::Switch *a_switch) override; bool on_switch(switch_::Switch *a_switch) override;
#endif #endif
#ifdef USE_BUTTON
bool on_button(button::Button *button) override { return true; };
#endif
#ifdef USE_TEXT_SENSOR #ifdef USE_TEXT_SENSOR
bool on_text_sensor(text_sensor::TextSensor *text_sensor) override; bool on_text_sensor(text_sensor::TextSensor *text_sensor) override;
#endif #endif

View file

@ -116,6 +116,21 @@ void ComponentIterator::advance() {
} }
break; break;
#endif #endif
#ifdef USE_BUTTON
case IteratorState::BUTTON:
if (this->at_ >= App.get_buttons().size()) {
advance_platform = true;
} else {
auto *button = App.get_buttons()[this->at_];
if (button->is_internal()) {
success = true;
break;
} else {
success = this->on_button(button);
}
}
break;
#endif
#ifdef USE_TEXT_SENSOR #ifdef USE_TEXT_SENSOR
case IteratorState::TEXT_SENSOR: case IteratorState::TEXT_SENSOR:
if (this->at_ >= App.get_text_sensors().size()) { if (this->at_ >= App.get_text_sensors().size()) {

View file

@ -38,6 +38,9 @@ class ComponentIterator {
#ifdef USE_SWITCH #ifdef USE_SWITCH
virtual bool on_switch(switch_::Switch *a_switch) = 0; virtual bool on_switch(switch_::Switch *a_switch) = 0;
#endif #endif
#ifdef USE_BUTTON
virtual bool on_button(button::Button *button) = 0;
#endif
#ifdef USE_TEXT_SENSOR #ifdef USE_TEXT_SENSOR
virtual bool on_text_sensor(text_sensor::TextSensor *text_sensor) = 0; virtual bool on_text_sensor(text_sensor::TextSensor *text_sensor) = 0;
#endif #endif
@ -78,6 +81,9 @@ class ComponentIterator {
#ifdef USE_SWITCH #ifdef USE_SWITCH
SWITCH, SWITCH,
#endif #endif
#ifdef USE_BUTTON
BUTTON,
#endif
#ifdef USE_TEXT_SENSOR #ifdef USE_TEXT_SENSOR
TEXT_SENSOR, TEXT_SENSOR,
#endif #endif

View file

@ -388,6 +388,15 @@ BLEDescriptor *BLECharacteristic::get_descriptor(uint16_t uuid) {
return this->get_descriptor(espbt::ESPBTUUID::from_uint16(uuid)); return this->get_descriptor(espbt::ESPBTUUID::from_uint16(uuid));
} }
void BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size) {
auto client = this->service->client;
auto status = esp_ble_gattc_write_char(client->gattc_if, client->conn_id, this->handle, new_val_size, new_val,
ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
if (status) {
ESP_LOGW(TAG, "Error sending write value to BLE gattc server, status=%d", status);
}
}
} // namespace ble_client } // namespace ble_client
} // namespace esphome } // namespace esphome

View file

@ -59,7 +59,7 @@ class BLECharacteristic {
void parse_descriptors(); void parse_descriptors();
BLEDescriptor *get_descriptor(espbt::ESPBTUUID uuid); BLEDescriptor *get_descriptor(espbt::ESPBTUUID uuid);
BLEDescriptor *get_descriptor(uint16_t uuid); BLEDescriptor *get_descriptor(uint16_t uuid);
void write_value(uint8_t *new_val, int16_t new_val_size);
BLEService *service; BLEService *service;
}; };

View file

@ -0,0 +1,67 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import output, ble_client, esp32_ble_tracker
from esphome.const import CONF_ID, CONF_SERVICE_UUID
from .. import ble_client_ns
DEPENDENCIES = ["ble_client"]
CONF_CHARACTERISTIC_UUID = "characteristic_uuid"
BLEBinaryOutput = ble_client_ns.class_(
"BLEBinaryOutput", output.BinaryOutput, ble_client.BLEClientNode, cg.Component
)
CONFIG_SCHEMA = cv.All(
output.BINARY_OUTPUT_SCHEMA.extend(
{
cv.Required(CONF_ID): cv.declare_id(BLEBinaryOutput),
cv.Required(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid,
cv.Required(CONF_CHARACTERISTIC_UUID): esp32_ble_tracker.bt_uuid,
}
)
.extend(cv.COMPONENT_SCHEMA)
.extend(ble_client.BLE_CLIENT_SCHEMA)
)
def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
cg.add(
var.set_service_uuid16(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))
)
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid32_format):
cg.add(
var.set_service_uuid32(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))
)
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format):
uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID])
cg.add(var.set_service_uuid128(uuid128))
if len(config[CONF_CHARACTERISTIC_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
cg.add(
var.set_char_uuid16(
esp32_ble_tracker.as_hex(config[CONF_CHARACTERISTIC_UUID])
)
)
elif len(config[CONF_CHARACTERISTIC_UUID]) == len(
esp32_ble_tracker.bt_uuid32_format
):
cg.add(
var.set_char_uuid32(
esp32_ble_tracker.as_hex(config[CONF_CHARACTERISTIC_UUID])
)
)
elif len(config[CONF_CHARACTERISTIC_UUID]) == len(
esp32_ble_tracker.bt_uuid128_format
):
uuid128 = esp32_ble_tracker.as_reversed_hex_array(
config[CONF_CHARACTERISTIC_UUID]
)
cg.add(var.set_char_uuid128(uuid128))
yield output.register_output(var, config)
yield ble_client.register_ble_node(var, config)
yield cg.register_component(var, config)

View file

@ -0,0 +1,71 @@
#include "ble_binary_output.h"
#include "esphome/core/log.h"
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#ifdef USE_ESP32
namespace esphome {
namespace ble_client {
static const char *const TAG = "ble_binary_output";
void BLEBinaryOutput::dump_config() {
ESP_LOGCONFIG(TAG, "BLE Binary Output:");
ESP_LOGCONFIG(TAG, " MAC address : %s", this->parent_->address_str().c_str());
ESP_LOGCONFIG(TAG, " Service UUID : %s", this->service_uuid_.to_string().c_str());
ESP_LOGCONFIG(TAG, " Characteristic UUID: %s", this->char_uuid_.to_string().c_str());
LOG_BINARY_OUTPUT(this);
}
void BLEBinaryOutput::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) {
switch (event) {
case ESP_GATTC_OPEN_EVT:
this->client_state_ = espbt::ClientState::ESTABLISHED;
ESP_LOGW(TAG, "[%s] Connected successfully!", this->char_uuid_.to_string().c_str());
break;
case ESP_GATTC_DISCONNECT_EVT:
ESP_LOGW(TAG, "[%s] Disconnected", this->char_uuid_.to_string().c_str());
this->client_state_ = espbt::ClientState::IDLE;
break;
case ESP_GATTC_WRITE_CHAR_EVT: {
if (param->write.status == 0) {
break;
}
auto chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_);
if (chr == nullptr) {
ESP_LOGW(TAG, "[%s] Characteristic not found.", this->char_uuid_.to_string().c_str());
break;
}
if (param->write.handle == chr->handle) {
ESP_LOGW(TAG, "[%s] Write error, status=%d", this->char_uuid_.to_string().c_str(), param->write.status);
}
break;
}
default:
break;
}
}
void BLEBinaryOutput::write_state(bool state) {
if (this->client_state_ != espbt::ClientState::ESTABLISHED) {
ESP_LOGW(TAG, "[%s] Not connected to BLE client. State update can not be written.",
this->char_uuid_.to_string().c_str());
return;
}
auto chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_);
if (chr == nullptr) {
ESP_LOGW(TAG, "[%s] Characteristic not found. State update can not be written.",
this->char_uuid_.to_string().c_str());
return;
}
uint8_t state_as_uint = (uint8_t) state;
ESP_LOGV(TAG, "[%s] Write State: %d", this->char_uuid_.to_string().c_str(), state_as_uint);
chr->write_value(&state_as_uint, sizeof(state_as_uint));
}
} // namespace ble_client
} // namespace esphome
#endif

View file

@ -0,0 +1,39 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/ble_client/ble_client.h"
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#include "esphome/components/output/binary_output.h"
#ifdef USE_ESP32
#include <esp_gattc_api.h>
namespace esphome {
namespace ble_client {
namespace espbt = esphome::esp32_ble_tracker;
class BLEBinaryOutput : public output::BinaryOutput, public BLEClientNode, public Component {
public:
void dump_config() override;
void loop() override {}
float get_setup_priority() const override { return setup_priority::DATA; }
void set_service_uuid16(uint16_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
void set_service_uuid32(uint32_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
void set_service_uuid128(uint8_t *uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
void set_char_uuid16(uint16_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
void set_char_uuid32(uint32_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
void set_char_uuid128(uint8_t *uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) override;
protected:
void write_state(bool state) override;
espbt::ESPBTUUID service_uuid_;
espbt::ESPBTUUID char_uuid_;
espbt::ClientState client_state_;
};
} // namespace ble_client
} // namespace esphome
#endif

View file

@ -33,6 +33,7 @@ static const uint8_t BME280_REGISTER_CONTROLHUMID = 0xF2;
static const uint8_t BME280_REGISTER_STATUS = 0xF3; static const uint8_t BME280_REGISTER_STATUS = 0xF3;
static const uint8_t BME280_REGISTER_CONTROL = 0xF4; static const uint8_t BME280_REGISTER_CONTROL = 0xF4;
static const uint8_t BME280_REGISTER_CONFIG = 0xF5; static const uint8_t BME280_REGISTER_CONFIG = 0xF5;
static const uint8_t BME280_REGISTER_MEASUREMENTS = 0xF7;
static const uint8_t BME280_REGISTER_PRESSUREDATA = 0xF7; static const uint8_t BME280_REGISTER_PRESSUREDATA = 0xF7;
static const uint8_t BME280_REGISTER_TEMPDATA = 0xFA; static const uint8_t BME280_REGISTER_TEMPDATA = 0xFA;
static const uint8_t BME280_REGISTER_HUMIDDATA = 0xFD; static const uint8_t BME280_REGISTER_HUMIDDATA = 0xFD;
@ -178,21 +179,27 @@ void BME280Component::update() {
return; return;
} }
float meas_time = 1.5; float meas_time = 1.5f;
meas_time += 2.3f * oversampling_to_time(this->temperature_oversampling_); meas_time += 2.3f * oversampling_to_time(this->temperature_oversampling_);
meas_time += 2.3f * oversampling_to_time(this->pressure_oversampling_) + 0.575f; meas_time += 2.3f * oversampling_to_time(this->pressure_oversampling_) + 0.575f;
meas_time += 2.3f * oversampling_to_time(this->humidity_oversampling_) + 0.575f; meas_time += 2.3f * oversampling_to_time(this->humidity_oversampling_) + 0.575f;
this->set_timeout("data", uint32_t(ceilf(meas_time)), [this]() { this->set_timeout("data", uint32_t(ceilf(meas_time)), [this]() {
uint8_t data[8];
if (!this->read_bytes(BME280_REGISTER_MEASUREMENTS, data, 8)) {
ESP_LOGW(TAG, "Error reading registers.");
this->status_set_warning();
return;
}
int32_t t_fine = 0; int32_t t_fine = 0;
float temperature = this->read_temperature_(&t_fine); float temperature = this->read_temperature_(data, &t_fine);
if (std::isnan(temperature)) { if (std::isnan(temperature)) {
ESP_LOGW(TAG, "Invalid temperature, cannot read pressure & humidity values."); ESP_LOGW(TAG, "Invalid temperature, cannot read pressure & humidity values.");
this->status_set_warning(); this->status_set_warning();
return; return;
} }
float pressure = this->read_pressure_(t_fine); float pressure = this->read_pressure_(data, t_fine);
float humidity = this->read_humidity_(t_fine); float humidity = this->read_humidity_(data, t_fine);
ESP_LOGD(TAG, "Got temperature=%.1f°C pressure=%.1fhPa humidity=%.1f%%", temperature, pressure, humidity); ESP_LOGD(TAG, "Got temperature=%.1f°C pressure=%.1fhPa humidity=%.1f%%", temperature, pressure, humidity);
if (this->temperature_sensor_ != nullptr) if (this->temperature_sensor_ != nullptr)
@ -204,11 +211,8 @@ void BME280Component::update() {
this->status_clear_warning(); this->status_clear_warning();
}); });
} }
float BME280Component::read_temperature_(int32_t *t_fine) { float BME280Component::read_temperature_(const uint8_t *data, int32_t *t_fine) {
uint8_t data[3]; int32_t adc = ((data[3] & 0xFF) << 16) | ((data[4] & 0xFF) << 8) | (data[5] & 0xFF);
if (!this->read_bytes(BME280_REGISTER_TEMPDATA, data, 3))
return NAN;
int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF);
adc >>= 4; adc >>= 4;
if (adc == 0x80000) if (adc == 0x80000)
// temperature was disabled // temperature was disabled
@ -226,10 +230,7 @@ float BME280Component::read_temperature_(int32_t *t_fine) {
return temperature / 100.0f; return temperature / 100.0f;
} }
float BME280Component::read_pressure_(int32_t t_fine) { float BME280Component::read_pressure_(const uint8_t *data, int32_t t_fine) {
uint8_t data[3];
if (!this->read_bytes(BME280_REGISTER_PRESSUREDATA, data, 3))
return NAN;
int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF); int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF);
adc >>= 4; adc >>= 4;
if (adc == 0x80000) if (adc == 0x80000)
@ -265,9 +266,9 @@ float BME280Component::read_pressure_(int32_t t_fine) {
return (p / 256.0f) / 100.0f; return (p / 256.0f) / 100.0f;
} }
float BME280Component::read_humidity_(int32_t t_fine) { float BME280Component::read_humidity_(const uint8_t *data, int32_t t_fine) {
uint16_t raw_adc; uint16_t raw_adc = ((data[6] & 0xFF) << 8) | (data[7] & 0xFF);
if (!this->read_byte_16(BME280_REGISTER_HUMIDDATA, &raw_adc) || raw_adc == 0x8000) if (raw_adc == 0x8000)
return NAN; return NAN;
int32_t adc = raw_adc; int32_t adc = raw_adc;

View file

@ -82,11 +82,11 @@ class BME280Component : public PollingComponent, public i2c::I2CDevice {
protected: protected:
/// Read the temperature value and store the calculated ambient temperature in t_fine. /// Read the temperature value and store the calculated ambient temperature in t_fine.
float read_temperature_(int32_t *t_fine); float read_temperature_(const uint8_t *data, int32_t *t_fine);
/// Read the pressure value in hPa using the provided t_fine value. /// Read the pressure value in hPa using the provided t_fine value.
float read_pressure_(int32_t t_fine); float read_pressure_(const uint8_t *data, int32_t t_fine);
/// Read the humidity value in % using the provided t_fine value. /// Read the humidity value in % using the provided t_fine value.
float read_humidity_(int32_t t_fine); float read_humidity_(const uint8_t *data, int32_t t_fine);
uint8_t read_u8_(uint8_t a_register); uint8_t read_u8_(uint8_t a_register);
uint16_t read_u16_le_(uint8_t a_register); uint16_t read_u16_le_(uint8_t a_register);
int16_t read_s16_le_(uint8_t a_register); int16_t read_s16_le_(uint8_t a_register);

View file

@ -0,0 +1,127 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.automation import maybe_simple_id
from esphome.components import mqtt
from esphome.const import (
CONF_DEVICE_CLASS,
CONF_ENTITY_CATEGORY,
CONF_ICON,
CONF_ID,
CONF_ON_PRESS,
CONF_TRIGGER_ID,
CONF_MQTT_ID,
DEVICE_CLASS_RESTART,
DEVICE_CLASS_UPDATE,
)
from esphome.core import CORE, coroutine_with_priority
from esphome.cpp_helpers import setup_entity
CODEOWNERS = ["@esphome/core"]
IS_PLATFORM_COMPONENT = True
DEVICE_CLASSES = [
DEVICE_CLASS_RESTART,
DEVICE_CLASS_UPDATE,
]
button_ns = cg.esphome_ns.namespace("button")
Button = button_ns.class_("Button", cg.EntityBase)
ButtonPtr = Button.operator("ptr")
PressAction = button_ns.class_("PressAction", automation.Action)
ButtonPressTrigger = button_ns.class_(
"ButtonPressTrigger", automation.Trigger.template()
)
validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
BUTTON_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
{
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTButtonComponent),
cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
cv.Optional(CONF_ON_PRESS): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ButtonPressTrigger),
}
),
}
)
_UNDEF = object()
def button_schema(
icon: str = _UNDEF,
entity_category: str = _UNDEF,
device_class: str = _UNDEF,
) -> cv.Schema:
schema = BUTTON_SCHEMA
if icon is not _UNDEF:
schema = schema.extend({cv.Optional(CONF_ICON, default=icon): cv.icon})
if entity_category is not _UNDEF:
schema = schema.extend(
{
cv.Optional(
CONF_ENTITY_CATEGORY, default=entity_category
): cv.entity_category
}
)
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):
await setup_entity(var, config)
for conf in config.get(CONF_ON_PRESS, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
if CONF_DEVICE_CLASS in config:
cg.add(var.set_device_class(config[CONF_DEVICE_CLASS]))
if CONF_MQTT_ID in config:
mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var)
await mqtt.register_mqtt_component(mqtt_, config)
async def register_button(var, config):
if not CORE.has_id(config[CONF_ID]):
var = cg.Pvariable(config[CONF_ID], var)
cg.add(cg.App.register_button(var))
await setup_button_core_(var, config)
async def new_button(config):
var = cg.new_Pvariable(config[CONF_ID])
await register_button(var, config)
return var
BUTTON_PRESS_SCHEMA = maybe_simple_id(
{
cv.Required(CONF_ID): cv.use_id(Button),
}
)
@automation.register_action("button.press", PressAction, BUTTON_PRESS_SCHEMA)
async def button_press_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)
@coroutine_with_priority(100.0)
async def to_code(config):
cg.add_global(button_ns.using)
cg.add_define("USE_BUTTON")

View file

@ -0,0 +1,28 @@
#pragma once
#include "esphome/components/button/button.h"
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
namespace esphome {
namespace button {
template<typename... Ts> class PressAction : public Action<Ts...> {
public:
explicit PressAction(Button *button) : button_(button) {}
void play(Ts... x) override { this->button_->press(); }
protected:
Button *button_;
};
class ButtonPressTrigger : public Trigger<> {
public:
ButtonPressTrigger(Button *button) {
button->add_on_press_callback([this]() { this->trigger(); });
}
};
} // namespace button
} // namespace esphome

View file

@ -0,0 +1,28 @@
#include "button.h"
#include "esphome/core/log.h"
namespace esphome {
namespace button {
static const char *const TAG = "button";
Button::Button(const std::string &name) : EntityBase(name) {}
Button::Button() : Button("") {}
void Button::press() {
ESP_LOGD(TAG, "'%s' Pressed.", this->get_name().c_str());
this->press_action();
this->press_callback_.call();
}
void Button::add_on_press_callback(std::function<void()> &&callback) { this->press_callback_.add(std::move(callback)); }
uint32_t Button::hash_base() { return 1495763804UL; }
void Button::set_device_class(const std::string &device_class) { this->device_class_ = device_class; }
std::string Button::get_device_class() {
if (this->device_class_.has_value())
return *this->device_class_;
return "";
}
} // namespace button
} // namespace esphome

View file

@ -0,0 +1,57 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/entity_base.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace button {
#define LOG_BUTTON(prefix, type, obj) \
if ((obj) != nullptr) { \
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
if (!(obj)->get_icon().empty()) { \
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \
} \
}
/** Base class for all buttons.
*
* A button is just a momentary switch that does not have a state, only a trigger.
*/
class Button : public EntityBase {
public:
explicit Button();
explicit Button(const std::string &name);
/** Press this button. This is called by the front-end.
*
* For implementing buttons, please override press_action.
*/
void press();
/** Set callback for state changes.
*
* @param callback The void() callback.
*/
void add_on_press_callback(std::function<void()> &&callback);
/// Set the Home Assistant device class (see button::device_class).
void set_device_class(const std::string &device_class);
/// Get the device class for this button.
std::string get_device_class();
protected:
/** You should implement this virtual method if you want to create your own button.
*/
virtual void press_action(){};
uint32_t hash_base() override;
CallbackManager<void()> press_callback_{};
optional<std::string> device_class_{};
};
} // namespace button
} // namespace esphome

View file

@ -85,14 +85,7 @@ void CaptivePortal::start() {
this->dns_server_->start(53, "*", (uint32_t) ip); this->dns_server_->start(53, "*", (uint32_t) ip);
this->base_->get_server()->onNotFound([this](AsyncWebServerRequest *req) { this->base_->get_server()->onNotFound([this](AsyncWebServerRequest *req) {
bool not_found = false; if (!this->active_ || req->host().c_str() == wifi::global_wifi_component->wifi_soft_ap_ip().str()) {
if (!this->active_) {
not_found = true;
} else if (req->host().c_str() == wifi::global_wifi_component->wifi_soft_ap_ip().str()) {
not_found = true;
}
if (not_found) {
req->send(404, "text/html", "File not found"); req->send(404, "text/html", "File not found");
return; return;
} }

View file

@ -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_STATE,
CONF_PRESET, CONF_PRESET,
CONF_SWING_MODE, CONF_SWING_MODE,
CONF_SWING_MODE_COMMAND_TOPIC, CONF_SWING_MODE_COMMAND_TOPIC,
@ -34,6 +35,7 @@ from esphome.const import (
CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC, CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC,
CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC, CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC,
CONF_TEMPERATURE_STEP, CONF_TEMPERATURE_STEP,
CONF_TRIGGER_ID,
CONF_VISUAL, CONF_VISUAL,
CONF_MQTT_ID, CONF_MQTT_ID,
) )
@ -101,6 +103,7 @@ validate_climate_swing_mode = cv.enum(CLIMATE_SWING_MODES, upper=True)
# Actions # Actions
ControlAction = climate_ns.class_("ControlAction", automation.Action) ControlAction = climate_ns.class_("ControlAction", automation.Action)
StateTrigger = climate_ns.class_("StateTrigger", automation.Trigger.template())
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(
{ {
@ -161,6 +164,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_STATE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger),
}
),
} }
) )
@ -205,7 +213,7 @@ async def setup_climate_core_(var, config):
if CONF_MODE_COMMAND_TOPIC in config: if CONF_MODE_COMMAND_TOPIC in config:
cg.add(mqtt_.set_custom_mode_command_topic(config[CONF_MODE_COMMAND_TOPIC])) cg.add(mqtt_.set_custom_mode_command_topic(config[CONF_MODE_COMMAND_TOPIC]))
if CONF_MODE_STATE_TOPIC in config: if CONF_MODE_STATE_TOPIC in config:
cg.add(mqtt_.set_custom_state_topic(config[CONF_MODE_STATE_TOPIC])) cg.add(mqtt_.set_custom_mode_state_topic(config[CONF_MODE_STATE_TOPIC]))
if CONF_SWING_MODE_COMMAND_TOPIC in config: if CONF_SWING_MODE_COMMAND_TOPIC in config:
cg.add( cg.add(
@ -256,6 +264,10 @@ async def setup_climate_core_(var, config):
) )
) )
for conf in config.get(CONF_ON_STATE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
async def register_climate(var, config): async def register_climate(var, config):
if not CORE.has_id(config[CONF_ID]): if not CORE.has_id(config[CONF_ID]):

View file

@ -42,5 +42,12 @@ template<typename... Ts> class ControlAction : public Action<Ts...> {
Climate *climate_; Climate *climate_;
}; };
class StateTrigger : public Trigger<> {
public:
StateTrigger(Climate *climate) {
climate->add_on_state_callback([this]() { this->trigger(); });
}
};
} // namespace climate } // namespace climate
} // namespace esphome } // namespace esphome

View file

@ -102,8 +102,6 @@ void CS5460AComponent::hw_init_() {
/* Doesn't reset the register values etc., just restarts the "computation cycle" */ /* Doesn't reset the register values etc., just restarts the "computation cycle" */
void CS5460AComponent::restart_() { void CS5460AComponent::restart_() {
int cnt;
this->enable(); this->enable();
/* Stop running conversion, wake up if needed */ /* Stop running conversion, wake up if needed */
this->write_byte(CMD_POWER_UP); this->write_byte(CMD_POWER_UP);

View file

@ -90,6 +90,7 @@ void CSE7766Component::parse_data_() {
uint32_t power_cycle = this->get_24_bit_uint_(17); uint32_t power_cycle = this->get_24_bit_uint_(17);
uint8_t adj = this->raw_data_[20]; uint8_t adj = this->raw_data_[20];
uint32_t cf_pulses = (this->raw_data_[21] << 8) + this->raw_data_[22];
bool power_ok = true; bool power_ok = true;
bool voltage_ok = true; bool voltage_ok = true;
@ -127,6 +128,18 @@ void CSE7766Component::parse_data_() {
power = power_calib / float(power_cycle); power = power_calib / float(power_cycle);
this->power_acc_ += power; this->power_acc_ += power;
this->power_counts_ += 1; this->power_counts_ += 1;
uint32_t difference;
if (this->cf_pulses_last_ == 0)
this->cf_pulses_last_ = cf_pulses;
if (cf_pulses < this->cf_pulses_last_) {
difference = cf_pulses + (0x10000 - this->cf_pulses_last_);
} else {
difference = cf_pulses - this->cf_pulses_last_;
}
this->cf_pulses_last_ = cf_pulses;
this->energy_total_ += difference * float(power_calib) / 1000000.0 / 3600.0;
} }
if ((adj & 0x20) == 0x20 && current_ok && voltage_ok && power != 0.0) { if ((adj & 0x20) == 0x20 && current_ok && voltage_ok && power != 0.0) {
@ -136,9 +149,9 @@ void CSE7766Component::parse_data_() {
} }
} }
void CSE7766Component::update() { void CSE7766Component::update() {
float voltage = this->voltage_counts_ > 0 ? this->voltage_acc_ / this->voltage_counts_ : 0.0; float voltage = this->voltage_counts_ > 0 ? this->voltage_acc_ / this->voltage_counts_ : 0.0f;
float current = this->current_counts_ > 0 ? this->current_acc_ / this->current_counts_ : 0.0; float current = this->current_counts_ > 0 ? this->current_acc_ / this->current_counts_ : 0.0f;
float power = this->power_counts_ > 0 ? this->power_acc_ / this->power_counts_ : 0.0; float power = this->power_counts_ > 0 ? this->power_acc_ / this->power_counts_ : 0.0f;
ESP_LOGV(TAG, "Got voltage_acc=%.2f current_acc=%.2f power_acc=%.2f", this->voltage_acc_, this->current_acc_, ESP_LOGV(TAG, "Got voltage_acc=%.2f current_acc=%.2f power_acc=%.2f", this->voltage_acc_, this->current_acc_,
this->power_acc_); this->power_acc_);
@ -152,6 +165,8 @@ void CSE7766Component::update() {
this->current_sensor_->publish_state(current); this->current_sensor_->publish_state(current);
if (this->power_sensor_ != nullptr) if (this->power_sensor_ != nullptr)
this->power_sensor_->publish_state(power); this->power_sensor_->publish_state(power);
if (this->energy_sensor_ != nullptr)
this->energy_sensor_->publish_state(this->energy_total_);
this->voltage_acc_ = 0.0f; this->voltage_acc_ = 0.0f;
this->current_acc_ = 0.0f; this->current_acc_ = 0.0f;
@ -172,6 +187,7 @@ void CSE7766Component::dump_config() {
LOG_SENSOR(" ", "Voltage", this->voltage_sensor_); LOG_SENSOR(" ", "Voltage", this->voltage_sensor_);
LOG_SENSOR(" ", "Current", this->current_sensor_); LOG_SENSOR(" ", "Current", this->current_sensor_);
LOG_SENSOR(" ", "Power", this->power_sensor_); LOG_SENSOR(" ", "Power", this->power_sensor_);
LOG_SENSOR(" ", "Energy", this->energy_sensor_);
this->check_uart_settings(4800); this->check_uart_settings(4800);
} }

View file

@ -12,6 +12,7 @@ class CSE7766Component : public PollingComponent, public uart::UARTDevice {
void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; } void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_sensor; } void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_sensor; }
void set_power_sensor(sensor::Sensor *power_sensor) { power_sensor_ = power_sensor; } void set_power_sensor(sensor::Sensor *power_sensor) { power_sensor_ = power_sensor; }
void set_energy_sensor(sensor::Sensor *energy_sensor) { energy_sensor_ = energy_sensor; }
void loop() override; void loop() override;
float get_setup_priority() const override; float get_setup_priority() const override;
@ -29,9 +30,12 @@ class CSE7766Component : public PollingComponent, public uart::UARTDevice {
sensor::Sensor *voltage_sensor_{nullptr}; sensor::Sensor *voltage_sensor_{nullptr};
sensor::Sensor *current_sensor_{nullptr}; sensor::Sensor *current_sensor_{nullptr};
sensor::Sensor *power_sensor_{nullptr}; sensor::Sensor *power_sensor_{nullptr};
sensor::Sensor *energy_sensor_{nullptr};
float voltage_acc_{0.0f}; float voltage_acc_{0.0f};
float current_acc_{0.0f}; float current_acc_{0.0f};
float power_acc_{0.0f}; float power_acc_{0.0f};
float energy_total_{0.0f};
uint32_t cf_pulses_last_{0};
uint32_t voltage_counts_{0}; uint32_t voltage_counts_{0};
uint32_t current_counts_{0}; uint32_t current_counts_{0};
uint32_t power_counts_{0}; uint32_t power_counts_{0};

View file

@ -3,16 +3,20 @@ import esphome.config_validation as cv
from esphome.components import sensor, uart from esphome.components import sensor, uart
from esphome.const import ( from esphome.const import (
CONF_CURRENT, CONF_CURRENT,
CONF_ENERGY,
CONF_ID, CONF_ID,
CONF_POWER, CONF_POWER,
CONF_VOLTAGE, CONF_VOLTAGE,
DEVICE_CLASS_CURRENT, DEVICE_CLASS_CURRENT,
DEVICE_CLASS_ENERGY,
DEVICE_CLASS_POWER, DEVICE_CLASS_POWER,
DEVICE_CLASS_VOLTAGE, DEVICE_CLASS_VOLTAGE,
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
STATE_CLASS_TOTAL_INCREASING,
UNIT_VOLT, UNIT_VOLT,
UNIT_AMPERE, UNIT_AMPERE,
UNIT_WATT, UNIT_WATT,
UNIT_WATT_HOURS,
) )
DEPENDENCIES = ["uart"] DEPENDENCIES = ["uart"]
@ -44,6 +48,12 @@ CONFIG_SCHEMA = (
device_class=DEVICE_CLASS_POWER, device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
), ),
cv.Optional(CONF_ENERGY): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT_HOURS,
accuracy_decimals=3,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
} }
) )
.extend(cv.polling_component_schema("60s")) .extend(cv.polling_component_schema("60s"))
@ -71,3 +81,7 @@ async def to_code(config):
conf = config[CONF_POWER] conf = config[CONF_POWER]
sens = await sensor.new_sensor(conf) sens = await sensor.new_sensor(conf)
cg.add(var.set_power_sensor(sens)) cg.add(var.set_power_sensor(sens))
if CONF_ENERGY in config:
conf = config[CONF_ENERGY]
sens = await sensor.new_sensor(conf)
cg.add(var.set_energy_sensor(sens))

View file

@ -231,7 +231,7 @@ bool DaikinClimate::on_receive(remote_base::RemoteReceiveData data) {
// frame header // frame header
if (byte != 0x27) if (byte != 0x27)
return false; return false;
} else if (pos == 3) { } else if (pos == 3) { // NOLINT(bugprone-branch-clone)
// frame header // frame header
if (byte != 0x00) if (byte != 0x00)
return false; return false;

View file

@ -4,20 +4,23 @@
#include "esphome/core/defines.h" #include "esphome/core/defines.h"
#include "esphome/core/version.h" #include "esphome/core/version.h"
#ifdef USE_ESP_IDF
#include <esp_heap_caps.h>
#include <esp_system.h>
#endif
#ifdef USE_ESP32 #ifdef USE_ESP32
#if ESP_IDF_VERSION_MAJOR >= 4
#include <esp32/rom/rtc.h>
#else
#include <rom/rtc.h> #include <rom/rtc.h>
#include <esp_idf_version.h> #endif
#endif #endif
#ifdef USE_ARDUINO #ifdef USE_ARDUINO
#include <Esp.h> #include <Esp.h>
#endif #endif
#ifdef USE_ESP_IDF
#include <esp_heap_caps.h>
#include <esp_system.h>
#endif
namespace esphome { namespace esphome {
namespace debug { namespace debug {

View file

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

View file

@ -496,7 +496,7 @@ bool Animation::get_pixel(int x, int y) const {
return false; return false;
const uint32_t width_8 = ((this->width_ + 7u) / 8u) * 8u; const uint32_t width_8 = ((this->width_ + 7u) / 8u) * 8u;
const uint32_t frame_index = this->height_ * width_8 * this->current_frame_; const uint32_t frame_index = this->height_ * width_8 * this->current_frame_;
if (frame_index >= this->width_ * this->height_ * this->animation_frame_count_) if (frame_index >= (uint32_t)(this->width_ * this->height_ * this->animation_frame_count_))
return false; return false;
const uint32_t pos = x + y * width_8 + frame_index; const uint32_t pos = x + y * width_8 + frame_index;
return progmem_read_byte(this->data_start_ + (pos / 8u)) & (0x80 >> (pos % 8u)); return progmem_read_byte(this->data_start_ + (pos / 8u)) & (0x80 >> (pos % 8u));
@ -505,7 +505,7 @@ Color Animation::get_color_pixel(int x, int y) const {
if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_) if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
return Color::BLACK; return Color::BLACK;
const uint32_t frame_index = this->width_ * this->height_ * this->current_frame_; const uint32_t frame_index = this->width_ * this->height_ * this->current_frame_;
if (frame_index >= this->width_ * this->height_ * this->animation_frame_count_) if (frame_index >= (uint32_t)(this->width_ * this->height_ * this->animation_frame_count_))
return Color::BLACK; return Color::BLACK;
const uint32_t pos = (x + y * this->width_ + frame_index) * 3; const uint32_t pos = (x + y * this->width_ + frame_index) * 3;
const uint32_t color32 = (progmem_read_byte(this->data_start_ + pos + 2) << 0) | const uint32_t color32 = (progmem_read_byte(this->data_start_ + pos + 2) << 0) |
@ -517,7 +517,7 @@ Color Animation::get_grayscale_pixel(int x, int y) const {
if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_) if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
return Color::BLACK; return Color::BLACK;
const uint32_t frame_index = this->width_ * this->height_ * this->current_frame_; const uint32_t frame_index = this->width_ * this->height_ * this->current_frame_;
if (frame_index >= this->width_ * this->height_ * this->animation_frame_count_) if (frame_index >= (uint32_t)(this->width_ * this->height_ * this->animation_frame_count_))
return Color::BLACK; return Color::BLACK;
const uint32_t pos = (x + y * this->width_ + frame_index); const uint32_t pos = (x + y * this->width_ + frame_index);
const uint8_t gray = progmem_read_byte(this->data_start_ + pos); const uint8_t gray = progmem_read_byte(this->data_start_ + pos);

View file

@ -1,9 +1,11 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome import pins
from esphome.components import uart from esphome.components import uart
from esphome.const import ( from esphome.const import (
CONF_ID, CONF_ID,
CONF_UART_ID, CONF_UART_ID,
CONF_RECEIVE_TIMEOUT,
) )
CODEOWNERS = ["@glmnet", "@zuidwijk"] CODEOWNERS = ["@glmnet", "@zuidwijk"]
@ -11,10 +13,13 @@ CODEOWNERS = ["@glmnet", "@zuidwijk"]
DEPENDENCIES = ["uart"] DEPENDENCIES = ["uart"]
AUTO_LOAD = ["sensor", "text_sensor"] AUTO_LOAD = ["sensor", "text_sensor"]
CONF_DSMR_ID = "dsmr_id"
CONF_DECRYPTION_KEY = "decryption_key"
CONF_CRC_CHECK = "crc_check" CONF_CRC_CHECK = "crc_check"
CONF_DECRYPTION_KEY = "decryption_key"
CONF_DSMR_ID = "dsmr_id"
CONF_GAS_MBUS_ID = "gas_mbus_id" CONF_GAS_MBUS_ID = "gas_mbus_id"
CONF_MAX_TELEGRAM_LENGTH = "max_telegram_length"
CONF_REQUEST_INTERVAL = "request_interval"
CONF_REQUEST_PIN = "request_pin"
# Hack to prevent compile error due to ambiguity with lib namespace # Hack to prevent compile error due to ambiguity with lib namespace
dsmr_ns = cg.esphome_ns.namespace("esphome::dsmr") dsmr_ns = cg.esphome_ns.namespace("esphome::dsmr")
@ -46,6 +51,14 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_DECRYPTION_KEY): _validate_key, cv.Optional(CONF_DECRYPTION_KEY): _validate_key,
cv.Optional(CONF_CRC_CHECK, default=True): cv.boolean, cv.Optional(CONF_CRC_CHECK, default=True): cv.boolean,
cv.Optional(CONF_GAS_MBUS_ID, default=1): cv.int_, cv.Optional(CONF_GAS_MBUS_ID, default=1): cv.int_,
cv.Optional(CONF_MAX_TELEGRAM_LENGTH, default=1500): cv.int_,
cv.Optional(CONF_REQUEST_PIN): pins.gpio_output_pin_schema,
cv.Optional(
CONF_REQUEST_INTERVAL, default="0ms"
): cv.positive_time_period_milliseconds,
cv.Optional(
CONF_RECEIVE_TIMEOUT, default="200ms"
): cv.positive_time_period_milliseconds,
} }
).extend(uart.UART_DEVICE_SCHEMA), ).extend(uart.UART_DEVICE_SCHEMA),
cv.only_with_arduino, cv.only_with_arduino,
@ -55,10 +68,17 @@ CONFIG_SCHEMA = cv.All(
async def to_code(config): async def to_code(config):
uart_component = await cg.get_variable(config[CONF_UART_ID]) uart_component = await cg.get_variable(config[CONF_UART_ID])
var = cg.new_Pvariable(config[CONF_ID], uart_component, config[CONF_CRC_CHECK]) var = cg.new_Pvariable(config[CONF_ID], uart_component, config[CONF_CRC_CHECK])
cg.add(var.set_max_telegram_length(config[CONF_MAX_TELEGRAM_LENGTH]))
if CONF_DECRYPTION_KEY in config: if CONF_DECRYPTION_KEY in config:
cg.add(var.set_decryption_key(config[CONF_DECRYPTION_KEY])) cg.add(var.set_decryption_key(config[CONF_DECRYPTION_KEY]))
await cg.register_component(var, config) await cg.register_component(var, config)
if CONF_REQUEST_PIN in config:
request_pin = await cg.gpio_pin_expression(config[CONF_REQUEST_PIN])
cg.add(var.set_request_pin(request_pin))
cg.add(var.set_request_interval(config[CONF_REQUEST_INTERVAL].total_milliseconds))
cg.add(var.set_receive_timeout(config[CONF_RECEIVE_TIMEOUT].total_milliseconds))
cg.add_define("DSMR_GAS_MBUS_ID", config[CONF_GAS_MBUS_ID]) cg.add_define("DSMR_GAS_MBUS_ID", config[CONF_GAS_MBUS_ID])
# DSMR Parser # DSMR Parser

View file

@ -12,171 +12,275 @@ namespace dsmr {
static const char *const TAG = "dsmr"; static const char *const TAG = "dsmr";
void Dsmr::loop() { void Dsmr::setup() {
if (this->decryption_key_.empty()) this->telegram_ = new char[this->max_telegram_len_]; // NOLINT
this->receive_telegram_(); if (this->request_pin_ != nullptr) {
else this->request_pin_->setup();
this->receive_encrypted_(); }
} }
void Dsmr::loop() {
if (this->ready_to_request_data_()) {
if (this->decryption_key_.empty()) {
this->receive_telegram_();
} else {
this->receive_encrypted_telegram_();
}
}
}
bool Dsmr::ready_to_request_data_() {
// When using a request pin, then wait for the next request interval.
if (this->request_pin_ != nullptr) {
if (!this->requesting_data_ && this->request_interval_reached_()) {
this->start_requesting_data_();
}
}
// Otherwise, sink serial data until next request interval.
else {
if (this->request_interval_reached_()) {
this->start_requesting_data_();
}
if (!this->requesting_data_) {
while (this->available()) {
this->read();
}
}
}
return this->requesting_data_;
}
bool Dsmr::request_interval_reached_() {
if (this->last_request_time_ == 0) {
return true;
}
return millis() - this->last_request_time_ > this->request_interval_;
}
bool Dsmr::receive_timeout_reached_() { return millis() - this->last_read_time_ > this->receive_timeout_; }
bool Dsmr::available_within_timeout_() { bool Dsmr::available_within_timeout_() {
uint8_t tries = READ_TIMEOUT_MS / 5; // Data are available for reading on the UART bus?
while (tries--) { // Then we can start reading right away.
if (this->available()) {
this->last_read_time_ = millis();
return true;
}
// When we're not in the process of reading a telegram, then there is
// no need to actively wait for new data to come in.
if (!header_found_) {
return false;
}
// A telegram is being read. The smart meter might not deliver a telegram
// in one go, but instead send it in chunks with small pauses in between.
// When the UART RX buffer cannot hold a full telegram, then make sure
// that the UART read buffer does not overflow while other components
// perform their work in their loop. Do this by not returning control to
// the main loop, until the read timeout is reached.
if (this->parent_->get_rx_buffer_size() < this->max_telegram_len_) {
while (!this->receive_timeout_reached_()) {
delay(5); delay(5);
if (available()) { if (this->available()) {
this->last_read_time_ = millis();
return true; return true;
} }
} }
}
// No new data has come in during the read timeout? Then stop reading the
// telegram and start waiting for the next one to arrive.
if (this->receive_timeout_reached_()) {
ESP_LOGW(TAG, "Timeout while reading data for telegram");
this->reset_telegram_();
}
return false; return false;
} }
void Dsmr::receive_telegram_() { void Dsmr::start_requesting_data_() {
while (true) { if (!this->requesting_data_) {
if (!available()) { if (this->request_pin_ != nullptr) {
if (!header_found_ || !available_within_timeout_()) { ESP_LOGV(TAG, "Start requesting data from P1 port");
return; this->request_pin_->digital_write(true);
} else {
ESP_LOGV(TAG, "Start reading data from P1 port");
} }
this->requesting_data_ = true;
this->last_request_time_ = millis();
} }
}
const char c = read(); void Dsmr::stop_requesting_data_() {
if (this->requesting_data_) {
if (this->request_pin_ != nullptr) {
ESP_LOGV(TAG, "Stop requesting data from P1 port");
this->request_pin_->digital_write(false);
} else {
ESP_LOGV(TAG, "Stop reading data from P1 port");
}
while (this->available()) {
this->read();
}
this->requesting_data_ = false;
}
}
void Dsmr::reset_telegram_() {
this->header_found_ = false;
this->footer_found_ = false;
this->bytes_read_ = 0;
this->crypt_bytes_read_ = 0;
this->crypt_telegram_len_ = 0;
this->last_read_time_ = 0;
}
void Dsmr::receive_telegram_() {
while (this->available_within_timeout_()) {
const char c = this->read();
// Find a new telegram header, i.e. forward slash. // Find a new telegram header, i.e. forward slash.
if (c == '/') { if (c == '/') {
ESP_LOGV(TAG, "Header of telegram found"); ESP_LOGV(TAG, "Header of telegram found");
header_found_ = true; this->reset_telegram_();
footer_found_ = false; this->header_found_ = true;
telegram_len_ = 0;
} }
if (!header_found_) if (!this->header_found_)
continue; continue;
// Check for buffer overflow. // Check for buffer overflow.
if (telegram_len_ >= MAX_TELEGRAM_LENGTH) { if (this->bytes_read_ >= this->max_telegram_len_) {
header_found_ = false; this->reset_telegram_();
footer_found_ = false; ESP_LOGE(TAG, "Error: telegram larger than buffer (%d bytes)", this->max_telegram_len_);
ESP_LOGE(TAG, "Error: telegram larger than buffer (%d bytes)", MAX_TELEGRAM_LENGTH);
return; return;
} }
// Some v2.2 or v3 meters will send a new value which starts with '(' // Some v2.2 or v3 meters will send a new value which starts with '('
// in a new line while the value belongs to the previous ObisId. For // in a new line, while the value belongs to the previous ObisId. For
// proper parsing remove these new line characters // proper parsing, remove these new line characters.
while (c == '(' && (telegram_[telegram_len_ - 1] == '\n' || telegram_[telegram_len_ - 1] == '\r')) if (c == '(') {
telegram_len_--; while (true) {
auto previous_char = this->telegram_[this->bytes_read_ - 1];
if (previous_char == '\n' || previous_char == '\r') {
this->bytes_read_--;
} else {
break;
}
}
}
// Store the byte in the buffer. // Store the byte in the buffer.
telegram_[telegram_len_] = c; this->telegram_[this->bytes_read_] = c;
telegram_len_++; this->bytes_read_++;
// Check for a footer, i.e. exlamation mark, followed by a hex checksum. // Check for a footer, i.e. exlamation mark, followed by a hex checksum.
if (c == '!') { if (c == '!') {
ESP_LOGV(TAG, "Footer of telegram found"); ESP_LOGV(TAG, "Footer of telegram found");
footer_found_ = true; this->footer_found_ = true;
continue; continue;
} }
// Check for the end of the hex checksum, i.e. a newline. // Check for the end of the hex checksum, i.e. a newline.
if (footer_found_ && c == '\n') { if (this->footer_found_ && c == '\n') {
// Parse the telegram and publish sensor values. // Parse the telegram and publish sensor values.
parse_telegram(); this->parse_telegram();
this->reset_telegram_();
header_found_ = false;
return; return;
} }
} }
} }
void Dsmr::receive_encrypted_() { void Dsmr::receive_encrypted_telegram_() {
// Encrypted buffer while (this->available_within_timeout_()) {
uint8_t buffer[MAX_TELEGRAM_LENGTH]; const char c = this->read();
size_t buffer_length = 0;
size_t packet_size = 0;
while (true) {
if (!available()) {
if (!header_found_) {
return;
}
if (!available_within_timeout_()) {
ESP_LOGW(TAG, "Timeout while reading data for encrypted telegram");
return;
}
}
const char c = read();
// Find a new telegram start byte. // Find a new telegram start byte.
if (!header_found_) { if (!this->header_found_) {
if ((uint8_t) c != 0xDB) { if ((uint8_t) c != 0xDB) {
continue; continue;
} }
ESP_LOGV(TAG, "Start byte 0xDB of encrypted telegram found"); ESP_LOGV(TAG, "Start byte 0xDB of encrypted telegram found");
header_found_ = true; this->reset_telegram_();
this->header_found_ = true;
} }
// Check for buffer overflow. // Check for buffer overflow.
if (buffer_length >= MAX_TELEGRAM_LENGTH) { if (this->crypt_bytes_read_ >= this->max_telegram_len_) {
header_found_ = false; this->reset_telegram_();
ESP_LOGE(TAG, "Error: encrypted telegram larger than buffer (%d bytes)", MAX_TELEGRAM_LENGTH); ESP_LOGE(TAG, "Error: encrypted telegram larger than buffer (%d bytes)", this->max_telegram_len_);
return; return;
} }
buffer[buffer_length++] = c; // Store the byte in the buffer.
this->crypt_telegram_[this->crypt_bytes_read_] = c;
this->crypt_bytes_read_++;
if (packet_size == 0 && buffer_length > 20) { // Read the length of the incoming encrypted telegram.
if (this->crypt_telegram_len_ == 0 && this->crypt_bytes_read_ > 20) {
// Complete header + data bytes // Complete header + data bytes
packet_size = 13 + (buffer[11] << 8 | buffer[12]); this->crypt_telegram_len_ = 13 + (this->crypt_telegram_[11] << 8 | this->crypt_telegram_[12]);
ESP_LOGV(TAG, "Encrypted telegram size: %d bytes", packet_size); ESP_LOGV(TAG, "Encrypted telegram length: %d bytes", this->crypt_telegram_len_);
}
// Check for the end of the encrypted telegram.
if (this->crypt_telegram_len_ == 0 || this->crypt_bytes_read_ != this->crypt_telegram_len_) {
continue;
} }
if (buffer_length == packet_size && packet_size > 0) {
ESP_LOGV(TAG, "End of encrypted telegram found"); ESP_LOGV(TAG, "End of encrypted telegram found");
// Decrypt the encrypted telegram.
GCM<AES128> *gcmaes128{new GCM<AES128>()}; GCM<AES128> *gcmaes128{new GCM<AES128>()};
gcmaes128->setKey(this->decryption_key_.data(), gcmaes128->keySize()); gcmaes128->setKey(this->decryption_key_.data(), gcmaes128->keySize());
// the iv is 8 bytes of the system title + 4 bytes frame counter // the iv is 8 bytes of the system title + 4 bytes frame counter
// system title is at byte 2 and frame counter at byte 15 // system title is at byte 2 and frame counter at byte 15
for (int i = 10; i < 14; i++) for (int i = 10; i < 14; i++)
buffer[i] = buffer[i + 4]; this->crypt_telegram_[i] = this->crypt_telegram_[i + 4];
constexpr uint16_t iv_size{12}; constexpr uint16_t iv_size{12};
gcmaes128->setIV(&buffer[2], iv_size); gcmaes128->setIV(&this->crypt_telegram_[2], iv_size);
gcmaes128->decrypt(reinterpret_cast<uint8_t *>(this->telegram_), gcmaes128->decrypt(reinterpret_cast<uint8_t *>(this->telegram_),
// the ciphertext start at byte 18 // the ciphertext start at byte 18
&buffer[18], &this->crypt_telegram_[18],
// cipher size // cipher size
buffer_length - 17); this->crypt_bytes_read_ - 17);
delete gcmaes128; // NOLINT(cppcoreguidelines-owning-memory) delete gcmaes128; // NOLINT(cppcoreguidelines-owning-memory)
telegram_len_ = strnlen(this->telegram_, sizeof(this->telegram_)); this->bytes_read_ = strnlen(this->telegram_, this->max_telegram_len_);
ESP_LOGV(TAG, "Decrypted telegram size: %d bytes", telegram_len_); ESP_LOGV(TAG, "Decrypted telegram size: %d bytes", this->bytes_read_);
ESP_LOGVV(TAG, "Decrypted telegram: %s", this->telegram_); ESP_LOGVV(TAG, "Decrypted telegram: %s", this->telegram_);
parse_telegram(); // Parse the decrypted telegram and publish sensor values.
this->parse_telegram();
header_found_ = false; this->reset_telegram_();
telegram_len_ = 0;
return; return;
} }
}
} }
bool Dsmr::parse_telegram() { bool Dsmr::parse_telegram() {
MyData data; MyData data;
ESP_LOGV(TAG, "Trying to parse telegram"); ESP_LOGV(TAG, "Trying to parse telegram");
this->stop_requesting_data_();
::dsmr::ParseResult<void> res = ::dsmr::ParseResult<void> res =
::dsmr::P1Parser::parse(&data, telegram_, telegram_len_, false, ::dsmr::P1Parser::parse(&data, this->telegram_, this->bytes_read_, false,
this->crc_check_); // Parse telegram according to data definition. Ignore unknown values. this->crc_check_); // Parse telegram according to data definition. Ignore unknown values.
if (res.err) { if (res.err) {
// Parsing error, show it // Parsing error, show it
auto err_str = res.fullError(telegram_, telegram_ + telegram_len_); auto err_str = res.fullError(this->telegram_, this->telegram_ + this->bytes_read_);
ESP_LOGE(TAG, "%s", err_str.c_str()); ESP_LOGE(TAG, "%s", err_str.c_str());
return false; return false;
} else { } else {
this->status_clear_warning(); this->status_clear_warning();
publish_sensors(data); this->publish_sensors(data);
return true; return true;
} }
} }
void Dsmr::dump_config() { void Dsmr::dump_config() {
ESP_LOGCONFIG(TAG, "DSMR:"); ESP_LOGCONFIG(TAG, "DSMR:");
ESP_LOGCONFIG(TAG, " Max telegram length: %d", this->max_telegram_len_);
ESP_LOGCONFIG(TAG, " Receive timeout: %.1fs", this->receive_timeout_ / 1e3f);
if (this->request_pin_ != nullptr) {
LOG_PIN(" Request Pin: ", this->request_pin_);
}
if (this->request_interval_ > 0) {
ESP_LOGCONFIG(TAG, " Request Interval: %.1fs", this->request_interval_ / 1e3f);
}
#define DSMR_LOG_SENSOR(s) LOG_SENSOR(" ", #s, this->s_##s##_); #define DSMR_LOG_SENSOR(s) LOG_SENSOR(" ", #s, this->s_##s##_);
DSMR_SENSOR_LIST(DSMR_LOG_SENSOR, ) DSMR_SENSOR_LIST(DSMR_LOG_SENSOR, )
@ -189,6 +293,10 @@ void Dsmr::set_decryption_key(const std::string &decryption_key) {
if (decryption_key.length() == 0) { if (decryption_key.length() == 0) {
ESP_LOGI(TAG, "Disabling decryption"); ESP_LOGI(TAG, "Disabling decryption");
this->decryption_key_.clear(); this->decryption_key_.clear();
if (this->crypt_telegram_ != nullptr) {
delete[] this->crypt_telegram_;
this->crypt_telegram_ = nullptr;
}
return; return;
} }
@ -205,7 +313,11 @@ void Dsmr::set_decryption_key(const std::string &decryption_key) {
char temp[3] = {0}; char temp[3] = {0};
for (int i = 0; i < 16; i++) { for (int i = 0; i < 16; i++) {
strncpy(temp, &(decryption_key.c_str()[i * 2]), 2); strncpy(temp, &(decryption_key.c_str()[i * 2]), 2);
decryption_key_.push_back(std::strtoul(temp, nullptr, 16)); this->decryption_key_.push_back(std::strtoul(temp, nullptr, 16));
}
if (this->crypt_telegram_ == nullptr) {
this->crypt_telegram_ = new uint8_t[this->max_telegram_len_]; // NOLINT
} }
} }

View file

@ -16,9 +16,6 @@
namespace esphome { namespace esphome {
namespace dsmr { namespace dsmr {
static constexpr uint32_t MAX_TELEGRAM_LENGTH = 1500;
static constexpr uint32_t READ_TIMEOUT_MS = 200;
using namespace ::dsmr::fields; using namespace ::dsmr::fields;
// DSMR_**_LIST generated by ESPHome and written in esphome/core/defines // DSMR_**_LIST generated by ESPHome and written in esphome/core/defines
@ -52,6 +49,7 @@ class Dsmr : public Component, public uart::UARTDevice {
public: public:
Dsmr(uart::UARTComponent *uart, bool crc_check) : uart::UARTDevice(uart), crc_check_(crc_check) {} Dsmr(uart::UARTComponent *uart, bool crc_check) : uart::UARTDevice(uart), crc_check_(crc_check) {}
void setup() override;
void loop() override; void loop() override;
bool parse_telegram(); bool parse_telegram();
@ -71,6 +69,10 @@ class Dsmr : public Component, public uart::UARTDevice {
void dump_config() override; void dump_config() override;
void set_decryption_key(const std::string &decryption_key); void set_decryption_key(const std::string &decryption_key);
void set_max_telegram_length(size_t length) { this->max_telegram_len_ = length; }
void set_request_pin(GPIOPin *request_pin) { this->request_pin_ = request_pin; }
void set_request_interval(uint32_t interval) { this->request_interval_ = interval; }
void set_receive_timeout(uint32_t timeout) { this->receive_timeout_ = timeout; }
// Sensor setters // Sensor setters
#define DSMR_SET_SENSOR(s) \ #define DSMR_SET_SENSOR(s) \
@ -83,7 +85,8 @@ class Dsmr : public Component, public uart::UARTDevice {
protected: protected:
void receive_telegram_(); void receive_telegram_();
void receive_encrypted_(); void receive_encrypted_telegram_();
void reset_telegram_();
/// Wait for UART data to become available within the read timeout. /// Wait for UART data to become available within the read timeout.
/// ///
@ -96,11 +99,26 @@ class Dsmr : public Component, public uart::UARTDevice {
/// lost in the process. /// lost in the process.
bool available_within_timeout_(); bool available_within_timeout_();
// Telegram buffer // Request telegram
char telegram_[MAX_TELEGRAM_LENGTH]; uint32_t request_interval_;
int telegram_len_{0}; bool request_interval_reached_();
GPIOPin *request_pin_{nullptr};
uint32_t last_request_time_{0};
bool requesting_data_{false};
bool ready_to_request_data_();
void start_requesting_data_();
void stop_requesting_data_();
// Serial parser // Read telegram
uint32_t receive_timeout_;
bool receive_timeout_reached_();
size_t max_telegram_len_;
char *telegram_{nullptr};
size_t bytes_read_{0};
uint8_t *crypt_telegram_{nullptr};
size_t crypt_telegram_len_{0};
size_t crypt_bytes_read_{0};
uint32_t last_read_time_{0};
bool header_found_{false}; bool header_found_{false};
bool footer_found_{false}; bool footer_found_{false};

View file

@ -311,9 +311,16 @@ async def to_code(config):
) )
add_idf_sdkconfig_option("CONFIG_COMPILER_OPTIMIZATION_DEFAULT", False) add_idf_sdkconfig_option("CONFIG_COMPILER_OPTIMIZATION_DEFAULT", False)
add_idf_sdkconfig_option("CONFIG_COMPILER_OPTIMIZATION_SIZE", True) add_idf_sdkconfig_option("CONFIG_COMPILER_OPTIMIZATION_SIZE", True)
# Increase freertos tick speed from 100Hz to 1kHz so that delay() resolution is 1ms # Increase freertos tick speed from 100Hz to 1kHz so that delay() resolution is 1ms
add_idf_sdkconfig_option("CONFIG_FREERTOS_HZ", 1000) add_idf_sdkconfig_option("CONFIG_FREERTOS_HZ", 1000)
# Setup watchdog
add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT", True)
add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT_PANIC", True)
add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0", False)
add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1", False)
cg.add_platformio_option("board_build.partitions", "partitions.csv") cg.add_platformio_option("board_build.partitions", "partitions.csv")
for name, value in conf[CONF_SDKCONFIG_OPTIONS].items(): for name, value in conf[CONF_SDKCONFIG_OPTIONS].items():

View file

@ -6,12 +6,17 @@
#include <freertos/FreeRTOS.h> #include <freertos/FreeRTOS.h>
#include <freertos/task.h> #include <freertos/task.h>
#include <esp_idf_version.h> #include <esp_idf_version.h>
#include <esp_task_wdt.h>
#include <soc/rtc.h> #include <soc/rtc.h>
#if ESP_IDF_VERSION_MAJOR >= 4 #if ESP_IDF_VERSION_MAJOR >= 4
#include <hal/cpu_hal.h> #include <hal/cpu_hal.h>
#endif #endif
#ifdef USE_ARDUINO
#include <esp32-hal.h>
#endif
void setup(); void setup();
void loop(); void loop();
@ -29,24 +34,24 @@ void arch_restart() {
yield(); yield();
} }
} }
void IRAM_ATTR HOT arch_feed_wdt() {
#ifdef USE_ARDUINO
#if CONFIG_ARDUINO_RUNNING_CORE == 0
#ifdef CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0
// ESP32 uses "Task Watchdog" which is hooked to the FreeRTOS idle task.
// To cause the Watchdog to be triggered we need to put the current task
// to sleep to get the idle task scheduled.
delay(1);
#endif
#endif
#endif // USE_ARDUINO
#ifdef USE_ESP_IDF void arch_init() {
#ifdef CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0 // Enable the task watchdog only on the loop task (from which we're currently running)
delay(1); #if defined(USE_ESP_IDF)
esp_task_wdt_add(nullptr);
// Idle task watchdog is disabled on ESP-IDF
#elif defined(USE_ARDUINO)
enableLoopWDT();
// Disable idle task watchdog on the core we're using (Arduino pins the process to a core)
#if CONFIG_ARDUINO_RUNNING_CORE == 0
disableCore0WDT();
#endif
#if CONFIG_ARDUINO_RUNNING_CORE == 1
disableCore1WDT();
#endif
#endif #endif
#endif // USE_ESP_IDF
} }
void IRAM_ATTR HOT arch_feed_wdt() { esp_task_wdt_reset(); }
uint8_t progmem_read_byte(const uint8_t *addr) { return *addr; } uint8_t progmem_read_byte(const uint8_t *addr) { return *addr; }
uint32_t arch_get_cpu_cycle_count() { uint32_t arch_get_cpu_cycle_count() {

View file

@ -4,7 +4,7 @@ from esphome.components import binary_sensor, output, esp32_ble_server
from esphome.const import CONF_ID from esphome.const import CONF_ID
AUTO_LOAD = ["binary_sensor", "output", "improv", "esp32_ble_server"] AUTO_LOAD = ["binary_sensor", "output", "esp32_ble_server"]
CODEOWNERS = ["@jesserockz"] CODEOWNERS = ["@jesserockz"]
CONFLICTS_WITH = ["esp32_ble_tracker", "esp32_ble_beacon"] CONFLICTS_WITH = ["esp32_ble_tracker", "esp32_ble_beacon"]
DEPENDENCIES = ["wifi", "esp32"] DEPENDENCIES = ["wifi", "esp32"]
@ -56,6 +56,7 @@ async def to_code(config):
cg.add(ble_server.register_service_component(var)) cg.add(ble_server.register_service_component(var))
cg.add_define("USE_IMPROV") cg.add_define("USE_IMPROV")
cg.add_library("esphome/Improv", "1.0.0")
cg.add(var.set_identify_duration(config[CONF_IDENTIFY_DURATION])) cg.add(var.set_identify_duration(config[CONF_IDENTIFY_DURATION]))
cg.add(var.set_authorized_duration(config[CONF_AUTHORIZED_DURATION])) cg.add(var.set_authorized_duration(config[CONF_AUTHORIZED_DURATION]))

View file

@ -1,9 +1,8 @@
#pragma once #pragma once
#include "esphome/components/binary_sensor/binary_sensor.h" #include "esphome/components/binary_sensor/binary_sensor.h"
#include "esphome/components/esp32_ble_server/ble_server.h"
#include "esphome/components/esp32_ble_server/ble_characteristic.h" #include "esphome/components/esp32_ble_server/ble_characteristic.h"
#include "esphome/components/improv/improv.h" #include "esphome/components/esp32_ble_server/ble_server.h"
#include "esphome/components/output/binary_output.h" #include "esphome/components/output/binary_output.h"
#include "esphome/components/wifi/wifi_component.h" #include "esphome/components/wifi/wifi_component.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
@ -12,6 +11,8 @@
#ifdef USE_ESP32 #ifdef USE_ESP32
#include <improv.h>
namespace esphome { namespace esphome {
namespace esp32_improv { namespace esp32_improv {

View file

@ -206,61 +206,3 @@ ESP8266_BOARD_PINS = {
"wio_node": {"LED": 2, "GROVE": 15, "D0": 3, "D1": 5, "BUTTON": 0}, "wio_node": {"LED": 2, "GROVE": 15, "D0": 3, "D1": 5, "BUTTON": 0},
"xinabox_cw01": {"SDA": 2, "SCL": 14, "LED": 5, "LED_RED": 12, "LED_GREEN": 13}, "xinabox_cw01": {"SDA": 2, "SCL": 14, "LED": 5, "LED_RED": 12, "LED_GREEN": 13},
} }
FLASH_SIZE_1_MB = 2 ** 20
FLASH_SIZE_512_KB = FLASH_SIZE_1_MB // 2
FLASH_SIZE_2_MB = 2 * FLASH_SIZE_1_MB
FLASH_SIZE_4_MB = 4 * FLASH_SIZE_1_MB
FLASH_SIZE_16_MB = 16 * FLASH_SIZE_1_MB
ESP8266_FLASH_SIZES = {
"d1": FLASH_SIZE_4_MB,
"d1_mini": FLASH_SIZE_4_MB,
"d1_mini_lite": FLASH_SIZE_1_MB,
"d1_mini_pro": FLASH_SIZE_16_MB,
"esp01": FLASH_SIZE_512_KB,
"esp01_1m": FLASH_SIZE_1_MB,
"esp07": FLASH_SIZE_4_MB,
"esp12e": FLASH_SIZE_4_MB,
"esp210": FLASH_SIZE_4_MB,
"esp8285": FLASH_SIZE_1_MB,
"esp_wroom_02": FLASH_SIZE_2_MB,
"espduino": FLASH_SIZE_4_MB,
"espectro": FLASH_SIZE_4_MB,
"espino": FLASH_SIZE_4_MB,
"espinotee": FLASH_SIZE_4_MB,
"espmxdevkit": FLASH_SIZE_1_MB,
"espresso_lite_v1": FLASH_SIZE_4_MB,
"espresso_lite_v2": FLASH_SIZE_4_MB,
"gen4iod": FLASH_SIZE_512_KB,
"heltec_wifi_kit_8": FLASH_SIZE_4_MB,
"huzzah": FLASH_SIZE_4_MB,
"inventone": FLASH_SIZE_4_MB,
"modwifi": FLASH_SIZE_2_MB,
"nodemcu": FLASH_SIZE_4_MB,
"nodemcuv2": FLASH_SIZE_4_MB,
"oak": FLASH_SIZE_4_MB,
"phoenix_v1": FLASH_SIZE_4_MB,
"phoenix_v2": FLASH_SIZE_4_MB,
"sonoff_basic": FLASH_SIZE_1_MB,
"sonoff_s20": FLASH_SIZE_1_MB,
"sonoff_sv": FLASH_SIZE_1_MB,
"sonoff_th": FLASH_SIZE_1_MB,
"sparkfunBlynk": FLASH_SIZE_4_MB,
"thing": FLASH_SIZE_512_KB,
"thingdev": FLASH_SIZE_512_KB,
"wifi_slot": FLASH_SIZE_1_MB,
"wifiduino": FLASH_SIZE_4_MB,
"wifinfo": FLASH_SIZE_1_MB,
"wio_link": FLASH_SIZE_4_MB,
"wio_node": FLASH_SIZE_4_MB,
"xinabox_cw01": FLASH_SIZE_4_MB,
}
ESP8266_LD_SCRIPTS = {
FLASH_SIZE_512_KB: ("eagle.flash.512k0.ld", "eagle.flash.512k.ld"),
FLASH_SIZE_1_MB: ("eagle.flash.1m0.ld", "eagle.flash.1m.ld"),
FLASH_SIZE_2_MB: ("eagle.flash.2m.ld", "eagle.flash.2m.ld"),
FLASH_SIZE_4_MB: ("eagle.flash.4m.ld", "eagle.flash.4m.ld"),
FLASH_SIZE_16_MB: ("eagle.flash.16m.ld", "eagle.flash.16m14m.ld"),
}

View file

@ -20,6 +20,7 @@ void arch_restart() {
yield(); yield();
} }
} }
void arch_init() {}
void IRAM_ATTR HOT arch_feed_wdt() { void IRAM_ATTR HOT arch_feed_wdt() {
ESP.wdtFeed(); // NOLINT(readability-static-accessed-through-instance) ESP.wdtFeed(); // NOLINT(readability-static-accessed-through-instance)
} }
@ -27,7 +28,7 @@ void IRAM_ATTR HOT arch_feed_wdt() {
uint8_t progmem_read_byte(const uint8_t *addr) { uint8_t progmem_read_byte(const uint8_t *addr) {
return pgm_read_byte(addr); // NOLINT return pgm_read_byte(addr); // NOLINT
} }
uint32_t arch_get_cpu_cycle_count() { uint32_t IRAM_ATTR HOT arch_get_cpu_cycle_count() {
return ESP.getCycleCount(); // NOLINT(readability-static-accessed-through-instance) return ESP.getCycleCount(); // NOLINT(readability-static-accessed-through-instance)
} }
uint32_t arch_get_cpu_freq_hz() { return F_CPU; } uint32_t arch_get_cpu_freq_hz() { return F_CPU; }

View file

@ -9,7 +9,7 @@ namespace esp8266 {
static const char *const TAG = "esp8266"; static const char *const TAG = "esp8266";
static int IRAM_ATTR flags_to_mode(gpio::Flags flags, uint8_t pin) { static int IRAM_ATTR flags_to_mode(gpio::Flags flags, uint8_t pin) {
if (flags == gpio::FLAG_INPUT) { if (flags == gpio::FLAG_INPUT) { // NOLINT(bugprone-branch-clone)
return INPUT; return INPUT;
} else if (flags == gpio::FLAG_OUTPUT) { } else if (flags == gpio::FLAG_OUTPUT) {
return OUTPUT; return OUTPUT;

View file

@ -55,7 +55,7 @@ static inline bool esp_rtc_user_mem_write(uint32_t index, uint32_t value) {
extern "C" uint32_t _SPIFFS_end; // NOLINT extern "C" uint32_t _SPIFFS_end; // NOLINT
static const uint32_t get_esp8266_flash_sector() { static uint32_t get_esp8266_flash_sector() {
union { union {
uint32_t *ptr; uint32_t *ptr;
uint32_t uint; uint32_t uint;
@ -63,7 +63,7 @@ static const uint32_t get_esp8266_flash_sector() {
data.ptr = &_SPIFFS_end; data.ptr = &_SPIFFS_end;
return (data.uint - 0x40200000) / SPI_FLASH_SEC_SIZE; return (data.uint - 0x40200000) / SPI_FLASH_SEC_SIZE;
} }
static const uint32_t get_esp8266_flash_address() { return get_esp8266_flash_sector() * SPI_FLASH_SEC_SIZE; } static uint32_t get_esp8266_flash_address() { return get_esp8266_flash_sector() * SPI_FLASH_SEC_SIZE; }
template<class It> uint32_t calculate_crc(It first, It last, uint32_t type) { template<class It> uint32_t calculate_crc(It first, It last, uint32_t type) {
uint32_t crc = type; uint32_t crc = type;

View file

@ -12,6 +12,8 @@ from esphome.const import (
CONF_TYPE, CONF_TYPE,
CONF_EXTERNAL_COMPONENTS, CONF_EXTERNAL_COMPONENTS,
CONF_PATH, CONF_PATH,
CONF_USERNAME,
CONF_PASSWORD,
) )
from esphome.core import CORE from esphome.core import CORE
from esphome import git, loader from esphome import git, loader
@ -27,6 +29,8 @@ TYPE_LOCAL = "local"
GIT_SCHEMA = { GIT_SCHEMA = {
cv.Required(CONF_URL): cv.url, cv.Required(CONF_URL): cv.url,
cv.Optional(CONF_REF): cv.git_ref, cv.Optional(CONF_REF): cv.git_ref,
cv.Optional(CONF_USERNAME): cv.string,
cv.Optional(CONF_PASSWORD): cv.string,
} }
LOCAL_SCHEMA = { LOCAL_SCHEMA = {
cv.Required(CONF_PATH): cv.directory, cv.Required(CONF_PATH): cv.directory,
@ -99,6 +103,8 @@ def _process_git_config(config: dict, refresh) -> str:
ref=config.get(CONF_REF), ref=config.get(CONF_REF),
refresh=refresh, refresh=refresh,
domain=DOMAIN, domain=DOMAIN,
username=config.get(CONF_USERNAME),
password=config.get(CONF_PASSWORD),
) )
if (repo_dir / "esphome" / "components").is_dir(): if (repo_dir / "esphome" / "components").is_dir():

View file

@ -75,7 +75,7 @@ void EZOSensor::loop() {
return; return;
// some sensors return multiple comma-separated values, terminate string after first one // some sensors return multiple comma-separated values, terminate string after first one
for (int i = 1; i < sizeof(buf) - 1; i++) for (size_t i = 1; i < sizeof(buf) - 1; i++)
if (buf[i] == ',') if (buf[i] == ',')
buf[i] = '\0'; buf[i] = '\0';

View file

@ -86,7 +86,7 @@ void Graph::draw(DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Colo
// Look back in trace data to best-fit into local range // Look back in trace data to best-fit into local range
float mx = NAN; float mx = NAN;
float mn = NAN; float mn = NAN;
for (int16_t i = 0; i < this->width_; i++) { for (uint32_t i = 0; i < this->width_; i++) {
for (auto *trace : traces_) { for (auto *trace : traces_) {
float v = trace->get_tracedata()->get_value(i); float v = trace->get_tracedata()->get_value(i);
if (!std::isnan(v)) { if (!std::isnan(v)) {
@ -132,7 +132,7 @@ void Graph::draw(DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Colo
if (!std::isnan(this->gridspacing_y_)) { if (!std::isnan(this->gridspacing_y_)) {
for (int y = yn; y <= ym; y++) { for (int y = yn; y <= ym; y++) {
int16_t py = (int16_t) roundf((this->height_ - 1) * (1.0 - (float) (y - yn) / (ym - yn))); int16_t py = (int16_t) roundf((this->height_ - 1) * (1.0 - (float) (y - yn) / (ym - yn)));
for (int x = 0; x < this->width_; x += 2) { for (uint32_t x = 0; x < this->width_; x += 2) {
buff->draw_pixel_at(x_offset + x, y_offset + py, color); buff->draw_pixel_at(x_offset + x, y_offset + py, color);
} }
} }
@ -147,7 +147,7 @@ void Graph::draw(DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Colo
ESP_LOGW(TAG, "Graphing reducing x-scale to prevent too many gridlines"); ESP_LOGW(TAG, "Graphing reducing x-scale to prevent too many gridlines");
} }
for (int i = 0; i <= n; i++) { for (int i = 0; i <= n; i++) {
for (int y = 0; y < this->height_; y += 2) { for (uint32_t y = 0; y < this->height_; y += 2) {
buff->draw_pixel_at(x_offset + i * (this->width_ - 1) / n, y_offset + y, color); buff->draw_pixel_at(x_offset + i * (this->width_ - 1) / n, y_offset + y, color);
} }
} }
@ -158,14 +158,14 @@ void Graph::draw(DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Colo
for (auto *trace : traces_) { for (auto *trace : traces_) {
Color c = trace->get_line_color(); Color c = trace->get_line_color();
uint16_t thick = trace->get_line_thickness(); uint16_t thick = trace->get_line_thickness();
for (int16_t i = 0; i < this->width_; i++) { for (uint32_t i = 0; i < this->width_; i++) {
float v = (trace->get_tracedata()->get_value(i) - ymin) / yrange; float v = (trace->get_tracedata()->get_value(i) - ymin) / yrange;
if (!std::isnan(v) && (thick > 0)) { if (!std::isnan(v) && (thick > 0)) {
int16_t x = this->width_ - 1 - i; int16_t x = this->width_ - 1 - i;
uint8_t b = (i % (thick * LineType::PATTERN_LENGTH)) / thick; uint8_t b = (i % (thick * LineType::PATTERN_LENGTH)) / thick;
if (((uint8_t) trace->get_line_type() & (1 << b)) == (1 << b)) { if (((uint8_t) trace->get_line_type() & (1 << b)) == (1 << b)) {
int16_t y = (int16_t) roundf((this->height_ - 1) * (1.0 - v)) - thick / 2; int16_t y = (int16_t) roundf((this->height_ - 1) * (1.0 - v)) - thick / 2;
for (int16_t t = 0; t < thick; t++) { for (uint16_t t = 0; t < thick; t++) {
buff->draw_pixel_at(x_offset + x, y_offset + y + t, c); buff->draw_pixel_at(x_offset + x, y_offset + y + t, c);
} }
} }
@ -179,8 +179,8 @@ void GraphLegend::init(Graph *g) {
parent_ = g; parent_ = g;
// Determine maximum expected text and value width / height // Determine maximum expected text and value width / height
int txtw = 0, txtos = 0, txtbl = 0, txth = 0; int txtw = 0, txth = 0;
int valw = 0, valos = 0, valbl = 0, valh = 0; int valw = 0, valh = 0;
int lt = 0; int lt = 0;
for (auto *trace : g->traces_) { for (auto *trace : g->traces_) {
std::string txtstr = trace->get_name(); std::string txtstr = trace->get_name();
@ -320,7 +320,7 @@ void Graph::draw_legend(display::DisplayBuffer *buff, uint16_t x_offset, uint16_
if (legend_->lines_) { if (legend_->lines_) {
uint16_t thick = trace->get_line_thickness(); uint16_t thick = trace->get_line_thickness();
for (int16_t i = 0; i < legend_->x0_ * 4 / 3; i++) { for (int i = 0; i < legend_->x0_ * 4 / 3; i++) {
uint8_t b = (i % (thick * LineType::PATTERN_LENGTH)) / thick; uint8_t b = (i % (thick * LineType::PATTERN_LENGTH)) / thick;
if (((uint8_t) trace->get_line_type() & (1 << b)) == (1 << b)) { if (((uint8_t) trace->get_line_type() & (1 << b)) == (1 << b)) {
buff->vertical_line(x - legend_->x0_ * 2 / 3 + i, y + legend_->yl_ - thick / 2, thick, buff->vertical_line(x - legend_->x0_ * 2 / 3 + i, y + legend_->yl_ - thick / 2, thick,

View file

@ -30,6 +30,7 @@ PROTOCOLS = {
"gree": Protocol.PROTOCOL_GREE, "gree": Protocol.PROTOCOL_GREE,
"greeya": Protocol.PROTOCOL_GREEYAA, "greeya": Protocol.PROTOCOL_GREEYAA,
"greeyan": Protocol.PROTOCOL_GREEYAN, "greeyan": Protocol.PROTOCOL_GREEYAN,
"greeyac": Protocol.PROTOCOL_GREEYAC,
"hisense_aud": Protocol.PROTOCOL_HISENSE_AUD, "hisense_aud": Protocol.PROTOCOL_HISENSE_AUD,
"hitachi": Protocol.PROTOCOL_HITACHI, "hitachi": Protocol.PROTOCOL_HITACHI,
"hyundai": Protocol.PROTOCOL_HYUNDAI, "hyundai": Protocol.PROTOCOL_HYUNDAI,
@ -111,4 +112,6 @@ def to_code(config):
cg.add(var.set_max_temperature(config[CONF_MIN_TEMPERATURE])) cg.add(var.set_max_temperature(config[CONF_MIN_TEMPERATURE]))
cg.add(var.set_min_temperature(config[CONF_MAX_TEMPERATURE])) cg.add(var.set_min_temperature(config[CONF_MAX_TEMPERATURE]))
cg.add_library("tonia/HeatpumpIR", "1.0.15") # PIO isn't updating releases, so referencing the release tag directly. See:
# https://github.com/ToniA/arduino-heatpumpir/commit/0948c619d86407a4e50e8db2f3c193e0576c86fd
cg.add_library("", "", "https://github.com/ToniA/arduino-heatpumpir.git#1.0.18")

View file

@ -25,6 +25,7 @@ const std::map<Protocol, std::function<HeatpumpIR *()>> PROTOCOL_CONSTRUCTOR_MAP
{PROTOCOL_GREE, []() { return new GreeGenericHeatpumpIR(); }}, // NOLINT {PROTOCOL_GREE, []() { return new GreeGenericHeatpumpIR(); }}, // NOLINT
{PROTOCOL_GREEYAA, []() { return new GreeYAAHeatpumpIR(); }}, // NOLINT {PROTOCOL_GREEYAA, []() { return new GreeYAAHeatpumpIR(); }}, // NOLINT
{PROTOCOL_GREEYAN, []() { return new GreeYANHeatpumpIR(); }}, // NOLINT {PROTOCOL_GREEYAN, []() { return new GreeYANHeatpumpIR(); }}, // NOLINT
{PROTOCOL_GREEYAC, []() { return new GreeYACHeatpumpIR(); }}, // NOLINT
{PROTOCOL_HISENSE_AUD, []() { return new HisenseHeatpumpIR(); }}, // NOLINT {PROTOCOL_HISENSE_AUD, []() { return new HisenseHeatpumpIR(); }}, // NOLINT
{PROTOCOL_HITACHI, []() { return new HitachiHeatpumpIR(); }}, // NOLINT {PROTOCOL_HITACHI, []() { return new HitachiHeatpumpIR(); }}, // NOLINT
{PROTOCOL_HYUNDAI, []() { return new HyundaiHeatpumpIR(); }}, // NOLINT {PROTOCOL_HYUNDAI, []() { return new HyundaiHeatpumpIR(); }}, // NOLINT
@ -61,6 +62,19 @@ void HeatpumpIRClimate::setup() {
} }
this->heatpump_ir_ = protocol_constructor->second(); this->heatpump_ir_ = protocol_constructor->second();
climate_ir::ClimateIR::setup(); climate_ir::ClimateIR::setup();
if (this->sensor_) {
this->sensor_->add_on_state_callback([this](float state) {
this->current_temperature = state;
IRSenderESPHome esp_sender(this->transmitter_);
this->heatpump_ir_->send(esp_sender, uint8_t(lround(this->current_temperature + 0.5)));
// current temperature changed, publish state
this->publish_state();
});
this->current_temperature = this->sensor_->state;
} else
this->current_temperature = NAN;
} }
void HeatpumpIRClimate::transmit_state() { void HeatpumpIRClimate::transmit_state() {
@ -171,8 +185,7 @@ void HeatpumpIRClimate::transmit_state() {
temperature_cmd = (uint8_t) clamp(this->target_temperature, this->min_temperature_, this->max_temperature_); temperature_cmd = (uint8_t) clamp(this->target_temperature, this->min_temperature_, this->max_temperature_);
IRSenderESPHome esp_sender(0, this->transmitter_); IRSenderESPHome esp_sender(this->transmitter_);
heatpump_ir_->send(esp_sender, power_mode_cmd, operating_mode_cmd, fan_speed_cmd, temperature_cmd, swing_v_cmd, heatpump_ir_->send(esp_sender, power_mode_cmd, operating_mode_cmd, fan_speed_cmd, temperature_cmd, swing_v_cmd,
swing_h_cmd); swing_h_cmd);
} }

View file

@ -25,6 +25,7 @@ enum Protocol {
PROTOCOL_GREE, PROTOCOL_GREE,
PROTOCOL_GREEYAA, PROTOCOL_GREEYAA,
PROTOCOL_GREEYAN, PROTOCOL_GREEYAN,
PROTOCOL_GREEYAC,
PROTOCOL_HISENSE_AUD, PROTOCOL_HISENSE_AUD,
PROTOCOL_HITACHI, PROTOCOL_HITACHI,
PROTOCOL_HYUNDAI, PROTOCOL_HYUNDAI,

View file

@ -11,8 +11,8 @@ namespace heatpumpir {
class IRSenderESPHome : public IRSender { class IRSenderESPHome : public IRSender {
public: public:
IRSenderESPHome(uint8_t pin, remote_transmitter::RemoteTransmitterComponent *transmitter) IRSenderESPHome(remote_transmitter::RemoteTransmitterComponent *transmitter)
: IRSender(pin), transmit_(transmitter->transmit()){}; : IRSender(0), transmit_(transmitter->transmit()){};
void setFrequency(int frequency) override; // NOLINT(readability-identifier-naming) void setFrequency(int frequency) override; // NOLINT(readability-identifier-naming)
void space(int space_length) override; void space(int space_length) override;
void mark(int mark_length) override; void mark(int mark_length) override;

View file

@ -299,9 +299,7 @@ bool HitachiClimate::parse_swing_(const uint8_t remote_state[]) {
GETBITS8(remote_state[HITACHI_AC344_SWINGH_BYTE], HITACHI_AC344_SWINGH_OFFSET, HITACHI_AC344_SWINGH_SIZE); GETBITS8(remote_state[HITACHI_AC344_SWINGH_BYTE], HITACHI_AC344_SWINGH_OFFSET, HITACHI_AC344_SWINGH_SIZE);
ESP_LOGV(TAG, "SwingH: %02X %02X", remote_state[HITACHI_AC344_SWINGH_BYTE], swing_modeh); ESP_LOGV(TAG, "SwingH: %02X %02X", remote_state[HITACHI_AC344_SWINGH_BYTE], swing_modeh);
if ((swing_modeh & 0x7) == 0x0) { if ((swing_modeh & 0x3) == 0x3) {
this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL;
} else if ((swing_modeh & 0x3) == 0x3) {
this->swing_mode = climate::CLIMATE_SWING_OFF; this->swing_mode = climate::CLIMATE_SWING_OFF;
} else { } else {
this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL; this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL;

View file

@ -300,9 +300,7 @@ bool HitachiClimate::parse_swing_(const uint8_t remote_state[]) {
HITACHI_AC424_SWINGH_SIZE); HITACHI_AC424_SWINGH_SIZE);
ESP_LOGV(TAG, "SwingH: %02X %02X", remote_state[HITACHI_AC424_SWINGH_BYTE], swing_modeh); ESP_LOGV(TAG, "SwingH: %02X %02X", remote_state[HITACHI_AC424_SWINGH_BYTE], swing_modeh);
if ((swing_modeh & 0x7) == 0x0) { if ((swing_modeh & 0x3) == 0x3) {
this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL;
} else if ((swing_modeh & 0x3) == 0x3) {
this->swing_mode = climate::CLIMATE_SWING_OFF; this->swing_mode = climate::CLIMATE_SWING_OFF;
} else { } else {
this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL; this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL;

View file

@ -1,6 +1,5 @@
#pragma once #pragma once
#ifdef USE_ARDUINO
#include <cstdint> #include <cstdint>
namespace esphome { namespace esphome {
@ -13,5 +12,3 @@ class AbstractAQICalculator {
} // namespace hm3301 } // namespace hm3301
} // namespace esphome } // namespace esphome
#endif // USE_ARDUINO

View file

@ -1,7 +1,5 @@
#pragma once #pragma once
#ifdef USE_ARDUINO
#include "abstract_aqi_calculator.h" #include "abstract_aqi_calculator.h"
namespace esphome { namespace esphome {
@ -48,5 +46,3 @@ class AQICalculator : public AbstractAQICalculator {
} // namespace hm3301 } // namespace hm3301
} // namespace esphome } // namespace esphome
#endif // USE_ARDUINO

View file

@ -1,7 +1,5 @@
#pragma once #pragma once
#ifdef USE_ARDUINO
#include "caqi_calculator.h" #include "caqi_calculator.h"
#include "aqi_calculator.h" #include "aqi_calculator.h"
@ -29,5 +27,3 @@ class AQICalculatorFactory {
} // namespace hm3301 } // namespace hm3301
} // namespace esphome } // namespace esphome
#endif // USE_ARDUINO

View file

@ -1,7 +1,5 @@
#pragma once #pragma once
#ifdef USE_ARDUINO
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "abstract_aqi_calculator.h" #include "abstract_aqi_calculator.h"
@ -52,5 +50,3 @@ class CAQICalculator : public AbstractAQICalculator {
} // namespace hm3301 } // namespace hm3301
} // namespace esphome } // namespace esphome
#endif // USE_ARDUINO

View file

@ -1,5 +1,3 @@
#ifdef USE_ARDUINO
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "hm3301.h" #include "hm3301.h"
@ -14,9 +12,8 @@ static const uint8_t PM_10_0_VALUE_INDEX = 7;
void HM3301Component::setup() { void HM3301Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up HM3301..."); ESP_LOGCONFIG(TAG, "Setting up HM3301...");
hm3301_ = make_unique<HM330X>(); if (i2c::ERROR_OK != this->write(&SELECT_COMM_CMD, 1)) {
error_code_ = hm3301_->init(); error_code_ = ERROR_COMM;
if (error_code_ != NO_ERROR) {
this->mark_failed(); this->mark_failed();
return; return;
} }
@ -38,7 +35,7 @@ void HM3301Component::dump_config() {
float HM3301Component::get_setup_priority() const { return setup_priority::DATA; } float HM3301Component::get_setup_priority() const { return setup_priority::DATA; }
void HM3301Component::update() { void HM3301Component::update() {
if (!this->read_sensor_value_(data_buffer_)) { if (this->read(data_buffer_, 29) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Read result failed"); ESP_LOGW(TAG, "Read result failed");
this->status_set_warning(); this->status_set_warning();
return; return;
@ -87,8 +84,6 @@ void HM3301Component::update() {
this->status_clear_warning(); this->status_clear_warning();
} }
bool HM3301Component::read_sensor_value_(uint8_t *data) { return !hm3301_->read_sensor_value(data, 29); }
bool HM3301Component::validate_checksum_(const uint8_t *data) { bool HM3301Component::validate_checksum_(const uint8_t *data) {
uint8_t sum = 0; uint8_t sum = 0;
for (int i = 0; i < 28; i++) { for (int i = 0; i < 28; i++) {
@ -104,5 +99,3 @@ uint16_t HM3301Component::get_sensor_value_(const uint8_t *data, uint8_t i) {
} // namespace hm3301 } // namespace hm3301
} // namespace esphome } // namespace esphome
#endif // USE_ARDUINO

View file

@ -1,17 +1,15 @@
#pragma once #pragma once
#ifdef USE_ARDUINO
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h" #include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h" #include "esphome/components/i2c/i2c.h"
#include "aqi_calculator_factory.h" #include "aqi_calculator_factory.h"
#include <Seeed_HM330X.h>
namespace esphome { namespace esphome {
namespace hm3301 { namespace hm3301 {
static const uint8_t SELECT_COMM_CMD = 0X88;
class HM3301Component : public PollingComponent, public i2c::I2CDevice { class HM3301Component : public PollingComponent, public i2c::I2CDevice {
public: public:
HM3301Component() = default; HM3301Component() = default;
@ -29,9 +27,12 @@ class HM3301Component : public PollingComponent, public i2c::I2CDevice {
void update() override; void update() override;
protected: protected:
std::unique_ptr<HM330X> hm3301_; enum {
NO_ERROR = 0,
HM330XErrorCode error_code_{NO_ERROR}; ERROR_PARAM = -1,
ERROR_COMM = -2,
ERROR_OTHERS = -128,
} error_code_{NO_ERROR};
uint8_t data_buffer_[30]; uint8_t data_buffer_[30];
@ -43,12 +44,9 @@ class HM3301Component : public PollingComponent, public i2c::I2CDevice {
AQICalculatorType aqi_calc_type_; AQICalculatorType aqi_calc_type_;
AQICalculatorFactory aqi_calculator_factory_ = AQICalculatorFactory(); AQICalculatorFactory aqi_calculator_factory_ = AQICalculatorFactory();
bool read_sensor_value_(uint8_t *);
bool validate_checksum_(const uint8_t *); bool validate_checksum_(const uint8_t *);
uint16_t get_sensor_value_(const uint8_t *, uint8_t); uint16_t get_sensor_value_(const uint8_t *, uint8_t);
}; };
} // namespace hm3301 } // namespace hm3301
} // namespace esphome } // namespace esphome
#endif // USE_ARDUINO

View file

@ -84,7 +84,6 @@ CONFIG_SCHEMA = cv.All(
.extend(cv.polling_component_schema("60s")) .extend(cv.polling_component_schema("60s"))
.extend(i2c.i2c_device_schema(0x40)), .extend(i2c.i2c_device_schema(0x40)),
_validate, _validate,
cv.only_with_arduino,
) )
@ -109,6 +108,3 @@ async def to_code(config):
sens = await sensor.new_sensor(config[CONF_AQI]) sens = await sensor.new_sensor(config[CONF_AQI])
cg.add(var.set_aqi_sensor(sens)) cg.add(var.set_aqi_sensor(sens))
cg.add(var.set_aqi_calculation_type(config[CONF_AQI][CONF_CALCULATION_TYPE])) cg.add(var.set_aqi_calculation_type(config[CONF_AQI][CONF_CALCULATION_TYPE]))
# https://platformio.org/lib/show/6306/Grove%20-%20Laser%20PM2.5%20Sensor%20HM3301
cg.add_library("seeed-studio/Grove - Laser PM2.5 Sensor HM3301", "1.0.3")

View file

@ -114,8 +114,8 @@ CONFIG_SCHEMA = (
def auto_data_rate(config): def auto_data_rate(config):
interval_sec = config[CONF_UPDATE_INTERVAL].seconds interval_msec = config[CONF_UPDATE_INTERVAL].total_milliseconds
interval_hz = 1.0 / interval_sec interval_hz = 1000.0 / interval_msec
for datarate in sorted(HMC5883LDatarates.keys()): for datarate in sorted(HMC5883LDatarates.keys()):
if float(datarate) >= interval_hz: if float(datarate) >= interval_hz:
return HMC5883LDatarates[datarate] return HMC5883LDatarates[datarate]

View file

@ -86,8 +86,8 @@ void ILI9341Display::update() {
void ILI9341Display::display_() { void ILI9341Display::display_() {
// we will only update the changed window to the display // we will only update the changed window to the display
int w = this->x_high_ - this->x_low_ + 1; uint16_t w = this->x_high_ - this->x_low_ + 1;
int h = this->y_high_ - this->y_low_ + 1; uint16_t h = this->y_high_ - this->y_low_ + 1;
set_addr_window_(this->x_low_, this->y_low_, w, h); set_addr_window_(this->x_low_, this->y_low_, w, h);
this->start_data_(); this->start_data_();

View file

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

View file

@ -1,99 +0,0 @@
#include "improv.h"
namespace improv {
ImprovCommand parse_improv_data(const std::vector<uint8_t> &data, bool check_checksum) {
return parse_improv_data(data.data(), data.size(), check_checksum);
}
ImprovCommand parse_improv_data(const uint8_t *data, size_t length, bool check_checksum) {
ImprovCommand improv_command;
Command command = (Command) data[0];
uint8_t data_length = data[1];
if (data_length != length - 2 - check_checksum) {
improv_command.command = UNKNOWN;
return improv_command;
}
if (check_checksum) {
uint8_t checksum = data[length - 1];
uint32_t calculated_checksum = 0;
for (uint8_t i = 0; i < length - 1; i++) {
calculated_checksum += data[i];
}
if ((uint8_t) calculated_checksum != checksum) {
improv_command.command = BAD_CHECKSUM;
return improv_command;
}
}
if (command == WIFI_SETTINGS) {
uint8_t ssid_length = data[2];
uint8_t ssid_start = 3;
size_t ssid_end = ssid_start + ssid_length;
uint8_t pass_length = data[ssid_end];
size_t pass_start = ssid_end + 1;
size_t pass_end = pass_start + pass_length;
std::string ssid(data + ssid_start, data + ssid_end);
std::string password(data + pass_start, data + pass_end);
return {.command = command, .ssid = ssid, .password = password};
}
improv_command.command = command;
return improv_command;
}
std::vector<uint8_t> build_rpc_response(Command command, const std::vector<std::string> &datum, bool add_checksum) {
std::vector<uint8_t> out;
uint32_t length = 0;
out.push_back(command);
for (auto str : datum) {
uint8_t len = str.length();
length += len;
out.push_back(len);
out.insert(out.end(), str.begin(), str.end());
}
out.insert(out.begin() + 1, length);
if (add_checksum) {
uint32_t calculated_checksum = 0;
for (uint8_t byte : out) {
calculated_checksum += byte;
}
out.push_back(calculated_checksum);
}
return out;
}
#ifdef ARDUINO
std::vector<uint8_t> build_rpc_response(Command command, const std::vector<String> &datum, bool add_checksum) {
std::vector<uint8_t> out;
uint32_t length = 0;
out.push_back(command);
for (auto str : datum) {
uint8_t len = str.length();
length += len;
out.push_back(len);
out.insert(out.end(), str.begin(), str.end());
}
out.insert(out.begin() + 1, length);
if (add_checksum) {
uint32_t calculated_checksum = 0;
for (uint8_t byte : out) {
calculated_checksum += byte;
}
out.push_back(calculated_checksum);
}
return out;
}
#endif // ARDUINO
} // namespace improv

View file

@ -1,63 +0,0 @@
#pragma once
#ifdef ARDUINO
#include "WString.h"
#endif // ARDUINO
#include <cstdint>
#include <string>
#include <vector>
namespace improv {
static const char *const SERVICE_UUID = "00467768-6228-2272-4663-277478268000";
static const char *const STATUS_UUID = "00467768-6228-2272-4663-277478268001";
static const char *const ERROR_UUID = "00467768-6228-2272-4663-277478268002";
static const char *const RPC_COMMAND_UUID = "00467768-6228-2272-4663-277478268003";
static const char *const RPC_RESULT_UUID = "00467768-6228-2272-4663-277478268004";
static const char *const CAPABILITIES_UUID = "00467768-6228-2272-4663-277478268005";
enum Error : uint8_t {
ERROR_NONE = 0x00,
ERROR_INVALID_RPC = 0x01,
ERROR_UNKNOWN_RPC = 0x02,
ERROR_UNABLE_TO_CONNECT = 0x03,
ERROR_NOT_AUTHORIZED = 0x04,
ERROR_UNKNOWN = 0xFF,
};
enum State : uint8_t {
STATE_STOPPED = 0x00,
STATE_AWAITING_AUTHORIZATION = 0x01,
STATE_AUTHORIZED = 0x02,
STATE_PROVISIONING = 0x03,
STATE_PROVISIONED = 0x04,
};
enum Command : uint8_t {
UNKNOWN = 0x00,
WIFI_SETTINGS = 0x01,
IDENTIFY = 0x02,
GET_CURRENT_STATE = 0x02,
GET_DEVICE_INFO = 0x03,
BAD_CHECKSUM = 0xFF,
};
static const uint8_t CAPABILITY_IDENTIFY = 0x01;
struct ImprovCommand {
Command command;
std::string ssid;
std::string password;
};
ImprovCommand parse_improv_data(const std::vector<uint8_t> &data, bool check_checksum = true);
ImprovCommand parse_improv_data(const uint8_t *data, size_t length, bool check_checksum = true);
std::vector<uint8_t> build_rpc_response(Command command, const std::vector<std::string> &datum,
bool add_checksum = true);
#ifdef ARDUINO
std::vector<uint8_t> build_rpc_response(Command command, const std::vector<String> &datum, bool add_checksum = true);
#endif // ARDUINO
} // namespace improv

View file

@ -5,7 +5,6 @@ import esphome.final_validate as fv
CODEOWNERS = ["@esphome/core"] CODEOWNERS = ["@esphome/core"]
DEPENDENCIES = ["logger", "wifi"] DEPENDENCIES = ["logger", "wifi"]
AUTO_LOAD = ["improv"]
improv_serial_ns = cg.esphome_ns.namespace("improv_serial") improv_serial_ns = cg.esphome_ns.namespace("improv_serial")
@ -31,3 +30,4 @@ FINAL_VALIDATE_SCHEMA = validate_logger_baud_rate
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)
cg.add_library("esphome/Improv", "1.0.0")

View file

@ -145,7 +145,7 @@ bool ImprovSerialComponent::parse_improv_serial_byte_(uint8_t byte) {
if (at == 8 + data_len + 1) { if (at == 8 + data_len + 1) {
uint8_t checksum = 0x00; uint8_t checksum = 0x00;
for (uint8_t i = 0; i < at; i++) for (size_t i = 0; i < at; i++)
checksum += raw[i]; checksum += raw[i];
if (checksum != byte) { if (checksum != byte) {

View file

@ -1,11 +1,12 @@
#pragma once #pragma once
#include "esphome/components/improv/improv.h"
#include "esphome/components/wifi/wifi_component.h" #include "esphome/components/wifi/wifi_component.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/defines.h" #include "esphome/core/defines.h"
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include <improv.h>
#ifdef USE_ARDUINO #ifdef USE_ARDUINO
#include <HardwareSerial.h> #include <HardwareSerial.h>
#endif #endif

View file

@ -17,7 +17,7 @@ void GPIOLCDDisplay::setup() {
this->enable_pin_->setup(); // OUTPUT this->enable_pin_->setup(); // OUTPUT
this->enable_pin_->digital_write(false); this->enable_pin_->digital_write(false);
for (uint8_t i = 0; i < (this->is_four_bit_mode() ? 4 : 8); i++) { for (uint8_t i = 0; i < (uint8_t)(this->is_four_bit_mode() ? 4u : 8u); i++) {
this->data_pins_[i]->setup(); // OUTPUT this->data_pins_[i]->setup(); // OUTPUT
this->data_pins_[i]->digital_write(false); this->data_pins_[i]->digital_write(false);
} }

View file

@ -15,18 +15,38 @@ namespace ledc {
static const char *const TAG = "ledc.output"; static const char *const TAG = "ledc.output";
#ifdef USE_ESP_IDF
static const int MAX_RES_BITS = LEDC_TIMER_BIT_MAX - 1;
#if SOC_LEDC_SUPPORT_HS_MODE
// Only ESP32 has LEDC_HIGH_SPEED_MODE
inline ledc_mode_t get_speed_mode(uint8_t channel) { return channel < 8 ? LEDC_HIGH_SPEED_MODE : LEDC_LOW_SPEED_MODE; }
#else
// S2, C3, S3 only support LEDC_LOW_SPEED_MODE
// See
// https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-reference/peripherals/ledc.html#functionality-overview
inline ledc_mode_t get_speed_mode(uint8_t) { return LEDC_LOW_SPEED_MODE; }
#endif
#else
static const int MAX_RES_BITS = 20;
#endif
float ledc_max_frequency_for_bit_depth(uint8_t bit_depth) { return 80e6f / float(1 << bit_depth); } float ledc_max_frequency_for_bit_depth(uint8_t bit_depth) { return 80e6f / float(1 << bit_depth); }
float ledc_min_frequency_for_bit_depth(uint8_t bit_depth) {
const float max_div_num = ((1 << 20) - 1) / 256.0f; float ledc_min_frequency_for_bit_depth(uint8_t bit_depth, bool low_frequency) {
const float max_div_num = ((1 << MAX_RES_BITS) - 1) / (low_frequency ? 32.0f : 256.0f);
return 80e6f / (max_div_num * float(1 << bit_depth)); return 80e6f / (max_div_num * float(1 << bit_depth));
} }
optional<uint8_t> ledc_bit_depth_for_frequency(float frequency) { optional<uint8_t> ledc_bit_depth_for_frequency(float frequency) {
for (int i = 20; i >= 1; i--) { ESP_LOGD(TAG, "Calculating resolution bit-depth for frequency %f", frequency);
const float min_frequency = ledc_min_frequency_for_bit_depth(i); for (int i = MAX_RES_BITS; i >= 1; i--) {
const float min_frequency = ledc_min_frequency_for_bit_depth(i, (frequency < 100));
const float max_frequency = ledc_max_frequency_for_bit_depth(i); const float max_frequency = ledc_max_frequency_for_bit_depth(i);
if (min_frequency <= frequency && frequency <= max_frequency) if (min_frequency <= frequency && frequency <= max_frequency) {
ESP_LOGD(TAG, "Resolution calculated as %d", i);
return i; return i;
} }
}
return {}; return {};
} }
@ -48,7 +68,7 @@ void LEDCOutput::write_state(float state) {
ledcWrite(this->channel_, duty); ledcWrite(this->channel_, duty);
#endif #endif
#ifdef USE_ESP_IDF #ifdef USE_ESP_IDF
auto speed_mode = channel_ < 8 ? LEDC_HIGH_SPEED_MODE : LEDC_LOW_SPEED_MODE; auto speed_mode = get_speed_mode(channel_);
auto chan_num = static_cast<ledc_channel_t>(channel_ % 8); auto chan_num = static_cast<ledc_channel_t>(channel_ % 8);
ledc_set_duty(speed_mode, chan_num, duty); ledc_set_duty(speed_mode, chan_num, duty);
ledc_update_duty(speed_mode, chan_num); ledc_update_duty(speed_mode, chan_num);
@ -63,11 +83,15 @@ void LEDCOutput::setup() {
ledcAttachPin(this->pin_->get_pin(), this->channel_); ledcAttachPin(this->pin_->get_pin(), this->channel_);
#endif #endif
#ifdef USE_ESP_IDF #ifdef USE_ESP_IDF
auto speed_mode = channel_ < 8 ? LEDC_HIGH_SPEED_MODE : LEDC_LOW_SPEED_MODE; auto speed_mode = get_speed_mode(channel_);
auto timer_num = static_cast<ledc_timer_t>((channel_ % 8) / 2); auto timer_num = static_cast<ledc_timer_t>((channel_ % 8) / 2);
auto chan_num = static_cast<ledc_channel_t>(channel_ % 8); auto chan_num = static_cast<ledc_channel_t>(channel_ % 8);
bit_depth_ = *ledc_bit_depth_for_frequency(frequency_); bit_depth_ = *ledc_bit_depth_for_frequency(frequency_);
if (bit_depth_ < 1) {
ESP_LOGW(TAG, "Frequency %f can't be achieved with any bit depth", frequency_);
this->status_set_warning();
}
ledc_timer_config_t timer_conf{}; ledc_timer_config_t timer_conf{};
timer_conf.speed_mode = speed_mode; timer_conf.speed_mode = speed_mode;
@ -114,7 +138,7 @@ void LEDCOutput::update_frequency(float frequency) {
ESP_LOGW(TAG, "LEDC output hasn't been initialized yet!"); ESP_LOGW(TAG, "LEDC output hasn't been initialized yet!");
return; return;
} }
auto speed_mode = channel_ < 8 ? LEDC_HIGH_SPEED_MODE : LEDC_LOW_SPEED_MODE; auto speed_mode = get_speed_mode(channel_);
auto timer_num = static_cast<ledc_timer_t>((channel_ % 8) / 2); auto timer_num = static_cast<ledc_timer_t>((channel_ % 8) / 2);
ledc_timer_config_t timer_conf{}; ledc_timer_config_t timer_conf{};

View file

@ -167,7 +167,7 @@ class AddressableScanEffect : public AddressableLightEffect {
this->last_move_ = now; this->last_move_ = now;
it.all() = Color::BLACK; it.all() = Color::BLACK;
for (auto i = 0; i < this->scan_width_; i++) { for (uint32_t i = 0; i < this->scan_width_; i++) {
it[this->at_led_ + i] = current_color; it[this->at_led_ + i] = current_color;
} }
@ -178,7 +178,7 @@ class AddressableScanEffect : public AddressableLightEffect {
uint32_t move_interval_{}; uint32_t move_interval_{};
uint32_t scan_width_{1}; uint32_t scan_width_{1};
uint32_t last_move_{0}; uint32_t last_move_{0};
int at_led_{0}; uint32_t at_led_{0};
bool direction_{true}; bool direction_{true};
}; };

View file

@ -98,7 +98,7 @@ void LightCall::perform() {
// EFFECT // EFFECT
auto effect = this->effect_; auto effect = this->effect_;
const char *effect_s; const char *effect_s;
if (effect == 0) if (effect == 0u)
effect_s = "None"; effect_s = "None";
else else
effect_s = this->parent_->effects_[*this->effect_ - 1]->get_name().c_str(); effect_s = this->parent_->effects_[*this->effect_ - 1]->get_name().c_str();

View file

@ -97,7 +97,7 @@ void LTR390Component::read_mode_(int mode_index) {
// If there are more modes to read then begin the next // If there are more modes to read then begin the next
// otherwise stop // otherwise stop
if (mode_index + 1 < this->mode_funcs_.size()) { if (mode_index + 1 < (int) this->mode_funcs_.size()) {
this->read_mode_(mode_index + 1); this->read_mode_(mode_index + 1);
} else { } else {
this->reading_ = false; this->reading_ = false;

View file

@ -203,16 +203,16 @@ float MAX31865Sensor::calc_temperature_(float rtd_ratio) {
rtd_resistance *= 100; rtd_resistance *= 100;
} }
float rpoly = rtd_resistance; float rpoly = rtd_resistance;
float neg_temp = -242.02; float neg_temp = -242.02f;
neg_temp += 2.2228 * rpoly; neg_temp += 2.2228f * rpoly;
rpoly *= rtd_resistance; // square rpoly *= rtd_resistance; // square
neg_temp += 2.5859e-3 * rpoly; neg_temp += 2.5859e-3f * rpoly;
rpoly *= rtd_resistance; // ^3 rpoly *= rtd_resistance; // ^3
neg_temp -= 4.8260e-6 * rpoly; neg_temp -= 4.8260e-6f * rpoly;
rpoly *= rtd_resistance; // ^4 rpoly *= rtd_resistance; // ^4
neg_temp -= 2.8183e-8 * rpoly; neg_temp -= 2.8183e-8f * rpoly;
rpoly *= rtd_resistance; // ^5 rpoly *= rtd_resistance; // ^5
neg_temp += 1.5243e-10 * rpoly; neg_temp += 1.5243e-10f * rpoly;
return neg_temp; return neg_temp;
} }

View file

@ -76,7 +76,7 @@ void MAX7219Component::loop() {
this->stepsleft_ = 0; this->stepsleft_ = 0;
// Return if there is no need to scroll or scroll is off // Return if there is no need to scroll or scroll is off
if (!this->scroll_ || (this->max_displaybuffer_[0].size() <= get_width_internal())) { if (!this->scroll_ || (this->max_displaybuffer_[0].size() <= (size_t) get_width_internal())) {
this->display(); this->display();
return; return;
} }
@ -88,7 +88,7 @@ void MAX7219Component::loop() {
// Dwell time at end of string in case of stop at end // Dwell time at end of string in case of stop at end
if (this->scroll_mode_ == ScrollMode::STOP) { if (this->scroll_mode_ == ScrollMode::STOP) {
if (this->stepsleft_ >= this->max_displaybuffer_[0].size() - get_width_internal() + 1) { if (this->stepsleft_ >= this->max_displaybuffer_[0].size() - (size_t) get_width_internal() + 1) {
if (now - this->last_scroll_ >= this->scroll_dwell_) { if (now - this->last_scroll_ >= this->scroll_dwell_) {
this->stepsleft_ = 0; this->stepsleft_ = 0;
this->last_scroll_ = now; this->last_scroll_ = now;
@ -155,7 +155,7 @@ int MAX7219Component::get_height_internal() {
int MAX7219Component::get_width_internal() { return this->num_chips_ / this->num_chip_lines_ * 8; } int MAX7219Component::get_width_internal() { return this->num_chips_ / this->num_chip_lines_ * 8; }
void HOT MAX7219Component::draw_absolute_pixel_internal(int x, int y, Color color) { void HOT MAX7219Component::draw_absolute_pixel_internal(int x, int y, Color color) {
if (x + 1 > this->max_displaybuffer_[0].size()) { // Extend the display buffer in case required if (x + 1 > (int) this->max_displaybuffer_[0].size()) { // Extend the display buffer in case required
for (int chip_line = 0; chip_line < this->num_chip_lines_; chip_line++) { for (int chip_line = 0; chip_line < this->num_chip_lines_; chip_line++) {
this->max_displaybuffer_[chip_line].resize(x + 1, this->bckgrnd_); this->max_displaybuffer_[chip_line].resize(x + 1, this->bckgrnd_);
} }

View file

@ -35,7 +35,6 @@ void MCP23S08::dump_config() {
} }
bool MCP23S08::read_reg(uint8_t reg, uint8_t *value) { bool MCP23S08::read_reg(uint8_t reg, uint8_t *value) {
uint8_t data;
this->enable(); this->enable();
this->transfer_byte(this->device_opcode_ | 1); this->transfer_byte(this->device_opcode_ | 1);
this->transfer_byte(reg); this->transfer_byte(reg);

View file

@ -127,9 +127,6 @@ canbus::Error MCP2515::set_mode_(const CanctrlReqopMode mode) {
} }
canbus::Error MCP2515::set_clk_out_(const CanClkOut divisor) { canbus::Error MCP2515::set_clk_out_(const CanClkOut divisor) {
canbus::Error res;
uint8_t cfg3;
if (divisor == CLKOUT_DISABLE) { if (divisor == CLKOUT_DISABLE) {
/* Turn off CLKEN */ /* Turn off CLKEN */
modify_register_(MCP_CANCTRL, CANCTRL_CLKEN, 0x00); modify_register_(MCP_CANCTRL, CANCTRL_CLKEN, 0x00);

View file

@ -1,10 +1,21 @@
import binascii
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.components import modbus from esphome.components import modbus
from esphome.const import CONF_ID, CONF_ADDRESS from esphome.const import CONF_ADDRESS, CONF_ID, CONF_NAME, CONF_LAMBDA, CONF_OFFSET
from esphome.cpp_helpers import logging from esphome.cpp_helpers import logging
from .const import ( from .const import (
CONF_BITMASK,
CONF_BYTE_OFFSET,
CONF_COMMAND_THROTTLE, CONF_COMMAND_THROTTLE,
CONF_CUSTOM_COMMAND,
CONF_FORCE_NEW_RANGE,
CONF_MODBUS_CONTROLLER_ID,
CONF_REGISTER_COUNT,
CONF_REGISTER_TYPE,
CONF_RESPONSE_SIZE,
CONF_SKIP_UPDATES,
CONF_VALUE_TYPE,
) )
CODEOWNERS = ["@martgras"] CODEOWNERS = ["@martgras"]
@ -37,6 +48,7 @@ MODBUS_FUNCTION_CODE = {
ModbusRegisterType_ns = modbus_controller_ns.namespace("ModbusRegisterType") ModbusRegisterType_ns = modbus_controller_ns.namespace("ModbusRegisterType")
ModbusRegisterType = ModbusRegisterType_ns.enum("ModbusRegisterType") ModbusRegisterType = ModbusRegisterType_ns.enum("ModbusRegisterType")
MODBUS_REGISTER_TYPE = { MODBUS_REGISTER_TYPE = {
"custom": ModbusRegisterType.CUSTOM,
"coil": ModbusRegisterType.COIL, "coil": ModbusRegisterType.COIL,
"discrete_input": ModbusRegisterType.DISCRETE_INPUT, "discrete_input": ModbusRegisterType.DISCRETE_INPUT,
"holding": ModbusRegisterType.HOLDING, "holding": ModbusRegisterType.HOLDING,
@ -95,6 +107,100 @@ CONFIG_SCHEMA = cv.All(
) )
ModbusItemBaseSchema = cv.Schema(
{
cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController),
cv.Optional(CONF_ADDRESS): cv.positive_int,
cv.Optional(CONF_CUSTOM_COMMAND): cv.ensure_list(cv.hex_uint8_t),
cv.Exclusive(
CONF_OFFSET,
"offset",
f"{CONF_OFFSET} and {CONF_BYTE_OFFSET} can't be used together",
): cv.positive_int,
cv.Exclusive(
CONF_BYTE_OFFSET,
"offset",
f"{CONF_OFFSET} and {CONF_BYTE_OFFSET} can't be used together",
): cv.positive_int,
cv.Optional(CONF_BITMASK, default=0xFFFFFFFF): cv.hex_uint32_t,
cv.Optional(CONF_SKIP_UPDATES, default=0): cv.positive_int,
cv.Optional(CONF_FORCE_NEW_RANGE, default=False): cv.boolean,
cv.Optional(CONF_LAMBDA): cv.returning_lambda,
cv.Optional(CONF_RESPONSE_SIZE, default=0): cv.positive_int,
},
)
def validate_modbus_register(config):
if CONF_CUSTOM_COMMAND not in config and CONF_ADDRESS not in config:
raise cv.Invalid(
f" {CONF_ADDRESS} is a required property if '{CONF_CUSTOM_COMMAND}:' isn't used"
)
if CONF_CUSTOM_COMMAND in config and CONF_REGISTER_TYPE in config:
raise cv.Invalid(
f"can't use '{CONF_REGISTER_TYPE}:' together with '{CONF_CUSTOM_COMMAND}:'",
)
if CONF_CUSTOM_COMMAND not in config and CONF_REGISTER_TYPE not in config:
raise cv.Invalid(
f" {CONF_REGISTER_TYPE} is a required property if '{CONF_CUSTOM_COMMAND}:' isn't used"
)
return config
def modbus_calc_properties(config):
byte_offset = 0
reg_count = 0
if CONF_OFFSET in config:
byte_offset = config[CONF_OFFSET]
# A CONF_BYTE_OFFSET setting overrides CONF_OFFSET
if CONF_BYTE_OFFSET in config:
byte_offset = config[CONF_BYTE_OFFSET]
if CONF_REGISTER_COUNT in config:
reg_count = config[CONF_REGISTER_COUNT]
if CONF_VALUE_TYPE in config:
value_type = config[CONF_VALUE_TYPE]
if reg_count == 0:
reg_count = TYPE_REGISTER_MAP[value_type]
if CONF_CUSTOM_COMMAND in config:
if CONF_ADDRESS not in config:
# generate a unique modbus address using the hash of the name
# CONF_NAME set even if only CONF_ID is used.
# a modbus register address is required to add the item to sensormap
value = config[CONF_NAME]
if isinstance(value, str):
value = value.encode()
config[CONF_ADDRESS] = binascii.crc_hqx(value, 0)
config[CONF_REGISTER_TYPE] = ModbusRegisterType.CUSTOM
config[CONF_FORCE_NEW_RANGE] = True
return byte_offset, reg_count
async def add_modbus_base_properties(
var, config, sensor_type, lamdba_param_type=cg.float_, lamdba_return_type=float
):
if CONF_CUSTOM_COMMAND in config:
cg.add(var.set_custom_data(config[CONF_CUSTOM_COMMAND]))
if config[CONF_RESPONSE_SIZE] > 0:
cg.add(var.set_register_size(config[CONF_RESPONSE_SIZE]))
if CONF_LAMBDA in config:
template_ = await cg.process_lambda(
config[CONF_LAMBDA],
[
(sensor_type.operator("ptr"), "item"),
(lamdba_param_type, "x"),
(
cg.std_vector.template(cg.uint8).operator("const").operator("ref"),
"data",
),
],
return_type=cg.optional.template(lamdba_return_type),
)
cg.add(var.set_template(template_))
async def to_code(config): async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID], config[CONF_COMMAND_THROTTLE]) var = cg.new_Pvariable(config[CONF_ID], config[CONF_COMMAND_THROTTLE])
cg.add(var.set_command_throttle(config[CONF_COMMAND_THROTTLE])) cg.add(var.set_command_throttle(config[CONF_COMMAND_THROTTLE]))
@ -119,11 +225,3 @@ def function_code_to_register(function_code):
"write_multiple_registers": ModbusRegisterType.HOLDING, "write_multiple_registers": ModbusRegisterType.HOLDING,
} }
return FUNCTION_CODE_TYPE_MAP[function_code] return FUNCTION_CODE_TYPE_MAP[function_code]
def find_by_value(dict, find_value):
for (key, value) in MODBUS_REGISTER_TYPE.items():
print(find_value, value)
if find_value == value:
return key
return "not found"

View file

@ -2,16 +2,18 @@ from esphome.components import binary_sensor
import esphome.config_validation as cv import esphome.config_validation as cv
import esphome.codegen as cg import esphome.codegen as cg
from esphome.const import CONF_ADDRESS, CONF_ID, CONF_LAMBDA, CONF_OFFSET from esphome.const import CONF_ADDRESS, CONF_ID
from .. import ( from .. import (
SensorItem, add_modbus_base_properties,
modbus_controller_ns, modbus_controller_ns,
ModbusController, modbus_calc_properties,
validate_modbus_register,
ModbusItemBaseSchema,
SensorItem,
MODBUS_REGISTER_TYPE, MODBUS_REGISTER_TYPE,
) )
from ..const import ( from ..const import (
CONF_BITMASK, CONF_BITMASK,
CONF_BYTE_OFFSET,
CONF_FORCE_NEW_RANGE, CONF_FORCE_NEW_RANGE,
CONF_MODBUS_CONTROLLER_ID, CONF_MODBUS_CONTROLLER_ID,
CONF_REGISTER_TYPE, CONF_REGISTER_TYPE,
@ -27,30 +29,20 @@ ModbusBinarySensor = modbus_controller_ns.class_(
) )
CONFIG_SCHEMA = cv.All( CONFIG_SCHEMA = cv.All(
binary_sensor.BINARY_SENSOR_SCHEMA.extend( binary_sensor.BINARY_SENSOR_SCHEMA.extend(cv.COMPONENT_SCHEMA)
.extend(ModbusItemBaseSchema)
.extend(
{ {
cv.GenerateID(): cv.declare_id(ModbusBinarySensor), cv.GenerateID(): cv.declare_id(ModbusBinarySensor),
cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController), cv.Optional(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE),
cv.Required(CONF_ADDRESS): cv.positive_int,
cv.Required(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE),
cv.Optional(CONF_OFFSET, default=0): cv.positive_int,
cv.Optional(CONF_BYTE_OFFSET): cv.positive_int,
cv.Optional(CONF_BITMASK, default=0x1): cv.hex_uint32_t,
cv.Optional(CONF_SKIP_UPDATES, default=0): cv.positive_int,
cv.Optional(CONF_FORCE_NEW_RANGE, default=False): cv.boolean,
cv.Optional(CONF_LAMBDA): cv.returning_lambda,
} }
).extend(cv.COMPONENT_SCHEMA), ),
validate_modbus_register,
) )
async def to_code(config): async def to_code(config):
byte_offset = 0 byte_offset, _ = modbus_calc_properties(config)
if CONF_OFFSET in config:
byte_offset = config[CONF_OFFSET]
# A CONF_BYTE_OFFSET setting overrides CONF_OFFSET
if CONF_BYTE_OFFSET in config:
byte_offset = config[CONF_BYTE_OFFSET]
var = cg.new_Pvariable( var = cg.new_Pvariable(
config[CONF_ID], config[CONF_ID],
config[CONF_REGISTER_TYPE], config[CONF_REGISTER_TYPE],
@ -65,17 +57,4 @@ async def to_code(config):
paren = await cg.get_variable(config[CONF_MODBUS_CONTROLLER_ID]) paren = await cg.get_variable(config[CONF_MODBUS_CONTROLLER_ID])
cg.add(paren.add_sensor_item(var)) cg.add(paren.add_sensor_item(var))
if CONF_LAMBDA in config: await add_modbus_base_properties(var, config, ModbusBinarySensor, cg.float_, bool)
template_ = await cg.process_lambda(
config[CONF_LAMBDA],
[
(ModbusBinarySensor.operator("ptr"), "item"),
(cg.float_, "x"),
(
cg.std_vector.template(cg.uint8).operator("const").operator("ref"),
"data",
),
],
return_type=cg.optional.template(bool),
)
cg.add(var.set_template(template_))

View file

@ -13,8 +13,6 @@ void ModbusBinarySensor::parse_and_publish(const std::vector<uint8_t> &data) {
switch (this->register_type) { switch (this->register_type) {
case ModbusRegisterType::DISCRETE_INPUT: case ModbusRegisterType::DISCRETE_INPUT:
value = coil_from_vector(this->offset, data);
break;
case ModbusRegisterType::COIL: case ModbusRegisterType::COIL:
// offset for coil is the actual number of the coil not the byte offset // offset for coil is the actual number of the coil not the byte offset
value = coil_from_vector(this->offset, data); value = coil_from_vector(this->offset, data);

View file

@ -1,6 +1,7 @@
CONF_BITMASK = "bitmask" CONF_BITMASK = "bitmask"
CONF_BYTE_OFFSET = "byte_offset" CONF_BYTE_OFFSET = "byte_offset"
CONF_COMMAND_THROTTLE = "command_throttle" CONF_COMMAND_THROTTLE = "command_throttle"
CONF_CUSTOM_COMMAND = "custom_command"
CONF_FORCE_NEW_RANGE = "force_new_range" CONF_FORCE_NEW_RANGE = "force_new_range"
CONF_MODBUS_CONTROLLER_ID = "modbus_controller_id" CONF_MODBUS_CONTROLLER_ID = "modbus_controller_id"
CONF_MODBUS_FUNCTIONCODE = "modbus_functioncode" CONF_MODBUS_FUNCTIONCODE = "modbus_functioncode"

View file

@ -28,7 +28,10 @@ bool ModbusController::send_next_command_() {
command->register_address, command->register_count); command->register_address, command->register_count);
command->send(); command->send();
this->last_command_timestamp_ = millis(); this->last_command_timestamp_ = millis();
if (!command->on_data_func) { // No handler remove from queue directly after sending // remove from queue if no handler is defined or command was sent too often
if (!command->on_data_func || command->send_countdown < 1) {
ESP_LOGD(TAG, "Modbus command to device=%d register=0x%02X countdown=%d removed from queue after send",
this->address_, command->register_address, command->send_countdown);
command_queue_.pop_front(); command_queue_.pop_front();
} }
} }
@ -69,24 +72,30 @@ void ModbusController::on_modbus_error(uint8_t function_code, uint8_t exception_
} }
} }
void ModbusController::on_register_data(ModbusRegisterType register_type, uint16_t start_address, std::map<uint64_t, SensorItem *>::iterator ModbusController::find_register_(ModbusRegisterType register_type,
const std::vector<uint8_t> &data) { uint16_t start_address) {
ESP_LOGV(TAG, "data for register address : 0x%X : ", start_address);
auto vec_it = find_if(begin(register_ranges_), end(register_ranges_), [=](RegisterRange const &r) { auto vec_it = find_if(begin(register_ranges_), end(register_ranges_), [=](RegisterRange const &r) {
return (r.start_address == start_address && r.register_type == register_type); return (r.start_address == start_address && r.register_type == register_type);
}); });
if (vec_it == register_ranges_.end()) { if (vec_it == register_ranges_.end()) {
ESP_LOGE(TAG, "Handle incoming data : No matching range for sensor found - start_address : 0x%X", start_address); ESP_LOGE(TAG, "No matching range for sensor found - start_address : 0x%X", start_address);
return; } else {
}
auto map_it = sensormap_.find(vec_it->first_sensorkey); auto map_it = sensormap_.find(vec_it->first_sensorkey);
if (map_it == sensormap_.end()) { if (map_it == sensormap_.end()) {
ESP_LOGE(TAG, "Handle incoming data : No sensor found in at start_address : 0x%X (0x%llX)", start_address, ESP_LOGE(TAG, "No sensor found in at start_address : 0x%X (0x%llX)", start_address, vec_it->first_sensorkey);
vec_it->first_sensorkey); } else {
return; return sensormap_.find(vec_it->first_sensorkey);
} }
}
// not found
return std::end(sensormap_);
}
void ModbusController::on_register_data(ModbusRegisterType register_type, uint16_t start_address,
const std::vector<uint8_t> &data) {
ESP_LOGV(TAG, "data for register address : 0x%X : ", start_address);
auto map_it = find_register_(register_type, start_address);
// loop through all sensors with the same start address // loop through all sensors with the same start address
while (map_it != sensormap_.end() && map_it->second->start_address == start_address) { while (map_it != sensormap_.end() && map_it->second->start_address == start_address) {
if (map_it->second->register_type == register_type) { if (map_it->second->register_type == register_type) {
@ -116,9 +125,23 @@ void ModbusController::update_range_(RegisterRange &r) {
ESP_LOGV(TAG, "Range : %X Size: %x (%d) skip: %d", r.start_address, r.register_count, (int) r.register_type, ESP_LOGV(TAG, "Range : %X Size: %x (%d) skip: %d", r.start_address, r.register_count, (int) r.register_type,
r.skip_updates_counter); r.skip_updates_counter);
if (r.skip_updates_counter == 0) { if (r.skip_updates_counter == 0) {
ModbusCommandItem command_item = // if a custom command is used the user supplied custom_data is only available in the SensorItem.
ModbusCommandItem::create_read_command(this, r.register_type, r.start_address, r.register_count); if (r.register_type == ModbusRegisterType::CUSTOM) {
auto it = this->find_register_(r.register_type, r.start_address);
if (it != sensormap_.end()) {
auto command_item = ModbusCommandItem::create_custom_command(
this, it->second->custom_data,
[this](ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data) {
this->on_register_data(ModbusRegisterType::CUSTOM, start_address, data);
});
command_item.register_address = it->second->start_address;
command_item.register_count = it->second->register_count;
command_item.function_code = ModbusFunctionCode::CUSTOM;
queue_command(command_item); queue_command(command_item);
}
} else {
queue_command(ModbusCommandItem::create_read_command(this, r.register_type, r.start_address, r.register_count));
}
r.skip_updates_counter = r.skip_updates; // reset counter to config value r.skip_updates_counter = r.skip_updates; // reset counter to config value
} else { } else {
r.skip_updates_counter--; r.skip_updates_counter--;
@ -422,6 +445,7 @@ bool ModbusCommandItem::send() {
modbusdevice->send_raw(this->payload); modbusdevice->send_raw(this->payload);
} }
ESP_LOGV(TAG, "Command sent %d 0x%X %d", uint8_t(this->function_code), this->register_address, this->register_count); ESP_LOGV(TAG, "Command sent %d 0x%X %d", uint8_t(this->function_code), this->register_address, this->register_count);
send_countdown--;
return true; return true;
} }
@ -549,6 +573,9 @@ float payload_to_float(const std::vector<uint8_t> &data, SensorValueType sensor_
ESP_LOGD(TAG, "FP32_R = 0x%08X => %f", raw_to_float.raw, raw_to_float.float_value); ESP_LOGD(TAG, "FP32_R = 0x%08X => %f", raw_to_float.raw, raw_to_float.float_value);
result = raw_to_float.float_value; result = raw_to_float.float_value;
} break; } break;
case SensorValueType::RAW:
result = NAN;
break;
default: default:
break; break;
} }

View file

@ -102,8 +102,6 @@ inline ModbusFunctionCode modbus_register_write_function(ModbusRegisterType reg_
return ModbusFunctionCode::READ_WRITE_MULTIPLE_REGISTERS; return ModbusFunctionCode::READ_WRITE_MULTIPLE_REGISTERS;
break; break;
case ModbusRegisterType::READ: case ModbusRegisterType::READ:
return ModbusFunctionCode::CUSTOM;
break;
default: default:
return ModbusFunctionCode::CUSTOM; return ModbusFunctionCode::CUSTOM;
break; break;
@ -221,7 +219,7 @@ template<typename N> N mask_and_shift_by_rightbit(N data, uint32_t mask) {
if (result == 0) { if (result == 0) {
return result; return result;
} }
for (int pos = 0; pos < sizeof(N) << 3; pos++) { for (size_t pos = 0; pos < sizeof(N) << 3; pos++) {
if ((mask & (1 << pos)) != 0) if ((mask & (1 << pos)) != 0)
return result >> pos; return result >> pos;
} }
@ -247,29 +245,36 @@ float payload_to_float(const std::vector<uint8_t> &data, SensorValueType sensor_
class ModbusController; class ModbusController;
struct SensorItem { class SensorItem {
public:
virtual void parse_and_publish(const std::vector<uint8_t> &data) = 0;
void set_custom_data(const std::vector<uint8_t> &data) { custom_data = data; }
uint64_t getkey() const { return calc_key(register_type, start_address, offset, bitmask); }
size_t virtual get_register_size() const {
if (register_type == ModbusRegisterType::COIL || register_type == ModbusRegisterType::DISCRETE_INPUT)
return 1;
else // if CONF_RESPONSE_BYTES is used override the default
return response_bytes > 0 ? response_bytes : register_count * 2;
}
// Override register size for modbus devices not using 1 register for one dword
void set_register_size(uint8_t register_size) { response_bytes = register_size; }
ModbusRegisterType register_type; ModbusRegisterType register_type;
SensorValueType sensor_value_type; SensorValueType sensor_value_type;
uint16_t start_address; uint16_t start_address;
uint32_t bitmask; uint32_t bitmask;
uint8_t offset; uint8_t offset;
uint8_t register_count; uint8_t register_count;
uint8_t response_bytes{0};
uint8_t skip_updates; uint8_t skip_updates;
std::vector<uint8_t> custom_data{};
bool force_new_range{false}; bool force_new_range{false};
virtual void parse_and_publish(const std::vector<uint8_t> &data) = 0;
uint64_t getkey() const { return calc_key(register_type, start_address, offset, bitmask); }
size_t virtual get_register_size() const {
if (register_type == ModbusRegisterType::COIL || register_type == ModbusRegisterType::DISCRETE_INPUT)
return 1;
else
return register_count * 2;
}
}; };
struct ModbusCommandItem { class ModbusCommandItem {
public:
static const size_t MAX_PAYLOAD_BYTES = 240; static const size_t MAX_PAYLOAD_BYTES = 240;
static const uint8_t MAX_SEND_REPEATS = 5;
ModbusController *modbusdevice; ModbusController *modbusdevice;
uint16_t register_address; uint16_t register_address;
uint16_t register_count; uint16_t register_count;
@ -279,7 +284,9 @@ struct ModbusCommandItem {
on_data_func; on_data_func;
std::vector<uint8_t> payload = {}; std::vector<uint8_t> payload = {};
bool send(); bool send();
// wrong commands (esp. custom commands) can block the send queue
// limit the number of repeats
uint8_t send_countdown{MAX_SEND_REPEATS};
/// factory methods /// factory methods
/** Create modbus read command /** Create modbus read command
* Function code 02-04 * Function code 02-04
@ -392,6 +399,8 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice {
protected: protected:
/// parse sensormap_ and create range of sequential addresses /// parse sensormap_ and create range of sequential addresses
size_t create_register_ranges_(); size_t create_register_ranges_();
// find register in sensormap. Returns iterator with all registers having the same start address
std::map<uint64_t, SensorItem *>::iterator find_register_(ModbusRegisterType register_type, uint16_t start_address);
/// submit the read command for the address range to the send queue /// submit the read command for the address range to the send queue
void update_range_(RegisterRange &r); void update_range_(RegisterRange &r);
/// parse incoming modbus data /// parse incoming modbus data

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