Skip to content

Commit 3acd322

Browse files
committed
v1.0.1 Added changelog, version file, and updated the XML Parser
1 parent e912f95 commit 3acd322

File tree

6 files changed

+320
-85
lines changed

6 files changed

+320
-85
lines changed

CHANGELOG.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Changelog
2+
3+
All notable changes to this project are documented in this file.
4+
5+
## Unreleased
6+
7+
### Added
8+
9+
- Add `changelog.md` to track unreleased and released project changes.
10+
- Add `agents.md` to require changelog updates for every project change.
11+
12+
### Changed
13+
14+
- Move application version constants into `ApplicationVersion` so version updates are centralized.
15+
16+
## 0.1.0 - 2026-03-06
17+
18+
_First tracked release baseline._

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# BACnetClientExampleJava
22

3-
A beginner-focused BACnet/IP client tutorial in Java using the CAS BACnet Stack.
3+
A beginner-focused BACnet/IP client tutorial in Java using the [CAS BACnet Stack](https://store.chipkin.com/services/stacks/bacnet-stack) v4.5.6.2626
44

55
This example shows how to:
66
- Send `Who-Is`
@@ -28,7 +28,7 @@ This project intentionally does **not** use stack Hook APIs.
2828

2929
- Java 8+
3030
- Maven
31-
- CAS BACnet Stack native library available on `PATH` or `java.library.path`
31+
- [CAS BACnet Stack](https://store.chipkin.com/services/stacks/bacnet-stack) native library available on `PATH` or `java.library.path`
3232
- Example on Windows x64: `CASBACnetStack_x64_Release.dll`
3333

3434
## Build
@@ -114,6 +114,7 @@ Commands:
114114
Input tip: type a command key and press Enter.
115115
116116
w
117+
117118
Command W: sending Who-Is (unconfirmed discovery).
118119
Destination: broadcast via 192.168.2.217:47808 (actual UDP destination 255.255.255.255:47808)
119120
BACnet network: 0 (local network)

agents.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# AGENTS.md
2+
3+
## Scope
4+
5+
These instructions apply to the entire repository.
6+
7+
## Required Changelog Policy
8+
9+
- Any change to code, behavior, configuration, dependencies, build scripts, docs, or tests must be documented in `changelog.md`.
10+
- Add new notes under `## Unreleased`, which must remain the first changelog entry at the top of `changelog.md`.
11+
- Do not move unreleased notes into a versioned section until a release is cut.
12+
13+
## Changelog Rules (Common Changelog aligned)
14+
15+
- Keep entries human-focused, concise, and impact-oriented.
16+
- Use change groups as needed: `Changed`, `Added`, `Removed`, `Fixed`.
17+
- Write each item in imperative mood (for example: `Add`, `Fix`, `Refactor`, `Document`).
18+
- Keep each item to one line when possible.
19+
- Include references (issues, PRs, commits) when available.
20+
- Prefix breaking changes with `**Breaking:**`.
21+
- For released versions, use `## <version> - <YYYY-MM-DD>`.
22+
23+
## Agent Workflow Requirement
24+
25+
- Before finishing any task that changes the repository, update `changelog.md` under `## Unreleased`.
26+
- If no user-visible or developer-relevant change occurred, document why no changelog entry was added.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.chipkin.bacnet;
2+
3+
/**
4+
* Central application version values.
5+
*
6+
* Update this file when bumping the example version.
7+
*/
8+
public final class ApplicationVersion {
9+
public static final int MAJOR = 1;
10+
public static final int MINOR = 0;
11+
public static final int PATCH = 1;
12+
13+
private ApplicationVersion() {
14+
}
15+
}

src/main/java/com/chipkin/bacnet/SimpleBACnetClientExample.java

Lines changed: 82 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
1+
/*
2+
* Copyright and License Notice
3+
* Chipkin Automation Systems Inc (CAS)
4+
* BACnet Stack as Source Code Product
5+
*
6+
* Simple BACnet Client Example with Business Logic
7+
*/
8+
19
package com.chipkin.bacnet;
210

3-
import com.sun.jna.Memory;
4-
import com.sun.jna.Pointer;
511
import java.io.BufferedReader;
612
import java.io.IOException;
713
import java.io.InputStreamReader;
@@ -13,6 +19,9 @@
1319
import java.util.Queue;
1420
import java.util.concurrent.ConcurrentLinkedQueue;
1521

22+
import com.sun.jna.Memory;
23+
import com.sun.jna.Pointer;
24+
1625
/**
1726
* BACnet client tutorial example.
1827
*
@@ -22,10 +31,6 @@
2231
*/
2332
public class SimpleBACnetClientExample {
2433

25-
private static final int APPLICATION_VERSION_MAJOR = 1;
26-
private static final int APPLICATION_VERSION_MINOR = 0;
27-
private static final int APPLICATION_VERSION_PATCH = 0;
28-
2934
private static final int BACNET_UDP_PORT = 47808;
3035
private static final int UDP_TIMEOUT_MS = 2;
3136
private static final int MAX_MESSAGE_LENGTH = 2048;
@@ -96,7 +101,7 @@ public static void main(String[] args) {
96101

97102
private void initializeStack() throws BACnetException {
98103
System.out.println("CAS BACnet Stack Client Example (Java) v"
99-
+ APPLICATION_VERSION_MAJOR + "." + APPLICATION_VERSION_MINOR + "." + APPLICATION_VERSION_PATCH);
104+
+ ApplicationVersion.MAJOR + "." + ApplicationVersion.MINOR + "." + ApplicationVersion.PATCH);
100105
System.out.println("Stack version: " + adapter.getVersionString());
101106

102107
// Core integration point:
@@ -127,12 +132,13 @@ private void initializeStack() throws BACnetException {
127132
}
128133

129134
private void runLoop() {
135+
final long TICK_SLEEP_MS = 5;
130136
while (running) {
131137
// Tick is the stack "heartbeat". Call this frequently.
132138
// It handles retries, timeouts, request tracking, and packet parsing.
133139
library.BACnetStack_Tick();
134140
processConsoleInput();
135-
sleepQuietly(5);
141+
sleepQuietly(TICK_SLEEP_MS);
136142
}
137143

138144
udpSocket.close();
@@ -171,10 +177,16 @@ private void handleCommand(char input) {
171177
}
172178

173179
private void sendWhoIs() {
180+
final String BROADCAST_IP = "255.255.255.255";
181+
final String BROADCAST_ENDPOINT = BROADCAST_IP + ":" + BACNET_UDP_PORT;
182+
final boolean IS_BROADCAST = true;
183+
final short LOCAL_NETWORK = 0;
184+
final Pointer ROUTER_MAC = Pointer.NULL;
185+
final byte ROUTER_MAC_LENGTH = 0;
174186
String target = connectionStringToHuman(targetConnectionString, targetConnectionString.length);
175187
System.out.println("Command W: sending Who-Is (unconfirmed discovery).");
176-
System.out.println(" Destination: broadcast via " + target + " (actual UDP destination 255.255.255.255:47808)");
177-
System.out.println(" BACnet network: 0 (local network)");
188+
System.out.println(" Destination: broadcast via " + target + " (actual UDP destination " + BROADCAST_ENDPOINT + ")");
189+
System.out.println(" BACnet network: " + LOCAL_NETWORK + " (local network)");
178190

179191
Memory connection = new Memory(targetConnectionString.length);
180192
connection.write(0, targetConnectionString, 0, targetConnectionString.length);
@@ -183,10 +195,10 @@ private void sendWhoIs() {
183195
connection,
184196
(byte) targetConnectionString.length,
185197
BACnetConstants.NETWORK_TYPE_IP,
186-
true,
187-
(short) 0,
188-
Pointer.NULL,
189-
(byte) 0
198+
IS_BROADCAST,
199+
LOCAL_NETWORK,
200+
ROUTER_MAC,
201+
ROUTER_MAC_LENGTH
190202
);
191203

192204
if (!sent) {
@@ -195,6 +207,12 @@ private void sendWhoIs() {
195207
}
196208

197209
private void sendReadProperty() {
210+
final boolean USE_ARRAY_INDEX = false;
211+
final int ARRAY_INDEX = 0;
212+
final int INVOKE_ID_LENGTH = 1;
213+
final short LOCAL_NETWORK = 0;
214+
final Pointer ROUTER_MAC = Pointer.NULL;
215+
final byte ROUTER_MAC_LENGTH = 0;
198216
String target = connectionStringToHuman(targetConnectionString, targetConnectionString.length);
199217
System.out.println("Command R: sending ReadProperty (confirmed request).");
200218
System.out.println(" Destination: " + target + ", deviceInstance=" + targetDeviceInstance);
@@ -209,16 +227,16 @@ private void sendReadProperty() {
209227
readObjectType,
210228
readObjectInstance,
211229
readProperty,
212-
false,
213-
0
230+
USE_ARRAY_INDEX,
231+
ARRAY_INDEX
214232
);
215233

216234
if (!built) {
217235
System.out.println("BuildReadProperty failed.");
218236
return;
219237
}
220238

221-
Memory invokeId = new Memory(1);
239+
Memory invokeId = new Memory(INVOKE_ID_LENGTH);
222240
Memory connection = new Memory(targetConnectionString.length);
223241
connection.write(0, targetConnectionString, 0, targetConnectionString.length);
224242

@@ -227,9 +245,9 @@ private void sendReadProperty() {
227245
connection,
228246
(byte) targetConnectionString.length,
229247
BACnetConstants.NETWORK_TYPE_IP,
230-
(short) 0,
231-
Pointer.NULL,
232-
(byte) 0
248+
LOCAL_NETWORK,
249+
ROUTER_MAC,
250+
ROUTER_MAC_LENGTH
233251
);
234252

235253
if (!sent) {
@@ -238,44 +256,54 @@ private void sendReadProperty() {
238256
}
239257

240258
private void sendSubscribeCov() {
259+
final int INVOKE_ID_LENGTH = 1;
260+
final boolean CONFIRMED_NOTIFICATIONS = false;
261+
final short LOCAL_NETWORK = 0;
262+
final Pointer ROUTER_MAC = Pointer.NULL;
263+
final byte ROUTER_MAC_LENGTH = 0;
264+
final int ANALOG_INPUT_PROCESS_ID = 1;
265+
final int ANALOG_VALUE_PROCESS_ID = 2;
266+
final int ANALOG_INPUT_INSTANCE = 0;
267+
final int ANALOG_VALUE_INSTANCE = 2;
241268
String target = connectionStringToHuman(targetConnectionString, targetConnectionString.length);
242269
System.out.println("Command S: sending SubscribeCOV (confirmed request).");
243270
System.out.println(" Destination: " + target + ", deviceInstance=" + targetDeviceInstance);
244271
System.out.println(" Subscription lifetime: " + SUBSCRIPTION_LIFETIME_SECONDS + " seconds");
245-
System.out.println(" Requests: analogInput:0 (processId=1), analogValue:2 (processId=2)");
272+
System.out.println(" Requests: analogInput:" + ANALOG_INPUT_INSTANCE + " (processId=" + ANALOG_INPUT_PROCESS_ID + "), analogValue:"
273+
+ ANALOG_VALUE_INSTANCE + " (processId=" + ANALOG_VALUE_PROCESS_ID + ")");
246274

247-
Memory invokeId = new Memory(1);
275+
Memory invokeId = new Memory(INVOKE_ID_LENGTH);
248276
Memory connection = new Memory(targetConnectionString.length);
249277
connection.write(0, targetConnectionString, 0, targetConnectionString.length);
250278

251279
boolean sentAi = library.BACnetStack_SendSubscribeCOV(
252280
invokeId,
253-
1,
281+
ANALOG_INPUT_PROCESS_ID,
254282
BACnetConstants.OBJECT_TYPE_ANALOG_INPUT,
255-
0,
256-
false,
283+
ANALOG_INPUT_INSTANCE,
284+
CONFIRMED_NOTIFICATIONS,
257285
SUBSCRIPTION_LIFETIME_SECONDS,
258286
connection,
259287
(byte) targetConnectionString.length,
260288
BACnetConstants.NETWORK_TYPE_IP,
261-
(short) 0,
262-
Pointer.NULL,
263-
(byte) 0
289+
LOCAL_NETWORK,
290+
ROUTER_MAC,
291+
ROUTER_MAC_LENGTH
264292
);
265293

266294
boolean sentAv = library.BACnetStack_SendSubscribeCOV(
267295
invokeId,
268-
2,
296+
ANALOG_VALUE_PROCESS_ID,
269297
BACnetConstants.OBJECT_TYPE_ANALOG_VALUE,
270-
2,
271-
false,
298+
ANALOG_VALUE_INSTANCE,
299+
CONFIRMED_NOTIFICATIONS,
272300
SUBSCRIPTION_LIFETIME_SECONDS,
273301
connection,
274302
(byte) targetConnectionString.length,
275303
BACnetConstants.NETWORK_TYPE_IP,
276-
(short) 0,
277-
Pointer.NULL,
278-
(byte) 0
304+
LOCAL_NETWORK,
305+
ROUTER_MAC,
306+
ROUTER_MAC_LENGTH
279307
);
280308

281309
if (!sentAi || !sentAv) {
@@ -333,6 +361,7 @@ private short handleSendMessage(Pointer message,
333361
byte connectionStringLength,
334362
byte networkType,
335363
boolean broadcast) {
364+
final int MIN_CONNECTION_STRING_LENGTH = 6;
336365
if (messageLength <= 0) {
337366
return 0;
338367
}
@@ -343,7 +372,7 @@ private short handleSendMessage(Pointer message,
343372
}
344373

345374
int connLength = Byte.toUnsignedInt(connectionStringLength);
346-
if (connLength < 6) {
375+
if (connLength < MIN_CONNECTION_STRING_LENGTH) {
347376
System.out.println("Cannot send, connection string too short: " + connLength);
348377
return 0;
349378
}
@@ -464,33 +493,41 @@ private void parseArguments(String[] args) {
464493
}
465494

466495
private void buildTargetConnectionString(String ip, int port) {
496+
final int IPV4_OCTET_COUNT = 4;
497+
final int PORT_HIGH_INDEX = 4;
498+
final int PORT_LOW_INDEX = 5;
467499
String[] parts = ip.split("\\.");
468-
if (parts.length != 4) {
500+
if (parts.length != IPV4_OCTET_COUNT) {
469501
throw new IllegalArgumentException("Invalid IPv4 address: " + ip);
470502
}
471503

472-
for (int i = 0; i < 4; i++) {
504+
for (int i = 0; i < IPV4_OCTET_COUNT; i++) {
473505
targetConnectionString[i] = (byte) parseIntOrDefault(parts[i], 0);
474506
}
475-
targetConnectionString[4] = (byte) ((port >> 8) & 0xFF);
476-
targetConnectionString[5] = (byte) (port & 0xFF);
507+
targetConnectionString[PORT_HIGH_INDEX] = (byte) ((port >> 8) & 0xFF);
508+
targetConnectionString[PORT_LOW_INDEX] = (byte) (port & 0xFF);
477509
}
478510

479511
private byte[] toConnectionString(InetAddress address, int port) {
480-
byte[] out = new byte[6];
512+
final int IPV4_OCTET_COUNT = 4;
513+
final int CONNECTION_STRING_LENGTH = 6;
514+
final int PORT_HIGH_INDEX = 4;
515+
final int PORT_LOW_INDEX = 5;
516+
byte[] out = new byte[CONNECTION_STRING_LENGTH];
481517
byte[] ipBytes = address.getAddress();
482-
if (ipBytes.length != 4) {
518+
if (ipBytes.length != IPV4_OCTET_COUNT) {
483519
throw new IllegalStateException("Expected IPv4 address, got length=" + ipBytes.length);
484520
}
485521

486-
System.arraycopy(ipBytes, 0, out, 0, 4);
487-
out[4] = (byte) ((port >> 8) & 0xFF);
488-
out[5] = (byte) (port & 0xFF);
522+
System.arraycopy(ipBytes, 0, out, 0, IPV4_OCTET_COUNT);
523+
out[PORT_HIGH_INDEX] = (byte) ((port >> 8) & 0xFF);
524+
out[PORT_LOW_INDEX] = (byte) (port & 0xFF);
489525
return out;
490526
}
491527

492528
private static String connectionStringToHuman(byte[] connectionString, int length) {
493-
if (connectionString == null || length < 6) {
529+
final int CONNECTION_STRING_LENGTH = 6;
530+
if (connectionString == null || length < CONNECTION_STRING_LENGTH) {
494531
return "invalid-connection-string";
495532
}
496533

0 commit comments

Comments
 (0)