12static const char *
const TAG =
"ufm01";
14static constexpr uint8_t COMMAND_ACK = 0xE5;
15static constexpr uint32_t COMMAND_ACK_TIMEOUT_MS = 500;
16static constexpr uint32_t STARTUP_DELAY_MS = 2000;
17static constexpr uint32_t POST_RESET_DELAY_MS = 2000;
18static constexpr uint32_t RESET_RETRY_DELAY_MS = 800;
19static constexpr uint32_t STARTUP_RETRY_MS = 3000;
20static constexpr uint32_t PASSIVE_POLL_INTERVAL_MS = 1000;
21static constexpr uint32_t ACTIVE_STALE_MS = 5000;
22static constexpr uint32_t PASSIVE_READ_TIMEOUT_MS = 1000;
23static constexpr uint32_t ACTIVE_FRAME_TIMEOUT_MS = 3000;
25static constexpr float L_PER_M3 = 1000.0f;
26static constexpr float M3_PER_L = 1.0f / L_PER_M3;
28static constexpr std::array<uint8_t, 7> ACTIVE_MODE = {0xFE, 0xFE, 0x11, 0x5C, 0x00, 0x5C, 0x16};
29static constexpr std::array<uint8_t, 7> CLEAR_ACCUMULATED_FLOW = {0xFE, 0xFE, 0x11, 0x5A, 0xFD, 0x57, 0x16};
30static constexpr std::array<uint8_t, 7> RESET_DEVICE = {0xFE, 0xFE, 0x11, 0x5D, 0xCB, 0x28, 0x16};
31static constexpr std::array<uint8_t, 7> READ_SENSOR_DATA_NO_ID = {0xFE, 0xFE, 0x11, 0x5B, 0x0F, 0x6A, 0x16};
34static constexpr size_t FRAME_CHECKSUM_INDEX = 30;
35static constexpr size_t FRAME_STOP_INDEX = 31;
36static constexpr uint8_t FRAME_START_BYTE_1 = 0x3C;
37static constexpr uint8_t FRAME_START_BYTE_2 = 0x32;
38static constexpr uint8_t PASSIVE_START_BYTE_2 = 0x64;
39static constexpr uint8_t FRAME_STOP_BYTE = 0x16;
40static constexpr uint8_t FRAME_INDEX_INSTANT_FLOW_FLAG = 15;
41static constexpr uint8_t FRAME_INDEX_RESERVED_SECTION = 21;
42static constexpr uint8_t FRAME_INDEX_TEMP_FLAG = 24;
43static constexpr uint8_t FRAME_FLAG_INSTANT_FLOW = 0x0B;
44static constexpr uint8_t FRAME_FLAG_RESERVED_SECTION = 0x0C;
45static constexpr uint8_t FRAME_FLAG_TEMP = 0x0D;
48static constexpr uint8_t FRAME_ACC_FLOW_FLAG_INDEX = 8;
49static constexpr uint8_t ACC_FLOW_M3_FLAG = 0x1A;
50static constexpr uint8_t FRAME_FLOW_SIGN_INDEX = 20;
51static constexpr uint8_t FLOW_NEGATIVE_SIGN = 0x80;
54static constexpr uint8_t FRAME_ST1_INDEX = 28;
55static constexpr uint8_t FRAME_ST2_INDEX = 29;
56static constexpr uint8_t ST1_EMPTY_TUBE_MASK = 0x20;
57static constexpr uint8_t ST2_UFC_ERROR_MASK = 0x20;
58static constexpr uint8_t ST2_FLOW_DIRECTION_WRONG_MASK = 0x08;
59static constexpr uint8_t ST2_FLOW_RATE_OUT_OF_RANGE_MASK = 0x04;
61static float to_float(uint8_t data) {
return (data >> 4) * 10 + (data & 0x0F); }
63static bool check_byte(
const uint8_t data[FRAME_SIZE],
size_t index, uint8_t expected,
const char *name) {
64 if (data[index] == expected)
66 ESP_LOGW(TAG,
"%s (byte %zu) - expected 0x%02X, but was 0x%02X", name, index, expected, data[index]);
70static bool validate_active_frame(
const uint8_t data[FRAME_SIZE]) {
72 for (
size_t i = 0; i < FRAME_CHECKSUM_INDEX; ++i)
74 return check_byte(data, 0, FRAME_START_BYTE_1,
"start byte 1") &&
75 check_byte(data, 1, FRAME_START_BYTE_2,
"start byte 2") &&
76 check_byte(data, FRAME_INDEX_INSTANT_FLOW_FLAG, FRAME_FLAG_INSTANT_FLOW,
"instant flow flag") &&
77 check_byte(data, FRAME_INDEX_RESERVED_SECTION, FRAME_FLAG_RESERVED_SECTION,
"reserved section flag") &&
78 check_byte(data, FRAME_INDEX_TEMP_FLAG, FRAME_FLAG_TEMP,
"temperature flag") &&
79 check_byte(data, FRAME_CHECKSUM_INDEX, sum,
"checksum") &&
80 check_byte(data, FRAME_STOP_INDEX, FRAME_STOP_BYTE,
"stop byte");
83static bool validate_passive_frame(
const uint8_t data[PASSIVE_FRAME_SIZE]) {
84 if (data[0] != FRAME_START_BYTE_1 || data[1] != PASSIVE_START_BYTE_2 || data[22] != FRAME_STOP_BYTE)
87 for (
size_t i = 0; i < 21; ++i)
89 return data[21] == (sum & 0xFF);
92static void passive_no_id_to_active_frame(
const uint8_t passive[PASSIVE_FRAME_SIZE], uint8_t active[FRAME_SIZE]) {
93 std::memset(active, 0, FRAME_SIZE);
94 active[0] = FRAME_START_BYTE_1;
95 active[1] = FRAME_START_BYTE_2;
97 active[8] = passive[2];
98 for (
size_t i = 0; i < 6; ++i)
99 active[9 + i] = passive[3 + i];
100 active[15] = passive[9];
101 for (
size_t i = 0; i < 5; ++i)
102 active[16 + i] = passive[10 + i];
103 active[21] = FRAME_FLAG_RESERVED_SECTION;
104 active[24] = passive[15];
105 for (
size_t i = 0; i < 3; ++i)
106 active[25 + i] = passive[16 + i];
107 active[28] = passive[19];
108 active[29] = passive[20];
109 active[30] = passive[21];
110 active[31] = FRAME_STOP_BYTE;
113static float read_accumulated_flow(
const uint8_t data[FRAME_SIZE]) {
114 return (data[FRAME_ACC_FLOW_FLAG_INDEX] == ACC_FLOW_M3_FLAG ? L_PER_M3 : 1.0f) *
115 (to_float(data[14]) * 10000000.0f + to_float(data[13]) * 100000.0f + to_float(data[12]) * 1000.0f +
116 to_float(data[11]) * 10.0f + to_float(data[10]) * 0.1f + to_float(data[9]) * 0.001f);
119static float read_flow(
const uint8_t data[FRAME_SIZE]) {
120 return (data[FRAME_FLOW_SIGN_INDEX] == FLOW_NEGATIVE_SIGN ? -1.0f : 1.0f) *
121 (to_float(data[19]) * 10000.0f + to_float(data[18]) * 100.0f + to_float(data[17]) +
122 to_float(data[16]) * 0.01f) *
126static void log_hex(
const uint8_t *data,
size_t len) {
131static float read_temperature(
const uint8_t data[FRAME_SIZE]) {
133 if (data[27] == 0x00 && (data[26] == 0x00 || data[26] == 0x70) && data[25] == 0x00) {
136 return to_float(data[27]) * 100.0f + to_float(data[26]) + to_float(data[25]) * 0.01f;
139static bool read_ufc_chip_error(
const uint8_t data[FRAME_SIZE]) {
return data[FRAME_ST2_INDEX] & ST2_UFC_ERROR_MASK; }
141static bool read_flow_direction_wrong(
const uint8_t data[FRAME_SIZE]) {
142 return data[FRAME_ST2_INDEX] & ST2_FLOW_DIRECTION_WRONG_MASK;
145static bool read_empty_tube(
const uint8_t data[FRAME_SIZE]) {
return data[FRAME_ST1_INDEX] & ST1_EMPTY_TUBE_MASK; }
147static bool read_flow_rate_out_of_range(
const uint8_t data[FRAME_SIZE]) {
148 return data[FRAME_ST2_INDEX] & ST2_FLOW_RATE_OUT_OF_RANGE_MASK;
151void UFM01Component::flush_rx_() {
156 this->read_index_ = 0;
159void UFM01Component::send_command_no_wait_(
const std::array<uint8_t, 7> &command) {
166bool UFM01Component::consume_ack_() {
171 if (
byte == COMMAND_ACK)
173 ESP_LOGV(TAG,
"Unexpected byte while waiting for command ACK: 0x%02X",
byte);
178bool UFM01Component::send_command_(
const std::array<uint8_t, 7> &command) {
179 this->send_command_no_wait_(command);
181 while (
millis() - start < COMMAND_ACK_TIMEOUT_MS) {
182 if (this->consume_ack_())
198 ESP_LOGI(TAG,
"Setting up UFM-01...");
199 this->startup_wait_ms_ = STARTUP_DELAY_MS;
203void UFM01Component::dump_config() {
204 ESP_LOGCONFIG(TAG,
"UFM-01:");
206 LOG_SENSOR(
" ",
"Accumulated Flow", this->accumulated_flow_sensor_);
207 LOG_SENSOR(
" ",
"Flow", this->flow_sensor_);
208 LOG_SENSOR(
" ",
"Temperature", this->temperature_sensor_);
210#ifdef USE_BINARY_SENSOR
211 LOG_BINARY_SENSOR(
" ",
"UFC Chip Error", this->ufc_chip_error_binary_sensor_);
212 LOG_BINARY_SENSOR(
" ",
"Flow Direction Wrong", this->flow_direction_wrong_binary_sensor_);
213 LOG_BINARY_SENSOR(
" ",
"Empty Tube", this->empty_tube_binary_sensor_);
214 LOG_BINARY_SENSOR(
" ",
"Flow Rate Out Of Range", this->flow_rate_out_of_range_binary_sensor_);
219void UFM01Component::on_active_frame_(uint8_t data[FRAME_SIZE]) {
220 bool empty_tube = read_empty_tube(data);
221#ifdef USE_BINARY_SENSOR
222 if (this->ufc_chip_error_binary_sensor_ !=
nullptr)
223 this->ufc_chip_error_binary_sensor_->publish_state(read_ufc_chip_error(data));
224 if (this->flow_direction_wrong_binary_sensor_ !=
nullptr)
225 this->flow_direction_wrong_binary_sensor_->publish_state(read_flow_direction_wrong(data));
226 if (this->empty_tube_binary_sensor_ !=
nullptr)
227 this->empty_tube_binary_sensor_->publish_state(empty_tube);
228 if (this->flow_rate_out_of_range_binary_sensor_ !=
nullptr)
229 this->flow_rate_out_of_range_binary_sensor_->publish_state(read_flow_rate_out_of_range(data));
234 if (this->accumulated_flow_sensor_ !=
nullptr)
235 this->accumulated_flow_sensor_->publish_state(read_accumulated_flow(data));
238 if (this->flow_sensor_ !=
nullptr)
239 this->flow_sensor_->publish_state(NAN);
240 if (this->temperature_sensor_ !=
nullptr)
241 this->temperature_sensor_->publish_state(NAN);
243 if (this->flow_sensor_ !=
nullptr)
244 this->flow_sensor_->publish_state(read_flow(data));
245 if (this->temperature_sensor_ !=
nullptr)
246 this->temperature_sensor_->publish_state(read_temperature(data));
249 this->last_valid_frame_ms_ =
millis();
254bool UFM01Component::process_active_stream_() {
255 bool got_valid_frame =
false;
258 if (!this->
read_byte(&this->data_[this->read_index_])) {
259 ESP_LOGW(TAG,
"unable to read byte");
260 this->read_index_ = 0;
263 if ((this->read_index_ == 0 && this->data_[0] != FRAME_START_BYTE_1) ||
264 (this->read_index_ == 1 && this->data_[1] != FRAME_START_BYTE_2)) {
265 ESP_LOGD(TAG,
"not start of data at %d (is 0x%02X)", this->read_index_, this->data_[this->read_index_]);
266 this->read_index_ = 0;
269 if (++this->read_index_ <
static_cast<int32_t
>(FRAME_SIZE))
272 if (validate_active_frame(this->data_)) {
273 this->on_active_frame_(this->data_);
274 this->read_index_ = 0;
275 got_valid_frame =
true;
279 log_hex(this->data_,
sizeof(this->data_));
280 ESP_LOGW(TAG,
"unable to read data");
282 i < static_cast<int32_t>(FRAME_STOP_INDEX) && this->read_index_ ==
static_cast<int32_t
>(FRAME_SIZE); ++i) {
283 if ((this->data_[i] == FRAME_START_BYTE_1) && (this->data_[i + 1] == FRAME_START_BYTE_2)) {
284 for (int32_t j = i; j < static_cast<int32_t>(FRAME_SIZE); ++
j)
285 this->data_[j - i] = this->data_[j];
286 this->read_index_ =
static_cast<int32_t
>(FRAME_SIZE) - i;
289 if (this->read_index_ ==
static_cast<int32_t
>(FRAME_SIZE))
290 this->read_index_ = 0;
293 return got_valid_frame;
296void UFM01Component::set_startup_phase_(
StartupPhase phase) {
297 this->startup_phase_ = phase;
298 this->phase_start_ms_ =
millis();
301void UFM01Component::enter_active_stream_(
const char *reason) {
302 ESP_LOGI(TAG,
"UFM-01 active stream %s", reason);
304 this->passive_read_pending_ =
false;
307void UFM01Component::start_passive_read_() {
308 this->send_command_no_wait_(READ_SENSOR_DATA_NO_ID);
309 this->passive_index_ = 0;
310 this->passive_start_ms_ =
millis();
315 while (this->
available() && this->passive_index_ < PASSIVE_FRAME_SIZE) {
320 if (this->passive_index_ == 0 &&
byte != FRAME_START_BYTE_1)
322 if (this->passive_index_ == 1 &&
byte != PASSIVE_START_BYTE_2) {
324 this->passive_index_ = (
byte == FRAME_START_BYTE_1) ? 1 : 0;
327 this->passive_frame_[this->passive_index_++] = byte;
330 if (this->passive_index_ < PASSIVE_FRAME_SIZE) {
331 if (
millis() - this->passive_start_ms_ < PASSIVE_READ_TIMEOUT_MS)
333 ESP_LOGD(TAG,
"passive read timeout (%zu/%zu bytes)", this->passive_index_, PASSIVE_FRAME_SIZE);
337 if (!validate_passive_frame(this->passive_frame_)) {
338 log_hex(this->passive_frame_, PASSIVE_FRAME_SIZE);
339 ESP_LOGW(TAG,
"invalid passive frame");
343 uint8_t active_frame[FRAME_SIZE];
344 passive_no_id_to_active_frame(this->passive_frame_, active_frame);
345 this->on_active_frame_(active_frame);
349void UFM01Component::loop_startup_() {
352 switch (this->startup_phase_) {
355 if (this->process_active_stream_()) {
356 this->enter_active_stream_(
"started");
359 if (elapsed < this->startup_wait_ms_)
361 ESP_LOGD(TAG,
"Running startup sequence");
363 this->reset_retried_ =
false;
364 this->send_command_no_wait_(RESET_DEVICE);
369 if (this->consume_ack_()) {
373 if (elapsed < COMMAND_ACK_TIMEOUT_MS)
375 if (!this->reset_retried_) {
376 ESP_LOGW(TAG,
"Reset not acknowledged, retrying in %" PRIu32
" ms", RESET_RETRY_DELAY_MS);
379 ESP_LOGW(TAG,
"Reset failed during startup");
385 if (elapsed < RESET_RETRY_DELAY_MS)
387 this->reset_retried_ =
true;
388 this->send_command_no_wait_(RESET_DEVICE);
393 if (elapsed < POST_RESET_DELAY_MS)
395 this->send_command_no_wait_(ACTIVE_MODE);
401 if (this->process_active_stream_()) {
402 this->enter_active_stream_(
"started");
405 if (elapsed < ACTIVE_FRAME_TIMEOUT_MS)
407 this->start_passive_read_();
412 switch (this->continue_passive_read_()) {
416 ESP_LOGI(TAG,
"UFM-01 using passive polling");
418 this->passive_read_pending_ =
false;
419 this->last_poll_ms_ =
millis();
422 ESP_LOGW(TAG,
"Startup failed, retrying in %" PRIu32
" ms", STARTUP_RETRY_MS);
423 this->startup_wait_ms_ = STARTUP_RETRY_MS;
430void UFM01Component::loop_active_stream_() {
431 this->process_active_stream_();
432 if (this->last_valid_frame_ms_ != 0 &&
millis() - this->last_valid_frame_ms_ > ACTIVE_STALE_MS) {
433 ESP_LOGW(TAG,
"Active stream stale, switching to passive polling");
435 this->passive_read_pending_ =
false;
436 this->last_poll_ms_ = 0;
441void UFM01Component::loop_passive_poll_() {
442 if (this->passive_read_pending_) {
446 this->passive_read_pending_ =
false;
452 if (this->process_active_stream_()) {
453 this->enter_active_stream_(
"resumed");
457 if (
millis() - this->last_poll_ms_ >= PASSIVE_POLL_INTERVAL_MS) {
458 this->last_poll_ms_ =
millis();
459 this->start_passive_read_();
460 this->passive_read_pending_ =
true;
464void UFM01Component::loop() {
465 switch (this->operating_mode_) {
467 this->loop_startup_();
470 this->loop_active_stream_();
473 this->loop_passive_poll_();