23 : logger_(logger ? std::move(logger) : di::null_logger()) {}
26 std::shared_ptr<di::ILogger> logger)
27 : logger_(logger ? std::move(logger) : di::null_logger()),
51 logger_->debug(
"UPS Watch SCU: Subscribe request" +
58 if (result.is_ok() && result.value().is_success()) {
60 logger_->info(
"UPS Watch SCU: Subscribe successful" +
76 logger_->debug(
"UPS Watch SCU: Unsubscribe request" +
83 if (result.is_ok() && result.value().is_success()) {
85 logger_->info(
"UPS Watch SCU: Unsubscribe successful" +
96 logger_->debug(
"UPS Watch SCU: Suspend global subscription");
103 if (result.is_ok() && result.value().is_success()) {
105 logger_->info(
"UPS Watch SCU: Global subscription suspended");
118 using namespace network::dimse;
121 if (event_rq.
command() != command_field::n_event_report_rq) {
124 "Expected N-EVENT-REPORT-RQ, got: " +
130 if (!event_type.has_value()) {
133 "Missing Event Type ID in N-EVENT-REPORT");
142 auto dataset_result = event_rq.
dataset();
143 if (dataset_result.is_ok()) {
145 event_type.value(), workitem_uid,
146 dataset_result.value().get());
148 event.event_type_id = event_type.value();
149 event.workitem_uid = workitem_uid;
150 event.timestamp = std::chrono::system_clock::now();
153 event.event_type_id = event_type.value();
154 event.workitem_uid = workitem_uid;
155 event.timestamp = std::chrono::system_clock::now();
160 logger_->info(
"UPS Watch SCU: Received event type " +
200 const std::string& sop_instance_uid,
201 uint16_t action_type_id) {
203 using namespace network::dimse;
205 auto start_time = std::chrono::steady_clock::now();
211 "Association not established");
219 "No accepted presentation context for UPS Watch SOP Class");
223 auto request = make_n_action_rq(
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();
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();
245 const auto& [recv_context_id, response] = recv_result.value();
248 if (response.command() != command_field::n_action_rsp) {
251 "Expected N-ACTION-RSP but received " +
252 std::string(
to_string(response.command())));
255 auto end_time = std::chrono::steady_clock::now();
256 auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
257 end_time - start_time);
262 result.
status =
static_cast<uint16_t
>(response.status());
265 if (response.command_set().contains(tag_error_comment)) {
266 result.
error_comment = response.command_set().get_string(tag_error_comment);
270 logger_->warn(
"UPS Watch SCU: N-ACTION returned status 0x" +
271 std::to_string(result.
status));
282 uint16_t event_type_id,
283 const std::string& workitem_uid,
288 event.workitem_uid = workitem_uid;
289 event.event_info = dataset;
290 event.timestamp = std::chrono::system_clock::now();
293 switch (event_type_id) {
295 event.procedure_step_state = dataset.
get_string(
300 event.cancellation_reason = dataset.
get_string(
auto get_string(dicom_tag tag, std::string_view default_value="") const -> std::string
Get the string value of an element.
bool is_established() const noexcept
Check if association is established and ready for DIMSE.
Result< std::monostate > send_dimse(uint8_t context_id, const dimse::dimse_message &msg)
Send a DIMSE message.
Result< std::pair< uint8_t, dimse::dimse_message > > receive_dimse(duration timeout=default_timeout)
Receive a DIMSE message.
std::optional< uint8_t > accepted_context_id(std::string_view abstract_syntax) const
Get the presentation context ID for an abstract syntax.
auto event_type_id() const -> std::optional< uint16_t >
Get the Event Type ID (for N-EVENT-REPORT)
auto has_dataset() const noexcept -> bool
Check if the message has an associated data set.
auto dataset() -> kcenon::pacs::Result< std::reference_wrapper< core::dicom_dataset > >
Get mutable reference to the data set.
auto affected_sop_instance_uid() const -> std::string
Get the Affected SOP Instance UID.
auto command() const noexcept -> command_field
Get the command field.
event_received_callback event_callback_
network::Result< ups_result > send_watch_action(network::association &assoc, const std::string &sop_instance_uid, uint16_t action_type_id)
size_t unsubscribes_performed() const noexcept
network::Result< ups_result > subscribe(network::association &assoc, const ups_subscribe_data &data)
Subscribe to UPS workitem events (N-ACTION Type 3)
std::atomic< size_t > unsubscribes_performed_
std::function< void( const ups_watch_event &event)> event_received_callback
Callback type for received event notifications.
size_t subscribes_performed() const noexcept
ups_watch_scu_config config_
std::shared_ptr< di::ILogger > logger_
std::atomic< size_t > events_received_
void set_event_received_callback(event_received_callback cb)
Set callback for received event notifications.
network::Result< ups_result > suspend_global(network::association &assoc)
Suspend global subscription (N-ACTION Type 5)
ups_watch_scu(std::shared_ptr< di::ILogger > logger=nullptr)
Construct UPS Watch SCU with default configuration.
std::atomic< size_t > subscribes_performed_
uint16_t next_message_id() noexcept
size_t events_received() const noexcept
void reset_statistics() noexcept
std::atomic< uint16_t > message_id_counter_
network::Result< ups_result > unsubscribe(network::association &assoc, const ups_unsubscribe_data &data)
Unsubscribe from UPS workitem events (N-ACTION Type 4)
network::Result< ups_watch_event > handle_event_report(const network::dimse::dimse_message &event_rq)
Handle an N-EVENT-REPORT-RQ received from the SCP.
static ups_watch_event parse_event_dataset(uint16_t event_type_id, const std::string &workitem_uid, const core::dicom_dataset &dataset)
DIMSE command field enumeration.
Compile-time constants for commonly used DICOM tags.
constexpr int ups_unexpected_command
constexpr int ups_context_not_accepted
constexpr int ups_invalid_action_type
constexpr int association_not_established
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 std::string_view ups_watch_sop_class_uid
UPS Watch SOP Class UID (PS3.4 Table CC.2-1)
constexpr uint16_t ups_watch_action_suspend_global
N-ACTION Type 5: Suspend Global Subscription (PS3.4 CC.2.3.5)
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...
auto to_string(mpps_status status) -> std::string_view
Convert mpps_status to DICOM string representation.
constexpr uint16_t ups_event_cancel_requested
Event Type 2: UPS Cancel Requested — performer should stop processing.
constexpr uint16_t ups_watch_action_unsubscribe
N-ACTION Type 4: Unsubscribe from Receiving UPS Event Reports (PS3.4 CC.2.3.4)
constexpr uint16_t ups_watch_action_subscribe
N-ACTION Type 3: Subscribe to Receive UPS Event Reports (PS3.4 CC.2.3.3)
Result< T > pacs_error(int code, const std::string &message, const std::string &details="")
Create a PACS error result with module context.
Result<T> type aliases and helpers for PACS system.
Result of a UPS SCU operation.
std::chrono::milliseconds elapsed
Time taken for the operation.
bool is_success() const noexcept
Check if the operation was successful.
std::string workitem_uid
Workitem SOP Instance UID.
uint16_t status
DIMSE status code (0x0000 = success)
std::string error_comment
Error comment from the SCP (if any)
Data for N-ACTION subscribe request.
std::string workitem_uid
Workitem SOP Instance UID (empty for global subscription)
Data for N-ACTION unsubscribe request.
std::string workitem_uid
Workitem SOP Instance UID (empty for global unsubscription)
Parsed UPS event notification received from SCP.
uint16_t event_type_id
Event type ID (ups_event_state_report, ups_event_cancel_requested, etc.)
std::string workitem_uid
Workitem SOP Instance UID that triggered the event.
Configuration for UPS Watch SCU service.
std::chrono::milliseconds timeout
Timeout for receiving DIMSE response.
DICOM UPS Watch SCU service (subscription client and event receiver)