PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
kcenon::pacs::services::ups_watch_scu Class Reference

UPS Watch SCU service for subscribing to events on remote systems. More...

#include <ups_watch_scu.h>

Collaboration diagram for kcenon::pacs::services::ups_watch_scu:
Collaboration graph

Public Types

using event_received_callback
 Callback type for received event notifications.
 

Public Member Functions

 ups_watch_scu (std::shared_ptr< di::ILogger > logger=nullptr)
 Construct UPS Watch SCU with default configuration.
 
 ups_watch_scu (const ups_watch_scu_config &config, std::shared_ptr< di::ILogger > logger=nullptr)
 Construct UPS Watch SCU with custom configuration.
 
 ~ups_watch_scu ()=default
 
 ups_watch_scu (const ups_watch_scu &)=delete
 
ups_watch_scuoperator= (const ups_watch_scu &)=delete
 
 ups_watch_scu (ups_watch_scu &&)=delete
 
ups_watch_scuoperator= (ups_watch_scu &&)=delete
 
void set_event_received_callback (event_received_callback cb)
 Set callback for received event notifications.
 
network::Result< ups_resultsubscribe (network::association &assoc, const ups_subscribe_data &data)
 Subscribe to UPS workitem events (N-ACTION Type 3)
 
network::Result< ups_resultunsubscribe (network::association &assoc, const ups_unsubscribe_data &data)
 Unsubscribe from UPS workitem events (N-ACTION Type 4)
 
network::Result< ups_resultsuspend_global (network::association &assoc)
 Suspend global subscription (N-ACTION Type 5)
 
network::Result< ups_watch_eventhandle_event_report (const network::dimse::dimse_message &event_rq)
 Handle an N-EVENT-REPORT-RQ received from the SCP.
 
size_t subscribes_performed () const noexcept
 
size_t unsubscribes_performed () const noexcept
 
size_t events_received () const noexcept
 
void reset_statistics () noexcept
 

Private Member Functions

network::Result< ups_resultsend_watch_action (network::association &assoc, const std::string &sop_instance_uid, uint16_t action_type_id)
 
uint16_t next_message_id () noexcept
 

Static Private Member Functions

static ups_watch_event parse_event_dataset (uint16_t event_type_id, const std::string &workitem_uid, const core::dicom_dataset &dataset)
 

Private Attributes

std::shared_ptr< di::ILoggerlogger_
 
ups_watch_scu_config config_
 
event_received_callback event_callback_
 
std::atomic< uint16_t > message_id_counter_ {1}
 
std::atomic< size_t > subscribes_performed_ {0}
 
std::atomic< size_t > unsubscribes_performed_ {0}
 
std::atomic< size_t > events_received_ {0}
 

Detailed Description

UPS Watch SCU service for subscribing to events on remote systems.

The UPS Watch SCU (Service Class User) sends N-ACTION requests to remote UPS Watch SCP servers to subscribe/unsubscribe from workitem events, and handles incoming N-EVENT-REPORT notifications.

UPS Watch SCU Message Flow

UPS Watch SCU (This PACS) Remote UPS Watch SCP
| |
| N-ACTION-RQ (Subscribe, Type 3) |
| WorkitemUID: 1.2.3.4... |
|-------------------------------------->|
| N-ACTION-RSP (Success) |
|<--------------------------------------|
| |
| [Workitem state changes on SCP] |
| |
| N-EVENT-REPORT-RQ (Type 1) |
| State: COMPLETED |
|<--------------------------------------|
| N-EVENT-REPORT-RSP |
|-------------------------------------->|
| |
| N-ACTION-RQ (Unsubscribe, Type 4) |
|-------------------------------------->|
| N-ACTION-RSP (Success) |
|<--------------------------------------|

Definition at line 131 of file ups_watch_scu.h.

Member Typedef Documentation

◆ event_received_callback

Initial value:
std::function<void(
const ups_watch_event& event)>

Callback type for received event notifications.

Invoked when an N-EVENT-REPORT is received from the SCP.

Definition at line 170 of file ups_watch_scu.h.

Constructor & Destructor Documentation

