STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
board_net.h
Go to the documentation of this file.
1/**
2 * Board networking — the ground station decides addresses, not the board.
3 *
4 * Enabled per board with -DSENSOR_ETH_USE_DHCP (every hotfire board today).
5 *
6 * 1. boot: DHCP DISCOVER. The DAQ server / Test-GUI answers from its
7 * MAC -> IP reservation table, so the address a board gets is decided in
8 * one place, by people, and is the same every boot. The board takes its
9 * ID from that address (192.168.2.31 -> board 31), so every board of a
10 * type runs the same image: which board is which is the reservation
11 * table's `mac =` lines, not a build flag.
12 * 2. renew the lease as it expires (suppressed during an OTA — see
13 * beginOtaHold()).
14 * 3. the board announces itself by broadcasting BOARD_HEARTBEAT until some
15 * server talks to it, and learns the server's address from that packet.
16 *
17 * When no DHCP server answers, the board takes its RECOVERY address,
18 * 169.254.<1 + mac[4] % 254>.<mac[5]>/16, and keeps asking for a lease every
19 * SENSOR_ETH_DHCP_RETRY_INTERVAL_MS. The recovery address exists so a board
20 * whose DHCP failed (server down, booted before the switch) can still be
21 * reflashed over Ethernet: `ota_upload.py --mac <mac>` computes the same
22 * address. It is never a working address — the DAQ does not talk to a board
23 * there, and the board's ID is 0 until a lease arrives.
24 *
25 * Why not the old static 192.168.2.<BOARD_ID> fallback: it needed a per-board
26 * build, and a board that missed DHCP looked exactly like one that got it, so
27 * DHCP could be broken for weeks unnoticed. Why derive from the MAC: it is
28 * burned into the ESP32, unique, and survives any flash erase; two bytes of
29 * it give 64k addresses, so collisions are checkable in advance (the config
30 * editor refuses two boards whose recovery addresses match).
31 *
32 * No ESP32 dependencies beyond Arduino/Ethernet, so it is unit-testable
33 * off-target — see Hotfire_Tests/test/test_board_net.
34 */
35
36#pragma once
37
38#include <Arduino.h>
39#include <Ethernet.h>
40
41#include "hotfire_config.h"
42
43namespace BoardNet {
44
45/** How the board ended up with the address it is using. */
46enum class AddressSource : uint8_t {
47 None, ///< not yet brought up
48 Dhcp, ///< assigned by the ground station — the normal case
49 Recovery ///< no DHCP answer: 169.254.x.y from the MAC, reflash-only
50};
51
52inline const char* sourceName(AddressSource s) {
53 switch (s) {
55 return "none";
57 return "DHCP (assigned by the server)";
59 return "recovery (no DHCP server answered; reflash only)";
60 }
61 return "unknown";
62}
63
64/** Subnet of the recovery address: link-local, 169.254.0.0/16. */
66 return IPAddress(255, 255, 0, 0);
67}
68
69/**
70 * The board's recovery address, from its MAC: 169.254.<1 + mac[4] %
71 * 254>.<mac[5]>.
72 *
73 * The third octet stays in 1..254, the range RFC 3927 lets a host use.
74 * firmware/tools/ota_upload.py and daq-server's config validation compute the
75 * same thing; test_board_net pins the formula with the vectors they test.
76 */
77inline IPAddress recoveryAddress(const byte mac[6]) {
78 return IPAddress(169, 254, 1 + (mac[4] % 254), mac[5]);
79}
80
81struct State {
82 // --- set once by configure(), at setup() ---
83 byte mac[6] = {0, 0, 0, 0, 0, 0};
84
85 // --- runtime ---
87 bool serverLearned = false; ///< server IP adopted from an inbound packet
88 bool otaHold = false; ///< suppress lease renewal during a flash
89 unsigned long lastServerPacketMillis = 0;
90 unsigned long lastDhcpAttemptMillis = 0;
91};
92
93inline void configure(State& s, const byte mac[6]) {
94 memcpy(s.mac, mac, 6);
95}
96
97/** Print the MAC as aa:bb:cc:dd:ee:ff.
98 *
99 * The ground station keys its reservation table on this, so it has to be
100 * readable off the serial log — otherwise registering a new board means
101 * guessing, or sniffing its DHCP request.
102 */
103inline void printMac(const State& s) {
104 Serial.print("MAC: ");
105 for (int i = 0; i < 6; i++) {
106 if (s.mac[i] < 0x10)
107 Serial.print("0");
108 Serial.print(s.mac[i], HEX);
109 if (i < 5)
110 Serial.print(":");
111 }
112 Serial.println();
113 Serial.flush();
114}
115
116inline bool usingDhcp(const State& s) {
117 return s.source == AddressSource::Dhcp;
118}
119
120inline bool hasAddress(const State& s) {
121 return s.source != AddressSource::None;
122}
123
124/**
125 * The board's ID: the last octet of the address the server assigned
126 * (192.168.2.31 -> 31). 0 while on the recovery address, where the board has
127 * no identity the server gave it.
128 */
129inline uint8_t boardId(const State& s) {
130 return usingDhcp(s) ? Ethernet.localIP()[3] : 0;
131}
132
133/**
134 * Renew the lease as it expires. No-op when we are not on a lease, and
135 * deliberately a no-op during an OTA: a renewal that moved our address
136 * mid-flash would drop the transfer.
137 */
138inline void maintainLease(State& s) {
139 if (usingDhcp(s) && !s.otaHold)
140 Ethernet.maintain();
141}
142
143/** Called when an OTA transfer starts, so the address cannot move under it. */
144inline void beginOtaHold(State& s) {
145 s.otaHold = true;
146}
147
148/**
149 * A server packet arrived from `remote_ip`; adopt it as the server address.
150 * Returns true if `serverIP` changed.
151 */
152inline bool onServerPacket(State& s, IPAddress remote_ip, IPAddress& serverIP) {
154 if (!s.serverLearned || !(remote_ip == serverIP)) {
155 serverIP = remote_ip;
156 s.serverLearned = true;
157 Serial.print("[NET] server learned: ");
158 Serial.println(remote_ip);
159 Serial.flush();
160 return true;
161 }
162 return false;
163}
164
165/** Server has stopped talking — forget it and resume discovery broadcasts. */
170
176
177/**
178 * Bring the interface up: ask the ground station for an address, and take the
179 * recovery address only if nothing answers. Returns how we ended up addressed.
180 */
182 Serial.println("[NET] requesting an address by DHCP...");
183 Serial.flush();
188 Serial.print("[NET] server assigned us ");
189 Serial.println(Ethernet.localIP());
190 Serial.flush();
191 return s.source;
192 }
193
195 Serial.print("[NET] no DHCP server answered -- on the recovery address ");
196 Serial.print(recoveryAddress(s.mac));
197 Serial.println(" (reflash only); asking again every few seconds.");
198 Serial.flush();
199 return s.source;
200}
201
202/**
203 * On the recovery address, ask for a lease again every
204 * SENSOR_ETH_DHCP_RETRY_INTERVAL_MS. Call every loop(); a no-op on a lease and
205 * during an OTA. Returns true when a lease arrived: the address and boardId()
206 * changed, so the caller should re-announce itself.
207 *
208 * Each attempt blocks for up to SENSOR_ETH_DHCP_RETRY_TIMEOUT_MS. That is only
209 * ever spent on the recovery address, where the DAQ cannot reach the board, so
210 * it can never stall a session. Open sockets survive: a new address does not
211 * close them, and the W5500 is not reset.
212 */
215 return false;
217 return false;
222 Serial.print("[NET] DHCP answered at last -- server assigned us ");
223 Serial.println(Ethernet.localIP());
224 Serial.flush();
225 return true;
226 }
227 // A failed attempt leaves the chip at 0.0.0.0; put the recovery address
228 // back so the board stays reflashable.
230 return false;
231}
232
233} // namespace BoardNet
static SerialStub Serial
Definition Arduino.h:128
unsigned long millis()
Definition Arduino.h:29
#define HEX
Definition Arduino.h:24
static EthernetClass Ethernet
Definition Ethernet.h:116
const int IPAddress
IPAddress dns(192, 168, 2, 1)
byte mac[6]
Definition main.cpp:31
IPAddress serverIP(192, 168, 2, 20)
IPAddress gateway(0, 0, 0, 0)
#define SENSOR_ETH_DHCP_RETRY_TIMEOUT_MS
#define SENSOR_ZEROCONF_SERVER_SILENCE_MS
#define SENSOR_ETH_DHCP_RETRY_INTERVAL_MS
#define SENSOR_ETH_DHCP_RESPONSE_TIMEOUT_MS
#define SENSOR_ETH_DHCP_TIMEOUT_MS
*brief append another JSON pointer at the end of this JSON pointer *sa ptr reference_tokens begin()
Board networking — the ground station decides addresses, not the board.
Definition board_net.h:43
void configure(State &s, const byte mac[6])
Definition board_net.h:93
void printMac(const State &s)
Print the MAC as aa:bb:cc:dd:ee:ff.
Definition board_net.h:103
IPAddress recoveryAddress(const byte mac[6])
The board's recovery address, from its MAC: 169.254.
Definition board_net.h:77
void useRecoveryAddress(State &s, IPAddress dns, IPAddress gateway)
Definition board_net.h:171
uint8_t boardId(const State &s)
The board's ID: the last octet of the address the server assigned (192.168.2.31 -> 31).
Definition board_net.h:129
void beginOtaHold(State &s)
Called when an OTA transfer starts, so the address cannot move under it.
Definition board_net.h:144
bool retryDhcp(State &s, IPAddress dns, IPAddress gateway)
On the recovery address, ask for a lease again every SENSOR_ETH_DHCP_RETRY_INTERVAL_MS.
Definition board_net.h:213
bool usingDhcp(const State &s)
Definition board_net.h:116
bool serverWentSilent(const State &s)
Server has stopped talking — forget it and resume discovery broadcasts.
Definition board_net.h:166
IPAddress recoverySubnet()
Subnet of the recovery address: link-local, 169.254.0.0/16.
Definition board_net.h:65
AddressSource
How the board ended up with the address it is using.
Definition board_net.h:46
@ None
not yet brought up
Definition board_net.h:47
@ Recovery
no DHCP answer: 169.254.x.y from the MAC, reflash-only
Definition board_net.h:49
@ Dhcp
assigned by the ground station — the normal case
Definition board_net.h:48
bool onServerPacket(State &s, IPAddress remote_ip, IPAddress &serverIP)
A server packet arrived from remote_ip; adopt it as the server address.
Definition board_net.h:152
void maintainLease(State &s)
Renew the lease as it expires.
Definition board_net.h:138
bool hasAddress(const State &s)
Definition board_net.h:120
const char * sourceName(AddressSource s)
Definition board_net.h:52
bool otaHold
suppress lease renewal during a flash
Definition board_net.h:88
bool serverLearned
server IP adopted from an inbound packet
Definition board_net.h:87
unsigned long lastServerPacketMillis
Definition board_net.h:89
AddressSource source
Definition board_net.h:86
unsigned long lastDhcpAttemptMillis
Definition board_net.h:90