This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
@bacnet-js/client — a BACnet® protocol stack written in pure TypeScript for Node.js (>= 20). Implements ASHRAE 135 standard for building automation and control networks. Published as an npm package with only two runtime dependencies (debug, iconv-lite).
npm run build # TypeScript compile (tsconfig.build.json)
npm run lint # ESLint check
npm run lint:fix # ESLint auto-fix
npm run test:all # All tests (unit + integration + compliance)
npm run test:unit # Unit tests only
npm run test:integration # Integration tests only
npm run test:compliance # Compliance tests (requires emulator, runs sequentially)
# Run a single test file
node --require esbuild-register --test test/unit/service-read-property.spec.ts
npm run emulator:start # Start BACnet device emulator (needed for compliance tests)
npm run docs # Generate TypeDoc API docsTests use the Node.js native test runner (node --test) with esbuild-register for TypeScript. No Jest/Mocha.
Each layer is a pure-function module with encode/decode exports sharing a mutable EncodeBuffer ({ buffer: Buffer, offset: number }):
UDP Socket (Transport)
→ BVLC (src/lib/bvlc.ts) — 4-byte BACnet/IP framing header
→ NPDU (src/lib/npdu.ts) — Network routing, source/dest addressing
→ APDU (src/lib/apdu.ts) — PDU type dispatch (confirmed/unconfirmed/ack/error/abort)
→ Service payload — Service-specific ASN.1 encoded data
src/lib/asn1.ts provides all ASN.1 primitives (context/application tags, object IDs, bitstrings, etc.). Uses iconv-lite for multi-encoding character string support.
BACnetClient extends TypedEventEmitter<BACnetClientEvents>. All BACnet services are methods on this class (~2100 lines).
Outbound flow (e.g., readProperty):
- Get rolling invoke ID (0-255)
- Allocate
EncodeBuffer, encode NPDU → APDU → service payload - Prepend BVLC header via
sendBvlc(), send UDP RequestManager.add(invokeId)returns a Promise that resolves when ACK arrives
Inbound flow:
Transportemitsmessage→_receiveData()- Decode BVLC → NPDU → determine PDU type
- For requests: look up service in
confirmedServiceMap/unconfirmedServiceMap, callServiceClass.decode(), emit event - For ACKs:
RequestManager.resolve(invokeId, data)
Each service is a static class extending BacnetService (or BacnetAckService):
class ReadProperty extends BacnetAckService {
static encode(buffer, ...) // Client sends request
static decode(buffer, offset, len) // Client receives request (server role)
static encodeAcknowledge(buffer, ...) // Client sends ACK (server role)
static decodeAcknowledge(buffer, ...) // Client receives ACK
}ServicesMap in services/index.ts maps event names (e.g., "readProperty") to service classes for dynamic dispatch.
Wraps a dgram UDP4 socket. Default port: 47808 (0xBAC0). Handles broadcast deduplication (10s window). Injectable via ClientOptions.transport for testing.
Maps invoke IDs to Deferred<NetworkOpResult> promises. Handles timeout sweeping. Timeout function is injectable for test mocking.
Thin re-export: BACnetClient as default export, plus all enums, types, and bitstring classes.
- Unit (
test/unit/): Encode/decode in isolation. UsesgetBuffer()helper fromtest/unit/utils.ts. - Integration (
test/integration/): UsesTransportStub(mock EventEmitter) injected intoBACnetClient. Simulates packets viatransportStub.emit('message', buffer, addr). - Compliance (
test/compliance/): Runs against the live emulator. Sequential execution (--test-concurrency=1).
EncodeBuffer:{ buffer: Buffer, offset: number }— mutable cursor passed through all encode layersBACNetObjectID:{ type: ObjectType, instance: number }TypedValue<Tag>:{ type: ApplicationTag, value: ... }— generic typed BACnet valueDecode<T>:{ len, value }— standard decode return shape- Enums in
src/lib/enum.ts:ObjectType,PropertyIdentifier,ApplicationTag,ConfirmedServiceChoice,UnconfirmedServiceChoice,PduType,ErrorClass,ErrorCode - Bitstrings in
src/lib/bitstring.ts:GenericBitString<E>,StatusFlagsBitString,ServicesSupportedBitString
This library implements ASHRAE Standard 135 (BACnet). Always consult the official specification and reference implementations when reviewing or implementing features:
- bacnet-stack — The most widely used open-source BACnet implementation in C. Use as the primary source of truth for protocol behavior and packet formats.
- BACnet.org technical papers — Free PDFs covering specific topics (foreign devices, BBMDs, routing, etc.)
- Wireshark BVLC dissector — Useful for verifying packet formats and field layouts
When implementing or modifying protocol features, reference the relevant standard section in a code comment. Example:
// Encode object identifier per ASHRAE 135-2024 §20.2.14
encodeApplicationObjectId(buffer, objectType, instance)Detailed standards are in .github/copilot-instructions.md. Key points:
- Strict TypeScript (
noImplicitAny,strictNullChecks) - Files: camelCase. Classes: PascalCase. Enums: PascalCase with ALL_CAPS values. Private members: underscore prefix.
interfaceovertypefor object shapes- Conventional Commits:
feat|fix|docs|test|chore|refactor|perf|ci[scope]: description - Scopes:
client,transport,services,asn1,enum,types,apdu,npdu,bvlc - Debug logging:
debuglibrary withbacnet:module:levelnamespaces