Skip to content

Commit 31cd0ba

Browse files
committed
add real project node demo
1 parent 796cec1 commit 31cd0ba

2 files changed

Lines changed: 325 additions & 0 deletions

File tree

Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
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+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5+
ARDUINO_CLI="$(command -v arduino-cli 2>/dev/null || true)"
6+
if [[ -z "${ARDUINO_CLI}" ]]; then
7+
ARDUINO_CLI="${ROOT_DIR}/bin/arduino-cli"
8+
fi
9+
PORT="${1:-/dev/ttyUSB1}"
10+
FQBN="esp32:esp32:esp32"
11+
CAPTURE_SECONDS="${CAPTURE_SECONDS:-8}"
12+
CAPTURE_START_DELAY="${CAPTURE_START_DELAY:-2}"
13+
CAPTURE_HELPER="${ROOT_DIR}/scripts/capture_esp32_serial.py"
14+
BUILD_LOG="/tmp/zerokernel_esp32_real_project_build.log"
15+
SERIAL_LOG="/tmp/zerokernel_esp32_real_project_serial.log"
16+
RESTORE_BUILD_LOG="/tmp/zerokernel_esp32_real_project_restore.log"
17+
18+
if [[ ! -x "${ARDUINO_CLI}" ]]; then
19+
echo "arduino-cli not found at ${ARDUINO_CLI}" >&2
20+
exit 1
21+
fi
22+
23+
restore_safe() {
24+
echo "Restoring KernelIdentity on ${PORT}"
25+
"${ARDUINO_CLI}" compile --fqbn "${FQBN}" --board-options UploadSpeed=115200 \
26+
--library "${ROOT_DIR}" \
27+
--build-property "build.extra_flags=-DZEROKERNEL_PROFILE_POWER_SAVE" \
28+
--upload -p "${PORT}" \
29+
"${ROOT_DIR}/examples/KernelIdentity" \
30+
2>&1 | tee "${RESTORE_BUILD_LOG}" >/dev/null || true
31+
}
32+
33+
trap restore_safe EXIT
34+
35+
"${ARDUINO_CLI}" compile --fqbn "${FQBN}" --board-options UploadSpeed=115200 \
36+
--library "${ROOT_DIR}" \
37+
--upload -p "${PORT}" \
38+
"${ROOT_DIR}/examples/RealProjectNode" \
39+
2>&1 | tee "${BUILD_LOG}"
40+
41+
python3 "${CAPTURE_HELPER}" \
42+
--port "${PORT}" \
43+
--seconds "${CAPTURE_SECONDS}" \
44+
--start-delay "${CAPTURE_START_DELAY}" \
45+
--output "${SERIAL_LOG}"
46+
47+
grep 'REAL_PROJECT_NODE' "${SERIAL_LOG}" | tail -n 1

0 commit comments

Comments
 (0)