-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathImprovProvisioningModule.h
More file actions
158 lines (142 loc) · 7.88 KB
/
Copy pathImprovProvisioningModule.h
File metadata and controls
158 lines (142 loc) · 7.88 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
#pragma once
#include "core/MoonModule.h"
#include "core/NetworkModule.h"
#include "core/SystemModule.h"
#include "core/HttpServerModule.h"
#include "core/build_info.h"
#include "platform/platform.h"
#include <atomic>
#include <cstdio>
#include <cstring>
namespace mm {
// ImprovProvisioningModule — listens for Improv WiFi frames on UART0 and
// pushes credentials into NetworkModule. Browser drives the protocol via
// ESP Web Tools / improv-wifi.com; a Python CLI mirror lives at
// scripts/build/improv_provision.py for rack / CI use over USB.
//
// The actual protocol parsing + UART task lives in the platform layer
// (`mm::platform::improvProvisioningInit` at platform.h). This module is the
// status surface: one read-only `provision_status` Control that reports
// "listening" / "received credentials" / "connecting" / "connected: <ssid>"
// / "error: …". Module's loop1s() polls a `ready` flag the platform task
// sets when credentials arrive, then calls NetworkModule::setWifiCredentials
// which writes through to the same buffers the AP-fallback UI flow uses.
//
// On desktop (platform::hasImprov == false) the module exists for UI
// uniformity; the listener-install is skipped.
class ImprovProvisioningModule : public MoonModule {
public:
void setSystemModule(SystemModule* s) { systemModule_ = s; }
void setNetworkModule(NetworkModule* n) { networkModule_ = n; }
// For the APPLY_OP vendor RPC — the module routes a pushed REST op to the
// HttpServerModule's apply-core (the same code /api/modules + /api/control use).
void setHttpServerModule(HttpServerModule* h) { httpServerModule_ = h; }
// Diagnostics keep ticking; matches FirmwareUpdateModule / SystemModule.
bool respectsEnabled() const override { return false; }
// Apparatus, not swappable content — provisioning is a fixed device service.
// Not deletable (matches Board / Preview); can still be disabled.
bool userEditable() const override { return false; }
void setup() override {
if constexpr (platform::hasImprov) {
// Strings borrowed; platform task copies them into its own storage
// on init, so locals going out of scope here is fine.
const char* deviceName = systemModule_ ? systemModule_->deviceName() : "projectMM";
platform::ImprovDeviceInfo info{
deviceName,
platform::chipModel(),
kVersion,
};
platform::improvProvisioningInit(
info,
pendingSsid_, sizeof(pendingSsid_),
pendingPassword_, sizeof(pendingPassword_),
&pendingCredentials_,
statusStr_, sizeof(statusStr_),
&pendingTxPower_, &pendingTxPowerReady_,
pendingOp_, sizeof(pendingOp_), &pendingOpReady_);
} else {
std::strncpy(statusStr_, "not supported on this platform", sizeof(statusStr_) - 1);
}
}
void onBuildControls() override {
controls_.addReadOnly("provision_status", statusStr_, sizeof(statusStr_));
}
void loop1s() override {
// Vendor SET_TX_POWER RPC — handled BEFORE the credentials on purpose:
// when an installer sends the cap and the credentials back-to-back,
// both flags can land within one tick, and the cap must be persisted
// before the STA attempt starts or a brown-out-prone board (a weak LDO /
// marginal supply) fails auth at full power — the exact hole this RPC closes.
if (pendingTxPowerReady_.load(std::memory_order_acquire) && networkModule_) {
networkModule_->setTxPowerSetting(pendingTxPower_);
pendingTxPowerReady_.store(false, std::memory_order_release);
}
// The platform task writes credentials into pendingSsid_/pendingPassword_
// then publishes via a release-store on pendingCredentials_. We do an
// acquire-load here so the buffer writes are visible before we read
// them. Pairs with the release-store in platform_esp32.cpp.
if (pendingCredentials_.load(std::memory_order_acquire) && networkModule_) {
networkModule_->setWifiCredentials(pendingSsid_, pendingPassword_);
// Wipe the on-stack-ish password buffer; status string keeps any
// error message the platform layer wrote. SSID is non-sensitive,
// leave it for the next poll if a re-provision arrives.
std::memset(pendingPassword_, 0, sizeof(pendingPassword_));
pendingCredentials_.store(false, std::memory_order_release);
}
// deviceModel arrives like any other catalog default: an APPLY_OP
// `set System.deviceModel` op, routed through the apply-core and the
// control's per-control validator (handled in the APPLY_OP poll below).
}
// APPLY_OP is polled per-TICK (not loop1s) because the installer pushes a burst
// of ops during provisioning and single-buffers them: the Improv task refuses a
// new op until this consumes the previous (clears pendingOpReady_), so a fast
// poll keeps the busy-window to ~one tick and the install snappy. Applying on the
// main loop here (not the Improv task) keeps the factory/tree mutation off the
// serial task — the same discipline the credentials/deviceModel paths follow.
void loop() override {
if (pendingOpReady_.load(std::memory_order_acquire) && httpServerModule_) {
// The Improv task already acked frame RECEIPT; the op is APPLIED here. A
// failed op (UnknownType, OutOfRange, a not-found target) can't travel back
// on that spent ack, so surface it: log it over serial and park it in
// provision_status, so a silently-misconfigured device is visible on a
// monitor and via /api/state rather than looking like a clean install.
// Ok and AlreadyExists are both success (a re-pushed op is idempotent);
// only a genuine failure is surfaced.
auto r = httpServerModule_->applyOp(pendingOp_);
if (r != HttpServerModule::OpResult::Ok &&
r != HttpServerModule::OpResult::AlreadyExists) {
std::printf("Improv APPLY_OP failed (result=%d): %s\n",
static_cast<int>(r), pendingOp_);
std::snprintf(statusStr_, sizeof(statusStr_), "error: apply failed (%d)",
static_cast<int>(r));
}
std::memset(pendingOp_, 0, sizeof(pendingOp_));
pendingOpReady_.store(false, std::memory_order_release);
}
MoonModule::loop(); // tick children (none today, but keep the contract)
}
private:
SystemModule* systemModule_ = nullptr;
NetworkModule* networkModule_ = nullptr;
HttpServerModule* httpServerModule_ = nullptr;
char statusStr_[64] = "listening";
// Buffers the platform task writes; sized to NetworkModule's storage.
// std::atomic<bool> for the ready flag — read by loop1s() on the
// scheduler thread, written by the Improv task. Acquire/release fencing
// ensures the buffer writes are ordered against the flag publication,
// which matters on dual-core Xtensa where the producer and consumer can
// run on different cores.
char pendingSsid_[33] = {};
char pendingPassword_[64] = {};
std::atomic<bool> pendingCredentials_{false};
// Vendor SET_TX_POWER RPC — the pre-association TX-power cap (whole dBm)
// for brown-out-prone boards; same producer/consumer shape as the above.
uint8_t pendingTxPower_ = 0;
std::atomic<bool> pendingTxPowerReady_{false};
// Vendor APPLY_OP RPC — one REST op as JSON, reassembled by the Improv task and
// applied here on the main loop via HttpServerModule::applyOp. Sized for the
// largest op (a long pins list fits comfortably in 512 bytes).
char pendingOp_[512] = {};
std::atomic<bool> pendingOpReady_{false};
};
} // namespace mm