367 lines
8.5 KiB
JavaScript
367 lines
8.5 KiB
JavaScript
import { ENVIRONMENT_DEV } from "../protocol/index.mjs";
|
|
|
|
export const DEFAULT_PROJECT_ID = "prj_mvp_topology";
|
|
export const DEFAULT_GATEWAY_SESSION_ID = "gws_mvp_simu";
|
|
|
|
export const SIM_PORT_DEFINITIONS = Object.freeze([
|
|
{
|
|
port: "AI1",
|
|
family: "AI",
|
|
direction: "input",
|
|
valueType: "number",
|
|
unit: "V",
|
|
initialValue: 0
|
|
},
|
|
{
|
|
port: "AO1",
|
|
family: "AO",
|
|
direction: "output",
|
|
valueType: "number",
|
|
unit: "V",
|
|
initialValue: 0
|
|
},
|
|
{
|
|
port: "DI1",
|
|
family: "DI",
|
|
direction: "input",
|
|
valueType: "boolean",
|
|
initialValue: false
|
|
},
|
|
{
|
|
port: "DO1",
|
|
family: "DO",
|
|
direction: "output",
|
|
valueType: "boolean",
|
|
initialValue: false
|
|
},
|
|
{
|
|
port: "FREQ1",
|
|
family: "FREQ",
|
|
direction: "bidirectional",
|
|
valueType: "number",
|
|
unit: "Hz",
|
|
initialValue: 0
|
|
},
|
|
{
|
|
port: "FREQ_IN1",
|
|
family: "FREQ_IN",
|
|
direction: "input",
|
|
valueType: "number",
|
|
unit: "Hz",
|
|
initialValue: 0
|
|
},
|
|
{
|
|
port: "FREQ_OUT1",
|
|
family: "FREQ_OUT",
|
|
direction: "output",
|
|
valueType: "number",
|
|
unit: "Hz",
|
|
initialValue: 0
|
|
},
|
|
{
|
|
port: "uart0",
|
|
family: "SERIAL",
|
|
direction: "bidirectional",
|
|
valueType: "string",
|
|
initialValue: null,
|
|
legacy: true
|
|
},
|
|
{
|
|
port: "eth0",
|
|
family: "NETWORK",
|
|
direction: "bidirectional",
|
|
valueType: "object",
|
|
initialValue: null,
|
|
legacy: true
|
|
},
|
|
{
|
|
port: "gpio0",
|
|
family: "GPIO",
|
|
direction: "bidirectional",
|
|
valueType: "boolean",
|
|
initialValue: null,
|
|
legacy: true
|
|
}
|
|
]);
|
|
|
|
export const SIM_PORTS = Object.freeze(SIM_PORT_DEFINITIONS.map((definition) => definition.port));
|
|
|
|
export const L2_PORT_FAMILIES = Object.freeze(["AI", "AO", "DI", "DO", "FREQ", "FREQ_IN", "FREQ_OUT"]);
|
|
|
|
function isoNow() {
|
|
return new Date().toISOString();
|
|
}
|
|
|
|
export function selectInstanceValue(
|
|
value,
|
|
fallback,
|
|
{
|
|
index = process.env.HWLAB_INSTANCE_INDEX,
|
|
hostname = process.env.HOSTNAME,
|
|
requireIndexForMultiple = false,
|
|
label = "instance value"
|
|
} = {}
|
|
) {
|
|
const items = String(value ?? "")
|
|
.split(",")
|
|
.map((item) => item.trim())
|
|
.filter(Boolean);
|
|
|
|
if (items.length === 0) {
|
|
return fallback;
|
|
}
|
|
if (items.length === 1) {
|
|
return items[0];
|
|
}
|
|
|
|
const parsedIndex = Number.parseInt(index ?? "", 10);
|
|
if (Number.isInteger(parsedIndex) && parsedIndex >= 0 && parsedIndex < items.length) {
|
|
return items[parsedIndex];
|
|
}
|
|
|
|
const ordinal = Number.parseInt(String(hostname ?? "").match(/-(\d+)$/)?.[1] ?? "", 10);
|
|
if (Number.isInteger(ordinal) && ordinal >= 0 && ordinal < items.length) {
|
|
return items[ordinal];
|
|
}
|
|
|
|
if (requireIndexForMultiple) {
|
|
throw new Error(
|
|
`${label} declares ${items.length} indexed values, but neither HWLAB_INSTANCE_INDEX nor a StatefulSet ordinal HOSTNAME selected one`
|
|
);
|
|
}
|
|
|
|
return items[0];
|
|
}
|
|
|
|
export function stableIdPart(value) {
|
|
const normalized = String(value ?? "")
|
|
.trim()
|
|
.replace(/[^A-Za-z0-9._:-]+/g, "_")
|
|
.replace(/^_+|_+$/g, "");
|
|
|
|
return normalized || "sim";
|
|
}
|
|
|
|
export function resourceIdForBox(boxId) {
|
|
return `res_${stableIdPart(boxId)}`;
|
|
}
|
|
|
|
export function createBoxResource({
|
|
boxId,
|
|
gatewaySessionId = DEFAULT_GATEWAY_SESSION_ID,
|
|
ports = SIM_PORTS,
|
|
portFamilies = L2_PORT_FAMILIES,
|
|
now = isoNow()
|
|
}) {
|
|
return {
|
|
resourceId: resourceIdForBox(boxId),
|
|
projectId: DEFAULT_PROJECT_ID,
|
|
gatewaySessionId,
|
|
boxId,
|
|
resourceType: "simulator_endpoint",
|
|
name: `${boxId} simulator endpoint`,
|
|
state: "available",
|
|
environment: ENVIRONMENT_DEV,
|
|
metadata: {
|
|
serviceId: "hwlab-box-simu",
|
|
ports,
|
|
...(portFamilies.length > 0 ? { portFamilies } : {}),
|
|
propagation: "patch-panel-only"
|
|
},
|
|
createdAt: now,
|
|
updatedAt: now
|
|
};
|
|
}
|
|
|
|
export function createBoxCapabilities({
|
|
boxId,
|
|
resourceId = resourceIdForBox(boxId),
|
|
projectId = DEFAULT_PROJECT_ID,
|
|
portDefinitions = SIM_PORT_DEFINITIONS,
|
|
now = isoNow()
|
|
}) {
|
|
const capabilities = [];
|
|
const boxPart = stableIdPart(boxId);
|
|
|
|
for (const definition of portDefinitions) {
|
|
if (definition.legacy) {
|
|
continue;
|
|
}
|
|
|
|
const portPart = definition.port.toLowerCase();
|
|
const family = definition.family.toLowerCase();
|
|
capabilities.push({
|
|
capabilityId: `cap_${boxPart}_${portPart}_read`,
|
|
resourceId,
|
|
projectId,
|
|
name: `${family}.read`,
|
|
description: `Read ${definition.port} on ${boxId}.`,
|
|
direction: "input",
|
|
valueType: definition.valueType,
|
|
...(definition.unit ? { unit: definition.unit } : {}),
|
|
constraints: {
|
|
port: definition.port,
|
|
portFamily: definition.family,
|
|
internalPortApi: true,
|
|
patchPanelOnlyPropagation: true
|
|
},
|
|
mutatesState: false,
|
|
createdAt: now,
|
|
updatedAt: now
|
|
});
|
|
|
|
if (definition.direction === "output" || definition.direction === "bidirectional") {
|
|
capabilities.push({
|
|
capabilityId: `cap_${boxPart}_${portPart}_write`,
|
|
resourceId,
|
|
projectId,
|
|
name: `${family}.write`,
|
|
description: `Write ${definition.port} on ${boxId}.`,
|
|
direction: definition.direction === "bidirectional" ? "bidirectional" : "output",
|
|
valueType: definition.valueType,
|
|
...(definition.unit ? { unit: definition.unit } : {}),
|
|
constraints: {
|
|
port: definition.port,
|
|
portFamily: definition.family,
|
|
internalPortApi: false,
|
|
patchPanelOnlyPropagation: true
|
|
},
|
|
mutatesState: true,
|
|
createdAt: now,
|
|
updatedAt: now
|
|
});
|
|
}
|
|
}
|
|
|
|
return capabilities;
|
|
}
|
|
|
|
export function getPortDefinition(port) {
|
|
return SIM_PORT_DEFINITIONS.find((definition) => definition.port === port);
|
|
}
|
|
|
|
export function createGatewaySession({
|
|
gatewayId,
|
|
serviceId = "hwlab-gateway-simu",
|
|
endpoint,
|
|
now = isoNow()
|
|
}) {
|
|
return {
|
|
gatewaySessionId: `gws_${gatewayId}`,
|
|
projectId: DEFAULT_PROJECT_ID,
|
|
serviceId,
|
|
gatewayId,
|
|
endpoint,
|
|
status: "connected",
|
|
environment: ENVIRONMENT_DEV,
|
|
startedAt: now,
|
|
lastSeenAt: now,
|
|
labels: {
|
|
simulator: serviceId === "hwlab-gateway-simu" ? "true" : "false"
|
|
}
|
|
};
|
|
}
|
|
|
|
export function createBoxState({
|
|
boxId,
|
|
gatewaySessionId = DEFAULT_GATEWAY_SESSION_ID,
|
|
ports: portNames = SIM_PORTS,
|
|
includePortDefinitions = true,
|
|
now = isoNow()
|
|
}) {
|
|
const selectedDefinitions = portNames
|
|
.map((port) => getPortDefinition(port) ?? {
|
|
port,
|
|
family: port,
|
|
direction: "bidirectional",
|
|
valueType: "object",
|
|
initialValue: null,
|
|
legacy: !includePortDefinitions
|
|
});
|
|
const ports = Object.fromEntries(
|
|
selectedDefinitions.map((definition) => [
|
|
definition.port,
|
|
{
|
|
port: definition.port,
|
|
...(includePortDefinitions
|
|
? {
|
|
family: definition.family,
|
|
direction: definition.direction,
|
|
valueType: definition.valueType,
|
|
...(definition.unit ? { unit: definition.unit } : {}),
|
|
internalPortApi: true
|
|
}
|
|
: {}),
|
|
value: definition.initialValue,
|
|
source: "local",
|
|
updatedAt: now
|
|
}
|
|
])
|
|
);
|
|
const portFamilies = includePortDefinitions
|
|
? [...new Set(selectedDefinitions.map((definition) => definition.family))]
|
|
: [];
|
|
const resource = createBoxResource({
|
|
boxId,
|
|
gatewaySessionId,
|
|
ports: portNames,
|
|
portFamilies,
|
|
now
|
|
});
|
|
|
|
return {
|
|
serviceId: "hwlab-box-simu",
|
|
boxId,
|
|
projectId: DEFAULT_PROJECT_ID,
|
|
...(includePortDefinitions ? { environment: ENVIRONMENT_DEV } : {}),
|
|
gatewaySessionId,
|
|
live: true,
|
|
resource,
|
|
...(includePortDefinitions
|
|
? {
|
|
capabilities: createBoxCapabilities({
|
|
boxId,
|
|
resourceId: resource.resourceId,
|
|
projectId: DEFAULT_PROJECT_ID,
|
|
portDefinitions: selectedDefinitions,
|
|
now
|
|
})
|
|
}
|
|
: {}),
|
|
ports,
|
|
constraints: {
|
|
crossDevicePropagation: "patch-panel-only",
|
|
localLoopbackEnabled: false
|
|
},
|
|
updatedAt: now
|
|
};
|
|
}
|
|
|
|
export function createGatewayState({
|
|
gatewayId,
|
|
serviceId = "hwlab-gateway-simu",
|
|
endpoint,
|
|
boxes = [],
|
|
now = isoNow()
|
|
}) {
|
|
return {
|
|
serviceId,
|
|
gatewayId,
|
|
projectId: DEFAULT_PROJECT_ID,
|
|
live: true,
|
|
session: createGatewaySession({ gatewayId, serviceId, endpoint, now }),
|
|
boxes,
|
|
updatedAt: now
|
|
};
|
|
}
|
|
|
|
export function createHealth({ serviceId, name, now = isoNow() }) {
|
|
return {
|
|
serviceId,
|
|
name,
|
|
environment: ENVIRONMENT_DEV,
|
|
live: true,
|
|
observedAt: now
|
|
};
|
|
}
|