Skip to content

Commit c901eba

Browse files
committed
Initial version
1 parent 2e4f697 commit c901eba

9 files changed

Lines changed: 1119 additions & 0 deletions

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
2+
CASBACnetStack_x64_Release.dll
3+
4+
target/
5+
*.jar
6+
*.war
7+
*.ear

README.md

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,130 @@
11
# BACnetClientExampleJava
2+
3+
A beginner-focused BACnet/IP client tutorial in Java using the CAS BACnet Stack.
4+
5+
This example shows how to:
6+
- Send `Who-Is`
7+
- Send `ReadProperty`
8+
- Send `SubscribeCOV`
9+
- Decode **all** sent and received BACnet packets as XML using `BACnetStack_DecodeAsXML`
10+
- Parse XML to print beginner-friendly summaries (I-Am and property values)
11+
12+
This project intentionally does **not** use stack Hook APIs.
13+
14+
## How This Example Is Structured
15+
16+
- `CASBACnetStackAdapter.java`
17+
- Loads the native CAS BACnet library.
18+
- `ICASBACnetStackLibrary.java`
19+
- JNA function signatures and callback signatures used by this tutorial.
20+
- `SimpleBACnetClientExample.java`
21+
- Main tutorial client flow, UDP networking, callbacks, command loop.
22+
- `XmlMessageParser.java`
23+
- Parses decoded XML and prints summarized BACnet events.
24+
- `BACnetConstants.java`
25+
- Minimal constants used by this tutorial.
26+
27+
## Prerequisites
28+
29+
- Java 8+
30+
- Maven
31+
- CAS BACnet Stack native library available on `PATH` or `java.library.path`
32+
- Example on Windows x64: `CASBACnetStack_x64_Release.dll`
33+
34+
## Build
35+
36+
```bash
37+
mvn -q -DskipTests compile
38+
```
39+
40+
## Run
41+
42+
```bash
43+
mvn exec:java
44+
```
45+
46+
Optional args:
47+
48+
```txt
49+
mvn exec:java -Dexec.args="<targetIp> <targetDeviceInstance> <readObjectType> <readObjectInstance> <readPropertyId>"
50+
```
51+
52+
Example:
53+
54+
```txt
55+
mvn exec:java -Dexec.args="192.168.2.217 389999 2 2 85"
56+
```
57+
58+
## Commands
59+
60+
Type a command key in the console (most terminals require Enter):
61+
62+
- `W` Send Who-Is
63+
- `R` Send ReadProperty
64+
- `S` Send SubscribeCOV
65+
- `H` Show help
66+
- `Q` Quit
67+
68+
## What New Users Should Notice
69+
70+
- The stack does BACnet encoding/decoding and state handling.
71+
- Your application provides network transport through callbacks:
72+
- `ReceiveMessage`: read UDP data from the network and pass bytes to stack.
73+
- `SendMessage`: stack gives encoded BACnet bytes, your app sends them via UDP.
74+
- `GetSystemTime`: stack asks your app for current time.
75+
- BACnet/IP connection strings are 6 bytes:
76+
- 4 bytes IPv4 address + 2 bytes UDP port.
77+
- `Who-Is` is unconfirmed discovery.
78+
- `ReadProperty` and `SubscribeCOV` are confirmed requests and normally produce ACK/error flows.
79+
- XML decode is used here for visibility and troubleshooting.
80+
81+
## Expected Output Pattern
82+
83+
- Outbound packet log:
84+
- `[TX] 192.168.x.x:47808`
85+
- `<BACnetPacket ...>...</BACnetPacket>`
86+
- Inbound packet log:
87+
- `[RX] 192.168.x.x:47808`
88+
- `<BACnetPacket ...>...</BACnetPacket>`
89+
- Parsed summaries:
90+
- `I-Am discovered: deviceId=..., connectionString=...`
91+
- `Property value: <object>:<instance>.<property> = <value>`
92+
93+
## Notes
94+
95+
- This tutorial keeps the API surface small to make first-time BACnet + CAS stack learning easier.
96+
- If your XML shape differs between stack versions, raw XML still prints, and parser fallbacks try to extract key fields safely.
97+
98+
## Example output
99+
100+
```txt
101+
CAS BACnet Stack Client Example (Java) v1.0.0
102+
Stack version: 4.5.6.2626
103+
UDP local bind: 0:0:0:0:0:0:0:0:47808
104+
Target connection string: 192.168.2.217:47808
105+
Target device instance (for tutorial context): 389999
106+
ReadProperty default: object=2:2, property=85
107+
108+
Commands:
109+
W - Send Who-Is
110+
R - Send ReadProperty
111+
S - Send SubscribeCOV
112+
H - Show help
113+
Q - Quit
114+
Input tip: type a command key and press Enter.
115+
116+
w
117+
Command W: sending Who-Is (unconfirmed discovery).
118+
Destination: broadcast via 192.168.2.217:47808 (actual UDP destination 255.255.255.255:47808)
119+
BACnet network: 0 (local network)
120+
[TX] 255.255.255.255:47808
121+
<!-- CAS BACnet Stack v4.5.6.2626 --><BACnetPacket networkType='IP'><BVLL function='originalBroadcastNPDU' /><NPDU control='0x00' version='1' /><UnconfirmedRequestPDU serviceChoice='whoIs'><WhoIsRequest /></UnconfirmedRequestPDU></BACnetPacket>
122+
123+
[RX] 192.168.3.76:47808
124+
<!-- CAS BACnet Stack v4.5.6.2626 --><BACnetPacket networkType='IP'><BVLL function='originalBroadcastNPDU' /><NPDU control='0x00' version='1' /><UnconfirmedRequestPDU serviceChoice='whoIs'><WhoIsRequest /></UnconfirmedRequestPDU></BACnetPacket>
125+
126+
[RX] 192.168.3.76:47808
127+
<!-- CAS BACnet Stack v4.5.6.2626 --><BACnetPacket networkType='IP'><BVLL function='originalBroadcastNPDU' /><NPDU control='0x00' version='1' /><UnconfirmedRequestPDU serviceChoice='iAm'><IAmRequest><IAmDeviceIdentifier datatype='12' objectInstance='389002' objectType='8'>device, 389002</IAmDeviceIdentifier><MaxAPDULengthAccepted datatype='2' value='1476'>1476</MaxAPDULengthAccepted><SegmentationSupported datatype='9' value='3'>noSegmentation</SegmentationSupported><VendorId datatype='2' value='389'>389</VendorId></IAmRequest></UnconfirmedRequestPDU></BACnetPacket>
128+
129+
I-Am discovered: deviceId=389002, connectionString=192.168.3.76:47808
130+
```

