[M5] mxaccess-asb: F25 step 8 — subscription operations
CreateSubscription / AddMonitoredItems / Publish / DeleteSubscription. Completes the IASBIDataV2 read-and-subscribe path; remaining ops (Write/PublishWriteComplete/DeleteMonitoredItems) are mechanical extensions of the same pattern. Contracts: * `MonitoredItemValue` codec (IAsbCustomSerializableType binary fast-path: ItemIdentity + RuntimeValue + AsbVariant per `AsbContracts.cs:1064-1068`) with array codec (4-byte int32 count + per-element body, mirrors `WriteArrayToStream` at `cs:1095-1103`). Request builders: * `build_create_subscription_request_body(max_queue_size, sample_interval)` — primitive fields per `cs:215-223`. * `build_delete_subscription_request_body(subscription_id)` — primitive field per `cs:232-237`. * `build_publish_request_body(subscription_id)` — primitive field per `cs:287-292`. * `build_add_monitored_items_request_body(subscription_id, items, require_id)` — minimal MonitoredItem shape (Item + SampleInterval + Buffered). Full optional-field set (Active/TimeDeadband/ValueDeadband/UserData) deferred to a later iteration once a live capture confirms the WCF DataContract XML wire form. Response decoders: * `decode_create_subscription_response` — single int64 SubscriptionId field. Decoder accepts Int64Text, Int32Text, Zero/One, or numeric-string Chars (covers all WCF binary numeric encodings). * `decode_add_monitored_items_response` — Status array + ItemCapabilities-presence flag (mirrors RegisterItemsResponse). * `decode_publish_response` — Status array + Values (MonitoredItemValue) array. `BodyField::Int64Element` variant added for the primitive SubscriptionId / MaxQueueSize / SampleInterval fields. `uint64` helper casts to i64 (covers proven value range; if ulong > i64::MAX ever appears we'll add UInt64Text to F21's NbfxText enum). Client wrappers (4 new methods on AsbClient): * `create_subscription(max_queue_size, sample_interval)` * `add_monitored_items(subscription_id, items, require_id)` * `publish(subscription_id)` * `delete_subscription(subscription_id)` 11 new tests cover: * MonitoredItemValue round-trip + array round-trip. * CreateSubscription request body shape (Int64 payloads). * CreateSubscription response decoder via Int64Text. * CreateSubscription response decoder via Chars text fallback. * CreateSubscription response missing-field error. * AddMonitoredItems body carries SubscriptionId + MonitoredItem elements. * AddMonitoredItems response Status round-trip. * DeleteSubscription body carries SubscriptionId. * Publish request body shape. * Publish response Status + Values round-trip. Workspace: 691 tests pass (was 680, +11). The asb-subscribe example can now do create_subscription → add_monitored_items → publish-loop → delete_subscription once wire-byte reconciliation against a live capture confirms the MonitoredItem XML shape. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -55,10 +55,16 @@ use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
|
||||
use crate::contracts::{ItemIdentity, ItemStatus};
|
||||
use crate::envelope::{ConnectionValidator, EnvelopeError, SoapEnvelope};
|
||||
use crate::operations::{
|
||||
ConnectResponse, OperationError, ReadResponse, RegisterItemsResponse, UnregisterItemsResponse,
|
||||
build_authenticate_me_request_body, build_connect_request_body, build_disconnect_request_body,
|
||||
build_keep_alive_request_body, build_read_request_body, build_register_items_request_body,
|
||||
build_unregister_items_request_body, decode_connect_response, decode_read_response,
|
||||
AddMonitoredItemsResponse, ConnectResponse, CreateSubscriptionResponse,
|
||||
DeleteSubscriptionResponse, MinimalMonitoredItem, OperationError, PublishResponse,
|
||||
ReadResponse, RegisterItemsResponse, UnregisterItemsResponse,
|
||||
build_add_monitored_items_request_body, build_authenticate_me_request_body,
|
||||
build_connect_request_body, build_create_subscription_request_body,
|
||||
build_delete_subscription_request_body, build_disconnect_request_body,
|
||||
build_keep_alive_request_body, build_publish_request_body, build_read_request_body,
|
||||
build_register_items_request_body, build_unregister_items_request_body,
|
||||
decode_add_monitored_items_response, decode_connect_response,
|
||||
decode_create_subscription_response, decode_publish_response, decode_read_response,
|
||||
decode_register_items_response, decode_unregister_items_response,
|
||||
};
|
||||
use crate::{actions, decode_envelope, encode_envelope};
|
||||
@@ -335,6 +341,67 @@ impl<T: AsyncRead + AsyncWrite + Unpin + Send> AsbClient<T> {
|
||||
Ok(decode_read_response(&response.body_tokens)?)
|
||||
}
|
||||
|
||||
/// `CreateSubscription` operation — allocates a server-side
|
||||
/// subscription and returns its ID. Caller threads the ID through
|
||||
/// subsequent `add_monitored_items` / `publish` /
|
||||
/// `delete_subscription` calls.
|
||||
pub async fn create_subscription(
|
||||
&mut self,
|
||||
max_queue_size: i64,
|
||||
sample_interval: u64,
|
||||
) -> Result<CreateSubscriptionResponse, ClientError> {
|
||||
let body = build_create_subscription_request_body(max_queue_size, sample_interval);
|
||||
let response = self
|
||||
.send_signed_envelope(actions::CREATE_SUBSCRIPTION, body, false)
|
||||
.await?;
|
||||
Ok(decode_create_subscription_response(
|
||||
&response.body_tokens,
|
||||
&self.read_dictionary,
|
||||
)?)
|
||||
}
|
||||
|
||||
/// `AddMonitoredItems` operation — adds items to an existing
|
||||
/// subscription. Uses [`MinimalMonitoredItem`] (Item +
|
||||
/// SampleInterval + Buffered); optional fields are deferred to a
|
||||
/// later F25 iteration.
|
||||
pub async fn add_monitored_items(
|
||||
&mut self,
|
||||
subscription_id: i64,
|
||||
items: &[MinimalMonitoredItem],
|
||||
require_id: bool,
|
||||
) -> Result<AddMonitoredItemsResponse, ClientError> {
|
||||
let body = build_add_monitored_items_request_body(subscription_id, items, require_id);
|
||||
let response = self
|
||||
.send_signed_envelope(actions::ADD_MONITORED_ITEMS, body, false)
|
||||
.await?;
|
||||
Ok(decode_add_monitored_items_response(&response.body_tokens)?)
|
||||
}
|
||||
|
||||
/// `Publish` operation — long-polls the subscription queue for
|
||||
/// available samples. Typical pattern is to call this in a loop
|
||||
/// with a small `tokio::time::timeout` per call.
|
||||
pub async fn publish(&mut self, subscription_id: i64) -> Result<PublishResponse, ClientError> {
|
||||
let body = build_publish_request_body(subscription_id);
|
||||
let response = self
|
||||
.send_signed_envelope(actions::PUBLISH, body, false)
|
||||
.await?;
|
||||
Ok(decode_publish_response(&response.body_tokens)?)
|
||||
}
|
||||
|
||||
/// `DeleteSubscription` operation — releases a server-side
|
||||
/// subscription. The response body is empty per
|
||||
/// `AsbContracts.cs:239-240`.
|
||||
pub async fn delete_subscription(
|
||||
&mut self,
|
||||
subscription_id: i64,
|
||||
) -> Result<DeleteSubscriptionResponse, ClientError> {
|
||||
let body = build_delete_subscription_request_body(subscription_id);
|
||||
let _ = self
|
||||
.send_signed_envelope(actions::DELETE_SUBSCRIPTION, body, false)
|
||||
.await?;
|
||||
Ok(DeleteSubscriptionResponse)
|
||||
}
|
||||
|
||||
/// `RegisterItems` operation — sends a signed `RegisterItemsIn`
|
||||
/// SOAP envelope and decodes the `RegisterItemsResponse`.
|
||||
pub async fn register_items(
|
||||
|
||||
Reference in New Issue
Block a user