mirror of
https://github.com/esphome/esphome.git
synced 2025-04-01 00:18:18 +01:00
Merge remote-tracking branch 'upstream/dev' into bh1745
This commit is contained in:
commit
5c666e2b7c
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'
|
||||
|
15
CODEOWNERS
15
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
|
||||
@ -79,12 +82,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
|
||||
@ -116,7 +122,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
|
||||
@ -276,6 +283,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
|
||||
@ -283,6 +291,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
|
||||
@ -327,6 +336,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
|
||||
@ -339,6 +349,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
|
||||
@ -370,6 +382,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
|
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) {}
|
||||
@ -1598,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);
|
||||
|
@ -7184,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
|
||||
|
@ -1850,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;
|
||||
}
|
||||
float ATM90E32Component::get_line_voltage_c_() {
|
||||
uint16_t voltage = this->read16_(ATM90E32_REGISTER_URMSC);
|
||||
return (float) voltage / 100;
|
||||
voltage = accumulation / reads;
|
||||
this->phase_[phase].voltage_ = (float) voltage / 100;
|
||||
return this->phase_[phase].voltage_;
|
||||
}
|
||||
float ATM90E32Component::get_line_current_a_() {
|
||||
uint16_t current = this->read16_(ATM90E32_REGISTER_IRMSA);
|
||||
|
||||
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_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
|
||||
}
|
||||
|
||||
|
@ -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_();
|
||||
|
@ -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,39 +454,123 @@ 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
|
||||
} // namespace esphome
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -144,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
|
||||
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace logger {
|
||||
@ -128,6 +129,24 @@ 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
|
||||
}
|
||||
|
||||
#ifdef USE_LOGGER_USB_CDC
|
||||
void Logger::loop() {
|
||||
#ifdef USE_ARDUINO
|
||||
if (this->uart_ != UART_SELECTION_USB_CDC) {
|
||||
return;
|
||||
}
|
||||
static bool opened = false;
|
||||
if (opened == Serial) {
|
||||
return;
|
||||
}
|
||||
if (false == opened) {
|
||||
App.schedule_dump_config();
|
||||
}
|
||||
opened = !opened;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
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) {
|
||||
this->log_levels_.push_back(LogLevelOverride{tag, log_level});
|
||||
@ -146,8 +165,10 @@ const char *const LOG_LEVELS[] = {"NONE", "ERROR", "WARN", "INFO", "CONFIG", "DE
|
||||
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_);
|
||||
ESP_LOGCONFIG(TAG, " Hardware UART: %s", get_uart_selection_());
|
||||
#endif
|
||||
|
||||
for (auto &it : this->log_levels_) {
|
||||
ESP_LOGCONFIG(TAG, " Level for '%s': %s", it.tag.c_str(), LOG_LEVELS[it.level]);
|
||||
|
@ -56,7 +56,9 @@ enum UARTSelection {
|
||||
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_; }
|
||||
@ -137,7 +139,9 @@ class Logger : public Component {
|
||||
va_end(arg);
|
||||
}
|
||||
|
||||
#ifndef USE_HOST
|
||||
const char *get_uart_selection_();
|
||||
#endif
|
||||
|
||||
uint32_t baud_rate_;
|
||||
char *tx_buffer_{nullptr};
|
||||
|
@ -16,6 +16,8 @@ void HOT Logger::write_msg_(const char *msg) {
|
||||
puts(msg);
|
||||
}
|
||||
|
||||
void Logger::pre_setup() { global_logger = this; }
|
||||
|
||||
} // namespace logger
|
||||
} // namespace esphome
|
||||
|
||||
|
@ -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(
|
||||
{
|
||||
|
@ -9,9 +9,53 @@ AUTO_LOAD = ["climate_ir"]
|
||||
mitsubishi_ns = cg.esphome_ns.namespace("mitsubishi")
|
||||
MitsubishiClimate = mitsubishi_ns.class_("MitsubishiClimate", climate_ir.ClimateIR)
|
||||
|
||||
CONFIG_SCHEMA = climate_ir.CLIMATE_IR_SCHEMA.extend(
|
||||
CONF_SET_FAN_MODE = "set_fan_mode"
|
||||
SetFanMode = mitsubishi_ns.enum("SetFanMode")
|
||||
SETFANMODE = {
|
||||
"quiet_4levels": SetFanMode.MITSUBISHI_FAN_Q4L,
|
||||
# "5levels": SetFanMode.MITSUBISHI_FAN_5L,
|
||||
"4levels": SetFanMode.MITSUBISHI_FAN_4L,
|
||||
"3levels": SetFanMode.MITSUBISHI_FAN_3L,
|
||||
}
|
||||
|
||||
CONF_SUPPORTS_DRY = "supports_dry"
|
||||
CONF_SUPPORTS_FAN_ONLY = "supports_fan_only"
|
||||
|
||||
CONF_HORIZONTAL_DEFAULT = "horizontal_default"
|
||||
HorizontalDirections = mitsubishi_ns.enum("HorizontalDirections")
|
||||
HORIZONTAL_DIRECTIONS = {
|
||||
"left": HorizontalDirections.HORIZONTAL_DIRECTION_LEFT,
|
||||
"middle-left": HorizontalDirections.HORIZONTAL_DIRECTION_MIDDLE_LEFT,
|
||||
"middle": HorizontalDirections.HORIZONTAL_DIRECTION_MIDDLE,
|
||||
"middle-right": HorizontalDirections.HORIZONTAL_DIRECTION_MIDDLE_RIGHT,
|
||||
"right": HorizontalDirections.HORIZONTAL_DIRECTION_RIGHT,
|
||||
"split": HorizontalDirections.HORIZONTAL_DIRECTION_SPLIT,
|
||||
}
|
||||
|
||||
CONF_VERTICAL_DEFAULT = "vertical_default"
|
||||
VerticalDirections = mitsubishi_ns.enum("VerticalDirections")
|
||||
VERTICAL_DIRECTIONS = {
|
||||
"auto": VerticalDirections.VERTICAL_DIRECTION_AUTO,
|
||||
"up": VerticalDirections.VERTICAL_DIRECTION_UP,
|
||||
"middle-up": VerticalDirections.VERTICAL_DIRECTION_MIDDLE_UP,
|
||||
"middle": VerticalDirections.VERTICAL_DIRECTION_MIDDLE,
|
||||
"middle-down": VerticalDirections.VERTICAL_DIRECTION_MIDDLE_DOWN,
|
||||
"down": VerticalDirections.VERTICAL_DIRECTION_DOWN,
|
||||
}
|
||||
|
||||
|
||||
CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(MitsubishiClimate),
|
||||
cv.Optional(CONF_SET_FAN_MODE, default="3levels"): cv.enum(SETFANMODE),
|
||||
cv.Optional(CONF_SUPPORTS_DRY, default=False): cv.boolean,
|
||||
cv.Optional(CONF_SUPPORTS_FAN_ONLY, default=False): cv.boolean,
|
||||
cv.Optional(CONF_HORIZONTAL_DEFAULT, default="middle"): cv.enum(
|
||||
HORIZONTAL_DIRECTIONS
|
||||
),
|
||||
cv.Optional(CONF_VERTICAL_DEFAULT, default="middle"): cv.enum(
|
||||
VERTICAL_DIRECTIONS
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
@ -19,3 +63,9 @@ CONFIG_SCHEMA = climate_ir.CLIMATE_IR_SCHEMA.extend(
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await climate_ir.register_climate_ir(var, config)
|
||||
|
||||
cg.add(var.set_fan_mode(config[CONF_SET_FAN_MODE]))
|
||||
cg.add(var.set_supports_dry(config[CONF_SUPPORTS_DRY]))
|
||||
cg.add(var.set_supports_fan_only(config[CONF_SUPPORTS_FAN_ONLY]))
|
||||
cg.add(var.set_horizontal_default(config[CONF_HORIZONTAL_DEFAULT]))
|
||||
cg.add(var.set_vertical_default(config[CONF_VERTICAL_DEFAULT]))
|
||||
|
@ -8,12 +8,31 @@ static const char *const TAG = "mitsubishi.climate";
|
||||
|
||||
const uint32_t MITSUBISHI_OFF = 0x00;
|
||||
|
||||
const uint8_t MITSUBISHI_COOL = 0x18;
|
||||
const uint8_t MITSUBISHI_DRY = 0x10;
|
||||
const uint8_t MITSUBISHI_AUTO = 0x20;
|
||||
const uint8_t MITSUBISHI_HEAT = 0x08;
|
||||
const uint8_t MITSUBISHI_MODE_AUTO = 0x20;
|
||||
const uint8_t MITSUBISHI_MODE_COOL = 0x18;
|
||||
const uint8_t MITSUBISHI_MODE_DRY = 0x10;
|
||||
const uint8_t MITSUBISHI_MODE_FAN_ONLY = 0x38;
|
||||
const uint8_t MITSUBISHI_MODE_HEAT = 0x08;
|
||||
|
||||
const uint8_t MITSUBISHI_MODE_A_HEAT = 0x00;
|
||||
const uint8_t MITSUBISHI_MODE_A_DRY = 0x02;
|
||||
const uint8_t MITSUBISHI_MODE_A_COOL = 0x06;
|
||||
const uint8_t MITSUBISHI_MODE_A_AUTO = 0x06;
|
||||
|
||||
const uint8_t MITSUBISHI_WIDE_VANE_SWING = 0xC0;
|
||||
|
||||
const uint8_t MITSUBISHI_FAN_AUTO = 0x00;
|
||||
|
||||
const uint8_t MITSUBISHI_VERTICAL_VANE_SWING = 0x38;
|
||||
|
||||
// const uint8_t MITSUBISHI_AUTO = 0X80;
|
||||
const uint8_t MITSUBISHI_OTHERWISE = 0X40;
|
||||
const uint8_t MITSUBISHI_POWERFUL = 0x08;
|
||||
|
||||
// Optional presets used to enable some model features
|
||||
const uint8_t MITSUBISHI_ECONOCOOL = 0x20;
|
||||
const uint8_t MITSUBISHI_NIGHTMODE = 0xC1;
|
||||
|
||||
// Pulse parameters in usec
|
||||
const uint16_t MITSUBISHI_BIT_MARK = 430;
|
||||
const uint16_t MITSUBISHI_ONE_SPACE = 1250;
|
||||
@ -22,19 +41,97 @@ const uint16_t MITSUBISHI_HEADER_MARK = 3500;
|
||||
const uint16_t MITSUBISHI_HEADER_SPACE = 1700;
|
||||
const uint16_t MITSUBISHI_MIN_GAP = 17500;
|
||||
|
||||
// Marker bytes
|
||||
const uint8_t MITSUBISHI_BYTE00 = 0X23;
|
||||
const uint8_t MITSUBISHI_BYTE01 = 0XCB;
|
||||
const uint8_t MITSUBISHI_BYTE02 = 0X26;
|
||||
const uint8_t MITSUBISHI_BYTE03 = 0X01;
|
||||
const uint8_t MITSUBISHI_BYTE04 = 0X00;
|
||||
const uint8_t MITSUBISHI_BYTE13 = 0X00;
|
||||
const uint8_t MITSUBISHI_BYTE16 = 0X00;
|
||||
|
||||
climate::ClimateTraits MitsubishiClimate::traits() {
|
||||
auto traits = climate::ClimateTraits();
|
||||
traits.set_supports_action(false);
|
||||
traits.set_visual_min_temperature(MITSUBISHI_TEMP_MIN);
|
||||
traits.set_visual_max_temperature(MITSUBISHI_TEMP_MAX);
|
||||
traits.set_visual_temperature_step(1.0f);
|
||||
traits.set_supported_modes({climate::CLIMATE_MODE_OFF});
|
||||
|
||||
if (this->supports_cool_)
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_COOL);
|
||||
if (this->supports_heat_)
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_HEAT);
|
||||
|
||||
if (this->supports_cool_ && this->supports_heat_)
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_HEAT_COOL);
|
||||
|
||||
if (this->supports_dry_)
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_DRY);
|
||||
if (this->supports_fan_only_)
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_FAN_ONLY);
|
||||
|
||||
// Default to only 3 levels in ESPHome even if most unit supports 4. The 3rd level is not used.
|
||||
traits.set_supported_fan_modes(
|
||||
{climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH});
|
||||
if (this->fan_mode_ == MITSUBISHI_FAN_Q4L)
|
||||
traits.add_supported_fan_mode(climate::CLIMATE_FAN_QUIET);
|
||||
if (/*this->fan_mode_ == MITSUBISHI_FAN_5L ||*/ this->fan_mode_ >= MITSUBISHI_FAN_4L)
|
||||
traits.add_supported_fan_mode(climate::CLIMATE_FAN_MIDDLE); // Shouldn't be used for this but it helps
|
||||
|
||||
traits.set_supported_swing_modes({climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_BOTH,
|
||||
climate::CLIMATE_SWING_VERTICAL, climate::CLIMATE_SWING_HORIZONTAL});
|
||||
|
||||
traits.set_supported_presets({climate::CLIMATE_PRESET_NONE, climate::CLIMATE_PRESET_ECO,
|
||||
climate::CLIMATE_PRESET_BOOST, climate::CLIMATE_PRESET_SLEEP});
|
||||
|
||||
return traits;
|
||||
}
|
||||
|
||||
void MitsubishiClimate::transmit_state() {
|
||||
uint32_t remote_state[18] = {0x23, 0xCB, 0x26, 0x01, 0x00, 0x20, 0x08, 0x00, 0x30,
|
||||
0x58, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
// Byte 0-4: Constant: 0x23, 0xCB, 0x26, 0x01, 0x00
|
||||
// Byte 5: On=0x20, Off: 0x00
|
||||
// Byte 6: MODE (See MODEs above (Heat/Dry/Cool/Auto/FanOnly)
|
||||
// Byte 7: TEMP bits 0,1,2,3, added to MITSUBISHI_TEMP_MIN
|
||||
// Example: 0x00 = 0°C+MITSUBISHI_TEMP_MIN = 16°C; 0x07 = 7°C+MITSUBISHI_TEMP_MIN = 23°C
|
||||
// Byte 8: MODE_A & Wide Vane (if present)
|
||||
// MODE_A bits 0,1,2 different than Byte 6 (See MODE_As above)
|
||||
// Wide Vane bits 4,5,6,7 (Middle = 0x30)
|
||||
// Byte 9: FAN/Vertical Vane/Switch To Auto
|
||||
// FAN (Speed) bits 0,1,2
|
||||
// Vertical Vane bits 3,4,5 (Auto = 0x00)
|
||||
// Switch To Auto bits 6,7
|
||||
// Byte 10: CLOCK Current time as configured on remote (0x00=Not used)
|
||||
// Byte 11: END CLOCK Stop time of HVAC (0x00 for no setting)
|
||||
// Byte 12: START CLOCK Start time of HVAC (0x00 for no setting)
|
||||
// Byte 13: Constant 0x00
|
||||
// Byte 14: HVAC specfic, i.e. ECONO COOL, CLEAN MODE, always 0x00
|
||||
// Byte 15: HVAC specfic, i.e. POWERFUL, SMART SET, PLASMA, always 0x00
|
||||
// Byte 16: Constant 0x00
|
||||
// Byte 17: Checksum: SUM[Byte0...Byte16]
|
||||
uint32_t remote_state[18] = {0x23, 0xCB, 0x26, 0x01, 0x00, 0x20, 0x08, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
|
||||
switch (this->mode) {
|
||||
case climate::CLIMATE_MODE_COOL:
|
||||
remote_state[6] = MITSUBISHI_COOL;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_HEAT:
|
||||
remote_state[6] = MITSUBISHI_HEAT;
|
||||
remote_state[6] = MITSUBISHI_MODE_HEAT;
|
||||
remote_state[8] = MITSUBISHI_MODE_A_HEAT;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_DRY:
|
||||
remote_state[6] = MITSUBISHI_MODE_DRY;
|
||||
remote_state[8] = MITSUBISHI_MODE_A_DRY;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_COOL:
|
||||
remote_state[6] = MITSUBISHI_MODE_COOL;
|
||||
remote_state[8] = MITSUBISHI_MODE_A_COOL;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_HEAT_COOL:
|
||||
remote_state[6] = MITSUBISHI_AUTO;
|
||||
remote_state[6] = MITSUBISHI_MODE_AUTO;
|
||||
remote_state[8] = MITSUBISHI_MODE_A_AUTO;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_FAN_ONLY:
|
||||
remote_state[6] = MITSUBISHI_MODE_FAN_ONLY;
|
||||
remote_state[8] = MITSUBISHI_MODE_A_AUTO;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_OFF:
|
||||
default:
|
||||
@ -42,17 +139,111 @@ void MitsubishiClimate::transmit_state() {
|
||||
break;
|
||||
}
|
||||
|
||||
remote_state[7] = (uint8_t) roundf(clamp<float>(this->target_temperature, MITSUBISHI_TEMP_MIN, MITSUBISHI_TEMP_MAX) -
|
||||
MITSUBISHI_TEMP_MIN);
|
||||
// Temperature
|
||||
if (this->mode == climate::CLIMATE_MODE_DRY) {
|
||||
remote_state[7] = 24 - MITSUBISHI_TEMP_MIN; // Remote sends always 24°C if "Dry" mode is selected
|
||||
} else {
|
||||
remote_state[7] = (uint8_t) roundf(
|
||||
clamp<float>(this->target_temperature, MITSUBISHI_TEMP_MIN, MITSUBISHI_TEMP_MAX) - MITSUBISHI_TEMP_MIN);
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "Sending Mitsubishi target temp: %.1f state: %02" PRIX32 " mode: %02" PRIX32 " temp: %02" PRIX32,
|
||||
this->target_temperature, remote_state[5], remote_state[6], remote_state[7]);
|
||||
// Wide Vane
|
||||
switch (this->swing_mode) {
|
||||
case climate::CLIMATE_SWING_HORIZONTAL:
|
||||
case climate::CLIMATE_SWING_BOTH:
|
||||
remote_state[8] = remote_state[8] | MITSUBISHI_WIDE_VANE_SWING; // Wide Vane Swing
|
||||
break;
|
||||
case climate::CLIMATE_SWING_OFF:
|
||||
default:
|
||||
remote_state[8] = remote_state[8] | this->default_horizontal_direction_; // Off--> horizontal default position
|
||||
break;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "default_horizontal_direction_: %02X", this->default_horizontal_direction_);
|
||||
|
||||
// Fan Speed & Vertical Vane
|
||||
// Map of Climate fan mode to this device expected value
|
||||
// For 3Level: Low = 1, Medium = 2, High = 3
|
||||
// For 4Level: Low = 1, Middle = 2, Medium = 3, High = 4
|
||||
// For 5Level: Low = 1, Middle = 2, Medium = 3, High = 4
|
||||
// For 4Level + Quiet: Low = 1, Middle = 2, Medium = 3, High = 4, Quiet = 5
|
||||
|
||||
switch (this->fan_mode.value()) {
|
||||
case climate::CLIMATE_FAN_LOW:
|
||||
remote_state[9] = 1;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_MEDIUM:
|
||||
if (this->fan_mode_ == MITSUBISHI_FAN_3L) {
|
||||
remote_state[9] = 2;
|
||||
} else {
|
||||
remote_state[9] = 3;
|
||||
}
|
||||
break;
|
||||
case climate::CLIMATE_FAN_HIGH:
|
||||
if (this->fan_mode_ == MITSUBISHI_FAN_3L) {
|
||||
remote_state[9] = 3;
|
||||
} else {
|
||||
remote_state[9] = 4;
|
||||
}
|
||||
break;
|
||||
case climate::CLIMATE_FAN_MIDDLE:
|
||||
remote_state[9] = 2;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_QUIET:
|
||||
remote_state[9] = 5;
|
||||
break;
|
||||
default:
|
||||
remote_state[9] = MITSUBISHI_FAN_AUTO;
|
||||
break;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "fan: %02x state: %02x", this->fan_mode.value(), remote_state[9]);
|
||||
|
||||
// Vertical Vane
|
||||
switch (this->swing_mode) {
|
||||
case climate::CLIMATE_SWING_VERTICAL:
|
||||
case climate::CLIMATE_SWING_BOTH:
|
||||
remote_state[9] = remote_state[9] | MITSUBISHI_VERTICAL_VANE_SWING | MITSUBISHI_OTHERWISE; // Vane Swing
|
||||
break;
|
||||
case climate::CLIMATE_SWING_OFF:
|
||||
default:
|
||||
remote_state[9] = remote_state[9] | this->default_vertical_direction_ |
|
||||
MITSUBISHI_OTHERWISE; // Off--> vertical default position
|
||||
break;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "default_vertical_direction_: %02X", this->default_vertical_direction_);
|
||||
|
||||
// Special modes
|
||||
switch (this->preset.value()) {
|
||||
case climate::CLIMATE_PRESET_ECO:
|
||||
remote_state[6] = MITSUBISHI_MODE_COOL | MITSUBISHI_OTHERWISE;
|
||||
remote_state[8] = (remote_state[8] & ~7) | MITSUBISHI_MODE_A_COOL;
|
||||
remote_state[14] = MITSUBISHI_ECONOCOOL;
|
||||
break;
|
||||
case climate::CLIMATE_PRESET_SLEEP:
|
||||
remote_state[9] = MITSUBISHI_FAN_AUTO;
|
||||
remote_state[14] = MITSUBISHI_NIGHTMODE;
|
||||
break;
|
||||
case climate::CLIMATE_PRESET_BOOST:
|
||||
remote_state[6] |= MITSUBISHI_OTHERWISE;
|
||||
remote_state[15] = MITSUBISHI_POWERFUL;
|
||||
break;
|
||||
case climate::CLIMATE_PRESET_NONE:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Checksum
|
||||
for (int i = 0; i < 17; i++) {
|
||||
remote_state[17] += remote_state[i];
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "sending: %02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X",
|
||||
remote_state[0], remote_state[1], remote_state[2], remote_state[3], remote_state[4], remote_state[5],
|
||||
remote_state[6], remote_state[7], remote_state[8], remote_state[9], remote_state[10], remote_state[11],
|
||||
remote_state[12], remote_state[13], remote_state[14], remote_state[15], remote_state[16], remote_state[17]);
|
||||
|
||||
auto transmit = this->transmitter_->transmit();
|
||||
auto *data = transmit.get_data();
|
||||
|
||||
@ -81,5 +272,119 @@ void MitsubishiClimate::transmit_state() {
|
||||
transmit.perform();
|
||||
}
|
||||
|
||||
bool MitsubishiClimate::parse_state_frame_(const uint8_t frame[]) { return false; }
|
||||
|
||||
bool MitsubishiClimate::on_receive(remote_base::RemoteReceiveData data) {
|
||||
uint8_t state_frame[18] = {};
|
||||
|
||||
if (!data.expect_item(MITSUBISHI_HEADER_MARK, MITSUBISHI_HEADER_SPACE)) {
|
||||
ESP_LOGV(TAG, "Header fail");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (uint8_t pos = 0; pos < 18; pos++) {
|
||||
uint8_t byte = 0;
|
||||
for (int8_t bit = 0; bit < 8; bit++) {
|
||||
if (data.expect_item(MITSUBISHI_BIT_MARK, MITSUBISHI_ONE_SPACE)) {
|
||||
byte |= 1 << bit;
|
||||
} else if (!data.expect_item(MITSUBISHI_BIT_MARK, MITSUBISHI_ZERO_SPACE)) {
|
||||
ESP_LOGV(TAG, "Byte %d bit %d fail", pos, bit);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
state_frame[pos] = byte;
|
||||
|
||||
// Check Header && Footer
|
||||
if ((pos == 0 && byte != MITSUBISHI_BYTE00) || (pos == 1 && byte != MITSUBISHI_BYTE01) ||
|
||||
(pos == 2 && byte != MITSUBISHI_BYTE02) || (pos == 3 && byte != MITSUBISHI_BYTE03) ||
|
||||
(pos == 4 && byte != MITSUBISHI_BYTE04) || (pos == 13 && byte != MITSUBISHI_BYTE13) ||
|
||||
(pos == 16 && byte != MITSUBISHI_BYTE16)) {
|
||||
ESP_LOGV(TAG, "Bytes 0,1,2,3,4,13 or 16 fail - invalid value");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// On/Off and Mode
|
||||
if (state_frame[5] == MITSUBISHI_OFF) {
|
||||
this->mode = climate::CLIMATE_MODE_OFF;
|
||||
} else {
|
||||
switch (state_frame[6]) {
|
||||
case MITSUBISHI_MODE_HEAT:
|
||||
this->mode = climate::CLIMATE_MODE_HEAT;
|
||||
break;
|
||||
case MITSUBISHI_MODE_DRY:
|
||||
this->mode = climate::CLIMATE_MODE_DRY;
|
||||
break;
|
||||
case MITSUBISHI_MODE_COOL:
|
||||
this->mode = climate::CLIMATE_MODE_COOL;
|
||||
break;
|
||||
case MITSUBISHI_MODE_FAN_ONLY:
|
||||
this->mode = climate::CLIMATE_MODE_FAN_ONLY;
|
||||
break;
|
||||
case MITSUBISHI_MODE_AUTO:
|
||||
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Temp
|
||||
this->target_temperature = state_frame[7] + MITSUBISHI_TEMP_MIN;
|
||||
|
||||
// Fan
|
||||
uint8_t fan = state_frame[9] & 0x07; //(Bit 0,1,2 = Speed)
|
||||
// Map of Climate fan mode to this device expected value
|
||||
// For 3Level: Low = 1, Medium = 2, High = 3
|
||||
// For 4Level: Low = 1, Middle = 2, Medium = 3, High = 4
|
||||
// For 5Level: Low = 1, Middle = 2, Medium = 3, High = 4
|
||||
// For 4Level + Quiet: Low = 1, Middle = 2, Medium = 3, High = 4, Quiet = 5
|
||||
climate::ClimateFanMode modes_mapping[8] = {
|
||||
climate::CLIMATE_FAN_AUTO,
|
||||
climate::CLIMATE_FAN_LOW,
|
||||
this->fan_mode_ == MITSUBISHI_FAN_3L ? climate::CLIMATE_FAN_MEDIUM : climate::CLIMATE_FAN_MIDDLE,
|
||||
this->fan_mode_ == MITSUBISHI_FAN_3L ? climate::CLIMATE_FAN_HIGH : climate::CLIMATE_FAN_MEDIUM,
|
||||
climate::CLIMATE_FAN_HIGH,
|
||||
climate::CLIMATE_FAN_QUIET,
|
||||
climate::CLIMATE_FAN_AUTO,
|
||||
climate::CLIMATE_FAN_AUTO};
|
||||
this->fan_mode = modes_mapping[fan];
|
||||
|
||||
// Wide Vane
|
||||
uint8_t wide_vane = state_frame[8] & 0xF0; // Bits 4,5,6,7
|
||||
switch (wide_vane) {
|
||||
case MITSUBISHI_WIDE_VANE_SWING:
|
||||
this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL;
|
||||
break;
|
||||
default:
|
||||
this->swing_mode = climate::CLIMATE_SWING_OFF;
|
||||
break;
|
||||
}
|
||||
|
||||
// Vertical Vane
|
||||
uint8_t vertical_vane = state_frame[9] & 0x38; // Bits 3,4,5
|
||||
switch (vertical_vane) {
|
||||
case MITSUBISHI_VERTICAL_VANE_SWING:
|
||||
if (this->swing_mode == climate::CLIMATE_SWING_HORIZONTAL) {
|
||||
this->swing_mode = climate::CLIMATE_SWING_BOTH;
|
||||
} else {
|
||||
this->swing_mode = climate::CLIMATE_SWING_VERTICAL;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
switch (state_frame[14]) {
|
||||
case MITSUBISHI_ECONOCOOL:
|
||||
this->preset = climate::CLIMATE_PRESET_ECO;
|
||||
break;
|
||||
case MITSUBISHI_NIGHTMODE:
|
||||
this->preset = climate::CLIMATE_PRESET_SLEEP;
|
||||
break;
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "Receiving: %s", format_hex_pretty(state_frame, 18).c_str());
|
||||
|
||||
this->publish_state();
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace mitsubishi
|
||||
} // namespace esphome
|
||||
|
@ -11,13 +11,72 @@ namespace mitsubishi {
|
||||
const uint8_t MITSUBISHI_TEMP_MIN = 16; // Celsius
|
||||
const uint8_t MITSUBISHI_TEMP_MAX = 31; // Celsius
|
||||
|
||||
// Fan mode
|
||||
enum SetFanMode {
|
||||
MITSUBISHI_FAN_3L = 0, // 3 levels + auto
|
||||
MITSUBISHI_FAN_4L, // 4 levels + auto
|
||||
MITSUBISHI_FAN_Q4L, // Quiet + 4 levels + auto
|
||||
// MITSUBISHI_FAN_5L, // 5 levels + auto
|
||||
};
|
||||
|
||||
// Enum to represent horizontal directios
|
||||
enum HorizontalDirection {
|
||||
HORIZONTAL_DIRECTION_LEFT = 0x10,
|
||||
HORIZONTAL_DIRECTION_MIDDLE_LEFT = 0x20,
|
||||
HORIZONTAL_DIRECTION_MIDDLE = 0x30,
|
||||
HORIZONTAL_DIRECTION_MIDDLE_RIGHT = 0x40,
|
||||
HORIZONTAL_DIRECTION_RIGHT = 0x50,
|
||||
HORIZONTAL_DIRECTION_SPLIT = 0x80,
|
||||
};
|
||||
|
||||
// Enum to represent vertical directions
|
||||
enum VerticalDirection {
|
||||
VERTICAL_DIRECTION_AUTO = 0x00,
|
||||
VERTICAL_DIRECTION_UP = 0x08,
|
||||
VERTICAL_DIRECTION_MIDDLE_UP = 0x10,
|
||||
VERTICAL_DIRECTION_MIDDLE = 0x18,
|
||||
VERTICAL_DIRECTION_MIDDLE_DOWN = 0x20,
|
||||
VERTICAL_DIRECTION_DOWN = 0x28,
|
||||
};
|
||||
|
||||
class MitsubishiClimate : public climate_ir::ClimateIR {
|
||||
public:
|
||||
MitsubishiClimate() : climate_ir::ClimateIR(MITSUBISHI_TEMP_MIN, MITSUBISHI_TEMP_MAX) {}
|
||||
MitsubishiClimate()
|
||||
: climate_ir::ClimateIR(MITSUBISHI_TEMP_MIN, MITSUBISHI_TEMP_MAX, 1.0f, true, true,
|
||||
{climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MIDDLE,
|
||||
climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH, climate::CLIMATE_FAN_QUIET},
|
||||
{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_BOTH, climate::CLIMATE_SWING_VERTICAL,
|
||||
climate::CLIMATE_SWING_HORIZONTAL},
|
||||
{climate::CLIMATE_PRESET_NONE, climate::CLIMATE_PRESET_ECO, climate::CLIMATE_PRESET_BOOST,
|
||||
climate::CLIMATE_PRESET_SLEEP}) {}
|
||||
|
||||
void set_supports_cool(bool supports_cool) { this->supports_cool_ = supports_cool; }
|
||||
void set_supports_dry(bool supports_dry) { this->supports_dry_ = supports_dry; }
|
||||
void set_supports_fan_only(bool supports_fan_only) { this->supports_fan_only_ = supports_fan_only; }
|
||||
void set_supports_heat(bool supports_heat) { this->supports_heat_ = supports_heat; }
|
||||
|
||||
void set_fan_mode(SetFanMode fan_mode) { this->fan_mode_ = fan_mode; }
|
||||
|
||||
void set_horizontal_default(HorizontalDirection horizontal_direction) {
|
||||
this->default_horizontal_direction_ = horizontal_direction;
|
||||
}
|
||||
void set_vertical_default(VerticalDirection vertical_direction) {
|
||||
this->default_vertical_direction_ = vertical_direction;
|
||||
}
|
||||
|
||||
protected:
|
||||
/// Transmit via IR the state of this climate controller.
|
||||
// Transmit via IR the state of this climate controller.
|
||||
void transmit_state() override;
|
||||
// Handle received IR Buffer
|
||||
bool on_receive(remote_base::RemoteReceiveData data) override;
|
||||
bool parse_state_frame_(const uint8_t frame[]);
|
||||
|
||||
SetFanMode fan_mode_;
|
||||
|
||||
HorizontalDirection default_horizontal_direction_;
|
||||
VerticalDirection default_vertical_direction_;
|
||||
|
||||
climate::ClimateTraits traits() override;
|
||||
};
|
||||
|
||||
} // namespace mitsubishi
|
||||
|
@ -13,10 +13,10 @@ void ModbusTextSensor::dump_config() { LOG_TEXT_SENSOR("", "Modbus Controller Te
|
||||
|
||||
void ModbusTextSensor::parse_and_publish(const std::vector<uint8_t> &data) {
|
||||
std::ostringstream output;
|
||||
uint8_t max_items = this->response_bytes;
|
||||
uint8_t items_left = this->response_bytes;
|
||||
uint8_t index = this->offset;
|
||||
char buffer[4];
|
||||
while ((max_items != 0) && index < data.size()) {
|
||||
while ((items_left > 0) && index < data.size()) {
|
||||
uint8_t b = data[index];
|
||||
switch (this->encode_) {
|
||||
case RawEncoding::HEXBYTES:
|
||||
@ -33,7 +33,7 @@ void ModbusTextSensor::parse_and_publish(const std::vector<uint8_t> &data) {
|
||||
output << (char) b;
|
||||
break;
|
||||
}
|
||||
|
||||
items_left--;
|
||||
index++;
|
||||
}
|
||||
|
||||
|
@ -113,6 +113,7 @@ MQTTSensorComponent = mqtt_ns.class_("MQTTSensorComponent", MQTTComponent)
|
||||
MQTTSwitchComponent = mqtt_ns.class_("MQTTSwitchComponent", MQTTComponent)
|
||||
MQTTTextSensor = mqtt_ns.class_("MQTTTextSensor", MQTTComponent)
|
||||
MQTTNumberComponent = mqtt_ns.class_("MQTTNumberComponent", MQTTComponent)
|
||||
MQTTDatetimeComponent = mqtt_ns.class_("MQTTDatetimeComponent", MQTTComponent)
|
||||
MQTTTextComponent = mqtt_ns.class_("MQTTTextComponent", MQTTComponent)
|
||||
MQTTSelectComponent = mqtt_ns.class_("MQTTSelectComponent", MQTTComponent)
|
||||
MQTTButtonComponent = mqtt_ns.class_("MQTTButtonComponent", MQTTComponent)
|
||||
@ -489,6 +490,8 @@ def get_default_topic_for(data, component_type, name, suffix):
|
||||
async def register_mqtt_component(var, config):
|
||||
await cg.register_component(var, {})
|
||||
|
||||
if CONF_QOS in config:
|
||||
cg.add(var.set_qos(config[CONF_QOS]))
|
||||
if CONF_RETAIN in config:
|
||||
cg.add(var.set_retain(config[CONF_RETAIN]))
|
||||
if not config.get(CONF_DISCOVERY, True):
|
||||
|
@ -474,8 +474,8 @@ bool MQTTClientComponent::publish(const MQTTMessage &message) {
|
||||
|
||||
if (!logging_topic) {
|
||||
if (ret) {
|
||||
ESP_LOGV(TAG, "Publish(topic='%s' payload='%s' retain=%d)", message.topic.c_str(), message.payload.c_str(),
|
||||
message.retain);
|
||||
ESP_LOGV(TAG, "Publish(topic='%s' payload='%s' retain=%d qos=%d)", message.topic.c_str(), message.payload.c_str(),
|
||||
message.retain, message.qos);
|
||||
} else {
|
||||
ESP_LOGV(TAG, "Publish failed for topic='%s' (len=%u). will retry later..", message.topic.c_str(),
|
||||
message.payload.length());
|
||||
|
@ -14,6 +14,8 @@ namespace mqtt {
|
||||
|
||||
static const char *const TAG = "mqtt.component";
|
||||
|
||||
void MQTTComponent::set_qos(uint8_t qos) { this->qos_ = qos; }
|
||||
|
||||
void MQTTComponent::set_retain(bool retain) { this->retain_ = retain; }
|
||||
|
||||
std::string MQTTComponent::get_discovery_topic_(const MQTTDiscoveryInfo &discovery_info) const {
|
||||
@ -47,13 +49,13 @@ std::string MQTTComponent::get_command_topic_() const {
|
||||
bool MQTTComponent::publish(const std::string &topic, const std::string &payload) {
|
||||
if (topic.empty())
|
||||
return false;
|
||||
return global_mqtt_client->publish(topic, payload, 0, this->retain_);
|
||||
return global_mqtt_client->publish(topic, payload, this->qos_, this->retain_);
|
||||
}
|
||||
|
||||
bool MQTTComponent::publish_json(const std::string &topic, const json::json_build_t &f) {
|
||||
if (topic.empty())
|
||||
return false;
|
||||
return global_mqtt_client->publish_json(topic, f, 0, this->retain_);
|
||||
return global_mqtt_client->publish_json(topic, f, this->qos_, this->retain_);
|
||||
}
|
||||
|
||||
bool MQTTComponent::send_discovery_() {
|
||||
@ -61,7 +63,7 @@ bool MQTTComponent::send_discovery_() {
|
||||
|
||||
if (discovery_info.clean) {
|
||||
ESP_LOGV(TAG, "'%s': Cleaning discovery...", this->friendly_name().c_str());
|
||||
return global_mqtt_client->publish(this->get_discovery_topic_(discovery_info), "", 0, 0, true);
|
||||
return global_mqtt_client->publish(this->get_discovery_topic_(discovery_info), "", 0, this->qos_, true);
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "'%s': Sending discovery...", this->friendly_name().c_str());
|
||||
@ -155,9 +157,11 @@ bool MQTTComponent::send_discovery_() {
|
||||
device_info[MQTT_DEVICE_MANUFACTURER] = "espressif";
|
||||
device_info[MQTT_DEVICE_SUGGESTED_AREA] = node_area;
|
||||
},
|
||||
0, discovery_info.retain);
|
||||
this->qos_, discovery_info.retain);
|
||||
}
|
||||
|
||||
uint8_t MQTTComponent::get_qos() const { return this->qos_; }
|
||||
|
||||
bool MQTTComponent::get_retain() const { return this->retain_; }
|
||||
|
||||
bool MQTTComponent::is_discovery_enabled() const {
|
||||
|
@ -77,6 +77,10 @@ class MQTTComponent : public Component {
|
||||
|
||||
virtual bool is_internal();
|
||||
|
||||
/// Set QOS for state messages.
|
||||
void set_qos(uint8_t qos);
|
||||
uint8_t get_qos() const;
|
||||
|
||||
/// Set whether state message should be retained.
|
||||
void set_retain(bool retain);
|
||||
bool get_retain() const;
|
||||
@ -199,6 +203,7 @@ class MQTTComponent : public Component {
|
||||
|
||||
bool command_retain_{false};
|
||||
bool retain_{true};
|
||||
uint8_t qos_{0};
|
||||
bool discovery_enabled_{true};
|
||||
bool resend_state_{false};
|
||||
};
|
||||
|
68
esphome/components/mqtt/mqtt_date.cpp
Normal file
68
esphome/components/mqtt/mqtt_date.cpp
Normal file
@ -0,0 +1,68 @@
|
||||
#include "mqtt_date.h"
|
||||
|
||||
#include <utility>
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include "mqtt_const.h"
|
||||
|
||||
#ifdef USE_MQTT
|
||||
#ifdef USE_DATETIME_DATE
|
||||
|
||||
namespace esphome {
|
||||
namespace mqtt {
|
||||
|
||||
static const char *const TAG = "mqtt.datetime";
|
||||
|
||||
using namespace esphome::datetime;
|
||||
|
||||
MQTTDateComponent::MQTTDateComponent(DateEntity *date) : date_(date) {}
|
||||
|
||||
void MQTTDateComponent::setup() {
|
||||
this->subscribe_json(this->get_command_topic_(), [this](const std::string &topic, JsonObject root) {
|
||||
auto call = this->date_->make_call();
|
||||
if (root.containsKey("year")) {
|
||||
call.set_year(root["year"]);
|
||||
}
|
||||
if (root.containsKey("month")) {
|
||||
call.set_month(root["month"]);
|
||||
}
|
||||
if (root.containsKey("day")) {
|
||||
call.set_day(root["day"]);
|
||||
}
|
||||
call.perform();
|
||||
});
|
||||
this->date_->add_on_state_callback(
|
||||
[this]() { this->publish_state(this->date_->year, this->date_->month, this->date_->day); });
|
||||
}
|
||||
|
||||
void MQTTDateComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "MQTT Date '%s':", this->date_->get_name().c_str());
|
||||
LOG_MQTT_COMPONENT(true, true)
|
||||
}
|
||||
|
||||
std::string MQTTDateComponent::component_type() const { return "date"; }
|
||||
const EntityBase *MQTTDateComponent::get_entity() const { return this->date_; }
|
||||
|
||||
void MQTTDateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
|
||||
// Nothing extra to add here
|
||||
}
|
||||
bool MQTTDateComponent::send_initial_state() {
|
||||
if (this->date_->has_state()) {
|
||||
return this->publish_state(this->date_->year, this->date_->month, this->date_->day);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
bool MQTTDateComponent::publish_state(uint16_t year, uint8_t month, uint8_t day) {
|
||||
return this->publish_json(this->get_state_topic_(), [year, month, day](JsonObject root) {
|
||||
root["year"] = year;
|
||||
root["month"] = month;
|
||||
root["day"] = day;
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace mqtt
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_DATETIME_DATE
|
||||
#endif // USE_MQTT
|
45
esphome/components/mqtt/mqtt_date.h
Normal file
45
esphome/components/mqtt/mqtt_date.h
Normal file
@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
#ifdef USE_MQTT
|
||||
#ifdef USE_DATETIME_DATE
|
||||
|
||||
#include "esphome/components/datetime/date_entity.h"
|
||||
#include "mqtt_component.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace mqtt {
|
||||
|
||||
class MQTTDateComponent : public mqtt::MQTTComponent {
|
||||
public:
|
||||
/** Construct this MQTTDatetimeComponent instance with the provided friendly_name and datetime
|
||||
*
|
||||
* @param datetime The datetime component.
|
||||
*/
|
||||
explicit MQTTDateComponent(datetime::DateEntity *date);
|
||||
|
||||
// ========== INTERNAL METHODS ==========
|
||||
// (In most use cases you won't need these)
|
||||
/// Override setup.
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
void send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) override;
|
||||
|
||||
bool send_initial_state() override;
|
||||
|
||||
bool publish_state(uint16_t year, uint8_t month, uint8_t day);
|
||||
|
||||
protected:
|
||||
std::string component_type() const override;
|
||||
const EntityBase *get_entity() const override;
|
||||
|
||||
datetime::DateEntity *date_;
|
||||
};
|
||||
|
||||
} // namespace mqtt
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_DATETIME_DATE
|
||||
#endif // USE_MQTT
|
@ -226,6 +226,7 @@ void RotaryEncoderSensor::loop() {
|
||||
}
|
||||
this->store_.last_read = counter;
|
||||
this->publish_state(counter);
|
||||
this->listeners_.call(counter);
|
||||
this->publish_initial_value_ = false;
|
||||
}
|
||||
}
|
||||
|
@ -92,6 +92,8 @@ class RotaryEncoderSensor : public sensor::Sensor, public Component {
|
||||
this->on_anticlockwise_callback_.add(std::move(callback));
|
||||
}
|
||||
|
||||
void register_listener(std::function<void(uint32_t)> listener) { this->listeners_.add(std::move(listener)); }
|
||||
|
||||
protected:
|
||||
InternalGPIOPin *pin_a_;
|
||||
InternalGPIOPin *pin_b_;
|
||||
@ -102,8 +104,9 @@ class RotaryEncoderSensor : public sensor::Sensor, public Component {
|
||||
|
||||
RotaryEncoderSensorStore store_{};
|
||||
|
||||
CallbackManager<void()> on_clockwise_callback_;
|
||||
CallbackManager<void()> on_anticlockwise_callback_;
|
||||
CallbackManager<void()> on_clockwise_callback_{};
|
||||
CallbackManager<void()> on_anticlockwise_callback_{};
|
||||
CallbackManager<void(int32_t)> listeners_{};
|
||||
};
|
||||
|
||||
template<typename... Ts> class RotaryEncoderSetValueAction : public Action<Ts...> {
|
||||
|
1
esphome/components/rpi_dpi_rgb/__init__.py
Normal file
1
esphome/components/rpi_dpi_rgb/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
CODEOWNERS = ["@clydebarrow"]
|
197
esphome/components/rpi_dpi_rgb/display.py
Normal file
197
esphome/components/rpi_dpi_rgb/display.py
Normal file
@ -0,0 +1,197 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import pins
|
||||
from esphome.components import display
|
||||
from esphome.const import (
|
||||
CONF_RESET_PIN,
|
||||
CONF_DATA_PINS,
|
||||
CONF_ID,
|
||||
CONF_IGNORE_STRAPPING_WARNING,
|
||||
CONF_DIMENSIONS,
|
||||
CONF_WIDTH,
|
||||
CONF_HEIGHT,
|
||||
CONF_LAMBDA,
|
||||
CONF_COLOR_ORDER,
|
||||
CONF_RED,
|
||||
CONF_GREEN,
|
||||
CONF_BLUE,
|
||||
CONF_NUMBER,
|
||||
CONF_OFFSET_HEIGHT,
|
||||
CONF_OFFSET_WIDTH,
|
||||
CONF_INVERT_COLORS,
|
||||
)
|
||||
from esphome.components.esp32 import (
|
||||
only_on_variant,
|
||||
const,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["esp32"]
|
||||
|
||||
CONF_DE_PIN = "de_pin"
|
||||
CONF_PCLK_PIN = "pclk_pin"
|
||||
CONF_HSYNC_PIN = "hsync_pin"
|
||||
CONF_VSYNC_PIN = "vsync_pin"
|
||||
|
||||
CONF_HSYNC_FRONT_PORCH = "hsync_front_porch"
|
||||
CONF_HSYNC_PULSE_WIDTH = "hsync_pulse_width"
|
||||
CONF_HSYNC_BACK_PORCH = "hsync_back_porch"
|
||||
CONF_VSYNC_FRONT_PORCH = "vsync_front_porch"
|
||||
CONF_VSYNC_PULSE_WIDTH = "vsync_pulse_width"
|
||||
CONF_VSYNC_BACK_PORCH = "vsync_back_porch"
|
||||
CONF_PCLK_FREQUENCY = "pclk_frequency"
|
||||
CONF_PCLK_INVERTED = "pclk_inverted"
|
||||
|
||||
rpi_dpi_rgb_ns = cg.esphome_ns.namespace("rpi_dpi_rgb")
|
||||
RPI_DPI_RGB = rpi_dpi_rgb_ns.class_("RpiDpiRgb", display.Display, cg.Component)
|
||||
ColorOrder = display.display_ns.enum("ColorMode")
|
||||
|
||||
COLOR_ORDERS = {
|
||||
"RGB": ColorOrder.COLOR_ORDER_RGB,
|
||||
"BGR": ColorOrder.COLOR_ORDER_BGR,
|
||||
}
|
||||
DATA_PIN_SCHEMA = pins.internal_gpio_output_pin_schema
|
||||
|
||||
|
||||
def data_pin_validate(value):
|
||||
"""
|
||||
It is safe to use strapping pins as RGB output data bits, as they are outputs only,
|
||||
and not initialised until after boot.
|
||||
"""
|
||||
if not isinstance(value, dict):
|
||||
try:
|
||||
return DATA_PIN_SCHEMA(
|
||||
{CONF_NUMBER: value, CONF_IGNORE_STRAPPING_WARNING: True}
|
||||
)
|
||||
except cv.Invalid:
|
||||
pass
|
||||
return DATA_PIN_SCHEMA(value)
|
||||
|
||||
|
||||
def data_pin_set(length):
|
||||
return cv.All(
|
||||
[data_pin_validate],
|
||||
cv.Length(min=length, max=length, msg=f"Exactly {length} data pins required"),
|
||||
)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
display.FULL_DISPLAY_SCHEMA.extend(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(RPI_DPI_RGB),
|
||||
cv.Required(CONF_DIMENSIONS): cv.Any(
|
||||
cv.dimensions,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_WIDTH): cv.int_,
|
||||
cv.Required(CONF_HEIGHT): cv.int_,
|
||||
cv.Optional(CONF_OFFSET_HEIGHT, default=0): cv.int_,
|
||||
cv.Optional(CONF_OFFSET_WIDTH, default=0): cv.int_,
|
||||
}
|
||||
),
|
||||
),
|
||||
cv.Optional(CONF_PCLK_FREQUENCY, default="16MHz"): cv.All(
|
||||
cv.frequency, cv.Range(min=4e6, max=30e6)
|
||||
),
|
||||
cv.Optional(CONF_PCLK_INVERTED, default=True): cv.boolean,
|
||||
cv.Required(CONF_DATA_PINS): cv.Any(
|
||||
data_pin_set(16),
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_RED): data_pin_set(5),
|
||||
cv.Required(CONF_GREEN): data_pin_set(6),
|
||||
cv.Required(CONF_BLUE): data_pin_set(5),
|
||||
}
|
||||
),
|
||||
),
|
||||
cv.Optional(CONF_COLOR_ORDER): cv.one_of(
|
||||
*COLOR_ORDERS.keys(), upper=True
|
||||
),
|
||||
cv.Optional(CONF_INVERT_COLORS, default=False): cv.boolean,
|
||||
cv.Required(CONF_DE_PIN): pins.internal_gpio_output_pin_schema,
|
||||
cv.Required(CONF_PCLK_PIN): pins.internal_gpio_output_pin_schema,
|
||||
cv.Required(CONF_HSYNC_PIN): pins.internal_gpio_output_pin_schema,
|
||||
cv.Required(CONF_VSYNC_PIN): pins.internal_gpio_output_pin_schema,
|
||||
cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Optional(CONF_HSYNC_PULSE_WIDTH, default=10): cv.int_,
|
||||
cv.Optional(CONF_HSYNC_BACK_PORCH, default=10): cv.int_,
|
||||
cv.Optional(CONF_HSYNC_FRONT_PORCH, default=20): cv.int_,
|
||||
cv.Optional(CONF_VSYNC_PULSE_WIDTH, default=10): cv.int_,
|
||||
cv.Optional(CONF_VSYNC_BACK_PORCH, default=10): cv.int_,
|
||||
cv.Optional(CONF_VSYNC_FRONT_PORCH, default=10): cv.int_,
|
||||
}
|
||||
)
|
||||
),
|
||||
only_on_variant(supported=[const.VARIANT_ESP32S3]),
|
||||
cv.only_with_esp_idf,
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await display.register_display(var, config)
|
||||
|
||||
cg.add(var.set_color_mode(COLOR_ORDERS[config[CONF_COLOR_ORDER]]))
|
||||
cg.add(var.set_invert_colors(config[CONF_INVERT_COLORS]))
|
||||
cg.add(var.set_hsync_pulse_width(config[CONF_HSYNC_PULSE_WIDTH]))
|
||||
cg.add(var.set_hsync_back_porch(config[CONF_HSYNC_BACK_PORCH]))
|
||||
cg.add(var.set_hsync_front_porch(config[CONF_HSYNC_FRONT_PORCH]))
|
||||
cg.add(var.set_vsync_pulse_width(config[CONF_VSYNC_PULSE_WIDTH]))
|
||||
cg.add(var.set_vsync_back_porch(config[CONF_VSYNC_BACK_PORCH]))
|
||||
cg.add(var.set_vsync_front_porch(config[CONF_VSYNC_FRONT_PORCH]))
|
||||
cg.add(var.set_pclk_inverted(config[CONF_PCLK_INVERTED]))
|
||||
cg.add(var.set_pclk_frequency(config[CONF_PCLK_FREQUENCY]))
|
||||
index = 0
|
||||
dpins = []
|
||||
if CONF_RED in config[CONF_DATA_PINS]:
|
||||
red_pins = config[CONF_DATA_PINS][CONF_RED]
|
||||
green_pins = config[CONF_DATA_PINS][CONF_GREEN]
|
||||
blue_pins = config[CONF_DATA_PINS][CONF_BLUE]
|
||||
if config[CONF_COLOR_ORDER] == "BGR":
|
||||
dpins.extend(red_pins)
|
||||
dpins.extend(green_pins)
|
||||
dpins.extend(blue_pins)
|
||||
else:
|
||||
dpins.extend(blue_pins)
|
||||
dpins.extend(green_pins)
|
||||
dpins.extend(red_pins)
|
||||
# swap bytes to match big-endian format
|
||||
dpins = dpins[8:16] + dpins[0:8]
|
||||
else:
|
||||
dpins = config[CONF_DATA_PINS]
|
||||
for pin in dpins:
|
||||
data_pin = await cg.gpio_pin_expression(pin)
|
||||
cg.add(var.add_data_pin(data_pin, index))
|
||||
index += 1
|
||||
|
||||
if reset_pin := config.get(CONF_RESET_PIN):
|
||||
reset = await cg.gpio_pin_expression(reset_pin)
|
||||
cg.add(var.set_reset_pin(reset))
|
||||
|
||||
if CONF_DIMENSIONS in config:
|
||||
dimensions = config[CONF_DIMENSIONS]
|
||||
if isinstance(dimensions, dict):
|
||||
cg.add(var.set_dimensions(dimensions[CONF_WIDTH], dimensions[CONF_HEIGHT]))
|
||||
cg.add(
|
||||
var.set_offsets(
|
||||
dimensions[CONF_OFFSET_WIDTH], dimensions[CONF_OFFSET_HEIGHT]
|
||||
)
|
||||
)
|
||||
else:
|
||||
(width, height) = dimensions
|
||||
cg.add(var.set_dimensions(width, height))
|
||||
|
||||
if lamb := config.get(CONF_LAMBDA):
|
||||
lambda_ = await cg.process_lambda(
|
||||
lamb, [(display.DisplayRef, "it")], return_type=cg.void
|
||||
)
|
||||
cg.add(var.set_writer(lambda_))
|
||||
|
||||
pin = await cg.gpio_pin_expression(config[CONF_DE_PIN])
|
||||
cg.add(var.set_de_pin(pin))
|
||||
pin = await cg.gpio_pin_expression(config[CONF_PCLK_PIN])
|
||||
cg.add(var.set_pclk_pin(pin))
|
||||
pin = await cg.gpio_pin_expression(config[CONF_HSYNC_PIN])
|
||||
cg.add(var.set_hsync_pin(pin))
|
||||
pin = await cg.gpio_pin_expression(config[CONF_VSYNC_PIN])
|
||||
cg.add(var.set_vsync_pin(pin))
|
116
esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.cpp
Normal file
116
esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.cpp
Normal file
@ -0,0 +1,116 @@
|
||||
#ifdef USE_ESP32_VARIANT_ESP32S3
|
||||
#include "rpi_dpi_rgb.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace rpi_dpi_rgb {
|
||||
|
||||
void RpiDpiRgb::setup() {
|
||||
esph_log_config(TAG, "Setting up RPI_DPI_RGB");
|
||||
esp_lcd_rgb_panel_config_t config{};
|
||||
config.flags.fb_in_psram = 1;
|
||||
config.timings.h_res = this->width_;
|
||||
config.timings.v_res = this->height_;
|
||||
config.timings.hsync_pulse_width = this->hsync_pulse_width_;
|
||||
config.timings.hsync_back_porch = this->hsync_back_porch_;
|
||||
config.timings.hsync_front_porch = this->hsync_front_porch_;
|
||||
config.timings.vsync_pulse_width = this->vsync_pulse_width_;
|
||||
config.timings.vsync_back_porch = this->vsync_back_porch_;
|
||||
config.timings.vsync_front_porch = this->vsync_front_porch_;
|
||||
config.timings.flags.pclk_active_neg = this->pclk_inverted_;
|
||||
config.timings.pclk_hz = this->pclk_frequency_;
|
||||
config.clk_src = LCD_CLK_SRC_PLL160M;
|
||||
config.sram_trans_align = 64;
|
||||
config.psram_trans_align = 64;
|
||||
size_t data_pin_count = sizeof(this->data_pins_) / sizeof(this->data_pins_[0]);
|
||||
for (size_t i = 0; i != data_pin_count; i++) {
|
||||
config.data_gpio_nums[i] = this->data_pins_[i]->get_pin();
|
||||
}
|
||||
config.data_width = data_pin_count;
|
||||
config.disp_gpio_num = -1;
|
||||
config.hsync_gpio_num = this->hsync_pin_->get_pin();
|
||||
config.vsync_gpio_num = this->vsync_pin_->get_pin();
|
||||
config.de_gpio_num = this->de_pin_->get_pin();
|
||||
config.pclk_gpio_num = this->pclk_pin_->get_pin();
|
||||
esp_err_t err = esp_lcd_new_rgb_panel(&config, &this->handle_);
|
||||
if (err != ESP_OK) {
|
||||
esph_log_e(TAG, "lcd_new_rgb_panel failed: %s", esp_err_to_name(err));
|
||||
}
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_reset(this->handle_));
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_init(this->handle_));
|
||||
esph_log_config(TAG, "RPI_DPI_RGB setup complete");
|
||||
}
|
||||
|
||||
void RpiDpiRgb::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order,
|
||||
display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) {
|
||||
if (w <= 0 || h <= 0)
|
||||
return;
|
||||
// if color mapping is required, pass the buck.
|
||||
// note that endianness is not considered here - it is assumed to match!
|
||||
if (bitness != display::COLOR_BITNESS_565) {
|
||||
return display::Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset,
|
||||
x_pad);
|
||||
}
|
||||
x_start += this->offset_x_;
|
||||
y_start += this->offset_y_;
|
||||
esp_err_t err;
|
||||
// x_ and y_offset are offsets into the source buffer, unrelated to our own offsets into the display.
|
||||
if (x_offset == 0 && x_pad == 0 && y_offset == 0) {
|
||||
// we could deal here with a non-zero y_offset, but if x_offset is zero, y_offset probably will be so don't bother
|
||||
err = esp_lcd_panel_draw_bitmap(this->handle_, x_start, y_start, x_start + w, y_start + h, ptr);
|
||||
} else {
|
||||
// draw line by line
|
||||
auto stride = x_offset + w + x_pad;
|
||||
for (int y = 0; y != h; y++) {
|
||||
err = esp_lcd_panel_draw_bitmap(this->handle_, x_start, y + y_start, x_start + w, y + y_start + 1,
|
||||
ptr + ((y + y_offset) * stride + x_offset) * 2);
|
||||
if (err != ESP_OK)
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (err != ESP_OK)
|
||||
esph_log_e(TAG, "lcd_lcd_panel_draw_bitmap failed: %s", esp_err_to_name(err));
|
||||
}
|
||||
|
||||
void RpiDpiRgb::draw_pixel_at(int x, int y, Color color) {
|
||||
if (!this->get_clipping().inside(x, y))
|
||||
return; // NOLINT
|
||||
|
||||
switch (this->rotation_) {
|
||||
case display::DISPLAY_ROTATION_0_DEGREES:
|
||||
break;
|
||||
case display::DISPLAY_ROTATION_90_DEGREES:
|
||||
std::swap(x, y);
|
||||
x = this->width_ - x - 1;
|
||||
break;
|
||||
case display::DISPLAY_ROTATION_180_DEGREES:
|
||||
x = this->width_ - x - 1;
|
||||
y = this->height_ - y - 1;
|
||||
break;
|
||||
case display::DISPLAY_ROTATION_270_DEGREES:
|
||||
std::swap(x, y);
|
||||
y = this->height_ - y - 1;
|
||||
break;
|
||||
}
|
||||
auto pixel = convert_big_endian(display::ColorUtil::color_to_565(color));
|
||||
|
||||
this->draw_pixels_at(x, y, 1, 1, (const uint8_t *) &pixel, display::COLOR_ORDER_RGB, display::COLOR_BITNESS_565, true,
|
||||
0, 0, 0);
|
||||
App.feed_wdt();
|
||||
}
|
||||
|
||||
void RpiDpiRgb::dump_config() {
|
||||
ESP_LOGCONFIG("", "RPI_DPI_RGB LCD");
|
||||
ESP_LOGCONFIG(TAG, " Height: %u", this->height_);
|
||||
ESP_LOGCONFIG(TAG, " Width: %u", this->width_);
|
||||
LOG_PIN(" DE Pin: ", this->de_pin_);
|
||||
LOG_PIN(" Reset Pin: ", this->reset_pin_);
|
||||
size_t data_pin_count = sizeof(this->data_pins_) / sizeof(this->data_pins_[0]);
|
||||
for (size_t i = 0; i != data_pin_count; i++)
|
||||
ESP_LOGCONFIG(TAG, " Data pin %d: %s", i, (this->data_pins_[i])->dump_summary().c_str());
|
||||
}
|
||||
|
||||
} // namespace rpi_dpi_rgb
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32_VARIANT_ESP32S3
|
92
esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.h
Normal file
92
esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.h
Normal file
@ -0,0 +1,92 @@
|
||||
//
|
||||
// Created by Clyde Stubbs on 29/10/2023.
|
||||
//
|
||||
#pragma once
|
||||
|
||||
// only applicable on ESP32-S3
|
||||
#ifdef USE_ESP32_VARIANT_ESP32S3
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/gpio.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/components/display/display.h"
|
||||
#include "esp_lcd_panel_ops.h"
|
||||
|
||||
#include "esp_lcd_panel_rgb.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace rpi_dpi_rgb {
|
||||
|
||||
constexpr static const char *const TAG = "rpi_dpi_rgb";
|
||||
|
||||
class RpiDpiRgb : public display::Display {
|
||||
public:
|
||||
void update() override { this->do_update_(); }
|
||||
void setup() override;
|
||||
void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order,
|
||||
display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) override;
|
||||
void draw_pixel_at(int x, int y, Color color) override;
|
||||
|
||||
display::ColorOrder get_color_mode() { return this->color_mode_; }
|
||||
void set_color_mode(display::ColorOrder color_mode) { this->color_mode_ = color_mode; }
|
||||
void set_invert_colors(bool invert_colors) { this->invert_colors_ = invert_colors; }
|
||||
|
||||
void add_data_pin(InternalGPIOPin *data_pin, size_t index) { this->data_pins_[index] = data_pin; };
|
||||
void set_de_pin(InternalGPIOPin *de_pin) { this->de_pin_ = de_pin; }
|
||||
void set_pclk_pin(InternalGPIOPin *pclk_pin) { this->pclk_pin_ = pclk_pin; }
|
||||
void set_vsync_pin(InternalGPIOPin *vsync_pin) { this->vsync_pin_ = vsync_pin; }
|
||||
void set_hsync_pin(InternalGPIOPin *hsync_pin) { this->hsync_pin_ = hsync_pin; }
|
||||
void set_reset_pin(GPIOPin *reset_pin) { this->reset_pin_ = reset_pin; }
|
||||
void set_width(uint16_t width) { this->width_ = width; }
|
||||
void set_dimensions(uint16_t width, uint16_t height) {
|
||||
this->width_ = width;
|
||||
this->height_ = height;
|
||||
}
|
||||
int get_width() override { return this->width_; }
|
||||
int get_height() override { return this->height_; }
|
||||
void set_hsync_back_porch(uint16_t hsync_back_porch) { this->hsync_back_porch_ = hsync_back_porch; }
|
||||
void set_hsync_front_porch(uint16_t hsync_front_porch) { this->hsync_front_porch_ = hsync_front_porch; }
|
||||
void set_hsync_pulse_width(uint16_t hsync_pulse_width) { this->hsync_pulse_width_ = hsync_pulse_width; }
|
||||
void set_vsync_pulse_width(uint16_t vsync_pulse_width) { this->vsync_pulse_width_ = vsync_pulse_width; }
|
||||
void set_vsync_back_porch(uint16_t vsync_back_porch) { this->vsync_back_porch_ = vsync_back_porch; }
|
||||
void set_vsync_front_porch(uint16_t vsync_front_porch) { this->vsync_front_porch_ = vsync_front_porch; }
|
||||
void set_pclk_frequency(uint32_t pclk_frequency) { this->pclk_frequency_ = pclk_frequency; }
|
||||
void set_pclk_inverted(bool inverted) { this->pclk_inverted_ = inverted; }
|
||||
void set_offsets(int16_t offset_x, int16_t offset_y) {
|
||||
this->offset_x_ = offset_x;
|
||||
this->offset_y_ = offset_y;
|
||||
}
|
||||
display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_COLOR; }
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
int get_width_internal() override { return this->width_; }
|
||||
int get_height_internal() override { return this->height_; }
|
||||
InternalGPIOPin *de_pin_{nullptr};
|
||||
InternalGPIOPin *pclk_pin_{nullptr};
|
||||
InternalGPIOPin *hsync_pin_{nullptr};
|
||||
InternalGPIOPin *vsync_pin_{nullptr};
|
||||
GPIOPin *reset_pin_{nullptr};
|
||||
InternalGPIOPin *data_pins_[16] = {};
|
||||
uint16_t hsync_front_porch_ = 8;
|
||||
uint16_t hsync_pulse_width_ = 4;
|
||||
uint16_t hsync_back_porch_ = 8;
|
||||
uint16_t vsync_front_porch_ = 8;
|
||||
uint16_t vsync_pulse_width_ = 4;
|
||||
uint16_t vsync_back_porch_ = 8;
|
||||
uint32_t pclk_frequency_ = 16 * 1000 * 1000;
|
||||
bool pclk_inverted_{true};
|
||||
|
||||
bool invert_colors_{};
|
||||
display::ColorOrder color_mode_{display::COLOR_ORDER_BGR};
|
||||
size_t width_{};
|
||||
size_t height_{};
|
||||
int16_t offset_x_{0};
|
||||
int16_t offset_y_{0};
|
||||
|
||||
esp_lcd_panel_handle_t handle_{};
|
||||
};
|
||||
|
||||
} // namespace rpi_dpi_rgb
|
||||
} // namespace esphome
|
||||
#endif // USE_ESP32_VARIANT_ESP32S3
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user