MySensors Library & Examples  2.3.2-62-ge298769
ESPNOW.h
1 
2 /* ESPNOW is a Strategy for the PJON framework.
3  It supports delivering PJON packets through the Espressif ESPNOW 802.11
4  protocol to a registered list of devices on the 802.11 ESPNOW network. Each
5  device must be registered with its device id and mac address.
6  ___________________________________________________________________________
7 
8  ESPNOW strategy proposed and developed by xlfe in September 2018
9 
10  Licensed under the Apache License, Version 2.0 (the "License");
11  you may not use this file except in compliance with the License.
12  You may obtain a copy of the License at
13 
14  http://www.apache.org/licenses/LICENSE-2.0
15 
16  Unless required by applicable law or agreed to in writing, software
17  distributed under the License is distributed on an "AS IS" BASIS,
18  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19  See the License for the specific language governing permissions and
20  limitations under the License. */
21 
22 #pragma once
23 #include "../../interfaces/ARDUINO/ESPNOWHelper.h"
24 
25 // Timeout waiting for an ACK. This can be increased if the latency is high.
26 #ifndef EN_RESPONSE_TIMEOUT
27 #define EN_RESPONSE_TIMEOUT 100000ul
28 #endif
29 
30 #ifndef EN_MAX_REMOTE_NODES
31 #define EN_MAX_REMOTE_NODES 10
32 #endif
33 
34 // Recommended receive time for this strategy, in microseconds
35 #ifndef EN_RECEIVE_TIME
36 #define EN_RECEIVE_TIME 0
37 #endif
38 
39 #define EN_MAGIC_HEADER (uint8_t*)"\xEE\xFE\x0E\xEF"
40 
41 class ESPNOW
42 {
43  bool _espnow_initialised = false;
44  bool _auto_registration = true;
45  uint8_t _channel = 14;
46  char _espnow_pmk[17] =
47  "\xdd\xdb\xdd\x44\x34\xd5\x6a\x0b\x7e\x9f\x4e\x27\xd6\x5b\xa2\x81";
48 
49  // Remote nodes
50  uint8_t _remote_node_count = 0;
51  uint8_t _remote_id[EN_MAX_REMOTE_NODES];
52  uint8_t _remote_mac[EN_MAX_REMOTE_NODES][ESP_NOW_ETH_ALEN];
53 
54  ENHelper en;
55 
56  bool check_en()
57  {
58  if(!_espnow_initialised) {
59  en.set_magic_header(EN_MAGIC_HEADER);
60  if(en.begin(_channel,(uint8_t*)_espnow_pmk)) {
61  _espnow_initialised = true;
62  }
63  }
64  return _espnow_initialised;
65  };
66 
67  int16_t find_remote_node(uint8_t id, uint8_t* mac)
68  {
69  for(uint8_t i = 0; i < _remote_node_count; i++)
70  if(
71  ((_remote_id[i] == id) && (id != PJON_NOT_ASSIGNED)) ||
72  (memcmp(_remote_mac, mac, ESP_NOW_ETH_ALEN) == 0)
73  ) {
74  return i;
75  }
76  return -1;
77  };
78 
79  void autoregister_sender(const uint8_t *message, uint16_t length)
80  {
81  // Add the last sender to the node table
82  if(_auto_registration && length>4) {
83  // First get PJON sender id from incoming packet
84  PJON_Packet_Info packet_info;
85  PJONTools::parse_header(message, packet_info);
86  uint8_t sender_id = packet_info.tx.id;
87  if(sender_id == 0) {
88  ESP_LOGE("ESPNOW", "AutoRegister parsing failed");
89  return; // If parsing fails, it will be 0
90  }
91 
92  // Then get the mac address of the sender
93  uint8_t sender_mac[ESP_NOW_ETH_ALEN];
94  en.get_sender(sender_mac);
95 
96  // See if PJON id is already registered, add if not
97  int16_t pos = find_remote_node(sender_id, sender_mac);
98  if(pos == -1) {
99  ESP_LOGI("ESPNOW", "Autoregister new sender %d",sender_id);
100  add_node(sender_id, sender_mac);
101  } else if(memcmp(_remote_mac[pos], sender_mac, ESP_NOW_ETH_ALEN) == 0) {
102  // Update mac of existing node
103  ESP_LOGI(
104  "ESPNOW",
105  "Update sender sender_id(%d) [%02X:%02X:%02X:%02X:%02X:%02X]",
106  sender_id,
107  sender_mac[0],
108  sender_mac[1],
109  sender_mac[2],
110  sender_mac[3],
111  sender_mac[4],
112  sender_mac[5]
113  );
114  memcpy(_remote_mac[pos], sender_mac, ESP_NOW_ETH_ALEN);
115  }
116  }
117  };
118 
119 public:
120 
121  void set_pmk(char *espnow_pmk)
122  {
123  memcpy(_espnow_pmk, espnow_pmk, 16);
124  }
125 
126  void set_channel(uint8_t channel)
127  {
128  _channel = channel;
129  }
130 
131  /* Register each device we want to send to */
132 
133  int16_t add_node(
134  uint8_t remote_id,
135  uint8_t remote_mac[]
136  )
137  {
138  if(_remote_node_count == EN_MAX_REMOTE_NODES) {
139  return -1;
140  }
141  _remote_id[_remote_node_count] = remote_id;
142  memcpy(_remote_mac[_remote_node_count], remote_mac, ESP_NOW_ETH_ALEN);
143  en.add_node_mac(remote_mac);
144  _remote_node_count++;
145  return _remote_node_count - 1;
146  };
147 
148 
149  /* Set if each packet's sender is automatically added as a node */
150 
151  void set_autoregistration(bool enabled)
152  {
153  _auto_registration = enabled;
154  };
155 
156 
157  /* Returns the suggested delay related to attempts passed as parameter: */
158 
159  uint32_t back_off(uint8_t attempts)
160  {
161  return 10000ul * attempts + PJON_RANDOM(10000);
162  };
163 
164 
165  /* Begin method, to be called on initialization:
166  (returns always true) */
167 
168  bool begin(uint8_t /*did*/ = 0)
169  {
170  return check_en();
171  };
172 
173 
174  /* Check if the channel is free for transmission */
175 
176  bool can_start()
177  {
178  return check_en();
179  };
180 
181 
182  /* Returns the maximum number of attempts for each transmission: */
183 
184  static uint8_t get_max_attempts()
185  {
186  return 10;
187  };
188 
189 
190  /* Returns the recommended receive time for this strategy: */
191 
192  static uint16_t get_receive_time()
193  {
194  return EN_RECEIVE_TIME;
195  };
196 
197 
198  /* Handle a collision (empty because handled on Ethernet level): */
199 
200  void handle_collision() { };
201 
202 
203  /* Receive a frame: */
204 
205  uint16_t receive_frame(uint8_t *data, uint16_t max_length)
206  {
207  uint16_t length = en.receive_frame(data, max_length);
208  if(length != PJON_FAIL) {
209  autoregister_sender(data, length);
210  }
211  return length;
212  }
213 
214 
215  /* Receive byte response */
216 
217  uint16_t receive_response()
218  {
219  /* TODO: Improve robustness by ignoring packets not from the previous
220  receiver (Perhaps not that important as long as ACK/NAK responses are
221  directed, not broadcast) */
222  uint32_t start = PJON_MICROS();
223  uint8_t result[PJON_PACKET_MAX_LENGTH];
224  uint16_t reply_length = 0;
225  do {
226  reply_length = receive_frame(result, sizeof result);
227  // We expect 1, if packet is larger it is not our ACK
228  if(reply_length == 1)
229  if(result[0] == PJON_ACK) {
230  return result[0];
231  }
232 
233  } while ((uint32_t)(PJON_MICROS() - start) < EN_RESPONSE_TIMEOUT);
234  return PJON_FAIL;
235  };
236 
237 
238  /* Send byte response to package transmitter.
239  We have the IP so we can reply directly. */
240 
241  void send_response(uint8_t response) // Empty, PJON_ACK is always sent
242  {
243  en.send_response(response);
244  };
245 
246 
247  /* Send a frame: */
248 
249  void send_frame(uint8_t *data, uint16_t length)
250  {
251  if(length > 0) {
252  uint8_t id = data[0]; // Package always starts with a receiver id
253  PJON_Packet_Info packet_info; // use parser to get intended recipient MAC
254  PJONTools::parse_header(data, packet_info);
255  if(id == 0) { // Broadcast, send to all receivers
256  en.send_frame(data, length);
257  } else { // To a specific receiver
258  int16_t pos = find_remote_node(id, packet_info.rx.mac);
259  if(pos != -1) {
260  en.send_frame(data, length, _remote_mac[pos]);
261  } else { //Broadcast - any replies will get registered
262  en.send_frame(data, length);
263  }
264  }
265  }
266  };
267 };
data
char data[MAX_PAYLOAD_SIZE+1]
Buffer for raw payload data.
Definition: MyMessage.h:654
PJON_Packet_Info
Definition: PJONDefines.h:207
ESPNOW
Definition: ESPNOW.h:41
ENHelper
Definition: ESPNOWHelper.h:98