English · 简体中文
A .NET 8 OPC data-collection client: browse the OPC address space, configure collection tasks, subscribe to OPC UA / DA / AE data in real time, and publish over TCP to a downstream broker.
Core principles: generic, decoupled, extensible — no assumptions about the broker protocol, no lock-in to a specific OPC SDK.
One collection engine, two forms: a Windows desktop app (Dc.App, WPF GUI, all protocols UA/DA/AE) and a headless server (Dc.Cli, pure .NET, deployable on Linux/Docker, UA collection + publish).
See the latest Releases:
| Form | Artifact | Usage | Protocols |
|---|---|---|---|
| Windows desktop GUI | Dc-v<ver>-win-x64.zip |
unzip, run Dc.App.exe |
UA / DA / AE |
| Linux headless collector | Dc.Cli-v<ver>-linux-x64.tar.gz (also -arm64) |
unzip, run ./Dc.Cli |
UA only |
| Docker image | ghcr.io/devminions/dc-cli:<ver> (multi-arch amd64/arm64) |
see below | UA only |
All are self-contained — the target machine needs no preinstalled .NET runtime. The headless collector reads configured tasks from sqlite.db (which can be configured in the GUI and shared; the schema is identical).
# Run the headless collector in Docker: put a sqlite.db with tasks in host ./data
docker run -d --name dc-collector -v "$PWD/data:/data" ghcr.io/devminions/dc-cli:latest
docker logs -f dc-collectorThe Windows zip/installer is not code-signed; on first run SmartScreen warns "unknown publisher" → click "More info → Run anyway".
Fluent Design · light/dark theme follows the system · live screenshots from the Windows app.
| Collection tasks (master-detail + 5 tabs) | Browse nodes |
|---|---|
![]() |
![]() |
| Live data | Diagnostics |
![]() |
![]() |
| Logs | Settings |
![]() |
![]() |
- OPC UA — subscribe, browse the address space, read values while browsing (batched read), certificate trust chain, KeepAlive auto-reconnect, per-task secure/None endpoint toggle. (OPC Foundation .NET Standard SDK.)
- OPC DA — subscribe, browse, OPCEnum server scan / IP discovery, active liveness probing (
GetServerStatus) for prompt disconnect detection. (Technosoftware DaAeHdaClient, COM/DCOM, Windows-only.) - OPC AE — alarms & events subscribe, Area/Source browse. (same SDK.)
- Tri-state quality — Good / Uncertain / Bad, parsed by
0xC0/0x40/0x00bit masking, color-coded in the UI. - Engineering-unit scaling — per real tag: scale factor + offset, applied to the raw value before publish (raw → engineering units).
- Virtual (computed) tags — a tag defined as a formula over other tags in the same task (DynamicExpresso engine, e.g.
T * 1.8 + 32; built-insSQRT/ABS/SIN/COS/IF/MIN/MAX/AVG/SUM/...): evaluated in the collection pipeline with input mapping, readiness gating and quality propagation, then published like a real tag.
- Master-detail workspace — task list with status filter (all / running / stopped) + live search; detail has 5 tabs (overview / tags / live data / diagnostics / config).
- Task CRUD — create/edit/delete tasks (UA/DA/AE; server/node/CLSID, sampling interval, deadband, downstream TCP target, per-task security), with a user-readable name.
- Tag CRUD — new/edit/delete real or virtual tags, with a referential-integrity guard (can't delete a tag a formula references; deleting a virtual tag cascade-removes its formula). Hot-applied to running tasks where safe.
- Discover → configure — browse, multi-select nodes, bulk "Add as Tags" to a task (data type auto-mapped), then jump straight to the task; empty states guide you to the browse page.
- Excel import/export — ClosedXML; import by Item + DataType into the current task (export also writes a TaskId column).
TaskOrchestrator— start/stop/restart, hot add/remove tags without a restart, heartbeat monitoring, watchdog auto-restart on timeout, connection-state tracking.- TCP publishing — batched sender, MessagePack / JSON switchable, wire format v1.1 (magic + format-id), reconnect cooldown + send timeout, optional bounded offline queue (drop-oldest on overflow).
- Decoupled & generic —
PublishAsync<T>is message-type agnostic; the broker protocol is the deployer's choice, no OPC SDK leaks past the abstraction layer.
- Dashboard — health score, running/stopped/alert counts, total throughput, per-task status at a glance.
- Live data — cross-task value stream, tri-state quality coloring, task filter + search + pause/clear, high-rate coalescing (stays smooth at thousands of updates/s).
- Diagnostics — per-task rate / publish-errors / restarts / heartbeat-age / queue-backlog / dropped-frames + sparkline, configurable refresh; distinguishes "no downstream consumer" from real send errors.
- Metrics & probes —
System.Diagnostics.Metrics+GET /metrics(Prometheusdc_collector_*),GET /healthz&/readyz; plus periodic structured diagnostic logs (incl. queue-overflow drop edge alerts). - Logs — Serilog rolling file + an in-app log viewer.
- Two forms, one engine — Windows desktop (UA/DA/AE) and headless
Dc.Cli(Linux/Docker, UA); both self-contained (no .NET runtime needed on the target). - Config backup/restore — export/import all tasks · tags · config as a JSON bundle (merge or replace), plus
appsettings.jsonexternalization. - Bilingual UI — full English / 简体中文 interface, switchable at runtime (Settings → Language) with no restart, or follow the OS language; the choice persists to
appsettings.json. (Only the UI culture switches — number/date formats and OPC value parsing stay stable.) - Desktop niceties — Fluent UI with light/dark/system theme, themed dialogs, live input validation, system tray + single-instance lock.
Persistence is EF Core + SQLite (tables: tasks / tags / formulas / system config). See the roadmap in ROADMAP.md.
Clean Architecture with one-way dependencies (UI → Infrastructure → Abstractions/Domain):
src/
├── Dc.Domain/ # Entities (no external dependencies)
├── Dc.Opc.Abstractions/ # IOpcSubscriber / IOpcBrowser / TagValue / OpcProtocol
├── Dc.Opc.Ua/ # UA subscriber + browser (OPC Foundation)
├── Dc.Opc.Da/ # DA subscriber + browser (Technosoftware)
├── Dc.Opc.Ae/ # AE subscriber + browser (Technosoftware)
├── Dc.Infrastructure/ # EF Core + serialization + TCP publishing + orchestration + observability
├── Dc.App/ # WPF app (MVVM + DI, Windows)
└── Dc.Cli/ # Headless collector (console, Linux/Docker, UA only)
tests/ # xUnit unit + integration tests (incl. in-process UA server end-to-end)
tools/Dc.WireDump/ # Receiver-side debug tool (parses wire frames)
- Use DbContext directly, no Repository wrapper — EF Core is already a Repository + Unit of Work.
- Serializer separated from Publisher + generic —
PublishAsync<T>(T)is message-type agnostic; the broker-side protocol is the deployer's choice. TaskOrchestratorsingle-object API — one object owns the whole task lifecycle + hot updates + watchdog.IOpcSubscriberexposes onlyChannelReader<T>— outside code pulls data read-only; subscriber state is not externally writable.- Quality codes parsed with
0xC0/0x40/0x00bit masking (notquality > 0). - DB columns use the
dc_prefix + snake_case (EFCore.NamingConventions).
Requires the .NET 8 SDK. The Technosoftware DA/AE/HDA client sources that OPC DA/AE depend on are vendored under
vendor/ClassicClient/ (GPL-3.0) — no extra fetch needed:
git clone https://github.com/DevMinions/wpf-opc-client.git
cd wpf-opc-client
# Build per project (global Platform=x64 propagates to all P2P refs, incl. vendor COM)
dotnet build src/Dc.App/Dc.App.csproj -p:Platform=x64 -p:CustomTestTarget=net8.0-windows
dotnet test tests/Dc.Infrastructure.Tests -p:Platform=x64 -p:CustomTestTarget=net8.0-windowsOn Windows you can use the script:
.\build.ps1 # build
.\build.ps1 -Target test # run tests
.\build.ps1 -Target run # launch the appWhy the two -p parameters:
Platform=x64— the vendorDaAeHdaClient.Com'sNETCOREmacro is only defined on x86/x64; otherwise compilation fails withFILETIMEnot found.CustomTestTarget=net8.0-windows— makes vendor build a single net8 TFM (default builds 5 TFMs incl. net9/net10), which would hit NETSDK1045 without the matching SDK.
Linux/WSL/macOS can build the non-GUI projects and run cross-platform tests, but the WPF app and OPC DA/AE (COM) only run on Windows.
dotnet run --project src/Dc.AppOn first run it creates, in the working directory: sqlite.db (empty), logs/dc-yyyyMMdd.log, and pki/ (OPC UA self-signed certificates).
- Collection tasks → New: pick protocol UA, set the server to
opc.tcp://host:4840, and set the TCP address to the downstream broker'shost:port. - Browse nodes → enter the server URI → Connect → tick multiple nodes in the tree → "Add as Tags" to the target task (or + New task). (Or: in the task's Tags tab, click New and enter the Item by hand.)
- Select the task → Start → watch values stream in under Live data.
Tags attach directly to a task — there is no group layer to set up.
- Virtual tag (formula) — in the Tag editor, tick Virtual tag (formula) and enter an expression over other tags in the same task (e.g.
T * 1.8 + 32), mapping each variable to a source tag. It is computed in the collection pipeline and published just like a real tag (built-ins includeSQRT/ABS/SIN/COS/IF/MIN/MAX/AVG/SUM/...). The result's quality is propagated from its inputs. - Scaling — for a real tag, set a scale factor and offset to convert the raw value to engineering units before publishing.
You need an OPC DA/AE server as the data source (pick one):
- KEPServerEX Demo (built-in Simulator channel, ProgID
Kepware.KEPServerEX.V6, 2-hour restart limit) - Matrikon OPC Simulation Server (free)
- Technosoftware Demo Server (shipped with their DA/AE/HDA client suite; not vendored here to keep the repo small)
Flow: Collection tasks → protocol DA, set node to host/IP (localhost for the local machine), set ProgID to the server's ProgID. On the Browse page, choosing DA shows a "Scan OPC servers" bar that auto-fills what it finds.
Cross-machine DA depends on OPCEnum + DCOM permissions and is fiddly to configure; get the flow working with a local demo server first.
Dc.Cli has no WPF dependency; it loads configured tasks from the same sqlite.db and runs UA collection + TCP publishing — ideal for a server/edge daemon. DA/AE go through COM and require Windows, so they are not in the headless build.
# tarball (bundled runtime, no .NET install needed)
tar xzf Dc.Cli-v<ver>-linux-x64.tar.gz && ./Dc.Cli
# or Docker (put a sqlite.db with tasks in the host ./data volume; Database__Path already points at /data; 9090 is the diagnostics port)
docker run -d --name dc-collector -v "$PWD/data:/data" -p 9090:9090 ghcr.io/devminions/dc-cli:latestThe task DB sqlite.db can be configured in the Windows GUI and copied/shared to the headless side (identical schema). Logs go to stdout (docker logs), and Ctrl+C / docker stop shuts down gracefully. From source: dotnet run --project src/Dc.Cli.
Diagnostics endpoints (listens on :9090 by default, configurable/disableable via Diagnostics:Http):
| Path | Purpose |
|---|---|
GET /healthz, /readyz |
liveness/readiness probes (usable directly by Docker HEALTHCHECK and k8s) |
GET /metrics |
Prometheus text, exports dc_collector_* (running tasks, per-task values/publish-errors/restarts/subscribed-tags/heartbeat-age/queue-backlog-bytes/cumulative-dropped-frames) |
The image has a built-in HEALTHCHECK (calls Dc.Cli --healthcheck to probe /healthz, so no curl needed in the image).
⚠️ /metricshas no auth and binds all interfaces by default (http://+:9090/); expose it only to a trusted scraping network (Prometheus / k8s) and don't map 9090 to the public internet. Disable viaDiagnostics:Http:Enabled=falseif unused. The image runs as non-root (uid 10001): when bind-mounting a host dir with-v, that dir must be writable by uid 10001 (chown -R 10001 ./data, or adjust withdocker run --user).
appsettings.json in the same directory can be externalized (no recompile):
{
"Database": { "Path": "sqlite.db" },
"Theme": "System",
"Language": "System",
"Messaging": { "Format": "msgpack" },
"Orchestrator": { "WatchdogIntervalSeconds": 30, "HeartbeatTimeoutSeconds": 120 },
"Diagnostics": {
"ReportIntervalSeconds": 30, "EnableLogging": true, "EnableMetrics": true,
"Http": { "Enabled": true, "Prefix": "http://+:9090/" }
},
"OpcUa": { "AutoAcceptUntrustedCertificates": false, "MinimumCertificateKeySize": 2048 }
}Requires Inno Setup 6 (default path, or set env var INNO_SETUP_DIR).
.\build.ps1 -Target installer # produces build/installer/Dc-Setup-x64-<version>.exe
.\build.ps1 -Target installer -Version 1.2.3Produces a self-contained installer (the user's machine needs no preinstalled .NET runtime) with the published output + scripts + docs + LICENSE. The installer detects OPCEnum and prompts to install the OPC Foundation Core Components if missing.
Not included: code signing (an EV cert is recommended for production to avoid SmartScreen), MSI format, auto-update.
Pushing a v* tag triggers release.yml, which builds and attaches everything to one GitHub Release:
- Windows —
Dc.Appself-contained zip - Linux —
Dc.Cliself-contained single-file tarball (x64 / arm64) - Docker — multi-arch image (amd64 / arm64) pushed to GHCR
ghcr.io/<owner>/dc-cli(<ver>+latest)
git tag v1.2.3 && git push origin v1.2.3The GHCR image is private on first push; set the
dc-clipackage to public in GitHub Packages settings to allow anonymousdocker pull.
Default security baseline: AutoAcceptUntrustedCertificates = false, MinimumCertificateKeySize = 2048.
The first connection to an unknown UA server is rejected and its certificate written to pki/rejected/certs/. After manual review, move it to pki/trusted/certs/ and restart to connect.
| Directory | Contents |
|---|---|
pki/own/ |
the client's own certificate (auto-generated) |
pki/trusted/ |
trusted server certificates (placed in manually) |
pki/issuer/ |
trusted CA issuer certificates |
pki/rejected/ |
rejected certificates (for review) |
For dev you can set "AutoAcceptUntrustedCertificates": true to skip this (not recommended for production).
Each UA task also has a per-task "Use secure connection" toggle: on selects the strongest endpoint (requires mutual certificate trust), off connects to the None endpoint directly (no certs — handy for a simulator/dev server). It defaults to on (production-safe).
- Remote DA discovery/connection is constrained by DCOM config — cross-machine DA hard-depends on OPCEnum + DCOM permissions.
- Single-connection TCP publisher — with a 2s reconnect cooldown + send timeout; the offline queue is optional (off by default).
- MessagePack carries no type info — the receiver must know the message type; see
docs/wire-format.md. - Installer is not code-signed — SmartScreen will block the first run in strict environments.
Issues and PRs welcome. Before submitting, make sure dotnet test tests/Dc.Infrastructure.Tests passes. See CONTRIBUTING.md.
GPL-3.0 (see LICENSE).
This project's dependencies OPCFoundation.NetStandard.Opc.Ua.Client and Technosoftware.DaAeHdaClient are dual-licensed (GPL / commercial); non-purchasers default to GPL, so this project is distributed as a whole under GPL-3.0 — anyone may freely use, modify, and redistribute it, but derivative works must likewise be open-sourced (GPL). For closed-source commercial use, you must separately purchase commercial licenses from OPC Foundation and Technosoftware.
The full third-party license list is in THIRD_PARTY_NOTICES.md.






