diff --git a/.clang-tidy b/.clang-tidy index 79276f81c3..b40e606121 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -5,11 +5,8 @@ Checks: >- -altera-*, -android-*, -boost-*, - -bugprone-branch-clone, - -bugprone-easily-swappable-parameters, -bugprone-narrowing-conversions, -bugprone-signed-char-misuse, - -bugprone-too-small-loop-variable, -cert-dcl50-cpp, -cert-err58-cpp, -cert-oop57-cpp, @@ -19,12 +16,10 @@ Checks: >- -clang-diagnostic-delete-abstract-non-virtual-dtor, -clang-diagnostic-delete-non-abstract-non-virtual-dtor, -clang-diagnostic-shadow-field, - -clang-diagnostic-sign-compare, - -clang-diagnostic-unused-variable, -clang-diagnostic-unused-const-variable, + -clang-diagnostic-unused-parameter, -concurrency-*, -cppcoreguidelines-avoid-c-arrays, - -cppcoreguidelines-avoid-goto, -cppcoreguidelines-avoid-magic-numbers, -cppcoreguidelines-init-variables, -cppcoreguidelines-macro-usage, @@ -41,7 +36,6 @@ Checks: >- -cppcoreguidelines-pro-type-union-access, -cppcoreguidelines-pro-type-vararg, -cppcoreguidelines-special-member-functions, - -fuchsia-default-arguments, -fuchsia-multiple-inheritance, -fuchsia-overloaded-operator, -fuchsia-statically-constructed-objects, @@ -51,6 +45,7 @@ Checks: >- -google-explicit-constructor, -google-readability-braces-around-statements, -google-readability-casting, + -google-readability-namespace-comments, -google-readability-todo, -google-runtime-references, -hicpp-*, @@ -97,9 +92,11 @@ CheckOptions: value: '1' - key: google-readability-function-size.StatementThreshold value: '800' - - key: google-readability-namespace-comments.ShortNamespaceLines + - key: google-runtime-int.TypeSuffix + value: '_t' + - key: llvm-namespace-comment.ShortNamespaceLines value: '10' - - key: google-readability-namespace-comments.SpacesBeforeComments + - key: llvm-namespace-comment.SpacesBeforeComments value: '2' - key: modernize-loop-convert.MaxCopySize value: '16' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 02b64d2bf5..93a29874f7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 on: @@ -11,6 +9,10 @@ on: permissions: contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: ci: name: ${{ matrix.name }} @@ -34,7 +36,7 @@ jobs: - id: test file: tests/test3.yaml name: Test tests/test3.yaml - pio_cache_key: test1 + pio_cache_key: test3 - id: test file: tests/test4.yaml name: Test tests/test4.yaml @@ -80,18 +82,23 @@ jobs: with: python-version: '3.7' - - name: Cache pip modules + - name: Cache virtualenv uses: actions/cache@v2 with: - path: ~/.cache/pip - key: pip-${{ steps.python.outputs.python-version }}-${{ hashFiles('requirements*.txt') }} + path: .venv + key: venv-${{ steps.python.outputs.python-version }}-${{ hashFiles('requirements*.txt') }} restore-keys: | - pip-${{ steps.python.outputs.python-version }}- + venv-${{ steps.python.outputs.python-version }}- - - name: Set up python environment + - name: Set up virtualenv run: | - pip3 install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt - pip3 install -e . + python -m venv .venv + 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 - name: Cache platformio diff --git a/.github/workflows/matchers/ci-custom.json b/.github/workflows/matchers/ci-custom.json index 4e1eafff5e..1d5f2551cd 100644 --- a/.github/workflows/matchers/ci-custom.json +++ b/.github/workflows/matchers/ci-custom.json @@ -4,7 +4,7 @@ "owner": "ci-custom", "pattern": [ { - "regexp": "^ERROR (.*):(\\d+):(\\d+) - (.*)$", + "regexp": "^(.*):(\\d+):(\\d+):\\s+lint:\\s+(.*)$", "file": 1, "line": 2, "column": 3, diff --git a/.github/workflows/matchers/gcc.json b/.github/workflows/matchers/gcc.json index 899239f816..a00d9c33f4 100644 --- a/.github/workflows/matchers/gcc.json +++ b/.github/workflows/matchers/gcc.json @@ -5,7 +5,7 @@ "severity": "error", "pattern": [ { - "regexp": "^(.*):(\\d+):(\\d+):\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$", + "regexp": "^src/(.*):(\\d+):(\\d+):\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$", "file": 1, "line": 2, "column": 3, diff --git a/.github/workflows/matchers/lint-python.json b/.github/workflows/matchers/lint-python.json index decbe36c4a..6a09f04770 100644 --- a/.github/workflows/matchers/lint-python.json +++ b/.github/workflows/matchers/lint-python.json @@ -1,11 +1,22 @@ { "problemMatcher": [ + { + "owner": "black", + "severity": "error", + "pattern": [ + { + "regexp": "^(.*): (Please format this file with the black formatter)", + "file": 1, + "message": 2 + } + ] + }, { "owner": "flake8", "severity": "error", "pattern": [ { - "regexp": "^(.*):(\\d+) - ([EFCDNW]\\d{3}.*)$", + "regexp": "^(.*):(\\d+): ([EFCDNW]\\d{3}.*)$", "file": 1, "line": 2, "message": 3 @@ -17,7 +28,7 @@ "severity": "error", "pattern": [ { - "regexp": "^(.*):(\\d+) - (\\[[EFCRW]\\d{4}\\(.*\\),.*\\].*)$", + "regexp": "^(.*):(\\d+): (\\[[EFCRW]\\d{4}\\(.*\\),.*\\].*)$", "file": 1, "line": 2, "message": 3 diff --git a/CODEOWNERS b/CODEOWNERS index 18b4564280..6dbdef12ec 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -30,6 +30,7 @@ esphome/components/bang_bang/* @OttoWinter esphome/components/binary_sensor/* @esphome/core esphome/components/ble_client/* @buxtronix esphome/components/bme680_bsec/* @trvrnrth +esphome/components/button/* @esphome/core esphome/components/canbus/* @danielschramm @mvturnho esphome/components/cap1188/* @MrEditor97 esphome/components/captive_portal/* @OttoWinter @@ -72,7 +73,6 @@ esphome/components/hitachi_ac424/* @sourabhjaiswal esphome/components/homeassistant/* @OttoWinter esphome/components/hrxl_maxsonar_wr/* @netmikey esphome/components/i2c/* @esphome/core -esphome/components/improv/* @jesserockz esphome/components/improv_serial/* @esphome/core esphome/components/inkbird_ibsth1_mini/* @fkirill esphome/components/inkplate6/* @jesserockz @@ -181,6 +181,7 @@ esphome/components/tuya/binary_sensor/* @jesserockz esphome/components/tuya/climate/* @jesserockz esphome/components/tuya/sensor/* @jesserockz esphome/components/tuya/switch/* @jesserockz +esphome/components/tuya/text_sensor/* @dentra esphome/components/uart/* @esphome/core esphome/components/ultrasonic/* @OttoWinter esphome/components/version/* @esphome/core diff --git a/docker/Dockerfile b/docker/Dockerfile index 0d30bb0267..62a64c851d 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -147,9 +147,9 @@ RUN \ /var/{cache,log}/* \ /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 \ - 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 VOLUME ["/esphome"] diff --git a/esphome/__main__.py b/esphome/__main__.py index c2a6dd343f..2f06a71b5f 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -18,6 +18,7 @@ from esphome.const import ( CONF_PORT, CONF_ESPHOME, CONF_PLATFORMIO_OPTIONS, + SECRETS_FILES, ) from esphome.core import CORE, EsphomeError, coroutine from esphome.helpers import indent @@ -200,8 +201,7 @@ def upload_using_esptool(config, port): firmware_offset = "0x10000" if CORE.is_esp32 else "0x0" flash_images = [ platformio_api.FlashImage( - path=idedata.firmware_bin_path, - offset=firmware_offset, + path=idedata.firmware_bin_path, offset=firmware_offset ), *idedata.extra_flash_images, ] @@ -607,10 +607,7 @@ def parse_args(argv): "wizard", help="A helpful setup wizard that will guide you through setting up ESPHome.", ) - parser_wizard.add_argument( - "configuration", - help="Your YAML configuration file.", - ) + parser_wizard.add_argument("configuration", help="Your YAML configuration file.") parser_fingerprint = subparsers.add_parser( "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." ) parser_dashboard.add_argument( - "configuration", - help="Your YAML configuration file directory.", + "configuration", help="Your YAML configuration file directory." ) parser_dashboard.add_argument( "--port", @@ -641,6 +637,12 @@ def parse_args(argv): type=int, default=6052, ) + parser_dashboard.add_argument( + "--address", + help="The address to bind to.", + type=str, + default="0.0.0.0", + ) parser_dashboard.add_argument( "--username", help="The optional username to require for authentication.", @@ -789,6 +791,10 @@ def run_esphome(argv): return 1 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.dashboard = args.dashboard diff --git a/esphome/components/adalight/adalight_light_effect.cpp b/esphome/components/adalight/adalight_light_effect.cpp index d9c2892d21..35e98d7360 100644 --- a/esphome/components/adalight/adalight_light_effect.cpp +++ b/esphome/components/adalight/adalight_light_effect.cpp @@ -25,7 +25,7 @@ void AdalightLightEffect::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 // 2 bytes: LED count // 1 byte: checksum diff --git a/esphome/components/adalight/adalight_light_effect.h b/esphome/components/adalight/adalight_light_effect.h index c1df55659b..b757191864 100644 --- a/esphome/components/adalight/adalight_light_effect.h +++ b/esphome/components/adalight/adalight_light_effect.h @@ -25,7 +25,7 @@ class AdalightLightEffect : public light::AddressableLightEffect, public uart::U 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 blank_all_leds_(light::AddressableLight &it); Frame parse_frame_(light::AddressableLight &it); diff --git a/esphome/components/aht10/aht10.cpp b/esphome/components/aht10/aht10.cpp index e5e04ac181..3c690c39b5 100644 --- a/esphome/components/aht10/aht10.cpp +++ b/esphome/components/aht10/aht10.cpp @@ -73,13 +73,6 @@ void AHT10Component::update() { bool success = false; for (int i = 0; i < AHT10_ATTEMPTS; ++i) { ESP_LOGVV(TAG, "Attempt %d at %6u", i, millis()); - delayMicroseconds(4); - - uint8_t reg = 0; - if (this->write(®, 1) != i2c::ERROR_OK) { - ESP_LOGD(TAG, "Communication with AHT10 failed, waiting..."); - continue; - } delay(delay_ms); if (this->read(data, 6) != i2c::ERROR_OK) { 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_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; if (raw_humidity == 0) { // unrealistic value humidity = NAN; } else { - humidity = (float) raw_humidity * 100.0 / 1048576.0; + humidity = (float) raw_humidity * 100.0f / 1048576.0f; } if (this->temperature_sensor_ != nullptr) { diff --git a/esphome/components/am2320/am2320.cpp b/esphome/components/am2320/am2320.cpp index b53eb69464..c06a2a34d7 100644 --- a/esphome/components/am2320/am2320.cpp +++ b/esphome/components/am2320/am2320.cpp @@ -38,9 +38,9 @@ void AM2320Component::update() { 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; - 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); if (this->temperature_sensor_ != nullptr) diff --git a/esphome/components/animation/__init__.py b/esphome/components/animation/__init__.py index 1780bdf72e..5b7e850cec 100644 --- a/esphome/components/animation/__init__.py +++ b/esphome/components/animation/__init__.py @@ -44,8 +44,9 @@ async def to_code(config): width, height = image.size frames = image.n_frames if CONF_RESIZE in config: - image.thumbnail(config[CONF_RESIZE]) - width, height = image.size + new_width_max, new_height_max = config[CONF_RESIZE] + ratio = min(new_width_max / width, new_height_max / height) + width, height = int(width * ratio), int(height * ratio) else: if width > 500 or height > 500: _LOGGER.warning( @@ -59,10 +60,12 @@ async def to_code(config): for frameIndex in range(frames): image.seek(frameIndex) frame = image.convert("L", dither=Image.NONE) + if CONF_RESIZE in config: + frame = frame.resize([width, height]) pixels = list(frame.getdata()) if len(pixels) != height * width: 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: data[pos] = pix @@ -76,10 +79,12 @@ async def to_code(config): if CONF_RESIZE in config: image.thumbnail(config[CONF_RESIZE]) frame = image.convert("RGB") + if CONF_RESIZE in config: + frame = frame.resize([width, height]) pixels = list(frame.getdata()) if len(pixels) != height * width: 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: data[pos] = pix[0] @@ -95,6 +100,8 @@ async def to_code(config): for frameIndex in range(frames): image.seek(frameIndex) frame = image.convert("1", dither=Image.NONE) + if CONF_RESIZE in config: + frame = frame.resize([width, height]) for y in range(height): for x in range(width): if frame.getpixel((x, y)): diff --git a/esphome/components/anova/anova.h b/esphome/components/anova/anova.h index 2e6910f326..4f8f0d0ee2 100644 --- a/esphome/components/anova/anova.h +++ b/esphome/components/anova/anova.h @@ -30,7 +30,7 @@ class Anova : public climate::Climate, public esphome::ble_client::BLEClientNode climate::ClimateTraits traits() override { auto traits = climate::ClimateTraits(); 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_max_temperature(100.0); traits.set_visual_temperature_step(0.1); diff --git a/esphome/components/anova/anova_base.cpp b/esphome/components/anova/anova_base.cpp index cb877bef35..ce4febbe37 100644 --- a/esphome/components/anova/anova_base.cpp +++ b/esphome/components/anova/anova_base.cpp @@ -103,13 +103,7 @@ void AnovaCodec::decode(const uint8_t *data, uint16_t length) { } break; } - case READ_TARGET_TEMPERATURE: { - this->target_temp_ = parse_number(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 READ_TARGET_TEMPERATURE: case SET_TARGET_TEMPERATURE: { this->target_temp_ = parse_number(str_until(buf, '\r')).value_or(0.0f); if (this->fahrenheit_) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 0eb7ead735..dca722dca5 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -40,6 +40,7 @@ service APIConnection { rpc climate_command (ClimateCommandRequest) returns (void) {} rpc number_command (NumberCommandRequest) returns (void) {} rpc select_command (SelectCommandRequest) returns (void) {} + rpc button_command (ButtonCommandRequest) returns (void) {} } @@ -868,6 +869,11 @@ message ClimateCommandRequest { } // ==================== NUMBER ==================== +enum NumberMode { + NUMBER_MODE_AUTO = 0; + NUMBER_MODE_BOX = 1; + NUMBER_MODE_SLIDER = 2; +} message ListEntitiesNumberResponse { option (id) = 49; option (source) = SOURCE_SERVER; @@ -884,6 +890,8 @@ message ListEntitiesNumberResponse { float step = 8; bool disabled_by_default = 9; EntityCategory entity_category = 10; + string unit_of_measurement = 11; + NumberMode mode = 12; } message NumberStateResponse { option (id) = 50; @@ -944,3 +952,28 @@ message SelectCommandRequest { fixed32 key = 1; 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; +} diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 715a4f48c1..f615815023 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -132,7 +132,7 @@ void APIConnection::loop() { if (state_subs_at_ != -1) { 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; } else { auto &it = subs[state_subs_at_]; @@ -619,6 +619,8 @@ bool APIConnection::send_number_info(number::Number *number) { msg.icon = number->get_icon(); msg.disabled_by_default = number->is_disabled_by_default(); msg.entity_category = static_cast(number->get_entity_category()); + msg.unit_of_measurement = number->traits.get_unit_of_measurement(); + msg.mode = static_cast(number->traits.get_mode()); msg.min_value = number->traits.get_min_value(); msg.max_value = number->traits.get_max_value(); @@ -674,6 +676,28 @@ void APIConnection::select_command(const SelectCommandRequest &msg) { } #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(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 void APIConnection::send_camera_state(std::shared_ptr image) { if (!this->state_subscription_) diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index a1f1769a19..72697b5911 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -73,6 +73,10 @@ class APIConnection : public APIServerConnection { bool send_select_state(select::Select *select, std::string state); bool send_select_info(select::Select *select); 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 bool send_log_message(int level, const char *tag, const char *line); void send_homeassistant_service_call(const HomeassistantServiceResponse &call) { diff --git a/esphome/components/api/api_frame_helper.cpp b/esphome/components/api/api_frame_helper.cpp index c0e37ec90d..23766ec1b1 100644 --- a/esphome/components/api/api_frame_helper.cpp +++ b/esphome/components/api/api_frame_helper.cpp @@ -174,9 +174,6 @@ APIError APINoiseFrameHelper::loop() { * errno API_ERROR_HANDSHAKE_PACKET_LEN: Packet too big for this phase. */ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) { - int err; - APIError aerr; - if (frame == nullptr) { HELPER_LOG("Bad argument for try_read_frame_"); return APIError::BAD_ARG; @@ -200,7 +197,7 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) { return APIError::CONNECTION_CLOSED; } rx_header_buf_len_ += received; - if (received != to_read) { + if ((size_t) received != to_read) { // not a full read return APIError::WOULD_BLOCK; } @@ -247,7 +244,7 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) { return APIError::CONNECTION_CLOSED; } rx_buf_len_ += received; - if (received != to_read) { + if ((size_t) received != to_read) { // not all read return APIError::WOULD_BLOCK; } @@ -544,7 +541,6 @@ APIError APINoiseFrameHelper::try_send_tx_buf_() { APIError APINoiseFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) { if (iovcnt == 0) return APIError::OK; - int err; APIError aerr; size_t total_write_len = 0; @@ -584,7 +580,7 @@ APIError APINoiseFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) { state_ = State::FAILED; HELPER_LOG("Socket write failed with errno %d", errno); 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 size_t to_consume = sent; 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. */ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) { - int err; - APIError aerr; - if (frame == nullptr) { HELPER_LOG("Bad argument for try_read_frame_"); return APIError::BAD_ARG; @@ -854,7 +847,7 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) { return APIError::CONNECTION_CLOSED; } rx_buf_len_ += received; - if (received != to_read) { + if ((size_t) received != to_read) { // not all read return APIError::WOULD_BLOCK; } @@ -874,7 +867,6 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) { } APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) { - int err; APIError aerr; 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(); } APIError APIPlaintextFrameHelper::write_packet(uint16_t type, const uint8_t *payload, size_t payload_len) { - int err; - APIError aerr; - if (state_ != State::DATA) { return APIError::BAD_STATE; } @@ -940,7 +929,6 @@ APIError APIPlaintextFrameHelper::try_send_tx_buf_() { APIError APIPlaintextFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) { if (iovcnt == 0) return APIError::OK; - int err; APIError aerr; size_t total_write_len = 0; @@ -980,7 +968,7 @@ APIError APIPlaintextFrameHelper::write_raw_(const struct iovec *iov, int iovcnt state_ = State::FAILED; HELPER_LOG("Socket write failed with errno %d", errno); 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 size_t to_consume = sent; for (int i = 0; i < iovcnt; i++) { diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 7bfa1e9edb..5b6853c276 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -266,6 +266,18 @@ template<> const char *proto_enum_to_string(enums::Climate return "UNKNOWN"; } } +template<> const char *proto_enum_to_string(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) { switch (field_id) { 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); } #ifdef HAS_PROTO_MESSAGE_DUMP void HelloRequest::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("HelloRequest {\n"); out.append(" client_info: "); out.append("'").append(this->client_info).append("'"); @@ -318,7 +330,7 @@ void HelloResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void HelloResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("HelloResponse {\n"); out.append(" 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); } #ifdef HAS_PROTO_MESSAGE_DUMP void ConnectRequest::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ConnectRequest {\n"); out.append(" password: "); 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); } #ifdef HAS_PROTO_MESSAGE_DUMP void ConnectResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ConnectResponse {\n"); out.append(" invalid_password: "); out.append(YESNO(this->invalid_password)); @@ -464,7 +476,7 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void DeviceInfoResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("DeviceInfoResponse {\n"); out.append(" uses_password: "); out.append(YESNO(this->uses_password)); @@ -588,7 +600,7 @@ void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ListEntitiesBinarySensorResponse {\n"); out.append(" object_id: "); out.append("'").append(this->object_id).append("'"); @@ -660,7 +672,7 @@ void BinarySensorStateResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void BinarySensorStateResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("BinarySensorStateResponse {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -754,7 +766,7 @@ void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesCoverResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ListEntitiesCoverResponse {\n"); out.append(" object_id: "); out.append("'").append(this->object_id).append("'"); @@ -844,7 +856,7 @@ void CoverStateResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void CoverStateResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("CoverStateResponse {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -927,7 +939,7 @@ void CoverCommandRequest::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void CoverCommandRequest::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("CoverCommandRequest {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -1043,7 +1055,7 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesFanResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ListEntitiesFanResponse {\n"); out.append(" object_id: "); out.append("'").append(this->object_id).append("'"); @@ -1139,7 +1151,7 @@ void FanStateResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void FanStateResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("FanStateResponse {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -1240,7 +1252,7 @@ void FanCommandRequest::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void FanCommandRequest::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("FanCommandRequest {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -1391,7 +1403,7 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesLightResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ListEntitiesLightResponse {\n"); out.append(" object_id: "); out.append("'").append(this->object_id).append("'"); @@ -1549,7 +1561,7 @@ void LightStateResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void LightStateResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("LightStateResponse {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -1772,7 +1784,7 @@ void LightCommandRequest::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void LightCommandRequest::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("LightCommandRequest {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -1983,7 +1995,7 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesSensorResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ListEntitiesSensorResponse {\n"); out.append(" object_id: "); out.append("'").append(this->object_id).append("'"); @@ -2072,7 +2084,7 @@ void SensorStateResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void SensorStateResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("SensorStateResponse {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -2152,7 +2164,7 @@ void ListEntitiesSwitchResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesSwitchResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ListEntitiesSwitchResponse {\n"); out.append(" object_id: "); out.append("'").append(this->object_id).append("'"); @@ -2215,7 +2227,7 @@ void SwitchStateResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void SwitchStateResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("SwitchStateResponse {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -2254,7 +2266,7 @@ void SwitchCommandRequest::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void SwitchCommandRequest::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("SwitchCommandRequest {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -2324,7 +2336,7 @@ void ListEntitiesTextSensorResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesTextSensorResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ListEntitiesTextSensorResponse {\n"); out.append(" object_id: "); out.append("'").append(this->object_id).append("'"); @@ -2394,7 +2406,7 @@ void TextSensorStateResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void TextSensorStateResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("TextSensorStateResponse {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -2431,7 +2443,7 @@ void SubscribeLogsRequest::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void SubscribeLogsRequest::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("SubscribeLogsRequest {\n"); out.append(" level: "); out.append(proto_enum_to_string(this->level)); @@ -2474,7 +2486,7 @@ void SubscribeLogsResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void SubscribeLogsResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("SubscribeLogsResponse {\n"); out.append(" level: "); out.append(proto_enum_to_string(this->level)); @@ -2516,7 +2528,7 @@ void HomeassistantServiceMap::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void HomeassistantServiceMap::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("HomeassistantServiceMap {\n"); out.append(" key: "); out.append("'").append(this->key).append("'"); @@ -2575,7 +2587,7 @@ void HomeassistantServiceResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void HomeassistantServiceResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("HomeassistantServiceResponse {\n"); out.append(" service: "); out.append("'").append(this->service).append("'"); @@ -2631,7 +2643,7 @@ void SubscribeHomeAssistantStateResponse::encode(ProtoWriteBuffer buffer) const } #ifdef HAS_PROTO_MESSAGE_DUMP void SubscribeHomeAssistantStateResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("SubscribeHomeAssistantStateResponse {\n"); out.append(" entity_id: "); out.append("'").append(this->entity_id).append("'"); @@ -2668,7 +2680,7 @@ void HomeAssistantStateResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void HomeAssistantStateResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("HomeAssistantStateResponse {\n"); out.append(" entity_id: "); 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); } #ifdef HAS_PROTO_MESSAGE_DUMP void GetTimeResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("GetTimeResponse {\n"); out.append(" epoch_seconds: "); sprintf(buffer, "%u", this->epoch_seconds); @@ -2736,7 +2748,7 @@ void ListEntitiesServicesArgument::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesServicesArgument::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ListEntitiesServicesArgument {\n"); out.append(" name: "); out.append("'").append(this->name).append("'"); @@ -2781,7 +2793,7 @@ void ListEntitiesServicesResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesServicesResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ListEntitiesServicesResponse {\n"); out.append(" name: "); out.append("'").append(this->name).append("'"); @@ -2875,7 +2887,7 @@ void ExecuteServiceArgument::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ExecuteServiceArgument::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ExecuteServiceArgument {\n"); out.append(" bool_: "); out.append(YESNO(this->bool_)); @@ -2956,7 +2968,7 @@ void ExecuteServiceRequest::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ExecuteServiceRequest::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ExecuteServiceRequest {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -3028,7 +3040,7 @@ void ListEntitiesCameraResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesCameraResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ListEntitiesCameraResponse {\n"); out.append(" object_id: "); out.append("'").append(this->object_id).append("'"); @@ -3098,7 +3110,7 @@ void CameraImageResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void CameraImageResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("CameraImageResponse {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -3135,7 +3147,7 @@ void CameraImageRequest::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void CameraImageRequest::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("CameraImageRequest {\n"); out.append(" single: "); out.append(YESNO(this->single)); @@ -3281,7 +3293,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesClimateResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ListEntitiesClimateResponse {\n"); out.append(" object_id: "); out.append("'").append(this->object_id).append("'"); @@ -3468,7 +3480,7 @@ void ClimateStateResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ClimateStateResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ClimateStateResponse {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -3656,7 +3668,7 @@ void ClimateCommandRequest::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ClimateCommandRequest::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ClimateCommandRequest {\n"); out.append(" 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(); return true; } + case 12: { + this->mode = value.as_enum(); + return true; + } default: return false; } @@ -3780,6 +3796,10 @@ bool ListEntitiesNumberResponse::decode_length(uint32_t field_id, ProtoLengthDel this->icon = value.as_string(); return true; } + case 11: { + this->unit_of_measurement = value.as_string(); + return true; + } default: return false; } @@ -3817,10 +3837,12 @@ void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_float(8, this->step); buffer.encode_bool(9, this->disabled_by_default); buffer.encode_enum(10, this->entity_category); + buffer.encode_string(11, this->unit_of_measurement); + buffer.encode_enum(12, this->mode); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesNumberResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ListEntitiesNumberResponse {\n"); out.append(" object_id: "); 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(proto_enum_to_string(this->entity_category)); 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(this->mode)); + out.append("\n"); out.append("}"); } #endif @@ -3899,7 +3929,7 @@ void NumberStateResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void NumberStateResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("NumberStateResponse {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -3937,7 +3967,7 @@ void NumberCommandRequest::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void NumberCommandRequest::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("NumberCommandRequest {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -4015,7 +4045,7 @@ void ListEntitiesSelectResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesSelectResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ListEntitiesSelectResponse {\n"); out.append(" object_id: "); out.append("'").append(this->object_id).append("'"); @@ -4091,7 +4121,7 @@ void SelectStateResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void SelectStateResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("SelectStateResponse {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -4134,7 +4164,7 @@ void SelectCommandRequest::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void SelectCommandRequest::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("SelectCommandRequest {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); @@ -4147,6 +4177,127 @@ void SelectCommandRequest::dump_to(std::string &out) const { out.append("}"); } #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(); + 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(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(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 esphome diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index ec11732c7d..e92b2fa4b6 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -123,6 +123,11 @@ enum ClimatePreset : uint32_t { CLIMATE_PRESET_SLEEP = 6, CLIMATE_PRESET_ACTIVITY = 7, }; +enum NumberMode : uint32_t { + NUMBER_MODE_AUTO = 0, + NUMBER_MODE_BOX = 1, + NUMBER_MODE_SLIDER = 2, +}; } // namespace enums @@ -957,6 +962,8 @@ class ListEntitiesNumberResponse : public ProtoMessage { float step{0.0f}; bool disabled_by_default{false}; enums::EntityCategory entity_category{}; + std::string unit_of_measurement{}; + enums::NumberMode mode{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP 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_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 esphome diff --git a/esphome/components/api/api_pb2_service.cpp b/esphome/components/api/api_pb2_service.cpp index ad2413ea57..567fbf02c9 100644 --- a/esphome/components/api/api_pb2_service.cpp +++ b/esphome/components/api/api_pb2_service.cpp @@ -282,6 +282,16 @@ bool APIServerConnectionBase::send_select_state_response(const SelectStateRespon #endif #ifdef USE_SELECT #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_(msg, 61); +} +#endif +#ifdef USE_BUTTON +#endif bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) { switch (msg_type) { 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()); #endif 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 break; } @@ -737,6 +758,19 @@ void APIServerConnection::on_select_command_request(const SelectCommandRequest & this->select_command(msg); } #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 esphome diff --git a/esphome/components/api/api_pb2_service.h b/esphome/components/api/api_pb2_service.h index 1b8d990b05..50b08d3ec4 100644 --- a/esphome/components/api/api_pb2_service.h +++ b/esphome/components/api/api_pb2_service.h @@ -129,6 +129,12 @@ class APIServerConnectionBase : public ProtoService { #endif #ifdef USE_SELECT 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 protected: 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 #ifdef USE_SELECT virtual void select_command(const SelectCommandRequest &msg) = 0; +#endif +#ifdef USE_BUTTON + virtual void button_command(const ButtonCommandRequest &msg) = 0; #endif protected: void on_hello_request(const HelloRequest &msg) override; @@ -209,6 +218,9 @@ class APIServerConnection : public APIServerConnectionBase { #ifdef USE_SELECT void on_select_command_request(const SelectCommandRequest &msg) override; #endif +#ifdef USE_BUTTON + void on_button_command_request(const ButtonCommandRequest &msg) override; +#endif }; } // namespace api diff --git a/esphome/components/api/list_entities.cpp b/esphome/components/api/list_entities.cpp index 745dd92c89..cb97df8ca1 100644 --- a/esphome/components/api/list_entities.cpp +++ b/esphome/components/api/list_entities.cpp @@ -27,6 +27,9 @@ bool ListEntitiesIterator::on_sensor(sensor::Sensor *sensor) { return this->clie #ifdef USE_SWITCH bool ListEntitiesIterator::on_switch(switch_::Switch *a_switch) { return this->client_->send_switch_info(a_switch); } #endif +#ifdef USE_BUTTON +bool ListEntitiesIterator::on_button(button::Button *button) { return this->client_->send_button_info(button); } +#endif #ifdef USE_TEXT_SENSOR bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) { return this->client_->send_text_sensor_info(text_sensor); diff --git a/esphome/components/api/list_entities.h b/esphome/components/api/list_entities.h index c728fb0a97..714edaa91f 100644 --- a/esphome/components/api/list_entities.h +++ b/esphome/components/api/list_entities.h @@ -30,6 +30,9 @@ class ListEntitiesIterator : public ComponentIterator { #ifdef USE_SWITCH bool on_switch(switch_::Switch *a_switch) override; #endif +#ifdef USE_BUTTON + bool on_button(button::Button *button) override; +#endif #ifdef USE_TEXT_SENSOR bool on_text_sensor(text_sensor::TextSensor *text_sensor) override; #endif diff --git a/esphome/components/api/subscribe_state.h b/esphome/components/api/subscribe_state.h index beb9b947d4..d3f2d3aa45 100644 --- a/esphome/components/api/subscribe_state.h +++ b/esphome/components/api/subscribe_state.h @@ -31,6 +31,9 @@ class InitialStateIterator : public ComponentIterator { #ifdef USE_SWITCH bool on_switch(switch_::Switch *a_switch) override; #endif +#ifdef USE_BUTTON + bool on_button(button::Button *button) override { return true; }; +#endif #ifdef USE_TEXT_SENSOR bool on_text_sensor(text_sensor::TextSensor *text_sensor) override; #endif diff --git a/esphome/components/api/util.cpp b/esphome/components/api/util.cpp index 5085994607..f5fd752101 100644 --- a/esphome/components/api/util.cpp +++ b/esphome/components/api/util.cpp @@ -116,6 +116,21 @@ void ComponentIterator::advance() { } break; #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 case IteratorState::TEXT_SENSOR: if (this->at_ >= App.get_text_sensors().size()) { diff --git a/esphome/components/api/util.h b/esphome/components/api/util.h index e404a95619..7849b3e028 100644 --- a/esphome/components/api/util.h +++ b/esphome/components/api/util.h @@ -38,6 +38,9 @@ class ComponentIterator { #ifdef USE_SWITCH virtual bool on_switch(switch_::Switch *a_switch) = 0; #endif +#ifdef USE_BUTTON + virtual bool on_button(button::Button *button) = 0; +#endif #ifdef USE_TEXT_SENSOR virtual bool on_text_sensor(text_sensor::TextSensor *text_sensor) = 0; #endif @@ -78,6 +81,9 @@ class ComponentIterator { #ifdef USE_SWITCH SWITCH, #endif +#ifdef USE_BUTTON + BUTTON, +#endif #ifdef USE_TEXT_SENSOR TEXT_SENSOR, #endif diff --git a/esphome/components/ble_client/ble_client.cpp b/esphome/components/ble_client/ble_client.cpp index e6cdb0c23d..407f1a1d17 100644 --- a/esphome/components/ble_client/ble_client.cpp +++ b/esphome/components/ble_client/ble_client.cpp @@ -388,6 +388,15 @@ BLEDescriptor *BLECharacteristic::get_descriptor(uint16_t uuid) { return this->get_descriptor(espbt::ESPBTUUID::from_uint16(uuid)); } +void BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size) { + auto client = this->service->client; + 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 esphome diff --git a/esphome/components/ble_client/ble_client.h b/esphome/components/ble_client/ble_client.h index 23123914e8..5680b69f72 100644 --- a/esphome/components/ble_client/ble_client.h +++ b/esphome/components/ble_client/ble_client.h @@ -59,7 +59,7 @@ class BLECharacteristic { void parse_descriptors(); BLEDescriptor *get_descriptor(espbt::ESPBTUUID uuid); BLEDescriptor *get_descriptor(uint16_t uuid); - + void write_value(uint8_t *new_val, int16_t new_val_size); BLEService *service; }; diff --git a/esphome/components/ble_client/output/__init__.py b/esphome/components/ble_client/output/__init__.py new file mode 100644 index 0000000000..fe5835ca82 --- /dev/null +++ b/esphome/components/ble_client/output/__init__.py @@ -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) diff --git a/esphome/components/ble_client/output/ble_binary_output.cpp b/esphome/components/ble_client/output/ble_binary_output.cpp new file mode 100644 index 0000000000..ff3711e842 --- /dev/null +++ b/esphome/components/ble_client/output/ble_binary_output.cpp @@ -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 diff --git a/esphome/components/ble_client/output/ble_binary_output.h b/esphome/components/ble_client/output/ble_binary_output.h new file mode 100644 index 0000000000..e1d62a267b --- /dev/null +++ b/esphome/components/ble_client/output/ble_binary_output.h @@ -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 +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 diff --git a/esphome/components/bme280/bme280.cpp b/esphome/components/bme280/bme280.cpp index 18386430a2..627072443e 100644 --- a/esphome/components/bme280/bme280.cpp +++ b/esphome/components/bme280/bme280.cpp @@ -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_CONTROL = 0xF4; 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_TEMPDATA = 0xFA; static const uint8_t BME280_REGISTER_HUMIDDATA = 0xFD; @@ -178,21 +179,27 @@ void BME280Component::update() { 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->pressure_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]() { + 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; - float temperature = this->read_temperature_(&t_fine); + float temperature = this->read_temperature_(data, &t_fine); if (std::isnan(temperature)) { ESP_LOGW(TAG, "Invalid temperature, cannot read pressure & humidity values."); this->status_set_warning(); return; } - float pressure = this->read_pressure_(t_fine); - float humidity = this->read_humidity_(t_fine); + float pressure = this->read_pressure_(data, t_fine); + float humidity = this->read_humidity_(data, t_fine); ESP_LOGD(TAG, "Got temperature=%.1f°C pressure=%.1fhPa humidity=%.1f%%", temperature, pressure, humidity); if (this->temperature_sensor_ != nullptr) @@ -204,11 +211,8 @@ void BME280Component::update() { this->status_clear_warning(); }); } -float BME280Component::read_temperature_(int32_t *t_fine) { - uint8_t data[3]; - 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); +float BME280Component::read_temperature_(const uint8_t *data, int32_t *t_fine) { + int32_t adc = ((data[3] & 0xFF) << 16) | ((data[4] & 0xFF) << 8) | (data[5] & 0xFF); adc >>= 4; if (adc == 0x80000) // temperature was disabled @@ -226,10 +230,7 @@ float BME280Component::read_temperature_(int32_t *t_fine) { return temperature / 100.0f; } -float BME280Component::read_pressure_(int32_t t_fine) { - uint8_t data[3]; - if (!this->read_bytes(BME280_REGISTER_PRESSUREDATA, data, 3)) - return NAN; +float BME280Component::read_pressure_(const uint8_t *data, int32_t t_fine) { int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF); adc >>= 4; if (adc == 0x80000) @@ -265,9 +266,9 @@ float BME280Component::read_pressure_(int32_t t_fine) { return (p / 256.0f) / 100.0f; } -float BME280Component::read_humidity_(int32_t t_fine) { - uint16_t raw_adc; - if (!this->read_byte_16(BME280_REGISTER_HUMIDDATA, &raw_adc) || raw_adc == 0x8000) +float BME280Component::read_humidity_(const uint8_t *data, int32_t t_fine) { + uint16_t raw_adc = ((data[6] & 0xFF) << 8) | (data[7] & 0xFF); + if (raw_adc == 0x8000) return NAN; int32_t adc = raw_adc; diff --git a/esphome/components/bme280/bme280.h b/esphome/components/bme280/bme280.h index 82724d6887..8511f73382 100644 --- a/esphome/components/bme280/bme280.h +++ b/esphome/components/bme280/bme280.h @@ -82,11 +82,11 @@ class BME280Component : public PollingComponent, public i2c::I2CDevice { protected: /// 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. - 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. - 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); uint16_t read_u16_le_(uint8_t a_register); int16_t read_s16_le_(uint8_t a_register); diff --git a/esphome/components/button/__init__.py b/esphome/components/button/__init__.py new file mode 100644 index 0000000000..1e248ddf07 --- /dev/null +++ b/esphome/components/button/__init__.py @@ -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") diff --git a/esphome/components/button/automation.h b/esphome/components/button/automation.h new file mode 100644 index 0000000000..a5fb9f35b7 --- /dev/null +++ b/esphome/components/button/automation.h @@ -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 class PressAction : public Action { + 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 diff --git a/esphome/components/button/button.cpp b/esphome/components/button/button.cpp new file mode 100644 index 0000000000..d57b46e9aa --- /dev/null +++ b/esphome/components/button/button.cpp @@ -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 &&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 diff --git a/esphome/components/button/button.h b/esphome/components/button/button.h new file mode 100644 index 0000000000..b21a96b8e1 --- /dev/null +++ b/esphome/components/button/button.h @@ -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 &&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 press_callback_{}; + optional device_class_{}; +}; + +} // namespace button +} // namespace esphome diff --git a/esphome/components/captive_portal/captive_portal.cpp b/esphome/components/captive_portal/captive_portal.cpp index ad4c32bb1f..d4e37f62f2 100644 --- a/esphome/components/captive_portal/captive_portal.cpp +++ b/esphome/components/captive_portal/captive_portal.cpp @@ -85,14 +85,7 @@ void CaptivePortal::start() { this->dns_server_->start(53, "*", (uint32_t) ip); this->base_->get_server()->onNotFound([this](AsyncWebServerRequest *req) { - bool not_found = false; - 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) { + if (!this->active_ || req->host().c_str() == wifi::global_wifi_component->wifi_soft_ap_ip().str()) { req->send(404, "text/html", "File not found"); return; } diff --git a/esphome/components/climate/__init__.py b/esphome/components/climate/__init__.py index 7ff769e5cb..87b9a4b3e2 100644 --- a/esphome/components/climate/__init__.py +++ b/esphome/components/climate/__init__.py @@ -20,6 +20,7 @@ from esphome.const import ( CONF_MODE, CONF_MODE_COMMAND_TOPIC, CONF_MODE_STATE_TOPIC, + CONF_ON_STATE, CONF_PRESET, CONF_SWING_MODE, CONF_SWING_MODE_COMMAND_TOPIC, @@ -34,6 +35,7 @@ from esphome.const import ( CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC, CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC, CONF_TEMPERATURE_STEP, + CONF_TRIGGER_ID, CONF_VISUAL, CONF_MQTT_ID, ) @@ -101,6 +103,7 @@ validate_climate_swing_mode = cv.enum(CLIMATE_SWING_MODES, upper=True) # Actions 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( { @@ -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.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: cg.add(mqtt_.set_custom_mode_command_topic(config[CONF_MODE_COMMAND_TOPIC])) 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: 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): if not CORE.has_id(config[CONF_ID]): diff --git a/esphome/components/climate/automation.h b/esphome/components/climate/automation.h index 49a87027f2..3145358dab 100644 --- a/esphome/components/climate/automation.h +++ b/esphome/components/climate/automation.h @@ -42,5 +42,12 @@ template class ControlAction : public Action { Climate *climate_; }; +class StateTrigger : public Trigger<> { + public: + StateTrigger(Climate *climate) { + climate->add_on_state_callback([this]() { this->trigger(); }); + } +}; + } // namespace climate } // namespace esphome diff --git a/esphome/components/cs5460a/cs5460a.cpp b/esphome/components/cs5460a/cs5460a.cpp index a172bcdf56..b0c0531936 100644 --- a/esphome/components/cs5460a/cs5460a.cpp +++ b/esphome/components/cs5460a/cs5460a.cpp @@ -102,8 +102,6 @@ void CS5460AComponent::hw_init_() { /* Doesn't reset the register values etc., just restarts the "computation cycle" */ void CS5460AComponent::restart_() { - int cnt; - this->enable(); /* Stop running conversion, wake up if needed */ this->write_byte(CMD_POWER_UP); diff --git a/esphome/components/cse7766/cse7766.cpp b/esphome/components/cse7766/cse7766.cpp index 87bc4c4bdf..25d75da3e6 100644 --- a/esphome/components/cse7766/cse7766.cpp +++ b/esphome/components/cse7766/cse7766.cpp @@ -90,6 +90,7 @@ void CSE7766Component::parse_data_() { uint32_t power_cycle = this->get_24_bit_uint_(17); 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 voltage_ok = true; @@ -127,6 +128,18 @@ void CSE7766Component::parse_data_() { power = power_calib / float(power_cycle); this->power_acc_ += power; 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) { @@ -136,9 +149,9 @@ void CSE7766Component::parse_data_() { } } void CSE7766Component::update() { - float voltage = this->voltage_counts_ > 0 ? this->voltage_acc_ / this->voltage_counts_ : 0.0; - float current = this->current_counts_ > 0 ? this->current_acc_ / this->current_counts_ : 0.0; - float power = this->power_counts_ > 0 ? this->power_acc_ / this->power_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.0f; + 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_, this->power_acc_); @@ -152,6 +165,8 @@ void CSE7766Component::update() { this->current_sensor_->publish_state(current); if (this->power_sensor_ != nullptr) 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->current_acc_ = 0.0f; @@ -172,6 +187,7 @@ void CSE7766Component::dump_config() { LOG_SENSOR(" ", "Voltage", this->voltage_sensor_); LOG_SENSOR(" ", "Current", this->current_sensor_); LOG_SENSOR(" ", "Power", this->power_sensor_); + LOG_SENSOR(" ", "Energy", this->energy_sensor_); this->check_uart_settings(4800); } diff --git a/esphome/components/cse7766/cse7766.h b/esphome/components/cse7766/cse7766.h index 6cacfee072..d6062c251c 100644 --- a/esphome/components/cse7766/cse7766.h +++ b/esphome/components/cse7766/cse7766.h @@ -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_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_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; float get_setup_priority() const override; @@ -29,9 +30,12 @@ class CSE7766Component : public PollingComponent, public uart::UARTDevice { sensor::Sensor *voltage_sensor_{nullptr}; sensor::Sensor *current_sensor_{nullptr}; sensor::Sensor *power_sensor_{nullptr}; + sensor::Sensor *energy_sensor_{nullptr}; float voltage_acc_{0.0f}; float current_acc_{0.0f}; float power_acc_{0.0f}; + float energy_total_{0.0f}; + uint32_t cf_pulses_last_{0}; uint32_t voltage_counts_{0}; uint32_t current_counts_{0}; uint32_t power_counts_{0}; diff --git a/esphome/components/cse7766/sensor.py b/esphome/components/cse7766/sensor.py index 1c8efc4f72..2f48aff0aa 100644 --- a/esphome/components/cse7766/sensor.py +++ b/esphome/components/cse7766/sensor.py @@ -3,16 +3,20 @@ import esphome.config_validation as cv from esphome.components import sensor, uart from esphome.const import ( CONF_CURRENT, + CONF_ENERGY, CONF_ID, CONF_POWER, CONF_VOLTAGE, DEVICE_CLASS_CURRENT, + DEVICE_CLASS_ENERGY, DEVICE_CLASS_POWER, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT, + STATE_CLASS_TOTAL_INCREASING, UNIT_VOLT, UNIT_AMPERE, UNIT_WATT, + UNIT_WATT_HOURS, ) DEPENDENCIES = ["uart"] @@ -44,6 +48,12 @@ CONFIG_SCHEMA = ( device_class=DEVICE_CLASS_POWER, state_class=STATE_CLASS_MEASUREMENT, ), + cv.Optional(CONF_ENERGY): sensor.sensor_schema( + unit_of_measurement=UNIT_WATT_HOURS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, + ), } ) .extend(cv.polling_component_schema("60s")) @@ -71,3 +81,7 @@ async def to_code(config): conf = config[CONF_POWER] sens = await sensor.new_sensor(conf) 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)) diff --git a/esphome/components/daikin/daikin.cpp b/esphome/components/daikin/daikin.cpp index 5f8d0288e2..83d0253691 100644 --- a/esphome/components/daikin/daikin.cpp +++ b/esphome/components/daikin/daikin.cpp @@ -231,7 +231,7 @@ bool DaikinClimate::on_receive(remote_base::RemoteReceiveData data) { // frame header if (byte != 0x27) return false; - } else if (pos == 3) { + } else if (pos == 3) { // NOLINT(bugprone-branch-clone) // frame header if (byte != 0x00) return false; diff --git a/esphome/components/debug/debug_component.cpp b/esphome/components/debug/debug_component.cpp index b856733121..40eb20fa6e 100644 --- a/esphome/components/debug/debug_component.cpp +++ b/esphome/components/debug/debug_component.cpp @@ -4,20 +4,23 @@ #include "esphome/core/defines.h" #include "esphome/core/version.h" +#ifdef USE_ESP_IDF +#include +#include +#endif + #ifdef USE_ESP32 +#if ESP_IDF_VERSION_MAJOR >= 4 +#include +#else #include -#include +#endif #endif #ifdef USE_ARDUINO #include #endif -#ifdef USE_ESP_IDF -#include -#include -#endif - namespace esphome { namespace debug { diff --git a/esphome/components/deep_sleep/deep_sleep_component.cpp b/esphome/components/deep_sleep/deep_sleep_component.cpp index 0998a57af3..c854b6da6e 100644 --- a/esphome/components/deep_sleep/deep_sleep_component.cpp +++ b/esphome/components/deep_sleep/deep_sleep_component.cpp @@ -77,8 +77,8 @@ void DeepSleepComponent::begin_sleep(bool manual) { if (this->sleep_duration_.has_value()) esp_sleep_enable_timer_wakeup(*this->sleep_duration_); if (this->wakeup_pin_ != nullptr) { - bool level = this->wakeup_pin_->is_inverted(); - if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && !this->wakeup_pin_->digital_read()) { + bool level = !this->wakeup_pin_->is_inverted(); + if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && this->wakeup_pin_->digital_read()) { level = !level; } esp_sleep_enable_ext0_wakeup(gpio_num_t(this->wakeup_pin_->get_pin()), level); diff --git a/esphome/components/display/display_buffer.cpp b/esphome/components/display/display_buffer.cpp index ac806611b5..1458629acd 100644 --- a/esphome/components/display/display_buffer.cpp +++ b/esphome/components/display/display_buffer.cpp @@ -496,7 +496,7 @@ bool Animation::get_pixel(int x, int y) const { return false; const uint32_t width_8 = ((this->width_ + 7u) / 8u) * 8u; 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; const uint32_t pos = x + y * width_8 + frame_index; 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_) return Color::BLACK; 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; const uint32_t pos = (x + y * this->width_ + frame_index) * 3; 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_) return Color::BLACK; 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; const uint32_t pos = (x + y * this->width_ + frame_index); const uint8_t gray = progmem_read_byte(this->data_start_ + pos); diff --git a/esphome/components/dsmr/__init__.py b/esphome/components/dsmr/__init__.py index dd6e6051aa..7a7681082e 100644 --- a/esphome/components/dsmr/__init__.py +++ b/esphome/components/dsmr/__init__.py @@ -1,9 +1,11 @@ import esphome.codegen as cg import esphome.config_validation as cv +from esphome import pins from esphome.components import uart from esphome.const import ( CONF_ID, CONF_UART_ID, + CONF_RECEIVE_TIMEOUT, ) CODEOWNERS = ["@glmnet", "@zuidwijk"] @@ -11,10 +13,13 @@ CODEOWNERS = ["@glmnet", "@zuidwijk"] DEPENDENCIES = ["uart"] AUTO_LOAD = ["sensor", "text_sensor"] -CONF_DSMR_ID = "dsmr_id" -CONF_DECRYPTION_KEY = "decryption_key" CONF_CRC_CHECK = "crc_check" +CONF_DECRYPTION_KEY = "decryption_key" +CONF_DSMR_ID = "dsmr_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 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_CRC_CHECK, default=True): cv.boolean, 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), cv.only_with_arduino, @@ -55,10 +68,17 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): uart_component = await cg.get_variable(config[CONF_UART_ID]) 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: cg.add(var.set_decryption_key(config[CONF_DECRYPTION_KEY])) 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]) # DSMR Parser diff --git a/esphome/components/dsmr/dsmr.cpp b/esphome/components/dsmr/dsmr.cpp index ea852e626e..7b339e5fe0 100644 --- a/esphome/components/dsmr/dsmr.cpp +++ b/esphome/components/dsmr/dsmr.cpp @@ -12,171 +12,275 @@ namespace dsmr { static const char *const TAG = "dsmr"; -void Dsmr::loop() { - if (this->decryption_key_.empty()) - this->receive_telegram_(); - else - this->receive_encrypted_(); +void Dsmr::setup() { + this->telegram_ = new char[this->max_telegram_len_]; // NOLINT + if (this->request_pin_ != nullptr) { + this->request_pin_->setup(); + } } -bool Dsmr::available_within_timeout_() { - uint8_t tries = READ_TIMEOUT_MS / 5; - while (tries--) { - delay(5); - if (available()) { - return true; +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_() { + // Data are available for reading on the UART bus? + // 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); + if (this->available()) { + this->last_read_time_ = millis(); + 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; } -void Dsmr::receive_telegram_() { - while (true) { - if (!available()) { - if (!header_found_ || !available_within_timeout_()) { - return; - } +void Dsmr::start_requesting_data_() { + if (!this->requesting_data_) { + if (this->request_pin_ != nullptr) { + ESP_LOGV(TAG, "Start requesting data from P1 port"); + 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. if (c == '/') { ESP_LOGV(TAG, "Header of telegram found"); - header_found_ = true; - footer_found_ = false; - telegram_len_ = 0; + this->reset_telegram_(); + this->header_found_ = true; } - if (!header_found_) + if (!this->header_found_) continue; // Check for buffer overflow. - if (telegram_len_ >= MAX_TELEGRAM_LENGTH) { - header_found_ = false; - footer_found_ = false; - ESP_LOGE(TAG, "Error: telegram larger than buffer (%d bytes)", MAX_TELEGRAM_LENGTH); + if (this->bytes_read_ >= this->max_telegram_len_) { + this->reset_telegram_(); + ESP_LOGE(TAG, "Error: telegram larger than buffer (%d bytes)", this->max_telegram_len_); return; } // 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 - // proper parsing remove these new line characters - while (c == '(' && (telegram_[telegram_len_ - 1] == '\n' || telegram_[telegram_len_ - 1] == '\r')) - telegram_len_--; + // in a new line, while the value belongs to the previous ObisId. For + // proper parsing, remove these new line characters. + if (c == '(') { + 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. - telegram_[telegram_len_] = c; - telegram_len_++; + this->telegram_[this->bytes_read_] = c; + this->bytes_read_++; // Check for a footer, i.e. exlamation mark, followed by a hex checksum. if (c == '!') { ESP_LOGV(TAG, "Footer of telegram found"); - footer_found_ = true; + this->footer_found_ = true; continue; } // 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_telegram(); - - header_found_ = false; + this->parse_telegram(); + this->reset_telegram_(); return; } } } -void Dsmr::receive_encrypted_() { - // Encrypted buffer - uint8_t buffer[MAX_TELEGRAM_LENGTH]; - 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(); +void Dsmr::receive_encrypted_telegram_() { + while (this->available_within_timeout_()) { + const char c = this->read(); // Find a new telegram start byte. - if (!header_found_) { + if (!this->header_found_) { if ((uint8_t) c != 0xDB) { continue; } ESP_LOGV(TAG, "Start byte 0xDB of encrypted telegram found"); - header_found_ = true; + this->reset_telegram_(); + this->header_found_ = true; } // Check for buffer overflow. - if (buffer_length >= MAX_TELEGRAM_LENGTH) { - header_found_ = false; - ESP_LOGE(TAG, "Error: encrypted telegram larger than buffer (%d bytes)", MAX_TELEGRAM_LENGTH); + if (this->crypt_bytes_read_ >= this->max_telegram_len_) { + this->reset_telegram_(); + ESP_LOGE(TAG, "Error: encrypted telegram larger than buffer (%d bytes)", this->max_telegram_len_); 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 - packet_size = 13 + (buffer[11] << 8 | buffer[12]); - ESP_LOGV(TAG, "Encrypted telegram size: %d bytes", packet_size); + this->crypt_telegram_len_ = 13 + (this->crypt_telegram_[11] << 8 | this->crypt_telegram_[12]); + ESP_LOGV(TAG, "Encrypted telegram length: %d bytes", this->crypt_telegram_len_); } - if (buffer_length == packet_size && packet_size > 0) { - ESP_LOGV(TAG, "End of encrypted telegram found"); - GCM *gcmaes128{new GCM()}; - gcmaes128->setKey(this->decryption_key_.data(), gcmaes128->keySize()); - // 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 - for (int i = 10; i < 14; i++) - buffer[i] = buffer[i + 4]; - constexpr uint16_t iv_size{12}; - gcmaes128->setIV(&buffer[2], iv_size); - gcmaes128->decrypt(reinterpret_cast(this->telegram_), - // the ciphertext start at byte 18 - &buffer[18], - // cipher size - buffer_length - 17); - delete gcmaes128; // NOLINT(cppcoreguidelines-owning-memory) - telegram_len_ = strnlen(this->telegram_, sizeof(this->telegram_)); - ESP_LOGV(TAG, "Decrypted telegram size: %d bytes", telegram_len_); - ESP_LOGVV(TAG, "Decrypted telegram: %s", this->telegram_); - - parse_telegram(); - - header_found_ = false; - telegram_len_ = 0; - return; + // Check for the end of the encrypted telegram. + if (this->crypt_telegram_len_ == 0 || this->crypt_bytes_read_ != this->crypt_telegram_len_) { + continue; } + ESP_LOGV(TAG, "End of encrypted telegram found"); + + // Decrypt the encrypted telegram. + GCM *gcmaes128{new GCM()}; + gcmaes128->setKey(this->decryption_key_.data(), gcmaes128->keySize()); + // 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 + for (int i = 10; i < 14; i++) + this->crypt_telegram_[i] = this->crypt_telegram_[i + 4]; + constexpr uint16_t iv_size{12}; + gcmaes128->setIV(&this->crypt_telegram_[2], iv_size); + gcmaes128->decrypt(reinterpret_cast(this->telegram_), + // the ciphertext start at byte 18 + &this->crypt_telegram_[18], + // cipher size + this->crypt_bytes_read_ - 17); + delete gcmaes128; // NOLINT(cppcoreguidelines-owning-memory) + + this->bytes_read_ = strnlen(this->telegram_, this->max_telegram_len_); + ESP_LOGV(TAG, "Decrypted telegram size: %d bytes", this->bytes_read_); + ESP_LOGVV(TAG, "Decrypted telegram: %s", this->telegram_); + + // Parse the decrypted telegram and publish sensor values. + this->parse_telegram(); + this->reset_telegram_(); + return; } } bool Dsmr::parse_telegram() { MyData data; ESP_LOGV(TAG, "Trying to parse telegram"); + this->stop_requesting_data_(); ::dsmr::ParseResult 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. if (res.err) { // 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()); return false; } else { this->status_clear_warning(); - publish_sensors(data); + this->publish_sensors(data); return true; } } void Dsmr::dump_config() { 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##_); 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) { ESP_LOGI(TAG, "Disabling decryption"); this->decryption_key_.clear(); + if (this->crypt_telegram_ != nullptr) { + delete[] this->crypt_telegram_; + this->crypt_telegram_ = nullptr; + } return; } @@ -205,7 +313,11 @@ void Dsmr::set_decryption_key(const std::string &decryption_key) { char temp[3] = {0}; for (int i = 0; i < 16; i++) { 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 } } diff --git a/esphome/components/dsmr/dsmr.h b/esphome/components/dsmr/dsmr.h index ca2c0f0877..76f79ee55c 100644 --- a/esphome/components/dsmr/dsmr.h +++ b/esphome/components/dsmr/dsmr.h @@ -16,9 +16,6 @@ namespace esphome { namespace dsmr { -static constexpr uint32_t MAX_TELEGRAM_LENGTH = 1500; -static constexpr uint32_t READ_TIMEOUT_MS = 200; - using namespace ::dsmr::fields; // DSMR_**_LIST generated by ESPHome and written in esphome/core/defines @@ -52,6 +49,7 @@ class Dsmr : public Component, public uart::UARTDevice { public: Dsmr(uart::UARTComponent *uart, bool crc_check) : uart::UARTDevice(uart), crc_check_(crc_check) {} + void setup() override; void loop() override; bool parse_telegram(); @@ -71,6 +69,10 @@ class Dsmr : public Component, public uart::UARTDevice { void dump_config() override; 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 #define DSMR_SET_SENSOR(s) \ @@ -83,7 +85,8 @@ class Dsmr : public Component, public uart::UARTDevice { protected: void receive_telegram_(); - void receive_encrypted_(); + void receive_encrypted_telegram_(); + void reset_telegram_(); /// 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. bool available_within_timeout_(); - // Telegram buffer - char telegram_[MAX_TELEGRAM_LENGTH]; - int telegram_len_{0}; + // Request telegram + uint32_t request_interval_; + 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 footer_found_{false}; diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 1c249476e7..d6f1180aa7 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -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_SIZE", True) + # Increase freertos tick speed from 100Hz to 1kHz so that delay() resolution is 1ms 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") for name, value in conf[CONF_SDKCONFIG_OPTIONS].items(): diff --git a/esphome/components/esp32/core.cpp b/esphome/components/esp32/core.cpp index 359999120f..a9756b41cd 100644 --- a/esphome/components/esp32/core.cpp +++ b/esphome/components/esp32/core.cpp @@ -6,12 +6,17 @@ #include #include #include +#include #include #if ESP_IDF_VERSION_MAJOR >= 4 #include #endif +#ifdef USE_ARDUINO +#include +#endif + void setup(); void loop(); @@ -29,24 +34,24 @@ void arch_restart() { 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 -#ifdef CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0 - delay(1); +void arch_init() { + // Enable the task watchdog only on the loop task (from which we're currently running) +#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 // 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; } uint32_t arch_get_cpu_cycle_count() { diff --git a/esphome/components/esp32_improv/__init__.py b/esphome/components/esp32_improv/__init__.py index 0b0214c63e..80c53f7c2a 100644 --- a/esphome/components/esp32_improv/__init__.py +++ b/esphome/components/esp32_improv/__init__.py @@ -4,7 +4,7 @@ from esphome.components import binary_sensor, output, esp32_ble_server 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"] CONFLICTS_WITH = ["esp32_ble_tracker", "esp32_ble_beacon"] DEPENDENCIES = ["wifi", "esp32"] @@ -56,6 +56,7 @@ async def to_code(config): cg.add(ble_server.register_service_component(var)) cg.add_define("USE_IMPROV") + cg.add_library("esphome/Improv", "1.0.0") cg.add(var.set_identify_duration(config[CONF_IDENTIFY_DURATION])) cg.add(var.set_authorized_duration(config[CONF_AUTHORIZED_DURATION])) diff --git a/esphome/components/esp32_improv/esp32_improv_component.h b/esphome/components/esp32_improv/esp32_improv_component.h index 3a5d150fbe..45639f2f63 100644 --- a/esphome/components/esp32_improv/esp32_improv_component.h +++ b/esphome/components/esp32_improv/esp32_improv_component.h @@ -1,9 +1,8 @@ #pragma once #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/improv/improv.h" +#include "esphome/components/esp32_ble_server/ble_server.h" #include "esphome/components/output/binary_output.h" #include "esphome/components/wifi/wifi_component.h" #include "esphome/core/component.h" @@ -12,6 +11,8 @@ #ifdef USE_ESP32 +#include + namespace esphome { namespace esp32_improv { diff --git a/esphome/components/esp8266/boards.py b/esphome/components/esp8266/boards.py index c49aae4ffa..410e934615 100644 --- a/esphome/components/esp8266/boards.py +++ b/esphome/components/esp8266/boards.py @@ -206,61 +206,3 @@ ESP8266_BOARD_PINS = { "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}, } - -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"), -} diff --git a/esphome/components/esp8266/core.cpp b/esphome/components/esp8266/core.cpp index 51f3ca50ec..828d71a3bd 100644 --- a/esphome/components/esp8266/core.cpp +++ b/esphome/components/esp8266/core.cpp @@ -20,6 +20,7 @@ void arch_restart() { yield(); } } +void arch_init() {} void IRAM_ATTR HOT arch_feed_wdt() { 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) { 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) } uint32_t arch_get_cpu_freq_hz() { return F_CPU; } diff --git a/esphome/components/esp8266/gpio.cpp b/esphome/components/esp8266/gpio.cpp index 2660318182..a24f217756 100644 --- a/esphome/components/esp8266/gpio.cpp +++ b/esphome/components/esp8266/gpio.cpp @@ -9,7 +9,7 @@ namespace esp8266 { static const char *const TAG = "esp8266"; static int IRAM_ATTR flags_to_mode(gpio::Flags flags, uint8_t pin) { - if (flags == gpio::FLAG_INPUT) { + if (flags == gpio::FLAG_INPUT) { // NOLINT(bugprone-branch-clone) return INPUT; } else if (flags == gpio::FLAG_OUTPUT) { return OUTPUT; diff --git a/esphome/components/esp8266/preferences.cpp b/esphome/components/esp8266/preferences.cpp index 041736943b..a8f8bd0d41 100644 --- a/esphome/components/esp8266/preferences.cpp +++ b/esphome/components/esp8266/preferences.cpp @@ -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 -static const uint32_t get_esp8266_flash_sector() { +static uint32_t get_esp8266_flash_sector() { union { uint32_t *ptr; uint32_t uint; @@ -63,7 +63,7 @@ static const uint32_t get_esp8266_flash_sector() { data.ptr = &_SPIFFS_end; 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 uint32_t calculate_crc(It first, It last, uint32_t type) { uint32_t crc = type; diff --git a/esphome/components/external_components/__init__.py b/esphome/components/external_components/__init__.py index e0548e8981..d0153f6104 100644 --- a/esphome/components/external_components/__init__.py +++ b/esphome/components/external_components/__init__.py @@ -12,6 +12,8 @@ from esphome.const import ( CONF_TYPE, CONF_EXTERNAL_COMPONENTS, CONF_PATH, + CONF_USERNAME, + CONF_PASSWORD, ) from esphome.core import CORE from esphome import git, loader @@ -27,6 +29,8 @@ TYPE_LOCAL = "local" GIT_SCHEMA = { cv.Required(CONF_URL): cv.url, cv.Optional(CONF_REF): cv.git_ref, + cv.Optional(CONF_USERNAME): cv.string, + cv.Optional(CONF_PASSWORD): cv.string, } LOCAL_SCHEMA = { cv.Required(CONF_PATH): cv.directory, @@ -99,6 +103,8 @@ def _process_git_config(config: dict, refresh) -> str: ref=config.get(CONF_REF), refresh=refresh, domain=DOMAIN, + username=config.get(CONF_USERNAME), + password=config.get(CONF_PASSWORD), ) if (repo_dir / "esphome" / "components").is_dir(): diff --git a/esphome/components/ezo/ezo.cpp b/esphome/components/ezo/ezo.cpp index ca6f121dbb..3c1b6e33e8 100644 --- a/esphome/components/ezo/ezo.cpp +++ b/esphome/components/ezo/ezo.cpp @@ -75,7 +75,7 @@ void EZOSensor::loop() { return; // 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] == ',') buf[i] = '\0'; diff --git a/esphome/components/graph/graph.cpp b/esphome/components/graph/graph.cpp index a9daad4ab9..daff89e0a6 100644 --- a/esphome/components/graph/graph.cpp +++ b/esphome/components/graph/graph.cpp @@ -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 float mx = 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_) { float v = trace->get_tracedata()->get_value(i); 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_)) { for (int y = yn; y <= ym; y++) { 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); } } @@ -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"); } 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); } } @@ -158,14 +158,14 @@ void Graph::draw(DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Colo for (auto *trace : traces_) { Color c = trace->get_line_color(); 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; if (!std::isnan(v) && (thick > 0)) { int16_t x = this->width_ - 1 - i; uint8_t b = (i % (thick * LineType::PATTERN_LENGTH)) / thick; if (((uint8_t) trace->get_line_type() & (1 << b)) == (1 << b)) { int16_t y = (int16_t) roundf((this->height_ - 1) * (1.0 - v)) - thick / 2; - for (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); } } @@ -179,8 +179,8 @@ void GraphLegend::init(Graph *g) { parent_ = g; // Determine maximum expected text and value width / height - int txtw = 0, txtos = 0, txtbl = 0, txth = 0; - int valw = 0, valos = 0, valbl = 0, valh = 0; + int txtw = 0, txth = 0; + int valw = 0, valh = 0; int lt = 0; for (auto *trace : g->traces_) { 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_) { 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; 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, diff --git a/esphome/components/heatpumpir/climate.py b/esphome/components/heatpumpir/climate.py index 36e56aa5da..592e03f959 100644 --- a/esphome/components/heatpumpir/climate.py +++ b/esphome/components/heatpumpir/climate.py @@ -30,6 +30,7 @@ PROTOCOLS = { "gree": Protocol.PROTOCOL_GREE, "greeya": Protocol.PROTOCOL_GREEYAA, "greeyan": Protocol.PROTOCOL_GREEYAN, + "greeyac": Protocol.PROTOCOL_GREEYAC, "hisense_aud": Protocol.PROTOCOL_HISENSE_AUD, "hitachi": Protocol.PROTOCOL_HITACHI, "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_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") diff --git a/esphome/components/heatpumpir/heatpumpir.cpp b/esphome/components/heatpumpir/heatpumpir.cpp index 8d9fc962c0..ad3731b955 100644 --- a/esphome/components/heatpumpir/heatpumpir.cpp +++ b/esphome/components/heatpumpir/heatpumpir.cpp @@ -25,6 +25,7 @@ const std::map> PROTOCOL_CONSTRUCTOR_MAP {PROTOCOL_GREE, []() { return new GreeGenericHeatpumpIR(); }}, // NOLINT {PROTOCOL_GREEYAA, []() { return new GreeYAAHeatpumpIR(); }}, // NOLINT {PROTOCOL_GREEYAN, []() { return new GreeYANHeatpumpIR(); }}, // NOLINT + {PROTOCOL_GREEYAC, []() { return new GreeYACHeatpumpIR(); }}, // NOLINT {PROTOCOL_HISENSE_AUD, []() { return new HisenseHeatpumpIR(); }}, // NOLINT {PROTOCOL_HITACHI, []() { return new HitachiHeatpumpIR(); }}, // NOLINT {PROTOCOL_HYUNDAI, []() { return new HyundaiHeatpumpIR(); }}, // NOLINT @@ -61,6 +62,19 @@ void HeatpumpIRClimate::setup() { } this->heatpump_ir_ = protocol_constructor->second(); 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() { @@ -171,8 +185,7 @@ void HeatpumpIRClimate::transmit_state() { 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, swing_h_cmd); } diff --git a/esphome/components/heatpumpir/heatpumpir.h b/esphome/components/heatpumpir/heatpumpir.h index e2d2b45dc4..18d9b5040f 100644 --- a/esphome/components/heatpumpir/heatpumpir.h +++ b/esphome/components/heatpumpir/heatpumpir.h @@ -25,6 +25,7 @@ enum Protocol { PROTOCOL_GREE, PROTOCOL_GREEYAA, PROTOCOL_GREEYAN, + PROTOCOL_GREEYAC, PROTOCOL_HISENSE_AUD, PROTOCOL_HITACHI, PROTOCOL_HYUNDAI, diff --git a/esphome/components/heatpumpir/ir_sender_esphome.h b/esphome/components/heatpumpir/ir_sender_esphome.h index 24e8ba9883..7546d990ea 100644 --- a/esphome/components/heatpumpir/ir_sender_esphome.h +++ b/esphome/components/heatpumpir/ir_sender_esphome.h @@ -11,8 +11,8 @@ namespace heatpumpir { class IRSenderESPHome : public IRSender { public: - IRSenderESPHome(uint8_t pin, remote_transmitter::RemoteTransmitterComponent *transmitter) - : IRSender(pin), transmit_(transmitter->transmit()){}; + IRSenderESPHome(remote_transmitter::RemoteTransmitterComponent *transmitter) + : IRSender(0), transmit_(transmitter->transmit()){}; void setFrequency(int frequency) override; // NOLINT(readability-identifier-naming) void space(int space_length) override; void mark(int mark_length) override; diff --git a/esphome/components/hitachi_ac344/hitachi_ac344.cpp b/esphome/components/hitachi_ac344/hitachi_ac344.cpp index 067ea39d07..7702baf312 100644 --- a/esphome/components/hitachi_ac344/hitachi_ac344.cpp +++ b/esphome/components/hitachi_ac344/hitachi_ac344.cpp @@ -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); ESP_LOGV(TAG, "SwingH: %02X %02X", remote_state[HITACHI_AC344_SWINGH_BYTE], swing_modeh); - if ((swing_modeh & 0x7) == 0x0) { - this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL; - } else if ((swing_modeh & 0x3) == 0x3) { + if ((swing_modeh & 0x3) == 0x3) { this->swing_mode = climate::CLIMATE_SWING_OFF; } else { this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL; diff --git a/esphome/components/hitachi_ac424/hitachi_ac424.cpp b/esphome/components/hitachi_ac424/hitachi_ac424.cpp index 2e5423a37a..713bc0be25 100644 --- a/esphome/components/hitachi_ac424/hitachi_ac424.cpp +++ b/esphome/components/hitachi_ac424/hitachi_ac424.cpp @@ -300,9 +300,7 @@ bool HitachiClimate::parse_swing_(const uint8_t remote_state[]) { HITACHI_AC424_SWINGH_SIZE); ESP_LOGV(TAG, "SwingH: %02X %02X", remote_state[HITACHI_AC424_SWINGH_BYTE], swing_modeh); - if ((swing_modeh & 0x7) == 0x0) { - this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL; - } else if ((swing_modeh & 0x3) == 0x3) { + if ((swing_modeh & 0x3) == 0x3) { this->swing_mode = climate::CLIMATE_SWING_OFF; } else { this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL; diff --git a/esphome/components/hm3301/abstract_aqi_calculator.h b/esphome/components/hm3301/abstract_aqi_calculator.h index fb41b921d9..42d900a262 100644 --- a/esphome/components/hm3301/abstract_aqi_calculator.h +++ b/esphome/components/hm3301/abstract_aqi_calculator.h @@ -1,6 +1,5 @@ #pragma once -#ifdef USE_ARDUINO #include namespace esphome { @@ -13,5 +12,3 @@ class AbstractAQICalculator { } // namespace hm3301 } // namespace esphome - -#endif // USE_ARDUINO diff --git a/esphome/components/hm3301/aqi_calculator.h b/esphome/components/hm3301/aqi_calculator.h index a3839b643c..08d1dc2921 100644 --- a/esphome/components/hm3301/aqi_calculator.h +++ b/esphome/components/hm3301/aqi_calculator.h @@ -1,7 +1,5 @@ #pragma once -#ifdef USE_ARDUINO - #include "abstract_aqi_calculator.h" namespace esphome { @@ -48,5 +46,3 @@ class AQICalculator : public AbstractAQICalculator { } // namespace hm3301 } // namespace esphome - -#endif // USE_ARDUINO diff --git a/esphome/components/hm3301/aqi_calculator_factory.h b/esphome/components/hm3301/aqi_calculator_factory.h index 3c6f9709b6..55608b6e51 100644 --- a/esphome/components/hm3301/aqi_calculator_factory.h +++ b/esphome/components/hm3301/aqi_calculator_factory.h @@ -1,7 +1,5 @@ #pragma once -#ifdef USE_ARDUINO - #include "caqi_calculator.h" #include "aqi_calculator.h" @@ -29,5 +27,3 @@ class AQICalculatorFactory { } // namespace hm3301 } // namespace esphome - -#endif // USE_ARDUINO diff --git a/esphome/components/hm3301/caqi_calculator.h b/esphome/components/hm3301/caqi_calculator.h index a7f5460e0a..1ec61f2416 100644 --- a/esphome/components/hm3301/caqi_calculator.h +++ b/esphome/components/hm3301/caqi_calculator.h @@ -1,7 +1,5 @@ #pragma once -#ifdef USE_ARDUINO - #include "esphome/core/log.h" #include "abstract_aqi_calculator.h" @@ -52,5 +50,3 @@ class CAQICalculator : public AbstractAQICalculator { } // namespace hm3301 } // namespace esphome - -#endif // USE_ARDUINO diff --git a/esphome/components/hm3301/hm3301.cpp b/esphome/components/hm3301/hm3301.cpp index 759157f330..a2bef2a01d 100644 --- a/esphome/components/hm3301/hm3301.cpp +++ b/esphome/components/hm3301/hm3301.cpp @@ -1,5 +1,3 @@ -#ifdef USE_ARDUINO - #include "esphome/core/log.h" #include "hm3301.h" @@ -14,9 +12,8 @@ static const uint8_t PM_10_0_VALUE_INDEX = 7; void HM3301Component::setup() { ESP_LOGCONFIG(TAG, "Setting up HM3301..."); - hm3301_ = make_unique(); - error_code_ = hm3301_->init(); - if (error_code_ != NO_ERROR) { + if (i2c::ERROR_OK != this->write(&SELECT_COMM_CMD, 1)) { + error_code_ = ERROR_COMM; this->mark_failed(); return; } @@ -38,7 +35,7 @@ void HM3301Component::dump_config() { float HM3301Component::get_setup_priority() const { return setup_priority::DATA; } 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"); this->status_set_warning(); return; @@ -87,8 +84,6 @@ void HM3301Component::update() { 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) { uint8_t sum = 0; 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 esphome - -#endif // USE_ARDUINO diff --git a/esphome/components/hm3301/hm3301.h b/esphome/components/hm3301/hm3301.h index 61bbf7e4ab..e13ffa466e 100644 --- a/esphome/components/hm3301/hm3301.h +++ b/esphome/components/hm3301/hm3301.h @@ -1,17 +1,15 @@ #pragma once -#ifdef USE_ARDUINO - #include "esphome/core/component.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/i2c/i2c.h" #include "aqi_calculator_factory.h" -#include - namespace esphome { namespace hm3301 { +static const uint8_t SELECT_COMM_CMD = 0X88; + class HM3301Component : public PollingComponent, public i2c::I2CDevice { public: HM3301Component() = default; @@ -29,9 +27,12 @@ class HM3301Component : public PollingComponent, public i2c::I2CDevice { void update() override; protected: - std::unique_ptr hm3301_; - - HM330XErrorCode error_code_{NO_ERROR}; + enum { + NO_ERROR = 0, + ERROR_PARAM = -1, + ERROR_COMM = -2, + ERROR_OTHERS = -128, + } error_code_{NO_ERROR}; uint8_t data_buffer_[30]; @@ -43,12 +44,9 @@ class HM3301Component : public PollingComponent, public i2c::I2CDevice { AQICalculatorType aqi_calc_type_; AQICalculatorFactory aqi_calculator_factory_ = AQICalculatorFactory(); - bool read_sensor_value_(uint8_t *); bool validate_checksum_(const uint8_t *); uint16_t get_sensor_value_(const uint8_t *, uint8_t); }; } // namespace hm3301 } // namespace esphome - -#endif // USE_ARDUINO diff --git a/esphome/components/hm3301/sensor.py b/esphome/components/hm3301/sensor.py index 976a0488e1..8e9ee4c6fb 100644 --- a/esphome/components/hm3301/sensor.py +++ b/esphome/components/hm3301/sensor.py @@ -84,7 +84,6 @@ CONFIG_SCHEMA = cv.All( .extend(cv.polling_component_schema("60s")) .extend(i2c.i2c_device_schema(0x40)), _validate, - cv.only_with_arduino, ) @@ -109,6 +108,3 @@ async def to_code(config): sens = await sensor.new_sensor(config[CONF_AQI]) cg.add(var.set_aqi_sensor(sens)) 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") diff --git a/esphome/components/hmc5883l/sensor.py b/esphome/components/hmc5883l/sensor.py index 73e7472dcf..9d8701079e 100644 --- a/esphome/components/hmc5883l/sensor.py +++ b/esphome/components/hmc5883l/sensor.py @@ -114,8 +114,8 @@ CONFIG_SCHEMA = ( def auto_data_rate(config): - interval_sec = config[CONF_UPDATE_INTERVAL].seconds - interval_hz = 1.0 / interval_sec + interval_msec = config[CONF_UPDATE_INTERVAL].total_milliseconds + interval_hz = 1000.0 / interval_msec for datarate in sorted(HMC5883LDatarates.keys()): if float(datarate) >= interval_hz: return HMC5883LDatarates[datarate] diff --git a/esphome/components/ili9341/ili9341_display.cpp b/esphome/components/ili9341/ili9341_display.cpp index ab5586fa28..a24f0bbb64 100644 --- a/esphome/components/ili9341/ili9341_display.cpp +++ b/esphome/components/ili9341/ili9341_display.cpp @@ -86,8 +86,8 @@ void ILI9341Display::update() { void ILI9341Display::display_() { // we will only update the changed window to the display - int w = this->x_high_ - this->x_low_ + 1; - int h = this->y_high_ - this->y_low_ + 1; + uint16_t w = this->x_high_ - this->x_low_ + 1; + uint16_t h = this->y_high_ - this->y_low_ + 1; set_addr_window_(this->x_low_, this->y_low_, w, h); this->start_data_(); diff --git a/esphome/components/improv/__init__.py b/esphome/components/improv/__init__.py deleted file mode 100644 index b1de57df8f..0000000000 --- a/esphome/components/improv/__init__.py +++ /dev/null @@ -1 +0,0 @@ -CODEOWNERS = ["@jesserockz"] diff --git a/esphome/components/improv/improv.cpp b/esphome/components/improv/improv.cpp deleted file mode 100644 index 759962b51a..0000000000 --- a/esphome/components/improv/improv.cpp +++ /dev/null @@ -1,99 +0,0 @@ -#include "improv.h" - -namespace improv { - -ImprovCommand parse_improv_data(const std::vector &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 build_rpc_response(Command command, const std::vector &datum, bool add_checksum) { - std::vector 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 build_rpc_response(Command command, const std::vector &datum, bool add_checksum) { - std::vector 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 diff --git a/esphome/components/improv/improv.h b/esphome/components/improv/improv.h deleted file mode 100644 index 9d1886ddaf..0000000000 --- a/esphome/components/improv/improv.h +++ /dev/null @@ -1,63 +0,0 @@ -#pragma once - -#ifdef ARDUINO -#include "WString.h" -#endif // ARDUINO - -#include -#include -#include - -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 &data, bool check_checksum = true); -ImprovCommand parse_improv_data(const uint8_t *data, size_t length, bool check_checksum = true); - -std::vector build_rpc_response(Command command, const std::vector &datum, - bool add_checksum = true); -#ifdef ARDUINO -std::vector build_rpc_response(Command command, const std::vector &datum, bool add_checksum = true); -#endif // ARDUINO - -} // namespace improv diff --git a/esphome/components/improv_serial/__init__.py b/esphome/components/improv_serial/__init__.py index b1cdc2d93e..ed7c382a2f 100644 --- a/esphome/components/improv_serial/__init__.py +++ b/esphome/components/improv_serial/__init__.py @@ -5,7 +5,6 @@ import esphome.final_validate as fv CODEOWNERS = ["@esphome/core"] DEPENDENCIES = ["logger", "wifi"] -AUTO_LOAD = ["improv"] 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): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) + cg.add_library("esphome/Improv", "1.0.0") diff --git a/esphome/components/improv_serial/improv_serial_component.cpp b/esphome/components/improv_serial/improv_serial_component.cpp index a9a7467125..b4d1d88370 100644 --- a/esphome/components/improv_serial/improv_serial_component.cpp +++ b/esphome/components/improv_serial/improv_serial_component.cpp @@ -145,7 +145,7 @@ bool ImprovSerialComponent::parse_improv_serial_byte_(uint8_t byte) { if (at == 8 + data_len + 1) { uint8_t checksum = 0x00; - for (uint8_t i = 0; i < at; i++) + for (size_t i = 0; i < at; i++) checksum += raw[i]; if (checksum != byte) { diff --git a/esphome/components/improv_serial/improv_serial_component.h b/esphome/components/improv_serial/improv_serial_component.h index 539674e2d3..304afdaf75 100644 --- a/esphome/components/improv_serial/improv_serial_component.h +++ b/esphome/components/improv_serial/improv_serial_component.h @@ -1,11 +1,12 @@ #pragma once -#include "esphome/components/improv/improv.h" #include "esphome/components/wifi/wifi_component.h" #include "esphome/core/component.h" #include "esphome/core/defines.h" #include "esphome/core/helpers.h" +#include + #ifdef USE_ARDUINO #include #endif diff --git a/esphome/components/lcd_gpio/gpio_lcd_display.cpp b/esphome/components/lcd_gpio/gpio_lcd_display.cpp index b0344d313c..94ddc34051 100644 --- a/esphome/components/lcd_gpio/gpio_lcd_display.cpp +++ b/esphome/components/lcd_gpio/gpio_lcd_display.cpp @@ -17,7 +17,7 @@ void GPIOLCDDisplay::setup() { this->enable_pin_->setup(); // OUTPUT 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]->digital_write(false); } diff --git a/esphome/components/ledc/ledc_output.cpp b/esphome/components/ledc/ledc_output.cpp index 21a747e34d..a56dccfd72 100644 --- a/esphome/components/ledc/ledc_output.cpp +++ b/esphome/components/ledc/ledc_output.cpp @@ -15,17 +15,37 @@ namespace ledc { 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_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)); } + optional ledc_bit_depth_for_frequency(float frequency) { - for (int i = 20; i >= 1; i--) { - const float min_frequency = ledc_min_frequency_for_bit_depth(i); + ESP_LOGD(TAG, "Calculating resolution bit-depth for frequency %f", frequency); + 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); - 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 {}; } @@ -48,7 +68,7 @@ void LEDCOutput::write_state(float state) { ledcWrite(this->channel_, duty); #endif #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(channel_ % 8); ledc_set_duty(speed_mode, chan_num, duty); ledc_update_duty(speed_mode, chan_num); @@ -63,11 +83,15 @@ void LEDCOutput::setup() { ledcAttachPin(this->pin_->get_pin(), this->channel_); #endif #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((channel_ % 8) / 2); auto chan_num = static_cast(channel_ % 8); 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{}; 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!"); 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((channel_ % 8) / 2); ledc_timer_config_t timer_conf{}; diff --git a/esphome/components/light/addressable_light_effect.h b/esphome/components/light/addressable_light_effect.h index 358fe69c23..5091bae2d5 100644 --- a/esphome/components/light/addressable_light_effect.h +++ b/esphome/components/light/addressable_light_effect.h @@ -167,7 +167,7 @@ class AddressableScanEffect : public AddressableLightEffect { this->last_move_ = now; 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; } @@ -178,7 +178,7 @@ class AddressableScanEffect : public AddressableLightEffect { uint32_t move_interval_{}; uint32_t scan_width_{1}; uint32_t last_move_{0}; - int at_led_{0}; + uint32_t at_led_{0}; bool direction_{true}; }; diff --git a/esphome/components/light/light_call.cpp b/esphome/components/light/light_call.cpp index 9858590850..3f1b8aef30 100644 --- a/esphome/components/light/light_call.cpp +++ b/esphome/components/light/light_call.cpp @@ -98,7 +98,7 @@ void LightCall::perform() { // EFFECT auto effect = this->effect_; const char *effect_s; - if (effect == 0) + if (effect == 0u) effect_s = "None"; else effect_s = this->parent_->effects_[*this->effect_ - 1]->get_name().c_str(); diff --git a/esphome/components/ltr390/ltr390.cpp b/esphome/components/ltr390/ltr390.cpp index 36f3835724..959af68235 100644 --- a/esphome/components/ltr390/ltr390.cpp +++ b/esphome/components/ltr390/ltr390.cpp @@ -97,7 +97,7 @@ void LTR390Component::read_mode_(int mode_index) { // If there are more modes to read then begin the next // 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); } else { this->reading_ = false; diff --git a/esphome/components/max31865/max31865.cpp b/esphome/components/max31865/max31865.cpp index 91946cde2c..126915dc15 100644 --- a/esphome/components/max31865/max31865.cpp +++ b/esphome/components/max31865/max31865.cpp @@ -203,16 +203,16 @@ float MAX31865Sensor::calc_temperature_(float rtd_ratio) { rtd_resistance *= 100; } float rpoly = rtd_resistance; - float neg_temp = -242.02; - neg_temp += 2.2228 * rpoly; + float neg_temp = -242.02f; + neg_temp += 2.2228f * rpoly; rpoly *= rtd_resistance; // square - neg_temp += 2.5859e-3 * rpoly; + neg_temp += 2.5859e-3f * rpoly; rpoly *= rtd_resistance; // ^3 - neg_temp -= 4.8260e-6 * rpoly; + neg_temp -= 4.8260e-6f * rpoly; rpoly *= rtd_resistance; // ^4 - neg_temp -= 2.8183e-8 * rpoly; + neg_temp -= 2.8183e-8f * rpoly; rpoly *= rtd_resistance; // ^5 - neg_temp += 1.5243e-10 * rpoly; + neg_temp += 1.5243e-10f * rpoly; return neg_temp; } diff --git a/esphome/components/max7219digit/max7219digit.cpp b/esphome/components/max7219digit/max7219digit.cpp index 0f86ac635c..2368c17448 100644 --- a/esphome/components/max7219digit/max7219digit.cpp +++ b/esphome/components/max7219digit/max7219digit.cpp @@ -76,7 +76,7 @@ void MAX7219Component::loop() { this->stepsleft_ = 0; // 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(); return; } @@ -88,7 +88,7 @@ void MAX7219Component::loop() { // Dwell time at end of string in case of stop at end 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_) { this->stepsleft_ = 0; 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; } 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++) { this->max_displaybuffer_[chip_line].resize(x + 1, this->bckgrnd_); } diff --git a/esphome/components/mcp23s08/mcp23s08.cpp b/esphome/components/mcp23s08/mcp23s08.cpp index b7adeb94d2..af834b4c40 100644 --- a/esphome/components/mcp23s08/mcp23s08.cpp +++ b/esphome/components/mcp23s08/mcp23s08.cpp @@ -35,7 +35,6 @@ void MCP23S08::dump_config() { } bool MCP23S08::read_reg(uint8_t reg, uint8_t *value) { - uint8_t data; this->enable(); this->transfer_byte(this->device_opcode_ | 1); this->transfer_byte(reg); diff --git a/esphome/components/mcp2515/mcp2515.cpp b/esphome/components/mcp2515/mcp2515.cpp index ce451cbb33..e845c79a64 100644 --- a/esphome/components/mcp2515/mcp2515.cpp +++ b/esphome/components/mcp2515/mcp2515.cpp @@ -127,9 +127,6 @@ canbus::Error MCP2515::set_mode_(const CanctrlReqopMode mode) { } canbus::Error MCP2515::set_clk_out_(const CanClkOut divisor) { - canbus::Error res; - uint8_t cfg3; - if (divisor == CLKOUT_DISABLE) { /* Turn off CLKEN */ modify_register_(MCP_CANCTRL, CANCTRL_CLKEN, 0x00); diff --git a/esphome/components/modbus_controller/__init__.py b/esphome/components/modbus_controller/__init__.py index 8499cec561..b927faf9a7 100644 --- a/esphome/components/modbus_controller/__init__.py +++ b/esphome/components/modbus_controller/__init__.py @@ -1,10 +1,21 @@ +import binascii import esphome.codegen as cg import esphome.config_validation as cv 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 .const import ( + CONF_BITMASK, + CONF_BYTE_OFFSET, 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"] @@ -37,6 +48,7 @@ MODBUS_FUNCTION_CODE = { ModbusRegisterType_ns = modbus_controller_ns.namespace("ModbusRegisterType") ModbusRegisterType = ModbusRegisterType_ns.enum("ModbusRegisterType") MODBUS_REGISTER_TYPE = { + "custom": ModbusRegisterType.CUSTOM, "coil": ModbusRegisterType.COIL, "discrete_input": ModbusRegisterType.DISCRETE_INPUT, "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): var = cg.new_Pvariable(config[CONF_ID], 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, } 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" diff --git a/esphome/components/modbus_controller/binary_sensor/__init__.py b/esphome/components/modbus_controller/binary_sensor/__init__.py index d46ff71f2d..99d56fed67 100644 --- a/esphome/components/modbus_controller/binary_sensor/__init__.py +++ b/esphome/components/modbus_controller/binary_sensor/__init__.py @@ -2,16 +2,18 @@ from esphome.components import binary_sensor import esphome.config_validation as cv 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 ( - SensorItem, + add_modbus_base_properties, modbus_controller_ns, - ModbusController, + modbus_calc_properties, + validate_modbus_register, + ModbusItemBaseSchema, + SensorItem, MODBUS_REGISTER_TYPE, ) from ..const import ( CONF_BITMASK, - CONF_BYTE_OFFSET, CONF_FORCE_NEW_RANGE, CONF_MODBUS_CONTROLLER_ID, CONF_REGISTER_TYPE, @@ -27,30 +29,20 @@ ModbusBinarySensor = modbus_controller_ns.class_( ) 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(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController), - 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, + cv.Optional(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE), } - ).extend(cv.COMPONENT_SCHEMA), + ), + validate_modbus_register, ) async def to_code(config): - byte_offset = 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] + byte_offset, _ = modbus_calc_properties(config) var = cg.new_Pvariable( config[CONF_ID], config[CONF_REGISTER_TYPE], @@ -65,17 +57,4 @@ async def to_code(config): paren = await cg.get_variable(config[CONF_MODBUS_CONTROLLER_ID]) cg.add(paren.add_sensor_item(var)) - if CONF_LAMBDA in config: - 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_)) + await add_modbus_base_properties(var, config, ModbusBinarySensor, cg.float_, bool) diff --git a/esphome/components/modbus_controller/binary_sensor/modbus_binarysensor.cpp b/esphome/components/modbus_controller/binary_sensor/modbus_binarysensor.cpp index 81066b3f5c..c3eb3d4411 100644 --- a/esphome/components/modbus_controller/binary_sensor/modbus_binarysensor.cpp +++ b/esphome/components/modbus_controller/binary_sensor/modbus_binarysensor.cpp @@ -13,8 +13,6 @@ void ModbusBinarySensor::parse_and_publish(const std::vector &data) { switch (this->register_type) { case ModbusRegisterType::DISCRETE_INPUT: - value = coil_from_vector(this->offset, data); - break; case ModbusRegisterType::COIL: // offset for coil is the actual number of the coil not the byte offset value = coil_from_vector(this->offset, data); diff --git a/esphome/components/modbus_controller/const.py b/esphome/components/modbus_controller/const.py index 3cd114e673..8d1676dd38 100644 --- a/esphome/components/modbus_controller/const.py +++ b/esphome/components/modbus_controller/const.py @@ -1,6 +1,7 @@ CONF_BITMASK = "bitmask" CONF_BYTE_OFFSET = "byte_offset" CONF_COMMAND_THROTTLE = "command_throttle" +CONF_CUSTOM_COMMAND = "custom_command" CONF_FORCE_NEW_RANGE = "force_new_range" CONF_MODBUS_CONTROLLER_ID = "modbus_controller_id" CONF_MODBUS_FUNCTIONCODE = "modbus_functioncode" diff --git a/esphome/components/modbus_controller/modbus_controller.cpp b/esphome/components/modbus_controller/modbus_controller.cpp index 70b5bf8eae..8b96c20691 100644 --- a/esphome/components/modbus_controller/modbus_controller.cpp +++ b/esphome/components/modbus_controller/modbus_controller.cpp @@ -28,7 +28,10 @@ bool ModbusController::send_next_command_() { command->register_address, command->register_count); command->send(); 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(); } } @@ -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, - const std::vector &data) { - ESP_LOGV(TAG, "data for register address : 0x%X : ", start_address); - +std::map::iterator ModbusController::find_register_(ModbusRegisterType register_type, + uint16_t start_address) { 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); }); if (vec_it == register_ranges_.end()) { - ESP_LOGE(TAG, "Handle incoming data : No matching range for sensor found - start_address : 0x%X", start_address); - return; - } - auto map_it = sensormap_.find(vec_it->first_sensorkey); - if (map_it == sensormap_.end()) { - ESP_LOGE(TAG, "Handle incoming data : No sensor found in at start_address : 0x%X (0x%llX)", start_address, - vec_it->first_sensorkey); - return; + ESP_LOGE(TAG, "No matching range for sensor found - start_address : 0x%X", start_address); + } else { + auto map_it = sensormap_.find(vec_it->first_sensorkey); + if (map_it == sensormap_.end()) { + ESP_LOGE(TAG, "No sensor found in at start_address : 0x%X (0x%llX)", start_address, vec_it->first_sensorkey); + } else { + 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 &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 while (map_it != sensormap_.end() && map_it->second->start_address == start_address) { 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, r.skip_updates_counter); if (r.skip_updates_counter == 0) { - ModbusCommandItem command_item = - ModbusCommandItem::create_read_command(this, r.register_type, r.start_address, r.register_count); - queue_command(command_item); + // if a custom command is used the user supplied custom_data is only available in the SensorItem. + 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 &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); + } + } 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 } else { r.skip_updates_counter--; @@ -422,6 +445,7 @@ bool ModbusCommandItem::send() { 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); + send_countdown--; return true; } @@ -549,6 +573,9 @@ float payload_to_float(const std::vector &data, SensorValueType sensor_ ESP_LOGD(TAG, "FP32_R = 0x%08X => %f", raw_to_float.raw, raw_to_float.float_value); result = raw_to_float.float_value; } break; + case SensorValueType::RAW: + result = NAN; + break; default: break; } diff --git a/esphome/components/modbus_controller/modbus_controller.h b/esphome/components/modbus_controller/modbus_controller.h index 222ebbd020..f4948e6ff9 100644 --- a/esphome/components/modbus_controller/modbus_controller.h +++ b/esphome/components/modbus_controller/modbus_controller.h @@ -102,8 +102,6 @@ inline ModbusFunctionCode modbus_register_write_function(ModbusRegisterType reg_ return ModbusFunctionCode::READ_WRITE_MULTIPLE_REGISTERS; break; case ModbusRegisterType::READ: - return ModbusFunctionCode::CUSTOM; - break; default: return ModbusFunctionCode::CUSTOM; break; @@ -221,7 +219,7 @@ template N mask_and_shift_by_rightbit(N data, uint32_t mask) { if (result == 0) { 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) return result >> pos; } @@ -247,29 +245,36 @@ float payload_to_float(const std::vector &data, SensorValueType sensor_ class ModbusController; -struct SensorItem { +class SensorItem { + public: + virtual void parse_and_publish(const std::vector &data) = 0; + + void set_custom_data(const std::vector &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; SensorValueType sensor_value_type; uint16_t start_address; uint32_t bitmask; uint8_t offset; uint8_t register_count; + uint8_t response_bytes{0}; uint8_t skip_updates; + std::vector custom_data{}; bool force_new_range{false}; - - virtual void parse_and_publish(const std::vector &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 uint8_t MAX_SEND_REPEATS = 5; ModbusController *modbusdevice; uint16_t register_address; uint16_t register_count; @@ -279,7 +284,9 @@ struct ModbusCommandItem { on_data_func; std::vector payload = {}; 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 /** Create modbus read command * Function code 02-04 @@ -392,6 +399,8 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice { protected: /// parse sensormap_ and create range of sequential addresses size_t create_register_ranges_(); + // find register in sensormap. Returns iterator with all registers having the same start address + std::map::iterator find_register_(ModbusRegisterType register_type, uint16_t start_address); /// submit the read command for the address range to the send queue void update_range_(RegisterRange &r); /// parse incoming modbus data diff --git a/esphome/components/modbus_controller/number/__init__.py b/esphome/components/modbus_controller/number/__init__.py index 4de0ffbcea..3c5db9b9c8 100644 --- a/esphome/components/modbus_controller/number/__init__.py +++ b/esphome/components/modbus_controller/number/__init__.py @@ -4,29 +4,26 @@ from esphome.components import number from esphome.const import ( CONF_ADDRESS, CONF_ID, - CONF_LAMBDA, CONF_MAX_VALUE, CONF_MIN_VALUE, CONF_MULTIPLY, - CONF_OFFSET, CONF_STEP, ) from .. import ( + add_modbus_base_properties, modbus_controller_ns, - ModbusController, - SENSOR_VALUE_TYPE, + modbus_calc_properties, + ModbusItemBaseSchema, SensorItem, - TYPE_REGISTER_MAP, + SENSOR_VALUE_TYPE, ) - from ..const import ( CONF_BITMASK, - CONF_BYTE_OFFSET, + CONF_CUSTOM_COMMAND, CONF_FORCE_NEW_RANGE, CONF_MODBUS_CONTROLLER_ID, - CONF_REGISTER_COUNT, CONF_SKIP_UPDATES, CONF_VALUE_TYPE, CONF_WRITE_LAMBDA, @@ -51,22 +48,21 @@ def validate_min_max(config): return config +def validate_modbus_number(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" + ) + return config + + CONFIG_SCHEMA = cv.All( - number.NUMBER_SCHEMA.extend( + number.NUMBER_SCHEMA.extend(ModbusItemBaseSchema) + .extend( { cv.GenerateID(): cv.declare_id(ModbusNumber), - cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController), - cv.Required(CONF_ADDRESS): cv.positive_int, - cv.Optional(CONF_OFFSET, default=0): cv.positive_int, - cv.Optional(CONF_BYTE_OFFSET): cv.positive_int, - cv.Optional(CONF_BITMASK, default=0xFFFFFFFF): cv.hex_uint32_t, cv.Optional(CONF_VALUE_TYPE, default="U_WORD"): cv.enum(SENSOR_VALUE_TYPE), - cv.Optional(CONF_REGISTER_COUNT, default=0): cv.positive_int, - 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_WRITE_LAMBDA): cv.returning_lambda, - cv.GenerateID(): cv.declare_id(ModbusNumber), # 24 bits are the maximum value for fp32 before precison is lost # 0x00FFFFFF = 16777215 cv.Optional(CONF_MAX_VALUE, default=16777215.0): cv.float_, @@ -74,22 +70,15 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_STEP, default=1): cv.positive_float, cv.Optional(CONF_MULTIPLY, default=1.0): cv.float_, } - ).extend(cv.polling_component_schema("60s")), + ) + .extend(cv.polling_component_schema("60s")), validate_min_max, + validate_modbus_number, ) async def to_code(config): - byte_offset = 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] - value_type = config[CONF_VALUE_TYPE] - reg_count = config[CONF_REGISTER_COUNT] - if reg_count == 0: - reg_count = TYPE_REGISTER_MAP[value_type] + byte_offset, reg_count = modbus_calc_properties(config) var = cg.new_Pvariable( config[CONF_ID], config[CONF_ADDRESS], @@ -115,20 +104,7 @@ async def to_code(config): cg.add(var.set_parent(parent)) cg.add(parent.add_sensor_item(var)) - if CONF_LAMBDA in config: - template_ = await cg.process_lambda( - config[CONF_LAMBDA], - [ - (ModbusNumber.operator("ptr"), "item"), - (cg.float_, "x"), - ( - cg.std_vector.template(cg.uint8).operator("const").operator("ref"), - "data", - ), - ], - return_type=cg.optional.template(float), - ) - cg.add(var.set_template(template_)) + await add_modbus_base_properties(var, config, ModbusNumber) if CONF_WRITE_LAMBDA in config: template_ = await cg.process_lambda( config[CONF_WRITE_LAMBDA], diff --git a/esphome/components/modbus_controller/number/modbus_number.cpp b/esphome/components/modbus_controller/number/modbus_number.cpp index 95c6ac6f6a..ba2ffdd09f 100644 --- a/esphome/components/modbus_controller/number/modbus_number.cpp +++ b/esphome/components/modbus_controller/number/modbus_number.cpp @@ -8,11 +8,6 @@ namespace modbus_controller { static const char *const TAG = "modbus.number"; void ModbusNumber::parse_and_publish(const std::vector &data) { - union { - float float_value; - uint32_t raw; - } raw_to_float; - float result = payload_to_float(data, *this); // Is there a lambda registered @@ -31,13 +26,7 @@ void ModbusNumber::parse_and_publish(const std::vector &data) { } void ModbusNumber::control(float value) { - union { - float float_value; - uint32_t raw; - } raw_to_float; - std::vector data; - auto original_value = value; // Is there are lambda configured? if (this->write_transform_func_.has_value()) { // data is passed by reference diff --git a/esphome/components/modbus_controller/output/__init__.py b/esphome/components/modbus_controller/output/__init__.py index 4aca4db64f..eacd96579f 100644 --- a/esphome/components/modbus_controller/output/__init__.py +++ b/esphome/components/modbus_controller/output/__init__.py @@ -6,24 +6,21 @@ from esphome.const import ( CONF_ADDRESS, CONF_ID, CONF_MULTIPLY, - CONF_OFFSET, ) from .. import ( - SensorItem, modbus_controller_ns, - ModbusController, - TYPE_REGISTER_MAP, + modbus_calc_properties, + validate_modbus_register, + ModbusItemBaseSchema, + SensorItem, ) from ..const import ( - CONF_BYTE_OFFSET, CONF_MODBUS_CONTROLLER_ID, - CONF_REGISTER_COUNT, CONF_VALUE_TYPE, CONF_WRITE_LAMBDA, ) -from ..sensor import SENSOR_VALUE_TYPE DEPENDENCIES = ["modbus_controller"] CODEOWNERS = ["@martgras"] @@ -34,38 +31,24 @@ ModbusOutput = modbus_controller_ns.class_( ) CONFIG_SCHEMA = cv.All( - output.FLOAT_OUTPUT_SCHEMA.extend( + output.FLOAT_OUTPUT_SCHEMA.extend(ModbusItemBaseSchema).extend( { - cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController), cv.GenerateID(): cv.declare_id(ModbusOutput), - cv.Required(CONF_ADDRESS): cv.positive_int, - cv.Optional(CONF_OFFSET, default=0): cv.positive_int, - cv.Optional(CONF_BYTE_OFFSET): cv.positive_int, - cv.Optional(CONF_VALUE_TYPE, default="U_WORD"): cv.enum(SENSOR_VALUE_TYPE), - cv.Optional(CONF_REGISTER_COUNT, default=0): cv.positive_int, cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda, cv.Optional(CONF_MULTIPLY, default=1.0): cv.float_, } ), + validate_modbus_register, ) async def to_code(config): - byte_offset = 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] - value_type = config[CONF_VALUE_TYPE] - reg_count = config[CONF_REGISTER_COUNT] - if reg_count == 0: - reg_count = TYPE_REGISTER_MAP[value_type] + byte_offset, reg_count = modbus_calc_properties(config) var = cg.new_Pvariable( config[CONF_ID], config[CONF_ADDRESS], byte_offset, - value_type, + config[CONF_VALUE_TYPE], reg_count, ) await output.register_output(var, config) diff --git a/esphome/components/modbus_controller/output/modbus_output.cpp b/esphome/components/modbus_controller/output/modbus_output.cpp index f7d7c42342..d2b5d02bda 100644 --- a/esphome/components/modbus_controller/output/modbus_output.cpp +++ b/esphome/components/modbus_controller/output/modbus_output.cpp @@ -13,11 +13,6 @@ void ModbusOutput::setup() {} * */ void ModbusOutput::write_state(float value) { - union { - float float_value; - uint32_t raw; - } raw_to_float; - std::vector data; auto original_value = value; // Is there are lambda configured? diff --git a/esphome/components/modbus_controller/sensor/__init__.py b/esphome/components/modbus_controller/sensor/__init__.py index 82acfe120b..da7b8928b4 100644 --- a/esphome/components/modbus_controller/sensor/__init__.py +++ b/esphome/components/modbus_controller/sensor/__init__.py @@ -2,18 +2,19 @@ from esphome.components import sensor import esphome.config_validation as cv import esphome.codegen as cg -from esphome.const import CONF_ID, CONF_ADDRESS, CONF_LAMBDA, CONF_OFFSET +from esphome.const import CONF_ID, CONF_ADDRESS from .. import ( - SensorItem, + add_modbus_base_properties, modbus_controller_ns, - ModbusController, + modbus_calc_properties, + validate_modbus_register, + ModbusItemBaseSchema, + SensorItem, MODBUS_REGISTER_TYPE, SENSOR_VALUE_TYPE, - TYPE_REGISTER_MAP, ) from ..const import ( CONF_BITMASK, - CONF_BYTE_OFFSET, CONF_FORCE_NEW_RANGE, CONF_MODBUS_CONTROLLER_ID, CONF_REGISTER_COUNT, @@ -31,43 +32,30 @@ ModbusSensor = modbus_controller_ns.class_( ) CONFIG_SCHEMA = cv.All( - sensor.SENSOR_SCHEMA.extend( + sensor.SENSOR_SCHEMA.extend(cv.COMPONENT_SCHEMA) + .extend(ModbusItemBaseSchema) + .extend( { cv.GenerateID(): cv.declare_id(ModbusSensor), - cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController), - 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=0xFFFFFFFF): cv.hex_uint32_t, + cv.Optional(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE), cv.Optional(CONF_VALUE_TYPE, default="U_WORD"): cv.enum(SENSOR_VALUE_TYPE), cv.Optional(CONF_REGISTER_COUNT, default=0): cv.positive_int, - 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): - byte_offset = 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] + byte_offset, reg_count = modbus_calc_properties(config) value_type = config[CONF_VALUE_TYPE] - reg_count = config[CONF_REGISTER_COUNT] - if reg_count == 0: - reg_count = TYPE_REGISTER_MAP[value_type] var = cg.new_Pvariable( config[CONF_ID], config[CONF_REGISTER_TYPE], config[CONF_ADDRESS], byte_offset, config[CONF_BITMASK], - config[CONF_VALUE_TYPE], + value_type, reg_count, config[CONF_SKIP_UPDATES], config[CONF_FORCE_NEW_RANGE], @@ -77,17 +65,4 @@ async def to_code(config): paren = await cg.get_variable(config[CONF_MODBUS_CONTROLLER_ID]) cg.add(paren.add_sensor_item(var)) - if CONF_LAMBDA in config: - template_ = await cg.process_lambda( - config[CONF_LAMBDA], - [ - (ModbusSensor.operator("ptr"), "item"), - (cg.float_, "x"), - ( - cg.std_vector.template(cg.uint8).operator("const").operator("ref"), - "data", - ), - ], - return_type=cg.optional.template(float), - ) - cg.add(var.set_template(template_)) + await add_modbus_base_properties(var, config, ModbusSensor) diff --git a/esphome/components/modbus_controller/sensor/modbus_sensor.cpp b/esphome/components/modbus_controller/sensor/modbus_sensor.cpp index dbd0525347..a21fd91032 100644 --- a/esphome/components/modbus_controller/sensor/modbus_sensor.cpp +++ b/esphome/components/modbus_controller/sensor/modbus_sensor.cpp @@ -10,11 +10,6 @@ static const char *const TAG = "modbus_controller.sensor"; void ModbusSensor::dump_config() { LOG_SENSOR(TAG, "Modbus Controller Sensor", this); } void ModbusSensor::parse_and_publish(const std::vector &data) { - union { - float float_value; - uint32_t raw; - } raw_to_float; - float result = payload_to_float(data, *this); // Is there a lambda registered diff --git a/esphome/components/modbus_controller/sensor/modbus_sensor.h b/esphome/components/modbus_controller/sensor/modbus_sensor.h index 4f48c2a4dd..37ea9d0dd0 100644 --- a/esphome/components/modbus_controller/sensor/modbus_sensor.h +++ b/esphome/components/modbus_controller/sensor/modbus_sensor.h @@ -25,6 +25,7 @@ class ModbusSensor : public Component, public sensor::Sensor, public SensorItem void parse_and_publish(const std::vector &data) override; void dump_config() override; using transform_func_t = std::function(ModbusSensor *, float, const std::vector &)>; + void set_template(transform_func_t &&f) { this->transform_func_ = f; } protected: diff --git a/esphome/components/modbus_controller/switch/__init__.py b/esphome/components/modbus_controller/switch/__init__.py index e03b0d37be..df11b268ac 100644 --- a/esphome/components/modbus_controller/switch/__init__.py +++ b/esphome/components/modbus_controller/switch/__init__.py @@ -3,21 +3,25 @@ import esphome.config_validation as cv import esphome.codegen as cg -from esphome.const import CONF_ID, CONF_ADDRESS, CONF_LAMBDA, CONF_OFFSET +from esphome.const import CONF_ID, CONF_ADDRESS from .. import ( - MODBUS_REGISTER_TYPE, - SensorItem, + add_modbus_base_properties, modbus_controller_ns, - ModbusController, + modbus_calc_properties, + validate_modbus_register, + ModbusItemBaseSchema, + SensorItem, + MODBUS_REGISTER_TYPE, ) from ..const import ( CONF_BITMASK, - CONF_BYTE_OFFSET, CONF_FORCE_NEW_RANGE, CONF_MODBUS_CONTROLLER_ID, CONF_REGISTER_TYPE, + CONF_WRITE_LAMBDA, ) +CONF_USE_WRITE_MULTIPLE = "use_write_multiple" DEPENDENCIES = ["modbus_controller"] CODEOWNERS = ["@martgras"] @@ -26,31 +30,23 @@ ModbusSwitch = modbus_controller_ns.class_( "ModbusSwitch", cg.Component, switch.Switch, SensorItem ) - CONFIG_SCHEMA = cv.All( - switch.SWITCH_SCHEMA.extend( + switch.SWITCH_SCHEMA.extend(cv.COMPONENT_SCHEMA) + .extend(ModbusItemBaseSchema) + .extend( { cv.GenerateID(): cv.declare_id(ModbusSwitch), - cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController), - cv.Required(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE), - cv.Required(CONF_ADDRESS): cv.positive_int, - 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_FORCE_NEW_RANGE, default=False): cv.boolean, - cv.Optional(CONF_LAMBDA): cv.returning_lambda, + cv.Optional(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE), + cv.Optional(CONF_USE_WRITE_MULTIPLE, default=False): cv.boolean, + cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda, } - ).extend(cv.COMPONENT_SCHEMA), + ), + validate_modbus_register, ) async def to_code(config): - byte_offset = 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] + byte_offset, _ = modbus_calc_properties(config) var = cg.new_Pvariable( config[CONF_ID], config[CONF_REGISTER_TYPE], @@ -63,19 +59,18 @@ async def to_code(config): await switch.register_switch(var, config) paren = await cg.get_variable(config[CONF_MODBUS_CONTROLLER_ID]) - cg.add(paren.add_sensor_item(var)) cg.add(var.set_parent(paren)) - if CONF_LAMBDA in config: - publish_template_ = await cg.process_lambda( - config[CONF_LAMBDA], + cg.add(var.set_use_write_mutiple(config[CONF_USE_WRITE_MULTIPLE])) + cg.add(paren.add_sensor_item(var)) + if CONF_WRITE_LAMBDA in config: + template_ = await cg.process_lambda( + config[CONF_WRITE_LAMBDA], [ (ModbusSwitch.operator("ptr"), "item"), - (bool, "x"), - ( - cg.std_vector.template(cg.uint8).operator("const").operator("ref"), - "data", - ), + (cg.bool_, "x"), + (cg.std_vector.template(cg.uint8).operator("ref"), "payload"), ], return_type=cg.optional.template(bool), ) - cg.add(var.set_template(publish_template_)) + cg.add(var.set_write_template(template_)) + await add_modbus_base_properties(var, config, ModbusSwitch, bool, bool) diff --git a/esphome/components/modbus_controller/switch/modbus_switch.cpp b/esphome/components/modbus_controller/switch/modbus_switch.cpp index ce9557e6c4..c7c3c419d4 100644 --- a/esphome/components/modbus_controller/switch/modbus_switch.cpp +++ b/esphome/components/modbus_controller/switch/modbus_switch.cpp @@ -45,22 +45,50 @@ void ModbusSwitch::parse_and_publish(const std::vector &data) { void ModbusSwitch::write_state(bool state) { // This will be called every time the user requests a state change. ModbusCommandItem cmd; - ESP_LOGV(TAG, "write_state '%s': new value = %s type = %d address = %X offset = %x", this->get_name().c_str(), - ONOFF(state), (int) this->register_type, this->start_address, this->offset); - switch (this->register_type) { - case ModbusRegisterType::COIL: + std::vector data; + // Is there are lambda configured? + if (this->write_transform_func_.has_value()) { + // data is passed by reference + // the lambda can fill the empty vector directly + // in that case the return value is ignored + auto val = (*this->write_transform_func_)(this, state, data); + if (val.has_value()) { + ESP_LOGV(TAG, "Value overwritten by lambda"); + state = val.value(); + } else { + ESP_LOGV(TAG, "Communication handled by lambda - exiting control"); + return; + } + } + if (!data.empty()) { + ESP_LOGV(TAG, "Modbus Switch write raw: %s", hexencode(data).c_str()); + cmd = ModbusCommandItem::create_custom_command( + this->parent_, data, + [this, cmd](ModbusRegisterType register_type, uint16_t start_address, const std::vector &data) { + this->parent_->on_write_register_response(cmd.register_type, this->start_address, data); + }); + } else { + ESP_LOGV(TAG, "write_state '%s': new value = %s type = %d address = %X offset = %x", this->get_name().c_str(), + ONOFF(state), (int) this->register_type, this->start_address, this->offset); + if (this->register_type == ModbusRegisterType::COIL) { // offset for coil and discrete inputs is the coil/register number not bytes - cmd = ModbusCommandItem::create_write_single_coil(parent_, this->start_address + this->offset, state); - break; - case ModbusRegisterType::DISCRETE_INPUT: - cmd = ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset, state); - break; - - default: + if (this->use_write_multiple_) { + std::vector states{state}; + cmd = ModbusCommandItem::create_write_multiple_coils(parent_, this->start_address + this->offset, states); + } else { + cmd = ModbusCommandItem::create_write_single_coil(parent_, this->start_address + this->offset, state); + } + } else { // since offset is in bytes and a register is 16 bits we get the start by adding offset/2 - cmd = ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset / 2, - state ? 0xFFFF & this->bitmask : 0); - break; + if (this->use_write_multiple_) { + std::vector bool_states(1, state ? (0xFFFF & this->bitmask) : 0); + cmd = ModbusCommandItem::create_write_multiple_command(parent_, this->start_address + this->offset / 2, 1, + bool_states); + } else { + cmd = ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset / 2, + state ? 0xFFFF & this->bitmask : 0u); + } + } } this->parent_->queue_command(cmd); publish_state(state); diff --git a/esphome/components/modbus_controller/switch/modbus_switch.h b/esphome/components/modbus_controller/switch/modbus_switch.h index a38668fabb..5ac2af01a1 100644 --- a/esphome/components/modbus_controller/switch/modbus_switch.h +++ b/esphome/components/modbus_controller/switch/modbus_switch.h @@ -33,11 +33,16 @@ class ModbusSwitch : public Component, public switch_::Switch, public SensorItem void set_parent(ModbusController *parent) { this->parent_ = parent; } using transform_func_t = std::function(ModbusSwitch *, bool, const std::vector &)>; + using write_transform_func_t = std::function(ModbusSwitch *, bool, std::vector &)>; void set_template(transform_func_t &&f) { this->publish_transform_func_ = f; } + void set_write_template(write_transform_func_t &&f) { this->write_transform_func_ = f; } + void set_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; } protected: ModbusController *parent_; + bool use_write_multiple_; optional publish_transform_func_{nullopt}; + optional write_transform_func_{nullopt}; }; } // namespace modbus_controller diff --git a/esphome/components/modbus_controller/text_sensor/__init__.py b/esphome/components/modbus_controller/text_sensor/__init__.py index 2c02c86795..5cc85af5bc 100644 --- a/esphome/components/modbus_controller/text_sensor/__init__.py +++ b/esphome/components/modbus_controller/text_sensor/__init__.py @@ -3,15 +3,17 @@ import esphome.config_validation as cv import esphome.codegen as cg -from esphome.const import CONF_ID, CONF_ADDRESS, CONF_LAMBDA, CONF_OFFSET +from esphome.const import CONF_ADDRESS, CONF_ID from .. import ( - SensorItem, + add_modbus_base_properties, modbus_controller_ns, - ModbusController, + modbus_calc_properties, + validate_modbus_register, + ModbusItemBaseSchema, + SensorItem, MODBUS_REGISTER_TYPE, ) from ..const import ( - CONF_BYTE_OFFSET, CONF_FORCE_NEW_RANGE, CONF_MODBUS_CONTROLLER_ID, CONF_REGISTER_COUNT, @@ -38,32 +40,23 @@ RAW_ENCODING = { } CONFIG_SCHEMA = cv.All( - text_sensor.TEXT_SENSOR_SCHEMA.extend( + text_sensor.TEXT_SENSOR_SCHEMA.extend(cv.COMPONENT_SCHEMA) + .extend(ModbusItemBaseSchema) + .extend( { cv.GenerateID(): cv.declare_id(ModbusTextSensor), - cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController), - cv.Required(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE), - cv.Required(CONF_ADDRESS): cv.positive_int, - cv.Optional(CONF_OFFSET, default=0): cv.positive_int, - cv.Optional(CONF_BYTE_OFFSET): cv.positive_int, + cv.Optional(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE), cv.Optional(CONF_REGISTER_COUNT, default=0): cv.positive_int, cv.Optional(CONF_RESPONSE_SIZE, default=2): cv.positive_int, cv.Optional(CONF_RAW_ENCODE, default="NONE"): cv.enum(RAW_ENCODING), - 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): - byte_offset = 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] + byte_offset, reg_count = modbus_calc_properties(config) response_size = config[CONF_RESPONSE_SIZE] reg_count = config[CONF_REGISTER_COUNT] if reg_count == 0: @@ -85,17 +78,6 @@ async def to_code(config): paren = await cg.get_variable(config[CONF_MODBUS_CONTROLLER_ID]) cg.add(paren.add_sensor_item(var)) - if CONF_LAMBDA in config: - template_ = await cg.process_lambda( - config[CONF_LAMBDA], - [ - (ModbusTextSensor.operator("ptr"), "item"), - (cg.std_string.operator("const").operator("ref"), "x"), - ( - cg.std_vector.template(cg.uint8).operator("const").operator("ref"), - "data", - ), - ], - return_type=cg.optional.template(cg.std_string), - ) - cg.add(var.set_template(template_)) + await add_modbus_base_properties( + var, config, ModbusTextSensor, cg.std_string, cg.std_string + ) diff --git a/esphome/components/modbus_controller/text_sensor/modbus_textsensor.cpp b/esphome/components/modbus_controller/text_sensor/modbus_textsensor.cpp index a06d44e90b..25b79474e8 100644 --- a/esphome/components/modbus_controller/text_sensor/modbus_textsensor.cpp +++ b/esphome/components/modbus_controller/text_sensor/modbus_textsensor.cpp @@ -13,7 +13,7 @@ void ModbusTextSensor::dump_config() { LOG_TEXT_SENSOR("", "Modbus Controller Te void ModbusTextSensor::parse_and_publish(const std::vector &data) { std::ostringstream output; - uint8_t max_items = this->response_bytes_; + uint8_t max_items = this->response_bytes; char buffer[4]; bool add_comma = false; for (auto b : data) { diff --git a/esphome/components/modbus_controller/text_sensor/modbus_textsensor.h b/esphome/components/modbus_controller/text_sensor/modbus_textsensor.h index 77b5b9363a..3db4d94a45 100644 --- a/esphome/components/modbus_controller/text_sensor/modbus_textsensor.h +++ b/esphome/components/modbus_controller/text_sensor/modbus_textsensor.h @@ -17,7 +17,7 @@ class ModbusTextSensor : public Component, public text_sensor::TextSensor, publi this->register_type = register_type; this->start_address = start_address; this->offset = offset; - this->response_bytes_ = response_bytes; + this->response_bytes = response_bytes; this->register_count = register_count; this->encode_ = encode; this->skip_updates = skip_updates; @@ -38,7 +38,6 @@ class ModbusTextSensor : public Component, public text_sensor::TextSensor, publi protected: RawEncoding encode_; - uint16_t response_bytes_; }; } // namespace modbus_controller diff --git a/esphome/components/mqtt/__init__.py b/esphome/components/mqtt/__init__.py index 0f7d246473..d677d54d23 100644 --- a/esphome/components/mqtt/__init__.py +++ b/esphome/components/mqtt/__init__.py @@ -14,6 +14,7 @@ from esphome.const import ( CONF_DISCOVERY, CONF_DISCOVERY_PREFIX, CONF_DISCOVERY_RETAIN, + CONF_DISCOVERY_UNIQUE_ID_GENERATOR, CONF_ID, CONF_KEEPALIVE, CONF_LEVEL, @@ -94,6 +95,13 @@ MQTTSwitchComponent = mqtt_ns.class_("MQTTSwitchComponent", MQTTComponent) MQTTTextSensor = mqtt_ns.class_("MQTTTextSensor", MQTTComponent) MQTTNumberComponent = mqtt_ns.class_("MQTTNumberComponent", MQTTComponent) MQTTSelectComponent = mqtt_ns.class_("MQTTSelectComponent", MQTTComponent) +MQTTButtonComponent = mqtt_ns.class_("MQTTButtonComponent", MQTTComponent) + +MQTTDiscoveryUniqueIdGenerator = mqtt_ns.enum("MQTTDiscoveryUniqueIdGenerator") +MQTT_DISCOVERY_UNIQUE_ID_GENERATOR_OPTIONS = { + "legacy": MQTTDiscoveryUniqueIdGenerator.MQTT_LEGACY_UNIQUE_ID_GENERATOR, + "mac": MQTTDiscoveryUniqueIdGenerator.MQTT_MAC_ADDRESS_UNIQUE_ID_GENERATOR, +} def validate_config(value): @@ -153,6 +161,9 @@ CONFIG_SCHEMA = cv.All( cv.Optional( CONF_DISCOVERY_PREFIX, default="homeassistant" ): cv.publish_topic, + cv.Optional(CONF_DISCOVERY_UNIQUE_ID_GENERATOR, default="legacy"): cv.enum( + MQTT_DISCOVERY_UNIQUE_ID_GENERATOR_OPTIONS + ), cv.Optional(CONF_USE_ABBREVIATIONS, default=True): cv.boolean, cv.Optional(CONF_BIRTH_MESSAGE): MQTT_MESSAGE_SCHEMA, cv.Optional(CONF_WILL_MESSAGE): MQTT_MESSAGE_SCHEMA, @@ -231,13 +242,22 @@ async def to_code(config): discovery = config[CONF_DISCOVERY] discovery_retain = config[CONF_DISCOVERY_RETAIN] discovery_prefix = config[CONF_DISCOVERY_PREFIX] + discovery_unique_id_generator = config[CONF_DISCOVERY_UNIQUE_ID_GENERATOR] if not discovery: cg.add(var.disable_discovery()) elif discovery == "CLEAN": - cg.add(var.set_discovery_info(discovery_prefix, discovery_retain, True)) + cg.add( + var.set_discovery_info( + discovery_prefix, discovery_unique_id_generator, discovery_retain, True + ) + ) elif CONF_DISCOVERY_RETAIN in config or CONF_DISCOVERY_PREFIX in config: - cg.add(var.set_discovery_info(discovery_prefix, discovery_retain)) + cg.add( + var.set_discovery_info( + discovery_prefix, discovery_unique_id_generator, discovery_retain + ) + ) cg.add(var.set_topic_prefix(config[CONF_TOPIC_PREFIX])) diff --git a/esphome/components/mqtt/mqtt_button.cpp b/esphome/components/mqtt/mqtt_button.cpp new file mode 100644 index 0000000000..25ff327cf9 --- /dev/null +++ b/esphome/components/mqtt/mqtt_button.cpp @@ -0,0 +1,45 @@ +#include "mqtt_button.h" +#include "esphome/core/log.h" + +#include "mqtt_const.h" + +#ifdef USE_MQTT +#ifdef USE_BUTTON + +namespace esphome { +namespace mqtt { + +static const char *const TAG = "mqtt.button"; + +using namespace esphome::button; + +MQTTButtonComponent::MQTTButtonComponent(button::Button *button) : MQTTComponent(), button_(button) {} + +void MQTTButtonComponent::setup() { + this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &payload) { + if (payload == "press") { + this->button_->press(); + } else { + ESP_LOGW(TAG, "'%s': Received unknown status payload: %s", this->friendly_name().c_str(), payload.c_str()); + this->status_momentary_warning("state", 5000); + } + }); +} +void MQTTButtonComponent::dump_config() { + ESP_LOGCONFIG(TAG, "MQTT Button '%s': ", this->button_->get_name().c_str()); + LOG_MQTT_COMPONENT(true, true); +} + +void MQTTButtonComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { + if (!this->button_->get_device_class().empty()) + root[MQTT_DEVICE_CLASS] = this->button_->get_device_class(); +} + +std::string MQTTButtonComponent::component_type() const { return "button"; } +const EntityBase *MQTTButtonComponent::get_entity() const { return this->button_; } + +} // namespace mqtt +} // namespace esphome + +#endif +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_button.h b/esphome/components/mqtt/mqtt_button.h new file mode 100644 index 0000000000..66e4b2609f --- /dev/null +++ b/esphome/components/mqtt/mqtt_button.h @@ -0,0 +1,40 @@ +#pragma once + +#include "esphome/core/defines.h" + +#ifdef USE_MQTT +#ifdef USE_BUTTON + +#include "esphome/components/button/button.h" +#include "mqtt_component.h" + +namespace esphome { +namespace mqtt { + +class MQTTButtonComponent : public mqtt::MQTTComponent { + public: + explicit MQTTButtonComponent(button::Button *button); + + // ========== INTERNAL METHODS ========== + // (In most use cases you won't need these) + void setup() override; + void dump_config() override; + + /// Buttons do not send a state so just return true. + bool send_initial_state() override { return true; } + + void send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) override; + + protected: + /// "button" component type. + std::string component_type() const override; + const EntityBase *get_entity() const override; + + button::Button *button_; +}; + +} // namespace mqtt +} // namespace esphome + +#endif +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index 040b0001fe..43c49e9f7f 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -535,8 +535,10 @@ void MQTTClientComponent::set_birth_message(MQTTMessage &&message) { void MQTTClientComponent::set_shutdown_message(MQTTMessage &&message) { this->shutdown_message_ = std::move(message); } -void MQTTClientComponent::set_discovery_info(std::string &&prefix, bool retain, bool clean) { +void MQTTClientComponent::set_discovery_info(std::string &&prefix, MQTTDiscoveryUniqueIdGenerator unique_id_generator, + bool retain, bool clean) { this->discovery_info_.prefix = std::move(prefix); + this->discovery_info_.unique_id_generator = unique_id_generator; this->discovery_info_.retain = retain; this->discovery_info_.clean = clean; } diff --git a/esphome/components/mqtt/mqtt_client.h b/esphome/components/mqtt/mqtt_client.h index fa689eaa04..d6194da794 100644 --- a/esphome/components/mqtt/mqtt_client.h +++ b/esphome/components/mqtt/mqtt_client.h @@ -55,6 +55,12 @@ struct Availability { std::string payload_not_available; }; +/// available discovery unique_id generators +enum MQTTDiscoveryUniqueIdGenerator { + MQTT_LEGACY_UNIQUE_ID_GENERATOR = 0, + MQTT_MAC_ADDRESS_UNIQUE_ID_GENERATOR, +}; + /** Internal struct for MQTT Home Assistant discovery * * See MQTT Discovery. @@ -63,6 +69,7 @@ struct MQTTDiscoveryInfo { std::string prefix; ///< The Home Assistant discovery prefix. Empty means disabled. bool retain; ///< Whether to retain discovery messages. bool clean; + MQTTDiscoveryUniqueIdGenerator unique_id_generator; }; enum MQTTClientState { @@ -98,9 +105,11 @@ class MQTTClientComponent : public Component { * * See MQTT Discovery. * @param prefix The Home Assistant discovery prefix. + * @param unique_id_generator Controls how UniqueId is generated. * @param retain Whether to retain discovery messages. */ - void set_discovery_info(std::string &&prefix, bool retain, bool clean = false); + void set_discovery_info(std::string &&prefix, MQTTDiscoveryUniqueIdGenerator unique_id_generator, bool retain, + bool clean = false); /// Get Home Assistant discovery info. const MQTTDiscoveryInfo &get_discovery_info() const; /// Globally disable Home Assistant discovery. diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp index e3ae4dea50..bf9f5e34b8 100644 --- a/esphome/components/mqtt/mqtt_component.cpp +++ b/esphome/components/mqtt/mqtt_component.cpp @@ -114,9 +114,17 @@ bool MQTTComponent::send_discovery_() { if (!unique_id.empty()) { root[MQTT_UNIQUE_ID] = unique_id; } else { - // default to almost-unique ID. It's a hack but the only way to get that - // gorgeous device registry view. - root[MQTT_UNIQUE_ID] = "ESP" + this->component_type() + this->get_default_object_id_(); + const MQTTDiscoveryInfo &discovery_info = global_mqtt_client->get_discovery_info(); + if (discovery_info.unique_id_generator == MQTT_MAC_ADDRESS_UNIQUE_ID_GENERATOR) { + char friendly_name_hash[9]; + sprintf(friendly_name_hash, "%08x", fnv1_hash(this->friendly_name())); + friendly_name_hash[8] = 0; // ensure the hash-string ends with null + root[MQTT_UNIQUE_ID] = get_mac_address() + "-" + this->component_type() + "-" + friendly_name_hash; + } else { + // default to almost-unique ID. It's a hack but the only way to get that + // gorgeous device registry view. + root[MQTT_UNIQUE_ID] = "ESP" + this->component_type() + this->get_default_object_id_(); + } } JsonObject &device_info = root.createNestedObject(MQTT_DEVICE); diff --git a/esphome/components/mqtt/mqtt_const.h b/esphome/components/mqtt/mqtt_const.h index 1d5e22efde..8134a6b53e 100644 --- a/esphome/components/mqtt/mqtt_const.h +++ b/esphome/components/mqtt/mqtt_const.h @@ -514,6 +514,7 @@ constexpr const char *const MQTT_DEVICE_SUGGESTED_AREA = "suggested_area"; // Additional MQTT fields where no abbreviation is defined in HA source constexpr const char *const MQTT_ENTITY_CATEGORY = "entity_category"; +constexpr const char *const MQTT_MODE = "mode"; } // namespace mqtt } // namespace esphome diff --git a/esphome/components/mqtt/mqtt_number.cpp b/esphome/components/mqtt/mqtt_number.cpp index 337013055a..18e3a61417 100644 --- a/esphome/components/mqtt/mqtt_number.cpp +++ b/esphome/components/mqtt/mqtt_number.cpp @@ -43,6 +43,18 @@ void MQTTNumberComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryCo root[MQTT_MIN] = traits.get_min_value(); root[MQTT_MAX] = traits.get_max_value(); root[MQTT_STEP] = traits.get_step(); + if (!this->number_->traits.get_unit_of_measurement().empty()) + root[MQTT_UNIT_OF_MEASUREMENT] = this->number_->traits.get_unit_of_measurement(); + switch (this->number_->traits.get_mode()) { + case NUMBER_MODE_AUTO: + break; + case NUMBER_MODE_BOX: + root[MQTT_MODE] = "box"; + break; + case NUMBER_MODE_SLIDER: + root[MQTT_MODE] = "slider"; + break; + } config.command_topic = true; } diff --git a/esphome/components/neopixelbus/_methods.py b/esphome/components/neopixelbus/_methods.py index b03544f246..4e3c3ca778 100644 --- a/esphome/components/neopixelbus/_methods.py +++ b/esphome/components/neopixelbus/_methods.py @@ -88,8 +88,8 @@ def _esp32_i2s_default_bus(): def _validate_esp32_i2s_bus(value): - if isinstance(value, str) and value.lower() == CHANNEL_DYNAMIC: - value = CHANNEL_DYNAMIC + if isinstance(value, str) and value.lower() == BUS_DYNAMIC: + value = BUS_DYNAMIC else: value = cv.int_(value) variant_buses = { diff --git a/esphome/components/nextion/nextion.cpp b/esphome/components/nextion/nextion.cpp index f23f55c9bb..494765db4d 100644 --- a/esphome/components/nextion/nextion.cpp +++ b/esphome/components/nextion/nextion.cpp @@ -64,7 +64,7 @@ bool Nextion::check_connect_() { if (response.empty() || response.find("comok") == std::string::npos) { #ifdef NEXTION_PROTOCOL_LOG ESP_LOGN(TAG, "Bad connect request %s", response.c_str()); - for (int i = 0; i < response.length(); i++) { + for (size_t i = 0; i < response.length(); i++) { ESP_LOGN(TAG, "response %s %d %d %c", response.c_str(), i, response[i], response[i]); } #endif @@ -563,11 +563,10 @@ void Nextion::process_nextion_commands_() { // FF FF FF - End case 0x90: { // Switched component std::string variable_name; - uint8_t index = 0; // Get variable name - index = to_process.find('\0'); - if (static_cast(index) == std::string::npos || (to_process_length - index - 1) < 1) { + auto index = to_process.find('\0'); + if (index == std::string::npos || (to_process_length - index - 1) < 1) { ESP_LOGE(TAG, "Bad switch component data received for 0x90 event!"); ESP_LOGN(TAG, "to_process %s %zu %d", to_process.c_str(), to_process_length, index); break; @@ -591,10 +590,9 @@ void Nextion::process_nextion_commands_() { // FF FF FF - End case 0x91: { // Sensor component std::string variable_name; - uint8_t index = 0; - index = to_process.find('\0'); - if (static_cast(index) == std::string::npos || (to_process_length - index - 1) != 4) { + auto index = to_process.find('\0'); + if (index == std::string::npos || (to_process_length - index - 1) != 4) { ESP_LOGE(TAG, "Bad sensor component data received for 0x91 event!"); ESP_LOGN(TAG, "to_process %s %zu %d", to_process.c_str(), to_process_length, index); break; @@ -626,11 +624,10 @@ void Nextion::process_nextion_commands_() { case 0x92: { // Text Sensor Component std::string variable_name; std::string text_value; - uint8_t index = 0; // Get variable name - index = to_process.find('\0'); - if (static_cast(index) == std::string::npos || (to_process_length - index - 1) < 1) { + auto index = to_process.find('\0'); + if (index == std::string::npos || (to_process_length - index - 1) < 1) { ESP_LOGE(TAG, "Bad text sensor component data received for 0x92 event!"); ESP_LOGN(TAG, "to_process %s %zu %d", to_process.c_str(), to_process_length, index); break; @@ -660,11 +657,10 @@ void Nextion::process_nextion_commands_() { // FF FF FF - End case 0x93: { // Binary Sensor component std::string variable_name; - uint8_t index = 0; // Get variable name - index = to_process.find('\0'); - if (static_cast(index) == std::string::npos || (to_process_length - index - 1) < 1) { + auto index = to_process.find('\0'); + if (index == std::string::npos || (to_process_length - index - 1) < 1) { ESP_LOGE(TAG, "Bad binary sensor component data received for 0x92 event!"); ESP_LOGN(TAG, "to_process %s %zu %d", to_process.c_str(), to_process_length, index); break; @@ -736,7 +732,7 @@ void Nextion::process_nextion_commands_() { uint32_t ms = millis(); if (!this->nextion_queue_.empty() && this->nextion_queue_.front()->queue_time + this->max_q_age_ms_ < ms) { - for (int i = 0; i < this->nextion_queue_.size(); i++) { + for (size_t i = 0; i < this->nextion_queue_.size(); i++) { NextionComponentBase *component = this->nextion_queue_[i]->component; if (this->nextion_queue_[i]->queue_time + this->max_q_age_ms_ < ms) { if (this->nextion_queue_[i]->queue_time == 0) diff --git a/esphome/components/nextion/nextion_upload.cpp b/esphome/components/nextion/nextion_upload.cpp index cd1c073320..b16f2fe7eb 100644 --- a/esphome/components/nextion/nextion_upload.cpp +++ b/esphome/components/nextion/nextion_upload.cpp @@ -95,7 +95,7 @@ int Nextion::upload_by_chunks_(HTTPClient *http, int range_start) { } http->end(); ESP_LOGN(TAG, "this->content_length_ %d sent %d", this->content_length_, sent); - for (uint32_t i = 0; i < range; i += 4096) { + for (int i = 0; i < range; i += 4096) { this->write_array(&this->transfer_buffer_[i], 4096); this->content_length_ -= 4096; ESP_LOGN(TAG, "this->content_length_ %d range %d range_end %d range_start %d", this->content_length_, range, @@ -238,7 +238,7 @@ void Nextion::upload_tft() { // The Nextion display will, if it's ready to accept data, send a 0x05 byte. ESP_LOGD(TAG, "Upgrade response is %s %zu", response.c_str(), response.length()); - for (int i = 0; i < response.length(); i++) { + for (size_t i = 0; i < response.length(); i++) { ESP_LOGD(TAG, "Available %d : 0x%02X", i, response[i]); } diff --git a/esphome/components/nextion/sensor/nextion_sensor.cpp b/esphome/components/nextion/sensor/nextion_sensor.cpp index 4b7532d32d..32bfccf9f8 100644 --- a/esphome/components/nextion/sensor/nextion_sensor.cpp +++ b/esphome/components/nextion/sensor/nextion_sensor.cpp @@ -24,7 +24,7 @@ void NextionSensor::add_to_wave_buffer(float state) { wave_buffer_.push_back(wave_state); - if (this->wave_buffer_.size() > this->wave_max_length_) { + if (this->wave_buffer_.size() > (size_t) this->wave_max_length_) { this->wave_buffer_.erase(this->wave_buffer_.begin()); } } diff --git a/esphome/components/nfc/ndef_message.cpp b/esphome/components/nfc/ndef_message.cpp index d8c940254e..d7d134aedb 100644 --- a/esphome/components/nfc/ndef_message.cpp +++ b/esphome/components/nfc/ndef_message.cpp @@ -93,7 +93,7 @@ bool NdefMessage::add_uri_record(const std::string &uri) { return this->add_reco std::vector NdefMessage::encode() { std::vector data; - for (uint8_t i = 0; i < this->records_.size(); i++) { + for (size_t i = 0; i < this->records_.size(); i++) { auto encoded_record = this->records_[i]->encode(i == 0, (i + 1) == this->records_.size()); data.insert(data.end(), encoded_record.begin(), encoded_record.end()); } diff --git a/esphome/components/nfc/nfc.cpp b/esphome/components/nfc/nfc.cpp index 706c09a5aa..09dbdcfe94 100644 --- a/esphome/components/nfc/nfc.cpp +++ b/esphome/components/nfc/nfc.cpp @@ -10,7 +10,7 @@ static const char *const TAG = "nfc"; std::string format_uid(std::vector &uid) { char buf[(uid.size() * 2) + uid.size() - 1]; int offset = 0; - for (uint8_t i = 0; i < uid.size(); i++) { + for (size_t i = 0; i < uid.size(); i++) { const char *format = "%02X"; if (i + 1 < uid.size()) format = "%02X-"; @@ -22,7 +22,7 @@ std::string format_uid(std::vector &uid) { std::string format_bytes(std::vector &bytes) { char buf[(bytes.size() * 2) + bytes.size() - 1]; int offset = 0; - for (uint8_t i = 0; i < bytes.size(); i++) { + for (size_t i = 0; i < bytes.size(); i++) { const char *format = "%02X"; if (i + 1 < bytes.size()) format = "%02X "; diff --git a/esphome/components/number/__init__.py b/esphome/components/number/__init__.py index 1da25caafe..71e288a4cc 100644 --- a/esphome/components/number/__init__.py +++ b/esphome/components/number/__init__.py @@ -7,9 +7,11 @@ from esphome.const import ( CONF_ABOVE, CONF_BELOW, CONF_ID, + CONF_MODE, CONF_ON_VALUE, CONF_ON_VALUE_RANGE, CONF_TRIGGER_ID, + CONF_UNIT_OF_MEASUREMENT, CONF_MQTT_ID, CONF_VALUE, ) @@ -39,6 +41,14 @@ NumberInRangeCondition = number_ns.class_( "NumberInRangeCondition", automation.Condition ) +NumberMode = number_ns.enum("NumberMode") + +NUMBER_MODES = { + "AUTO": NumberMode.NUMBER_MODE_AUTO, + "BOX": NumberMode.NUMBER_MODE_BOX, + "SLIDER": NumberMode.NUMBER_MODE_SLIDER, +} + icon = cv.icon NUMBER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( @@ -58,6 +68,8 @@ NUMBER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).e }, cv.has_at_least_one_key(CONF_ABOVE, CONF_BELOW), ), + cv.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string_strict, + cv.Optional(CONF_MODE, default="AUTO"): cv.enum(NUMBER_MODES, upper=True), } ) @@ -72,6 +84,8 @@ async def setup_number_core_( if step is not None: cg.add(var.traits.set_step(step)) + cg.add(var.traits.set_mode(config[CONF_MODE])) + for conf in config.get(CONF_ON_VALUE, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation(trigger, [(float, "x")], conf) @@ -86,6 +100,8 @@ async def setup_number_core_( cg.add(trigger.set_max(template_)) await automation.build_automation(trigger, [(float, "x")], conf) + if CONF_UNIT_OF_MEASUREMENT in config: + cg.add(var.traits.set_unit_of_measurement(config[CONF_UNIT_OF_MEASUREMENT])) if CONF_MQTT_ID in config: mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) await mqtt.register_mqtt_component(mqtt_, config) diff --git a/esphome/components/number/number.cpp b/esphome/components/number/number.cpp index 57a5c7c4bd..99a2c04a22 100644 --- a/esphome/components/number/number.cpp +++ b/esphome/components/number/number.cpp @@ -41,6 +41,15 @@ void Number::add_on_state_callback(std::function &&callback) { this->state_callback_.add(std::move(callback)); } +std::string NumberTraits::get_unit_of_measurement() { + if (this->unit_of_measurement_.has_value()) + return *this->unit_of_measurement_; + return ""; +} +void NumberTraits::set_unit_of_measurement(const std::string &unit_of_measurement) { + this->unit_of_measurement_ = unit_of_measurement; +} + uint32_t Number::hash_base() { return 2282307003UL; } } // namespace number diff --git a/esphome/components/number/number.h b/esphome/components/number/number.h index ed104fb477..40fdfceec1 100644 --- a/esphome/components/number/number.h +++ b/esphome/components/number/number.h @@ -13,6 +13,9 @@ namespace number { if (!(obj)->get_icon().empty()) { \ ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \ } \ + if (!(obj)->traits.get_unit_of_measurement().empty()) { \ + ESP_LOGCONFIG(TAG, "%s Unit of Measurement: '%s'", prefix, (obj)->traits.get_unit_of_measurement().c_str()); \ + } \ } class Number; @@ -33,6 +36,12 @@ class NumberCall { optional value_; }; +enum NumberMode : uint8_t { + NUMBER_MODE_AUTO = 0, + NUMBER_MODE_BOX = 1, + NUMBER_MODE_SLIDER = 2, +}; + class NumberTraits { public: void set_min_value(float min_value) { min_value_ = min_value; } @@ -42,10 +51,21 @@ class NumberTraits { void set_step(float step) { step_ = step; } float get_step() const { return step_; } + /// Get the unit of measurement, using the manual override if set. + std::string get_unit_of_measurement(); + /// Manually set the unit of measurement. + void set_unit_of_measurement(const std::string &unit_of_measurement); + + // Get/set the frontend mode. + NumberMode get_mode() const { return this->mode_; } + void set_mode(NumberMode mode) { this->mode_ = mode; } + protected: float min_value_ = NAN; float max_value_ = NAN; float step_ = NAN; + optional unit_of_measurement_; ///< Unit of measurement override + NumberMode mode_{NUMBER_MODE_AUTO}; }; /** Base-class for all numbers. diff --git a/esphome/components/ota/ota_component.cpp b/esphome/components/ota/ota_component.cpp index e49c108320..79edd91173 100644 --- a/esphome/components/ota/ota_component.cpp +++ b/esphome/components/ota/ota_component.cpp @@ -141,14 +141,14 @@ void OTAComponent::handle_() { if (!this->readall_(buf, 5)) { ESP_LOGW(TAG, "Reading magic bytes failed!"); - goto error; + goto error; // NOLINT(cppcoreguidelines-avoid-goto) } // 0x6C, 0x26, 0xF7, 0x5C, 0x45 if (buf[0] != 0x6C || buf[1] != 0x26 || buf[2] != 0xF7 || buf[3] != 0x5C || buf[4] != 0x45) { ESP_LOGW(TAG, "Magic bytes do not match! 0x%02X-0x%02X-0x%02X-0x%02X-0x%02X", buf[0], buf[1], buf[2], buf[3], buf[4]); error_code = OTA_RESPONSE_ERROR_MAGIC; - goto error; + goto error; // NOLINT(cppcoreguidelines-avoid-goto) } // Send OK and version - 2 bytes @@ -161,7 +161,7 @@ void OTAComponent::handle_() { // Read features - 1 byte if (!this->readall_(buf, 1)) { ESP_LOGW(TAG, "Reading features failed!"); - goto error; + goto error; // NOLINT(cppcoreguidelines-avoid-goto) } ota_features = buf[0]; // NOLINT ESP_LOGV(TAG, "OTA features is 0x%02X", ota_features); @@ -189,7 +189,7 @@ void OTAComponent::handle_() { // Send nonce, 32 bytes hex MD5 if (!this->writeall_(reinterpret_cast(sbuf), 32)) { ESP_LOGW(TAG, "Auth: Writing nonce failed!"); - goto error; + goto error; // NOLINT(cppcoreguidelines-avoid-goto) } // prepare challenge @@ -201,7 +201,7 @@ void OTAComponent::handle_() { // Receive cnonce, 32 bytes hex MD5 if (!this->readall_(buf, 32)) { ESP_LOGW(TAG, "Auth: Reading cnonce failed!"); - goto error; + goto error; // NOLINT(cppcoreguidelines-avoid-goto) } sbuf[32] = '\0'; ESP_LOGV(TAG, "Auth: CNonce is %s", sbuf); @@ -216,7 +216,7 @@ void OTAComponent::handle_() { // Receive result, 32 bytes hex MD5 if (!this->readall_(buf + 64, 32)) { ESP_LOGW(TAG, "Auth: Reading response failed!"); - goto error; + goto error; // NOLINT(cppcoreguidelines-avoid-goto) } sbuf[64 + 32] = '\0'; ESP_LOGV(TAG, "Auth: Response is %s", sbuf + 64); @@ -228,7 +228,7 @@ void OTAComponent::handle_() { if (!matches) { ESP_LOGW(TAG, "Auth failed! Passwords do not match!"); error_code = OTA_RESPONSE_ERROR_AUTH_INVALID; - goto error; + goto error; // NOLINT(cppcoreguidelines-avoid-goto) } } #endif // USE_OTA_PASSWORD @@ -240,7 +240,7 @@ void OTAComponent::handle_() { // Read size, 4 bytes MSB first if (!this->readall_(buf, 4)) { ESP_LOGW(TAG, "Reading size failed!"); - goto error; + goto error; // NOLINT(cppcoreguidelines-avoid-goto) } ota_size = 0; for (uint8_t i = 0; i < 4; i++) { @@ -251,7 +251,7 @@ void OTAComponent::handle_() { error_code = backend->begin(ota_size); if (error_code != OTA_RESPONSE_OK) - goto error; + goto error; // NOLINT(cppcoreguidelines-avoid-goto) update_started = true; // Acknowledge prepare OK - 1 byte @@ -261,7 +261,7 @@ void OTAComponent::handle_() { // Read binary MD5, 32 bytes if (!this->readall_(buf, 32)) { ESP_LOGW(TAG, "Reading binary MD5 checksum failed!"); - goto error; + goto error; // NOLINT(cppcoreguidelines-avoid-goto) } sbuf[32] = '\0'; ESP_LOGV(TAG, "Update: Binary MD5 is %s", sbuf); @@ -281,19 +281,19 @@ void OTAComponent::handle_() { continue; } ESP_LOGW(TAG, "Error receiving data for update, errno: %d", errno); - goto error; + goto error; // NOLINT(cppcoreguidelines-avoid-goto) } else if (read == 0) { // $ man recv // "When a stream socket peer has performed an orderly shutdown, the return value will // be 0 (the traditional "end-of-file" return)." ESP_LOGW(TAG, "Remote end closed connection"); - goto error; + goto error; // NOLINT(cppcoreguidelines-avoid-goto) } error_code = backend->write(buf, read); if (error_code != OTA_RESPONSE_OK) { ESP_LOGW(TAG, "Error writing binary data to flash!"); - goto error; + goto error; // NOLINT(cppcoreguidelines-avoid-goto) } total += read; @@ -317,7 +317,7 @@ void OTAComponent::handle_() { error_code = backend->end(); if (error_code != OTA_RESPONSE_OK) { ESP_LOGW(TAG, "Error ending OTA!"); - goto error; + goto error; // NOLINT(cppcoreguidelines-avoid-goto) } // Acknowledge Update end OK - 1 byte diff --git a/esphome/components/packages/__init__.py b/esphome/components/packages/__init__.py index df0f0de13d..7483d65b9d 100644 --- a/esphome/components/packages/__init__.py +++ b/esphome/components/packages/__init__.py @@ -10,6 +10,8 @@ from esphome.const import ( CONF_REF, CONF_REFRESH, CONF_URL, + CONF_USERNAME, + CONF_PASSWORD, ) import esphome.config_validation as cv @@ -93,6 +95,8 @@ BASE_SCHEMA = cv.All( cv.Schema( { cv.Required(CONF_URL): cv.url, + cv.Optional(CONF_USERNAME): cv.string, + cv.Optional(CONF_PASSWORD): cv.string, cv.Exclusive(CONF_FILE, "files"): validate_yaml_filename, cv.Exclusive(CONF_FILES, "files"): cv.All( cv.ensure_list(validate_yaml_filename), @@ -124,6 +128,8 @@ def _process_base_package(config: dict) -> dict: ref=config.get(CONF_REF), refresh=config[CONF_REFRESH], domain=DOMAIN, + username=config.get(CONF_USERNAME), + password=config.get(CONF_PASSWORD), ) files: str = config[CONF_FILES] diff --git a/esphome/components/pid/pid_autotuner.cpp b/esphome/components/pid/pid_autotuner.cpp index 15c1c5f076..fc012aaa39 100644 --- a/esphome/components/pid/pid_autotuner.cpp +++ b/esphome/components/pid/pid_autotuner.cpp @@ -330,7 +330,7 @@ bool PIDAutotuner::OscillationAmplitudeDetector::has_enough_data() const { float PIDAutotuner::OscillationAmplitudeDetector::get_mean_oscillation_amplitude() const { float total_amplitudes = 0; size_t total_amplitudes_n = 0; - for (int i = 1; i < std::min(phase_mins.size(), phase_maxs.size()) - 1; i++) { + for (size_t i = 1; i < std::min(phase_mins.size(), phase_maxs.size()) - 1; i++) { total_amplitudes += std::abs(phase_maxs[i] - phase_mins[i + 1]); total_amplitudes_n++; } diff --git a/esphome/components/pipsolar/pipsolar.cpp b/esphome/components/pipsolar/pipsolar.cpp index 9f8b57003a..13a08bbd16 100644 --- a/esphome/components/pipsolar/pipsolar.cpp +++ b/esphome/components/pipsolar/pipsolar.cpp @@ -413,8 +413,6 @@ void Pipsolar::loop() { this->state_ = STATE_IDLE; break; case POLLING_QT: - this->state_ = STATE_IDLE; - break; case POLLING_QMN: this->state_ = STATE_IDLE; break; @@ -481,7 +479,7 @@ void Pipsolar::loop() { ESP_LOGD(TAG, "Decode QFLAG"); // result like:"(EbkuvxzDajy" // get through all char: ignore first "(" Enable flag on 'E', Disable on 'D') else set the corresponding value - for (int i = 1; i < strlen(tmp); i++) { + for (size_t i = 1; i < strlen(tmp); i++) { switch (tmp[i]) { case 'E': enabled = true; @@ -530,7 +528,7 @@ void Pipsolar::loop() { this->value_warnings_present_ = false; this->value_faults_present_ = true; - for (int i = 1; i < strlen(tmp); i++) { + for (size_t i = 1; i < strlen(tmp); i++) { enabled = tmp[i] == '1'; switch (i) { case 1: diff --git a/esphome/components/pmsx003/pmsx003.cpp b/esphome/components/pmsx003/pmsx003.cpp index 0474d6ffd0..5de94699f0 100644 --- a/esphome/components/pmsx003/pmsx003.cpp +++ b/esphome/components/pmsx003/pmsx003.cpp @@ -96,6 +96,7 @@ optional PMSX003Component::check_byte_() { length_matches = payload_length == 28 || payload_length == 20; break; case PMSX003_TYPE_5003T: + case PMSX003_TYPE_5003S: length_matches = payload_length == 28; break; case PMSX003_TYPE_5003ST: @@ -133,20 +134,25 @@ optional PMSX003Component::check_byte_() { void PMSX003Component::parse_data_() { switch (this->type_) { case PMSX003_TYPE_5003ST: { - uint16_t formaldehyde = this->get_16_bit_uint_(28); float temperature = this->get_16_bit_uint_(30) / 10.0f; float humidity = this->get_16_bit_uint_(32) / 10.0f; - ESP_LOGD(TAG, "Got Temperature: %.1f°C, Humidity: %.1f%% Formaldehyde: %u µg/m^3", temperature, humidity, - formaldehyde); + ESP_LOGD(TAG, "Got Temperature: %.1f°C, Humidity: %.1f%%", temperature, humidity); if (this->temperature_sensor_ != nullptr) this->temperature_sensor_->publish_state(temperature); if (this->humidity_sensor_ != nullptr) this->humidity_sensor_->publish_state(humidity); + // The rest of the PMS5003ST matches the PMS5003S, continue on + } + case PMSX003_TYPE_5003S: { + uint16_t formaldehyde = this->get_16_bit_uint_(28); + + ESP_LOGD(TAG, "Got Formaldehyde: %u µg/m^3", formaldehyde); + if (this->formaldehyde_sensor_ != nullptr) this->formaldehyde_sensor_->publish_state(formaldehyde); - // The rest of the PMS5003ST matches the PMS5003, continue on + // The rest of the PMS5003S matches the PMS5003, continue on } case PMSX003_TYPE_X003: { uint16_t pm_1_0_std_concentration = this->get_16_bit_uint_(4); diff --git a/esphome/components/pmsx003/pmsx003.h b/esphome/components/pmsx003/pmsx003.h index a5adecb534..fd6364c70c 100644 --- a/esphome/components/pmsx003/pmsx003.h +++ b/esphome/components/pmsx003/pmsx003.h @@ -11,6 +11,7 @@ enum PMSX003Type { PMSX003_TYPE_X003 = 0, PMSX003_TYPE_5003T, PMSX003_TYPE_5003ST, + PMSX003_TYPE_5003S, }; class PMSX003Component : public uart::UARTDevice, public Component { diff --git a/esphome/components/pmsx003/sensor.py b/esphome/components/pmsx003/sensor.py index 350117a235..56a91d22fc 100644 --- a/esphome/components/pmsx003/sensor.py +++ b/esphome/components/pmsx003/sensor.py @@ -42,21 +42,23 @@ PMSX003Sensor = pmsx003_ns.class_("PMSX003Sensor", sensor.Sensor) TYPE_PMSX003 = "PMSX003" TYPE_PMS5003T = "PMS5003T" TYPE_PMS5003ST = "PMS5003ST" +TYPE_PMS5003S = "PMS5003S" PMSX003Type = pmsx003_ns.enum("PMSX003Type") PMSX003_TYPES = { TYPE_PMSX003: PMSX003Type.PMSX003_TYPE_X003, TYPE_PMS5003T: PMSX003Type.PMSX003_TYPE_5003T, TYPE_PMS5003ST: PMSX003Type.PMSX003_TYPE_5003ST, + TYPE_PMS5003S: PMSX003Type.PMSX003_TYPE_5003S, } SENSORS_TO_TYPE = { - CONF_PM_1_0: [TYPE_PMSX003, TYPE_PMS5003ST], - CONF_PM_2_5: [TYPE_PMSX003, TYPE_PMS5003T, TYPE_PMS5003ST], - CONF_PM_10_0: [TYPE_PMSX003, TYPE_PMS5003ST], + CONF_PM_1_0: [TYPE_PMSX003, TYPE_PMS5003ST, TYPE_PMS5003S], + CONF_PM_2_5: [TYPE_PMSX003, TYPE_PMS5003T, TYPE_PMS5003ST, TYPE_PMS5003S], + CONF_PM_10_0: [TYPE_PMSX003, TYPE_PMS5003ST, TYPE_PMS5003S], CONF_TEMPERATURE: [TYPE_PMS5003T, TYPE_PMS5003ST], CONF_HUMIDITY: [TYPE_PMS5003T, TYPE_PMS5003ST], - CONF_FORMALDEHYDE: [TYPE_PMS5003ST], + CONF_FORMALDEHYDE: [TYPE_PMS5003ST, TYPE_PMS5003S], } diff --git a/esphome/components/pn532/pn532.cpp b/esphome/components/pn532/pn532.cpp index ed2a2c1e35..0c46ff8a57 100644 --- a/esphome/components/pn532/pn532.cpp +++ b/esphome/components/pn532/pn532.cpp @@ -145,7 +145,7 @@ void PN532::loop() { if (nfcid.size() == this->current_uid_.size()) { bool same_uid = false; - for (uint8_t i = 0; i < nfcid.size(); i++) + for (size_t i = 0; i < nfcid.size(); i++) same_uid |= nfcid[i] == this->current_uid_[i]; if (same_uid) return; @@ -367,7 +367,7 @@ bool PN532BinarySensor::process(std::vector &data) { if (data.size() != this->uid_.size()) return false; - for (uint8_t i = 0; i < data.size(); i++) { + for (size_t i = 0; i < data.size(); i++) { if (data[i] != this->uid_[i]) return false; } diff --git a/esphome/components/pulse_meter/pulse_meter_sensor.cpp b/esphome/components/pulse_meter/pulse_meter_sensor.cpp index fd1403b4fd..7d526b241b 100644 --- a/esphome/components/pulse_meter/pulse_meter_sensor.cpp +++ b/esphome/components/pulse_meter/pulse_meter_sensor.cpp @@ -35,7 +35,7 @@ void PulseMeterSensor::loop() { this->publish_state(0); } else { // Calculate pulses/min from the pulse width in ms - this->publish_state((60.0 * 1000.0) / pulse_width_ms); + this->publish_state((60.0f * 1000.0f) / pulse_width_ms); } } diff --git a/esphome/components/rc522/rc522.cpp b/esphome/components/rc522/rc522.cpp index 385641fea0..d203b3ce8f 100644 --- a/esphome/components/rc522/rc522.cpp +++ b/esphome/components/rc522/rc522.cpp @@ -28,7 +28,7 @@ std::string format_buffer(uint8_t *b, uint8_t len) { std::string format_uid(std::vector &uid) { char buf[32]; int offset = 0; - for (uint8_t i = 0; i < uid.size(); i++) { + for (size_t i = 0; i < uid.size(); i++) { const char *format = "%02X"; if (i + 1 < uid.size()) format = "%02X-"; @@ -479,7 +479,7 @@ bool RC522BinarySensor::process(std::vector &data) { if (data.size() != this->uid_.size()) result = false; else { - for (uint8_t i = 0; i < data.size(); i++) { + for (size_t i = 0; i < data.size(); i++) { if (data[i] != this->uid_[i]) { result = false; break; diff --git a/esphome/components/remote_base/nec_protocol.cpp b/esphome/components/remote_base/nec_protocol.cpp index 79a30903a4..47b4d676dd 100644 --- a/esphome/components/remote_base/nec_protocol.cpp +++ b/esphome/components/remote_base/nec_protocol.cpp @@ -17,14 +17,14 @@ void NECProtocol::encode(RemoteTransmitData *dst, const NECData &data) { dst->set_carrier_frequency(38000); dst->item(HEADER_HIGH_US, HEADER_LOW_US); - for (uint32_t mask = 1UL << 15; mask; mask >>= 1) { + for (uint16_t mask = 1; mask; mask <<= 1) { if (data.address & mask) dst->item(BIT_HIGH_US, BIT_ONE_LOW_US); else dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US); } - for (uint32_t mask = 1UL << 15; mask; mask >>= 1) { + for (uint16_t mask = 1; mask; mask <<= 1) { if (data.command & mask) dst->item(BIT_HIGH_US, BIT_ONE_LOW_US); else @@ -41,7 +41,7 @@ optional NECProtocol::decode(RemoteReceiveData src) { if (!src.expect_item(HEADER_HIGH_US, HEADER_LOW_US)) return {}; - for (uint32_t mask = 1UL << 15; mask != 0; mask >>= 1) { + for (uint16_t mask = 1; mask; mask <<= 1) { if (src.expect_item(BIT_HIGH_US, BIT_ONE_LOW_US)) { data.address |= mask; } else if (src.expect_item(BIT_HIGH_US, BIT_ZERO_LOW_US)) { @@ -51,7 +51,7 @@ optional NECProtocol::decode(RemoteReceiveData src) { } } - for (uint32_t mask = 1UL << 15; mask != 0; mask >>= 1) { + for (uint16_t mask = 1; mask; mask <<= 1) { if (src.expect_item(BIT_HIGH_US, BIT_ONE_LOW_US)) { data.command |= mask; } else if (src.expect_item(BIT_HIGH_US, BIT_ZERO_LOW_US)) { diff --git a/esphome/components/remote_base/pronto_protocol.cpp b/esphome/components/remote_base/pronto_protocol.cpp index 11aebb6c5d..4f6ace720c 100644 --- a/esphome/components/remote_base/pronto_protocol.cpp +++ b/esphome/components/remote_base/pronto_protocol.cpp @@ -113,7 +113,7 @@ void ProntoProtocol::send_pronto_(RemoteTransmitData *dst, const std::string &st const char *p = str.c_str(); char *endptr[1]; - for (uint16_t i = 0; i < len; i++) { + for (size_t i = 0; i < len; i++) { uint16_t x = strtol(p, endptr, 16); if (x == 0 && i >= NUMBERS_IN_PREAMBLE) { // Alignment error?, bail immediately (often right result). diff --git a/esphome/components/remote_base/remote_base.cpp b/esphome/components/remote_base/remote_base.cpp index a853c9849e..97ee027b84 100644 --- a/esphome/components/remote_base/remote_base.cpp +++ b/esphome/components/remote_base/remote_base.cpp @@ -33,7 +33,7 @@ void RemoteTransmitterBase::send_(uint32_t send_times, uint32_t send_wait) { uint32_t buffer_offset = 0; buffer_offset += sprintf(buffer, "Sending times=%u wait=%ums: ", send_times, send_wait); - for (int32_t i = 0; i < vec.size(); i++) { + for (size_t i = 0; i < vec.size(); i++) { const int32_t value = vec[i]; const uint32_t remaining_length = sizeof(buffer) - buffer_offset; int written; diff --git a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp b/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp index c3b61b72c2..368b21f892 100644 --- a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp +++ b/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp @@ -113,7 +113,7 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen this->rmt_temp_.push_back(rmt_item); } - for (uint16_t i = 0; i < send_times; i++) { + for (uint32_t i = 0; i < send_times; i++) { esp_err_t error = rmt_write_items(this->channel_, this->rmt_temp_.data(), this->rmt_temp_.size(), true); if (error != ESP_OK) { ESP_LOGW(TAG, "rmt_write_items failed: %s", esp_err_to_name(error)); diff --git a/esphome/components/restart/button/__init__.py b/esphome/components/restart/button/__init__.py new file mode 100644 index 0000000000..1a0e9cdc3d --- /dev/null +++ b/esphome/components/restart/button/__init__.py @@ -0,0 +1,25 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import button +from esphome.const import ( + CONF_ID, + DEVICE_CLASS_RESTART, + ENTITY_CATEGORY_CONFIG, +) + +restart_ns = cg.esphome_ns.namespace("restart") +RestartButton = restart_ns.class_("RestartButton", button.Button, cg.Component) + +CONFIG_SCHEMA = ( + button.button_schema( + device_class=DEVICE_CLASS_RESTART, entity_category=ENTITY_CATEGORY_CONFIG + ) + .extend({cv.GenerateID(): cv.declare_id(RestartButton)}) + .extend(cv.COMPONENT_SCHEMA) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await button.register_button(var, config) diff --git a/esphome/components/restart/button/restart_button.cpp b/esphome/components/restart/button/restart_button.cpp new file mode 100644 index 0000000000..d8ff061355 --- /dev/null +++ b/esphome/components/restart/button/restart_button.cpp @@ -0,0 +1,20 @@ +#include "restart_button.h" +#include "esphome/core/application.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace restart { + +static const char *const TAG = "restart.button"; + +void RestartButton::press_action() { + ESP_LOGI(TAG, "Restarting device..."); + // Let MQTT settle a bit + delay(100); // NOLINT + App.safe_reboot(); +} +void RestartButton::dump_config() { LOG_BUTTON("", "Restart Button", this); } + +} // namespace restart +} // namespace esphome diff --git a/esphome/components/restart/button/restart_button.h b/esphome/components/restart/button/restart_button.h new file mode 100644 index 0000000000..db18f1dadc --- /dev/null +++ b/esphome/components/restart/button/restart_button.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/button/button.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace restart { + +class RestartButton : public button::Button, public Component { + public: + void dump_config() override; + + protected: + void press_action() override; +}; + +} // namespace restart +} // namespace esphome diff --git a/esphome/components/restart/switch.py b/esphome/components/restart/switch/__init__.py similarity index 100% rename from esphome/components/restart/switch.py rename to esphome/components/restart/switch/__init__.py diff --git a/esphome/components/restart/restart_switch.cpp b/esphome/components/restart/switch/restart_switch.cpp similarity index 100% rename from esphome/components/restart/restart_switch.cpp rename to esphome/components/restart/switch/restart_switch.cpp diff --git a/esphome/components/restart/restart_switch.h b/esphome/components/restart/switch/restart_switch.h similarity index 100% rename from esphome/components/restart/restart_switch.h rename to esphome/components/restart/switch/restart_switch.h diff --git a/esphome/components/rf_bridge/rf_bridge.cpp b/esphome/components/rf_bridge/rf_bridge.cpp index a4259e5aa2..d8c8047496 100644 --- a/esphome/components/rf_bridge/rf_bridge.cpp +++ b/esphome/components/rf_bridge/rf_bridge.cpp @@ -52,7 +52,7 @@ bool RFBridgeComponent::parse_bridge_byte_(uint8_t byte) { if (action == RF_CODE_LEARN_OK) ESP_LOGD(TAG, "Learning success"); - ESP_LOGD(TAG, "Received RFBridge Code: sync=0x%04X low=0x%04X high=0x%04X code=0x%06X", data.sync, data.low, + ESP_LOGI(TAG, "Received RFBridge Code: sync=0x%04X low=0x%04X high=0x%04X code=0x%06X", data.sync, data.low, data.high, data.code); this->data_callback_.call(data); break; @@ -73,7 +73,7 @@ bool RFBridgeComponent::parse_bridge_byte_(uint8_t byte) { data.code += next_byte; } - ESP_LOGD(TAG, "Received RFBridge Advanced Code: length=0x%02X protocol=0x%02X code=0x%s", data.length, + ESP_LOGI(TAG, "Received RFBridge Advanced Code: length=0x%02X protocol=0x%02X code=0x%s", data.length, data.protocol, data.code.c_str()); this->advanced_data_callback_.call(data); break; @@ -97,7 +97,7 @@ bool RFBridgeComponent::parse_bridge_byte_(uint8_t byte) { str += " "; } } - ESP_LOGD(TAG, "Received RFBridge Bucket: %s", str.c_str()); + ESP_LOGI(TAG, "Received RFBridge Bucket: %s", str.c_str()); break; } default: @@ -186,7 +186,7 @@ void RFBridgeComponent::dump_config() { } void RFBridgeComponent::start_advanced_sniffing() { - ESP_LOGD(TAG, "Advanced Sniffing on"); + ESP_LOGI(TAG, "Advanced Sniffing on"); this->write(RF_CODE_START); this->write(RF_CODE_SNIFFING_ON); this->write(RF_CODE_STOP); @@ -194,7 +194,7 @@ void RFBridgeComponent::start_advanced_sniffing() { } void RFBridgeComponent::stop_advanced_sniffing() { - ESP_LOGD(TAG, "Advanced Sniffing off"); + ESP_LOGI(TAG, "Advanced Sniffing off"); this->write(RF_CODE_START); this->write(RF_CODE_SNIFFING_OFF); this->write(RF_CODE_STOP); @@ -202,7 +202,7 @@ void RFBridgeComponent::stop_advanced_sniffing() { } void RFBridgeComponent::start_bucket_sniffing() { - ESP_LOGD(TAG, "Raw Bucket Sniffing on"); + ESP_LOGI(TAG, "Raw Bucket Sniffing on"); this->write(RF_CODE_START); this->write(RF_CODE_RFIN_BUCKET); this->write(RF_CODE_STOP); diff --git a/esphome/components/rtttl/rtttl.cpp b/esphome/components/rtttl/rtttl.cpp index d571c2f287..c76d4a89b0 100644 --- a/esphome/components/rtttl/rtttl.cpp +++ b/esphome/components/rtttl/rtttl.cpp @@ -159,7 +159,7 @@ void Rtttl::loop() { // Now play the note if (note) { auto note_index = (scale - 4) * 12 + note; - if (note_index < 0 || note_index >= sizeof(NOTES)) { + if (note_index < 0 || note_index >= (int) sizeof(NOTES)) { ESP_LOGE(TAG, "Note out of valid range"); return; } diff --git a/esphome/components/sdp3x/sdp3x.cpp b/esphome/components/sdp3x/sdp3x.cpp index b0d8bcc6c4..107ed2902f 100644 --- a/esphome/components/sdp3x/sdp3x.cpp +++ b/esphome/components/sdp3x/sdp3x.cpp @@ -11,6 +11,7 @@ static const uint8_t SDP3X_SOFT_RESET[2] = {0x00, 0x06}; static const uint8_t SDP3X_READ_ID1[2] = {0x36, 0x7C}; static const uint8_t SDP3X_READ_ID2[2] = {0xE1, 0x02}; static const uint8_t SDP3X_START_DP_AVG[2] = {0x36, 0x15}; +static const uint8_t SDP3X_START_MASS_FLOW_AVG[2] = {0x36, 0x03}; static const uint8_t SDP3X_STOP_MEAS[2] = {0x3F, 0xF9}; void SDP3XComponent::update() { this->read_pressure_(); } @@ -26,46 +27,69 @@ void SDP3XComponent::setup() { ESP_LOGW(TAG, "Soft Reset SDP3X failed!"); // This sometimes fails for no good reason } - delayMicroseconds(20000); + this->set_timeout(20, [this] { + if (this->write(SDP3X_READ_ID1, 2) != i2c::ERROR_OK) { + ESP_LOGE(TAG, "Read ID1 SDP3X failed!"); + this->mark_failed(); + return; + } + if (this->write(SDP3X_READ_ID2, 2) != i2c::ERROR_OK) { + ESP_LOGE(TAG, "Read ID2 SDP3X failed!"); + this->mark_failed(); + return; + } - if (this->write(SDP3X_READ_ID1, 2) != i2c::ERROR_OK) { - ESP_LOGE(TAG, "Read ID1 SDP3X failed!"); - this->mark_failed(); - return; - } - if (this->write(SDP3X_READ_ID2, 2) != i2c::ERROR_OK) { - ESP_LOGE(TAG, "Read ID2 SDP3X failed!"); - this->mark_failed(); - return; - } + uint8_t data[18]; + if (this->read(data, 18) != i2c::ERROR_OK) { + ESP_LOGE(TAG, "Read ID SDP3X failed!"); + this->mark_failed(); + return; + } + if (!(check_crc_(&data[0], 2, data[2]) && check_crc_(&data[3], 2, data[5]))) { + ESP_LOGE(TAG, "CRC ID SDP3X failed!"); + this->mark_failed(); + return; + } - uint8_t data[18]; - if (this->read(data, 18) != i2c::ERROR_OK) { - ESP_LOGE(TAG, "Read ID SDP3X failed!"); - this->mark_failed(); - return; - } + // SDP8xx + // ref: + // https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/8_Differential_Pressure/Datasheets/Sensirion_Differential_Pressure_Datasheet_SDP8xx_Digital.pdf + if (data[2] == 0x02) { + switch (data[3]) { + case 0x01: // SDP800-500Pa + ESP_LOGCONFIG(TAG, "Sensor is SDP800-500Pa"); + break; + case 0x0A: // SDP810-500Pa + ESP_LOGCONFIG(TAG, "Sensor is SDP810-500Pa"); + break; + case 0x04: // SDP801-500Pa + ESP_LOGCONFIG(TAG, "Sensor is SDP801-500Pa"); + break; + case 0x0D: // SDP811-500Pa + ESP_LOGCONFIG(TAG, "Sensor is SDP811-500Pa"); + break; + case 0x02: // SDP800-125Pa + ESP_LOGCONFIG(TAG, "Sensor is SDP800-125Pa"); + break; + case 0x0B: // SDP810-125Pa + ESP_LOGCONFIG(TAG, "Sensor is SDP810-125Pa"); + break; + } + } else if (data[2] == 0x01) { + if (data[3] == 0x01) { + ESP_LOGCONFIG(TAG, "Sensor is SDP31-500Pa"); + } else if (data[3] == 0x02) { + ESP_LOGCONFIG(TAG, "Sensor is SDP32-125Pa"); + } + } - if (!(check_crc_(&data[0], 2, data[2]) && check_crc_(&data[3], 2, data[5]))) { - ESP_LOGE(TAG, "CRC ID SDP3X failed!"); - this->mark_failed(); - return; - } - - if (data[3] == 0x01) { - ESP_LOGCONFIG(TAG, "SDP3X is SDP31"); - pressure_scale_factor_ = 60.0f * 100.0f; // Scale factors converted to hPa per count - } else if (data[3] == 0x02) { - ESP_LOGCONFIG(TAG, "SDP3X is SDP32"); - pressure_scale_factor_ = 240.0f * 100.0f; - } - - if (this->write(SDP3X_START_DP_AVG, 2) != i2c::ERROR_OK) { - ESP_LOGE(TAG, "Start Measurements SDP3X failed!"); - this->mark_failed(); - return; - } - ESP_LOGCONFIG(TAG, "SDP3X started!"); + if (this->write(measurement_mode_ == DP_AVG ? SDP3X_START_DP_AVG : SDP3X_START_MASS_FLOW_AVG, 2) != i2c::ERROR_OK) { + ESP_LOGE(TAG, "Start Measurements SDP3X failed!"); + this->mark_failed(); + return; + } + ESP_LOGCONFIG(TAG, "SDP3X started!"); + }); } void SDP3XComponent::dump_config() { LOG_SENSOR(" ", "SDP3X", this); @@ -91,8 +115,12 @@ void SDP3XComponent::read_pressure_() { } int16_t pressure_raw = encode_uint16(data[0], data[1]); - float pressure = pressure_raw / pressure_scale_factor_; - ESP_LOGV(TAG, "Got raw pressure=%d, scale factor =%.3f ", pressure_raw, pressure_scale_factor_); + int16_t temperature_raw = encode_uint16(data[3], data[4]); + int16_t scale_factor_raw = encode_uint16(data[6], data[7]); + // scale factor is in Pa - convert to hPa + float pressure = pressure_raw / (scale_factor_raw * 100.0f); + ESP_LOGV(TAG, "Got raw pressure=%d, raw scale factor =%d, raw temperature=%d ", pressure_raw, scale_factor_raw, + temperature_raw); ESP_LOGD(TAG, "Got Pressure=%.3f hPa", pressure); this->publish_state(pressure); diff --git a/esphome/components/sdp3x/sdp3x.h b/esphome/components/sdp3x/sdp3x.h index 51c9973c61..0e74d0883d 100644 --- a/esphome/components/sdp3x/sdp3x.h +++ b/esphome/components/sdp3x/sdp3x.h @@ -7,6 +7,8 @@ namespace esphome { namespace sdp3x { +enum MeasurementMode { MASS_FLOW_AVG, DP_AVG }; + class SDP3XComponent : public PollingComponent, public i2c::I2CDevice, public sensor::Sensor { public: /// Schedule temperature+pressure readings. @@ -16,14 +18,14 @@ class SDP3XComponent : public PollingComponent, public i2c::I2CDevice, public se void dump_config() override; float get_setup_priority() const override; + void set_measurement_mode(MeasurementMode mode) { measurement_mode_ = mode; } protected: /// Internal method to read the pressure from the component after it has been scheduled. void read_pressure_(); bool check_crc_(const uint8_t data[], uint8_t size, uint8_t checksum); - - float pressure_scale_factor_ = 0.0f; // hPa per count + MeasurementMode measurement_mode_; }; } // namespace sdp3x diff --git a/esphome/components/sdp3x/sensor.py b/esphome/components/sdp3x/sensor.py index 08d7250f6e..45f5cc4d9a 100644 --- a/esphome/components/sdp3x/sensor.py +++ b/esphome/components/sdp3x/sensor.py @@ -14,6 +14,14 @@ CODEOWNERS = ["@Azimath"] sdp3x_ns = cg.esphome_ns.namespace("sdp3x") SDP3XComponent = sdp3x_ns.class_("SDP3XComponent", cg.PollingComponent, i2c.I2CDevice) + +MeasurementMode = sdp3x_ns.enum("MeasurementMode") +MEASUREMENT_MODE = { + "mass_flow": MeasurementMode.MASS_FLOW_AVG, + "differential_pressure": MeasurementMode.DP_AVG, +} +CONF_MEASUREMENT_MODE = "measurement_mode" + CONFIG_SCHEMA = ( sensor.sensor_schema( unit_of_measurement=UNIT_HECTOPASCAL, @@ -24,6 +32,9 @@ CONFIG_SCHEMA = ( .extend( { cv.GenerateID(): cv.declare_id(SDP3XComponent), + cv.Optional( + CONF_MEASUREMENT_MODE, default="differential_pressure" + ): cv.enum(MEASUREMENT_MODE), } ) .extend(cv.polling_component_schema("60s")) @@ -36,3 +47,4 @@ async def to_code(config): await cg.register_component(var, config) await i2c.register_i2c_device(var, config) await sensor.register_sensor(var, config) + cg.add(var.set_measurement_mode(config[CONF_MEASUREMENT_MODE])) diff --git a/esphome/components/sgp30/sgp30.cpp b/esphome/components/sgp30/sgp30.cpp index 87cf0fa61a..4157fd55cf 100644 --- a/esphome/components/sgp30/sgp30.cpp +++ b/esphome/components/sgp30/sgp30.cpp @@ -147,8 +147,8 @@ void SGP30Component::read_iaq_baseline_() { // much if (this->store_baseline_ && (this->seconds_since_last_store_ > SHORTEST_BASELINE_STORE_INTERVAL || - abs(this->baselines_storage_.eco2 - this->eco2_baseline_) > MAXIMUM_STORAGE_DIFF || - abs(this->baselines_storage_.tvoc - this->tvoc_baseline_) > MAXIMUM_STORAGE_DIFF)) { + (uint32_t) abs(this->baselines_storage_.eco2 - this->eco2_baseline_) > MAXIMUM_STORAGE_DIFF || + (uint32_t) abs(this->baselines_storage_.tvoc - this->tvoc_baseline_) > MAXIMUM_STORAGE_DIFF)) { this->seconds_since_last_store_ = 0; this->baselines_storage_.eco2 = this->eco2_baseline_; this->baselines_storage_.tvoc = this->tvoc_baseline_; diff --git a/esphome/components/sgp40/sensirion_voc_algorithm.cpp b/esphome/components/sgp40/sensirion_voc_algorithm.cpp index f3cdeee35b..d76b776641 100644 --- a/esphome/components/sgp40/sensirion_voc_algorithm.cpp +++ b/esphome/components/sgp40/sensirion_voc_algorithm.cpp @@ -149,7 +149,7 @@ static fix16_t fix16_div(fix16_t a, fix16_t b) { /* Figure out the sign of result */ if ((a ^ b) & 0x80000000) { #ifndef FIXMATH_NO_OVERFLOW - if (result == FIX16_MINIMUM) + if (result == FIX16_MINIMUM) // NOLINT(clang-diagnostic-sign-compare) return FIX16_OVERFLOW; #endif diff --git a/esphome/components/sgp40/sgp40.cpp b/esphome/components/sgp40/sgp40.cpp index a3d2c74eb7..9561efcde2 100644 --- a/esphome/components/sgp40/sgp40.cpp +++ b/esphome/components/sgp40/sgp40.cpp @@ -144,8 +144,8 @@ int32_t SGP40Component::measure_voc_index_() { // much if (this->store_baseline_ && this->seconds_since_last_store_ > SHORTEST_BASELINE_STORE_INTERVAL) { voc_algorithm_get_states(&voc_algorithm_params_, &this->state0_, &this->state1_); - if (abs(this->baselines_storage_.state0 - this->state0_) > MAXIMUM_STORAGE_DIFF || - abs(this->baselines_storage_.state1 - this->state1_) > MAXIMUM_STORAGE_DIFF) { + if ((uint32_t) abs(this->baselines_storage_.state0 - this->state0_) > MAXIMUM_STORAGE_DIFF || + (uint32_t) abs(this->baselines_storage_.state1 - this->state1_) > MAXIMUM_STORAGE_DIFF) { this->seconds_since_last_store_ = 0; this->baselines_storage_.state0 = this->state0_; this->baselines_storage_.state1 = this->state1_; diff --git a/esphome/components/sgp40/sgp40.h b/esphome/components/sgp40/sgp40.h index bb68a1ffcf..c854b21060 100644 --- a/esphome/components/sgp40/sgp40.h +++ b/esphome/components/sgp40/sgp40.h @@ -66,7 +66,7 @@ class SGP40Component : public PollingComponent, public sensor::Sensor, public i2 uint8_t generate_crc_(const uint8_t *data, uint8_t datalen); uint16_t measure_raw_(); ESPPreferenceObject pref_; - int32_t seconds_since_last_store_; + uint32_t seconds_since_last_store_; SGP40Baselines baselines_storage_; VocAlgorithmParams voc_algorithm_params_; bool self_test_complete_; diff --git a/esphome/components/sm300d2/sm300d2.cpp b/esphome/components/sm300d2/sm300d2.cpp index e41a4855db..c726faec48 100644 --- a/esphome/components/sm300d2/sm300d2.cpp +++ b/esphome/components/sm300d2/sm300d2.cpp @@ -50,10 +50,10 @@ void SM300D2Sensor::update() { const uint16_t pm_2_5 = (response[8] * 256) + response[9]; const uint16_t pm_10_0 = (response[10] * 256) + response[11]; // A negative value is indicated by adding 0x80 (128) to the temperature value - const float temperature = ((response[12] + (response[13] * 0.1)) > 128) - ? (((response[12] + (response[13] * 0.1)) - 128) * -1) - : response[12] + (response[13] * 0.1); - const float humidity = response[14] + (response[15] * 0.1); + const float temperature = ((response[12] + (response[13] * 0.1f)) > 128) + ? (((response[12] + (response[13] * 0.1f)) - 128) * -1) + : response[12] + (response[13] * 0.1f); + const float humidity = response[14] + (response[15] * 0.1f); ESP_LOGD(TAG, "Received CO₂: %u ppm", co2); if (this->co2_sensor_ != nullptr) diff --git a/esphome/components/sntp/sntp_component.cpp b/esphome/components/sntp/sntp_component.cpp index 96be0e9709..21fcb96842 100644 --- a/esphome/components/sntp/sntp_component.cpp +++ b/esphome/components/sntp/sntp_component.cpp @@ -71,7 +71,7 @@ void SNTPComponent::loop() { if (!time.is_valid()) return; - ESP_LOGD(TAG, "Synchronized time: %d-%d-%d %d:%02d:%02d", time.year, time.month, time.day_of_month, time.hour, + ESP_LOGD(TAG, "Synchronized time: %04d-%02d-%02d %02d:%02d:%02d", time.year, time.month, time.day_of_month, time.hour, time.minute, time.second); this->time_sync_callback_.call(); this->has_time_ = true; diff --git a/esphome/components/socket/lwip_raw_tcp_impl.cpp b/esphome/components/socket/lwip_raw_tcp_impl.cpp index 922d895ff4..d57413c739 100644 --- a/esphome/components/socket/lwip_raw_tcp_impl.cpp +++ b/esphome/components/socket/lwip_raw_tcp_impl.cpp @@ -383,7 +383,7 @@ class LWIPRawImpl : public Socket { return err; } ret += err; - if (err != iov[i].iov_len) + if ((size_t) err != iov[i].iov_len) break; } return ret; @@ -462,7 +462,7 @@ class LWIPRawImpl : public Socket { return err; } written += err; - if (err != iov[i].iov_len) + if ((size_t) err != iov[i].iov_len) break; } if (written == 0) diff --git a/esphome/components/spi/spi.cpp b/esphome/components/spi/spi.cpp index d883142c81..d427e2c91b 100644 --- a/esphome/components/spi/spi.cpp +++ b/esphome/components/spi/spi.cpp @@ -55,13 +55,9 @@ void SPIComponent::setup() { } } #ifdef USE_ESP8266 - if (clk_pin == 6 && miso_pin == 7 && mosi_pin == 8) { - // pass - } else if (clk_pin == 14 && (!has_miso || miso_pin == 12) && (!has_mosi || mosi_pin == 13)) { - // pass - } else { + if (!(clk_pin == 6 && miso_pin == 7 && mosi_pin == 8) && + !(clk_pin == 14 && (!has_miso || miso_pin == 12) && (!has_mosi || mosi_pin == 13))) use_hw_spi = false; - } if (use_hw_spi) { this->hw_spi_ = &SPI; diff --git a/esphome/components/spi/spi.h b/esphome/components/spi/spi.h index 601a5c5a7e..6c3fd17e56 100644 --- a/esphome/components/spi/spi.h +++ b/esphome/components/spi/spi.h @@ -195,7 +195,14 @@ class SPIComponent : public Component { void enable(GPIOPin *cs) { #ifdef USE_SPI_ARDUINO_BACKEND if (this->hw_spi_ != nullptr) { - uint8_t data_mode = (uint8_t(CLOCK_POLARITY) << 1) | uint8_t(CLOCK_PHASE); + uint8_t data_mode = SPI_MODE0; + if (!CLOCK_POLARITY && CLOCK_PHASE) { + data_mode = SPI_MODE1; + } else if (CLOCK_POLARITY && !CLOCK_PHASE) { + data_mode = SPI_MODE2; + } else if (CLOCK_POLARITY && CLOCK_PHASE) { + data_mode = SPI_MODE3; + } SPISettings settings(DATA_RATE, BIT_ORDER, data_mode); this->hw_spi_->beginTransaction(settings); } else { diff --git a/esphome/components/ssd1306_base/ssd1306_base.cpp b/esphome/components/ssd1306_base/ssd1306_base.cpp index 2537133605..4b9feb10ce 100644 --- a/esphome/components/ssd1306_base/ssd1306_base.cpp +++ b/esphome/components/ssd1306_base/ssd1306_base.cpp @@ -292,7 +292,7 @@ const char *SSD1306::model_str_() { case SSD1305_MODEL_128_32: return "SSD1305 128x32"; case SSD1305_MODEL_128_64: - return "SSD1305 128x32"; + return "SSD1305 128x64"; default: return "Unknown"; } diff --git a/esphome/components/ssd1306_i2c/ssd1306_i2c.cpp b/esphome/components/ssd1306_i2c/ssd1306_i2c.cpp index fddea25fc8..64b09c0672 100644 --- a/esphome/components/ssd1306_i2c/ssd1306_i2c.cpp +++ b/esphome/components/ssd1306_i2c/ssd1306_i2c.cpp @@ -40,12 +40,12 @@ void I2CSSD1306::command(uint8_t value) { this->write_byte(0x00, value); } void HOT I2CSSD1306::write_display_data() { if (this->is_sh1106_()) { uint32_t i = 0; - for (uint8_t page = 0; page < this->get_height_internal() / 8; page++) { + for (uint8_t page = 0; page < (uint8_t) this->get_height_internal() / 8; page++) { this->command(0xB0 + page); // row this->command(0x02); // lower column this->command(0x10); // higher column - for (uint8_t x = 0; x < this->get_width_internal() / 16; x++) { + for (uint8_t x = 0; x < (uint8_t) this->get_width_internal() / 16; x++) { uint8_t data[16]; for (uint8_t &j : data) j = this->buffer_[i++]; diff --git a/esphome/components/ssd1306_spi/ssd1306_spi.cpp b/esphome/components/ssd1306_spi/ssd1306_spi.cpp index 33d474a8ee..7f025d77cd 100644 --- a/esphome/components/ssd1306_spi/ssd1306_spi.cpp +++ b/esphome/components/ssd1306_spi/ssd1306_spi.cpp @@ -37,12 +37,12 @@ void SPISSD1306::command(uint8_t value) { } void HOT SPISSD1306::write_display_data() { if (this->is_sh1106_()) { - for (uint8_t y = 0; y < this->get_height_internal() / 8; y++) { + for (uint8_t y = 0; y < (uint8_t) this->get_height_internal() / 8; y++) { this->command(0xB0 + y); this->command(0x02); this->command(0x10); this->dc_pin_->digital_write(true); - for (uint8_t x = 0; x < this->get_width_internal(); x++) { + for (uint8_t x = 0; x < (uint8_t) this->get_width_internal(); x++) { this->enable(); this->write_byte(this->buffer_[x + y * this->get_width_internal()]); this->disable(); diff --git a/esphome/components/st7735/st7735.cpp b/esphome/components/st7735/st7735.cpp index 8490aa1fe4..c5178986f3 100644 --- a/esphome/components/st7735/st7735.cpp +++ b/esphome/components/st7735/st7735.cpp @@ -275,7 +275,7 @@ void ST7735::setup() { uint8_t data = 0; if (this->model_ != INITR_HALLOWING) { - uint8_t data = ST77XX_MADCTL_MX | ST77XX_MADCTL_MY; + data = ST77XX_MADCTL_MX | ST77XX_MADCTL_MY; } if (this->usebgr_) { data = data | ST7735_MADCTL_BGR; @@ -446,7 +446,7 @@ void HOT ST7735::write_display_data_() { this->dc_pin_->digital_write(true); if (this->eightbitcolor_) { - for (int line = 0; line < this->get_buffer_length(); line = line + this->get_width_internal()) { + for (size_t line = 0; line < this->get_buffer_length(); line = line + this->get_width_internal()) { for (int index = 0; index < this->get_width_internal(); ++index) { auto color332 = display::ColorUtil::to_color(this->buffer_[index + line], display::ColorOrder::COLOR_ORDER_RGB, display::ColorBitness::COLOR_BITNESS_332, true); diff --git a/esphome/components/st7920/st7920.cpp b/esphome/components/st7920/st7920.cpp index d985b0a426..63fa0ba72f 100644 --- a/esphome/components/st7920/st7920.cpp +++ b/esphome/components/st7920/st7920.cpp @@ -74,7 +74,7 @@ void ST7920::goto_xy_(uint16_t x, uint16_t y) { void HOT ST7920::write_display_data() { uint8_t i, j, b; - for (j = 0; j < this->get_height_internal() / 2; j++) { + for (j = 0; j < (uint8_t)(this->get_height_internal() / 2); j++) { this->goto_xy_(0, j); this->enable(); for (i = 0; i < 16; i++) { // 16 bytes from line #0+ diff --git a/esphome/components/tca9548a/tca9548a.cpp b/esphome/components/tca9548a/tca9548a.cpp index e902eb5ed4..f3f8685287 100644 --- a/esphome/components/tca9548a/tca9548a.cpp +++ b/esphome/components/tca9548a/tca9548a.cpp @@ -22,7 +22,7 @@ i2c::ErrorCode TCA9548AChannel::writev(uint8_t address, i2c::WriteBuffer *buffer void TCA9548AComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up TCA9548A..."); uint8_t status = 0; - if (this->read_register(0x00, &status, 1) != i2c::ERROR_OK) { + if (this->read(&status, 1) != i2c::ERROR_OK) { ESP_LOGI(TAG, "TCA9548A failed"); this->mark_failed(); return; diff --git a/esphome/components/tca9548a/tca9548a.h b/esphome/components/tca9548a/tca9548a.h index 314346d317..39d07c2eb4 100644 --- a/esphome/components/tca9548a/tca9548a.h +++ b/esphome/components/tca9548a/tca9548a.h @@ -24,6 +24,7 @@ class TCA9548AComponent : public Component, public i2c::I2CDevice { public: void setup() override; void dump_config() override; + float get_setup_priority() const override { return setup_priority::IO; } void update(); i2c::ErrorCode switch_to_channel(uint8_t channel); diff --git a/esphome/components/teleinfo/teleinfo.cpp b/esphome/components/teleinfo/teleinfo.cpp index 5a1e44ac8b..d9f80134f4 100644 --- a/esphome/components/teleinfo/teleinfo.cpp +++ b/esphome/components/teleinfo/teleinfo.cpp @@ -118,7 +118,7 @@ void TeleInfo::loop() { * */ while ((buf_finger = static_cast(memchr(buf_finger, (int) 0xa, buf_index_ - 1))) && - ((buf_finger - buf_) < buf_index_)) { + ((buf_finger - buf_) < buf_index_)) { // NOLINT(clang-diagnostic-sign-compare) /* * Make sure timesamp is nullified between each tag as some tags don't * have a timestamp diff --git a/esphome/components/template/button/__init__.py b/esphome/components/template/button/__init__.py new file mode 100644 index 0000000000..aa192d118e --- /dev/null +++ b/esphome/components/template/button/__init__.py @@ -0,0 +1,13 @@ +import esphome.config_validation as cv +from esphome.components import button + + +CONFIG_SCHEMA = button.BUTTON_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(button.Button), + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + await button.new_button(config) diff --git a/esphome/components/text_sensor/__init__.py b/esphome/components/text_sensor/__init__.py index a070e6f86d..e0fc6af19c 100644 --- a/esphome/components/text_sensor/__init__.py +++ b/esphome/components/text_sensor/__init__.py @@ -49,6 +49,7 @@ ToLowerFilter = text_sensor_ns.class_("ToLowerFilter", Filter) AppendFilter = text_sensor_ns.class_("AppendFilter", Filter) PrependFilter = text_sensor_ns.class_("PrependFilter", Filter) SubstituteFilter = text_sensor_ns.class_("SubstituteFilter", Filter) +MapFilter = text_sensor_ns.class_("MapFilter", Filter) @FILTER_REGISTRY.register("lambda", LambdaFilter, cv.returning_lambda) @@ -79,26 +80,21 @@ async def prepend_filter_to_code(config, filter_id): return cg.new_Pvariable(filter_id, config) -def validate_substitute(value): - if isinstance(value, dict): - return cv.Schema( - { - cv.Required(CONF_FROM): cv.string, - cv.Required(CONF_TO): cv.string, - } - )(value) - value = cv.string(value) - if "->" not in value: - raise cv.Invalid("Substitute mapping must contain '->'") - a, b = value.split("->", 1) - a, b = a.strip(), b.strip() - return validate_substitute({CONF_FROM: cv.string(a), CONF_TO: cv.string(b)}) +def validate_mapping(value): + if not isinstance(value, dict): + value = cv.string(value) + if "->" not in value: + raise cv.Invalid("Mapping must contain '->'") + a, b = value.split("->", 1) + value = {CONF_FROM: a.strip(), CONF_TO: b.strip()} + + return cv.Schema( + {cv.Required(CONF_FROM): cv.string, cv.Required(CONF_TO): cv.string} + )(value) @FILTER_REGISTRY.register( - "substitute", - SubstituteFilter, - cv.All(cv.ensure_list(validate_substitute), cv.Length(min=2)), + "substitute", SubstituteFilter, cv.ensure_list(validate_mapping) ) async def substitute_filter_to_code(config, filter_id): from_strings = [conf[CONF_FROM] for conf in config] @@ -106,6 +102,14 @@ async def substitute_filter_to_code(config, filter_id): return cg.new_Pvariable(filter_id, from_strings, to_strings) +@FILTER_REGISTRY.register("map", MapFilter, cv.ensure_list(validate_mapping)) +async def map_filter_to_code(config, filter_id): + map_ = cg.std_ns.class_("map").template(cg.std_string, cg.std_string) + return cg.new_Pvariable( + filter_id, map_([(item[CONF_FROM], item[CONF_TO]) for item in config]) + ) + + icon = cv.icon diff --git a/esphome/components/text_sensor/filter.cpp b/esphome/components/text_sensor/filter.cpp index 14df6238ff..c6cbcd7c1e 100644 --- a/esphome/components/text_sensor/filter.cpp +++ b/esphome/components/text_sensor/filter.cpp @@ -64,11 +64,17 @@ optional PrependFilter::new_value(std::string value) { return this- // Substitute optional SubstituteFilter::new_value(std::string value) { std::size_t pos; - for (int i = 0; i < this->from_strings_.size(); i++) + for (size_t i = 0; i < this->from_strings_.size(); i++) while ((pos = value.find(this->from_strings_[i])) != std::string::npos) value.replace(pos, this->from_strings_[i].size(), this->to_strings_[i]); return value; } +// Map +optional MapFilter::new_value(std::string value) { + auto item = mappings_.find(value); + return item == mappings_.end() ? value : item->second; +} + } // namespace text_sensor } // namespace esphome diff --git a/esphome/components/text_sensor/filter.h b/esphome/components/text_sensor/filter.h index 6a1d9ab04e..38f35e6172 100644 --- a/esphome/components/text_sensor/filter.h +++ b/esphome/components/text_sensor/filter.h @@ -4,6 +4,7 @@ #include "esphome/core/helpers.h" #include #include +#include namespace esphome { namespace text_sensor { @@ -108,5 +109,15 @@ class SubstituteFilter : public Filter { std::vector to_strings_; }; +/// A filter that maps values from one set to another +class MapFilter : public Filter { + public: + MapFilter(std::map mappings) : mappings_(std::move(mappings)) {} + optional new_value(std::string value) override; + + protected: + std::map mappings_; +}; + } // namespace text_sensor } // namespace esphome diff --git a/esphome/components/time/real_time_clock.cpp b/esphome/components/time/real_time_clock.cpp index 064e6f899c..6f6739d293 100644 --- a/esphome/components/time/real_time_clock.cpp +++ b/esphome/components/time/real_time_clock.cpp @@ -35,7 +35,7 @@ void RealTimeClock::synchronize_epoch_(uint32_t epoch) { } auto time = this->now(); - ESP_LOGD(TAG, "Synchronized time: %d-%d-%d %d:%d:%d", time.year, time.month, time.day_of_month, time.hour, + ESP_LOGD(TAG, "Synchronized time: %04d-%02d-%02d %02d:%02d:%02d", time.year, time.month, time.day_of_month, time.hour, time.minute, time.second); this->time_sync_callback_.call(); diff --git a/esphome/components/tof10120/tof10120_sensor.cpp b/esphome/components/tof10120/tof10120_sensor.cpp index 4ba591f9c4..5cd086938e 100644 --- a/esphome/components/tof10120/tof10120_sensor.cpp +++ b/esphome/components/tof10120/tof10120_sensor.cpp @@ -50,7 +50,7 @@ void TOF10120Sensor::update() { ESP_LOGW(TAG, "Distance measurement out of range"); this->publish_state(NAN); } else { - this->publish_state(distance_mm / 1000.0); + this->publish_state(distance_mm / 1000.0f); } this->status_clear_warning(); } diff --git a/esphome/components/toshiba/toshiba.cpp b/esphome/components/toshiba/toshiba.cpp index 25528abbe1..975a149b52 100644 --- a/esphome/components/toshiba/toshiba.cpp +++ b/esphome/components/toshiba/toshiba.cpp @@ -580,13 +580,13 @@ bool ToshibaClimate::on_receive(remote_base::RemoteReceiveData data) { temperature_code = (message[4] >> 4) | (message[14] & RAC_PT1411HWRU_FLAG_FRAC) | (message[15] & RAC_PT1411HWRU_FLAG_NEG); if (message[15] & RAC_PT1411HWRU_FLAG_FAH) { - for (uint8_t i = 0; i < RAC_PT1411HWRU_TEMPERATURE_F.size(); i++) { + for (size_t i = 0; i < RAC_PT1411HWRU_TEMPERATURE_F.size(); i++) { if (RAC_PT1411HWRU_TEMPERATURE_F[i] == temperature_code) { this->target_temperature = static_cast((i + TOSHIBA_RAC_PT1411HWRU_TEMP_F_MIN - 32) * 5) / 9; } } } else { - for (uint8_t i = 0; i < RAC_PT1411HWRU_TEMPERATURE_C.size(); i++) { + for (size_t i = 0; i < RAC_PT1411HWRU_TEMPERATURE_C.size(); i++) { if (RAC_PT1411HWRU_TEMPERATURE_C[i] == temperature_code) { this->target_temperature = i + TOSHIBA_RAC_PT1411HWRU_TEMP_C_MIN; } diff --git a/esphome/components/total_daily_energy/sensor.py b/esphome/components/total_daily_energy/sensor.py index 6a8a416b81..0c20ccd27c 100644 --- a/esphome/components/total_daily_energy/sensor.py +++ b/esphome/components/total_daily_energy/sensor.py @@ -4,6 +4,7 @@ from esphome.components import sensor, time from esphome.const import ( CONF_ICON, CONF_ID, + CONF_RESTORE, CONF_TIME_ID, DEVICE_CLASS_ENERGY, CONF_METHOD, @@ -36,6 +37,7 @@ CONFIG_SCHEMA = ( cv.GenerateID(): cv.declare_id(TotalDailyEnergy), cv.GenerateID(CONF_TIME_ID): cv.use_id(time.RealTimeClock), cv.Required(CONF_POWER_ID): cv.use_id(sensor.Sensor), + cv.Optional(CONF_RESTORE, default=True): cv.boolean, cv.Optional( CONF_MIN_SAVE_INTERVAL, default="0s" ): cv.positive_time_period_milliseconds, @@ -70,5 +72,6 @@ async def to_code(config): cg.add(var.set_parent(sens)) time_ = await cg.get_variable(config[CONF_TIME_ID]) cg.add(var.set_time(time_)) + cg.add(var.set_restore(config[CONF_RESTORE])) cg.add(var.set_min_save_interval(config[CONF_MIN_SAVE_INTERVAL])) cg.add(var.set_method(config[CONF_METHOD])) diff --git a/esphome/components/total_daily_energy/total_daily_energy.cpp b/esphome/components/total_daily_energy/total_daily_energy.cpp index 178dc7cbe0..3746301715 100644 --- a/esphome/components/total_daily_energy/total_daily_energy.cpp +++ b/esphome/components/total_daily_energy/total_daily_energy.cpp @@ -7,14 +7,14 @@ namespace total_daily_energy { static const char *const TAG = "total_daily_energy"; void TotalDailyEnergy::setup() { - this->pref_ = global_preferences->make_preference(this->get_object_id_hash()); + float initial_value = 0; - float recovered; - if (this->pref_.load(&recovered)) { - this->publish_state_and_save(recovered); - } else { - this->publish_state_and_save(0); + if (this->restore_) { + this->pref_ = global_preferences->make_preference(this->get_object_id_hash()); + this->pref_.load(&initial_value); } + this->publish_state_and_save(initial_value); + this->last_update_ = millis(); this->last_save_ = this->last_update_; diff --git a/esphome/components/total_daily_energy/total_daily_energy.h b/esphome/components/total_daily_energy/total_daily_energy.h index fedceafbd3..498f65891e 100644 --- a/esphome/components/total_daily_energy/total_daily_energy.h +++ b/esphome/components/total_daily_energy/total_daily_energy.h @@ -17,6 +17,7 @@ enum TotalDailyEnergyMethod { class TotalDailyEnergy : public sensor::Sensor, public Component { public: + void set_restore(bool restore) { restore_ = restore; } void set_min_save_interval(uint32_t min_interval) { this->min_save_interval_ = min_interval; } void set_time(time::RealTimeClock *time) { time_ = time; } void set_parent(Sensor *parent) { parent_ = parent; } @@ -41,6 +42,7 @@ class TotalDailyEnergy : public sensor::Sensor, public Component { uint32_t last_update_{0}; uint32_t last_save_{0}; uint32_t min_save_interval_{0}; + bool restore_; float total_energy_{0.0f}; float last_power_state_{0.0f}; }; diff --git a/esphome/components/tuya/__init__.py b/esphome/components/tuya/__init__.py index 436759979a..965893e012 100644 --- a/esphome/components/tuya/__init__.py +++ b/esphome/components/tuya/__init__.py @@ -1,16 +1,84 @@ from esphome.components import time +from esphome import automation import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import uart -from esphome.const import CONF_ID, CONF_TIME_ID +from esphome.const import CONF_ID, CONF_TIME_ID, CONF_TRIGGER_ID, CONF_SENSOR_DATAPOINT DEPENDENCIES = ["uart"] CONF_IGNORE_MCU_UPDATE_ON_DATAPOINTS = "ignore_mcu_update_on_datapoints" +CONF_ON_DATAPOINT_UPDATE = "on_datapoint_update" +CONF_DATAPOINT_TYPE = "datapoint_type" + tuya_ns = cg.esphome_ns.namespace("tuya") Tuya = tuya_ns.class_("Tuya", cg.Component, uart.UARTDevice) +DPTYPE_ANY = "any" +DPTYPE_RAW = "raw" +DPTYPE_BOOL = "bool" +DPTYPE_INT = "int" +DPTYPE_UINT = "uint" +DPTYPE_STRING = "string" +DPTYPE_ENUM = "enum" +DPTYPE_BITMASK = "bitmask" + +DATAPOINT_TYPES = { + DPTYPE_ANY: tuya_ns.struct("TuyaDatapoint"), + DPTYPE_RAW: cg.std_vector.template(cg.uint8), + DPTYPE_BOOL: cg.bool_, + DPTYPE_INT: cg.int_, + DPTYPE_UINT: cg.uint32, + DPTYPE_STRING: cg.std_string, + DPTYPE_ENUM: cg.uint8, + DPTYPE_BITMASK: cg.uint32, +} + +DATAPOINT_TRIGGERS = { + DPTYPE_ANY: tuya_ns.class_( + "TuyaDatapointUpdateTrigger", + automation.Trigger.template(DATAPOINT_TYPES[DPTYPE_ANY]), + ), + DPTYPE_RAW: tuya_ns.class_( + "TuyaRawDatapointUpdateTrigger", + automation.Trigger.template(DATAPOINT_TYPES[DPTYPE_RAW]), + ), + DPTYPE_BOOL: tuya_ns.class_( + "TuyaBoolDatapointUpdateTrigger", + automation.Trigger.template(DATAPOINT_TYPES[DPTYPE_BOOL]), + ), + DPTYPE_INT: tuya_ns.class_( + "TuyaIntDatapointUpdateTrigger", + automation.Trigger.template(DATAPOINT_TYPES[DPTYPE_INT]), + ), + DPTYPE_UINT: tuya_ns.class_( + "TuyaUIntDatapointUpdateTrigger", + automation.Trigger.template(DATAPOINT_TYPES[DPTYPE_UINT]), + ), + DPTYPE_STRING: tuya_ns.class_( + "TuyaStringDatapointUpdateTrigger", + automation.Trigger.template(DATAPOINT_TYPES[DPTYPE_STRING]), + ), + DPTYPE_ENUM: tuya_ns.class_( + "TuyaEnumDatapointUpdateTrigger", + automation.Trigger.template(DATAPOINT_TYPES[DPTYPE_ENUM]), + ), + DPTYPE_BITMASK: tuya_ns.class_( + "TuyaBitmaskDatapointUpdateTrigger", + automation.Trigger.template(DATAPOINT_TYPES[DPTYPE_BITMASK]), + ), +} + + +def assign_declare_id(value): + value = value.copy() + value[CONF_TRIGGER_ID] = cv.declare_id( + DATAPOINT_TRIGGERS[value[CONF_DATAPOINT_TYPE]] + )(value[CONF_TRIGGER_ID].id) + return value + + CONF_TUYA_ID = "tuya_id" CONFIG_SCHEMA = ( cv.Schema( @@ -20,6 +88,18 @@ CONFIG_SCHEMA = ( cv.Optional(CONF_IGNORE_MCU_UPDATE_ON_DATAPOINTS): cv.ensure_list( cv.uint8_t ), + cv.Optional(CONF_ON_DATAPOINT_UPDATE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + DATAPOINT_TRIGGERS[DPTYPE_ANY] + ), + cv.Required(CONF_SENSOR_DATAPOINT): cv.uint8_t, + cv.Optional(CONF_DATAPOINT_TYPE, default=DPTYPE_ANY): cv.one_of( + *DATAPOINT_TRIGGERS, lower=True + ), + }, + extra_validators=assign_declare_id, + ), } ) .extend(cv.COMPONENT_SCHEMA) @@ -37,3 +117,10 @@ async def to_code(config): if CONF_IGNORE_MCU_UPDATE_ON_DATAPOINTS in config: for dp in config[CONF_IGNORE_MCU_UPDATE_ON_DATAPOINTS]: cg.add(var.add_ignore_mcu_update_on_datapoints(dp)) + for conf in config.get(CONF_ON_DATAPOINT_UPDATE, []): + trigger = cg.new_Pvariable( + conf[CONF_TRIGGER_ID], var, conf[CONF_SENSOR_DATAPOINT] + ) + await automation.build_automation( + trigger, [(DATAPOINT_TYPES[conf[CONF_DATAPOINT_TYPE]], "x")], conf + ) diff --git a/esphome/components/tuya/automation.cpp b/esphome/components/tuya/automation.cpp new file mode 100644 index 0000000000..a8cfd098f1 --- /dev/null +++ b/esphome/components/tuya/automation.cpp @@ -0,0 +1,67 @@ +#include "esphome/core/log.h" + +#include "automation.h" + +static const char *const TAG = "tuya.automation"; + +namespace esphome { +namespace tuya { + +void check_expected_datapoint(const TuyaDatapoint &dp, TuyaDatapointType expected) { + if (dp.type != expected) { + ESP_LOGW(TAG, "Tuya sensor %u expected datapoint type %#02hhX but got %#02hhX", dp.id, + static_cast(expected), static_cast(dp.type)); + } +} + +TuyaRawDatapointUpdateTrigger::TuyaRawDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id) { + parent->register_listener(sensor_id, [this](const TuyaDatapoint &dp) { + check_expected_datapoint(dp, TuyaDatapointType::RAW); + this->trigger(dp.value_raw); + }); +} + +TuyaBoolDatapointUpdateTrigger::TuyaBoolDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id) { + parent->register_listener(sensor_id, [this](const TuyaDatapoint &dp) { + check_expected_datapoint(dp, TuyaDatapointType::BOOLEAN); + this->trigger(dp.value_bool); + }); +} + +TuyaIntDatapointUpdateTrigger::TuyaIntDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id) { + parent->register_listener(sensor_id, [this](const TuyaDatapoint &dp) { + check_expected_datapoint(dp, TuyaDatapointType::INTEGER); + this->trigger(dp.value_int); + }); +} + +TuyaUIntDatapointUpdateTrigger::TuyaUIntDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id) { + parent->register_listener(sensor_id, [this](const TuyaDatapoint &dp) { + check_expected_datapoint(dp, TuyaDatapointType::INTEGER); + this->trigger(dp.value_uint); + }); +} + +TuyaStringDatapointUpdateTrigger::TuyaStringDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id) { + parent->register_listener(sensor_id, [this](const TuyaDatapoint &dp) { + check_expected_datapoint(dp, TuyaDatapointType::STRING); + this->trigger(dp.value_string); + }); +} + +TuyaEnumDatapointUpdateTrigger::TuyaEnumDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id) { + parent->register_listener(sensor_id, [this](const TuyaDatapoint &dp) { + check_expected_datapoint(dp, TuyaDatapointType::ENUM); + this->trigger(dp.value_enum); + }); +} + +TuyaBitmaskDatapointUpdateTrigger::TuyaBitmaskDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id) { + parent->register_listener(sensor_id, [this](const TuyaDatapoint &dp) { + check_expected_datapoint(dp, TuyaDatapointType::BITMASK); + this->trigger(dp.value_bitmask); + }); +} + +} // namespace tuya +} // namespace esphome diff --git a/esphome/components/tuya/automation.h b/esphome/components/tuya/automation.h new file mode 100644 index 0000000000..d7706e1d60 --- /dev/null +++ b/esphome/components/tuya/automation.h @@ -0,0 +1,53 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/automation.h" +#include "tuya.h" + +namespace esphome { +namespace tuya { + +class TuyaDatapointUpdateTrigger : public Trigger { + public: + explicit TuyaDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id) { + parent->register_listener(sensor_id, [this](const TuyaDatapoint &dp) { this->trigger(dp); }); + } +}; + +class TuyaRawDatapointUpdateTrigger : public Trigger> { + public: + explicit TuyaRawDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id); +}; + +class TuyaBoolDatapointUpdateTrigger : public Trigger { + public: + explicit TuyaBoolDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id); +}; + +class TuyaIntDatapointUpdateTrigger : public Trigger { + public: + explicit TuyaIntDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id); +}; + +class TuyaUIntDatapointUpdateTrigger : public Trigger { + public: + explicit TuyaUIntDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id); +}; + +class TuyaStringDatapointUpdateTrigger : public Trigger { + public: + explicit TuyaStringDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id); +}; + +class TuyaEnumDatapointUpdateTrigger : public Trigger { + public: + explicit TuyaEnumDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id); +}; + +class TuyaBitmaskDatapointUpdateTrigger : public Trigger { + public: + explicit TuyaBitmaskDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id); +}; + +} // namespace tuya +} // namespace esphome diff --git a/esphome/components/tuya/binary_sensor/__init__.py b/esphome/components/tuya/binary_sensor/__init__.py index 65f13ea422..cd4a2db89f 100644 --- a/esphome/components/tuya/binary_sensor/__init__.py +++ b/esphome/components/tuya/binary_sensor/__init__.py @@ -1,14 +1,12 @@ from esphome.components import binary_sensor import esphome.config_validation as cv import esphome.codegen as cg -from esphome.const import CONF_ID +from esphome.const import CONF_ID, CONF_SENSOR_DATAPOINT from .. import tuya_ns, CONF_TUYA_ID, Tuya DEPENDENCIES = ["tuya"] CODEOWNERS = ["@jesserockz"] -CONF_SENSOR_DATAPOINT = "sensor_datapoint" - TuyaBinarySensor = tuya_ns.class_( "TuyaBinarySensor", binary_sensor.BinarySensor, cg.Component ) diff --git a/esphome/components/tuya/cover/__init__.py b/esphome/components/tuya/cover/__init__.py index 5a654841f7..f886c7030f 100644 --- a/esphome/components/tuya/cover/__init__.py +++ b/esphome/components/tuya/cover/__init__.py @@ -5,16 +5,27 @@ from esphome.const import ( CONF_OUTPUT_ID, CONF_MIN_VALUE, CONF_MAX_VALUE, + CONF_RESTORE_MODE, ) from .. import tuya_ns, CONF_TUYA_ID, Tuya DEPENDENCIES = ["tuya"] +CONF_CONTROL_DATAPOINT = "control_datapoint" +CONF_DIRECTION_DATAPOINT = "direction_datapoint" CONF_POSITION_DATAPOINT = "position_datapoint" +CONF_POSITION_REPORT_DATAPOINT = "position_report_datapoint" CONF_INVERT_POSITION = "invert_position" TuyaCover = tuya_ns.class_("TuyaCover", cover.Cover, cg.Component) +TuyaCoverRestoreMode = tuya_ns.enum("TuyaCoverRestoreMode") +RESTORE_MODES = { + "NO_RESTORE": TuyaCoverRestoreMode.COVER_NO_RESTORE, + "RESTORE": TuyaCoverRestoreMode.COVER_RESTORE, + "RESTORE_AND_CALL": TuyaCoverRestoreMode.COVER_RESTORE_AND_CALL, +} + def validate_range(config): if config[CONF_MIN_VALUE] > config[CONF_MAX_VALUE]: @@ -29,10 +40,16 @@ CONFIG_SCHEMA = cv.All( { cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(TuyaCover), cv.GenerateID(CONF_TUYA_ID): cv.use_id(Tuya), + cv.Optional(CONF_CONTROL_DATAPOINT): cv.uint8_t, + cv.Optional(CONF_DIRECTION_DATAPOINT): cv.uint8_t, cv.Required(CONF_POSITION_DATAPOINT): cv.uint8_t, + cv.Optional(CONF_POSITION_REPORT_DATAPOINT): cv.uint8_t, cv.Optional(CONF_MIN_VALUE, default=0): cv.int_, cv.Optional(CONF_MAX_VALUE, default=100): cv.int_, cv.Optional(CONF_INVERT_POSITION, default=False): cv.boolean, + cv.Optional(CONF_RESTORE_MODE, default="RESTORE"): cv.enum( + RESTORE_MODES, upper=True + ), }, ).extend(cv.COMPONENT_SCHEMA), validate_range, @@ -44,9 +61,16 @@ async def to_code(config): await cg.register_component(var, config) await cover.register_cover(var, config) + if CONF_CONTROL_DATAPOINT in config: + cg.add(var.set_control_id(config[CONF_CONTROL_DATAPOINT])) + if CONF_DIRECTION_DATAPOINT in config: + cg.add(var.set_direction_id(config[CONF_DIRECTION_DATAPOINT])) cg.add(var.set_position_id(config[CONF_POSITION_DATAPOINT])) + if CONF_POSITION_REPORT_DATAPOINT in config: + cg.add(var.set_position_report_id(config[CONF_POSITION_REPORT_DATAPOINT])) cg.add(var.set_min_value(config[CONF_MIN_VALUE])) cg.add(var.set_max_value(config[CONF_MAX_VALUE])) cg.add(var.set_invert_position(config[CONF_INVERT_POSITION])) + cg.add(var.set_restore_mode(config[CONF_RESTORE_MODE])) paren = await cg.get_variable(config[CONF_TUYA_ID]) cg.add(var.set_tuya_parent(paren)) diff --git a/esphome/components/tuya/cover/tuya_cover.cpp b/esphome/components/tuya/cover/tuya_cover.cpp index 7da1312938..b63eb9109d 100644 --- a/esphome/components/tuya/cover/tuya_cover.cpp +++ b/esphome/components/tuya/cover/tuya_cover.cpp @@ -4,48 +4,122 @@ namespace esphome { namespace tuya { +const uint8_t COMMAND_OPEN = 0x00; +const uint8_t COMMAND_CLOSE = 0x02; +const uint8_t COMMAND_STOP = 0x01; + +using namespace esphome::cover; + static const char *const TAG = "tuya.cover"; void TuyaCover::setup() { this->value_range_ = this->max_value_ - this->min_value_; - if (this->position_id_.has_value()) { - this->parent_->register_listener(*this->position_id_, [this](const TuyaDatapoint &datapoint) { - auto pos = float(datapoint.value_uint - this->min_value_) / this->value_range_; - if (this->invert_position_) - pos = 1.0f - pos; - this->position = pos; - this->publish_state(); - }); + + this->parent_->add_on_initialized_callback([this]() { + // Set the direction (if configured/supported). + this->set_direction_(this->invert_position_); + + // Handle configured restore mode. + switch (this->restore_mode_) { + case COVER_NO_RESTORE: + break; + case COVER_RESTORE: { + auto restore = this->restore_state_(); + if (restore.has_value()) + restore->apply(this); + break; + } + case COVER_RESTORE_AND_CALL: { + auto restore = this->restore_state_(); + if (restore.has_value()) { + restore->to_call(this).perform(); + } + break; + } + } + }); + + uint8_t report_id = *this->position_id_; + if (this->position_report_id_.has_value()) { + // A position report datapoint is configured; listen to that instead. + report_id = *this->position_report_id_; } + + this->parent_->register_listener(report_id, [this](const TuyaDatapoint &datapoint) { + if (datapoint.value_int == 123) { + ESP_LOGD(TAG, "Ignoring MCU position report - not calibrated"); + return; + } + auto pos = float(datapoint.value_uint - this->min_value_) / this->value_range_; + this->position = 1.0f - pos; + this->publish_state(); + }); } void TuyaCover::control(const cover::CoverCall &call) { if (call.get_stop()) { - auto pos = this->position; - if (this->invert_position_) + if (this->control_id_.has_value()) { + this->parent_->force_set_enum_datapoint_value(*this->control_id_, COMMAND_STOP); + } else { + auto pos = this->position; pos = 1.0f - pos; - auto position_int = static_cast(pos * this->value_range_); - position_int = position_int + this->min_value_; + auto position_int = static_cast(pos * this->value_range_); + position_int = position_int + this->min_value_; - parent_->set_integer_datapoint_value(*this->position_id_, position_int); + parent_->set_integer_datapoint_value(*this->position_id_, position_int); + } } if (call.get_position().has_value()) { auto pos = *call.get_position(); - if (this->invert_position_) + if (this->control_id_.has_value() && (pos == COVER_OPEN || pos == COVER_CLOSED)) { + if (pos == COVER_OPEN) { + this->parent_->force_set_enum_datapoint_value(*this->control_id_, COMMAND_OPEN); + } else { + this->parent_->force_set_enum_datapoint_value(*this->control_id_, COMMAND_CLOSE); + } + } else { pos = 1.0f - pos; - auto position_int = static_cast(pos * this->value_range_); - position_int = position_int + this->min_value_; + auto position_int = static_cast(pos * this->value_range_); + position_int = position_int + this->min_value_; - parent_->set_integer_datapoint_value(*this->position_id_, position_int); + parent_->set_integer_datapoint_value(*this->position_id_, position_int); + } } this->publish_state(); } +void TuyaCover::set_direction_(bool inverted) { + if (!this->direction_id_.has_value()) { + return; + } + + if (inverted) { + ESP_LOGD(TAG, "Setting direction: inverted"); + } else { + ESP_LOGD(TAG, "Setting direction: normal"); + } + + this->parent_->set_boolean_datapoint_value(*this->direction_id_, inverted); +} + void TuyaCover::dump_config() { ESP_LOGCONFIG(TAG, "Tuya Cover:"); + if (this->invert_position_) { + if (this->direction_id_.has_value()) { + ESP_LOGCONFIG(TAG, " Inverted"); + } else { + ESP_LOGCONFIG(TAG, " Configured as Inverted, but direction_datapoint isn't configured"); + } + } + if (this->control_id_.has_value()) + ESP_LOGCONFIG(TAG, " Control has datapoint ID %u", *this->control_id_); + if (this->direction_id_.has_value()) + ESP_LOGCONFIG(TAG, " Direction has datapoint ID %u", *this->direction_id_); if (this->position_id_.has_value()) ESP_LOGCONFIG(TAG, " Position has datapoint ID %u", *this->position_id_); + if (this->position_report_id_.has_value()) + ESP_LOGCONFIG(TAG, " Position Report has datapoint ID %u", *this->position_report_id_); } cover::CoverTraits TuyaCover::get_traits() { diff --git a/esphome/components/tuya/cover/tuya_cover.h b/esphome/components/tuya/cover/tuya_cover.h index c3b0c3e069..87c72b0e66 100644 --- a/esphome/components/tuya/cover/tuya_cover.h +++ b/esphome/components/tuya/cover/tuya_cover.h @@ -7,22 +7,37 @@ namespace esphome { namespace tuya { +enum TuyaCoverRestoreMode { + COVER_NO_RESTORE, + COVER_RESTORE, + COVER_RESTORE_AND_CALL, +}; + class TuyaCover : public cover::Cover, public Component { public: void setup() override; void dump_config() override; - void set_position_id(uint8_t dimmer_id) { this->position_id_ = dimmer_id; } + void set_control_id(uint8_t control_id) { this->control_id_ = control_id; } + void set_direction_id(uint8_t direction_id) { this->direction_id_ = direction_id; } + void set_position_id(uint8_t position_id) { this->position_id_ = position_id; } + void set_position_report_id(uint8_t position_report_id) { this->position_report_id_ = position_report_id; } void set_tuya_parent(Tuya *parent) { this->parent_ = parent; } void set_min_value(uint32_t min_value) { min_value_ = min_value; } void set_max_value(uint32_t max_value) { max_value_ = max_value; } void set_invert_position(bool invert_position) { invert_position_ = invert_position; } + void set_restore_mode(TuyaCoverRestoreMode restore_mode) { restore_mode_ = restore_mode; } protected: void control(const cover::CoverCall &call) override; + void set_direction_(bool inverted); cover::CoverTraits get_traits() override; Tuya *parent_; + TuyaCoverRestoreMode restore_mode_{}; + optional control_id_{}; + optional direction_id_{}; optional position_id_{}; + optional position_report_id_{}; uint32_t min_value_; uint32_t max_value_; uint32_t value_range_; diff --git a/esphome/components/tuya/sensor/__init__.py b/esphome/components/tuya/sensor/__init__.py index d87a2e7ce4..441400fa43 100644 --- a/esphome/components/tuya/sensor/__init__.py +++ b/esphome/components/tuya/sensor/__init__.py @@ -1,14 +1,12 @@ from esphome.components import sensor import esphome.config_validation as cv import esphome.codegen as cg -from esphome.const import CONF_ID +from esphome.const import CONF_ID, CONF_SENSOR_DATAPOINT from .. import tuya_ns, CONF_TUYA_ID, Tuya DEPENDENCIES = ["tuya"] CODEOWNERS = ["@jesserockz"] -CONF_SENSOR_DATAPOINT = "sensor_datapoint" - TuyaSensor = tuya_ns.class_("TuyaSensor", sensor.Sensor, cg.Component) CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend( diff --git a/esphome/components/tuya/text_sensor/__init__.py b/esphome/components/tuya/text_sensor/__init__.py new file mode 100644 index 0000000000..1989ca10e3 --- /dev/null +++ b/esphome/components/tuya/text_sensor/__init__.py @@ -0,0 +1,29 @@ +from esphome.components import text_sensor +import esphome.config_validation as cv +import esphome.codegen as cg +from esphome.const import CONF_ID, CONF_SENSOR_DATAPOINT +from .. import tuya_ns, CONF_TUYA_ID, Tuya + +DEPENDENCIES = ["tuya"] +CODEOWNERS = ["@dentra"] + +TuyaTextSensor = tuya_ns.class_("TuyaTextSensor", text_sensor.TextSensor, cg.Component) + +CONFIG_SCHEMA = text_sensor.TEXT_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(TuyaTextSensor), + cv.GenerateID(CONF_TUYA_ID): cv.use_id(Tuya), + cv.Required(CONF_SENSOR_DATAPOINT): cv.uint8_t, + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await text_sensor.register_text_sensor(var, config) + + paren = await cg.get_variable(config[CONF_TUYA_ID]) + cg.add(var.set_tuya_parent(paren)) + + cg.add(var.set_sensor_id(config[CONF_SENSOR_DATAPOINT])) diff --git a/esphome/components/tuya/text_sensor/tuya_text_sensor.cpp b/esphome/components/tuya/text_sensor/tuya_text_sensor.cpp new file mode 100644 index 0000000000..e939225453 --- /dev/null +++ b/esphome/components/tuya/text_sensor/tuya_text_sensor.cpp @@ -0,0 +1,35 @@ +#include "esphome/core/log.h" +#include "tuya_text_sensor.h" + +namespace esphome { +namespace tuya { + +static const char *const TAG = "tuya.text_sensor"; + +void TuyaTextSensor::setup() { + this->parent_->register_listener(this->sensor_id_, [this](const TuyaDatapoint &datapoint) { + switch (datapoint.type) { + case TuyaDatapointType::STRING: + ESP_LOGD(TAG, "MCU reported text sensor %u is: %s", datapoint.id, datapoint.value_string.c_str()); + this->publish_state(datapoint.value_string); + break; + case TuyaDatapointType::RAW: { + std::string data = hexencode(datapoint.value_raw); + ESP_LOGD(TAG, "MCU reported text sensor %u is: %s", datapoint.id, data.c_str()); + this->publish_state(data); + break; + } + default: + ESP_LOGW(TAG, "Unsupported data type for tuya text sensor %u: %#02hhX", datapoint.id, datapoint.type); + break; + } + }); +} + +void TuyaTextSensor::dump_config() { + ESP_LOGCONFIG(TAG, "Tuya Text Sensor:"); + ESP_LOGCONFIG(TAG, " Text Sensor has datapoint ID %u", this->sensor_id_); +} + +} // namespace tuya +} // namespace esphome diff --git a/esphome/components/tuya/text_sensor/tuya_text_sensor.h b/esphome/components/tuya/text_sensor/tuya_text_sensor.h new file mode 100644 index 0000000000..502ae5e8c7 --- /dev/null +++ b/esphome/components/tuya/text_sensor/tuya_text_sensor.h @@ -0,0 +1,24 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/tuya/tuya.h" +#include "esphome/components/text_sensor/text_sensor.h" + +namespace esphome { +namespace tuya { + +class TuyaTextSensor : public text_sensor::TextSensor, public Component { + public: + void setup() override; + void dump_config() override; + void set_sensor_id(uint8_t sensor_id) { this->sensor_id_ = sensor_id; } + + void set_tuya_parent(Tuya *parent) { this->parent_ = parent; } + + protected: + Tuya *parent_; + uint8_t sensor_id_{0}; +}; + +} // namespace tuya +} // namespace esphome diff --git a/esphome/components/tuya/tuya.cpp b/esphome/components/tuya/tuya.cpp index 4f65fa7118..404a70a80e 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -143,7 +143,7 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff case TuyaCommandType::PRODUCT_QUERY: { // check it is a valid string made up of printable characters bool valid = true; - for (int i = 0; i < len; i++) { + for (size_t i = 0; i < len; i++) { if (!std::isprint(buffer[i])) { valid = false; break; @@ -195,6 +195,7 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff if (this->init_state_ == TuyaInitState::INIT_DATAPOINT) { this->init_state_ = TuyaInitState::INIT_DONE; this->set_timeout("datapoint_dump", 1000, [this] { this->dump_config(); }); + this->initialized_callback_.call(); } this->handle_datapoint_(buffer, len); break; @@ -339,8 +340,6 @@ void Tuya::send_raw_command_(TuyaCommand command) { this->expected_response_ = TuyaCommandType::CONF_QUERY; break; case TuyaCommandType::DATAPOINT_DELIVER: - this->expected_response_ = TuyaCommandType::DATAPOINT_REPORT; - break; case TuyaCommandType::DATAPOINT_QUERY: this->expected_response_ = TuyaCommandType::DATAPOINT_REPORT; break; @@ -441,53 +440,51 @@ void Tuya::send_local_time_() { #endif void Tuya::set_raw_datapoint_value(uint8_t datapoint_id, const std::vector &value) { - ESP_LOGD(TAG, "Setting datapoint %u to %s", datapoint_id, hexencode(value).c_str()); - optional datapoint = this->get_datapoint_(datapoint_id); - if (!datapoint.has_value()) { - ESP_LOGW(TAG, "Setting unknown datapoint %u", datapoint_id); - } else if (datapoint->type != TuyaDatapointType::RAW) { - ESP_LOGE(TAG, "Attempt to set datapoint %u with incorrect type", datapoint_id); - return; - } else if (datapoint->value_raw == value) { - ESP_LOGV(TAG, "Not sending unchanged value"); - return; - } - this->send_datapoint_command_(datapoint_id, TuyaDatapointType::RAW, value); + this->set_raw_datapoint_value_(datapoint_id, value, false); } void Tuya::set_boolean_datapoint_value(uint8_t datapoint_id, bool value) { - this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::BOOLEAN, value, 1); + this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::BOOLEAN, value, 1, false); } void Tuya::set_integer_datapoint_value(uint8_t datapoint_id, uint32_t value) { - this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::INTEGER, value, 4); + this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::INTEGER, value, 4, false); } void Tuya::set_string_datapoint_value(uint8_t datapoint_id, const std::string &value) { - ESP_LOGD(TAG, "Setting datapoint %u to %s", datapoint_id, value.c_str()); - optional datapoint = this->get_datapoint_(datapoint_id); - if (!datapoint.has_value()) { - ESP_LOGW(TAG, "Setting unknown datapoint %u", datapoint_id); - } else if (datapoint->type != TuyaDatapointType::STRING) { - ESP_LOGE(TAG, "Attempt to set datapoint %u with incorrect type", datapoint_id); - return; - } else if (datapoint->value_string == value) { - ESP_LOGV(TAG, "Not sending unchanged value"); - return; - } - std::vector data; - for (char const &c : value) { - data.push_back(c); - } - this->send_datapoint_command_(datapoint_id, TuyaDatapointType::STRING, data); + this->set_string_datapoint_value_(datapoint_id, value, false); } void Tuya::set_enum_datapoint_value(uint8_t datapoint_id, uint8_t value) { - this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::ENUM, value, 1); + this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::ENUM, value, 1, false); } void Tuya::set_bitmask_datapoint_value(uint8_t datapoint_id, uint32_t value, uint8_t length) { - this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::BITMASK, value, length); + this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::BITMASK, value, length, false); +} + +void Tuya::force_set_raw_datapoint_value(uint8_t datapoint_id, const std::vector &value) { + this->set_raw_datapoint_value_(datapoint_id, value, true); +} + +void Tuya::force_set_boolean_datapoint_value(uint8_t datapoint_id, bool value) { + this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::BOOLEAN, value, 1, true); +} + +void Tuya::force_set_integer_datapoint_value(uint8_t datapoint_id, uint32_t value) { + this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::INTEGER, value, 4, true); +} + +void Tuya::force_set_string_datapoint_value(uint8_t datapoint_id, const std::string &value) { + this->set_string_datapoint_value_(datapoint_id, value, true); +} + +void Tuya::force_set_enum_datapoint_value(uint8_t datapoint_id, uint8_t value) { + this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::ENUM, value, 1, true); +} + +void Tuya::force_set_bitmask_datapoint_value(uint8_t datapoint_id, uint32_t value, uint8_t length) { + this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::BITMASK, value, length, true); } optional Tuya::get_datapoint_(uint8_t datapoint_id) { @@ -498,7 +495,7 @@ optional Tuya::get_datapoint_(uint8_t datapoint_id) { } void Tuya::set_numeric_datapoint_value_(uint8_t datapoint_id, TuyaDatapointType datapoint_type, const uint32_t value, - uint8_t length) { + uint8_t length, bool forced) { ESP_LOGD(TAG, "Setting datapoint %u to %u", datapoint_id, value); optional datapoint = this->get_datapoint_(datapoint_id); if (!datapoint.has_value()) { @@ -506,7 +503,7 @@ void Tuya::set_numeric_datapoint_value_(uint8_t datapoint_id, TuyaDatapointType } else if (datapoint->type != datapoint_type) { ESP_LOGE(TAG, "Attempt to set datapoint %u with incorrect type", datapoint_id); return; - } else if (datapoint->value_uint == value) { + } else if (!forced && datapoint->value_uint == value) { ESP_LOGV(TAG, "Not sending unchanged value"); return; } @@ -528,6 +525,40 @@ void Tuya::set_numeric_datapoint_value_(uint8_t datapoint_id, TuyaDatapointType this->send_datapoint_command_(datapoint_id, datapoint_type, data); } +void Tuya::set_raw_datapoint_value_(uint8_t datapoint_id, const std::vector &value, bool forced) { + ESP_LOGD(TAG, "Setting datapoint %u to %s", datapoint_id, hexencode(value).c_str()); + optional datapoint = this->get_datapoint_(datapoint_id); + if (!datapoint.has_value()) { + ESP_LOGW(TAG, "Setting unknown datapoint %u", datapoint_id); + } else if (datapoint->type != TuyaDatapointType::RAW) { + ESP_LOGE(TAG, "Attempt to set datapoint %u with incorrect type", datapoint_id); + return; + } else if (!forced && datapoint->value_raw == value) { + ESP_LOGV(TAG, "Not sending unchanged value"); + return; + } + this->send_datapoint_command_(datapoint_id, TuyaDatapointType::RAW, value); +} + +void Tuya::set_string_datapoint_value_(uint8_t datapoint_id, const std::string &value, bool forced) { + ESP_LOGD(TAG, "Setting datapoint %u to %s", datapoint_id, value.c_str()); + optional datapoint = this->get_datapoint_(datapoint_id); + if (!datapoint.has_value()) { + ESP_LOGW(TAG, "Setting unknown datapoint %u", datapoint_id); + } else if (datapoint->type != TuyaDatapointType::STRING) { + ESP_LOGE(TAG, "Attempt to set datapoint %u with incorrect type", datapoint_id); + return; + } else if (!forced && datapoint->value_string == value) { + ESP_LOGV(TAG, "Not sending unchanged value"); + return; + } + std::vector data; + for (char const &c : value) { + data.push_back(c); + } + this->send_datapoint_command_(datapoint_id, TuyaDatapointType::STRING, data); +} + void Tuya::send_datapoint_command_(uint8_t datapoint_id, TuyaDatapointType datapoint_type, std::vector data) { std::vector buffer; buffer.push_back(datapoint_id); @@ -552,5 +583,7 @@ void Tuya::register_listener(uint8_t datapoint_id, const std::functioninit_state_; } + } // namespace tuya } // namespace esphome diff --git a/esphome/components/tuya/tuya.h b/esphome/components/tuya/tuya.h index 785399502b..c46d61119e 100644 --- a/esphome/components/tuya/tuya.h +++ b/esphome/components/tuya/tuya.h @@ -2,6 +2,7 @@ #include "esphome/core/component.h" #include "esphome/core/defines.h" +#include "esphome/core/helpers.h" #include "esphome/components/uart/uart.h" #ifdef USE_TIME @@ -81,12 +82,22 @@ class Tuya : public Component, public uart::UARTDevice { void set_string_datapoint_value(uint8_t datapoint_id, const std::string &value); void set_enum_datapoint_value(uint8_t datapoint_id, uint8_t value); void set_bitmask_datapoint_value(uint8_t datapoint_id, uint32_t value, uint8_t length); + void force_set_raw_datapoint_value(uint8_t datapoint_id, const std::vector &value); + void force_set_boolean_datapoint_value(uint8_t datapoint_id, bool value); + void force_set_integer_datapoint_value(uint8_t datapoint_id, uint32_t value); + void force_set_string_datapoint_value(uint8_t datapoint_id, const std::string &value); + void force_set_enum_datapoint_value(uint8_t datapoint_id, uint8_t value); + void force_set_bitmask_datapoint_value(uint8_t datapoint_id, uint32_t value, uint8_t length); + TuyaInitState get_init_state(); #ifdef USE_TIME void set_time_id(time::RealTimeClock *time_id) { this->time_id_ = time_id; } #endif void add_ignore_mcu_update_on_datapoints(uint8_t ignore_mcu_update_on_datapoints) { this->ignore_mcu_update_on_datapoints_.push_back(ignore_mcu_update_on_datapoints); } + void add_on_initialized_callback(std::function callback) { + this->initialized_callback_.add(std::move(callback)); + } protected: void handle_char_(uint8_t c); @@ -100,7 +111,9 @@ class Tuya : public Component, public uart::UARTDevice { void send_command_(const TuyaCommand &command); void send_empty_command_(TuyaCommandType command); void set_numeric_datapoint_value_(uint8_t datapoint_id, TuyaDatapointType datapoint_type, uint32_t value, - uint8_t length); + uint8_t length, bool forced); + void set_string_datapoint_value_(uint8_t datapoint_id, const std::string &value, bool forced); + void set_raw_datapoint_value_(uint8_t datapoint_id, const std::vector &value, bool forced); void send_datapoint_command_(uint8_t datapoint_id, TuyaDatapointType datapoint_type, std::vector data); void send_wifi_status_(); @@ -122,6 +135,7 @@ class Tuya : public Component, public uart::UARTDevice { std::vector command_queue_; optional expected_response_{}; uint8_t wifi_status_ = -1; + CallbackManager initialized_callback_{}; }; } // namespace tuya diff --git a/esphome/components/tx20/tx20.cpp b/esphome/components/tx20/tx20.cpp index 6e0b6343d1..fefcc8f4d5 100644 --- a/esphome/components/tx20/tx20.cpp +++ b/esphome/components/tx20/tx20.cpp @@ -113,7 +113,7 @@ void Tx20Component::decode_and_publish_() { if (tx20_sa == 4) { if (chk == tx20_sd) { if (tx20_sf == tx20_sc) { - tx20_wind_speed_kmh = float(tx20_sc) * 0.36; + tx20_wind_speed_kmh = float(tx20_sc) * 0.36f; ESP_LOGV(TAG, "WindSpeed %f", tx20_wind_speed_kmh); if (this->wind_speed_sensor_ != nullptr) this->wind_speed_sensor_->publish_state(tx20_wind_speed_kmh); diff --git a/esphome/components/uart/__init__.py b/esphome/components/uart/__init__.py index 159b08d2d9..a63b220fc7 100644 --- a/esphome/components/uart/__init__.py +++ b/esphome/components/uart/__init__.py @@ -3,6 +3,7 @@ from typing import Optional import esphome.codegen as cg import esphome.config_validation as cv import esphome.final_validate as fv +from esphome.yaml_util import make_data_base from esphome import pins, automation from esphome.const import ( CONF_BAUD_RATE, @@ -13,6 +14,7 @@ from esphome.const import ( CONF_UART_ID, CONF_DATA, CONF_RX_BUFFER_SIZE, + CONF_INVERTED, CONF_INVERT, CONF_TRIGGER_ID, CONF_SEQUENCE, @@ -24,6 +26,7 @@ from esphome.const import ( CONF_DELIMITER, CONF_DUMMY_RECEIVER, CONF_DUMMY_RECEIVER_ID, + CONF_LAMBDA, ) from esphome.core import CORE @@ -65,6 +68,19 @@ def validate_rx_pin(value): return value +def validate_invert_esp32(config): + if ( + CORE.is_esp32 + and CONF_TX_PIN in config + and CONF_RX_PIN in config + and config[CONF_TX_PIN][CONF_INVERTED] != config[CONF_RX_PIN][CONF_INVERTED] + ): + raise cv.Invalid( + "Different invert values for TX and RX pin are not (yet) supported for ESP32." + ) + return config + + def _uart_declare_type(value): if CORE.is_esp8266: return cv.declare_id(ESP8266UartComponent)(value) @@ -94,7 +110,26 @@ UART_DIRECTIONS = { "BOTH": UARTDirection.UART_DIRECTION_BOTH, } -AFTER_DEFAULTS = {CONF_BYTES: 256, CONF_TIMEOUT: "100ms"} +# The reason for having CONF_BYTES at 150 by default: +# +# The log message buffer size is 512 bytes by default. About 35 bytes are +# used for the log prefix. That leaves us with 477 bytes for logging data. +# The default log output is hex, which uses 3 characters per represented +# byte (2 hex chars + 1 separator). That means that 477 / 3 = 159 bytes +# can be represented in a single log line. Using 150, because people love +# round numbers. +AFTER_DEFAULTS = {CONF_BYTES: 150, CONF_TIMEOUT: "100ms"} + +# By default, log in hex format when no specific sequence is provided. +DEFAULT_DEBUG_OUTPUT = "UARTDebug::log_hex(direction, bytes, ':');" +DEFAULT_SEQUENCE = [{CONF_LAMBDA: make_data_base(DEFAULT_DEBUG_OUTPUT)}] + + +def maybe_empty_debug(value): + if value is None: + value = {} + return DEBUG_SCHEMA(value) + DEBUG_SCHEMA = cv.Schema( { @@ -113,7 +148,9 @@ DEBUG_SCHEMA = cv.Schema( cv.Optional(CONF_DELIMITER): cv.templatable(validate_raw_data), } ), - cv.Required(CONF_SEQUENCE): automation.validate_automation(), + cv.Optional( + CONF_SEQUENCE, default=DEFAULT_SEQUENCE + ): automation.validate_automation(), cv.Optional(CONF_DUMMY_RECEIVER, default=False): cv.boolean, cv.GenerateID(CONF_DUMMY_RECEIVER_ID): cv.declare_id(UARTDummyReceiver), } @@ -135,10 +172,11 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_INVERT): cv.invalid( "This option has been removed. Please instead use invert in the tx/rx pin schemas." ), - cv.Optional(CONF_DEBUG): DEBUG_SCHEMA, + cv.Optional(CONF_DEBUG): maybe_empty_debug, } ).extend(cv.COMPONENT_SCHEMA), cv.has_at_least_one_key(CONF_TX_PIN, CONF_RX_PIN), + validate_invert_esp32, ) diff --git a/esphome/components/uart/uart_component.h b/esphome/components/uart/uart_component.h index 73694d3db7..42702cf5b8 100644 --- a/esphome/components/uart/uart_component.h +++ b/esphome/components/uart/uart_component.h @@ -52,6 +52,7 @@ class UARTComponent { void set_tx_pin(InternalGPIOPin *tx_pin) { this->tx_pin_ = tx_pin; } void set_rx_pin(InternalGPIOPin *rx_pin) { this->rx_pin_ = rx_pin; } void set_rx_buffer_size(size_t rx_buffer_size) { this->rx_buffer_size_ = rx_buffer_size; } + size_t get_rx_buffer_size() { return this->rx_buffer_size_; } void set_stop_bits(uint8_t stop_bits) { this->stop_bits_ = stop_bits; } uint8_t get_stop_bits() const { return this->stop_bits_; } diff --git a/esphome/components/uart/uart_component_esp8266.cpp b/esphome/components/uart/uart_component_esp8266.cpp index c367de05bb..370adad779 100644 --- a/esphome/components/uart/uart_component_esp8266.cpp +++ b/esphome/components/uart/uart_component_esp8266.cpp @@ -45,6 +45,11 @@ uint32_t ESP8266UartComponent::get_config() { else config |= UART_NB_STOP_BIT_2; + if (this->tx_pin_ != nullptr && this->tx_pin_->is_inverted()) + config |= BIT(22); + if (this->rx_pin_ != nullptr && this->rx_pin_->is_inverted()) + config |= BIT(19); + return config; } @@ -209,9 +214,7 @@ void IRAM_ATTR ESP8266SoftwareSerial::gpio_intr(ESP8266SoftwareSerial *arg) { /* If parity is enabled, just read it and ignore it. */ /* TODO: Should we check parity? Or is it too slow for nothing added..*/ - if (arg->parity_ == UART_CONFIG_PARITY_EVEN) - arg->read_bit_(&wait, start); - else if (arg->parity_ == UART_CONFIG_PARITY_ODD) + if (arg->parity_ == UART_CONFIG_PARITY_EVEN || arg->parity_ == UART_CONFIG_PARITY_ODD) arg->read_bit_(&wait, start); // Stop bit diff --git a/esphome/components/uart/uart_debugger.cpp b/esphome/components/uart/uart_debugger.cpp index 9a535656a2..e2d92eac60 100644 --- a/esphome/components/uart/uart_debugger.cpp +++ b/esphome/components/uart/uart_debugger.cpp @@ -90,6 +90,11 @@ void UARTDummyReceiver::loop() { } } +// In the upcoming log functions, a delay was added after all log calls. +// This is done to allow the system to ship the log lines via the API +// TCP connection(s). Without these delays, debug log lines could go +// missing when UART devices block the main loop for too long. + void UARTDebug::log_hex(UARTDirection direction, std::vector bytes, uint8_t separator) { std::string res; if (direction == UART_DIRECTION_RX) { @@ -107,6 +112,7 @@ void UARTDebug::log_hex(UARTDirection direction, std::vector bytes, uin res += buf; } ESP_LOGD(TAG, "%s", res.c_str()); + delay(10); } void UARTDebug::log_string(UARTDirection direction, std::vector bytes) { @@ -150,6 +156,7 @@ void UARTDebug::log_string(UARTDirection direction, std::vector bytes) } res += '"'; ESP_LOGD(TAG, "%s", res.c_str()); + delay(10); } void UARTDebug::log_int(UARTDirection direction, std::vector bytes, uint8_t separator) { @@ -167,6 +174,7 @@ void UARTDebug::log_int(UARTDirection direction, std::vector bytes, uin res += to_string(bytes[i]); } ESP_LOGD(TAG, "%s", res.c_str()); + delay(10); } void UARTDebug::log_binary(UARTDirection direction, std::vector bytes, uint8_t separator) { @@ -186,6 +194,7 @@ void UARTDebug::log_binary(UARTDirection direction, std::vector bytes, res += buf; } ESP_LOGD(TAG, "%s", res.c_str()); + delay(10); } } // namespace uart diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.cpp b/esphome/components/waveshare_epaper/waveshare_epaper.cpp index 92fa289cfa..322c375f0e 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.cpp +++ b/esphome/components/waveshare_epaper/waveshare_epaper.cpp @@ -360,15 +360,18 @@ void HOT WaveshareEPaperTypeA::display() { // COMMAND DISPLAY UPDATE CONTROL 2 this->command(0x22); - if (this->model_ == WAVESHARE_EPAPER_2_9_IN_V2 || this->model_ == WAVESHARE_EPAPER_1_54_IN_V2) { - this->data(full_update ? 0xF7 : 0xFF); - } else if (this->model_ == TTGO_EPAPER_2_13_IN_B73) { - this->data(0xC7); - } else if (this->model_ == TTGO_EPAPER_2_13_IN_B74) { - // this->data(0xC7); - this->data(full_update ? 0xF7 : 0xFF); - } else { - this->data(0xC4); + switch (this->model_) { + case WAVESHARE_EPAPER_2_9_IN_V2: + case WAVESHARE_EPAPER_1_54_IN_V2: + case TTGO_EPAPER_2_13_IN_B74: + this->data(full_update ? 0xF7 : 0xFF); + break; + case TTGO_EPAPER_2_13_IN_B73: + this->data(0xC7); + break; + default: + this->data(0xC4); + break; } // COMMAND MASTER ACTIVATION @@ -381,20 +384,14 @@ void HOT WaveshareEPaperTypeA::display() { int WaveshareEPaperTypeA::get_width_internal() { switch (this->model_) { case WAVESHARE_EPAPER_1_54_IN: - return 200; case WAVESHARE_EPAPER_1_54_IN_V2: return 200; case WAVESHARE_EPAPER_2_13_IN: - return 128; case TTGO_EPAPER_2_13_IN: - return 128; case TTGO_EPAPER_2_13_IN_B73: case TTGO_EPAPER_2_13_IN_B74: - return 128; case TTGO_EPAPER_2_13_IN_B1: - return 128; case WAVESHARE_EPAPER_2_9_IN: - return 128; case WAVESHARE_EPAPER_2_9_IN_V2: return 128; } @@ -403,20 +400,15 @@ int WaveshareEPaperTypeA::get_width_internal() { int WaveshareEPaperTypeA::get_height_internal() { switch (this->model_) { case WAVESHARE_EPAPER_1_54_IN: - return 200; case WAVESHARE_EPAPER_1_54_IN_V2: return 200; case WAVESHARE_EPAPER_2_13_IN: - return 250; case TTGO_EPAPER_2_13_IN: - return 250; case TTGO_EPAPER_2_13_IN_B73: case TTGO_EPAPER_2_13_IN_B74: - return 250; case TTGO_EPAPER_2_13_IN_B1: return 250; case WAVESHARE_EPAPER_2_9_IN: - return 296; case WAVESHARE_EPAPER_2_9_IN_V2: return 296; } @@ -433,11 +425,10 @@ void WaveshareEPaperTypeA::set_full_update_every(uint32_t full_update_every) { this->full_update_every_ = full_update_every; } -int WaveshareEPaperTypeA::idle_timeout_() { +uint32_t WaveshareEPaperTypeA::idle_timeout_() { switch (this->model_) { case TTGO_EPAPER_2_13_IN_B1: return 2500; - break; default: return WaveshareEPaper::idle_timeout_(); } @@ -646,7 +637,7 @@ void HOT WaveshareEPaper2P9InB::display() { this->command(0x13); delay(2); this->start_data_(); - for (int i = 0; i < this->get_buffer_length_(); i++) + for (size_t i = 0; i < this->get_buffer_length_(); i++) this->write_byte(0x00); this->end_data_(); delay(2); @@ -825,7 +816,7 @@ void HOT WaveshareEPaper4P2InBV2::display() { // COMMAND DATA START TRANSMISSION 2 (RED data) this->command(0x13); this->start_data_(); - for (int i = 0; i < this->get_buffer_length_(); i++) + for (size_t i = 0; i < this->get_buffer_length_(); i++) this->write_byte(0xFF); this->end_data_(); delay(2); @@ -1183,7 +1174,7 @@ static const uint8_t PART_UPDATE_LUT_TTGO_DKE[LUT_SIZE_TTGO_DKE_PART] = { 0x0, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0xF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, @@ -1293,7 +1284,7 @@ void HOT WaveshareEPaper2P13InDKE::display() { int WaveshareEPaper2P13InDKE::get_width_internal() { return 128; } int WaveshareEPaper2P13InDKE::get_height_internal() { return 250; } -int WaveshareEPaper2P13InDKE::idle_timeout_() { return 5000; } +uint32_t WaveshareEPaper2P13InDKE::idle_timeout_() { return 5000; } void WaveshareEPaper2P13InDKE::dump_config() { LOG_DISPLAY("", "Waveshare E-Paper", this); ESP_LOGCONFIG(TAG, " Model: 2.13inDKE"); diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.h b/esphome/components/waveshare_epaper/waveshare_epaper.h index b50596643d..a1e2f6037a 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.h +++ b/esphome/components/waveshare_epaper/waveshare_epaper.h @@ -61,7 +61,7 @@ class WaveshareEPaper : public PollingComponent, GPIOPin *reset_pin_{nullptr}; GPIOPin *dc_pin_; GPIOPin *busy_pin_{nullptr}; - virtual int idle_timeout_() { return 1000; } // NOLINT(readability-identifier-naming) + virtual uint32_t idle_timeout_() { return 1000u; } // NOLINT(readability-identifier-naming) }; enum WaveshareEPaperTypeAModel { @@ -110,7 +110,7 @@ class WaveshareEPaperTypeA : public WaveshareEPaper { uint32_t full_update_every_{30}; uint32_t at_update_{0}; WaveshareEPaperTypeAModel model_; - int idle_timeout_() override; + uint32_t idle_timeout_() override; }; enum WaveshareEPaperTypeBModel { @@ -346,7 +346,7 @@ class WaveshareEPaper2P13InDKE : public WaveshareEPaper { int get_height_internal() override; - int idle_timeout_() override; + uint32_t idle_timeout_() override; uint32_t full_update_every_{30}; uint32_t at_update_{0}; diff --git a/esphome/components/web_server/__init__.py b/esphome/components/web_server/__init__.py index dc652e0312..d9ff84d501 100644 --- a/esphome/components/web_server/__init__.py +++ b/esphome/components/web_server/__init__.py @@ -12,6 +12,7 @@ from esphome.const import ( CONF_AUTH, CONF_USERNAME, CONF_PASSWORD, + CONF_INCLUDE_INTERNAL, CONF_OTA, ) from esphome.core import CORE, coroutine_with_priority @@ -42,6 +43,7 @@ CONFIG_SCHEMA = cv.Schema( cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id( web_server_base.WebServerBase ), + cv.Optional(CONF_INCLUDE_INTERNAL, default=False): cv.boolean, cv.Optional(CONF_OTA, default=True): cv.boolean, } ).extend(cv.COMPONENT_SCHEMA) @@ -75,3 +77,4 @@ async def to_code(config): path = CORE.relative_config_path(config[CONF_JS_INCLUDE]) with open(file=path, mode="r", encoding="utf-8") as myfile: cg.add(var.set_js_include(myfile.read())) + cg.add(var.set_include_internal(config[CONF_INCLUDE_INTERNAL])) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 6f47f460af..29cb4827bd 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -31,10 +31,10 @@ static const char *const TAG = "web_server"; void write_row(AsyncResponseStream *stream, EntityBase *obj, const std::string &klass, const std::string &action, const std::function &action_func = nullptr) { - if (obj->is_internal()) - return; stream->print("print(klass.c_str()); + if (obj->is_internal()) + stream->print(" internal"); stream->print("\" id=\""); stream->print(klass.c_str()); stream->print("-"); @@ -83,7 +83,7 @@ void WebServer::set_js_include(const char *js_include) { this->js_include_ = js_ void WebServer::setup() { ESP_LOGCONFIG(TAG, "Setting up web server..."); - this->setup_controller(); + this->setup_controller(this->include_internal_); this->base_->init(); this->events_.onConnect([this](AsyncEventSourceClient *client) { @@ -92,55 +92,55 @@ void WebServer::setup() { #ifdef USE_SENSOR for (auto *obj : App.get_sensors()) - if (!obj->is_internal()) + if (this->include_internal_ || !obj->is_internal()) client->send(this->sensor_json(obj, obj->state).c_str(), "state"); #endif #ifdef USE_SWITCH for (auto *obj : App.get_switches()) - if (!obj->is_internal()) + if (this->include_internal_ || !obj->is_internal()) client->send(this->switch_json(obj, obj->state).c_str(), "state"); #endif #ifdef USE_BINARY_SENSOR for (auto *obj : App.get_binary_sensors()) - if (!obj->is_internal()) + if (this->include_internal_ || !obj->is_internal()) client->send(this->binary_sensor_json(obj, obj->state).c_str(), "state"); #endif #ifdef USE_FAN for (auto *obj : App.get_fans()) - if (!obj->is_internal()) + if (this->include_internal_ || !obj->is_internal()) client->send(this->fan_json(obj).c_str(), "state"); #endif #ifdef USE_LIGHT for (auto *obj : App.get_lights()) - if (!obj->is_internal()) + if (this->include_internal_ || !obj->is_internal()) client->send(this->light_json(obj).c_str(), "state"); #endif #ifdef USE_TEXT_SENSOR for (auto *obj : App.get_text_sensors()) - if (!obj->is_internal()) + if (this->include_internal_ || !obj->is_internal()) client->send(this->text_sensor_json(obj, obj->state).c_str(), "state"); #endif #ifdef USE_COVER for (auto *obj : App.get_covers()) - if (!obj->is_internal()) + if (this->include_internal_ || !obj->is_internal()) client->send(this->cover_json(obj).c_str(), "state"); #endif #ifdef USE_NUMBER for (auto *obj : App.get_numbers()) - if (!obj->is_internal()) + if (this->include_internal_ || !obj->is_internal()) client->send(this->number_json(obj, obj->state).c_str(), "state"); #endif #ifdef USE_SELECT for (auto *obj : App.get_selects()) - if (!obj->is_internal()) + if (this->include_internal_ || !obj->is_internal()) client->send(this->select_json(obj, obj->state).c_str(), "state"); #endif }); @@ -188,57 +188,71 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { #ifdef USE_SENSOR for (auto *obj : App.get_sensors()) - write_row(stream, obj, "sensor", ""); + if (this->include_internal_ || !obj->is_internal()) + write_row(stream, obj, "sensor", ""); #endif #ifdef USE_SWITCH for (auto *obj : App.get_switches()) - write_row(stream, obj, "switch", ""); + if (this->include_internal_ || !obj->is_internal()) + write_row(stream, obj, "switch", ""); +#endif + +#ifdef USE_BUTTON + for (auto *obj : App.get_buttons()) + write_row(stream, obj, "button", ""); #endif #ifdef USE_BINARY_SENSOR for (auto *obj : App.get_binary_sensors()) - write_row(stream, obj, "binary_sensor", ""); + if (this->include_internal_ || !obj->is_internal()) + write_row(stream, obj, "binary_sensor", ""); #endif #ifdef USE_FAN for (auto *obj : App.get_fans()) - write_row(stream, obj, "fan", ""); + if (this->include_internal_ || !obj->is_internal()) + write_row(stream, obj, "fan", ""); #endif #ifdef USE_LIGHT for (auto *obj : App.get_lights()) - write_row(stream, obj, "light", ""); + if (this->include_internal_ || !obj->is_internal()) + write_row(stream, obj, "light", ""); #endif #ifdef USE_TEXT_SENSOR for (auto *obj : App.get_text_sensors()) - write_row(stream, obj, "text_sensor", ""); + if (this->include_internal_ || !obj->is_internal()) + write_row(stream, obj, "text_sensor", ""); #endif #ifdef USE_COVER for (auto *obj : App.get_covers()) - write_row(stream, obj, "cover", ""); + if (this->include_internal_ || !obj->is_internal()) + write_row(stream, obj, "cover", ""); #endif #ifdef USE_NUMBER for (auto *obj : App.get_numbers()) - write_row(stream, obj, "number", ""); + if (this->include_internal_ || !obj->is_internal()) + write_row(stream, obj, "number", ""); #endif #ifdef USE_SELECT for (auto *obj : App.get_selects()) - write_row(stream, obj, "select", "", [](AsyncResponseStream &stream, EntityBase *obj) { - select::Select *select = (select::Select *) obj; - stream.print(""); - }); + if (this->include_internal_ || !obj->is_internal()) + write_row(stream, obj, "select", "", [](AsyncResponseStream &stream, EntityBase *obj) { + select::Select *select = (select::Select *) obj; + stream.print(""); + }); #endif stream->print(F("

See ESPHome Web API for " @@ -293,8 +307,6 @@ void WebServer::on_sensor_update(sensor::Sensor *obj, float state) { } void WebServer::handle_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (sensor::Sensor *obj : App.get_sensors()) { - if (obj->is_internal()) - continue; if (obj->get_object_id() != match.id) continue; std::string data = this->sensor_json(obj, obj->state); @@ -321,8 +333,6 @@ void WebServer::on_text_sensor_update(text_sensor::TextSensor *obj, const std::s } void WebServer::handle_text_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (text_sensor::TextSensor *obj : App.get_text_sensors()) { - if (obj->is_internal()) - continue; if (obj->get_object_id() != match.id) continue; std::string data = this->text_sensor_json(obj, obj->state); @@ -353,8 +363,6 @@ std::string WebServer::switch_json(switch_::Switch *obj, bool value) { } void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (switch_::Switch *obj : App.get_switches()) { - if (obj->is_internal()) - continue; if (obj->get_object_id() != match.id) continue; @@ -379,10 +387,28 @@ void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlM } #endif +#ifdef USE_BUTTON +void WebServer::handle_button_request(AsyncWebServerRequest *request, const UrlMatch &match) { + for (button::Button *obj : App.get_buttons()) { + if (obj->is_internal()) + continue; + if (obj->get_object_id() != match.id) + continue; + + if (request->method() == HTTP_POST && match.method == "press") { + this->defer([obj]() { obj->press(); }); + request->send(200); + } else { + request->send(404); + } + return; + } + request->send(404); +} +#endif + #ifdef USE_BINARY_SENSOR void WebServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) { - if (obj->is_internal()) - return; this->events_.send(this->binary_sensor_json(obj, state).c_str(), "state"); } std::string WebServer::binary_sensor_json(binary_sensor::BinarySensor *obj, bool value) { @@ -394,8 +420,6 @@ std::string WebServer::binary_sensor_json(binary_sensor::BinarySensor *obj, bool } void WebServer::handle_binary_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (binary_sensor::BinarySensor *obj : App.get_binary_sensors()) { - if (obj->is_internal()) - continue; if (obj->get_object_id() != match.id) continue; std::string data = this->binary_sensor_json(obj, obj->state); @@ -407,11 +431,7 @@ void WebServer::handle_binary_sensor_request(AsyncWebServerRequest *request, con #endif #ifdef USE_FAN -void WebServer::on_fan_update(fan::FanState *obj) { - if (obj->is_internal()) - return; - this->events_.send(this->fan_json(obj).c_str(), "state"); -} +void WebServer::on_fan_update(fan::FanState *obj) { this->events_.send(this->fan_json(obj).c_str(), "state"); } std::string WebServer::fan_json(fan::FanState *obj) { return json::build_json([obj](JsonObject &root) { root["id"] = "fan-" + obj->get_object_id(); @@ -442,8 +462,6 @@ std::string WebServer::fan_json(fan::FanState *obj) { } void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (fan::FanState *obj : App.get_fans()) { - if (obj->is_internal()) - continue; if (obj->get_object_id() != match.id) continue; @@ -504,15 +522,9 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc #endif #ifdef USE_LIGHT -void WebServer::on_light_update(light::LightState *obj) { - if (obj->is_internal()) - return; - this->events_.send(this->light_json(obj).c_str(), "state"); -} +void WebServer::on_light_update(light::LightState *obj) { this->events_.send(this->light_json(obj).c_str(), "state"); } void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (light::LightState *obj : App.get_lights()) { - if (obj->is_internal()) - continue; if (obj->get_object_id() != match.id) continue; @@ -579,15 +591,9 @@ std::string WebServer::light_json(light::LightState *obj) { #endif #ifdef USE_COVER -void WebServer::on_cover_update(cover::Cover *obj) { - if (obj->is_internal()) - return; - this->events_.send(this->cover_json(obj).c_str(), "state"); -} +void WebServer::on_cover_update(cover::Cover *obj) { this->events_.send(this->cover_json(obj).c_str(), "state"); } void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (cover::Cover *obj : App.get_covers()) { - if (obj->is_internal()) - continue; if (obj->get_object_id() != match.id) continue; @@ -646,8 +652,6 @@ void WebServer::on_number_update(number::Number *obj, float state) { } void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (auto *obj : App.get_numbers()) { - if (obj->is_internal()) - continue; if (obj->get_object_id() != match.id) continue; std::string data = this->number_json(obj, obj->state); @@ -673,8 +677,6 @@ void WebServer::on_select_update(select::Select *obj, const std::string &state) } void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (auto *obj : App.get_selects()) { - if (obj->is_internal()) - continue; if (obj->get_object_id() != match.id) continue; @@ -738,6 +740,11 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) { return true; #endif +#ifdef USE_BUTTON + if (request->method() == HTTP_POST && match.domain == "button") + return true; +#endif + #ifdef USE_BINARY_SENSOR if (request->method() == HTTP_GET && match.domain == "binary_sensor") return true; @@ -810,6 +817,13 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) { } #endif +#ifdef USE_BUTTON + if (match.domain == "button") { + this->handle_button_request(request, match); + return; + } +#endif + #ifdef USE_BINARY_SENSOR if (match.domain == "binary_sensor") { this->handle_binary_sensor_request(request, match); diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index cdfec51cf1..8edb4237a2 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -58,6 +58,12 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { */ void set_js_include(const char *js_include); + /** Determine whether internal components should be displayed on the web server. + * Defaults to false. + * + * @param include_internal Whether internal components should be displayed. + */ + void set_include_internal(bool include_internal) { include_internal_ = include_internal; } /** Set whether or not the webserver should expose the OTA form and handler. * * @param allow_ota. @@ -106,6 +112,11 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { std::string switch_json(switch_::Switch *obj, bool value); #endif +#ifdef USE_BUTTON + /// Handle a button request under '/button//press'. + void handle_button_request(AsyncWebServerRequest *request, const UrlMatch &match); +#endif + #ifdef USE_BINARY_SENSOR void on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) override; @@ -188,6 +199,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { const char *css_include_{nullptr}; const char *js_url_{nullptr}; const char *js_include_{nullptr}; + bool include_internal_{false}; bool allow_ota_{true}; }; diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index 7a9319f5e0..a24791b458 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -221,10 +221,22 @@ def _validate(config): raise cv.Invalid("Fast connect can only be used with one network!") if CONF_USE_ADDRESS not in config: + use_address = CORE.name + config[CONF_DOMAIN] if CONF_MANUAL_IP in config: use_address = str(config[CONF_MANUAL_IP][CONF_STATIC_IP]) - else: - use_address = CORE.name + config[CONF_DOMAIN] + elif CONF_NETWORKS in config: + ips = set( + str(net[CONF_MANUAL_IP][CONF_STATIC_IP]) + for net in config[CONF_NETWORKS] + if CONF_MANUAL_IP in net + ) + if len(ips) > 1: + raise cv.Invalid( + "Must specify use_address when using multiple static IP addresses." + ) + if len(ips) == 1: + use_address = next(iter(ips)) + config[CONF_USE_ADDRESS] = use_address return config @@ -334,7 +346,8 @@ async def to_code(config): cg.add(var.set_use_address(config[CONF_USE_ADDRESS])) for network in config.get(CONF_NETWORKS, []): - cg.add(var.add_sta(wifi_network(network, config.get(CONF_MANUAL_IP)))) + ip_config = network.get(CONF_MANUAL_IP, config.get(CONF_MANUAL_IP)) + cg.add(var.add_sta(wifi_network(network, ip_config))) if CONF_AP in config: conf = config[CONF_AP] diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index 1d346c0a8e..5a81fd0a39 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -67,9 +67,9 @@ void event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, voi memset(&event, 0, sizeof(IDFWiFiEvent)); event.event_base = event_base; event.event_id = event_id; - if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) { + if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) { // NOLINT(bugprone-branch-clone) // no data - } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_STOP) { + } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_STOP) { // NOLINT(bugprone-branch-clone) // no data } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_AUTHMODE_CHANGE) { memcpy(&event.data.sta_authmode_change, event_data, sizeof(wifi_event_sta_authmode_change_t)); @@ -79,13 +79,13 @@ void event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, voi memcpy(&event.data.sta_disconnected, event_data, sizeof(wifi_event_sta_disconnected_t)); } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { memcpy(&event.data.ip_got_ip, event_data, sizeof(ip_event_got_ip_t)); - } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_LOST_IP) { + } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_LOST_IP) { // NOLINT(bugprone-branch-clone) // no data } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_SCAN_DONE) { memcpy(&event.data.sta_scan_done, event_data, sizeof(wifi_event_sta_scan_done_t)); - } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_START) { + } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_START) { // NOLINT(bugprone-branch-clone) // no data - } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_STOP) { + } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_STOP) { // NOLINT(bugprone-branch-clone) // no data } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_PROBEREQRECVED) { memcpy(&event.data.ap_probe_req_rx, event_data, sizeof(wifi_event_ap_probe_req_rx_t)); @@ -430,7 +430,7 @@ bool WiFiComponent::wifi_sta_ip_config_(optional manual_ip) { info.netmask.addr = static_cast(manual_ip->subnet); err = tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_STA); - if (err != ESP_OK) { + if (err != ESP_OK && err != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STOPPED) { ESP_LOGV(TAG, "tcpip_adapter_dhcpc_stop failed: %s", esp_err_to_name(err)); return false; } diff --git a/esphome/components/xiaomi_ble/xiaomi_ble.cpp b/esphome/components/xiaomi_ble/xiaomi_ble.cpp index 884969f793..7588198c70 100644 --- a/esphome/components/xiaomi_ble/xiaomi_ble.cpp +++ b/esphome/components/xiaomi_ble/xiaomi_ble.cpp @@ -171,10 +171,8 @@ optional parse_xiaomi_header(const esp32_ble_tracker::Service result.type = XiaomiParseResult::TYPE_MUE4094RT; result.name = "MUE4094RT"; result.raw_offset -= 6; - } else if ((raw[2] == 0x47) && (raw[3] == 0x03)) { // ClearGrass-branded, round body, e-ink display - result.type = XiaomiParseResult::TYPE_CGG1; - result.name = "CGG1"; - } else if ((raw[2] == 0x48) && (raw[3] == 0x0B)) { // Qingping-branded, round body, e-ink display — with bindkeys + } else if ((raw[2] == 0x47 && raw[3] == 0x03) || // ClearGrass-branded, round body, e-ink display + (raw[2] == 0x48 && raw[3] == 0x0B)) { // Qingping-branded, round body, e-ink display — with bindkeys result.type = XiaomiParseResult::TYPE_CGG1; result.name = "CGG1"; } else if ((raw[2] == 0xbc) && (raw[3] == 0x03)) { // VegTrug Grow Care Garden diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 2bb45487fa..8df74ba861 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -296,9 +296,11 @@ def icon(value): value = string_strict(value) if not value: return value - if value.startswith("mdi:"): + if re.match("^[\\w\\-]+:[\\w\\-]+$", value): return value - raise Invalid('Icons should start with prefix "mdi:"') + raise Invalid( + 'Icons must match the format "[icon pack]:[icon]", e.g. "mdi:home-assistant"' + ) def boolean(value): diff --git a/esphome/const.py b/esphome/const.py index c2df6ea922..925e198fd7 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.11.4" +__version__ = "2021.12.0b1" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" @@ -8,31 +8,10 @@ PLATFORM_ESP32 = "esp32" PLATFORM_ESP8266 = "esp8266" TARGET_PLATFORMS = [PLATFORM_ESP32, PLATFORM_ESP8266] -TARGET_FRAMEWORKS = ["arduino", "esp-idf"] -# See also https://github.com/platformio/platform-espressif8266/releases -ARDUINO_VERSION_ESP8266 = { - "dev": "https://github.com/platformio/platform-espressif8266.git", - "3.0.1": "platformio/espressif8266@3.1.0", - "3.0.0": "platformio/espressif8266@3.0.0", - "2.7.4": "platformio/espressif8266@2.6.2", - "2.7.3": "platformio/espressif8266@2.6.1", - "2.7.2": "platformio/espressif8266@2.6.0", - "2.7.1": "platformio/espressif8266@2.5.3", - "2.7.0": "platformio/espressif8266@2.5.0", - "2.6.3": "platformio/espressif8266@2.4.0", - "2.6.2": "platformio/espressif8266@2.3.1", - "2.6.1": "platformio/espressif8266@2.3.0", - "2.5.2": "platformio/espressif8266@2.2.3", - "2.5.1": "platformio/espressif8266@2.1.1", - "2.5.0": "platformio/espressif8266@2.0.4", - "2.4.2": "platformio/espressif8266@1.8.0", - "2.4.1": "platformio/espressif8266@1.7.3", - "2.4.0": "platformio/espressif8266@1.6.0", - "2.3.0": "platformio/espressif8266@1.5.0", -} SOURCE_FILE_EXTENSIONS = {".cpp", ".hpp", ".h", ".c", ".tcc", ".ino"} HEADER_FILE_EXTENSIONS = {".h", ".hpp", ".tcc"} +SECRETS_FILES = {"secrets.yaml", "secrets.yml"} CONF_ABOVE = "above" @@ -189,6 +168,7 @@ CONF_DISABLED_BY_DEFAULT = "disabled_by_default" CONF_DISCOVERY = "discovery" CONF_DISCOVERY_PREFIX = "discovery_prefix" CONF_DISCOVERY_RETAIN = "discovery_retain" +CONF_DISCOVERY_UNIQUE_ID_GENERATOR = "discovery_unique_id_generator" CONF_DISTANCE = "distance" CONF_DITHER = "dither" CONF_DIV_RATIO = "div_ratio" @@ -306,6 +286,7 @@ CONF_ILLUMINANCE = "illuminance" CONF_IMPEDANCE = "impedance" CONF_IMPORT_ACTIVE_ENERGY = "import_active_energy" CONF_IMPORT_REACTIVE_ENERGY = "import_reactive_energy" +CONF_INCLUDE_INTERNAL = "include_internal" CONF_INCLUDES = "includes" CONF_INDEX = "index" CONF_INDOOR = "indoor" @@ -598,6 +579,7 @@ CONF_SEND_EVERY = "send_every" CONF_SEND_FIRST_AT = "send_first_at" CONF_SENSING_PIN = "sensing_pin" CONF_SENSOR = "sensor" +CONF_SENSOR_DATAPOINT = "sensor_datapoint" CONF_SENSOR_ID = "sensor_id" CONF_SENSORS = "sensors" CONF_SEQUENCE = "sequence" @@ -883,7 +865,6 @@ DEVICE_CLASS_SAFETY = "safety" DEVICE_CLASS_SMOKE = "smoke" DEVICE_CLASS_SOUND = "sound" DEVICE_CLASS_TAMPER = "tamper" -DEVICE_CLASS_UPDATE = "update" DEVICE_CLASS_VIBRATION = "vibration" DEVICE_CLASS_WINDOW = "window" # device classes of both binary_sensor and sensor component @@ -915,6 +896,11 @@ DEVICE_CLASS_TEMPERATURE = "temperature" DEVICE_CLASS_TIMESTAMP = "timestamp" DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS = "volatile_organic_compounds" DEVICE_CLASS_VOLTAGE = "voltage" +# device classes of both binary_sensor and button component +DEVICE_CLASS_UPDATE = "update" +# device classes of button component +DEVICE_CLASS_RESTART = "restart" + # state classes STATE_CLASS_NONE = "" diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index f67fc826cf..1bef99e868 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -94,7 +94,7 @@ void Application::loop() { } this->last_loop_ = now; - if (this->dump_config_at_ >= 0 && this->dump_config_at_ < this->components_.size()) { + if (this->dump_config_at_ < this->components_.size()) { if (this->dump_config_at_ == 0) { ESP_LOGI(TAG, "ESPHome version " ESPHOME_VERSION " compiled on %s", this->compilation_time_.c_str()); #ifdef ESPHOME_PROJECT_NAME diff --git a/esphome/core/application.h b/esphome/core/application.h index 5c1483d301..2a20793c19 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -5,6 +5,7 @@ #include "esphome/core/defines.h" #include "esphome/core/preferences.h" #include "esphome/core/component.h" +#include "esphome/core/hal.h" #include "esphome/core/helpers.h" #include "esphome/core/scheduler.h" @@ -17,6 +18,9 @@ #ifdef USE_SWITCH #include "esphome/components/switch/switch.h" #endif +#ifdef USE_BUTTON +#include "esphome/components/button/button.h" +#endif #ifdef USE_TEXT_SENSOR #include "esphome/components/text_sensor/text_sensor.h" #endif @@ -44,6 +48,7 @@ namespace esphome { class Application { public: void pre_setup(const std::string &name, const char *compilation_time, bool name_add_mac_suffix) { + arch_init(); this->name_add_mac_suffix_ = name_add_mac_suffix; if (name_add_mac_suffix) { this->name_ = name + "-" + get_mac_address().substr(6); @@ -67,6 +72,10 @@ class Application { void register_switch(switch_::Switch *a_switch) { this->switches_.push_back(a_switch); } #endif +#ifdef USE_BUTTON + void register_button(button::Button *button) { this->buttons_.push_back(button); } +#endif + #ifdef USE_TEXT_SENSOR void register_text_sensor(text_sensor::TextSensor *sensor) { this->text_sensors_.push_back(sensor); } #endif @@ -167,6 +176,15 @@ class Application { return nullptr; } #endif +#ifdef USE_BUTTON + const std::vector &get_buttons() { return this->buttons_; } + button::Button *get_button_by_key(uint32_t key, bool include_internal = false) { + for (auto *obj : this->buttons_) + if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) + return obj; + return nullptr; + } +#endif #ifdef USE_SENSOR const std::vector &get_sensors() { return this->sensors_; } sensor::Sensor *get_sensor_by_key(uint32_t key, bool include_internal = false) { @@ -260,6 +278,9 @@ class Application { #ifdef USE_SWITCH std::vector switches_{}; #endif +#ifdef USE_BUTTON + std::vector buttons_{}; +#endif #ifdef USE_SENSOR std::vector sensors_{}; #endif @@ -290,7 +311,7 @@ class Application { bool name_add_mac_suffix_; uint32_t last_loop_{0}; uint32_t loop_interval_{16}; - int dump_config_at_{-1}; + size_t dump_config_at_{SIZE_MAX}; uint32_t app_state_{0}; }; diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index 5692194a91..591c9943b5 100644 --- a/esphome/core/component.cpp +++ b/esphome/core/component.cpp @@ -55,6 +55,15 @@ bool Component::cancel_interval(const std::string &name) { // NOLINT return App.scheduler.cancel_interval(this, name); } +void Component::set_retry(const std::string &name, uint32_t initial_wait_time, uint8_t max_attempts, + std::function &&f, float backoff_increase_factor) { // NOLINT + App.scheduler.set_retry(this, name, initial_wait_time, max_attempts, std::move(f), backoff_increase_factor); +} + +bool Component::cancel_retry(const std::string &name) { // NOLINT + return App.scheduler.cancel_retry(this, name); +} + void Component::set_timeout(const std::string &name, uint32_t timeout, std::function &&f) { // NOLINT return App.scheduler.set_timeout(this, name, timeout, std::move(f)); } @@ -87,7 +96,7 @@ void Component::call() { // State loop: Call loop this->call_loop(); break; - case COMPONENT_STATE_FAILED: + case COMPONENT_STATE_FAILED: // NOLINT(bugprone-branch-clone) // State failed: Do nothing break; default: @@ -120,6 +129,10 @@ void Component::set_timeout(uint32_t timeout, std::function &&f) { // N void Component::set_interval(uint32_t interval, std::function &&f) { // NOLINT App.scheduler.set_interval(this, "", interval, std::move(f)); } +void Component::set_retry(uint32_t initial_wait_time, uint8_t max_attempts, std::function &&f, + float backoff_increase_factor) { // NOLINT + App.scheduler.set_retry(this, "", initial_wait_time, max_attempts, std::move(f), backoff_increase_factor); +} bool Component::is_failed() { return (this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_FAILED; } bool Component::can_proceed() { return true; } bool Component::status_has_warning() { return this->component_state_ & STATUS_LED_WARNING; } diff --git a/esphome/core/component.h b/esphome/core/component.h index a1afc17c2c..c3a4ac3782 100644 --- a/esphome/core/component.h +++ b/esphome/core/component.h @@ -61,6 +61,8 @@ extern const uint32_t STATUS_LED_OK; extern const uint32_t STATUS_LED_WARNING; extern const uint32_t STATUS_LED_ERROR; +enum RetryResult { DONE, RETRY }; + class Component { public: /** Where the component's initialization should happen. @@ -180,7 +182,35 @@ class Component { */ bool cancel_interval(const std::string &name); // NOLINT - void set_timeout(uint32_t timeout, std::function &&f); // NOLINT + /** Set an retry function with a unique name. Empty name means no cancelling possible. + * + * This will call f. If f returns RetryResult::RETRY f is called again after initial_wait_time ms. + * f should return RetryResult::DONE if no repeat is required. The initial wait time will be increased + * by backoff_increase_factor for each iteration. Default is doubling the time between iterations + * Can be cancelled via cancel_retry(). + * + * IMPORTANT: Do not rely on this having correct timing. This is only called from + * loop() and therefore can be significantly delayed. + * + * @param name The identifier for this retry function. + * @param initial_wait_time The time in ms before f is called again + * @param max_attempts The maximum number of retries + * @param f The function (or lambda) that should be called + * @param backoff_increase_factor time between retries is increased by this factor on every retry + * @see cancel_retry() + */ + void set_retry(const std::string &name, uint32_t initial_wait_time, uint8_t max_attempts, // NOLINT + std::function &&f, float backoff_increase_factor = 1.0f); // NOLINT + + void set_retry(uint32_t initial_wait_time, uint8_t max_attempts, std::function &&f, // NOLINT + float backoff_increase_factor = 1.0f); // NOLINT + + /** Cancel a retry function. + * + * @param name The identifier for this retry function. + * @return Whether a retry function was deleted. + */ + bool cancel_retry(const std::string &name); // NOLINT /** Set a timeout function with a unique name. * @@ -198,6 +228,8 @@ class Component { */ void set_timeout(const std::string &name, uint32_t timeout, std::function &&f); // NOLINT + void set_timeout(uint32_t timeout, std::function &&f); // NOLINT + /** Cancel a timeout function. * * @param name The identifier for this timeout function. diff --git a/esphome/core/controller.cpp b/esphome/core/controller.cpp index 1d25be41f2..6d3a76a292 100644 --- a/esphome/core/controller.cpp +++ b/esphome/core/controller.cpp @@ -4,64 +4,64 @@ namespace esphome { -void Controller::setup_controller() { +void Controller::setup_controller(bool include_internal) { #ifdef USE_BINARY_SENSOR for (auto *obj : App.get_binary_sensors()) { - if (!obj->is_internal()) + if (include_internal || !obj->is_internal()) obj->add_on_state_callback([this, obj](bool state) { this->on_binary_sensor_update(obj, state); }); } #endif #ifdef USE_FAN for (auto *obj : App.get_fans()) { - if (!obj->is_internal()) + if (include_internal || !obj->is_internal()) obj->add_on_state_callback([this, obj]() { this->on_fan_update(obj); }); } #endif #ifdef USE_LIGHT for (auto *obj : App.get_lights()) { - if (!obj->is_internal()) + if (include_internal || !obj->is_internal()) obj->add_new_remote_values_callback([this, obj]() { this->on_light_update(obj); }); } #endif #ifdef USE_SENSOR for (auto *obj : App.get_sensors()) { - if (!obj->is_internal()) + if (include_internal || !obj->is_internal()) obj->add_on_state_callback([this, obj](float state) { this->on_sensor_update(obj, state); }); } #endif #ifdef USE_SWITCH for (auto *obj : App.get_switches()) { - if (!obj->is_internal()) + if (include_internal || !obj->is_internal()) obj->add_on_state_callback([this, obj](bool state) { this->on_switch_update(obj, state); }); } #endif #ifdef USE_COVER for (auto *obj : App.get_covers()) { - if (!obj->is_internal()) + if (include_internal || !obj->is_internal()) obj->add_on_state_callback([this, obj]() { this->on_cover_update(obj); }); } #endif #ifdef USE_TEXT_SENSOR for (auto *obj : App.get_text_sensors()) { - if (!obj->is_internal()) + if (include_internal || !obj->is_internal()) obj->add_on_state_callback([this, obj](const std::string &state) { this->on_text_sensor_update(obj, state); }); } #endif #ifdef USE_CLIMATE for (auto *obj : App.get_climates()) { - if (!obj->is_internal()) + if (include_internal || !obj->is_internal()) obj->add_on_state_callback([this, obj]() { this->on_climate_update(obj); }); } #endif #ifdef USE_NUMBER for (auto *obj : App.get_numbers()) { - if (!obj->is_internal()) + if (include_internal || !obj->is_internal()) obj->add_on_state_callback([this, obj](float state) { this->on_number_update(obj, state); }); } #endif #ifdef USE_SELECT for (auto *obj : App.get_selects()) { - if (!obj->is_internal()) + if (include_internal || !obj->is_internal()) obj->add_on_state_callback([this, obj](const std::string &state) { this->on_select_update(obj, state); }); } #endif diff --git a/esphome/core/controller.h b/esphome/core/controller.h index 0de8f7ea19..0c3722855c 100644 --- a/esphome/core/controller.h +++ b/esphome/core/controller.h @@ -22,6 +22,9 @@ #ifdef USE_SWITCH #include "esphome/components/switch/switch.h" #endif +#ifdef USE_BUTTON +#include "esphome/components/button/button.h" +#endif #ifdef USE_CLIMATE #include "esphome/components/climate/climate.h" #endif @@ -36,7 +39,7 @@ namespace esphome { class Controller { public: - void setup_controller(); + void setup_controller(bool include_internal = false); #ifdef USE_BINARY_SENSOR virtual void on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state){}; #endif diff --git a/esphome/core/defines.h b/esphome/core/defines.h index e679fe1cef..a74755f651 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -16,10 +16,10 @@ #define USE_API_NOISE #define USE_API_PLAINTEXT #define USE_BINARY_SENSOR +#define USE_BUTTON #define USE_CLIMATE #define USE_COVER #define USE_DEEP_SLEEP -#define USE_ESP8266_PREFERENCES_FLASH #define USE_FAN #define USE_GRAPH #define USE_HOMEASSISTANT_TIME @@ -30,7 +30,6 @@ #define USE_OTA_PASSWORD #define USE_OTA_STATE_CALLBACK #define USE_POWER_SUPPLY -#define USE_PROMETHEUS #define USE_SELECT #define USE_SENSOR #define USE_STATUS_LED @@ -38,18 +37,18 @@ #define USE_TEXT_SENSOR #define USE_TIME #define USE_UART_DEBUGGER -#define USE_WEBSERVER #define USE_WIFI -#define WEBSERVER_PORT 80 // NOLINT - // Arduino-specific feature flags #ifdef USE_ARDUINO #define USE_CAPTIVE_PORTAL #define USE_JSON #define USE_NEXTION_TFT_UPLOAD #define USE_MQTT +#define USE_PROMETHEUS +#define USE_WEBSERVER #define USE_WIFI_WPA2_EAP +#define WEBSERVER_PORT 80 // NOLINT #endif // ESP32-specific feature flags @@ -68,6 +67,7 @@ // ESP8266-specific feature flags #ifdef USE_ESP8266 #define USE_ADC_SENSOR_VCC +#define USE_ESP8266_PREFERENCES_FLASH #define USE_HTTP_REQUEST_ESP8266_HTTPS #define USE_SOCKET_IMPL_LWIP_TCP #endif diff --git a/esphome/core/hal.h b/esphome/core/hal.h index a86dbf2534..034f9d692f 100644 --- a/esphome/core/hal.h +++ b/esphome/core/hal.h @@ -39,6 +39,7 @@ uint32_t micros(); void delay(uint32_t ms); void delayMicroseconds(uint32_t us); // NOLINT(readability-identifier-naming) void __attribute__((noreturn)) arch_restart(); +void arch_init(); void arch_feed_wdt(); uint32_t arch_get_cpu_cycle_count(); uint32_t arch_get_cpu_freq_hz(); diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index cfc1c74145..6678eddbff 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -6,11 +6,10 @@ #include #if defined(USE_ESP8266) -#ifdef USE_WIFI -#include -#endif -#include #include +#include +// for xt_rsil()/xt_wsr_ps() +#include #elif defined(USE_ESP32_FRAMEWORK_ARDUINO) #include #elif defined(USE_ESP_IDF) @@ -31,8 +30,8 @@ namespace esphome { static const char *const TAG = "helpers"; void get_mac_address_raw(uint8_t *mac) { -#ifdef USE_ESP32 -#ifdef USE_ESP32_IGNORE_EFUSE_MAC_CRC +#if defined(USE_ESP32) +#if defined(USE_ESP32_IGNORE_EFUSE_MAC_CRC) // On some devices, the MAC address that is burnt into EFuse does not // match the CRC that goes along with it. For those devices, this // work-around reads and uses the MAC address as-is from EFuse, @@ -41,30 +40,21 @@ void get_mac_address_raw(uint8_t *mac) { #else esp_efuse_mac_get_default(mac); #endif -#endif -#if (defined USE_ESP8266 && defined USE_WIFI) - WiFi.macAddress(mac); +#elif defined(USE_ESP8266) + wifi_get_macaddr(STATION_IF, mac); #endif } std::string get_mac_address() { - char tmp[20]; uint8_t mac[6]; get_mac_address_raw(mac); -#ifdef USE_WIFI - sprintf(tmp, "%02x%02x%02x%02x%02x%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); -#else - return ""; -#endif - return std::string(tmp); + return str_snprintf("%02x%02x%02x%02x%02x%02x", 12, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); } std::string get_mac_address_pretty() { - char tmp[20]; uint8_t mac[6]; get_mac_address_raw(mac); - sprintf(tmp, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); - return std::string(tmp); + return str_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); } #ifdef USE_ESP32 @@ -108,7 +98,7 @@ uint16_t fast_random_16() { return (rand32 & 0xFFFF) + (rand32 >> 16); } uint8_t fast_random_8() { - uint8_t rand32 = fast_random_32(); + uint32_t rand32 = fast_random_32(); return (rand32 & 0xFF) + ((rand32 >> 8) & 0xFF); } @@ -335,6 +325,20 @@ bool str_startswith(const std::string &full, const std::string &start) { return bool str_endswith(const std::string &full, const std::string &ending) { return full.rfind(ending) == (full.size() - ending.size()); } +std::string str_snprintf(const char *fmt, size_t length, ...) { + std::string str; + va_list args; + + str.resize(length); + va_start(args, length); + size_t out_length = vsnprintf(&str[0], length + 1, fmt, args); + va_end(args); + + if (out_length < length) + str.resize(out_length); + + return str; +} std::string str_sprintf(const char *fmt, ...) { std::string str; va_list args; diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 7718c5f1b2..63aa4123ae 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -25,14 +25,13 @@ namespace esphome { -/// Read the raw MAC address into the provided byte array (6 bytes). +/// Get the device MAC address as raw bytes, written into the provided byte array (6 bytes). void get_mac_address_raw(uint8_t *mac); -/// Get the MAC address as a string, using lower case hex notation. -/// This can be used as way to identify this ESP. +/// Get the device MAC address as a string, in lowercase hex notation. std::string get_mac_address(); -/// Get the MAC address as a string, using colon-separated upper case hex notation. +/// Get the device MAC address as a string, in colon-separated uppercase hex notation. std::string get_mac_address_pretty(); #ifdef USE_ESP32 @@ -58,7 +57,10 @@ bool str_equals_case_insensitive(const std::string &a, const std::string &b); bool str_startswith(const std::string &full, const std::string &start); bool str_endswith(const std::string &full, const std::string &ending); -/// sprintf-like function returning std::string instead of writing to char array. +/// snprintf-like function returning std::string with a given maximum length. +std::string __attribute__((format(printf, 1, 3))) str_snprintf(const char *fmt, size_t length, ...); + +/// sprintf-like function returning std::string. std::string __attribute__((format(printf, 1, 2))) str_sprintf(const char *fmt, ...); class HighFrequencyLoopRequester { diff --git a/esphome/core/scheduler.cpp b/esphome/core/scheduler.cpp index a6d3e0307e..3fe07f94b5 100644 --- a/esphome/core/scheduler.cpp +++ b/esphome/core/scheduler.cpp @@ -32,7 +32,7 @@ void HOT Scheduler::set_timeout(Component *component, const std::string &name, u item->timeout = timeout; item->last_execution = now; item->last_execution_major = this->millis_major_; - item->f = std::move(func); + item->void_callback = std::move(func); item->remove = false; this->push_(std::move(item)); } @@ -65,13 +65,47 @@ void HOT Scheduler::set_interval(Component *component, const std::string &name, item->last_execution_major = this->millis_major_; if (item->last_execution > now) item->last_execution_major--; - item->f = std::move(func); + item->void_callback = std::move(func); item->remove = false; this->push_(std::move(item)); } bool HOT Scheduler::cancel_interval(Component *component, const std::string &name) { return this->cancel_item_(component, name, SchedulerItem::INTERVAL); } + +void HOT Scheduler::set_retry(Component *component, const std::string &name, uint32_t initial_wait_time, + uint8_t max_attempts, std::function &&func, + float backoff_increase_factor) { + const uint32_t now = this->millis_(); + + if (!name.empty()) + this->cancel_retry(component, name); + + if (initial_wait_time == SCHEDULER_DONT_RUN) + return; + + ESP_LOGVV(TAG, "set_retry(name='%s', initial_wait_time=%u,max_attempts=%u, backoff_factor=%0.1f)", name.c_str(), + initial_wait_time, max_attempts, backoff_increase_factor); + + auto item = make_unique(); + item->component = component; + item->name = name; + item->type = SchedulerItem::RETRY; + item->interval = initial_wait_time; + item->retry_countdown = max_attempts; + item->backoff_multiplier = backoff_increase_factor; + item->last_execution = now - initial_wait_time; + item->last_execution_major = this->millis_major_; + if (item->last_execution > now) + item->last_execution_major--; + item->retry_callback = std::move(func); + item->remove = false; + this->push_(std::move(item)); +} +bool HOT Scheduler::cancel_retry(Component *component, const std::string &name) { + return this->cancel_item_(component, name, SchedulerItem::RETRY); +} + optional HOT Scheduler::next_schedule_in() { if (this->empty_()) return {}; @@ -95,10 +129,9 @@ void IRAM_ATTR HOT Scheduler::call() { ESP_LOGVV(TAG, "Items: count=%u, now=%u", this->items_.size(), now); while (!this->empty_()) { auto item = std::move(this->items_[0]); - const char *type = item->type == SchedulerItem::INTERVAL ? "interval" : "timeout"; - ESP_LOGVV(TAG, " %s '%s' interval=%u last_execution=%u (%u) next=%u (%u)", type, item->name.c_str(), - item->interval, item->last_execution, item->last_execution_major, item->next_execution(), - item->next_execution_major()); + ESP_LOGVV(TAG, " %s '%s' interval=%u last_execution=%u (%u) next=%u (%u)", item->get_type_str(), + item->name.c_str(), item->interval, item->last_execution, item->last_execution_major, + item->next_execution(), item->next_execution_major()); this->pop_raw_(); old_items.push_back(std::move(item)); @@ -129,6 +162,7 @@ void IRAM_ATTR HOT Scheduler::call() { } while (!this->empty_()) { + RetryResult retry_result = RETRY; // use scoping to indicate visibility of `item` variable { // Don't copy-by value yet @@ -147,17 +181,19 @@ void IRAM_ATTR HOT Scheduler::call() { } #ifdef ESPHOME_LOG_HAS_VERY_VERBOSE - const char *type = item->type == SchedulerItem::INTERVAL ? "interval" : "timeout"; - ESP_LOGVV(TAG, "Running %s '%s' with interval=%u last_execution=%u (now=%u)", type, item->name.c_str(), - item->interval, item->last_execution, now); + ESP_LOGVV(TAG, "Running %s '%s' with interval=%u last_execution=%u (now=%u)", item->get_type_str(), + item->name.c_str(), item->interval, item->last_execution, now); #endif - // Warning: During f(), a lot of stuff can happen, including: + // Warning: During callback(), a lot of stuff can happen, including: // - timeouts/intervals get added, potentially invalidating vector pointers // - timeouts/intervals get cancelled { WarnIfComponentBlockingGuard guard{item->component}; - item->f(); + if (item->type == SchedulerItem::RETRY) + retry_result = item->retry_callback(); + else + item->void_callback(); } } @@ -175,13 +211,16 @@ void IRAM_ATTR HOT Scheduler::call() { continue; } - if (item->type == SchedulerItem::INTERVAL) { + if (item->type == SchedulerItem::INTERVAL || + (item->type == SchedulerItem::RETRY && (--item->retry_countdown > 0 && retry_result != RetryResult::DONE))) { if (item->interval != 0) { const uint32_t before = item->last_execution; const uint32_t amount = (now - item->last_execution) / item->interval; item->last_execution += amount * item->interval; if (item->last_execution < before) item->last_execution_major++; + if (item->type == SchedulerItem::RETRY) + item->interval *= item->backoff_multiplier; } this->push_(std::move(item)); } diff --git a/esphome/core/scheduler.h b/esphome/core/scheduler.h index d1839cb4a7..dc96d58329 100644 --- a/esphome/core/scheduler.h +++ b/esphome/core/scheduler.h @@ -15,6 +15,10 @@ class Scheduler { void set_interval(Component *component, const std::string &name, uint32_t interval, std::function &&func); bool cancel_interval(Component *component, const std::string &name); + void set_retry(Component *component, const std::string &name, uint32_t initial_wait_time, uint8_t max_attempts, + std::function &&func, float backoff_increase_factor = 1.0f); + bool cancel_retry(Component *component, const std::string &name); + optional next_schedule_in(); void call(); @@ -25,13 +29,20 @@ class Scheduler { struct SchedulerItem { Component *component; std::string name; - enum Type { TIMEOUT, INTERVAL } type; + enum Type { TIMEOUT, INTERVAL, RETRY } type; union { uint32_t interval; uint32_t timeout; }; uint32_t last_execution; - std::function f; + // Ideally this should be a union or std::variant + // but unions don't work with object like std::function + // union CallBack_{ + std::function void_callback; + std::function retry_callback; + // }; + uint8_t retry_countdown{3}; + float backoff_multiplier{1.0f}; bool remove; uint8_t last_execution_major; @@ -45,6 +56,18 @@ class Scheduler { } static bool cmp(const std::unique_ptr &a, const std::unique_ptr &b); + const char *get_type_str() { + switch (this->type) { + case SchedulerItem::INTERVAL: + return "interval"; + case SchedulerItem::RETRY: + return "retry"; + case SchedulerItem::TIMEOUT: + return "timeout"; + default: + return ""; + } + } }; uint32_t millis_(); diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index 11571ec889..c98047d9e5 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -970,16 +970,17 @@ def start_web_server(args): server.add_socket(socket) else: _LOGGER.info( - "Starting dashboard web server on http://0.0.0.0:%s and configuration dir %s...", + "Starting dashboard web server on http://%s:%s and configuration dir %s...", + args.address, args.port, settings.config_dir, ) - app.listen(args.port) + app.listen(args.port, args.address) if args.open_ui: import webbrowser - webbrowser.open(f"localhost:{args.port}") + webbrowser.open(f"http://{args.address}:{args.port}") if settings.status_use_ping: status_thread = PingStatusThread() diff --git a/esphome/git.py b/esphome/git.py index 25d893b2f5..64c8d6a6b7 100644 --- a/esphome/git.py +++ b/esphome/git.py @@ -2,6 +2,7 @@ from pathlib import Path import subprocess import hashlib import logging +import urllib.parse from datetime import datetime @@ -36,9 +37,21 @@ def _compute_destination_path(key: str, domain: str) -> Path: def clone_or_update( - *, url: str, ref: str = None, refresh: TimePeriodSeconds, domain: str + *, + url: str, + ref: str = None, + refresh: TimePeriodSeconds, + domain: str, + username: str = None, + password: str = None, ) -> Path: key = f"{url}@{ref}" + + if username is not None and password is not None: + url = url.replace( + "://", f"://{urllib.parse.quote(username)}:{urllib.parse.quote(password)}@" + ) + repo_dir = _compute_destination_path(key, domain) fetch_pr_branch = ref is not None and ref.startswith("pull/") if not repo_dir.is_dir(): diff --git a/esphome/util.py b/esphome/util.py index 937635fa43..b2ba0c22c3 100644 --- a/esphome/util.py +++ b/esphome/util.py @@ -219,24 +219,23 @@ def run_external_process(*cmd, **kwargs): capture_stdout = kwargs.get("capture_stdout", False) if capture_stdout: - sub_stdout = io.BytesIO() + sub_stdout = subprocess.PIPE else: sub_stdout = RedirectText(sys.stdout, filter_lines=filter_lines) sub_stderr = RedirectText(sys.stderr, filter_lines=filter_lines) try: - return subprocess.call(cmd, stdout=sub_stdout, stderr=sub_stderr) + proc = subprocess.run( + cmd, stdout=sub_stdout, stderr=sub_stderr, encoding="utf-8", check=False + ) + return proc.stdout if capture_stdout else proc.returncode except KeyboardInterrupt: # pylint: disable=try-except-raise raise except Exception as err: # pylint: disable=broad-except _LOGGER.error("Running command failed: %s", err) _LOGGER.error("Please try running %s locally.", full_cmd) return 1 - finally: - if capture_stdout: - # pylint: disable=lost-exception - return sub_stdout.getvalue() def is_dev_esphome_version(): diff --git a/platformio.ini b/platformio.ini index 5e89afe8e6..3c0b725d65 100644 --- a/platformio.ini +++ b/platformio.ini @@ -11,7 +11,6 @@ include_dir = [runtime] ; This are the flags as set by the runtime. build_flags = - -Wno-unused-variable -Wno-unused-but-set-variable -Wno-sign-compare @@ -19,15 +18,18 @@ build_flags = ; This are the flags for clang-tidy. build_flags = -Wall + -Wextra -Wunreachable-code -Wfor-loop-analysis -Wshadow-field -Wshadow-field-in-constructor + -Wshadow-uncaptured-local [common] lib_deps = esphome/noise-c@0.1.4 ; api makuna/NeoPixelBus@2.6.9 ; neopixelbus + esphome/Improv@1.0.0 ; improv_serial / esp32_improv build_flags = -DESPHOME_LOG_LEVEL=ESPHOME_LOG_LEVEL_VERY_VERBOSE src_filter = @@ -45,11 +47,12 @@ lib_deps = fastled/FastLED@3.3.2 ; fastled_base mikalhart/TinyGPSPlus@1.0.2 ; gps freekode/TM1651@1.0.1 ; tm1651 - seeed-studio/Grove - Laser PM2.5 Sensor HM3301@1.0.3 ; hm3301 glmnet/Dsmr@0.5 ; dsmr rweather/Crypto@0.2.0 ; dsmr dudanov/MideaUART@1.1.8 ; midea - tonia/HeatpumpIR@1.0.15 ; heatpumpir + ; PIO isn't update releases correctly, see: + ; https://github.com/ToniA/arduino-heatpumpir/commit/0948c619d86407a4e50e8db2f3c193e0576c86fd + https://github.com/ToniA/arduino-heatpumpir.git#1.0.18 ; heatpumpir build_flags = ${common.build_flags} -DUSE_ARDUINO diff --git a/requirements.txt b/requirements.txt index c4b211283d..6061476802 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,8 +9,8 @@ pyserial==3.5 platformio==5.2.2 # When updating platformio, also update Dockerfile esptool==3.2 click==8.0.3 -esphome-dashboard==20211021.1 -aioesphomeapi==10.2.0 +esphome-dashboard==20211201.0 +aioesphomeapi==10.6.0 zeroconf==0.36.13 # esp-idf requires this, but doesn't bundle it by default diff --git a/requirements_test.txt b/requirements_test.txt index 03879c5d0e..b916e8bb1b 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,7 +1,6 @@ -pylint==2.11.1 +pylint==2.12.1 flake8==4.0.1 -black==21.10b0 -pexpect==4.8.0 +black==21.11b1 pre-commit # Unit tests diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index 7ccdc5a24e..016a0995b9 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -649,7 +649,7 @@ def build_message_type(desc): o += f" {dump[0]} " else: o += "\n" - o += f" char buffer[64];\n" + o += f" __attribute__((unused)) char buffer[64];\n" o += f' out.append("{desc.name} {{\\n");\n' o += indent("\n".join(dump)) + "\n" o += f' out.append("}}");\n' diff --git a/script/ci-custom.py b/script/ci-custom.py index 89550afd3d..52ac4025ca 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -1,16 +1,16 @@ #!/usr/bin/env python3 -from helpers import git_ls_files, filter_changed +from helpers import styled, print_error_for_file, git_ls_files, filter_changed +import argparse import codecs import collections +import colorama import fnmatch +import functools import os.path import re -import subprocess import sys import time -import functools -import argparse sys.path.append(os.path.dirname(__file__)) @@ -20,7 +20,7 @@ def find_all(a_str, sub): # Optimization: If str is not in whole text, then do not try # on each line return - for i, line in enumerate(a_str.splitlines()): + for i, line in enumerate(a_str.split('\n')): column = 0 while True: column = line.find(sub, column) @@ -30,6 +30,8 @@ def find_all(a_str, sub): column += len(sub) +colorama.init() + parser = argparse.ArgumentParser() parser.add_argument( "files", nargs="*", default=[], help="files to be processed (regex on path)" @@ -170,7 +172,7 @@ def lint_re_check(regex, **kwargs): return decorator -def lint_content_find_check(find, **kwargs): +def lint_content_find_check(find, only_first=False, **kwargs): decor = lint_content_check(**kwargs) def decorator(func): @@ -183,6 +185,8 @@ def lint_content_find_check(find, **kwargs): for line, col in find_all(content, find_): err = func(fname) errors.append((line + 1, col + 1, err)) + if only_first: + break return errors return decor(new_func) @@ -232,6 +236,7 @@ def lint_executable_bit(fname): @lint_content_find_check( "\t", + only_first=True, exclude=[ "esphome/dashboard/static/ace.js", "esphome/dashboard/static/ext-searchbox.js", @@ -241,9 +246,9 @@ def lint_tabs(fname): return "File contains tab character. Please convert tabs to spaces." -@lint_content_find_check("\r") +@lint_content_find_check("\r", only_first=True) def lint_newline(fname): - return "File contains windows newline. Please set your editor to unix newline mode." + return "File contains Windows newline. Please set your editor to Unix newline mode." @lint_content_check(exclude=["*.svg"]) @@ -601,6 +606,7 @@ def lint_inclusive_language(fname, match): "esphome/components/switch/switch.h", "esphome/components/text_sensor/text_sensor.h", "esphome/components/climate/climate.h", + "esphome/components/button/button.h", "esphome/core/component.h", "esphome/core/gpio.h", "esphome/core/log.h", @@ -657,10 +663,10 @@ for fname in files: run_checks(LINT_POST_CHECKS, "POST") for f, errs in sorted(errors.items()): - print(f"\033[0;32m************* File \033[1;32m{f}\033[0m") - for lineno, col, msg in errs: - print(f"ERROR {f}:{lineno}:{col} - {msg}") - print() + bold = functools.partial(styled, colorama.Style.BRIGHT) + bold_red = functools.partial(styled, (colorama.Style.BRIGHT, colorama.Fore.RED)) + err_str = (f"{bold(f'{f}:{lineno}:{col}:')} {bold_red('lint:')} {msg}\n" for lineno, col, msg in errs) + print_error_for_file(f, "\n".join(err_str)) if args.print_slowest: lint_times = [] diff --git a/script/clang-format b/script/clang-format index d6588f1ccb..515df4c027 100755 --- a/script/clang-format +++ b/script/clang-format @@ -1,6 +1,9 @@ #!/usr/bin/env python3 +from helpers import print_error_for_file, get_output, git_ls_files, filter_changed import argparse +import click +import colorama import multiprocessing import os import queue @@ -9,11 +12,6 @@ import subprocess import sys import threading -import click - -sys.path.append(os.path.dirname(__file__)) -from helpers import get_output, git_ls_files, filter_changed - def run_format(args, queue, lock, failed_files): """Takes filenames out of queue and runs clang-format on them.""" @@ -29,11 +27,7 @@ def run_format(args, queue, lock, failed_files): proc = subprocess.run(invocation, capture_output=True, encoding='utf-8') if proc.returncode != 0: with lock: - print() - print("\033[0;32m************* File \033[1;32m{}\033[0m".format(path)) - print(proc.stdout) - print(proc.stderr) - print() + print_error_for_file(path, proc.stderr) failed_files.append(path) queue.task_done() @@ -43,6 +37,8 @@ def progress_bar_show(value): def main(): + colorama.init() + parser = argparse.ArgumentParser() parser.add_argument('-j', '--jobs', type=int, default=multiprocessing.cpu_count(), diff --git a/script/clang-tidy b/script/clang-tidy index ad5fdfeb04..7450084634 100755 --- a/script/clang-tidy +++ b/script/clang-tidy @@ -1,7 +1,10 @@ #!/usr/bin/env python3 +from helpers import print_error_for_file, get_output, filter_grep, \ + build_all_include, temp_header_file, git_ls_files, filter_changed, load_idedata, basepath import argparse -import json +import click +import colorama import multiprocessing import os import queue @@ -12,13 +15,6 @@ import sys import tempfile import threading -import click -import pexpect - -sys.path.append(os.path.dirname(__file__)) -from helpers import shlex_quote, get_output, filter_grep, \ - build_all_include, temp_header_file, git_ls_files, filter_changed, load_idedata, basepath - def clang_options(idedata): cmd = [ @@ -87,23 +83,20 @@ def run_tidy(args, options, tmpdir, queue, lock, failed_files): invocation.append(name) if args.quiet: - invocation.append('-quiet') + invocation.append('--quiet') + + if sys.stdout.isatty(): + invocation.append('--use-color') - invocation.append(os.path.abspath(path)) invocation.append(f"--header-filter={os.path.abspath(basepath)}/.*") + invocation.append(os.path.abspath(path)) invocation.append('--') invocation.extend(options) - invocation_s = ' '.join(shlex_quote(x) for x in invocation) - # Use pexpect for a pseudy-TTY with colored output - output, rc = pexpect.run(invocation_s, withexitstatus=True, encoding='utf-8', - timeout=15 * 60) - if rc != 0: + proc = subprocess.run(invocation, capture_output=True, encoding='utf-8') + if proc.returncode != 0: with lock: - print() - print("\033[0;32m************* File \033[1;32m{}\033[0m".format(path)) - print(output) - print() + print_error_for_file(path, proc.stdout) failed_files.append(path) queue.task_done() @@ -119,6 +112,8 @@ def split_list(a, n): def main(): + colorama.init() + parser = argparse.ArgumentParser() parser.add_argument('-j', '--jobs', type=int, default=multiprocessing.cpu_count(), diff --git a/script/helpers.py b/script/helpers.py index 430d8a8e7f..abf970b8a2 100644 --- a/script/helpers.py +++ b/script/helpers.py @@ -1,4 +1,4 @@ -import codecs +import colorama import os.path import re import subprocess @@ -11,13 +11,18 @@ temp_folder = os.path.join(root_path, ".temp") temp_header_file = os.path.join(temp_folder, "all-include.cpp") -def shlex_quote(s): - if not s: - return "''" - if re.search(r"[^\w@%+=:,./-]", s) is None: - return s +def styled(color, msg, reset=True): + prefix = ''.join(color) if isinstance(color, tuple) else color + suffix = colorama.Style.RESET_ALL if reset else '' + return prefix + msg + suffix - return "'" + s.replace("'", "'\"'\"'") + "'" + +def print_error_for_file(file, body): + print(styled(colorama.Fore.GREEN, "### File ") + styled((colorama.Fore.GREEN, colorama.Style.BRIGHT), file)) + print() + if body is not None: + print(body) + print() def build_all_include(): diff --git a/script/lint-python b/script/lint-python index 41885b9672..8ee038a661 100755 --- a/script/lint-python +++ b/script/lint-python @@ -1,15 +1,13 @@ #!/usr/bin/env python3 from __future__ import print_function -from helpers import get_output, get_err, git_ls_files, filter_changed - +from helpers import styled, print_error_for_file, get_output, get_err, git_ls_files, filter_changed import argparse +import colorama import os import re import sys -sys.path.append(os.path.dirname(__file__)) - curfile = None @@ -17,14 +15,18 @@ def print_error(file, lineno, msg): global curfile if curfile != file: - print() - print("\033[0;32m************* File \033[1;32m{}\033[0m".format(file)) + print_error_for_file(file, None) curfile = file - print("{}:{} - {}".format(file, lineno, msg)) + if lineno is not None: + print(f"{styled(colorama.Style.BRIGHT, f'{file}:{lineno}:')} {msg}") + else: + print(f"{styled(colorama.Style.BRIGHT, f'{file}:')} {msg}") def main(): + colorama.init() + parser = argparse.ArgumentParser() parser.add_argument( "files", nargs="*", default=[], help="files to be processed (regex on path)" @@ -56,6 +58,7 @@ def main(): cmd = ["black", "--verbose", "--check"] + files print("Running black...") + print() log = get_err(*cmd) for line in log.splitlines(): WOULD_REFORMAT = "would reformat" @@ -65,7 +68,9 @@ def main(): errors += 1 cmd = ["flake8"] + files + print() print("Running flake8...") + print() log = get_output(*cmd) for line in log.splitlines(): line = line.split(":", 4) @@ -78,7 +83,9 @@ def main(): errors += 1 cmd = ["pylint", "-f", "parseable", "--persistent=n"] + files + print() print("Running pylint...") + print() log = get_output(*cmd) for line in log.splitlines(): line = line.split(":", 3) diff --git a/sdkconfig.defaults b/sdkconfig.defaults index 26db4705b8..72ca3f6e9c 100644 --- a/sdkconfig.defaults +++ b/sdkconfig.defaults @@ -9,6 +9,10 @@ CONFIG_PARTITION_TABLE_CUSTOM=y #CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_SINGLE_APP=n CONFIG_FREERTOS_HZ=1000 +CONFIG_ESP_TASK_WDT=y +CONFIG_ESP_TASK_WDT_PANIC=y +CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0=n +CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1=n # esp32_ble CONFIG_BT_ENABLED=y diff --git a/tests/test1.yaml b/tests/test1.yaml index a7f2e24465..18c6610b08 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -98,6 +98,7 @@ mqtt: discovery: True discovery_retain: False discovery_prefix: discovery + discovery_unique_id_generator: legacy topic_prefix: helloworld log_topic: topic: helloworld/hi @@ -1707,6 +1708,8 @@ climate: min_temperature: 18 max_temperature: 30 - platform: midea + on_state: + logger.log: "State changed!" id: midea_unit uart_id: uart0 name: Midea Climate diff --git a/tests/test2.yaml b/tests/test2.yaml index 3afef9501d..50743dc643 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -458,6 +458,16 @@ text_sensor: name: 'Template Text Sensor' lambda: |- return {"Hello World"}; + filters: + - to_upper: + - to_lower: + - append: "xyz" + - prepend: "abcd" + - substitute: + - Hello -> Goodbye + - map: + - red -> green + - lambda: return {"1234"}; - platform: homeassistant entity_id: sensor.hello_world2 id: ha_hello_world2 diff --git a/tests/test3.yaml b/tests/test3.yaml index cf80c06aa8..50cd6d6cf6 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -227,7 +227,9 @@ spi: uart: - id: uart1 - tx_pin: GPIO1 + tx_pin: + number: GPIO1 + inverted: yes rx_pin: GPIO3 baud_rate: 115200 - id: uart2 @@ -254,6 +256,8 @@ uart: tx_pin: GPIO4 rx_pin: GPIO5 baud_rate: 38400 + # Specifically added for testing debug with no options at all. + debug: modbus: uart_id: uart1 @@ -1308,6 +1312,10 @@ fingerprint_grow: dsmr: decryption_key: 00112233445566778899aabbccddeeff uart_id: uart6 + max_telegram_length: 1000 + request_pin: D5 + request_interval: 20s + receive_timeout: 100ms daly_bms: update_interval: 20s diff --git a/tests/test4.yaml b/tests/test4.yaml index 938145235a..b4708acf65 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -49,6 +49,7 @@ web_server: auth: username: admin password: admin + include_internal: true time: - platform: sntp @@ -519,3 +520,7 @@ xpt2046: id(touchscreen).y_raw, id(touchscreen).z_raw ); + +button: + - platform: restart + name: Restart Button diff --git a/tests/test5.yaml b/tests/test5.yaml index f1fb786fe5..37e65e7da2 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -10,12 +10,16 @@ esp32: framework: type: esp-idf advanced: - ignore_efuse_mac_crc: true + ignore_efuse_mac_crc: true wifi: networks: - ssid: 'MySSID' password: 'password1' + manual_ip: + static_ip: 192.168.1.23 + gateway: 192.168.1.1 + subnet: 255.255.255.0 api: @@ -97,6 +101,8 @@ number: max_value: 100 min_value: 0 step: 5 + unit_of_measurement: '%' + mode: slider select: - platform: template diff --git a/tests/unit_tests/test_config_validation.py b/tests/unit_tests/test_config_validation.py index 16cfb16e94..9e9af52d00 100644 --- a/tests/unit_tests/test_config_validation.py +++ b/tests/unit_tests/test_config_validation.py @@ -66,7 +66,7 @@ def test_string_string__invalid(value): config_validation.string_strict(value) -@given(builds(lambda v: "mdi:" + v, text())) +@given(builds(lambda v: "mdi:" + v, text(alphabet=string.ascii_letters + string.digits + "-_", min_size=1, max_size=20))) @example("") def test_icon__valid(value): actual = config_validation.icon(value) @@ -75,7 +75,7 @@ def test_icon__valid(value): def test_icon__invalid(): - with pytest.raises(Invalid, match="Icons should start with prefix"): + with pytest.raises(Invalid, match="Icons must match the format "): config_validation.icon("foo")