ESPHome 2025.5.2
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Modules Pages
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
17 ESP_LOGCONFIG(TAG, "Mopeka Std Check");
18 ESP_LOGCONFIG(TAG, " Propane Butane mix: %.0f%%", this->propane_butane_mix_ * 100);
19 ESP_LOGCONFIG(TAG, " Tank distance empty: %" PRIi32 "mm", this->empty_mm_);
20 ESP_LOGCONFIG(TAG, " Tank distance full: %" PRIi32 "mm", this->full_mm_);
21 LOG_SENSOR(" ", "Level", this->level_);
22 LOG_SENSOR(" ", "Temperature", this->temperature_);
23 LOG_SENSOR(" ", "Battery Level", this->battery_level_);
24 LOG_SENSOR(" ", "Reading Distance", this->distance_);
25}
26
33 {
34 // Validate address.
35 if (device.address_uint64() != this->address_) {
36 return false;
37 }
38
39 ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str());
40 }
41
42 {
43 // Validate service uuid
44 const auto &service_uuids = device.get_service_uuids();
45 if (service_uuids.size() != 1) {
46 return false;
47 }
48 const auto &service_uuid = service_uuids[0];
49 if (service_uuid != esp32_ble_tracker::ESPBTUUID::from_uint16(SERVICE_UUID)) {
50 return false;
51 }
52 }
53
54 const auto &manu_datas = device.get_manufacturer_datas();
55
56 if (manu_datas.size() != 1) {
57 ESP_LOGE(TAG, "[%s] Unexpected manu_datas size (%d)", device.address_str().c_str(), manu_datas.size());
58 return false;
59 }
60
61 const auto &manu_data = manu_datas[0];
62
63 ESP_LOGVV(TAG, "[%s] Manufacturer data: %s", device.address_str().c_str(), format_hex_pretty(manu_data.data).c_str());
64
65 if (manu_data.data.size() != MANUFACTURER_DATA_LENGTH) {
66 ESP_LOGE(TAG, "[%s] Unexpected manu_data size (%d)", device.address_str().c_str(), manu_data.data.size());
67 return false;
68 }
69
70 // Now parse the data
71 const auto *mopeka_data = (const mopeka_std_package *) manu_data.data.data();
72
73 const u_int8_t hardware_id = mopeka_data->data_1 & 0xCF;
74 if (static_cast<SensorType>(hardware_id) != STANDARD && static_cast<SensorType>(hardware_id) != XL &&
75 static_cast<SensorType>(hardware_id) != ETRAILER) {
76 ESP_LOGE(TAG, "[%s] Unsupported Sensor Type (0x%X)", device.address_str().c_str(), hardware_id);
77 return false;
78 }
79
80 ESP_LOGVV(TAG, "[%s] Sensor slow update rate: %d", device.address_str().c_str(), mopeka_data->slow_update_rate);
81 ESP_LOGVV(TAG, "[%s] Sensor sync pressed: %d", device.address_str().c_str(), mopeka_data->sync_pressed);
82 for (u_int8_t i = 0; i < 3; i++) {
83 ESP_LOGVV(TAG, "[%s] %u. Sensor data %u time %u.", device.address_str().c_str(), (i * 4) + 1,
84 mopeka_data->val[i].value_0, mopeka_data->val[i].time_0);
85 ESP_LOGVV(TAG, "[%s] %u. Sensor data %u time %u.", device.address_str().c_str(), (i * 4) + 2,
86 mopeka_data->val[i].value_1, mopeka_data->val[i].time_1);
87 ESP_LOGVV(TAG, "[%s] %u. Sensor data %u time %u.", device.address_str().c_str(), (i * 4) + 3,
88 mopeka_data->val[i].value_2, mopeka_data->val[i].time_2);
89 ESP_LOGVV(TAG, "[%s] %u. Sensor data %u time %u.", device.address_str().c_str(), (i * 4) + 4,
90 mopeka_data->val[i].value_3, mopeka_data->val[i].time_3);
91 }
92
93 // Get battery level first
94 if (this->battery_level_ != nullptr) {
95 uint8_t level = this->parse_battery_level_(mopeka_data);
96 this->battery_level_->publish_state(level);
97 }
98
99 // Get temperature of sensor
100 uint8_t temp_in_c = this->parse_temperature_(mopeka_data);
101 if (this->temperature_ != nullptr) {
102 this->temperature_->publish_state(temp_in_c);
103 }
104
105 // Get distance and level if either are sensors
106 if ((this->distance_ != nullptr) || (this->level_ != nullptr)) {
107 // Message contains 12 sensor dataset each 10 bytes long.
108 // each sensor dataset contains 5 byte time and 5 byte value.
109
110 // time in 10us ticks.
111 // value is amplitude.
112
113 std::array<u_int8_t, 12> measurements_time = {};
114 std::array<u_int8_t, 12> measurements_value = {};
115 // Copy measurements over into my array.
116 {
117 u_int8_t measurements_index = 0;
118 for (u_int8_t i = 0; i < 3; i++) {
119 measurements_time[measurements_index] = mopeka_data->val[i].time_0 + 1;
120 measurements_value[measurements_index] = mopeka_data->val[i].value_0;
121 measurements_index++;
122 measurements_time[measurements_index] = mopeka_data->val[i].time_1 + 1;
123 measurements_value[measurements_index] = mopeka_data->val[i].value_1;
124 measurements_index++;
125 measurements_time[measurements_index] = mopeka_data->val[i].time_2 + 1;
126 measurements_value[measurements_index] = mopeka_data->val[i].value_2;
127 measurements_index++;
128 measurements_time[measurements_index] = mopeka_data->val[i].time_3 + 1;
129 measurements_value[measurements_index] = mopeka_data->val[i].value_3;
130 measurements_index++;
131 }
132 }
133
134 // Find best(strongest) value(amplitude) and it's belonging time in sensor dataset.
135 u_int8_t number_of_usable_values = 0;
136 u_int16_t best_value = 0;
137 u_int16_t best_time = 0;
138 {
139 u_int16_t measurement_time = 0;
140 for (u_int8_t i = 0; i < 12; i++) {
141 // Time is summed up until a value is reported. This allows time values larger than the 5 bits in transport.
142 measurement_time += measurements_time[i];
143 if (measurements_value[i] != 0) {
144 // I got a value
145 number_of_usable_values++;
146 if (measurements_value[i] > best_value) {
147 // This value is better than a previous one.
148 best_value = measurements_value[i];
149 best_time = measurement_time;
150 }
151 // Reset measurement_time or next values.
152 measurement_time = 0;
153 }
154 }
155 }
156
157 ESP_LOGV(TAG, "[%s] Found %u values with best data %u time %u.", device.address_str().c_str(),
158 number_of_usable_values, best_value, best_time);
159
160 if (number_of_usable_values < 1 || best_value < 2 || best_time < 2) {
161 // At least two measurement values must be present.
162 ESP_LOGW(TAG, "[%s] Poor read quality. Setting distance to 0.", device.address_str().c_str());
163 if (this->distance_ != nullptr) {
164 this->distance_->publish_state(0);
165 }
166 if (this->level_ != nullptr) {
167 this->level_->publish_state(0);
168 }
169 } else {
170 float lpg_speed_of_sound = this->get_lpg_speed_of_sound_(temp_in_c);
171 ESP_LOGV(TAG, "[%s] Speed of sound in current fluid %f m/s", device.address_str().c_str(), lpg_speed_of_sound);
172
173 uint32_t distance_value = lpg_speed_of_sound * best_time / 100.0f;
174
175 // update distance sensor
176 if (this->distance_ != nullptr) {
177 this->distance_->publish_state(distance_value);
178 }
179
180 // update level sensor
181 if (this->level_ != nullptr) {
182 uint8_t tank_level = 0;
183 if (distance_value >= this->full_mm_) {
184 tank_level = 100; // cap at 100%
185 } else if (distance_value > this->empty_mm_) {
186 tank_level = ((100.0f / (this->full_mm_ - this->empty_mm_)) * (distance_value - this->empty_mm_));
187 }
188 this->level_->publish_state(tank_level);
189 }
190 }
191 }
192
193 return true;
194}
195
197 return 1040.71f - 4.87f * temperature - 137.5f * this->propane_butane_mix_ - 0.0107f * temperature * temperature -
198 1.63f * temperature * this->propane_butane_mix_;
199}
200
202 const float voltage = (float) ((message->raw_voltage / 256.0f) * 2.0f + 1.5f);
203 ESP_LOGVV(TAG, "Sensor battery voltage: %f V", voltage);
204 // convert voltage and scale for CR2032
205 const float percent = (voltage - 2.2f) / 0.65f * 100.0f;
206 if (percent < 0.0f) {
207 return 0;
208 }
209 if (percent > 100.0f) {
210 return 100;
211 }
212 return (uint8_t) percent;
213}
214
216 uint8_t tmp = message->raw_temp;
217 if (tmp == 0x0) {
218 return -40;
219 } else {
220 return (uint8_t) ((tmp - 25.0f) * 1.776964f);
221 }
222}
223
224} // namespace mopeka_std_check
225} // namespace esphome
226
227#endif
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:39
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
std::string format_hex_pretty(const uint8_t *data, size_t length)
Format the byte array data of length len in pretty-printed, human-readable hex.
Definition helpers.cpp:372
uint16_t temperature
Definition sun_gtil2.cpp:12