GCC Code Coverage Report


Directory: ./
File: components/parking_system/src/gates/EntryGateController.cpp
Date: 2025-12-16 19:41:02
Exec Total Coverage
Lines: 121 154 78.6%
Functions: 13 18 72.2%
Branches: 41 76 53.9%

Line Branch Exec Source
1 #include "EntryGateController.h"
2 #include "esp_log.h"
3
4 static const char* TAG = "EntryGateController";
5 static const char* entryGateStateToString(EntryGateState state);
6
7 6 EntryGateController::EntryGateController(
8 IEventBus& eventBus,
9 IGpioInput& button,
10 IGate& gate,
11 ITicketService& ticketService,
12 6 uint32_t barrierTimeoutMs)
13 6 : m_eventBus(eventBus)
14 6 , m_button(&button)
15 6 , m_gate(&gate)
16 6 , m_ticketService(ticketService)
17 6 , m_state(EntryGateState::Idle)
18 6 , m_barrierTimeoutMs(barrierTimeoutMs)
19 6 , m_currentTicketId(0)
20 6 , m_barrierTimer(nullptr) {
21 // Subscribe to events
22
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 m_eventBus.subscribe(EventType::EntryButtonPressed,
23 12 [this](const Event& e) { onButtonPressed(e); });
24
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 m_eventBus.subscribe(EventType::EntryLightBarrierBlocked,
25 7 [this](const Event& e) { onLightBarrierBlocked(e); });
26
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 m_eventBus.subscribe(EventType::EntryLightBarrierCleared,
27 7 [this](const Event& e) { onLightBarrierCleared(e); });
28
29 // Create barrier timer
30 12 m_barrierTimer = xTimerCreate(
31 "EntryBarrierTimer",
32 6 pdMS_TO_TICKS(m_barrierTimeoutMs),
33 pdFALSE,
34 this,
35 barrierTimerCallback);
36
37 6 ESP_LOGI(TAG, "EntryGateController initialized");
38 6 }
39
40 6 EntryGateController::~EntryGateController() {
41
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 if (m_barrierTimer) {
42 6 xTimerDelete(m_barrierTimer, 0);
43 }
44 6 }
45
46 ✗ void EntryGateController::setupGpioInterrupts() {
47 // Setup entry button interrupt
48 ✗ m_button->setInterruptHandler([this](bool level) {
49 // Button pressed when level goes LOW (pull-up resistor)
50 ✗ EventType eventType = level ? EventType::EntryButtonReleased : EventType::EntryButtonPressed;
51 ✗ Event event(eventType);
52 ✗ m_eventBus.publish(event);
53 ✗ });
54 ✗ m_button->enableInterrupt();
55
56 ✗ ESP_LOGI(TAG, "Entry gate GPIO interrupts configured");
57 ✗ }
58
59 26 const char* EntryGateController::getStateString() const {
60
8/9
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 6 times.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 2 times.
✓ Branch 7 taken 2 times.
✗ Branch 8 not taken.
26 switch (m_state) {
61 5 case EntryGateState::Idle:
62 5 return "Idle";
63 5 case EntryGateState::CheckingCapacity:
64 5 return "CheckingCapacity";
65 4 case EntryGateState::IssuingTicket:
66 4 return "IssuingTicket";
67 6 case EntryGateState::OpeningBarrier:
68 6 return "OpeningBarrier";
69 1 case EntryGateState::WaitingForCar:
70 1 return "WaitingForCar";
71 1 case EntryGateState::CarPassing:
72 1 return "CarPassing";
73 2 case EntryGateState::WaitingBeforeClose:
74 2 return "WaitingBeforeClose";
75 2 case EntryGateState::ClosingBarrier:
76 2 return "ClosingBarrier";
77 ✗ default:
78 ✗ return "Unknown";
79 }
80 }
81
82 ✗ void EntryGateController::reset() {
83 // Stop timer if running
84 ✗ if (m_barrierTimer && xTimerIsTimerActive(m_barrierTimer)) {
85 ✗ xTimerStop(m_barrierTimer, 0);
86 }
87
88 // Reset state
89 ✗ m_state = EntryGateState::Idle;
90 ✗ m_currentTicketId = 0;
91
92 // Ensure barrier is closed
93 ✗ m_gate->close();
94
95 ✗ ESP_LOGI(TAG, "EntryGateController reset to Idle");
96 ✗ }
97
98 21 void EntryGateController::setState(EntryGateState newState) {
99
1/2
✓ Branch 0 taken 21 times.
✗ Branch 1 not taken.
21 if (m_state != newState) {
100 21 ESP_LOGI(TAG, "State: %s -> %s", getStateString(), entryGateStateToString(newState));
101 21 m_state = newState;
102 }
103 21 }
104
105 6 void EntryGateController::onButtonPressed(const Event& event) {
106 (void) event;
107
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 5 times.
6 if (m_state != EntryGateState::Idle) {
108 1 ESP_LOGW(TAG, "Button pressed in non-Idle state, ignoring");
109 1 return;
110 }
111
112 5 ESP_LOGI(TAG, "Entry button pressed");
113 5 setState(EntryGateState::CheckingCapacity);
114
115 // Check capacity
116 5 uint32_t activeCount = m_ticketService.getActiveTicketCount();
117 5 uint32_t capacity = m_ticketService.getCapacity();
118
119
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 4 times.
5 if (activeCount >= capacity) {
120 1 ESP_LOGW(TAG, "Parking full! (%lu/%lu)", (unsigned long) activeCount, (unsigned long) capacity);
121
1/2
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
1 m_eventBus.publish(Event(EventType::CapacityFull));
122 1 setState(EntryGateState::Idle);
123 1 return;
124 }
125
126 // Capacity available, issue ticket
127 4 setState(EntryGateState::IssuingTicket);
128 4 m_currentTicketId = m_ticketService.getNewTicket();
129
130
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (m_currentTicketId == 0) {
131 ✗ ESP_LOGE(TAG, "Failed to issue ticket");
132 ✗ setState(EntryGateState::Idle);
133 ✗ return;
134 }
135
136 4 ESP_LOGI(TAG, "Ticket issued: ID=%lu", (unsigned long) m_currentTicketId);
137
1/2
✓ Branch 3 taken 4 times.
✗ Branch 4 not taken.
4 m_eventBus.publish(Event(EventType::TicketIssued, 0, m_currentTicketId));
138
139 // Open barrier
140 4 setState(EntryGateState::OpeningBarrier);
141 4 m_gate->open();
142
1/2
✓ Branch 3 taken 4 times.
✗ Branch 4 not taken.
4 m_eventBus.publish(Event(EventType::EntryBarrierOpened));
143 4 startBarrierTimer();
144 }
145
146 1 void EntryGateController::onLightBarrierBlocked(const Event& event) {
147 (void) event;
148
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (m_state == EntryGateState::WaitingForCar) {
149 1 ESP_LOGI(TAG, "Car entering");
150 1 setState(EntryGateState::CarPassing);
151 }
152 1 }
153
154 1 void EntryGateController::onLightBarrierCleared(const Event& event) {
155 (void) event;
156
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (m_state == EntryGateState::CarPassing) {
157 1 ESP_LOGI(TAG, "Car passed through, waiting %u ms before closing barrier", (unsigned) m_barrierTimeoutMs);
158
1/2
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
1 m_eventBus.publish(Event(EventType::CarEnteredParking, 0, m_currentTicketId));
159
160 // Wait before closing barrier (uses configured timeout)
161 1 setState(EntryGateState::WaitingBeforeClose);
162
163
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (m_barrierTimer) {
164 1 xTimerChangePeriod(m_barrierTimer, pdMS_TO_TICKS(m_barrierTimeoutMs), 0);
165 1 xTimerReset(m_barrierTimer, 0);
166 }
167 }
168 1 }
169
170 5 void EntryGateController::onBarrierTimeout() {
171 5 ESP_LOGD(TAG, "Barrier timeout in state: %s", getStateString());
172
173
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 2 times.
5 if (m_state == EntryGateState::OpeningBarrier) {
174 // Barrier finished opening
175 3 setState(EntryGateState::WaitingForCar);
176
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 } else if (m_state == EntryGateState::WaitingBeforeClose) {
177 // Wait period finished, now close barrier
178 1 ESP_LOGI(TAG, "Wait period finished, closing barrier");
179 1 setState(EntryGateState::ClosingBarrier);
180 1 m_gate->close();
181
1/2
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
1 m_eventBus.publish(Event(EventType::EntryBarrierClosed));
182
183 // Start timer with normal barrier timeout
184
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (m_barrierTimer) {
185 1 xTimerChangePeriod(m_barrierTimer, pdMS_TO_TICKS(m_barrierTimeoutMs), 0);
186 1 xTimerReset(m_barrierTimer, 0);
187 }
188
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 } else if (m_state == EntryGateState::ClosingBarrier) {
189 // Barrier finished closing
190 1 setState(EntryGateState::Idle);
191 1 m_currentTicketId = 0;
192 }
193 5 }
194
195 4 void EntryGateController::startBarrierTimer() {
196
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 if (m_barrierTimer) {
197 4 xTimerReset(m_barrierTimer, 0);
198 }
199 4 }
200
201 ✗ void EntryGateController::stopBarrierTimer() {
202 ✗ if (m_barrierTimer) {
203 ✗ xTimerStop(m_barrierTimer, 0);
204 }
205 ✗ }
206
207 ✗ void EntryGateController::barrierTimerCallback(TimerHandle_t xTimer) {
208 ✗ auto* controller = static_cast<EntryGateController*>(pvTimerGetTimerID(xTimer));
209 ✗ if (controller) {
210 ✗ controller->onBarrierTimeout();
211 }
212 ✗ }
213
214 // Helper function to convert state to string (avoids stack-heavy lambda)
215 21 static const char* entryGateStateToString(EntryGateState state) {
216
8/9
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 4 times.
✓ Branch 4 taken 3 times.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 1 times.
✓ Branch 7 taken 1 times.
✗ Branch 8 not taken.
21 switch (state) {
217 2 case EntryGateState::Idle:
218 2 return "Idle";
219 5 case EntryGateState::CheckingCapacity:
220 5 return "CheckingCapacity";
221 4 case EntryGateState::IssuingTicket:
222 4 return "IssuingTicket";
223 4 case EntryGateState::OpeningBarrier:
224 4 return "OpeningBarrier";
225 3 case EntryGateState::WaitingForCar:
226 3 return "WaitingForCar";
227 1 case EntryGateState::CarPassing:
228 1 return "CarPassing";
229 1 case EntryGateState::WaitingBeforeClose:
230 1 return "WaitingBeforeClose";
231 1 case EntryGateState::ClosingBarrier:
232 1 return "ClosingBarrier";
233 ✗ default:
234 ✗ return "Unknown";
235 }
236 }
237