◆ ups_watch_scu() [1/4]

kcenon::pacs::services::ups_watch_scu::ups_watch_scu ( std::shared_ptr< di::ILogger > logger = nullptr)
explicit

Construct UPS Watch SCU with default configuration.

Parameters
loggerLogger instance (nullptr uses null_logger)

Definition at line 22 of file ups_watch_scu.cpp.

23 : logger_(logger ? std::move(logger) : di::null_logger()) {}
std::shared_ptr< di::ILogger > logger_
std::shared_ptr< ILogger > null_logger()
Get a shared null logger instance.
Definition ilogger.h:271

◆ ups_watch_scu() [2/4]

kcenon::pacs::services::ups_watch_scu::ups_watch_scu ( const ups_watch_scu_config & config,
std::shared_ptr< di::ILogger > logger = nullptr )
explicit

Construct UPS Watch SCU with custom configuration.

Parameters
configConfiguration options
loggerLogger instance (nullptr uses null_logger)

Definition at line 25 of file ups_watch_scu.cpp.

27 : logger_(logger ? std::move(logger) : di::null_logger()),
28 config_(config) {}

◆ ~ups_watch_scu()

kcenon::pacs::services::ups_watch_scu::~ups_watch_scu ( )
default

◆ ups_watch_scu() [3/4]

kcenon::pacs::services::ups_watch_scu::ups_watch_scu ( const ups_watch_scu & )
delete

◆ ups_watch_scu() [4/4]

kcenon::pacs::services::ups_watch_scu::ups_watch_scu ( ups_watch_scu && )
delete

Member Function Documentation

◆ events_received()

size_t kcenon::pacs::services::ups_watch_scu::events_received ( ) const
nodiscardnoexcept

Definition at line 184 of file ups_watch_scu.cpp.

184 {
185 return events_received_.load(std::memory_order_relaxed);
186}

References events_received_.

◆ handle_event_report()

network::Result< ups_watch_event > kcenon::pacs::services::ups_watch_scu::handle_event_report ( const network::dimse::dimse_message & event_rq)
nodiscard

Handle an N-EVENT-REPORT-RQ received from the SCP.

Parses the event dataset and invokes the registered callback.

Parameters
event_rqThe N-EVENT-REPORT-RQ message
Returns
The parsed event notification

Definition at line 115 of file ups_watch_scu.cpp.

116 {
117
118 using namespace network::dimse;
119
120 // Verify command is N-EVENT-REPORT-RQ
121 if (event_rq.command() != command_field::n_event_report_rq) {
124 "Expected N-EVENT-REPORT-RQ, got: " +
125 std::string(to_string(event_rq.command())));
126 }
127
128 // Get event type ID
129 auto event_type = event_rq.event_type_id();
130 if (!event_type.has_value()) {
133 "Missing Event Type ID in N-EVENT-REPORT");
134 }
135
136 // Get workitem UID from the affected SOP Instance UID
137 std::string workitem_uid{event_rq.affected_sop_instance_uid()};
138
139 // Parse dataset if present
140 ups_watch_event event;
141 if (event_rq.has_dataset()) {
142 auto dataset_result = event_rq.dataset();
143 if (dataset_result.is_ok()) {
144 event = parse_event_dataset(
145 event_type.value(), workitem_uid,
146 dataset_result.value().get());
147 } else {
148 event.event_type_id = event_type.value();
149 event.workitem_uid = workitem_uid;
150 event.timestamp = std::chrono::system_clock::now();
151 }
152 } else {
153 event.event_type_id = event_type.value();
154 event.workitem_uid = workitem_uid;
155 event.timestamp = std::chrono::system_clock::now();
156 }
157
158 events_received_.fetch_add(1, std::memory_order_relaxed);
159
160 logger_->info("UPS Watch SCU: Received event type " +
161 std::to_string(event.event_type_id) +
162 " for workitem " + event.workitem_uid);
163
164 // Invoke callback if registered
165 if (event_callback_) {
166 event_callback_(event);
167 }
168
169 return kcenon::pacs::Result<ups_watch_event>::ok(std::move(event));
170}
event_received_callback event_callback_
static ups_watch_event parse_event_dataset(uint16_t event_type_id, const std::string &workitem_uid, const core::dicom_dataset &dataset)
constexpr int ups_unexpected_command
Definition result.h:210
constexpr int ups_invalid_action_type
Definition result.h:214
auto to_string(mpps_status status) -> std::string_view
Convert mpps_status to DICOM string representation.
Definition mpps_scp.h:60
Result< T > pacs_error(int code, const std::string &message, const std::string &details="")
Create a PACS error result with module context.
Definition result.h:234

