ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
basic_session.cpp
Go to the documentation of this file.
1#include "basic_session.h"
2#ifdef USE_PACKET_INTERFACE_BASIC_SESSION
5#include "esphome/core/log.h"
6
8
9ESPHOME_LOG_TAG(TAG, "packet_interface.session");
10
11static constexpr uint8_t PACKET_TYPE_DATA = 0x00;
12static constexpr uint8_t PACKET_TYPE_KEEP_ALIVE = 0x01;
13// Type byte and the 32-bit boot id, little endian
14static constexpr size_t KEEP_ALIVE_SIZE = 5;
15
17 : PacketInterface(remaining_packet_size(lower->get_max_packet_size(), HEADER_SIZE)), lower_(lower) {
18 this->stack_on_(lower, CAPABILITY_SESSION);
19 // Data lost by a reliability layer below must be sent again, as after a restart of the peer
21 lower->add_on_session_state_callback([this](bool connected) {
22 if (!connected && this->connected_)
23 this->set_connected_(false);
24 });
25 }
26}
27
29 if (this->lower_->get_max_packet_size() < KEEP_ALIVE_SIZE) {
30 this->mark_failed(LOG_STR("max_packet_size too small for the configured layers"));
31 return;
32 }
33 this->boot_id_ = random_uint32();
34#ifdef USE_BINARY_SENSOR
35 if (this->connected_sensor_ != nullptr)
37#endif
39 [this](const PacketBuffer &data, const PacketMetaData &meta_data) { this->receive_(data, meta_data); });
40 // A reliability layer below queues this until its link is up, so the peer hears of a restart straight away
41 this->send_keep_alive_();
42 this->set_interval(this->keep_alive_ms_, [this] { this->on_interval_(); });
43}
44
46 ESP_LOGCONFIG(TAG,
47 "Basic Session on '%s':\n"
48 " Max Packet Size: %zu\n"
49 " Keep Alive: %" PRIu32 "ms\n"
50 " Timeout: %" PRIu32 "ms",
51 this->name_, this->max_packet_size_, this->keep_alive_ms_, this->timeout_ms_);
52#ifdef USE_BINARY_SENSOR
53 LOG_BINARY_SENSOR(" ", "Connected", this->connected_sensor_);
54#endif
55}
56
57bool BasicSession::send_to_interface(const PacketBuffer &data, const PacketMetaData &meta_data) {
58 if (this->is_failed())
59 return false;
60 uint8_t header = PACKET_TYPE_DATA;
61 PacketBuffer packet(&header, HEADER_SIZE, &data, nullptr, 0);
62 return this->lower_->send_to_interface(packet, meta_data);
63}
64
66 uint8_t packet[KEEP_ALIVE_SIZE] = {
67 PACKET_TYPE_KEEP_ALIVE, static_cast<uint8_t>(this->boot_id_), static_cast<uint8_t>(this->boot_id_ >> 8),
68 static_cast<uint8_t>(this->boot_id_ >> 16), static_cast<uint8_t>(this->boot_id_ >> 24)};
69 if (!this->lower_->send_to_interface(PacketBuffer(packet, sizeof(packet)), {}))
70 ESP_LOGV(TAG, "%s: Lower layer did not accept keep-alive", this->name_);
71}
72
74 this->send_keep_alive_();
75 if (this->connected_ && App.get_loop_component_start_time() - this->last_receive_ms_ > this->timeout_ms_) {
76 ESP_LOGW(TAG, "%s: Peer not heard for %" PRIu32 "ms", this->name_, this->timeout_ms_);
77 this->set_connected_(false);
78 }
79}
80
81void BasicSession::set_connected_(bool connected) {
82 this->connected_ = connected;
83 ESP_LOGD(TAG, "%s: Session %s", this->name_, connected ? LOG_STR_LITERAL("connected") : LOG_STR_LITERAL("lost"));
84#ifdef USE_BINARY_SENSOR
85 if (this->connected_sensor_ != nullptr)
86 this->connected_sensor_->publish_state(connected);
87#endif
88 this->session_callback_.call(connected);
89}
90
91void BasicSession::receive_(const PacketBuffer &data, const PacketMetaData &meta_data) {
92 // Lower layers deliver packets in one piece; the payload is passed up without copying
93 if (!data.is_contiguous()) {
94 ESP_LOGW(TAG, "%s: Packet split across buffers is not supported", this->name_);
95 return;
96 }
97 auto packet = data.as_span();
98 if (packet.empty()) {
99 this->note_received_(false);
100 return;
101 }
102 uint8_t type = packet[0];
103 if (type == PACKET_TYPE_KEEP_ALIVE) {
104 if (packet.size() != KEEP_ALIVE_SIZE) {
105 ESP_LOGV(TAG, "%s: Keep-alive of %zu bytes dropped", this->name_, packet.size());
106 this->note_received_(false);
107 return;
108 }
109 uint32_t boot_id = encode_uint32(packet[4], packet[3], packet[2], packet[1]);
110 if (!this->peer_known_ || boot_id != this->peer_boot_id_) {
111 // Data may arrive before the first keep-alive, so only a changed id means a restart
112 if (this->connected_ && this->peer_known_) {
113 ESP_LOGD(TAG, "%s: Peer has restarted", this->name_);
114 this->set_connected_(false);
115 }
116 this->peer_boot_id_ = boot_id;
117 this->peer_known_ = true;
118 // Answer a new peer at once; it answers only if our id is new to it, so this cannot repeat
119 this->send_keep_alive_();
120 }
121 } else if (type != PACKET_TYPE_DATA) {
122 ESP_LOGV(TAG, "%s: Unknown packet type 0x%02X dropped", this->name_, type);
123 this->note_received_(false);
124 return;
125 }
126 this->note_received_(true);
128 if (!this->connected_)
129 this->set_connected_(true);
130 if (type == PACKET_TYPE_DATA)
131 this->on_receive_from_interface_(PacketBuffer(packet.data() + HEADER_SIZE, packet.size() - HEADER_SIZE), meta_data);
132}
133
134} // namespace esphome::packet_interface
135#endif
uint32_t IRAM_ATTR HOT get_loop_component_start_time() const
Get the cached time in milliseconds from when the current component started its loop execution.
void mark_failed()
Mark this component as failed.
bool is_failed() const
Definition component.h:274
void set_interval(const char *name, uint32_t interval, std::function< void()> &&f)
Set an interval function with a const char* name.
Definition component.cpp:88
void publish_state(bool new_state)
Publish a new state to the front-end.
void publish_initial_state(bool new_state)
Publish the initial state, this will not make the callback manager send callbacks and is meant only f...
binary_sensor::BinarySensor * connected_sensor_
bool send_to_interface(const PacketBuffer &data, const PacketMetaData &meta_data) override
static constexpr size_t HEADER_SIZE
The packet type byte.
void receive_(const PacketBuffer &data, const PacketMetaData &meta_data)
A buffer class that efficiently represents a composite buffer with three sections:
A class that will transport data to and from an abstract channel.
bool has_capability(Capability capability) const
void add_on_session_state_callback(F &&callback)
With CAPABILITY_SESSION, the callback is called with true when the peer can be reached,...
size_t get_max_packet_size() const
The largest packet this layer accepts; fixed when the layer is constructed.
void note_received_(bool understood)
Called by a layer for each received packet it understood, or could not make sense of.
void stack_on_(PacketInterface *lower, Capability capability)
Called by a layer's constructor to record itself as the layer above lower, adding its own capability.
virtual bool send_to_interface(const PacketBuffer &data, const PacketMetaData &meta_data)=0
Method to be implemented by child classes to send data to the interface.
void add_packet_interface_listener(F &&callback)
Add a listener that will be called when data is received.
LazyCallbackManager< void(bool)> session_callback_
void on_receive_from_interface_(const PacketBuffer &data, const PacketMetaData &meta_data)
This function should be called by implementing classes with received data to be passed to any listene...
A struct to hold metadata about a packet.
uint8_t type
constexpr size_t remaining_packet_size(size_t size, size_t used)
The room left for a packet of size bytes after used bytes, or 0 if it is too small.
ESPHOME_LOG_TAG(TAG, "packet_interface.session")
uint32_t random_uint32()
Return a random 32-bit unsigned integer.
Definition helpers.cpp:12
constexpr uint32_t encode_uint32(uint8_t byte1, uint8_t byte2, uint8_t byte3, uint8_t byte4)
Encode a 32-bit value given four bytes in most to least significant byte order.
Definition helpers.h:1006
Application App
Global storage of Application pointer - only one Application can exist.
static void uint32_t