ESPHome  2024.4.1
mopeka_pro_check.cpp
Go to the documentation of this file.
1 #include "mopeka_pro_check.h"
2 #include "esphome/core/log.h"
3 
4 #ifdef USE_ESP32
5 
6 namespace esphome {
7 namespace mopeka_pro_check {
8 
9 static const char *const TAG = "mopeka_pro_check";
10 static const uint8_t MANUFACTURER_DATA_LENGTH = 10;
11 static const uint16_t MANUFACTURER_ID = 0x0059;
12 static const double MOPEKA_LPG_COEF[] = {0.573045, -0.002822, -0.00000535}; // Magic numbers provided by Mopeka
13 
15  ESP_LOGCONFIG(TAG, "Mopeka Pro Check");
16  LOG_SENSOR(" ", "Level", this->level_);
17  LOG_SENSOR(" ", "Temperature", this->temperature_);
18  LOG_SENSOR(" ", "Battery Level", this->battery_level_);
19  LOG_SENSOR(" ", "Reading Distance", this->distance_);
20 }
21 
28  if (device.address_uint64() != this->address_) {
29  return false;
30  }
31 
32  ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str());
33 
34  const auto &manu_datas = device.get_manufacturer_datas();
35 
36  if (manu_datas.size() != 1) {
37  ESP_LOGE(TAG, "Unexpected manu_datas size (%d)", manu_datas.size());
38  return false;
39  }
40 
41  const auto &manu_data = manu_datas[0];
42 
43  ESP_LOGVV(TAG, "Manufacturer data:");
44  for (const uint8_t byte : manu_data.data) {
45  ESP_LOGVV(TAG, "0x%02x", byte);
46  }
47 
48  if (manu_data.data.size() != MANUFACTURER_DATA_LENGTH) {
49  ESP_LOGE(TAG, "Unexpected manu_data size (%d)", manu_data.data.size());
50  return false;
51  }
52 
53  // Now parse the data - See Datasheet for definition
54 
55  if (static_cast<SensorType>(manu_data.data[0]) != STANDARD_BOTTOM_UP &&
56  static_cast<SensorType>(manu_data.data[0]) != LIPPERT_BOTTOM_UP &&
57  static_cast<SensorType>(manu_data.data[0]) != PLUS_BOTTOM_UP &&
58  static_cast<SensorType>(manu_data.data[0]) != PRO_UNIVERSAL) {
59  ESP_LOGE(TAG, "Unsupported Sensor Type (0x%X)", manu_data.data[0]);
60  return false;
61  }
62 
63  // Get battery level first
64  if (this->battery_level_ != nullptr) {
65  uint8_t level = this->parse_battery_level_(manu_data.data);
66  this->battery_level_->publish_state(level);
67  }
68 
69  // Get distance and level if either are sensors
70  if ((this->distance_ != nullptr) || (this->level_ != nullptr)) {
71  uint32_t distance_value = this->parse_distance_(manu_data.data);
72  SensorReadQuality quality_value = this->parse_read_quality_(manu_data.data);
73  ESP_LOGD(TAG, "Distance Sensor: Quality (0x%X) Distance (%" PRId32 "mm)", quality_value, distance_value);
74  if (quality_value < QUALITY_HIGH) {
75  ESP_LOGW(TAG, "Poor read quality.");
76  }
77  if (quality_value < QUALITY_MED) {
78  // if really bad reading set to 0
79  ESP_LOGW(TAG, "Setting distance to 0");
80  distance_value = 0;
81  }
82 
83  // update distance sensor
84  if (this->distance_ != nullptr) {
85  this->distance_->publish_state(distance_value);
86  }
87 
88  // update level sensor
89  if (this->level_ != nullptr) {
90  uint8_t tank_level = 0;
91  if (distance_value >= this->full_mm_) {
92  tank_level = 100; // cap at 100%
93  } else if (distance_value > this->empty_mm_) {
94  tank_level = ((100.0f / (this->full_mm_ - this->empty_mm_)) * (distance_value - this->empty_mm_));
95  }
96  this->level_->publish_state(tank_level);
97  }
98  }
99 
100  // Get temperature of sensor
101  if (this->temperature_ != nullptr) {
102  uint8_t temp_in_c = this->parse_temperature_(manu_data.data);
103  this->temperature_->publish_state(temp_in_c);
104  }
105 
106  return true;
107 }
108 
109 uint8_t MopekaProCheck::parse_battery_level_(const std::vector<uint8_t> &message) {
110  float v = (float) ((message[1] & 0x7F) / 32.0f);
111  // convert voltage and scale for CR2032
112  float percent = (v - 2.2f) / 0.65f * 100.0f;
113  if (percent < 0.0f) {
114  return 0;
115  }
116  if (percent > 100.0f) {
117  return 100;
118  }
119  return (uint8_t) percent;
120 }
121 
122 uint32_t MopekaProCheck::parse_distance_(const std::vector<uint8_t> &message) {
123  uint16_t raw = (message[4] * 256) + message[3];
124  double raw_level = raw & 0x3FFF;
125  double raw_t = (message[2] & 0x7F);
126 
127  return (uint32_t) (raw_level *
128  (MOPEKA_LPG_COEF[0] + MOPEKA_LPG_COEF[1] * raw_t + MOPEKA_LPG_COEF[2] * raw_t * raw_t));
129 }
130 
131 uint8_t MopekaProCheck::parse_temperature_(const std::vector<uint8_t> &message) { return (message[2] & 0x7F) - 40; }
132 
133 SensorReadQuality MopekaProCheck::parse_read_quality_(const std::vector<uint8_t> &message) {
134  return static_cast<SensorReadQuality>(message[4] >> 6);
135 }
136 
137 } // namespace mopeka_pro_check
138 } // namespace esphome
139 
140 #endif
uint8_t raw[35]
Definition: bl0939.h:19
SensorReadQuality parse_read_quality_(const std::vector< uint8_t > &message)
uint32_t parse_distance_(const std::vector< uint8_t > &message)
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_battery_level_(const std::vector< uint8_t > &message)
void publish_state(float state)
Publish a new state to the front-end.
Definition: sensor.cpp:39
uint8_t parse_temperature_(const std::vector< uint8_t > &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