miravo 0.6.0 → 0.6.1
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 +199 -0
- package/README.md +7 -70
- package/bin/miravo.js +84 -0
- package/package.json +20 -36
- package/console-dist/assets/activity-DsWeRq4h.js +0 -1
- package/console-dist/assets/api-4AqKvsm9.js +0 -1
- package/console-dist/assets/arrow-right-Ea_UhTNi.js +0 -1
- package/console-dist/assets/arrow-up-down-DFSjzzl2.js +0 -1
- package/console-dist/assets/blueprint-card-classes-DBFCDy0M.js +0 -1
- package/console-dist/assets/box-C1WaoOMF.js +0 -1
- package/console-dist/assets/card-BjeP32PN.js +0 -1
- package/console-dist/assets/catalog-BJzyzbLR.js +0 -1
- package/console-dist/assets/catalog-DKjEm-c4.js +0 -1
- package/console-dist/assets/catalog-template-icons-BrUeHaCY.js +0 -1
- package/console-dist/assets/checkbox-DZlJXTE4.js +0 -1
- package/console-dist/assets/chunk-OE4NN4TA-CEjpfhS6.js +0 -3
- package/console-dist/assets/combobox-BGnuCYxO.js +0 -1
- package/console-dist/assets/control-CPhvwx9o.js +0 -1
- package/console-dist/assets/control-Cg2up9NA.js +0 -1
- package/console-dist/assets/copy-button-WkbNkMcd.js +0 -1
- package/console-dist/assets/createLucideIcon-Bwxw859K.js +0 -1
- package/console-dist/assets/deploy-tab-JeWDwPoZ.js +0 -1
- package/console-dist/assets/dist-BhbDML0a.js +0 -1
- package/console-dist/assets/dist-Cy8XTNFT.js +0 -2
- package/console-dist/assets/download-DQ6NH16U.js +0 -1
- package/console-dist/assets/engine-store-CrLnz692.js +0 -1
- package/console-dist/assets/events-CXm29Ok4.js +0 -1
- package/console-dist/assets/events-D01IxhnP.js +0 -1
- package/console-dist/assets/fira-code-cyrillic-ext-wght-normal-DhYMMuQd.woff2 +0 -0
- package/console-dist/assets/fira-code-cyrillic-wght-normal-Y3u8pIsh.woff2 +0 -0
- package/console-dist/assets/fira-code-greek-ext-wght-normal-wWus70Ix.woff2 +0 -0
- package/console-dist/assets/fira-code-greek-wght-normal-B2SviObF.woff2 +0 -0
- package/console-dist/assets/fira-code-latin-ext-wght-normal-Dvfvaomy.woff2 +0 -0
- package/console-dist/assets/fira-code-latin-wght-normal-CHoedHDv.woff2 +0 -0
- package/console-dist/assets/fira-code-symbols2-wght-normal-CE6EOz_n.woff2 +0 -0
- package/console-dist/assets/fleet-CfxbKXt9.js +0 -1
- package/console-dist/assets/fleet-lotMGVqe.js +0 -1
- package/console-dist/assets/format-kXTonpDJ.js +0 -1
- package/console-dist/assets/gauge-Ci8zNZlk.js +0 -1
- package/console-dist/assets/get-error-message-3Q5Q-2G5.js +0 -1
- package/console-dist/assets/index-CVPKSDvJ.js +0 -12
- package/console-dist/assets/index-DJkgDeun.css +0 -2
- package/console-dist/assets/input-BWEQFptx.js +0 -1
- package/console-dist/assets/instance-detail-C5IVwuh3.js +0 -1
- package/console-dist/assets/karla-latin-ext-wght-normal-Dq0rrjgs.woff2 +0 -0
- package/console-dist/assets/karla-latin-wght-normal-C3-ma4ov.woff2 +0 -0
- package/console-dist/assets/label-uephX5ZG.js +0 -1
- package/console-dist/assets/layers-CUsLQ1fq.js +0 -1
- package/console-dist/assets/login-C3RJli0m.js +0 -126
- package/console-dist/assets/models-B_UioWQ5.js +0 -1
- package/console-dist/assets/models-_IPlPiZ7.js +0 -1
- package/console-dist/assets/not-found-B8gAm08S.js +0 -1
- package/console-dist/assets/platform-targets-R3T-4eJ7.js +0 -1
- package/console-dist/assets/platforms-BWh0BQ3Y.js +0 -1
- package/console-dist/assets/platforms-DStNbtVc.js +0 -1
- package/console-dist/assets/proxy-CsnYqnPp.js +0 -9
- package/console-dist/assets/query-keys-BvtE7BMs.js +0 -1
- package/console-dist/assets/radio-CoHegOv5.js +0 -1
- package/console-dist/assets/rolldown-runtime-COnpUsM8.js +0 -1
- package/console-dist/assets/schibsted-grotesk-latin-ext-wght-normal-hsMS0n0O.woff2 +0 -0
- package/console-dist/assets/schibsted-grotesk-latin-wght-normal-Bb8VGrTG.woff2 +0 -0
- package/console-dist/assets/select-CeO4vP0G.js +0 -1
- package/console-dist/assets/select-display-items-CIzY8953.js +0 -1
- package/console-dist/assets/server-D9Kfn8fQ.js +0 -1
- package/console-dist/assets/shallow-6lGjQhtJ.js +0 -1
- package/console-dist/assets/tags-CiXOIHtY.js +0 -4
- package/console-dist/assets/tags-FQCXIxrP.js +0 -1
- package/console-dist/assets/tooltip-HGLF366Q.js +0 -1
- package/console-dist/assets/transition-nav-link-Df4Rk6vV.js +0 -1
- package/console-dist/assets/transition-types-CPlGtM8w.js +0 -1
- package/console-dist/assets/triangle-alert-adNk_sea.js +0 -1
- package/console-dist/assets/use-platform-stream-DGbK1k2L.js +0 -2
- package/console-dist/assets/use-transition-navigate-CCpcWXB0.js +0 -2
- package/console-dist/assets/validation-Blvud9_O.js +0 -1
- package/console-dist/assets/vendor-charts-CAZdaPlq.js +0 -60
- package/console-dist/assets/vendor-table-wd8JuMmT.js +0 -4
- package/console-dist/assets/vendor-ui-ByahnaY0.js +0 -1
- package/console-dist/favicon.svg +0 -37
- package/console-dist/index.html +0 -82
- package/console-dist/miravo-logo.png +0 -0
- package/src/__tests__/integration/executable-methods-runner.ts +0 -262
- package/src/__tests__/integration/executable-methods.integration.ts +0 -2348
- package/src/__tests__/integration/opcua-concurrent-clients.integration.ts +0 -314
- package/src/commands/adapter.ts +0 -249
- package/src/commands/apply.test.ts +0 -62
- package/src/commands/apply.ts +0 -216
- package/src/commands/cleanup.test.ts +0 -34
- package/src/commands/cleanup.ts +0 -201
- package/src/commands/cli-test-setup.ts +0 -91
- package/src/commands/export.test.ts +0 -51
- package/src/commands/export.ts +0 -404
- package/src/commands/fork.ts +0 -219
- package/src/commands/inject.test.ts +0 -57
- package/src/commands/inject.ts +0 -212
- package/src/commands/load.ts +0 -167
- package/src/commands/logs.ts +0 -173
- package/src/commands/models.test.ts +0 -162
- package/src/commands/models.ts +0 -338
- package/src/commands/pause.ts +0 -67
- package/src/commands/platforms.test.ts +0 -186
- package/src/commands/platforms.ts +0 -371
- package/src/commands/ps.ts +0 -116
- package/src/commands/remove.ts +0 -38
- package/src/commands/reset.ts +0 -44
- package/src/commands/rm.ts +0 -97
- package/src/commands/spawn.ts +0 -45
- package/src/commands/speed.ts +0 -40
- package/src/commands/start/adapter-config.ts +0 -157
- package/src/commands/start/catalog-le-dt.test.ts +0 -50
- package/src/commands/start/catalog-le-dt.ts +0 -187
- package/src/commands/start/catalog-model-schema.ts +0 -89
- package/src/commands/start/catalog-template-detail.ts +0 -54
- package/src/commands/start/daemon.ts +0 -255
- package/src/commands/start/display.ts +0 -150
- package/src/commands/start/engine-setup.ts +0 -247
- package/src/commands/start/events.ts +0 -126
- package/src/commands/start/index.ts +0 -897
- package/src/commands/start/protocol-plans.ts +0 -239
- package/src/commands/start/template-loader.ts +0 -197
- package/src/commands/start/template-resolver.ts +0 -185
- package/src/commands/start/types.ts +0 -73
- package/src/commands/start.test.ts +0 -480
- package/src/commands/status.test.ts +0 -20
- package/src/commands/status.ts +0 -470
- package/src/commands/stop.test.ts +0 -84
- package/src/commands/stop.ts +0 -169
- package/src/commands/templates.test.ts +0 -58
- package/src/commands/templates.ts +0 -277
- package/src/index.ts +0 -166
- package/src/lib/admin-auth.ts +0 -157
- package/src/lib/admin-client.test.ts +0 -51
- package/src/lib/admin-client.ts +0 -326
- package/src/lib/admin-platform-routes.ts +0 -1017
- package/src/lib/admin-platform-stream.test.ts +0 -434
- package/src/lib/admin-platform-stream.ts +0 -788
- package/src/lib/admin-responses.ts +0 -211
- package/src/lib/admin-server.test.ts +0 -781
- package/src/lib/admin-server.ts +0 -797
- package/src/lib/auth-store.ts +0 -87
- package/src/lib/daemon-registry.test.ts +0 -429
- package/src/lib/daemon-registry.ts +0 -343
- package/src/lib/le/le-apply.test.ts +0 -591
- package/src/lib/le/le-apply.ts +0 -832
- package/src/lib/le/le-auth.ts +0 -89
- package/src/lib/le/le-cleanup.test.ts +0 -168
- package/src/lib/le/le-cleanup.ts +0 -399
- package/src/lib/le/le-cli-helpers.ts +0 -77
- package/src/lib/le/le-client.ts +0 -184
- package/src/lib/le/le-concurrency.test.ts +0 -157
- package/src/lib/le/le-concurrency.ts +0 -78
- package/src/lib/le/le-connection-test.ts +0 -42
- package/src/lib/le/le-devicehub-properties.test.ts +0 -53
- package/src/lib/le/le-devicehub-properties.ts +0 -33
- package/src/lib/le/le-devicehub.ts +0 -213
- package/src/lib/le/le-digital-twins.ts +0 -285
- package/src/lib/le/le-drivers.ts +0 -23
- package/src/lib/le/le-endpoint.ts +0 -40
- package/src/lib/le/le-environments.test.ts +0 -73
- package/src/lib/le/le-environments.ts +0 -156
- package/src/lib/le/le-markers.ts +0 -87
- package/src/lib/le/le-plan.test.ts +0 -26
- package/src/lib/le/le-plan.ts +0 -109
- package/src/lib/le/le-platform-operations.test.ts +0 -198
- package/src/lib/le/le-platform-operations.ts +0 -340
- package/src/lib/le/le-static-model-schema.ts +0 -78
- package/src/lib/le/le-static-planner.test.ts +0 -255
- package/src/lib/le/le-static-planner.ts +0 -442
- package/src/lib/le/le-template-exporter.test.ts +0 -527
- package/src/lib/le/le-template-exporter.ts +0 -459
- package/src/lib/le/le-value-types.ts +0 -23
- package/src/lib/mqtt-client.ts +0 -115
- package/src/lib/parse.test.ts +0 -123
- package/src/lib/parse.ts +0 -87
- package/src/lib/resolve-simulation.test.ts +0 -118
- package/src/lib/resolve-simulation.ts +0 -181
- package/src/lib/session-store.ts +0 -110
- package/src/lib/shutdown.test.ts +0 -562
- package/src/lib/shutdown.ts +0 -621
- package/src/lib/template-category.test.ts +0 -71
- package/src/lib/template-category.ts +0 -72
- package/src/lib/template-locator.ts +0 -55
- package/src/lib/template-utils.ts +0 -45
- package/src/lib/ws-hub.ts +0 -281
- package/src/logger.test.ts +0 -172
- package/src/logger.ts +0 -402
- package/src/output.test.ts +0 -64
- package/src/output.ts +0 -160
|
@@ -1,442 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Builds desired state for LE apply from a local template + model catalog.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { resolve } from "node:path";
|
|
6
|
-
import type { ContentCatalog } from "@miravo/core";
|
|
7
|
-
import { deriveInstancePrefix } from "@miravo/core";
|
|
8
|
-
import { dataTypeToModbus } from "@miravo/modbus";
|
|
9
|
-
import type { MiravoTemplate, ProtocolName, TwinModel } from "@miravo/shared";
|
|
10
|
-
import {
|
|
11
|
-
MiravoError,
|
|
12
|
-
MiravoTemplateSchema,
|
|
13
|
-
resolveProtocolScope,
|
|
14
|
-
TWIN_FILE_EXTENSION,
|
|
15
|
-
TwinModelSchema,
|
|
16
|
-
} from "@miravo/shared";
|
|
17
|
-
import { parse as parseYAML } from "yaml";
|
|
18
|
-
import { buildLeDtJsonFromSchema } from "../../commands/start/catalog-le-dt.ts";
|
|
19
|
-
import { buildUNSBasePath, collectReferencedModels } from "../template-utils.ts";
|
|
20
|
-
import type {
|
|
21
|
-
ApplyDesiredState,
|
|
22
|
-
DesiredDevice,
|
|
23
|
-
DesiredInstance,
|
|
24
|
-
DesiredModel,
|
|
25
|
-
DesiredTag,
|
|
26
|
-
} from "./le-apply.ts";
|
|
27
|
-
import { extractTemplateId } from "./le-cli-helpers.ts";
|
|
28
|
-
import { buildLeDeviceHubDeviceProperties } from "./le-devicehub-properties.ts";
|
|
29
|
-
import { resolveProtocolPort } from "./le-endpoint.ts";
|
|
30
|
-
import { toModelSchema } from "./le-static-model-schema.ts";
|
|
31
|
-
import { MODBUS_VALUE_TYPE, OPCUA_VALUE_TYPE } from "./le-value-types.ts";
|
|
32
|
-
|
|
33
|
-
interface StaticPlannerFilters {
|
|
34
|
-
readonly area?: string | undefined;
|
|
35
|
-
readonly line?: string | undefined;
|
|
36
|
-
readonly instance?: string | undefined;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
interface BuildStaticDesiredStateInput {
|
|
40
|
-
readonly templatePath: string;
|
|
41
|
-
readonly catalog: ContentCatalog;
|
|
42
|
-
readonly sourceHostname: string;
|
|
43
|
-
readonly protocol?: ProtocolName | undefined;
|
|
44
|
-
readonly filters?: StaticPlannerFilters | undefined;
|
|
45
|
-
readonly onDefaultProtocol?: ((protocol: ProtocolName) => void) | undefined;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
interface StaticPlannerResult {
|
|
49
|
-
readonly templateId: string;
|
|
50
|
-
readonly desired: ApplyDesiredState;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
interface PlannedInstance {
|
|
54
|
-
readonly instanceId: string;
|
|
55
|
-
readonly modelName: string;
|
|
56
|
-
readonly protocol: ProtocolName;
|
|
57
|
-
readonly browsePath: string;
|
|
58
|
-
readonly areaName: string;
|
|
59
|
-
readonly lineName: string;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
interface ProtocolPortConfig {
|
|
63
|
-
readonly mqtt?: { readonly port?: number };
|
|
64
|
-
readonly opcua?: { readonly port?: number };
|
|
65
|
-
readonly modbus?: { readonly port?: number };
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export async function buildDesiredStateFromTemplate(
|
|
69
|
-
input: BuildStaticDesiredStateInput,
|
|
70
|
-
): Promise<StaticPlannerResult> {
|
|
71
|
-
if (!input.sourceHostname.trim()) {
|
|
72
|
-
throw new MiravoError(
|
|
73
|
-
"sourceHostname must not be empty.",
|
|
74
|
-
"Provide a valid hostname or IP address for the source server.",
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const template = await loadTemplate(input.templatePath);
|
|
79
|
-
const modelsByName = await loadReferencedModels(template, input.templatePath, input.catalog);
|
|
80
|
-
|
|
81
|
-
const plannedInstances: PlannedInstance[] = [];
|
|
82
|
-
const modelCounters = new Map<string, number>();
|
|
83
|
-
const modbusAddressByMember = new Map<string, number>();
|
|
84
|
-
let nextModbusAddress = 0;
|
|
85
|
-
|
|
86
|
-
for (const area of template.areas) {
|
|
87
|
-
for (const line of area.lines) {
|
|
88
|
-
for (const twin of line.twins) {
|
|
89
|
-
const protocol = resolveSingleProtocol(
|
|
90
|
-
area.name,
|
|
91
|
-
line.name,
|
|
92
|
-
twin.model,
|
|
93
|
-
{ template, area, line, twin },
|
|
94
|
-
input.protocol,
|
|
95
|
-
input.onDefaultProtocol,
|
|
96
|
-
);
|
|
97
|
-
|
|
98
|
-
const model = modelsByName.get(twin.model);
|
|
99
|
-
if (!model) {
|
|
100
|
-
throw new MiravoError(
|
|
101
|
-
`Model "${twin.model}" was not loaded for static LE planning.`,
|
|
102
|
-
"Ensure the template references valid model files and retry.",
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
for (let index = 0; index < twin.count; index++) {
|
|
107
|
-
const counter = (modelCounters.get(twin.model) ?? 0) + 1;
|
|
108
|
-
modelCounters.set(twin.model, counter);
|
|
109
|
-
|
|
110
|
-
const instanceId = `${deriveInstancePrefix(twin.model)}-${String(counter).padStart(3, "0")}`;
|
|
111
|
-
const browsePath = `${buildUNSBasePath(template, area.name, line.name)}/${instanceId}`;
|
|
112
|
-
|
|
113
|
-
plannedInstances.push({
|
|
114
|
-
instanceId,
|
|
115
|
-
modelName: twin.model,
|
|
116
|
-
protocol,
|
|
117
|
-
browsePath,
|
|
118
|
-
areaName: area.name,
|
|
119
|
-
lineName: line.name,
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
if (protocol === "modbus") {
|
|
123
|
-
const modbusAddresses = allocateModbusAddresses(model, nextModbusAddress);
|
|
124
|
-
for (const [memberName, address] of modbusAddresses.addressByMember) {
|
|
125
|
-
modbusAddressByMember.set(`${instanceId}/${memberName}`, address);
|
|
126
|
-
}
|
|
127
|
-
nextModbusAddress = modbusAddresses.nextAddress;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const devices: DesiredDevice[] = [];
|
|
135
|
-
const instances: DesiredInstance[] = [];
|
|
136
|
-
const selectedModels = new Set<string>();
|
|
137
|
-
const protocolPortConfig = buildProtocolPortConfig(template);
|
|
138
|
-
|
|
139
|
-
for (const instance of plannedInstances) {
|
|
140
|
-
if (!matchesFilters(instance, input.filters)) continue;
|
|
141
|
-
|
|
142
|
-
const model = modelsByName.get(instance.modelName);
|
|
143
|
-
if (!model) continue;
|
|
144
|
-
|
|
145
|
-
selectedModels.add(instance.modelName);
|
|
146
|
-
|
|
147
|
-
const endpointPort = resolveProtocolPort(instance.protocol, protocolPortConfig);
|
|
148
|
-
|
|
149
|
-
devices.push({
|
|
150
|
-
instanceId: instance.instanceId,
|
|
151
|
-
modelName: instance.modelName,
|
|
152
|
-
protocol: instance.protocol,
|
|
153
|
-
deviceName: instance.instanceId,
|
|
154
|
-
browsePath: instance.browsePath,
|
|
155
|
-
endpoint: {
|
|
156
|
-
host: input.sourceHostname,
|
|
157
|
-
port: endpointPort,
|
|
158
|
-
},
|
|
159
|
-
properties: buildLeDeviceHubDeviceProperties(
|
|
160
|
-
instance.protocol,
|
|
161
|
-
input.sourceHostname,
|
|
162
|
-
endpointPort,
|
|
163
|
-
),
|
|
164
|
-
tags: buildTagsForInstance({
|
|
165
|
-
instance,
|
|
166
|
-
model,
|
|
167
|
-
modbusAddressByMember,
|
|
168
|
-
}),
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
instances.push({
|
|
172
|
-
instanceId: instance.instanceId,
|
|
173
|
-
modelName: instance.modelName,
|
|
174
|
-
dtInstanceName: instance.instanceId,
|
|
175
|
-
dtModelName: instance.modelName.toUpperCase(),
|
|
176
|
-
boundDeviceName: instance.instanceId,
|
|
177
|
-
browsePath: instance.browsePath,
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
const models: DesiredModel[] = [];
|
|
182
|
-
for (const modelName of selectedModels) {
|
|
183
|
-
const model = modelsByName.get(modelName);
|
|
184
|
-
if (!model) continue;
|
|
185
|
-
|
|
186
|
-
models.push({
|
|
187
|
-
name: modelName.toUpperCase(),
|
|
188
|
-
leDtJson: JSON.parse(buildLeDtJsonFromSchema(toModelSchema(model))) as unknown,
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
return {
|
|
193
|
-
templateId: extractTemplateId(input.templatePath),
|
|
194
|
-
desired: {
|
|
195
|
-
devices,
|
|
196
|
-
models,
|
|
197
|
-
instances,
|
|
198
|
-
},
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
function matchesFilters(
|
|
203
|
-
instance: PlannedInstance,
|
|
204
|
-
filters?: StaticPlannerFilters | undefined,
|
|
205
|
-
): boolean {
|
|
206
|
-
if (!filters) return true;
|
|
207
|
-
if (filters.area && instance.areaName !== filters.area) return false;
|
|
208
|
-
if (filters.line && instance.lineName !== filters.line) return false;
|
|
209
|
-
if (filters.instance && instance.instanceId !== filters.instance) return false;
|
|
210
|
-
return true;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
function resolveSingleProtocol(
|
|
214
|
-
areaName: string,
|
|
215
|
-
lineName: string,
|
|
216
|
-
modelName: string,
|
|
217
|
-
ctx: Parameters<typeof resolveProtocolScope>[0],
|
|
218
|
-
cliProtocol?: ProtocolName | undefined,
|
|
219
|
-
onDefaultProtocol?: ((protocol: ProtocolName) => void) | undefined,
|
|
220
|
-
): ProtocolName {
|
|
221
|
-
const scope = resolveProtocolScope(ctx);
|
|
222
|
-
|
|
223
|
-
// CLI --protocol override or default when template has no protocol config
|
|
224
|
-
if (scope.length === 0) {
|
|
225
|
-
const resolved = cliProtocol ?? "opcua";
|
|
226
|
-
if (!cliProtocol) onDefaultProtocol?.(resolved);
|
|
227
|
-
return resolved;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// CLI --protocol narrows multi-protocol scope to the requested one
|
|
231
|
-
if (scope.length > 1) {
|
|
232
|
-
if (cliProtocol) {
|
|
233
|
-
if (scope.includes(cliProtocol)) return cliProtocol;
|
|
234
|
-
throw new MiravoError(
|
|
235
|
-
`--protocol ${cliProtocol} is not in scope (${scope.join(", ")}) for model "${modelName}" at ${areaName}/${lineName}.`,
|
|
236
|
-
`Choose one of: ${scope.join(", ")}.`,
|
|
237
|
-
);
|
|
238
|
-
}
|
|
239
|
-
throw new MiravoError(
|
|
240
|
-
`Instance scope for model "${modelName}" at ${areaName}/${lineName} resolved to multiple protocols: ${scope.join(", ")}.`,
|
|
241
|
-
"Provide --protocol to select one, or set protocol_scope to exactly one protocol.",
|
|
242
|
-
);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
const protocol = scope[0];
|
|
246
|
-
if (!protocol) {
|
|
247
|
-
throw new MiravoError(
|
|
248
|
-
`Failed to resolve protocol for model "${modelName}" at ${areaName}/${lineName}.`,
|
|
249
|
-
"Provide --protocol <mqtt|opcua|modbus>.",
|
|
250
|
-
);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
return protocol;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
function buildProtocolPortConfig(template: MiravoTemplate): ProtocolPortConfig {
|
|
257
|
-
const config: {
|
|
258
|
-
mqtt?: { port?: number };
|
|
259
|
-
opcua?: { port?: number };
|
|
260
|
-
modbus?: { port?: number };
|
|
261
|
-
} = {};
|
|
262
|
-
|
|
263
|
-
const protocols = template.protocols;
|
|
264
|
-
if (!protocols) return config;
|
|
265
|
-
|
|
266
|
-
if (protocols.mqtt !== undefined) {
|
|
267
|
-
config.mqtt = protocols.mqtt.port !== undefined ? { port: protocols.mqtt.port } : {};
|
|
268
|
-
}
|
|
269
|
-
if (protocols.opcua !== undefined) {
|
|
270
|
-
config.opcua = protocols.opcua.port !== undefined ? { port: protocols.opcua.port } : {};
|
|
271
|
-
}
|
|
272
|
-
if (protocols.modbus !== undefined) {
|
|
273
|
-
config.modbus = protocols.modbus.port !== undefined ? { port: protocols.modbus.port } : {};
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
return config;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
function buildTagsForInstance(input: {
|
|
280
|
-
instance: PlannedInstance;
|
|
281
|
-
model: TwinModel;
|
|
282
|
-
modbusAddressByMember: ReadonlyMap<string, number>;
|
|
283
|
-
}): readonly DesiredTag[] {
|
|
284
|
-
const memberEntries = Object.entries(input.model.members);
|
|
285
|
-
|
|
286
|
-
switch (input.instance.protocol) {
|
|
287
|
-
case "opcua":
|
|
288
|
-
return memberEntries.map(([memberName, memberDef]) => ({
|
|
289
|
-
name: `ns=1;s=${input.instance.browsePath}/${memberName}`,
|
|
290
|
-
tagName: memberName,
|
|
291
|
-
valueType: OPCUA_VALUE_TYPE[memberDef.data_type] ?? "string",
|
|
292
|
-
properties: [],
|
|
293
|
-
unsPath: `${input.instance.browsePath}/${memberName}`,
|
|
294
|
-
access: memberDef.access,
|
|
295
|
-
unit: memberDef.unit,
|
|
296
|
-
euRange: "eu_range" in memberDef ? memberDef.eu_range : undefined,
|
|
297
|
-
}));
|
|
298
|
-
|
|
299
|
-
case "mqtt":
|
|
300
|
-
return memberEntries.map(([memberName, memberDef]) => ({
|
|
301
|
-
name: `${input.instance.browsePath}/${memberName}`,
|
|
302
|
-
tagName: memberName,
|
|
303
|
-
valueType: "string",
|
|
304
|
-
properties: [],
|
|
305
|
-
unsPath: `${input.instance.browsePath}/${memberName}`,
|
|
306
|
-
access: memberDef.access,
|
|
307
|
-
unit: memberDef.unit,
|
|
308
|
-
euRange: "eu_range" in memberDef ? memberDef.eu_range : undefined,
|
|
309
|
-
}));
|
|
310
|
-
|
|
311
|
-
case "modbus": {
|
|
312
|
-
const tags: DesiredTag[] = [];
|
|
313
|
-
for (const [memberName, memberDef] of memberEntries) {
|
|
314
|
-
const typeInfo = dataTypeToModbus(memberDef.data_type);
|
|
315
|
-
if (!typeInfo) continue;
|
|
316
|
-
|
|
317
|
-
const addressKey = `${input.instance.instanceId}/${memberName}`;
|
|
318
|
-
const address = input.modbusAddressByMember.get(addressKey);
|
|
319
|
-
if (address === undefined) {
|
|
320
|
-
throw new MiravoError(
|
|
321
|
-
`Missing planned Modbus address for "${addressKey}".`,
|
|
322
|
-
"Re-run LE apply; if this persists, inspect model member data types.",
|
|
323
|
-
);
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
tags.push({
|
|
327
|
-
name: "H",
|
|
328
|
-
tagName: memberName,
|
|
329
|
-
valueType: MODBUS_VALUE_TYPE[typeInfo.encoding] ?? "float32",
|
|
330
|
-
properties: [
|
|
331
|
-
{ Key: "address", Value: String(address) },
|
|
332
|
-
{ Key: "count", Value: "1" },
|
|
333
|
-
{ Key: "polling_interval", Value: "1.0" },
|
|
334
|
-
{ Key: "endianness", Value: "AB CD" },
|
|
335
|
-
],
|
|
336
|
-
unsPath: `${input.instance.browsePath}/${memberName}`,
|
|
337
|
-
access: memberDef.access,
|
|
338
|
-
unit: memberDef.unit ?? undefined,
|
|
339
|
-
euRange: "eu_range" in memberDef ? (memberDef.eu_range ?? undefined) : undefined,
|
|
340
|
-
});
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
return tags;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
default: {
|
|
347
|
-
const _never: never = input.instance.protocol;
|
|
348
|
-
throw new Error(`Unknown protocol: ${_never}`);
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
function allocateModbusAddresses(
|
|
354
|
-
model: TwinModel,
|
|
355
|
-
startAddress: number,
|
|
356
|
-
): {
|
|
357
|
-
readonly addressByMember: ReadonlyMap<string, number>;
|
|
358
|
-
readonly nextAddress: number;
|
|
359
|
-
} {
|
|
360
|
-
const addressByMember = new Map<string, number>();
|
|
361
|
-
let nextAddress = startAddress;
|
|
362
|
-
|
|
363
|
-
for (const [memberName, memberDef] of Object.entries(model.members)) {
|
|
364
|
-
const typeInfo = dataTypeToModbus(memberDef.data_type);
|
|
365
|
-
if (!typeInfo) continue;
|
|
366
|
-
|
|
367
|
-
addressByMember.set(memberName, nextAddress);
|
|
368
|
-
nextAddress += typeInfo.registerCount;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
return { addressByMember, nextAddress };
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
async function loadTemplate(templatePath: string): Promise<MiravoTemplate> {
|
|
375
|
-
try {
|
|
376
|
-
const text = await Bun.file(templatePath).text();
|
|
377
|
-
return MiravoTemplateSchema.parse(parseYAML(text));
|
|
378
|
-
} catch (error) {
|
|
379
|
-
throw new MiravoError(
|
|
380
|
-
`Failed to load template "${templatePath}" for static LE planning.`,
|
|
381
|
-
error instanceof Error ? error.message : "Ensure the template YAML is valid.",
|
|
382
|
-
);
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
async function loadReferencedModels(
|
|
387
|
-
template: MiravoTemplate,
|
|
388
|
-
templatePath: string,
|
|
389
|
-
catalog: ContentCatalog,
|
|
390
|
-
): Promise<ReadonlyMap<string, TwinModel>> {
|
|
391
|
-
const modelsByName = new Map<string, TwinModel>();
|
|
392
|
-
|
|
393
|
-
if (template.models) {
|
|
394
|
-
for (const model of template.models) {
|
|
395
|
-
modelsByName.set(model.model.name, model);
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
const referencedModelNames = collectReferencedModels(template);
|
|
400
|
-
|
|
401
|
-
for (const modelName of referencedModelNames) {
|
|
402
|
-
if (modelsByName.has(modelName)) continue;
|
|
403
|
-
|
|
404
|
-
const modelPath = await resolveModelPath(catalog, templatePath, modelName);
|
|
405
|
-
const model = await loadTwinModelFromPath(modelName, modelPath);
|
|
406
|
-
modelsByName.set(modelName, model);
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
return modelsByName;
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
async function resolveModelPath(
|
|
413
|
-
catalog: ContentCatalog,
|
|
414
|
-
templatePath: string,
|
|
415
|
-
modelName: string,
|
|
416
|
-
): Promise<string> {
|
|
417
|
-
const searchPaths = catalog.getTemplateValidationSearchPaths(templatePath);
|
|
418
|
-
|
|
419
|
-
for (const dir of searchPaths) {
|
|
420
|
-
const candidate = resolve(dir, `${modelName}${TWIN_FILE_EXTENSION}`);
|
|
421
|
-
if (await Bun.file(candidate).exists()) {
|
|
422
|
-
return candidate;
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
throw new MiravoError(
|
|
427
|
-
`Model "${modelName}" not found for template "${templatePath}".`,
|
|
428
|
-
`Add "${modelName}${TWIN_FILE_EXTENSION}" to the template directory or one of: ${searchPaths.join(", ")}.`,
|
|
429
|
-
);
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
async function loadTwinModelFromPath(modelName: string, modelPath: string): Promise<TwinModel> {
|
|
433
|
-
try {
|
|
434
|
-
const text = await Bun.file(modelPath).text();
|
|
435
|
-
return TwinModelSchema.parse(parseYAML(text));
|
|
436
|
-
} catch (error) {
|
|
437
|
-
throw new MiravoError(
|
|
438
|
-
`Failed to load model "${modelName}" from "${modelPath}".`,
|
|
439
|
-
error instanceof Error ? error.message : "Ensure the model YAML is valid.",
|
|
440
|
-
);
|
|
441
|
-
}
|
|
442
|
-
}
|