References kcenon::pacs::network::dimse::dimse_message::affected_sop_instance_uid(), kcenon::pacs::network::dimse::dimse_message::command(), kcenon::pacs::network::dimse::dimse_message::dataset(), event_callback_, kcenon::pacs::network::dimse::dimse_message::event_type_id(), kcenon::pacs::services::ups_watch_event::event_type_id, events_received_, kcenon::pacs::network::dimse::dimse_message::has_dataset(), logger_, kcenon::pacs::pacs_error(), parse_event_dataset(), kcenon::pacs::services::to_string(), kcenon::pacs::error_codes::ups_invalid_action_type, kcenon::pacs::error_codes::ups_unexpected_command, and kcenon::pacs::services::ups_watch_event::workitem_uid.

Here is the call graph for this function:

◆ next_message_id()

uint16_t kcenon::pacs::services::ups_watch_scu::next_message_id ( )
nodiscardprivatenoexcept

Definition at line 320 of file ups_watch_scu.cpp.

320 {
321 uint16_t id = message_id_counter_.fetch_add(1, std::memory_order_relaxed);
322 if (id == 0) {
323 id = message_id_counter_.fetch_add(1, std::memory_order_relaxed);
324 }
325 return id;
326}
std::atomic< uint16_t > message_id_counter_
@ id
Implant Displaced (alternate code)

References message_id_counter_.

Referenced by send_watch_action().

Here is the caller graph for this function:

◆ operator=() [1/2]

ups_watch_scu & kcenon::pacs::services::ups_watch_scu::operator= ( const ups_watch_scu & )
delete

◆ operator=() [2/2]

ups_watch_scu & kcenon::pacs::services::ups_watch_scu::operator= ( ups_watch_scu && )
delete

◆ parse_event_dataset()

ups_watch_event kcenon::pacs::services::ups_watch_scu::parse_event_dataset ( uint16_t event_type_id,
const std::string & workitem_uid,
const core::dicom_dataset & dataset )
staticnodiscardprivate

Definition at line 281 of file ups_watch_scu.cpp.

