@ya-modbus/mqtt-bridge 0.4.1-refactor-scope-driver-packages.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +41 -0
- package/LICENSE +674 -0
- package/README.md +190 -0
- package/dist/bin/ya-modbus-bridge.d.ts +9 -0
- package/dist/bin/ya-modbus-bridge.d.ts.map +1 -0
- package/dist/bin/ya-modbus-bridge.js +10 -0
- package/dist/bin/ya-modbus-bridge.js.map +1 -0
- package/dist/src/cli.d.ts +4 -0
- package/dist/src/cli.d.ts.map +1 -0
- package/dist/src/cli.js +109 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/device-manager.d.ts +17 -0
- package/dist/src/device-manager.d.ts.map +1 -0
- package/dist/src/device-manager.js +79 -0
- package/dist/src/device-manager.js.map +1 -0
- package/dist/src/driver-loader.d.ts +53 -0
- package/dist/src/driver-loader.d.ts.map +1 -0
- package/dist/src/driver-loader.js +120 -0
- package/dist/src/driver-loader.js.map +1 -0
- package/dist/src/index.d.ts +13 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +285 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/polling-scheduler.d.ts +48 -0
- package/dist/src/polling-scheduler.d.ts.map +1 -0
- package/dist/src/polling-scheduler.js +128 -0
- package/dist/src/polling-scheduler.js.map +1 -0
- package/dist/src/types.d.ts +86 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +2 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/utils/__mocks__/package-info.d.ts +9 -0
- package/dist/src/utils/__mocks__/package-info.d.ts.map +1 -0
- package/dist/src/utils/__mocks__/package-info.js +11 -0
- package/dist/src/utils/__mocks__/package-info.js.map +1 -0
- package/dist/src/utils/__mocks__/process.d.ts +20 -0
- package/dist/src/utils/__mocks__/process.d.ts.map +1 -0
- package/dist/src/utils/__mocks__/process.js +37 -0
- package/dist/src/utils/__mocks__/process.js.map +1 -0
- package/dist/src/utils/config-validator.d.ts +3 -0
- package/dist/src/utils/config-validator.d.ts.map +1 -0
- package/dist/src/utils/config-validator.js +32 -0
- package/dist/src/utils/config-validator.js.map +1 -0
- package/dist/src/utils/config.d.ts +3 -0
- package/dist/src/utils/config.d.ts.map +1 -0
- package/dist/src/utils/config.js +52 -0
- package/dist/src/utils/config.js.map +1 -0
- package/dist/src/utils/device-validation.d.ts +31 -0
- package/dist/src/utils/device-validation.d.ts.map +1 -0
- package/dist/src/utils/device-validation.js +70 -0
- package/dist/src/utils/device-validation.js.map +1 -0
- package/dist/src/utils/package-info.d.ts +5 -0
- package/dist/src/utils/package-info.d.ts.map +1 -0
- package/dist/src/utils/package-info.js +11 -0
- package/dist/src/utils/package-info.js.map +1 -0
- package/dist/src/utils/process.d.ts +10 -0
- package/dist/src/utils/process.d.ts.map +1 -0
- package/dist/src/utils/process.js +13 -0
- package/dist/src/utils/process.js.map +1 -0
- package/dist/src/utils/test-utils.d.ts +313 -0
- package/dist/src/utils/test-utils.d.ts.map +1 -0
- package/dist/src/utils/test-utils.js +535 -0
- package/dist/src/utils/test-utils.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +63 -0
package/README.md
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# @ya-modbus/mqtt-bridge
|
|
2
|
+
|
|
3
|
+
MQTT bridge for ya-modbus - orchestrates device management, polling, and MQTT publishing.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The MQTT bridge provides the foundational layer for connecting Modbus devices to MQTT:
|
|
8
|
+
|
|
9
|
+
### Current Features (Phase 1)
|
|
10
|
+
|
|
11
|
+
- **MQTT Connection Management** - Connect to MQTT brokers with authentication
|
|
12
|
+
- **Topic Publish/Subscribe** - Publish and subscribe to MQTT topics with QoS support
|
|
13
|
+
- **Device Registry** - Add, remove, and track devices
|
|
14
|
+
- **Graceful Lifecycle** - Start, stop, and status reporting
|
|
15
|
+
|
|
16
|
+
### Future Features
|
|
17
|
+
|
|
18
|
+
- Driver integration and polling coordination (Phase 2)
|
|
19
|
+
- State persistence and MQTT configuration topics (Phase 3)
|
|
20
|
+
|
|
21
|
+
See [Architecture documentation](./docs/ARCHITECTURE.md) for complete details and roadmap.
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install @ya-modbus/mqtt-bridge
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## CLI Reference
|
|
30
|
+
|
|
31
|
+
The `ya-modbus-bridge` command-line tool provides the following commands:
|
|
32
|
+
|
|
33
|
+
### Commands
|
|
34
|
+
|
|
35
|
+
- `run` - Run the MQTT bridge with a configuration file
|
|
36
|
+
|
|
37
|
+
### Options
|
|
38
|
+
|
|
39
|
+
**Configuration:**
|
|
40
|
+
|
|
41
|
+
- `-c, --config <path>` - Path to configuration file (optional if using CLI options)
|
|
42
|
+
- `--mqtt-url <url>` - MQTT broker URL (mqtt://, mqtts://, ws://, wss://)
|
|
43
|
+
- `--mqtt-client-id <id>` - MQTT client identifier
|
|
44
|
+
- `--mqtt-username <username>` - MQTT authentication username
|
|
45
|
+
- `--mqtt-password <password>` - MQTT authentication password
|
|
46
|
+
- `--mqtt-reconnect-period <ms>` - Reconnection interval in milliseconds
|
|
47
|
+
- `--topic-prefix <prefix>` - Topic prefix for all MQTT topics (default: modbus)
|
|
48
|
+
- `--state-dir <path>` - Directory path for state persistence
|
|
49
|
+
|
|
50
|
+
**General:**
|
|
51
|
+
|
|
52
|
+
- `-h, --help` - Display help for command
|
|
53
|
+
- `-V, --version` - Output version number
|
|
54
|
+
|
|
55
|
+
### Examples
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# Run with config file
|
|
59
|
+
ya-modbus-bridge run --config /path/to/config.json
|
|
60
|
+
|
|
61
|
+
# Run with CLI options only
|
|
62
|
+
ya-modbus-bridge run --mqtt-url mqtt://localhost:1883
|
|
63
|
+
|
|
64
|
+
# Run with config file and override options
|
|
65
|
+
ya-modbus-bridge run --config config.json --mqtt-url mqtt://broker.example.com:1883
|
|
66
|
+
|
|
67
|
+
# Run with authentication
|
|
68
|
+
ya-modbus-bridge run --mqtt-url mqtt://broker.example.com:1883 \
|
|
69
|
+
--mqtt-username user --mqtt-password pass
|
|
70
|
+
|
|
71
|
+
# Show help
|
|
72
|
+
ya-modbus-bridge --help
|
|
73
|
+
ya-modbus-bridge run --help
|
|
74
|
+
|
|
75
|
+
# Show version
|
|
76
|
+
ya-modbus-bridge --version
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## CLI Usage
|
|
80
|
+
|
|
81
|
+
Run the MQTT bridge using the command-line interface:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
# Run with configuration file
|
|
85
|
+
ya-modbus-bridge run --config config.json
|
|
86
|
+
|
|
87
|
+
# Show help
|
|
88
|
+
ya-modbus-bridge --help
|
|
89
|
+
|
|
90
|
+
# Show version
|
|
91
|
+
ya-modbus-bridge --version
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Configuration File
|
|
95
|
+
|
|
96
|
+
Create a `config.json` file:
|
|
97
|
+
|
|
98
|
+
```json
|
|
99
|
+
{
|
|
100
|
+
"mqtt": {
|
|
101
|
+
"url": "mqtt://localhost:1883",
|
|
102
|
+
"clientId": "modbus-bridge",
|
|
103
|
+
"username": "user",
|
|
104
|
+
"password": "pass"
|
|
105
|
+
},
|
|
106
|
+
"topicPrefix": "modbus"
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**Configuration options:**
|
|
111
|
+
|
|
112
|
+
- `mqtt.url` (required) - MQTT broker URL (mqtt://, mqtts://, ws://, wss://)
|
|
113
|
+
- `mqtt.clientId` (optional) - MQTT client identifier
|
|
114
|
+
- `mqtt.username` (optional) - Authentication username
|
|
115
|
+
- `mqtt.password` (optional) - Authentication password
|
|
116
|
+
- `mqtt.reconnectPeriod` (optional) - Reconnection interval in milliseconds (default: 5000)
|
|
117
|
+
- `topicPrefix` (optional) - Topic prefix for all MQTT topics (default: 'modbus')
|
|
118
|
+
- `stateDir` (optional) - Directory path for state persistence (future)
|
|
119
|
+
|
|
120
|
+
## Programmatic Usage
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
import { createBridge } from '@ya-modbus/mqtt-bridge'
|
|
124
|
+
|
|
125
|
+
const bridge = createBridge({
|
|
126
|
+
mqtt: {
|
|
127
|
+
url: 'mqtt://localhost:1883',
|
|
128
|
+
clientId: 'modbus-bridge',
|
|
129
|
+
},
|
|
130
|
+
topicPrefix: 'modbus',
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
await bridge.start()
|
|
134
|
+
|
|
135
|
+
// Publish to topic
|
|
136
|
+
await bridge.publish('device1/data', JSON.stringify({ temp: 25.5 }))
|
|
137
|
+
|
|
138
|
+
// Subscribe to topic
|
|
139
|
+
await bridge.subscribe('device1/cmd', (message) => {
|
|
140
|
+
console.log('Received:', message.payload.toString())
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
// Add device
|
|
144
|
+
await bridge.addDevice({
|
|
145
|
+
deviceId: 'device1',
|
|
146
|
+
driver: 'ya-modbus-driver-example',
|
|
147
|
+
connection: {
|
|
148
|
+
type: 'tcp',
|
|
149
|
+
host: '192.168.1.100',
|
|
150
|
+
port: 502,
|
|
151
|
+
slaveId: 1,
|
|
152
|
+
},
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
// List devices
|
|
156
|
+
const devices = bridge.listDevices()
|
|
157
|
+
|
|
158
|
+
// Stop bridge
|
|
159
|
+
await bridge.stop()
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Architecture
|
|
163
|
+
|
|
164
|
+
See [docs/ARCHITECTURE.md](./docs/ARCHITECTURE.md) for complete bridge architecture including:
|
|
165
|
+
|
|
166
|
+
- Component structure
|
|
167
|
+
- MQTT topic structure
|
|
168
|
+
- State management
|
|
169
|
+
- Lifecycle and event handling
|
|
170
|
+
- Data flow and validation
|
|
171
|
+
|
|
172
|
+
## Development
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
# Build
|
|
176
|
+
npm run build
|
|
177
|
+
|
|
178
|
+
# Test
|
|
179
|
+
npm test
|
|
180
|
+
|
|
181
|
+
# Lint
|
|
182
|
+
npm run lint
|
|
183
|
+
|
|
184
|
+
# Clean
|
|
185
|
+
npm run clean
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## License
|
|
189
|
+
|
|
190
|
+
GPL-3.0-or-later
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ya-modbus-bridge.d.ts","sourceRoot":"","sources":["../../bin/ya-modbus-bridge.ts"],"names":[],"mappings":";AAEA;;;;;GAKG"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* CLI entry point
|
|
4
|
+
*
|
|
5
|
+
* This file is the executable entry point that parses command-line arguments.
|
|
6
|
+
* The actual CLI implementation is in src/cli.ts
|
|
7
|
+
*/
|
|
8
|
+
import { program } from '../src/cli.js';
|
|
9
|
+
program.parse();
|
|
10
|
+
//# sourceMappingURL=ya-modbus-bridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ya-modbus-bridge.js","sourceRoot":"","sources":["../../bin/ya-modbus-bridge.ts"],"names":[],"mappings":";AAEA;;;;;GAKG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AAEvC,OAAO,CAAC,KAAK,EAAE,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":";AAGA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAUnC,eAAO,MAAM,OAAO,SAAgB,CAAA"}
|
package/dist/src/cli.js
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import { validateConfig } from './utils/config-validator.js';
|
|
5
|
+
import { loadConfig } from './utils/config.js';
|
|
6
|
+
import { getPackageInfo } from './utils/package-info.js';
|
|
7
|
+
import { processUtils } from './utils/process.js';
|
|
8
|
+
import { createBridge } from './index.js';
|
|
9
|
+
export const program = new Command();
|
|
10
|
+
const packageInfo = getPackageInfo();
|
|
11
|
+
program.name('ya-modbus-bridge').description(packageInfo.description).version(packageInfo.version);
|
|
12
|
+
// Error handler for command execution
|
|
13
|
+
program.exitOverride(); // Prevent automatic exit, let us handle errors
|
|
14
|
+
program.configureOutput({
|
|
15
|
+
/* istanbul ignore next - commander internal error output */
|
|
16
|
+
writeErr: (str) => process.stderr.write(str),
|
|
17
|
+
});
|
|
18
|
+
program
|
|
19
|
+
.command('run')
|
|
20
|
+
.description('Run the MQTT bridge')
|
|
21
|
+
.option('-c, --config <path>', 'Path to configuration file')
|
|
22
|
+
.option('--mqtt-url <url>', 'MQTT broker URL (mqtt://, mqtts://, ws://, wss://)')
|
|
23
|
+
.option('--mqtt-client-id <id>', 'MQTT client identifier')
|
|
24
|
+
.option('--mqtt-username <username>', 'MQTT authentication username')
|
|
25
|
+
.option('--mqtt-password <password>', 'MQTT authentication password')
|
|
26
|
+
.option('--mqtt-reconnect-period <ms>', 'Reconnection interval in milliseconds', parseInt)
|
|
27
|
+
.option('--topic-prefix <prefix>', 'Topic prefix for all MQTT topics (default: modbus)')
|
|
28
|
+
.option('--state-dir <path>', 'Directory path for state persistence')
|
|
29
|
+
.action(async (options) => {
|
|
30
|
+
try {
|
|
31
|
+
await runCommand(options);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
console.error(chalk.red('Error:'), error instanceof Error ? error.message : String(error));
|
|
35
|
+
processUtils.exit(1);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
async function runCommand(options) {
|
|
39
|
+
console.log(chalk.blue('Loading configuration...'));
|
|
40
|
+
let config;
|
|
41
|
+
if (options.config) {
|
|
42
|
+
// Load from file
|
|
43
|
+
config = await loadConfig(options.config);
|
|
44
|
+
// Override with CLI options if provided
|
|
45
|
+
if (options.mqttUrl)
|
|
46
|
+
config.mqtt.url = options.mqttUrl;
|
|
47
|
+
if (options.mqttClientId)
|
|
48
|
+
config.mqtt.clientId = options.mqttClientId;
|
|
49
|
+
if (options.mqttUsername)
|
|
50
|
+
config.mqtt.username = options.mqttUsername;
|
|
51
|
+
if (options.mqttPassword)
|
|
52
|
+
config.mqtt.password = options.mqttPassword;
|
|
53
|
+
if (options.mqttReconnectPeriod)
|
|
54
|
+
config.mqtt.reconnectPeriod = options.mqttReconnectPeriod;
|
|
55
|
+
if (options.topicPrefix)
|
|
56
|
+
config.topicPrefix = options.topicPrefix;
|
|
57
|
+
if (options.stateDir)
|
|
58
|
+
config.stateDir = options.stateDir;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
// Build from CLI options only
|
|
62
|
+
if (!options.mqttUrl) {
|
|
63
|
+
throw new Error('Either --config or --mqtt-url must be provided');
|
|
64
|
+
}
|
|
65
|
+
config = {
|
|
66
|
+
mqtt: {
|
|
67
|
+
url: options.mqttUrl,
|
|
68
|
+
...(options.mqttClientId && { clientId: options.mqttClientId }),
|
|
69
|
+
...(options.mqttUsername && { username: options.mqttUsername }),
|
|
70
|
+
...(options.mqttPassword && { password: options.mqttPassword }),
|
|
71
|
+
...(options.mqttReconnectPeriod && { reconnectPeriod: options.mqttReconnectPeriod }),
|
|
72
|
+
},
|
|
73
|
+
...(options.topicPrefix && { topicPrefix: options.topicPrefix }),
|
|
74
|
+
...(options.stateDir && { stateDir: options.stateDir }),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
// Validate configuration after merging CLI options
|
|
78
|
+
validateConfig(config);
|
|
79
|
+
// Sanitize URL to hide credentials
|
|
80
|
+
const sanitizedUrl = config.mqtt.url.replace(/:\/\/([^:]+):([^@]+)@/, '://$1:****@');
|
|
81
|
+
console.log(chalk.blue(`Starting MQTT bridge - connecting to ${sanitizedUrl}...`));
|
|
82
|
+
const bridge = createBridge(config);
|
|
83
|
+
await bridge.start();
|
|
84
|
+
console.log(chalk.green('Bridge started successfully'));
|
|
85
|
+
let isShuttingDown = false;
|
|
86
|
+
const handleShutdown = async (signal) => {
|
|
87
|
+
if (isShuttingDown) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
isShuttingDown = true;
|
|
91
|
+
console.log(chalk.yellow(`\nReceived ${signal}, shutting down...`));
|
|
92
|
+
await bridge.stop();
|
|
93
|
+
console.log(chalk.green('Bridge stopped'));
|
|
94
|
+
processUtils.exit(0);
|
|
95
|
+
};
|
|
96
|
+
processUtils.onSignal('SIGINT', () => {
|
|
97
|
+
handleShutdown('SIGINT').catch((err) => {
|
|
98
|
+
console.error(chalk.red('Shutdown error:'), err);
|
|
99
|
+
processUtils.exit(1);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
processUtils.onSignal('SIGTERM', () => {
|
|
103
|
+
handleShutdown('SIGTERM').catch((err) => {
|
|
104
|
+
console.error(chalk.red('Shutdown error:'), err);
|
|
105
|
+
processUtils.exit(1);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=cli.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { DriverLoader } from './driver-loader.js';
|
|
2
|
+
import type { DeviceConfig, DeviceStatus } from './types.js';
|
|
3
|
+
export declare class DeviceManager {
|
|
4
|
+
private driverLoader;
|
|
5
|
+
private devices;
|
|
6
|
+
private configs;
|
|
7
|
+
constructor(driverLoader: DriverLoader);
|
|
8
|
+
addDevice(config: DeviceConfig): Promise<void>;
|
|
9
|
+
removeDevice(deviceId: string): Promise<void>;
|
|
10
|
+
getDevice(deviceId: string): DeviceStatus | undefined;
|
|
11
|
+
listDevices(): DeviceStatus[];
|
|
12
|
+
getDeviceCount(): number;
|
|
13
|
+
updateDeviceState(deviceId: string, updates: Partial<Omit<DeviceStatus, 'deviceId'>>): void;
|
|
14
|
+
clear(): Promise<void>;
|
|
15
|
+
getDeviceConfig(deviceId: string): DeviceConfig | undefined;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=device-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device-manager.d.ts","sourceRoot":"","sources":["../../src/device-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACtD,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAG5D,qBAAa,aAAa;IAIZ,OAAO,CAAC,YAAY;IAHhC,OAAO,CAAC,OAAO,CAAkC;IACjD,OAAO,CAAC,OAAO,CAAkC;gBAE7B,YAAY,EAAE,YAAY;IAExC,SAAS,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAsC9C,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAUnD,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;IAIrD,WAAW,IAAI,YAAY,EAAE;IAI7B,cAAc,IAAI,MAAM;IAIxB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC,GAAG,IAAI;IAYrF,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAO5B,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;CAG5D"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { validateDeviceConfig } from './utils/device-validation.js';
|
|
2
|
+
export class DeviceManager {
|
|
3
|
+
driverLoader;
|
|
4
|
+
devices = new Map();
|
|
5
|
+
configs = new Map();
|
|
6
|
+
constructor(driverLoader) {
|
|
7
|
+
this.driverLoader = driverLoader;
|
|
8
|
+
}
|
|
9
|
+
async addDevice(config) {
|
|
10
|
+
validateDeviceConfig(config);
|
|
11
|
+
if (this.devices.has(config.deviceId)) {
|
|
12
|
+
throw new Error(`Device ${config.deviceId} already exists`);
|
|
13
|
+
}
|
|
14
|
+
const enabled = config.enabled ?? true;
|
|
15
|
+
const status = {
|
|
16
|
+
deviceId: config.deviceId,
|
|
17
|
+
state: 'disconnected',
|
|
18
|
+
enabled,
|
|
19
|
+
connected: false,
|
|
20
|
+
};
|
|
21
|
+
this.devices.set(config.deviceId, status);
|
|
22
|
+
this.configs.set(config.deviceId, config);
|
|
23
|
+
// Load driver if enabled
|
|
24
|
+
if (enabled) {
|
|
25
|
+
try {
|
|
26
|
+
this.updateDeviceState(config.deviceId, { state: 'connecting' });
|
|
27
|
+
await this.driverLoader.loadDriver(config.driver, config.connection, config.deviceId);
|
|
28
|
+
this.updateDeviceState(config.deviceId, {
|
|
29
|
+
state: 'connected',
|
|
30
|
+
connected: true,
|
|
31
|
+
lastUpdate: Date.now(),
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
// Clean up device state to allow retry (make operation atomic)
|
|
36
|
+
this.devices.delete(config.deviceId);
|
|
37
|
+
this.configs.delete(config.deviceId);
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async removeDevice(deviceId) {
|
|
43
|
+
if (!this.devices.has(deviceId)) {
|
|
44
|
+
throw new Error(`Device ${deviceId} not found`);
|
|
45
|
+
}
|
|
46
|
+
await this.driverLoader.unloadDriver(deviceId);
|
|
47
|
+
this.devices.delete(deviceId);
|
|
48
|
+
this.configs.delete(deviceId);
|
|
49
|
+
}
|
|
50
|
+
getDevice(deviceId) {
|
|
51
|
+
return this.devices.get(deviceId);
|
|
52
|
+
}
|
|
53
|
+
listDevices() {
|
|
54
|
+
return Array.from(this.devices.values());
|
|
55
|
+
}
|
|
56
|
+
getDeviceCount() {
|
|
57
|
+
return this.devices.size;
|
|
58
|
+
}
|
|
59
|
+
updateDeviceState(deviceId, updates) {
|
|
60
|
+
const device = this.devices.get(deviceId);
|
|
61
|
+
if (!device) {
|
|
62
|
+
throw new Error(`Device ${deviceId} not found`);
|
|
63
|
+
}
|
|
64
|
+
this.devices.set(deviceId, {
|
|
65
|
+
...device,
|
|
66
|
+
...updates,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
async clear() {
|
|
70
|
+
const deviceIds = Array.from(this.devices.keys());
|
|
71
|
+
await Promise.all(deviceIds.map((id) => this.driverLoader.unloadDriver(id)));
|
|
72
|
+
this.devices.clear();
|
|
73
|
+
this.configs.clear();
|
|
74
|
+
}
|
|
75
|
+
getDeviceConfig(deviceId) {
|
|
76
|
+
return this.configs.get(deviceId);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=device-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device-manager.js","sourceRoot":"","sources":["../../src/device-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { loadDriver as loadDriverPackage } from '@ya-modbus/driver-loader';
|
|
2
|
+
import type { DeviceDriver } from '@ya-modbus/driver-types';
|
|
3
|
+
import { TransportManager } from '@ya-modbus/transport';
|
|
4
|
+
import type { DeviceConnection } from './types.js';
|
|
5
|
+
type LoadDriverFunction = typeof loadDriverPackage;
|
|
6
|
+
/**
|
|
7
|
+
* Manages dynamic loading and lifecycle of device drivers
|
|
8
|
+
* Uses TransportManager to pool RTU transports and prevent bus collisions
|
|
9
|
+
*/
|
|
10
|
+
export declare class DriverLoader {
|
|
11
|
+
private readonly driverInstances;
|
|
12
|
+
private readonly devicePackages;
|
|
13
|
+
private readonly loadDriverFn;
|
|
14
|
+
private readonly transportManager;
|
|
15
|
+
constructor(loadDriverFn?: LoadDriverFunction, transportManager?: TransportManager);
|
|
16
|
+
/**
|
|
17
|
+
* Load a driver package and create an instance for a device
|
|
18
|
+
*
|
|
19
|
+
* @param packageName - NPM package name (e.g., '@ya-modbus/driver-xymd1' or 'ya-modbus-driver-test')
|
|
20
|
+
* @param connection - Device connection configuration
|
|
21
|
+
* @param deviceId - Unique device identifier for tracking the instance
|
|
22
|
+
* @returns Driver instance
|
|
23
|
+
*/
|
|
24
|
+
loadDriver(packageName: string, connection: DeviceConnection, deviceId?: string): Promise<DeviceDriver>;
|
|
25
|
+
/**
|
|
26
|
+
* Unload a driver instance and clean up resources
|
|
27
|
+
* Note: Transport is managed by TransportManager and not closed here
|
|
28
|
+
*
|
|
29
|
+
* @param deviceId - Device identifier
|
|
30
|
+
*/
|
|
31
|
+
unloadDriver(deviceId: string): Promise<void>;
|
|
32
|
+
/**
|
|
33
|
+
* Get a loaded driver instance
|
|
34
|
+
*
|
|
35
|
+
* @param deviceId - Device identifier
|
|
36
|
+
* @returns Driver instance or undefined if not loaded
|
|
37
|
+
*/
|
|
38
|
+
getDriver(deviceId: string): DeviceDriver | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* Close all managed transports
|
|
41
|
+
* Should be called when shutting down the bridge
|
|
42
|
+
*/
|
|
43
|
+
closeAllTransports(): Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* Convert DeviceConnection to TransportConfig
|
|
46
|
+
*
|
|
47
|
+
* @param connection - Device connection configuration
|
|
48
|
+
* @returns Transport configuration
|
|
49
|
+
*/
|
|
50
|
+
private connectionToTransportConfig;
|
|
51
|
+
}
|
|
52
|
+
export {};
|
|
53
|
+
//# sourceMappingURL=driver-loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"driver-loader.d.ts","sourceRoot":"","sources":["../../src/driver-loader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,IAAI,iBAAiB,EAAE,MAAM,0BAA0B,CAAA;AAC1E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AAC3D,OAAO,EAAE,gBAAgB,EAAwB,MAAM,sBAAsB,CAAA;AAE7E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAElD,KAAK,kBAAkB,GAAG,OAAO,iBAAiB,CAAA;AAElD;;;GAGG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAkC;IAClE,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA4B;IAC3D,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAoB;IACjD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAkB;gBAEvC,YAAY,CAAC,EAAE,kBAAkB,EAAE,gBAAgB,CAAC,EAAE,gBAAgB;IAKlF;;;;;;;OAOG;IACG,UAAU,CACd,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,gBAAgB,EAC5B,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,YAAY,CAAC;IA2CxB;;;;;OAKG;IACG,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAiBnD;;;;;OAKG;IACH,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;IAIrD;;;OAGG;IACG,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC;IAIzC;;;;;OAKG;IACH,OAAO,CAAC,2BAA2B;CAkBpC"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { loadDriver as loadDriverPackage } from '@ya-modbus/driver-loader';
|
|
2
|
+
import { TransportManager } from '@ya-modbus/transport';
|
|
3
|
+
/**
|
|
4
|
+
* Manages dynamic loading and lifecycle of device drivers
|
|
5
|
+
* Uses TransportManager to pool RTU transports and prevent bus collisions
|
|
6
|
+
*/
|
|
7
|
+
export class DriverLoader {
|
|
8
|
+
driverInstances = new Map();
|
|
9
|
+
devicePackages = new Map();
|
|
10
|
+
loadDriverFn;
|
|
11
|
+
transportManager;
|
|
12
|
+
constructor(loadDriverFn, transportManager) {
|
|
13
|
+
this.loadDriverFn = loadDriverFn ?? loadDriverPackage;
|
|
14
|
+
this.transportManager = transportManager ?? new TransportManager();
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Load a driver package and create an instance for a device
|
|
18
|
+
*
|
|
19
|
+
* @param packageName - NPM package name (e.g., '@ya-modbus/driver-xymd1' or 'ya-modbus-driver-test')
|
|
20
|
+
* @param connection - Device connection configuration
|
|
21
|
+
* @param deviceId - Unique device identifier for tracking the instance
|
|
22
|
+
* @returns Driver instance
|
|
23
|
+
*/
|
|
24
|
+
async loadDriver(packageName, connection, deviceId) {
|
|
25
|
+
// Security validation: prevent path traversal and code injection
|
|
26
|
+
// Accept both scoped (@ya-modbus/driver-*) and unscoped (ya-modbus-driver-*) packages
|
|
27
|
+
const isValidScopedDriver = packageName.startsWith('@ya-modbus/driver-');
|
|
28
|
+
const isValidUnscopedDriver = packageName.startsWith('ya-modbus-driver-');
|
|
29
|
+
if (!isValidScopedDriver && !isValidUnscopedDriver) {
|
|
30
|
+
throw new Error(`Invalid driver package name: must be @ya-modbus/driver-<name> or ya-modbus-driver-<name>`);
|
|
31
|
+
}
|
|
32
|
+
if (packageName.includes('..') || packageName.includes('\\')) {
|
|
33
|
+
throw new Error('Invalid driver package name: path traversal not allowed');
|
|
34
|
+
}
|
|
35
|
+
// Load driver package using driver-loader
|
|
36
|
+
const loadedDriver = await this.loadDriverFn({ driverPackage: packageName });
|
|
37
|
+
// Get transport from manager (shared for RTU, unique for TCP)
|
|
38
|
+
const config = this.connectionToTransportConfig(connection);
|
|
39
|
+
const transport = await this.transportManager.getTransport(config);
|
|
40
|
+
// Create driver instance
|
|
41
|
+
const driver = await loadedDriver.createDriver({
|
|
42
|
+
transport,
|
|
43
|
+
slaveId: connection.slaveId,
|
|
44
|
+
});
|
|
45
|
+
// Call initialize if available
|
|
46
|
+
if (driver.initialize) {
|
|
47
|
+
await driver.initialize();
|
|
48
|
+
}
|
|
49
|
+
// Cache instance if deviceId provided
|
|
50
|
+
if (deviceId) {
|
|
51
|
+
this.driverInstances.set(deviceId, driver);
|
|
52
|
+
this.devicePackages.set(deviceId, packageName);
|
|
53
|
+
}
|
|
54
|
+
// Note: Don't close transport on failure - it may be shared by other devices
|
|
55
|
+
// The transport manager will handle cleanup when closeAll() is called
|
|
56
|
+
return driver;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Unload a driver instance and clean up resources
|
|
60
|
+
* Note: Transport is managed by TransportManager and not closed here
|
|
61
|
+
*
|
|
62
|
+
* @param deviceId - Device identifier
|
|
63
|
+
*/
|
|
64
|
+
async unloadDriver(deviceId) {
|
|
65
|
+
const driver = this.driverInstances.get(deviceId);
|
|
66
|
+
try {
|
|
67
|
+
if (driver?.destroy) {
|
|
68
|
+
await driver.destroy();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
finally {
|
|
72
|
+
// Always remove from cache, even if destroy fails
|
|
73
|
+
this.driverInstances.delete(deviceId);
|
|
74
|
+
this.devicePackages.delete(deviceId);
|
|
75
|
+
}
|
|
76
|
+
// Note: Transport is managed by TransportManager and shared across devices
|
|
77
|
+
// It will be closed when the entire bridge shuts down via closeAll()
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Get a loaded driver instance
|
|
81
|
+
*
|
|
82
|
+
* @param deviceId - Device identifier
|
|
83
|
+
* @returns Driver instance or undefined if not loaded
|
|
84
|
+
*/
|
|
85
|
+
getDriver(deviceId) {
|
|
86
|
+
return this.driverInstances.get(deviceId);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Close all managed transports
|
|
90
|
+
* Should be called when shutting down the bridge
|
|
91
|
+
*/
|
|
92
|
+
async closeAllTransports() {
|
|
93
|
+
await this.transportManager.closeAll();
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Convert DeviceConnection to TransportConfig
|
|
97
|
+
*
|
|
98
|
+
* @param connection - Device connection configuration
|
|
99
|
+
* @returns Transport configuration
|
|
100
|
+
*/
|
|
101
|
+
connectionToTransportConfig(connection) {
|
|
102
|
+
return connection.type === 'rtu'
|
|
103
|
+
? {
|
|
104
|
+
port: connection.port,
|
|
105
|
+
baudRate: connection.baudRate,
|
|
106
|
+
dataBits: connection.dataBits,
|
|
107
|
+
parity: connection.parity,
|
|
108
|
+
stopBits: connection.stopBits,
|
|
109
|
+
slaveId: connection.slaveId,
|
|
110
|
+
timeout: connection.timeout,
|
|
111
|
+
}
|
|
112
|
+
: {
|
|
113
|
+
host: connection.host,
|
|
114
|
+
port: connection.port,
|
|
115
|
+
slaveId: connection.slaveId,
|
|
116
|
+
timeout: connection.timeout,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=driver-loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"driver-loader.js","sourceRoot":"","sources":["../../src/driver-loader.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { DeviceManager } from './device-manager.js';
|
|
2
|
+
import { DriverLoader } from './driver-loader.js';
|
|
3
|
+
import { PollingScheduler } from './polling-scheduler.js';
|
|
4
|
+
import type { MqttBridgeConfig, MqttBridge } from './types.js';
|
|
5
|
+
export type { MqttBridgeConfig, BridgeStatus, MqttBridge, PublishOptions, SubscribeOptions, MessageHandler, MqttMessage, DeviceConfig, DeviceStatus, DeviceConnection, RTUConnection, TCPConnection, } from './types.js';
|
|
6
|
+
export { loadConfig } from './utils/config.js';
|
|
7
|
+
interface BridgeDependencies {
|
|
8
|
+
driverLoader?: DriverLoader;
|
|
9
|
+
deviceManager?: DeviceManager;
|
|
10
|
+
pollingScheduler?: PollingScheduler;
|
|
11
|
+
}
|
|
12
|
+
export declare function createBridge(config: MqttBridgeConfig, dependencies?: BridgeDependencies): MqttBridge;
|
|
13
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AACzD,OAAO,KAAK,EACV,gBAAgB,EAChB,UAAU,EAMX,MAAM,YAAY,CAAA;AAQnB,YAAY,EACV,gBAAgB,EAChB,YAAY,EACZ,UAAU,EACV,cAAc,EACd,gBAAgB,EAChB,cAAc,EACd,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,gBAAgB,EAChB,aAAa,EACb,aAAa,GACd,MAAM,YAAY,CAAA;AACnB,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAG9C,UAAU,kBAAkB;IAC1B,YAAY,CAAC,EAAE,YAAY,CAAA;IAC3B,aAAa,CAAC,EAAE,aAAa,CAAA;IAC7B,gBAAgB,CAAC,EAAE,gBAAgB,CAAA;CACpC;AAED,wBAAgB,YAAY,CAC1B,MAAM,EAAE,gBAAgB,EACxB,YAAY,CAAC,EAAE,kBAAkB,GAChC,UAAU,CA0UZ"}
|