mirror of
https://github.com/esphome/esphome.git
synced 2024-11-22 15:08:10 +01:00
commit
318b930e9f
258 changed files with 6345 additions and 1381 deletions
20
.github/workflows/ci.yml
vendored
20
.github/workflows/ci.yml
vendored
|
@ -51,26 +51,26 @@ jobs:
|
||||||
name: Run script/clang-format
|
name: Run script/clang-format
|
||||||
- id: clang-tidy
|
- id: clang-tidy
|
||||||
name: Run script/clang-tidy for ESP8266
|
name: Run script/clang-tidy for ESP8266
|
||||||
options: --environment esp8266-tidy --grep USE_ESP8266
|
options: --environment esp8266-arduino-tidy --grep USE_ESP8266
|
||||||
pio_cache_key: tidyesp8266
|
pio_cache_key: tidyesp8266
|
||||||
- id: clang-tidy
|
- id: clang-tidy
|
||||||
name: Run script/clang-tidy for ESP32 1/4
|
name: Run script/clang-tidy for ESP32 Arduino 1/4
|
||||||
options: --environment esp32-tidy --split-num 4 --split-at 1
|
options: --environment esp32-arduino-tidy --split-num 4 --split-at 1
|
||||||
pio_cache_key: tidyesp32
|
pio_cache_key: tidyesp32
|
||||||
- id: clang-tidy
|
- id: clang-tidy
|
||||||
name: Run script/clang-tidy for ESP32 2/4
|
name: Run script/clang-tidy for ESP32 Arduino 2/4
|
||||||
options: --environment esp32-tidy --split-num 4 --split-at 2
|
options: --environment esp32-arduino-tidy --split-num 4 --split-at 2
|
||||||
pio_cache_key: tidyesp32
|
pio_cache_key: tidyesp32
|
||||||
- id: clang-tidy
|
- id: clang-tidy
|
||||||
name: Run script/clang-tidy for ESP32 3/4
|
name: Run script/clang-tidy for ESP32 Arduino 3/4
|
||||||
options: --environment esp32-tidy --split-num 4 --split-at 3
|
options: --environment esp32-arduino-tidy --split-num 4 --split-at 3
|
||||||
pio_cache_key: tidyesp32
|
pio_cache_key: tidyesp32
|
||||||
- id: clang-tidy
|
- id: clang-tidy
|
||||||
name: Run script/clang-tidy for ESP32 4/4
|
name: Run script/clang-tidy for ESP32 Arduino 4/4
|
||||||
options: --environment esp32-tidy --split-num 4 --split-at 4
|
options: --environment esp32-arduino-tidy --split-num 4 --split-at 4
|
||||||
pio_cache_key: tidyesp32
|
pio_cache_key: tidyesp32
|
||||||
- id: clang-tidy
|
- id: clang-tidy
|
||||||
name: Run script/clang-tidy for ESP32 esp-idf
|
name: Run script/clang-tidy for ESP32 IDF
|
||||||
options: --environment esp32-idf-tidy --grep USE_ESP_IDF
|
options: --environment esp32-idf-tidy --grep USE_ESP_IDF
|
||||||
pio_cache_key: tidyesp32-idf
|
pio_cache_key: tidyesp32-idf
|
||||||
|
|
||||||
|
|
16
CODEOWNERS
16
CODEOWNERS
|
@ -28,13 +28,16 @@ esphome/components/b_parasite/* @rbaron
|
||||||
esphome/components/ballu/* @bazuchan
|
esphome/components/ballu/* @bazuchan
|
||||||
esphome/components/bang_bang/* @OttoWinter
|
esphome/components/bang_bang/* @OttoWinter
|
||||||
esphome/components/binary_sensor/* @esphome/core
|
esphome/components/binary_sensor/* @esphome/core
|
||||||
|
esphome/components/bl0940/* @tobias-
|
||||||
esphome/components/ble_client/* @buxtronix
|
esphome/components/ble_client/* @buxtronix
|
||||||
esphome/components/bme680_bsec/* @trvrnrth
|
esphome/components/bme680_bsec/* @trvrnrth
|
||||||
|
esphome/components/bmp3xx/* @martgras
|
||||||
esphome/components/button/* @esphome/core
|
esphome/components/button/* @esphome/core
|
||||||
esphome/components/canbus/* @danielschramm @mvturnho
|
esphome/components/canbus/* @danielschramm @mvturnho
|
||||||
esphome/components/cap1188/* @MrEditor97
|
esphome/components/cap1188/* @MrEditor97
|
||||||
esphome/components/captive_portal/* @OttoWinter
|
esphome/components/captive_portal/* @OttoWinter
|
||||||
esphome/components/ccs811/* @habbie
|
esphome/components/ccs811/* @habbie
|
||||||
|
esphome/components/cd74hc4067/* @asoehlke
|
||||||
esphome/components/climate/* @esphome/core
|
esphome/components/climate/* @esphome/core
|
||||||
esphome/components/climate_ir/* @glmnet
|
esphome/components/climate_ir/* @glmnet
|
||||||
esphome/components/color_temperature/* @jesserockz
|
esphome/components/color_temperature/* @jesserockz
|
||||||
|
@ -55,6 +58,7 @@ esphome/components/esp32/* @esphome/core
|
||||||
esphome/components/esp32_ble/* @jesserockz
|
esphome/components/esp32_ble/* @jesserockz
|
||||||
esphome/components/esp32_ble_server/* @jesserockz
|
esphome/components/esp32_ble_server/* @jesserockz
|
||||||
esphome/components/esp32_camera_web_server/* @ayufan
|
esphome/components/esp32_camera_web_server/* @ayufan
|
||||||
|
esphome/components/esp32_can/* @Sympatron
|
||||||
esphome/components/esp32_improv/* @jesserockz
|
esphome/components/esp32_improv/* @jesserockz
|
||||||
esphome/components/esp8266/* @esphome/core
|
esphome/components/esp8266/* @esphome/core
|
||||||
esphome/components/exposure_notifications/* @OttoWinter
|
esphome/components/exposure_notifications/* @OttoWinter
|
||||||
|
@ -65,6 +69,7 @@ esphome/components/globals/* @esphome/core
|
||||||
esphome/components/gpio/* @esphome/core
|
esphome/components/gpio/* @esphome/core
|
||||||
esphome/components/gps/* @coogle
|
esphome/components/gps/* @coogle
|
||||||
esphome/components/graph/* @synco
|
esphome/components/graph/* @synco
|
||||||
|
esphome/components/growatt_solar/* @leeuwte
|
||||||
esphome/components/havells_solar/* @sourabhjaiswal
|
esphome/components/havells_solar/* @sourabhjaiswal
|
||||||
esphome/components/hbridge/fan/* @WeekendWarrior
|
esphome/components/hbridge/fan/* @WeekendWarrior
|
||||||
esphome/components/hbridge/light/* @DotNetDann
|
esphome/components/hbridge/light/* @DotNetDann
|
||||||
|
@ -74,11 +79,13 @@ esphome/components/homeassistant/* @OttoWinter
|
||||||
esphome/components/hrxl_maxsonar_wr/* @netmikey
|
esphome/components/hrxl_maxsonar_wr/* @netmikey
|
||||||
esphome/components/i2c/* @esphome/core
|
esphome/components/i2c/* @esphome/core
|
||||||
esphome/components/improv_serial/* @esphome/core
|
esphome/components/improv_serial/* @esphome/core
|
||||||
|
esphome/components/ina260/* @MrEditor97
|
||||||
esphome/components/inkbird_ibsth1_mini/* @fkirill
|
esphome/components/inkbird_ibsth1_mini/* @fkirill
|
||||||
esphome/components/inkplate6/* @jesserockz
|
esphome/components/inkplate6/* @jesserockz
|
||||||
esphome/components/integration/* @OttoWinter
|
esphome/components/integration/* @OttoWinter
|
||||||
esphome/components/interval/* @esphome/core
|
esphome/components/interval/* @esphome/core
|
||||||
esphome/components/json/* @OttoWinter
|
esphome/components/json/* @OttoWinter
|
||||||
|
esphome/components/kalman_combinator/* @Cat-Ion
|
||||||
esphome/components/ledc/* @OttoWinter
|
esphome/components/ledc/* @OttoWinter
|
||||||
esphome/components/light/* @esphome/core
|
esphome/components/light/* @esphome/core
|
||||||
esphome/components/logger/* @esphome/core
|
esphome/components/logger/* @esphome/core
|
||||||
|
@ -92,10 +99,13 @@ esphome/components/mcp23x08_base/* @jesserockz
|
||||||
esphome/components/mcp23x17_base/* @jesserockz
|
esphome/components/mcp23x17_base/* @jesserockz
|
||||||
esphome/components/mcp23xxx_base/* @jesserockz
|
esphome/components/mcp23xxx_base/* @jesserockz
|
||||||
esphome/components/mcp2515/* @danielschramm @mvturnho
|
esphome/components/mcp2515/* @danielschramm @mvturnho
|
||||||
|
esphome/components/mcp3204/* @rsumner
|
||||||
|
esphome/components/mcp47a1/* @jesserockz
|
||||||
esphome/components/mcp9808/* @k7hpn
|
esphome/components/mcp9808/* @k7hpn
|
||||||
esphome/components/md5/* @esphome/core
|
esphome/components/md5/* @esphome/core
|
||||||
esphome/components/mdns/* @esphome/core
|
esphome/components/mdns/* @esphome/core
|
||||||
esphome/components/midea/* @dudanov
|
esphome/components/midea/* @dudanov
|
||||||
|
esphome/components/midea_ir/* @dudanov
|
||||||
esphome/components/mitsubishi/* @RubyBailey
|
esphome/components/mitsubishi/* @RubyBailey
|
||||||
esphome/components/modbus_controller/* @martgras
|
esphome/components/modbus_controller/* @martgras
|
||||||
esphome/components/modbus_controller/binary_sensor/* @martgras
|
esphome/components/modbus_controller/binary_sensor/* @martgras
|
||||||
|
@ -123,6 +133,7 @@ esphome/components/pn532_i2c/* @OttoWinter @jesserockz
|
||||||
esphome/components/pn532_spi/* @OttoWinter @jesserockz
|
esphome/components/pn532_spi/* @OttoWinter @jesserockz
|
||||||
esphome/components/power_supply/* @esphome/core
|
esphome/components/power_supply/* @esphome/core
|
||||||
esphome/components/preferences/* @esphome/core
|
esphome/components/preferences/* @esphome/core
|
||||||
|
esphome/components/psram/* @esphome/core
|
||||||
esphome/components/pulse_meter/* @stevebaxter
|
esphome/components/pulse_meter/* @stevebaxter
|
||||||
esphome/components/pvvx_mithermometer/* @pasiz
|
esphome/components/pvvx_mithermometer/* @pasiz
|
||||||
esphome/components/rc522/* @glmnet
|
esphome/components/rc522/* @glmnet
|
||||||
|
@ -132,7 +143,7 @@ esphome/components/restart/* @esphome/core
|
||||||
esphome/components/rf_bridge/* @jesserockz
|
esphome/components/rf_bridge/* @jesserockz
|
||||||
esphome/components/rgbct/* @jesserockz
|
esphome/components/rgbct/* @jesserockz
|
||||||
esphome/components/rtttl/* @glmnet
|
esphome/components/rtttl/* @glmnet
|
||||||
esphome/components/safe_mode/* @paulmonigatti
|
esphome/components/safe_mode/* @jsuanet @paulmonigatti
|
||||||
esphome/components/scd4x/* @sjtrny
|
esphome/components/scd4x/* @sjtrny
|
||||||
esphome/components/script/* @esphome/core
|
esphome/components/script/* @esphome/core
|
||||||
esphome/components/sdm_meter/* @jesserockz @polyfaces
|
esphome/components/sdm_meter/* @jesserockz @polyfaces
|
||||||
|
@ -142,7 +153,7 @@ esphome/components/select/* @esphome/core
|
||||||
esphome/components/sensor/* @esphome/core
|
esphome/components/sensor/* @esphome/core
|
||||||
esphome/components/sgp40/* @SenexCrenshaw
|
esphome/components/sgp40/* @SenexCrenshaw
|
||||||
esphome/components/sht4x/* @sjtrny
|
esphome/components/sht4x/* @sjtrny
|
||||||
esphome/components/shutdown/* @esphome/core
|
esphome/components/shutdown/* @esphome/core @jsuanet
|
||||||
esphome/components/sim800l/* @glmnet
|
esphome/components/sim800l/* @glmnet
|
||||||
esphome/components/sm2135/* @BoukeHaarsma23
|
esphome/components/sm2135/* @BoukeHaarsma23
|
||||||
esphome/components/socket/* @esphome/core
|
esphome/components/socket/* @esphome/core
|
||||||
|
@ -179,6 +190,7 @@ esphome/components/toshiba/* @kbx81
|
||||||
esphome/components/tsl2591/* @wjcarpenter
|
esphome/components/tsl2591/* @wjcarpenter
|
||||||
esphome/components/tuya/binary_sensor/* @jesserockz
|
esphome/components/tuya/binary_sensor/* @jesserockz
|
||||||
esphome/components/tuya/climate/* @jesserockz
|
esphome/components/tuya/climate/* @jesserockz
|
||||||
|
esphome/components/tuya/number/* @frankiboy1
|
||||||
esphome/components/tuya/sensor/* @jesserockz
|
esphome/components/tuya/sensor/* @jesserockz
|
||||||
esphome/components/tuya/switch/* @jesserockz
|
esphome/components/tuya/switch/* @jesserockz
|
||||||
esphome/components/tuya/text_sensor/* @dentra
|
esphome/components/tuya/text_sensor/* @dentra
|
||||||
|
|
|
@ -5,12 +5,12 @@
|
||||||
# One of "docker", "hassio"
|
# One of "docker", "hassio"
|
||||||
ARG BASEIMGTYPE=docker
|
ARG BASEIMGTYPE=docker
|
||||||
|
|
||||||
FROM ghcr.io/hassio-addons/debian-base/amd64:5.1.1 AS base-hassio-amd64
|
FROM ghcr.io/hassio-addons/debian-base/amd64:5.2.3 AS base-hassio-amd64
|
||||||
FROM ghcr.io/hassio-addons/debian-base/aarch64:5.1.1 AS base-hassio-arm64
|
FROM ghcr.io/hassio-addons/debian-base/aarch64:5.2.3 AS base-hassio-arm64
|
||||||
FROM ghcr.io/hassio-addons/debian-base/armv7:5.1.1 AS base-hassio-armv7
|
FROM ghcr.io/hassio-addons/debian-base/armv7:5.2.3 AS base-hassio-armv7
|
||||||
FROM debian:bullseye-20211011-slim AS base-docker-amd64
|
FROM debian:bullseye-20211220-slim AS base-docker-amd64
|
||||||
FROM debian:bullseye-20211011-slim AS base-docker-arm64
|
FROM debian:bullseye-20211220-slim AS base-docker-arm64
|
||||||
FROM debian:bullseye-20211011-slim AS base-docker-armv7
|
FROM debian:bullseye-20211220-slim AS base-docker-armv7
|
||||||
|
|
||||||
# Use TARGETARCH/TARGETVARIANT defined by docker
|
# Use TARGETARCH/TARGETVARIANT defined by docker
|
||||||
# https://docs.docker.com/engine/reference/builder/#automatic-platform-args-in-the-global-scope
|
# https://docs.docker.com/engine/reference/builder/#automatic-platform-args-in-the-global-scope
|
||||||
|
@ -42,8 +42,8 @@ ENV \
|
||||||
RUN \
|
RUN \
|
||||||
# Ubuntu python3-pip is missing wheel
|
# Ubuntu python3-pip is missing wheel
|
||||||
pip3 install --no-cache-dir \
|
pip3 install --no-cache-dir \
|
||||||
wheel==0.36.2 \
|
wheel==0.37.1 \
|
||||||
platformio==5.2.2 \
|
platformio==5.2.4 \
|
||||||
# Change some platformio settings
|
# Change some platformio settings
|
||||||
&& platformio settings set enable_telemetry No \
|
&& platformio settings set enable_telemetry No \
|
||||||
&& platformio settings set check_libraries_interval 1000000 \
|
&& platformio settings set check_libraries_interval 1000000 \
|
||||||
|
@ -64,7 +64,7 @@ RUN \
|
||||||
|
|
||||||
# Copy esphome and install
|
# Copy esphome and install
|
||||||
COPY . /esphome
|
COPY . /esphome
|
||||||
RUN pip3 install --no-cache-dir /esphome
|
RUN pip3 install --no-cache-dir --no-use-pep517 -e /esphome
|
||||||
|
|
||||||
# Settings for dashboard
|
# Settings for dashboard
|
||||||
ENV USERNAME="" PASSWORD=""
|
ENV USERNAME="" PASSWORD=""
|
||||||
|
@ -112,7 +112,7 @@ RUN \
|
||||||
|
|
||||||
# Copy esphome and install
|
# Copy esphome and install
|
||||||
COPY . /esphome
|
COPY . /esphome
|
||||||
RUN pip3 install --no-cache-dir /esphome
|
RUN pip3 install --no-cache-dir --no-use-pep517 -e /esphome
|
||||||
|
|
||||||
# Labels
|
# Labels
|
||||||
LABEL \
|
LABEL \
|
||||||
|
|
|
@ -32,6 +32,7 @@ parser.add_argument("--dry-run", action="store_true", help="Don't run any comman
|
||||||
subparsers = parser.add_subparsers(help="Action to perform", dest="command", required=True)
|
subparsers = parser.add_subparsers(help="Action to perform", dest="command", required=True)
|
||||||
build_parser = subparsers.add_parser("build", help="Build the image")
|
build_parser = subparsers.add_parser("build", help="Build the image")
|
||||||
build_parser.add_argument("--push", help="Also push the images", action="store_true")
|
build_parser.add_argument("--push", help="Also push the images", action="store_true")
|
||||||
|
build_parser.add_argument("--load", help="Load the docker image locally", action="store_true")
|
||||||
manifest_parser = subparsers.add_parser("manifest", help="Create a manifest from already pushed images")
|
manifest_parser = subparsers.add_parser("manifest", help="Create a manifest from already pushed images")
|
||||||
|
|
||||||
|
|
||||||
|
@ -132,6 +133,8 @@ def main():
|
||||||
cmd += ["--tag", img]
|
cmd += ["--tag", img]
|
||||||
if args.push:
|
if args.push:
|
||||||
cmd += ["--push", "--cache-to", f"type=registry,ref={cache_img},mode=max"]
|
cmd += ["--push", "--cache-to", f"type=registry,ref={cache_img},mode=max"]
|
||||||
|
if args.load:
|
||||||
|
cmd += ["--load"]
|
||||||
|
|
||||||
run_command(*cmd, ".")
|
run_command(*cmd, ".")
|
||||||
elif args.command == "manifest":
|
elif args.command == "manifest":
|
||||||
|
|
|
@ -145,6 +145,8 @@ def wrap_to_code(name, comp):
|
||||||
if comp.config_schema is not None:
|
if comp.config_schema is not None:
|
||||||
conf_str = yaml_util.dump(conf)
|
conf_str = yaml_util.dump(conf)
|
||||||
conf_str = conf_str.replace("//", "")
|
conf_str = conf_str.replace("//", "")
|
||||||
|
# remove tailing \ to avoid multi-line comment warning
|
||||||
|
conf_str = conf_str.replace("\\\n", "\n")
|
||||||
cg.add(cg.LineComment(indent(conf_str)))
|
cg.add(cg.LineComment(indent(conf_str)))
|
||||||
await coro(conf)
|
await coro(conf)
|
||||||
|
|
||||||
|
|
|
@ -75,8 +75,7 @@ from esphome.cpp_types import ( # noqa
|
||||||
optional,
|
optional,
|
||||||
arduino_json_ns,
|
arduino_json_ns,
|
||||||
JsonObject,
|
JsonObject,
|
||||||
JsonObjectRef,
|
JsonObjectConst,
|
||||||
JsonObjectConstRef,
|
|
||||||
Controller,
|
Controller,
|
||||||
GPIOPin,
|
GPIOPin,
|
||||||
InternalGPIOPin,
|
InternalGPIOPin,
|
||||||
|
|
|
@ -20,6 +20,7 @@ namespace esphome {
|
||||||
namespace api {
|
namespace api {
|
||||||
|
|
||||||
static const char *const TAG = "api.connection";
|
static const char *const TAG = "api.connection";
|
||||||
|
static const int ESP32_CAMERA_STOP_STREAM = 5000;
|
||||||
|
|
||||||
APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *parent)
|
APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *parent)
|
||||||
: parent_(parent), initial_state_iterator_(parent, this), list_entities_iterator_(parent, this) {
|
: parent_(parent), initial_state_iterator_(parent, this), list_entities_iterator_(parent, this) {
|
||||||
|
@ -704,6 +705,8 @@ void APIConnection::send_camera_state(std::shared_ptr<esp32_camera::CameraImage>
|
||||||
return;
|
return;
|
||||||
if (this->image_reader_.available())
|
if (this->image_reader_.available())
|
||||||
return;
|
return;
|
||||||
|
if (image->was_requested_by(esphome::esp32_camera::API_REQUESTER) ||
|
||||||
|
image->was_requested_by(esphome::esp32_camera::IDLE))
|
||||||
this->image_reader_.set_image(std::move(image));
|
this->image_reader_.set_image(std::move(image));
|
||||||
}
|
}
|
||||||
bool APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) {
|
bool APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) {
|
||||||
|
@ -722,9 +725,14 @@ void APIConnection::camera_image(const CameraImageRequest &msg) {
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (msg.single)
|
if (msg.single)
|
||||||
esp32_camera::global_esp32_camera->request_image();
|
esp32_camera::global_esp32_camera->request_image(esphome::esp32_camera::API_REQUESTER);
|
||||||
if (msg.stream)
|
if (msg.stream) {
|
||||||
esp32_camera::global_esp32_camera->request_stream();
|
esp32_camera::global_esp32_camera->start_stream(esphome::esp32_camera::API_REQUESTER);
|
||||||
|
|
||||||
|
App.scheduler.set_timeout(this->parent_, "api_esp32_camera_stop_stream", ESP32_CAMERA_STOP_STREAM, []() {
|
||||||
|
esp32_camera::global_esp32_camera->stop_stream(esphome::esp32_camera::API_REQUESTER);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#include "api_frame_helper.h"
|
#include "api_frame_helper.h"
|
||||||
|
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/hal.h"
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "proto.h"
|
#include "proto.h"
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
@ -252,7 +253,7 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
|
||||||
|
|
||||||
// uncomment for even more debugging
|
// uncomment for even more debugging
|
||||||
#ifdef HELPER_LOG_PACKETS
|
#ifdef HELPER_LOG_PACKETS
|
||||||
ESP_LOGVV(TAG, "Received frame: %s", hexencode(rx_buf_).c_str());
|
ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(rx_buf_).c_str());
|
||||||
#endif
|
#endif
|
||||||
frame->msg = std::move(rx_buf_);
|
frame->msg = std::move(rx_buf_);
|
||||||
// consume msg
|
// consume msg
|
||||||
|
@ -546,7 +547,8 @@ APIError APINoiseFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) {
|
||||||
size_t total_write_len = 0;
|
size_t total_write_len = 0;
|
||||||
for (int i = 0; i < iovcnt; i++) {
|
for (int i = 0; i < iovcnt; i++) {
|
||||||
#ifdef HELPER_LOG_PACKETS
|
#ifdef HELPER_LOG_PACKETS
|
||||||
ESP_LOGVV(TAG, "Sending raw: %s", hexencode(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len).c_str());
|
ESP_LOGVV(TAG, "Sending raw: %s",
|
||||||
|
format_hex_pretty(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len).c_str());
|
||||||
#endif
|
#endif
|
||||||
total_write_len += iov[i].iov_len;
|
total_write_len += iov[i].iov_len;
|
||||||
}
|
}
|
||||||
|
@ -720,7 +722,12 @@ APIError APINoiseFrameHelper::shutdown(int how) {
|
||||||
}
|
}
|
||||||
extern "C" {
|
extern "C" {
|
||||||
// declare how noise generates random bytes (here with a good HWRNG based on the RF system)
|
// declare how noise generates random bytes (here with a good HWRNG based on the RF system)
|
||||||
void noise_rand_bytes(void *output, size_t len) { esphome::fill_random(reinterpret_cast<uint8_t *>(output), len); }
|
void noise_rand_bytes(void *output, size_t len) {
|
||||||
|
if (!esphome::random_bytes(reinterpret_cast<uint8_t *>(output), len)) {
|
||||||
|
ESP_LOGE(TAG, "Failed to acquire random bytes, rebooting!");
|
||||||
|
arch_restart();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif // USE_API_NOISE
|
#endif // USE_API_NOISE
|
||||||
|
|
||||||
|
@ -855,7 +862,7 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
|
||||||
|
|
||||||
// uncomment for even more debugging
|
// uncomment for even more debugging
|
||||||
#ifdef HELPER_LOG_PACKETS
|
#ifdef HELPER_LOG_PACKETS
|
||||||
ESP_LOGVV(TAG, "Received frame: %s", hexencode(rx_buf_).c_str());
|
ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(rx_buf_).c_str());
|
||||||
#endif
|
#endif
|
||||||
frame->msg = std::move(rx_buf_);
|
frame->msg = std::move(rx_buf_);
|
||||||
// consume msg
|
// consume msg
|
||||||
|
@ -934,7 +941,8 @@ APIError APIPlaintextFrameHelper::write_raw_(const struct iovec *iov, int iovcnt
|
||||||
size_t total_write_len = 0;
|
size_t total_write_len = 0;
|
||||||
for (int i = 0; i < iovcnt; i++) {
|
for (int i = 0; i < iovcnt; i++) {
|
||||||
#ifdef HELPER_LOG_PACKETS
|
#ifdef HELPER_LOG_PACKETS
|
||||||
ESP_LOGVV(TAG, "Sending raw: %s", hexencode(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len).c_str());
|
ESP_LOGVV(TAG, "Sending raw: %s",
|
||||||
|
format_hex_pretty(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len).c_str());
|
||||||
#endif
|
#endif
|
||||||
total_write_len += iov[i].iov_len;
|
total_write_len += iov[i].iov_len;
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,10 +12,10 @@ template<typename... X> class TemplatableStringValue : public TemplatableValue<s
|
||||||
public:
|
public:
|
||||||
TemplatableStringValue() : TemplatableValue<std::string, X...>() {}
|
TemplatableStringValue() : TemplatableValue<std::string, X...>() {}
|
||||||
|
|
||||||
template<typename F, enable_if_t<!is_callable<F, X...>::value, int> = 0>
|
template<typename F, enable_if_t<!is_invocable<F, X...>::value, int> = 0>
|
||||||
TemplatableStringValue(F value) : TemplatableValue<std::string, X...>(value) {}
|
TemplatableStringValue(F value) : TemplatableValue<std::string, X...>(value) {}
|
||||||
|
|
||||||
template<typename F, enable_if_t<is_callable<F, X...>::value, int> = 0>
|
template<typename F, enable_if_t<is_invocable<F, X...>::value, int> = 0>
|
||||||
TemplatableStringValue(F f)
|
TemplatableStringValue(F f)
|
||||||
: TemplatableValue<std::string, X...>([f](X... x) -> std::string { return to_string(f(x...)); }) {}
|
: TemplatableValue<std::string, X...>([f](X... x) -> std::string { return to_string(f(x...)); }) {}
|
||||||
};
|
};
|
||||||
|
|
|
@ -45,6 +45,8 @@ bool ATCMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &device
|
||||||
this->battery_voltage_->publish_state(*res->battery_voltage);
|
this->battery_voltage_->publish_state(*res->battery_voltage);
|
||||||
success = true;
|
success = true;
|
||||||
}
|
}
|
||||||
|
if (this->signal_strength_ != nullptr)
|
||||||
|
this->signal_strength_->publish_state(device.get_rssi());
|
||||||
|
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ class ATCMiThermometer : public Component, public esp32_ble_tracker::ESPBTDevice
|
||||||
void set_humidity(sensor::Sensor *humidity) { humidity_ = humidity; }
|
void set_humidity(sensor::Sensor *humidity) { humidity_ = humidity; }
|
||||||
void set_battery_level(sensor::Sensor *battery_level) { battery_level_ = battery_level; }
|
void set_battery_level(sensor::Sensor *battery_level) { battery_level_ = battery_level; }
|
||||||
void set_battery_voltage(sensor::Sensor *battery_voltage) { battery_voltage_ = battery_voltage; }
|
void set_battery_voltage(sensor::Sensor *battery_voltage) { battery_voltage_ = battery_voltage; }
|
||||||
|
void set_signal_strength(sensor::Sensor *signal_strength) { signal_strength_ = signal_strength; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
uint64_t address_;
|
uint64_t address_;
|
||||||
|
@ -35,6 +36,7 @@ class ATCMiThermometer : public Component, public esp32_ble_tracker::ESPBTDevice
|
||||||
sensor::Sensor *humidity_{nullptr};
|
sensor::Sensor *humidity_{nullptr};
|
||||||
sensor::Sensor *battery_level_{nullptr};
|
sensor::Sensor *battery_level_{nullptr};
|
||||||
sensor::Sensor *battery_voltage_{nullptr};
|
sensor::Sensor *battery_voltage_{nullptr};
|
||||||
|
sensor::Sensor *signal_strength_{nullptr};
|
||||||
|
|
||||||
optional<ParseResult> parse_header_(const esp32_ble_tracker::ServiceData &service_data);
|
optional<ParseResult> parse_header_(const esp32_ble_tracker::ServiceData &service_data);
|
||||||
bool parse_message_(const std::vector<uint8_t> &message, ParseResult &result);
|
bool parse_message_(const std::vector<uint8_t> &message, ParseResult &result);
|
||||||
|
|
|
@ -6,15 +6,18 @@ from esphome.const import (
|
||||||
CONF_BATTERY_VOLTAGE,
|
CONF_BATTERY_VOLTAGE,
|
||||||
CONF_MAC_ADDRESS,
|
CONF_MAC_ADDRESS,
|
||||||
CONF_HUMIDITY,
|
CONF_HUMIDITY,
|
||||||
|
CONF_SIGNAL_STRENGTH,
|
||||||
CONF_TEMPERATURE,
|
CONF_TEMPERATURE,
|
||||||
CONF_ID,
|
CONF_ID,
|
||||||
DEVICE_CLASS_BATTERY,
|
DEVICE_CLASS_BATTERY,
|
||||||
DEVICE_CLASS_HUMIDITY,
|
DEVICE_CLASS_HUMIDITY,
|
||||||
|
DEVICE_CLASS_SIGNAL_STRENGTH,
|
||||||
DEVICE_CLASS_TEMPERATURE,
|
DEVICE_CLASS_TEMPERATURE,
|
||||||
DEVICE_CLASS_VOLTAGE,
|
DEVICE_CLASS_VOLTAGE,
|
||||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||||
STATE_CLASS_MEASUREMENT,
|
STATE_CLASS_MEASUREMENT,
|
||||||
UNIT_CELSIUS,
|
UNIT_CELSIUS,
|
||||||
|
UNIT_DECIBEL_MILLIWATT,
|
||||||
UNIT_PERCENT,
|
UNIT_PERCENT,
|
||||||
UNIT_VOLT,
|
UNIT_VOLT,
|
||||||
)
|
)
|
||||||
|
@ -59,6 +62,13 @@ CONFIG_SCHEMA = (
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||||
),
|
),
|
||||||
|
cv.Optional(CONF_SIGNAL_STRENGTH): sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_DECIBEL_MILLIWATT,
|
||||||
|
accuracy_decimals=0,
|
||||||
|
device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
|
.extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
|
||||||
|
@ -85,3 +95,6 @@ async def to_code(config):
|
||||||
if CONF_BATTERY_VOLTAGE in config:
|
if CONF_BATTERY_VOLTAGE in config:
|
||||||
sens = await sensor.new_sensor(config[CONF_BATTERY_VOLTAGE])
|
sens = await sensor.new_sensor(config[CONF_BATTERY_VOLTAGE])
|
||||||
cg.add(var.set_battery_voltage(sens))
|
cg.add(var.set_battery_voltage(sens))
|
||||||
|
if CONF_SIGNAL_STRENGTH in config:
|
||||||
|
sens = await sensor.new_sensor(config[CONF_SIGNAL_STRENGTH])
|
||||||
|
cg.add(var.set_signal_strength(sens))
|
||||||
|
|
|
@ -80,21 +80,23 @@ void BangBangClimate::compute_state_() {
|
||||||
|
|
||||||
climate::ClimateAction target_action;
|
climate::ClimateAction target_action;
|
||||||
if (too_cold) {
|
if (too_cold) {
|
||||||
// too cold -> enable heating if possible, else idle
|
// too cold -> enable heating if possible and enabled, else idle
|
||||||
if (this->supports_heat_)
|
if (this->supports_heat_ &&
|
||||||
|
(this->mode == climate::CLIMATE_MODE_HEAT_COOL || this->mode == climate::CLIMATE_MODE_HEAT))
|
||||||
target_action = climate::CLIMATE_ACTION_HEATING;
|
target_action = climate::CLIMATE_ACTION_HEATING;
|
||||||
else
|
else
|
||||||
target_action = climate::CLIMATE_ACTION_IDLE;
|
target_action = climate::CLIMATE_ACTION_IDLE;
|
||||||
} else if (too_hot) {
|
} else if (too_hot) {
|
||||||
// too hot -> enable cooling if possible, else idle
|
// too hot -> enable cooling if possible and enabled, else idle
|
||||||
if (this->supports_cool_)
|
if (this->supports_cool_ &&
|
||||||
|
(this->mode == climate::CLIMATE_MODE_HEAT_COOL || this->mode == climate::CLIMATE_MODE_COOL))
|
||||||
target_action = climate::CLIMATE_ACTION_COOLING;
|
target_action = climate::CLIMATE_ACTION_COOLING;
|
||||||
else
|
else
|
||||||
target_action = climate::CLIMATE_ACTION_IDLE;
|
target_action = climate::CLIMATE_ACTION_IDLE;
|
||||||
} else {
|
} else {
|
||||||
// neither too hot nor too cold -> in range
|
// neither too hot nor too cold -> in range
|
||||||
if (this->supports_cool_ && this->supports_heat_) {
|
if (this->supports_cool_ && this->supports_heat_ && this->mode == climate::CLIMATE_MODE_HEAT_COOL) {
|
||||||
// if supports both ends, go to idle action
|
// if supports both ends and both cooling and heating enabled, go to idle action
|
||||||
target_action = climate::CLIMATE_ACTION_IDLE;
|
target_action = climate::CLIMATE_ACTION_IDLE;
|
||||||
} else {
|
} else {
|
||||||
// else use current mode and don't change (hysteresis)
|
// else use current mode and don't change (hysteresis)
|
||||||
|
|
|
@ -48,7 +48,10 @@ void BinarySensor::set_device_class(const std::string &device_class) { this->dev
|
||||||
std::string BinarySensor::get_device_class() {
|
std::string BinarySensor::get_device_class() {
|
||||||
if (this->device_class_.has_value())
|
if (this->device_class_.has_value())
|
||||||
return *this->device_class_;
|
return *this->device_class_;
|
||||||
|
#pragma GCC diagnostic push
|
||||||
|
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||||
return this->device_class();
|
return this->device_class();
|
||||||
|
#pragma GCC diagnostic pop
|
||||||
}
|
}
|
||||||
void BinarySensor::add_filter(Filter *filter) {
|
void BinarySensor::add_filter(Filter *filter) {
|
||||||
filter->parent_ = this;
|
filter->parent_ = this;
|
||||||
|
|
|
@ -74,7 +74,10 @@ class BinarySensor : public EntityBase {
|
||||||
|
|
||||||
// ========== OVERRIDE METHODS ==========
|
// ========== OVERRIDE METHODS ==========
|
||||||
// (You'll only need this when creating your own custom binary sensor)
|
// (You'll only need this when creating your own custom binary sensor)
|
||||||
/// Get the default device class for this sensor, or empty string for no default.
|
/** Override this to set the default device class.
|
||||||
|
*
|
||||||
|
* @deprecated This method is deprecated, set the property during config validation instead. (2022.1)
|
||||||
|
*/
|
||||||
virtual std::string device_class();
|
virtual std::string device_class();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
1
esphome/components/bl0940/__init__.py
Normal file
1
esphome/components/bl0940/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
CODEOWNERS = ["@tobias-"]
|
137
esphome/components/bl0940/bl0940.cpp
Normal file
137
esphome/components/bl0940/bl0940.cpp
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
#include "bl0940.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace bl0940 {
|
||||||
|
|
||||||
|
static const char *const TAG = "bl0940";
|
||||||
|
|
||||||
|
static const uint8_t BL0940_READ_COMMAND = 0x50; // 0x58 according to documentation
|
||||||
|
static const uint8_t BL0940_FULL_PACKET = 0xAA;
|
||||||
|
static const uint8_t BL0940_PACKET_HEADER = 0x55; // 0x58 according to documentation
|
||||||
|
|
||||||
|
static const uint8_t BL0940_WRITE_COMMAND = 0xA0; // 0xA8 according to documentation
|
||||||
|
static const uint8_t BL0940_REG_I_FAST_RMS_CTRL = 0x10;
|
||||||
|
static const uint8_t BL0940_REG_MODE = 0x18;
|
||||||
|
static const uint8_t BL0940_REG_SOFT_RESET = 0x19;
|
||||||
|
static const uint8_t BL0940_REG_USR_WRPROT = 0x1A;
|
||||||
|
static const uint8_t BL0940_REG_TPS_CTRL = 0x1B;
|
||||||
|
|
||||||
|
const uint8_t BL0940_INIT[5][6] = {
|
||||||
|
// Reset to default
|
||||||
|
{BL0940_WRITE_COMMAND, BL0940_REG_SOFT_RESET, 0x5A, 0x5A, 0x5A, 0x38},
|
||||||
|
// Enable User Operation Write
|
||||||
|
{BL0940_WRITE_COMMAND, BL0940_REG_USR_WRPROT, 0x55, 0x00, 0x00, 0xF0},
|
||||||
|
// 0x0100 = CF_UNABLE energy pulse, AC_FREQ_SEL 50Hz, RMS_UPDATE_SEL 800mS
|
||||||
|
{BL0940_WRITE_COMMAND, BL0940_REG_MODE, 0x00, 0x10, 0x00, 0x37},
|
||||||
|
// 0x47FF = Over-current and leakage alarm on, Automatic temperature measurement, Interval 100mS
|
||||||
|
{BL0940_WRITE_COMMAND, BL0940_REG_TPS_CTRL, 0xFF, 0x47, 0x00, 0xFE},
|
||||||
|
// 0x181C = Half cycle, Fast RMS threshold 6172
|
||||||
|
{BL0940_WRITE_COMMAND, BL0940_REG_I_FAST_RMS_CTRL, 0x1C, 0x18, 0x00, 0x1B}};
|
||||||
|
|
||||||
|
void BL0940::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 BL0940::validate_checksum(const DataPacket *data) {
|
||||||
|
uint8_t checksum = BL0940_READ_COMMAND;
|
||||||
|
// Whole package but checksum
|
||||||
|
for (uint32_t i = 0; i < sizeof(data->raw) - 1; i++) {
|
||||||
|
checksum += data->raw[i];
|
||||||
|
}
|
||||||
|
checksum ^= 0xFF;
|
||||||
|
if (checksum != data->checksum) {
|
||||||
|
ESP_LOGW(TAG, "BL0940 invalid checksum! 0x%02X != 0x%02X", checksum, data->checksum);
|
||||||
|
}
|
||||||
|
return checksum == data->checksum;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BL0940::update() {
|
||||||
|
this->flush();
|
||||||
|
this->write_byte(BL0940_READ_COMMAND);
|
||||||
|
this->write_byte(BL0940_FULL_PACKET);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BL0940::setup() {
|
||||||
|
for (auto i : BL0940_INIT) {
|
||||||
|
this->write_array(i, 6);
|
||||||
|
delay(1);
|
||||||
|
}
|
||||||
|
this->flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
float BL0940::update_temp_(sensor::Sensor *sensor, ube16_t temperature) const {
|
||||||
|
auto tb = (float) (temperature.h << 8 | temperature.l);
|
||||||
|
float converted_temp = ((float) 170 / 448) * (tb / 2 - 32) - 45;
|
||||||
|
if (sensor != nullptr) {
|
||||||
|
if (sensor->has_state() && std::abs(converted_temp - sensor->get_state()) > max_temperature_diff_) {
|
||||||
|
ESP_LOGD("bl0940", "Invalid temperature change. Sensor: '%s', Old temperature: %f, New temperature: %f",
|
||||||
|
sensor->get_name().c_str(), sensor->get_state(), converted_temp);
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
sensor->publish_state(converted_temp);
|
||||||
|
}
|
||||||
|
return converted_temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BL0940::received_package_(const DataPacket *data) const {
|
||||||
|
// Bad header
|
||||||
|
if (data->frame_header != BL0940_PACKET_HEADER) {
|
||||||
|
ESP_LOGI("bl0940", "Invalid data. Header mismatch: %d", data->frame_header);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
float v_rms = (float) to_uint32_t(data->v_rms) / voltage_reference_;
|
||||||
|
float i_rms = (float) to_uint32_t(data->i_rms) / current_reference_;
|
||||||
|
float watt = (float) to_int32_t(data->watt) / power_reference_;
|
||||||
|
uint32_t cf_cnt = to_uint32_t(data->cf_cnt);
|
||||||
|
float total_energy_consumption = (float) cf_cnt / energy_reference_;
|
||||||
|
|
||||||
|
float tps1 = update_temp_(internal_temperature_sensor_, data->tps1);
|
||||||
|
float tps2 = update_temp_(external_temperature_sensor_, data->tps2);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGV("bl0940", "BL0940: U %fV, I %fA, P %fW, Cnt %d, ∫P %fkWh, T1 %f°C, T2 %f°C", v_rms, i_rms, watt, cf_cnt,
|
||||||
|
total_energy_consumption, tps1, tps2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BL0940::dump_config() { // NOLINT(readability-function-cognitive-complexity)
|
||||||
|
ESP_LOGCONFIG(TAG, "BL0940:");
|
||||||
|
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("", "Internal temperature", this->internal_temperature_sensor_);
|
||||||
|
LOG_SENSOR("", "External temperature", this->external_temperature_sensor_);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t BL0940::to_uint32_t(ube24_t input) { return input.h << 16 | input.m << 8 | input.l; }
|
||||||
|
|
||||||
|
int32_t BL0940::to_int32_t(sbe24_t input) { return input.h << 16 | input.m << 8 | input.l; }
|
||||||
|
|
||||||
|
} // namespace bl0940
|
||||||
|
} // namespace esphome
|
109
esphome/components/bl0940/bl0940.h
Normal file
109
esphome/components/bl0940/bl0940.h
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/components/uart/uart.h"
|
||||||
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace bl0940 {
|
||||||
|
|
||||||
|
static const float BL0940_PREF = 1430;
|
||||||
|
static const float BL0940_UREF = 33000;
|
||||||
|
static const float BL0940_IREF = 275000; // 2750 from tasmota. Seems to generate values 100 times too high
|
||||||
|
|
||||||
|
// Measured to 297J per click according to power consumption of 5 minutes
|
||||||
|
// Converted to kWh (3.6MJ per kwH). Used to be 256 * 1638.4
|
||||||
|
static const float BL0940_EREF = 3.6e6 / 297;
|
||||||
|
|
||||||
|
struct ube24_t { // NOLINT(readability-identifier-naming,altera-struct-pack-align)
|
||||||
|
uint8_t l;
|
||||||
|
uint8_t m;
|
||||||
|
uint8_t h;
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
struct ube16_t { // NOLINT(readability-identifier-naming,altera-struct-pack-align)
|
||||||
|
uint8_t l;
|
||||||
|
uint8_t h;
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
struct sbe24_t { // NOLINT(readability-identifier-naming,altera-struct-pack-align)
|
||||||
|
uint8_t l;
|
||||||
|
uint8_t m;
|
||||||
|
int8_t h;
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
// Caveat: All these values are big endian (low - middle - high)
|
||||||
|
|
||||||
|
union DataPacket { // NOLINT(altera-struct-pack-align)
|
||||||
|
uint8_t raw[35];
|
||||||
|
struct {
|
||||||
|
uint8_t frame_header; // value of 0x58 according to docs. 0x55 according to Tasmota real world tests. Reality wins.
|
||||||
|
ube24_t i_fast_rms; // 0x00
|
||||||
|
ube24_t i_rms; // 0x04
|
||||||
|
ube24_t RESERVED0; // reserved
|
||||||
|
ube24_t v_rms; // 0x06
|
||||||
|
ube24_t RESERVED1; // reserved
|
||||||
|
sbe24_t watt; // 0x08
|
||||||
|
ube24_t RESERVED2; // reserved
|
||||||
|
ube24_t cf_cnt; // 0x0A
|
||||||
|
ube24_t RESERVED3; // reserved
|
||||||
|
ube16_t tps1; // 0x0c
|
||||||
|
uint8_t RESERVED4; // value of 0x00
|
||||||
|
ube16_t tps2; // 0x0c
|
||||||
|
uint8_t RESERVED5; // value of 0x00
|
||||||
|
uint8_t checksum; // checksum
|
||||||
|
};
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
class BL0940 : 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_internal_temperature_sensor(sensor::Sensor *internal_temperature_sensor) {
|
||||||
|
internal_temperature_sensor_ = internal_temperature_sensor;
|
||||||
|
}
|
||||||
|
void set_external_temperature_sensor(sensor::Sensor *external_temperature_sensor) {
|
||||||
|
external_temperature_sensor_ = external_temperature_sensor;
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() override;
|
||||||
|
|
||||||
|
void update() override;
|
||||||
|
void setup() override;
|
||||||
|
void dump_config() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
sensor::Sensor *voltage_sensor_;
|
||||||
|
sensor::Sensor *current_sensor_;
|
||||||
|
// 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_;
|
||||||
|
|
||||||
|
// Max difference between two measurements of the temperature. Used to avoid noise.
|
||||||
|
float max_temperature_diff_{0};
|
||||||
|
// Divide by this to turn into Watt
|
||||||
|
float power_reference_ = BL0940_PREF;
|
||||||
|
// Divide by this to turn into Volt
|
||||||
|
float voltage_reference_ = BL0940_UREF;
|
||||||
|
// Divide by this to turn into Ampere
|
||||||
|
float current_reference_ = BL0940_IREF;
|
||||||
|
// Divide by this to turn into kWh
|
||||||
|
float energy_reference_ = BL0940_EREF;
|
||||||
|
|
||||||
|
float update_temp_(sensor::Sensor *sensor, ube16_t packed_temperature) const;
|
||||||
|
|
||||||
|
static uint32_t to_uint32_t(ube24_t input);
|
||||||
|
|
||||||
|
static int32_t to_int32_t(sbe24_t input);
|
||||||
|
|
||||||
|
static bool validate_checksum(const DataPacket *data);
|
||||||
|
|
||||||
|
void received_package_(const DataPacket *data) const;
|
||||||
|
};
|
||||||
|
} // namespace bl0940
|
||||||
|
} // namespace esphome
|
106
esphome/components/bl0940/sensor.py
Normal file
106
esphome/components/bl0940/sensor.py
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
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,
|
||||||
|
DEVICE_CLASS_CURRENT,
|
||||||
|
DEVICE_CLASS_ENERGY,
|
||||||
|
DEVICE_CLASS_POWER,
|
||||||
|
DEVICE_CLASS_VOLTAGE,
|
||||||
|
DEVICE_CLASS_TEMPERATURE,
|
||||||
|
ICON_EMPTY,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
STATE_CLASS_NONE,
|
||||||
|
UNIT_AMPERE,
|
||||||
|
UNIT_CELSIUS,
|
||||||
|
UNIT_KILOWATT_HOURS,
|
||||||
|
UNIT_VOLT,
|
||||||
|
UNIT_WATT,
|
||||||
|
)
|
||||||
|
|
||||||
|
DEPENDENCIES = ["uart"]
|
||||||
|
|
||||||
|
CONF_INTERNAL_TEMPERATURE = "internal_temperature"
|
||||||
|
CONF_EXTERNAL_TEMPERATURE = "external_temperature"
|
||||||
|
|
||||||
|
bl0940_ns = cg.esphome_ns.namespace("bl0940")
|
||||||
|
BL0940 = bl0940_ns.class_("BL0940", cg.PollingComponent, uart.UARTDevice)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = (
|
||||||
|
cv.Schema(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(BL0940),
|
||||||
|
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
|
||||||
|
UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_CURRENT): sensor.sensor_schema(
|
||||||
|
UNIT_AMPERE,
|
||||||
|
ICON_EMPTY,
|
||||||
|
2,
|
||||||
|
DEVICE_CLASS_CURRENT,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_POWER): sensor.sensor_schema(
|
||||||
|
UNIT_WATT, ICON_EMPTY, 0, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_ENERGY): sensor.sensor_schema(
|
||||||
|
UNIT_KILOWATT_HOURS,
|
||||||
|
ICON_EMPTY,
|
||||||
|
0,
|
||||||
|
DEVICE_CLASS_ENERGY,
|
||||||
|
STATE_CLASS_NONE,
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_INTERNAL_TEMPERATURE): sensor.sensor_schema(
|
||||||
|
UNIT_CELSIUS,
|
||||||
|
ICON_EMPTY,
|
||||||
|
0,
|
||||||
|
DEVICE_CLASS_TEMPERATURE,
|
||||||
|
STATE_CLASS_NONE,
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_EXTERNAL_TEMPERATURE): sensor.sensor_schema(
|
||||||
|
UNIT_CELSIUS,
|
||||||
|
ICON_EMPTY,
|
||||||
|
0,
|
||||||
|
DEVICE_CLASS_TEMPERATURE,
|
||||||
|
STATE_CLASS_NONE,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.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_INTERNAL_TEMPERATURE in config:
|
||||||
|
conf = config[CONF_INTERNAL_TEMPERATURE]
|
||||||
|
sens = await sensor.new_sensor(conf)
|
||||||
|
cg.add(var.set_internal_temperature_sensor(sens))
|
||||||
|
if CONF_EXTERNAL_TEMPERATURE in config:
|
||||||
|
conf = config[CONF_EXTERNAL_TEMPERATURE]
|
||||||
|
sens = await sensor.new_sensor(conf)
|
||||||
|
cg.add(var.set_external_temperature_sensor(sens))
|
|
@ -201,7 +201,7 @@ void BME280Component::update() {
|
||||||
float pressure = this->read_pressure_(data, t_fine);
|
float pressure = this->read_pressure_(data, t_fine);
|
||||||
float humidity = this->read_humidity_(data, t_fine);
|
float humidity = this->read_humidity_(data, t_fine);
|
||||||
|
|
||||||
ESP_LOGD(TAG, "Got temperature=%.1f°C pressure=%.1fhPa humidity=%.1f%%", temperature, pressure, humidity);
|
ESP_LOGV(TAG, "Got temperature=%.1f°C pressure=%.1fhPa humidity=%.1f%%", temperature, pressure, humidity);
|
||||||
if (this->temperature_sensor_ != nullptr)
|
if (this->temperature_sensor_ != nullptr)
|
||||||
this->temperature_sensor_->publish_state(temperature);
|
this->temperature_sensor_->publish_state(temperature);
|
||||||
if (this->pressure_sensor_ != nullptr)
|
if (this->pressure_sensor_ != nullptr)
|
||||||
|
|
0
esphome/components/bmp3xx/__init__.py
Normal file
0
esphome/components/bmp3xx/__init__.py
Normal file
388
esphome/components/bmp3xx/bmp3xx.cpp
Normal file
388
esphome/components/bmp3xx/bmp3xx.cpp
Normal file
|
@ -0,0 +1,388 @@
|
||||||
|
/*
|
||||||
|
based on BMP388_DEV by Martin Lindupp
|
||||||
|
under MIT License (MIT)
|
||||||
|
Copyright (C) Martin Lindupp 2020
|
||||||
|
http://github.com/MartinL1/BMP388_DEV
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "bmp3xx.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/hal.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace bmp3xx {
|
||||||
|
|
||||||
|
static const char *const TAG = "bmp3xx.sensor";
|
||||||
|
|
||||||
|
static const LogString *chip_type_to_str(uint8_t chip_type) {
|
||||||
|
switch (chip_type) {
|
||||||
|
case BMP388_ID:
|
||||||
|
return LOG_STR("BMP 388");
|
||||||
|
case BMP390_ID:
|
||||||
|
return LOG_STR("BMP 390");
|
||||||
|
default:
|
||||||
|
return LOG_STR("Unknown Chip Type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const LogString *oversampling_to_str(Oversampling oversampling) {
|
||||||
|
switch (oversampling) {
|
||||||
|
case Oversampling::OVERSAMPLING_NONE:
|
||||||
|
return LOG_STR("None");
|
||||||
|
case Oversampling::OVERSAMPLING_X2:
|
||||||
|
return LOG_STR("2x");
|
||||||
|
case Oversampling::OVERSAMPLING_X4:
|
||||||
|
return LOG_STR("4x");
|
||||||
|
case Oversampling::OVERSAMPLING_X8:
|
||||||
|
return LOG_STR("8x");
|
||||||
|
case Oversampling::OVERSAMPLING_X16:
|
||||||
|
return LOG_STR("16x");
|
||||||
|
case Oversampling::OVERSAMPLING_X32:
|
||||||
|
return LOG_STR("32x");
|
||||||
|
default:
|
||||||
|
return LOG_STR("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const LogString *iir_filter_to_str(IIRFilter filter) {
|
||||||
|
switch (filter) {
|
||||||
|
case IIRFilter::IIR_FILTER_OFF:
|
||||||
|
return LOG_STR("OFF");
|
||||||
|
case IIRFilter::IIR_FILTER_2:
|
||||||
|
return LOG_STR("2x");
|
||||||
|
case IIRFilter::IIR_FILTER_4:
|
||||||
|
return LOG_STR("4x");
|
||||||
|
case IIRFilter::IIR_FILTER_8:
|
||||||
|
return LOG_STR("8x");
|
||||||
|
case IIRFilter::IIR_FILTER_16:
|
||||||
|
return LOG_STR("16x");
|
||||||
|
case IIRFilter::IIR_FILTER_32:
|
||||||
|
return LOG_STR("32x");
|
||||||
|
case IIRFilter::IIR_FILTER_64:
|
||||||
|
return LOG_STR("64x");
|
||||||
|
case IIRFilter::IIR_FILTER_128:
|
||||||
|
return LOG_STR("128x");
|
||||||
|
default:
|
||||||
|
return LOG_STR("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BMP3XXComponent::setup() {
|
||||||
|
this->error_code_ = NONE;
|
||||||
|
ESP_LOGCONFIG(TAG, "Setting up BMP3XX...");
|
||||||
|
// Call the Device base class "initialise" function
|
||||||
|
if (!reset()) {
|
||||||
|
ESP_LOGE(TAG, "Failed to reset BMP3XX...");
|
||||||
|
this->error_code_ = ERROR_SENSOR_RESET;
|
||||||
|
this->mark_failed();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!read_byte(BMP388_CHIP_ID, &this->chip_id_.reg)) {
|
||||||
|
ESP_LOGE(TAG, "Can't read chip id");
|
||||||
|
this->error_code_ = ERROR_COMMUNICATION_FAILED;
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ESP_LOGCONFIG(TAG, "Chip %s Id 0x%X", LOG_STR_ARG(chip_type_to_str(this->chip_id_.reg)), this->chip_id_.reg);
|
||||||
|
|
||||||
|
if (chip_id_.reg != BMP388_ID && chip_id_.reg != BMP390_ID) {
|
||||||
|
ESP_LOGE(TAG, "Unknown chip id - is this really a BMP388 or BMP390?");
|
||||||
|
this->error_code_ = ERROR_WRONG_CHIP_ID;
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// set sensor in sleep mode
|
||||||
|
stop_conversion();
|
||||||
|
// Read the calibration parameters into the params structure
|
||||||
|
if (!read_bytes(BMP388_TRIM_PARAMS, (uint8_t *) &compensation_params_, sizeof(compensation_params_))) {
|
||||||
|
ESP_LOGE(TAG, "Can't read calibration data");
|
||||||
|
this->error_code_ = ERROR_COMMUNICATION_FAILED;
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
compensation_float_params_.param_T1 =
|
||||||
|
(float) compensation_params_.param_T1 / powf(2.0f, -8.0f); // Calculate the floating point trim parameters
|
||||||
|
compensation_float_params_.param_T2 = (float) compensation_params_.param_T2 / powf(2.0f, 30.0f);
|
||||||
|
compensation_float_params_.param_T3 = (float) compensation_params_.param_T3 / powf(2.0f, 48.0f);
|
||||||
|
compensation_float_params_.param_P1 = ((float) compensation_params_.param_P1 - powf(2.0f, 14.0f)) / powf(2.0f, 20.0f);
|
||||||
|
compensation_float_params_.param_P2 = ((float) compensation_params_.param_P2 - powf(2.0f, 14.0f)) / powf(2.0f, 29.0f);
|
||||||
|
compensation_float_params_.param_P3 = (float) compensation_params_.param_P3 / powf(2.0f, 32.0f);
|
||||||
|
compensation_float_params_.param_P4 = (float) compensation_params_.param_P4 / powf(2.0f, 37.0f);
|
||||||
|
compensation_float_params_.param_P5 = (float) compensation_params_.param_P5 / powf(2.0f, -3.0f);
|
||||||
|
compensation_float_params_.param_P6 = (float) compensation_params_.param_P6 / powf(2.0f, 6.0f);
|
||||||
|
compensation_float_params_.param_P7 = (float) compensation_params_.param_P7 / powf(2.0f, 8.0f);
|
||||||
|
compensation_float_params_.param_P8 = (float) compensation_params_.param_P8 / powf(2.0f, 15.0f);
|
||||||
|
compensation_float_params_.param_P9 = (float) compensation_params_.param_P9 / powf(2.0f, 48.0f);
|
||||||
|
compensation_float_params_.param_P10 = (float) compensation_params_.param_P10 / powf(2.0f, 48.0f);
|
||||||
|
compensation_float_params_.param_P11 = (float) compensation_params_.param_P11 / powf(2.0f, 65.0f);
|
||||||
|
|
||||||
|
// Initialise the BMP388 IIR filter register
|
||||||
|
if (!set_iir_filter(this->iir_filter_)) {
|
||||||
|
ESP_LOGE(TAG, "Failed to set IIR filter");
|
||||||
|
this->error_code_ = ERROR_COMMUNICATION_FAILED;
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set power control registers
|
||||||
|
pwr_ctrl_.bit.press_en = 1;
|
||||||
|
pwr_ctrl_.bit.temp_en = 1;
|
||||||
|
// Disable pressure if no sensor defined
|
||||||
|
// keep temperature enabled since it's needed for compensation
|
||||||
|
if (this->pressure_sensor_ == nullptr) {
|
||||||
|
pwr_ctrl_.bit.press_en = 0;
|
||||||
|
this->pressure_oversampling_ = OVERSAMPLING_NONE;
|
||||||
|
}
|
||||||
|
// just disable oeversampling for temp if not used
|
||||||
|
if (this->temperature_sensor_ == nullptr) {
|
||||||
|
this->temperature_oversampling_ = OVERSAMPLING_NONE;
|
||||||
|
}
|
||||||
|
// Initialise the BMP388 oversampling register
|
||||||
|
if (!set_oversampling_register(this->pressure_oversampling_, this->temperature_oversampling_)) {
|
||||||
|
ESP_LOGE(TAG, "Failed to set oversampling register");
|
||||||
|
this->error_code_ = ERROR_COMMUNICATION_FAILED;
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BMP3XXComponent::dump_config() {
|
||||||
|
ESP_LOGCONFIG(TAG, "BMP3XX:");
|
||||||
|
ESP_LOGCONFIG(TAG, " Type: %s (0x%X)", LOG_STR_ARG(chip_type_to_str(this->chip_id_.reg)), this->chip_id_.reg);
|
||||||
|
LOG_I2C_DEVICE(this);
|
||||||
|
switch (this->error_code_) {
|
||||||
|
case NONE:
|
||||||
|
break;
|
||||||
|
case ERROR_COMMUNICATION_FAILED:
|
||||||
|
ESP_LOGE(TAG, "Communication with BMP3XX failed!");
|
||||||
|
break;
|
||||||
|
case ERROR_WRONG_CHIP_ID:
|
||||||
|
ESP_LOGE(
|
||||||
|
TAG,
|
||||||
|
"BMP3XX has wrong chip ID (reported id: 0x%X) - please check if you are really using a BMP 388 or BMP 390",
|
||||||
|
this->chip_id_.reg);
|
||||||
|
break;
|
||||||
|
case ERROR_SENSOR_RESET:
|
||||||
|
ESP_LOGE(TAG, "BMP3XX failed to reset");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ESP_LOGE(TAG, "BMP3XX error code %d", (int) this->error_code_);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ESP_LOGCONFIG(TAG, " IIR Filter: %s", LOG_STR_ARG(iir_filter_to_str(this->iir_filter_)));
|
||||||
|
LOG_UPDATE_INTERVAL(this);
|
||||||
|
if (this->temperature_sensor_) {
|
||||||
|
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
|
||||||
|
ESP_LOGCONFIG(TAG, " Oversampling: %s", LOG_STR_ARG(oversampling_to_str(this->temperature_oversampling_)));
|
||||||
|
}
|
||||||
|
if (this->pressure_sensor_) {
|
||||||
|
LOG_SENSOR(" ", "Pressure", this->pressure_sensor_);
|
||||||
|
ESP_LOGCONFIG(TAG, " Oversampling: %s", LOG_STR_ARG(oversampling_to_str(this->pressure_oversampling_)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
float BMP3XXComponent::get_setup_priority() const { return setup_priority::DATA; }
|
||||||
|
|
||||||
|
inline uint8_t oversampling_to_time(Oversampling over_sampling) { return (1 << uint8_t(over_sampling)); }
|
||||||
|
|
||||||
|
void BMP3XXComponent::update() {
|
||||||
|
// Enable sensor
|
||||||
|
ESP_LOGV(TAG, "Sending conversion request...");
|
||||||
|
float meas_time = 1.0f;
|
||||||
|
// Ref: https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp390-ds002.pdf 3.9.2
|
||||||
|
meas_time += 2.02f * oversampling_to_time(this->temperature_oversampling_) + 0.163f;
|
||||||
|
meas_time += 2.02f * oversampling_to_time(this->pressure_oversampling_) + 0.392f;
|
||||||
|
meas_time += 0.234f;
|
||||||
|
if (!set_mode(FORCED_MODE)) {
|
||||||
|
ESP_LOGE(TAG, "Failed start forced mode");
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGVV(TAG, "measurement time %d", uint32_t(ceilf(meas_time)));
|
||||||
|
this->set_timeout("data", uint32_t(ceilf(meas_time)), [this]() {
|
||||||
|
float temperature = 0.0f;
|
||||||
|
float pressure = 0.0f;
|
||||||
|
if (this->pressure_sensor_ != nullptr) {
|
||||||
|
if (!get_measurements(temperature, pressure)) {
|
||||||
|
ESP_LOGW(TAG, "Failed to read pressure and temperature - skipping update");
|
||||||
|
this->status_set_warning();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ESP_LOGD(TAG, "Got temperature=%.1f°C pressure=%.1fhPa", temperature, pressure);
|
||||||
|
} else {
|
||||||
|
if (!get_temperature(temperature)) {
|
||||||
|
ESP_LOGW(TAG, "Failed to read temperature - skipping update");
|
||||||
|
this->status_set_warning();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ESP_LOGD(TAG, "Got temperature=%.1f°C", temperature);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->temperature_sensor_ != nullptr)
|
||||||
|
this->temperature_sensor_->publish_state(temperature);
|
||||||
|
if (this->pressure_sensor_ != nullptr)
|
||||||
|
this->pressure_sensor_->publish_state(pressure);
|
||||||
|
this->status_clear_warning();
|
||||||
|
set_mode(SLEEP_MODE);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset the BMP3XX
|
||||||
|
uint8_t BMP3XXComponent::reset() {
|
||||||
|
write_byte(BMP388_CMD, RESET_CODE); // Write the reset code to the command register
|
||||||
|
// Wait for 10ms
|
||||||
|
delay(10);
|
||||||
|
this->read_byte(BMP388_EVENT, &event_.reg); // Read the BMP388's event register
|
||||||
|
return event_.bit.por_detected; // Return if device reset is complete
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start a one shot measurement in FORCED_MODE
|
||||||
|
bool BMP3XXComponent::start_forced_conversion() {
|
||||||
|
// Only set FORCED_MODE if we're already in SLEEP_MODE
|
||||||
|
if (pwr_ctrl_.bit.mode == SLEEP_MODE) {
|
||||||
|
return set_mode(FORCED_MODE);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop the conversion and return to SLEEP_MODE
|
||||||
|
bool BMP3XXComponent::stop_conversion() { return set_mode(SLEEP_MODE); }
|
||||||
|
|
||||||
|
// Set the pressure oversampling rate
|
||||||
|
bool BMP3XXComponent::set_pressure_oversampling(Oversampling oversampling) {
|
||||||
|
osr_.bit.osr_p = oversampling;
|
||||||
|
return this->write_byte(BMP388_OSR, osr_.reg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the temperature oversampling rate
|
||||||
|
bool BMP3XXComponent::set_temperature_oversampling(Oversampling oversampling) {
|
||||||
|
osr_.bit.osr_t = oversampling;
|
||||||
|
return this->write_byte(BMP388_OSR, osr_.reg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the IIR filter setting
|
||||||
|
bool BMP3XXComponent::set_iir_filter(IIRFilter iir_filter) {
|
||||||
|
config_.bit.iir_filter = iir_filter;
|
||||||
|
return this->write_byte(BMP388_CONFIG, config_.reg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get temperature
|
||||||
|
bool BMP3XXComponent::get_temperature(float &temperature) {
|
||||||
|
// Check if a measurement is ready
|
||||||
|
if (!data_ready()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
uint8_t data[3];
|
||||||
|
// Read the temperature
|
||||||
|
if (!this->read_bytes(BMP388_DATA_3, &data[0], 3)) {
|
||||||
|
ESP_LOGE(TAG, "Failed to read temperature");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Copy the temperature data into the adc variables
|
||||||
|
int32_t adc_temp = (int32_t) data[2] << 16 | (int32_t) data[1] << 8 | (int32_t) data[0];
|
||||||
|
// Temperature compensation (function from BMP388 datasheet)
|
||||||
|
temperature = bmp388_compensate_temperature_((float) adc_temp);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the pressure
|
||||||
|
bool BMP3XXComponent::get_pressure(float &pressure) {
|
||||||
|
float temperature;
|
||||||
|
return get_measurements(temperature, pressure);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get temperature and pressure
|
||||||
|
bool BMP3XXComponent::get_measurements(float &temperature, float &pressure) {
|
||||||
|
// Check if a measurement is ready
|
||||||
|
if (!data_ready()) {
|
||||||
|
ESP_LOGD(TAG, "BMP3XX Get measurement - data not ready skipping update");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t data[6];
|
||||||
|
// Read the temperature and pressure data
|
||||||
|
if (!this->read_bytes(BMP388_DATA_0, &data[0], 6)) {
|
||||||
|
ESP_LOGE(TAG, "Failed to read measurements");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Copy the temperature and pressure data into the adc variables
|
||||||
|
int32_t adc_pres = (int32_t) data[2] << 16 | (int32_t) data[1] << 8 | (int32_t) data[0];
|
||||||
|
int32_t adc_temp = (int32_t) data[5] << 16 | (int32_t) data[4] << 8 | (int32_t) data[3];
|
||||||
|
|
||||||
|
// Temperature compensation (function from BMP388 datasheet)
|
||||||
|
temperature = bmp388_compensate_temperature_((float) adc_temp);
|
||||||
|
// Pressure compensation (function from BMP388 datasheet)
|
||||||
|
pressure = bmp388_compensate_pressure_((float) adc_pres, temperature);
|
||||||
|
// Calculate the pressure in millibar/hPa
|
||||||
|
pressure /= 100.0f;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the BMP388's mode in the power control register
|
||||||
|
bool BMP3XXComponent::set_mode(OperationMode mode) {
|
||||||
|
pwr_ctrl_.bit.mode = mode;
|
||||||
|
return this->write_byte(BMP388_PWR_CTRL, pwr_ctrl_.reg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the BMP388 oversampling register
|
||||||
|
bool BMP3XXComponent::set_oversampling_register(Oversampling pressure_oversampling,
|
||||||
|
Oversampling temperature_oversampling) {
|
||||||
|
osr_.reg = temperature_oversampling << 3 | pressure_oversampling;
|
||||||
|
return this->write_byte(BMP388_OSR, osr_.reg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if measurement data is ready
|
||||||
|
bool BMP3XXComponent::data_ready() {
|
||||||
|
// If we're in SLEEP_MODE return immediately
|
||||||
|
if (pwr_ctrl_.bit.mode == SLEEP_MODE) {
|
||||||
|
ESP_LOGD(TAG, "Not ready - sensor is in sleep mode");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Read the interrupt status register
|
||||||
|
uint8_t status;
|
||||||
|
if (!this->read_byte(BMP388_INT_STATUS, &status)) {
|
||||||
|
ESP_LOGE(TAG, "Failed to read status register");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int_status_.reg = status;
|
||||||
|
ESP_LOGVV(TAG, "data ready status %d", status);
|
||||||
|
// If we're in FORCED_MODE switch back to SLEEP_MODE
|
||||||
|
if (int_status_.bit.drdy) {
|
||||||
|
if (pwr_ctrl_.bit.mode == FORCED_MODE) {
|
||||||
|
pwr_ctrl_.bit.mode = SLEEP_MODE;
|
||||||
|
}
|
||||||
|
return true; // The measurement is ready
|
||||||
|
}
|
||||||
|
return false; // The measurement is still pending
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Bosch BMP3XXComponent (Private) Member Functions
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
float BMP3XXComponent::bmp388_compensate_temperature_(float uncomp_temp) {
|
||||||
|
float partial_data1 = uncomp_temp - compensation_float_params_.param_T1;
|
||||||
|
float partial_data2 = partial_data1 * compensation_float_params_.param_T2;
|
||||||
|
return partial_data2 + partial_data1 * partial_data1 * compensation_float_params_.param_T3;
|
||||||
|
}
|
||||||
|
|
||||||
|
float BMP3XXComponent::bmp388_compensate_pressure_(float uncomp_press, float t_lin) {
|
||||||
|
float partial_data1 = compensation_float_params_.param_P6 * t_lin;
|
||||||
|
float partial_data2 = compensation_float_params_.param_P7 * t_lin * t_lin;
|
||||||
|
float partial_data3 = compensation_float_params_.param_P8 * t_lin * t_lin * t_lin;
|
||||||
|
float partial_out1 = compensation_float_params_.param_P5 + partial_data1 + partial_data2 + partial_data3;
|
||||||
|
partial_data1 = compensation_float_params_.param_P2 * t_lin;
|
||||||
|
partial_data2 = compensation_float_params_.param_P3 * t_lin * t_lin;
|
||||||
|
partial_data3 = compensation_float_params_.param_P4 * t_lin * t_lin * t_lin;
|
||||||
|
float partial_out2 =
|
||||||
|
uncomp_press * (compensation_float_params_.param_P1 + partial_data1 + partial_data2 + partial_data3);
|
||||||
|
partial_data1 = uncomp_press * uncomp_press;
|
||||||
|
partial_data2 = compensation_float_params_.param_P9 + compensation_float_params_.param_P10 * t_lin;
|
||||||
|
partial_data3 = partial_data1 * partial_data2;
|
||||||
|
float partial_data4 =
|
||||||
|
partial_data3 + uncomp_press * uncomp_press * uncomp_press * compensation_float_params_.param_P11;
|
||||||
|
return partial_out1 + partial_out2 + partial_data4;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace bmp3xx
|
||||||
|
} // namespace esphome
|
237
esphome/components/bmp3xx/bmp3xx.h
Normal file
237
esphome/components/bmp3xx/bmp3xx.h
Normal file
|
@ -0,0 +1,237 @@
|
||||||
|
/*
|
||||||
|
based on BMP388_DEV by Martin Lindupp
|
||||||
|
under MIT License (MIT)
|
||||||
|
Copyright (C) Martin Lindupp 2020
|
||||||
|
http://github.com/MartinL1/BMP388_DEV
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
#include "esphome/components/i2c/i2c.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace bmp3xx {
|
||||||
|
|
||||||
|
static const uint8_t BMP388_ID = 0x50; // The BMP388 device ID
|
||||||
|
static const uint8_t BMP390_ID = 0x60; // The BMP390 device ID
|
||||||
|
static const uint8_t RESET_CODE = 0xB6; // The BMP388 reset code
|
||||||
|
|
||||||
|
/// BMP388_DEV Registers
|
||||||
|
enum {
|
||||||
|
BMP388_CHIP_ID = 0x00, // Chip ID register sub-address
|
||||||
|
BMP388_ERR_REG = 0x02, // Error register sub-address
|
||||||
|
BMP388_STATUS = 0x03, // Status register sub-address
|
||||||
|
BMP388_DATA_0 = 0x04, // Pressure eXtended Least Significant Byte (XLSB) register sub-address
|
||||||
|
BMP388_DATA_1 = 0x05, // Pressure Least Significant Byte (LSB) register sub-address
|
||||||
|
BMP388_DATA_2 = 0x06, // Pressure Most Significant Byte (MSB) register sub-address
|
||||||
|
BMP388_DATA_3 = 0x07, // Temperature eXtended Least Significant Byte (XLSB) register sub-address
|
||||||
|
BMP388_DATA_4 = 0x08, // Temperature Least Significant Byte (LSB) register sub-address
|
||||||
|
BMP388_DATA_5 = 0x09, // Temperature Most Significant Byte (MSB) register sub-address
|
||||||
|
BMP388_SENSORTIME_0 = 0x0C, // Sensor time register 0 sub-address
|
||||||
|
BMP388_SENSORTIME_1 = 0x0D, // Sensor time register 1 sub-address
|
||||||
|
BMP388_SENSORTIME_2 = 0x0E, // Sensor time register 2 sub-address
|
||||||
|
BMP388_EVENT = 0x10, // Event register sub-address
|
||||||
|
BMP388_INT_STATUS = 0x11, // Interrupt Status register sub-address
|
||||||
|
BMP388_INT_CTRL = 0x19, // Interrupt Control register sub-address
|
||||||
|
BMP388_IF_CONFIG = 0x1A, // Interface Configuration register sub-address
|
||||||
|
BMP388_PWR_CTRL = 0x1B, // Power Control register sub-address
|
||||||
|
BMP388_OSR = 0x1C, // Oversampling register sub-address
|
||||||
|
BMP388_ODR = 0x1D, // Output Data Rate register sub-address
|
||||||
|
BMP388_CONFIG = 0x1F, // Configuration register sub-address
|
||||||
|
BMP388_TRIM_PARAMS = 0x31, // Trim parameter registers' base sub-address
|
||||||
|
BMP388_CMD = 0x7E // Command register sub-address
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Device mode bitfield in the control and measurement register
|
||||||
|
enum OperationMode { SLEEP_MODE = 0x00, FORCED_MODE = 0x01, NORMAL_MODE = 0x03 };
|
||||||
|
|
||||||
|
/// Oversampling bit fields in the control and measurement register
|
||||||
|
enum Oversampling {
|
||||||
|
OVERSAMPLING_NONE = 0x00,
|
||||||
|
OVERSAMPLING_X2 = 0x01,
|
||||||
|
OVERSAMPLING_X4 = 0x02,
|
||||||
|
OVERSAMPLING_X8 = 0x03,
|
||||||
|
OVERSAMPLING_X16 = 0x04,
|
||||||
|
OVERSAMPLING_X32 = 0x05
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Infinite Impulse Response (IIR) filter bit field in the configuration register
|
||||||
|
enum IIRFilter {
|
||||||
|
IIR_FILTER_OFF = 0x00,
|
||||||
|
IIR_FILTER_2 = 0x01,
|
||||||
|
IIR_FILTER_4 = 0x02,
|
||||||
|
IIR_FILTER_8 = 0x03,
|
||||||
|
IIR_FILTER_16 = 0x04,
|
||||||
|
IIR_FILTER_32 = 0x05,
|
||||||
|
IIR_FILTER_64 = 0x06,
|
||||||
|
IIR_FILTER_128 = 0x07
|
||||||
|
};
|
||||||
|
|
||||||
|
/// This class implements support for the BMP3XX Temperature+Pressure i2c sensor.
|
||||||
|
class BMP3XXComponent : 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; }
|
||||||
|
|
||||||
|
/// Set the oversampling value for the temperature sensor. Default is 16x.
|
||||||
|
void set_temperature_oversampling_config(Oversampling temperature_oversampling) {
|
||||||
|
this->temperature_oversampling_ = temperature_oversampling;
|
||||||
|
}
|
||||||
|
/// Set the oversampling value for the pressure sensor. Default is 16x.
|
||||||
|
void set_pressure_oversampling_config(Oversampling pressure_oversampling) {
|
||||||
|
this->pressure_oversampling_ = pressure_oversampling;
|
||||||
|
}
|
||||||
|
/// Set the IIR Filter used to increase accuracy, defaults to no IIR Filter.
|
||||||
|
void set_iir_filter_config(IIRFilter iir_filter) { this->iir_filter_ = iir_filter; }
|
||||||
|
|
||||||
|
/// Soft reset the sensor
|
||||||
|
uint8_t reset();
|
||||||
|
/// Start continuous measurement in NORMAL_MODE
|
||||||
|
bool start_normal_conversion();
|
||||||
|
/// Start a one shot measurement in FORCED_MODE
|
||||||
|
bool start_forced_conversion();
|
||||||
|
/// Stop the conversion and return to SLEEP_MODE
|
||||||
|
bool stop_conversion();
|
||||||
|
/// Set the pressure oversampling: OFF, X1, X2, X4, X8, X16, X32
|
||||||
|
bool set_pressure_oversampling(Oversampling pressure_oversampling);
|
||||||
|
/// Set the temperature oversampling: OFF, X1, X2, X4, X8, X16, X32
|
||||||
|
bool set_temperature_oversampling(Oversampling temperature_oversampling);
|
||||||
|
/// Set the IIR filter setting: OFF, 2, 3, 8, 16, 32
|
||||||
|
bool set_iir_filter(IIRFilter iir_filter);
|
||||||
|
/// Get a temperature measurement
|
||||||
|
bool get_temperature(float &temperature);
|
||||||
|
/// Get a pressure measurement
|
||||||
|
bool get_pressure(float &pressure);
|
||||||
|
/// Get a temperature and pressure measurement
|
||||||
|
bool get_measurements(float &temperature, float &pressure);
|
||||||
|
/// Get a temperature and pressure measurement
|
||||||
|
bool get_measurement();
|
||||||
|
/// Set the barometer mode
|
||||||
|
bool set_mode(OperationMode mode);
|
||||||
|
/// Set the BMP388 oversampling register
|
||||||
|
bool set_oversampling_register(Oversampling pressure_oversampling, Oversampling temperature_oversampling);
|
||||||
|
/// Checks if a measurement is ready
|
||||||
|
bool data_ready();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Oversampling temperature_oversampling_{OVERSAMPLING_X16};
|
||||||
|
Oversampling pressure_oversampling_{OVERSAMPLING_X16};
|
||||||
|
IIRFilter iir_filter_{IIR_FILTER_OFF};
|
||||||
|
OperationMode operation_mode_{FORCED_MODE};
|
||||||
|
sensor::Sensor *temperature_sensor_;
|
||||||
|
sensor::Sensor *pressure_sensor_;
|
||||||
|
enum ErrorCode {
|
||||||
|
NONE = 0,
|
||||||
|
ERROR_COMMUNICATION_FAILED,
|
||||||
|
ERROR_WRONG_CHIP_ID,
|
||||||
|
ERROR_SENSOR_STATUS,
|
||||||
|
ERROR_SENSOR_RESET,
|
||||||
|
} error_code_{NONE};
|
||||||
|
|
||||||
|
struct { // The BMP388 compensation trim parameters (coefficients)
|
||||||
|
uint16_t param_T1;
|
||||||
|
uint16_t param_T2;
|
||||||
|
int8_t param_T3;
|
||||||
|
int16_t param_P1;
|
||||||
|
int16_t param_P2;
|
||||||
|
int8_t param_P3;
|
||||||
|
int8_t param_P4;
|
||||||
|
uint16_t param_P5;
|
||||||
|
uint16_t param_P6;
|
||||||
|
int8_t param_P7;
|
||||||
|
int8_t param_P8;
|
||||||
|
int16_t param_P9;
|
||||||
|
int8_t param_P10;
|
||||||
|
int8_t param_P11;
|
||||||
|
} __attribute__((packed)) compensation_params_;
|
||||||
|
|
||||||
|
struct FloatParams { // The BMP388 float point compensation trim parameters
|
||||||
|
float param_T1;
|
||||||
|
float param_T2;
|
||||||
|
float param_T3;
|
||||||
|
float param_P1;
|
||||||
|
float param_P2;
|
||||||
|
float param_P3;
|
||||||
|
float param_P4;
|
||||||
|
float param_P5;
|
||||||
|
float param_P6;
|
||||||
|
float param_P7;
|
||||||
|
float param_P8;
|
||||||
|
float param_P9;
|
||||||
|
float param_P10;
|
||||||
|
float param_P11;
|
||||||
|
} compensation_float_params_;
|
||||||
|
|
||||||
|
union { // Copy of the BMP388's chip id register
|
||||||
|
struct {
|
||||||
|
uint8_t chip_id_nvm : 4;
|
||||||
|
uint8_t chip_id_fixed : 4;
|
||||||
|
} bit;
|
||||||
|
uint8_t reg;
|
||||||
|
} chip_id_ = {.reg = 0};
|
||||||
|
|
||||||
|
union { // Copy of the BMP388's event register
|
||||||
|
struct {
|
||||||
|
uint8_t por_detected : 1;
|
||||||
|
} bit;
|
||||||
|
uint8_t reg;
|
||||||
|
} event_ = {.reg = 0};
|
||||||
|
|
||||||
|
union { // Copy of the BMP388's interrupt status register
|
||||||
|
struct {
|
||||||
|
uint8_t fwm_int : 1;
|
||||||
|
uint8_t ffull_int : 1;
|
||||||
|
uint8_t : 1;
|
||||||
|
uint8_t drdy : 1;
|
||||||
|
} bit;
|
||||||
|
uint8_t reg;
|
||||||
|
} int_status_ = {.reg = 0};
|
||||||
|
|
||||||
|
union { // Copy of the BMP388's power control register
|
||||||
|
struct {
|
||||||
|
uint8_t press_en : 1;
|
||||||
|
uint8_t temp_en : 1;
|
||||||
|
uint8_t : 2;
|
||||||
|
uint8_t mode : 2;
|
||||||
|
} bit;
|
||||||
|
uint8_t reg;
|
||||||
|
} pwr_ctrl_ = {.reg = 0};
|
||||||
|
|
||||||
|
union { // Copy of the BMP388's oversampling register
|
||||||
|
struct {
|
||||||
|
uint8_t osr_p : 3;
|
||||||
|
uint8_t osr_t : 3;
|
||||||
|
} bit;
|
||||||
|
uint8_t reg;
|
||||||
|
} osr_ = {.reg = 0};
|
||||||
|
|
||||||
|
union { // Copy of the BMP388's output data rate register
|
||||||
|
struct {
|
||||||
|
uint8_t odr_sel : 5;
|
||||||
|
} bit;
|
||||||
|
uint8_t reg;
|
||||||
|
} odr_ = {.reg = 0};
|
||||||
|
|
||||||
|
union { // Copy of the BMP388's configuration register
|
||||||
|
struct {
|
||||||
|
uint8_t : 1;
|
||||||
|
uint8_t iir_filter : 3;
|
||||||
|
} bit;
|
||||||
|
uint8_t reg;
|
||||||
|
} config_ = {.reg = 0};
|
||||||
|
|
||||||
|
// Bosch temperature compensation function
|
||||||
|
float bmp388_compensate_temperature_(float uncomp_temp);
|
||||||
|
// Bosch pressure compensation function
|
||||||
|
float bmp388_compensate_pressure_(float uncomp_press, float t_lin);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace bmp3xx
|
||||||
|
} // namespace esphome
|
100
esphome/components/bmp3xx/sensor.py
Normal file
100
esphome/components/bmp3xx/sensor.py
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import i2c, sensor
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_ID,
|
||||||
|
CONF_IIR_FILTER,
|
||||||
|
CONF_OVERSAMPLING,
|
||||||
|
CONF_PRESSURE,
|
||||||
|
CONF_TEMPERATURE,
|
||||||
|
DEVICE_CLASS_PRESSURE,
|
||||||
|
DEVICE_CLASS_TEMPERATURE,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
UNIT_CELSIUS,
|
||||||
|
UNIT_HECTOPASCAL,
|
||||||
|
)
|
||||||
|
|
||||||
|
CODEOWNERS = ["@martgras"]
|
||||||
|
DEPENDENCIES = ["i2c"]
|
||||||
|
|
||||||
|
bmp3xx_ns = cg.esphome_ns.namespace("bmp3xx")
|
||||||
|
Oversampling = bmp3xx_ns.enum("Oversampling")
|
||||||
|
OVERSAMPLING_OPTIONS = {
|
||||||
|
"NONE": Oversampling.OVERSAMPLING_NONE,
|
||||||
|
"2X": Oversampling.OVERSAMPLING_X2,
|
||||||
|
"4X": Oversampling.OVERSAMPLING_X4,
|
||||||
|
"8X": Oversampling.OVERSAMPLING_X8,
|
||||||
|
"16X": Oversampling.OVERSAMPLING_X16,
|
||||||
|
"32x": Oversampling.OVERSAMPLING_X32,
|
||||||
|
}
|
||||||
|
|
||||||
|
IIRFilter = bmp3xx_ns.enum("IIRFilter")
|
||||||
|
IIR_FILTER_OPTIONS = {
|
||||||
|
"OFF": IIRFilter.IIR_FILTER_OFF,
|
||||||
|
"2X": IIRFilter.IIR_FILTER_2,
|
||||||
|
"4X": IIRFilter.IIR_FILTER_4,
|
||||||
|
"8X": IIRFilter.IIR_FILTER_8,
|
||||||
|
"16X": IIRFilter.IIR_FILTER_16,
|
||||||
|
"32X": IIRFilter.IIR_FILTER_32,
|
||||||
|
"64X": IIRFilter.IIR_FILTER_64,
|
||||||
|
"128X": IIRFilter.IIR_FILTER_128,
|
||||||
|
}
|
||||||
|
|
||||||
|
BMP3XXComponent = bmp3xx_ns.class_(
|
||||||
|
"BMP3XXComponent", cg.PollingComponent, i2c.I2CDevice
|
||||||
|
)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = (
|
||||||
|
cv.Schema(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(BMP3XXComponent),
|
||||||
|
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.Optional(CONF_OVERSAMPLING, default="2X"): cv.enum(
|
||||||
|
OVERSAMPLING_OPTIONS, upper=True
|
||||||
|
),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_HECTOPASCAL,
|
||||||
|
accuracy_decimals=1,
|
||||||
|
device_class=DEVICE_CLASS_PRESSURE,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
).extend(
|
||||||
|
{
|
||||||
|
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
|
||||||
|
OVERSAMPLING_OPTIONS, upper=True
|
||||||
|
),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_IIR_FILTER, default="OFF"): cv.enum(
|
||||||
|
IIR_FILTER_OPTIONS, upper=True
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.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)
|
||||||
|
cg.add(var.set_iir_filter_config(config[CONF_IIR_FILTER]))
|
||||||
|
if CONF_TEMPERATURE in config:
|
||||||
|
conf = config[CONF_TEMPERATURE]
|
||||||
|
sens = await sensor.new_sensor(conf)
|
||||||
|
cg.add(var.set_temperature_sensor(sens))
|
||||||
|
cg.add(var.set_temperature_oversampling_config(conf[CONF_OVERSAMPLING]))
|
||||||
|
|
||||||
|
if CONF_PRESSURE in config:
|
||||||
|
conf = config[CONF_PRESSURE]
|
||||||
|
sens = await sensor.new_sensor(conf)
|
||||||
|
cg.add(var.set_pressure_sensor(sens))
|
||||||
|
cg.add(var.set_pressure_oversampling_config(conf[CONF_OVERSAMPLING]))
|
53
esphome/components/cd74hc4067/__init__.py
Normal file
53
esphome/components/cd74hc4067/__init__.py
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
from esphome import pins
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_DELAY,
|
||||||
|
CONF_ID,
|
||||||
|
)
|
||||||
|
|
||||||
|
CODEOWNERS = ["@asoehlke"]
|
||||||
|
AUTO_LOAD = ["sensor", "voltage_sampler"]
|
||||||
|
|
||||||
|
cd74hc4067_ns = cg.esphome_ns.namespace("cd74hc4067")
|
||||||
|
|
||||||
|
CD74HC4067Component = cd74hc4067_ns.class_(
|
||||||
|
"CD74HC4067Component", cg.Component, cg.PollingComponent
|
||||||
|
)
|
||||||
|
|
||||||
|
CONF_PIN_S0 = "pin_s0"
|
||||||
|
CONF_PIN_S1 = "pin_s1"
|
||||||
|
CONF_PIN_S2 = "pin_s2"
|
||||||
|
CONF_PIN_S3 = "pin_s3"
|
||||||
|
|
||||||
|
DEFAULT_DELAY = "2ms"
|
||||||
|
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = cv.Schema(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(CD74HC4067Component),
|
||||||
|
cv.Required(CONF_PIN_S0): pins.internal_gpio_output_pin_schema,
|
||||||
|
cv.Required(CONF_PIN_S1): pins.internal_gpio_output_pin_schema,
|
||||||
|
cv.Required(CONF_PIN_S2): pins.internal_gpio_output_pin_schema,
|
||||||
|
cv.Required(CONF_PIN_S3): pins.internal_gpio_output_pin_schema,
|
||||||
|
cv.Optional(
|
||||||
|
CONF_DELAY, default=DEFAULT_DELAY
|
||||||
|
): cv.positive_time_period_milliseconds,
|
||||||
|
}
|
||||||
|
).extend(cv.COMPONENT_SCHEMA)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
await cg.register_component(var, config)
|
||||||
|
|
||||||
|
pin_s0 = await cg.gpio_pin_expression(config[CONF_PIN_S0])
|
||||||
|
cg.add(var.set_pin_s0(pin_s0))
|
||||||
|
pin_s1 = await cg.gpio_pin_expression(config[CONF_PIN_S1])
|
||||||
|
cg.add(var.set_pin_s1(pin_s1))
|
||||||
|
pin_s2 = await cg.gpio_pin_expression(config[CONF_PIN_S2])
|
||||||
|
cg.add(var.set_pin_s2(pin_s2))
|
||||||
|
pin_s3 = await cg.gpio_pin_expression(config[CONF_PIN_S3])
|
||||||
|
cg.add(var.set_pin_s3(pin_s3))
|
||||||
|
|
||||||
|
cg.add(var.set_switch_delay(config[CONF_DELAY]))
|
86
esphome/components/cd74hc4067/cd74hc4067.cpp
Normal file
86
esphome/components/cd74hc4067/cd74hc4067.cpp
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
#include "cd74hc4067.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace cd74hc4067 {
|
||||||
|
|
||||||
|
static const char *const TAG = "cd74hc4067";
|
||||||
|
|
||||||
|
float CD74HC4067Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||||
|
|
||||||
|
void CD74HC4067Component::setup() {
|
||||||
|
ESP_LOGCONFIG(TAG, "Setting up CD74HC4067...");
|
||||||
|
|
||||||
|
this->pin_s0_->setup();
|
||||||
|
this->pin_s1_->setup();
|
||||||
|
this->pin_s2_->setup();
|
||||||
|
this->pin_s3_->setup();
|
||||||
|
|
||||||
|
// set other pin, so that activate_pin will really switch
|
||||||
|
this->active_pin_ = 1;
|
||||||
|
this->activate_pin(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CD74HC4067Component::dump_config() {
|
||||||
|
ESP_LOGCONFIG(TAG, "CD74HC4067 Multiplexer:");
|
||||||
|
LOG_PIN(" S0 Pin: ", this->pin_s0_);
|
||||||
|
LOG_PIN(" S1 Pin: ", this->pin_s1_);
|
||||||
|
LOG_PIN(" S2 Pin: ", this->pin_s2_);
|
||||||
|
LOG_PIN(" S3 Pin: ", this->pin_s3_);
|
||||||
|
ESP_LOGCONFIG(TAG, "switch delay: %d", this->switch_delay_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CD74HC4067Component::activate_pin(uint8_t pin) {
|
||||||
|
if (this->active_pin_ != pin) {
|
||||||
|
ESP_LOGD(TAG, "switch to input %d", pin);
|
||||||
|
|
||||||
|
static int mux_channel[16][4] = {
|
||||||
|
{0, 0, 0, 0}, // channel 0
|
||||||
|
{1, 0, 0, 0}, // channel 1
|
||||||
|
{0, 1, 0, 0}, // channel 2
|
||||||
|
{1, 1, 0, 0}, // channel 3
|
||||||
|
{0, 0, 1, 0}, // channel 4
|
||||||
|
{1, 0, 1, 0}, // channel 5
|
||||||
|
{0, 1, 1, 0}, // channel 6
|
||||||
|
{1, 1, 1, 0}, // channel 7
|
||||||
|
{0, 0, 0, 1}, // channel 8
|
||||||
|
{1, 0, 0, 1}, // channel 9
|
||||||
|
{0, 1, 0, 1}, // channel 10
|
||||||
|
{1, 1, 0, 1}, // channel 11
|
||||||
|
{0, 0, 1, 1}, // channel 12
|
||||||
|
{1, 0, 1, 1}, // channel 13
|
||||||
|
{0, 1, 1, 1}, // channel 14
|
||||||
|
{1, 1, 1, 1} // channel 15
|
||||||
|
};
|
||||||
|
this->pin_s0_->digital_write(mux_channel[pin][0]);
|
||||||
|
this->pin_s1_->digital_write(mux_channel[pin][1]);
|
||||||
|
this->pin_s2_->digital_write(mux_channel[pin][2]);
|
||||||
|
this->pin_s3_->digital_write(mux_channel[pin][3]);
|
||||||
|
// small delay is needed to let the multiplexer switch
|
||||||
|
delay(this->switch_delay_);
|
||||||
|
this->active_pin_ = pin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CD74HC4067Sensor::CD74HC4067Sensor(CD74HC4067Component *parent) : parent_(parent) {}
|
||||||
|
|
||||||
|
void CD74HC4067Sensor::update() {
|
||||||
|
float value_v = this->sample();
|
||||||
|
this->publish_state(value_v);
|
||||||
|
}
|
||||||
|
|
||||||
|
float CD74HC4067Sensor::get_setup_priority() const { return this->parent_->get_setup_priority() - 1.0f; }
|
||||||
|
|
||||||
|
float CD74HC4067Sensor::sample() {
|
||||||
|
this->parent_->activate_pin(this->pin_);
|
||||||
|
return this->source_->sample();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CD74HC4067Sensor::dump_config() {
|
||||||
|
LOG_SENSOR(TAG, "CD74HC4067 Sensor", this);
|
||||||
|
ESP_LOGCONFIG(TAG, " Pin: %u", this->pin_);
|
||||||
|
LOG_UPDATE_INTERVAL(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace cd74hc4067
|
||||||
|
} // namespace esphome
|
65
esphome/components/cd74hc4067/cd74hc4067.h
Normal file
65
esphome/components/cd74hc4067/cd74hc4067.h
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/core/hal.h"
|
||||||
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
#include "esphome/components/voltage_sampler/voltage_sampler.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace cd74hc4067 {
|
||||||
|
|
||||||
|
class CD74HC4067Component : public Component {
|
||||||
|
public:
|
||||||
|
/// Set up the internal sensor array.
|
||||||
|
void setup() override;
|
||||||
|
void dump_config() override;
|
||||||
|
float get_setup_priority() const override;
|
||||||
|
|
||||||
|
/// setting pin active by setting the right combination of the four multiplexer input pins
|
||||||
|
void activate_pin(uint8_t pin);
|
||||||
|
|
||||||
|
/// set the pin connected to multiplexer control pin 0
|
||||||
|
void set_pin_s0(InternalGPIOPin *pin) { this->pin_s0_ = pin; }
|
||||||
|
/// set the pin connected to multiplexer control pin 1
|
||||||
|
void set_pin_s1(InternalGPIOPin *pin) { this->pin_s1_ = pin; }
|
||||||
|
/// set the pin connected to multiplexer control pin 2
|
||||||
|
void set_pin_s2(InternalGPIOPin *pin) { this->pin_s2_ = pin; }
|
||||||
|
/// set the pin connected to multiplexer control pin 3
|
||||||
|
void set_pin_s3(InternalGPIOPin *pin) { this->pin_s3_ = pin; }
|
||||||
|
|
||||||
|
/// set the delay needed after an input switch
|
||||||
|
void set_switch_delay(uint32_t switch_delay) { this->switch_delay_ = switch_delay; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
InternalGPIOPin *pin_s0_;
|
||||||
|
InternalGPIOPin *pin_s1_;
|
||||||
|
InternalGPIOPin *pin_s2_;
|
||||||
|
InternalGPIOPin *pin_s3_;
|
||||||
|
/// the currently active pin
|
||||||
|
uint8_t active_pin_;
|
||||||
|
uint32_t switch_delay_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CD74HC4067Sensor : public sensor::Sensor, public PollingComponent, public voltage_sampler::VoltageSampler {
|
||||||
|
public:
|
||||||
|
CD74HC4067Sensor(CD74HC4067Component *parent);
|
||||||
|
|
||||||
|
void update() override;
|
||||||
|
|
||||||
|
void dump_config() override;
|
||||||
|
/// `HARDWARE_LATE` setup priority.
|
||||||
|
float get_setup_priority() const override;
|
||||||
|
void set_pin(uint8_t pin) { this->pin_ = pin; }
|
||||||
|
void set_source(voltage_sampler::VoltageSampler *source) { this->source_ = source; }
|
||||||
|
|
||||||
|
float sample() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
CD74HC4067Component *parent_;
|
||||||
|
/// The sampling source to read values from.
|
||||||
|
voltage_sampler::VoltageSampler *source_;
|
||||||
|
|
||||||
|
uint8_t pin_;
|
||||||
|
};
|
||||||
|
} // namespace cd74hc4067
|
||||||
|
} // namespace esphome
|
55
esphome/components/cd74hc4067/sensor.py
Normal file
55
esphome/components/cd74hc4067/sensor.py
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import sensor, voltage_sampler
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_ID,
|
||||||
|
CONF_SENSOR,
|
||||||
|
CONF_NUMBER,
|
||||||
|
ICON_FLASH,
|
||||||
|
UNIT_VOLT,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
DEVICE_CLASS_VOLTAGE,
|
||||||
|
)
|
||||||
|
from . import cd74hc4067_ns, CD74HC4067Component
|
||||||
|
|
||||||
|
DEPENDENCIES = ["cd74hc4067"]
|
||||||
|
|
||||||
|
CD74HC4067Sensor = cd74hc4067_ns.class_(
|
||||||
|
"CD74HC4067Sensor",
|
||||||
|
sensor.Sensor,
|
||||||
|
cg.PollingComponent,
|
||||||
|
voltage_sampler.VoltageSampler,
|
||||||
|
)
|
||||||
|
|
||||||
|
CONF_CD74HC4067_ID = "cd74hc4067_id"
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = (
|
||||||
|
sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_VOLT,
|
||||||
|
accuracy_decimals=3,
|
||||||
|
device_class=DEVICE_CLASS_VOLTAGE,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
icon=ICON_FLASH,
|
||||||
|
)
|
||||||
|
.extend(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(CD74HC4067Sensor),
|
||||||
|
cv.GenerateID(CONF_CD74HC4067_ID): cv.use_id(CD74HC4067Component),
|
||||||
|
cv.Required(CONF_NUMBER): cv.int_range(0, 15),
|
||||||
|
cv.Required(CONF_SENSOR): cv.use_id(voltage_sampler.VoltageSampler),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(cv.polling_component_schema("60s"))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
parent = await cg.get_variable(config[CONF_CD74HC4067_ID])
|
||||||
|
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID], parent)
|
||||||
|
await cg.register_component(var, config)
|
||||||
|
await sensor.register_sensor(var, config)
|
||||||
|
cg.add(var.set_pin(config[CONF_NUMBER]))
|
||||||
|
|
||||||
|
sens = await cg.get_variable(config[CONF_SENSOR])
|
||||||
|
cg.add(var.set_source(sens))
|
|
@ -10,21 +10,22 @@ climate::ClimateTraits ClimateIR::traits() {
|
||||||
auto traits = climate::ClimateTraits();
|
auto traits = climate::ClimateTraits();
|
||||||
traits.set_supports_current_temperature(this->sensor_ != nullptr);
|
traits.set_supports_current_temperature(this->sensor_ != nullptr);
|
||||||
traits.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_HEAT_COOL});
|
traits.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_HEAT_COOL});
|
||||||
if (supports_cool_)
|
if (this->supports_cool_)
|
||||||
traits.add_supported_mode(climate::CLIMATE_MODE_COOL);
|
traits.add_supported_mode(climate::CLIMATE_MODE_COOL);
|
||||||
if (supports_heat_)
|
if (this->supports_heat_)
|
||||||
traits.add_supported_mode(climate::CLIMATE_MODE_HEAT);
|
traits.add_supported_mode(climate::CLIMATE_MODE_HEAT);
|
||||||
if (supports_dry_)
|
if (this->supports_dry_)
|
||||||
traits.add_supported_mode(climate::CLIMATE_MODE_DRY);
|
traits.add_supported_mode(climate::CLIMATE_MODE_DRY);
|
||||||
if (supports_fan_only_)
|
if (this->supports_fan_only_)
|
||||||
traits.add_supported_mode(climate::CLIMATE_MODE_FAN_ONLY);
|
traits.add_supported_mode(climate::CLIMATE_MODE_FAN_ONLY);
|
||||||
|
|
||||||
traits.set_supports_two_point_target_temperature(false);
|
traits.set_supports_two_point_target_temperature(false);
|
||||||
traits.set_visual_min_temperature(this->minimum_temperature_);
|
traits.set_visual_min_temperature(this->minimum_temperature_);
|
||||||
traits.set_visual_max_temperature(this->maximum_temperature_);
|
traits.set_visual_max_temperature(this->maximum_temperature_);
|
||||||
traits.set_visual_temperature_step(this->temperature_step_);
|
traits.set_visual_temperature_step(this->temperature_step_);
|
||||||
traits.set_supported_fan_modes(fan_modes_);
|
traits.set_supported_fan_modes(this->fan_modes_);
|
||||||
traits.set_supported_swing_modes(swing_modes_);
|
traits.set_supported_swing_modes(this->swing_modes_);
|
||||||
|
traits.set_supported_presets(this->presets_);
|
||||||
return traits;
|
return traits;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,6 +51,7 @@ void ClimateIR::setup() {
|
||||||
roundf(clamp(this->current_temperature, this->minimum_temperature_, this->maximum_temperature_));
|
roundf(clamp(this->current_temperature, this->minimum_temperature_, this->maximum_temperature_));
|
||||||
this->fan_mode = climate::CLIMATE_FAN_AUTO;
|
this->fan_mode = climate::CLIMATE_FAN_AUTO;
|
||||||
this->swing_mode = climate::CLIMATE_SWING_OFF;
|
this->swing_mode = climate::CLIMATE_SWING_OFF;
|
||||||
|
this->preset = climate::CLIMATE_PRESET_NONE;
|
||||||
}
|
}
|
||||||
// Never send nan to HA
|
// Never send nan to HA
|
||||||
if (std::isnan(this->target_temperature))
|
if (std::isnan(this->target_temperature))
|
||||||
|
@ -65,6 +67,8 @@ void ClimateIR::control(const climate::ClimateCall &call) {
|
||||||
this->fan_mode = *call.get_fan_mode();
|
this->fan_mode = *call.get_fan_mode();
|
||||||
if (call.get_swing_mode().has_value())
|
if (call.get_swing_mode().has_value())
|
||||||
this->swing_mode = *call.get_swing_mode();
|
this->swing_mode = *call.get_swing_mode();
|
||||||
|
if (call.get_preset().has_value())
|
||||||
|
this->preset = *call.get_preset();
|
||||||
this->transmit_state();
|
this->transmit_state();
|
||||||
this->publish_state();
|
this->publish_state();
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ class ClimateIR : public climate::Climate, public Component, public remote_base:
|
||||||
public:
|
public:
|
||||||
ClimateIR(float minimum_temperature, float maximum_temperature, float temperature_step = 1.0f,
|
ClimateIR(float minimum_temperature, float maximum_temperature, float temperature_step = 1.0f,
|
||||||
bool supports_dry = false, bool supports_fan_only = false, std::set<climate::ClimateFanMode> fan_modes = {},
|
bool supports_dry = false, bool supports_fan_only = false, std::set<climate::ClimateFanMode> fan_modes = {},
|
||||||
std::set<climate::ClimateSwingMode> swing_modes = {}) {
|
std::set<climate::ClimateSwingMode> swing_modes = {}, std::set<climate::ClimatePreset> presets = {}) {
|
||||||
this->minimum_temperature_ = minimum_temperature;
|
this->minimum_temperature_ = minimum_temperature;
|
||||||
this->maximum_temperature_ = maximum_temperature;
|
this->maximum_temperature_ = maximum_temperature;
|
||||||
this->temperature_step_ = temperature_step;
|
this->temperature_step_ = temperature_step;
|
||||||
|
@ -30,6 +30,7 @@ class ClimateIR : public climate::Climate, public Component, public remote_base:
|
||||||
this->supports_fan_only_ = supports_fan_only;
|
this->supports_fan_only_ = supports_fan_only;
|
||||||
this->fan_modes_ = std::move(fan_modes);
|
this->fan_modes_ = std::move(fan_modes);
|
||||||
this->swing_modes_ = std::move(swing_modes);
|
this->swing_modes_ = std::move(swing_modes);
|
||||||
|
this->presets_ = std::move(presets);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setup() override;
|
void setup() override;
|
||||||
|
@ -61,6 +62,7 @@ class ClimateIR : public climate::Climate, public Component, public remote_base:
|
||||||
bool supports_fan_only_{false};
|
bool supports_fan_only_{false};
|
||||||
std::set<climate::ClimateFanMode> fan_modes_ = {};
|
std::set<climate::ClimateFanMode> fan_modes_ = {};
|
||||||
std::set<climate::ClimateSwingMode> swing_modes_ = {};
|
std::set<climate::ClimateSwingMode> swing_modes_ = {};
|
||||||
|
std::set<climate::ClimatePreset> presets_ = {};
|
||||||
|
|
||||||
remote_transmitter::RemoteTransmitterComponent *transmitter_;
|
remote_transmitter::RemoteTransmitterComponent *transmitter_;
|
||||||
sensor::Sensor *sensor_{nullptr};
|
sensor::Sensor *sensor_{nullptr};
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#include "coolix.h"
|
#include "coolix.h"
|
||||||
|
#include "esphome/components/remote_base/coolix_protocol.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
|
@ -6,29 +7,29 @@ namespace coolix {
|
||||||
|
|
||||||
static const char *const TAG = "coolix.climate";
|
static const char *const TAG = "coolix.climate";
|
||||||
|
|
||||||
const uint32_t COOLIX_OFF = 0xB27BE0;
|
static const uint32_t COOLIX_OFF = 0xB27BE0;
|
||||||
const uint32_t COOLIX_SWING = 0xB26BE0;
|
static const uint32_t COOLIX_SWING = 0xB26BE0;
|
||||||
const uint32_t COOLIX_LED = 0xB5F5A5;
|
static const uint32_t COOLIX_LED = 0xB5F5A5;
|
||||||
const uint32_t COOLIX_SILENCE_FP = 0xB5F5B6;
|
static const uint32_t COOLIX_SILENCE_FP = 0xB5F5B6;
|
||||||
|
|
||||||
// On, 25C, Mode: Auto, Fan: Auto, Zone Follow: Off, Sensor Temp: Ignore.
|
// On, 25C, Mode: Auto, Fan: Auto, Zone Follow: Off, Sensor Temp: Ignore.
|
||||||
const uint8_t COOLIX_COOL = 0b0000;
|
static const uint8_t COOLIX_COOL = 0b0000;
|
||||||
const uint8_t COOLIX_DRY_FAN = 0b0100;
|
static const uint8_t COOLIX_DRY_FAN = 0b0100;
|
||||||
const uint8_t COOLIX_AUTO = 0b1000;
|
static const uint8_t COOLIX_AUTO = 0b1000;
|
||||||
const uint8_t COOLIX_HEAT = 0b1100;
|
static const uint8_t COOLIX_HEAT = 0b1100;
|
||||||
const uint32_t COOLIX_MODE_MASK = 0b1100;
|
static const uint32_t COOLIX_MODE_MASK = 0b1100;
|
||||||
const uint32_t COOLIX_FAN_MASK = 0xF000;
|
static const uint32_t COOLIX_FAN_MASK = 0xF000;
|
||||||
const uint32_t COOLIX_FAN_MODE_AUTO_DRY = 0x1000;
|
static const uint32_t COOLIX_FAN_MODE_AUTO_DRY = 0x1000;
|
||||||
const uint32_t COOLIX_FAN_AUTO = 0xB000;
|
static const uint32_t COOLIX_FAN_AUTO = 0xB000;
|
||||||
const uint32_t COOLIX_FAN_MIN = 0x9000;
|
static const uint32_t COOLIX_FAN_MIN = 0x9000;
|
||||||
const uint32_t COOLIX_FAN_MED = 0x5000;
|
static const uint32_t COOLIX_FAN_MED = 0x5000;
|
||||||
const uint32_t COOLIX_FAN_MAX = 0x3000;
|
static const uint32_t COOLIX_FAN_MAX = 0x3000;
|
||||||
|
|
||||||
// Temperature
|
// Temperature
|
||||||
const uint8_t COOLIX_TEMP_RANGE = COOLIX_TEMP_MAX - COOLIX_TEMP_MIN + 1;
|
static const uint8_t COOLIX_TEMP_RANGE = COOLIX_TEMP_MAX - COOLIX_TEMP_MIN + 1;
|
||||||
const uint8_t COOLIX_FAN_TEMP_CODE = 0b11100000; // Part of Fan Mode.
|
static const uint8_t COOLIX_FAN_TEMP_CODE = 0b11100000; // Part of Fan Mode.
|
||||||
const uint32_t COOLIX_TEMP_MASK = 0b11110000;
|
static const uint32_t COOLIX_TEMP_MASK = 0b11110000;
|
||||||
const uint8_t COOLIX_TEMP_MAP[COOLIX_TEMP_RANGE] = {
|
static const uint8_t COOLIX_TEMP_MAP[COOLIX_TEMP_RANGE] = {
|
||||||
0b00000000, // 17C
|
0b00000000, // 17C
|
||||||
0b00010000, // 18c
|
0b00010000, // 18c
|
||||||
0b00110000, // 19C
|
0b00110000, // 19C
|
||||||
|
@ -45,17 +46,6 @@ const uint8_t COOLIX_TEMP_MAP[COOLIX_TEMP_RANGE] = {
|
||||||
0b10110000 // 30C
|
0b10110000 // 30C
|
||||||
};
|
};
|
||||||
|
|
||||||
// Constants
|
|
||||||
static const uint32_t BIT_MARK_US = 660;
|
|
||||||
static const uint32_t HEADER_MARK_US = 560 * 8;
|
|
||||||
static const uint32_t HEADER_SPACE_US = 560 * 8;
|
|
||||||
static const uint32_t BIT_ONE_SPACE_US = 1500;
|
|
||||||
static const uint32_t BIT_ZERO_SPACE_US = 450;
|
|
||||||
static const uint32_t FOOTER_MARK_US = BIT_MARK_US;
|
|
||||||
static const uint32_t FOOTER_SPACE_US = HEADER_SPACE_US;
|
|
||||||
|
|
||||||
const uint16_t COOLIX_BITS = 24;
|
|
||||||
|
|
||||||
void CoolixClimate::transmit_state() {
|
void CoolixClimate::transmit_state() {
|
||||||
uint32_t remote_state = 0xB20F00;
|
uint32_t remote_state = 0xB20F00;
|
||||||
|
|
||||||
|
@ -111,119 +101,60 @@ void CoolixClimate::transmit_state() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ESP_LOGV(TAG, "Sending coolix code: 0x%02X", remote_state);
|
ESP_LOGV(TAG, "Sending coolix code: 0x%06X", remote_state);
|
||||||
|
|
||||||
auto transmit = this->transmitter_->transmit();
|
auto transmit = this->transmitter_->transmit();
|
||||||
auto data = transmit.get_data();
|
auto data = transmit.get_data();
|
||||||
|
remote_base::CoolixProtocol().encode(data, remote_state);
|
||||||
data->set_carrier_frequency(38000);
|
|
||||||
uint16_t repeat = 1;
|
|
||||||
for (uint16_t r = 0; r <= repeat; r++) {
|
|
||||||
// Header
|
|
||||||
data->mark(HEADER_MARK_US);
|
|
||||||
data->space(HEADER_SPACE_US);
|
|
||||||
// Data
|
|
||||||
// Break data into bytes, starting at the Most Significant
|
|
||||||
// Byte. Each byte then being sent normal, then followed inverted.
|
|
||||||
for (uint16_t i = 8; i <= COOLIX_BITS; i += 8) {
|
|
||||||
// Grab a bytes worth of data.
|
|
||||||
uint8_t byte = (remote_state >> (COOLIX_BITS - i)) & 0xFF;
|
|
||||||
// Normal
|
|
||||||
for (uint64_t mask = 1ULL << 7; mask; mask >>= 1) {
|
|
||||||
data->mark(BIT_MARK_US);
|
|
||||||
data->space((byte & mask) ? BIT_ONE_SPACE_US : BIT_ZERO_SPACE_US);
|
|
||||||
}
|
|
||||||
// Inverted
|
|
||||||
for (uint64_t mask = 1ULL << 7; mask; mask >>= 1) {
|
|
||||||
data->mark(BIT_MARK_US);
|
|
||||||
data->space(!(byte & mask) ? BIT_ONE_SPACE_US : BIT_ZERO_SPACE_US);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Footer
|
|
||||||
data->mark(BIT_MARK_US);
|
|
||||||
data->space(FOOTER_SPACE_US); // Pause before repeating
|
|
||||||
}
|
|
||||||
|
|
||||||
transmit.perform();
|
transmit.perform();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CoolixClimate::on_receive(remote_base::RemoteReceiveData data) {
|
bool CoolixClimate::on_coolix(climate::Climate *parent, remote_base::RemoteReceiveData data) {
|
||||||
|
auto decoded = remote_base::CoolixProtocol().decode(data);
|
||||||
|
if (!decoded.has_value())
|
||||||
|
return false;
|
||||||
// Decoded remote state y 3 bytes long code.
|
// Decoded remote state y 3 bytes long code.
|
||||||
uint32_t remote_state = 0;
|
uint32_t remote_state = *decoded;
|
||||||
// The protocol sends the data twice, read here
|
ESP_LOGV(TAG, "Decoded 0x%06X", remote_state);
|
||||||
uint32_t loop_read;
|
if ((remote_state & 0xFF0000) != 0xB20000)
|
||||||
for (uint16_t loop = 1; loop <= 2; loop++) {
|
|
||||||
if (!data.expect_item(HEADER_MARK_US, HEADER_SPACE_US))
|
|
||||||
return false;
|
|
||||||
loop_read = 0;
|
|
||||||
for (uint8_t a_byte = 0; a_byte < 3; a_byte++) {
|
|
||||||
uint8_t byte = 0;
|
|
||||||
for (int8_t a_bit = 7; a_bit >= 0; a_bit--) {
|
|
||||||
if (data.expect_item(BIT_MARK_US, BIT_ONE_SPACE_US))
|
|
||||||
byte |= 1 << a_bit;
|
|
||||||
else if (!data.expect_item(BIT_MARK_US, BIT_ZERO_SPACE_US))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Need to see this segment inverted
|
|
||||||
for (int8_t a_bit = 7; a_bit >= 0; a_bit--) {
|
|
||||||
bool bit = byte & (1 << a_bit);
|
|
||||||
if (!data.expect_item(BIT_MARK_US, bit ? BIT_ZERO_SPACE_US : BIT_ONE_SPACE_US))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Receiving MSB first: reorder bytes
|
|
||||||
loop_read |= byte << ((2 - a_byte) * 8);
|
|
||||||
}
|
|
||||||
// Footer Mark
|
|
||||||
if (!data.expect_mark(BIT_MARK_US))
|
|
||||||
return false;
|
|
||||||
if (loop == 1) {
|
|
||||||
// Back up state on first loop
|
|
||||||
remote_state = loop_read;
|
|
||||||
if (!data.expect_space(FOOTER_SPACE_US))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ESP_LOGV(TAG, "Decoded 0x%02X", remote_state);
|
|
||||||
if (remote_state != loop_read || (remote_state & 0xFF0000) != 0xB20000)
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (remote_state == COOLIX_OFF) {
|
if (remote_state == COOLIX_OFF) {
|
||||||
this->mode = climate::CLIMATE_MODE_OFF;
|
parent->mode = climate::CLIMATE_MODE_OFF;
|
||||||
} else if (remote_state == COOLIX_SWING) {
|
} else if (remote_state == COOLIX_SWING) {
|
||||||
this->swing_mode =
|
parent->swing_mode =
|
||||||
this->swing_mode == climate::CLIMATE_SWING_OFF ? climate::CLIMATE_SWING_VERTICAL : climate::CLIMATE_SWING_OFF;
|
parent->swing_mode == climate::CLIMATE_SWING_OFF ? climate::CLIMATE_SWING_VERTICAL : climate::CLIMATE_SWING_OFF;
|
||||||
} else {
|
} else {
|
||||||
if ((remote_state & COOLIX_MODE_MASK) == COOLIX_HEAT)
|
if ((remote_state & COOLIX_MODE_MASK) == COOLIX_HEAT)
|
||||||
this->mode = climate::CLIMATE_MODE_HEAT;
|
parent->mode = climate::CLIMATE_MODE_HEAT;
|
||||||
else if ((remote_state & COOLIX_MODE_MASK) == COOLIX_AUTO)
|
else if ((remote_state & COOLIX_MODE_MASK) == COOLIX_AUTO)
|
||||||
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
|
parent->mode = climate::CLIMATE_MODE_HEAT_COOL;
|
||||||
else if ((remote_state & COOLIX_MODE_MASK) == COOLIX_DRY_FAN) {
|
else if ((remote_state & COOLIX_MODE_MASK) == COOLIX_DRY_FAN) {
|
||||||
if ((remote_state & COOLIX_FAN_MASK) == COOLIX_FAN_MODE_AUTO_DRY)
|
if ((remote_state & COOLIX_FAN_MASK) == COOLIX_FAN_MODE_AUTO_DRY)
|
||||||
this->mode = climate::CLIMATE_MODE_DRY;
|
parent->mode = climate::CLIMATE_MODE_DRY;
|
||||||
else
|
else
|
||||||
this->mode = climate::CLIMATE_MODE_FAN_ONLY;
|
parent->mode = climate::CLIMATE_MODE_FAN_ONLY;
|
||||||
} else
|
} else
|
||||||
this->mode = climate::CLIMATE_MODE_COOL;
|
parent->mode = climate::CLIMATE_MODE_COOL;
|
||||||
|
|
||||||
// Fan Speed
|
// Fan Speed
|
||||||
if ((remote_state & COOLIX_FAN_AUTO) == COOLIX_FAN_AUTO || this->mode == climate::CLIMATE_MODE_HEAT_COOL ||
|
if ((remote_state & COOLIX_FAN_AUTO) == COOLIX_FAN_AUTO || parent->mode == climate::CLIMATE_MODE_HEAT_COOL ||
|
||||||
this->mode == climate::CLIMATE_MODE_DRY)
|
parent->mode == climate::CLIMATE_MODE_DRY)
|
||||||
this->fan_mode = climate::CLIMATE_FAN_AUTO;
|
parent->fan_mode = climate::CLIMATE_FAN_AUTO;
|
||||||
else if ((remote_state & COOLIX_FAN_MIN) == COOLIX_FAN_MIN)
|
else if ((remote_state & COOLIX_FAN_MIN) == COOLIX_FAN_MIN)
|
||||||
this->fan_mode = climate::CLIMATE_FAN_LOW;
|
parent->fan_mode = climate::CLIMATE_FAN_LOW;
|
||||||
else if ((remote_state & COOLIX_FAN_MED) == COOLIX_FAN_MED)
|
else if ((remote_state & COOLIX_FAN_MED) == COOLIX_FAN_MED)
|
||||||
this->fan_mode = climate::CLIMATE_FAN_MEDIUM;
|
parent->fan_mode = climate::CLIMATE_FAN_MEDIUM;
|
||||||
else if ((remote_state & COOLIX_FAN_MAX) == COOLIX_FAN_MAX)
|
else if ((remote_state & COOLIX_FAN_MAX) == COOLIX_FAN_MAX)
|
||||||
this->fan_mode = climate::CLIMATE_FAN_HIGH;
|
parent->fan_mode = climate::CLIMATE_FAN_HIGH;
|
||||||
|
|
||||||
// Temperature
|
// Temperature
|
||||||
uint8_t temperature_code = remote_state & COOLIX_TEMP_MASK;
|
uint8_t temperature_code = remote_state & COOLIX_TEMP_MASK;
|
||||||
for (uint8_t i = 0; i < COOLIX_TEMP_RANGE; i++)
|
for (uint8_t i = 0; i < COOLIX_TEMP_RANGE; i++)
|
||||||
if (COOLIX_TEMP_MAP[i] == temperature_code)
|
if (COOLIX_TEMP_MAP[i] == temperature_code)
|
||||||
this->target_temperature = i + COOLIX_TEMP_MIN;
|
parent->target_temperature = i + COOLIX_TEMP_MIN;
|
||||||
}
|
}
|
||||||
this->publish_state();
|
parent->publish_state();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,11 +26,15 @@ class CoolixClimate : public climate_ir::ClimateIR {
|
||||||
climate_ir::ClimateIR::control(call);
|
climate_ir::ClimateIR::control(call);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This static method can be used in other climate components that accept the Coolix protocol. See midea_ir for
|
||||||
|
/// example.
|
||||||
|
static bool on_coolix(climate::Climate *parent, remote_base::RemoteReceiveData data);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/// Transmit via IR the state of this climate controller.
|
/// Transmit via IR the state of this climate controller.
|
||||||
void transmit_state() override;
|
void transmit_state() override;
|
||||||
/// Handle received IR Buffer
|
/// Handle received IR Buffer
|
||||||
bool on_receive(remote_base::RemoteReceiveData data) override;
|
bool on_receive(remote_base::RemoteReceiveData data) override { return CoolixClimate::on_coolix(this, data); }
|
||||||
|
|
||||||
bool send_swing_cmd_{false};
|
bool send_swing_cmd_{false};
|
||||||
};
|
};
|
||||||
|
|
|
@ -210,7 +210,10 @@ Cover::Cover() : Cover("") {}
|
||||||
std::string Cover::get_device_class() {
|
std::string Cover::get_device_class() {
|
||||||
if (this->device_class_override_.has_value())
|
if (this->device_class_override_.has_value())
|
||||||
return *this->device_class_override_;
|
return *this->device_class_override_;
|
||||||
|
#pragma GCC diagnostic push
|
||||||
|
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||||
return this->device_class();
|
return this->device_class();
|
||||||
|
#pragma GCC diagnostic pop
|
||||||
}
|
}
|
||||||
bool Cover::is_fully_open() const { return this->position == COVER_OPEN; }
|
bool Cover::is_fully_open() const { return this->position == COVER_OPEN; }
|
||||||
bool Cover::is_fully_closed() const { return this->position == COVER_CLOSED; }
|
bool Cover::is_fully_closed() const { return this->position == COVER_CLOSED; }
|
||||||
|
|
|
@ -169,6 +169,11 @@ class Cover : public EntityBase {
|
||||||
friend CoverCall;
|
friend CoverCall;
|
||||||
|
|
||||||
virtual void control(const CoverCall &call) = 0;
|
virtual void control(const CoverCall &call) = 0;
|
||||||
|
|
||||||
|
/** Override this to set the default device class.
|
||||||
|
*
|
||||||
|
* @deprecated This method is deprecated, set the property during config validation instead. (2022.1)
|
||||||
|
*/
|
||||||
virtual std::string device_class();
|
virtual std::string device_class();
|
||||||
|
|
||||||
optional<CoverRestoreState> restore_state_();
|
optional<CoverRestoreState> restore_state_();
|
||||||
|
|
|
@ -38,10 +38,9 @@ void DallasComponent::setup() {
|
||||||
raw_sensors = this->one_wire_->search_vec();
|
raw_sensors = this->one_wire_->search_vec();
|
||||||
|
|
||||||
for (auto &address : raw_sensors) {
|
for (auto &address : raw_sensors) {
|
||||||
std::string s = uint64_to_string(address);
|
|
||||||
auto *address8 = reinterpret_cast<uint8_t *>(&address);
|
auto *address8 = reinterpret_cast<uint8_t *>(&address);
|
||||||
if (crc8(address8, 7) != address8[7]) {
|
if (crc8(address8, 7) != address8[7]) {
|
||||||
ESP_LOGW(TAG, "Dallas device 0x%s has invalid CRC.", s.c_str());
|
ESP_LOGW(TAG, "Dallas device 0x%s has invalid CRC.", format_hex(address).c_str());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (address8[0] != DALLAS_MODEL_DS18S20 && address8[0] != DALLAS_MODEL_DS1822 &&
|
if (address8[0] != DALLAS_MODEL_DS18S20 && address8[0] != DALLAS_MODEL_DS1822 &&
|
||||||
|
@ -77,8 +76,7 @@ void DallasComponent::dump_config() {
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGD(TAG, " Found sensors:");
|
ESP_LOGD(TAG, " Found sensors:");
|
||||||
for (auto &address : this->found_sensors_) {
|
for (auto &address : this->found_sensors_) {
|
||||||
std::string s = uint64_to_string(address);
|
ESP_LOGD(TAG, " 0x%s", format_hex(address).c_str());
|
||||||
ESP_LOGD(TAG, " 0x%s", s.c_str());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,7 +145,7 @@ void DallasTemperatureSensor::set_index(uint8_t index) { this->index_ = index; }
|
||||||
uint8_t *DallasTemperatureSensor::get_address8() { return reinterpret_cast<uint8_t *>(&this->address_); }
|
uint8_t *DallasTemperatureSensor::get_address8() { return reinterpret_cast<uint8_t *>(&this->address_); }
|
||||||
const std::string &DallasTemperatureSensor::get_address_name() {
|
const std::string &DallasTemperatureSensor::get_address_name() {
|
||||||
if (this->address_name_.empty()) {
|
if (this->address_name_.empty()) {
|
||||||
this->address_name_ = std::string("0x") + uint64_to_string(this->address_);
|
this->address_name_ = std::string("0x") + format_hex(this->address_);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this->address_name_;
|
return this->address_name_;
|
||||||
|
@ -237,7 +235,7 @@ float DallasTemperatureSensor::get_temp_c() {
|
||||||
|
|
||||||
return temp / 128.0f;
|
return temp / 128.0f;
|
||||||
}
|
}
|
||||||
std::string DallasTemperatureSensor::unique_id() { return "dallas-" + uint64_to_string(this->address_); }
|
std::string DallasTemperatureSensor::unique_id() { return "dallas-" + str_upper_case(format_hex(this->address_)); }
|
||||||
|
|
||||||
} // namespace dallas
|
} // namespace dallas
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
|
@ -3,6 +3,7 @@ from pathlib import Path
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.components.packages import validate_source_shorthand
|
from esphome.components.packages import validate_source_shorthand
|
||||||
|
from esphome.wizard import wizard_file
|
||||||
from esphome.yaml_util import dump
|
from esphome.yaml_util import dump
|
||||||
|
|
||||||
|
|
||||||
|
@ -48,6 +49,18 @@ def import_config(path: str, name: str, project_name: str, import_url: str) -> N
|
||||||
if p.exists():
|
if p.exists():
|
||||||
raise FileExistsError
|
raise FileExistsError
|
||||||
|
|
||||||
|
if project_name == "esphome.web":
|
||||||
|
p.write_text(
|
||||||
|
wizard_file(
|
||||||
|
name=name,
|
||||||
|
platform="ESP32" if "esp32" in import_url else "ESP8266",
|
||||||
|
board="esp32dev" if "esp32" in import_url else "esp01_1m",
|
||||||
|
ssid="!secret wifi_ssid",
|
||||||
|
psk="!secret wifi_password",
|
||||||
|
),
|
||||||
|
encoding="utf8",
|
||||||
|
)
|
||||||
|
else:
|
||||||
config = {
|
config = {
|
||||||
"substitutions": {"name": name},
|
"substitutions": {"name": name},
|
||||||
"packages": {project_name: import_url},
|
"packages": {project_name: import_url},
|
||||||
|
|
|
@ -101,7 +101,7 @@ void DebugComponent::dump_config() {
|
||||||
info.features &= ~CHIP_FEATURE_BT;
|
info.features &= ~CHIP_FEATURE_BT;
|
||||||
}
|
}
|
||||||
if (info.features)
|
if (info.features)
|
||||||
features += "Other:" + uint64_to_string(info.features);
|
features += "Other:" + format_hex(info.features);
|
||||||
ESP_LOGD(TAG, "Chip: Model=%s, Features=%s Cores=%u, Revision=%u", model, features.c_str(), info.cores,
|
ESP_LOGD(TAG, "Chip: Model=%s, Features=%s Cores=%u, Revision=%u", model, features.c_str(), info.cores,
|
||||||
info.revision);
|
info.revision);
|
||||||
|
|
||||||
|
|
|
@ -41,15 +41,30 @@ EXT1_WAKEUP_MODES = {
|
||||||
"ALL_LOW": esp_sleep_ext1_wakeup_mode_t.ESP_EXT1_WAKEUP_ALL_LOW,
|
"ALL_LOW": esp_sleep_ext1_wakeup_mode_t.ESP_EXT1_WAKEUP_ALL_LOW,
|
||||||
"ANY_HIGH": esp_sleep_ext1_wakeup_mode_t.ESP_EXT1_WAKEUP_ANY_HIGH,
|
"ANY_HIGH": esp_sleep_ext1_wakeup_mode_t.ESP_EXT1_WAKEUP_ANY_HIGH,
|
||||||
}
|
}
|
||||||
|
WakeupCauseToRunDuration = deep_sleep_ns.struct("WakeupCauseToRunDuration")
|
||||||
|
|
||||||
CONF_WAKEUP_PIN_MODE = "wakeup_pin_mode"
|
CONF_WAKEUP_PIN_MODE = "wakeup_pin_mode"
|
||||||
CONF_ESP32_EXT1_WAKEUP = "esp32_ext1_wakeup"
|
CONF_ESP32_EXT1_WAKEUP = "esp32_ext1_wakeup"
|
||||||
CONF_TOUCH_WAKEUP = "touch_wakeup"
|
CONF_TOUCH_WAKEUP = "touch_wakeup"
|
||||||
|
CONF_DEFAULT = "default"
|
||||||
|
CONF_GPIO_WAKEUP_REASON = "gpio_wakeup_reason"
|
||||||
|
CONF_TOUCH_WAKEUP_REASON = "touch_wakeup_reason"
|
||||||
|
|
||||||
|
WAKEUP_CAUSES_SCHEMA = cv.Schema(
|
||||||
|
{
|
||||||
|
cv.Required(CONF_DEFAULT): cv.positive_time_period_milliseconds,
|
||||||
|
cv.Optional(CONF_TOUCH_WAKEUP_REASON): cv.positive_time_period_milliseconds,
|
||||||
|
cv.Optional(CONF_GPIO_WAKEUP_REASON): cv.positive_time_period_milliseconds,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.Schema(
|
CONFIG_SCHEMA = cv.Schema(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(DeepSleepComponent),
|
cv.GenerateID(): cv.declare_id(DeepSleepComponent),
|
||||||
cv.Optional(CONF_RUN_DURATION): cv.positive_time_period_milliseconds,
|
cv.Optional(CONF_RUN_DURATION): cv.Any(
|
||||||
|
cv.All(cv.only_on_esp32, WAKEUP_CAUSES_SCHEMA),
|
||||||
|
cv.positive_time_period_milliseconds,
|
||||||
|
),
|
||||||
cv.Optional(CONF_SLEEP_DURATION): cv.positive_time_period_milliseconds,
|
cv.Optional(CONF_SLEEP_DURATION): cv.positive_time_period_milliseconds,
|
||||||
cv.Optional(CONF_WAKEUP_PIN): cv.All(
|
cv.Optional(CONF_WAKEUP_PIN): cv.All(
|
||||||
cv.only_on_esp32, pins.internal_gpio_input_pin_schema, validate_pin_number
|
cv.only_on_esp32, pins.internal_gpio_input_pin_schema, validate_pin_number
|
||||||
|
@ -85,7 +100,28 @@ async def to_code(config):
|
||||||
if CONF_WAKEUP_PIN_MODE in config:
|
if CONF_WAKEUP_PIN_MODE in config:
|
||||||
cg.add(var.set_wakeup_pin_mode(config[CONF_WAKEUP_PIN_MODE]))
|
cg.add(var.set_wakeup_pin_mode(config[CONF_WAKEUP_PIN_MODE]))
|
||||||
if CONF_RUN_DURATION in config:
|
if CONF_RUN_DURATION in config:
|
||||||
|
run_duration_config = config[CONF_RUN_DURATION]
|
||||||
|
if not isinstance(run_duration_config, dict):
|
||||||
cg.add(var.set_run_duration(config[CONF_RUN_DURATION]))
|
cg.add(var.set_run_duration(config[CONF_RUN_DURATION]))
|
||||||
|
else:
|
||||||
|
default_run_duration = run_duration_config[CONF_DEFAULT]
|
||||||
|
wakeup_cause_to_run_duration = cg.StructInitializer(
|
||||||
|
WakeupCauseToRunDuration,
|
||||||
|
("default_cause", default_run_duration),
|
||||||
|
(
|
||||||
|
"touch_cause",
|
||||||
|
run_duration_config.get(
|
||||||
|
CONF_TOUCH_WAKEUP_REASON, default_run_duration
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"gpio_cause",
|
||||||
|
run_duration_config.get(
|
||||||
|
CONF_GPIO_WAKEUP_REASON, default_run_duration
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
cg.add(var.set_run_duration(wakeup_cause_to_run_duration))
|
||||||
|
|
||||||
if CONF_ESP32_EXT1_WAKEUP in config:
|
if CONF_ESP32_EXT1_WAKEUP in config:
|
||||||
conf = config[CONF_ESP32_EXT1_WAKEUP]
|
conf = config[CONF_ESP32_EXT1_WAKEUP]
|
||||||
|
|
|
@ -13,12 +13,35 @@ static const char *const TAG = "deep_sleep";
|
||||||
|
|
||||||
bool global_has_deep_sleep = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
bool global_has_deep_sleep = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||||
|
|
||||||
|
optional<uint32_t> DeepSleepComponent::get_run_duration_() const {
|
||||||
|
#ifdef USE_ESP32
|
||||||
|
if (this->wakeup_cause_to_run_duration_.has_value()) {
|
||||||
|
esp_sleep_wakeup_cause_t wakeup_cause = esp_sleep_get_wakeup_cause();
|
||||||
|
switch (wakeup_cause) {
|
||||||
|
case ESP_SLEEP_WAKEUP_EXT0:
|
||||||
|
case ESP_SLEEP_WAKEUP_EXT1:
|
||||||
|
return this->wakeup_cause_to_run_duration_->gpio_cause;
|
||||||
|
case ESP_SLEEP_WAKEUP_TOUCHPAD:
|
||||||
|
return this->wakeup_cause_to_run_duration_->touch_cause;
|
||||||
|
default:
|
||||||
|
return this->wakeup_cause_to_run_duration_->default_cause;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return this->run_duration_;
|
||||||
|
}
|
||||||
|
|
||||||
void DeepSleepComponent::setup() {
|
void DeepSleepComponent::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Setting up Deep Sleep...");
|
ESP_LOGCONFIG(TAG, "Setting up Deep Sleep...");
|
||||||
global_has_deep_sleep = true;
|
global_has_deep_sleep = true;
|
||||||
|
|
||||||
if (this->run_duration_.has_value())
|
const optional<uint32_t> run_duration = get_run_duration_();
|
||||||
this->set_timeout(*this->run_duration_, [this]() { this->begin_sleep(); });
|
if (run_duration.has_value()) {
|
||||||
|
ESP_LOGI(TAG, "Scheduling Deep Sleep to start in %u ms", *run_duration);
|
||||||
|
this->set_timeout(*run_duration, [this]() { this->begin_sleep(); });
|
||||||
|
} else {
|
||||||
|
ESP_LOGD(TAG, "Not scheduling Deep Sleep, as no run duration is configured.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
void DeepSleepComponent::dump_config() {
|
void DeepSleepComponent::dump_config() {
|
||||||
ESP_LOGCONFIG(TAG, "Setting up Deep Sleep...");
|
ESP_LOGCONFIG(TAG, "Setting up Deep Sleep...");
|
||||||
|
@ -33,6 +56,11 @@ void DeepSleepComponent::dump_config() {
|
||||||
if (wakeup_pin_ != nullptr) {
|
if (wakeup_pin_ != nullptr) {
|
||||||
LOG_PIN(" Wakeup Pin: ", this->wakeup_pin_);
|
LOG_PIN(" Wakeup Pin: ", this->wakeup_pin_);
|
||||||
}
|
}
|
||||||
|
if (this->wakeup_cause_to_run_duration_.has_value()) {
|
||||||
|
ESP_LOGCONFIG(TAG, " Default Wakeup Run Duration: %u ms", this->wakeup_cause_to_run_duration_->default_cause);
|
||||||
|
ESP_LOGCONFIG(TAG, " Touch Wakeup Run Duration: %u ms", this->wakeup_cause_to_run_duration_->touch_cause);
|
||||||
|
ESP_LOGCONFIG(TAG, " GPIO Wakeup Run Duration: %u ms", this->wakeup_cause_to_run_duration_->gpio_cause);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
void DeepSleepComponent::loop() {
|
void DeepSleepComponent::loop() {
|
||||||
|
@ -49,6 +77,9 @@ void DeepSleepComponent::set_wakeup_pin_mode(WakeupPinMode wakeup_pin_mode) {
|
||||||
}
|
}
|
||||||
void DeepSleepComponent::set_ext1_wakeup(Ext1Wakeup ext1_wakeup) { this->ext1_wakeup_ = ext1_wakeup; }
|
void DeepSleepComponent::set_ext1_wakeup(Ext1Wakeup ext1_wakeup) { this->ext1_wakeup_ = ext1_wakeup; }
|
||||||
void DeepSleepComponent::set_touch_wakeup(bool touch_wakeup) { this->touch_wakeup_ = touch_wakeup; }
|
void DeepSleepComponent::set_touch_wakeup(bool touch_wakeup) { this->touch_wakeup_ = touch_wakeup; }
|
||||||
|
void DeepSleepComponent::set_run_duration(WakeupCauseToRunDuration wakeup_cause_to_run_duration) {
|
||||||
|
wakeup_cause_to_run_duration_ = wakeup_cause_to_run_duration;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
void DeepSleepComponent::set_run_duration(uint32_t time_ms) { this->run_duration_ = time_ms; }
|
void DeepSleepComponent::set_run_duration(uint32_t time_ms) { this->run_duration_ = time_ms; }
|
||||||
void DeepSleepComponent::begin_sleep(bool manual) {
|
void DeepSleepComponent::begin_sleep(bool manual) {
|
||||||
|
|
|
@ -32,6 +32,15 @@ struct Ext1Wakeup {
|
||||||
esp_sleep_ext1_wakeup_mode_t wakeup_mode;
|
esp_sleep_ext1_wakeup_mode_t wakeup_mode;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct WakeupCauseToRunDuration {
|
||||||
|
// Run duration if woken up by timer or any other reason besides those below.
|
||||||
|
uint32_t default_cause;
|
||||||
|
// Run duration if woken up by touch pads.
|
||||||
|
uint32_t touch_cause;
|
||||||
|
// Run duration if woken up by GPIO pins.
|
||||||
|
uint32_t gpio_cause;
|
||||||
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
template<typename... Ts> class EnterDeepSleepAction;
|
template<typename... Ts> class EnterDeepSleepAction;
|
||||||
|
@ -59,6 +68,11 @@ class DeepSleepComponent : public Component {
|
||||||
void set_ext1_wakeup(Ext1Wakeup ext1_wakeup);
|
void set_ext1_wakeup(Ext1Wakeup ext1_wakeup);
|
||||||
|
|
||||||
void set_touch_wakeup(bool touch_wakeup);
|
void set_touch_wakeup(bool touch_wakeup);
|
||||||
|
|
||||||
|
// Set the duration in ms for how long the code should run before entering
|
||||||
|
// deep sleep mode, according to the cause the ESP32 has woken.
|
||||||
|
void set_run_duration(WakeupCauseToRunDuration wakeup_cause_to_run_duration);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
/// Set a duration in ms for how long the code should run before entering deep sleep mode.
|
/// Set a duration in ms for how long the code should run before entering deep sleep mode.
|
||||||
void set_run_duration(uint32_t time_ms);
|
void set_run_duration(uint32_t time_ms);
|
||||||
|
@ -75,12 +89,17 @@ class DeepSleepComponent : public Component {
|
||||||
void prevent_deep_sleep();
|
void prevent_deep_sleep();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
// Returns nullopt if no run duration is set. Otherwise, returns the run
|
||||||
|
// duration before entering deep sleep.
|
||||||
|
optional<uint32_t> get_run_duration_() const;
|
||||||
|
|
||||||
optional<uint64_t> sleep_duration_;
|
optional<uint64_t> sleep_duration_;
|
||||||
#ifdef USE_ESP32
|
#ifdef USE_ESP32
|
||||||
InternalGPIOPin *wakeup_pin_;
|
InternalGPIOPin *wakeup_pin_;
|
||||||
WakeupPinMode wakeup_pin_mode_{WAKEUP_PIN_MODE_IGNORE};
|
WakeupPinMode wakeup_pin_mode_{WAKEUP_PIN_MODE_IGNORE};
|
||||||
optional<Ext1Wakeup> ext1_wakeup_;
|
optional<Ext1Wakeup> ext1_wakeup_;
|
||||||
optional<bool> touch_wakeup_;
|
optional<bool> touch_wakeup_;
|
||||||
|
optional<WakeupCauseToRunDuration> wakeup_cause_to_run_duration_;
|
||||||
#endif
|
#endif
|
||||||
optional<uint32_t> run_duration_;
|
optional<uint32_t> run_duration_;
|
||||||
bool next_enter_deep_sleep_{false};
|
bool next_enter_deep_sleep_{false};
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#include "esphome/core/color.h"
|
#include "esphome/core/color.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include "esphome/core/hal.h"
|
#include "esphome/core/hal.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace display {
|
namespace display {
|
||||||
|
@ -15,7 +16,8 @@ const Color COLOR_OFF(0, 0, 0, 0);
|
||||||
const Color COLOR_ON(255, 255, 255, 255);
|
const Color COLOR_ON(255, 255, 255, 255);
|
||||||
|
|
||||||
void DisplayBuffer::init_internal_(uint32_t buffer_length) {
|
void DisplayBuffer::init_internal_(uint32_t buffer_length) {
|
||||||
this->buffer_ = new (std::nothrow) uint8_t[buffer_length]; // NOLINT
|
ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
|
||||||
|
this->buffer_ = allocator.allocate(buffer_length);
|
||||||
if (this->buffer_ == nullptr) {
|
if (this->buffer_ == nullptr) {
|
||||||
ESP_LOGE(TAG, "Could not allocate buffer for display!");
|
ESP_LOGE(TAG, "Could not allocate buffer for display!");
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -23,22 +23,24 @@ void DutyCycleSensor::dump_config() {
|
||||||
}
|
}
|
||||||
void DutyCycleSensor::update() {
|
void DutyCycleSensor::update() {
|
||||||
const uint32_t now = micros();
|
const uint32_t now = micros();
|
||||||
|
const uint32_t last_interrupt = this->store_.last_interrupt; // Read the measurement taken by the interrupt
|
||||||
|
uint32_t on_time = this->store_.on_time;
|
||||||
|
|
||||||
|
this->store_.on_time = 0; // Start new measurement, exactly aligned with the micros() reading
|
||||||
|
this->store_.last_interrupt = now;
|
||||||
|
|
||||||
if (this->last_update_ != 0) {
|
if (this->last_update_ != 0) {
|
||||||
const bool level = this->store_.last_level;
|
const bool level = this->store_.last_level;
|
||||||
const uint32_t last_interrupt = this->store_.last_interrupt;
|
|
||||||
uint32_t on_time = this->store_.on_time;
|
|
||||||
|
|
||||||
if (level)
|
if (level)
|
||||||
on_time += now - last_interrupt;
|
on_time += now - last_interrupt;
|
||||||
|
|
||||||
const float total_time = float(now - this->last_update_);
|
const float total_time = float(now - this->last_update_);
|
||||||
|
|
||||||
const float value = (on_time / total_time) * 100.0f;
|
const float value = (on_time * 100.0f) / total_time;
|
||||||
ESP_LOGD(TAG, "'%s' Got duty cycle=%.1f%%", this->get_name().c_str(), value);
|
ESP_LOGD(TAG, "'%s' Got duty cycle=%.1f%%", this->get_name().c_str(), value);
|
||||||
this->publish_state(value);
|
this->publish_state(value);
|
||||||
}
|
}
|
||||||
this->store_.on_time = 0;
|
|
||||||
this->store_.last_interrupt = now;
|
|
||||||
this->last_update_ = now;
|
this->last_update_ = now;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,9 @@ from dataclasses import dataclass
|
||||||
from typing import Union
|
from typing import Union
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
from esphome.helpers import write_file_if_changed
|
from esphome.helpers import copy_file_if_changed, write_file_if_changed
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_BOARD,
|
CONF_BOARD,
|
||||||
CONF_FRAMEWORK,
|
CONF_FRAMEWORK,
|
||||||
|
@ -295,6 +296,8 @@ async def to_code(config):
|
||||||
conf = config[CONF_FRAMEWORK]
|
conf = config[CONF_FRAMEWORK]
|
||||||
cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION])
|
cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION])
|
||||||
|
|
||||||
|
cg.add_platformio_option("extra_scripts", ["post:post_build.py"])
|
||||||
|
|
||||||
if conf[CONF_TYPE] == FRAMEWORK_ESP_IDF:
|
if conf[CONF_TYPE] == FRAMEWORK_ESP_IDF:
|
||||||
cg.add_platformio_option("framework", "espidf")
|
cg.add_platformio_option("framework", "espidf")
|
||||||
cg.add_build_flag("-DUSE_ESP_IDF")
|
cg.add_build_flag("-DUSE_ESP_IDF")
|
||||||
|
@ -412,3 +415,10 @@ def copy_files():
|
||||||
CORE.relative_build_path("partitions.csv"),
|
CORE.relative_build_path("partitions.csv"),
|
||||||
IDF_PARTITIONS_CSV,
|
IDF_PARTITIONS_CSV,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
dir = os.path.dirname(__file__)
|
||||||
|
post_build_file = os.path.join(dir, "post_build.py.script")
|
||||||
|
copy_file_if_changed(
|
||||||
|
post_build_file,
|
||||||
|
CORE.relative_build_path("post_build.py"),
|
||||||
|
)
|
||||||
|
|
|
@ -261,6 +261,37 @@ ESP32_BOARD_PINS = {
|
||||||
"SS": 33,
|
"SS": 33,
|
||||||
"TX": 17,
|
"TX": 17,
|
||||||
},
|
},
|
||||||
|
"featheresp32-s2": {
|
||||||
|
"SDA": 3,
|
||||||
|
"SCL": 4,
|
||||||
|
"SS": 42,
|
||||||
|
"MOSI": 35,
|
||||||
|
"SCK": 36,
|
||||||
|
"MISO": 37,
|
||||||
|
"A0": 18,
|
||||||
|
"A1": 17,
|
||||||
|
"A10": 27,
|
||||||
|
"A11": 12,
|
||||||
|
"A12": 13,
|
||||||
|
"A13": 35,
|
||||||
|
"A2": 16,
|
||||||
|
"A3": 15,
|
||||||
|
"A4": 14,
|
||||||
|
"A5": 8,
|
||||||
|
"LED": 13,
|
||||||
|
"TX": 39,
|
||||||
|
"RX": 38,
|
||||||
|
"T5": 5,
|
||||||
|
"T8": 8,
|
||||||
|
"T9": 9,
|
||||||
|
"T10": 10,
|
||||||
|
"T11": 11,
|
||||||
|
"T12": 12,
|
||||||
|
"T13": 13,
|
||||||
|
"T14": 14,
|
||||||
|
"DAC1": 17,
|
||||||
|
"DAC2": 18,
|
||||||
|
},
|
||||||
"firebeetle32": {"LED": 2},
|
"firebeetle32": {"LED": 2},
|
||||||
"fm-devkit": {
|
"fm-devkit": {
|
||||||
"D0": 34,
|
"D0": 34,
|
||||||
|
|
|
@ -18,7 +18,7 @@ _ESP_SDIO_PINS = {
|
||||||
11: "Flash Command",
|
11: "Flash Command",
|
||||||
}
|
}
|
||||||
|
|
||||||
_ESP32_STRAPPING_PINS = {0, 2, 4, 15}
|
_ESP32_STRAPPING_PINS = {0, 2, 4, 12, 15}
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
|
43
esphome/components/esp32/post_build.py.script
Normal file
43
esphome/components/esp32/post_build.py.script
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
# Source https://github.com/letscontrolit/ESPEasy/pull/3845#issuecomment-1005864664
|
||||||
|
|
||||||
|
import esptool
|
||||||
|
|
||||||
|
# pylint: disable=E0602
|
||||||
|
Import("env") # noqa
|
||||||
|
|
||||||
|
|
||||||
|
def esp32_create_combined_bin(source, target, env):
|
||||||
|
print("Generating combined binary for serial flashing")
|
||||||
|
app_offset = 0x10000
|
||||||
|
|
||||||
|
new_file_name = env.subst("$BUILD_DIR/${PROGNAME}-factory.bin")
|
||||||
|
sections = env.subst(env.get("FLASH_EXTRA_IMAGES"))
|
||||||
|
firmware_name = env.subst("$BUILD_DIR/${PROGNAME}.bin")
|
||||||
|
chip = env.get("BOARD_MCU")
|
||||||
|
flash_size = env.BoardConfig().get("upload.flash_size")
|
||||||
|
cmd = [
|
||||||
|
"--chip",
|
||||||
|
chip,
|
||||||
|
"merge_bin",
|
||||||
|
"-o",
|
||||||
|
new_file_name,
|
||||||
|
"--flash_size",
|
||||||
|
flash_size,
|
||||||
|
]
|
||||||
|
print(" Offset | File")
|
||||||
|
for section in sections:
|
||||||
|
sect_adr, sect_file = section.split(" ", 1)
|
||||||
|
print(f" - {sect_adr} | {sect_file}")
|
||||||
|
cmd += [sect_adr, sect_file]
|
||||||
|
|
||||||
|
print(f" - {hex(app_offset)} | {firmware_name}")
|
||||||
|
cmd += [hex(app_offset), firmware_name]
|
||||||
|
|
||||||
|
print()
|
||||||
|
print(f"Using esptool.py arguments: {' '.join(cmd)}")
|
||||||
|
print()
|
||||||
|
esptool.main(cmd)
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=E0602
|
||||||
|
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp32_create_combined_bin) # noqa
|
|
@ -483,6 +483,7 @@ optional<ESPBLEiBeacon> ESPBLEiBeacon::from_manufacturer_data(const ServiceData
|
||||||
}
|
}
|
||||||
|
|
||||||
void ESPBTDevice::parse_scan_rst(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m) {
|
void ESPBTDevice::parse_scan_rst(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m) {
|
||||||
|
this->scan_result_ = param;
|
||||||
for (uint8_t i = 0; i < ESP_BD_ADDR_LEN; i++)
|
for (uint8_t i = 0; i < ESP_BD_ADDR_LEN; i++)
|
||||||
this->address_[i] = param.bda[i];
|
this->address_[i] = param.bda[i];
|
||||||
this->address_type_ = param.ble_addr_type;
|
this->address_type_ = param.ble_addr_type;
|
||||||
|
@ -524,7 +525,7 @@ void ESPBTDevice::parse_scan_rst(const esp_ble_gap_cb_param_t::ble_scan_result_e
|
||||||
ESP_LOGVV(TAG, " Service UUID: %s", uuid.to_string().c_str());
|
ESP_LOGVV(TAG, " Service UUID: %s", uuid.to_string().c_str());
|
||||||
}
|
}
|
||||||
for (auto &data : this->manufacturer_datas_) {
|
for (auto &data : this->manufacturer_datas_) {
|
||||||
ESP_LOGVV(TAG, " Manufacturer data: %s", hexencode(data.data).c_str());
|
ESP_LOGVV(TAG, " Manufacturer data: %s", format_hex_pretty(data.data).c_str());
|
||||||
if (this->get_ibeacon().has_value()) {
|
if (this->get_ibeacon().has_value()) {
|
||||||
auto ibeacon = this->get_ibeacon().value();
|
auto ibeacon = this->get_ibeacon().value();
|
||||||
ESP_LOGVV(TAG, " iBeacon data:");
|
ESP_LOGVV(TAG, " iBeacon data:");
|
||||||
|
@ -537,10 +538,10 @@ void ESPBTDevice::parse_scan_rst(const esp_ble_gap_cb_param_t::ble_scan_result_e
|
||||||
for (auto &data : this->service_datas_) {
|
for (auto &data : this->service_datas_) {
|
||||||
ESP_LOGVV(TAG, " Service data:");
|
ESP_LOGVV(TAG, " Service data:");
|
||||||
ESP_LOGVV(TAG, " UUID: %s", data.uuid.to_string().c_str());
|
ESP_LOGVV(TAG, " UUID: %s", data.uuid.to_string().c_str());
|
||||||
ESP_LOGVV(TAG, " Data: %s", hexencode(data.data).c_str());
|
ESP_LOGVV(TAG, " Data: %s", format_hex_pretty(data.data).c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
ESP_LOGVV(TAG, "Adv data: %s", hexencode(param.ble_adv, param.adv_data_len + param.scan_rsp_len).c_str());
|
ESP_LOGVV(TAG, "Adv data: %s", format_hex_pretty(param.ble_adv, param.adv_data_len + param.scan_rsp_len).c_str());
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
void ESPBTDevice::parse_adv_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m) {
|
void ESPBTDevice::parse_adv_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m) {
|
||||||
|
|
|
@ -97,6 +97,8 @@ class ESPBTDevice {
|
||||||
|
|
||||||
const std::vector<ServiceData> &get_service_datas() const { return service_datas_; }
|
const std::vector<ServiceData> &get_service_datas() const { return service_datas_; }
|
||||||
|
|
||||||
|
const esp_ble_gap_cb_param_t::ble_scan_result_evt_param &get_scan_result() const { return scan_result_; }
|
||||||
|
|
||||||
optional<ESPBLEiBeacon> get_ibeacon() const {
|
optional<ESPBLEiBeacon> get_ibeacon() const {
|
||||||
for (auto &it : this->manufacturer_datas_) {
|
for (auto &it : this->manufacturer_datas_) {
|
||||||
auto res = ESPBLEiBeacon::from_manufacturer_data(it);
|
auto res = ESPBLEiBeacon::from_manufacturer_data(it);
|
||||||
|
@ -121,6 +123,7 @@ class ESPBTDevice {
|
||||||
std::vector<ESPBTUUID> service_uuids_;
|
std::vector<ESPBTUUID> service_uuids_;
|
||||||
std::vector<ServiceData> manufacturer_datas_{};
|
std::vector<ServiceData> manufacturer_datas_{};
|
||||||
std::vector<ServiceData> service_datas_{};
|
std::vector<ServiceData> service_datas_{};
|
||||||
|
esp_ble_gap_cb_param_t::ble_scan_result_evt_param scan_result_{};
|
||||||
};
|
};
|
||||||
|
|
||||||
class ESP32BLETracker;
|
class ESP32BLETracker;
|
||||||
|
|
|
@ -19,6 +19,8 @@ from esphome.cpp_helpers import setup_entity
|
||||||
|
|
||||||
DEPENDENCIES = ["esp32"]
|
DEPENDENCIES = ["esp32"]
|
||||||
|
|
||||||
|
AUTO_LOAD = ["psram"]
|
||||||
|
|
||||||
esp32_camera_ns = cg.esphome_ns.namespace("esp32_camera")
|
esp32_camera_ns = cg.esphome_ns.namespace("esp32_camera")
|
||||||
ESP32Camera = esp32_camera_ns.class_("ESP32Camera", cg.PollingComponent, cg.EntityBase)
|
ESP32Camera = esp32_camera_ns.class_("ESP32Camera", cg.PollingComponent, cg.EntityBase)
|
||||||
ESP32CameraFrameSize = esp32_camera_ns.enum("ESP32CameraFrameSize")
|
ESP32CameraFrameSize = esp32_camera_ns.enum("ESP32CameraFrameSize")
|
||||||
|
@ -57,6 +59,9 @@ CONF_IDLE_FRAMERATE = "idle_framerate"
|
||||||
CONF_JPEG_QUALITY = "jpeg_quality"
|
CONF_JPEG_QUALITY = "jpeg_quality"
|
||||||
CONF_VERTICAL_FLIP = "vertical_flip"
|
CONF_VERTICAL_FLIP = "vertical_flip"
|
||||||
CONF_HORIZONTAL_MIRROR = "horizontal_mirror"
|
CONF_HORIZONTAL_MIRROR = "horizontal_mirror"
|
||||||
|
CONF_AEC2 = "aec2"
|
||||||
|
CONF_AE_LEVEL = "ae_level"
|
||||||
|
CONF_AEC_VALUE = "aec_value"
|
||||||
CONF_SATURATION = "saturation"
|
CONF_SATURATION = "saturation"
|
||||||
CONF_TEST_PATTERN = "test_pattern"
|
CONF_TEST_PATTERN = "test_pattern"
|
||||||
|
|
||||||
|
@ -102,6 +107,9 @@ CONFIG_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(
|
||||||
cv.Optional(CONF_SATURATION, default=0): camera_range_param,
|
cv.Optional(CONF_SATURATION, default=0): camera_range_param,
|
||||||
cv.Optional(CONF_VERTICAL_FLIP, default=True): cv.boolean,
|
cv.Optional(CONF_VERTICAL_FLIP, default=True): cv.boolean,
|
||||||
cv.Optional(CONF_HORIZONTAL_MIRROR, default=True): cv.boolean,
|
cv.Optional(CONF_HORIZONTAL_MIRROR, default=True): cv.boolean,
|
||||||
|
cv.Optional(CONF_AEC2, default=False): cv.boolean,
|
||||||
|
cv.Optional(CONF_AE_LEVEL, default=0): camera_range_param,
|
||||||
|
cv.Optional(CONF_AEC_VALUE, default=300): cv.int_range(min=0, max=1200),
|
||||||
cv.Optional(CONF_TEST_PATTERN, default=False): cv.boolean,
|
cv.Optional(CONF_TEST_PATTERN, default=False): cv.boolean,
|
||||||
}
|
}
|
||||||
).extend(cv.COMPONENT_SCHEMA)
|
).extend(cv.COMPONENT_SCHEMA)
|
||||||
|
@ -116,6 +124,9 @@ SETTERS = {
|
||||||
CONF_JPEG_QUALITY: "set_jpeg_quality",
|
CONF_JPEG_QUALITY: "set_jpeg_quality",
|
||||||
CONF_VERTICAL_FLIP: "set_vertical_flip",
|
CONF_VERTICAL_FLIP: "set_vertical_flip",
|
||||||
CONF_HORIZONTAL_MIRROR: "set_horizontal_mirror",
|
CONF_HORIZONTAL_MIRROR: "set_horizontal_mirror",
|
||||||
|
CONF_AEC2: "set_aec2",
|
||||||
|
CONF_AE_LEVEL: "set_ae_level",
|
||||||
|
CONF_AEC_VALUE: "set_aec_value",
|
||||||
CONF_CONTRAST: "set_contrast",
|
CONF_CONTRAST: "set_contrast",
|
||||||
CONF_BRIGHTNESS: "set_brightness",
|
CONF_BRIGHTNESS: "set_brightness",
|
||||||
CONF_SATURATION: "set_saturation",
|
CONF_SATURATION: "set_saturation",
|
||||||
|
@ -144,9 +155,7 @@ async def to_code(config):
|
||||||
cg.add(var.set_frame_size(config[CONF_RESOLUTION]))
|
cg.add(var.set_frame_size(config[CONF_RESOLUTION]))
|
||||||
|
|
||||||
cg.add_define("USE_ESP32_CAMERA")
|
cg.add_define("USE_ESP32_CAMERA")
|
||||||
cg.add_build_flag("-DBOARD_HAS_PSRAM")
|
|
||||||
|
|
||||||
if CORE.using_esp_idf:
|
if CORE.using_esp_idf:
|
||||||
cg.add_library("espressif/esp32-camera", "1.0.0")
|
cg.add_library("espressif/esp32-camera", "1.0.0")
|
||||||
add_idf_sdkconfig_option("CONFIG_RTCIO_SUPPORT_RTC_GPIO_DESC", True)
|
add_idf_sdkconfig_option("CONFIG_RTCIO_SUPPORT_RTC_GPIO_DESC", True)
|
||||||
add_idf_sdkconfig_option("CONFIG_ESP32_SPIRAM_SUPPORT", True)
|
|
||||||
|
|
|
@ -26,6 +26,9 @@ void ESP32Camera::setup() {
|
||||||
sensor_t *s = esp_camera_sensor_get();
|
sensor_t *s = esp_camera_sensor_get();
|
||||||
s->set_vflip(s, this->vertical_flip_);
|
s->set_vflip(s, this->vertical_flip_);
|
||||||
s->set_hmirror(s, this->horizontal_mirror_);
|
s->set_hmirror(s, this->horizontal_mirror_);
|
||||||
|
s->set_aec2(s, this->aec2_); // 0 = disable , 1 = enable
|
||||||
|
s->set_ae_level(s, this->ae_level_); // -2 to 2
|
||||||
|
s->set_aec_value(s, this->aec_value_); // 0 to 1200
|
||||||
s->set_contrast(s, this->contrast_);
|
s->set_contrast(s, this->contrast_);
|
||||||
s->set_brightness(s, this->brightness_);
|
s->set_brightness(s, this->brightness_);
|
||||||
s->set_saturation(s, this->saturation_);
|
s->set_saturation(s, this->saturation_);
|
||||||
|
@ -46,9 +49,6 @@ void ESP32Camera::dump_config() {
|
||||||
ESP_LOGCONFIG(TAG, "ESP32 Camera:");
|
ESP_LOGCONFIG(TAG, "ESP32 Camera:");
|
||||||
ESP_LOGCONFIG(TAG, " Name: %s", this->name_.c_str());
|
ESP_LOGCONFIG(TAG, " Name: %s", this->name_.c_str());
|
||||||
ESP_LOGCONFIG(TAG, " Internal: %s", YESNO(this->internal_));
|
ESP_LOGCONFIG(TAG, " Internal: %s", YESNO(this->internal_));
|
||||||
#ifdef USE_ARDUINO
|
|
||||||
ESP_LOGCONFIG(TAG, " Board Has PSRAM: %s", YESNO(psramFound()));
|
|
||||||
#endif // USE_ARDUINO
|
|
||||||
ESP_LOGCONFIG(TAG, " Data Pins: D0:%d D1:%d D2:%d D3:%d D4:%d D5:%d D6:%d D7:%d", conf.pin_d0, conf.pin_d1,
|
ESP_LOGCONFIG(TAG, " Data Pins: D0:%d D1:%d D2:%d D3:%d D4:%d D5:%d D6:%d D7:%d", conf.pin_d0, conf.pin_d1,
|
||||||
conf.pin_d2, conf.pin_d3, conf.pin_d4, conf.pin_d5, conf.pin_d6, conf.pin_d7);
|
conf.pin_d2, conf.pin_d3, conf.pin_d4, conf.pin_d5, conf.pin_d6, conf.pin_d7);
|
||||||
ESP_LOGCONFIG(TAG, " VSYNC Pin: %d", conf.pin_vsync);
|
ESP_LOGCONFIG(TAG, " VSYNC Pin: %d", conf.pin_vsync);
|
||||||
|
@ -111,9 +111,9 @@ void ESP32Camera::dump_config() {
|
||||||
// ESP_LOGCONFIG(TAG, " Auto White Balance: %u", st.awb);
|
// ESP_LOGCONFIG(TAG, " Auto White Balance: %u", st.awb);
|
||||||
// ESP_LOGCONFIG(TAG, " Auto White Balance Gain: %u", st.awb_gain);
|
// ESP_LOGCONFIG(TAG, " Auto White Balance Gain: %u", st.awb_gain);
|
||||||
// ESP_LOGCONFIG(TAG, " Auto Exposure Control: %u", st.aec);
|
// ESP_LOGCONFIG(TAG, " Auto Exposure Control: %u", st.aec);
|
||||||
// ESP_LOGCONFIG(TAG, " Auto Exposure Control 2: %u", st.aec2);
|
ESP_LOGCONFIG(TAG, " Auto Exposure Control 2: %u", st.aec2);
|
||||||
// ESP_LOGCONFIG(TAG, " Auto Exposure Level: %d", st.ae_level);
|
ESP_LOGCONFIG(TAG, " Auto Exposure Level: %d", st.ae_level);
|
||||||
// ESP_LOGCONFIG(TAG, " Auto Exposure Value: %u", st.aec_value);
|
ESP_LOGCONFIG(TAG, " Auto Exposure Value: %u", st.aec_value);
|
||||||
// ESP_LOGCONFIG(TAG, " AGC: %u", st.agc);
|
// ESP_LOGCONFIG(TAG, " AGC: %u", st.agc);
|
||||||
// ESP_LOGCONFIG(TAG, " AGC Gain: %u", st.agc_gain);
|
// ESP_LOGCONFIG(TAG, " AGC Gain: %u", st.agc_gain);
|
||||||
// ESP_LOGCONFIG(TAG, " Gain Ceiling: %u", st.gainceiling);
|
// ESP_LOGCONFIG(TAG, " Gain Ceiling: %u", st.gainceiling);
|
||||||
|
@ -133,6 +133,13 @@ void ESP32Camera::loop() {
|
||||||
this->current_image_.reset();
|
this->current_image_.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// request idle image every idle_update_interval
|
||||||
|
const uint32_t now = millis();
|
||||||
|
if (this->idle_update_interval_ != 0 && now - this->last_idle_request_ > this->idle_update_interval_) {
|
||||||
|
this->last_idle_request_ = now;
|
||||||
|
this->request_image(IDLE);
|
||||||
|
}
|
||||||
|
|
||||||
// Check if we should fetch a new image
|
// Check if we should fetch a new image
|
||||||
if (!this->has_requested_image_())
|
if (!this->has_requested_image_())
|
||||||
return;
|
return;
|
||||||
|
@ -140,7 +147,6 @@ void ESP32Camera::loop() {
|
||||||
// image is still in use
|
// image is still in use
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const uint32_t now = millis();
|
|
||||||
if (now - this->last_update_ <= this->max_update_interval_)
|
if (now - this->last_update_ <= this->max_update_interval_)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -157,12 +163,12 @@ void ESP32Camera::loop() {
|
||||||
xQueueSend(this->framebuffer_return_queue_, &fb, portMAX_DELAY);
|
xQueueSend(this->framebuffer_return_queue_, &fb, portMAX_DELAY);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this->current_image_ = std::make_shared<CameraImage>(fb);
|
this->current_image_ = std::make_shared<CameraImage>(fb, this->single_requesters_ | this->stream_requesters_);
|
||||||
|
|
||||||
ESP_LOGD(TAG, "Got Image: len=%u", fb->len);
|
ESP_LOGD(TAG, "Got Image: len=%u", fb->len);
|
||||||
this->new_image_callback_.call(this->current_image_);
|
this->new_image_callback_.call(this->current_image_);
|
||||||
this->last_update_ = now;
|
this->last_update_ = now;
|
||||||
this->single_requester_ = false;
|
this->single_requesters_ = 0;
|
||||||
}
|
}
|
||||||
void ESP32Camera::framebuffer_task(void *pv) {
|
void ESP32Camera::framebuffer_task(void *pv) {
|
||||||
while (true) {
|
while (true) {
|
||||||
|
@ -250,29 +256,18 @@ void ESP32Camera::add_image_callback(std::function<void(std::shared_ptr<CameraIm
|
||||||
}
|
}
|
||||||
void ESP32Camera::set_vertical_flip(bool vertical_flip) { this->vertical_flip_ = vertical_flip; }
|
void ESP32Camera::set_vertical_flip(bool vertical_flip) { this->vertical_flip_ = vertical_flip; }
|
||||||
void ESP32Camera::set_horizontal_mirror(bool horizontal_mirror) { this->horizontal_mirror_ = horizontal_mirror; }
|
void ESP32Camera::set_horizontal_mirror(bool horizontal_mirror) { this->horizontal_mirror_ = horizontal_mirror; }
|
||||||
|
void ESP32Camera::set_aec2(bool aec2) { this->aec2_ = aec2; }
|
||||||
|
void ESP32Camera::set_ae_level(int ae_level) { this->ae_level_ = ae_level; }
|
||||||
|
void ESP32Camera::set_aec_value(uint32_t aec_value) { this->aec_value_ = aec_value; }
|
||||||
void ESP32Camera::set_contrast(int contrast) { this->contrast_ = contrast; }
|
void ESP32Camera::set_contrast(int contrast) { this->contrast_ = contrast; }
|
||||||
void ESP32Camera::set_brightness(int brightness) { this->brightness_ = brightness; }
|
void ESP32Camera::set_brightness(int brightness) { this->brightness_ = brightness; }
|
||||||
void ESP32Camera::set_saturation(int saturation) { this->saturation_ = saturation; }
|
void ESP32Camera::set_saturation(int saturation) { this->saturation_ = saturation; }
|
||||||
float ESP32Camera::get_setup_priority() const { return setup_priority::DATA; }
|
float ESP32Camera::get_setup_priority() const { return setup_priority::DATA; }
|
||||||
uint32_t ESP32Camera::hash_base() { return 3010542557UL; }
|
uint32_t ESP32Camera::hash_base() { return 3010542557UL; }
|
||||||
void ESP32Camera::request_image() { this->single_requester_ = true; }
|
void ESP32Camera::request_image(CameraRequester requester) { this->single_requesters_ |= 1 << requester; }
|
||||||
void ESP32Camera::request_stream() { this->last_stream_request_ = millis(); }
|
void ESP32Camera::start_stream(CameraRequester requester) { this->stream_requesters_ |= 1 << requester; }
|
||||||
bool ESP32Camera::has_requested_image_() const {
|
void ESP32Camera::stop_stream(CameraRequester requester) { this->stream_requesters_ &= ~(1 << requester); }
|
||||||
if (this->single_requester_)
|
bool ESP32Camera::has_requested_image_() const { return this->single_requesters_ || this->stream_requesters_; }
|
||||||
// single request
|
|
||||||
return true;
|
|
||||||
|
|
||||||
uint32_t now = millis();
|
|
||||||
if (now - this->last_stream_request_ < 5000)
|
|
||||||
// stream request
|
|
||||||
return true;
|
|
||||||
|
|
||||||
if (this->idle_update_interval_ != 0 && now - this->last_update_ > this->idle_update_interval_)
|
|
||||||
// idle update
|
|
||||||
return true;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
bool ESP32Camera::can_return_image_() const { return this->current_image_.use_count() == 1; }
|
bool ESP32Camera::can_return_image_() const { return this->current_image_.use_count() == 1; }
|
||||||
void ESP32Camera::set_max_update_interval(uint32_t max_update_interval) {
|
void ESP32Camera::set_max_update_interval(uint32_t max_update_interval) {
|
||||||
this->max_update_interval_ = max_update_interval;
|
this->max_update_interval_ = max_update_interval;
|
||||||
|
@ -301,7 +296,10 @@ uint8_t *CameraImageReader::peek_data_buffer() { return this->image_->get_data_b
|
||||||
camera_fb_t *CameraImage::get_raw_buffer() { return this->buffer_; }
|
camera_fb_t *CameraImage::get_raw_buffer() { return this->buffer_; }
|
||||||
uint8_t *CameraImage::get_data_buffer() { return this->buffer_->buf; }
|
uint8_t *CameraImage::get_data_buffer() { return this->buffer_->buf; }
|
||||||
size_t CameraImage::get_data_length() { return this->buffer_->len; }
|
size_t CameraImage::get_data_length() { return this->buffer_->len; }
|
||||||
CameraImage::CameraImage(camera_fb_t *buffer) : buffer_(buffer) {}
|
bool CameraImage::was_requested_by(CameraRequester requester) const {
|
||||||
|
return (this->requesters_ & (1 << requester)) != 0;
|
||||||
|
}
|
||||||
|
CameraImage::CameraImage(camera_fb_t *buffer, uint8_t requesters) : buffer_(buffer), requesters_(requesters) {}
|
||||||
|
|
||||||
} // namespace esp32_camera
|
} // namespace esp32_camera
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
|
@ -14,15 +14,19 @@ namespace esp32_camera {
|
||||||
|
|
||||||
class ESP32Camera;
|
class ESP32Camera;
|
||||||
|
|
||||||
|
enum CameraRequester { IDLE, API_REQUESTER, WEB_REQUESTER };
|
||||||
|
|
||||||
class CameraImage {
|
class CameraImage {
|
||||||
public:
|
public:
|
||||||
CameraImage(camera_fb_t *buffer);
|
CameraImage(camera_fb_t *buffer, uint8_t requester);
|
||||||
camera_fb_t *get_raw_buffer();
|
camera_fb_t *get_raw_buffer();
|
||||||
uint8_t *get_data_buffer();
|
uint8_t *get_data_buffer();
|
||||||
size_t get_data_length();
|
size_t get_data_length();
|
||||||
|
bool was_requested_by(CameraRequester requester) const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
camera_fb_t *buffer_;
|
camera_fb_t *buffer_;
|
||||||
|
uint8_t requesters_;
|
||||||
};
|
};
|
||||||
|
|
||||||
class CameraImageReader {
|
class CameraImageReader {
|
||||||
|
@ -67,6 +71,9 @@ class ESP32Camera : public Component, public EntityBase {
|
||||||
void set_power_down_pin(uint8_t pin);
|
void set_power_down_pin(uint8_t pin);
|
||||||
void set_vertical_flip(bool vertical_flip);
|
void set_vertical_flip(bool vertical_flip);
|
||||||
void set_horizontal_mirror(bool horizontal_mirror);
|
void set_horizontal_mirror(bool horizontal_mirror);
|
||||||
|
void set_aec2(bool aec2);
|
||||||
|
void set_ae_level(int ae_level);
|
||||||
|
void set_aec_value(uint32_t aec_value);
|
||||||
void set_contrast(int contrast);
|
void set_contrast(int contrast);
|
||||||
void set_brightness(int brightness);
|
void set_brightness(int brightness);
|
||||||
void set_saturation(int saturation);
|
void set_saturation(int saturation);
|
||||||
|
@ -78,8 +85,9 @@ class ESP32Camera : public Component, public EntityBase {
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
void add_image_callback(std::function<void(std::shared_ptr<CameraImage>)> &&f);
|
void add_image_callback(std::function<void(std::shared_ptr<CameraImage>)> &&f);
|
||||||
float get_setup_priority() const override;
|
float get_setup_priority() const override;
|
||||||
void request_stream();
|
void start_stream(CameraRequester requester);
|
||||||
void request_image();
|
void stop_stream(CameraRequester requester);
|
||||||
|
void request_image(CameraRequester requester);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
uint32_t hash_base() override;
|
uint32_t hash_base() override;
|
||||||
|
@ -91,6 +99,9 @@ class ESP32Camera : public Component, public EntityBase {
|
||||||
camera_config_t config_{};
|
camera_config_t config_{};
|
||||||
bool vertical_flip_{true};
|
bool vertical_flip_{true};
|
||||||
bool horizontal_mirror_{true};
|
bool horizontal_mirror_{true};
|
||||||
|
bool aec2_{false};
|
||||||
|
int ae_level_{0};
|
||||||
|
uint32_t aec_value_{300};
|
||||||
int contrast_{0};
|
int contrast_{0};
|
||||||
int brightness_{0};
|
int brightness_{0};
|
||||||
int saturation_{0};
|
int saturation_{0};
|
||||||
|
@ -98,13 +109,14 @@ class ESP32Camera : public Component, public EntityBase {
|
||||||
|
|
||||||
esp_err_t init_error_{ESP_OK};
|
esp_err_t init_error_{ESP_OK};
|
||||||
std::shared_ptr<CameraImage> current_image_;
|
std::shared_ptr<CameraImage> current_image_;
|
||||||
uint32_t last_stream_request_{0};
|
uint8_t single_requesters_{0};
|
||||||
bool single_requester_{false};
|
uint8_t stream_requesters_{0};
|
||||||
QueueHandle_t framebuffer_get_queue_;
|
QueueHandle_t framebuffer_get_queue_;
|
||||||
QueueHandle_t framebuffer_return_queue_;
|
QueueHandle_t framebuffer_return_queue_;
|
||||||
CallbackManager<void(std::shared_ptr<CameraImage>)> new_image_callback_;
|
CallbackManager<void(std::shared_ptr<CameraImage>)> new_image_callback_;
|
||||||
uint32_t max_update_interval_{1000};
|
uint32_t max_update_interval_{1000};
|
||||||
uint32_t idle_update_interval_{15000};
|
uint32_t idle_update_interval_{15000};
|
||||||
|
uint32_t last_idle_request_{0};
|
||||||
uint32_t last_update_{0};
|
uint32_t last_update_{0};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace esp32_camera_web_server {
|
namespace esp32_camera_web_server {
|
||||||
|
|
||||||
static const int IMAGE_REQUEST_TIMEOUT = 2000;
|
static const int IMAGE_REQUEST_TIMEOUT = 5000;
|
||||||
static const char *const TAG = "esp32_camera_web_server";
|
static const char *const TAG = "esp32_camera_web_server";
|
||||||
|
|
||||||
#define PART_BOUNDARY "123456789000000000000987654321"
|
#define PART_BOUNDARY "123456789000000000000987654321"
|
||||||
|
@ -68,7 +68,7 @@ void CameraWebServer::setup() {
|
||||||
httpd_register_uri_handler(this->httpd_, &uri);
|
httpd_register_uri_handler(this->httpd_, &uri);
|
||||||
|
|
||||||
esp32_camera::global_esp32_camera->add_image_callback([this](std::shared_ptr<esp32_camera::CameraImage> image) {
|
esp32_camera::global_esp32_camera->add_image_callback([this](std::shared_ptr<esp32_camera::CameraImage> image) {
|
||||||
if (this->running_) {
|
if (this->running_ && image->was_requested_by(esp32_camera::WEB_REQUESTER)) {
|
||||||
this->image_ = std::move(image);
|
this->image_ = std::move(image);
|
||||||
xSemaphoreGive(this->semaphore_);
|
xSemaphoreGive(this->semaphore_);
|
||||||
}
|
}
|
||||||
|
@ -169,11 +169,9 @@ esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) {
|
||||||
uint32_t last_frame = millis();
|
uint32_t last_frame = millis();
|
||||||
uint32_t frames = 0;
|
uint32_t frames = 0;
|
||||||
|
|
||||||
while (res == ESP_OK && this->running_) {
|
esp32_camera::global_esp32_camera->start_stream(esphome::esp32_camera::WEB_REQUESTER);
|
||||||
if (esp32_camera::global_esp32_camera != nullptr) {
|
|
||||||
esp32_camera::global_esp32_camera->request_stream();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
while (res == ESP_OK && this->running_) {
|
||||||
auto image = this->wait_for_image_();
|
auto image = this->wait_for_image_();
|
||||||
|
|
||||||
if (!image) {
|
if (!image) {
|
||||||
|
@ -204,6 +202,8 @@ esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) {
|
||||||
res = httpd_send_all(req, STREAM_ERROR, strlen(STREAM_ERROR));
|
res = httpd_send_all(req, STREAM_ERROR, strlen(STREAM_ERROR));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
esp32_camera::global_esp32_camera->stop_stream(esphome::esp32_camera::WEB_REQUESTER);
|
||||||
|
|
||||||
ESP_LOGI(TAG, "STREAM: closed. Frames: %u", frames);
|
ESP_LOGI(TAG, "STREAM: closed. Frames: %u", frames);
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
|
@ -212,9 +212,7 @@ esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) {
|
||||||
esp_err_t CameraWebServer::snapshot_handler_(struct httpd_req *req) {
|
esp_err_t CameraWebServer::snapshot_handler_(struct httpd_req *req) {
|
||||||
esp_err_t res = ESP_OK;
|
esp_err_t res = ESP_OK;
|
||||||
|
|
||||||
if (esp32_camera::global_esp32_camera != nullptr) {
|
esp32_camera::global_esp32_camera->request_image(esphome::esp32_camera::WEB_REQUESTER);
|
||||||
esp32_camera::global_esp32_camera->request_image();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto image = this->wait_for_image_();
|
auto image = this->wait_for_image_();
|
||||||
|
|
||||||
|
@ -233,9 +231,6 @@ esp_err_t CameraWebServer::snapshot_handler_(struct httpd_req *req) {
|
||||||
|
|
||||||
httpd_resp_set_hdr(req, "Content-Disposition", "inline; filename=capture.jpg");
|
httpd_resp_set_hdr(req, "Content-Disposition", "inline; filename=capture.jpg");
|
||||||
|
|
||||||
if (res == ESP_OK) {
|
|
||||||
res = httpd_resp_set_hdr(req, CONTENT_LENGTH, esphome::to_string(image->get_data_length()).c_str());
|
|
||||||
}
|
|
||||||
if (res == ESP_OK) {
|
if (res == ESP_OK) {
|
||||||
res = httpd_resp_send(req, (const char *) image->get_data_buffer(), image->get_data_length());
|
res = httpd_resp_send(req, (const char *) image->get_data_buffer(), image->get_data_length());
|
||||||
}
|
}
|
||||||
|
|
0
esphome/components/esp32_can/__init__.py
Normal file
0
esphome/components/esp32_can/__init__.py
Normal file
39
esphome/components/esp32_can/canbus.py
Normal file
39
esphome/components/esp32_can/canbus.py
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome import pins
|
||||||
|
from esphome.components import canbus
|
||||||
|
from esphome.const import CONF_ID, CONF_RX_PIN, CONF_TX_PIN
|
||||||
|
from esphome.components.canbus import CanbusComponent, CanSpeed, CONF_BIT_RATE
|
||||||
|
|
||||||
|
CODEOWNERS = ["@Sympatron"]
|
||||||
|
DEPENDENCIES = ["esp32"]
|
||||||
|
|
||||||
|
esp32_can_ns = cg.esphome_ns.namespace("esp32_can")
|
||||||
|
esp32_can = esp32_can_ns.class_("ESP32Can", CanbusComponent)
|
||||||
|
|
||||||
|
# Currently the driver only supports a subset of the bit rates defined in canbus
|
||||||
|
CAN_SPEEDS = {
|
||||||
|
"50KBPS": CanSpeed.CAN_50KBPS,
|
||||||
|
"100KBPS": CanSpeed.CAN_100KBPS,
|
||||||
|
"125KBPS": CanSpeed.CAN_125KBPS,
|
||||||
|
"250KBPS": CanSpeed.CAN_250KBPS,
|
||||||
|
"500KBPS": CanSpeed.CAN_500KBPS,
|
||||||
|
"1000KBPS": CanSpeed.CAN_1000KBPS,
|
||||||
|
}
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = canbus.CANBUS_SCHEMA.extend(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(esp32_can),
|
||||||
|
cv.Optional(CONF_BIT_RATE, default="125KBPS"): cv.enum(CAN_SPEEDS, upper=True),
|
||||||
|
cv.Required(CONF_RX_PIN): pins.internal_gpio_input_pin_number,
|
||||||
|
cv.Required(CONF_TX_PIN): pins.internal_gpio_output_pin_number,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
await canbus.register_canbus(var, config)
|
||||||
|
|
||||||
|
cg.add(var.set_rx(config[CONF_RX_PIN]))
|
||||||
|
cg.add(var.set_tx(config[CONF_TX_PIN]))
|
123
esphome/components/esp32_can/esp32_can.cpp
Normal file
123
esphome/components/esp32_can/esp32_can.cpp
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
#ifdef USE_ESP32
|
||||||
|
#include "esp32_can.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
#include <driver/can.h>
|
||||||
|
|
||||||
|
// WORKAROUND, because CAN_IO_UNUSED is just defined as (-1) in this version
|
||||||
|
// of the framework which does not work with -fpermissive
|
||||||
|
#undef CAN_IO_UNUSED
|
||||||
|
#define CAN_IO_UNUSED ((gpio_num_t) -1)
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace esp32_can {
|
||||||
|
|
||||||
|
static const char *const TAG = "esp32_can";
|
||||||
|
|
||||||
|
static bool get_bitrate(canbus::CanSpeed bitrate, can_timing_config_t *t_config) {
|
||||||
|
switch (bitrate) {
|
||||||
|
case canbus::CAN_50KBPS:
|
||||||
|
*t_config = (can_timing_config_t) CAN_TIMING_CONFIG_50KBITS();
|
||||||
|
return true;
|
||||||
|
case canbus::CAN_100KBPS:
|
||||||
|
*t_config = (can_timing_config_t) CAN_TIMING_CONFIG_100KBITS();
|
||||||
|
return true;
|
||||||
|
case canbus::CAN_125KBPS:
|
||||||
|
*t_config = (can_timing_config_t) CAN_TIMING_CONFIG_125KBITS();
|
||||||
|
return true;
|
||||||
|
case canbus::CAN_250KBPS:
|
||||||
|
*t_config = (can_timing_config_t) CAN_TIMING_CONFIG_250KBITS();
|
||||||
|
return true;
|
||||||
|
case canbus::CAN_500KBPS:
|
||||||
|
*t_config = (can_timing_config_t) CAN_TIMING_CONFIG_500KBITS();
|
||||||
|
return true;
|
||||||
|
case canbus::CAN_1000KBPS:
|
||||||
|
*t_config = (can_timing_config_t) CAN_TIMING_CONFIG_1MBITS();
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ESP32Can::setup_internal() {
|
||||||
|
can_general_config_t g_config =
|
||||||
|
CAN_GENERAL_CONFIG_DEFAULT((gpio_num_t) this->tx_, (gpio_num_t) this->rx_, CAN_MODE_NORMAL);
|
||||||
|
can_filter_config_t f_config = CAN_FILTER_CONFIG_ACCEPT_ALL();
|
||||||
|
can_timing_config_t t_config;
|
||||||
|
|
||||||
|
if (!get_bitrate(this->bit_rate_, &t_config)) {
|
||||||
|
// invalid bit rate
|
||||||
|
this->mark_failed();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Install CAN driver
|
||||||
|
if (can_driver_install(&g_config, &t_config, &f_config) != ESP_OK) {
|
||||||
|
// Failed to install driver
|
||||||
|
this->mark_failed();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start CAN driver
|
||||||
|
if (can_start() != ESP_OK) {
|
||||||
|
// Failed to start driver
|
||||||
|
this->mark_failed();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
canbus::Error ESP32Can::send_message(struct canbus::CanFrame *frame) {
|
||||||
|
if (frame->can_data_length_code > canbus::CAN_MAX_DATA_LENGTH) {
|
||||||
|
return canbus::ERROR_FAILTX;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t flags = CAN_MSG_FLAG_NONE;
|
||||||
|
if (frame->use_extended_id) {
|
||||||
|
flags |= CAN_MSG_FLAG_EXTD;
|
||||||
|
}
|
||||||
|
if (frame->remote_transmission_request) {
|
||||||
|
flags |= CAN_MSG_FLAG_RTR;
|
||||||
|
}
|
||||||
|
|
||||||
|
can_message_t message = {
|
||||||
|
.flags = flags,
|
||||||
|
.identifier = frame->can_id,
|
||||||
|
.data_length_code = frame->can_data_length_code,
|
||||||
|
};
|
||||||
|
if (!frame->remote_transmission_request) {
|
||||||
|
memcpy(message.data, frame->data, frame->can_data_length_code);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (can_transmit(&message, pdMS_TO_TICKS(1000)) == ESP_OK) {
|
||||||
|
return canbus::ERROR_OK;
|
||||||
|
} else {
|
||||||
|
return canbus::ERROR_ALLTXBUSY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
canbus::Error ESP32Can::read_message(struct canbus::CanFrame *frame) {
|
||||||
|
can_message_t message;
|
||||||
|
|
||||||
|
if (can_receive(&message, 0) != ESP_OK) {
|
||||||
|
return canbus::ERROR_NOMSG;
|
||||||
|
}
|
||||||
|
|
||||||
|
frame->can_id = message.identifier;
|
||||||
|
frame->use_extended_id = message.flags & CAN_MSG_FLAG_EXTD;
|
||||||
|
frame->remote_transmission_request = message.flags & CAN_MSG_FLAG_RTR;
|
||||||
|
frame->can_data_length_code = message.data_length_code;
|
||||||
|
|
||||||
|
if (!frame->remote_transmission_request) {
|
||||||
|
size_t dlc =
|
||||||
|
message.data_length_code < canbus::CAN_MAX_DATA_LENGTH ? message.data_length_code : canbus::CAN_MAX_DATA_LENGTH;
|
||||||
|
memcpy(frame->data, message.data, dlc);
|
||||||
|
}
|
||||||
|
|
||||||
|
return canbus::ERROR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace esp32_can
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
|
#endif
|
29
esphome/components/esp32_can/esp32_can.h
Normal file
29
esphome/components/esp32_can/esp32_can.h
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef USE_ESP32
|
||||||
|
|
||||||
|
#include "esphome/components/canbus/canbus.h"
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace esp32_can {
|
||||||
|
|
||||||
|
class ESP32Can : public canbus::Canbus {
|
||||||
|
public:
|
||||||
|
void set_rx(int rx) { rx_ = rx; }
|
||||||
|
void set_tx(int tx) { tx_ = tx; }
|
||||||
|
ESP32Can(){};
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool setup_internal() override;
|
||||||
|
canbus::Error send_message(struct canbus::CanFrame *frame) override;
|
||||||
|
canbus::Error read_message(struct canbus::CanFrame *frame) override;
|
||||||
|
|
||||||
|
int rx_{-1};
|
||||||
|
int tx_{-1};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace esp32_can
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
|
#endif
|
|
@ -219,7 +219,7 @@ void ESP32ImprovComponent::dump_config() {
|
||||||
void ESP32ImprovComponent::process_incoming_data_() {
|
void ESP32ImprovComponent::process_incoming_data_() {
|
||||||
uint8_t length = this->incoming_data_[1];
|
uint8_t length = this->incoming_data_[1];
|
||||||
|
|
||||||
ESP_LOGD(TAG, "Processing bytes - %s", hexencode(this->incoming_data_).c_str());
|
ESP_LOGD(TAG, "Processing bytes - %s", format_hex_pretty(this->incoming_data_).c_str());
|
||||||
if (this->incoming_data_.size() - 3 == length) {
|
if (this->incoming_data_.size() - 3 == length) {
|
||||||
this->set_error_(improv::ERROR_NONE);
|
this->set_error_(improv::ERROR_NONE);
|
||||||
improv::ImprovCommand command = improv::parse_improv_data(this->incoming_data_);
|
improv::ImprovCommand command = improv::parse_improv_data(this->incoming_data_);
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_BOARD,
|
CONF_BOARD,
|
||||||
|
@ -14,6 +15,7 @@ from esphome.const import (
|
||||||
from esphome.core import CORE, coroutine_with_priority
|
from esphome.core import CORE, coroutine_with_priority
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
|
from esphome.helpers import copy_file_if_changed
|
||||||
|
|
||||||
from .const import CONF_RESTORE_FROM_FLASH, KEY_BOARD, KEY_ESP8266, esp8266_ns
|
from .const import CONF_RESTORE_FROM_FLASH, KEY_BOARD, KEY_ESP8266, esp8266_ns
|
||||||
from .boards import ESP8266_FLASH_SIZES, ESP8266_LD_SCRIPTS
|
from .boards import ESP8266_FLASH_SIZES, ESP8266_LD_SCRIPTS
|
||||||
|
@ -158,6 +160,8 @@ async def to_code(config):
|
||||||
cg.add_define("ESPHOME_BOARD", config[CONF_BOARD])
|
cg.add_define("ESPHOME_BOARD", config[CONF_BOARD])
|
||||||
cg.add_define("ESPHOME_VARIANT", "ESP8266")
|
cg.add_define("ESPHOME_VARIANT", "ESP8266")
|
||||||
|
|
||||||
|
cg.add_platformio_option("extra_scripts", ["post:post_build.py"])
|
||||||
|
|
||||||
conf = config[CONF_FRAMEWORK]
|
conf = config[CONF_FRAMEWORK]
|
||||||
cg.add_platformio_option("framework", "arduino")
|
cg.add_platformio_option("framework", "arduino")
|
||||||
cg.add_build_flag("-DUSE_ARDUINO")
|
cg.add_build_flag("-DUSE_ARDUINO")
|
||||||
|
@ -210,3 +214,14 @@ async def to_code(config):
|
||||||
|
|
||||||
if ld_script is not None:
|
if ld_script is not None:
|
||||||
cg.add_platformio_option("board_build.ldscript", ld_script)
|
cg.add_platformio_option("board_build.ldscript", ld_script)
|
||||||
|
|
||||||
|
|
||||||
|
# Called by writer.py
|
||||||
|
def copy_files():
|
||||||
|
|
||||||
|
dir = os.path.dirname(__file__)
|
||||||
|
post_build_file = os.path.join(dir, "post_build.py.script")
|
||||||
|
copy_file_if_changed(
|
||||||
|
post_build_file,
|
||||||
|
CORE.relative_build_path("post_build.py"),
|
||||||
|
)
|
||||||
|
|
15
esphome/components/esp8266/post_build.py.script
Normal file
15
esphome/components/esp8266/post_build.py.script
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
# pylint: disable=E0602
|
||||||
|
Import("env") # noqa
|
||||||
|
|
||||||
|
|
||||||
|
def esp8266_copy_factory_bin(source, target, env):
|
||||||
|
firmware_name = env.subst("$BUILD_DIR/${PROGNAME}.bin")
|
||||||
|
new_file_name = env.subst("$BUILD_DIR/${PROGNAME}-factory.bin")
|
||||||
|
|
||||||
|
shutil.copyfile(firmware_name, new_file_name)
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=E0602
|
||||||
|
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp8266_copy_factory_bin) # noqa
|
|
@ -79,7 +79,7 @@ void EZOSensor::loop() {
|
||||||
if (buf[i] == ',')
|
if (buf[i] == ',')
|
||||||
buf[i] = '\0';
|
buf[i] = '\0';
|
||||||
|
|
||||||
float val = parse_number<float>((char *) &buf[1], sizeof(buf) - 2).value_or(0);
|
float val = parse_number<float>((char *) &buf[1]).value_or(0);
|
||||||
this->publish_state(val);
|
this->publish_state(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
0
esphome/components/growatt_solar/__init__.py
Normal file
0
esphome/components/growatt_solar/__init__.py
Normal file
69
esphome/components/growatt_solar/growatt_solar.cpp
Normal file
69
esphome/components/growatt_solar/growatt_solar.cpp
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
#include "growatt_solar.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace growatt_solar {
|
||||||
|
|
||||||
|
static const char *const TAG = "growatt_solar";
|
||||||
|
|
||||||
|
static const uint8_t MODBUS_CMD_READ_IN_REGISTERS = 0x04;
|
||||||
|
static const uint8_t MODBUS_REGISTER_COUNT = 33;
|
||||||
|
|
||||||
|
void GrowattSolar::update() { this->send(MODBUS_CMD_READ_IN_REGISTERS, 0, MODBUS_REGISTER_COUNT); }
|
||||||
|
|
||||||
|
void GrowattSolar::on_modbus_data(const std::vector<uint8_t> &data) {
|
||||||
|
auto publish_1_reg_sensor_state = [&](sensor::Sensor *sensor, size_t i, float unit) -> void {
|
||||||
|
if (sensor == nullptr)
|
||||||
|
return;
|
||||||
|
float value = encode_uint16(data[i * 2], data[i * 2 + 1]) * unit;
|
||||||
|
sensor->publish_state(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
auto publish_2_reg_sensor_state = [&](sensor::Sensor *sensor, size_t reg1, size_t reg2, float unit) -> void {
|
||||||
|
float value = ((encode_uint16(data[reg1 * 2], data[reg1 * 2 + 1]) << 16) +
|
||||||
|
encode_uint16(data[reg2 * 2], data[reg2 * 2 + 1])) *
|
||||||
|
unit;
|
||||||
|
if (sensor != nullptr)
|
||||||
|
sensor->publish_state(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
publish_1_reg_sensor_state(this->inverter_status_, 0, 1);
|
||||||
|
|
||||||
|
publish_2_reg_sensor_state(this->pv_active_power_sensor_, 1, 2, ONE_DEC_UNIT);
|
||||||
|
|
||||||
|
publish_1_reg_sensor_state(this->pvs_[0].voltage_sensor_, 3, ONE_DEC_UNIT);
|
||||||
|
publish_1_reg_sensor_state(this->pvs_[0].current_sensor_, 4, ONE_DEC_UNIT);
|
||||||
|
publish_2_reg_sensor_state(this->pvs_[0].active_power_sensor_, 5, 6, ONE_DEC_UNIT);
|
||||||
|
|
||||||
|
publish_1_reg_sensor_state(this->pvs_[1].voltage_sensor_, 7, ONE_DEC_UNIT);
|
||||||
|
publish_1_reg_sensor_state(this->pvs_[1].current_sensor_, 8, ONE_DEC_UNIT);
|
||||||
|
publish_2_reg_sensor_state(this->pvs_[1].active_power_sensor_, 9, 10, ONE_DEC_UNIT);
|
||||||
|
|
||||||
|
publish_2_reg_sensor_state(this->grid_active_power_sensor_, 11, 12, ONE_DEC_UNIT);
|
||||||
|
publish_1_reg_sensor_state(this->grid_frequency_sensor_, 13, TWO_DEC_UNIT);
|
||||||
|
|
||||||
|
publish_1_reg_sensor_state(this->phases_[0].voltage_sensor_, 14, ONE_DEC_UNIT);
|
||||||
|
publish_1_reg_sensor_state(this->phases_[0].current_sensor_, 15, ONE_DEC_UNIT);
|
||||||
|
publish_2_reg_sensor_state(this->phases_[0].active_power_sensor_, 16, 17, ONE_DEC_UNIT);
|
||||||
|
|
||||||
|
publish_1_reg_sensor_state(this->phases_[1].voltage_sensor_, 18, ONE_DEC_UNIT);
|
||||||
|
publish_1_reg_sensor_state(this->phases_[1].current_sensor_, 19, ONE_DEC_UNIT);
|
||||||
|
publish_2_reg_sensor_state(this->phases_[1].active_power_sensor_, 20, 21, ONE_DEC_UNIT);
|
||||||
|
|
||||||
|
publish_1_reg_sensor_state(this->phases_[2].voltage_sensor_, 22, ONE_DEC_UNIT);
|
||||||
|
publish_1_reg_sensor_state(this->phases_[2].current_sensor_, 23, ONE_DEC_UNIT);
|
||||||
|
publish_2_reg_sensor_state(this->phases_[2].active_power_sensor_, 24, 25, ONE_DEC_UNIT);
|
||||||
|
|
||||||
|
publish_2_reg_sensor_state(this->today_production_, 26, 27, ONE_DEC_UNIT);
|
||||||
|
publish_2_reg_sensor_state(this->total_energy_production_, 28, 29, ONE_DEC_UNIT);
|
||||||
|
|
||||||
|
publish_1_reg_sensor_state(this->inverter_module_temp_, 32, ONE_DEC_UNIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GrowattSolar::dump_config() {
|
||||||
|
ESP_LOGCONFIG(TAG, "GROWATT Solar:");
|
||||||
|
ESP_LOGCONFIG(TAG, " Address: 0x%02X", this->address_);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace growatt_solar
|
||||||
|
} // namespace esphome
|
73
esphome/components/growatt_solar/growatt_solar.h
Normal file
73
esphome/components/growatt_solar/growatt_solar.h
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
#include "esphome/components/modbus/modbus.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace growatt_solar {
|
||||||
|
|
||||||
|
static const float TWO_DEC_UNIT = 0.01;
|
||||||
|
static const float ONE_DEC_UNIT = 0.1;
|
||||||
|
|
||||||
|
class GrowattSolar : public PollingComponent, public modbus::ModbusDevice {
|
||||||
|
public:
|
||||||
|
void update() override;
|
||||||
|
void on_modbus_data(const std::vector<uint8_t> &data) override;
|
||||||
|
void dump_config() override;
|
||||||
|
|
||||||
|
void set_inverter_status_sensor(sensor::Sensor *sensor) { this->inverter_status_ = sensor; }
|
||||||
|
|
||||||
|
void set_grid_frequency_sensor(sensor::Sensor *sensor) { this->grid_frequency_sensor_ = sensor; }
|
||||||
|
void set_grid_active_power_sensor(sensor::Sensor *sensor) { this->grid_active_power_sensor_ = sensor; }
|
||||||
|
void set_pv_active_power_sensor(sensor::Sensor *sensor) { this->pv_active_power_sensor_ = sensor; }
|
||||||
|
|
||||||
|
void set_today_production_sensor(sensor::Sensor *sensor) { this->today_production_ = sensor; }
|
||||||
|
void set_total_energy_production_sensor(sensor::Sensor *sensor) { this->total_energy_production_ = sensor; }
|
||||||
|
void set_inverter_module_temp_sensor(sensor::Sensor *sensor) { this->inverter_module_temp_ = sensor; }
|
||||||
|
|
||||||
|
void set_voltage_sensor(uint8_t phase, sensor::Sensor *voltage_sensor) {
|
||||||
|
this->phases_[phase].voltage_sensor_ = voltage_sensor;
|
||||||
|
}
|
||||||
|
void set_current_sensor(uint8_t phase, sensor::Sensor *current_sensor) {
|
||||||
|
this->phases_[phase].current_sensor_ = current_sensor;
|
||||||
|
}
|
||||||
|
void set_active_power_sensor(uint8_t phase, sensor::Sensor *active_power_sensor) {
|
||||||
|
this->phases_[phase].active_power_sensor_ = active_power_sensor;
|
||||||
|
}
|
||||||
|
void set_voltage_sensor_pv(uint8_t pv, sensor::Sensor *voltage_sensor) {
|
||||||
|
this->pvs_[pv].voltage_sensor_ = voltage_sensor;
|
||||||
|
}
|
||||||
|
void set_current_sensor_pv(uint8_t pv, sensor::Sensor *current_sensor) {
|
||||||
|
this->pvs_[pv].current_sensor_ = current_sensor;
|
||||||
|
}
|
||||||
|
void set_active_power_sensor_pv(uint8_t pv, sensor::Sensor *active_power_sensor) {
|
||||||
|
this->pvs_[pv].active_power_sensor_ = active_power_sensor;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
struct GrowattPhase {
|
||||||
|
sensor::Sensor *voltage_sensor_{nullptr};
|
||||||
|
sensor::Sensor *current_sensor_{nullptr};
|
||||||
|
sensor::Sensor *active_power_sensor_{nullptr};
|
||||||
|
} phases_[3];
|
||||||
|
struct GrowattPV {
|
||||||
|
sensor::Sensor *voltage_sensor_{nullptr};
|
||||||
|
sensor::Sensor *current_sensor_{nullptr};
|
||||||
|
sensor::Sensor *active_power_sensor_{nullptr};
|
||||||
|
} pvs_[2];
|
||||||
|
|
||||||
|
sensor::Sensor *inverter_status_{nullptr};
|
||||||
|
|
||||||
|
sensor::Sensor *grid_frequency_sensor_{nullptr};
|
||||||
|
sensor::Sensor *grid_active_power_sensor_{nullptr};
|
||||||
|
|
||||||
|
sensor::Sensor *pv_active_power_sensor_{nullptr};
|
||||||
|
|
||||||
|
sensor::Sensor *today_production_{nullptr};
|
||||||
|
sensor::Sensor *total_energy_production_{nullptr};
|
||||||
|
sensor::Sensor *inverter_module_temp_{nullptr};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace growatt_solar
|
||||||
|
} // namespace esphome
|
201
esphome/components/growatt_solar/sensor.py
Normal file
201
esphome/components/growatt_solar/sensor.py
Normal file
|
@ -0,0 +1,201 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import sensor, modbus
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_ACTIVE_POWER,
|
||||||
|
CONF_CURRENT,
|
||||||
|
CONF_FREQUENCY,
|
||||||
|
CONF_ID,
|
||||||
|
CONF_VOLTAGE,
|
||||||
|
DEVICE_CLASS_CURRENT,
|
||||||
|
DEVICE_CLASS_ENERGY,
|
||||||
|
DEVICE_CLASS_POWER,
|
||||||
|
DEVICE_CLASS_VOLTAGE,
|
||||||
|
ICON_CURRENT_AC,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
STATE_CLASS_TOTAL_INCREASING,
|
||||||
|
UNIT_AMPERE,
|
||||||
|
UNIT_CELSIUS,
|
||||||
|
UNIT_HERTZ,
|
||||||
|
UNIT_VOLT,
|
||||||
|
UNIT_WATT,
|
||||||
|
)
|
||||||
|
|
||||||
|
CONF_PHASE_A = "phase_a"
|
||||||
|
CONF_PHASE_B = "phase_b"
|
||||||
|
CONF_PHASE_C = "phase_c"
|
||||||
|
|
||||||
|
CONF_ENERGY_PRODUCTION_DAY = "energy_production_day"
|
||||||
|
CONF_TOTAL_ENERGY_PRODUCTION = "total_energy_production"
|
||||||
|
CONF_TOTAL_GENERATION_TIME = "total_generation_time"
|
||||||
|
CONF_TODAY_GENERATION_TIME = "today_generation_time"
|
||||||
|
CONF_PV1 = "pv1"
|
||||||
|
CONF_PV2 = "pv2"
|
||||||
|
UNIT_KILOWATT_HOURS = "kWh"
|
||||||
|
UNIT_HOURS = "h"
|
||||||
|
UNIT_KOHM = "kΩ"
|
||||||
|
UNIT_MILLIAMPERE = "mA"
|
||||||
|
|
||||||
|
CONF_INVERTER_STATUS = "inverter_status"
|
||||||
|
CONF_PV_ACTIVE_POWER = "pv_active_power"
|
||||||
|
CONF_INVERTER_MODULE_TEMP = "inverter_module_temp"
|
||||||
|
|
||||||
|
|
||||||
|
AUTO_LOAD = ["modbus"]
|
||||||
|
CODEOWNERS = ["@leeuwte"]
|
||||||
|
|
||||||
|
growatt_solar_ns = cg.esphome_ns.namespace("growatt_solar")
|
||||||
|
GrowattSolar = growatt_solar_ns.class_(
|
||||||
|
"GrowattSolar", cg.PollingComponent, modbus.ModbusDevice
|
||||||
|
)
|
||||||
|
|
||||||
|
PHASE_SENSORS = {
|
||||||
|
CONF_VOLTAGE: sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_VOLT,
|
||||||
|
accuracy_decimals=2,
|
||||||
|
device_class=DEVICE_CLASS_VOLTAGE,
|
||||||
|
),
|
||||||
|
CONF_CURRENT: sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_AMPERE,
|
||||||
|
accuracy_decimals=2,
|
||||||
|
device_class=DEVICE_CLASS_CURRENT,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
),
|
||||||
|
CONF_ACTIVE_POWER: sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_WATT,
|
||||||
|
accuracy_decimals=0,
|
||||||
|
device_class=DEVICE_CLASS_POWER,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
PV_SENSORS = {
|
||||||
|
CONF_VOLTAGE: sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_VOLT,
|
||||||
|
accuracy_decimals=2,
|
||||||
|
device_class=DEVICE_CLASS_VOLTAGE,
|
||||||
|
),
|
||||||
|
CONF_CURRENT: sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_AMPERE,
|
||||||
|
accuracy_decimals=2,
|
||||||
|
device_class=DEVICE_CLASS_CURRENT,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
),
|
||||||
|
CONF_ACTIVE_POWER: sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_WATT,
|
||||||
|
accuracy_decimals=0,
|
||||||
|
device_class=DEVICE_CLASS_POWER,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
PHASE_SCHEMA = cv.Schema(
|
||||||
|
{cv.Optional(sensor): schema for sensor, schema in PHASE_SENSORS.items()}
|
||||||
|
)
|
||||||
|
PV_SCHEMA = cv.Schema(
|
||||||
|
{cv.Optional(sensor): schema for sensor, schema in PV_SENSORS.items()}
|
||||||
|
)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = (
|
||||||
|
cv.Schema(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(GrowattSolar),
|
||||||
|
cv.Optional(CONF_PHASE_A): PHASE_SCHEMA,
|
||||||
|
cv.Optional(CONF_PHASE_B): PHASE_SCHEMA,
|
||||||
|
cv.Optional(CONF_PHASE_C): PHASE_SCHEMA,
|
||||||
|
cv.Optional(CONF_PV1): PV_SCHEMA,
|
||||||
|
cv.Optional(CONF_PV2): PV_SCHEMA,
|
||||||
|
cv.Optional(CONF_INVERTER_STATUS): sensor.sensor_schema(),
|
||||||
|
cv.Optional(CONF_FREQUENCY): sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_HERTZ,
|
||||||
|
icon=ICON_CURRENT_AC,
|
||||||
|
accuracy_decimals=2,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_ACTIVE_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_PV_ACTIVE_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_PRODUCTION_DAY): sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_KILOWATT_HOURS,
|
||||||
|
accuracy_decimals=2,
|
||||||
|
device_class=DEVICE_CLASS_ENERGY,
|
||||||
|
state_class=STATE_CLASS_TOTAL_INCREASING,
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_TOTAL_ENERGY_PRODUCTION): sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_KILOWATT_HOURS,
|
||||||
|
accuracy_decimals=0,
|
||||||
|
device_class=DEVICE_CLASS_ENERGY,
|
||||||
|
state_class=STATE_CLASS_TOTAL_INCREASING,
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_INVERTER_MODULE_TEMP): sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_CELSIUS,
|
||||||
|
accuracy_decimals=1,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(cv.polling_component_schema("10s"))
|
||||||
|
.extend(modbus.modbus_device_schema(0x01))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
await cg.register_component(var, config)
|
||||||
|
await modbus.register_modbus_device(var, config)
|
||||||
|
|
||||||
|
if CONF_INVERTER_STATUS in config:
|
||||||
|
sens = await sensor.new_sensor(config[CONF_INVERTER_STATUS])
|
||||||
|
cg.add(var.set_inverter_status_sensor(sens))
|
||||||
|
|
||||||
|
if CONF_FREQUENCY in config:
|
||||||
|
sens = await sensor.new_sensor(config[CONF_FREQUENCY])
|
||||||
|
cg.add(var.set_grid_frequency_sensor(sens))
|
||||||
|
|
||||||
|
if CONF_ACTIVE_POWER in config:
|
||||||
|
sens = await sensor.new_sensor(config[CONF_ACTIVE_POWER])
|
||||||
|
cg.add(var.set_grid_active_power_sensor(sens))
|
||||||
|
|
||||||
|
if CONF_PV_ACTIVE_POWER in config:
|
||||||
|
sens = await sensor.new_sensor(config[CONF_PV_ACTIVE_POWER])
|
||||||
|
cg.add(var.set_pv_active_power_sensor(sens))
|
||||||
|
|
||||||
|
if CONF_ENERGY_PRODUCTION_DAY in config:
|
||||||
|
sens = await sensor.new_sensor(config[CONF_ENERGY_PRODUCTION_DAY])
|
||||||
|
cg.add(var.set_today_production_sensor(sens))
|
||||||
|
|
||||||
|
if CONF_TOTAL_ENERGY_PRODUCTION in config:
|
||||||
|
sens = await sensor.new_sensor(config[CONF_TOTAL_ENERGY_PRODUCTION])
|
||||||
|
cg.add(var.set_total_energy_production_sensor(sens))
|
||||||
|
|
||||||
|
if CONF_INVERTER_MODULE_TEMP in config:
|
||||||
|
sens = await sensor.new_sensor(config[CONF_INVERTER_MODULE_TEMP])
|
||||||
|
cg.add(var.set_inverter_module_temp_sensor(sens))
|
||||||
|
|
||||||
|
for i, phase in enumerate([CONF_PHASE_A, CONF_PHASE_B, CONF_PHASE_C]):
|
||||||
|
if phase not in config:
|
||||||
|
continue
|
||||||
|
|
||||||
|
phase_config = config[phase]
|
||||||
|
for sensor_type in PHASE_SENSORS:
|
||||||
|
if sensor_type in phase_config:
|
||||||
|
sens = await sensor.new_sensor(phase_config[sensor_type])
|
||||||
|
cg.add(getattr(var, f"set_{sensor_type}_sensor")(i, sens))
|
||||||
|
|
||||||
|
for i, pv in enumerate([CONF_PV1, CONF_PV2]):
|
||||||
|
if pv not in config:
|
||||||
|
continue
|
||||||
|
|
||||||
|
pv_config = config[pv]
|
||||||
|
for sensor_type in pv_config:
|
||||||
|
if sensor_type in pv_config:
|
||||||
|
sens = await sensor.new_sensor(pv_config[sensor_type])
|
||||||
|
cg.add(getattr(var, f"set_{sensor_type}_sensor_pv")(i, sens))
|
|
@ -21,7 +21,9 @@ void HDC1080Component::setup() {
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!this->write_bytes(HDC1080_CMD_CONFIGURATION, data, 2)) {
|
if (!this->write_bytes(HDC1080_CMD_CONFIGURATION, data, 2)) {
|
||||||
this->mark_failed();
|
// as instruction is same as powerup defaults (for now), interpret as warning if this fails
|
||||||
|
ESP_LOGW(TAG, "HDC1080 initial config instruction error");
|
||||||
|
this->status_set_warning();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -109,8 +109,8 @@ def to_code(config):
|
||||||
cg.add(var.set_protocol(config[CONF_PROTOCOL]))
|
cg.add(var.set_protocol(config[CONF_PROTOCOL]))
|
||||||
cg.add(var.set_horizontal_default(config[CONF_HORIZONTAL_DEFAULT]))
|
cg.add(var.set_horizontal_default(config[CONF_HORIZONTAL_DEFAULT]))
|
||||||
cg.add(var.set_vertical_default(config[CONF_VERTICAL_DEFAULT]))
|
cg.add(var.set_vertical_default(config[CONF_VERTICAL_DEFAULT]))
|
||||||
cg.add(var.set_max_temperature(config[CONF_MIN_TEMPERATURE]))
|
cg.add(var.set_max_temperature(config[CONF_MAX_TEMPERATURE]))
|
||||||
cg.add(var.set_min_temperature(config[CONF_MAX_TEMPERATURE]))
|
cg.add(var.set_min_temperature(config[CONF_MIN_TEMPERATURE]))
|
||||||
|
|
||||||
# PIO isn't updating releases, so referencing the release tag directly. See:
|
# PIO isn't updating releases, so referencing the release tag directly. See:
|
||||||
# https://github.com/ToniA/arduino-heatpumpir/commit/0948c619d86407a4e50e8db2f3c193e0576c86fd
|
# https://github.com/ToniA/arduino-heatpumpir/commit/0948c619d86407a4e50e8db2f3c193e0576c86fd
|
||||||
|
|
|
@ -172,7 +172,7 @@ async def http_request_action_to_code(config, action_id, template_arg, args):
|
||||||
if CONF_JSON in config:
|
if CONF_JSON in config:
|
||||||
json_ = config[CONF_JSON]
|
json_ = config[CONF_JSON]
|
||||||
if isinstance(json_, Lambda):
|
if isinstance(json_, Lambda):
|
||||||
args_ = args + [(cg.JsonObjectRef, "root")]
|
args_ = args + [(cg.JsonObject, "root")]
|
||||||
lambda_ = await cg.process_lambda(json_, args_, return_type=cg.void)
|
lambda_ = await cg.process_lambda(json_, args_, return_type=cg.void)
|
||||||
cg.add(var.set_json(lambda_))
|
cg.add(var.set_json(lambda_))
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -115,8 +115,11 @@ void HttpRequestComponent::close() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *HttpRequestComponent::get_string() {
|
const char *HttpRequestComponent::get_string() {
|
||||||
static const String STR = this->client_.getString();
|
// The static variable is here because HTTPClient::getString() returns a String on ESP32, and we need something to
|
||||||
return STR.c_str();
|
// to keep a buffer alive.
|
||||||
|
static std::string str;
|
||||||
|
str = this->client_.getString().c_str();
|
||||||
|
return str.c_str();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace http_request
|
} // namespace http_request
|
||||||
|
|
|
@ -78,7 +78,7 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
|
||||||
|
|
||||||
void add_json(const char *key, TemplatableValue<std::string, Ts...> value) { this->json_.insert({key, value}); }
|
void add_json(const char *key, TemplatableValue<std::string, Ts...> value) { this->json_.insert({key, value}); }
|
||||||
|
|
||||||
void set_json(std::function<void(Ts..., JsonObject &)> json_func) { this->json_func_ = json_func; }
|
void set_json(std::function<void(Ts..., JsonObject)> json_func) { this->json_func_ = json_func; }
|
||||||
|
|
||||||
void register_response_trigger(HttpRequestResponseTrigger *trigger) { this->response_triggers_.push_back(trigger); }
|
void register_response_trigger(HttpRequestResponseTrigger *trigger) { this->response_triggers_.push_back(trigger); }
|
||||||
|
|
||||||
|
@ -118,17 +118,17 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void encode_json_(Ts... x, JsonObject &root) {
|
void encode_json_(Ts... x, JsonObject root) {
|
||||||
for (const auto &item : this->json_) {
|
for (const auto &item : this->json_) {
|
||||||
auto val = item.second;
|
auto val = item.second;
|
||||||
root[item.first] = val.value(x...);
|
root[item.first] = val.value(x...);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void encode_json_func_(Ts... x, JsonObject &root) { this->json_func_(x..., root); }
|
void encode_json_func_(Ts... x, JsonObject root) { this->json_func_(x..., root); }
|
||||||
HttpRequestComponent *parent_;
|
HttpRequestComponent *parent_;
|
||||||
std::map<const char *, TemplatableValue<const char *, Ts...>> headers_{};
|
std::map<const char *, TemplatableValue<const char *, Ts...>> headers_{};
|
||||||
std::map<const char *, TemplatableValue<std::string, Ts...>> json_{};
|
std::map<const char *, TemplatableValue<std::string, Ts...>> json_{};
|
||||||
std::function<void(Ts..., JsonObject &)> json_func_{nullptr};
|
std::function<void(Ts..., JsonObject)> json_func_{nullptr};
|
||||||
std::vector<HttpRequestResponseTrigger *> response_triggers_;
|
std::vector<HttpRequestResponseTrigger *> response_triggers_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace i2c {
|
namespace i2c {
|
||||||
|
@ -40,6 +42,20 @@ class I2CBus {
|
||||||
return writev(address, &buf, 1);
|
return writev(address, &buf, 1);
|
||||||
}
|
}
|
||||||
virtual ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t cnt) = 0;
|
virtual ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t cnt) = 0;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void i2c_scan_() {
|
||||||
|
for (uint8_t address = 8; address < 120; address++) {
|
||||||
|
auto err = writev(address, nullptr, 0);
|
||||||
|
if (err == ERROR_OK) {
|
||||||
|
scan_results_.emplace_back(address, true);
|
||||||
|
} else if (err == ERROR_UNKNOWN) {
|
||||||
|
scan_results_.emplace_back(address, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::vector<std::pair<uint8_t, bool>> scan_results_;
|
||||||
|
bool scan_{false};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace i2c
|
} // namespace i2c
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#include "i2c_bus_arduino.h"
|
#include "i2c_bus_arduino.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
|
@ -24,9 +25,13 @@ void ArduinoI2CBus::setup() {
|
||||||
wire_ = &Wire; // NOLINT(cppcoreguidelines-prefer-member-initializer)
|
wire_ = &Wire; // NOLINT(cppcoreguidelines-prefer-member-initializer)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
wire_->begin(sda_pin_, scl_pin_);
|
wire_->begin(static_cast<int>(sda_pin_), static_cast<int>(scl_pin_));
|
||||||
wire_->setClock(frequency_);
|
wire_->setClock(frequency_);
|
||||||
initialized_ = true;
|
initialized_ = true;
|
||||||
|
if (this->scan_) {
|
||||||
|
ESP_LOGV(TAG, "Scanning i2c bus for active devices...");
|
||||||
|
this->i2c_scan_();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
void ArduinoI2CBus::dump_config() {
|
void ArduinoI2CBus::dump_config() {
|
||||||
ESP_LOGCONFIG(TAG, "I2C Bus:");
|
ESP_LOGCONFIG(TAG, "I2C Bus:");
|
||||||
|
@ -45,22 +50,20 @@ void ArduinoI2CBus::dump_config() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (this->scan_) {
|
if (this->scan_) {
|
||||||
ESP_LOGI(TAG, "Scanning i2c bus for active devices...");
|
ESP_LOGI(TAG, "Results from i2c bus scan:");
|
||||||
uint8_t found = 0;
|
if (scan_results_.empty()) {
|
||||||
for (uint8_t address = 8; address < 120; address++) {
|
|
||||||
auto err = writev(address, nullptr, 0);
|
|
||||||
if (err == ERROR_OK) {
|
|
||||||
ESP_LOGI(TAG, "Found i2c device at address 0x%02X", address);
|
|
||||||
found++;
|
|
||||||
} else if (err == ERROR_UNKNOWN) {
|
|
||||||
ESP_LOGI(TAG, "Unknown error at address 0x%02X", address);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (found == 0) {
|
|
||||||
ESP_LOGI(TAG, "Found no i2c devices!");
|
ESP_LOGI(TAG, "Found no i2c devices!");
|
||||||
|
} else {
|
||||||
|
for (const auto &s : scan_results_) {
|
||||||
|
if (s.second)
|
||||||
|
ESP_LOGI(TAG, "Found i2c device at address 0x%02X", s.first);
|
||||||
|
else
|
||||||
|
ESP_LOGE(TAG, "Unknown error at address 0x%02X", s.first);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ErrorCode ArduinoI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt) {
|
ErrorCode ArduinoI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt) {
|
||||||
// logging is only enabled with vv level, if warnings are shown the caller
|
// logging is only enabled with vv level, if warnings are shown the caller
|
||||||
// should log them
|
// should log them
|
||||||
|
|
|
@ -34,7 +34,6 @@ class ArduinoI2CBus : public I2CBus, public Component {
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
TwoWire *wire_;
|
TwoWire *wire_;
|
||||||
bool scan_;
|
|
||||||
uint8_t sda_pin_;
|
uint8_t sda_pin_;
|
||||||
uint8_t scl_pin_;
|
uint8_t scl_pin_;
|
||||||
uint32_t frequency_;
|
uint32_t frequency_;
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#include "i2c_bus_esp_idf.h"
|
#include "i2c_bus_esp_idf.h"
|
||||||
#include "esphome/core/hal.h"
|
#include "esphome/core/hal.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
|
@ -37,6 +38,10 @@ void IDFI2CBus::setup() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
initialized_ = true;
|
initialized_ = true;
|
||||||
|
if (this->scan_) {
|
||||||
|
ESP_LOGV(TAG, "Scanning i2c bus for active devices...");
|
||||||
|
this->i2c_scan_();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
void IDFI2CBus::dump_config() {
|
void IDFI2CBus::dump_config() {
|
||||||
ESP_LOGCONFIG(TAG, "I2C Bus:");
|
ESP_LOGCONFIG(TAG, "I2C Bus:");
|
||||||
|
@ -55,23 +60,20 @@ void IDFI2CBus::dump_config() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (this->scan_) {
|
if (this->scan_) {
|
||||||
ESP_LOGI(TAG, "Scanning i2c bus for active devices...");
|
ESP_LOGI(TAG, "Results from i2c bus scan:");
|
||||||
uint8_t found = 0;
|
if (scan_results_.empty()) {
|
||||||
for (uint8_t address = 8; address < 120; address++) {
|
|
||||||
auto err = writev(address, nullptr, 0);
|
|
||||||
|
|
||||||
if (err == ERROR_OK) {
|
|
||||||
ESP_LOGI(TAG, "Found i2c device at address 0x%02X", address);
|
|
||||||
found++;
|
|
||||||
} else if (err == ERROR_UNKNOWN) {
|
|
||||||
ESP_LOGI(TAG, "Unknown error at address 0x%02X", address);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (found == 0) {
|
|
||||||
ESP_LOGI(TAG, "Found no i2c devices!");
|
ESP_LOGI(TAG, "Found no i2c devices!");
|
||||||
|
} else {
|
||||||
|
for (const auto &s : scan_results_) {
|
||||||
|
if (s.second)
|
||||||
|
ESP_LOGI(TAG, "Found i2c device at address 0x%02X", s.first);
|
||||||
|
else
|
||||||
|
ESP_LOGE(TAG, "Unknown error at address 0x%02X", s.first);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ErrorCode IDFI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt) {
|
ErrorCode IDFI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt) {
|
||||||
// logging is only enabled with vv level, if warnings are shown the caller
|
// logging is only enabled with vv level, if warnings are shown the caller
|
||||||
// should log them
|
// should log them
|
||||||
|
|
|
@ -36,7 +36,6 @@ class IDFI2CBus : public I2CBus, public Component {
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
i2c_port_t port_;
|
i2c_port_t port_;
|
||||||
bool scan_;
|
|
||||||
uint8_t sda_pin_;
|
uint8_t sda_pin_;
|
||||||
bool sda_pullup_enabled_;
|
bool sda_pullup_enabled_;
|
||||||
uint8_t scl_pin_;
|
uint8_t scl_pin_;
|
||||||
|
|
0
esphome/components/ina260/__init__.py
Normal file
0
esphome/components/ina260/__init__.py
Normal file
128
esphome/components/ina260/ina260.cpp
Normal file
128
esphome/components/ina260/ina260.cpp
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
#include "ina260.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/hal.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace ina260 {
|
||||||
|
|
||||||
|
static const char *const TAG = "ina260";
|
||||||
|
|
||||||
|
// | A0 | A1 | Address |
|
||||||
|
// | GND | GND | 0x40 |
|
||||||
|
// | GND | V_S+ | 0x41 |
|
||||||
|
// | GND | SDA | 0x42 |
|
||||||
|
// | GND | SCL | 0x43 |
|
||||||
|
// | V_S+ | GND | 0x44 |
|
||||||
|
// | V_S+ | V_S+ | 0x45 |
|
||||||
|
// | V_S+ | SDA | 0x46 |
|
||||||
|
// | V_S+ | SCL | 0x47 |
|
||||||
|
// | SDA | GND | 0x48 |
|
||||||
|
// | SDA | V_S+ | 0x49 |
|
||||||
|
// | SDA | SDA | 0x4A |
|
||||||
|
// | SDA | SCL | 0x4B |
|
||||||
|
// | SCL | GND | 0x4C |
|
||||||
|
// | SCL | V_S+ | 0x4D |
|
||||||
|
// | SCL | SDA | 0x4E |
|
||||||
|
// | SCL | SCL | 0x4F |
|
||||||
|
|
||||||
|
static const uint8_t INA260_REGISTER_CONFIG = 0x00;
|
||||||
|
static const uint8_t INA260_REGISTER_CURRENT = 0x01;
|
||||||
|
static const uint8_t INA260_REGISTER_BUS_VOLTAGE = 0x02;
|
||||||
|
static const uint8_t INA260_REGISTER_POWER = 0x03;
|
||||||
|
static const uint8_t INA260_REGISTER_MASK_ENABLE = 0x06;
|
||||||
|
static const uint8_t INA260_REGISTER_ALERT_LIMIT = 0x07;
|
||||||
|
static const uint8_t INA260_REGISTER_MANUFACTURE_ID = 0xFE;
|
||||||
|
static const uint8_t INA260_REGISTER_DEVICE_ID = 0xFF;
|
||||||
|
|
||||||
|
void INA260Component::setup() {
|
||||||
|
ESP_LOGCONFIG(TAG, "Setting up INA260...");
|
||||||
|
|
||||||
|
// Reset device on setup
|
||||||
|
if (!this->write_byte_16(INA260_REGISTER_CONFIG, 0x8000)) {
|
||||||
|
this->error_code_ = DEVICE_RESET_FAILED;
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
delay(2);
|
||||||
|
|
||||||
|
this->read_byte_16(INA260_REGISTER_MANUFACTURE_ID, &this->manufacture_id_);
|
||||||
|
this->read_byte_16(INA260_REGISTER_DEVICE_ID, &this->device_id_);
|
||||||
|
|
||||||
|
if (this->manufacture_id_ != (uint16_t) 0x5449 || this->device_id_ != (uint16_t) 0x2270) {
|
||||||
|
this->error_code_ = COMMUNICATION_FAILED;
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this->write_byte_16(INA260_REGISTER_CONFIG, (uint16_t) 0b0000001100000111)) {
|
||||||
|
this->error_code_ = FAILED_TO_UPDATE_CONFIGURATION;
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void INA260Component::dump_config() {
|
||||||
|
ESP_LOGCONFIG(TAG, "INA260:");
|
||||||
|
LOG_I2C_DEVICE(this);
|
||||||
|
LOG_UPDATE_INTERVAL(this);
|
||||||
|
|
||||||
|
ESP_LOGCONFIG(TAG, " Manufacture ID: 0x%x", this->manufacture_id_);
|
||||||
|
ESP_LOGCONFIG(TAG, " Device ID: 0x%x", this->device_id_);
|
||||||
|
|
||||||
|
LOG_SENSOR(" ", "Bus Voltage", this->bus_voltage_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Current", this->current_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Power", this->power_sensor_);
|
||||||
|
|
||||||
|
switch (this->error_code_) {
|
||||||
|
case COMMUNICATION_FAILED:
|
||||||
|
ESP_LOGE(TAG, "Connected device does not match a known INA260 sensor");
|
||||||
|
break;
|
||||||
|
case DEVICE_RESET_FAILED:
|
||||||
|
ESP_LOGE(TAG, "Device reset failed - Is the device connected?");
|
||||||
|
break;
|
||||||
|
case FAILED_TO_UPDATE_CONFIGURATION:
|
||||||
|
ESP_LOGE(TAG, "Failed to update device configuration");
|
||||||
|
break;
|
||||||
|
case NONE:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void INA260Component::update() {
|
||||||
|
if (this->bus_voltage_sensor_ != nullptr) {
|
||||||
|
uint16_t raw_bus_voltage;
|
||||||
|
if (!this->read_byte_16(INA260_REGISTER_BUS_VOLTAGE, &raw_bus_voltage)) {
|
||||||
|
this->status_set_warning();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
float bus_voltage_v = int16_t(raw_bus_voltage) * 0.00125f;
|
||||||
|
this->bus_voltage_sensor_->publish_state(bus_voltage_v);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->current_sensor_ != nullptr) {
|
||||||
|
uint16_t raw_current;
|
||||||
|
if (!this->read_byte_16(INA260_REGISTER_CURRENT, &raw_current)) {
|
||||||
|
this->status_set_warning();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
float current_a = int16_t(raw_current) * 0.00125f;
|
||||||
|
this->current_sensor_->publish_state(current_a);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->power_sensor_ != nullptr) {
|
||||||
|
uint16_t raw_power;
|
||||||
|
if (!this->read_byte_16(INA260_REGISTER_POWER, &raw_power)) {
|
||||||
|
this->status_set_warning();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
float power_w = ((int16_t(raw_power) * 10.0f) / 1000.0f);
|
||||||
|
this->power_sensor_->publish_state(power_w);
|
||||||
|
}
|
||||||
|
|
||||||
|
this->status_clear_warning();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ina260
|
||||||
|
} // namespace esphome
|
39
esphome/components/ina260/ina260.h
Normal file
39
esphome/components/ina260/ina260.h
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
#include "esphome/components/i2c/i2c.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace ina260 {
|
||||||
|
|
||||||
|
class INA260Component : 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_bus_voltage_sensor(sensor::Sensor *bus_voltage_sensor) { this->bus_voltage_sensor_ = bus_voltage_sensor; }
|
||||||
|
void set_current_sensor(sensor::Sensor *current_sensor) { this->current_sensor_ = current_sensor; }
|
||||||
|
void set_power_sensor(sensor::Sensor *power_sensor) { this->power_sensor_ = power_sensor; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
uint16_t manufacture_id_{0};
|
||||||
|
uint16_t device_id_{0};
|
||||||
|
|
||||||
|
sensor::Sensor *bus_voltage_sensor_{nullptr};
|
||||||
|
sensor::Sensor *current_sensor_{nullptr};
|
||||||
|
sensor::Sensor *power_sensor_{nullptr};
|
||||||
|
|
||||||
|
enum ErrorCode {
|
||||||
|
NONE,
|
||||||
|
COMMUNICATION_FAILED,
|
||||||
|
DEVICE_RESET_FAILED,
|
||||||
|
FAILED_TO_UPDATE_CONFIGURATION,
|
||||||
|
} error_code_{NONE};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ina260
|
||||||
|
} // namespace esphome
|
71
esphome/components/ina260/sensor.py
Normal file
71
esphome/components/ina260/sensor.py
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import i2c, sensor
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_ID,
|
||||||
|
CONF_BUS_VOLTAGE,
|
||||||
|
CONF_CURRENT,
|
||||||
|
CONF_POWER,
|
||||||
|
DEVICE_CLASS_VOLTAGE,
|
||||||
|
DEVICE_CLASS_CURRENT,
|
||||||
|
DEVICE_CLASS_POWER,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
UNIT_VOLT,
|
||||||
|
UNIT_AMPERE,
|
||||||
|
UNIT_WATT,
|
||||||
|
)
|
||||||
|
|
||||||
|
DEPENDENCIES = ["i2c"]
|
||||||
|
CODEOWNERS = ["@MrEditor97"]
|
||||||
|
|
||||||
|
ina260_ns = cg.esphome_ns.namespace("ina260")
|
||||||
|
INA260Component = ina260_ns.class_(
|
||||||
|
"INA260Component", cg.PollingComponent, i2c.I2CDevice
|
||||||
|
)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = cv.All(
|
||||||
|
cv.Schema(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(INA260Component),
|
||||||
|
cv.Optional(CONF_BUS_VOLTAGE): sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_VOLT,
|
||||||
|
accuracy_decimals=2,
|
||||||
|
device_class=DEVICE_CLASS_VOLTAGE,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_CURRENT): sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_AMPERE,
|
||||||
|
accuracy_decimals=3,
|
||||||
|
device_class=DEVICE_CLASS_CURRENT,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_POWER): sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_WATT,
|
||||||
|
accuracy_decimals=2,
|
||||||
|
device_class=DEVICE_CLASS_POWER,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(cv.polling_component_schema("60s"))
|
||||||
|
.extend(i2c.i2c_device_schema(0x40))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
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_BUS_VOLTAGE in config:
|
||||||
|
sens = await sensor.new_sensor(config[CONF_BUS_VOLTAGE])
|
||||||
|
cg.add(var.set_bus_voltage_sensor(sens))
|
||||||
|
|
||||||
|
if CONF_CURRENT in config:
|
||||||
|
sens = await sensor.new_sensor(config[CONF_CURRENT])
|
||||||
|
cg.add(var.set_current_sensor(sens))
|
||||||
|
|
||||||
|
if CONF_POWER in config:
|
||||||
|
sens = await sensor.new_sensor(config[CONF_POWER])
|
||||||
|
cg.add(var.set_power_sensor(sens))
|
|
@ -6,11 +6,13 @@ from esphome.const import (
|
||||||
CONF_FULL_UPDATE_EVERY,
|
CONF_FULL_UPDATE_EVERY,
|
||||||
CONF_ID,
|
CONF_ID,
|
||||||
CONF_LAMBDA,
|
CONF_LAMBDA,
|
||||||
|
CONF_MODEL,
|
||||||
CONF_PAGES,
|
CONF_PAGES,
|
||||||
CONF_WAKEUP_PIN,
|
CONF_WAKEUP_PIN,
|
||||||
)
|
)
|
||||||
|
|
||||||
DEPENDENCIES = ["i2c", "esp32"]
|
DEPENDENCIES = ["i2c", "esp32"]
|
||||||
|
AUTO_LOAD = ["psram"]
|
||||||
|
|
||||||
CONF_DISPLAY_DATA_0_PIN = "display_data_0_pin"
|
CONF_DISPLAY_DATA_0_PIN = "display_data_0_pin"
|
||||||
CONF_DISPLAY_DATA_1_PIN = "display_data_1_pin"
|
CONF_DISPLAY_DATA_1_PIN = "display_data_1_pin"
|
||||||
|
@ -40,6 +42,13 @@ Inkplate6 = inkplate6_ns.class_(
|
||||||
"Inkplate6", cg.PollingComponent, i2c.I2CDevice, display.DisplayBuffer
|
"Inkplate6", cg.PollingComponent, i2c.I2CDevice, display.DisplayBuffer
|
||||||
)
|
)
|
||||||
|
|
||||||
|
InkplateModel = inkplate6_ns.enum("InkplateModel")
|
||||||
|
|
||||||
|
MODELS = {
|
||||||
|
"inkplate_6": InkplateModel.INKPLATE_6,
|
||||||
|
"inkplate_10": InkplateModel.INKPLATE_10,
|
||||||
|
}
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.All(
|
CONFIG_SCHEMA = cv.All(
|
||||||
display.FULL_DISPLAY_SCHEMA.extend(
|
display.FULL_DISPLAY_SCHEMA.extend(
|
||||||
{
|
{
|
||||||
|
@ -47,6 +56,9 @@ CONFIG_SCHEMA = cv.All(
|
||||||
cv.Optional(CONF_GREYSCALE, default=False): cv.boolean,
|
cv.Optional(CONF_GREYSCALE, default=False): cv.boolean,
|
||||||
cv.Optional(CONF_PARTIAL_UPDATING, default=True): cv.boolean,
|
cv.Optional(CONF_PARTIAL_UPDATING, default=True): cv.boolean,
|
||||||
cv.Optional(CONF_FULL_UPDATE_EVERY, default=10): cv.uint32_t,
|
cv.Optional(CONF_FULL_UPDATE_EVERY, default=10): cv.uint32_t,
|
||||||
|
cv.Optional(CONF_MODEL, default="inkplate_6"): cv.enum(
|
||||||
|
MODELS, lower=True, space="_"
|
||||||
|
),
|
||||||
# Control pins
|
# Control pins
|
||||||
cv.Required(CONF_CKV_PIN): pins.gpio_output_pin_schema,
|
cv.Required(CONF_CKV_PIN): pins.gpio_output_pin_schema,
|
||||||
cv.Required(CONF_GMOD_PIN): pins.gpio_output_pin_schema,
|
cv.Required(CONF_GMOD_PIN): pins.gpio_output_pin_schema,
|
||||||
|
@ -110,6 +122,8 @@ async def to_code(config):
|
||||||
cg.add(var.set_partial_updating(config[CONF_PARTIAL_UPDATING]))
|
cg.add(var.set_partial_updating(config[CONF_PARTIAL_UPDATING]))
|
||||||
cg.add(var.set_full_update_every(config[CONF_FULL_UPDATE_EVERY]))
|
cg.add(var.set_full_update_every(config[CONF_FULL_UPDATE_EVERY]))
|
||||||
|
|
||||||
|
cg.add(var.set_model(config[CONF_MODEL]))
|
||||||
|
|
||||||
ckv = await cg.gpio_pin_expression(config[CONF_CKV_PIN])
|
ckv = await cg.gpio_pin_expression(config[CONF_CKV_PIN])
|
||||||
cg.add(var.set_ckv_pin(ckv))
|
cg.add(var.set_ckv_pin(ckv))
|
||||||
|
|
||||||
|
@ -166,5 +180,3 @@ async def to_code(config):
|
||||||
|
|
||||||
display_data_7 = await cg.gpio_pin_expression(config[CONF_DISPLAY_DATA_7_PIN])
|
display_data_7 = await cg.gpio_pin_expression(config[CONF_DISPLAY_DATA_7_PIN])
|
||||||
cg.add(var.set_display_data_7_pin(display_data_7))
|
cg.add(var.set_display_data_7_pin(display_data_7))
|
||||||
|
|
||||||
cg.add_build_flag("-DBOARD_HAS_PSRAM")
|
|
||||||
|
|
|
@ -42,32 +42,32 @@ void Inkplate6::setup() {
|
||||||
this->display();
|
this->display();
|
||||||
}
|
}
|
||||||
void Inkplate6::initialize_() {
|
void Inkplate6::initialize_() {
|
||||||
|
ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
|
||||||
uint32_t buffer_size = this->get_buffer_length_();
|
uint32_t buffer_size = this->get_buffer_length_();
|
||||||
|
if (buffer_size == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
if (this->partial_buffer_ != nullptr) {
|
if (this->partial_buffer_ != nullptr)
|
||||||
free(this->partial_buffer_); // NOLINT
|
allocator.deallocate(this->partial_buffer_, buffer_size);
|
||||||
}
|
if (this->partial_buffer_2_ != nullptr)
|
||||||
if (this->partial_buffer_2_ != nullptr) {
|
allocator.deallocate(this->partial_buffer_2_, buffer_size * 2);
|
||||||
free(this->partial_buffer_2_); // NOLINT
|
if (this->buffer_ != nullptr)
|
||||||
}
|
allocator.deallocate(this->buffer_, buffer_size);
|
||||||
if (this->buffer_ != nullptr) {
|
|
||||||
free(this->buffer_); // NOLINT
|
|
||||||
}
|
|
||||||
|
|
||||||
this->buffer_ = (uint8_t *) ps_malloc(buffer_size);
|
this->buffer_ = allocator.allocate(buffer_size);
|
||||||
if (this->buffer_ == nullptr) {
|
if (this->buffer_ == nullptr) {
|
||||||
ESP_LOGE(TAG, "Could not allocate buffer for display!");
|
ESP_LOGE(TAG, "Could not allocate buffer for display!");
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!this->greyscale_) {
|
if (!this->greyscale_) {
|
||||||
this->partial_buffer_ = (uint8_t *) ps_malloc(buffer_size);
|
this->partial_buffer_ = allocator.allocate(buffer_size);
|
||||||
if (this->partial_buffer_ == nullptr) {
|
if (this->partial_buffer_ == nullptr) {
|
||||||
ESP_LOGE(TAG, "Could not allocate partial buffer for display!");
|
ESP_LOGE(TAG, "Could not allocate partial buffer for display!");
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this->partial_buffer_2_ = (uint8_t *) ps_malloc(buffer_size * 2);
|
this->partial_buffer_2_ = allocator.allocate(buffer_size * 2);
|
||||||
if (this->partial_buffer_2_ == nullptr) {
|
if (this->partial_buffer_2_ == nullptr) {
|
||||||
ESP_LOGE(TAG, "Could not allocate partial buffer 2 for display!");
|
ESP_LOGE(TAG, "Could not allocate partial buffer 2 for display!");
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
|
|
|
@ -10,6 +10,11 @@
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace inkplate6 {
|
namespace inkplate6 {
|
||||||
|
|
||||||
|
enum InkplateModel : uint8_t {
|
||||||
|
INKPLATE_6 = 0,
|
||||||
|
INKPLATE_10 = 1,
|
||||||
|
};
|
||||||
|
|
||||||
class Inkplate6 : public PollingComponent, public display::DisplayBuffer, public i2c::I2CDevice {
|
class Inkplate6 : public PollingComponent, public display::DisplayBuffer, public i2c::I2CDevice {
|
||||||
public:
|
public:
|
||||||
const uint8_t LUT2[16] = {0b10101010, 0b10101001, 0b10100110, 0b10100101, 0b10011010, 0b10011001,
|
const uint8_t LUT2[16] = {0b10101010, 0b10101001, 0b10100110, 0b10100101, 0b10011010, 0b10011001,
|
||||||
|
@ -43,6 +48,8 @@ class Inkplate6 : public PollingComponent, public display::DisplayBuffer, public
|
||||||
void set_partial_updating(bool partial_updating) { this->partial_updating_ = partial_updating; }
|
void set_partial_updating(bool partial_updating) { this->partial_updating_ = partial_updating; }
|
||||||
void set_full_update_every(uint32_t full_update_every) { this->full_update_every_ = full_update_every; }
|
void set_full_update_every(uint32_t full_update_every) { this->full_update_every_ = full_update_every; }
|
||||||
|
|
||||||
|
void set_model(InkplateModel model) { this->model_ = model; }
|
||||||
|
|
||||||
void set_display_data_0_pin(InternalGPIOPin *data) { this->display_data_0_pin_ = data; }
|
void set_display_data_0_pin(InternalGPIOPin *data) { this->display_data_0_pin_ = data; }
|
||||||
void set_display_data_1_pin(InternalGPIOPin *data) { this->display_data_1_pin_ = data; }
|
void set_display_data_1_pin(InternalGPIOPin *data) { this->display_data_1_pin_ = data; }
|
||||||
void set_display_data_2_pin(InternalGPIOPin *data) { this->display_data_2_pin_ = data; }
|
void set_display_data_2_pin(InternalGPIOPin *data) { this->display_data_2_pin_ = data; }
|
||||||
|
@ -101,9 +108,21 @@ class Inkplate6 : public PollingComponent, public display::DisplayBuffer, public
|
||||||
void pins_z_state_();
|
void pins_z_state_();
|
||||||
void pins_as_outputs_();
|
void pins_as_outputs_();
|
||||||
|
|
||||||
int get_width_internal() override { return 800; }
|
int get_width_internal() override {
|
||||||
|
if (this->model_ == INKPLATE_6)
|
||||||
|
return 800;
|
||||||
|
else if (this->model_ == INKPLATE_10)
|
||||||
|
return 1200;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
int get_height_internal() override { return 600; }
|
int get_height_internal() override {
|
||||||
|
if (this->model_ == INKPLATE_6)
|
||||||
|
return 600;
|
||||||
|
else if (this->model_ == INKPLATE_10)
|
||||||
|
return 825;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
size_t get_buffer_length_();
|
size_t get_buffer_length_();
|
||||||
|
|
||||||
|
@ -133,6 +152,8 @@ class Inkplate6 : public PollingComponent, public display::DisplayBuffer, public
|
||||||
bool greyscale_;
|
bool greyscale_;
|
||||||
bool partial_updating_;
|
bool partial_updating_;
|
||||||
|
|
||||||
|
InkplateModel model_;
|
||||||
|
|
||||||
InternalGPIOPin *display_data_0_pin_;
|
InternalGPIOPin *display_data_0_pin_;
|
||||||
InternalGPIOPin *display_data_1_pin_;
|
InternalGPIOPin *display_data_1_pin_;
|
||||||
InternalGPIOPin *display_data_2_pin_;
|
InternalGPIOPin *display_data_2_pin_;
|
||||||
|
|
|
@ -23,31 +23,6 @@ void IntegrationSensor::setup() {
|
||||||
this->sensor_->add_on_state_callback([this](float state) { this->process_sensor_value_(state); });
|
this->sensor_->add_on_state_callback([this](float state) { this->process_sensor_value_(state); });
|
||||||
}
|
}
|
||||||
void IntegrationSensor::dump_config() { LOG_SENSOR("", "Integration Sensor", this); }
|
void IntegrationSensor::dump_config() { LOG_SENSOR("", "Integration Sensor", this); }
|
||||||
std::string IntegrationSensor::unit_of_measurement() {
|
|
||||||
std::string suffix;
|
|
||||||
switch (this->time_) {
|
|
||||||
case INTEGRATION_SENSOR_TIME_MILLISECOND:
|
|
||||||
suffix = "ms";
|
|
||||||
break;
|
|
||||||
case INTEGRATION_SENSOR_TIME_SECOND:
|
|
||||||
suffix = "s";
|
|
||||||
break;
|
|
||||||
case INTEGRATION_SENSOR_TIME_MINUTE:
|
|
||||||
suffix = "min";
|
|
||||||
break;
|
|
||||||
case INTEGRATION_SENSOR_TIME_HOUR:
|
|
||||||
suffix = "h";
|
|
||||||
break;
|
|
||||||
case INTEGRATION_SENSOR_TIME_DAY:
|
|
||||||
suffix = "d";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
std::string base = this->sensor_->get_unit_of_measurement();
|
|
||||||
if (str_endswith(base, "/" + suffix)) {
|
|
||||||
return base.substr(0, base.size() - suffix.size() - 1);
|
|
||||||
}
|
|
||||||
return base + suffix;
|
|
||||||
}
|
|
||||||
void IntegrationSensor::process_sensor_value_(float value) {
|
void IntegrationSensor::process_sensor_value_(float value) {
|
||||||
const uint32_t now = millis();
|
const uint32_t now = millis();
|
||||||
const double old_value = this->last_value_;
|
const double old_value = this->last_value_;
|
||||||
|
|
|
@ -63,8 +63,6 @@ class IntegrationSensor : public sensor::Sensor, public Component {
|
||||||
this->last_save_ = now;
|
this->last_save_ = now;
|
||||||
this->rtc_.save(&result_f);
|
this->rtc_.save(&result_f);
|
||||||
}
|
}
|
||||||
std::string unit_of_measurement() override;
|
|
||||||
int8_t accuracy_decimals() override { return this->sensor_->get_accuracy_decimals() + 2; }
|
|
||||||
|
|
||||||
sensor::Sensor *sensor_;
|
sensor::Sensor *sensor_;
|
||||||
IntegrationSensorTime time_;
|
IntegrationSensorTime time_;
|
||||||
|
|
|
@ -2,7 +2,14 @@ import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome import automation
|
from esphome import automation
|
||||||
from esphome.components import sensor
|
from esphome.components import sensor
|
||||||
from esphome.const import CONF_ICON, CONF_ID, CONF_SENSOR, CONF_RESTORE
|
from esphome.const import (
|
||||||
|
CONF_ICON,
|
||||||
|
CONF_ID,
|
||||||
|
CONF_SENSOR,
|
||||||
|
CONF_RESTORE,
|
||||||
|
CONF_UNIT_OF_MEASUREMENT,
|
||||||
|
CONF_ACCURACY_DECIMALS,
|
||||||
|
)
|
||||||
from esphome.core.entity_helpers import inherit_property_from
|
from esphome.core.entity_helpers import inherit_property_from
|
||||||
|
|
||||||
integration_ns = cg.esphome_ns.namespace("integration")
|
integration_ns = cg.esphome_ns.namespace("integration")
|
||||||
|
@ -30,6 +37,18 @@ CONF_TIME_UNIT = "time_unit"
|
||||||
CONF_INTEGRATION_METHOD = "integration_method"
|
CONF_INTEGRATION_METHOD = "integration_method"
|
||||||
CONF_MIN_SAVE_INTERVAL = "min_save_interval"
|
CONF_MIN_SAVE_INTERVAL = "min_save_interval"
|
||||||
|
|
||||||
|
|
||||||
|
def inherit_unit_of_measurement(uom, config):
|
||||||
|
suffix = config[CONF_TIME_UNIT]
|
||||||
|
if uom.endswith("/" + suffix):
|
||||||
|
return uom[0 : -len("/" + suffix)]
|
||||||
|
return uom + suffix
|
||||||
|
|
||||||
|
|
||||||
|
def inherit_accuracy_decimals(decimals, config):
|
||||||
|
return decimals + 2
|
||||||
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend(
|
CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(IntegrationSensor),
|
cv.GenerateID(): cv.declare_id(IntegrationSensor),
|
||||||
|
@ -51,11 +70,19 @@ FINAL_VALIDATE_SCHEMA = cv.All(
|
||||||
{
|
{
|
||||||
cv.Required(CONF_ID): cv.use_id(IntegrationSensor),
|
cv.Required(CONF_ID): cv.use_id(IntegrationSensor),
|
||||||
cv.Optional(CONF_ICON): cv.icon,
|
cv.Optional(CONF_ICON): cv.icon,
|
||||||
|
cv.Optional(CONF_UNIT_OF_MEASUREMENT): sensor.validate_unit_of_measurement,
|
||||||
|
cv.Optional(CONF_ACCURACY_DECIMALS): sensor.validate_accuracy_decimals,
|
||||||
cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor),
|
cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor),
|
||||||
},
|
},
|
||||||
extra=cv.ALLOW_EXTRA,
|
extra=cv.ALLOW_EXTRA,
|
||||||
),
|
),
|
||||||
inherit_property_from(CONF_ICON, CONF_SENSOR),
|
inherit_property_from(CONF_ICON, CONF_SENSOR),
|
||||||
|
inherit_property_from(
|
||||||
|
CONF_UNIT_OF_MEASUREMENT, CONF_SENSOR, transform=inherit_unit_of_measurement
|
||||||
|
),
|
||||||
|
inherit_property_from(
|
||||||
|
CONF_ACCURACY_DECIMALS, CONF_SENSOR, transform=inherit_accuracy_decimals
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -7,12 +7,11 @@ json_ns = cg.esphome_ns.namespace("json")
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.All(
|
CONFIG_SCHEMA = cv.All(
|
||||||
cv.Schema({}),
|
cv.Schema({}),
|
||||||
cv.only_with_arduino,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@coroutine_with_priority(1.0)
|
@coroutine_with_priority(1.0)
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
cg.add_library("ottowinter/ArduinoJson-esphomelib", "5.13.3")
|
cg.add_library("bblanchon/ArduinoJson", "6.18.5")
|
||||||
cg.add_define("USE_JSON")
|
cg.add_define("USE_JSON")
|
||||||
cg.add_global(json_ns.using)
|
cg.add_global(json_ns.using)
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
#ifdef USE_ARDUINO
|
|
||||||
|
|
||||||
#include "json_util.h"
|
#include "json_util.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
#ifdef USE_ESP8266
|
||||||
|
#include <Esp.h>
|
||||||
|
#endif
|
||||||
|
#ifdef USE_ESP32
|
||||||
|
#include <esp_heap_caps.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace json {
|
namespace json {
|
||||||
|
|
||||||
|
@ -10,110 +15,49 @@ static const char *const TAG = "json";
|
||||||
|
|
||||||
static std::vector<char> global_json_build_buffer; // NOLINT
|
static std::vector<char> global_json_build_buffer; // NOLINT
|
||||||
|
|
||||||
const char *build_json(const json_build_t &f, size_t *length) {
|
std::string build_json(const json_build_t &f) {
|
||||||
global_json_buffer.clear();
|
// Here we are allocating as much heap memory as available minus 2kb to be safe
|
||||||
JsonObject &root = global_json_buffer.createObject();
|
// as we can not have a true dynamic sized document.
|
||||||
|
// The excess memory is freed below with `shrinkToFit()`
|
||||||
|
#ifdef USE_ESP8266
|
||||||
|
const size_t free_heap = ESP.getMaxFreeBlockSize() - 2048; // NOLINT(readability-static-accessed-through-instance)
|
||||||
|
#elif defined(USE_ESP32)
|
||||||
|
const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_DEFAULT) - 2048;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
DynamicJsonDocument json_document(free_heap);
|
||||||
|
JsonObject root = json_document.to<JsonObject>();
|
||||||
f(root);
|
f(root);
|
||||||
|
json_document.shrinkToFit();
|
||||||
|
|
||||||
// The Json buffer size gives us a good estimate for the required size.
|
std::string output;
|
||||||
// Usually, it's a bit larger than the actual required string size
|
serializeJson(json_document, output);
|
||||||
// | JSON Buffer Size | String Size |
|
return output;
|
||||||
// Discovery | 388 | 351 |
|
|
||||||
// Discovery | 372 | 356 |
|
|
||||||
// Discovery | 336 | 311 |
|
|
||||||
// Discovery | 408 | 393 |
|
|
||||||
global_json_build_buffer.reserve(global_json_buffer.size() + 1);
|
|
||||||
size_t bytes_written = root.printTo(global_json_build_buffer.data(), global_json_build_buffer.capacity());
|
|
||||||
|
|
||||||
if (bytes_written >= global_json_build_buffer.capacity() - 1) {
|
|
||||||
global_json_build_buffer.reserve(root.measureLength() + 1);
|
|
||||||
bytes_written = root.printTo(global_json_build_buffer.data(), global_json_build_buffer.capacity());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
*length = bytes_written;
|
|
||||||
return global_json_build_buffer.data();
|
|
||||||
}
|
|
||||||
void parse_json(const std::string &data, const json_parse_t &f) {
|
void parse_json(const std::string &data, const json_parse_t &f) {
|
||||||
global_json_buffer.clear();
|
// Here we are allocating as much heap memory as available minus 2kb to be safe
|
||||||
JsonObject &root = global_json_buffer.parseObject(data);
|
// as we can not have a true dynamic sized document.
|
||||||
|
// The excess memory is freed below with `shrinkToFit()`
|
||||||
|
#ifdef USE_ESP8266
|
||||||
|
const size_t free_heap = ESP.getMaxFreeBlockSize() - 2048; // NOLINT(readability-static-accessed-through-instance)
|
||||||
|
#elif defined(USE_ESP32)
|
||||||
|
const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_DEFAULT) - 2048;
|
||||||
|
#endif
|
||||||
|
|
||||||
if (!root.success()) {
|
DynamicJsonDocument json_document(free_heap);
|
||||||
|
DeserializationError err = deserializeJson(json_document, data);
|
||||||
|
json_document.shrinkToFit();
|
||||||
|
|
||||||
|
JsonObject root = json_document.as<JsonObject>();
|
||||||
|
|
||||||
|
if (err) {
|
||||||
ESP_LOGW(TAG, "Parsing JSON failed.");
|
ESP_LOGW(TAG, "Parsing JSON failed.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
f(root);
|
f(root);
|
||||||
}
|
}
|
||||||
std::string build_json(const json_build_t &f) {
|
|
||||||
size_t len;
|
|
||||||
const char *c_str = build_json(f, &len);
|
|
||||||
return std::string(c_str, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
VectorJsonBuffer::String::String(VectorJsonBuffer *parent) : parent_(parent), start_(parent->size_) {}
|
|
||||||
void VectorJsonBuffer::String::append(char c) const {
|
|
||||||
char *last = static_cast<char *>(this->parent_->do_alloc(1));
|
|
||||||
*last = c;
|
|
||||||
}
|
|
||||||
const char *VectorJsonBuffer::String::c_str() const {
|
|
||||||
this->append('\0');
|
|
||||||
return &this->parent_->buffer_[this->start_];
|
|
||||||
}
|
|
||||||
void VectorJsonBuffer::clear() {
|
|
||||||
for (char *block : this->free_blocks_)
|
|
||||||
free(block); // NOLINT
|
|
||||||
|
|
||||||
this->size_ = 0;
|
|
||||||
this->free_blocks_.clear();
|
|
||||||
}
|
|
||||||
VectorJsonBuffer::String VectorJsonBuffer::startString() { return {this}; } // NOLINT
|
|
||||||
void *VectorJsonBuffer::alloc(size_t bytes) {
|
|
||||||
// Make sure memory addresses are aligned
|
|
||||||
uint32_t new_size = round_size_up(this->size_);
|
|
||||||
this->resize(new_size);
|
|
||||||
return this->do_alloc(bytes);
|
|
||||||
}
|
|
||||||
void *VectorJsonBuffer::do_alloc(size_t bytes) { // NOLINT
|
|
||||||
const uint32_t begin = this->size_;
|
|
||||||
this->resize(begin + bytes);
|
|
||||||
return &this->buffer_[begin];
|
|
||||||
}
|
|
||||||
void VectorJsonBuffer::resize(size_t size) { // NOLINT
|
|
||||||
if (size <= this->size_) {
|
|
||||||
this->size_ = size;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->reserve(size);
|
|
||||||
this->size_ = size;
|
|
||||||
}
|
|
||||||
void VectorJsonBuffer::reserve(size_t size) { // NOLINT
|
|
||||||
if (size <= this->capacity_)
|
|
||||||
return;
|
|
||||||
|
|
||||||
uint32_t target_capacity = this->capacity_;
|
|
||||||
if (this->capacity_ == 0) {
|
|
||||||
// lazily initialize with a reasonable size
|
|
||||||
target_capacity = JSON_OBJECT_SIZE(16);
|
|
||||||
}
|
|
||||||
while (target_capacity < size)
|
|
||||||
target_capacity *= 2;
|
|
||||||
|
|
||||||
char *old_buffer = this->buffer_;
|
|
||||||
this->buffer_ = new char[target_capacity]; // NOLINT
|
|
||||||
if (old_buffer != nullptr && this->capacity_ != 0) {
|
|
||||||
this->free_blocks_.push_back(old_buffer);
|
|
||||||
memcpy(this->buffer_, old_buffer, this->capacity_);
|
|
||||||
}
|
|
||||||
this->capacity_ = target_capacity;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t VectorJsonBuffer::size() const { return this->size_; }
|
|
||||||
|
|
||||||
VectorJsonBuffer global_json_buffer; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
|
||||||
|
|
||||||
} // namespace json
|
} // namespace json
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
||||||
#endif // USE_ARDUINO
|
|
||||||
|
|
|
@ -1,68 +1,28 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#ifdef USE_ARDUINO
|
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
|
|
||||||
|
#undef ARDUINOJSON_ENABLE_STD_STRING
|
||||||
|
#define ARDUINOJSON_ENABLE_STD_STRING 1 // NOLINT
|
||||||
|
|
||||||
#include <ArduinoJson.h>
|
#include <ArduinoJson.h>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace json {
|
namespace json {
|
||||||
|
|
||||||
/// Callback function typedef for parsing JsonObjects.
|
/// Callback function typedef for parsing JsonObjects.
|
||||||
using json_parse_t = std::function<void(JsonObject &)>;
|
using json_parse_t = std::function<void(JsonObject)>;
|
||||||
|
|
||||||
/// Callback function typedef for building JsonObjects.
|
/// Callback function typedef for building JsonObjects.
|
||||||
using json_build_t = std::function<void(JsonObject &)>;
|
using json_build_t = std::function<void(JsonObject)>;
|
||||||
|
|
||||||
/// Build a JSON string with the provided json build function.
|
/// Build a JSON string with the provided json build function.
|
||||||
const char *build_json(const json_build_t &f, size_t *length);
|
|
||||||
|
|
||||||
std::string build_json(const json_build_t &f);
|
std::string build_json(const json_build_t &f);
|
||||||
|
|
||||||
/// Parse a JSON string and run the provided json parse function if it's valid.
|
/// Parse a JSON string and run the provided json parse function if it's valid.
|
||||||
void parse_json(const std::string &data, const json_parse_t &f);
|
void parse_json(const std::string &data, const json_parse_t &f);
|
||||||
|
|
||||||
class VectorJsonBuffer : public ArduinoJson::Internals::JsonBufferBase<VectorJsonBuffer> {
|
|
||||||
public:
|
|
||||||
class String {
|
|
||||||
public:
|
|
||||||
String(VectorJsonBuffer *parent);
|
|
||||||
|
|
||||||
void append(char c) const;
|
|
||||||
|
|
||||||
const char *c_str() const;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
VectorJsonBuffer *parent_;
|
|
||||||
uint32_t start_;
|
|
||||||
};
|
|
||||||
|
|
||||||
void *alloc(size_t bytes) override;
|
|
||||||
|
|
||||||
size_t size() const;
|
|
||||||
|
|
||||||
void clear();
|
|
||||||
|
|
||||||
String startString(); // NOLINT
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void *do_alloc(size_t bytes); // NOLINT
|
|
||||||
|
|
||||||
void resize(size_t size); // NOLINT
|
|
||||||
|
|
||||||
void reserve(size_t size); // NOLINT
|
|
||||||
|
|
||||||
char *buffer_{nullptr};
|
|
||||||
size_t size_{0};
|
|
||||||
size_t capacity_{0};
|
|
||||||
std::vector<char *> free_blocks_;
|
|
||||||
};
|
|
||||||
|
|
||||||
extern VectorJsonBuffer global_json_buffer; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
|
||||||
|
|
||||||
} // namespace json
|
} // namespace json
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
||||||
#endif // USE_ARDUINO
|
|
||||||
|
|
1
esphome/components/kalman_combinator/__init__.py
Normal file
1
esphome/components/kalman_combinator/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
CODEOWNERS = ["@Cat-Ion"]
|
82
esphome/components/kalman_combinator/kalman_combinator.cpp
Normal file
82
esphome/components/kalman_combinator/kalman_combinator.cpp
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
#include "kalman_combinator.h"
|
||||||
|
#include "esphome/core/hal.h"
|
||||||
|
#include <cmath>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace kalman_combinator {
|
||||||
|
|
||||||
|
void KalmanCombinatorComponent::dump_config() {
|
||||||
|
ESP_LOGCONFIG("kalman_combinator", "Kalman Combinator:");
|
||||||
|
ESP_LOGCONFIG("kalman_combinator", " Update variance: %f per ms", this->update_variance_value_);
|
||||||
|
ESP_LOGCONFIG("kalman_combinator", " Sensors:");
|
||||||
|
for (const auto &sensor : this->sensors_) {
|
||||||
|
auto &entity = *sensor.first;
|
||||||
|
ESP_LOGCONFIG("kalman_combinator", " - %s", entity.get_name().c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void KalmanCombinatorComponent::setup() {
|
||||||
|
for (const auto &sensor : this->sensors_) {
|
||||||
|
const auto stddev = sensor.second;
|
||||||
|
sensor.first->add_on_state_callback([this, stddev](float x) -> void { this->correct_(x, stddev(x)); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void KalmanCombinatorComponent::add_source(Sensor *sensor, std::function<float(float)> const &stddev) {
|
||||||
|
this->sensors_.emplace_back(sensor, stddev);
|
||||||
|
}
|
||||||
|
|
||||||
|
void KalmanCombinatorComponent::add_source(Sensor *sensor, float stddev) {
|
||||||
|
this->add_source(sensor, std::function<float(float)>{[stddev](float) -> float { return stddev; }});
|
||||||
|
}
|
||||||
|
|
||||||
|
void KalmanCombinatorComponent::update_variance_() {
|
||||||
|
uint32_t now = millis();
|
||||||
|
|
||||||
|
// Variance increases by update_variance_ each millisecond
|
||||||
|
auto dt = now - this->last_update_;
|
||||||
|
auto dv = this->update_variance_value_ * dt;
|
||||||
|
this->variance_ += dv;
|
||||||
|
this->last_update_ = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
void KalmanCombinatorComponent::correct_(float value, float stddev) {
|
||||||
|
if (std::isnan(value) || std::isinf(stddev)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std::isnan(this->state_) || std::isinf(this->variance_)) {
|
||||||
|
this->state_ = value;
|
||||||
|
this->variance_ = stddev * stddev;
|
||||||
|
if (this->std_dev_sensor_ != nullptr) {
|
||||||
|
this->std_dev_sensor_->publish_state(stddev);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->update_variance_();
|
||||||
|
|
||||||
|
// Combine two gaussian distributions mu1+-var1, mu2+-var2 to a new one around mu
|
||||||
|
// Use the value with the smaller variance as mu1 to prevent precision errors
|
||||||
|
const bool this_first = this->variance_ < (stddev * stddev);
|
||||||
|
const float mu1 = this_first ? this->state_ : value;
|
||||||
|
const float mu2 = this_first ? value : this->state_;
|
||||||
|
|
||||||
|
const float var1 = this_first ? this->variance_ : stddev * stddev;
|
||||||
|
const float var2 = this_first ? stddev * stddev : this->variance_;
|
||||||
|
|
||||||
|
const float mu = mu1 + var1 * (mu2 - mu1) / (var1 + var2);
|
||||||
|
const float var = var1 - (var1 * var1) / (var1 + var2);
|
||||||
|
|
||||||
|
// Update and publish state
|
||||||
|
this->state_ = mu;
|
||||||
|
this->variance_ = var;
|
||||||
|
|
||||||
|
this->publish_state(mu);
|
||||||
|
if (this->std_dev_sensor_ != nullptr) {
|
||||||
|
this->std_dev_sensor_->publish_state(std::sqrt(var));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace kalman_combinator
|
||||||
|
} // namespace esphome
|
46
esphome/components/kalman_combinator/kalman_combinator.h
Normal file
46
esphome/components/kalman_combinator/kalman_combinator.h
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
#pragma once
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
#include <cmath>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace kalman_combinator {
|
||||||
|
|
||||||
|
class KalmanCombinatorComponent : public Component, public sensor::Sensor {
|
||||||
|
public:
|
||||||
|
KalmanCombinatorComponent() = default;
|
||||||
|
|
||||||
|
float get_setup_priority() const override { return esphome::setup_priority::DATA; }
|
||||||
|
|
||||||
|
void dump_config() override;
|
||||||
|
void setup() override;
|
||||||
|
|
||||||
|
void add_source(Sensor *sensor, std::function<float(float)> const &stddev);
|
||||||
|
void add_source(Sensor *sensor, float stddev);
|
||||||
|
void set_process_std_dev(float process_std_dev) {
|
||||||
|
this->update_variance_value_ = process_std_dev * process_std_dev * 0.001f;
|
||||||
|
}
|
||||||
|
void set_std_dev_sensor(Sensor *sensor) { this->std_dev_sensor_ = sensor; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void update_variance_();
|
||||||
|
void correct_(float value, float stddev);
|
||||||
|
|
||||||
|
// Source sensors and their error functions
|
||||||
|
std::vector<std::pair<Sensor *, std::function<float(float)>>> sensors_;
|
||||||
|
|
||||||
|
// Optional sensor for publishing the current error
|
||||||
|
sensor::Sensor *std_dev_sensor_{nullptr};
|
||||||
|
|
||||||
|
// Tick of the last update
|
||||||
|
uint32_t last_update_{0};
|
||||||
|
// Change of the variance, per ms
|
||||||
|
float update_variance_value_{0.f};
|
||||||
|
|
||||||
|
// Best guess for the state and its variance
|
||||||
|
float state_{NAN};
|
||||||
|
float variance_{INFINITY};
|
||||||
|
};
|
||||||
|
} // namespace kalman_combinator
|
||||||
|
} // namespace esphome
|
87
esphome/components/kalman_combinator/sensor.py
Normal file
87
esphome/components/kalman_combinator/sensor.py
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import sensor
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_ID,
|
||||||
|
CONF_SOURCE,
|
||||||
|
CONF_ACCURACY_DECIMALS,
|
||||||
|
CONF_DEVICE_CLASS,
|
||||||
|
CONF_ENTITY_CATEGORY,
|
||||||
|
CONF_ICON,
|
||||||
|
CONF_UNIT_OF_MEASUREMENT,
|
||||||
|
)
|
||||||
|
from esphome.core.entity_helpers import inherit_property_from
|
||||||
|
|
||||||
|
kalman_combinator_ns = cg.esphome_ns.namespace("kalman_combinator")
|
||||||
|
KalmanCombinatorComponent = kalman_combinator_ns.class_(
|
||||||
|
"KalmanCombinatorComponent", cg.Component, sensor.Sensor
|
||||||
|
)
|
||||||
|
|
||||||
|
CONF_ERROR = "error"
|
||||||
|
CONF_SOURCES = "sources"
|
||||||
|
CONF_PROCESS_STD_DEV = "process_std_dev"
|
||||||
|
CONF_STD_DEV = "std_dev"
|
||||||
|
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend(cv.COMPONENT_SCHEMA).extend(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(KalmanCombinatorComponent),
|
||||||
|
cv.Required(CONF_PROCESS_STD_DEV): cv.positive_float,
|
||||||
|
cv.Required(CONF_SOURCES): cv.ensure_list(
|
||||||
|
cv.Schema(
|
||||||
|
{
|
||||||
|
cv.Required(CONF_SOURCE): cv.use_id(sensor.Sensor),
|
||||||
|
cv.Required(CONF_ERROR): cv.templatable(cv.positive_float),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_STD_DEV): sensor.SENSOR_SCHEMA,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Inherit some sensor values from the first source, for both the state and the error value
|
||||||
|
properties_to_inherit = [
|
||||||
|
CONF_ACCURACY_DECIMALS,
|
||||||
|
CONF_DEVICE_CLASS,
|
||||||
|
CONF_ENTITY_CATEGORY,
|
||||||
|
CONF_ICON,
|
||||||
|
CONF_UNIT_OF_MEASUREMENT,
|
||||||
|
# CONF_STATE_CLASS could also be inherited, but might lead to unexpected behaviour with "total_increasing"
|
||||||
|
]
|
||||||
|
inherit_schema_for_state = [
|
||||||
|
inherit_property_from(property, [CONF_SOURCES, 0, CONF_SOURCE])
|
||||||
|
for property in properties_to_inherit
|
||||||
|
]
|
||||||
|
inherit_schema_for_std_dev = [
|
||||||
|
inherit_property_from([CONF_STD_DEV, property], [CONF_SOURCES, 0, CONF_SOURCE])
|
||||||
|
for property in properties_to_inherit
|
||||||
|
]
|
||||||
|
|
||||||
|
FINAL_VALIDATE_SCHEMA = cv.All(
|
||||||
|
CONFIG_SCHEMA.extend(
|
||||||
|
{cv.Required(CONF_ID): cv.use_id(KalmanCombinatorComponent)},
|
||||||
|
extra=cv.ALLOW_EXTRA,
|
||||||
|
),
|
||||||
|
*inherit_schema_for_state,
|
||||||
|
*inherit_schema_for_std_dev,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
await cg.register_component(var, config)
|
||||||
|
await sensor.register_sensor(var, config)
|
||||||
|
|
||||||
|
cg.add(var.set_process_std_dev(config[CONF_PROCESS_STD_DEV]))
|
||||||
|
for source_conf in config[CONF_SOURCES]:
|
||||||
|
source = await cg.get_variable(source_conf[CONF_SOURCE])
|
||||||
|
error = await cg.templatable(
|
||||||
|
source_conf[CONF_ERROR],
|
||||||
|
[(float, "x")],
|
||||||
|
cg.float_,
|
||||||
|
)
|
||||||
|
cg.add(var.add_source(source, error))
|
||||||
|
|
||||||
|
if CONF_STD_DEV in config:
|
||||||
|
sens = await sensor.new_sensor(config[CONF_STD_DEV])
|
||||||
|
cg.add(var.set_std_dev_sensor(sens))
|
|
@ -14,6 +14,7 @@ from esphome.const import (
|
||||||
CONF_RESTORE_MODE,
|
CONF_RESTORE_MODE,
|
||||||
CONF_ON_TURN_OFF,
|
CONF_ON_TURN_OFF,
|
||||||
CONF_ON_TURN_ON,
|
CONF_ON_TURN_ON,
|
||||||
|
CONF_ON_STATE,
|
||||||
CONF_TRIGGER_ID,
|
CONF_TRIGGER_ID,
|
||||||
CONF_COLD_WHITE_COLOR_TEMPERATURE,
|
CONF_COLD_WHITE_COLOR_TEMPERATURE,
|
||||||
CONF_WARM_WHITE_COLOR_TEMPERATURE,
|
CONF_WARM_WHITE_COLOR_TEMPERATURE,
|
||||||
|
@ -37,6 +38,7 @@ from .types import ( # noqa
|
||||||
AddressableLight,
|
AddressableLight,
|
||||||
LightTurnOnTrigger,
|
LightTurnOnTrigger,
|
||||||
LightTurnOffTrigger,
|
LightTurnOffTrigger,
|
||||||
|
LightStateTrigger,
|
||||||
)
|
)
|
||||||
|
|
||||||
CODEOWNERS = ["@esphome/core"]
|
CODEOWNERS = ["@esphome/core"]
|
||||||
|
@ -69,6 +71,11 @@ LIGHT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).ex
|
||||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LightTurnOffTrigger),
|
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LightTurnOffTrigger),
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
cv.Optional(CONF_ON_STATE): auto.validate_automation(
|
||||||
|
{
|
||||||
|
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LightStateTrigger),
|
||||||
|
}
|
||||||
|
),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -151,6 +158,9 @@ async def setup_light_core_(light_var, output_var, config):
|
||||||
for conf in config.get(CONF_ON_TURN_OFF, []):
|
for conf in config.get(CONF_ON_TURN_OFF, []):
|
||||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], light_var)
|
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], light_var)
|
||||||
await auto.build_automation(trigger, [], conf)
|
await auto.build_automation(trigger, [], conf)
|
||||||
|
for conf in config.get(CONF_ON_STATE, []):
|
||||||
|
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], light_var)
|
||||||
|
await auto.build_automation(trigger, [], conf)
|
||||||
|
|
||||||
if CONF_COLOR_CORRECT in config:
|
if CONF_COLOR_CORRECT in config:
|
||||||
cg.add(output_var.set_correction(*config[CONF_COLOR_CORRECT]))
|
cg.add(output_var.set_correction(*config[CONF_COLOR_CORRECT]))
|
||||||
|
|
|
@ -331,9 +331,10 @@ class AddressableFlickerEffect : public AddressableLightEffect {
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this->last_update_ = now;
|
this->last_update_ = now;
|
||||||
fast_random_set_seed(random_uint32());
|
uint32_t rng_state = random_uint32();
|
||||||
for (auto var : it) {
|
for (auto var : it) {
|
||||||
const uint8_t flicker = fast_random_8() % intensity;
|
rng_state = (rng_state * 0x9E3779B9) + 0x9E37;
|
||||||
|
const uint8_t flicker = (rng_state & 0xFF) % intensity;
|
||||||
// scale down by random factor
|
// scale down by random factor
|
||||||
var = var.get() * (255 - flicker);
|
var = var.get() * (255 - flicker);
|
||||||
|
|
||||||
|
|
|
@ -141,6 +141,13 @@ class LightTurnOffTrigger : public Trigger<> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class LightStateTrigger : public Trigger<> {
|
||||||
|
public:
|
||||||
|
LightStateTrigger(LightState *a_light) {
|
||||||
|
a_light->add_new_remote_values_callback([this]() { this->trigger(); });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// This is slightly ugly, but we can't log in headers, and can't make this a static method on AddressableSet
|
// This is slightly ugly, but we can't log in headers, and can't make this a static method on AddressableSet
|
||||||
// due to the template. It's just a temporary warning anyway.
|
// due to the template. It's just a temporary warning anyway.
|
||||||
void addressableset_warn_about_scale(const char *field);
|
void addressableset_warn_about_scale(const char *field);
|
||||||
|
|
|
@ -8,7 +8,7 @@ namespace light {
|
||||||
|
|
||||||
// See https://www.home-assistant.io/integrations/light.mqtt/#json-schema for documentation on the schema
|
// See https://www.home-assistant.io/integrations/light.mqtt/#json-schema for documentation on the schema
|
||||||
|
|
||||||
void LightJSONSchema::dump_json(LightState &state, JsonObject &root) {
|
void LightJSONSchema::dump_json(LightState &state, JsonObject root) {
|
||||||
if (state.supports_effects())
|
if (state.supports_effects())
|
||||||
root["effect"] = state.get_effect_name();
|
root["effect"] = state.get_effect_name();
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ void LightJSONSchema::dump_json(LightState &state, JsonObject &root) {
|
||||||
if (values.get_color_mode() & ColorCapability::BRIGHTNESS)
|
if (values.get_color_mode() & ColorCapability::BRIGHTNESS)
|
||||||
root["brightness"] = uint8_t(values.get_brightness() * 255);
|
root["brightness"] = uint8_t(values.get_brightness() * 255);
|
||||||
|
|
||||||
JsonObject &color = root.createNestedObject("color");
|
JsonObject color = root.createNestedObject("color");
|
||||||
if (values.get_color_mode() & ColorCapability::RGB) {
|
if (values.get_color_mode() & ColorCapability::RGB) {
|
||||||
color["r"] = uint8_t(values.get_color_brightness() * values.get_red() * 255);
|
color["r"] = uint8_t(values.get_color_brightness() * values.get_red() * 255);
|
||||||
color["g"] = uint8_t(values.get_color_brightness() * values.get_green() * 255);
|
color["g"] = uint8_t(values.get_color_brightness() * values.get_green() * 255);
|
||||||
|
@ -72,7 +72,7 @@ void LightJSONSchema::dump_json(LightState &state, JsonObject &root) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void LightJSONSchema::parse_color_json(LightState &state, LightCall &call, JsonObject &root) {
|
void LightJSONSchema::parse_color_json(LightState &state, LightCall &call, JsonObject root) {
|
||||||
if (root.containsKey("state")) {
|
if (root.containsKey("state")) {
|
||||||
auto val = parse_on_off(root["state"]);
|
auto val = parse_on_off(root["state"]);
|
||||||
switch (val) {
|
switch (val) {
|
||||||
|
@ -95,7 +95,7 @@ void LightJSONSchema::parse_color_json(LightState &state, LightCall &call, JsonO
|
||||||
}
|
}
|
||||||
|
|
||||||
if (root.containsKey("color")) {
|
if (root.containsKey("color")) {
|
||||||
JsonObject &color = root["color"];
|
JsonObject color = root["color"];
|
||||||
// HA also encodes brightness information in the r, g, b values, so extract that and set it as color brightness.
|
// HA also encodes brightness information in the r, g, b values, so extract that and set it as color brightness.
|
||||||
float max_rgb = 0.0f;
|
float max_rgb = 0.0f;
|
||||||
if (color.containsKey("r")) {
|
if (color.containsKey("r")) {
|
||||||
|
@ -140,7 +140,7 @@ void LightJSONSchema::parse_color_json(LightState &state, LightCall &call, JsonO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void LightJSONSchema::parse_json(LightState &state, LightCall &call, JsonObject &root) {
|
void LightJSONSchema::parse_json(LightState &state, LightCall &call, JsonObject root) {
|
||||||
LightJSONSchema::parse_color_json(state, call, root);
|
LightJSONSchema::parse_color_json(state, call, root);
|
||||||
|
|
||||||
if (root.containsKey("flash")) {
|
if (root.containsKey("flash")) {
|
||||||
|
|
|
@ -14,12 +14,12 @@ namespace light {
|
||||||
class LightJSONSchema {
|
class LightJSONSchema {
|
||||||
public:
|
public:
|
||||||
/// Dump the state of a light as JSON.
|
/// Dump the state of a light as JSON.
|
||||||
static void dump_json(LightState &state, JsonObject &root);
|
static void dump_json(LightState &state, JsonObject root);
|
||||||
/// Parse the JSON state of a light to a LightCall.
|
/// Parse the JSON state of a light to a LightCall.
|
||||||
static void parse_json(LightState &state, LightCall &call, JsonObject &root);
|
static void parse_json(LightState &state, LightCall &call, JsonObject root);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
static void parse_color_json(LightState &state, LightCall &call, JsonObject &root);
|
static void parse_color_json(LightState &state, LightCall &call, JsonObject root);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace light
|
} // namespace light
|
||||||
|
|
|
@ -41,6 +41,7 @@ LightTurnOnTrigger = light_ns.class_(
|
||||||
LightTurnOffTrigger = light_ns.class_(
|
LightTurnOffTrigger = light_ns.class_(
|
||||||
"LightTurnOffTrigger", automation.Trigger.template()
|
"LightTurnOffTrigger", automation.Trigger.template()
|
||||||
)
|
)
|
||||||
|
LightStateTrigger = light_ns.class_("LightStateTrigger", automation.Trigger.template())
|
||||||
|
|
||||||
# Effects
|
# Effects
|
||||||
LightEffect = light_ns.class_("LightEffect")
|
LightEffect = light_ns.class_("LightEffect")
|
||||||
|
|
27
esphome/components/mcp3204/__init__.py
Normal file
27
esphome/components/mcp3204/__init__.py
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import spi
|
||||||
|
from esphome.const import CONF_ID
|
||||||
|
|
||||||
|
DEPENDENCIES = ["spi"]
|
||||||
|
MULTI_CONF = True
|
||||||
|
CODEOWNERS = ["@rsumner"]
|
||||||
|
|
||||||
|
mcp3204_ns = cg.esphome_ns.namespace("mcp3204")
|
||||||
|
MCP3204 = mcp3204_ns.class_("MCP3204", cg.Component, spi.SPIDevice)
|
||||||
|
|
||||||
|
CONF_REFERENCE_VOLTAGE = "reference_voltage"
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = cv.Schema(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(MCP3204),
|
||||||
|
cv.Optional(CONF_REFERENCE_VOLTAGE, default="3.3V"): cv.voltage,
|
||||||
|
}
|
||||||
|
).extend(spi.spi_device_schema(cs_pin_required=True))
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
cg.add(var.set_reference_voltage(config[CONF_REFERENCE_VOLTAGE]))
|
||||||
|
await cg.register_component(var, config)
|
||||||
|
await spi.register_spi_device(var, config)
|
35
esphome/components/mcp3204/mcp3204.cpp
Normal file
35
esphome/components/mcp3204/mcp3204.cpp
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
#include "mcp3204.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace mcp3204 {
|
||||||
|
|
||||||
|
static const char *const TAG = "mcp3204";
|
||||||
|
|
||||||
|
float MCP3204::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||||
|
|
||||||
|
void MCP3204::setup() {
|
||||||
|
ESP_LOGCONFIG(TAG, "Setting up mcp3204");
|
||||||
|
this->spi_setup();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MCP3204::dump_config() {
|
||||||
|
ESP_LOGCONFIG(TAG, "MCP3204:");
|
||||||
|
LOG_PIN(" CS Pin:", this->cs_);
|
||||||
|
ESP_LOGCONFIG(TAG, " Reference Voltage: %.2fV", this->reference_voltage_);
|
||||||
|
}
|
||||||
|
|
||||||
|
float MCP3204::read_data(uint8_t pin) {
|
||||||
|
uint8_t adc_primary_config = 0b00000110 & 0b00000111;
|
||||||
|
uint8_t adc_secondary_config = pin << 6;
|
||||||
|
this->enable();
|
||||||
|
this->transfer_byte(adc_primary_config);
|
||||||
|
uint8_t adc_primary_byte = this->transfer_byte(adc_secondary_config);
|
||||||
|
uint8_t adc_secondary_byte = this->transfer_byte(0x00);
|
||||||
|
this->disable();
|
||||||
|
uint16_t digital_value = (adc_primary_byte << 8 | adc_secondary_byte) & 0b111111111111;
|
||||||
|
return float(digital_value) / 4096.000 * this->reference_voltage_;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace mcp3204
|
||||||
|
} // namespace esphome
|
28
esphome/components/mcp3204/mcp3204.h
Normal file
28
esphome/components/mcp3204/mcp3204.h
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/core/hal.h"
|
||||||
|
#include "esphome/components/spi/spi.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace mcp3204 {
|
||||||
|
|
||||||
|
class MCP3204 : public Component,
|
||||||
|
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,
|
||||||
|
spi::DATA_RATE_1MHZ> {
|
||||||
|
public:
|
||||||
|
MCP3204() = default;
|
||||||
|
|
||||||
|
void set_reference_voltage(float reference_voltage) { this->reference_voltage_ = reference_voltage; }
|
||||||
|
|
||||||
|
void setup() override;
|
||||||
|
void dump_config() override;
|
||||||
|
float get_setup_priority() const override;
|
||||||
|
float read_data(uint8_t pin);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
float reference_voltage_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace mcp3204
|
||||||
|
} // namespace esphome
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue