f3cd685da5
Closes the §7.2 priority-3 item. Sql was a shipped, merged driver with NO
user-facing documentation at all -- only design and plan files, which describe
what was intended rather than what an operator needs to author one.
Sql.md covers: SQL Server as the only constructed provider (the other four
SqlProvider names are reserved, and authoring one fails rather than falling
back), the KeyValue / WideRow tag models with worked TagConfig, Query being
rejected end-to-end, the config table with real defaults read from
SqlDriverOptions, connectionStringRef never carrying credentials into an
artifact, per-source query grouping and its case-sensitive group keys, and the
value-bound-vs-identifier-validated split that makes it injection-safe.
Calculation.md covers: what a pseudo-driver is and why it occupies a driver slot,
a Calculation-vs-VirtualTags decision table (the two are easy to confuse), the
literal-only ctx.GetTag dependency extraction and the runtime-built-path trap,
the two deploy gates, the all-deps-arrived publish gate, and the Good->Bad-once
error semantics.
Two stale claims found by checking against source rather than inheriting them:
- The driver README's Calculation row still carried G-1 ("no typed config form,
RunTimeout unauthorable"), fixed on 2026-07-27 by CalculationDriverForm.
- docs/Raw.md's "one auto-created default Engine device" NEVER SHIPPED. I had
copied it into the new doc before checking; "Engine" has zero occurrences in
src/ and nothing special-cases Calculation at device creation. Corrected in
both files. The config key was also wrong in my first draft: runTimeoutMs, an
int in milliseconds, not a TimeSpan string -- and it is ignored on reinit
because the evaluator's timeout is constructor-fixed.
Full solution: 49 suites pass; the 2 failures are the known pre-existing
environment ones (AbLegacy.IntegrationTests docker fixture, Host.IntegrationTests).
OpcUaClient.IntegrationTests, which was silently failing, is now green.
Claude-Session: https://claude.ai/code/session_015p7wGqy3YpZNCpDzTpGMKo
94 lines
5.5 KiB
Markdown
94 lines
5.5 KiB
Markdown
# The Raw project tree (`/raw`)
|
|
|
|
v3 authors device I/O in a **Raw project tree** — a cluster-rooted, Kepware-style hierarchy
|
|
that is the single surface for driver/device/tag authoring. It is the peer of the `/uns`
|
|
unified-namespace page; the two OPC UA namespaces (`ns=Raw` / `ns=UNS`) light up in Batch 4.
|
|
|
|
```
|
|
Enterprise → Cluster → Folder(s) → Driver → Device → TagGroup(s) → Tag
|
|
```
|
|
|
|
Every node's identity is its **RawPath** (`<Folder/…>/<Driver>/<Device>/<TagGroup/…>/<Tag>`,
|
|
cluster-scoped, `/`-separated, ordinal). Names are validated as RawPath segments (no `/`, no
|
|
leading/trailing whitespace) at authoring and at the deploy gate.
|
|
|
|
## The tree
|
|
|
|
`/raw` (`GlobalRaw.razor` → `RawTree.razor`, backed by `IRawTreeService`) lazily loads each
|
|
container level on expand (unlike `/uns`, which eager-loads). Each node has a right-click
|
|
context menu **and** a `⋯` fallback (the reusable `ContextMenu` component):
|
|
|
|
| Node | Actions |
|
|
|---|---|
|
|
| Cluster | New folder · New driver |
|
|
| Folder | New folder · New driver · Rename · Delete |
|
|
| Driver | Configure · New device · Enable/Disable · Rename · Delete |
|
|
| Device | Configure (endpoint + **Test connect**) · New tag-group · Add tags ▸ · Delete |
|
|
| TagGroup | New group · Add tags ▸ · Rename · Delete |
|
|
| Tag | Edit · Delete |
|
|
|
|
Deletes are blocked while a node has children (or, for a raw tag, while a `UnsTagReference`
|
|
points at it — the error names the referencing equipment). Renames return non-blocking
|
|
**warnings**: renaming a node changes the RawPath of every historized / UNS-referenced tag
|
|
beneath it (its historian tagname / UNS projection path moves).
|
|
|
|
## Endpoint → DeviceConfig (the channel/device split)
|
|
|
|
v3 moves the **connection endpoint** off the driver and onto the **Device** (`DeviceConfig`),
|
|
mirroring Kepware's channel/device split. The driver's `DriverConfig` holds protocol/channel
|
|
settings; the device's `DeviceConfig` holds host/port/endpoint. At deploy/probe time
|
|
`DriverDeviceConfigMerger` merges the device's endpoint up into the driver config, and Test
|
|
connect (inside the **Device** modal) probes that merged config. Driver forms no longer
|
|
serialize the endpoint keys, so `DeviceConfig` is the single source of truth.
|
|
|
|
## Authoring tags
|
|
|
|
**Add tags ▸** on a Device or TagGroup offers:
|
|
|
|
- **Manual entry** — a grid of Name / DataType / AccessLevel / WriteIdempotent / PollGroup +
|
|
the driver-typed `TagConfig` editor per row.
|
|
- **Import/Export CSV** — a staged flow (upload → parse → **review grid** with per-row
|
|
verdicts → commit) over an RFC-4180 parser. Columns: the common set
|
|
(`Name, TagGroupPath, DataType, AccessLevel, WriteIdempotent, PollGroup, IsHistorized,
|
|
HistorianTagname, IsArray, ArrayLength, Alarm.*, TagConfigJson`) plus the per-driver typed
|
|
columns (e.g. Modbus `Region, Address, ModbusDataType, ByteOrder, …`). `TagGroupPath`
|
|
auto-creates nested groups; typed columns win over `TagConfigJson` on conflict (flagged in
|
|
the review grid). **Import is all-or-nothing** — any invalid row blocks the whole commit.
|
|
Export round-trips the same shape.
|
|
- **Browse device…** — enabled per the two-tier resolution (bespoke `IDriverBrowser` →
|
|
universal `DiscoveryDriverBrowser` when the driver's `ITagDiscovery.SupportsOnlineDiscovery`
|
|
is true → grayed out otherwise). Multi-selected leaves become raw `Tag` rows under the
|
|
target, with the browsed reference written into the driver-typed `TagConfig` **address
|
|
field** (`nodeId` / `tagPath` / …), never an identity key. An opt-in "create matching
|
|
tag-groups" toggle mirrors the browse folder nesting. The browser dials a real ephemeral
|
|
driver against the **merged** Driver+Device config.
|
|
|
|
## The `Calculation` driver
|
|
|
|
Signal-level calculated tags are ordinary raw tags bound to a `Calculation` driver
|
|
(`DriverTypeNames.Calculation`), computed by C# scripts that read other tags' live values via
|
|
`ctx.GetTag("<RawPath>")`. See `docs/plans/2026-07-15-calculation-driver-mini-design.md` for
|
|
the full design. Highlights:
|
|
|
|
- ⚠️ **Correction (2026-07-28): the "auto-created default `Engine` device" below never shipped.**
|
|
`"Engine"` has zero occurrences in `src/`, and nothing special-cases `Calculation` at device
|
|
creation — author the device like any other. Per-tag `TagConfig` is
|
|
`{ "scriptId": "…", "changeTriggered": true, "timerIntervalMs": 5000 }`.
|
|
- The host feeds dependency values via the new `IDependencyConsumer` capability + a
|
|
`DependencyConsumerMuxAdapter` on the per-node dependency mux; calc-of-calc chains work
|
|
because a calc output re-enters the mux. The driver rebuilds its tag/dependency table on
|
|
every redeploy (`ReinitializeAsync`) and re-registers its refs after the delta applies.
|
|
- **Deploy gates** (`DraftValidator`): `scriptId` existence, and a hard **cycle gate**
|
|
(`DependencyGraph.DetectCycles` over calc→calc edges; cross-driver refs are terminal) that
|
|
rejects an A→B→A oscillation loop naming the members. Compile is not hard-gated (VirtualTag
|
|
parity); a runtime compile/throw/timeout lands as Bad quality + a `script-logs` entry.
|
|
|
|
## Retirement note
|
|
|
|
The routed `/clusters/{id}/drivers` driver-authoring flow (`DriverTypePicker`,
|
|
`DriverEditRouter`, the 8 per-type `*DriverPage` shells, and the per-cluster Drivers list) is
|
|
**retired** — authoring lives in `/raw`. The extracted driver/device **form bodies** live on
|
|
inside the `/raw` modals. The `DriverType` dispatch maps (`TagConfigEditorMap`,
|
|
`TagConfigValidator`, `EquipmentTagConfigInspector`) are keyed off the single-source-of-truth
|
|
`DriverTypeNames` constants (fixing the historical `TwinCat`/`Focas` drift).
|