37cb3b0df8
All five client CLIs now share one credential contract for `authenticate-user`: flags `--password` / `--password-env` (Go: `-password` / `-password-env`) with default env `MXGATEWAY_VERIFY_PASSWORD`, resolution flag-then-env, and a resolved credential that is missing *or empty* is a usage error naming the flag and the variable. The value is never echoed and never reaches the wire. Go and Java previously sent an empty credential when the variable was unset, turning a misconfigured environment into a real MXAccess authentication attempt. Go now returns the guard error before dialing; Java throws a picocli ParameterException instead of falling back to "". Python's `--password-env` gained the canonical default and its UsageError names the resolved variable. Rust treats an empty flag or env value as missing, with the resolution extracted into a testable `resolve_verify_user_password`. .NET adopts the canonical flags and keeps `--verify-user-password`, `--verify-user-password-env`, and MXGATEWAY_VERIFY_USER_PASSWORD as deprecated aliases for one release. Docs same commit: CrossLanguageSmokeMatrix.md gains the credential contract and the per-CLI subcommand-coverage table (the documented-not-fixed half of the finding); all five READMEs name the canonical variable and the fail-fast rule, and the .NET README carries the deprecation note. Tracking flipped to Done in both remediation registers with a change-log row. No .proto changed; no generated code regenerated.
494 lines
21 KiB
Markdown
494 lines
21 KiB
Markdown
# .NET Client Projects
|
|
|
|
The .NET client workspace contains the MXAccess Gateway client library, test
|
|
CLI, and unit tests.
|
|
|
|
## Projects
|
|
|
|
| Project | Purpose |
|
|
|---------|---------|
|
|
| `ZB.MOM.WW.MxGateway.Client` | .NET 10 library entry point, raw gRPC calls, and session helpers. |
|
|
| `ZB.MOM.WW.MxGateway.Client.Cli` | Test CLI for smoke and diagnostic commands. |
|
|
| `ZB.MOM.WW.MxGateway.Client.Tests` | Unit tests for client options, generated contract wiring, auth metadata, session helpers, cancellation, and event streaming. |
|
|
|
|
The projects reference `src/ZB.MOM.WW.MxGateway.Contracts/ZB.MOM.WW.MxGateway.Contracts.csproj` so
|
|
the client compiles against the same generated protobuf and gRPC types as the
|
|
gateway. `clients/dotnet/generated` remains reserved for generator output if a
|
|
future client build switches to client-local `Grpc.Tools` generation.
|
|
|
|
## Build And Test
|
|
|
|
```powershell
|
|
dotnet build clients/dotnet/ZB.MOM.WW.MxGateway.Client.slnx
|
|
dotnet test clients/dotnet/ZB.MOM.WW.MxGateway.Client.slnx --no-build
|
|
```
|
|
|
|
## Packaging
|
|
|
|
Create local library and CLI artifacts from the repository root:
|
|
|
|
```powershell
|
|
$dotnetPackageOutput = Join-Path (Get-Location) 'artifacts/clients/dotnet'
|
|
dotnet pack clients/dotnet/ZB.MOM.WW.MxGateway.Client/ZB.MOM.WW.MxGateway.Client.csproj -c Release -p:PackageOutputPath="$dotnetPackageOutput"
|
|
dotnet publish clients/dotnet/ZB.MOM.WW.MxGateway.Client.Cli/ZB.MOM.WW.MxGateway.Client.Cli.csproj -c Release -o artifacts/clients/dotnet/mxgw-dotnet
|
|
```
|
|
|
|
The library package references the shared contracts project at build time. The
|
|
published CLI runs from `artifacts/clients/dotnet/mxgw-dotnet`.
|
|
|
|
## Regenerating Protobuf Bindings
|
|
|
|
The .NET client uses the generated C# types from
|
|
`src/ZB.MOM.WW.MxGateway.Contracts/Generated`. Regenerate those files through the
|
|
contracts project:
|
|
|
|
```powershell
|
|
dotnet build src/ZB.MOM.WW.MxGateway.Contracts/ZB.MOM.WW.MxGateway.Contracts.csproj
|
|
```
|
|
|
|
## Client Usage
|
|
|
|
`MxGatewayClient` opens a gRPC channel to the gateway and attaches the API key
|
|
to every unary and streaming call as `authorization: Bearer <api-key>`.
|
|
Cancellation tokens passed to the public methods flow to the generated gRPC
|
|
call. Client-side cancellation stops waiting for the gateway response; it does
|
|
not abort an MXAccess COM call that is already executing inside a worker.
|
|
|
|
```csharp
|
|
await using MxGatewayClient client = MxGatewayClient.Create(
|
|
new MxGatewayClientOptions
|
|
{
|
|
Endpoint = new Uri("http://localhost:5000"),
|
|
ApiKey = apiKey,
|
|
});
|
|
|
|
MxGatewaySession session = await client.OpenSessionAsync();
|
|
try
|
|
{
|
|
int serverHandle = await session.RegisterAsync("sample-client");
|
|
int itemHandle = await session.AddItemAsync(
|
|
serverHandle,
|
|
"Area001.Pump001.Speed");
|
|
|
|
await session.AdviseAsync(serverHandle, itemHandle);
|
|
}
|
|
finally
|
|
{
|
|
await session.CloseAsync();
|
|
}
|
|
```
|
|
|
|
Use `OpenSessionRawAsync`, `CloseSessionRawAsync`, `InvokeAsync`, and
|
|
`StreamEventsAsync` when tests or parity tools need direct generated protobuf
|
|
messages. `MxGatewaySession.OpenSessionReply` keeps the raw session-open reply
|
|
available, and command helpers have `*RawAsync` variants when callers need the
|
|
complete `MxCommandReply`.
|
|
|
|
### Event Streaming And Replay Gaps
|
|
|
|
`StreamEventsAsync(afterWorkerSequence)` yields raw generated `MxEvent`
|
|
messages. Passing a non-zero `afterWorkerSequence` resumes a session's event
|
|
stream after a known worker sequence — this is the reconnect cursor. If that
|
|
cursor is *stale* — older than the oldest event the gateway still retains in the
|
|
session replay ring — the events in between were evicted and cannot be replayed.
|
|
The gateway signals this by emitting a single **replay-gap sentinel** at the head
|
|
of the resumed stream: an `MxEvent` with its `ReplayGap` field set, `Family`
|
|
unspecified, and no body. It means "you missed events — discard local state and
|
|
re-snapshot."
|
|
|
|
Rather than force callers to inspect the raw sentinel, the client exposes a
|
|
typed surface, `StreamEventItemsAsync`, which yields `MxEventStreamItem` values:
|
|
|
|
```csharp
|
|
await foreach (MxEventStreamItem item in session.StreamEventItemsAsync(
|
|
afterWorkerSequence: lastSeenSequence))
|
|
{
|
|
if (item.IsReplayGap)
|
|
{
|
|
// We missed events: throw away local state and re-snapshot.
|
|
ReplayGap gap = item.ReplayGap!;
|
|
// Resume without incurring another gap:
|
|
lastSeenSequence = gap.OldestAvailableSequence - 1;
|
|
await ReSnapshotAsync();
|
|
continue;
|
|
}
|
|
|
|
HandleEvent(item.Event); // normal MXAccess event, IsReplayGap == false
|
|
lastSeenSequence = item.Event.WorkerSequence;
|
|
}
|
|
```
|
|
|
|
The typed surface never synthesizes or drops events — it only makes the
|
|
gateway's own sentinel observable. Normal events pass through with
|
|
`IsReplayGap == false` and `ReplayGap == null`. The gap is only ever produced by
|
|
`StreamEvents`; the diagnostic drain path never emits it. If you already consume
|
|
the raw `StreamEventsAsync` (or the client-level stream), the
|
|
`AsStreamItemsAsync()` extension projects any `IAsyncEnumerable<MxEvent>` into
|
|
the same `MxEventStreamItem` surface.
|
|
|
|
For alarms, the client exposes `QueryActiveAlarmsAsync` (one-shot snapshot of
|
|
the active alarms the gateway's central monitor currently holds),
|
|
`StreamAlarmsAsync` (server-streaming feed of alarm-state-change messages
|
|
keyed by the same monitor), and `AcknowledgeAlarmAsync` (ack by alarm
|
|
reference, optional comment, ack target). All three accept a cancellation
|
|
token and pass through the `MxGateway:Alarms` configuration on the
|
|
server — when alarms are disabled, the gateway returns an empty list / empty
|
|
stream rather than failing.
|
|
|
|
`MxGatewaySession.CloseAsync` is explicit and idempotent. Repeated calls return
|
|
the first `CloseSessionReply` instead of sending another close request.
|
|
|
|
## Values, Status, And Errors
|
|
|
|
The client provides extension helpers for generated protobuf values. Use
|
|
`ToMxValue()` on .NET scalar values and typed arrays to create `MxValue`
|
|
instances for `Write` and `Write2`. Use `ToClrValue()` and
|
|
`GetProjectionKind()` when test or diagnostic code needs to inspect generated
|
|
`MxValue` replies while preserving `rawDiagnostic`, raw data type fields, and
|
|
raw byte payloads.
|
|
|
|
`MxStatusProxy.IsSuccess()` and `ToDiagnosticSummary()` expose MXAccess status
|
|
arrays without collapsing them into a single gateway success flag. Command
|
|
reply helpers follow the same split:
|
|
|
|
```csharp
|
|
reply.EnsureProtocolSuccess();
|
|
reply.EnsureMxAccessSuccess();
|
|
```
|
|
|
|
`EnsureProtocolSuccess()` raises gateway, session, worker, or command
|
|
exceptions for gateway-level failures. It leaves
|
|
`PROTOCOL_STATUS_CODE_MXACCESS_FAILURE` to `EnsureMxAccessSuccess()` so callers
|
|
can keep the full `MxCommandReply`, HRESULT, and status array when MXAccess
|
|
itself rejects a command. `MxAccessException.Reply` contains the raw generated
|
|
reply.
|
|
|
|
## Write Semantics And Common Pitfalls
|
|
|
|
These are MXAccess parity behaviors that surprise new callers. The gateway
|
|
forwards them unchanged — it does not paper over them.
|
|
|
|
### Attributing a write to a user without `AuthenticateUser`
|
|
|
|
MXAccess only stamps a plain `Write`/`Write2` with a Galaxy user id when the
|
|
item carries an active *supervisory* advise. If you are **not** using the
|
|
verified/secured path (`AuthenticateUser` → `WriteSecured`/`WriteSecured2`) but
|
|
still need the write attributed to a user id, you must first advise the item
|
|
supervisory and then pass that user id on the write. Without the supervisory
|
|
advise the `userId` on a plain write is ignored.
|
|
|
|
The library exposes `Advise`/`UnAdvise` as named helpers but not supervisory
|
|
advise, so send it through the generic command channel:
|
|
|
|
```csharp
|
|
await session.InvokeAsync(new MxCommandRequest
|
|
{
|
|
SessionId = session.SessionId,
|
|
Command = new MxCommand
|
|
{
|
|
Kind = MxCommandKind.AdviseSupervisory,
|
|
AdviseSupervisory = new AdviseSupervisoryCommand
|
|
{
|
|
ServerHandle = serverHandle,
|
|
ItemHandle = itemHandle,
|
|
},
|
|
},
|
|
});
|
|
|
|
await session.WriteAsync(serverHandle, itemHandle, value.ToMxValue(), userId);
|
|
```
|
|
|
|
The CLI exposes the same command as `advise-supervisory`, and `write` /
|
|
`write2` take `--user-id`.
|
|
|
|
### Array writes replace the whole array
|
|
|
|
A write to an array attribute **replaces the entire array**; it is not an
|
|
element-wise patch. To change a subset of elements, send the full array with
|
|
the unchanged elements included. For example, to change 2 elements of a
|
|
20-element array, build the `MxValue` from all 20 values (the 18 unchanged plus
|
|
the 2 new ones). Sending only the 2 changed values overwrites the attribute
|
|
with a 2-element array.
|
|
|
|
When only a few indices need changing and the rest should be reset to the
|
|
element type's default, use `WriteArrayElementsAsync` instead of building the
|
|
full array manually:
|
|
|
|
```csharp
|
|
await session.WriteArrayElementsAsync(
|
|
serverHandle, itemHandle,
|
|
elementDataType: MxDataType.Integer,
|
|
totalLength: 20,
|
|
elements: new Dictionary<uint, MxValue>
|
|
{
|
|
[2] = 42.ToMxValue(),
|
|
[7] = 99.ToMxValue(),
|
|
});
|
|
```
|
|
|
|
The gateway expands the sparse descriptor into a full `totalLength`-element
|
|
array before forwarding to the worker. Indices not listed in `elements` are
|
|
written as the element type's default — this is a **reset**, not a preserve;
|
|
current values at those positions are discarded. `totalLength` is required and
|
|
must match the declared length of the array attribute. Bare-name array items
|
|
(`Area001.Pump001.Speed`) are auto-normalized to the `[]` form across the whole
|
|
add family — `AddItem`, `AddItem2`, `AddItemBulk`, and `AddBufferedItem` — so the
|
|
array attribute accepts the write.
|
|
|
|
## CLI Usage
|
|
|
|
The test CLI supports deterministic JSON output for automation:
|
|
|
|
```powershell
|
|
dotnet run --project clients/dotnet/ZB.MOM.WW.MxGateway.Client.Cli -- version --json
|
|
dotnet run --project clients/dotnet/ZB.MOM.WW.MxGateway.Client.Cli -- open-session --endpoint http://localhost:5000 --api-key-env MXGATEWAY_API_KEY --json
|
|
dotnet run --project clients/dotnet/ZB.MOM.WW.MxGateway.Client.Cli -- register --session-id <id> --client-name mxgw-dotnet-cli --json
|
|
dotnet run --project clients/dotnet/ZB.MOM.WW.MxGateway.Client.Cli -- add-item --session-id <id> --server-handle 1 --item Area001.Pump001.Speed --json
|
|
dotnet run --project clients/dotnet/ZB.MOM.WW.MxGateway.Client.Cli -- advise --session-id <id> --server-handle 1 --item-handle 1 --json
|
|
dotnet run --project clients/dotnet/ZB.MOM.WW.MxGateway.Client.Cli -- write --session-id <id> --server-handle 1 --item-handle 1 --type int32 --value 123 --json
|
|
dotnet run --project clients/dotnet/ZB.MOM.WW.MxGateway.Client.Cli -- write2 --session-id <id> --server-handle 1 --item-handle 1 --type int32 --value 123 --timestamp 2026-01-01T00:00:00Z --json
|
|
dotnet run --project clients/dotnet/ZB.MOM.WW.MxGateway.Client.Cli -- stream-events --session-id <id> --max-events 1 --json
|
|
dotnet run --project clients/dotnet/ZB.MOM.WW.MxGateway.Client.Cli -- stream-alarms --filter-prefix Area001 --max-events 1 --json
|
|
dotnet run --project clients/dotnet/ZB.MOM.WW.MxGateway.Client.Cli -- acknowledge-alarm --reference "\\Galaxy\Area001.Pump001.PumpFault" --comment "ack from cli" --operator operator1 --json
|
|
dotnet run --project clients/dotnet/ZB.MOM.WW.MxGateway.Client.Cli -- smoke --endpoint http://localhost:5000 --api-key-env MXGATEWAY_API_KEY --item Area001.Pump001.Speed --json
|
|
```
|
|
|
|
`smoke` opens a session, registers a client, adds one item, advises it,
|
|
optionally writes a value when `--type` and `--value` are supplied, reads a
|
|
bounded event stream, and closes the session in a `finally` block. CLI error
|
|
output redacts API keys supplied through `--api-key`.
|
|
|
|
### `authenticate-user` credentials
|
|
|
|
```powershell
|
|
$env:MXGATEWAY_VERIFY_PASSWORD = "<verify-user password>"
|
|
dotnet run --project clients/dotnet/ZB.MOM.WW.MxGateway.Client.Cli -- authenticate-user --session-id <id> --server-handle 1 --verify-user operator --json
|
|
```
|
|
|
|
The credential comes from `--password` or, preferably, the environment variable
|
|
named by `--password-env` (default `MXGATEWAY_VERIFY_PASSWORD`) so it stays out
|
|
of shell history and the process table. It is never echoed to stdout or stderr,
|
|
and error output routes it through the same redaction seam as the API key. A
|
|
missing or empty resolved credential is a usage error naming the option and the
|
|
variable: the CLI fails before the invoke rather than authenticating with an
|
|
empty password.
|
|
|
|
`MXGATEWAY_VERIFY_PASSWORD` is the canonical variable across all five client CLIs
|
|
— see [Cross-Language Smoke Matrix](../../docs/CrossLanguageSmokeMatrix.md).
|
|
|
|
**Deprecated names.** This CLI previously used `--verify-user-password`,
|
|
`--verify-user-password-env`, and `MXGATEWAY_VERIFY_USER_PASSWORD`. All three
|
|
still resolve, for one release only, so existing scripts keep working; migrate to
|
|
the canonical names above. The full resolution order is `--password`,
|
|
`--verify-user-password`, the variable named by `--password-env` (or the
|
|
deprecated `--verify-user-password-env`, default `MXGATEWAY_VERIFY_PASSWORD`),
|
|
then `MXGATEWAY_VERIFY_USER_PASSWORD`.
|
|
|
|
## Galaxy Repository Browse
|
|
|
|
`GalaxyRepositoryClient` is a separate read-only wrapper around the
|
|
`GalaxyRepository` gRPC service exposed by the same gateway. It shares the API
|
|
key auth interceptor with `MxGatewayClient` and requires the `metadata:read`
|
|
scope server-side. Use it to probe the ZB SQL connection, watch
|
|
`time_of_last_deploy` for redeployments, and enumerate the deployed Galaxy
|
|
object hierarchy plus each object's dynamic attributes.
|
|
|
|
```csharp
|
|
await using GalaxyRepositoryClient repository = GalaxyRepositoryClient.Create(
|
|
new MxGatewayClientOptions
|
|
{
|
|
Endpoint = new Uri("http://localhost:5000"),
|
|
ApiKey = apiKey,
|
|
});
|
|
|
|
bool ok = await repository.TestConnectionAsync();
|
|
DateTime? lastDeploy = await repository.GetLastDeployTimeAsync();
|
|
|
|
IReadOnlyList<GalaxyObject> objects = await repository.DiscoverHierarchyAsync();
|
|
foreach (GalaxyObject galaxyObject in objects)
|
|
{
|
|
Console.WriteLine($"{galaxyObject.TagName} ({galaxyObject.ContainedName})");
|
|
foreach (GalaxyAttribute attribute in galaxyObject.Attributes)
|
|
{
|
|
Console.WriteLine($" {attribute.AttributeName} -> {attribute.FullTagReference}");
|
|
}
|
|
}
|
|
```
|
|
|
|
Use `DiscoverHierarchyOptions` to request a server-side slice without pulling
|
|
the full Galaxy:
|
|
|
|
```csharp
|
|
IReadOnlyList<GalaxyObject> pumps = await repository.DiscoverHierarchyAsync(
|
|
new DiscoverHierarchyOptions
|
|
{
|
|
RootContainedPath = "Area1/Line3",
|
|
TagNameGlob = "Pump_*",
|
|
IncludeAttributes = false,
|
|
});
|
|
```
|
|
|
|
The CLI exposes the same operations:
|
|
|
|
```powershell
|
|
dotnet run --project clients/dotnet/ZB.MOM.WW.MxGateway.Client.Cli -- galaxy-test-connection --endpoint http://localhost:5000 --api-key-env MXGATEWAY_API_KEY --json
|
|
dotnet run --project clients/dotnet/ZB.MOM.WW.MxGateway.Client.Cli -- galaxy-last-deploy --endpoint http://localhost:5000 --api-key-env MXGATEWAY_API_KEY --json
|
|
dotnet run --project clients/dotnet/ZB.MOM.WW.MxGateway.Client.Cli -- galaxy-discover --endpoint http://localhost:5000 --api-key-env MXGATEWAY_API_KEY
|
|
```
|
|
|
|
### Browsing lazily
|
|
|
|
For UI trees or OPC UA bridges, use `BrowseChildrenAsync` to walk one level at a
|
|
time instead of paging the full hierarchy. Pass an empty request for root objects;
|
|
subsequent calls supply `ParentGobjectId`, `ParentTagName`, or
|
|
`ParentContainedPath`. Each child's `ChildHasChildren[i]` tells you whether to
|
|
draw an expand triangle. Filter fields match `DiscoverHierarchy`. See
|
|
[Galaxy Repository](../../docs/GalaxyRepository.md#browsechildren) for full
|
|
request and filter semantics.
|
|
|
|
```csharp
|
|
BrowseChildrenReply roots = await repository.BrowseChildrenAsync(
|
|
new BrowseChildrenRequest());
|
|
|
|
for (int i = 0; i < roots.Children.Count; i++)
|
|
{
|
|
GalaxyObject child = roots.Children[i];
|
|
bool hasChildren = roots.ChildHasChildren[i];
|
|
Console.WriteLine($"{child.TagName} expand={hasChildren}");
|
|
}
|
|
```
|
|
|
|
#### High-level walker
|
|
|
|
For UI trees, the client provides a `LazyBrowseNode` walker that handles
|
|
sibling pagination and the `child_has_children` hint for you:
|
|
|
|
```csharp
|
|
await using GalaxyRepositoryClient repository = GalaxyRepositoryClient.Create(
|
|
new MxGatewayClientOptions { Endpoint = new Uri("http://localhost:5000"), ApiKey = apiKey });
|
|
IReadOnlyList<LazyBrowseNode> roots = await repository.BrowseAsync();
|
|
foreach (LazyBrowseNode root in roots)
|
|
{
|
|
if (root.HasChildrenHint)
|
|
{
|
|
await root.ExpandAsync();
|
|
}
|
|
foreach (LazyBrowseNode child in root.Children)
|
|
{
|
|
Console.WriteLine($"{child.Object.TagName} ({(child.HasChildrenHint ? "has children" : "leaf")})");
|
|
}
|
|
}
|
|
```
|
|
|
|
`ExpandAsync` is idempotent — calling it twice fires only one RPC,
|
|
and is safe under concurrent callers. To refresh after a Galaxy redeploy, call
|
|
`BrowseAsync` again from the root.
|
|
|
|
The CLI counterpart is `galaxy-browse`. Without `--parent` it walks the root
|
|
objects and eagerly expands `--depth` further levels into an indented tree; with
|
|
`--parent <gobject-id>` it fetches exactly one level of children for that object
|
|
(`--depth` is ignored there). Filter flags map onto `BrowseChildrenOptions`:
|
|
`--category-ids` and `--template-contains` are comma-separated lists,
|
|
`--tag-name-glob` / `--alarm-bearing-only` / `--historized-only` are scalar, and
|
|
`--include-attributes` overrides the server default for attribute population.
|
|
|
|
```powershell
|
|
dotnet run --project clients/dotnet/ZB.MOM.WW.MxGateway.Client.Cli -- galaxy-browse --endpoint http://localhost:5000 --api-key-env MXGATEWAY_API_KEY --depth 1
|
|
dotnet run --project clients/dotnet/ZB.MOM.WW.MxGateway.Client.Cli -- galaxy-browse --endpoint http://localhost:5000 --api-key-env MXGATEWAY_API_KEY --parent 42 --json
|
|
```
|
|
|
|
### Watching deploy events
|
|
|
|
`WatchDeployEventsAsync` opens the `WatchDeployEvents` server-streaming RPC. The
|
|
server emits a bootstrap event with the current state on subscribe, then one
|
|
event per new `time_of_last_deploy`. Pass a `lastSeenDeployTime` to suppress the
|
|
bootstrap when the caller already holds the current deploy time. Use the
|
|
monotonic `Sequence` field to detect dropped events: gaps mean the
|
|
per-subscriber server-side buffer overflowed and the caller should reconcile.
|
|
|
|
Streaming RPCs are not wrapped by the unary safe-read retry pipeline. The
|
|
caller is responsible for reopening the stream on transient failures.
|
|
|
|
```csharp
|
|
await using GalaxyRepositoryClient repository = GalaxyRepositoryClient.Create(options);
|
|
|
|
DateTimeOffset? lastSeen = null;
|
|
await foreach (DeployEvent evt in repository.WatchDeployEventsAsync(
|
|
lastSeen,
|
|
cancellationToken))
|
|
{
|
|
Console.WriteLine(
|
|
$"seq={evt.Sequence} objects={evt.ObjectCount} attributes={evt.AttributeCount}");
|
|
if (evt.TimeOfLastDeployPresent && evt.TimeOfLastDeploy is not null)
|
|
{
|
|
lastSeen = evt.TimeOfLastDeploy.ToDateTimeOffset();
|
|
}
|
|
}
|
|
```
|
|
|
|
The CLI counterpart streams events until Ctrl+C (or `--max-events`):
|
|
|
|
```powershell
|
|
dotnet run --project clients/dotnet/ZB.MOM.WW.MxGateway.Client.Cli -- galaxy-watch --endpoint http://localhost:5000 --api-key-env MXGATEWAY_API_KEY
|
|
dotnet run --project clients/dotnet/ZB.MOM.WW.MxGateway.Client.Cli -- galaxy-watch --endpoint http://localhost:5000 --api-key-env MXGATEWAY_API_KEY --last-seen-deploy-time 2026-04-28T14:30:00Z --json
|
|
dotnet run --project clients/dotnet/ZB.MOM.WW.MxGateway.Client.Cli -- galaxy-watch --endpoint http://localhost:5000 --api-key-env MXGATEWAY_API_KEY --max-events 5 --json
|
|
```
|
|
|
|
Use TLS options for a secured gateway:
|
|
|
|
```powershell
|
|
dotnet run --project clients/dotnet/ZB.MOM.WW.MxGateway.Client.Cli -- smoke --endpoint https://ZB.MOM.WW.MxGateway.example.local:5001 --tls --ca-file C:\certs\mxgateway-ca.pem --server-name ZB.MOM.WW.MxGateway.example.local --api-key-env MXGATEWAY_API_KEY --item Area001.Pump001.Speed --json
|
|
```
|
|
|
|
### TLS trust
|
|
|
|
The gateway can auto-generate its own self-signed certificate (it has no PKI), so
|
|
the client is **lenient by default**: a TLS connection (`UseTls` / `--tls`) with
|
|
no pinned CA accepts whatever certificate the gateway presents. To verify
|
|
instead, pin a CA with `CaCertificatePath` / `--ca-file` (this path also enforces
|
|
the certificate hostname/SAN match), or set `RequireCertificateValidation` to
|
|
force OS/system-trust verification without pinning. Use `ServerNameOverride` /
|
|
`--server-name` when the dialed host differs from the certificate SAN. See
|
|
[Gateway Configuration](../../docs/GatewayConfiguration.md#automatic-self-signed-certificate).
|
|
|
|
## Integration Checks
|
|
|
|
Run live checks only when a gateway and MXAccess-backed worker are available:
|
|
|
|
```powershell
|
|
$env:MXGATEWAY_INTEGRATION = '1'
|
|
$env:MXGATEWAY_ENDPOINT = 'http://localhost:5000'
|
|
$env:MXGATEWAY_API_KEY = '<gateway-api-key>'
|
|
$env:MXGATEWAY_TEST_ITEM = 'Area001.Pump001.Speed'
|
|
dotnet run --project clients/dotnet/ZB.MOM.WW.MxGateway.Client.Cli -- smoke --endpoint $env:MXGATEWAY_ENDPOINT --api-key-env MXGATEWAY_API_KEY --item $env:MXGATEWAY_TEST_ITEM --json
|
|
```
|
|
|
|
## Installing as a NuGet Package
|
|
|
|
The client publishes to the internal Gitea NuGet feed at
|
|
`https://gitea.dohertylan.com/api/packages/dohertj2/nuget/index.json`.
|
|
|
|
Add the feed once:
|
|
|
|
````bash
|
|
dotnet nuget add source https://gitea.dohertylan.com/api/packages/dohertj2/nuget/index.json \
|
|
--name dohertj2-gitea \
|
|
--username <gitea-username> \
|
|
--password <gitea-token-or-password> \
|
|
--store-password-in-clear-text
|
|
````
|
|
|
|
Then add the package to your project:
|
|
|
|
````bash
|
|
dotnet add package ZB.MOM.WW.MxGateway.Client --version 0.1.1
|
|
````
|
|
|
|
The `ZB.MOM.WW.MxGateway.Contracts` package is pulled in transitively.
|
|
|
|
## Related Documentation
|
|
|
|
- [Client Packaging](../../docs/ClientPackaging.md)
|
|
- [Client Proto Generation](../../docs/ClientProtoGeneration.md)
|
|
- [.NET Client Detailed Design](./DotnetClientDesign.md)
|