-
Notifications
You must be signed in to change notification settings - Fork 239
/
GlobalUDP.h
230 lines (169 loc) · 6.83 KB
/
GlobalUDP.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
/* EthernetUDP is a Strategy for the PJON framework.
It supports delivering PJON packets through Ethernet UDP to a registered list
of devices on the LAN, WAN or Internet. Each device must be registered with
its device id, IP address and listening port number.
___________________________________________________________________________
EthernetUDP strategy proposed and developed by Fred Larsen 01/09/2017
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. */
#pragma once
#ifdef HAS_ETHERNETUDP
#include "../../interfaces/ARDUINO/UDPHelper_ARDUINO.h"
#else
#include "../../interfaces/LINUX/UDPHelper_POSIX.h"
#endif
#include <PJONDefines.h>
// Timeout waiting for an ACK. This can be increased if the latency is high.
#ifndef GUDP_RESPONSE_TIMEOUT
#define GUDP_RESPONSE_TIMEOUT 100000ul
#endif
#ifndef GUDP_MAX_REMOTE_NODES
#define GUDP_MAX_REMOTE_NODES 10
#endif
// Recommended receive time for this strategy, in microseconds
#ifndef GUDP_RECEIVE_TIME
#define GUDP_RECEIVE_TIME 0
#endif
#define GUDP_DEFAULT_PORT 7000
#define GUDP_MAGIC_HEADER (uint32_t) 0x0DFAC3FF
class GlobalUDP {
bool _udp_initialized = false;
uint16_t _port = GUDP_DEFAULT_PORT;
bool _auto_registration = true;
// Remote nodes
uint8_t _remote_node_count = 0;
uint8_t _remote_id[GUDP_MAX_REMOTE_NODES];
uint8_t _remote_ip[GUDP_MAX_REMOTE_NODES][4];
uint16_t _remote_port[GUDP_MAX_REMOTE_NODES];
UDPHelper udp;
bool check_udp() {
if(!_udp_initialized) {
udp.set_magic_header(htonl(GUDP_MAGIC_HEADER));
if (udp.begin(_port)) _udp_initialized = true;
}
return _udp_initialized;
};
int16_t find_remote_node(uint8_t id) {
for(uint8_t i = 0; i < _remote_node_count; i++)
if(_remote_id[i] == id)
return i;
return -1;
};
void autoregister_sender(const uint8_t *message, uint16_t length) {
// Add the last sender to the node table
if (_auto_registration && length>4) {
// First get PJON sender id from incoming packet
PJON_Packet_Info packet_info;
PJONTools::parse_header(message, packet_info);
uint8_t sender_id = packet_info.tx.id;
if (sender_id == 0) return; // If parsing fails, it will be 0
// Then get the IP address and port number of the sender
uint8_t sender_ip[4];
uint16_t sender_port;
udp.get_sender(sender_ip, sender_port);
// See if PJON id is already registered, add if not
int16_t pos = find_remote_node(sender_id);
if (pos == -1) add_node(sender_id, sender_ip, sender_port);
else {
// Update IP and port of existing node
memcpy(_remote_ip[pos], sender_ip, 4);
_remote_port[pos] = sender_port;
}
}
}
public:
/* Register each device we want to send to */
int16_t add_node(
uint8_t remote_id,
const uint8_t remote_ip[],
uint16_t port_number = GUDP_DEFAULT_PORT
) {
if (_remote_node_count == GUDP_MAX_REMOTE_NODES) return -1;
_remote_id[_remote_node_count] = remote_id;
memcpy(_remote_ip[_remote_node_count], remote_ip, 4);
_remote_port[_remote_node_count] = port_number;
_remote_node_count++;
return _remote_node_count - 1;
};
/* Select if incoming packets should automatically add their sender as a node */
void set_autoregistration(bool enabled) {
_auto_registration = enabled;
}
/* Returns the suggested delay related to attempts passed as parameter: */
uint32_t back_off(uint8_t attempts) {
#ifdef PJON_ESP
return 10000ul * attempts + PJON_RANDOM(10000);
#elif _WIN32
(void)attempts; // Avoid "unused parameter" warning
return 1000ul + PJON_RANDOM(1000);
#else
(void)attempts; // Avoid "unused parameter" warning
return 1;
#endif
};
/* Begin method, to be called on initialization:
(returns always true) */
bool begin(uint8_t /*did*/ = 0) { return check_udp(); };
/* Check if the channel is free for transmission */
bool can_start() { return check_udp(); };
/* Returns the maximum number of attempts for each transmission: */
static uint8_t get_max_attempts() { return 10; };
/* Returns the recommended receive time for this strategy: */
static uint16_t get_receive_time() { return GUDP_RECEIVE_TIME; };
/* Handle a collision (empty because handled on Ethernet level): */
void handle_collision() { };
/* Receive a frame: */
uint16_t receive_frame(uint8_t *data, uint16_t max_length) {
uint16_t length = udp.receive_frame(data, max_length);
if (length != PJON_FAIL) autoregister_sender(data, length);
return length;
}
/* Receive byte response */
uint16_t receive_response() {
/* TODO: Improve robustness by ignoring packets not from the previous
receiver (Perhaps not that important as long as ACK/NAK responses are
directed, not broadcast) */
uint32_t start = PJON_MICROS();
uint8_t result[8];
uint16_t reply_length = 0;
do {
reply_length = receive_frame(result, sizeof result);
// We expect 1, if packet is larger it is not our ACK
if(reply_length == 1)
if (result[0] == PJON_ACK)
return result[0];
} while ((uint32_t)(PJON_MICROS() - start) < GUDP_RESPONSE_TIMEOUT);
return PJON_FAIL;
};
/* Send byte response to package transmitter.
We have the IP so we can reply directly. */
void send_response(uint8_t response) { // Empty, PJON_ACK is always sent
udp.send_response(response);
};
/* Send a frame: */
void send_frame(uint8_t *data, uint16_t length) {
if(length > 0) {
uint8_t id = data[0]; // Package always starts with a receiver id
if (id == 0) { // Broadcast, send to all receivers
for(uint8_t pos = 0; pos < _remote_node_count; pos++)
udp.send_frame(data, length, _remote_ip[pos], _remote_port[pos]);
} else { // To a specific receiver
int16_t pos = find_remote_node(id);
if (pos != -1) {
udp.send_frame(data, length, _remote_ip[pos], _remote_port[pos]);
}
}
}
};
/* Set the UDP port: */
void set_port(uint16_t port = GUDP_DEFAULT_PORT) {
_port = port;
};
};