pom.xml

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<project xmlns="http://maven.apache.org/POM/4.0.0"
2+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
6+
<groupId>com.chipkin</groupId>
7+
<artifactId>bacnet-client-example-java</artifactId>
8+
<version>1.0.0</version>
9+
<packaging>jar</packaging>
10+
11+
<name>BACnetClientExampleJava</name>
12+
<description>BACnet client tutorial using CAS BACnet Stack and XML decoding</description>
13+
14+
<properties>
15+
<maven.compiler.source>1.8</maven.compiler.source>
16+
<maven.compiler.target>1.8</maven.compiler.target>
17+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
18+
<jna.version>5.13.0</jna.version>
19+
</properties>
20+
21+
<dependencies>
22+
<dependency>
23+
<groupId>net.java.dev.jna</groupId>
24+
<artifactId>jna</artifactId>
25+
<version>${jna.version}</version>
26+
</dependency>
27+
</dependencies>
28+
29+
<build>
30+
<plugins>
31+
<plugin>
32+
<groupId>org.apache.maven.plugins</groupId>
33+
<artifactId>maven-compiler-plugin</artifactId>
34+
<version>3.11.0</version>
35+
</plugin>
36+
<plugin>
37+
<groupId>org.codehaus.mojo</groupId>
38+
<artifactId>exec-maven-plugin</artifactId>
39+
<version>3.1.0</version>
40+
<configuration>
41+
<mainClass>com.chipkin.bacnet.SimpleBACnetClientExample</mainClass>
42+
</configuration>
43+
</plugin>
44+
</plugins>
45+
</build>
46+
</project>
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.chipkin.bacnet;
2+
3+
/**
4+
* Minimal constants used by this tutorial client.
5+
*
6+
* Full BACnet enumerations are much larger. New users can start with this
7+
* subset and add more constants as they add services and object types.
8+
*/
9+
public final class BACnetConstants {
10+
11+
private BACnetConstants() {
12+
}
13+
14+
// Network type understood by the stack for BACnet/IP.
15+
public static final byte NETWORK_TYPE_IP = 0;
16+
17+
// Common object types used in this example.
18+
public static final short OBJECT_TYPE_ANALOG_INPUT = 0;
19+
public static final short OBJECT_TYPE_ANALOG_VALUE = 2;
20+
public static final short OBJECT_TYPE_DEVICE = 8;
21+
22+
// Common property identifiers used in this example.
23+
public static final int PROPERTY_IDENTIFIER_OBJECT_NAME = 77;
24+
public static final int PROPERTY_IDENTIFIER_PRESENT_VALUE = 85;
25+
26+
// Service constants used when enabling services on the local client device.
27+
public static final int SERVICE_SUBSCRIBE_COV = 5;
28+
public static final int SERVICE_READ_PROPERTY_MULTIPLE = 14;
29+
public static final int SERVICE_I_AM = 26;
30+
public static final int SERVICE_I_HAVE = 27;
31+
public static final int SERVICE_WHO_IS = 34;
32+
public static final int SERVICE_SUBSCRIBE_COV_PROPERTY = 38;
33+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.chipkin.bacnet;
2+
3+
/**
4+
* Small exception wrapper used by the tutorial project.
5+
*/
6+
public class BACnetException extends Exception {
7+
public BACnetException(String message) {
8+
super(message);
9+
}
10+
11+
public BACnetException(String message, Throwable cause) {
12+
super(message, cause);
13+
}
14+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package com.chipkin.bacnet;
2+
3+
import com.sun.jna.Native;
4+
5+
/**
6+
* Small adapter that loads the native CAS BACnet Stack library and exposes the
7+
* JNA interface.
8+
*/
9+
public class CASBACnetStackAdapter {
10+
11+
private final ICASBACnetStackLibrary library;
12+
13+
public CASBACnetStackAdapter() throws BACnetException {
14+
try {
15+
// Native.load binds the JNA interface to exported C functions in the DLL/SO.
16+
this.library = Native.load(detectLibraryName(), ICASBACnetStackLibrary.class);
17+
} catch (UnsatisfiedLinkError ex) {
18+
throw new BACnetException(
19+
"Failed to load CAS BACnet Stack native library. "
20+
+ "Put the DLL/SO on PATH or java.library.path.",
21+
ex
22+
);
23+
}
24+
}
25+
26+
public ICASBACnetStackLibrary getLibrary() {
27+
return library;
28+
}
29+
30+
public String getVersionString() {
31+
return String.format(
32+
"%d.%d.%d.%d",
33+
library.BACnetStack_GetAPIMajorVersion(),
34+
library.BACnetStack_GetAPIMinorVersion(),
35+
library.BACnetStack_GetAPIPatchVersion(),
36+
library.BACnetStack_GetAPIBuildVersion()
37+
);
38+
}
39+
40+
private String detectLibraryName() {
41+
// The CAS stack ships multiple binaries by OS/CPU architecture.
42+
// We pick a default name based on runtime environment.
43+
String osName = System.getProperty("os.name", "").toLowerCase();
44+
String osArch = System.getProperty("os.arch", "").toLowerCase();
45+
46+
if (osName.contains("windows")) {
47+
return osArch.contains("64") ? "CASBACnetStack_x64_Release" : "CASBACnetStack_x86_Release";
48+
}
49+
if (osName.contains("linux")) {
50+
if (osArch.contains("arm")) {
51+
return "CASBACnetStack_arm7_Release";
52+
}
53+
return osArch.contains("64") ? "CASBACnetStack_x64_Release" : "CASBACnetStack_x86_Release";
54+
}
55+
56+
throw new IllegalStateException("Unsupported OS/arch for tutorial: " + osName + " / " + osArch);
57+
}
58+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package com.chipkin.bacnet;
2+
3+
import com.sun.jna.Callback;
4+
import com.sun.jna.Library;
5+
import com.sun.jna.Pointer;
6+
7+
/**
8+
* Focused JNA mapping for the APIs this tutorial needs.
9+
*
10+
* Keeping this interface small helps beginners understand the minimum calls
11+
* required to build a BACnet client around the CAS BACnet Stack.
12+
*/
13+
public interface ICASBACnetStackLibrary extends Library {
14+
15+
int BACnetStack_GetAPIMajorVersion();
16+
int BACnetStack_GetAPIMinorVersion();
17+
int BACnetStack_GetAPIPatchVersion();
18+
int BACnetStack_GetAPIBuildVersion();
19+
20+
// Call often from your main loop. This drives stack processing.
21+
boolean BACnetStack_Tick();
22+
boolean BACnetStack_AddDevice(int deviceInstance);
23+
// Enables/disables a BACnet service for the specified local device.
24+
boolean BACnetStack_SetServiceEnabled(int deviceInstance, int service, boolean enabled);
25+
26+
int BACnetStack_DecodeAsXML(Pointer inMessageRaw,
27+
short inMessageLength,
28+
Pointer outMessageXML,
29+
int outMessageMaxLength,
30+
byte networkType);
31+
32+
// Transport integration callbacks.
33+
// ReceiveMessage: app -> stack (incoming UDP bytes).
34+
// SendMessage: stack -> app (outgoing UDP bytes).
35+
// GetSystemTime: app -> stack (UTC seconds).
36+
void BACnetStack_RegisterCallbackReceiveMessage(ReceiveMessageCallback callback);
37+
void BACnetStack_RegisterCallbackSendMessage(SendMessageCallback callback);
38+
void BACnetStack_RegisterCallbackGetSystemTime(GetSystemTimeCallback callback);
39+
40+
boolean BACnetStack_SendWhoIs(Pointer connectionString,
41+
byte connectionStringLength,
42+
byte networkType,
43+
boolean broadcast,
44+
short destinationNetwork,
45+
Pointer destinationAddress,
46+
byte destinationAddressLength);
47+
48+
boolean BACnetStack_BuildReadProperty(short objectType,
49+
int objectInstance,
50+
int propertyIdentifier,
51+
boolean usePropertyArrayIndex,
52+
int propertyArrayIndex);
53+
54+
void BACnetStack_ClearReadProperty();
55+
56+
boolean BACnetStack_SendReadProperty(Pointer sentInvokeId,
57+
Pointer connectionString,
58+
byte connectionStringLength,
59+
byte networkType,
60+
short destinationNetwork,
61+
Pointer destinationAddress,
62+
byte destinationAddressLength);
63+
64+
boolean BACnetStack_SendSubscribeCOV(Pointer sentInvokeId,
65+
int subscriberProcessIdentifier,
66+
short monitoredObjectType,
67+
int monitoredObjectInstance,
68+
boolean issueConfirmedNotifications,
69+
int lifetime,
70+
Pointer connectionString,
71+
byte connectionStringLength,
72+
byte networkType,
73+
short destinationNetwork,
74+
Pointer destinationAddress,
75+
byte destinationAddressLength);
76+
77+
// Return number of bytes copied into `message`.
78+
// Fill source connection string and network type for each received packet.
79+
interface ReceiveMessageCallback extends Callback {
80+
short callback(Pointer message,
81+
short maxMessageLength,
82+
Pointer receivedConnectionString,
83+
byte maxConnectionStringLength,
84+
Pointer receivedConnectionStringLength,
85+
Pointer networkType);
86+
}
87+
88+
// Send the `message` bytes to `connectionString`.
89+
// Return number of bytes sent.
90+
interface SendMessageCallback extends Callback {
91+
short callback(Pointer message,
92+
short messageLength,
93+
Pointer connectionString,
94+
byte connectionStringLength,
95+
byte networkType,
96+
boolean broadcast);
97+
}
98+
99+
interface GetSystemTimeCallback extends Callback {
100+
long callback();
101+
}
102+
}

0 commit comments

Comments
 (0)