Compare commits

...
Author SHA1 Message Date
J. Nick Koston 5cd8781a94 Merge branch 'dev' into ota-inflate-table-decode 2026-10-26 06:37:45 -10:00
J. Nick Koston cfe5809654 [wifi] Hand the DPP bootstrapping key to the supplicant DER encoded (#20882) 2026-10-26 16:37:25 +00:00
J. Nick Koston 9c54f96520 [libretiny] Send only the UF2 images the device writes over OTA (#21642) 2026-10-26 06:28:54 -10:00
J. Nick Kostonandpre-commit-ci-lite[bot] 0cc5b372fb [core] Defer integration imports in ir_rf_base and media_player (#21601)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2026-10-26 06:26:00 -10:00
J. Nick Koston f7c8cba8b7 [components] Use pulse_reset for blocking reset pin pulses (#21472) 2026-10-26 06:22:11 -10:00
J. Nick Koston 79a22e9a9a [core] Add pulse_reset for active-low reset pins (#21470) 2026-10-26 06:22:10 -10:00
J. Nick Koston 103023737d [openthread] Resolve IPv6 literal addresses when the DNS64 client is enabled (#19269) 2026-10-26 16:18:12 +00:00
J. Nick Koston 6b6b70a8b6 [dfrobot_sen0395] Collect lines with LineReader (#20979) 2026-10-26 12:16:51 -04:00
J. Nick Koston d312bf1b73 [core] Let the yaml editor insert lines, write with a rollback and restore (#20602) 2026-10-26 06:16:26 -10:00
J. Nick Koston 0d2f8c0e88 [components] Set or clear entity, socket and sensor bits with assign_bits (#21188) 2026-10-26 12:10:29 -04:00
J. Nick Koston 2f8b50c5af [components] Build the remaining enum option dicts with options() (#20972) 2026-10-26 12:10:08 -04:00
Kevin Ahrendt c4b598be3e [microphone] Pass each audio buffer as an AudioCapture (#21645) 2026-10-26 06:09:53 -10:00
J. Nick Koston 0cd16df16f [components] Write constant codec register tables with write_registers (#20946) 2026-10-26 12:09:47 -04:00
J. Nick Koston a680974da2 [ld6002b] Use Apply entities and find the setting by entity pointer (#20937) 2026-10-26 12:09:03 -04:00
J. Nick Koston 78f0b1ceba [esp32_rmt_led_strip] Stop blocking the main loop while waiting for the previous frame (#19337) 2026-10-26 06:08:13 -10:00
J. Nick Koston c5a841e11e [seeed_mr60fda2] Keep the parameter value tables in flash (#20720) 2026-10-26 12:07:24 -04:00
J. Nick Koston 365965f9bb [veml7700] Keep the gain and integration time tables in flash (#20719) 2026-10-26 12:07:06 -04:00
J. Nick Koston 16011d7fce [nextion] Keep the queue type name table in flash (#20658) 2026-10-26 12:06:31 -04:00
J. Nick Koston 4486dc53d8 [tx20] Keep the wind direction table in flash (#20657) 2026-10-26 12:05:43 -04:00
8d5e3ce534 [components] Use Apply selects for hub forwarding selects (#20966)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2026-10-26 16:05:20 +00:00
J. Nick Koston 9ea4d35a50 [uart] Keep debug delimiters and event patterns in flash (#20109) 2026-10-26 12:05:06 -04:00
J. Nick Koston ff26d32406 Merge branch 'dev' into ota-inflate-table-decode 2026-10-26 06:02:30 -10:00
J. Nick Koston 5cceddfbf4 [kamstrup_kmp] Keep the unit name table in flash (#20642) 2026-10-26 11:58:43 -04:00
J. Nick Koston ff6a37a052 [micronova] Keep the stove state table in flash (#20645) 2026-10-26 11:58:15 -04:00
J. Nick Koston 6990092be0 [ezo] Keep the command and calibration type tables in flash (#20655) 2026-10-26 11:57:41 -04:00
J. Nick Koston 9220c79a51 [bme680_bsec] Keep the IAQ accuracy names in flash (#20656) 2026-10-26 11:57:23 -04:00
J. Nick Koston 215fcd4f5d [pulse_counter] Keep the edge mode name table in flash (#20660) 2026-10-26 11:57:03 -04:00
J. Nick Koston 918e3b3a58 [haier] Keep the protocol phase names in flash (#20710) 2026-10-26 11:56:43 -04:00
J. Nick Koston 347eb3902e [seeed_mr24hpc1] Keep the text sensor state tables in flash (#20711) 2026-10-26 11:55:18 -04:00
J. Nick Koston 177820083f [epaper_spi] Keep the state name table in flash (#20713) 2026-10-26 11:54:07 -04:00
J. Nick Koston b0a0524091 [cc1101] Keep the modulation name table in flash (#20716) 2026-10-26 11:53:10 -04:00
J. Nick Koston 2df80fbf71 Merge branch 'dev' into ota-inflate-table-decode 2026-10-25 23:49:16 -10:00
J. Nick Koston 470f7eb51d [esphome] Read a stored byte before advancing the OTA output cursor
C leaves the order of *d->dest++ and the read on the right of the
assignment open, so dest could move first; a read that needs input
flushes the output up to dest where the ack follows the write, and
would write the byte before it was stored. The test harness can now
take the output inside the read callback the same way, and every
stream runs through it.
2026-10-25 23:05:30 -10:00
J. Nick Koston e7b2dfed57 Merge branch 'dev' into ota-inflate-table-decode 2026-10-25 22:51:40 -10:00
J. Nick Koston 83080c4560 [esphome] Wrap OTA back references as an index and test stored blocks after Huffman ones
Forming a pointer before the ring is undefined behaviour even when it is
wrapped before use; wrap the ring position instead. A new test stream
puts stored blocks after dynamic ones, so they start with read ahead
bits in the bit buffer.
2026-10-25 21:52:28 -10:00
J. Nick Koston 104cb09475 [esphome] Update the inflate session size in its comment 2026-10-25 21:44:28 -10:00
J. Nick Koston a7824ed4e7 [esphome] Trim the OTA decoder's flash and heap
The careful path, which only decodes the last few bytes of each read,
uses the canonical decoder alone instead of the lookup table too. The
canonical decoder accumulates each length's first code and symbol index
as it walks, so the trees no longer store them, and the trans bound
check, which the symbol count already guarantees, goes with the size
field. 94 bytes less flash and 132 bytes less heap on a BK72xx, same
speed on a BK7238.
2026-10-25 21:43:44 -10:00
J. Nick Koston 6420db90b1 [esphome] Let the OTA decoder own its ring and finish each window in the fast loop
The decoder now starts dest at the ring and starts it over once a call
has filled it, so callers no longer set the output bounds and cannot
point them outside the ring. The fast loop runs to the end of the ring,
leaving a match that does not fit to the resume path, instead of handing
the last 258 bytes of every window to the careful path; match copies
run in at most two pieces instead of testing for the wrap on every byte.

The careful path decodes long codes through the same canonical decoder
as the fast loop, the distance lookup table is 6 bits for a 30 symbol
alphabet (1.3 KB more heap than before this change instead of 2.2 KB),
and the tests share one stream table.
2026-10-25 21:33:18 -10:00
J. Nick Koston d3c2a7c734 [esphome] Decode OTA deflate with lookup tables and a fast loop
Inflating dominated compressed uploads on slow chips: the uzlib decoder
read every Huffman code one bit at a time. Decode from a 32 bit bit
buffer through 9 bit lookup tables, run a fast loop with the bit buffer
and cursors in locals while a whole symbol fits in the input and the
output, and write each output byte once, into the ring window.

About 5 times faster on a BK7231N, a BK7238 and an ESP32, for about
830 bytes of flash and 2.2 KB more heap while a compressed upload runs.
2026-10-25 21:05:35 -10:00
211 changed files with 3984 additions and 2280 deletions

No files matched your search

+17 -10
View File
@@ -1305,10 +1305,7 @@ def upload_program(
f"{option_string} is only supported with the esphome OTA platform; "
"the web_server OTA path can only update the firmware image."
)
binary = CORE.firmware_bin
if getattr(args, "file", None) is not None:
binary = Path(args.file)
return _upload_via_web_server(config, network_devices, binary)
return _upload_via_web_server(config, network_devices, _app_ota_image(args))
return _upload_via_native_api(config, network_devices, args)
@@ -1364,6 +1361,16 @@ def _esphome_ota_conf(config: ConfigType) -> ConfigType | None:
return next(iter(esphome_ota_items(config)), None)
def _app_ota_image(args: ArgsProtocol) -> Path:
"""The file an app update sends: ``--file`` or the build output, as the
target platform's ota_image hook picks it."""
binary = CORE.firmware_bin
if (file := getattr(args, "file", None)) is not None:
binary = Path(file)
ota_image = platform_hooks.get_platform_hook(CORE.target_platform, "ota_image")
return ota_image(binary) if ota_image is not None else binary
def _upload_via_native_api(
config: ConfigType, network_devices: list[str], args: ArgsProtocol
) -> tuple[int, str | None]:
@@ -1413,27 +1420,27 @@ def _upload_via_native_api(
f"flash a build with the option enabled, and then retry {option_string}."
)
binary = CORE.firmware_bin
ota_type = espota2.OTA_TYPE_UPDATE_APP
file = getattr(args, "file", None)
if getattr(args, "partition_table", False):
# Fail fast if the resolved ESPHome OTA config does not enable allow_partition_access.
# The device-side handshake also rejects this with "Device only supports app updates",
# but checking here surfaces the misconfiguration before opening a network connection.
check_partition_access("--partition-table")
binary = CORE.partition_table_bin
binary = Path(file) if file is not None else CORE.partition_table_bin
ota_type = espota2.OTA_TYPE_UPDATE_PARTITION_TABLE
elif getattr(args, "bootloader", False):
check_partition_access("--bootloader")
if (
getattr(args, "file", None) is None
file is None
and (native := native_backend())
and (hint := getattr(native, "missing_image_hint", lambda: None)())
):
raise EsphomeError(hint)
binary = CORE.bootloader_bin
binary = Path(file) if file is not None else CORE.bootloader_bin
ota_type = espota2.OTA_TYPE_UPDATE_BOOTLOADER
if getattr(args, "file", None) is not None:
binary = Path(args.file)
else:
binary = _app_ota_image(args)
if ota_type == espota2.OTA_TYPE_UPDATE_PARTITION_TABLE:
_validate_partition_table_binary(binary)
+27 -1
View File
@@ -33,9 +33,35 @@ _CACHE_VERSION = 1
_LAMBDA_KEY = "__esphome_lambda__"
_CACHE_SUFFIX = ".validated.json"
def compiled_config_path(config_filename: str) -> Path:
"""Path to the cached validated config alongside the storage sidecar."""
return CORE.data_dir / "storage" / f"{config_filename}.validated.json"
return CORE.data_dir / "storage" / f"{config_filename}{_CACHE_SUFFIX}"
def invalidate_compiled_config() -> None:
"""Drop every cache in the storage dir after a change to a file the
mtime check cannot see, such as secrets.yaml or an include; other
configurations may share that file."""
storage = compiled_config_path(CORE.config_filename).parent
try:
caches = [p for p in storage.iterdir() if p.name.endswith(_CACHE_SUFFIX)]
except FileNotFoundError:
return
except OSError as err:
raise EsphomeError(
f"Could not list the validated config caches in {storage}: {err}"
) from err
for path in caches:
try:
path.unlink(missing_ok=True)
except OSError as err:
# A stale cache would present the old key on the next upload
raise EsphomeError(
f"Could not remove the validated config cache {path}: {err}"
) from err
def save_compiled_config(config: ConfigType) -> None:
+1 -3
View File
@@ -297,9 +297,7 @@ void ADE7880::init_device_() {
void ADE7880::reset_device_() {
if (this->reset_pin_ != nullptr) {
ESP_LOGD(TAG, "Reset device using RESET pin");
this->reset_pin_->digital_write(false);
delay(1);
this->reset_pin_->digital_write(true);
pulse_reset(this->reset_pin_, {.low_ms = 1});
} else {
ESP_LOGD(TAG, "Reset device using SWRST command");
this->write_u16_register16_(CONFIG, CONFIG_SWRST);
+73 -73
View File
@@ -14,80 +14,80 @@ ESPHOME_LOG_TAG(TAG, "aic3204");
return; \
}
static constexpr i2c::I2CRegValue AIC3204_INIT[] PROGMEM = {
// Set register page to 0
{AIC3204_PAGE_CTRL, 0x00},
// Initiate SW reset (PLL is powered off as part of reset)
{AIC3204_SW_RST, 0x01},
// *** 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
{AIC3204_NDAC, 0x82},
// Power up MDAC and set to 2
{AIC3204_MDAC, 0x82},
// Program DOSR = 128
{AIC3204_DOSR, 0x80},
// Set Audio Interface Config: I2S, 32 bits, DOUT always driving
{AIC3204_CODEC_IF, 0x30},
// For I2S Firmware only, set SCLK/MFP3 pin as Audio Data In
{AIC3204_SCLK_MFP3, 0x02},
{AIC3204_AUDIO_IF_4, 0x01},
{AIC3204_AUDIO_IF_5, 0x01},
// Program the DAC processing block to be used - PRB_P1
{AIC3204_DAC_SIG_PROC, 0x01},
// *** Select Page 1 ***
{AIC3204_PAGE_CTRL, 0x01},
// Enable the internal AVDD_LDO:
{AIC3204_LDO_CTRL, 0x09},
// *** Program Analog Blocks ***
// Disable Internal Crude AVdd in presence of external AVdd supply or before powering up internal AVdd LDO
{AIC3204_PWR_CFG, 0x08},
// Enable Master Analog Power Control
{AIC3204_LDO_CTRL, 0x01},
// 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)
{AIC3204_CM_CTRL, 0x40},
// *** Set PowerTune Modes ***
// Set the Left & Right DAC PowerTune mode to PTM_P3/4. Use Class-AB driver.
{AIC3204_PLAY_CFG1, 0x00},
{AIC3204_PLAY_CFG2, 0x00},
// Set the REF charging time to 40ms
{AIC3204_REF_STARTUP, 0x01},
// 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.
{AIC3204_HP_START, 0x25},
// Route Left DAC to HPL
{AIC3204_HPL_ROUTE, 0x08},
// Route Right DAC to HPR
{AIC3204_HPR_ROUTE, 0x08},
// Route Left DAC to LOL
{AIC3204_LOL_ROUTE, 0x08},
// Route Right DAC to LOR
{AIC3204_LOR_ROUTE, 0x08},
// Unmute HPL and set gain to -2dB (see comment before configuring the AIC3204_CM_CTRL register)
{AIC3204_HPL_GAIN, 0x3e},
// Unmute HPR and set gain to -2dB (see comment before configuring the AIC3204_CM_CTRL register)
{AIC3204_HPR_GAIN, 0x3e},
// Unmute LOL and set gain to 0dB
{AIC3204_LOL_DRV_GAIN, 0x00},
// Unmute LOR and set gain to 0dB
{AIC3204_LOR_DRV_GAIN, 0x00},
// Power up HPL and HPR, LOL and LOR drivers
{AIC3204_OP_PWR_CTRL, 0x3C},
};
void AIC3204::setup() {
// 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");
ERROR_CHECK(this->write_registers(AIC3204_INIT), "Failed to initialize");
// Wait for 2.5 sec for soft stepping to take effect before attempting power-up
this->set_timeout(2500, [this]() {
+4 -8
View File
@@ -174,14 +174,10 @@ def _register_provisioning_source(config: ConfigType) -> ConfigType:
CONF_SUPPORTS_RESPONSE = "supports_response"
# Enum values in api::enums namespace
enums_ns = api_ns.namespace("enums")
SUPPORTS_RESPONSE_OPTIONS = {
"none": enums_ns.SUPPORTS_RESPONSE_NONE,
"optional": enums_ns.SUPPORTS_RESPONSE_OPTIONAL,
"only": enums_ns.SUPPORTS_RESPONSE_ONLY,
"status": enums_ns.SUPPORTS_RESPONSE_STATUS,
}
SupportsResponseType = api_ns.namespace("enums").enum("SupportsResponseType")
SUPPORTS_RESPONSE_OPTIONS = SupportsResponseType.options(
"none", "optional", "only", "status", prefix="SUPPORTS_RESPONSE_"
)
def _auto_detect_supports_response(config: ConfigType) -> ConfigType:
+7 -6
View File
@@ -54,7 +54,8 @@ static const char AS7341_NAME[] PROGMEM = "AS7341";
static constexpr uint8_t AS7341_ADC_SLOTS = 6;
static constexpr uint8_t AS7341_SMUX_LEN = 20;
static constexpr uint8_t AS7341_SMUX_STEPS = 2;
static const RegValue AS7341_INIT[] PROGMEM = {{0x70, 0x00}}; // CONFIG: SPM mode, INT_SEL and LED_SEL cleared
static constexpr i2c::I2CRegValue AS7341_INIT[] PROGMEM = {
{0x70, 0x00}}; // CONFIG: SPM mode, INT_SEL and LED_SEL cleared
static const uint8_t AS7341_SMUX[] PROGMEM = {
0x30, 0x01, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x50, 0x00, // step 0: F1 to F4, NIR, Clear
0x00, 0x00, 0x20, 0x04, 0x00, 0x30, 0x01, 0x50, 0x00, 0x06, // step 0, continued
@@ -150,8 +151,8 @@ static constexpr uint8_t AS7343_ADC_SLOTS = 18;
static constexpr uint8_t AS7343_SMUX_LEN = 10;
static const char TCS3448_NAME[] PROGMEM = "TCS3448";
// CFG6, FD_CFG0, CFG10, CFG8, CFG20 (auto_smux, 18 channels in 3 cycles), AGC_GAIN_MAX, FD_TIME_1, FD_TIME_2
static const RegValue AS7343_INIT[] PROGMEM = {{0xF5, 0x00}, {0xDF, 0xA1}, {0x65, 0xF2}, {0xC9, 0xC8},
{0xD6, 0x62}, {0xD7, 0x99}, {0xE0, 0x64}, {0xE2, 0x21}};
static constexpr i2c::I2CRegValue AS7343_INIT[] PROGMEM = {{0xF5, 0x00}, {0xDF, 0xA1}, {0x65, 0xF2}, {0xC9, 0xC8},
{0xD6, 0x62}, {0xD7, 0x99}, {0xE0, 0x64}, {0xE2, 0x21}};
static const uint8_t AS7343_SMUX[] PROGMEM = {0x00, 0x04, 0x65, 0x02, 0x00, 0x05, 0x00, 0x01, 0x00, 0x30,
0x00, 0x00, 0x60, 0x20, 0x04, 0x50, 0x03, 0x00, 0x01, 0x05,
0x05, 0x00, 0x60, 0x00, 0x30, 0x00, 0x40, 0x10, 0x20, 0x00};
@@ -230,7 +231,7 @@ static constexpr ChipInfo as7343_chip(const char *name) {
.gain_correction = AS7343_GAIN_CORRECTION,
.contributions = AS7343_CONTRIBUTIONS,
.tristimulus = AS7343_TRISTIMULUS,
.init_count = sizeof(AS7343_INIT) / sizeof(RegValue),
.init_count = sizeof(AS7343_INIT) / sizeof(i2c::I2CRegValue),
.astep_reg = 0xD4,
.cfg0_reg = 0xBF,
.cfg1_reg = 0xC6,
@@ -298,9 +299,9 @@ void AS734XComponent::setup() {
this->mark_failed(LOG_STR("Wrong chip ID"));
return;
}
const RegValue *init = pgm(chip->init);
const i2c::I2CRegValue *init = pgm(chip->init);
for (uint8_t i = 0, n = pgm(chip->init_count); ok && i < n; i++) {
RegValue rv;
i2c::I2CRegValue rv;
progmem_memcpy(&rv, &init[i], sizeof(rv));
ok = this->select_bank_(rv.reg) && this->write_byte(rv.reg, rv.value);
}
+1 -6
View File
@@ -68,15 +68,10 @@ struct ChannelTristimulus {
float z;
};
struct RegValue {
uint8_t reg;
uint8_t value;
};
/// Per-chip constants, stored in flash. The pointed-to tables are in flash too.
struct ChipInfo {
const char *name;
const RegValue *init; // written in order; the bank is selected per register address
const i2c::I2CRegValue *init; // written in order; the bank is selected per register address
const uint8_t *smux; // AS7341: SMUX RAM image per step; AS7343: the three chain images
const uint8_t *readout; // per step, the ADC slot of each band (0xFF: not read in this step)
const uint16_t *wavelengths; // per band, WIDEBAND_NM for clear and ABSENT_NM for a missing band
@@ -16,13 +16,7 @@ constexpr static const uint8_t AXS_READ_TOUCHPAD[11] = {0xb5, 0xab, 0xa5, 0x5a,
}
void AXS15231Touchscreen::setup() {
if (this->reset_pin_ != nullptr) {
this->reset_pin_->setup();
this->reset_pin_->digital_write(false);
delay(5);
this->reset_pin_->digital_write(true);
delay(10);
}
pulse_reset(this->reset_pin_, {.low_ms = 5, .high_after_ms = 10});
if (this->interrupt_pin_ != nullptr) {
this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT);
this->interrupt_pin_->setup();
+4
View File
@@ -78,3 +78,7 @@ async def pin_to_code(config: ConfigType) -> MockObj:
# Called by writer.py; delegates to the shared libretiny implementation.
def copy_files() -> None:
libretiny.copy_files()
# Called by the OTA upload; the shared libretiny implementation.
ota_image = libretiny.device_ota_image
@@ -1,7 +1,7 @@
#include "bme680_bsec.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include <string>
#include "esphome/core/progmem.h"
namespace esphome::bme680_bsec {
#ifdef USE_BSEC
@@ -9,7 +9,7 @@ ESPHOME_LOG_TAG(TAG, "bme680_bsec.sensor");
static constexpr uint32_t READ_TIMEOUT_ID = 0;
static const std::string IAQ_ACCURACY_STATES[4] = {"Stabilizing", "Uncertain", "Calibrating", "Calibrated"};
PROGMEM_STRING_TABLE(IaqAccuracyStrings, "Stabilizing", "Uncertain", "Calibrating", "Calibrated");
std::vector<BME680BSECComponent *>
BME680BSECComponent::instances; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
@@ -386,10 +386,11 @@ void BME680BSECComponent::publish_(const bsec_output_t *outputs, uint8_t num_out
switch (outputs[i].sensor_id) {
case BSEC_OUTPUT_IAQ:
case BSEC_OUTPUT_STATIC_IAQ: {
uint8_t accuracy = std::min<uint8_t>(outputs[i].accuracy, std::size(IAQ_ACCURACY_STATES) - 1);
uint8_t accuracy = std::min<uint8_t>(outputs[i].accuracy, IaqAccuracyStrings::LAST_INDEX);
this->queue_push_([this, signal]() { this->publish_sensor_(this->iaq_sensor_, signal); });
this->queue_push_([this, accuracy]() {
this->publish_sensor_(this->iaq_accuracy_text_sensor_, IAQ_ACCURACY_STATES[accuracy]);
this->publish_sensor_(this->iaq_accuracy_text_sensor_,
IaqAccuracyStrings::get_progmem_str(accuracy, IaqAccuracyStrings::LAST_INDEX));
});
this->queue_push_([this, accuracy]() { this->publish_sensor_(this->iaq_accuracy_sensor_, accuracy, true); });
@@ -435,8 +436,9 @@ void BME680BSECComponent::publish_sensor_(sensor::Sensor *sensor, float value, b
sensor->publish_state(value);
}
void BME680BSECComponent::publish_sensor_(text_sensor::TextSensor *sensor, const std::string &value) {
if (!sensor || (sensor->has_state() && sensor->state == value)) {
void BME680BSECComponent::publish_sensor_(text_sensor::TextSensor *sensor, ProgmemStr value) {
if (!sensor ||
(sensor->has_state() && ESPHOME_strcmp_P(sensor->state.c_str(), reinterpret_cast<ESPHOME_PGM_P>(value)) == 0)) {
return;
}
sensor->publish_state(value);
+1 -1
View File
@@ -77,7 +77,7 @@ class BME680BSECComponent final : public Component, public i2c::I2CDevice {
int64_t get_time_ns_();
void publish_sensor_(sensor::Sensor *sensor, float value, bool change_only = false);
void publish_sensor_(text_sensor::TextSensor *sensor, const std::string &value);
void publish_sensor_(text_sensor::TextSensor *sensor, ProgmemStr value);
void snapshot_state_(); // Fetch the current BSEC library state and save it in the bsec_state_data_ member (volatile
// memory)
+7 -4
View File
@@ -2,6 +2,7 @@
#include "cc1101pa.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "esphome/core/progmem.h"
#include <cmath>
#include <cstring>
@@ -14,6 +15,9 @@ namespace esphome::cc1101 {
ESPHOME_LOG_TAG(TAG, "cc1101");
static constexpr size_t TX_FIFO_SIZE = 64;
// MDMCFG2 MOD_FORMAT values
PROGMEM_STRING_TABLE(ModulationStrings, "2-FSK", "GFSK", "UNUSED", "ASK/OOK", "4-FSK", "UNUSED", "UNUSED", "MSK");
static void split_float(float value, int mbits, uint8_t &e, uint32_t &m) {
int e_tmp;
float m_tmp = std::frexp(value, &e_tmp);
@@ -238,8 +242,6 @@ void CC1101Component::loop() {
}
void CC1101Component::dump_config() {
static const char *const MODULATION_NAMES[] = {"2-FSK", "GFSK", "UNUSED", "ASK/OOK",
"4-FSK", "UNUSED", "UNUSED", "MSK"};
int32_t freq = static_cast<int32_t>(this->state_.FREQ2 << 16 | this->state_.FREQ1 << 8 | this->state_.FREQ0) *
XTAL_FREQUENCY / (1 << 16);
float symbol_rate = (((256.0f + this->state_.DRATE_M) * (1 << this->state_.DRATE_E)) / (1 << 28)) * XTAL_FREQUENCY;
@@ -253,8 +255,9 @@ void CC1101Component::dump_config() {
" Symbol Rate: %.0f baud\n"
" Filter Bandwidth: %.1f Hz\n"
" Output Power: %.1f dBm",
this->chip_id_, freq, this->state_.CHANNR, MODULATION_NAMES[this->state_.MOD_FORMAT & 0x07],
symbol_rate, bw, this->output_power_effective_);
this->chip_id_, freq, this->state_.CHANNR,
LOG_STR_ARG(ModulationStrings::get_log_str(this->state_.MOD_FORMAT & 0x07, 0)), symbol_rate, bw,
this->output_power_effective_);
LOG_PIN(" CS Pin: ", this->cs_);
}
@@ -6,12 +6,7 @@ ESPHOME_LOG_TAG(TAG, "cst226.touchscreen");
void CST226Touchscreen::setup() {
if (this->reset_pin_ != nullptr) {
this->reset_pin_->setup();
this->reset_pin_->digital_write(true);
delay(5);
this->reset_pin_->digital_write(false);
delay(5);
this->reset_pin_->digital_write(true);
pulse_reset(this->reset_pin_, {.high_before_ms = 5, .low_ms = 5});
this->set_timeout(30, [this] { this->continue_setup_(); });
} else {
this->continue_setup_();
@@ -43,9 +43,7 @@ void CST328Touchscreen::setup() {
}
void CST328Touchscreen::reset_device_() {
this->reset_pin_->digital_write(false);
delay(5);
this->reset_pin_->digital_write(true);
pulse_reset(this->reset_pin_, {.low_ms = 5});
this->set_timeout(CST328_TRANSITION_TIMEOUT, [this] { this->continue_setup_(); });
}
@@ -52,12 +52,7 @@ void CST816Touchscreen::continue_setup_() {
void CST816Touchscreen::setup() {
if (this->reset_pin_ != nullptr) {
this->reset_pin_->setup();
this->reset_pin_->digital_write(true);
delay(5);
this->reset_pin_->digital_write(false);
delay(5);
this->reset_pin_->digital_write(true);
pulse_reset(this->reset_pin_, {.high_before_ms = 5, .low_ms = 5});
this->set_timeout(30, [this] { this->continue_setup_(); });
} else {
this->continue_setup_();
@@ -10,14 +10,7 @@ static constexpr uint32_t REREAD_TIMEOUT_ID = 1;
static constexpr uint32_t REREAD_DELAY_MS = 20;
void CST9220Touchscreen::setup() {
if (this->reset_pin_ != nullptr) {
this->reset_pin_->setup();
this->reset_pin_->digital_write(true);
delay(5);
this->reset_pin_->digital_write(false);
delay(10);
this->reset_pin_->digital_write(true);
}
pulse_reset(this->reset_pin_, {.high_before_ms = 5, .low_ms = 10});
// Wait for the controller to leave its bootloader before talking to it.
this->set_timeout(30, [this] { this->continue_setup_(); });
}
@@ -42,6 +42,7 @@ CONFIG_SCHEMA = cv.All(
async def to_code(config: ConfigType) -> None:
cg.add_define("USE_LINE_READER")
await uart.new_uart_component(config)
@@ -11,10 +11,10 @@
namespace esphome::dfrobot_sen0395 {
ESPHOME_LOG_TAG(TAG, "dfrobot_sen0395");
static constexpr char ASCII_CR = 0x0D;
static constexpr char ASCII_LF = 0x0A;
static constexpr float DET_RANGE_STEP_M = 0.15f;
static constexpr float LATENCY_STEP_S = 0.025f;
// Sent after a reset with no line end
static constexpr char PROMPT[] PROGMEM = "leapMMW:/>";
// What each CommandType cannot do while the sensor runs; empty for commands that do not need it stopped
PROGMEM_STRING_TABLE(BusyActions, "", "configure range config", "configure output latency",
@@ -121,9 +121,9 @@ bool DfrobotSen0395Component::run_command_(const Command &cmd, uint32_t now) {
return false; // Command not done yet
}
if (this->read_message_()) {
if (ESPHOME_strstr_P(this->read_buffer_, ESPHOME_PSTR("is not recognized as a CLI command")) == nullptr) {
if (!this->handle_reply_(cmd))
if (const char *message = this->read_message_()) {
if (ESPHOME_strstr_P(message, ESPHOME_PSTR("is not recognized as a CLI command")) == nullptr) {
if (!this->handle_reply_(cmd, message))
return false; // Command not done yet
this->read_message_(); // Skip the prompt
return true;
@@ -146,12 +146,13 @@ bool DfrobotSen0395Component::run_command_(const Command &cmd, uint32_t now) {
}
void DfrobotSen0395Component::read_state_() {
if (!this->read_message_())
const char *message = this->read_message_();
if (message == nullptr)
return;
if (ESPHOME_strstr_P(this->read_buffer_, ESPHOME_PSTR("$JYBSS,0, , , *")) != nullptr) {
if (ESPHOME_strstr_P(message, ESPHOME_PSTR("$JYBSS,0, , , *")) != nullptr) {
this->set_detected_(false);
this->set_active(true);
} else if (ESPHOME_strstr_P(this->read_buffer_, ESPHOME_PSTR("$JYBSS,1, , , *")) != nullptr) {
} else if (ESPHOME_strstr_P(message, ESPHOME_PSTR("$JYBSS,1, , , *")) != nullptr) {
this->set_detected_(true);
this->set_active(true);
}
@@ -206,8 +207,7 @@ void DfrobotSen0395Component::format_command(const Command &cmd, char *buf, size
}
}
bool DfrobotSen0395Component::handle_reply_(const Command &cmd) {
const char *message = this->read_buffer_;
bool DfrobotSen0395Component::handle_reply_(const Command &cmd, const char *message) {
auto is = [message](ESPHOME_PGM_P text) { return ESPHOME_strcmp_P(message, text) == 0; };
// Commands with their own replies
@@ -230,7 +230,7 @@ bool DfrobotSen0395Component::handle_reply_(const Command &cmd) {
}
return true;
case CommandType::COMMAND_TYPE_RESET_SYSTEM:
if (!is(ESPHOME_PSTR("leapMMW:/>")))
if (!is(PROMPT))
return false;
ESP_LOGI(TAG, "Restarted sensor.");
return true;
@@ -300,34 +300,24 @@ bool DfrobotSen0395Component::handle_reply_(const Command &cmd) {
return true;
}
bool DfrobotSen0395Component::read_message_() {
const char *DfrobotSen0395Component::read_message_() {
while (this->available()) {
uint8_t byte;
this->read_byte(&byte);
if (this->read_pos_ == MMWAVE_READ_BUFFER_LENGTH)
this->read_pos_ = 0;
ESP_LOGVV(TAG, "Buffer pos: %u %d", this->read_pos_, byte);
if (byte == ASCII_CR)
continue;
if (byte >= 0x7F)
byte = '?'; // needs to be valid utf8 string for log functions.
this->read_buffer_[this->read_pos_] = byte;
if (this->read_pos_ == 9 && byte == '>')
this->read_buffer_[++this->read_pos_] = ASCII_LF;
if (this->read_buffer_[this->read_pos_] == ASCII_LF) {
this->read_buffer_[this->read_pos_] = 0;
this->read_pos_ = 0;
ESP_LOGV(TAG, "Message: %s", this->read_buffer_);
return true; // Full message in buffer
const char *line = this->line_reader_.feed(byte);
if (line == nullptr && byte == '>' && this->line_reader_.length() == sizeof(PROMPT) - 1) {
line = this->line_reader_.partial();
this->line_reader_.reset();
}
this->read_pos_++;
if (line != nullptr) {
ESP_LOGV(TAG, "Message: %s", line);
return line;
}
this->line_reader_.log_if_overflowed(TAG);
}
return false; // No full message yet
return nullptr;
}
void DfrobotSen0395Component::set_detected_(bool detected) {
@@ -4,6 +4,7 @@
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include "esphome/core/line_reader.h"
#ifdef USE_BINARY_SENSOR
#include "esphome/components/binary_sensor/binary_sensor.h"
@@ -14,7 +15,7 @@
namespace esphome::dfrobot_sen0395 {
static constexpr uint8_t MMWAVE_READ_BUFFER_LENGTH = 255;
static constexpr uint8_t MMWAVE_READ_BUFFER_LENGTH = 253; // largest LineReader size with a one byte position
static constexpr uint8_t COMMAND_QUEUE_SIZE = 20;
// Keep in sync with the BusyActions table in dfrobot_sen0395.cpp
@@ -121,8 +122,7 @@ class DfrobotSen0395Component final : public uart::UARTDevice, public Component
bool led_active_{false};
bool uart_presence_active_{false};
bool start_after_boot_{false};
char read_buffer_[MMWAVE_READ_BUFFER_LENGTH];
size_t read_pos_{0};
LineReader<MMWAVE_READ_BUFFER_LENGTH> line_reader_;
bool cmd_sent_{false};
uint8_t retries_left_{2};
StaticRingBuffer<Command, COMMAND_QUEUE_SIZE> cmd_queue_;
@@ -131,12 +131,13 @@ class DfrobotSen0395Component final : public uart::UARTDevice, public Component
int16_t det_range_steps_[8]{};
uint16_t latency_steps_[2]{};
bool read_message_();
/// The next complete line, valid until the next call, or nullptr
const char *read_message_();
void read_state_();
/// Run the command at the front of the queue; true when it is done
bool run_command_(const Command &cmd, uint32_t now);
/// Handle a reply to a sent command; true when the command is done
bool handle_reply_(const Command &cmd);
bool handle_reply_(const Command &cmd, const char *message);
void set_detected_(bool detected);
};
@@ -1,5 +1,6 @@
#include "ds248x_one_wire_bus.h"
#include "ds248x.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome::ds248x {
@@ -151,11 +152,7 @@ uint64_t DS248xOneWireBus::search_int() {
}
// Update address based on direction taken
if (dir_taken) {
address |= bit_mask;
} else {
address &= ~bit_mask;
}
assign_bits(address, bit_mask, dir_taken);
}
// Search successful
+4 -6
View File
@@ -18,13 +18,11 @@ E131AddressableLightEffect = e131_ns.class_(
)
E131Component = e131_ns.class_("E131Component", cg.Component)
METHODS = {"UNICAST": e131_ns.E131_UNICAST, "MULTICAST": e131_ns.E131_MULTICAST}
E131ListenMethod = e131_ns.enum("E131ListenMethod")
METHODS = E131ListenMethod.options("UNICAST", "MULTICAST", prefix="E131_")
CHANNELS = {
"MONO": e131_ns.E131_MONO,
"RGB": e131_ns.E131_RGB,
"RGBW": e131_ns.E131_RGBW,
}
E131LightChannels = e131_ns.enum("E131LightChannels")
CHANNELS = E131LightChannels.options("MONO", "RGB", "RGBW", prefix="E131_")
CONF_UNIVERSE = "universe"
CONF_E131_ID = "e131_id"
@@ -20,9 +20,7 @@ void EKTF2232Touchscreen::setup() {
this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_FALLING_EDGE);
this->reset_pin_->setup();
this->hard_reset_();
pulse_reset(this->reset_pin_, {.low_ms = 15, .high_after_ms = 15});
if (!this->soft_reset_()) {
ESP_LOGE(TAG, "Failed to soft reset EKT2232!");
this->interrupt_pin_->detach_interrupt();
@@ -96,13 +94,6 @@ bool EKTF2232Touchscreen::get_power_state() {
return (received[1] >> 3) & 1;
}
void EKTF2232Touchscreen::hard_reset_() {
this->reset_pin_->digital_write(false);
delay(15);
this->reset_pin_->digital_write(true);
delay(15);
}
bool EKTF2232Touchscreen::soft_reset_() {
auto err = this->write(SOFT_RESET_CMD, 4);
if (err != i2c::ERROR_OK)
@@ -22,7 +22,6 @@ class EKTF2232Touchscreen final : public Touchscreen, public i2c::I2CDevice {
bool get_power_state();
protected:
void hard_reset_();
bool soft_reset_();
void update_touches() override;
+12 -15
View File
@@ -4,21 +4,18 @@
#include "esphome/core/application.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "esphome/core/progmem.h"
namespace esphome::epaper_spi {
ESPHOME_LOG_TAG(TAG, "epaper_spi");
static constexpr size_t EPAPER_MAX_CMD_LOG_BYTES = 128;
static constexpr const char *const EPAPER_STATE_STRINGS[] = {
"IDLE", "UPDATE", "RESET", "RESET_END", "SHOULD_WAIT", "INITIALISE",
"TRANSFER_DATA", "POWER_ON", "REFRESH_SCREEN", "POWER_OFF", "DEEP_SLEEP",
};
PROGMEM_STRING_TABLE(EPaperStateStrings, "IDLE", "UPDATE", "RESET", "RESET_END", "SHOULD_WAIT", "INITIALISE",
"TRANSFER_DATA", "POWER_ON", "REFRESH_SCREEN", "POWER_OFF", "DEEP_SLEEP", "Unknown");
const char *EPaperBase::epaper_state_to_string_() {
if (auto idx = static_cast<unsigned>(this->state_); idx < std::size(EPAPER_STATE_STRINGS))
return EPAPER_STATE_STRINGS[idx];
return "Unknown";
const LogString *EPaperBase::epaper_state_to_string_() {
return EPaperStateStrings::get_log_str(static_cast<uint8_t>(this->state_), EPaperStateStrings::LAST_INDEX);
}
void EPaperBase::setup() {
@@ -159,7 +156,7 @@ void EPaperBase::update_effective_transform_() {
void EPaperBase::update() {
if (this->state_ != EPaperState::IDLE) {
ESP_LOGE(TAG, "Display already in state %s", epaper_state_to_string_());
ESP_LOGE(TAG, "Display already in state %s", LOG_STR_ARG(epaper_state_to_string_()));
return;
}
this->set_state_(EPaperState::UPDATE);
@@ -212,7 +209,7 @@ void EPaperBase::loop() {
this->high_frequency_loop_.stop();
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
if (now - this->waiting_for_idle_last_print_ >= 1000) {
ESP_LOGV(TAG, "Waiting for idle in state %s", this->epaper_state_to_string_());
ESP_LOGV(TAG, "Waiting for idle in state %s", LOG_STR_ARG(this->epaper_state_to_string_()));
this->waiting_for_idle_last_print_ = millis();
}
#endif
@@ -227,7 +224,7 @@ void EPaperBase::loop() {
// The busy pin never cleared: give up on this update so later ones are not refused, and make the next one full
void EPaperBase::abandon_update_() {
ESP_LOGE(TAG, "Display stayed busy for %u s in state %s; check the busy pin", (unsigned) this->busy_timeout_seconds_,
this->epaper_state_to_string_());
LOG_STR_ARG(this->epaper_state_to_string_()));
this->status_set_warning(LOG_STR("Busy pin timeout"));
if (this->state_ == EPaperState::TRANSFER_DATA)
this->abort_transfer();
@@ -247,10 +244,10 @@ void EPaperBase::abandon_update_() {
* Should a subclassed class need to override this, the method will need to be made virtual.
*/
void EPaperBase::process_state_() {
ESP_LOGV(TAG, "Process state entered in state %s", epaper_state_to_string_());
ESP_LOGV(TAG, "Process state entered in state %s", LOG_STR_ARG(epaper_state_to_string_()));
switch (this->state_) {
default:
ESP_LOGE(TAG, "Display is in unhandled state %s", epaper_state_to_string_());
ESP_LOGE(TAG, "Display is in unhandled state %s", LOG_STR_ARG(epaper_state_to_string_()));
this->set_state_(EPaperState::IDLE);
break;
case EPaperState::IDLE:
@@ -342,7 +339,7 @@ void EPaperBase::process_state_() {
}
void EPaperBase::set_state_(EPaperState state, uint16_t delay) {
ESP_LOGV(TAG, "Exit state %s", this->epaper_state_to_string_());
ESP_LOGV(TAG, "Exit state %s", LOG_STR_ARG(this->epaper_state_to_string_()));
this->state_ = state;
// allow subclasses to nominate delays
if (delay == 0)
@@ -350,7 +347,7 @@ void EPaperBase::set_state_(EPaperState state, uint16_t delay) {
this->next_delay_ = 0;
this->delay_until_ = millis() + delay;
this->wait_for_idle_(state > EPaperState::SHOULD_WAIT);
ESP_LOGV(TAG, "Enter state %s, delay %u, wait_for_idle=%s", this->epaper_state_to_string_(), delay,
ESP_LOGV(TAG, "Enter state %s, delay %u, wait_for_idle=%s", LOG_STR_ARG(this->epaper_state_to_string_()), delay,
TRUEFALSE(this->waiting_for_idle_));
// Each step of an update otherwise waits for the next loop iteration; loop() slows down again for long waits
if (state == EPaperState::IDLE) {
+1 -1
View File
@@ -125,7 +125,7 @@ class EPaperBase : public Display,
bool is_using_partial_update_() const { return this->full_update_every_ > 1; }
void process_state_();
const char *epaper_state_to_string_();
const LogString *epaper_state_to_string_();
bool is_idle_() const;
void setup_pins_() const;
virtual bool reset();
@@ -72,9 +72,7 @@ bool EPaperJD79660::reset() {
// As commented on SLEEP_MS_RESET1: Reset pulse must happen within time window.
// So do not use FSM loop, and avoid other calls/logs during pulse below.
this->reset_pin_->digital_write(false);
delay(SLEEP_MS_RESET1);
this->reset_pin_->digital_write(true);
pulse_reset(this->reset_pin_, {.low_ms = SLEEP_MS_RESET1});
this->reset_duration_ = SLEEP_MS_RESET2;
this->step_ = FSMState::RESET_STEP2_IDLECHECK;
+42 -36
View File
@@ -33,53 +33,59 @@ void ES7210::dump_config() {
this->channel_gains_[2], this->channel_gains_[3]);
}
static constexpr i2c::I2CRegValue ES7210_RESET[] PROGMEM = {
// Software reset
{ES7210_RESET_REG00, 0xff},
{ES7210_RESET_REG00, 0x32},
{ES7210_CLOCK_OFF_REG01, 0x3f},
// Set initialization time when device powers up
{ES7210_TIME_CONTROL0_REG09, 0x30},
{ES7210_TIME_CONTROL1_REG0A, 0x30},
// Configure HFP for all ADC channels
{ES7210_ADC12_HPF2_REG23, 0x2a},
{ES7210_ADC12_HPF1_REG22, 0x0a},
{ES7210_ADC34_HPF2_REG20, 0x0a},
{ES7210_ADC34_HPF1_REG21, 0x2a},
};
static constexpr i2c::I2CRegValue ES7210_ANALOG[] PROGMEM = {
// Configure analog power
{ES7210_ANALOG_REG40, 0xC3},
// Set mic bias
{ES7210_MIC12_BIAS_REG41, 0x70},
{ES7210_MIC34_BIAS_REG42, 0x70},
};
static constexpr i2c::I2CRegValue ES7210_POWER_ON[] PROGMEM = {
// Power on mics 1 through 4
{ES7210_MIC1_POWER_REG47, 0x08},
{ES7210_MIC2_POWER_REG48, 0x08},
{ES7210_MIC3_POWER_REG49, 0x08},
{ES7210_MIC4_POWER_REG4A, 0x08},
// Power down DLL
{ES7210_POWER_DOWN_REG06, 0x04},
// Power on MIC1-4 bias & ADC1-4 & PGA1-4 Power
{ES7210_MIC12_POWER_REG4B, 0x0F},
{ES7210_MIC34_POWER_REG4C, 0x0F},
// Enable device
{ES7210_RESET_REG00, 0x71},
{ES7210_RESET_REG00, 0x41},
};
void ES7210::setup() {
// Software reset
ES7210_ERROR_FAILED(this->write_byte(ES7210_RESET_REG00, 0xff));
ES7210_ERROR_FAILED(this->write_byte(ES7210_RESET_REG00, 0x32));
ES7210_ERROR_FAILED(this->write_byte(ES7210_CLOCK_OFF_REG01, 0x3f));
// Set initialization time when device powers up
ES7210_ERROR_FAILED(this->write_byte(ES7210_TIME_CONTROL0_REG09, 0x30));
ES7210_ERROR_FAILED(this->write_byte(ES7210_TIME_CONTROL1_REG0A, 0x30));
// Configure HFP for all ADC channels
ES7210_ERROR_FAILED(this->write_byte(ES7210_ADC12_HPF2_REG23, 0x2a));
ES7210_ERROR_FAILED(this->write_byte(ES7210_ADC12_HPF1_REG22, 0x0a));
ES7210_ERROR_FAILED(this->write_byte(ES7210_ADC34_HPF2_REG20, 0x0a));
ES7210_ERROR_FAILED(this->write_byte(ES7210_ADC34_HPF1_REG21, 0x2a));
ES7210_ERROR_FAILED(this->write_registers(ES7210_RESET));
// Secondary I2S mode settings
ES7210_ERROR_FAILED(this->update_register(ES7210_MODE_CONFIG_REG08, 0x01, 0x00));
// Configure analog power
ES7210_ERROR_FAILED(this->write_byte(ES7210_ANALOG_REG40, 0xC3));
// Set mic bias
ES7210_ERROR_FAILED(this->write_byte(ES7210_MIC12_BIAS_REG41, 0x70));
ES7210_ERROR_FAILED(this->write_byte(ES7210_MIC34_BIAS_REG42, 0x70));
ES7210_ERROR_FAILED(this->write_registers(ES7210_ANALOG));
// Configure I2S settings, sample rate, and microphone gains
ES7210_ERROR_FAILED(this->configure_i2s_format_());
ES7210_ERROR_FAILED(this->configure_sample_rate_());
ES7210_ERROR_FAILED(this->configure_mic_gain_());
// Power on mics 1 through 4
ES7210_ERROR_FAILED(this->write_byte(ES7210_MIC1_POWER_REG47, 0x08));
ES7210_ERROR_FAILED(this->write_byte(ES7210_MIC2_POWER_REG48, 0x08));
ES7210_ERROR_FAILED(this->write_byte(ES7210_MIC3_POWER_REG49, 0x08));
ES7210_ERROR_FAILED(this->write_byte(ES7210_MIC4_POWER_REG4A, 0x08));
// Power down DLL
ES7210_ERROR_FAILED(this->write_byte(ES7210_POWER_DOWN_REG06, 0x04));
// Power on MIC1-4 bias & ADC1-4 & PGA1-4 Power
ES7210_ERROR_FAILED(this->write_byte(ES7210_MIC12_POWER_REG4B, 0x0F));
ES7210_ERROR_FAILED(this->write_byte(ES7210_MIC34_POWER_REG4C, 0x0F));
// Enable device
ES7210_ERROR_FAILED(this->write_byte(ES7210_RESET_REG00, 0x71));
ES7210_ERROR_FAILED(this->write_byte(ES7210_RESET_REG00, 0x41));
ES7210_ERROR_FAILED(this->write_registers(ES7210_POWER_ON));
this->setup_complete_ = true;
}
+54 -48
View File
@@ -25,56 +25,62 @@ ESPHOME_LOG_TAG(TAG, "es7243e");
void ES7243E::dump_config() { ESP_LOGCONFIG(TAG, "ES7243E audio ADC:"); }
// clang-format off
static constexpr i2c::I2CRegValue ES7243E_INIT[] PROGMEM = {
{ES7243E_CLOCK_MGR_REG01, 0x3A},
{ES7243E_RESET_REG00, 0x80},
{ES7243E_TEST_MODE_REGF9, 0x00},
{ES7243E_CLOCK_MGR_REG04, 0x02},
{ES7243E_CLOCK_MGR_REG04, 0x01},
{ES7243E_TEST_MODE_REGF9, 0x01},
{ES7243E_RESET_REG00, 0x1E},
{ES7243E_CLOCK_MGR_REG01, 0x00},
{ES7243E_CLOCK_MGR_REG02, 0x00},
{ES7243E_CLOCK_MGR_REG03, 0x20},
{ES7243E_CLOCK_MGR_REG04, 0x01},
{ES7243E_ADC_CTRL_REG0D, 0x00},
{ES7243E_CLOCK_MGR_REG05, 0x00},
{ES7243E_CLOCK_MGR_REG06, 0x03}, // SCLK=MCLK/4
{ES7243E_CLOCK_MGR_REG07, 0x00}, // LRCK=MCLK/256
{ES7243E_CLOCK_MGR_REG08, 0xFF}, // LRCK=MCLK/256
{ES7243E_CLOCK_MGR_REG09, 0xCA},
{ES7243E_SDP_REG0A, 0x85},
{ES7243E_SDP_REG0B, 0x00},
{ES7243E_ADC_CTRL_REG0E, 0xBF},
{ES7243E_ADC_CTRL_REG0F, 0x80},
{ES7243E_ADC_CTRL_REG14, 0x0C},
{ES7243E_ADC_CTRL_REG15, 0x0C},
{ES7243E_ANALOG_REG17, 0x02},
{ES7243E_ANALOG_REG18, 0x26},
{ES7243E_ANALOG_REG19, 0x77},
{ES7243E_ANALOG_REG1A, 0xF4},
{ES7243E_ANALOG_REG1B, 0x66},
{ES7243E_ANALOG_REG1C, 0x44},
{ES7243E_ANALOG_REG1E, 0x00},
{ES7243E_ANALOG_REG1F, 0x0C},
{ES7243E_ANALOG_REG20, 0x1A}, // PGA gain +30dB
{ES7243E_ANALOG_REG21, 0x1A},
{ES7243E_RESET_REG00, 0x80},
{ES7243E_CLOCK_MGR_REG01, 0x3A},
{ES7243E_ANALOG_REG16, 0x3F},
{ES7243E_ANALOG_REG16, 0x00},
{ES7243E_TEST_MODE_REGF9, 0x00},
{ES7243E_CLOCK_MGR_REG04, 0x01},
{ES7243E_ANALOG_REG17, 0x01},
};
// clang-format on
static constexpr i2c::I2CRegValue ES7243E_START[] PROGMEM = {
{ES7243E_RESET_REG00, 0x80},
{ES7243E_CLOCK_MGR_REG01, 0x3A},
{ES7243E_ANALOG_REG16, 0x3F},
{ES7243E_ANALOG_REG16, 0x00},
};
void ES7243E::setup() {
ES7243E_ERROR_FAILED(this->write_byte(ES7243E_CLOCK_MGR_REG01, 0x3A));
ES7243E_ERROR_FAILED(this->write_byte(ES7243E_RESET_REG00, 0x80));
ES7243E_ERROR_FAILED(this->write_byte(ES7243E_TEST_MODE_REGF9, 0x00));
ES7243E_ERROR_FAILED(this->write_byte(ES7243E_CLOCK_MGR_REG04, 0x02));
ES7243E_ERROR_FAILED(this->write_byte(ES7243E_CLOCK_MGR_REG04, 0x01));
ES7243E_ERROR_FAILED(this->write_byte(ES7243E_TEST_MODE_REGF9, 0x01));
ES7243E_ERROR_FAILED(this->write_byte(ES7243E_RESET_REG00, 0x1E));
ES7243E_ERROR_FAILED(this->write_byte(ES7243E_CLOCK_MGR_REG01, 0x00));
ES7243E_ERROR_FAILED(this->write_byte(ES7243E_CLOCK_MGR_REG02, 0x00));
ES7243E_ERROR_FAILED(this->write_byte(ES7243E_CLOCK_MGR_REG03, 0x20));
ES7243E_ERROR_FAILED(this->write_byte(ES7243E_CLOCK_MGR_REG04, 0x01));
ES7243E_ERROR_FAILED(this->write_byte(ES7243E_ADC_CTRL_REG0D, 0x00));
ES7243E_ERROR_FAILED(this->write_byte(ES7243E_CLOCK_MGR_REG05, 0x00));
ES7243E_ERROR_FAILED(this->write_byte(ES7243E_CLOCK_MGR_REG06, 0x03)); // SCLK=MCLK/4
ES7243E_ERROR_FAILED(this->write_byte(ES7243E_CLOCK_MGR_REG07, 0x00)); // LRCK=MCLK/256
ES7243E_ERROR_FAILED(this->write_byte(ES7243E_CLOCK_MGR_REG08, 0xFF)); // LRCK=MCLK/256
ES7243E_ERROR_FAILED(this->write_byte(ES7243E_CLOCK_MGR_REG09, 0xCA));
ES7243E_ERROR_FAILED(this->write_byte(ES7243E_SDP_REG0A, 0x85));
ES7243E_ERROR_FAILED(this->write_byte(ES7243E_SDP_REG0B, 0x00));
ES7243E_ERROR_FAILED(this->write_byte(ES7243E_ADC_CTRL_REG0E, 0xBF));
ES7243E_ERROR_FAILED(this->write_byte(ES7243E_ADC_CTRL_REG0F, 0x80));
ES7243E_ERROR_FAILED(this->write_byte(ES7243E_ADC_CTRL_REG14, 0x0C));
ES7243E_ERROR_FAILED(this->write_byte(ES7243E_ADC_CTRL_REG15, 0x0C));
ES7243E_ERROR_FAILED(this->write_byte(ES7243E_ANALOG_REG17, 0x02));
ES7243E_ERROR_FAILED(this->write_byte(ES7243E_ANALOG_REG18, 0x26));
ES7243E_ERROR_FAILED(this->write_byte(ES7243E_ANALOG_REG19, 0x77));
ES7243E_ERROR_FAILED(this->write_byte(ES7243E_ANALOG_REG1A, 0xF4));
ES7243E_ERROR_FAILED(this->write_byte(ES7243E_ANALOG_REG1B, 0x66));
ES7243E_ERROR_FAILED(this->write_byte(ES7243E_ANALOG_REG1C, 0x44));
ES7243E_ERROR_FAILED(this->write_byte(ES7243E_ANALOG_REG1E, 0x00));
ES7243E_ERROR_FAILED(this->write_byte(ES7243E_ANALOG_REG1F, 0x0C));
ES7243E_ERROR_FAILED(this->write_byte(ES7243E_ANALOG_REG20, 0x1A)); // PGA gain +30dB
ES7243E_ERROR_FAILED(this->write_byte(ES7243E_ANALOG_REG21, 0x1A));
ES7243E_ERROR_FAILED(this->write_byte(ES7243E_RESET_REG00, 0x80));
ES7243E_ERROR_FAILED(this->write_byte(ES7243E_CLOCK_MGR_REG01, 0x3A));
ES7243E_ERROR_FAILED(this->write_byte(ES7243E_ANALOG_REG16, 0x3F));
ES7243E_ERROR_FAILED(this->write_byte(ES7243E_ANALOG_REG16, 0x00));
ES7243E_ERROR_FAILED(this->write_byte(ES7243E_TEST_MODE_REGF9, 0x00));
ES7243E_ERROR_FAILED(this->write_byte(ES7243E_CLOCK_MGR_REG04, 0x01));
ES7243E_ERROR_FAILED(this->write_byte(ES7243E_ANALOG_REG17, 0x01));
ES7243E_ERROR_FAILED(this->write_registers(ES7243E_INIT));
ES7243E_ERROR_FAILED(this->configure_mic_gain_());
ES7243E_ERROR_FAILED(this->write_byte(ES7243E_RESET_REG00, 0x80));
ES7243E_ERROR_FAILED(this->write_byte(ES7243E_CLOCK_MGR_REG01, 0x3A));
ES7243E_ERROR_FAILED(this->write_byte(ES7243E_ANALOG_REG16, 0x3F));
ES7243E_ERROR_FAILED(this->write_byte(ES7243E_ANALOG_REG16, 0x00));
ES7243E_ERROR_FAILED(this->write_registers(ES7243E_START));
this->setup_complete_ = true;
}
+46 -62
View File
@@ -9,70 +9,54 @@ namespace esphome::es8156 {
ESPHOME_LOG_TAG(TAG, "es8156");
// Mark the component as failed; use only in setup
#define ES8156_ERROR_FAILED(func) \
if (!(func)) { \
this->mark_failed(LOG_STR("Failed to initialize")); \
return; \
}
static constexpr i2c::I2CRegValue ES8156_INIT[] PROGMEM = {
// REG02 MODE CONFIG 1: Enable software mode for I2C control of volume/mute
// Bit 2: SOFT_MODE_SEL=1 (software mode enabled)
{ES8156_REG02_SCLK_MODE, 0x04},
// Analog system configuration (active-low power down bits, active-high enables)
// REG20 ANALOG SYSTEM: Configure analog signal path
{ES8156_REG20_ANALOG_SYS1, 0x2A},
// REG21 ANALOG SYSTEM: VSEL=0x1C (bias level ~120%), normal VREF ramp speed
{ES8156_REG21_ANALOG_SYS2, 0x3C},
// REG22 ANALOG SYSTEM: Line out mode (HPSW=0), OUT_MUTE=0 (not muted)
{ES8156_REG22_ANALOG_SYS3, 0x00},
// REG24 ANALOG SYSTEM: Low power mode for VREFBUF, HPCOM, DACVRP; DAC normal power
// Bits 2:0 = 0x07: LPVREFBUF=1, LPHPCOM=1, LPDACVRP=1, LPDAC=0
{ES8156_REG24_ANALOG_LP, 0x07},
// REG23 ANALOG SYSTEM: Lowest bias (IBIAS_SW=0), VMIDLVL=VDDA/2, normal impedance
{ES8156_REG23_ANALOG_SYS4, 0x00},
// Timing and interface configuration
// REG0A/0B TIME CONTROL: Fast state machine transitions
{ES8156_REG0A_TIME_CONTROL1, 0x01},
{ES8156_REG0B_TIME_CONTROL2, 0x01},
// REG11 SDP INTERFACE CONFIG: Default I2S format (24-bit, I2S mode)
{ES8156_REG11_DAC_SDP, 0x00},
// REG19 EQ CONTROL 1: EQ disabled (EQ_ON=0), EQ_BAND_NUM=2
{ES8156_REG19_EQ_CONTROL1, 0x20},
// REG0D P2S CONTROL: Parallel-to-serial converter settings
{ES8156_REG0D_P2S_CONTROL, 0x14},
// REG09 MISC CONTROL 2: Default settings
{ES8156_REG09_MISC_CONTROL2, 0x00},
// REG18 MISC CONTROL 3: Stereo channel routing, no inversion
// Bits 5:4 CHN_CROSS: 0=L→L/R→R, 1=L to both, 2=R to both, 3=swap L/R
// Bits 3:2: LCH_INV/RCH_INV channel inversion
{ES8156_REG18_MISC_CONTROL3, 0x00},
// REG08 CLOCK OFF: Enable all internal clocks (0x3F = all clock gates open)
{ES8156_REG08_CLOCK_ON_OFF, 0x3F},
// REG00 RESET CONTROL: Reset sequence
// First: RST_DIG=1 (assert digital reset)
{ES8156_REG00_RESET, 0x02},
// Then: CSM_ON=1 (enable chip state machine), RST_DIG=1
{ES8156_REG00_RESET, 0x03},
// REG25 ANALOG SYSTEM: Power up analog blocks
// VMIDSEL=2 (normal VMID operation), PDN_ANA=0, ENREFR=0, ENHPCOM=0
// PDN_DACVREFGEN=0, PDN_VREFBUF=0, PDN_DAC=0 (all enabled)
{ES8156_REG25_ANALOG_SYS5, 0x20},
};
void ES8156::setup() {
// REG02 MODE CONFIG 1: Enable software mode for I2C control of volume/mute
// Bit 2: SOFT_MODE_SEL=1 (software mode enabled)
ES8156_ERROR_FAILED(this->write_byte(ES8156_REG02_SCLK_MODE, 0x04));
// Analog system configuration (active-low power down bits, active-high enables)
// REG20 ANALOG SYSTEM: Configure analog signal path
ES8156_ERROR_FAILED(this->write_byte(ES8156_REG20_ANALOG_SYS1, 0x2A));
// REG21 ANALOG SYSTEM: VSEL=0x1C (bias level ~120%), normal VREF ramp speed
ES8156_ERROR_FAILED(this->write_byte(ES8156_REG21_ANALOG_SYS2, 0x3C));
// REG22 ANALOG SYSTEM: Line out mode (HPSW=0), OUT_MUTE=0 (not muted)
ES8156_ERROR_FAILED(this->write_byte(ES8156_REG22_ANALOG_SYS3, 0x00));
// REG24 ANALOG SYSTEM: Low power mode for VREFBUF, HPCOM, DACVRP; DAC normal power
// Bits 2:0 = 0x07: LPVREFBUF=1, LPHPCOM=1, LPDACVRP=1, LPDAC=0
ES8156_ERROR_FAILED(this->write_byte(ES8156_REG24_ANALOG_LP, 0x07));
// REG23 ANALOG SYSTEM: Lowest bias (IBIAS_SW=0), VMIDLVL=VDDA/2, normal impedance
ES8156_ERROR_FAILED(this->write_byte(ES8156_REG23_ANALOG_SYS4, 0x00));
// Timing and interface configuration
// REG0A/0B TIME CONTROL: Fast state machine transitions
ES8156_ERROR_FAILED(this->write_byte(ES8156_REG0A_TIME_CONTROL1, 0x01));
ES8156_ERROR_FAILED(this->write_byte(ES8156_REG0B_TIME_CONTROL2, 0x01));
// REG11 SDP INTERFACE CONFIG: Default I2S format (24-bit, I2S mode)
ES8156_ERROR_FAILED(this->write_byte(ES8156_REG11_DAC_SDP, 0x00));
// REG19 EQ CONTROL 1: EQ disabled (EQ_ON=0), EQ_BAND_NUM=2
ES8156_ERROR_FAILED(this->write_byte(ES8156_REG19_EQ_CONTROL1, 0x20));
// REG0D P2S CONTROL: Parallel-to-serial converter settings
ES8156_ERROR_FAILED(this->write_byte(ES8156_REG0D_P2S_CONTROL, 0x14));
// REG09 MISC CONTROL 2: Default settings
ES8156_ERROR_FAILED(this->write_byte(ES8156_REG09_MISC_CONTROL2, 0x00));
// REG18 MISC CONTROL 3: Stereo channel routing, no inversion
// Bits 5:4 CHN_CROSS: 0=L→L/R→R, 1=L to both, 2=R to both, 3=swap L/R
// Bits 3:2: LCH_INV/RCH_INV channel inversion
ES8156_ERROR_FAILED(this->write_byte(ES8156_REG18_MISC_CONTROL3, 0x00));
// REG08 CLOCK OFF: Enable all internal clocks (0x3F = all clock gates open)
ES8156_ERROR_FAILED(this->write_byte(ES8156_REG08_CLOCK_ON_OFF, 0x3F));
// REG00 RESET CONTROL: Reset sequence
// First: RST_DIG=1 (assert digital reset)
ES8156_ERROR_FAILED(this->write_byte(ES8156_REG00_RESET, 0x02));
// Then: CSM_ON=1 (enable chip state machine), RST_DIG=1
ES8156_ERROR_FAILED(this->write_byte(ES8156_REG00_RESET, 0x03));
// REG25 ANALOG SYSTEM: Power up analog blocks
// VMIDSEL=2 (normal VMID operation), PDN_ANA=0, ENREFR=0, ENHPCOM=0
// PDN_DACVREFGEN=0, PDN_VREFBUF=0, PDN_DAC=0 (all enabled)
ES8156_ERROR_FAILED(this->write_byte(ES8156_REG25_ANALOG_SYS5, 0x20));
if (!this->write_registers(ES8156_INIT))
this->mark_failed(LOG_STR("Failed to initialize"));
}
void ES8156::dump_config() { ESP_LOGCONFIG(TAG, "ES8156 Audio Codec:"); }
+69 -78
View File
@@ -22,79 +22,76 @@ ESPHOME_LOG_TAG(TAG, "es8388");
return false; \
}
static constexpr i2c::I2CRegValue ES8388_INIT[] PROGMEM = {
// I2S worker mode
{ES8388_MASTERMODE, 0x00},
/* Chip Control and Power Management */
{ES8388_CONTROL2, 0x50},
// normal all and power up all
{ES8388_CHIPPOWER, 0x00},
// vmidsel/500k
// EnRef=0,Play&Record Mode,(0x17-both of mic&play)
{ES8388_CONTROL1, 0x12},
// i2s 16 bits
{ES8388_DACCONTROL1, 0x18},
// sample freq 256
// DACFsMode,SINGLE SPEED; DACFsRatio,256
{ES8388_DACCONTROL2, 0x02},
// 0x00 audio on LIN1&RIN1, 0x09 LIN2&RIN2
{ES8388_DACCONTROL16, 0x00},
// only left DAC to left mixer enable 0db
{ES8388_DACCONTROL17, 0x90},
// only right DAC to right mixer enable 0db
{ES8388_DACCONTROL20, 0x90},
// set internal ADC and DAC use the same LRCK clock, ADC LRCK as internal LRCK
{ES8388_DACCONTROL21, 0x80},
// vroi=0 - 1.5k VREF to analog output resistance (default)
{ES8388_DACCONTROL23, 0x00},
// power down adc and line in
{ES8388_ADCPOWER, 0xFF},
//@nightdav
{ES8388_ADCCONTROL1, 0x00}, // +21dB : recommended value for ALC & voice recording
// set to Mono Right
{ES8388_ADCCONTROL3, 0x02},
// I2S 16 Bits length and I2S serial audio data format
{ES8388_ADCCONTROL4, 0x0d},
// ADCFsMode,singel SPEED,RATIO=256
{ES8388_ADCCONTROL5, 0x02},
// ADC Volume
{ES8388_ADCCONTROL8, 0x00},
{ES8388_ADCCONTROL9, 0x00},
//@nightDav
// ALC Config (as recommended by ES8388 user guide for voice recording)
// Reg 0x12 = 0xe2 (ALC enable, PGA Max. Gain=23.5dB, Min. Gain=0dB)
{ES8388_ADCCONTROL10, 0xe2},
// Reg 0x13 = 0xa0 (ALC Target=-1.5dB, ALC Hold time =0 mS)
{ES8388_ADCCONTROL11, 0xa0},
// Reg 0x14 = 0x12(Decay time =820uS , Attack time = 416 uS)
{ES8388_ADCCONTROL12, 0x12},
// Reg 0x15 = 0x06(ALC mode)
{ES8388_ADCCONTROL13, 0x06},
// Reg 0x16 = 0xc3(nose gate = -40.5dB, NGG = 0x01(mute ADC))
{ES8388_ADCCONTROL14, 0xc3},
// Power on ADC
{ES8388_DACCONTROL21, 0x80},
// Start state machine
{ES8388_CHIPPOWER, 0xF0},
};
static constexpr i2c::I2CRegValue ES8388_UNMUTE[] PROGMEM = {
// unmute ADC with fade in
{ES8388_ADCCONTROL7, 0x60},
// unmute DAC with fade in
{ES8388_DACCONTROL3, 0x20},
// Power on ADC, Enable LIN&RIN, Power off MICBIAS, set int1lp to low power mode
{ES8388_ADCPOWER, 0x09},
};
void ES8388::setup() {
// mute DAC
this->set_mute_state_(true);
// I2S worker mode
ES8388_ERROR_FAILED(this->write_byte(ES8388_MASTERMODE, 0x00));
/* Chip Control and Power Management */
ES8388_ERROR_FAILED(this->write_byte(ES8388_CONTROL2, 0x50));
// normal all and power up all
ES8388_ERROR_FAILED(this->write_byte(ES8388_CHIPPOWER, 0x00));
// vmidsel/500k
// EnRef=0,Play&Record Mode,(0x17-both of mic&play)
ES8388_ERROR_FAILED(this->write_byte(ES8388_CONTROL1, 0x12));
// i2s 16 bits
ES8388_ERROR_FAILED(this->write_byte(ES8388_DACCONTROL1, 0x18));
// sample freq 256
// DACFsMode,SINGLE SPEED; DACFsRatio,256
ES8388_ERROR_FAILED(this->write_byte(ES8388_DACCONTROL2, 0x02));
// 0x00 audio on LIN1&RIN1, 0x09 LIN2&RIN2
ES8388_ERROR_FAILED(this->write_byte(ES8388_DACCONTROL16, 0x00));
// only left DAC to left mixer enable 0db
ES8388_ERROR_FAILED(this->write_byte(ES8388_DACCONTROL17, 0x90));
// only right DAC to right mixer enable 0db
ES8388_ERROR_FAILED(this->write_byte(ES8388_DACCONTROL20, 0x90));
// set internal ADC and DAC use the same LRCK clock, ADC LRCK as internal LRCK
ES8388_ERROR_FAILED(this->write_byte(ES8388_DACCONTROL21, 0x80));
// vroi=0 - 1.5k VREF to analog output resistance (default)
ES8388_ERROR_FAILED(this->write_byte(ES8388_DACCONTROL23, 0x00));
// power down adc and line in
ES8388_ERROR_FAILED(this->write_byte(ES8388_ADCPOWER, 0xFF));
//@nightdav
ES8388_ERROR_FAILED(
this->write_byte(ES8388_ADCCONTROL1, 0x00)); // +21dB : recommended value for ALC & voice recording
// set to Mono Right
ES8388_ERROR_FAILED(this->write_byte(ES8388_ADCCONTROL3, 0x02));
// I2S 16 Bits length and I2S serial audio data format
ES8388_ERROR_FAILED(this->write_byte(ES8388_ADCCONTROL4, 0x0d));
// ADCFsMode,singel SPEED,RATIO=256
ES8388_ERROR_FAILED(this->write_byte(ES8388_ADCCONTROL5, 0x02));
// ADC Volume
ES8388_ERROR_FAILED(this->write_byte(ES8388_ADCCONTROL8, 0x00));
ES8388_ERROR_FAILED(this->write_byte(ES8388_ADCCONTROL9, 0x00));
//@nightDav
// ALC Config (as recommended by ES8388 user guide for voice recording)
// Reg 0x12 = 0xe2 (ALC enable, PGA Max. Gain=23.5dB, Min. Gain=0dB)
ES8388_ERROR_FAILED(this->write_byte(ES8388_ADCCONTROL10, 0xe2));
// Reg 0x13 = 0xa0 (ALC Target=-1.5dB, ALC Hold time =0 mS)
ES8388_ERROR_FAILED(this->write_byte(ES8388_ADCCONTROL11, 0xa0));
// Reg 0x14 = 0x12(Decay time =820uS , Attack time = 416 uS)
ES8388_ERROR_FAILED(this->write_byte(ES8388_ADCCONTROL12, 0x12));
// Reg 0x15 = 0x06(ALC mode)
ES8388_ERROR_FAILED(this->write_byte(ES8388_ADCCONTROL13, 0x06));
// Reg 0x16 = 0xc3(nose gate = -40.5dB, NGG = 0x01(mute ADC))
ES8388_ERROR_FAILED(this->write_byte(ES8388_ADCCONTROL14, 0xc3));
// Power on ADC
ES8388_ERROR_FAILED(this->write_byte(ES8388_DACCONTROL21, 0x80));
// Start state machine
ES8388_ERROR_FAILED(this->write_byte(ES8388_CHIPPOWER, 0xF0));
ES8388_ERROR_FAILED(this->write_registers(ES8388_INIT));
delay(1);
ES8388_ERROR_FAILED(this->write_byte(ES8388_CHIPPOWER, 0x00));
@@ -104,13 +101,7 @@ void ES8388::setup() {
this->set_mute_state_(false);
// unmute ADC with fade in
ES8388_ERROR_FAILED(this->write_byte(ES8388_ADCCONTROL7, 0x60));
// unmute DAC with fade in
ES8388_ERROR_FAILED(this->write_byte(ES8388_DACCONTROL3, 0x20));
// Power on ADC, Enable LIN&RIN, Power off MICBIAS, set int1lp to low power mode
ES8388_ERROR_FAILED(this->write_byte(ES8388_ADCPOWER, 0x09));
ES8388_ERROR_FAILED(this->write_registers(ES8388_UNMUTE));
#ifdef USE_ES8388_SELECT
if (this->dac_output_select_ != nullptr) {
@@ -152,8 +143,8 @@ void ES8388::dump_config() {
ESP_LOGCONFIG(TAG, "ES8388 Audio Codec:");
LOG_I2C_DEVICE(this);
#ifdef USE_ES8388_SELECT
LOG_SELECT(" ", "DacOutputSelect", this->dac_output_select_);
LOG_SELECT(" ", "ADCInputMicSelect", this->adc_input_mic_select_);
LOG_SELECT(" ", "DAC Output", this->dac_output_select_);
LOG_SELECT(" ", "ADC Input Mic", this->adc_input_mic_select_);
#endif
}
+9 -5
View File
@@ -24,14 +24,12 @@ ADC_INPUT_MIC_OPTIONS = AdcInputMicLine.options(
)
ADC_INPUT_MIC_OPTION_NAMES = list(ADC_INPUT_MIC_OPTIONS)
DacOutputSelect = es8388_ns.class_("DacOutputSelect", select.Select)
ADCInputMicSelect = es8388_ns.class_("ADCInputMicSelect", select.Select)
CONFIG_SCHEMA = cv.All(
{
cv.GenerateID(CONF_ES8388_ID): cv.use_id(ES8388),
cv.Optional(CONF_DAC_OUTPUT): select.select_schema(
DacOutputSelect,
select.ApplySelect,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_CHIP,
).extend(
@@ -42,7 +40,7 @@ CONFIG_SCHEMA = cv.All(
}
),
cv.Optional(CONF_ADC_INPUT_MIC): select.select_schema(
ADCInputMicSelect,
select.ApplySelect,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_CHIP,
).extend(
@@ -61,12 +59,18 @@ async def to_code(config: ConfigType) -> None:
hub = await cg.get_variable(config[CONF_ES8388_ID])
selects = select.sub_selects(config, parent=hub)
await selects(
CONF_DAC_OUTPUT, hub.set_dac_output_select, options=DAC_OUTPUT_OPTION_NAMES
CONF_DAC_OUTPUT,
hub.set_dac_output_select,
options=DAC_OUTPUT_OPTION_NAMES,
apply="set_dac_output(static_cast<es8388::DacOutputLine>(index))",
publish="before",
)
await selects(
CONF_ADC_INPUT_MIC,
hub.set_adc_input_mic_select,
options=ADC_INPUT_MIC_OPTION_NAMES,
apply="set_adc_input_mic(static_cast<es8388::AdcInputMicLine>(index))",
publish="before",
)
if (
initial := config.get(CONF_DAC_OUTPUT, {}).get(CONF_INITIAL_OPTION)
@@ -1,10 +0,0 @@
#include "adc_input_mic_select.h"
namespace esphome::es8388 {
void ADCInputMicSelect::control(size_t index) {
this->publish_state(index);
this->parent_->set_adc_input_mic(static_cast<AdcInputMicLine>(index));
}
} // namespace esphome::es8388
@@ -1,13 +0,0 @@
#pragma once
#include "esphome/components/es8388/es8388.h"
#include "esphome/components/select/select.h"
namespace esphome::es8388 {
class ADCInputMicSelect final : public select::Select, public Parented<ES8388> {
protected:
void control(size_t index) override;
};
} // namespace esphome::es8388
@@ -1,10 +0,0 @@
#include "dac_output_select.h"
namespace esphome::es8388 {
void DacOutputSelect::control(size_t index) {
this->publish_state(index);
this->parent_->set_dac_output(static_cast<DacOutputLine>(index));
}
} // namespace esphome::es8388
@@ -1,13 +0,0 @@
#pragma once
#include "esphome/components/es8388/es8388.h"
#include "esphome/components/select/select.h"
namespace esphome::es8388 {
class DacOutputSelect final : public select::Select, public Parented<ES8388> {
protected:
void control(size_t index) override;
};
} // namespace esphome::es8388
@@ -114,7 +114,8 @@ BLECharacteristicNotifyAction = esp32_ble_server_automations_ns.class_(
"BLECharacteristicNotifyAction", automation.Action
)
bytebuffer_ns = cg.esphome_ns.namespace("bytebuffer")
Endianness_ns = bytebuffer_ns.namespace("Endian")
# Unscoped in C++; is_class keeps the generated Endian::LITTLE spelling
Endian = bytebuffer_ns.enum("Endian", is_class=True)
PROPERTY_MAP = {
@@ -410,10 +411,7 @@ def value_schema(default_type=UNDEFINED, templatable=True):
),
cv.Optional(CONF_STRING_ENCODING, default="utf_8"): validate_encoding,
cv.Optional(CONF_ENDIANNESS, default="LITTLE"): cv.enum(
{
"LITTLE": Endianness_ns.LITTLE,
"BIG": Endianness_ns.BIG,
}
Endian.options("LITTLE", "BIG")
),
},
validate_value_type,
+13 -17
View File
@@ -54,25 +54,21 @@ CAN_MODES = {
# The supported bit rates differ between ESP32 variants.
# See ESP-IDF Programming Guide --> API Reference --> Two-Wire Automotive Interface (TWAI)
CAN_SPEEDS_ESP32 = {
"20KBPS": CanSpeed.CAN_20KBPS,
"25KBPS": CanSpeed.CAN_25KBPS,
"50KBPS": CanSpeed.CAN_50KBPS,
"100KBPS": CanSpeed.CAN_100KBPS,
"125KBPS": CanSpeed.CAN_125KBPS,
"250KBPS": CanSpeed.CAN_250KBPS,
"500KBPS": CanSpeed.CAN_500KBPS,
"800KBPS": CanSpeed.CAN_800KBPS,
"1000KBPS": CanSpeed.CAN_1000KBPS,
}
CAN_SPEEDS_ESP32 = CanSpeed.options(
"20KBPS",
"25KBPS",
"50KBPS",
"100KBPS",
"125KBPS",
"250KBPS",
"500KBPS",
"800KBPS",
"1000KBPS",
prefix="CAN_",
)
CAN_SPEEDS_ESP32_S2 = {
"1KBPS": CanSpeed.CAN_1KBPS,
"5KBPS": CanSpeed.CAN_5KBPS,
"10KBPS": CanSpeed.CAN_10KBPS,
"12K5BPS": CanSpeed.CAN_12K5BPS,
"16KBPS": CanSpeed.CAN_16KBPS,
"20KBPS": CanSpeed.CAN_20KBPS,
**CanSpeed.options("1KBPS", "5KBPS", "10KBPS", "12K5BPS", "16KBPS", prefix="CAN_"),
**CAN_SPEEDS_ESP32,
}
@@ -1,4 +1,5 @@
#include "led_strip.h"
#include <algorithm>
#include <cinttypes>
#ifdef USE_ESP32
@@ -14,6 +15,8 @@ namespace esphome::esp32_rmt_led_strip {
ESPHOME_LOG_TAG(TAG, "esp32_rmt_led_strip");
static const size_t RMT_SYMBOLS_PER_BYTE = 8;
// Idle time the line must stay low between frames so the strip latches
static constexpr uint32_t RESET_GAP_US = 50;
// Query the RMT default clock source frequency. This varies by variant:
// APB (80MHz) on ESP32/S2/S3/C3, PLL_F80M (80MHz) on C6/P4, XTAL (32MHz) on H2.
@@ -91,10 +94,20 @@ void ESP32RMTLEDStripLightOutput::setup() {
this->rmt_buf_ = rmt_allocator.allocate(buffer_size * 8 + 1);
#endif
const uint32_t resolution_hz = rmt_resolution_hz();
// Longest wire time of one frame; write_state() defers instead of blocking until it has elapsed
uint32_t bit_ticks = std::max(this->params_.bit0.duration0 + this->params_.bit0.duration1,
this->params_.bit1.duration0 + this->params_.bit1.duration1);
uint64_t frame_ticks = (uint64_t) bit_ticks * buffer_size * RMT_SYMBOLS_PER_BYTE + this->params_.reset.duration0 +
this->params_.reset.duration1;
this->frame_time_us_ = frame_ticks * 1000000 / resolution_hz + RESET_GAP_US;
// Seed with the interval write_state() gates on, so the first frame is not deferred
this->last_refresh_ = micros() - std::max(this->frame_time_us_, this->max_refresh_rate_);
rmt_tx_channel_config_t channel;
memset(&channel, 0, sizeof(channel));
channel.clk_src = RMT_CLK_SRC_DEFAULT;
channel.resolution_hz = rmt_resolution_hz();
channel.resolution_hz = resolution_hz;
channel.gpio_num = gpio_num_t(this->pin_);
channel.mem_block_symbols = this->rmt_symbols_;
channel.trans_queue_depth = 1;
@@ -157,22 +170,21 @@ void ESP32RMTLEDStripLightOutput::set_led_params(uint32_t bit0_high, uint32_t bi
}
void ESP32RMTLEDStripLightOutput::write_state(light::LightState *state) {
uint32_t now = micros();
if (this->throttle_show_(now, this->last_refresh_, this->max_refresh_rate_))
// Previous frame still on the wire, inside its reset gap, or refreshing too often: try again next loop
if (this->throttle_show_(micros(), this->last_refresh_, std::max(this->frame_time_us_, this->max_refresh_rate_)))
return;
this->last_refresh_ = now;
// The gate above should always cover the previous frame; never overwrite rmt_buf_ while the RMT still reads it
if (rmt_tx_wait_all_done(this->channel_, 0) != ESP_OK) {
ESP_LOGE(TAG, "RMT TX still busy");
this->status_set_warning();
this->schedule_show();
return;
}
this->mark_shown_();
ESP_LOGVV(TAG, "Writing RGB values to bus");
esp_err_t error = rmt_tx_wait_all_done(this->channel_, 1000);
if (error != ESP_OK) {
ESP_LOGE(TAG, "RMT TX timeout");
this->status_set_warning();
return;
}
delayMicroseconds(50);
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)
memcpy(this->rmt_buf_, this->buf_, this->get_buffer_size_());
#else
@@ -203,13 +215,19 @@ void ESP32RMTLEDStripLightOutput::write_state(light::LightState *state) {
rmt_transmit_config_t config;
memset(&config, 0, sizeof(config));
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)
error = rmt_transmit(this->channel_, this->encoder_, this->rmt_buf_, this->get_buffer_size_(), &config);
esp_err_t error = rmt_transmit(this->channel_, this->encoder_, this->rmt_buf_, this->get_buffer_size_(), &config);
#else
error = rmt_transmit(this->channel_, this->encoder_, this->rmt_buf_, len * sizeof(rmt_symbol_word_t), &config);
esp_err_t error =
rmt_transmit(this->channel_, this->encoder_, this->rmt_buf_, len * sizeof(rmt_symbol_word_t), &config);
#endif
if (!this->status_comm_result(error == ESP_OK, nullptr)) {
ESP_LOGE(TAG, "RMT TX error");
// retry next loop iteration so the frame is not lost
this->schedule_show();
return;
}
// Stamped after the transfer started so the gate above stays conservative
this->last_refresh_ = micros();
}
void ESP32RMTLEDStripLightOutput::dump_config() {
@@ -81,6 +81,8 @@ class ESP32RMTLEDStripLightOutput final : public light::AddressableLight {
uint32_t last_refresh_{0};
uint32_t max_refresh_rate_{0};
// Worst-case wire time of one frame plus the reset gap, computed in setup()
uint32_t frame_time_us_{0};
};
} // namespace esphome::esp32_rmt_led_strip
@@ -9,24 +9,23 @@ from .. import (
CONF_ESPECTRE_ID,
DOMAIN,
ESPectreComponent,
espectre_ns,
supported_traffic_generator_modes,
)
DEPENDENCIES = ["espectre"]
TrafficModeSelect = espectre_ns.class_(
"TrafficModeSelect", select.Select, cg.Parented.template(ESPectreComponent)
)
CONFIG_SCHEMA = select.select_schema(
TrafficModeSelect, entity_category=ENTITY_CATEGORY_CONFIG
select.ApplySelect, entity_category=ENTITY_CATEGORY_CONFIG
).extend({cv.GenerateID(CONF_ESPECTRE_ID): cv.use_id(ESPectreComponent)})
async def to_code(config: ConfigType) -> None:
options = supported_traffic_generator_modes(CORE.config[DOMAIN])
var = await select.new_select(config, options=options)
await cg.register_parented(var, config[CONF_ESPECTRE_ID])
parent = await cg.get_variable(config[CONF_ESPECTRE_ID])
cg.add(parent.set_traffic_mode_select(var))
hub = await cg.get_variable(config[CONF_ESPECTRE_ID])
await select.sub_selects(config, parent=hub).create(
config,
hub.set_traffic_mode_select,
options=options,
apply="request_traffic_generator_mode(option)",
)
@@ -1,11 +0,0 @@
#include "espectre_select.h"
#ifdef USE_ESPECTRE
namespace esphome::espectre {
void TrafficModeSelect::control(size_t index) { this->parent_->request_traffic_generator_mode(this->option_at(index)); }
} // namespace esphome::espectre
#endif // USE_ESPECTRE
@@ -1,19 +0,0 @@
#pragma once
#include "esphome/core/defines.h"
#ifdef USE_ESPECTRE
#include "esphome/components/select/select.h"
#include "../espectre.h"
namespace esphome::espectre {
class TrafficModeSelect final : public select::Select, public Parented<ESPectreComponent> {
protected:
void control(size_t index) override;
};
} // namespace esphome::espectre
#endif // USE_ESPECTRE
@@ -373,7 +373,7 @@ void ESPHomeOTAComponent::handle_handshake_() {
// Value initialized: a corrupt stream that back references the
// window before it is filled then copies zeros, never stale memory.
// Default placement, PSRAM first where present: the session lives for one
// upload and keeps 4.9 KB of internal heap free while it runs
// upload and keeps 6.0 KB of internal heap free while it runs
this->inflate_ = RAMAllocator<InflateSession>().make_unique();
if (this->inflate_ != nullptr) {
this->handshake_buf_[1] |= SERVER_FEATURE_SUPPORTS_DEFLATE;
@@ -36,7 +36,10 @@
* Altered for ESPHome: this is the raw deflate decoder from uzlib's
* tinflate.c (v2.9.5) with the gzip/zlib header parsers, checksums,
* runtime table builder and in-memory (non ring window) output path
* removed, and the public names prefixed with ota_inflate.
* removed, and the public names prefixed with ota_inflate. Symbols are
* decoded from a 32 bit bit buffer through 9 bit lookup tables, with a
* fast loop for when a whole symbol fits in both the input and the output,
* and the output is written straight into the ring window.
*/
#include "ota_esphome_inflate.h"
@@ -49,16 +52,6 @@
#define TINF_DICT_ERROR OTA_INFLATE_DICT_ERROR
#define TINF_DATA struct OtaInflateState
#define TINF_TREE struct OtaInflateTree
#define TINF_ARRAY_SIZE(arr) (sizeof(arr) / sizeof(*(arr)))
/* every output byte also goes into the ring window */
#define TINF_PUT(d, c) \
{ \
*d->dest++ = c; \
d->dict_ring[d->dict_idx++] = c; \
if (d->dict_idx == d->dict_size) \
d->dict_idx = 0; \
}
/* --------------------------------------------------- *
* -- constant tables (upstream builds them at runtime) -- *
@@ -82,34 +75,52 @@ static const unsigned char CLCIDX[] = {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4,
* -- utility functions -- *
* ----------------------- */
/* given an array of code lengths, build a tree */
static void tinf_build_tree(TINF_TREE *t, const unsigned char *lengths, unsigned int num) {
unsigned short offs[16];
unsigned int i, sum;
enum {
LIT_FAST_BITS = OTA_INFLATE_LIT_FAST_BITS,
DIST_FAST_BITS = OTA_INFLATE_DIST_FAST_BITS,
/* a lookup table entry is (code length << SYM_BITS) | symbol; 0 marks a longer code */
SYM_BITS = 9,
SYM_MASK = (1 << SYM_BITS) - 1,
/* input bytes one pass of the fast loop may take: three refills of up to 4 bytes */
FAST_IN_MIN = 12,
};
/* given an array of code lengths, build a tree and its lookup table */
static void tinf_build_tree(TINF_TREE *t, const unsigned char *lengths, unsigned int num, unsigned int fast_bits) {
const unsigned int fast_size = 1u << fast_bits;
unsigned short offs[16], next[16];
unsigned int i, sum, code;
/* clear code length count table */
for (i = 0; i < 16; ++i)
t->table[i] = 0;
/* scan symbol lengths, and sum code length counts */
for (i = 0; i < num; ++i)
t->table[lengths[i]]++;
/* In the lengths array, 0 means unused code. So, t->table[0] now contains
number of unused codes. But table's purpose is to contain # of codes of
particular length, and there're 0 codes of length 0. */
t->table[0] = 0;
/* compute offset table for distribution sort */
for (sum = 0, i = 0; i < 16; ++i) {
for (sum = 0, code = 0, i = 0; i < 16; ++i) {
offs[i] = sum;
sum += t->table[i];
if (i > 0)
code = (code + t->table[i - 1]) << 1;
next[i] = code;
}
/* create code->symbol translation table (symbols sorted by code) */
for (i = 0; i < fast_size; ++i)
t->fast[i] = 0;
for (i = 0; i < num; ++i) {
if (lengths[i])
t->trans[offs[lengths[i]]++] = i;
unsigned int len = lengths[i];
if (!len)
continue;
t->trans[offs[len]++] = i;
if (len <= fast_bits) {
/* codes arrive MSB first, the bit buffer is LSB first */
unsigned int c = next[len]++, rev = 0, j;
for (j = 0; j < len; ++j, c >>= 1)
rev = (rev << 1) | (c & 1);
for (j = rev; j < fast_size; j += 1u << len)
t->fast[j] = (unsigned short) ((len << SYM_BITS) | i);
}
}
}
@@ -117,89 +128,78 @@ static void tinf_build_tree(TINF_TREE *t, const unsigned char *lengths, unsigned
* -- decode functions -- *
* ---------------------- */
static unsigned char uzlib_get_byte(TINF_DATA *d) {
/* If end of source buffer is not reached, return next byte from source
buffer. */
if (d->source < d->source_limit) {
return *d->source++;
/* top the bit buffer up from the input buffer, without blocking for more */
static inline void tinf_refill(TINF_DATA *d) {
while (d->bitcount <= 24 && d->source < d->source_limit) {
d->tag |= (unsigned int) *d->source++ << d->bitcount;
d->bitcount += 8;
}
/* Otherwise if there's callback and we haven't seen EOF yet, try to
read next byte using it. (Note: the callback can also update ->source
and ->source_limit). */
if (!d->eof) {
int val = d->source_read_cb(d);
if (val >= 0) {
return (unsigned char) val;
}
}
/* Otherwise, we hit EOF (either from ->source_read_cb() or from exhaustion
of the buffer), and it will be "sticky", i.e. further calls to this
function will end up here too. */
d->eof = true;
return 0;
}
/* get one bit from source stream */
static int tinf_getbit(TINF_DATA *d) {
unsigned int bit;
/* check if tag is empty */
if (!d->bitcount--) {
/* load next tag */
d->tag = uzlib_get_byte(d);
d->bitcount = 7;
/* make at least num (<= 16) bits available; past the end of input reads zeros and sets eof */
static inline void tinf_need(TINF_DATA *d, unsigned int num) {
while (d->bitcount < num) {
int val = -1;
if (d->source < d->source_limit) {
val = *d->source++;
} else if (!d->eof) {
val = d->source_read_cb(d);
}
if (val < 0) {
d->eof = true;
val = 0;
}
d->tag |= (unsigned int) val << d->bitcount;
d->bitcount += 8;
}
}
/* shift bit out of tag */
bit = d->tag & 0x01;
d->tag >>= 1;
return bit;
static inline unsigned int tinf_take(TINF_DATA *d, unsigned int num) {
unsigned int val = d->tag & ((1u << num) - 1);
d->tag >>= num;
d->bitcount -= num;
return val;
}
/* read a num bit value from a stream and add base */
static unsigned int tinf_read_bits(TINF_DATA *d, int num, int base) {
unsigned int val = 0;
/* read num bits */
if (num) {
unsigned int limit = 1 << (num);
unsigned int mask;
for (mask = 1; mask < limit; mask *= 2)
if (tinf_getbit(d))
val += mask;
}
return val + base;
static inline unsigned int tinf_read_bits(TINF_DATA *d, unsigned int num, unsigned int base) {
tinf_need(d, num);
return tinf_take(d, num) + base;
}
/* given a data stream and a tree, decode a symbol */
static int tinf_decode_symbol(TINF_DATA *d, TINF_TREE *t) {
int sum = 0, cur = 0, len = 0;
/* get more bits while code value is above sum */
do {
cur = 2 * cur + tinf_getbit(d);
if (++len == TINF_ARRAY_SIZE(t->table)) {
return TINF_DATA_ERROR;
/* canonical decode of the code at the bottom of tag; len gets the code length.
The trans index stays below the tree's symbol count, which trans always holds */
static int tinf_decode_long(const TINF_TREE *t, unsigned int tag, unsigned int *len) {
unsigned int code = 0, first = 0, index = 0, l;
for (l = 1; l < 16; ++l, tag >>= 1) {
unsigned int count = t->table[l];
code |= tag & 1;
if (code - first < count) {
*len = l;
return t->trans[index + code - first];
}
sum += t->table[len];
cur -= t->table[len];
} while (cur >= 0);
sum += cur;
if (sum < 0 || sum >= t->size) {
return TINF_DATA_ERROR;
index += count;
first = (first + count) << 1;
code <<= 1;
}
return TINF_DATA_ERROR;
}
return t->trans[sum];
/* decode a symbol, reading more input only while the code is not all in the bit
buffer; bits above bitcount are zero, so a code no longer than bitcount is real */
static int tinf_decode_symbol(TINF_DATA *d, const TINF_TREE *t) {
tinf_refill(d);
for (;;) {
unsigned int len;
int sym = tinf_decode_long(t, d->tag, &len);
if (sym >= 0 && len <= d->bitcount) {
tinf_take(d, len);
return sym;
}
if (d->bitcount >= 15)
return TINF_DATA_ERROR;
tinf_need(d, d->bitcount + 1);
}
}
/* given a data stream, decode dynamic trees from it */
@@ -230,7 +230,7 @@ static int tinf_decode_trees(TINF_DATA *d, TINF_TREE *lt, TINF_TREE *dt) {
}
/* build code length tree, temporarily use length tree */
tinf_build_tree(lt, lengths, 19);
tinf_build_tree(lt, lengths, 19, LIT_FAST_BITS);
/* decode code lengths for the dynamic trees */
hlimit = hlit + hdist;
@@ -282,8 +282,8 @@ static int tinf_decode_trees(TINF_DATA *d, TINF_TREE *lt, TINF_TREE *dt) {
}
/* build dynamic trees */
tinf_build_tree(lt, lengths, hlit);
tinf_build_tree(dt, lengths + hlit, hdist);
tinf_build_tree(lt, lengths, hlit, LIT_FAST_BITS);
tinf_build_tree(dt, lengths + hlit, hdist, DIST_FAST_BITS);
return TINF_OK;
}
@@ -302,84 +302,189 @@ static void tinf_build_fixed_trees(TINF_TREE *lt, TINF_TREE *dt) {
lengths[i] = 7;
for (; i < 288; ++i)
lengths[i] = 8;
tinf_build_tree(lt, lengths, 288);
tinf_build_tree(lt, lengths, 288, LIT_FAST_BITS);
for (i = 0; i < 32; ++i)
lengths[i] = 5;
tinf_build_tree(dt, lengths, 32);
tinf_build_tree(dt, lengths, 32, DIST_FAST_BITS);
}
/* inflate with the bit buffer and both cursors in locals while the input holds a
whole symbol; a match that runs past the end of the ring is left in curlen */
static int tinf_inflate_fast(TINF_DATA *d, const TINF_TREE *lt, const TINF_TREE *dt) {
const unsigned char *src = d->source;
const unsigned char *const src_end = d->source_limit;
unsigned char *const ring = d->dict_ring;
unsigned char *const ring_end = ring + d->dict_size;
unsigned char *out = d->dest;
const uint16_t *const lfast = lt->fast;
const uint16_t *const dfast = dt->fast;
unsigned int tag = d->tag, bits = d->bitcount;
int res = TINF_OK;
#define REFILL() \
while (bits <= 24) { \
tag |= (unsigned int) *src++ << bits; \
bits += 8; \
}
#define DROP(n) (tag >>= (n), bits -= (n))
while (src_end - src >= FAST_IN_MIN && out < ring_end) {
unsigned int e, len;
int sym;
REFILL();
e = lfast[tag & ((1u << LIT_FAST_BITS) - 1)];
if (e) {
len = e >> SYM_BITS;
sym = e & SYM_MASK;
} else {
sym = tinf_decode_long(lt, tag, &len);
if (sym < 0) {
res = sym;
break;
}
}
DROP(len);
if (sym < 256) {
*out++ = sym;
continue;
}
if (sym == 256) {
res = TINF_DONE;
break;
}
sym -= 257;
if (sym >= 29) {
res = TINF_DATA_ERROR;
break;
}
/* the lit/len code and its extra bits fit the refilled buffer together */
unsigned int length = LENGTH_BASE[sym] + (tag & ((1u << LENGTH_BITS[sym]) - 1));
DROP(LENGTH_BITS[sym]);
REFILL();
e = dfast[tag & ((1u << DIST_FAST_BITS) - 1)];
if (e) {
len = e >> SYM_BITS;
sym = e & SYM_MASK;
} else {
sym = tinf_decode_long(dt, tag, &len);
}
if (sym < 0 || sym >= 30) {
res = TINF_DATA_ERROR;
break;
}
DROP(len);
REFILL();
unsigned int offs = DIST_BASE[sym] + (tag & ((1u << DIST_BITS[sym]) - 1));
DROP(DIST_BITS[sym]);
if (offs > d->dict_size) {
res = TINF_DICT_ERROR;
break;
}
/* wrap as an index: a pointer before the ring would be undefined */
unsigned int at = out - ring;
const unsigned char *from = ring + (at >= offs ? at - offs : at + d->dict_size - offs);
unsigned int n = ring_end - out;
if (n > length)
n = length;
length -= n;
/* at most two runs: up to the end of the ring, then on from its start */
while (n) {
unsigned int run = ring_end - from;
if (run > n)
run = n;
n -= run;
do {
*out++ = *from++;
} while (--run);
if (from == ring_end)
from = ring;
}
if (length) {
d->curlen = length;
d->lz_off = from - ring;
}
}
#undef REFILL
#undef DROP
d->source = src;
d->dest = out;
d->tag = tag;
d->bitcount = bits;
return res;
}
/* ----------------------------- *
* -- block inflate functions -- *
* ----------------------------- */
/* given a stream and two trees, inflate next chunk of output (a byte or more) */
/* given a stream and two trees, inflate until the ring is full or the block ends */
static int tinf_inflate_block_data(TINF_DATA *d, TINF_TREE *lt, TINF_TREE *dt) {
if (d->curlen == 0) {
unsigned char *const ring_end = d->dict_ring + d->dict_size;
while (d->dest < ring_end) {
if (d->curlen != 0) {
unsigned int n = ring_end - d->dest;
if (n > d->curlen)
n = d->curlen;
d->curlen -= n;
do {
*d->dest++ = d->dict_ring[d->lz_off];
if ((unsigned) ++d->lz_off == d->dict_size)
d->lz_off = 0;
} while (--n);
continue;
}
if (d->source_limit - d->source >= FAST_IN_MIN) {
int res = tinf_inflate_fast(d, lt, dt);
if (res != TINF_OK)
return res;
continue;
}
unsigned int offs;
int dist;
int sym = tinf_decode_symbol(d, lt);
if (d->eof) {
if (d->eof)
return TINF_DATA_ERROR;
}
if (sym < 0) {
if (sym < 0)
return sym;
}
/* literal byte */
if (sym < 256) {
TINF_PUT(d, sym);
return TINF_OK;
*d->dest++ = sym;
continue;
}
/* end of block */
if (sym == 256) {
if (sym == 256)
return TINF_DONE;
}
/* substring from sliding dictionary */
sym -= 257;
if (sym >= 29) {
if (sym >= 29)
return TINF_DATA_ERROR;
}
/* possibly get more bits from length code */
d->curlen = tinf_read_bits(d, LENGTH_BITS[sym], LENGTH_BASE[sym]);
dist = tinf_decode_symbol(d, dt);
if (dist < 0 || dist >= 30) {
if (dist < 0 || dist >= 30)
return TINF_DATA_ERROR;
}
/* possibly get more bits from distance code */
offs = tinf_read_bits(d, DIST_BITS[dist], DIST_BASE[dist]);
/* calculate and validate actual LZ offset to use */
if (offs > d->dict_size) {
if (offs > d->dict_size)
return TINF_DICT_ERROR;
}
/* Note: we don't try to catch offset which points to not yet filled
part of the dictionary here. Doing so would require keeping another
variable to track "filled in" size of the dictionary. Appearance of
such an offset cannot lead to accessing memory outside of the
dictionary buffer, and clients which don't want to leak unrelated
information, should explicitly initialize dictionary buffer passed
to uzlib. */
d->lz_off = d->dict_idx - offs;
if (d->lz_off < 0) {
d->lz_off = (int) (d->dest - d->dict_ring) - (int) offs;
if (d->lz_off < 0)
d->lz_off += d->dict_size;
}
}
/* copy next byte from dict substring */
TINF_PUT(d, d->dict_ring[d->lz_off]);
if ((unsigned) ++d->lz_off == d->dict_size) {
d->lz_off = 0;
}
d->curlen--;
return TINF_OK;
}
@@ -388,30 +493,22 @@ static int tinf_inflate_uncompressed_block(TINF_DATA *d) {
if (d->curlen == 0) {
unsigned int length, invlength;
/* get length */
length = uzlib_get_byte(d);
length += 256 * uzlib_get_byte(d);
/* get one's complement of length */
invlength = uzlib_get_byte(d);
invlength += 256 * uzlib_get_byte(d);
/* check length */
/* the block starts on a byte boundary */
tinf_take(d, d->bitcount & 7);
length = tinf_read_bits(d, 16, 0);
invlength = tinf_read_bits(d, 16, 0);
if (length != (~invlength & 0x0000ffff))
return TINF_DATA_ERROR;
/* increment length to properly return TINF_DONE below, without
producing data at the same time */
d->curlen = length + 1;
/* make sure we start next block on a byte boundary */
d->bitcount = 0;
}
if (--d->curlen == 0) {
if (--d->curlen == 0)
return TINF_DONE;
}
unsigned char c = uzlib_get_byte(d);
TINF_PUT(d, c);
/* read first: a read that needs input may flush the output up to dest */
unsigned char c = tinf_read_bits(d, 8, 0);
*d->dest++ = c;
return TINF_OK;
}
@@ -419,10 +516,10 @@ static int tinf_inflate_uncompressed_block(TINF_DATA *d) {
* -- public functions -- *
* ---------------------- */
/* initialize decompression structure */
void ota_inflate_init(TINF_DATA *d, unsigned char *dict, unsigned int dict_len) {
d->source = NULL;
d->source_limit = NULL;
d->dest = dict;
d->tag = 0;
d->eof = 0;
d->bitcount = 0;
@@ -431,68 +528,56 @@ void ota_inflate_init(TINF_DATA *d, unsigned char *dict, unsigned int dict_len)
d->btype = -1;
d->dict_size = dict_len;
d->dict_ring = dict;
d->dict_idx = 0;
d->curlen = 0;
d->ltree.trans = d->ltrans;
d->ltree.size = TINF_ARRAY_SIZE(d->ltrans);
d->ltree.fast = d->lfast;
d->dtree.trans = d->dtrans;
d->dtree.size = TINF_ARRAY_SIZE(d->dtrans);
d->dtree.fast = d->dfast;
}
/* inflate next output bytes from compressed stream */
int ota_inflate(TINF_DATA *d) {
/* the caller has taken the previous ringful */
if (d->dest == d->dict_ring + d->dict_size)
d->dest = d->dict_ring;
do {
int res;
/* start a new block */
if (d->btype == -1) {
int old_btype;
next_blk:
old_btype = d->btype;
/* read final block flag */
d->bfinal = tinf_getbit(d);
/* read block type (2 bits) */
d->bfinal = tinf_read_bits(d, 1, 0);
d->btype = tinf_read_bits(d, 2, 0);
if (d->btype == 1 && old_btype != 1) {
/* build fixed huffman trees */
tinf_build_fixed_trees(&d->ltree, &d->dtree);
} else if (d->btype == 2) {
/* decode trees from stream */
res = tinf_decode_trees(d, &d->ltree, &d->dtree);
if (res != TINF_OK) {
if (res != TINF_OK)
return res;
}
}
}
/* process current block */
switch (d->btype) {
case 0:
/* decompress uncompressed block */
res = tinf_inflate_uncompressed_block(d);
break;
case 1:
case 2:
/* decompress block with fixed/dynamic huffman trees */
/* trees were decoded previously, so it's the same routine for both */
res = tinf_inflate_block_data(d, &d->ltree, &d->dtree);
break;
default:
return TINF_DATA_ERROR;
}
if (res == TINF_DONE && !d->bfinal) {
/* the block has ended (without producing more data), but we
can't return without data, so start procesing next block */
if (res == TINF_DONE && !d->bfinal)
goto next_blk;
}
if (res != TINF_OK) {
if (res != TINF_OK)
return res;
}
} while (d->dest < d->dest_limit);
} while (d->dest < d->dict_ring + d->dict_size);
return TINF_OK;
}
@@ -16,10 +16,14 @@ enum OtaInflateResult {
OTA_INFLATE_DICT_ERROR = -5,
};
/* Code bits each lookup table resolves; longer codes take the canonical decode.
The distance alphabet has 30 symbols, so its table is kept small */
enum { OTA_INFLATE_LIT_FAST_BITS = 9, OTA_INFLATE_DIST_FAST_BITS = 6 };
struct OtaInflateTree {
uint16_t table[16]; /* table of code length counts */
uint16_t *trans; /* code -> symbol translation table, size entries */
uint16_t size;
uint16_t *fast; /* (len << 9) | symbol by the next input bits, 0 = longer code */
uint16_t *trans; /* code -> symbol translation table */
};
struct OtaInflateState {
@@ -29,9 +33,8 @@ struct OtaInflateState {
/* Called when source is exhausted; returns the next byte or -1 at EOF.
It may refill source/source_limit for buffered operation. */
int (*source_read_cb)(struct OtaInflateState *d);
/* Output cursor and one past the end of the output buffer */
/* Output cursor in dict_ring: each call fills the ring from here */
unsigned char *dest;
unsigned char *dest_limit;
/* Ring window holding the last dict_size output bytes for back references */
unsigned char *dict_ring;
@@ -39,9 +42,8 @@ struct OtaInflateState {
unsigned int curlen;
int lz_off;
unsigned int dict_size;
unsigned int dict_idx;
/* One word: btype is -1 between blocks, bitcount never exceeds 7 */
/* btype is -1 between blocks; bitcount is the number of valid bits in tag (<= 32) */
int8_t btype;
uint8_t bfinal;
uint8_t bitcount;
@@ -51,13 +53,15 @@ struct OtaInflateState {
struct OtaInflateTree dtree; /* dynamic distance tree */
uint16_t ltrans[288];
uint16_t dtrans[32]; /* the distance alphabet has 30 symbols, so the tree is kept small */
uint16_t lfast[1 << OTA_INFLATE_LIT_FAST_BITS];
uint16_t dfast[1 << OTA_INFLATE_DIST_FAST_BITS];
};
/* dict must cover the encoder's window (its largest back reference) */
void ota_inflate_init(struct OtaInflateState *d, unsigned char *dict, unsigned int dict_len);
/* Fills dest up to dest_limit (OK) or to the end of the stream (DONE). dest may
alias dict only if dest_limit - dest == dict_len and dest is reset to dict
exactly when a call returns OK, so the ring index and dest stay in lockstep */
/* Inflates into dict until it is full (OK) or the stream ends (DONE); the output
is dict up to dest. The next call after OK starts dict over, so take its
contents first */
int ota_inflate(struct OtaInflateState *d);
#ifdef __cplusplus
@@ -65,16 +65,15 @@ ota::OTAResponseTypes ESPHomeOTAComponent::inflate_data_(uint8_t *in, size_t ima
};
int res;
session.flushed = 0;
do {
// The ring index wrapped to 0 exactly when the window filled
session.dest = session.window;
session.dest_limit = session.window + OTA_INFLATE_WINDOW_SIZE;
session.flushed = 0;
res = ota_inflate(&session);
// A stored block keeps emitting zeros after a failed read, hence eof
if (res < 0 || session.eof)
break;
session.error = this->inflate_flush_(session);
// A full window is all in flash; the next call starts it over
session.flushed = 0;
} while (res != OTA_INFLATE_DONE && session.error == ota::OTA_RESPONSE_OK);
// Transport and flash failures are logged where they happen
+12 -9
View File
@@ -1,14 +1,14 @@
#include "ezo.h"
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
#include "esphome/core/progmem.h"
namespace esphome::ezo {
static const char *const EZO_COMMAND_TYPE_STRINGS[] = {"EZO_READ", "EZO_LED", "EZO_DEVICE_INFORMATION",
"EZO_SLOPE", "EZO_CALIBRATION", "EZO_SLEEP",
"EZO_I2C", "EZO_T", "EZO_CUSTOM"};
PROGMEM_STRING_TABLE(EzoCommandTypeStrings, "EZO_READ", "EZO_LED", "EZO_DEVICE_INFORMATION", "EZO_SLOPE",
"EZO_CALIBRATION", "EZO_SLEEP", "EZO_I2C", "EZO_T", "EZO_CUSTOM");
static const char *const EZO_CALIBRATION_TYPE_STRINGS[] = {"LOW", "MID", "HIGH"};
PROGMEM_STRING_TABLE(EzoCalibrationTypeStrings, "LOW", "MID", "HIGH");
void EZOSensor::dump_config() {
LOG_SENSOR("", "EZO", this);
@@ -103,7 +103,8 @@ void EZOSensor::loop() {
break;
}
ESP_LOGV(TAG, "Received buffer \"%s\" for command type %s", &buf[1], EZO_COMMAND_TYPE_STRINGS[to_run->command_type]);
ESP_LOGV(TAG, "Received buffer \"%s\" for command type %s", &buf[1],
LOG_STR_ARG(EzoCommandTypeStrings::get_log_str(to_run->command_type, EzoCommandTypeStrings::LAST_INDEX)));
if (buf[0] == 1) {
std::string payload = reinterpret_cast<char *>(&buf[1]);
@@ -168,7 +169,9 @@ void EZOSensor::add_command_(const char *command, EzoCommandType command_type, u
void EZOSensor::set_calibration_point_(EzoCalibrationType type, float value) {
// max 21: "Cal,"(4) + type(4) + ","(1) + float(11) + null; use 24 for safety
char payload[24];
snprintf(payload, sizeof(payload), "Cal,%s,%0.2f", EZO_CALIBRATION_TYPE_STRINGS[type], value);
ESPHOME_snprintf_P(payload, sizeof(payload), ESPHOME_PSTR("Cal,%s,%0.2f"),
LOG_STR_ARG(EzoCalibrationTypeStrings::get_log_str(type, EzoCalibrationTypeStrings::LAST_INDEX)),
value);
this->add_command_(payload, EzoCommandType::EZO_CALIBRATION, 900);
}
@@ -176,7 +179,7 @@ void EZOSensor::set_address(uint8_t address) {
if (address > 0 && address < 128) {
// max 8: "I2C,"(4) + uint8(3) + null
char payload[8];
snprintf(payload, sizeof(payload), "I2C,%u", address);
ESPHOME_snprintf_P(payload, sizeof(payload), ESPHOME_PSTR("I2C,%u"), address);
this->new_address_ = address;
this->add_command_(payload, EzoCommandType::EZO_I2C);
} else {
@@ -197,7 +200,7 @@ void EZOSensor::get_t() { this->add_command_("T,?", EzoCommandType::EZO_T); }
void EZOSensor::set_t(float value) {
// max 14 bytes: "T,"(2) + float with "%0.2f" (up to 11 chars) + null(1); use 16 for alignment
char payload[16];
snprintf(payload, sizeof(payload), "T,%0.2f", value);
ESPHOME_snprintf_P(payload, sizeof(payload), ESPHOME_PSTR("T,%0.2f"), value);
this->add_command_(payload, EzoCommandType::EZO_T);
}
@@ -220,7 +223,7 @@ void EZOSensor::set_calibration_point_high(float value) {
void EZOSensor::set_calibration_generic(float value) {
// exact 16 bytes: "Cal," (4) + float with "%0.2f" (up to 11 chars, e.g. "-9999999.99") + null (1) = 16
char payload[16];
snprintf(payload, sizeof(payload), "Cal,%0.2f", value);
ESPHOME_snprintf_P(payload, sizeof(payload), ESPHOME_PSTR("Cal,%0.2f"), value);
this->add_command_(payload, EzoCommandType::EZO_CALIBRATION, 900);
}
+1 -12
View File
@@ -34,10 +34,7 @@ void FT63X6Touchscreen::setup() {
this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_ANY_EDGE);
}
if (this->reset_pin_ != nullptr) {
this->reset_pin_->setup();
this->hard_reset_();
}
pulse_reset(this->reset_pin_, {.low_ms = 10});
// Get touch resolution
if (this->x_raw_max_ == this->x_raw_min_) {
@@ -57,14 +54,6 @@ void FT63X6Touchscreen::setup() {
this->write_byte(FT6X36_ADDR_TOUCHRATE_ACTIVE, 0x0E);
}
void FT63X6Touchscreen::hard_reset_() {
if (this->reset_pin_ != nullptr) {
this->reset_pin_->digital_write(false);
delay(10);
this->reset_pin_->digital_write(true);
}
}
void FT63X6Touchscreen::dump_config() {
ESP_LOGCONFIG(TAG, "FT63X6 Touchscreen:");
LOG_I2C_DEVICE(this);
-1
View File
@@ -27,7 +27,6 @@ class FT63X6Touchscreen final : public Touchscreen, public i2c::I2CDevice {
void set_threshold(uint8_t threshold) { this->threshold_ = threshold; }
protected:
void hard_reset_();
void update_touches() override;
InternalGPIOPin *interrupt_pin_{nullptr};
@@ -83,13 +83,7 @@ void GSL3670Touchscreen::update_touches() {
void GSL3670Touchscreen::clear_reg_() {
ESP_LOGD(TAG, "clear_reg");
// GPIO reset pulse
if (this->reset_pin_ != nullptr) {
this->reset_pin_->digital_write(false);
delay(1);
this->reset_pin_->digital_write(true);
delay(5);
}
pulse_reset(this->reset_pin_, {.low_ms = 1, .high_after_ms = 5});
this->write_reg8_(0x88, 0x01);
// delay(5);
@@ -106,12 +100,7 @@ void GSL3670Touchscreen::clear_reg_() {
void GSL3670Touchscreen::reset_() {
ESP_LOGD(TAG, "reset");
if (this->reset_pin_ != nullptr) {
this->reset_pin_->digital_write(false);
delay(1);
this->reset_pin_->digital_write(true);
delay(5);
}
pulse_reset(this->reset_pin_, {.low_ms = 1, .high_after_ms = 5});
this->write_reg8_(0xe4, 0x04);
+9 -17
View File
@@ -3,7 +3,11 @@ import logging
from esphome import automation
import esphome.codegen as cg
from esphome.components import climate, logger, uart
from esphome.components.climate import ClimateMode, ClimatePreset, ClimateSwingMode
from esphome.components.climate import (
CLIMATE_MODES,
CLIMATE_PRESETS,
CLIMATE_SWING_MODES,
)
from esphome.components.logger import DOMAIN as LOGGER_DOMAIN
from esphome.components.wifi import DOMAIN as WIFI_DOMAIN
import esphome.config_validation as cv
@@ -77,31 +81,19 @@ AIRFLOW_HORIZONTAL_DIRECTION_OPTIONS = AirflowHorizontalDirection.options(
)
SUPPORTED_SWING_MODES_OPTIONS = {
"OFF": ClimateSwingMode.CLIMATE_SWING_OFF,
"VERTICAL": ClimateSwingMode.CLIMATE_SWING_VERTICAL,
"HORIZONTAL": ClimateSwingMode.CLIMATE_SWING_HORIZONTAL,
"BOTH": ClimateSwingMode.CLIMATE_SWING_BOTH,
k: CLIMATE_SWING_MODES[k] for k in ("OFF", "VERTICAL", "HORIZONTAL", "BOTH")
}
SUPPORTED_CLIMATE_MODES_OPTIONS = {
"OFF": ClimateMode.CLIMATE_MODE_OFF, # always available
"HEAT_COOL": ClimateMode.CLIMATE_MODE_HEAT_COOL, # always available
"COOL": ClimateMode.CLIMATE_MODE_COOL,
"HEAT": ClimateMode.CLIMATE_MODE_HEAT,
"DRY": ClimateMode.CLIMATE_MODE_DRY,
"FAN_ONLY": ClimateMode.CLIMATE_MODE_FAN_ONLY,
k: CLIMATE_MODES[k] for k in ("OFF", "HEAT_COOL", "COOL", "HEAT", "DRY", "FAN_ONLY")
}
SUPPORTED_CLIMATE_PRESETS_SMARTAIR2_OPTIONS = {
"AWAY": ClimatePreset.CLIMATE_PRESET_AWAY,
"BOOST": ClimatePreset.CLIMATE_PRESET_BOOST,
"COMFORT": ClimatePreset.CLIMATE_PRESET_COMFORT,
k: CLIMATE_PRESETS[k] for k in ("AWAY", "BOOST", "COMFORT")
}
SUPPORTED_CLIMATE_PRESETS_HON_OPTIONS = {
"AWAY": ClimatePreset.CLIMATE_PRESET_AWAY,
"BOOST": ClimatePreset.CLIMATE_PRESET_BOOST,
"SLEEP": ClimatePreset.CLIMATE_PRESET_SLEEP,
k: CLIMATE_PRESETS[k] for k in ("AWAY", "BOOST", "SLEEP")
}
HonControlMethod = haier_ns.enum("HonControlMethod", True)
+14 -24
View File
@@ -6,6 +6,7 @@
#include "esphome/components/wifi/wifi_component.h"
#endif
#include "haier_base.h"
#include "esphome/core/progmem.h"
using namespace esphome::climate;
using namespace esphome::uart;
@@ -19,28 +20,16 @@ constexpr size_t PROTOCOL_INITIALIZATION_INTERVAL = 10000;
constexpr size_t DEFAULT_MESSAGES_INTERVAL_MS = 2000;
constexpr size_t CONTROL_MESSAGES_INTERVAL_MS = 400;
const char *HaierClimateBase::phase_to_string_(ProtocolPhases phase) {
static const char *phase_names[] = {
"SENDING_INIT_1",
"SENDING_INIT_2",
"SENDING_FIRST_STATUS_REQUEST",
"SENDING_FIRST_ALARM_STATUS_REQUEST",
"IDLE",
"SENDING_STATUS_REQUEST",
"SENDING_UPDATE_SIGNAL_REQUEST",
"SENDING_SIGNAL_LEVEL",
"SENDING_CONTROL",
"SENDING_ACTION_COMMAND",
"SENDING_ALARM_STATUS_REQUEST",
"UNKNOWN" // Should be the last!
};
static_assert(
(sizeof(phase_names) / sizeof(char *)) == (((int) ProtocolPhases::NUM_PROTOCOL_PHASES) + 1),
"Wrong phase_names array size. Please, make sure that this array is aligned with the enum ProtocolPhases");
int phase_index = (int) phase;
if ((phase_index > (int) ProtocolPhases::NUM_PROTOCOL_PHASES) || (phase_index < 0))
phase_index = (int) ProtocolPhases::NUM_PROTOCOL_PHASES;
return phase_names[phase_index];
PROGMEM_STRING_TABLE(ProtocolPhaseStrings, "SENDING_INIT_1", "SENDING_INIT_2", "SENDING_FIRST_STATUS_REQUEST",
"SENDING_FIRST_ALARM_STATUS_REQUEST", "IDLE", "SENDING_STATUS_REQUEST",
"SENDING_UPDATE_SIGNAL_REQUEST", "SENDING_SIGNAL_LEVEL", "SENDING_CONTROL",
"SENDING_ACTION_COMMAND", "SENDING_ALARM_STATUS_REQUEST", "UNKNOWN");
const LogString *HaierClimateBase::phase_to_string_(ProtocolPhases phase) {
static_assert(ProtocolPhaseStrings::COUNT == ((int) ProtocolPhases::NUM_PROTOCOL_PHASES) + 1,
"ProtocolPhaseStrings must match the enum ProtocolPhases, with UNKNOWN last");
// UNKNOWN (-1) wraps past the table and reads as the last entry, "UNKNOWN"
return ProtocolPhaseStrings::get_log_str(static_cast<uint8_t>(phase), ProtocolPhaseStrings::LAST_INDEX);
}
bool check_timeout(std::chrono::steady_clock::time_point now, std::chrono::steady_clock::time_point tpoint,
@@ -71,7 +60,8 @@ HaierClimateBase::~HaierClimateBase() {}
void HaierClimateBase::set_phase(ProtocolPhases phase) {
if (this->protocol_phase_ != phase) {
ESP_LOGV(TAG, "Phase transition: %s => %s", phase_to_string_(this->protocol_phase_), phase_to_string_(phase));
ESP_LOGV(TAG, "Phase transition: %s => %s", LOG_STR_ARG(phase_to_string_(this->protocol_phase_)),
LOG_STR_ARG(phase_to_string_(phase)));
this->protocol_phase_ = phase;
}
}
@@ -225,7 +215,7 @@ haier_protocol::HandlerError HaierClimateBase::report_network_status_answer_hand
haier_protocol::HandlerError HaierClimateBase::timeout_default_handler_(haier_protocol::FrameType request_type) {
ESP_LOGW(TAG, "Answer timeout for command %02X, phase %s", (uint8_t) request_type,
phase_to_string_(this->protocol_phase_));
LOG_STR_ARG(phase_to_string_(this->protocol_phase_)));
if (this->protocol_phase_ > ProtocolPhases::IDLE) {
this->set_phase(ProtocolPhases::IDLE);
} else {
+1 -1
View File
@@ -95,7 +95,7 @@ class HaierClimateBase : public esphome::Component,
SENDING_ALARM_STATUS_REQUEST,
NUM_PROTOCOL_PHASES
};
const char *phase_to_string_(ProtocolPhases phase);
const LogString *phase_to_string_(ProtocolPhases phase);
virtual void set_handlers() = 0;
virtual void process_phase(std::chrono::steady_clock::time_point now) = 0;
virtual haier_protocol::HaierMessage get_control_message() = 0; // NOLINT(readability-identifier-naming)
+1 -1
View File
@@ -564,7 +564,7 @@ void HonClimate::process_phase(std::chrono::steady_clock::time_point now) {
default:
// Shouldn't get here
ESP_LOGE(TAG, "Wrong protocol handler state: %s (%d), resetting communication",
phase_to_string_(this->protocol_phase_), (int) this->protocol_phase_);
LOG_STR_ARG(phase_to_string_(this->protocol_phase_)), (int) this->protocol_phase_);
this->set_phase(ProtocolPhases::SENDING_INIT_1);
break;
}
+6 -5
View File
@@ -9,12 +9,9 @@ from ..climate import (
CONF_HORIZONTAL_AIRFLOW,
CONF_VERTICAL_AIRFLOW,
HonClimate,
haier_ns,
)
CODEOWNERS = ["@paveldn"]
VerticalAirflowSelect = haier_ns.class_("VerticalAirflowSelect", select.Select)
HorizontalAirflowSelect = haier_ns.class_("HorizontalAirflowSelect", select.Select)
# Same order as VERTICAL_SWING_MODES_ORDER and HORIZONTAL_SWING_MODES_ORDER in hon_climate.h
AIRFLOW_VERTICAL_DIRECTION_OPTIONS = [
@@ -39,12 +36,12 @@ CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_HAIER_ID): cv.use_id(HonClimate),
cv.Optional(CONF_VERTICAL_AIRFLOW): select.select_schema(
VerticalAirflowSelect,
select.ApplySelect,
icon="mdi:arrow-expand-vertical",
entity_category=ENTITY_CATEGORY_CONFIG,
),
cv.Optional(CONF_HORIZONTAL_AIRFLOW): select.select_schema(
HorizontalAirflowSelect,
select.ApplySelect,
icon="mdi:arrow-expand-horizontal",
entity_category=ENTITY_CATEGORY_CONFIG,
),
@@ -59,9 +56,13 @@ async def to_code(config: ConfigType) -> None:
CONF_VERTICAL_AIRFLOW,
hub.set_vertical_airflow_select,
options=AIRFLOW_VERTICAL_DIRECTION_OPTIONS,
apply="set_vertical_airflow("
"haier::airflow_order_at(haier::VERTICAL_SWING_MODES_ORDER, index))",
)
await selects(
CONF_HORIZONTAL_AIRFLOW,
hub.set_horizontal_airflow_select,
options=AIRFLOW_HORIZONTAL_DIRECTION_OPTIONS,
apply="set_horizontal_airflow("
"haier::airflow_order_at(haier::HORIZONTAL_SWING_MODES_ORDER, index))",
)
@@ -1,15 +0,0 @@
#pragma once
#include "esphome/components/select/select.h"
#include "../hon_climate.h"
namespace esphome::haier {
class HorizontalAirflowSelect : public select::Select, public Parented<HonClimate> {
protected:
void control(size_t index) override {
this->parent_->set_horizontal_airflow(airflow_order_at(HORIZONTAL_SWING_MODES_ORDER, index));
}
};
} // namespace esphome::haier
@@ -1,15 +0,0 @@
#pragma once
#include "esphome/components/select/select.h"
#include "../hon_climate.h"
namespace esphome::haier {
class VerticalAirflowSelect : public select::Select, public Parented<HonClimate> {
protected:
void control(size_t index) override {
this->parent_->set_vertical_airflow(airflow_order_at(VERTICAL_SWING_MODES_ORDER, index));
}
};
} // namespace esphome::haier
@@ -93,7 +93,7 @@ haier_protocol::HandlerError Smartair2Climate::messages_timeout_handler_with_cyc
if (this->protocol_phase_ >= ProtocolPhases::IDLE)
return HaierClimateBase::timeout_default_handler_(message_type);
ESP_LOGI(TAG, "Answer timeout for command %02X, phase %s", (uint8_t) message_type,
phase_to_string_(this->protocol_phase_));
LOG_STR_ARG(phase_to_string_(this->protocol_phase_)));
ProtocolPhases new_phase = (ProtocolPhases) ((int) this->protocol_phase_ + 1);
if (new_phase >= ProtocolPhases::SENDING_FIRST_ALARM_STATUS_REQUEST)
new_phase = ProtocolPhases::SENDING_INIT_1;
@@ -217,7 +217,7 @@ void Smartair2Climate::process_phase(std::chrono::steady_clock::time_point now)
default:
// Shouldn't get here
ESP_LOGE(TAG, "Wrong protocol handler state: %s (%d), resetting communication",
phase_to_string_(this->protocol_phase_), (int) this->protocol_phase_);
LOG_STR_ARG(phase_to_string_(this->protocol_phase_)), (int) this->protocol_phase_);
this->set_phase(ProtocolPhases::SENDING_INIT_1);
break;
}
+3 -6
View File
@@ -1,6 +1,7 @@
#include "htu21d.h"
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome::htu21d {
@@ -112,11 +113,7 @@ void HTU21DComponent::set_heater(bool status) {
this->status_set_warning();
return;
}
if (status) {
raw_heater |= (1 << HTU21D_REG_HTRE_BIT);
} else {
raw_heater &= ~(1 << HTU21D_REG_HTRE_BIT);
}
assign_bits(raw_heater, 1 << HTU21D_REG_HTRE_BIT, status);
if (this->write_register(HTU21D_WRITERHT_REG_CMD, &raw_heater, 1) != i2c::ERROR_OK) {
this->status_set_warning();
return;
+1 -4
View File
@@ -40,10 +40,7 @@ RG_MODELS = {
# 1.200 - https://rainsensors.com/wp-content/uploads/sites/3/2022/03/2022.02.17-rev-1.200-rg-9_instructions.pdf
}
RG15_RESOLUTION = {
"low": RG15Resolution.FORCE_LOW,
"high": RG15Resolution.FORCE_HIGH,
}
RG15_RESOLUTION = RG15Resolution.options("low", "high", prefix="FORCE_")
SUPPORTED_OPTIONS = {
CONF_ACC: ["RG_15"],
@@ -270,21 +270,23 @@ void I2SAudioMicrophone::mic_task(void *params) {
{ // Ensures the samples vector is freed when the task stops
const size_t bytes_to_read = this_microphone->audio_stream_info_.ms_to_bytes(READ_DURATION_MS);
// Copied so the format cannot change while the task runs
const audio::AudioStreamInfo stream_info = this_microphone->audio_stream_info_;
const size_t bytes_to_read = stream_info.ms_to_bytes(READ_DURATION_MS);
std::vector<uint8_t> samples;
samples.reserve(bytes_to_read);
xEventGroupSetBits(this_microphone->event_group_, MicrophoneEventGroupBits::TASK_RUNNING);
while (!(xEventGroupGetBits(this_microphone->event_group_) & MicrophoneEventGroupBits::COMMAND_STOP)) {
if (this_microphone->data_callbacks_.size() > 0) {
if (this_microphone->has_data_callbacks_()) {
samples.resize(bytes_to_read);
size_t bytes_read = this_microphone->read_(samples.data(), bytes_to_read, 2 * READ_DURATION_MS);
samples.resize(bytes_read);
if (this_microphone->correct_dc_offset_) {
this_microphone->fix_dc_offset_(samples);
}
this_microphone->data_callbacks_.call(samples);
this_microphone->publish_audio_(samples, stream_info);
} else {
vTaskDelay(pdMS_TO_TICKS(READ_DURATION_MS));
}
+1 -6
View File
@@ -19,12 +19,7 @@ void ICNT86Touchscreen::setup() {
}
// Perform reset if necessary
if (this->reset_pin_ != nullptr) {
this->reset_pin_->setup();
this->reset_pin_->digital_write(false);
delay(10);
this->reset_pin_->digital_write(true);
}
pulse_reset(this->reset_pin_, {.low_ms = 10});
if (this->x_raw_max_ == this->x_raw_min_) {
this->x_raw_max_ = this->display_->get_native_width();
+4 -3
View File
@@ -1,8 +1,7 @@
"""Shared base for the infrared and radio_frequency entity components."""
import esphome.codegen as cg
from esphome.components import remote_base
from esphome.components.api import DOMAIN as API_DOMAIN
from esphome.const import CONF_API
from esphome.core import CORE
from esphome.core.entity_helpers import queue_entity_register
from esphome.types import ConfigType
@@ -24,8 +23,10 @@ async def attach_transmitter(var: cg.MockObj, config: ConfigType, key: str) -> N
does. Without this call anywhere in the build a request is answered as
soon as the frame is handed over.
"""
from esphome.components import remote_base
await remote_base.register_transmittable(var, config, key)
if API_DOMAIN in CORE.config:
if CONF_API in CORE.config:
cg.add_define("USE_IR_RF_TRANSMIT_COMPLETE")
@@ -2,11 +2,20 @@
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "esphome/core/progmem.h"
namespace esphome::kamstrup_kmp {
ESPHOME_LOG_TAG(TAG, "kamstrup_kmp");
// KMP units indexed by unit code; the empty first entry doubles as the fallback for unknown codes
PROGMEM_STRING_TABLE(KmpUnits, "", "Wh", "kWh", "MWh", "GWh", "J", "kJ", "MJ", "GJ", "Cal", "kCal", "Mcal", "Gcal",
"varh", "kvarh", "Mvarh", "Gvarh", "VAh", "kVAh", "MVAh", "GVAh", "kW", "kW", "MW", "GW", "kvar",
"kvar", "Mvar", "Gvar", "VA", "kVA", "MVA", "GVA", "V", "A", "kV", "kA", "C", "K", "l", "m3",
"l/h", "m3/h", "m3xC", "ton", "ton/h", "h", "hh:mm:ss", "yy:mm:dd", "yyyy:mm:dd", "mm:dd", "",
"bar", "RTC", "ASCII", "m3 x 10", "ton x 10", "GJ x 10", "minutes", "Bitfield", "s", "ms", "days",
"RTC-Q", "Datetime");
void KamstrupKMPComponent::dump_config() {
ESP_LOGCONFIG(TAG, "kamstrup_kmp:");
LOG_UPDATE_INTERVAL(this);
@@ -237,8 +246,6 @@ void KamstrupKMPComponent::parse_command_message_(uint16_t command, const uint8_
}
void KamstrupKMPComponent::set_sensor_value_(uint16_t command, float value, uint8_t unit_idx) {
const char *unit = unit_idx < sizeof(UNITS) / sizeof(UNITS[0]) ? UNITS[unit_idx] : "";
// Standard sensors
if (command == CMD_HEAT_ENERGY && this->heat_energy_sensor_ != nullptr) {
this->heat_energy_sensor_->publish_state(value);
@@ -263,7 +270,8 @@ void KamstrupKMPComponent::set_sensor_value_(uint16_t command, float value, uint
}
}
ESP_LOGD(TAG, "Received value for command 0x%04X: %.3f [%s]", command, value, unit);
ESP_LOGD(TAG, "Received value for command 0x%04X: %.3f [%s]", command, value,
LOG_STR_ARG(KmpUnits::get_log_str(unit_idx, 0)));
}
} // namespace esphome::kamstrup_kmp
@@ -63,16 +63,6 @@ static const uint16_t CMD_TEMP_DIFF = 0x0059;
static const uint16_t CMD_FLOW = 0x004A;
static const uint16_t CMD_VOLUME = 0x0044;
// KMP units
static const char *const UNITS[] = {
"", "Wh", "kWh", "MWh", "GWh", "J", "kJ", "MJ", "GJ", "Cal",
"kCal", "Mcal", "Gcal", "varh", "kvarh", "Mvarh", "Gvarh", "VAh", "kVAh", "MVAh",
"GVAh", "kW", "kW", "MW", "GW", "kvar", "kvar", "Mvar", "Gvar", "VA",
"kVA", "MVA", "GVA", "V", "A", "kV", "kA", "C", "K", "l",
"m3", "l/h", "m3/h", "m3xC", "ton", "ton/h", "h", "hh:mm:ss", "yy:mm:dd", "yyyy:mm:dd",
"mm:dd", "", "bar", "RTC", "ASCII", "m3 x 10", "ton x 10", "GJ x 10", "minutes", "Bitfield",
"s", "ms", "days", "RTC-Q", "Datetime"};
class KamstrupKMPComponent final : public PollingComponent, public uart::UARTDevice {
public:
void set_heat_energy_sensor(sensor::Sensor *sensor) { this->heat_energy_sensor_ = sensor; }
+18 -10
View File
@@ -13,11 +13,7 @@ from esphome.const import (
)
from esphome.types import ConfigType
from .. import CONF_LD2410_ID, LD2410Component, ld2410_ns
BaudRateSelect = ld2410_ns.class_("BaudRateSelect", select.Select)
DistanceResolutionSelect = ld2410_ns.class_("DistanceResolutionSelect", select.Select)
LightOutControlSelect = ld2410_ns.class_("LightOutControlSelect", select.Select)
from .. import CONF_LD2410_ID, LD2410Component
CONF_DISTANCE_RESOLUTION = "distance_resolution"
CONF_LIGHT_FUNCTION = "light_function"
@@ -27,22 +23,22 @@ CONFIG_SCHEMA = {
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component),
cv.Optional(CONF_DISTANCE_RESOLUTION): select.select_schema(
DistanceResolutionSelect,
select.ApplySelect,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_RULER,
),
cv.Optional(CONF_LIGHT_FUNCTION): select.select_schema(
LightOutControlSelect,
select.ApplySelect,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_LIGHTBULB,
),
cv.Optional(CONF_OUT_PIN_LEVEL): select.select_schema(
LightOutControlSelect,
select.ApplySelect,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_SCALE,
),
cv.Optional(CONF_BAUD_RATE): select.select_schema(
BaudRateSelect,
select.ApplySelect,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_THERMOMETER,
),
@@ -57,18 +53,30 @@ async def to_code(config: ConfigType) -> None:
CONF_DISTANCE_RESOLUTION,
hub.set_distance_resolution_select,
options=["0.2m", "0.75m"],
apply=(
"set_distance_resolution(static_cast<ld2410::DistanceResolution>("
"progmem_read_byte(&ld2410::DISTANCE_RESOLUTIONS[index])))"
),
publish="before",
)
light = {"apply": "set_light_out_control()", "publish": "before"}
await selects(
CONF_OUT_PIN_LEVEL, hub.set_out_pin_level_select, options=["low", "high"]
CONF_OUT_PIN_LEVEL,
hub.set_out_pin_level_select,
options=["low", "high"],
**light,
)
await selects(
CONF_LIGHT_FUNCTION,
hub.set_light_function_select,
options=["off", "below", "above"],
**light,
)
await selects(
CONF_BAUD_RATE,
hub.set_baud_rate_select,
apply="set_baud_rate(static_cast<ld2410::BaudRate>(index + 1))",
publish="before",
options=[
"9600",
"19200",
@@ -1,10 +0,0 @@
#include "baud_rate_select.h"
namespace esphome::ld2410 {
void BaudRateSelect::control(size_t index) {
this->publish_state(index);
this->parent_->set_baud_rate(static_cast<BaudRate>(index + 1));
}
} // namespace esphome::ld2410
@@ -1,16 +0,0 @@
#pragma once
#include "esphome/components/select/select.h"
#include "../ld2410.h"
namespace esphome::ld2410 {
class BaudRateSelect final : public select::Select, public Parented<LD2410Component> {
public:
BaudRateSelect() = default;
protected:
void control(size_t index) override;
};
} // namespace esphome::ld2410
@@ -1,11 +0,0 @@
#include "distance_resolution_select.h"
namespace esphome::ld2410 {
void DistanceResolutionSelect::control(size_t index) {
this->publish_state(index);
this->parent_->set_distance_resolution(
static_cast<DistanceResolution>(progmem_read_byte(&DISTANCE_RESOLUTIONS[index])));
}
} // namespace esphome::ld2410
@@ -1,16 +0,0 @@
#pragma once
#include "esphome/components/select/select.h"
#include "../ld2410.h"
namespace esphome::ld2410 {
class DistanceResolutionSelect final : public select::Select, public Parented<LD2410Component> {
public:
DistanceResolutionSelect() = default;
protected:
void control(size_t index) override;
};
} // namespace esphome::ld2410
@@ -1,10 +0,0 @@
#include "light_out_control_select.h"
namespace esphome::ld2410 {
void LightOutControlSelect::control(size_t index) {
this->publish_state(index);
this->parent_->set_light_out_control();
}
} // namespace esphome::ld2410
@@ -1,16 +0,0 @@
#pragma once
#include "esphome/components/select/select.h"
#include "../ld2410.h"
namespace esphome::ld2410 {
class LightOutControlSelect final : public select::Select, public Parented<LD2410Component> {
public:
LightOutControlSelect() = default;
protected:
void control(size_t index) override;
};
} // namespace esphome::ld2410
+13 -8
View File
@@ -13,11 +13,7 @@ from esphome.const import (
)
from esphome.types import ConfigType
from .. import CONF_LD2412_ID, LD2412_ns, LD2412Component
BaudRateSelect = LD2412_ns.class_("BaudRateSelect", select.Select)
DistanceResolutionSelect = LD2412_ns.class_("DistanceResolutionSelect", select.Select)
LightOutControlSelect = LD2412_ns.class_("LightOutControlSelect", select.Select)
from .. import CONF_LD2412_ID, LD2412Component
CONF_DISTANCE_RESOLUTION = "distance_resolution"
CONF_LIGHT_FUNCTION = "light_function"
@@ -27,17 +23,17 @@ CONFIG_SCHEMA = {
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
cv.GenerateID(CONF_LD2412_ID): cv.use_id(LD2412Component),
cv.Optional(CONF_BAUD_RATE): select.select_schema(
BaudRateSelect,
select.ApplySelect,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_THERMOMETER,
),
cv.Optional(CONF_DISTANCE_RESOLUTION): select.select_schema(
DistanceResolutionSelect,
select.ApplySelect,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_RULER,
),
cv.Optional(CONF_LIGHT_FUNCTION): select.select_schema(
LightOutControlSelect,
select.ApplySelect,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_LIGHTBULB,
),
@@ -66,16 +62,25 @@ async def to_code(config: ConfigType) -> None:
"256000",
"460800",
],
apply="set_baud_rate(static_cast<ld2412::BaudRate>(index + 1))",
publish="before",
)
await selects(
CONF_DISTANCE_RESOLUTION,
hub.set_distance_resolution_select,
options=["0.2m", "0.5m", "0.75m"],
apply=(
"set_distance_resolution(static_cast<ld2412::DistanceResolution>("
"progmem_read_byte(&ld2412::DISTANCE_RESOLUTIONS[index])))"
),
publish="before",
)
await selects(
CONF_LIGHT_FUNCTION,
hub.set_light_function_select,
options=["off", "below", "above"],
apply="set_light_out_control()",
publish="before",
)
# The pin level travels in the basic configuration command, not the light control one
await selects(
@@ -1,10 +0,0 @@
#include "baud_rate_select.h"
namespace esphome::ld2412 {
void BaudRateSelect::control(size_t index) {
this->publish_state(index);
this->parent_->set_baud_rate(static_cast<BaudRate>(index + 1));
}
} // namespace esphome::ld2412
@@ -1,17 +0,0 @@
#pragma once
#include "esphome/components/select/select.h"
#include "../ld2412.h"
namespace esphome::ld2412 {
class BaudRateSelect final : public select::Select, public Parented<LD2412Component> {
public:
// User provided, not "= default": `new(p) BaudRateSelect()` would zero-fill .bss that is already zero.
BaudRateSelect() {}
protected:
void control(size_t index) override;
};
} // namespace esphome::ld2412
@@ -1,11 +0,0 @@
#include "distance_resolution_select.h"
namespace esphome::ld2412 {
void DistanceResolutionSelect::control(size_t index) {
this->publish_state(index);
this->parent_->set_distance_resolution(
static_cast<DistanceResolution>(progmem_read_byte(&DISTANCE_RESOLUTIONS[index])));
}
} // namespace esphome::ld2412
@@ -1,17 +0,0 @@
#pragma once
#include "esphome/components/select/select.h"
#include "../ld2412.h"
namespace esphome::ld2412 {
class DistanceResolutionSelect final : public select::Select, public Parented<LD2412Component> {
public:
// User provided, not "= default": `new(p) DistanceResolutionSelect()` would zero-fill .bss that is already zero.
DistanceResolutionSelect() {}
protected:
void control(size_t index) override;
};
} // namespace esphome::ld2412
@@ -1,10 +0,0 @@
#include "light_out_control_select.h"
namespace esphome::ld2412 {
void LightOutControlSelect::control(size_t index) {
this->publish_state(index);
this->parent_->set_light_out_control();
}
} // namespace esphome::ld2412
@@ -1,17 +0,0 @@
#pragma once
#include "esphome/components/select/select.h"
#include "../ld2412.h"
namespace esphome::ld2412 {
class LightOutControlSelect final : public select::Select, public Parented<LD2412Component> {
public:
// User provided, not "= default": `new(p) LightOutControlSelect()` would zero-fill .bss that is already zero.
LightOutControlSelect() {}
protected:
void control(size_t index) override;
};
} // namespace esphome::ld2412
+7 -4
View File
@@ -4,7 +4,7 @@ import esphome.config_validation as cv
from esphome.const import ENTITY_CATEGORY_CONFIG
from esphome.types import ConfigType
from .. import CONF_LD2420_ID, LD2420Component, ld2420_ns
from .. import CONF_LD2420_ID, LD2420Component
CONF_OPERATING_MODE = "operating_mode"
CONF_SELECTS = [
@@ -13,12 +13,11 @@ CONF_SELECTS = [
"Simple",
]
LD2420Select = ld2420_ns.class_("LD2420Select", select.Select, cg.Component)
CONFIG_SCHEMA = {
cv.GenerateID(CONF_LD2420_ID): cv.use_id(LD2420Component),
cv.Required(CONF_OPERATING_MODE): select.select_schema(
LD2420Select,
select.ApplySelect,
entity_category=ENTITY_CATEGORY_CONFIG,
),
}
@@ -28,5 +27,9 @@ async def to_code(config: ConfigType) -> None:
hub = await cg.get_variable(config[CONF_LD2420_ID])
selects = select.sub_selects(config, parent=hub)
await selects(
CONF_OPERATING_MODE, hub.set_operating_mode_select, options=CONF_SELECTS
CONF_OPERATING_MODE,
hub.set_operating_mode_select,
options=CONF_SELECTS,
apply="set_operating_mode(static_cast<ld2420::OpMode>(index + 1))",
publish="before",
)
@@ -1,14 +0,0 @@
#include "operating_mode_select.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome::ld2420 {
ESPHOME_LOG_TAG(TAG, "ld2420.select");
void LD2420Select::control(size_t index) {
this->publish_state(index);
this->parent_->set_operating_mode(static_cast<OpMode>(index + 1));
}
} // namespace esphome::ld2420
@@ -1,16 +0,0 @@
#pragma once
#include "../ld2420.h"
#include "esphome/components/select/select.h"
namespace esphome::ld2420 {
class LD2420Select final : public Component, public select::Select, public Parented<LD2420Component> {
public:
LD2420Select() = default;
protected:
void control(size_t index) override;
};
} // namespace esphome::ld2420
+7 -5
View File
@@ -9,23 +9,21 @@ from esphome.const import (
)
from esphome.types import ConfigType
from .. import CONF_LD2450_ID, LD2450Component, ld2450_ns
from .. import CONF_LD2450_ID, LD2450Component
CONF_ZONE_TYPE = "zone_type"
BaudRateSelect = ld2450_ns.class_("BaudRateSelect", select.Select)
ZoneTypeSelect = ld2450_ns.class_("ZoneTypeSelect", select.Select)
CONFIG_SCHEMA = {
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component),
cv.Optional(CONF_BAUD_RATE): select.select_schema(
BaudRateSelect,
select.ApplySelect,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_THERMOMETER,
),
cv.Optional(CONF_ZONE_TYPE): select.select_schema(
ZoneTypeSelect,
select.ApplySelect,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_THERMOMETER,
),
@@ -49,6 +47,8 @@ async def to_code(config: ConfigType) -> None:
"256000",
"460800",
],
apply="set_baud_rate(static_cast<ld2450::BaudRate>(index + 1))",
publish="before",
)
await selects(
CONF_ZONE_TYPE,
@@ -58,4 +58,6 @@ async def to_code(config: ConfigType) -> None:
"Detection",
"Filter",
],
apply="set_zone_type(static_cast<ld2450::ZoneType>(index))",
publish="before",
)
@@ -1,10 +0,0 @@
#include "baud_rate_select.h"
namespace esphome::ld2450 {
void BaudRateSelect::control(size_t index) {
this->publish_state(index);
this->parent_->set_baud_rate(static_cast<BaudRate>(index + 1));
}
} // namespace esphome::ld2450
@@ -1,17 +0,0 @@
#pragma once
#include "esphome/components/select/select.h"
#include "../ld2450.h"
namespace esphome::ld2450 {
class BaudRateSelect : public select::Select, public Parented<LD2450Component> {
public:
// User provided, not "= default": `new(p) BaudRateSelect()` would zero-fill .bss that is already zero.
BaudRateSelect() {}
protected:
void control(size_t index) override;
};
} // namespace esphome::ld2450
@@ -1,10 +0,0 @@
#include "zone_type_select.h"
namespace esphome::ld2450 {
void ZoneTypeSelect::control(size_t index) {
this->publish_state(index);
this->parent_->set_zone_type(static_cast<ZoneType>(index));
}
} // namespace esphome::ld2450
@@ -1,17 +0,0 @@
#pragma once
#include "esphome/components/select/select.h"
#include "../ld2450.h"
namespace esphome::ld2450 {
class ZoneTypeSelect : public select::Select, public Parented<LD2450Component> {
public:
// User provided, not "= default": `new(p) ZoneTypeSelect()` would zero-fill .bss that is already zero.
ZoneTypeSelect() {}
protected:
void control(size_t index) override;
};
} // namespace esphome::ld2450
+33 -2
View File
@@ -2,10 +2,11 @@ from esphome import pins
import esphome.codegen as cg
from esphome.components import uart
import esphome.config_validation as cv
from esphome.const import CONF_WAKEUP_PIN
from esphome.const import CONF_PLATFORM, CONF_WAKEUP_PIN
import esphome.final_validate as fv
from esphome.types import ConfigType
from .const import CONF_AUTO_WAKE, CONF_WAKEUP_PULSE
from .const import CONF_AREA_CONFIG, CONF_AUTO_WAKE, CONF_LD6002B_ID, CONF_WAKEUP_PULSE
CODEOWNERS = ["@hepter"]
DEPENDENCIES = ["uart"]
@@ -69,3 +70,33 @@ async def to_code(config: ConfigType) -> None:
cg.add(var.set_wakeup_pulse_ms(config[CONF_WAKEUP_PULSE].total_milliseconds))
cg.add(var.set_auto_wake(config[CONF_AUTO_WAKE]))
def validate_unique_settings(domain: str, config: ConfigType) -> None:
"""Reject a setting set in two platform blocks of one hub, which keeps one entity per setting."""
hub_id = config[CONF_LD6002B_ID]
entries = [
entry
for entry in fv.full_config.get().get(domain, [])
if entry.get(CONF_PLATFORM) == DOMAIN and entry.get(CONF_LD6002B_ID) == hub_id
]
for key, value in config.items():
if key in (CONF_PLATFORM, CONF_LD6002B_ID):
continue
# area_config groups six settings, so each one is checked on its own
for path in (
[[key, sub] for sub in value] if key == CONF_AREA_CONFIG else [[key]]
):
if sum(_has_path(entry, path) for entry in entries) > 1:
raise cv.Invalid(
f"{'.'.join(path)} is already set for this ld6002b in another {domain} block",
path=path,
)
def _has_path(entry: ConfigType, path: list[str]) -> bool:
for key in path:
if key not in entry:
return False
entry = entry[key]
return True
+47 -20
View File
@@ -1,3 +1,4 @@
from esphome.automation import parent_ref
import esphome.codegen as cg
from esphome.components import button
from esphome.components.select import DOMAIN as SELECT_DOMAIN
@@ -9,6 +10,7 @@ from esphome.const import (
ENTITY_CATEGORY_CONFIG,
ENTITY_CATEGORY_DIAGNOSTIC,
)
from esphome.cpp_generator import ParameterListExpression
import esphome.final_validate as fv
from esphome.types import ConfigType
@@ -33,53 +35,52 @@ from ..const import (
DEPENDENCIES = ["ld6002b"]
LD6002BButton = ld6002b_ns.class_("LD6002BButton", button.Button)
ButtonType = ld6002b_ns.enum("ButtonType", is_class=True)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_LD6002B_ID): cv.use_id(LD6002BComponent),
cv.Optional(CONF_APPLY_AREA): button.button_schema(
LD6002BButton, entity_category=ENTITY_CATEGORY_CONFIG
button.ApplyButton, entity_category=ENTITY_CATEGORY_CONFIG
),
cv.Optional(CONF_AUTO_INTERFERENCE): button.button_schema(
LD6002BButton, entity_category=ENTITY_CATEGORY_CONFIG
button.ApplyButton, entity_category=ENTITY_CATEGORY_CONFIG
),
cv.Optional(CONF_GET_AREAS): button.button_schema(
LD6002BButton, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
button.ApplyButton, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
),
cv.Optional(CONF_CLEAR_INTERFERENCE): button.button_schema(
LD6002BButton, entity_category=ENTITY_CATEGORY_CONFIG
button.ApplyButton, entity_category=ENTITY_CATEGORY_CONFIG
),
cv.Optional(CONF_RESET_DETECTION_AREA): button.button_schema(
LD6002BButton, entity_category=ENTITY_CATEGORY_CONFIG
button.ApplyButton, entity_category=ENTITY_CATEGORY_CONFIG
),
cv.Optional(CONF_GET_DELAY): button.button_schema(
LD6002BButton, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
button.ApplyButton, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
),
cv.Optional(CONF_GET_SENSITIVITY): button.button_schema(
LD6002BButton, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
button.ApplyButton, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
),
cv.Optional(CONF_GET_TRIGGER_SPEED): button.button_schema(
LD6002BButton, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
button.ApplyButton, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
),
cv.Optional(CONF_GET_Z_RANGE): button.button_schema(
LD6002BButton, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
button.ApplyButton, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
),
cv.Optional(CONF_GET_INSTALLATION): button.button_schema(
LD6002BButton, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
button.ApplyButton, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
),
cv.Optional(CONF_GET_LOW_POWER_MODE): button.button_schema(
LD6002BButton, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
button.ApplyButton, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
),
cv.Optional(CONF_GET_LOW_POWER_SLEEP_TIME): button.button_schema(
LD6002BButton, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
button.ApplyButton, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
),
cv.Optional(CONF_RESET_UNATTENDED): button.button_schema(
LD6002BButton, entity_category=ENTITY_CATEGORY_CONFIG
button.ApplyButton, entity_category=ENTITY_CATEGORY_CONFIG
),
cv.Optional(CONF_WAKE): button.button_schema(
LD6002BButton, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
button.ApplyButton, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
),
}
)
@@ -130,8 +131,34 @@ BUTTON_MAP = {
async def to_code(config: ConfigType) -> None:
for key, button_type in BUTTON_MAP.items():
if button_config := config.get(key):
b = cg.new_Pvariable(button_config[CONF_ID], button_type)
await button.register_button(b, button_config)
await cg.register_parented(b, config[CONF_LD6002B_ID])
confs = [
(button_type, conf)
for key, button_type in BUTTON_MAP.items()
if (conf := config.get(key)) is not None
]
if not confs:
return
hub = await cg.get_variable(config[CONF_LD6002B_ID])
params = button.APPLY_ENTITY.params
# One function for all buttons, picking the type by pointer: one class instead of one per button.
# Named after the first button so two button blocks on one hub do not clash.
fn = f"esphome__{confs[0][1][CONF_ID].id}__button_fn"
cg.add_global(
cg.RawStatement(f"static void {fn}({ParameterListExpression(*params)});")
)
template = cg.TemplateArguments(cg.RawExpression(fn))
created = [
(button_type, await button.new_button(conf, template))
for button_type, conf in confs
]
*head, (last_type, _) = created
chain = "".join(f"entity == {parent_ref(var)} ? {t} : " for t, var in head)
cg.static_function(
fn,
cg.void,
list(params),
[
f"const auto type = {chain}{last_type};",
f"{parent_ref(hub)}->press_button(type);",
],
)
@@ -1,7 +0,0 @@
#include "ld6002b_button.h"
namespace esphome::ld6002b {
void LD6002BButton::press_action() { this->parent_->press_button(this->type_); }
} // namespace esphome::ld6002b
@@ -1,18 +0,0 @@
#pragma once
#include "esphome/components/button/button.h"
#include "../ld6002b.h"
namespace esphome::ld6002b {
class LD6002BButton : public button::Button, public Parented<LD6002BComponent> {
public:
explicit LD6002BButton(ButtonType type) : type_(type) {}
protected:
void press_action() override;
ButtonType type_;
};
} // namespace esphome::ld6002b
+88 -108
View File
@@ -1336,92 +1336,74 @@ void LD6002BComponent::wake_() {
[this]() { this->wakeup_pin_->digital_write(true); });
}
void LD6002BComponent::set_number_value(NumberType type, float value) {
switch (type) {
case NumberType::HOLD_DELAY: {
uint32_t delay = static_cast<uint32_t>(value);
uint8_t data[4];
write_u32_le(data, delay);
this->queue_command_(TYPE_SET_HOLD_DELAY, data, sizeof(data));
break;
}
case NumberType::Z_MIN:
this->z_min_ = value;
this->send_z_range_();
break;
case NumberType::Z_MAX:
this->z_max_ = value;
this->send_z_range_();
break;
case NumberType::LOW_POWER_SLEEP: {
uint32_t sleep_ms = static_cast<uint32_t>(value);
uint8_t data[4];
write_u32_le(data, sleep_ms);
this->queue_command_(TYPE_SET_LOW_POWER_SLEEP, data, sizeof(data));
break;
}
case NumberType::AREA_X_MIN:
this->area_x_min_ = value;
this->area_edits_.x_min = value;
break;
case NumberType::AREA_X_MAX:
this->area_x_max_ = value;
this->area_edits_.x_max = value;
break;
case NumberType::AREA_Y_MIN:
this->area_y_min_ = value;
this->area_edits_.y_min = value;
break;
case NumberType::AREA_Y_MAX:
this->area_y_max_ = value;
this->area_edits_.y_max = value;
break;
case NumberType::AREA_Z_MIN:
this->area_z_min_ = value;
this->area_edits_.z_min = value;
break;
case NumberType::AREA_Z_MAX:
this->area_z_max_ = value;
this->area_edits_.z_max = value;
break;
#ifdef USE_NUMBER
// Each setter picks its setting by entity pointer, so the entities share one generated class.
void LD6002BComponent::set_number_value(number::Number *entity, float value) {
if (entity == this->hold_delay_number_ || entity == this->low_power_sleep_number_) {
uint8_t data[4];
write_u32_le(data, static_cast<uint32_t>(value));
this->queue_command_(entity == this->hold_delay_number_ ? TYPE_SET_HOLD_DELAY : TYPE_SET_LOW_POWER_SLEEP, data,
sizeof(data));
} else if (entity == this->z_min_number_) {
this->z_min_ = value;
this->send_z_range_();
} else if (entity == this->z_max_number_) {
this->z_max_ = value;
this->send_z_range_();
} else if (entity == this->area_x_min_number_) {
this->area_x_min_ = value;
this->area_edits_.x_min = value;
} else if (entity == this->area_x_max_number_) {
this->area_x_max_ = value;
this->area_edits_.x_max = value;
} else if (entity == this->area_y_min_number_) {
this->area_y_min_ = value;
this->area_edits_.y_min = value;
} else if (entity == this->area_y_max_number_) {
this->area_y_max_ = value;
this->area_edits_.y_max = value;
} else if (entity == this->area_z_min_number_) {
this->area_z_min_ = value;
this->area_edits_.z_min = value;
} else { // Only registered entities call this, so the last one needs no compare
this->area_z_max_ = value;
this->area_edits_.z_max = value;
}
}
#endif
void LD6002BComponent::set_select_value(SelectType type, size_t index) {
switch (type) {
case SelectType::SENSITIVITY:
if (index == 0) {
this->send_control_command_(CMD_SENSITIVITY_LOW);
} else if (index == 1) {
this->send_control_command_(CMD_SENSITIVITY_MEDIUM);
} else if (index == 2) {
this->send_control_command_(CMD_SENSITIVITY_HIGH);
}
break;
case SelectType::TRIGGER_SPEED:
if (index == 0) {
this->send_control_command_(CMD_TRIGGER_SLOW);
} else if (index == 1) {
this->send_control_command_(CMD_TRIGGER_MEDIUM);
} else if (index == 2) {
this->send_control_command_(CMD_TRIGGER_FAST);
}
break;
case SelectType::INSTALLATION_MODE:
if (index == 0) {
this->send_control_command_(CMD_INSTALL_TOP);
} else if (index == 1) {
this->send_control_command_(CMD_INSTALL_SIDE);
}
break;
case SelectType::AREA_ID:
this->area_id_ = static_cast<uint8_t>(index);
this->area_id_set_ = true;
this->update_area_numbers_for_id_(this->area_id_);
this->save_area_id_pref_(this->area_id_);
break;
#ifdef USE_SELECT
void LD6002BComponent::set_select_value(select::Select *entity, size_t index) {
if (entity == this->sensitivity_select_) {
if (index == 0) {
this->send_control_command_(CMD_SENSITIVITY_LOW);
} else if (index == 1) {
this->send_control_command_(CMD_SENSITIVITY_MEDIUM);
} else if (index == 2) {
this->send_control_command_(CMD_SENSITIVITY_HIGH);
}
} else if (entity == this->trigger_speed_select_) {
if (index == 0) {
this->send_control_command_(CMD_TRIGGER_SLOW);
} else if (index == 1) {
this->send_control_command_(CMD_TRIGGER_MEDIUM);
} else if (index == 2) {
this->send_control_command_(CMD_TRIGGER_FAST);
}
} else if (entity == this->installation_select_) {
if (index == 0) {
this->send_control_command_(CMD_INSTALL_TOP);
} else if (index == 1) {
this->send_control_command_(CMD_INSTALL_SIDE);
}
} else { // Only registered entities call this, so the last one needs no compare
this->area_id_ = static_cast<uint8_t>(index);
this->area_id_set_ = true;
this->update_area_numbers_for_id_(this->area_id_);
this->save_area_id_pref_(this->area_id_);
}
}
#endif
void LD6002BComponent::update_area_numbers_(const AreaConfig &area) {
// A report refreshes every axis the user is not in the middle of changing. An
@@ -1749,37 +1731,35 @@ void LD6002BComponent::clear_target_state_() {
}
}
void LD6002BComponent::set_switch_state(SwitchType type, bool state) {
switch (type) {
case SwitchType::LOW_POWER:
this->low_power_enabled_ = state;
this->low_power_reported_ = true;
this->send_control_command_(state ? CMD_LOW_POWER_ON : CMD_LOW_POWER_OFF);
this->update_work_mode_fallback_();
break;
case SwitchType::POINT_CLOUD:
this->point_cloud_enabled_ = state;
this->send_control_command_(state ? CMD_POINT_CLOUD_ON : CMD_POINT_CLOUD_OFF);
#ifdef USE_SWITCH
void LD6002BComponent::set_switch_state(switch_::Switch *entity, bool state) {
if (entity == this->low_power_switch_) {
this->low_power_enabled_ = state;
this->low_power_reported_ = true;
this->send_control_command_(state ? CMD_LOW_POWER_ON : CMD_LOW_POWER_OFF);
this->update_work_mode_fallback_();
} else if (entity == this->point_cloud_switch_) {
this->point_cloud_enabled_ = state;
this->send_control_command_(state ? CMD_POINT_CLOUD_ON : CMD_POINT_CLOUD_OFF);
#ifdef USE_SENSOR
// The count only moves while the stream runs, so the last one would stand as
// a live reading. The dedup sentinel is cleared with it: the same count is
// new again when the stream comes back.
if (!state && this->point_count_sensor_ != nullptr && this->last_point_count_ != 0xFFFFFFFF) {
this->point_count_sensor_->publish_state(NAN);
this->last_point_count_ = 0xFFFFFFFF;
}
// The count only moves while the stream runs, so the last one would stand as
// a live reading. The dedup sentinel is cleared with it: the same count is
// new again when the stream comes back.
if (!state && this->point_count_sensor_ != nullptr && this->last_point_count_ != 0xFFFFFFFF) {
this->point_count_sensor_->publish_state(NAN);
this->last_point_count_ = 0xFFFFFFFF;
}
#endif
break;
case SwitchType::TARGET_DISPLAY:
this->target_display_enabled_ = state;
this->send_control_command_(state ? CMD_TARGET_DISPLAY_ON : CMD_TARGET_DISPLAY_OFF);
if (!state) {
// Every target entity is fed by the reports this just stopped.
this->clear_target_state_();
}
break;
} else { // Only registered entities call this, so the last one needs no compare
this->target_display_enabled_ = state;
this->send_control_command_(state ? CMD_TARGET_DISPLAY_ON : CMD_TARGET_DISPLAY_OFF);
if (!state) {
// Every target entity is fed by the reports this just stopped.
this->clear_target_state_();
}
}
}
#endif
void LD6002BComponent::press_button(ButtonType type) {
switch (type) {
+9 -29
View File
@@ -40,32 +40,6 @@ static constexpr size_t DEFAULT_MAX_DATA_LEN_POINT_CLOUD = 4096;
// Largest protocol payload is TYPE_SET_AREA: int32 area id + 6 floats = 28 bytes.
static constexpr size_t CMD_MAX_DATA_LEN = 28;
enum class NumberType : uint8_t {
HOLD_DELAY,
Z_MIN,
Z_MAX,
LOW_POWER_SLEEP,
AREA_X_MIN,
AREA_X_MAX,
AREA_Y_MIN,
AREA_Y_MAX,
AREA_Z_MIN,
AREA_Z_MAX,
};
enum class SelectType : uint8_t {
SENSITIVITY,
TRIGGER_SPEED,
INSTALLATION_MODE,
AREA_ID,
};
enum class SwitchType : uint8_t {
LOW_POWER,
POINT_CLOUD,
TARGET_DISPLAY,
};
enum class ButtonType : uint8_t {
APPLY_AREA,
AUTO_INTERFERENCE,
@@ -264,9 +238,15 @@ class LD6002BComponent : public Component, public uart::UARTDevice {
void set_target_display_switch(switch_::Switch *sw) { this->target_display_switch_ = sw; }
#endif
void set_number_value(NumberType type, float value);
void set_select_value(SelectType type, size_t index);
void set_switch_state(SwitchType type, bool state);
#ifdef USE_NUMBER
void set_number_value(number::Number *entity, float value);
#endif
#ifdef USE_SELECT
void set_select_value(select::Select *entity, size_t index);
#endif
#ifdef USE_SWITCH
void set_switch_state(switch_::Switch *entity, bool state);
#endif
void press_button(ButtonType type);
protected:
+41 -51
View File
@@ -15,7 +15,7 @@ from esphome.const import (
import esphome.final_validate as fv
from esphome.types import ConfigType
from .. import LD6002BComponent, ld6002b_ns
from .. import LD6002BComponent, validate_unique_settings
from ..const import (
CONF_APPLY_AREA,
CONF_AREA_CONFIG,
@@ -32,32 +32,29 @@ from ..const import (
DEPENDENCIES = ["ld6002b"]
LD6002BNumber = ld6002b_ns.class_("LD6002BNumber", number.Number)
NumberType = ld6002b_ns.enum("NumberType", is_class=True)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_LD6002B_ID): cv.use_id(LD6002BComponent),
cv.Optional(CONF_HOLD_DELAY): number.number_schema(
LD6002BNumber,
number.ApplyNumber,
unit_of_measurement=UNIT_SECOND,
device_class=DEVICE_CLASS_DURATION,
entity_category=ENTITY_CATEGORY_CONFIG,
),
cv.Optional(CONF_Z_MIN): number.number_schema(
LD6002BNumber,
number.ApplyNumber,
unit_of_measurement=UNIT_METER,
device_class=DEVICE_CLASS_DISTANCE,
entity_category=ENTITY_CATEGORY_CONFIG,
),
cv.Optional(CONF_Z_MAX): number.number_schema(
LD6002BNumber,
number.ApplyNumber,
unit_of_measurement=UNIT_METER,
device_class=DEVICE_CLASS_DISTANCE,
entity_category=ENTITY_CATEGORY_CONFIG,
),
cv.Optional(CONF_LOW_POWER_SLEEP_TIME): number.number_schema(
LD6002BNumber,
number.ApplyNumber,
unit_of_measurement=UNIT_MILLISECOND,
device_class=DEVICE_CLASS_DURATION,
entity_category=ENTITY_CATEGORY_CONFIG,
@@ -65,37 +62,37 @@ CONFIG_SCHEMA = cv.Schema(
cv.Optional(CONF_AREA_CONFIG): cv.Schema(
{
cv.Optional(KEY_X_MIN): number.number_schema(
LD6002BNumber,
number.ApplyNumber,
unit_of_measurement=UNIT_METER,
device_class=DEVICE_CLASS_DISTANCE,
entity_category=ENTITY_CATEGORY_CONFIG,
),
cv.Optional(KEY_X_MAX): number.number_schema(
LD6002BNumber,
number.ApplyNumber,
unit_of_measurement=UNIT_METER,
device_class=DEVICE_CLASS_DISTANCE,
entity_category=ENTITY_CATEGORY_CONFIG,
),
cv.Optional(KEY_Y_MIN): number.number_schema(
LD6002BNumber,
number.ApplyNumber,
unit_of_measurement=UNIT_METER,
device_class=DEVICE_CLASS_DISTANCE,
entity_category=ENTITY_CATEGORY_CONFIG,
),
cv.Optional(KEY_Y_MAX): number.number_schema(
LD6002BNumber,
number.ApplyNumber,
unit_of_measurement=UNIT_METER,
device_class=DEVICE_CLASS_DISTANCE,
entity_category=ENTITY_CATEGORY_CONFIG,
),
cv.Optional(CONF_Z_MIN): number.number_schema(
LD6002BNumber,
number.ApplyNumber,
unit_of_measurement=UNIT_METER,
device_class=DEVICE_CLASS_DISTANCE,
entity_category=ENTITY_CATEGORY_CONFIG,
),
cv.Optional(CONF_Z_MAX): number.number_schema(
LD6002BNumber,
number.ApplyNumber,
unit_of_measurement=UNIT_METER,
device_class=DEVICE_CLASS_DISTANCE,
entity_category=ENTITY_CATEGORY_CONFIG,
@@ -107,6 +104,7 @@ CONFIG_SCHEMA = cv.Schema(
def final_validate(config: ConfigType) -> None:
validate_unique_settings(number.DOMAIN, config)
if config.get(CONF_AREA_CONFIG) is None:
return
@@ -139,42 +137,34 @@ FINAL_VALIDATE_SCHEMA = final_validate
async def to_code(config: ConfigType) -> None:
hub = await cg.get_variable(config[CONF_LD6002B_ID])
for key, number_type, setter, min_value, max_value, step in (
(CONF_HOLD_DELAY, NumberType.HOLD_DELAY, "set_hold_delay_number", 0, 65535, 1),
(CONF_Z_MIN, NumberType.Z_MIN, "set_z_min_number", -10, 10, 0.1),
(CONF_Z_MAX, NumberType.Z_MAX, "set_z_max_number", -10, 10, 0.1),
# 0x0205 carries a uint32 of milliseconds; the vendor documents 500 ms as
# the default and no upper bound, so the range ends at a minute rather
# than at a default the module is free to be sleeping past.
(
CONF_LOW_POWER_SLEEP_TIME,
NumberType.LOW_POWER_SLEEP,
"set_low_power_sleep_number",
0,
60000,
100,
),
):
if conf := config.get(key):
n = await number.new_number(
conf, number_type, min_value=min_value, max_value=max_value, step=step
)
await cg.register_parented(n, config[CONF_LD6002B_ID])
cg.add(getattr(hub, setter)(n))
control = {"apply": "set_number_value(entity, value)", "publish": "before"}
numbers = number.sub_numbers(config, parent=hub)
await numbers(
CONF_HOLD_DELAY,
hub.set_hold_delay_number,
min_value=0,
max_value=65535,
step=1,
**control,
)
meters = {"min_value": -10, "max_value": 10, "step": 0.1, **control}
await numbers(CONF_Z_MIN, hub.set_z_min_number, **meters)
await numbers(CONF_Z_MAX, hub.set_z_max_number, **meters)
# 0x0205 is a uint32 of ms with no documented maximum; cap it at a minute
await numbers(
CONF_LOW_POWER_SLEEP_TIME,
hub.set_low_power_sleep_number,
min_value=0,
max_value=60000,
step=100,
**control,
)
if area_config := config.get(CONF_AREA_CONFIG):
for key, number_type, setter in (
(KEY_X_MIN, NumberType.AREA_X_MIN, "set_area_x_min_number"),
(KEY_X_MAX, NumberType.AREA_X_MAX, "set_area_x_max_number"),
(KEY_Y_MIN, NumberType.AREA_Y_MIN, "set_area_y_min_number"),
(KEY_Y_MAX, NumberType.AREA_Y_MAX, "set_area_y_max_number"),
(CONF_Z_MIN, NumberType.AREA_Z_MIN, "set_area_z_min_number"),
(CONF_Z_MAX, NumberType.AREA_Z_MAX, "set_area_z_max_number"),
):
if conf := area_config.get(key):
n = await number.new_number(
conf, number_type, min_value=-10, max_value=10, step=0.1
)
await cg.register_parented(n, config[CONF_LD6002B_ID])
cg.add(getattr(hub, setter)(n))
area = number.sub_numbers(area_config, parent=hub)
await area(KEY_X_MIN, hub.set_area_x_min_number, **meters)
await area(KEY_X_MAX, hub.set_area_x_max_number, **meters)
await area(KEY_Y_MIN, hub.set_area_y_min_number, **meters)
await area(KEY_Y_MAX, hub.set_area_y_max_number, **meters)
await area(CONF_Z_MIN, hub.set_area_z_min_number, **meters)
await area(CONF_Z_MAX, hub.set_area_z_max_number, **meters)
@@ -1,10 +0,0 @@
#include "ld6002b_number.h"
namespace esphome::ld6002b {
void LD6002BNumber::control(float value) {
this->publish_state(value);
this->parent_->set_number_value(this->type_, value);
}
} // namespace esphome::ld6002b
@@ -1,18 +0,0 @@
#pragma once
#include "esphome/components/number/number.h"
#include "../ld6002b.h"
namespace esphome::ld6002b {
class LD6002BNumber : public number::Number, public Parented<LD6002BComponent> {
public:
explicit LD6002BNumber(NumberType type) : type_(type) {}
protected:
void control(float value) override;
NumberType type_;
};
} // namespace esphome::ld6002b
+31 -35
View File
@@ -1,17 +1,16 @@
from functools import partial
import esphome.codegen as cg
from esphome.components import select
import esphome.config_validation as cv
from esphome.const import CONF_AREA_ID, CONF_SENSITIVITY, ENTITY_CATEGORY_CONFIG
from esphome.types import ConfigType
from .. import LD6002BComponent, ld6002b_ns
from .. import LD6002BComponent, validate_unique_settings
from ..const import CONF_INSTALLATION_MODE, CONF_LD6002B_ID, CONF_TRIGGER_SPEED
DEPENDENCIES = ["ld6002b"]
LD6002BSelect = ld6002b_ns.class_("LD6002BSelect", select.Select)
SelectType = ld6002b_ns.enum("SelectType", is_class=True)
AREA_ID_OPTIONS = [
"interference_area_0",
"interference_area_1",
@@ -27,49 +26,46 @@ CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_LD6002B_ID): cv.use_id(LD6002BComponent),
cv.Optional(CONF_SENSITIVITY): select.select_schema(
LD6002BSelect, entity_category=ENTITY_CATEGORY_CONFIG
select.ApplySelect, entity_category=ENTITY_CATEGORY_CONFIG
),
cv.Optional(CONF_TRIGGER_SPEED): select.select_schema(
LD6002BSelect, entity_category=ENTITY_CATEGORY_CONFIG
select.ApplySelect, entity_category=ENTITY_CATEGORY_CONFIG
),
cv.Optional(CONF_INSTALLATION_MODE): select.select_schema(
LD6002BSelect, entity_category=ENTITY_CATEGORY_CONFIG
select.ApplySelect, entity_category=ENTITY_CATEGORY_CONFIG
),
cv.Optional(CONF_AREA_ID): select.select_schema(
LD6002BSelect, entity_category=ENTITY_CATEGORY_CONFIG
select.ApplySelect, entity_category=ENTITY_CATEGORY_CONFIG
),
}
)
SELECT_MAP = (
(
CONF_SENSITIVITY,
SelectType.SENSITIVITY,
"set_sensitivity_select",
["low", "medium", "high"],
),
(
CONF_TRIGGER_SPEED,
SelectType.TRIGGER_SPEED,
"set_trigger_speed_select",
["slow", "medium", "fast"],
),
(
CONF_INSTALLATION_MODE,
SelectType.INSTALLATION_MODE,
"set_installation_select",
["top", "side"],
),
(CONF_AREA_ID, SelectType.AREA_ID, "set_area_id_select", AREA_ID_OPTIONS),
)
FINAL_VALIDATE_SCHEMA = partial(validate_unique_settings, select.DOMAIN)
async def to_code(config: ConfigType) -> None:
hub = await cg.get_variable(config[CONF_LD6002B_ID])
for key, select_type, setter, options in SELECT_MAP:
if conf := config.get(key):
s = await select.new_select(conf, select_type, options=options)
await cg.register_parented(s, config[CONF_LD6002B_ID])
cg.add(getattr(hub, setter)(s))
control = {"apply": "set_select_value(entity, index)", "publish": "before"}
selects = select.sub_selects(config, parent=hub)
await selects(
CONF_SENSITIVITY,
hub.set_sensitivity_select,
options=["low", "medium", "high"],
**control,
)
await selects(
CONF_TRIGGER_SPEED,
hub.set_trigger_speed_select,
options=["slow", "medium", "fast"],
**control,
)
await selects(
CONF_INSTALLATION_MODE,
hub.set_installation_select,
options=["top", "side"],
**control,
)
await selects(
CONF_AREA_ID, hub.set_area_id_select, options=AREA_ID_OPTIONS, **control
)
@@ -1,10 +0,0 @@
#include "ld6002b_select.h"
namespace esphome::ld6002b {
void LD6002BSelect::control(size_t index) {
this->publish_state(index);
this->parent_->set_select_value(this->type_, index);
}
} // namespace esphome::ld6002b
@@ -1,18 +0,0 @@
#pragma once
#include "esphome/components/select/select.h"
#include "../ld6002b.h"
namespace esphome::ld6002b {
class LD6002BSelect : public select::Select, public Parented<LD6002BComponent> {
public:
explicit LD6002BSelect(SelectType type) : type_(type) {}
protected:
void control(size_t index) override;
SelectType type_;
};
} // namespace esphome::ld6002b
+14 -17
View File
@@ -1,10 +1,12 @@
from functools import partial
import esphome.codegen as cg
from esphome.components import switch
import esphome.config_validation as cv
from esphome.const import DEVICE_CLASS_SWITCH, ENTITY_CATEGORY_CONFIG
from esphome.types import ConfigType
from .. import LD6002BComponent, ld6002b_ns
from .. import LD6002BComponent, validate_unique_settings
from ..const import (
CONF_LD6002B_ID,
CONF_LOW_POWER,
@@ -14,9 +16,6 @@ from ..const import (
DEPENDENCIES = ["ld6002b"]
LD6002BSwitch = ld6002b_ns.class_("LD6002BSwitch", switch.Switch)
SwitchType = ld6002b_ns.enum("SwitchType", is_class=True)
# None of these three carry an inversion. They name what the module is doing, not
# how something is wired to it, so an inverted one would only report the opposite
# of the truth -- and the boot restore, which applies a state nothing reports back,
@@ -25,19 +24,19 @@ CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_LD6002B_ID): cv.use_id(LD6002BComponent),
cv.Optional(CONF_LOW_POWER): switch.switch_schema(
LD6002BSwitch,
switch.ApplySwitch,
block_inverted=True,
device_class=DEVICE_CLASS_SWITCH,
entity_category=ENTITY_CATEGORY_CONFIG,
),
cv.Optional(CONF_POINT_CLOUD): switch.switch_schema(
LD6002BSwitch,
switch.ApplySwitch,
block_inverted=True,
device_class=DEVICE_CLASS_SWITCH,
entity_category=ENTITY_CATEGORY_CONFIG,
),
cv.Optional(CONF_TARGET_DISPLAY): switch.switch_schema(
LD6002BSwitch,
switch.ApplySwitch,
block_inverted=True,
device_class=DEVICE_CLASS_SWITCH,
entity_category=ENTITY_CATEGORY_CONFIG,
@@ -47,15 +46,13 @@ CONFIG_SCHEMA = cv.Schema(
)
FINAL_VALIDATE_SCHEMA = partial(validate_unique_settings, switch.DOMAIN)
async def to_code(config: ConfigType) -> None:
hub = await cg.get_variable(config[CONF_LD6002B_ID])
for key, switch_type, setter in (
(CONF_LOW_POWER, SwitchType.LOW_POWER, "set_low_power_switch"),
(CONF_POINT_CLOUD, SwitchType.POINT_CLOUD, "set_point_cloud_switch"),
(CONF_TARGET_DISPLAY, SwitchType.TARGET_DISPLAY, "set_target_display_switch"),
):
if conf := config.get(key):
s = await switch.new_switch(conf, switch_type)
await cg.register_parented(s, config[CONF_LD6002B_ID])
cg.add(getattr(hub, setter)(s))
control = {"apply": "set_switch_state(entity, state)", "publish": "after"}
switches = switch.sub_switches(config, parent=hub)
await switches(CONF_LOW_POWER, hub.set_low_power_switch, **control)
await switches(CONF_POINT_CLOUD, hub.set_point_cloud_switch, **control)
await switches(CONF_TARGET_DISPLAY, hub.set_target_display_switch, **control)
Loaded 100 of 211 files, more files were not shown because too many files have changed in this diff. Show more