Merge branch 'beta' into bump-2022.9.0

This commit is contained in:
Jesse Hills 2022-09-21 15:34:02 +12:00
commit 130c9fad22
No known key found for this signature in database
GPG Key ID: BEAAE804EFD8E83A
220 changed files with 7097 additions and 1691 deletions

View File

@ -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

1
.github/FUNDING.yml vendored
View File

@ -1,3 +1,4 @@
---
# These are supported funding model platforms
custom: https://www.nabucasa.com

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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')

View File

@ -1,8 +1,10 @@
---
name: Lock
# yamllint disable-line rule:truthy
on:
schedule:
- cron: '30 0 * * *'
- cron: "30 0 * * *"
workflow_dispatch:
permissions:

View File

@ -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

View File

@ -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:

View File

@ -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

View File

@ -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:

3
.yamllint Normal file
View File

@ -0,0 +1,3 @@
---
ignore: |
venv/

View File

@ -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

View File

@ -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)

View File

@ -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<uint16_t>(roundf(state * 65535));
if (new_value != 0 && this->store_.value == 0)
this->store_.init_cycle = this->init_with_half_cycle_;

View File

@ -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};

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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]))

View File

@ -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)

View File

@ -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};

View File

@ -116,9 +116,9 @@ class APINoiseFrameHelper : public APIFrameHelper {
std::vector<uint8_t> prologue_;
std::shared_ptr<APINoiseContext> 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 {

View File

@ -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_;

View File

@ -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<std::string> BEDJET_FAN_STEP_NAMES_SET BEDJET_FAN_STEP_NAMES_;
} // namespace bedjet

View File

@ -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,

View File

@ -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;
}

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -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,
)

View File

@ -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;

View File

@ -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};

View File

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

View File

@ -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

View File

@ -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

View File

@ -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))

View File

@ -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)

View File

@ -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

View File

@ -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]))

View File

@ -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();

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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,

View File

@ -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])

View File

@ -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) {

View File

@ -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

View File

@ -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)),
}
)

View File

@ -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)

View File

@ -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

View File

View File

@ -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, &reg)) {
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, &reg)) {
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

View File

@ -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

View File

@ -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))

View File

@ -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

View File

@ -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() {

View File

@ -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])

View File

@ -76,6 +76,32 @@ class BLEManufacturerDataAdvertiseTrigger : public Trigger<const adv_data_t &>,
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<typename... Ts> class ESP32BLEStartScanAction : public Action<Ts...> {
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<typename... Ts> class ESP32BLEStopScanAction : public Action<Ts...>, public Parented<ESP32BLETracker> {
public:
void play(Ts... x) override { this->parent_->stop_scan(); }
};
} // namespace esp32_ble_tracker
} // namespace esphome

View File

@ -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 <nvs_flash.h>
#include <freertos/FreeRTOSConfig.h>
@ -15,6 +16,10 @@
#include <esp_gap_ble_api.h>
#include <esp_bt_defs.h>
#ifdef USE_OTA
#include "esphome/components/ota/ota_component.h"
#endif
#ifdef USE_ARDUINO
#include <esp32-hal-bt.h>
#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_) {

View File

@ -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};

View File

@ -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() {

View File

@ -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")

View File

@ -6,6 +6,7 @@
#ifdef USE_ESP32_FRAMEWORK_ARDUINO
#include <eth_phy/phy_lan8720.h>
#include <eth_phy/phy_ip101.h>
#include <eth_phy/phy_tlk110.h>
#include <lwip/dns.h>
@ -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

View File

@ -17,6 +17,7 @@ namespace ethernet {
enum EthernetType {
ETHERNET_TYPE_LAN8720 = 0,
ETHERNET_TYPE_TLK110,
ETHERNET_TYPE_IP101,
};
struct ManualIP {

View File

@ -0,0 +1,5 @@
import esphome.codegen as cg
CODEOWNERS = ["@anatoly-savchenkov"]
factory_reset_ns = cg.esphome_ns.namespace("factory_reset")

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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))

View File

@ -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

View File

@ -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};

View File

@ -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,

View File

@ -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_();

View File

@ -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))

View File

@ -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());
}
}

View File

@ -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;

View File

@ -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))

View File

@ -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,
)

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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)

View File

View File

@ -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

View File

@ -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

View File

@ -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))

View File

@ -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

View File

@ -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);

View File

@ -40,8 +40,6 @@ class Modbus : public uart::UARTDevice, public Component {
std::vector<ModbusDevice *> devices_;
};
uint16_t crc16(const uint8_t *data, uint8_t len);
class ModbusDevice {
public:
void set_parent(Modbus *parent) { parent_ = parent; }

View File

@ -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,

View File

View File

@ -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

View File

@ -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

View File

@ -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))

View File

@ -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;
}

