Files
zigbee2mqtt/.github/copilot-instructions.md

460 lines
17 KiB
Markdown

# GitHub Copilot Instructions
## Priority Guidelines
When generating code for this repository:
1. **Version Compatibility**: Always detect and respect the exact versions of languages, frameworks, and libraries used in this project
2. **Context Files**: Prioritize patterns and standards defined in the .github/copilot directory
3. **Codebase Patterns**: When context files don't provide specific guidance, scan the codebase for established patterns
4. **Architectural Consistency**: Maintain our layered architectural style with clear separation between controller, extensions, models, and utilities
5. **Code Quality**: Prioritize maintainability, performance, security, and testability in all generated code
## Technology Stack
### Core Technologies
- **Language**: TypeScript 5.9.3 with target `esnext` and module `NodeNext`
- **Runtime**: Node.js ^20 || ^22 || ^24
- **Package Manager**: pnpm 10.12.1
- **Testing**: Vitest 3.1.1 with @vitest/coverage-v8
- **Linting/Formatting**: Biome 2.2.5 (configured with 4-space indents, 150 line width, no bracket spacing)
### Key Dependencies
- **zigbee-herdsman**: 6.2.0 (exact version - critical for Zigbee protocol compatibility)
- **zigbee-herdsman-converters**: 25.42.0 (exact version - device definitions)
- **MQTT**: mqtt 5.14.1
- **Logging**: winston 3.18.3
- **YAML**: js-yaml 4.1.0
- **Decorators**: bind-decorator 1.0.11
- **WebSocket**: ws 8.18.1
### TypeScript Configuration
- **Strict Mode**: Enabled with `noImplicitAny` and `noImplicitThis`
- **Module System**: NodeNext with ESM interop
- **Decorators**: Experimental decorators enabled
- **Composite**: True (for project references)
- **Source Maps**: Inline source maps enabled
- **Output**: Compiled to `dist/` directory
## Project Architecture
### Directory Structure
```
lib/ # Source TypeScript files
├── controller.ts # Main controller orchestrating all components
├── mqtt.ts # MQTT client management
├── zigbee.ts # Zigbee network management
├── state.ts # State management
├── eventBus.ts # Event-driven communication
├── extension/ # Extension system (plugins)
│ ├── extension.ts # Abstract base class
│ ├── availability.ts
│ ├── bind.ts
│ ├── bridge.ts
│ ├── configure.ts
│ └── ...
├── model/ # Domain models
│ ├── device.ts
│ └── group.ts
├── util/ # Utility functions
│ ├── logger.ts
│ ├── settings.ts
│ ├── utils.ts
│ └── ...
└── types/ # TypeScript type definitions
└── api.ts
test/ # Vitest test files
data/ # Runtime configuration and data
```
### Architectural Patterns
#### Extension Pattern
All extensions inherit from the abstract `Extension` base class:
```typescript
abstract class Extension {
protected zigbee: Zigbee;
protected mqtt: Mqtt;
protected state: State;
protected publishEntityState: PublishEntityState;
protected eventBus: EventBus;
async start(): Promise<void> {}
async stop(): Promise<void> {}
}
```
#### Event-Driven Architecture
Use the `EventBus` for component communication. Events are strongly typed:
```typescript
interface EventBusMap {
deviceMessage: [data: eventdata.DeviceMessage];
mqttMessage: [data: eventdata.MQTTMessage];
publishEntityState: [data: eventdata.PublishEntityState];
// ... other events
}
```
#### Dependency Injection
The `Controller` class instantiates and injects dependencies into extensions. Follow this pattern when creating new extensions.
## Code Style and Conventions
### Naming Conventions
- **Classes**: PascalCase (e.g., `Extension`, `Device`, `EventBus`)
- **Interfaces/Types**: PascalCase (e.g., `MqttPublishOptions`, `DeviceOptions`)
- **Functions/Methods**: camelCase (e.g., `publishEntityState`, `enableDisableExtension`)
- **Constants**: SCREAMING_SNAKE_CASE for top-level constants (e.g., `CURRENT_VERSION`, `LOG_LEVELS`)
- **Private members**: Prefix with underscore for private class fields only when needed to distinguish from public properties (e.g., `_definitionModelID`)
- **Files**: camelCase for TypeScript files (e.g., `eventBus.ts`, `externalJS.ts`)
### Import Organization
Follow this import order (separated by blank lines):
1. Node.js built-in modules (use `node:` prefix: `import fs from "node:fs"`)
2. Third-party libraries (e.g., `bind-decorator`, `mqtt`)
3. Type-only imports from external packages (using `type` keyword)
4. Internal absolute imports from project root
5. Type-only imports from internal modules
Example:
```typescript
import fs from "node:fs";
import bind from "bind-decorator";
import type {IClientOptions} from "mqtt";
import {connectAsync} from "mqtt";
import type {Zigbee2MQTTAPI} from "./types/api";
import logger from "./util/logger";
import * as settings from "./util/settings";
```
### Type Annotations
- Use `type` imports for TypeScript types: `import type * as zhc from "zigbee-herdsman-converters"`
- Explicitly type function parameters and return types
- Use `KeyValue` type for generic object payloads: `type KeyValue = Record<string, any>`
- Prefer interfaces for object shapes, type aliases for unions/intersections
- Use namespace exports for related types: `export type * as ZSpec from "zigbee-herdsman/dist/zspec"`
### Async/Await Patterns
- Always use `async/await` for asynchronous operations
- Return types should be explicitly `Promise<Type>`
- Methods that don't return values should be `Promise<void>`
- Use `Awaited<ReturnType<typeof fn>>` for inferring async function return types
### Decorators
Use `@bind` decorator from `bind-decorator` for methods that need `this` binding:
```typescript
@bind async onMQTTMessage(data: eventdata.MQTTMessage): Promise<void> {
// Implementation
}
```
### Error Handling
- Use `throw new Error("message")` for explicit errors
- Include descriptive error messages
- Log errors using the logger: `logger.error("message")`
- For Zigbee-herdsman errors, log the stack trace: `logger.error((error as Error).stack!)`
- Catch and handle errors at appropriate boundaries (controller level)
### Logging
Use the centralized logger (winston-based):
```typescript
import logger from "./util/logger";
logger.info("message");
logger.warning("message");
logger.error("message");
logger.debug("message");
```
- Use namespaced loggers for specific modules (created internally by logger)
- Log levels: `error`, `warning`, `info`, `debug` (from most to least critical)
- Include relevant context in log messages (device names, IEEE addresses, etc.)
## Code Quality Standards
### Maintainability
- Write self-documenting code with clear, descriptive names
- Keep methods focused on single responsibilities
- Abstract classes should define clear contracts with protected members for subclasses
- Use constructor dependency injection for required dependencies
- Limit function complexity - methods should be concise and focused
- Use TypeScript's strict mode features (`noImplicitAny`, `noImplicitThis`)
### Performance
- Use `rimrafSync` for synchronous file deletion when appropriate
- Leverage async/await for I/O operations to avoid blocking
- Use JSON stable stringify for consistent object serialization: `json-stable-stringify-without-jsonify`
- Cache computed values when appropriate (see device model patterns)
- Use getter methods for computed properties that should be cached
### Security
- Validate input using Ajv JSON schema validation (see `settings.ts` pattern)
- Sanitize file paths using `path.join` from Node.js
- Use YAML safe loading: `yaml.safeLoad()`
- Handle sensitive data (credentials, tokens) through settings with proper defaults
- Never log sensitive information (passwords, tokens)
### Testability
- Write tests using Vitest with describe/it/expect patterns
- Mock external dependencies using Vitest's `vi.mock()`
- Use `beforeEach`, `afterEach`, `beforeAll`, `afterAll` for test setup/teardown
- Place test files in `test/` directory with `.test.ts` extension
- Mock constructors and modules in the pattern shown in `test/controller.test.ts`
- Use `flushPromises()` utility for async test synchronization
- Target 100% code coverage (configured in vitest.config.mts)
## Testing Standards
### Unit Testing Structure
```typescript
import {afterAll, beforeAll, beforeEach, describe, expect, it, vi} from "vitest";
describe("ComponentName", () => {
beforeEach(() => {
// Setup
});
it("Should do something specific", async () => {
// Arrange
const input = {};
// Act
const result = await someFunction(input);
// Assert
expect(result).toBe(expected);
});
});
```
### Mocking Patterns
- Create mock modules in `test/mocks/` directory
- Use `vi.fn()` for function mocks
- Use `vi.mock()` for module mocks
- Clear mocks in `afterEach` or between tests
- Mock external libraries like `mqtt`, `zigbee-herdsman` consistently
### Test Coverage
- All code in `lib/**` should be covered
- Use coverage reports: `pnpm test:coverage`
- Thresholds set to 100% (can be adjusted per project needs)
- Tests should cover both success and failure paths
## Documentation Standards
### JSDoc Comments
Use JSDoc-style comments for classes and public methods:
```typescript
/**
* Besides initializing variables, the constructor should do nothing!
*
* @param {Zigbee} zigbee Zigbee controller
* @param {Mqtt} mqtt MQTT controller
* @param {State} state State controller
* @param {Function} publishEntityState Method to publish device state to MQTT.
* @param {EventBus} eventBus The event bus
*/
constructor(zigbee: Zigbee, mqtt: Mqtt, state: State, ...) {
```
### Comment Style
- Use single-line comments (`//`) for implementation notes
- Use JSDoc (`/** */`) for public APIs and class/method documentation
- Include context for non-obvious logic
- Document parameters with their types and purposes
- Use `@param` tags with TypeScript types in braces
- Use biome-ignore comments when necessary: `// biome-ignore lint/rule: reason`
### Code Documentation
- Document complex algorithms or business logic
- Explain "why" not just "what" when logic is non-trivial
- Include links to relevant issues or documentation when applicable
- Document deprecations and breaking changes
## TypeScript-Specific Guidelines
### Module System
- Use ES modules with `import`/`export` syntax
- Default exports for main classes: `export default class Device {}`
- Named exports for utilities and types: `export const LOG_LEVELS = ...`
- Namespace exports for related types: `export type * as ZSpec from ...`
- Use `.js` extension in imports for local modules when using dynamic imports: `await import("./extension/frontend.js")`
### Type Safety
- Enable all strict type checking options
- Use type guards and assertions when necessary: `asserts expose is zhc.Numeric`
- Prefer `unknown` over `any` when type is truly unknown
- Use `// biome-ignore lint/suspicious/noExplicitAny: API` when `any` is necessary
- Define proper interfaces for external module types (e.g., `unix-dgram.d.ts`)
### Generic Types
- Use generics for reusable, type-safe abstractions
- Example: `abstract class ExternalJSExtension<M> extends Extension`
- Constrain generics when appropriate
- Document generic type parameters
### Utility Types
- Use built-in utility types: `Partial`, `Required`, `Pick`, `Omit`, `Record`
- Use `Awaited<ReturnType<typeof fn>>` for async function return types
- Define custom utility types when patterns emerge
- Use `type` for aliases, `interface` for object shapes
## Version Control and Releases
### Versioning Strategy
- Follow Semantic Versioning (MAJOR.MINOR.PATCH)
- Current version managed in `package.json`
- Use `-dev` suffix for development versions (e.g., `2.6.2-dev`)
- Configuration version tracked separately: `CURRENT_VERSION = 4`
### Changelog
- Maintain CHANGELOG.md with all changes
- Group changes by type: Bug Fixes, Features, Breaking Changes
- Include issue/PR references: `([#28583](url))`
- Include commit references: `([09f33b3](url))`
- Use conventional commits format
### Git Workflow
- Development on `dev` branch
- Production releases from `master` branch
- Use meaningful commit messages
- Reference issues in commits
## Project-Specific Patterns
### Settings Management
- All configuration loaded through `util/settings.ts`
- Validate settings using Ajv with JSON schema
- Schema defined in `settings.schema.json`
- Support runtime setting changes with restart detection
- Use `settings.get()` to access current configuration
- Use `settings.getDevice(ieeeAddr)` for device-specific config
### Device and Group Models
- Devices and groups are domain models wrapping `zigbee-herdsman` entities
- Access underlying entity via `.zh` property
- Expose computed properties as getters
- Include definition from `zigbee-herdsman-converters`
- Handle coordinator devices specially (type checking)
### MQTT Integration
- MQTT client wrapped in `Mqtt` class
- Publish options: `retain`, `qos` properties
- Topics follow pattern: `{base_topic}/{device}/{attribute}`
- Event-based message handling via EventBus
- Clean disconnect handling with retry logic
### Extension System
- Extensions are loosely coupled plugins
- Lifecycle: constructor → start() → stop()
- Constructor should only assign properties (no side effects)
- Use EventBus for inter-extension communication
- Extensions can be enabled/disabled at runtime
- External extensions loaded from `data/external_extensions/`
### State Management
- State persisted to `state.json`
- Cached in memory for performance
- Device states include all exposed attributes
- State changes trigger events via EventBus
## Best Practices Specific to This Project
1. **Never use language features beyond TypeScript 5.9.3 or ES2024**
2. **Always respect exact versions of zigbee-herdsman and zigbee-herdsman-converters** - these are critical for device compatibility
3. **Use the EventBus for all component communication** - avoid direct coupling
4. **Follow the Extension pattern for new features** - don't add logic directly to Controller
5. **Log appropriately** - info for user-relevant events, debug for developer info, error for failures
6. **Test with real Zigbee scenarios** - many edge cases exist with different device types
7. **Handle coordinator specially** - coordinator is a device but with unique behavior
8. **Validate all external input** - MQTT messages, configuration files, device data
9. **Use the bind decorator** for event handlers to preserve `this` context
10. **Match the exact code formatting** - Biome enforces 4 spaces, 150 line width, no bracket spacing
## Common Patterns to Follow
### Creating a New Extension
1. Extend `Extension` abstract class
2. Accept all dependencies in constructor
3. Implement `start()` method for initialization
4. Subscribe to EventBus events in `start()`
5. Implement `stop()` method for cleanup
6. Export as default: `export default class MyExtension extends Extension`
### Accessing Device Information
```typescript
const device: Device; // Our wrapper
device.ieeeAddr; // IEEE address
device.name; // Friendly name
device.zh; // Underlying zigbee-herdsman device
device.definition; // zigbee-herdsman-converters definition
device.options; // User configuration
```
### Publishing MQTT Messages
```typescript
await this.mqtt.publish(topic, message, {retain: true, qos: 0});
```
### Emitting Events
```typescript
this.eventBus.emit('deviceMessage', {device, message});
```
### Listening to Events
```typescript
this.eventBus.on('deviceMessage', this.onDeviceMessage, this);
```
## Integration Points
### Zigbee-Herdsman Integration
- Start controller: `await this.zigbee.start()`
- Access coordinator: `this.zigbee.coordinator()`
- Device operations through `zigbee-herdsman` API
- Event handling through EventBus wrappers
### MQTT Integration
- Connect: `await this.mqtt.connect()`
- Subscribe: `await this.mqtt.subscribe(topic)`
- Publish: `await this.mqtt.publish(topic, message, options)`
- Handle messages via EventBus `mqttMessage` event
### Frontend Integration
- Optional extension loaded dynamically
- Serves static files with compression
- WebSocket support for real-time updates
- Configurable port and base URL
### Home Assistant Integration
- Optional extension for discovery
- Publishes discovery messages to MQTT
- Supports entities, sensors, and devices
- Configurable discovery topic
## Critical Compatibility Notes
1. **Node.js**: Only versions 20, 22, and 24 are supported
2. **TypeScript**: Features must be compatible with 5.9.3
3. **Zigbee Libraries**: Exact versions are critical - do not suggest upgrades without testing
4. **MQTT Protocol**: Uses MQTT 3.1.1 and 5.0 features
5. **ES Modules**: Project uses ESM with NodeNext resolution
6. **Experimental Decorators**: Required for `@bind` decorator support
## When in Doubt
1. **Search for similar patterns** in the existing codebase
2. **Check existing extensions** for implementation examples
3. **Follow the controller and extension architecture** - don't bypass it
4. **Consult the test files** for usage examples
5. **Match the exact style** - run `pnpm check` to verify
6. **Prioritize consistency** over external best practices
7. **Test thoroughly** - this project controls real hardware
## Resources
- Repository: https://github.com/Koenkk/zigbee2mqtt
- Documentation: https://koenkk.github.io/zigbee2mqtt
- License: GPL-3.0
- Issue Tracker: https://github.com/Koenkk/zigbee2mqtt/issues