ESPHome 2026.3.0
Loading...
Searching...
No Matches
bedjet_codec.cpp
Go to the documentation of this file.
1#include "bedjet_codec.h"
2#include <algorithm>
3#include <cstdio>
4#include <cstring>
5
6namespace esphome {
7namespace bedjet {
8
10float bedjet_temp_to_f(const uint8_t temp) {
11 // BedJet temp is "C*2"; to get F, multiply by 0.9 (half 1.8) and add 32.
12 return 0.9f * temp + 32.0f;
13}
14
17 // So far no commands require more than 2 bytes of data
18 if (this->packet_.data_length > 2) {
19 ESP_LOGW(TAG, "Packet may be malformed");
20 }
21 for (int i = this->packet_.data_length; i < 2; i++) {
22 this->packet_.data[i] = '\0';
23 }
24 ESP_LOGV(TAG, "Created packet: %02X, %02X %02X", this->packet_.command, this->packet_.data[0], this->packet_.data[1]);
25 return &this->packet_;
26}
27
31 this->packet_.data_length = 1;
32 this->packet_.data[0] = button;
33 return this->clean_packet_();
34}
35
39 this->packet_.data_length = 1;
40 this->packet_.data[0] = temperature * 2;
41 return this->clean_packet_();
42}
43
47 this->packet_.data_length = 1;
48 this->packet_.data[0] = fan_step;
49 return this->clean_packet_();
50}
51
55 this->packet_.data_length = 2;
56 this->packet_.data[0] = hour;
57 this->packet_.data[1] = minute;
58 return this->clean_packet_();
59}
60
64 this->packet_.data_length = 2;
65 this->packet_.data[0] = hour;
66 this->packet_.data[1] = minute;
67 return this->clean_packet_();
68}
69
71void BedjetCodec::decode_extra(const uint8_t *data, uint16_t length) {
72 if (length < 5) {
73 ESP_LOGVV(TAG, "Received extra: %d bytes (too short)", length);
74 return;
75 }
76 ESP_LOGVV(TAG, "Received extra: %d bytes: %d %d %d %d", length, data[1], data[2], data[3], data[4]);
77 uint8_t offset = this->last_buffer_size_;
78 if (offset > 0 && length + offset <= sizeof(BedjetStatusPacket)) {
79 memcpy(((uint8_t *) (&this->buf_)) + offset, data, length);
80 ESP_LOGVV(TAG,
81 "Extra bytes: skip1=0x%08x, skip2=0x%04x, skip3=0x%02x; update phase=0x%02x, "
82 "flags=BedjetFlags <conn=%c, leds=%c, units=%c, mute=%c; packed=%02x>",
83 this->buf_.unused_1, this->buf_.unused_2, this->buf_.unused_3, this->buf_.update_phase,
84 this->buf_.flags.conn_test_passed ? '1' : '0', this->buf_.flags.leds_enabled ? '1' : '0',
85 this->buf_.flags.units_setup ? '1' : '0', this->buf_.flags.beeps_muted ? '1' : '0',
86 this->buf_.flags_packed);
87 } else {
88 ESP_LOGI(TAG, "Could not determine where to append to, last offset=%d, max size=%u, new size would be %d", offset,
89 sizeof(BedjetStatusPacket), length + offset);
90 }
91}
92
97bool BedjetCodec::decode_notify(const uint8_t *data, uint16_t length) {
98 if (length < 5) {
99 ESP_LOGW(TAG, "Received short packet: %d bytes", length);
100 return false;
101 }
102 ESP_LOGV(TAG, "Received: %d bytes: %d %d %d %d", length, data[1], data[2], data[3], data[4]);
103
104 if (data[1] == PACKET_FORMAT_V3_HOME && data[3] == PACKET_TYPE_STATUS) {
105 // Clear old buffer
106 memset(&this->buf_, 0, sizeof(BedjetStatusPacket));
107 // Copy new data into buffer
108 size_t copy_len = std::min(static_cast<size_t>(length), sizeof(BedjetStatusPacket));
109 memcpy(&this->buf_, data, copy_len);
110 this->last_buffer_size_ = copy_len;
111
112 // TODO: validate the packet checksum?
113 if (this->buf_.mode < 7 && this->buf_.target_temp_step >= 38 && this->buf_.target_temp_step <= 86 &&
114 this->buf_.actual_temp_step > 1 && this->buf_.actual_temp_step <= 100 && this->buf_.ambient_temp_step > 1 &&
115 this->buf_.ambient_temp_step <= 100) {
116 // and save it for the update() loop
117 this->status_packet_ = &this->buf_;
118 return this->buf_.is_partial;
119 } else {
120 this->status_packet_ = nullptr;
121 // TODO: log a warning if we detect that we connected to a non-V3 device.
122 ESP_LOGW(TAG, "Received potentially invalid packet (len %d):", length);
123 }
124 } else if (data[1] == PACKET_FORMAT_DEBUG || data[3] == PACKET_TYPE_DEBUG) {
125 // We don't actually know the packet format for this. Dump packets to log, in case a pattern presents itself.
126 if (length >= 13) {
127 ESP_LOGVV(TAG,
128 "received DEBUG packet: set1=%01fF, set2=%01fF, air=%01fF; [7]=%d, [8]=%d, [9]=%d, [10]=%d, [11]=%d, "
129 "[12]=%d, [-1]=%d",
130 bedjet_temp_to_f(data[4]), bedjet_temp_to_f(data[5]), bedjet_temp_to_f(data[6]), data[7], data[8],
131 data[9], data[10], data[11], data[12], data[length - 1]);
132 }
133
134 if (this->has_status() && length >= 7) {
135 this->status_packet_->ambient_temp_step = data[6];
136 }
137 } else {
138 // TODO: log a warning if we detect that we connected to a non-V3 device.
139 }
140
141 return false;
142}
143
145bool BedjetCodec::compare(const uint8_t *data, uint16_t length) {
146 if (data == nullptr) {
147 return false;
148 }
149
150 if (length < 17) {
151 // New packet looks small, skip it.
152 return false;
153 }
154
156 this->buf_.packet_type != PACKET_TYPE_STATUS) { // No last seen packet, so take the new one.
157 return true;
158 }
159
160 if (data[1] != PACKET_FORMAT_V3_HOME || data[3] != PACKET_TYPE_STATUS) { // New packet is not a v3 status, skip it.
161 return false;
162 }
163
164 // Now coerce it to a status packet and compare some key fields
165 const BedjetStatusPacket *test = reinterpret_cast<const BedjetStatusPacket *>(data);
166 // These are fields that will only change due to explicit action.
167 // That is why we do not check ambient or actual temp here, because those are environmental.
168 bool explicit_fields_changed = this->buf_.mode != test->mode || this->buf_.fan_step != test->fan_step ||
170
171 return explicit_fields_changed;
172}
173
175float bedjet_temp_to_c(uint8_t temp) {
176 // BedJet temp is "C*2"; to get C, divide by 2.
177 return temp / 2.0f;
178}
179
180} // namespace bedjet
181} // namespace esphome
uint8_t fan_step
BedJet fan speed; value is in the 0-19 range, representing 5% increments (5%-100%): 5 + 5 /< * fan_st...
BedjetPacket * get_set_target_temp_request(float temperature)
Returns a BedjetPacket that will set the device's target temperature.
bool compare(const uint8_t *data, uint16_t length)
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 * clean_packet_()
Cleans up the packet before sending.
BedjetStatusPacket * status_packet_
bool decode_notify(const uint8_t *data, uint16_t length)
Decodes the incoming status packet received on the BEDJET_STATUS_UUID.
BedjetPacket * get_set_runtime_remaining_request(uint8_t hour, uint8_t minute)
Returns a BedjetPacket that will set the device's remaining runtime.
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's current time.
BedjetPacket * get_set_fan_speed_request(uint8_t fan_step)
Returns a BedjetPacket that will set the device's target fan speed.
uint8_t minute
uint8_t hour
float bedjet_temp_to_c(uint8_t temp)
Converts a BedJet temp step into degrees Celsius.
float bedjet_temp_to_f(const uint8_t temp)
Converts a BedJet temp step into degrees Fahrenheit.
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
The format of a BedJet V3 status packet.
uint8_t target_temp_step
Target temp that the BedJet will try to heat to. See actual_temp_step.
uint8_t fan_step
BedJet fan speed; value is in the 0-19 range, representing 5% increments (5%-100%): 5 + 5 /< * fan_st...
bool is_partial
1 indicates that this is a partial packet, and more data can be read directly from the characteristic...
BedjetMode mode
BedJet operating mode.
BedjetPacketFormat packet_format
BedjetPacketFormat::PACKET_FORMAT_V3_HOME for BedJet V3 status packet format.
uint8_t ambient_temp_step
Current ambient air temp.
uint16_t temperature
Definition sun_gtil2.cpp:12
uint16_t length
Definition tt21100.cpp:0