mirror of
https://github.com/esphome/esphome.git
synced 2024-11-27 17:27:59 +01:00
Merge branch 'dev' into optolink
This commit is contained in:
commit
aec702ffc0
263 changed files with 12623 additions and 1171 deletions
4
.github/actions/build-image/action.yaml
vendored
4
.github/actions/build-image/action.yaml
vendored
|
@ -36,7 +36,7 @@ runs:
|
|||
|
||||
- name: Build and push to ghcr by digest
|
||||
id: build-ghcr
|
||||
uses: docker/build-push-action@v5.0.0
|
||||
uses: docker/build-push-action@v5.2.0
|
||||
with:
|
||||
context: .
|
||||
file: ./docker/Dockerfile
|
||||
|
@ -67,7 +67,7 @@ runs:
|
|||
|
||||
- name: Build and push to dockerhub by digest
|
||||
id: build-dockerhub
|
||||
uses: docker/build-push-action@v5.0.0
|
||||
uses: docker/build-push-action@v5.2.0
|
||||
with:
|
||||
context: .
|
||||
file: ./docker/Dockerfile
|
||||
|
|
2
.github/actions/restore-python/action.yml
vendored
2
.github/actions/restore-python/action.yml
vendored
|
@ -22,7 +22,7 @@ runs:
|
|||
python-version: ${{ inputs.python-version }}
|
||||
- name: Restore Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v3.3.2
|
||||
uses: actions/cache/restore@v4.0.1
|
||||
with:
|
||||
path: venv
|
||||
# yamllint disable-line rule:line-length
|
||||
|
|
10
.github/dependabot.yml
vendored
10
.github/dependabot.yml
vendored
|
@ -13,3 +13,13 @@ updates:
|
|||
schedule:
|
||||
interval: daily
|
||||
open-pull-requests-limit: 10
|
||||
- package-ecosystem: github-actions
|
||||
directory: "/.github/actions/build-image"
|
||||
schedule:
|
||||
interval: daily
|
||||
open-pull-requests-limit: 10
|
||||
- package-ecosystem: github-actions
|
||||
directory: "/.github/actions/restore-python"
|
||||
schedule:
|
||||
interval: daily
|
||||
open-pull-requests-limit: 10
|
||||
|
|
2
.github/workflows/ci-docker.yml
vendored
2
.github/workflows/ci-docker.yml
vendored
|
@ -46,7 +46,7 @@ jobs:
|
|||
with:
|
||||
python-version: "3.9"
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3.0.0
|
||||
uses: docker/setup-buildx-action@v3.1.0
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3.0.0
|
||||
|
||||
|
|
8
.github/workflows/ci.yml
vendored
8
.github/workflows/ci.yml
vendored
|
@ -12,6 +12,7 @@ on:
|
|||
- "!.github/workflows/*.yml"
|
||||
- ".github/workflows/ci.yml"
|
||||
- "!.yamllint"
|
||||
- "!.github/dependabot.yml"
|
||||
merge_group:
|
||||
|
||||
permissions:
|
||||
|
@ -46,7 +47,7 @@ jobs:
|
|||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
- name: Restore Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v4.0.0
|
||||
uses: actions/cache@v4.0.1
|
||||
with:
|
||||
path: venv
|
||||
# yamllint disable-line rule:line-length
|
||||
|
@ -366,7 +367,7 @@ jobs:
|
|||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
- name: Cache platformio
|
||||
uses: actions/cache@v4.0.0
|
||||
uses: actions/cache@v4.0.1
|
||||
with:
|
||||
path: ~/.platformio
|
||||
# yamllint disable-line rule:line-length
|
||||
|
@ -433,6 +434,9 @@ jobs:
|
|||
matrix:
|
||||
file: ${{ fromJson(needs.list-components.outputs.matrix) }}
|
||||
steps:
|
||||
- name: Install libsodium
|
||||
run: sudo apt-get install libsodium-dev
|
||||
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.1
|
||||
- name: Restore Python
|
||||
|
|
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
|
@ -85,7 +85,7 @@ jobs:
|
|||
python-version: "3.9"
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3.0.0
|
||||
uses: docker/setup-buildx-action@v3.1.0
|
||||
- name: Set up QEMU
|
||||
if: matrix.platform != 'linux/amd64'
|
||||
uses: docker/setup-qemu-action@v3.0.0
|
||||
|
@ -163,7 +163,7 @@ jobs:
|
|||
name: digests-${{ matrix.image.target }}-${{ matrix.registry }}
|
||||
path: /tmp/digests
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3.0.0
|
||||
uses: docker/setup-buildx-action@v3.1.0
|
||||
|
||||
- name: Log in to docker hub
|
||||
if: matrix.registry == 'dockerhub'
|
||||
|
|
16
CODEOWNERS
16
CODEOWNERS
|
@ -22,12 +22,15 @@ esphome/components/ade7880/* @kpfleming
|
|||
esphome/components/ade7953/* @angelnu
|
||||
esphome/components/ade7953_i2c/* @angelnu
|
||||
esphome/components/ade7953_spi/* @angelnu
|
||||
esphome/components/ads1118/* @solomondg1
|
||||
esphome/components/ags10/* @mak-42
|
||||
esphome/components/airthings_ble/* @jeromelaban
|
||||
esphome/components/airthings_wave_base/* @jeromelaban @kpfleming @ncareau
|
||||
esphome/components/airthings_wave_mini/* @ncareau
|
||||
esphome/components/airthings_wave_plus/* @jeromelaban
|
||||
esphome/components/alarm_control_panel/* @grahambrown11 @hwstar
|
||||
esphome/components/alpha3/* @jan-hofmeier
|
||||
esphome/components/am2315c/* @swoboda1337
|
||||
esphome/components/am43/* @buxtronix
|
||||
esphome/components/am43/cover/* @buxtronix
|
||||
esphome/components/am43/sensor/* @buxtronix
|
||||
|
@ -78,12 +81,15 @@ esphome/components/copy/* @OttoWinter
|
|||
esphome/components/cover/* @esphome/core
|
||||
esphome/components/cs5460a/* @balrog-kun
|
||||
esphome/components/cse7761/* @berfenger
|
||||
esphome/components/cst226/* @clydebarrow
|
||||
esphome/components/cst816/* @clydebarrow
|
||||
esphome/components/ct_clamp/* @jesserockz
|
||||
esphome/components/current_based/* @djwmarcx
|
||||
esphome/components/dac7678/* @NickB1
|
||||
esphome/components/daikin_brc/* @hagak
|
||||
esphome/components/daly_bms/* @s1lvi0
|
||||
esphome/components/dashboard_import/* @esphome/core
|
||||
esphome/components/datetime/* @rfdarter
|
||||
esphome/components/debug/* @OttoWinter
|
||||
esphome/components/delonghi/* @grob6000
|
||||
esphome/components/dfplayer/* @glmnet
|
||||
|
@ -115,7 +121,8 @@ esphome/components/ezo_pmp/* @carlos-sarmiento
|
|||
esphome/components/factory_reset/* @anatoly-savchenkov
|
||||
esphome/components/fastled_base/* @OttoWinter
|
||||
esphome/components/feedback/* @ianchi
|
||||
esphome/components/fingerprint_grow/* @OnFreund @loongyh
|
||||
esphome/components/fingerprint_grow/* @OnFreund @alexborro @loongyh
|
||||
esphome/components/font/* @clydebarrow @esphome/core
|
||||
esphome/components/fs3000/* @kahrendt
|
||||
esphome/components/ft5x06/* @clydebarrow
|
||||
esphome/components/ft63x6/* @gpambrozio
|
||||
|
@ -225,6 +232,7 @@ esphome/components/mopeka_pro_check/* @spbrogan
|
|||
esphome/components/mopeka_std_check/* @Fabian-Schmidt
|
||||
esphome/components/mpl3115a2/* @kbickar
|
||||
esphome/components/mpu6886/* @fabaff
|
||||
esphome/components/ms8607/* @e28eta
|
||||
esphome/components/network/* @esphome/core
|
||||
esphome/components/nextion/* @senexcrenshaw
|
||||
esphome/components/nextion/binary_sensor/* @senexcrenshaw
|
||||
|
@ -281,6 +289,7 @@ esphome/components/rgbct/* @jesserockz
|
|||
esphome/components/rp2040/* @jesserockz
|
||||
esphome/components/rp2040_pio_led_strip/* @Papa-DMan
|
||||
esphome/components/rp2040_pwm/* @jesserockz
|
||||
esphome/components/rpi_dpi_rgb/* @clydebarrow
|
||||
esphome/components/rtl87xx/* @kuba2k2
|
||||
esphome/components/rtttl/* @glmnet
|
||||
esphome/components/safe_mode/* @jsuanet @paulmonigatti
|
||||
|
@ -288,6 +297,7 @@ esphome/components/scd4x/* @martgras @sjtrny
|
|||
esphome/components/script/* @esphome/core
|
||||
esphome/components/sdm_meter/* @jesserockz @polyfaces
|
||||
esphome/components/sdp3x/* @Azimath
|
||||
esphome/components/seeed_mr24hpc1/* @limengdu
|
||||
esphome/components/selec_meter/* @sourabhjaiswal
|
||||
esphome/components/select/* @esphome/core
|
||||
esphome/components/sen0321/* @notjj
|
||||
|
@ -332,6 +342,7 @@ esphome/components/ssd1351_spi/* @kbx81
|
|||
esphome/components/st7567_base/* @latonita
|
||||
esphome/components/st7567_i2c/* @latonita
|
||||
esphome/components/st7567_spi/* @latonita
|
||||
esphome/components/st7701s/* @clydebarrow
|
||||
esphome/components/st7735/* @SenexCrenshaw
|
||||
esphome/components/st7789v/* @kbx81
|
||||
esphome/components/st7920/* @marsjan155
|
||||
|
@ -344,6 +355,8 @@ esphome/components/tcl112/* @glmnet
|
|||
esphome/components/tee501/* @Stock-M
|
||||
esphome/components/teleinfo/* @0hax
|
||||
esphome/components/template/alarm_control_panel/* @grahambrown11 @hwstar
|
||||
esphome/components/template/datetime/* @rfdarter
|
||||
esphome/components/template/fan/* @ssieb
|
||||
esphome/components/text/* @mauritskorse
|
||||
esphome/components/thermostat/* @kbx81
|
||||
esphome/components/time/* @OttoWinter
|
||||
|
@ -375,6 +388,7 @@ esphome/components/ultrasonic/* @OttoWinter
|
|||
esphome/components/uponor_smatrix/* @kroimon
|
||||
esphome/components/vbus/* @ssieb
|
||||
esphome/components/veml3235/* @kbx81
|
||||
esphome/components/veml7700/* @latonita
|
||||
esphome/components/version/* @esphome/core
|
||||
esphome/components/voice_assistant/* @jesserockz
|
||||
esphome/components/wake_on_lan/* @willwill2will54
|
||||
|
|
|
@ -297,8 +297,27 @@ def upload_using_platformio(config, port):
|
|||
return platformio_api.run_platformio_cli_run(config, CORE.verbose, *upload_args)
|
||||
|
||||
|
||||
def check_permissions(port):
|
||||
if os.name == "posix" and get_port_type(port) == "SERIAL":
|
||||
# Check if we can open selected serial port
|
||||
if not os.access(port, os.F_OK):
|
||||
raise EsphomeError(
|
||||
"The selected serial port does not exist. To resolve this issue, "
|
||||
"check that the device is connected to this computer with a USB cable and that "
|
||||
"the USB cable can be used for data and is not a power-only cable."
|
||||
)
|
||||
if not (os.access(port, os.R_OK | os.W_OK)):
|
||||
raise EsphomeError(
|
||||
"You do not have read or write permission on the selected serial port. "
|
||||
"To resolve this issue, you can add your user to the dialout group "
|
||||
f"by running the following command: sudo usermod -a -G dialout {os.getlogin()}. "
|
||||
"You will need to log out & back in or reboot to activate the new group access."
|
||||
)
|
||||
|
||||
|
||||
def upload_program(config, args, host):
|
||||
if get_port_type(host) == "SERIAL":
|
||||
check_permissions(host)
|
||||
if CORE.target_platform in (PLATFORM_ESP32, PLATFORM_ESP8266):
|
||||
file = getattr(args, "file", None)
|
||||
return upload_using_esptool(config, host, file)
|
||||
|
@ -344,6 +363,7 @@ def show_logs(config, args, port):
|
|||
if "logger" not in config:
|
||||
raise EsphomeError("Logger is not configured!")
|
||||
if get_port_type(port) == "SERIAL":
|
||||
check_permissions(port)
|
||||
return run_miniterm(config, port)
|
||||
if get_port_type(port) == "NETWORK" and "api" in config:
|
||||
if config[CONF_MDNS][CONF_DISABLED] and CONF_MQTT in config:
|
||||
|
|
|
@ -87,4 +87,5 @@ from esphome.cpp_types import ( # noqa
|
|||
gpio_Flags,
|
||||
EntityCategory,
|
||||
Parented,
|
||||
ESPTime,
|
||||
)
|
||||
|
|
25
esphome/components/ads1118/__init__.py
Normal file
25
esphome/components/ads1118/__init__.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import spi
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
CODEOWNERS = ["@solomondg1"]
|
||||
DEPENDENCIES = ["spi"]
|
||||
MULTI_CONF = True
|
||||
|
||||
CONF_ADS1118_ID = "ads1118_id"
|
||||
|
||||
ads1118_ns = cg.esphome_ns.namespace("ads1118")
|
||||
ADS1118 = ads1118_ns.class_("ADS1118", cg.Component, spi.SPIDevice)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ADS1118),
|
||||
}
|
||||
).extend(spi.spi_device_schema(cs_pin_required=True))
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await spi.register_spi_device(var, config)
|
126
esphome/components/ads1118/ads1118.cpp
Normal file
126
esphome/components/ads1118/ads1118.cpp
Normal file
|
@ -0,0 +1,126 @@
|
|||
#include "ads1118.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ads1118 {
|
||||
|
||||
static const char *const TAG = "ads1118";
|
||||
static const uint8_t ADS1118_DATA_RATE_860_SPS = 0b111;
|
||||
|
||||
void ADS1118::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up ads1118");
|
||||
this->spi_setup();
|
||||
|
||||
this->config_ = 0;
|
||||
// Setup multiplexer
|
||||
// 0bx000xxxxxxxxxxxx
|
||||
this->config_ |= ADS1118_MULTIPLEXER_P0_NG << 12;
|
||||
|
||||
// Setup Gain
|
||||
// 0bxxxx000xxxxxxxxx
|
||||
this->config_ |= ADS1118_GAIN_6P144 << 9;
|
||||
|
||||
// Set singleshot mode
|
||||
// 0bxxxxxxx1xxxxxxxx
|
||||
this->config_ |= 0b0000000100000000;
|
||||
|
||||
// Set data rate - 860 samples per second (we're in singleshot mode)
|
||||
// 0bxxxxxxxx100xxxxx
|
||||
this->config_ |= ADS1118_DATA_RATE_860_SPS << 5;
|
||||
|
||||
// Set temperature sensor mode - ADC
|
||||
// 0bxxxxxxxxxxx0xxxx
|
||||
this->config_ |= 0b0000000000000000;
|
||||
|
||||
// Set DOUT pull up - enable
|
||||
// 0bxxxxxxxxxxxx0xxx
|
||||
this->config_ |= 0b0000000000001000;
|
||||
|
||||
// NOP - must be 01
|
||||
// 0bxxxxxxxxxxxxx01x
|
||||
this->config_ |= 0b0000000000000010;
|
||||
|
||||
// Not used - can be 0 or 1, lets be positive
|
||||
// 0bxxxxxxxxxxxxxxx1
|
||||
this->config_ |= 0b0000000000000001;
|
||||
}
|
||||
|
||||
void ADS1118::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "ADS1118:");
|
||||
LOG_PIN(" CS Pin:", this->cs_);
|
||||
}
|
||||
|
||||
float ADS1118::request_measurement(ADS1118Multiplexer multiplexer, ADS1118Gain gain, bool temperature_mode) {
|
||||
uint16_t temp_config = this->config_;
|
||||
// Multiplexer
|
||||
// 0bxBBBxxxxxxxxxxxx
|
||||
temp_config &= 0b1000111111111111;
|
||||
temp_config |= (multiplexer & 0b111) << 12;
|
||||
|
||||
// Gain
|
||||
// 0bxxxxBBBxxxxxxxxx
|
||||
temp_config &= 0b1111000111111111;
|
||||
temp_config |= (gain & 0b111) << 9;
|
||||
|
||||
if (temperature_mode) {
|
||||
// Set temperature sensor mode
|
||||
// 0bxxxxxxxxxxx1xxxx
|
||||
temp_config |= 0b0000000000010000;
|
||||
} else {
|
||||
// Set ADC mode
|
||||
// 0bxxxxxxxxxxx0xxxx
|
||||
temp_config &= 0b1111111111101111;
|
||||
}
|
||||
|
||||
// Start conversion
|
||||
temp_config |= 0b1000000000000000;
|
||||
|
||||
this->enable();
|
||||
this->write_byte16(temp_config);
|
||||
this->disable();
|
||||
|
||||
// about 1.2 ms with 860 samples per second
|
||||
delay(2);
|
||||
|
||||
this->enable();
|
||||
uint8_t adc_first_byte = this->read_byte();
|
||||
uint8_t adc_second_byte = this->read_byte();
|
||||
this->disable();
|
||||
uint16_t raw_conversion = encode_uint16(adc_first_byte, adc_second_byte);
|
||||
|
||||
auto signed_conversion = static_cast<int16_t>(raw_conversion);
|
||||
|
||||
if (temperature_mode) {
|
||||
return (signed_conversion >> 2) * 0.03125f;
|
||||
} else {
|
||||
float millivolts;
|
||||
float divider = 32768.0f;
|
||||
switch (gain) {
|
||||
case ADS1118_GAIN_6P144:
|
||||
millivolts = (signed_conversion * 6144) / divider;
|
||||
break;
|
||||
case ADS1118_GAIN_4P096:
|
||||
millivolts = (signed_conversion * 4096) / divider;
|
||||
break;
|
||||
case ADS1118_GAIN_2P048:
|
||||
millivolts = (signed_conversion * 2048) / divider;
|
||||
break;
|
||||
case ADS1118_GAIN_1P024:
|
||||
millivolts = (signed_conversion * 1024) / divider;
|
||||
break;
|
||||
case ADS1118_GAIN_0P512:
|
||||
millivolts = (signed_conversion * 512) / divider;
|
||||
break;
|
||||
case ADS1118_GAIN_0P256:
|
||||
millivolts = (signed_conversion * 256) / divider;
|
||||
break;
|
||||
default:
|
||||
millivolts = NAN;
|
||||
}
|
||||
|
||||
return millivolts / 1e3f;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ads1118
|
||||
} // namespace esphome
|
46
esphome/components/ads1118/ads1118.h
Normal file
46
esphome/components/ads1118/ads1118.h
Normal file
|
@ -0,0 +1,46 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/components/spi/spi.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ads1118 {
|
||||
|
||||
enum ADS1118Multiplexer {
|
||||
ADS1118_MULTIPLEXER_P0_N1 = 0b000,
|
||||
ADS1118_MULTIPLEXER_P0_N3 = 0b001,
|
||||
ADS1118_MULTIPLEXER_P1_N3 = 0b010,
|
||||
ADS1118_MULTIPLEXER_P2_N3 = 0b011,
|
||||
ADS1118_MULTIPLEXER_P0_NG = 0b100,
|
||||
ADS1118_MULTIPLEXER_P1_NG = 0b101,
|
||||
ADS1118_MULTIPLEXER_P2_NG = 0b110,
|
||||
ADS1118_MULTIPLEXER_P3_NG = 0b111,
|
||||
};
|
||||
|
||||
enum ADS1118Gain {
|
||||
ADS1118_GAIN_6P144 = 0b000,
|
||||
ADS1118_GAIN_4P096 = 0b001,
|
||||
ADS1118_GAIN_2P048 = 0b010,
|
||||
ADS1118_GAIN_1P024 = 0b011,
|
||||
ADS1118_GAIN_0P512 = 0b100,
|
||||
ADS1118_GAIN_0P256 = 0b101,
|
||||
};
|
||||
|
||||
class ADS1118 : public Component,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_TRAILING,
|
||||
spi::DATA_RATE_1MHZ> {
|
||||
public:
|
||||
ADS1118() = default;
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
/// Helper method to request a measurement from a sensor.
|
||||
float request_measurement(ADS1118Multiplexer multiplexer, ADS1118Gain gain, bool temperature_mode);
|
||||
|
||||
protected:
|
||||
uint16_t config_{0};
|
||||
};
|
||||
|
||||
} // namespace ads1118
|
||||
} // namespace esphome
|
97
esphome/components/ads1118/sensor/__init__.py
Normal file
97
esphome/components/ads1118/sensor/__init__.py
Normal file
|
@ -0,0 +1,97 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor, voltage_sampler
|
||||
from esphome.const import (
|
||||
CONF_GAIN,
|
||||
CONF_MULTIPLEXER,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_VOLT,
|
||||
CONF_TYPE,
|
||||
)
|
||||
from .. import ads1118_ns, ADS1118, CONF_ADS1118_ID
|
||||
|
||||
AUTO_LOAD = ["voltage_sampler"]
|
||||
DEPENDENCIES = ["ads1118"]
|
||||
|
||||
ADS1118Multiplexer = ads1118_ns.enum("ADS1118Multiplexer")
|
||||
MUX = {
|
||||
"A0_A1": ADS1118Multiplexer.ADS1118_MULTIPLEXER_P0_N1,
|
||||
"A0_A3": ADS1118Multiplexer.ADS1118_MULTIPLEXER_P0_N3,
|
||||
"A1_A3": ADS1118Multiplexer.ADS1118_MULTIPLEXER_P1_N3,
|
||||
"A2_A3": ADS1118Multiplexer.ADS1118_MULTIPLEXER_P2_N3,
|
||||
"A0_GND": ADS1118Multiplexer.ADS1118_MULTIPLEXER_P0_NG,
|
||||
"A1_GND": ADS1118Multiplexer.ADS1118_MULTIPLEXER_P1_NG,
|
||||
"A2_GND": ADS1118Multiplexer.ADS1118_MULTIPLEXER_P2_NG,
|
||||
"A3_GND": ADS1118Multiplexer.ADS1118_MULTIPLEXER_P3_NG,
|
||||
}
|
||||
|
||||
ADS1118Gain = ads1118_ns.enum("ADS1118Gain")
|
||||
GAIN = {
|
||||
"6.144": ADS1118Gain.ADS1118_GAIN_6P144,
|
||||
"4.096": ADS1118Gain.ADS1118_GAIN_4P096,
|
||||
"2.048": ADS1118Gain.ADS1118_GAIN_2P048,
|
||||
"1.024": ADS1118Gain.ADS1118_GAIN_1P024,
|
||||
"0.512": ADS1118Gain.ADS1118_GAIN_0P512,
|
||||
"0.256": ADS1118Gain.ADS1118_GAIN_0P256,
|
||||
}
|
||||
|
||||
|
||||
ADS1118Sensor = ads1118_ns.class_(
|
||||
"ADS1118Sensor",
|
||||
cg.PollingComponent,
|
||||
sensor.Sensor,
|
||||
voltage_sampler.VoltageSampler,
|
||||
cg.Parented.template(ADS1118),
|
||||
)
|
||||
|
||||
TYPE_ADC = "adc"
|
||||
TYPE_TEMPERATURE = "temperature"
|
||||
|
||||
CONFIG_SCHEMA = cv.typed_schema(
|
||||
{
|
||||
TYPE_ADC: sensor.sensor_schema(
|
||||
ADS1118Sensor,
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
accuracy_decimals=3,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
)
|
||||
.extend(
|
||||
{
|
||||
cv.GenerateID(CONF_ADS1118_ID): cv.use_id(ADS1118),
|
||||
cv.Required(CONF_MULTIPLEXER): cv.enum(MUX, upper=True, space="_"),
|
||||
cv.Required(CONF_GAIN): cv.enum(GAIN, string=True),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s")),
|
||||
TYPE_TEMPERATURE: sensor.sensor_schema(
|
||||
ADS1118Sensor,
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
)
|
||||
.extend(
|
||||
{
|
||||
cv.GenerateID(CONF_ADS1118_ID): cv.use_id(ADS1118),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s")),
|
||||
},
|
||||
default_type=TYPE_ADC,
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = await sensor.new_sensor(config)
|
||||
await cg.register_component(var, config)
|
||||
await cg.register_parented(var, config[CONF_ADS1118_ID])
|
||||
|
||||
if config[CONF_TYPE] == TYPE_ADC:
|
||||
cg.add(var.set_multiplexer(config[CONF_MULTIPLEXER]))
|
||||
cg.add(var.set_gain(config[CONF_GAIN]))
|
||||
if config[CONF_TYPE] == TYPE_TEMPERATURE:
|
||||
cg.add(var.set_temperature_mode(True))
|
29
esphome/components/ads1118/sensor/ads1118_sensor.cpp
Normal file
29
esphome/components/ads1118/sensor/ads1118_sensor.cpp
Normal file
|
@ -0,0 +1,29 @@
|
|||
#include "ads1118_sensor.h"
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ads1118 {
|
||||
|
||||
static const char *const TAG = "ads1118.sensor";
|
||||
|
||||
void ADS1118Sensor::dump_config() {
|
||||
LOG_SENSOR(" ", "ADS1118 Sensor", this);
|
||||
ESP_LOGCONFIG(TAG, " Multiplexer: %u", this->multiplexer_);
|
||||
ESP_LOGCONFIG(TAG, " Gain: %u", this->gain_);
|
||||
}
|
||||
|
||||
float ADS1118Sensor::sample() {
|
||||
return this->parent_->request_measurement(this->multiplexer_, this->gain_, this->temperature_mode_);
|
||||
}
|
||||
|
||||
void ADS1118Sensor::update() {
|
||||
float v = this->sample();
|
||||
if (!std::isnan(v)) {
|
||||
ESP_LOGD(TAG, "'%s': Got Voltage=%fV", this->get_name().c_str(), v);
|
||||
this->publish_state(v);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ads1118
|
||||
} // namespace esphome
|
36
esphome/components/ads1118/sensor/ads1118_sensor.h
Normal file
36
esphome/components/ads1118/sensor/ads1118_sensor.h
Normal file
|
@ -0,0 +1,36 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/voltage_sampler/voltage_sampler.h"
|
||||
|
||||
#include "../ads1118.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ads1118 {
|
||||
|
||||
class ADS1118Sensor : public PollingComponent,
|
||||
public sensor::Sensor,
|
||||
public voltage_sampler::VoltageSampler,
|
||||
public Parented<ADS1118> {
|
||||
public:
|
||||
void update() override;
|
||||
|
||||
void set_multiplexer(ADS1118Multiplexer multiplexer) { this->multiplexer_ = multiplexer; }
|
||||
void set_gain(ADS1118Gain gain) { this->gain_ = gain; }
|
||||
void set_temperature_mode(bool temp) { this->temperature_mode_ = temp; }
|
||||
|
||||
float sample() override;
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
ADS1118Multiplexer multiplexer_{ADS1118_MULTIPLEXER_P0_NG};
|
||||
ADS1118Gain gain_{ADS1118_GAIN_6P144};
|
||||
bool temperature_mode_;
|
||||
};
|
||||
|
||||
} // namespace ads1118
|
||||
} // namespace esphome
|
1
esphome/components/ags10/__init__.py
Normal file
1
esphome/components/ags10/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
CODEOWNERS = ["@mak-42"]
|
212
esphome/components/ags10/ags10.cpp
Normal file
212
esphome/components/ags10/ags10.cpp
Normal file
|
@ -0,0 +1,212 @@
|
|||
#include "ags10.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ags10 {
|
||||
static const char *const TAG = "ags10";
|
||||
|
||||
// Data acquisition.
|
||||
static const uint8_t REG_TVOC = 0x00;
|
||||
// Zero-point calibration.
|
||||
static const uint8_t REG_CALIBRATION = 0x01;
|
||||
// Read version.
|
||||
static const uint8_t REG_VERSION = 0x11;
|
||||
// Read current resistance.
|
||||
static const uint8_t REG_RESISTANCE = 0x20;
|
||||
// Modify target address.
|
||||
static const uint8_t REG_ADDRESS = 0x21;
|
||||
|
||||
// Zero-point calibration with current resistance.
|
||||
static const uint16_t ZP_CURRENT = 0x0000;
|
||||
// Zero-point reset.
|
||||
static const uint16_t ZP_DEFAULT = 0xFFFF;
|
||||
|
||||
void AGS10Component::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up ags10...");
|
||||
|
||||
auto version = this->read_version_();
|
||||
if (version) {
|
||||
ESP_LOGD(TAG, "AGS10 Sensor Version: 0x%02X", *version);
|
||||
if (this->version_ != nullptr) {
|
||||
this->version_->publish_state(*version);
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "AGS10 Sensor Version: unknown");
|
||||
}
|
||||
|
||||
auto resistance = this->read_resistance_();
|
||||
if (resistance) {
|
||||
ESP_LOGD(TAG, "AGS10 Sensor Resistance: 0x%08X", *resistance);
|
||||
if (this->resistance_ != nullptr) {
|
||||
this->resistance_->publish_state(*resistance);
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "AGS10 Sensor Resistance: unknown");
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Sensor initialized");
|
||||
}
|
||||
|
||||
void AGS10Component::update() {
|
||||
auto tvoc = this->read_tvoc_();
|
||||
if (tvoc) {
|
||||
this->tvoc_->publish_state(*tvoc);
|
||||
this->status_clear_warning();
|
||||
} else {
|
||||
this->status_set_warning();
|
||||
}
|
||||
}
|
||||
|
||||
void AGS10Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "AGS10:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
switch (this->error_code_) {
|
||||
case NONE:
|
||||
break;
|
||||
case COMMUNICATION_FAILED:
|
||||
ESP_LOGE(TAG, "Communication with AGS10 failed!");
|
||||
break;
|
||||
case CRC_CHECK_FAILED:
|
||||
ESP_LOGE(TAG, "The crc check failed");
|
||||
break;
|
||||
case ILLEGAL_STATUS:
|
||||
ESP_LOGE(TAG, "AGS10 is not ready to return TVOC data or sensor in pre-heat stage.");
|
||||
break;
|
||||
case UNSUPPORTED_UNITS:
|
||||
ESP_LOGE(TAG, "AGS10 returns TVOC data in unsupported units.");
|
||||
break;
|
||||
default:
|
||||
ESP_LOGE(TAG, "Unknown error: %d", this->error_code_);
|
||||
break;
|
||||
}
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
LOG_SENSOR(" ", "TVOC Sensor", this->tvoc_);
|
||||
LOG_SENSOR(" ", "Firmware Version Sensor", this->version_);
|
||||
LOG_SENSOR(" ", "Resistance Sensor", this->resistance_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets new I2C address of AGS10.
|
||||
*/
|
||||
bool AGS10Component::new_i2c_address(uint8_t newaddress) {
|
||||
uint8_t rev_newaddress = ~newaddress;
|
||||
std::array<uint8_t, 5> data{newaddress, rev_newaddress, newaddress, rev_newaddress, 0};
|
||||
data[4] = calc_crc8_(data, 4);
|
||||
if (!this->write_bytes(REG_ADDRESS, data)) {
|
||||
this->error_code_ = COMMUNICATION_FAILED;
|
||||
this->status_set_warning();
|
||||
ESP_LOGE(TAG, "couldn't write the new I2C address 0x%02X", newaddress);
|
||||
return false;
|
||||
}
|
||||
this->set_i2c_address(newaddress);
|
||||
ESP_LOGW(TAG, "changed I2C address to 0x%02X", newaddress);
|
||||
this->error_code_ = NONE;
|
||||
this->status_clear_warning();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AGS10Component::set_zero_point_with_factory_defaults() { return this->set_zero_point_with(ZP_DEFAULT); }
|
||||
|
||||
bool AGS10Component::set_zero_point_with_current_resistance() { return this->set_zero_point_with(ZP_CURRENT); }
|
||||
|
||||
bool AGS10Component::set_zero_point_with(uint16_t value) {
|
||||
std::array<uint8_t, 5> data{0x00, 0x0C, (uint8_t) ((value >> 8) & 0xFF), (uint8_t) (value & 0xFF), 0};
|
||||
data[4] = calc_crc8_(data, 4);
|
||||
if (!this->write_bytes(REG_CALIBRATION, data)) {
|
||||
this->error_code_ = COMMUNICATION_FAILED;
|
||||
this->status_set_warning();
|
||||
ESP_LOGE(TAG, "unable to set zero-point calibration with 0x%02X", value);
|
||||
return false;
|
||||
}
|
||||
if (value == ZP_CURRENT) {
|
||||
ESP_LOGI(TAG, "zero-point calibration has been set with current resistance");
|
||||
} else if (value == ZP_DEFAULT) {
|
||||
ESP_LOGI(TAG, "zero-point calibration has been reset to the factory defaults");
|
||||
} else {
|
||||
ESP_LOGI(TAG, "zero-point calibration has been set with 0x%02X", value);
|
||||
}
|
||||
this->error_code_ = NONE;
|
||||
this->status_clear_warning();
|
||||
return true;
|
||||
}
|
||||
|
||||
optional<uint32_t> AGS10Component::read_tvoc_() {
|
||||
auto data = this->read_and_check_<5>(REG_TVOC);
|
||||
if (!data) {
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
auto res = *data;
|
||||
auto status_byte = res[0];
|
||||
|
||||
int units = status_byte & 0x0e;
|
||||
int status_bit = status_byte & 0x01;
|
||||
|
||||
if (status_bit != 0) {
|
||||
this->error_code_ = ILLEGAL_STATUS;
|
||||
ESP_LOGW(TAG, "Reading AGS10 data failed: illegal status (not ready or sensor in pre-heat stage)!");
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
if (units != 0) {
|
||||
this->error_code_ = UNSUPPORTED_UNITS;
|
||||
ESP_LOGE(TAG, "Reading AGS10 data failed: unsupported units (%d)!", units);
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
return encode_uint24(res[1], res[2], res[3]);
|
||||
}
|
||||
|
||||
optional<uint8_t> AGS10Component::read_version_() {
|
||||
auto data = this->read_and_check_<5>(REG_VERSION);
|
||||
if (data) {
|
||||
auto res = *data;
|
||||
return res[3];
|
||||
}
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
optional<uint32_t> AGS10Component::read_resistance_() {
|
||||
auto data = this->read_and_check_<5>(REG_RESISTANCE);
|
||||
if (data) {
|
||||
auto res = *data;
|
||||
return encode_uint32(res[0], res[1], res[2], res[3]);
|
||||
}
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
template<size_t N> optional<std::array<uint8_t, N>> AGS10Component::read_and_check_(uint8_t a_register) {
|
||||
auto data = this->read_bytes<N>(a_register);
|
||||
if (!data.has_value()) {
|
||||
this->error_code_ = COMMUNICATION_FAILED;
|
||||
ESP_LOGE(TAG, "Reading AGS10 version failed!");
|
||||
return optional<std::array<uint8_t, N>>();
|
||||
}
|
||||
auto len = N - 1;
|
||||
auto res = *data;
|
||||
auto crc_byte = res[len];
|
||||
|
||||
if (crc_byte != calc_crc8_(res, len)) {
|
||||
this->error_code_ = CRC_CHECK_FAILED;
|
||||
ESP_LOGE(TAG, "Reading AGS10 version failed: crc error!");
|
||||
return optional<std::array<uint8_t, N>>();
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
template<size_t N> uint8_t AGS10Component::calc_crc8_(std::array<uint8_t, N> dat, uint8_t num) {
|
||||
uint8_t i, byte1, crc = 0xFF;
|
||||
for (byte1 = 0; byte1 < num; byte1++) {
|
||||
crc ^= (dat[byte1]);
|
||||
for (i = 0; i < 8; i++) {
|
||||
if (crc & 0x80) {
|
||||
crc = (crc << 1) ^ 0x31;
|
||||
} else {
|
||||
crc = (crc << 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
} // namespace ags10
|
||||
} // namespace esphome
|
152
esphome/components/ags10/ags10.h
Normal file
152
esphome/components/ags10/ags10.h
Normal file
|
@ -0,0 +1,152 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ags10 {
|
||||
|
||||
class AGS10Component : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
/**
|
||||
* Sets TVOC sensor.
|
||||
*/
|
||||
void set_tvoc(sensor::Sensor *tvoc) { this->tvoc_ = tvoc; }
|
||||
|
||||
/**
|
||||
* Sets version info sensor.
|
||||
*/
|
||||
void set_version(sensor::Sensor *version) { this->version_ = version; }
|
||||
|
||||
/**
|
||||
* Sets resistance info sensor.
|
||||
*/
|
||||
void set_resistance(sensor::Sensor *resistance) { this->resistance_ = resistance; }
|
||||
|
||||
void setup() override;
|
||||
|
||||
void update() override;
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
|
||||
/**
|
||||
* Modifies target address of AGS10.
|
||||
*
|
||||
* New address is saved and takes effect immediately even after power-off.
|
||||
*/
|
||||
bool new_i2c_address(uint8_t newaddress);
|
||||
|
||||
/**
|
||||
* Sets zero-point with factory defaults.
|
||||
*/
|
||||
bool set_zero_point_with_factory_defaults();
|
||||
|
||||
/**
|
||||
* Sets zero-point with current sensor resistance.
|
||||
*/
|
||||
bool set_zero_point_with_current_resistance();
|
||||
|
||||
/**
|
||||
* Sets zero-point with the value.
|
||||
*/
|
||||
bool set_zero_point_with(uint16_t value);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* TVOC.
|
||||
*/
|
||||
sensor::Sensor *tvoc_{nullptr};
|
||||
|
||||
/**
|
||||
* Firmvare version.
|
||||
*/
|
||||
sensor::Sensor *version_{nullptr};
|
||||
|
||||
/**
|
||||
* Resistance.
|
||||
*/
|
||||
sensor::Sensor *resistance_{nullptr};
|
||||
|
||||
/**
|
||||
* Last operation error code.
|
||||
*/
|
||||
enum ErrorCode {
|
||||
NONE = 0,
|
||||
COMMUNICATION_FAILED,
|
||||
CRC_CHECK_FAILED,
|
||||
ILLEGAL_STATUS,
|
||||
UNSUPPORTED_UNITS,
|
||||
} error_code_{NONE};
|
||||
|
||||
/**
|
||||
* Reads and returns value of TVOC.
|
||||
*/
|
||||
optional<uint32_t> read_tvoc_();
|
||||
|
||||
/**
|
||||
* Reads and returns a firmware version of AGS10.
|
||||
*/
|
||||
optional<uint8_t> read_version_();
|
||||
|
||||
/**
|
||||
* Reads and returns the resistance of AGS10.
|
||||
*/
|
||||
optional<uint32_t> read_resistance_();
|
||||
|
||||
/**
|
||||
* Read, checks and returns data from the sensor.
|
||||
*/
|
||||
template<size_t N> optional<std::array<uint8_t, N>> read_and_check_(uint8_t a_register);
|
||||
|
||||
/**
|
||||
* Calculates CRC8 value.
|
||||
*
|
||||
* CRC8 calculation, initial value: 0xFF, polynomial: 0x31 (x8+ x5+ x4+1)
|
||||
*
|
||||
* @param[in] dat the data buffer
|
||||
* @param num number of bytes in the buffer
|
||||
*/
|
||||
template<size_t N> uint8_t calc_crc8_(std::array<uint8_t, N> dat, uint8_t num);
|
||||
};
|
||||
|
||||
template<typename... Ts> class AGS10NewI2cAddressAction : public Action<Ts...>, public Parented<AGS10Component> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(uint8_t, new_address)
|
||||
|
||||
void play(Ts... x) override { this->parent_->new_i2c_address(this->new_address_.value(x...)); }
|
||||
};
|
||||
|
||||
enum AGS10SetZeroPointActionMode {
|
||||
// Zero-point reset.
|
||||
FACTORY_DEFAULT,
|
||||
// Zero-point calibration with current resistance.
|
||||
CURRENT_VALUE,
|
||||
// Zero-point calibration with custom resistance.
|
||||
CUSTOM_VALUE,
|
||||
};
|
||||
|
||||
template<typename... Ts> class AGS10SetZeroPointAction : public Action<Ts...>, public Parented<AGS10Component> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(uint16_t, value)
|
||||
TEMPLATABLE_VALUE(AGS10SetZeroPointActionMode, mode)
|
||||
|
||||
void play(Ts... x) override {
|
||||
switch (this->mode_.value(x...)) {
|
||||
case FACTORY_DEFAULT:
|
||||
this->parent_->set_zero_point_with_factory_defaults();
|
||||
break;
|
||||
case CURRENT_VALUE:
|
||||
this->parent_->set_zero_point_with_current_resistance();
|
||||
break;
|
||||
case CUSTOM_VALUE:
|
||||
this->parent_->set_zero_point_with(this->value_.value(x...));
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
} // namespace ags10
|
||||
} // namespace esphome
|
132
esphome/components/ags10/sensor.py
Normal file
132
esphome/components/ags10/sensor.py
Normal file
|
@ -0,0 +1,132 @@
|
|||
import esphome.codegen as cg
|
||||
from esphome import automation
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
ICON_RADIATOR,
|
||||
ICON_RESTART,
|
||||
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
|
||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_OHM,
|
||||
UNIT_PARTS_PER_BILLION,
|
||||
CONF_ADDRESS,
|
||||
CONF_TVOC,
|
||||
CONF_VERSION,
|
||||
CONF_MODE,
|
||||
CONF_VALUE,
|
||||
)
|
||||
|
||||
CONF_RESISTANCE = "resistance"
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
ags10_ns = cg.esphome_ns.namespace("ags10")
|
||||
AGS10Component = ags10_ns.class_("AGS10Component", cg.PollingComponent, i2c.I2CDevice)
|
||||
|
||||
# Actions
|
||||
AGS10NewI2cAddressAction = ags10_ns.class_(
|
||||
"AGS10NewI2cAddressAction", automation.Action
|
||||
)
|
||||
AGS10SetZeroPointAction = ags10_ns.class_("AGS10SetZeroPointAction", automation.Action)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(AGS10Component),
|
||||
cv.Optional(CONF_TVOC): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PARTS_PER_BILLION,
|
||||
icon=ICON_RADIATOR,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_VERSION): sensor.sensor_schema(
|
||||
icon=ICON_RESTART,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_RESISTANCE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_OHM,
|
||||
icon=ICON_RESTART,
|
||||
accuracy_decimals=0,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(i2c.i2c_device_schema(0x1A))
|
||||
)
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = i2c.final_validate_device_schema("ags10", max_frequency="15khz")
|
||||
|
||||
|
||||
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)
|
||||
|
||||
sens = await sensor.new_sensor(config[CONF_TVOC])
|
||||
cg.add(var.set_tvoc(sens))
|
||||
|
||||
if version_config := config.get(CONF_VERSION):
|
||||
sens = await sensor.new_sensor(version_config)
|
||||
cg.add(var.set_version(sens))
|
||||
|
||||
if resistance_config := config.get(CONF_RESISTANCE):
|
||||
sens = await sensor.new_sensor(resistance_config)
|
||||
cg.add(var.set_resistance(sens))
|
||||
|
||||
|
||||
AGS10_NEW_I2C_ADDRESS_SCHEMA = cv.maybe_simple_value(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(AGS10Component),
|
||||
cv.Required(CONF_ADDRESS): cv.templatable(cv.i2c_address),
|
||||
},
|
||||
key=CONF_ADDRESS,
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"ags10.new_i2c_address",
|
||||
AGS10NewI2cAddressAction,
|
||||
AGS10_NEW_I2C_ADDRESS_SCHEMA,
|
||||
)
|
||||
async def ags10newi2caddress_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
address = await cg.templatable(config[CONF_ADDRESS], args, int)
|
||||
cg.add(var.set_new_address(address))
|
||||
return var
|
||||
|
||||
|
||||
AGS10SetZeroPointActionMode = ags10_ns.enum("AGS10SetZeroPointActionMode")
|
||||
AGS10_SET_ZERO_POINT_ACTION_MODE = {
|
||||
"FACTORY_DEFAULT": AGS10SetZeroPointActionMode.FACTORY_DEFAULT,
|
||||
"CURRENT_VALUE": AGS10SetZeroPointActionMode.CURRENT_VALUE,
|
||||
"CUSTOM_VALUE": AGS10SetZeroPointActionMode.CUSTOM_VALUE,
|
||||
}
|
||||
|
||||
AGS10_SET_ZERO_POINT_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(AGS10Component),
|
||||
cv.Required(CONF_MODE): cv.enum(AGS10_SET_ZERO_POINT_ACTION_MODE, upper=True),
|
||||
cv.Optional(CONF_VALUE, default=0xFFFF): cv.templatable(cv.uint16_t),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"ags10.set_zero_point",
|
||||
AGS10SetZeroPointAction,
|
||||
AGS10_SET_ZERO_POINT_SCHEMA,
|
||||
)
|
||||
async def ags10setzeropoint_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
mode = await cg.templatable(config.get(CONF_MODE), args, enumerate)
|
||||
cg.add(var.set_mode(mode))
|
||||
value = await cg.templatable(config[CONF_VALUE], args, int)
|
||||
cg.add(var.set_value(value))
|
||||
return var
|
|
@ -21,30 +21,39 @@ namespace esphome {
|
|||
namespace aht10 {
|
||||
|
||||
static const char *const TAG = "aht10";
|
||||
static const size_t SIZE_CALIBRATE_CMD = 3;
|
||||
static const uint8_t AHT10_CALIBRATE_CMD[] = {0xE1, 0x08, 0x00};
|
||||
static const uint8_t AHT20_CALIBRATE_CMD[] = {0xBE, 0x08, 0x00};
|
||||
static const uint8_t AHT10_INITIALIZE_CMD[] = {0xE1, 0x08, 0x00};
|
||||
static const uint8_t AHT20_INITIALIZE_CMD[] = {0xBE, 0x08, 0x00};
|
||||
static const uint8_t AHT10_MEASURE_CMD[] = {0xAC, 0x33, 0x00};
|
||||
static const uint8_t AHT10_DEFAULT_DELAY = 5; // ms, for calibration and temperature measurement
|
||||
static const uint8_t AHT10_SOFTRESET_CMD[] = {0xBA};
|
||||
|
||||
static const uint8_t AHT10_DEFAULT_DELAY = 5; // ms, for initialization and temperature measurement
|
||||
static const uint8_t AHT10_HUMIDITY_DELAY = 30; // ms
|
||||
static const uint8_t AHT10_SOFTRESET_DELAY = 30; // ms
|
||||
|
||||
static const uint8_t AHT10_ATTEMPTS = 3; // safety margin, normally 3 attempts are enough: 3*30=90ms
|
||||
static const uint8_t AHT10_CAL_ATTEMPTS = 10;
|
||||
static const uint8_t AHT10_INIT_ATTEMPTS = 10;
|
||||
|
||||
static const uint8_t AHT10_STATUS_BUSY = 0x80;
|
||||
|
||||
void AHT10Component::setup() {
|
||||
const uint8_t *calibrate_cmd;
|
||||
if (this->write(AHT10_SOFTRESET_CMD, sizeof(AHT10_SOFTRESET_CMD)) != i2c::ERROR_OK) {
|
||||
ESP_LOGE(TAG, "Reset AHT10 failed!");
|
||||
}
|
||||
delay(AHT10_SOFTRESET_DELAY);
|
||||
|
||||
const uint8_t *init_cmd;
|
||||
switch (this->variant_) {
|
||||
case AHT10Variant::AHT20:
|
||||
calibrate_cmd = AHT20_CALIBRATE_CMD;
|
||||
init_cmd = AHT20_INITIALIZE_CMD;
|
||||
ESP_LOGCONFIG(TAG, "Setting up AHT20");
|
||||
break;
|
||||
case AHT10Variant::AHT10:
|
||||
default:
|
||||
calibrate_cmd = AHT10_CALIBRATE_CMD;
|
||||
init_cmd = AHT10_INITIALIZE_CMD;
|
||||
ESP_LOGCONFIG(TAG, "Setting up AHT10");
|
||||
}
|
||||
|
||||
if (this->write(calibrate_cmd, SIZE_CALIBRATE_CMD) != i2c::ERROR_OK) {
|
||||
if (this->write(init_cmd, sizeof(init_cmd)) != i2c::ERROR_OK) {
|
||||
ESP_LOGE(TAG, "Communication with AHT10 failed!");
|
||||
this->mark_failed();
|
||||
return;
|
||||
|
@ -59,19 +68,19 @@ void AHT10Component::setup() {
|
|||
return;
|
||||
}
|
||||
++cal_attempts;
|
||||
if (cal_attempts > AHT10_CAL_ATTEMPTS) {
|
||||
ESP_LOGE(TAG, "AHT10 calibration timed out!");
|
||||
if (cal_attempts > AHT10_INIT_ATTEMPTS) {
|
||||
ESP_LOGE(TAG, "AHT10 initialization timed out!");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if ((data & 0x68) != 0x08) { // Bit[6:5] = 0b00, NORMAL mode and Bit[3] = 0b1, CALIBRATED
|
||||
ESP_LOGE(TAG, "AHT10 calibration failed!");
|
||||
ESP_LOGE(TAG, "AHT10 initialization failed!");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "AHT10 calibrated");
|
||||
ESP_LOGV(TAG, "AHT10 initialization");
|
||||
}
|
||||
|
||||
void AHT10Component::update() {
|
||||
|
|
1
esphome/components/am2315c/__init__.py
Normal file
1
esphome/components/am2315c/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
CODEOWNERS = ["@swoboda1337"]
|
200
esphome/components/am2315c/am2315c.cpp
Normal file
200
esphome/components/am2315c/am2315c.cpp
Normal file
|
@ -0,0 +1,200 @@
|
|||
// MIT License
|
||||
//
|
||||
// Copyright (c) 2023-2024 Rob Tillaart
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
#include "am2315c.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace am2315c {
|
||||
|
||||
static const char *const TAG = "am2315c";
|
||||
|
||||
uint8_t AM2315C::crc8_(uint8_t *data, uint8_t len) {
|
||||
uint8_t crc = 0xFF;
|
||||
while (len--) {
|
||||
crc ^= *data++;
|
||||
for (uint8_t i = 0; i < 8; i++) {
|
||||
if (crc & 0x80) {
|
||||
crc <<= 1;
|
||||
crc ^= 0x31;
|
||||
} else {
|
||||
crc <<= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
bool AM2315C::reset_register_(uint8_t reg) {
|
||||
// code based on demo code sent by www.aosong.com
|
||||
// no further documentation.
|
||||
// 0x1B returned 18, 0, 4
|
||||
// 0x1C returned 18, 65, 0
|
||||
// 0x1E returned 18, 8, 0
|
||||
// 18 seems to be status register
|
||||
// other values unknown.
|
||||
uint8_t data[3];
|
||||
data[0] = reg;
|
||||
data[1] = 0;
|
||||
data[2] = 0;
|
||||
ESP_LOGD(TAG, "Reset register: 0x%02x", reg);
|
||||
if (this->write(data, 3) != i2c::ERROR_OK) {
|
||||
ESP_LOGE(TAG, "Write failed!");
|
||||
this->mark_failed();
|
||||
return false;
|
||||
}
|
||||
delay(5);
|
||||
if (this->read(data, 3) != i2c::ERROR_OK) {
|
||||
ESP_LOGE(TAG, "Read failed!");
|
||||
this->mark_failed();
|
||||
return false;
|
||||
}
|
||||
delay(10);
|
||||
data[0] = 0xB0 | reg;
|
||||
if (this->write(data, 3) != i2c::ERROR_OK) {
|
||||
ESP_LOGE(TAG, "Write failed!");
|
||||
this->mark_failed();
|
||||
return false;
|
||||
}
|
||||
delay(5);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AM2315C::convert_(uint8_t *data, float &humidity, float &temperature) {
|
||||
uint32_t raw;
|
||||
raw = (data[1] << 12) | (data[2] << 4) | (data[3] >> 4);
|
||||
humidity = raw * 9.5367431640625e-5;
|
||||
raw = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5];
|
||||
temperature = raw * 1.9073486328125e-4 - 50;
|
||||
return this->crc8_(data, 6) == data[6];
|
||||
}
|
||||
|
||||
void AM2315C::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up AM2315C...");
|
||||
|
||||
// get status
|
||||
uint8_t status = 0;
|
||||
if (this->read(&status, 1) != i2c::ERROR_OK) {
|
||||
ESP_LOGE(TAG, "Read failed!");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// reset registers if required, according to the datasheet
|
||||
// this can be required after power on, although this was
|
||||
// never required during testing
|
||||
if ((status & 0x18) != 0x18) {
|
||||
ESP_LOGD(TAG, "Resetting AM2315C registers");
|
||||
if (!this->reset_register_(0x1B)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
if (!this->reset_register_(0x1C)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
if (!this->reset_register_(0x1E)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AM2315C::update() {
|
||||
// request measurement
|
||||
uint8_t data[3];
|
||||
data[0] = 0xAC;
|
||||
data[1] = 0x33;
|
||||
data[2] = 0x00;
|
||||
if (this->write(data, 3) != i2c::ERROR_OK) {
|
||||
ESP_LOGE(TAG, "Write failed!");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// wait for hw to complete measurement
|
||||
set_timeout(160, [this]() {
|
||||
// check status
|
||||
uint8_t status = 0;
|
||||
if (this->read(&status, 1) != i2c::ERROR_OK) {
|
||||
ESP_LOGE(TAG, "Read failed!");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
if ((status & 0x80) == 0x80) {
|
||||
ESP_LOGE(TAG, "HW still busy!");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// read
|
||||
uint8_t data[7];
|
||||
if (this->read(data, 7) != i2c::ERROR_OK) {
|
||||
ESP_LOGE(TAG, "Read failed!");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// check for all zeros
|
||||
bool zeros = true;
|
||||
for (uint8_t i : data) {
|
||||
zeros = zeros && (i == 0);
|
||||
}
|
||||
if (zeros) {
|
||||
ESP_LOGW(TAG, "Data all zeros!");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
// convert
|
||||
float temperature = 0.0;
|
||||
float humidity = 0.0;
|
||||
if (this->convert_(data, humidity, temperature)) {
|
||||
if (this->temperature_sensor_ != nullptr) {
|
||||
this->temperature_sensor_->publish_state(temperature);
|
||||
}
|
||||
if (this->humidity_sensor_ != nullptr) {
|
||||
this->humidity_sensor_->publish_state(humidity);
|
||||
}
|
||||
this->status_clear_warning();
|
||||
} else {
|
||||
ESP_LOGW(TAG, "CRC failed!");
|
||||
this->status_set_warning();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void AM2315C::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "AM2315C:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, "Communication with AM2315C failed!");
|
||||
}
|
||||
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
|
||||
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
|
||||
}
|
||||
|
||||
float AM2315C::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
} // namespace am2315c
|
||||
} // namespace esphome
|
51
esphome/components/am2315c/am2315c.h
Normal file
51
esphome/components/am2315c/am2315c.h
Normal file
|
@ -0,0 +1,51 @@
|
|||
// MIT License
|
||||
//
|
||||
// Copyright (c) 2023-2024 Rob Tillaart
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace am2315c {
|
||||
|
||||
class AM2315C : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
void dump_config() override;
|
||||
void update() override;
|
||||
void setup() override;
|
||||
float get_setup_priority() const override;
|
||||
|
||||
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { this->temperature_sensor_ = temperature_sensor; }
|
||||
void set_humidity_sensor(sensor::Sensor *humidity_sensor) { this->humidity_sensor_ = humidity_sensor; }
|
||||
|
||||
protected:
|
||||
uint8_t crc8_(uint8_t *data, uint8_t len);
|
||||
bool convert_(uint8_t *data, float &humidity, float &temperature);
|
||||
bool reset_register_(uint8_t reg);
|
||||
|
||||
sensor::Sensor *temperature_sensor_{nullptr};
|
||||
sensor::Sensor *humidity_sensor_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace am2315c
|
||||
} // namespace esphome
|
54
esphome/components/am2315c/sensor.py
Normal file
54
esphome/components/am2315c/sensor.py
Normal file
|
@ -0,0 +1,54 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.const import (
|
||||
CONF_HUMIDITY,
|
||||
CONF_ID,
|
||||
CONF_TEMPERATURE,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_PERCENT,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
am2315c_ns = cg.esphome_ns.namespace("am2315c")
|
||||
AM2315C = am2315c_ns.class_("AM2315C", cg.PollingComponent, i2c.I2CDevice)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(AM2315C),
|
||||
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,
|
||||
),
|
||||
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_HUMIDITY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(i2c.i2c_device_schema(0x38))
|
||||
)
|
||||
|
||||
|
||||
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 temperature_config := config.get(CONF_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(temperature_config)
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
|
||||
if humidity_config := config.get(CONF_HUMIDITY):
|
||||
sens = await sensor.new_sensor(humidity_config)
|
||||
cg.add(var.set_humidity_sensor(sens))
|
|
@ -44,6 +44,7 @@ service APIConnection {
|
|||
rpc button_command (ButtonCommandRequest) returns (void) {}
|
||||
rpc lock_command (LockCommandRequest) returns (void) {}
|
||||
rpc media_player_command (MediaPlayerCommandRequest) returns (void) {}
|
||||
rpc date_command (DateCommandRequest) returns (void) {}
|
||||
|
||||
rpc subscribe_bluetooth_le_advertisements(SubscribeBluetoothLEAdvertisementsRequest) returns (void) {}
|
||||
rpc bluetooth_device_request(BluetoothDeviceRequest) returns (void) {}
|
||||
|
@ -1450,6 +1451,7 @@ message VoiceAssistantRequest {
|
|||
string conversation_id = 2;
|
||||
uint32 flags = 3;
|
||||
VoiceAssistantAudioSettings audio_settings = 4;
|
||||
string wake_word_phrase = 5;
|
||||
}
|
||||
|
||||
message VoiceAssistantResponse {
|
||||
|
@ -1597,3 +1599,45 @@ message TextCommandRequest {
|
|||
fixed32 key = 1;
|
||||
string state = 2;
|
||||
}
|
||||
|
||||
|
||||
// ==================== DATETIME DATE ====================
|
||||
message ListEntitiesDateResponse {
|
||||
option (id) = 100;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_DATETIME_DATE";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
|
||||
string icon = 5;
|
||||
bool disabled_by_default = 6;
|
||||
EntityCategory entity_category = 7;
|
||||
}
|
||||
message DateStateResponse {
|
||||
option (id) = 101;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_DATETIME_DATE";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
// If the date does not have a valid state yet.
|
||||
// Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller
|
||||
bool missing_state = 2;
|
||||
uint32 year = 3;
|
||||
uint32 month = 4;
|
||||
uint32 day = 5;
|
||||
}
|
||||
message DateCommandRequest {
|
||||
option (id) = 102;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_DATETIME_DATE";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
uint32 year = 2;
|
||||
uint32 month = 3;
|
||||
uint32 day = 4;
|
||||
}
|
||||
|
|
|
@ -698,6 +698,43 @@ void APIConnection::number_command(const NumberCommandRequest &msg) {
|
|||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_DATETIME_DATE
|
||||
bool APIConnection::send_date_state(datetime::DateEntity *date) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
DateStateResponse resp{};
|
||||
resp.key = date->get_object_id_hash();
|
||||
resp.missing_state = !date->has_state();
|
||||
resp.year = date->year;
|
||||
resp.month = date->month;
|
||||
resp.day = date->day;
|
||||
return this->send_date_state_response(resp);
|
||||
}
|
||||
bool APIConnection::send_date_info(datetime::DateEntity *date) {
|
||||
ListEntitiesDateResponse msg;
|
||||
msg.key = date->get_object_id_hash();
|
||||
msg.object_id = date->get_object_id();
|
||||
if (date->has_own_name())
|
||||
msg.name = date->get_name();
|
||||
msg.unique_id = get_default_unique_id("date", date);
|
||||
msg.icon = date->get_icon();
|
||||
msg.disabled_by_default = date->is_disabled_by_default();
|
||||
msg.entity_category = static_cast<enums::EntityCategory>(date->get_entity_category());
|
||||
|
||||
return this->send_list_entities_date_response(msg);
|
||||
}
|
||||
void APIConnection::date_command(const DateCommandRequest &msg) {
|
||||
datetime::DateEntity *date = App.get_date_by_key(msg.key);
|
||||
if (date == nullptr)
|
||||
return;
|
||||
|
||||
auto call = date->make_call();
|
||||
call.set_date(msg.year, msg.month, msg.day);
|
||||
call.perform();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT
|
||||
bool APIConnection::send_text_state(text::Text *text, std::string state) {
|
||||
if (!this->state_subscription_)
|
||||
|
|
|
@ -72,6 +72,11 @@ class APIConnection : public APIServerConnection {
|
|||
bool send_number_info(number::Number *number);
|
||||
void number_command(const NumberCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_DATETIME_DATE
|
||||
bool send_date_state(datetime::DateEntity *date);
|
||||
bool send_date_info(datetime::DateEntity *date);
|
||||
void date_command(const DateCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_TEXT
|
||||
bool send_text_state(text::Text *text, std::string state);
|
||||
bool send_text_info(text::Text *text);
|
||||
|
|
|
@ -6603,6 +6603,10 @@ bool VoiceAssistantRequest::decode_length(uint32_t field_id, ProtoLengthDelimite
|
|||
this->audio_settings = value.as_message<VoiceAssistantAudioSettings>();
|
||||
return true;
|
||||
}
|
||||
case 5: {
|
||||
this->wake_word_phrase = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
@ -6612,6 +6616,7 @@ void VoiceAssistantRequest::encode(ProtoWriteBuffer buffer) const {
|
|||
buffer.encode_string(2, this->conversation_id);
|
||||
buffer.encode_uint32(3, this->flags);
|
||||
buffer.encode_message<VoiceAssistantAudioSettings>(4, this->audio_settings);
|
||||
buffer.encode_string(5, this->wake_word_phrase);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void VoiceAssistantRequest::dump_to(std::string &out) const {
|
||||
|
@ -6633,6 +6638,10 @@ void VoiceAssistantRequest::dump_to(std::string &out) const {
|
|||
out.append(" audio_settings: ");
|
||||
this->audio_settings.dump_to(out);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" wake_word_phrase: ");
|
||||
out.append("'").append(this->wake_word_phrase).append("'");
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
|
@ -7175,6 +7184,225 @@ void TextCommandRequest::dump_to(std::string &out) const {
|
|||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool ListEntitiesDateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 6: {
|
||||
this->disabled_by_default = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 7: {
|
||||
this->entity_category = value.as_enum<enums::EntityCategory>();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool ListEntitiesDateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->object_id = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 3: {
|
||||
this->name = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 4: {
|
||||
this->unique_id = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 5: {
|
||||
this->icon = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool ListEntitiesDateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
switch (field_id) {
|
||||
case 2: {
|
||||
this->key = value.as_fixed32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void ListEntitiesDateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
buffer.encode_string(4, this->unique_id);
|
||||
buffer.encode_string(5, this->icon);
|
||||
buffer.encode_bool(6, this->disabled_by_default);
|
||||
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesDateResponse::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("ListEntitiesDateResponse {\n");
|
||||
out.append(" object_id: ");
|
||||
out.append("'").append(this->object_id).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" key: ");
|
||||
sprintf(buffer, "%" PRIu32, this->key);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" name: ");
|
||||
out.append("'").append(this->name).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" unique_id: ");
|
||||
out.append("'").append(this->unique_id).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" icon: ");
|
||||
out.append("'").append(this->icon).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" disabled_by_default: ");
|
||||
out.append(YESNO(this->disabled_by_default));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" entity_category: ");
|
||||
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool DateStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 2: {
|
||||
this->missing_state = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 3: {
|
||||
this->year = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
case 4: {
|
||||
this->month = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
case 5: {
|
||||
this->day = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool DateStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->key = value.as_fixed32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void DateStateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.encode_bool(2, this->missing_state);
|
||||
buffer.encode_uint32(3, this->year);
|
||||
buffer.encode_uint32(4, this->month);
|
||||
buffer.encode_uint32(5, this->day);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void DateStateResponse::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("DateStateResponse {\n");
|
||||
out.append(" key: ");
|
||||
sprintf(buffer, "%" PRIu32, this->key);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" missing_state: ");
|
||||
out.append(YESNO(this->missing_state));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" year: ");
|
||||
sprintf(buffer, "%" PRIu32, this->year);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" month: ");
|
||||
sprintf(buffer, "%" PRIu32, this->month);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" day: ");
|
||||
sprintf(buffer, "%" PRIu32, this->day);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool DateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 2: {
|
||||
this->year = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
case 3: {
|
||||
this->month = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
case 4: {
|
||||
this->day = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool DateCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->key = value.as_fixed32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void DateCommandRequest::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.encode_uint32(2, this->year);
|
||||
buffer.encode_uint32(3, this->month);
|
||||
buffer.encode_uint32(4, this->day);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void DateCommandRequest::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("DateCommandRequest {\n");
|
||||
out.append(" key: ");
|
||||
sprintf(buffer, "%" PRIu32, this->key);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" year: ");
|
||||
sprintf(buffer, "%" PRIu32, this->year);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" month: ");
|
||||
sprintf(buffer, "%" PRIu32, this->month);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" day: ");
|
||||
sprintf(buffer, "%" PRIu32, this->day);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
|
|
|
@ -1702,6 +1702,7 @@ class VoiceAssistantRequest : public ProtoMessage {
|
|||
std::string conversation_id{};
|
||||
uint32_t flags{0};
|
||||
VoiceAssistantAudioSettings audio_settings{};
|
||||
std::string wake_word_phrase{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
|
@ -1849,6 +1850,56 @@ class TextCommandRequest : public ProtoMessage {
|
|||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
};
|
||||
class ListEntitiesDateResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string object_id{};
|
||||
uint32_t key{0};
|
||||
std::string name{};
|
||||
std::string unique_id{};
|
||||
std::string icon{};
|
||||
bool disabled_by_default{false};
|
||||
enums::EntityCategory entity_category{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class DateStateResponse : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0};
|
||||
bool missing_state{false};
|
||||
uint32_t year{0};
|
||||
uint32_t month{0};
|
||||
uint32_t day{0};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class DateCommandRequest : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0};
|
||||
uint32_t year{0};
|
||||
uint32_t month{0};
|
||||
uint32_t day{0};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
|
|
|
@ -513,6 +513,24 @@ bool APIServerConnectionBase::send_text_state_response(const TextStateResponse &
|
|||
#endif
|
||||
#ifdef USE_TEXT
|
||||
#endif
|
||||
#ifdef USE_DATETIME_DATE
|
||||
bool APIServerConnectionBase::send_list_entities_date_response(const ListEntitiesDateResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_list_entities_date_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<ListEntitiesDateResponse>(msg, 100);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_DATETIME_DATE
|
||||
bool APIServerConnectionBase::send_date_state_response(const DateStateResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_date_state_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<DateStateResponse>(msg, 101);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_DATETIME_DATE
|
||||
#endif
|
||||
bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {
|
||||
switch (msg_type) {
|
||||
case 1: {
|
||||
|
@ -942,6 +960,17 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||
ESP_LOGVV(TAG, "on_text_command_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_text_command_request(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case 102: {
|
||||
#ifdef USE_DATETIME_DATE
|
||||
DateCommandRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_date_command_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_date_command_request(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
@ -1218,6 +1247,19 @@ void APIServerConnection::on_media_player_command_request(const MediaPlayerComma
|
|||
this->media_player_command(msg);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_DATETIME_DATE
|
||||
void APIServerConnection::on_date_command_request(const DateCommandRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->date_command(msg);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request(
|
||||
const SubscribeBluetoothLEAdvertisementsRequest &msg) {
|
||||
|
|
|
@ -257,6 +257,15 @@ class APIServerConnectionBase : public ProtoService {
|
|||
#endif
|
||||
#ifdef USE_TEXT
|
||||
virtual void on_text_command_request(const TextCommandRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_DATETIME_DATE
|
||||
bool send_list_entities_date_response(const ListEntitiesDateResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_DATETIME_DATE
|
||||
bool send_date_state_response(const DateStateResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_DATETIME_DATE
|
||||
virtual void on_date_command_request(const DateCommandRequest &value){};
|
||||
#endif
|
||||
protected:
|
||||
bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;
|
||||
|
@ -312,6 +321,9 @@ class APIServerConnection : public APIServerConnectionBase {
|
|||
#ifdef USE_MEDIA_PLAYER
|
||||
virtual void media_player_command(const MediaPlayerCommandRequest &msg) = 0;
|
||||
#endif
|
||||
#ifdef USE_DATETIME_DATE
|
||||
virtual void date_command(const DateCommandRequest &msg) = 0;
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) = 0;
|
||||
#endif
|
||||
|
@ -398,6 +410,9 @@ class APIServerConnection : public APIServerConnectionBase {
|
|||
#ifdef USE_MEDIA_PLAYER
|
||||
void on_media_player_command_request(const MediaPlayerCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_DATETIME_DATE
|
||||
void on_date_command_request(const DateCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override;
|
||||
#endif
|
||||
|
|
|
@ -255,6 +255,15 @@ void APIServer::on_number_update(number::Number *obj, float state) {
|
|||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_DATETIME_DATE
|
||||
void APIServer::on_date_update(datetime::DateEntity *obj) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto &c : this->clients_)
|
||||
c->send_date_state(obj);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT
|
||||
void APIServer::on_text_update(text::Text *obj, const std::string &state) {
|
||||
if (obj->is_internal())
|
||||
|
|
|
@ -66,6 +66,9 @@ class APIServer : public Component, public Controller {
|
|||
#ifdef USE_NUMBER
|
||||
void on_number_update(number::Number *obj, float state) override;
|
||||
#endif
|
||||
#ifdef USE_DATETIME_DATE
|
||||
void on_date_update(datetime::DateEntity *obj) override;
|
||||
#endif
|
||||
#ifdef USE_TEXT
|
||||
void on_text_update(text::Text *obj, const std::string &state) override;
|
||||
#endif
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
#include "list_entities.h"
|
||||
#include "esphome/core/util.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "api_connection.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/util.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
@ -60,6 +60,10 @@ bool ListEntitiesIterator::on_climate(climate::Climate *climate) { return this->
|
|||
bool ListEntitiesIterator::on_number(number::Number *number) { return this->client_->send_number_info(number); }
|
||||
#endif
|
||||
|
||||
#ifdef USE_DATETIME_DATE
|
||||
bool ListEntitiesIterator::on_date(datetime::DateEntity *date) { return this->client_->send_date_info(date); }
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT
|
||||
bool ListEntitiesIterator::on_text(text::Text *text) { return this->client_->send_text_info(text); }
|
||||
#endif
|
||||
|
|
|
@ -46,6 +46,9 @@ class ListEntitiesIterator : public ComponentIterator {
|
|||
#ifdef USE_NUMBER
|
||||
bool on_number(number::Number *number) override;
|
||||
#endif
|
||||
#ifdef USE_DATETIME_DATE
|
||||
bool on_date(datetime::DateEntity *date) override;
|
||||
#endif
|
||||
#ifdef USE_TEXT
|
||||
bool on_text(text::Text *text) override;
|
||||
#endif
|
||||
|
|
|
@ -42,6 +42,9 @@ bool InitialStateIterator::on_number(number::Number *number) {
|
|||
return this->client_->send_number_state(number, number->state);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_DATETIME_DATE
|
||||
bool InitialStateIterator::on_date(datetime::DateEntity *date) { return this->client_->send_date_state(date); }
|
||||
#endif
|
||||
#ifdef USE_TEXT
|
||||
bool InitialStateIterator::on_text(text::Text *text) { return this->client_->send_text_state(text, text->state); }
|
||||
#endif
|
||||
|
|
|
@ -43,6 +43,9 @@ class InitialStateIterator : public ComponentIterator {
|
|||
#ifdef USE_NUMBER
|
||||
bool on_number(number::Number *number) override;
|
||||
#endif
|
||||
#ifdef USE_DATETIME_DATE
|
||||
bool on_date(datetime::DateEntity *date) override;
|
||||
#endif
|
||||
#ifdef USE_TEXT
|
||||
bool on_text(text::Text *text) override;
|
||||
#endif
|
||||
|
|
|
@ -117,7 +117,7 @@ void ATM90E26Component::setup() {
|
|||
this->write16_(ATM90E26_REGISTER_ADJSTART,
|
||||
0x8765); // Checks correctness of 31-3A registers and starts normal measurement if ok
|
||||
|
||||
uint16_t sys_status = this->read16_(ATM90E26_REGISTER_SYSSTATUS);
|
||||
const uint16_t sys_status = this->read16_(ATM90E26_REGISTER_SYSSTATUS);
|
||||
if (sys_status & 0xC000) { // Checksum 1 Error
|
||||
|
||||
ESP_LOGW(TAG, "Could not initialize ATM90E26 IC: CS1 was incorrect, expected: 0x%04X",
|
||||
|
@ -177,27 +177,27 @@ void ATM90E26Component::write16_(uint8_t a_register, uint16_t val) {
|
|||
}
|
||||
|
||||
float ATM90E26Component::get_line_current_() {
|
||||
uint16_t current = this->read16_(ATM90E26_REGISTER_IRMS);
|
||||
const uint16_t current = this->read16_(ATM90E26_REGISTER_IRMS);
|
||||
return current / 1000.0f;
|
||||
}
|
||||
|
||||
float ATM90E26Component::get_line_voltage_() {
|
||||
uint16_t voltage = this->read16_(ATM90E26_REGISTER_URMS);
|
||||
const uint16_t voltage = this->read16_(ATM90E26_REGISTER_URMS);
|
||||
return voltage / 100.0f;
|
||||
}
|
||||
|
||||
float ATM90E26Component::get_active_power_() {
|
||||
int16_t val = this->read16_(ATM90E26_REGISTER_PMEAN); // two's complement
|
||||
const int16_t val = this->read16_(ATM90E26_REGISTER_PMEAN); // two's complement
|
||||
return (float) val;
|
||||
}
|
||||
|
||||
float ATM90E26Component::get_reactive_power_() {
|
||||
int16_t val = this->read16_(ATM90E26_REGISTER_QMEAN); // two's complement
|
||||
const int16_t val = this->read16_(ATM90E26_REGISTER_QMEAN); // two's complement
|
||||
return (float) val;
|
||||
}
|
||||
|
||||
float ATM90E26Component::get_power_factor_() {
|
||||
uint16_t val = this->read16_(ATM90E26_REGISTER_POWERF); // signed
|
||||
const uint16_t val = this->read16_(ATM90E26_REGISTER_POWERF); // signed
|
||||
if (val & 0x8000) {
|
||||
return -(val & 0x7FF) / 1000.0f;
|
||||
} else {
|
||||
|
@ -206,7 +206,7 @@ float ATM90E26Component::get_power_factor_() {
|
|||
}
|
||||
|
||||
float ATM90E26Component::get_forward_active_energy_() {
|
||||
uint16_t val = this->read16_(ATM90E26_REGISTER_APENERGY);
|
||||
const uint16_t val = this->read16_(ATM90E26_REGISTER_APENERGY);
|
||||
if ((UINT32_MAX - this->cumulative_forward_active_energy_) > val) {
|
||||
this->cumulative_forward_active_energy_ += val;
|
||||
} else {
|
||||
|
@ -217,7 +217,7 @@ float ATM90E26Component::get_forward_active_energy_() {
|
|||
}
|
||||
|
||||
float ATM90E26Component::get_reverse_active_energy_() {
|
||||
uint16_t val = this->read16_(ATM90E26_REGISTER_ANENERGY);
|
||||
const uint16_t val = this->read16_(ATM90E26_REGISTER_ANENERGY);
|
||||
if (UINT32_MAX - this->cumulative_reverse_active_energy_ > val) {
|
||||
this->cumulative_reverse_active_energy_ += val;
|
||||
} else {
|
||||
|
@ -227,7 +227,7 @@ float ATM90E26Component::get_reverse_active_energy_() {
|
|||
}
|
||||
|
||||
float ATM90E26Component::get_frequency_() {
|
||||
uint16_t freq = this->read16_(ATM90E26_REGISTER_FREQ);
|
||||
const uint16_t freq = this->read16_(ATM90E26_REGISTER_FREQ);
|
||||
return freq / 100.0f;
|
||||
}
|
||||
|
||||
|
|
|
@ -7,75 +7,112 @@ namespace esphome {
|
|||
namespace atm90e32 {
|
||||
|
||||
static const char *const TAG = "atm90e32";
|
||||
|
||||
void ATM90E32Component::update() {
|
||||
if (this->read16_(ATM90E32_REGISTER_METEREN) != 1) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
void ATM90E32Component::loop() {
|
||||
if (this->get_publish_interval_flag_()) {
|
||||
this->set_publish_interval_flag_(false);
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
if (this->phase_[phase].voltage_sensor_ != nullptr) {
|
||||
this->phase_[phase].voltage_ = this->get_phase_voltage_(phase);
|
||||
}
|
||||
|
||||
if (this->phase_[0].voltage_sensor_ != nullptr) {
|
||||
this->phase_[0].voltage_sensor_->publish_state(this->get_line_voltage_a_());
|
||||
}
|
||||
if (this->phase_[1].voltage_sensor_ != nullptr) {
|
||||
this->phase_[1].voltage_sensor_->publish_state(this->get_line_voltage_b_());
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
if (this->phase_[phase].current_sensor_ != nullptr) {
|
||||
this->phase_[phase].current_ = this->get_phase_current_(phase);
|
||||
}
|
||||
if (this->phase_[2].voltage_sensor_ != nullptr) {
|
||||
this->phase_[2].voltage_sensor_->publish_state(this->get_line_voltage_c_());
|
||||
}
|
||||
if (this->phase_[0].current_sensor_ != nullptr) {
|
||||
this->phase_[0].current_sensor_->publish_state(this->get_line_current_a_());
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
if (this->phase_[phase].power_sensor_ != nullptr) {
|
||||
this->phase_[phase].active_power_ = this->get_phase_active_power_(phase);
|
||||
}
|
||||
if (this->phase_[1].current_sensor_ != nullptr) {
|
||||
this->phase_[1].current_sensor_->publish_state(this->get_line_current_b_());
|
||||
}
|
||||
if (this->phase_[2].current_sensor_ != nullptr) {
|
||||
this->phase_[2].current_sensor_->publish_state(this->get_line_current_c_());
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
if (this->phase_[phase].power_factor_sensor_ != nullptr) {
|
||||
this->phase_[phase].power_factor_ = this->get_phase_power_factor_(phase);
|
||||
}
|
||||
if (this->phase_[0].power_sensor_ != nullptr) {
|
||||
this->phase_[0].power_sensor_->publish_state(this->get_active_power_a_());
|
||||
}
|
||||
if (this->phase_[1].power_sensor_ != nullptr) {
|
||||
this->phase_[1].power_sensor_->publish_state(this->get_active_power_b_());
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
if (this->phase_[phase].reactive_power_sensor_ != nullptr) {
|
||||
this->phase_[phase].reactive_power_ = this->get_phase_reactive_power_(phase);
|
||||
}
|
||||
if (this->phase_[2].power_sensor_ != nullptr) {
|
||||
this->phase_[2].power_sensor_->publish_state(this->get_active_power_c_());
|
||||
}
|
||||
if (this->phase_[0].reactive_power_sensor_ != nullptr) {
|
||||
this->phase_[0].reactive_power_sensor_->publish_state(this->get_reactive_power_a_());
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
if (this->phase_[phase].forward_active_energy_sensor_ != nullptr) {
|
||||
this->phase_[phase].forward_active_energy_ = this->get_phase_forward_active_energy_(phase);
|
||||
}
|
||||
if (this->phase_[1].reactive_power_sensor_ != nullptr) {
|
||||
this->phase_[1].reactive_power_sensor_->publish_state(this->get_reactive_power_b_());
|
||||
}
|
||||
if (this->phase_[2].reactive_power_sensor_ != nullptr) {
|
||||
this->phase_[2].reactive_power_sensor_->publish_state(this->get_reactive_power_c_());
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
if (this->phase_[phase].reverse_active_energy_sensor_ != nullptr) {
|
||||
this->phase_[phase].reverse_active_energy_ = this->get_phase_reverse_active_energy_(phase);
|
||||
}
|
||||
if (this->phase_[0].power_factor_sensor_ != nullptr) {
|
||||
this->phase_[0].power_factor_sensor_->publish_state(this->get_power_factor_a_());
|
||||
}
|
||||
if (this->phase_[1].power_factor_sensor_ != nullptr) {
|
||||
this->phase_[1].power_factor_sensor_->publish_state(this->get_power_factor_b_());
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
if (this->phase_[phase].phase_angle_sensor_ != nullptr) {
|
||||
this->phase_[phase].phase_angle_ = this->get_phase_angle_(phase);
|
||||
}
|
||||
if (this->phase_[2].power_factor_sensor_ != nullptr) {
|
||||
this->phase_[2].power_factor_sensor_->publish_state(this->get_power_factor_c_());
|
||||
}
|
||||
if (this->phase_[0].forward_active_energy_sensor_ != nullptr) {
|
||||
this->phase_[0].forward_active_energy_sensor_->publish_state(this->get_forward_active_energy_a_());
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
if (this->phase_[phase].harmonic_active_power_sensor_ != nullptr) {
|
||||
this->phase_[phase].harmonic_active_power_ = this->get_phase_harmonic_active_power_(phase);
|
||||
}
|
||||
if (this->phase_[1].forward_active_energy_sensor_ != nullptr) {
|
||||
this->phase_[1].forward_active_energy_sensor_->publish_state(this->get_forward_active_energy_b_());
|
||||
}
|
||||
if (this->phase_[2].forward_active_energy_sensor_ != nullptr) {
|
||||
this->phase_[2].forward_active_energy_sensor_->publish_state(this->get_forward_active_energy_c_());
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
if (this->phase_[phase].peak_current_sensor_ != nullptr) {
|
||||
this->phase_[phase].peak_current_ = this->get_phase_peak_current_(phase);
|
||||
}
|
||||
if (this->phase_[0].reverse_active_energy_sensor_ != nullptr) {
|
||||
this->phase_[0].reverse_active_energy_sensor_->publish_state(this->get_reverse_active_energy_a_());
|
||||
}
|
||||
if (this->phase_[1].reverse_active_energy_sensor_ != nullptr) {
|
||||
this->phase_[1].reverse_active_energy_sensor_->publish_state(this->get_reverse_active_energy_b_());
|
||||
// After the local store in collected we can publish them trusting they are withing +-1 haardware sampling
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
if (this->phase_[phase].voltage_sensor_ != nullptr) {
|
||||
this->phase_[phase].voltage_sensor_->publish_state(this->get_local_phase_voltage_(phase));
|
||||
}
|
||||
}
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
if (this->phase_[phase].current_sensor_ != nullptr) {
|
||||
this->phase_[phase].current_sensor_->publish_state(this->get_local_phase_current_(phase));
|
||||
}
|
||||
}
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
if (this->phase_[phase].power_sensor_ != nullptr) {
|
||||
this->phase_[phase].power_sensor_->publish_state(this->get_local_phase_active_power_(phase));
|
||||
}
|
||||
}
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
if (this->phase_[phase].power_factor_sensor_ != nullptr) {
|
||||
this->phase_[phase].power_factor_sensor_->publish_state(this->get_local_phase_power_factor_(phase));
|
||||
}
|
||||
}
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
if (this->phase_[phase].reactive_power_sensor_ != nullptr) {
|
||||
this->phase_[phase].reactive_power_sensor_->publish_state(this->get_local_phase_reactive_power_(phase));
|
||||
}
|
||||
}
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
if (this->phase_[phase].forward_active_energy_sensor_ != nullptr) {
|
||||
this->phase_[phase].forward_active_energy_sensor_->publish_state(
|
||||
this->get_local_phase_forward_active_energy_(phase));
|
||||
}
|
||||
}
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
if (this->phase_[phase].reverse_active_energy_sensor_ != nullptr) {
|
||||
this->phase_[phase].reverse_active_energy_sensor_->publish_state(
|
||||
this->get_local_phase_reverse_active_energy_(phase));
|
||||
}
|
||||
}
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
if (this->phase_[phase].phase_angle_sensor_ != nullptr) {
|
||||
this->phase_[phase].phase_angle_sensor_->publish_state(this->get_local_phase_angle_(phase));
|
||||
}
|
||||
}
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
if (this->phase_[phase].harmonic_active_power_sensor_ != nullptr) {
|
||||
this->phase_[phase].harmonic_active_power_sensor_->publish_state(
|
||||
this->get_local_phase_harmonic_active_power_(phase));
|
||||
}
|
||||
}
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
if (this->phase_[phase].peak_current_sensor_ != nullptr) {
|
||||
this->phase_[phase].peak_current_sensor_->publish_state(this->get_local_phase_peak_current_(phase));
|
||||
}
|
||||
if (this->phase_[2].reverse_active_energy_sensor_ != nullptr) {
|
||||
this->phase_[2].reverse_active_energy_sensor_->publish_state(this->get_reverse_active_energy_c_());
|
||||
}
|
||||
if (this->freq_sensor_ != nullptr) {
|
||||
this->freq_sensor_->publish_state(this->get_frequency_());
|
||||
|
@ -83,6 +120,15 @@ void ATM90E32Component::update() {
|
|||
if (this->chip_temperature_sensor_ != nullptr) {
|
||||
this->chip_temperature_sensor_->publish_state(this->get_chip_temperature_());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ATM90E32Component::update() {
|
||||
if (this->read16_(ATM90E32_REGISTER_METEREN) != 1) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
this->set_publish_interval_flag_(true);
|
||||
this->status_clear_warning();
|
||||
}
|
||||
|
||||
|
@ -101,13 +147,16 @@ void ATM90E32Component::setup() {
|
|||
}
|
||||
|
||||
this->write16_(ATM90E32_REGISTER_SOFTRESET, 0x789A); // Perform soft reset
|
||||
delay(6); // Wait for the minimum 5ms + 1ms
|
||||
this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x55AA); // enable register config access
|
||||
this->write16_(ATM90E32_REGISTER_METEREN, 0x0001); // Enable Metering
|
||||
if (this->read16_(ATM90E32_REGISTER_LASTSPIDATA) != 0x0001) {
|
||||
if (this->read16_(ATM90E32_REGISTER_LASTSPIDATA) != 0x55AA) {
|
||||
ESP_LOGW(TAG, "Could not initialize ATM90E32 IC, check SPI settings");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
this->write16_(ATM90E32_REGISTER_METEREN, 0x0001); // Enable Metering
|
||||
this->write16_(ATM90E32_REGISTER_SAGPEAKDETCFG, 0xFF3F); // Peak Detector time ms (15:8), Sag Period ms (7:0)
|
||||
this->write16_(ATM90E32_REGISTER_PLCONSTH, 0x0861); // PL Constant MSB (default) = 140625000
|
||||
this->write16_(ATM90E32_REGISTER_PLCONSTL, 0xC468); // PL Constant LSB (default)
|
||||
this->write16_(ATM90E32_REGISTER_ZXCONFIG, 0xD654); // ZX2, ZX1, ZX0 pin config
|
||||
|
@ -115,14 +164,33 @@ void ATM90E32Component::setup() {
|
|||
this->write16_(ATM90E32_REGISTER_MMODE1, pga_gain_); // PGA Gain Configuration for Current Channels
|
||||
this->write16_(ATM90E32_REGISTER_PSTARTTH, 0x1D4C); // All Active Startup Power Threshold - 0.02A/0.00032 = 7500
|
||||
this->write16_(ATM90E32_REGISTER_QSTARTTH, 0x1D4C); // All Reactive Startup Power Threshold - 50%
|
||||
this->write16_(ATM90E32_REGISTER_SSTARTTH, 0x1D4C); // All Reactive Startup Power Threshold - 50%
|
||||
this->write16_(ATM90E32_REGISTER_PPHASETH, 0x02EE); // Each Phase Active Phase Threshold - 0.002A/0.00032 = 750
|
||||
this->write16_(ATM90E32_REGISTER_QPHASETH, 0x02EE); // Each phase Reactive Phase Threshold - 10%
|
||||
this->write16_(ATM90E32_REGISTER_UGAINA, this->phase_[0].volt_gain_); // A Voltage rms gain
|
||||
this->write16_(ATM90E32_REGISTER_IGAINA, this->phase_[0].ct_gain_); // A line current gain
|
||||
this->write16_(ATM90E32_REGISTER_UGAINB, this->phase_[1].volt_gain_); // B Voltage rms gain
|
||||
this->write16_(ATM90E32_REGISTER_IGAINB, this->phase_[1].ct_gain_); // B line current gain
|
||||
this->write16_(ATM90E32_REGISTER_UGAINC, this->phase_[2].volt_gain_); // C Voltage rms gain
|
||||
this->write16_(ATM90E32_REGISTER_IGAINC, this->phase_[2].ct_gain_); // C line current gain
|
||||
// Setup voltage and current calibration offsets for PHASE A
|
||||
this->phase_[PHASEA].voltage_offset_ = calibrate_voltage_offset_phase(PHASEA);
|
||||
this->write16_(ATM90E32_REGISTER_UOFFSETA, this->phase_[PHASEA].voltage_offset_); // A Voltage offset
|
||||
this->phase_[PHASEA].current_offset_ = calibrate_current_offset_phase(PHASEA);
|
||||
this->write16_(ATM90E32_REGISTER_IOFFSETA, this->phase_[PHASEA].current_offset_); // A Current offset
|
||||
// Setup voltage and current gain for PHASE A
|
||||
this->write16_(ATM90E32_REGISTER_UGAINA, this->phase_[PHASEA].voltage_gain_); // A Voltage rms gain
|
||||
this->write16_(ATM90E32_REGISTER_IGAINA, this->phase_[PHASEA].ct_gain_); // A line current gain
|
||||
// Setup voltage and current calibration offsets for PHASE B
|
||||
this->phase_[PHASEB].voltage_offset_ = calibrate_voltage_offset_phase(PHASEB);
|
||||
this->write16_(ATM90E32_REGISTER_UOFFSETB, this->phase_[PHASEB].voltage_offset_); // B Voltage offset
|
||||
this->phase_[PHASEB].current_offset_ = calibrate_current_offset_phase(PHASEB);
|
||||
this->write16_(ATM90E32_REGISTER_IOFFSETB, this->phase_[PHASEB].current_offset_); // B Current offset
|
||||
// Setup voltage and current gain for PHASE B
|
||||
this->write16_(ATM90E32_REGISTER_UGAINB, this->phase_[PHASEB].voltage_gain_); // B Voltage rms gain
|
||||
this->write16_(ATM90E32_REGISTER_IGAINB, this->phase_[PHASEB].ct_gain_); // B line current gain
|
||||
// Setup voltage and current calibration offsets for PHASE C
|
||||
this->phase_[PHASEC].voltage_offset_ = calibrate_voltage_offset_phase(PHASEC);
|
||||
this->write16_(ATM90E32_REGISTER_UOFFSETC, this->phase_[PHASEC].voltage_offset_); // C Voltage offset
|
||||
this->phase_[PHASEC].current_offset_ = calibrate_current_offset_phase(PHASEC);
|
||||
this->write16_(ATM90E32_REGISTER_IOFFSETC, this->phase_[PHASEC].current_offset_); // C Current offset
|
||||
// Setup voltage and current gain for PHASE C
|
||||
this->write16_(ATM90E32_REGISTER_UGAINC, this->phase_[PHASEC].voltage_gain_); // C Voltage rms gain
|
||||
this->write16_(ATM90E32_REGISTER_IGAINC, this->phase_[PHASEC].ct_gain_); // C line current gain
|
||||
this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x0000); // end configuration
|
||||
}
|
||||
|
||||
|
@ -133,43 +201,54 @@ void ATM90E32Component::dump_config() {
|
|||
ESP_LOGE(TAG, "Communication with ATM90E32 failed!");
|
||||
}
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
LOG_SENSOR(" ", "Voltage A", this->phase_[0].voltage_sensor_);
|
||||
LOG_SENSOR(" ", "Current A", this->phase_[0].current_sensor_);
|
||||
LOG_SENSOR(" ", "Power A", this->phase_[0].power_sensor_);
|
||||
LOG_SENSOR(" ", "Reactive Power A", this->phase_[0].reactive_power_sensor_);
|
||||
LOG_SENSOR(" ", "PF A", this->phase_[0].power_factor_sensor_);
|
||||
LOG_SENSOR(" ", "Active Forward Energy A", this->phase_[0].forward_active_energy_sensor_);
|
||||
LOG_SENSOR(" ", "Active Reverse Energy A", this->phase_[0].reverse_active_energy_sensor_);
|
||||
LOG_SENSOR(" ", "Voltage B", this->phase_[1].voltage_sensor_);
|
||||
LOG_SENSOR(" ", "Current B", this->phase_[1].current_sensor_);
|
||||
LOG_SENSOR(" ", "Power B", this->phase_[1].power_sensor_);
|
||||
LOG_SENSOR(" ", "Reactive Power B", this->phase_[1].reactive_power_sensor_);
|
||||
LOG_SENSOR(" ", "PF B", this->phase_[1].power_factor_sensor_);
|
||||
LOG_SENSOR(" ", "Active Forward Energy B", this->phase_[1].forward_active_energy_sensor_);
|
||||
LOG_SENSOR(" ", "Active Reverse Energy B", this->phase_[1].reverse_active_energy_sensor_);
|
||||
LOG_SENSOR(" ", "Voltage C", this->phase_[2].voltage_sensor_);
|
||||
LOG_SENSOR(" ", "Current C", this->phase_[2].current_sensor_);
|
||||
LOG_SENSOR(" ", "Power C", this->phase_[2].power_sensor_);
|
||||
LOG_SENSOR(" ", "Reactive Power C", this->phase_[2].reactive_power_sensor_);
|
||||
LOG_SENSOR(" ", "PF C", this->phase_[2].power_factor_sensor_);
|
||||
LOG_SENSOR(" ", "Active Forward Energy C", this->phase_[2].forward_active_energy_sensor_);
|
||||
LOG_SENSOR(" ", "Active Reverse Energy C", this->phase_[2].reverse_active_energy_sensor_);
|
||||
LOG_SENSOR(" ", "Voltage A", this->phase_[PHASEA].voltage_sensor_);
|
||||
LOG_SENSOR(" ", "Current A", this->phase_[PHASEA].current_sensor_);
|
||||
LOG_SENSOR(" ", "Power A", this->phase_[PHASEA].power_sensor_);
|
||||
LOG_SENSOR(" ", "Reactive Power A", this->phase_[PHASEA].reactive_power_sensor_);
|
||||
LOG_SENSOR(" ", "PF A", this->phase_[PHASEA].power_factor_sensor_);
|
||||
LOG_SENSOR(" ", "Active Forward Energy A", this->phase_[PHASEA].forward_active_energy_sensor_);
|
||||
LOG_SENSOR(" ", "Active Reverse Energy A", this->phase_[PHASEA].reverse_active_energy_sensor_);
|
||||
LOG_SENSOR(" ", "Harmonic Power A", this->phase_[PHASEA].harmonic_active_power_sensor_);
|
||||
LOG_SENSOR(" ", "Phase Angle A", this->phase_[PHASEA].phase_angle_sensor_);
|
||||
LOG_SENSOR(" ", "Peak Current A", this->phase_[PHASEA].peak_current_sensor_);
|
||||
LOG_SENSOR(" ", "Voltage B", this->phase_[PHASEB].voltage_sensor_);
|
||||
LOG_SENSOR(" ", "Current B", this->phase_[PHASEB].current_sensor_);
|
||||
LOG_SENSOR(" ", "Power B", this->phase_[PHASEB].power_sensor_);
|
||||
LOG_SENSOR(" ", "Reactive Power B", this->phase_[PHASEB].reactive_power_sensor_);
|
||||
LOG_SENSOR(" ", "PF B", this->phase_[PHASEB].power_factor_sensor_);
|
||||
LOG_SENSOR(" ", "Active Forward Energy B", this->phase_[PHASEB].forward_active_energy_sensor_);
|
||||
LOG_SENSOR(" ", "Active Reverse Energy B", this->phase_[PHASEB].reverse_active_energy_sensor_);
|
||||
LOG_SENSOR(" ", "Harmonic Power A", this->phase_[PHASEB].harmonic_active_power_sensor_);
|
||||
LOG_SENSOR(" ", "Phase Angle A", this->phase_[PHASEB].phase_angle_sensor_);
|
||||
LOG_SENSOR(" ", "Peak Current A", this->phase_[PHASEB].peak_current_sensor_);
|
||||
LOG_SENSOR(" ", "Voltage C", this->phase_[PHASEC].voltage_sensor_);
|
||||
LOG_SENSOR(" ", "Current C", this->phase_[PHASEC].current_sensor_);
|
||||
LOG_SENSOR(" ", "Power C", this->phase_[PHASEC].power_sensor_);
|
||||
LOG_SENSOR(" ", "Reactive Power C", this->phase_[PHASEC].reactive_power_sensor_);
|
||||
LOG_SENSOR(" ", "PF C", this->phase_[PHASEC].power_factor_sensor_);
|
||||
LOG_SENSOR(" ", "Active Forward Energy C", this->phase_[PHASEC].forward_active_energy_sensor_);
|
||||
LOG_SENSOR(" ", "Active Reverse Energy C", this->phase_[PHASEC].reverse_active_energy_sensor_);
|
||||
LOG_SENSOR(" ", "Harmonic Power A", this->phase_[PHASEC].harmonic_active_power_sensor_);
|
||||
LOG_SENSOR(" ", "Phase Angle A", this->phase_[PHASEC].phase_angle_sensor_);
|
||||
LOG_SENSOR(" ", "Peak Current A", this->phase_[PHASEC].peak_current_sensor_);
|
||||
LOG_SENSOR(" ", "Frequency", this->freq_sensor_);
|
||||
LOG_SENSOR(" ", "Chip Temp", this->chip_temperature_sensor_);
|
||||
}
|
||||
float ATM90E32Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
float ATM90E32Component::get_setup_priority() const { return setup_priority::IO; }
|
||||
|
||||
// R/C registers can conly be cleared after the LastSPIData register is updated (register 78H)
|
||||
// Peakdetect period: 05H. Bit 15:8 are PeakDet_period in ms. 7:0 are Sag_period
|
||||
// Default is 143FH (20ms, 63ms)
|
||||
uint16_t ATM90E32Component::read16_(uint16_t a_register) {
|
||||
uint8_t addrh = (1 << 7) | ((a_register >> 8) & 0x03);
|
||||
uint8_t addrl = (a_register & 0xFF);
|
||||
uint8_t data[2];
|
||||
uint16_t output;
|
||||
|
||||
this->enable();
|
||||
delayMicroseconds(10);
|
||||
delay_microseconds_safe(10);
|
||||
this->write_byte(addrh);
|
||||
this->write_byte(addrl);
|
||||
delayMicroseconds(4);
|
||||
this->read_array(data, 2);
|
||||
this->disable();
|
||||
|
||||
|
@ -179,9 +258,9 @@ uint16_t ATM90E32Component::read16_(uint16_t a_register) {
|
|||
}
|
||||
|
||||
int ATM90E32Component::read32_(uint16_t addr_h, uint16_t addr_l) {
|
||||
uint16_t val_h = this->read16_(addr_h);
|
||||
uint16_t val_l = this->read16_(addr_l);
|
||||
int32_t val = (val_h << 16) | val_l;
|
||||
const uint16_t val_h = this->read16_(addr_h);
|
||||
const uint16_t val_l = this->read16_(addr_l);
|
||||
const int32_t val = (val_h << 16) | val_l;
|
||||
|
||||
ESP_LOGVV(TAG,
|
||||
"read32_ addr_h 0x%04" PRIX16 " val_h 0x%04" PRIX16 " addr_l 0x%04" PRIX16 " val_l 0x%04" PRIX16
|
||||
|
@ -192,141 +271,174 @@ int ATM90E32Component::read32_(uint16_t addr_h, uint16_t addr_l) {
|
|||
}
|
||||
|
||||
void ATM90E32Component::write16_(uint16_t a_register, uint16_t val) {
|
||||
uint8_t addrh = (a_register >> 8) & 0x03;
|
||||
uint8_t addrl = (a_register & 0xFF);
|
||||
|
||||
ESP_LOGVV(TAG, "write16_ 0x%04" PRIX16 " val 0x%04" PRIX16, a_register, val);
|
||||
this->enable();
|
||||
delayMicroseconds(10);
|
||||
this->write_byte(addrh);
|
||||
this->write_byte(addrl);
|
||||
delayMicroseconds(4);
|
||||
this->write_byte((val >> 8) & 0xff);
|
||||
this->write_byte(val & 0xFF);
|
||||
this->write_byte16(a_register);
|
||||
this->write_byte16(val);
|
||||
this->disable();
|
||||
if (this->read16_(ATM90E32_REGISTER_LASTSPIDATA) != val)
|
||||
ESP_LOGW(TAG, "SPI write error 0x%04X val 0x%04X", a_register, val);
|
||||
}
|
||||
|
||||
float ATM90E32Component::get_line_voltage_a_() {
|
||||
uint16_t voltage = this->read16_(ATM90E32_REGISTER_URMSA);
|
||||
float ATM90E32Component::get_local_phase_voltage_(uint8_t phase) { return this->phase_[phase].voltage_; }
|
||||
|
||||
float ATM90E32Component::get_local_phase_current_(uint8_t phase) { return this->phase_[phase].current_; }
|
||||
|
||||
float ATM90E32Component::get_local_phase_active_power_(uint8_t phase) { return this->phase_[phase].active_power_; }
|
||||
|
||||
float ATM90E32Component::get_local_phase_reactive_power_(uint8_t phase) { return this->phase_[phase].reactive_power_; }
|
||||
|
||||
float ATM90E32Component::get_local_phase_power_factor_(uint8_t phase) { return this->phase_[phase].power_factor_; }
|
||||
|
||||
float ATM90E32Component::get_local_phase_forward_active_energy_(uint8_t phase) {
|
||||
return this->phase_[phase].forward_active_energy_;
|
||||
}
|
||||
|
||||
float ATM90E32Component::get_local_phase_reverse_active_energy_(uint8_t phase) {
|
||||
return this->phase_[phase].reverse_active_energy_;
|
||||
}
|
||||
|
||||
float ATM90E32Component::get_local_phase_angle_(uint8_t phase) { return this->phase_[phase].phase_angle_; }
|
||||
|
||||
float ATM90E32Component::get_local_phase_harmonic_active_power_(uint8_t phase) {
|
||||
return this->phase_[phase].harmonic_active_power_;
|
||||
}
|
||||
|
||||
float ATM90E32Component::get_local_phase_peak_current_(uint8_t phase) { return this->phase_[phase].peak_current_; }
|
||||
|
||||
float ATM90E32Component::get_phase_voltage_(uint8_t phase) {
|
||||
const uint16_t voltage = this->read16_(ATM90E32_REGISTER_URMS + phase);
|
||||
if (this->read16_(ATM90E32_REGISTER_LASTSPIDATA) != voltage)
|
||||
ESP_LOGW(TAG, "SPI URMS voltage register read error.");
|
||||
return (float) voltage / 100;
|
||||
}
|
||||
float ATM90E32Component::get_line_voltage_b_() {
|
||||
uint16_t voltage = this->read16_(ATM90E32_REGISTER_URMSB);
|
||||
return (float) voltage / 100;
|
||||
|
||||
float ATM90E32Component::get_phase_voltage_avg_(uint8_t phase) {
|
||||
const uint8_t reads = 10;
|
||||
uint32_t accumulation = 0;
|
||||
uint16_t voltage = 0;
|
||||
for (uint8_t i = 0; i < reads; i++) {
|
||||
voltage = this->read16_(ATM90E32_REGISTER_URMS + phase);
|
||||
if (this->read16_(ATM90E32_REGISTER_LASTSPIDATA) != voltage)
|
||||
ESP_LOGW(TAG, "SPI URMS voltage register read error.");
|
||||
accumulation += voltage;
|
||||
}
|
||||
voltage = accumulation / reads;
|
||||
this->phase_[phase].voltage_ = (float) voltage / 100;
|
||||
return this->phase_[phase].voltage_;
|
||||
}
|
||||
float ATM90E32Component::get_line_voltage_c_() {
|
||||
uint16_t voltage = this->read16_(ATM90E32_REGISTER_URMSC);
|
||||
return (float) voltage / 100;
|
||||
|
||||
float ATM90E32Component::get_phase_current_avg_(uint8_t phase) {
|
||||
const uint8_t reads = 10;
|
||||
uint32_t accumulation = 0;
|
||||
uint16_t current = 0;
|
||||
for (uint8_t i = 0; i < reads; i++) {
|
||||
current = this->read16_(ATM90E32_REGISTER_IRMS + phase);
|
||||
if (this->read16_(ATM90E32_REGISTER_LASTSPIDATA) != current)
|
||||
ESP_LOGW(TAG, "SPI IRMS current register read error.");
|
||||
accumulation += current;
|
||||
}
|
||||
current = accumulation / reads;
|
||||
this->phase_[phase].current_ = (float) current / 1000;
|
||||
return this->phase_[phase].current_;
|
||||
}
|
||||
float ATM90E32Component::get_line_current_a_() {
|
||||
uint16_t current = this->read16_(ATM90E32_REGISTER_IRMSA);
|
||||
|
||||
float ATM90E32Component::get_phase_current_(uint8_t phase) {
|
||||
const uint16_t current = this->read16_(ATM90E32_REGISTER_IRMS + phase);
|
||||
if (this->read16_(ATM90E32_REGISTER_LASTSPIDATA) != current)
|
||||
ESP_LOGW(TAG, "SPI IRMS current register read error.");
|
||||
return (float) current / 1000;
|
||||
}
|
||||
float ATM90E32Component::get_line_current_b_() {
|
||||
uint16_t current = this->read16_(ATM90E32_REGISTER_IRMSB);
|
||||
return (float) current / 1000;
|
||||
}
|
||||
float ATM90E32Component::get_line_current_c_() {
|
||||
uint16_t current = this->read16_(ATM90E32_REGISTER_IRMSC);
|
||||
return (float) current / 1000;
|
||||
}
|
||||
float ATM90E32Component::get_active_power_a_() {
|
||||
int val = this->read32_(ATM90E32_REGISTER_PMEANA, ATM90E32_REGISTER_PMEANALSB);
|
||||
|
||||
float ATM90E32Component::get_phase_active_power_(uint8_t phase) {
|
||||
const int val = this->read32_(ATM90E32_REGISTER_PMEAN + phase, ATM90E32_REGISTER_PMEANLSB + phase);
|
||||
return val * 0.00032f;
|
||||
}
|
||||
float ATM90E32Component::get_active_power_b_() {
|
||||
int val = this->read32_(ATM90E32_REGISTER_PMEANB, ATM90E32_REGISTER_PMEANBLSB);
|
||||
|
||||
float ATM90E32Component::get_phase_reactive_power_(uint8_t phase) {
|
||||
const int val = this->read32_(ATM90E32_REGISTER_QMEAN + phase, ATM90E32_REGISTER_QMEANLSB + phase);
|
||||
return val * 0.00032f;
|
||||
}
|
||||
float ATM90E32Component::get_active_power_c_() {
|
||||
int val = this->read32_(ATM90E32_REGISTER_PMEANC, ATM90E32_REGISTER_PMEANCLSB);
|
||||
|
||||
float ATM90E32Component::get_phase_power_factor_(uint8_t phase) {
|
||||
const int16_t powerfactor = this->read16_(ATM90E32_REGISTER_PFMEAN + phase);
|
||||
if (this->read16_(ATM90E32_REGISTER_LASTSPIDATA) != powerfactor)
|
||||
ESP_LOGW(TAG, "SPI power factor read error.");
|
||||
return (float) powerfactor / 1000;
|
||||
}
|
||||
|
||||
float ATM90E32Component::get_phase_forward_active_energy_(uint8_t phase) {
|
||||
const uint16_t val = this->read16_(ATM90E32_REGISTER_APENERGY + phase);
|
||||
if ((UINT32_MAX - this->phase_[phase].cumulative_forward_active_energy_) > val) {
|
||||
this->phase_[phase].cumulative_forward_active_energy_ += val;
|
||||
} else {
|
||||
this->phase_[phase].cumulative_forward_active_energy_ = val;
|
||||
}
|
||||
return ((float) this->phase_[phase].cumulative_forward_active_energy_ * 10 / 3200);
|
||||
}
|
||||
|
||||
float ATM90E32Component::get_phase_reverse_active_energy_(uint8_t phase) {
|
||||
const uint16_t val = this->read16_(ATM90E32_REGISTER_ANENERGY);
|
||||
if (UINT32_MAX - this->phase_[phase].cumulative_reverse_active_energy_ > val) {
|
||||
this->phase_[phase].cumulative_reverse_active_energy_ += val;
|
||||
} else {
|
||||
this->phase_[phase].cumulative_reverse_active_energy_ = val;
|
||||
}
|
||||
return ((float) this->phase_[phase].cumulative_reverse_active_energy_ * 10 / 3200);
|
||||
}
|
||||
|
||||
float ATM90E32Component::get_phase_harmonic_active_power_(uint8_t phase) {
|
||||
int val = this->read32_(ATM90E32_REGISTER_PMEANH + phase, ATM90E32_REGISTER_PMEANHLSB + phase);
|
||||
return val * 0.00032f;
|
||||
}
|
||||
float ATM90E32Component::get_reactive_power_a_() {
|
||||
int val = this->read32_(ATM90E32_REGISTER_QMEANA, ATM90E32_REGISTER_QMEANALSB);
|
||||
return val * 0.00032f;
|
||||
|
||||
float ATM90E32Component::get_phase_angle_(uint8_t phase) {
|
||||
uint16_t val = this->read16_(ATM90E32_REGISTER_PANGLE + phase) / 10.0;
|
||||
return (float) (val > 180) ? val - 360.0 : val;
|
||||
}
|
||||
float ATM90E32Component::get_reactive_power_b_() {
|
||||
int val = this->read32_(ATM90E32_REGISTER_QMEANB, ATM90E32_REGISTER_QMEANBLSB);
|
||||
return val * 0.00032f;
|
||||
}
|
||||
float ATM90E32Component::get_reactive_power_c_() {
|
||||
int val = this->read32_(ATM90E32_REGISTER_QMEANC, ATM90E32_REGISTER_QMEANCLSB);
|
||||
return val * 0.00032f;
|
||||
}
|
||||
float ATM90E32Component::get_power_factor_a_() {
|
||||
int16_t pf = this->read16_(ATM90E32_REGISTER_PFMEANA);
|
||||
return (float) pf / 1000;
|
||||
}
|
||||
float ATM90E32Component::get_power_factor_b_() {
|
||||
int16_t pf = this->read16_(ATM90E32_REGISTER_PFMEANB);
|
||||
return (float) pf / 1000;
|
||||
}
|
||||
float ATM90E32Component::get_power_factor_c_() {
|
||||
int16_t pf = this->read16_(ATM90E32_REGISTER_PFMEANC);
|
||||
return (float) pf / 1000;
|
||||
}
|
||||
float ATM90E32Component::get_forward_active_energy_a_() {
|
||||
uint16_t val = this->read16_(ATM90E32_REGISTER_APENERGYA);
|
||||
if ((UINT32_MAX - this->phase_[0].cumulative_forward_active_energy_) > val) {
|
||||
this->phase_[0].cumulative_forward_active_energy_ += val;
|
||||
} else {
|
||||
this->phase_[0].cumulative_forward_active_energy_ = val;
|
||||
}
|
||||
return ((float) this->phase_[0].cumulative_forward_active_energy_ * 10 / 3200);
|
||||
}
|
||||
float ATM90E32Component::get_forward_active_energy_b_() {
|
||||
uint16_t val = this->read16_(ATM90E32_REGISTER_APENERGYB);
|
||||
if (UINT32_MAX - this->phase_[1].cumulative_forward_active_energy_ > val) {
|
||||
this->phase_[1].cumulative_forward_active_energy_ += val;
|
||||
} else {
|
||||
this->phase_[1].cumulative_forward_active_energy_ = val;
|
||||
}
|
||||
return ((float) this->phase_[1].cumulative_forward_active_energy_ * 10 / 3200);
|
||||
}
|
||||
float ATM90E32Component::get_forward_active_energy_c_() {
|
||||
uint16_t val = this->read16_(ATM90E32_REGISTER_APENERGYC);
|
||||
if (UINT32_MAX - this->phase_[2].cumulative_forward_active_energy_ > val) {
|
||||
this->phase_[2].cumulative_forward_active_energy_ += val;
|
||||
} else {
|
||||
this->phase_[2].cumulative_forward_active_energy_ = val;
|
||||
}
|
||||
return ((float) this->phase_[2].cumulative_forward_active_energy_ * 10 / 3200);
|
||||
}
|
||||
float ATM90E32Component::get_reverse_active_energy_a_() {
|
||||
uint16_t val = this->read16_(ATM90E32_REGISTER_ANENERGYA);
|
||||
if (UINT32_MAX - this->phase_[0].cumulative_reverse_active_energy_ > val) {
|
||||
this->phase_[0].cumulative_reverse_active_energy_ += val;
|
||||
} else {
|
||||
this->phase_[0].cumulative_reverse_active_energy_ = val;
|
||||
}
|
||||
return ((float) this->phase_[0].cumulative_reverse_active_energy_ * 10 / 3200);
|
||||
}
|
||||
float ATM90E32Component::get_reverse_active_energy_b_() {
|
||||
uint16_t val = this->read16_(ATM90E32_REGISTER_ANENERGYB);
|
||||
if (UINT32_MAX - this->phase_[1].cumulative_reverse_active_energy_ > val) {
|
||||
this->phase_[1].cumulative_reverse_active_energy_ += val;
|
||||
} else {
|
||||
this->phase_[1].cumulative_reverse_active_energy_ = val;
|
||||
}
|
||||
return ((float) this->phase_[1].cumulative_reverse_active_energy_ * 10 / 3200);
|
||||
}
|
||||
float ATM90E32Component::get_reverse_active_energy_c_() {
|
||||
uint16_t val = this->read16_(ATM90E32_REGISTER_ANENERGYC);
|
||||
if (UINT32_MAX - this->phase_[2].cumulative_reverse_active_energy_ > val) {
|
||||
this->phase_[2].cumulative_reverse_active_energy_ += val;
|
||||
} else {
|
||||
this->phase_[2].cumulative_reverse_active_energy_ = val;
|
||||
}
|
||||
return ((float) this->phase_[2].cumulative_reverse_active_energy_ * 10 / 3200);
|
||||
|
||||
float ATM90E32Component::get_phase_peak_current_(uint8_t phase) {
|
||||
int16_t val = (float) this->read16_(ATM90E32_REGISTER_IPEAK + phase);
|
||||
if (!this->peak_current_signed_)
|
||||
val = abs(val);
|
||||
// phase register * phase current gain value / 1000 * 2^13
|
||||
return (float) (val * this->phase_[phase].ct_gain_ / 8192000.0);
|
||||
}
|
||||
|
||||
float ATM90E32Component::get_frequency_() {
|
||||
uint16_t freq = this->read16_(ATM90E32_REGISTER_FREQ);
|
||||
const uint16_t freq = this->read16_(ATM90E32_REGISTER_FREQ);
|
||||
return (float) freq / 100;
|
||||
}
|
||||
|
||||
float ATM90E32Component::get_chip_temperature_() {
|
||||
uint16_t ctemp = this->read16_(ATM90E32_REGISTER_TEMP);
|
||||
const uint16_t ctemp = this->read16_(ATM90E32_REGISTER_TEMP);
|
||||
return (float) ctemp;
|
||||
}
|
||||
|
||||
uint16_t ATM90E32Component::calibrate_voltage_offset_phase(uint8_t phase) {
|
||||
const uint8_t num_reads = 5;
|
||||
uint64_t total_value = 0;
|
||||
for (int i = 0; i < num_reads; ++i) {
|
||||
const uint32_t measurement_value = read32_(ATM90E32_REGISTER_URMS + phase, ATM90E32_REGISTER_URMSLSB + phase);
|
||||
total_value += measurement_value;
|
||||
}
|
||||
const uint32_t average_value = total_value / num_reads;
|
||||
const uint32_t shifted_value = average_value >> 7;
|
||||
const uint32_t voltage_offset = ~shifted_value + 1;
|
||||
return voltage_offset & 0xFFFF; // Take the lower 16 bits
|
||||
}
|
||||
|
||||
uint16_t ATM90E32Component::calibrate_current_offset_phase(uint8_t phase) {
|
||||
const uint8_t num_reads = 5;
|
||||
uint64_t total_value = 0;
|
||||
for (int i = 0; i < num_reads; ++i) {
|
||||
const uint32_t measurement_value = read32_(ATM90E32_REGISTER_IRMS + phase, ATM90E32_REGISTER_IRMSLSB + phase);
|
||||
total_value += measurement_value;
|
||||
}
|
||||
const uint32_t average_value = total_value / num_reads;
|
||||
const uint32_t current_offset = ~average_value + 1;
|
||||
return current_offset & 0xFFFF; // Take the lower 16 bits
|
||||
}
|
||||
|
||||
} // namespace atm90e32
|
||||
} // namespace esphome
|
||||
|
|
|
@ -3,14 +3,19 @@
|
|||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/spi/spi.h"
|
||||
#include "atm90e32_reg.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace atm90e32 {
|
||||
|
||||
class ATM90E32Component : public PollingComponent,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_HIGH,
|
||||
spi::CLOCK_PHASE_TRAILING, spi::DATA_RATE_200KHZ> {
|
||||
spi::CLOCK_PHASE_TRAILING, spi::DATA_RATE_1MHZ> {
|
||||
public:
|
||||
static const uint8_t PHASEA = 0;
|
||||
static const uint8_t PHASEB = 1;
|
||||
static const uint8_t PHASEC = 2;
|
||||
void loop() override;
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
|
@ -20,6 +25,7 @@ class ATM90E32Component : public PollingComponent,
|
|||
void set_current_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].current_sensor_ = obj; }
|
||||
void set_power_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].power_sensor_ = obj; }
|
||||
void set_reactive_power_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].reactive_power_sensor_ = obj; }
|
||||
void set_apparent_power_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].apparent_power_sensor_ = obj; }
|
||||
void set_forward_active_energy_sensor(int phase, sensor::Sensor *obj) {
|
||||
this->phase_[phase].forward_active_energy_sensor_ = obj;
|
||||
}
|
||||
|
@ -27,64 +33,94 @@ class ATM90E32Component : public PollingComponent,
|
|||
this->phase_[phase].reverse_active_energy_sensor_ = obj;
|
||||
}
|
||||
void set_power_factor_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].power_factor_sensor_ = obj; }
|
||||
void set_volt_gain(int phase, uint16_t gain) { this->phase_[phase].volt_gain_ = gain; }
|
||||
void set_phase_angle_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].phase_angle_sensor_ = obj; }
|
||||
void set_harmonic_active_power_sensor(int phase, sensor::Sensor *obj) {
|
||||
this->phase_[phase].harmonic_active_power_sensor_ = obj;
|
||||
}
|
||||
void set_peak_current_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].peak_current_sensor_ = obj; }
|
||||
void set_volt_gain(int phase, uint16_t gain) { this->phase_[phase].voltage_gain_ = gain; }
|
||||
void set_ct_gain(int phase, uint16_t gain) { this->phase_[phase].ct_gain_ = gain; }
|
||||
|
||||
void set_freq_sensor(sensor::Sensor *freq_sensor) { freq_sensor_ = freq_sensor; }
|
||||
void set_peak_current_signed(bool flag) { peak_current_signed_ = flag; }
|
||||
void set_chip_temperature_sensor(sensor::Sensor *chip_temperature_sensor) {
|
||||
chip_temperature_sensor_ = chip_temperature_sensor;
|
||||
}
|
||||
void set_line_freq(int freq) { line_freq_ = freq; }
|
||||
void set_current_phases(int phases) { current_phases_ = phases; }
|
||||
void set_pga_gain(uint16_t gain) { pga_gain_ = gain; }
|
||||
uint16_t calibrate_voltage_offset_phase(uint8_t /*phase*/);
|
||||
uint16_t calibrate_current_offset_phase(uint8_t /*phase*/);
|
||||
|
||||
int32_t last_periodic_millis = millis();
|
||||
|
||||
protected:
|
||||
uint16_t read16_(uint16_t a_register);
|
||||
int read32_(uint16_t addr_h, uint16_t addr_l);
|
||||
void write16_(uint16_t a_register, uint16_t val);
|
||||
|
||||
float get_line_voltage_a_();
|
||||
float get_line_voltage_b_();
|
||||
float get_line_voltage_c_();
|
||||
float get_line_current_a_();
|
||||
float get_line_current_b_();
|
||||
float get_line_current_c_();
|
||||
float get_active_power_a_();
|
||||
float get_active_power_b_();
|
||||
float get_active_power_c_();
|
||||
float get_reactive_power_a_();
|
||||
float get_reactive_power_b_();
|
||||
float get_reactive_power_c_();
|
||||
float get_power_factor_a_();
|
||||
float get_power_factor_b_();
|
||||
float get_power_factor_c_();
|
||||
float get_forward_active_energy_a_();
|
||||
float get_forward_active_energy_b_();
|
||||
float get_forward_active_energy_c_();
|
||||
float get_reverse_active_energy_a_();
|
||||
float get_reverse_active_energy_b_();
|
||||
float get_reverse_active_energy_c_();
|
||||
float get_local_phase_voltage_(uint8_t /*phase*/);
|
||||
float get_local_phase_current_(uint8_t /*phase*/);
|
||||
float get_local_phase_active_power_(uint8_t /*phase*/);
|
||||
float get_local_phase_reactive_power_(uint8_t /*phase*/);
|
||||
float get_local_phase_power_factor_(uint8_t /*phase*/);
|
||||
float get_local_phase_forward_active_energy_(uint8_t /*phase*/);
|
||||
float get_local_phase_reverse_active_energy_(uint8_t /*phase*/);
|
||||
float get_local_phase_angle_(uint8_t /*phase*/);
|
||||
float get_local_phase_harmonic_active_power_(uint8_t /*phase*/);
|
||||
float get_local_phase_peak_current_(uint8_t /*phase*/);
|
||||
float get_phase_voltage_(uint8_t /*phase*/);
|
||||
float get_phase_voltage_avg_(uint8_t /*phase*/);
|
||||
float get_phase_current_(uint8_t /*phase*/);
|
||||
float get_phase_current_avg_(uint8_t /*phase*/);
|
||||
float get_phase_active_power_(uint8_t /*phase*/);
|
||||
float get_phase_reactive_power_(uint8_t /*phase*/);
|
||||
float get_phase_power_factor_(uint8_t /*phase*/);
|
||||
float get_phase_forward_active_energy_(uint8_t /*phase*/);
|
||||
float get_phase_reverse_active_energy_(uint8_t /*phase*/);
|
||||
float get_phase_angle_(uint8_t /*phase*/);
|
||||
float get_phase_harmonic_active_power_(uint8_t /*phase*/);
|
||||
float get_phase_peak_current_(uint8_t /*phase*/);
|
||||
float get_frequency_();
|
||||
float get_chip_temperature_();
|
||||
bool get_publish_interval_flag_() { return publish_interval_flag_; };
|
||||
void set_publish_interval_flag_(bool flag) { publish_interval_flag_ = flag; };
|
||||
|
||||
struct ATM90E32Phase {
|
||||
uint16_t volt_gain_{7305};
|
||||
uint16_t voltage_gain_{7305};
|
||||
uint16_t ct_gain_{27961};
|
||||
uint16_t voltage_offset_{0};
|
||||
uint16_t current_offset_{0};
|
||||
float voltage_{0};
|
||||
float current_{0};
|
||||
float active_power_{0};
|
||||
float reactive_power_{0};
|
||||
float power_factor_{0};
|
||||
float forward_active_energy_{0};
|
||||
float reverse_active_energy_{0};
|
||||
float phase_angle_{0};
|
||||
float harmonic_active_power_{0};
|
||||
float peak_current_{0};
|
||||
sensor::Sensor *voltage_sensor_{nullptr};
|
||||
sensor::Sensor *current_sensor_{nullptr};
|
||||
sensor::Sensor *power_sensor_{nullptr};
|
||||
sensor::Sensor *reactive_power_sensor_{nullptr};
|
||||
sensor::Sensor *apparent_power_sensor_{nullptr};
|
||||
sensor::Sensor *power_factor_sensor_{nullptr};
|
||||
sensor::Sensor *forward_active_energy_sensor_{nullptr};
|
||||
sensor::Sensor *reverse_active_energy_sensor_{nullptr};
|
||||
sensor::Sensor *phase_angle_sensor_{nullptr};
|
||||
sensor::Sensor *harmonic_active_power_sensor_{nullptr};
|
||||
sensor::Sensor *peak_current_sensor_{nullptr};
|
||||
uint32_t cumulative_forward_active_energy_{0};
|
||||
uint32_t cumulative_reverse_active_energy_{0};
|
||||
} phase_[3];
|
||||
|
||||
sensor::Sensor *freq_sensor_{nullptr};
|
||||
sensor::Sensor *chip_temperature_sensor_{nullptr};
|
||||
uint16_t pga_gain_{0x15};
|
||||
int line_freq_{60};
|
||||
int current_phases_{3};
|
||||
bool publish_interval_flag_{true};
|
||||
bool peak_current_signed_{false};
|
||||
};
|
||||
|
||||
} // namespace atm90e32
|
||||
|
|
|
@ -131,10 +131,12 @@ static const uint16_t ATM90E32_REGISTER_IOFFSETN = 0x6E; // N Current Offset
|
|||
|
||||
/* ENERGY REGISTERS */
|
||||
static const uint16_t ATM90E32_REGISTER_APENERGYT = 0x80; // Total Forward Active
|
||||
static const uint16_t ATM90E32_REGISTER_APENERGY = 0x81; // Forward Active Reg Base
|
||||
static const uint16_t ATM90E32_REGISTER_APENERGYA = 0x81; // A Forward Active
|
||||
static const uint16_t ATM90E32_REGISTER_APENERGYB = 0x82; // B Forward Active
|
||||
static const uint16_t ATM90E32_REGISTER_APENERGYC = 0x83; // C Forward Active
|
||||
static const uint16_t ATM90E32_REGISTER_ANENERGYT = 0x84; // Total Reverse Active
|
||||
static const uint16_t ATM90E32_REGISTER_ANENERGY = 0x85; // Reverse Active Reg Base
|
||||
static const uint16_t ATM90E32_REGISTER_ANENERGYA = 0x85; // A Reverse Active
|
||||
static const uint16_t ATM90E32_REGISTER_ANENERGYB = 0x86; // B Reverse Active
|
||||
static const uint16_t ATM90E32_REGISTER_ANENERGYC = 0x87; // C Reverse Active
|
||||
|
@ -172,10 +174,12 @@ static const uint16_t ATM90E32_REGISTER_ANENERGYCH = 0xAF; // C Reverse Harm. E
|
|||
|
||||
/* POWER & P.F. REGISTERS */
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANT = 0xB0; // Total Mean Power (P)
|
||||
static const uint16_t ATM90E32_REGISTER_PMEAN = 0xB1; // Mean Power Reg Base (P)
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANA = 0xB1; // A Mean Power (P)
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANB = 0xB2; // B Mean Power (P)
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANC = 0xB3; // C Mean Power (P)
|
||||
static const uint16_t ATM90E32_REGISTER_QMEANT = 0xB4; // Total Mean Power (Q)
|
||||
static const uint16_t ATM90E32_REGISTER_QMEAN = 0xB5; // Mean Power Reg Base (Q)
|
||||
static const uint16_t ATM90E32_REGISTER_QMEANA = 0xB5; // A Mean Power (Q)
|
||||
static const uint16_t ATM90E32_REGISTER_QMEANB = 0xB6; // B Mean Power (Q)
|
||||
static const uint16_t ATM90E32_REGISTER_QMEANC = 0xB7; // C Mean Power (Q)
|
||||
|
@ -184,15 +188,18 @@ static const uint16_t ATM90E32_REGISTER_SMEANA = 0xB9; // A Mean Power (S)
|
|||
static const uint16_t ATM90E32_REGISTER_SMEANB = 0xBA; // B Mean Power (S)
|
||||
static const uint16_t ATM90E32_REGISTER_SMEANC = 0xBB; // C Mean Power (S)
|
||||
static const uint16_t ATM90E32_REGISTER_PFMEANT = 0xBC; // Mean Power Factor
|
||||
static const uint16_t ATM90E32_REGISTER_PFMEAN = 0xBD; // Power Factor Reg Base
|
||||
static const uint16_t ATM90E32_REGISTER_PFMEANA = 0xBD; // A Power Factor
|
||||
static const uint16_t ATM90E32_REGISTER_PFMEANB = 0xBE; // B Power Factor
|
||||
static const uint16_t ATM90E32_REGISTER_PFMEANC = 0xBF; // C Power Factor
|
||||
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANTLSB = 0xC0; // Lower Word (Tot. Act. Power)
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANLSB = 0xC1; // Lower Word Reg Base (Active Power)
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANALSB = 0xC1; // Lower Word (A Act. Power)
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANBLSB = 0xC2; // Lower Word (B Act. Power)
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANCLSB = 0xC3; // Lower Word (C Act. Power)
|
||||
static const uint16_t ATM90E32_REGISTER_QMEANTLSB = 0xC4; // Lower Word (Tot. React. Power)
|
||||
static const uint16_t ATM90E32_REGISTER_QMEANLSB = 0xC5; // Lower Word Reg Base (Reactive Power)
|
||||
static const uint16_t ATM90E32_REGISTER_QMEANALSB = 0xC5; // Lower Word (A React. Power)
|
||||
static const uint16_t ATM90E32_REGISTER_QMEANBLSB = 0xC6; // Lower Word (B React. Power)
|
||||
static const uint16_t ATM90E32_REGISTER_QMEANCLSB = 0xC7; // Lower Word (C React. Power)
|
||||
|
@ -207,12 +214,15 @@ static const uint16_t ATM90E32_REGISTER_PMEANAF = 0xD1; // A Active Fund. Power
|
|||
static const uint16_t ATM90E32_REGISTER_PMEANBF = 0xD2; // B Active Fund. Power
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANCF = 0xD3; // C Active Fund. Power
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANTH = 0xD4; // Total Active Harm. Power
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANH = 0xD5; // Active Harm. Power Reg Base
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANAH = 0xD5; // A Active Harm. Power
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANBH = 0xD6; // B Active Harm. Power
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANCH = 0xD7; // C Active Harm. Power
|
||||
static const uint16_t ATM90E32_REGISTER_URMS = 0xD9; // RMS Voltage Reg Base
|
||||
static const uint16_t ATM90E32_REGISTER_URMSA = 0xD9; // A RMS Voltage
|
||||
static const uint16_t ATM90E32_REGISTER_URMSB = 0xDA; // B RMS Voltage
|
||||
static const uint16_t ATM90E32_REGISTER_URMSC = 0xDB; // C RMS Voltage
|
||||
static const uint16_t ATM90E32_REGISTER_IRMS = 0xDD; // RMS Current Reg Base
|
||||
static const uint16_t ATM90E32_REGISTER_IRMSA = 0xDD; // A RMS Current
|
||||
static const uint16_t ATM90E32_REGISTER_IRMSB = 0xDE; // B RMS Current
|
||||
static const uint16_t ATM90E32_REGISTER_IRMSC = 0xDF; // C RMS Current
|
||||
|
@ -223,12 +233,15 @@ static const uint16_t ATM90E32_REGISTER_PMEANAFLSB = 0xE1; // Lower Word (A Act
|
|||
static const uint16_t ATM90E32_REGISTER_PMEANBFLSB = 0xE2; // Lower Word (B Act. Fund. Power)
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANCFLSB = 0xE3; // Lower Word (C Act. Fund. Power)
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANTHLSB = 0xE4; // Lower Word (Tot. Act. Harm. Power)
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANHLSB = 0xE5; // Lower Word (A Act. Harm. Power) Reg Base
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANAHLSB = 0xE5; // Lower Word (A Act. Harm. Power)
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANBHLSB = 0xE6; // Lower Word (B Act. Harm. Power)
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANCHLSB = 0xE7; // Lower Word (C Act. Harm. Power)
|
||||
static const uint16_t ATM90E32_REGISTER_URMSLSB = 0xE9; // Lower Word RMS Voltage Reg Base
|
||||
static const uint16_t ATM90E32_REGISTER_URMSALSB = 0xE9; // Lower Word (A RMS Voltage)
|
||||
static const uint16_t ATM90E32_REGISTER_URMSBLSB = 0xEA; // Lower Word (B RMS Voltage)
|
||||
static const uint16_t ATM90E32_REGISTER_URMSCLSB = 0xEB; // Lower Word (C RMS Voltage)
|
||||
static const uint16_t ATM90E32_REGISTER_IRMSLSB = 0xED; // Lower Word RMS Current Reg Base
|
||||
static const uint16_t ATM90E32_REGISTER_IRMSALSB = 0xED; // Lower Word (A RMS Current)
|
||||
static const uint16_t ATM90E32_REGISTER_IRMSBLSB = 0xEE; // Lower Word (B RMS Current)
|
||||
static const uint16_t ATM90E32_REGISTER_IRMSCLSB = 0xEF; // Lower Word (C RMS Current)
|
||||
|
@ -237,10 +250,12 @@ static const uint16_t ATM90E32_REGISTER_IRMSCLSB = 0xEF; // Lower Word (C RMS
|
|||
static const uint16_t ATM90E32_REGISTER_UPEAKA = 0xF1; // A Voltage Peak
|
||||
static const uint16_t ATM90E32_REGISTER_UPEAKB = 0xF2; // B Voltage Peak
|
||||
static const uint16_t ATM90E32_REGISTER_UPEAKC = 0xF3; // C Voltage Peak
|
||||
static const uint16_t ATM90E32_REGISTER_IPEAK = 0xF5; // Peak Current Reg Base
|
||||
static const uint16_t ATM90E32_REGISTER_IPEAKA = 0xF5; // A Current Peak
|
||||
static const uint16_t ATM90E32_REGISTER_IPEAKB = 0xF6; // B Current Peak
|
||||
static const uint16_t ATM90E32_REGISTER_IPEAKC = 0xF7; // C Current Peak
|
||||
static const uint16_t ATM90E32_REGISTER_FREQ = 0xF8; // Frequency
|
||||
static const uint16_t ATM90E32_REGISTER_PANGLE = 0xF9; // Mean Phase Angle Reg Base
|
||||
static const uint16_t ATM90E32_REGISTER_PANGLEA = 0xF9; // A Mean Phase Angle
|
||||
static const uint16_t ATM90E32_REGISTER_PANGLEB = 0xFA; // B Mean Phase Angle
|
||||
static const uint16_t ATM90E32_REGISTER_PANGLEC = 0xFB; // C Mean Phase Angle
|
||||
|
|
|
@ -9,8 +9,10 @@ from esphome.const import (
|
|||
CONF_PHASE_A,
|
||||
CONF_PHASE_B,
|
||||
CONF_PHASE_C,
|
||||
CONF_PHASE_ANGLE,
|
||||
CONF_POWER,
|
||||
CONF_POWER_FACTOR,
|
||||
CONF_APPARENT_POWER,
|
||||
CONF_FREQUENCY,
|
||||
CONF_FORWARD_ACTIVE_ENERGY,
|
||||
CONF_REVERSE_ACTIVE_ENERGY,
|
||||
|
@ -25,12 +27,13 @@ from esphome.const import (
|
|||
ICON_CURRENT_AC,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
STATE_CLASS_TOTAL_INCREASING,
|
||||
UNIT_AMPERE,
|
||||
UNIT_DEGREES,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_HERTZ,
|
||||
UNIT_VOLT,
|
||||
UNIT_AMPERE,
|
||||
UNIT_WATT,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_VOLT_AMPS_REACTIVE,
|
||||
UNIT_WATT,
|
||||
UNIT_WATT_HOURS,
|
||||
)
|
||||
|
||||
|
@ -40,6 +43,10 @@ CONF_GAIN_PGA = "gain_pga"
|
|||
CONF_CURRENT_PHASES = "current_phases"
|
||||
CONF_GAIN_VOLTAGE = "gain_voltage"
|
||||
CONF_GAIN_CT = "gain_ct"
|
||||
CONF_HARMONIC_POWER = "harmonic_power"
|
||||
CONF_PEAK_CURRENT = "peak_current"
|
||||
CONF_PEAK_CURRENT_SIGNED = "peak_current_signed"
|
||||
UNIT_DEG = "degrees"
|
||||
LINE_FREQS = {
|
||||
"50HZ": 50,
|
||||
"60HZ": 60,
|
||||
|
@ -85,6 +92,12 @@ ATM90E32_PHASE_SCHEMA = cv.Schema(
|
|||
accuracy_decimals=2,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_APPARENT_POWER): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_WATT,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema(
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_POWER_FACTOR,
|
||||
|
@ -102,6 +115,24 @@ ATM90E32_PHASE_SCHEMA = cv.Schema(
|
|||
device_class=DEVICE_CLASS_ENERGY,
|
||||
state_class=STATE_CLASS_TOTAL_INCREASING,
|
||||
),
|
||||
cv.Optional(CONF_PHASE_ANGLE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_DEGREES,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_HARMONIC_POWER): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_WATT,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_PEAK_CURRENT): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_AMPERE,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_CURRENT,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_GAIN_VOLTAGE, default=7305): cv.uint16_t,
|
||||
cv.Optional(CONF_GAIN_CT, default=27961): cv.uint16_t,
|
||||
}
|
||||
|
@ -132,6 +163,7 @@ CONFIG_SCHEMA = (
|
|||
CURRENT_PHASES, upper=True
|
||||
),
|
||||
cv.Optional(CONF_GAIN_PGA, default="2X"): cv.enum(PGA_GAINS, upper=True),
|
||||
cv.Optional(CONF_PEAK_CURRENT_SIGNED, default=False): cv.boolean,
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
|
@ -162,6 +194,9 @@ async def to_code(config):
|
|||
if reactive_power_config := conf.get(CONF_REACTIVE_POWER):
|
||||
sens = await sensor.new_sensor(reactive_power_config)
|
||||
cg.add(var.set_reactive_power_sensor(i, sens))
|
||||
if apparent_power_config := conf.get(CONF_APPARENT_POWER):
|
||||
sens = await sensor.new_sensor(apparent_power_config)
|
||||
cg.add(var.set_apparent_power_sensor(i, sens))
|
||||
if power_factor_config := conf.get(CONF_POWER_FACTOR):
|
||||
sens = await sensor.new_sensor(power_factor_config)
|
||||
cg.add(var.set_power_factor_sensor(i, sens))
|
||||
|
@ -171,6 +206,15 @@ async def to_code(config):
|
|||
if reverse_active_energy_config := conf.get(CONF_REVERSE_ACTIVE_ENERGY):
|
||||
sens = await sensor.new_sensor(reverse_active_energy_config)
|
||||
cg.add(var.set_reverse_active_energy_sensor(i, sens))
|
||||
if phase_angle_config := conf.get(CONF_PHASE_ANGLE):
|
||||
sens = await sensor.new_sensor(phase_angle_config)
|
||||
cg.add(var.set_phase_angle_sensor(i, sens))
|
||||
if harmonic_active_power_config := conf.get(CONF_HARMONIC_POWER):
|
||||
sens = await sensor.new_sensor(harmonic_active_power_config)
|
||||
cg.add(var.set_harmonic_active_power_sensor(i, sens))
|
||||
if peak_current_config := conf.get(CONF_PEAK_CURRENT):
|
||||
sens = await sensor.new_sensor(peak_current_config)
|
||||
cg.add(var.set_peak_current_sensor(i, sens))
|
||||
|
||||
if frequency_config := config.get(CONF_FREQUENCY):
|
||||
sens = await sensor.new_sensor(frequency_config)
|
||||
|
@ -182,3 +226,4 @@ async def to_code(config):
|
|||
cg.add(var.set_line_freq(config[CONF_LINE_FREQUENCY]))
|
||||
cg.add(var.set_current_phases(config[CONF_CURRENT_PHASES]))
|
||||
cg.add(var.set_pga_gain(config[CONF_GAIN_PGA]))
|
||||
cg.add(var.set_peak_current_signed(config[CONF_PEAK_CURRENT_SIGNED]))
|
||||
|
|
|
@ -194,8 +194,8 @@ void BangBangClimate::dump_config() {
|
|||
ESP_LOGCONFIG(TAG, " Supports HEAT: %s", YESNO(this->supports_heat_));
|
||||
ESP_LOGCONFIG(TAG, " Supports COOL: %s", YESNO(this->supports_cool_));
|
||||
ESP_LOGCONFIG(TAG, " Supports AWAY mode: %s", YESNO(this->supports_away_));
|
||||
ESP_LOGCONFIG(TAG, " Default Target Temperature Low: %.1f°C", this->normal_config_.default_temperature_low);
|
||||
ESP_LOGCONFIG(TAG, " Default Target Temperature High: %.1f°C", this->normal_config_.default_temperature_high);
|
||||
ESP_LOGCONFIG(TAG, " Default Target Temperature Low: %.2f°C", this->normal_config_.default_temperature_low);
|
||||
ESP_LOGCONFIG(TAG, " Default Target Temperature High: %.2f°C", this->normal_config_.default_temperature_high);
|
||||
}
|
||||
|
||||
BangBangClimateTargetTempConfig::BangBangClimateTargetTempConfig() = default;
|
||||
|
|
|
@ -8,8 +8,11 @@ from esphome.const import (
|
|||
CONF_IBEACON_MINOR,
|
||||
CONF_IBEACON_UUID,
|
||||
CONF_MIN_RSSI,
|
||||
CONF_TIMEOUT,
|
||||
)
|
||||
|
||||
CONF_IRK = "irk"
|
||||
|
||||
DEPENDENCIES = ["esp32_ble_tracker"]
|
||||
|
||||
ble_presence_ns = cg.esphome_ns.namespace("ble_presence")
|
||||
|
@ -34,10 +37,12 @@ CONFIG_SCHEMA = cv.All(
|
|||
.extend(
|
||||
{
|
||||
cv.Optional(CONF_MAC_ADDRESS): cv.mac_address,
|
||||
cv.Optional(CONF_IRK): cv.uuid,
|
||||
cv.Optional(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid,
|
||||
cv.Optional(CONF_IBEACON_MAJOR): cv.uint16_t,
|
||||
cv.Optional(CONF_IBEACON_MINOR): cv.uint16_t,
|
||||
cv.Optional(CONF_IBEACON_UUID): cv.uuid,
|
||||
cv.Optional(CONF_TIMEOUT, default="5min"): cv.positive_time_period,
|
||||
cv.Optional(CONF_MIN_RSSI): cv.All(
|
||||
cv.decibel, cv.int_range(min=-100, max=-30)
|
||||
),
|
||||
|
@ -45,7 +50,9 @@ CONFIG_SCHEMA = cv.All(
|
|||
)
|
||||
.extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
|
||||
.extend(cv.COMPONENT_SCHEMA),
|
||||
cv.has_exactly_one_key(CONF_MAC_ADDRESS, CONF_SERVICE_UUID, CONF_IBEACON_UUID),
|
||||
cv.has_exactly_one_key(
|
||||
CONF_MAC_ADDRESS, CONF_IRK, CONF_SERVICE_UUID, CONF_IBEACON_UUID
|
||||
),
|
||||
_validate,
|
||||
)
|
||||
|
||||
|
@ -55,12 +62,17 @@ async def to_code(config):
|
|||
await cg.register_component(var, config)
|
||||
await esp32_ble_tracker.register_ble_device(var, config)
|
||||
|
||||
cg.add(var.set_timeout(config[CONF_TIMEOUT].total_milliseconds))
|
||||
if min_rssi := config.get(CONF_MIN_RSSI):
|
||||
cg.add(var.set_minimum_rssi(min_rssi))
|
||||
|
||||
if mac_address := config.get(CONF_MAC_ADDRESS):
|
||||
cg.add(var.set_address(mac_address.as_hex))
|
||||
|
||||
if irk := config.get(CONF_IRK):
|
||||
irk = esp32_ble_tracker.as_hex_array(str(irk))
|
||||
cg.add(var.set_irk(irk))
|
||||
|
||||
if service_uuid := config.get(CONF_SERVICE_UUID):
|
||||
if len(service_uuid) == len(esp32_ble_tracker.bt_uuid16_format):
|
||||
cg.add(var.set_service_uuid16(esp32_ble_tracker.as_hex(service_uuid)))
|
||||
|
|
|
@ -6,6 +6,16 @@
|
|||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
#include "mbedtls/aes.h"
|
||||
#include "mbedtls/base64.h"
|
||||
#endif
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
#define MBEDTLS_AES_ALT
|
||||
#include <aes_alt.h>
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_presence {
|
||||
|
||||
|
@ -17,6 +27,10 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff,
|
|||
this->match_by_ = MATCH_BY_MAC_ADDRESS;
|
||||
this->address_ = address;
|
||||
}
|
||||
void set_irk(uint8_t *irk) {
|
||||
this->match_by_ = MATCH_BY_IRK;
|
||||
this->irk_ = irk;
|
||||
}
|
||||
void set_service_uuid16(uint16_t uuid) {
|
||||
this->match_by_ = MATCH_BY_SERVICE_UUID;
|
||||
this->uuid_ = esp32_ble_tracker::ESPBTUUID::from_uint16(uuid);
|
||||
|
@ -45,11 +59,7 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff,
|
|||
this->check_minimum_rssi_ = true;
|
||||
this->minimum_rssi_ = rssi;
|
||||
}
|
||||
void on_scan_end() override {
|
||||
if (!this->found_)
|
||||
this->publish_state(false);
|
||||
this->found_ = false;
|
||||
}
|
||||
void set_timeout(uint32_t timeout) { this->timeout_ = timeout; }
|
||||
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override {
|
||||
if (this->check_minimum_rssi_ && this->minimum_rssi_ > device.get_rssi()) {
|
||||
return false;
|
||||
|
@ -57,6 +67,12 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff,
|
|||
switch (this->match_by_) {
|
||||
case MATCH_BY_MAC_ADDRESS:
|
||||
if (device.address_uint64() == this->address_) {
|
||||
this->set_found_(true);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case MATCH_BY_IRK:
|
||||
if (resolve_irk_(device.address_uint64(), this->irk_)) {
|
||||
this->publish_state(true);
|
||||
this->found_ = true;
|
||||
return true;
|
||||
|
@ -65,8 +81,7 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff,
|
|||
case MATCH_BY_SERVICE_UUID:
|
||||
for (auto uuid : device.get_service_uuids()) {
|
||||
if (this->uuid_ == uuid) {
|
||||
this->publish_state(true);
|
||||
this->found_ = true;
|
||||
this->set_found_(true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -90,20 +105,31 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff,
|
|||
return false;
|
||||
}
|
||||
|
||||
this->publish_state(true);
|
||||
this->found_ = true;
|
||||
this->set_found_(true);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void loop() override {
|
||||
if (this->found_ && this->last_seen_ + this->timeout_ < millis())
|
||||
this->set_found_(false);
|
||||
}
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
|
||||
protected:
|
||||
enum MatchType { MATCH_BY_MAC_ADDRESS, MATCH_BY_SERVICE_UUID, MATCH_BY_IBEACON_UUID };
|
||||
void set_found_(bool state) {
|
||||
this->found_ = state;
|
||||
if (state)
|
||||
this->last_seen_ = millis();
|
||||
this->publish_state(state);
|
||||
}
|
||||
enum MatchType { MATCH_BY_MAC_ADDRESS, MATCH_BY_IRK, MATCH_BY_SERVICE_UUID, MATCH_BY_IBEACON_UUID };
|
||||
MatchType match_by_;
|
||||
|
||||
uint64_t address_;
|
||||
uint8_t *irk_;
|
||||
|
||||
esp32_ble_tracker::ESPBTUUID uuid_;
|
||||
|
||||
|
@ -117,7 +143,46 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff,
|
|||
bool check_ibeacon_minor_{false};
|
||||
bool check_minimum_rssi_{false};
|
||||
|
||||
bool resolve_irk_(uint64_t addr64, const uint8_t *irk) {
|
||||
uint8_t ecb_key[16];
|
||||
uint8_t ecb_plaintext[16];
|
||||
uint8_t ecb_ciphertext[16];
|
||||
|
||||
memcpy(&ecb_key, irk, 16);
|
||||
memset(&ecb_plaintext, 0, 16);
|
||||
|
||||
ecb_plaintext[13] = (addr64 >> 40) & 0xff;
|
||||
ecb_plaintext[14] = (addr64 >> 32) & 0xff;
|
||||
ecb_plaintext[15] = (addr64 >> 24) & 0xff;
|
||||
|
||||
mbedtls_aes_context ctx = {0, 0, {0}};
|
||||
mbedtls_aes_init(&ctx);
|
||||
|
||||
if (mbedtls_aes_setkey_enc(&ctx, ecb_key, 128) != 0) {
|
||||
mbedtls_aes_free(&ctx);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mbedtls_aes_crypt_ecb(&ctx,
|
||||
#ifdef USE_ARDUINO
|
||||
MBEDTLS_AES_ENCRYPT,
|
||||
#elif defined(USE_ESP_IDF)
|
||||
ESP_AES_ENCRYPT,
|
||||
#endif
|
||||
ecb_plaintext, ecb_ciphertext) != 0) {
|
||||
mbedtls_aes_free(&ctx);
|
||||
return false;
|
||||
}
|
||||
|
||||
mbedtls_aes_free(&ctx);
|
||||
|
||||
return ecb_ciphertext[15] == (addr64 & 0xff) && ecb_ciphertext[14] == ((addr64 >> 8) & 0xff) &&
|
||||
ecb_ciphertext[13] == ((addr64 >> 16) & 0xff);
|
||||
}
|
||||
|
||||
bool found_{false};
|
||||
uint32_t last_seen_{};
|
||||
uint32_t timeout_{};
|
||||
};
|
||||
|
||||
} // namespace ble_presence
|
||||
|
|
6
esphome/components/cst226/__init__.py
Normal file
6
esphome/components/cst226/__init__.py
Normal file
|
@ -0,0 +1,6 @@
|
|||
import esphome.codegen as cg
|
||||
|
||||
CODEOWNERS = ["@clydebarrow"]
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
cst226_ns = cg.esphome_ns.namespace("cst226")
|
38
esphome/components/cst226/touchscreen/__init__.py
Normal file
38
esphome/components/cst226/touchscreen/__init__.py
Normal file
|
@ -0,0 +1,38 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
|
||||
from esphome import pins
|
||||
from esphome.components import i2c, touchscreen
|
||||
from esphome.const import CONF_INTERRUPT_PIN, CONF_ID, CONF_RESET_PIN
|
||||
from .. import cst226_ns
|
||||
|
||||
|
||||
CST226Touchscreen = cst226_ns.class_(
|
||||
"CST226Touchscreen",
|
||||
touchscreen.Touchscreen,
|
||||
i2c.I2CDevice,
|
||||
)
|
||||
|
||||
CST226ButtonListener = cst226_ns.class_("CST226ButtonListener")
|
||||
CONFIG_SCHEMA = (
|
||||
touchscreen.touchscreen_schema("100ms")
|
||||
.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(CST226Touchscreen),
|
||||
cv.Optional(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pin_schema,
|
||||
cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
|
||||
}
|
||||
)
|
||||
.extend(i2c.i2c_device_schema(0x5A))
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await touchscreen.register_touchscreen(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if interrupt_pin := config.get(CONF_INTERRUPT_PIN):
|
||||
cg.add(var.set_interrupt_pin(await cg.gpio_pin_expression(interrupt_pin)))
|
||||
if reset_pin := config.get(CONF_RESET_PIN):
|
||||
cg.add(var.set_reset_pin(await cg.gpio_pin_expression(reset_pin)))
|
92
esphome/components/cst226/touchscreen/cst226_touchscreen.cpp
Normal file
92
esphome/components/cst226/touchscreen/cst226_touchscreen.cpp
Normal file
|
@ -0,0 +1,92 @@
|
|||
#include "cst226_touchscreen.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace cst226 {
|
||||
|
||||
void CST226Touchscreen::setup() {
|
||||
esph_log_config(TAG, "Setting up CST226 Touchscreen...");
|
||||
if (this->reset_pin_ != nullptr) {
|
||||
this->reset_pin_->setup();
|
||||
this->reset_pin_->digital_write(true);
|
||||
delay(5);
|
||||
this->reset_pin_->digital_write(false);
|
||||
delay(5);
|
||||
this->reset_pin_->digital_write(true);
|
||||
this->set_timeout(30, [this] { this->continue_setup_(); });
|
||||
} else {
|
||||
this->continue_setup_();
|
||||
}
|
||||
}
|
||||
|
||||
void CST226Touchscreen::update_touches() {
|
||||
uint8_t data[28];
|
||||
if (!this->read_bytes(CST226_REG_STATUS, data, sizeof data)) {
|
||||
this->status_set_warning();
|
||||
this->skip_update_ = true;
|
||||
return;
|
||||
}
|
||||
this->status_clear_warning();
|
||||
if (data[6] != 0xAB || data[0] == 0xAB || data[5] == 0x80) {
|
||||
this->skip_update_ = true;
|
||||
return;
|
||||
}
|
||||
uint8_t num_of_touches = data[5] & 0x7F;
|
||||
if (num_of_touches == 0 || num_of_touches > 5) {
|
||||
this->write_byte(0, 0xAB);
|
||||
return;
|
||||
}
|
||||
|
||||
size_t index = 0;
|
||||
for (uint8_t i = 0; i != num_of_touches; i++) {
|
||||
uint8_t id = data[index] >> 4;
|
||||
int16_t x = (data[index + 1] << 4) | ((data[index + 3] >> 4) & 0x0F);
|
||||
int16_t y = (data[index + 2] << 4) | (data[index + 3] & 0x0F);
|
||||
int16_t z = data[index + 4];
|
||||
this->add_raw_touch_position_(id, x, y, z);
|
||||
esph_log_v(TAG, "Read touch %d: %d/%d", id, x, y);
|
||||
index += 5;
|
||||
if (i == 0)
|
||||
index += 2;
|
||||
}
|
||||
}
|
||||
|
||||
void CST226Touchscreen::continue_setup_() {
|
||||
uint8_t buffer[8];
|
||||
if (this->interrupt_pin_ != nullptr) {
|
||||
this->interrupt_pin_->setup();
|
||||
this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_FALLING_EDGE);
|
||||
}
|
||||
buffer[0] = 0xD1;
|
||||
if (this->write_register16(0xD1, buffer, 1) != i2c::ERROR_OK) {
|
||||
esph_log_e(TAG, "Write byte to 0xD1 failed");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
delay(10);
|
||||
if (this->read16_(0xD204, buffer, 4)) {
|
||||
uint16_t chip_id = buffer[2] + (buffer[3] << 8);
|
||||
uint16_t project_id = buffer[0] + (buffer[1] << 8);
|
||||
esph_log_config(TAG, "Chip ID %X, project ID %x", chip_id, project_id);
|
||||
}
|
||||
if (this->x_raw_max_ == 0 || this->y_raw_max_ == 0) {
|
||||
if (this->read16_(0xD1F8, buffer, 4)) {
|
||||
this->x_raw_max_ = buffer[0] + (buffer[1] << 8);
|
||||
this->y_raw_max_ = buffer[2] + (buffer[3] << 8);
|
||||
} else {
|
||||
this->x_raw_max_ = this->display_->get_native_width();
|
||||
this->y_raw_max_ = this->display_->get_native_height();
|
||||
}
|
||||
}
|
||||
this->setup_complete_ = true;
|
||||
esph_log_config(TAG, "CST226 Touchscreen setup complete");
|
||||
}
|
||||
|
||||
void CST226Touchscreen::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "CST226 Touchscreen:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_);
|
||||
LOG_PIN(" Reset Pin: ", this->reset_pin_);
|
||||
}
|
||||
|
||||
} // namespace cst226
|
||||
} // namespace esphome
|
44
esphome/components/cst226/touchscreen/cst226_touchscreen.h
Normal file
44
esphome/components/cst226/touchscreen/cst226_touchscreen.h
Normal file
|
@ -0,0 +1,44 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/touchscreen/touchscreen.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace cst226 {
|
||||
|
||||
static const char *const TAG = "cst226.touchscreen";
|
||||
|
||||
static const uint8_t CST226_REG_STATUS = 0x00;
|
||||
|
||||
class CST226Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
void update_touches() override;
|
||||
void dump_config() override;
|
||||
|
||||
void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; }
|
||||
void set_reset_pin(GPIOPin *pin) { this->reset_pin_ = pin; }
|
||||
bool can_proceed() override { return this->setup_complete_ || this->is_failed(); }
|
||||
|
||||
protected:
|
||||
bool read16_(uint16_t addr, uint8_t *data, size_t len) {
|
||||
if (this->read_register16(addr, data, len) != i2c::ERROR_OK) {
|
||||
esph_log_e(TAG, "Read data from 0x%04X failed", addr);
|
||||
this->mark_failed();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
void continue_setup_();
|
||||
|
||||
InternalGPIOPin *interrupt_pin_{};
|
||||
GPIOPin *reset_pin_{};
|
||||
uint8_t chip_id_{};
|
||||
bool setup_complete_{};
|
||||
};
|
||||
|
||||
} // namespace cst226
|
||||
} // namespace esphome
|
6
esphome/components/cst816/__init__.py
Normal file
6
esphome/components/cst816/__init__.py
Normal file
|
@ -0,0 +1,6 @@
|
|||
import esphome.codegen as cg
|
||||
|
||||
CODEOWNERS = ["@clydebarrow"]
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
cst816_ns = cg.esphome_ns.namespace("cst816")
|
28
esphome/components/cst816/binary_sensor/__init__.py
Normal file
28
esphome/components/cst816/binary_sensor/__init__.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import binary_sensor
|
||||
|
||||
from .. import cst816_ns
|
||||
from ..touchscreen import CST816Touchscreen, CST816ButtonListener
|
||||
|
||||
CONF_CST816_ID = "cst816_id"
|
||||
|
||||
CST816Button = cst816_ns.class_(
|
||||
"CST816Button",
|
||||
binary_sensor.BinarySensor,
|
||||
cg.Component,
|
||||
CST816ButtonListener,
|
||||
cg.Parented.template(CST816Touchscreen),
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(CST816Button).extend(
|
||||
{
|
||||
cv.GenerateID(CONF_CST816_ID): cv.use_id(CST816Touchscreen),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = await binary_sensor.new_binary_sensor(config)
|
||||
await cg.register_component(var, config)
|
||||
await cg.register_parented(var, config[CONF_CST816_ID])
|
27
esphome/components/cst816/binary_sensor/cst816_button.h
Normal file
27
esphome/components/cst816/binary_sensor/cst816_button.h
Normal file
|
@ -0,0 +1,27 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
#include "esphome/components/cst816/touchscreen/cst816_touchscreen.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace cst816 {
|
||||
|
||||
class CST816Button : public binary_sensor::BinarySensor,
|
||||
public Component,
|
||||
public CST816ButtonListener,
|
||||
public Parented<CST816Touchscreen> {
|
||||
public:
|
||||
void setup() override {
|
||||
this->parent_->register_button_listener(this);
|
||||
this->publish_initial_state(false);
|
||||
}
|
||||
|
||||
void dump_config() override { LOG_BINARY_SENSOR("", "CST816 Button", this); }
|
||||
|
||||
void update_button(bool state) override { this->publish_state(state); }
|
||||
};
|
||||
|
||||
} // namespace cst816
|
||||
} // namespace esphome
|
34
esphome/components/cst816/touchscreen/__init__.py
Normal file
34
esphome/components/cst816/touchscreen/__init__.py
Normal file
|
@ -0,0 +1,34 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
|
||||
from esphome import pins
|
||||
from esphome.components import i2c, touchscreen
|
||||
from esphome.const import CONF_INTERRUPT_PIN, CONF_ID, CONF_RESET_PIN
|
||||
from .. import cst816_ns
|
||||
|
||||
|
||||
CST816Touchscreen = cst816_ns.class_(
|
||||
"CST816Touchscreen",
|
||||
touchscreen.Touchscreen,
|
||||
i2c.I2CDevice,
|
||||
)
|
||||
|
||||
CST816ButtonListener = cst816_ns.class_("CST816ButtonListener")
|
||||
CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(CST816Touchscreen),
|
||||
cv.Optional(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pin_schema,
|
||||
cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
|
||||
}
|
||||
).extend(i2c.i2c_device_schema(0x15))
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await touchscreen.register_touchscreen(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if interrupt_pin := config.get(CONF_INTERRUPT_PIN):
|
||||
cg.add(var.set_interrupt_pin(await cg.gpio_pin_expression(interrupt_pin)))
|
||||
if reset_pin := config.get(CONF_RESET_PIN):
|
||||
cg.add(var.set_reset_pin(await cg.gpio_pin_expression(reset_pin)))
|
113
esphome/components/cst816/touchscreen/cst816_touchscreen.cpp
Normal file
113
esphome/components/cst816/touchscreen/cst816_touchscreen.cpp
Normal file
|
@ -0,0 +1,113 @@
|
|||
#include "cst816_touchscreen.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace cst816 {
|
||||
|
||||
void CST816Touchscreen::continue_setup_() {
|
||||
if (this->interrupt_pin_ != nullptr) {
|
||||
this->interrupt_pin_->setup();
|
||||
this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_FALLING_EDGE);
|
||||
}
|
||||
if (!this->read_byte(REG_CHIP_ID, &this->chip_id_)) {
|
||||
this->mark_failed();
|
||||
esph_log_e(TAG, "Failed to read chip id");
|
||||
return;
|
||||
}
|
||||
switch (this->chip_id_) {
|
||||
case CST820_CHIP_ID:
|
||||
case CST716_CHIP_ID:
|
||||
case CST816S_CHIP_ID:
|
||||
case CST816D_CHIP_ID:
|
||||
case CST816T_CHIP_ID:
|
||||
break;
|
||||
default:
|
||||
this->mark_failed();
|
||||
esph_log_e(TAG, "Unknown chip ID 0x%02X", this->chip_id_);
|
||||
return;
|
||||
}
|
||||
this->write_byte(REG_IRQ_CTL, IRQ_EN_MOTION);
|
||||
if (this->x_raw_max_ == this->x_raw_min_) {
|
||||
this->x_raw_max_ = this->display_->get_native_width();
|
||||
}
|
||||
if (this->y_raw_max_ == this->y_raw_min_) {
|
||||
this->y_raw_max_ = this->display_->get_native_height();
|
||||
}
|
||||
esph_log_config(TAG, "CST816 Touchscreen setup complete");
|
||||
}
|
||||
|
||||
void CST816Touchscreen::update_button_state_(bool state) {
|
||||
if (this->button_touched_ == state)
|
||||
return;
|
||||
this->button_touched_ = state;
|
||||
for (auto *listener : this->button_listeners_)
|
||||
listener->update_button(state);
|
||||
}
|
||||
|
||||
void CST816Touchscreen::setup() {
|
||||
esph_log_config(TAG, "Setting up CST816 Touchscreen...");
|
||||
if (this->reset_pin_ != nullptr) {
|
||||
this->reset_pin_->setup();
|
||||
this->reset_pin_->digital_write(true);
|
||||
delay(5);
|
||||
this->reset_pin_->digital_write(false);
|
||||
delay(5);
|
||||
this->reset_pin_->digital_write(true);
|
||||
this->set_timeout(30, [this] { this->continue_setup_(); });
|
||||
} else {
|
||||
this->continue_setup_();
|
||||
}
|
||||
}
|
||||
|
||||
void CST816Touchscreen::update_touches() {
|
||||
uint8_t data[13];
|
||||
if (!this->read_bytes(REG_STATUS, data, sizeof data)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
uint8_t num_of_touches = data[REG_TOUCH_NUM] & 3;
|
||||
if (num_of_touches == 0) {
|
||||
this->update_button_state_(false);
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t x = encode_uint16(data[REG_XPOS_HIGH] & 0xF, data[REG_XPOS_LOW]);
|
||||
uint16_t y = encode_uint16(data[REG_YPOS_HIGH] & 0xF, data[REG_YPOS_LOW]);
|
||||
esph_log_v(TAG, "Read touch %d/%d", x, y);
|
||||
if (x >= this->x_raw_max_) {
|
||||
this->update_button_state_(true);
|
||||
} else {
|
||||
this->add_raw_touch_position_(0, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
void CST816Touchscreen::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "CST816 Touchscreen:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_);
|
||||
LOG_PIN(" Reset Pin: ", this->reset_pin_);
|
||||
const char *name;
|
||||
switch (this->chip_id_) {
|
||||
case CST820_CHIP_ID:
|
||||
name = "CST820";
|
||||
break;
|
||||
case CST816S_CHIP_ID:
|
||||
name = "CST816S";
|
||||
break;
|
||||
case CST816D_CHIP_ID:
|
||||
name = "CST816D";
|
||||
break;
|
||||
case CST716_CHIP_ID:
|
||||
name = "CST716";
|
||||
break;
|
||||
case CST816T_CHIP_ID:
|
||||
name = "CST816T";
|
||||
break;
|
||||
default:
|
||||
name = "Unknown";
|
||||
break;
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " Chip type: %s", name);
|
||||
}
|
||||
|
||||
} // namespace cst816
|
||||
} // namespace esphome
|
60
esphome/components/cst816/touchscreen/cst816_touchscreen.h
Normal file
60
esphome/components/cst816/touchscreen/cst816_touchscreen.h
Normal file
|
@ -0,0 +1,60 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/touchscreen/touchscreen.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace cst816 {
|
||||
|
||||
static const char *const TAG = "cst816.touchscreen";
|
||||
|
||||
static const uint8_t REG_STATUS = 0x00;
|
||||
static const uint8_t REG_TOUCH_NUM = 0x02;
|
||||
static const uint8_t REG_XPOS_HIGH = 0x03;
|
||||
static const uint8_t REG_XPOS_LOW = 0x04;
|
||||
static const uint8_t REG_YPOS_HIGH = 0x05;
|
||||
static const uint8_t REG_YPOS_LOW = 0x06;
|
||||
static const uint8_t REG_DIS_AUTOSLEEP = 0xFE;
|
||||
static const uint8_t REG_CHIP_ID = 0xA7;
|
||||
static const uint8_t REG_FW_VERSION = 0xA9;
|
||||
static const uint8_t REG_SLEEP = 0xE5;
|
||||
static const uint8_t REG_IRQ_CTL = 0xFA;
|
||||
static const uint8_t IRQ_EN_MOTION = 0x70;
|
||||
|
||||
static const uint8_t CST820_CHIP_ID = 0xB7;
|
||||
static const uint8_t CST816S_CHIP_ID = 0xB4;
|
||||
static const uint8_t CST816D_CHIP_ID = 0xB6;
|
||||
static const uint8_t CST816T_CHIP_ID = 0xB5;
|
||||
static const uint8_t CST716_CHIP_ID = 0x20;
|
||||
|
||||
class CST816ButtonListener {
|
||||
public:
|
||||
virtual void update_button(bool state) = 0;
|
||||
};
|
||||
|
||||
class CST816Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
void update_touches() override;
|
||||
void register_button_listener(CST816ButtonListener *listener) { this->button_listeners_.push_back(listener); }
|
||||
void dump_config() override;
|
||||
|
||||
void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; }
|
||||
void set_reset_pin(GPIOPin *pin) { this->reset_pin_ = pin; }
|
||||
|
||||
protected:
|
||||
void continue_setup_();
|
||||
void update_button_state_(bool state);
|
||||
|
||||
InternalGPIOPin *interrupt_pin_{};
|
||||
GPIOPin *reset_pin_{};
|
||||
uint8_t chip_id_{};
|
||||
std::vector<CST816ButtonListener *> button_listeners_;
|
||||
bool button_touched_{};
|
||||
};
|
||||
|
||||
} // namespace cst816
|
||||
} // namespace esphome
|
146
esphome/components/datetime/__init__.py
Normal file
146
esphome/components/datetime/__init__.py
Normal file
|
@ -0,0 +1,146 @@
|
|||
import esphome.codegen as cg
|
||||
|
||||
# import cpp_generator as cpp
|
||||
import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
from esphome.components import mqtt
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_ON_VALUE,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_TYPE,
|
||||
CONF_MQTT_ID,
|
||||
CONF_DATE,
|
||||
CONF_YEAR,
|
||||
CONF_MONTH,
|
||||
CONF_DAY,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
|
||||
|
||||
CODEOWNERS = ["@rfdarter"]
|
||||
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
|
||||
datetime_ns = cg.esphome_ns.namespace("datetime")
|
||||
DateTimeBase = datetime_ns.class_("DateTimeBase", cg.EntityBase)
|
||||
DateEntity = datetime_ns.class_("DateEntity", DateTimeBase)
|
||||
|
||||
# Actions
|
||||
DateSetAction = datetime_ns.class_("DateSetAction", automation.Action)
|
||||
|
||||
DateTimeStateTrigger = datetime_ns.class_(
|
||||
"DateTimeStateTrigger", automation.Trigger.template(cg.ESPTime)
|
||||
)
|
||||
|
||||
DATETIME_MODES = [
|
||||
"DATE",
|
||||
"TIME",
|
||||
"DATETIME",
|
||||
]
|
||||
|
||||
|
||||
_DATETIME_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTDatetimeComponent),
|
||||
cv.Optional(CONF_ON_VALUE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DateTimeStateTrigger),
|
||||
}
|
||||
),
|
||||
}
|
||||
).extend(cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA))
|
||||
|
||||
|
||||
def date_schema(class_: MockObjClass) -> cv.Schema:
|
||||
schema = {
|
||||
cv.GenerateID(): cv.declare_id(class_),
|
||||
cv.Optional(CONF_TYPE, default="DATE"): cv.one_of("DATE", upper=True),
|
||||
}
|
||||
return _DATETIME_SCHEMA.extend(schema)
|
||||
|
||||
|
||||
def time_schema(class_: MockObjClass) -> cv.Schema:
|
||||
schema = {
|
||||
cv.GenerateID(): cv.declare_id(class_),
|
||||
cv.Optional(CONF_TYPE, default="TIME"): cv.one_of("TIME", upper=True),
|
||||
}
|
||||
return _DATETIME_SCHEMA.extend(schema)
|
||||
|
||||
|
||||
def datetime_schema(class_: MockObjClass) -> cv.Schema:
|
||||
schema = {
|
||||
cv.GenerateID(): cv.declare_id(class_),
|
||||
cv.Optional(CONF_TYPE, default="DATETIME"): cv.one_of("DATETIME", upper=True),
|
||||
}
|
||||
return _DATETIME_SCHEMA.extend(schema)
|
||||
|
||||
|
||||
async def setup_datetime_core_(var, config):
|
||||
await setup_entity(var, config)
|
||||
|
||||
if CONF_MQTT_ID in config:
|
||||
mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var)
|
||||
await mqtt.register_mqtt_component(mqtt_, config)
|
||||
for conf in config.get(CONF_ON_VALUE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [(cg.ESPTime, "x")], conf)
|
||||
|
||||
|
||||
async def register_datetime(var, config):
|
||||
if not CORE.has_id(config[CONF_ID]):
|
||||
var = cg.Pvariable(config[CONF_ID], var)
|
||||
cg.add(getattr(cg.App, f"register_{config[CONF_TYPE].lower()}")(var))
|
||||
await setup_datetime_core_(var, config)
|
||||
cg.add_define(f"USE_DATETIME_{config[CONF_TYPE]}")
|
||||
|
||||
|
||||
async def new_datetime(config, *args):
|
||||
var = cg.new_Pvariable(config[CONF_ID], *args)
|
||||
await register_datetime(var, config)
|
||||
return var
|
||||
|
||||
|
||||
@coroutine_with_priority(40.0)
|
||||
async def to_code(config):
|
||||
cg.add_define("USE_DATETIME")
|
||||
cg.add_global(datetime_ns.using)
|
||||
|
||||
|
||||
OPERATION_BASE_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(DateEntity),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"datetime.date.set",
|
||||
DateSetAction,
|
||||
OPERATION_BASE_SCHEMA.extend(
|
||||
{
|
||||
cv.Required(CONF_DATE): cv.Any(
|
||||
cv.returning_lambda, cv.date_time(allowed_time=False)
|
||||
),
|
||||
}
|
||||
),
|
||||
)
|
||||
async def datetime_date_set_to_code(config, action_id, template_arg, args):
|
||||
action_var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(action_var, config[CONF_ID])
|
||||
|
||||
date = config[CONF_DATE]
|
||||
if cg.is_template(date):
|
||||
template_ = await cg.templatable(config[CONF_DATE], [], cg.ESPTime)
|
||||
cg.add(action_var.set_date(template_))
|
||||
else:
|
||||
date_struct = cg.StructInitializer(
|
||||
cg.ESPTime,
|
||||
("day_of_month", date[CONF_DAY]),
|
||||
("month", date[CONF_MONTH]),
|
||||
("year", date[CONF_YEAR]),
|
||||
)
|
||||
cg.add(action_var.set_date(date_struct))
|
||||
return action_var
|
117
esphome/components/datetime/date_entity.cpp
Normal file
117
esphome/components/datetime/date_entity.cpp
Normal file
|
@ -0,0 +1,117 @@
|
|||
#include "date_entity.h"
|
||||
|
||||
#ifdef USE_DATETIME_DATE
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace datetime {
|
||||
|
||||
static const char *const TAG = "datetime.date_entity";
|
||||
|
||||
void DateEntity::publish_state() {
|
||||
if (this->year_ == 0 || this->month_ == 0 || this->day_ == 0) {
|
||||
this->has_state_ = false;
|
||||
return;
|
||||
}
|
||||
if (this->year_ < 1970 || this->year_ > 3000) {
|
||||
this->has_state_ = false;
|
||||
ESP_LOGE(TAG, "Year must be between 1970 and 3000");
|
||||
return;
|
||||
}
|
||||
if (this->month_ < 1 || this->month_ > 12) {
|
||||
this->has_state_ = false;
|
||||
ESP_LOGE(TAG, "Month must be between 1 and 12");
|
||||
return;
|
||||
}
|
||||
if (this->day_ > days_in_month(this->month_, this->year_)) {
|
||||
this->has_state_ = false;
|
||||
ESP_LOGE(TAG, "Day must be between 1 and %d for month %d", days_in_month(this->month_, this->year_), this->month_);
|
||||
return;
|
||||
}
|
||||
this->has_state_ = true;
|
||||
ESP_LOGD(TAG, "'%s': Sending date %d-%d-%d", this->get_name().c_str(), this->year_, this->month_, this->day_);
|
||||
this->state_callback_.call();
|
||||
}
|
||||
|
||||
DateCall DateEntity::make_call() { return DateCall(this); }
|
||||
|
||||
void DateCall::validate_() {
|
||||
if (this->year_.has_value() && (this->year_ < 1970 || this->year_ > 3000)) {
|
||||
ESP_LOGE(TAG, "Year must be between 1970 and 3000");
|
||||
this->year_.reset();
|
||||
}
|
||||
if (this->month_.has_value() && (this->month_ < 1 || this->month_ > 12)) {
|
||||
ESP_LOGE(TAG, "Month must be between 1 and 12");
|
||||
this->month_.reset();
|
||||
}
|
||||
if (this->day_.has_value()) {
|
||||
uint16_t year = 0;
|
||||
uint8_t month = 0;
|
||||
if (this->month_.has_value()) {
|
||||
month = *this->month_;
|
||||
} else {
|
||||
if (this->parent_->month != 0) {
|
||||
month = this->parent_->month;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Month must be set to validate day");
|
||||
this->day_.reset();
|
||||
}
|
||||
}
|
||||
if (this->year_.has_value()) {
|
||||
year = *this->year_;
|
||||
} else {
|
||||
if (this->parent_->year != 0) {
|
||||
year = this->parent_->year;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Year must be set to validate day");
|
||||
this->day_.reset();
|
||||
}
|
||||
}
|
||||
if (this->day_.has_value() && *this->day_ > days_in_month(month, year)) {
|
||||
ESP_LOGE(TAG, "Day must be between 1 and %d for month %d", days_in_month(month, year), month);
|
||||
this->day_.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DateCall::perform() {
|
||||
this->validate_();
|
||||
this->parent_->control(*this);
|
||||
}
|
||||
|
||||
DateCall &DateCall::set_date(uint16_t year, uint8_t month, uint8_t day) {
|
||||
this->year_ = year;
|
||||
this->month_ = month;
|
||||
this->day_ = day;
|
||||
return *this;
|
||||
};
|
||||
|
||||
DateCall &DateCall::set_date(ESPTime time) { return this->set_date(time.year, time.month, time.day_of_month); };
|
||||
|
||||
DateCall &DateCall::set_date(const std::string &date) {
|
||||
ESPTime val{};
|
||||
if (!ESPTime::strptime(date, val)) {
|
||||
ESP_LOGE(TAG, "Could not convert the date string to an ESPTime object");
|
||||
return *this;
|
||||
}
|
||||
return this->set_date(val);
|
||||
}
|
||||
|
||||
DateCall DateEntityRestoreState::to_call(DateEntity *date) {
|
||||
DateCall call = date->make_call();
|
||||
call.set_date(this->year, this->month, this->day);
|
||||
return call;
|
||||
}
|
||||
|
||||
void DateEntityRestoreState::apply(DateEntity *date) {
|
||||
date->year_ = this->year;
|
||||
date->month_ = this->month;
|
||||
date->day_ = this->day;
|
||||
date->publish_state();
|
||||
}
|
||||
|
||||
} // namespace datetime
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_DATETIME_DATE
|
117
esphome/components/datetime/date_entity.h
Normal file
117
esphome/components/datetime/date_entity.h
Normal file
|
@ -0,0 +1,117 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
#ifdef USE_DATETIME_DATE
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/time.h"
|
||||
|
||||
#include "datetime_base.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace datetime {
|
||||
|
||||
#define LOG_DATETIME_DATE(prefix, type, obj) \
|
||||
if ((obj) != nullptr) { \
|
||||
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
|
||||
if (!(obj)->get_icon().empty()) { \
|
||||
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \
|
||||
} \
|
||||
}
|
||||
|
||||
class DateCall;
|
||||
class DateEntity;
|
||||
|
||||
struct DateEntityRestoreState {
|
||||
uint16_t year;
|
||||
uint8_t month;
|
||||
uint8_t day;
|
||||
|
||||
DateCall to_call(DateEntity *date);
|
||||
void apply(DateEntity *date);
|
||||
} __attribute__((packed));
|
||||
|
||||
class DateEntity : public DateTimeBase {
|
||||
protected:
|
||||
uint16_t year_;
|
||||
uint8_t month_;
|
||||
uint8_t day_;
|
||||
|
||||
public:
|
||||
void publish_state();
|
||||
DateCall make_call();
|
||||
|
||||
ESPTime state_as_esptime() const override {
|
||||
ESPTime obj;
|
||||
obj.year = this->year_;
|
||||
obj.month = this->month_;
|
||||
obj.day_of_month = this->day_;
|
||||
return obj;
|
||||
}
|
||||
|
||||
const uint16_t &year = year_;
|
||||
const uint8_t &month = month_;
|
||||
const uint8_t &day = day_;
|
||||
|
||||
protected:
|
||||
friend class DateCall;
|
||||
friend struct DateEntityRestoreState;
|
||||
|
||||
virtual void control(const DateCall &call) = 0;
|
||||
};
|
||||
|
||||
class DateCall {
|
||||
public:
|
||||
explicit DateCall(DateEntity *parent) : parent_(parent) {}
|
||||
void perform();
|
||||
DateCall &set_date(uint16_t year, uint8_t month, uint8_t day);
|
||||
DateCall &set_date(ESPTime time);
|
||||
DateCall &set_date(const std::string &date);
|
||||
|
||||
DateCall &set_year(uint16_t year) {
|
||||
this->year_ = year;
|
||||
return *this;
|
||||
}
|
||||
DateCall &set_month(uint8_t month) {
|
||||
this->month_ = month;
|
||||
return *this;
|
||||
}
|
||||
DateCall &set_day(uint8_t day) {
|
||||
this->day_ = day;
|
||||
return *this;
|
||||
}
|
||||
|
||||
optional<uint16_t> get_year() const { return this->year_; }
|
||||
optional<uint8_t> get_month() const { return this->month_; }
|
||||
optional<uint8_t> get_day() const { return this->day_; }
|
||||
|
||||
protected:
|
||||
void validate_();
|
||||
|
||||
DateEntity *parent_;
|
||||
|
||||
optional<int16_t> year_;
|
||||
optional<uint8_t> month_;
|
||||
optional<uint8_t> day_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class DateSetAction : public Action<Ts...>, public Parented<DateEntity> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(ESPTime, date)
|
||||
|
||||
void play(Ts... x) override {
|
||||
auto call = this->parent_->make_call();
|
||||
|
||||
if (this->date_.has_value()) {
|
||||
call.set_date(this->date_.value(x...));
|
||||
}
|
||||
call.perform();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace datetime
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_DATETIME_DATE
|
34
esphome/components/datetime/datetime_base.h
Normal file
34
esphome/components/datetime/datetime_base.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/entity_base.h"
|
||||
#include "esphome/core/time.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace datetime {
|
||||
|
||||
class DateTimeBase : public EntityBase {
|
||||
public:
|
||||
/// Return whether this Datetime has gotten a full state yet.
|
||||
bool has_state() const { return this->has_state_; }
|
||||
|
||||
virtual ESPTime state_as_esptime() const = 0;
|
||||
|
||||
void add_on_state_callback(std::function<void()> &&callback) { this->state_callback_.add(std::move(callback)); }
|
||||
|
||||
protected:
|
||||
CallbackManager<void()> state_callback_;
|
||||
|
||||
bool has_state_{false};
|
||||
};
|
||||
|
||||
class DateTimeStateTrigger : public Trigger<ESPTime> {
|
||||
public:
|
||||
explicit DateTimeStateTrigger(DateTimeBase *parent) {
|
||||
parent->add_on_state_callback([this, parent]() { this->trigger(parent->state_as_esptime()); });
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace datetime
|
||||
} // namespace esphome
|
|
@ -6,6 +6,7 @@
|
|||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/version.h"
|
||||
#include <cinttypes>
|
||||
#include <climits>
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
|
@ -49,6 +50,8 @@ static uint32_t get_free_heap() {
|
|||
return rp2040.getFreeHeap();
|
||||
#elif defined(USE_LIBRETINY)
|
||||
return lt_heap_get_free();
|
||||
#elif defined(USE_HOST)
|
||||
return INT_MAX;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
|
@ -7,10 +7,10 @@ namespace dfplayer {
|
|||
static const char *const TAG = "dfplayer";
|
||||
|
||||
void DFPlayer::play_folder(uint16_t folder, uint16_t file) {
|
||||
if (folder <= 10 && file <= 1000) {
|
||||
if (folder < 100 && file < 256) {
|
||||
this->ack_set_is_playing_ = true;
|
||||
this->send_cmd_(0x0F, (uint8_t) folder, (uint8_t) file);
|
||||
} else if (folder < 100 && file < 256) {
|
||||
} else if (folder <= 15 && file <= 3000) {
|
||||
this->ack_set_is_playing_ = true;
|
||||
this->send_cmd_(0x14, (((uint16_t) folder) << 12) | file);
|
||||
} else {
|
||||
|
|
|
@ -319,17 +319,19 @@ void Display::filled_regular_polygon(int x, int y, int radius, int edges, Color
|
|||
regular_polygon(x, y, radius, edges, VARIATION_POINTY_TOP, ROTATION_0_DEGREES, color, DRAWING_FILLED);
|
||||
}
|
||||
|
||||
void Display::print(int x, int y, BaseFont *font, Color color, TextAlign align, const char *text) {
|
||||
void Display::print(int x, int y, BaseFont *font, Color color, TextAlign align, const char *text, Color background) {
|
||||
int x_start, y_start;
|
||||
int width, height;
|
||||
this->get_text_bounds(x, y, text, font, align, &x_start, &y_start, &width, &height);
|
||||
font->print(x_start, y_start, this, color, text);
|
||||
font->print(x_start, y_start, this, color, text, background);
|
||||
}
|
||||
void Display::vprintf_(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, va_list arg) {
|
||||
|
||||
void Display::vprintf_(int x, int y, BaseFont *font, Color color, Color background, TextAlign align, const char *format,
|
||||
va_list arg) {
|
||||
char buffer[256];
|
||||
int ret = vsnprintf(buffer, sizeof(buffer), format, arg);
|
||||
if (ret > 0)
|
||||
this->print(x, y, font, color, align, buffer);
|
||||
this->print(x, y, font, color, align, buffer, background);
|
||||
}
|
||||
|
||||
void Display::image(int x, int y, BaseImage *image, Color color_on, Color color_off) {
|
||||
|
@ -423,8 +425,8 @@ void Display::get_text_bounds(int x, int y, const char *text, BaseFont *font, Te
|
|||
break;
|
||||
}
|
||||
}
|
||||
void Display::print(int x, int y, BaseFont *font, Color color, const char *text) {
|
||||
this->print(x, y, font, color, TextAlign::TOP_LEFT, text);
|
||||
void Display::print(int x, int y, BaseFont *font, Color color, const char *text, Color background) {
|
||||
this->print(x, y, font, color, TextAlign::TOP_LEFT, text, background);
|
||||
}
|
||||
void Display::print(int x, int y, BaseFont *font, TextAlign align, const char *text) {
|
||||
this->print(x, y, font, COLOR_ON, align, text);
|
||||
|
@ -432,28 +434,35 @@ void Display::print(int x, int y, BaseFont *font, TextAlign align, const char *t
|
|||
void Display::print(int x, int y, BaseFont *font, const char *text) {
|
||||
this->print(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, text);
|
||||
}
|
||||
void Display::printf(int x, int y, BaseFont *font, Color color, Color background, TextAlign align, const char *format,
|
||||
...) {
|
||||
va_list arg;
|
||||
va_start(arg, format);
|
||||
this->vprintf_(x, y, font, color, background, align, format, arg);
|
||||
va_end(arg);
|
||||
}
|
||||
void Display::printf(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, ...) {
|
||||
va_list arg;
|
||||
va_start(arg, format);
|
||||
this->vprintf_(x, y, font, color, align, format, arg);
|
||||
this->vprintf_(x, y, font, color, COLOR_OFF, align, format, arg);
|
||||
va_end(arg);
|
||||
}
|
||||
void Display::printf(int x, int y, BaseFont *font, Color color, const char *format, ...) {
|
||||
va_list arg;
|
||||
va_start(arg, format);
|
||||
this->vprintf_(x, y, font, color, TextAlign::TOP_LEFT, format, arg);
|
||||
this->vprintf_(x, y, font, color, COLOR_OFF, TextAlign::TOP_LEFT, format, arg);
|
||||
va_end(arg);
|
||||
}
|
||||
void Display::printf(int x, int y, BaseFont *font, TextAlign align, const char *format, ...) {
|
||||
va_list arg;
|
||||
va_start(arg, format);
|
||||
this->vprintf_(x, y, font, COLOR_ON, align, format, arg);
|
||||
this->vprintf_(x, y, font, COLOR_ON, COLOR_OFF, align, format, arg);
|
||||
va_end(arg);
|
||||
}
|
||||
void Display::printf(int x, int y, BaseFont *font, const char *format, ...) {
|
||||
va_list arg;
|
||||
va_start(arg, format);
|
||||
this->vprintf_(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, format, arg);
|
||||
this->vprintf_(x, y, font, COLOR_ON, COLOR_OFF, TextAlign::TOP_LEFT, format, arg);
|
||||
va_end(arg);
|
||||
}
|
||||
void Display::set_writer(display_writer_t &&writer) { this->writer_ = writer; }
|
||||
|
|
|
@ -200,7 +200,7 @@ class BaseImage {
|
|||
|
||||
class BaseFont {
|
||||
public:
|
||||
virtual void print(int x, int y, Display *display, Color color, const char *text) = 0;
|
||||
virtual void print(int x, int y, Display *display, Color color, const char *text, Color background) = 0;
|
||||
virtual void measure(const char *str, int *width, int *x_offset, int *baseline, int *height) = 0;
|
||||
};
|
||||
|
||||
|
@ -327,8 +327,10 @@ class Display : public PollingComponent {
|
|||
* @param color The color to draw the text with.
|
||||
* @param align The alignment of the text.
|
||||
* @param text The text to draw.
|
||||
* @param background When using multi-bit (anti-aliased) fonts, blend this background color into pixels
|
||||
*/
|
||||
void print(int x, int y, BaseFont *font, Color color, TextAlign align, const char *text);
|
||||
void print(int x, int y, BaseFont *font, Color color, TextAlign align, const char *text,
|
||||
Color background = COLOR_OFF);
|
||||
|
||||
/** Print `text` with the top left at [x,y] with `font`.
|
||||
*
|
||||
|
@ -337,8 +339,9 @@ class Display : public PollingComponent {
|
|||
* @param font The font to draw the text with.
|
||||
* @param color The color to draw the text with.
|
||||
* @param text The text to draw.
|
||||
* @param background When using multi-bit (anti-aliased) fonts, blend this background color into pixels
|
||||
*/
|
||||
void print(int x, int y, BaseFont *font, Color color, const char *text);
|
||||
void print(int x, int y, BaseFont *font, Color color, const char *text, Color background = COLOR_OFF);
|
||||
|
||||
/** Print `text` with the anchor point at [x,y] with `font`.
|
||||
*
|
||||
|
@ -359,6 +362,20 @@ class Display : public PollingComponent {
|
|||
*/
|
||||
void print(int x, int y, BaseFont *font, const char *text);
|
||||
|
||||
/** Evaluate the printf-format `format` and print the result with the anchor point at [x,y] with `font`.
|
||||
*
|
||||
* @param x The x coordinate of the text alignment anchor point.
|
||||
* @param y The y coordinate of the text alignment anchor point.
|
||||
* @param font The font to draw the text with.
|
||||
* @param color The color to draw the text with.
|
||||
* @param background The background color to use for anti-aliasing
|
||||
* @param align The alignment of the text.
|
||||
* @param format The format to use.
|
||||
* @param ... The arguments to use for the text formatting.
|
||||
*/
|
||||
void printf(int x, int y, BaseFont *font, Color color, Color background, TextAlign align, const char *format, ...)
|
||||
__attribute__((format(printf, 8, 9)));
|
||||
|
||||
/** Evaluate the printf-format `format` and print the result with the anchor point at [x,y] with `font`.
|
||||
*
|
||||
* @param x The x coordinate of the text alignment anchor point.
|
||||
|
@ -610,7 +627,8 @@ class Display : public PollingComponent {
|
|||
protected:
|
||||
bool clamp_x_(int x, int w, int &min_x, int &max_x);
|
||||
bool clamp_y_(int y, int h, int &min_y, int &max_y);
|
||||
void vprintf_(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, va_list arg);
|
||||
void vprintf_(int x, int y, BaseFont *font, Color color, Color background, TextAlign align, const char *format,
|
||||
va_list arg);
|
||||
|
||||
void do_update_();
|
||||
void clear_clipping_();
|
||||
|
|
|
@ -293,7 +293,7 @@ void ESP32ImprovComponent::process_incoming_data_() {
|
|||
this->connecting_sta_ = sta;
|
||||
|
||||
wifi::global_wifi_component->set_sta(sta);
|
||||
wifi::global_wifi_component->start_scanning();
|
||||
wifi::global_wifi_component->start_connecting(sta, false);
|
||||
this->set_state_(improv::STATE_PROVISIONING);
|
||||
ESP_LOGD(TAG, "Received Improv wifi settings ssid=%s, password=" LOG_SECRET("%s"), command.ssid.c_str(),
|
||||
command.password.c_str());
|
||||
|
|
|
@ -15,7 +15,10 @@ from esphome.const import (
|
|||
CONF_SPEED_COMMAND_TOPIC,
|
||||
CONF_SPEED_STATE_TOPIC,
|
||||
CONF_OFF_SPEED_CYCLE,
|
||||
CONF_ON_DIRECTION_SET,
|
||||
CONF_ON_OSCILLATING_SET,
|
||||
CONF_ON_SPEED_SET,
|
||||
CONF_ON_STATE,
|
||||
CONF_ON_TURN_OFF,
|
||||
CONF_ON_TURN_ON,
|
||||
CONF_ON_PRESET_SET,
|
||||
|
@ -55,11 +58,22 @@ TurnOffAction = fan_ns.class_("TurnOffAction", automation.Action)
|
|||
ToggleAction = fan_ns.class_("ToggleAction", automation.Action)
|
||||
CycleSpeedAction = fan_ns.class_("CycleSpeedAction", automation.Action)
|
||||
|
||||
FanStateTrigger = fan_ns.class_(
|
||||
"FanStateTrigger", automation.Trigger.template(Fan.operator("ptr"))
|
||||
)
|
||||
FanTurnOnTrigger = fan_ns.class_("FanTurnOnTrigger", automation.Trigger.template())
|
||||
FanTurnOffTrigger = fan_ns.class_("FanTurnOffTrigger", automation.Trigger.template())
|
||||
FanSpeedSetTrigger = fan_ns.class_("FanSpeedSetTrigger", automation.Trigger.template())
|
||||
FanDirectionSetTrigger = fan_ns.class_(
|
||||
"FanDirectionSetTrigger", automation.Trigger.template(FanDirection)
|
||||
)
|
||||
FanOscillatingSetTrigger = fan_ns.class_(
|
||||
"FanOscillatingSetTrigger", automation.Trigger.template(cg.bool_)
|
||||
)
|
||||
FanSpeedSetTrigger = fan_ns.class_(
|
||||
"FanSpeedSetTrigger", automation.Trigger.template(cg.int_)
|
||||
)
|
||||
FanPresetSetTrigger = fan_ns.class_(
|
||||
"FanPresetSetTrigger", automation.Trigger.template()
|
||||
"FanPresetSetTrigger", automation.Trigger.template(cg.std_string)
|
||||
)
|
||||
|
||||
FanIsOnCondition = fan_ns.class_("FanIsOnCondition", automation.Condition.template())
|
||||
|
@ -90,6 +104,11 @@ FAN_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).exte
|
|||
cv.Optional(CONF_SPEED_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.subscribe_topic
|
||||
),
|
||||
cv.Optional(CONF_ON_STATE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanStateTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_TURN_ON): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanTurnOnTrigger),
|
||||
|
@ -100,6 +119,16 @@ FAN_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).exte
|
|||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanTurnOffTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_DIRECTION_SET): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanDirectionSetTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_OSCILLATING_SET): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanOscillatingSetTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_SPEED_SET): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanSpeedSetTrigger),
|
||||
|
@ -186,18 +215,27 @@ async def setup_fan_core_(var, config):
|
|||
mqtt_.set_custom_speed_command_topic(config[CONF_SPEED_COMMAND_TOPIC])
|
||||
)
|
||||
|
||||
for conf in config.get(CONF_ON_STATE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [(Fan.operator("ptr"), "x")], conf)
|
||||
for conf in config.get(CONF_ON_TURN_ON, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
for conf in config.get(CONF_ON_TURN_OFF, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
for conf in config.get(CONF_ON_DIRECTION_SET, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [(FanDirection, "x")], conf)
|
||||
for conf in config.get(CONF_ON_OSCILLATING_SET, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [(cg.bool_, "x")], conf)
|
||||
for conf in config.get(CONF_ON_SPEED_SET, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
await automation.build_automation(trigger, [(cg.int_, "x")], conf)
|
||||
for conf in config.get(CONF_ON_PRESET_SET, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
await automation.build_automation(trigger, [(cg.std_string, "x")], conf)
|
||||
|
||||
|
||||
async def register_fan(var, config):
|
||||
|
|
|
@ -111,6 +111,13 @@ template<typename... Ts> class FanIsOffCondition : public Condition<Ts...> {
|
|||
Fan *state_;
|
||||
};
|
||||
|
||||
class FanStateTrigger : public Trigger<Fan *> {
|
||||
public:
|
||||
FanStateTrigger(Fan *state) {
|
||||
state->add_on_state_callback([this, state]() { this->trigger(state); });
|
||||
}
|
||||
};
|
||||
|
||||
class FanTurnOnTrigger : public Trigger<> {
|
||||
public:
|
||||
FanTurnOnTrigger(Fan *state) {
|
||||
|
@ -147,15 +154,51 @@ class FanTurnOffTrigger : public Trigger<> {
|
|||
bool last_on_;
|
||||
};
|
||||
|
||||
class FanSpeedSetTrigger : public Trigger<> {
|
||||
class FanDirectionSetTrigger : public Trigger<FanDirection> {
|
||||
public:
|
||||
FanDirectionSetTrigger(Fan *state) {
|
||||
state->add_on_state_callback([this, state]() {
|
||||
auto direction = state->direction;
|
||||
auto should_trigger = direction != this->last_direction_;
|
||||
this->last_direction_ = direction;
|
||||
if (should_trigger) {
|
||||
this->trigger(direction);
|
||||
}
|
||||
});
|
||||
this->last_direction_ = state->direction;
|
||||
}
|
||||
|
||||
protected:
|
||||
FanDirection last_direction_;
|
||||
};
|
||||
|
||||
class FanOscillatingSetTrigger : public Trigger<bool> {
|
||||
public:
|
||||
FanOscillatingSetTrigger(Fan *state) {
|
||||
state->add_on_state_callback([this, state]() {
|
||||
auto oscillating = state->oscillating;
|
||||
auto should_trigger = oscillating != this->last_oscillating_;
|
||||
this->last_oscillating_ = oscillating;
|
||||
if (should_trigger) {
|
||||
this->trigger(oscillating);
|
||||
}
|
||||
});
|
||||
this->last_oscillating_ = state->oscillating;
|
||||
}
|
||||
|
||||
protected:
|
||||
bool last_oscillating_;
|
||||
};
|
||||
|
||||
class FanSpeedSetTrigger : public Trigger<int> {
|
||||
public:
|
||||
FanSpeedSetTrigger(Fan *state) {
|
||||
state->add_on_state_callback([this, state]() {
|
||||
auto speed = state->speed;
|
||||
auto should_trigger = speed != !this->last_speed_;
|
||||
auto should_trigger = speed != this->last_speed_;
|
||||
this->last_speed_ = speed;
|
||||
if (should_trigger) {
|
||||
this->trigger();
|
||||
this->trigger(speed);
|
||||
}
|
||||
});
|
||||
this->last_speed_ = state->speed;
|
||||
|
@ -165,7 +208,7 @@ class FanSpeedSetTrigger : public Trigger<> {
|
|||
int last_speed_;
|
||||
};
|
||||
|
||||
class FanPresetSetTrigger : public Trigger<> {
|
||||
class FanPresetSetTrigger : public Trigger<std::string> {
|
||||
public:
|
||||
FanPresetSetTrigger(Fan *state) {
|
||||
state->add_on_state_callback([this, state]() {
|
||||
|
@ -173,7 +216,7 @@ class FanPresetSetTrigger : public Trigger<> {
|
|||
auto should_trigger = preset_mode != this->last_preset_mode_;
|
||||
this->last_preset_mode_ = preset_mode;
|
||||
if (should_trigger) {
|
||||
this->trigger();
|
||||
this->trigger(preset_mode);
|
||||
}
|
||||
});
|
||||
this->last_preset_mode_ = state->preset_mode;
|
||||
|
|
|
@ -25,12 +25,14 @@ from esphome.const import (
|
|||
CONF_TRIGGER_ID,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@OnFreund", "@loongyh"]
|
||||
CODEOWNERS = ["@OnFreund", "@loongyh", "@alexborro"]
|
||||
DEPENDENCIES = ["uart"]
|
||||
AUTO_LOAD = ["binary_sensor", "sensor"]
|
||||
MULTI_CONF = True
|
||||
|
||||
CONF_FINGERPRINT_GROW_ID = "fingerprint_grow_id"
|
||||
CONF_SENSOR_POWER_PIN = "sensor_power_pin"
|
||||
CONF_IDLE_PERIOD_TO_SLEEP = "idle_period_to_sleep"
|
||||
|
||||
fingerprint_grow_ns = cg.esphome_ns.namespace("fingerprint_grow")
|
||||
FingerprintGrowComponent = fingerprint_grow_ns.class_(
|
||||
|
@ -102,11 +104,26 @@ AURA_LED_COLORS = {
|
|||
}
|
||||
validate_aura_led_colors = cv.enum(AURA_LED_COLORS, upper=True)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
|
||||
def validate(config):
|
||||
if CONF_SENSOR_POWER_PIN in config and CONF_SENSING_PIN not in config:
|
||||
raise cv.Invalid("You cannot use the Sensor Power Pin without a Sensing Pin")
|
||||
if CONF_IDLE_PERIOD_TO_SLEEP in config and CONF_SENSOR_POWER_PIN not in config:
|
||||
raise cv.Invalid(
|
||||
"You cannot have an Idle Period to Sleep without a Sensor Power Pin"
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(FingerprintGrowComponent),
|
||||
cv.Optional(CONF_SENSING_PIN): pins.gpio_input_pin_schema,
|
||||
cv.Optional(CONF_SENSOR_POWER_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Optional(
|
||||
CONF_IDLE_PERIOD_TO_SLEEP
|
||||
): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_PASSWORD): cv.uint32_t,
|
||||
cv.Optional(CONF_NEW_PASSWORD): cv.uint32_t,
|
||||
cv.Optional(CONF_ON_FINGER_SCAN_START): automation.validate_automation(
|
||||
|
@ -168,7 +185,8 @@ CONFIG_SCHEMA = (
|
|||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("500ms"))
|
||||
.extend(uart.UART_DEVICE_SCHEMA)
|
||||
.extend(uart.UART_DEVICE_SCHEMA),
|
||||
validate,
|
||||
)
|
||||
|
||||
|
||||
|
@ -188,6 +206,14 @@ async def to_code(config):
|
|||
sensing_pin = await cg.gpio_pin_expression(config[CONF_SENSING_PIN])
|
||||
cg.add(var.set_sensing_pin(sensing_pin))
|
||||
|
||||
if CONF_SENSOR_POWER_PIN in config:
|
||||
sensor_power_pin = await cg.gpio_pin_expression(config[CONF_SENSOR_POWER_PIN])
|
||||
cg.add(var.set_sensor_power_pin(sensor_power_pin))
|
||||
|
||||
if CONF_IDLE_PERIOD_TO_SLEEP in config:
|
||||
idle_period_to_sleep_ms = config[CONF_IDLE_PERIOD_TO_SLEEP]
|
||||
cg.add(var.set_idle_period_to_sleep_ms(idle_period_to_sleep_ms))
|
||||
|
||||
for conf in config.get(CONF_ON_FINGER_SCAN_START, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
|
|
|
@ -16,9 +16,14 @@ void FingerprintGrowComponent::update() {
|
|||
}
|
||||
|
||||
if (this->has_sensing_pin_) {
|
||||
// A finger touch results in a low level (digital_read() == false)
|
||||
if (this->sensing_pin_->digital_read()) {
|
||||
ESP_LOGV(TAG, "No touch sensing");
|
||||
this->waiting_removal_ = false;
|
||||
if ((this->enrollment_image_ == 0) && // Not in enrolment process
|
||||
(millis() - this->last_transfer_ms_ > this->idle_period_to_sleep_ms_) && (this->is_sensor_awake_)) {
|
||||
this->sensor_sleep_();
|
||||
}
|
||||
return;
|
||||
} else if (!this->waiting_removal_) {
|
||||
this->finger_scan_start_callback_.call();
|
||||
|
@ -53,7 +58,29 @@ void FingerprintGrowComponent::update() {
|
|||
|
||||
void FingerprintGrowComponent::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up Grow Fingerprint Reader...");
|
||||
|
||||
this->has_sensing_pin_ = (this->sensing_pin_ != nullptr);
|
||||
this->has_power_pin_ = (this->sensor_power_pin_ != nullptr);
|
||||
|
||||
// Call pins setup, so we effectively apply the config generated from the yaml file.
|
||||
if (this->has_sensing_pin_) {
|
||||
this->sensing_pin_->setup();
|
||||
}
|
||||
if (this->has_power_pin_) {
|
||||
// Starts with output low (disabling power) to avoid glitches in the sensor
|
||||
this->sensor_power_pin_->digital_write(false);
|
||||
this->sensor_power_pin_->setup();
|
||||
|
||||
// If the user didn't specify an idle period to sleep, applies the default.
|
||||
if (this->idle_period_to_sleep_ms_ == UINT32_MAX) {
|
||||
this->idle_period_to_sleep_ms_ = DEFAULT_IDLE_PERIOD_TO_SLEEP_MS;
|
||||
}
|
||||
}
|
||||
|
||||
// Place the sensor in a known (sleep/off) state and sync internal var state.
|
||||
this->sensor_sleep_();
|
||||
delay(20); // This delay guarantees the sensor will in fact be powered power.
|
||||
|
||||
if (this->check_password_()) {
|
||||
if (this->new_password_ != -1) {
|
||||
if (this->set_password_())
|
||||
|
@ -335,7 +362,7 @@ void FingerprintGrowComponent::aura_led_control(uint8_t state, uint8_t speed, ui
|
|||
}
|
||||
}
|
||||
|
||||
uint8_t FingerprintGrowComponent::send_command_() {
|
||||
uint8_t FingerprintGrowComponent::transfer_(std::vector<uint8_t> *p_data_buffer) {
|
||||
while (this->available())
|
||||
this->read();
|
||||
this->write((uint8_t) (START_CODE >> 8));
|
||||
|
@ -346,12 +373,12 @@ uint8_t FingerprintGrowComponent::send_command_() {
|
|||
this->write(this->address_[3]);
|
||||
this->write(COMMAND);
|
||||
|
||||
uint16_t wire_length = this->data_.size() + 2;
|
||||
uint16_t wire_length = p_data_buffer->size() + 2;
|
||||
this->write((uint8_t) (wire_length >> 8));
|
||||
this->write((uint8_t) (wire_length & 0xFF));
|
||||
|
||||
uint16_t sum = ((wire_length) >> 8) + ((wire_length) &0xFF) + COMMAND;
|
||||
for (auto data : this->data_) {
|
||||
for (auto data : *p_data_buffer) {
|
||||
this->write(data);
|
||||
sum += data;
|
||||
}
|
||||
|
@ -359,7 +386,7 @@ uint8_t FingerprintGrowComponent::send_command_() {
|
|||
this->write((uint8_t) (sum >> 8));
|
||||
this->write((uint8_t) (sum & 0xFF));
|
||||
|
||||
this->data_.clear();
|
||||
p_data_buffer->clear();
|
||||
|
||||
uint8_t byte;
|
||||
uint16_t idx = 0, length = 0;
|
||||
|
@ -369,7 +396,9 @@ uint8_t FingerprintGrowComponent::send_command_() {
|
|||
delay(1);
|
||||
continue;
|
||||
}
|
||||
|
||||
byte = this->read();
|
||||
|
||||
switch (idx) {
|
||||
case 0:
|
||||
if (byte != (uint8_t) (START_CODE >> 8))
|
||||
|
@ -403,9 +432,9 @@ uint8_t FingerprintGrowComponent::send_command_() {
|
|||
length |= byte;
|
||||
break;
|
||||
default:
|
||||
this->data_.push_back(byte);
|
||||
p_data_buffer->push_back(byte);
|
||||
if ((idx - 8) == length) {
|
||||
switch (this->data_[0]) {
|
||||
switch ((*p_data_buffer)[0]) {
|
||||
case OK:
|
||||
case NO_FINGER:
|
||||
case IMAGE_FAIL:
|
||||
|
@ -425,38 +454,122 @@ uint8_t FingerprintGrowComponent::send_command_() {
|
|||
ESP_LOGE(TAG, "Reader failed to process request");
|
||||
break;
|
||||
default:
|
||||
ESP_LOGE(TAG, "Unknown response received from reader: %d", this->data_[0]);
|
||||
ESP_LOGE(TAG, "Unknown response received from reader: 0x%.2X", (*p_data_buffer)[0]);
|
||||
break;
|
||||
}
|
||||
return this->data_[0];
|
||||
this->last_transfer_ms_ = millis();
|
||||
return (*p_data_buffer)[0];
|
||||
}
|
||||
break;
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
ESP_LOGE(TAG, "No response received from reader");
|
||||
this->data_[0] = TIMEOUT;
|
||||
(*p_data_buffer)[0] = TIMEOUT;
|
||||
this->last_transfer_ms_ = millis();
|
||||
return TIMEOUT;
|
||||
}
|
||||
|
||||
uint8_t FingerprintGrowComponent::send_command_() {
|
||||
this->sensor_wakeup_();
|
||||
return this->transfer_(&this->data_);
|
||||
}
|
||||
|
||||
void FingerprintGrowComponent::sensor_wakeup_() {
|
||||
// Immediately return if there is no power pin or the sensor is already on
|
||||
if ((!this->has_power_pin_) || (this->is_sensor_awake_))
|
||||
return;
|
||||
|
||||
this->sensor_power_pin_->digital_write(true);
|
||||
this->is_sensor_awake_ = true;
|
||||
|
||||
uint8_t byte = TIMEOUT;
|
||||
|
||||
// Wait for the byte HANDSHAKE_SIGN from the sensor meaning it is operational.
|
||||
for (uint16_t timer = 0; timer < WAIT_FOR_WAKE_UP_MS; timer++) {
|
||||
if (this->available() > 0) {
|
||||
byte = this->read();
|
||||
|
||||
/* If the received byte is zero, the UART probably misinterpreted a raising edge on
|
||||
* the RX pin due the power up as byte "zero" - I verified this behaviour using
|
||||
* the esp32-arduino lib. So here we just ignore this fake byte.
|
||||
*/
|
||||
if (byte != 0)
|
||||
break;
|
||||
}
|
||||
delay(1);
|
||||
}
|
||||
|
||||
/* Lets check if the received by is a HANDSHAKE_SIGN, otherwise log an error
|
||||
* message and try to continue on the best effort.
|
||||
*/
|
||||
if (byte == HANDSHAKE_SIGN) {
|
||||
ESP_LOGD(TAG, "Sensor has woken up!");
|
||||
} else if (byte == TIMEOUT) {
|
||||
ESP_LOGE(TAG, "Timed out waiting for sensor wake-up");
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Received wrong byte from the sensor during wake-up: 0x%.2X", byte);
|
||||
}
|
||||
|
||||
/* Next step, we must authenticate with the password. We cannot call check_password_ here
|
||||
* neither use data_ to store the command because it might be already in use by the caller
|
||||
* of send_command_()
|
||||
*/
|
||||
std::vector<uint8_t> buffer = {VERIFY_PASSWORD, (uint8_t) (this->password_ >> 24), (uint8_t) (this->password_ >> 16),
|
||||
(uint8_t) (this->password_ >> 8), (uint8_t) (this->password_ & 0xFF)};
|
||||
|
||||
if (this->transfer_(&buffer) != OK) {
|
||||
ESP_LOGE(TAG, "Wrong password");
|
||||
}
|
||||
}
|
||||
|
||||
void FingerprintGrowComponent::sensor_sleep_() {
|
||||
// Immediately return if the power pin feature is not implemented
|
||||
if (!this->has_power_pin_)
|
||||
return;
|
||||
|
||||
this->sensor_power_pin_->digital_write(false);
|
||||
this->is_sensor_awake_ = false;
|
||||
ESP_LOGD(TAG, "Fingerprint sensor is now in sleep mode.");
|
||||
}
|
||||
|
||||
void FingerprintGrowComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "GROW_FINGERPRINT_READER:");
|
||||
ESP_LOGCONFIG(TAG, " System Identifier Code: 0x%.4X", this->system_identifier_code_);
|
||||
ESP_LOGCONFIG(TAG, " Touch Sensing Pin: %s",
|
||||
this->has_sensing_pin_ ? this->sensing_pin_->dump_summary().c_str() : "None");
|
||||
ESP_LOGCONFIG(TAG, " Sensor Power Pin: %s",
|
||||
this->has_power_pin_ ? this->sensor_power_pin_->dump_summary().c_str() : "None");
|
||||
if (this->idle_period_to_sleep_ms_ < UINT32_MAX) {
|
||||
ESP_LOGCONFIG(TAG, " Idle Period to Sleep: %u ms", this->idle_period_to_sleep_ms_);
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, " Idle Period to Sleep: Never");
|
||||
}
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
if (this->fingerprint_count_sensor_) {
|
||||
LOG_SENSOR(" ", "Fingerprint Count", this->fingerprint_count_sensor_);
|
||||
ESP_LOGCONFIG(TAG, " Current Value: %d", (uint16_t) this->fingerprint_count_sensor_->get_state());
|
||||
}
|
||||
if (this->status_sensor_) {
|
||||
LOG_SENSOR(" ", "Status", this->status_sensor_);
|
||||
ESP_LOGCONFIG(TAG, " Current Value: %d", (uint8_t) this->status_sensor_->get_state());
|
||||
}
|
||||
if (this->capacity_sensor_) {
|
||||
LOG_SENSOR(" ", "Capacity", this->capacity_sensor_);
|
||||
ESP_LOGCONFIG(TAG, " Current Value: %d", (uint16_t) this->capacity_sensor_->get_state());
|
||||
}
|
||||
if (this->security_level_sensor_) {
|
||||
LOG_SENSOR(" ", "Security Level", this->security_level_sensor_);
|
||||
ESP_LOGCONFIG(TAG, " Current Value: %d", (uint8_t) this->security_level_sensor_->get_state());
|
||||
}
|
||||
if (this->last_finger_id_sensor_) {
|
||||
LOG_SENSOR(" ", "Last Finger ID", this->last_finger_id_sensor_);
|
||||
ESP_LOGCONFIG(TAG, " Current Value: %d", (uint32_t) this->last_finger_id_sensor_->get_state());
|
||||
}
|
||||
if (this->last_confidence_sensor_) {
|
||||
LOG_SENSOR(" ", "Last Confidence", this->last_confidence_sensor_);
|
||||
ESP_LOGCONFIG(TAG, " Current Value: %d", (uint32_t) this->last_confidence_sensor_->get_state());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace fingerprint_grow
|
||||
|
|
|
@ -15,6 +15,11 @@ static const uint16_t START_CODE = 0xEF01;
|
|||
|
||||
static const uint16_t ENROLLMENT_SLOT_UNUSED = 0xFFFF;
|
||||
|
||||
// The datasheet says a max wake up time of of 200ms.
|
||||
static const uint8_t WAIT_FOR_WAKE_UP_MS = 200;
|
||||
|
||||
static const uint32_t DEFAULT_IDLE_PERIOD_TO_SLEEP_MS = 5000;
|
||||
|
||||
enum GrowPacketType {
|
||||
COMMAND = 0x01,
|
||||
DATA = 0x02,
|
||||
|
@ -63,6 +68,7 @@ enum GrowResponse {
|
|||
INVALID_IMAGE = 0x15,
|
||||
FLASH_ERR = 0x18,
|
||||
INVALID_REG = 0x1A,
|
||||
HANDSHAKE_SIGN = 0x55,
|
||||
BAD_PACKET = 0xFE,
|
||||
TIMEOUT = 0xFF,
|
||||
};
|
||||
|
@ -99,8 +105,10 @@ class FingerprintGrowComponent : public PollingComponent, public uart::UARTDevic
|
|||
this->address_[3] = (uint8_t) (address & 0xFF);
|
||||
}
|
||||
void set_sensing_pin(GPIOPin *sensing_pin) { this->sensing_pin_ = sensing_pin; }
|
||||
void set_sensor_power_pin(GPIOPin *sensor_power_pin) { this->sensor_power_pin_ = sensor_power_pin; }
|
||||
void set_password(uint32_t password) { this->password_ = password; }
|
||||
void set_new_password(uint32_t new_password) { this->new_password_ = new_password; }
|
||||
void set_idle_period_to_sleep_ms(uint32_t period_ms) { this->idle_period_to_sleep_ms_ = period_ms; }
|
||||
void set_fingerprint_count_sensor(sensor::Sensor *fingerprint_count_sensor) {
|
||||
this->fingerprint_count_sensor_ = fingerprint_count_sensor;
|
||||
}
|
||||
|
@ -160,7 +168,10 @@ class FingerprintGrowComponent : public PollingComponent, public uart::UARTDevic
|
|||
bool set_password_();
|
||||
bool get_parameters_();
|
||||
void get_fingerprint_count_();
|
||||
uint8_t transfer_(std::vector<uint8_t> *p_data_buffer);
|
||||
uint8_t send_command_();
|
||||
void sensor_wakeup_();
|
||||
void sensor_sleep_();
|
||||
|
||||
std::vector<uint8_t> data_ = {};
|
||||
uint8_t address_[4] = {0xFF, 0xFF, 0xFF, 0xFF};
|
||||
|
@ -168,14 +179,19 @@ class FingerprintGrowComponent : public PollingComponent, public uart::UARTDevic
|
|||
uint32_t password_ = 0x0;
|
||||
uint32_t new_password_ = -1;
|
||||
GPIOPin *sensing_pin_{nullptr};
|
||||
GPIOPin *sensor_power_pin_{nullptr};
|
||||
uint8_t enrollment_image_ = 0;
|
||||
uint16_t enrollment_slot_ = ENROLLMENT_SLOT_UNUSED;
|
||||
uint8_t enrollment_buffers_ = 5;
|
||||
bool waiting_removal_ = false;
|
||||
bool has_sensing_pin_ = false;
|
||||
bool has_power_pin_ = false;
|
||||
bool is_sensor_awake_ = false;
|
||||
uint32_t last_transfer_ms_ = 0;
|
||||
uint32_t last_aura_led_control_ = 0;
|
||||
uint16_t last_aura_led_duration_ = 0;
|
||||
uint16_t system_identifier_code_ = 0;
|
||||
uint32_t idle_period_to_sleep_ms_ = UINT32_MAX;
|
||||
sensor::Sensor *fingerprint_count_sensor_{nullptr};
|
||||
sensor::Sensor *status_sensor_{nullptr};
|
||||
sensor::Sensor *capacity_sensor_{nullptr};
|
||||
|
|
|
@ -10,7 +10,10 @@ import requests
|
|||
from esphome import core
|
||||
import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
from esphome.helpers import copy_file_if_changed
|
||||
from esphome.helpers import (
|
||||
copy_file_if_changed,
|
||||
cpp_string_escape,
|
||||
)
|
||||
from esphome.const import (
|
||||
CONF_FAMILY,
|
||||
CONF_FILE,
|
||||
|
@ -22,26 +25,29 @@ from esphome.const import (
|
|||
CONF_PATH,
|
||||
CONF_WEIGHT,
|
||||
)
|
||||
from esphome.core import CORE, HexInt
|
||||
|
||||
from esphome.core import (
|
||||
CORE,
|
||||
HexInt,
|
||||
)
|
||||
|
||||
DOMAIN = "font"
|
||||
DEPENDENCIES = ["display"]
|
||||
MULTI_CONF = True
|
||||
|
||||
CODEOWNERS = ["@esphome/core", "@clydebarrow"]
|
||||
|
||||
font_ns = cg.esphome_ns.namespace("font")
|
||||
|
||||
Font = font_ns.class_("Font")
|
||||
Glyph = font_ns.class_("Glyph")
|
||||
GlyphData = font_ns.struct("GlyphData")
|
||||
|
||||
CONF_BPP = "bpp"
|
||||
CONF_EXTRAS = "extras"
|
||||
CONF_FONTS = "fonts"
|
||||
|
||||
def validate_glyphs(value):
|
||||
if isinstance(value, list):
|
||||
value = cv.Schema([cv.string])(value)
|
||||
value = cv.Schema([cv.string])(list(value))
|
||||
|
||||
def comparator(x, y):
|
||||
def glyph_comparator(x, y):
|
||||
x_ = x.encode("utf-8")
|
||||
y_ = y.encode("utf-8")
|
||||
|
||||
|
@ -57,10 +63,37 @@ def validate_glyphs(value):
|
|||
return 1
|
||||
raise cv.Invalid(f"Found duplicate glyph {x}")
|
||||
|
||||
value.sort(key=functools.cmp_to_key(comparator))
|
||||
|
||||
def validate_glyphs(value):
|
||||
if isinstance(value, list):
|
||||
value = cv.Schema([cv.string])(value)
|
||||
value = cv.Schema([cv.string])(list(value))
|
||||
|
||||
value.sort(key=functools.cmp_to_key(glyph_comparator))
|
||||
return value
|
||||
|
||||
|
||||
font_map = {}
|
||||
|
||||
|
||||
def merge_glyphs(config):
|
||||
glyphs = []
|
||||
glyphs.extend(config[CONF_GLYPHS])
|
||||
font_list = [(EFont(config[CONF_FILE], config[CONF_SIZE], config[CONF_GLYPHS]))]
|
||||
if extras := config.get(CONF_EXTRAS):
|
||||
extra_fonts = list(
|
||||
map(
|
||||
lambda x: EFont(x[CONF_FILE], config[CONF_SIZE], x[CONF_GLYPHS]), extras
|
||||
)
|
||||
)
|
||||
font_list.extend(extra_fonts)
|
||||
for extra in extras:
|
||||
glyphs.extend(extra[CONF_GLYPHS])
|
||||
validate_glyphs(glyphs)
|
||||
font_map[config[CONF_ID]] = font_list
|
||||
return config
|
||||
|
||||
|
||||
def validate_pillow_installed(value):
|
||||
try:
|
||||
import PIL
|
||||
|
@ -79,16 +112,16 @@ def validate_pillow_installed(value):
|
|||
return value
|
||||
|
||||
|
||||
FONT_EXTENSIONS = (".ttf", ".woff", ".otf")
|
||||
|
||||
|
||||
def validate_truetype_file(value):
|
||||
if value.endswith(".zip"): # for Google Fonts downloads
|
||||
if value.lower().endswith(".zip"): # for Google Fonts downloads
|
||||
raise cv.Invalid(
|
||||
f"Please unzip the font archive '{value}' first and then use the .ttf files inside."
|
||||
)
|
||||
if not value.endswith(".ttf"):
|
||||
raise cv.Invalid(
|
||||
"Only truetype (.ttf) files are supported. Please make sure you're "
|
||||
"using the correct format or rename the extension to .ttf"
|
||||
)
|
||||
if not any(map(value.lower().endswith, FONT_EXTENSIONS)):
|
||||
raise cv.Invalid(f"Only {FONT_EXTENSIONS} files are supported.")
|
||||
return cv.file_(value)
|
||||
|
||||
|
||||
|
@ -233,7 +266,6 @@ def _file_schema(value):
|
|||
|
||||
FILE_SCHEMA = cv.Schema(_file_schema)
|
||||
|
||||
|
||||
DEFAULT_GLYPHS = (
|
||||
' !"%()+=,-.:/?0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz°'
|
||||
)
|
||||
|
@ -245,12 +277,22 @@ FONT_SCHEMA = cv.Schema(
|
|||
cv.Required(CONF_FILE): FILE_SCHEMA,
|
||||
cv.Optional(CONF_GLYPHS, default=DEFAULT_GLYPHS): validate_glyphs,
|
||||
cv.Optional(CONF_SIZE, default=20): cv.int_range(min=1),
|
||||
cv.Optional(CONF_BPP, default=1): cv.one_of(1, 2, 4, 8),
|
||||
cv.Optional(CONF_EXTRAS): cv.ensure_list(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_FILE): FILE_SCHEMA,
|
||||
cv.Required(CONF_GLYPHS): validate_glyphs,
|
||||
}
|
||||
)
|
||||
),
|
||||
cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8),
|
||||
cv.GenerateID(CONF_RAW_GLYPH_ID): cv.declare_id(GlyphData),
|
||||
}
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(validate_pillow_installed, FONT_SCHEMA)
|
||||
CONFIG_SCHEMA = cv.All(validate_pillow_installed, FONT_SCHEMA, merge_glyphs)
|
||||
|
||||
|
||||
# PIL doesn't provide a consistent interface for both TrueType and bitmap
|
||||
# fonts. So, we use our own wrappers to give us the consistency that we need.
|
||||
|
@ -292,8 +334,32 @@ class BitmapFontWrapper:
|
|||
return (max_height, 0)
|
||||
|
||||
|
||||
class EFont:
|
||||
def __init__(self, file, size, glyphs):
|
||||
self.glyphs = glyphs
|
||||
ftype = file[CONF_TYPE]
|
||||
if ftype == TYPE_LOCAL_BITMAP:
|
||||
font = load_bitmap_font(CORE.relative_config_path(file[CONF_PATH]))
|
||||
elif ftype == TYPE_LOCAL:
|
||||
path = CORE.relative_config_path(file[CONF_PATH])
|
||||
font = load_ttf_font(path, size)
|
||||
elif ftype == TYPE_GFONTS:
|
||||
path = _compute_gfonts_local_path(file)
|
||||
font = load_ttf_font(path, size)
|
||||
else:
|
||||
raise cv.Invalid(f"Could not load font: unknown type: {ftype}")
|
||||
self.font = font
|
||||
self.ascent, self.descent = font.getmetrics(glyphs)
|
||||
|
||||
def has_glyph(self, glyph):
|
||||
return glyph in self.glyphs
|
||||
|
||||
|
||||
def convert_bitmap_to_pillow_font(filepath):
|
||||
from PIL import PcfFontFile, BdfFontFile
|
||||
from PIL import (
|
||||
PcfFontFile,
|
||||
BdfFontFile,
|
||||
)
|
||||
|
||||
local_bitmap_font_file = _compute_local_font_dir(filepath) / os.path.basename(
|
||||
filepath
|
||||
|
@ -347,60 +413,82 @@ def load_ttf_font(path, size):
|
|||
return TrueTypeFontWrapper(font)
|
||||
|
||||
|
||||
class GlyphInfo:
|
||||
def __init__(self, data_len, offset_x, offset_y, width, height):
|
||||
self.data_len = data_len
|
||||
self.offset_x = offset_x
|
||||
self.offset_y = offset_y
|
||||
self.width = width
|
||||
self.height = height
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
conf = config[CONF_FILE]
|
||||
if conf[CONF_TYPE] == TYPE_LOCAL_BITMAP:
|
||||
font = load_bitmap_font(CORE.relative_config_path(conf[CONF_PATH]))
|
||||
elif conf[CONF_TYPE] == TYPE_LOCAL:
|
||||
path = CORE.relative_config_path(conf[CONF_PATH])
|
||||
font = load_ttf_font(path, config[CONF_SIZE])
|
||||
elif conf[CONF_TYPE] == TYPE_GFONTS:
|
||||
path = _compute_gfonts_local_path(conf)
|
||||
font = load_ttf_font(path, config[CONF_SIZE])
|
||||
else:
|
||||
raise core.EsphomeError(f"Could not load font: unknown type: {conf[CONF_TYPE]}")
|
||||
|
||||
ascent, descent = font.getmetrics(config[CONF_GLYPHS])
|
||||
|
||||
glyph_to_font_map = {}
|
||||
font_list = font_map[config[CONF_ID]]
|
||||
glyphs = []
|
||||
for font in font_list:
|
||||
glyphs.extend(font.glyphs)
|
||||
for glyph in font.glyphs:
|
||||
glyph_to_font_map[glyph] = font
|
||||
glyphs.sort(key=functools.cmp_to_key(glyph_comparator))
|
||||
glyph_args = {}
|
||||
data = []
|
||||
for glyph in config[CONF_GLYPHS]:
|
||||
mask = font.getmask(glyph, mode="1")
|
||||
bpp = config[CONF_BPP]
|
||||
if bpp == 1:
|
||||
mode = "1"
|
||||
scale = 1
|
||||
else:
|
||||
mode = "L"
|
||||
scale = 256 // (1 << bpp)
|
||||
for glyph in glyphs:
|
||||
font = glyph_to_font_map[glyph].font
|
||||
mask = font.getmask(glyph, mode=mode)
|
||||
offset_x, offset_y = font.getoffset(glyph)
|
||||
width, height = mask.size
|
||||
width8 = ((width + 7) // 8) * 8
|
||||
glyph_data = [0] * (height * width8 // 8)
|
||||
glyph_data = [0] * ((height * width * bpp + 7) // 8)
|
||||
pos = 0
|
||||
for y in range(height):
|
||||
for x in range(width):
|
||||
if not mask.getpixel((x, y)):
|
||||
continue
|
||||
pos = x + y * width8
|
||||
pixel = mask.getpixel((x, y)) // scale
|
||||
for bit_num in range(bpp):
|
||||
if pixel & (1 << (bpp - bit_num - 1)):
|
||||
glyph_data[pos // 8] |= 0x80 >> (pos % 8)
|
||||
glyph_args[glyph] = (len(data), offset_x, offset_y, width, height)
|
||||
pos += 1
|
||||
glyph_args[glyph] = GlyphInfo(len(data), offset_x, offset_y, width, height)
|
||||
data += glyph_data
|
||||
|
||||
rhs = [HexInt(x) for x in data]
|
||||
prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs)
|
||||
|
||||
glyph_initializer = []
|
||||
for glyph in config[CONF_GLYPHS]:
|
||||
for glyph in glyphs:
|
||||
glyph_initializer.append(
|
||||
cg.StructInitializer(
|
||||
GlyphData,
|
||||
("a_char", glyph),
|
||||
(
|
||||
"a_char",
|
||||
cg.RawExpression(f"(const uint8_t *){cpp_string_escape(glyph)}"),
|
||||
),
|
||||
(
|
||||
"data",
|
||||
cg.RawExpression(f"{str(prog_arr)} + {str(glyph_args[glyph][0])}"),
|
||||
cg.RawExpression(
|
||||
f"{str(prog_arr)} + {str(glyph_args[glyph].data_len)}"
|
||||
),
|
||||
("offset_x", glyph_args[glyph][1]),
|
||||
("offset_y", glyph_args[glyph][2]),
|
||||
("width", glyph_args[glyph][3]),
|
||||
("height", glyph_args[glyph][4]),
|
||||
),
|
||||
("offset_x", glyph_args[glyph].offset_x),
|
||||
("offset_y", glyph_args[glyph].offset_y),
|
||||
("width", glyph_args[glyph].width),
|
||||
("height", glyph_args[glyph].height),
|
||||
)
|
||||
)
|
||||
|
||||
glyphs = cg.static_const_array(config[CONF_RAW_GLYPH_ID], glyph_initializer)
|
||||
|
||||
cg.new_Pvariable(
|
||||
config[CONF_ID], glyphs, len(glyph_initializer), ascent, ascent + descent
|
||||
config[CONF_ID],
|
||||
glyphs,
|
||||
len(glyph_initializer),
|
||||
font_list[0].ascent,
|
||||
font_list[0].ascent + font_list[0].descent,
|
||||
bpp,
|
||||
)
|
||||
|
|
|
@ -10,29 +10,10 @@ namespace font {
|
|||
|
||||
static const char *const TAG = "font";
|
||||
|
||||
void Glyph::draw(int x_at, int y_start, display::Display *display, Color color) const {
|
||||
int scan_x1, scan_y1, scan_width, scan_height;
|
||||
this->scan_area(&scan_x1, &scan_y1, &scan_width, &scan_height);
|
||||
|
||||
const unsigned char *data = this->glyph_data_->data;
|
||||
const int max_x = x_at + scan_x1 + scan_width;
|
||||
const int max_y = y_start + scan_y1 + scan_height;
|
||||
|
||||
for (int glyph_y = y_start + scan_y1; glyph_y < max_y; glyph_y++) {
|
||||
for (int glyph_x = x_at + scan_x1; glyph_x < max_x; data++, glyph_x += 8) {
|
||||
uint8_t pixel_data = progmem_read_byte(data);
|
||||
const int pixel_max_x = std::min(max_x, glyph_x + 8);
|
||||
|
||||
for (int pixel_x = glyph_x; pixel_x < pixel_max_x && pixel_data; pixel_x++, pixel_data <<= 1) {
|
||||
if (pixel_data & 0x80) {
|
||||
display->draw_pixel_at(pixel_x, glyph_y, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const char *Glyph::get_char() const { return this->glyph_data_->a_char; }
|
||||
bool Glyph::compare_to(const char *str) const {
|
||||
const uint8_t *Glyph::get_char() const { return this->glyph_data_->a_char; }
|
||||
// Compare the char at the string position with this char.
|
||||
// Return true if this char is less than or equal the other.
|
||||
bool Glyph::compare_to(const uint8_t *str) const {
|
||||
// 1 -> this->char_
|
||||
// 2 -> str
|
||||
for (uint32_t i = 0;; i++) {
|
||||
|
@ -48,7 +29,7 @@ bool Glyph::compare_to(const char *str) const {
|
|||
// this should not happen
|
||||
return false;
|
||||
}
|
||||
int Glyph::match_length(const char *str) const {
|
||||
int Glyph::match_length(const uint8_t *str) const {
|
||||
for (uint32_t i = 0;; i++) {
|
||||
if (this->glyph_data_->a_char[i] == '\0')
|
||||
return i;
|
||||
|
@ -65,12 +46,13 @@ void Glyph::scan_area(int *x1, int *y1, int *width, int *height) const {
|
|||
*height = this->glyph_data_->height;
|
||||
}
|
||||
|
||||
Font::Font(const GlyphData *data, int data_nr, int baseline, int height) : baseline_(baseline), height_(height) {
|
||||
Font::Font(const GlyphData *data, int data_nr, int baseline, int height, uint8_t bpp)
|
||||
: baseline_(baseline), height_(height), bpp_(bpp) {
|
||||
glyphs_.reserve(data_nr);
|
||||
for (int i = 0; i < data_nr; ++i)
|
||||
glyphs_.emplace_back(&data[i]);
|
||||
}
|
||||
int Font::match_next_glyph(const char *str, int *match_length) {
|
||||
int Font::match_next_glyph(const uint8_t *str, int *match_length) {
|
||||
int lo = 0;
|
||||
int hi = this->glyphs_.size() - 1;
|
||||
while (lo != hi) {
|
||||
|
@ -95,7 +77,7 @@ void Font::measure(const char *str, int *width, int *x_offset, int *baseline, in
|
|||
int x = 0;
|
||||
while (str[i] != '\0') {
|
||||
int match_length;
|
||||
int glyph_n = this->match_next_glyph(str + i, &match_length);
|
||||
int glyph_n = this->match_next_glyph((const uint8_t *) str + i, &match_length);
|
||||
if (glyph_n < 0) {
|
||||
// Unknown char, skip
|
||||
if (!this->get_glyphs().empty())
|
||||
|
@ -118,12 +100,13 @@ void Font::measure(const char *str, int *width, int *x_offset, int *baseline, in
|
|||
*x_offset = min_x;
|
||||
*width = x - min_x;
|
||||
}
|
||||
void Font::print(int x_start, int y_start, display::Display *display, Color color, const char *text) {
|
||||
void Font::print(int x_start, int y_start, display::Display *display, Color color, const char *text, Color background) {
|
||||
int i = 0;
|
||||
int x_at = x_start;
|
||||
int scan_x1, scan_y1, scan_width, scan_height;
|
||||
while (text[i] != '\0') {
|
||||
int match_length;
|
||||
int glyph_n = this->match_next_glyph(text + i, &match_length);
|
||||
int glyph_n = this->match_next_glyph((const uint8_t *) text + i, &match_length);
|
||||
if (glyph_n < 0) {
|
||||
// Unknown char, skip
|
||||
ESP_LOGW(TAG, "Encountered character without representation in font: '%c'", text[i]);
|
||||
|
@ -138,7 +121,41 @@ void Font::print(int x_start, int y_start, display::Display *display, Color colo
|
|||
}
|
||||
|
||||
const Glyph &glyph = this->get_glyphs()[glyph_n];
|
||||
glyph.draw(x_at, y_start, display, color);
|
||||
glyph.scan_area(&scan_x1, &scan_y1, &scan_width, &scan_height);
|
||||
|
||||
const uint8_t *data = glyph.glyph_data_->data;
|
||||
const int max_x = x_at + scan_x1 + scan_width;
|
||||
const int max_y = y_start + scan_y1 + scan_height;
|
||||
|
||||
uint8_t bitmask = 0;
|
||||
uint8_t pixel_data = 0;
|
||||
float bpp_max = (1 << this->bpp_) - 1;
|
||||
for (int glyph_y = y_start + scan_y1; glyph_y != max_y; glyph_y++) {
|
||||
for (int glyph_x = x_at + scan_x1; glyph_x != max_x; glyph_x++) {
|
||||
uint8_t pixel = 0;
|
||||
for (int bit_num = 0; bit_num != this->bpp_; bit_num++) {
|
||||
if (bitmask == 0) {
|
||||
pixel_data = progmem_read_byte(data++);
|
||||
bitmask = 0x80;
|
||||
}
|
||||
pixel <<= 1;
|
||||
if ((pixel_data & bitmask) != 0)
|
||||
pixel |= 1;
|
||||
bitmask >>= 1;
|
||||
}
|
||||
if (pixel == bpp_max) {
|
||||
display->draw_pixel_at(glyph_x, glyph_y, color);
|
||||
} else if (pixel != 0) {
|
||||
float on = (float) pixel / bpp_max;
|
||||
float off = 1.0 - on;
|
||||
Color blended;
|
||||
blended.r = color.r * on + background.r * off;
|
||||
blended.g = color.r * on + background.g * off;
|
||||
blended.b = color.r * on + background.b * off;
|
||||
display->draw_pixel_at(glyph_x, glyph_y, blended);
|
||||
}
|
||||
}
|
||||
}
|
||||
x_at += glyph.glyph_data_->width + glyph.glyph_data_->offset_x;
|
||||
|
||||
i += match_length;
|
||||
|
|
|
@ -10,7 +10,7 @@ namespace font {
|
|||
class Font;
|
||||
|
||||
struct GlyphData {
|
||||
const char *a_char;
|
||||
const uint8_t *a_char;
|
||||
const uint8_t *data;
|
||||
int offset_x;
|
||||
int offset_y;
|
||||
|
@ -22,16 +22,16 @@ class Glyph {
|
|||
public:
|
||||
Glyph(const GlyphData *data) : glyph_data_(data) {}
|
||||
|
||||
void draw(int x, int y, display::Display *display, Color color) const;
|
||||
const uint8_t *get_char() const;
|
||||
|
||||
const char *get_char() const;
|
||||
bool compare_to(const uint8_t *str) const;
|
||||
|
||||
bool compare_to(const char *str) const;
|
||||
|
||||
int match_length(const char *str) const;
|
||||
int match_length(const uint8_t *str) const;
|
||||
|
||||
void scan_area(int *x1, int *y1, int *width, int *height) const;
|
||||
|
||||
const GlyphData *get_glyph_data() const { return this->glyph_data_; }
|
||||
|
||||
protected:
|
||||
friend Font;
|
||||
|
||||
|
@ -46,14 +46,16 @@ class Font : public display::BaseFont {
|
|||
* @param baseline The y-offset from the top of the text to the baseline.
|
||||
* @param bottom The y-offset from the top of the text to the bottom (i.e. height).
|
||||
*/
|
||||
Font(const GlyphData *data, int data_nr, int baseline, int height);
|
||||
Font(const GlyphData *data, int data_nr, int baseline, int height, uint8_t bpp = 1);
|
||||
|
||||
int match_next_glyph(const char *str, int *match_length);
|
||||
int match_next_glyph(const uint8_t *str, int *match_length);
|
||||
|
||||
void print(int x_start, int y_start, display::Display *display, Color color, const char *text) override;
|
||||
void print(int x_start, int y_start, display::Display *display, Color color, const char *text,
|
||||
Color background) override;
|
||||
void measure(const char *str, int *width, int *x_offset, int *baseline, int *height) override;
|
||||
inline int get_baseline() { return this->baseline_; }
|
||||
inline int get_height() { return this->height_; }
|
||||
inline int get_bpp() { return this->bpp_; }
|
||||
|
||||
const std::vector<Glyph, ExternalRAMAllocator<Glyph>> &get_glyphs() const { return glyphs_; }
|
||||
|
||||
|
@ -61,6 +63,7 @@ class Font : public display::BaseFont {
|
|||
std::vector<Glyph, ExternalRAMAllocator<Glyph>> glyphs_;
|
||||
int baseline_;
|
||||
int height_;
|
||||
uint8_t bpp_; // bits per pixel
|
||||
};
|
||||
|
||||
} // namespace font
|
||||
|
|
70
esphome/components/haier/binary_sensor/__init__.py
Normal file
70
esphome/components/haier/binary_sensor/__init__.py
Normal file
|
@ -0,0 +1,70 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import binary_sensor
|
||||
from esphome.const import (
|
||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
ICON_FAN,
|
||||
ICON_RADIATOR,
|
||||
)
|
||||
from ..climate import (
|
||||
CONF_HAIER_ID,
|
||||
HonClimate,
|
||||
)
|
||||
|
||||
BinarySensorTypeEnum = HonClimate.enum("SubBinarySensorType", True)
|
||||
|
||||
# Haier sensors
|
||||
CONF_OUTDOOR_FAN_STATUS = "outdoor_fan_status"
|
||||
CONF_DEFROST_STATUS = "defrost_status"
|
||||
CONF_COMPRESSOR_STATUS = "compressor_status"
|
||||
CONF_INDOOR_FAN_STATUS = "indoor_fan_status"
|
||||
CONF_FOUR_WAY_VALVE_STATUS = "four_way_valve_status"
|
||||
CONF_INDOOR_ELECTRIC_HEATING_STATUS = "indoor_electric_heating_status"
|
||||
|
||||
# Additional icons
|
||||
ICON_SNOWFLAKE_THERMOMETER = "mdi:snowflake-thermometer"
|
||||
ICON_HVAC = "mdi:hvac"
|
||||
ICON_VALVE = "mdi:valve"
|
||||
|
||||
SENSOR_TYPES = {
|
||||
CONF_OUTDOOR_FAN_STATUS: binary_sensor.binary_sensor_schema(
|
||||
icon=ICON_FAN,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
CONF_DEFROST_STATUS: binary_sensor.binary_sensor_schema(
|
||||
icon=ICON_SNOWFLAKE_THERMOMETER,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
CONF_COMPRESSOR_STATUS: binary_sensor.binary_sensor_schema(
|
||||
icon=ICON_HVAC,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
CONF_INDOOR_FAN_STATUS: binary_sensor.binary_sensor_schema(
|
||||
icon=ICON_FAN,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
CONF_FOUR_WAY_VALVE_STATUS: binary_sensor.binary_sensor_schema(
|
||||
icon=ICON_VALVE,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
CONF_INDOOR_ELECTRIC_HEATING_STATUS: binary_sensor.binary_sensor_schema(
|
||||
icon=ICON_RADIATOR,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_HAIER_ID): cv.use_id(HonClimate),
|
||||
}
|
||||
).extend({cv.Optional(type): schema for type, schema in SENSOR_TYPES.items()})
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
paren = await cg.get_variable(config[CONF_HAIER_ID])
|
||||
|
||||
for type, _ in SENSOR_TYPES.items():
|
||||
if conf := config.get(type):
|
||||
sens = await binary_sensor.new_binary_sensor(conf)
|
||||
binary_sensor_type = getattr(BinarySensorTypeEnum, type.upper())
|
||||
cg.add(paren.set_sub_binary_sensor(binary_sensor_type, sens))
|
|
@ -2,7 +2,7 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
import esphome.final_validate as fv
|
||||
from esphome.components import uart, sensor, climate, logger
|
||||
from esphome.components import uart, climate, logger
|
||||
from esphome import automation
|
||||
from esphome.const import (
|
||||
CONF_BEEPER,
|
||||
|
@ -21,10 +21,6 @@ from esphome.const import (
|
|||
CONF_TRIGGER_ID,
|
||||
CONF_VISUAL,
|
||||
CONF_WIFI,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
ICON_THERMOMETER,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
)
|
||||
from esphome.components.climate import (
|
||||
ClimateMode,
|
||||
|
@ -42,7 +38,6 @@ PROTOCOL_CURRENT_TEMPERATURE_STEP = 0.5
|
|||
PROTOCOL_CONTROL_PACKET_SIZE = 10
|
||||
|
||||
CODEOWNERS = ["@paveldn"]
|
||||
AUTO_LOAD = ["sensor"]
|
||||
DEPENDENCIES = ["climate", "uart"]
|
||||
CONF_ALTERNATIVE_SWING_CONTROL = "alternative_swing_control"
|
||||
CONF_ANSWER_TIMEOUT = "answer_timeout"
|
||||
|
@ -58,7 +53,6 @@ CONF_WIFI_SIGNAL = "wifi_signal"
|
|||
|
||||
PROTOCOL_HON = "HON"
|
||||
PROTOCOL_SMARTAIR2 = "SMARTAIR2"
|
||||
PROTOCOLS_SUPPORTED = [PROTOCOL_HON, PROTOCOL_SMARTAIR2]
|
||||
|
||||
haier_ns = cg.esphome_ns.namespace("haier")
|
||||
HaierClimateBase = haier_ns.class_(
|
||||
|
@ -67,6 +61,7 @@ HaierClimateBase = haier_ns.class_(
|
|||
HonClimate = haier_ns.class_("HonClimate", HaierClimateBase)
|
||||
Smartair2Climate = haier_ns.class_("Smartair2Climate", HaierClimateBase)
|
||||
|
||||
CONF_HAIER_ID = "haier_id"
|
||||
|
||||
AirflowVerticalDirection = haier_ns.enum("AirflowVerticalDirection", True)
|
||||
AIRFLOW_VERTICAL_DIRECTION_OPTIONS = {
|
||||
|
@ -239,12 +234,8 @@ CONFIG_SCHEMA = cv.All(
|
|||
): cv.ensure_list(
|
||||
cv.enum(SUPPORTED_CLIMATE_PRESETS_HON_OPTIONS, upper=True)
|
||||
),
|
||||
cv.Optional(CONF_OUTDOOR_TEMPERATURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
icon=ICON_THERMOMETER,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
cv.Optional(CONF_OUTDOOR_TEMPERATURE): cv.invalid(
|
||||
f"The {CONF_OUTDOOR_TEMPERATURE} option is deprecated, use a sensor for a haier platform instead"
|
||||
),
|
||||
cv.Optional(CONF_ON_ALARM_START): automation.validate_automation(
|
||||
{
|
||||
|
@ -463,9 +454,6 @@ async def to_code(config):
|
|||
cg.add(var.set_beeper_state(config[CONF_BEEPER]))
|
||||
if CONF_DISPLAY in config:
|
||||
cg.add(var.set_display_state(config[CONF_DISPLAY]))
|
||||
if CONF_OUTDOOR_TEMPERATURE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_OUTDOOR_TEMPERATURE])
|
||||
cg.add(var.set_outdoor_temperature_sensor(sens))
|
||||
if CONF_SUPPORTED_MODES in config:
|
||||
cg.add(var.set_supported_modes(config[CONF_SUPPORTED_MODES]))
|
||||
if CONF_SUPPORTED_SWING_MODES in config:
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include <string>
|
||||
#include "esphome/components/climate/climate.h"
|
||||
#include "esphome/components/uart/uart.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "hon_climate.h"
|
||||
#include "hon_packet.h"
|
||||
|
||||
|
@ -51,10 +52,9 @@ hon_protocol::HorizontalSwingMode get_horizontal_swing_mode(AirflowHorizontalDir
|
|||
}
|
||||
|
||||
HonClimate::HonClimate()
|
||||
: cleaning_status_(CleaningState::NO_CLEANING),
|
||||
got_valid_outdoor_temp_(false),
|
||||
active_alarms_{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
|
||||
outdoor_sensor_(nullptr) {
|
||||
: cleaning_status_(CleaningState::NO_CLEANING), got_valid_outdoor_temp_(false), active_alarms_{0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00,
|
||||
0x00, 0x00} {
|
||||
last_status_message_ = std::unique_ptr<uint8_t[]>(new uint8_t[sizeof(hon_protocol::HaierPacketControl)]);
|
||||
this->fan_mode_speed_ = (uint8_t) hon_protocol::FanMode::FAN_MID;
|
||||
this->other_modes_fan_speed_ = (uint8_t) hon_protocol::FanMode::FAN_AUTO;
|
||||
|
@ -66,8 +66,6 @@ void HonClimate::set_beeper_state(bool state) { this->beeper_status_ = state; }
|
|||
|
||||
bool HonClimate::get_beeper_state() const { return this->beeper_status_; }
|
||||
|
||||
void HonClimate::set_outdoor_temperature_sensor(esphome::sensor::Sensor *sensor) { this->outdoor_sensor_ = sensor; }
|
||||
|
||||
AirflowVerticalDirection HonClimate::get_vertical_airflow() const { return this->vertical_direction_; };
|
||||
|
||||
void HonClimate::set_vertical_airflow(AirflowVerticalDirection direction) {
|
||||
|
@ -368,7 +366,14 @@ void HonClimate::process_phase(std::chrono::steady_clock::time_point now) {
|
|||
if (this->can_send_message() && this->is_message_interval_exceeded_(now)) {
|
||||
static const haier_protocol::HaierMessage STATUS_REQUEST(
|
||||
haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::GET_USER_DATA);
|
||||
static const haier_protocol::HaierMessage BIG_DATA_REQUEST(
|
||||
haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::GET_BIG_DATA);
|
||||
if ((this->protocol_phase_ == ProtocolPhases::SENDING_FIRST_STATUS_REQUEST) ||
|
||||
(!this->should_get_big_data_())) {
|
||||
this->send_message_(STATUS_REQUEST, this->use_crc_);
|
||||
} else {
|
||||
this->send_message_(BIG_DATA_REQUEST, this->use_crc_);
|
||||
}
|
||||
this->last_status_request_ = now;
|
||||
}
|
||||
break;
|
||||
|
@ -685,9 +690,87 @@ void HonClimate::process_alarm_message_(const uint8_t *packet, uint8_t size, boo
|
|||
}
|
||||
}
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
void HonClimate::set_sub_sensor(SubSensorType type, sensor::Sensor *sens) {
|
||||
if (type < SubSensorType::SUB_SENSOR_TYPE_COUNT) {
|
||||
if (type >= SubSensorType::BIG_DATA_FRAME_SUB_SENSORS) {
|
||||
if ((this->sub_sensors_[(size_t) type] != nullptr) && (sens == nullptr)) {
|
||||
this->big_data_sensors_--;
|
||||
} else if ((this->sub_sensors_[(size_t) type] == nullptr) && (sens != nullptr)) {
|
||||
this->big_data_sensors_++;
|
||||
}
|
||||
}
|
||||
this->sub_sensors_[(size_t) type] = sens;
|
||||
}
|
||||
}
|
||||
|
||||
void HonClimate::update_sub_sensor_(SubSensorType type, float value) {
|
||||
if (type < SubSensorType::SUB_SENSOR_TYPE_COUNT) {
|
||||
size_t index = (size_t) type;
|
||||
if ((this->sub_sensors_[index] != nullptr) &&
|
||||
((!this->sub_sensors_[index]->has_state()) || (this->sub_sensors_[index]->raw_state != value)))
|
||||
this->sub_sensors_[index]->publish_state(value);
|
||||
}
|
||||
}
|
||||
#endif // USE_SENSOR
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
void HonClimate::set_sub_binary_sensor(SubBinarySensorType type, binary_sensor::BinarySensor *sens) {
|
||||
if (type < SubBinarySensorType::SUB_BINARY_SENSOR_TYPE_COUNT) {
|
||||
if ((this->sub_binary_sensors_[(size_t) type] != nullptr) && (sens == nullptr)) {
|
||||
this->big_data_sensors_--;
|
||||
} else if ((this->sub_binary_sensors_[(size_t) type] == nullptr) && (sens != nullptr)) {
|
||||
this->big_data_sensors_++;
|
||||
}
|
||||
this->sub_binary_sensors_[(size_t) type] = sens;
|
||||
}
|
||||
}
|
||||
|
||||
void HonClimate::update_sub_binary_sensor_(SubBinarySensorType type, uint8_t value) {
|
||||
if (value < 2) {
|
||||
bool converted_value = value == 1;
|
||||
size_t index = (size_t) type;
|
||||
if ((this->sub_binary_sensors_[index] != nullptr) && ((!this->sub_binary_sensors_[index]->has_state()) ||
|
||||
(this->sub_binary_sensors_[index]->state != converted_value)))
|
||||
this->sub_binary_sensors_[index]->publish_state(converted_value);
|
||||
}
|
||||
}
|
||||
#endif // USE_BINARY_SENSOR
|
||||
|
||||
haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *packet_buffer, uint8_t size) {
|
||||
if (size < hon_protocol::HAIER_STATUS_FRAME_SIZE + this->extra_control_packet_bytes_)
|
||||
size_t expected_size = 2 + sizeof(hon_protocol::HaierPacketControl) + sizeof(hon_protocol::HaierPacketSensors) +
|
||||
this->extra_control_packet_bytes_;
|
||||
if (size < expected_size)
|
||||
return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE;
|
||||
uint16_t subtype = (((uint16_t) packet_buffer[0]) << 8) + packet_buffer[1];
|
||||
if ((subtype == 0x7D01) && (size >= expected_size + 4 + sizeof(hon_protocol::HaierPacketBigData))) {
|
||||
// Got BigData packet
|
||||
const hon_protocol::HaierPacketBigData *bd_packet =
|
||||
(const hon_protocol::HaierPacketBigData *) (&packet_buffer[expected_size + 4]);
|
||||
#ifdef USE_SENSOR
|
||||
this->update_sub_sensor_(SubSensorType::INDOOR_COIL_TEMPERATURE, bd_packet->indoor_coil_temperature / 2.0 - 20);
|
||||
this->update_sub_sensor_(SubSensorType::OUTDOOR_COIL_TEMPERATURE, bd_packet->outdoor_coil_temperature - 64);
|
||||
this->update_sub_sensor_(SubSensorType::OUTDOOR_DEFROST_TEMPERATURE, bd_packet->outdoor_coil_temperature - 64);
|
||||
this->update_sub_sensor_(SubSensorType::OUTDOOR_IN_AIR_TEMPERATURE, bd_packet->outdoor_in_air_temperature - 64);
|
||||
this->update_sub_sensor_(SubSensorType::OUTDOOR_OUT_AIR_TEMPERATURE, bd_packet->outdoor_out_air_temperature - 64);
|
||||
this->update_sub_sensor_(SubSensorType::POWER, encode_uint16(bd_packet->power[0], bd_packet->power[1]));
|
||||
this->update_sub_sensor_(SubSensorType::COMPRESSOR_FREQUENCY, bd_packet->compressor_frequency);
|
||||
this->update_sub_sensor_(SubSensorType::COMPRESSOR_CURRENT,
|
||||
encode_uint16(bd_packet->compressor_current[0], bd_packet->compressor_current[1]) / 10.0);
|
||||
this->update_sub_sensor_(
|
||||
SubSensorType::EXPANSION_VALVE_OPEN_DEGREE,
|
||||
encode_uint16(bd_packet->expansion_valve_open_degree[0], bd_packet->expansion_valve_open_degree[1]) / 4095.0);
|
||||
#endif // USE_SENSOR
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
this->update_sub_binary_sensor_(SubBinarySensorType::OUTDOOR_FAN_STATUS, bd_packet->outdoor_fan_status);
|
||||
this->update_sub_binary_sensor_(SubBinarySensorType::DEFROST_STATUS, bd_packet->defrost_status);
|
||||
this->update_sub_binary_sensor_(SubBinarySensorType::COMPRESSOR_STATUS, bd_packet->compressor_status);
|
||||
this->update_sub_binary_sensor_(SubBinarySensorType::INDOOR_FAN_STATUS, bd_packet->indoor_fan_status);
|
||||
this->update_sub_binary_sensor_(SubBinarySensorType::FOUR_WAY_VALVE_STATUS, bd_packet->four_way_valve_status);
|
||||
this->update_sub_binary_sensor_(SubBinarySensorType::INDOOR_ELECTRIC_HEATING_STATUS,
|
||||
bd_packet->indoor_electric_heating_status);
|
||||
#endif // USE_BINARY_SENSOR
|
||||
}
|
||||
struct {
|
||||
hon_protocol::HaierPacketControl control;
|
||||
hon_protocol::HaierPacketSensors sensors;
|
||||
|
@ -699,13 +782,17 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *
|
|||
if (packet.sensors.error_status != 0) {
|
||||
ESP_LOGW(TAG, "HVAC error, code=0x%02X", packet.sensors.error_status);
|
||||
}
|
||||
if ((this->outdoor_sensor_ != nullptr) &&
|
||||
#ifdef USE_SENSOR
|
||||
if ((this->sub_sensors_[(size_t) SubSensorType::OUTDOOR_TEMPERATURE] != nullptr) &&
|
||||
(this->got_valid_outdoor_temp_ || (packet.sensors.outdoor_temperature > 0))) {
|
||||
this->got_valid_outdoor_temp_ = true;
|
||||
float otemp = (float) (packet.sensors.outdoor_temperature + PROTOCOL_OUTDOOR_TEMPERATURE_OFFSET);
|
||||
if ((!this->outdoor_sensor_->has_state()) || (this->outdoor_sensor_->get_raw_state() != otemp))
|
||||
this->outdoor_sensor_->publish_state(otemp);
|
||||
this->update_sub_sensor_(SubSensorType::OUTDOOR_TEMPERATURE,
|
||||
(float) (packet.sensors.outdoor_temperature + PROTOCOL_OUTDOOR_TEMPERATURE_OFFSET));
|
||||
}
|
||||
if ((this->sub_sensors_[(size_t) SubSensorType::HUMIDITY] != nullptr) && (packet.sensors.room_humidity <= 100)) {
|
||||
this->update_sub_sensor_(SubSensorType::HUMIDITY, (float) packet.sensors.room_humidity);
|
||||
}
|
||||
#endif // USE_SENSOR
|
||||
bool should_publish = false;
|
||||
{
|
||||
// Extra modes/presets
|
||||
|
@ -1009,21 +1096,22 @@ void HonClimate::fill_control_messages_queue_() {
|
|||
break;
|
||||
}
|
||||
}
|
||||
if (quiet_mode_buf[1] != 0xFF) {
|
||||
auto presets = this->traits_.get_supported_presets();
|
||||
if ((quiet_mode_buf[1] != 0xFF) && ((presets.find(climate::ClimatePreset::CLIMATE_PRESET_ECO) != presets.end()))) {
|
||||
this->control_messages_queue_.push(
|
||||
haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
|
||||
(uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
|
||||
(uint8_t) hon_protocol::DataParameters::QUIET_MODE,
|
||||
quiet_mode_buf, 2));
|
||||
}
|
||||
if (fast_mode_buf[1] != 0xFF) {
|
||||
if ((fast_mode_buf[1] != 0xFF) && ((presets.find(climate::ClimatePreset::CLIMATE_PRESET_BOOST) != presets.end()))) {
|
||||
this->control_messages_queue_.push(
|
||||
haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
|
||||
(uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
|
||||
(uint8_t) hon_protocol::DataParameters::FAST_MODE,
|
||||
fast_mode_buf, 2));
|
||||
}
|
||||
if (away_mode_buf[1] != 0xFF) {
|
||||
if ((away_mode_buf[1] != 0xFF) && ((presets.find(climate::ClimatePreset::CLIMATE_PRESET_AWAY) != presets.end()))) {
|
||||
this->control_messages_queue_.push(
|
||||
haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
|
||||
(uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
|
||||
|
@ -1032,7 +1120,7 @@ void HonClimate::fill_control_messages_queue_() {
|
|||
}
|
||||
}
|
||||
// Target temperature
|
||||
if (climate_control.target_temperature.has_value()) {
|
||||
if (climate_control.target_temperature.has_value() && (this->mode != ClimateMode::CLIMATE_MODE_FAN_ONLY)) {
|
||||
uint8_t buffer[2] = {0x00, 0x00};
|
||||
buffer[1] = ((uint8_t) climate_control.target_temperature.value()) - 16;
|
||||
this->control_messages_queue_.push(
|
||||
|
@ -1119,12 +1207,24 @@ bool HonClimate::prepare_pending_action() {
|
|||
|
||||
void HonClimate::process_protocol_reset() {
|
||||
HaierClimateBase::process_protocol_reset();
|
||||
if (this->outdoor_sensor_ != nullptr) {
|
||||
this->outdoor_sensor_->publish_state(NAN);
|
||||
#ifdef USE_SENSOR
|
||||
for (auto &sub_sensor : this->sub_sensors_) {
|
||||
if ((sub_sensor != nullptr) && sub_sensor->has_state())
|
||||
sub_sensor->publish_state(NAN);
|
||||
}
|
||||
#endif // USE_SENSOR
|
||||
this->got_valid_outdoor_temp_ = false;
|
||||
this->hvac_hardware_info_.reset();
|
||||
}
|
||||
|
||||
bool HonClimate::should_get_big_data_() {
|
||||
if (this->big_data_sensors_ > 0) {
|
||||
static uint8_t counter = 0;
|
||||
counter = (counter + 1) % 3;
|
||||
return counter == 1;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace haier
|
||||
} // namespace esphome
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#ifdef USE_SENSOR
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#endif
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
#endif
|
||||
#include "esphome/core/automation.h"
|
||||
#include "haier_base.h"
|
||||
|
||||
|
@ -34,6 +39,48 @@ enum class CleaningState : uint8_t {
|
|||
enum class HonControlMethod { MONITOR_ONLY = 0, SET_GROUP_PARAMETERS, SET_SINGLE_PARAMETER };
|
||||
|
||||
class HonClimate : public HaierClimateBase {
|
||||
#ifdef USE_SENSOR
|
||||
public:
|
||||
enum class SubSensorType {
|
||||
// Used data based sensors
|
||||
OUTDOOR_TEMPERATURE = 0,
|
||||
HUMIDITY,
|
||||
// Big data based sensors
|
||||
INDOOR_COIL_TEMPERATURE,
|
||||
OUTDOOR_COIL_TEMPERATURE,
|
||||
OUTDOOR_DEFROST_TEMPERATURE,
|
||||
OUTDOOR_IN_AIR_TEMPERATURE,
|
||||
OUTDOOR_OUT_AIR_TEMPERATURE,
|
||||
POWER,
|
||||
COMPRESSOR_FREQUENCY,
|
||||
COMPRESSOR_CURRENT,
|
||||
EXPANSION_VALVE_OPEN_DEGREE,
|
||||
SUB_SENSOR_TYPE_COUNT,
|
||||
BIG_DATA_FRAME_SUB_SENSORS = INDOOR_COIL_TEMPERATURE,
|
||||
};
|
||||
void set_sub_sensor(SubSensorType type, sensor::Sensor *sens);
|
||||
|
||||
protected:
|
||||
void update_sub_sensor_(SubSensorType type, float value);
|
||||
sensor::Sensor *sub_sensors_[(size_t) SubSensorType::SUB_SENSOR_TYPE_COUNT]{nullptr};
|
||||
#endif
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
public:
|
||||
enum class SubBinarySensorType {
|
||||
OUTDOOR_FAN_STATUS = 0,
|
||||
DEFROST_STATUS,
|
||||
COMPRESSOR_STATUS,
|
||||
INDOOR_FAN_STATUS,
|
||||
FOUR_WAY_VALVE_STATUS,
|
||||
INDOOR_ELECTRIC_HEATING_STATUS,
|
||||
SUB_BINARY_SENSOR_TYPE_COUNT,
|
||||
};
|
||||
void set_sub_binary_sensor(SubBinarySensorType type, binary_sensor::BinarySensor *sens);
|
||||
|
||||
protected:
|
||||
void update_sub_binary_sensor_(SubBinarySensorType type, uint8_t value);
|
||||
binary_sensor::BinarySensor *sub_binary_sensors_[(size_t) SubBinarySensorType::SUB_BINARY_SENSOR_TYPE_COUNT]{nullptr};
|
||||
#endif
|
||||
public:
|
||||
HonClimate();
|
||||
HonClimate(const HonClimate &) = delete;
|
||||
|
@ -42,7 +89,6 @@ class HonClimate : public HaierClimateBase {
|
|||
void dump_config() override;
|
||||
void set_beeper_state(bool state);
|
||||
bool get_beeper_state() const;
|
||||
void set_outdoor_temperature_sensor(esphome::sensor::Sensor *sensor);
|
||||
AirflowVerticalDirection get_vertical_airflow() const;
|
||||
void set_vertical_airflow(AirflowVerticalDirection direction);
|
||||
AirflowHorizontalDirection get_horizontal_airflow() const;
|
||||
|
@ -64,6 +110,7 @@ class HonClimate : public HaierClimateBase {
|
|||
haier_protocol::HaierMessage get_power_message(bool state) override;
|
||||
bool prepare_pending_action() override;
|
||||
void process_protocol_reset() override;
|
||||
bool should_get_big_data_();
|
||||
|
||||
// Answers handlers
|
||||
haier_protocol::HandlerError get_device_version_answer_handler_(haier_protocol::FrameType request_type,
|
||||
|
@ -106,12 +153,12 @@ class HonClimate : public HaierClimateBase {
|
|||
uint8_t active_alarms_[8];
|
||||
int extra_control_packet_bytes_;
|
||||
HonControlMethod control_method_;
|
||||
esphome::sensor::Sensor *outdoor_sensor_;
|
||||
std::queue<haier_protocol::HaierMessage> control_messages_queue_;
|
||||
CallbackManager<void(uint8_t, const char *)> alarm_start_callback_{};
|
||||
CallbackManager<void(uint8_t, const char *)> alarm_end_callback_{};
|
||||
float active_alarm_count_{NAN};
|
||||
std::chrono::steady_clock::time_point last_alarm_request_;
|
||||
int big_data_sensors_{0};
|
||||
};
|
||||
|
||||
class HaierAlarmStartTrigger : public Trigger<uint8_t, const char *> {
|
||||
|
|
|
@ -55,18 +55,18 @@ enum class FanMode : uint8_t { FAN_HIGH = 0x01, FAN_MID = 0x02, FAN_LOW = 0x03,
|
|||
|
||||
struct HaierPacketControl {
|
||||
// Control bytes starts here
|
||||
// 10
|
||||
// 1
|
||||
uint8_t set_point; // Target temperature with 16°C offset (0x00 = 16°C)
|
||||
// 11
|
||||
// 2
|
||||
uint8_t vertical_swing_mode : 4; // See enum VerticalSwingMode
|
||||
uint8_t : 0;
|
||||
// 12
|
||||
// 3
|
||||
uint8_t fan_mode : 3; // See enum FanMode
|
||||
uint8_t special_mode : 2; // See enum SpecialMode
|
||||
uint8_t ac_mode : 3; // See enum ConditioningMode
|
||||
// 13
|
||||
// 4
|
||||
uint8_t : 8;
|
||||
// 14
|
||||
// 5
|
||||
uint8_t ten_degree : 1; // 10 degree status
|
||||
uint8_t display_status : 1; // If 0 disables AC's display
|
||||
uint8_t half_degree : 1; // Use half degree
|
||||
|
@ -75,7 +75,7 @@ struct HaierPacketControl {
|
|||
uint8_t use_fahrenheit : 1; // Use Fahrenheit instead of Celsius
|
||||
uint8_t : 1;
|
||||
uint8_t steri_clean : 1;
|
||||
// 15
|
||||
// 6
|
||||
uint8_t ac_power : 1; // Is ac on or off
|
||||
uint8_t health_mode : 1; // Health mode (negative ions) on or off
|
||||
uint8_t electric_heating_status : 1; // Electric heating status
|
||||
|
@ -84,16 +84,16 @@ struct HaierPacketControl {
|
|||
uint8_t sleep_mode : 1; // Sleep mode
|
||||
uint8_t lock_remote : 1; // Disable remote
|
||||
uint8_t beeper_status : 1; // If 1 disables AC's command feedback beeper (need to be set on every control command)
|
||||
// 16
|
||||
// 7
|
||||
uint8_t target_humidity; // Target humidity (0=30% .. 3C=90%, step = 1%)
|
||||
// 17
|
||||
// 8
|
||||
uint8_t horizontal_swing_mode : 3; // See enum HorizontalSwingMode
|
||||
uint8_t : 3;
|
||||
uint8_t human_sensing_status : 2; // Human sensing status
|
||||
// 18
|
||||
// 9
|
||||
uint8_t change_filter : 1; // Filter need replacement
|
||||
uint8_t : 0;
|
||||
// 19
|
||||
// 10
|
||||
uint8_t fresh_air_status : 1; // Fresh air status
|
||||
uint8_t humidification_status : 1; // Humidification status
|
||||
uint8_t pm2p5_cleaning_status : 1; // PM2.5 cleaning status
|
||||
|
@ -105,40 +105,68 @@ struct HaierPacketControl {
|
|||
};
|
||||
|
||||
struct HaierPacketSensors {
|
||||
// 20
|
||||
// 11
|
||||
uint8_t room_temperature; // 0.5°C step
|
||||
// 21
|
||||
// 12
|
||||
uint8_t room_humidity; // 0%-100% with 1% step
|
||||
// 22
|
||||
// 13
|
||||
uint8_t outdoor_temperature; // 1°C step, -64°C offset (0=-64°C)
|
||||
// 23
|
||||
// 14
|
||||
uint8_t pm2p5_level : 2; // Indoor PM2.5 grade (00: Excellent, 01: good, 02: Medium, 03: Bad)
|
||||
uint8_t air_quality : 2; // Air quality grade (00: Excellent, 01: good, 02: Medium, 03: Bad)
|
||||
uint8_t human_sensing : 2; // Human presence result (00: N/A, 01: not detected, 02: One, 03: Multiple)
|
||||
uint8_t : 1;
|
||||
uint8_t ac_type : 1; // 00 - Heat and cool, 01 - Cool only)
|
||||
// 24
|
||||
// 15
|
||||
uint8_t error_status; // See enum ErrorStatus
|
||||
// 25
|
||||
// 16
|
||||
uint8_t operation_source : 2; // who is controlling AC (00: Other, 01: Remote control, 02: Button, 03: ESP)
|
||||
uint8_t operation_mode_hk : 2; // Homekit only, operation mode (00: Cool, 01: Dry, 02: Heat, 03: Fan)
|
||||
uint8_t : 3;
|
||||
uint8_t err_confirmation : 1; // If 1 clear error status
|
||||
// 26
|
||||
// 17
|
||||
uint16_t total_cleaning_time; // Cleaning cumulative time (1h step)
|
||||
// 28
|
||||
// 19
|
||||
uint16_t indoor_pm2p5_value; // Indoor PM2.5 value (0 ug/m3 - 4095 ug/m3, 1 ug/m3 step)
|
||||
// 30
|
||||
// 21
|
||||
uint16_t outdoor_pm2p5_value; // Outdoor PM2.5 value (0 ug/m3 - 4095 ug/m3, 1 ug/m3 step)
|
||||
// 32
|
||||
// 23
|
||||
uint16_t ch2o_value; // Formaldehyde value (0 ug/m3 - 10000 ug/m3, 1 ug/m3 step)
|
||||
// 34
|
||||
// 25
|
||||
uint16_t voc_value; // VOC value (Volatile Organic Compounds) (0 ug/m3 - 1023 ug/m3, 1 ug/m3 step)
|
||||
// 36
|
||||
// 27
|
||||
uint16_t co2_value; // CO2 value (0 PPM - 10000 PPM, 1 PPM step)
|
||||
};
|
||||
|
||||
constexpr size_t HAIER_STATUS_FRAME_SIZE = 2 + sizeof(HaierPacketControl) + sizeof(HaierPacketSensors);
|
||||
struct HaierPacketBigData {
|
||||
// 29
|
||||
uint8_t power[2]; // AC power consumption (0W - 65535W, 1W step)
|
||||
// 31
|
||||
uint8_t indoor_coil_temperature; // 0.5°C step, -20°C offset (0=-20°C)
|
||||
// 32
|
||||
uint8_t outdoor_out_air_temperature; // 1°C step, -64°C offset (0=-64°C)
|
||||
// 33
|
||||
uint8_t outdoor_coil_temperature; // 1°C step, -64°C offset (0=-64°C)
|
||||
// 34
|
||||
uint8_t outdoor_in_air_temperature; // 1°C step, -64°C offset (0=-64°C)
|
||||
// 35
|
||||
uint8_t outdoor_defrost_temperature; // 1°C step, -64°C offset (0=-64°C)
|
||||
// 36
|
||||
uint8_t compressor_frequency; // 1Hz step, 0Hz - 127Hz
|
||||
// 37
|
||||
uint8_t compressor_current[2]; // 0.1A step, 0.0A - 51.1A (0x0000 - 0x01FF)
|
||||
// 39
|
||||
uint8_t outdoor_fan_status : 2; // 0 - off, 1 - on, 2 - information not available
|
||||
uint8_t defrost_status : 2; // 0 - off, 1 - on, 2 - information not available
|
||||
uint8_t : 0;
|
||||
// 40
|
||||
uint8_t compressor_status : 2; // 0 - off, 1 - on, 2 - information not available
|
||||
uint8_t indoor_fan_status : 2; // 0 - off, 1 - on, 2 - information not available
|
||||
uint8_t four_way_valve_status : 2; // 0 - off, 1 - on, 2 - information not available
|
||||
uint8_t indoor_electric_heating_status : 2; // 0 - off, 1 - on, 2 - information not available
|
||||
// 41
|
||||
uint8_t expansion_valve_open_degree[2]; // 0 - 4095
|
||||
};
|
||||
|
||||
struct DeviceVersionAnswer {
|
||||
char protocol_version[8];
|
||||
|
|
151
esphome/components/haier/sensor/__init__.py
Normal file
151
esphome/components/haier/sensor/__init__.py
Normal file
|
@ -0,0 +1,151 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor
|
||||
from esphome.const import (
|
||||
CONF_POWER,
|
||||
CONF_HUMIDITY,
|
||||
DEVICE_CLASS_CURRENT,
|
||||
DEVICE_CLASS_FREQUENCY,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_POWER,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
ICON_CURRENT_AC,
|
||||
ICON_FLASH,
|
||||
ICON_GAUGE,
|
||||
ICON_HEATING_COIL,
|
||||
ICON_PULSE,
|
||||
ICON_THERMOMETER,
|
||||
ICON_WATER_PERCENT,
|
||||
ICON_WEATHER_WINDY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_AMPERE,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_HERTZ,
|
||||
UNIT_PERCENT,
|
||||
UNIT_WATT,
|
||||
)
|
||||
from ..climate import (
|
||||
CONF_HAIER_ID,
|
||||
HonClimate,
|
||||
)
|
||||
|
||||
SensorTypeEnum = HonClimate.enum("SubSensorType", True)
|
||||
|
||||
# Haier sensors
|
||||
CONF_COMPRESSOR_CURRENT = "compressor_current"
|
||||
CONF_COMPRESSOR_FREQUENCY = "compressor_frequency"
|
||||
CONF_EXPANSION_VALVE_OPEN_DEGREE = "expansion_valve_open_degree"
|
||||
CONF_INDOOR_COIL_TEMPERATURE = "indoor_coil_temperature"
|
||||
CONF_OUTDOOR_COIL_TEMPERATURE = "outdoor_coil_temperature"
|
||||
CONF_OUTDOOR_DEFROST_TEMPERATURE = "outdoor_defrost_temperature"
|
||||
CONF_OUTDOOR_IN_AIR_TEMPERATURE = "outdoor_in_air_temperature"
|
||||
CONF_OUTDOOR_OUT_AIR_TEMPERATURE = "outdoor_out_air_temperature"
|
||||
CONF_OUTDOOR_TEMPERATURE = "outdoor_temperature"
|
||||
|
||||
# Additional icons
|
||||
ICON_SNOWFLAKE_THERMOMETER = "mdi:snowflake-thermometer"
|
||||
|
||||
SENSOR_TYPES = {
|
||||
CONF_COMPRESSOR_CURRENT: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_AMPERE,
|
||||
icon=ICON_CURRENT_AC,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_CURRENT,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
CONF_COMPRESSOR_FREQUENCY: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_HERTZ,
|
||||
icon=ICON_PULSE,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_FREQUENCY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
CONF_EXPANSION_VALVE_OPEN_DEGREE: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
icon=ICON_GAUGE,
|
||||
accuracy_decimals=2,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
CONF_HUMIDITY: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
icon=ICON_WATER_PERCENT,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_HUMIDITY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
CONF_INDOOR_COIL_TEMPERATURE: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
icon=ICON_HEATING_COIL,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
CONF_OUTDOOR_COIL_TEMPERATURE: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
icon=ICON_HEATING_COIL,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
CONF_OUTDOOR_DEFROST_TEMPERATURE: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
icon=ICON_SNOWFLAKE_THERMOMETER,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
CONF_OUTDOOR_IN_AIR_TEMPERATURE: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
icon=ICON_WEATHER_WINDY,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
CONF_OUTDOOR_OUT_AIR_TEMPERATURE: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
icon=ICON_WEATHER_WINDY,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
CONF_OUTDOOR_TEMPERATURE: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
icon=ICON_THERMOMETER,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
CONF_POWER: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_WATT,
|
||||
icon=ICON_FLASH,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_HAIER_ID): cv.use_id(HonClimate),
|
||||
}
|
||||
).extend({cv.Optional(type): schema for type, schema in SENSOR_TYPES.items()})
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
paren = await cg.get_variable(config[CONF_HAIER_ID])
|
||||
|
||||
for type, _ in SENSOR_TYPES.items():
|
||||
if conf := config.get(type):
|
||||
sens = await sensor.new_sensor(conf)
|
||||
sensor_type = getattr(SensorTypeEnum, type.upper())
|
||||
cg.add(paren.set_sub_sensor(sensor_type, sens))
|
|
@ -6,6 +6,7 @@ DEPENDENCIES = ["uart"]
|
|||
|
||||
hydreon_rgxx_ns = cg.esphome_ns.namespace("hydreon_rgxx")
|
||||
RGModel = hydreon_rgxx_ns.enum("RGModel")
|
||||
RG15Resolution = hydreon_rgxx_ns.enum("RG15Resolution")
|
||||
HydreonRGxxComponent = hydreon_rgxx_ns.class_(
|
||||
"HydreonRGxxComponent", cg.PollingComponent, uart.UARTDevice
|
||||
)
|
||||
|
|
|
@ -22,6 +22,11 @@ void HydreonRGxxComponent::dump_config() {
|
|||
ESP_LOGCONFIG(TAG, " Disable Led: %s", TRUEFALSE(this->disable_led_));
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, " Model: RG15");
|
||||
if (this->resolution_ == FORCE_HIGH) {
|
||||
ESP_LOGCONFIG(TAG, " Resolution: high");
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, " Resolution: low");
|
||||
}
|
||||
}
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
|
||||
|
@ -195,7 +200,11 @@ void HydreonRGxxComponent::process_line_() {
|
|||
ESP_LOGI(TAG, "Boot detected: %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str());
|
||||
|
||||
if (this->model_ == RG15) {
|
||||
if (this->resolution_ == FORCE_HIGH) {
|
||||
this->write_str("P\nH\nM\n"); // set sensor to (P)polling mode, (H)high res mode, (M)metric mode
|
||||
} else {
|
||||
this->write_str("P\nL\nM\n"); // set sensor to (P)polling mode, (L)low res mode, (M)metric mode
|
||||
}
|
||||
}
|
||||
|
||||
if (this->model_ == RG9) {
|
||||
|
|
|
@ -16,6 +16,11 @@ enum RGModel {
|
|||
RG15 = 2,
|
||||
};
|
||||
|
||||
enum RG15Resolution {
|
||||
FORCE_LOW = 1,
|
||||
FORCE_HIGH = 2,
|
||||
};
|
||||
|
||||
#ifdef HYDREON_RGXX_NUM_SENSORS
|
||||
static const uint8_t NUM_SENSORS = HYDREON_RGXX_NUM_SENSORS;
|
||||
#else
|
||||
|
@ -37,6 +42,7 @@ class HydreonRGxxComponent : public PollingComponent, public uart::UARTDevice {
|
|||
void set_em_sat_sensor(binary_sensor::BinarySensor *sensor) { this->em_sat_sensor_ = sensor; }
|
||||
#endif
|
||||
void set_model(RGModel model) { model_ = model; }
|
||||
void set_resolution(RG15Resolution resolution) { resolution_ = resolution; }
|
||||
void set_request_temperature(bool b) { request_temperature_ = b; }
|
||||
|
||||
/// Schedule data readings.
|
||||
|
@ -68,7 +74,10 @@ class HydreonRGxxComponent : public PollingComponent, public uart::UARTDevice {
|
|||
int16_t boot_count_ = 0;
|
||||
int16_t no_response_count_ = 0;
|
||||
std::string buffer_;
|
||||
|
||||
RGModel model_ = RG9;
|
||||
RG15Resolution resolution_ = FORCE_HIGH;
|
||||
|
||||
int sw_version_ = 0;
|
||||
bool too_cold_ = false;
|
||||
bool lens_bad_ = false;
|
||||
|
|
|
@ -5,6 +5,7 @@ from esphome.const import (
|
|||
CONF_ID,
|
||||
CONF_MODEL,
|
||||
CONF_MOISTURE,
|
||||
CONF_RESOLUTION,
|
||||
CONF_TEMPERATURE,
|
||||
DEVICE_CLASS_PRECIPITATION_INTENSITY,
|
||||
DEVICE_CLASS_PRECIPITATION,
|
||||
|
@ -14,7 +15,7 @@ from esphome.const import (
|
|||
ICON_THERMOMETER,
|
||||
)
|
||||
|
||||
from . import RGModel, HydreonRGxxComponent
|
||||
from . import RGModel, RG15Resolution, HydreonRGxxComponent
|
||||
|
||||
UNIT_INTENSITY = "intensity"
|
||||
UNIT_MILLIMETERS = "mm"
|
||||
|
@ -37,11 +38,18 @@ RG_MODELS = {
|
|||
# 1.100 - https://rainsensors.com/wp-content/uploads/sites/3/2021/03/2021.03.11-rg-9_instructions.pdf
|
||||
# 1.200 - https://rainsensors.com/wp-content/uploads/sites/3/2022/03/2022.02.17-rev-1.200-rg-9_instructions.pdf
|
||||
}
|
||||
SUPPORTED_SENSORS = {
|
||||
|
||||
RG15_RESOLUTION = {
|
||||
"low": RG15Resolution.FORCE_LOW,
|
||||
"high": RG15Resolution.FORCE_HIGH,
|
||||
}
|
||||
|
||||
SUPPORTED_OPTIONS = {
|
||||
CONF_ACC: ["RG_15"],
|
||||
CONF_EVENT_ACC: ["RG_15"],
|
||||
CONF_TOTAL_ACC: ["RG_15"],
|
||||
CONF_R_INT: ["RG_15"],
|
||||
CONF_RESOLUTION: ["RG_15"],
|
||||
CONF_MOISTURE: ["RG_9"],
|
||||
CONF_TEMPERATURE: ["RG_9"],
|
||||
CONF_DISABLE_LED: ["RG_9"],
|
||||
|
@ -57,7 +65,7 @@ PROTOCOL_NAMES = {
|
|||
|
||||
|
||||
def _validate(config):
|
||||
for conf, models in SUPPORTED_SENSORS.items():
|
||||
for conf, models in SUPPORTED_OPTIONS.items():
|
||||
if conf in config:
|
||||
if config[CONF_MODEL] not in models:
|
||||
raise cv.Invalid(
|
||||
|
@ -75,6 +83,7 @@ CONFIG_SCHEMA = cv.All(
|
|||
upper=True,
|
||||
space="_",
|
||||
),
|
||||
cv.Optional(CONF_RESOLUTION): cv.enum(RG15_RESOLUTION, upper=False),
|
||||
cv.Optional(CONF_ACC): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_MILLIMETERS,
|
||||
accuracy_decimals=2,
|
||||
|
@ -139,6 +148,9 @@ async def to_code(config):
|
|||
cg.add(var.set_sensor(sens, i))
|
||||
|
||||
cg.add(var.set_model(config[CONF_MODEL]))
|
||||
if CONF_RESOLUTION in config:
|
||||
cg.add(var.set_resolution(config[CONF_RESOLUTION]))
|
||||
|
||||
cg.add(var.set_request_temperature(CONF_TEMPERATURE in config))
|
||||
|
||||
if CONF_DISABLE_LED in config:
|
||||
|
|
|
@ -51,6 +51,7 @@ ILI9XXXColorMode = ili9xxx_ns.enum("ILI9XXXColorMode")
|
|||
ColorOrder = display.display_ns.enum("ColorMode")
|
||||
|
||||
MODELS = {
|
||||
"GC9A01A": ili9xxx_ns.class_("ILI9XXXGC9A01A", ILI9XXXDisplay),
|
||||
"M5STACK": ili9xxx_ns.class_("ILI9XXXM5Stack", ILI9XXXDisplay),
|
||||
"M5CORE": ili9xxx_ns.class_("ILI9XXXM5CORE", ILI9XXXDisplay),
|
||||
"TFT_2.4": ili9xxx_ns.class_("ILI9XXXILI9341", ILI9XXXDisplay),
|
||||
|
|
|
@ -42,7 +42,9 @@ void ILI9XXXDisplay::setup() {
|
|||
this->y_low_ = this->height_;
|
||||
this->x_high_ = 0;
|
||||
this->y_high_ = 0;
|
||||
}
|
||||
|
||||
void ILI9XXXDisplay::alloc_buffer_() {
|
||||
if (this->buffer_color_mode_ == BITS_16) {
|
||||
this->init_internal_(this->get_buffer_length_() * 2);
|
||||
if (this->buffer_ != nullptr) {
|
||||
|
@ -107,6 +109,8 @@ void ILI9XXXDisplay::dump_config() {
|
|||
float ILI9XXXDisplay::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
|
||||
void ILI9XXXDisplay::fill(Color color) {
|
||||
if (!this->check_buffer_())
|
||||
return;
|
||||
uint16_t new_color = 0;
|
||||
this->x_low_ = 0;
|
||||
this->y_low_ = 0;
|
||||
|
@ -124,7 +128,6 @@ void ILI9XXXDisplay::fill(Color color) {
|
|||
// Upper and lower is equal can use quicker memset operation. Takes ~20ms.
|
||||
memset(this->buffer_, (uint8_t) new_color, buffer_length_16_bits);
|
||||
} else {
|
||||
// Slower set of both buffers. Takes ~30ms.
|
||||
for (uint32_t i = 0; i < buffer_length_16_bits; i = i + 2) {
|
||||
this->buffer_[i] = (uint8_t) (new_color >> 8);
|
||||
this->buffer_[i + 1] = (uint8_t) new_color;
|
||||
|
@ -144,6 +147,8 @@ void HOT ILI9XXXDisplay::draw_absolute_pixel_internal(int x, int y, Color color)
|
|||
if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0) {
|
||||
return;
|
||||
}
|
||||
if (!this->check_buffer_())
|
||||
return;
|
||||
uint32_t pos = (y * width_) + x;
|
||||
uint16_t new_color;
|
||||
bool updated = false;
|
||||
|
|
|
@ -17,13 +17,9 @@ enum ILI9XXXColorMode {
|
|||
BITS_16 = 0x10,
|
||||
};
|
||||
|
||||
#ifndef ILI9XXXDisplay_DATA_RATE
|
||||
#define ILI9XXXDisplay_DATA_RATE spi::DATA_RATE_40MHZ
|
||||
#endif // ILI9XXXDisplay_DATA_RATE
|
||||
|
||||
class ILI9XXXDisplay : public display::DisplayBuffer,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
|
||||
spi::CLOCK_PHASE_LEADING, ILI9XXXDisplay_DATA_RATE> {
|
||||
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_40MHZ> {
|
||||
public:
|
||||
ILI9XXXDisplay() = default;
|
||||
ILI9XXXDisplay(uint8_t const *init_sequence, int16_t width, int16_t height, bool invert_colors)
|
||||
|
@ -90,6 +86,14 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
|
|||
display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) override;
|
||||
|
||||
protected:
|
||||
inline bool check_buffer_() {
|
||||
if (this->buffer_ == nullptr) {
|
||||
this->alloc_buffer_();
|
||||
return !this->is_failed();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void draw_absolute_pixel_internal(int x, int y, Color color) override;
|
||||
void setup_pins_();
|
||||
|
||||
|
@ -120,6 +124,7 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
|
|||
void end_command_();
|
||||
void start_data_();
|
||||
void end_data_();
|
||||
void alloc_buffer_();
|
||||
|
||||
GPIOPin *reset_pin_{nullptr};
|
||||
GPIOPin *dc_pin_{nullptr};
|
||||
|
@ -249,5 +254,10 @@ class ILI9XXXS3BoxLite : public ILI9XXXDisplay {
|
|||
ILI9XXXS3BoxLite() : ILI9XXXDisplay(INITCMD_S3BOXLITE, 320, 240, true) {}
|
||||
};
|
||||
|
||||
class ILI9XXXGC9A01A : public ILI9XXXDisplay {
|
||||
public:
|
||||
ILI9XXXGC9A01A() : ILI9XXXDisplay(INITCMD_GC9A01A, 240, 240, true) {}
|
||||
};
|
||||
|
||||
} // namespace ili9xxx
|
||||
} // namespace esphome
|
||||
|
|
|
@ -316,6 +316,60 @@ static const uint8_t PROGMEM INITCMD_ST7789V[] = {
|
|||
0x00 // End of list
|
||||
};
|
||||
|
||||
static const uint8_t PROGMEM INITCMD_GC9A01A[] = {
|
||||
0xEF, 0,
|
||||
0xEB, 1, 0x14, // ?
|
||||
0xFE, 0,
|
||||
0xEF, 0,
|
||||
0xEB, 1, 0x14, // ?
|
||||
0x84, 1, 0x40, // ?
|
||||
0x85, 1, 0xFF, // ?
|
||||
0x86, 1, 0xFF, // ?
|
||||
0x87, 1, 0xFF, // ?
|
||||
0x88, 1, 0x0A, // ?
|
||||
0x89, 1, 0x21, // ?
|
||||
0x8A, 1, 0x00, // ?
|
||||
0x8B, 1, 0x80, // ?
|
||||
0x8C, 1, 0x01, // ?
|
||||
0x8D, 1, 0x01, // ?
|
||||
0x8E, 1, 0xFF, // ?
|
||||
0x8F, 1, 0xFF, // ?
|
||||
0xB6, 2, 0x00, 0x00, // ?
|
||||
0x90, 4, 0x08, 0x08, 0x08, 0x08, // ?
|
||||
ILI9XXX_PIXFMT , 1, 0x05,
|
||||
ILI9XXX_MADCTL , 1, MADCTL_MX| MADCTL_BGR, // Memory Access Control
|
||||
0xBD, 1, 0x06, // ?
|
||||
0xBC, 1, 0x00, // ?
|
||||
0xFF, 3, 0x60, 0x01, 0x04, // ?
|
||||
0xC3, 1, 0x13,
|
||||
0xC4, 1, 0x13,
|
||||
0xF9, 1, 0x22,
|
||||
0xBE, 1, 0x11, // ?
|
||||
0xE1, 2, 0x10, 0x0E, // ?
|
||||
0xDF, 3, 0x21, 0x0c, 0x02, // ?
|
||||
0xF0, 6, 0x45, 0x09, 0x08, 0x08, 0x26, 0x2A,
|
||||
0xF1, 6, 0x43, 0x70, 0x72, 0x36, 0x37, 0x6F,
|
||||
0xF2, 6, 0x45, 0x09, 0x08, 0x08, 0x26, 0x2A,
|
||||
0xF3, 6, 0x43, 0x70, 0x72, 0x36, 0x37, 0x6F,
|
||||
0xED, 2, 0x1B, 0x0B, // ?
|
||||
0xAE, 1, 0x77, // ?
|
||||
0xCD, 1, 0x63, // ?
|
||||
0xE8, 1, 0x34,
|
||||
0x62, 12, 0x18, 0x0D, 0x71, 0xED, 0x70, 0x70, // ?
|
||||
0x18, 0x0F, 0x71, 0xEF, 0x70, 0x70,
|
||||
0x63, 12, 0x18, 0x11, 0x71, 0xF1, 0x70, 0x70, // ?
|
||||
0x18, 0x13, 0x71, 0xF3, 0x70, 0x70,
|
||||
0x64, 7, 0x28, 0x29, 0xF1, 0x01, 0xF1, 0x00, 0x07, // ?
|
||||
0x66, 10, 0x3C, 0x00, 0xCD, 0x67, 0x45, 0x45, 0x10, 0x00, 0x00, 0x00, // ?
|
||||
0x67, 10, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x01, 0x54, 0x10, 0x32, 0x98, // ?
|
||||
0x74, 7, 0x10, 0x85, 0x80, 0x00, 0x00, 0x4E, 0x00, // ?
|
||||
0x98, 2, 0x3e, 0x07, // ?
|
||||
0x35, 0,
|
||||
ILI9XXX_SLPOUT , 0x80, // Exit Sleep
|
||||
ILI9XXX_DISPON , 0x80, // Display on
|
||||
0x00 // End of list
|
||||
};
|
||||
|
||||
// clang-format on
|
||||
} // namespace ili9xxx
|
||||
} // namespace esphome
|
||||
|
|
|
@ -196,7 +196,7 @@ bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command
|
|||
this->connecting_sta_ = sta;
|
||||
|
||||
wifi::global_wifi_component->set_sta(sta);
|
||||
wifi::global_wifi_component->start_scanning();
|
||||
wifi::global_wifi_component->start_connecting(sta, false);
|
||||
this->set_state_(improv::STATE_PROVISIONING);
|
||||
ESP_LOGD(TAG, "Received Improv wifi settings ssid=%s, password=" LOG_SECRET("%s"), command.ssid.c_str(),
|
||||
command.password.c_str());
|
||||
|
|
|
@ -127,6 +127,10 @@ def uart_selection(value):
|
|||
if CORE.using_arduino and value.upper() in ESP_ARDUINO_UNSUPPORTED_USB_UARTS:
|
||||
raise cv.Invalid(f"Arduino framework does not support {value}.")
|
||||
variant = get_esp32_variant()
|
||||
if CORE.using_esp_idf and variant == VARIANT_ESP32C3 and value == USB_CDC:
|
||||
raise cv.Invalid(
|
||||
f"{value} is not supported for variant {variant} when using ESP-IDF."
|
||||
)
|
||||
if variant in UART_SELECTION_ESP32:
|
||||
return cv.one_of(*UART_SELECTION_ESP32[variant], upper=True)(value)
|
||||
if CORE.is_esp8266:
|
||||
|
@ -140,6 +144,8 @@ def uart_selection(value):
|
|||
component = get_libretiny_component()
|
||||
if component in UART_SELECTION_LIBRETINY:
|
||||
return cv.one_of(*UART_SELECTION_LIBRETINY[component], upper=True)(value)
|
||||
if CORE.is_host:
|
||||
raise cv.Invalid("Uart selection not valid for host platform")
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
|
@ -274,6 +280,16 @@ async def to_code(config):
|
|||
add_idf_sdkconfig_option("CONFIG_ESP_CONSOLE_USB_CDC", True)
|
||||
elif config[CONF_HARDWARE_UART] == USB_SERIAL_JTAG:
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG", True)
|
||||
try:
|
||||
uart_selection(USB_SERIAL_JTAG)
|
||||
cg.add_define("USE_LOGGER_USB_SERIAL_JTAG")
|
||||
except cv.Invalid:
|
||||
pass
|
||||
try:
|
||||
uart_selection(USB_CDC)
|
||||
cg.add_define("USE_LOGGER_USB_CDC")
|
||||
except cv.Invalid:
|
||||
pass
|
||||
|
||||
# Register at end for safe mode
|
||||
await cg.register_component(log, config)
|
||||
|
|
|
@ -1,30 +1,9 @@
|
|||
#include "logger.h"
|
||||
#include <cinttypes>
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
#include <driver/uart.h>
|
||||
|
||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S3) || \
|
||||
defined(USE_ESP32_VARIANT_ESP32H2)
|
||||
#include <driver/usb_serial_jtag.h>
|
||||
#include <esp_vfs_dev.h>
|
||||
#include <esp_vfs_usb_serial_jtag.h>
|
||||
#endif
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "esp_idf_version.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <fcntl.h>
|
||||
|
||||
#endif // USE_ESP_IDF
|
||||
|
||||
#if defined(USE_ESP32_FRAMEWORK_ARDUINO) || defined(USE_ESP_IDF)
|
||||
#include <esp_log.h>
|
||||
#endif // USE_ESP32_FRAMEWORK_ARDUINO || USE_ESP_IDF
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace logger {
|
||||
|
@ -106,58 +85,6 @@ void Logger::log_vprintf_(int level, const char *tag, int line, const __FlashStr
|
|||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
void Logger::init_uart_() {
|
||||
uart_config_t uart_config{};
|
||||
uart_config.baud_rate = (int) baud_rate_;
|
||||
uart_config.data_bits = UART_DATA_8_BITS;
|
||||
uart_config.parity = UART_PARITY_DISABLE;
|
||||
uart_config.stop_bits = UART_STOP_BITS_1;
|
||||
uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE;
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||
uart_config.source_clk = UART_SCLK_DEFAULT;
|
||||
#endif
|
||||
uart_param_config(this->uart_num_, &uart_config);
|
||||
const int uart_buffer_size = tx_buffer_size_;
|
||||
// Install UART driver using an event queue here
|
||||
uart_driver_install(this->uart_num_, uart_buffer_size, uart_buffer_size, 10, nullptr, 0);
|
||||
}
|
||||
|
||||
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
void Logger::init_usb_cdc_() {}
|
||||
#endif
|
||||
|
||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S3) || \
|
||||
defined(USE_ESP32_VARIANT_ESP32H2)
|
||||
void Logger::init_usb_serial_jtag_() {
|
||||
setvbuf(stdin, NULL, _IONBF, 0); // Disable buffering on stdin
|
||||
|
||||
// Minicom, screen, idf_monitor send CR when ENTER key is pressed
|
||||
esp_vfs_dev_usb_serial_jtag_set_rx_line_endings(ESP_LINE_ENDINGS_CR);
|
||||
// Move the caret to the beginning of the next line on '\n'
|
||||
esp_vfs_dev_usb_serial_jtag_set_tx_line_endings(ESP_LINE_ENDINGS_CRLF);
|
||||
|
||||
// Enable non-blocking mode on stdin and stdout
|
||||
fcntl(fileno(stdout), F_SETFL, 0);
|
||||
fcntl(fileno(stdin), F_SETFL, 0);
|
||||
|
||||
usb_serial_jtag_driver_config_t usb_serial_jtag_config{};
|
||||
usb_serial_jtag_config.rx_buffer_size = 512;
|
||||
usb_serial_jtag_config.tx_buffer_size = 512;
|
||||
|
||||
esp_err_t ret = ESP_OK;
|
||||
// Install USB-SERIAL-JTAG driver for interrupt-driven reads and writes
|
||||
ret = usb_serial_jtag_driver_install(&usb_serial_jtag_config);
|
||||
if (ret != ESP_OK) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Tell vfs to use usb-serial-jtag driver
|
||||
esp_vfs_usb_serial_jtag_use_driver();
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
int HOT Logger::level_for(const char *tag) {
|
||||
// Uses std::vector<> for low memory footprint, though the vector
|
||||
// could be sorted to minimize lookup times. This feature isn't used that
|
||||
|
@ -169,6 +96,7 @@ int HOT Logger::level_for(const char *tag) {
|
|||
}
|
||||
return ESPHOME_LOG_LEVEL;
|
||||
}
|
||||
|
||||
void HOT Logger::log_message_(int level, const char *tag, int offset) {
|
||||
// remove trailing newline
|
||||
if (this->tx_buffer_[this->tx_buffer_at_ - 1] == '\n') {
|
||||
|
@ -178,28 +106,9 @@ void HOT Logger::log_message_(int level, const char *tag, int offset) {
|
|||
this->set_null_terminator_();
|
||||
|
||||
const char *msg = this->tx_buffer_ + offset;
|
||||
|
||||
if (this->baud_rate_ > 0) {
|
||||
#ifdef USE_ARDUINO
|
||||
this->hw_serial_->println(msg);
|
||||
#endif // USE_ARDUINO
|
||||
#ifdef USE_ESP_IDF
|
||||
if (
|
||||
#if defined(USE_ESP32_VARIANT_ESP32S2)
|
||||
this->uart_ == UART_SELECTION_USB_CDC
|
||||
#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32H2)
|
||||
this->uart_ == UART_SELECTION_USB_SERIAL_JTAG
|
||||
#elif defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
this->uart_ == UART_SELECTION_USB_CDC || this->uart_ == UART_SELECTION_USB_SERIAL_JTAG
|
||||
#else
|
||||
/* DISABLES CODE */ (false) // NOLINT
|
||||
#endif
|
||||
) {
|
||||
puts(msg);
|
||||
} else {
|
||||
uart_write_bytes(this->uart_num_, msg, strlen(msg));
|
||||
uart_write_bytes(this->uart_num_, "\n", 1);
|
||||
}
|
||||
#endif
|
||||
this->write_msg_(msg);
|
||||
}
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
@ -211,17 +120,6 @@ void HOT Logger::log_message_(int level, const char *tag, int offset) {
|
|||
if (xPortGetFreeHeapSize() < 2048)
|
||||
return;
|
||||
#endif
|
||||
#ifdef USE_HOST
|
||||
time_t rawtime;
|
||||
struct tm *timeinfo;
|
||||
char buffer[80];
|
||||
|
||||
time(&rawtime);
|
||||
timeinfo = localtime(&rawtime);
|
||||
strftime(buffer, sizeof buffer, "[%H:%M:%S]", timeinfo);
|
||||
fputs(buffer, stdout);
|
||||
puts(msg);
|
||||
#endif
|
||||
|
||||
this->log_callback_.call(level, tag, msg);
|
||||
}
|
||||
|
@ -231,178 +129,23 @@ Logger::Logger(uint32_t baud_rate, size_t tx_buffer_size) : baud_rate_(baud_rate
|
|||
this->tx_buffer_ = new char[this->tx_buffer_size_ + 1]; // NOLINT
|
||||
}
|
||||
|
||||
#ifndef USE_LIBRETINY
|
||||
void Logger::pre_setup() {
|
||||
if (this->baud_rate_ > 0) {
|
||||
#ifdef USE_LOGGER_USB_CDC
|
||||
void Logger::loop() {
|
||||
#ifdef USE_ARDUINO
|
||||
switch (this->uart_) {
|
||||
case UART_SELECTION_UART0:
|
||||
#ifdef USE_ESP8266
|
||||
case UART_SELECTION_UART0_SWAP:
|
||||
#endif
|
||||
#ifdef USE_RP2040
|
||||
this->hw_serial_ = &Serial1;
|
||||
Serial1.begin(this->baud_rate_);
|
||||
#else
|
||||
#if ARDUINO_USB_CDC_ON_BOOT
|
||||
this->hw_serial_ = &Serial0;
|
||||
Serial0.begin(this->baud_rate_);
|
||||
#else
|
||||
this->hw_serial_ = &Serial;
|
||||
Serial.begin(this->baud_rate_);
|
||||
#endif
|
||||
#endif
|
||||
#ifdef USE_ESP8266
|
||||
if (this->uart_ == UART_SELECTION_UART0_SWAP) {
|
||||
Serial.swap();
|
||||
if (this->uart_ != UART_SELECTION_USB_CDC) {
|
||||
return;
|
||||
}
|
||||
Serial.setDebugOutput(ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE);
|
||||
static bool opened = false;
|
||||
if (opened == Serial) {
|
||||
return;
|
||||
}
|
||||
if (false == opened) {
|
||||
App.schedule_dump_config();
|
||||
}
|
||||
opened = !opened;
|
||||
#endif
|
||||
break;
|
||||
case UART_SELECTION_UART1:
|
||||
#ifdef USE_RP2040
|
||||
this->hw_serial_ = &Serial2;
|
||||
Serial2.begin(this->baud_rate_);
|
||||
#else
|
||||
this->hw_serial_ = &Serial1;
|
||||
Serial1.begin(this->baud_rate_);
|
||||
#endif
|
||||
#ifdef USE_ESP8266
|
||||
Serial1.setDebugOutput(ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE);
|
||||
#endif
|
||||
break;
|
||||
#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6) && \
|
||||
!defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
case UART_SELECTION_UART2:
|
||||
this->hw_serial_ = &Serial2;
|
||||
Serial2.begin(this->baud_rate_);
|
||||
break;
|
||||
#endif
|
||||
#if defined(USE_ESP32) && \
|
||||
(defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C3))
|
||||
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C3)
|
||||
case UART_SELECTION_USB_CDC:
|
||||
#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32C3
|
||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
case UART_SELECTION_USB_SERIAL_JTAG:
|
||||
#endif // USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32S3
|
||||
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C3)
|
||||
#if ARDUINO_USB_CDC_ON_BOOT
|
||||
this->hw_serial_ = &Serial;
|
||||
Serial.setTxTimeoutMs(0); // workaround for 2.0.9 crash when there's no data connection
|
||||
Serial.begin(this->baud_rate_);
|
||||
#else
|
||||
this->hw_serial_ = &Serial;
|
||||
Serial.begin(this->baud_rate_);
|
||||
#endif // ARDUINO_USB_CDC_ON_BOOT
|
||||
#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32C3
|
||||
break;
|
||||
#endif // USE_ESP32 && (USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32C3)
|
||||
#ifdef USE_RP2040
|
||||
case UART_SELECTION_USB_CDC:
|
||||
this->hw_serial_ = &Serial;
|
||||
Serial.begin(this->baud_rate_);
|
||||
break;
|
||||
#endif // USE_RP2040
|
||||
}
|
||||
#endif // USE_ARDUINO
|
||||
#ifdef USE_ESP_IDF
|
||||
this->uart_num_ = UART_NUM_0;
|
||||
switch (this->uart_) {
|
||||
case UART_SELECTION_UART0:
|
||||
this->uart_num_ = UART_NUM_0;
|
||||
break;
|
||||
case UART_SELECTION_UART1:
|
||||
this->uart_num_ = UART_NUM_1;
|
||||
break;
|
||||
#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6) && \
|
||||
!defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32S3) && !defined(USE_ESP32_VARIANT_ESP32H2)
|
||||
case UART_SELECTION_UART2:
|
||||
this->uart_num_ = UART_NUM_2;
|
||||
break;
|
||||
#endif // !USE_ESP32_VARIANT_ESP32C3 && !USE_ESP32_VARIANT_ESP32S2 && !USE_ESP32_VARIANT_ESP32S3 &&
|
||||
// !USE_ESP32_VARIANT_ESP32H2
|
||||
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
case UART_SELECTION_USB_CDC:
|
||||
this->uart_num_ = -1;
|
||||
this->init_usb_cdc_();
|
||||
break;
|
||||
#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3
|
||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S3) || \
|
||||
defined(USE_ESP32_VARIANT_ESP32H2)
|
||||
case UART_SELECTION_USB_SERIAL_JTAG:
|
||||
this->uart_num_ = -1;
|
||||
this->init_usb_serial_jtag_();
|
||||
break;
|
||||
#endif // USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C6 || USE_ESP32_VARIANT_ESP32S3 ||
|
||||
// USE_ESP32_VARIANT_ESP32H2
|
||||
}
|
||||
if (this->uart_num_ >= 0) {
|
||||
this->init_uart_();
|
||||
}
|
||||
#endif // USE_ESP_IDF
|
||||
}
|
||||
#ifdef USE_ESP8266
|
||||
else {
|
||||
uart_set_debug(UART_NO);
|
||||
}
|
||||
#endif // USE_ESP8266
|
||||
|
||||
global_logger = this;
|
||||
#if defined(USE_ESP_IDF) || defined(USE_ESP32_FRAMEWORK_ARDUINO)
|
||||
esp_log_set_vprintf(esp_idf_log_vprintf_);
|
||||
if (ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE) {
|
||||
esp_log_level_set("*", ESP_LOG_VERBOSE);
|
||||
}
|
||||
#endif // USE_ESP_IDF || USE_ESP32_FRAMEWORK_ARDUINO
|
||||
|
||||
ESP_LOGI(TAG, "Log initialized");
|
||||
}
|
||||
#else // USE_LIBRETINY
|
||||
void Logger::pre_setup() {
|
||||
if (this->baud_rate_ > 0) {
|
||||
switch (this->uart_) {
|
||||
#if LT_HW_UART0
|
||||
case UART_SELECTION_UART0:
|
||||
this->hw_serial_ = &Serial0;
|
||||
Serial0.begin(this->baud_rate_);
|
||||
break;
|
||||
#endif
|
||||
#if LT_HW_UART1
|
||||
case UART_SELECTION_UART1:
|
||||
this->hw_serial_ = &Serial1;
|
||||
Serial1.begin(this->baud_rate_);
|
||||
break;
|
||||
#endif
|
||||
#if LT_HW_UART2
|
||||
case UART_SELECTION_UART2:
|
||||
this->hw_serial_ = &Serial2;
|
||||
Serial2.begin(this->baud_rate_);
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
this->hw_serial_ = &Serial;
|
||||
Serial.begin(this->baud_rate_);
|
||||
if (this->uart_ != UART_SELECTION_DEFAULT) {
|
||||
ESP_LOGW(TAG, " The chosen logger UART port is not available on this board."
|
||||
"The default port was used instead.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// change lt_log() port to match default Serial
|
||||
if (this->uart_ == UART_SELECTION_DEFAULT) {
|
||||
this->uart_ = (UARTSelection) (LT_UART_DEFAULT_SERIAL + 1);
|
||||
lt_log_set_port(LT_UART_DEFAULT_SERIAL);
|
||||
} else {
|
||||
lt_log_set_port(this->uart_ - 1);
|
||||
}
|
||||
}
|
||||
|
||||
global_logger = this;
|
||||
ESP_LOGI(TAG, "Log initialized");
|
||||
}
|
||||
#endif // USE_LIBRETINY
|
||||
|
||||
void Logger::set_baud_rate(uint32_t baud_rate) { this->baud_rate_ = baud_rate; }
|
||||
void Logger::set_log_level(const std::string &tag, int log_level) {
|
||||
|
@ -418,37 +161,13 @@ void Logger::add_on_log_callback(std::function<void(int, const char *, const cha
|
|||
}
|
||||
float Logger::get_setup_priority() const { return setup_priority::BUS + 500.0f; }
|
||||
const char *const LOG_LEVELS[] = {"NONE", "ERROR", "WARN", "INFO", "CONFIG", "DEBUG", "VERBOSE", "VERY_VERBOSE"};
|
||||
#ifdef USE_ESP32
|
||||
const char *const UART_SELECTIONS[] = {
|
||||
"UART0", "UART1",
|
||||
#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6) && \
|
||||
!defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32S3) && !defined(USE_ESP32_VARIANT_ESP32H2)
|
||||
"UART2",
|
||||
#endif // !USE_ESP32_VARIANT_ESP32C3 && !USE_ESP32_VARINT_ESP32C6 && !USE_ESP32_VARIANT_ESP32S2 &&
|
||||
// !USE_ESP32_VARIANT_ESP32S3 && !USE_ESP32_VARIANT_ESP32H2
|
||||
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
"USB_CDC",
|
||||
#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3
|
||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
"USB_SERIAL_JTAG",
|
||||
#endif // USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32S3
|
||||
};
|
||||
#endif // USE_ESP32
|
||||
#ifdef USE_ESP8266
|
||||
const char *const UART_SELECTIONS[] = {"UART0", "UART1", "UART0_SWAP"};
|
||||
#endif // USE_ESP8266
|
||||
#ifdef USE_RP2040
|
||||
const char *const UART_SELECTIONS[] = {"UART0", "UART1", "USB_CDC"};
|
||||
#endif // USE_RP2040
|
||||
#ifdef USE_LIBRETINY
|
||||
const char *const UART_SELECTIONS[] = {"DEFAULT", "UART0", "UART1", "UART2"};
|
||||
#endif // USE_LIBRETINY
|
||||
|
||||
void Logger::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Logger:");
|
||||
ESP_LOGCONFIG(TAG, " Level: %s", LOG_LEVELS[ESPHOME_LOG_LEVEL]);
|
||||
#ifndef USE_HOST
|
||||
ESP_LOGCONFIG(TAG, " Log Baud Rate: %" PRIu32, this->baud_rate_);
|
||||
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY)
|
||||
ESP_LOGCONFIG(TAG, " Hardware UART: %s", UART_SELECTIONS[this->uart_]);
|
||||
ESP_LOGCONFIG(TAG, " Hardware UART: %s", get_uart_selection_());
|
||||
#endif
|
||||
|
||||
for (auto &it : this->log_levels_) {
|
||||
|
|
|
@ -34,41 +34,31 @@ enum UARTSelection {
|
|||
#ifdef USE_LIBRETINY
|
||||
UART_SELECTION_DEFAULT = 0,
|
||||
UART_SELECTION_UART0,
|
||||
UART_SELECTION_UART1,
|
||||
UART_SELECTION_UART2,
|
||||
#else
|
||||
UART_SELECTION_UART0 = 0,
|
||||
#endif
|
||||
UART_SELECTION_UART1,
|
||||
#if defined(USE_ESP32)
|
||||
#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6) && \
|
||||
!defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32S3) && !defined(USE_ESP32_VARIANT_ESP32H2)
|
||||
#if defined(USE_LIBRETINY) || defined(USE_ESP32_VARIANT_ESP32)
|
||||
UART_SELECTION_UART2,
|
||||
#endif // !USE_ESP32_VARIANT_ESP32C3 && !USE_ESP32_VARIANT_ESP32C6 && !USE_ESP32_VARIANT_ESP32S2 &&
|
||||
// !USE_ESP32_VARIANT_ESP32S3 && !USE_ESP32_VARIANT_ESP32H2
|
||||
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || \
|
||||
(defined(USE_ESP32_VARIANT_ESP32C3) && defined(USE_ARDUINO))
|
||||
#endif
|
||||
#ifdef USE_LOGGER_USB_CDC
|
||||
UART_SELECTION_USB_CDC,
|
||||
#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32C3
|
||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S3) || \
|
||||
defined(USE_ESP32_VARIANT_ESP32H2)
|
||||
#endif
|
||||
#ifdef USE_LOGGER_USB_SERIAL_JTAG
|
||||
UART_SELECTION_USB_SERIAL_JTAG,
|
||||
#endif // USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C6 || USE_ESP32_VARIANT_ESP32S3 ||
|
||||
// USE_ESP32_VARIANT_ESP32H2
|
||||
#endif // USE_ESP32
|
||||
#endif
|
||||
#ifdef USE_ESP8266
|
||||
UART_SELECTION_UART0_SWAP,
|
||||
#endif // USE_ESP8266
|
||||
#ifdef USE_RP2040
|
||||
UART_SELECTION_USB_CDC,
|
||||
#endif // USE_RP2040
|
||||
#endif // USE_LIBRETINY
|
||||
};
|
||||
#endif // USE_ESP32 || USE_ESP8266 || USE_RP2040 || USE_LIBRETINY
|
||||
|
||||
class Logger : public Component {
|
||||
public:
|
||||
explicit Logger(uint32_t baud_rate, size_t tx_buffer_size);
|
||||
|
||||
#ifdef USE_LOGGER_USB_CDC
|
||||
void loop() override;
|
||||
#endif
|
||||
/// Manually set the baud rate for serial, set to 0 to disable.
|
||||
void set_baud_rate(uint32_t baud_rate);
|
||||
uint32_t get_baud_rate() const { return baud_rate_; }
|
||||
|
@ -106,19 +96,10 @@ class Logger : public Component {
|
|||
#endif
|
||||
|
||||
protected:
|
||||
#ifdef USE_ESP_IDF
|
||||
void init_uart_();
|
||||
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
void init_usb_cdc_();
|
||||
#endif
|
||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S3) || \
|
||||
defined(USE_ESP32_VARIANT_ESP32H2)
|
||||
void init_usb_serial_jtag_();
|
||||
#endif
|
||||
#endif
|
||||
void write_header_(int level, const char *tag, int line);
|
||||
void write_footer_();
|
||||
void log_message_(int level, const char *tag, int offset = 0);
|
||||
void write_msg_(const char *msg);
|
||||
|
||||
inline bool is_buffer_full_() const { return this->tx_buffer_at_ >= this->tx_buffer_size_; }
|
||||
inline int buffer_remaining_capacity_() const { return this->tx_buffer_size_ - this->tx_buffer_at_; }
|
||||
|
@ -158,6 +139,10 @@ class Logger : public Component {
|
|||
va_end(arg);
|
||||
}
|
||||
|
||||
#ifndef USE_HOST
|
||||
const char *get_uart_selection_();
|
||||
#endif
|
||||
|
||||
uint32_t baud_rate_;
|
||||
char *tx_buffer_{nullptr};
|
||||
int tx_buffer_at_{0};
|
||||
|
|
201
esphome/components/logger/logger_esp32.cpp
Normal file
201
esphome/components/logger/logger_esp32.cpp
Normal file
|
@ -0,0 +1,201 @@
|
|||
#ifdef USE_ESP32
|
||||
#include "logger.h"
|
||||
|
||||
#if defined(USE_ESP32_FRAMEWORK_ARDUINO) || defined(USE_ESP_IDF)
|
||||
#include <esp_log.h>
|
||||
#endif // USE_ESP32_FRAMEWORK_ARDUINO || USE_ESP_IDF
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
#include <driver/uart.h>
|
||||
|
||||
#ifdef USE_LOGGER_USB_SERIAL_JTAG
|
||||
#include <driver/usb_serial_jtag.h>
|
||||
#include <esp_vfs_dev.h>
|
||||
#include <esp_vfs_usb_serial_jtag.h>
|
||||
#endif
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "esp_idf_version.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <fcntl.h>
|
||||
|
||||
#endif // USE_ESP_IDF
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace logger {
|
||||
|
||||
static const char *const TAG = "logger";
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
|
||||
#ifdef USE_LOGGER_USB_SERIAL_JTAG
|
||||
static void init_usb_serial_jtag_() {
|
||||
setvbuf(stdin, NULL, _IONBF, 0); // Disable buffering on stdin
|
||||
|
||||
// Minicom, screen, idf_monitor send CR when ENTER key is pressed
|
||||
esp_vfs_dev_usb_serial_jtag_set_rx_line_endings(ESP_LINE_ENDINGS_CR);
|
||||
// Move the caret to the beginning of the next line on '\n'
|
||||
esp_vfs_dev_usb_serial_jtag_set_tx_line_endings(ESP_LINE_ENDINGS_CRLF);
|
||||
|
||||
// Enable non-blocking mode on stdin and stdout
|
||||
fcntl(fileno(stdout), F_SETFL, 0);
|
||||
fcntl(fileno(stdin), F_SETFL, 0);
|
||||
|
||||
usb_serial_jtag_driver_config_t usb_serial_jtag_config{};
|
||||
usb_serial_jtag_config.rx_buffer_size = 512;
|
||||
usb_serial_jtag_config.tx_buffer_size = 512;
|
||||
|
||||
esp_err_t ret = ESP_OK;
|
||||
// Install USB-SERIAL-JTAG driver for interrupt-driven reads and writes
|
||||
ret = usb_serial_jtag_driver_install(&usb_serial_jtag_config);
|
||||
if (ret != ESP_OK) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Tell vfs to use usb-serial-jtag driver
|
||||
esp_vfs_usb_serial_jtag_use_driver();
|
||||
}
|
||||
#endif
|
||||
|
||||
void init_uart(uart_port_t uart_num, uint32_t baud_rate, int tx_buffer_size) {
|
||||
uart_config_t uart_config{};
|
||||
uart_config.baud_rate = (int) baud_rate;
|
||||
uart_config.data_bits = UART_DATA_8_BITS;
|
||||
uart_config.parity = UART_PARITY_DISABLE;
|
||||
uart_config.stop_bits = UART_STOP_BITS_1;
|
||||
uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE;
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||
uart_config.source_clk = UART_SCLK_DEFAULT;
|
||||
#endif
|
||||
uart_param_config(uart_num, &uart_config);
|
||||
const int uart_buffer_size = tx_buffer_size;
|
||||
// Install UART driver using an event queue here
|
||||
uart_driver_install(uart_num, uart_buffer_size, uart_buffer_size, 10, nullptr, 0);
|
||||
}
|
||||
|
||||
#endif // USE_ESP_IDF
|
||||
|
||||
void Logger::pre_setup() {
|
||||
if (this->baud_rate_ > 0) {
|
||||
#ifdef USE_ARDUINO
|
||||
switch (this->uart_) {
|
||||
case UART_SELECTION_UART0:
|
||||
#if ARDUINO_USB_CDC_ON_BOOT
|
||||
this->hw_serial_ = &Serial0;
|
||||
Serial0.begin(this->baud_rate_);
|
||||
#else
|
||||
this->hw_serial_ = &Serial;
|
||||
Serial.begin(this->baud_rate_);
|
||||
#endif
|
||||
break;
|
||||
case UART_SELECTION_UART1:
|
||||
this->hw_serial_ = &Serial1;
|
||||
Serial1.begin(this->baud_rate_);
|
||||
break;
|
||||
#ifdef USE_ESP32_VARIANT_ESP32
|
||||
case UART_SELECTION_UART2:
|
||||
this->hw_serial_ = &Serial2;
|
||||
Serial2.begin(this->baud_rate_);
|
||||
break;
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOGGER_USB_CDC
|
||||
case UART_SELECTION_USB_CDC:
|
||||
this->hw_serial_ = &Serial;
|
||||
#if ARDUINO_USB_CDC_ON_BOOT
|
||||
Serial.setTxTimeoutMs(0); // workaround for 2.0.9 crash when there's no data connection
|
||||
#endif
|
||||
Serial.begin(this->baud_rate_);
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
#endif // USE_ARDUINO
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
this->uart_num_ = UART_NUM_0;
|
||||
switch (this->uart_) {
|
||||
case UART_SELECTION_UART0:
|
||||
this->uart_num_ = UART_NUM_0;
|
||||
break;
|
||||
case UART_SELECTION_UART1:
|
||||
this->uart_num_ = UART_NUM_1;
|
||||
break;
|
||||
#ifdef USE_ESP32_VARIANT_ESP32
|
||||
case UART_SELECTION_UART2:
|
||||
this->uart_num_ = UART_NUM_2;
|
||||
break;
|
||||
#endif
|
||||
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
case UART_SELECTION_USB_CDC:
|
||||
this->uart_num_ = -1;
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_LOGGER_USB_SERIAL_JTAG
|
||||
case UART_SELECTION_USB_SERIAL_JTAG:
|
||||
this->uart_num_ = -1;
|
||||
init_usb_serial_jtag_();
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
if (this->uart_num_ >= 0) {
|
||||
init_uart(this->uart_num_, baud_rate_, tx_buffer_size_);
|
||||
}
|
||||
#endif // USE_ESP_IDF
|
||||
}
|
||||
|
||||
global_logger = this;
|
||||
#if defined(USE_ESP_IDF) || defined(USE_ESP32_FRAMEWORK_ARDUINO)
|
||||
esp_log_set_vprintf(esp_idf_log_vprintf_);
|
||||
if (ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE) {
|
||||
esp_log_level_set("*", ESP_LOG_VERBOSE);
|
||||
}
|
||||
#endif // USE_ESP_IDF || USE_ESP32_FRAMEWORK_ARDUINO
|
||||
|
||||
ESP_LOGI(TAG, "Log initialized");
|
||||
}
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
void HOT Logger::write_msg_(const char *msg) {
|
||||
if (
|
||||
#if defined(USE_ESP32_VARIANT_ESP32S2)
|
||||
this->uart_ == UART_SELECTION_USB_CDC
|
||||
#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32H2)
|
||||
this->uart_ == UART_SELECTION_USB_SERIAL_JTAG
|
||||
#elif defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
this->uart_ == UART_SELECTION_USB_CDC || this->uart_ == UART_SELECTION_USB_SERIAL_JTAG
|
||||
#else
|
||||
/* DISABLES CODE */ (false) // NOLINT
|
||||
#endif
|
||||
) {
|
||||
puts(msg);
|
||||
} else {
|
||||
uart_write_bytes(this->uart_num_, msg, strlen(msg));
|
||||
uart_write_bytes(this->uart_num_, "\n", 1);
|
||||
}
|
||||
}
|
||||
#else
|
||||
void HOT Logger::write_msg_(const char *msg) { this->hw_serial_->println(msg); }
|
||||
#endif
|
||||
|
||||
const char *const UART_SELECTIONS[] = {
|
||||
"UART0", "UART1",
|
||||
#ifdef USE_ESP32_VARIANT_ESP32
|
||||
"UART2",
|
||||
#endif
|
||||
#ifdef USE_LOGGER_USB_CDC
|
||||
"USB_CDC",
|
||||
#endif
|
||||
#ifdef USE_LOGGER_USB_SERIAL_JTAG
|
||||
"USB_SERIAL_JTAG",
|
||||
#endif
|
||||
};
|
||||
|
||||
const char *Logger::get_uart_selection_() { return UART_SELECTIONS[this->uart_]; }
|
||||
|
||||
} // namespace logger
|
||||
} // namespace esphome
|
||||
#endif
|
45
esphome/components/logger/logger_esp8266.cpp
Normal file
45
esphome/components/logger/logger_esp8266.cpp
Normal file
|
@ -0,0 +1,45 @@
|
|||
#ifdef USE_ESP8266
|
||||
#include "logger.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace logger {
|
||||
|
||||
static const char *const TAG = "logger";
|
||||
|
||||
void Logger::pre_setup() {
|
||||
if (this->baud_rate_ > 0) {
|
||||
switch (this->uart_) {
|
||||
case UART_SELECTION_UART0:
|
||||
case UART_SELECTION_UART0_SWAP:
|
||||
this->hw_serial_ = &Serial;
|
||||
Serial.begin(this->baud_rate_);
|
||||
if (this->uart_ == UART_SELECTION_UART0_SWAP) {
|
||||
Serial.swap();
|
||||
}
|
||||
Serial.setDebugOutput(ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE);
|
||||
break;
|
||||
case UART_SELECTION_UART1:
|
||||
this->hw_serial_ = &Serial1;
|
||||
Serial1.begin(this->baud_rate_);
|
||||
Serial1.setDebugOutput(ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
uart_set_debug(UART_NO);
|
||||
}
|
||||
|
||||
global_logger = this;
|
||||
|
||||
ESP_LOGI(TAG, "Log initialized");
|
||||
}
|
||||
|
||||
void HOT Logger::write_msg_(const char *msg) { this->hw_serial_->println(msg); }
|
||||
|
||||
const char *const UART_SELECTIONS[] = {"UART0", "UART1", "UART0_SWAP"};
|
||||
|
||||
const char *Logger::get_uart_selection_() { return UART_SELECTIONS[this->uart_]; }
|
||||
|
||||
} // namespace logger
|
||||
} // namespace esphome
|
||||
#endif
|
24
esphome/components/logger/logger_host.cpp
Normal file
24
esphome/components/logger/logger_host.cpp
Normal file
|
@ -0,0 +1,24 @@
|
|||
#if defined(USE_HOST)
|
||||
#include "logger.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace logger {
|
||||
|
||||
void HOT Logger::write_msg_(const char *msg) {
|
||||
time_t rawtime;
|
||||
struct tm *timeinfo;
|
||||
char buffer[80];
|
||||
|
||||
time(&rawtime);
|
||||
timeinfo = localtime(&rawtime);
|
||||
strftime(buffer, sizeof buffer, "[%H:%M:%S]", timeinfo);
|
||||
fputs(buffer, stdout);
|
||||
puts(msg);
|
||||
}
|
||||
|
||||
void Logger::pre_setup() { global_logger = this; }
|
||||
|
||||
} // namespace logger
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
62
esphome/components/logger/logger_libretiny.cpp
Normal file
62
esphome/components/logger/logger_libretiny.cpp
Normal file
|
@ -0,0 +1,62 @@
|
|||
#ifdef USE_LIBRETINY
|
||||
#include "logger.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace logger {
|
||||
|
||||
static const char *const TAG = "logger";
|
||||
|
||||
void Logger::pre_setup() {
|
||||
if (this->baud_rate_ > 0) {
|
||||
switch (this->uart_) {
|
||||
#if LT_HW_UART0
|
||||
case UART_SELECTION_UART0:
|
||||
this->hw_serial_ = &Serial0;
|
||||
Serial0.begin(this->baud_rate_);
|
||||
break;
|
||||
#endif
|
||||
#if LT_HW_UART1
|
||||
case UART_SELECTION_UART1:
|
||||
this->hw_serial_ = &Serial1;
|
||||
Serial1.begin(this->baud_rate_);
|
||||
break;
|
||||
#endif
|
||||
#if LT_HW_UART2
|
||||
case UART_SELECTION_UART2:
|
||||
this->hw_serial_ = &Serial2;
|
||||
Serial2.begin(this->baud_rate_);
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
this->hw_serial_ = &Serial;
|
||||
Serial.begin(this->baud_rate_);
|
||||
if (this->uart_ != UART_SELECTION_DEFAULT) {
|
||||
ESP_LOGW(TAG, " The chosen logger UART port is not available on this board."
|
||||
"The default port was used instead.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// change lt_log() port to match default Serial
|
||||
if (this->uart_ == UART_SELECTION_DEFAULT) {
|
||||
this->uart_ = (UARTSelection) (LT_UART_DEFAULT_SERIAL + 1);
|
||||
lt_log_set_port(LT_UART_DEFAULT_SERIAL);
|
||||
} else {
|
||||
lt_log_set_port(this->uart_ - 1);
|
||||
}
|
||||
}
|
||||
|
||||
global_logger = this;
|
||||
ESP_LOGI(TAG, "Log initialized");
|
||||
}
|
||||
|
||||
void HOT Logger::write_msg_(const char *msg) { this->hw_serial_->println(msg); }
|
||||
|
||||
const char *const UART_SELECTIONS[] = {"DEFAULT", "UART0", "UART1", "UART2"};
|
||||
|
||||
const char *Logger::get_uart_selection_() { return UART_SELECTIONS[this->uart_]; }
|
||||
|
||||
} // namespace logger
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_LIBRETINY
|
39
esphome/components/logger/logger_rp2040.cpp
Normal file
39
esphome/components/logger/logger_rp2040.cpp
Normal file
|
@ -0,0 +1,39 @@
|
|||
#ifdef USE_RP2040
|
||||
#include "logger.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace logger {
|
||||
|
||||
static const char *const TAG = "logger";
|
||||
|
||||
void Logger::pre_setup() {
|
||||
if (this->baud_rate_ > 0) {
|
||||
switch (this->uart_) {
|
||||
case UART_SELECTION_UART0:
|
||||
this->hw_serial_ = &Serial1;
|
||||
Serial1.begin(this->baud_rate_);
|
||||
break;
|
||||
case UART_SELECTION_UART1:
|
||||
this->hw_serial_ = &Serial2;
|
||||
Serial2.begin(this->baud_rate_);
|
||||
break;
|
||||
case UART_SELECTION_USB_CDC:
|
||||
this->hw_serial_ = &Serial;
|
||||
Serial.begin(this->baud_rate_);
|
||||
break;
|
||||
}
|
||||
}
|
||||
global_logger = this;
|
||||
ESP_LOGI(TAG, "Log initialized");
|
||||
}
|
||||
|
||||
void HOT Logger::write_msg_(const char *msg) { this->hw_serial_->println(msg); }
|
||||
|
||||
const char *const UART_SELECTIONS[] = {"UART0", "UART1", "USB_CDC"};
|
||||
|
||||
const char *Logger::get_uart_selection_() { return UART_SELECTIONS[this->uart_]; }
|
||||
|
||||
} // namespace logger
|
||||
} // namespace esphome
|
||||
#endif // USE_RP2040
|
|
@ -88,7 +88,7 @@ async def to_code(config):
|
|||
add_idf_component(
|
||||
name="mdns",
|
||||
repo="https://github.com/espressif/esp-protocols.git",
|
||||
ref="mdns-v1.2.2",
|
||||
ref="mdns-v1.2.5",
|
||||
path="components/mdns",
|
||||
)
|
||||
|
||||
|
|
|
@ -29,6 +29,14 @@ void MHZ19Component::setup() {
|
|||
}
|
||||
|
||||
void MHZ19Component::update() {
|
||||
uint32_t now_ms = millis();
|
||||
uint32_t warmup_ms = this->warmup_seconds_ * 1000;
|
||||
if (now_ms < warmup_ms) {
|
||||
ESP_LOGW(TAG, "MHZ19 warming up, %ds left", (warmup_ms - now_ms) / 1000);
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t response[MHZ19_RESPONSE_LENGTH];
|
||||
if (!this->mhz19_write_command_(MHZ19_COMMAND_GET_PPM, response)) {
|
||||
ESP_LOGW(TAG, "Reading data from MHZ19 failed!");
|
||||
|
@ -101,6 +109,8 @@ void MHZ19Component::dump_config() {
|
|||
} else if (this->abc_boot_logic_ == MHZ19_ABC_DISABLED) {
|
||||
ESP_LOGCONFIG(TAG, " Automatic baseline calibration disabled on boot");
|
||||
}
|
||||
|
||||
ESP_LOGCONFIG(TAG, " Warmup seconds: %ds", this->warmup_seconds_);
|
||||
}
|
||||
|
||||
} // namespace mhz19
|
||||
|
|
|
@ -25,6 +25,7 @@ class MHZ19Component : public PollingComponent, public uart::UARTDevice {
|
|||
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }
|
||||
void set_co2_sensor(sensor::Sensor *co2_sensor) { co2_sensor_ = co2_sensor; }
|
||||
void set_abc_enabled(bool abc_enabled) { abc_boot_logic_ = abc_enabled ? MHZ19_ABC_ENABLED : MHZ19_ABC_DISABLED; }
|
||||
void set_warmup_seconds(uint32_t seconds) { warmup_seconds_ = seconds; }
|
||||
|
||||
protected:
|
||||
bool mhz19_write_command_(const uint8_t *command, uint8_t *response);
|
||||
|
@ -32,6 +33,7 @@ class MHZ19Component : public PollingComponent, public uart::UARTDevice {
|
|||
sensor::Sensor *temperature_sensor_{nullptr};
|
||||
sensor::Sensor *co2_sensor_{nullptr};
|
||||
MHZ19ABCLogic abc_boot_logic_{MHZ19_ABC_NONE};
|
||||
uint32_t warmup_seconds_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class MHZ19CalibrateZeroAction : public Action<Ts...> {
|
||||
|
|
|
@ -18,6 +18,7 @@ from esphome.const import (
|
|||
DEPENDENCIES = ["uart"]
|
||||
|
||||
CONF_AUTOMATIC_BASELINE_CALIBRATION = "automatic_baseline_calibration"
|
||||
CONF_WARMUP_TIME = "warmup_time"
|
||||
|
||||
mhz19_ns = cg.esphome_ns.namespace("mhz19")
|
||||
MHZ19Component = mhz19_ns.class_("MHZ19Component", cg.PollingComponent, uart.UARTDevice)
|
||||
|
@ -45,6 +46,9 @@ CONFIG_SCHEMA = (
|
|||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_AUTOMATIC_BASELINE_CALIBRATION): cv.boolean,
|
||||
cv.Optional(
|
||||
CONF_WARMUP_TIME, default="75s"
|
||||
): cv.positive_time_period_seconds,
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
|
@ -68,6 +72,8 @@ async def to_code(config):
|
|||
if CONF_AUTOMATIC_BASELINE_CALIBRATION in config:
|
||||
cg.add(var.set_abc_enabled(config[CONF_AUTOMATIC_BASELINE_CALIBRATION]))
|
||||
|
||||
cg.add(var.set_warmup_seconds(config[CONF_WARMUP_TIME]))
|
||||
|
||||
|
||||
CALIBRATION_ACTION_SCHEMA = maybe_simple_id(
|
||||
{
|
||||
|
|
|
@ -134,7 +134,7 @@ void MicroWakeWord::loop() {
|
|||
this->set_state_(State::IDLE);
|
||||
if (this->detected_) {
|
||||
this->detected_ = false;
|
||||
this->wake_word_detected_trigger_->trigger("");
|
||||
this->wake_word_detected_trigger_->trigger(this->wake_word_);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue