MySensors Library & Examples  2.3.2-62-ge298769
PJONLocal.h
1 
2 /*-O//\ __ __
3  |-gfo\ |__| | | | |\ | ®
4  |!y°o:\ | __| |__| | \| 13.0
5  |y"s§+`\ multi-master, multi-media bus network protocol
6  /so+:-..`\ Copyright 2010-2020 by Giovanni Blu Mitolo [email protected]
7  |+/:ngr-*.`\
8  |5/:%&-a3f.:;\
9  \+//u/+g%{osv,,\
10  \=+&/osw+olds.\\
11  \:/+-.-°-:+oss\
12  | | \oy\\
13  > <
14 ______-| |-__________________________________________________________________
15 
16 PJONLocal implements a subset of the PJON protocol's features. It does support
17 only local mode and does not support packet queueing, infact it can transmit
18 only one packet at a time without any buffering, although it conserves
19 interoperability with other classes.
20 
21 This class has been developed to enable PJON networking on very limited
22 microcontrollers, like ATtiny45, ATtiny84 and ATtiny85, where even 1kB
23 of program memory and 100B of ram make a difference.
24 
25  For examples see examples/ARDUINO/Local/SoftwareBitBang/PJONLocal/
26 
27 Thanks to the support, expertise, kindness and talent of the following
28 contributors, the protocol's documentation, specification and implementation
29 have been strongly tested, enhanced and verified:
30 
31  Fred Larsen, Zbigniew Zasieczny, Matheus Garbelini, sticilface,
32  Felix Barbalet, Oleh Halitskiy, fotosettore, fabpolli, Adrian Sławiński,
33  Osman Selçuk Aktepe, Jorgen-VikingGod, drtrigon, Endre Karlson,
34  Wilfried Klaas, budaics, ibantxo, gonnavis, maxidroms83, Evgeny Dontsov,
35  zcattacz, Valerii Koval, Ivan Kravets, Esben Soeltoft, Alex Grishin,
36  Andrew Grande, Michael Teeww, Paolo Paolucci, per1234, Santiago Castro,
37  pacproduct, elusive-code, Emanuele Iannone, Christian Pointner,
38  Fabian Gärtner, Mauro Mombelli, Remo Kallio, hyndruide, sigmaeo, filogranaf,
39  Maximiliano Duarte, Viktor Szépe, Shachar Limor, Andrei Volkau, maniekq,
40  DetAtHome, Michael Branson, chestwood96, Mattze96 and Steven Bense,
41  Jack Anderson, callalilychen and Julio Aguirre.
42 
43 Compatible tools:
44 
45  - ModuleInterface - https://github.com/fredilarsen/ModuleInterface
46  - PJON-cython - https://github.com/xlfe/PJON-cython
47  - PJON-piper - https://github.com/Girgitt/PJON-piper
48  - PJON-python - https://github.com/Girgitt/PJON-python
49  - PJON-gRPC - https://github.com/Galitskiy/PJON-gRPC
50 _____________________________________________________________________________
51 
52 This software is experimental and it is distributed "AS IS" without any
53 warranty, use it at your own risk.
54 
55 Copyright 2010-2020 by Giovanni Blu Mitolo [email protected]
56 
57 Licensed under the Apache License, Version 2.0 (the "License");
58 you may not use this file except in compliance with the License.
59 You may obtain a copy of the License at
60 
61  http://www.apache.org/licenses/LICENSE-2.0
62 
63 Unless required by applicable law or agreed to in writing, software
64 distributed under the License is distributed on an "AS IS" BASIS,
65 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
66 See the License for the specific language governing permissions and
67 limitations under the License. */
68 
69 #pragma once
70 #define PJON_LOCAL
71 #include "interfaces/PJON_Interfaces.h"
72 #include "PJONDefines.h"
73 
74 template<typename Strategy>
75 class PJONLocal
76 {
77 public:
78  Strategy strategy;
79  uint8_t config = PJON_TX_INFO_BIT | PJON_ACK_REQ_BIT;
80 
81 #if(PJON_INCLUDE_PACKET_ID)
82  PJON_Packet_Record recent_packet_ids[PJON_MAX_RECENT_PACKET_IDS];
83 #endif
84 
85 #if(PJON_INCLUDE_PORT)
86  uint16_t port = PJON_BROADCAST;
87 #endif
88 
89  /* PJONLocal initialization with no parameters:
90  PJONLocal<SoftwareBitBang> bus; */
91 
92  PJONLocal() : strategy(Strategy())
93  {
94  _device_id = PJON_NOT_ASSIGNED;
95  set_default();
96  };
97 
98  /* PJONLocal initialization passing device id:
99  PJONLocal<SoftwareBitBang> bus(1); */
100 
101  PJONLocal(uint8_t device_id) : strategy(Strategy())
102  {
103  _device_id = device_id;
104  set_default();
105  };
106 
107  /* Begin function to be called after initialization: */
108 
109  void begin()
110  {
111  strategy.begin(_device_id);
112 #if(PJON_INCLUDE_ASYNC_ACK || PJON_INCLUDE_PACKET_ID)
113  _packet_id_seed = PJON_RANDOM(65535) + _device_id;
114 #endif
115  };
116 
117  /* Compose packet in PJON format: */
118 
119  uint16_t compose_packet(
120  const uint8_t id,
121  uint8_t *destination,
122  const void *source,
123  uint16_t length,
124  uint8_t header = PJON_NO_HEADER,
125  uint16_t packet_id = 0,
126  uint16_t rx_port = PJON_BROADCAST
127  )
128  {
129  PJON_Packet_Info info;
130  info.rx.id = id;
131  info.tx.id = _device_id;
132  info.header = (header == PJON_NO_HEADER) ? config : header;
133 #if(PJON_INCLUDE_PACKET_ID)
134  if(!packet_id && (info.header & PJON_PACKET_ID_BIT)) {
135  info.id = PJONTools::new_packet_id(_packet_id_seed++);
136  } else {
137  info.id = packet_id;
138  }
139 #else
140  (void)packet_id;
141 #endif
142 #if(PJON_INCLUDE_PORT)
143  info.port = (rx_port == PJON_BROADCAST) ? port : rx_port;
144 #else
145  (void)rx_port;
146 #endif
147  uint16_t l =
148  PJONTools::compose_packet(info, destination, source, length);
149  return l;
150  };
151 
152  /* Get device id: */
153 
154  uint8_t device_id() const
155  {
156  return _device_id;
157  };
158 
159  /* Returns a pointer to the start of the payload: */
160 
161  uint8_t *get_payload(uint8_t *buffer)
162  {
163  return buffer + (
164  PJONTools::packet_overhead(buffer[1]) -
165  PJONTools::crc_overhead(buffer[1])
166  );
167  }
168 
169  /* Calculate packet overhead: */
170 
171  uint8_t packet_overhead(uint8_t header = 0) const
172  {
173  return PJONTools::packet_overhead(header);
174  };
175 
176  /* Fill a PJON_Packet_Info struct with data parsing a packet: */
177 
178  void parse(const uint8_t *packet, PJON_Packet_Info &packet_info) const
179  {
180  PJONTools::parse_header(packet, packet_info);
181  };
182 
183  /* Try to receive data: */
184 
185  uint16_t receive(uint8_t *buffer, PJON_Packet_Info info)
186  {
187  uint16_t length = PJON_PACKET_MAX_LENGTH;
188  uint16_t batch_length = 0;
189  uint8_t overhead = 0;
190  bool extended_length = false;
191  for(uint16_t i = 0; i < length; i++) {
192  if(!batch_length) {
193  batch_length = strategy.receive_frame(buffer + i, length - i);
194  if(batch_length == PJON_FAIL || batch_length == 0) {
195  return 0;
196  }
197  }
198  batch_length--;
199 
200  if(!i && !_router)
201  if((buffer[i] != _device_id) && (buffer[i] != PJON_BROADCAST)) {
202  return 0;
203  }
204 
205  if(i == 1) {
206  if(
207  (buffer[1] & PJON_MODE_BIT) ||
208  (buffer[1] & PJON_MAC_BIT) || (
209  (buffer[0] == PJON_BROADCAST) && (buffer[1] & PJON_ACK_REQ_BIT)
210  ) || (
211  (buffer[1] & PJON_EXT_LEN_BIT) && !(buffer[1] & PJON_CRC_BIT)
212  ) || (
213  !PJON_INCLUDE_PACKET_ID && (buffer[1] & PJON_PACKET_ID_BIT)
214  )
215  ) {
216  return 0;
217  }
218  extended_length = buffer[i] & PJON_EXT_LEN_BIT;
219  overhead = packet_overhead(buffer[i]);
220  }
221 
222  if((i == 2) && !extended_length) {
223  length = buffer[i];
224  if((length < overhead) || (length >= PJON_PACKET_MAX_LENGTH)) {
225  return 0;
226  }
227  if((length > 15) && !(buffer[1] & PJON_CRC_BIT)) {
228  return PJON_BUSY;
229  }
230  }
231 
232  if((i == 3) && extended_length) {
233  length = (buffer[i - 1] << 8) | (buffer[i] & 0xFF);
234  if((length < overhead) || (length >= PJON_PACKET_MAX_LENGTH)) {
235  return 0;
236  }
237  if((length > 15) && !(buffer[1] & PJON_CRC_BIT)) {
238  return PJON_BUSY;
239  }
240  }
241  }
242 
243  if(
244  PJON_crc8::compute(buffer, 3 + extended_length) !=
245  buffer[3 + extended_length]
246  ) {
247  return 0;
248  }
249 
250  if(buffer[1] & PJON_CRC_BIT) {
251  if(
252  !PJON_crc32::compare(
253  PJON_crc32::compute(buffer, length - 4), buffer + (length - 4)
254  )
255  ) {
256  return 0;
257  }
258  } else if(PJON_crc8::compute(buffer, length - 1) != buffer[length - 1]) {
259  return 0;
260  }
261  if(buffer[1] & PJON_ACK_REQ_BIT && buffer[0] != PJON_BROADCAST)
262  if((_mode != PJON_SIMPLEX) && !_router) {
263  strategy.send_response(PJON_ACK);
264  }
265  parse(buffer, info);
266 #if(PJON_INCLUDE_PACKET_ID)
267  if(
268  (info.header & PJON_PACKET_ID_BIT) &&
269  known_packet_id(info) && !_router
270  ) {
271  return 0;
272  }
273 #endif
274 #if(PJON_INCLUDE_PORT)
275  if((port != PJON_BROADCAST) && (port != info.port)) {
276  return 0;
277  }
278 #endif
279  return length - overhead;
280  };
281 
282  /* Check if ready to send a packet: */
283 
284  bool ready_to_send()
285  {
286  if(
287  (
288  !_retries || (
289  (uint32_t)(PJON_MICROS() - _last_send) >
290  (uint32_t)(strategy.back_off(_retries))
291  )
292  )
293  ) {
294  return strategy.can_start();
295  }
296  return false;
297  };
298 
299  /* Transmit an already composed packet: */
300 
301  uint16_t send_packet(const uint8_t *payload, uint16_t length)
302  {
303  _last_send = PJON_MICROS();
304  strategy.send_frame((uint8_t *)payload, length);
305  if(
306  payload[0] == PJON_BROADCAST || !(payload[1] & PJON_ACK_REQ_BIT) ||
307  _mode == PJON_SIMPLEX
308  ) {
309  _retries = 0;
310  return PJON_ACK;
311  }
312  uint16_t response = strategy.receive_response();
313  if(response == PJON_ACK) {
314  _retries = 0;
315  return response;
316  }
317  if(_retries < strategy.get_max_attempts()) {
318  _retries++;
319  } else {
320  _retries = 0;
321  }
322  if(response == PJON_FAIL) {
323  return response;
324  } else {
325  return PJON_BUSY;
326  }
327  };
328 
329  /* Compose and transmit a packet passing its info as parameters: */
330 
331  uint16_t send_packet(
332  uint8_t id,
333  uint8_t *buffer,
334  const void *payload,
335  uint16_t length,
336  uint8_t header = PJON_NO_HEADER,
337  uint16_t packet_id = 0,
338  uint16_t rx_port = PJON_BROADCAST
339  )
340  {
341  if(!(length = compose_packet(
342  id, buffer, payload, length, header, packet_id, rx_port
343  ))) {
344  return PJON_FAIL;
345  }
346  return send_packet(buffer, length);
347  };
348 
349  /* In router mode, the receiver function can acknowledge
350  for selected receiver device ids for which the route is known */
351 
352  void send_acknowledge()
353  {
354  strategy.send_response(PJON_ACK);
355  };
356 
357  /* Set the config bit state: */
358 
359  void set_config_bit(bool new_state, uint8_t bit)
360  {
361  if(new_state) {
362  config |= bit;
363  } else {
364  config &= ~bit;
365  }
366  };
367 
368  /* Configure synchronous acknowledge presence:
369  TRUE: Send 8bits synchronous acknowledge when a packet is received
370  FALSE: Avoid acknowledge transmission */
371 
372  void set_acknowledge(bool state)
373  {
374  set_config_bit(state, PJON_ACK_REQ_BIT);
375  };
376 
377  /* Configure CRC selected for packet checking:
378  TRUE: CRC32
379  FALSE: CRC8 */
380 
381  void set_crc_32(bool state)
382  {
383  set_config_bit(state, PJON_CRC_BIT);
384  };
385 
386  /* Set communication mode:
387  Passing PJON_SIMPLEX communication is mono-directional
388  Padding PJON_HALF_DUPLEX communication is bi-directional */
389 
390  void set_communication_mode(uint8_t mode)
391  {
392  _mode = mode;
393  };
394 
395  /* Set default configuration: */
396 
397  void set_default()
398  {
399  _mode = PJON_HALF_DUPLEX;
400  };
401 
402  /* Set the device id passing a single byte (watch out to id collision): */
403 
404  void set_id(uint8_t id)
405  {
406  _device_id = id;
407  };
408 
409  /* Configure sender's information inclusion in the packet.
410  TRUE: sender's device id (+8bits overhead)
411  FALSE: No sender's device id inclusion (-8bits overhead)
412 
413  If you don't need the sender info disable the inclusion to reduce
414  overhead and obtain higher communication speed. */
415 
416  void include_sender_info(bool state)
417  {
418  set_config_bit(state, PJON_TX_INFO_BIT);
419  };
420 
421  /* Configure if device acts as a router:
422  TRUE: device receives messages only for its bus and device id
423  FALSE: receiver function is always called if data is received */
424 
425  void set_router(bool state)
426  {
427  _router = state;
428  };
429 
430 #if(PJON_INCLUDE_PACKET_ID)
431 
432  /* Check if the packet id and its transmitter info are already present in
433  buffer of recently received packets, if not add it to the buffer. */
434 
435  bool known_packet_id(PJON_Packet_Info info)
436  {
437  for(uint8_t i = 0; i < PJON_MAX_RECENT_PACKET_IDS; i++)
438  if(
439  info.id == recent_packet_ids[i].id &&
440  info.sender_id == recent_packet_ids[i].sender_id
441  ) {
442  return true;
443  }
444  save_packet_id(info);
445  return false;
446  };
447 
448  /* Save packet id in the buffer: */
449 
450  void save_packet_id(PJON_Packet_Info info)
451  {
452  for(uint8_t i = PJON_MAX_RECENT_PACKET_IDS - 1; i > 0; i--) {
453  recent_packet_ids[i] = recent_packet_ids[i - 1];
454  }
455  recent_packet_ids[0].id = info.id;
456  recent_packet_ids[0].header = info.header;
457  recent_packet_ids[0].sender_id = info.sender_id;
458  };
459 
460  /* Configure packet id presence:
461  TRUE: include packet id, FALSE: Avoid packet id inclusion */
462 
463  void set_packet_id(bool state)
464  {
465  set_config_bit(state, PJON_PACKET_ID_BIT);
466  };
467 
468 #endif
469 
470 #if(PJON_INCLUDE_PORT)
471 
472  /* Include the port:
473  p = 1-65535 -> Include 16 bits port id
474  p = 0 -> Avoid port id inclusion */
475 
476  void include_port(uint16_t p)
477  {
478  set_config_bit((p != 0) ? 1 : 0, PJON_PORT_BIT);
479  port = p;
480  };
481 
482 #endif
483 
484 
485 private:
486  uint32_t _last_send = 0;
487  uint8_t _mode;
488  uint16_t _packet_id_seed = 0;
489  bool _router = false;
490  uint8_t _retries = 0;
491 protected:
492  uint8_t _device_id;
493 };
config
Config file.
Definition: config.h:30
PJON_Packet_Info
Definition: PJONDefines.h:207
destination
uint8_t destination
8 bit - Id of destination node
Definition: MyMessage.h:336
PJON_Packet_Record
Definition: PJONDefines.h:186
PJONLocal
Definition: PJONLocal.h:75