12 namespace esp32_improv {
14 using namespace bytebuffer;
16 static const char *
const TAG =
"esp32_improv.component";
17 static const char *
const ESPHOME_MY_LINK =
"https://my.home-assistant.io/redirect/config_flow_start?domain=esphome";
22 #ifdef USE_BINARY_SENSOR 23 if (this->authorizer_ !=
nullptr) {
24 this->authorizer_->add_on_state_callback([
this](
bool state) {
26 this->authorized_start_ =
millis();
27 this->identify_start_ = 0;
33 [
this](uint16_t conn_id) { this->set_error_(improv::ERROR_NONE); });
37 this->status_ = this->service_->create_characteristic(
40 this->status_->add_descriptor(status_descriptor);
42 this->error_ = this->service_->create_characteristic(
45 this->error_->add_descriptor(error_descriptor);
51 this->incoming_data_.insert(this->incoming_data_.end(), data.begin(), data.end());
55 this->rpc_->add_descriptor(rpc_descriptor);
57 this->rpc_response_ = this->service_->create_characteristic(
60 this->rpc_response_->add_descriptor(rpc_response_descriptor);
65 this->capabilities_->add_descriptor(capabilities_descriptor);
66 uint8_t capabilities = 0x00;
68 if (this->status_indicator_ !=
nullptr)
69 capabilities |= improv::CAPABILITY_IDENTIFY;
72 this->setup_complete_ =
true;
79 #ifdef USE_ESP32_IMPROV_STATE_CALLBACK 80 this->state_callback_.call(this->state_, this->error_state_);
83 this->incoming_data_.clear();
86 if (this->service_ ==
nullptr) {
88 ESP_LOGD(TAG,
"Creating Improv service");
90 this->setup_characteristics();
93 if (!this->incoming_data_.empty())
94 this->process_incoming_data_();
97 switch (this->state_) {
99 this->set_status_indicator_state_(
false);
101 if (this->should_start_ && this->setup_complete_) {
102 if (this->service_->is_created()) {
103 this->service_->start();
104 }
else if (this->service_->is_running()) {
107 this->set_state_(improv::STATE_AWAITING_AUTHORIZATION);
108 this->set_error_(improv::ERROR_NONE);
109 ESP_LOGD(TAG,
"Service started!");
113 case improv::STATE_AWAITING_AUTHORIZATION: {
114 #ifdef USE_BINARY_SENSOR 115 if (this->authorizer_ ==
nullptr ||
116 (this->authorized_start_ != 0 && ((now - this->authorized_start_) < this->authorized_duration_))) {
117 this->set_state_(improv::STATE_AUTHORIZED);
120 { this->set_state_(improv::STATE_AUTHORIZED); }
123 if (!this->check_identify_())
124 this->set_status_indicator_state_(
true);
128 case improv::STATE_AUTHORIZED: {
129 #ifdef USE_BINARY_SENSOR 130 if (this->authorizer_ !=
nullptr) {
131 if (now - this->authorized_start_ > this->authorized_duration_) {
132 ESP_LOGD(TAG,
"Authorization timeout");
133 this->set_state_(improv::STATE_AWAITING_AUTHORIZATION);
138 if (!this->check_identify_()) {
139 this->set_status_indicator_state_((now % 1000) < 500);
143 case improv::STATE_PROVISIONING: {
144 this->set_status_indicator_state_((now % 200) < 100);
147 this->connecting_sta_.get_password());
148 this->connecting_sta_ = {};
149 this->cancel_timeout(
"wifi-connect-timeout");
150 this->set_state_(improv::STATE_PROVISIONED);
152 std::vector<std::string> urls = {ESPHOME_MY_LINK};
156 std::string webserver_url =
"http://" + ip.str() +
":" +
to_string(USE_WEBSERVER_PORT);
157 urls.push_back(webserver_url);
162 std::vector<uint8_t> data = improv::build_rpc_response(improv::WIFI_SETTINGS, urls);
163 this->send_response_(data);
168 case improv::STATE_PROVISIONED: {
169 this->incoming_data_.clear();
170 this->set_status_indicator_state_(
false);
178 if (this->status_indicator_ ==
nullptr)
180 if (this->status_indicator_state_ == state)
182 this->status_indicator_state_ =
state;
184 this->status_indicator_->turn_on();
186 this->status_indicator_->turn_off();
194 bool identify = this->identify_start_ != 0 && now - this->identify_start_ <= this->identify_duration_;
197 uint32_t time = now % 1000;
198 this->set_status_indicator_state_(time < 600 && time % 200 < 100);
204 ESP_LOGV(TAG,
"Setting state: %d", state);
205 this->state_ =
state;
206 if (this->status_->get_value().empty() || this->status_->get_value()[0] !=
state) {
209 this->status_->notify();
211 std::vector<uint8_t> service_data(8, 0);
212 service_data[0] = 0x77;
213 service_data[1] = 0x46;
214 service_data[2] =
static_cast<uint8_t
>(
state);
216 uint8_t capabilities = 0x00;
218 if (this->status_indicator_ !=
nullptr)
219 capabilities |= improv::CAPABILITY_IDENTIFY;
222 service_data[3] = capabilities;
223 service_data[4] = 0x00;
224 service_data[5] = 0x00;
225 service_data[6] = 0x00;
226 service_data[7] = 0x00;
229 #ifdef USE_ESP32_IMPROV_STATE_CALLBACK 230 this->state_callback_.call(this->state_, this->error_state_);
235 if (error != improv::ERROR_NONE) {
236 ESP_LOGE(TAG,
"Error: %d", error);
238 if (this->error_->get_value().empty() || this->error_->get_value()[0] != error) {
241 this->error_->notify();
248 this->rpc_response_->notify();
255 ESP_LOGD(TAG,
"Setting Improv to start");
256 this->should_start_ =
true;
260 this->should_start_ =
false;
261 this->set_timeout(
"end-service", 1000, [
this] {
264 this->service_->stop();
272 ESP_LOGCONFIG(TAG,
"ESP32 Improv:");
273 #ifdef USE_BINARY_SENSOR 274 LOG_BINARY_SENSOR(
" ",
"Authorizer", this->authorizer_);
277 ESP_LOGCONFIG(TAG,
" Status Indicator: '%s'", YESNO(this->status_indicator_ !=
nullptr));
282 uint8_t
length = this->incoming_data_[1];
284 ESP_LOGV(TAG,
"Processing bytes - %s",
format_hex_pretty(this->incoming_data_).c_str());
285 if (this->incoming_data_.size() - 3 ==
length) {
286 this->set_error_(improv::ERROR_NONE);
287 improv::ImprovCommand command = improv::parse_improv_data(this->incoming_data_);
288 switch (command.command) {
289 case improv::BAD_CHECKSUM:
290 ESP_LOGW(TAG,
"Error decoding Improv payload");
291 this->set_error_(improv::ERROR_INVALID_RPC);
292 this->incoming_data_.clear();
294 case improv::WIFI_SETTINGS: {
295 if (this->state_ != improv::STATE_AUTHORIZED) {
296 ESP_LOGW(TAG,
"Settings received, but not authorized");
297 this->set_error_(improv::ERROR_NOT_AUTHORIZED);
298 this->incoming_data_.clear();
303 sta.set_password(command.password);
304 this->connecting_sta_ = sta;
308 this->set_state_(improv::STATE_PROVISIONING);
309 ESP_LOGD(TAG,
"Received Improv Wi-Fi settings ssid=%s, password=" LOG_SECRET(
"%s"), command.ssid.c_str(),
310 command.password.c_str());
313 this->set_timeout(
"wifi-connect-timeout", 30000, f);
314 this->incoming_data_.clear();
317 case improv::IDENTIFY:
318 this->incoming_data_.clear();
319 this->identify_start_ =
millis();
322 ESP_LOGW(TAG,
"Unknown Improv payload");
323 this->set_error_(improv::ERROR_UNKNOWN_RPC);
324 this->incoming_data_.clear();
326 }
else if (this->incoming_data_.size() - 2 >
length) {
327 ESP_LOGV(TAG,
"Too much data received or data malformed; resetting buffer...");
328 this->incoming_data_.clear();
330 ESP_LOGV(TAG,
"Waiting for split data packets...");
335 this->set_error_(improv::ERROR_UNABLE_TO_CONNECT);
336 this->set_state_(improv::STATE_AUTHORIZED);
337 #ifdef USE_BINARY_SENSOR 338 if (this->authorizer_ !=
nullptr)
339 this->authorized_start_ =
millis();
341 ESP_LOGW(TAG,
"Timed out while connecting to Wi-Fi network");
static const uint32_t PROPERTY_WRITE
std::string format_hex_pretty(const uint8_t *data, size_t length)
Format the byte array data of length len in pretty-printed, human-readable hex.
void process_incoming_data_()
void save_wifi_sta(const std::string &ssid, const std::string &password)
EventEmitterListenerID on(EvtType event, std::function< void(Args...)> listener)
void on_wifi_connect_timeout_()
const float AFTER_BLUETOOTH
bool is_connected()
Return whether the node is connected to the network (through wifi, eth, ...)
uint32_t IRAM_ATTR HOT millis()
void setup_characteristics()
void start_connecting(const WiFiAP &ap, bool two)
BLEServer * global_ble_server
void set_status_indicator_state_(bool state)
void set_state_(improv::State state)
void dump_config() override
void send_response_(std::vector< uint8_t > &response)
void advertising_set_service_data(const std::vector< uint8_t > &data)
void set_ssid(const std::string &ssid)
WiFiComponent * global_wifi_component
float get_setup_priority() const override
ESP32ImprovComponent * global_improv_component
std::string to_string(int value)
void set_error_(improv::Error error)
void set_sta(const WiFiAP &ap)
BLEService * create_service(ESPBTUUID uuid, bool advertise=false, uint16_t num_handles=15)
Implementation of SPI Controller mode.
static ESPBTUUID from_raw(const uint8_t *data)
static const uint32_t PROPERTY_READ
static ByteBuffer wrap(T value, Endian endianness=LITTLE)
static const uint32_t PROPERTY_NOTIFY