ESPHome  2024.4.0
mopeka_std_check.cpp
Go to the documentation of this file.
1 #include "mopeka_std_check.h"
2 
3 #include "esphome/core/helpers.h"
4 #include "esphome/core/log.h"
5 
6 #ifdef USE_ESP32
7 
8 namespace esphome {
9 namespace mopeka_std_check {
10 
11 static const char *const TAG = "mopeka_std_check";
12 static const uint16_t SERVICE_UUID = 0xADA0;
13 static const uint8_t MANUFACTURER_DATA_LENGTH = 23;
14 static 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
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:361
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override
Main parse function that gets called for all ble advertisements.
const std::vector< ServiceData > & get_manufacturer_datas() const
uint8_t parse_temperature_(const mopeka_std_package *message)
const std::vector< ESPBTUUID > & get_service_uuids() const
float get_lpg_speed_of_sound_(float temperature)
static ESPBTUUID from_uint16(uint16_t uuid)
Definition: ble_uuid.cpp:16
void publish_state(float state)
Publish a new state to the front-end.
Definition: sensor.cpp:39
uint16_t temperature
Definition: sun_gtil2.cpp:26
uint8_t parse_battery_level_(const mopeka_std_package *message)
This is a workaround until we can figure out a way to get the tflite-micro idf component code availab...
Definition: a01nyub.cpp:7