|
| 1 | +#include <ZeroKernel.h> |
| 2 | +#include <adapters/PowerSaveLoopAdapter.h> |
| 3 | +#include <modules/net/ZeroHttpPump.h> |
| 4 | +#include <modules/net/ZeroMqttPump.h> |
| 5 | +#include <modules/net/ZeroWiFiMaintainer.h> |
| 6 | + |
| 7 | +using zerokernel::Kernel; |
| 8 | +using zerokernel::modules::net::ZeroHttpPump; |
| 9 | +using zerokernel::modules::net::ZeroMqttPump; |
| 10 | +using zerokernel::modules::net::ZeroWiFiMaintainer; |
| 11 | + |
| 12 | +namespace { |
| 13 | + |
| 14 | +const unsigned long kSamplePeriodUs = 100000UL; |
| 15 | +const unsigned long kSummaryPeriodMs = 5000UL; |
| 16 | +const unsigned long kLinkDropPeriodMs = 12000UL; |
| 17 | +const unsigned long kLinkRestoreDelayMs = 1500UL; |
| 18 | +const Kernel::TopicKey kWiFiStateTopic = Kernel::makeTopicKey("real.wifi"); |
| 19 | +const Kernel::TopicKey kTelemetryTopic = Kernel::makeTopicKey("real.mqtt.telemetry"); |
| 20 | + |
| 21 | +ZeroWiFiMaintainer g_wifiMaintainer; |
| 22 | +ZeroHttpPump g_httpPump; |
| 23 | +ZeroMqttPump g_mqttPump; |
| 24 | + |
| 25 | +bool g_wifiConnected = false; |
| 26 | +bool g_wifiReadyToConnect = false; |
| 27 | +bool g_mqttConnected = false; |
| 28 | +unsigned long g_wifiConnectCalls = 0; |
| 29 | +unsigned long g_wifiForcedDropAtMs = 0; |
| 30 | +unsigned long g_lastDropWindowIndex = 0; |
| 31 | +unsigned long g_httpConnectAttempts = 0; |
| 32 | +unsigned long g_httpRequestSeq = 0; |
| 33 | +unsigned long g_httpWriteCalls = 0; |
| 34 | +unsigned long g_mqttPublishCalls = 0; |
| 35 | +unsigned long g_nextExpectedUs = 0; |
| 36 | +unsigned long g_lagAccumUs = 0; |
| 37 | +unsigned long g_maxLagUs = 0; |
| 38 | +unsigned long g_sampleRuns = 0; |
| 39 | +unsigned long g_fastMisses = 0; |
| 40 | +unsigned long g_sensorValue = 1000; |
| 41 | +unsigned long g_startedAtMs = 0; |
| 42 | +unsigned long g_lastSummaryAtMs = 0; |
| 43 | + |
| 44 | +unsigned long boardMillis() { |
| 45 | + return millis(); |
| 46 | +} |
| 47 | + |
| 48 | +unsigned long percentage(unsigned long ok, unsigned long fail) { |
| 49 | + const unsigned long total = ok + fail; |
| 50 | + if (total == 0) { |
| 51 | + return 100; |
| 52 | + } |
| 53 | + return (ok * 100UL) / total; |
| 54 | +} |
| 55 | + |
| 56 | +void updateLinkSimulation(unsigned long nowMs) { |
| 57 | + const unsigned long windowIndex = (nowMs / kLinkDropPeriodMs); |
| 58 | + if (windowIndex > 0 && windowIndex != g_lastDropWindowIndex) { |
| 59 | + g_lastDropWindowIndex = windowIndex; |
| 60 | + g_wifiConnected = false; |
| 61 | + g_mqttConnected = false; |
| 62 | + g_wifiReadyToConnect = false; |
| 63 | + g_wifiForcedDropAtMs = nowMs; |
| 64 | + } |
| 65 | +} |
| 66 | + |
| 67 | +bool isWiFiConnected() { |
| 68 | + return g_wifiConnected; |
| 69 | +} |
| 70 | + |
| 71 | +void connectWiFi() { |
| 72 | + const unsigned long nowMs = millis(); |
| 73 | + ++g_wifiConnectCalls; |
| 74 | + |
| 75 | + if (!g_wifiReadyToConnect) { |
| 76 | + g_wifiReadyToConnect = true; |
| 77 | + return; |
| 78 | + } |
| 79 | + |
| 80 | + if (g_wifiForcedDropAtMs != 0 && (nowMs - g_wifiForcedDropAtMs) < kLinkRestoreDelayMs) { |
| 81 | + return; |
| 82 | + } |
| 83 | + |
| 84 | + g_wifiConnected = true; |
| 85 | + g_mqttConnected = true; |
| 86 | + g_wifiForcedDropAtMs = 0; |
| 87 | +} |
| 88 | + |
| 89 | +void disconnectWiFi() { |
| 90 | + g_wifiConnected = false; |
| 91 | + g_mqttConnected = false; |
| 92 | + g_wifiReadyToConnect = false; |
| 93 | + g_wifiForcedDropAtMs = millis(); |
| 94 | +} |
| 95 | + |
| 96 | +ZeroHttpPump::StepResult httpConnectStep(const ZeroHttpPump::Request&, void*) { |
| 97 | + ++g_httpConnectAttempts; |
| 98 | + return ZeroHttpPump::kStepComplete; |
| 99 | +} |
| 100 | + |
| 101 | +ZeroHttpPump::StepResult httpWriteStep(const ZeroHttpPump::Request&, void*) { |
| 102 | + ++g_httpWriteCalls; |
| 103 | + if ((g_httpWriteCalls % 7UL) == 0) { |
| 104 | + return ZeroHttpPump::kStepFailed; |
| 105 | + } |
| 106 | + |
| 107 | + return ZeroHttpPump::kStepComplete; |
| 108 | +} |
| 109 | + |
| 110 | +ZeroHttpPump::StepResult httpReadStep(const ZeroHttpPump::Request&, void*) { |
| 111 | + return ZeroHttpPump::kStepComplete; |
| 112 | +} |
| 113 | + |
| 114 | +bool mqttLinkProbe() { |
| 115 | + return g_mqttConnected; |
| 116 | +} |
| 117 | + |
| 118 | +bool mqttConnectStep(void*) { |
| 119 | + g_mqttConnected = g_wifiConnected; |
| 120 | + return g_mqttConnected; |
| 121 | +} |
| 122 | + |
| 123 | +void mqttLoopStep(void*) {} |
| 124 | + |
| 125 | +bool mqttPublishStep(Kernel::TopicKey, const Kernel::EventValue&, void*) { |
| 126 | + ++g_mqttPublishCalls; |
| 127 | + return (g_mqttPublishCalls % 9UL) != 0; |
| 128 | +} |
| 129 | + |
| 130 | +void sampleTask() { |
| 131 | + const unsigned long nowUs = micros(); |
| 132 | + if (g_nextExpectedUs == 0) { |
| 133 | + g_nextExpectedUs = nowUs; |
| 134 | + } |
| 135 | + |
| 136 | + const unsigned long lagUs = nowUs > g_nextExpectedUs ? nowUs - g_nextExpectedUs : 0; |
| 137 | + g_lagAccumUs += lagUs; |
| 138 | + if (lagUs > g_maxLagUs) { |
| 139 | + g_maxLagUs = lagUs; |
| 140 | + } |
| 141 | + if (lagUs > 1500UL) { |
| 142 | + ++g_fastMisses; |
| 143 | + } |
| 144 | + ++g_sampleRuns; |
| 145 | + ++g_sensorValue; |
| 146 | + g_nextExpectedUs += kSamplePeriodUs; |
| 147 | + if (nowUs > g_nextExpectedUs + kSamplePeriodUs) { |
| 148 | + g_nextExpectedUs = nowUs + kSamplePeriodUs; |
| 149 | + } |
| 150 | +} |
| 151 | + |
| 152 | +void queueTask() { |
| 153 | + static char payload[64]; |
| 154 | + const int written = |
| 155 | + snprintf(payload, sizeof(payload), "{\"seq\":%lu,\"sensor\":%lu}", |
| 156 | + g_httpRequestSeq++, g_sensorValue); |
| 157 | + if (written > 0) { |
| 158 | + ZeroHttpPump::Request request; |
| 159 | + request.path = "/api/data"; |
| 160 | + request.contentType = "application/json"; |
| 161 | + request.body = payload; |
| 162 | + request.bodyLength = static_cast<uint16_t>(written); |
| 163 | + g_httpPump.enqueue(request); |
| 164 | + } |
| 165 | + |
| 166 | + g_mqttPump.enqueue(kTelemetryTopic, Kernel::EventValue::fromUnsigned(g_sensorValue), 1); |
| 167 | +} |
| 168 | + |
| 169 | +void reportTask() { |
| 170 | + const unsigned long nowMs = millis(); |
| 171 | + if ((nowMs - g_lastSummaryAtMs) < kSummaryPeriodMs) { |
| 172 | + return; |
| 173 | + } |
| 174 | + |
| 175 | + g_lastSummaryAtMs = nowMs; |
| 176 | + updateLinkSimulation(nowMs); |
| 177 | + |
| 178 | + const unsigned long windowMs = nowMs - g_startedAtMs; |
| 179 | + const unsigned long avgLagUs = g_sampleRuns == 0 ? 0 : g_lagAccumUs / g_sampleRuns; |
| 180 | + const zerokernel::modules::net::ZeroTransportMetrics::Snapshot httpMetrics = |
| 181 | + g_httpPump.metrics().snapshot(); |
| 182 | + const zerokernel::modules::net::ZeroTransportMetrics::Snapshot mqttMetrics = |
| 183 | + g_mqttPump.metrics().snapshot(); |
| 184 | + |
| 185 | + Serial.print("REAL_PROJECT_NODE window_ms="); |
| 186 | + Serial.print(windowMs); |
| 187 | + Serial.print(" sample_runs="); |
| 188 | + Serial.print(g_sampleRuns); |
| 189 | + Serial.print(" fast_avg_lag_us="); |
| 190 | + Serial.print(avgLagUs); |
| 191 | + Serial.print(" fast_max_lag_us="); |
| 192 | + Serial.print(g_maxLagUs); |
| 193 | + Serial.print(" fast_miss="); |
| 194 | + Serial.print(g_fastMisses); |
| 195 | + Serial.print(" link_up="); |
| 196 | + Serial.print(g_wifiConnected ? 1 : 0); |
| 197 | + Serial.print(" wifi_attempts="); |
| 198 | + Serial.print(g_wifiMaintainer.connectAttempts()); |
| 199 | + Serial.print(" reconnects="); |
| 200 | + Serial.print(g_wifiMaintainer.reconnectTransitions()); |
| 201 | + Serial.print(" http_ok="); |
| 202 | + Serial.print(httpMetrics.sendSuccesses); |
| 203 | + Serial.print(" http_fail="); |
| 204 | + Serial.print(httpMetrics.sendFailures); |
| 205 | + Serial.print(" http_rate="); |
| 206 | + Serial.print(percentage(httpMetrics.sendSuccesses, httpMetrics.sendFailures)); |
| 207 | + Serial.print(" mqtt_ok="); |
| 208 | + Serial.print(mqttMetrics.sendSuccesses); |
| 209 | + Serial.print(" mqtt_fail="); |
| 210 | + Serial.print(mqttMetrics.sendFailures); |
| 211 | + Serial.print(" mqtt_rate="); |
| 212 | + Serial.print(percentage(mqttMetrics.sendSuccesses, mqttMetrics.sendFailures)); |
| 213 | + Serial.print(" http_queue="); |
| 214 | + Serial.print(g_httpPump.queuedCount()); |
| 215 | + Serial.print(" mqtt_queue="); |
| 216 | + Serial.println(g_mqttPump.queuedCount()); |
| 217 | +} |
| 218 | + |
| 219 | +void onWiFiState(const char*, const Kernel::EventValue&) {} |
| 220 | + |
| 221 | +} // namespace |
| 222 | + |
| 223 | +void setup() { |
| 224 | + Serial.begin(115200); |
| 225 | + delay(50); |
| 226 | + |
| 227 | + ZeroKernel.begin(boardMillis); |
| 228 | + ZeroKernel.setIdleStrategy(zerokernel::Kernel::kIdleSleep); |
| 229 | + ZeroKernel.subscribeTypedFast(kWiFiStateTopic, onWiFiState); |
| 230 | + |
| 231 | + ZeroWiFiMaintainer::Config wifiConfig; |
| 232 | + wifiConfig.pollIntervalMs = 250; |
| 233 | + wifiConfig.retryBaseMs = 500; |
| 234 | + wifiConfig.retryMaxMs = 1000; |
| 235 | + wifiConfig.stateTopicKey = kWiFiStateTopic; |
| 236 | + g_wifiMaintainer.begin(ZeroKernel, isWiFiConnected, connectWiFi, disconnectWiFi, wifiConfig); |
| 237 | + |
| 238 | + ZeroHttpPump::Config httpConfig; |
| 239 | + httpConfig.pollIntervalMs = 100; |
| 240 | + httpConfig.retryBaseMs = 300; |
| 241 | + httpConfig.retryMaxMs = 600; |
| 242 | + httpConfig.maxRetries = 1; |
| 243 | + g_httpPump.begin(ZeroKernel, |
| 244 | + httpConnectStep, |
| 245 | + httpWriteStep, |
| 246 | + httpReadStep, |
| 247 | + NULL, |
| 248 | + httpConfig); |
| 249 | + |
| 250 | + ZeroMqttPump::Config mqttConfig; |
| 251 | + mqttConfig.pollIntervalMs = 100; |
| 252 | + mqttConfig.retryBaseMs = 250; |
| 253 | + mqttConfig.retryMaxMs = 500; |
| 254 | + mqttConfig.maxRetries = 1; |
| 255 | + mqttConfig.stateTopicKey = 0; |
| 256 | + g_mqttPump.begin(ZeroKernel, |
| 257 | + mqttLinkProbe, |
| 258 | + mqttConnectStep, |
| 259 | + mqttLoopStep, |
| 260 | + mqttPublishStep, |
| 261 | + mqttConfig); |
| 262 | + |
| 263 | + ZeroKernel.addTask("Sample", sampleTask, 100, 0); |
| 264 | + ZeroKernel.addTask("Queue", queueTask, 300, 0); |
| 265 | + ZeroKernel.addTask("Report", reportTask, 1000, 0); |
| 266 | + |
| 267 | + const unsigned long startedAtMs = millis(); |
| 268 | + g_startedAtMs = startedAtMs; |
| 269 | + g_lastSummaryAtMs = startedAtMs; |
| 270 | +} |
| 271 | + |
| 272 | +void loop() { |
| 273 | + updateLinkSimulation(millis()); |
| 274 | + zerokernel::adapters::powerSaveTick(ZeroKernel); |
| 275 | + g_wifiMaintainer.tick(); |
| 276 | + g_httpPump.tick(); |
| 277 | + g_mqttPump.tick(); |
| 278 | +} |
0 commit comments