View File

@ -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];
};

View File

@ -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),

View File

@ -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")

View File

@ -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;

View File

@ -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<OTABackend> make_ota_backend() {
#ifdef USE_ARDUINO
#ifdef USE_ESP8266
@ -35,6 +37,8 @@ std::unique_ptr<OTABackend> 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)
}

View File

@ -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

View File

@ -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_))

View File

@ -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_;

View File

@ -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))

View File

@ -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]))

View File

@ -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');

View File

@ -2,6 +2,9 @@
#ifdef USE_ARDUINO
#include <map>
#include <utility>
#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<EntityBase *, std::string> relabel_map_id_;
std::map<EntityBase *, std::string> relabel_map_name_;
};
} // namespace prometheus

View File

@ -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;

View File

@ -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

View File

@ -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])

View File

@ -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<uint32_t> pulse_width_dedupe_;

View File

@ -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_))

View File

@ -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

View File

@ -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 <esp_gattc_api.h>
#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<void(PVVXDisplay &)>;
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<pvvx_writer_t> writer_{};
};
} // namespace pvvx_mithermometer
} // namespace esphome
#endif

View File

@ -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,

View File

@ -27,12 +27,12 @@ class PZEMAC : public PollingComponent, public modbus::ModbusDevice {
protected:
template<typename... Ts> 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_();
};

View File

@ -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

View File

@ -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,

View File

@ -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};

View File

@ -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]))

View File

@ -0,0 +1,103 @@
#include "aeha_protocol.h"
#include "esphome/core/log.h"
#include <cinttypes>
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<AEHAData> 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<uint8_t> &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

View File

@ -0,0 +1,42 @@
#pragma once
#include "remote_base.h"
namespace esphome {
namespace remote_base {
struct AEHAData {
uint16_t address;
std::vector<uint8_t> data;
bool operator==(const AEHAData &rhs) const { return address == rhs.address && data == rhs.data; }
};
class AEHAProtocol : public RemoteProtocol<AEHAData> {
public:
void encode(RemoteTransmitData *dst, const AEHAData &data) override;
optional<AEHAData> decode(RemoteReceiveData src) override;
void dump(const AEHAData &data) override;
private:
std::string format_data_(const std::vector<uint8_t> &data);
};
DECLARE_REMOTE_PROTOCOL(AEHA)
template<typename... Ts> class AEHAAction : public RemoteTransmitterActionBase<Ts...> {
public:
TEMPLATABLE_VALUE(uint16_t, address)
TEMPLATABLE_VALUE(std::vector<uint8_t>, data)
void set_data(const std::vector<uint8_t> &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

View File

@ -106,6 +106,7 @@ void ProntoProtocol::send_pronto_(RemoteTransmitData *dst, const std::vector<uin
ESP_LOGD(TAG, "Send Pronto: intros=%d", intros);
ESP_LOGD(TAG, "Send Pronto: repeats=%d", repeats);
if (NUMBERS_IN_PREAMBLE + intros + repeats != data.size()) { // inconsistent sizes
ESP_LOGE(TAG, "Inconsistent data, not sending");
return;
}
@ -226,7 +227,18 @@ optional<ProntoData> 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

View File

@ -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) {

View File

@ -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)

View File

@ -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))

View File

@ -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");

View File

@ -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};

View File

@ -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}")

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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<uint8_t>(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<uint8_t>(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;

View File

@ -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<void(std::string, std::string)> callback) {
this->callback_.add(std::move(callback));
this->sms_received_callback_.add(std::move(callback));
}
void add_on_incoming_call_callback(std::function<void(std::string)> callback) {
this->incoming_call_callback_.add(std::move(callback));
}
void add_on_call_connected_callback(std::function<void()> callback) {
this->call_connected_callback_.add(std::move(callback));
}
void add_on_call_disconnected_callback(std::function<void()> callback) {
this->call_disconnected_callback_.add(std::move(callback));
}
void add_on_ussd_received_callback(std::function<void(std::string)> 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<void(std::string, std::string)> callback_;
CallbackManager<void(std::string, std::string)> sms_received_callback_;
CallbackManager<void(std::string)> incoming_call_callback_;
CallbackManager<void()> call_connected_callback_;
CallbackManager<void()> call_disconnected_callback_;
CallbackManager<void(std::string)> ussd_received_callback_;
};
class Sim800LReceivedMessageTrigger : public Trigger<std::string, std::string> {
@ -100,6 +129,33 @@ class Sim800LReceivedMessageTrigger : public Trigger<std::string, std::string> {
}
};
class Sim800LIncomingCallTrigger : public Trigger<std::string> {
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<std::string> {
public:
explicit Sim800LReceivedUssdTrigger(Sim800LComponent *parent) {
parent->add_on_ussd_received_callback([this](const std::string &ussd) { this->trigger(ussd); });
}
};
template<typename... Ts> class Sim800LSendSmsAction : public Action<Ts...> {
public:
Sim800LSendSmsAction(Sim800LComponent *parent) : parent_(parent) {}
@ -116,6 +172,20 @@ template<typename... Ts> class Sim800LSendSmsAction : public Action<Ts...> {
Sim800LComponent *parent_;
};
template<typename... Ts> class Sim800LSendUssdAction : public Action<Ts...> {
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<typename... Ts> class Sim800LDialAction : public Action<Ts...> {
public:
Sim800LDialAction(Sim800LComponent *parent) : parent_(parent) {}
@ -129,6 +199,25 @@ template<typename... Ts> class Sim800LDialAction : public Action<Ts...> {
protected:
Sim800LComponent *parent_;
};
template<typename... Ts> class Sim800LConnectAction : public Action<Ts...> {
public:
Sim800LConnectAction(Sim800LComponent *parent) : parent_(parent) {}
void play(Ts... x) { this->parent_->connect(); }
protected:
Sim800LComponent *parent_;
};
template<typename... Ts> class Sim800LDisconnectAction : public Action<Ts...> {
public:
Sim800LDisconnectAction(Sim800LComponent *parent) : parent_(parent) {}
void play(Ts... x) { this->parent_->disconnect(); }
protected:
Sim800LComponent *parent_;
};
} // namespace sim800l
} // namespace esphome

View File

@ -195,6 +195,11 @@ class SPIComponent : public Component {
template<SPIBitOrder BIT_ORDER, SPIClockPolarity CLOCK_POLARITY, SPIClockPhase CLOCK_PHASE, uint32_t DATA_RATE>
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();

View File

@ -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,

View File

@ -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);

View File

@ -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);

View File

@ -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

View File

@ -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):

View File

@ -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(

View File

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

View File

@ -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_))

