diff --git a/.editorconfig b/.editorconfig index 8ccf1eeebc..9e203f60e4 100644 --- a/.editorconfig +++ b/.editorconfig @@ -25,10 +25,9 @@ indent_size = 2 [*.{yaml,yml}] indent_style = space indent_size = 2 -quote_type = single +quote_type = double # JSON [*.json] indent_style = space indent_size = 2 - diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 864586fe6b..a8ca63d158 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,3 +1,4 @@ +--- # These are supported funding model platforms custom: https://www.nabucasa.com diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 7f99701e39..804dad47c7 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,3 +1,4 @@ +--- blank_issues_enabled: false contact_links: - name: Issue Tracker @@ -5,7 +6,10 @@ contact_links: about: Please create bug reports in the dedicated issue tracker. - name: Feature Request Tracker url: https://github.com/esphome/feature-requests - about: Please create feature requests in the dedicated feature request tracker. + about: | + Please create feature requests in the dedicated feature request tracker. - name: Frequently Asked Question url: https://esphome.io/guides/faq.html - about: Please view the FAQ for common questions and what to include in a bug report. + about: | + Please view the FAQ for common questions and what + to include in a bug report. diff --git a/.github/dependabot.yml b/.github/dependabot.yml index c67378093e..666532f360 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,13 +1,14 @@ +--- version: 2 updates: - - package-ecosystem: "pip" + - package-ecosystem: pip directory: "/" schedule: - interval: "daily" + interval: daily ignore: # Hypotehsis is only used for testing and is updated quite often - dependency-name: hypothesis - - package-ecosystem: "github-actions" + - package-ecosystem: github-actions directory: "/" schedule: interval: daily diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml index d424bd3b60..7eb4cd1f66 100644 --- a/.github/workflows/ci-docker.yml +++ b/.github/workflows/ci-docker.yml @@ -1,21 +1,23 @@ +--- name: CI for docker images # Only run when docker paths change +# yamllint disable-line rule:truthy on: push: branches: [dev, beta, release] paths: - - 'docker/**' - - '.github/workflows/**' - - 'requirements*.txt' - - 'platformio.ini' + - "docker/**" + - ".github/workflows/**" + - "requirements*.txt" + - "platformio.ini" pull_request: paths: - - 'docker/**' - - '.github/workflows/**' - - 'requirements*.txt' - - 'platformio.ini' + - "docker/**" + - ".github/workflows/**" + - "requirements*.txt" + - "platformio.ini" permissions: contents: read @@ -30,24 +32,24 @@ jobs: arch: [amd64, armv7, aarch64] build_type: ["ha-addon", "docker", "lint"] steps: - - uses: actions/checkout@v3 - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: '3.9' - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - name: Set up QEMU - uses: docker/setup-qemu-action@v2 + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.9" + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 - - name: Set TAG - run: | - echo "TAG=check" >> $GITHUB_ENV + - name: Set TAG + run: | + echo "TAG=check" >> $GITHUB_ENV - - name: Run build - run: | - docker/build.py \ - --tag "${TAG}" \ - --arch "${{ matrix.arch }}" \ - --build-type "${{ matrix.build_type }}" \ - build + - name: Run build + run: | + docker/build.py \ + --tag "${TAG}" \ + --arch "${{ matrix.arch }}" \ + --build-type "${{ matrix.build_type }}" \ + build diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1705610947..b5b7521b3f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,5 +1,7 @@ +--- name: CI +# yamllint disable-line rule:truthy on: push: branches: [dev, beta, release] @@ -10,6 +12,7 @@ permissions: contents: read concurrency: + # yamllint disable-line rule:line-length group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true @@ -73,6 +76,8 @@ jobs: name: Run script/clang-tidy for ESP32 IDF options: --environment esp32-idf-tidy --grep USE_ESP_IDF pio_cache_key: tidyesp32-idf + - id: yamllint + name: Run yamllint steps: - uses: actions/checkout@v3 @@ -80,17 +85,19 @@ jobs: uses: actions/setup-python@v4 id: python with: - python-version: '3.8' + python-version: "3.8" - name: Cache virtualenv uses: actions/cache@v3 with: path: .venv + # yamllint disable-line rule:line-length key: venv-${{ steps.python.outputs.python-version }}-${{ hashFiles('requirements*.txt') }} restore-keys: | venv-${{ steps.python.outputs.python-version }}- - name: Set up virtualenv + # yamllint disable rule:line-length run: | python -m venv .venv source .venv/bin/activate @@ -99,12 +106,14 @@ jobs: pip install -e . echo "$GITHUB_WORKSPACE/.venv/bin" >> $GITHUB_PATH echo "VIRTUAL_ENV=$GITHUB_WORKSPACE/.venv" >> $GITHUB_ENV + # yamllint enable rule:line-length # Use per check platformio cache because checks use different parts - name: Cache platformio uses: actions/cache@v3 with: path: ~/.platformio + # yamllint disable-line rule:line-length key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }} if: matrix.id == 'test' || matrix.id == 'clang-tidy' @@ -145,8 +154,9 @@ jobs: pytest -vv --tb=native tests if: matrix.id == 'pytest' - # Also run git-diff-index so that the step is marked as failed on formatting errors, - # since clang-format doesn't do anything but change files if -i is passed. + # Also run git-diff-index so that the step is marked as failed on + # formatting errors, since clang-format doesn't do anything but + # change files if -i is passed. - name: Run clang-format run: | script/clang-format -i @@ -161,6 +171,11 @@ jobs: # Also cache libdeps, store them in a ~/.platformio subfolder PLATFORMIO_LIBDEPS_DIR: ~/.platformio/libdeps + - name: Run yamllint + if: matrix.id == 'yamllint' + uses: frenck/action-yamllint@v1.3.0 + - name: Suggested changes run: script/ci-suggest-changes + # yamllint disable-line rule:line-length if: always() && (matrix.id == 'clang-tidy' || matrix.id == 'clang-format' || matrix.id == 'lint-python') diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml index ceb45b2a91..1cf82895f3 100644 --- a/.github/workflows/lock.yml +++ b/.github/workflows/lock.yml @@ -1,8 +1,10 @@ +--- name: Lock +# yamllint disable-line rule:truthy on: schedule: - - cron: '30 0 * * *' + - cron: "30 0 * * *" workflow_dispatch: permissions: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 216c094122..65d170f5d3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,5 +1,7 @@ +--- name: Publish Release +# yamllint disable-line rule:truthy on: workflow_dispatch: release: @@ -20,6 +22,7 @@ jobs: - uses: actions/checkout@v3 - name: Get tag id: tag + # yamllint disable rule:line-length run: | if [[ "$GITHUB_EVENT_NAME" = "release" ]]; then TAG="${GITHUB_REF#refs/tags/}" @@ -29,6 +32,7 @@ jobs: TAG="${TAG}${today}" fi echo "::set-output name=tag::${TAG}" + # yamllint enable rule:line-length deploy-pypi: name: Build and publish to PyPi @@ -39,7 +43,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: '3.x' + python-version: "3.x" - name: Set up python environment run: | script/setup @@ -65,37 +69,37 @@ jobs: arch: [amd64, armv7, aarch64] build_type: ["ha-addon", "docker", "lint"] steps: - - uses: actions/checkout@v3 - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: '3.9' + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.9" - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - name: Set up QEMU - uses: docker/setup-qemu-action@v2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 - - name: Log in to docker hub - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKER_USER }} - password: ${{ secrets.DOCKER_PASSWORD }} - - name: Log in to the GitHub container registry - uses: docker/login-action@v2 - with: + - name: Log in to docker hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_USER }} + password: ${{ secrets.DOCKER_PASSWORD }} + - name: Log in to the GitHub container registry + uses: docker/login-action@v2 + with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Build and push - run: | - docker/build.py \ - --tag "${{ needs.init.outputs.tag }}" \ - --arch "${{ matrix.arch }}" \ - --build-type "${{ matrix.build_type }}" \ - build \ - --push + - name: Build and push + run: | + docker/build.py \ + --tag "${{ needs.init.outputs.tag }}" \ + --arch "${{ matrix.arch }}" \ + --build-type "${{ matrix.build_type }}" \ + build \ + --push deploy-docker-manifest: if: github.repository == 'esphome/esphome' @@ -108,34 +112,34 @@ jobs: matrix: build_type: ["ha-addon", "docker", "lint"] steps: - - uses: actions/checkout@v3 - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: '3.9' - - name: Enable experimental manifest support - run: | - mkdir -p ~/.docker - echo "{\"experimental\": \"enabled\"}" > ~/.docker/config.json + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.9" + - name: Enable experimental manifest support + run: | + mkdir -p ~/.docker + echo "{\"experimental\": \"enabled\"}" > ~/.docker/config.json - - name: Log in to docker hub - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKER_USER }} - password: ${{ secrets.DOCKER_PASSWORD }} - - name: Log in to the GitHub container registry - uses: docker/login-action@v2 - with: + - name: Log in to docker hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_USER }} + password: ${{ secrets.DOCKER_PASSWORD }} + - name: Log in to the GitHub container registry + uses: docker/login-action@v2 + with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Run manifest - run: | - docker/build.py \ - --tag "${{ needs.init.outputs.tag }}" \ - --build-type "${{ matrix.build_type }}" \ - manifest + - name: Run manifest + run: | + docker/build.py \ + --tag "${{ needs.init.outputs.tag }}" \ + --build-type "${{ matrix.build_type }}" \ + manifest deploy-ha-addon-repo: if: github.repository == 'esphome/esphome' && github.event_name == 'release' @@ -144,6 +148,7 @@ jobs: steps: - env: TOKEN: ${{ secrets.DEPLOY_HA_ADDON_REPO_TOKEN }} + # yamllint disable rule:line-length run: | TAG="${GITHUB_REF#refs/tags/}" curl \ @@ -152,3 +157,4 @@ jobs: -H "Accept: application/vnd.github.v3+json" \ https://api.github.com/repos/esphome/home-assistant-addon/actions/workflows/bump-version.yml/dispatches \ -d "{\"ref\":\"main\",\"inputs\":{\"version\":\"$TAG\"}}" + # yamllint enable rule:line-length diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index b998043039..33f7ad041c 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -1,8 +1,10 @@ +--- name: Stale +# yamllint disable-line rule:truthy on: schedule: - - cron: '30 0 * * *' + - cron: "30 0 * * *" workflow_dispatch: permissions: @@ -31,7 +33,8 @@ jobs: and will be closed if no further activity occurs within 7 days. Thank you for your contributions. - # Use stale to automatically close issues with a reference to the issue tracker + # Use stale to automatically close issues with a + # reference to the issue tracker close-issues: runs-on: ubuntu-latest steps: diff --git a/.gitpod.yml b/.gitpod.yml index e3f786a403..be2f11227c 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -1,6 +1,8 @@ +--- ports: -- port: 6052 - onOpen: open-preview + - port: 6052 + onOpen: open-preview tasks: -- before: pyenv local $(pyenv version | grep '^3\.' | cut -d ' ' -f 1) && script/setup - command: python -m esphome dashboard config + # yamllint disable-line rule:line-length + - before: pyenv local $(pyenv version | grep '^3\.' | cut -d ' ' -f 1) && script/setup + command: python -m esphome dashboard config diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 95365ff5bb..083aea117d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,14 +1,15 @@ +--- # See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks repos: - repo: https://github.com/ambv/black rev: 22.6.0 hooks: - - id: black - args: - - --safe - - --quiet - files: ^((esphome|script|tests)/.+)?[^/]+\.py$ + - id: black + args: + - --safe + - --quiet + files: ^((esphome|script|tests)/.+)?[^/]+\.py$ - repo: https://gitlab.com/pycqa/flake8 rev: 4.0.1 hooks: diff --git a/.yamllint b/.yamllint new file mode 100644 index 0000000000..4fea263214 --- /dev/null +++ b/.yamllint @@ -0,0 +1,3 @@ +--- +ignore: | + venv/ diff --git a/CODEOWNERS b/CODEOWNERS index 7f7562dec4..69e30027a9 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -29,10 +29,13 @@ esphome/components/b_parasite/* @rbaron esphome/components/ballu/* @bazuchan esphome/components/bang_bang/* @OttoWinter esphome/components/bedjet/* @jhansche +esphome/components/bedjet/climate/* @jhansche +esphome/components/bedjet/fan/* @jhansche esphome/components/bh1750/* @OttoWinter esphome/components/binary_sensor/* @esphome/core esphome/components/bl0939/* @ziceva esphome/components/bl0940/* @tobias- +esphome/components/bl0942/* @dbuezas esphome/components/ble_client/* @buxtronix esphome/components/bluetooth_proxy/* @jesserockz esphome/components/bme680_bsec/* @trvrnrth @@ -60,6 +63,7 @@ esphome/components/debug/* @OttoWinter esphome/components/delonghi/* @grob6000 esphome/components/dfplayer/* @glmnet esphome/components/dht/* @OttoWinter +esphome/components/dps310/* @kbx81 esphome/components/ds1307/* @badbadc0ffee esphome/components/dsmr/* @glmnet @zuidwijk esphome/components/ektf2232/* @jesserockz @@ -73,6 +77,7 @@ esphome/components/esp32_improv/* @jesserockz esphome/components/esp8266/* @esphome/core esphome/components/exposure_notifications/* @OttoWinter esphome/components/ezo/* @ssieb +esphome/components/factory_reset/* @anatoly-savchenkov esphome/components/fastled_base/* @OttoWinter esphome/components/feedback/* @ianchi esphome/components/fingerprint_grow/* @OnFreund @loongyh @@ -121,6 +126,7 @@ esphome/components/mcp2515/* @danielschramm @mvturnho esphome/components/mcp3204/* @rsumner esphome/components/mcp4728/* @berfenger esphome/components/mcp47a1/* @jesserockz +esphome/components/mcp9600/* @MrEditor97 esphome/components/mcp9808/* @k7hpn esphome/components/md5/* @esphome/core esphome/components/mdns/* @esphome/core @@ -139,6 +145,7 @@ esphome/components/modbus_controller/switch/* @martgras esphome/components/modbus_controller/text_sensor/* @martgras esphome/components/mopeka_ble/* @spbrogan esphome/components/mopeka_pro_check/* @spbrogan +esphome/components/mpl3115a2/* @kbickar esphome/components/mpu6886/* @fabaff esphome/components/network/* @esphome/core esphome/components/nextion/* @senexcrenshaw @@ -221,7 +228,9 @@ esphome/components/teleinfo/* @0hax esphome/components/thermostat/* @kbx81 esphome/components/time/* @OttoWinter esphome/components/tlc5947/* @rnauber +esphome/components/tm1621/* @Philippe12 esphome/components/tm1637/* @glmnet +esphome/components/tm1638/* @skykingjwc esphome/components/tmp102/* @timsavage esphome/components/tmp117/* @Azimath esphome/components/tof10120/* @wstrzalka @@ -236,6 +245,8 @@ esphome/components/tuya/sensor/* @jesserockz esphome/components/tuya/switch/* @jesserockz esphome/components/tuya/text_sensor/* @dentra esphome/components/uart/* @esphome/core +esphome/components/ufire_ec/* @pvizeli +esphome/components/ufire_ise/* @pvizeli esphome/components/ultrasonic/* @OttoWinter esphome/components/version/* @esphome/core esphome/components/wake_on_lan/* @willwill2will54 diff --git a/docker/build.py b/docker/build.py index d5926ae3d4..ae977f87c1 100755 --- a/docker/build.py +++ b/docker/build.py @@ -88,10 +88,12 @@ def main(): sys.exit(1) # detect channel from tag - match = re.match(r'^\d+\.\d+(?:\.\d+)?(b\d+)?$', args.tag) + match = re.match(r"^(\d+\.\d+)(?:\.\d+)?(b\d+)?$", args.tag) + major_minor_version = None if match is None: channel = CHANNEL_DEV - elif match.group(1) is None: + elif match.group(2) is None: + major_minor_version = match.group(1) channel = CHANNEL_RELEASE else: channel = CHANNEL_BETA @@ -106,6 +108,11 @@ def main(): tags_to_push.append("beta") tags_to_push.append("latest") + # Compatibility with HA tags + if major_minor_version: + tags_to_push.append("stable") + tags_to_push.append(major_minor_version) + if args.command == "build": # 1. pull cache image params = DockerParams.for_type_arch(args.build_type, args.arch) diff --git a/esphome/components/ac_dimmer/ac_dimmer.cpp b/esphome/components/ac_dimmer/ac_dimmer.cpp index 1d0cd8d0ab..16101a1c2c 100644 --- a/esphome/components/ac_dimmer/ac_dimmer.cpp +++ b/esphome/components/ac_dimmer/ac_dimmer.cpp @@ -121,11 +121,8 @@ void IRAM_ATTR HOT AcDimmerDataStore::gpio_intr() { // calculate time until enable in µs: (1.0-value)*cycle_time, but with integer arithmetic // also take into account min_power auto min_us = this->cycle_time_us * this->min_power / 1000; - // calculate required value to provide a true RMS voltage output - this->enable_time_us = - std::max((uint32_t) 1, (uint32_t)((65535 - (acos(1 - (2 * this->value / 65535.0)) / 3.14159 * 65535)) * - (this->cycle_time_us - min_us)) / - 65535); + this->enable_time_us = std::max((uint32_t) 1, ((65535 - this->value) * (this->cycle_time_us - min_us)) / 65535); + if (this->method == DIM_METHOD_LEADING_PULSE) { // Minimum pulse time should be enough for the triac to trigger when it is close to the ZC zone // this is for brightness near 99% @@ -206,6 +203,7 @@ void AcDimmer::setup() { #endif } void AcDimmer::write_state(float state) { + state = std::acos(1 - (2 * state)) / 3.14159; // RMS power compensation auto new_value = static_cast(roundf(state * 65535)); if (new_value != 0 && this->store_.value == 0) this->store_.init_cycle = this->init_with_half_cycle_; diff --git a/esphome/components/ade7953/ade7953.h b/esphome/components/ade7953/ade7953.h index bb160cd8eb..418ad1c9e7 100644 --- a/esphome/components/ade7953/ade7953.h +++ b/esphome/components/ade7953/ade7953.h @@ -82,7 +82,7 @@ class ADE7953 : public i2c::I2CDevice, public PollingComponent { return i2c::ERROR_OK; } - InternalGPIOPin *irq_pin_ = nullptr; + InternalGPIOPin *irq_pin_{nullptr}; bool is_setup_{false}; sensor::Sensor *voltage_sensor_{nullptr}; sensor::Sensor *current_a_sensor_{nullptr}; diff --git a/esphome/components/aht10/aht10.h b/esphome/components/aht10/aht10.h index bfb6b07a7a..4d0eaa5919 100644 --- a/esphome/components/aht10/aht10.h +++ b/esphome/components/aht10/aht10.h @@ -18,8 +18,8 @@ class AHT10Component : public PollingComponent, public i2c::I2CDevice { void set_humidity_sensor(sensor::Sensor *humidity_sensor) { humidity_sensor_ = humidity_sensor; } protected: - sensor::Sensor *temperature_sensor_; - sensor::Sensor *humidity_sensor_; + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *humidity_sensor_{nullptr}; }; } // namespace aht10 diff --git a/esphome/components/am2320/am2320.cpp b/esphome/components/am2320/am2320.cpp index 8ab48a348e..ec501f2d36 100644 --- a/esphome/components/am2320/am2320.cpp +++ b/esphome/components/am2320/am2320.cpp @@ -4,33 +4,15 @@ // - Arduino - AM2320: https://github.com/EngDial/AM2320/blob/master/src/AM2320.cpp #include "am2320.h" -#include "esphome/core/log.h" #include "esphome/core/hal.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" namespace esphome { namespace am2320 { static const char *const TAG = "am2320"; -// ---=== Calc CRC16 ===--- -uint16_t crc_16(uint8_t *ptr, uint8_t length) { - uint16_t crc = 0xFFFF; - uint8_t i; - //------------------------------ - while (length--) { - crc ^= *ptr++; - for (i = 0; i < 8; i++) { - if ((crc & 0x01) != 0) { - crc >>= 1; - crc ^= 0xA001; - } else { - crc >>= 1; - } - } - } - return crc; -} - void AM2320Component::update() { uint8_t data[8]; data[0] = 0; @@ -98,7 +80,7 @@ bool AM2320Component::read_data_(uint8_t *data) { checksum = data[7] << 8; checksum += data[6]; - if (crc_16(data, 6) != checksum) { + if (crc16(data, 6) != checksum) { ESP_LOGW(TAG, "AM2320 Checksum invalid!"); return false; } diff --git a/esphome/components/am2320/am2320.h b/esphome/components/am2320/am2320.h index 33e1d30aa0..da1e87cf65 100644 --- a/esphome/components/am2320/am2320.h +++ b/esphome/components/am2320/am2320.h @@ -21,8 +21,8 @@ class AM2320Component : public PollingComponent, public i2c::I2CDevice { bool read_data_(uint8_t *data); bool read_bytes_(uint8_t a_register, uint8_t *data, uint8_t len, uint32_t conversion = 0); - sensor::Sensor *temperature_sensor_; - sensor::Sensor *humidity_sensor_; + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *humidity_sensor_{nullptr}; }; } // namespace am2320 diff --git a/esphome/components/apds9960/__init__.py b/esphome/components/apds9960/__init__.py index 8de83251b7..37dc4c0b28 100644 --- a/esphome/components/apds9960/__init__.py +++ b/esphome/components/apds9960/__init__.py @@ -8,6 +8,27 @@ AUTO_LOAD = ["sensor", "binary_sensor"] MULTI_CONF = True CONF_APDS9960_ID = "apds9960_id" +CONF_LED_DRIVE = "led_drive" +CONF_PROXIMITY_GAIN = "proximity_gain" +CONF_AMBIENT_LIGHT_GAIN = "ambient_light_gain" +CONF_GESTURE_LED_DRIVE = "gesture_led_drive" +CONF_GESTURE_GAIN = "gesture_gain" +CONF_GESTURE_WAIT_TIME = "gesture_wait_time" + +DRIVE_LEVELS = {"100ma": 0, "50ma": 1, "25ma": 2, "12.5ma": 3} +PROXIMITY_LEVELS = {"1x": 0, "2x": 1, "4x": 2, "8x": 3} +AMBIENT_LEVELS = {"1x": 0, "4x": 1, "16x": 2, "64x": 3} +GESTURE_LEVELS = {"1x": 0, "2x": 1, "4x": 2, "8x": 3} +GESTURE_WAIT_TIMES = { + "0ms": 0, + "2.8ms": 1, + "5.6ms": 2, + "8.4ms": 3, + "14ms": 4, + "22.4ms": 5, + "30.8ms": 6, + "39.2ms": 7, +} apds9960_nds = cg.esphome_ns.namespace("apds9960") APDS9960 = apds9960_nds.class_("APDS9960", cg.PollingComponent, i2c.I2CDevice) @@ -16,6 +37,20 @@ CONFIG_SCHEMA = ( cv.Schema( { cv.GenerateID(): cv.declare_id(APDS9960), + cv.Optional(CONF_LED_DRIVE, "100mA"): cv.enum(DRIVE_LEVELS, lower=True), + cv.Optional(CONF_PROXIMITY_GAIN, "4x"): cv.enum( + PROXIMITY_LEVELS, lower=True + ), + cv.Optional(CONF_AMBIENT_LIGHT_GAIN, "4x"): cv.enum( + AMBIENT_LEVELS, lower=True + ), + cv.Optional(CONF_GESTURE_LED_DRIVE, "100mA"): cv.enum( + DRIVE_LEVELS, lower=True + ), + cv.Optional(CONF_GESTURE_GAIN, "4x"): cv.enum(GESTURE_LEVELS, lower=True), + cv.Optional(CONF_GESTURE_WAIT_TIME, "2.8ms"): cv.enum( + GESTURE_WAIT_TIMES, lower=True + ), } ) .extend(cv.polling_component_schema("60s")) @@ -27,3 +62,9 @@ async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) + cg.add(var.set_led_drive(config[CONF_LED_DRIVE])) + cg.add(var.set_proximity_gain(config[CONF_PROXIMITY_GAIN])) + cg.add(var.set_ambient_gain(config[CONF_AMBIENT_LIGHT_GAIN])) + cg.add(var.set_gesture_led_drive(config[CONF_GESTURE_LED_DRIVE])) + cg.add(var.set_gesture_gain(config[CONF_GESTURE_GAIN])) + cg.add(var.set_gesture_wait_time(config[CONF_GESTURE_WAIT_TIME])) diff --git a/esphome/components/apds9960/apds9960.cpp b/esphome/components/apds9960/apds9960.cpp index 5ba3afbacc..05091f3f7d 100644 --- a/esphome/components/apds9960/apds9960.cpp +++ b/esphome/components/apds9960/apds9960.cpp @@ -46,16 +46,16 @@ void APDS9960::setup() { uint8_t val = 0; APDS9960_ERROR_CHECK(this->read_byte(0x8F, &val)); val &= 0b00111111; - uint8_t led_drive = 0; // led drive, 0 -> 100mA, 1 -> 50mA, 2 -> 25mA, 3 -> 12.5mA - val |= (led_drive & 0b11) << 6; + // led drive, 0 -> 100mA, 1 -> 50mA, 2 -> 25mA, 3 -> 12.5mA + val |= (this->led_drive_ & 0b11) << 6; val &= 0b11110011; - uint8_t proximity_gain = 2; // proximity gain, 0 -> 1x, 1 -> 2X, 2 -> 4X, 4 -> 8X - val |= (proximity_gain & 0b11) << 2; + // proximity gain, 0 -> 1x, 1 -> 2X, 2 -> 4X, 3 -> 8X + val |= (this->proximity_gain_ & 0b11) << 2; val &= 0b11111100; - uint8_t ambient_gain = 1; // ambient light gain, 0 -> 1x, 1 -> 4x, 2 -> 16x, 3 -> 64x - val |= (ambient_gain & 0b11) << 0; + // ambient light gain, 0 -> 1x, 1 -> 4x, 2 -> 16x, 3 -> 64x + val |= (this->ambient_gain_ & 0b11) << 0; APDS9960_WRITE_BYTE(0x8F, val); // Pers (0x8C) -> 0x11 (2 consecutive proximity or ALS for interrupt) @@ -75,19 +75,18 @@ void APDS9960::setup() { // GConf 2 (0xA3, gesture config 2) -> APDS9960_ERROR_CHECK(this->read_byte(0xA3, &val)); val &= 0b10011111; - uint8_t gesture_gain = 2; // gesture gain, 0 -> 1x, 1 -> 2x, 2 -> 4x, 3 -> 8x - val |= (gesture_gain & 0b11) << 5; + // gesture gain, 0 -> 1x, 1 -> 2x, 2 -> 4x, 3 -> 8x + val |= (this->gesture_gain_ & 0b11) << 5; val &= 0b11100111; - uint8_t gesture_led_drive = 0; // gesture led drive, 0 -> 100mA, 1 -> 50mA, 2 -> 25mA, 3 -> 12.5mA - val |= (gesture_led_drive & 0b11) << 3; + // gesture led drive, 0 -> 100mA, 1 -> 50mA, 2 -> 25mA, 3 -> 12.5mA + val |= (this->gesture_led_drive_ & 0b11) << 3; val &= 0b11111000; // gesture wait time // 0 -> 0ms, 1 -> 2.8ms, 2 -> 5.6ms, 3 -> 8.4ms // 4 -> 14.0ms, 5 -> 22.4 ms, 6 -> 30.8ms, 7 -> 39.2 ms - uint8_t gesture_wait_time = 1; // gesture wait time - val |= (gesture_wait_time & 0b111) << 0; + val |= (this->gesture_wait_time_ & 0b111) << 0; APDS9960_WRITE_BYTE(0xA3, val); // GOffsetU (0xA4) -> 0x00 (no offset) diff --git a/esphome/components/apds9960/apds9960.h b/esphome/components/apds9960/apds9960.h index ae44b5da0f..23d9835640 100644 --- a/esphome/components/apds9960/apds9960.h +++ b/esphome/components/apds9960/apds9960.h @@ -16,6 +16,13 @@ class APDS9960 : public PollingComponent, public i2c::I2CDevice { void update() override; void loop() override; + void set_led_drive(uint8_t level) { this->led_drive_ = level; } + void set_proximity_gain(uint8_t gain) { this->proximity_gain_ = gain; } + void set_ambient_gain(uint8_t gain) { this->ambient_gain_ = gain; } + void set_gesture_led_drive(uint8_t level) { this->gesture_led_drive_ = level; } + void set_gesture_gain(uint8_t gain) { this->gesture_gain_ = gain; } + void set_gesture_wait_time(uint8_t wait_time) { this->gesture_wait_time_ = wait_time; } + void set_red_channel(sensor::Sensor *red_channel) { red_channel_ = red_channel; } void set_green_channel(sensor::Sensor *green_channel) { green_channel_ = green_channel; } void set_blue_channel(sensor::Sensor *blue_channel) { blue_channel_ = blue_channel; } @@ -36,6 +43,13 @@ class APDS9960 : public PollingComponent, public i2c::I2CDevice { void report_gesture_(int gesture); void process_dataset_(int up, int down, int left, int right); + uint8_t led_drive_; + uint8_t proximity_gain_; + uint8_t ambient_gain_; + uint8_t gesture_led_drive_; + uint8_t gesture_gain_; + uint8_t gesture_wait_time_; + sensor::Sensor *red_channel_{nullptr}; sensor::Sensor *green_channel_{nullptr}; sensor::Sensor *blue_channel_{nullptr}; diff --git a/esphome/components/api/api_frame_helper.h b/esphome/components/api/api_frame_helper.h index 57e3c961d5..348a9b574f 100644 --- a/esphome/components/api/api_frame_helper.h +++ b/esphome/components/api/api_frame_helper.h @@ -116,9 +116,9 @@ class APINoiseFrameHelper : public APIFrameHelper { std::vector prologue_; std::shared_ptr ctx_; - NoiseHandshakeState *handshake_ = nullptr; - NoiseCipherState *send_cipher_ = nullptr; - NoiseCipherState *recv_cipher_ = nullptr; + NoiseHandshakeState *handshake_{nullptr}; + NoiseCipherState *send_cipher_{nullptr}; + NoiseCipherState *recv_cipher_{nullptr}; NoiseProtocolId nid_; enum class State { diff --git a/esphome/components/as3935/as3935.h b/esphome/components/as3935/as3935.h index 2e65aab4d1..2cba9b11a0 100644 --- a/esphome/components/as3935/as3935.h +++ b/esphome/components/as3935/as3935.h @@ -92,9 +92,9 @@ class AS3935Component : public Component { virtual void write_register(uint8_t reg, uint8_t mask, uint8_t bits, uint8_t start_position) = 0; - sensor::Sensor *distance_sensor_; - sensor::Sensor *energy_sensor_; - binary_sensor::BinarySensor *thunder_alert_binary_sensor_; + sensor::Sensor *distance_sensor_{nullptr}; + sensor::Sensor *energy_sensor_{nullptr}; + binary_sensor::BinarySensor *thunder_alert_binary_sensor_{nullptr}; GPIOPin *irq_pin_; bool indoor_; diff --git a/esphome/components/bedjet/bedjet_const.h b/esphome/components/bedjet/bedjet_const.h index bd2fb2421d..27a75b2671 100644 --- a/esphome/components/bedjet/bedjet_const.h +++ b/esphome/components/bedjet/bedjet_const.h @@ -89,8 +89,10 @@ enum BedjetCommand : uint8_t { "85%", "90%", "95%", "100%" \ } -static const char *const BEDJET_FAN_STEP_NAMES[20] = BEDJET_FAN_STEP_NAMES_; -static const std::string BEDJET_FAN_STEP_NAME_STRINGS[20] = BEDJET_FAN_STEP_NAMES_; +static const uint8_t BEDJET_FAN_SPEED_COUNT = 20; + +static const char *const BEDJET_FAN_STEP_NAMES[BEDJET_FAN_SPEED_COUNT] = BEDJET_FAN_STEP_NAMES_; +static const std::string BEDJET_FAN_STEP_NAME_STRINGS[BEDJET_FAN_SPEED_COUNT] = BEDJET_FAN_STEP_NAMES_; static const std::set BEDJET_FAN_STEP_NAMES_SET BEDJET_FAN_STEP_NAMES_; } // namespace bedjet diff --git a/esphome/components/bedjet/climate.py b/esphome/components/bedjet/climate/__init__.py similarity index 89% rename from esphome/components/bedjet/climate.py rename to esphome/components/bedjet/climate/__init__.py index 9865cd716d..b12622868f 100644 --- a/esphome/components/bedjet/climate.py +++ b/esphome/components/bedjet/climate/__init__.py @@ -9,19 +9,17 @@ from esphome.const import ( CONF_RECEIVE_TIMEOUT, CONF_TIME_ID, ) -from . import ( +from .. import ( BEDJET_CLIENT_SCHEMA, + bedjet_ns, register_bedjet_child, ) _LOGGER = logging.getLogger(__name__) CODEOWNERS = ["@jhansche"] -DEPENDENCIES = ["ble_client"] +DEPENDENCIES = ["bedjet"] -bedjet_ns = cg.esphome_ns.namespace("bedjet") -BedJetClimate = bedjet_ns.class_( - "BedJetClimate", climate.Climate, ble_client.BLEClientNode, cg.PollingComponent -) +BedJetClimate = bedjet_ns.class_("BedJetClimate", climate.Climate, cg.PollingComponent) BedjetHeatMode = bedjet_ns.enum("BedjetHeatMode") BEDJET_HEAT_MODES = { "heat": BedjetHeatMode.HEAT_MODE_HEAT, diff --git a/esphome/components/bedjet/bedjet_climate.cpp b/esphome/components/bedjet/climate/bedjet_climate.cpp similarity index 99% rename from esphome/components/bedjet/bedjet_climate.cpp rename to esphome/components/bedjet/climate/bedjet_climate.cpp index 8d9fdd7318..431cf614e9 100644 --- a/esphome/components/bedjet/bedjet_climate.cpp +++ b/esphome/components/bedjet/climate/bedjet_climate.cpp @@ -15,13 +15,13 @@ float bedjet_temp_to_c(const uint8_t temp) { } static const std::string *bedjet_fan_step_to_fan_mode(const uint8_t fan_step) { - if (fan_step <= 19) + if (fan_step < BEDJET_FAN_SPEED_COUNT) return &BEDJET_FAN_STEP_NAME_STRINGS[fan_step]; return nullptr; } static uint8_t bedjet_fan_speed_to_step(const std::string &fan_step_percent) { - for (int i = 0; i < sizeof(BEDJET_FAN_STEP_NAME_STRINGS); i++) { + for (int i = 0; i < BEDJET_FAN_SPEED_COUNT; i++) { if (fan_step_percent == BEDJET_FAN_STEP_NAME_STRINGS[i]) { return i; } diff --git a/esphome/components/bedjet/bedjet_climate.h b/esphome/components/bedjet/climate/bedjet_climate.h similarity index 95% rename from esphome/components/bedjet/bedjet_climate.h rename to esphome/components/bedjet/climate/bedjet_climate.h index 27ee5c7501..48c50d842f 100644 --- a/esphome/components/bedjet/bedjet_climate.h +++ b/esphome/components/bedjet/climate/bedjet_climate.h @@ -1,12 +1,12 @@ #pragma once -#include "esphome/components/climate/climate.h" #include "esphome/core/component.h" #include "esphome/core/defines.h" #include "esphome/core/hal.h" -#include "bedjet_child.h" -#include "bedjet_codec.h" -#include "bedjet_hub.h" +#include "esphome/components/bedjet/bedjet_child.h" +#include "esphome/components/bedjet/bedjet_codec.h" +#include "esphome/components/bedjet/bedjet_hub.h" +#include "esphome/components/climate/climate.h" #ifdef USE_ESP32 diff --git a/esphome/components/bedjet/fan/__init__.py b/esphome/components/bedjet/fan/__init__.py new file mode 100644 index 0000000000..06e81ea979 --- /dev/null +++ b/esphome/components/bedjet/fan/__init__.py @@ -0,0 +1,36 @@ +import logging + +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import fan +from esphome.const import ( + CONF_ID, +) +from .. import ( + BEDJET_CLIENT_SCHEMA, + bedjet_ns, + register_bedjet_child, +) + +_LOGGER = logging.getLogger(__name__) +CODEOWNERS = ["@jhansche"] +DEPENDENCIES = ["bedjet"] + +BedJetFan = bedjet_ns.class_("BedJetFan", fan.Fan, cg.PollingComponent) + +CONFIG_SCHEMA = ( + fan.FAN_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(BedJetFan), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(BEDJET_CLIENT_SCHEMA) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await fan.register_fan(var, config) + await register_bedjet_child(var, config) diff --git a/esphome/components/bedjet/fan/bedjet_fan.cpp b/esphome/components/bedjet/fan/bedjet_fan.cpp new file mode 100644 index 0000000000..02ac289e0e --- /dev/null +++ b/esphome/components/bedjet/fan/bedjet_fan.cpp @@ -0,0 +1,108 @@ +#include "bedjet_fan.h" +#include "esphome/core/log.h" + +#ifdef USE_ESP32 + +namespace esphome { +namespace bedjet { + +using namespace esphome::fan; + +void BedJetFan::dump_config() { LOG_FAN("", "BedJet Fan", this); } +std::string BedJetFan::describe() { return "BedJet Fan"; } + +void BedJetFan::control(const fan::FanCall &call) { + ESP_LOGD(TAG, "Received BedJetFan::control"); + if (!this->parent_->is_connected()) { + ESP_LOGW(TAG, "Not connected, cannot handle control call yet."); + return; + } + bool did_change = false; + + if (call.get_state().has_value() && this->state != *call.get_state()) { + // Turning off is easy: + if (this->state && this->parent_->button_off()) { + this->state = false; + this->publish_state(); + return; + } + + // Turning on, we have to choose a specific mode; for now, use "COOL" mode + // In the future we could configure the mode to use for fan.turn_on. + if (this->parent_->button_cool()) { + this->state = true; + did_change = true; + } + } + + // ignore speed changes if not on or turning on + if (this->state && call.get_speed().has_value()) { + this->speed = *call.get_speed(); + this->parent_->set_fan_index(this->speed); + did_change = true; + } + + if (did_change) { + this->publish_state(); + } +} + +void BedJetFan::on_status(const BedjetStatusPacket *data) { + ESP_LOGVV(TAG, "[%s] Handling on_status with data=%p", this->get_name().c_str(), (void *) data); + bool did_change = false; + bool new_state = data->mode != MODE_STANDBY && data->mode != MODE_WAIT; + + if (new_state != this->state) { + this->state = new_state; + did_change = true; + } + + if (data->fan_step != this->speed) { + this->speed = data->fan_step; + did_change = true; + } + + if (did_change) { + this->publish_state(); + } +} + +/** Attempts to update the fan device from the last received BedjetStatusPacket. + * + * This will be called from #on_status() when the parent dispatches new status packets, + * and from #update() when the polling interval is triggered. + * + * @return `true` if the status has been applied; `false` if there is nothing to apply. + */ +bool BedJetFan::update_status_() { + if (!this->parent_->is_connected()) + return false; + if (!this->parent_->has_status()) + return false; + + auto *status = this->parent_->get_status_packet(); + + if (status == nullptr) + return false; + + this->on_status(status); + return true; +} + +void BedJetFan::update() { + ESP_LOGD(TAG, "[%s] update()", this->get_name().c_str()); + // TODO: if the hub component is already polling, do we also need to include polling? + // We're already going to get on_status() at the hub's polling interval. + auto result = this->update_status_(); + ESP_LOGD(TAG, "[%s] update_status result=%s", this->get_name().c_str(), result ? "true" : "false"); +} + +/** Resets states to defaults. */ +void BedJetFan::reset_state_() { + this->state = false; + this->publish_state(); +} +} // namespace bedjet +} // namespace esphome + +#endif diff --git a/esphome/components/bedjet/fan/bedjet_fan.h b/esphome/components/bedjet/fan/bedjet_fan.h new file mode 100644 index 0000000000..19db06e9d3 --- /dev/null +++ b/esphome/components/bedjet/fan/bedjet_fan.h @@ -0,0 +1,40 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/defines.h" +#include "esphome/core/hal.h" +#include "esphome/components/bedjet/bedjet_child.h" +#include "esphome/components/bedjet/bedjet_codec.h" +#include "esphome/components/bedjet/bedjet_hub.h" +#include "esphome/components/fan/fan.h" + +#ifdef USE_ESP32 + +namespace esphome { +namespace bedjet { + +class BedJetFan : public fan::Fan, public BedJetClient, public PollingComponent { + public: + void update() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } + + /* BedJetClient status update */ + void on_status(const BedjetStatusPacket *data) override; + void on_bedjet_state(bool is_ready) override{}; + std::string describe() override; + + fan::FanTraits get_traits() override { return fan::FanTraits(false, true, false, BEDJET_FAN_SPEED_COUNT); } + + protected: + void control(const fan::FanCall &call) override; + + private: + void reset_state_(); + bool update_status_(); +}; + +} // namespace bedjet +} // namespace esphome + +#endif diff --git a/esphome/components/binary_sensor_map/binary_sensor_map.cpp b/esphome/components/binary_sensor_map/binary_sensor_map.cpp index d1123ddff0..b2dffaf74d 100644 --- a/esphome/components/binary_sensor_map/binary_sensor_map.cpp +++ b/esphome/components/binary_sensor_map/binary_sensor_map.cpp @@ -13,6 +13,9 @@ void BinarySensorMap::loop() { case BINARY_SENSOR_MAP_TYPE_GROUP: this->process_group_(); break; + case BINARY_SENSOR_MAP_TYPE_SUM: + this->process_sum_(); + break; } } @@ -46,6 +49,34 @@ void BinarySensorMap::process_group_() { this->last_mask_ = mask; } +void BinarySensorMap::process_sum_() { + float total_current_value = 0.0; + uint64_t mask = 0x00; + // check all binary_sensors for its state. when active add its value to total_current_value. + // create a bitmask for the binary_sensor status on all channels + for (size_t i = 0; i < this->channels_.size(); i++) { + auto bs = this->channels_[i]; + if (bs.binary_sensor->state) { + total_current_value += bs.sensor_value; + mask |= 1 << i; + } + } + // check if the sensor map was touched + if (mask != 0ULL) { + // did the bit_mask change or is it a new sensor touch + if (this->last_mask_ != mask) { + float publish_value = total_current_value; + ESP_LOGD(TAG, "'%s' - Publishing %.2f", this->name_.c_str(), publish_value); + this->publish_state(publish_value); + } + } else if (this->last_mask_ != 0ULL) { + // is this a new sensor release + ESP_LOGD(TAG, "'%s' - No binary sensor active, publishing 0", this->name_.c_str()); + this->publish_state(0.0); + } + this->last_mask_ = mask; +} + void BinarySensorMap::add_channel(binary_sensor::BinarySensor *sensor, float value) { BinarySensorMapChannel sensor_channel{ .binary_sensor = sensor, diff --git a/esphome/components/binary_sensor_map/binary_sensor_map.h b/esphome/components/binary_sensor_map/binary_sensor_map.h index e99b4e18d6..a880be9623 100644 --- a/esphome/components/binary_sensor_map/binary_sensor_map.h +++ b/esphome/components/binary_sensor_map/binary_sensor_map.h @@ -9,6 +9,7 @@ namespace binary_sensor_map { enum BinarySensorMapType { BINARY_SENSOR_MAP_TYPE_GROUP, + BINARY_SENSOR_MAP_TYPE_SUM, }; struct BinarySensorMapChannel { @@ -50,8 +51,10 @@ class BinarySensorMap : public sensor::Sensor, public Component { /** * methods to process the types of binary_sensor_maps * GROUP: process_group_() just map to a value + * ADD: process_add_() adds all the values * */ void process_group_(); + void process_sum_(); }; } // namespace binary_sensor_map diff --git a/esphome/components/binary_sensor_map/sensor.py b/esphome/components/binary_sensor_map/sensor.py index 7ddf0ecf2a..025be490cd 100644 --- a/esphome/components/binary_sensor_map/sensor.py +++ b/esphome/components/binary_sensor_map/sensor.py @@ -9,6 +9,7 @@ from esphome.const import ( ICON_CHECK_CIRCLE_OUTLINE, CONF_BINARY_SENSOR, CONF_GROUP, + CONF_SUM, ) DEPENDENCIES = ["binary_sensor"] @@ -21,6 +22,7 @@ SensorMapType = binary_sensor_map_ns.enum("SensorMapType") SENSOR_MAP_TYPES = { CONF_GROUP: SensorMapType.BINARY_SENSOR_MAP_TYPE_GROUP, + CONF_SUM: SensorMapType.BINARY_SENSOR_MAP_TYPE_SUM, } entry = { @@ -41,6 +43,17 @@ CONFIG_SCHEMA = cv.typed_schema( ), } ), + CONF_SUM: sensor.sensor_schema( + BinarySensorMap, + icon=ICON_CHECK_CIRCLE_OUTLINE, + accuracy_decimals=0, + ).extend( + { + cv.Required(CONF_CHANNELS): cv.All( + cv.ensure_list(entry), cv.Length(min=1) + ), + } + ), }, lower=True, ) diff --git a/esphome/components/bl0939/bl0939.h b/esphome/components/bl0939/bl0939.h index d3dab67cc1..673d4ff351 100644 --- a/esphome/components/bl0939/bl0939.h +++ b/esphome/components/bl0939/bl0939.h @@ -75,16 +75,16 @@ class BL0939 : public PollingComponent, public uart::UARTDevice { void dump_config() override; protected: - sensor::Sensor *voltage_sensor_; - sensor::Sensor *current_sensor_1_; - sensor::Sensor *current_sensor_2_; + sensor::Sensor *voltage_sensor_{nullptr}; + sensor::Sensor *current_sensor_1_{nullptr}; + sensor::Sensor *current_sensor_2_{nullptr}; // NB This may be negative as the circuits is seemingly able to measure // power in both directions - sensor::Sensor *power_sensor_1_; - sensor::Sensor *power_sensor_2_; - sensor::Sensor *energy_sensor_1_; - sensor::Sensor *energy_sensor_2_; - sensor::Sensor *energy_sensor_sum_; + sensor::Sensor *power_sensor_1_{nullptr}; + sensor::Sensor *power_sensor_2_{nullptr}; + sensor::Sensor *energy_sensor_1_{nullptr}; + sensor::Sensor *energy_sensor_2_{nullptr}; + sensor::Sensor *energy_sensor_sum_{nullptr}; // Divide by this to turn into Watt float power_reference_ = BL0939_PREF; diff --git a/esphome/components/bl0940/bl0940.h b/esphome/components/bl0940/bl0940.h index 49c8e50595..2d4e7ccaac 100644 --- a/esphome/components/bl0940/bl0940.h +++ b/esphome/components/bl0940/bl0940.h @@ -75,14 +75,14 @@ class BL0940 : public PollingComponent, public uart::UARTDevice { void dump_config() override; protected: - sensor::Sensor *voltage_sensor_; - sensor::Sensor *current_sensor_; + sensor::Sensor *voltage_sensor_{nullptr}; + sensor::Sensor *current_sensor_{nullptr}; // NB This may be negative as the circuits is seemingly able to measure // power in both directions - sensor::Sensor *power_sensor_; - sensor::Sensor *energy_sensor_; - sensor::Sensor *internal_temperature_sensor_; - sensor::Sensor *external_temperature_sensor_; + sensor::Sensor *power_sensor_{nullptr}; + sensor::Sensor *energy_sensor_{nullptr}; + sensor::Sensor *internal_temperature_sensor_{nullptr}; + sensor::Sensor *external_temperature_sensor_{nullptr}; // Max difference between two measurements of the temperature. Used to avoid noise. float max_temperature_diff_{0}; diff --git a/esphome/components/bl0942/__init__.py b/esphome/components/bl0942/__init__.py new file mode 100644 index 0000000000..8ef7857b7b --- /dev/null +++ b/esphome/components/bl0942/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@dbuezas"] diff --git a/esphome/components/bl0942/bl0942.cpp b/esphome/components/bl0942/bl0942.cpp new file mode 100644 index 0000000000..e6d18a82a7 --- /dev/null +++ b/esphome/components/bl0942/bl0942.cpp @@ -0,0 +1,121 @@ +#include "bl0942.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace bl0942 { + +static const char *const TAG = "bl0942"; + +static const uint8_t BL0942_READ_COMMAND = 0x58; +static const uint8_t BL0942_FULL_PACKET = 0xAA; +static const uint8_t BL0942_PACKET_HEADER = 0x55; + +static const uint8_t BL0942_WRITE_COMMAND = 0xA8; +static const uint8_t BL0942_REG_I_FAST_RMS_CTRL = 0x10; +static const uint8_t BL0942_REG_MODE = 0x18; +static const uint8_t BL0942_REG_SOFT_RESET = 0x19; +static const uint8_t BL0942_REG_USR_WRPROT = 0x1A; +static const uint8_t BL0942_REG_TPS_CTRL = 0x1B; + +// TODO: Confirm insialisation works as intended +const uint8_t BL0942_INIT[5][6] = { + // Reset to default + {BL0942_WRITE_COMMAND, BL0942_REG_SOFT_RESET, 0x5A, 0x5A, 0x5A, 0x38}, + // Enable User Operation Write + {BL0942_WRITE_COMMAND, BL0942_REG_USR_WRPROT, 0x55, 0x00, 0x00, 0xF0}, + // 0x0100 = CF_UNABLE energy pulse, AC_FREQ_SEL 50Hz, RMS_UPDATE_SEL 800mS + {BL0942_WRITE_COMMAND, BL0942_REG_MODE, 0x00, 0x10, 0x00, 0x37}, + // 0x47FF = Over-current and leakage alarm on, Automatic temperature measurement, Interval 100mS + {BL0942_WRITE_COMMAND, BL0942_REG_TPS_CTRL, 0xFF, 0x47, 0x00, 0xFE}, + // 0x181C = Half cycle, Fast RMS threshold 6172 + {BL0942_WRITE_COMMAND, BL0942_REG_I_FAST_RMS_CTRL, 0x1C, 0x18, 0x00, 0x1B}}; + +void BL0942::loop() { + DataPacket buffer; + if (!this->available()) { + return; + } + if (read_array((uint8_t *) &buffer, sizeof(buffer))) { + if (validate_checksum(&buffer)) { + received_package_(&buffer); + } + } else { + ESP_LOGW(TAG, "Junk on wire. Throwing away partial message"); + while (read() >= 0) + ; + } +} + +bool BL0942::validate_checksum(DataPacket *data) { + uint8_t checksum = BL0942_READ_COMMAND; + // Whole package but checksum + uint8_t *raw = (uint8_t *) data; + for (uint32_t i = 0; i < sizeof(*data) - 1; i++) { + checksum += raw[i]; + } + checksum ^= 0xFF; + if (checksum != data->checksum) { + ESP_LOGW(TAG, "BL0942 invalid checksum! 0x%02X != 0x%02X", checksum, data->checksum); + } + return checksum == data->checksum; +} + +void BL0942::update() { + this->flush(); + this->write_byte(BL0942_READ_COMMAND); + this->write_byte(BL0942_FULL_PACKET); +} + +void BL0942::setup() { + for (auto *i : BL0942_INIT) { + this->write_array(i, 6); + delay(1); + } + this->flush(); +} + +void BL0942::received_package_(DataPacket *data) { + // Bad header + if (data->frame_header != BL0942_PACKET_HEADER) { + ESP_LOGI(TAG, "Invalid data. Header mismatch: %d", data->frame_header); + return; + } + + float v_rms = (uint24_t) data->v_rms / voltage_reference_; + float i_rms = (uint24_t) data->i_rms / current_reference_; + float watt = (int24_t) data->watt / power_reference_; + uint32_t cf_cnt = (uint24_t) data->cf_cnt; + float total_energy_consumption = cf_cnt / energy_reference_; + float frequency = 1000000.0f / data->frequency; + + if (voltage_sensor_ != nullptr) { + voltage_sensor_->publish_state(v_rms); + } + if (current_sensor_ != nullptr) { + current_sensor_->publish_state(i_rms); + } + if (power_sensor_ != nullptr) { + power_sensor_->publish_state(watt); + } + if (energy_sensor_ != nullptr) { + energy_sensor_->publish_state(total_energy_consumption); + } + if (frequency_sensor_ != nullptr) { + frequency_sensor_->publish_state(frequency); + } + + ESP_LOGV(TAG, "BL0942: U %fV, I %fA, P %fW, Cnt %d, ∫P %fkWh, frequency %f°Hz, status 0x%08X", v_rms, i_rms, watt, + cf_cnt, total_energy_consumption, frequency, data->status); +} + +void BL0942::dump_config() { // NOLINT(readability-function-cognitive-complexity) + ESP_LOGCONFIG(TAG, "BL0942:"); + LOG_SENSOR("", "Voltage", this->voltage_sensor_); + LOG_SENSOR("", "Current", this->current_sensor_); + LOG_SENSOR("", "Power", this->power_sensor_); + LOG_SENSOR("", "Energy", this->energy_sensor_); + LOG_SENSOR("", "frequency", this->frequency_sensor_); +} + +} // namespace bl0942 +} // namespace esphome diff --git a/esphome/components/bl0942/bl0942.h b/esphome/components/bl0942/bl0942.h new file mode 100644 index 0000000000..12489915e1 --- /dev/null +++ b/esphome/components/bl0942/bl0942.h @@ -0,0 +1,68 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/datatypes.h" +#include "esphome/components/uart/uart.h" +#include "esphome/components/sensor/sensor.h" + +namespace esphome { +namespace bl0942 { + +static const float BL0942_PREF = 596; // taken from tasmota +static const float BL0942_UREF = 15873.35944299; // should be 73989/1.218 +static const float BL0942_IREF = 251213.46469622; // 305978/1.218 +static const float BL0942_EREF = 3304.61127328; // Measured + +struct DataPacket { + uint8_t frame_header; + uint24_le_t i_rms; + uint24_le_t v_rms; + uint24_le_t i_fast_rms; + int24_le_t watt; + uint24_le_t cf_cnt; + uint16_le_t frequency; + uint8_t reserved1; + uint8_t status; + uint8_t reserved2; + uint8_t reserved3; + uint8_t checksum; +} __attribute__((packed)); + +class BL0942 : public PollingComponent, public uart::UARTDevice { + public: + 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 set_frequency_sensor(sensor::Sensor *frequency_sensor) { frequency_sensor_ = frequency_sensor; } + + void loop() override; + + void update() override; + void setup() override; + void dump_config() override; + + protected: + sensor::Sensor *voltage_sensor_{nullptr}; + sensor::Sensor *current_sensor_{nullptr}; + // NB This may be negative as the circuits is seemingly able to measure + // power in both directions + sensor::Sensor *power_sensor_{nullptr}; + sensor::Sensor *energy_sensor_{nullptr}; + sensor::Sensor *frequency_sensor_{nullptr}; + + // Divide by this to turn into Watt + float power_reference_ = BL0942_PREF; + // Divide by this to turn into Volt + float voltage_reference_ = BL0942_UREF; + // Divide by this to turn into Ampere + float current_reference_ = BL0942_IREF; + // Divide by this to turn into kWh + float energy_reference_ = BL0942_EREF; + + static bool validate_checksum(DataPacket *data); + + void received_package_(DataPacket *data); +}; +} // namespace bl0942 +} // namespace esphome diff --git a/esphome/components/bl0942/sensor.py b/esphome/components/bl0942/sensor.py new file mode 100644 index 0000000000..f23375b309 --- /dev/null +++ b/esphome/components/bl0942/sensor.py @@ -0,0 +1,93 @@ +import esphome.codegen as cg +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, + CONF_FREQUENCY, + DEVICE_CLASS_CURRENT, + DEVICE_CLASS_ENERGY, + DEVICE_CLASS_POWER, + DEVICE_CLASS_VOLTAGE, + DEVICE_CLASS_FREQUENCY, + STATE_CLASS_MEASUREMENT, + UNIT_AMPERE, + UNIT_KILOWATT_HOURS, + UNIT_VOLT, + UNIT_WATT, + UNIT_HERTZ, +) + +DEPENDENCIES = ["uart"] + +bl0942_ns = cg.esphome_ns.namespace("bl0942") +BL0942 = bl0942_ns.class_("BL0942", cg.PollingComponent, uart.UARTDevice) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(BL0942), + cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CURRENT): sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=2, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_POWER): sensor.sensor_schema( + unit_of_measurement=UNIT_WATT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_ENERGY): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT_HOURS, + accuracy_decimals=0, + device_class=DEVICE_CLASS_ENERGY, + ), + cv.Optional(CONF_FREQUENCY): sensor.sensor_schema( + unit_of_measurement=UNIT_HERTZ, + accuracy_decimals=0, + device_class=DEVICE_CLASS_FREQUENCY, + state_class=STATE_CLASS_MEASUREMENT, + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(uart.UART_DEVICE_SCHEMA) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await uart.register_uart_device(var, config) + + if CONF_VOLTAGE in config: + conf = config[CONF_VOLTAGE] + sens = await sensor.new_sensor(conf) + cg.add(var.set_voltage_sensor(sens)) + if CONF_CURRENT in config: + conf = config[CONF_CURRENT] + sens = await sensor.new_sensor(conf) + cg.add(var.set_current_sensor(sens)) + if CONF_POWER in config: + conf = config[CONF_POWER] + sens = await sensor.new_sensor(conf) + cg.add(var.set_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)) + if CONF_FREQUENCY in config: + conf = config[CONF_FREQUENCY] + sens = await sensor.new_sensor(conf) + cg.add(var.set_frequency_sensor(sens)) diff --git a/esphome/components/ble_client/switch/__init__.py b/esphome/components/ble_client/switch/__init__.py index e5b5ab281b..2304d65c01 100644 --- a/esphome/components/ble_client/switch/__init__.py +++ b/esphome/components/ble_client/switch/__init__.py @@ -1,7 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import switch, ble_client -from esphome.const import CONF_ICON, CONF_ID, CONF_INVERTED, ICON_BLUETOOTH +from esphome.const import ICON_BLUETOOTH from .. import ble_client_ns BLEClientSwitch = ble_client_ns.class_( @@ -9,22 +9,13 @@ BLEClientSwitch = ble_client_ns.class_( ) CONFIG_SCHEMA = ( - switch.SWITCH_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(BLEClientSwitch), - cv.Optional(CONF_INVERTED): cv.invalid( - "BLE client switches do not support inverted mode!" - ), - cv.Optional(CONF_ICON, default=ICON_BLUETOOTH): switch.icon, - } - ) + switch.switch_schema(BLEClientSwitch, icon=ICON_BLUETOOTH, block_inverted=True) .extend(ble_client.BLE_CLIENT_SCHEMA) .extend(cv.COMPONENT_SCHEMA) ) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await switch.new_switch(config) await cg.register_component(var, config) - await switch.register_switch(var, config) await ble_client.register_ble_node(var, config) diff --git a/esphome/components/ble_rssi/ble_rssi_sensor.h b/esphome/components/ble_rssi/ble_rssi_sensor.h index c6acae2593..0cb511de3b 100644 --- a/esphome/components/ble_rssi/ble_rssi_sensor.h +++ b/esphome/components/ble_rssi/ble_rssi_sensor.h @@ -12,41 +12,78 @@ namespace ble_rssi { class BLERSSISensor : public sensor::Sensor, public esp32_ble_tracker::ESPBTDeviceListener, public Component { public: void set_address(uint64_t address) { - this->by_address_ = true; + this->match_by_ = MATCH_BY_MAC_ADDRESS; this->address_ = address; } void set_service_uuid16(uint16_t uuid) { - this->by_address_ = false; + this->match_by_ = MATCH_BY_SERVICE_UUID; this->uuid_ = esp32_ble_tracker::ESPBTUUID::from_uint16(uuid); } void set_service_uuid32(uint32_t uuid) { - this->by_address_ = false; + this->match_by_ = MATCH_BY_SERVICE_UUID; this->uuid_ = esp32_ble_tracker::ESPBTUUID::from_uint32(uuid); } void set_service_uuid128(uint8_t *uuid) { - this->by_address_ = false; + this->match_by_ = MATCH_BY_SERVICE_UUID; this->uuid_ = esp32_ble_tracker::ESPBTUUID::from_raw(uuid); } + void set_ibeacon_uuid(uint8_t *uuid) { + this->match_by_ = MATCH_BY_IBEACON_UUID; + this->ibeacon_uuid_ = esp32_ble_tracker::ESPBTUUID::from_raw(uuid); + } + void set_ibeacon_major(uint16_t major) { + this->check_ibeacon_major_ = true; + this->ibeacon_major_ = major; + } + void set_ibeacon_minor(uint16_t minor) { + this->check_ibeacon_minor_ = true; + this->ibeacon_minor_ = minor; + } void on_scan_end() override { if (!this->found_) this->publish_state(NAN); this->found_ = false; } bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override { - if (this->by_address_) { - if (device.address_uint64() == this->address_) { - this->publish_state(device.get_rssi()); - this->found_ = true; - return true; - } - } else { - for (auto uuid : device.get_service_uuids()) { - if (this->uuid_ == uuid) { + switch (this->match_by_) { + case MATCH_BY_MAC_ADDRESS: + if (device.address_uint64() == this->address_) { this->publish_state(device.get_rssi()); this->found_ = true; return true; } - } + break; + case MATCH_BY_SERVICE_UUID: + for (auto uuid : device.get_service_uuids()) { + if (this->uuid_ == uuid) { + this->publish_state(device.get_rssi()); + this->found_ = true; + return true; + } + } + break; + case MATCH_BY_IBEACON_UUID: + if (!device.get_ibeacon().has_value()) { + return false; + } + + auto ibeacon = device.get_ibeacon().value(); + + if (this->ibeacon_uuid_ != ibeacon.get_uuid()) { + return false; + } + + if (this->check_ibeacon_major_ && this->ibeacon_major_ != ibeacon.get_major()) { + return false; + } + + if (this->check_ibeacon_minor_ && this->ibeacon_minor_ != ibeacon.get_minor()) { + return false; + } + + this->publish_state(device.get_rssi()); + this->found_ = true; + return true; } return false; } @@ -54,10 +91,20 @@ class BLERSSISensor : public sensor::Sensor, public esp32_ble_tracker::ESPBTDevi float get_setup_priority() const override { return setup_priority::DATA; } protected: + enum MatchType { MATCH_BY_MAC_ADDRESS, MATCH_BY_SERVICE_UUID, MATCH_BY_IBEACON_UUID }; + MatchType match_by_; + bool found_{false}; - bool by_address_{false}; + uint64_t address_; + esp32_ble_tracker::ESPBTUUID uuid_; + + esp32_ble_tracker::ESPBTUUID ibeacon_uuid_; + uint16_t ibeacon_major_; + bool check_ibeacon_major_; + uint16_t ibeacon_minor_; + bool check_ibeacon_minor_; }; } // namespace ble_rssi diff --git a/esphome/components/ble_rssi/sensor.py b/esphome/components/ble_rssi/sensor.py index fd2c2e5cb1..bd8e4f98e3 100644 --- a/esphome/components/ble_rssi/sensor.py +++ b/esphome/components/ble_rssi/sensor.py @@ -2,6 +2,9 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, esp32_ble_tracker from esphome.const import ( + CONF_IBEACON_MAJOR, + CONF_IBEACON_MINOR, + CONF_IBEACON_UUID, CONF_SERVICE_UUID, CONF_MAC_ADDRESS, DEVICE_CLASS_SIGNAL_STRENGTH, @@ -16,6 +19,15 @@ BLERSSISensor = ble_rssi_ns.class_( "BLERSSISensor", sensor.Sensor, cg.Component, esp32_ble_tracker.ESPBTDeviceListener ) + +def _validate(config): + if CONF_IBEACON_MAJOR in config and CONF_IBEACON_UUID not in config: + raise cv.Invalid("iBeacon major identifier requires iBeacon UUID") + if CONF_IBEACON_MINOR in config and CONF_IBEACON_UUID not in config: + raise cv.Invalid("iBeacon minor identifier requires iBeacon UUID") + return config + + CONFIG_SCHEMA = cv.All( sensor.sensor_schema( BLERSSISensor, @@ -28,11 +40,15 @@ CONFIG_SCHEMA = cv.All( { cv.Optional(CONF_MAC_ADDRESS): cv.mac_address, cv.Optional(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid, + cv.Optional(CONF_IBEACON_MAJOR): cv.uint16_t, + cv.Optional(CONF_IBEACON_MINOR): cv.uint16_t, + cv.Optional(CONF_IBEACON_UUID): cv.uuid, } ) .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) .extend(cv.COMPONENT_SCHEMA), - cv.has_exactly_one_key(CONF_MAC_ADDRESS, CONF_SERVICE_UUID), + cv.has_exactly_one_key(CONF_MAC_ADDRESS, CONF_SERVICE_UUID, CONF_IBEACON_UUID), + _validate, ) @@ -60,3 +76,13 @@ async def to_code(config): 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 CONF_IBEACON_UUID in config: + ibeacon_uuid = esp32_ble_tracker.as_hex_array(str(config[CONF_IBEACON_UUID])) + cg.add(var.set_ibeacon_uuid(ibeacon_uuid)) + + if CONF_IBEACON_MAJOR in config: + cg.add(var.set_ibeacon_major(config[CONF_IBEACON_MAJOR])) + + if CONF_IBEACON_MINOR in config: + cg.add(var.set_ibeacon_minor(config[CONF_IBEACON_MINOR])) diff --git a/esphome/components/bme280/bme280.cpp b/esphome/components/bme280/bme280.cpp index 345b24a36e..d8124f5dc3 100644 --- a/esphome/components/bme280/bme280.cpp +++ b/esphome/components/bme280/bme280.cpp @@ -163,7 +163,7 @@ void BME280Component::setup() { return; } config_register &= ~0b11111100; - config_register |= 0b000 << 5; // 0.5 ms standby time + config_register |= 0b101 << 5; // 1000 ms standby time config_register |= (this->iir_filter_ & 0b111) << 2; if (!this->write_byte(BME280_REGISTER_CONFIG, config_register)) { this->mark_failed(); diff --git a/esphome/components/bme280/bme280.h b/esphome/components/bme280/bme280.h index 8511f73382..50d398c40f 100644 --- a/esphome/components/bme280/bme280.h +++ b/esphome/components/bme280/bme280.h @@ -96,9 +96,9 @@ class BME280Component : public PollingComponent, public i2c::I2CDevice { BME280Oversampling pressure_oversampling_{BME280_OVERSAMPLING_16X}; BME280Oversampling humidity_oversampling_{BME280_OVERSAMPLING_16X}; BME280IIRFilter iir_filter_{BME280_IIR_FILTER_OFF}; - sensor::Sensor *temperature_sensor_; - sensor::Sensor *pressure_sensor_; - sensor::Sensor *humidity_sensor_; + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *pressure_sensor_{nullptr}; + sensor::Sensor *humidity_sensor_{nullptr}; enum ErrorCode { NONE = 0, COMMUNICATION_FAILED, diff --git a/esphome/components/bme680/bme680.h b/esphome/components/bme680/bme680.h index 0671cd990e..6446449742 100644 --- a/esphome/components/bme680/bme680.h +++ b/esphome/components/bme680/bme680.h @@ -129,10 +129,10 @@ class BME680Component : public PollingComponent, public i2c::I2CDevice { uint16_t heater_temperature_{320}; uint16_t heater_duration_{150}; - sensor::Sensor *temperature_sensor_; - sensor::Sensor *pressure_sensor_; - sensor::Sensor *humidity_sensor_; - sensor::Sensor *gas_resistance_sensor_; + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *pressure_sensor_{nullptr}; + sensor::Sensor *humidity_sensor_{nullptr}; + sensor::Sensor *gas_resistance_sensor_{nullptr}; }; } // namespace bme680 diff --git a/esphome/components/bme680_bsec/bme680_bsec.h b/esphome/components/bme680_bsec/bme680_bsec.h index 650b4d2413..6fe8f8fef7 100644 --- a/esphome/components/bme680_bsec/bme680_bsec.h +++ b/esphome/components/bme680_bsec/bme680_bsec.h @@ -100,15 +100,15 @@ class BME680BSECComponent : public Component, public i2c::I2CDevice { SampleRate pressure_sample_rate_{SAMPLE_RATE_DEFAULT}; SampleRate humidity_sample_rate_{SAMPLE_RATE_DEFAULT}; - sensor::Sensor *temperature_sensor_; - sensor::Sensor *pressure_sensor_; - sensor::Sensor *humidity_sensor_; - sensor::Sensor *gas_resistance_sensor_; - sensor::Sensor *iaq_sensor_; - text_sensor::TextSensor *iaq_accuracy_text_sensor_; - sensor::Sensor *iaq_accuracy_sensor_; - sensor::Sensor *co2_equivalent_sensor_; - sensor::Sensor *breath_voc_equivalent_sensor_; + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *pressure_sensor_{nullptr}; + sensor::Sensor *humidity_sensor_{nullptr}; + sensor::Sensor *gas_resistance_sensor_{nullptr}; + sensor::Sensor *iaq_sensor_{nullptr}; + text_sensor::TextSensor *iaq_accuracy_text_sensor_{nullptr}; + sensor::Sensor *iaq_accuracy_sensor_{nullptr}; + sensor::Sensor *co2_equivalent_sensor_{nullptr}; + sensor::Sensor *breath_voc_equivalent_sensor_{nullptr}; }; #endif } // namespace bme680_bsec diff --git a/esphome/components/bmp280/bmp280.h b/esphome/components/bmp280/bmp280.h index f8646fb547..96eb470155 100644 --- a/esphome/components/bmp280/bmp280.h +++ b/esphome/components/bmp280/bmp280.h @@ -81,8 +81,8 @@ class BMP280Component : public PollingComponent, public i2c::I2CDevice { BMP280Oversampling temperature_oversampling_{BMP280_OVERSAMPLING_16X}; BMP280Oversampling pressure_oversampling_{BMP280_OVERSAMPLING_16X}; BMP280IIRFilter iir_filter_{BMP280_IIR_FILTER_OFF}; - sensor::Sensor *temperature_sensor_; - sensor::Sensor *pressure_sensor_; + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *pressure_sensor_{nullptr}; enum ErrorCode { NONE = 0, COMMUNICATION_FAILED, diff --git a/esphome/components/bmp3xx/bmp3xx.h b/esphome/components/bmp3xx/bmp3xx.h index ab20abfe9b..d3b15f601d 100644 --- a/esphome/components/bmp3xx/bmp3xx.h +++ b/esphome/components/bmp3xx/bmp3xx.h @@ -125,8 +125,8 @@ class BMP3XXComponent : public PollingComponent, public i2c::I2CDevice { Oversampling pressure_oversampling_{OVERSAMPLING_X16}; IIRFilter iir_filter_{IIR_FILTER_OFF}; OperationMode operation_mode_{FORCED_MODE}; - sensor::Sensor *temperature_sensor_; - sensor::Sensor *pressure_sensor_; + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *pressure_sensor_{nullptr}; enum ErrorCode { NONE = 0, ERROR_COMMUNICATION_FAILED, diff --git a/esphome/components/copy/switch/__init__.py b/esphome/components/copy/switch/__init__.py index 6622412123..beffbe7fbb 100644 --- a/esphome/components/copy/switch/__init__.py +++ b/esphome/components/copy/switch/__init__.py @@ -5,7 +5,6 @@ from esphome.const import ( CONF_DEVICE_CLASS, CONF_ENTITY_CATEGORY, CONF_ICON, - CONF_ID, CONF_SOURCE_ID, ) from esphome.core.entity_helpers import inherit_property_from @@ -15,12 +14,15 @@ from .. import copy_ns CopySwitch = copy_ns.class_("CopySwitch", switch.Switch, cg.Component) -CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(CopySwitch), - cv.Required(CONF_SOURCE_ID): cv.use_id(switch.Switch), - } -).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = ( + switch.switch_schema(CopySwitch) + .extend( + { + cv.Required(CONF_SOURCE_ID): cv.use_id(switch.Switch), + } + ) + .extend(cv.COMPONENT_SCHEMA) +) FINAL_VALIDATE_SCHEMA = cv.All( inherit_property_from(CONF_ICON, CONF_SOURCE_ID), @@ -30,8 +32,7 @@ FINAL_VALIDATE_SCHEMA = cv.All( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await switch.register_switch(var, config) + var = await switch.new_switch(config) await cg.register_component(var, config) source = await cg.get_variable(config[CONF_SOURCE_ID]) diff --git a/esphome/components/cse7766/cse7766.cpp b/esphome/components/cse7766/cse7766.cpp index 25d75da3e6..f232f35ea6 100644 --- a/esphome/components/cse7766/cse7766.cpp +++ b/esphome/components/cse7766/cse7766.cpp @@ -13,8 +13,9 @@ void CSE7766Component::loop() { this->raw_data_index_ = 0; } - if (this->available() == 0) + if (this->available() == 0) { return; + } this->last_transmission_ = now; while (this->available() != 0) { @@ -22,6 +23,7 @@ void CSE7766Component::loop() { if (!this->check_byte_()) { this->raw_data_index_ = 0; this->status_set_warning(); + continue; } if (this->raw_data_index_ == 23) { @@ -51,8 +53,9 @@ bool CSE7766Component::check_byte_() { if (index == 23) { uint8_t checksum = 0; - for (uint8_t i = 2; i < 23; i++) + for (uint8_t i = 2; i < 23; i++) { checksum += this->raw_data_[i]; + } if (checksum != this->raw_data_[23]) { ESP_LOGW(TAG, "Invalid checksum from CSE7766: 0x%02X != 0x%02X", checksum, this->raw_data_[23]); @@ -66,20 +69,34 @@ bool CSE7766Component::check_byte_() { void CSE7766Component::parse_data_() { ESP_LOGVV(TAG, "CSE7766 Data: "); for (uint8_t i = 0; i < 23; i++) { - ESP_LOGVV(TAG, " i=%u: 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", i, BYTE_TO_BINARY(this->raw_data_[i]), + ESP_LOGVV(TAG, " %u: 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", i + 1, BYTE_TO_BINARY(this->raw_data_[i]), this->raw_data_[i]); } uint8_t header1 = this->raw_data_[0]; if (header1 == 0xAA) { - ESP_LOGW(TAG, "CSE7766 not calibrated!"); + ESP_LOGE(TAG, "CSE7766 not calibrated!"); return; } - if ((header1 & 0xF0) == 0xF0 && ((header1 >> 0) & 1) == 1) { - ESP_LOGW(TAG, "CSE7766 reports abnormal hardware: (0x%02X)", header1); - ESP_LOGW(TAG, " Coefficient storage area is abnormal."); - return; + bool power_cycle_exceeds_range = false; + + if ((header1 & 0xF0) == 0xF0) { + if (header1 & 0xD) { + ESP_LOGE(TAG, "CSE7766 reports abnormal external circuit or chip damage: (0x%02X)", header1); + if (header1 & (1 << 3)) { + ESP_LOGE(TAG, " Voltage cycle exceeds range."); + } + if (header1 & (1 << 2)) { + ESP_LOGE(TAG, " Current cycle exceeds range."); + } + if (header1 & (1 << 0)) { + ESP_LOGE(TAG, " Coefficient storage area is abnormal."); + } + return; + } + + power_cycle_exceeds_range = header1 & (1 << 1); } uint32_t voltage_calib = this->get_24_bit_uint_(2); @@ -92,46 +109,29 @@ void CSE7766Component::parse_data_() { 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; - bool current_ok = true; - - if (header1 > 0xF0) { - // ESP_LOGV(TAG, "CSE7766 reports abnormal hardware: (0x%02X)", byte); - if ((header1 >> 3) & 1) { - ESP_LOGV(TAG, " Voltage cycle exceeds range."); - voltage_ok = false; - } - if ((header1 >> 2) & 1) { - ESP_LOGV(TAG, " Current cycle exceeds range."); - current_ok = false; - } - if ((header1 >> 1) & 1) { - ESP_LOGV(TAG, " Power cycle exceeds range."); - power_ok = false; - } - if ((header1 >> 0) & 1) { - ESP_LOGV(TAG, " Coefficient storage area is abnormal."); - return; - } - } - - if ((adj & 0x40) == 0x40 && voltage_ok && current_ok) { + bool have_voltage = adj & 0x40; + if (have_voltage) { // voltage cycle of serial port outputted is a complete cycle; this->voltage_acc_ += voltage_calib / float(voltage_cycle); this->voltage_counts_ += 1; } - float power = 0; - if ((adj & 0x10) == 0x10 && voltage_ok && current_ok && power_ok) { + bool have_power = adj & 0x10; + float power = 0.0f; + + if (have_power) { // power cycle of serial port outputted is a complete cycle; - power = power_calib / float(power_cycle); + // According to the user manual, power cycle exceeding range means the measured power is 0 + if (!power_cycle_exceeds_range) { + power = power_calib / float(power_cycle); + } this->power_acc_ += power; this->power_counts_ += 1; uint32_t difference; - if (this->cf_pulses_last_ == 0) + 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_); @@ -139,41 +139,52 @@ void CSE7766Component::parse_data_() { difference = cf_pulses - this->cf_pulses_last_; } this->cf_pulses_last_ = cf_pulses; - this->energy_total_ += difference * float(power_calib) / 1000000.0 / 3600.0; + this->energy_total_ += difference * float(power_calib) / 1000000.0f / 3600.0f; + this->energy_total_counts_ += 1; } - if ((adj & 0x20) == 0x20 && current_ok && voltage_ok && power != 0.0) { + if (adj & 0x20) { // indicates current cycle of serial port outputted is a complete cycle; - this->current_acc_ += current_calib / float(current_cycle); + float current = 0.0f; + if (have_voltage && !have_power) { + // Testing has shown that when we have voltage and current but not power, that means the power is 0. + // We report a power of 0, which in turn means we should report a current of 0. + this->power_counts_ += 1; + } else if (power != 0.0f) { + current = current_calib / float(current_cycle); + } + this->current_acc_ += current; this->current_counts_ += 1; } } void CSE7766Component::update() { - 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; + const auto publish_state = [](const char *name, sensor::Sensor *sensor, float &acc, uint32_t &counts) { + if (counts != 0) { + const auto avg = acc / counts; - ESP_LOGV(TAG, "Got voltage_acc=%.2f current_acc=%.2f power_acc=%.2f", this->voltage_acc_, this->current_acc_, - this->power_acc_); - ESP_LOGV(TAG, "Got voltage_counts=%d current_counts=%d power_counts=%d", this->voltage_counts_, this->current_counts_, - this->power_counts_); - ESP_LOGD(TAG, "Got voltage=%.1fV current=%.1fA power=%.1fW", voltage, current, power); + ESP_LOGV(TAG, "Got %s_acc=%.2f %s_counts=%d %s=%.1f", name, acc, name, counts, name, avg); - if (this->voltage_sensor_ != nullptr) - this->voltage_sensor_->publish_state(voltage); - if (this->current_sensor_ != nullptr) - 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_); + if (sensor != nullptr) { + sensor->publish_state(avg); + } - this->voltage_acc_ = 0.0f; - this->current_acc_ = 0.0f; - this->power_acc_ = 0.0f; - this->voltage_counts_ = 0; - this->power_counts_ = 0; - this->current_counts_ = 0; + acc = 0.0f; + counts = 0; + } + }; + + publish_state("voltage", this->voltage_sensor_, this->voltage_acc_, this->voltage_counts_); + publish_state("current", this->current_sensor_, this->current_acc_, this->current_counts_); + publish_state("power", this->power_sensor_, this->power_acc_, this->power_counts_); + + if (this->energy_total_counts_ != 0) { + ESP_LOGV(TAG, "Got energy_total=%.2f energy_total_counts=%d", this->energy_total_, this->energy_total_counts_); + + if (this->energy_sensor_ != nullptr) { + this->energy_sensor_->publish_state(this->energy_total_); + } + this->energy_total_counts_ = 0; + } } uint32_t CSE7766Component::get_24_bit_uint_(uint8_t start_index) { diff --git a/esphome/components/cse7766/cse7766.h b/esphome/components/cse7766/cse7766.h index d6062c251c..2f30eec09f 100644 --- a/esphome/components/cse7766/cse7766.h +++ b/esphome/components/cse7766/cse7766.h @@ -39,6 +39,8 @@ class CSE7766Component : public PollingComponent, public uart::UARTDevice { uint32_t voltage_counts_{0}; uint32_t current_counts_{0}; uint32_t power_counts_{0}; + // Setting this to 1 means it will always publish 0 once at startup + uint32_t energy_total_counts_{1}; }; } // namespace cse7766 diff --git a/esphome/components/custom/switch/__init__.py b/esphome/components/custom/switch/__init__.py index e0b9d7751a..5538ae6aa0 100644 --- a/esphome/components/custom/switch/__init__.py +++ b/esphome/components/custom/switch/__init__.py @@ -10,13 +10,7 @@ CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(): cv.declare_id(CustomSwitchConstructor), cv.Required(CONF_LAMBDA): cv.returning_lambda, - cv.Required(CONF_SWITCHES): cv.ensure_list( - switch.SWITCH_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(switch.Switch), - } - ) - ), + cv.Required(CONF_SWITCHES): cv.ensure_list(switch.switch_schema(switch.Switch)), } ) diff --git a/esphome/components/demo/__init__.py b/esphome/components/demo/__init__.py index 7aa742919a..19b35828a5 100644 --- a/esphome/components/demo/__init__.py +++ b/esphome/components/demo/__init__.py @@ -349,13 +349,7 @@ CONFIG_SCHEMA = cv.Schema( CONF_ICON: ICON_BLUETOOTH, }, ], - ): [ - switch.SWITCH_SCHEMA.extend(cv.COMPONENT_SCHEMA).extend( - { - cv.GenerateID(): cv.declare_id(DemoSwitch), - } - ) - ], + ): [switch.switch_schema(DemoSwitch).extend(cv.COMPONENT_SCHEMA)], cv.Optional( CONF_TEXT_SENSORS, default=[ @@ -422,9 +416,8 @@ async def to_code(config): await cg.register_component(var, conf) for conf in config[CONF_SWITCHES]: - var = cg.new_Pvariable(conf[CONF_ID]) + var = await switch.new_switch(conf) await cg.register_component(var, conf) - await switch.register_switch(var, conf) for conf in config[CONF_TEXT_SENSORS]: var = await text_sensor.new_text_sensor(conf) diff --git a/esphome/components/dht12/dht12.h b/esphome/components/dht12/dht12.h index ae4d4fd607..2a706039ba 100644 --- a/esphome/components/dht12/dht12.h +++ b/esphome/components/dht12/dht12.h @@ -20,8 +20,8 @@ class DHT12Component : public PollingComponent, public i2c::I2CDevice { protected: bool read_data_(uint8_t *data); - sensor::Sensor *temperature_sensor_; - sensor::Sensor *humidity_sensor_; + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *humidity_sensor_{nullptr}; }; } // namespace dht12 diff --git a/esphome/components/dps310/__init__.py b/esphome/components/dps310/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/dps310/dps310.cpp b/esphome/components/dps310/dps310.cpp new file mode 100644 index 0000000000..22fb52967f --- /dev/null +++ b/esphome/components/dps310/dps310.cpp @@ -0,0 +1,189 @@ +#include "dps310.h" +#include "esphome/core/log.h" +#include "esphome/core/hal.h" + +namespace esphome { +namespace dps310 { + +static const char *const TAG = "dps310"; + +void DPS310Component::setup() { + uint8_t coef_data_raw[DPS310_NUM_COEF_REGS]; + auto timer = DPS310_INIT_TIMEOUT; + uint8_t reg = 0; + + ESP_LOGCONFIG(TAG, "Setting up DPS310..."); + // first, reset the sensor + if (!this->write_byte(DPS310_REG_RESET, DPS310_CMD_RESET)) { + this->mark_failed(); + return; + } + delay(10); + // wait for the sensor and its coefficients to be ready + while (timer-- && (!(reg & DPS310_BIT_SENSOR_RDY) || !(reg & DPS310_BIT_COEF_RDY))) { + reg = this->read_byte(DPS310_REG_MEAS_CFG).value_or(0); + delay(5); + } + + if (!(reg & DPS310_BIT_SENSOR_RDY) || !(reg & DPS310_BIT_COEF_RDY)) { // the flags were not set in time + this->mark_failed(); + return; + } + // read device ID + if (!this->read_byte(DPS310_REG_PROD_REV_ID, &this->prod_rev_id_)) { + this->mark_failed(); + return; + } + // read in coefficients used to calculate the compensated pressure and temperature values + if (!this->read_bytes(DPS310_REG_COEF, coef_data_raw, DPS310_NUM_COEF_REGS)) { + this->mark_failed(); + return; + } + // read in coefficients source register, too -- we need this a few lines down + if (!this->read_byte(DPS310_REG_TMP_COEF_SRC, ®)) { + this->mark_failed(); + return; + } + // set up operational stuff + if (!this->write_byte(DPS310_REG_PRS_CFG, DPS310_VAL_PRS_CFG)) { + this->mark_failed(); + return; + } + if (!this->write_byte(DPS310_REG_TMP_CFG, DPS310_VAL_TMP_CFG | (reg & DPS310_BIT_TMP_COEF_SRC))) { + this->mark_failed(); + return; + } + if (!this->write_byte(DPS310_REG_CFG, DPS310_VAL_REG_CFG)) { + this->mark_failed(); + return; + } + if (!this->write_byte(DPS310_REG_MEAS_CFG, 0x07)) { // enable background mode + this->mark_failed(); + return; + } + + this->c0_ = // we only ever use c0/2, so just divide by 2 here to save time later + DPS310Component::twos_complement( + int16_t(((uint16_t) coef_data_raw[0] << 4) | (((uint16_t) coef_data_raw[1] >> 4) & 0x0F)), 12) / + 2; + + this->c1_ = + DPS310Component::twos_complement(int16_t((((uint16_t) coef_data_raw[1] & 0x0F) << 8) | coef_data_raw[2]), 12); + + this->c00_ = ((uint32_t) coef_data_raw[3] << 12) | ((uint32_t) coef_data_raw[4] << 4) | + (((uint32_t) coef_data_raw[5] >> 4) & 0x0F); + this->c00_ = DPS310Component::twos_complement(c00_, 20); + + this->c10_ = + (((uint32_t) coef_data_raw[5] & 0x0F) << 16) | ((uint32_t) coef_data_raw[6] << 8) | (uint32_t) coef_data_raw[7]; + this->c10_ = DPS310Component::twos_complement(c10_, 20); + + this->c01_ = int16_t(((uint16_t) coef_data_raw[8] << 8) | (uint16_t) coef_data_raw[9]); + this->c11_ = int16_t(((uint16_t) coef_data_raw[10] << 8) | (uint16_t) coef_data_raw[11]); + this->c20_ = int16_t(((uint16_t) coef_data_raw[12] << 8) | (uint16_t) coef_data_raw[13]); + this->c21_ = int16_t(((uint16_t) coef_data_raw[14] << 8) | (uint16_t) coef_data_raw[15]); + this->c30_ = int16_t(((uint16_t) coef_data_raw[16] << 8) | (uint16_t) coef_data_raw[17]); +} + +void DPS310Component::dump_config() { + ESP_LOGCONFIG(TAG, "DPS310:"); + ESP_LOGCONFIG(TAG, " Product ID: %u", this->prod_rev_id_ & 0x0F); + ESP_LOGCONFIG(TAG, " Revision ID: %u", (this->prod_rev_id_ >> 4) & 0x0F); + LOG_I2C_DEVICE(this); + if (this->is_failed()) { + ESP_LOGE(TAG, "Communication with DPS310 failed!"); + } + LOG_UPDATE_INTERVAL(this); + LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); + LOG_SENSOR(" ", "Pressure", this->pressure_sensor_); +} + +float DPS310Component::get_setup_priority() const { return setup_priority::DATA; } + +void DPS310Component::update() { + if (!this->update_in_progress_) { + this->update_in_progress_ = true; + this->read_(); + } +} + +void DPS310Component::read_() { + uint8_t reg = 0; + if (!this->read_byte(DPS310_REG_MEAS_CFG, ®)) { + this->status_set_warning(); + return; + } + + if ((!this->got_pres_) && (reg & DPS310_BIT_PRS_RDY)) { + this->read_pressure_(); + } + + if ((!this->got_temp_) && (reg & DPS310_BIT_TMP_RDY)) { + this->read_temperature_(); + } + + if (this->got_pres_ && this->got_temp_) { + this->calculate_values_(this->raw_temperature_, this->raw_pressure_); + this->got_pres_ = false; + this->got_temp_ = false; + this->update_in_progress_ = false; + this->status_clear_warning(); + } else { + auto f = std::bind(&DPS310Component::read_, this); + this->set_timeout("dps310", 10, f); + } +} + +void DPS310Component::read_pressure_() { + uint8_t bytes[3]; + if (!this->read_bytes(DPS310_REG_PRS_B2, bytes, 3)) { + this->status_set_warning(); + return; + } + this->got_pres_ = true; + this->raw_pressure_ = DPS310Component::twos_complement( + int32_t((uint32_t(bytes[0]) << 16) | (uint32_t(bytes[1]) << 8) | (uint32_t(bytes[2]))), 24); +} + +void DPS310Component::read_temperature_() { + uint8_t bytes[3]; + if (!this->read_bytes(DPS310_REG_TMP_B2, bytes, 3)) { + this->status_set_warning(); + return; + } + this->got_temp_ = true; + this->raw_temperature_ = DPS310Component::twos_complement( + int32_t((uint32_t(bytes[0]) << 16) | (uint32_t(bytes[1]) << 8) | (uint32_t(bytes[2]))), 24); +} + +// Calculations are taken from the datasheet which can be found here: +// https://www.infineon.com/dgdl/Infineon-DPS310-DataSheet-v01_02-EN.pdf?fileId=5546d462576f34750157750826c42242 +// Sections "How to Calculate Compensated Pressure Values" and "How to Calculate Compensated Temperature Values" +// Variable names below match variable names from the datasheet but lowercased +void DPS310Component::calculate_values_(int32_t raw_temperature, int32_t raw_pressure) { + const float t_raw_sc = (float) raw_temperature / DPS310_SCALE_FACTOR; + const float p_raw_sc = (float) raw_pressure / DPS310_SCALE_FACTOR; + + const float temperature = t_raw_sc * this->c1_ + this->c0_; // c0/2 done earlier! + + const float pressure = (this->c00_ + p_raw_sc * (this->c10_ + p_raw_sc * (this->c20_ + p_raw_sc * this->c30_)) + + t_raw_sc * this->c01_ + t_raw_sc * p_raw_sc * (this->c11_ + p_raw_sc * this->c21_)) / + 100; // divide by 100 for hPa + + if (this->temperature_sensor_ != nullptr) { + this->temperature_sensor_->publish_state(temperature); + } + if (this->pressure_sensor_ != nullptr) { + this->pressure_sensor_->publish_state(pressure); + } +} + +int32_t DPS310Component::twos_complement(int32_t val, uint8_t bits) { + if (val & ((uint32_t) 1 << (bits - 1))) { + val -= (uint32_t) 1 << bits; + } + return val; +} + +} // namespace dps310 +} // namespace esphome diff --git a/esphome/components/dps310/dps310.h b/esphome/components/dps310/dps310.h new file mode 100644 index 0000000000..50e7d93c8a --- /dev/null +++ b/esphome/components/dps310/dps310.h @@ -0,0 +1,65 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace dps310 { + +static const uint8_t DPS310_REG_PRS_B2 = 0x00; // Highest byte of pressure data +static const uint8_t DPS310_REG_TMP_B2 = 0x03; // Highest byte of temperature data +static const uint8_t DPS310_REG_PRS_CFG = 0x06; // Pressure configuration +static const uint8_t DPS310_REG_TMP_CFG = 0x07; // Temperature configuration +static const uint8_t DPS310_REG_MEAS_CFG = 0x08; // Sensor configuration +static const uint8_t DPS310_REG_CFG = 0x09; // Interrupt/FIFO configuration +static const uint8_t DPS310_REG_RESET = 0x0C; // Soft reset +static const uint8_t DPS310_REG_PROD_REV_ID = 0x0D; // Register that contains the part ID +static const uint8_t DPS310_REG_COEF = 0x10; // Top of calibration coefficient data space +static const uint8_t DPS310_REG_TMP_COEF_SRC = 0x28; // Temperature calibration src + +static const uint8_t DPS310_BIT_PRS_RDY = 0x10; // Pressure measurement is ready +static const uint8_t DPS310_BIT_TMP_RDY = 0x20; // Temperature measurement is ready +static const uint8_t DPS310_BIT_SENSOR_RDY = 0x40; // Sensor initialization complete when bit is set +static const uint8_t DPS310_BIT_COEF_RDY = 0x80; // Coefficients are available when bit is set +static const uint8_t DPS310_BIT_TMP_COEF_SRC = 0x80; // Temperature measurement source (0 = ASIC, 1 = MEMS element) +static const uint8_t DPS310_BIT_REQ_PRES = 0x01; // Set this bit to request pressure reading +static const uint8_t DPS310_BIT_REQ_TEMP = 0x02; // Set this bit to request temperature reading + +static const uint8_t DPS310_CMD_RESET = 0x89; // What to write to reset the device + +static const uint8_t DPS310_VAL_PRS_CFG = 0x01; // Value written to DPS310_REG_PRS_CFG at startup +static const uint8_t DPS310_VAL_TMP_CFG = 0x01; // Value written to DPS310_REG_TMP_CFG at startup +static const uint8_t DPS310_VAL_REG_CFG = 0x00; // Value written to DPS310_REG_CFG at startup + +static const uint8_t DPS310_INIT_TIMEOUT = 20; // How long to wait for DPS310 start-up to complete +static const uint8_t DPS310_NUM_COEF_REGS = 18; // Number of coefficients we need to read from the device +static const int32_t DPS310_SCALE_FACTOR = 1572864; // Measurement compensation scale factor + +class DPS310Component : public PollingComponent, public i2c::I2CDevice { + public: + void setup() override; + void dump_config() override; + float get_setup_priority() const override; + void update() override; + + void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; } + void set_pressure_sensor(sensor::Sensor *pressure_sensor) { pressure_sensor_ = pressure_sensor; } + + protected: + void read_(); + void read_pressure_(); + void read_temperature_(); + void calculate_values_(int32_t raw_temperature, int32_t raw_pressure); + static int32_t twos_complement(int32_t val, uint8_t bits); + + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *pressure_sensor_{nullptr}; + int32_t raw_pressure_, raw_temperature_, c00_, c10_; + int16_t c0_, c1_, c01_, c11_, c20_, c21_, c30_; + uint8_t prod_rev_id_; + bool got_pres_, got_temp_, update_in_progress_; +}; + +} // namespace dps310 +} // namespace esphome diff --git a/esphome/components/dps310/sensor.py b/esphome/components/dps310/sensor.py new file mode 100644 index 0000000000..742c873d9e --- /dev/null +++ b/esphome/components/dps310/sensor.py @@ -0,0 +1,62 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + CONF_ID, + CONF_PRESSURE, + CONF_TEMPERATURE, + DEVICE_CLASS_PRESSURE, + DEVICE_CLASS_TEMPERATURE, + STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, + ICON_GAUGE, + ICON_THERMOMETER, + UNIT_HECTOPASCAL, +) + +CODEOWNERS = ["@kbx81"] + +DEPENDENCIES = ["i2c"] + +dps310_ns = cg.esphome_ns.namespace("dps310") +DPS310Component = dps310_ns.class_( + "DPS310Component", cg.PollingComponent, i2c.I2CDevice +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(DPS310Component), + cv.Required(CONF_TEMPERATURE): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_THERMOMETER, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Required(CONF_PRESSURE): sensor.sensor_schema( + unit_of_measurement=UNIT_HECTOPASCAL, + icon=ICON_GAUGE, + accuracy_decimals=1, + device_class=DEVICE_CLASS_PRESSURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x77)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + + if CONF_TEMPERATURE in config: + sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) + cg.add(var.set_temperature_sensor(sens)) + + if CONF_PRESSURE in config: + sens = await sensor.new_sensor(config[CONF_PRESSURE]) + cg.add(var.set_pressure_sensor(sens)) diff --git a/esphome/components/ens210/ens210.h b/esphome/components/ens210/ens210.h index 342be04799..0fb6ff634d 100644 --- a/esphome/components/ens210/ens210.h +++ b/esphome/components/ens210/ens210.h @@ -31,8 +31,8 @@ class ENS210Component : public PollingComponent, public i2c::I2CDevice { bool set_low_power_(bool enable); void extract_measurement_(uint32_t val, int *data, int *status); - sensor::Sensor *temperature_sensor_; - sensor::Sensor *humidity_sensor_; + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *humidity_sensor_{nullptr}; }; } // namespace ens210 diff --git a/esphome/components/esp32/preferences.cpp b/esphome/components/esp32/preferences.cpp index d070a20d82..6a6305cf87 100644 --- a/esphome/components/esp32/preferences.cpp +++ b/esphome/components/esp32/preferences.cpp @@ -141,7 +141,7 @@ class ESP32Preferences : public ESPPreferences { ESP_LOGD(TAG, "Saving %d preferences to flash: %d cached, %d written, %d failed", cached + written + failed, cached, written, failed); if (failed > 0) { - ESP_LOGD(TAG, "Error saving %d preferences to flash. Last error=%s for key=%s", failed, esp_err_to_name(last_err), + ESP_LOGE(TAG, "Error saving %d preferences to flash. Last error=%s for key=%s", failed, esp_err_to_name(last_err), last_key.c_str()); } @@ -170,6 +170,17 @@ class ESP32Preferences : public ESPPreferences { } return to_save.data != stored_data.data; } + + bool reset() override { + ESP_LOGD(TAG, "Cleaning up preferences in flash..."); + s_pending_save.clear(); + + nvs_flash_deinit(); + nvs_flash_erase(); + // Make the handle invalid to prevent any saves until restart + nvs_handle = 0; + return true; + } }; void setup_preferences() { diff --git a/esphome/components/esp32_ble_tracker/__init__.py b/esphome/components/esp32_ble_tracker/__init__.py index e647b74a8f..6d7868d4c5 100644 --- a/esphome/components/esp32_ble_tracker/__init__.py +++ b/esphome/components/esp32_ble_tracker/__init__.py @@ -23,6 +23,8 @@ CONF_ESP32_BLE_ID = "esp32_ble_id" CONF_SCAN_PARAMETERS = "scan_parameters" CONF_WINDOW = "window" CONF_ACTIVE = "active" +CONF_CONTINUOUS = "continuous" +CONF_ON_SCAN_END = "on_scan_end" esp32_ble_tracker_ns = cg.esphome_ns.namespace("esp32_ble_tracker") ESP32BLETracker = esp32_ble_tracker_ns.class_("ESP32BLETracker", cg.Component) ESPBTClient = esp32_ble_tracker_ns.class_("ESPBTClient") @@ -42,6 +44,16 @@ BLEManufacturerDataAdvertiseTrigger = esp32_ble_tracker_ns.class_( "BLEManufacturerDataAdvertiseTrigger", automation.Trigger.template(adv_data_t_const_ref), ) +BLEEndOfScanTrigger = esp32_ble_tracker_ns.class_( + "BLEEndOfScanTrigger", automation.Trigger.template() +) +# Actions +ESP32BLEStartScanAction = esp32_ble_tracker_ns.class_( + "ESP32BLEStartScanAction", automation.Action +) +ESP32BLEStopScanAction = esp32_ble_tracker_ns.class_( + "ESP32BLEStopScanAction", automation.Action +) def validate_scan_parameters(config): @@ -138,6 +150,7 @@ CONFIG_SCHEMA = cv.Schema( CONF_WINDOW, default="30ms" ): cv.positive_time_period_milliseconds, cv.Optional(CONF_ACTIVE, default=True): cv.boolean, + cv.Optional(CONF_CONTINUOUS, default=True): cv.boolean, } ), validate_scan_parameters, @@ -168,6 +181,9 @@ CONFIG_SCHEMA = cv.Schema( cv.Required(CONF_MANUFACTURER_ID): bt_uuid, } ), + cv.Optional(CONF_ON_SCAN_END): automation.validate_automation( + {cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(BLEEndOfScanTrigger)} + ), } ).extend(cv.COMPONENT_SCHEMA) @@ -186,6 +202,7 @@ async def to_code(config): cg.add(var.set_scan_interval(int(params[CONF_INTERVAL].total_milliseconds / 0.625))) cg.add(var.set_scan_window(int(params[CONF_WINDOW].total_milliseconds / 0.625))) cg.add(var.set_scan_active(params[CONF_ACTIVE])) + cg.add(var.set_scan_continuous(params[CONF_CONTINUOUS])) for conf in config.get(CONF_ON_BLE_ADVERTISE, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) if CONF_MAC_ADDRESS in conf: @@ -215,10 +232,59 @@ async def to_code(config): if CONF_MAC_ADDRESS in conf: cg.add(trigger.set_address(conf[CONF_MAC_ADDRESS].as_hex)) await automation.build_automation(trigger, [(adv_data_t_const_ref, "x")], conf) + for conf in config.get(CONF_ON_SCAN_END, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) if CORE.using_esp_idf: add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True) + cg.add_define("USE_OTA_STATE_CALLBACK") # To be notified when an OTA update starts + + +ESP32_BLE_START_SCAN_ACTION_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.use_id(ESP32BLETracker), + cv.Optional(CONF_CONTINUOUS, default=False): cv.templatable(cv.boolean), + } +) + + +@automation.register_action( + "esp32_ble_tracker.start_scan", + ESP32BLEStartScanAction, + ESP32_BLE_START_SCAN_ACTION_SCHEMA, +) +async def esp32_ble_tracker_start_scan_action_to_code( + config, action_id, template_arg, args +): + paren = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, paren) + cg.add(var.set_continuous(config[CONF_CONTINUOUS])) + return var + + +ESP32_BLE_STOP_SCAN_ACTION_SCHEMA = automation.maybe_simple_id( + cv.Schema( + { + cv.GenerateID(): cv.use_id(ESP32BLETracker), + } + ) +) + + +@automation.register_action( + "esp32_ble_tracker.stop_scan", + ESP32BLEStopScanAction, + ESP32_BLE_STOP_SCAN_ACTION_SCHEMA, +) +async def esp32_ble_tracker_stop_scan_action_to_code( + config, action_id, template_arg, args +): + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + return var + async def register_ble_device(var, config): paren = await cg.get_variable(config[CONF_ESP32_BLE_ID]) diff --git a/esphome/components/esp32_ble_tracker/automation.h b/esphome/components/esp32_ble_tracker/automation.h index 3505e9c26d..6131d6ddf7 100644 --- a/esphome/components/esp32_ble_tracker/automation.h +++ b/esphome/components/esp32_ble_tracker/automation.h @@ -76,6 +76,32 @@ class BLEManufacturerDataAdvertiseTrigger : public Trigger, ESPBTUUID uuid_; }; +class BLEEndOfScanTrigger : public Trigger<>, public ESPBTDeviceListener { + public: + explicit BLEEndOfScanTrigger(ESP32BLETracker *parent) { parent->register_listener(this); } + + bool parse_device(const ESPBTDevice &device) override { return false; } + void on_scan_end() override { this->trigger(); } +}; + +template class ESP32BLEStartScanAction : public Action { + public: + ESP32BLEStartScanAction(ESP32BLETracker *parent) : parent_(parent) {} + TEMPLATABLE_VALUE(bool, continuous) + void play(Ts... x) override { + this->parent_->set_scan_continuous(this->continuous_.value(x...)); + this->parent_->start_scan(); + } + + protected: + ESP32BLETracker *parent_; +}; + +template class ESP32BLEStopScanAction : public Action, public Parented { + public: + void play(Ts... x) override { this->parent_->stop_scan(); } +}; + } // namespace esp32_ble_tracker } // namespace esphome diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index 82945dc771..f7e51a8ab3 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -1,10 +1,11 @@ #ifdef USE_ESP32 #include "esp32_ble_tracker.h" -#include "esphome/core/log.h" #include "esphome/core/application.h" -#include "esphome/core/helpers.h" +#include "esphome/core/defines.h" #include "esphome/core/hal.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" #include #include @@ -15,6 +16,10 @@ #include #include +#ifdef USE_OTA +#include "esphome/components/ota/ota_component.h" +#endif + #ifdef USE_ARDUINO #include #endif @@ -46,13 +51,23 @@ void ESP32BLETracker::setup() { global_esp32_ble_tracker = this; this->scan_result_lock_ = xSemaphoreCreateMutex(); this->scan_end_lock_ = xSemaphoreCreateMutex(); - + this->scanner_idle_ = true; if (!ESP32BLETracker::ble_setup()) { this->mark_failed(); return; } - global_esp32_ble_tracker->start_scan_(true); +#ifdef USE_OTA + ota::global_ota_component->add_on_state_callback([this](ota::OTAState state, float progress, uint8_t error) { + if (state == ota::OTA_STARTED) { + this->stop_scan(); + } + }); +#endif + + if (this->scan_continuous_) { + this->start_scan_(true); + } } void ESP32BLETracker::loop() { @@ -68,14 +83,25 @@ void ESP32BLETracker::loop() { ble_event = this->ble_events_.pop(); } + if (this->scanner_idle_) { + return; + } + bool connecting = false; for (auto *client : this->clients_) { if (client->state() == ClientState::CONNECTING || client->state() == ClientState::DISCOVERED) connecting = true; } + if (!connecting && xSemaphoreTake(this->scan_end_lock_, 0L)) { xSemaphoreGive(this->scan_end_lock_); - global_esp32_ble_tracker->start_scan_(false); + if (this->scan_continuous_) { + this->start_scan_(false); + } else if (xSemaphoreTake(this->scan_end_lock_, 0L) && !this->scanner_idle_) { + xSemaphoreGive(this->scan_end_lock_); + this->end_of_scan_(); + return; + } } if (xSemaphoreTake(this->scan_result_lock_, 5L / portTICK_PERIOD_MS)) { @@ -134,6 +160,22 @@ void ESP32BLETracker::loop() { } } +void ESP32BLETracker::start_scan() { + if (xSemaphoreTake(this->scan_end_lock_, 0L)) { + xSemaphoreGive(this->scan_end_lock_); + this->start_scan_(true); + } else { + ESP_LOGW(TAG, "Scan requested when a scan is already in progress. Ignoring."); + } +} + +void ESP32BLETracker::stop_scan() { + ESP_LOGD(TAG, "Stopping scan."); + this->scan_continuous_ = false; + esp_ble_gap_stop_scanning(); + this->cancel_timeout("scan"); +} + bool ESP32BLETracker::ble_setup() { // Initialize non-volatile storage for the bluetooth controller esp_err_t err = nvs_flash_init(); @@ -225,6 +267,7 @@ void ESP32BLETracker::start_scan_(bool first) { listener->on_scan_end(); } this->already_discovered_.clear(); + this->scanner_idle_ = false; this->scan_params_.scan_type = this->scan_active_ ? BLE_SCAN_TYPE_ACTIVE : BLE_SCAN_TYPE_PASSIVE; this->scan_params_.own_addr_type = BLE_ADDR_TYPE_PUBLIC; this->scan_params_.scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL; @@ -240,6 +283,22 @@ void ESP32BLETracker::start_scan_(bool first) { }); } +void ESP32BLETracker::end_of_scan_() { + if (!xSemaphoreTake(this->scan_end_lock_, 0L)) { + ESP_LOGW(TAG, "Cannot clean up end of scan!"); + return; + } + + ESP_LOGD(TAG, "End of scan."); + this->scanner_idle_ = true; + this->already_discovered_.clear(); + xSemaphoreGive(this->scan_end_lock_); + this->cancel_timeout("scan"); + + for (auto *listener : this->listeners_) + listener->on_scan_end(); +} + void ESP32BLETracker::register_client(ESPBTClient *client) { client->app_id = ++this->app_id_; this->clients_.push_back(client); @@ -253,21 +312,21 @@ void ESP32BLETracker::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_ga void ESP32BLETracker::real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { switch (event) { case ESP_GAP_BLE_SCAN_RESULT_EVT: - global_esp32_ble_tracker->gap_scan_result_(param->scan_rst); + this->gap_scan_result_(param->scan_rst); break; case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: - global_esp32_ble_tracker->gap_scan_set_param_complete_(param->scan_param_cmpl); + this->gap_scan_set_param_complete_(param->scan_param_cmpl); break; case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT: - global_esp32_ble_tracker->gap_scan_start_complete_(param->scan_start_cmpl); + this->gap_scan_start_complete_(param->scan_start_cmpl); break; case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT: - global_esp32_ble_tracker->gap_scan_stop_complete_(param->scan_stop_cmpl); + this->gap_scan_stop_complete_(param->scan_stop_cmpl); break; default: break; } - for (auto *client : global_esp32_ble_tracker->clients_) { + for (auto *client : this->clients_) { client->gap_event_handler(event, param); } } @@ -305,7 +364,7 @@ void ESP32BLETracker::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_i void ESP32BLETracker::real_gattc_event_handler_(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) { - for (auto *client : global_esp32_ble_tracker->clients_) { + for (auto *client : this->clients_) { client->gattc_event_handler(event, gattc_if, param); } } @@ -719,7 +778,9 @@ void ESP32BLETracker::dump_config() { ESP_LOGCONFIG(TAG, " Scan Interval: %.1f ms", this->scan_interval_ * 0.625f); ESP_LOGCONFIG(TAG, " Scan Window: %.1f ms", this->scan_window_ * 0.625f); ESP_LOGCONFIG(TAG, " Scan Type: %s", this->scan_active_ ? "ACTIVE" : "PASSIVE"); + ESP_LOGCONFIG(TAG, " Continuous Scanning: %s", this->scan_continuous_ ? "True" : "False"); } + void ESP32BLETracker::print_bt_device_info(const ESPBTDevice &device) { const uint64_t address = device.address_uint64(); for (auto &disc : this->already_discovered_) { diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h index 62fff30a20..29d0c81542 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/automation.h" #include "esphome/core/helpers.h" #include "queue.h" @@ -171,6 +172,7 @@ class ESP32BLETracker : public Component { void set_scan_interval(uint32_t scan_interval) { scan_interval_ = scan_interval; } void set_scan_window(uint32_t scan_window) { scan_window_ = scan_window; } void set_scan_active(bool scan_active) { scan_active_ = scan_active; } + void set_scan_continuous(bool scan_continuous) { scan_continuous_ = scan_continuous; } /// Setup the FreeRTOS task and the Bluetooth stack. void setup() override; @@ -188,11 +190,16 @@ class ESP32BLETracker : public Component { void print_bt_device_info(const ESPBTDevice &device); + void start_scan(); + void stop_scan(); + protected: /// The FreeRTOS task managing the bluetooth interface. static bool ble_setup(); /// Start a single scan by setting up the parameters and doing some esp-idf calls. void start_scan_(bool first); + /// Called when a scan ends + void end_of_scan_(); /// Callback that will handle all GAP events and redistribute them to other callbacks. static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); void real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); @@ -221,7 +228,9 @@ class ESP32BLETracker : public Component { uint32_t scan_duration_; uint32_t scan_interval_; uint32_t scan_window_; + bool scan_continuous_; bool scan_active_; + bool scanner_idle_; SemaphoreHandle_t scan_result_lock_; SemaphoreHandle_t scan_end_lock_; size_t scan_result_index_{0}; diff --git a/esphome/components/esp8266/preferences.cpp b/esphome/components/esp8266/preferences.cpp index 0e42cea576..1bd20f16ae 100644 --- a/esphome/components/esp8266/preferences.cpp +++ b/esphome/components/esp8266/preferences.cpp @@ -243,17 +243,34 @@ class ESP8266Preferences : public ESPPreferences { } } if (erase_res != SPI_FLASH_RESULT_OK) { - ESP_LOGV(TAG, "Erase ESP8266 flash failed!"); + ESP_LOGE(TAG, "Erase ESP8266 flash failed!"); return false; } if (write_res != SPI_FLASH_RESULT_OK) { - ESP_LOGV(TAG, "Write ESP8266 flash failed!"); + ESP_LOGE(TAG, "Write ESP8266 flash failed!"); return false; } s_flash_dirty = false; return true; } + + bool reset() override { + ESP_LOGD(TAG, "Cleaning up preferences in flash..."); + SpiFlashOpResult erase_res; + { + InterruptLock lock; + erase_res = spi_flash_erase_sector(get_esp8266_flash_sector()); + } + if (erase_res != SPI_FLASH_RESULT_OK) { + ESP_LOGE(TAG, "Erase ESP8266 flash failed!"); + return false; + } + + // Protect flash from writing till restart + s_prevent_write = true; + return true; + } }; void setup_preferences() { diff --git a/esphome/components/ethernet/__init__.py b/esphome/components/ethernet/__init__.py index bbf64a3cd1..696cc62e75 100644 --- a/esphome/components/ethernet/__init__.py +++ b/esphome/components/ethernet/__init__.py @@ -31,6 +31,7 @@ EthernetType = ethernet_ns.enum("EthernetType") ETHERNET_TYPES = { "LAN8720": EthernetType.ETHERNET_TYPE_LAN8720, "TLK110": EthernetType.ETHERNET_TYPE_TLK110, + "IP101": EthernetType.ETHERNET_TYPE_IP101, } eth_clock_mode_t = cg.global_ns.enum("eth_clock_mode_t") diff --git a/esphome/components/ethernet/ethernet_component.cpp b/esphome/components/ethernet/ethernet_component.cpp index 384a31ed2f..d768964017 100644 --- a/esphome/components/ethernet/ethernet_component.cpp +++ b/esphome/components/ethernet/ethernet_component.cpp @@ -6,6 +6,7 @@ #ifdef USE_ESP32_FRAMEWORK_ARDUINO #include +#include #include #include @@ -33,6 +34,7 @@ EthernetComponent *global_eth_component; // NOLINT(cppcoreguidelines-avoid-non- } EthernetComponent::EthernetComponent() { global_eth_component = this; } + void EthernetComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up Ethernet..."); @@ -52,6 +54,10 @@ void EthernetComponent::setup() { memcpy(&this->eth_config_, &phy_tlk110_default_ethernet_config, sizeof(eth_config_t)); break; } + case ETHERNET_TYPE_IP101: { + memcpy(&this->eth_config_, &phy_ip101_default_ethernet_config, sizeof(eth_config_t)); + break; + } default: { this->mark_failed(); return; @@ -76,6 +82,7 @@ void EthernetComponent::setup() { err = esp_eth_enable(); ESPHL_ERROR_CHECK(err, "ETH enable error"); } + void EthernetComponent::loop() { const uint32_t now = millis(); @@ -115,16 +122,39 @@ void EthernetComponent::loop() { break; } } + void EthernetComponent::dump_config() { + std::string eth_type; + switch (this->type_) { + case ETHERNET_TYPE_LAN8720: + eth_type = "LAN8720"; + break; + + case ETHERNET_TYPE_TLK110: + eth_type = "TLK110"; + break; + + case ETHERNET_TYPE_IP101: + eth_type = "IP101"; + break; + + default: + eth_type = "Unknown"; + break; + } + ESP_LOGCONFIG(TAG, "Ethernet:"); this->dump_connect_params_(); LOG_PIN(" Power Pin: ", this->power_pin_); ESP_LOGCONFIG(TAG, " MDC Pin: %u", this->mdc_pin_); ESP_LOGCONFIG(TAG, " MDIO Pin: %u", this->mdio_pin_); - ESP_LOGCONFIG(TAG, " Type: %s", this->type_ == ETHERNET_TYPE_LAN8720 ? "LAN8720" : "TLK110"); + ESP_LOGCONFIG(TAG, " Type: %s", eth_type.c_str()); } + float EthernetComponent::get_setup_priority() const { return setup_priority::WIFI; } + bool EthernetComponent::can_proceed() { return this->is_connected(); } + network::IPAddress EthernetComponent::get_ip_address() { tcpip_adapter_ip_info_t ip; tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_ETH, &ip); @@ -213,17 +243,21 @@ void EthernetComponent::start_connect_() { this->connect_begin_ = millis(); this->status_set_warning(); } + void EthernetComponent::eth_phy_config_gpio() { phy_rmii_configure_data_interface_pins(); phy_rmii_smi_configure_pins(global_eth_component->mdc_pin_, global_eth_component->mdio_pin_); } + void EthernetComponent::eth_phy_power_enable(bool enable) { global_eth_component->power_pin_->digital_write(enable); // power up takes some time, datasheet says max 300µs delay(1); global_eth_component->orig_power_enable_fun_(enable); } + bool EthernetComponent::is_connected() { return this->state_ == EthernetComponentState::CONNECTED; } + void EthernetComponent::dump_connect_params_() { tcpip_adapter_ip_info_t ip; tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_ETH, &ip); @@ -250,6 +284,7 @@ void EthernetComponent::dump_connect_params_() { ESP_LOGCONFIG(TAG, " Link Up: %s", YESNO(this->eth_config_.phy_check_link())); ESP_LOGCONFIG(TAG, " Link Speed: %u", this->eth_config_.phy_get_speed_mode() ? 100 : 10); } + void EthernetComponent::set_phy_addr(uint8_t phy_addr) { this->phy_addr_ = phy_addr; } void EthernetComponent::set_power_pin(GPIOPin *power_pin) { this->power_pin_ = power_pin; } void EthernetComponent::set_mdc_pin(uint8_t mdc_pin) { this->mdc_pin_ = mdc_pin; } @@ -257,12 +292,14 @@ void EthernetComponent::set_mdio_pin(uint8_t mdio_pin) { this->mdio_pin_ = mdio_ void EthernetComponent::set_type(EthernetType type) { this->type_ = type; } void EthernetComponent::set_clk_mode(eth_clock_mode_t clk_mode) { this->clk_mode_ = clk_mode; } void EthernetComponent::set_manual_ip(const ManualIP &manual_ip) { this->manual_ip_ = manual_ip; } + std::string EthernetComponent::get_use_address() const { if (this->use_address_.empty()) { return App.get_name() + ".local"; } return this->use_address_; } + void EthernetComponent::set_use_address(const std::string &use_address) { this->use_address_ = use_address; } } // namespace ethernet diff --git a/esphome/components/ethernet/ethernet_component.h b/esphome/components/ethernet/ethernet_component.h index abe1c62030..0924405521 100644 --- a/esphome/components/ethernet/ethernet_component.h +++ b/esphome/components/ethernet/ethernet_component.h @@ -17,6 +17,7 @@ namespace ethernet { enum EthernetType { ETHERNET_TYPE_LAN8720 = 0, ETHERNET_TYPE_TLK110, + ETHERNET_TYPE_IP101, }; struct ManualIP { diff --git a/esphome/components/factory_reset/__init__.py b/esphome/components/factory_reset/__init__.py new file mode 100644 index 0000000000..f1bcfd8c55 --- /dev/null +++ b/esphome/components/factory_reset/__init__.py @@ -0,0 +1,5 @@ +import esphome.codegen as cg + +CODEOWNERS = ["@anatoly-savchenkov"] + +factory_reset_ns = cg.esphome_ns.namespace("factory_reset") diff --git a/esphome/components/factory_reset/button/__init__.py b/esphome/components/factory_reset/button/__init__.py new file mode 100644 index 0000000000..d5beac34b5 --- /dev/null +++ b/esphome/components/factory_reset/button/__init__.py @@ -0,0 +1,30 @@ +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, + ICON_RESTART_ALERT, +) +from .. import factory_reset_ns + +FactoryResetButton = factory_reset_ns.class_( + "FactoryResetButton", button.Button, cg.Component +) + +CONFIG_SCHEMA = ( + button.button_schema( + device_class=DEVICE_CLASS_RESTART, + entity_category=ENTITY_CATEGORY_CONFIG, + icon=ICON_RESTART_ALERT, + ) + .extend({cv.GenerateID(): cv.declare_id(FactoryResetButton)}) + .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/factory_reset/button/factory_reset_button.cpp b/esphome/components/factory_reset/button/factory_reset_button.cpp new file mode 100644 index 0000000000..9354a3363e --- /dev/null +++ b/esphome/components/factory_reset/button/factory_reset_button.cpp @@ -0,0 +1,21 @@ +#include "factory_reset_button.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" +#include "esphome/core/application.h" + +namespace esphome { +namespace factory_reset { + +static const char *const TAG = "factory_reset.button"; + +void FactoryResetButton::dump_config() { LOG_BUTTON("", "Factory Reset Button", this); } +void FactoryResetButton::press_action() { + ESP_LOGI(TAG, "Resetting to factory defaults..."); + // Let MQTT settle a bit + delay(100); // NOLINT + global_preferences->reset(); + App.safe_reboot(); +} + +} // namespace factory_reset +} // namespace esphome diff --git a/esphome/components/factory_reset/button/factory_reset_button.h b/esphome/components/factory_reset/button/factory_reset_button.h new file mode 100644 index 0000000000..9996a860d9 --- /dev/null +++ b/esphome/components/factory_reset/button/factory_reset_button.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/button/button.h" + +namespace esphome { +namespace factory_reset { + +class FactoryResetButton : public button::Button, public Component { + public: + void dump_config() override; + + protected: + void press_action() override; +}; + +} // namespace factory_reset +} // namespace esphome diff --git a/esphome/components/factory_reset/switch/__init__.py b/esphome/components/factory_reset/switch/__init__.py new file mode 100644 index 0000000000..3cc19a35a3 --- /dev/null +++ b/esphome/components/factory_reset/switch/__init__.py @@ -0,0 +1,35 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import switch +from esphome.const import ( + CONF_ENTITY_CATEGORY, + CONF_ID, + CONF_INVERTED, + CONF_ICON, + ENTITY_CATEGORY_CONFIG, + ICON_RESTART_ALERT, +) +from .. import factory_reset_ns + +FactoryResetSwitch = factory_reset_ns.class_( + "FactoryResetSwitch", switch.Switch, cg.Component +) + +CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(FactoryResetSwitch), + cv.Optional(CONF_INVERTED): cv.invalid( + "Factory Reset switches do not support inverted mode!" + ), + cv.Optional(CONF_ICON, default=ICON_RESTART_ALERT): cv.icon, + cv.Optional( + CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_CONFIG + ): cv.entity_category, + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await switch.register_switch(var, config) diff --git a/esphome/components/factory_reset/switch/factory_reset_switch.cpp b/esphome/components/factory_reset/switch/factory_reset_switch.cpp new file mode 100644 index 0000000000..7bc8676736 --- /dev/null +++ b/esphome/components/factory_reset/switch/factory_reset_switch.cpp @@ -0,0 +1,26 @@ +#include "factory_reset_switch.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" +#include "esphome/core/application.h" + +namespace esphome { +namespace factory_reset { + +static const char *const TAG = "factory_reset.switch"; + +void FactoryResetSwitch::dump_config() { LOG_SWITCH("", "Factory Reset Switch", this); } +void FactoryResetSwitch::write_state(bool state) { + // Acknowledge + this->publish_state(false); + + if (state) { + ESP_LOGI(TAG, "Resetting to factory defaults..."); + // Let MQTT settle a bit + delay(100); // NOLINT + global_preferences->reset(); + App.safe_reboot(); + } +} + +} // namespace factory_reset +} // namespace esphome diff --git a/esphome/components/factory_reset/switch/factory_reset_switch.h b/esphome/components/factory_reset/switch/factory_reset_switch.h new file mode 100644 index 0000000000..2c914ea76d --- /dev/null +++ b/esphome/components/factory_reset/switch/factory_reset_switch.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/switch/switch.h" + +namespace esphome { +namespace factory_reset { + +class FactoryResetSwitch : public switch_::Switch, public Component { + public: + void dump_config() override; + + protected: + void write_state(bool state) override; +}; + +} // namespace factory_reset +} // namespace esphome diff --git a/esphome/components/font/__init__.py b/esphome/components/font/__init__.py index 9317b2ec94..aa165ebaa5 100644 --- a/esphome/components/font/__init__.py +++ b/esphome/components/font/__init__.py @@ -1,6 +1,7 @@ import functools from pathlib import Path import hashlib +import os import re import requests @@ -9,6 +10,7 @@ from esphome import core from esphome.components import display import esphome.config_validation as cv import esphome.codegen as cg +from esphome.helpers import copy_file_if_changed from esphome.const import ( CONF_FAMILY, CONF_FILE, @@ -88,21 +90,33 @@ def validate_truetype_file(value): return cv.file_(value) -def _compute_gfonts_local_path(value) -> Path: - name = f"{value[CONF_FAMILY]}@{value[CONF_WEIGHT]}@{value[CONF_ITALIC]}@v1" +def _compute_local_font_dir(name) -> Path: base_dir = Path(CORE.config_dir) / ".esphome" / DOMAIN h = hashlib.new("sha256") h.update(name.encode()) - return base_dir / h.hexdigest()[:8] / "font.ttf" + return base_dir / h.hexdigest()[:8] + + +def _compute_gfonts_local_path(value) -> Path: + name = f"{value[CONF_FAMILY]}@{value[CONF_WEIGHT]}@{value[CONF_ITALIC]}@v1" + return _compute_local_font_dir(name) / "font.ttf" TYPE_LOCAL = "local" +TYPE_LOCAL_BITMAP = "local_bitmap" TYPE_GFONTS = "gfonts" LOCAL_SCHEMA = cv.Schema( { cv.Required(CONF_PATH): validate_truetype_file, } ) + +LOCAL_BITMAP_SCHEMA = cv.Schema( + { + cv.Required(CONF_PATH): cv.file_, + } +) + CONF_ITALIC = "italic" FONT_WEIGHTS = { "thin": 100, @@ -132,7 +146,7 @@ def download_gfonts(value): if path.is_file(): return value try: - req = requests.get(url) + req = requests.get(url, timeout=30) req.raise_for_status() except requests.exceptions.RequestException as e: raise cv.Invalid( @@ -148,7 +162,7 @@ def download_gfonts(value): ttf_url = match.group(1) try: - req = requests.get(ttf_url) + req = requests.get(ttf_url, timeout=30) req.raise_for_status() except requests.exceptions.RequestException as e: raise cv.Invalid(f"Could not download ttf file for {name} ({ttf_url}): {e}") @@ -185,6 +199,15 @@ def validate_file_shorthand(value): if weight is not None: data[CONF_WEIGHT] = weight[1:] return FILE_SCHEMA(data) + + if value.endswith(".pcf") or value.endswith(".bdf"): + return FILE_SCHEMA( + { + CONF_TYPE: TYPE_LOCAL_BITMAP, + CONF_PATH: value, + } + ) + return FILE_SCHEMA( { CONF_TYPE: TYPE_LOCAL, @@ -197,6 +220,7 @@ TYPED_FILE_SCHEMA = cv.typed_schema( { TYPE_LOCAL: LOCAL_SCHEMA, TYPE_GFONTS: GFONTS_SCHEMA, + TYPE_LOCAL_BITMAP: LOCAL_BITMAP_SCHEMA, } ) @@ -228,27 +252,121 @@ FONT_SCHEMA = cv.Schema( CONFIG_SCHEMA = cv.All(validate_pillow_installed, FONT_SCHEMA) +# PIL doesn't provide a consistent interface for both TrueType and bitmap +# fonts. So, we use our own wrappers to give us the consistency that we need. -async def to_code(config): + +class TrueTypeFontWrapper: + def __init__(self, font): + self.font = font + + def getoffset(self, glyph): + _, (offset_x, offset_y) = self.font.font.getsize(glyph) + return offset_x, offset_y + + def getmask(self, glyph, **kwargs): + return self.font.getmask(glyph, **kwargs) + + def getmetrics(self, glyphs): + return self.font.getmetrics() + + +class BitmapFontWrapper: + def __init__(self, font): + self.font = font + self.max_height = 0 + + def getoffset(self, glyph): + return 0, 0 + + def getmask(self, glyph, **kwargs): + return self.font.getmask(glyph, **kwargs) + + def getmetrics(self, glyphs): + max_height = 0 + for glyph in glyphs: + mask = self.getmask(glyph, mode="1") + _, height = mask.size + if height > max_height: + max_height = height + return (max_height, 0) + + +def convert_bitmap_to_pillow_font(filepath): + from PIL import PcfFontFile, BdfFontFile + + local_bitmap_font_file = _compute_local_font_dir(filepath) / os.path.basename( + filepath + ) + + copy_file_if_changed(filepath, local_bitmap_font_file) + + with open(local_bitmap_font_file, "rb") as fp: + try: + try: + p = PcfFontFile.PcfFontFile(fp) + except SyntaxError: + fp.seek(0) + p = BdfFontFile.BdfFontFile(fp) + + # Convert to pillow-formatted fonts, which have a .pil and .pbm extension. + p.save(local_bitmap_font_file) + except (SyntaxError, OSError) as err: + raise core.EsphomeError( + f"Failed to parse as bitmap font: '{filepath}': {err}" + ) + + local_pil_font_file = os.path.splitext(local_bitmap_font_file)[0] + ".pil" + return cv.file_(local_pil_font_file) + + +def load_bitmap_font(filepath): from PIL import ImageFont - conf = config[CONF_FILE] - if conf[CONF_TYPE] == TYPE_LOCAL: - path = CORE.relative_config_path(conf[CONF_PATH]) - elif conf[CONF_TYPE] == TYPE_GFONTS: - path = _compute_gfonts_local_path(conf) + # Convert bpf and pcf files to pillow fonts, first. + pil_font_path = convert_bitmap_to_pillow_font(filepath) + try: - font = ImageFont.truetype(str(path), config[CONF_SIZE]) + font = ImageFont.load(str(pil_font_path)) + except Exception as e: + raise core.EsphomeError( + f"Failed to load bitmap font file: {pil_font_path} : {e}" + ) + + return BitmapFontWrapper(font) + + +def load_ttf_font(path, size): + from PIL import ImageFont + + try: + font = ImageFont.truetype(str(path), size) except Exception as e: raise core.EsphomeError(f"Could not load truetype file {path}: {e}") - ascent, descent = font.getmetrics() + return TrueTypeFontWrapper(font) + + +async def to_code(config): + conf = config[CONF_FILE] + if conf[CONF_TYPE] == TYPE_LOCAL_BITMAP: + font = load_bitmap_font(CORE.relative_config_path(conf[CONF_PATH])) + elif conf[CONF_TYPE] == TYPE_LOCAL: + path = CORE.relative_config_path(conf[CONF_PATH]) + font = load_ttf_font(path, config[CONF_SIZE]) + elif conf[CONF_TYPE] == TYPE_GFONTS: + path = _compute_gfonts_local_path(conf) + font = load_ttf_font(path, config[CONF_SIZE]) + else: + raise core.EsphomeError(f"Could not load font: unknown type: {conf[CONF_TYPE]}") + + ascent, descent = font.getmetrics(config[CONF_GLYPHS]) glyph_args = {} data = [] for glyph in config[CONF_GLYPHS]: mask = font.getmask(glyph, mode="1") - _, (offset_x, offset_y) = font.font.getsize(glyph) + offset_x, offset_y = font.getoffset(glyph) width, height = mask.size width8 = ((width + 7) // 8) * 8 glyph_data = [0] * (height * width8 // 8) diff --git a/esphome/components/gpio/switch/__init__.py b/esphome/components/gpio/switch/__init__.py index a03e16a2c1..f81cd5b6fb 100644 --- a/esphome/components/gpio/switch/__init__.py +++ b/esphome/components/gpio/switch/__init__.py @@ -2,7 +2,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import pins from esphome.components import switch -from esphome.const import CONF_ID, CONF_INTERLOCK, CONF_PIN, CONF_RESTORE_MODE +from esphome.const import CONF_INTERLOCK, CONF_PIN, CONF_RESTORE_MODE from .. import gpio_ns GPIOSwitch = gpio_ns.class_("GPIOSwitch", switch.Switch, cg.Component) @@ -18,25 +18,27 @@ RESTORE_MODES = { } CONF_INTERLOCK_WAIT_TIME = "interlock_wait_time" -CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(GPIOSwitch), - cv.Required(CONF_PIN): pins.gpio_output_pin_schema, - cv.Optional(CONF_RESTORE_MODE, default="RESTORE_DEFAULT_OFF"): cv.enum( - RESTORE_MODES, upper=True, space="_" - ), - cv.Optional(CONF_INTERLOCK): cv.ensure_list(cv.use_id(switch.Switch)), - cv.Optional( - CONF_INTERLOCK_WAIT_TIME, default="0ms" - ): cv.positive_time_period_milliseconds, - } -).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = ( + switch.switch_schema(GPIOSwitch) + .extend( + { + cv.Required(CONF_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_RESTORE_MODE, default="RESTORE_DEFAULT_OFF"): cv.enum( + RESTORE_MODES, upper=True, space="_" + ), + cv.Optional(CONF_INTERLOCK): cv.ensure_list(cv.use_id(switch.Switch)), + cv.Optional( + CONF_INTERLOCK_WAIT_TIME, default="0ms" + ): cv.positive_time_period_milliseconds, + } + ) + .extend(cv.COMPONENT_SCHEMA) +) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await switch.new_switch(config) await cg.register_component(var, config) - await switch.register_switch(var, config) pin = await cg.gpio_pin_expression(config[CONF_PIN]) cg.add(var.set_pin(pin)) diff --git a/esphome/components/hdc1080/hdc1080.h b/esphome/components/hdc1080/hdc1080.h index 9cb87cdb8b..2ff7b6dc33 100644 --- a/esphome/components/hdc1080/hdc1080.h +++ b/esphome/components/hdc1080/hdc1080.h @@ -21,8 +21,8 @@ class HDC1080Component : public PollingComponent, public i2c::I2CDevice { float get_setup_priority() const override; protected: - sensor::Sensor *temperature_; - sensor::Sensor *humidity_; + sensor::Sensor *temperature_{nullptr}; + sensor::Sensor *humidity_{nullptr}; }; } // namespace hdc1080 diff --git a/esphome/components/hlw8012/hlw8012.h b/esphome/components/hlw8012/hlw8012.h index 5060957cf1..adb49ffb66 100644 --- a/esphome/components/hlw8012/hlw8012.h +++ b/esphome/components/hlw8012/hlw8012.h @@ -16,8 +16,17 @@ enum HLW8012SensorModels { HLW8012_SENSOR_MODEL_BL0937 }; +#ifdef HAS_PCNT +#define USE_PCNT true +#else +#define USE_PCNT false +#endif + class HLW8012Component : public PollingComponent { public: + HLW8012Component() + : cf_store_(*pulse_counter::get_storage(USE_PCNT)), cf1_store_(*pulse_counter::get_storage(USE_PCNT)) {} + void setup() override; void dump_config() override; float get_setup_priority() const override; @@ -49,9 +58,9 @@ class HLW8012Component : public PollingComponent { uint64_t cf_total_pulses_{0}; GPIOPin *sel_pin_; InternalGPIOPin *cf_pin_; - pulse_counter::PulseCounterStorage cf_store_; + pulse_counter::PulseCounterStorageBase &cf_store_; InternalGPIOPin *cf1_pin_; - pulse_counter::PulseCounterStorage cf1_store_; + pulse_counter::PulseCounterStorageBase &cf1_store_; sensor::Sensor *voltage_sensor_{nullptr}; sensor::Sensor *current_sensor_{nullptr}; sensor::Sensor *power_sensor_{nullptr}; diff --git a/esphome/components/hmc5883l/hmc5883l.h b/esphome/components/hmc5883l/hmc5883l.h index 41d41baa22..3481f45dc8 100644 --- a/esphome/components/hmc5883l/hmc5883l.h +++ b/esphome/components/hmc5883l/hmc5883l.h @@ -54,10 +54,10 @@ class HMC5883LComponent : public PollingComponent, public i2c::I2CDevice { HMC5883LOversampling oversampling_{HMC5883L_OVERSAMPLING_1}; HMC5883LDatarate datarate_{HMC5883L_DATARATE_15_0_HZ}; HMC5883LRange range_{HMC5883L_RANGE_130_UT}; - sensor::Sensor *x_sensor_; - sensor::Sensor *y_sensor_; - sensor::Sensor *z_sensor_; - sensor::Sensor *heading_sensor_; + sensor::Sensor *x_sensor_{nullptr}; + sensor::Sensor *y_sensor_{nullptr}; + sensor::Sensor *z_sensor_{nullptr}; + sensor::Sensor *heading_sensor_{nullptr}; enum ErrorCode { NONE = 0, COMMUNICATION_FAILED, diff --git a/esphome/components/honeywellabp/honeywellabp.h b/esphome/components/honeywellabp/honeywellabp.h index 44d5952ca6..98f6f08c4a 100644 --- a/esphome/components/honeywellabp/honeywellabp.h +++ b/esphome/components/honeywellabp/honeywellabp.h @@ -29,8 +29,8 @@ class HONEYWELLABPSensor : public PollingComponent, uint8_t status_ = 0; // byte to hold status information. int pressure_count_ = 0; // hold raw pressure data (14 - bit, 0 - 16384) int temperature_count_ = 0; // hold raw temperature data (11 - bit, 0 - 2048) - sensor::Sensor *pressure_sensor_; - sensor::Sensor *temperature_sensor_; + sensor::Sensor *pressure_sensor_{nullptr}; + sensor::Sensor *temperature_sensor_{nullptr}; uint8_t readsensor_(); uint8_t readstatus_(); int rawpressure_(); diff --git a/esphome/components/hydreon_rgxx/binary_sensor.py b/esphome/components/hydreon_rgxx/binary_sensor.py index 0d489ebcb7..776be8a5d8 100644 --- a/esphome/components/hydreon_rgxx/binary_sensor.py +++ b/esphome/components/hydreon_rgxx/binary_sensor.py @@ -4,12 +4,15 @@ from esphome.components import binary_sensor from esphome.const import ( CONF_ID, DEVICE_CLASS_COLD, + DEVICE_CLASS_PROBLEM, ) from . import hydreon_rgxx_ns, HydreonRGxxComponent CONF_HYDREON_RGXX_ID = "hydreon_rgxx_id" CONF_TOO_COLD = "too_cold" +CONF_LENS_BAD = "lens_bad" +CONF_EM_SAT = "em_sat" HydreonRGxxBinarySensor = hydreon_rgxx_ns.class_( "HydreonRGxxBinaryComponent", cg.Component @@ -23,6 +26,12 @@ CONFIG_SCHEMA = cv.Schema( cv.Optional(CONF_TOO_COLD): binary_sensor.binary_sensor_schema( device_class=DEVICE_CLASS_COLD ), + cv.Optional(CONF_LENS_BAD): binary_sensor.binary_sensor_schema( + device_class=DEVICE_CLASS_PROBLEM, + ), + cv.Optional(CONF_EM_SAT): binary_sensor.binary_sensor_schema( + device_class=DEVICE_CLASS_PROBLEM, + ), } ) @@ -31,6 +40,14 @@ async def to_code(config): main_sensor = await cg.get_variable(config[CONF_HYDREON_RGXX_ID]) bin_component = cg.new_Pvariable(config[CONF_ID], main_sensor) await cg.register_component(bin_component, config) - if CONF_TOO_COLD in config: - tc = await binary_sensor.new_binary_sensor(config[CONF_TOO_COLD]) - cg.add(main_sensor.set_too_cold_sensor(tc)) + + mapping = { + CONF_TOO_COLD: main_sensor.set_too_cold_sensor, + CONF_LENS_BAD: main_sensor.set_lens_bad_sensor, + CONF_EM_SAT: main_sensor.set_em_sat_sensor, + } + + for key, value in mapping.items(): + if key in config: + sensor = await binary_sensor.new_binary_sensor(config[key]) + cg.add(value(sensor)) diff --git a/esphome/components/hydreon_rgxx/hydreon_rgxx.cpp b/esphome/components/hydreon_rgxx/hydreon_rgxx.cpp index 25d2617677..da4345e136 100644 --- a/esphome/components/hydreon_rgxx/hydreon_rgxx.cpp +++ b/esphome/components/hydreon_rgxx/hydreon_rgxx.cpp @@ -9,6 +9,7 @@ static const int MAX_DATA_LENGTH_BYTES = 80; static const uint8_t ASCII_LF = 0x0A; #define HYDREON_RGXX_COMMA , static const char *const PROTOCOL_NAMES[] = {HYDREON_RGXX_PROTOCOL_LIST(, HYDREON_RGXX_COMMA)}; +static const char *const IGNORE_STRINGS[] = {HYDREON_RGXX_IGNORE_LIST(, HYDREON_RGXX_COMMA)}; void HydreonRGxxComponent::dump_config() { this->check_uart_settings(9600, 1, esphome::uart::UART_CONFIG_PARITY_NONE, 8); @@ -34,33 +35,37 @@ void HydreonRGxxComponent::setup() { this->schedule_reboot_(); } -bool HydreonRGxxComponent::sensor_missing_() { +int HydreonRGxxComponent::num_sensors_missing_() { if (this->sensors_received_ == -1) { - // no request sent yet, don't check - return false; - } else { - if (this->sensors_received_ == 0) { - ESP_LOGW(TAG, "No data at all"); - return true; - } - for (int i = 0; i < NUM_SENSORS; i++) { - if (this->sensors_[i] == nullptr) { - continue; - } - if ((this->sensors_received_ >> i & 1) == 0) { - ESP_LOGW(TAG, "Missing %s", PROTOCOL_NAMES[i]); - return true; - } - } - return false; + return -1; } + int ret = NUM_SENSORS; + for (int i = 0; i < NUM_SENSORS; i++) { + if (this->sensors_[i] == nullptr) { + ret -= 1; + continue; + } + if ((this->sensors_received_ >> i & 1) != 0) { + ret -= 1; + } + } + return ret; } void HydreonRGxxComponent::update() { if (this->boot_count_ > 0) { - if (this->sensor_missing_()) { + if (this->num_sensors_missing_() > 0) { + for (int i = 0; i < NUM_SENSORS; i++) { + if (this->sensors_[i] == nullptr) { + continue; + } + if ((this->sensors_received_ >> i & 1) == 0) { + ESP_LOGW(TAG, "Missing %s", PROTOCOL_NAMES[i]); + } + } + this->no_response_count_++; - ESP_LOGE(TAG, "data missing %d times", this->no_response_count_); + ESP_LOGE(TAG, "missing %d sensors; %d times in a row", this->num_sensors_missing_(), this->no_response_count_); if (this->no_response_count_ > 15) { ESP_LOGE(TAG, "asking sensor to reboot"); for (auto &sensor : this->sensors_) { @@ -79,8 +84,16 @@ void HydreonRGxxComponent::update() { if (this->too_cold_sensor_ != nullptr) { this->too_cold_sensor_->publish_state(this->too_cold_); } + if (this->lens_bad_sensor_ != nullptr) { + this->lens_bad_sensor_->publish_state(this->lens_bad_); + } + if (this->em_sat_sensor_ != nullptr) { + this->em_sat_sensor_->publish_state(this->em_sat_); + } #endif this->too_cold_ = false; + this->lens_bad_ = false; + this->em_sat_ = false; this->sensors_received_ = 0; } } @@ -146,6 +159,25 @@ void HydreonRGxxComponent::process_line_() { ESP_LOGI(TAG, "Comment: %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str()); return; } + std::string::size_type newlineposn = this->buffer_.find('\n'); + if (newlineposn <= 1) { + // allow both \r\n and \n + ESP_LOGD(TAG, "Received empty line"); + return; + } + if (newlineposn <= 2) { + // single character lines, such as acknowledgements + ESP_LOGD(TAG, "Received ack: %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str()); + return; + } + if (this->buffer_.find("LensBad") != std::string::npos) { + ESP_LOGW(TAG, "Received LensBad!"); + this->lens_bad_ = true; + } + if (this->buffer_.find("EmSat") != std::string::npos) { + ESP_LOGW(TAG, "Received EmSat!"); + this->em_sat_ = true; + } if (this->buffer_starts_with_("PwrDays")) { if (this->boot_count_ <= 0) { this->boot_count_ = 1; @@ -200,7 +232,16 @@ void HydreonRGxxComponent::process_line_() { ESP_LOGD(TAG, "Received %s: %f", PROTOCOL_NAMES[i], this->sensors_[i]->get_raw_state()); this->sensors_received_ |= (1 << i); } + if (this->request_temperature_ && this->num_sensors_missing_() == 1) { + this->write_str("T\n"); + } } else { + for (const auto *ignore : IGNORE_STRINGS) { + if (this->buffer_starts_with_(ignore)) { + ESP_LOGI(TAG, "Ignoring %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str()); + return; + } + } ESP_LOGI(TAG, "Got unknown line: %s", this->buffer_.c_str()); } } diff --git a/esphome/components/hydreon_rgxx/hydreon_rgxx.h b/esphome/components/hydreon_rgxx/hydreon_rgxx.h index ebe4a35b19..34b9bd8d5e 100644 --- a/esphome/components/hydreon_rgxx/hydreon_rgxx.h +++ b/esphome/components/hydreon_rgxx/hydreon_rgxx.h @@ -26,13 +26,18 @@ static const uint8_t NUM_SENSORS = 1; #define HYDREON_RGXX_PROTOCOL_LIST(F, SEP) F("") #endif +#define HYDREON_RGXX_IGNORE_LIST(F, SEP) F("Emitters") SEP F("Event") SEP F("Reset") + class HydreonRGxxComponent : public PollingComponent, public uart::UARTDevice { public: void set_sensor(sensor::Sensor *sensor, int index) { this->sensors_[index] = sensor; } #ifdef USE_BINARY_SENSOR void set_too_cold_sensor(binary_sensor::BinarySensor *sensor) { this->too_cold_sensor_ = sensor; } + void set_lens_bad_sensor(binary_sensor::BinarySensor *sensor) { this->lens_bad_sensor_ = sensor; } + void set_em_sat_sensor(binary_sensor::BinarySensor *sensor) { this->em_sat_sensor_ = sensor; } #endif void set_model(RGModel model) { model_ = model; } + void set_request_temperature(bool b) { request_temperature_ = b; } /// Schedule data readings. void update() override; @@ -49,11 +54,13 @@ class HydreonRGxxComponent : public PollingComponent, public uart::UARTDevice { void schedule_reboot_(); bool buffer_starts_with_(const std::string &prefix); bool buffer_starts_with_(const char *prefix); - bool sensor_missing_(); + int num_sensors_missing_(); sensor::Sensor *sensors_[NUM_SENSORS] = {nullptr}; #ifdef USE_BINARY_SENSOR - binary_sensor::BinarySensor *too_cold_sensor_ = nullptr; + binary_sensor::BinarySensor *too_cold_sensor_{nullptr}; + binary_sensor::BinarySensor *lens_bad_sensor_{nullptr}; + binary_sensor::BinarySensor *em_sat_sensor_{nullptr}; #endif int16_t boot_count_ = 0; @@ -62,6 +69,9 @@ class HydreonRGxxComponent : public PollingComponent, public uart::UARTDevice { RGModel model_ = RG9; int sw_version_ = 0; bool too_cold_ = false; + bool lens_bad_ = false; + bool em_sat_ = false; + bool request_temperature_ = false; // bit field showing which sensors we have received data for int sensors_received_ = -1; diff --git a/esphome/components/hydreon_rgxx/sensor.py b/esphome/components/hydreon_rgxx/sensor.py index c604f8d3c1..730499a493 100644 --- a/esphome/components/hydreon_rgxx/sensor.py +++ b/esphome/components/hydreon_rgxx/sensor.py @@ -5,8 +5,11 @@ from esphome.const import ( CONF_ID, CONF_MODEL, CONF_MOISTURE, + CONF_TEMPERATURE, DEVICE_CLASS_HUMIDITY, STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, + ICON_THERMOMETER, ) from . import RGModel, HydreonRGxxComponent @@ -33,6 +36,7 @@ SUPPORTED_SENSORS = { CONF_TOTAL_ACC: ["RG_15"], CONF_R_INT: ["RG_15"], CONF_MOISTURE: ["RG_9"], + CONF_TEMPERATURE: ["RG_9"], } PROTOCOL_NAMES = { CONF_MOISTURE: "R", @@ -40,6 +44,7 @@ PROTOCOL_NAMES = { CONF_R_INT: "RInt", CONF_EVENT_ACC: "EventAcc", CONF_TOTAL_ACC: "TotalAcc", + CONF_TEMPERATURE: "t", } @@ -92,6 +97,12 @@ CONFIG_SCHEMA = cv.All( device_class=DEVICE_CLASS_HUMIDITY, state_class=STATE_CLASS_MEASUREMENT, ), + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=0, + icon=ICON_THERMOMETER, + state_class=STATE_CLASS_MEASUREMENT, + ), } ) .extend(cv.polling_component_schema("60s")) @@ -108,7 +119,7 @@ async def to_code(config): cg.add_define( "HYDREON_RGXX_PROTOCOL_LIST(F, sep)", cg.RawExpression( - " sep ".join([f'F("{name}")' for name in PROTOCOL_NAMES.values()]) + " sep ".join([f'F("{name} ")' for name in PROTOCOL_NAMES.values()]) ), ) cg.add_define("HYDREON_RGXX_NUM_SENSORS", len(PROTOCOL_NAMES)) @@ -117,3 +128,5 @@ async def to_code(config): if conf in config: sens = await sensor.new_sensor(config[conf]) cg.add(var.set_sensor(sens, i)) + + cg.add(var.set_request_temperature(CONF_TEMPERATURE in config)) diff --git a/esphome/components/i2c/__init__.py b/esphome/components/i2c/__init__.py index 46f0abacc6..a04e63e789 100644 --- a/esphome/components/i2c/__init__.py +++ b/esphome/components/i2c/__init__.py @@ -1,5 +1,6 @@ import esphome.codegen as cg import esphome.config_validation as cv +import esphome.final_validate as fv from esphome import pins from esphome.const import ( CONF_FREQUENCY, @@ -110,3 +111,27 @@ async def register_i2c_device(var, config): parent = await cg.get_variable(config[CONF_I2C_ID]) cg.add(var.set_i2c_bus(parent)) cg.add(var.set_i2c_address(config[CONF_ADDRESS])) + + +def final_validate_device_schema( + name: str, *, min_frequency: cv.frequency = None, max_frequency: cv.frequency = None +): + hub_schema = {} + if min_frequency is not None: + hub_schema[cv.Required(CONF_FREQUENCY)] = cv.Range( + min=cv.frequency(min_frequency), + min_included=True, + msg=f"Component {name} requires a minimum frequency of {min_frequency} for the I2C bus", + ) + + if max_frequency is not None: + hub_schema[cv.Required(CONF_FREQUENCY)] = cv.Range( + max=cv.frequency(max_frequency), + max_included=True, + msg=f"Component {name} cannot be used with a frequency of over {max_frequency} for the I2C bus", + ) + + return cv.Schema( + {cv.Required(CONF_I2C_ID): fv.id_declaration_match_schema(hub_schema)}, + extra=cv.ALLOW_EXTRA, + ) diff --git a/esphome/components/ili9341/ili9341_display.cpp b/esphome/components/ili9341/ili9341_display.cpp index c4fa5f1b10..117de3de89 100644 --- a/esphome/components/ili9341/ili9341_display.cpp +++ b/esphome/components/ili9341/ili9341_display.cpp @@ -10,7 +10,6 @@ namespace ili9341 { static const char *const TAG = "ili9341"; void ILI9341Display::setup_pins_() { - this->init_internal_(this->get_buffer_length_()); this->dc_pin_->setup(); // OUTPUT this->dc_pin_->digital_write(false); if (this->reset_pin_ != nullptr) { @@ -28,15 +27,14 @@ void ILI9341Display::setup_pins_() { void ILI9341Display::dump_config() { LOG_DISPLAY("", "ili9341", this); - ESP_LOGCONFIG(TAG, " Width: %d, Height: %d, Rotation: %d", this->width_, this->height_, this->rotation_); LOG_PIN(" Reset Pin: ", this->reset_pin_); LOG_PIN(" DC Pin: ", this->dc_pin_); LOG_PIN(" Busy Pin: ", this->busy_pin_); - LOG_PIN(" Backlight Pin: ", this->led_pin_); LOG_UPDATE_INTERVAL(this); } -float ILI9341Display::get_setup_priority() const { return setup_priority::PROCESSOR; } +float ILI9341Display::get_setup_priority() const { return setup_priority::HARDWARE; } + void ILI9341Display::command(uint8_t value) { this->start_command_(); this->write_byte(value); @@ -88,10 +86,19 @@ void ILI9341Display::display_() { // we will only update the changed window to the display uint16_t w = this->x_high_ - this->x_low_ + 1; uint16_t h = this->y_high_ - this->y_low_ + 1; + uint32_t start_pos = ((this->y_low_ * this->width_) + x_low_); + + // check if something was displayed + if ((this->x_high_ < this->x_low_) || (this->y_high_ < this->y_low_)) { + return; + } set_addr_window_(this->x_low_, this->y_low_, w, h); + + ESP_LOGVV("ILI9341", "Start ILI9341Display::display_(xl:%d, xh:%d, yl:%d, yh:%d, w:%d, h:%d, start_pos:%d)", + this->x_low_, this->x_high_, this->y_low_, this->y_high_, w, h, start_pos); + this->start_data_(); - uint32_t start_pos = ((this->y_low_ * this->width_) + x_low_); for (uint16_t row = 0; row < h; row++) { uint32_t pos = start_pos + (row * width_); uint32_t rem = w; @@ -101,7 +108,9 @@ void ILI9341Display::display_() { this->write_array(transfer_buffer_, 2 * sz); pos += sz; rem -= sz; + App.feed_wdt(); } + App.feed_wdt(); } this->end_data_(); @@ -121,20 +130,10 @@ void ILI9341Display::fill(Color color) { this->y_high_ = this->get_height_internal() - 1; } -void ILI9341Display::fill_internal_(Color color) { - if (color.raw_32 == Color::BLACK.raw_32) { - memset(transfer_buffer_, 0, sizeof(transfer_buffer_)); - } else { - uint8_t *dst = transfer_buffer_; - auto color565 = display::ColorUtil::color_to_565(color); +void ILI9341Display::fill_internal_(uint8_t color) { + memset(transfer_buffer_, color, sizeof(transfer_buffer_)); - while (dst < transfer_buffer_ + sizeof(transfer_buffer_)) { - *dst++ = (uint8_t)(color565 >> 8); - *dst++ = (uint8_t) color565; - } - } - - uint32_t rem = this->get_width_internal() * this->get_height_internal(); + uint32_t rem = (this->get_buffer_length_() * 2); this->set_addr_window_(0, 0, this->get_width_internal(), this->get_height_internal()); this->start_data_(); @@ -147,26 +146,58 @@ void ILI9341Display::fill_internal_(Color color) { this->end_data_(); - memset(buffer_, 0, (this->get_width_internal()) * (this->get_height_internal())); + memset(buffer_, color, this->get_buffer_length_()); +} + +void ILI9341Display::rotate_my_(uint8_t m) { + uint8_t rotation = m & 3; // can't be higher than 3 + switch (rotation) { + case 0: + m = (MADCTL_MX | MADCTL_BGR); + // _width = ILI9341_TFTWIDTH; + // _height = ILI9341_TFTHEIGHT; + break; + case 1: + m = (MADCTL_MV | MADCTL_BGR); + // _width = ILI9341_TFTHEIGHT; + // _height = ILI9341_TFTWIDTH; + break; + case 2: + m = (MADCTL_MY | MADCTL_BGR); + // _width = ILI9341_TFTWIDTH; + // _height = ILI9341_TFTHEIGHT; + break; + case 3: + m = (MADCTL_MX | MADCTL_MY | MADCTL_MV | MADCTL_BGR); + // _width = ILI9341_TFTHEIGHT; + // _height = ILI9341_TFTWIDTH; + break; + } + + this->command(ILI9341_MADCTL); + this->data(m); } void HOT ILI9341Display::draw_absolute_pixel_internal(int x, int y, Color color) { if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0) return; - // low and high watermark may speed up drawing from buffer - this->x_low_ = (x < this->x_low_) ? x : this->x_low_; - this->y_low_ = (y < this->y_low_) ? y : this->y_low_; - this->x_high_ = (x > this->x_high_) ? x : this->x_high_; - this->y_high_ = (y > this->y_high_) ? y : this->y_high_; - uint32_t pos = (y * width_) + x; + uint8_t new_color; + if (this->buffer_color_mode_ == BITS_8) { - uint8_t color332 = display::ColorUtil::color_to_332(color, display::ColorOrder::COLOR_ORDER_RGB); - buffer_[pos] = color332; + new_color = display::ColorUtil::color_to_332(color, display::ColorOrder::COLOR_ORDER_RGB); } else { // if (this->buffer_color_mode_ == BITS_8_INDEXED) { - uint8_t index = display::ColorUtil::color_to_index8_palette888(color, this->palette_); - buffer_[pos] = index; + new_color = display::ColorUtil::color_to_index8_palette888(color, this->palette_); + } + + if (buffer_[pos] != new_color) { + buffer_[pos] = new_color; + // low and high watermark may speed up drawing from buffer + this->x_low_ = (x < this->x_low_) ? x : this->x_low_; + this->y_low_ = (y < this->y_low_) ? y : this->y_low_; + this->x_high_ = (x > this->x_high_) ? x : this->x_high_; + this->y_high_ = (y > this->y_high_) ? y : this->y_high_; } } @@ -252,7 +283,6 @@ void ILI9341M5Stack::initialize() { this->width_ = 320; this->height_ = 240; this->invert_display_(true); - this->fill_internal_(Color::BLACK); } // 24_TFT display @@ -260,7 +290,6 @@ void ILI9341TFT24::initialize() { this->init_lcd_(INITCMD_TFT); this->width_ = 240; this->height_ = 320; - this->fill_internal_(Color::BLACK); } // 24_TFT rotated display @@ -268,7 +297,6 @@ void ILI9341TFT24R::initialize() { this->init_lcd_(INITCMD_TFT); this->width_ = 320; this->height_ = 240; - this->fill_internal_(Color::BLACK); } } // namespace ili9341 diff --git a/esphome/components/ili9341/ili9341_display.h b/esphome/components/ili9341/ili9341_display.h index 3fbc144ac2..547c608ae8 100644 --- a/esphome/components/ili9341/ili9341_display.h +++ b/esphome/components/ili9341/ili9341_display.h @@ -5,6 +5,7 @@ #include "esphome/components/display/display_buffer.h" #include "ili9341_defines.h" #include "ili9341_init.h" +#include "esphome/core/log.h" namespace esphome { namespace ili9341 { @@ -47,6 +48,14 @@ class ILI9341Display : public PollingComponent, void setup() override { this->setup_pins_(); this->initialize(); + + this->x_low_ = this->width_; + this->y_low_ = this->height_; + this->x_high_ = 0; + this->y_high_ = 0; + + this->init_internal_(this->get_buffer_length_()); + this->fill_internal_(0x00); } display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_COLOR; } @@ -59,8 +68,9 @@ class ILI9341Display : public PollingComponent, void set_addr_window_(uint16_t x, uint16_t y, uint16_t w, uint16_t h); void invert_display_(bool invert); void reset_(); - void fill_internal_(Color color); + void fill_internal_(uint8_t color); void display_(); + void rotate_my_(uint8_t m); ILI9341Model model_; int16_t width_{320}; ///< Display width as modified by current rotation diff --git a/esphome/components/mcp23008/mcp23008.cpp b/esphome/components/mcp23008/mcp23008.cpp index 351360fe1c..24b9a63f48 100644 --- a/esphome/components/mcp23008/mcp23008.cpp +++ b/esphome/components/mcp23008/mcp23008.cpp @@ -14,6 +14,9 @@ void MCP23008::setup() { return; } + // Read current output register state + this->read_reg(mcp23x08_base::MCP23X08_OLAT, &this->olat_); + if (this->open_drain_ints_) { // enable open-drain interrupt pins, 3.3V-safe this->write_reg(mcp23x08_base::MCP23X08_IOCON, 0x04); diff --git a/esphome/components/mcp23016/mcp23016.cpp b/esphome/components/mcp23016/mcp23016.cpp index 9787da6faa..70df0dfb7b 100644 --- a/esphome/components/mcp23016/mcp23016.cpp +++ b/esphome/components/mcp23016/mcp23016.cpp @@ -15,6 +15,10 @@ void MCP23016::setup() { return; } + // Read current output register state + this->read_reg_(MCP23016_OLAT0, &this->olat_0_); + this->read_reg_(MCP23016_OLAT1, &this->olat_1_); + // all pins input this->write_reg_(MCP23016_IODIR0, 0xFF); this->write_reg_(MCP23016_IODIR1, 0xFF); diff --git a/esphome/components/mcp23017/mcp23017.cpp b/esphome/components/mcp23017/mcp23017.cpp index 7344f482e0..b82e750eaf 100644 --- a/esphome/components/mcp23017/mcp23017.cpp +++ b/esphome/components/mcp23017/mcp23017.cpp @@ -14,6 +14,10 @@ void MCP23017::setup() { return; } + // Read current output register state + this->read_reg(mcp23x17_base::MCP23X17_OLATA, &this->olat_a_); + this->read_reg(mcp23x17_base::MCP23X17_OLATB, &this->olat_b_); + if (this->open_drain_ints_) { // enable open-drain interrupt pins, 3.3V-safe this->write_reg(mcp23x17_base::MCP23X17_IOCONA, 0x04); diff --git a/esphome/components/mcp23s08/mcp23s08.cpp b/esphome/components/mcp23s08/mcp23s08.cpp index af834b4c40..2f885743de 100644 --- a/esphome/components/mcp23s08/mcp23s08.cpp +++ b/esphome/components/mcp23s08/mcp23s08.cpp @@ -23,6 +23,9 @@ void MCP23S08::setup() { this->transfer_byte(0b00011000); // Enable HAEN pins for addressing this->disable(); + // Read current output register state + this->read_reg(mcp23x08_base::MCP23X08_OLAT, &this->olat_); + if (this->open_drain_ints_) { // enable open-drain interrupt pins, 3.3V-safe this->write_reg(mcp23x08_base::MCP23X08_IOCON, 0x04); diff --git a/esphome/components/mcp23s17/mcp23s17.cpp b/esphome/components/mcp23s17/mcp23s17.cpp index 8e3d5213f8..2fc9c74634 100644 --- a/esphome/components/mcp23s17/mcp23s17.cpp +++ b/esphome/components/mcp23s17/mcp23s17.cpp @@ -23,6 +23,10 @@ void MCP23S17::setup() { this->transfer_byte(0b00011000); // Enable HAEN pins for addressing this->disable(); + // Read current output register state + this->read_reg(mcp23x17_base::MCP23X17_OLATA, &this->olat_a_); + this->read_reg(mcp23x17_base::MCP23X17_OLATB, &this->olat_b_); + if (this->open_drain_ints_) { // enable open-drain interrupt pins, 3.3V-safe this->write_reg(mcp23x17_base::MCP23X17_IOCONA, 0x04); diff --git a/esphome/components/mcp3008/__init__.py b/esphome/components/mcp3008/__init__.py index 431963acfd..24a48664c1 100644 --- a/esphome/components/mcp3008/__init__.py +++ b/esphome/components/mcp3008/__init__.py @@ -2,7 +2,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import spi from esphome.const import CONF_ID -from esphome.core import CORE DEPENDENCIES = ["spi"] AUTO_LOAD = ["sensor"] @@ -24,6 +23,3 @@ async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await spi.register_spi_device(var, config) - - if CORE.is_esp32: - cg.add_library("SPI", None) diff --git a/esphome/components/mcp9600/__init__.py b/esphome/components/mcp9600/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/mcp9600/mcp9600.cpp b/esphome/components/mcp9600/mcp9600.cpp new file mode 100644 index 0000000000..3fdd788fc6 --- /dev/null +++ b/esphome/components/mcp9600/mcp9600.cpp @@ -0,0 +1,115 @@ +#include "mcp9600.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace mcp9600 { + +static const char *const TAG = "mcp9600"; + +static const uint8_t MCP9600_REGISTER_HOT_JUNCTION = 0x00; +// static const uint8_t MCP9600_REGISTER_JUNCTION_DELTA = 0x01; // Unused, but kept for future reference +static const uint8_t MCP9600_REGISTER_COLD_JUNTION = 0x02; +// static const uint8_t MCP9600_REGISTER_RAW_DATA_ADC = 0x03; // Unused, but kept for future reference +static const uint8_t MCP9600_REGISTER_STATUS = 0x04; +static const uint8_t MCP9600_REGISTER_SENSOR_CONFIG = 0x05; +static const uint8_t MCP9600_REGISTER_CONFIG = 0x06; +static const uint8_t MCP9600_REGISTER_ALERT1_CONFIG = 0x08; +static const uint8_t MCP9600_REGISTER_ALERT2_CONFIG = 0x09; +static const uint8_t MCP9600_REGISTER_ALERT3_CONFIG = 0x0A; +static const uint8_t MCP9600_REGISTER_ALERT4_CONFIG = 0x0B; +static const uint8_t MCP9600_REGISTER_ALERT1_HYSTERESIS = 0x0C; +static const uint8_t MCP9600_REGISTER_ALERT2_HYSTERESIS = 0x0D; +static const uint8_t MCP9600_REGISTER_ALERT3_HYSTERESIS = 0x0E; +static const uint8_t MCP9600_REGISTER_ALERT4_HYSTERESIS = 0x0F; +static const uint8_t MCP9600_REGISTER_ALERT1_LIMIT = 0x10; +static const uint8_t MCP9600_REGISTER_ALERT2_LIMIT = 0x11; +static const uint8_t MCP9600_REGISTER_ALERT3_LIMIT = 0x12; +static const uint8_t MCP9600_REGISTER_ALERT4_LIMIT = 0x13; +static const uint8_t MCP9600_REGISTER_DEVICE_ID = 0x20; + +void MCP9600Component::setup() { + ESP_LOGCONFIG(TAG, "Setting up MCP9600..."); + + uint16_t dev_id = 0; + this->read_byte_16(MCP9600_REGISTER_DEVICE_ID, &dev_id); + this->device_id_ = (uint8_t)(dev_id >> 8); + + // Allows both MCP9600's and MCP9601's to be connected. + if (this->device_id_ != (uint8_t) 0x40 && this->device_id_ != (uint8_t) 0x41) { + this->error_code_ = COMMUNICATION_FAILED; + this->mark_failed(); + return; + } + + bool success = this->write_byte(MCP9600_REGISTER_STATUS, 0x00); + success |= this->write_byte(MCP9600_REGISTER_SENSOR_CONFIG, uint8_t(0x00 | thermocouple_type_ << 4)); + success |= this->write_byte(MCP9600_REGISTER_CONFIG, 0x00); + success |= this->write_byte(MCP9600_REGISTER_ALERT1_CONFIG, 0x00); + success |= this->write_byte(MCP9600_REGISTER_ALERT2_CONFIG, 0x00); + success |= this->write_byte(MCP9600_REGISTER_ALERT3_CONFIG, 0x00); + success |= this->write_byte(MCP9600_REGISTER_ALERT4_CONFIG, 0x00); + success |= this->write_byte(MCP9600_REGISTER_ALERT1_HYSTERESIS, 0x00); + success |= this->write_byte(MCP9600_REGISTER_ALERT2_HYSTERESIS, 0x00); + success |= this->write_byte(MCP9600_REGISTER_ALERT3_HYSTERESIS, 0x00); + success |= this->write_byte(MCP9600_REGISTER_ALERT4_HYSTERESIS, 0x00); + success |= this->write_byte_16(MCP9600_REGISTER_ALERT1_LIMIT, 0x0000); + success |= this->write_byte_16(MCP9600_REGISTER_ALERT2_LIMIT, 0x0000); + success |= this->write_byte_16(MCP9600_REGISTER_ALERT3_LIMIT, 0x0000); + success |= this->write_byte_16(MCP9600_REGISTER_ALERT4_LIMIT, 0x0000); + + if (!success) { + this->error_code_ = FAILED_TO_UPDATE_CONFIGURATION; + this->mark_failed(); + return; + } +} + +void MCP9600Component::dump_config() { + ESP_LOGCONFIG(TAG, "MCP9600:"); + LOG_I2C_DEVICE(this); + LOG_UPDATE_INTERVAL(this); + + ESP_LOGCONFIG(TAG, " Device ID: 0x%x", this->device_id_); + + LOG_SENSOR(" ", "Hot Junction Temperature", this->hot_junction_sensor_); + LOG_SENSOR(" ", "Cold Junction Temperature", this->cold_junction_sensor_); + + switch (this->error_code_) { + case COMMUNICATION_FAILED: + ESP_LOGE(TAG, "Connected device does not match a known MCP9600 or MCP901 sensor"); + break; + case FAILED_TO_UPDATE_CONFIGURATION: + ESP_LOGE(TAG, "Failed to update device configuration"); + break; + case NONE: + default: + break; + } +} + +void MCP9600Component::update() { + if (this->hot_junction_sensor_ != nullptr) { + uint16_t raw_hot_junction_temperature; + if (!this->read_byte_16(MCP9600_REGISTER_HOT_JUNCTION, &raw_hot_junction_temperature)) { + this->status_set_warning(); + return; + } + float hot_junction_temperature = int16_t(raw_hot_junction_temperature) * 0.0625; + this->hot_junction_sensor_->publish_state(hot_junction_temperature); + } + + if (this->cold_junction_sensor_ != nullptr) { + uint16_t raw_cold_junction_temperature; + if (!this->read_byte_16(MCP9600_REGISTER_COLD_JUNTION, &raw_cold_junction_temperature)) { + this->status_set_warning(); + return; + } + float cold_junction_temperature = int16_t(raw_cold_junction_temperature) * 0.0625; + this->cold_junction_sensor_->publish_state(cold_junction_temperature); + } + + this->status_clear_warning(); +} + +} // namespace mcp9600 +} // namespace esphome diff --git a/esphome/components/mcp9600/mcp9600.h b/esphome/components/mcp9600/mcp9600.h new file mode 100644 index 0000000000..92612cc26d --- /dev/null +++ b/esphome/components/mcp9600/mcp9600.h @@ -0,0 +1,51 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace mcp9600 { + +enum MCP9600ThermocoupleType : uint8_t { + MCP9600_THERMOCOUPLE_TYPE_K = 0b000, + MCP9600_THERMOCOUPLE_TYPE_J = 0b001, + MCP9600_THERMOCOUPLE_TYPE_T = 0b010, + MCP9600_THERMOCOUPLE_TYPE_N = 0b011, + MCP9600_THERMOCOUPLE_TYPE_S = 0b100, + MCP9600_THERMOCOUPLE_TYPE_E = 0b101, + MCP9600_THERMOCOUPLE_TYPE_B = 0b110, + MCP9600_THERMOCOUPLE_TYPE_R = 0b111, +}; + +class MCP9600Component : public PollingComponent, public i2c::I2CDevice { + public: + void setup() override; + void dump_config() override; + void update() override; + + float get_setup_priority() const override { return setup_priority::DATA; } + + void set_hot_junction(sensor::Sensor *hot_junction) { this->hot_junction_sensor_ = hot_junction; } + void set_cold_junction(sensor::Sensor *cold_junction) { this->cold_junction_sensor_ = cold_junction; } + void set_thermocouple_type(MCP9600ThermocoupleType thermocouple_type) { + this->thermocouple_type_ = thermocouple_type; + }; + + protected: + uint8_t device_id_{0}; + + sensor::Sensor *hot_junction_sensor_{nullptr}; + sensor::Sensor *cold_junction_sensor_{nullptr}; + + MCP9600ThermocoupleType thermocouple_type_{MCP9600_THERMOCOUPLE_TYPE_K}; + + enum ErrorCode { + NONE, + COMMUNICATION_FAILED, + FAILED_TO_UPDATE_CONFIGURATION, + } error_code_{NONE}; +}; + +} // namespace mcp9600 +} // namespace esphome diff --git a/esphome/components/mcp9600/sensor.py b/esphome/components/mcp9600/sensor.py new file mode 100644 index 0000000000..4c10df2dab --- /dev/null +++ b/esphome/components/mcp9600/sensor.py @@ -0,0 +1,81 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + CONF_ID, + DEVICE_CLASS_TEMPERATURE, + STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, +) + +CONF_THERMOCOUPLE_TYPE = "thermocouple_type" +CONF_HOT_JUNCTION = "hot_junction" +CONF_COLD_JUNCTION = "cold_junction" + +DEPENDENCIES = ["i2c"] +CODEOWNERS = ["@MrEditor97"] + +mcp9600_ns = cg.esphome_ns.namespace("mcp9600") +MCP9600Component = mcp9600_ns.class_( + "MCP9600Component", cg.PollingComponent, i2c.I2CDevice +) + +MCP9600ThermocoupleType = mcp9600_ns.enum("MCP9600ThermocoupleType") +THERMOCOUPLE_TYPE = { + "K": MCP9600ThermocoupleType.MCP9600_THERMOCOUPLE_TYPE_K, + "J": MCP9600ThermocoupleType.MCP9600_THERMOCOUPLE_TYPE_J, + "T": MCP9600ThermocoupleType.MCP9600_THERMOCOUPLE_TYPE_T, + "N": MCP9600ThermocoupleType.MCP9600_THERMOCOUPLE_TYPE_N, + "S": MCP9600ThermocoupleType.MCP9600_THERMOCOUPLE_TYPE_S, + "E": MCP9600ThermocoupleType.MCP9600_THERMOCOUPLE_TYPE_E, + "B": MCP9600ThermocoupleType.MCP9600_THERMOCOUPLE_TYPE_B, + "R": MCP9600ThermocoupleType.MCP9600_THERMOCOUPLE_TYPE_R, +} + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(MCP9600Component), + cv.Optional(CONF_THERMOCOUPLE_TYPE, default="K"): cv.enum( + THERMOCOUPLE_TYPE, upper=True + ), + cv.Optional(CONF_HOT_JUNCTION): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_COLD_JUNCTION): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x67)) +) + +FINAL_VALIDATE_SCHEMA = i2c.final_validate_device_schema( + "mcp9600", min_frequency="100khz" +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + + cg.add(var.set_thermocouple_type(config[CONF_THERMOCOUPLE_TYPE])) + + if CONF_HOT_JUNCTION in config: + conf = config[CONF_HOT_JUNCTION] + sens = await sensor.new_sensor(conf) + cg.add(var.set_hot_junction(sens)) + + if CONF_COLD_JUNCTION in config: + conf = config[CONF_COLD_JUNCTION] + sens = await sensor.new_sensor(conf) + cg.add(var.set_cold_junction(sens)) diff --git a/esphome/components/mlx90393/sensor_mlx90393.h b/esphome/components/mlx90393/sensor_mlx90393.h index fc33ad1aa8..8dfb7e6a13 100644 --- a/esphome/components/mlx90393/sensor_mlx90393.h +++ b/esphome/components/mlx90393/sensor_mlx90393.h @@ -52,7 +52,7 @@ class MLX90393Cls : public PollingComponent, public i2c::I2CDevice, public MLX90 uint8_t temperature_oversampling_ = 0; uint8_t filter_; uint8_t resolutions_[3] = {0}; - GPIOPin *drdy_pin_ = nullptr; + GPIOPin *drdy_pin_{nullptr}; }; } // namespace mlx90393 diff --git a/esphome/components/modbus/modbus.cpp b/esphome/components/modbus/modbus.cpp index 845fa92e95..4d75675d0f 100644 --- a/esphome/components/modbus/modbus.cpp +++ b/esphome/components/modbus/modbus.cpp @@ -35,22 +35,6 @@ void Modbus::loop() { } } -uint16_t crc16(const uint8_t *data, uint8_t len) { - uint16_t crc = 0xFFFF; - while (len--) { - crc ^= *data++; - for (uint8_t i = 0; i < 8; i++) { - if ((crc & 0x01) != 0) { - crc >>= 1; - crc ^= 0xA001; - } else { - crc >>= 1; - } - } - } - return crc; -} - bool Modbus::parse_modbus_byte_(uint8_t byte) { size_t at = this->rx_buffer_.size(); this->rx_buffer_.push_back(byte); diff --git a/esphome/components/modbus/modbus.h b/esphome/components/modbus/modbus.h index 400e29e08b..629ab6dcce 100644 --- a/esphome/components/modbus/modbus.h +++ b/esphome/components/modbus/modbus.h @@ -40,8 +40,6 @@ class Modbus : public uart::UARTDevice, public Component { std::vector devices_; }; -uint16_t crc16(const uint8_t *data, uint8_t len); - class ModbusDevice { public: void set_parent(Modbus *parent) { parent_ = parent; } diff --git a/esphome/components/modbus_controller/switch/__init__.py b/esphome/components/modbus_controller/switch/__init__.py index 0dfbd83cb8..9673a066e3 100644 --- a/esphome/components/modbus_controller/switch/__init__.py +++ b/esphome/components/modbus_controller/switch/__init__.py @@ -32,11 +32,11 @@ ModbusSwitch = modbus_controller_ns.class_( ) CONFIG_SCHEMA = cv.All( - switch.SWITCH_SCHEMA.extend(cv.COMPONENT_SCHEMA) + switch.switch_schema(ModbusSwitch) + .extend(cv.COMPONENT_SCHEMA) .extend(ModbusItemBaseSchema) .extend( { - cv.GenerateID(): cv.declare_id(ModbusSwitch), 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, diff --git a/esphome/components/mpl3115a2/__init__.py b/esphome/components/mpl3115a2/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/mpl3115a2/mpl3115a2.cpp b/esphome/components/mpl3115a2/mpl3115a2.cpp new file mode 100644 index 0000000000..f1e553e107 --- /dev/null +++ b/esphome/components/mpl3115a2/mpl3115a2.cpp @@ -0,0 +1,99 @@ +#include "mpl3115a2.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace mpl3115a2 { + +static const char *const TAG = "mpl3115a2"; + +void MPL3115A2Component::setup() { + ESP_LOGCONFIG(TAG, "Setting up MPL3115A2..."); + + uint8_t whoami = 0xFF; + if (!this->read_byte(MPL3115A2_WHOAMI, &whoami, false)) { + this->error_code_ = COMMUNICATION_FAILED; + this->mark_failed(); + return; + } + if (whoami != 0xC4) { + this->error_code_ = WRONG_ID; + this->mark_failed(); + return; + } + + // reset + this->write_byte(MPL3115A2_CTRL_REG1, MPL3115A2_CTRL_REG1_RST); + delay(15); + + // enable data ready events for pressure/altitude and temperature + this->write_byte(MPL3115A2_PT_DATA_CFG, + MPL3115A2_PT_DATA_CFG_TDEFE | MPL3115A2_PT_DATA_CFG_PDEFE | MPL3115A2_PT_DATA_CFG_DREM); +} + +void MPL3115A2Component::dump_config() { + ESP_LOGCONFIG(TAG, "MPL3115A2:"); + LOG_I2C_DEVICE(this); + if (this->is_failed()) { + switch (this->error_code_) { + case COMMUNICATION_FAILED: + ESP_LOGE(TAG, "Communication with MPL3115A2 failed!"); + break; + case WRONG_ID: + ESP_LOGE(TAG, "MPL3115A2 has invalid id"); + break; + default: + ESP_LOGE(TAG, "Setting up MPL3115A2 registers failed!"); + break; + } + } + LOG_UPDATE_INTERVAL(this); + LOG_SENSOR(" ", "Temperature", this->temperature_); + LOG_SENSOR(" ", "Pressure", this->pressure_); + LOG_SENSOR(" ", "Altitude", this->altitude_); +} + +void MPL3115A2Component::update() { + uint8_t mode = MPL3115A2_CTRL_REG1_OS128; + this->write_byte(MPL3115A2_CTRL_REG1, mode, true); + // Trigger a new reading + mode |= MPL3115A2_CTRL_REG1_OST; + if (this->altitude_ != nullptr) + mode |= MPL3115A2_CTRL_REG1_ALT; + this->write_byte(MPL3115A2_CTRL_REG1, mode, true); + + // Wait until status shows reading available + uint8_t status = 0; + if (!this->read_byte(MPL3115A2_REGISTER_STATUS, &status, false) || (status & MPL3115A2_REGISTER_STATUS_PDR) == 0) { + delay(10); + if (!this->read_byte(MPL3115A2_REGISTER_STATUS, &status, false) || (status & MPL3115A2_REGISTER_STATUS_PDR) == 0) { + return; + } + } + + uint8_t buffer[5] = {0, 0, 0, 0, 0}; + this->read_register(MPL3115A2_REGISTER_PRESSURE_MSB, buffer, 5, false); + + float altitude = 0, pressure = 0; + if (this->altitude_ != nullptr) { + int32_t alt = encode_uint32(buffer[0], buffer[1], buffer[2], 0); + altitude = float(alt) / 65536.0; + this->altitude_->publish_state(altitude); + } else { + uint32_t p = encode_uint32(0, buffer[0], buffer[1], buffer[2]); + pressure = float(p) / 6400.0; + if (this->pressure_ != nullptr) + this->pressure_->publish_state(pressure); + } + int16_t t = encode_uint16(buffer[3], buffer[4]); + float temperature = float(t) / 256.0; + if (this->temperature_ != nullptr) + this->temperature_->publish_state(temperature); + + ESP_LOGD(TAG, "Got Temperature=%.1f°C Altitude=%.1f Pressure=%.1f", temperature, altitude, pressure); + + this->status_clear_warning(); +} + +} // namespace mpl3115a2 +} // namespace esphome diff --git a/esphome/components/mpl3115a2/mpl3115a2.h b/esphome/components/mpl3115a2/mpl3115a2.h new file mode 100644 index 0000000000..00a6d90c52 --- /dev/null +++ b/esphome/components/mpl3115a2/mpl3115a2.h @@ -0,0 +1,108 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace mpl3115a2 { + +// enums from https://github.com/adafruit/Adafruit_MPL3115A2_Library/ +/** MPL3115A2 registers **/ +enum { + MPL3115A2_REGISTER_STATUS = (0x00), + + MPL3115A2_REGISTER_PRESSURE_MSB = (0x01), + MPL3115A2_REGISTER_PRESSURE_CSB = (0x02), + MPL3115A2_REGISTER_PRESSURE_LSB = (0x03), + + MPL3115A2_REGISTER_TEMP_MSB = (0x04), + MPL3115A2_REGISTER_TEMP_LSB = (0x05), + + MPL3115A2_REGISTER_DR_STATUS = (0x06), + + MPL3115A2_OUT_P_DELTA_MSB = (0x07), + MPL3115A2_OUT_P_DELTA_CSB = (0x08), + MPL3115A2_OUT_P_DELTA_LSB = (0x09), + + MPL3115A2_OUT_T_DELTA_MSB = (0x0A), + MPL3115A2_OUT_T_DELTA_LSB = (0x0B), + + MPL3115A2_WHOAMI = (0x0C), + + MPL3115A2_BAR_IN_MSB = (0x14), + MPL3115A2_BAR_IN_LSB = (0x15), +}; + +/** MPL3115A2 status register bits **/ +enum { + MPL3115A2_REGISTER_STATUS_TDR = 0x02, + MPL3115A2_REGISTER_STATUS_PDR = 0x04, + MPL3115A2_REGISTER_STATUS_PTDR = 0x08, +}; + +/** MPL3115A2 PT DATA register bits **/ +enum { + MPL3115A2_PT_DATA_CFG = 0x13, + MPL3115A2_PT_DATA_CFG_TDEFE = 0x01, + MPL3115A2_PT_DATA_CFG_PDEFE = 0x02, + MPL3115A2_PT_DATA_CFG_DREM = 0x04, +}; + +/** MPL3115A2 control registers **/ +enum { + + MPL3115A2_CTRL_REG1 = (0x26), + MPL3115A2_CTRL_REG2 = (0x27), + MPL3115A2_CTRL_REG3 = (0x28), + MPL3115A2_CTRL_REG4 = (0x29), + MPL3115A2_CTRL_REG5 = (0x2A), +}; + +/** MPL3115A2 control register bits **/ +enum { + MPL3115A2_CTRL_REG1_SBYB = 0x01, + MPL3115A2_CTRL_REG1_OST = 0x02, + MPL3115A2_CTRL_REG1_RST = 0x04, + MPL3115A2_CTRL_REG1_RAW = 0x40, + MPL3115A2_CTRL_REG1_ALT = 0x80, + MPL3115A2_CTRL_REG1_BAR = 0x00, +}; + +/** MPL3115A2 oversample values **/ +enum { + MPL3115A2_CTRL_REG1_OS1 = 0x00, + MPL3115A2_CTRL_REG1_OS2 = 0x08, + MPL3115A2_CTRL_REG1_OS4 = 0x10, + MPL3115A2_CTRL_REG1_OS8 = 0x18, + MPL3115A2_CTRL_REG1_OS16 = 0x20, + MPL3115A2_CTRL_REG1_OS32 = 0x28, + MPL3115A2_CTRL_REG1_OS64 = 0x30, + MPL3115A2_CTRL_REG1_OS128 = 0x38, +}; + +class MPL3115A2Component : public PollingComponent, public i2c::I2CDevice { + public: + void set_temperature(sensor::Sensor *temperature) { temperature_ = temperature; } + void set_altitude(sensor::Sensor *altitude) { altitude_ = altitude; } + void set_pressure(sensor::Sensor *pressure) { pressure_ = pressure; } + + void setup() override; + void dump_config() override; + void update() override; + + float get_setup_priority() const override { return setup_priority::DATA; } + + protected: + sensor::Sensor *temperature_{nullptr}; + sensor::Sensor *altitude_{nullptr}; + sensor::Sensor *pressure_{nullptr}; + enum ErrorCode { + NONE = 0, + COMMUNICATION_FAILED, + WRONG_ID, + } error_code_{NONE}; +}; + +} // namespace mpl3115a2 +} // namespace esphome diff --git a/esphome/components/mpl3115a2/sensor.py b/esphome/components/mpl3115a2/sensor.py new file mode 100644 index 0000000000..68ed0e08a8 --- /dev/null +++ b/esphome/components/mpl3115a2/sensor.py @@ -0,0 +1,75 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + CONF_ALTITUDE, + CONF_ID, + CONF_PRESSURE, + CONF_TEMPERATURE, + DEVICE_CLASS_PRESSURE, + DEVICE_CLASS_TEMPERATURE, + STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, + UNIT_HECTOPASCAL, + UNIT_METER, +) + +CODEOWNERS = ["@kbickar"] +DEPENDENCIES = ["i2c"] + +mpl3115a2_ns = cg.esphome_ns.namespace("mpl3115a2") +MPL3115A2Component = mpl3115a2_ns.class_( + "MPL3115A2Component", cg.PollingComponent, i2c.I2CDevice +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(MPL3115A2Component), + cv.Exclusive( + CONF_PRESSURE, + "pressure", + f"{CONF_PRESSURE} and {CONF_ALTITUDE} can't be used together", + ): sensor.sensor_schema( + unit_of_measurement=UNIT_HECTOPASCAL, + accuracy_decimals=1, + device_class=DEVICE_CLASS_PRESSURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Exclusive( + CONF_ALTITUDE, + "pressure", + f"{CONF_PRESSURE} and {CONF_ALTITUDE} can't be used together", + ): sensor.sensor_schema( + unit_of_measurement=UNIT_METER, + accuracy_decimals=1, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x60)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + + if CONF_PRESSURE in config: + sens = await sensor.new_sensor(config[CONF_PRESSURE]) + cg.add(var.set_pressure(sens)) + elif CONF_ALTITUDE in config: + sens = await sensor.new_sensor(config[CONF_ALTITUDE]) + cg.add(var.set_altitude(sens)) + + if CONF_TEMPERATURE in config: + sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) + cg.add(var.set_temperature(sens)) diff --git a/esphome/components/mpu6050/mpu6050.cpp b/esphome/components/mpu6050/mpu6050.cpp index 8a0756e63c..cc426e58a2 100644 --- a/esphome/components/mpu6050/mpu6050.cpp +++ b/esphome/components/mpu6050/mpu6050.cpp @@ -23,7 +23,7 @@ const float GRAVITY_EARTH = 9.80665f; void MPU6050Component::setup() { ESP_LOGCONFIG(TAG, "Setting up MPU6050..."); uint8_t who_am_i; - if (!this->read_byte(MPU6050_REGISTER_WHO_AM_I, &who_am_i) || who_am_i != 0x68) { + if (!this->read_byte(MPU6050_REGISTER_WHO_AM_I, &who_am_i) || (who_am_i != 0x68 && who_am_i != 0x98)) { this->mark_failed(); return; } diff --git a/esphome/components/ms5611/ms5611.h b/esphome/components/ms5611/ms5611.h index b5663ad736..476db79612 100644 --- a/esphome/components/ms5611/ms5611.h +++ b/esphome/components/ms5611/ms5611.h @@ -22,8 +22,8 @@ class MS5611Component : public PollingComponent, public i2c::I2CDevice { void read_pressure_(uint32_t raw_temperature); void calculate_values_(uint32_t raw_temperature, uint32_t raw_pressure); - sensor::Sensor *temperature_sensor_; - sensor::Sensor *pressure_sensor_; + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *pressure_sensor_{nullptr}; uint16_t prom_[6]; }; diff --git a/esphome/components/nextion/switch/__init__.py b/esphome/components/nextion/switch/__init__.py index 068681fa14..91ab0cc81f 100644 --- a/esphome/components/nextion/switch/__init__.py +++ b/esphome/components/nextion/switch/__init__.py @@ -17,11 +17,7 @@ CODEOWNERS = ["@senexcrenshaw"] NextionSwitch = nextion_ns.class_("NextionSwitch", switch.Switch, cg.PollingComponent) CONFIG_SCHEMA = cv.All( - switch.SWITCH_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(NextionSwitch), - } - ) + switch.switch_schema(NextionSwitch) .extend(CONFIG_SWITCH_COMPONENT_SCHEMA) .extend(cv.polling_component_schema("never")), cv.has_exactly_one_key(CONF_COMPONENT_NAME, CONF_VARIABLE_NAME), diff --git a/esphome/components/ota/__init__.py b/esphome/components/ota/__init__.py index b3d3b7ad23..1bc4012ce2 100644 --- a/esphome/components/ota/__init__.py +++ b/esphome/components/ota/__init__.py @@ -78,6 +78,7 @@ CONFIG_SCHEMA = cv.Schema( async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) cg.add(var.set_port(config[CONF_PORT])) + cg.add_define("USE_OTA") if CONF_PASSWORD in config: cg.add(var.set_auth_password(config[CONF_PASSWORD])) cg.add_define("USE_OTA_PASSWORD") diff --git a/esphome/components/ota/ota_backend_esp_idf.cpp b/esphome/components/ota/ota_backend_esp_idf.cpp index 336b3798d9..167f8c059b 100644 --- a/esphome/components/ota/ota_backend_esp_idf.cpp +++ b/esphome/components/ota/ota_backend_esp_idf.cpp @@ -49,7 +49,7 @@ OTAResponseTypes IDFOTABackend::end() { this->md5_.calculate(); if (!this->md5_.equals_hex(this->expected_bin_md5_)) { this->abort(); - return OTA_RESPONSE_ERROR_UPDATE_END; + return OTA_RESPONSE_ERROR_MD5_MISMATCH; } esp_err_t err = esp_ota_end(this->update_handle_); this->update_handle_ = 0; diff --git a/esphome/components/ota/ota_component.cpp b/esphome/components/ota/ota_component.cpp index fa2605d589..a02d64cd08 100644 --- a/esphome/components/ota/ota_component.cpp +++ b/esphome/components/ota/ota_component.cpp @@ -21,6 +21,8 @@ static const char *const TAG = "ota"; static const uint8_t OTA_VERSION_1_0 = 1; +OTAComponent *global_ota_component = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + std::unique_ptr make_ota_backend() { #ifdef USE_ARDUINO #ifdef USE_ESP8266 @@ -35,6 +37,8 @@ std::unique_ptr make_ota_backend() { #endif // USE_ESP_IDF } +OTAComponent::OTAComponent() { global_ota_component = this; } + void OTAComponent::setup() { server_ = socket::socket_ip(SOCK_STREAM, 0); if (server_ == nullptr) { @@ -296,7 +300,7 @@ void OTAComponent::handle_() { error_code = backend->write(buf, read); if (error_code != OTA_RESPONSE_OK) { - ESP_LOGW(TAG, "Error writing binary data to flash!"); + ESP_LOGW(TAG, "Error writing binary data to flash!, error_code: %d", error_code); goto error; // NOLINT(cppcoreguidelines-avoid-goto) } total += read; @@ -321,7 +325,7 @@ void OTAComponent::handle_() { error_code = backend->end(); if (error_code != OTA_RESPONSE_OK) { - ESP_LOGW(TAG, "Error ending OTA!"); + ESP_LOGW(TAG, "Error ending OTA!, error_code: %d", error_code); goto error; // NOLINT(cppcoreguidelines-avoid-goto) } diff --git a/esphome/components/ota/ota_component.h b/esphome/components/ota/ota_component.h index 5647d52eeb..9a1c92f727 100644 --- a/esphome/components/ota/ota_component.h +++ b/esphome/components/ota/ota_component.h @@ -32,6 +32,7 @@ enum OTAResponseTypes { OTA_RESPONSE_ERROR_ESP8266_NOT_ENOUGH_SPACE = 136, OTA_RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE = 137, OTA_RESPONSE_ERROR_NO_UPDATE_PARTITION = 138, + OTA_RESPONSE_ERROR_MD5_MISMATCH = 139, OTA_RESPONSE_ERROR_UNKNOWN = 255, }; @@ -40,6 +41,7 @@ enum OTAState { OTA_COMPLETED = 0, OTA_STARTED, OTA_IN_PROGRESS, OTA_ERROR }; /// OTAComponent provides a simple way to integrate Over-the-Air updates into your app using ArduinoOTA. class OTAComponent : public Component { public: + OTAComponent(); #ifdef USE_OTA_PASSWORD void set_auth_password(const std::string &password) { password_ = password; } #endif // USE_OTA_PASSWORD @@ -102,5 +104,7 @@ class OTAComponent : public Component { #endif }; +extern OTAComponent *global_ota_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + } // namespace ota } // namespace esphome diff --git a/esphome/components/output/switch/__init__.py b/esphome/components/output/switch/__init__.py index 46135d117e..3a23c1e33f 100644 --- a/esphome/components/output/switch/__init__.py +++ b/esphome/components/output/switch/__init__.py @@ -1,7 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import output, switch -from esphome.const import CONF_ID, CONF_OUTPUT, CONF_RESTORE_MODE +from esphome.const import CONF_OUTPUT, CONF_RESTORE_MODE from .. import output_ns OutputSwitch = output_ns.class_("OutputSwitch", switch.Switch, cg.Component) @@ -16,21 +16,23 @@ RESTORE_MODES = { "RESTORE_INVERTED_DEFAULT_ON": OutputSwitchRestoreMode.OUTPUT_SWITCH_RESTORE_INVERTED_DEFAULT_ON, } -CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(OutputSwitch), - cv.Required(CONF_OUTPUT): cv.use_id(output.BinaryOutput), - cv.Optional(CONF_RESTORE_MODE, default="RESTORE_DEFAULT_OFF"): cv.enum( - RESTORE_MODES, upper=True, space="_" - ), - } -).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = ( + switch.switch_schema(OutputSwitch) + .extend( + { + cv.Required(CONF_OUTPUT): cv.use_id(output.BinaryOutput), + cv.Optional(CONF_RESTORE_MODE, default="RESTORE_DEFAULT_OFF"): cv.enum( + RESTORE_MODES, upper=True, space="_" + ), + } + ) + .extend(cv.COMPONENT_SCHEMA) +) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await switch.new_switch(config) await cg.register_component(var, config) - await switch.register_switch(var, config) output_ = await cg.get_variable(config[CONF_OUTPUT]) cg.add(var.set_output(output_)) diff --git a/esphome/components/pid/pid_climate.h b/esphome/components/pid/pid_climate.h index ff301386b6..095c00eb49 100644 --- a/esphome/components/pid/pid_climate.h +++ b/esphome/components/pid/pid_climate.h @@ -59,8 +59,8 @@ class PIDClimate : public climate::Climate, public Component { /// The sensor used for getting the current temperature sensor::Sensor *sensor_; - output::FloatOutput *cool_output_ = nullptr; - output::FloatOutput *heat_output_ = nullptr; + output::FloatOutput *cool_output_{nullptr}; + output::FloatOutput *heat_output_{nullptr}; PIDController controller_; /// Output value as reported by the PID controller, for PIDClimateSensor float output_value_; diff --git a/esphome/components/pipsolar/switch/__init__.py b/esphome/components/pipsolar/switch/__init__.py index 5ff33b10ff..7658c7d4f8 100644 --- a/esphome/components/pipsolar/switch/__init__.py +++ b/esphome/components/pipsolar/switch/__init__.py @@ -1,12 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import switch -from esphome.const import ( - CONF_ID, - CONF_INVERTED, - CONF_ICON, - ICON_POWER, -) +from esphome.const import ICON_POWER from .. import CONF_PIPSOLAR_ID, PIPSOLAR_COMPONENT_SCHEMA, pipsolar_ns DEPENDENCIES = ["uart"] @@ -29,14 +24,8 @@ TYPES = { PipsolarSwitch = pipsolar_ns.class_("PipsolarSwitch", switch.Switch, cg.Component) -PIPSWITCH_SCHEMA = switch.SWITCH_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(PipsolarSwitch), - cv.Optional(CONF_INVERTED): cv.invalid( - "Pipsolar switches do not support inverted mode!" - ), - cv.Optional(CONF_ICON, default=ICON_POWER): switch.icon, - } +PIPSWITCH_SCHEMA = switch.switch_schema( + PipsolarSwitch, icon=ICON_POWER, block_inverted=True ).extend(cv.COMPONENT_SCHEMA) CONFIG_SCHEMA = PIPSOLAR_COMPONENT_SCHEMA.extend( @@ -50,9 +39,8 @@ async def to_code(config): for type, (on, off) in TYPES.items(): if type in config: conf = config[type] - var = cg.new_Pvariable(conf[CONF_ID]) + var = await switch.new_switch(conf) await cg.register_component(var, conf) - await switch.register_switch(var, conf) cg.add(getattr(paren, f"set_{type}_switch")(var)) cg.add(var.set_parent(paren)) cg.add(var.set_on_command(on)) diff --git a/esphome/components/prometheus/__init__.py b/esphome/components/prometheus/__init__.py index e7c0459251..5b63710c6a 100644 --- a/esphome/components/prometheus/__init__.py +++ b/esphome/components/prometheus/__init__.py @@ -2,16 +2,27 @@ import esphome.config_validation as cv import esphome.codegen as cg from esphome.const import ( CONF_ID, + CONF_NAME, CONF_INCLUDE_INTERNAL, + CONF_RELABEL, ) from esphome.components.web_server_base import CONF_WEB_SERVER_BASE_ID from esphome.components import web_server_base +from esphome.cpp_types import EntityBase AUTO_LOAD = ["web_server_base"] prometheus_ns = cg.esphome_ns.namespace("prometheus") PrometheusHandler = prometheus_ns.class_("PrometheusHandler", cg.Component) +CUSTOMIZED_ENTITY = cv.Schema( + { + cv.Optional(CONF_ID): cv.string_strict, + cv.Optional(CONF_NAME): cv.string_strict, + }, + cv.has_at_least_one_key, +) + CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(): cv.declare_id(PrometheusHandler), @@ -19,6 +30,11 @@ CONFIG_SCHEMA = cv.Schema( web_server_base.WebServerBase ), cv.Optional(CONF_INCLUDE_INTERNAL, default=False): cv.boolean, + cv.Optional(CONF_RELABEL, default={}): cv.Schema( + { + cv.use_id(EntityBase): CUSTOMIZED_ENTITY, + } + ), }, cv.only_with_arduino, ).extend(cv.COMPONENT_SCHEMA) @@ -33,3 +49,10 @@ async def to_code(config): await cg.register_component(var, config) cg.add(var.set_include_internal(config[CONF_INCLUDE_INTERNAL])) + + for key, value in config[CONF_RELABEL].items(): + entity = await cg.get_variable(key) + if CONF_ID in value: + cg.add(var.add_label_id(entity, value[CONF_ID])) + if CONF_NAME in value: + cg.add(var.add_label_name(entity, value[CONF_NAME])) diff --git a/esphome/components/prometheus/prometheus_handler.cpp b/esphome/components/prometheus/prometheus_handler.cpp index a52347ba57..abb5111aaf 100644 --- a/esphome/components/prometheus/prometheus_handler.cpp +++ b/esphome/components/prometheus/prometheus_handler.cpp @@ -54,6 +54,16 @@ void PrometheusHandler::handleRequest(AsyncWebServerRequest *req) { req->send(stream); } +std::string PrometheusHandler::relabel_id_(EntityBase *obj) { + auto item = relabel_map_id_.find(obj); + return item == relabel_map_id_.end() ? obj->get_object_id() : item->second; +} + +std::string PrometheusHandler::relabel_name_(EntityBase *obj) { + auto item = relabel_map_name_.find(obj); + return item == relabel_map_name_.end() ? obj->get_name() : item->second; +} + // Type-specific implementation #ifdef USE_SENSOR void PrometheusHandler::sensor_type_(AsyncResponseStream *stream) { @@ -66,15 +76,15 @@ void PrometheusHandler::sensor_row_(AsyncResponseStream *stream, sensor::Sensor if (!std::isnan(obj->state)) { // We have a valid value, output this value stream->print(F("esphome_sensor_failed{id=\"")); - stream->print(obj->get_object_id().c_str()); + stream->print(relabel_id_(obj).c_str()); stream->print(F("\",name=\"")); - stream->print(obj->get_name().c_str()); + stream->print(relabel_name_(obj).c_str()); stream->print(F("\"} 0\n")); // Data itself stream->print(F("esphome_sensor_value{id=\"")); - stream->print(obj->get_object_id().c_str()); + stream->print(relabel_id_(obj).c_str()); stream->print(F("\",name=\"")); - stream->print(obj->get_name().c_str()); + stream->print(relabel_name_(obj).c_str()); stream->print(F("\",unit=\"")); stream->print(obj->get_unit_of_measurement().c_str()); stream->print(F("\"} ")); @@ -83,9 +93,9 @@ void PrometheusHandler::sensor_row_(AsyncResponseStream *stream, sensor::Sensor } else { // Invalid state stream->print(F("esphome_sensor_failed{id=\"")); - stream->print(obj->get_object_id().c_str()); + stream->print(relabel_id_(obj).c_str()); stream->print(F("\",name=\"")); - stream->print(obj->get_name().c_str()); + stream->print(relabel_name_(obj).c_str()); stream->print(F("\"} 1\n")); } } @@ -103,24 +113,24 @@ void PrometheusHandler::binary_sensor_row_(AsyncResponseStream *stream, binary_s if (obj->has_state()) { // We have a valid value, output this value stream->print(F("esphome_binary_sensor_failed{id=\"")); - stream->print(obj->get_object_id().c_str()); + stream->print(relabel_id_(obj).c_str()); stream->print(F("\",name=\"")); - stream->print(obj->get_name().c_str()); + stream->print(relabel_name_(obj).c_str()); stream->print(F("\"} 0\n")); // Data itself stream->print(F("esphome_binary_sensor_value{id=\"")); - stream->print(obj->get_object_id().c_str()); + stream->print(relabel_id_(obj).c_str()); stream->print(F("\",name=\"")); - stream->print(obj->get_name().c_str()); + stream->print(relabel_name_(obj).c_str()); stream->print(F("\"} ")); stream->print(obj->state); stream->print('\n'); } else { // Invalid state stream->print(F("esphome_binary_sensor_failed{id=\"")); - stream->print(obj->get_object_id().c_str()); + stream->print(relabel_id_(obj).c_str()); stream->print(F("\",name=\"")); - stream->print(obj->get_name().c_str()); + stream->print(relabel_name_(obj).c_str()); stream->print(F("\"} 1\n")); } } @@ -137,24 +147,24 @@ void PrometheusHandler::fan_row_(AsyncResponseStream *stream, fan::Fan *obj) { if (obj->is_internal() && !this->include_internal_) return; stream->print(F("esphome_fan_failed{id=\"")); - stream->print(obj->get_object_id().c_str()); + stream->print(relabel_id_(obj).c_str()); stream->print(F("\",name=\"")); - stream->print(obj->get_name().c_str()); + stream->print(relabel_name_(obj).c_str()); stream->print(F("\"} 0\n")); // Data itself stream->print(F("esphome_fan_value{id=\"")); - stream->print(obj->get_object_id().c_str()); + stream->print(relabel_id_(obj).c_str()); stream->print(F("\",name=\"")); - stream->print(obj->get_name().c_str()); + stream->print(relabel_name_(obj).c_str()); stream->print(F("\"} ")); stream->print(obj->state); stream->print('\n'); // Speed if available if (obj->get_traits().supports_speed()) { stream->print(F("esphome_fan_speed{id=\"")); - stream->print(obj->get_object_id().c_str()); + stream->print(relabel_id_(obj).c_str()); stream->print(F("\",name=\"")); - stream->print(obj->get_name().c_str()); + stream->print(relabel_name_(obj).c_str()); stream->print(F("\"} ")); stream->print(obj->speed); stream->print('\n'); @@ -162,9 +172,9 @@ void PrometheusHandler::fan_row_(AsyncResponseStream *stream, fan::Fan *obj) { // Oscillation if available if (obj->get_traits().supports_oscillation()) { stream->print(F("esphome_fan_oscillation{id=\"")); - stream->print(obj->get_object_id().c_str()); + stream->print(relabel_id_(obj).c_str()); stream->print(F("\",name=\"")); - stream->print(obj->get_name().c_str()); + stream->print(relabel_name_(obj).c_str()); stream->print(F("\"} ")); stream->print(obj->oscillating); stream->print('\n'); @@ -183,9 +193,9 @@ void PrometheusHandler::light_row_(AsyncResponseStream *stream, light::LightStat return; // State stream->print(F("esphome_light_state{id=\"")); - stream->print(obj->get_object_id().c_str()); + stream->print(relabel_id_(obj).c_str()); stream->print(F("\",name=\"")); - stream->print(obj->get_name().c_str()); + stream->print(relabel_name_(obj).c_str()); stream->print(F("\"} ")); stream->print(obj->remote_values.is_on()); stream->print(F("\n")); @@ -195,37 +205,37 @@ void PrometheusHandler::light_row_(AsyncResponseStream *stream, light::LightStat color.as_brightness(&brightness); color.as_rgbw(&r, &g, &b, &w); stream->print(F("esphome_light_color{id=\"")); - stream->print(obj->get_object_id().c_str()); + stream->print(relabel_id_(obj).c_str()); stream->print(F("\",name=\"")); - stream->print(obj->get_name().c_str()); + stream->print(relabel_name_(obj).c_str()); stream->print(F("\",channel=\"brightness\"} ")); stream->print(brightness); stream->print(F("\n")); stream->print(F("esphome_light_color{id=\"")); - stream->print(obj->get_object_id().c_str()); + stream->print(relabel_id_(obj).c_str()); stream->print(F("\",name=\"")); - stream->print(obj->get_name().c_str()); + stream->print(relabel_name_(obj).c_str()); stream->print(F("\",channel=\"r\"} ")); stream->print(r); stream->print(F("\n")); stream->print(F("esphome_light_color{id=\"")); - stream->print(obj->get_object_id().c_str()); + stream->print(relabel_id_(obj).c_str()); stream->print(F("\",name=\"")); - stream->print(obj->get_name().c_str()); + stream->print(relabel_name_(obj).c_str()); stream->print(F("\",channel=\"g\"} ")); stream->print(g); stream->print(F("\n")); stream->print(F("esphome_light_color{id=\"")); - stream->print(obj->get_object_id().c_str()); + stream->print(relabel_id_(obj).c_str()); stream->print(F("\",name=\"")); - stream->print(obj->get_name().c_str()); + stream->print(relabel_name_(obj).c_str()); stream->print(F("\",channel=\"b\"} ")); stream->print(b); stream->print(F("\n")); stream->print(F("esphome_light_color{id=\"")); - stream->print(obj->get_object_id().c_str()); + stream->print(relabel_id_(obj).c_str()); stream->print(F("\",name=\"")); - stream->print(obj->get_name().c_str()); + stream->print(relabel_name_(obj).c_str()); stream->print(F("\",channel=\"w\"} ")); stream->print(w); stream->print(F("\n")); @@ -233,15 +243,15 @@ void PrometheusHandler::light_row_(AsyncResponseStream *stream, light::LightStat std::string effect = obj->get_effect_name(); if (effect == "None") { stream->print(F("esphome_light_effect_active{id=\"")); - stream->print(obj->get_object_id().c_str()); + stream->print(relabel_id_(obj).c_str()); stream->print(F("\",name=\"")); - stream->print(obj->get_name().c_str()); + stream->print(relabel_name_(obj).c_str()); stream->print(F("\",effect=\"None\"} 0\n")); } else { stream->print(F("esphome_light_effect_active{id=\"")); - stream->print(obj->get_object_id().c_str()); + stream->print(relabel_id_(obj).c_str()); stream->print(F("\",name=\"")); - stream->print(obj->get_name().c_str()); + stream->print(relabel_name_(obj).c_str()); stream->print(F("\",effect=\"")); stream->print(effect.c_str()); stream->print(F("\"} 1\n")); @@ -260,23 +270,23 @@ void PrometheusHandler::cover_row_(AsyncResponseStream *stream, cover::Cover *ob if (!std::isnan(obj->position)) { // We have a valid value, output this value stream->print(F("esphome_cover_failed{id=\"")); - stream->print(obj->get_object_id().c_str()); + stream->print(relabel_id_(obj).c_str()); stream->print(F("\",name=\"")); - stream->print(obj->get_name().c_str()); + stream->print(relabel_name_(obj).c_str()); stream->print(F("\"} 0\n")); // Data itself stream->print(F("esphome_cover_value{id=\"")); - stream->print(obj->get_object_id().c_str()); + stream->print(relabel_id_(obj).c_str()); stream->print(F("\",name=\"")); - stream->print(obj->get_name().c_str()); + stream->print(relabel_name_(obj).c_str()); stream->print(F("\"} ")); stream->print(obj->position); stream->print('\n'); if (obj->get_traits().get_supports_tilt()) { stream->print(F("esphome_cover_tilt{id=\"")); - stream->print(obj->get_object_id().c_str()); + stream->print(relabel_id_(obj).c_str()); stream->print(F("\",name=\"")); - stream->print(obj->get_name().c_str()); + stream->print(relabel_name_(obj).c_str()); stream->print(F("\"} ")); stream->print(obj->tilt); stream->print('\n'); @@ -284,9 +294,9 @@ void PrometheusHandler::cover_row_(AsyncResponseStream *stream, cover::Cover *ob } else { // Invalid state stream->print(F("esphome_cover_failed{id=\"")); - stream->print(obj->get_object_id().c_str()); + stream->print(relabel_id_(obj).c_str()); stream->print(F("\",name=\"")); - stream->print(obj->get_name().c_str()); + stream->print(relabel_name_(obj).c_str()); stream->print(F("\"} 1\n")); } } @@ -301,15 +311,15 @@ void PrometheusHandler::switch_row_(AsyncResponseStream *stream, switch_::Switch if (obj->is_internal() && !this->include_internal_) return; stream->print(F("esphome_switch_failed{id=\"")); - stream->print(obj->get_object_id().c_str()); + stream->print(relabel_id_(obj).c_str()); stream->print(F("\",name=\"")); - stream->print(obj->get_name().c_str()); + stream->print(relabel_name_(obj).c_str()); stream->print(F("\"} 0\n")); // Data itself stream->print(F("esphome_switch_value{id=\"")); - stream->print(obj->get_object_id().c_str()); + stream->print(relabel_id_(obj).c_str()); stream->print(F("\",name=\"")); - stream->print(obj->get_name().c_str()); + stream->print(relabel_name_(obj).c_str()); stream->print(F("\"} ")); stream->print(obj->state); stream->print('\n'); @@ -325,15 +335,15 @@ void PrometheusHandler::lock_row_(AsyncResponseStream *stream, lock::Lock *obj) if (obj->is_internal() && !this->include_internal_) return; stream->print(F("esphome_lock_failed{id=\"")); - stream->print(obj->get_object_id().c_str()); + stream->print(relabel_id_(obj).c_str()); stream->print(F("\",name=\"")); - stream->print(obj->get_name().c_str()); + stream->print(relabel_name_(obj).c_str()); stream->print(F("\"} 0\n")); // Data itself stream->print(F("esphome_lock_value{id=\"")); - stream->print(obj->get_object_id().c_str()); + stream->print(relabel_id_(obj).c_str()); stream->print(F("\",name=\"")); - stream->print(obj->get_name().c_str()); + stream->print(relabel_name_(obj).c_str()); stream->print(F("\"} ")); stream->print(obj->state); stream->print('\n'); diff --git a/esphome/components/prometheus/prometheus_handler.h b/esphome/components/prometheus/prometheus_handler.h index b378e46ea3..f416ecf246 100644 --- a/esphome/components/prometheus/prometheus_handler.h +++ b/esphome/components/prometheus/prometheus_handler.h @@ -2,6 +2,9 @@ #ifdef USE_ARDUINO +#include +#include + #include "esphome/components/web_server_base/web_server_base.h" #include "esphome/core/controller.h" #include "esphome/core/component.h" @@ -20,6 +23,20 @@ class PrometheusHandler : public AsyncWebHandler, public Component { */ void set_include_internal(bool include_internal) { include_internal_ = include_internal; } + /** Add the value for an entity's "id" label. + * + * @param obj The entity for which to set the "id" label + * @param value The value for the "id" label + */ + void add_label_id(EntityBase *obj, const std::string &value) { relabel_map_id_.insert({obj, value}); } + + /** Add the value for an entity's "name" label. + * + * @param obj The entity for which to set the "name" label + * @param value The value for the "name" label + */ + void add_label_name(EntityBase *obj, const std::string &value) { relabel_map_name_.insert({obj, value}); } + bool canHandle(AsyncWebServerRequest *request) override { if (request->method() == HTTP_GET) { if (request->url() == "/metrics") @@ -41,6 +58,9 @@ class PrometheusHandler : public AsyncWebHandler, public Component { } protected: + std::string relabel_id_(EntityBase *obj); + std::string relabel_name_(EntityBase *obj); + #ifdef USE_SENSOR /// Return the type for prometheus void sensor_type_(AsyncResponseStream *stream); @@ -92,6 +112,8 @@ class PrometheusHandler : public AsyncWebHandler, public Component { web_server_base::WebServerBase *base_; bool include_internal_{false}; + std::map relabel_map_id_; + std::map relabel_map_name_; }; } // namespace prometheus diff --git a/esphome/components/pulse_counter/pulse_counter_sensor.cpp b/esphome/components/pulse_counter/pulse_counter_sensor.cpp index 002f6dcac9..1f50360fed 100644 --- a/esphome/components/pulse_counter/pulse_counter_sensor.cpp +++ b/esphome/components/pulse_counter/pulse_counter_sensor.cpp @@ -8,8 +8,16 @@ static const char *const TAG = "pulse_counter"; const char *const EDGE_MODE_TO_STRING[] = {"DISABLE", "INCREMENT", "DECREMENT"}; -#ifndef HAS_PCNT -void IRAM_ATTR PulseCounterStorage::gpio_intr(PulseCounterStorage *arg) { +#ifdef HAS_PCNT +PulseCounterStorageBase *get_storage(bool hw_pcnt) { + return (hw_pcnt ? (PulseCounterStorageBase *) (new HwPulseCounterStorage) + : (PulseCounterStorageBase *) (new BasicPulseCounterStorage)); +} +#else +PulseCounterStorageBase *get_storage(bool) { return new BasicPulseCounterStorage; } +#endif + +void IRAM_ATTR BasicPulseCounterStorage::gpio_intr(BasicPulseCounterStorage *arg) { const uint32_t now = micros(); const bool discard = now - arg->last_pulse < arg->filter_us; arg->last_pulse = now; @@ -28,23 +36,22 @@ void IRAM_ATTR PulseCounterStorage::gpio_intr(PulseCounterStorage *arg) { break; } } -bool PulseCounterStorage::pulse_counter_setup(InternalGPIOPin *pin) { +bool BasicPulseCounterStorage::pulse_counter_setup(InternalGPIOPin *pin) { this->pin = pin; this->pin->setup(); this->isr_pin = this->pin->to_isr(); - this->pin->attach_interrupt(PulseCounterStorage::gpio_intr, this, gpio::INTERRUPT_ANY_EDGE); + this->pin->attach_interrupt(BasicPulseCounterStorage::gpio_intr, this, gpio::INTERRUPT_ANY_EDGE); return true; } -pulse_counter_t PulseCounterStorage::read_raw_value() { +pulse_counter_t BasicPulseCounterStorage::read_raw_value() { pulse_counter_t counter = this->counter; pulse_counter_t ret = counter - this->last_value; this->last_value = counter; return ret; } -#endif #ifdef HAS_PCNT -bool PulseCounterStorage::pulse_counter_setup(InternalGPIOPin *pin) { +bool HwPulseCounterStorage::pulse_counter_setup(InternalGPIOPin *pin) { static pcnt_unit_t next_pcnt_unit = PCNT_UNIT_0; this->pin = pin; this->pin->setup(); @@ -127,7 +134,7 @@ bool PulseCounterStorage::pulse_counter_setup(InternalGPIOPin *pin) { } return true; } -pulse_counter_t PulseCounterStorage::read_raw_value() { +pulse_counter_t HwPulseCounterStorage::read_raw_value() { pulse_counter_t counter; pcnt_get_counter_value(this->pcnt_unit, &counter); pulse_counter_t ret = counter - this->last_value; diff --git a/esphome/components/pulse_counter/pulse_counter_sensor.h b/esphome/components/pulse_counter/pulse_counter_sensor.h index f81d20a646..ef944f106f 100644 --- a/esphome/components/pulse_counter/pulse_counter_sensor.h +++ b/esphome/components/pulse_counter/pulse_counter_sensor.h @@ -24,31 +24,44 @@ using pulse_counter_t = int16_t; using pulse_counter_t = int32_t; #endif -struct PulseCounterStorage { - bool pulse_counter_setup(InternalGPIOPin *pin); - pulse_counter_t read_raw_value(); - - static void gpio_intr(PulseCounterStorage *arg); - -#ifndef HAS_PCNT - volatile pulse_counter_t counter{0}; - volatile uint32_t last_pulse{0}; -#endif +struct PulseCounterStorageBase { + virtual bool pulse_counter_setup(InternalGPIOPin *pin) = 0; + virtual pulse_counter_t read_raw_value() = 0; InternalGPIOPin *pin; -#ifdef HAS_PCNT - pcnt_unit_t pcnt_unit; -#else - ISRInternalGPIOPin isr_pin; -#endif PulseCounterCountMode rising_edge_mode{PULSE_COUNTER_INCREMENT}; PulseCounterCountMode falling_edge_mode{PULSE_COUNTER_DISABLE}; uint32_t filter_us{0}; pulse_counter_t last_value{0}; }; +struct BasicPulseCounterStorage : public PulseCounterStorageBase { + static void gpio_intr(BasicPulseCounterStorage *arg); + + bool pulse_counter_setup(InternalGPIOPin *pin) override; + pulse_counter_t read_raw_value() override; + + volatile pulse_counter_t counter{0}; + volatile uint32_t last_pulse{0}; + + ISRInternalGPIOPin isr_pin; +}; + +#ifdef HAS_PCNT +struct HwPulseCounterStorage : public PulseCounterStorageBase { + bool pulse_counter_setup(InternalGPIOPin *pin) override; + pulse_counter_t read_raw_value() override; + + pcnt_unit_t pcnt_unit; +}; +#endif + +PulseCounterStorageBase *get_storage(bool hw_pcnt = false); + class PulseCounterSensor : public sensor::Sensor, public PollingComponent { public: + explicit PulseCounterSensor(bool hw_pcnt = false) : storage_(*get_storage(hw_pcnt)) {} + void set_pin(InternalGPIOPin *pin) { pin_ = pin; } void set_rising_edge_mode(PulseCounterCountMode mode) { storage_.rising_edge_mode = mode; } void set_falling_edge_mode(PulseCounterCountMode mode) { storage_.falling_edge_mode = mode; } @@ -65,10 +78,10 @@ class PulseCounterSensor : public sensor::Sensor, public PollingComponent { protected: InternalGPIOPin *pin_; - PulseCounterStorage storage_; + PulseCounterStorageBase &storage_; uint32_t last_time_{0}; uint32_t current_total_{0}; - sensor::Sensor *total_sensor_; + sensor::Sensor *total_sensor_{nullptr}; }; } // namespace pulse_counter diff --git a/esphome/components/pulse_counter/sensor.py b/esphome/components/pulse_counter/sensor.py index 88f53bdf77..27364a34b3 100644 --- a/esphome/components/pulse_counter/sensor.py +++ b/esphome/components/pulse_counter/sensor.py @@ -20,6 +20,8 @@ from esphome.const import ( ) from esphome.core import CORE +CONF_USE_PCNT = "use_pcnt" + pulse_counter_ns = cg.esphome_ns.namespace("pulse_counter") PulseCounterCountMode = pulse_counter_ns.enum("PulseCounterCountMode") COUNT_MODES = { @@ -40,11 +42,19 @@ SetTotalPulsesAction = pulse_counter_ns.class_( def validate_internal_filter(value): - value = cv.positive_time_period_microseconds(value) - if CORE.is_esp32: - if value.total_microseconds > 13: - raise cv.Invalid("Maximum internal filter value for ESP32 is 13us") - return value + use_pcnt = value.get(CONF_USE_PCNT) + if CORE.is_esp8266 and use_pcnt: + raise cv.Invalid( + "Using hardware PCNT is only available on ESP32", + [CONF_USE_PCNT], + ) + + if CORE.is_esp32 and use_pcnt: + if value.get(CONF_INTERNAL_FILTER).total_microseconds > 13: + raise cv.Invalid( + "Maximum internal filter value when using ESP32 hardware PCNT is 13us", + [CONF_INTERNAL_FILTER], + ) return value @@ -69,7 +79,7 @@ def validate_count_mode(value): return value -CONFIG_SCHEMA = ( +CONFIG_SCHEMA = cv.All( sensor.sensor_schema( PulseCounterSensor, unit_of_measurement=UNIT_PULSES_PER_MINUTE, @@ -95,21 +105,25 @@ CONFIG_SCHEMA = ( ), validate_count_mode, ), - cv.Optional(CONF_INTERNAL_FILTER, default="13us"): validate_internal_filter, + cv.SplitDefault(CONF_USE_PCNT, esp32=True): cv.boolean, + cv.Optional( + CONF_INTERNAL_FILTER, default="13us" + ): cv.positive_time_period_microseconds, cv.Optional(CONF_TOTAL): sensor.sensor_schema( unit_of_measurement=UNIT_PULSES, icon=ICON_PULSE, accuracy_decimals=0, state_class=STATE_CLASS_TOTAL_INCREASING, ), - } + }, ) - .extend(cv.polling_component_schema("60s")) + .extend(cv.polling_component_schema("60s")), + validate_internal_filter, ) async def to_code(config): - var = await sensor.new_sensor(config) + var = await sensor.new_sensor(config, config.get(CONF_USE_PCNT)) await cg.register_component(var, config) pin = await cg.gpio_pin_expression(config[CONF_PIN]) diff --git a/esphome/components/pulse_meter/pulse_meter_sensor.h b/esphome/components/pulse_meter/pulse_meter_sensor.h index cf08f8c92d..bf50eab6ff 100644 --- a/esphome/components/pulse_meter/pulse_meter_sensor.h +++ b/esphome/components/pulse_meter/pulse_meter_sensor.h @@ -31,11 +31,11 @@ class PulseMeterSensor : public sensor::Sensor, public Component { protected: static void gpio_intr(PulseMeterSensor *sensor); - InternalGPIOPin *pin_ = nullptr; + InternalGPIOPin *pin_{nullptr}; ISRInternalGPIOPin isr_pin_; uint32_t filter_us_ = 0; uint32_t timeout_us_ = 1000000UL * 60UL * 5UL; - sensor::Sensor *total_sensor_ = nullptr; + sensor::Sensor *total_sensor_{nullptr}; InternalFilterMode filter_mode_{FILTER_EDGE}; Deduplicator pulse_width_dedupe_; diff --git a/esphome/components/pvvx_mithermometer/display/__init__.py b/esphome/components/pvvx_mithermometer/display/__init__.py new file mode 100644 index 0000000000..d935638933 --- /dev/null +++ b/esphome/components/pvvx_mithermometer/display/__init__.py @@ -0,0 +1,56 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import ble_client, display, time +from esphome.const import ( + CONF_AUTO_CLEAR_ENABLED, + CONF_DISCONNECT_DELAY, + CONF_ID, + CONF_LAMBDA, + CONF_TIME_ID, + CONF_VALIDITY_PERIOD, +) + +DEPENDENCIES = ["ble_client"] + +pvvx_ns = cg.esphome_ns.namespace("pvvx_mithermometer") +PVVXDisplay = pvvx_ns.class_( + "PVVXDisplay", cg.PollingComponent, ble_client.BLEClientNode +) +PVVXDisplayRef = PVVXDisplay.operator("ref") + +CONFIG_SCHEMA = ( + display.BASIC_DISPLAY_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(PVVXDisplay), + cv.Optional(CONF_TIME_ID): cv.use_id(time.RealTimeClock), + cv.Optional(CONF_AUTO_CLEAR_ENABLED, default=True): cv.boolean, + cv.Optional(CONF_DISCONNECT_DELAY, default="5s"): cv.positive_time_period, + cv.Optional(CONF_VALIDITY_PERIOD, default="5min"): cv.All( + cv.positive_time_period_seconds, + cv.Range(max=cv.TimePeriod(seconds=65535)), + ), + } + ) + .extend(ble_client.BLE_CLIENT_SCHEMA) + .extend(cv.polling_component_schema("60s")) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await display.register_display(var, config) + await ble_client.register_ble_node(var, config) + cg.add(var.set_disconnect_delay(config[CONF_DISCONNECT_DELAY].total_milliseconds)) + cg.add(var.set_auto_clear(config[CONF_AUTO_CLEAR_ENABLED])) + cg.add(var.set_validity_period(config[CONF_VALIDITY_PERIOD].total_seconds)) + + if CONF_TIME_ID in config: + time_ = await cg.get_variable(config[CONF_TIME_ID]) + cg.add(var.set_time(time_)) + + if CONF_LAMBDA in config: + lambda_ = await cg.process_lambda( + config[CONF_LAMBDA], [(PVVXDisplayRef, "it")], return_type=cg.void + ) + cg.add(var.set_writer(lambda_)) diff --git a/esphome/components/pvvx_mithermometer/display/pvvx_display.cpp b/esphome/components/pvvx_mithermometer/display/pvvx_display.cpp new file mode 100644 index 0000000000..21638ef7e4 --- /dev/null +++ b/esphome/components/pvvx_mithermometer/display/pvvx_display.cpp @@ -0,0 +1,154 @@ +#include "pvvx_display.h" +#include "esphome/core/log.h" + +#ifdef USE_ESP32 +namespace esphome { +namespace pvvx_mithermometer { + +static const char *const TAG = "display.pvvx_mithermometer"; + +void PVVXDisplay::dump_config() { + ESP_LOGCONFIG(TAG, "PVVX MiThermometer display:"); + 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()); + ESP_LOGCONFIG(TAG, " Auto clear : %s", YESNO(this->auto_clear_enabled_)); + ESP_LOGCONFIG(TAG, " Set time on connection: %s", YESNO(this->time_ != nullptr)); + ESP_LOGCONFIG(TAG, " Disconnect delay : %dms", this->disconnect_delay_ms_); + LOG_UPDATE_INTERVAL(this); +} + +void PVVXDisplay::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: + ESP_LOGV(TAG, "[%s] Connected successfully!", this->parent_->address_str().c_str()); + this->delayed_disconnect_(); + break; + case ESP_GATTC_DISCONNECT_EVT: + ESP_LOGV(TAG, "[%s] Disconnected", this->parent_->address_str().c_str()); + this->connection_established_ = false; + this->cancel_timeout("disconnect"); + this->char_handle_ = 0; + break; + case ESP_GATTC_SEARCH_CMPL_EVT: { + auto *chr = this->parent_->get_characteristic(this->service_uuid_, this->char_uuid_); + if (chr == nullptr) { + ESP_LOGW(TAG, "[%s] Characteristic not found.", this->parent_->address_str().c_str()); + break; + } + this->connection_established_ = true; + this->char_handle_ = chr->handle; +#ifdef USE_TIME + this->sync_time_(); +#endif + this->display(); + break; + } + default: + break; + } +} + +void PVVXDisplay::update() { + if (this->auto_clear_enabled_) + this->clear(); + if (this->writer_.has_value()) + (*this->writer_)(*this); + this->display(); +} + +void PVVXDisplay::display() { + if (!this->parent_->enabled) { + ESP_LOGD(TAG, "[%s] BLE client not enabled. Init connection.", this->parent_->address_str().c_str()); + this->parent_->set_enabled(true); + return; + } + if (!this->connection_established_) { + ESP_LOGW(TAG, "[%s] Not connected to BLE client. State update can not be written.", + this->parent_->address_str().c_str()); + return; + } + if (!this->char_handle_) { + ESP_LOGW(TAG, "[%s] No ble handle to BLE client. State update can not be written.", + this->parent_->address_str().c_str()); + return; + } + ESP_LOGD(TAG, "[%s] Send to display: bignum %d, smallnum: %d, cfg: 0x%02x, validity period: %u.", + this->parent_->address_str().c_str(), this->bignum_, this->smallnum_, this->cfg_, this->validity_period_); + uint8_t blk[8] = {}; + blk[0] = 0x22; + blk[1] = this->bignum_ & 0xff; + blk[2] = (this->bignum_ >> 8) & 0xff; + blk[3] = this->smallnum_ & 0xff; + blk[4] = (this->smallnum_ >> 8) & 0xff; + blk[5] = this->validity_period_ & 0xff; + blk[6] = (this->validity_period_ >> 8) & 0xff; + blk[7] = this->cfg_; + this->send_to_setup_char_(blk, sizeof(blk)); +} + +void PVVXDisplay::setcfgbit_(uint8_t bit, bool value) { + uint8_t mask = 1 << bit; + if (value) { + this->cfg_ |= mask; + } else { + this->cfg_ &= (0xFF ^ mask); + } +} + +void PVVXDisplay::send_to_setup_char_(uint8_t *blk, size_t size) { + if (!this->connection_established_) { + ESP_LOGW(TAG, "[%s] Not connected to BLE client.", this->parent_->address_str().c_str()); + return; + } + auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, size, blk, + ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); + if (status) { + ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status); + } else { + ESP_LOGV(TAG, "[%s] send %u bytes", this->parent_->address_str().c_str(), size); + this->delayed_disconnect_(); + } +} + +void PVVXDisplay::delayed_disconnect_() { + if (this->disconnect_delay_ms_ == 0) + return; + this->cancel_timeout("disconnect"); + this->set_timeout("disconnect", this->disconnect_delay_ms_, [this]() { this->parent_->set_enabled(false); }); +} + +#ifdef USE_TIME +void PVVXDisplay::sync_time_() { + if (this->time_ == nullptr) + return; + if (!this->connection_established_) { + ESP_LOGW(TAG, "[%s] Not connected to BLE client. Time can not be synced.", this->parent_->address_str().c_str()); + return; + } + if (!this->char_handle_) { + ESP_LOGW(TAG, "[%s] No ble handle to BLE client. Time can not be synced.", this->parent_->address_str().c_str()); + return; + } + auto time = this->time_->now(); + if (!time.is_valid()) { + ESP_LOGW(TAG, "[%s] Time is not yet valid. Time can not be synced.", this->parent_->address_str().c_str()); + return; + } + time.recalc_timestamp_utc(true); // calculate timestamp of local time + uint8_t blk[5] = {}; + ESP_LOGD(TAG, "[%s] Sync time with timestamp %lu.", this->parent_->address_str().c_str(), time.timestamp); + blk[0] = 0x23; + blk[1] = time.timestamp & 0xff; + blk[2] = (time.timestamp >> 8) & 0xff; + blk[3] = (time.timestamp >> 16) & 0xff; + blk[4] = (time.timestamp >> 24) & 0xff; + this->send_to_setup_char_(blk, sizeof(blk)); +} +#endif + +} // namespace pvvx_mithermometer +} // namespace esphome + +#endif diff --git a/esphome/components/pvvx_mithermometer/display/pvvx_display.h b/esphome/components/pvvx_mithermometer/display/pvvx_display.h new file mode 100644 index 0000000000..c7e7cc04fb --- /dev/null +++ b/esphome/components/pvvx_mithermometer/display/pvvx_display.h @@ -0,0 +1,133 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/defines.h" +#include "esphome/components/ble_client/ble_client.h" + +#ifdef USE_ESP32 +#include +#ifdef USE_TIME +#include "esphome/components/time/real_time_clock.h" +#endif + +namespace esphome { +namespace pvvx_mithermometer { + +class PVVXDisplay; + +/// Possible units for the big number +enum UNIT { + UNIT_NONE = 0, ///< do not show a unit + UNIT_DEG_GHE, ///< show "°Г" + UNIT_MINUS, ///< show " -" + UNIT_DEG_F, ///< show "°F" + UNIT_LOWDASH, ///< show " _" + UNIT_DEG_C, ///< show "°C" + UNIT_LINES, ///< show " =" + UNIT_DEG_E, ///< show "°E" +}; + +using pvvx_writer_t = std::function; + +class PVVXDisplay : public ble_client::BLEClientNode, public PollingComponent { + public: + void set_writer(pvvx_writer_t &&writer) { this->writer_ = writer; } + void set_auto_clear(bool auto_clear_enabled) { this->auto_clear_enabled_ = auto_clear_enabled; } + void set_disconnect_delay(uint32_t ms) { this->disconnect_delay_ms_ = ms; } + + void dump_config() override; + + float get_setup_priority() const override { return setup_priority::DATA; } + + void update() override; + + void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t *param) override; + + /// Set validity period of the display information in seconds (1..65535) + void set_validity_period(uint16_t validity_period) { this->validity_period_ = validity_period; } + /// Clear the screen + void clear() { + this->bignum_ = 0; + this->smallnum_ = 0; + this->cfg_ = 0; + } + /** + * Print the big number + * + * Valid values are from -99.5 to 1999.5. Smaller values are displayed as Lo, higher as Hi. + * It will printed as it fits in the screen. + */ + void print_bignum(float bignum) { this->bignum_ = bignum * 10; } + /** + * Print the small number + * + * Valid values are from -9 to 99. Smaller values are displayed as Lo, higher as Hi. + */ + void print_smallnum(float smallnum) { this->smallnum_ = smallnum; } + /** + * Print a happy face + * + * Can be combined with print_sad() print_bracket(). + * Possible ouputs are: + * + * @verbatim + * bracket sad happy + * 0 0 0 " " + * 0 0 1 " ^_^ " + * 0 1 0 " -∧- " + * 0 1 1 " Δ△Δ " + * 1 0 0 "( )" + * 1 0 1 "(^_^)" + * 1 1 0 "(-∧-)" + * 1 1 1 "(Δ△Δ)" + * @endverbatim + */ + void print_happy(bool happy = true) { this->setcfgbit_(0, happy); } + /// Print a sad face + void print_sad(bool sad = true) { this->setcfgbit_(1, sad); } + /// Print round brackets around the face + void print_bracket(bool bracket = true) { this->setcfgbit_(2, bracket); } + /// Print percent sign at small number + void print_percent(bool percent = true) { this->setcfgbit_(3, percent); } + /// Print battery sign + void print_battery(bool battery = true) { this->setcfgbit_(4, battery); } + /// Print unit of big number + void print_unit(UNIT unit) { this->cfg_ = (this->cfg_ & 0x1F) | ((unit & 0x7) << 5); } + + void display(); + +#ifdef USE_TIME + void set_time(time::RealTimeClock *time) { this->time_ = time; }; +#endif + + protected: + bool auto_clear_enabled_{true}; + uint32_t disconnect_delay_ms_ = 5000; + uint16_t validity_period_ = 300; + uint16_t bignum_ = 0; + uint16_t smallnum_ = 0; + uint8_t cfg_ = 0; + + void setcfgbit_(uint8_t bit, bool value); + void send_to_setup_char_(uint8_t *blk, size_t size); + void delayed_disconnect_(); +#ifdef USE_TIME + void sync_time_(); + time::RealTimeClock *time_{nullptr}; +#endif + uint16_t char_handle_ = 0; + bool connection_established_ = false; + + esp32_ble_tracker::ESPBTUUID service_uuid_ = + esp32_ble_tracker::ESPBTUUID::from_raw("00001f10-0000-1000-8000-00805f9b34fb"); + esp32_ble_tracker::ESPBTUUID char_uuid_ = + esp32_ble_tracker::ESPBTUUID::from_raw("00001f1f-0000-1000-8000-00805f9b34fb"); + + optional writer_{}; +}; + +} // namespace pvvx_mithermometer +} // namespace esphome + +#endif diff --git a/esphome/components/pzem004t/pzem004t.h b/esphome/components/pzem004t/pzem004t.h index f4f9f29b4d..e18413f35c 100644 --- a/esphome/components/pzem004t/pzem004t.h +++ b/esphome/components/pzem004t/pzem004t.h @@ -23,10 +23,10 @@ class PZEM004T : public PollingComponent, public uart::UARTDevice { void dump_config() override; protected: - sensor::Sensor *voltage_sensor_; - sensor::Sensor *current_sensor_; - sensor::Sensor *power_sensor_; - sensor::Sensor *energy_sensor_; + sensor::Sensor *voltage_sensor_{nullptr}; + sensor::Sensor *current_sensor_{nullptr}; + sensor::Sensor *power_sensor_{nullptr}; + sensor::Sensor *energy_sensor_{nullptr}; enum PZEM004TReadState { SET_ADDRESS = 0xB4, diff --git a/esphome/components/pzemac/pzemac.h b/esphome/components/pzemac/pzemac.h index e9f76972a3..8f2cf1460d 100644 --- a/esphome/components/pzemac/pzemac.h +++ b/esphome/components/pzemac/pzemac.h @@ -27,12 +27,12 @@ class PZEMAC : public PollingComponent, public modbus::ModbusDevice { protected: template friend class ResetEnergyAction; - sensor::Sensor *voltage_sensor_; - sensor::Sensor *current_sensor_; - sensor::Sensor *power_sensor_; - sensor::Sensor *energy_sensor_; - sensor::Sensor *frequency_sensor_; - sensor::Sensor *power_factor_sensor_; + sensor::Sensor *voltage_sensor_{nullptr}; + sensor::Sensor *current_sensor_{nullptr}; + sensor::Sensor *power_sensor_{nullptr}; + sensor::Sensor *energy_sensor_{nullptr}; + sensor::Sensor *frequency_sensor_{nullptr}; + sensor::Sensor *power_factor_sensor_{nullptr}; void reset_energy_(); }; diff --git a/esphome/components/pzemdc/pzemdc.h b/esphome/components/pzemdc/pzemdc.h index d838eb4167..a78a48a6fb 100644 --- a/esphome/components/pzemdc/pzemdc.h +++ b/esphome/components/pzemdc/pzemdc.h @@ -22,11 +22,11 @@ class PZEMDC : public PollingComponent, public modbus::ModbusDevice { void dump_config() override; protected: - sensor::Sensor *voltage_sensor_; - sensor::Sensor *current_sensor_; - sensor::Sensor *power_sensor_; - sensor::Sensor *frequency_sensor_; - sensor::Sensor *power_factor_sensor_; + sensor::Sensor *voltage_sensor_{nullptr}; + sensor::Sensor *current_sensor_{nullptr}; + sensor::Sensor *power_sensor_{nullptr}; + sensor::Sensor *frequency_sensor_{nullptr}; + sensor::Sensor *power_factor_sensor_{nullptr}; }; } // namespace pzemdc diff --git a/esphome/components/qmc5883l/qmc5883l.h b/esphome/components/qmc5883l/qmc5883l.h index 01697ecbd0..15ef435ce5 100644 --- a/esphome/components/qmc5883l/qmc5883l.h +++ b/esphome/components/qmc5883l/qmc5883l.h @@ -45,10 +45,10 @@ class QMC5883LComponent : public PollingComponent, public i2c::I2CDevice { QMC5883LDatarate datarate_{QMC5883L_DATARATE_10_HZ}; QMC5883LRange range_{QMC5883L_RANGE_200_UT}; QMC5883LOversampling oversampling_{QMC5883L_SAMPLING_512}; - sensor::Sensor *x_sensor_; - sensor::Sensor *y_sensor_; - sensor::Sensor *z_sensor_; - sensor::Sensor *heading_sensor_; + sensor::Sensor *x_sensor_{nullptr}; + sensor::Sensor *y_sensor_{nullptr}; + sensor::Sensor *z_sensor_{nullptr}; + sensor::Sensor *heading_sensor_{nullptr}; enum ErrorCode { NONE = 0, COMMUNICATION_FAILED, diff --git a/esphome/components/qmp6988/qmp6988.h b/esphome/components/qmp6988/qmp6988.h index ef944ba4ff..f0c11adf43 100644 --- a/esphome/components/qmp6988/qmp6988.h +++ b/esphome/components/qmp6988/qmp6988.h @@ -91,8 +91,8 @@ class QMP6988Component : public PollingComponent, public i2c::I2CDevice { protected: qmp6988_data_t qmp6988_data_; - sensor::Sensor *temperature_sensor_; - sensor::Sensor *pressure_sensor_; + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *pressure_sensor_{nullptr}; QMP6988Oversampling temperature_oversampling_{QMP6988_OVERSAMPLING_16X}; QMP6988Oversampling pressure_oversampling_{QMP6988_OVERSAMPLING_16X}; diff --git a/esphome/components/remote_base/__init__.py b/esphome/components/remote_base/__init__.py index 5730cba1eb..e4d1e115e7 100644 --- a/esphome/components/remote_base/__init__.py +++ b/esphome/components/remote_base/__init__.py @@ -1338,3 +1338,48 @@ def midea_dumper(var, config): ) async def midea_action(var, config, args): cg.add(var.set_code(config[CONF_CODE])) + + +# AEHA +AEHAData, AEHABinarySensor, AEHATrigger, AEHAAction, AEHADumper = declare_protocol( + "AEHA" +) +AEHA_SCHEMA = cv.Schema( + { + cv.Required(CONF_ADDRESS): cv.hex_uint16_t, + cv.Required(CONF_DATA): cv.All( + [cv.Any(cv.hex_uint8_t, cv.uint8_t)], + cv.Length(min=2, max=35), + ), + } +) + + +@register_binary_sensor("aeha", AEHABinarySensor, AEHA_SCHEMA) +def aeha_binary_sensor(var, config): + cg.add( + var.set_data( + cg.StructInitializer( + AEHAData, + ("address", config[CONF_ADDRESS]), + ("data", config[CONF_DATA]), + ) + ) + ) + + +@register_trigger("aeha", AEHATrigger, AEHAData) +def aeha_trigger(var, config): + pass + + +@register_dumper("aeha", AEHADumper) +def aeha_dumper(var, config): + pass + + +@register_action("aeha", AEHAAction, AEHA_SCHEMA) +async def aeha_action(var, config, args): + template_ = await cg.templatable(config[CONF_ADDRESS], args, cg.uint16) + cg.add(var.set_address(template_)) + cg.add(var.set_data(config[CONF_DATA])) diff --git a/esphome/components/remote_base/aeha_protocol.cpp b/esphome/components/remote_base/aeha_protocol.cpp new file mode 100644 index 0000000000..ee1616ed6d --- /dev/null +++ b/esphome/components/remote_base/aeha_protocol.cpp @@ -0,0 +1,103 @@ +#include "aeha_protocol.h" +#include "esphome/core/log.h" +#include + +namespace esphome { +namespace remote_base { + +static const char *const TAG = "remote.aeha"; + +static const uint16_t BITWISE = 425; +static const uint16_t HEADER_HIGH_US = BITWISE * 8; +static const uint16_t HEADER_LOW_US = BITWISE * 4; +static const uint16_t BIT_HIGH_US = BITWISE; +static const uint16_t BIT_ONE_LOW_US = BITWISE * 3; +static const uint16_t BIT_ZERO_LOW_US = BITWISE; +static const uint16_t TRAILER = BITWISE; + +void AEHAProtocol::encode(RemoteTransmitData *dst, const AEHAData &data) { + dst->set_carrier_frequency(38000); + dst->reserve(2 + 32 + (data.data.size() * 2) + 1); + + dst->item(HEADER_HIGH_US, HEADER_LOW_US); + + for (uint16_t mask = 1 << 15; mask != 0; 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 (uint8_t bit : data.data) { + for (uint8_t mask = 1 << 7; mask != 0; mask >>= 1) { + if (bit & mask) { + dst->item(BIT_HIGH_US, BIT_ONE_LOW_US); + } else { + dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US); + } + } + } + + dst->mark(TRAILER); +} +optional AEHAProtocol::decode(RemoteReceiveData src) { + AEHAData out{ + .address = 0, + .data = {}, + }; + if (!src.expect_item(HEADER_HIGH_US, HEADER_LOW_US)) + return {}; + + for (uint16_t mask = 1 << 15; mask != 0; mask >>= 1) { + if (src.expect_item(BIT_HIGH_US, BIT_ONE_LOW_US)) { + out.address |= mask; + } else if (src.expect_item(BIT_HIGH_US, BIT_ZERO_LOW_US)) { + out.address &= ~mask; + } else { + return {}; + } + } + + for (uint8_t pos = 0; pos < 35; pos++) { + uint8_t data = 0; + for (uint8_t mask = 1 << 7; mask != 0; mask >>= 1) { + if (src.expect_item(BIT_HIGH_US, BIT_ONE_LOW_US)) { + data |= mask; + } else if (src.expect_item(BIT_HIGH_US, BIT_ZERO_LOW_US)) { + data &= ~mask; + } else if (pos > 1 && src.expect_mark(TRAILER)) { + return out; + } else { + return {}; + } + } + + out.data.push_back(data); + } + + if (src.expect_mark(TRAILER)) { + return out; + } + + return {}; +} + +std::string AEHAProtocol::format_data_(const std::vector &data) { + std::string out; + for (uint8_t byte : data) { + char buf[6]; + sprintf(buf, "0x%02X,", byte); + out += buf; + } + out.pop_back(); + return out; +} + +void AEHAProtocol::dump(const AEHAData &data) { + auto data_str = format_data_(data.data); + ESP_LOGD(TAG, "Received AEHA: address=0x%04X, data=[%s]", data.address, data_str.c_str()); +} + +} // namespace remote_base +} // namespace esphome diff --git a/esphome/components/remote_base/aeha_protocol.h b/esphome/components/remote_base/aeha_protocol.h new file mode 100644 index 0000000000..6cb4706506 --- /dev/null +++ b/esphome/components/remote_base/aeha_protocol.h @@ -0,0 +1,42 @@ +#pragma once + +#include "remote_base.h" + +namespace esphome { +namespace remote_base { + +struct AEHAData { + uint16_t address; + std::vector data; + + bool operator==(const AEHAData &rhs) const { return address == rhs.address && data == rhs.data; } +}; + +class AEHAProtocol : public RemoteProtocol { + public: + void encode(RemoteTransmitData *dst, const AEHAData &data) override; + optional decode(RemoteReceiveData src) override; + void dump(const AEHAData &data) override; + + private: + std::string format_data_(const std::vector &data); +}; + +DECLARE_REMOTE_PROTOCOL(AEHA) + +template class AEHAAction : public RemoteTransmitterActionBase { + public: + TEMPLATABLE_VALUE(uint16_t, address) + TEMPLATABLE_VALUE(std::vector, data) + + void set_data(const std::vector &data) { data_ = data; } + void encode(RemoteTransmitData *dst, Ts... x) override { + AEHAData data{}; + data.address = this->address_.value(x...); + data.data = this->data_.value(x...); + AEHAProtocol().encode(dst, data); + } +}; + +} // namespace remote_base +} // namespace esphome diff --git a/esphome/components/remote_base/pronto_protocol.cpp b/esphome/components/remote_base/pronto_protocol.cpp index a2b1a16e07..d8798d4ab9 100644 --- a/esphome/components/remote_base/pronto_protocol.cpp +++ b/esphome/components/remote_base/pronto_protocol.cpp @@ -106,6 +106,7 @@ void ProntoProtocol::send_pronto_(RemoteTransmitData *dst, const std::vector ProntoProtocol::decode(RemoteReceiveData src) { return out; } -void ProntoProtocol::dump(const ProntoData &data) { ESP_LOGD(TAG, "Received Pronto: data=%s", data.data.c_str()); } +void ProntoProtocol::dump(const ProntoData &data) { + std::string first, rest; + if (data.data.size() < 230) { + first = data.data; + } else { + first = data.data.substr(0, 229); + rest = data.data.substr(230); + } + ESP_LOGD(TAG, "Received Pronto: data=%s", first.c_str()); + if (!rest.empty()) + ESP_LOGD(TAG, "%s", rest.c_str()); +} } // namespace remote_base } // namespace esphome diff --git a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp b/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp index 368b21f892..c3d4d42e4f 100644 --- a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp +++ b/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp @@ -113,6 +113,10 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen this->rmt_temp_.push_back(rmt_item); } + if ((this->rmt_temp_.data() == nullptr) || this->rmt_temp_.empty()) { + ESP_LOGE(TAG, "Empty data"); + return; + } 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) { diff --git a/esphome/components/restart/switch/__init__.py b/esphome/components/restart/switch/__init__.py index de30392b45..89805b4246 100644 --- a/esphome/components/restart/switch/__init__.py +++ b/esphome/components/restart/switch/__init__.py @@ -2,10 +2,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import switch from esphome.const import ( - CONF_ENTITY_CATEGORY, - CONF_ID, - CONF_INVERTED, - CONF_ICON, ENTITY_CATEGORY_CONFIG, ICON_RESTART, ) @@ -13,21 +9,14 @@ from esphome.const import ( restart_ns = cg.esphome_ns.namespace("restart") RestartSwitch = restart_ns.class_("RestartSwitch", switch.Switch, cg.Component) -CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(RestartSwitch), - cv.Optional(CONF_INVERTED): cv.invalid( - "Restart switches do not support inverted mode!" - ), - cv.Optional(CONF_ICON, default=ICON_RESTART): switch.icon, - cv.Optional( - CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_CONFIG - ): cv.entity_category, - } +CONFIG_SCHEMA = switch.switch_schema( + RestartSwitch, + icon=ICON_RESTART, + entity_category=ENTITY_CATEGORY_CONFIG, + block_inverted=True, ).extend(cv.COMPONENT_SCHEMA) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await switch.new_switch(config) await cg.register_component(var, config) - await switch.register_switch(var, config) diff --git a/esphome/components/safe_mode/switch/__init__.py b/esphome/components/safe_mode/switch/__init__.py index b6c3e852f6..a6fcdfbece 100644 --- a/esphome/components/safe_mode/switch/__init__.py +++ b/esphome/components/safe_mode/switch/__init__.py @@ -3,10 +3,6 @@ import esphome.config_validation as cv from esphome.components import switch from esphome.components.ota import OTAComponent from esphome.const import ( - CONF_ENTITY_CATEGORY, - CONF_ID, - CONF_INVERTED, - CONF_ICON, CONF_OTA, ENTITY_CATEGORY_CONFIG, ICON_RESTART_ALERT, @@ -17,25 +13,21 @@ DEPENDENCIES = ["ota"] SafeModeSwitch = safe_mode_ns.class_("SafeModeSwitch", switch.Switch, cg.Component) -CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(SafeModeSwitch), - cv.GenerateID(CONF_OTA): cv.use_id(OTAComponent), - cv.Optional(CONF_INVERTED): cv.invalid( - "Safe Mode Restart switches do not support inverted mode!" - ), - cv.Optional(CONF_ICON, default=ICON_RESTART_ALERT): switch.icon, - cv.Optional( - CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_CONFIG - ): cv.entity_category, - } -).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = ( + switch.switch_schema( + SafeModeSwitch, + icon=ICON_RESTART_ALERT, + entity_category=ENTITY_CATEGORY_CONFIG, + block_inverted=True, + ) + .extend({cv.GenerateID(CONF_OTA): cv.use_id(OTAComponent)}) + .extend(cv.COMPONENT_SCHEMA) +) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await switch.new_switch(config) await cg.register_component(var, config) - await switch.register_switch(var, config) ota = await cg.get_variable(config[CONF_OTA]) cg.add(var.set_ota(ota)) diff --git a/esphome/components/senseair/senseair.cpp b/esphome/components/senseair/senseair.cpp index 50b9e01f17..7a98584201 100644 --- a/esphome/components/senseair/senseair.cpp +++ b/esphome/components/senseair/senseair.cpp @@ -1,4 +1,5 @@ #include "senseair.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" namespace esphome { @@ -42,7 +43,7 @@ void SenseAirComponent::update() { return; } - uint16_t calc_checksum = this->senseair_checksum_(response, 11); + uint16_t calc_checksum = crc16(response, 11); uint16_t resp_checksum = (uint16_t(response[12]) << 8) | response[11]; if (resp_checksum != calc_checksum) { ESP_LOGW(TAG, "SenseAir checksum doesn't match: 0x%02X!=0x%02X", resp_checksum, calc_checksum); @@ -60,23 +61,6 @@ void SenseAirComponent::update() { this->co2_sensor_->publish_state(ppm); } -uint16_t SenseAirComponent::senseair_checksum_(uint8_t *ptr, uint8_t length) { - uint16_t crc = 0xFFFF; - uint8_t i; - while (length--) { - crc ^= *ptr++; - for (i = 0; i < 8; i++) { - if ((crc & 0x01) != 0) { - crc >>= 1; - crc ^= 0xA001; - } else { - crc >>= 1; - } - } - } - return crc; -} - void SenseAirComponent::background_calibration() { // Responses are just echoes but must be read to clear the buffer ESP_LOGD(TAG, "SenseAir Starting background calibration"); diff --git a/esphome/components/senseair/senseair.h b/esphome/components/senseair/senseair.h index c03a0848e9..bcec638f79 100644 --- a/esphome/components/senseair/senseair.h +++ b/esphome/components/senseair/senseair.h @@ -23,7 +23,6 @@ class SenseAirComponent : public PollingComponent, public uart::UARTDevice { void abc_disable(); protected: - uint16_t senseair_checksum_(uint8_t *ptr, uint8_t length); bool senseair_write_command_(const uint8_t *command, uint8_t *response, uint8_t response_length); sensor::Sensor *co2_sensor_{nullptr}; diff --git a/esphome/components/shelly_dimmer/light.py b/esphome/components/shelly_dimmer/light.py index 003498c090..3978d37c0b 100644 --- a/esphome/components/shelly_dimmer/light.py +++ b/esphome/components/shelly_dimmer/light.py @@ -73,7 +73,7 @@ def get_firmware(value): def dl(url): try: - req = requests.get(url) + req = requests.get(url, timeout=30) req.raise_for_status() except requests.exceptions.RequestException as e: raise cv.Invalid(f"Could not download firmware file ({url}): {e}") diff --git a/esphome/components/sht3xd/sht3xd.h b/esphome/components/sht3xd/sht3xd.h index 3164aa0687..41ca3c5d6e 100644 --- a/esphome/components/sht3xd/sht3xd.h +++ b/esphome/components/sht3xd/sht3xd.h @@ -19,8 +19,8 @@ class SHT3XDComponent : public PollingComponent, public sensirion_common::Sensir void update() override; protected: - sensor::Sensor *temperature_sensor_; - sensor::Sensor *humidity_sensor_; + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *humidity_sensor_{nullptr}; }; } // namespace sht3xd diff --git a/esphome/components/shtcx/shtcx.h b/esphome/components/shtcx/shtcx.h index c44fb9d9c1..084d3bfc35 100644 --- a/esphome/components/shtcx/shtcx.h +++ b/esphome/components/shtcx/shtcx.h @@ -26,8 +26,8 @@ class SHTCXComponent : public PollingComponent, public sensirion_common::Sensiri protected: SHTCXType type_; uint16_t sensor_id_; - sensor::Sensor *temperature_sensor_; - sensor::Sensor *humidity_sensor_; + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *humidity_sensor_{nullptr}; }; } // namespace shtcx diff --git a/esphome/components/shutdown/switch/__init__.py b/esphome/components/shutdown/switch/__init__.py index 49970b4c2f..5de9f2d189 100644 --- a/esphome/components/shutdown/switch/__init__.py +++ b/esphome/components/shutdown/switch/__init__.py @@ -2,10 +2,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import switch from esphome.const import ( - CONF_ENTITY_CATEGORY, - CONF_ID, - CONF_INVERTED, - CONF_ICON, ENTITY_CATEGORY_CONFIG, ICON_POWER, ) @@ -13,21 +9,14 @@ from esphome.const import ( shutdown_ns = cg.esphome_ns.namespace("shutdown") ShutdownSwitch = shutdown_ns.class_("ShutdownSwitch", switch.Switch, cg.Component) -CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(ShutdownSwitch), - cv.Optional(CONF_INVERTED): cv.invalid( - "Shutdown switches do not support inverted mode!" - ), - cv.Optional(CONF_ICON, default=ICON_POWER): switch.icon, - cv.Optional( - CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_CONFIG - ): cv.entity_category, - } +CONFIG_SCHEMA = switch.switch_schema( + ShutdownSwitch, + icon=ICON_POWER, + entity_category=ENTITY_CATEGORY_CONFIG, + block_inverted=True, ).extend(cv.COMPONENT_SCHEMA) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await switch.new_switch(config) await cg.register_component(var, config) - await switch.register_switch(var, config) diff --git a/esphome/components/sim800l/__init__.py b/esphome/components/sim800l/__init__.py index 564b685b37..698e3cda9e 100644 --- a/esphome/components/sim800l/__init__.py +++ b/esphome/components/sim800l/__init__.py @@ -18,15 +18,42 @@ Sim800LReceivedMessageTrigger = sim800l_ns.class_( "Sim800LReceivedMessageTrigger", automation.Trigger.template(cg.std_string, cg.std_string), ) +Sim800LIncomingCallTrigger = sim800l_ns.class_( + "Sim800LIncomingCallTrigger", + automation.Trigger.template(cg.std_string), +) +Sim800LCallConnectedTrigger = sim800l_ns.class_( + "Sim800LCallConnectedTrigger", + automation.Trigger.template(), +) +Sim800LCallDisconnectedTrigger = sim800l_ns.class_( + "Sim800LCallDisconnectedTrigger", + automation.Trigger.template(), +) + +Sim800LReceivedUssdTrigger = sim800l_ns.class_( + "Sim800LReceivedUssdTrigger", + automation.Trigger.template(cg.std_string), +) # Actions Sim800LSendSmsAction = sim800l_ns.class_("Sim800LSendSmsAction", automation.Action) +Sim800LSendUssdAction = sim800l_ns.class_("Sim800LSendUssdAction", automation.Action) Sim800LDialAction = sim800l_ns.class_("Sim800LDialAction", automation.Action) +Sim800LConnectAction = sim800l_ns.class_("Sim800LConnectAction", automation.Action) +Sim800LDisconnectAction = sim800l_ns.class_( + "Sim800LDisconnectAction", automation.Action +) CONF_SIM800L_ID = "sim800l_id" CONF_ON_SMS_RECEIVED = "on_sms_received" +CONF_ON_USSD_RECEIVED = "on_ussd_received" +CONF_ON_INCOMING_CALL = "on_incoming_call" +CONF_ON_CALL_CONNECTED = "on_call_connected" +CONF_ON_CALL_DISCONNECTED = "on_call_disconnected" CONF_RECIPIENT = "recipient" CONF_MESSAGE = "message" +CONF_USSD = "ussd" CONFIG_SCHEMA = cv.All( cv.Schema( @@ -39,6 +66,34 @@ CONFIG_SCHEMA = cv.All( ), } ), + cv.Optional(CONF_ON_INCOMING_CALL): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + Sim800LIncomingCallTrigger + ), + } + ), + cv.Optional(CONF_ON_CALL_CONNECTED): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + Sim800LCallConnectedTrigger + ), + } + ), + cv.Optional(CONF_ON_CALL_DISCONNECTED): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + Sim800LCallDisconnectedTrigger + ), + } + ), + cv.Optional(CONF_ON_USSD_RECEIVED): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + Sim800LReceivedUssdTrigger + ), + } + ), } ) .extend(cv.polling_component_schema("5s")) @@ -59,6 +114,19 @@ async def to_code(config): await automation.build_automation( trigger, [(cg.std_string, "message"), (cg.std_string, "sender")], conf ) + for conf in config.get(CONF_ON_INCOMING_CALL, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [(cg.std_string, "caller_id")], conf) + for conf in config.get(CONF_ON_CALL_CONNECTED, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + for conf in config.get(CONF_ON_CALL_DISCONNECTED, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + + for conf in config.get(CONF_ON_USSD_RECEIVED, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [(cg.std_string, "ussd")], conf) SIM800L_SEND_SMS_SCHEMA = cv.Schema( @@ -98,3 +166,44 @@ async def sim800l_dial_to_code(config, action_id, template_arg, args): template_ = await cg.templatable(config[CONF_RECIPIENT], args, cg.std_string) cg.add(var.set_recipient(template_)) return var + + +@automation.register_action( + "sim800l.connect", + Sim800LConnectAction, + cv.Schema({cv.GenerateID(): cv.use_id(Sim800LComponent)}), +) +async def sim800l_connect_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, paren) + return var + + +SIM800L_SEND_USSD_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.use_id(Sim800LComponent), + cv.Required(CONF_USSD): cv.templatable(cv.string_strict), + } +) + + +@automation.register_action( + "sim800l.send_ussd", Sim800LSendUssdAction, SIM800L_SEND_USSD_SCHEMA +) +async def sim800l_send_ussd_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, paren) + template_ = await cg.templatable(config[CONF_USSD], args, cg.std_string) + cg.add(var.set_ussd(template_)) + return var + + +@automation.register_action( + "sim800l.disconnect", + Sim800LDisconnectAction, + cv.Schema({cv.GenerateID(): cv.use_id(Sim800LComponent)}), +) +async def sim800l_disconnect_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, paren) + return var diff --git a/esphome/components/sim800l/sim800l.cpp b/esphome/components/sim800l/sim800l.cpp index 709e241491..3ae5de491a 100644 --- a/esphome/components/sim800l/sim800l.cpp +++ b/esphome/components/sim800l/sim800l.cpp @@ -16,20 +16,38 @@ void Sim800LComponent::update() { this->write(26); } + if (this->expect_ack_) + return; + if (state_ == STATE_INIT) { if (this->registered_ && this->send_pending_) { this->send_cmd_("AT+CSCS=\"GSM\""); - this->state_ = STATE_SENDINGSMS1; + this->state_ = STATE_SENDING_SMS_1; } else if (this->registered_ && this->dial_pending_) { this->send_cmd_("AT+CSCS=\"GSM\""); this->state_ = STATE_DIALING1; + } else if (this->registered_ && this->connect_pending_) { + this->connect_pending_ = false; + ESP_LOGI(TAG, "Connecting..."); + this->send_cmd_("ATA"); + this->state_ = STATE_ATA_SENT; + } else if (this->registered_ && this->send_ussd_pending_) { + this->send_cmd_("AT+CSCS=\"GSM\""); + this->state_ = STATE_SEND_USSD1; + } else if (this->registered_ && this->disconnect_pending_) { + this->disconnect_pending_ = false; + ESP_LOGI(TAG, "Disconnecting..."); + this->send_cmd_("ATH"); + } else if (this->registered_ && this->call_state_ != 6) { + send_cmd_("AT+CLCC"); + this->state_ = STATE_CHECK_CALL; + return; } else { this->send_cmd_("AT"); - this->state_ = STATE_CHECK_AT; + this->state_ = STATE_SETUP_CMGF; } this->expect_ack_ = true; - } - if (state_ == STATE_RECEIVEDSMS) { + } else if (state_ == STATE_RECEIVED_SMS) { // Serial Buffer should have flushed. // Send cmd to delete received sms char delete_cmd[20]; @@ -44,20 +62,34 @@ void Sim800LComponent::send_cmd_(const std::string &message) { ESP_LOGV(TAG, "S: %s - %d", message.c_str(), this->state_); this->watch_dog_ = 0; this->write_str(message.c_str()); + this->write_byte(ASCII_CR); this->write_byte(ASCII_LF); } void Sim800LComponent::parse_cmd_(std::string message) { - ESP_LOGV(TAG, "R: %s - %d", message.c_str(), this->state_); - if (message.empty()) return; + ESP_LOGV(TAG, "R: %s - %d", message.c_str(), this->state_); + + if (this->state_ != STATE_RECEIVE_SMS) { + if (message == "RING") { + // Incoming call... + this->state_ = STATE_PARSE_CLIP; + this->expect_ack_ = false; + } else if (message == "NO CARRIER") { + if (this->call_state_ != 6) { + this->call_state_ = 6; + this->call_disconnected_callback_.call(); + } + } + } + + bool ok = message == "OK"; if (this->expect_ack_) { - bool ok = message == "OK"; this->expect_ack_ = false; if (!ok) { - if (this->state_ == STATE_CHECK_AT && message == "AT") { + if (this->state_ == STATE_SETUP_CMGF && message == "AT") { // Expected ack but AT echo received this->state_ = STATE_DISABLE_ECHO; this->expect_ack_ = true; @@ -67,6 +99,10 @@ void Sim800LComponent::parse_cmd_(std::string message) { return; } } + } else if (ok && (this->state_ != STATE_PARSE_SMS_RESPONSE && this->state_ != STATE_CHECK_CALL && + this->state_ != STATE_RECEIVE_SMS && this->state_ != STATE_DIALING2)) { + ESP_LOGW(TAG, "Received unexpected OK. Ignoring"); + return; } switch (this->state_) { @@ -74,30 +110,88 @@ void Sim800LComponent::parse_cmd_(std::string message) { // While we were waiting for update to check for messages, this notifies a message // is available. bool message_available = message.compare(0, 6, "+CMTI:") == 0; - if (!message_available) + if (!message_available) { + if (message == "RING") { + // Incoming call... + this->state_ = STATE_PARSE_CLIP; + } else if (message == "NO CARRIER") { + if (this->call_state_ != 6) { + this->call_state_ = 6; + this->call_disconnected_callback_.call(); + } + } else if (message.compare(0, 6, "+CUSD:") == 0) { + // Incoming USSD MESSAGE + this->state_ = STATE_CHECK_USSD; + } break; + } + // Else fall thru ... } case STATE_CHECK_SMS: send_cmd_("AT+CMGL=\"ALL\""); - this->state_ = STATE_PARSE_SMS; + this->state_ = STATE_PARSE_SMS_RESPONSE; this->parse_index_ = 0; break; case STATE_DISABLE_ECHO: send_cmd_("ATE0"); - this->state_ = STATE_CHECK_AT; + this->state_ = STATE_SETUP_CMGF; this->expect_ack_ = true; break; - case STATE_CHECK_AT: + case STATE_SETUP_CMGF: send_cmd_("AT+CMGF=1"); + this->state_ = STATE_SETUP_CLIP; + this->expect_ack_ = true; + break; + case STATE_SETUP_CLIP: + send_cmd_("AT+CLIP=1"); this->state_ = STATE_CREG; this->expect_ack_ = true; break; + case STATE_SETUP_USSD: + send_cmd_("AT+CUSD=1"); + this->state_ = STATE_CREG; + this->expect_ack_ = true; + break; + case STATE_SEND_USSD1: + this->send_cmd_("AT+CUSD=1, \"" + this->ussd_ + "\""); + this->state_ = STATE_SEND_USSD2; + break; + case STATE_SEND_USSD2: + ESP_LOGD(TAG, "SendUssd2: '%s'", message.c_str()); + if (message == "OK") { + // Dialing + ESP_LOGD(TAG, "Dialing ussd code: '%s' done.", this->ussd_.c_str()); + this->state_ = STATE_CHECK_USSD; + this->send_ussd_pending_ = false; + } else { + this->set_registered_(false); + this->state_ = STATE_INIT; + this->send_cmd_("AT+CMEE=2"); + this->write(26); + } + break; + case STATE_CHECK_USSD: + ESP_LOGD(TAG, "Check ussd code: '%s'", message.c_str()); + if (message.compare(0, 6, "+CUSD:") == 0) { + this->state_ = STATE_RECEIVED_USSD; + this->ussd_ = ""; + size_t start = 10; + size_t end = message.find_last_of(','); + if (end > start) { + this->ussd_ = message.substr(start + 1, end - start - 2); + this->ussd_received_callback_.call(this->ussd_); + } + } + // Otherwise we receive another OK, we do nothing just wait polling to continuously check for SMS + if (message == "OK") + this->state_ = STATE_INIT; + break; case STATE_CREG: send_cmd_("AT+CREG?"); - this->state_ = STATE_CREGWAIT; + this->state_ = STATE_CREG_WAIT; break; - case STATE_CREGWAIT: { + case STATE_CREG_WAIT: { // Response: "+CREG: 0,1" -- the one there means registered ok // "+CREG: -,-" means not registered ok bool registered = message.compare(0, 6, "+CREG:") == 0 && (message[9] == '1' || message[9] == '5'); @@ -111,10 +205,10 @@ void Sim800LComponent::parse_cmd_(std::string message) { if (message[7] == '0') { // Network registration is disable, enable it send_cmd_("AT+CREG=1"); this->expect_ack_ = true; - this->state_ = STATE_CHECK_AT; + this->state_ = STATE_SETUP_CMGF; } else { // Keep waiting registration - this->state_ = STATE_CREG; + this->state_ = STATE_INIT; } } set_registered_(registered); @@ -144,9 +238,6 @@ void Sim800LComponent::parse_cmd_(std::string message) { this->expect_ack_ = true; this->state_ = STATE_CHECK_SMS; break; - case STATE_PARSE_SMS: - this->state_ = STATE_PARSE_SMS_RESPONSE; - break; case STATE_PARSE_SMS_RESPONSE: if (message.compare(0, 6, "+CMGL:") == 0 && this->parse_index_ == 0) { size_t start = 7; @@ -157,10 +248,11 @@ void Sim800LComponent::parse_cmd_(std::string message) { if (item == 1) { // Slot Index this->parse_index_ = parse_number(message.substr(start, end - start)).value_or(0); } - // item 2 = STATUS, usually "REC UNERAD" + // item 2 = STATUS, usually "REC UNREAD" if (item == 3) { // recipient // Add 1 and remove 2 from substring to get rid of "quotes" this->sender_ = message.substr(start + 1, end - start - 2); + this->message_.clear(); break; } // item 4 = "" @@ -173,42 +265,83 @@ void Sim800LComponent::parse_cmd_(std::string message) { ESP_LOGD(TAG, "Invalid message %d %s", this->state_, message.c_str()); return; } - this->state_ = STATE_RECEIVESMS; + this->state_ = STATE_RECEIVE_SMS; + } + // Otherwise we receive another OK + if (ok) { + send_cmd_("AT+CLCC"); + this->state_ = STATE_CHECK_CALL; } - // Otherwise we receive another OK, we do nothing just wait polling to continuously check for SMS - if (message == "OK") - this->state_ = STATE_INIT; break; - case STATE_RECEIVESMS: + case STATE_CHECK_CALL: + if (message.compare(0, 6, "+CLCC:") == 0 && this->parse_index_ == 0) { + this->expect_ack_ = true; + size_t start = 7; + size_t end = message.find(',', start); + uint8_t item = 0; + while (end != start) { + item++; + // item 1 call index for +CHLD + // item 2 dir 0 Mobile originated; 1 Mobile terminated + if (item == 3) { // stat + uint8_t current_call_state = parse_number(message.substr(start, end - start)).value_or(6); + if (current_call_state != this->call_state_) { + ESP_LOGD(TAG, "Call state is now: %d", current_call_state); + if (current_call_state == 0) + this->call_connected_callback_.call(); + } + this->call_state_ = current_call_state; + break; + } + // item 4 = "" + // item 5 = Received timestamp + start = end + 1; + end = message.find(',', start); + } + + if (item < 2) { + ESP_LOGD(TAG, "Invalid message %d %s", this->state_, message.c_str()); + return; + } + } else if (ok) { + if (this->call_state_ != 6) { + // no call in progress + this->call_state_ = 6; // Disconnect + this->call_disconnected_callback_.call(); + } + } + this->state_ = STATE_INIT; + break; + case STATE_RECEIVE_SMS: /* Our recipient is set and the message body is in message kick ESPHome callback now */ - ESP_LOGD(TAG, "Received SMS from: %s", this->sender_.c_str()); - ESP_LOGD(TAG, "%s", message.c_str()); - this->callback_.call(message, this->sender_); - /* If the message is multiline, next lines will contain message data. - If there were other messages in the list, next line will be +CMGL: ... - At the end of the list the new line and the OK should be received. - To keep this simple just first line of message if considered, then - the next state will swallow all received data and in next poll event - this message index is marked for deletion. - */ - this->state_ = STATE_RECEIVEDSMS; + if (ok || message.compare(0, 6, "+CMGL:") == 0) { + ESP_LOGD(TAG, "Received SMS from: %s", this->sender_.c_str()); + ESP_LOGD(TAG, "%s", this->message_.c_str()); + this->sms_received_callback_.call(this->message_, this->sender_); + this->state_ = STATE_RECEIVED_SMS; + } else { + if (this->message_.length() > 0) + this->message_ += "\n"; + this->message_ += message; + } break; - case STATE_RECEIVEDSMS: + case STATE_RECEIVED_SMS: + case STATE_RECEIVED_USSD: // Let the buffer flush. Next poll will request to delete the parsed index message. break; - case STATE_SENDINGSMS1: + case STATE_SENDING_SMS_1: this->send_cmd_("AT+CMGS=\"" + this->recipient_ + "\""); - this->state_ = STATE_SENDINGSMS2; + this->state_ = STATE_SENDING_SMS_2; break; - case STATE_SENDINGSMS2: + case STATE_SENDING_SMS_2: if (message == ">") { // Send sms body - ESP_LOGD(TAG, "Sending message: '%s'", this->outgoing_message_.c_str()); + ESP_LOGI(TAG, "Sending to %s message: '%s'", this->recipient_.c_str(), this->outgoing_message_.c_str()); this->write_str(this->outgoing_message_.c_str()); this->write(26); - this->state_ = STATE_SENDINGSMS3; + this->state_ = STATE_SENDING_SMS_3; } else { set_registered_(false); this->state_ = STATE_INIT; @@ -216,7 +349,7 @@ void Sim800LComponent::parse_cmd_(std::string message) { this->write(26); } break; - case STATE_SENDINGSMS3: + case STATE_SENDING_SMS_3: if (message.compare(0, 6, "+CMGS:") == 0) { ESP_LOGD(TAG, "SMS Sent OK: %s", message.c_str()); this->send_pending_ = false; @@ -229,23 +362,55 @@ void Sim800LComponent::parse_cmd_(std::string message) { this->state_ = STATE_DIALING2; break; case STATE_DIALING2: - if (message == "OK") { - // Dialing - ESP_LOGD(TAG, "Dialing: '%s'", this->recipient_.c_str()); - this->state_ = STATE_INIT; + if (ok) { + ESP_LOGI(TAG, "Dialing: '%s'", this->recipient_.c_str()); this->dial_pending_ = false; } else { this->set_registered_(false); - this->state_ = STATE_INIT; this->send_cmd_("AT+CMEE=2"); this->write(26); } + this->state_ = STATE_INIT; + break; + case STATE_PARSE_CLIP: + if (message.compare(0, 6, "+CLIP:") == 0) { + std::string caller_id; + size_t start = 7; + size_t end = message.find(',', start); + uint8_t item = 0; + while (end != start) { + item++; + if (item == 1) { // Slot Index + // Add 1 and remove 2 from substring to get rid of "quotes" + caller_id = message.substr(start + 1, end - start - 2); + break; + } + // item 4 = "" + // item 5 = Received timestamp + start = end + 1; + end = message.find(',', start); + } + if (this->call_state_ != 4) { + this->call_state_ = 4; + ESP_LOGI(TAG, "Incoming call from %s", caller_id.c_str()); + incoming_call_callback_.call(caller_id); + } + this->state_ = STATE_INIT; + } + break; + case STATE_ATA_SENT: + ESP_LOGI(TAG, "Call connected"); + if (this->call_state_ != 0) { + this->call_state_ = 0; + this->call_connected_callback_.call(); + } + this->state_ = STATE_INIT; break; default: - ESP_LOGD(TAG, "Unhandled: %s - %d", message.c_str(), this->state_); + ESP_LOGW(TAG, "Unhandled: %s - %d", message.c_str(), this->state_); break; } -} +} // namespace sim800l void Sim800LComponent::loop() { // Read message @@ -264,7 +429,7 @@ void Sim800LComponent::loop() { byte = '?'; // need to be valid utf8 string for log functions. this->read_buffer_[this->read_pos_] = byte; - if (this->state_ == STATE_SENDINGSMS2 && this->read_pos_ == 0 && byte == '>') + if (this->state_ == STATE_SENDING_SMS_2 && this->read_pos_ == 0 && byte == '>') this->read_buffer_[++this->read_pos_] = ASCII_LF; if (this->read_buffer_[this->read_pos_] == ASCII_LF) { @@ -275,13 +440,23 @@ void Sim800LComponent::loop() { this->read_pos_++; } } + if (state_ == STATE_INIT && this->registered_ && + (this->call_state_ != 6 // A call is in progress + || this->send_pending_ || this->dial_pending_ || this->connect_pending_ || this->disconnect_pending_)) { + this->update(); + } } void Sim800LComponent::send_sms(const std::string &recipient, const std::string &message) { - ESP_LOGD(TAG, "Sending to %s: %s", recipient.c_str(), message.c_str()); this->recipient_ = recipient; this->outgoing_message_ = message; this->send_pending_ = true; +} + +void Sim800LComponent::send_ussd(const std::string &ussd_code) { + ESP_LOGD(TAG, "Sending USSD code: %s", ussd_code.c_str()); + this->ussd_ = ussd_code; + this->send_ussd_pending_ = true; this->update(); } void Sim800LComponent::dump_config() { @@ -294,11 +469,11 @@ void Sim800LComponent::dump_config() { #endif } void Sim800LComponent::dial(const std::string &recipient) { - ESP_LOGD(TAG, "Dialing %s", recipient.c_str()); this->recipient_ = recipient; this->dial_pending_ = true; - this->update(); } +void Sim800LComponent::connect() { this->connect_pending_ = true; } +void Sim800LComponent::disconnect() { this->disconnect_pending_ = true; } void Sim800LComponent::set_registered_(bool registered) { this->registered_ = registered; diff --git a/esphome/components/sim800l/sim800l.h b/esphome/components/sim800l/sim800l.h index 3535b96283..bf7efd6915 100644 --- a/esphome/components/sim800l/sim800l.h +++ b/esphome/components/sim800l/sim800l.h @@ -16,31 +16,35 @@ namespace esphome { namespace sim800l { -const uint8_t SIM800L_READ_BUFFER_LENGTH = 255; +const uint16_t SIM800L_READ_BUFFER_LENGTH = 1024; enum State { STATE_IDLE = 0, STATE_INIT, - STATE_CHECK_AT, + STATE_SETUP_CMGF, + STATE_SETUP_CLIP, STATE_CREG, - STATE_CREGWAIT, + STATE_CREG_WAIT, STATE_CSQ, STATE_CSQ_RESPONSE, - STATE_IDLEWAIT, - STATE_SENDINGSMS1, - STATE_SENDINGSMS2, - STATE_SENDINGSMS3, + STATE_SENDING_SMS_1, + STATE_SENDING_SMS_2, + STATE_SENDING_SMS_3, STATE_CHECK_SMS, - STATE_PARSE_SMS, STATE_PARSE_SMS_RESPONSE, - STATE_RECEIVESMS, - STATE_READSMS, - STATE_RECEIVEDSMS, - STATE_DELETEDSMS, + STATE_RECEIVE_SMS, + STATE_RECEIVED_SMS, STATE_DISABLE_ECHO, - STATE_PARSE_SMS_OK, STATE_DIALING1, - STATE_DIALING2 + STATE_DIALING2, + STATE_PARSE_CLIP, + STATE_ATA_SENT, + STATE_CHECK_CALL, + STATE_SETUP_USSD, + STATE_SEND_USSD1, + STATE_SEND_USSD2, + STATE_CHECK_USSD, + STATE_RECEIVED_USSD }; class Sim800LComponent : public uart::UARTDevice, public PollingComponent { @@ -58,10 +62,25 @@ class Sim800LComponent : public uart::UARTDevice, public PollingComponent { void set_rssi_sensor(sensor::Sensor *rssi_sensor) { rssi_sensor_ = rssi_sensor; } #endif void add_on_sms_received_callback(std::function callback) { - this->callback_.add(std::move(callback)); + this->sms_received_callback_.add(std::move(callback)); + } + void add_on_incoming_call_callback(std::function callback) { + this->incoming_call_callback_.add(std::move(callback)); + } + void add_on_call_connected_callback(std::function callback) { + this->call_connected_callback_.add(std::move(callback)); + } + void add_on_call_disconnected_callback(std::function callback) { + this->call_disconnected_callback_.add(std::move(callback)); + } + void add_on_ussd_received_callback(std::function callback) { + this->ussd_received_callback_.add(std::move(callback)); } void send_sms(const std::string &recipient, const std::string &message); + void send_ussd(const std::string &ussd_code); void dial(const std::string &recipient); + void connect(); + void disconnect(); protected: void send_cmd_(const std::string &message); @@ -76,6 +95,7 @@ class Sim800LComponent : public uart::UARTDevice, public PollingComponent { sensor::Sensor *rssi_sensor_{nullptr}; #endif std::string sender_; + std::string message_; char read_buffer_[SIM800L_READ_BUFFER_LENGTH]; size_t read_pos_{0}; uint8_t parse_index_{0}; @@ -86,10 +106,19 @@ class Sim800LComponent : public uart::UARTDevice, public PollingComponent { std::string recipient_; std::string outgoing_message_; + std::string ussd_; bool send_pending_; bool dial_pending_; + bool connect_pending_; + bool disconnect_pending_; + bool send_ussd_pending_; + uint8_t call_state_{6}; - CallbackManager callback_; + CallbackManager sms_received_callback_; + CallbackManager incoming_call_callback_; + CallbackManager call_connected_callback_; + CallbackManager call_disconnected_callback_; + CallbackManager ussd_received_callback_; }; class Sim800LReceivedMessageTrigger : public Trigger { @@ -100,6 +129,33 @@ class Sim800LReceivedMessageTrigger : public Trigger { } }; +class Sim800LIncomingCallTrigger : public Trigger { + public: + explicit Sim800LIncomingCallTrigger(Sim800LComponent *parent) { + parent->add_on_incoming_call_callback([this](const std::string &caller_id) { this->trigger(caller_id); }); + } +}; + +class Sim800LCallConnectedTrigger : public Trigger<> { + public: + explicit Sim800LCallConnectedTrigger(Sim800LComponent *parent) { + parent->add_on_call_connected_callback([this]() { this->trigger(); }); + } +}; + +class Sim800LCallDisconnectedTrigger : public Trigger<> { + public: + explicit Sim800LCallDisconnectedTrigger(Sim800LComponent *parent) { + parent->add_on_call_disconnected_callback([this]() { this->trigger(); }); + } +}; +class Sim800LReceivedUssdTrigger : public Trigger { + public: + explicit Sim800LReceivedUssdTrigger(Sim800LComponent *parent) { + parent->add_on_ussd_received_callback([this](const std::string &ussd) { this->trigger(ussd); }); + } +}; + template class Sim800LSendSmsAction : public Action { public: Sim800LSendSmsAction(Sim800LComponent *parent) : parent_(parent) {} @@ -116,6 +172,20 @@ template class Sim800LSendSmsAction : public Action { Sim800LComponent *parent_; }; +template class Sim800LSendUssdAction : public Action { + public: + Sim800LSendUssdAction(Sim800LComponent *parent) : parent_(parent) {} + TEMPLATABLE_VALUE(std::string, ussd) + + void play(Ts... x) { + auto ussd_code = this->ussd_.value(x...); + this->parent_->send_ussd(ussd_code); + } + + protected: + Sim800LComponent *parent_; +}; + template class Sim800LDialAction : public Action { public: Sim800LDialAction(Sim800LComponent *parent) : parent_(parent) {} @@ -129,6 +199,25 @@ template class Sim800LDialAction : public Action { protected: Sim800LComponent *parent_; }; +template class Sim800LConnectAction : public Action { + public: + Sim800LConnectAction(Sim800LComponent *parent) : parent_(parent) {} + + void play(Ts... x) { this->parent_->connect(); } + + protected: + Sim800LComponent *parent_; +}; + +template class Sim800LDisconnectAction : public Action { + public: + Sim800LDisconnectAction(Sim800LComponent *parent) : parent_(parent) {} + + void play(Ts... x) { this->parent_->disconnect(); } + + protected: + Sim800LComponent *parent_; +}; } // namespace sim800l } // namespace esphome diff --git a/esphome/components/spi/spi.h b/esphome/components/spi/spi.h index 7f0b0f481a..6c92321ac8 100644 --- a/esphome/components/spi/spi.h +++ b/esphome/components/spi/spi.h @@ -195,6 +195,11 @@ class SPIComponent : public Component { template void enable(GPIOPin *cs) { + if (cs != nullptr) { + this->active_cs_ = cs; + this->active_cs_->digital_write(false); + } + #ifdef USE_SPI_ARDUINO_BACKEND if (this->hw_spi_ != nullptr) { uint8_t data_mode = SPI_MODE0; @@ -215,11 +220,6 @@ class SPIComponent : public Component { #ifdef USE_SPI_ARDUINO_BACKEND } #endif // USE_SPI_ARDUINO_BACKEND - - if (cs != nullptr) { - this->active_cs_ = cs; - this->active_cs_->digital_write(false); - } } void disable(); diff --git a/esphome/components/sprinkler/__init__.py b/esphome/components/sprinkler/__init__.py index 659eb5b58e..4e80cfa021 100644 --- a/esphome/components/sprinkler/__init__.py +++ b/esphome/components/sprinkler/__init__.py @@ -223,13 +223,7 @@ SPRINKLER_ACTION_QUEUE_VALVE_SCHEMA = cv.Schema( SPRINKLER_VALVE_SCHEMA = cv.Schema( { cv.Optional(CONF_ENABLE_SWITCH): cv.maybe_simple_value( - switch.SWITCH_SCHEMA.extend( - cv.Schema( - { - cv.GenerateID(): cv.declare_id(SprinklerControllerSwitch), - } - ) - ), + switch.switch_schema(SprinklerControllerSwitch), key=CONF_NAME, ), cv.Optional(CONF_PUMP_OFF_SWITCH_ID): cv.use_id(switch.Switch), @@ -237,13 +231,7 @@ SPRINKLER_VALVE_SCHEMA = cv.Schema( cv.Optional(CONF_PUMP_SWITCH_ID): cv.use_id(switch.Switch), cv.Required(CONF_RUN_DURATION): cv.positive_time_period_seconds, cv.Required(CONF_VALVE_SWITCH): cv.maybe_simple_value( - switch.SWITCH_SCHEMA.extend( - cv.Schema( - { - cv.GenerateID(): cv.declare_id(SprinklerControllerSwitch), - } - ) - ), + switch.switch_schema(SprinklerControllerSwitch), key=CONF_NAME, ), cv.Optional(CONF_VALVE_OFF_SWITCH_ID): cv.use_id(switch.Switch), @@ -256,43 +244,19 @@ SPRINKLER_CONTROLLER_SCHEMA = cv.Schema( { cv.GenerateID(): cv.declare_id(Sprinkler), cv.Optional(CONF_AUTO_ADVANCE_SWITCH): cv.maybe_simple_value( - switch.SWITCH_SCHEMA.extend( - cv.Schema( - { - cv.GenerateID(): cv.declare_id(SprinklerControllerSwitch), - } - ) - ), + switch.switch_schema(SprinklerControllerSwitch), key=CONF_NAME, ), cv.Optional(CONF_MAIN_SWITCH): cv.maybe_simple_value( - switch.SWITCH_SCHEMA.extend( - cv.Schema( - { - cv.GenerateID(): cv.declare_id(SprinklerControllerSwitch), - } - ) - ), + switch.switch_schema(SprinklerControllerSwitch), key=CONF_NAME, ), cv.Optional(CONF_QUEUE_ENABLE_SWITCH): cv.maybe_simple_value( - switch.SWITCH_SCHEMA.extend( - cv.Schema( - { - cv.GenerateID(): cv.declare_id(SprinklerControllerSwitch), - } - ) - ), + switch.switch_schema(SprinklerControllerSwitch), key=CONF_NAME, ), cv.Optional(CONF_REVERSE_SWITCH): cv.maybe_simple_value( - switch.SWITCH_SCHEMA.extend( - cv.Schema( - { - cv.GenerateID(): cv.declare_id(SprinklerControllerSwitch), - } - ) - ), + switch.switch_schema(SprinklerControllerSwitch), key=CONF_NAME, ), cv.Optional(CONF_MANUAL_SELECTION_DELAY): cv.positive_time_period_seconds, diff --git a/esphome/components/sprinkler/sprinkler.cpp b/esphome/components/sprinkler/sprinkler.cpp index ab694c8412..2be71a08d0 100644 --- a/esphome/components/sprinkler/sprinkler.cpp +++ b/esphome/components/sprinkler/sprinkler.cpp @@ -769,7 +769,7 @@ void Sprinkler::resume() { ESP_LOGD(TAG, "Resuming valve %u with %u seconds remaining", this->paused_valve_.value_or(0), this->resume_duration_.value_or(0)); this->fsm_request_(this->paused_valve_.value(), this->resume_duration_.value()); - this->reset_resume_(); + this->reset_resume(); } else { ESP_LOGD(TAG, "No valve to resume!"); } @@ -783,6 +783,11 @@ void Sprinkler::resume_or_start_full_cycle() { } } +void Sprinkler::reset_resume() { + this->paused_valve_.reset(); + this->resume_duration_.reset(); +} + const char *Sprinkler::valve_name(const size_t valve_number) { if (this->is_a_valid_valve(valve_number)) { return this->valve_[valve_number].controller_switch->get_name().c_str(); @@ -1101,11 +1106,6 @@ void Sprinkler::reset_cycle_states_() { } } -void Sprinkler::reset_resume_() { - this->paused_valve_.reset(); - this->resume_duration_.reset(); -} - void Sprinkler::fsm_request_(size_t requested_valve, uint32_t requested_run_duration) { this->next_req_.set_valve(requested_valve); this->next_req_.set_run_duration(requested_run_duration); diff --git a/esphome/components/sprinkler/sprinkler.h b/esphome/components/sprinkler/sprinkler.h index 1243a844fa..acd168d791 100644 --- a/esphome/components/sprinkler/sprinkler.h +++ b/esphome/components/sprinkler/sprinkler.h @@ -308,6 +308,9 @@ class Sprinkler : public Component, public EntityBase { /// if a cycle was suspended using pause(), resumes it. otherwise calls start_full_cycle() void resume_or_start_full_cycle(); + /// resets resume state + void reset_resume(); + /// returns a pointer to a valve's name string object; returns nullptr if valve_number is invalid const char *valve_name(size_t valve_number); @@ -401,9 +404,6 @@ class Sprinkler : public Component, public EntityBase { /// resets the cycle state for all valves void reset_cycle_states_(); - /// resets resume state - void reset_resume_(); - /// make a request of the state machine void fsm_request_(size_t requested_valve, uint32_t requested_run_duration = 0); diff --git a/esphome/components/switch/__init__.py b/esphome/components/switch/__init__.py index 54ad2b852e..336c7d38d6 100644 --- a/esphome/components/switch/__init__.py +++ b/esphome/components/switch/__init__.py @@ -6,6 +6,7 @@ from esphome.components import mqtt from esphome.const import ( CONF_DEVICE_CLASS, CONF_ENTITY_CATEGORY, + CONF_ICON, CONF_ID, CONF_INVERTED, CONF_MQTT_ID, @@ -45,7 +46,6 @@ SwitchTurnOffTrigger = switch_ns.class_( "SwitchTurnOffTrigger", automation.Trigger.template() ) -icon = cv.icon validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True) @@ -76,6 +76,8 @@ def switch_schema( *, entity_category: str = _UNDEF, device_class: str = _UNDEF, + icon: str = _UNDEF, + block_inverted: bool = False, ): schema = SWITCH_SCHEMA if class_ is not _UNDEF: @@ -96,6 +98,16 @@ def switch_schema( ): validate_device_class } ) + if icon is not _UNDEF: + schema = schema.extend({cv.Optional(CONF_ICON, default=icon): cv.icon}) + if block_inverted: + schema = schema.extend( + { + cv.Optional(CONF_INVERTED): cv.invalid( + "Inverted is not supported for this platform!" + ) + } + ) return schema diff --git a/esphome/components/template/button/__init__.py b/esphome/components/template/button/__init__.py index a8bf595942..2ad5e54c80 100644 --- a/esphome/components/template/button/__init__.py +++ b/esphome/components/template/button/__init__.py @@ -9,7 +9,7 @@ CONFIG_SCHEMA = button.BUTTON_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(TemplateButton), } -).extend(cv.COMPONENT_SCHEMA) +) async def to_code(config): diff --git a/esphome/components/template/switch/__init__.py b/esphome/components/template/switch/__init__.py index 6095a7c561..e002c4e3d8 100644 --- a/esphome/components/template/switch/__init__.py +++ b/esphome/components/template/switch/__init__.py @@ -31,9 +31,9 @@ def validate(config): CONFIG_SCHEMA = cv.All( - switch.SWITCH_SCHEMA.extend( + switch.switch_schema(TemplateSwitch) + .extend( { - cv.GenerateID(): cv.declare_id(TemplateSwitch), cv.Optional(CONF_LAMBDA): cv.returning_lambda, cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean, cv.Optional(CONF_ASSUMED_STATE, default=False): cv.boolean, @@ -45,15 +45,15 @@ CONFIG_SCHEMA = cv.All( ), cv.Optional(CONF_RESTORE_STATE, default=False): cv.boolean, } - ).extend(cv.COMPONENT_SCHEMA), + ) + .extend(cv.COMPONENT_SCHEMA), validate, ) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await switch.new_switch(config) await cg.register_component(var, config) - await switch.register_switch(var, config) if CONF_LAMBDA in config: template_ = await cg.process_lambda( diff --git a/esphome/components/tm1621/__init__.py b/esphome/components/tm1621/__init__.py new file mode 100644 index 0000000000..2e88d4f366 --- /dev/null +++ b/esphome/components/tm1621/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@Philippe12"] diff --git a/esphome/components/tm1621/display.py b/esphome/components/tm1621/display.py new file mode 100644 index 0000000000..edbc5f6928 --- /dev/null +++ b/esphome/components/tm1621/display.py @@ -0,0 +1,47 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import pins +from esphome.components import display +from esphome.const import ( + CONF_DATA_PIN, + CONF_CS_PIN, + CONF_ID, + CONF_LAMBDA, + CONF_READ_PIN, + CONF_WRITE_PIN, +) + +tm1621_ns = cg.esphome_ns.namespace("tm1621") +TM1621Display = tm1621_ns.class_("TM1621Display", cg.PollingComponent) +TM1621DisplayRef = TM1621Display.operator("ref") + +CONFIG_SCHEMA = display.BASIC_DISPLAY_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(TM1621Display), + cv.Required(CONF_CS_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_DATA_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_READ_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_WRITE_PIN): pins.gpio_output_pin_schema, + } +).extend(cv.polling_component_schema("1s")) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await display.register_display(var, config) + + cs = await cg.gpio_pin_expression(config[CONF_CS_PIN]) + cg.add(var.set_cs_pin(cs)) + data = await cg.gpio_pin_expression(config[CONF_DATA_PIN]) + cg.add(var.set_data_pin(data)) + read = await cg.gpio_pin_expression(config[CONF_READ_PIN]) + cg.add(var.set_read_pin(read)) + write = await cg.gpio_pin_expression(config[CONF_WRITE_PIN]) + cg.add(var.set_write_pin(write)) + + if CONF_LAMBDA in config: + lambda_ = await cg.process_lambda( + config[CONF_LAMBDA], [(TM1621DisplayRef, "it")], return_type=cg.void + ) + cg.add(var.set_writer(lambda_)) diff --git a/esphome/components/tm1621/tm1621.cpp b/esphome/components/tm1621/tm1621.cpp new file mode 100644 index 0000000000..ebaa5a3457 --- /dev/null +++ b/esphome/components/tm1621/tm1621.cpp @@ -0,0 +1,283 @@ +#include "tm1621.h" +#include "esphome/core/log.h" +#include "esphome/core/helpers.h" +#include "esphome/core/hal.h" + +namespace esphome { +namespace tm1621 { + +static const char *const TAG = "tm1621"; + +const uint8_t TM1621_PULSE_WIDTH = 10; // microseconds (Sonoff = 100) + +const uint8_t TM1621_SYS_EN = 0x01; // 0b00000001 +const uint8_t TM1621_LCD_ON = 0x03; // 0b00000011 +const uint8_t TM1621_TIMER_DIS = 0x04; // 0b00000100 +const uint8_t TM1621_WDT_DIS = 0x05; // 0b00000101 +const uint8_t TM1621_TONE_OFF = 0x08; // 0b00001000 +const uint8_t TM1621_BIAS = 0x29; // 0b00101001 = LCD 1/3 bias 4 commons option +const uint8_t TM1621_IRQ_DIS = 0x80; // 0b100x0xxx + +enum Tm1621Device { TM1621_USER, TM1621_POWR316D, TM1621_THR316D }; + +const uint8_t TM1621_COMMANDS[] = {TM1621_SYS_EN, TM1621_LCD_ON, TM1621_BIAS, TM1621_TIMER_DIS, + TM1621_WDT_DIS, TM1621_TONE_OFF, TM1621_IRQ_DIS}; + +const char TM1621_KCHAR[] PROGMEM = {"0|1|2|3|4|5|6|7|8|9|-| "}; +// 0 1 2 3 4 5 6 7 8 9 - off +const uint8_t TM1621_DIGIT_ROW[2][12] = {{0x5F, 0x50, 0x3D, 0x79, 0x72, 0x6B, 0x6F, 0x51, 0x7F, 0x7B, 0x20, 0x00}, + {0xF5, 0x05, 0xB6, 0x97, 0x47, 0xD3, 0xF3, 0x85, 0xF7, 0xD7, 0x02, 0x00}}; + +void TM1621Display::setup() { + ESP_LOGCONFIG(TAG, "Setting up TM1621..."); + + this->cs_pin_->setup(); // OUTPUT + this->cs_pin_->digital_write(true); + this->data_pin_->setup(); // OUTPUT + this->data_pin_->digital_write(true); + this->read_pin_->setup(); // OUTPUT + this->read_pin_->digital_write(true); + this->write_pin_->setup(); // OUTPUT + this->write_pin_->digital_write(true); + + this->state_ = 100; + + this->cs_pin_->digital_write(false); + delayMicroseconds(80); + this->read_pin_->digital_write(false); + delayMicroseconds(15); + this->write_pin_->digital_write(false); + delayMicroseconds(25); + this->data_pin_->digital_write(false); + delayMicroseconds(TM1621_PULSE_WIDTH); + this->data_pin_->digital_write(true); + + for (uint8_t tm1621_command : TM1621_COMMANDS) { + this->send_command_(tm1621_command); + } + + this->send_address_(0x00); + for (uint32_t segment = 0; segment < 16; segment++) { + this->send_common_(0); + } + this->stop_(); + + snprintf(this->row_[0], sizeof(this->row_[0]), "----"); + snprintf(this->row_[1], sizeof(this->row_[1]), "----"); + + this->display(); +} +void TM1621Display::dump_config() { + ESP_LOGCONFIG(TAG, "TM1621:"); + LOG_PIN(" CS Pin: ", this->cs_pin_); + LOG_PIN(" DATA Pin: ", this->data_pin_); + LOG_PIN(" READ Pin: ", this->read_pin_); + LOG_PIN(" WRITE Pin: ", this->write_pin_); + LOG_UPDATE_INTERVAL(this); +} + +void TM1621Display::update() { + // memset(this->row, 0, sizeof(this->row)); + if (this->writer_.has_value()) + (*this->writer_)(*this); + this->display(); +} + +float TM1621Display::get_setup_priority() const { return setup_priority::PROCESSOR; } +void TM1621Display::bit_delay_() { delayMicroseconds(100); } + +void TM1621Display::stop_() { + this->cs_pin_->digital_write(true); // Stop command sequence + delayMicroseconds(TM1621_PULSE_WIDTH / 2); + this->data_pin_->digital_write(true); // Reset data +} + +void TM1621Display::display() { + // Tm1621.row[x] = "text", "----", " " or a number with one decimal like "0.4", "237.5", "123456.7" + // "123456.7" will be shown as "9999" being a four digit overflow + + // AddLog(LOG_LEVEL_DEBUG, PSTR("TM1: Row1 '%s', Row2 '%s'"), Tm1621.row[0], Tm1621.row[1]); + + uint8_t buffer[8] = {0}; // TM1621 16-segment 4-bit common buffer + char row[4]; + for (uint32_t j = 0; j < 2; j++) { + // 0.4V => " 04", 0.0A => " ", 1234.5V => "1234" + uint32_t len = strlen(this->row_[j]); + char *dp = nullptr; // Expect number larger than "123" + int row_idx = len - 3; // "1234.5" + if (len <= 5) { // "----", " ", "0.4", "237.5" + dp = strchr(this->row_[j], '.'); + row_idx = len - 1; + } else if (len > 6) { // "12345.6" + snprintf(this->row_[j], sizeof(this->row_[j]), "9999"); + row_idx = 3; + } + row[3] = (row_idx >= 0) ? this->row_[j][row_idx--] : ' '; + if ((row_idx >= 0) && dp) { + row_idx--; + } + row[2] = (row_idx >= 0) ? this->row_[j][row_idx--] : ' '; + row[1] = (row_idx >= 0) ? this->row_[j][row_idx--] : ' '; + row[0] = (row_idx >= 0) ? this->row_[j][row_idx--] : ' '; + + // AddLog(LOG_LEVEL_DEBUG, PSTR("TM1: Dump%d %4_H"), j +1, row); + + char command[10]; + char needle[2] = {0}; + for (uint32_t i = 0; i < 4; i++) { + needle[0] = row[i]; + int index = this->get_command_code_(command, sizeof(command), (const char *) needle, TM1621_KCHAR); + if (-1 == index) { + index = 11; + } + uint32_t bidx = (0 == j) ? i : 7 - i; + buffer[bidx] = TM1621_DIGIT_ROW[j][index]; + } + if (dp) { + if (0 == j) { + buffer[2] |= 0x80; // Row 1 decimal point + } else { + buffer[5] |= 0x08; // Row 2 decimal point + } + } + } + + if (this->fahrenheit_) { + buffer[1] |= 0x80; + } + if (this->celsius_) { + buffer[3] |= 0x80; + } + if (this->kwh_) { + buffer[4] |= 0x08; + } + if (this->humidity_) { + buffer[6] |= 0x08; + } + if (this->voltage_) { + buffer[7] |= 0x08; + } + + // AddLog(LOG_LEVEL_DEBUG, PSTR("TM1: Dump3 %8_H"), buffer); + + this->send_address_(0x10); // Sonoff only uses the upper 16 Segments + for (uint8_t i : buffer) { + this->send_common_(i); + } + this->stop_(); +} + +bool TM1621Display::send_command_(uint16_t command) { + uint16_t full_command = (0x0400 | command) << 5; // 0b100cccccccc00000 + this->cs_pin_->digital_write(false); // Start command sequence + delayMicroseconds(TM1621_PULSE_WIDTH / 2); + for (uint32_t i = 0; i < 12; i++) { + this->write_pin_->digital_write(false); // Start write sequence + if (full_command & 0x8000) { + this->data_pin_->digital_write(true); // Set data + } else { + this->data_pin_->digital_write(false); // Set data + } + delayMicroseconds(TM1621_PULSE_WIDTH); + this->write_pin_->digital_write(true); // Read data + delayMicroseconds(TM1621_PULSE_WIDTH); + full_command <<= 1; + } + this->stop_(); + return true; +} + +bool TM1621Display::send_common_(uint8_t common) { + for (uint32_t i = 0; i < 8; i++) { + this->write_pin_->digital_write(false); // Start write sequence + if (common & 1) { + this->data_pin_->digital_write(true); // Set data + } else { + this->data_pin_->digital_write(false); // Set data + } + delayMicroseconds(TM1621_PULSE_WIDTH); + this->write_pin_->digital_write(true); // Read data + delayMicroseconds(TM1621_PULSE_WIDTH); + common >>= 1; + } + return true; +} + +bool TM1621Display::send_address_(uint16_t address) { + uint16_t full_address = (address | 0x0140) << 7; // 0b101aaaaaa0000000 + this->cs_pin_->digital_write(false); // Start command sequence + delayMicroseconds(TM1621_PULSE_WIDTH / 2); + for (uint32_t i = 0; i < 9; i++) { + this->write_pin_->digital_write(false); // Start write sequence + if (full_address & 0x8000) { + this->data_pin_->digital_write(true); // Set data + } else { + this->data_pin_->digital_write(false); // Set data + } + delayMicroseconds(TM1621_PULSE_WIDTH); + this->write_pin_->digital_write(true); // Read data + delayMicroseconds(TM1621_PULSE_WIDTH); + full_address <<= 1; + } + return true; +} + +uint8_t TM1621Display::print(uint8_t start_pos, const char *str) { + // ESP_LOGD(TAG, "Print at %d: %s", start_pos, str); + return snprintf(this->row_[start_pos], sizeof(this->row_[start_pos]), "%s", str); +} +uint8_t TM1621Display::print(const char *str) { return this->print(0, str); } +uint8_t TM1621Display::printf(uint8_t pos, const char *format, ...) { + va_list arg; + va_start(arg, format); + char buffer[64]; + int ret = vsnprintf(buffer, sizeof(buffer), format, arg); + va_end(arg); + if (ret > 0) + return this->print(pos, buffer); + return 0; +} +uint8_t TM1621Display::printf(const char *format, ...) { + va_list arg; + va_start(arg, format); + char buffer[64]; + int ret = vsnprintf(buffer, sizeof(buffer), format, arg); + va_end(arg); + if (ret > 0) + return this->print(buffer); + return 0; +} + +int TM1621Display::get_command_code_(char *destination, size_t destination_size, const char *needle, + const char *haystack) { + // Returns -1 of not found + // Returns index and command if found + int result = -1; + const char *read = haystack; + char *write = destination; + + while (true) { + result++; + size_t size = destination_size - 1; + write = destination; + char ch = '.'; + while ((ch != '\0') && (ch != '|')) { + ch = *(read++); + if (size && (ch != '|')) { + *write++ = ch; + size--; + } + } + *write = '\0'; + if (!strcasecmp(needle, destination)) { + break; + } + if (0 == ch) { + result = -1; + break; + } + } + return result; +} +} // namespace tm1621 +} // namespace esphome diff --git a/esphome/components/tm1621/tm1621.h b/esphome/components/tm1621/tm1621.h new file mode 100644 index 0000000000..b9f330e96e --- /dev/null +++ b/esphome/components/tm1621/tm1621.h @@ -0,0 +1,74 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/defines.h" +#include "esphome/core/hal.h" + +namespace esphome { +namespace tm1621 { + +class TM1621Display; + +using tm1621_writer_t = std::function; + +class TM1621Display : public PollingComponent { + public: + void set_writer(tm1621_writer_t &&writer) { this->writer_ = writer; } + + void setup() override; + + void dump_config() override; + + void set_cs_pin(GPIOPin *pin) { cs_pin_ = pin; } + void set_data_pin(GPIOPin *pin) { data_pin_ = pin; } + void set_read_pin(GPIOPin *pin) { read_pin_ = pin; } + void set_write_pin(GPIOPin *pin) { write_pin_ = pin; } + + void display_celsius(bool d) { celsius_ = d; } + void display_fahrenheit(bool d) { fahrenheit_ = d; } + void display_humidity(bool d) { humidity_ = d; } + void display_voltage(bool d) { voltage_ = d; } + void display_kwh(bool d) { kwh_ = d; } + + float get_setup_priority() const override; + + void update() override; + + /// Evaluate the printf-format and print the result at the given position. + uint8_t printf(uint8_t pos, const char *format, ...) __attribute__((format(printf, 3, 4))); + /// Evaluate the printf-format and print the result at position 0. + uint8_t printf(const char *format, ...) __attribute__((format(printf, 2, 3))); + + /// Print `str` at the given position. + uint8_t print(uint8_t pos, const char *str); + /// Print `str` at position 0. + uint8_t print(const char *str); + + void display(); + + protected: + void bit_delay_(); + void setup_pins_(); + bool send_command_(uint16_t command); + bool send_common_(uint8_t common); + bool send_address_(uint16_t address); + void stop_(); + int get_command_code_(char *destination, size_t destination_size, const char *needle, const char *haystack); + + GPIOPin *data_pin_; + GPIOPin *cs_pin_; + GPIOPin *read_pin_; + GPIOPin *write_pin_; + optional writer_{}; + char row_[2][12]; + uint8_t state_; + uint8_t device_; + bool celsius_; + bool fahrenheit_; + bool humidity_; + bool voltage_; + bool kwh_; +}; + +} // namespace tm1621 +} // namespace esphome diff --git a/esphome/components/tm1638/__init__.py b/esphome/components/tm1638/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/tm1638/binary_sensor/__init__.py b/esphome/components/tm1638/binary_sensor/__init__.py new file mode 100644 index 0000000000..7262d9e9e1 --- /dev/null +++ b/esphome/components/tm1638/binary_sensor/__init__.py @@ -0,0 +1,22 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import binary_sensor +from esphome.const import CONF_KEY +from ..display import tm1638_ns, TM1638Component, CONF_TM1638_ID + +TM1638Key = tm1638_ns.class_("TM1638Key", binary_sensor.BinarySensor) + +CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(TM1638Key), + cv.GenerateID(CONF_TM1638_ID): cv.use_id(TM1638Component), + cv.Required(CONF_KEY): cv.int_range(min=0, max=15), + } +) + + +async def to_code(config): + var = await binary_sensor.new_binary_sensor(config) + cg.add(var.set_keycode(config[CONF_KEY])) + hub = await cg.get_variable(config[CONF_TM1638_ID]) + cg.add(hub.register_listener(var)) diff --git a/esphome/components/tm1638/binary_sensor/tm1638_key.cpp b/esphome/components/tm1638/binary_sensor/tm1638_key.cpp new file mode 100644 index 0000000000..c143bafaea --- /dev/null +++ b/esphome/components/tm1638/binary_sensor/tm1638_key.cpp @@ -0,0 +1,13 @@ +#include "tm1638_key.h" + +namespace esphome { +namespace tm1638 { + +void TM1638Key::keys_update(uint8_t keys) { + bool pressed = keys & (1 << key_code_); + if (pressed != this->state) + this->publish_state(pressed); +} + +} // namespace tm1638 +} // namespace esphome diff --git a/esphome/components/tm1638/binary_sensor/tm1638_key.h b/esphome/components/tm1638/binary_sensor/tm1638_key.h new file mode 100644 index 0000000000..0ea385f434 --- /dev/null +++ b/esphome/components/tm1638/binary_sensor/tm1638_key.h @@ -0,0 +1,19 @@ +#pragma once + +#include "esphome/components/binary_sensor/binary_sensor.h" +#include "../tm1638.h" + +namespace esphome { +namespace tm1638 { + +class TM1638Key : public binary_sensor::BinarySensor, public KeyListener { + public: + void set_keycode(uint8_t key_code) { key_code_ = key_code; }; + void keys_update(uint8_t keys) override; + + protected: + uint8_t key_code_{0}; +}; + +} // namespace tm1638 +} // namespace esphome diff --git a/esphome/components/tm1638/display.py b/esphome/components/tm1638/display.py new file mode 100644 index 0000000000..6339983674 --- /dev/null +++ b/esphome/components/tm1638/display.py @@ -0,0 +1,55 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import pins +from esphome.components import display +from esphome.const import ( + CONF_ID, + CONF_INTENSITY, + CONF_LAMBDA, + CONF_CLK_PIN, + CONF_DIO_PIN, + CONF_STB_PIN, +) + +CODEOWNERS = ["@skykingjwc"] + +CONF_TM1638_ID = "tm1638_id" + +tm1638_ns = cg.esphome_ns.namespace("tm1638") +TM1638Component = tm1638_ns.class_("TM1638Component", cg.PollingComponent) +TM1638ComponentRef = TM1638Component.operator("ref") + + +CONFIG_SCHEMA = display.BASIC_DISPLAY_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(TM1638Component), + cv.Required(CONF_CLK_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_STB_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_DIO_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_INTENSITY, default=7): cv.int_range(min=0, max=8), + } +).extend(cv.polling_component_schema("1s")) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await display.register_display(var, config) + + clk = await cg.gpio_pin_expression(config[CONF_CLK_PIN]) + cg.add(var.set_clk_pin(clk)) + + dio = await cg.gpio_pin_expression(config[CONF_DIO_PIN]) + cg.add(var.set_dio_pin(dio)) + + stb = await cg.gpio_pin_expression(config[CONF_STB_PIN]) + cg.add(var.set_stb_pin(stb)) + + cg.add(var.set_intensity(config[CONF_INTENSITY])) + + if CONF_LAMBDA in config: + lambda_ = await cg.process_lambda( + config[CONF_LAMBDA], [(TM1638ComponentRef, "it")], return_type=cg.void + ) + + cg.add(var.set_writer(lambda_)) diff --git a/esphome/components/tm1638/output/__init__.py b/esphome/components/tm1638/output/__init__.py new file mode 100644 index 0000000000..2d982e409d --- /dev/null +++ b/esphome/components/tm1638/output/__init__.py @@ -0,0 +1,25 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import output +from esphome.const import CONF_ID, CONF_LED +from ..display import tm1638_ns, TM1638Component, CONF_TM1638_ID + +TM1638OutputLed = tm1638_ns.class_("TM1638OutputLed", output.BinaryOutput, cg.Component) + + +CONFIG_SCHEMA = output.BINARY_OUTPUT_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(TM1638OutputLed), + cv.GenerateID(CONF_TM1638_ID): cv.use_id(TM1638Component), + cv.Required(CONF_LED): cv.int_range(min=0, max=7), + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await output.register_output(var, config) + await cg.register_component(var, config) + cg.add(var.set_lednum(config[CONF_LED])) + hub = await cg.get_variable(config[CONF_TM1638_ID]) + cg.add(var.set_tm1638(hub)) diff --git a/esphome/components/tm1638/output/tm1638_output_led.cpp b/esphome/components/tm1638/output/tm1638_output_led.cpp new file mode 100644 index 0000000000..ea1c84e64b --- /dev/null +++ b/esphome/components/tm1638/output/tm1638_output_led.cpp @@ -0,0 +1,17 @@ +#include "tm1638_output_led.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace tm1638 { + +static const char *const TAG = "tm1638.led"; + +void TM1638OutputLed::write_state(bool state) { tm1638_->set_led(led_, state); } + +void TM1638OutputLed::dump_config() { + LOG_BINARY_OUTPUT(this); + ESP_LOGCONFIG(TAG, " LED: %d", led_); +} + +} // namespace tm1638 +} // namespace esphome diff --git a/esphome/components/tm1638/output/tm1638_output_led.h b/esphome/components/tm1638/output/tm1638_output_led.h new file mode 100644 index 0000000000..6aa1015aae --- /dev/null +++ b/esphome/components/tm1638/output/tm1638_output_led.h @@ -0,0 +1,25 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/output/binary_output.h" +#include "../tm1638.h" + +namespace esphome { +namespace tm1638 { + +class TM1638OutputLed : public output::BinaryOutput, public Component { + public: + void dump_config() override; + + void set_tm1638(TM1638Component *tm1638) { tm1638_ = tm1638; } + void set_lednum(int led) { led_ = led; } + + protected: + void write_state(bool state) override; + + TM1638Component *tm1638_; + int led_; +}; + +} // namespace tm1638 +} // namespace esphome diff --git a/esphome/components/tm1638/sevenseg.h b/esphome/components/tm1638/sevenseg.h new file mode 100644 index 0000000000..e20a55a69f --- /dev/null +++ b/esphome/components/tm1638/sevenseg.h @@ -0,0 +1,107 @@ +#pragma once + +namespace esphome { +namespace tm1638 { +namespace TM1638Translation { + +const unsigned char SEVEN_SEG[] PROGMEM = { + 0x00, /* (space) */ + 0x86, /* ! */ + 0x22, /* " */ + 0x7E, /* # */ + 0x6D, /* $ */ + 0xD2, /* % */ + 0x46, /* & */ + 0x20, /* ' */ + 0x29, /* ( */ + 0x0B, /* ) */ + 0x21, /* * */ + 0x70, /* + */ + 0x10, /* , */ + 0x40, /* - */ + 0x80, /* . */ + 0x52, /* / */ + 0x3F, /* 0 */ + 0x06, /* 1 */ + 0x5B, /* 2 */ + 0x4F, /* 3 */ + 0x66, /* 4 */ + 0x6D, /* 5 */ + 0x7D, /* 6 */ + 0x07, /* 7 */ + 0x7F, /* 8 */ + 0x6F, /* 9 */ + 0x09, /* : */ + 0x0D, /* ; */ + 0x61, /* < */ + 0x48, /* = */ + 0x43, /* > */ + 0xD3, /* ? */ + 0x5F, /* @ */ + 0x77, /* A */ + 0x7C, /* B */ + 0x39, /* C */ + 0x5E, /* D */ + 0x79, /* E */ + 0x71, /* F */ + 0x3D, /* G */ + 0x76, /* H */ + 0x30, /* I */ + 0x1E, /* J */ + 0x75, /* K */ + 0x38, /* L */ + 0x15, /* M */ + 0x37, /* N */ + 0x3F, /* O */ + 0x73, /* P */ + 0x6B, /* Q */ + 0x33, /* R */ + 0x6D, /* S */ + 0x78, /* T */ + 0x3E, /* U */ + 0x3E, /* V */ + 0x2A, /* W */ + 0x76, /* X */ + 0x6E, /* Y */ + 0x5B, /* Z */ + 0x39, /* [ */ + 0x64, /* \ */ + 0x0F, /* ] */ + 0x23, /* ^ */ + 0x08, /* _ */ + 0x02, /* ` */ + 0x5F, /* a */ + 0x7C, /* b */ + 0x58, /* c */ + 0x5E, /* d */ + 0x7B, /* e */ + 0x71, /* f */ + 0x6F, /* g */ + 0x74, /* h */ + 0x10, /* i */ + 0x0C, /* j */ + 0x75, /* k */ + 0x30, /* l */ + 0x14, /* m */ + 0x54, /* n */ + 0x5C, /* o */ + 0x73, /* p */ + 0x67, /* q */ + 0x50, /* r */ + 0x6D, /* s */ + 0x78, /* t */ + 0x1C, /* u */ + 0x1C, /* v */ + 0x14, /* w */ + 0x76, /* x */ + 0x6E, /* y */ + 0x5B, /* z */ + 0x46, /* { */ + 0x30, /* | */ + 0x70, /* } */ + 0x01, /* ~ */ +}; + +}; // namespace TM1638Translation +} // namespace tm1638 +} // namespace esphome diff --git a/esphome/components/tm1638/switch/__init__.py b/esphome/components/tm1638/switch/__init__.py new file mode 100644 index 0000000000..ed6aa91d03 --- /dev/null +++ b/esphome/components/tm1638/switch/__init__.py @@ -0,0 +1,24 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import switch +from esphome.const import CONF_LED +from ..display import tm1638_ns, TM1638Component, CONF_TM1638_ID + +TM1638SwitchLed = tm1638_ns.class_("TM1638SwitchLed", switch.Switch, cg.Component) + + +CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(TM1638SwitchLed), + cv.GenerateID(CONF_TM1638_ID): cv.use_id(TM1638Component), + cv.Required(CONF_LED): cv.int_range(min=0, max=7), + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + var = await switch.new_switch(config) + await cg.register_component(var, config) + cg.add(var.set_lednum(config[CONF_LED])) + hub = await cg.get_variable(config[CONF_TM1638_ID]) + cg.add(var.set_tm1638(hub)) diff --git a/esphome/components/tm1638/switch/tm1638_switch_led.cpp b/esphome/components/tm1638/switch/tm1638_switch_led.cpp new file mode 100644 index 0000000000..60c9e8b4a9 --- /dev/null +++ b/esphome/components/tm1638/switch/tm1638_switch_led.cpp @@ -0,0 +1,20 @@ +#include "tm1638_switch_led.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace tm1638 { + +static const char *const TAG = "tm1638.led"; + +void TM1638SwitchLed::write_state(bool state) { + tm1638_->set_led(led_, state); + publish_state(state); +} + +void TM1638SwitchLed::dump_config() { + LOG_SWITCH("", "TM1638 LED", this); + ESP_LOGCONFIG(TAG, " LED: %d", led_); +} + +} // namespace tm1638 +} // namespace esphome diff --git a/esphome/components/tm1638/switch/tm1638_switch_led.h b/esphome/components/tm1638/switch/tm1638_switch_led.h new file mode 100644 index 0000000000..10516e0079 --- /dev/null +++ b/esphome/components/tm1638/switch/tm1638_switch_led.h @@ -0,0 +1,23 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/switch/switch.h" +#include "../tm1638.h" + +namespace esphome { +namespace tm1638 { + +class TM1638SwitchLed : public switch_::Switch, public Component { + public: + void dump_config() override; + + void set_tm1638(TM1638Component *tm1638) { tm1638_ = tm1638; } + void set_lednum(int led) { led_ = led; } + + protected: + void write_state(bool state) override; + TM1638Component *tm1638_; + int led_; +}; +} // namespace tm1638 +} // namespace esphome diff --git a/esphome/components/tm1638/tm1638.cpp b/esphome/components/tm1638/tm1638.cpp new file mode 100644 index 0000000000..526b53f601 --- /dev/null +++ b/esphome/components/tm1638/tm1638.cpp @@ -0,0 +1,288 @@ +#include "tm1638.h" +#include "sevenseg.h" +#include "esphome/core/log.h" +#include "esphome/core/helpers.h" +#include "esphome/core/hal.h" + +namespace esphome { +namespace tm1638 { + +static const char *const TAG = "display.tm1638"; +static const uint8_t TM1638_REGISTER_FIXEDADDRESS = 0x44; +static const uint8_t TM1638_REGISTER_AUTOADDRESS = 0x40; +static const uint8_t TM1638_REGISTER_READBUTTONS = 0x42; +static const uint8_t TM1638_REGISTER_DISPLAYOFF = 0x80; +static const uint8_t TM1638_REGISTER_DISPLAYON = 0x88; +static const uint8_t TM1638_REGISTER_7SEG_0 = 0xC0; +static const uint8_t TM1638_REGISTER_LED_0 = 0xC1; +static const uint8_t TM1638_UNKNOWN_CHAR = 0b11111111; + +static const uint8_t TM1638_SHIFT_DELAY = 4; // clock pause between commands, default 4ms + +void TM1638Component::setup() { + ESP_LOGD(TAG, "Setting up TM1638..."); + + this->clk_pin_->setup(); // OUTPUT + this->dio_pin_->setup(); // OUTPUT + this->stb_pin_->setup(); // OUTPUT + + this->clk_pin_->pin_mode(gpio::FLAG_OUTPUT); + this->dio_pin_->pin_mode(gpio::FLAG_OUTPUT); + this->stb_pin_->pin_mode(gpio::FLAG_OUTPUT); + + this->clk_pin_->digital_write(false); + this->dio_pin_->digital_write(false); + this->stb_pin_->digital_write(false); + + this->set_intensity(intensity_); + + this->reset_(); // all LEDs off + + for (uint8_t i = 0; i < 8; i++) // zero fill print buffer + this->buffer_[i] = 0; +} + +void TM1638Component::dump_config() { + ESP_LOGCONFIG(TAG, "TM1638:"); + ESP_LOGCONFIG(TAG, " Intensity: %u", this->intensity_); + LOG_PIN(" CLK Pin: ", this->clk_pin_); + LOG_PIN(" DIO Pin: ", this->dio_pin_); + LOG_PIN(" STB Pin: ", this->stb_pin_); + LOG_UPDATE_INTERVAL(this); +} + +void TM1638Component::loop() { + if (this->listeners_.empty()) + return; + + uint8_t keys = this->get_keys(); + for (auto &listener : this->listeners_) + listener->keys_update(keys); +} + +uint8_t TM1638Component::get_keys() { + uint8_t buttons = 0; + + this->stb_pin_->digital_write(false); + + this->shift_out_(TM1638_REGISTER_READBUTTONS); + + this->dio_pin_->pin_mode(gpio::FLAG_INPUT); + + delayMicroseconds(10); + + for (uint8_t i = 0; i < 4; i++) { // read the 4 button registers + uint8_t v = this->shift_in_(); + buttons |= v << i; // shift bits to correct slots in the byte + } + + this->dio_pin_->pin_mode(gpio::FLAG_OUTPUT); + + this->stb_pin_->digital_write(true); + + return buttons; +} + +void TM1638Component::update() { // this is called at the interval specified in the config.yaml + if (this->writer_.has_value()) { + (*this->writer_)(*this); + } + + this->display(); +} + +float TM1638Component::get_setup_priority() const { return setup_priority::PROCESSOR; } + +void TM1638Component::display() { + for (uint8_t i = 0; i < 8; i++) { + this->set_7seg_(i, buffer_[i]); + } +} + +void TM1638Component::reset_() { + uint8_t num_commands = 16; // 16 addresses, 8 for 7seg and 8 for LEDs + uint8_t commands[num_commands]; + + for (uint8_t i = 0; i < num_commands; i++) { + commands[i] = 0; + } + + this->send_command_sequence_(commands, num_commands, TM1638_REGISTER_7SEG_0); +} + +/////////////// LEDs ///////////////// + +void TM1638Component::set_led(int led_pos, bool led_on_off) { + this->send_command_(TM1638_REGISTER_FIXEDADDRESS); + + uint8_t commands[2]; + + commands[0] = TM1638_REGISTER_LED_0 + (led_pos << 1); + commands[1] = led_on_off; + + this->send_commands_(commands, 2); +} + +void TM1638Component::set_7seg_(int seg_pos, uint8_t seg_bits) { + this->send_command_(TM1638_REGISTER_FIXEDADDRESS); + + uint8_t commands[2] = {}; + + commands[0] = TM1638_REGISTER_7SEG_0 + (seg_pos << 1); + commands[1] = seg_bits; + + this->send_commands_(commands, 2); +} + +void TM1638Component::set_intensity(uint8_t brightness_level) { + this->intensity_ = brightness_level; + + this->send_command_(TM1638_REGISTER_FIXEDADDRESS); + + if (brightness_level > 0) { + this->send_command_((uint8_t)(TM1638_REGISTER_DISPLAYON | intensity_)); + } else { + this->send_command_(TM1638_REGISTER_DISPLAYOFF); + } +} + +/////////////// DISPLAY PRINT ///////////////// + +uint8_t TM1638Component::print(uint8_t start_pos, const char *str) { + uint8_t pos = start_pos; + + bool last_was_dot = false; + + for (; *str != '\0'; str++) { + uint8_t data = TM1638_UNKNOWN_CHAR; + + if (*str >= ' ' && *str <= '~') { + data = progmem_read_byte(&TM1638Translation::SEVEN_SEG[*str - 32]); // subract 32 to account for ASCII offset + } else if (data == TM1638_UNKNOWN_CHAR) { + ESP_LOGW(TAG, "Encountered character '%c' with no TM1638 representation while translating string!", *str); + } + + if (*str == '.') // handle dots + { + if (pos != start_pos && + !last_was_dot) // if we are not at the first position, backup by one unless last char was a dot + { + pos--; + } + this->buffer_[pos] |= 0b10000000; // turn on the dot on the previous position + last_was_dot = true; // set a bit in case the next chracter is also a dot + } else // if not a dot, then just write the character to display + { + if (pos >= 8) { + ESP_LOGI(TAG, "TM1638 String is too long for the display!"); + break; + } + this->buffer_[pos] = data; + last_was_dot = false; // clear dot tracking bit + } + + pos++; + } + return pos - start_pos; +} + +/////////////// PRINT ///////////////// + +uint8_t TM1638Component::print(const char *str) { return this->print(0, str); } + +uint8_t TM1638Component::printf(uint8_t pos, const char *format, ...) { + va_list arg; + va_start(arg, format); + char buffer[64]; + int ret = vsnprintf(buffer, sizeof(buffer), format, arg); + va_end(arg); + if (ret > 0) + return this->print(pos, buffer); + return 0; +} +uint8_t TM1638Component::printf(const char *format, ...) { + va_list arg; + va_start(arg, format); + char buffer[64]; + int ret = vsnprintf(buffer, sizeof(buffer), format, arg); + va_end(arg); + if (ret > 0) + return this->print(buffer); + return 0; +} + +#ifdef USE_TIME +uint8_t TM1638Component::strftime(uint8_t pos, const char *format, time::ESPTime time) { + char buffer[64]; + size_t ret = time.strftime(buffer, sizeof(buffer), format); + if (ret > 0) + return this->print(pos, buffer); + return 0; +} +uint8_t TM1638Component::strftime(const char *format, time::ESPTime time) { return this->strftime(0, format, time); } +#endif + +//////////////// SPI //////////////// + +void TM1638Component::send_command_(uint8_t value) { + this->stb_pin_->pin_mode(gpio::FLAG_OUTPUT); + this->stb_pin_->digital_write(false); + this->shift_out_(value); + this->stb_pin_->digital_write(true); +} + +void TM1638Component::send_commands_(uint8_t const commands[], uint8_t num_commands) { + this->stb_pin_->digital_write(false); + + for (uint8_t i = 0; i < num_commands; i++) { + uint8_t command = commands[i]; + this->shift_out_(command); + } + this->stb_pin_->digital_write(true); +} + +void TM1638Component::send_command_leave_open_(uint8_t value) { + this->stb_pin_->digital_write(false); + this->shift_out_(value); +} + +void TM1638Component::send_command_sequence_(uint8_t commands[], uint8_t num_commands, uint8_t starting_address) { + this->send_command_(TM1638_REGISTER_AUTOADDRESS); + this->send_command_leave_open_(starting_address); + + for (uint8_t i = 0; i < num_commands; i++) { + this->shift_out_(commands[i]); + } + + this->stb_pin_->digital_write(true); +} + +uint8_t TM1638Component::shift_in_() { + uint8_t value = 0; + + for (int i = 0; i < 8; ++i) { + value |= dio_pin_->digital_read() << i; + delayMicroseconds(TM1638_SHIFT_DELAY); + this->clk_pin_->digital_write(true); + delayMicroseconds(TM1638_SHIFT_DELAY); + this->clk_pin_->digital_write(false); + delayMicroseconds(TM1638_SHIFT_DELAY); + } + return value; +} + +void TM1638Component::shift_out_(uint8_t val) { + for (int i = 0; i < 8; i++) { + this->dio_pin_->digital_write((val & (1 << i))); + delayMicroseconds(TM1638_SHIFT_DELAY); + + this->clk_pin_->digital_write(true); + delayMicroseconds(TM1638_SHIFT_DELAY); + + this->clk_pin_->digital_write(false); + delayMicroseconds(TM1638_SHIFT_DELAY); + } +} + +} // namespace tm1638 +} // namespace esphome diff --git a/esphome/components/tm1638/tm1638.h b/esphome/components/tm1638/tm1638.h new file mode 100644 index 0000000000..44160ad227 --- /dev/null +++ b/esphome/components/tm1638/tm1638.h @@ -0,0 +1,81 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/defines.h" +#include "esphome/core/automation.h" +#include "esphome/core/hal.h" + +#ifdef USE_TIME +#include "esphome/components/time/real_time_clock.h" +#endif + +namespace esphome { +namespace tm1638 { + +class KeyListener { + public: + virtual void keys_update(uint8_t keys){}; +}; + +class TM1638Component; + +using tm1638_writer_t = std::function; + +class TM1638Component : public PollingComponent { + public: + void set_writer(tm1638_writer_t &&writer) { this->writer_ = writer; } + void setup() override; + void dump_config() override; + void update() override; + float get_setup_priority() const override; + void set_intensity(uint8_t brightness_level); + void display(); + + void set_clk_pin(GPIOPin *pin) { this->clk_pin_ = pin; } + void set_dio_pin(GPIOPin *pin) { this->dio_pin_ = pin; } + void set_stb_pin(GPIOPin *pin) { this->stb_pin_ = pin; } + + void register_listener(KeyListener *listener) { this->listeners_.push_back(listener); } + + /// Evaluate the printf-format and print the result at the given position. + uint8_t printf(uint8_t pos, const char *format, ...) __attribute__((format(printf, 3, 4))); + /// Evaluate the printf-format and print the result at position 0. + uint8_t printf(const char *format, ...) __attribute__((format(printf, 2, 3))); + + /// Print `str` at the given position. + uint8_t print(uint8_t pos, const char *str); + /// Print `str` at position 0. + uint8_t print(const char *str); + + void loop() override; + uint8_t get_keys(); + +#ifdef USE_TIME + /// Evaluate the strftime-format and print the result at the given position. + uint8_t strftime(uint8_t pos, const char *format, time::ESPTime time) __attribute__((format(strftime, 3, 0))); + /// Evaluate the strftime-format and print the result at position 0. + uint8_t strftime(const char *format, time::ESPTime time) __attribute__((format(strftime, 2, 0))); +#endif + + void set_led(int led_pos, bool led_on_off); + + protected: + void set_7seg_(int seg_pos, uint8_t seg_bits); + void send_command_(uint8_t value); + void send_command_leave_open_(uint8_t value); + void send_commands_(uint8_t const commands[], uint8_t num_commands); + void send_command_sequence_(uint8_t commands[], uint8_t num_commands, uint8_t starting_address); + void shift_out_(uint8_t value); + void reset_(); + uint8_t shift_in_(); + uint8_t intensity_{}; /// brghtness of the display 0 through 7 + GPIOPin *clk_pin_; + GPIOPin *stb_pin_; + GPIOPin *dio_pin_; + uint8_t *buffer_ = new uint8_t[8]; + optional writer_{}; + std::vector listeners_{}; +}; + +} // namespace tm1638 +} // namespace esphome diff --git a/esphome/components/tsl2591/tsl2591.h b/esphome/components/tsl2591/tsl2591.h index d82dbc395f..5b7eea35ec 100644 --- a/esphome/components/tsl2591/tsl2591.h +++ b/esphome/components/tsl2591/tsl2591.h @@ -245,10 +245,10 @@ class TSL2591Component : public PollingComponent, public i2c::I2CDevice { protected: const char *name_; - sensor::Sensor *full_spectrum_sensor_; - sensor::Sensor *infrared_sensor_; - sensor::Sensor *visible_sensor_; - sensor::Sensor *calculated_lux_sensor_; + sensor::Sensor *full_spectrum_sensor_{nullptr}; + sensor::Sensor *infrared_sensor_{nullptr}; + sensor::Sensor *visible_sensor_{nullptr}; + sensor::Sensor *calculated_lux_sensor_{nullptr}; TSL2591IntegrationTime integration_time_; TSL2591ComponentGain component_gain_; TSL2591Gain gain_; diff --git a/esphome/components/tuya/switch/__init__.py b/esphome/components/tuya/switch/__init__.py index 4df6bba713..b4ec53ec59 100644 --- a/esphome/components/tuya/switch/__init__.py +++ b/esphome/components/tuya/switch/__init__.py @@ -1,7 +1,7 @@ from esphome.components import switch import esphome.config_validation as cv import esphome.codegen as cg -from esphome.const import CONF_ID, CONF_SWITCH_DATAPOINT +from esphome.const import CONF_SWITCH_DATAPOINT from .. import tuya_ns, CONF_TUYA_ID, Tuya DEPENDENCIES = ["tuya"] @@ -9,19 +9,21 @@ CODEOWNERS = ["@jesserockz"] TuyaSwitch = tuya_ns.class_("TuyaSwitch", switch.Switch, cg.Component) -CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(TuyaSwitch), - cv.GenerateID(CONF_TUYA_ID): cv.use_id(Tuya), - cv.Required(CONF_SWITCH_DATAPOINT): cv.uint8_t, - } -).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = ( + switch.switch_schema(TuyaSwitch) + .extend( + { + cv.GenerateID(CONF_TUYA_ID): cv.use_id(Tuya), + cv.Required(CONF_SWITCH_DATAPOINT): cv.uint8_t, + } + ) + .extend(cv.COMPONENT_SCHEMA) +) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await switch.new_switch(config) await cg.register_component(var, config) - await switch.register_switch(var, config) paren = await cg.get_variable(config[CONF_TUYA_ID]) cg.add(var.set_tuya_parent(paren)) diff --git a/esphome/components/tx20/tx20.h b/esphome/components/tx20/tx20.h index 1c617d0674..95a9517227 100644 --- a/esphome/components/tx20/tx20.h +++ b/esphome/components/tx20/tx20.h @@ -43,8 +43,8 @@ class Tx20Component : public Component { std::string wind_cardinal_direction_; InternalGPIOPin *pin_; - sensor::Sensor *wind_speed_sensor_; - sensor::Sensor *wind_direction_degrees_sensor_; + sensor::Sensor *wind_speed_sensor_{nullptr}; + sensor::Sensor *wind_direction_degrees_sensor_{nullptr}; Tx20ComponentStore store_; }; diff --git a/esphome/components/uart/switch/__init__.py b/esphome/components/uart/switch/__init__.py index 9e7f95bd2a..60f5ddaf0d 100644 --- a/esphome/components/uart/switch/__init__.py +++ b/esphome/components/uart/switch/__init__.py @@ -1,7 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import switch, uart -from esphome.const import CONF_DATA, CONF_ID, CONF_INVERTED, CONF_SEND_EVERY +from esphome.const import CONF_DATA, CONF_SEND_EVERY from esphome.core import HexInt from .. import uart_ns, validate_raw_data @@ -11,13 +11,10 @@ UARTSwitch = uart_ns.class_("UARTSwitch", switch.Switch, uart.UARTDevice, cg.Com CONFIG_SCHEMA = ( - switch.SWITCH_SCHEMA.extend( + switch.switch_schema(UARTSwitch, block_inverted=True) + .extend( { - cv.GenerateID(): cv.declare_id(UARTSwitch), cv.Required(CONF_DATA): validate_raw_data, - cv.Optional(CONF_INVERTED): cv.invalid( - "UART switches do not support inverted mode!" - ), cv.Optional(CONF_SEND_EVERY): cv.positive_time_period_milliseconds, } ) @@ -27,9 +24,8 @@ CONFIG_SCHEMA = ( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await switch.new_switch(config) await cg.register_component(var, config) - await switch.register_switch(var, config) await uart.register_uart_device(var, config) data = config[CONF_DATA] diff --git a/esphome/components/ufire_ec/__init__.py b/esphome/components/ufire_ec/__init__.py new file mode 100644 index 0000000000..08f36c7934 --- /dev/null +++ b/esphome/components/ufire_ec/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@pvizeli"] diff --git a/esphome/components/ufire_ec/sensor.py b/esphome/components/ufire_ec/sensor.py new file mode 100644 index 0000000000..9602d0c2d0 --- /dev/null +++ b/esphome/components/ufire_ec/sensor.py @@ -0,0 +1,126 @@ +import esphome.codegen as cg +from esphome import automation +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + CONF_ID, + CONF_EC, + CONF_TEMPERATURE, + DEVICE_CLASS_EMPTY, + DEVICE_CLASS_TEMPERATURE, + ICON_EMPTY, + STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, + UNIT_MILLISIEMENS_PER_CENTIMETER, +) + +DEPENDENCIES = ["i2c"] + +CONF_SOLUTION = "solution" +CONF_TEMPERATURE_SENSOR = "temperature_sensor" +CONF_TEMPERATURE_COMPENSATION = "temperature_compensation" +CONF_TEMPERATURE_COEFFICIENT = "temperature_coefficient" + +ufire_ec_ns = cg.esphome_ns.namespace("ufire_ec") +UFireECComponent = ufire_ec_ns.class_( + "UFireECComponent", cg.PollingComponent, i2c.I2CDevice +) + +# Actions +UFireECCalibrateProbeAction = ufire_ec_ns.class_( + "UFireECCalibrateProbeAction", automation.Action +) +UFireECResetAction = ufire_ec_ns.class_("UFireECResetAction", automation.Action) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(UFireECComponent), + cv.Exclusive(CONF_TEMPERATURE, "temperature"): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + accuracy_decimals=1, + ), + cv.Optional(CONF_EC): sensor.sensor_schema( + unit_of_measurement=UNIT_MILLISIEMENS_PER_CENTIMETER, + icon=ICON_EMPTY, + device_class=DEVICE_CLASS_EMPTY, + state_class=STATE_CLASS_MEASUREMENT, + accuracy_decimals=1, + ), + cv.Exclusive(CONF_TEMPERATURE_SENSOR, "temperature"): cv.use_id( + sensor.Sensor + ), + cv.Optional(CONF_TEMPERATURE_COMPENSATION, default=21.0): cv.temperature, + cv.Optional(CONF_TEMPERATURE_COEFFICIENT, default=0.019): cv.float_range( + min=0.01, max=0.04 + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x3C)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + cg.add(var.set_temperature_compensation(config[CONF_TEMPERATURE_COMPENSATION])) + cg.add(var.set_temperature_coefficient(config[CONF_TEMPERATURE_COEFFICIENT])) + + if CONF_TEMPERATURE in config: + sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) + cg.add(var.set_temperature_sensor(sens)) + + if CONF_EC in config: + sens = await sensor.new_sensor(config[CONF_EC]) + cg.add(var.set_ec_sensor(sens)) + + if CONF_TEMPERATURE_SENSOR in config: + sens = await cg.get_variable(config[CONF_TEMPERATURE_SENSOR]) + cg.add(var.set_temperature_sensor_external(sens)) + + await i2c.register_i2c_device(var, config) + + +UFIRE_EC_CALIBRATE_PROBE_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.use_id(UFireECComponent), + cv.Required(CONF_SOLUTION): cv.templatable(float), + cv.Required(CONF_TEMPERATURE): cv.templatable(cv.temperature), + } +) + + +@automation.register_action( + "ufire_ec.calibrate_probe", + UFireECCalibrateProbeAction, + UFIRE_EC_CALIBRATE_PROBE_SCHEMA, +) +async def ufire_ec_calibrate_probe_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, paren) + solution_ = await cg.templatable(config[CONF_SOLUTION], args, float) + temperature_ = await cg.templatable(config[CONF_TEMPERATURE], args, float) + cg.add(var.set_solution(solution_)) + cg.add(var.set_temperature(temperature_)) + return var + + +UFIRE_EC_RESET_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.use_id(UFireECComponent), + } +) + + +@automation.register_action( + "ufire_ec.reset", + UFireECResetAction, + UFIRE_EC_RESET_SCHEMA, +) +async def ufire_ec_reset_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, paren) + return var diff --git a/esphome/components/ufire_ec/ufire_ec.cpp b/esphome/components/ufire_ec/ufire_ec.cpp new file mode 100644 index 0000000000..7af4fadf75 --- /dev/null +++ b/esphome/components/ufire_ec/ufire_ec.cpp @@ -0,0 +1,118 @@ +#include "esphome/core/log.h" +#include "ufire_ec.h" + +namespace esphome { +namespace ufire_ec { + +static const char *const TAG = "ufire_ec"; + +void UFireECComponent::setup() { + ESP_LOGCONFIG(TAG, "Setting up uFire_ec..."); + + uint8_t version; + if (!this->read_byte(REGISTER_VERSION, &version) && version != 0xFF) { + this->mark_failed(); + return; + } + ESP_LOGI(TAG, "Found ufire_ec board version 0x%02X", version); + + // Write option for temperature adjustments + uint8_t config; + this->read_byte(REGISTER_CONFIG, &config); + if (this->temperature_sensor_ == nullptr && this->temperature_sensor_external_ == nullptr) { + config &= ~CONFIG_TEMP_COMPENSATION; + } else { + config |= CONFIG_TEMP_COMPENSATION; + } + this->write_byte(REGISTER_CONFIG, config); + + // Update temperature compensation + this->set_compensation_(this->temperature_compensation_); + this->set_coefficient_(this->temperature_coefficient_); +} + +void UFireECComponent::update() { + int wait = 0; + + if (this->temperature_sensor_ != nullptr) { + this->write_byte(REGISTER_TASK, COMMAND_MEASURE_TEMP); + wait += 750; + } else if (this->temperature_sensor_external_ != nullptr) { + this->set_temperature_(this->temperature_sensor_external_->state); + } + + if (this->ec_sensor_ != nullptr) { + this->write_byte(REGISTER_TASK, COMMAND_MEASURE_EC); + wait += 750; + } + + if (wait > 0) { + this->set_timeout("data", wait, [this]() { this->update_internal_(); }); + } +} + +void UFireECComponent::update_internal_() { + if (this->temperature_sensor_ != nullptr) + this->temperature_sensor_->publish_state(this->measure_temperature_()); + if (this->ec_sensor_ != nullptr) + this->ec_sensor_->publish_state(this->measure_ms_()); +} + +float UFireECComponent::measure_temperature_() { return this->read_data_(REGISTER_TEMP); } + +float UFireECComponent::measure_ms_() { return this->read_data_(REGISTER_MS); } + +void UFireECComponent::set_solution_(float solution, float temperature) { + solution /= (1 - (this->temperature_coefficient_ * (temperature - 25))); + this->write_data_(REGISTER_SOLUTION, solution); +} + +void UFireECComponent::set_compensation_(float temperature) { this->write_data_(REGISTER_COMPENSATION, temperature); } + +void UFireECComponent::set_coefficient_(float coefficient) { this->write_data_(REGISTER_COEFFICENT, coefficient); } + +void UFireECComponent::set_temperature_(float temperature) { this->write_data_(REGISTER_TEMP, temperature); } + +void UFireECComponent::calibrate_probe(float solution, float temperature) { + this->set_solution_(solution, temperature); + this->write_byte(REGISTER_TASK, COMMAND_CALIBRATE_PROBE); +} + +void UFireECComponent::reset_board() { this->write_data_(REGISTER_CALIBRATE_OFFSET, NAN); } + +float UFireECComponent::read_data_(uint8_t reg) { + float f; + uint8_t temp[4]; + + this->write(®, 1); + delay(10); + + for (uint8_t i = 0; i < 4; i++) { + this->read_bytes_raw(temp + i, 1); + } + memcpy(&f, temp, sizeof(f)); + + return f; +} + +void UFireECComponent::write_data_(uint8_t reg, float data) { + uint8_t temp[4]; + + memcpy(temp, &data, sizeof(data)); + this->write_bytes(reg, temp, 4); + delay(10); +} + +void UFireECComponent::dump_config() { + ESP_LOGCONFIG(TAG, "uFire-EC"); + LOG_I2C_DEVICE(this) + LOG_UPDATE_INTERVAL(this) + LOG_SENSOR(" ", "EC Sensor", this->ec_sensor_) + LOG_SENSOR(" ", "Temperature Sensor", this->temperature_sensor_) + LOG_SENSOR(" ", "Temperature Sensor external", this->temperature_sensor_external_) + ESP_LOGCONFIG(TAG, " Temperature Compensation: %f", this->temperature_compensation_); + ESP_LOGCONFIG(TAG, " Temperature Coefficient: %f", this->temperature_coefficient_); +} + +} // namespace ufire_ec +} // namespace esphome diff --git a/esphome/components/ufire_ec/ufire_ec.h b/esphome/components/ufire_ec/ufire_ec.h new file mode 100644 index 0000000000..3d436555a2 --- /dev/null +++ b/esphome/components/ufire_ec/ufire_ec.h @@ -0,0 +1,87 @@ +#pragma once + +#include "esphome/core/automation.h" +#include "esphome/core/component.h" +#include "esphome/core/hal.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace ufire_ec { + +static const uint8_t CONFIG_TEMP_COMPENSATION = 0x02; + +static const uint8_t REGISTER_VERSION = 0; +static const uint8_t REGISTER_MS = 1; +static const uint8_t REGISTER_TEMP = 5; +static const uint8_t REGISTER_SOLUTION = 9; +static const uint8_t REGISTER_COEFFICENT = 13; +static const uint8_t REGISTER_CALIBRATE_OFFSET = 33; +static const uint8_t REGISTER_COMPENSATION = 45; +static const uint8_t REGISTER_CONFIG = 54; +static const uint8_t REGISTER_TASK = 55; + +static const uint8_t COMMAND_CALIBRATE_PROBE = 20; +static const uint8_t COMMAND_MEASURE_TEMP = 40; +static const uint8_t COMMAND_MEASURE_EC = 80; + +class UFireECComponent : public PollingComponent, public i2c::I2CDevice { + public: + void setup() override; + void update() override; + void dump_config() override; + + void set_temperature_sensor(sensor::Sensor *temperature_sensor) { this->temperature_sensor_ = temperature_sensor; } + void set_temperature_sensor_external(sensor::Sensor *temperature_sensor) { + this->temperature_sensor_external_ = temperature_sensor; + } + void set_ec_sensor(sensor::Sensor *ec_sensor) { this->ec_sensor_ = ec_sensor; } + void set_temperature_compensation(float compensation) { this->temperature_compensation_ = compensation; } + void set_temperature_coefficient(float coefficient) { this->temperature_coefficient_ = coefficient; } + void calibrate_probe(float solution, float temperature); + void reset_board(); + + protected: + float measure_temperature_(); + float measure_ms_(); + void set_solution_(float solution, float temperature); + void set_compensation_(float temperature); + void set_coefficient_(float coefficient); + void set_temperature_(float temperature); + float read_data_(uint8_t reg); + void write_data_(uint8_t reg, float data); + void update_internal_(); + + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *temperature_sensor_external_{nullptr}; + sensor::Sensor *ec_sensor_{nullptr}; + float temperature_compensation_{0.0}; + float temperature_coefficient_{0.0}; +}; + +template class UFireECCalibrateProbeAction : public Action { + public: + UFireECCalibrateProbeAction(UFireECComponent *parent) : parent_(parent) {} + TEMPLATABLE_VALUE(float, solution) + TEMPLATABLE_VALUE(float, temperature) + + void play(Ts... x) override { + this->parent_->calibrate_probe(this->solution_.value(x...), this->temperature_.value(x...)); + } + + protected: + UFireECComponent *parent_; +}; + +template class UFireECResetAction : public Action { + public: + UFireECResetAction(UFireECComponent *parent) : parent_(parent) {} + + void play(Ts... x) override { this->parent_->reset_board(); } + + protected: + UFireECComponent *parent_; +}; + +} // namespace ufire_ec +} // namespace esphome diff --git a/esphome/components/ufire_ise/__init__.py b/esphome/components/ufire_ise/__init__.py new file mode 100644 index 0000000000..08f36c7934 --- /dev/null +++ b/esphome/components/ufire_ise/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@pvizeli"] diff --git a/esphome/components/ufire_ise/sensor.py b/esphome/components/ufire_ise/sensor.py new file mode 100644 index 0000000000..8f4359d6af --- /dev/null +++ b/esphome/components/ufire_ise/sensor.py @@ -0,0 +1,127 @@ +import esphome.codegen as cg +from esphome import automation +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + CONF_ID, + CONF_PH, + CONF_TEMPERATURE, + DEVICE_CLASS_EMPTY, + DEVICE_CLASS_TEMPERATURE, + ICON_EMPTY, + STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, + UNIT_PH, +) + +DEPENDENCIES = ["i2c"] + +CONF_SOLUTION = "solution" +CONF_TEMPERATURE_SENSOR = "temperature_sensor" + +ufire_ise_ns = cg.esphome_ns.namespace("ufire_ise") +UFireISEComponent = ufire_ise_ns.class_( + "UFireISEComponent", cg.PollingComponent, i2c.I2CDevice +) + +# Actions +UFireISECalibrateProbeLowAction = ufire_ise_ns.class_( + "UFireISECalibrateProbeLowAction", automation.Action +) +UFireISECalibrateProbeHighAction = ufire_ise_ns.class_( + "UFireISECalibrateProbeHighAction", automation.Action +) +UFireISEResetAction = ufire_ise_ns.class_("UFireISEResetAction", automation.Action) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(UFireISEComponent), + cv.Exclusive(CONF_TEMPERATURE, "temperature"): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + accuracy_decimals=1, + ), + cv.Optional(CONF_PH): sensor.sensor_schema( + unit_of_measurement=UNIT_PH, + icon=ICON_EMPTY, + device_class=DEVICE_CLASS_EMPTY, + state_class=STATE_CLASS_MEASUREMENT, + accuracy_decimals=1, + ), + cv.Exclusive(CONF_TEMPERATURE_SENSOR, "temperature"): cv.use_id( + sensor.Sensor + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x3F)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + + if CONF_TEMPERATURE in config: + sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) + cg.add(var.set_temperature_sensor(sens)) + + if CONF_PH in config: + sens = await sensor.new_sensor(config[CONF_PH]) + cg.add(var.set_ph_sensor(sens)) + + if CONF_TEMPERATURE_SENSOR in config: + sens = await cg.get_variable(config[CONF_TEMPERATURE_SENSOR]) + cg.add(var.set_temperature_sensor_external(sens)) + + await i2c.register_i2c_device(var, config) + + +UFIRE_ISE_CALIBRATE_PROBE_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.use_id(UFireISEComponent), + cv.Required(CONF_SOLUTION): cv.templatable(float), + } +) + + +@automation.register_action( + "ufire_ise.calibrate_probe_low", + UFireISECalibrateProbeLowAction, + UFIRE_ISE_CALIBRATE_PROBE_SCHEMA, +) +async def ufire_ise_calibrate_probe_low_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, paren) + template_ = await cg.templatable(config[CONF_SOLUTION], args, float) + cg.add(var.set_solution(template_)) + return var + + +@automation.register_action( + "ufire_ise.calibrate_probe_high", + UFireISECalibrateProbeHighAction, + UFIRE_ISE_CALIBRATE_PROBE_SCHEMA, +) +async def ufire_ise_calibrate_probe_high_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, paren) + template_ = await cg.templatable(config[CONF_SOLUTION], args, float) + cg.add(var.set_solution(template_)) + return var + + +UFIRE_ISE_RESET_SCHEMA = cv.Schema({cv.GenerateID(): cv.use_id(UFireISEComponent)}) + + +@automation.register_action( + "ufire_ise.reset", + UFireISEResetAction, + UFIRE_ISE_RESET_SCHEMA, +) +async def ufire_ise_reset_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, paren) + return var diff --git a/esphome/components/ufire_ise/ufire_ise.cpp b/esphome/components/ufire_ise/ufire_ise.cpp new file mode 100644 index 0000000000..957e6f3299 --- /dev/null +++ b/esphome/components/ufire_ise/ufire_ise.cpp @@ -0,0 +1,153 @@ +#include "esphome/core/log.h" +#include "ufire_ise.h" + +#include + +namespace esphome { +namespace ufire_ise { + +static const char *const TAG = "ufire_ise"; + +void UFireISEComponent::setup() { + ESP_LOGCONFIG(TAG, "Setting up uFire_ise..."); + + uint8_t version; + if (!this->read_byte(REGISTER_VERSION, &version) && version != 0xFF) { + this->mark_failed(); + return; + } + ESP_LOGI(TAG, "Found uFire_ise board version 0x%02X", version); + + // Write option for temperature adjustments + uint8_t config; + this->read_byte(REGISTER_CONFIG, &config); + if (this->temperature_sensor_ == nullptr && this->temperature_sensor_external_ == nullptr) { + config &= ~CONFIG_TEMP_COMPENSATION; + } else { + config |= CONFIG_TEMP_COMPENSATION; + } + this->write_byte(REGISTER_CONFIG, config); +} + +void UFireISEComponent::update() { + int wait = 0; + if (this->temperature_sensor_ != nullptr) { + this->write_byte(REGISTER_TASK, COMMAND_MEASURE_TEMP); + wait += 750; + } + if (this->ph_sensor_ != nullptr) { + this->write_byte(REGISTER_TASK, COMMAND_MEASURE_MV); + wait += 750; + } + + // Wait until measurement are taken + this->set_timeout("data", wait, [this]() { this->update_internal_(); }); +} + +void UFireISEComponent::update_internal_() { + float temperature = 0; + + // Read temperature internal and populate it + if (this->temperature_sensor_ != nullptr) { + temperature = this->measure_temperature_(); + this->temperature_sensor_->publish_state(temperature); + } + // Get temperature from external only for adjustments + else if (this->temperature_sensor_external_ != nullptr) { + temperature = this->temperature_sensor_external_->state; + } + + if (this->ph_sensor_ != nullptr) { + this->ph_sensor_->publish_state(this->measure_ph_(temperature)); + } +} + +float UFireISEComponent::measure_temperature_() { return this->read_data_(REGISTER_TEMP); } + +float UFireISEComponent::measure_mv_() { return this->read_data_(REGISTER_MV); } + +float UFireISEComponent::measure_ph_(float temperature) { + float mv, ph; + + mv = this->measure_mv_(); + if (mv == -1) + return -1; + + ph = fabs(7.0 - (mv / PROBE_MV_TO_PH)); + + // Determine the temperature correction + float distance_from_7 = std::abs(7 - roundf(ph)); + float distance_from_25 = std::floor(std::abs(25 - roundf(temperature)) / 10); + float temp_multiplier = (distance_from_25 * distance_from_7) * PROBE_TMP_CORRECTION; + if ((ph >= 8.0) && (temperature >= 35)) + temp_multiplier *= -1; + if ((ph <= 6.0) && (temperature <= 15)) + temp_multiplier *= -1; + + ph += temp_multiplier; + if ((ph <= 0.0) || (ph > 14.0)) + ph = -1; + if (std::isinf(ph)) + ph = -1; + if (std::isnan(ph)) + ph = -1; + + return ph; +} + +void UFireISEComponent::set_solution_(float solution) { + solution = (7 - solution) * PROBE_MV_TO_PH; + this->write_data_(REGISTER_SOLUTION, solution); +} + +void UFireISEComponent::calibrate_probe_low(float solution) { + this->set_solution_(solution); + this->write_byte(REGISTER_TASK, COMMAND_CALIBRATE_LOW); +} + +void UFireISEComponent::calibrate_probe_high(float solution) { + this->set_solution_(solution); + this->write_byte(REGISTER_TASK, COMMAND_CALIBRATE_HIGH); +} + +void UFireISEComponent::reset_board() { + this->write_data_(REGISTER_REFHIGH, NAN); + this->write_data_(REGISTER_REFLOW, NAN); + this->write_data_(REGISTER_READHIGH, NAN); + this->write_data_(REGISTER_READLOW, NAN); +} + +float UFireISEComponent::read_data_(uint8_t reg) { + float f; + uint8_t temp[4]; + + this->write(®, 1); + delay(10); + + for (uint8_t i = 0; i < 4; i++) { + this->read_bytes_raw(temp + i, 1); + } + memcpy(&f, temp, sizeof(f)); + + return f; +} + +void UFireISEComponent::write_data_(uint8_t reg, float data) { + uint8_t temp[4]; + + memcpy(temp, &data, sizeof(data)); + this->write_bytes(reg, temp, 4); + delay(10); +} + +void UFireISEComponent::dump_config() { + ESP_LOGCONFIG(TAG, "uFire-ISE"); + LOG_I2C_DEVICE(this) + LOG_UPDATE_INTERVAL(this) + LOG_SENSOR(" ", "PH Sensor", this->ph_sensor_) + LOG_SENSOR(" ", "Temperature Sensor", this->temperature_sensor_) + LOG_SENSOR(" ", "Temperature Sensor external", this->temperature_sensor_external_) +} + +} // namespace ufire_ise +} // namespace esphome diff --git a/esphome/components/ufire_ise/ufire_ise.h b/esphome/components/ufire_ise/ufire_ise.h new file mode 100644 index 0000000000..01efdcdb55 --- /dev/null +++ b/esphome/components/ufire_ise/ufire_ise.h @@ -0,0 +1,95 @@ +#pragma once + +#include "esphome/core/automation.h" +#include "esphome/core/component.h" +#include "esphome/core/hal.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace ufire_ise { + +static const float PROBE_MV_TO_PH = 59.2; +static const float PROBE_TMP_CORRECTION = 0.03; + +static const uint8_t CONFIG_TEMP_COMPENSATION = 0x02; + +static const uint8_t REGISTER_VERSION = 0; +static const uint8_t REGISTER_MV = 1; +static const uint8_t REGISTER_TEMP = 5; +static const uint8_t REGISTER_REFHIGH = 13; +static const uint8_t REGISTER_REFLOW = 17; +static const uint8_t REGISTER_READHIGH = 21; +static const uint8_t REGISTER_READLOW = 25; +static const uint8_t REGISTER_SOLUTION = 29; +static const uint8_t REGISTER_CONFIG = 38; +static const uint8_t REGISTER_TASK = 39; + +static const uint8_t COMMAND_CALIBRATE_HIGH = 8; +static const uint8_t COMMAND_CALIBRATE_LOW = 10; +static const uint8_t COMMAND_MEASURE_TEMP = 40; +static const uint8_t COMMAND_MEASURE_MV = 80; + +class UFireISEComponent : public PollingComponent, public i2c::I2CDevice { + public: + void setup() override; + void update() override; + void dump_config() override; + + void set_temperature_sensor(sensor::Sensor *temperature_sensor) { this->temperature_sensor_ = temperature_sensor; } + void set_temperature_sensor_external(sensor::Sensor *temperature_sensor) { + this->temperature_sensor_external_ = temperature_sensor; + } + void set_ph_sensor(sensor::Sensor *ph_sensor) { this->ph_sensor_ = ph_sensor; } + void calibrate_probe_low(float solution); + void calibrate_probe_high(float solution); + void reset_board(); + + protected: + float measure_temperature_(); + float measure_mv_(); + float measure_ph_(float temperature); + void set_solution_(float solution); + float read_data_(uint8_t reg); + void write_data_(uint8_t reg, float data); + void update_internal_(); + + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *temperature_sensor_external_{nullptr}; + sensor::Sensor *ph_sensor_{nullptr}; +}; + +template class UFireISECalibrateProbeLowAction : public Action { + public: + UFireISECalibrateProbeLowAction(UFireISEComponent *parent) : parent_(parent) {} + TEMPLATABLE_VALUE(float, solution) + + void play(Ts... x) override { this->parent_->calibrate_probe_low(this->solution_.value(x...)); } + + protected: + UFireISEComponent *parent_; +}; + +template class UFireISECalibrateProbeHighAction : public Action { + public: + UFireISECalibrateProbeHighAction(UFireISEComponent *parent) : parent_(parent) {} + TEMPLATABLE_VALUE(float, solution) + + void play(Ts... x) override { this->parent_->calibrate_probe_high(this->solution_.value(x...)); } + + protected: + UFireISEComponent *parent_; +}; + +template class UFireISEResetAction : public Action { + public: + UFireISEResetAction(UFireISEComponent *parent) : parent_(parent) {} + + void play(Ts... x) override { this->parent_->reset_board(); } + + protected: + UFireISEComponent *parent_; +}; + +} // namespace ufire_ise +} // namespace esphome diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index c3f70506e2..846a8e1303 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -10,6 +10,8 @@ from esphome.const import ( CONF_DNS1, CONF_DNS2, CONF_DOMAIN, + CONF_ENABLE_BTM, + CONF_ENABLE_RRM, CONF_FAST_CONNECT, CONF_GATEWAY, CONF_HIDDEN, @@ -32,10 +34,10 @@ from esphome.const import ( CONF_EAP, ) from esphome.core import CORE, HexInt, coroutine_with_priority +from esphome.components.esp32 import add_idf_sdkconfig_option from esphome.components.network import IPAddress from . import wpa2_eap - AUTO_LOAD = ["network"] wifi_ns = cg.esphome_ns.namespace("wifi") @@ -272,6 +274,12 @@ CONFIG_SCHEMA = cv.All( cv.SplitDefault(CONF_OUTPUT_POWER, esp8266=20.0): cv.All( cv.decibel, cv.float_range(min=8.5, max=20.5) ), + cv.SplitDefault(CONF_ENABLE_BTM, esp32_idf=False): cv.All( + cv.boolean, cv.only_with_esp_idf + ), + cv.SplitDefault(CONF_ENABLE_RRM, esp32_idf=False): cv.All( + cv.boolean, cv.only_with_esp_idf + ), cv.Optional("enable_mdns"): cv.invalid( "This option has been removed. Please use the [disabled] option under the " "new mdns component instead." @@ -373,6 +381,15 @@ async def to_code(config): elif CORE.is_esp32 and CORE.using_arduino: cg.add_library("WiFi", None) + if CORE.is_esp32 and CORE.using_esp_idf: + if config[CONF_ENABLE_BTM] or config[CONF_ENABLE_RRM]: + add_idf_sdkconfig_option("CONFIG_WPA_11KV_SUPPORT", True) + cg.add_define("USE_WIFI_11KV_SUPPORT") + if config[CONF_ENABLE_BTM]: + cg.add(var.set_btm(config[CONF_ENABLE_BTM])) + if config[CONF_ENABLE_RRM]: + cg.add(var.set_rrm(config[CONF_ENABLE_RRM])) + cg.add_define("USE_WIFI") # Register at end for OTA safe mode diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index 0ac227cd82..37f5276c8f 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -73,8 +73,11 @@ void WiFiComponent::setup() { ESP_LOGV(TAG, "Setting Output Power Option failed!"); } #ifdef USE_CAPTIVE_PORTAL - if (captive_portal::global_captive_portal != nullptr) + if (captive_portal::global_captive_portal != nullptr) { + this->wifi_sta_pre_setup_(); + this->start_scanning(); captive_portal::global_captive_portal->start(); + } #endif } #ifdef USE_IMPROV @@ -166,6 +169,10 @@ WiFiComponent::WiFiComponent() { global_wifi_component = this; } bool WiFiComponent::has_ap() const { return this->has_ap_; } bool WiFiComponent::has_sta() const { return !this->sta_.empty(); } void WiFiComponent::set_fast_connect(bool fast_connect) { this->fast_connect_ = fast_connect; } +#ifdef USE_WIFI_11KV_SUPPORT +void WiFiComponent::set_btm(bool btm) { this->btm_ = btm; } +void WiFiComponent::set_rrm(bool rrm) { this->rrm_ = rrm; } +#endif network::IPAddress WiFiComponent::get_ip_address() { if (this->has_sta()) return this->wifi_sta_ip(); @@ -366,6 +373,10 @@ void WiFiComponent::print_connect_params_() { ESP_LOGCONFIG(TAG, " Gateway: %s", wifi_gateway_ip_().str().c_str()); ESP_LOGCONFIG(TAG, " DNS1: %s", wifi_dns_ip_(0).str().c_str()); ESP_LOGCONFIG(TAG, " DNS2: %s", wifi_dns_ip_(1).str().c_str()); +#ifdef USE_WIFI_11KV_SUPPORT + ESP_LOGCONFIG(TAG, " BTM: %s", this->btm_ ? "enabled" : "disabled"); + ESP_LOGCONFIG(TAG, " RRM: %s", this->rrm_ ? "enabled" : "disabled"); +#endif } void WiFiComponent::start_scanning() { diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index 7941e7f643..6c5202ed7a 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -219,6 +219,11 @@ class WiFiComponent : public Component { bool has_sta() const; bool has_ap() const; +#ifdef USE_WIFI_11KV_SUPPORT + void set_btm(bool btm); + void set_rrm(bool rrm); +#endif + network::IPAddress get_ip_address(); std::string get_use_address() const; void set_use_address(const std::string &use_address); @@ -327,6 +332,10 @@ class WiFiComponent : public Component { optional output_power_; ESPPreferenceObject pref_; bool has_saved_wifi_settings_{false}; +#ifdef USE_WIFI_11KV_SUPPORT + bool btm_{false}; + bool rrm_{false}; +#endif }; extern WiFiComponent *global_wifi_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index 6d20219c69..2883164495 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -285,6 +285,11 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { } #endif +#ifdef USE_WIFI_11KV_SUPPORT + conf.sta.btm_enabled = this->btm_; + conf.sta.rm_enabled = this->rrm_; +#endif + if (ap.get_bssid().has_value()) { conf.sta.bssid_set = true; memcpy(conf.sta.bssid, ap.get_bssid()->data(), 6); diff --git a/esphome/config.py b/esphome/config.py index 545b805367..04717be6f5 100644 --- a/esphome/config.py +++ b/esphome/config.py @@ -165,15 +165,19 @@ class Config(OrderedDict, fv.FinalValidateConfig): return err return None - def get_deepest_document_range_for_path(self, path): - # type: (ConfigPath) -> Optional[ESPHomeDataBase] + def get_deepest_document_range_for_path(self, path, get_key=False): + # type: (ConfigPath, bool) -> Optional[ESPHomeDataBase] data = self doc_range = None - for item_index in path: + for index, path_item in enumerate(path): try: - if item_index in data: - doc_range = [x for x in data.keys() if x == item_index][0].esp_range - data = data[item_index] + if path_item in data: + key_data = [x for x in data.keys() if x == path_item][0] + if isinstance(key_data, ESPHomeDataBase): + doc_range = key_data.esp_range + if get_key and index == len(path) - 1: + return doc_range + data = data[path_item] except (KeyError, IndexError, TypeError, AttributeError): return doc_range if isinstance(data, core.ID): @@ -244,6 +248,8 @@ def iter_ids(config, path=None): yield from iter_ids(item, path + [i]) elif isinstance(config, dict): for key, value in config.items(): + if isinstance(key, core.ID): + yield key, path yield from iter_ids(value, path + [key]) @@ -279,7 +285,7 @@ class ConfigValidationStep(abc.ABC): class LoadValidationStep(ConfigValidationStep): """Load step, this step is called once for each domain config fragment. - Responsibilties: + Responsibilities: - Load component code - Ensure all AUTO_LOADs are added - Set output paths of result @@ -736,6 +742,10 @@ def validate_config(config, command_line_substitutions) -> Config: result.add_validation_step(LoadValidationStep(key, config[key])) result.run_validation_steps() + if result.errors: + # do not try to validate further as we don't know what the target is + return result + for domain, conf in config.items(): result.add_validation_step(LoadValidationStep(domain, conf)) result.add_validation_step(IDPassValidationStep()) diff --git a/esphome/const.py b/esphome/const.py index d65aea7159..290e5e2994 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.8.3" +__version__ = "2022.9.0b5" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" @@ -173,6 +173,7 @@ CONF_DIR_PIN = "dir_pin" CONF_DIRECTION = "direction" CONF_DIRECTION_OUTPUT = "direction_output" CONF_DISABLED_BY_DEFAULT = "disabled_by_default" +CONF_DISCONNECT_DELAY = "disconnect_delay" CONF_DISCOVERY = "discovery" CONF_DISCOVERY_OBJECT_ID_GENERATOR = "discovery_object_id_generator" CONF_DISCOVERY_PREFIX = "discovery_prefix" @@ -191,13 +192,16 @@ CONF_DUMMY_RECEIVER_ID = "dummy_receiver_id" CONF_DUMP = "dump" CONF_DURATION = "duration" CONF_EAP = "eap" +CONF_EC = "ec" CONF_ECHO_PIN = "echo_pin" CONF_ECO2 = "eco2" CONF_EFFECT = "effect" CONF_EFFECTS = "effects" CONF_ELSE = "else" +CONF_ENABLE_BTM = "enable_btm" CONF_ENABLE_IPV6 = "enable_ipv6" CONF_ENABLE_PIN = "enable_pin" +CONF_ENABLE_RRM = "enable_rrm" CONF_ENABLE_TIME = "enable_time" CONF_ENERGY = "energy" CONF_ENTITY_CATEGORY = "entity_category" @@ -330,6 +334,7 @@ CONF_LAMBDA = "lambda" CONF_LAST_CONFIDENCE = "last_confidence" CONF_LAST_FINGER_ID = "last_finger_id" CONF_LATITUDE = "latitude" +CONF_LED = "led" CONF_LEGEND = "legend" CONF_LENGTH = "length" CONF_LEVEL = "level" @@ -488,6 +493,7 @@ CONF_PAYLOAD = "payload" CONF_PAYLOAD_AVAILABLE = "payload_available" CONF_PAYLOAD_NOT_AVAILABLE = "payload_not_available" CONF_PERIOD = "period" +CONF_PH = "ph" CONF_PHASE_ANGLE = "phase_angle" CONF_PHASE_BALANCER = "phase_balancer" CONF_PIN = "pin" @@ -555,6 +561,7 @@ CONF_RAW_DATA_ID = "raw_data_id" CONF_RC_CODE_1 = "rc_code_1" CONF_RC_CODE_2 = "rc_code_2" CONF_REACTIVE_POWER = "reactive_power" +CONF_READ_PIN = "read_pin" CONF_REBOOT_TIMEOUT = "reboot_timeout" CONF_RECEIVE_TIMEOUT = "receive_timeout" CONF_RED = "red" @@ -562,6 +569,7 @@ CONF_REF = "ref" CONF_REFERENCE_RESISTANCE = "reference_resistance" CONF_REFERENCE_TEMPERATURE = "reference_temperature" CONF_REFRESH = "refresh" +CONF_RELABEL = "relabel" CONF_REPEAT = "repeat" CONF_REPOSITORY = "repository" CONF_RESET_DURATION = "reset_duration" @@ -647,6 +655,7 @@ CONF_STATE_CLASS = "state_class" CONF_STATE_TOPIC = "state_topic" CONF_STATIC_IP = "static_ip" CONF_STATUS = "status" +CONF_STB_PIN = "stb_pin" CONF_STEP = "step" CONF_STEP_MODE = "step_mode" CONF_STEP_PIN = "step_pin" @@ -655,6 +664,7 @@ CONF_STOP_ACTION = "stop_action" CONF_STORE_BASELINE = "store_baseline" CONF_SUBNET = "subnet" CONF_SUBSTITUTIONS = "substitutions" +CONF_SUM = "sum" CONF_SUPPLEMENTAL_COOLING_ACTION = "supplemental_cooling_action" CONF_SUPPLEMENTAL_COOLING_DELTA = "supplemental_cooling_delta" CONF_SUPPLEMENTAL_HEATING_ACTION = "supplemental_heating_action" @@ -736,6 +746,7 @@ CONF_USE_ABBREVIATIONS = "use_abbreviations" CONF_USE_ADDRESS = "use_address" CONF_USERNAME = "username" CONF_UUID = "uuid" +CONF_VALIDITY_PERIOD = "validity_period" CONF_VALUE = "value" CONF_VALUE_FONT = "value_font" CONF_VARIABLES = "variables" @@ -763,6 +774,7 @@ CONF_WILL_MESSAGE = "will_message" CONF_WIND_DIRECTION_DEGREES = "wind_direction_degrees" CONF_WIND_SPEED = "wind_speed" CONF_WINDOW_SIZE = "window_size" +CONF_WRITE_PIN = "write_pin" CONF_X_GRID = "x_grid" CONF_Y_GRID = "y_grid" CONF_ZERO = "zero" @@ -863,12 +875,14 @@ UNIT_MICROSIEMENS_PER_CENTIMETER = "µS/cm" UNIT_MICROTESLA = "µT" UNIT_MILLIGRAMS_PER_CUBIC_METER = "mg/m³" UNIT_MILLISECOND = "ms" +UNIT_MILLISIEMENS_PER_CENTIMETER = "mS/cm" UNIT_MINUTE = "min" UNIT_OHM = "Ω" UNIT_PARTS_PER_BILLION = "ppb" UNIT_PARTS_PER_MILLION = "ppm" UNIT_PASCAL = "Pa" UNIT_PERCENT = "%" +UNIT_PH = "pH" UNIT_PULSES = "pulses" UNIT_PULSES_PER_MINUTE = "pulses/min" UNIT_SECOND = "s" diff --git a/esphome/core/automation.h b/esphome/core/automation.h index 92bc32247b..84c754e081 100644 --- a/esphome/core/automation.h +++ b/esphome/core/automation.h @@ -176,7 +176,7 @@ template class Action { return this->next_->is_running(); } - Action *next_ = nullptr; + Action *next_{nullptr}; /// The number of instances of this sequence in the list of actions /// that is currently being executed. diff --git a/esphome/core/color.h b/esphome/core/color.h index 7596eeb0cf..7062a2a8c8 100644 --- a/esphome/core/color.h +++ b/esphome/core/color.h @@ -44,6 +44,20 @@ struct Color { w((colorcode >> 24) & 0xFF) {} inline bool is_on() ALWAYS_INLINE { return this->raw_32 != 0; } + + inline bool operator==(const Color &rhs) { // NOLINT + return this->raw_32 == rhs.raw_32; + } + inline bool operator==(uint32_t colorcode) { // NOLINT + return this->raw_32 == colorcode; + } + inline bool operator!=(const Color &rhs) { // NOLINT + return this->raw_32 != rhs.raw_32; + } + inline bool operator!=(uint32_t colorcode) { // NOLINT + return this->raw_32 != colorcode; + } + inline Color &operator=(const Color &rhs) ALWAYS_INLINE { // NOLINT this->r = rhs.r; this->g = rhs.g; diff --git a/esphome/core/component.h b/esphome/core/component.h index e394736653..cb97a93d21 100644 --- a/esphome/core/component.h +++ b/esphome/core/component.h @@ -254,7 +254,7 @@ class Component { uint32_t component_state_{0x0000}; ///< State of this component. float setup_priority_override_{NAN}; - const char *component_source_ = nullptr; + const char *component_source_{nullptr}; }; /** This class simplifies creating components that periodically check a state. diff --git a/esphome/core/config.py b/esphome/core/config.py index f1337be04b..82cf37d44d 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -179,7 +179,11 @@ def preload_core_config(config, result): ] if not has_oldstyle and not newstyle_found: - raise cv.Invalid("Platform missing for core options!", [CONF_ESPHOME]) + raise cv.Invalid( + "Platform missing. You must include one of the available platform keys: " + + ", ".join(TARGET_PLATFORMS), + [CONF_ESPHOME], + ) if has_oldstyle and newstyle_found: raise cv.Invalid( f"Please remove the `platform` key from the [esphome] block. You're already using the new style with the [{conf[CONF_PLATFORM]}] block", diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 51da2ceb66..cea53b8545 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -32,6 +32,7 @@ #define USE_MEDIA_PLAYER #define USE_MQTT #define USE_NUMBER +#define USE_OTA #define USE_OTA_PASSWORD #define USE_OTA_STATE_CALLBACK #define USE_POWER_SUPPLY @@ -70,6 +71,7 @@ #define USE_ESP32_IGNORE_EFUSE_MAC_CRC #define USE_IMPROV #define USE_SOCKET_IMPL_BSD_SOCKETS +#define USE_WIFI_11KV_SUPPORT #define USE_BLUETOOTH_PROXY #ifdef USE_ARDUINO diff --git a/esphome/core/gpio.h b/esphome/core/gpio.h index b953a95664..1b6f2ba1e6 100644 --- a/esphome/core/gpio.h +++ b/esphome/core/gpio.h @@ -73,7 +73,7 @@ class ISRInternalGPIOPin { void pin_mode(gpio::Flags flags); protected: - void *arg_ = nullptr; + void *arg_{nullptr}; }; class InternalGPIOPin : public GPIOPin { diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index d82e452c3d..3aca944a36 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -62,6 +62,21 @@ uint8_t crc8(uint8_t *data, uint8_t len) { } return crc; } +uint16_t crc16(const uint8_t *data, uint8_t len) { + uint16_t crc = 0xFFFF; + while (len--) { + crc ^= *data++; + for (uint8_t i = 0; i < 8; i++) { + if ((crc & 0x01) != 0) { + crc >>= 1; + crc ^= 0xA001; + } else { + crc >>= 1; + } + } + } + return crc; +} uint32_t fnv1_hash(const std::string &str) { uint32_t hash = 2166136261UL; for (char c : str) { diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 6bed743010..1459f0ef55 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -149,6 +149,9 @@ template T remap(U value, U min, U max, T min_out, T max /// Calculate a CRC-8 checksum of \p data with size \p len. uint8_t crc8(uint8_t *data, uint8_t len); +/// Calculate a CRC-16 checksum of \p data with size \p len. +uint16_t crc16(const uint8_t *data, uint8_t len); + /// Calculate a FNV-1 hash of \p str. uint32_t fnv1_hash(const std::string &str); diff --git a/esphome/core/preferences.h b/esphome/core/preferences.h index 2b13061a59..6d2dd967e9 100644 --- a/esphome/core/preferences.h +++ b/esphome/core/preferences.h @@ -46,6 +46,14 @@ class ESPPreferences { */ virtual bool sync() = 0; + /** + * Forget all unsaved changes and re-initialize the permanent preferences storage. + * Usually followed by a restart which moves the system to "factory" conditions + * + * @return true if operation is successful. + */ + virtual bool reset() = 0; + template::value, bool> = true> ESPPreferenceObject make_preference(uint32_t type, bool in_flash) { return this->make_preference(sizeof(T), type, in_flash); diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index 21730033a5..9a8f072237 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -644,6 +644,33 @@ def _ping_func(filename, address): return filename, rc == 0 +class PrometheusServiceDiscoveryHandler(BaseHandler): + @authenticated + def get(self): + entries = _list_dashboard_entries() + self.set_header("content-type", "application/json") + sd = [] + for entry in entries: + if entry.web_port is None: + continue + labels = { + "__meta_name": entry.name, + "__meta_esp_platform": entry.target_platform, + "__meta_esphome_version": entry.storage.esphome_version, + } + for integration in entry.storage.loaded_integrations: + labels[f"__meta_integration_{integration}"] = "true" + sd.append( + { + "targets": [ + f"{entry.address}:{entry.web_port}", + ], + "labels": labels, + } + ) + self.write(json.dumps(sd)) + + class MDNSStatusThread(threading.Thread): def run(self): global IMPORT_RESULT @@ -950,9 +977,12 @@ def make_app(debug=get_bool_env(ENV_DEV)): class StaticFileHandler(tornado.web.StaticFileHandler): def set_extra_headers(self, path): - self.set_header( - "Cache-Control", "no-store, no-cache, must-revalidate, max-age=0" - ) + if "favicon.ico" in path: + self.set_header("Cache-Control", "max-age=84600, public") + else: + self.set_header( + "Cache-Control", "no-store, no-cache, must-revalidate, max-age=0" + ) app_settings = { "debug": debug, @@ -990,6 +1020,7 @@ def make_app(debug=get_bool_env(ENV_DEV)): (f"{rel}import", ImportRequestHandler), (f"{rel}secret_keys", SecretKeysRequestHandler), (f"{rel}rename", EsphomeRenameHandler), + (f"{rel}prometheus-sd", PrometheusServiceDiscoveryHandler), ], **app_settings, ) diff --git a/esphome/espota2.py b/esphome/espota2.py index 76f3b917c9..98d6d3a0d9 100644 --- a/esphome/espota2.py +++ b/esphome/espota2.py @@ -30,6 +30,8 @@ RESPONSE_ERROR_WRONG_CURRENT_FLASH_CONFIG = 134 RESPONSE_ERROR_WRONG_NEW_FLASH_CONFIG = 135 RESPONSE_ERROR_ESP8266_NOT_ENOUGH_SPACE = 136 RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE = 137 +RESPONSE_ERROR_NO_UPDATE_PARTITION = 138 +RESPONSE_ERROR_MD5_MISMATCH = 139 RESPONSE_ERROR_UNKNOWN = 255 OTA_VERSION_1_0 = 1 @@ -150,6 +152,16 @@ def check_error(data, expect): "Error: The OTA partition on the ESP is too small. ESPHome needs to resize " "this partition, please flash over USB." ) + if dat == RESPONSE_ERROR_NO_UPDATE_PARTITION: + raise OTAError( + "Error: The OTA partition on the ESP couldn't be found. ESPHome needs to create " + "this partition, please flash over USB." + ) + if dat == RESPONSE_ERROR_MD5_MISMATCH: + raise OTAError( + "Error: Application MD5 code mismatch. Please try again " + "or flash over USB with a good quality cable." + ) if dat == RESPONSE_ERROR_UNKNOWN: raise OTAError("Unknown error from ESP") if not isinstance(expect, (list, tuple)): diff --git a/esphome/vscode.py b/esphome/vscode.py index 68d59abd02..6a43a654ed 100644 --- a/esphome/vscode.py +++ b/esphome/vscode.py @@ -12,7 +12,9 @@ from typing import Optional def _get_invalid_range(res, invalid): # type: (Config, cv.Invalid) -> Optional[DocumentRange] - return res.get_deepest_document_range_for_path(invalid.path) + return res.get_deepest_document_range_for_path( + invalid.path, invalid.error_message == "extra keys not allowed" + ) def _dump_range(range): diff --git a/requirements.txt b/requirements.txt index f31b1662bd..55544e442e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,9 +9,9 @@ pyserial==3.5 platformio==6.0.2 # When updating platformio, also update Dockerfile esptool==3.3.1 click==8.1.3 -esphome-dashboard==20220508.0 -aioesphomeapi==10.11.0 -zeroconf==0.39.0 +esphome-dashboard==20220920.1 +aioesphomeapi==10.13.0 +zeroconf==0.39.1 # esp-idf requires this, but doesn't bundle it by default # https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24 diff --git a/requirements_test.txt b/requirements_test.txt index ed48818276..5ef791906d 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,11 +1,11 @@ -pylint==2.14.5 +pylint==2.15.2 flake8==5.0.4 -black==22.6.0 # also change in .pre-commit-config.yaml when updating +black==22.8.0 # also change in .pre-commit-config.yaml when updating pyupgrade==2.37.3 # also change in .pre-commit-config.yaml when updating pre-commit # Unit tests -pytest==7.1.1 +pytest==7.1.3 pytest-cov==3.0.0 pytest-mock==3.8.2 pytest-asyncio==0.19.0 diff --git a/tests/component_tests/binary_sensor/test_binary_sensor.yaml b/tests/component_tests/binary_sensor/test_binary_sensor.yaml index 912ae115eb..f98ce693f7 100644 --- a/tests/component_tests/binary_sensor/test_binary_sensor.yaml +++ b/tests/component_tests/binary_sensor/test_binary_sensor.yaml @@ -1,3 +1,4 @@ +--- esphome: name: test platform: ESP8266 @@ -6,13 +7,13 @@ esphome: binary_sensor: - platform: gpio id: bs_1 - name: "test bs1" + name: test bs1 internal: true pin: number: D0 - platform: gpio id: bs_2 - name: "test bs2" + name: test bs2 internal: false pin: number: D1 diff --git a/tests/component_tests/button/test_button.yaml b/tests/component_tests/button/test_button.yaml index 32d2e8d93b..48e13f0353 100644 --- a/tests/component_tests/button/test_button.yaml +++ b/tests/component_tests/button/test_button.yaml @@ -1,3 +1,4 @@ +--- esphome: name: test platform: ESP8266 diff --git a/tests/component_tests/deep_sleep/test_deep_sleep1.yaml b/tests/component_tests/deep_sleep/test_deep_sleep1.yaml index 18a425df58..96514a677f 100644 --- a/tests/component_tests/deep_sleep/test_deep_sleep1.yaml +++ b/tests/component_tests/deep_sleep/test_deep_sleep1.yaml @@ -1,3 +1,4 @@ +--- esphome: name: test platform: ESP32 diff --git a/tests/component_tests/deep_sleep/test_deep_sleep2.yaml b/tests/component_tests/deep_sleep/test_deep_sleep2.yaml index 49a7f510f2..0e8e598402 100644 --- a/tests/component_tests/deep_sleep/test_deep_sleep2.yaml +++ b/tests/component_tests/deep_sleep/test_deep_sleep2.yaml @@ -1,3 +1,4 @@ +--- esphome: name: test platform: ESP32 diff --git a/tests/component_tests/sensor/test_sensor.yaml b/tests/component_tests/sensor/test_sensor.yaml index a38dd14041..8c0fd85b17 100644 --- a/tests/component_tests/sensor/test_sensor.yaml +++ b/tests/component_tests/sensor/test_sensor.yaml @@ -1,3 +1,4 @@ +--- esphome: name: test platform: ESP8266 @@ -7,6 +8,6 @@ sensor: - platform: adc pin: A0 id: s_1 - name: "test s1" + name: test s1 update_interval: 60s - device_class: "voltage" + device_class: voltage diff --git a/tests/test1.yaml b/tests/test1.yaml index 15ede4d05e..e213a8b041 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1,3 +1,4 @@ +--- substitutions: devicename: test1 sensorname: my @@ -43,13 +44,13 @@ esphome: json: key: !lambda |- return id(${textname}_text).state; - greeting: "Hello World" + greeting: Hello World - http_request.send: method: PUT url: https://esphome.io headers: Content-Type: application/json - body: "Some data" + body: Some data verify_ssl: false on_response: then: @@ -95,8 +96,8 @@ mqtt: password: "debug" client_id: someclient use_abbreviations: false - discovery: True - discovery_retain: False + discovery: true + discovery_retain: false discovery_prefix: discovery discovery_unique_id_generator: legacy topic_prefix: helloworld @@ -109,7 +110,7 @@ mqtt: topic: topic/to/send/to payload: hi qos: 2 - retain: True + retain: true keepalive: 60s reboot_timeout: 60s on_message: @@ -154,6 +155,7 @@ mqtt: return effect; - light.control: id: ${roomname}_lights + # yamllint disable-line rule:line-length brightness: !lambda "return id(${roomname}_lights).current_values.get_brightness() + 0.5;" - light.dim_relative: id: ${roomname}_lights @@ -179,7 +181,7 @@ mqtt: i2c: sda: 21 scl: 22 - scan: True + scan: true frequency: 100kHz setup_priority: -100 id: i2c_bus @@ -192,10 +194,10 @@ spi: uart: - tx_pin: number: GPIO22 - inverted: yes + inverted: true rx_pin: number: GPIO23 - inverted: yes + inverted: true baud_rate: 115200 id: uart0 parity: NONE @@ -222,7 +224,7 @@ uart: rx_buffer_size: 1024 ota: - safe_mode: True + safe_mode: true password: "superlongpasswordthatnoonewillknow" port: 3286 reboot_timeout: 2min @@ -233,14 +235,14 @@ ota: ESP_LOGD("ota", "State %d", state); on_begin: then: - logger.log: "OTA begin" + logger.log: OTA begin on_progress: then: lambda: >- ESP_LOGD("ota", "Got progress %f", x); on_end: then: - logger.log: "OTA end" + logger.log: OTA end on_error: then: lambda: >- @@ -258,7 +260,7 @@ web_server: version: 2 power_supply: - id: "atx_power_supply" + id: atx_power_supply enable_time: 20ms keep_on_time: 10s pin: @@ -308,22 +310,22 @@ bedjet: id: my_bedjet_client time_id: sntp_time mcp23s08: - - id: "mcp23s08_hub" + - id: mcp23s08_hub cs_pin: GPIO12 deviceaddress: 0 mcp23s17: - - id: "mcp23s17_hub" + - id: mcp23s17_hub cs_pin: GPIO12 deviceaddress: 1 sensor: - platform: ble_client ble_client_id: ble_foo - name: "Green iTag btn" - service_uuid: "ffe0" - characteristic_uuid: "ffe1" - descriptor_uuid: "ffe2" + name: Green iTag btn + service_uuid: ffe0 + characteristic_uuid: ffe1 + descriptor_uuid: ffe2 notify: true update_interval: never lambda: |- @@ -335,7 +337,7 @@ sensor: ESP_LOGD("green_btn", "Button was pressed, val%f", x); - platform: adc pin: A0 - name: "Living Room Brightness" + name: Living Room Brightness update_interval: "1:01" attenuation: 2.5db unit_of_measurement: "°C" @@ -384,11 +386,13 @@ sensor: - lambda: return x * (9.0/5.0) + 32.0; on_value: then: + # yamllint disable rule:line-length - lambda: |- ESP_LOGD("main", "Got value %f", x); id(${sensorname}_sensor).publish_state(42.0); ESP_LOGI("main", "Value of my sensor: %f", id(${sensorname}_sensor).state); ESP_LOGI("main", "Raw Value of my sensor: %f", id(${sensorname}_sensor).state); + # yamllint enable rule:line-length on_value_range: above: 5 below: 10 @@ -405,18 +409,18 @@ sensor: ESP_LOGD("main", "Got raw value %f", x); - logger.log: level: DEBUG - format: "Got raw value %f" + format: Got raw value %f args: ["x"] - - logger.log: "Got raw value NAN" + - logger.log: Got raw value NAN - mqtt.publish: topic: some/topic payload: Hello qos: 2 - retain: True + retain: true - platform: esp32_hall name: ESP32 Hall Sensor - platform: ads1115 - multiplexer: "A0_A1" + multiplexer: A0_A1 gain: 1.024 id: ${sensorname}_sensor filters: @@ -427,57 +431,57 @@ sensor: cs_pin: 5 phase_a: voltage: - name: "EMON Line Voltage A" + name: EMON Line Voltage A current: - name: "EMON CT1 Current" + name: EMON CT1 Current power: - name: "EMON Active Power CT1" + name: EMON Active Power CT1 reactive_power: - name: "EMON Reactive Power CT1" + name: EMON Reactive Power CT1 power_factor: - name: "EMON Power Factor CT1" + name: EMON Power Factor CT1 gain_voltage: 7305 gain_ct: 27961 phase_b: current: - name: "EMON CT2 Current" + name: EMON CT2 Current power: - name: "EMON Active Power CT2" + name: EMON Active Power CT2 reactive_power: - name: "EMON Reactive Power CT2" + name: EMON Reactive Power CT2 power_factor: - name: "EMON Power Factor CT2" + name: EMON Power Factor CT2 gain_voltage: 7305 gain_ct: 27961 phase_c: current: - name: "EMON CT3 Current" + name: EMON CT3 Current power: - name: "EMON Active Power CT3" + name: EMON Active Power CT3 reactive_power: - name: "EMON Reactive Power CT3" + name: EMON Reactive Power CT3 power_factor: - name: "EMON Power Factor CT3" + name: EMON Power Factor CT3 gain_voltage: 7305 gain_ct: 27961 frequency: - name: "EMON Line Frequency" + name: EMON Line Frequency chip_temperature: - name: "EMON Chip Temp A" + name: EMON Chip Temp A line_frequency: 60Hz current_phases: 3 gain_pga: 2X - platform: bh1750 - name: "Living Room Brightness 3" + name: Living Room Brightness 3 internal: true address: 0x23 update_interval: 30s - retain: False + retain: false availability: state_topic: livingroom/custom_state_topic i2c_id: i2c_bus - platform: max44009 - name: "Outside Brightness 1" + name: Outside Brightness 1 internal: true address: 0x4A update_interval: 30s @@ -485,13 +489,13 @@ sensor: i2c_id: i2c_bus - platform: bme280 temperature: - name: "Outside Temperature" + name: Outside Temperature oversampling: 16x pressure: - name: "Outside Pressure" + name: Outside Pressure oversampling: none humidity: - name: "Outside Humidity" + name: Outside Humidity oversampling: 8x address: 0x77 iir_filter: 16x @@ -499,14 +503,14 @@ sensor: i2c_id: i2c_bus - platform: bme680 temperature: - name: "Outside Temperature" + name: Outside Temperature oversampling: 16x pressure: - name: "Outside Pressure" + name: Outside Pressure humidity: - name: "Outside Humidity" + name: Outside Humidity gas_resistance: - name: "Outside Gas Sensor" + name: Outside Gas Sensor address: 0x77 heater: temperature: 320 @@ -515,9 +519,9 @@ sensor: i2c_id: i2c_bus - platform: bmp085 temperature: - name: "Outside Temperature" + name: Outside Temperature pressure: - name: "Outside Pressure" + name: Outside Pressure filters: - lambda: >- return x / powf(1.0 - (x / 44330.0), 5.255); @@ -525,54 +529,56 @@ sensor: i2c_id: i2c_bus - platform: bmp280 temperature: - name: "Outside Temperature" + name: Outside Temperature oversampling: 16x pressure: - name: "Outside Pressure" + name: Outside Pressure address: 0x77 update_interval: 15s iir_filter: 16x i2c_id: i2c_bus - platform: dallas address: 0x1C0000031EDD2A28 - name: "Living Room Temperature" + name: Living Room Temperature resolution: 9 - platform: dallas index: 1 - name: "Living Room Temperature 2" + name: Living Room Temperature 2 - platform: dht pin: GPIO26 temperature: - name: "Living Room Temperature 3" + id: dht_temperature + name: Living Room Temperature 3 humidity: - name: "Living Room Humidity 3" + id: dht_humidity + name: Living Room Humidity 3 model: AM2302 update_interval: 15s - platform: dht12 temperature: - name: "Living Room Temperature 4" + name: Living Room Temperature 4 humidity: - name: "Living Room Humidity 4" + name: Living Room Humidity 4 update_interval: 15s i2c_id: i2c_bus - platform: duty_cycle pin: GPIO25 name: Duty Cycle Sensor - platform: esp32_hall - name: "ESP32 Hall Sensor" + name: ESP32 Hall Sensor update_interval: 15s - platform: ens210 temperature: - name: "Living Room Temperature 5" + name: Living Room Temperature 5 humidity: - name: 'Living Room Humidity 5' + name: Living Room Humidity 5 update_interval: 15s i2c_id: i2c_bus - platform: hdc1080 temperature: - name: 'Living Room Temperature 6' + name: Living Room Temperature 6 humidity: - name: 'Living Room Humidity 5' + name: Living Room Humidity 5 update_interval: 15s i2c_id: i2c_bus - platform: hlw8012 @@ -580,14 +586,14 @@ sensor: cf_pin: 14 cf1_pin: 13 current: - name: "HLW8012 Current" + name: HLW8012 Current voltage: - name: "HLW8012 Voltage" + name: HLW8012 Voltage power: - name: "HLW8012 Power" + name: HLW8012 Power id: hlw8012_power energy: - name: "HLW8012 Energy" + name: HLW8012 Energy id: hlw8012_energy update_interval: 15s current_resistor: 0.001 ohm @@ -597,53 +603,53 @@ sensor: model: hlw8012 - platform: total_daily_energy power_id: hlw8012_power - name: "HLW8012 Total Daily Energy" + name: HLW8012 Total Daily Energy - platform: integration sensor: hlw8012_power - name: "Integration Sensor" + name: Integration Sensor time_unit: s - platform: integration sensor: hlw8012_power - name: "Integration Sensor lazy" + name: Integration Sensor lazy time_unit: s - platform: hmc5883l address: 0x68 field_strength_x: - name: "HMC5883L Field Strength X" + name: HMC5883L Field Strength X field_strength_y: - name: "HMC5883L Field Strength Y" + name: HMC5883L Field Strength Y field_strength_z: - name: "HMC5883L Field Strength Z" + name: HMC5883L Field Strength Z heading: - name: "HMC5883L Heading" + name: HMC5883L Heading range: 130uT oversampling: 8x update_interval: 15s i2c_id: i2c_bus - platform: honeywellabp pressure: - name: "Honeywell pressure" + name: Honeywell pressure min_pressure: 0 max_pressure: 15 temperature: - name: "Honeywell temperature" + name: Honeywell temperature cs_pin: GPIO5 - platform: qmc5883l address: 0x0D field_strength_x: - name: "QMC5883L Field Strength X" + name: QMC5883L Field Strength X field_strength_y: - name: "QMC5883L Field Strength Y" + name: QMC5883L Field Strength Y field_strength_z: - name: "QMC5883L Field Strength Z" + name: QMC5883L Field Strength Z heading: - name: "QMC5883L Heading" + name: QMC5883L Heading range: 800uT oversampling: 256x update_interval: 15s i2c_id: i2c_bus - platform: hx711 - name: "HX711 Value" + name: HX711 Value dout_pin: GPIO23 clk_pin: GPIO25 gain: 128 @@ -652,13 +658,13 @@ sensor: address: 0x40 shunt_resistance: 0.1 ohm current: - name: "INA219 Current" + name: INA219 Current power: - name: "INA219 Power" + name: INA219 Power bus_voltage: - name: "INA219 Bus Voltage" + name: INA219 Bus Voltage shunt_voltage: - name: "INA219 Shunt Voltage" + name: INA219 Shunt Voltage max_voltage: 32.0V max_current: 3.2A update_interval: 15s @@ -667,13 +673,13 @@ sensor: address: 0x40 shunt_resistance: 0.1 ohm current: - name: "INA226 Current" + name: INA226 Current power: - name: "INA226 Power" + name: INA226 Power bus_voltage: - name: "INA226 Bus Voltage" + name: INA226 Bus Voltage shunt_voltage: - name: "INA226 Shunt Voltage" + name: INA226 Shunt Voltage max_current: 3.2A update_interval: 15s i2c_id: i2c_bus @@ -682,17 +688,17 @@ sensor: channel_1: shunt_resistance: 0.1 ohm current: - name: "INA3221 Channel 1 Current" + name: INA3221 Channel 1 Current power: - name: "INA3221 Channel 1 Power" + name: INA3221 Channel 1 Power bus_voltage: - name: "INA3221 Channel 1 Bus Voltage" + name: INA3221 Channel 1 Bus Voltage shunt_voltage: - name: "INA3221 Channel 1 Shunt Voltage" + name: INA3221 Channel 1 Shunt Voltage update_interval: 15s i2c_id: i2c_bus - platform: kalman_combinator - name: "Kalman-filtered temperature" + name: Kalman-filtered temperature process_std_dev: 0.00139 sources: - source: scd30_temperature @@ -702,106 +708,114 @@ sensor: error: 1.5 - platform: htu21d temperature: - name: "Living Room Temperature 6" + name: Living Room Temperature 6 humidity: - name: "Living Room Humidity 6" + name: Living Room Humidity 6 update_interval: 15s i2c_id: i2c_bus - platform: max6675 - name: "Living Room Temperature" + name: Living Room Temperature cs_pin: GPIO23 update_interval: 15s - platform: max31855 - name: "Den Temperature" + name: Den Temperature cs_pin: GPIO23 update_interval: 15s reference_temperature: - name: "MAX31855 Internal Temperature" + name: MAX31855 Internal Temperature - platform: max31856 - name: "BBQ Temperature" + name: BBQ Temperature cs_pin: GPIO17 update_interval: 15s mains_filter: 50Hz - platform: max31865 - name: "Water Tank Temperature" + name: Water Tank Temperature cs_pin: GPIO23 update_interval: 15s - reference_resistance: "430 Ω" - rtd_nominal_resistance: "100 Ω" + reference_resistance: 430 Ω + rtd_nominal_resistance: 100 Ω - platform: mhz19 uart_id: uart0 co2: - name: "MH-Z19 CO2 Value" + name: MH-Z19 CO2 Value temperature: - name: "MH-Z19 Temperature" + name: MH-Z19 Temperature update_interval: 15s automatic_baseline_calibration: false - platform: mpu6050 address: 0x68 accel_x: - name: "MPU6050 Accel X" + name: MPU6050 Accel X accel_y: - name: "MPU6050 Accel Y" + name: MPU6050 Accel Y accel_z: - name: "MPU6050 Accel z" + name: MPU6050 Accel z gyro_x: - name: "MPU6050 Gyro X" + name: MPU6050 Gyro X gyro_y: - name: "MPU6050 Gyro Y" + name: MPU6050 Gyro Y gyro_z: - name: "MPU6050 Gyro z" + name: MPU6050 Gyro z temperature: - name: "MPU6050 Temperature" + name: MPU6050 Temperature i2c_id: i2c_bus - platform: mpu6886 address: 0x68 accel_x: - name: "MPU6886 Accel X" + name: MPU6886 Accel X accel_y: - name: "MPU6886 Accel Y" + name: MPU6886 Accel Y accel_z: - name: "MPU6886 Accel z" + name: MPU6886 Accel z gyro_x: - name: "MPU6886 Gyro X" + name: MPU6886 Gyro X gyro_y: - name: "MPU6886 Gyro Y" + name: MPU6886 Gyro Y gyro_z: - name: "MPU6886 Gyro z" + name: MPU6886 Gyro z temperature: - name: "MPU6886 Temperature" + name: MPU6886 Temperature + i2c_id: i2c_bus + - platform: dps310 + temperature: + name: DPS310 Temperature + pressure: + name: DPS310 Pressure + address: 0x77 + update_interval: 15s i2c_id: i2c_bus - platform: ms5611 temperature: - name: "Outside Temperature" + name: Outside Temperature pressure: - name: "Outside Pressure" + name: Outside Pressure address: 0x77 update_interval: 15s i2c_id: i2c_bus - platform: pmsa003i pm_1_0: - name: "PMSA003i PM1.0" + name: PMSA003i PM1.0 pm_2_5: - name: "PMSA003i PM2.5" + name: PMSA003i PM2.5 pm_10_0: - name: "PMSA003i PM10.0" + name: PMSA003i PM10.0 pmc_0_3: - name: "PMSA003i PMC <0.3µm" + name: PMSA003i PMC <0.3µm pmc_0_5: - name: "PMSA003i PMC <0.5µm" + name: PMSA003i PMC <0.5µm pmc_1_0: - name: "PMSA003i PMC <1µm" + name: PMSA003i PMC <1µm pmc_2_5: - name: "PMSA003i PMC <2.5µm" + name: PMSA003i PMC <2.5µm pmc_5_0: - name: "PMSA003i PMC <5µm" + name: PMSA003i PMC <5µm pmc_10_0: - name: "PMSA003i PMC <10µm" + name: PMSA003i PMC <10µm address: 0x12 - standard_units: True + standard_units: true i2c_id: i2c_bus - platform: pulse_counter - name: "Pulse Counter" + name: Pulse Counter pin: GPIO12 count_mode: rising_edge: INCREMENT @@ -809,7 +823,7 @@ sensor: internal_filter: 13us update_interval: 15s - platform: pulse_meter - name: "Pulse Meter" + name: Pulse Meter id: pulse_meter_sensor pin: GPIO12 internal_filter: 100ms @@ -819,20 +833,20 @@ sensor: id: pulse_meter_sensor value: 12345 total: - name: "Pulse Meter Total" + name: Pulse Meter Total - platform: qmp6988 temperature: - name: "Living Temperature QMP" + name: Living Temperature QMP oversampling: 32x pressure: - name: "Living Pressure QMP" + name: Living Pressure QMP oversampling: 2x address: 0x70 update_interval: 30s iir_filter: 16x i2c_id: i2c_bus - platform: rotary_encoder - name: "Rotary Encoder" + name: Rotary Encoder id: rotary_encoder1 pin_a: GPIO23 pin_b: GPIO25 @@ -852,49 +866,49 @@ sensor: id: rotary_encoder1 value: !lambda "return -1;" on_clockwise: - - logger.log: "Clockwise" + - logger.log: Clockwise on_anticlockwise: - - logger.log: "Anticlockwise" + - logger.log: Anticlockwise - platform: pulse_width name: Pulse Width pin: GPIO12 - platform: sm300d2 uart_id: uart0 co2: - name: "SM300D2 CO2 Value" + name: SM300D2 CO2 Value formaldehyde: - name: "SM300D2 Formaldehyde Value" + name: SM300D2 Formaldehyde Value tvoc: - name: "SM300D2 TVOC Value" + name: SM300D2 TVOC Value pm_2_5: - name: "SM300D2 PM2.5 Value" + name: SM300D2 PM2.5 Value pm_10_0: - name: "SM300D2 PM10 Value" + name: SM300D2 PM10 Value temperature: - name: "SM300D2 Temperature Value" + name: SM300D2 Temperature Value humidity: - name: "SM300D2 Humidity Value" + name: SM300D2 Humidity Value update_interval: 60s - platform: sht3xd temperature: - name: "Living Room Temperature 8" + name: Living Room Temperature 8 humidity: - name: "Living Room Humidity 8" + name: Living Room Humidity 8 address: 0x44 i2c_id: i2c_bus update_interval: 15s - platform: sts3x - name: "Living Room Temperature 9" + name: Living Room Temperature 9 address: 0x4A i2c_id: i2c_bus - platform: scd30 co2: - name: "Living Room CO2 9" + name: Living Room CO2 9 temperature: id: scd30_temperature - name: "Living Room Temperature 9" + name: Living Room Temperature 9 humidity: - name: "Living Room Humidity 9" + name: Living Room Humidity 9 address: 0x61 update_interval: 15s automatic_self_calibration: true @@ -905,12 +919,12 @@ sensor: - platform: scd4x id: scd40 co2: - name: "SCD4X CO2" + name: SCD4X CO2 temperature: id: scd4x_temperature - name: "SCD4X Temperature" + name: SCD4X Temperature humidity: - name: "SCD4X Humidity" + name: SCD4X Humidity update_interval: 15s automatic_self_calibration: true altitude_compensation: 10m @@ -919,63 +933,63 @@ sensor: i2c_id: i2c_bus - platform: sgp30 eco2: - name: "Workshop eCO2" + name: Workshop eCO2 accuracy_decimals: 1 tvoc: - name: "Workshop TVOC" + name: Workshop TVOC accuracy_decimals: 1 address: 0x58 update_interval: 5s i2c_id: i2c_bus - platform: sps30 pm_1_0: - name: "Workshop PM <1µm Weight concentration" - id: "workshop_PM_1_0" + name: Workshop PM <1µm Weight concentration + id: workshop_PM_1_0 pm_2_5: - name: "Workshop PM <2.5µm Weight concentration" - id: "workshop_PM_2_5" + name: Workshop PM <2.5µm Weight concentration + id: workshop_PM_2_5 pm_4_0: - name: "Workshop PM <4µm Weight concentration" - id: "workshop_PM_4_0" + name: Workshop PM <4µm Weight concentration + id: workshop_PM_4_0 pm_10_0: - name: "Workshop PM <10µm Weight concentration" - id: "workshop_PM_10_0" + name: Workshop PM <10µm Weight concentration + id: workshop_PM_10_0 pmc_0_5: - name: "Workshop PM <0.5µm Number concentration" - id: "workshop_PMC_0_5" + name: Workshop PM <0.5µm Number concentration + id: workshop_PMC_0_5 pmc_1_0: - name: "Workshop PM <1µm Number concentration" - id: "workshop_PMC_1_0" + name: Workshop PM <1µm Number concentration + id: workshop_PMC_1_0 pmc_2_5: - name: "Workshop PM <2.5µm Number concentration" - id: "workshop_PMC_2_5" + name: Workshop PM <2.5µm Number concentration + id: workshop_PMC_2_5 pmc_4_0: - name: "Workshop PM <4µm Number concentration" - id: "workshop_PMC_4_0" + name: Workshop PM <4µm Number concentration + id: workshop_PMC_4_0 pmc_10_0: - name: "Workshop PM <10µm Number concentration" - id: "workshop_PMC_10_0" + name: Workshop PM <10µm Number concentration + id: workshop_PMC_10_0 address: 0x69 update_interval: 10s i2c_id: i2c_bus - platform: sht4x temperature: - name: "SHT4X Temperature" + name: SHT4X Temperature humidity: - name: "SHT4X Humidity" + name: SHT4X Humidity address: 0x44 update_interval: 15s i2c_id: i2c_bus - platform: shtcx temperature: - name: "Living Room Temperature 10" + name: Living Room Temperature 10 humidity: - name: "Living Room Humidity 10" + name: Living Room Humidity 10 address: 0x70 update_interval: 15s i2c_id: i2c_bus - platform: template - name: "Template Sensor" + name: Template Sensor state_class: measurement id: template_sensor lambda: |- @@ -993,7 +1007,7 @@ sensor: id: template_sensor state: !lambda "return NAN;" - platform: tsl2561 - name: "TSL2561 Ambient Light" + name: TSL2561 Ambient Light address: 0x39 update_interval: 15s is_cs_package: true @@ -1007,17 +1021,17 @@ sensor: integration_time: 600ms gain: high visible: - name: "tsl2591 visible" + name: tsl2591 visible id: tsl2591_vis - unit_of_measurement: "pH" + unit_of_measurement: pH infrared: - name: "tsl2591 infrared" + name: tsl2591 infrared id: tsl2591_ir full_spectrum: - name: "tsl2591 full_spectrum" + name: tsl2591 full_spectrum id: tsl2591_fs calculated_lux: - name: "tsl2591 calculated_lux" + name: tsl2591 calculated_lux id: tsl2591_cl i2c_id: i2c_bus - platform: ultrasonic @@ -1025,17 +1039,17 @@ sensor: echo_pin: number: GPIO23 inverted: true - name: "Ultrasonic Sensor" + name: Ultrasonic Sensor timeout: 5.5m id: ultrasonic_sensor1 - platform: uptime name: Uptime Sensor - platform: wifi_signal - name: "WiFi Signal Sensor" + name: WiFi Signal Sensor update_interval: 15s - platform: mqtt_subscribe - name: "MQTT Subscribe Sensor 1" - topic: "mqtt/topic" + name: MQTT Subscribe Sensor 1 + topic: mqtt/topic id: the_sensor qos: 2 on_value: @@ -1047,9 +1061,9 @@ sensor: - platform: sds011 uart_id: uart0 pm_2_5: - name: "SDS011 PM2.5" + name: SDS011 PM2.5 pm_10_0: - name: "SDS011 PM10.0" + name: SDS011 PM10.0 update_interval: 5min rx_only: false - platform: ccs811 @@ -1062,9 +1076,9 @@ sensor: i2c_id: i2c_bus - platform: tx20 wind_speed: - name: "Windspeed" + name: Windspeed wind_direction_degrees: - name: "Winddirection Degrees" + name: Winddirection Degrees pin: number: GPIO04 mode: INPUT @@ -1072,48 +1086,48 @@ sensor: clock_pin: GPIO5 data_pin: GPIO4 co2: - name: "ZyAura CO2" + name: ZyAura CO2 temperature: - name: "ZyAura Temperature" + name: ZyAura Temperature humidity: - name: "ZyAura Humidity" + name: ZyAura Humidity - platform: as3935 lightning_energy: - name: "Lightning Energy" + name: Lightning Energy distance: - name: "Distance Storm" + name: Distance Storm - platform: tmp117 - name: "TMP117 Temperature" + name: TMP117 Temperature update_interval: 5s i2c_id: i2c_bus - platform: hm3301 pm_1_0: - name: "PM1.0" + name: PM1.0 pm_2_5: - name: "PM2.5" + name: PM2.5 pm_10_0: - name: "PM10.0" + name: PM10.0 aqi: - name: "AQI" - calculation_type: "CAQI" + name: AQI + calculation_type: CAQI i2c_id: i2c_bus - platform: teleinfo - tag_name: "HCHC" - name: "hchc" - unit_of_measurement: "Wh" + tag_name: HCHC + name: hchc + unit_of_measurement: Wh icon: mdi:flash teleinfo_id: myteleinfo - platform: mcp9808 - name: "MCP9808 Temperature" + name: MCP9808 Temperature update_interval: 15s i2c_id: i2c_bus - platform: ezo id: ph_ezo address: 99 - unit_of_measurement: "pH" + unit_of_measurement: pH i2c_id: i2c_bus - platform: sdp3x - name: "HVAC Filter Pressure drop" + name: HVAC Filter Pressure drop id: filter_pressure update_interval: 5s accuracy_decimals: 3 @@ -1121,11 +1135,11 @@ sensor: - platform: cs5460a id: cs5460a1 current: - name: "Socket current" + name: Socket current voltage: - name: "Mains voltage" + name: Mains voltage power: - name: "Socket power" + name: Socket power on_value: then: cs5460a.restart: cs5460a1 @@ -1133,8 +1147,8 @@ sensor: pga_gain: 10X current_gain: 0.01 voltage_gain: 0.000573 - current_hpf: on - voltage_hpf: on + current_hpf: true + voltage_hpf: true phase_offset: 20 pulse_energy: 0.01 kWh cs_pin: @@ -1143,7 +1157,7 @@ sensor: - platform: max9611 i2c_id: i2c_bus shunt_resistance: 0.2 ohm - gain: "1X" + gain: 1X voltage: name: Max9611 Voltage current: @@ -1153,9 +1167,16 @@ sensor: temperature: name: Max9611 Temp update_interval: 1s + - platform: mpl3115a2 + i2c_id: i2c_bus + temperature: + name: "MPL3115A2 Temperature" + pressure: + name: "MPL3115A2 Pressure" + update_interval: 10s esp32_touch: - setup_mode: False + setup_mode: false iir_filter: 10ms sleep_duration: 27ms measurement_duration: 8ms @@ -1172,7 +1193,7 @@ binary_sensor: number: 1 # One of INPUT or INPUT_PULLUP mode: INPUT_PULLUP - inverted: False + inverted: false - platform: gpio name: "MCP23S17 Pin #1" pin: @@ -1181,7 +1202,7 @@ binary_sensor: number: 1 # One of INPUT or INPUT_PULLUP mode: INPUT_PULLUP - inverted: False + inverted: false - platform: gpio name: "MCP23S17 Pin #1 with interrupt" pin: @@ -1190,11 +1211,11 @@ binary_sensor: number: 1 # One of INPUT or INPUT_PULLUP mode: INPUT_PULLUP - inverted: False + inverted: false interrupt: FALLING - platform: gpio pin: GPIO9 - name: "Living Room Window" + name: Living Room Window device_class: window filters: - invert: @@ -1234,7 +1255,7 @@ binary_sensor: - OFF for at least 0.2s then: - logger.log: - format: "Multi Clicked TWO" + format: Multi Clicked TWO level: warn - timing: - OFF for 1s to 2s @@ -1242,30 +1263,30 @@ binary_sensor: - OFF for at least 0.5s then: - logger.log: - format: "Multi Clicked LONG SINGLE" + format: Multi Clicked LONG SINGLE level: warn - timing: - ON for at most 1s - OFF for at least 0.5s then: - logger.log: - format: "Multi Clicked SINGLE" + format: Multi Clicked SINGLE level: warn id: binary_sensor1 - platform: gpio pin: number: GPIO9 mode: INPUT_PULLUP - name: "Living Room Window 2" + name: Living Room Window 2 - platform: status - name: "Living Room Status" + name: Living Room Status - platform: esp32_touch - name: "ESP32 Touch Pad GPIO27" + name: ESP32 Touch Pad GPIO27 pin: GPIO27 threshold: 1000 id: btn_left - platform: template - name: "Garage Door Open" + name: Garage Door Open id: garage_door lambda: |- if (isnan(id(${sensorname}_sensor).state)) { @@ -1283,7 +1304,7 @@ binary_sensor: on_press: - binary_sensor.template.publish: id: garage_door - state: OFF + state: false - output.ledc.set_frequency: id: gpio_19 frequency: 500.0Hz @@ -1293,41 +1314,41 @@ binary_sensor: - platform: pn532 pn532_id: pn532_bs uid: 74-10-37-94 - name: "PN532 NFC Tag" + name: PN532 NFC Tag - platform: rdm6300 uid: 7616525 - name: "RDM6300 NFC Tag" + name: RDM6300 NFC Tag - platform: gpio - name: "PCF binary sensor" + name: PCF binary sensor pin: pcf8574: pcf8574_hub number: 1 mode: INPUT - inverted: True + inverted: true - platform: gpio - name: "MCP21 binary sensor" + name: MCP21 binary sensor pin: mcp23xxx: mcp23017_hub number: 1 mode: INPUT - inverted: True + inverted: true - platform: gpio - name: "MCP22 binary sensor" + name: MCP22 binary sensor pin: mcp23xxx: mcp23008_hub number: 7 mode: INPUT_PULLUP - inverted: False + inverted: false - platform: gpio - name: "MCP23 binary sensor" + name: MCP23 binary sensor pin: mcp23016: mcp23016_hub number: 7 mode: INPUT - inverted: False + inverted: false - platform: remote_receiver - name: "Raw Remote Receiver Test" + name: Raw Remote Receiver Test raw: code: [ @@ -1368,7 +1389,7 @@ binary_sensor: 1709, ] - platform: as3935 - name: "Storm Alert" + name: Storm Alert - platform: analog_threshold name: Analog Trheshold 1 sensor_id: template_sensor @@ -1426,7 +1447,7 @@ output: pin: GPIO26 id: gpio_26 power_supply: atx_power_supply - inverted: False + inverted: false - platform: ledc pin: 19 id: gpio_19 @@ -1460,67 +1481,67 @@ output: - platform: tlc59208f id: tlc_0 channel: 0 - tlc59208f_id: "tlc59208f_1" + tlc59208f_id: tlc59208f_1 - platform: tlc59208f id: tlc_1 channel: 1 - tlc59208f_id: "tlc59208f_1" + tlc59208f_id: tlc59208f_1 - platform: tlc59208f id: tlc_2 channel: 2 - tlc59208f_id: "tlc59208f_1" + tlc59208f_id: tlc59208f_1 - platform: tlc59208f id: tlc_3 channel: 0 - tlc59208f_id: "tlc59208f_2" + tlc59208f_id: tlc59208f_2 - platform: tlc59208f id: tlc_4 channel: 1 - tlc59208f_id: "tlc59208f_2" + tlc59208f_id: tlc59208f_2 - platform: tlc59208f id: tlc_5 channel: 2 - tlc59208f_id: "tlc59208f_2" + tlc59208f_id: tlc59208f_2 - platform: tlc59208f id: tlc_6 channel: 0 - tlc59208f_id: "tlc59208f_3" + tlc59208f_id: tlc59208f_3 - platform: tlc59208f id: tlc_7 channel: 1 - tlc59208f_id: "tlc59208f_3" + tlc59208f_id: tlc59208f_3 - platform: tlc59208f id: tlc_8 channel: 2 - tlc59208f_id: "tlc59208f_3" + tlc59208f_id: tlc59208f_3 - platform: gpio id: id2 pin: pcf8574: pcf8574_hub number: 0 mode: OUTPUT - inverted: False + inverted: false - platform: gpio id: id22 pin: mcp23xxx: mcp23017_hub number: 0 mode: OUTPUT - inverted: False + inverted: false - platform: gpio id: id23 pin: mcp23xxx: mcp23008_hub number: 0 mode: OUTPUT - inverted: False + inverted: false - platform: gpio id: id25 pin: mcp23016: mcp23016_hub number: 0 mode: OUTPUT - inverted: False + inverted: false - platform: my9231 id: my_0 channel: 0 @@ -1580,27 +1601,27 @@ e131: light: - platform: binary - name: "Desk Lamp" + name: Desk Lamp output: gpio_26 effects: - strobe: - strobe: - name: "My Strobe" + name: My Strobe colors: - - state: True + - state: true duration: 250ms - - state: False + - state: false duration: 250ms on_turn_on: - switch.template.publish: id: livingroom_lights - state: yes + state: true on_turn_off: - switch.template.publish: id: livingroom_lights - state: yes + state: true - platform: monochromatic - name: "Kitchen Lights" + name: Kitchen Lights id: kitchen output: gpio_19 gamma_correct: 2.8 @@ -1609,7 +1630,7 @@ light: - strobe: - flicker: - flicker: - name: "My Flicker" + name: My Flicker alpha: 98% intensity: 1.5% - lambda: @@ -1621,20 +1642,20 @@ light: if (state == 4) state = 0; - platform: rgb - name: "Living Room Lights" + name: Living Room Lights id: ${roomname}_lights red: pca_0 green: pca_1 blue: pca_2 - platform: rgbw - name: "Living Room Lights 2" + name: Living Room Lights 2 red: pca_3 green: pca_4 blue: pca_5 white: pca_6 color_interlock: true - platform: rgbww - name: "Living Room Lights 2" + name: Living Room Lights 2 red: pca_3 green: pca_4 blue: pca_5 @@ -1644,7 +1665,7 @@ light: warm_white_color_temperature: 500 mireds color_interlock: true - platform: rgbct - name: "Living Room Lights 2" + name: Living Room Lights 2 red: pca_3 green: pca_4 blue: pca_5 @@ -1654,14 +1675,14 @@ light: warm_white_color_temperature: 500 mireds color_interlock: true - platform: cwww - name: "Living Room Lights 2" + name: Living Room Lights 2 cold_white: pca_6 warm_white: pca_6 cold_white_color_temperature: 153 mireds warm_white_color_temperature: 500 mireds constant_brightness: true - platform: color_temperature - name: "Living Room Lights 2" + name: Living Room Lights 2 color_temperature: pca_6 brightness: pca_6 cold_white_color_temperature: 153 mireds @@ -1675,7 +1696,7 @@ light: max_refresh_rate: 20ms power_supply: atx_power_supply color_correct: [75%, 100%, 50%] - name: "FastLED WS2811 Light" + name: FastLED WS2811 Light effects: - addressable_color_wipe: - addressable_color_wipe: @@ -1690,7 +1711,7 @@ light: blue: 0% num_leds: 1 add_led_interval: 100ms - reverse: False + reverse: false - addressable_scan: - addressable_scan: name: Scan Effect With Custom Values @@ -1718,7 +1739,7 @@ light: update_interval: 16ms intensity: 5% - addressable_lambda: - name: "Test For Custom Lambda Effect" + name: Test For Custom Lambda Effect lambda: |- if (initial_run) { it[0] = current_color; @@ -1754,10 +1775,10 @@ light: data_rate: 2MHz num_leds: 60 rgb_order: BRG - name: "FastLED SPI Light" + name: FastLED SPI Light - platform: neopixelbus id: addr3 - name: "Neopixelbus Light" + name: Neopixelbus Light gamma_correct: 2.8 color_correct: [0.0, 0.0, 0.0, 0.0] default_transition_length: 10s @@ -1773,7 +1794,7 @@ light: num_leds: 60 pin: GPIO23 - platform: partition - name: "Partition Light" + name: Partition Light segments: - id: addr1 from: 0 @@ -1787,13 +1808,13 @@ light: - single_light_id: ${roomname}_lights - platform: shelly_dimmer - name: "Shelly Dimmer Light" + name: Shelly Dimmer Light power: - name: "Shelly Dimmer Power" + name: Shelly Dimmer Power voltage: - name: "Shelly Dimmer Voltage" + name: Shelly Dimmer Voltage current: - name: "Shelly Dimmer Current" + name: Shelly Dimmer Current max_brightness: 500 firmware: "51.6" uart_id: uart0 @@ -1805,8 +1826,8 @@ remote_transmitter: climate: - platform: tcl112 name: TCL112 Climate With Sensor - supports_heat: True - supports_cool: True + supports_heat: true + supports_cool: true sensor: ${sensorname}_sensor - platform: tcl112 name: TCL112 Climate @@ -1828,8 +1849,8 @@ climate: target_temperature_state_topic: target/temperature/state/topic - platform: coolix name: Coolix Climate With Sensor - supports_heat: True - supports_cool: True + supports_heat: true + supports_cool: true sensor: ${sensorname}_sensor - platform: coolix name: Coolix Climate @@ -1863,7 +1884,7 @@ climate: use_fahrenheit: true - platform: midea on_state: - logger.log: "State changed!" + logger.log: State changed! id: midea_unit uart_id: uart0 name: Midea Climate @@ -1897,11 +1918,11 @@ climate: - HORIZONTAL - BOTH outdoor_temperature: - name: "Temp" + name: Temp power_usage: - name: "Power" + name: Power humidity_setpoint: - name: "Humidity" + name: Humidity - platform: anova name: Anova cooker ble_client_id: ble_blah @@ -1947,7 +1968,7 @@ switch: # Use pin number 0 number: 0 mode: OUTPUT - inverted: False + inverted: false - platform: gpio name: "MCP23S17 Pin #0" pin: @@ -1955,12 +1976,12 @@ switch: # Use pin number 0 number: 1 mode: OUTPUT - inverted: False + inverted: false - platform: gpio pin: GPIO25 - name: "Living Room Dehumidifier" + name: Living Room Dehumidifier icon: "mdi:restart" - inverted: True + inverted: true command_topic: custom_command_topic command_retain: true restore_mode: ALWAYS_OFF @@ -2037,20 +2058,20 @@ switch: remote_transmitter.transmit_rc_switch_type_a: group: "11001" device: "01000" - state: True + state: true protocol: pulse_length: 175 sync: [1, 31] zero: [1, 3] one: [3, 1] - inverted: False + inverted: false - platform: template name: RC Switch Type B turn_on_action: remote_transmitter.transmit_rc_switch_type_b: address: 4 channel: 2 - state: True + state: true - platform: template name: RC Switch Type C turn_on_action: @@ -2058,14 +2079,14 @@ switch: family: "a" group: 1 device: 2 - state: True + state: true - platform: template name: RC Switch Type D turn_on_action: remote_transmitter.transmit_rc_switch_type_d: group: "a" device: 2 - state: True + state: true - platform: template name: RC5 turn_on_action: @@ -2077,11 +2098,55 @@ switch: turn_on_action: remote_transmitter.transmit_raw: code: [1000, -1000] + - platform: template + name: AEHA + id: eaha_hitachi_climate_power_on + turn_on_action: + remote_transmitter.transmit_aeha: + address: 0x8008 + data: + [ + 0x00, + 0x02, + 0xFD, + 0xFF, + 0x00, + 0x33, + 0xCC, + 0x49, + 0xB6, + 0xC8, + 0x37, + 0x16, + 0xE9, + 0x00, + 0xFF, + 0x00, + 0xFF, + 0x00, + 0xFF, + 0x00, + 0xFF, + 0x00, + 0xFF, + 0xCA, + 0x35, + 0x8F, + 0x70, + 0x00, + 0xFF, + 0x00, + 0xFF, + 0x00, + 0xFF, + 0x00, + 0xFF, + ] - platform: template name: Living Room Lights id: livingroom_lights - optimistic: True - assumed_state: yes + optimistic: true + assumed_state: true turn_on_action: - switch.turn_on: living_room_lights_on - output.set_level: @@ -2104,22 +2169,24 @@ switch: level: !lambda "return 0.5;" turn_off_action: - switch.turn_on: living_room_lights_off - restore_state: False + restore_state: false on_turn_on: - switch.template.publish: id: livingroom_lights - state: yes + state: true - platform: restart - name: "Living Room Restart" + name: Living Room Restart - platform: safe_mode - name: "Living Room Restart (Safe Mode)" + name: Living Room Restart (Safe Mode) + - platform: factory_reset + name: Living Room Restart (Factory Default Settings) - platform: shutdown - name: "Living Room Shutdown" + name: Living Room Shutdown - platform: output - name: "Generic Output" + name: Generic Output output: pca_6 - platform: template - name: "Template Switch" + name: Template Switch id: my_switch lambda: |- if (id(binary_sensor1).state) { @@ -2137,27 +2204,27 @@ switch: // Switch is OFF, do something else here } optimistic: true - assumed_state: no - restore_state: True + assumed_state: false + restore_state: true on_turn_off: - switch.template.publish: id: my_switch state: !lambda "return false;" - platform: uart uart_id: uart0 - name: "UART String Output" - data: "DataToSend" + name: UART String Output + data: DataToSend - platform: uart uart_id: uart0 - name: "UART Bytes Output" + name: UART Bytes Output data: [0xDE, 0xAD, 0xBE, 0xEF] - platform: uart uart_id: uart0 - name: "UART Recurring Output" + name: UART Recurring Output data: [0xDE, 0xAD, 0xBE, 0xEF] send_every: 1s - platform: template - assumed_state: yes + assumed_state: true name: Stepper Switch turn_on_action: - stepper.set_target: @@ -2179,7 +2246,7 @@ switch: sn74hc595: sn74hc595_hub # Use pin number 0 number: 0 - inverted: False + inverted: false - platform: template id: ble1_status optimistic: true @@ -2191,7 +2258,7 @@ switch: fan: - platform: binary output: gpio_26 - name: "Living Room Fan 1" + name: Living Room Fan 1 oscillation_output: gpio_19 direction_output: gpio_26 - platform: speed @@ -2199,7 +2266,7 @@ fan: icon: mdi:weather-windy output: pca_6 speed_count: 10 - name: "Living Room Fan 2" + name: Living Room Fan 2 oscillation_output: gpio_19 direction_output: gpio_26 oscillation_state_topic: oscillation/state/topic @@ -2210,10 +2277,13 @@ fan: speed_command_topic: speed/command/topic on_speed_set: then: - - logger.log: "Fan speed was changed!" + - logger.log: Fan speed was changed! + - platform: bedjet + name: My Bedjet fan + bedjet_id: my_bedjet_client - platform: copy source_id: fan_speed - name: "Fan Speed Copy" + name: Fan Speed Copy interval: - interval: 10s @@ -2224,6 +2294,7 @@ interval: - display.page.show_previous: display1 - interval: 2s then: + # yamllint disable rule:line-length - lambda: |- static uint16_t btn_left_state = id(btn_left)->get_value(); @@ -2232,13 +2303,14 @@ interval: btn_left_state = ((uint32_t) id(btn_left)->get_value() + 63 * (uint32_t)btn_left_state) >> 6; id(btn_left)->set_threshold(btn_left_state * 0.9); + # yamllint enable rule:line-length - if: condition: display.is_displaying_page: id: display1 page_id: page1 then: - - logger.log: "Seeing page 1" + - logger.log: Seeing page 1 color: - id: kbx_red @@ -2310,7 +2382,7 @@ display: lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); - platform: ssd1306_i2c - model: "SSD1306_128X64" + model: SSD1306_128X64 reset_pin: GPIO23 address: 0x3C id: display1 @@ -2331,28 +2403,28 @@ display: ESP_LOGD("display", "1 -> 2"); i2c_id: i2c_bus - platform: ssd1306_spi - model: "SSD1306 128x64" + model: SSD1306 128x64 cs_pin: GPIO23 dc_pin: GPIO23 reset_pin: GPIO23 lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); - platform: ssd1322_spi - model: "SSD1322 256x64" + model: SSD1322 256x64 cs_pin: GPIO23 dc_pin: GPIO23 reset_pin: GPIO23 lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); - platform: ssd1325_spi - model: "SSD1325 128x64" + model: SSD1325 128x64 cs_pin: GPIO23 dc_pin: GPIO23 reset_pin: GPIO23 lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); - platform: ssd1327_i2c - model: "SSD1327 128X128" + model: SSD1327 128X128 reset_pin: GPIO23 address: 0x3D id: display1327 @@ -2366,7 +2438,7 @@ display: // Nothing i2c_id: i2c_bus - platform: ssd1327_spi - model: "SSD1327 128x128" + model: SSD1327 128x128 cs_pin: GPIO23 dc_pin: GPIO23 reset_pin: GPIO23 @@ -2379,7 +2451,7 @@ display: lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); - platform: ssd1351_spi - model: "SSD1351 128x128" + model: SSD1351 128x128 cs_pin: GPIO23 dc_pin: GPIO23 reset_pin: GPIO23 @@ -2402,7 +2474,7 @@ display: lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); - platform: st7735 - model: "INITR_BLACKTAB" + model: INITR_BLACKTAB cs_pin: GPIO5 dc_pin: GPIO16 reset_pin: GPIO23 @@ -2414,7 +2486,7 @@ display: lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); - platform: ili9341 - model: "TFT 2.4" + model: TFT 2.4 cs_pin: GPIO5 dc_pin: GPIO4 reset_pin: GPIO22 @@ -2424,7 +2496,7 @@ display: lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); - platform: ili9341 - model: "TFT 2.4" + model: TFT 2.4 cs_pin: GPIO5 dc_pin: GPIO4 reset_pin: GPIO22 @@ -2438,6 +2510,32 @@ display: it.fill(Color::WHITE); id(glob_bool_processed) = true; } + - platform: pvvx_mithermometer + ble_client_id: ble_foo + time_id: sntp_time + disconnect_delay: 3s + update_interval: 10min + validity_period: 20min + lambda: |- + it.print_bignum(188.8); + it.print_unit(pvvx_mithermometer::UNIT_DEG_E); + it.print_smallnum(88); + it.print_percent(true); + it.print_happy(true); + it.print_sad(true); + it.print_bracket(true); + it.print_battery(true); + - platform: tm1621 + id: tm1621_display + cs_pin: GPIO17 + data_pin: GPIO5 + read_pin: GPIO23 + write_pin: GPIO18 + lambda: |- + it.printf(0, "%.1f", id(dht_temperature).state); + it.display_celsius(true); + it.printf(1, "%.1f", id(dht_humidity).state); + it.display_humidity(true); tm1651: id: tm1651_battery @@ -2496,7 +2594,7 @@ rc522_i2c: mcp4728: - id: mcp4728_dac - store_in_eeprom: False + store_in_eeprom: false address: 0x60 i2c_id: i2c_bus @@ -2529,7 +2627,7 @@ time: cover: - platform: template - name: "Template Cover" + name: Template Cover id: template_cover lambda: |- if (id(binary_sensor1).state) { @@ -2542,8 +2640,8 @@ cover: - cover.template.publish: id: template_cover state: CLOSED - assumed_state: no - has_position: yes + assumed_state: false + has_position: true position_state_topic: position/state/topic position_command_topic: position/command/topic tilt_lambda: !lambda "return 0.5;" @@ -2556,12 +2654,12 @@ cover: then: - lambda: 'ESP_LOGD("cover", "closed");' - platform: am43 - name: "Test AM43" + name: Test AM43 id: am43_test ble_client_id: ble_foo icon: mdi:blinds - platform: feedback - name: "Feedback Cover" + name: Feedback Cover id: gate device_class: gate @@ -2605,24 +2703,24 @@ tca9548a: i2c_id: multiplex0_chan0 pcf8574: - - id: "pcf8574_hub" + - id: pcf8574_hub address: 0x21 - pcf8575: False + pcf8575: false i2c_id: i2c_bus mcp23017: - - id: "mcp23017_hub" - open_drain_interrupt: "true" + - id: mcp23017_hub + open_drain_interrupt: true i2c_id: i2c_bus mcp23008: - - id: "mcp23008_hub" + - id: mcp23008_hub address: 0x22 - open_drain_interrupt: "true" + open_drain_interrupt: true i2c_id: i2c_bus mcp23016: - - id: "mcp23016_hub" + - id: mcp23016_hub address: 0x23 i2c_id: i2c_bus @@ -2639,29 +2737,29 @@ stepper: globals: - id: glob_int type: int - restore_value: yes + restore_value: true initial_value: "0" - id: glob_float type: float - restore_value: yes + restore_value: true initial_value: "0.0f" - id: glob_bool type: bool - restore_value: no + restore_value: false initial_value: "true" - id: glob_string type: std::string - restore_value: no + restore_value: false # initial_value: "" - id: glob_bool_processed type: bool - restore_value: no + restore_value: false initial_value: "false" text_sensor: - platform: ble_client ble_client_id: ble_foo - name: "Sensor Location" + name: Sensor Location service_uuid: "180d" characteristic_uuid: "2a38" descriptor_uuid: "2902" @@ -2672,7 +2770,7 @@ text_sensor: - lambda: |- ESP_LOGD("green_btn", "Location changed: %s", x.c_str()); - platform: mqtt_subscribe - name: "MQTT Subscribe Text" + name: MQTT Subscribe Text topic: "the/topic" qos: 2 on_value: @@ -2709,25 +2807,25 @@ text_sensor: id: ${textname}_text - platform: wifi_info scan_results: - name: "Scan Results" + name: Scan Results ip_address: - name: "IP Address" + name: IP Address ssid: - name: "SSID" + name: SSID bssid: - name: "BSSID" + name: BSSID mac_address: - name: "Mac Address" + name: Mac Address - platform: version - name: "ESPHome Version No Timestamp" - hide_timestamp: True + name: ESPHome Version No Timestamp + hide_timestamp: true - platform: teleinfo - tag_name: "OPTARIF" - name: "optarif" + tag_name: OPTARIF + name: optarif teleinfo_id: myteleinfo sn74hc595: - - id: "sn74hc595_hub" + - id: sn74hc595_hub data_pin: GPIO21 clock_pin: GPIO23 latch_pin: GPIO22 @@ -2850,7 +2948,7 @@ qr_code: lock: - platform: template id: test_lock1 - name: "Template Switch" + name: Template Switch lambda: |- if (id(binary_sensor1).state) { return LOCK_STATE_LOCKED; @@ -2858,7 +2956,7 @@ lock: return LOCK_STATE_UNLOCKED; } optimistic: true - assumed_state: no + assumed_state: false on_unlock: - lock.template.publish: id: test_lock1 @@ -2868,7 +2966,7 @@ lock: id: test_lock1 state: !lambda "return LOCK_STATE_LOCKED;" - platform: output - name: "Generic Output Lock" + name: Generic Output Lock id: test_lock2 output: pca_6 - platform: copy @@ -2877,7 +2975,7 @@ lock: button: - platform: template - name: "Start calibration" + name: Start calibration on_press: - scd4x.perform_forced_calibration: value: 419 diff --git a/tests/test2.yaml b/tests/test2.yaml index 110e8e6625..5507fa0631 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -1,3 +1,4 @@ +--- esphome: name: $devicename platform: ESP32 @@ -28,7 +29,7 @@ api: i2c: sda: 21 scl: 22 - scan: False + scan: false spi: clk_pin: GPIO21 @@ -47,7 +48,7 @@ uart: - lambda: UARTDebug::log_hex(direction, bytes, ':'); ota: - safe_mode: True + safe_mode: true port: 3286 num_attempts: 15 @@ -67,7 +68,7 @@ as3935_i2c: irq_pin: GPIO12 mcp3008: - - id: 'mcp3008_hub' + - id: mcp3008_hub cs_pin: GPIO12 output: @@ -86,32 +87,35 @@ sensor: id: ha_hello_world_temperature - platform: ble_rssi mac_address: AC:37:43:77:5F:4C - name: 'BLE Google Home Mini RSSI value' + name: BLE Google Home Mini RSSI value - platform: ble_rssi - service_uuid: '11aa' - name: 'BLE Test Service 16' + service_uuid: 11aa + name: BLE Test Service 16 - platform: ble_rssi - service_uuid: '11223344' - name: 'BLE Test Service 32' + service_uuid: "11223344" + name: BLE Test Service 32 - platform: ble_rssi - service_uuid: '11223344-5566-7788-99aa-bbccddeeff00' - name: 'BLE Test Service 128' + service_uuid: 11223344-5566-7788-99aa-bbccddeeff00 + name: BLE Test Service 128 + - platform: ble_rssi + service_uuid: 11223344-5566-7788-99aa-bbccddeeff00 + name: BLE Test iBeacon UUID - platform: b_parasite mac_address: F0:CA:F0:CA:01:01 humidity: - name: 'b-parasite Air Humidity' + name: b-parasite Air Humidity temperature: - name: 'b-parasite Air Temperature' + name: b-parasite Air Temperature moisture: - name: 'b-parasite Soil Moisture' + name: b-parasite Soil Moisture battery_voltage: - name: 'b-parasite Battery Voltage' + name: b-parasite Battery Voltage illuminance: - name: 'b-parasite Illuminance' + name: b-parasite Illuminance - platform: senseair id: senseair0 co2: - name: 'SenseAir CO2 Value' + name: SenseAir CO2 Value on_value: then: - senseair.background_calibration: senseair0 @@ -123,167 +127,167 @@ sensor: - platform: ruuvitag mac_address: FF:56:D3:2F:7D:E8 humidity: - name: 'RuuviTag Humidity' + name: RuuviTag Humidity temperature: - name: 'RuuviTag Temperature' + name: RuuviTag Temperature pressure: - name: 'RuuviTag Pressure' + name: RuuviTag Pressure acceleration_x: - name: 'RuuviTag Acceleration X' + name: RuuviTag Acceleration X acceleration_y: - name: 'RuuviTag Acceleration Y' + name: RuuviTag Acceleration Y acceleration_z: - name: 'RuuviTag Acceleration Z' + name: RuuviTag Acceleration Z battery_voltage: - name: 'RuuviTag Battery Voltage' + name: RuuviTag Battery Voltage tx_power: - name: 'RuuviTag TX Power' + name: RuuviTag TX Power movement_counter: - name: 'RuuviTag Movement Counter' + name: RuuviTag Movement Counter measurement_sequence_number: - name: 'RuuviTag Measurement Sequence Number' + name: RuuviTag Measurement Sequence Number - platform: as3935 lightning_energy: - name: 'Lightning Energy' + name: Lightning Energy distance: - name: 'Distance Storm' + name: Distance Storm - platform: xiaomi_hhccjcy01 mac_address: 94:2B:FF:5C:91:61 temperature: - name: 'Xiaomi HHCCJCY01 Temperature' + name: Xiaomi HHCCJCY01 Temperature moisture: - name: 'Xiaomi HHCCJCY01 Moisture' + name: Xiaomi HHCCJCY01 Moisture illuminance: - name: 'Xiaomi HHCCJCY01 Illuminance' + name: Xiaomi HHCCJCY01 Illuminance conductivity: - name: 'Xiaomi HHCCJCY01 Soil Conductivity' + name: Xiaomi HHCCJCY01 Soil Conductivity battery_level: - name: 'Xiaomi HHCCJCY01 Battery Level' + name: Xiaomi HHCCJCY01 Battery Level - platform: xiaomi_lywsdcgq mac_address: 7A:80:8E:19:36:BA temperature: - name: 'Xiaomi LYWSDCGQ Temperature' + name: Xiaomi LYWSDCGQ Temperature humidity: - name: 'Xiaomi LYWSDCGQ Humidity' + name: Xiaomi LYWSDCGQ Humidity battery_level: - name: 'Xiaomi LYWSDCGQ Battery Level' + name: Xiaomi LYWSDCGQ Battery Level - platform: xiaomi_lywsd02 mac_address: 3F:5B:7D:82:58:4E temperature: - name: 'Xiaomi LYWSD02 Temperature' + name: Xiaomi LYWSD02 Temperature humidity: - name: 'Xiaomi LYWSD02 Humidity' + name: Xiaomi LYWSD02 Humidity battery_level: - name: 'Xiaomi LYWSD02 Battery Level' + name: Xiaomi LYWSD02 Battery Level - platform: xiaomi_cgg1 mac_address: 7A:80:8E:19:36:BA temperature: - name: 'Xiaomi CGG1 Temperature' + name: Xiaomi CGG1 Temperature humidity: - name: 'Xiaomi CGG1 Humidity' + name: Xiaomi CGG1 Humidity battery_level: - name: 'Xiaomi CGG1 Battery Level' + name: Xiaomi CGG1 Battery Level - platform: xiaomi_gcls002 - mac_address: '94:2B:FF:5C:91:61' + mac_address: 94:2B:FF:5C:91:61 temperature: - name: 'GCLS02 Temperature' + name: GCLS02 Temperature moisture: - name: 'GCLS02 Moisture' + name: GCLS02 Moisture conductivity: - name: 'GCLS02 Soil Conductivity' + name: GCLS02 Soil Conductivity illuminance: - name: 'GCLS02 Illuminance' + name: GCLS02 Illuminance - platform: xiaomi_hhccpot002 - mac_address: '94:2B:FF:5C:91:61' + mac_address: 94:2B:FF:5C:91:61 moisture: - name: 'HHCCPOT002 Moisture' + name: HHCCPOT002 Moisture conductivity: - name: 'HHCCPOT002 Soil Conductivity' + name: HHCCPOT002 Soil Conductivity - platform: xiaomi_lywsd03mmc - mac_address: 'A4:C1:38:4E:16:78' - bindkey: 'e9efaa6873f9f9c87a5e75a5f814801c' + mac_address: A4:C1:38:4E:16:78 + bindkey: e9efaa6873f9f9c87a5e75a5f814801c temperature: - name: 'Xiaomi LYWSD03MMC Temperature' + name: Xiaomi LYWSD03MMC Temperature humidity: - name: 'Xiaomi LYWSD03MMC Humidity' + name: Xiaomi LYWSD03MMC Humidity battery_level: - name: 'Xiaomi LYWSD03MMC Battery Level' + name: Xiaomi LYWSD03MMC Battery Level - platform: xiaomi_cgd1 - mac_address: 'A4:C1:38:D1:61:7D' - bindkey: 'c99d2313182473b38001086febf781bd' + mac_address: A4:C1:38:D1:61:7D + bindkey: c99d2313182473b38001086febf781bd temperature: - name: 'Xiaomi CGD1 Temperature' + name: Xiaomi CGD1 Temperature humidity: - name: 'Xiaomi CGD1 Humidity' + name: Xiaomi CGD1 Humidity battery_level: - name: 'Xiaomi CGD1 Battery Level' + name: Xiaomi CGD1 Battery Level - platform: xiaomi_jqjcy01ym - mac_address: '7A:80:8E:19:36:BA' + mac_address: 7A:80:8E:19:36:BA temperature: - name: 'JQJCY01YM Temperature' + name: JQJCY01YM Temperature humidity: - name: 'JQJCY01YM Humidity' + name: JQJCY01YM Humidity formaldehyde: - name: 'JQJCY01YM Formaldehyde' + name: JQJCY01YM Formaldehyde battery_level: - name: 'JQJCY01YM Battery Level' + name: JQJCY01YM Battery Level - platform: xiaomi_mhoc303 - mac_address: 'E7:50:59:32:A0:1C' + mac_address: E7:50:59:32:A0:1C temperature: - name: 'MHO-C303 Temperature' + name: MHO-C303 Temperature humidity: - name: 'MHO-C303 Humidity' + name: MHO-C303 Humidity battery_level: - name: 'MHO-C303 Battery Level' + name: MHO-C303 Battery Level - platform: atc_mithermometer - mac_address: 'A4:C1:38:4E:16:78' + mac_address: A4:C1:38:4E:16:78 temperature: - name: 'ATC Temperature' + name: ATC Temperature humidity: - name: 'ATC Humidity' + name: ATC Humidity battery_level: - name: 'ATC Battery-Level' + name: ATC Battery-Level battery_voltage: - name: 'ATC Battery-Voltage' + name: ATC Battery-Voltage - platform: pvvx_mithermometer - mac_address: 'A4:C1:38:4E:16:78' + mac_address: A4:C1:38:4E:16:78 temperature: - name: 'PVVX Temperature' + name: PVVX Temperature humidity: - name: 'PVVX Humidity' + name: PVVX Humidity battery_level: - name: 'PVVX Battery-Level' + name: PVVX Battery-Level battery_voltage: - name: 'PVVX Battery-Voltage' + name: PVVX Battery-Voltage - platform: inkbird_ibsth1_mini mac_address: 38:81:D7:0A:9C:11 temperature: - name: 'Inkbird IBS-TH1 Temperature' + name: Inkbird IBS-TH1 Temperature humidity: - name: 'Inkbird IBS-TH1 Humidity' + name: Inkbird IBS-TH1 Humidity battery_level: - name: 'Inkbird IBS-TH1 Battery Level' + name: Inkbird IBS-TH1 Battery Level - platform: xiaomi_rtcgq02lm id: motion_rtcgq02lm battery_level: - name: 'Mi Motion Sensor 2 Battery level' + name: Mi Motion Sensor 2 Battery level - platform: ltr390 uv: - name: "LTR390 UV" + name: LTR390 UV uv_index: - name: "LTR390 UVI" + name: LTR390 UVI light: - name: "LTR390 Light" + name: LTR390 Light ambient_light: - name: "LTR390 ALS" - gain: "X3" + name: LTR390 ALS + gain: X3 resolution: 18 window_correction_factor: 1.0 address: 0x53 update_interval: 60s - platform: sgp4x voc: - name: "VOC Index" + name: VOC Index id: sgp40_voc_index algorithm_tuning: index_offset: 100 @@ -293,7 +297,7 @@ sensor: std_initial: 50 gain_factor: 230 nox: - name: "NOx" + name: NOx algorithm_tuning: index_offset: 100 learning_time_offset_hours: 12 @@ -304,7 +308,7 @@ sensor: update_interval: 5s - platform: mcp3008 update_interval: 5s - mcp3008_id: 'mcp3008_hub' + mcp3008_id: mcp3008_hub id: freezer_temp_source reference_voltage: 3.19 number: 0 @@ -312,59 +316,71 @@ sensor: ble_client_id: airthings01 update_interval: 5min temperature: - name: "Wave Plus Temperature" + name: Wave Plus Temperature radon: - name: "Wave Plus Radon" + name: Wave Plus Radon radon_long_term: - name: "Wave Plus Radon Long Term" + name: Wave Plus Radon Long Term pressure: - name: "Wave Plus Pressure" + name: Wave Plus Pressure humidity: - name: "Wave Plus Humidity" + name: Wave Plus Humidity co2: - name: "Wave Plus CO2" + name: Wave Plus CO2 tvoc: - name: "Wave Plus VOC" + name: Wave Plus VOC - platform: airthings_wave_mini ble_client_id: airthingsmini01 update_interval: 5min temperature: - name: "Wave Mini Temperature" + name: Wave Mini Temperature humidity: - name: "Wave Mini Humidity" + name: Wave Mini Humidity pressure: - name: "Wave Mini Pressure" + name: Wave Mini Pressure tvoc: - name: "Wave Mini VOC" + name: Wave Mini VOC - platform: ina260 address: 0x40 current: - name: "INA260 Current" + name: INA260 Current power: - name: "INA260 Power" + name: INA260 Power bus_voltage: - name: "INA260 Voltage" + name: INA260 Voltage update_interval: 60s - platform: radon_eye_rd200 ble_client_id: radon_eye_ble_id update_interval: 10min radon: - name: "RD200 Radon" + name: RD200 Radon radon_long_term: - name: "RD200 Radon Long Term" + name: RD200 Radon Long Term - platform: mopeka_pro_check mac_address: D3:75:F2:DC:16:91 tank_type: CUSTOM custom_distance_full: 40cm custom_distance_empty: 10mm temperature: - name: "Propane test temp" + name: Propane test temp level: - name: "Propane test level" + name: Propane test level distance: - name: "Propane test distance" + name: Propane test distance battery_level: - name: "Propane test battery level" + name: Propane test battery level + - platform: ufire_ec + id: ufire_ec_board + ec: + name: Ufire EC + temperature_sensor: ha_hello_world_temperature + temperature_compensation: 20.0 + temperature_coefficient: 0.019 + - platform: ufire_ise + id: ufire_ise_board + temperature_sensor: ha_hello_world_temperature + ph: + name: Ufire pH time: - platform: homeassistant @@ -374,7 +390,7 @@ time: - logger.log: It's 16:00 esp32_touch: - setup_mode: True + setup_mode: true binary_sensor: - platform: homeassistant @@ -386,76 +402,80 @@ binary_sensor: id: ha_hello_world_binary_attribute - platform: ble_presence mac_address: AC:37:43:77:5F:4C - name: 'ESP32 BLE Tracker Google Home Mini' + name: ESP32 BLE Tracker Google Home Mini - platform: ble_presence - service_uuid: '11aa' - name: 'BLE Test Service 16 Presence' + service_uuid: 11aa + name: BLE Test Service 16 Presence - platform: ble_presence - service_uuid: '11223344' - name: 'BLE Test Service 32 Presence' + service_uuid: "11223344" + name: BLE Test Service 32 Presence - platform: ble_presence - service_uuid: '11223344-5566-7788-99aa-bbccddeeff00' - name: 'BLE Test Service 128 Presence' + service_uuid: 11223344-5566-7788-99aa-bbccddeeff00 + name: BLE Test Service 128 Presence - platform: ble_presence - ibeacon_uuid: '11223344-5566-7788-99aa-bbccddeeff00' + ibeacon_uuid: 11223344-5566-7788-99aa-bbccddeeff00 ibeacon_major: 100 ibeacon_minor: 1 - name: 'BLE Test iBeacon Presence' + name: BLE Test iBeacon Presence - platform: esp32_touch - name: 'ESP32 Touch Pad GPIO27' + name: ESP32 Touch Pad GPIO27 pin: GPIO27 threshold: 1000 - platform: as3935 - name: 'Storm Alert' + name: Storm Alert - platform: xiaomi_mue4094rt - name: 'MUE4094RT Motion' - mac_address: '7A:80:8E:19:36:BA' - timeout: '5s' + name: MUE4094RT Motion + mac_address: 7A:80:8E:19:36:BA + timeout: 5s - platform: xiaomi_mjyd02yla - name: 'MJYD02YL-A Motion' - mac_address: '50:EC:50:CD:32:02' - bindkey: '48403ebe2d385db8d0c187f81e62cb64' + name: MJYD02YL-A Motion + mac_address: 50:EC:50:CD:32:02 + bindkey: 48403ebe2d385db8d0c187f81e62cb64 idle_time: - name: 'MJYD02YL-A Idle Time' + name: MJYD02YL-A Idle Time light: - name: 'MJYD02YL-A Light Status' + name: MJYD02YL-A Light Status battery_level: - name: 'MJYD02YL-A Battery Level' + name: MJYD02YL-A Battery Level - platform: xiaomi_wx08zm - name: 'WX08ZM Activation State' - mac_address: '74:a3:4a:b5:07:34' + name: WX08ZM Activation State + mac_address: 74:a3:4a:b5:07:34 tablet: - name: 'WX08ZM Tablet Resource' + name: WX08ZM Tablet Resource battery_level: - name: 'WX08ZM Battery Level' + name: WX08ZM Battery Level - platform: xiaomi_cgpr1 - name: 'CGPR1 Motion' - mac_address: '12:34:56:12:34:56' - bindkey: '48403ebe2d385db8d0c187f81e62cb64' + name: CGPR1 Motion + mac_address: "12:34:56:12:34:56" + bindkey: 48403ebe2d385db8d0c187f81e62cb64 battery_level: - name: 'CGPR1 battery Level' + name: CGPR1 battery Level idle_time: - name: 'CGPR1 Idle Time' + name: CGPR1 Idle Time illuminance: - name: 'CGPR1 Illuminance' + name: CGPR1 Illuminance - platform: xiaomi_rtcgq02lm id: motion_rtcgq02lm motion: - name: 'Mi Motion Sensor 2' + name: Mi Motion Sensor 2 light: - name: 'Mi Motion Sensor 2 Light' + name: Mi Motion Sensor 2 Light button: - name: 'Mi Motion Sensor 2 Button' + name: Mi Motion Sensor 2 Button esp32_ble_tracker: on_ble_advertise: - mac_address: AC:37:43:77:5F:4C then: + # yamllint disable rule:line-length - lambda: !lambda |- ESP_LOGD("main", "The device address is %s", x.address_str().c_str()); + # yamllint enable rule:line-length - then: + # yamllint disable rule:line-length - lambda: !lambda |- ESP_LOGD("main", "The device address is %s", x.address_str().c_str()); + # yamllint enable rule:line-length on_ble_service_data_advertise: - service_uuid: ABCD then: @@ -491,16 +511,12 @@ xiaomi_rtcgq02lm: mac_address: 01:02:03:04:05:06 bindkey: '48403ebe2d385db8d0c187f81e62cb64' -#esp32_ble_beacon: -# type: iBeacon -# uuid: 'c29ce823-e67a-4e71-bff2-abaa32e77a98' - status_led: pin: GPIO2 text_sensor: - platform: version - name: 'ESPHome Version' + name: ESPHome Version icon: mdi:icon id: version_sensor on_value: @@ -508,8 +524,10 @@ text_sensor: condition: - api.connected: then: + # yamllint disable rule:line-length - lambda: !lambda |- ESP_LOGD("main", "The state is %s=%s", x.c_str(), id(version_sensor).state.c_str()); + # yamllint enable rule:line-length - script.execute: my_script - homeassistant.service: service: notify.html5 @@ -534,19 +552,19 @@ text_sensor: - deep_sleep.enter: sleep_duration: !lambda "return 30 * 60 * 1000;" - platform: template - name: 'Template Text Sensor' + name: Template Text Sensor lambda: |- return {"Hello World"}; filters: - to_upper: - to_lower: - - append: "xyz" - - prepend: "abcd" + - append: xyz + - prepend: abcd - substitute: - Hello -> Goodbye - map: - red -> green - - lambda: return {"1234"}; + - lambda: 'return {"1234"};' - platform: homeassistant entity_id: sensor.hello_world2 id: ha_hello_world2 @@ -584,7 +602,7 @@ stepper: pin_b: GPIO27 pin_c: GPIO25 pin_d: GPIO26 - sleep_when_done: no + sleep_when_done: false step_mode: HALF_STEP max_speed: 250 steps/s @@ -595,7 +613,7 @@ stepper: interval: interval: 5s then: - - logger.log: 'Interval Run' + - logger.log: Interval Run display: @@ -608,7 +626,7 @@ cap1188: switch: - platform: template - name: "Test BLE Write Action" + name: Test BLE Write Action turn_on_action: - ble_client.ble_write: id: airthings01 diff --git a/tests/test3.yaml b/tests/test3.yaml index 1abbee8dc5..4eee0fd2c9 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -1,3 +1,4 @@ +--- esphome: name: $device_name comment: $device_comment @@ -9,38 +10,38 @@ esphome: - wifi.connected - time.has_time then: - - logger.log: "Have time" + - logger.log: Have time includes: - custom.h esp8266: board: d1_mini - early_pin_init: True + early_pin_init: true substitutions: device_name: test3 device_comment: test3 device - min_sub: '0.03' - max_sub: '12.0%' + min_sub: "0.03" + max_sub: "12.0%" api: port: 8000 - password: 'pwd' + password: pwd reboot_timeout: 0min encryption: - key: 'bOFFzzvfpg5DB94DuBGLXD/hMnhpDKgP9UQyBulwWVU=' + key: bOFFzzvfpg5DB94DuBGLXD/hMnhpDKgP9UQyBulwWVU= services: - service: hello_world variables: name: string then: - logger.log: - format: 'Hello World %s!' + format: Hello World %s! args: - name.c_str() - service: empty_service then: - - logger.log: 'Service Called' + - logger.log: Service Called - service: all_types variables: bool_: bool @@ -48,7 +49,7 @@ api: float_: float string_: string then: - - logger.log: 'Something happened' + - logger.log: Something happened - stepper.set_target: id: my_stepper2 target: !lambda 'return int_;' @@ -60,7 +61,9 @@ api: string_arr: string[] then: - logger.log: + # yamllint disable rule:line-length format: 'Bool: %s (%u), Int: %d (%u), Float: %f (%u), String: %s (%u)' + # yamllint enable rule:line-length args: - YESNO(bool_arr[0]) - bool_arr.size() @@ -104,7 +107,7 @@ api: then: - dfplayer.play_folder: folder: !lambda 'return folder;' - loop: True + loop: true - service: dfplayer_set_device variables: @@ -122,7 +125,9 @@ api: variables: preset: int then: + # yamllint disable rule:line-length - dfplayer.set_eq: !lambda 'return static_cast(preset);' + # yamllint enable rule:line-length - service: dfplayer_sleep then: @@ -220,7 +225,7 @@ wifi: i2c: sda: 4 scl: 5 - scan: False + scan: false spi: clk_pin: GPIO12 @@ -231,7 +236,7 @@ uart: - id: uart1 tx_pin: number: GPIO1 - inverted: yes + inverted: true rx_pin: GPIO3 baud_rate: 115200 - id: uart2 @@ -279,7 +284,7 @@ modbus: uart_id: uart1 ota: - safe_mode: True + safe_mode: true port: 3286 reboot_timeout: 15min @@ -302,40 +307,40 @@ adalight: sensor: - platform: daly_bms voltage: - name: "Battery Voltage" + name: Battery Voltage current: - name: "Battery Current" + name: Battery Current battery_level: - name: "Battery Level" + name: Battery Level max_cell_voltage: - name: "Max Cell Voltage" + name: Max Cell Voltage max_cell_voltage_number: - name: "Max Cell Voltage Number" + name: Max Cell Voltage Number min_cell_voltage: - name: "Min Cell Voltage" + name: Min Cell Voltage min_cell_voltage_number: - name: "Min Cell Voltage Number" + name: Min Cell Voltage Number max_temperature: - name: "Max Temperature" + name: Max Temperature max_temperature_probe_number: - name: "Max Temperature Probe Number" + name: Max Temperature Probe Number min_temperature: - name: "Min Temperature" + name: Min Temperature min_temperature_probe_number: - name: "Min Temperature Probe Number" + name: Min Temperature Probe Number remaining_capacity: - name: "Remaining Capacity" + name: Remaining Capacity cells_number: - name: "Cells Number" + name: Cells Number temperature_1: - name: "Temperature 1" + name: Temperature 1 temperature_2: - name: "Temperature 2" + name: Temperature 2 - platform: apds9960 type: proximity name: APDS9960 Proximity - platform: vl53l0x - name: 'VL53L0x Distance' + name: VL53L0x Distance address: 0x29 update_interval: 60s enable_pin: GPIO13 @@ -357,32 +362,32 @@ sensor: id: ha_hello_world - platform: aht10 temperature: - name: 'Temperature' + name: Temperature humidity: - name: 'Humidity' + name: Humidity - platform: am2320 temperature: - name: 'Temperature' + name: Temperature humidity: - name: 'Humidity' + name: Humidity - platform: hydreon_rgxx - model: "RG 9" + model: RG 9 uart_id: uart6 - id: "hydreon_rg9" + id: hydreon_rg9 moisture: - name: "hydreon_rain" + name: hydreon_rain id: hydreon_rain - platform: hydreon_rgxx - model: "RG_15" + model: RG_15 uart_id: uart6 acc: - name: "hydreon_acc" + name: hydreon_acc event_acc: - name: "hydreon_event_acc" + name: hydreon_event_acc total_acc: - name: "hydreon_total_acc" + name: hydreon_total_acc r_int: - name: "hydreon_r_int" + name: hydreon_r_int - platform: adc pin: VCC id: my_sensor @@ -496,281 +501,293 @@ sensor: - platform: bl0939 uart_id: uart8 voltage: - name: 'BL0939 Voltage' + name: BL0939 Voltage current_1: - name: 'BL0939 Current 1' + name: BL0939 Current 1 current_2: - name: 'BL0939 Current 2' + name: BL0939 Current 2 active_power_1: - name: 'BL0939 Active Power 1' + name: BL0939 Active Power 1 active_power_2: - name: 'BL0939 Active Power 2' + name: BL0939 Active Power 2 energy_1: - name: 'BL0939 Energy 1' + name: BL0939 Energy 1 energy_2: - name: 'BL0939 Energy 2' + name: BL0939 Energy 2 energy_total: - name: 'BL0939 Total energy' + name: BL0939 Total energy - platform: bl0940 uart_id: uart3 voltage: - name: 'BL0940 Voltage' + name: BL0940 Voltage current: - name: 'BL0940 Current' + name: BL0940 Current power: - name: 'BL0940 Power' + name: BL0940 Power energy: - name: 'BL0940 Energy' + name: BL0940 Energy internal_temperature: - name: 'BL0940 Internal temperature' + name: BL0940 Internal temperature external_temperature: - name: 'BL0940 External temperature' + name: BL0940 External temperature + - platform: bl0942 + uart_id: uart3 + voltage: + name: 'BL0942 Voltage' + current: + name: 'BL0942 Current' + power: + name: 'BL0942 Power' + energy: + name: 'BL0942 Energy' + frequency: + name: "BL0942 Frequency" - platform: pzem004t uart_id: uart3 voltage: - name: 'PZEM004T Voltage' + name: PZEM004T Voltage current: - name: 'PZEM004T Current' + name: PZEM004T Current power: - name: 'PZEM004T Power' + name: PZEM004T Power - platform: pzemac id: pzemac1 voltage: - name: 'PZEMAC Voltage' + name: PZEMAC Voltage current: - name: 'PZEMAC Current' + name: PZEMAC Current power: - name: 'PZEMAC Power' + name: PZEMAC Power energy: - name: 'PZEMAC Energy' + name: PZEMAC Energy frequency: - name: 'PZEMAC Frequency' + name: PZEMAC Frequency power_factor: - name: 'PZEMAC Power Factor' + name: PZEMAC Power Factor - platform: pzemdc voltage: - name: 'PZEMDC Voltage' + name: PZEMDC Voltage current: - name: 'PZEMDC Current' + name: PZEMDC Current power: - name: 'PZEMDC Power' + name: PZEMDC Power - platform: tmp102 - name: 'TMP102 Temperature' + name: TMP102 Temperature - platform: hm3301 pm_1_0: - name: 'PM1.0' + name: PM1.0 pm_2_5: - name: 'PM2.5' + name: PM2.5 pm_10_0: - name: 'PM10.0' + name: PM10.0 aqi: - name: 'AQI' - calculation_type: 'AQI' + name: AQI + calculation_type: AQI - platform: pmsx003 uart_id: uart9 type: PMSX003 pm_1_0: - name: 'PM 1.0 Concentration' + name: PM 1.0 Concentration pm_2_5: - name: 'PM 2.5 Concentration' + name: PM 2.5 Concentration pm_10_0: - name: 'PM 10.0 Concentration' + name: PM 10.0 Concentration pm_1_0_std: - name: 'PM 1.0 Standard Atmospher Concentration' + name: PM 1.0 Standard Atmospher Concentration pm_2_5_std: - name: 'PM 2.5 Standard Atmospher Concentration' + name: PM 2.5 Standard Atmospher Concentration pm_10_0_std: - name: 'PM 10.0 Standard Atmospher Concentration' + name: PM 10.0 Standard Atmospher Concentration pm_0_3um: - name: 'Particulate Count >0.3um' + name: Particulate Count >0.3um pm_0_5um: - name: 'Particulate Count >0.5um' + name: Particulate Count >0.5um pm_1_0um: - name: 'Particulate Count >1.0um' + name: Particulate Count >1.0um pm_2_5um: - name: 'Particulate Count >2.5um' + name: Particulate Count >2.5um pm_5_0um: - name: 'Particulate Count >5.0um' + name: Particulate Count >5.0um pm_10_0um: - name: 'Particulate Count >10.0um' + name: Particulate Count >10.0um update_interval: 30s - platform: pmsx003 uart_id: uart5 type: PMS5003T pm_2_5: - name: 'PM 2.5 Concentration' + name: PM 2.5 Concentration temperature: - name: 'PMS Temperature' + name: PMS Temperature humidity: - name: 'PMS Humidity' + name: PMS Humidity - platform: pmsx003 uart_id: uart6 type: PMS5003ST pm_1_0: - name: 'PM 1.0 Concentration' + name: PM 1.0 Concentration pm_2_5: - name: 'PM 2.5 Concentration' + name: PM 2.5 Concentration pm_10_0: - name: 'PM 10.0 Concentration' + name: PM 10.0 Concentration pm_1_0_std: - name: 'PM 1.0 Standard Atmospher Concentration' + name: PM 1.0 Standard Atmospher Concentration pm_2_5_std: - name: 'PM 2.5 Standard Atmospher Concentration' + name: PM 2.5 Standard Atmospher Concentration pm_10_0_std: - name: 'PM 10.0 Standard Atmospher Concentration' + name: PM 10.0 Standard Atmospher Concentration pm_0_3um: - name: 'Particulate Count >0.3um' + name: Particulate Count >0.3um pm_0_5um: - name: 'Particulate Count >0.5um' + name: Particulate Count >0.5um pm_1_0um: - name: 'Particulate Count >1.0um' + name: Particulate Count >1.0um pm_2_5um: - name: 'Particulate Count >2.5um' + name: Particulate Count >2.5um pm_5_0um: - name: 'Particulate Count >5.0um' + name: Particulate Count >5.0um pm_10_0um: - name: 'Particulate Count >10.0um' + name: Particulate Count >10.0um temperature: - name: 'PMS Temperature' + name: PMS Temperature humidity: - name: 'PMS Humidity' + name: PMS Humidity formaldehyde: - name: 'PMS Formaldehyde Concentration' + name: PMS Formaldehyde Concentration - platform: cse7761 uart_id: uart7 voltage: - name: 'CSE7761 Voltage' + name: CSE7761 Voltage current_1: - name: 'CSE7761 Current 1' + name: CSE7761 Current 1 current_2: - name: 'CSE7761 Current 2' + name: CSE7761 Current 2 active_power_1: - name: 'CSE7761 Active Power 1' + name: CSE7761 Active Power 1 active_power_2: - name: 'CSE7761 Active Power 2' + name: CSE7761 Active Power 2 - platform: cse7766 uart_id: uart3 voltage: - name: 'CSE7766 Voltage' + name: CSE7766 Voltage current: - name: 'CSE7766 Current' + name: CSE7766 Current power: - name: 'CSE776 Power' + name: CSE776 Power - platform: ezo id: ph_ezo address: 99 - unit_of_measurement: 'pH' + unit_of_measurement: pH - platform: tof10120 - name: "Distance sensor" + name: Distance sensor update_interval: 5s - platform: fingerprint_grow fingerprint_count: - name: "Fingerprint Count" + name: Fingerprint Count status: - name: "Fingerprint Status" + name: Fingerprint Status capacity: - name: "Fingerprint Capacity" + name: Fingerprint Capacity security_level: - name: "Fingerprint Security Level" + name: Fingerprint Security Level last_finger_id: - name: "Fingerprint Last Finger ID" + name: Fingerprint Last Finger ID last_confidence: - name: "Fingerprint Last Confidence" + name: Fingerprint Last Confidence - platform: sdm_meter phase_a: current: - name: 'Phase A Current' + name: Phase A Current voltage: - name: 'Phase A Voltage' + name: Phase A Voltage active_power: - name: 'Phase A Power' + name: Phase A Power power_factor: - name: 'Phase A Power Factor' + name: Phase A Power Factor apparent_power: - name: 'Phase A Apparent Power' + name: Phase A Apparent Power reactive_power: - name: 'Phase A Reactive Power' + name: Phase A Reactive Power phase_angle: - name: 'Phase A Phase Angle' + name: Phase A Phase Angle phase_b: current: - name: 'Phase B Current' + name: Phase B Current voltage: - name: 'Phase B Voltage' + name: Phase B Voltage active_power: - name: 'Phase B Power' + name: Phase B Power power_factor: - name: 'Phase B Power Factor' + name: Phase B Power Factor apparent_power: - name: 'Phase B Apparent Power' + name: Phase B Apparent Power reactive_power: - name: 'Phase B Reactive Power' + name: Phase B Reactive Power phase_angle: - name: 'Phase B Phase Angle' + name: Phase B Phase Angle phase_c: current: - name: 'Phase C Current' + name: Phase C Current voltage: - name: 'Phase C Voltage' + name: Phase C Voltage active_power: - name: 'Phase C Power' + name: Phase C Power power_factor: - name: 'Phase C Power Factor' + name: Phase C Power Factor apparent_power: - name: 'Phase C Apparent Power' + name: Phase C Apparent Power reactive_power: - name: 'Phase C Reactive Power' + name: Phase C Reactive Power phase_angle: - name: 'Phase C Phase Angle' + name: Phase C Phase Angle frequency: - name: 'Frequency' + name: Frequency import_active_energy: - name: 'Import Active Energy' + name: Import Active Energy export_active_energy: - name: 'Export Active Energy' + name: Export Active Energy import_reactive_energy: - name: 'Import Reactive Energy' + name: Import Reactive Energy export_reactive_energy: - name: 'Export Reactive Energy' + name: Export Reactive Energy - platform: dsmr energy_delivered_tariff1: name: dsmr_energy_delivered_tariff1 - platform: nextion id: testnumber - name: 'testnumber' + name: testnumber variable_name: testnumber - platform: nextion id: testwave - name: 'testwave' + name: testwave component_id: 2 wave_channel_id: 1 - platform: mlx90393 oversampling: 1 filter: 0 - gain: "3X" + gain: 3X x_axis: - name: "mlxxaxis" + name: mlxxaxis y_axis: - name: "mlxyaxis" + name: mlxyaxis z_axis: - name: "mlxzaxis" + name: mlxzaxis resolution: 17BIT temperature: - name: "mlxtemp" + name: mlxtemp oversampling: 2 - platform: smt100 uart_id: uart10 counts: - name: "Counts" + name: Counts dielectric_constant: - name: "Dielectric Constant" + name: Dielectric Constant temperature: - name: "Temperature" + name: Temperature moisture: - name: "Moisture" + name: Moisture voltage: - name: "Voltage" + name: Voltage update_interval: 60s time: - platform: homeassistant @@ -786,9 +803,9 @@ mpr121: binary_sensor: - platform: daly_bms charging_mos_enabled: - name: "Charging MOS" + name: Charging MOS discharging_mos_enabled: - name: "Discharging MOS" + name: Discharging MOS - platform: apds9960 direction: up name: APDS9960 Up @@ -816,18 +833,18 @@ binary_sensor: - platform: mpr121 id: touchkey0 channel: 0 - name: 'touchkey0' + name: touchkey0 - platform: mpr121 channel: 1 - name: 'touchkey1' + name: touchkey1 id: bin1 - platform: mpr121 channel: 2 - name: 'touchkey2' + name: touchkey2 id: bin2 - platform: mpr121 channel: 3 - name: 'touchkey3' + name: touchkey3 id: bin3 on_press: then: @@ -839,7 +856,7 @@ binary_sensor: channel: 1 name: TTP229 BSF Test - platform: fingerprint_grow - name: "Fingerprint Enrolling" + name: Fingerprint Enrolling - platform: custom lambda: |- auto s = new CustomBinarySensor(); @@ -851,23 +868,27 @@ binary_sensor: - platform: nextion page_id: 0 component_id: 2 - name: 'Nextion Component 2 Touch' + name: Nextion Component 2 Touch - platform: nextion id: r0_sensor - name: 'R0 Sensor' + name: R0 Sensor component_name: page0.r0 - platform: template - id: 'cover_toggle' + id: cover_toggle on_press: then: - cover.toggle: time_based_cover - cover.toggle: endstop_cover - platform: hydreon_rgxx - hydreon_rgxx_id: "hydreon_rg9" + hydreon_rgxx_id: hydreon_rg9 too_cold: - name: "rg9_toocold" + name: rg9_toocold + em_sat: + name: rg9_emsat + lens_bad: + name: rg9_lens_bad - platform: template - id: 'pzemac_reset_energy' + id: pzemac_reset_energy on_press: then: - pzemac.reset_energy: pzemac1 @@ -887,14 +908,16 @@ status_led: text_sensor: - platform: daly_bms status: - name: "BMS Status" + name: BMS Status - platform: version - name: 'ESPHome Version' + name: ESPHome Version icon: mdi:icon id: version_sensor on_value: + # yamllint disable rule:line-length - lambda: !lambda |- ESP_LOGD("main", "The state is %s=%s", x.c_str(), id(version_sensor).state.c_str()); + # yamllint enable rule:line-length - script.execute: my_script - script.wait: my_script - script.stop: my_script @@ -908,7 +931,7 @@ text_sensor: my_variable: |- return id(version_sensor).state; - platform: template - name: 'Template Text Sensor' + name: Template Text Sensor lambda: |- return {"Hello World"}; - platform: homeassistant @@ -929,9 +952,9 @@ text_sensor: component_name: text0 - platform: dsmr identification: - name: "dsmr_identification" + name: dsmr_identification p1_version: - name: "dsmr_p1_version" + name: dsmr_p1_version script: - id: my_script @@ -944,9 +967,9 @@ sm2135: switch: - platform: template - name: 'mpr121_toggle' + name: mpr121_toggle id: mpr121_toggle - optimistic: True + optimistic: true - platform: gpio id: gpio_switch1 pin: @@ -974,7 +997,7 @@ switch: name: Custom Switch - platform: nextion id: r0 - name: 'R0 Switch' + name: R0 Switch component_name: page0.r0 custom_component: @@ -990,7 +1013,7 @@ stepper: pin_b: GPIO13 pin_c: GPIO14 pin_d: GPIO15 - sleep_when_done: no + sleep_when_done: false step_mode: HALF_STEP max_speed: 250 steps/s acceleration: inf @@ -1006,7 +1029,7 @@ stepper: interval: interval: 5s then: - - logger.log: 'Interval Run' + - logger.log: Interval Run - stepper.set_target: id: my_stepper2 target: 500 @@ -1119,7 +1142,7 @@ climate: default_target_temperature_high: 20°C - platform: pid id: pid_climate - name: 'PID Climate Controller' + name: PID Climate Controller sensor: ha_hello_world default_target_temperature: 21°C heat_output: my_slow_pwm @@ -1130,42 +1153,42 @@ climate: sprinkler: - id: yard_sprinkler_ctrlr - main_switch: "Yard Sprinklers" - auto_advance_switch: "Yard Sprinklers Auto Advance" - reverse_switch: "Yard Sprinklers Reverse" + main_switch: Yard Sprinklers + auto_advance_switch: Yard Sprinklers Auto Advance + reverse_switch: Yard Sprinklers Reverse pump_start_pump_delay: 2s pump_stop_valve_delay: 4s pump_switch_off_during_valve_open_delay: true valve_open_delay: 5s valves: - - valve_switch: "Yard Valve 0" - enable_switch: "Enable Yard Valve 0" + - valve_switch: Yard Valve 0 + enable_switch: Enable Yard Valve 0 pump_switch_id: gpio_switch1 run_duration: 10s valve_switch_id: gpio_switch2 - - valve_switch: "Yard Valve 1" - enable_switch: "Enable Yard Valve 1" + - valve_switch: Yard Valve 1 + enable_switch: Enable Yard Valve 1 pump_switch_id: gpio_switch1 run_duration: 10s valve_switch_id: gpio_switch2 - - valve_switch: "Yard Valve 2" - enable_switch: "Enable Yard Valve 2" + - valve_switch: Yard Valve 2 + enable_switch: Enable Yard Valve 2 pump_switch_id: gpio_switch1 run_duration: 10s valve_switch_id: gpio_switch2 - id: garden_sprinkler_ctrlr - main_switch: "Garden Sprinklers" - auto_advance_switch: "Garden Sprinklers Auto Advance" - reverse_switch: "Garden Sprinklers Reverse" + main_switch: Garden Sprinklers + auto_advance_switch: Garden Sprinklers Auto Advance + reverse_switch: Garden Sprinklers Reverse valve_overlap: 5s valves: - - valve_switch: "Garden Valve 0" - enable_switch: "Enable Garden Valve 0" + - valve_switch: Garden Valve 0 + enable_switch: Enable Garden Valve 0 pump_switch_id: gpio_switch1 run_duration: 10s valve_switch_id: gpio_switch2 - - valve_switch: "Garden Valve 1" - enable_switch: "Enable Garden Valve 1" + - valve_switch: Garden Valve 1 + enable_switch: Enable Garden Valve 1 pump_switch_id: gpio_switch1 run_duration: 10s valve_switch_id: gpio_switch2 @@ -1215,7 +1238,7 @@ cover: - switch.turn_on: gpio_switch2 close_duration: 4.5min - platform: current_based - name: "Current Based Cover" + name: Current Based Cover open_sensor: ade7953_current_a open_moving_current_threshold: 0.5 open_obstacle_current_threshold: 0.8 @@ -1236,7 +1259,7 @@ cover: malfunction_detection: true malfunction_action: then: - - logger.log: "Malfunction Detected" + - logger.log: Malfunction Detected - platform: template name: Template Cover with Tilt tilt_lambda: 'return 0.5;' @@ -1322,7 +1345,7 @@ light: pin_b: out2 - platform: sonoff_d1 uart_id: uart2 - use_rm433_remote: False + use_rm433_remote: false name: Sonoff D1 Dimmer id: d1_light restore_mode: RESTORE_DEFAULT_OFF @@ -1348,7 +1371,7 @@ sim800l: str = sender; str = message; - sim800l.send_sms: - message: 'hello you' + message: hello you recipient: '+1234' - sim800l.dial: recipient: '+1234' @@ -1361,7 +1384,7 @@ dfplayer: condition: not: dfplayer.is_playing then: - logger.log: 'Playback finished event' + logger.log: Playback finished event tm1651: id: tm1651_battery clk_pin: D6 @@ -1390,8 +1413,8 @@ rf_bridge: test = data.length; test = data.protocol; test_code = data.code; - - rf_bridge.start_advanced_sniffing - - rf_bridge.stop_advanced_sniffing + - rf_bridge.start_advanced_sniffing: + - rf_bridge.stop_advanced_sniffing: - rf_bridge.send_advanced_code: length: 0x04 protocol: 0x01 @@ -1409,13 +1432,13 @@ rf_bridge: json: key: !lambda |- return id(version_sensor).state; - greeting: 'Hello World' + greeting: Hello World - http_request.send: method: PUT url: https://esphome.io headers: Content-Type: application/json - body: 'Some data' + body: Some data verify_ssl: false display: @@ -1424,13 +1447,13 @@ display: num_chips: 4 rotate_chip: 0 intensity: 10 - scroll_mode: 'STOP' + scroll_mode: STOP id: my_matrix lambda: |- it.printdigit("hello"); - platform: nextion uart_id: uart1 - tft_url: 'http://esphome.io/default35.tft' + tft_url: http://esphome.io/default35.tft update_interval: 5s on_sleep: then: @@ -1506,6 +1529,9 @@ button: target_mac_address: 12:34:56:78:90:ab name: wol_test_1 id: wol_1 + - platform: factory_reset + name: Restart Button (Factory Default Settings) + cd74hc4067: pin_s0: GPIO12 diff --git a/tests/test4.yaml b/tests/test4.yaml index 847639289e..6293e0f7b7 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -1,3 +1,4 @@ +--- esphome: name: $devicename platform: ESP32 @@ -25,7 +26,7 @@ api: i2c: sda: 21 scl: 22 - scan: False + scan: false spi: clk_pin: GPIO21 @@ -38,7 +39,7 @@ uart: baud_rate: 115200 ota: - safe_mode: True + safe_mode: true port: 3286 logger: @@ -71,7 +72,7 @@ select: 2: Both pipsolar: - id: inverter0 + id: inverter0 sx1509: - id: sx1509_hub @@ -81,9 +82,9 @@ mcp3204: cs_pin: GPIO23 dac7678: - address: 0x4A - id: dac7678_hub1 - internal_reference: true + address: 0x4A + id: dac7678_hub1 + internal_reference: true sensor: - platform: homeassistant @@ -226,22 +227,36 @@ sensor: pv_charging_power: id: inverter0_pv_charging_power name: inverter0_pv_charging_power - - platform: "hrxl_maxsonar_wr" - name: "Rainwater Tank Level" + - platform: hrxl_maxsonar_wr + name: Rainwater Tank Level filters: - sliding_window_moving_average: window_size: 12 send_every: 12 - or: - - throttle: "20min" - - delta: 0.02 + - throttle: 20min + - delta: 0.02 - platform: mcp3204 - name: "MCP3204 Pin 1" + name: MCP3204 Pin 1 number: 1 id: mcp_sensor - platform: copy source_id: mcp_sensor - name: "MCP binary sensor copy" + name: MCP binary sensor copy + - platform: ufire_ec + id: ufire_ec_board + temperature: + name: Ufire Temperature + ec: + name: Ufire EC + temperature_compensation: 20.0 + temperature_coefficient: 0.019 + - platform: ufire_ise + id: ufire_ise_board + temperature: + name: Ufire Temperature + ph: + name: Ufire pH # # platform sensor.apds9960 requires component apds9960 @@ -321,15 +336,15 @@ binary_sensor: name: inverter0_backlight_on - platform: template id: ar1 - lambda: 'return {};' + lambda: "return {};" filters: - autorepeat: - - delay: 2s - time_off: 100ms - time_on: 900ms - - delay: 4s - time_off: 100ms - time_on: 400ms + - delay: 2s + time_off: 100ms + time_on: 900ms + - delay: 4s + time_off: 100ms + time_on: 400ms on_state: then: - lambda: 'ESP_LOGI("ar1:", "%d", x);' @@ -356,8 +371,7 @@ binary_sensor: y_min: 0 y_max: 100 on_press: - - logger.log: "Touched" - + - logger.log: Touched climate: - platform: tuya @@ -392,7 +406,7 @@ switch: light: - platform: fastled_clockless id: led_matrix_32x8 - name: "led_matrix_32x8" + name: led_matrix_32x8 chipset: WS2812B pin: GPIO15 num_leds: 256 @@ -417,7 +431,7 @@ cover: position_datapoint: 2 - platform: copy source_id: tuya_cover - name: "Tuya Cover copy" + name: Tuya Cover copy display: - platform: addressable_light @@ -516,7 +530,7 @@ text_sensor: name: inverter0_last_qflag - platform: copy source_id: inverter0_device_mode - name: "Inverter Text Sensor Copy" + name: Inverter Text Sensor Copy output: - platform: pipsolar @@ -524,37 +538,37 @@ output: battery_recharge_voltage: id: inverter0_battery_recharge_voltage_out - platform: dac7678 - dac7678_id: 'dac7678_hub1' + dac7678_id: dac7678_hub1 channel: 0 - id: 'dac7678_1_ch0' + id: dac7678_1_ch0 - platform: dac7678 - dac7678_id: 'dac7678_hub1' + dac7678_id: dac7678_hub1 channel: 1 - id: 'dac7678_1_ch1' + id: dac7678_1_ch1 - platform: dac7678 - dac7678_id: 'dac7678_hub1' + dac7678_id: dac7678_hub1 channel: 2 - id: 'dac7678_1_ch2' + id: dac7678_1_ch2 - platform: dac7678 - dac7678_id: 'dac7678_hub1' + dac7678_id: dac7678_hub1 channel: 3 - id: 'dac7678_1_ch3' + id: dac7678_1_ch3 - platform: dac7678 - dac7678_id: 'dac7678_hub1' + dac7678_id: dac7678_hub1 channel: 4 - id: 'dac7678_1_ch4' + id: dac7678_1_ch4 - platform: dac7678 - dac7678_id: 'dac7678_hub1' + dac7678_id: dac7678_hub1 channel: 5 - id: 'dac7678_1_ch5' + id: dac7678_1_ch5 - platform: dac7678 - dac7678_id: 'dac7678_hub1' + dac7678_id: dac7678_hub1 channel: 6 - id: 'dac7678_1_ch6' + id: dac7678_1_ch6 - platform: dac7678 - dac7678_id: 'dac7678_hub1' + dac7678_id: dac7678_hub1 channel: 7 - id: 'dac7678_1_ch7' + id: dac7678_1_ch7 esp32_camera: name: ESP-32 Camera data_pins: [GPIO17, GPIO35, GPIO34, GPIO5, GPIO39, GPIO18, GPIO36, GPIO19] @@ -581,9 +595,9 @@ esp32_camera_web_server: external_components: - source: github://esphome/esphome@dev refresh: 1d - components: ["bh1750"] + components: [bh1750] - source: ../esphome/components - components: ["sntp"] + components: [sntp] xpt2046: id: xpt_touchscreen cs_pin: 17 @@ -597,8 +611,9 @@ xpt2046: calibration_x_max: 280 calibration_y_min: 340 calibration_y_max: 3860 - swap_x_y: False + swap_x_y: false on_state: + # yamllint disable rule:line-length - lambda: |- ESP_LOGI("main", "args x=%d, y=%d, touched=%s", x, y, (touched ? "touch" : "release")); ESP_LOGI("main", "member x=%d, y=%d, touched=%d, x_raw=%d, y_raw=%d, z_raw=%d", @@ -609,6 +624,7 @@ xpt2046: id(xpt_touchscreen).y_raw, id(xpt_touchscreen).z_raw ); + # yamllint enable rule:line-length button: - platform: restart @@ -622,7 +638,6 @@ button: source_id: shutdown_btn name: Shutdown Button Copy - touchscreen: - platform: ektf2232 interrupt_pin: GPIO36 @@ -631,7 +646,7 @@ touchscreen: on_touch: - logger.log: format: Touch at (%d, %d) - args: ["touch.x", "touch.y"] + args: [touch.x, touch.y] - platform: lilygo_t5_47 id: lilygo_touchscreen @@ -640,7 +655,7 @@ touchscreen: on_touch: - logger.log: format: Touch at (%d, %d) - args: ["touch.x", "touch.y"] + args: [touch.x, touch.y] media_player: - platform: i2s_audio @@ -667,3 +682,10 @@ media_player: - media_player.volume_up: - media_player.volume_down: - media_player.volume_set: 50% + +prometheus: + include_internal: true + relabel: + ha_hello_world: + id: hellow_world + name: Hello World diff --git a/tests/test5.yaml b/tests/test5.yaml index 35f6b14f2a..7fc20c452f 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -1,3 +1,4 @@ +--- esphome: name: test5 build_path: build/test5 @@ -38,6 +39,7 @@ uart: baud_rate: 19200 i2c: + frequency: 100khz modbus: uart_id: uart1 @@ -59,8 +61,10 @@ mqtt: topic: testing/sensor/testing_sensor/state qos: 0 then: + # yamllint disable rule:line-length - lambda: |- - ESP_LOGD("Mqtt Test","testing/sensor/testing_sensor/state=[%s]",x.c_str()); + ESP_LOGD("Mqtt Test", "testing/sensor/testing_sensor/state=[%s]", x.c_str()); + # yamllint enable rule:line-length binary_sensor: - platform: gpio @@ -73,8 +77,93 @@ binary_sensor: id: modbus_binsensortest register_type: read address: 0x3200 - bitmask: 0x80 #(bit 8) - lambda: !lambda "{ return x ;}" + bitmask: 0x80 # (bit 8) + lambda: "return x;" + + - platform: tm1638 + id: Button0 + key: 0 + filters: + - delayed_on: 10ms + on_press: + then: + - switch.turn_on: Led0 + on_release: + then: + - switch.turn_off: Led0 + + - platform: tm1638 + id: Button1 + key: 1 + on_press: + then: + - switch.turn_on: Led1 + on_release: + then: + - switch.turn_off: Led1 + + - platform: tm1638 + id: Button2 + key: 2 + on_press: + then: + - switch.turn_on: Led2 + on_release: + then: + - switch.turn_off: Led2 + + - platform: tm1638 + id: Button3 + key: 3 + on_press: + then: + - switch.turn_on: Led3 + on_release: + then: + - switch.turn_off: Led3 + + - platform: tm1638 + id: Button4 + key: 4 + on_press: + then: + - output.turn_on: Led4 + on_release: + then: + - output.turn_off: Led4 + + - platform: tm1638 + id: Button5 + key: 5 + on_press: + then: + - output.turn_on: Led5 + on_release: + then: + - output.turn_off: Led5 + + - platform: tm1638 + id: Button6 + key: 6 + on_press: + then: + - output.turn_on: Led6 + on_release: + then: + - output.turn_off: Led6 + + - platform: tm1638 + id: Button7 + key: 7 + on_press: + then: + - output.turn_on: Led7 + on_release: + then: + - output.turn_off: Led7 + + + tlc5947: data_pin: GPIO12 @@ -102,13 +191,29 @@ output: address: 0x9001 value_type: U_WORD + - platform: tm1638 + id: Led4 + led: 4 + + - platform: tm1638 + id: Led5 + led: 5 + + - platform: tm1638 + id: Led6 + led: 6 + + - platform: tm1638 + id: Led7 + led: 7 + demo: esp32_ble: esp32_ble_server: - manufacturer: "ESPHome" - model: "Test5" + manufacturer: ESPHome + model: Test5 esp32_improv: authorizer: io0_button @@ -127,12 +232,12 @@ number: mode: slider on_value: - logger.log: - format: "Number changed to %f" - args: ["x"] + format: Number changed to %f + args: [x] set_action: - logger.log: - format: "Template Number set to %f" - args: ["x"] + format: Template Number set to %f + args: [x] - number.set: id: template_number_id value: 50 @@ -162,10 +267,10 @@ number: - id: modbus_numbertest platform: modbus_controller modbus_controller_id: modbus_controller_test - name: "ModbusNumber" + name: ModbusNumber address: 0x9002 value_type: U_WORD - lambda: "return x * 1.0; " + lambda: "return x * 1.0;" write_lambda: |- return x * 1.0 ; multiply: 1.0 @@ -179,11 +284,11 @@ select: restore_value: true on_value: - logger.log: - format: "Select changed to %s (index %d)" + format: Select changed to %s (index %d)" args: ["x.c_str()", "i"] set_action: - logger.log: - format: "Template Select set to %s" + format: Template Select set to %s args: ["x.c_str()"] - select.set: id: template_select_id @@ -215,7 +320,7 @@ select: - three - platform: modbus_controller - name: "Modbus Select Register 1000" + name: Modbus Select Register 1000 address: 1000 value_type: U_WORD optionsmap: @@ -227,41 +332,41 @@ select: sensor: - platform: selec_meter total_active_energy: - name: "SelecEM2M Total Active Energy" + name: SelecEM2M Total Active Energy import_active_energy: - name: "SelecEM2M Import Active Energy" + name: SelecEM2M Import Active Energy export_active_energy: - name: "SelecEM2M Export Active Energy" + name: SelecEM2M Export Active Energy total_reactive_energy: - name: "SelecEM2M Total Reactive Energy" + name: SelecEM2M Total Reactive Energy import_reactive_energy: - name: "SelecEM2M Import Reactive Energy" + name: SelecEM2M Import Reactive Energy export_reactive_energy: - name: "SelecEM2M Export Reactive Energy" + name: SelecEM2M Export Reactive Energy apparent_energy: - name: "SelecEM2M Apparent Energy" + name: SelecEM2M Apparent Energy active_power: - name: "SelecEM2M Active Power" + name: SelecEM2M Active Power reactive_power: - name: "SelecEM2M Reactive Power" + name: SelecEM2M Reactive Power apparent_power: - name: "SelecEM2M Apparent Power" + name: SelecEM2M Apparent Power voltage: - name: "SelecEM2M Voltage" + name: SelecEM2M Voltage current: - name: "SelecEM2M Current" + name: SelecEM2M Current power_factor: - name: "SelecEM2M Power Factor" + name: SelecEM2M Power Factor frequency: - name: "SelecEM2M Frequency" + name: SelecEM2M Frequency maximum_demand_active_power: - name: "SelecEM2M Maximum Demand Active Power" + name: SelecEM2M Maximum Demand Active Power disabled_by_default: true maximum_demand_reactive_power: - name: "SelecEM2M Maximum Demand Reactive Power" + name: SelecEM2M Maximum Demand Reactive Power disabled_by_default: true maximum_demand_apparent_power: - name: "SelecEM2M Maximum Demand Apparent Power" + name: SelecEM2M Maximum Demand Apparent Power disabled_by_default: true - id: modbus_sensortest @@ -278,41 +383,41 @@ sensor: - platform: bmp3xx temperature: - name: "BMP Temperature" + name: BMP Temperature oversampling: 16x pressure: - name: "BMP Pressure" + name: BMP Pressure address: 0x77 iir_filter: 2X - platform: sen5x id: sen54 temperature: - name: "Temperature" + name: Temperature accuracy_decimals: 1 humidity: - name: "Humidity" + name: Humidity accuracy_decimals: 0 pm_1_0: - name: " PM <1µm Weight concentration" + name: PM <1µm Weight concentration id: pm_1_0 accuracy_decimals: 1 pm_2_5: - name: " PM <2.5µm Weight concentration" + name: PM <2.5µm Weight concentration id: pm_2_5 accuracy_decimals: 1 pm_4_0: - name: " PM <4µm Weight concentration" + name: PM <4µm Weight concentration id: pm_4_0 accuracy_decimals: 1 pm_10_0: - name: " PM <10µm Weight concentration" + name: PM <10µm Weight concentration id: pm_10_0 accuracy_decimals: 1 nox: - name: "NOx" + name: NOx voc: - name: "VOC" + name: VOC algorithm_tuning: index_offset: 100 learning_time_offset_hours: 12 @@ -328,13 +433,20 @@ sensor: store_baseline: true address: 0x69 + - platform: mcp9600 + thermocouple_type: K + hot_junction: + name: Thermocouple Temperature + cold_junction: + name: Ambient Temperature + script: - id: automation_test then: - repeat: count: 5 then: - - logger.log: "looping!" + - logger.log: looping! switch: - platform: modbus_controller @@ -343,3 +455,35 @@ switch: register_type: coil address: 2 bitmask: 1 + + - platform: tm1638 + id: Led0 + led: 0 + name: TM1638Led0 + + - platform: tm1638 + id: Led1 + led: 1 + name: TM1638Led1 + + - platform: tm1638 + id: Led2 + led: 2 + name: TM1638Led2 + + - platform: tm1638 + id: Led3 + led: 3 + name: TM1638Led3 + +display: + - platform: tm1638 + id: primarydisplay + stb_pin: 5 #TM1638 STB + clk_pin: 18 #TM1638 CLK + dio_pin: 23 #TM1638 DIO + update_interval: 5s + intensity: 5 + lambda: |- + it.print("81818181"); + diff --git a/tests/test_packages/test_packages_package1.yaml b/tests/test_packages/test_packages_package1.yaml index 0495984d42..312bbe574a 100644 --- a/tests/test_packages/test_packages_package1.yaml +++ b/tests/test_packages/test_packages_package1.yaml @@ -1,2 +1,3 @@ +--- sensor: - <<: !include ./test_uptime_sensor.yaml diff --git a/tests/test_packages/test_packages_package_wifi.yaml b/tests/test_packages/test_packages_package_wifi.yaml index 7d5d41ddab..a8c610edfd 100644 --- a/tests/test_packages/test_packages_package_wifi.yaml +++ b/tests/test_packages/test_packages_package_wifi.yaml @@ -1,4 +1,5 @@ +--- wifi: networks: - - ssid: 'WiFiFromPackage' - password: 'password1' + - ssid: "WiFiFromPackage" + password: "password1" diff --git a/tests/test_packages/test_uptime_sensor.yaml b/tests/test_packages/test_uptime_sensor.yaml index 1bf52a6d0b..f15d968fee 100644 --- a/tests/test_packages/test_uptime_sensor.yaml +++ b/tests/test_packages/test_uptime_sensor.yaml @@ -1,3 +1,4 @@ +--- # Uptime sensor. platform: uptime id: ${devicename}_uptime_pcg diff --git a/tests/unit_tests/fixtures/yaml_util/includes/scalar.yaml b/tests/unit_tests/fixtures/yaml_util/includes/scalar.yaml index ddd2156b5e..89879248aa 100644 --- a/tests/unit_tests/fixtures/yaml_util/includes/scalar.yaml +++ b/tests/unit_tests/fixtures/yaml_util/includes/scalar.yaml @@ -1 +1,2 @@ +--- ${var1} diff --git a/tests/unit_tests/fixtures/yaml_util/includetest.yaml b/tests/unit_tests/fixtures/yaml_util/includetest.yaml index 959283df60..af0a4e2030 100644 --- a/tests/unit_tests/fixtures/yaml_util/includetest.yaml +++ b/tests/unit_tests/fixtures/yaml_util/includetest.yaml @@ -8,10 +8,11 @@ wifi: !include name: my_custom_ssid esphome: - # should be substituted as 'original', not overwritten by vars in the !include above + # should be substituted as 'original', + # not overwritten by vars in the !include above name: ${name} name_add_mac_suffix: true platform: esp8266 - board: !include { file: includes/scalar.yaml, vars: { var1: nodemcu } } + board: !include {file: includes/scalar.yaml, vars: {var1: nodemcu}} - libraries: !include { file: includes/list.yaml, vars: { var1: Wire } } + libraries: !include {file: includes/list.yaml, vars: {var1: Wire}}