MySensors Library & Examples  2.3.2-62-ge298769
PJONVirtualBusRouter.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 PJONVirtualBusRouter has been contributed by Fred Larsen.
17 
18 This class adds functionality to the PJONSwitch, PJONRouter, PJONDynamicRouter
19 and potential future classes derived from them. This functionality allows a
20 switch or router to treat multiple attached buses with the same bus id as a
21 "virtual" bus, where devices can be placed anywhere independent of device id.
22 
23 It will start in promiscuous mode, delivering every packet to all attached
24 buses except the one where the packet comes from. As it learns by looking at
25 the sender ids of observed packets, it will deliver each packet only to the
26 attached bus where the receiver device can be found, increasing precision
27 and reducing traffic.
28 _____________________________________________________________________________
29 
30 This software is experimental and it is distributed "AS IS" without any
31 warranty, use it at your own risk.
32 
33 Copyright 2010-2020 by Giovanni Blu Mitolo [email protected]
34 
35 Licensed under the Apache License, Version 2.0 (the "License");
36 you may not use this file except in compliance with the License.
37 You may obtain a copy of the License at
38 
39  http://www.apache.org/licenses/LICENSE-2.0
40 
41 Unless required by applicable law or agreed to in writing, software
42 distributed under the License is distributed on an "AS IS" BASIS,
43 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
44 See the License for the specific language governing permissions and
45 limitations under the License. */
46 
47 #pragma once
48 #include "PJONDynamicRouter.h"
49 
50 // If none of your devices have a device id above a specific number,
51 // you can save some bytes of RAM by lowering this constant.
52 // The constant must be higher than the highest device id that
53 // can be encountered, and will limit the router to this range.
54 #ifndef PJON_VIRTUALBUS_MAX_DEVICES
55 #define PJON_VIRTUALBUS_MAX_DEVICES 255
56 #endif
57 
58 // After having observed a device on one bus it is associated with that bus
59 // and packets will not be forwarded to other buses.
60 // This setting can activate a timeout so that if there is no traffic from a
61 // device for a certain time, its position is cleared and packets will be
62 // forwared to all buses. This allows a device to be moved from behind one
63 // router to behind another without restarting routers. The value is in seconds.
64 #ifndef PJON_VIRTUALBUS_ROUTE_TIMEOUT_S
65 #define PJON_VIRTUALBUS_ROUTE_TIMEOUT_S 0
66 #endif
67 
68 template<class RouterClass = PJONSwitch>
69 class PJONVirtualBusRouter : public RouterClass
70 {
71 protected:
72  // The array position of one (any) of the parts of the virtual bus.
73  uint8_t virtual_bus = PJON_NOT_ASSIGNED;
74  uint8_t device_via_attached_bus[PJON_VIRTUALBUS_MAX_DEVICES];
75 #if (PJON_VIRTUALBUS_ROUTE_TIMEOUT != 0)
76  uint32_t device_last_activity_time[PJON_VIRTUALBUS_MAX_DEVICES];
77 #endif
78 
79  // Support for disabling ACK for devices with unknown location
80  bool unknown_device_location = false;
81 
82  void init_vbus()
83  {
84  for (uint8_t i=0; i<PJON_VIRTUALBUS_MAX_DEVICES; i++) {
85  device_via_attached_bus[i] = PJON_NOT_ASSIGNED;
86  }
87  }
88 
89  uint8_t find_vbus_with_device(const uint8_t *bus_id, const uint8_t device_id)
90  {
91  if (!is_vbus(bus_id) || device_id >= PJON_VIRTUALBUS_MAX_DEVICES) {
92  return PJON_NOT_ASSIGNED;
93  }
94 #if (PJON_VIRTUALBUS_ROUTE_TIMEOUT != 0)
95  // Forget the device location if no packet from it has been observed for some time
96  if ((uint32_t)(PJON_MILLIS() - device_last_activity_time[device_id])
97  > (uint32_t)PJON_VIRTUALBUS_ROUTE_TIMEOUT*1000) {
98  device_via_attached_bus[device_id] = PJON_NOT_ASSIGNED;
99  }
100 #endif
101  return device_via_attached_bus[device_id];
102  }
103 
104  bool is_vbus(const uint8_t bus_id[])
105  {
106  return virtual_bus < RouterClass::bus_count &&
107  memcmp(RouterClass::buses[virtual_bus]->tx.bus_id, bus_id, 4)==0;
108  }
109 
110  void register_device_on_vbus(const uint8_t device_id, const uint8_t attached_bus)
111  {
112  if (device_id < PJON_VIRTUALBUS_MAX_DEVICES) {
113 #ifdef DEBUG_PRINT
114  if (attached_bus != device_via_attached_bus[device_id]) {
115  Serial.print(F("Device "));
116  Serial.print(device_id);
117  Serial.print(F(" on bus "));
118  Serial.println(attached_bus);
119  }
120 #endif
121  device_via_attached_bus[device_id] = attached_bus;
122 #if (PJON_VIRTUALBUS_ROUTE_TIMEOUT != 0)
123  device_last_activity_time[device_id] = PJON_MILLIS();
124 #endif
125  }
126  }
127 
128  virtual void send_packet(const uint8_t *payload, const uint16_t length,
129  const uint8_t receiver_bus, const uint8_t sender_bus,
130  bool &ack_sent, const PJON_Packet_Info &packet_info)
131  {
132  // Override the base class send_packet to disable requesting and sending ACK
133  // if the receiver's location is not registered.
134  bool disable_ack = unknown_device_location && is_vbus(RouterClass::buses[receiver_bus]->tx.bus_id);
135  if (disable_ack) {
136  PJON_Packet_Info info = packet_info;
137  info.header &= ~PJON_ACK_REQ_BIT;
138 
139  bool disable_ack = true;
140  RouterClass::send_packet(payload, length, receiver_bus, sender_bus, disable_ack, info);
141 #ifdef DEBUG_PRINT_PACKETS
142  Serial.print(F("FORWARD NOACK "));
143  Serial.print(info.receiver_id);
144  Serial.print(F(" to bus "));
145  Serial.println(receiver_bus);
146 #endif
147  } else {
148  RouterClass::send_packet(payload, length, receiver_bus, sender_bus, ack_sent, packet_info);
149 #ifdef DEBUG_PRINT_PACKETS
150  Serial.print(F("FORWARD "));
151  Serial.print(packet_info.rx.id);
152  Serial.print(F(" to bus "));
153  Serial.println(receiver_bus);
154 #endif
155  }
156  }
157 
158  void handle_send_error(uint8_t code, uint8_t packet)
159  {
160  // Find out which device id does not receive
161  if (PJON_CONNECTION_LOST == code &&
162  is_vbus(RouterClass::buses[RouterClass::current_bus]->tx.bus_id) &&
163  (packet < PJON_MAX_PACKETS || PJON_MAX_PACKETS == 0)) {
164  PJON_Packet_Info info;
165 #if PJON_MAX_PACKETS == 0
166  RouterClass::buses[RouterClass::current_bus]->
167  parse((RouterClass::buses[RouterClass::current_bus]->data), info);
168 #else
169  RouterClass::buses[RouterClass::current_bus]->
170  parse((RouterClass::buses[RouterClass::current_bus]->packets[packet].content), info);
171 #endif
172  if (info.rx.id < PJON_VIRTUALBUS_MAX_DEVICES && is_vbus(info.rx.bus_id)) {
173  // Unregister the device if we got an error trying to deliver to the attached
174  // bus on which it is registered. This will step back from pointed delivery
175  // to duplication on all parts of the virtual bus for this device.
176  if (device_via_attached_bus[info.rx.id] == RouterClass::current_bus) {
177  device_via_attached_bus[info.rx.id] = PJON_NOT_ASSIGNED;
178 #ifdef DEBUG_PRINT
179  Serial.print("Unregister ");
180  Serial.print(info.rx.id);
181  Serial.print(" from bus ");
182  Serial.println(RouterClass::current_bus);
183 #endif
184  }
185  }
186  }
187  }
188 
189  virtual void dynamic_receiver_function(uint8_t *payload, uint16_t length,
190  const PJON_Packet_Info &packet_info)
191  {
192  // First register the sender device if it belongs to a virtual bus.
193  // If a packet is sent to this device later, it is possible to fall back
194  // from delivering a copy to each part of the virtual bus, to just
195  // delivering to the part where the device is actually present.
196  if (is_vbus(packet_info.tx.bus_id)) {
197  register_device_on_vbus(packet_info.tx.id, RouterClass::current_bus);
198  }
199 
200  // Search for the device on the virtual bus
201  uint8_t receiver_bus = find_vbus_with_device(packet_info.rx.bus_id, packet_info.rx.id);
202 
203  // If found on part of a virtual bus, do not deliver copies to others
204  if (receiver_bus != PJON_NOT_ASSIGNED) {
205  bool ack_sent = false;
206  if (receiver_bus != RouterClass::current_bus) {
207  RouterClass::forward_packet(payload, length, receiver_bus, RouterClass::current_bus, ack_sent,
208  packet_info);
209  }
210  } else {
211  unknown_device_location = true;
212  RouterClass::dynamic_receiver_function(payload, length, packet_info);
213  unknown_device_location = false;
214  }
215  }
216 
217  virtual void dynamic_error_function(uint8_t code, uint16_t data)
218  {
219  handle_send_error(code, data);
220  }
221 
222 public:
223  PJONVirtualBusRouter() : RouterClass()
224  {
225  init_vbus();
226  }
228  uint8_t bus_count,
229  PJONAny* const *buses,
230  uint8_t default_gateway = PJON_NOT_ASSIGNED)
231  : RouterClass(bus_count, buses, default_gateway)
232  {
233  init_vbus();
234  }
235 
236  /* Support multiple of the attached physical buses to have the same bus id,
237  / forming a "virtual bus" where devices can be in any part independent of
238  / device id. Specify the array position of one of the bus parts. */
239  void set_virtual_bus(uint8_t first_bus)
240  {
241  virtual_bus = first_bus;
242  }
243 };
244 
245 // Specialized class to simplify declaration when using 2 buses
246 template<class A, class B, class RouterClass = PJONSwitch>
247 class PJONVirtualBusRouter2 : public PJONVirtualBusRouter<RouterClass>
248 {
249  StrategyLink<A> linkA;
250  StrategyLink<B> linkB;
251  PJONAny busA, busB;
252 public:
253  PJONVirtualBusRouter2(uint8_t default_gateway = PJON_NOT_ASSIGNED)
254  {
255  PJON<Any>* buses[2] = { &busA, &busB };
256  PJONSimpleSwitch<Any>::connect_buses(2, buses, default_gateway);
257  busA.set_link(&linkA);
258  busB.set_link(&linkB);
259  };
260 
261  PJONAny &get_bus(const uint8_t ix)
262  {
263  return ix == 0 ? busA : busB;
264  }
265 
266  A &get_strategy_0()
267  {
268  return linkA.strategy;
269  }
270  B &get_strategy_1()
271  {
272  return linkB.strategy;
273  }
274 };
275 
276 // Specialized class to simplify declaration when using 3 buses
277 template<class A, class B, class C, class RouterClass = PJONSwitch>
278 class PJONVirtualBusRouter3 : public PJONVirtualBusRouter<RouterClass>
279 {
280  StrategyLink<A> linkA;
281  StrategyLink<B> linkB;
282  StrategyLink<C> linkC;
283  PJONAny busA, busB, busC;
284 public:
285  PJONVirtualBusRouter3(uint8_t default_gateway = PJON_NOT_ASSIGNED)
286  {
287  PJON<Any>* buses[3] = { &busA, &busB, &busC };
288  PJONSimpleSwitch<Any>::connect_buses(3, buses, default_gateway);
289  busA.set_link(&linkA);
290  busB.set_link(&linkB);
291  busC.set_link(&linkC);
292  };
293 
294  PJONAny &get_bus(const uint8_t ix)
295  {
296  return ix == 0 ? busA : (ix == 1 ? busB : busC);
297  }
298 
299  A &get_strategy_0()
300  {
301  return linkA.strategy;
302  }
303  B &get_strategy_1()
304  {
305  return linkB.strategy;
306  }
307  C &get_strategy_2()
308  {
309  return linkC.strategy;
310  }
311 };
data
char data[MAX_PAYLOAD_SIZE+1]
Buffer for raw payload data.
Definition: MyMessage.h:654
PJON_Packet_Info
Definition: PJONDefines.h:207
PJONAny
Definition: PJONSwitch.h:44
PJON< Any >
PJONVirtualBusRouter
Definition: PJONVirtualBusRouter.h:69
PJONVirtualBusRouter3
Definition: PJONVirtualBusRouter.h:278
PJONSimpleSwitch
Definition: PJONSimpleSwitch.h:63
PJONVirtualBusRouter2
Definition: PJONVirtualBusRouter.h:247