mirror of
https://github.com/esphome/esphome.git
synced 2024-11-30 10:44:13 +01:00
Merge branch 'esphome:dev' into feature-max6921
This commit is contained in:
commit
818527c55c
152 changed files with 6572 additions and 4279 deletions
4
.github/actions/build-image/action.yaml
vendored
4
.github/actions/build-image/action.yaml
vendored
|
@ -46,7 +46,7 @@ runs:
|
||||||
|
|
||||||
- name: Build and push to ghcr by digest
|
- name: Build and push to ghcr by digest
|
||||||
id: build-ghcr
|
id: build-ghcr
|
||||||
uses: docker/build-push-action@v6.7.0
|
uses: docker/build-push-action@v6.9.0
|
||||||
env:
|
env:
|
||||||
DOCKER_BUILD_SUMMARY: false
|
DOCKER_BUILD_SUMMARY: false
|
||||||
DOCKER_BUILD_RECORD_UPLOAD: false
|
DOCKER_BUILD_RECORD_UPLOAD: false
|
||||||
|
@ -72,7 +72,7 @@ runs:
|
||||||
|
|
||||||
- name: Build and push to dockerhub by digest
|
- name: Build and push to dockerhub by digest
|
||||||
id: build-dockerhub
|
id: build-dockerhub
|
||||||
uses: docker/build-push-action@v6.7.0
|
uses: docker/build-push-action@v6.9.0
|
||||||
env:
|
env:
|
||||||
DOCKER_BUILD_SUMMARY: false
|
DOCKER_BUILD_SUMMARY: false
|
||||||
DOCKER_BUILD_RECORD_UPLOAD: false
|
DOCKER_BUILD_RECORD_UPLOAD: false
|
||||||
|
|
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 }}
|
python-version: ${{ inputs.python-version }}
|
||||||
- name: Restore Python virtual environment
|
- name: Restore Python virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache/restore@v4.0.2
|
uses: actions/cache/restore@v4.1.1
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
# yamllint disable-line rule:line-length
|
# yamllint disable-line rule:line-length
|
||||||
|
|
2
.github/workflows/ci-docker.yml
vendored
2
.github/workflows/ci-docker.yml
vendored
|
@ -46,7 +46,7 @@ jobs:
|
||||||
with:
|
with:
|
||||||
python-version: "3.9"
|
python-version: "3.9"
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3.6.1
|
uses: docker/setup-buildx-action@v3.7.1
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3.2.0
|
uses: docker/setup-qemu-action@v3.2.0
|
||||||
|
|
||||||
|
|
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
|
@ -46,7 +46,7 @@ jobs:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
- name: Restore Python virtual environment
|
- name: Restore Python virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache@v4.0.2
|
uses: actions/cache@v4.1.1
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
# yamllint disable-line rule:line-length
|
# yamllint disable-line rule:line-length
|
||||||
|
@ -302,14 +302,14 @@ jobs:
|
||||||
|
|
||||||
- name: Cache platformio
|
- name: Cache platformio
|
||||||
if: github.ref == 'refs/heads/dev'
|
if: github.ref == 'refs/heads/dev'
|
||||||
uses: actions/cache@v4.0.2
|
uses: actions/cache@v4.1.1
|
||||||
with:
|
with:
|
||||||
path: ~/.platformio
|
path: ~/.platformio
|
||||||
key: platformio-${{ matrix.pio_cache_key }}
|
key: platformio-${{ matrix.pio_cache_key }}
|
||||||
|
|
||||||
- name: Cache platformio
|
- name: Cache platformio
|
||||||
if: github.ref != 'refs/heads/dev'
|
if: github.ref != 'refs/heads/dev'
|
||||||
uses: actions/cache/restore@v4.0.2
|
uses: actions/cache/restore@v4.1.1
|
||||||
with:
|
with:
|
||||||
path: ~/.platformio
|
path: ~/.platformio
|
||||||
key: platformio-${{ matrix.pio_cache_key }}
|
key: platformio-${{ matrix.pio_cache_key }}
|
||||||
|
|
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
|
@ -65,7 +65,7 @@ jobs:
|
||||||
pip3 install build
|
pip3 install build
|
||||||
python3 -m build
|
python3 -m build
|
||||||
- name: Publish
|
- name: Publish
|
||||||
uses: pypa/gh-action-pypi-publish@v1.10.2
|
uses: pypa/gh-action-pypi-publish@v1.10.3
|
||||||
|
|
||||||
deploy-docker:
|
deploy-docker:
|
||||||
name: Build ESPHome ${{ matrix.platform }}
|
name: Build ESPHome ${{ matrix.platform }}
|
||||||
|
@ -90,7 +90,7 @@ jobs:
|
||||||
python-version: "3.9"
|
python-version: "3.9"
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3.6.1
|
uses: docker/setup-buildx-action@v3.7.1
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
if: matrix.platform != 'linux/amd64'
|
if: matrix.platform != 'linux/amd64'
|
||||||
uses: docker/setup-qemu-action@v3.2.0
|
uses: docker/setup-qemu-action@v3.2.0
|
||||||
|
@ -141,7 +141,7 @@ jobs:
|
||||||
echo name=$(cat /tmp/platform) >> $GITHUB_OUTPUT
|
echo name=$(cat /tmp/platform) >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Upload digests
|
- name: Upload digests
|
||||||
uses: actions/upload-artifact@v4.4.0
|
uses: actions/upload-artifact@v4.4.3
|
||||||
with:
|
with:
|
||||||
name: digests-${{ steps.sanitize.outputs.name }}
|
name: digests-${{ steps.sanitize.outputs.name }}
|
||||||
path: /tmp/digests
|
path: /tmp/digests
|
||||||
|
@ -184,7 +184,7 @@ jobs:
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3.6.1
|
uses: docker/setup-buildx-action@v3.7.1
|
||||||
|
|
||||||
- name: Log in to docker hub
|
- name: Log in to docker hub
|
||||||
if: matrix.registry == 'dockerhub'
|
if: matrix.registry == 'dockerhub'
|
||||||
|
|
|
@ -24,6 +24,7 @@ esphome/components/ade7953_i2c/* @angelnu
|
||||||
esphome/components/ade7953_spi/* @angelnu
|
esphome/components/ade7953_spi/* @angelnu
|
||||||
esphome/components/ads1118/* @solomondg1
|
esphome/components/ads1118/* @solomondg1
|
||||||
esphome/components/ags10/* @mak-42
|
esphome/components/ags10/* @mak-42
|
||||||
|
esphome/components/aic3204/* @kbx81
|
||||||
esphome/components/airthings_ble/* @jeromelaban
|
esphome/components/airthings_ble/* @jeromelaban
|
||||||
esphome/components/airthings_wave_base/* @jeromelaban @kpfleming @ncareau
|
esphome/components/airthings_wave_base/* @jeromelaban @kpfleming @ncareau
|
||||||
esphome/components/airthings_wave_mini/* @ncareau
|
esphome/components/airthings_wave_mini/* @ncareau
|
||||||
|
@ -47,6 +48,7 @@ esphome/components/at581x/* @X-Ryl669
|
||||||
esphome/components/atc_mithermometer/* @ahpohl
|
esphome/components/atc_mithermometer/* @ahpohl
|
||||||
esphome/components/atm90e26/* @danieltwagner
|
esphome/components/atm90e26/* @danieltwagner
|
||||||
esphome/components/atm90e32/* @circuitsetup @descipher
|
esphome/components/atm90e32/* @circuitsetup @descipher
|
||||||
|
esphome/components/audio_dac/* @kbx81
|
||||||
esphome/components/b_parasite/* @rbaron
|
esphome/components/b_parasite/* @rbaron
|
||||||
esphome/components/ballu/* @bazuchan
|
esphome/components/ballu/* @bazuchan
|
||||||
esphome/components/bang_bang/* @OttoWinter
|
esphome/components/bang_bang/* @OttoWinter
|
||||||
|
@ -160,6 +162,7 @@ esphome/components/gps/* @coogle
|
||||||
esphome/components/graph/* @synco
|
esphome/components/graph/* @synco
|
||||||
esphome/components/graphical_display_menu/* @MrMDavidson
|
esphome/components/graphical_display_menu/* @MrMDavidson
|
||||||
esphome/components/gree/* @orestismers
|
esphome/components/gree/* @orestismers
|
||||||
|
esphome/components/grove_gas_mc_v2/* @YorkshireIoT
|
||||||
esphome/components/grove_tb6612fng/* @max246
|
esphome/components/grove_tb6612fng/* @max246
|
||||||
esphome/components/growatt_solar/* @leeuwte
|
esphome/components/growatt_solar/* @leeuwte
|
||||||
esphome/components/gt911/* @clydebarrow @jesserockz
|
esphome/components/gt911/* @clydebarrow @jesserockz
|
||||||
|
@ -281,6 +284,7 @@ esphome/components/mopeka_std_check/* @Fabian-Schmidt
|
||||||
esphome/components/mpl3115a2/* @kbickar
|
esphome/components/mpl3115a2/* @kbickar
|
||||||
esphome/components/mpu6886/* @fabaff
|
esphome/components/mpu6886/* @fabaff
|
||||||
esphome/components/ms8607/* @e28eta
|
esphome/components/ms8607/* @e28eta
|
||||||
|
esphome/components/nau7802/* @cujomalainey
|
||||||
esphome/components/network/* @esphome/core
|
esphome/components/network/* @esphome/core
|
||||||
esphome/components/nextion/* @edwardtfn @senexcrenshaw
|
esphome/components/nextion/* @edwardtfn @senexcrenshaw
|
||||||
esphome/components/nextion/binary_sensor/* @senexcrenshaw
|
esphome/components/nextion/binary_sensor/* @senexcrenshaw
|
||||||
|
@ -289,6 +293,7 @@ esphome/components/nextion/switch/* @senexcrenshaw
|
||||||
esphome/components/nextion/text_sensor/* @senexcrenshaw
|
esphome/components/nextion/text_sensor/* @senexcrenshaw
|
||||||
esphome/components/nfc/* @jesserockz @kbx81
|
esphome/components/nfc/* @jesserockz @kbx81
|
||||||
esphome/components/noblex/* @AGalfra
|
esphome/components/noblex/* @AGalfra
|
||||||
|
esphome/components/npi19/* @bakerkj
|
||||||
esphome/components/number/* @esphome/core
|
esphome/components/number/* @esphome/core
|
||||||
esphome/components/one_wire/* @ssieb
|
esphome/components/one_wire/* @ssieb
|
||||||
esphome/components/online_image/* @guillempages
|
esphome/components/online_image/* @guillempages
|
||||||
|
@ -404,6 +409,7 @@ esphome/components/tca9555/* @mobrembski
|
||||||
esphome/components/tcl112/* @glmnet
|
esphome/components/tcl112/* @glmnet
|
||||||
esphome/components/tee501/* @Stock-M
|
esphome/components/tee501/* @Stock-M
|
||||||
esphome/components/teleinfo/* @0hax
|
esphome/components/teleinfo/* @0hax
|
||||||
|
esphome/components/tem3200/* @bakerkj
|
||||||
esphome/components/template/alarm_control_panel/* @grahambrown11 @hwstar
|
esphome/components/template/alarm_control_panel/* @grahambrown11 @hwstar
|
||||||
esphome/components/template/datetime/* @rfdarter
|
esphome/components/template/datetime/* @rfdarter
|
||||||
esphome/components/template/event/* @nohat
|
esphome/components/template/event/* @nohat
|
||||||
|
|
0
esphome/components/aic3204/__init__.py
Normal file
0
esphome/components/aic3204/__init__.py
Normal file
173
esphome/components/aic3204/aic3204.cpp
Normal file
173
esphome/components/aic3204/aic3204.cpp
Normal file
|
@ -0,0 +1,173 @@
|
||||||
|
#include "aic3204.h"
|
||||||
|
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace aic3204 {
|
||||||
|
|
||||||
|
static const char *const TAG = "aic3204";
|
||||||
|
|
||||||
|
#define ERROR_CHECK(err, msg) \
|
||||||
|
if (!(err)) { \
|
||||||
|
ESP_LOGE(TAG, msg); \
|
||||||
|
this->mark_failed(); \
|
||||||
|
return; \
|
||||||
|
}
|
||||||
|
|
||||||
|
void AIC3204::setup() {
|
||||||
|
ESP_LOGCONFIG(TAG, "Setting up AIC3204...");
|
||||||
|
|
||||||
|
// Set register page to 0
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_PAGE_CTRL, 0x00), "Set page 0 failed");
|
||||||
|
// Initiate SW reset (PLL is powered off as part of reset)
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_SW_RST, 0x01), "Software reset failed");
|
||||||
|
// *** Program clock settings ***
|
||||||
|
// Default is CODEC_CLKIN is from MCLK pin. Don't need to change this.
|
||||||
|
// MDAC*NDAC*FOSR*48Khz = mClk (24.576 MHz when the XMOS is expecting 48kHz audio)
|
||||||
|
// (See page 51 of https://www.ti.com/lit/ml/slaa557/slaa557.pdf)
|
||||||
|
// We do need MDAC*DOSR/32 >= the resource compute level for the processing block
|
||||||
|
// So here 2*128/32 = 8, which is equal to processing block 1 's resource compute
|
||||||
|
// See page 5 of https://www.ti.com/lit/an/slaa404c/slaa404c.pdf for the workflow
|
||||||
|
// for determining these settings.
|
||||||
|
|
||||||
|
// Power up NDAC and set to 2
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_NDAC, 0x82), "Set NDAC failed");
|
||||||
|
// Power up MDAC and set to 2
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_MDAC, 0x82), "Set MDAC failed");
|
||||||
|
// Program DOSR = 128
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_DOSR, 0x80), "Set DOSR failed");
|
||||||
|
// Set Audio Interface Config: I2S, 32 bits, DOUT always driving
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_CODEC_IF, 0x30), "Set CODEC_IF failed");
|
||||||
|
// For I2S Firmware only, set SCLK/MFP3 pin as Audio Data In
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_SCLK_MFP3, 0x02), "Set SCLK/MFP3 failed");
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_AUDIO_IF_4, 0x01), "Set AUDIO_IF_4 failed");
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_AUDIO_IF_5, 0x01), "Set AUDIO_IF_5 failed");
|
||||||
|
// Program the DAC processing block to be used - PRB_P1
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_DAC_SIG_PROC, 0x01), "Set DAC_SIG_PROC failed");
|
||||||
|
|
||||||
|
// *** Select Page 1 ***
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_PAGE_CTRL, 0x01), "Set page 1 failed");
|
||||||
|
// Enable the internal AVDD_LDO:
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_LDO_CTRL, 0x09), "Set LDO_CTRL failed");
|
||||||
|
// *** Program Analog Blocks ***
|
||||||
|
// Disable Internal Crude AVdd in presence of external AVdd supply or before powering up internal AVdd LDO
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_PWR_CFG, 0x08), "Set PWR_CFG failed");
|
||||||
|
// Enable Master Analog Power Control
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_LDO_CTRL, 0x01), "Set LDO_CTRL failed");
|
||||||
|
// Page 125: Common mode control register, set d6 to 1 to make the full chip common mode = 0.75 v
|
||||||
|
// We are using the internal AVdd regulator with a nominal output of 1.72 V (see LDO_CTRL_REGISTER on page 123)
|
||||||
|
// Page 86 says to only set the common mode voltage to 0.9 v if AVdd >= 1.8... but it isn't on our hardware
|
||||||
|
// We also adjust the HPL and HPR gains to -2dB gian later in this config flow compensate (see page 47)
|
||||||
|
// (All pages refer to the TLV320AIC3204 Application Reference Guide)
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_CM_CTRL, 0x40), "Set CM_CTRL failed");
|
||||||
|
// *** Set PowerTune Modes ***
|
||||||
|
// Set the Left & Right DAC PowerTune mode to PTM_P3/4. Use Class-AB driver.
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_PLAY_CFG1, 0x00), "Set PLAY_CFG1 failed");
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_PLAY_CFG2, 0x00), "Set PLAY_CFG2 failed");
|
||||||
|
// Set the REF charging time to 40ms
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_REF_STARTUP, 0x01), "Set REF_STARTUP failed");
|
||||||
|
// HP soft stepping settings for optimal pop performance at power up
|
||||||
|
// Rpop used is 6k with N = 6 and soft step = 20usec. This should work with 47uF coupling
|
||||||
|
// capacitor. Can try N=5,6 or 7 time constants as well. Trade-off delay vs “pop” sound.
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_HP_START, 0x25), "Set HP_START failed");
|
||||||
|
// Route Left DAC to HPL
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_HPL_ROUTE, 0x08), "Set HPL_ROUTE failed");
|
||||||
|
// Route Right DAC to HPR
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_HPR_ROUTE, 0x08), "Set HPR_ROUTE failed");
|
||||||
|
// Route Left DAC to LOL
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_LOL_ROUTE, 0x08), "Set LOL_ROUTE failed");
|
||||||
|
// Route Right DAC to LOR
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_LOR_ROUTE, 0x08), "Set LOR_ROUTE failed");
|
||||||
|
|
||||||
|
// Unmute HPL and set gain to -2dB (see comment before configuring the AIC3204_CM_CTRL register)
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_HPL_GAIN, 0x3e), "Set HPL_GAIN failed");
|
||||||
|
// Unmute HPR and set gain to -2dB (see comment before configuring the AIC3204_CM_CTRL register)
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_HPR_GAIN, 0x3e), "Set HPR_GAIN failed");
|
||||||
|
// Unmute LOL and set gain to 0dB
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_LOL_DRV_GAIN, 0x00), "Set LOL_DRV_GAIN failed");
|
||||||
|
// Unmute LOR and set gain to 0dB
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_LOR_DRV_GAIN, 0x00), "Set LOR_DRV_GAIN failed");
|
||||||
|
|
||||||
|
// Power up HPL and HPR, LOL and LOR drivers
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_OP_PWR_CTRL, 0x3C), "Set OP_PWR_CTRL failed");
|
||||||
|
|
||||||
|
// Wait for 2.5 sec for soft stepping to take effect before attempting power-up
|
||||||
|
this->set_timeout(2500, [this]() {
|
||||||
|
// *** Power Up DAC ***
|
||||||
|
// Select Page 0
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_PAGE_CTRL, 0x00), "Set PAGE_CTRL failed");
|
||||||
|
// Power up the Left and Right DAC Channels. Route Left data to Left DAC and Right data to Right DAC.
|
||||||
|
// DAC Vol control soft step 1 step per DAC word clock.
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_DAC_CH_SET1, 0xd4), "Set DAC_CH_SET1 failed");
|
||||||
|
// Set left and right DAC digital volume control
|
||||||
|
ERROR_CHECK(this->write_volume_(), "Set volume failed");
|
||||||
|
// Unmute left and right channels
|
||||||
|
ERROR_CHECK(this->write_mute_(), "Set mute failed");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void AIC3204::dump_config() {
|
||||||
|
ESP_LOGCONFIG(TAG, "AIC3204:");
|
||||||
|
LOG_I2C_DEVICE(this);
|
||||||
|
|
||||||
|
if (this->is_failed()) {
|
||||||
|
ESP_LOGE(TAG, "Communication with AIC3204 failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AIC3204::set_mute_off() {
|
||||||
|
this->is_muted_ = false;
|
||||||
|
return this->write_mute_();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AIC3204::set_mute_on() {
|
||||||
|
this->is_muted_ = true;
|
||||||
|
return this->write_mute_();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AIC3204::set_auto_mute_mode(uint8_t auto_mute_mode) {
|
||||||
|
this->auto_mute_mode_ = auto_mute_mode & 0x07;
|
||||||
|
ESP_LOGVV(TAG, "Setting auto_mute_mode to 0x%.2x", this->auto_mute_mode_);
|
||||||
|
return this->write_mute_();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AIC3204::set_volume(float volume) {
|
||||||
|
this->volume_ = clamp<float>(volume, 0.0, 1.0);
|
||||||
|
return this->write_volume_();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AIC3204::is_muted() { return this->is_muted_; }
|
||||||
|
|
||||||
|
float AIC3204::volume() { return this->volume_; }
|
||||||
|
|
||||||
|
bool AIC3204::write_mute_() {
|
||||||
|
uint8_t mute_mode_byte = this->auto_mute_mode_ << 4; // auto-mute control is bits 4-6
|
||||||
|
mute_mode_byte |= this->is_muted_ ? 0x0c : 0x00; // mute bits are 2-3
|
||||||
|
if (!this->write_byte(AIC3204_PAGE_CTRL, 0x00) || !this->write_byte(AIC3204_DAC_CH_SET2, mute_mode_byte)) {
|
||||||
|
ESP_LOGE(TAG, "Writing mute modes failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AIC3204::write_volume_() {
|
||||||
|
const int8_t dvc_min_byte = -127;
|
||||||
|
const int8_t dvc_max_byte = 48;
|
||||||
|
|
||||||
|
int8_t volume_byte = dvc_min_byte + (this->volume_ * (dvc_max_byte - dvc_min_byte));
|
||||||
|
volume_byte = clamp<int8_t>(volume_byte, dvc_min_byte, dvc_max_byte);
|
||||||
|
|
||||||
|
ESP_LOGVV(TAG, "Setting volume to 0x%.2x", volume_byte & 0xFF);
|
||||||
|
|
||||||
|
if ((!this->write_byte(AIC3204_PAGE_CTRL, 0x00)) || (!this->write_byte(AIC3204_DACL_VOL_D, volume_byte)) ||
|
||||||
|
(!this->write_byte(AIC3204_DACR_VOL_D, volume_byte))) {
|
||||||
|
ESP_LOGE(TAG, "Writing volume failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace aic3204
|
||||||
|
} // namespace esphome
|
88
esphome/components/aic3204/aic3204.h
Normal file
88
esphome/components/aic3204/aic3204.h
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/components/audio_dac/audio_dac.h"
|
||||||
|
#include "esphome/components/i2c/i2c.h"
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
|
#include "esphome/core/hal.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace aic3204 {
|
||||||
|
|
||||||
|
// TLV320AIC3204 Register Addresses
|
||||||
|
// Page 0
|
||||||
|
static const uint8_t AIC3204_PAGE_CTRL = 0x00; // Register 0 - Page Control
|
||||||
|
static const uint8_t AIC3204_SW_RST = 0x01; // Register 1 - Software Reset
|
||||||
|
static const uint8_t AIC3204_CLK_PLL1 = 0x04; // Register 4 - Clock Setting Register 1, Multiplexers
|
||||||
|
static const uint8_t AIC3204_CLK_PLL2 = 0x05; // Register 5 - Clock Setting Register 2, P and R values
|
||||||
|
static const uint8_t AIC3204_CLK_PLL3 = 0x06; // Register 6 - Clock Setting Register 3, J values
|
||||||
|
static const uint8_t AIC3204_NDAC = 0x0B; // Register 11 - NDAC Divider Value
|
||||||
|
static const uint8_t AIC3204_MDAC = 0x0C; // Register 12 - MDAC Divider Value
|
||||||
|
static const uint8_t AIC3204_DOSR = 0x0E; // Register 14 - DOSR Divider Value (LS Byte)
|
||||||
|
static const uint8_t AIC3204_NADC = 0x12; // Register 18 - NADC Divider Value
|
||||||
|
static const uint8_t AIC3204_MADC = 0x13; // Register 19 - MADC Divider Value
|
||||||
|
static const uint8_t AIC3204_AOSR = 0x14; // Register 20 - AOSR Divider Value
|
||||||
|
static const uint8_t AIC3204_CODEC_IF = 0x1B; // Register 27 - CODEC Interface Control
|
||||||
|
static const uint8_t AIC3204_AUDIO_IF_4 = 0x1F; // Register 31 - Audio Interface Setting Register 4
|
||||||
|
static const uint8_t AIC3204_AUDIO_IF_5 = 0x20; // Register 32 - Audio Interface Setting Register 5
|
||||||
|
static const uint8_t AIC3204_SCLK_MFP3 = 0x38; // Register 56 - SCLK/MFP3 Function Control
|
||||||
|
static const uint8_t AIC3204_DAC_SIG_PROC = 0x3C; // Register 60 - DAC Sig Processing Block Control
|
||||||
|
static const uint8_t AIC3204_ADC_SIG_PROC = 0x3D; // Register 61 - ADC Sig Processing Block Control
|
||||||
|
static const uint8_t AIC3204_DAC_CH_SET1 = 0x3F; // Register 63 - DAC Channel Setup 1
|
||||||
|
static const uint8_t AIC3204_DAC_CH_SET2 = 0x40; // Register 64 - DAC Channel Setup 2
|
||||||
|
static const uint8_t AIC3204_DACL_VOL_D = 0x41; // Register 65 - DAC Left Digital Vol Control
|
||||||
|
static const uint8_t AIC3204_DACR_VOL_D = 0x42; // Register 66 - DAC Right Digital Vol Control
|
||||||
|
static const uint8_t AIC3204_DRC_ENABLE = 0x44;
|
||||||
|
static const uint8_t AIC3204_ADC_CH_SET = 0x51; // Register 81 - ADC Channel Setup
|
||||||
|
static const uint8_t AIC3204_ADC_FGA_MUTE = 0x52; // Register 82 - ADC Fine Gain Adjust/Mute
|
||||||
|
|
||||||
|
// Page 1
|
||||||
|
static const uint8_t AIC3204_PWR_CFG = 0x01; // Register 1 - Power Config
|
||||||
|
static const uint8_t AIC3204_LDO_CTRL = 0x02; // Register 2 - LDO Control
|
||||||
|
static const uint8_t AIC3204_PLAY_CFG1 = 0x03; // Register 3 - Playback Config 1
|
||||||
|
static const uint8_t AIC3204_PLAY_CFG2 = 0x04; // Register 4 - Playback Config 2
|
||||||
|
static const uint8_t AIC3204_OP_PWR_CTRL = 0x09; // Register 9 - Output Driver Power Control
|
||||||
|
static const uint8_t AIC3204_CM_CTRL = 0x0A; // Register 10 - Common Mode Control
|
||||||
|
static const uint8_t AIC3204_HPL_ROUTE = 0x0C; // Register 12 - HPL Routing Select
|
||||||
|
static const uint8_t AIC3204_HPR_ROUTE = 0x0D; // Register 13 - HPR Routing Select
|
||||||
|
static const uint8_t AIC3204_LOL_ROUTE = 0x0E; // Register 14 - LOL Routing Selection
|
||||||
|
static const uint8_t AIC3204_LOR_ROUTE = 0x0F; // Register 15 - LOR Routing Selection
|
||||||
|
static const uint8_t AIC3204_HPL_GAIN = 0x10; // Register 16 - HPL Driver Gain
|
||||||
|
static const uint8_t AIC3204_HPR_GAIN = 0x11; // Register 17 - HPR Driver Gain
|
||||||
|
static const uint8_t AIC3204_LOL_DRV_GAIN = 0x12; // Register 18 - LOL Driver Gain Setting
|
||||||
|
static const uint8_t AIC3204_LOR_DRV_GAIN = 0x13; // Register 19 - LOR Driver Gain Setting
|
||||||
|
static const uint8_t AIC3204_HP_START = 0x14; // Register 20 - Headphone Driver Startup
|
||||||
|
static const uint8_t AIC3204_LPGA_P_ROUTE = 0x34; // Register 52 - Left PGA Positive Input Route
|
||||||
|
static const uint8_t AIC3204_LPGA_N_ROUTE = 0x36; // Register 54 - Left PGA Negative Input Route
|
||||||
|
static const uint8_t AIC3204_RPGA_P_ROUTE = 0x37; // Register 55 - Right PGA Positive Input Route
|
||||||
|
static const uint8_t AIC3204_RPGA_N_ROUTE = 0x39; // Register 57 - Right PGA Negative Input Route
|
||||||
|
static const uint8_t AIC3204_LPGA_VOL = 0x3B; // Register 59 - Left PGA Volume
|
||||||
|
static const uint8_t AIC3204_RPGA_VOL = 0x3C; // Register 60 - Right PGA Volume
|
||||||
|
static const uint8_t AIC3204_ADC_PTM = 0x3D; // Register 61 - ADC Power Tune Config
|
||||||
|
static const uint8_t AIC3204_AN_IN_CHRG = 0x47; // Register 71 - Analog Input Quick Charging Config
|
||||||
|
static const uint8_t AIC3204_REF_STARTUP = 0x7B; // Register 123 - Reference Power Up Config
|
||||||
|
|
||||||
|
class AIC3204 : public audio_dac::AudioDac, public Component, public i2c::I2CDevice {
|
||||||
|
public:
|
||||||
|
void setup() override;
|
||||||
|
void dump_config() override;
|
||||||
|
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||||
|
|
||||||
|
bool set_mute_off() override;
|
||||||
|
bool set_mute_on() override;
|
||||||
|
bool set_auto_mute_mode(uint8_t auto_mute_mode);
|
||||||
|
bool set_volume(float volume) override;
|
||||||
|
|
||||||
|
bool is_muted() override;
|
||||||
|
float volume() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool write_mute_();
|
||||||
|
bool write_volume_();
|
||||||
|
|
||||||
|
uint8_t auto_mute_mode_{0};
|
||||||
|
float volume_{0};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace aic3204
|
||||||
|
} // namespace esphome
|
52
esphome/components/aic3204/audio_dac.py
Normal file
52
esphome/components/aic3204/audio_dac.py
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
from esphome import automation
|
||||||
|
import esphome.codegen as cg
|
||||||
|
from esphome.components import i2c
|
||||||
|
from esphome.components.audio_dac import AudioDac
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.const import CONF_ID, CONF_MODE
|
||||||
|
|
||||||
|
CODEOWNERS = ["@kbx81"]
|
||||||
|
DEPENDENCIES = ["i2c"]
|
||||||
|
|
||||||
|
aic3204_ns = cg.esphome_ns.namespace("aic3204")
|
||||||
|
AIC3204 = aic3204_ns.class_("AIC3204", AudioDac, cg.Component, i2c.I2CDevice)
|
||||||
|
|
||||||
|
SetAutoMuteAction = aic3204_ns.class_("SetAutoMuteAction", automation.Action)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = (
|
||||||
|
cv.Schema(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(AIC3204),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(cv.COMPONENT_SCHEMA)
|
||||||
|
.extend(i2c.i2c_device_schema(0x18))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
SET_AUTO_MUTE_ACTION_SCHEMA = cv.maybe_simple_value(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.use_id(AIC3204),
|
||||||
|
cv.Required(CONF_MODE): cv.templatable(cv.int_range(max=7, min=0)),
|
||||||
|
},
|
||||||
|
key=CONF_MODE,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@automation.register_action(
|
||||||
|
"aic3204.set_auto_mute_mode", SetAutoMuteAction, SET_AUTO_MUTE_ACTION_SCHEMA
|
||||||
|
)
|
||||||
|
async def aic3204_set_volume_to_code(config, action_id, template_arg, args):
|
||||||
|
paren = await cg.get_variable(config[CONF_ID])
|
||||||
|
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||||
|
|
||||||
|
template_ = await cg.templatable(config.get(CONF_MODE), args, int)
|
||||||
|
cg.add(var.set_auto_mute_mode(template_))
|
||||||
|
|
||||||
|
return var
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
23
esphome/components/aic3204/automation.h
Normal file
23
esphome/components/aic3204/automation.h
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/automation.h"
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "aic3204.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace aic3204 {
|
||||||
|
|
||||||
|
template<typename... Ts> class SetAutoMuteAction : public Action<Ts...> {
|
||||||
|
public:
|
||||||
|
explicit SetAutoMuteAction(AIC3204 *aic3204) : aic3204_(aic3204) {}
|
||||||
|
|
||||||
|
TEMPLATABLE_VALUE(uint8_t, auto_mute_mode)
|
||||||
|
|
||||||
|
void play(Ts... x) override { this->aic3204_->set_auto_mute_mode(this->auto_mute_mode_.value(x...)); }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
AIC3204 *aic3204_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace aic3204
|
||||||
|
} // namespace esphome
|
|
@ -9,7 +9,7 @@ from esphome.const import (
|
||||||
CONF_MQTT_ID,
|
CONF_MQTT_ID,
|
||||||
CONF_ON_STATE,
|
CONF_ON_STATE,
|
||||||
CONF_TRIGGER_ID,
|
CONF_TRIGGER_ID,
|
||||||
CONF_WEB_SERVER_ID,
|
CONF_WEB_SERVER,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, coroutine_with_priority
|
from esphome.core import CORE, coroutine_with_priority
|
||||||
from esphome.cpp_helpers import setup_entity
|
from esphome.cpp_helpers import setup_entity
|
||||||
|
@ -195,9 +195,8 @@ async def setup_alarm_control_panel_core_(var, config):
|
||||||
for conf in config.get(CONF_ON_READY, []):
|
for conf in config.get(CONF_ON_READY, []):
|
||||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||||
await automation.build_automation(trigger, [], conf)
|
await automation.build_automation(trigger, [], conf)
|
||||||
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
|
if web_server_config := config.get(CONF_WEB_SERVER):
|
||||||
web_server_ = await cg.get_variable(webserver_id)
|
await web_server.add_entity_config(var, web_server_config)
|
||||||
web_server.add_entity_to_sorting_list(web_server_, var, config)
|
|
||||||
if mqtt_id := config.get(CONF_MQTT_ID):
|
if mqtt_id := config.get(CONF_MQTT_ID):
|
||||||
mqtt_ = cg.new_Pvariable(mqtt_id, var)
|
mqtt_ = cg.new_Pvariable(mqtt_id, var)
|
||||||
await mqtt.register_mqtt_component(mqtt_, config)
|
await mqtt.register_mqtt_component(mqtt_, config)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#include "api_connection.h"
|
#include "api_connection.h"
|
||||||
|
#ifdef USE_API
|
||||||
#include <cerrno>
|
#include <cerrno>
|
||||||
#include <cinttypes>
|
#include <cinttypes>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
@ -1568,3 +1569,4 @@ void APIConnection::on_fatal_error() {
|
||||||
|
|
||||||
} // namespace api
|
} // namespace api
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
#endif
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
|
#ifdef USE_API
|
||||||
#include "api_frame_helper.h"
|
#include "api_frame_helper.h"
|
||||||
#include "api_pb2.h"
|
#include "api_pb2.h"
|
||||||
#include "api_pb2_service.h"
|
#include "api_pb2_service.h"
|
||||||
#include "api_server.h"
|
#include "api_server.h"
|
||||||
#include "esphome/core/application.h"
|
#include "esphome/core/application.h"
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/core/defines.h"
|
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
@ -268,3 +269,4 @@ class APIConnection : public APIServerConnection {
|
||||||
|
|
||||||
} // namespace api
|
} // namespace api
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
#endif
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#include "api_frame_helper.h"
|
#include "api_frame_helper.h"
|
||||||
|
#ifdef USE_API
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include "esphome/core/hal.h"
|
#include "esphome/core/hal.h"
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
|
@ -1028,3 +1028,4 @@ APIError APIPlaintextFrameHelper::shutdown(int how) {
|
||||||
|
|
||||||
} // namespace api
|
} // namespace api
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
#endif
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "esphome/core/defines.h"
|
#include "esphome/core/defines.h"
|
||||||
|
#ifdef USE_API
|
||||||
#ifdef USE_API_NOISE
|
#ifdef USE_API_NOISE
|
||||||
#include "noise/protocol.h"
|
#include "noise/protocol.h"
|
||||||
#endif
|
#endif
|
||||||
|
@ -190,3 +190,4 @@ class APIPlaintextFrameHelper : public APIFrameHelper {
|
||||||
|
|
||||||
} // namespace api
|
} // namespace api
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
#endif
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#include "api_server.h"
|
#include "api_server.h"
|
||||||
|
#ifdef USE_API
|
||||||
#include <cerrno>
|
#include <cerrno>
|
||||||
#include "api_connection.h"
|
#include "api_connection.h"
|
||||||
#include "esphome/components/network/util.h"
|
#include "esphome/components/network/util.h"
|
||||||
|
@ -403,3 +404,4 @@ void APIServer::on_alarm_control_panel_update(alarm_control_panel::AlarmControlP
|
||||||
|
|
||||||
} // namespace api
|
} // namespace api
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
#endif
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
|
#ifdef USE_API
|
||||||
#include "api_noise_context.h"
|
#include "api_noise_context.h"
|
||||||
#include "api_pb2.h"
|
#include "api_pb2.h"
|
||||||
#include "api_pb2_service.h"
|
#include "api_pb2_service.h"
|
||||||
|
@ -7,7 +9,6 @@
|
||||||
#include "esphome/core/automation.h"
|
#include "esphome/core/automation.h"
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/core/controller.h"
|
#include "esphome/core/controller.h"
|
||||||
#include "esphome/core/defines.h"
|
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include "list_entities.h"
|
#include "list_entities.h"
|
||||||
#include "subscribe_state.h"
|
#include "subscribe_state.h"
|
||||||
|
@ -153,3 +154,4 @@ template<typename... Ts> class APIConnectedCondition : public Condition<Ts...> {
|
||||||
|
|
||||||
} // namespace api
|
} // namespace api
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
#endif
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include "user_services.h"
|
|
||||||
#include "api_server.h"
|
#include "api_server.h"
|
||||||
|
#ifdef USE_API
|
||||||
|
#include "user_services.h"
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace api {
|
namespace api {
|
||||||
|
|
||||||
|
@ -216,3 +216,4 @@ class CustomAPIDevice {
|
||||||
|
|
||||||
} // namespace api
|
} // namespace api
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
#endif
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "api_server.h"
|
||||||
|
#ifdef USE_API
|
||||||
|
#include "api_pb2.h"
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/automation.h"
|
#include "esphome/core/automation.h"
|
||||||
#include "api_pb2.h"
|
|
||||||
#include "api_server.h"
|
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
|
@ -81,3 +81,4 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
|
||||||
|
|
||||||
} // namespace api
|
} // namespace api
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
#endif
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#include "list_entities.h"
|
#include "list_entities.h"
|
||||||
|
#ifdef USE_API
|
||||||
#include "api_connection.h"
|
#include "api_connection.h"
|
||||||
#include "esphome/core/application.h"
|
#include "esphome/core/application.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
@ -104,3 +105,4 @@ bool ListEntitiesIterator::on_update(update::UpdateEntity *update) { return this
|
||||||
|
|
||||||
} // namespace api
|
} // namespace api
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
#endif
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
|
#ifdef USE_API
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/core/component_iterator.h"
|
#include "esphome/core/component_iterator.h"
|
||||||
#include "esphome/core/defines.h"
|
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace api {
|
namespace api {
|
||||||
|
|
||||||
|
@ -87,3 +87,4 @@ class ListEntitiesIterator : public ComponentIterator {
|
||||||
|
|
||||||
} // namespace api
|
} // namespace api
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
#endif
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#include "subscribe_state.h"
|
#include "subscribe_state.h"
|
||||||
|
#ifdef USE_API
|
||||||
#include "api_connection.h"
|
#include "api_connection.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
@ -84,3 +85,4 @@ InitialStateIterator::InitialStateIterator(APIConnection *client) : client_(clie
|
||||||
|
|
||||||
} // namespace api
|
} // namespace api
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
#endif
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
|
#ifdef USE_API
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/core/component_iterator.h"
|
#include "esphome/core/component_iterator.h"
|
||||||
#include "esphome/core/controller.h"
|
#include "esphome/core/controller.h"
|
||||||
#include "esphome/core/defines.h"
|
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace api {
|
namespace api {
|
||||||
|
|
||||||
|
@ -82,3 +82,4 @@ class InitialStateIterator : public ComponentIterator {
|
||||||
|
|
||||||
} // namespace api
|
} // namespace api
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
#endif
|
||||||
|
|
57
esphome/components/audio_dac/__init__.py
Normal file
57
esphome/components/audio_dac/__init__.py
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
from esphome import automation
|
||||||
|
from esphome.automation import maybe_simple_id
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.const import CONF_ID, CONF_VOLUME
|
||||||
|
from esphome.core import coroutine_with_priority
|
||||||
|
|
||||||
|
CODEOWNERS = ["@kbx81"]
|
||||||
|
IS_PLATFORM_COMPONENT = True
|
||||||
|
|
||||||
|
audio_dac_ns = cg.esphome_ns.namespace("audio_dac")
|
||||||
|
AudioDac = audio_dac_ns.class_("AudioDac")
|
||||||
|
|
||||||
|
MuteOffAction = audio_dac_ns.class_("MuteOffAction", automation.Action)
|
||||||
|
MuteOnAction = audio_dac_ns.class_("MuteOnAction", automation.Action)
|
||||||
|
SetVolumeAction = audio_dac_ns.class_("SetVolumeAction", automation.Action)
|
||||||
|
|
||||||
|
|
||||||
|
MUTE_ACTION_SCHEMA = maybe_simple_id(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.use_id(AudioDac),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
SET_VOLUME_ACTION_SCHEMA = cv.maybe_simple_value(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.use_id(AudioDac),
|
||||||
|
cv.Required(CONF_VOLUME): cv.templatable(cv.percentage),
|
||||||
|
},
|
||||||
|
key=CONF_VOLUME,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@automation.register_action("audio_dac.mute_off", MuteOffAction, MUTE_ACTION_SCHEMA)
|
||||||
|
@automation.register_action("audio_dac.mute_on", MuteOnAction, MUTE_ACTION_SCHEMA)
|
||||||
|
async def audio_dac_mute_action_to_code(config, action_id, template_arg, args):
|
||||||
|
paren = await cg.get_variable(config[CONF_ID])
|
||||||
|
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||||
|
|
||||||
|
|
||||||
|
@automation.register_action(
|
||||||
|
"audio_dac.set_volume", SetVolumeAction, SET_VOLUME_ACTION_SCHEMA
|
||||||
|
)
|
||||||
|
async def audio_dac_set_volume_to_code(config, action_id, template_arg, args):
|
||||||
|
paren = await cg.get_variable(config[CONF_ID])
|
||||||
|
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||||
|
|
||||||
|
template_ = await cg.templatable(config.get(CONF_VOLUME), args, float)
|
||||||
|
cg.add(var.set_volume(template_))
|
||||||
|
|
||||||
|
return var
|
||||||
|
|
||||||
|
|
||||||
|
@coroutine_with_priority(100.0)
|
||||||
|
async def to_code(config):
|
||||||
|
cg.add_define("USE_AUDIO_DAC")
|
||||||
|
cg.add_global(audio_dac_ns.using)
|
23
esphome/components/audio_dac/audio_dac.h
Normal file
23
esphome/components/audio_dac/audio_dac.h
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
|
#include "esphome/core/hal.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace audio_dac {
|
||||||
|
|
||||||
|
class AudioDac {
|
||||||
|
public:
|
||||||
|
virtual bool set_mute_off() = 0;
|
||||||
|
virtual bool set_mute_on() = 0;
|
||||||
|
virtual bool set_volume(float volume) = 0;
|
||||||
|
|
||||||
|
virtual bool is_muted() = 0;
|
||||||
|
virtual float volume() = 0;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool is_muted_{false};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace audio_dac
|
||||||
|
} // namespace esphome
|
43
esphome/components/audio_dac/automation.h
Normal file
43
esphome/components/audio_dac/automation.h
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/automation.h"
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "audio_dac.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace audio_dac {
|
||||||
|
|
||||||
|
template<typename... Ts> class MuteOffAction : public Action<Ts...> {
|
||||||
|
public:
|
||||||
|
explicit MuteOffAction(AudioDac *audio_dac) : audio_dac_(audio_dac) {}
|
||||||
|
|
||||||
|
void play(Ts... x) override { this->audio_dac_->set_mute_off(); }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
AudioDac *audio_dac_;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename... Ts> class MuteOnAction : public Action<Ts...> {
|
||||||
|
public:
|
||||||
|
explicit MuteOnAction(AudioDac *audio_dac) : audio_dac_(audio_dac) {}
|
||||||
|
|
||||||
|
void play(Ts... x) override { this->audio_dac_->set_mute_on(); }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
AudioDac *audio_dac_;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename... Ts> class SetVolumeAction : public Action<Ts...> {
|
||||||
|
public:
|
||||||
|
explicit SetVolumeAction(AudioDac *audio_dac) : audio_dac_(audio_dac) {}
|
||||||
|
|
||||||
|
TEMPLATABLE_VALUE(float, volume)
|
||||||
|
|
||||||
|
void play(Ts... x) override { this->audio_dac_->set_volume(this->volume_.value(x...)); }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
AudioDac *audio_dac_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace audio_dac
|
||||||
|
} // namespace esphome
|
|
@ -157,8 +157,11 @@ void BangBangClimate::switch_to_action_(climate::ClimateAction action) {
|
||||||
default:
|
default:
|
||||||
trig = nullptr;
|
trig = nullptr;
|
||||||
}
|
}
|
||||||
assert(trig != nullptr);
|
if (trig != nullptr) {
|
||||||
trig->trigger();
|
trig->trigger();
|
||||||
|
} else {
|
||||||
|
ESP_LOGW(TAG, "trig not set - unsupported action");
|
||||||
|
}
|
||||||
this->action = action;
|
this->action = action;
|
||||||
this->prev_trigger_ = trig;
|
this->prev_trigger_ = trig;
|
||||||
this->publish_state();
|
this->publish_state();
|
||||||
|
|
|
@ -13,8 +13,10 @@ float bedjet_temp_to_f(const uint8_t temp) {
|
||||||
|
|
||||||
/** Cleans up the packet before sending. */
|
/** Cleans up the packet before sending. */
|
||||||
BedjetPacket *BedjetCodec::clean_packet_() {
|
BedjetPacket *BedjetCodec::clean_packet_() {
|
||||||
// So far no commands require more than 2 bytes of data.
|
// So far no commands require more than 2 bytes of data
|
||||||
assert(this->packet_.data_length <= 2);
|
if (this->packet_.data_length > 2) {
|
||||||
|
ESP_LOGW(TAG, "Packet may be malformed");
|
||||||
|
}
|
||||||
for (int i = this->packet_.data_length; i < 2; i++) {
|
for (int i = this->packet_.data_length; i < 2; i++) {
|
||||||
this->packet_.data[i] = '\0';
|
this->packet_.data[i] = '\0';
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ from esphome.const import (
|
||||||
CONF_STATE,
|
CONF_STATE,
|
||||||
CONF_TIMING,
|
CONF_TIMING,
|
||||||
CONF_TRIGGER_ID,
|
CONF_TRIGGER_ID,
|
||||||
CONF_WEB_SERVER_ID,
|
CONF_WEB_SERVER,
|
||||||
DEVICE_CLASS_BATTERY,
|
DEVICE_CLASS_BATTERY,
|
||||||
DEVICE_CLASS_BATTERY_CHARGING,
|
DEVICE_CLASS_BATTERY_CHARGING,
|
||||||
DEVICE_CLASS_CARBON_MONOXIDE,
|
DEVICE_CLASS_CARBON_MONOXIDE,
|
||||||
|
@ -543,9 +543,8 @@ async def setup_binary_sensor_core_(var, config):
|
||||||
mqtt_ = cg.new_Pvariable(mqtt_id, var)
|
mqtt_ = cg.new_Pvariable(mqtt_id, var)
|
||||||
await mqtt.register_mqtt_component(mqtt_, config)
|
await mqtt.register_mqtt_component(mqtt_, config)
|
||||||
|
|
||||||
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
|
if web_server_config := config.get(CONF_WEB_SERVER):
|
||||||
web_server_ = await cg.get_variable(webserver_id)
|
await web_server.add_entity_config(var, web_server_config)
|
||||||
web_server.add_entity_to_sorting_list(web_server_, var, config)
|
|
||||||
|
|
||||||
|
|
||||||
async def register_binary_sensor(var, config):
|
async def register_binary_sensor(var, config):
|
||||||
|
|
|
@ -11,7 +11,7 @@ from esphome.const import (
|
||||||
CONF_MQTT_ID,
|
CONF_MQTT_ID,
|
||||||
CONF_ON_PRESS,
|
CONF_ON_PRESS,
|
||||||
CONF_TRIGGER_ID,
|
CONF_TRIGGER_ID,
|
||||||
CONF_WEB_SERVER_ID,
|
CONF_WEB_SERVER,
|
||||||
DEVICE_CLASS_EMPTY,
|
DEVICE_CLASS_EMPTY,
|
||||||
DEVICE_CLASS_IDENTIFY,
|
DEVICE_CLASS_IDENTIFY,
|
||||||
DEVICE_CLASS_RESTART,
|
DEVICE_CLASS_RESTART,
|
||||||
|
@ -97,9 +97,8 @@ async def setup_button_core_(var, config):
|
||||||
mqtt_ = cg.new_Pvariable(mqtt_id, var)
|
mqtt_ = cg.new_Pvariable(mqtt_id, var)
|
||||||
await mqtt.register_mqtt_component(mqtt_, config)
|
await mqtt.register_mqtt_component(mqtt_, config)
|
||||||
|
|
||||||
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
|
if web_server_config := config.get(CONF_WEB_SERVER):
|
||||||
web_server_ = await cg.get_variable(webserver_id)
|
await web_server.add_entity_config(var, web_server_config)
|
||||||
web_server.add_entity_to_sorting_list(web_server_, var, config)
|
|
||||||
|
|
||||||
|
|
||||||
async def register_button(var, config):
|
async def register_button(var, config):
|
||||||
|
|
|
@ -43,7 +43,7 @@ from esphome.const import (
|
||||||
CONF_TEMPERATURE_STEP,
|
CONF_TEMPERATURE_STEP,
|
||||||
CONF_TRIGGER_ID,
|
CONF_TRIGGER_ID,
|
||||||
CONF_VISUAL,
|
CONF_VISUAL,
|
||||||
CONF_WEB_SERVER_ID,
|
CONF_WEB_SERVER,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, coroutine_with_priority
|
from esphome.core import CORE, coroutine_with_priority
|
||||||
from esphome.cpp_helpers import setup_entity
|
from esphome.cpp_helpers import setup_entity
|
||||||
|
@ -408,9 +408,8 @@ async def setup_climate_core_(var, config):
|
||||||
trigger, [(ClimateCall.operator("ref"), "x")], conf
|
trigger, [(ClimateCall.operator("ref"), "x")], conf
|
||||||
)
|
)
|
||||||
|
|
||||||
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
|
if web_server_config := config.get(CONF_WEB_SERVER):
|
||||||
web_server_ = await cg.get_variable(webserver_id)
|
await web_server.add_entity_config(var, web_server_config)
|
||||||
web_server.add_entity_to_sorting_list(web_server_, var, config)
|
|
||||||
|
|
||||||
|
|
||||||
async def register_climate(var, config):
|
async def register_climate(var, config):
|
||||||
|
|
|
@ -17,7 +17,7 @@ from esphome.const import (
|
||||||
CONF_TILT_COMMAND_TOPIC,
|
CONF_TILT_COMMAND_TOPIC,
|
||||||
CONF_TILT_STATE_TOPIC,
|
CONF_TILT_STATE_TOPIC,
|
||||||
CONF_TRIGGER_ID,
|
CONF_TRIGGER_ID,
|
||||||
CONF_WEB_SERVER_ID,
|
CONF_WEB_SERVER,
|
||||||
DEVICE_CLASS_AWNING,
|
DEVICE_CLASS_AWNING,
|
||||||
DEVICE_CLASS_BLIND,
|
DEVICE_CLASS_BLIND,
|
||||||
DEVICE_CLASS_CURTAIN,
|
DEVICE_CLASS_CURTAIN,
|
||||||
|
@ -137,10 +137,6 @@ async def setup_cover_core_(var, config):
|
||||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||||
await automation.build_automation(trigger, [], conf)
|
await automation.build_automation(trigger, [], conf)
|
||||||
|
|
||||||
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
|
|
||||||
web_server_ = await cg.get_variable(webserver_id)
|
|
||||||
web_server.add_entity_to_sorting_list(web_server_, var, config)
|
|
||||||
|
|
||||||
if (mqtt_id := config.get(CONF_MQTT_ID)) is not None:
|
if (mqtt_id := config.get(CONF_MQTT_ID)) is not None:
|
||||||
mqtt_ = cg.new_Pvariable(mqtt_id, var)
|
mqtt_ = cg.new_Pvariable(mqtt_id, var)
|
||||||
await mqtt.register_mqtt_component(mqtt_, config)
|
await mqtt.register_mqtt_component(mqtt_, config)
|
||||||
|
@ -156,6 +152,9 @@ async def setup_cover_core_(var, config):
|
||||||
if (tilt_command_topic := config.get(CONF_TILT_COMMAND_TOPIC)) is not None:
|
if (tilt_command_topic := config.get(CONF_TILT_COMMAND_TOPIC)) is not None:
|
||||||
cg.add(mqtt_.set_custom_tilt_command_topic(tilt_command_topic))
|
cg.add(mqtt_.set_custom_tilt_command_topic(tilt_command_topic))
|
||||||
|
|
||||||
|
if web_server_config := config.get(CONF_WEB_SERVER):
|
||||||
|
await web_server.add_entity_config(var, web_server_config)
|
||||||
|
|
||||||
|
|
||||||
async def register_cover(var, config):
|
async def register_cover(var, config):
|
||||||
if not CORE.has_id(config[CONF_ID]):
|
if not CORE.has_id(config[CONF_ID]):
|
||||||
|
|
|
@ -244,7 +244,7 @@ void CSE7766Component::dump_config() {
|
||||||
LOG_SENSOR(" ", "Apparent Power", this->apparent_power_sensor_);
|
LOG_SENSOR(" ", "Apparent Power", this->apparent_power_sensor_);
|
||||||
LOG_SENSOR(" ", "Reactive Power", this->reactive_power_sensor_);
|
LOG_SENSOR(" ", "Reactive Power", this->reactive_power_sensor_);
|
||||||
LOG_SENSOR(" ", "Power Factor", this->power_factor_sensor_);
|
LOG_SENSOR(" ", "Power Factor", this->power_factor_sensor_);
|
||||||
this->check_uart_settings(4800);
|
this->check_uart_settings(4800, 1, uart::UART_CONFIG_PARITY_EVEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace cse7766
|
} // namespace cse7766
|
||||||
|
|
|
@ -79,7 +79,7 @@ CONFIG_SCHEMA = cv.Schema(
|
||||||
}
|
}
|
||||||
).extend(uart.UART_DEVICE_SCHEMA)
|
).extend(uart.UART_DEVICE_SCHEMA)
|
||||||
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
|
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
|
||||||
"cse7766", baud_rate=4800, require_rx=True
|
"cse7766", baud_rate=4800, parity="EVEN", require_rx=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
import esphome.codegen as cg
|
|
||||||
import esphome.config_validation as cv
|
|
||||||
|
|
||||||
from esphome import pins
|
from esphome import pins
|
||||||
|
import esphome.codegen as cg
|
||||||
from esphome.components import i2c, touchscreen
|
from esphome.components import i2c, touchscreen
|
||||||
from esphome.const import CONF_INTERRUPT_PIN, CONF_ID, CONF_RESET_PIN
|
import esphome.config_validation as cv
|
||||||
from .. import cst816_ns
|
from esphome.const import CONF_ID, CONF_INTERRUPT_PIN, CONF_RESET_PIN
|
||||||
|
|
||||||
|
from .. import cst816_ns
|
||||||
|
|
||||||
CST816Touchscreen = cst816_ns.class_(
|
CST816Touchscreen = cst816_ns.class_(
|
||||||
"CST816Touchscreen",
|
"CST816Touchscreen",
|
||||||
|
@ -14,11 +13,14 @@ CST816Touchscreen = cst816_ns.class_(
|
||||||
)
|
)
|
||||||
|
|
||||||
CST816ButtonListener = cst816_ns.class_("CST816ButtonListener")
|
CST816ButtonListener = cst816_ns.class_("CST816ButtonListener")
|
||||||
|
|
||||||
|
CONF_SKIP_PROBE = "skip_probe"
|
||||||
CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend(
|
CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(CST816Touchscreen),
|
cv.GenerateID(): cv.declare_id(CST816Touchscreen),
|
||||||
cv.Optional(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pin_schema,
|
cv.Optional(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pin_schema,
|
||||||
cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
|
cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
|
||||||
|
cv.Optional(CONF_SKIP_PROBE, default=False): cv.boolean,
|
||||||
}
|
}
|
||||||
).extend(i2c.i2c_device_schema(0x15))
|
).extend(i2c.i2c_device_schema(0x15))
|
||||||
|
|
||||||
|
@ -28,6 +30,7 @@ async def to_code(config):
|
||||||
await touchscreen.register_touchscreen(var, config)
|
await touchscreen.register_touchscreen(var, config)
|
||||||
await i2c.register_i2c_device(var, config)
|
await i2c.register_i2c_device(var, config)
|
||||||
|
|
||||||
|
cg.add(var.set_skip_probe(config[CONF_SKIP_PROBE]))
|
||||||
if interrupt_pin := config.get(CONF_INTERRUPT_PIN):
|
if interrupt_pin := config.get(CONF_INTERRUPT_PIN):
|
||||||
cg.add(var.set_interrupt_pin(await cg.gpio_pin_expression(interrupt_pin)))
|
cg.add(var.set_interrupt_pin(await cg.gpio_pin_expression(interrupt_pin)))
|
||||||
if reset_pin := config.get(CONF_RESET_PIN):
|
if reset_pin := config.get(CONF_RESET_PIN):
|
||||||
|
|
|
@ -8,11 +8,7 @@ void CST816Touchscreen::continue_setup_() {
|
||||||
this->interrupt_pin_->setup();
|
this->interrupt_pin_->setup();
|
||||||
this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_FALLING_EDGE);
|
this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_FALLING_EDGE);
|
||||||
}
|
}
|
||||||
if (!this->read_byte(REG_CHIP_ID, &this->chip_id_)) {
|
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_) {
|
switch (this->chip_id_) {
|
||||||
case CST820_CHIP_ID:
|
case CST820_CHIP_ID:
|
||||||
case CST826_CHIP_ID:
|
case CST826_CHIP_ID:
|
||||||
|
@ -23,17 +19,22 @@ void CST816Touchscreen::continue_setup_() {
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
esph_log_e(TAG, "Unknown chip ID 0x%02X", this->chip_id_);
|
this->status_set_error(str_sprintf("Unknown chip ID 0x%02X", this->chip_id_).c_str());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this->write_byte(REG_IRQ_CTL, IRQ_EN_MOTION);
|
this->write_byte(REG_IRQ_CTL, IRQ_EN_MOTION);
|
||||||
|
} else if (!this->skip_probe_) {
|
||||||
|
this->status_set_error("Failed to read chip id");
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (this->x_raw_max_ == this->x_raw_min_) {
|
if (this->x_raw_max_ == this->x_raw_min_) {
|
||||||
this->x_raw_max_ = this->display_->get_native_width();
|
this->x_raw_max_ = this->display_->get_native_width();
|
||||||
}
|
}
|
||||||
if (this->y_raw_max_ == this->y_raw_min_) {
|
if (this->y_raw_max_ == this->y_raw_min_) {
|
||||||
this->y_raw_max_ = this->display_->get_native_height();
|
this->y_raw_max_ = this->display_->get_native_height();
|
||||||
}
|
}
|
||||||
esph_log_config(TAG, "CST816 Touchscreen setup complete");
|
ESP_LOGCONFIG(TAG, "CST816 Touchscreen setup complete");
|
||||||
}
|
}
|
||||||
|
|
||||||
void CST816Touchscreen::update_button_state_(bool state) {
|
void CST816Touchscreen::update_button_state_(bool state) {
|
||||||
|
@ -45,7 +46,7 @@ void CST816Touchscreen::update_button_state_(bool state) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void CST816Touchscreen::setup() {
|
void CST816Touchscreen::setup() {
|
||||||
esph_log_config(TAG, "Setting up CST816 Touchscreen...");
|
ESP_LOGCONFIG(TAG, "Setting up CST816 Touchscreen...");
|
||||||
if (this->reset_pin_ != nullptr) {
|
if (this->reset_pin_ != nullptr) {
|
||||||
this->reset_pin_->setup();
|
this->reset_pin_->setup();
|
||||||
this->reset_pin_->digital_write(true);
|
this->reset_pin_->digital_write(true);
|
||||||
|
@ -73,7 +74,7 @@ void CST816Touchscreen::update_touches() {
|
||||||
|
|
||||||
uint16_t x = encode_uint16(data[REG_XPOS_HIGH] & 0xF, data[REG_XPOS_LOW]);
|
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]);
|
uint16_t y = encode_uint16(data[REG_YPOS_HIGH] & 0xF, data[REG_YPOS_LOW]);
|
||||||
esph_log_v(TAG, "Read touch %d/%d", x, y);
|
ESP_LOGV(TAG, "Read touch %d/%d", x, y);
|
||||||
if (x >= this->x_raw_max_) {
|
if (x >= this->x_raw_max_) {
|
||||||
this->update_button_state_(true);
|
this->update_button_state_(true);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -45,6 +45,7 @@ class CST816Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice
|
||||||
|
|
||||||
void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; }
|
void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; }
|
||||||
void set_reset_pin(GPIOPin *pin) { this->reset_pin_ = pin; }
|
void set_reset_pin(GPIOPin *pin) { this->reset_pin_ = pin; }
|
||||||
|
void set_skip_probe(bool skip_probe) { this->skip_probe_ = skip_probe; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void continue_setup_();
|
void continue_setup_();
|
||||||
|
@ -53,6 +54,7 @@ class CST816Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice
|
||||||
InternalGPIOPin *interrupt_pin_{};
|
InternalGPIOPin *interrupt_pin_{};
|
||||||
GPIOPin *reset_pin_{};
|
GPIOPin *reset_pin_{};
|
||||||
uint8_t chip_id_{};
|
uint8_t chip_id_{};
|
||||||
|
bool skip_probe_{}; // if set, do not expect to be able to probe the controller on the i2c bus.
|
||||||
std::vector<CST816ButtonListener *> button_listeners_;
|
std::vector<CST816ButtonListener *> button_listeners_;
|
||||||
bool button_touched_{};
|
bool button_touched_{};
|
||||||
};
|
};
|
||||||
|
|
|
@ -18,7 +18,7 @@ from esphome.const import (
|
||||||
CONF_TIME_ID,
|
CONF_TIME_ID,
|
||||||
CONF_TRIGGER_ID,
|
CONF_TRIGGER_ID,
|
||||||
CONF_TYPE,
|
CONF_TYPE,
|
||||||
CONF_WEB_SERVER_ID,
|
CONF_WEB_SERVER,
|
||||||
CONF_YEAR,
|
CONF_YEAR,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, coroutine_with_priority
|
from esphome.core import CORE, coroutine_with_priority
|
||||||
|
@ -138,9 +138,8 @@ async def setup_datetime_core_(var, config):
|
||||||
if (mqtt_id := config.get(CONF_MQTT_ID)) is not None:
|
if (mqtt_id := config.get(CONF_MQTT_ID)) is not None:
|
||||||
mqtt_ = cg.new_Pvariable(mqtt_id, var)
|
mqtt_ = cg.new_Pvariable(mqtt_id, var)
|
||||||
await mqtt.register_mqtt_component(mqtt_, config)
|
await mqtt.register_mqtt_component(mqtt_, config)
|
||||||
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
|
if web_server_config := config.get(CONF_WEB_SERVER):
|
||||||
web_server_ = await cg.get_variable(webserver_id)
|
await web_server.add_entity_config(var, web_server_config)
|
||||||
web_server.add_entity_to_sorting_list(web_server_, var, config)
|
|
||||||
for conf in config.get(CONF_ON_VALUE, []):
|
for conf in config.get(CONF_ON_VALUE, []):
|
||||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||||
await automation.build_automation(trigger, [(cg.ESPTime, "x")], conf)
|
await automation.build_automation(trigger, [(cg.ESPTime, "x")], conf)
|
||||||
|
|
|
@ -53,6 +53,7 @@ from .const import ( # noqa
|
||||||
KEY_SDKCONFIG_OPTIONS,
|
KEY_SDKCONFIG_OPTIONS,
|
||||||
KEY_SUBMODULES,
|
KEY_SUBMODULES,
|
||||||
KEY_VARIANT,
|
KEY_VARIANT,
|
||||||
|
VARIANT_ESP32,
|
||||||
VARIANT_FRIENDLY,
|
VARIANT_FRIENDLY,
|
||||||
VARIANTS,
|
VARIANTS,
|
||||||
)
|
)
|
||||||
|
@ -376,6 +377,15 @@ def final_validate(config):
|
||||||
f"Please specify {CONF_FLASH_SIZE} within esp32 configuration only"
|
f"Please specify {CONF_FLASH_SIZE} within esp32 configuration only"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (
|
||||||
|
config[CONF_VARIANT] != VARIANT_ESP32
|
||||||
|
and CONF_ADVANCED in (conf_fw := config[CONF_FRAMEWORK])
|
||||||
|
and CONF_IGNORE_EFUSE_MAC_CRC in conf_fw[CONF_ADVANCED]
|
||||||
|
):
|
||||||
|
raise cv.Invalid(
|
||||||
|
f"{CONF_IGNORE_EFUSE_MAC_CRC} is not supported on {config[CONF_VARIANT]}"
|
||||||
|
)
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
@ -405,7 +415,7 @@ ESP_IDF_FRAMEWORK_SCHEMA = cv.All(
|
||||||
cv.Optional(
|
cv.Optional(
|
||||||
CONF_IGNORE_EFUSE_CUSTOM_MAC, default=False
|
CONF_IGNORE_EFUSE_CUSTOM_MAC, default=False
|
||||||
): cv.boolean,
|
): cv.boolean,
|
||||||
cv.Optional(CONF_IGNORE_EFUSE_MAC_CRC, default=False): cv.boolean,
|
cv.Optional(CONF_IGNORE_EFUSE_MAC_CRC): cv.boolean,
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_COMPONENTS, default=[]): cv.ensure_list(
|
cv.Optional(CONF_COMPONENTS, default=[]): cv.ensure_list(
|
||||||
|
@ -532,8 +542,8 @@ async def to_code(config):
|
||||||
|
|
||||||
if conf[CONF_ADVANCED][CONF_IGNORE_EFUSE_CUSTOM_MAC]:
|
if conf[CONF_ADVANCED][CONF_IGNORE_EFUSE_CUSTOM_MAC]:
|
||||||
cg.add_define("USE_ESP32_IGNORE_EFUSE_CUSTOM_MAC")
|
cg.add_define("USE_ESP32_IGNORE_EFUSE_CUSTOM_MAC")
|
||||||
if conf[CONF_ADVANCED][CONF_IGNORE_EFUSE_MAC_CRC]:
|
if conf[CONF_ADVANCED].get(CONF_IGNORE_EFUSE_MAC_CRC):
|
||||||
cg.add_define("USE_ESP32_IGNORE_EFUSE_MAC_CRC")
|
add_idf_sdkconfig_option("CONFIG_ESP_MAC_IGNORE_MAC_CRC_ERROR", True)
|
||||||
if (framework_ver.major, framework_ver.minor) >= (4, 4):
|
if (framework_ver.major, framework_ver.minor) >= (4, 4):
|
||||||
add_idf_sdkconfig_option(
|
add_idf_sdkconfig_option(
|
||||||
"CONFIG_ESP_PHY_CALIBRATION_AND_DATA_STORAGE", False
|
"CONFIG_ESP_PHY_CALIBRATION_AND_DATA_STORAGE", False
|
||||||
|
|
|
@ -25,7 +25,7 @@ from esphome.const import (
|
||||||
CONF_SPEED_LEVEL_STATE_TOPIC,
|
CONF_SPEED_LEVEL_STATE_TOPIC,
|
||||||
CONF_SPEED_STATE_TOPIC,
|
CONF_SPEED_STATE_TOPIC,
|
||||||
CONF_TRIGGER_ID,
|
CONF_TRIGGER_ID,
|
||||||
CONF_WEB_SERVER_ID,
|
CONF_WEB_SERVER,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, coroutine_with_priority
|
from esphome.core import CORE, coroutine_with_priority
|
||||||
from esphome.cpp_helpers import setup_entity
|
from esphome.cpp_helpers import setup_entity
|
||||||
|
@ -218,9 +218,8 @@ async def setup_fan_core_(var, config):
|
||||||
if (speed_command_topic := config.get(CONF_SPEED_COMMAND_TOPIC)) is not None:
|
if (speed_command_topic := config.get(CONF_SPEED_COMMAND_TOPIC)) is not None:
|
||||||
cg.add(mqtt_.set_custom_speed_command_topic(speed_command_topic))
|
cg.add(mqtt_.set_custom_speed_command_topic(speed_command_topic))
|
||||||
|
|
||||||
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
|
if web_server_config := config.get(CONF_WEB_SERVER):
|
||||||
web_server_ = await cg.get_variable(webserver_id)
|
await web_server.add_entity_config(var, web_server_config)
|
||||||
web_server.add_entity_to_sorting_list(web_server_, var, config)
|
|
||||||
|
|
||||||
for conf in config.get(CONF_ON_STATE, []):
|
for conf in config.get(CONF_ON_STATE, []):
|
||||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||||
|
|
|
@ -98,13 +98,13 @@ def validate_pillow_installed(value):
|
||||||
except ImportError as err:
|
except ImportError as err:
|
||||||
raise cv.Invalid(
|
raise cv.Invalid(
|
||||||
"Please install the pillow python package to use this feature. "
|
"Please install the pillow python package to use this feature. "
|
||||||
'(pip install "pillow==10.2.0")'
|
'(pip install "pillow==10.4.0")'
|
||||||
) from err
|
) from err
|
||||||
|
|
||||||
if version.parse(PIL.__version__) != version.parse("10.2.0"):
|
if version.parse(PIL.__version__) != version.parse("10.4.0"):
|
||||||
raise cv.Invalid(
|
raise cv.Invalid(
|
||||||
"Please update your pillow installation to 10.2.0. "
|
"Please update your pillow installation to 10.4.0. "
|
||||||
'(pip install "pillow==10.2.0")'
|
'(pip install "pillow==10.4.0")'
|
||||||
)
|
)
|
||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
0
esphome/components/grove_gas_mc_v2/__init__.py
Normal file
0
esphome/components/grove_gas_mc_v2/__init__.py
Normal file
88
esphome/components/grove_gas_mc_v2/grove_gas_mc_v2.cpp
Normal file
88
esphome/components/grove_gas_mc_v2/grove_gas_mc_v2.cpp
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
#include "grove_gas_mc_v2.h"
|
||||||
|
#include "esphome/core/hal.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace grove_gas_mc_v2 {
|
||||||
|
|
||||||
|
static const char *const TAG = "grove_gas_mc_v2";
|
||||||
|
|
||||||
|
// I2C Commands for Grove Gas Multichannel V2 Sensor
|
||||||
|
// Taken from:
|
||||||
|
// https://github.com/Seeed-Studio/Seeed_Arduino_MultiGas/blob/master/src/Multichannel_Gas_GroveGasMultichannelV2.h
|
||||||
|
static const uint8_t GROVE_GAS_MC_V2_HEAT_ON = 0xFE;
|
||||||
|
static const uint8_t GROVE_GAS_MC_V2_HEAT_OFF = 0xFF;
|
||||||
|
static const uint8_t GROVE_GAS_MC_V2_READ_GM102B = 0x01;
|
||||||
|
static const uint8_t GROVE_GAS_MC_V2_READ_GM302B = 0x03;
|
||||||
|
static const uint8_t GROVE_GAS_MC_V2_READ_GM502B = 0x05;
|
||||||
|
static const uint8_t GROVE_GAS_MC_V2_READ_GM702B = 0x07;
|
||||||
|
|
||||||
|
bool GroveGasMultichannelV2Component::read_sensor_(uint8_t address, sensor::Sensor *sensor) {
|
||||||
|
if (sensor == nullptr) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
uint32_t value = 0;
|
||||||
|
if (!this->read_bytes(address, (uint8_t *) &value, 4)) {
|
||||||
|
ESP_LOGW(TAG, "Reading Grove Gas Sensor data failed!");
|
||||||
|
this->error_code_ = COMMUNICATION_FAILED;
|
||||||
|
this->status_set_warning();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
sensor->publish_state(value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GroveGasMultichannelV2Component::setup() {
|
||||||
|
ESP_LOGCONFIG(TAG, "Setting up Grove Multichannel Gas Sensor V2...");
|
||||||
|
|
||||||
|
// Before reading sensor values, must preheat sensor
|
||||||
|
if (!(this->write_bytes(GROVE_GAS_MC_V2_HEAT_ON, {}))) {
|
||||||
|
this->mark_failed();
|
||||||
|
this->error_code_ = APP_START_FAILED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GroveGasMultichannelV2Component::update() {
|
||||||
|
// Read from each of the gas sensors
|
||||||
|
if (!this->read_sensor_(GROVE_GAS_MC_V2_READ_GM102B, this->nitrogen_dioxide_sensor_))
|
||||||
|
return;
|
||||||
|
if (!this->read_sensor_(GROVE_GAS_MC_V2_READ_GM302B, this->ethanol_sensor_))
|
||||||
|
return;
|
||||||
|
if (!this->read_sensor_(GROVE_GAS_MC_V2_READ_GM502B, this->tvoc_sensor_))
|
||||||
|
return;
|
||||||
|
if (!this->read_sensor_(GROVE_GAS_MC_V2_READ_GM702B, this->carbon_monoxide_sensor_))
|
||||||
|
return;
|
||||||
|
|
||||||
|
this->status_clear_warning();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GroveGasMultichannelV2Component::dump_config() {
|
||||||
|
ESP_LOGCONFIG(TAG, "Grove Multichannel Gas Sensor V2");
|
||||||
|
LOG_I2C_DEVICE(this)
|
||||||
|
LOG_UPDATE_INTERVAL(this)
|
||||||
|
LOG_SENSOR(" ", "Nitrogen Dioxide", this->nitrogen_dioxide_sensor_)
|
||||||
|
LOG_SENSOR(" ", "Ethanol", this->ethanol_sensor_)
|
||||||
|
LOG_SENSOR(" ", "Carbon Monoxide", this->carbon_monoxide_sensor_)
|
||||||
|
LOG_SENSOR(" ", "TVOC", this->tvoc_sensor_)
|
||||||
|
|
||||||
|
if (this->is_failed()) {
|
||||||
|
switch (this->error_code_) {
|
||||||
|
case COMMUNICATION_FAILED:
|
||||||
|
ESP_LOGW(TAG, "Communication failed! Is the sensor connected?");
|
||||||
|
break;
|
||||||
|
case APP_INVALID:
|
||||||
|
ESP_LOGW(TAG, "Sensor reported invalid APP installed.");
|
||||||
|
break;
|
||||||
|
case APP_START_FAILED:
|
||||||
|
ESP_LOGW(TAG, "Sensor reported APP start failed.");
|
||||||
|
break;
|
||||||
|
case UNKNOWN:
|
||||||
|
default:
|
||||||
|
ESP_LOGW(TAG, "Unknown setup error!");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace grove_gas_mc_v2
|
||||||
|
} // namespace esphome
|
39
esphome/components/grove_gas_mc_v2/grove_gas_mc_v2.h
Normal file
39
esphome/components/grove_gas_mc_v2/grove_gas_mc_v2.h
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/components/i2c/i2c.h"
|
||||||
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/core/preferences.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace grove_gas_mc_v2 {
|
||||||
|
|
||||||
|
class GroveGasMultichannelV2Component : public PollingComponent, public i2c::I2CDevice {
|
||||||
|
SUB_SENSOR(tvoc)
|
||||||
|
SUB_SENSOR(carbon_monoxide)
|
||||||
|
SUB_SENSOR(nitrogen_dioxide)
|
||||||
|
SUB_SENSOR(ethanol)
|
||||||
|
|
||||||
|
public:
|
||||||
|
/// Setup the sensor and test for a connection.
|
||||||
|
void setup() override;
|
||||||
|
/// Schedule temperature+pressure readings.
|
||||||
|
void update() override;
|
||||||
|
|
||||||
|
void dump_config() override;
|
||||||
|
|
||||||
|
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
enum ErrorCode {
|
||||||
|
UNKNOWN,
|
||||||
|
COMMUNICATION_FAILED,
|
||||||
|
APP_INVALID,
|
||||||
|
APP_START_FAILED,
|
||||||
|
} error_code_{UNKNOWN};
|
||||||
|
|
||||||
|
bool read_sensor_(uint8_t address, sensor::Sensor *sensor);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace grove_gas_mc_v2
|
||||||
|
} // namespace esphome
|
77
esphome/components/grove_gas_mc_v2/sensor.py
Normal file
77
esphome/components/grove_gas_mc_v2/sensor.py
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
from esphome.components import i2c, sensor
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_CARBON_MONOXIDE,
|
||||||
|
CONF_ETHANOL,
|
||||||
|
CONF_ID,
|
||||||
|
CONF_NITROGEN_DIOXIDE,
|
||||||
|
CONF_TVOC,
|
||||||
|
DEVICE_CLASS_CARBON_MONOXIDE,
|
||||||
|
DEVICE_CLASS_NITROGEN_DIOXIDE,
|
||||||
|
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
|
||||||
|
ICON_AIR_FILTER,
|
||||||
|
ICON_FLASK_ROUND_BOTTOM,
|
||||||
|
ICON_GAS_CYLINDER,
|
||||||
|
ICON_MOLECULE_CO,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
UNIT_MICROGRAMS_PER_CUBIC_METER,
|
||||||
|
UNIT_PARTS_PER_MILLION,
|
||||||
|
)
|
||||||
|
|
||||||
|
CODEOWNERS = ["@YorkshireIoT"]
|
||||||
|
DEPENDENCIES = ["i2c"]
|
||||||
|
|
||||||
|
grove_gas_mc_v2_ns = cg.esphome_ns.namespace("grove_gas_mc_v2")
|
||||||
|
|
||||||
|
GroveGasMultichannelV2Component = grove_gas_mc_v2_ns.class_(
|
||||||
|
"GroveGasMultichannelV2Component", cg.PollingComponent, i2c.I2CDevice
|
||||||
|
)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = (
|
||||||
|
cv.Schema(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(GroveGasMultichannelV2Component),
|
||||||
|
cv.Optional(CONF_TVOC): sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER,
|
||||||
|
icon=ICON_AIR_FILTER,
|
||||||
|
accuracy_decimals=0,
|
||||||
|
device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_CARBON_MONOXIDE): sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_PARTS_PER_MILLION,
|
||||||
|
icon=ICON_MOLECULE_CO,
|
||||||
|
accuracy_decimals=0,
|
||||||
|
device_class=DEVICE_CLASS_CARBON_MONOXIDE,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_NITROGEN_DIOXIDE): sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER,
|
||||||
|
icon=ICON_GAS_CYLINDER,
|
||||||
|
accuracy_decimals=0,
|
||||||
|
device_class=DEVICE_CLASS_NITROGEN_DIOXIDE,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_ETHANOL): sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_PARTS_PER_MILLION,
|
||||||
|
icon=ICON_FLASK_ROUND_BOTTOM,
|
||||||
|
accuracy_decimals=0,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(cv.polling_component_schema("60s"))
|
||||||
|
.extend(i2c.i2c_device_schema(0x08))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
for key in [CONF_TVOC, CONF_CARBON_MONOXIDE, CONF_NITROGEN_DIOXIDE, CONF_ETHANOL]:
|
||||||
|
if sensor_config := config.get(key):
|
||||||
|
sensor_ = await sensor.new_sensor(sensor_config)
|
||||||
|
cg.add(getattr(var, f"set_{key}_sensor")(sensor_))
|
|
@ -1,6 +1,7 @@
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include "hmac_md5.h"
|
#include "hmac_md5.h"
|
||||||
|
#ifdef USE_MD5
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
|
@ -54,3 +55,4 @@ bool HmacMD5::equals_hex(const char *expected) { return this->ohash_.equals_hex(
|
||||||
|
|
||||||
} // namespace hmac_md5
|
} // namespace hmac_md5
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
#endif
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "esphome/core/defines.h"
|
#include "esphome/core/defines.h"
|
||||||
|
#ifdef USE_MD5
|
||||||
#include "esphome/components/md5/md5.h"
|
#include "esphome/components/md5/md5.h"
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
|
@ -46,3 +46,4 @@ class HmacMD5 {
|
||||||
|
|
||||||
} // namespace hmac_md5
|
} // namespace hmac_md5
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
#endif
|
||||||
|
|
|
@ -89,6 +89,7 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
|
||||||
|
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
void setup() override;
|
void setup() override;
|
||||||
|
void on_shutdown() override { this->command(ILI9XXX_SLPIN); }
|
||||||
|
|
||||||
display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_COLOR; }
|
display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_COLOR; }
|
||||||
void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order,
|
void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order,
|
||||||
|
|
|
@ -79,6 +79,50 @@ Color Image::get_pixel(int x, int y, Color color_on, Color color_off) const {
|
||||||
return color_off;
|
return color_off;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#ifdef USE_LVGL
|
||||||
|
lv_img_dsc_t *Image::get_lv_img_dsc() {
|
||||||
|
// lazily construct lvgl image_dsc.
|
||||||
|
if (this->dsc_.data != this->data_start_) {
|
||||||
|
this->dsc_.data = this->data_start_;
|
||||||
|
this->dsc_.header.always_zero = 0;
|
||||||
|
this->dsc_.header.reserved = 0;
|
||||||
|
this->dsc_.header.w = this->width_;
|
||||||
|
this->dsc_.header.h = this->height_;
|
||||||
|
this->dsc_.data_size = image_type_to_width_stride(this->dsc_.header.w * this->dsc_.header.h, this->get_type());
|
||||||
|
switch (this->get_type()) {
|
||||||
|
case IMAGE_TYPE_BINARY:
|
||||||
|
this->dsc_.header.cf = LV_IMG_CF_ALPHA_1BIT;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case IMAGE_TYPE_GRAYSCALE:
|
||||||
|
this->dsc_.header.cf = LV_IMG_CF_ALPHA_8BIT;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case IMAGE_TYPE_RGB24:
|
||||||
|
this->dsc_.header.cf = LV_IMG_CF_RGB888;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case IMAGE_TYPE_RGB565:
|
||||||
|
#if LV_COLOR_DEPTH == 16
|
||||||
|
this->dsc_.header.cf = this->has_transparency() ? LV_IMG_CF_TRUE_COLOR_CHROMA_KEYED : LV_IMG_CF_TRUE_COLOR;
|
||||||
|
#else
|
||||||
|
this->dsc_.header.cf = LV_IMG_CF_RGB565;
|
||||||
|
#endif
|
||||||
|
break;
|
||||||
|
|
||||||
|
case image::IMAGE_TYPE_RGBA:
|
||||||
|
#if LV_COLOR_DEPTH == 32
|
||||||
|
this->dsc_.header.cf = LV_IMG_CF_TRUE_COLOR;
|
||||||
|
#else
|
||||||
|
this->dsc_.header.cf = LV_IMG_CF_RGBA8888;
|
||||||
|
#endif
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &this->dsc_;
|
||||||
|
}
|
||||||
|
#endif // USE_LVGL
|
||||||
|
|
||||||
bool Image::get_binary_pixel_(int x, int y) const {
|
bool Image::get_binary_pixel_(int x, int y) const {
|
||||||
const uint32_t width_8 = ((this->width_ + 7u) / 8u) * 8u;
|
const uint32_t width_8 = ((this->width_ + 7u) / 8u) * 8u;
|
||||||
const uint32_t pos = x + y * width_8;
|
const uint32_t pos = x + y * width_8;
|
||||||
|
|
|
@ -1,6 +1,15 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "esphome/core/color.h"
|
#include "esphome/core/color.h"
|
||||||
#include "esphome/components/display/display_buffer.h"
|
#include "esphome/components/display/display.h"
|
||||||
|
|
||||||
|
#ifdef USE_LVGL
|
||||||
|
// required for clang-tidy
|
||||||
|
#ifndef LV_CONF_H
|
||||||
|
#define LV_CONF_SKIP 1 // NOLINT
|
||||||
|
#endif // LV_CONF_H
|
||||||
|
|
||||||
|
#include <lvgl.h>
|
||||||
|
#endif // USE_LVGL
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace image {
|
namespace image {
|
||||||
|
@ -37,7 +46,7 @@ class Image : public display::BaseImage {
|
||||||
Color get_pixel(int x, int y, Color color_on = display::COLOR_ON, Color color_off = display::COLOR_OFF) const;
|
Color get_pixel(int x, int y, Color color_on = display::COLOR_ON, Color color_off = display::COLOR_OFF) const;
|
||||||
int get_width() const override;
|
int get_width() const override;
|
||||||
int get_height() const override;
|
int get_height() const override;
|
||||||
const uint8_t *get_data_start() { return this->data_start_; }
|
const uint8_t *get_data_start() const { return this->data_start_; }
|
||||||
ImageType get_type() const;
|
ImageType get_type() const;
|
||||||
|
|
||||||
void draw(int x, int y, display::Display *display, Color color_on, Color color_off) override;
|
void draw(int x, int y, display::Display *display, Color color_on, Color color_off) override;
|
||||||
|
@ -45,6 +54,9 @@ class Image : public display::BaseImage {
|
||||||
void set_transparency(bool transparent) { transparent_ = transparent; }
|
void set_transparency(bool transparent) { transparent_ = transparent; }
|
||||||
bool has_transparency() const { return transparent_; }
|
bool has_transparency() const { return transparent_; }
|
||||||
|
|
||||||
|
#ifdef USE_LVGL
|
||||||
|
lv_img_dsc_t *get_lv_img_dsc();
|
||||||
|
#endif
|
||||||
protected:
|
protected:
|
||||||
bool get_binary_pixel_(int x, int y) const;
|
bool get_binary_pixel_(int x, int y) const;
|
||||||
Color get_rgb24_pixel_(int x, int y) const;
|
Color get_rgb24_pixel_(int x, int y) const;
|
||||||
|
@ -57,6 +69,9 @@ class Image : public display::BaseImage {
|
||||||
ImageType type_;
|
ImageType type_;
|
||||||
const uint8_t *data_start_;
|
const uint8_t *data_start_;
|
||||||
bool transparent_;
|
bool transparent_;
|
||||||
|
#ifdef USE_LVGL
|
||||||
|
lv_img_dsc_t dsc_{};
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace image
|
} // namespace image
|
||||||
|
|
|
@ -18,7 +18,7 @@ from esphome.const import (
|
||||||
CONF_RESTORE_MODE,
|
CONF_RESTORE_MODE,
|
||||||
CONF_TRIGGER_ID,
|
CONF_TRIGGER_ID,
|
||||||
CONF_WARM_WHITE_COLOR_TEMPERATURE,
|
CONF_WARM_WHITE_COLOR_TEMPERATURE,
|
||||||
CONF_WEB_SERVER_ID,
|
CONF_WEB_SERVER,
|
||||||
)
|
)
|
||||||
from esphome.core import coroutine_with_priority
|
from esphome.core import coroutine_with_priority
|
||||||
from esphome.cpp_helpers import setup_entity
|
from esphome.cpp_helpers import setup_entity
|
||||||
|
@ -181,9 +181,8 @@ async def setup_light_core_(light_var, output_var, config):
|
||||||
mqtt_ = cg.new_Pvariable(mqtt_id, light_var)
|
mqtt_ = cg.new_Pvariable(mqtt_id, light_var)
|
||||||
await mqtt.register_mqtt_component(mqtt_, config)
|
await mqtt.register_mqtt_component(mqtt_, config)
|
||||||
|
|
||||||
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
|
if web_server_config := config.get(CONF_WEB_SERVER):
|
||||||
web_server_ = await cg.get_variable(webserver_id)
|
await web_server.add_entity_config(light_var, web_server_config)
|
||||||
web_server.add_entity_to_sorting_list(web_server_, light_var, config)
|
|
||||||
|
|
||||||
|
|
||||||
async def register_light(output_var, config):
|
async def register_light(output_var, config):
|
||||||
|
|
|
@ -9,7 +9,7 @@ from esphome.const import (
|
||||||
CONF_ON_LOCK,
|
CONF_ON_LOCK,
|
||||||
CONF_ON_UNLOCK,
|
CONF_ON_UNLOCK,
|
||||||
CONF_TRIGGER_ID,
|
CONF_TRIGGER_ID,
|
||||||
CONF_WEB_SERVER_ID,
|
CONF_WEB_SERVER,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, coroutine_with_priority
|
from esphome.core import CORE, coroutine_with_priority
|
||||||
from esphome.cpp_helpers import setup_entity
|
from esphome.cpp_helpers import setup_entity
|
||||||
|
@ -66,9 +66,8 @@ async def setup_lock_core_(var, config):
|
||||||
mqtt_ = cg.new_Pvariable(mqtt_id, var)
|
mqtt_ = cg.new_Pvariable(mqtt_id, var)
|
||||||
await mqtt.register_mqtt_component(mqtt_, config)
|
await mqtt.register_mqtt_component(mqtt_, config)
|
||||||
|
|
||||||
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
|
if web_server_config := config.get(CONF_WEB_SERVER):
|
||||||
web_server_ = await cg.get_variable(webserver_id)
|
await web_server.add_entity_config(var, web_server_config)
|
||||||
web_server.add_entity_to_sorting_list(web_server_, var, config)
|
|
||||||
|
|
||||||
|
|
||||||
async def register_lock(var, config):
|
async def register_lock(var, config):
|
||||||
|
|
|
@ -53,7 +53,7 @@ from .types import (
|
||||||
lv_style_t,
|
lv_style_t,
|
||||||
lvgl_ns,
|
lvgl_ns,
|
||||||
)
|
)
|
||||||
from .widgets import Widget, add_widgets, lv_scr_act, set_obj_properties
|
from .widgets import Widget, add_widgets, lv_scr_act, set_obj_properties, styles_used
|
||||||
from .widgets.animimg import animimg_spec
|
from .widgets.animimg import animimg_spec
|
||||||
from .widgets.arc import arc_spec
|
from .widgets.arc import arc_spec
|
||||||
from .widgets.button import button_spec
|
from .widgets.button import button_spec
|
||||||
|
@ -280,6 +280,8 @@ async def to_code(config):
|
||||||
|
|
||||||
for comp in helpers.lvgl_components_required:
|
for comp in helpers.lvgl_components_required:
|
||||||
CORE.add_define(f"USE_LVGL_{comp.upper()}")
|
CORE.add_define(f"USE_LVGL_{comp.upper()}")
|
||||||
|
if "transform_angle" in styles_used:
|
||||||
|
add_define("LV_COLOR_SCREEN_TRANSP", "1")
|
||||||
for use in helpers.lv_uses:
|
for use in helpers.lv_uses:
|
||||||
add_define(f"LV_USE_{use.upper()}")
|
add_define(f"LV_USE_{use.upper()}")
|
||||||
lv_conf_h_file = CORE.relative_src_path(LV_CONF_FILENAME)
|
lv_conf_h_file = CORE.relative_src_path(LV_CONF_FILENAME)
|
||||||
|
|
|
@ -452,6 +452,7 @@ CONF_OFFSET_Y = "offset_y"
|
||||||
CONF_ONE_CHECKED = "one_checked"
|
CONF_ONE_CHECKED = "one_checked"
|
||||||
CONF_ONE_LINE = "one_line"
|
CONF_ONE_LINE = "one_line"
|
||||||
CONF_ON_SELECT = "on_select"
|
CONF_ON_SELECT = "on_select"
|
||||||
|
CONF_OPA = "opa"
|
||||||
CONF_NEXT = "next"
|
CONF_NEXT = "next"
|
||||||
CONF_PAD_ROW = "pad_row"
|
CONF_PAD_ROW = "pad_row"
|
||||||
CONF_PAD_COLUMN = "pad_column"
|
CONF_PAD_COLUMN = "pad_column"
|
||||||
|
|
|
@ -31,7 +31,6 @@ from .defines import (
|
||||||
literal,
|
literal,
|
||||||
)
|
)
|
||||||
from .helpers import esphome_fonts_used, lv_fonts_used, requires_component
|
from .helpers import esphome_fonts_used, lv_fonts_used, requires_component
|
||||||
from .lvcode import lv_expr
|
|
||||||
from .types import lv_font_t, lv_gradient_t, lv_img_t
|
from .types import lv_font_t, lv_gradient_t, lv_img_t
|
||||||
|
|
||||||
opacity_consts = LvConstant("LV_OPA_", "TRANSP", "COVER")
|
opacity_consts = LvConstant("LV_OPA_", "TRANSP", "COVER")
|
||||||
|
@ -243,6 +242,8 @@ def pixels_or_percent_validator(value):
|
||||||
"""A length in one axis - either a number (pixels) or a percentage"""
|
"""A length in one axis - either a number (pixels) or a percentage"""
|
||||||
if value == SCHEMA_EXTRACT:
|
if value == SCHEMA_EXTRACT:
|
||||||
return ["pixels", "..%"]
|
return ["pixels", "..%"]
|
||||||
|
if isinstance(value, str) and value.lower().endswith("px"):
|
||||||
|
value = cv.int_(value[:-2])
|
||||||
value = cv.Any(cv.int_, cv.percentage)(value)
|
value = cv.Any(cv.int_, cv.percentage)(value)
|
||||||
if isinstance(value, int):
|
if isinstance(value, int):
|
||||||
return value
|
return value
|
||||||
|
@ -330,7 +331,7 @@ def image_validator(value):
|
||||||
lv_image = LValidator(
|
lv_image = LValidator(
|
||||||
image_validator,
|
image_validator,
|
||||||
lv_img_t,
|
lv_img_t,
|
||||||
retmapper=lambda x: lv_expr.img_from(MockObj(x)),
|
retmapper=lambda x: MockObj(x, "->").get_lv_img_dsc(),
|
||||||
requires="image",
|
requires="image",
|
||||||
)
|
)
|
||||||
lv_bool = LValidator(cv.boolean, cg.bool_, retmapper=literal)
|
lv_bool = LValidator(cv.boolean, cg.bool_, retmapper=literal)
|
||||||
|
|
|
@ -356,49 +356,6 @@ bool lv_is_pre_initialise() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef USE_LVGL_IMAGE
|
|
||||||
lv_img_dsc_t *lv_img_from(image::Image *src, lv_img_dsc_t *img_dsc) {
|
|
||||||
if (img_dsc == nullptr)
|
|
||||||
img_dsc = new lv_img_dsc_t(); // NOLINT
|
|
||||||
img_dsc->header.always_zero = 0;
|
|
||||||
img_dsc->header.reserved = 0;
|
|
||||||
img_dsc->header.w = src->get_width();
|
|
||||||
img_dsc->header.h = src->get_height();
|
|
||||||
img_dsc->data = src->get_data_start();
|
|
||||||
img_dsc->data_size = image_type_to_width_stride(img_dsc->header.w * img_dsc->header.h, src->get_type());
|
|
||||||
switch (src->get_type()) {
|
|
||||||
case image::IMAGE_TYPE_BINARY:
|
|
||||||
img_dsc->header.cf = LV_IMG_CF_ALPHA_1BIT;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case image::IMAGE_TYPE_GRAYSCALE:
|
|
||||||
img_dsc->header.cf = LV_IMG_CF_ALPHA_8BIT;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case image::IMAGE_TYPE_RGB24:
|
|
||||||
img_dsc->header.cf = LV_IMG_CF_RGB888;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case image::IMAGE_TYPE_RGB565:
|
|
||||||
#if LV_COLOR_DEPTH == 16
|
|
||||||
img_dsc->header.cf = src->has_transparency() ? LV_IMG_CF_TRUE_COLOR_CHROMA_KEYED : LV_IMG_CF_TRUE_COLOR;
|
|
||||||
#else
|
|
||||||
img_dsc->header.cf = LV_IMG_CF_RGB565;
|
|
||||||
#endif
|
|
||||||
break;
|
|
||||||
|
|
||||||
case image::IMAGE_TYPE_RGBA:
|
|
||||||
#if LV_COLOR_DEPTH == 32
|
|
||||||
img_dsc->header.cf = LV_IMG_CF_TRUE_COLOR;
|
|
||||||
#else
|
|
||||||
img_dsc->header.cf = LV_IMG_CF_RGBA8888;
|
|
||||||
#endif
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return img_dsc;
|
|
||||||
}
|
|
||||||
#endif // USE_LVGL_IMAGE
|
|
||||||
|
|
||||||
#ifdef USE_LVGL_ANIMIMG
|
#ifdef USE_LVGL_ANIMIMG
|
||||||
void lv_animimg_stop(lv_obj_t *obj) {
|
void lv_animimg_stop(lv_obj_t *obj) {
|
||||||
auto *animg = (lv_animimg_t *) obj;
|
auto *animg = (lv_animimg_t *) obj;
|
||||||
|
|
|
@ -102,10 +102,6 @@ class FontEngine {
|
||||||
lv_font_t lv_font_{};
|
lv_font_t lv_font_{};
|
||||||
};
|
};
|
||||||
#endif // USE_LVGL_FONT
|
#endif // USE_LVGL_FONT
|
||||||
#ifdef USE_LVGL_IMAGE
|
|
||||||
lv_img_dsc_t *lv_img_from(image::Image *src, lv_img_dsc_t *img_dsc = nullptr);
|
|
||||||
#endif // USE_LVGL_IMAGE
|
|
||||||
|
|
||||||
#ifdef USE_LVGL_ANIMIMG
|
#ifdef USE_LVGL_ANIMIMG
|
||||||
void lv_animimg_stop(lv_obj_t *obj);
|
void lv_animimg_stop(lv_obj_t *obj);
|
||||||
#endif // USE_LVGL_ANIMIMG
|
#endif // USE_LVGL_ANIMIMG
|
||||||
|
|
|
@ -12,7 +12,7 @@ from .defines import (
|
||||||
)
|
)
|
||||||
from .helpers import add_lv_use
|
from .helpers import add_lv_use
|
||||||
from .lvcode import LambdaContext, LocalVariable, lv, lv_assign, lv_variable
|
from .lvcode import LambdaContext, LocalVariable, lv, lv_assign, lv_variable
|
||||||
from .schemas import ALL_STYLES
|
from .schemas import ALL_STYLES, STYLE_REMAP
|
||||||
from .types import lv_lambda_t, lv_obj_t, lv_obj_t_ptr
|
from .types import lv_lambda_t, lv_obj_t, lv_obj_t_ptr
|
||||||
from .widgets import Widget, add_widgets, set_obj_properties, theme_widget_map
|
from .widgets import Widget, add_widgets, set_obj_properties, theme_widget_map
|
||||||
from .widgets.obj import obj_spec
|
from .widgets.obj import obj_spec
|
||||||
|
@ -31,7 +31,8 @@ async def styles_to_code(config):
|
||||||
value = await validator.process(value)
|
value = await validator.process(value)
|
||||||
if isinstance(value, list):
|
if isinstance(value, list):
|
||||||
value = "|".join(value)
|
value = "|".join(value)
|
||||||
lv.call(f"style_set_{prop}", svar, literal(value))
|
remapped_prop = STYLE_REMAP.get(prop, prop)
|
||||||
|
lv.call(f"style_set_{remapped_prop}", svar, literal(value))
|
||||||
|
|
||||||
|
|
||||||
async def theme_to_code(config):
|
async def theme_to_code(config):
|
||||||
|
|
|
@ -34,13 +34,13 @@ async def to_code(config):
|
||||||
widget = widget[0]
|
widget = widget[0]
|
||||||
await wait_for_widgets()
|
await wait_for_widgets()
|
||||||
async with LambdaContext([(cg.std_string, "text_value")]) as control:
|
async with LambdaContext([(cg.std_string, "text_value")]) as control:
|
||||||
await widget.set_property("text", "text_value.c_str())")
|
await widget.set_property("text", "text_value.c_str()")
|
||||||
lv.event_send(widget.obj, API_EVENT, None)
|
lv.event_send(widget.obj, API_EVENT, cg.nullptr)
|
||||||
control.add(textvar.publish_state(widget.get_value()))
|
control.add(textvar.publish_state(widget.get_value()))
|
||||||
async with LambdaContext(EVENT_ARG) as lamb:
|
async with LambdaContext(EVENT_ARG) as lamb:
|
||||||
lv_add(textvar.publish_state(widget.get_value()))
|
lv_add(textvar.publish_state(widget.get_value()))
|
||||||
async with LvContext(paren):
|
async with LvContext(paren):
|
||||||
widget.var.set_control_lambda(await control.get_lambda())
|
lv_add(textvar.set_control_lambda(await control.get_lambda()))
|
||||||
lv_add(
|
lv_add(
|
||||||
paren.add_event_cb(
|
paren.add_event_cb(
|
||||||
widget.obj,
|
widget.obj,
|
||||||
|
|
|
@ -52,6 +52,7 @@ from ..types import LV_STATE, LvType, WidgetType, lv_coord_t, lv_obj_t, lv_obj_t
|
||||||
EVENT_LAMB = "event_lamb__"
|
EVENT_LAMB = "event_lamb__"
|
||||||
|
|
||||||
theme_widget_map = {}
|
theme_widget_map = {}
|
||||||
|
styles_used = set()
|
||||||
|
|
||||||
|
|
||||||
class LvScrActType(WidgetType):
|
class LvScrActType(WidgetType):
|
||||||
|
@ -158,6 +159,7 @@ class Widget:
|
||||||
def set_style(self, prop, value, state):
|
def set_style(self, prop, value, state):
|
||||||
if value is None:
|
if value is None:
|
||||||
return
|
return
|
||||||
|
styles_used.add(prop)
|
||||||
lv.call(f"obj_set_style_{prop}", self.obj, value, state)
|
lv.call(f"obj_set_style_{prop}", self.obj, value, state)
|
||||||
|
|
||||||
def __type_base(self):
|
def __type_base(self):
|
||||||
|
|
|
@ -2,13 +2,12 @@ from esphome import automation
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import CONF_DURATION, CONF_ID
|
from esphome.const import CONF_DURATION, CONF_ID
|
||||||
from esphome.cpp_generator import MockObj
|
|
||||||
|
|
||||||
from ..automation import action_to_code
|
from ..automation import action_to_code
|
||||||
from ..defines import CONF_AUTO_START, CONF_MAIN, CONF_REPEAT_COUNT, CONF_SRC
|
from ..defines import CONF_AUTO_START, CONF_MAIN, CONF_REPEAT_COUNT, CONF_SRC
|
||||||
from ..helpers import lvgl_components_required
|
from ..helpers import lvgl_components_required
|
||||||
from ..lv_validation import lv_image, lv_milliseconds
|
from ..lv_validation import lv_image, lv_milliseconds
|
||||||
from ..lvcode import lv, lv_expr
|
from ..lvcode import lv
|
||||||
from ..types import LvType, ObjUpdateAction, void_ptr
|
from ..types import LvType, ObjUpdateAction, void_ptr
|
||||||
from . import Widget, WidgetType, get_widgets
|
from . import Widget, WidgetType, get_widgets
|
||||||
from .img import CONF_IMAGE
|
from .img import CONF_IMAGE
|
||||||
|
@ -63,7 +62,7 @@ class AnimimgType(WidgetType):
|
||||||
if CONF_SRC in config:
|
if CONF_SRC in config:
|
||||||
for x in config[CONF_SRC]:
|
for x in config[CONF_SRC]:
|
||||||
await cg.get_variable(x)
|
await cg.get_variable(x)
|
||||||
srcs = [lv_expr.img_from(MockObj(x)) for x in config[CONF_SRC]]
|
srcs = [await lv_image.process(x) for x in config[CONF_SRC]]
|
||||||
src_id = cg.static_const_array(config[CONF_SRC_LIST_ID], srcs)
|
src_id = cg.static_const_array(config[CONF_SRC_LIST_ID], srcs)
|
||||||
count = len(config[CONF_SRC])
|
count = len(config[CONF_SRC])
|
||||||
lv.animimg_set_src(w.obj, src_id, count)
|
lv.animimg_set_src(w.obj, src_id, count)
|
||||||
|
@ -73,7 +72,7 @@ class AnimimgType(WidgetType):
|
||||||
lv.animimg_start(w.obj)
|
lv.animimg_start(w.obj)
|
||||||
|
|
||||||
def get_uses(self):
|
def get_uses(self):
|
||||||
return CONF_IMAGE, CONF_LABEL
|
return "img", CONF_IMAGE, CONF_LABEL
|
||||||
|
|
||||||
|
|
||||||
animimg_spec = AnimimgType()
|
animimg_spec = AnimimgType()
|
||||||
|
|
|
@ -6,6 +6,8 @@ from ..defines import (
|
||||||
CONF_DIR,
|
CONF_DIR,
|
||||||
CONF_INDICATOR,
|
CONF_INDICATOR,
|
||||||
CONF_MAIN,
|
CONF_MAIN,
|
||||||
|
CONF_SCROLLBAR,
|
||||||
|
CONF_SELECTED,
|
||||||
CONF_SELECTED_INDEX,
|
CONF_SELECTED_INDEX,
|
||||||
CONF_SYMBOL,
|
CONF_SYMBOL,
|
||||||
DIRECTIONS,
|
DIRECTIONS,
|
||||||
|
@ -23,7 +25,9 @@ CONF_DROPDOWN_LIST = "dropdown_list"
|
||||||
|
|
||||||
lv_dropdown_t = LvSelect("lv_dropdown_t")
|
lv_dropdown_t = LvSelect("lv_dropdown_t")
|
||||||
lv_dropdown_list_t = LvType("lv_dropdown_list_t")
|
lv_dropdown_list_t = LvType("lv_dropdown_list_t")
|
||||||
dropdown_list_spec = WidgetType(CONF_DROPDOWN_LIST, lv_dropdown_list_t, (CONF_MAIN,))
|
dropdown_list_spec = WidgetType(
|
||||||
|
CONF_DROPDOWN_LIST, lv_dropdown_list_t, (CONF_MAIN, CONF_SELECTED, CONF_SCROLLBAR)
|
||||||
|
)
|
||||||
|
|
||||||
DROPDOWN_BASE_SCHEMA = cv.Schema(
|
DROPDOWN_BASE_SCHEMA = cv.Schema(
|
||||||
{
|
{
|
||||||
|
|
|
@ -20,6 +20,7 @@ from ..defines import (
|
||||||
CONF_END_VALUE,
|
CONF_END_VALUE,
|
||||||
CONF_INDICATOR,
|
CONF_INDICATOR,
|
||||||
CONF_MAIN,
|
CONF_MAIN,
|
||||||
|
CONF_OPA,
|
||||||
CONF_PIVOT_X,
|
CONF_PIVOT_X,
|
||||||
CONF_PIVOT_Y,
|
CONF_PIVOT_Y,
|
||||||
CONF_SRC,
|
CONF_SRC,
|
||||||
|
@ -35,10 +36,11 @@ from ..lv_validation import (
|
||||||
lv_color,
|
lv_color,
|
||||||
lv_float,
|
lv_float,
|
||||||
lv_image,
|
lv_image,
|
||||||
|
opacity,
|
||||||
requires_component,
|
requires_component,
|
||||||
size,
|
size,
|
||||||
)
|
)
|
||||||
from ..lvcode import LocalVariable, lv, lv_assign, lv_expr
|
from ..lvcode import LocalVariable, lv, lv_assign, lv_expr, lv_obj
|
||||||
from ..types import LvType, ObjUpdateAction
|
from ..types import LvType, ObjUpdateAction
|
||||||
from . import Widget, WidgetType, get_widgets
|
from . import Widget, WidgetType, get_widgets
|
||||||
from .arc import CONF_ARC
|
from .arc import CONF_ARC
|
||||||
|
@ -76,6 +78,7 @@ INDICATOR_LINE_SCHEMA = cv.Schema(
|
||||||
cv.Optional(CONF_COLOR, default=0): lv_color,
|
cv.Optional(CONF_COLOR, default=0): lv_color,
|
||||||
cv.Optional(CONF_R_MOD, default=0): size,
|
cv.Optional(CONF_R_MOD, default=0): size,
|
||||||
cv.Optional(CONF_VALUE): lv_float,
|
cv.Optional(CONF_VALUE): lv_float,
|
||||||
|
cv.Optional(CONF_OPA): opacity,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
INDICATOR_IMG_SCHEMA = cv.Schema(
|
INDICATOR_IMG_SCHEMA = cv.Schema(
|
||||||
|
@ -84,6 +87,7 @@ INDICATOR_IMG_SCHEMA = cv.Schema(
|
||||||
cv.Required(CONF_PIVOT_X): pixels,
|
cv.Required(CONF_PIVOT_X): pixels,
|
||||||
cv.Required(CONF_PIVOT_Y): pixels,
|
cv.Required(CONF_PIVOT_Y): pixels,
|
||||||
cv.Optional(CONF_VALUE): lv_float,
|
cv.Optional(CONF_VALUE): lv_float,
|
||||||
|
cv.Optional(CONF_OPA): opacity,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
INDICATOR_ARC_SCHEMA = cv.Schema(
|
INDICATOR_ARC_SCHEMA = cv.Schema(
|
||||||
|
@ -94,6 +98,7 @@ INDICATOR_ARC_SCHEMA = cv.Schema(
|
||||||
cv.Exclusive(CONF_VALUE, CONF_VALUE): lv_float,
|
cv.Exclusive(CONF_VALUE, CONF_VALUE): lv_float,
|
||||||
cv.Exclusive(CONF_START_VALUE, CONF_VALUE): lv_float,
|
cv.Exclusive(CONF_START_VALUE, CONF_VALUE): lv_float,
|
||||||
cv.Optional(CONF_END_VALUE): lv_float,
|
cv.Optional(CONF_END_VALUE): lv_float,
|
||||||
|
cv.Optional(CONF_OPA): opacity,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
INDICATOR_TICKS_SCHEMA = cv.Schema(
|
INDICATOR_TICKS_SCHEMA = cv.Schema(
|
||||||
|
@ -218,9 +223,7 @@ class MeterType(WidgetType):
|
||||||
for indicator in scale_conf.get(CONF_INDICATORS, ()):
|
for indicator in scale_conf.get(CONF_INDICATORS, ()):
|
||||||
(t, v) = next(iter(indicator.items()))
|
(t, v) = next(iter(indicator.items()))
|
||||||
iid = v[CONF_ID]
|
iid = v[CONF_ID]
|
||||||
ivar = cg.new_variable(
|
ivar = cg.Pvariable(iid, cg.nullptr, type_=lv_meter_indicator_t)
|
||||||
iid, cg.nullptr, type_=lv_meter_indicator_t_ptr
|
|
||||||
)
|
|
||||||
# Enable getting the meter to which this belongs.
|
# Enable getting the meter to which this belongs.
|
||||||
wid = Widget.create(iid, var, obj_spec, v)
|
wid = Widget.create(iid, var, obj_spec, v)
|
||||||
wid.obj = ivar
|
wid.obj = ivar
|
||||||
|
@ -268,9 +271,7 @@ class MeterType(WidgetType):
|
||||||
v[CONF_PIVOT_Y],
|
v[CONF_PIVOT_Y],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
start_value = await get_start_value(v)
|
await set_indicator_values(var, ivar, v)
|
||||||
end_value = await get_end_value(v)
|
|
||||||
set_indicator_values(var, ivar, start_value, end_value)
|
|
||||||
|
|
||||||
|
|
||||||
meter_spec = MeterType()
|
meter_spec = MeterType()
|
||||||
|
@ -285,21 +286,22 @@ meter_spec = MeterType()
|
||||||
cv.Exclusive(CONF_VALUE, CONF_VALUE): lv_float,
|
cv.Exclusive(CONF_VALUE, CONF_VALUE): lv_float,
|
||||||
cv.Exclusive(CONF_START_VALUE, CONF_VALUE): lv_float,
|
cv.Exclusive(CONF_START_VALUE, CONF_VALUE): lv_float,
|
||||||
cv.Optional(CONF_END_VALUE): lv_float,
|
cv.Optional(CONF_END_VALUE): lv_float,
|
||||||
|
cv.Optional(CONF_OPA): opacity,
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
async def indicator_update_to_code(config, action_id, template_arg, args):
|
async def indicator_update_to_code(config, action_id, template_arg, args):
|
||||||
widget = await get_widgets(config)
|
widget = await get_widgets(config)
|
||||||
start_value = await get_start_value(config)
|
|
||||||
end_value = await get_end_value(config)
|
|
||||||
|
|
||||||
async def set_value(w: Widget):
|
async def set_value(w: Widget):
|
||||||
set_indicator_values(w.var, w.obj, start_value, end_value)
|
await set_indicator_values(w.var, w.obj, config)
|
||||||
|
|
||||||
return await action_to_code(widget, set_value, action_id, template_arg, args)
|
return await action_to_code(widget, set_value, action_id, template_arg, args)
|
||||||
|
|
||||||
|
|
||||||
def set_indicator_values(meter, indicator, start_value, end_value):
|
async def set_indicator_values(meter, indicator, config):
|
||||||
|
start_value = await get_start_value(config)
|
||||||
|
end_value = await get_end_value(config)
|
||||||
if start_value is not None:
|
if start_value is not None:
|
||||||
if end_value is None:
|
if end_value is None:
|
||||||
lv.meter_set_indicator_value(meter, indicator, start_value)
|
lv.meter_set_indicator_value(meter, indicator, start_value)
|
||||||
|
@ -307,3 +309,6 @@ def set_indicator_values(meter, indicator, start_value, end_value):
|
||||||
lv.meter_set_indicator_start_value(meter, indicator, start_value)
|
lv.meter_set_indicator_start_value(meter, indicator, start_value)
|
||||||
if end_value is not None:
|
if end_value is not None:
|
||||||
lv.meter_set_indicator_end_value(meter, indicator, end_value)
|
lv.meter_set_indicator_end_value(meter, indicator, end_value)
|
||||||
|
if (opa := config.get(CONF_OPA)) is not None:
|
||||||
|
lv_assign(indicator.opa, await opacity.process(opa))
|
||||||
|
lv_obj.invalidate(meter)
|
||||||
|
|
|
@ -1 +1,7 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
|
||||||
CODEOWNERS = ["@esphome/core"]
|
CODEOWNERS = ["@esphome/core"]
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
cg.add_define("USE_MD5")
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include "md5.h"
|
#include "md5.h"
|
||||||
|
#ifdef USE_MD5
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
|
@ -65,3 +66,4 @@ bool MD5Digest::equals_hex(const char *expected) {
|
||||||
|
|
||||||
} // namespace md5
|
} // namespace md5
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
#endif
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "esphome/core/defines.h"
|
#include "esphome/core/defines.h"
|
||||||
|
#ifdef USE_MD5
|
||||||
|
|
||||||
#ifdef USE_ESP_IDF
|
#ifdef USE_ESP_IDF
|
||||||
#include "esp_rom_md5.h"
|
#include "esp_rom_md5.h"
|
||||||
|
@ -66,3 +67,4 @@ class MD5Digest {
|
||||||
|
|
||||||
} // namespace md5
|
} // namespace md5
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
#endif
|
||||||
|
|
0
esphome/components/nau7802/__init__.py
Normal file
0
esphome/components/nau7802/__init__.py
Normal file
323
esphome/components/nau7802/nau7802.cpp
Normal file
323
esphome/components/nau7802/nau7802.cpp
Normal file
|
@ -0,0 +1,323 @@
|
||||||
|
#include "nau7802.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/hal.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace nau7802 {
|
||||||
|
|
||||||
|
static const char *const TAG = "nau7802";
|
||||||
|
|
||||||
|
// Only define what we need
|
||||||
|
|
||||||
|
static const uint8_t READ_BIT = 0x01;
|
||||||
|
|
||||||
|
static const uint8_t PU_CTRL_REG = 0x00;
|
||||||
|
static const uint8_t PU_CTRL_REGISTER_RESET = 0x01;
|
||||||
|
static const uint8_t PU_CTRL_POWERUP_DIGITAL = 0x02;
|
||||||
|
static const uint8_t PU_CTRL_POWERUP_ANALOG = 0x04;
|
||||||
|
static const uint8_t PU_CTRL_POWERUP_READY = 0x08;
|
||||||
|
static const uint8_t PU_CTRL_CYCLE_START = 0x10;
|
||||||
|
static const uint8_t PU_CTRL_CYCLE_READY = 0x20;
|
||||||
|
static const uint8_t PU_CTRL_AVDD_EXTERNAL = 0x80;
|
||||||
|
|
||||||
|
static const uint8_t CTRL1_REG = 0x01;
|
||||||
|
static const uint8_t CTRL1_LDO_SHIFT = 3;
|
||||||
|
static const uint8_t CTRL1_LDO_MASK = (0x7 << CTRL1_LDO_SHIFT);
|
||||||
|
static const uint8_t CTRL1_GAIN_MASK = 0x7;
|
||||||
|
|
||||||
|
static const uint8_t CTRL2_REG = 0x02;
|
||||||
|
static const uint8_t CTRL2_CRS_SHIFT = 4;
|
||||||
|
static const uint8_t CTRL2_CRS_MASK = (0x7 << CTRL2_CRS_SHIFT);
|
||||||
|
static const uint8_t CTRL2_CALS = 0x04;
|
||||||
|
static const uint8_t CTRL2_CAL_ERR = 0x08;
|
||||||
|
static const uint8_t CTRL2_GAIN_CALIBRATION = 0x03;
|
||||||
|
static const uint8_t CTRL2_CONFIG_MASK = 0xF0;
|
||||||
|
|
||||||
|
static const uint8_t OCAL1_B2_REG = 0x03;
|
||||||
|
static const uint8_t GCAL1_B3_REG = 0x06;
|
||||||
|
static const uint8_t GCAL1_FRACTIONAL = 23;
|
||||||
|
|
||||||
|
// only need the first data register for sequential read method
|
||||||
|
static const uint8_t ADCO_B2_REG = 0x12;
|
||||||
|
|
||||||
|
static const uint8_t ADC_REG = 0x15;
|
||||||
|
static const uint8_t ADC_CHPS_DISABLE = 0x30;
|
||||||
|
|
||||||
|
static const uint8_t PGA_REG = 0x1B;
|
||||||
|
static const uint8_t PGA_LDOMODE_ESR = 0x40;
|
||||||
|
|
||||||
|
static const uint8_t POWER_REG = 0x1C;
|
||||||
|
static const uint8_t POWER_PGA_CAP_EN = 0x80;
|
||||||
|
|
||||||
|
static const uint8_t DEVICE_REV = 0x1F;
|
||||||
|
|
||||||
|
void NAU7802Sensor::setup() {
|
||||||
|
i2c::I2CRegister pu_ctrl = this->reg(PU_CTRL_REG);
|
||||||
|
ESP_LOGCONFIG(TAG, "Setting up NAU7802 '%s'...", this->name_.c_str());
|
||||||
|
uint8_t rev;
|
||||||
|
|
||||||
|
if (this->read_register(DEVICE_REV | READ_BIT, &rev, 1)) {
|
||||||
|
ESP_LOGE(TAG, "Failed I2C read during setup()");
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ESP_LOGI(TAG, "Setting up NAU7802 Rev %d", rev);
|
||||||
|
|
||||||
|
// reset
|
||||||
|
pu_ctrl |= PU_CTRL_REGISTER_RESET;
|
||||||
|
delay(10);
|
||||||
|
pu_ctrl &= ~PU_CTRL_REGISTER_RESET;
|
||||||
|
|
||||||
|
// power up digital hw
|
||||||
|
pu_ctrl |= PU_CTRL_POWERUP_DIGITAL;
|
||||||
|
|
||||||
|
delay(1);
|
||||||
|
if (!(pu_ctrl.get() & PU_CTRL_POWERUP_READY)) {
|
||||||
|
ESP_LOGE(TAG, "Failed to reset sensor during setup()");
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t gcal = (uint32_t) (round(this->gain_calibration_ * (1 << GCAL1_FRACTIONAL)));
|
||||||
|
this->write_value_(OCAL1_B2_REG, 3, this->offset_calibration_);
|
||||||
|
this->write_value_(GCAL1_B3_REG, 4, gcal);
|
||||||
|
|
||||||
|
// turn on AFE
|
||||||
|
pu_ctrl |= PU_CTRL_POWERUP_ANALOG;
|
||||||
|
auto f = std::bind(&NAU7802Sensor::complete_setup_, this);
|
||||||
|
this->set_timeout(600, f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NAU7802Sensor::complete_setup_() {
|
||||||
|
i2c::I2CRegister pu_ctrl = this->reg(PU_CTRL_REG);
|
||||||
|
i2c::I2CRegister ctrl1 = this->reg(CTRL1_REG);
|
||||||
|
i2c::I2CRegister ctrl2 = this->reg(CTRL2_REG);
|
||||||
|
pu_ctrl |= PU_CTRL_CYCLE_START;
|
||||||
|
|
||||||
|
// set gain
|
||||||
|
ctrl1 &= ~CTRL1_GAIN_MASK;
|
||||||
|
ctrl1 |= this->gain_;
|
||||||
|
|
||||||
|
// enable internal LDO
|
||||||
|
if (this->ldo_ != NAU7802_LDO_EXTERNAL) {
|
||||||
|
pu_ctrl |= PU_CTRL_AVDD_EXTERNAL;
|
||||||
|
ctrl1 &= ~CTRL1_LDO_MASK;
|
||||||
|
ctrl1 |= this->ldo_ << CTRL1_LDO_SHIFT;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set sps
|
||||||
|
ctrl2 &= ~CTRL2_CRS_MASK;
|
||||||
|
ctrl2 |= this->sps_ << CTRL2_CRS_SHIFT;
|
||||||
|
|
||||||
|
// disable ADC chopper clock
|
||||||
|
i2c::I2CRegister adc_reg = this->reg(ADC_REG);
|
||||||
|
adc_reg |= ADC_CHPS_DISABLE;
|
||||||
|
|
||||||
|
// use low ESR caps
|
||||||
|
i2c::I2CRegister pga_reg = this->reg(PGA_REG);
|
||||||
|
pga_reg &= ~PGA_LDOMODE_ESR;
|
||||||
|
|
||||||
|
// PGA stabilizer cap on output
|
||||||
|
i2c::I2CRegister pwr_reg = this->reg(POWER_REG);
|
||||||
|
pwr_reg |= POWER_PGA_CAP_EN;
|
||||||
|
|
||||||
|
this->setup_complete_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NAU7802Sensor::dump_config() {
|
||||||
|
LOG_SENSOR("", "NAU7802", this);
|
||||||
|
LOG_I2C_DEVICE(this);
|
||||||
|
|
||||||
|
if (this->is_failed()) {
|
||||||
|
ESP_LOGE(TAG, "Communication with NAU7802 failed earlier, during setup");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Note these may differ from the values on the device if calbration has been run
|
||||||
|
ESP_LOGCONFIG(TAG, " Offset Calibration: %s", to_string(this->offset_calibration_).c_str());
|
||||||
|
ESP_LOGCONFIG(TAG, " Gain Calibration: %f", this->gain_calibration_);
|
||||||
|
|
||||||
|
std::string voltage = "unknown";
|
||||||
|
switch (this->ldo_) {
|
||||||
|
case NAU7802_LDO_2V4:
|
||||||
|
voltage = "2.4V";
|
||||||
|
break;
|
||||||
|
case NAU7802_LDO_2V7:
|
||||||
|
voltage = "2.7V";
|
||||||
|
break;
|
||||||
|
case NAU7802_LDO_3V0:
|
||||||
|
voltage = "3.0V";
|
||||||
|
break;
|
||||||
|
case NAU7802_LDO_3V3:
|
||||||
|
voltage = "3.3V";
|
||||||
|
break;
|
||||||
|
case NAU7802_LDO_3V6:
|
||||||
|
voltage = "3.6V";
|
||||||
|
break;
|
||||||
|
case NAU7802_LDO_3V9:
|
||||||
|
voltage = "3.9V";
|
||||||
|
break;
|
||||||
|
case NAU7802_LDO_4V2:
|
||||||
|
voltage = "4.2V";
|
||||||
|
break;
|
||||||
|
case NAU7802_LDO_4V5:
|
||||||
|
voltage = "4.5V";
|
||||||
|
break;
|
||||||
|
case NAU7802_LDO_EXTERNAL:
|
||||||
|
voltage = "External";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ESP_LOGCONFIG(TAG, " LDO Voltage: %s", voltage.c_str());
|
||||||
|
int gain = 0;
|
||||||
|
switch (this->gain_) {
|
||||||
|
case NAU7802_GAIN_128:
|
||||||
|
gain = 128;
|
||||||
|
break;
|
||||||
|
case NAU7802_GAIN_64:
|
||||||
|
gain = 64;
|
||||||
|
break;
|
||||||
|
case NAU7802_GAIN_32:
|
||||||
|
gain = 32;
|
||||||
|
break;
|
||||||
|
case NAU7802_GAIN_16:
|
||||||
|
gain = 16;
|
||||||
|
break;
|
||||||
|
case NAU7802_GAIN_8:
|
||||||
|
gain = 8;
|
||||||
|
break;
|
||||||
|
case NAU7802_GAIN_4:
|
||||||
|
gain = 4;
|
||||||
|
break;
|
||||||
|
case NAU7802_GAIN_2:
|
||||||
|
gain = 2;
|
||||||
|
break;
|
||||||
|
case NAU7802_GAIN_1:
|
||||||
|
gain = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ESP_LOGCONFIG(TAG, " Gain: %dx", gain);
|
||||||
|
int sps = 0;
|
||||||
|
switch (this->sps_) {
|
||||||
|
case NAU7802_SPS_320:
|
||||||
|
sps = 320;
|
||||||
|
break;
|
||||||
|
case NAU7802_SPS_80:
|
||||||
|
sps = 80;
|
||||||
|
break;
|
||||||
|
case NAU7802_SPS_40:
|
||||||
|
sps = 40;
|
||||||
|
break;
|
||||||
|
case NAU7802_SPS_20:
|
||||||
|
sps = 20;
|
||||||
|
break;
|
||||||
|
case NAU7802_SPS_10:
|
||||||
|
sps = 10;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ESP_LOGCONFIG(TAG, " Samples Per Second: %d", sps);
|
||||||
|
LOG_UPDATE_INTERVAL(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NAU7802Sensor::write_value_(uint8_t start_reg, size_t size, int32_t value) {
|
||||||
|
uint8_t data[4];
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
data[i] = 0xFF & (value >> (size - 1 - i) * 8);
|
||||||
|
}
|
||||||
|
this->write_register(start_reg, data, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t NAU7802Sensor::read_value_(uint8_t start_reg, size_t size) {
|
||||||
|
uint8_t data[4];
|
||||||
|
this->read_register(start_reg, data, size);
|
||||||
|
int32_t result = 0;
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
result |= data[i] << (size - 1 - i) * 8;
|
||||||
|
}
|
||||||
|
// extend sign bit
|
||||||
|
if (result & 0x800000 && size == 3) {
|
||||||
|
result |= 0xFF000000;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NAU7802Sensor::calibrate_(enum NAU7802CalibrationModes mode) {
|
||||||
|
// check if already calbrating
|
||||||
|
if (this->state_ != CalibrationState::INACTIVE) {
|
||||||
|
ESP_LOGW(TAG, "Calibration already in progress");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->state_ = mode == NAU7802_CALIBRATE_GAIN ? CalibrationState::GAIN : CalibrationState::OFFSET;
|
||||||
|
|
||||||
|
i2c::I2CRegister ctrl2 = this->reg(CTRL2_REG);
|
||||||
|
// clear calibraye registers
|
||||||
|
ctrl2 &= CTRL2_CONFIG_MASK;
|
||||||
|
// Calibrate
|
||||||
|
ctrl2 |= mode;
|
||||||
|
ctrl2 |= CTRL2_CALS;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NAU7802Sensor::set_calibration_failure_(bool failed) {
|
||||||
|
switch (this->state_) {
|
||||||
|
case CalibrationState::GAIN:
|
||||||
|
this->gain_calibration_failed_ = failed;
|
||||||
|
break;
|
||||||
|
case CalibrationState::OFFSET:
|
||||||
|
this->offset_calibration_failed_ = failed;
|
||||||
|
break;
|
||||||
|
case CalibrationState::INACTIVE:
|
||||||
|
// shouldn't happen
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NAU7802Sensor::loop() {
|
||||||
|
i2c::I2CRegister ctrl2 = this->reg(CTRL2_REG);
|
||||||
|
|
||||||
|
if (this->state_ != CalibrationState::INACTIVE && !(ctrl2.get() & CTRL2_CALS)) {
|
||||||
|
if (ctrl2.get() & CTRL2_CAL_ERR) {
|
||||||
|
this->set_calibration_failure_(true);
|
||||||
|
this->state_ = CalibrationState::INACTIVE;
|
||||||
|
ESP_LOGE(TAG, "Failed to calibrate sensor");
|
||||||
|
this->status_set_error("Calibration Failed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->set_calibration_failure_(false);
|
||||||
|
this->state_ = CalibrationState::INACTIVE;
|
||||||
|
|
||||||
|
if (!this->offset_calibration_failed_ && !this->gain_calibration_failed_)
|
||||||
|
this->status_clear_error();
|
||||||
|
|
||||||
|
int32_t ocal = this->read_value_(OCAL1_B2_REG, 3);
|
||||||
|
ESP_LOGI(TAG, "New Offset: %s", to_string(ocal).c_str());
|
||||||
|
uint32_t gcal = this->read_value_(GCAL1_B3_REG, 4);
|
||||||
|
float gcal_f = ((float) gcal / (float) (1 << GCAL1_FRACTIONAL));
|
||||||
|
ESP_LOGI(TAG, "New Gain: %f", gcal_f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float NAU7802Sensor::get_setup_priority() const { return setup_priority::DATA; }
|
||||||
|
|
||||||
|
void NAU7802Sensor::update() {
|
||||||
|
if (!this->is_data_ready_()) {
|
||||||
|
ESP_LOGW(TAG, "No measurements ready!");
|
||||||
|
this->status_set_warning();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->status_clear_warning();
|
||||||
|
|
||||||
|
// Get the most recent sample to publish
|
||||||
|
int32_t result = this->read_value_(ADCO_B2_REG, 3);
|
||||||
|
|
||||||
|
ESP_LOGD(TAG, "'%s': Got value %" PRId32, this->name_.c_str(), result);
|
||||||
|
this->publish_state(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NAU7802Sensor::is_data_ready_() { return this->reg(PU_CTRL_REG).get() & PU_CTRL_CYCLE_READY; }
|
||||||
|
|
||||||
|
bool NAU7802Sensor::can_proceed() { return this->setup_complete_; }
|
||||||
|
|
||||||
|
} // namespace nau7802
|
||||||
|
} // namespace esphome
|
121
esphome/components/nau7802/nau7802.h
Normal file
121
esphome/components/nau7802/nau7802.h
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/core/automation.h"
|
||||||
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
#include "esphome/components/i2c/i2c.h"
|
||||||
|
|
||||||
|
#include <cinttypes>
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace nau7802 {
|
||||||
|
|
||||||
|
enum NAU7802Gain {
|
||||||
|
NAU7802_GAIN_128 = 0b111,
|
||||||
|
NAU7802_GAIN_64 = 0b110,
|
||||||
|
NAU7802_GAIN_32 = 0b101,
|
||||||
|
NAU7802_GAIN_16 = 0b100,
|
||||||
|
NAU7802_GAIN_8 = 0b011,
|
||||||
|
NAU7802_GAIN_4 = 0b010,
|
||||||
|
NAU7802_GAIN_2 = 0b001,
|
||||||
|
NAU7802_GAIN_1 = 0b000,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum NAU7802SPS {
|
||||||
|
NAU7802_SPS_320 = 0b111,
|
||||||
|
NAU7802_SPS_80 = 0b011,
|
||||||
|
NAU7802_SPS_40 = 0b010,
|
||||||
|
NAU7802_SPS_20 = 0b001,
|
||||||
|
NAU7802_SPS_10 = 0b000,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum NAU7802LDO {
|
||||||
|
NAU7802_LDO_2V4 = 0b111,
|
||||||
|
NAU7802_LDO_2V7 = 0b110,
|
||||||
|
NAU7802_LDO_3V0 = 0b101,
|
||||||
|
NAU7802_LDO_3V3 = 0b100,
|
||||||
|
NAU7802_LDO_3V6 = 0b011,
|
||||||
|
NAU7802_LDO_3V9 = 0b010,
|
||||||
|
NAU7802_LDO_4V2 = 0b001,
|
||||||
|
NAU7802_LDO_4V5 = 0b000,
|
||||||
|
// Never write this to a register
|
||||||
|
NAU7802_LDO_EXTERNAL = 0b1000,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum NAU7802CalibrationModes {
|
||||||
|
NAU7802_CALIBRATE_EXTERNAL_OFFSET = 0b10,
|
||||||
|
NAU7802_CALIBRATE_INTERNAL_OFFSET = 0b00,
|
||||||
|
NAU7802_CALIBRATE_GAIN = 0b11,
|
||||||
|
};
|
||||||
|
|
||||||
|
class NAU7802Sensor : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice {
|
||||||
|
public:
|
||||||
|
void set_samples_per_second(NAU7802SPS sps) { this->sps_ = sps; }
|
||||||
|
void set_ldo_voltage(NAU7802LDO ldo) { this->ldo_ = ldo; }
|
||||||
|
void set_gain(NAU7802Gain gain) { this->gain_ = gain; }
|
||||||
|
void set_gain_calibration(float gain_calibration) { this->gain_calibration_ = gain_calibration; }
|
||||||
|
void set_offset_calibration(int32_t offset_calibration) { this->offset_calibration_ = offset_calibration; }
|
||||||
|
bool calibrate_external_offset() { return this->calibrate_(NAU7802_CALIBRATE_EXTERNAL_OFFSET); }
|
||||||
|
bool calibrate_internal_offset() { return this->calibrate_(NAU7802_CALIBRATE_INTERNAL_OFFSET); }
|
||||||
|
bool calibrate_gain() { return this->calibrate_(NAU7802_CALIBRATE_GAIN); }
|
||||||
|
|
||||||
|
void setup() override;
|
||||||
|
void loop() override;
|
||||||
|
bool can_proceed() override;
|
||||||
|
void dump_config() override;
|
||||||
|
float get_setup_priority() const override;
|
||||||
|
void update() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
//
|
||||||
|
// Internal state
|
||||||
|
//
|
||||||
|
enum class CalibrationState : uint8_t {
|
||||||
|
INACTIVE,
|
||||||
|
OFFSET,
|
||||||
|
GAIN,
|
||||||
|
} state_{CalibrationState::INACTIVE};
|
||||||
|
|
||||||
|
float gain_calibration_;
|
||||||
|
int32_t offset_calibration_;
|
||||||
|
bool offset_calibration_failed_ = false;
|
||||||
|
bool gain_calibration_failed_ = false;
|
||||||
|
bool setup_complete_ = false;
|
||||||
|
|
||||||
|
//
|
||||||
|
// Config values
|
||||||
|
//
|
||||||
|
NAU7802LDO ldo_;
|
||||||
|
NAU7802SPS sps_;
|
||||||
|
NAU7802Gain gain_;
|
||||||
|
|
||||||
|
//
|
||||||
|
// Internal Methods
|
||||||
|
//
|
||||||
|
bool calibrate_(enum NAU7802CalibrationModes mode);
|
||||||
|
void complete_setup_();
|
||||||
|
void write_value_(uint8_t start_reg, size_t size, int32_t value);
|
||||||
|
int32_t read_value_(uint8_t start_reg, size_t size);
|
||||||
|
bool is_data_ready_();
|
||||||
|
void set_calibration_failure_(bool failed);
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename... Ts>
|
||||||
|
class NAU7802CalbrateExternalOffsetAction : public Action<Ts...>, public Parented<NAU7802Sensor> {
|
||||||
|
public:
|
||||||
|
void play(Ts... x) override { this->parent_->calibrate_external_offset(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename... Ts>
|
||||||
|
class NAU7802CalbrateInternalOffsetAction : public Action<Ts...>, public Parented<NAU7802Sensor> {
|
||||||
|
public:
|
||||||
|
void play(Ts... x) override { this->parent_->calibrate_internal_offset(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename... Ts> class NAU7802CalbrateGainAction : public Action<Ts...>, public Parented<NAU7802Sensor> {
|
||||||
|
public:
|
||||||
|
void play(Ts... x) override { this->parent_->calibrate_gain(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace nau7802
|
||||||
|
} // namespace esphome
|
134
esphome/components/nau7802/sensor.py
Normal file
134
esphome/components/nau7802/sensor.py
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
from esphome import automation
|
||||||
|
from esphome.automation import maybe_simple_id
|
||||||
|
import esphome.codegen as cg
|
||||||
|
from esphome.components import i2c, sensor
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.const import CONF_GAIN, CONF_ID, ICON_SCALE, STATE_CLASS_MEASUREMENT
|
||||||
|
|
||||||
|
CODEOWNERS = ["@cujomalainey"]
|
||||||
|
DEPENDENCIES = ["i2c"]
|
||||||
|
|
||||||
|
CONF_GAIN_CALIBRATION = "gain_calibration"
|
||||||
|
CONF_OFFSET_CALIBRATION = "offset_calibration"
|
||||||
|
CONF_LDO_VOLTAGE = "ldo_voltage"
|
||||||
|
CONF_SAMPLES_PER_SECOND = "samples_per_second"
|
||||||
|
|
||||||
|
nau7802_ns = cg.esphome_ns.namespace("nau7802")
|
||||||
|
NAU7802Sensor = nau7802_ns.class_(
|
||||||
|
"NAU7802Sensor", sensor.Sensor, cg.PollingComponent, i2c.I2CDevice
|
||||||
|
)
|
||||||
|
NAU7802CalbrateExternalOffsetAction = nau7802_ns.class_(
|
||||||
|
"NAU7802CalbrateExternalOffsetAction",
|
||||||
|
automation.Action,
|
||||||
|
cg.Parented.template(NAU7802Sensor),
|
||||||
|
)
|
||||||
|
NAU7802CalbrateInternalOffsetAction = nau7802_ns.class_(
|
||||||
|
"NAU7802CalbrateInternalOffsetAction",
|
||||||
|
automation.Action,
|
||||||
|
cg.Parented.template(NAU7802Sensor),
|
||||||
|
)
|
||||||
|
NAU7802CalbrateGainAction = nau7802_ns.class_(
|
||||||
|
"NAU7802CalbrateGainAction", automation.Action, cg.Parented.template(NAU7802Sensor)
|
||||||
|
)
|
||||||
|
|
||||||
|
NAU7802Gain = nau7802_ns.enum("NAU7802Gain")
|
||||||
|
GAINS = {
|
||||||
|
128: NAU7802Gain.NAU7802_GAIN_128,
|
||||||
|
64: NAU7802Gain.NAU7802_GAIN_64,
|
||||||
|
32: NAU7802Gain.NAU7802_GAIN_32,
|
||||||
|
16: NAU7802Gain.NAU7802_GAIN_16,
|
||||||
|
8: NAU7802Gain.NAU7802_GAIN_8,
|
||||||
|
4: NAU7802Gain.NAU7802_GAIN_4,
|
||||||
|
2: NAU7802Gain.NAU7802_GAIN_2,
|
||||||
|
1: NAU7802Gain.NAU7802_GAIN_1,
|
||||||
|
}
|
||||||
|
|
||||||
|
NAU7802SPS = nau7802_ns.enum("NAU7802SPS")
|
||||||
|
SAMPLES_PER_SECOND = {
|
||||||
|
320: NAU7802SPS.NAU7802_SPS_320,
|
||||||
|
80: NAU7802SPS.NAU7802_SPS_80,
|
||||||
|
40: NAU7802SPS.NAU7802_SPS_40,
|
||||||
|
20: NAU7802SPS.NAU7802_SPS_20,
|
||||||
|
10: NAU7802SPS.NAU7802_SPS_10,
|
||||||
|
}
|
||||||
|
|
||||||
|
NAU7802LDO = nau7802_ns.enum("NAU7802LDO")
|
||||||
|
LDO = {
|
||||||
|
"2.4V": NAU7802LDO.NAU7802_LDO_2V4,
|
||||||
|
"2.7V": NAU7802LDO.NAU7802_LDO_2V7,
|
||||||
|
"3.0V": NAU7802LDO.NAU7802_LDO_3V0,
|
||||||
|
"3.3V": NAU7802LDO.NAU7802_LDO_3V3,
|
||||||
|
"3.6V": NAU7802LDO.NAU7802_LDO_3V6,
|
||||||
|
"3.9V": NAU7802LDO.NAU7802_LDO_3V9,
|
||||||
|
"4.2V": NAU7802LDO.NAU7802_LDO_4V2,
|
||||||
|
"4.5V": NAU7802LDO.NAU7802_LDO_4V5,
|
||||||
|
"EXTERNAL": NAU7802LDO.NAU7802_LDO_EXTERNAL,
|
||||||
|
"EXT": NAU7802LDO.NAU7802_LDO_EXTERNAL,
|
||||||
|
}
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = (
|
||||||
|
sensor.sensor_schema(
|
||||||
|
NAU7802Sensor,
|
||||||
|
icon=ICON_SCALE,
|
||||||
|
accuracy_decimals=0,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
)
|
||||||
|
.extend(
|
||||||
|
{
|
||||||
|
cv.Optional(CONF_LDO_VOLTAGE, default="3.0V"): cv.enum(LDO, upper=True),
|
||||||
|
cv.Optional(CONF_SAMPLES_PER_SECOND, default=10): cv.enum(
|
||||||
|
SAMPLES_PER_SECOND, int=True
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_GAIN, default=128): cv.enum(GAINS, int=True),
|
||||||
|
cv.Optional(CONF_OFFSET_CALIBRATION, default=0): cv.int_range(
|
||||||
|
min=-8388608, max=8388607
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_GAIN_CALIBRATION, default=1.0): cv.float_range(
|
||||||
|
min=0, max=511.9999998807907
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(cv.polling_component_schema("60s"))
|
||||||
|
.extend(i2c.i2c_device_schema(0x2A))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
await sensor.register_sensor(var, config)
|
||||||
|
|
||||||
|
cg.add(var.set_samples_per_second(config[CONF_SAMPLES_PER_SECOND]))
|
||||||
|
cg.add(var.set_ldo_voltage(config[CONF_LDO_VOLTAGE]))
|
||||||
|
cg.add(var.set_gain(config[CONF_GAIN]))
|
||||||
|
cg.add(var.set_gain_calibration(config[CONF_GAIN_CALIBRATION]))
|
||||||
|
cg.add(var.set_offset_calibration(config[CONF_OFFSET_CALIBRATION]))
|
||||||
|
|
||||||
|
|
||||||
|
NAU7802_CALIBRATE_SCHEMA = maybe_simple_id(
|
||||||
|
{
|
||||||
|
cv.GenerateID(CONF_ID): cv.use_id(NAU7802Sensor),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@automation.register_action(
|
||||||
|
"nau7802.calibrate_internal_offset",
|
||||||
|
NAU7802CalbrateInternalOffsetAction,
|
||||||
|
NAU7802_CALIBRATE_SCHEMA,
|
||||||
|
)
|
||||||
|
@automation.register_action(
|
||||||
|
"nau7802.calibrate_external_offset",
|
||||||
|
NAU7802CalbrateExternalOffsetAction,
|
||||||
|
NAU7802_CALIBRATE_SCHEMA,
|
||||||
|
)
|
||||||
|
@automation.register_action(
|
||||||
|
"nau7802.calibrate_gain",
|
||||||
|
NAU7802CalbrateGainAction,
|
||||||
|
NAU7802_CALIBRATE_SCHEMA,
|
||||||
|
)
|
||||||
|
async def nau7802_calibrate_to_code(config, action_id, template_arg, args):
|
||||||
|
var = cg.new_Pvariable(action_id, template_arg)
|
||||||
|
await cg.register_parented(var, config[CONF_ID])
|
||||||
|
return var
|
0
esphome/components/npi19/__init__.py
Normal file
0
esphome/components/npi19/__init__.py
Normal file
111
esphome/components/npi19/npi19.cpp
Normal file
111
esphome/components/npi19/npi19.cpp
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
#include "npi19.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
#include "esphome/core/hal.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace npi19 {
|
||||||
|
|
||||||
|
static const char *const TAG = "npi19";
|
||||||
|
|
||||||
|
static const uint8_t READ_COMMAND = 0xAC;
|
||||||
|
|
||||||
|
void NPI19Component::setup() {
|
||||||
|
ESP_LOGCONFIG(TAG, "Setting up NPI19...");
|
||||||
|
|
||||||
|
uint16_t raw_temperature(0);
|
||||||
|
uint16_t raw_pressure(0);
|
||||||
|
i2c::ErrorCode err = this->read_(raw_temperature, raw_pressure);
|
||||||
|
if (err != i2c::ERROR_OK) {
|
||||||
|
ESP_LOGCONFIG(TAG, " I2C Communication Failed...");
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGCONFIG(TAG, " Success...");
|
||||||
|
}
|
||||||
|
|
||||||
|
void NPI19Component::dump_config() {
|
||||||
|
ESP_LOGCONFIG(TAG, "NPI19:");
|
||||||
|
LOG_I2C_DEVICE(this);
|
||||||
|
LOG_UPDATE_INTERVAL(this);
|
||||||
|
LOG_SENSOR(" ", "Raw Pressure", this->raw_pressure_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
|
||||||
|
}
|
||||||
|
|
||||||
|
float NPI19Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||||
|
|
||||||
|
i2c::ErrorCode NPI19Component::read_(uint16_t &raw_temperature, uint16_t &raw_pressure) {
|
||||||
|
// initiate data read from device
|
||||||
|
i2c::ErrorCode w_err = write(&READ_COMMAND, sizeof(READ_COMMAND), true);
|
||||||
|
if (w_err != i2c::ERROR_OK) {
|
||||||
|
return w_err;
|
||||||
|
}
|
||||||
|
|
||||||
|
// read 4 bytes from senesor
|
||||||
|
uint8_t response[4] = {0x00, 0x00, 0x00, 0x00};
|
||||||
|
i2c::ErrorCode r_err = this->read(response, 4);
|
||||||
|
|
||||||
|
if (r_err != i2c::ERROR_OK) {
|
||||||
|
return r_err;
|
||||||
|
}
|
||||||
|
|
||||||
|
// extract top 6 bits of first byte and all bits of second byte for pressure
|
||||||
|
raw_pressure = ((response[0] & 0x3F) << 8) | response[1];
|
||||||
|
|
||||||
|
// extract all bytes of 3rd byte and top 3 bits of fourth byte for temperature
|
||||||
|
raw_temperature = (response[2] << 3) | ((response[3] & 0xE0) >> 5);
|
||||||
|
|
||||||
|
return i2c::ERROR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float convert_temperature(uint16_t raw_temperature) {
|
||||||
|
/*
|
||||||
|
* Correspondance with Amphenol confirmed the appropriate equation for computing temperature is:
|
||||||
|
* T (°C) =(((((Th*8)+Tl)/2048)*200)-50), where Th is the high (third) byte and Tl is the low (fourth) byte.
|
||||||
|
*
|
||||||
|
* Tl is actually the upper 3 bits of the fourth data byte; the first 5 (LSBs) must be masked out.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* The NPI-19 I2C has a temperature output, however the manufacturer does
|
||||||
|
* not specify its accuracy on the published datasheet. They indicate
|
||||||
|
* that the sensor should not be used as a calibrated temperature
|
||||||
|
* reading; it’s only intended for curve fitting data during
|
||||||
|
* compensation.
|
||||||
|
*/
|
||||||
|
const float temperature_bits_span = 2048;
|
||||||
|
const float temperature_max = 150;
|
||||||
|
const float temperature_min = -50;
|
||||||
|
const float temperature_span = temperature_max - temperature_min;
|
||||||
|
|
||||||
|
float temperature = (raw_temperature * temperature_span / temperature_bits_span) + temperature_min;
|
||||||
|
|
||||||
|
return temperature;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NPI19Component::update() {
|
||||||
|
uint16_t raw_temperature(0);
|
||||||
|
uint16_t raw_pressure(0);
|
||||||
|
|
||||||
|
i2c::ErrorCode err = this->read_(raw_temperature, raw_pressure);
|
||||||
|
|
||||||
|
if (err != i2c::ERROR_OK) {
|
||||||
|
ESP_LOGW(TAG, "I2C Communication Failed");
|
||||||
|
this->status_set_warning();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
float temperature = convert_temperature(raw_temperature);
|
||||||
|
|
||||||
|
ESP_LOGD(TAG, "Got raw pressure=%d, temperature=%.1f°C", raw_pressure, temperature);
|
||||||
|
|
||||||
|
if (this->temperature_sensor_ != nullptr)
|
||||||
|
this->temperature_sensor_->publish_state(temperature);
|
||||||
|
if (this->raw_pressure_sensor_ != nullptr)
|
||||||
|
this->raw_pressure_sensor_->publish_state(raw_pressure);
|
||||||
|
|
||||||
|
this->status_clear_warning();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace npi19
|
||||||
|
} // namespace esphome
|
30
esphome/components/npi19/npi19.h
Normal file
30
esphome/components/npi19/npi19.h
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
#include "esphome/components/i2c/i2c.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace npi19 {
|
||||||
|
|
||||||
|
/// This class implements support for the npi19 pressure and temperature i2c sensors.
|
||||||
|
class NPI19Component : public PollingComponent, public i2c::I2CDevice {
|
||||||
|
public:
|
||||||
|
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { this->temperature_sensor_ = temperature_sensor; }
|
||||||
|
void set_raw_pressure_sensor(sensor::Sensor *raw_pressure_sensor) {
|
||||||
|
this->raw_pressure_sensor_ = raw_pressure_sensor;
|
||||||
|
}
|
||||||
|
|
||||||
|
float get_setup_priority() const override;
|
||||||
|
void setup() override;
|
||||||
|
void dump_config() override;
|
||||||
|
void update() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
i2c::ErrorCode read_(uint16_t &raw_temperature, uint16_t &raw_pressure);
|
||||||
|
sensor::Sensor *temperature_sensor_{nullptr};
|
||||||
|
sensor::Sensor *raw_pressure_sensor_{nullptr};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace npi19
|
||||||
|
} // namespace esphome
|
52
esphome/components/npi19/sensor.py
Normal file
52
esphome/components/npi19/sensor.py
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
from esphome.components import i2c, sensor
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_ID,
|
||||||
|
CONF_TEMPERATURE,
|
||||||
|
DEVICE_CLASS_TEMPERATURE,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
UNIT_CELSIUS,
|
||||||
|
)
|
||||||
|
|
||||||
|
CODEOWNERS = ["@bakerkj"]
|
||||||
|
DEPENDENCIES = ["i2c"]
|
||||||
|
|
||||||
|
npi19_ns = cg.esphome_ns.namespace("npi19")
|
||||||
|
|
||||||
|
NPI19Component = npi19_ns.class_("NPI19Component", cg.PollingComponent, i2c.I2CDevice)
|
||||||
|
|
||||||
|
CONF_RAW_PRESSURE = "raw_pressure"
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = (
|
||||||
|
cv.Schema(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(NPI19Component),
|
||||||
|
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_RAW_PRESSURE): sensor.sensor_schema(
|
||||||
|
accuracy_decimals=0, state_class=STATE_CLASS_MEASUREMENT
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(cv.polling_component_schema("60s"))
|
||||||
|
.extend(i2c.i2c_device_schema(0x28))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
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 raw_pressure_config := config.get(CONF_RAW_PRESSURE):
|
||||||
|
sens = await sensor.new_sensor(raw_pressure_config)
|
||||||
|
cg.add(var.set_raw_pressure_sensor(sens))
|
|
@ -18,7 +18,7 @@ from esphome.const import (
|
||||||
CONF_TRIGGER_ID,
|
CONF_TRIGGER_ID,
|
||||||
CONF_UNIT_OF_MEASUREMENT,
|
CONF_UNIT_OF_MEASUREMENT,
|
||||||
CONF_VALUE,
|
CONF_VALUE,
|
||||||
CONF_WEB_SERVER_ID,
|
CONF_WEB_SERVER,
|
||||||
DEVICE_CLASS_APPARENT_POWER,
|
DEVICE_CLASS_APPARENT_POWER,
|
||||||
DEVICE_CLASS_AQI,
|
DEVICE_CLASS_AQI,
|
||||||
DEVICE_CLASS_ATMOSPHERIC_PRESSURE,
|
DEVICE_CLASS_ATMOSPHERIC_PRESSURE,
|
||||||
|
@ -254,10 +254,8 @@ async def setup_number_core_(
|
||||||
if (mqtt_id := config.get(CONF_MQTT_ID)) is not None:
|
if (mqtt_id := config.get(CONF_MQTT_ID)) is not None:
|
||||||
mqtt_ = cg.new_Pvariable(mqtt_id, var)
|
mqtt_ = cg.new_Pvariable(mqtt_id, var)
|
||||||
await mqtt.register_mqtt_component(mqtt_, config)
|
await mqtt.register_mqtt_component(mqtt_, config)
|
||||||
|
if web_server_config := config.get(CONF_WEB_SERVER):
|
||||||
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
|
await web_server.add_entity_config(var, web_server_config)
|
||||||
web_server_ = await cg.get_variable(webserver_id)
|
|
||||||
web_server.add_entity_to_sorting_list(web_server_, var, config)
|
|
||||||
|
|
||||||
|
|
||||||
async def register_number(
|
async def register_number(
|
||||||
|
|
|
@ -125,7 +125,7 @@ async def online_image_action_to_code(config, action_id, template_arg, args):
|
||||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||||
|
|
||||||
if CONF_URL in config:
|
if CONF_URL in config:
|
||||||
template_ = await cg.templatable(config[CONF_URL], args, cg.const_char_ptr)
|
template_ = await cg.templatable(config[CONF_URL], args, cg.std_string)
|
||||||
cg.add(var.set_url(template_))
|
cg.add(var.set_url(template_))
|
||||||
return var
|
return var
|
||||||
|
|
||||||
|
|
|
@ -157,7 +157,7 @@ class OnlineImage : public PollingComponent,
|
||||||
template<typename... Ts> class OnlineImageSetUrlAction : public Action<Ts...> {
|
template<typename... Ts> class OnlineImageSetUrlAction : public Action<Ts...> {
|
||||||
public:
|
public:
|
||||||
OnlineImageSetUrlAction(OnlineImage *parent) : parent_(parent) {}
|
OnlineImageSetUrlAction(OnlineImage *parent) : parent_(parent) {}
|
||||||
TEMPLATABLE_VALUE(const char *, url)
|
TEMPLATABLE_VALUE(std::string, url)
|
||||||
void play(Ts... x) override {
|
void play(Ts... x) override {
|
||||||
this->parent_->set_url(this->url_.value(x...));
|
this->parent_->set_url(this->url_.value(x...));
|
||||||
this->parent_->update();
|
this->parent_->update();
|
||||||
|
@ -170,7 +170,6 @@ template<typename... Ts> class OnlineImageSetUrlAction : public Action<Ts...> {
|
||||||
template<typename... Ts> class OnlineImageReleaseAction : public Action<Ts...> {
|
template<typename... Ts> class OnlineImageReleaseAction : public Action<Ts...> {
|
||||||
public:
|
public:
|
||||||
OnlineImageReleaseAction(OnlineImage *parent) : parent_(parent) {}
|
OnlineImageReleaseAction(OnlineImage *parent) : parent_(parent) {}
|
||||||
TEMPLATABLE_VALUE(const char *, url)
|
|
||||||
void play(Ts... x) override { this->parent_->release(); }
|
void play(Ts... x) override { this->parent_->release(); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
|
@ -53,12 +53,19 @@ pulse_counter_t BasicPulseCounterStorage::read_raw_value() {
|
||||||
#ifdef HAS_PCNT
|
#ifdef HAS_PCNT
|
||||||
bool HwPulseCounterStorage::pulse_counter_setup(InternalGPIOPin *pin) {
|
bool HwPulseCounterStorage::pulse_counter_setup(InternalGPIOPin *pin) {
|
||||||
static pcnt_unit_t next_pcnt_unit = PCNT_UNIT_0;
|
static pcnt_unit_t next_pcnt_unit = PCNT_UNIT_0;
|
||||||
|
static pcnt_channel_t next_pcnt_channel = PCNT_CHANNEL_0;
|
||||||
this->pin = pin;
|
this->pin = pin;
|
||||||
this->pin->setup();
|
this->pin->setup();
|
||||||
this->pcnt_unit = next_pcnt_unit;
|
this->pcnt_unit = next_pcnt_unit;
|
||||||
|
this->pcnt_channel = next_pcnt_channel;
|
||||||
next_pcnt_unit = pcnt_unit_t(int(next_pcnt_unit) + 1);
|
next_pcnt_unit = pcnt_unit_t(int(next_pcnt_unit) + 1);
|
||||||
|
if (int(next_pcnt_unit) >= PCNT_UNIT_0 + PCNT_UNIT_MAX) {
|
||||||
|
next_pcnt_unit = PCNT_UNIT_0;
|
||||||
|
next_pcnt_channel = pcnt_channel_t(int(next_pcnt_channel) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
ESP_LOGCONFIG(TAG, " PCNT Unit Number: %u", this->pcnt_unit);
|
ESP_LOGCONFIG(TAG, " PCNT Unit Number: %u", this->pcnt_unit);
|
||||||
|
ESP_LOGCONFIG(TAG, " PCNT Channel Number: %u", this->pcnt_channel);
|
||||||
|
|
||||||
pcnt_count_mode_t rising = PCNT_COUNT_DIS, falling = PCNT_COUNT_DIS;
|
pcnt_count_mode_t rising = PCNT_COUNT_DIS, falling = PCNT_COUNT_DIS;
|
||||||
switch (this->rising_edge_mode) {
|
switch (this->rising_edge_mode) {
|
||||||
|
@ -94,7 +101,7 @@ bool HwPulseCounterStorage::pulse_counter_setup(InternalGPIOPin *pin) {
|
||||||
.counter_h_lim = 0,
|
.counter_h_lim = 0,
|
||||||
.counter_l_lim = 0,
|
.counter_l_lim = 0,
|
||||||
.unit = this->pcnt_unit,
|
.unit = this->pcnt_unit,
|
||||||
.channel = PCNT_CHANNEL_0,
|
.channel = this->pcnt_channel,
|
||||||
};
|
};
|
||||||
esp_err_t error = pcnt_unit_config(&pcnt_config);
|
esp_err_t error = pcnt_unit_config(&pcnt_config);
|
||||||
if (error != ESP_OK) {
|
if (error != ESP_OK) {
|
||||||
|
|
|
@ -55,6 +55,7 @@ struct HwPulseCounterStorage : public PulseCounterStorageBase {
|
||||||
pulse_counter_t read_raw_value() override;
|
pulse_counter_t read_raw_value() override;
|
||||||
|
|
||||||
pcnt_unit_t pcnt_unit;
|
pcnt_unit_t pcnt_unit;
|
||||||
|
pcnt_channel_t pcnt_channel;
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
#include "radon_eye_listener.h"
|
#include "radon_eye_listener.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
#include <algorithm>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#ifdef USE_ESP32
|
#ifdef USE_ESP32
|
||||||
|
|
||||||
|
@ -10,9 +12,14 @@ static const char *const TAG = "radon_eye_ble";
|
||||||
|
|
||||||
bool RadonEyeListener::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
|
bool RadonEyeListener::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
|
||||||
if (not device.get_name().empty()) {
|
if (not device.get_name().empty()) {
|
||||||
if (device.get_name().rfind("FR:R", 0) == 0) {
|
// Vector containing the prefixes to search for
|
||||||
// This is an RD200, I think
|
std::vector<std::string> prefixes = {"FR:R", "FR:I", "FR:H"};
|
||||||
ESP_LOGD(TAG, "Found Radon Eye RD200 device Name: %s (MAC: %s)", device.get_name().c_str(),
|
|
||||||
|
// Check if the device name starts with any of the prefixes
|
||||||
|
if (std::any_of(prefixes.begin(), prefixes.end(),
|
||||||
|
[&](const std::string &prefix) { return device.get_name().rfind(prefix, 0) == 0; })) {
|
||||||
|
// Device found
|
||||||
|
ESP_LOGD(TAG, "Found Radon Eye device Name: %s (MAC: %s)", device.get_name().c_str(),
|
||||||
device.address_str().c_str());
|
device.address_str().c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ from esphome.const import (
|
||||||
CONF_OPERATION,
|
CONF_OPERATION,
|
||||||
CONF_OPTION,
|
CONF_OPTION,
|
||||||
CONF_TRIGGER_ID,
|
CONF_TRIGGER_ID,
|
||||||
CONF_WEB_SERVER_ID,
|
CONF_WEB_SERVER,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, coroutine_with_priority
|
from esphome.core import CORE, coroutine_with_priority
|
||||||
from esphome.cpp_generator import MockObjClass
|
from esphome.cpp_generator import MockObjClass
|
||||||
|
@ -104,9 +104,8 @@ async def setup_select_core_(var, config, *, options: list[str]):
|
||||||
mqtt_ = cg.new_Pvariable(mqtt_id, var)
|
mqtt_ = cg.new_Pvariable(mqtt_id, var)
|
||||||
await mqtt.register_mqtt_component(mqtt_, config)
|
await mqtt.register_mqtt_component(mqtt_, config)
|
||||||
|
|
||||||
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
|
if web_server_config := config.get(CONF_WEB_SERVER):
|
||||||
web_server_ = await cg.get_variable(webserver_id)
|
await web_server.add_entity_config(var, web_server_config)
|
||||||
web_server.add_entity_to_sorting_list(web_server_, var, config)
|
|
||||||
|
|
||||||
|
|
||||||
async def register_select(var, config, *, options: list[str]):
|
async def register_select(var, config, *, options: list[str]):
|
||||||
|
|
|
@ -36,7 +36,7 @@ from esphome.const import (
|
||||||
CONF_TYPE,
|
CONF_TYPE,
|
||||||
CONF_UNIT_OF_MEASUREMENT,
|
CONF_UNIT_OF_MEASUREMENT,
|
||||||
CONF_VALUE,
|
CONF_VALUE,
|
||||||
CONF_WEB_SERVER_ID,
|
CONF_WEB_SERVER,
|
||||||
CONF_WINDOW_SIZE,
|
CONF_WINDOW_SIZE,
|
||||||
DEVICE_CLASS_APPARENT_POWER,
|
DEVICE_CLASS_APPARENT_POWER,
|
||||||
DEVICE_CLASS_AQI,
|
DEVICE_CLASS_AQI,
|
||||||
|
@ -800,9 +800,8 @@ async def setup_sensor_core_(var, config):
|
||||||
else:
|
else:
|
||||||
cg.add(mqtt_.set_expire_after(expire_after))
|
cg.add(mqtt_.set_expire_after(expire_after))
|
||||||
|
|
||||||
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
|
if web_server_config := config.get(CONF_WEB_SERVER):
|
||||||
web_server_ = await cg.get_variable(webserver_id)
|
await web_server.add_entity_config(var, web_server_config)
|
||||||
web_server.add_entity_to_sorting_list(web_server_, var, config)
|
|
||||||
|
|
||||||
|
|
||||||
async def register_sensor(var, config):
|
async def register_sensor(var, config):
|
||||||
|
|
|
@ -14,7 +14,7 @@ from esphome.const import (
|
||||||
CONF_ON_TURN_ON,
|
CONF_ON_TURN_ON,
|
||||||
CONF_RESTORE_MODE,
|
CONF_RESTORE_MODE,
|
||||||
CONF_TRIGGER_ID,
|
CONF_TRIGGER_ID,
|
||||||
CONF_WEB_SERVER_ID,
|
CONF_WEB_SERVER,
|
||||||
DEVICE_CLASS_EMPTY,
|
DEVICE_CLASS_EMPTY,
|
||||||
DEVICE_CLASS_OUTLET,
|
DEVICE_CLASS_OUTLET,
|
||||||
DEVICE_CLASS_SWITCH,
|
DEVICE_CLASS_SWITCH,
|
||||||
|
@ -156,9 +156,8 @@ async def setup_switch_core_(var, config):
|
||||||
mqtt_ = cg.new_Pvariable(mqtt_id, var)
|
mqtt_ = cg.new_Pvariable(mqtt_id, var)
|
||||||
await mqtt.register_mqtt_component(mqtt_, config)
|
await mqtt.register_mqtt_component(mqtt_, config)
|
||||||
|
|
||||||
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
|
if web_server_config := config.get(CONF_WEB_SERVER):
|
||||||
web_server_ = await cg.get_variable(webserver_id)
|
await web_server.add_entity_config(var, web_server_config)
|
||||||
web_server.add_entity_to_sorting_list(web_server_, var, config)
|
|
||||||
|
|
||||||
if (device_class := config.get(CONF_DEVICE_CLASS)) is not None:
|
if (device_class := config.get(CONF_DEVICE_CLASS)) is not None:
|
||||||
cg.add(var.set_device_class(device_class))
|
cg.add(var.set_device_class(device_class))
|
||||||
|
|
0
esphome/components/tem3200/__init__.py
Normal file
0
esphome/components/tem3200/__init__.py
Normal file
55
esphome/components/tem3200/sensor.py
Normal file
55
esphome/components/tem3200/sensor.py
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import i2c, sensor
|
||||||
|
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_ID,
|
||||||
|
CONF_TEMPERATURE,
|
||||||
|
DEVICE_CLASS_TEMPERATURE,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
UNIT_CELSIUS,
|
||||||
|
)
|
||||||
|
|
||||||
|
CODEOWNERS = ["@bakerkj"]
|
||||||
|
DEPENDENCIES = ["i2c"]
|
||||||
|
|
||||||
|
tem3200_ns = cg.esphome_ns.namespace("tem3200")
|
||||||
|
|
||||||
|
TEM3200Component = tem3200_ns.class_(
|
||||||
|
"TEM3200Component", cg.PollingComponent, i2c.I2CDevice
|
||||||
|
)
|
||||||
|
|
||||||
|
CONF_RAW_PRESSURE = "raw_pressure"
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = (
|
||||||
|
cv.Schema(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(TEM3200Component),
|
||||||
|
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_RAW_PRESSURE): sensor.sensor_schema(
|
||||||
|
accuracy_decimals=0, state_class=STATE_CLASS_MEASUREMENT
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(cv.polling_component_schema("60s"))
|
||||||
|
.extend(i2c.i2c_device_schema(0x28))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
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 raw_pressure_config := config.get(CONF_RAW_PRESSURE):
|
||||||
|
sens = await sensor.new_sensor(raw_pressure_config)
|
||||||
|
cg.add(var.set_raw_pressure_sensor(sens))
|
151
esphome/components/tem3200/tem3200.cpp
Normal file
151
esphome/components/tem3200/tem3200.cpp
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
#include "tem3200.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
#include "esphome/core/hal.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace tem3200 {
|
||||||
|
|
||||||
|
static const char *const TAG = "tem3200";
|
||||||
|
|
||||||
|
enum ErrorCode {
|
||||||
|
NONE = 0,
|
||||||
|
RESERVED = 1,
|
||||||
|
STALE = 2,
|
||||||
|
FAULT = 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
void TEM3200Component::setup() {
|
||||||
|
ESP_LOGCONFIG(TAG, "Setting up TEM3200...");
|
||||||
|
|
||||||
|
uint8_t status(NONE);
|
||||||
|
uint16_t raw_temperature(0);
|
||||||
|
uint16_t raw_pressure(0);
|
||||||
|
|
||||||
|
i2c::ErrorCode err = this->read_(status, raw_temperature, raw_pressure);
|
||||||
|
if (err != i2c::ERROR_OK) {
|
||||||
|
ESP_LOGCONFIG(TAG, " I2C Communication Failed...");
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (status) {
|
||||||
|
case RESERVED:
|
||||||
|
ESP_LOGE(TAG, "Invalid RESERVED Device Status");
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
case FAULT:
|
||||||
|
ESP_LOGE(TAG, "FAULT condition in the SSC or sensing element");
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
case STALE:
|
||||||
|
ESP_LOGE(TAG, "STALE data. Data has not been updated since last fetch");
|
||||||
|
this->status_set_warning();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ESP_LOGCONFIG(TAG, " Success...");
|
||||||
|
}
|
||||||
|
|
||||||
|
void TEM3200Component::dump_config() {
|
||||||
|
ESP_LOGCONFIG(TAG, "TEM3200:");
|
||||||
|
LOG_I2C_DEVICE(this);
|
||||||
|
LOG_UPDATE_INTERVAL(this);
|
||||||
|
LOG_SENSOR(" ", "Raw Pressure", this->raw_pressure_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
|
||||||
|
}
|
||||||
|
|
||||||
|
float TEM3200Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||||
|
|
||||||
|
i2c::ErrorCode TEM3200Component::read_(uint8_t &status, uint16_t &raw_temperature, uint16_t &raw_pressure) {
|
||||||
|
uint8_t response[4] = {0x00, 0x00, 0x00, 0x00};
|
||||||
|
|
||||||
|
// initiate data read
|
||||||
|
i2c::ErrorCode err = this->read(response, 4);
|
||||||
|
if (err != i2c::ERROR_OK) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
// extract top 2 bits of first byte for status
|
||||||
|
status = (ErrorCode) (response[0] & 0xc0) >> 6;
|
||||||
|
if (status == RESERVED || status == FAULT) {
|
||||||
|
return i2c::ERROR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if data is stale; reread
|
||||||
|
if (status == STALE) {
|
||||||
|
// wait for measurement 2ms
|
||||||
|
delay(2);
|
||||||
|
|
||||||
|
err = this->read(response, 4);
|
||||||
|
if (err != i2c::ERROR_OK) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// extract top 2 bits of first byte for status
|
||||||
|
status = (ErrorCode) (response[0] & 0xc0) >> 6;
|
||||||
|
if (status == RESERVED || status == FAULT) {
|
||||||
|
return i2c::ERROR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// extract top 6 bits of first byte and all bits of second byte for pressure
|
||||||
|
raw_pressure = (((response[0] & 0x3f)) << 8 | response[1]);
|
||||||
|
|
||||||
|
// extract all bytes of 3rd byte and top 3 bits of fourth byte for temperature
|
||||||
|
raw_temperature = ((response[2] << 3) | (response[3] & 0xe0) >> 5);
|
||||||
|
|
||||||
|
return i2c::ERROR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float convert_temperature(uint16_t raw_temperature) {
|
||||||
|
const float temperature_bits_span = 2048;
|
||||||
|
const float temperature_max = 150;
|
||||||
|
const float temperature_min = -50;
|
||||||
|
const float temperature_span = temperature_max - temperature_min;
|
||||||
|
|
||||||
|
float temperature = (raw_temperature * temperature_span / temperature_bits_span) + temperature_min;
|
||||||
|
|
||||||
|
return temperature;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TEM3200Component::update() {
|
||||||
|
uint8_t status(NONE);
|
||||||
|
uint16_t raw_temperature(0);
|
||||||
|
uint16_t raw_pressure(0);
|
||||||
|
i2c::ErrorCode err = this->read_(status, raw_temperature, raw_pressure);
|
||||||
|
|
||||||
|
if (err != i2c::ERROR_OK) {
|
||||||
|
ESP_LOGW(TAG, "I2C Communication Failed");
|
||||||
|
this->status_set_warning();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (status) {
|
||||||
|
case RESERVED:
|
||||||
|
ESP_LOGE(TAG, "Failed: Device return RESERVED status");
|
||||||
|
this->status_set_warning();
|
||||||
|
return;
|
||||||
|
case FAULT:
|
||||||
|
ESP_LOGE(TAG, "Failed: FAULT condition in the SSC or sensing element");
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
case STALE:
|
||||||
|
ESP_LOGE(TAG, "Warning: STALE data. Data has not been updated since last fetch");
|
||||||
|
this->status_set_warning();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
float temperature = convert_temperature(raw_temperature);
|
||||||
|
|
||||||
|
ESP_LOGD(TAG, "Got raw pressure=%d, temperature=%.1f°C", raw_pressure, temperature);
|
||||||
|
|
||||||
|
if (this->temperature_sensor_ != nullptr)
|
||||||
|
this->temperature_sensor_->publish_state(temperature);
|
||||||
|
if (this->raw_pressure_sensor_ != nullptr)
|
||||||
|
this->raw_pressure_sensor_->publish_state(raw_pressure);
|
||||||
|
|
||||||
|
this->status_clear_warning();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace tem3200
|
||||||
|
} // namespace esphome
|
30
esphome/components/tem3200/tem3200.h
Normal file
30
esphome/components/tem3200/tem3200.h
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
#include "esphome/components/i2c/i2c.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace tem3200 {
|
||||||
|
|
||||||
|
/// This class implements support for the tem3200 pressure and temperature i2c sensors.
|
||||||
|
class TEM3200Component : public PollingComponent, public i2c::I2CDevice {
|
||||||
|
public:
|
||||||
|
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { this->temperature_sensor_ = temperature_sensor; }
|
||||||
|
void set_raw_pressure_sensor(sensor::Sensor *raw_pressure_sensor) {
|
||||||
|
this->raw_pressure_sensor_ = raw_pressure_sensor;
|
||||||
|
}
|
||||||
|
|
||||||
|
float get_setup_priority() const override;
|
||||||
|
void setup() override;
|
||||||
|
void dump_config() override;
|
||||||
|
void update() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
i2c::ErrorCode read_(uint8_t &status, uint16_t &raw_temperature, uint16_t &raw_pressure);
|
||||||
|
sensor::Sensor *temperature_sensor_{nullptr};
|
||||||
|
sensor::Sensor *raw_pressure_sensor_{nullptr};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace tem3200
|
||||||
|
} // namespace esphome
|
|
@ -1,8 +1,10 @@
|
||||||
import esphome.codegen as cg
|
|
||||||
import esphome.config_validation as cv
|
|
||||||
from esphome import automation
|
from esphome import automation
|
||||||
|
import esphome.codegen as cg
|
||||||
from esphome.components import binary_sensor
|
from esphome.components import binary_sensor
|
||||||
from esphome.const import CONF_ID, CONF_LAMBDA, CONF_STATE
|
import esphome.config_validation as cv
|
||||||
|
from esphome.const import CONF_CONDITION, CONF_ID, CONF_LAMBDA, CONF_STATE
|
||||||
|
from esphome.cpp_generator import LambdaExpression
|
||||||
|
|
||||||
from .. import template_ns
|
from .. import template_ns
|
||||||
|
|
||||||
TemplateBinarySensor = template_ns.class_(
|
TemplateBinarySensor = template_ns.class_(
|
||||||
|
@ -13,7 +15,10 @@ CONFIG_SCHEMA = (
|
||||||
binary_sensor.binary_sensor_schema(TemplateBinarySensor)
|
binary_sensor.binary_sensor_schema(TemplateBinarySensor)
|
||||||
.extend(
|
.extend(
|
||||||
{
|
{
|
||||||
cv.Optional(CONF_LAMBDA): cv.returning_lambda,
|
cv.Exclusive(CONF_LAMBDA, CONF_CONDITION): cv.returning_lambda,
|
||||||
|
cv.Exclusive(
|
||||||
|
CONF_CONDITION, CONF_CONDITION
|
||||||
|
): automation.validate_potentially_and_condition,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.extend(cv.COMPONENT_SCHEMA)
|
.extend(cv.COMPONENT_SCHEMA)
|
||||||
|
@ -24,9 +29,17 @@ async def to_code(config):
|
||||||
var = await binary_sensor.new_binary_sensor(config)
|
var = await binary_sensor.new_binary_sensor(config)
|
||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
|
|
||||||
if CONF_LAMBDA in config:
|
if lamb := config.get(CONF_LAMBDA):
|
||||||
template_ = await cg.process_lambda(
|
template_ = await cg.process_lambda(
|
||||||
config[CONF_LAMBDA], [], return_type=cg.optional.template(bool)
|
lamb, [], return_type=cg.optional.template(bool)
|
||||||
|
)
|
||||||
|
cg.add(var.set_template(template_))
|
||||||
|
if condition := config.get(CONF_CONDITION):
|
||||||
|
condition = await automation.build_condition(
|
||||||
|
condition, cg.TemplateArguments(), []
|
||||||
|
)
|
||||||
|
template_ = LambdaExpression(
|
||||||
|
f"return {condition.check()};", [], return_type=cg.optional.template(bool)
|
||||||
)
|
)
|
||||||
cg.add(var.set_template(template_))
|
cg.add(var.set_template(template_))
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ from esphome.const import (
|
||||||
CONF_ON_VALUE,
|
CONF_ON_VALUE,
|
||||||
CONF_TRIGGER_ID,
|
CONF_TRIGGER_ID,
|
||||||
CONF_VALUE,
|
CONF_VALUE,
|
||||||
CONF_WEB_SERVER_ID,
|
CONF_WEB_SERVER,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, coroutine_with_priority
|
from esphome.core import CORE, coroutine_with_priority
|
||||||
from esphome.cpp_helpers import setup_entity
|
from esphome.cpp_helpers import setup_entity
|
||||||
|
@ -82,9 +82,8 @@ async def setup_text_core_(
|
||||||
mqtt_ = cg.new_Pvariable(mqtt_id, var)
|
mqtt_ = cg.new_Pvariable(mqtt_id, var)
|
||||||
await mqtt.register_mqtt_component(mqtt_, config)
|
await mqtt.register_mqtt_component(mqtt_, config)
|
||||||
|
|
||||||
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
|
if web_server_config := config.get(CONF_WEB_SERVER):
|
||||||
web_server_ = await cg.get_variable(webserver_id)
|
await web_server.add_entity_config(var, web_server_config)
|
||||||
web_server.add_entity_to_sorting_list(web_server_, var, config)
|
|
||||||
|
|
||||||
|
|
||||||
async def register_text(
|
async def register_text(
|
||||||
|
|
|
@ -15,7 +15,7 @@ from esphome.const import (
|
||||||
CONF_STATE,
|
CONF_STATE,
|
||||||
CONF_TO,
|
CONF_TO,
|
||||||
CONF_TRIGGER_ID,
|
CONF_TRIGGER_ID,
|
||||||
CONF_WEB_SERVER_ID,
|
CONF_WEB_SERVER,
|
||||||
DEVICE_CLASS_DATE,
|
DEVICE_CLASS_DATE,
|
||||||
DEVICE_CLASS_EMPTY,
|
DEVICE_CLASS_EMPTY,
|
||||||
DEVICE_CLASS_TIMESTAMP,
|
DEVICE_CLASS_TIMESTAMP,
|
||||||
|
@ -212,9 +212,8 @@ async def setup_text_sensor_core_(var, config):
|
||||||
mqtt_ = cg.new_Pvariable(mqtt_id, var)
|
mqtt_ = cg.new_Pvariable(mqtt_id, var)
|
||||||
await mqtt.register_mqtt_component(mqtt_, config)
|
await mqtt.register_mqtt_component(mqtt_, config)
|
||||||
|
|
||||||
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
|
if web_server_config := config.get(CONF_WEB_SERVER):
|
||||||
web_server_ = await cg.get_variable(webserver_id)
|
await web_server.add_entity_config(var, web_server_config)
|
||||||
web_server.add_entity_to_sorting_list(web_server_, var, config)
|
|
||||||
|
|
||||||
|
|
||||||
async def register_text_sensor(var, config):
|
async def register_text_sensor(var, config):
|
||||||
|
|
|
@ -502,8 +502,9 @@ void ThermostatClimate::switch_to_action_(climate::ClimateAction action, bool pu
|
||||||
}
|
}
|
||||||
this->action = action;
|
this->action = action;
|
||||||
this->prev_action_trigger_ = trig;
|
this->prev_action_trigger_ = trig;
|
||||||
assert(trig != nullptr);
|
if (trig != nullptr) {
|
||||||
trig->trigger();
|
trig->trigger();
|
||||||
|
}
|
||||||
// if enabled, call the fan_only action with cooling/heating actions
|
// if enabled, call the fan_only action with cooling/heating actions
|
||||||
if (trig_fan != nullptr) {
|
if (trig_fan != nullptr) {
|
||||||
ESP_LOGVV(TAG, "Calling FAN_ONLY action with HEATING/COOLING action");
|
ESP_LOGVV(TAG, "Calling FAN_ONLY action with HEATING/COOLING action");
|
||||||
|
@ -564,7 +565,6 @@ void ThermostatClimate::trigger_supplemental_action_() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (trig != nullptr) {
|
if (trig != nullptr) {
|
||||||
assert(trig != nullptr);
|
|
||||||
trig->trigger();
|
trig->trigger();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -634,8 +634,9 @@ void ThermostatClimate::switch_to_fan_mode_(climate::ClimateFanMode fan_mode, bo
|
||||||
this->prev_fan_mode_trigger_ = nullptr;
|
this->prev_fan_mode_trigger_ = nullptr;
|
||||||
}
|
}
|
||||||
this->start_timer_(thermostat::TIMER_FAN_MODE);
|
this->start_timer_(thermostat::TIMER_FAN_MODE);
|
||||||
assert(trig != nullptr);
|
if (trig != nullptr) {
|
||||||
trig->trigger();
|
trig->trigger();
|
||||||
|
}
|
||||||
this->prev_fan_mode_ = fan_mode;
|
this->prev_fan_mode_ = fan_mode;
|
||||||
this->prev_fan_mode_trigger_ = trig;
|
this->prev_fan_mode_trigger_ = trig;
|
||||||
}
|
}
|
||||||
|
@ -678,8 +679,9 @@ void ThermostatClimate::switch_to_mode_(climate::ClimateMode mode, bool publish_
|
||||||
mode = climate::CLIMATE_MODE_HEAT_COOL;
|
mode = climate::CLIMATE_MODE_HEAT_COOL;
|
||||||
// trig = this->auto_mode_trigger_;
|
// trig = this->auto_mode_trigger_;
|
||||||
}
|
}
|
||||||
assert(trig != nullptr);
|
if (trig != nullptr) {
|
||||||
trig->trigger();
|
trig->trigger();
|
||||||
|
}
|
||||||
this->mode = mode;
|
this->mode = mode;
|
||||||
this->prev_mode_ = mode;
|
this->prev_mode_ = mode;
|
||||||
this->prev_mode_trigger_ = trig;
|
this->prev_mode_trigger_ = trig;
|
||||||
|
@ -718,8 +720,9 @@ void ThermostatClimate::switch_to_swing_mode_(climate::ClimateSwingMode swing_mo
|
||||||
swing_mode = climate::CLIMATE_SWING_OFF;
|
swing_mode = climate::CLIMATE_SWING_OFF;
|
||||||
// trig = this->swing_mode_off_trigger_;
|
// trig = this->swing_mode_off_trigger_;
|
||||||
}
|
}
|
||||||
assert(trig != nullptr);
|
if (trig != nullptr) {
|
||||||
trig->trigger();
|
trig->trigger();
|
||||||
|
}
|
||||||
this->swing_mode = swing_mode;
|
this->swing_mode = swing_mode;
|
||||||
this->prev_swing_mode_ = swing_mode;
|
this->prev_swing_mode_ = swing_mode;
|
||||||
this->prev_swing_mode_trigger_ = trig;
|
this->prev_swing_mode_trigger_ = trig;
|
||||||
|
@ -867,8 +870,9 @@ void ThermostatClimate::check_temperature_change_trigger_() {
|
||||||
}
|
}
|
||||||
// trigger the action
|
// trigger the action
|
||||||
Trigger<> *trig = this->temperature_change_trigger_;
|
Trigger<> *trig = this->temperature_change_trigger_;
|
||||||
assert(trig != nullptr);
|
if (trig != nullptr) {
|
||||||
trig->trigger();
|
trig->trigger();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ThermostatClimate::cooling_required_() {
|
bool ThermostatClimate::cooling_required_() {
|
||||||
|
@ -998,9 +1002,10 @@ void ThermostatClimate::change_preset_(climate::ClimatePreset preset) {
|
||||||
this->preset.value() != preset) {
|
this->preset.value() != preset) {
|
||||||
// Fire any preset changed trigger if defined
|
// Fire any preset changed trigger if defined
|
||||||
Trigger<> *trig = this->preset_change_trigger_;
|
Trigger<> *trig = this->preset_change_trigger_;
|
||||||
assert(trig != nullptr);
|
|
||||||
this->preset = preset;
|
this->preset = preset;
|
||||||
|
if (trig != nullptr) {
|
||||||
trig->trigger();
|
trig->trigger();
|
||||||
|
}
|
||||||
|
|
||||||
this->refresh();
|
this->refresh();
|
||||||
ESP_LOGI(TAG, "Preset %s applied", LOG_STR_ARG(climate::climate_preset_to_string(preset)));
|
ESP_LOGI(TAG, "Preset %s applied", LOG_STR_ARG(climate::climate_preset_to_string(preset)));
|
||||||
|
@ -1023,9 +1028,10 @@ void ThermostatClimate::change_custom_preset_(const std::string &custom_preset)
|
||||||
this->custom_preset.value() != custom_preset) {
|
this->custom_preset.value() != custom_preset) {
|
||||||
// Fire any preset changed trigger if defined
|
// Fire any preset changed trigger if defined
|
||||||
Trigger<> *trig = this->preset_change_trigger_;
|
Trigger<> *trig = this->preset_change_trigger_;
|
||||||
assert(trig != nullptr);
|
|
||||||
this->custom_preset = custom_preset;
|
this->custom_preset = custom_preset;
|
||||||
|
if (trig != nullptr) {
|
||||||
trig->trigger();
|
trig->trigger();
|
||||||
|
}
|
||||||
|
|
||||||
this->refresh();
|
this->refresh();
|
||||||
ESP_LOGI(TAG, "Custom preset %s applied", custom_preset.c_str());
|
ESP_LOGI(TAG, "Custom preset %s applied", custom_preset.c_str());
|
||||||
|
|
|
@ -8,7 +8,7 @@ from esphome.const import (
|
||||||
CONF_FORCE_UPDATE,
|
CONF_FORCE_UPDATE,
|
||||||
CONF_ID,
|
CONF_ID,
|
||||||
CONF_MQTT_ID,
|
CONF_MQTT_ID,
|
||||||
CONF_WEB_SERVER_ID,
|
CONF_WEB_SERVER,
|
||||||
DEVICE_CLASS_EMPTY,
|
DEVICE_CLASS_EMPTY,
|
||||||
DEVICE_CLASS_FIRMWARE,
|
DEVICE_CLASS_FIRMWARE,
|
||||||
ENTITY_CATEGORY_CONFIG,
|
ENTITY_CATEGORY_CONFIG,
|
||||||
|
@ -73,9 +73,8 @@ async def setup_update_core_(var, config):
|
||||||
mqtt_ = cg.new_Pvariable(mqtt_id_config, var)
|
mqtt_ = cg.new_Pvariable(mqtt_id_config, var)
|
||||||
await mqtt.register_mqtt_component(mqtt_, config)
|
await mqtt.register_mqtt_component(mqtt_, config)
|
||||||
|
|
||||||
if web_server_id_config := config.get(CONF_WEB_SERVER_ID):
|
if web_server_config := config.get(CONF_WEB_SERVER):
|
||||||
web_server_ = await cg.get_variable(web_server_id_config)
|
await web_server.add_entity_config(var, web_server_config)
|
||||||
web_server.add_entity_to_sorting_list(web_server_, var, config)
|
|
||||||
|
|
||||||
|
|
||||||
async def register_update(var, config):
|
async def register_update(var, config):
|
||||||
|
|
|
@ -14,7 +14,7 @@ from esphome.const import (
|
||||||
CONF_STATE,
|
CONF_STATE,
|
||||||
CONF_STOP,
|
CONF_STOP,
|
||||||
CONF_TRIGGER_ID,
|
CONF_TRIGGER_ID,
|
||||||
CONF_WEB_SERVER_ID,
|
CONF_WEB_SERVER,
|
||||||
DEVICE_CLASS_EMPTY,
|
DEVICE_CLASS_EMPTY,
|
||||||
DEVICE_CLASS_GAS,
|
DEVICE_CLASS_GAS,
|
||||||
DEVICE_CLASS_WATER,
|
DEVICE_CLASS_WATER,
|
||||||
|
@ -124,9 +124,8 @@ async def setup_valve_core_(var, config):
|
||||||
mqtt_.set_custom_position_command_topic(position_command_topic_config)
|
mqtt_.set_custom_position_command_topic(position_command_topic_config)
|
||||||
)
|
)
|
||||||
|
|
||||||
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
|
if web_server_config := config.get(CONF_WEB_SERVER):
|
||||||
web_server_ = await cg.get_variable(webserver_id)
|
await web_server.add_entity_config(var, web_server_config)
|
||||||
web_server.add_entity_to_sorting_list(web_server_, var, config)
|
|
||||||
|
|
||||||
|
|
||||||
async def register_valve(var, config):
|
async def register_valve(var, config):
|
||||||
|
|
|
@ -17,13 +17,14 @@ from esphome.const import (
|
||||||
CONF_JS_URL,
|
CONF_JS_URL,
|
||||||
CONF_LOCAL,
|
CONF_LOCAL,
|
||||||
CONF_LOG,
|
CONF_LOG,
|
||||||
|
CONF_NAME,
|
||||||
CONF_OTA,
|
CONF_OTA,
|
||||||
CONF_PASSWORD,
|
CONF_PASSWORD,
|
||||||
CONF_PORT,
|
CONF_PORT,
|
||||||
CONF_USERNAME,
|
CONF_USERNAME,
|
||||||
CONF_VERSION,
|
CONF_VERSION,
|
||||||
|
CONF_WEB_SERVER,
|
||||||
CONF_WEB_SERVER_ID,
|
CONF_WEB_SERVER_ID,
|
||||||
CONF_WEB_SERVER_SORTING_WEIGHT,
|
|
||||||
PLATFORM_BK72XX,
|
PLATFORM_BK72XX,
|
||||||
PLATFORM_ESP32,
|
PLATFORM_ESP32,
|
||||||
PLATFORM_ESP8266,
|
PLATFORM_ESP8266,
|
||||||
|
@ -34,9 +35,15 @@ import esphome.final_validate as fv
|
||||||
|
|
||||||
AUTO_LOAD = ["json", "web_server_base"]
|
AUTO_LOAD = ["json", "web_server_base"]
|
||||||
|
|
||||||
|
CONF_SORTING_GROUP_ID = "sorting_group_id"
|
||||||
|
CONF_SORTING_GROUPS = "sorting_groups"
|
||||||
|
CONF_SORTING_WEIGHT = "sorting_weight"
|
||||||
|
|
||||||
web_server_ns = cg.esphome_ns.namespace("web_server")
|
web_server_ns = cg.esphome_ns.namespace("web_server")
|
||||||
WebServer = web_server_ns.class_("WebServer", cg.Component, cg.Controller)
|
WebServer = web_server_ns.class_("WebServer", cg.Component, cg.Controller)
|
||||||
|
|
||||||
|
sorting_groups = {}
|
||||||
|
|
||||||
|
|
||||||
def default_url(config):
|
def default_url(config):
|
||||||
config = config.copy()
|
config = config.copy()
|
||||||
|
@ -70,42 +77,74 @@ def validate_ota(config):
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
def _validate_no_sorting_weight(
|
def validate_sorting_groups(config):
|
||||||
webserver_version: int, config: dict, path: list[str] | None = None
|
if CONF_SORTING_GROUPS in config and config[CONF_VERSION] != 3:
|
||||||
) -> None:
|
raise cv.Invalid(
|
||||||
if path is None:
|
f"'{CONF_SORTING_GROUPS}' is only supported in 'web_server' version 3"
|
||||||
path = []
|
|
||||||
if CONF_WEB_SERVER_SORTING_WEIGHT in config:
|
|
||||||
raise cv.FinalExternalInvalid(
|
|
||||||
f"Sorting weight on entities is not supported in web_server version {webserver_version}",
|
|
||||||
path=path + [CONF_WEB_SERVER_SORTING_WEIGHT],
|
|
||||||
)
|
)
|
||||||
for p, value in config.items():
|
|
||||||
if isinstance(value, dict):
|
|
||||||
_validate_no_sorting_weight(webserver_version, value, path + [p])
|
|
||||||
elif isinstance(value, list):
|
|
||||||
for i, item in enumerate(value):
|
|
||||||
if isinstance(item, dict):
|
|
||||||
_validate_no_sorting_weight(webserver_version, item, path + [p, i])
|
|
||||||
|
|
||||||
|
|
||||||
def _final_validate_sorting_weight(config):
|
|
||||||
if (webserver_version := config.get(CONF_VERSION)) != 3:
|
|
||||||
_validate_no_sorting_weight(webserver_version, fv.full_config.get())
|
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
FINAL_VALIDATE_SCHEMA = _final_validate_sorting_weight
|
def _validate_no_sorting_component(
|
||||||
|
sorting_component: str,
|
||||||
|
webserver_version: int,
|
||||||
|
config: dict,
|
||||||
|
path: list[str] | None = None,
|
||||||
|
) -> None:
|
||||||
|
if path is None:
|
||||||
|
path = []
|
||||||
|
if CONF_WEB_SERVER in config and sorting_component in config[CONF_WEB_SERVER]:
|
||||||
|
raise cv.FinalExternalInvalid(
|
||||||
|
f"{sorting_component} on entities is not supported in web_server version {webserver_version}",
|
||||||
|
path=path + [sorting_component],
|
||||||
|
)
|
||||||
|
for p, value in config.items():
|
||||||
|
if isinstance(value, dict):
|
||||||
|
_validate_no_sorting_component(
|
||||||
|
sorting_component, webserver_version, value, path + [p]
|
||||||
|
)
|
||||||
|
elif isinstance(value, list):
|
||||||
|
for i, item in enumerate(value):
|
||||||
|
if isinstance(item, dict):
|
||||||
|
_validate_no_sorting_component(
|
||||||
|
sorting_component, webserver_version, item, path + [p, i]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _final_validate_sorting(config):
|
||||||
|
if (webserver_version := config.get(CONF_VERSION)) != 3:
|
||||||
|
_validate_no_sorting_component(
|
||||||
|
CONF_SORTING_WEIGHT, webserver_version, fv.full_config.get()
|
||||||
|
)
|
||||||
|
_validate_no_sorting_component(
|
||||||
|
CONF_SORTING_GROUP_ID, webserver_version, fv.full_config.get()
|
||||||
|
)
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
FINAL_VALIDATE_SCHEMA = _final_validate_sorting
|
||||||
|
|
||||||
|
sorting_group = {
|
||||||
|
cv.Required(CONF_ID): cv.declare_id(cg.int_),
|
||||||
|
cv.Required(CONF_NAME): cv.string,
|
||||||
|
cv.Optional(CONF_SORTING_WEIGHT): cv.float_,
|
||||||
|
}
|
||||||
|
|
||||||
WEBSERVER_SORTING_SCHEMA = cv.Schema(
|
WEBSERVER_SORTING_SCHEMA = cv.Schema(
|
||||||
|
{
|
||||||
|
cv.Optional(CONF_WEB_SERVER): cv.Schema(
|
||||||
{
|
{
|
||||||
cv.OnlyWith(CONF_WEB_SERVER_ID, "web_server"): cv.use_id(WebServer),
|
cv.OnlyWith(CONF_WEB_SERVER_ID, "web_server"): cv.use_id(WebServer),
|
||||||
cv.Optional(CONF_WEB_SERVER_SORTING_WEIGHT): cv.All(
|
cv.Optional(CONF_SORTING_WEIGHT): cv.All(
|
||||||
cv.requires_component("web_server"),
|
cv.requires_component("web_server"),
|
||||||
cv.float_,
|
cv.float_,
|
||||||
),
|
),
|
||||||
|
cv.Optional(CONF_SORTING_GROUP_ID): cv.All(
|
||||||
|
cv.requires_component("web_server"),
|
||||||
|
cv.use_id(cg.int_),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -145,24 +184,38 @@ CONFIG_SCHEMA = cv.All(
|
||||||
): cv.boolean,
|
): cv.boolean,
|
||||||
cv.Optional(CONF_LOG, default=True): cv.boolean,
|
cv.Optional(CONF_LOG, default=True): cv.boolean,
|
||||||
cv.Optional(CONF_LOCAL): cv.boolean,
|
cv.Optional(CONF_LOCAL): cv.boolean,
|
||||||
|
cv.Optional(CONF_SORTING_GROUPS): cv.ensure_list(sorting_group),
|
||||||
}
|
}
|
||||||
).extend(cv.COMPONENT_SCHEMA),
|
).extend(cv.COMPONENT_SCHEMA),
|
||||||
cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_BK72XX, PLATFORM_RTL87XX]),
|
cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_BK72XX, PLATFORM_RTL87XX]),
|
||||||
default_url,
|
default_url,
|
||||||
validate_local,
|
validate_local,
|
||||||
validate_ota,
|
validate_ota,
|
||||||
|
validate_sorting_groups,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def add_entity_to_sorting_list(web_server, entity, config):
|
def add_sorting_groups(web_server_var, config):
|
||||||
sorting_weight = 50
|
for group in config:
|
||||||
if CONF_WEB_SERVER_SORTING_WEIGHT in config:
|
sorting_groups[group[CONF_ID]] = group[CONF_NAME]
|
||||||
sorting_weight = config[CONF_WEB_SERVER_SORTING_WEIGHT]
|
group_sorting_weight = group.get(CONF_SORTING_WEIGHT, 50)
|
||||||
|
cg.add(
|
||||||
|
web_server_var.add_sorting_group(
|
||||||
|
hash(group[CONF_ID]), group[CONF_NAME], group_sorting_weight
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def add_entity_config(entity, config):
|
||||||
|
web_server = await cg.get_variable(config[CONF_WEB_SERVER_ID])
|
||||||
|
sorting_weight = config.get(CONF_SORTING_WEIGHT, 50)
|
||||||
|
sorting_group_hash = hash(config.get(CONF_SORTING_GROUP_ID))
|
||||||
|
|
||||||
cg.add(
|
cg.add(
|
||||||
web_server.add_entity_to_sorting_list(
|
web_server.add_entity_config(
|
||||||
entity,
|
entity,
|
||||||
sorting_weight,
|
sorting_weight,
|
||||||
|
sorting_group_hash,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -241,3 +294,6 @@ async def to_code(config):
|
||||||
cg.add(var.set_include_internal(config[CONF_INCLUDE_INTERNAL]))
|
cg.add(var.set_include_internal(config[CONF_INCLUDE_INTERNAL]))
|
||||||
if CONF_LOCAL in config and config[CONF_LOCAL]:
|
if CONF_LOCAL in config and config[CONF_LOCAL]:
|
||||||
cg.add_define("USE_WEBSERVER_LOCAL")
|
cg.add_define("USE_WEBSERVER_LOCAL")
|
||||||
|
|
||||||
|
if (sorting_group_config := config.get(CONF_SORTING_GROUPS)) is not None:
|
||||||
|
add_sorting_groups(var, sorting_group_config)
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -105,6 +105,14 @@ void WebServer::setup() {
|
||||||
// Configure reconnect timeout and send config
|
// Configure reconnect timeout and send config
|
||||||
client->send(this->get_config_json().c_str(), "ping", millis(), 30000);
|
client->send(this->get_config_json().c_str(), "ping", millis(), 30000);
|
||||||
|
|
||||||
|
for (auto &group : this->sorting_groups_) {
|
||||||
|
client->send(json::build_json([group](JsonObject root) {
|
||||||
|
root["name"] = group.second.name;
|
||||||
|
root["sorting_weight"] = group.second.weight;
|
||||||
|
}).c_str(),
|
||||||
|
"sorting_group");
|
||||||
|
}
|
||||||
|
|
||||||
this->entities_iterator_.begin(this->include_internal_);
|
this->entities_iterator_.begin(this->include_internal_);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -246,6 +254,9 @@ std::string WebServer::sensor_json(sensor::Sensor *obj, float value, JsonDetail
|
||||||
if (start_config == DETAIL_ALL) {
|
if (start_config == DETAIL_ALL) {
|
||||||
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
|
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
|
||||||
root["sorting_weight"] = this->sorting_entitys_[obj].weight;
|
root["sorting_weight"] = this->sorting_entitys_[obj].weight;
|
||||||
|
if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
|
||||||
|
root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!obj->get_unit_of_measurement().empty())
|
if (!obj->get_unit_of_measurement().empty())
|
||||||
root["uom"] = obj->get_unit_of_measurement();
|
root["uom"] = obj->get_unit_of_measurement();
|
||||||
|
@ -284,6 +295,9 @@ std::string WebServer::text_sensor_json(text_sensor::TextSensor *obj, const std:
|
||||||
if (start_config == DETAIL_ALL) {
|
if (start_config == DETAIL_ALL) {
|
||||||
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
|
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
|
||||||
root["sorting_weight"] = this->sorting_entitys_[obj].weight;
|
root["sorting_weight"] = this->sorting_entitys_[obj].weight;
|
||||||
|
if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
|
||||||
|
root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -332,6 +346,9 @@ std::string WebServer::switch_json(switch_::Switch *obj, bool value, JsonDetail
|
||||||
root["assumed_state"] = obj->assumed_state();
|
root["assumed_state"] = obj->assumed_state();
|
||||||
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
|
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
|
||||||
root["sorting_weight"] = this->sorting_entitys_[obj].weight;
|
root["sorting_weight"] = this->sorting_entitys_[obj].weight;
|
||||||
|
if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
|
||||||
|
root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -368,6 +385,9 @@ std::string WebServer::button_json(button::Button *obj, JsonDetail start_config)
|
||||||
if (start_config == DETAIL_ALL) {
|
if (start_config == DETAIL_ALL) {
|
||||||
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
|
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
|
||||||
root["sorting_weight"] = this->sorting_entitys_[obj].weight;
|
root["sorting_weight"] = this->sorting_entitys_[obj].weight;
|
||||||
|
if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
|
||||||
|
root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -404,6 +424,9 @@ std::string WebServer::binary_sensor_json(binary_sensor::BinarySensor *obj, bool
|
||||||
if (start_config == DETAIL_ALL) {
|
if (start_config == DETAIL_ALL) {
|
||||||
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
|
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
|
||||||
root["sorting_weight"] = this->sorting_entitys_[obj].weight;
|
root["sorting_weight"] = this->sorting_entitys_[obj].weight;
|
||||||
|
if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
|
||||||
|
root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -487,6 +510,9 @@ std::string WebServer::fan_json(fan::Fan *obj, JsonDetail start_config) {
|
||||||
if (start_config == DETAIL_ALL) {
|
if (start_config == DETAIL_ALL) {
|
||||||
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
|
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
|
||||||
root["sorting_weight"] = this->sorting_entitys_[obj].weight;
|
root["sorting_weight"] = this->sorting_entitys_[obj].weight;
|
||||||
|
if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
|
||||||
|
root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -603,6 +629,9 @@ std::string WebServer::light_json(light::LightState *obj, JsonDetail start_confi
|
||||||
}
|
}
|
||||||
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
|
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
|
||||||
root["sorting_weight"] = this->sorting_entitys_[obj].weight;
|
root["sorting_weight"] = this->sorting_entitys_[obj].weight;
|
||||||
|
if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
|
||||||
|
root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -684,6 +713,9 @@ std::string WebServer::cover_json(cover::Cover *obj, JsonDetail start_config) {
|
||||||
if (start_config == DETAIL_ALL) {
|
if (start_config == DETAIL_ALL) {
|
||||||
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
|
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
|
||||||
root["sorting_weight"] = this->sorting_entitys_[obj].weight;
|
root["sorting_weight"] = this->sorting_entitys_[obj].weight;
|
||||||
|
if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
|
||||||
|
root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -745,6 +777,9 @@ std::string WebServer::number_json(number::Number *obj, float value, JsonDetail
|
||||||
root["uom"] = obj->traits.get_unit_of_measurement();
|
root["uom"] = obj->traits.get_unit_of_measurement();
|
||||||
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
|
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
|
||||||
root["sorting_weight"] = this->sorting_entitys_[obj].weight;
|
root["sorting_weight"] = this->sorting_entitys_[obj].weight;
|
||||||
|
if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
|
||||||
|
root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (std::isnan(value)) {
|
if (std::isnan(value)) {
|
||||||
|
@ -814,6 +849,9 @@ std::string WebServer::date_json(datetime::DateEntity *obj, JsonDetail start_con
|
||||||
if (start_config == DETAIL_ALL) {
|
if (start_config == DETAIL_ALL) {
|
||||||
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
|
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
|
||||||
root["sorting_weight"] = this->sorting_entitys_[obj].weight;
|
root["sorting_weight"] = this->sorting_entitys_[obj].weight;
|
||||||
|
if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
|
||||||
|
root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -872,6 +910,9 @@ std::string WebServer::time_json(datetime::TimeEntity *obj, JsonDetail start_con
|
||||||
if (start_config == DETAIL_ALL) {
|
if (start_config == DETAIL_ALL) {
|
||||||
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
|
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
|
||||||
root["sorting_weight"] = this->sorting_entitys_[obj].weight;
|
root["sorting_weight"] = this->sorting_entitys_[obj].weight;
|
||||||
|
if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
|
||||||
|
root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -931,6 +972,9 @@ std::string WebServer::datetime_json(datetime::DateTimeEntity *obj, JsonDetail s
|
||||||
if (start_config == DETAIL_ALL) {
|
if (start_config == DETAIL_ALL) {
|
||||||
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
|
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
|
||||||
root["sorting_weight"] = this->sorting_entitys_[obj].weight;
|
root["sorting_weight"] = this->sorting_entitys_[obj].weight;
|
||||||
|
if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
|
||||||
|
root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -992,6 +1036,9 @@ std::string WebServer::text_json(text::Text *obj, const std::string &value, Json
|
||||||
root["mode"] = (int) obj->traits.get_mode();
|
root["mode"] = (int) obj->traits.get_mode();
|
||||||
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
|
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
|
||||||
root["sorting_weight"] = this->sorting_entitys_[obj].weight;
|
root["sorting_weight"] = this->sorting_entitys_[obj].weight;
|
||||||
|
if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
|
||||||
|
root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1048,6 +1095,9 @@ std::string WebServer::select_json(select::Select *obj, const std::string &value
|
||||||
}
|
}
|
||||||
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
|
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
|
||||||
root["sorting_weight"] = this->sorting_entitys_[obj].weight;
|
root["sorting_weight"] = this->sorting_entitys_[obj].weight;
|
||||||
|
if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
|
||||||
|
root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1164,6 +1214,9 @@ std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_conf
|
||||||
}
|
}
|
||||||
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
|
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
|
||||||
root["sorting_weight"] = this->sorting_entitys_[obj].weight;
|
root["sorting_weight"] = this->sorting_entitys_[obj].weight;
|
||||||
|
if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
|
||||||
|
root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1257,6 +1310,9 @@ std::string WebServer::lock_json(lock::Lock *obj, lock::LockState value, JsonDet
|
||||||
if (start_config == DETAIL_ALL) {
|
if (start_config == DETAIL_ALL) {
|
||||||
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
|
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
|
||||||
root["sorting_weight"] = this->sorting_entitys_[obj].weight;
|
root["sorting_weight"] = this->sorting_entitys_[obj].weight;
|
||||||
|
if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
|
||||||
|
root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1326,8 +1382,13 @@ std::string WebServer::valve_json(valve::Valve *obj, JsonDetail start_config) {
|
||||||
|
|
||||||
if (obj->get_traits().get_supports_position())
|
if (obj->get_traits().get_supports_position())
|
||||||
root["position"] = obj->position;
|
root["position"] = obj->position;
|
||||||
|
if (start_config == DETAIL_ALL) {
|
||||||
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
|
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
|
||||||
root["sorting_weight"] = this->sorting_entitys_[obj].weight;
|
root["sorting_weight"] = this->sorting_entitys_[obj].weight;
|
||||||
|
if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
|
||||||
|
root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1367,6 +1428,9 @@ std::string WebServer::alarm_control_panel_json(alarm_control_panel::AlarmContro
|
||||||
if (start_config == DETAIL_ALL) {
|
if (start_config == DETAIL_ALL) {
|
||||||
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
|
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
|
||||||
root["sorting_weight"] = this->sorting_entitys_[obj].weight;
|
root["sorting_weight"] = this->sorting_entitys_[obj].weight;
|
||||||
|
if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
|
||||||
|
root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1453,6 +1517,9 @@ std::string WebServer::update_json(update::UpdateEntity *obj, JsonDetail start_c
|
||||||
root["release_url"] = obj->update_info.release_url;
|
root["release_url"] = obj->update_info.release_url;
|
||||||
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
|
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
|
||||||
root["sorting_weight"] = this->sorting_entitys_[obj].weight;
|
root["sorting_weight"] = this->sorting_entitys_[obj].weight;
|
||||||
|
if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
|
||||||
|
root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1751,8 +1818,12 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) {
|
||||||
|
|
||||||
bool WebServer::isRequestHandlerTrivial() { return false; }
|
bool WebServer::isRequestHandlerTrivial() { return false; }
|
||||||
|
|
||||||
void WebServer::add_entity_to_sorting_list(EntityBase *entity, float weight) {
|
void WebServer::add_entity_config(EntityBase *entity, float weight, uint64_t group) {
|
||||||
this->sorting_entitys_[entity] = SortingComponents{weight};
|
this->sorting_entitys_[entity] = SortingComponents{weight, group};
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebServer::add_sorting_group(uint64_t group_id, const std::string &group_name, float weight) {
|
||||||
|
this->sorting_groups_[group_id] = SortingGroup{group_name, weight};
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebServer::schedule_(std::function<void()> &&f) {
|
void WebServer::schedule_(std::function<void()> &&f) {
|
||||||
|
|
|
@ -44,6 +44,12 @@ struct UrlMatch {
|
||||||
|
|
||||||
struct SortingComponents {
|
struct SortingComponents {
|
||||||
float weight;
|
float weight;
|
||||||
|
uint64_t group_id;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SortingGroup {
|
||||||
|
std::string name;
|
||||||
|
float weight;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum JsonDetail { DETAIL_ALL, DETAIL_STATE };
|
enum JsonDetail { DETAIL_ALL, DETAIL_STATE };
|
||||||
|
@ -337,7 +343,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
|
||||||
/// This web handle is not trivial.
|
/// This web handle is not trivial.
|
||||||
bool isRequestHandlerTrivial() override; // NOLINT(readability-identifier-naming)
|
bool isRequestHandlerTrivial() override; // NOLINT(readability-identifier-naming)
|
||||||
|
|
||||||
void add_entity_to_sorting_list(EntityBase *entity, float weight);
|
void add_entity_config(EntityBase *entity, float weight, uint64_t group);
|
||||||
|
void add_sorting_group(uint64_t group_id, const std::string &group_name, float weight);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void schedule_(std::function<void()> &&f);
|
void schedule_(std::function<void()> &&f);
|
||||||
|
@ -346,6 +353,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
|
||||||
AsyncEventSource events_{"/events"};
|
AsyncEventSource events_{"/events"};
|
||||||
ListEntitiesIterator entities_iterator_;
|
ListEntitiesIterator entities_iterator_;
|
||||||
std::map<EntityBase *, SortingComponents> sorting_entitys_;
|
std::map<EntityBase *, SortingComponents> sorting_entitys_;
|
||||||
|
std::map<uint64_t, SortingGroup> sorting_groups_;
|
||||||
|
|
||||||
#if USE_WEBSERVER_VERSION == 1
|
#if USE_WEBSERVER_VERSION == 1
|
||||||
const char *css_url_{nullptr};
|
const char *css_url_{nullptr};
|
||||||
const char *js_url_{nullptr};
|
const char *js_url_{nullptr};
|
||||||
|
|
|
@ -131,16 +131,10 @@ void event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, voi
|
||||||
|
|
||||||
void WiFiComponent::wifi_pre_setup_() {
|
void WiFiComponent::wifi_pre_setup_() {
|
||||||
uint8_t mac[6];
|
uint8_t mac[6];
|
||||||
#ifdef USE_ESP32_IGNORE_EFUSE_MAC_CRC
|
|
||||||
get_mac_address_raw(mac);
|
|
||||||
set_mac_address(mac);
|
|
||||||
ESP_LOGV(TAG, "Use EFuse MAC without checking CRC: %s", get_mac_address_pretty().c_str());
|
|
||||||
#else
|
|
||||||
if (has_custom_mac_address()) {
|
if (has_custom_mac_address()) {
|
||||||
get_mac_address_raw(mac);
|
get_mac_address_raw(mac);
|
||||||
set_mac_address(mac);
|
set_mac_address(mac);
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
esp_err_t err = esp_netif_init();
|
esp_err_t err = esp_netif_init();
|
||||||
if (err != ERR_OK) {
|
if (err != ERR_OK) {
|
||||||
ESP_LOGE(TAG, "esp_netif_init failed: %s", esp_err_to_name(err));
|
ESP_LOGE(TAG, "esp_netif_init failed: %s", esp_err_to_name(err));
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
"""Constants used by esphome."""
|
"""Constants used by esphome."""
|
||||||
|
|
||||||
__version__ = "2024.10.0-dev"
|
__version__ = "2024.11.0-dev"
|
||||||
|
|
||||||
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
||||||
VALID_SUBSTITUTIONS_CHARACTERS = (
|
VALID_SUBSTITUTIONS_CHARACTERS = (
|
||||||
|
@ -934,7 +934,6 @@ CONF_WARM_WHITE_COLOR_TEMPERATURE = "warm_white_color_temperature"
|
||||||
CONF_WATCHDOG_THRESHOLD = "watchdog_threshold"
|
CONF_WATCHDOG_THRESHOLD = "watchdog_threshold"
|
||||||
CONF_WEB_SERVER = "web_server"
|
CONF_WEB_SERVER = "web_server"
|
||||||
CONF_WEB_SERVER_ID = "web_server_id"
|
CONF_WEB_SERVER_ID = "web_server_id"
|
||||||
CONF_WEB_SERVER_SORTING_WEIGHT = "web_server_sorting_weight"
|
|
||||||
CONF_WEIGHT = "weight"
|
CONF_WEIGHT = "weight"
|
||||||
CONF_WHILE = "while"
|
CONF_WHILE = "while"
|
||||||
CONF_WHITE = "white"
|
CONF_WHITE = "white"
|
||||||
|
@ -962,6 +961,7 @@ ICON_ACCELERATION_Y = "mdi:axis-y-arrow"
|
||||||
ICON_ACCELERATION_Z = "mdi:axis-z-arrow"
|
ICON_ACCELERATION_Z = "mdi:axis-z-arrow"
|
||||||
ICON_ACCOUNT = "mdi:account"
|
ICON_ACCOUNT = "mdi:account"
|
||||||
ICON_ACCOUNT_CHECK = "mdi:account-check"
|
ICON_ACCOUNT_CHECK = "mdi:account-check"
|
||||||
|
ICON_AIR_FILTER = "mdi:air-filter"
|
||||||
ICON_ARROW_EXPAND_VERTICAL = "mdi:arrow-expand-vertical"
|
ICON_ARROW_EXPAND_VERTICAL = "mdi:arrow-expand-vertical"
|
||||||
ICON_BATTERY = "mdi:battery"
|
ICON_BATTERY = "mdi:battery"
|
||||||
ICON_BLUETOOTH = "mdi:bluetooth"
|
ICON_BLUETOOTH = "mdi:bluetooth"
|
||||||
|
@ -983,6 +983,7 @@ ICON_FINGERPRINT = "mdi:fingerprint"
|
||||||
ICON_FLASH = "mdi:flash"
|
ICON_FLASH = "mdi:flash"
|
||||||
ICON_FLASK = "mdi:flask"
|
ICON_FLASK = "mdi:flask"
|
||||||
ICON_FLASK_OUTLINE = "mdi:flask-outline"
|
ICON_FLASK_OUTLINE = "mdi:flask-outline"
|
||||||
|
ICON_FLASK_ROUND_BOTTOM = "mdi:flask-round-bottom"
|
||||||
ICON_FLOWER = "mdi:flower"
|
ICON_FLOWER = "mdi:flower"
|
||||||
ICON_GAS_CYLINDER = "mdi:gas-cylinder"
|
ICON_GAS_CYLINDER = "mdi:gas-cylinder"
|
||||||
ICON_GAUGE = "mdi:gauge"
|
ICON_GAUGE = "mdi:gauge"
|
||||||
|
@ -995,6 +996,7 @@ ICON_KEY_PLUS = "mdi:key-plus"
|
||||||
ICON_LIGHTBULB = "mdi:lightbulb"
|
ICON_LIGHTBULB = "mdi:lightbulb"
|
||||||
ICON_MAGNET = "mdi:magnet"
|
ICON_MAGNET = "mdi:magnet"
|
||||||
ICON_MEMORY = "mdi:memory"
|
ICON_MEMORY = "mdi:memory"
|
||||||
|
ICON_MOLECULE_CO = "mdi:molecule-co"
|
||||||
ICON_MOLECULE_CO2 = "mdi:molecule-co2"
|
ICON_MOLECULE_CO2 = "mdi:molecule-co2"
|
||||||
ICON_MOTION_SENSOR = "mdi:motion-sensor"
|
ICON_MOTION_SENSOR = "mdi:motion-sensor"
|
||||||
ICON_NEW_BOX = "mdi:new-box"
|
ICON_NEW_BOX = "mdi:new-box"
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue