ESPHome 2026.1.4
Loading...
Searching...
No Matches
mopeka_std_check.cpp
Go to the documentation of this file.
1#include "mopeka_std_check.h"
2
4#include "esphome/core/log.h"
5
6#ifdef USE_ESP32
7
8namespace esphome {
9namespace mopeka_std_check {
10
11static const char *const TAG = "mopeka_std_check";
12static const uint16_t SERVICE_UUID = 0xADA0;
13static const uint8_t MANUFACTURER_DATA_LENGTH = 23;
14static const uint16_t MANUFACTURER_ID = 0x000D;
15
16// Maximum bytes to log in very verbose hex output
17static constexpr size_t MOPEKA_MAX_LOG_BYTES = 32;
18
20 ESP_LOGCONFIG(TAG,
21 "Mopeka Std Check\n"
22 " Propane Butane mix: %.0f%%\n"
23 " Tank distance empty: %" PRIi32 "mm\n"
24 " Tank distance full: %" PRIi32 "mm",
25 this->propane_butane_mix_ * 100, this->empty_mm_, this->full_mm_);
26 LOG_SENSOR(" ", "Level", this->level_);
27 LOG_SENSOR(" ", "Temperature", this->temperature_);
28 LOG_SENSOR(" ", "Battery Level", this->battery_level_);
29 LOG_SENSOR(" ", "Reading Distance", this->distance_);
30}
31
38 // Validate address.
39 if (device.address_uint64() != this->address_) {
40 return false;
41 }
42
43 // Stack buffer for MAC address formatting - reused throughout function
44 char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
45 const char *addr_str = device.address_str_to(addr_buf);
46
47 ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str);
48
49 {
50 // Validate service uuid
51 const auto &service_uuids = device.get_service_uuids();
52 if (service_uuids.size() != 1) {
53 return false;
54 }
55 const auto &service_uuid = service_uuids[0];
56 if (service_uuid != esp32_ble_tracker::ESPBTUUID::from_uint16(SERVICE_UUID)) {
57 return false;
58 }
59 }
60
61 const auto &manu_datas = device.get_manufacturer_datas();
62
63 if (manu_datas.size() != 1) {
64 ESP_LOGE(TAG, "[%s] Unexpected manu_datas size (%d)", addr_str, manu_datas.size());
65 return false;
66 }
67
68 const auto &manu_data = manu_datas[0];
69
70#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
71 char hex_buf[format_hex_pretty_size(MOPEKA_MAX_LOG_BYTES)];
72#endif
73 ESP_LOGVV(TAG, "[%s] Manufacturer data: %s", addr_str,
74 format_hex_pretty_to(hex_buf, manu_data.data.data(), manu_data.data.size()));
75
76 if (manu_data.data.size() != MANUFACTURER_DATA_LENGTH) {
77 ESP_LOGE(TAG, "[%s] Unexpected manu_data size (%d)", addr_str, manu_data.data.size());
78 return false;
79 }
80
81 // Now parse the data
82 const auto *mopeka_data = (const mopeka_std_package *) manu_data.data.data();
83
84 const u_int8_t hardware_id = mopeka_data->data_1 & 0xCF;
85 if (static_cast<SensorType>(hardware_id) != STANDARD && static_cast<SensorType>(hardware_id) != XL &&
86 static_cast<SensorType>(hardware_id) != ETRAILER && static_cast<SensorType>(hardware_id) != STANDARD_ALT) {
87 ESP_LOGE(TAG, "[%s] Unsupported Sensor Type (0x%X)", addr_str, hardware_id);
88 return false;
89 }
90
91 ESP_LOGVV(TAG, "[%s] Sensor slow update rate: %d", addr_str, mopeka_data->slow_update_rate);
92 ESP_LOGVV(TAG, "[%s] Sensor sync pressed: %d", addr_str, mopeka_data->sync_pressed);
93 for (u_int8_t i = 0; i < 3; i++) {
94 ESP_LOGVV(TAG, "[%s] %u. Sensor data %u time %u.", addr_str, (i * 4) + 1, mopeka_data->val[i].value_0,
95 mopeka_data->val[i].time_0);
96 ESP_LOGVV(TAG, "[%s] %u. Sensor data %u time %u.", addr_str, (i * 4) + 2, mopeka_data->val[i].value_1,
97 mopeka_data->val[i].time_1);
98 ESP_LOGVV(TAG, "[%s] %u. Sensor data %u time %u.", addr_str, (i * 4) + 3, mopeka_data->val[i].value_2,
99 mopeka_data->val[i].time_2);
100 ESP_LOGVV(TAG, "[%s] %u. Sensor data %u time %u.", addr_str, (i * 4) + 4, mopeka_data->val[i].value_3,
101 mopeka_data->val[i].time_3);
102 }
103
104 // Get battery level first
105 if (this->battery_level_ != nullptr) {
106 uint8_t level = this->parse_battery_level_(mopeka_data);
107 this->battery_level_->publish_state(level);
108 }
109
110 // Get temperature of sensor
111 uint8_t temp_in_c = this->parse_temperature_(mopeka_data);
112 if (this->temperature_ != nullptr) {
113 this->temperature_->publish_state(temp_in_c);
114 }
115
116 // Get distance and level if either are sensors
117 if ((this->distance_ != nullptr) || (this->level_ != nullptr)) {
118 // Message contains 12 sensor dataset each 10 bytes long.
119 // each sensor dataset contains 5 byte time and 5 byte value.
120
121 // time in 10us ticks.
122 // value is amplitude.
123
124 std::array<u_int8_t, 12> measurements_time = {};
125 std::array<u_int8_t, 12> measurements_value = {};
126 // Copy measurements over into my array.
127 {
128 u_int8_t measurements_index = 0;
129 for (u_int8_t i = 0; i < 3; i++) {
130 measurements_time[measurements_index] = mopeka_data->val[i].time_0 + 1;
131 measurements_value[measurements_index] = mopeka_data->val[i].value_0;
132 measurements_index++;
133 measurements_time[measurements_index] = mopeka_data->val[i].time_1 + 1;
134 measurements_value[measurements_index] = mopeka_data->val[i].value_1;
135 measurements_index++;
136 measurements_time[measurements_index] = mopeka_data->val[i].time_2 + 1;
137 measurements_value[measurements_index] = mopeka_data->val[i].value_2;
138 measurements_index++;
139 measurements_time[measurements_index] = mopeka_data->val[i].time_3 + 1;
140 measurements_value[measurements_index] = mopeka_data->val[i].value_3;
141 measurements_index++;
142 }
143 }
144
145 // Find best(strongest) value(amplitude) and it's belonging time in sensor dataset.
146 u_int8_t number_of_usable_values = 0;
147 u_int16_t best_value = 0;
148 u_int16_t best_time = 0;
149 {
150 u_int16_t measurement_time = 0;
151 for (u_int8_t i = 0; i < 12; i++) {
152 // Time is summed up until a value is reported. This allows time values larger than the 5 bits in transport.
153 measurement_time += measurements_time[i];
154 if (measurements_value[i] != 0) {
155 // I got a value
156 number_of_usable_values++;
157 if (measurements_value[i] > best_value) {
158 // This value is better than a previous one.
159 best_value = measurements_value[i];
160 best_time = measurement_time;
161 }
162 // Reset measurement_time or next values.
163 measurement_time = 0;
164 }
165 }
166 }
167
168 ESP_LOGV(TAG, "[%s] Found %u values with best data %u time %u.", addr_str, number_of_usable_values, best_value,
169 best_time);
170
171 if (number_of_usable_values < 1 || best_value < 2 || best_time < 2) {
172 // At least two measurement values must be present.
173 ESP_LOGW(TAG, "[%s] Poor read quality. Setting distance to 0.", addr_str);
174 if (this->distance_ != nullptr) {
175 this->distance_->publish_state(0);
176 }
177 if (this->level_ != nullptr) {
178 this->level_->publish_state(0);
179 }
180 } else {
181 float lpg_speed_of_sound = this->get_lpg_speed_of_sound_(temp_in_c);
182 ESP_LOGV(TAG, "[%s] Speed of sound in current fluid %f m/s", addr_str, lpg_speed_of_sound);
183
184 uint32_t distance_value = lpg_speed_of_sound * best_time / 100.0f;
185
186 // update distance sensor
187 if (this->distance_ != nullptr) {
188 this->distance_->publish_state(distance_value);
189 }
190
191 // update level sensor
192 if (this->level_ != nullptr) {
193 uint8_t tank_level = 0;
194 if (distance_value >= this->full_mm_) {
195 tank_level = 100; // cap at 100%
196 } else if (distance_value > this->empty_mm_) {
197 tank_level = ((100.0f / (this->full_mm_ - this->empty_mm_)) * (distance_value - this->empty_mm_));
198 }
199 this->level_->publish_state(tank_level);
200 }
201 }
202 }
203
204 return true;
205}
206
208 return 1040.71f - 4.87f * temperature - 137.5f * this->propane_butane_mix_ - 0.0107f * temperature * temperature -
209 1.63f * temperature * this->propane_butane_mix_;
210}
211
213 const float voltage = (float) ((message->raw_voltage / 256.0f) * 2.0f + 1.5f);
214 ESP_LOGVV(TAG, "Sensor battery voltage: %f V", voltage);
215 // convert voltage and scale for CR2032
216 const float percent = (voltage - 2.2f) / 0.65f * 100.0f;
217 if (percent < 0.0f) {
218 return 0;
219 }
220 if (percent > 100.0f) {
221 return 100;
222 }
223 return (uint8_t) percent;
224}
225
227 uint8_t tmp = message->raw_temp;
228 if (tmp == 0x0) {
229 return -40;
230 } else {
231 return (uint8_t) ((tmp - 25.0f) * 1.776964f);
232 }
233}
234
235} // namespace mopeka_std_check
236} // namespace esphome
237
238#endif
const char * address_str_to(std::span< char, MAC_ADDRESS_PRETTY_BUFFER_SIZE > buf) const
Format MAC address into provided buffer, returns pointer to buffer for convenience.
const std::vector< ServiceData > & get_manufacturer_datas() const
const std::vector< ESPBTUUID > & get_service_uuids() const
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override
Main parse function that gets called for all ble advertisements.
uint8_t parse_temperature_(const mopeka_std_package *message)
uint8_t parse_battery_level_(const mopeka_std_package *message)
void publish_state(float state)
Publish a new state to the front-end.
Definition sensor.cpp:76
const char * message
Definition component.cpp:38
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
char * format_hex_pretty_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length, char separator)
Format byte array as uppercase hex to buffer (base implementation).
Definition helpers.cpp:334
constexpr size_t format_hex_pretty_size(size_t byte_count)
Calculate buffer size needed for format_hex_pretty_to with separator: "XX:XX:...:XX\0".
Definition helpers.h:830
uint16_t temperature
Definition sun_gtil2.cpp:12