Branch data Line data Source code
1 : : #include <cstring>
2 : :
3 : : #include "esp_log.h"
4 : :
5 : : #include "i_peer_manager.hpp"
6 : : #include "i_tx_manager.hpp"
7 : :
8 : : #include "pairing_manager.hpp"
9 : : #include "protocol_types.hpp"
10 : :
11 : : static const char* TAG = "PairingMgr";
12 : :
13 : 41 : PairingManager::PairingManager(
14 : : ITxManager& tx_mgr,
15 : : IPeerManager& peer_mgr,
16 : : IFreeRTOSHAL& hal_freertos,
17 : 41 : ITimerHAL& hal_timer)
18 : 41 : : tx_mgr_(tx_mgr)
19 : 41 : , peer_mgr_(peer_mgr)
20 : 41 : , hal_freertos_(hal_freertos)
21 : 41 : , hal_timer_(hal_timer)
22 : : {
23 : 41 : }
24 : :
25 : 40 : esp_err_t PairingManager::init(NodeId id, NodeType type, TaskHandle_t rx_task_handle, uint32_t heartbeat_interval_ms)
26 : : {
27 [ + + ]: 40 : if (rx_task_handle == nullptr) {
28 : : return ESP_ERR_INVALID_ARG;
29 : : }
30 [ + + ]: 39 : if (is_initialized_) {
31 : : return ESP_ERR_INVALID_STATE;
32 : : }
33 : :
34 : 38 : my_id_ = id;
35 : 38 : my_type_ = type;
36 : 38 : rx_task_handle_ = rx_task_handle;
37 : 38 : heartbeat_interval_ms_ = heartbeat_interval_ms;
38 : 38 : is_initialized_ = true;
39 : :
40 : 38 : return ESP_OK;
41 : : }
42 : :
43 : 4 : void PairingManager::deinit()
44 : : {
45 : 4 : is_initialized_ = false;
46 : 4 : is_active_ = false;
47 : 4 : rx_task_handle_ = nullptr;
48 : 4 : }
49 : :
50 : 9 : void PairingManager::tick(int64_t now_ms)
51 : : {
52 [ + + + + ]: 9 : if (!is_initialized_ || !is_active_) {
53 : : return;
54 : : }
55 : :
56 [ + + ]: 7 : if (now_ms - started_at_ms_ >= timeout_ms_) {
57 : 1 : is_active_ = false;
58 : 1 : ESP_LOGI(TAG, "Pairing timed out.");
59 : 1 : notify_rx_task_pairing_done();
60 : 1 : return;
61 : : }
62 [ + + + + ]: 6 : if (my_type_ != ReservedTypes::HUB && now_ms - last_request_ms_ >= periodic_interval_ms_) {
63 : 3 : send_pair_request();
64 : 3 : last_request_ms_ = now_ms;
65 : : }
66 : : }
67 : :
68 : 27 : esp_err_t PairingManager::start(uint32_t timeout_ms, int64_t now_ms)
69 : : {
70 [ + + + + ]: 27 : if (!is_initialized_ || is_active_) {
71 : : return ESP_ERR_INVALID_STATE;
72 : : }
73 : :
74 : 25 : timeout_ms_ = timeout_ms;
75 : 25 : started_at_ms_ = now_ms;
76 : 25 : last_request_ms_ = now_ms;
77 : 25 : is_active_ = true;
78 : :
79 [ + + ]: 25 : if (my_type_ != ReservedTypes::HUB) {
80 : 19 : send_pair_request(); // Send initial pair request immediately
81 : : }
82 : :
83 : : return ESP_OK;
84 : : }
85 : :
86 : 6 : void PairingManager::handle_request(const DecodedRxPacket& decoded)
87 : : {
88 : : // Only initialized and active pairing session processes requests
89 [ + + + + ]: 6 : if (!is_initialized_ || !is_active_) {
90 : 3 : return;
91 : : }
92 : : // Only HUB handle pair requests; Nodes only expect pair responses from the HUB
93 [ + + ]: 4 : if (my_type_ != ReservedTypes::HUB) {
94 : : return;
95 : : }
96 : :
97 : 3 : const MessageHeader& header = decoded.header;
98 : : // PairRequest is packed — safe to reinterpret raw data directly
99 : 3 : const PairRequest* req = reinterpret_cast<const PairRequest*>(decoded.raw.data);
100 : :
101 : 3 : ESP_LOGI(TAG, "Pair request from Node ID %d", (int)header.sender_node_id);
102 : :
103 : 3 : DecodedTxPacket tx_packet{};
104 : 3 : memcpy(tx_packet.dest_mac, BROADCAST_MAC, 6);
105 : :
106 : 3 : uint64_t now_ms = get_time_ms();
107 : :
108 : 3 : tx_packet.header.msg_type = MessageType::PAIR_RESPONSE;
109 : 3 : tx_packet.header.sender_node_id = my_id_;
110 : 3 : tx_packet.header.sender_type = my_type_;
111 : 3 : tx_packet.header.dest_node_id = header.sender_node_id;
112 : 3 : tx_packet.header.timestamp_ms = now_ms;
113 : :
114 : 3 : PairStatus status;
115 [ + + ]: 3 : if (header.sender_type == ReservedTypes::HUB) {
116 : : status = PairStatus::REJECTED_NOT_ALLOWED;
117 : : }
118 : : else {
119 : 2 : peer_mgr_.add(header.sender_node_id, decoded.raw.src_mac, header.sender_type, req->heartbeat_interval_ms);
120 : 2 : status = PairStatus::ACCEPTED;
121 : 2 : notify_rx_task_peer_add();
122 : : }
123 : :
124 : 3 : PairResponse resp{};
125 : 3 : resp.status = status;
126 : : // assigned_id, heartbeat_interval_ms, report_interval_ms remain zero for now
127 : :
128 : : // Copy only the payload portion of PairResponse, skipping MessageHeader.
129 : : // &resp.status points to the first field after the header.
130 : 3 : tx_packet.payload_len = sizeof(PairResponse) - sizeof(MessageHeader);
131 : 3 : memcpy(tx_packet.payload, &resp.status, tx_packet.payload_len);
132 : :
133 : 3 : tx_mgr_.queue_packet(tx_packet);
134 : : }
135 : :
136 : 10 : void PairingManager::handle_response(const DecodedRxPacket& decoded)
137 : : {
138 : : // Only initialized and active pairing session processes requests
139 [ + + + + ]: 10 : if (!is_initialized_ || !is_active_) {
140 : : return;
141 : : }
142 : : // Only non-HUB nodes expect pair responses from the HUB
143 [ + + ]: 7 : if (my_type_ == ReservedTypes::HUB) {
144 : : return;
145 : : }
146 : :
147 : : // Broadcast responses must be explicitly addressed to this node.
148 : : // Without this check, two nodes pairing simultaneously would both
149 : : // accept the first ACCEPTED response on air.
150 [ + + ]: 6 : if (decoded.header.dest_node_id != my_id_) {
151 : : return;
152 : : }
153 : :
154 : 5 : const PairResponse* resp = reinterpret_cast<const PairResponse*>(decoded.raw.data);
155 [ + + ]: 5 : if (resp->status == PairStatus::ACCEPTED) {
156 : 4 : ESP_LOGI(TAG, "Pairing accepted by Hub");
157 : 4 : peer_mgr_.add(decoded.header.sender_node_id, decoded.raw.src_mac, decoded.header.sender_type);
158 : 4 : notify_rx_task_peer_add();
159 : 4 : is_active_ = false;
160 : 4 : notify_rx_task_pairing_done();
161 : : }
162 : : }
163 : :
164 : 22 : void PairingManager::send_pair_request()
165 : : {
166 : 22 : DecodedTxPacket tx_packet{};
167 : 22 : memcpy(tx_packet.dest_mac, BROADCAST_MAC, 6);
168 : :
169 : 22 : tx_packet.header.msg_type = MessageType::PAIR_REQUEST;
170 : 22 : tx_packet.header.sender_node_id = my_id_;
171 : 22 : tx_packet.header.sender_type = my_type_;
172 : 22 : tx_packet.header.dest_node_id = ReservedIds::HUB;
173 : 22 : tx_packet.header.timestamp_ms = get_time_ms();
174 : :
175 : 22 : PairRequest req{};
176 : 22 : req.heartbeat_interval_ms = heartbeat_interval_ms_;
177 : : // other fiels remain zero for now
178 : :
179 : : // Copy only the payload portion of PairRequest, skipping MessageHeader.
180 : : // &req.heartbeat_interval_ms points to the first field after the header.
181 : 22 : tx_packet.payload_len = sizeof(PairRequest) - sizeof(MessageHeader);
182 : 22 : memcpy(tx_packet.payload, &req.heartbeat_interval_ms, tx_packet.payload_len);
183 : :
184 : 22 : tx_mgr_.queue_packet(tx_packet);
185 : 22 : }
186 : :
187 : 5 : void PairingManager::notify_rx_task_pairing_done()
188 : : {
189 [ + - ]: 5 : if (rx_task_handle_ != nullptr) {
190 : 5 : hal_freertos_.task_notify(rx_task_handle_, NOTIFY_PAIRING_DONE, eSetBits);
191 : : }
192 : 5 : }
193 : :
194 : 6 : void PairingManager::notify_rx_task_peer_add()
195 : : {
196 [ + - ]: 6 : if (rx_task_handle_ != nullptr) {
197 : 6 : hal_freertos_.task_notify(rx_task_handle_, NOTIFY_PEER_ADDED, eSetBits);
198 : : }
199 : 6 : }
200 : :
201 : 25 : int64_t PairingManager::get_time_ms() const
202 : : {
203 : 25 : return hal_timer_.get_time_us() / 1000;
204 : : }
|