STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
STAR_EthernetOTA.h
Go to the documentation of this file.
1/**
2 * STAR_EthernetOTA — firmware updates over Ethernet (W5500) for STAR boards.
3 *
4 * Every STAR board with a W5500 already listens for firmware over TCP; this
5 * library is the single implementation of that, so the sense boards, the
6 * actuator board, the stacklight, the encoder and the environmental tracker
7 * all speak the same protocol and cannot drift apart.
8 *
9 * Usage:
10 * #include <STAR_EthernetOTA.h>
11 * static StarOTA::Server ota; // default port 3232
12 *
13 * void setup() { ...Ethernet.begin(...)...; ota.begin(); }
14 * void loop() { ota.poll(); } // returns immediately if idle
15 *
16 * ``poll()`` only blocks once a client has actually connected, and then only
17 * for the duration of the transfer (or ``timeout_ms`` of silence). On success
18 * the board reboots into the new image and never returns from poll().
19 *
20 * Wire protocol (unchanged from the original hotfire_ota.h, so existing
21 * uploaders keep working):
22 *
23 * client -> board : [4-byte big-endian image size][raw firmware bytes]
24 * board -> client: "OK\r\n", then reboot
25 *
26 * Upload with ``firmware/tools/ota_upload.py``, with ``pio run -e ota -t
27 * upload`` in a project that declares an OTA env, or from the Test-GUI's
28 * OTA tab.
29 *
30 * Verifying an update took: every board prints its running image's SHA-256 at
31 * boot (``firmware_hash.h``) and reports it in BOARD_HEARTBEAT, so the hash of
32 * the .bin you uploaded should appear on the board a few seconds later. For a
33 * quicker eyeball check while bench-testing, build with
34 * ``-DSTAR_OTA_TEST_MESSAGE='"whatever"'`` and call ``printTestMessage()`` in
35 * loop(): the board prints that string on a timer, so consecutive uploads with
36 * different messages are visibly different on the serial monitor.
37 */
38
39#pragma once
40
41#include <Arduino.h>
42#include <Ethernet.h>
43
44/** TCP port the board listens on. */
45#ifndef STAR_OTA_DEFAULT_PORT
46#define STAR_OTA_DEFAULT_PORT 3232
47#endif
48
49/** Read buffer for the firmware stream. */
50#ifndef STAR_OTA_CHUNK_SIZE
51#define STAR_OTA_CHUNK_SIZE 4096
52#endif
53
54/** How long to wait for the header, or for more data mid-transfer. */
55#ifndef STAR_OTA_TIMEOUT_MS
56#define STAR_OTA_TIMEOUT_MS 10000
57#endif
58
59/** Sanity cap on the announced image size (2 MB). */
60#ifndef STAR_OTA_MAX_IMAGE_BYTES
61#define STAR_OTA_MAX_IMAGE_BYTES 0x200000
62#endif
63
64/**
65 * Optional bench-test marker. Build with
66 * -DSTAR_OTA_TEST_MESSAGE='"hello"' and call printTestMessage() from loop().
67 * Empty (the default) makes printTestMessage() a no-op.
68 */
69#ifndef STAR_OTA_TEST_MESSAGE
70#define STAR_OTA_TEST_MESSAGE ""
71#endif
72
73/** How often printTestMessage() prints, when a message is set. */
74#ifndef STAR_OTA_TEST_MESSAGE_INTERVAL_MS
75#define STAR_OTA_TEST_MESSAGE_INTERVAL_MS 2000
76#endif
77
78namespace StarOTA {
79
80/** Outcome of one OTA attempt. */
81enum class Result : uint8_t {
82 Idle, ///< no client was waiting; nothing happened
83 Success, ///< image written; the board reboots unless told not to
84 HeaderTimeout, ///< client connected but never sent the 4-byte size
85 BadSize, ///< announced size was zero or implausibly large
86 BeginFailed, ///< Update.begin() refused (no OTA partition / too big)
87 DataTimeout, ///< transfer stalled partway through
88 WriteFailed, ///< flash write came up short
89 EndFailed ///< image received but failed validation
90};
91
92/** Human-readable form of a Result, for logs. */
93const char* resultName(Result r);
94
95struct Config {
99 /// Reboot into the new image on success. Off is for tests only — the
100 /// running image has already been replaced, so the board is not itself
101 /// until it restarts.
102 bool reboot_on_success = true;
103 /// Print progress and errors to Serial.
104 bool verbose = true;
105};
106
107/**
108 * Some Arduino-ESP32 cores make Server::begin() pure virtual as
109 * begin(uint16_t); others use begin(). Official Ethernet 2.x EthernetServer
110 * only implements void begin(), so EthernetServer can be abstract on CI.
111 * Implementing begin(uint16_t) WITHOUT 'override' matches newer Server.h on
112 * CI while staying valid on older cores whose Server has no begin(uint16_t).
113 */
115public:
116 explicit PortableServer(uint16_t port) : EthernetServer(port) {
117 }
118 void begin(uint16_t port = 0) {
119 (void)port;
121 }
123};
124
125/**
126 * The OTA listener. One per board.
127 *
128 * Callbacks let a board react without this library having to know about it —
129 * the hotfire boards use onStart() to suspend DHCP lease renewal, so their
130 * address cannot move mid-flash.
131 */
132class Server {
133public:
134 using StartCallback = void (*)(void* user_data, uint32_t image_bytes);
135 using ProgressCallback = void (*)(void* user_data, uint32_t received,
136 uint32_t total);
137 using EndCallback = void (*)(void* user_data, Result result);
138
139 explicit Server(uint16_t port = STAR_OTA_DEFAULT_PORT);
140
141 /** Start listening. Call after Ethernet.begin(). */
142 void begin();
143
144 /** Is a client waiting? Cheap; safe to call every loop. */
145 bool clientWaiting();
146
147 /**
148 * Accept a waiting client and run the transfer to completion.
149 * Returns Idle immediately when no client is waiting, so this is the
150 * only call most boards need in loop().
151 */
152 Result poll();
153
154 /**
155 * Run the transfer on a client you accepted yourself.
156 *
157 * poll() is what you want; this exists for boards that own their own
158 * listening socket (and for the legacy hotfire_ota.h shim). Blocks for
159 * the duration of the transfer, replies "OK" and reboots on success.
160 */
162
163 /** Result of the last attempt that was not Idle. */
165 return last_result_;
166 }
167
168 /** Called once the size header is in, before any flash write. */
169 void onStart(StartCallback cb, void* user_data = nullptr);
170 /** Called every STAR_OTA_CHUNK_SIZE-ish bytes. */
171 void onProgress(ProgressCallback cb, void* user_data = nullptr);
172 /** Called on both success and failure, before any reboot. */
173 void onEnd(EndCallback cb, void* user_data = nullptr);
174
176
177private:
179 Result fail(EthernetClient& client, Result r, const char* why);
180 bool readSize(EthernetClient& client, uint32_t& size_out);
181
184
186 void* on_start_data_ = nullptr;
188 void* on_progress_data_ = nullptr;
190 void* on_end_data_ = nullptr;
191};
192
193/**
194 * Bench-test helper: prints STAR_OTA_TEST_MESSAGE on a timer as
195 *
196 * [OTA-MSG] <message>
197 *
198 * so that uploading a build with a different message is immediately visible
199 * on the serial monitor. The Test-GUI bakes this flag, uploads, and watches
200 * for the line to change — which is the cheapest end-to-end proof that an OTA
201 * actually landed. No-op when no message was compiled in, so it is safe to
202 * leave the call in loop() on flight builds.
203 */
204void printTestMessage();
205
206/** The compiled-in test message ("" when none). */
207const char* testMessage();
208
209} // namespace StarOTA
#define STAR_OTA_MAX_IMAGE_BYTES
Sanity cap on the announced image size (2 MB).
#define STAR_OTA_TIMEOUT_MS
How long to wait for the header, or for more data mid-transfer.
#define STAR_OTA_DEFAULT_PORT
STAR_EthernetOTA — firmware updates over Ethernet (W5500) for STAR boards.
EthernetServer(uint16_t)
Definition Ethernet.h:35
virtual void begin()
Definition Ethernet.h:37
Some Arduino-ESP32 cores make Server::begin() pure virtual as begin(uint16_t); others use begin().
void begin(uint16_t port=0)
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 lastResult() const
Result of the last attempt that was not Idle.
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
json_pointer result
Definition json.hpp:14064
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).
bool reboot_on_success
Reboot into the new image on success.
bool verbose
Print progress and errors to Serial.
static boot_report_t r