1use ad_core::ndarray::NDArray;
9use ad_core::ndarray_pool::NDArrayPool;
10use ad_core::plugin::runtime::{NDPluginProcess, ParamUpdate, ProcessResult};
11use asyn_rs::error::AsynError;
12use asyn_rs::param::ParamType;
13use asyn_rs::port::PortDriverBase;
14
15#[derive(Clone, Copy, Default)]
17pub struct AttributeParams {
18 pub attr_name: usize,
19 pub value: usize,
20 pub value_sum: usize,
21 pub reset: usize,
22}
23
24pub struct AttributeProcessor {
26 attr_name: String,
27 value: f64,
28 value_sum: f64,
29 params: AttributeParams,
30}
31
32impl AttributeProcessor {
33 pub fn new(attr_name: &str) -> Self {
34 Self {
35 attr_name: attr_name.to_string(),
36 value: 0.0,
37 value_sum: 0.0,
38 params: AttributeParams::default(),
39 }
40 }
41
42 pub fn reset(&mut self) {
44 self.value_sum = 0.0;
45 }
46
47 pub fn value(&self) -> f64 {
49 self.value
50 }
51
52 pub fn value_sum(&self) -> f64 {
54 self.value_sum
55 }
56
57 pub fn attr_name(&self) -> &str {
59 &self.attr_name
60 }
61
62 pub fn set_attr_name(&mut self, name: &str) {
64 self.attr_name = name.to_string();
65 }
66
67 fn extract_value(&self, array: &NDArray) -> Option<f64> {
69 match self.attr_name.as_str() {
70 "NDArrayUniqueId" => Some(array.unique_id as f64),
71 "NDArrayTimeStamp" => Some(array.timestamp.as_f64()),
72 _ => {
73 array.attributes.get(&self.attr_name)
74 .and_then(|attr| attr.value.as_f64())
75 }
76 }
77 }
78}
79
80impl NDPluginProcess for AttributeProcessor {
81 fn process_array(&mut self, array: &NDArray, _pool: &NDArrayPool) -> ProcessResult {
82 if let Some(val) = self.extract_value(array) {
83 self.value = val;
84 self.value_sum += val;
85 }
86
87 let updates = vec![
88 ParamUpdate::float64(self.params.value, self.value),
89 ParamUpdate::float64(self.params.value_sum, self.value_sum),
90 ];
91
92 ProcessResult::sink(updates)
93 }
94
95 fn plugin_type(&self) -> &str {
96 "NDPluginAttribute"
97 }
98
99 fn register_params(&mut self, base: &mut PortDriverBase) -> Result<(), AsynError> {
100 self.params.attr_name = base.create_param("ATTR_NAME", ParamType::Octet)?;
101 self.params.value = base.create_param("ATTR_VAL", ParamType::Float64)?;
102 self.params.value_sum = base.create_param("ATTR_VAL_SUM", ParamType::Float64)?;
103 self.params.reset = base.create_param("ATTR_RESET", ParamType::Int32)?;
104 Ok(())
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111 use ad_core::attributes::{NDAttrSource, NDAttrValue, NDAttribute};
112 use ad_core::ndarray::{NDDataType, NDDimension};
113
114 fn make_array_with_attr(name: &str, value: f64, uid: i32) -> NDArray {
115 let mut arr = NDArray::new(vec![NDDimension::new(4)], NDDataType::UInt8);
116 arr.unique_id = uid;
117 arr.attributes.add(NDAttribute {
118 name: name.to_string(),
119 description: String::new(),
120 source: NDAttrSource::Driver,
121 value: NDAttrValue::Float64(value),
122 });
123 arr
124 }
125
126 #[test]
127 fn test_extract_named_attribute() {
128 let mut proc = AttributeProcessor::new("Temperature");
129 let pool = NDArrayPool::new(1_000_000);
130
131 let arr = make_array_with_attr("Temperature", 25.5, 1);
132 let result = proc.process_array(&arr, &pool);
133
134 assert!(result.output_arrays.is_empty(), "attribute plugin is a sink");
135 assert!((proc.value() - 25.5).abs() < 1e-10);
136 assert!((proc.value_sum() - 25.5).abs() < 1e-10);
137 }
138
139 #[test]
140 fn test_sum_accumulation() {
141 let mut proc = AttributeProcessor::new("Intensity");
142 let pool = NDArrayPool::new(1_000_000);
143
144 let arr1 = make_array_with_attr("Intensity", 10.0, 1);
145 proc.process_array(&arr1, &pool);
146 assert!((proc.value_sum() - 10.0).abs() < 1e-10);
147
148 let arr2 = make_array_with_attr("Intensity", 20.0, 2);
149 proc.process_array(&arr2, &pool);
150 assert!((proc.value() - 20.0).abs() < 1e-10);
151 assert!((proc.value_sum() - 30.0).abs() < 1e-10);
152
153 let arr3 = make_array_with_attr("Intensity", 5.0, 3);
154 proc.process_array(&arr3, &pool);
155 assert!((proc.value() - 5.0).abs() < 1e-10);
156 assert!((proc.value_sum() - 35.0).abs() < 1e-10);
157 }
158
159 #[test]
160 fn test_reset() {
161 let mut proc = AttributeProcessor::new("Count");
162 let pool = NDArrayPool::new(1_000_000);
163
164 let arr1 = make_array_with_attr("Count", 100.0, 1);
165 proc.process_array(&arr1, &pool);
166 assert!((proc.value_sum() - 100.0).abs() < 1e-10);
167
168 proc.reset();
169 assert!((proc.value_sum() - 0.0).abs() < 1e-10);
170 assert!((proc.value() - 100.0).abs() < 1e-10);
172
173 let arr2 = make_array_with_attr("Count", 50.0, 2);
174 proc.process_array(&arr2, &pool);
175 assert!((proc.value_sum() - 50.0).abs() < 1e-10);
176 }
177
178 #[test]
179 fn test_special_attr_unique_id() {
180 let mut proc = AttributeProcessor::new("NDArrayUniqueId");
181 let pool = NDArrayPool::new(1_000_000);
182
183 let mut arr = NDArray::new(vec![NDDimension::new(4)], NDDataType::UInt8);
184 arr.unique_id = 42;
185
186 proc.process_array(&arr, &pool);
187 assert!((proc.value() - 42.0).abs() < 1e-10);
188 }
189
190 #[test]
191 fn test_special_attr_timestamp() {
192 let mut proc = AttributeProcessor::new("NDArrayTimeStamp");
193 let pool = NDArrayPool::new(1_000_000);
194
195 let mut arr = NDArray::new(vec![NDDimension::new(4)], NDDataType::UInt8);
196 arr.timestamp = ad_core::timestamp::EpicsTimestamp { sec: 100, nsec: 500_000_000 };
197
198 proc.process_array(&arr, &pool);
199 assert!((proc.value() - 100.5).abs() < 1e-9);
200 }
201
202 #[test]
203 fn test_missing_attribute() {
204 let mut proc = AttributeProcessor::new("NonExistent");
205 let pool = NDArrayPool::new(1_000_000);
206
207 let arr = NDArray::new(vec![NDDimension::new(4)], NDDataType::UInt8);
208 proc.process_array(&arr, &pool);
209
210 assert!((proc.value() - 0.0).abs() < 1e-10);
212 assert!((proc.value_sum() - 0.0).abs() < 1e-10);
213 }
214
215 #[test]
216 fn test_string_attribute_ignored() {
217 let mut proc = AttributeProcessor::new("Label");
218 let pool = NDArrayPool::new(1_000_000);
219
220 let mut arr = NDArray::new(vec![NDDimension::new(4)], NDDataType::UInt8);
221 arr.attributes.add(NDAttribute {
222 name: "Label".to_string(),
223 description: String::new(),
224 source: NDAttrSource::Driver,
225 value: NDAttrValue::String("hello".to_string()),
226 });
227
228 proc.process_array(&arr, &pool);
229 assert!((proc.value() - 0.0).abs() < 1e-10);
231 }
232
233 #[test]
234 fn test_int32_attribute() {
235 let mut proc = AttributeProcessor::new("Counter");
236 let pool = NDArrayPool::new(1_000_000);
237
238 let mut arr = NDArray::new(vec![NDDimension::new(4)], NDDataType::UInt8);
239 arr.attributes.add(NDAttribute {
240 name: "Counter".to_string(),
241 description: String::new(),
242 source: NDAttrSource::Driver,
243 value: NDAttrValue::Int32(7),
244 });
245
246 proc.process_array(&arr, &pool);
247 assert!((proc.value() - 7.0).abs() < 1e-10);
248 }
249
250 #[test]
251 fn test_set_attr_name() {
252 let mut proc = AttributeProcessor::new("A");
253 assert_eq!(proc.attr_name(), "A");
254
255 proc.set_attr_name("B");
256 assert_eq!(proc.attr_name(), "B");
257
258 let pool = NDArrayPool::new(1_000_000);
259 let arr = make_array_with_attr("B", 99.0, 1);
260 proc.process_array(&arr, &pool);
261 assert!((proc.value() - 99.0).abs() < 1e-10);
262 }
263}