284 {
285
286 ups_watch_event event;
287 event.event_type_id = event_type_id;
288 event.workitem_uid = workitem_uid;
289 event.event_info = dataset;
290 event.timestamp = std::chrono::system_clock::now();
291
292 // Extract fields based on event type
293 switch (event_type_id) {
295 event.procedure_step_state = dataset.get_string(
297 break;
298
300 event.cancellation_reason = dataset.get_string(
302 break;
303
305 event.progress = dataset.get_string(
307 break;
308
309 default:
310 break;
311 }
312
313 return event;
314}
constexpr core::dicom_tag procedure_step_progress
Procedure Step Progress (0074,1004)
constexpr core::dicom_tag procedure_step_state
Procedure Step State (0074,1000)
constexpr core::dicom_tag reason_for_cancellation
Reason for Cancellation (0074,1238)
constexpr uint16_t ups_event_state_report
Event Type 1: UPS State Report — workitem state changed.
constexpr uint16_t ups_event_progress_report
Event Type 3: UPS Progress Report — progress percentage update.
constexpr uint16_t ups_event_cancel_requested
Event Type 2: UPS Cancel Requested — performer should stop processing.

References kcenon::pacs::services::ups_watch_event::event_type_id, kcenon::pacs::core::dicom_dataset::get_string(), kcenon::pacs::services::ups_tags::procedure_step_progress, kcenon::pacs::services::ups_tags::procedure_step_state, kcenon::pacs::services::ups_tags::reason_for_cancellation, kcenon::pacs::services::ups_event_cancel_requested, kcenon::pacs::services::ups_event_progress_report, and kcenon::pacs::services::ups_event_state_report.

Referenced by handle_event_report().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ reset_statistics()

void kcenon::pacs::services::ups_watch_scu::reset_statistics ( )
noexcept

Definition at line 188 of file ups_watch_scu.cpp.

188 {
189 subscribes_performed_.store(0, std::memory_order_relaxed);
190 unsubscribes_performed_.store(0, std::memory_order_relaxed);
191 events_received_.store(0, std::memory_order_relaxed);
192}
std::atomic< size_t > unsubscribes_performed_
std::atomic< size_t > subscribes_performed_

References events_received_, subscribes_performed_, and unsubscribes_performed_.

◆ send_watch_action()

network::Result< ups_result > kcenon::pacs::services::ups_watch_scu::send_watch_action ( network::association & assoc,
const std::string & sop_instance_uid,
uint16_t action_type_id )
nodiscardprivate

Definition at line 198 of file ups_watch_scu.cpp.

201 {
202
203 using namespace network::dimse;
204
205 auto start_time = std::chrono::steady_clock::now();
206
207 // Verify association is established
208 if (!assoc.is_established()) {
211 "Association not established");
212 }
213
214 // Get accepted presentation context for UPS Watch
215 auto context_id = assoc.accepted_context_id(ups_watch_sop_class_uid);
216 if (!context_id) {
219 "No accepted presentation context for UPS Watch SOP Class");
220 }
221
222 // Build the N-ACTION request
223 auto request = make_n_action_rq(
226 sop_instance_uid,
227 action_type_id);
228
229 // Send the request
230 auto send_result = assoc.send_dimse(*context_id, request);
231 if (send_result.is_err()) {
232 logger_->error("UPS Watch SCU: Failed to send N-ACTION: " +
233 send_result.error().message);
234 return send_result.error();
235 }
236
237 // Receive the response
238 auto recv_result = assoc.receive_dimse(config_.timeout);
239 if (recv_result.is_err()) {
240 logger_->error("UPS Watch SCU: Failed to receive N-ACTION response: " +
241 recv_result.error().message);
242 return recv_result.error();
243 }
244
245 const auto& [recv_context_id, response] = recv_result.value();
246
247 // Verify it's an N-ACTION response
248 if (response.command() != command_field::n_action_rsp) {
251 "Expected N-ACTION-RSP but received " +
252 std::string(to_string(response.command())));
253 }
254
255 auto end_time = std::chrono::steady_clock::now();
256 auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
257 end_time - start_time);
258
259 // Build result
260 ups_result result;
261 result.workitem_uid = sop_instance_uid;
262 result.status = static_cast<uint16_t>(response.status());
263 result.elapsed = elapsed;
264
265 if (response.command_set().contains(tag_error_comment)) {
266 result.error_comment = response.command_set().get_string(tag_error_comment);
267 }
268
269 if (!result.is_success()) {
270 logger_->warn("UPS Watch SCU: N-ACTION returned status 0x" +
271 std::to_string(result.status));
272 }
273
274 return result;
275}
constexpr dicom_tag sop_instance_uid
SOP Instance UID.
constexpr int ups_context_not_accepted
Definition result.h:216
constexpr int association_not_established
Definition result.h:202
constexpr std::string_view ups_watch_sop_class_uid
UPS Watch SOP Class UID (PS3.4 Table CC.2-1)
std::chrono::milliseconds timeout
Timeout for receiving DIMSE response.

References kcenon::pacs::network::association::accepted_context_id(), kcenon::pacs::error_codes::association_not_established, config_, kcenon::pacs::services::ups_result::elapsed, kcenon::pacs::services::ups_result::error_comment, kcenon::pacs::network::association::is_established(), kcenon::pacs::services::ups_result::is_success(), logger_, next_message_id(), kcenon::pacs::pacs_error(), kcenon::pacs::network::association::receive_dimse(), kcenon::pacs::network::association::send_dimse(), kcenon::pacs::services::ups_result::status, kcenon::pacs::services::ups_watch_scu_config::timeout, kcenon::pacs::services::to_string(), kcenon::pacs::error_codes::ups_context_not_accepted, kcenon::pacs::error_codes::ups_unexpected_command, kcenon::pacs::services::ups_watch_sop_class_uid, and kcenon::pacs::services::ups_result::workitem_uid.

