AlteriomPainlessMesh v2.1.1
Easy ESP32/ESP8266 mesh networking with Alteriom IoT extensions
Loading...
Searching...
No Matches
painlessMeshSTA.h
Go to the documentation of this file.
1#ifndef _PAINLESS_MESH_STA_H_
2#define _PAINLESS_MESH_STA_H_
3
5
7
8#include <list>
9#include <map>
10
11typedef struct {
12 uint8_t bssid[6];
14 int8_t rssi;
16
17// Entry for tracking nodes where TCP connection failed
18typedef struct {
19 uint32_t nodeId;
20 uint32_t blockUntil; // millis() timestamp when this node can be retried
22
24 public:
25 Task task; // Station scanning for connections
26
27#ifdef ESP8266
28 Task asyncTask;
29#endif
30
33 uint16_t port, uint8_t channel, bool hidden);
34 void stationScan();
35 void scanComplete();
36 // The scan-done event has arrived (ESP32: on the core's network-event
37 // task). The result is read by scanComplete() from the station task,
38 // in the loop, where the radio may be reconfigured.
39 void scanDone();
40 void filterAPs();
41 void connectToAP();
42 // Move both Wi-Fi interfaces to a bridge-announced channel immediately.
43 bool followBridgeChannel(uint8_t targetChannel);
44 // A bridge has spoken, from this channel: its status message carries the
45 // channel it is on, which is its router's, and a message just received
46 // is live — unlike a cached tree, which can carry a root that has gone.
47 // That channel is home.
48 void noteRooted(uint8_t channel) {
49 everRooted = true;
51 homeStays = 0;
52 }
53 // The tree contains a root. Enough for the rules that ask whether this
54 // mesh ever had one; not enough to say which channel is home.
55 void noteEverRooted() { everRooted = true; }
56 // The station attempt requestIP() started is over: it got an address, or
57 // it was disconnected. The half-open guard judges only an attempt still
58 // in progress. Without this it judged a fresh association by the clock of
59 // an attempt made 109 s earlier — the node had followed the bridge to its
60 // channel in between — and dropped it, costing a scan interval.
62 // The station got an address: its link is up. Cleared once the drop
63 // callbacks have judged a disconnect, so they can tell a link that was
64 // up and went away from an attempt that never got that far.
65 void stationUp() {
66 stationLinkUp = true;
67 // A link again, so the immediate-rescan allowance is restored now
68 // rather than at the next scan. Without this a node that dropped
69 // twice inside one scan interval paid the full fifteen seconds for
70 // the second drop (#459).
71 rejoinScanned = false;
72 }
73 void stationDown() { stationLinkUp = false; }
74 bool stationLinkUp = false;
75 // The station link was closed by this node's own channel move a moment
76 // ago (followBridgeChannel() closes it and scans next). Not a loss.
77 bool droppedByMove() const {
78 return channelMovedAt != 0 && millis() - channelMovedAt < 5000;
79 }
80 // The next scan covers every channel. For a node whose uplink just went
81 // away in a mesh that should have a root: the AP it was on left for the
82 // bridge's channel, and so will whatever is still here.
84 // On the channel the bridge's status named: the bridge's AP is here.
85 bool atHome() const { return rootedChannel != 0 && channel == rootedChannel; }
86 // This one will call the connectToAP next in the task and should be used
87 // instead of connectToAP
89 task.yield([this]() { connectToAP(); });
90 }
91
92 // Scan every channel for the mesh SSID. With avoidChannel set, prefer a
93 // channel other than it: re-detection runs when the node's own partition
94 // has gone quiet, so the mesh on its current channel is the partition it
95 // is stranded in, not the one it is looking for.
96 // With routerSSID set, the mesh on the router's channel is preferred over
97 // every other: that is where a bridge, and so the mesh, lives.
98 static uint8_t scanForMeshChannel(TSTRING meshSSID, bool meshHidden,
99 uint8_t avoidChannel = 0,
100 TSTRING routerSSID = "");
101
102 // Check if channel re-synchronization is needed or in progress
106
107 // Get the number of consecutive empty scans
108 uint16_t getConsecutiveEmptyScans() const {
110 }
111
112 // Add a node to the TCP failure blocklist
113 // This prevents repeated connection attempts to nodes where TCP server is unresponsive
114 void blockNodeAfterTCPFailure(uint32_t nodeId, uint32_t blockDurationMs = 60000);
115
116 // Check if a node is currently blocked due to TCP failures
117 bool isNodeBlocked(uint32_t nodeId) const;
118
119 // Clean up expired entries from the blocklist
120 void cleanupBlocklist();
121
123 std::list<WiFi_AP_Record_t> lastAPs;
124
125 protected:
128 // init() sets these; until then the scan belongs to no mesh and no port.
129 painlessMesh *mesh = nullptr;
130 uint16_t port = 0;
131 uint8_t channel = 0;
132 bool hidden = false;
133 std::list<WiFi_AP_Record_t> aps;
134
135 void requestIP(WiFi_AP_Record_t &ap);
136
137 // Manually configure network and ip
138 bool manual = false;
140
141 // Track consecutive scans with no mesh nodes found (for channel re-detection)
143 // millis() of the last requestIP(), to tell a station that is still
144 // obtaining an address from one that associated and never got one.
146 // millis() of the last followBridgeChannel(), for droppedByMove().
147 uint32_t channelMovedAt = 0;
148 // Whether the current attempt's half-open association has already been
149 // dropped. WiFi.disconnect() on a station that has nothing to disconnect
150 // leaves the status where it was, and a guard that fired on every pass
151 // held a node out of the mesh for the rest of a test.
152 bool halfOpenDropped = false;
153 // Set when this task starts an async scan, cleared when its result is
154 // consumed. The scan-done event also fires for the synchronous scans
155 // channel re-detection runs, and consuming those results here found them
156 // already deleted, reported "wifi scan failed", and rescanned at once.
157 bool scanRequested = false;
158 // Whether this node has already scanned at once on losing its last
159 // connection. Cleared the moment it has one again, so the immediate
160 // rescan is one per outage and not a scan loop: a node that is out of
161 // the mesh and finds nothing still falls back to the interval below.
162 bool rejoinScanned = false;
163 // Set when the next station scan must cover every channel: the empty
164 // scans have piled up and the node is looking for the channel the mesh
165 // moved to. The re-detection is this task's ordinary asynchronous scan
166 // with the channel left open, not a synchronous all-channel scan run
167 // inline. The synchronous one held the main loop and the radio for four
168 // to seven seconds; measured on the rig, an ACK owed through the node
169 // during that window came back after the sender's eight-second budget
170 // and counted as a loss — one in every two or three soak runs.
171 bool redetectRequested = false;
172 // The scan in flight covers every channel (the fast re-detection of a
173 // node with nothing under its AP), or is one slice of a sliced hunt.
174 bool scanAllChannels = false;
175 uint8_t scanSlice = 0;
176 // A re-detection with stations under this node's AP is done a channel at
177 // a time: an all-channel scan takes the AP off its channel for two to
178 // three seconds, and an ESP8266 station does not survive that — on the
179 // rig the soak's sender ran one with the ESP8266 as its child and lost
180 // it for the rest of the test. A slice is one channel for 120 ms, then
181 // 1.5 s at home; the mesh APs seen on each channel are tallied, and the
182 // decision is taken on the own-channel scan that follows the last slice,
183 // by the same rules as the all-channel scan.
184 uint8_t huntChannel = 0;
185 bool huntPending = false;
186 std::map<uint8_t, size_t> huntCounts;
187 std::map<uint8_t, int8_t> huntRssi;
188 // Doubles the scan interval of a connected node that keeps finding the
189 // mesh only on its own channel while told the mesh has a root: a mesh
190 // that is simply rootless would otherwise cost every node a full
191 // all-channel scan every half interval, for as long as it stays so.
192 uint8_t orphanScanBackoff = 0;
193 // Re-detections in a row, while connected and rootless, that found the
194 // mesh only on this channel and nothing new on it. Two, and a leaf
195 // drops its station link to look for the root with an empty tree.
196 uint8_t orphanRedetects = 0;
197 // Whether this node's tree has ever contained a root since the mesh
198 // started. A leaf leaves a rootless partition to look for the root only
199 // if there was one: a mesh that never had a bridge is rootless by
200 // design, and its leaves dropping their links every other minute to look
201 // for what does not exist cost the soak its deliveries.
202 bool everRooted = false;
203 // The channel the mesh was on the last time this node's tree had a root:
204 // the bridge's channel, which is its router's. A rootless node that has
205 // one treats it as home. It does not leave home for a partition on
206 // another channel, and away from home it goes back as soon as it sees
207 // the mesh there, whatever the sizes: a bridge that has just been
208 // promoted is one AP on the router's channel, and it is the one to join.
209 // On the rig, the node the failover test sends from followed a two-node
210 // partition off the router's channel while the backup was being elected
211 // beside it, then stayed away because its new partition was bigger than
212 // the lone bridge, and never heard the bridge's status.
213 uint8_t rootedChannel = 0;
214 // Re-detections in a row, at home, that declined to follow a partition
215 // seen elsewhere. A bridge that moves for good (its router changed
216 // channel) would otherwise keep the old channel rootless forever, so
217 // after enough of them home is forgotten and the ordinary rules apply.
218 uint8_t homeStays = 0;
219 // The other channel a disconnected node saw a smaller partition on, at
220 // its last re-detection: it follows only if the same channel shows the
221 // mesh again a scan later — a straggler is gone by then, a bridge is
222 // not.
223 uint8_t pendingElsewhere = 0;
224 // Consecutive scans, while connected and unrooted in a mesh that should
225 // have a root, that found nodes this node has no route to: a partition,
226 // with the root on the other side. Two of them and the node joins it.
227 uint8_t partitionScans = 0;
228 // Empty scans before re-detecting the mesh channel. A disconnected or
229 // orphaned node scans every 0.5 * SCAN_INTERVAL = 15 s, so 2 is ~30 s —
230 // what the old comment promised while the value of 6 delivered 90 s, long
231 // enough for a bridge's followers to miss a 120 s gateway contract.
232 static const uint16_t EMPTY_SCAN_THRESHOLD = 2;
233
234 // TCP failure blocklist to prevent infinite retry loops
235 // Maps nodeId -> blockUntil timestamp (millis())
236 std::map<uint32_t, uint32_t> tcpFailureBlocklist;
237
238 // Threshold for detecting millis() rollover in time comparisons
239 // Using 2^30 (~12 days) as reasonable limit - any time difference larger is likely rollover
240 static constexpr int32_t MILLIS_ROLLOVER_THRESHOLD = (int32_t)(1U << 30);
241
243};
244
245#endif
boost::asio::ip::address IPAddress
Definition asynctcp.hpp:29
Definition painlessMeshSTA.h:23
void noteEverRooted()
Definition painlessMeshSTA.h:55
void init(painlessmesh::wifi::Mesh *pMesh, TSTRING ssid, TSTRING password, uint16_t port, uint8_t channel, bool hidden)
Definition painlessMeshSTA.cpp:25
static constexpr int32_t MILLIS_ROLLOVER_THRESHOLD
Definition painlessMeshSTA.h:240
void connectToAP()
Definition painlessMeshSTA.cpp:567
bool scanAllChannels
Definition painlessMeshSTA.h:174
bool droppedByMove() const
Definition painlessMeshSTA.h:77
uint8_t pendingElsewhere
Definition painlessMeshSTA.h:223
std::list< WiFi_AP_Record_t > lastAPs
Valid APs found during the last scan.
Definition painlessMeshSTA.h:123
void stationScan()
Definition painlessMeshSTA.cpp:69
bool redetectRequested
Definition painlessMeshSTA.h:171
std::map< uint8_t, int8_t > huntRssi
Definition painlessMeshSTA.h:187
bool followBridgeChannel(uint8_t targetChannel)
Definition painlessMeshSTA.cpp:854
bool stationLinkUp
Definition painlessMeshSTA.h:74
Task task
Definition painlessMeshSTA.h:25
IPAddress manualIP
Definition painlessMeshSTA.h:139
bool everRooted
Definition painlessMeshSTA.h:202
bool isNodeBlocked(uint32_t nodeId) const
Definition painlessMeshSTA.cpp:491
void scanDone()
Definition painlessMeshSTA.cpp:192
uint32_t channelMovedAt
Definition painlessMeshSTA.h:147
bool rejoinScanned
Definition painlessMeshSTA.h:162
void noteRooted(uint8_t channel)
Definition painlessMeshSTA.h:48
TSTRING ssid
Definition painlessMeshSTA.h:126
painlessMesh * mesh
Definition painlessMeshSTA.h:129
uint8_t orphanScanBackoff
Definition painlessMeshSTA.h:192
bool hidden
Definition painlessMeshSTA.h:132
uint8_t channel
Definition painlessMeshSTA.h:131
void stationAttemptOver()
Definition painlessMeshSTA.h:61
std::map< uint8_t, size_t > huntCounts
Definition painlessMeshSTA.h:186
std::map< uint32_t, uint32_t > tcpFailureBlocklist
Definition painlessMeshSTA.h:236
uint16_t getConsecutiveEmptyScans() const
Definition painlessMeshSTA.h:108
void scanComplete()
Definition painlessMeshSTA.cpp:203
uint16_t consecutiveEmptyScans
Definition painlessMeshSTA.h:142
uint8_t scanSlice
Definition painlessMeshSTA.h:175
void yieldConnectToAP()
Definition painlessMeshSTA.h:88
friend painlessMesh
Definition painlessMeshSTA.h:242
void stationUp()
Definition painlessMeshSTA.h:65
void redetectOnNextScan()
Definition painlessMeshSTA.h:83
uint8_t huntChannel
Definition painlessMeshSTA.h:184
bool isChannelResyncNeeded() const
Definition painlessMeshSTA.h:103
void requestIP(WiFi_AP_Record_t &ap)
Definition painlessMeshSTA.cpp:552
bool huntPending
Definition painlessMeshSTA.h:185
bool halfOpenDropped
Definition painlessMeshSTA.h:152
bool atHome() const
Definition painlessMeshSTA.h:85
TSTRING password
Definition painlessMeshSTA.h:127
void stationDown()
Definition painlessMeshSTA.h:73
uint16_t port
Definition painlessMeshSTA.h:130
uint8_t orphanRedetects
Definition painlessMeshSTA.h:196
std::list< WiFi_AP_Record_t > aps
Definition painlessMeshSTA.h:133
bool manual
Definition painlessMeshSTA.h:138
void cleanupBlocklist()
Definition painlessMeshSTA.cpp:506
static const uint16_t EMPTY_SCAN_THRESHOLD
Definition painlessMeshSTA.h:232
void filterAPs()
Definition painlessMeshSTA.cpp:525
bool scanRequested
Definition painlessMeshSTA.h:157
StationScan()
Definition painlessMeshSTA.h:31
uint32_t connectAttemptStarted
Definition painlessMeshSTA.h:145
uint8_t homeStays
Definition painlessMeshSTA.h:218
uint8_t partitionScans
Definition painlessMeshSTA.h:227
uint8_t rootedChannel
Definition painlessMeshSTA.h:213
void blockNodeAfterTCPFailure(uint32_t nodeId, uint32_t blockDurationMs=60000)
Definition painlessMeshSTA.cpp:483
static uint8_t scanForMeshChannel(TSTRING meshSSID, bool meshHidden, uint8_t avoidChannel=0, TSTRING routerSSID="")
Definition painlessMeshSTA.cpp:909
Definition wifi.hpp:36
String TSTRING
Definition configuration.hpp:132
Definition painlessMeshSTA.h:18
uint32_t blockUntil
Definition painlessMeshSTA.h:20
uint32_t nodeId
Definition painlessMeshSTA.h:19
Definition painlessMeshSTA.h:11
TSTRING ssid
Definition painlessMeshSTA.h:13
int8_t rssi
Definition painlessMeshSTA.h:14