|
1 | 1 | # CLAUDE.md |
2 | 2 |
|
3 | | -This file provides guidance to Claude Code (claude.ai/code) when working with this repository. |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
4 | 4 |
|
5 | | -## Project Instructions |
| 5 | +## Project Overview |
6 | 6 |
|
7 | | -All coding standards, architecture patterns, testing guidelines, and contribution requirements are maintained in a single shared location: |
| 7 | +`@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`). |
8 | 8 |
|
9 | | -**[.github/copilot-instructions.md](.github/copilot-instructions.md)** |
| 9 | +## Commands |
10 | 10 |
|
11 | | -That file is the primary reference for: |
| 11 | +```bash |
| 12 | +npm run build # TypeScript compile (tsconfig.build.json) |
| 13 | +npm run lint # ESLint check |
| 14 | +npm run lint:fix # ESLint auto-fix |
12 | 15 |
|
13 | | -- Code style and TypeScript conventions |
14 | | -- Architecture patterns (event-driven design, buffer management, ASN.1 encoding) |
15 | | -- BACnet protocol specifics |
16 | | -- Testing strategy and coverage requirements |
17 | | -- Git workflow and conventional commits |
18 | | -- Pull request quality standards |
| 16 | +npm run test:all # All tests (unit + integration + compliance) |
| 17 | +npm run test:unit # Unit tests only |
| 18 | +npm run test:integration # Integration tests only |
| 19 | +npm run test:compliance # Compliance tests (requires emulator, runs sequentially) |
19 | 20 |
|
20 | | -## Quick Reference |
| 21 | +# Run a single test file |
| 22 | +node --require esbuild-register --test test/unit/service-read-property.spec.ts |
21 | 23 |
|
22 | | -```bash |
23 | | -# Build |
24 | | -npm run build |
25 | | - |
26 | | -# Lint |
27 | | -npm run lint |
28 | | -npm run lint:fix |
29 | | - |
30 | | -# Test |
31 | | -npm run test:all # Run all tests |
32 | | -npm run test:unit # Unit tests only |
33 | | -npm run test:integration # Integration tests only |
34 | | -npm run test:compliance # Compliance tests only |
35 | | - |
36 | | -# Development |
37 | | -npm run emulator:start # Start BACnet device emulator |
38 | | -npm run docs # Generate API docs with TypeDoc |
| 24 | +npm run emulator:start # Start BACnet device emulator (needed for compliance tests) |
| 25 | +npm run docs # Generate TypeDoc API docs |
| 26 | +``` |
| 27 | + |
| 28 | +Tests use the **Node.js native test runner** (`node --test`) with `esbuild-register` for TypeScript. No Jest/Mocha. |
| 29 | + |
| 30 | +## Architecture |
| 31 | + |
| 32 | +### Protocol Stack (bottom-up) |
| 33 | + |
| 34 | +Each layer is a pure-function module with `encode`/`decode` exports sharing a mutable `EncodeBuffer` (`{ buffer: Buffer, offset: number }`): |
| 35 | + |
| 36 | +``` |
| 37 | +UDP Socket (Transport) |
| 38 | + → BVLC (src/lib/bvlc.ts) — 4-byte BACnet/IP framing header |
| 39 | + → NPDU (src/lib/npdu.ts) — Network routing, source/dest addressing |
| 40 | + → APDU (src/lib/apdu.ts) — PDU type dispatch (confirmed/unconfirmed/ack/error/abort) |
| 41 | + → Service payload — Service-specific ASN.1 encoded data |
| 42 | +``` |
| 43 | + |
| 44 | +`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. |
| 45 | + |
| 46 | +### Main Client (`src/lib/client.ts`) |
| 47 | + |
| 48 | +`BACnetClient` extends `TypedEventEmitter<BACnetClientEvents>`. All BACnet services are methods on this class (~2100 lines). |
| 49 | + |
| 50 | +**Outbound flow (e.g., readProperty):** |
| 51 | +1. Get rolling invoke ID (0-255) |
| 52 | +2. Allocate `EncodeBuffer`, encode NPDU → APDU → service payload |
| 53 | +3. Prepend BVLC header via `sendBvlc()`, send UDP |
| 54 | +4. `RequestManager.add(invokeId)` returns a Promise that resolves when ACK arrives |
| 55 | + |
| 56 | +**Inbound flow:** |
| 57 | +1. `Transport` emits `message` → `_receiveData()` |
| 58 | +2. Decode BVLC → NPDU → determine PDU type |
| 59 | +3. For requests: look up service in `confirmedServiceMap`/`unconfirmedServiceMap`, call `ServiceClass.decode()`, emit event |
| 60 | +4. For ACKs: `RequestManager.resolve(invokeId, data)` |
| 61 | + |
| 62 | +### Services (`src/lib/services/`) |
| 63 | + |
| 64 | +Each service is a static class extending `BacnetService` (or `BacnetAckService`): |
| 65 | + |
| 66 | +```typescript |
| 67 | +class ReadProperty extends BacnetAckService { |
| 68 | + static encode(buffer, ...) // Client sends request |
| 69 | + static decode(buffer, offset, len) // Client receives request (server role) |
| 70 | + static encodeAcknowledge(buffer, ...) // Client sends ACK (server role) |
| 71 | + static decodeAcknowledge(buffer, ...) // Client receives ACK |
| 72 | +} |
| 73 | +``` |
| 74 | + |
| 75 | +`ServicesMap` in `services/index.ts` maps event names (e.g., `"readProperty"`) to service classes for dynamic dispatch. |
| 76 | + |
| 77 | +### Transport (`src/lib/transport.ts`) |
| 78 | + |
| 79 | +Wraps a `dgram` UDP4 socket. Default port: `47808` (0xBAC0). Handles broadcast deduplication (10s window). Injectable via `ClientOptions.transport` for testing. |
| 80 | + |
| 81 | +### RequestManager (`src/lib/request-manager.ts`) |
| 82 | + |
| 83 | +Maps invoke IDs to `Deferred<NetworkOpResult>` promises. Handles timeout sweeping. Timeout function is injectable for test mocking. |
| 84 | + |
| 85 | +### Key Entry Point (`src/index.ts`) |
| 86 | + |
| 87 | +Thin re-export: `BACnetClient` as default export, plus all enums, types, and bitstring classes. |
| 88 | + |
| 89 | +## Testing Tiers |
| 90 | + |
| 91 | +- **Unit** (`test/unit/`): Encode/decode in isolation. Uses `getBuffer()` helper from `test/unit/utils.ts`. |
| 92 | +- **Integration** (`test/integration/`): Uses `TransportStub` (mock EventEmitter) injected into `BACnetClient`. Simulates packets via `transportStub.emit('message', buffer, addr)`. |
| 93 | +- **Compliance** (`test/compliance/`): Runs against the live emulator. Sequential execution (`--test-concurrency=1`). |
| 94 | + |
| 95 | +## Key Types and Conventions |
| 96 | + |
| 97 | +- `EncodeBuffer`: `{ buffer: Buffer, offset: number }` — mutable cursor passed through all encode layers |
| 98 | +- `BACNetObjectID`: `{ type: ObjectType, instance: number }` |
| 99 | +- `TypedValue<Tag>`: `{ type: ApplicationTag, value: ... }` — generic typed BACnet value |
| 100 | +- `Decode<T>`: `{ len, value }` — standard decode return shape |
| 101 | +- Enums in `src/lib/enum.ts`: `ObjectType`, `PropertyIdentifier`, `ApplicationTag`, `ConfirmedServiceChoice`, `UnconfirmedServiceChoice`, `PduType`, `ErrorClass`, `ErrorCode` |
| 102 | +- Bitstrings in `src/lib/bitstring.ts`: `GenericBitString<E>`, `StatusFlagsBitString`, `ServicesSupportedBitString` |
| 103 | + |
| 104 | +## ASHRAE 135 Standard Reference |
| 105 | + |
| 106 | +This library implements [ASHRAE Standard 135 (BACnet)](https://www.ashrae.org/technical-resources/standards-and-guidelines/read-only-versions-of-ashrae-standards). Always consult the official specification when reviewing or implementing features: |
| 107 | + |
| 108 | +- **[Standard 135-2024](https://ashrae.iwrapper.com/ASHRAE_PREVIEW_ONLY_STANDARDS/STD_135_2024)** — Current version |
| 109 | +- **[Standard 135-2020](https://ashrae.iwrapper.com/ASHRAE_PREVIEW_ONLY_STANDARDS/STD_135_2020)** — Previous version |
| 110 | + |
| 111 | +When implementing or modifying protocol features, reference the relevant standard section in a code comment with a link when possible. Example: |
| 112 | + |
| 113 | +```typescript |
| 114 | +// Encode object identifier per ASHRAE 135-2024 §20.2.14 |
| 115 | +// https://ashrae.iwrapper.com/ASHRAE_PREVIEW_ONLY_STANDARDS/STD_135_2024 |
| 116 | +encodeApplicationObjectId(buffer, objectType, instance) |
39 | 117 | ``` |
| 118 | + |
| 119 | +## Coding Standards |
| 120 | + |
| 121 | +Detailed standards are in **[.github/copilot-instructions.md](.github/copilot-instructions.md)**. Key points: |
| 122 | + |
| 123 | +- Strict TypeScript (`noImplicitAny`, `strictNullChecks`) |
| 124 | +- Files: camelCase. Classes: PascalCase. Enums: PascalCase with ALL_CAPS values. Private members: underscore prefix. |
| 125 | +- `interface` over `type` for object shapes |
| 126 | +- Conventional Commits: `feat|fix|docs|test|chore|refactor|perf|ci[scope]: description` |
| 127 | +- Scopes: `client`, `transport`, `services`, `asn1`, `enum`, `types`, `apdu`, `npdu`, `bvlc` |
| 128 | +- Debug logging: `debug` library with `bacnet:module:level` namespaces |
0 commit comments