mirror of
https://github.com/esphome/esphome.git
synced 2026-10-19 04:32:50 +00:00
[bl0906] Read stale receive bytes instead of spinning on flush() (#21035)
This commit is contained in:
@@ -20,7 +20,7 @@ constexpr uint8_t bl0906_checksum(const uint8_t address, const DataPacket *data)
|
||||
|
||||
void BL0906::loop() {
|
||||
while (this->available())
|
||||
this->flush();
|
||||
this->read();
|
||||
|
||||
if (this->current_stage_ == STAGE_IDLE) {
|
||||
// Woken up between cycles to drain the action queue. Go back to sleep.
|
||||
@@ -92,7 +92,7 @@ void BL0906::advance_stage_() {
|
||||
|
||||
void BL0906::setup() {
|
||||
while (this->available())
|
||||
this->flush();
|
||||
this->read();
|
||||
this->write_array(USR_WRPROT_WITABLE, sizeof(USR_WRPROT_WITABLE));
|
||||
// Calibration (1: register address; 2: value before calibration; 3: value after calibration)
|
||||
this->bias_correction_(BL0906_RMSOS_1, 0.01600, 0); // Calibration current_1
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
esphome:
|
||||
name: uart-mock-bl0906-stray-rx
|
||||
|
||||
host:
|
||||
api:
|
||||
batch_delay: 0ms # Disable batching to receive all state updates
|
||||
logger:
|
||||
level: VERBOSE
|
||||
|
||||
external_components:
|
||||
- source:
|
||||
type: local
|
||||
path: EXTERNAL_COMPONENT_PATH
|
||||
|
||||
# Dummy uart entry to satisfy DEPENDENCIES = ["uart"]
|
||||
uart:
|
||||
baud_rate: 9600
|
||||
port: /dev/null
|
||||
|
||||
# Answers register reads; the first temperature answer has one stray trailing byte
|
||||
uart_mock:
|
||||
id: mock_uart
|
||||
baud_rate: 19200
|
||||
on_tx:
|
||||
- then:
|
||||
- lambda: |-
|
||||
// Command and register address arrive as separate one-byte writes
|
||||
static bool read_pending = false;
|
||||
static bool stray_sent = false;
|
||||
if (data.size() != 1)
|
||||
return;
|
||||
if (!read_pending) {
|
||||
read_pending = data[0] == 0x35;
|
||||
return;
|
||||
}
|
||||
read_pending = false;
|
||||
const uint8_t address = data[0];
|
||||
uint32_t raw;
|
||||
switch (address) {
|
||||
case 0x5E: // temperature: (300 - 64) * 12.5 / 59 - 40 = 10 C
|
||||
raw = 300;
|
||||
break;
|
||||
case 0x16: // voltage
|
||||
raw = 0x2A1B3C;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
const uint8_t l = raw & 0xFF, m = (raw >> 8) & 0xFF, h = (raw >> 16) & 0xFF;
|
||||
std::vector<uint8_t> reply{l, m, h, static_cast<uint8_t>((address + l + m + h) ^ 0xFF)};
|
||||
if (address == 0x5E && !stray_sent) {
|
||||
stray_sent = true;
|
||||
reply.push_back(0xAA);
|
||||
}
|
||||
id(mock_uart).inject_to_rx_buffer(reply);
|
||||
|
||||
sensor:
|
||||
- platform: bl0906
|
||||
uart_id: mock_uart
|
||||
update_interval: 1s
|
||||
temperature:
|
||||
name: Temperature
|
||||
voltage:
|
||||
name: Voltage
|
||||
@@ -0,0 +1,57 @@
|
||||
"""A BL0906 that leaves a stray byte in the receive buffer keeps reading."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from pathlib import Path
|
||||
|
||||
from aioesphomeapi import EntityState, SensorInfo
|
||||
import pytest
|
||||
|
||||
from .state_utils import require_entity
|
||||
from .types import APIClientConnectedFactory, RunCompiledFunction
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_uart_mock_bl0906_stray_rx(
|
||||
yaml_config: str,
|
||||
run_compiled: RunCompiledFunction,
|
||||
api_client_connected: APIClientConnectedFactory,
|
||||
) -> None:
|
||||
"""A stray received byte does not stop the read cycle."""
|
||||
external_components_path = str(
|
||||
Path(__file__).parent / "fixtures" / "external_components"
|
||||
)
|
||||
yaml_config = yaml_config.replace(
|
||||
"EXTERNAL_COMPONENT_PATH", external_components_path
|
||||
)
|
||||
|
||||
lines: list[str] = []
|
||||
states: dict[int, EntityState] = {}
|
||||
|
||||
async with (
|
||||
run_compiled(yaml_config, line_callback=lines.append),
|
||||
api_client_connected() as client,
|
||||
):
|
||||
entities, _ = await client.list_entities_services()
|
||||
temperature = require_entity(entities, "temperature", SensorInfo)
|
||||
voltage = require_entity(entities, "voltage", SensorInfo)
|
||||
|
||||
client.subscribe_states(lambda state: states.__setitem__(state.key, state))
|
||||
|
||||
def state_of(entity: SensorInfo) -> float | None:
|
||||
state = states.get(entity.key)
|
||||
return None if state is None or state.missing_state else state.state
|
||||
|
||||
try:
|
||||
async with asyncio.timeout(10.0):
|
||||
while state_of(voltage) is None:
|
||||
await asyncio.sleep(0.05)
|
||||
except TimeoutError:
|
||||
pytest.fail(f"No voltage reading; log: {lines[-20:]}")
|
||||
|
||||
assert any("RX inject 5 bytes" in x for x in lines), "stray byte not sent"
|
||||
assert state_of(temperature) == pytest.approx(10.0, abs=0.01)
|
||||
uref = 1.097 * 100000 / (13162 * 100 * 1000)
|
||||
assert state_of(voltage) == pytest.approx(0x2A1B3C * uref, rel=1e-5)
|
||||
assert not any("Junk on wire" in x for x in lines)
|
||||
Reference in New Issue
Block a user