View File

@ -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

View File

@ -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<void(TM1621Display &)>;
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<tm1621_writer_t> 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

View File

View File

@ -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))

View File

@ -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

View File

@ -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

View File

@ -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_))

View File

@ -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))

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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))

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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<void(TM1638Component &)>;
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<tm1638_writer_t> writer_{};
std::vector<KeyListener *> listeners_{};
};
} // namespace tm1638
} // namespace esphome

View File

@ -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_;

View File

@ -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))

View File

@ -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_;
};

View File

@ -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]

View File

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

View File

@ -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

View File

@ -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(&reg, 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

View File

@ -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<typename... Ts> class UFireECCalibrateProbeAction : public Action<Ts...> {
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<typename... Ts> class UFireECResetAction : public Action<Ts...> {
public:
UFireECResetAction(UFireECComponent *parent) : parent_(parent) {}
void play(Ts... x) override { this->parent_->reset_board(); }
protected:
UFireECComponent *parent_;
};
} // namespace ufire_ec
} // namespace esphome

View File

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

View File

@ -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

View File

@ -0,0 +1,153 @@
#include "esphome/core/log.h"
#include "ufire_ise.h"
#include <cmath>
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(&reg, 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

View File

@ -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<typename... Ts> class UFireISECalibrateProbeLowAction : public Action<Ts...> {
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<typename... Ts> class UFireISECalibrateProbeHighAction : public Action<Ts...> {
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<typename... Ts> class UFireISEResetAction : public Action<Ts...> {
public:
UFireISEResetAction(UFireISEComponent *parent) : parent_(parent) {}
void play(Ts... x) override { this->parent_->reset_board(); }
protected:
UFireISEComponent *parent_;
};
} // namespace ufire_ise
} // namespace esphome

View File

@ -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

View File

@ -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() {

View File

@ -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<float> 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)

View File

@ -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);

View File

@ -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())

View File

@ -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"

View File

@ -176,7 +176,7 @@ template<typename... Ts> class Action {
return this->next_->is_running();
}
Action<Ts...> *next_ = nullptr;
Action<Ts...> *next_{nullptr};
/// The number of instances of this sequence in the list of actions
/// that is currently being executed.

View File

@ -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;

View File

@ -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.

View File

@ -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",

View File

@ -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

View File

@ -73,7 +73,7 @@ class ISRInternalGPIOPin {
void pin_mode(gpio::Flags flags);
protected:
void *arg_ = nullptr;
void *arg_{nullptr};
};
class InternalGPIOPin : public GPIOPin {

View File

@ -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) {

View File

@ -149,6 +149,9 @@ template<typename T, typename U> 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);

View File

@ -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<typename T, enable_if_t<is_trivially_copyable<T>::value, bool> = true>
ESPPreferenceObject make_preference(uint32_t type, bool in_flash) {
return this->make_preference(sizeof(T), type, in_flash);

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