STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
STAR_EthernetOTA.cpp
Go to the documentation of this file.
1#include "STAR_EthernetOTA.h"
2
3#include <Update.h>
4
5namespace StarOTA {
6
7namespace {
8
9// Serial output is gated on config.verbose, but errors are worth printing
10// even on a quiet board: an OTA that silently fails is the worst outcome.
11void logf(bool verbose, const char* fmt, ...) {
12 if (!verbose)
13 return;
14 char buf[128];
15 va_list args;
16 va_start(args, fmt);
17 vsnprintf(buf, sizeof(buf), fmt, args);
18 va_end(args);
19 Serial.print(buf);
20}
21
22} // namespace
23
24const char* resultName(Result r) {
25 switch (r) {
26 case Result::Idle:
27 return "idle";
28 case Result::Success:
29 return "success";
31 return "timed out waiting for size header";
32 case Result::BadSize:
33 return "implausible image size";
35 return "Update.begin() failed";
37 return "timed out waiting for firmware data";
39 return "flash write size mismatch";
41 return "Update.end() failed";
42 }
43 return "unknown";
44}
45
47 config.port = port;
48}
49
51 // Pass the port explicitly. A bare begin() is ambiguous on cores where
52 // EthernetServer exposes both begin() and begin(uint16_t) — see
53 // PortableServer's note — and which shape you get varies by core version.
54 server_.begin(config.port);
55 Serial.print("[OTA] listening on TCP port ");
56 Serial.println(config.port);
57 Serial.flush();
58}
59
60void Server::onStart(StartCallback cb, void* user_data) {
61 on_start_ = cb;
62 on_start_data_ = user_data;
63}
64
65void Server::onProgress(ProgressCallback cb, void* user_data) {
66 on_progress_ = cb;
67 on_progress_data_ = user_data;
68}
69
70void Server::onEnd(EndCallback cb, void* user_data) {
71 on_end_ = cb;
72 on_end_data_ = user_data;
73}
74
76 EthernetClient c = server_.available();
77 return static_cast<bool>(c);
78}
79
81 EthernetClient client = server_.available();
82 if (!client)
83 return Result::Idle;
84 return handleClient(client);
85}
86
88 Result r = transfer(client);
90 if (on_end_)
92
93 if (r == Result::Success) {
94 Serial.println("[OTA] update successful");
95 Serial.flush();
96 client.println("OK");
97 client.flush();
98 client.stop();
99 if (config.reboot_on_success) {
100 Serial.println("[OTA] rebooting into the new image");
101 Serial.flush();
102 delay(500);
103 ESP.restart();
104 }
105 }
106 return r;
107}
108
109Result Server::fail(EthernetClient& client, Result r, const char* why) {
110 // Always printed: a silent OTA failure is worse than a noisy one.
111 Serial.print("[OTA] ERROR: ");
112 Serial.println(why);
113 Serial.flush();
114 client.stop();
115 return r;
116}
117
118bool Server::readSize(EthernetClient& client, uint32_t& size_out) {
119 unsigned long start = millis();
120 while (client.available() < 4) {
121 if (millis() - start > config.timeout_ms)
122 return false;
123 delay(1);
124 }
125 size_out = 0;
126 size_out |= static_cast<uint32_t>(client.read()) << 24;
127 size_out |= static_cast<uint32_t>(client.read()) << 16;
128 size_out |= static_cast<uint32_t>(client.read()) << 8;
129 size_out |= static_cast<uint32_t>(client.read());
130 return true;
131}
132
134 logf(config.verbose, "[OTA] client connected -- starting transfer\n");
135
136 uint32_t image_bytes = 0;
137 if (!readSize(client, image_bytes))
138 return fail(client, Result::HeaderTimeout,
139 "timed out waiting for the 4-byte size header");
140
141 if (image_bytes == 0 || image_bytes > config.max_image_bytes) {
142 Serial.print("[OTA] announced size: ");
143 Serial.println(image_bytes);
144 return fail(client, Result::BadSize, "implausible image size");
145 }
146 logf(config.verbose, "[OTA] image size: %u bytes\n",
147 static_cast<unsigned>(image_bytes));
148
149 // Tell the board before touching flash — the hotfire boards use this to
150 // freeze their address so they cannot re-address mid-flash.
151 if (on_start_)
152 on_start_(on_start_data_, image_bytes);
153
154 if (!Update.begin(image_bytes)) {
155 Update.printError(Serial);
156 return fail(client, Result::BeginFailed, "Update.begin() refused");
157 }
158
159 uint8_t buf[STAR_OTA_CHUNK_SIZE];
160 uint32_t received = 0;
161 int last_percent_bucket = -1;
162 unsigned long last_data_ms = millis();
163
164 while (received < image_bytes) {
165 int available = client.available();
166 if (available <= 0) {
167 if (millis() - last_data_ms > config.timeout_ms) {
168 Update.abort();
169 return fail(client, Result::DataTimeout,
170 "transfer stalled partway through");
171 }
172 delay(1);
173 continue;
174 }
175
176 int to_read = available < static_cast<int>(sizeof(buf))
177 ? available
178 : static_cast<int>(sizeof(buf));
179 const uint32_t remaining = image_bytes - received;
180 if (static_cast<uint32_t>(to_read) > remaining)
181 to_read = static_cast<int>(remaining);
182
183 int bytes_read = client.read(buf, to_read);
184 if (bytes_read <= 0)
185 continue;
186
187 if (Update.write(buf, bytes_read) != static_cast<size_t>(bytes_read)) {
188 Update.printError(Serial);
189 Update.abort();
190 return fail(client, Result::WriteFailed,
191 "flash write came up short");
192 }
193
194 received += bytes_read;
195 last_data_ms = millis();
196
197 if (on_progress_)
198 on_progress_(on_progress_data_, received, image_bytes);
199
200 int percent = static_cast<int>((received * 100UL) / image_bytes);
201 if (percent / 5 != last_percent_bucket) {
202 last_percent_bucket = percent / 5;
203 logf(config.verbose, "[OTA] progress: %d%% (%u / %u bytes)\n",
204 percent, static_cast<unsigned>(received),
205 static_cast<unsigned>(image_bytes));
206 }
207 }
208
209 logf(config.verbose, "[OTA] all bytes received -- finalizing\n");
210 if (!Update.end(true)) {
211 Update.printError(Serial);
212 return fail(client, Result::EndFailed, "image failed validation");
213 }
214 return Result::Success;
215}
216
217//-----------------------------------------------------------------------------
218// Bench-test marker
219//-----------------------------------------------------------------------------
220const char* testMessage() {
222}
223
225 static const char* message = STAR_OTA_TEST_MESSAGE;
226 if (message[0] == '\0')
227 return; // no message compiled in — nothing to do on a flight build
228
229 static unsigned long last_ms = 0;
230 unsigned long now = millis();
231 // Print immediately on the first call, then on the interval, so the new
232 // message shows up the moment the board comes back from an OTA.
233 if (last_ms != 0 && now - last_ms < STAR_OTA_TEST_MESSAGE_INTERVAL_MS)
234 return;
235 last_ms = now;
236 Serial.print("[OTA-MSG] ");
237 Serial.println(message);
238 Serial.flush();
239}
240
241} // namespace StarOTA
static SerialStub Serial
Definition Arduino.h:128
static ESPClassStub ESP
Definition Arduino.h:139
unsigned long millis()
Definition Arduino.h:29
#define STAR_OTA_TEST_MESSAGE
Optional bench-test marker.
#define STAR_OTA_CHUNK_SIZE
Read buffer for the firmware stream.
#define STAR_OTA_TEST_MESSAGE_INTERVAL_MS
How often printTestMessage() prints, when a message is set.
static UpdateClass Update
Definition Update.h:23
void delay(int ms)
static bool verbose()
void begin()
Start listening.
void(*)(void *user_data, uint32_t image_bytes) StartCallback
StartCallback on_start_
bool readSize(EthernetClient &client, uint32_t &size_out)
Result poll()
Accept a waiting client and run the transfer to completion.
void onEnd(EndCallback cb, void *user_data=nullptr)
Called on both success and failure, before any reboot.
void onProgress(ProgressCallback cb, void *user_data=nullptr)
Called every STAR_OTA_CHUNK_SIZE-ish bytes.
Result handleClient(EthernetClient &client)
Run the transfer on a client you accepted yourself.
Result transfer(EthernetClient &client)
Server(uint16_t port=STAR_OTA_DEFAULT_PORT)
void(*)(void *user_data, uint32_t received, uint32_t total) ProgressCallback
bool clientWaiting()
Is a client waiting?
void onStart(StartCallback cb, void *user_data=nullptr)
Called once the size header is in, before any flash write.
PortableServer server_
ProgressCallback on_progress_
Result fail(EthernetClient &client, Result r, const char *why)
void(*)(void *user_data, Result result) EndCallback
static const emmc_port_t port
Definition emmc_stm32.c:210
static void start(gnss_frame_parser_t *p, uint8_t byte)
Definition gnss_frame.c:45
void logf(bool verbose, const char *fmt,...)
Result
Outcome of one OTA attempt.
@ HeaderTimeout
client connected but never sent the 4-byte size
@ WriteFailed
flash write came up short
@ BadSize
announced size was zero or implausibly large
@ Success
image written; the board reboots unless told not to
@ BeginFailed
Update.begin() refused (no OTA partition / too big).
@ EndFailed
image received but failed validation
@ Idle
no client was waiting; nothing happened
@ DataTimeout
transfer stalled partway through
void printTestMessage()
Bench-test helper: prints STAR_OTA_TEST_MESSAGE on a timer as.
const char * resultName(Result r)
Human-readable form of a Result, for logs.
const char * testMessage()
The compiled-in test message ("" when none).
static boot_report_t r