mqtt-scenario-sim 1.0.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/LICENSE +21 -0
- package/README.md +264 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +265 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +37 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +77 -0
- package/dist/config.js.map +1 -0
- package/dist/effects.d.ts +12 -0
- package/dist/effects.d.ts.map +1 -0
- package/dist/effects.js +28 -0
- package/dist/effects.js.map +1 -0
- package/dist/encoder.d.ts +17 -0
- package/dist/encoder.d.ts.map +1 -0
- package/dist/encoder.js +86 -0
- package/dist/encoder.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +9 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +31 -0
- package/dist/logger.js.map +1 -0
- package/dist/scenarios.d.ts +7 -0
- package/dist/scenarios.d.ts.map +1 -0
- package/dist/scenarios.js +72 -0
- package/dist/scenarios.js.map +1 -0
- package/dist/sensors.d.ts +52 -0
- package/dist/sensors.d.ts.map +1 -0
- package/dist/sensors.js +78 -0
- package/dist/sensors.js.map +1 -0
- package/dist/simulator.d.ts +35 -0
- package/dist/simulator.d.ts.map +1 -0
- package/dist/simulator.js +199 -0
- package/dist/simulator.js.map +1 -0
- package/examples/custom-proto.yaml +46 -0
- package/examples/greenhouse.yaml +139 -0
- package/examples/minimal.yaml +19 -0
- package/package.json +65 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 dislev
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="assets/logo.png" alt="mqtt-scenario-sim logo" width="160" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
# mqtt-scenario-sim
|
|
6
|
+
|
|
7
|
+
[](https://github.com/dislev/mqtt-scenario-sim/actions/workflows/ci.yml)
|
|
8
|
+
[](https://www.npmjs.com/package/mqtt-scenario-sim)
|
|
9
|
+
[](LICENSE)
|
|
10
|
+
[](package.json)
|
|
11
|
+
[](https://codecov.io/gh/dislev/mqtt-scenario-sim)
|
|
12
|
+
|
|
13
|
+
A configurable MQTT sensor simulator with built-in test scenarios. Publish synthetic sensor data over MQTT — as JSON or protobuf — without real hardware.
|
|
14
|
+
|
|
15
|
+
Define your sources with any labels you want. Wire up any MQTT topic template. Switch between test scenarios over HTTP to drive your alert and pipeline logic.
|
|
16
|
+
|
|
17
|
+
> **Maintenance:** This project is maintained on a best-effort basis. Bug reports and PRs are welcome — response times may vary.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Quick start
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx mqtt-scenario-sim --config examples/minimal.yaml
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Requires a running MQTT broker (e.g. `docker run -p 1883:1883 eclipse-mosquitto`).
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Scenarios
|
|
32
|
+
|
|
33
|
+
The real differentiator. Switch your entire simulator into a controlled test state at any time — no restarts.
|
|
34
|
+
|
|
35
|
+
| ID | Key | What it does |
|
|
36
|
+
|---|---|---|
|
|
37
|
+
| 0 | `normal` | Each metric runs its configured mode |
|
|
38
|
+
| 1 | `out_of_range` | All metrics 30% above max — tests breach detection |
|
|
39
|
+
| 2 | `trending_to_breach` | Rising toward max — tests early-warning alerts |
|
|
40
|
+
| 3 | `stable_healthy` | Held at midpoint ideal — tests steady-state |
|
|
41
|
+
| 4 | `recovery` | Starts out-of-range, decays to ideal over ~5 min |
|
|
42
|
+
| 5 | `oscillating` | ±10% swing — tests flapping suppression |
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# Activate a scenario
|
|
46
|
+
curl -X POST http://localhost:4000/scenario/1
|
|
47
|
+
|
|
48
|
+
# With auto-revert after 60 seconds
|
|
49
|
+
curl -X POST "http://localhost:4000/scenario/4?durationSeconds=60"
|
|
50
|
+
|
|
51
|
+
# Target a single source by its labels key
|
|
52
|
+
curl -X POST "http://localhost:4000/scenario/2?sourceKey=%7B%22device%22%3A%22node-1%22%7D"
|
|
53
|
+
|
|
54
|
+
# Check current scenario
|
|
55
|
+
curl http://localhost:4000/scenario
|
|
56
|
+
|
|
57
|
+
# Back to normal
|
|
58
|
+
curl -X POST http://localhost:4000/scenario/0
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## YAML config
|
|
64
|
+
|
|
65
|
+
```yaml
|
|
66
|
+
mqtt:
|
|
67
|
+
host: localhost
|
|
68
|
+
port: 1883
|
|
69
|
+
|
|
70
|
+
publishIntervalMs: 5000
|
|
71
|
+
|
|
72
|
+
encoding:
|
|
73
|
+
type: json # or "protobuf" (see Protobuf section)
|
|
74
|
+
|
|
75
|
+
sources:
|
|
76
|
+
- labels: # fully freeform — any keys you want
|
|
77
|
+
building: hq
|
|
78
|
+
floor: "3"
|
|
79
|
+
zone: east
|
|
80
|
+
topic: "{building}/{floor}/{zone}/metrics" # template using label keys
|
|
81
|
+
metrics:
|
|
82
|
+
- name: temperature
|
|
83
|
+
units: "°C"
|
|
84
|
+
mode: sinusoidal # sinusoidal | drift | normal | spike
|
|
85
|
+
range: { low: 18, high: 28 }
|
|
86
|
+
periodSeconds: 3600 # optional
|
|
87
|
+
- name: humidity
|
|
88
|
+
units: "%"
|
|
89
|
+
mode: drift
|
|
90
|
+
range: { low: 30, high: 70 }
|
|
91
|
+
effects: # optional — external things that bias metric readings
|
|
92
|
+
- name: hvac
|
|
93
|
+
effects:
|
|
94
|
+
temperature: -0.40 # fraction of range span, negative = reduce
|
|
95
|
+
humidity: -0.20
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Sensor modes
|
|
99
|
+
|
|
100
|
+
| Mode | Description |
|
|
101
|
+
|---|---|
|
|
102
|
+
| `sinusoidal` | Smooth oscillation over `periodSeconds` |
|
|
103
|
+
| `normal` | Gaussian noise around baseline |
|
|
104
|
+
| `drift` | Slow random walk that bounces at range edges |
|
|
105
|
+
| `spike` | Normal noise with random anomaly spikes |
|
|
106
|
+
|
|
107
|
+
### Effects
|
|
108
|
+
|
|
109
|
+
Effects let you simulate external influences on metric readings. Send a command to `{topic}/cmd`:
|
|
110
|
+
|
|
111
|
+
```json
|
|
112
|
+
{ "effect": "hvac", "state": true }
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Each effect's `effects` map specifies bias as a fraction of the metric's span. `-0.40` on temperature with a span of 10°C = -4°C bias.
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Protobuf
|
|
120
|
+
|
|
121
|
+
```yaml
|
|
122
|
+
encoding:
|
|
123
|
+
type: protobuf
|
|
124
|
+
protoFile: ./my.proto
|
|
125
|
+
messageType: myapp.SensorReading
|
|
126
|
+
fieldMap: # optional — remap internal fields to proto field names
|
|
127
|
+
metric: sensor_name
|
|
128
|
+
value: reading_value
|
|
129
|
+
timestamp: recorded_at
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Internal fields available for mapping: `metric`, `value`, `units`, `timestamp`, plus any label key (e.g. `building`, `floor`).
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## HTTP API
|
|
137
|
+
|
|
138
|
+
| Method | Path | Description |
|
|
139
|
+
|---|---|---|
|
|
140
|
+
| `GET` | `/stream` | SSE stream — one event per metric publish |
|
|
141
|
+
| `GET` | `/status` | Full snapshot: uptime, scenario, all readings, all effects |
|
|
142
|
+
| `GET` | `/health` | Status, uptime, source/metric counts, active scenario |
|
|
143
|
+
| `GET` | `/scenario` | Current scenario name + description |
|
|
144
|
+
| `POST` | `/scenario/:id` | Activate scenario (0–5 or name). Query: `durationSeconds`, `sourceKey` |
|
|
145
|
+
| `GET` | `/state` | Last published value per metric |
|
|
146
|
+
| `GET` | `/effects` | Current effect state per source |
|
|
147
|
+
|
|
148
|
+
### Live tailing with `/stream`
|
|
149
|
+
|
|
150
|
+
`/stream` is a Server-Sent Events endpoint. It pushes one JSON event per metric publish and stays open until you disconnect.
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
curl -N http://localhost:4000/stream
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
data: {"labels":{"building":"hq","floor":"3"},"topic":"hq/3/east/metrics","metric":"temperature","units":"°C","value":22.45,"scenario":"normal","timestamp":1747613001234}
|
|
158
|
+
|
|
159
|
+
data: {"labels":{"building":"hq","floor":"3"},"topic":"hq/3/east/metrics","metric":"humidity","units":"%","value":58.1,"scenario":"normal","timestamp":1747613001235}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Multiple clients can connect simultaneously. The `scenario` field reflects the active scenario at publish time.
|
|
163
|
+
|
|
164
|
+
### Full snapshot with `/status`
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
curl http://localhost:4000/status
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
```json
|
|
171
|
+
{
|
|
172
|
+
"status": "ok",
|
|
173
|
+
"uptime": 142,
|
|
174
|
+
"sources": 1,
|
|
175
|
+
"metrics": 2,
|
|
176
|
+
"scenario": {
|
|
177
|
+
"id": "normal",
|
|
178
|
+
"label": "0 / n — Normal (uses each metric's configured mode)",
|
|
179
|
+
"detail": "Each metric runs its configured mode: sinusoidal, drift, normal, or spike."
|
|
180
|
+
},
|
|
181
|
+
"readings": [
|
|
182
|
+
{ "labels": { "building": "hq", "floor": "3" }, "metric": "temperature", "units": "°C", "value": 22.45, "lastPublishedAt": 1747613001234 },
|
|
183
|
+
{ "labels": { "building": "hq", "floor": "3" }, "metric": "humidity", "units": "%", "value": 58.1, "lastPublishedAt": 1747613001235 }
|
|
184
|
+
],
|
|
185
|
+
"effects": {
|
|
186
|
+
"hq/3/east/metrics": { "hvac": false }
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## CLI args
|
|
194
|
+
|
|
195
|
+
| Argument | Description |
|
|
196
|
+
|---|---|
|
|
197
|
+
| `--config <path>` | Path to YAML config file |
|
|
198
|
+
| `--log-level <level>` | `silent` \| `error` \| `warn` \| `info` \| `debug` (default: `info`) |
|
|
199
|
+
| `-h`, `--help` | Print help and exit |
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
# Verbose — see every metric publish
|
|
203
|
+
npx mqtt-scenario-sim --config my.yaml --log-level debug
|
|
204
|
+
|
|
205
|
+
# Quiet — errors only
|
|
206
|
+
npx mqtt-scenario-sim --config my.yaml --log-level error
|
|
207
|
+
|
|
208
|
+
# Help
|
|
209
|
+
npx mqtt-scenario-sim --help
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## Env vars
|
|
215
|
+
|
|
216
|
+
| Variable | Default | Description |
|
|
217
|
+
|---|---|---|
|
|
218
|
+
| `MQTT_HOST` | `localhost` | MQTT broker host |
|
|
219
|
+
| `MQTT_PORT` | `1883` | MQTT broker port |
|
|
220
|
+
| `PUBLISH_INTERVAL_MS` | `5000` | Default publish interval |
|
|
221
|
+
| `ENCODING` | `json` | `json` or `protobuf` |
|
|
222
|
+
| `PROTO_FILE` | — | Path to `.proto` file |
|
|
223
|
+
| `PROTO_MESSAGE_TYPE` | — | Fully-qualified proto message type |
|
|
224
|
+
| `PROTO_FIELD_MAP` | — | JSON string field map |
|
|
225
|
+
| `CONFIG_PATH` | `examples/minimal.yaml` | Path to YAML config |
|
|
226
|
+
| `PORT` | `4000` | HTTP control plane port |
|
|
227
|
+
| `LOG_LEVEL` | `info` | `silent` \| `error` \| `warn` \| `info` \| `debug` |
|
|
228
|
+
|
|
229
|
+
CLI args take precedence over env vars.
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Examples
|
|
234
|
+
|
|
235
|
+
- [`examples/minimal.yaml`](examples/minimal.yaml) — 1 source, 1 metric, JSON
|
|
236
|
+
- [`examples/greenhouse.yaml`](examples/greenhouse.yaml) — multi-source, multi-metric IoT example
|
|
237
|
+
- [`examples/custom-proto.yaml`](examples/custom-proto.yaml) — protobuf with fieldMap
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## Docker
|
|
242
|
+
|
|
243
|
+
A [`Dockerfile`](Dockerfile) is included. Build and run with your own config:
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
docker build -t mqtt-scenario-sim .
|
|
247
|
+
docker run --rm -e MQTT_HOST=host.docker.internal mqtt-scenario-sim
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
To use a custom config, mount it at runtime:
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
docker run --rm \
|
|
254
|
+
-v $(pwd)/my-config.yaml:/app/my-config.yaml \
|
|
255
|
+
-e CONFIG_PATH=/app/my-config.yaml \
|
|
256
|
+
-e MQTT_HOST=host.docker.internal \
|
|
257
|
+
mqtt-scenario-sim
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## License
|
|
263
|
+
|
|
264
|
+
MIT
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
37
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
38
|
+
};
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
const readline = __importStar(require("readline"));
|
|
41
|
+
const mqtt = __importStar(require("mqtt"));
|
|
42
|
+
const path = __importStar(require("path"));
|
|
43
|
+
const express_1 = __importDefault(require("express"));
|
|
44
|
+
const config_1 = require("./config");
|
|
45
|
+
const encoder_1 = require("./encoder");
|
|
46
|
+
const simulator_1 = require("./simulator");
|
|
47
|
+
const scenarios_1 = require("./scenarios");
|
|
48
|
+
const logger_1 = require("./logger");
|
|
49
|
+
const PORT = parseInt(process.env['PORT'] ?? '4000', 10);
|
|
50
|
+
const logLevelArg = process.argv.find((a) => a.startsWith('--log-level='))?.slice('--log-level='.length)
|
|
51
|
+
?? (process.argv.indexOf('--log-level') !== -1 ? process.argv[process.argv.indexOf('--log-level') + 1] : undefined)
|
|
52
|
+
?? process.env['LOG_LEVEL'];
|
|
53
|
+
if (logLevelArg)
|
|
54
|
+
logger_1.logger.setLevel(logLevelArg);
|
|
55
|
+
if (process.argv.includes('--help') || process.argv.includes('-h')) {
|
|
56
|
+
console.log(`
|
|
57
|
+
Usage: mqtt-scenario-sim [options]
|
|
58
|
+
|
|
59
|
+
Options:
|
|
60
|
+
--config <path> Path to YAML config file (default: examples/minimal.yaml)
|
|
61
|
+
--log-level <level> Log verbosity: silent | error | warn | info | debug (default: info)
|
|
62
|
+
-h, --help Show this help message
|
|
63
|
+
|
|
64
|
+
Env vars:
|
|
65
|
+
MQTT_HOST MQTT broker host (default: localhost)
|
|
66
|
+
MQTT_PORT MQTT broker port (default: 1883)
|
|
67
|
+
PUBLISH_INTERVAL_MS Publish interval in ms (default: 5000)
|
|
68
|
+
ENCODING json | protobuf (default: json)
|
|
69
|
+
PROTO_FILE Path to .proto file
|
|
70
|
+
PROTO_MESSAGE_TYPE Fully-qualified message type
|
|
71
|
+
PROTO_FIELD_MAP JSON string field map
|
|
72
|
+
CONFIG_PATH Path to YAML config file (default: examples/minimal.yaml)
|
|
73
|
+
PORT HTTP control plane port (default: 4000)
|
|
74
|
+
LOG_LEVEL Log verbosity level (default: info)
|
|
75
|
+
|
|
76
|
+
HTTP API (default port 4000):
|
|
77
|
+
GET /stream SSE stream of live metric publishes
|
|
78
|
+
GET /status Full snapshot: uptime, scenario, readings, effects
|
|
79
|
+
GET /health Status, uptime, source/metric counts, scenario
|
|
80
|
+
GET /scenario Current scenario name + description
|
|
81
|
+
POST /scenario/:id Activate scenario by ID (0–5) or name
|
|
82
|
+
GET /state Last published value per metric
|
|
83
|
+
GET /effects Current effect state per source
|
|
84
|
+
|
|
85
|
+
Scenarios:
|
|
86
|
+
0 / normal Each metric runs its configured mode
|
|
87
|
+
1 / out_of_range All metrics 30% above max
|
|
88
|
+
2 / trending_to_breach Rising toward max
|
|
89
|
+
3 / stable_healthy Held at midpoint ideal
|
|
90
|
+
4 / recovery Starts out-of-range, decays to ideal over ~5 min
|
|
91
|
+
5 / oscillating ±10% swing around ideal
|
|
92
|
+
|
|
93
|
+
Examples:
|
|
94
|
+
mqtt-scenario-sim --config examples/minimal.yaml
|
|
95
|
+
mqtt-scenario-sim --config my.yaml --log-level debug
|
|
96
|
+
`);
|
|
97
|
+
process.exit(0);
|
|
98
|
+
}
|
|
99
|
+
function printMenu() {
|
|
100
|
+
logger_1.logger.info('\n┌─ Scenario control ──────────────────────────────────────────┐');
|
|
101
|
+
for (const label of Object.values(scenarios_1.SCENARIO_LABELS)) {
|
|
102
|
+
logger_1.logger.info(`│ ${label.padEnd(61)}│`);
|
|
103
|
+
}
|
|
104
|
+
logger_1.logger.info('│ │');
|
|
105
|
+
logger_1.logger.info('│ status / ? show active scenario │');
|
|
106
|
+
logger_1.logger.info('│ help re-print this menu │');
|
|
107
|
+
logger_1.logger.info('└─────────────────────────────────────────────────────────────┘\n');
|
|
108
|
+
}
|
|
109
|
+
function resolveScenarioId(input) {
|
|
110
|
+
return scenarios_1.SCENARIO_KEYS[input.trim().toLowerCase()] ?? null;
|
|
111
|
+
}
|
|
112
|
+
async function main() {
|
|
113
|
+
const configArg = process.argv.find((a) => a.startsWith('--config='))?.slice('--config='.length)
|
|
114
|
+
?? process.argv[process.argv.indexOf('--config') + 1];
|
|
115
|
+
const configPath = configArg
|
|
116
|
+
? path.resolve(configArg)
|
|
117
|
+
: process.env['CONFIG_PATH'];
|
|
118
|
+
const config = (0, config_1.loadConfig)(configPath);
|
|
119
|
+
const totalMetrics = config.sources.reduce((n, s) => n + s.metrics.length, 0);
|
|
120
|
+
logger_1.logger.info(`[init] ${config.sources.length} source(s) | ${totalMetrics} metric(s) | MQTT: ${config.mqtt.host}:${config.mqtt.port}`);
|
|
121
|
+
const encode = config.encoding.type === 'protobuf'
|
|
122
|
+
? (0, encoder_1.buildEncoder)(config.encoding)
|
|
123
|
+
: encoder_1.jsonEncoder;
|
|
124
|
+
const brokerUrl = `mqtt://${config.mqtt.host}:${config.mqtt.port}`;
|
|
125
|
+
const client = mqtt.connect(brokerUrl, { reconnectPeriod: 2000 });
|
|
126
|
+
await new Promise((resolve, reject) => {
|
|
127
|
+
client.once('connect', () => resolve());
|
|
128
|
+
client.once('error', (err) => reject(err));
|
|
129
|
+
});
|
|
130
|
+
logger_1.logger.info(`[mqtt] connected to ${brokerUrl}`);
|
|
131
|
+
const sim = (0, simulator_1.startSimulator)(config, client, encode);
|
|
132
|
+
// ── HTTP control plane ────────────────────────────────────────────────────
|
|
133
|
+
const app = (0, express_1.default)();
|
|
134
|
+
const startTime = Date.now();
|
|
135
|
+
app.use(express_1.default.json());
|
|
136
|
+
app.get('/health', (_req, res) => {
|
|
137
|
+
res.json({
|
|
138
|
+
status: 'ok',
|
|
139
|
+
sources: config.sources.length,
|
|
140
|
+
metrics: totalMetrics,
|
|
141
|
+
uptime: Math.floor((Date.now() - startTime) / 1000),
|
|
142
|
+
scenario: sim.getScenario(),
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
app.get('/scenario', (_req, res) => {
|
|
146
|
+
const id = sim.getScenario();
|
|
147
|
+
res.json({ scenario: id, label: scenarios_1.SCENARIO_LABELS[id], detail: scenarios_1.SCENARIO_DETAIL[id] });
|
|
148
|
+
});
|
|
149
|
+
app.post('/scenario/:id', (req, res) => {
|
|
150
|
+
const id = resolveScenarioId(req.params['id'] ?? '');
|
|
151
|
+
if (!id) {
|
|
152
|
+
res.status(400).json({
|
|
153
|
+
error: `Unknown scenario "${req.params['id']}". Valid: ${Object.keys(scenarios_1.SCENARIO_KEYS).join(', ')}`,
|
|
154
|
+
});
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const rawDuration = req.query['durationSeconds'];
|
|
158
|
+
const rawSourceKey = req.query['sourceKey'];
|
|
159
|
+
const durationSeconds = typeof rawDuration === 'string' && rawDuration.length > 0
|
|
160
|
+
? parseInt(rawDuration, 10)
|
|
161
|
+
: undefined;
|
|
162
|
+
const sourceKey = typeof rawSourceKey === 'string' && rawSourceKey.length > 0
|
|
163
|
+
? rawSourceKey
|
|
164
|
+
: undefined;
|
|
165
|
+
sim.setScenario(id, sourceKey, durationSeconds && durationSeconds > 0 ? durationSeconds : undefined);
|
|
166
|
+
res.json({
|
|
167
|
+
ok: true,
|
|
168
|
+
scenario: id,
|
|
169
|
+
label: scenarios_1.SCENARIO_LABELS[id],
|
|
170
|
+
...(sourceKey ? { sourceKey } : { appliedTo: 'all' }),
|
|
171
|
+
...(durationSeconds && durationSeconds > 0 ? { autoRevertAfterSeconds: durationSeconds } : {}),
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
app.get('/state', (_req, res) => {
|
|
175
|
+
res.json(sim.getState());
|
|
176
|
+
});
|
|
177
|
+
app.get('/effects', (_req, res) => {
|
|
178
|
+
res.json(sim.getEffectStates());
|
|
179
|
+
});
|
|
180
|
+
app.get('/stream', (req, res) => {
|
|
181
|
+
res.setHeader('Content-Type', 'text/event-stream');
|
|
182
|
+
res.setHeader('Cache-Control', 'no-cache');
|
|
183
|
+
res.setHeader('Connection', 'keep-alive');
|
|
184
|
+
res.flushHeaders();
|
|
185
|
+
const unsub = sim.onPublish((event) => {
|
|
186
|
+
res.write(`data: ${JSON.stringify(event)}\n\n`);
|
|
187
|
+
});
|
|
188
|
+
req.on('close', unsub);
|
|
189
|
+
});
|
|
190
|
+
app.get('/status', (_req, res) => {
|
|
191
|
+
const scenarioId = sim.getScenario();
|
|
192
|
+
const state = sim.getState();
|
|
193
|
+
res.json({
|
|
194
|
+
status: 'ok',
|
|
195
|
+
uptime: Math.floor((Date.now() - startTime) / 1000),
|
|
196
|
+
sources: config.sources.length,
|
|
197
|
+
metrics: totalMetrics,
|
|
198
|
+
scenario: {
|
|
199
|
+
id: scenarioId,
|
|
200
|
+
label: scenarios_1.SCENARIO_LABELS[scenarioId],
|
|
201
|
+
detail: scenarios_1.SCENARIO_DETAIL[scenarioId],
|
|
202
|
+
},
|
|
203
|
+
readings: state.metrics,
|
|
204
|
+
effects: sim.getEffectStates(),
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
const server = app.listen(PORT, () => {
|
|
208
|
+
logger_1.logger.info(`[http] :${PORT}`);
|
|
209
|
+
logger_1.logger.info(` GET /health GET /scenario POST /scenario/:id`);
|
|
210
|
+
logger_1.logger.info(` GET /state GET /effects`);
|
|
211
|
+
});
|
|
212
|
+
server.on('error', (err) => {
|
|
213
|
+
if (err.code === 'EADDRINUSE') {
|
|
214
|
+
logger_1.logger.warn(`[http] port ${PORT} in use — HTTP unavailable, sim still running`);
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
logger_1.logger.error('[http] error:', err);
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
// ── Stdin REPL ─────────────────────────────────────────────────────────────
|
|
221
|
+
if (process.stdin.isTTY) {
|
|
222
|
+
printMenu();
|
|
223
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout, prompt: 'scenario> ' });
|
|
224
|
+
rl.prompt();
|
|
225
|
+
rl.on('line', (line) => {
|
|
226
|
+
const input = line.trim().toLowerCase();
|
|
227
|
+
if (!input) {
|
|
228
|
+
rl.prompt();
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
if (input === 'help') {
|
|
232
|
+
printMenu();
|
|
233
|
+
rl.prompt();
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
if (input === 'status' || input === '?') {
|
|
237
|
+
const id = sim.getScenario();
|
|
238
|
+
logger_1.logger.info(`[scenario] ${id} — ${scenarios_1.SCENARIO_DETAIL[id]}`);
|
|
239
|
+
rl.prompt();
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
const id = resolveScenarioId(input);
|
|
243
|
+
if (id)
|
|
244
|
+
sim.setScenario(id);
|
|
245
|
+
else
|
|
246
|
+
logger_1.logger.info(`Unknown: "${input}". Type help for options.`);
|
|
247
|
+
rl.prompt();
|
|
248
|
+
});
|
|
249
|
+
rl.on('close', () => logger_1.logger.info('[repl] stdin closed'));
|
|
250
|
+
}
|
|
251
|
+
// ── Graceful shutdown ──────────────────────────────────────────────────────
|
|
252
|
+
const shutdown = () => {
|
|
253
|
+
logger_1.logger.info('[shutdown] stopping...');
|
|
254
|
+
sim.stop();
|
|
255
|
+
client.end();
|
|
256
|
+
server.close(() => process.exit(0));
|
|
257
|
+
};
|
|
258
|
+
process.on('SIGTERM', shutdown);
|
|
259
|
+
process.on('SIGINT', shutdown);
|
|
260
|
+
}
|
|
261
|
+
main().catch((err) => {
|
|
262
|
+
logger_1.logger.error('[fatal]', err);
|
|
263
|
+
process.exit(1);
|
|
264
|
+
});
|
|
265
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { SensorMode, SensorRange } from './sensors';
|
|
2
|
+
import { EffectConfig } from './effects';
|
|
3
|
+
import { EncodingConfig } from './encoder';
|
|
4
|
+
export interface MetricConfig {
|
|
5
|
+
name: string;
|
|
6
|
+
units: string;
|
|
7
|
+
mode: SensorMode;
|
|
8
|
+
range: SensorRange;
|
|
9
|
+
baseline?: number;
|
|
10
|
+
amplitude?: number;
|
|
11
|
+
periodSeconds?: number;
|
|
12
|
+
driftRate?: number;
|
|
13
|
+
min?: number;
|
|
14
|
+
max?: number;
|
|
15
|
+
intervalMs?: number;
|
|
16
|
+
spikeProbability?: number;
|
|
17
|
+
spikeMagnitude?: number;
|
|
18
|
+
}
|
|
19
|
+
export interface SourceConfig {
|
|
20
|
+
labels: Record<string, string>;
|
|
21
|
+
topic: string;
|
|
22
|
+
metrics: MetricConfig[];
|
|
23
|
+
effects?: EffectConfig[];
|
|
24
|
+
}
|
|
25
|
+
export interface SimulatorConfig {
|
|
26
|
+
mqtt: {
|
|
27
|
+
host: string;
|
|
28
|
+
port: number;
|
|
29
|
+
};
|
|
30
|
+
publishIntervalMs: number;
|
|
31
|
+
encoding: EncodingConfig;
|
|
32
|
+
sources: SourceConfig[];
|
|
33
|
+
}
|
|
34
|
+
declare function resolveTopicTemplate(template: string, labels: Record<string, string>): string;
|
|
35
|
+
export { resolveTopicTemplate };
|
|
36
|
+
export declare function loadConfig(configPath?: string): SimulatorConfig;
|
|
37
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAE3C,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,UAAU,CAAC;IACjB,KAAK,EAAE,WAAW,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,OAAO,CAAC,EAAE,YAAY,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE;QACJ,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;IACF,iBAAiB,EAAE,MAAM,CAAC;IAC1B,QAAQ,EAAE,cAAc,CAAC;IACzB,OAAO,EAAE,YAAY,EAAE,CAAC;CACzB;AAED,iBAAS,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAEtF;AAED,OAAO,EAAE,oBAAoB,EAAE,CAAC;AAEhC,wBAAgB,UAAU,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,eAAe,CAsC/D"}
|