Thanks a lot I overlooked this nice “debugging” functionality:
Web Server Component — ESPHome
I will verify now whether my esp also shows the correct binary sensor state unlike the home assistant state.
Unfortunately I face this only ~ once a week. So a bit difficult to analyse.
My fear is that the ESP device does not send all packets with updates or on the Home Assistant side not all are handled. And for binary sensor not changing that often this is fatal as the value is only sent in case of state changes. So if the first sate change e.g. from true => false is somehow skipped then the value remains on true until it goes back to true and then to false again.
Regards, Christof
My quick analysis of the code, When you call publish_state with a state parameter then only in case the duplicate check (publish_dedup_.next) finds a different value then the send_state internal or filter_list is called. Otherwise in case state parameter is the same then publish_state returns without doing anything.
When now a write of the updated sensor value (see functions at the end) fail for some unknown reason (network e.g.) then the package is not repeated and more important the new state is stored in the Deduplicator instance and any further publish_state with the same value will do nothing (return).
I hope I did not overlook something like a retry.
In my opinion the logic should only update the Deduplicator in case the update package was sent correctly if possible.
// esphome\components\binary_sensor\binary_sensor.cpp:
void BinarySensor::publish_state(bool state) {
if (!this->**publish_dedup_**.next(state)) // check whether state is the same as before and store new state if different
return; // NO CHANGE of state detected
if (this->filter_list_ == nullptr) {
this->**send_state_internal**(state, false);
} else {
this->filter_list_->input(state, false);
}
template<typename T> class Deduplicator {
public:
/// Feeds the next item in the series to the deduplicator and returns whether this is a duplicate.
bool next(T value) {
if (this->has_value_) {
if (this->last_value_ == value)
return false;
}
this->has_value_ = true;
this->last_value_ = value; // take over new value
return true;
}
void BinarySensor::send_state_internal(bool state, bool is_initial) {
if (is_initial) {
ESP_LOGD(TAG, "'%s': Sending initial state %s", this->get_name().c_str(), ONOFF(state));
} else {
ESP_LOGD(TAG, "'%s': Sending state %s", this->get_name().c_str(), ONOFF(state));
}
this->has_state_ = true;
this->state = state; // take over new value
if (!is_initial || this->publish_initial_state_) {
this->state_callback_.call(state); // I assume this now calls APIServer::on_binary_sensor_update
}
}
void Controller::setup_controller(bool include_internal) {
#ifdef USE_BINARY_SENSOR
for (auto *obj : App.get_binary_sensors()) {
if (include_internal || !obj->is_internal())
obj->add_on_state_callback([this, obj](bool state) { this->on_binary_sensor_update(obj, state); });
// esphome\components\api\api_server.cpp
#ifdef USE_BINARY_SENSOR
void APIServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_binary_sensor_state(obj, state);
}
// \esphome\components\api\api_connection.cpp:
#ifdef USE_BINARY_SENSOR
bool APIConnection::send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor, bool state) {
if (!this->state_subscription_)
return false;
BinarySensorStateResponse resp;
resp.key = binary_sensor->get_object_id_hash();
resp.state = state;
resp.missing_state = !binary_sensor->has_state();
return this->send_binary_sensor_state_response(resp);
#ifdef USE_BINARY_SENSOR
bool APIServerConnectionBase::send_binary_sensor_state_response(const BinarySensorStateResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_binary_sensor_state_response: %s", msg.dump().c_str());
#endif
return this->send_message_<BinarySensorStateResponse>(msg, 21);
}
bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) {
if (this->remove_)
return false;
if (!this->helper_->can_write_without_blocking()) {
delay(0);
APIError err = this->helper_->loop();
if (err != APIError::OK) {
on_fatal_error();
ESP_LOGW(TAG, "%s: Socket operation failed: %s errno=%d", this->client_combined_info_.c_str(),
api_error_to_str(err), errno);
return false;
}
if (!this->helper_->can_write_without_blocking()) {
// SubscribeLogsResponse
if (message_type != 29) {
ESP_LOGV(TAG, "Cannot send message because of TCP buffer space");
}
delay(0);
return false;
}
}
APIError err = this->helper_->write_packet(message_type, buffer.get_buffer()->data(), buffer.get_buffer()->size());
if (err == APIError::WOULD_BLOCK)
return false;
if (err != APIError::OK) {
on_fatal_error();
if (err == APIError::SOCKET_WRITE_FAILED && errno == ECONNRESET) {
ESP_LOGW(TAG, "%s: Connection reset", this->client_combined_info_.c_str());
} else {
ESP_LOGW(TAG, "%s: Packet write failed %s errno=%d", this->client_combined_info_.c_str(), api_error_to_str(err),
errno);
}
return false;