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