MySensors Library & Examples  2.3.2-62-ge298769
DualUDP.h
1 
2 /* DualUDP is a Strategy for the PJON framework.
3  It supports delivering PJON packets through Ethernet UDP to a registered
4  list of devices on the LAN, WAN or Internet. Each device must be registered
5  with its device id, IP address and listening port number.
6 
7  Autopopulating the node table based on received packets and broadcasts is
8  enabled by default. This requires only remote (non-LAN) devices to be
9  manually registered.
10 
11  So this strategy combines the manually populated node list from the
12  GlobalUDP strategy with the broadcast based autodiscovery of devices
13  on the LAN in the LocalUDP strategy. If improves on LocalUDP by
14  stepping from UDP broadcasts to directed UDP packets, creating less
15  noise for other devices that may be listening to the same port.
16  It can communicate with devices outside the LAN if they are added manually
17  to the list, or if they send a packet to this device first.
18 
19  Note that this strategy cannot send and receive any contents, only
20  packets with a valid PJON header.
21  ___________________________________________________________________________
22 
23  DualUDP strategy proposed and developed by Fred Larsen 01/12/2018
24 
25  Licensed under the Apache License, Version 2.0 (the "License");
26  you may not use this file except in compliance with the License.
27  You may obtain a copy of the License at
28 
29  http://www.apache.org/licenses/LICENSE-2.0
30 
31  Unless required by applicable law or agreed to in writing, software
32  distributed under the License is distributed on an "AS IS" BASIS,
33  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
34  See the License for the specific language governing permissions and
35  limitations under the License. */
36 
37 #pragma once
38 
39 #ifdef HAS_ETHERNETUDP
40 #include <interfaces/ARDUINO/UDPHelper_ARDUINO.h>
41 #else
42 #include <interfaces/LINUX/UDPHelper_POSIX.h>
43 #endif
44 
45 #include <PJONDefines.h>
46 
47 // Timeout waiting for an ACK. This can be increased if the latency is high
48 #ifndef DUDP_RESPONSE_TIMEOUT
49 #define DUDP_RESPONSE_TIMEOUT 50000ul
50 #endif
51 
52 // Minimum time interval in ms between send attempts. Some devices go into
53 // contention if sending too fast. This can be overridden in an interface
54 // for a device type, or in user sketches.
55 #ifndef DUDP_MINIMUM_SEND_INTERVAL_MS
56 #define DUDP_MINIMUM_SEND_INTERVAL_MS 8
57 #endif
58 
59 // Backoff function that can be overridden depending on network and devices
60 #ifndef DUDP_BACKOFF
61 #define DUDP_BACKOFF(attempts) (1000ul * attempts + PJON_RANDOM(500))
62 #endif
63 
64 // Max number of retries
65 #ifndef DUDP_MAX_RETRIES
66 #define DUDP_MAX_RETRIES 5
67 #endif
68 
69 // The size of the node table
70 #ifndef DUDP_MAX_REMOTE_NODES
71 #define DUDP_MAX_REMOTE_NODES 10
72 #endif
73 
74 // Remove automatically registered nodes after this number of send failures
75 #ifndef DUDP_MAX_FAILURES
76 #define DUDP_MAX_FAILURES 10
77 #endif
78 
79 #define DUDP_DEFAULT_PORT 7500
80 #define DUDP_MAGIC_HEADER (uint32_t) 0x0EFA23FF
81 
82 // Recommended receive time for this strategy, in microseconds
83 #ifndef DUDP_RECEIVE_TIME
84 #define DUDP_RECEIVE_TIME 0
85 #endif
86 
87 //#define DUDP_DEBUG_PRINT
88 
89 class DualUDP
90 {
91  bool _udp_initialized = false;
92  bool _auto_registration = true, // Add all sender devices to node table
93  _auto_discovery = true, // Use UDP broadcast to locate LAN devices
94  _did_broadcast = false; // Whether last send was a broadcast
95 
96  uint16_t _port = DUDP_DEFAULT_PORT;
97  uint8_t _unremovable_node_count = PJON_NOT_ASSIGNED;
98 
99  // Remember the details of the last outgoing packet
100  uint8_t _last_out_receiver_id = 0;
101  uint8_t _last_out_sender_id = 0;
102  uint32_t _last_out_time = 0;
103 
104  // Remember the details of the last incoming packet
105  PJON_Packet_Info _packet_info; // Also used for last outgoing
106  uint16_t _last_in_sender_port = 0;
107  uint8_t _last_in_sender_ip[4];
108  uint8_t _last_in_receiver_id = 0;
109  uint8_t _last_in_sender_id = 0;
110 
111  // Remote nodes table
112  uint8_t _remote_node_count = 0;
113  uint8_t _remote_id[DUDP_MAX_REMOTE_NODES];
114  uint8_t _remote_ip[DUDP_MAX_REMOTE_NODES][4];
115  uint16_t _remote_port[DUDP_MAX_REMOTE_NODES];
116  uint8_t _send_attempts[DUDP_MAX_REMOTE_NODES];
117 
118  UDPHelper udp;
119 
120  bool check_udp()
121  {
122  if(!_udp_initialized) {
123  udp.set_magic_header(htonl(DUDP_MAGIC_HEADER));
124  if(udp.begin(_port)) {
125  _udp_initialized = true;
126  }
127  }
128  return _udp_initialized;
129  };
130 
131  int16_t find_remote_node(uint8_t id)
132  {
133  for(uint8_t i = 0; i < _remote_node_count; i++)
134  if(_remote_id[i] == id) {
135  return i;
136  }
137  return -1;
138  };
139 
140  int16_t autoregister_sender()
141  {
142  // Add the last sender to the node table
143  if(_auto_registration) {
144  // If parsing fails, it will be 0
145  if( _last_in_sender_id == 0) {
146  return -1;
147  }
148  // See if PJON id is already registered, add if not
149  int16_t pos = find_remote_node( _last_in_sender_id);
150  if(pos == -1)
151  return add_node(
152  _last_in_sender_id,
153  _last_in_sender_ip,
154  _last_in_sender_port
155  );
156  else { // Update IP and port of existing node
157  memcpy(_remote_ip[pos], _last_in_sender_ip, 4);
158  _remote_port[pos] = _last_in_sender_port;
159  return pos;
160  }
161  }
162  return -1;
163  };
164 
165 public:
166 
167  /* Register each device we want to send to.
168  If device id is set and autoregistration enabled, this table will
169  be filled automatically with devices that send to this device.
170  Devices that this device will send to must be registered if they are
171  outside the LAN and do not send a packet to this device first.
172  Devices on this LAN do not need to be manually registered. */
173 
174  int16_t add_node(
175  uint8_t remote_id,
176  const uint8_t remote_ip[],
177  uint16_t port_number = DUDP_DEFAULT_PORT
178  )
179  {
180  if(_remote_node_count == DUDP_MAX_REMOTE_NODES) {
181  return -1;
182  }
183  // Remember how many nodes were present at startup
184  if(_unremovable_node_count == PJON_NOT_ASSIGNED) {
185  _unremovable_node_count = _remote_node_count;
186  }
187 #ifdef DUDP_DEBUG_PRINT
188  Serial.print("Register id ");
189  Serial.print(remote_id);
190  Serial.print(" ip ");
191  Serial.println(remote_ip[3]);
192 #endif
193  // Add the new node
194  _remote_id[_remote_node_count] = remote_id;
195  memcpy(_remote_ip[_remote_node_count], remote_ip, 4);
196  _remote_port[_remote_node_count] = port_number;
197  _send_attempts[_remote_node_count] = 0;
198  _remote_node_count++;
199  return _remote_node_count - 1;
200  };
201 
202  /* Unregister a node, if unreachable */
203 
204  bool remove_node(uint8_t pos)
205  {
206  // Only allow the automatically added nodes to be removed
207  if(pos < _unremovable_node_count) {
208  return false;
209  }
210 #ifdef DUDP_DEBUG_PRINT
211  Serial.print("Unregistering id ");
212  Serial.println(_remote_id[pos]);
213 #endif
214  for(uint8_t i = pos; i < _remote_node_count - 1; i++) {
215  _remote_id[i] = _remote_id[i + 1];
216  memcpy(_remote_ip[i], _remote_ip[i + 1], 4);
217  _remote_port[i] = _remote_port[i + 1];
218  _send_attempts[i] = _send_attempts[i + 1];
219  }
220  _remote_node_count--;
221  return true;
222  };
223 
224  /* Whether the last send was a broadcast or a directed packet.
225  This is to let routers and sketches have a little insight. */
226 
227  bool did_broadcast()
228  {
229  return _did_broadcast;
230  };
231 
232  /* Select if incoming packets should automatically add their sender
233  as a node */
234 
235  void set_autoregistration(bool enabled = true)
236  {
237  _auto_registration = enabled;
238  };
239 
240  /* Select if broadcast shall be used to reach unregistered devices
241  and then add them to the node table when replying, going from
242  broadcast to directed communication as soon as possible. */
243 
244  void set_autodiscovery(bool enabled = true)
245  {
246  _auto_discovery = enabled;
247  };
248 
249  /* Returns the suggested delay related to attempts passed as parameter: */
250 
251  uint32_t back_off(uint8_t attempts)
252  {
253  return DUDP_BACKOFF(attempts);
254  };
255 
256  /* Begin method, to be called on initialization:
257  (returns always true) */
258 
259  bool begin(uint8_t device_id)
260  {
261  (void)device_id; // Avoid "unused parameter" warning
262  return check_udp();
263  };
264 
265  /* Check if the channel is free for transmission */
266 
267  bool can_start()
268  {
269  return check_udp() && ((uint32_t)(PJON_MILLIS() - _last_out_time) >=
270  DUDP_MINIMUM_SEND_INTERVAL_MS);
271  };
272 
273  /* Returns the maximum number of attempts for each transmission: */
274 
275  static uint8_t get_max_attempts()
276  {
277  return DUDP_MAX_RETRIES;
278  };
279 
280  /* Returns the recommended receive time for this strategy: */
281 
282  static uint16_t get_receive_time()
283  {
284  return DUDP_RECEIVE_TIME;
285  };
286 
287  /* Handle a collision (empty because handled on Ethernet level): */
288 
289  void handle_collision() { };
290 
291  /* Receive a frame: */
292 
293  uint16_t receive_frame(uint8_t *data, uint16_t max_length)
294  {
295  uint16_t length = udp.receive_frame(data, max_length);
296  // Then get the IP address and port number of the sender
297  udp.get_sender( _last_in_sender_ip, _last_in_sender_port);
298  if(length != PJON_FAIL && length > 4) {
299  // Extract some info from the header
300  PJONTools::parse_header(data, _packet_info);
301  _last_in_receiver_id = _packet_info.rx.id;
302  _last_in_sender_id = _packet_info.tx.id;
303  // Autoregister sender if the packet was sent directly
304  if(
305  _packet_info.tx.id != PJON_NOT_ASSIGNED &&
306  _last_out_sender_id != PJON_NOT_ASSIGNED &&
307  _packet_info.rx.id == _last_out_sender_id
308  ) {
309  autoregister_sender();
310  }
311  }
312  return length;
313  };
314 
315  /* Receive byte response */
316 
317  uint16_t receive_response()
318  {
319  uint32_t start = PJON_MICROS();
320  uint8_t result[10];
321  uint16_t reply_length = 0;
322  do {
323  reply_length = receive_frame(result, sizeof result);
324  if(reply_length == PJON_FAIL) {
325  continue;
326  }
327 
328  // Ignore full PJON packets, we expect only a tiny response packet
329  if(reply_length != 3) {
330  continue;
331  }
332 
333  // Decode response packet
334  _last_in_receiver_id = result[0];
335  _last_in_sender_id = result[1];
336  uint8_t code = result[2];
337 
338  // Ignore packets not responding to the last outgoing packet
339  if(_last_in_receiver_id != _last_out_sender_id) {
340  continue;
341  }
342 
343  // Ignore packets not from the receiver of the last outgoing packet
344  // 20181205: NO, allow these ACKS even if they are delayed,
345  // because it could be caused by forwarding multiple hops,
346  // and a delayed ACK is still a confirmation of the correct route.
347  //if(_last_in_sender_id != _last_out_receiver_id) continue;
348 
349  if(code == PJON_ACK) {
350  // Autoregister sender of ACK
351  int16_t pos = autoregister_sender();
352  // Reset send attempt counter
353  if(pos != -1) {
354  _send_attempts[pos] = 0;
355  }
356  return code;
357  }
358  } while((uint32_t)(PJON_MICROS() - start) < DUDP_RESPONSE_TIMEOUT);
359 #ifdef DUDP_DEBUG_PRINT
360  Serial.println("Receive_response FAILED");
361 #endif
362  return PJON_FAIL;
363  };
364 
365  /* Send byte response to package transmitter.
366  We have the IP so we can reply directly.
367  Use the receiver id of the last incoming packet instead of the id of
368  this device, to function also in router mode. */
369 
370  void send_response(uint8_t response) // Empty, PJON_ACK is always sent
371  {
372  uint8_t buf[3];
373  buf[0] = _last_in_sender_id; // Send to the device last received from
374  buf[1] = _last_in_receiver_id; // Send from the id last received to
375  buf[2] = response;
376  udp.send_response(buf, 3);
377  };
378 
379  /* Send a frame: */
380 
381  void send_frame(uint8_t *data, uint16_t length)
382  {
383  _did_broadcast = false;
384  if(length > 4) {
385  // Extract some info from the header
386  PJONTools::parse_header(data, _packet_info);
387  _last_out_receiver_id = _packet_info.rx.id;
388  _last_out_sender_id = _packet_info.tx.id;
389 
390  // Locate receiver in table unless it is a PJON broadcast (receiver 0)
391  int16_t pos = -1;
392  if(_last_out_receiver_id != 0) {
393  pos = find_remote_node(_last_out_receiver_id);
394  }
395 
396  // Check if receiver is not responding and should be unregistered
397  if(
398  pos != -1 &&
399  (_send_attempts[pos] > (get_max_attempts() * DUDP_MAX_FAILURES)) &&
400  remove_node((uint8_t)pos)
401  ) {
402  pos = -1;
403  }
404 
405  if(pos == -1) { // UDP Broadcast, send to all receivers
406  if(_auto_discovery) {
407  udp.send_frame(data, length);
408  }
409  _did_broadcast = true;
410 #ifdef DUDP_DEBUG_PRINT
411  Serial.print("Broadcast, id ");
412  Serial.println(_last_out_receiver_id);
413 #endif
414  } else { // To a specific IP+port
415  udp.send_frame(data, length, _remote_ip[pos], _remote_port[pos]);
416  _send_attempts[pos]++;
417  }
418  _last_out_time = PJON_MILLIS();
419  }
420  };
421 
422  /* Set the UDP port: */
423 
424  void set_port(uint16_t port = DUDP_DEFAULT_PORT)
425  {
426  _port = port;
427  };
428 };
data
char data[MAX_PAYLOAD_SIZE+1]
Buffer for raw payload data.
Definition: MyMessage.h:654
DualUDP
Definition: DualUDP.h:89
PJON_Packet_Info
Definition: PJONDefines.h:207
UDPHelper
Definition: UDPHelper_ARDUINO.h:21