Seed the canonical OT schemas content under 3yearplan/schemas/ as a temporary location until a dedicated schemas repo is created (Gitea push-to-create is disabled, the dedicated repo needs a manual UI step). Initial seed contributed by the OtOpcUa team to unblock the EquipmentClassRef integration timeline (lmxopcua decision #112) and to provide the future cross-team owner with a concrete starting point rather than a blank slate. Marked DRAFT throughout with prominent "ownership TBD" framing in README and CONTRIBUTING — the future owner team should treat this seed as a starting point and revise format / structure / naming as the open questions in README "Open Questions" get resolved.
Includes: README explaining purpose / scope / temporary-location framing / format decision, CONTRIBUTING.md with proposed workflow + per-class semver versioning policy + validation commands, format/equipment-class.schema.json defining the shape of a class template (classId, version, displayName, applicability, signals, alarms, optional stateModel), format/tag-definition.schema.json defining the shape of a single canonical signal (name, dataType, category, unit, isArray, accessLevel, writeIdempotent, isHistorized, scaling), format/uns-subtree.schema.json defining the shape of a per-site UNS subtree (enterprise + site + areas + lines), classes/fanuc-cnc.json as the worked pilot class with 16 signals + 3 alarms + suggested state-derivation notes (per OtOpcUa corrections doc D1), uns/example-warsaw-west.json as a worked UNS subtree example, docs/overview.md (what / why / lifecycle / what's NOT in this repo), docs/format-decisions.md (8 numbered decisions covering JSON Schema choice per corrections D2, per-class semver, additive-only minor bumps, _default placeholder reservation, signal-name vs UNS-segment regex distinction, stateModel-as-informational, no per-equipment overrides at this layer, applicability.drivers as OtOpcUa driver enumeration), docs/consumer-integration.md (how OtOpcUa / Redpanda / dbt each integrate). $id URLs in the JSON schemas resolve at the actual current path so validators don't 404. Top-level README adds a row to the Component Detail Files table pointing to schemas/. Corrections doc B2 (schemas-repo dependencies) marked partially RESOLVED with the seed location and a list of what still needs the plan team or cross-team owner to decide (owner team naming, dedicated repo migration, format-decision ratification, FANUC CNC pilot confirmation, CI gate setup, Redpanda + dbt consumer integration plumbing). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
93
schemas/format/equipment-class.schema.json
Normal file
93
schemas/format/equipment-class.schema.json
Normal file
@@ -0,0 +1,93 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://gitea.dohertylan.com/dohertj2/3yearplan/raw/branch/main/schemas/format/equipment-class.schema.json",
|
||||
"title": "Equipment-Class Template",
|
||||
"description": "Declares the canonical raw signal vocabulary for an equipment class. Consumed by OtOpcUa (validates per-equipment tag config), Redpanda (derives Protobuf event schemas), and dbt (derives column definitions for the curated layer).",
|
||||
"type": "object",
|
||||
"required": ["classId", "version", "displayName", "signals"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"classId": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-z0-9-]{1,64}$",
|
||||
"description": "Stable logical ID for the class. Lowercase, hyphens allowed. Once published, never changes — clients pin to it."
|
||||
},
|
||||
"version": {
|
||||
"type": "string",
|
||||
"pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$",
|
||||
"description": "Semver. Major bump = breaking change; minor = additive non-breaking; patch = doc/clarification only."
|
||||
},
|
||||
"displayName": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 128,
|
||||
"description": "Human-readable name (e.g. 'FANUC CNC')."
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"maxLength": 1024,
|
||||
"description": "Optional longer description."
|
||||
},
|
||||
"vendor": {
|
||||
"type": "string",
|
||||
"maxLength": 64,
|
||||
"description": "Vendor or family (e.g. 'FANUC', 'Allen-Bradley', 'Siemens')."
|
||||
},
|
||||
"applicability": {
|
||||
"type": "object",
|
||||
"description": "When this class applies. Optional; omit for vendor-agnostic classes.",
|
||||
"properties": {
|
||||
"drivers": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": ["Galaxy", "ModbusTcp", "AbCip", "AbLegacy", "S7", "TwinCat", "Focas", "OpcUaClient"]
|
||||
},
|
||||
"description": "Which OtOpcUa drivers can populate equipment of this class."
|
||||
},
|
||||
"models": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"description": "Free-text model identifiers (e.g. '0i-F', '30i-B' for FANUC; '1756-L84E' for Logix)."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"signals": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": { "$ref": "tag-definition.schema.json" },
|
||||
"description": "Required signals every equipment of this class must expose. Operator-configured tags in OtOpcUa are validated against this list — missing required signals = config error; extra signals are allowed (operator-extensible)."
|
||||
},
|
||||
"alarms": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["alarmId", "displayName", "severity"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"alarmId": { "type": "string", "pattern": "^[a-z0-9-]{1,64}$" },
|
||||
"displayName": { "type": "string", "minLength": 1, "maxLength": 128 },
|
||||
"severity": { "type": "string", "enum": ["Low", "Medium", "High", "Critical"] },
|
||||
"description": { "type": "string", "maxLength": 1024 }
|
||||
}
|
||||
},
|
||||
"description": "Optional canonical alarm definitions for this class."
|
||||
},
|
||||
"stateModel": {
|
||||
"type": "object",
|
||||
"description": "Optional declaration of how this class's raw signals derive into the canonical machine-state vocabulary at Layer 3 (System Platform / Ignition). Informational — derivation lives at Layer 3, not in OtOpcUa.",
|
||||
"properties": {
|
||||
"states": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": ["Running", "Idle", "Faulted", "Starved", "Blocked", "Changeover", "Maintenance", "Setup"]
|
||||
}
|
||||
},
|
||||
"derivationNotes": { "type": "string", "maxLength": 2048 }
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}
|
||||
83
schemas/format/tag-definition.schema.json
Normal file
83
schemas/format/tag-definition.schema.json
Normal file
@@ -0,0 +1,83 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://gitea.dohertylan.com/dohertj2/3yearplan/raw/branch/main/schemas/format/tag-definition.schema.json",
|
||||
"title": "Tag Definition (Signal)",
|
||||
"description": "Declares a single canonical signal an equipment class exposes. Used inside equipment-class templates.",
|
||||
"type": "object",
|
||||
"required": ["name", "dataType", "category"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"pattern": "^[A-Za-z][A-Za-z0-9_]{0,63}$",
|
||||
"description": "Canonical signal name. PascalCase or snake_case at the equipment class's discretion; must be unique within the class. This is the OPC UA browse-name children of the equipment node."
|
||||
},
|
||||
"displayName": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 128,
|
||||
"description": "Human-readable name for dashboards and Admin UI. Defaults to `name` if omitted."
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"maxLength": 1024
|
||||
},
|
||||
"category": {
|
||||
"type": "string",
|
||||
"enum": ["Identity", "Status", "Process", "Position", "Velocity", "Acceleration", "Temperature", "Pressure", "Flow", "Counter", "Setpoint", "Command", "Alarm", "Diagnostic", "Other"],
|
||||
"description": "Coarse-grained category for filtering and dashboarding. Adds taxonomy without forcing a deep type hierarchy."
|
||||
},
|
||||
"dataType": {
|
||||
"type": "string",
|
||||
"enum": ["Boolean", "Int16", "Int32", "Int64", "UInt16", "UInt32", "UInt64", "Float32", "Float64", "String", "DateTime", "Reference"],
|
||||
"description": "OPC UA built-in type. Mirrors the values OtOpcUa drivers map to (per lmxopcua/docs/v2/driver-specs.md DataType column)."
|
||||
},
|
||||
"unit": {
|
||||
"type": "string",
|
||||
"maxLength": 32,
|
||||
"description": "Optional engineering unit (e.g. 'mm', 'mm/min', 'rpm', 'degC', 'bar'). Use UCUM where possible (https://ucum.org/)."
|
||||
},
|
||||
"isArray": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "True if this signal is an array (e.g. per-axis position vector)."
|
||||
},
|
||||
"arrayDimension": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"description": "Required when isArray=true. Maximum array length."
|
||||
},
|
||||
"accessLevel": {
|
||||
"type": "string",
|
||||
"enum": ["Read", "ReadWrite"],
|
||||
"default": "Read",
|
||||
"description": "Default access level when this signal is exposed via OPC UA. Per-equipment ACL grants (see lmxopcua/docs/v2/acl-design.md) can further restrict write access."
|
||||
},
|
||||
"writeIdempotent": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "True if writing the same value twice is safe (setpoint overwrite, mode selection). Drives OtOpcUa's Polly write-retry policy (lmxopcua decision #44–45)."
|
||||
},
|
||||
"isRequired": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "True (default) means every equipment of this class MUST expose this signal. False means it's class-supported but optional per equipment."
|
||||
},
|
||||
"isHistorized": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "True if this signal is expected to feed historian. Drives HistoryRead permission and OtOpcUa's IHistorianDataSource binding."
|
||||
},
|
||||
"scaling": {
|
||||
"type": "object",
|
||||
"description": "Optional scaling for raw integer values that represent decimal quantities (e.g. FANUC scaled-integer positions).",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"method": { "type": "string", "enum": ["Linear", "DecimalPlaces", "DriverDefined"] },
|
||||
"multiplier": { "type": "number" },
|
||||
"offset": { "type": "number" },
|
||||
"decimalPlaces": { "type": "integer" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
64
schemas/format/uns-subtree.schema.json
Normal file
64
schemas/format/uns-subtree.schema.json
Normal file
@@ -0,0 +1,64 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://gitea.dohertylan.com/dohertj2/3yearplan/raw/branch/main/schemas/format/uns-subtree.schema.json",
|
||||
"title": "UNS Subtree (per-site)",
|
||||
"description": "Declares the canonical UNS subtree for a site: Enterprise (level 1) + Site (level 2) + the Areas / Lines that exist at that site. Equipment (level 5) is configured per-cluster in OtOpcUa, not declared here — this file defines the higher-level structure operators are required to use.",
|
||||
"type": "object",
|
||||
"required": ["enterprise", "site", "areas"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"enterprise": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-z0-9-]{1,32}$",
|
||||
"description": "UNS level 1. Must match `ServerCluster.Enterprise` in every OtOpcUa cluster at this site."
|
||||
},
|
||||
"site": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-z0-9-]{1,32}$",
|
||||
"description": "UNS level 2. Must match `ServerCluster.Site` in every OtOpcUa cluster at this site."
|
||||
},
|
||||
"displayName": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 128,
|
||||
"description": "Human-readable site name (e.g. 'Warsaw West')."
|
||||
},
|
||||
"areas": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["name", "displayName"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"name": {
|
||||
"anyOf": [
|
||||
{ "type": "string", "pattern": "^[a-z0-9-]{1,32}$" },
|
||||
{ "const": "_default" }
|
||||
],
|
||||
"description": "UNS level 3 segment. `_default` is the reserved placeholder for sites where Area doesn't apply."
|
||||
},
|
||||
"displayName": { "type": "string", "minLength": 1, "maxLength": 128 },
|
||||
"lines": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["name", "displayName"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"name": {
|
||||
"anyOf": [
|
||||
{ "type": "string", "pattern": "^[a-z0-9-]{1,32}$" },
|
||||
{ "const": "_default" }
|
||||
],
|
||||
"description": "UNS level 4 segment."
|
||||
},
|
||||
"displayName": { "type": "string", "minLength": 1, "maxLength": 128 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user