Skip to content

Commit 5285d89

Browse files
Update README.md
Added instructions for the example
1 parent f0d704c commit 5285d89

File tree

1 file changed

+348
-0
lines changed

1 file changed

+348
-0
lines changed

README.md

Lines changed: 348 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,357 @@
22

33
This example shows how to implement BACnet Virtual Devices with BBMD functionality using the CAS BACnet Stack.
44

5+
## CAS BACnet Stack
6+
57
This example was written using the CAS BACnet Stack version 5.1.6
68

79
Please contact Chipkin Automation Systems to purchase the CAS BACnet Stack
810

911
More information about the CAS BACnet Stack can be found here: [CAS BACnet Stack](https://store.chipkin.com/services/stacks/bacnet-stack)
1012

13+
## Implementation Notes
14+
15+
The following sections provided code-snippets from the example with instructions on how to implement each portion.
16+
17+
### 1. Loading the CAS BACnet Stack API, Connecting to UDP Resource, and Registering Callbacks
18+
19+
#### 1.1 Load the CAS BACnet Stack functions
20+
21+
Call the CASBACnetStackAdapter function to LoadBACnetFunctions. This will setup all the CAS BACnet Stack API functions to be linked and ready to use whether binding them from a static or dynamic library or linking directly to source code. Printing out the version number of the CAS BACnet Stack is a good indicator that the library functions were properly linked. Resolve any linking errors before proceeding.
22+
23+
```cpp
24+
// Lines 80-88 in BACnetVirtualDevicesBBMDExcampleCPP
25+
// 1. Load the CAS BACnet stack functions
26+
// ---------------------------------------------------------------------------
27+
std::cout << "FYI: Loading CAS BACnet Stack functions... ";
28+
if (!LoadBACnetFunctions()) {
29+
std::cerr << "Failed to load the functions from the DLL" << std::endl;
30+
return 0;
31+
}
32+
std::cout << "OK" << std::endl;
33+
std::cout << "FYI: CAS BACnet Stack version: " << fpGetAPIMajorVersion() << "." << fpGetAPIMinorVersion() << "." << fpGetAPIPatchVersion() << "." << fpGetAPIBuildVersion() << std::endl;
34+
```
35+
#### 1.2 Connect the UDP resource to the BACnet Port
36+
37+
This example uses a simple implementation of a UDP resource, SimpleUDP. Connect to the UDP resource which binds the port. This example supports the NetworkPort object, so the port that is specified is stored in the NetworkPort object, which is set to 47808 by default.
38+
39+
``` cpp
40+
// Lines 90-99 in BACnetVirtualDevicesBBMDExcampleCPP
41+
// 2. Connect the UDP resource to the BACnet Port
42+
// ---------------------------------------------------------------------------
43+
std::cout << "FYI: Connecting UDP Resource to port=[" << g_database.networkPort.BACnetIPUDPPort << "]... ";
44+
if (!g_udp.Connect(g_database.networkPort.BACnetIPUDPPort)) {
45+
std::cerr << "Failed to connect to UDP Resource" << std::endl;
46+
std::cerr << "Press any key to exit the application..." << std::endl;
47+
(void)getchar();
48+
return -1;
49+
}
50+
std::cout << "OK, Connected to port" << std::endl;
51+
```
52+
53+
#### 1.3 Setup the Callbacks
54+
55+
Register any required callbacks with the CAS BACnet Stack. Review the implemented callbacks in the example. This example uses a minimal amount of objects and properties so only a few of the callbacks are implemented.
56+
57+
``` cpp
58+
// Lines 101-117 in BACnetVirtualDevicesBBMDExcampleCPP
59+
// 3. Setup the callbacks
60+
// ---------------------------------------------------------------------------
61+
std::cout << "FYI: Registering the callback Functions with the CAS BACnet Stack" << std::endl;
62+
63+
// Message Callback Functions
64+
fpRegisterCallbackReceiveMessage(CallbackReceiveMessage);
65+
fpRegisterCallbackSendMessage(CallbackSendMessage);
66+
67+
// System Time Callback Functions
68+
fpRegisterCallbackGetSystemTime(CallbackGetSystemTime);
69+
70+
// Get Property Callback Functions
71+
fpRegisterCallbackGetPropertyCharacterString(CallbackGetPropertyCharString);
72+
fpRegisterCallbackGetPropertyEnumerated(CallbackGetPropertyEnum);
73+
fpRegisterCallbackGetPropertyOctetString(CallbackGetPropertyOctetString);
74+
fpRegisterCallbackGetPropertyReal(CallbackGetPropertyReal);
75+
fpRegisterCallbackGetPropertyUnsignedInteger(CallbackGetPropertyUInt);
76+
```
77+
78+
### 2. Setting up the Main Device
79+
80+
The Main Device represents the main interface to the BACnet IP network in this example. It acts as the virtual router and will eventually be setup to be a BBMD later in the example. In this example, the Main Device has only a NetworkPort object.
81+
82+
#### 2.1 Create the Main Device and enable any required BACnet Services
83+
84+
Create the Main Device using the fpAddDevice API function. Then enable the BACnet Services that this device supports. In this example, the Main Device will only support a minimal amount of services.
85+
86+
```cpp
87+
// Lines 119-154 in BACnetVirtualDevicesBBMDExcampleCPP
88+
// 4. Setup the BACnet device
89+
// ---------------------------------------------------------------------------
90+
91+
std::cout << "Setting up main server device. device.instance=[" << g_database.mainDevice.instance << "]" << std::endl;
92+
93+
// Create the Main Device
94+
if (!fpAddDevice(g_database.mainDevice.instance)) {
95+
std::cerr << "Failed to add Device." << std::endl;
96+
return false;
97+
}
98+
std::cout << "Created Device." << std::endl;
99+
100+
// Enable the services that this device supports
101+
// Some services are mandatory for BACnet devices and are already enabled.
102+
// These are: Read Property, Who Is, Who Has
103+
//
104+
// Any other services need to be enabled as below.
105+
std::cout << "Enabling IAm... ";
106+
if (!fpSetServiceEnabled(g_database.mainDevice.instance, ExampleConstants::SERVICE_I_AM, true)) {
107+
std::cerr << "Failed to enable the IAm" << std::endl;
108+
return -1;
109+
}
110+
std::cout << "OK" << std::endl;
111+
112+
std::cout << "Enabling ReadPropertyMultiple... ";
113+
if (!fpSetServiceEnabled(g_database.mainDevice.instance, ExampleConstants::SERVICE_READ_PROPERTY_MULTIPLE, true)) {
114+
std::cerr << "Failed to enable the ReadPropertyMultiple" << std::endl;
115+
return -1;
116+
}
117+
std::cout << "OK" << std::endl;
118+
119+
// Enable Optional Device Properties
120+
if (!fpSetPropertyEnabled(g_database.mainDevice.instance, ExampleConstants::OBJECT_TYPE_DEVICE, g_database.mainDevice.instance, ExampleConstants::PROPERTY_IDENTIFIER_DESCRIPTION, true)) {
121+
std::cerr << "Failed to enable the description property for the Main Device" << std::endl;
122+
return false;
123+
}
124+
```
125+
126+
#### 2.2 Add Main Device Objects
127+
128+
As mentioned above, in this example the Main Device only contains the NetworkPort object. This NetworkPort object contains the information that is used by the Main Device to communicate on the BACnet IP network. All properties pertaining to the NetworkPort object can be seen in the various CallbackGetProperty{{datatype}} functions. For example: IP_Address property is in the CallbackGetPropertyOctetString.
129+
130+
```cpp
131+
// Lines 156-165 in BACnetVirtualDevicesBBMDExcampleCPP
132+
// Add Main Device Objects
133+
// ---------------------------------------
134+
135+
// Add the Network Port Object
136+
std::cout << "Adding NetworkPort. networkPort.instance=[" << g_database.networkPort.instance << "]... ";
137+
if (!fpAddNetworkPortObject(g_database.mainDevice.instance, g_database.networkPort.instance, ExampleConstants::NETWORK_TYPE_IPV4, ExampleConstants::PROTOCOL_LEVEL_BACNET_APPLICATION, ExampleConstants::NETWORK_PORT_LOWEST_PROTOCOL_LAYER)) {
138+
std::cerr << "Failed to add NetworkPort" << std::endl;
139+
return -1;
140+
}
141+
std::cout << "OK" << std::endl;
142+
```
143+
144+
### 3. Setting up Virtual Devices
145+
146+
Add the Virtual Devices and any objects and services supported by the Virtual Devices. In this example, each Virtual Device contains just one AnalogInput Object.
147+
148+
```cpp
149+
// Lines 167-213 in BACnetVirtualDevicesBBMDExcampleCPP
150+
// Add Virtual Devices and Objects
151+
std::cout << "Adding Virtual Devices and Objects..." << std::endl;
152+
std::map<uint16_t, std::vector<ExampleDatabaseDevice> >::iterator it;
153+
for (it = g_database.virtualDevices.begin(); it != g_database.virtualDevices.end(); ++it) {
154+
// Add the Virtual network
155+
if (!fpAddVirtualNetwork(g_database.mainDevice.instance, it->first, it->first)) {
156+
std::cerr << "Failed to add virtual network " << it->first << std::endl;
157+
return -1;
158+
}
159+
160+
std::vector<ExampleDatabaseDevice>::iterator devIt;
161+
for (devIt = it->second.begin(); devIt != it->second.end(); ++devIt) {
162+
// Add the Virtual Device
163+
std::cout << "Adding Virtual Device. device.instance=[" << devIt->instance << "] to network=[" << it->first << "]...";
164+
if (!fpAddDeviceToVirtualNetwork(devIt->instance, it->first)) {
165+
std::cerr << "Failed to add Virtual Device" << std::endl;
166+
return -1;
167+
}
168+
std::cout << "OK" << std::endl;
169+
170+
// Enable IAm
171+
std::cout << "Enabling IAm... ";
172+
if (!fpSetServiceEnabled(devIt->instance, ExampleConstants::SERVICE_I_AM, true)) {
173+
std::cerr << "Failed to enable IAm" << std::endl;
174+
return -1;
175+
}
176+
std::cout << "OK" << std::endl;
177+
178+
// Enable Read Property Multiple
179+
if (!fpSetServiceEnabled(devIt->instance, ExampleConstants::SERVICE_READ_PROPERTY_MULTIPLE, true)) {
180+
std::cerr << "Failed to enable ReadPropertyMultiple" << std::endl;
181+
return -1;
182+
}
183+
std::cout << "OK" << std::endl;
184+
185+
// Add the Analog Input to the Virtual Device
186+
std::cout << "Adding Analog Input to Virtual Device. device.instance=[" << devIt->instance << "], analogInput.instance=[" << g_database.analogInputs[devIt->instance].instance << "]...";
187+
if (!fpAddObject(devIt->instance, ExampleConstants::OBJECT_TYPE_ANALOG_INPUT, g_database.analogInputs[devIt->instance].instance)) {
188+
std::cerr << "Failed to add AnalogInput" << std::endl;
189+
return -1;
190+
}
191+
std::cout << "OK" << std::endl;
192+
193+
// Enable Reliability property
194+
fpSetPropertyByObjectTypeEnabled(devIt->instance, ExampleConstants::OBJECT_TYPE_ANALOG_INPUT, ExampleConstants::PROPERTY_IDENTIFIER_RELIABILITY, true);
195+
}
196+
}
197+
```
198+
199+
### 4. Setting up the BBMD
200+
201+
Finally, setup the BBMD functionality.
202+
203+
#### 4.1 Update the Main Device NetworkPort Object to enable the required BBMD properties
204+
205+
BBMD requires specific properties to be enabled on the Main Device's Network Port object. These properties are BBMD_Accept_FD_Registrations, BBMD_Broadcast_Distribution_Table, and BBMD_Foreign_Device_Table
206+
207+
```cpp
208+
// Lines 218-238 in BACnetVirtualDevicesBBMDExcampleCPP
209+
// Add BBMD specific network port properties to the main device such as accept registrations, FDT, and BDT
210+
std::cout << "Enabling bbmd_accept_fd_registrations property to the Main Network Port Object networkPort.instance=[" << g_database.networkPort.instance << "]... ";
211+
if (!fpSetPropertyEnabled(g_database.mainDevice.instance, ExampleConstants::OBJECT_TYPE_NETWORK_PORT, g_database.networkPort.instance, ExampleConstants::PROPERTY_IDENTIFIER_BBMD_ACCEPT_FD_REGISTRATIONS, true)) {
212+
std::cerr << "Failed to enable the bbmd_accept_fd_registrations property for the Main Network Port Object" << std::endl;
213+
return -1;
214+
}
215+
std::cout << "OK" << std::endl;
216+
217+
std::cout << "Enabling bbmd_broadcast_distribution_table property to the Main Network Port Object networkPort.instance=[" << g_database.networkPort.instance << "]... ";
218+
if (!fpSetPropertyEnabled(g_database.mainDevice.instance, ExampleConstants::OBJECT_TYPE_NETWORK_PORT, g_database.networkPort.instance, ExampleConstants::PROPERTY_IDENTIFIER_BBMD_BROADCAST_DISTRIBUTION_TABLE, true)) {
219+
std::cerr << "Failed to enable the bbmd_broadcast_distribution_table property for the Main Network Port Object" << std::endl;
220+
return -1;
221+
}
222+
std::cout << "OK" << std::endl;
223+
224+
std::cout << "Enabling bbmd_foreign_device_table property to the Main Network Port Object networkPort.instance=[" << g_database.networkPort.instance << "]... ";
225+
if (!fpSetPropertyEnabled(g_database.mainDevice.instance, ExampleConstants::OBJECT_TYPE_NETWORK_PORT, g_database.networkPort.instance, ExampleConstants::PROPERTY_IDENTIFIER_BBMD_FOREIGN_DEVICE_TABLE, true)) {
226+
std::cerr << "Failed to enable the bbmd_foreign_device_table property for the Main Network Port Object" << std::endl;
227+
return -1;
228+
}
229+
std::cout << "OK" << std::endl;
230+
```
231+
232+
#### 4.2 Add the Main Device to the BDT Table
233+
234+
The first entry in the BBMD's BDT (Broadcast Distribution Table) must be itself.
235+
236+
```cpp
237+
// Lines 240-251 in BACnetVirtualDevicesBBMDExcampleCPP
238+
// Add Main Device to the BDT Table (the first entry must be this device)
239+
std::cout << "Adding Main Device to BDT Table... ";
240+
uint8_t bdtAddress[6];
241+
memcpy(bdtAddress, g_database.networkPort.IPAddress, 4);
242+
bdtAddress[4] = g_database.networkPort.BACnetIPUDPPort / 256;
243+
bdtAddress[5] = g_database.networkPort.BACnetIPUDPPort % 256;
244+
uint8_t bdtMask[4] = { 255, 255, 255, 255 };
245+
if (!fpAddBDTEntry(bdtAddress, 6, bdtMask, 4)) {
246+
std::cerr << "Failed to add the Main Device to the BDT Table" << std::endl;
247+
return -1;
248+
}
249+
std::cout << "OK" << std::endl;
250+
```
251+
252+
#### 4.3 Add a remote BBMD to the BDT Table
253+
254+
Then add additional BBMDs to the BDT Table. This example uses a hardcoded BBMD IP Address, but production implementations should allow for this table to be configurable, either via BACnet or from a GUI.
255+
256+
```cpp
257+
// Lines 253-261 in BACnetVirtualDevicesBBMDExcampleCPP
258+
// Add BBMD to the BDT Table. In this example, this is a hard-coded value for testing.
259+
// In an actual implementation, this should be configurable from a settings file / gui
260+
// or users can use the BACnet BVLL function WriteBroadcastDistributionTable to update the BDT.
261+
std::cout << "Adding BBMD to BDT Table... ";
262+
if (!fpAddBDTEntry(g_bbmdAddress, 6, bdtMask, 4)) {
263+
std::cerr << "Failed to add the BBMD to the BDT Table" << std::endl;
264+
return -1;
265+
}
266+
std::cout << "OK" << std::endl;
267+
```
268+
269+
#### 4.4 Enable BBMD
270+
271+
Finally, call fpSetBBMD to turn on the BBMD Functionality. This function will return with a false value if there was an error with any of the BBMD setup. Review the callstack and resolve any errors.
272+
273+
```cpp
274+
// Lines 263-269 in BACnetVirtualDevicesBBMDExcampleCPP
275+
// Enable BBMD
276+
std::cout << "Enabling BBMD... ";
277+
if (!fpSetBBMD(g_database.mainDevice.instance, g_database.networkPort.instance)) {
278+
std::cerr << "Failed to enable the BBMD" << std::endl;
279+
return -1;
280+
}
281+
std::cout << "OK" << std::endl;
282+
```
283+
284+
### 5. Send I-Ams for the Main Device and the Virtual Devices
285+
286+
As part of the boot-up process, once all the devices have been setup, send an I-Am for the Main device and for each Virtual Device by callin ghte fpSendIam API function.
287+
288+
```cpp
289+
// Lines 272-296 in BACnetVirtualDevicesBBMDExcampleCPP
290+
// 5. Send I-Am of this device
291+
// ---------------------------------------------------------------------------
292+
// To be a good citizen on a BACnet network. We should announce ourselves when we start up.
293+
std::cout << "FYI: Sending I-AM broadcast" << std::endl;
294+
uint8_t connectionString[6]; //= { 0xC0, 0xA8, 0x01, 0xFF, 0xBA, 0xC0 };
295+
memcpy(connectionString, g_database.networkPort.BroadcastIPAddress, 4);
296+
connectionString[4] = g_database.networkPort.BACnetIPUDPPort / 256;
297+
connectionString[5] = g_database.networkPort.BACnetIPUDPPort % 256;
298+
299+
// Send IAm for the Main Device
300+
if (!fpSendIAm(g_database.mainDevice.instance, connectionString, 6, ExampleConstants::NETWORK_TYPE_IP, true, 65535, NULL, 0)) {
301+
std::cerr << "Unable to send IAm broadcast for mainDevice.instance=[" << g_database.mainDevice.instance << "]" << std::endl;
302+
return false;
303+
}
304+
305+
// Send IAm for each virtual device
306+
for (it = g_database.virtualDevices.begin(); it != g_database.virtualDevices.end(); ++it) {
307+
std::vector<ExampleDatabaseDevice>::iterator devIt;
308+
for (devIt = it->second.begin(); devIt != it->second.end(); ++devIt) {
309+
if (!fpSendIAm(devIt->instance, connectionString, 6, ExampleConstants::NETWORK_TYPE_IP, true, 65535, NULL, 0)) {
310+
std::cerr << "Unable to send IAm broadcast for virtualDevice.instance=[" << devIt->instance << "]" << std::endl;
311+
return false;
312+
}
313+
}
314+
}
315+
```
316+
317+
### 6. Send I-Am-Router-To-Network from the Main Device as a Virtual Router
318+
319+
Next announce to the network that the Main Device is also a Virtual Router to each of the Virtual Networks that have been setup.
320+
321+
```cpp
322+
// Lines 298-302 in BACnetVirtualDevicesBBMDExcampleCPP
323+
// Send IAmRouterToNetwork
324+
if (!fpSendIAmRouterToNetwork(connectionString, 6, ExampleConstants::NETWORK_TYPE_IP, true, 65535, NULL, 0)) {
325+
std::cerr << "Unable to send IAmRouterToNetwork broadcast" << std::endl;
326+
return false;
327+
}
328+
```
329+
330+
### 7. Enter the Main Loop
331+
332+
Start the Main Loop calling the fpTick API function to process and handle incoming messages.
333+
334+
```cpp
335+
// Lines 304-325 in BACnetVirtualDevicesBBMDExcampleCPP
336+
// 6. Start the main loop
337+
// ---------------------------------------------------------------------------
338+
std::cout << "FYI: Entering main loop..." << std::endl;
339+
for (;;) {
340+
// Call the DLLs loop function which checks for messages and processes them.
341+
fpTick();
342+
343+
// Handle any user input.
344+
// Note: User input in this example is used for the following:
345+
// h - Display options
346+
// q - Quit
347+
if (!DoUserInput()) {
348+
// User press 'q' to quit the example application.
349+
break;
350+
}
351+
352+
// Update values in the example database
353+
g_database.Loop();
354+
355+
// Call Sleep to give some time back to the system
356+
Sleep(0); // Windows
357+
}
358+
```

0 commit comments

Comments
 (0)