1use std::{
2 sync::{Arc, OnceLock},
3 time::{Duration, Instant},
4};
5
6use opcua_core::sync::Mutex;
7use opcua_types::{
8 AttributeId, BuildInfo, DataValue, DateTime, ExtensionObject, LocalizedText, MonitoringMode,
9 NodeId, ServerState, ServerStatusDataType, VariableId,
10};
11
12use crate::{node_manager::SyncSampler, SubscriptionCache};
13
14pub struct ServerStatusWrapper {
18 status: Arc<Mutex<ServerStatusDataType>>,
19 subscriptions: Arc<SubscriptionCache>,
20 #[allow(unused)]
21 sampler: SyncSampler,
22 shutdown: Arc<OnceLock<ShutdownTarget>>,
23}
24
25struct ShutdownTarget {
26 reason: LocalizedText,
27 deadline: Instant,
28 #[allow(unused)]
29 time: DateTime,
30}
31
32#[allow(unused)]
33impl ServerStatusWrapper {
34 pub(crate) fn new(build_info: BuildInfo, subscriptions: Arc<SubscriptionCache>) -> Self {
35 let sampler = SyncSampler::new();
36 sampler.run(Duration::from_secs(1), subscriptions.clone());
37
38 Self {
39 status: Arc::new(Mutex::new(ServerStatusDataType {
40 start_time: DateTime::null(),
41 current_time: DateTime::now(),
42 state: opcua_types::ServerState::Shutdown,
43 build_info,
44 seconds_till_shutdown: 0,
45 shutdown_reason: LocalizedText::null(),
46 })),
47 subscriptions,
48 sampler,
49 shutdown: Arc::new(OnceLock::new()),
50 }
51 }
52
53 pub(crate) fn get_managed_id(&self, id: &NodeId) -> Option<VariableId> {
54 let Ok(var_id) = id.as_variable_id() else {
55 return None;
56 };
57 if matches!(
58 var_id,
59 VariableId::Server_ServerStatus
60 | VariableId::Server_ServerStatus_CurrentTime
61 | VariableId::Server_ServerStatus_SecondsTillShutdown
62 | VariableId::Server_ServerStatus_ShutdownReason
63 ) {
64 Some(var_id)
65 } else {
66 None
67 }
68 }
69
70 pub(crate) fn subscribe_to_component(
71 &self,
72 id: VariableId,
73 mode: MonitoringMode,
74 handle: crate::MonitoredItemHandle,
75 sampling_interval: Duration,
76 ) {
77 let status = self.status.clone();
78 let shutdown = self.shutdown.clone();
79 match id {
80 VariableId::Server_ServerStatus => self.sampler.add_sampler(
81 id.into(),
82 AttributeId::Value,
83 move || {
84 let mut status = status.lock();
85 status.current_time = DateTime::now();
86 Some(DataValue::new_now(ExtensionObject::from_message(
87 status.clone(),
88 )))
89 },
90 mode,
91 handle,
92 sampling_interval,
93 ),
94 VariableId::Server_ServerStatus_CurrentTime => self.sampler.add_sampler(
95 id.into(),
96 AttributeId::Value,
97 || Some(DataValue::new_now(DateTime::now())),
98 mode,
99 handle,
100 sampling_interval,
101 ),
102 VariableId::Server_ServerStatus_SecondsTillShutdown => self.sampler.add_sampler(
103 id.into(),
104 AttributeId::Value,
105 move || {
106 if let Some(v) = shutdown.get() {
107 let now = Instant::now();
108 let left = if now < v.deadline {
109 (v.deadline - now).as_secs()
110 } else {
111 0
112 };
113 Some(DataValue::new_now(left as u32))
114 } else {
115 None
116 }
117 },
118 mode,
119 handle,
120 sampling_interval,
121 ),
122 VariableId::Server_ServerStatus_ShutdownReason => self.sampler.add_sampler(
123 id.into(),
124 AttributeId::Value,
125 move || {
126 shutdown
127 .get()
128 .map(|v| DataValue::new_at(v.reason.clone(), v.time))
129 },
130 mode,
131 handle,
132 sampling_interval,
133 ),
134 _ => (),
135 }
136 }
137
138 pub(crate) fn sampler(&self) -> &SyncSampler {
139 &self.sampler
140 }
141
142 fn notify_status_object_change(&self) {
143 self.subscriptions.maybe_notify(
144 [(&VariableId::Server_ServerStatus.into(), AttributeId::Value)].into_iter(),
145 |_, _, n, _| {
146 if n.has_range() {
147 None
148 } else {
149 Some(DataValue::new_now(ExtensionObject::from_message(
150 self.status.lock().clone(),
151 )))
152 }
153 },
154 )
155 }
156
157 pub fn set_state(&self, state: ServerState) {
160 self.status.lock().state = state;
161 self.subscriptions.notify_data_change(
162 [(
163 DataValue::new_now(state as i32),
164 &VariableId::Server_ServerStatus_State.into(),
165 AttributeId::Value,
166 )]
167 .into_iter(),
168 );
169 self.notify_status_object_change();
170 }
171
172 pub(crate) fn set_start_time(&self, time: DateTime) {
173 self.status.lock().start_time = time;
174 }
175
176 pub(crate) fn set_server_started(&self) {
177 self.set_state(ServerState::Running);
178 self.set_start_time(DateTime::now());
179 }
180
181 pub(crate) fn schedule_shutdown(&self, reason: LocalizedText, deadline: Instant) {
182 let _ = self.shutdown.set(ShutdownTarget {
183 time: DateTime::now(),
184 reason,
185 deadline,
186 });
187 }
188
189 pub fn build_info(&self) -> BuildInfo {
191 self.status.lock().build_info.clone()
192 }
193
194 pub fn state(&self) -> ServerState {
196 self.status.lock().state
197 }
198
199 pub fn start_time(&self) -> DateTime {
201 self.status.lock().start_time
202 }
203
204 pub fn seconds_till_shutdown(&self) -> Option<u32> {
206 self.shutdown.get().map(|v| {
207 let now = Instant::now();
208 let left = if now < v.deadline {
209 (v.deadline - now).as_secs()
210 } else {
211 0
212 };
213 left as u32
214 })
215 }
216
217 pub fn shutdown_reason(&self) -> Option<LocalizedText> {
219 self.shutdown.get().map(|v| v.reason.clone())
220 }
221
222 pub fn full_status_obj(&self) -> ExtensionObject {
224 ExtensionObject::from_message(self.status.lock().clone())
225 }
226}