ESPHome  2022.9.1
bedjet_codec.cpp
Go to the documentation of this file.
1 #include "bedjet_codec.h"
2 #include <cstdio>
3 #include <cstring>
4 
5 namespace esphome {
6 namespace bedjet {
7 
9 float bedjet_temp_to_f(const uint8_t temp) {
10  // BedJet temp is "C*2"; to get F, multiply by 0.9 (half 1.8) and add 32.
11  return 0.9f * temp + 32.0f;
12 }
13 
16  // So far no commands require more than 2 bytes of data.
17  assert(this->packet_.data_length <= 2);
18  for (int i = this->packet_.data_length; i < 2; i++) {
19  this->packet_.data[i] = '\0';
20  }
21  ESP_LOGV(TAG, "Created packet: %02X, %02X %02X", this->packet_.command, this->packet_.data[0], this->packet_.data[1]);
22  return &this->packet_;
23 }
24 
27  this->packet_.command = CMD_BUTTON;
28  this->packet_.data_length = 1;
29  this->packet_.data[0] = button;
30  return this->clean_packet_();
31 }
32 
36  this->packet_.data_length = 1;
37  this->packet_.data[0] = temperature * 2;
38  return this->clean_packet_();
39 }
40 
43  this->packet_.command = CMD_SET_FAN;
44  this->packet_.data_length = 1;
45  this->packet_.data[0] = fan_step;
46  return this->clean_packet_();
47 }
48 
50 BedjetPacket *BedjetCodec::get_set_time_request(const uint8_t hour, const uint8_t minute) {
52  this->packet_.data_length = 2;
53  this->packet_.data[0] = hour;
54  this->packet_.data[1] = minute;
55  return this->clean_packet_();
56 }
57 
59 BedjetPacket *BedjetCodec::get_set_runtime_remaining_request(const uint8_t hour, const uint8_t minute) {
61  this->packet_.data_length = 2;
62  this->packet_.data[0] = hour;
63  this->packet_.data[1] = minute;
64  return this->clean_packet_();
65 }
66 
68 void BedjetCodec::decode_extra(const uint8_t *data, uint16_t length) {
69  ESP_LOGVV(TAG, "Received extra: %d bytes: %d %d %d %d", length, data[1], data[2], data[3], data[4]);
70  uint8_t offset = this->last_buffer_size_;
71  if (offset > 0 && length + offset <= sizeof(BedjetStatusPacket)) {
72  memcpy(((uint8_t *) (&this->buf_)) + offset, data, length);
73  ESP_LOGVV(TAG,
74  "Extra bytes: skip1=0x%08x, skip2=0x%04x, skip3=0x%02x; update phase=0x%02x, "
75  "flags=BedjetFlags <conn=%c, leds=%c, units=%c, mute=%c; packed=%02x>",
76  this->buf_.unused_1, this->buf_.unused_2, this->buf_.unused_3, this->buf_.update_phase,
77  this->buf_.flags.conn_test_passed ? '1' : '0', this->buf_.flags.leds_enabled ? '1' : '0',
78  this->buf_.flags.units_setup ? '1' : '0', this->buf_.flags.beeps_muted ? '1' : '0',
79  this->buf_.flags_packed);
80  } else {
81  ESP_LOGI(TAG, "Could not determine where to append to, last offset=%d, max size=%u, new size would be %d", offset,
82  sizeof(BedjetStatusPacket), length + offset);
83  }
84 }
85 
90 bool BedjetCodec::decode_notify(const uint8_t *data, uint16_t length) {
91  ESP_LOGV(TAG, "Received: %d bytes: %d %d %d %d", length, data[1], data[2], data[3], data[4]);
92 
93  if (data[1] == PACKET_FORMAT_V3_HOME && data[3] == PACKET_TYPE_STATUS) {
94  // Clear old buffer
95  memset(&this->buf_, 0, sizeof(BedjetStatusPacket));
96  // Copy new data into buffer
97  memcpy(&this->buf_, data, length);
98  this->last_buffer_size_ = length;
99 
100  // TODO: validate the packet checksum?
101  if (this->buf_.mode < 7 && this->buf_.target_temp_step >= 38 && this->buf_.target_temp_step <= 86 &&
102  this->buf_.actual_temp_step > 1 && this->buf_.actual_temp_step <= 100 && this->buf_.ambient_temp_step > 1 &&
103  this->buf_.ambient_temp_step <= 100) {
104  // and save it for the update() loop
105  this->status_packet_ = &this->buf_;
106  return this->buf_.is_partial;
107  } else {
108  this->status_packet_ = nullptr;
109  // TODO: log a warning if we detect that we connected to a non-V3 device.
110  ESP_LOGW(TAG, "Received potentially invalid packet (len %d):", length);
111  }
112  } else if (data[1] == PACKET_FORMAT_DEBUG || data[3] == PACKET_TYPE_DEBUG) {
113  // We don't actually know the packet format for this. Dump packets to log, in case a pattern presents itself.
114  ESP_LOGVV(TAG,
115  "received DEBUG packet: set1=%01fF, set2=%01fF, air=%01fF; [7]=%d, [8]=%d, [9]=%d, [10]=%d, [11]=%d, "
116  "[12]=%d, [-1]=%d",
117  bedjet_temp_to_f(data[4]), bedjet_temp_to_f(data[5]), bedjet_temp_to_f(data[6]), data[7], data[8],
118  data[9], data[10], data[11], data[12], data[length - 1]);
119 
120  if (this->has_status()) {
121  this->status_packet_->ambient_temp_step = data[6];
122  }
123  } else {
124  // TODO: log a warning if we detect that we connected to a non-V3 device.
125  }
126 
127  return false;
128 }
129 
131 bool BedjetCodec::compare(const uint8_t *data, uint16_t length) {
132  if (data == nullptr) {
133  return false;
134  }
135 
136  if (length < 17) {
137  // New packet looks small, skip it.
138  return false;
139  }
140 
142  this->buf_.packet_type != PACKET_TYPE_STATUS) { // No last seen packet, so take the new one.
143  return true;
144  }
145 
146  if (data[1] != PACKET_FORMAT_V3_HOME || data[3] != PACKET_TYPE_STATUS) { // New packet is not a v3 status, skip it.
147  return false;
148  }
149 
150  // Now coerce it to a status packet and compare some key fields
151  const BedjetStatusPacket *test = reinterpret_cast<const BedjetStatusPacket *>(data);
152  // These are fields that will only change due to explicit action.
153  // That is why we do not check ambient or actual temp here, because those are environmental.
154  bool explicit_fields_changed = this->buf_.mode != test->mode || this->buf_.fan_step != test->fan_step ||
155  this->buf_.target_temp_step != test->target_temp_step;
156 
157  return explicit_fields_changed;
158 }
159 
160 } // namespace bedjet
161 } // namespace esphome
BedjetPacketFormat packet_format
BedjetPacketFormat::PACKET_FORMAT_V3_HOME for BedJet V3 status packet format.
Definition: bedjet_codec.h:43
bool compare(const uint8_t *data, uint16_t length)
uint8_t fan_step
BedJet fan speed; value is in the 0-19 range, representing 5% increments (5%-100%): `5 + 5...
Definition: bedjet_codec.h:62
BedjetStatusPacket buf_
Definition: bedjet_codec.h:187
bool is_partial
1 indicates that this is a partial packet, and more data can be read directly from the characteristic...
Definition: bedjet_codec.h:41
BedjetPacket * get_set_target_temp_request(float temperature)
Returns a BedjetPacket that will set the device&#39;s target temperature.
float temperature
Definition: qmp6988.h:71
uint8_t ambient_temp_step
Current ambient air temp.
Definition: bedjet_codec.h:73
The format of a BedJet V3 status packet.
Definition: bedjet_codec.h:39
BedjetMode mode
BedJet operating mode.
Definition: bedjet_codec.h:59
BedjetPacket * clean_packet_()
Cleans up the packet before sending.
float bedjet_temp_to_f(const uint8_t temp)
Converts a BedJet temp step into degrees Fahrenheit.
Definition: bedjet_codec.cpp:9
void decode_extra(const uint8_t *data, uint16_t length)
Decodes the extra bytes that were received after being notified with a partial packet.
BedjetPacket * get_set_fan_speed_request(uint8_t fan_step)
Returns a BedjetPacket that will set the device&#39;s target fan speed.
BedjetPacket * get_button_request(BedjetButton button)
Returns a BedjetPacket that will initiate a BedjetButton press.
BedjetPacket * get_set_time_request(uint8_t hour, uint8_t minute)
Returns a BedjetPacket that will set the device&#39;s current time.
BedjetPacket * get_set_runtime_remaining_request(uint8_t hour, uint8_t minute)
Returns a BedjetPacket that will set the device&#39;s remaining runtime.
uint8_t target_temp_step
Target temp that the BedJet will try to heat to. See actual_temp_step.
Definition: bedjet_codec.h:56
Definition: a4988.cpp:4
uint8_t fan_step
BedJet fan speed; value is in the 0-19 range, representing 5% increments (5%-100%): `5 + 5...
Definition: bedjet_codec.h:154
BedjetStatusPacket * status_packet_
Definition: bedjet_codec.h:186
bool decode_notify(const uint8_t *data, uint16_t length)
Decodes the incoming status packet received on the BEDJET_STATUS_UUID.