GCC Code Coverage Report


Directory: ./
File: components/parking_system/src/gates/ExitGateController.cpp
Date: 2025-12-16 19:41:02
Exec Total Coverage
Lines: 122 166 73.5%
Functions: 12 20 60.0%
Branches: 53 108 49.1%

Line Branch Exec Source
1 #include "ExitGateController.h"
2 #include "esp_log.h"
3
4 static const char* TAG = "ExitGateController";
5 static const char* exitGateStateToString(ExitGateState state);
6
7 6 ExitGateController::ExitGateController(
8 IEventBus& eventBus,
9 IGate& gate,
10 ITicketService& ticketService,
11 uint32_t barrierTimeoutMs,
12 6 uint32_t validationTimeMs)
13 6 : m_eventBus(eventBus)
14 6 , m_gate(&gate)
15 6 , m_ticketService(ticketService)
16 6 , m_state(ExitGateState::Idle)
17 6 , m_barrierTimeoutMs(barrierTimeoutMs)
18 6 , m_validationTimeMs(validationTimeMs)
19 6 , m_currentTicketId(0)
20 6 , m_barrierTimer(nullptr)
21 6 , m_validationTimer(nullptr) {
22 // Subscribe to events
23
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 m_eventBus.subscribe(EventType::ExitLightBarrierBlocked,
24 8 [this](const Event& e) { onLightBarrierBlocked(e); });
25
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 m_eventBus.subscribe(EventType::ExitLightBarrierCleared,
26 7 [this](const Event& e) { onLightBarrierCleared(e); });
27
28 // Create timers
29 12 m_barrierTimer = xTimerCreate(
30 "ExitBarrierTimer",
31 6 pdMS_TO_TICKS(m_barrierTimeoutMs),
32 pdFALSE,
33 this,
34 barrierTimerCallback);
35
36 12 m_validationTimer = xTimerCreate(
37 "ExitValidationTimer",
38 6 pdMS_TO_TICKS(m_validationTimeMs),
39 pdFALSE,
40 this,
41 validationTimerCallback);
42
43 6 ESP_LOGI(TAG, "ExitGateController initialized");
44 6 }
45
46 6 ExitGateController::~ExitGateController() {
47
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 if (m_barrierTimer) {
48 6 xTimerDelete(m_barrierTimer, 0);
49 }
50
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 if (m_validationTimer) {
51 6 xTimerDelete(m_validationTimer, 0);
52 }
53 6 }
54
55 ✗ void ExitGateController::setupGpioInterrupts() {
56 ✗ ESP_LOGI(TAG, "Exit gate GPIO interrupts configured");
57 ✗ }
58
59 ✗ void ExitGateController::reset() {
60 // Stop timers if running
61 ✗ if (m_barrierTimer && xTimerIsTimerActive(m_barrierTimer)) {
62 ✗ xTimerStop(m_barrierTimer, 0);
63 }
64 ✗ if (m_validationTimer && xTimerIsTimerActive(m_validationTimer)) {
65 ✗ xTimerStop(m_validationTimer, 0);
66 }
67
68 // Reset state
69 ✗ m_state = ExitGateState::Idle;
70 ✗ m_currentTicketId = 0;
71
72 // Ensure barrier is closed
73 ✗ m_gate->close();
74
75 ✗ ESP_LOGI(TAG, "ExitGateController reset to Idle");
76 ✗ }
77
78 16 const char* ExitGateController::getStateString() const {
79
7/8
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 2 times.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
16 switch (m_state) {
80 4 case ExitGateState::Idle:
81 4 return "Idle";
82 4 case ExitGateState::ValidatingTicket:
83 4 return "ValidatingTicket";
84 2 case ExitGateState::OpeningBarrier:
85 2 return "OpeningBarrier";
86 1 case ExitGateState::WaitingForCarToPass:
87 1 return "WaitingForCarToPass";
88 1 case ExitGateState::CarPassing:
89 1 return "CarPassing";
90 2 case ExitGateState::WaitingBeforeClose:
91 2 return "WaitingBeforeClose";
92 2 case ExitGateState::ClosingBarrier:
93 2 return "ClosingBarrier";
94 ✗ default:
95 ✗ return "Unknown";
96 }
97 }
98
99 13 void ExitGateController::setState(ExitGateState newState) {
100
1/2
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
13 if (m_state != newState) {
101 13 ESP_LOGI(TAG, "State: %s -> %s", getStateString(), exitGateStateToString(newState));
102 13 m_state = newState;
103 }
104 13 }
105
106 2 void ExitGateController::onLightBarrierBlocked(const Event& event) {
107 (void) event;
108
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 if (m_state == ExitGateState::WaitingForCarToPass) {
109 1 ESP_LOGI(TAG, "Car entering exit barrier");
110 1 setState(ExitGateState::CarPassing);
111 }
112 // Note: Idle state does not react to light barrier
113 // Exit sequence must be started manually via ticket_validate command
114 2 }
115
116 1 void ExitGateController::onLightBarrierCleared(const Event& event) {
117 (void) event;
118
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (m_state == ExitGateState::CarPassing) {
119 1 ESP_LOGI(TAG, "Car exited parking, waiting %u ms before closing barrier", (unsigned) m_barrierTimeoutMs);
120
1/2
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
1 m_eventBus.publish(Event(EventType::CarExitedParking, 0, m_currentTicketId));
121
122 // Wait before closing barrier (uses configured timeout)
123 1 setState(ExitGateState::WaitingBeforeClose);
124
125
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (m_barrierTimer) {
126 1 xTimerChangePeriod(m_barrierTimer, pdMS_TO_TICKS(m_barrierTimeoutMs), 0);
127 1 xTimerReset(m_barrierTimer, 0);
128 }
129 }
130 1 }
131
132 ✗ void ExitGateController::onValidationTimeout() {
133 // This function is not used in manual validation mode
134 // Validation must be triggered manually via validateTicketManually()
135 ✗ ESP_LOGW(TAG, "Validation timeout - should not happen in manual mode");
136 ✗ }
137
138 4 bool ExitGateController::validateTicketManually(uint32_t ticketId) {
139
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (m_state != ExitGateState::Idle) {
140 ✗ ESP_LOGW(TAG, "Cannot validate manually - must be in Idle state (current: %s)", getStateString());
141 ✗ return false;
142 }
143
144
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 ESP_LOGI(TAG, "Starting manual ticket validation for ID=%lu", (unsigned long) ticketId);
145
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 setState(ExitGateState::ValidatingTicket);
146 4 m_currentTicketId = ticketId;
147
148 // Check if ticket is paid
149 4 Ticket ticket;
150
3/4
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
✓ Branch 4 taken 1 times.
4 if (m_ticketService.getTicketInfo(ticketId, ticket)) {
151
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 2 times.
3 if (!ticket.isPaid) {
152
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 ESP_LOGW(TAG, "Ticket not paid: ID=%lu - use 'ticket_pay %lu' command first!",
153 (unsigned long) ticketId, (unsigned long) ticketId);
154
1/2
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
1 m_eventBus.publish(Event(EventType::TicketRejected));
155
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 setState(ExitGateState::Idle);
156 1 return false;
157 }
158
159
2/4
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
2 if (m_ticketService.validateAndUseTicket(ticketId)) {
160
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 ESP_LOGI(TAG, "Ticket validation successful: ID=%lu", (unsigned long) ticketId);
161
1/2
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
2 m_eventBus.publish(Event(EventType::TicketValidated, 0, ticketId));
162
163
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 setState(ExitGateState::OpeningBarrier);
164
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 m_gate->open();
165
1/2
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
2 m_eventBus.publish(Event(EventType::ExitBarrierOpened));
166 2 startBarrierTimer();
167 2 return true;
168 }
169 }
170
171
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 ESP_LOGW(TAG, "Ticket validation failed: ID=%lu", (unsigned long) ticketId);
172
1/2
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
1 m_eventBus.publish(Event(EventType::TicketRejected));
173
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 setState(ExitGateState::Idle);
174 1 return false;
175 }
176
177 3 void ExitGateController::onBarrierTimeout() {
178 3 ESP_LOGD(TAG, "Barrier timeout in state: %s", getStateString());
179
180
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 2 times.
3 if (m_state == ExitGateState::OpeningBarrier) {
181 1 setState(ExitGateState::WaitingForCarToPass);
182
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 } else if (m_state == ExitGateState::WaitingBeforeClose) {
183 // Wait period finished, now close barrier
184 1 ESP_LOGI(TAG, "Wait period finished, closing barrier");
185 1 setState(ExitGateState::ClosingBarrier);
186 1 m_gate->close();
187
1/2
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
1 m_eventBus.publish(Event(EventType::ExitBarrierClosed));
188
189 // Start timer with normal barrier timeout
190
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (m_barrierTimer) {
191 1 xTimerChangePeriod(m_barrierTimer, pdMS_TO_TICKS(m_barrierTimeoutMs), 0);
192 1 xTimerReset(m_barrierTimer, 0);
193 }
194
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 } else if (m_state == ExitGateState::ClosingBarrier) {
195 1 setState(ExitGateState::Idle);
196 1 m_currentTicketId = 0;
197 }
198 3 }
199
200 2 void ExitGateController::startBarrierTimer() {
201
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (m_barrierTimer) {
202 2 xTimerReset(m_barrierTimer, 0);
203 }
204 2 }
205
206 ✗ void ExitGateController::stopBarrierTimer() {
207 ✗ if (m_barrierTimer) {
208 ✗ xTimerStop(m_barrierTimer, 0);
209 }
210 ✗ }
211
212 ✗ void ExitGateController::startValidationTimer() {
213 ✗ if (m_validationTimer) {
214 ✗ xTimerReset(m_validationTimer, 0);
215 }
216 ✗ }
217
218 ✗ void ExitGateController::stopValidationTimer() {
219 ✗ if (m_validationTimer) {
220 ✗ xTimerStop(m_validationTimer, 0);
221 }
222 ✗ }
223
224 ✗ void ExitGateController::barrierTimerCallback(TimerHandle_t xTimer) {
225 ✗ auto* controller = static_cast<ExitGateController*>(pvTimerGetTimerID(xTimer));
226 ✗ if (controller) {
227 ✗ controller->onBarrierTimeout();
228 }
229 ✗ }
230
231 ✗ void ExitGateController::validationTimerCallback(TimerHandle_t xTimer) {
232 ✗ auto* controller = static_cast<ExitGateController*>(pvTimerGetTimerID(xTimer));
233 ✗ if (controller) {
234 ✗ controller->onValidationTimeout();
235 }
236 ✗ }
237
238 // Helper function to convert state to string (avoids stack-heavy lambda)
239 13 static const char* exitGateStateToString(ExitGateState state) {
240
7/8
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 1 times.
✗ Branch 7 not taken.
13 switch (state) {
241 3 case ExitGateState::Idle:
242 3 return "Idle";
243 4 case ExitGateState::ValidatingTicket:
244 4 return "ValidatingTicket";
245 2 case ExitGateState::OpeningBarrier:
246 2 return "OpeningBarrier";
247 1 case ExitGateState::WaitingForCarToPass:
248 1 return "WaitingForCarToPass";
249 1 case ExitGateState::CarPassing:
250 1 return "CarPassing";
251 1 case ExitGateState::WaitingBeforeClose:
252 1 return "WaitingBeforeClose";
253 1 case ExitGateState::ClosingBarrier:
254 1 return "ClosingBarrier";
255 ✗ default:
256 ✗ return "Unknown";
257 }
258 }
259