@alteriom/mqtt-schema 0.2.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/README.md +174 -0
- package/dist/cjs/generated/types.d.ts +101 -0
- package/dist/cjs/generated/types.js +104 -0
- package/dist/cjs/index.d.ts +2 -0
- package/dist/cjs/index.js +23 -0
- package/dist/cjs/schema_data.d.ts +319 -0
- package/dist/cjs/schema_data.js +402 -0
- package/dist/cjs/schemas/control_response.schema.json +15 -0
- package/dist/cjs/schemas/envelope.schema.json +16 -0
- package/dist/cjs/schemas/firmware_status.schema.json +17 -0
- package/dist/cjs/schemas/gateway_info.schema.json +21 -0
- package/dist/cjs/schemas/gateway_metrics.schema.json +26 -0
- package/dist/cjs/schemas/mqtt_v1_bundle.json +14 -0
- package/dist/cjs/schemas/sensor_data.schema.json +33 -0
- package/dist/cjs/schemas/sensor_heartbeat.schema.json +14 -0
- package/dist/cjs/schemas/sensor_status.schema.json +14 -0
- package/dist/cjs/schemas/validation_rules.md +44 -0
- package/dist/cjs/types.d.ts +1 -0
- package/dist/cjs/types.js +23 -0
- package/dist/cjs/validators.d.ts +23 -0
- package/dist/cjs/validators.js +84 -0
- package/dist/esm/generated/types.js +92 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/schema_data.js +399 -0
- package/dist/esm/schemas/control_response.schema.json +15 -0
- package/dist/esm/schemas/envelope.schema.json +16 -0
- package/dist/esm/schemas/firmware_status.schema.json +17 -0
- package/dist/esm/schemas/gateway_info.schema.json +21 -0
- package/dist/esm/schemas/gateway_metrics.schema.json +26 -0
- package/dist/esm/schemas/mqtt_v1_bundle.json +14 -0
- package/dist/esm/schemas/sensor_data.schema.json +33 -0
- package/dist/esm/schemas/sensor_heartbeat.schema.json +14 -0
- package/dist/esm/schemas/sensor_status.schema.json +14 -0
- package/dist/esm/schemas/validation_rules.md +44 -0
- package/dist/esm/types.js +7 -0
- package/dist/esm/validators.js +76 -0
- package/package.json +91 -0
@@ -0,0 +1,21 @@
|
|
1
|
+
{
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
3
|
+
"$id": "https://schemas.alteriom.io/mqtt/v1/gateway_info.schema.json",
|
4
|
+
"title": "Gateway Info v1",
|
5
|
+
"allOf": [{"$ref": "envelope.schema.json"}],
|
6
|
+
"type": "object",
|
7
|
+
"properties": {
|
8
|
+
"mac_address": {"type": "string", "pattern": "^[0-9A-Fa-f:]{17}$"},
|
9
|
+
"ip_address": {"type": "string", "format": "ipv4"},
|
10
|
+
"capabilities": {
|
11
|
+
"type": "object",
|
12
|
+
"properties": {
|
13
|
+
"max_nodes": {"type": "integer", "minimum": 0},
|
14
|
+
"supports_mesh": {"type": "boolean"},
|
15
|
+
"firmware_update": {"type": "boolean"}
|
16
|
+
},
|
17
|
+
"additionalProperties": true
|
18
|
+
}
|
19
|
+
},
|
20
|
+
"additionalProperties": true
|
21
|
+
}
|
@@ -0,0 +1,26 @@
|
|
1
|
+
{
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
3
|
+
"$id": "https://schemas.alteriom.io/mqtt/v1/gateway_metrics.schema.json",
|
4
|
+
"title": "Gateway Metrics v1",
|
5
|
+
"allOf": [{"$ref": "envelope.schema.json"}],
|
6
|
+
"type": "object",
|
7
|
+
"required": ["metrics"],
|
8
|
+
"properties": {
|
9
|
+
"metrics": {
|
10
|
+
"type": "object",
|
11
|
+
"required": ["uptime_s"],
|
12
|
+
"properties": {
|
13
|
+
"uptime_s": {"type": "integer", "minimum": 0},
|
14
|
+
"cpu_usage_pct": {"type": "number", "minimum": 0, "maximum": 100},
|
15
|
+
"memory_usage_pct": {"type": "number", "minimum": 0, "maximum": 100},
|
16
|
+
"temperature_c": {"type": "number"},
|
17
|
+
"connected_devices": {"type": "integer", "minimum": 0},
|
18
|
+
"mesh_nodes": {"type": "integer", "minimum": 0},
|
19
|
+
"packet_loss_pct": {"type": "number", "minimum": 0, "maximum": 100},
|
20
|
+
"data_throughput_kbps": {"type": "number", "minimum": 0}
|
21
|
+
},
|
22
|
+
"additionalProperties": true
|
23
|
+
}
|
24
|
+
},
|
25
|
+
"additionalProperties": true
|
26
|
+
}
|
@@ -0,0 +1,14 @@
|
|
1
|
+
{
|
2
|
+
"$comment": "Convenience bundle referencing all v1 schema artifact filenames for tooling discovery.",
|
3
|
+
"version": 1,
|
4
|
+
"schemas": {
|
5
|
+
"envelope": "envelope.schema.json",
|
6
|
+
"sensor_data": "sensor_data.schema.json",
|
7
|
+
"sensor_heartbeat": "sensor_heartbeat.schema.json",
|
8
|
+
"sensor_status": "sensor_status.schema.json",
|
9
|
+
"gateway_info": "gateway_info.schema.json",
|
10
|
+
"gateway_metrics": "gateway_metrics.schema.json",
|
11
|
+
"firmware_status": "firmware_status.schema.json",
|
12
|
+
"control_response": "control_response.schema.json"
|
13
|
+
}
|
14
|
+
}
|
@@ -0,0 +1,33 @@
|
|
1
|
+
{
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
3
|
+
"$id": "https://schemas.alteriom.io/mqtt/v1/sensor_data.schema.json",
|
4
|
+
"title": "Sensor Data Message v1",
|
5
|
+
"allOf": [{"$ref": "envelope.schema.json"}],
|
6
|
+
"type": "object",
|
7
|
+
"required": ["sensors"],
|
8
|
+
"properties": {
|
9
|
+
"sensors": {
|
10
|
+
"type": "object",
|
11
|
+
"minProperties": 1,
|
12
|
+
"additionalProperties": {
|
13
|
+
"type": "object",
|
14
|
+
"required": ["value"],
|
15
|
+
"properties": {
|
16
|
+
"value": {"type": ["number", "integer"]},
|
17
|
+
"unit": {"type": "string"},
|
18
|
+
"raw_value": {"type": ["number", "integer"]},
|
19
|
+
"calibrated_value": {"type": ["number", "integer"]},
|
20
|
+
"quality_score": {"type": "number", "minimum": 0, "maximum": 1},
|
21
|
+
"name": {"type": "string"},
|
22
|
+
"location": {"type": "string"},
|
23
|
+
"additional_data": {"type": "object"}
|
24
|
+
},
|
25
|
+
"additionalProperties": false
|
26
|
+
}
|
27
|
+
},
|
28
|
+
"battery_level": {"type": "integer", "minimum": 0, "maximum": 100},
|
29
|
+
"signal_strength": {"type": "integer", "minimum": -200, "maximum": 0},
|
30
|
+
"additional": {"type": "object"}
|
31
|
+
},
|
32
|
+
"additionalProperties": true
|
33
|
+
}
|
@@ -0,0 +1,14 @@
|
|
1
|
+
{
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
3
|
+
"$id": "https://schemas.alteriom.io/mqtt/v1/sensor_heartbeat.schema.json",
|
4
|
+
"title": "Sensor Heartbeat v1",
|
5
|
+
"type": "object",
|
6
|
+
"required": ["schema_version", "device_id", "device_type", "timestamp"],
|
7
|
+
"properties": {
|
8
|
+
"schema_version": {"type": "integer", "const": 1},
|
9
|
+
"device_id": {"type": "string", "minLength": 1, "maxLength": 64, "pattern": "^[A-Za-z0-9_-]+$"},
|
10
|
+
"device_type": {"type": "string", "enum": ["sensor", "gateway"]},
|
11
|
+
"timestamp": {"type": "string", "format": "date-time"}
|
12
|
+
},
|
13
|
+
"additionalProperties": true
|
14
|
+
}
|
@@ -0,0 +1,14 @@
|
|
1
|
+
{
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
3
|
+
"$id": "https://schemas.alteriom.io/mqtt/v1/sensor_status.schema.json",
|
4
|
+
"title": "Sensor Status v1",
|
5
|
+
"allOf": [{"$ref": "envelope.schema.json"}],
|
6
|
+
"type": "object",
|
7
|
+
"required": ["status"],
|
8
|
+
"properties": {
|
9
|
+
"status": {"type": "string", "enum": ["online", "offline", "updating", "error"]},
|
10
|
+
"battery_level": {"type": "integer", "minimum": 0, "maximum": 100},
|
11
|
+
"signal_strength": {"type": "integer", "minimum": -200, "maximum": 0}
|
12
|
+
},
|
13
|
+
"additionalProperties": true
|
14
|
+
}
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# Validation Rules (Operational Layer)
|
2
|
+
|
3
|
+
This file captures dynamic / contextual validation that is OUTSIDE pure structural JSON Schema constraints.
|
4
|
+
|
5
|
+
## Timing & Rate
|
6
|
+
- Future timestamp drift: reject if > 5s ahead of server reference.
|
7
|
+
- Heartbeat interval recommended 30s; backend may rate-limit <10s.
|
8
|
+
- Sensor data soft min 30s unless negotiated high-frequency mode.
|
9
|
+
|
10
|
+
## Heartbeat Firmware Version Exception
|
11
|
+
- Only heartbeat topic may omit `firmware_version` when unchanged.
|
12
|
+
- All other topics require the field.
|
13
|
+
|
14
|
+
## Forbidden / Drop Conditions
|
15
|
+
| Reason | Condition |
|
16
|
+
|--------|----------|
|
17
|
+
| missing_fields | Required base fields absent (heartbeat firmware exception applied). |
|
18
|
+
| invalid_timestamp | Non-ISO 8601 / unparseable timestamp. |
|
19
|
+
| deprecated_keys | Usage of forbidden alias keys (f, fw, ver, version, u, up, rssi). |
|
20
|
+
| invalid_sensor_entry | Sensor object missing `value` key. |
|
21
|
+
| future_drift | Timestamp too far in future (>5s). |
|
22
|
+
| out_of_range | battery_level not 0-100 OR quality_score not 0-1. |
|
23
|
+
| invalid_numeric | Non-numeric where numeric expected. |
|
24
|
+
| spec_violation | Generic fallback after failed internal validation. |
|
25
|
+
|
26
|
+
## Sensor Object Semantics
|
27
|
+
- `value` REQUIRED.
|
28
|
+
- `unit`, `raw_value`, `calibrated_value`, `quality_score`, `name`, `location`, `additional_data` OPTIONAL.
|
29
|
+
- If `quality_score` present must be 0.0–1.0 inclusive.
|
30
|
+
|
31
|
+
## Metrics Constraints
|
32
|
+
- Gateway metrics MUST appear under `metrics` (never at top-level).
|
33
|
+
- Required minimal metric: `uptime_s`.
|
34
|
+
|
35
|
+
## Firmware Update Status
|
36
|
+
- `status` must be one of: pending, downloading, flashing, verifying, rebooting, completed, failed.
|
37
|
+
- `progress_pct` (if present) must be 0–100.
|
38
|
+
|
39
|
+
## Extensibility
|
40
|
+
- Unknown top-level keys tolerated (future evolution) except if they collide with any deprecated alias or reserved future keys announced in CHANGELOG.
|
41
|
+
|
42
|
+
## Versioning
|
43
|
+
- Current `schema_version`: 1
|
44
|
+
- Additions remain optional; breaking changes introduce new schema set.
|
@@ -0,0 +1,7 @@
|
|
1
|
+
// Re-export the generated firmware repo types by importing relative path.
|
2
|
+
// This keeps single source of truth while enabling package consumers to import from package root.
|
3
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
4
|
+
// Re-export generated types copied during prebuild from firmware docs into src/generated/types.ts
|
5
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
6
|
+
// @ts-ignore
|
7
|
+
export * from './generated/types.js';
|
@@ -0,0 +1,76 @@
|
|
1
|
+
import Ajv from 'ajv/dist/2020.js';
|
2
|
+
import addFormats from 'ajv-formats';
|
3
|
+
// Schemas embedded via generated schema_data.ts (copy-schemas.cjs) to avoid filesystem dependency
|
4
|
+
import { envelope_schema, sensor_data_schema, sensor_heartbeat_schema, sensor_status_schema, gateway_info_schema, gateway_metrics_schema, firmware_status_schema, control_response_schema } from './schema_data.js';
|
5
|
+
// Load JSON schemas via createRequire so it works in both CJS and ESM builds without import assertions.
|
6
|
+
// Bind embedded schema objects for Ajv consumption
|
7
|
+
const envelope = envelope_schema;
|
8
|
+
const sensorData = sensor_data_schema;
|
9
|
+
const sensorHeartbeat = sensor_heartbeat_schema;
|
10
|
+
const sensorStatus = sensor_status_schema;
|
11
|
+
const gatewayInfo = gateway_info_schema;
|
12
|
+
const gatewayMetrics = gateway_metrics_schema;
|
13
|
+
const firmwareStatus = firmware_status_schema;
|
14
|
+
const controlResponse = control_response_schema;
|
15
|
+
// Lazy singleton Ajv instance so consumers can optionally supply their own if needed.
|
16
|
+
let _ajv = null;
|
17
|
+
function getAjv(opts) {
|
18
|
+
if (_ajv)
|
19
|
+
return _ajv;
|
20
|
+
_ajv = new Ajv({
|
21
|
+
strict: false,
|
22
|
+
allErrors: true,
|
23
|
+
allowUnionTypes: true,
|
24
|
+
...opts
|
25
|
+
});
|
26
|
+
addFormats(_ajv);
|
27
|
+
// Add base schema so $ref works for those referencing envelope
|
28
|
+
_ajv.addSchema(envelope, 'envelope.schema.json');
|
29
|
+
return _ajv;
|
30
|
+
}
|
31
|
+
function toResult(v, data) {
|
32
|
+
const valid = v(data);
|
33
|
+
if (valid)
|
34
|
+
return { valid: true };
|
35
|
+
return { valid: false, errors: (v.errors || []).map((e) => `${e.instancePath || '/'} ${e.message || ''}`.trim()) };
|
36
|
+
}
|
37
|
+
// Pre-compile validators (they are small; compilation cost negligible for typical web usage)
|
38
|
+
const ajv = getAjv();
|
39
|
+
const sensorDataValidate = ajv.compile(sensorData);
|
40
|
+
const sensorHeartbeatValidate = ajv.compile(sensorHeartbeat);
|
41
|
+
const sensorStatusValidate = ajv.compile(sensorStatus);
|
42
|
+
const gatewayInfoValidate = ajv.compile(gatewayInfo);
|
43
|
+
const gatewayMetricsValidate = ajv.compile(gatewayMetrics);
|
44
|
+
const firmwareStatusValidate = ajv.compile(firmwareStatus);
|
45
|
+
const controlResponseValidate = ajv.compile(controlResponse);
|
46
|
+
export const validators = {
|
47
|
+
sensorData: (d) => toResult(sensorDataValidate, d),
|
48
|
+
sensorHeartbeat: (d) => toResult(sensorHeartbeatValidate, d),
|
49
|
+
sensorStatus: (d) => toResult(sensorStatusValidate, d),
|
50
|
+
gatewayInfo: (d) => toResult(gatewayInfoValidate, d),
|
51
|
+
gatewayMetrics: (d) => toResult(gatewayMetricsValidate, d),
|
52
|
+
firmwareStatus: (d) => toResult(firmwareStatusValidate, d),
|
53
|
+
controlResponse: (d) => toResult(controlResponseValidate, d)
|
54
|
+
};
|
55
|
+
export function validateMessage(kind, data) {
|
56
|
+
return validators[kind](data);
|
57
|
+
}
|
58
|
+
// Classifier using lightweight heuristics to pick a schema validator.
|
59
|
+
export function classifyAndValidate(data) {
|
60
|
+
if (!data || typeof data !== 'object')
|
61
|
+
return { result: { valid: false, errors: ['Not an object'] } };
|
62
|
+
if (data.metrics)
|
63
|
+
return { kind: 'gatewayMetrics', result: validators.gatewayMetrics(data) };
|
64
|
+
if (data.sensors)
|
65
|
+
return { kind: 'sensorData', result: validators.sensorData(data) };
|
66
|
+
if (data.progress_pct !== undefined || (data.status && ['pending', 'downloading', 'flashing', 'verifying', 'rebooting', 'completed', 'failed'].includes(data.status)))
|
67
|
+
return { kind: 'firmwareStatus', result: validators.firmwareStatus(data) };
|
68
|
+
if (data.status && ['online', 'offline', 'updating', 'error'].includes(data.status) && data.device_type === 'sensor')
|
69
|
+
return { kind: 'sensorStatus', result: validators.sensorStatus(data) };
|
70
|
+
if (data.status && ['ok', 'error'].includes(data.status))
|
71
|
+
return { kind: 'controlResponse', result: validators.controlResponse(data) };
|
72
|
+
if (data.device_type === 'gateway')
|
73
|
+
return { kind: 'gatewayInfo', result: validators.gatewayInfo(data) };
|
74
|
+
// Fallback treat as heartbeat attempt
|
75
|
+
return { kind: 'sensorHeartbeat', result: validators.sensorHeartbeat(data) };
|
76
|
+
}
|
package/package.json
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
{
|
2
|
+
"name": "@alteriom/mqtt-schema",
|
3
|
+
"version": "0.2.0",
|
4
|
+
"description": "Alteriom MQTT v1 schemas, TypeScript types, and validation helpers for web integration",
|
5
|
+
"license": "MIT",
|
6
|
+
"author": "Alteriom Development Team",
|
7
|
+
"repository": {
|
8
|
+
"type": "git",
|
9
|
+
"url": "https://github.com/Alteriom/alteriom-firmware.git"
|
10
|
+
},
|
11
|
+
"homepage": "https://github.com/Alteriom/alteriom-firmware/tree/main/docs/mqtt_schema",
|
12
|
+
"bugs": {
|
13
|
+
"url": "https://github.com/Alteriom/alteriom-firmware/issues"
|
14
|
+
},
|
15
|
+
"type": "commonjs",
|
16
|
+
"main": "dist/cjs/index.js",
|
17
|
+
"module": "dist/esm/index.js",
|
18
|
+
"types": "dist/cjs/index.d.ts",
|
19
|
+
"sideEffects": false,
|
20
|
+
"exports": {
|
21
|
+
".": {
|
22
|
+
"require": "./dist/cjs/index.js",
|
23
|
+
"import": "./dist/esm/index.js",
|
24
|
+
"types": "./dist/cjs/index.d.ts",
|
25
|
+
"default": "./dist/cjs/index.js"
|
26
|
+
},
|
27
|
+
"./validators": {
|
28
|
+
"require": "./dist/cjs/validators.js",
|
29
|
+
"import": "./dist/esm/validators.js",
|
30
|
+
"types": "./dist/cjs/validators.d.ts",
|
31
|
+
"default": "./dist/cjs/validators.js"
|
32
|
+
},
|
33
|
+
"./types": {
|
34
|
+
"require": "./dist/cjs/types.js",
|
35
|
+
"import": "./dist/esm/types.js",
|
36
|
+
"types": "./dist/cjs/types.d.ts",
|
37
|
+
"default": "./dist/cjs/types.js"
|
38
|
+
},
|
39
|
+
"./schemas/*": {
|
40
|
+
"default": "./schemas/*"
|
41
|
+
},
|
42
|
+
"./package.json": "./package.json"
|
43
|
+
},
|
44
|
+
"typesVersions": {
|
45
|
+
"*": {
|
46
|
+
"validators": [
|
47
|
+
"dist/cjs/validators.d.ts"
|
48
|
+
],
|
49
|
+
"types": [
|
50
|
+
"dist/cjs/types.d.ts"
|
51
|
+
],
|
52
|
+
"schemas/*": [
|
53
|
+
"schemas/*"
|
54
|
+
]
|
55
|
+
}
|
56
|
+
},
|
57
|
+
"files": [
|
58
|
+
"dist/",
|
59
|
+
"schemas/",
|
60
|
+
"README.md",
|
61
|
+
"LICENSE"
|
62
|
+
],
|
63
|
+
"scripts": {
|
64
|
+
"prebuild": "npm run clean && node scripts/copy-schemas.cjs",
|
65
|
+
"build:cjs": "tsc -p tsconfig.json",
|
66
|
+
"build:esm": "tsc -p tsconfig.esm.json && node scripts/post-esm.cjs",
|
67
|
+
"build": "npm run build:cjs && npm run build:esm && node scripts/copy-schemas.cjs",
|
68
|
+
"clean": "rimraf dist schemas",
|
69
|
+
"prepare": "npm run build",
|
70
|
+
"lint": "echo 'No linter configured'",
|
71
|
+
"test": "node test/validate-fixtures.cjs",
|
72
|
+
"release:prepare": "node scripts/release-prepare.cjs"
|
73
|
+
},
|
74
|
+
"keywords": [
|
75
|
+
"alteriom",
|
76
|
+
"mqtt",
|
77
|
+
"iot",
|
78
|
+
"schema",
|
79
|
+
"validation",
|
80
|
+
"typescript"
|
81
|
+
],
|
82
|
+
"peerDependencies": {
|
83
|
+
"ajv": ">=8.0.0"
|
84
|
+
},
|
85
|
+
"devDependencies": {
|
86
|
+
"ajv": "^8.17.0",
|
87
|
+
"ajv-formats": "^2.1.1",
|
88
|
+
"rimraf": "^5.0.5",
|
89
|
+
"typescript": "^5.4.0"
|
90
|
+
}
|
91
|
+
}
|