clients: document supervisory/array-write parity gotchas and add advise-supervisory to all CLIs

A consuming project hit two MXAccess parity surprises: a plain Write only
records its user_id when the item has an active supervisory advise (the path
to take when not authenticating), and array writes replace the whole array
rather than patching individual elements. Document both across the five client
READMEs and gateway.md's compatibility baseline, and expose the missing
advise-supervisory subcommand in the go/python/rust/java CLIs (plus the .NET
help text) so callers can establish the supervisory advise without dropping to
the raw command API.
This commit is contained in:
Joseph Doherty
2026-06-17 20:14:48 -04:00
parent bed647ca2c
commit 9eedf9d6a9
12 changed files with 416 additions and 4 deletions
+47
View File
@@ -121,6 +121,53 @@ can keep the full `MxCommandReply`, HRESULT, and status array when MXAccess
itself rejects a command. `MxAccessException.Reply` contains the raw generated itself rejects a command. `MxAccessException.Reply` contains the raw generated
reply. 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.
## CLI Usage ## CLI Usage
The test CLI supports deterministic JSON output for automation: The test CLI supports deterministic JSON output for automation:
@@ -110,6 +110,8 @@ public static class MxGatewayClientCli
.ConfigureAwait(false), .ConfigureAwait(false),
"advise" => await AdviseAsync(arguments, client, standardOutput, cancellation.Token) "advise" => await AdviseAsync(arguments, client, standardOutput, cancellation.Token)
.ConfigureAwait(false), .ConfigureAwait(false),
"advise-supervisory" => await AdviseSupervisoryAsync(arguments, client, standardOutput, cancellation.Token)
.ConfigureAwait(false),
"subscribe-bulk" => await SubscribeBulkAsync(arguments, client, standardOutput, cancellation.Token) "subscribe-bulk" => await SubscribeBulkAsync(arguments, client, standardOutput, cancellation.Token)
.ConfigureAwait(false), .ConfigureAwait(false),
"unsubscribe-bulk" => await UnsubscribeBulkAsync(arguments, client, standardOutput, cancellation.Token) "unsubscribe-bulk" => await UnsubscribeBulkAsync(arguments, client, standardOutput, cancellation.Token)
@@ -451,6 +453,28 @@ public static class MxGatewayClientCli
cancellationToken); cancellationToken);
} }
private static Task<int> AdviseSupervisoryAsync(
CliArguments arguments,
IMxGatewayCliClient client,
TextWriter output,
CancellationToken cancellationToken)
{
return InvokeAndWriteAsync(
arguments,
client,
output,
new MxCommand
{
Kind = MxCommandKind.AdviseSupervisory,
AdviseSupervisory = new AdviseSupervisoryCommand
{
ServerHandle = arguments.GetInt32("server-handle"),
ItemHandle = arguments.GetInt32("item-handle"),
},
},
cancellationToken);
}
private static Task<int> SubscribeBulkAsync( private static Task<int> SubscribeBulkAsync(
CliArguments arguments, CliArguments arguments,
IMxGatewayCliClient client, IMxGatewayCliClient client,
@@ -2066,6 +2090,7 @@ public static class MxGatewayClientCli
writer.WriteLine("mxgw-dotnet register --session-id <id> --client-name <name> [--json]"); writer.WriteLine("mxgw-dotnet register --session-id <id> --client-name <name> [--json]");
writer.WriteLine("mxgw-dotnet add-item --session-id <id> --server-handle <n> --item <ref> [--json]"); writer.WriteLine("mxgw-dotnet add-item --session-id <id> --server-handle <n> --item <ref> [--json]");
writer.WriteLine("mxgw-dotnet advise --session-id <id> --server-handle <n> --item-handle <n> [--json]"); writer.WriteLine("mxgw-dotnet advise --session-id <id> --server-handle <n> --item-handle <n> [--json]");
writer.WriteLine("mxgw-dotnet advise-supervisory --session-id <id> --server-handle <n> --item-handle <n> [--json]");
writer.WriteLine("mxgw-dotnet subscribe-bulk --session-id <id> --server-handle <n> --items <ref,ref> [--json]"); writer.WriteLine("mxgw-dotnet subscribe-bulk --session-id <id> --server-handle <n> --items <ref,ref> [--json]");
writer.WriteLine("mxgw-dotnet unsubscribe-bulk --session-id <id> --server-handle <n> --item-handles <n,n> [--json]"); writer.WriteLine("mxgw-dotnet unsubscribe-bulk --session-id <id> --server-handle <n> --item-handles <n,n> [--json]");
writer.WriteLine("mxgw-dotnet read-bulk --session-id <id> --server-handle <n> --items <ref,ref> [--timeout-ms <n>] [--json]"); writer.WriteLine("mxgw-dotnet read-bulk --session-id <id> --server-handle <n> --items <ref,ref> [--timeout-ms <n>] [--json]");
+46
View File
@@ -99,6 +99,52 @@ call returns a `StreamAlarmsClient`; cancel its context to terminate the
stream. All three pass straight through to the gateway's central alarm stream. All three pass straight through to the gateway's central alarm
monitor. monitor.
## 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 session exposes `Advise`/`UnAdvise` but not supervisory advise, so send it
through the generic command channel:
```go
_, err := client.Invoke(ctx, &pb.MxCommandRequest{
SessionId: session.ID(),
Command: &pb.MxCommand{
Kind: pb.MxCommandKind_MX_COMMAND_KIND_ADVISE_SUPERVISORY,
Payload: &pb.MxCommand_AdviseSupervisory{
AdviseSupervisory: &pb.AdviseSupervisoryCommand{
ServerHandle: serverHandle,
ItemHandle: itemHandle,
},
},
},
})
err = session.Write(ctx, serverHandle, itemHandle, value, 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.
## Galaxy Repository browse ## Galaxy Repository browse
The `GalaxyRepository` service (proto package `galaxy_repository.v1`) is a The `GalaxyRepository` service (proto package `galaxy_repository.v1`) is a
+40
View File
@@ -21,6 +21,7 @@ import (
"syscall" "syscall"
"time" "time"
pb "gitea.dohertylan.com/dohertj2/mxaccessgw/clients/go/internal/generated"
"gitea.dohertylan.com/dohertj2/mxaccessgw/clients/go/mxgateway" "gitea.dohertylan.com/dohertj2/mxaccessgw/clients/go/mxgateway"
"google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/reflect/protoreflect" "google.golang.org/protobuf/reflect/protoreflect"
@@ -87,6 +88,8 @@ func runWithIO(ctx context.Context, args []string, stdout, stderr io.Writer) err
return runAddItem(ctx, args[1:], stdout, stderr) return runAddItem(ctx, args[1:], stdout, stderr)
case "advise": case "advise":
return runAdvise(ctx, args[1:], stdout, stderr) return runAdvise(ctx, args[1:], stdout, stderr)
case "advise-supervisory":
return runAdviseSupervisory(ctx, args[1:], stdout, stderr)
case "subscribe-bulk": case "subscribe-bulk":
return runSubscribeBulk(ctx, args[1:], stdout, stderr) return runSubscribeBulk(ctx, args[1:], stdout, stderr)
case "unsubscribe-bulk": case "unsubscribe-bulk":
@@ -358,6 +361,43 @@ func runAdvise(ctx context.Context, args []string, stdout, stderr io.Writer) err
return writeCommandOutput(stdout, *jsonOutput, "advise", options, reply, err) return writeCommandOutput(stdout, *jsonOutput, "advise", options, reply, err)
} }
func runAdviseSupervisory(ctx context.Context, args []string, stdout, stderr io.Writer) error {
flags := flag.NewFlagSet("advise-supervisory", flag.ContinueOnError)
flags.SetOutput(stderr)
common := bindCommonFlags(flags)
jsonOutput := flags.Bool("json", false, "write JSON output")
sessionID := flags.String("session-id", "", "gateway session id")
serverHandle := flags.Int("server-handle", 0, "MXAccess server handle")
itemHandle := flags.Int("item-handle", 0, "MXAccess item handle")
if err := flags.Parse(args); err != nil {
return err
}
if *sessionID == "" {
return errors.New("session-id is required")
}
client, options, err := dialForCommand(ctx, common)
if err != nil {
return err
}
defer client.Close()
reply, err := client.Invoke(ctx, &pb.MxCommandRequest{
SessionId: *sessionID,
Command: &pb.MxCommand{
Kind: pb.MxCommandKind_MX_COMMAND_KIND_ADVISE_SUPERVISORY,
Payload: &pb.MxCommand_AdviseSupervisory{
AdviseSupervisory: &pb.AdviseSupervisoryCommand{
ServerHandle: int32(*serverHandle),
ItemHandle: int32(*itemHandle),
},
},
},
})
return writeCommandOutput(stdout, *jsonOutput, "advise-supervisory", options, reply, err)
}
func runSubscribeBulk(ctx context.Context, args []string, stdout, stderr io.Writer) error { func runSubscribeBulk(ctx context.Context, args []string, stdout, stderr io.Writer) error {
flags := flag.NewFlagSet("subscribe-bulk", flag.ContinueOnError) flags := flag.NewFlagSet("subscribe-bulk", flag.ContinueOnError)
flags.SetOutput(stderr) flags.SetOutput(stderr)
+40
View File
@@ -84,6 +84,46 @@ yields alarm-feed messages from the gateway's central monitor), and
`acknowledgeAlarm` (ack by full alarm reference with an optional comment and `acknowledgeAlarm` (ack by full alarm reference with an optional comment and
ack target). Close the subscription to cancel the underlying gRPC stream. ack target). Close the subscription to cancel the underlying gRPC stream.
## 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 session exposes `advise`/`unAdvise` but not supervisory advise, so send it
through the generic command channel:
```java
session.invokeCommand(MxCommand.newBuilder()
.setKind(MxCommandKind.MX_COMMAND_KIND_ADVISE_SUPERVISORY)
.setAdviseSupervisory(AdviseSupervisoryCommand.newBuilder()
.setServerHandle(serverHandle)
.setItemHandle(itemHandle))
.build());
session.write(serverHandle, itemHandle, value, 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.
## Galaxy Repository Browse ## Galaxy Repository Browse
The Galaxy Repository service is a separate metadata-only gRPC service exposed The Galaxy Repository service is a separate metadata-only gRPC service exposed
@@ -153,6 +153,8 @@ public final class MxGatewayCli implements Callable<Integer> {
commandLine.addSubcommand("register", new RegisterCommand(clientFactory)); commandLine.addSubcommand("register", new RegisterCommand(clientFactory));
commandLine.addSubcommand("add-item", new AddItemCommand(clientFactory)); commandLine.addSubcommand("add-item", new AddItemCommand(clientFactory));
commandLine.addSubcommand("advise", new AdviseCommand(clientFactory)); commandLine.addSubcommand("advise", new AdviseCommand(clientFactory));
commandLine.addSubcommand(
"advise-supervisory", new AdviseSupervisoryCommand(clientFactory));
commandLine.addSubcommand("subscribe-bulk", new SubscribeBulkCommand(clientFactory)); commandLine.addSubcommand("subscribe-bulk", new SubscribeBulkCommand(clientFactory));
commandLine.addSubcommand("unsubscribe-bulk", new UnsubscribeBulkCommand(clientFactory)); commandLine.addSubcommand("unsubscribe-bulk", new UnsubscribeBulkCommand(clientFactory));
commandLine.addSubcommand("read-bulk", new ReadBulkCommand(clientFactory)); commandLine.addSubcommand("read-bulk", new ReadBulkCommand(clientFactory));
@@ -1044,6 +1046,34 @@ public final class MxGatewayCli implements Callable<Integer> {
} }
} }
@Command(
name = "advise-supervisory",
description = "Invokes MXAccess AdviseSupervisory.")
static final class AdviseSupervisoryCommand extends GatewayCommand {
@Option(names = "--session-id", required = true, description = "Gateway session id.")
String sessionId;
@Option(names = "--server-handle", required = true, description = "MXAccess server handle.")
int serverHandle;
@Option(names = "--item-handle", required = true, description = "MXAccess item handle.")
int itemHandle;
AdviseSupervisoryCommand(MxGatewayCliClientFactory clientFactory) {
super(clientFactory);
}
@Override
public Integer call() {
try (MxGatewayCliClient client = clientFactory.connect(common.resolved())) {
MxCommandReply reply =
client.session(sessionId).adviseSupervisoryRaw(serverHandle, itemHandle);
writeOutput("advise-supervisory", common, json, reply, () -> reply.getKind().name());
}
return 0;
}
}
@Command(name = "subscribe-bulk", description = "Invokes MXAccess SubscribeBulk.") @Command(name = "subscribe-bulk", description = "Invokes MXAccess SubscribeBulk.")
static final class SubscribeBulkCommand extends GatewayCommand { static final class SubscribeBulkCommand extends GatewayCommand {
@Option(names = "--session-id", required = true, description = "Gateway session id.") @Option(names = "--session-id", required = true, description = "Gateway session id.")
@@ -1830,6 +1860,8 @@ public final class MxGatewayCli implements Callable<Integer> {
MxCommandReply adviseRaw(int serverHandle, int itemHandle); MxCommandReply adviseRaw(int serverHandle, int itemHandle);
MxCommandReply adviseSupervisoryRaw(int serverHandle, int itemHandle);
MxCommandReply writeRaw(int serverHandle, int itemHandle, MxValue value, int userId); MxCommandReply writeRaw(int serverHandle, int itemHandle, MxValue value, int userId);
List<SubscribeResult> subscribeBulk(int serverHandle, List<String> items); List<SubscribeResult> subscribeBulk(int serverHandle, List<String> items);
@@ -1948,6 +1980,17 @@ public final class MxGatewayCli implements Callable<Integer> {
return session.adviseRaw(serverHandle, itemHandle); return session.adviseRaw(serverHandle, itemHandle);
} }
@Override
public MxCommandReply adviseSupervisoryRaw(int serverHandle, int itemHandle) {
return session.invokeCommand(MxCommand.newBuilder()
.setKind(MxCommandKind.MX_COMMAND_KIND_ADVISE_SUPERVISORY)
.setAdviseSupervisory(
mxaccess_gateway.v1.MxaccessGateway.AdviseSupervisoryCommand.newBuilder()
.setServerHandle(serverHandle)
.setItemHandle(itemHandle))
.build());
}
@Override @Override
public MxCommandReply writeRaw(int serverHandle, int itemHandle, MxValue value, int userId) { public MxCommandReply writeRaw(int serverHandle, int itemHandle, MxValue value, int userId) {
return session.writeRaw(serverHandle, itemHandle, value, userId); return session.writeRaw(serverHandle, itemHandle, value, userId);
@@ -1302,6 +1302,15 @@ final class MxGatewayCliTests {
.build(); .build();
} }
@Override
public MxCommandReply adviseSupervisoryRaw(int serverHandle, int itemHandle) {
adviseCalled = true;
return MxCommandReply.newBuilder()
.setKind(MxCommandKind.MX_COMMAND_KIND_ADVISE_SUPERVISORY)
.setProtocolStatus(ok())
.build();
}
@Override @Override
public MxCommandReply writeRaw(int serverHandle, int itemHandle, MxValue value, int userId) { public MxCommandReply writeRaw(int serverHandle, int itemHandle, MxValue value, int userId) {
lastWriteValue = value; lastWriteValue = value;
+43
View File
@@ -105,6 +105,49 @@ terminate the stream.
Canceling a Python task cancels the client-side gRPC call or stream wait. It Canceling a Python task cancels the client-side gRPC call or stream wait. It
does not abort an in-flight MXAccess COM call inside the worker process. does not abort an in-flight MXAccess COM call inside the worker process.
## 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 `authenticate_user`
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 (`authenticate_user``write_secured`/`write_secured2`)
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 `user_id` on a plain write is ignored.
The session exposes `advise`/`unadvise` but not supervisory advise, so send it
through the generic command channel:
```python
await session.invoke(
pb.MxCommand(
kind=pb.MX_COMMAND_KIND_ADVISE_SUPERVISORY,
advise_supervisory=pb.AdviseSupervisoryCommand(
server_handle=server_handle,
item_handle=item_handle,
),
)
)
await session.write(server_handle, item_handle, value, user_id=user_id)
```
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.
## Galaxy Repository Browse ## Galaxy Repository Browse
The `GalaxyRepositoryClient` wraps the read-only `GalaxyRepository` gRPC The `GalaxyRepositoryClient` wraps the read-only `GalaxyRepository` gRPC
@@ -277,6 +277,23 @@ def advise(**kwargs: Any) -> None:
_run(_advise(**kwargs), output_json=kwargs["output_json"], secrets=_secrets(kwargs)) _run(_advise(**kwargs), output_json=kwargs["output_json"], secrets=_secrets(kwargs))
@main.command("advise-supervisory")
@gateway_options
@click.option("--session-id", required=True, help="Gateway session id.")
@click.option("--server-handle", required=True, type=int, help="MXAccess server handle.")
@click.option("--item-handle", required=True, type=int, help="MXAccess item handle.")
@click.option("--correlation-id", default="", help="Client correlation id.")
@click.option("--json", "output_json", is_flag=True, help="Emit JSON output.")
def advise_supervisory(**kwargs: Any) -> None:
"""Invoke MXAccess AdviseSupervisory."""
_run(
_advise_supervisory(**kwargs),
output_json=kwargs["output_json"],
secrets=_secrets(kwargs),
)
@main.command("subscribe-bulk") @main.command("subscribe-bulk")
@gateway_options @gateway_options
@click.option("--session-id", required=True, help="Gateway session id.") @click.option("--session-id", required=True, help="Gateway session id.")
@@ -725,6 +742,22 @@ async def _advise(**kwargs: Any) -> dict[str, Any]:
return {"ok": True} return {"ok": True}
async def _advise_supervisory(**kwargs: Any) -> dict[str, Any]:
async with await _connect(kwargs) as client:
session = _session(client, kwargs["session_id"])
await session.invoke(
pb.MxCommand(
kind=pb.MX_COMMAND_KIND_ADVISE_SUPERVISORY,
advise_supervisory=pb.AdviseSupervisoryCommand(
server_handle=kwargs["server_handle"],
item_handle=kwargs["item_handle"],
),
),
correlation_id=kwargs["correlation_id"],
)
return {"ok": True}
async def _subscribe_bulk(**kwargs: Any) -> dict[str, Any]: async def _subscribe_bulk(**kwargs: Any) -> dict[str, Any]:
async with await _connect(kwargs) as client: async with await _connect(kwargs) as client:
session = _session(client, kwargs["session_id"]) session = _session(client, kwargs["session_id"])
+43
View File
@@ -125,6 +125,49 @@ preserving the raw message for parity diagnostics. Command replies whose
protocol status is not `PROTOCOL_STATUS_CODE_OK` become `Error::Command` and protocol status is not `PROTOCOL_STATUS_CODE_OK` become `Error::Command` and
retain the raw `MxCommandReply`. retain the raw `MxCommandReply`.
## 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 `authenticate_user`
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 (`authenticate_user``write_secured`/`write_secured2`)
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 `user_id` on a plain write is ignored.
The session exposes `advise`/`un_advise` but not supervisory advise, so send it
through the generic command channel:
```rust
session
.invoke(
MxCommandKind::AdviseSupervisory,
Payload::AdviseSupervisory(AdviseSupervisoryCommand {
server_handle,
item_handle,
}),
)
.await?;
session.write(server_handle, item_handle, value, user_id).await?;
```
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.
## Galaxy Repository browse ## Galaxy Repository browse
The Galaxy Repository service exposes a read-only browse over the AVEVA System The Galaxy Repository service exposes a read-only browse over the AVEVA System
+38 -4
View File
@@ -21,10 +21,11 @@ use serde_json::Value;
use zb_mom_ww_mxgateway_client::galaxy::{BrowseChildrenOptions, LazyBrowseNode}; use zb_mom_ww_mxgateway_client::galaxy::{BrowseChildrenOptions, LazyBrowseNode};
use zb_mom_ww_mxgateway_client::generated::galaxy_repository::v1::DeployEvent; use zb_mom_ww_mxgateway_client::generated::galaxy_repository::v1::DeployEvent;
use zb_mom_ww_mxgateway_client::generated::mxaccess_gateway::v1::{ use zb_mom_ww_mxgateway_client::generated::mxaccess_gateway::v1::{
alarm_feed_message, AcknowledgeAlarmRequest, AlarmFeedMessage, CloseSessionRequest, MxCommand, alarm_feed_message, AcknowledgeAlarmRequest, AdviseSupervisoryCommand, AlarmFeedMessage,
MxCommandKind, MxCommandRequest, MxEvent, MxEventFamily, MxValue as ProtoMxValue, CloseSessionRequest, MxCommand, MxCommandKind, MxCommandRequest, MxEvent, MxEventFamily,
OpenSessionRequest, PingCommand, StreamAlarmsRequest, StreamEventsRequest, Write2BulkEntry, MxValue as ProtoMxValue, OpenSessionRequest, PingCommand, StreamAlarmsRequest,
WriteBulkEntry, WriteSecured2BulkEntry, WriteSecuredBulkEntry, StreamEventsRequest, Write2BulkEntry, WriteBulkEntry, WriteSecured2BulkEntry,
WriteSecuredBulkEntry,
}; };
use zb_mom_ww_mxgateway_client::{ use zb_mom_ww_mxgateway_client::{
next_correlation_id, ApiKey, ClientOptions, Error, GalaxyClient, GatewayClient, MxValue, next_correlation_id, ApiKey, ClientOptions, Error, GalaxyClient, GatewayClient, MxValue,
@@ -105,6 +106,18 @@ enum Command {
#[arg(long)] #[arg(long)]
json: bool, json: bool,
}, },
AdviseSupervisory {
#[command(flatten)]
connection: ConnectionArgs,
#[arg(long)]
session_id: String,
#[arg(long)]
server_handle: i32,
#[arg(long)]
item_handle: i32,
#[arg(long)]
json: bool,
},
SubscribeBulk { SubscribeBulk {
#[command(flatten)] #[command(flatten)]
connection: ConnectionArgs, connection: ConnectionArgs,
@@ -647,6 +660,27 @@ async fn dispatch(command: Command) -> Result<(), Error> {
session.advise(server_handle, item_handle).await?; session.advise(server_handle, item_handle).await?;
print_ok("advise", json); print_ok("advise", json);
} }
Command::AdviseSupervisory {
connection,
session_id,
server_handle,
item_handle,
json,
} => {
let session = session_for(connection, session_id).await?;
session
.invoke(
MxCommandKind::AdviseSupervisory,
zb_mom_ww_mxgateway_client::generated::mxaccess_gateway::v1::mx_command::Payload::AdviseSupervisory(
AdviseSupervisoryCommand {
server_handle,
item_handle,
},
),
)
.await?;
print_ok("advise-supervisory", json);
}
Command::SubscribeBulk { Command::SubscribeBulk {
connection, connection,
session_id, session_id,
+9
View File
@@ -1049,6 +1049,15 @@ Known important parity areas from existing captures:
- Invalid handles and cross-server handles have specific exception/status - Invalid handles and cross-server handles have specific exception/status
behavior. behavior.
- STA message pumping is required for event delivery. - STA message pumping is required for event delivery.
- A plain `Write`/`Write2` only honors its `user_id` when the item has an active
supervisory advise. Callers that do not go through the
`AuthenticateUser``WriteSecured`/`WriteSecured2` path must send
`AdviseSupervisory` for the item before a user id on a plain write is
recorded; otherwise the user id is ignored.
- Writing an array attribute replaces the whole array — it is not an
element-wise patch. To change a subset of elements the caller must send the
full array (unchanged elements included); sending only the changed elements
resizes the attribute.
The gateway should not "fix" these behaviors unless the client explicitly opts The gateway should not "fix" these behaviors unless the client explicitly opts
into a non-parity mode. into a non-parity mode.