LCOV - code coverage report
Current view: top level - src - pairing_manager.cpp (source / functions) Coverage Total Hit
Test: ESP-NOW Manager Unified Coverage Lines: 100.0 % 99 99
Test Date: 2026-04-21 02:14:32 Functions: 100.0 % 11 11
Branches: 95.2 % 42 40

             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                 :             : }
        

Generated by: LCOV version 2.0-1