text: #pragma once
  
  #include "esphome/components/ble_client/ble_client.h"
  #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
  #include "esphome/components/climate/climate.h"
  #include "esphome/core/component.h"
  #include "esphome/core/defines.h"
  #include "esphome/core/hal.h"
  #include "bedjet_base.h"
  
  #ifdef USE_TIME
  #include "esphome/components/time/real_time_clock.h"
  #endif
  
  #ifdef USE_ESP32
  
  #include <esp_gattc_api.h>
  
  namespace esphome {
  namespace bedjet {
  
  namespace espbt = esphome::esp32_ble_tracker;
  
  static const espbt::ESPBTUUID BEDJET_SERVICE_UUID = espbt::ESPBTUUID::from_raw("00001000-bed0-0080-aa55-4265644a6574");
  static const espbt::ESPBTUUID BEDJET_STATUS_UUID = espbt::ESPBTUUID::from_raw("00002000-bed0-0080-aa55-4265644a6574");
  static const espbt::ESPBTUUID BEDJET_COMMAND_UUID = espbt::ESPBTUUID::from_raw("00002004-bed0-0080-aa55-4265644a6574");
  static const espbt::ESPBTUUID BEDJET_NAME_UUID = espbt::ESPBTUUID::from_raw("00002001-bed0-0080-aa55-4265644a6574");
  
  class Bedjet : public climate::Climate, public esphome::ble_client::BLEClientNode, public PollingComponent {
   public:
    void setup() override;
    void loop() override;
    void update() override;
    void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
                             esp_ble_gattc_cb_param_t *param) override;
    void dump_config() override;
    float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
  
  #ifdef USE_TIME
    void set_time_id(time::RealTimeClock *time_id) { this->time_id_ = time_id; }
    void send_local_time();
  #endif
    void set_clock(uint8_t hour, uint8_t minute);
    void set_status_timeout(uint32_t timeout) { this->timeout_ = timeout; }
    /** Sets the default strategy to use for climate::CLIMATE_MODE_HEAT. */
    void set_heating_mode(BedjetHeatMode mode) { this->heating_mode_ = mode; }
  
    /** Attempts to check for and apply firmware updates. */
    void upgrade_firmware();
  
    climate::ClimateTraits traits() override {
      auto traits = climate::ClimateTraits();
      traits.set_supports_action(true);
      traits.set_supports_current_temperature(true);
      traits.set_supported_modes({
          climate::CLIMATE_MODE_OFF,
          climate::CLIMATE_MODE_HEAT,
          // climate::CLIMATE_MODE_TURBO // Not supported by Climate: see presets instead
          climate::CLIMATE_MODE_FAN_ONLY,
          climate::CLIMATE_MODE_DRY,
      });
  
      // It would be better if we had a slider for the fan modes.
      traits.set_supported_custom_fan_modes(BEDJET_FAN_STEP_NAMES_SET);
      traits.set_supported_presets({
          // If we support NONE, then have to decide what happens if the user switches to it (turn off?)
          // climate::CLIMATE_PRESET_NONE,
          // Climate doesn't have a "TURBO" mode, but we can use the BOOST preset instead.
          climate::CLIMATE_PRESET_BOOST,
      });
      traits.set_supported_custom_presets({
          // We could fetch biodata from bedjet and set these names that way.
          // But then we have to invert the lookup in order to send the right preset.
          // For now, we can leave them as M1-3 to match the remote buttons.
          "M1",
          "M2",
          "M3",
      });
      if (this->heating_mode_ == HEAT_MODE_EXTENDED) {
        traits.add_supported_custom_preset("LTD HT");
      } else {
        traits.add_supported_custom_preset("EXT HT");
      }
      traits.set_visual_min_temperature(19.0);
      traits.set_visual_max_temperature(43.0);
      traits.set_visual_temperature_step(1.0);
      return traits;
    }
  
   protected:
    void control(const climate::ClimateCall &call) override;
  
  #ifdef USE_TIME
    void setup_time_();
    optional<time::RealTimeClock *> time_id_{};
  #endif
  
    uint32_t timeout_{DEFAULT_STATUS_TIMEOUT};
    BedjetHeatMode heating_mode_ = HEAT_MODE_HEAT;
  
    static const uint32_t MIN_NOTIFY_THROTTLE = 5000;
    static const uint32_t NOTIFY_WARN_THRESHOLD = 300000;
    static const uint32_t DEFAULT_STATUS_TIMEOUT = 900000;
  
    uint8_t set_notify_(bool enable);
    uint8_t write_bedjet_packet_(BedjetPacket *pkt);
    void reset_state_();
    bool update_status_();
  
    bool is_valid_() {
      // FIXME: find a better way to check this?
      return !std::isnan(this->current_temperature) && !std::isnan(this->target_temperature) &&
             this->current_temperature > 1 && this->target_temperature > 1;
    }
  
    uint32_t last_notify_ = 0;
    bool force_refresh_ = false;
  
    std::unique_ptr<BedjetCodec> codec_;
    uint16_t char_handle_cmd_;
    uint16_t char_handle_name_;
    uint16_t char_handle_status_;
    uint16_t config_descr_status_;
  
    uint8_t write_notify_config_descriptor_(bool enable);
  };
  
  }  // namespace bedjet
  }  // namespace esphome
  
  #endif
  