12static const LogString *bedjet_button_to_string(
BedjetButton button) {
15 return LOG_STR(
"OFF");
17 return LOG_STR(
"COOL");
19 return LOG_STR(
"HEAT");
21 return LOG_STR(
"EXT HT");
23 return LOG_STR(
"TURBO");
25 return LOG_STR(
"DRY");
33 return LOG_STR(
"unknown");
44 ESP_LOGW(TAG,
"[%s] MAGIC_UPDATE button failed, status=%d", this->
get_name().c_str(),
status);
59 if (fan_speed_index > 19) {
60 ESP_LOGW(TAG,
"Invalid fan speed index %d, expecting 0-19.", fan_speed_index);
64 auto *pkt = this->
codec_->get_set_fan_speed_request(fan_speed_index);
68 ESP_LOGW(TAG,
"[%s] writing fan speed failed, status=%d", this->
get_name().c_str(),
status);
82 auto *pkt = this->
codec_->get_set_target_temp_request(temp_c);
86 ESP_LOGW(TAG,
"[%s] writing target temp failed, status=%d", this->
get_name().c_str(),
status);
93 auto *pkt = this->
codec_->get_set_runtime_remaining_request(hours, mins);
97 ESP_LOGW(TAG,
"[%s] writing remaining runtime failed, status=%d", this->
get_name().c_str(),
status);
103 auto *pkt = this->
codec_->get_button_request(button);
107 ESP_LOGW(TAG,
"[%s] writing button %s failed, status=%d", this->
get_name().c_str(),
108 LOG_STR_ARG(bedjet_button_to_string(button)),
status);
110 ESP_LOGD(TAG,
"[%s] writing button %s success", this->
get_name().c_str(),
111 LOG_STR_ARG(bedjet_button_to_string(button)));
119 return status->time_remaining_secs +
status->time_remaining_mins * 60 +
status->time_remaining_hrs * 3600;
129 ESP_LOGI(TAG,
"[%s] Cannot write packet: Not connected, enabled=false", this->
get_name().c_str());
131 ESP_LOGW(TAG,
"[%s] Cannot write packet: Not connected", this->
get_name().c_str());
137 ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
146 this->char_handle_status_);
148 ESP_LOGW(TAG,
"[%s] esp_ble_gattc_register_for_notify failed, status=%d", this->
get_name().c_str(),
status);
152 this->char_handle_status_);
154 ESP_LOGW(TAG,
"[%s] esp_ble_gattc_unregister_for_notify failed, status=%d", this->
get_name().c_str(),
status);
157 ESP_LOGV(TAG,
"[%s] set_notify: enable=%d; result=%d", this->
get_name().c_str(), enable,
status);
167 if (chr ==
nullptr) {
168 ESP_LOGW(TAG,
"[%s] No control service found at device, not a BedJet..?", this->
get_name().c_str());
177 if (chr ==
nullptr) {
178 ESP_LOGW(TAG,
"[%s] No status service found at device, not a BedJet..?", this->
get_name().c_str());
190 if (descr ==
nullptr) {
191 ESP_LOGW(TAG,
"No config descriptor for status handle 0x%x. Will not be able to receive status notifications",
194 }
else if (descr->uuid.get_uuid().len != ESP_UUID_LEN_16 ||
195 descr->uuid.get_uuid().uuid.uuid16 != ESP_GATT_UUID_CHAR_CLIENT_CONFIG) {
196 ESP_LOGW(TAG,
"Config descriptor 0x%x (uuid %s) is not a client config char uuid", this->
char_handle_status_,
197 descr->uuid.to_string().c_str());
206 if (chr ==
nullptr) {
207 ESP_LOGW(TAG,
"[%s] No name service found at device, not a BedJet..?", this->
get_name().c_str());
212 this->char_handle_name_, ESP_GATT_AUTH_REQ_NONE);
214 ESP_LOGI(TAG,
"[%s] Unable to read name characteristic: %d", this->
get_name().c_str(),
status);
219 ESP_LOGI(TAG,
"[%s] Discovered service characteristics: ", this->
get_name().c_str());
229 esp_ble_gattc_cb_param_t *param) {
231 case ESP_GATTC_DISCONNECT_EVT: {
232 ESP_LOGV(TAG,
"Disconnected: reason=%d", param->disconnect.reason);
237 case ESP_GATTC_SEARCH_CMPL_EVT: {
241 ESP_LOGD(TAG,
"[%s] Services complete: obtained char handles.", this->
get_name().c_str());
242 this->
node_state = espbt::ClientState::ESTABLISHED;
253 ESP_LOGW(TAG,
"[%s] Failed discovering service characteristics.", this->
get_name().c_str());
260 case ESP_GATTC_WRITE_DESCR_EVT: {
261 if (param->write.status != ESP_GATT_OK) {
262 if (param->write.status == ESP_GATT_INVALID_ATTR_LEN) {
265 ESP_LOGW(TAG,
"[%s] Invalid attr length writing descr at handle 0x%04d, status=%d", this->
get_name().c_str(),
266 param->write.handle, param->write.status);
268 ESP_LOGW(TAG,
"[%s] Error writing descr at handle 0x%04d, status=%d", this->
get_name().c_str(),
269 param->write.handle, param->write.status);
273 ESP_LOGD(TAG,
"[%s] Write to handle 0x%04x status=%d", this->
get_name().c_str(), param->write.handle,
274 param->write.status);
277 case ESP_GATTC_WRITE_CHAR_EVT: {
278 if (param->write.status != ESP_GATT_OK) {
279 ESP_LOGW(TAG,
"Error writing char at handle 0x%04d, status=%d", param->write.handle, param->write.status);
282 if (param->write.handle == this->char_handle_cmd_) {
291 case ESP_GATTC_READ_CHAR_EVT: {
292 if (param->read.conn_id != this->parent_->get_conn_id())
294 if (param->read.status != ESP_GATT_OK) {
295 ESP_LOGW(TAG,
"Error reading char at handle %d, status=%d", param->read.handle, param->read.status);
299 if (param->read.handle == this->char_handle_status_) {
301 this->
codec_->decode_extra(param->read.value, param->read.value_len);
303 }
else if (param->read.handle == this->char_handle_name_) {
305 if (param->read.status == ESP_GATT_OK && param->read.value_len > 0) {
306 std::string bedjet_name(
reinterpret_cast<char const *
>(param->read.value), param->read.value_len);
307 ESP_LOGV(TAG,
"[%s] Got BedJet name: '%s'", this->
get_name().c_str(), bedjet_name.c_str());
313 case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
321 if (param->reg_for_notify.handle != this->char_handle_status_) {
322 ESP_LOGW(TAG,
"[%s] Register for notify on unexpected handle 0x%04x, expecting 0x%04x",
323 this->
get_name().c_str(), param->reg_for_notify.handle, this->char_handle_status_);
332 case ESP_GATTC_UNREG_FOR_NOTIFY_EVT: {
334 if (param->unreg_for_notify.handle != this->char_handle_status_) {
335 ESP_LOGW(TAG,
"[%s] Unregister for notify on unexpected handle 0x%04x, expecting 0x%04x",
336 this->
get_name().c_str(), param->unreg_for_notify.handle, this->char_handle_status_);
345 case ESP_GATTC_NOTIFY_EVT: {
349 if (param->notify.conn_id != this->parent_->get_conn_id()) {
350 ESP_LOGW(TAG,
"[%s] Received notify event for unexpected parent conn: expect %x, got %x",
355 if (param->notify.handle != this->char_handle_status_) {
356 ESP_LOGW(TAG,
"[%s] Unexpected notify handle, wanted %04X, got %04X", this->
get_name().c_str(),
369 if (!this->
force_refresh_ && this->
codec_->compare(param->notify.value, param->notify.value_len)) {
372 ESP_LOGV(TAG,
"[%s] Incoming packet indicates a significant change.", this->
get_name().c_str());
378 ESP_LOGVV(TAG,
"[%s] Decoding packet: last=%" PRId32
", delta=%" PRId32
", force=%s", this->
get_name().c_str(),
380 bool needs_extra = this->
codec_->decode_notify(param->notify.value, param->notify.value_len);
386 this->char_handle_status_, ESP_GATT_AUTH_REQ_NONE);
388 ESP_LOGI(TAG,
"[%s] Unable to read extended status packet", this->
get_name().c_str());
397 ESP_LOGVV(TAG,
"[%s] gattc unhandled event: enum=%d", this->
get_name().c_str(), event);
428 uint16_t notify_en = enable ? 1 : 0;
430 sizeof(notify_en), (uint8_t *) ¬ify_en, ESP_GATT_WRITE_TYPE_RSP,
431 ESP_GATT_AUTH_REQ_NONE);
433 ESP_LOGW(TAG,
"esp_ble_gattc_write_char_descr error, status=%d",
status);
436 ESP_LOGD(TAG,
"[%s] wrote notify=%s to status config 0x%04x, for conn %d", this->
get_name().c_str(),
449 ESP_LOGD(TAG,
"Using time component to set BedJet clock: %d:%02d", now.
hour, now.
minute);
452 ESP_LOGI(TAG,
"`time_id` is not configured: will not sync BedJet clock.");
461 ESP_LOGI(TAG,
"`time_id` is not configured: will not sync BedJet clock.");
468 ESP_LOGV(TAG,
"[%s] Not connected, cannot send time.", this->
get_name().c_str());
475 ESP_LOGW(TAG,
"Failed setting BedJet clock: %d",
status);
477 ESP_LOGD(TAG,
"[%s] BedJet clock set to: %d:%02d", this->
get_name().c_str(),
hour,
minute);
487 ESP_LOGCONFIG(TAG,
"BedJet Hub '%s'", this->
get_name().c_str());
488 ESP_LOGCONFIG(TAG,
" ble_client.app_id: %d", this->
parent()->app_id);
489 ESP_LOGCONFIG(TAG,
" ble_client.conn_id: %d", this->
parent()->get_conn_id());
490 LOG_UPDATE_INTERVAL(
this)
491 ESP_LOGCONFIG(TAG,
" Child components (%d):", this->
children_.size());
493 ESP_LOGCONFIG(TAG,
" - %s", child->describe().c_str());
507 ESP_LOGD(TAG,
"[%s] Not connected, will not send status.", this->
get_name().c_str());
508 }
else if (
status !=
nullptr) {
509 ESP_LOGD(TAG,
"[%s] Notifying %d children of latest status @%p.", this->
get_name().c_str(), this->
children_.size(),
518 if (this->last_notify_ == 0) {
524 ESP_LOGI(TAG,
"[%s] Still waiting for first GATT notify event.", this->
get_name().c_str());
526 ESP_LOGW(TAG,
"[%s] Last GATT notify was %" PRId32
" seconds ago.", this->
get_name().c_str(), diff / 1000);
530 ESP_LOGW(TAG,
"[%s] Timed out after %" PRId32
" sec. Retrying...", this->
get_name().c_str(), this->
timeout_);
void status_set_warning(const char *message="unspecified")
void set_parent(T *parent)
Set the parent of this object.
void status_packet_ready_()
bool set_target_temp(float temp_c)
Set the target temperature to temp_c in °C.
void set_name_(const std::string &name)
void dump_config() override
bool button_cool()
Press the COOL button.
void setup_time_()
Initializes time sync callbacks to support syncing current time to the BedJet.
bool button_dry()
Press the DRY button.
uint8_t write_bedjet_packet_(BedjetPacket *pkt)
Send the BedjetPacket to the device.
bool button_ext_heat()
Press the EXT HT button.
uint8_t get_fan_index()
Return the fan speed index, in the range 0-19.
std::unique_ptr< BedjetCodec > codec_
void set_clock(uint8_t hour, uint8_t minute)
Attempt to set the BedJet device's clock to the specified time.
bool discover_characteristics_()
static const uint32_t MIN_NOTIFY_THROTTLE
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) override
time::RealTimeClock * time_id_
bool set_fan_index(uint8_t fan_speed_index)
Set the fan speed to a stepped index in the range 0-19.
uint16_t get_time_remaining()
Return the remaining runtime, in seconds.
bool set_time_remaining(uint8_t hours, uint8_t mins)
Set the operational runtime remaining.
void upgrade_firmware()
Attempts to check for and apply firmware updates.
uint16_t char_handle_name_
void dispatch_state_(bool is_ready)
bool button_memory1()
Press the M1 (memory recall) button.
uint8_t set_notify_(bool enable)
Configures the local ESP BLE client to register (true) or unregister (false) for status notifications...
std::vector< BedJetClient * > children_
bool button_turbo()
Press the TURBO button.
bool button_heat()
Press the HEAT button.
void register_child(BedJetClient *obj)
Register a BedJetClient child component.
bool send_button(BedjetButton button)
Send the button.
uint8_t write_notify_config_descriptor_(bool enable)
Reimplementation of BLEClient.gattc_event_handler() for ESP_GATTC_REG_FOR_NOTIFY_EVT.
uint16_t char_handle_status_
bool button_memory3()
Press the M3 (memory recall) button.
uint16_t char_handle_cmd_
bool button_off()
Press the OFF button.
static const uint32_t NOTIFY_WARN_THRESHOLD
uint16_t config_descr_status_
void send_local_time()
Attempts to sync the local time (via time_id) to the BedJet device.
bool button_memory2()
Press the M2 (memory recall) button.
void set_state(espbt::ClientState state) override
void set_enabled(bool enabled)
espbt::ClientState node_state
BLECharacteristic * get_characteristic(espbt::ESPBTUUID service, espbt::ESPBTUUID chr)
BLEDescriptor * get_config_descriptor(uint16_t handle)
uint16_t get_conn_id() const
void add_on_time_sync_callback(std::function< void()> callback)
ESPTime now()
Get the time in the currently defined timezone.
@ BTN_TURBO
Enter Turbo mode (high heat, limited to 10 minutes)
@ BTN_M1
Start the M1 biorhythm/preset program.
@ BTN_DRY
Enter Dry mode (high speed, no heat)
@ BTN_EXTHT
Enter Extended Heat mode (limited to 10 hours)
@ BTN_HEAT
Enter Heat mode (limited to 4 hours)
@ BTN_COOL
Enter Cool mode (fan only)
@ BTN_OFF
Turn BedJet off.
@ BTN_M2
Start the M2 biorhythm/preset program.
@ BTN_M3
Start the M3 biorhythm/preset program.
@ MAGIC_UPDATE
Request a firmware update. This will also restart the Bedjet.
Providing packet encoding functions for exchanging data with a remote host.
uint32_t IRAM_ATTR HOT millis()
A more user-friendly version of struct tm from time.h.
uint8_t minute
minutes after the hour [0-59]
uint8_t hour
hours since midnight [0-23]
bool is_valid() const
Check if this ESPTime is valid (all fields in range and year is greater than 2018)