3-phase PV router
Loading...
Searching...
No Matches
test_main.cpp
Go to the documentation of this file.
1#include <unity.h>
2#include <cstdio>
3#include <vector>
4#include <string>
5
6// Mock Arduino types for native compilation
7typedef unsigned char byte;
8
9// Include EWMA implementation (copied from main project)
10constexpr uint8_t round_up_to_power_of_2(uint16_t v)
11{
12 if (__builtin_popcount(v) == 1) { return __builtin_ctz(v) - 1; }
13
14 uint8_t next_pow_of_2{ 0 };
15
16 while (v)
17 {
18 v >>= 1;
19 ++next_pow_of_2;
20 }
21
22 return --next_pow_of_2;
23}
24
25template< uint8_t A = 10 >
26class EWMA_average
27{
28private:
29 int32_t ema_raw{ 0 };
30 int32_t ema{ 0 };
31 int32_t ema_ema_raw{ 0 };
32 int32_t ema_ema{ 0 };
33 int32_t ema_ema_ema_raw{ 0 };
34 int32_t ema_ema_ema{ 0 };
35
36public:
48
49 auto getAverageS() const
50 {
51 return ema;
52 }
53 auto getAverageD() const
54 {
55 return (ema << 1) - ema_ema;
56 }
57 auto getAverageT() const
58 {
59 return ema + (ema - ema_ema) + (ema - ema_ema_ema);
60 }
61
62 void reset()
63 {
65 }
66};
67
68// Convert RELAY_FILTER_DELAY_MINUTES to alpha parameter
69// Assuming 5-second sampling rate: alpha = delay_minutes * 60 / 5 = delay_minutes * 12
70constexpr uint8_t delay_minutes_to_alpha(uint8_t delay_minutes)
71{
72 uint8_t alpha = delay_minutes * 12;
73 return (alpha > 0) ? alpha : 8; // Minimum alpha of 8
74}
75
76// Mock relay class for testing with configurable parameters
78{
79public:
84
85 TestRelay(int32_t import_threshold = 20, int32_t surplus_threshold = 20, uint8_t filter_delay = 2)
86 : m_relay_state(false), m_import_threshold(import_threshold),
87 m_surplus_threshold(surplus_threshold), m_filter_delay_minutes(filter_delay) {}
88
89 bool proceed_relay(int32_t power_value)
90 {
91 bool new_state = m_relay_state;
92
93 if (m_import_threshold < 0)
94 {
95 // Battery scenario: negative threshold means we want surplus above abs(threshold)
96 int32_t required_surplus = -m_import_threshold;
97 if (!m_relay_state && power_value >= required_surplus)
98 {
99 new_state = true; // Turn on when surplus exceeds threshold
100 }
101 else if (m_relay_state && power_value < (required_surplus - m_surplus_threshold))
102 {
103 new_state = false; // Turn off when surplus drops below (threshold - hysteresis)
104 }
105 }
106 else
107 {
108 // Normal scenario: positive threshold means import/export boundary
109 if (!m_relay_state && power_value >= m_import_threshold)
110 {
111 new_state = true; // Turn on when import exceeds threshold
112 }
113 else if (m_relay_state && power_value < (m_import_threshold - m_surplus_threshold))
114 {
115 new_state = false; // Turn off when import drops below threshold minus hysteresis
116 }
117 }
118
119 m_relay_state = new_state;
120 return m_relay_state;
121 }
122
124 {
125 m_relay_state = false;
126 }
127};
128
129// Cloud pattern definitions (realistic power measurements in Watts)
131{
132 std::string name;
133 std::string description;
134 std::vector< int32_t > power_data;
136};
137
138// Create realistic cloud patterns with 5-second sampling over 15-20 minutes
139// Each pattern starts with 5 minutes of stable production, then introduces clouds
140std::vector< CloudPattern > create_cloud_patterns()
141{
142 return {
143 { "Scattered Clouds - 20 minutes",
144 "5min stable, then light scattered clouds with brief shadows (spring/fall conditions)",
145 // 20 minutes = 240 samples at 5-second intervals
146 // 0-5 minutes: Stable production around 1200W
147 { 1200, 1210, 1195, 1205, 1190, 1200, 1215, 1205, 1195, 1200, // 0-50s
148 1210, 1200, 1205, 1195, 1210, 1200, 1190, 1205, 1210, 1195, // 50-100s
149 1200, 1205, 1210, 1195, 1200, 1215, 1200, 1205, 1190, 1200, // 100-150s
150 1210, 1195, 1200, 1205, 1210, 1200, 1195, 1205, 1200, 1210, // 150-200s
151 1195, 1200, 1205, 1210, 1200, 1195, 1205, 1200, 1210, 1195, // 200-250s
152 1200, 1205, 1210, 1195, 1200, 1205, 1210, 1200, 1195, 1205, // 250-300s (5min)
153
154 // 5-10 minutes: First cloud shadows
155 1000, 1150, 950, 1180, 800, 1200, 1050, 1220, 900, 1180, // 300-350s
156 1100, 1190, 850, 1200, 1000, 1210, 950, 1180, 1100, 1200, // 350-400s
157 1150, 1210, 1000, 1190, 1200, 1180, 950, 1200, 1100, 1190, // 400-450s
158 1050, 1200, 1150, 1180, 1000, 1210, 1200, 1190, 1100, 1200, // 450-500s
159 1150, 1180, 950, 1200, 1100, 1190, 1050, 1210, 1200, 1180, // 500-550s
160 1000, 1200, 1150, 1190, 1100, 1200, 1050, 1180, 1200, 1190, // 550-600s (10min)
161
162 // 10-15 minutes: More frequent cloud shadows
163 900, 1180, 800, 1200, 950, 1190, 850, 1210, 1000, 1180, // 600-650s
164 1100, 1200, 900, 1190, 800, 1180, 950, 1200, 1050, 1190, // 650-700s
165 850, 1180, 1000, 1200, 900, 1190, 1100, 1180, 950, 1200, // 700-750s
166 1000, 1190, 850, 1180, 1100, 1200, 900, 1190, 1050, 1180, // 750-800s
167 950, 1200, 1000, 1190, 900, 1180, 1100, 1200, 950, 1190, // 800-850s
168 1050, 1180, 900, 1200, 1100, 1190, 950, 1180, 1000, 1200, // 850-900s (15min)
169
170 // 15-20 minutes: Clouds clearing, return to stable
171 1100, 1190, 1150, 1200, 1180, 1210, 1200, 1190, 1180, 1200, // 900-950s
172 1190, 1200, 1180, 1210, 1200, 1190, 1180, 1200, 1190, 1210, // 950-1000s
173 1200, 1190, 1180, 1200, 1190, 1210, 1200, 1180, 1190, 1200, // 1000-1050s
174 1190, 1200, 1180, 1210, 1200, 1190, 1180, 1200, 1190, 1200, // 1050-1100s
175 1180, 1200, 1190, 1210, 1200, 1180, 1190, 1200, 1180, 1200, // 1100-1150s
176 1190, 1200, 1180, 1200, 1190, 1180, 1200, 1190, 1200, 1180 }, // 1150-1200s (20min)
177 500 },
178 { "Heavy Cloud Bank - 18 minutes",
179 "5min stable, then dense cloud formation passes over with dramatic drop",
180 // 18 minutes = 216 samples
181 // 0-5 minutes: Stable high production
182 {
183 1300, 1310, 1295, 1305, 1290, 1300, 1315, 1305, 1295, 1300, // 0-50s
184 1310, 1300, 1305, 1295, 1310, 1300, 1290, 1305, 1310, 1295, // 50-100s
185 1300, 1305, 1310, 1295, 1300, 1315, 1300, 1305, 1290, 1300, // 100-150s
186 1310, 1295, 1300, 1305, 1310, 1300, 1295, 1305, 1300, 1310, // 150-200s
187 1295, 1300, 1305, 1310, 1300, 1295, 1305, 1300, 1310, 1295, // 200-250s
188 1300, 1305, 1310, 1295, 1300, 1305, 1310, 1300, 1295, 1305, // 250-300s (5min)
189
190 // 5-8 minutes: Cloud bank approaches - gradual decline
191 1250, 1200, 1150, 1100, 1000, 900, 800, 700, 600, 500, // 300-350s
192 400, 350, 300, 250, 200, 180, 150, 120, 100, 80, // 350-400s
193 60, 50, 40, 30, 25, 20, 15, 12, 10, 8, // 400-450s
194 6, 5, 4, 3, 2, 1, 1, 0, 0, 0, // 450-500s (cloud peak)
195
196 // 8-11 minutes: Under dense cloud - very low production
197 0, 1, 2, 3, 5, 8, 10, 15, 20, 25, // 500-550s
198 30, 40, 50, 60, 80, 100, 120, 150, 180, 200, // 550-600s
199 250, 300, 350, 400, 500, 600, 700, 800, 900, 1000, // 600-650s
200
201 // 11-15 minutes: Cloud clearing - gradual recovery
202 1100, 1150, 1200, 1220, 1240, 1250, 1260, 1270, 1275, 1280, // 650-700s
203 1285, 1290, 1292, 1294, 1296, 1298, 1299, 1300, 1300, 1301, // 700-750s
204 1302, 1303, 1304, 1305, 1305, 1305, 1304, 1303, 1302, 1301, // 750-800s
205 1300, 1301, 1302, 1303, 1304, 1305, 1305, 1304, 1303, 1302, // 800-850s
206 1301, 1300, 1299, 1300, 1301, 1302, 1303, 1304, 1305, 1305, // 850-900s (15min)
207
208 // 15-18 minutes: Back to stable high production
209 1305, 1304, 1303, 1302, 1301, 1300, 1301, 1302, 1303, 1304, // 900-950s
210 1305, 1304, 1303, 1302, 1301, 1300, 1299, 1300, 1301, 1302, // 950-1000s
211 1303, 1304, 1305, 1304, 1303, 1302, 1301, 1300, 1301, 1302, // 1000-1050s
212 1303, 1304, 1305, 1304, 1303, 1302, 1301, 1300, 1299, 1300, // 1050-1080s (18min)
213 },
214 500 },
215 { "Intermittent Clouds - 16 minutes",
216 "5min stable, then frequent cloud/sun alternation (challenging conditions)",
217 // 16 minutes = 192 samples
218 // 0-5 minutes: Stable production
219 {
220 1100, 1110, 1095, 1105, 1090, 1100, 1115, 1105, 1095, 1100, // 0-50s
221 1110, 1100, 1105, 1095, 1110, 1100, 1090, 1105, 1110, 1095, // 50-100s
222 1100, 1105, 1110, 1095, 1100, 1115, 1100, 1105, 1090, 1100, // 100-150s
223 1110, 1095, 1100, 1105, 1110, 1100, 1095, 1105, 1100, 1110, // 150-200s
224 1095, 1100, 1105, 1110, 1100, 1095, 1105, 1100, 1110, 1095, // 200-250s
225 1100, 1105, 1110, 1095, 1100, 1105, 1110, 1100, 1095, 1105, // 250-300s (5min)
226
227 // 5-11 minutes: Rapid cloud/sun alternation
228 600, 1100, 500, 1200, 400, 1150, 300, 1100, 700, 1200, // 300-350s
229 800, 1150, 450, 1100, 350, 1200, 600, 1150, 500, 1100, // 350-400s
230 400, 1200, 650, 1150, 550, 1100, 300, 1200, 750, 1150, // 400-450s
231 450, 1100, 350, 1200, 600, 1150, 500, 1100, 400, 1200, // 450-500s
232 700, 1150, 600, 1100, 350, 1200, 800, 1150, 450, 1100, // 500-550s
233 300, 1200, 650, 1150, 550, 1100, 400, 1200, 750, 1150, // 550-600s (10min)
234 500, 1100, 350, 1200, 600, 1150, 450, 1100, 300, 1200, // 600-650s
235 700, 1150, 550, 1100, 400, 1200, 650, 1150, 500, 1100, // 650-660s (11min)
236
237 // 11-16 minutes: Clouds becoming less frequent, stabilizing
238 800, 1150, 900, 1100, 950, 1200, 1000, 1150, 1050, 1100, // 660-710s
239 1100, 1150, 1120, 1100, 1080, 1150, 1100, 1120, 1110, 1100, // 710-760s
240 1090, 1150, 1100, 1120, 1110, 1100, 1105, 1150, 1110, 1100, // 760-810s
241 1095, 1120, 1105, 1100, 1110, 1105, 1100, 1110, 1105, 1100, // 810-860s
242 1105, 1110, 1100, 1105, 1110, 1100, 1105, 1110, 1100, 1105, // 860-910s
243 1110, 1100, 1105, 1110, 1100, 1105, 1110, 1100, 1105, 1110, // 910-960s (16min)
244 },
245 500 },
246 { "Storm Approach - 17 minutes",
247 "5min stable, then gradual deterioration to storm conditions",
248 // 17 minutes = 204 samples
249 // 0-5 minutes: Beautiful clear morning
250 {
251 1350, 1360, 1345, 1355, 1340, 1350, 1365, 1355, 1345, 1350, // 0-50s
252 1360, 1350, 1355, 1345, 1360, 1350, 1340, 1355, 1360, 1345, // 50-100s
253 1350, 1355, 1360, 1345, 1350, 1365, 1350, 1355, 1340, 1350, // 100-150s
254 1360, 1345, 1350, 1355, 1360, 1350, 1345, 1355, 1350, 1360, // 150-200s
255 1345, 1350, 1355, 1360, 1350, 1345, 1355, 1350, 1360, 1345, // 200-250s
256 1350, 1355, 1360, 1345, 1350, 1355, 1360, 1350, 1345, 1355, // 250-300s (5min)
257
258 // 5-9 minutes: High clouds moving in - gradual decline
259 1300, 1280, 1250, 1200, 1150, 1100, 1050, 1000, 950, 900, // 300-350s
260 850, 800, 750, 700, 650, 600, 550, 500, 450, 400, // 350-400s
261 380, 360, 340, 320, 300, 280, 260, 240, 220, 200, // 400-450s
262 180, 160, 140, 120, 100, 90, 80, 70, 60, 50, // 450-500s
263 45, 40, 35, 30, 25, 22, 20, 18, 15, 12, // 500-540s (9min)
264
265 // 9-13 minutes: Storm conditions - very low, erratic production
266 10, 8, 6, 4, 2, 1, 0, 1, 3, 5, // 540-590s
267 8, 12, 15, 10, 5, 2, 0, 1, 4, 8, // 590-640s
268 12, 18, 25, 20, 15, 10, 5, 2, 1, 0, // 640-690s
269 1, 3, 6, 10, 15, 12, 8, 4, 1, 0, // 690-740s
270 2, 5, 8, 12, 18, 15, 10, 6, 3, 1, // 740-780s (13min)
271
272 // 13-17 minutes: Storm passing, very slow recovery
273 5, 10, 15, 20, 30, 40, 50, 60, 80, 100, // 780-830s
274 120, 150, 180, 200, 250, 300, 350, 400, 450, 500, // 830-880s
275 550, 600, 650, 700, 750, 800, 850, 900, 950, 1000, // 880-930s
276 1050, 1100, 1120, 1140, 1160, 1180, 1200, 1220, 1240, 1260, // 930-980s
277 1280, 1300, 1310, 1320, 1330, 1340, 1345, 1350, 1350, 1350, // 980-1020s (17min)
278 },
279 500 },
280 { "Morning Fog Clearing - 15 minutes",
281 "5min stable low production, then fog gradually lifts (coastal/mountain)",
282 // 15 minutes = 180 samples
283 // 0-5 minutes: Stable low production in fog
284 {
285 50, 55, 45, 52, 48, 50, 58, 52, 48, 50, // 0-50s
286 55, 50, 52, 48, 55, 50, 48, 52, 55, 48, // 50-100s
287 50, 52, 55, 48, 50, 58, 50, 52, 48, 50, // 100-150s
288 55, 48, 50, 52, 55, 50, 48, 52, 50, 55, // 150-200s
289 48, 50, 52, 55, 50, 48, 52, 50, 55, 48, // 200-250s
290 50, 52, 55, 48, 50, 52, 55, 50, 48, 52, // 250-300s (5min)
291
292 // 5-10 minutes: Fog starting to lift - gradual power increase
293 60, 70, 80, 90, 100, 120, 140, 160, 180, 200, // 300-350s
294 220, 250, 280, 320, 360, 400, 450, 500, 550, 600, // 350-400s
295 650, 700, 750, 800, 850, 900, 950, 1000, 1050, 1100, // 400-450s
296 1120, 1140, 1160, 1180, 1200, 1210, 1220, 1230, 1240, 1250, // 450-500s
297 1260, 1270, 1275, 1280, 1285, 1290, 1292, 1294, 1296, 1298, // 500-550s
298 1300, 1302, 1304, 1306, 1308, 1310, 1312, 1314, 1316, 1318, // 550-600s (10min)
299
300 // 10-15 minutes: Clear conditions achieved - stable high production
301 1320, 1322, 1324, 1326, 1328, 1330, 1328, 1326, 1324, 1322, // 600-650s
302 1320, 1322, 1324, 1326, 1328, 1330, 1332, 1330, 1328, 1326, // 650-700s
303 1324, 1322, 1320, 1322, 1324, 1326, 1328, 1330, 1328, 1326, // 700-750s
304 1324, 1322, 1320, 1318, 1320, 1322, 1324, 1326, 1328, 1330, // 750-800s
305 1328, 1326, 1324, 1322, 1320, 1322, 1324, 1326, 1328, 1330, // 800-850s
306 1328, 1326, 1324, 1322, 1320, 1322, 1324, 1326, 1328, 1330, // 850-900s (15min)
307 },
308 500 },
309 {
310 "Battery System - 20 minutes",
311 "5min stable, then varied conditions showing battery system behavior",
312 // 20 minutes = 240 samples, with negative import values representing import from grid
313 // 0-5 minutes: Stable moderate surplus
314 { 200, 210, 195, 205, 190, 200, 215, 205, 195, 200, // 0-50s
315 210, 200, 205, 195, 210, 200, 190, 205, 210, 195, // 50-100s
316 200, 205, 210, 195, 200, 215, 200, 205, 190, 200, // 100-150s
317 210, 195, 200, 205, 210, 200, 195, 205, 200, 210, // 150-200s
318 195, 200, 205, 210, 200, 195, 205, 200, 210, 195, // 200-250s
319 200, 205, 210, 195, 200, 205, 210, 200, 195, 205, // 250-300s (5min)
320
321 // 5-10 minutes: Variable conditions - import and export
322 -150, -100, -50, 0, 50, 100, 150, 200, 150, 100, // 300-350s (battery charging/discharging)
323 50, 0, -50, -100, -150, -100, -50, 0, 50, 100, // 350-400s
324 150, 200, 250, 300, 250, 200, 150, 100, 50, 0, // 400-450s
325 -50, -100, -150, -200, -150, -100, -50, 0, 50, 100, // 450-500s
326 150, 200, 300, 400, 500, 600, 500, 400, 300, 200, // 500-550s
327 100, 50, 0, -50, -100, -50, 0, 50, 100, 150, // 550-600s (10min)
328
329 // 10-15 minutes: High solar with battery behavior
330 800, 900, 1000, 1100, 1200, 1100, 1000, 900, 800, 700, // 600-650s
331 600, 500, 400, 300, 200, 100, 0, -100, -50, 0, // 650-700s
332 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, // 700-750s
333 1000, 1100, 1200, 1300, 1200, 1100, 1000, 900, 800, 700, // 750-800s
334 600, 500, 400, 300, 200, 100, 50, 0, -50, -100, // 800-850s
335 -50, 0, 50, 100, 200, 300, 400, 500, 600, 700, // 850-900s (15min)
336
337 // 15-20 minutes: Evening reduction with battery supporting loads
338 600, 500, 400, 300, 200, 100, 0, -100, -200, -300, // 900-950s
339 -250, -200, -150, -100, -50, 0, 50, 100, 50, 0, // 950-1000s
340 -50, -100, -150, -200, -150, -100, -50, 0, 50, 100, // 1000-1050s
341 150, 100, 50, 0, -50, -100, -150, -100, -50, 0, // 1050-1100s
342 50, 100, 50, 0, -50, -100, -50, 0, 50, 0, // 1100-1150s
343 -50, -100, -150, -100, -50, 0, 50, 100, 50, 0 }, // 1150-1200s (20min)
344 -100 // Negative threshold: need 100W+ surplus to turn on
345 }
346 };
347}
348
349void setUp(void)
350{
351 // Set up before each test
352}
353
354void tearDown(void)
355{
356 // Clean up after each test
357}
358
359// Test a specific cloud pattern with different filter delay settings
361{
362 printf("\n=== %s ===\n", pattern.name.c_str());
363 printf("%s\n", pattern.description.c_str());
364 printf("Duration: %.1f minutes (%zu samples at 5-second intervals)\n",
365 pattern.power_data.size() * 5.0 / 60.0, pattern.power_data.size());
366 printf("Threshold: %dW (positive=import limit, negative=surplus requirement)\n",
367 (int)pattern.relay_threshold);
368 printf("\nTesting RELAY_FILTER_DELAY_MINUTES: 1, 2, 3, 4, 5 minutes\n");
369 printf("Time(mm:ss),Power(W),1min,2min,3min,4min,5min,State_1m,State_2m,State_3m,State_4m,State_5m\n");
370
371 // Create EWMA filters for different delay settings (using TEMA for best cloud immunity)
372 EWMA_average< 12 > filter_1min; // 1 minute * 12 = alpha 12
373 EWMA_average< 24 > filter_2min; // 2 minutes * 12 = alpha 24
374 EWMA_average< 36 > filter_3min; // 3 minutes * 12 = alpha 36
375 EWMA_average< 48 > filter_4min; // 4 minutes * 12 = alpha 48
376 EWMA_average< 60 > filter_5min; // 5 minutes * 12 = alpha 60
377
378 // Create relay instances for each filter delay
379 TestRelay relay_1min(pattern.relay_threshold, 100, 1);
380 TestRelay relay_2min(pattern.relay_threshold, 100, 2);
381 TestRelay relay_3min(pattern.relay_threshold, 100, 3);
382 TestRelay relay_4min(pattern.relay_threshold, 100, 4);
383 TestRelay relay_5min(pattern.relay_threshold, 100, 5);
384
385 // Count relay state changes for each setting
386 int changes_1min = 0, changes_2min = 0, changes_3min = 0, changes_4min = 0, changes_5min = 0;
387 bool prev_1min = false, prev_2min = false, prev_3min = false, prev_4min = false, prev_5min = false;
388
389 // Track period summaries
390 std::vector< std::string > period_names = { "0-5min: Stable", "5-10min: Clouds Start", "10-15min: Peak Activity", "15-20min: Stabilizing" };
391 std::vector< int > period_starts = { 0, 60, 120, 180 }; // Sample indices for period starts
392
393 // Process each power measurement
394 for (size_t i = 0; i < pattern.power_data.size(); i++)
395 {
396 int32_t raw_power = pattern.power_data[i];
397
398 // Apply EWMA filtering (using TEMA for best cloud immunity)
399 filter_1min.addValue(raw_power);
400 filter_2min.addValue(raw_power);
401 filter_3min.addValue(raw_power);
402 filter_4min.addValue(raw_power);
403 filter_5min.addValue(raw_power);
404
405 int32_t filtered_1min = filter_1min.getAverageT();
406 int32_t filtered_2min = filter_2min.getAverageT();
407 int32_t filtered_3min = filter_3min.getAverageT();
408 int32_t filtered_4min = filter_4min.getAverageT();
409 int32_t filtered_5min = filter_5min.getAverageT();
410
411 // Apply relay logic
412 bool state_1min = relay_1min.proceed_relay(filtered_1min);
413 bool state_2min = relay_2min.proceed_relay(filtered_2min);
414 bool state_3min = relay_3min.proceed_relay(filtered_3min);
415 bool state_4min = relay_4min.proceed_relay(filtered_4min);
416 bool state_5min = relay_5min.proceed_relay(filtered_5min);
417
418 // Count state changes
419 if (state_1min != prev_1min) changes_1min++;
420 if (state_2min != prev_2min) changes_2min++;
421 if (state_3min != prev_3min) changes_3min++;
422 if (state_4min != prev_4min) changes_4min++;
423 if (state_5min != prev_5min) changes_5min++;
424
425 prev_1min = state_1min;
426 prev_2min = state_2min;
427 prev_3min = state_3min;
428 prev_4min = state_4min;
429 prev_5min = state_5min;
430
431 // Output data every 30 seconds for readability (every 6th sample)
432 if (i % 6 == 0 || i < 12 || i >= pattern.power_data.size() - 6)
433 {
434 int minutes = (i * 5) / 60;
435 int seconds = (i * 5) % 60;
436 printf("%02d:%02d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\n",
437 minutes, seconds, (int)raw_power,
438 (int)filtered_1min, (int)filtered_2min, (int)filtered_3min,
439 (int)filtered_4min, (int)filtered_5min,
440 state_1min ? 1 : 0, state_2min ? 1 : 0, state_3min ? 1 : 0,
441 state_4min ? 1 : 0, state_5min ? 1 : 0);
442 }
443
444 // Print period summaries
445 for (size_t p = 0; p < period_starts.size(); p++)
446 {
447 if (i == period_starts[p] && i > 0)
448 {
449 printf("# %s\n", period_names[p].c_str());
450 }
451 }
452 }
453
454 printf("\nšŸ“Š RELAY BEHAVIOR ANALYSIS\n");
455 printf("================================\n");
456 printf("Relay State Changes (lower = better stability):\n");
457 printf("1 minute delay: %d changes\n", changes_1min);
458 printf("2 minute delay: %d changes\n", changes_2min);
459 printf("3 minute delay: %d changes\n", changes_3min);
460 printf("4 minute delay: %d changes\n", changes_4min);
461 printf("5 minute delay: %d changes\n", changes_5min);
462
463 // Enhanced analysis
464 printf("\nšŸŽÆ PERFORMANCE COMPARISON:\n");
465 if (changes_1min > 0)
466 {
467 printf("Improvement with longer delays:\n");
468 if (changes_2min < changes_1min) printf(" 2min vs 1min: %d%% fewer changes\n", (changes_1min - changes_2min) * 100 / changes_1min);
469 if (changes_3min < changes_1min) printf(" 3min vs 1min: %d%% fewer changes\n", (changes_1min - changes_3min) * 100 / changes_1min);
470 if (changes_4min < changes_1min) printf(" 4min vs 1min: %d%% fewer changes\n", (changes_1min - changes_4min) * 100 / changes_1min);
471 if (changes_5min < changes_1min) printf(" 5min vs 1min: %d%% fewer changes\n", (changes_1min - changes_5min) * 100 / changes_1min);
472 }
473
474 // Smart recommendation based on pattern characteristics
475 printf("\nšŸ’” RECOMMENDATION for '%s':\n", pattern.name.c_str());
476 if (changes_1min <= 2)
477 {
478 printf("āœ… 1-2 minute delay is sufficient (responsive without excessive chatter)\n");
479 printf(" This pattern has stable characteristics suitable for fast response.\n");
480 }
481 else if (changes_3min < changes_1min * 0.6)
482 {
483 printf("āœ… 3 minute delay recommended (good balance of stability vs responsiveness)\n");
484 printf(" This pattern benefits significantly from additional filtering.\n");
485 }
486 else if (changes_4min < changes_2min * 0.8)
487 {
488 printf("āœ… 4-5 minute delay recommended (maximum stability for challenging conditions)\n");
489 printf(" This pattern requires heavy filtering to prevent relay chatter.\n");
490 }
491 else
492 {
493 printf("āœ… 2-3 minute delay recommended (standard setting works well)\n");
494 printf(" This pattern responds well to moderate filtering.\n");
495 }
496 printf("\n");
497}
498
500{
501 auto patterns = create_cloud_patterns();
503 TEST_ASSERT_TRUE(true); // Pass test - results shown in output
504}
505
507{
508 auto patterns = create_cloud_patterns();
510 TEST_ASSERT_TRUE(true); // Pass test - results shown in output
511}
512
514{
515 auto patterns = create_cloud_patterns();
517 TEST_ASSERT_TRUE(true); // Pass test - results shown in output
518}
519
521{
522 auto patterns = create_cloud_patterns();
524 TEST_ASSERT_TRUE(true); // Pass test - results shown in output
525}
526
528{
529 auto patterns = create_cloud_patterns();
531 TEST_ASSERT_TRUE(true); // Pass test - results shown in output
532}
533
535{
536 auto patterns = create_cloud_patterns();
538 TEST_ASSERT_TRUE(true); // Pass test - results shown in output
539}
540
542{
543 printf("\n=== RELAY_FILTER_DELAY_MINUTES Configuration Guide ===\n");
544 printf("\nBased on cloud pattern analysis:\n\n");
545
546 printf("šŸŒ¤ļø CLEAR SKY REGIONS (minimal clouds):\n");
547 printf(" RELAY_FILTER_DELAY_MINUTES = 1\n");
548 printf(" - Fast response to power changes\n");
549 printf(" - Suitable when cloud events are rare\n");
550 printf(" - Examples: Desert regions, dry climates\n\n");
551
552 printf("ā›… MIXED CONDITIONS (occasional clouds):\n");
553 printf(" RELAY_FILTER_DELAY_MINUTES = 2\n");
554 printf(" - Balanced response vs stability\n");
555 printf(" - Good default for most installations\n");
556 printf(" - Examples: Continental climates, suburban areas\n\n");
557
558 printf("ā˜ļø FREQUENTLY CLOUDY (regular cloud cover):\n");
559 printf(" RELAY_FILTER_DELAY_MINUTES = 3\n");
560 printf(" - Enhanced stability during cloud events\n");
561 printf(" - Reduces relay chatter significantly\n");
562 printf(" - Examples: Coastal areas, temperate climates\n\n");
563
564 printf("šŸŒ§ļø VERY CLOUDY (challenging conditions):\n");
565 printf(" RELAY_FILTER_DELAY_MINUTES = 4-5\n");
566 printf(" - Maximum stability for difficult conditions\n");
567 printf(" - Slower response but very stable\n");
568 printf(" - Examples: Mountain regions, tropical areas\n\n");
569
570 printf("šŸ”‹ BATTERY SYSTEMS:\n");
571 printf(" RELAY_FILTER_DELAY_MINUTES = 2-3\n");
572 printf(" - Use negative import threshold (e.g., -100W)\n");
573 printf(" - Requires surplus above threshold to activate\n");
574 printf(" - Prevents relay cycling due to battery buffer\n\n");
575
576 printf("Configuration example in config.h:\n");
577 printf("inline constexpr uint8_t RELAY_FILTER_DELAY_MINUTES{ 2 }; // Adjust based on your climate\n");
578 printf("// Normal installation:\n");
579 printf("inline constexpr RelayEngine< 1, RELAY_FILTER_DELAY_MINUTES > relays{ { { unused_pin, 1000, 200, 1, 1 } } };\n");
580 printf("// Battery installation:\n");
581 printf("inline constexpr RelayEngine< 1, RELAY_FILTER_DELAY_MINUTES > relays{ { { unused_pin, 1000, -100, 1, 1 } } };\n\n");
582
583 TEST_ASSERT_TRUE(true); // Pass test - guide shown in output
584}
585
586int main()
587{
588 UNITY_BEGIN();
589
590 printf("\nšŸŒ¤ļø =============================================== šŸŒ¤ļø\n");
591 printf(" PV Router Cloud Pattern Analysis Tool\n");
592 printf(" Tune RELAY_FILTER_DELAY_MINUTES for your climate\n");
593 printf("šŸŒ¤ļø =============================================== šŸŒ¤ļø\n");
594
602
603 return UNITY_END();
604}
constexpr float alpha
Definition calibration.h:54
Implements an Exponentially Weighted Moving Average (EWMA).
Definition ewma_avg.hpp:108
void addValue(int32_t input)
Definition test_main.cpp:37
int32_t ema_ema_ema
Definition ewma_avg.hpp:162
int32_t ema
Definition ewma_avg.hpp:166
int32_t ema_ema_ema_raw
Definition ewma_avg.hpp:161
auto getAverageD() const
Definition test_main.cpp:53
auto getAverageT() const
Definition test_main.cpp:57
int32_t ema_ema_raw
Definition ewma_avg.hpp:163
int32_t ema_raw
Definition ewma_avg.hpp:165
auto getAverageS() const
Definition test_main.cpp:49
int32_t ema_ema
Definition ewma_avg.hpp:164
uint8_t m_filter_delay_minutes
Definition test_main.cpp:83
TestRelay(int32_t import_threshold=20, int32_t surplus_threshold=20, uint8_t filter_delay=2)
Definition test_main.cpp:85
bool m_relay_state
Definition test_main.cpp:80
bool proceed_relay(int32_t power_value)
Definition test_main.cpp:89
int32_t m_import_threshold
Definition test_main.cpp:81
void reset_state()
int32_t m_surplus_threshold
Definition test_main.cpp:82
void setUp(void)
Definition test_main.cpp:6
void tearDown(void)
Definition test_main.cpp:11
uint8_t i
unsigned char byte
Definition test_main.cpp:7
void test_cloud_pattern_with_filter_delays(const CloudPattern &pattern)
void test_morning_fog_clearing_pattern()
void test_scattered_clouds_pattern()
void test_intermittent_clouds_pattern()
void test_filter_delay_configuration_guide()
constexpr uint8_t delay_minutes_to_alpha(uint8_t delay_minutes)
Definition test_main.cpp:70
std::vector< CloudPattern > create_cloud_patterns()
constexpr uint8_t round_up_to_power_of_2(uint16_t v)
Definition test_main.cpp:10
void test_heavy_cloud_bank_pattern()
int main()
void test_battery_system_pattern()
void test_storm_approach_pattern()
constexpr uint8_t round_up_to_power_of_2(uint16_t v)
Definition test_main.cpp:11
int32_t relay_threshold
std::string name
std::string description
std::vector< int32_t > power_data