15static constexpr uint8_t COMMAND_ACK = 0xE5;
16static constexpr uint32_t COMMAND_ACK_TIMEOUT_MS = 500;
17static constexpr uint32_t CLEAR_QUEUE_TIMEOUT_MS = 15000;
18static constexpr uint32_t STARTUP_DELAY_MS = 2000;
19static constexpr uint32_t POST_RESET_DELAY_MS = 2000;
20static constexpr uint32_t RESET_RETRY_DELAY_MS = 800;
21static constexpr uint32_t STARTUP_RETRY_MS = 3000;
22static constexpr uint32_t PASSIVE_POLL_INTERVAL_MS = 1000;
23static constexpr uint32_t ACTIVE_STALE_MS = 5000;
24static constexpr uint32_t PASSIVE_READ_TIMEOUT_MS = 1000;
25static constexpr uint32_t SOFTWARE_VERSION_READ_TIMEOUT_MS = 2000;
26static constexpr uint32_t SOFTWARE_VERSION_RETRY_MS = 30000;
27static constexpr uint8_t SOFTWARE_VERSION_MAX_ATTEMPTS = 3;
28static constexpr uint32_t ACTIVE_FRAME_TIMEOUT_MS = 3000;
30static constexpr uint8_t PASSIVE_FAIL_ESCALATE_COUNT = 8;
32static constexpr float L_PER_M3 = 1000.0f;
33static constexpr float M3_PER_L = 1.0f / L_PER_M3;
35static constexpr std::array<uint8_t, 7> ACTIVE_MODE = {0xFE, 0xFE, 0x11, 0x5C, 0x00, 0x5C, 0x16};
36static constexpr std::array<uint8_t, 7> PASSIVE_MODE = {0xFE, 0xFE, 0x11, 0x5C, 0x01, 0x5D, 0x16};
37static constexpr std::array<uint8_t, 7> CLEAR_ACCUMULATED_FLOW = {0xFE, 0xFE, 0x11, 0x5A, 0xFD, 0x57, 0x16};
38static constexpr std::array<uint8_t, 7> RESET_DEVICE = {0xFE, 0xFE, 0x11, 0x5D, 0xCB, 0x28, 0x16};
39static constexpr std::array<uint8_t, 7> READ_SENSOR_DATA_NO_ID = {0xFE, 0xFE, 0x11, 0x5B, 0x0F, 0x6A, 0x16};
40static constexpr std::array<uint8_t, 7> READ_SENSOR_DATA_WITH_ID = {0xFE, 0xFE, 0x11, 0x5B, 0xCB, 0x26, 0x16};
41static constexpr std::array<uint8_t, 7> GET_SOFTWARE_VERSION = {0xFE, 0xFE, 0x11, 0x5E, 0x62, 0xC0, 0x16};
44static constexpr size_t FRAME_DEVICE_ID_INDEX = 2;
45static constexpr size_t FRAME_CHECKSUM_INDEX = 30;
46static constexpr size_t FRAME_STOP_INDEX = 31;
47static constexpr uint8_t FRAME_START_BYTE_1 = 0x3C;
48static constexpr uint8_t FRAME_START_BYTE_2 = 0x32;
49static constexpr uint8_t PASSIVE_START_BYTE_2 = 0x64;
50static constexpr uint8_t PASSIVE_START_BYTE_2_WITH_ID = 0x96;
51static constexpr uint8_t FRAME_STOP_BYTE = 0x16;
52static constexpr size_t PASSIVE_WITH_ID_DEVICE_ID_INDEX = 2;
53static constexpr uint8_t PASSIVE_WITH_ID_ACC_FLOW_FLAG_INDEX = 8;
54static constexpr uint8_t PASSIVE_WITH_ID_INSTANT_FLOW_FLAG_INDEX = 22;
55static constexpr uint8_t PASSIVE_WITH_ID_TEMP_FLAG_INDEX = 31;
56static constexpr uint8_t PASSIVE_WITH_ID_ST1_INDEX = 35;
57static constexpr size_t PASSIVE_WITH_ID_CHECKSUM_INDEX = 37;
58static constexpr size_t PASSIVE_WITH_ID_STOP_INDEX = 38;
59static constexpr uint8_t FRAME_INDEX_INSTANT_FLOW_FLAG = 15;
60static constexpr uint8_t FRAME_INDEX_RESERVED_SECTION = 21;
61static constexpr uint8_t FRAME_INDEX_TEMP_FLAG = 24;
62static constexpr uint8_t FRAME_FLAG_INSTANT_FLOW = 0x0B;
63static constexpr uint8_t FRAME_FLAG_RESERVED_SECTION = 0x0C;
64static constexpr uint8_t FRAME_FLAG_TEMP = 0x0D;
67static constexpr uint8_t FRAME_ACC_FLOW_FLAG_INDEX = 8;
68static constexpr uint8_t ACC_FLOW_M3_FLAG = 0x1A;
69static constexpr uint8_t FRAME_FLOW_SIGN_INDEX = 20;
70static constexpr uint8_t FLOW_NEGATIVE_SIGN = 0x80;
73static constexpr uint8_t FRAME_ST1_INDEX = 28;
74static constexpr uint8_t FRAME_ST2_INDEX = 29;
75static constexpr uint8_t ST1_EMPTY_TUBE_MASK = 0x20;
76static constexpr uint8_t ST2_UFC_ERROR_MASK = 0x20;
77static constexpr uint8_t ST2_FLOW_DIRECTION_WRONG_MASK = 0x08;
78static constexpr uint8_t ST2_FLOW_RATE_OUT_OF_RANGE_MASK = 0x04;
80static_assert(SOFTWARE_VERSION_RESPONSE_SIZE <= PASSIVE_FRAME_MAX_SIZE,
"software version read reuses passive_frame_");
81static constexpr size_t SOFTWARE_VERSION_CHECKSUM_INDEX = 5;
82static constexpr size_t SOFTWARE_VERSION_STOP_INDEX = 6;
84static float to_float(uint8_t data) {
return (data >> 4) * 10 + (
data & 0x0F); }
86static bool bcd_to_digits(
const uint8_t *data,
size_t len,
char *
out) {
88 for (
size_t i = 0; i <
len; ++i) {
89 const uint8_t lo =
data[i] & 0x0F;
90 const uint8_t hi =
data[i] >> 4;
93 const size_t pos = (
len - 1 - i) * 2;
96 out[
pos] =
static_cast<char>(
'0' + hi);
99 out[
pos + 1] =
static_cast<char>(
'0' + lo);
105static bool validate_software_version_response(
const uint8_t data[SOFTWARE_VERSION_RESPONSE_SIZE]) {
106 if (data[0] != COMMAND_ACK || data[SOFTWARE_VERSION_STOP_INDEX] != FRAME_STOP_BYTE)
109 for (
size_t i = 1; i < SOFTWARE_VERSION_CHECKSUM_INDEX; ++i)
111 return data[SOFTWARE_VERSION_CHECKSUM_INDEX] == sum;
114static bool check_byte(
const uint8_t data[FRAME_SIZE],
size_t index, uint8_t expected,
const char *name) {
115 if (data[index] == expected)
117 ESP_LOGW(TAG,
"%s (byte %zu) - expected 0x%02X, but was 0x%02X", name, index, expected, data[index]);
121static bool validate_active_frame(
const uint8_t data[FRAME_SIZE]) {
122 const uint8_t sum =
sum_bytes(data, FRAME_CHECKSUM_INDEX);
123 return check_byte(data, 0, FRAME_START_BYTE_1,
"start byte 1") &&
124 check_byte(data, 1, FRAME_START_BYTE_2,
"start byte 2") &&
125 check_byte(data, FRAME_INDEX_INSTANT_FLOW_FLAG, FRAME_FLAG_INSTANT_FLOW,
"instant flow flag") &&
126 check_byte(data, FRAME_INDEX_RESERVED_SECTION, FRAME_FLAG_RESERVED_SECTION,
"reserved section flag") &&
127 check_byte(data, FRAME_INDEX_TEMP_FLAG, FRAME_FLAG_TEMP,
"temperature flag") &&
128 check_byte(data, FRAME_CHECKSUM_INDEX, sum,
"checksum") &&
129 check_byte(data, FRAME_STOP_INDEX, FRAME_STOP_BYTE,
"stop byte");
132static bool validate_passive_frame(
const uint8_t data[PASSIVE_FRAME_SIZE]) {
133 if (data[0] != FRAME_START_BYTE_1 || data[1] != PASSIVE_START_BYTE_2 || data[22] != FRAME_STOP_BYTE)
138#ifdef USE_UFM01_METER_ID
139static bool validate_passive_with_id_frame(
const uint8_t data[PASSIVE_FRAME_WITH_ID_SIZE]) {
140 if (data[0] != FRAME_START_BYTE_1 || data[1] != PASSIVE_START_BYTE_2_WITH_ID ||
141 data[PASSIVE_WITH_ID_STOP_INDEX] != FRAME_STOP_BYTE)
144 for (
size_t i = 0; i < PASSIVE_WITH_ID_CHECKSUM_INDEX; ++i)
146 return data[PASSIVE_WITH_ID_CHECKSUM_INDEX] == sum;
151struct PassiveLayout {
153 uint8_t instant_flow;
157static constexpr PassiveLayout PASSIVE_NO_ID_LAYOUT{2, 9, 15, 19};
158#ifdef USE_UFM01_METER_ID
159static constexpr PassiveLayout PASSIVE_WITH_ID_LAYOUT{PASSIVE_WITH_ID_ACC_FLOW_FLAG_INDEX,
160 PASSIVE_WITH_ID_INSTANT_FLOW_FLAG_INDEX,
161 PASSIVE_WITH_ID_TEMP_FLAG_INDEX, PASSIVE_WITH_ID_ST1_INDEX};
164static void passive_to_active_frame(
const uint8_t *passive,
const PassiveLayout &layout, uint8_t active[FRAME_SIZE]) {
165 std::memset(active, 0, FRAME_SIZE);
166 active[0] = FRAME_START_BYTE_1;
167 active[1] = FRAME_START_BYTE_2;
168 std::copy_n(&passive[layout.acc_flow], 7, &active[FRAME_ACC_FLOW_FLAG_INDEX]);
169 std::copy_n(&passive[layout.instant_flow], 6, &active[FRAME_INDEX_INSTANT_FLOW_FLAG]);
170 active[FRAME_INDEX_RESERVED_SECTION] = FRAME_FLAG_RESERVED_SECTION;
171 std::copy_n(&passive[layout.temperature], 4, &active[FRAME_INDEX_TEMP_FLAG]);
172 std::copy_n(&passive[layout.status], 3, &active[FRAME_ST1_INDEX]);
173 active[FRAME_STOP_INDEX] = FRAME_STOP_BYTE;
176static float read_accumulated_flow(
const uint8_t data[FRAME_SIZE]) {
177 return (data[FRAME_ACC_FLOW_FLAG_INDEX] == ACC_FLOW_M3_FLAG ? L_PER_M3 : 1.0f) *
178 (to_float(data[14]) * 10000000.0f + to_float(data[13]) * 100000.0f + to_float(data[12]) * 1000.0f +
179 to_float(data[11]) * 10.0f + to_float(data[10]) * 0.1f + to_float(data[9]) * 0.001f);
182static float read_flow(
const uint8_t data[FRAME_SIZE]) {
183 return (data[FRAME_FLOW_SIGN_INDEX] == FLOW_NEGATIVE_SIGN ? -1.0f : 1.0f) *
184 (to_float(data[19]) * 10000.0f + to_float(data[18]) * 100.0f + to_float(data[17]) +
185 to_float(data[16]) * 0.01f) *
189static constexpr size_t LOG_HEX_MAX_SIZE = std::max(FRAME_SIZE, PASSIVE_FRAME_MAX_SIZE);
191static void log_hex(
const uint8_t *data,
size_t len) {
196static float read_temperature(
const uint8_t data[FRAME_SIZE]) {
198 if (data[27] == 0x00 && (data[26] == 0x00 || data[26] == 0x70) && data[25] == 0x00) {
201 return to_float(data[27]) * 100.0f + to_float(data[26]) + to_float(data[25]) * 0.01f;
204static bool read_ufc_chip_error(
const uint8_t data[FRAME_SIZE]) {
return data[FRAME_ST2_INDEX] & ST2_UFC_ERROR_MASK; }
206static bool read_flow_direction_wrong(
const uint8_t data[FRAME_SIZE]) {
207 return data[FRAME_ST2_INDEX] & ST2_FLOW_DIRECTION_WRONG_MASK;
210static bool read_empty_tube(
const uint8_t data[FRAME_SIZE]) {
return data[FRAME_ST1_INDEX] & ST1_EMPTY_TUBE_MASK; }
212static bool read_flow_rate_out_of_range(
const uint8_t data[FRAME_SIZE]) {
213 return data[FRAME_ST2_INDEX] & ST2_FLOW_RATE_OUT_OF_RANGE_MASK;
216void UFM01Component::flush_rx_() {
221 this->read_index_ = 0;
224void UFM01Component::send_command_no_wait_(
const std::array<uint8_t, 7> &command) {
230bool UFM01Component::consume_ack_() {
235 if (
byte == COMMAND_ACK)
237 ESP_LOGV(TAG,
"Unexpected byte while waiting for command ACK: 0x%02X",
byte);
242#ifdef USE_UFM01_CLEAR_ACCUMULATED_FLOW_ACTION
243bool UFM01Component::can_start_clear_action_()
const {
245#ifdef USE_UFM01_SOFTWARE_VERSION
246 !this->software_version_read_pending_ &&
252void UFM01Component::request_clear_accumulated_flow() {
253 if (this->clear_pending_) {
254 ESP_LOGD(TAG,
"Clear accumulated flow already pending");
257 this->clear_pending_ =
true;
258 this->pending_clear_sent_ =
false;
262void UFM01Component::finish_pending_clear_() {
263 this->clear_pending_ =
false;
264 this->pending_clear_sent_ =
false;
267void UFM01Component::loop_pending_clear_action_(
uint32_t now) {
268 if (!this->clear_pending_)
270 if (!this->pending_clear_sent_) {
271 if (!this->can_start_clear_action_()) {
272 if (now - this->pending_clear_start_ms_ < CLEAR_QUEUE_TIMEOUT_MS)
274 ESP_LOGW(TAG,
"Clear accumulated flow timed out waiting for an idle UART");
275 this->finish_pending_clear_();
278 this->pending_clear_start_ms_ = now;
279 this->pending_clear_sent_ =
true;
280 this->send_command_no_wait_(CLEAR_ACCUMULATED_FLOW);
283 if (this->consume_ack_()) {
284 ESP_LOGI(TAG,
"Clear accumulated flow acknowledged");
285 this->finish_pending_clear_();
288 if (now - this->pending_clear_start_ms_ < COMMAND_ACK_TIMEOUT_MS)
290 ESP_LOGW(TAG,
"Clear accumulated flow not acknowledged");
291 this->finish_pending_clear_();
298 ESP_LOGI(TAG,
"Setting up UFM-01...");
299 this->startup_wait_ms_ = STARTUP_DELAY_MS;
303void UFM01Component::dump_config() {
304 ESP_LOGCONFIG(TAG,
"UFM-01:");
306 LOG_SENSOR(
" ",
"Accumulated Flow", this->accumulated_flow_sensor_);
307 LOG_SENSOR(
" ",
"Flow", this->flow_sensor_);
308 LOG_SENSOR(
" ",
"Temperature", this->temperature_sensor_);
310#ifdef USE_BINARY_SENSOR
311 LOG_BINARY_SENSOR(
" ",
"UFC Chip Error", this->ufc_chip_error_binary_sensor_);
312 LOG_BINARY_SENSOR(
" ",
"Flow Direction Wrong", this->flow_direction_wrong_binary_sensor_);
313 LOG_BINARY_SENSOR(
" ",
"Empty Tube", this->empty_tube_binary_sensor_);
314 LOG_BINARY_SENSOR(
" ",
"Flow Rate Out Of Range", this->flow_rate_out_of_range_binary_sensor_);
316#ifdef USE_UFM01_METER_ID
317 LOG_TEXT_SENSOR(
" ",
"Meter ID", this->meter_id_text_sensor_);
319#ifdef USE_UFM01_SOFTWARE_VERSION
320 LOG_TEXT_SENSOR(
" ",
"Software Version", this->software_version_text_sensor_);
325void UFM01Component::publish_stale_flow_and_temperature_() {
327 if (this->flow_sensor_ !=
nullptr)
328 this->flow_sensor_->publish_state(NAN);
329 if (this->temperature_sensor_ !=
nullptr)
330 this->temperature_sensor_->publish_state(NAN);
334#ifdef USE_UFM01_METER_ID
335void UFM01Component::publish_meter_id_from_frame_(
const uint8_t data[FRAME_SIZE]) {
336 if (this->meter_id_text_sensor_ ==
nullptr || this->meter_id_published_)
339 char meter_id_str[DEVICE_ID_STRING_LENGTH + 1];
340 if (!bcd_to_digits(&data[FRAME_DEVICE_ID_INDEX], DEVICE_ID_LENGTH, meter_id_str)) {
341 if (!this->meter_id_invalid_logged_) {
342 ESP_LOGW(TAG,
"Meter ID in frame is zero or not BCD, leaving the sensor unknown");
343 this->meter_id_invalid_logged_ =
true;
348 this->meter_id_text_sensor_->publish_state(meter_id_str);
349 this->meter_id_published_ =
true;
350 ESP_LOGI(TAG,
"UFM-01 meter ID: %s", meter_id_str);
354#ifdef USE_UFM01_SOFTWARE_VERSION
355void UFM01Component::start_software_version_read_(
uint32_t now) {
356 this->send_command_no_wait_(GET_SOFTWARE_VERSION);
357 this->passive_index_ = 0;
358 this->passive_start_ms_ = now;
359 this->software_version_read_pending_ =
true;
363 while (this->
available() && this->passive_index_ < SOFTWARE_VERSION_RESPONSE_SIZE) {
368 if (this->passive_index_ == 0 &&
byte != COMMAND_ACK)
370 this->passive_frame_[this->passive_index_++] = byte;
373 if (this->passive_index_ < SOFTWARE_VERSION_RESPONSE_SIZE) {
374 if (now - this->passive_start_ms_ < SOFTWARE_VERSION_READ_TIMEOUT_MS)
376 ESP_LOGD(TAG,
"software version read timeout (%u/%u bytes)", this->passive_index_,
377 static_cast<unsigned>(SOFTWARE_VERSION_RESPONSE_SIZE));
381 if (!validate_software_version_response(this->passive_frame_)) {
382 log_hex(this->passive_frame_, SOFTWARE_VERSION_RESPONSE_SIZE);
383 ESP_LOGW(TAG,
"invalid software version response");
387 if (this->software_version_text_sensor_ !=
nullptr && !this->software_version_published_) {
388 char version_str[SOFTWARE_VERSION_STRING_LENGTH + 1];
389 if (!bcd_to_digits(&this->passive_frame_[1], SOFTWARE_VERSION_LENGTH, version_str)) {
390 ESP_LOGW(TAG,
"invalid BCD data in software version response");
393 this->software_version_text_sensor_->publish_state(version_str);
394 this->software_version_published_ =
true;
395 ESP_LOGI(TAG,
"UFM-01 software version: %s", version_str);
402 this->software_version_read_pending_ =
false;
403 this->last_software_version_attempt_ms_ = now;
405 ++this->software_version_failures_ == SOFTWARE_VERSION_MAX_ATTEMPTS) {
406 ESP_LOGW(TAG,
"Software version not read after %u attempts, giving up", SOFTWARE_VERSION_MAX_ATTEMPTS);
411bool UFM01Component::service_software_version_(
uint32_t now) {
412 if (this->software_version_read_pending_) {
413 const ReadResult result = this->continue_software_version_read_(now);
415 this->finish_software_version_read_(result, now);
418 if (this->software_version_text_sensor_ ==
nullptr || this->software_version_published_ ||
419 this->software_version_failures_ >= SOFTWARE_VERSION_MAX_ATTEMPTS || this->passive_read_pending_)
421 if (this->last_software_version_attempt_ms_ != 0 &&
422 now - this->last_software_version_attempt_ms_ < SOFTWARE_VERSION_RETRY_MS)
424 this->start_software_version_read_(now);
429void UFM01Component::on_active_frame_(uint8_t data[FRAME_SIZE],
uint32_t now) {
430#ifdef USE_UFM01_METER_ID
431 this->publish_meter_id_from_frame_(data);
433 bool empty_tube = read_empty_tube(data);
434#ifdef USE_BINARY_SENSOR
435 if (this->ufc_chip_error_binary_sensor_ !=
nullptr)
436 this->ufc_chip_error_binary_sensor_->publish_state(read_ufc_chip_error(data));
437 if (this->flow_direction_wrong_binary_sensor_ !=
nullptr)
438 this->flow_direction_wrong_binary_sensor_->publish_state(read_flow_direction_wrong(data));
439 if (this->empty_tube_binary_sensor_ !=
nullptr)
440 this->empty_tube_binary_sensor_->publish_state(empty_tube);
441 if (this->flow_rate_out_of_range_binary_sensor_ !=
nullptr)
442 this->flow_rate_out_of_range_binary_sensor_->publish_state(read_flow_rate_out_of_range(data));
447 if (this->accumulated_flow_sensor_ !=
nullptr)
448 this->accumulated_flow_sensor_->publish_state(read_accumulated_flow(data));
451 this->publish_stale_flow_and_temperature_();
453 if (this->flow_sensor_ !=
nullptr)
454 this->flow_sensor_->publish_state(read_flow(data));
455 if (this->temperature_sensor_ !=
nullptr)
456 this->temperature_sensor_->publish_state(read_temperature(data));
459 this->last_valid_frame_ms_ = now;
464bool UFM01Component::process_active_stream_(
uint32_t now) {
465 bool got_valid_frame =
false;
468 if (!this->
read_byte(&this->data_[this->read_index_])) {
469 ESP_LOGW(TAG,
"unable to read byte");
470 this->read_index_ = 0;
473 if ((this->read_index_ == 0 && this->data_[0] != FRAME_START_BYTE_1) ||
474 (this->read_index_ == 1 && this->data_[1] != FRAME_START_BYTE_2)) {
475 ESP_LOGD(TAG,
"not start of data at %" PRId32
" (is 0x%02X)", this->read_index_, this->data_[this->read_index_]);
476 this->read_index_ = 0;
479 if (++this->read_index_ <
static_cast<int32_t
>(FRAME_SIZE))
482 if (validate_active_frame(this->data_)) {
483 this->on_active_frame_(this->data_, now);
484 this->read_index_ = 0;
485 got_valid_frame =
true;
489 log_hex(this->data_,
sizeof(this->data_));
490 ESP_LOGW(TAG,
"unable to read data");
492 i < static_cast<int32_t>(FRAME_STOP_INDEX) && this->read_index_ ==
static_cast<int32_t
>(FRAME_SIZE); ++i) {
493 if ((this->data_[i] == FRAME_START_BYTE_1) && (this->data_[i + 1] == FRAME_START_BYTE_2)) {
494 for (int32_t j = i; j < static_cast<int32_t>(FRAME_SIZE); ++
j)
495 this->data_[j - i] = this->data_[j];
496 this->read_index_ =
static_cast<int32_t
>(FRAME_SIZE) - i;
499 if (this->read_index_ ==
static_cast<int32_t
>(FRAME_SIZE))
500 this->read_index_ = 0;
503 return got_valid_frame;
507 this->operating_mode_ =
mode;
508 this->phase_start_ms_ = now;
509 this->passive_read_pending_ =
false;
510#ifdef USE_UFM01_SOFTWARE_VERSION
511 this->software_version_read_pending_ =
false;
513 this->consecutive_passive_failures_ = 0;
517 this->startup_phase_ = phase;
518 this->phase_start_ms_ = now;
521void UFM01Component::send_reset_(
const LogString *status,
uint32_t now) {
523 this->reset_retried_ =
false;
524 this->send_command_no_wait_(RESET_DEVICE);
528void UFM01Component::enter_active_stream_(
const LogString *reason,
uint32_t now) {
529 ESP_LOGI(TAG,
"UFM-01 active stream %s", LOG_STR_ARG(reason));
533void UFM01Component::enter_passive_from_stale_(
uint32_t now) {
534 ESP_LOGW(TAG,
"Active stream stale, switching to passive polling");
535 this->publish_stale_flow_and_temperature_();
537 this->send_command_no_wait_(PASSIVE_MODE);
539 this->last_poll_ms_ = now;
543void UFM01Component::restart_startup_(
const LogString *reason,
uint32_t now) {
544 ESP_LOGW(TAG,
"%s, re-initializing UFM-01", LOG_STR_ARG(reason));
545 this->publish_stale_flow_and_temperature_();
547 this->send_reset_(LOG_STR(
"re-initializing UFM-01"), now);
550void UFM01Component::note_passive_poll_result_(
bool success,
uint32_t now) {
552 this->consecutive_passive_failures_ = 0;
556 if (++this->consecutive_passive_failures_ < PASSIVE_FAIL_ESCALATE_COUNT)
558 this->restart_startup_(LOG_STR(
"Passive poll failed repeatedly"), now);
561void UFM01Component::start_passive_read_(
uint32_t now) {
562#ifdef USE_UFM01_METER_ID
563 if (this->meter_id_text_sensor_ !=
nullptr && !this->meter_id_published_) {
564 this->passive_expected_size_ = PASSIVE_FRAME_WITH_ID_SIZE;
565 this->passive_start_byte_2_ = PASSIVE_START_BYTE_2_WITH_ID;
566 this->send_command_no_wait_(READ_SENSOR_DATA_WITH_ID);
570 this->passive_expected_size_ = PASSIVE_FRAME_SIZE;
571 this->passive_start_byte_2_ = PASSIVE_START_BYTE_2;
572 this->send_command_no_wait_(READ_SENSOR_DATA_NO_ID);
574 this->passive_index_ = 0;
575 this->passive_start_ms_ = now;
579 const uint8_t expected_size = this->passive_expected_size_;
581 while (this->
available() && this->passive_index_ < expected_size) {
586 if (this->passive_index_ == 0 &&
byte != FRAME_START_BYTE_1)
588 if (this->passive_index_ == 1 &&
byte != this->passive_start_byte_2_) {
590 this->passive_index_ = (
byte == FRAME_START_BYTE_1) ? 1 : 0;
593 this->passive_frame_[this->passive_index_++] = byte;
596 if (this->passive_index_ < expected_size) {
597 if (now - this->passive_start_ms_ < PASSIVE_READ_TIMEOUT_MS)
599 ESP_LOGD(TAG,
"passive read timeout (%u/%u bytes)", this->passive_index_, expected_size);
603 uint8_t active_frame[FRAME_SIZE];
604#ifdef USE_UFM01_METER_ID
605 if (expected_size == PASSIVE_FRAME_WITH_ID_SIZE) {
606 if (!validate_passive_with_id_frame(this->passive_frame_)) {
607 log_hex(this->passive_frame_, PASSIVE_FRAME_WITH_ID_SIZE);
608 ESP_LOGW(TAG,
"invalid passive frame with ID");
611 passive_to_active_frame(this->passive_frame_, PASSIVE_WITH_ID_LAYOUT, active_frame);
613 std::copy_n(&this->passive_frame_[PASSIVE_WITH_ID_DEVICE_ID_INDEX], DEVICE_ID_LENGTH + 1,
614 &active_frame[FRAME_DEVICE_ID_INDEX]);
618 if (!validate_passive_frame(this->passive_frame_)) {
619 log_hex(this->passive_frame_, PASSIVE_FRAME_SIZE);
620 ESP_LOGW(TAG,
"invalid passive frame");
623 passive_to_active_frame(this->passive_frame_, PASSIVE_NO_ID_LAYOUT, active_frame);
624 active_frame[FRAME_DEVICE_ID_INDEX + DEVICE_ID_LENGTH] = 0x01;
626 this->on_active_frame_(active_frame, now);
630void UFM01Component::loop_startup_(
uint32_t now) {
631 const uint32_t elapsed = now - this->phase_start_ms_;
633 switch (this->startup_phase_) {
636 if (this->process_active_stream_(now)) {
637 this->enter_active_stream_(LOG_STR(
"started"), now);
640 if (elapsed < this->startup_wait_ms_)
642 ESP_LOGD(TAG,
"Running startup sequence");
643 this->send_reset_(LOG_STR(
"initializing UFM-01"), now);
647 if (this->consume_ack_()) {
651 if (elapsed < COMMAND_ACK_TIMEOUT_MS)
653 if (!this->reset_retried_) {
654 ESP_LOGW(TAG,
"Reset not acknowledged, retrying in %" PRIu32
" ms", RESET_RETRY_DELAY_MS);
657 ESP_LOGW(TAG,
"Reset failed during startup");
663 if (elapsed < RESET_RETRY_DELAY_MS)
665 this->reset_retried_ =
true;
666 this->send_command_no_wait_(RESET_DEVICE);
671 if (elapsed < POST_RESET_DELAY_MS)
673#ifdef USE_UFM01_SOFTWARE_VERSION
674 if (this->software_version_text_sensor_ !=
nullptr && !this->software_version_published_ &&
675 this->software_version_failures_ < SOFTWARE_VERSION_MAX_ATTEMPTS) {
676 this->start_software_version_read_(now);
681 this->send_command_no_wait_(ACTIVE_MODE);
685#ifdef USE_UFM01_SOFTWARE_VERSION
687 const ReadResult result = this->continue_software_version_read_(now);
691 ESP_LOGD(TAG,
"Software version read during startup");
693 ESP_LOGW(TAG,
"Software version read failed during startup, continuing");
695 this->finish_software_version_read_(result, now);
696 this->send_command_no_wait_(ACTIVE_MODE);
704 if (this->process_active_stream_(now)) {
705 this->enter_active_stream_(LOG_STR(
"started"), now);
708 if (elapsed < ACTIVE_FRAME_TIMEOUT_MS)
710 this->send_command_no_wait_(PASSIVE_MODE);
715 if (this->consume_ack_()) {
716 ESP_LOGD(TAG,
"Passive mode acknowledged during startup");
717 }
else if (elapsed < COMMAND_ACK_TIMEOUT_MS) {
720 ESP_LOGW(TAG,
"SET_PASSIVE_MODE not acknowledged during startup, continuing");
722 this->start_passive_read_(now);
727 switch (this->continue_passive_read_(now)) {
731 ESP_LOGI(TAG,
"UFM-01 using passive polling");
733 this->last_poll_ms_ = now;
736 ESP_LOGW(TAG,
"Startup failed, retrying in %" PRIu32
" ms", STARTUP_RETRY_MS);
737 this->startup_wait_ms_ = STARTUP_RETRY_MS;
744void UFM01Component::loop_active_stream_(
uint32_t now) {
745#ifdef USE_UFM01_SOFTWARE_VERSION
746 if (this->service_software_version_(now))
750 this->process_active_stream_(now);
752 if (this->last_valid_frame_ms_ != 0 && now - this->last_valid_frame_ms_ > ACTIVE_STALE_MS)
753 this->enter_passive_from_stale_(now);
756void UFM01Component::loop_passive_poll_(
uint32_t now) {
757#ifdef USE_UFM01_SOFTWARE_VERSION
758 if (this->service_software_version_(now))
762 if (this->passive_read_pending_) {
763 const ReadResult result = this->continue_passive_read_(now);
766 this->passive_read_pending_ =
false;
771 if (this->process_active_stream_(now)) {
772 this->enter_active_stream_(LOG_STR(
"resumed"), now);
776 if (now - this->last_poll_ms_ >= PASSIVE_POLL_INTERVAL_MS) {
777 this->last_poll_ms_ = now;
778 this->start_passive_read_(now);
779 this->passive_read_pending_ =
true;
783void UFM01Component::loop() {
785#ifdef USE_UFM01_CLEAR_ACCUMULATED_FLOW_ACTION
786 this->loop_pending_clear_action_(now);
787 if (this->clear_pending_ && this->pending_clear_sent_)
790 switch (this->operating_mode_) {
792 this->loop_startup_(now);
795 this->loop_active_stream_(now);
798 this->loop_passive_poll_(now);