Referenced by subscribe(), suspend_global(), and unsubscribe().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ set_event_received_callback()

void kcenon::pacs::services::ups_watch_scu::set_event_received_callback ( event_received_callback cb)

Set callback for received event notifications.

Parameters
cbThe callback function

Definition at line 34 of file ups_watch_scu.cpp.

34 {
35 event_callback_ = std::move(cb);
36}

References event_callback_.

◆ subscribe()

network::Result< ups_result > kcenon::pacs::services::ups_watch_scu::subscribe ( network::association & assoc,
const ups_subscribe_data & data )
nodiscard

Subscribe to UPS workitem events (N-ACTION Type 3)

Sends a subscribe request to the remote UPS Watch SCP. Use empty workitem_uid for global subscription.

Parameters
assocThe established association to use
dataThe subscription parameters
Returns
Result containing ups_result on success, or error

Definition at line 42 of file ups_watch_scu.cpp.

44 {
45
46 // Determine the SOP Instance UID: workitem-specific or global
47 std::string sop_instance_uid = data.workitem_uid.empty()
49 : data.workitem_uid;
50
51 logger_->debug("UPS Watch SCU: Subscribe request" +
52 (data.workitem_uid.empty() ? std::string(" (global)") :
53 " for workitem " + data.workitem_uid));
54
55 auto result = send_watch_action(
56 assoc, sop_instance_uid, ups_watch_action_subscribe);
57
58 if (result.is_ok() && result.value().is_success()) {
59 subscribes_performed_.fetch_add(1, std::memory_order_relaxed);
60 logger_->info("UPS Watch SCU: Subscribe successful" +
61 (data.workitem_uid.empty() ? std::string(" (global)") :
62 " for workitem " + data.workitem_uid));
63 }
64
65 return result;
66}
network::Result< ups_result > send_watch_action(network::association &assoc, const std::string &sop_instance_uid, uint16_t action_type_id)
constexpr std::string_view ups_global_subscription_instance_uid
UPS Global Subscription SOP Instance UID Used as the SOP Instance UID for global (non-workitem-specif...
constexpr uint16_t ups_watch_action_subscribe
N-ACTION Type 3: Subscribe to Receive UPS Event Reports (PS3.4 CC.2.3.3)

References logger_, send_watch_action(), subscribes_performed_, kcenon::pacs::services::ups_global_subscription_instance_uid, kcenon::pacs::services::ups_watch_action_subscribe, and kcenon::pacs::services::ups_subscribe_data::workitem_uid.

Here is the call graph for this function:

◆ subscribes_performed()

size_t kcenon::pacs::services::ups_watch_scu::subscribes_performed ( ) const
nodiscardnoexcept

Definition at line 176 of file ups_watch_scu.cpp.

176 {
177 return subscribes_performed_.load(std::memory_order_relaxed);
178}

References subscribes_performed_.

◆ suspend_global()

network::Result< ups_result > kcenon::pacs::services::ups_watch_scu::suspend_global ( network::association & assoc)
nodiscard

Suspend global subscription (N-ACTION Type 5)

Parameters
assocThe established association to use
Returns
Result containing ups_result on success, or error

Definition at line 93 of file ups_watch_scu.cpp.

94 {
95
96 logger_->debug("UPS Watch SCU: Suspend global subscription");
97
98 auto result = send_watch_action(
99 assoc,
102
103 if (result.is_ok() && result.value().is_success()) {
104 unsubscribes_performed_.fetch_add(1, std::memory_order_relaxed);
105 logger_->info("UPS Watch SCU: Global subscription suspended");
106 }
107
108 return result;
109}
constexpr uint16_t ups_watch_action_suspend_global
N-ACTION Type 5: Suspend Global Subscription (PS3.4 CC.2.3.5)

References logger_, send_watch_action(), unsubscribes_performed_, kcenon::pacs::services::ups_global_subscription_instance_uid, and kcenon::pacs::services::ups_watch_action_suspend_global.

Here is the call graph for this function:

◆ unsubscribe()

network::Result< ups_result > kcenon::pacs::services::ups_watch_scu::unsubscribe ( network::association & assoc,
const ups_unsubscribe_data & data )
nodiscard

Unsubscribe from UPS workitem events (N-ACTION Type 4)

Parameters
assocThe established association to use
dataThe unsubscription parameters
Returns
Result containing ups_result on success, or error

Definition at line 68 of file ups_watch_scu.cpp.

70 {
71
72 std::string sop_instance_uid = data.workitem_uid.empty()
74 : data.workitem_uid;
75
76 logger_->debug("UPS Watch SCU: Unsubscribe request" +
77 (data.workitem_uid.empty() ? std::string(" (global)") :
78 " for workitem " + data.workitem_uid));
79
80 auto result = send_watch_action(
81 assoc, sop_instance_uid, ups_watch_action_unsubscribe);
82
83 if (result.is_ok() && result.value().is_success()) {
84 unsubscribes_performed_.fetch_add(1, std::memory_order_relaxed);
85 logger_->info("UPS Watch SCU: Unsubscribe successful" +
86 (data.workitem_uid.empty() ? std::string(" (global)") :
87 " for workitem " + data.workitem_uid));
88 }
89
90 return result;
91}
constexpr uint16_t ups_watch_action_unsubscribe
N-ACTION Type 4: Unsubscribe from Receiving UPS Event Reports (PS3.4 CC.2.3.4)

References logger_, send_watch_action(), unsubscribes_performed_, kcenon::pacs::services::ups_global_subscription_instance_uid, kcenon::pacs::services::ups_watch_action_unsubscribe, and kcenon::pacs::services::ups_unsubscribe_data::workitem_uid.

Here is the call graph for this function:

◆ unsubscribes_performed()

size_t kcenon::pacs::services::ups_watch_scu::unsubscribes_performed ( ) const
nodiscardnoexcept

Definition at line 180 of file ups_watch_scu.cpp.

180 {
181 return unsubscribes_performed_.load(std::memory_order_relaxed);
182}

References unsubscribes_performed_.

Member Data Documentation

◆ config_

ups_watch_scu_config kcenon::pacs::services::ups_watch_scu::config_
private

Definition at line 264 of file ups_watch_scu.h.

Referenced by send_watch_action().

◆ event_callback_

event_received_callback kcenon::pacs::services::ups_watch_scu::event_callback_
private

Definition at line 265 of file ups_watch_scu.h.

Referenced by handle_event_report(), and set_event_received_callback().

◆ events_received_

std::atomic<size_t> kcenon::pacs::services::ups_watch_scu::events_received_ {0}
private

Definition at line 269 of file ups_watch_scu.h.

269{0};

Referenced by events_received(), handle_event_report(), and reset_statistics().

◆ logger_

std::shared_ptr<di::ILogger> kcenon::pacs::services::ups_watch_scu::logger_
private

◆ message_id_counter_

std::atomic<uint16_t> kcenon::pacs::services::ups_watch_scu::message_id_counter_ {1}
private

Definition at line 266 of file ups_watch_scu.h.

266{1};

Referenced by next_message_id().

◆ subscribes_performed_

std::atomic<size_t> kcenon::pacs::services::ups_watch_scu::subscribes_performed_ {0}
private

Definition at line 267 of file ups_watch_scu.h.

267{0};

Referenced by reset_statistics(), subscribe(), and subscribes_performed().

◆ unsubscribes_performed_

std::atomic<size_t> kcenon::pacs::services::ups_watch_scu::unsubscribes_performed_ {0}
private

Definition at line 268 of file ups_watch_scu.h.

268{0};

Referenced by reset_statistics(), suspend_global(), unsubscribe(), and unsubscribes_performed().


The documentation for this class was generated from the following files: