1use alloc::vec::Vec;
19
20use crate::desc::{
21 Access, AddressSpace, ComposedSub, DateTimeEncoding, FieldDesc, NaDesc, PointDesc, Rational,
22 ScaleMode, ScaleRefDesc, SelectorCaseDesc, SelectorDesc, StorageType, StringPadding,
23 StringTermination, ValueKind, WriteDesc,
24};
25use crate::schema;
26
27#[derive(Debug, Default)]
30pub struct DescBufs<'a> {
31 flags: Vec<(u8, &'a str)>,
32 fields: Vec<FieldDesc<'a>>,
33 cases: Vec<SelectorCaseDesc>,
34 na: Vec<NaDesc<'a>>,
35}
36
37pub fn desc_bufs(p: &schema::Point) -> DescBufs<'_> {
39 let mut b = DescBufs::default();
40
41 if let Some(schema::value_type::Kind::Flags(fl)) =
42 p.value_type.as_ref().and_then(|v| v.kind.as_ref())
43 {
44 for (bit, name) in &fl.bits {
46 b.flags.push((*bit as u8, name.as_str()));
47 }
48 }
49
50 for f in &p.bit_fields {
52 b.fields.push(FieldDesc {
53 id: &f.field_id,
54 bit_offset: f.bit_offset,
55 bit_length: f.bit_length,
56 });
57 }
58 for f in &p.fields {
59 b.fields.push(FieldDesc {
60 id: &f.field_id,
61 bit_offset: f.bit_offset,
62 bit_length: f.bit_length,
63 });
64 }
65
66 if let Some(sel) = &p.selector_ref {
67 for (key, c) in &sel.cases {
68 b.cases.push(SelectorCaseDesc {
69 key: *key,
70 scale: c.scale.as_ref().map(rational),
71 offset: c.offset.as_ref().map(rational),
72 });
73 }
74 }
75
76 for na in &p.na_values {
77 b.na.push(NaDesc {
78 raw: na.raw,
79 meaning: &na.meaning,
80 });
81 }
82
83 b
84}
85
86pub fn point_desc<'a>(
89 p: &'a schema::Point,
90 block_space: schema::AddressSpace,
91 bufs: &'a DescBufs<'a>,
92) -> PointDesc<'a> {
93 let m = p.mapping.as_ref();
94 let t = p.transform.as_ref();
95
96 let space = m
97 .map(|m| m.space())
98 .filter(|s| *s != schema::AddressSpace::Unspecified)
99 .unwrap_or(block_space);
100
101 let storage = storage_type(p.storage_type());
102
103 PointDesc {
104 id: &p.point_id,
105 space: address_space(space),
106 offset: m.map(|m| m.offset as u16).unwrap_or(0),
107 model_relative_offset: m.map(|m| m.model_relative_offset as u16).unwrap_or(0),
108 length_words: m.map(|m| m.length_words as u16).unwrap_or(0),
109 storage,
110 value: value_kind(p, storage, bufs),
111 byte_big: m
112 .map(|m| m.byte_order() != schema::ByteOrder::LittleEndian)
113 .unwrap_or(true),
114 word_big: m
115 .map(|m| m.word_order() != schema::WordOrder::WordLittleEndian)
116 .unwrap_or(true),
117 scale: t.and_then(|t| t.scale.as_ref()).map(rational),
118 offset_add: t.and_then(|t| t.offset.as_ref()).map(rational),
119 scale_ref: t.and_then(|t| t.scale_ref.as_ref()).map(|sr| ScaleRefDesc {
120 point_id: &sr.point_id,
121 mode: if sr.mode() == schema::ScaleMode::Multiply {
122 ScaleMode::Multiply
123 } else {
124 ScaleMode::Pow10
125 },
126 denominator: sr.denominator,
127 }),
128 selector: p.selector_ref.as_ref().map(|sel| SelectorDesc {
129 point_id: &sel.point_id,
130 cases: &bufs.cases,
131 }),
132 na: &bufs.na,
133 access: match p.access() {
134 schema::AccessMode::WriteOnly => Access::WriteOnly,
135 schema::AccessMode::ReadWrite => Access::ReadWrite,
136 schema::AccessMode::Command => Access::Command,
137 _ => Access::ReadOnly,
138 },
139 write: p
140 .write
141 .as_ref()
142 .and_then(|w| w.constraints.as_ref())
143 .map(|c| WriteDesc {
144 min: c.min_value.as_ref().map(rational),
145 max: c.max_value.as_ref().map(rational),
146 step: c.step.as_ref().map(rational),
147 allowed: &c.allowed_values,
148 }),
149 }
150}
151
152pub fn point_words(p: &schema::Point) -> usize {
154 let lw = p.mapping.as_ref().map(|m| m.length_words).unwrap_or(0);
155 if lw > 0 {
156 lw as usize
157 } else {
158 storage_type(p.storage_type()).default_words()
159 }
160}
161
162fn rational(r: &schema::Rational) -> Rational {
163 Rational {
164 num: r.numerator,
165 den: r.denominator,
166 }
167}
168
169fn address_space(s: schema::AddressSpace) -> AddressSpace {
170 match s {
171 schema::AddressSpace::Coil => AddressSpace::Coil,
172 schema::AddressSpace::DiscreteInput => AddressSpace::DiscreteInput,
173 schema::AddressSpace::InputRegister => AddressSpace::InputRegister,
174 _ => AddressSpace::HoldingRegister,
175 }
176}
177
178fn storage_type(s: schema::StorageType) -> StorageType {
179 match s {
180 schema::StorageType::Bit => StorageType::Bit,
181 schema::StorageType::U16 => StorageType::U16,
182 schema::StorageType::S16 => StorageType::S16,
183 schema::StorageType::U24 => StorageType::U24,
184 schema::StorageType::U32 => StorageType::U32,
185 schema::StorageType::S32 => StorageType::S32,
186 schema::StorageType::U48 => StorageType::U48,
187 schema::StorageType::S48 => StorageType::S48,
188 schema::StorageType::U64 => StorageType::U64,
189 schema::StorageType::S64 => StorageType::S64,
190 schema::StorageType::Ieee754F32 => StorageType::F32,
191 schema::StorageType::Ieee754F64 => StorageType::F64,
192 schema::StorageType::StringAscii => StorageType::StringAscii,
193 schema::StorageType::StringUtf8 => StorageType::StringUtf8,
194 schema::StorageType::BytesRaw => StorageType::BytesRaw,
195 schema::StorageType::Bcd => StorageType::Bcd,
196 schema::StorageType::Composed => StorageType::Composed,
197 _ => StorageType::Unspecified,
200 }
201}
202
203fn value_kind<'a>(
204 p: &'a schema::Point,
205 storage: StorageType,
206 bufs: &'a DescBufs<'a>,
207) -> ValueKind<'a> {
208 if storage == StorageType::Composed || p.mapping.as_ref().is_some_and(|m| m.composed.is_some())
211 {
212 let c = p.mapping.as_ref().and_then(|m| m.composed.as_deref());
213 let sub = |m: Option<&schema::Mapping>| -> ComposedSub {
214 let Some(m) = m else {
215 return ComposedSub {
216 offset: 0,
217 words: 1,
218 bit_offset: 0,
219 bit_length: 0,
220 width_bits: 16,
221 signed: true,
222 };
223 };
224 let words = if m.length_words == 0 {
225 1
226 } else {
227 m.length_words as u8
228 };
229 let st = storage_type(m.storage_type());
233 ComposedSub {
234 offset: m.offset as u16,
235 words,
236 bit_offset: m.bit_offset as u8,
237 bit_length: m.bit_length as u8,
238 width_bits: st.bits(words as usize) as u8,
239 signed: st == StorageType::Unspecified || st.signed(),
240 }
241 };
242 return ValueKind::Composed {
243 base: c.map(|c| c.base).unwrap_or(0),
244 mantissa: sub(c.and_then(|c| c.mantissa.as_deref())),
245 exponent: sub(c.and_then(|c| c.exponent.as_deref())),
246 };
247 }
248
249 if !bufs.flags.is_empty()
250 || matches!(
251 p.value_type.as_ref().and_then(|v| v.kind.as_ref()),
252 Some(schema::value_type::Kind::Flags(_))
253 )
254 {
255 return ValueKind::Flags(&bufs.flags);
256 }
257
258 if !bufs.fields.is_empty() {
259 return ValueKind::Fields(&bufs.fields);
260 }
261
262 if storage == StorageType::StringAscii || storage == StorageType::StringUtf8 {
263 let enc = p.mapping.as_ref().and_then(|m| m.string_encoding.as_ref());
264 return ValueKind::Str {
265 padding: match enc.map(|e| e.padding()) {
266 Some(schema::Padding::Null) => StringPadding::Null,
267 Some(schema::Padding::Space) => StringPadding::Space,
268 _ => StringPadding::None,
269 },
270 termination: match enc.map(|e| e.termination()) {
271 Some(schema::Termination::NullTerminated) => StringTermination::NullTerminated,
272 _ => StringTermination::FixedLength,
273 },
274 };
275 }
276 if storage == StorageType::BytesRaw {
277 return ValueKind::Bytes;
278 }
279
280 let prim = match p.value_type.as_ref().and_then(|v| v.kind.as_ref()) {
281 Some(schema::value_type::Kind::Primitive(v)) => {
282 schema::PrimitiveType::try_from(*v).unwrap_or(schema::PrimitiveType::Unspecified)
283 }
284 _ => schema::PrimitiveType::Unspecified,
285 };
286
287 match prim {
288 schema::PrimitiveType::Bool => ValueKind::Bool,
289 schema::PrimitiveType::Datetime => ValueKind::DateTime(
290 if p.datetime.as_ref().map(|d| d.encoding()) == Some(schema::DateTimeEncoding::EpochMs)
291 {
292 DateTimeEncoding::EpochMillis
293 } else {
294 DateTimeEncoding::EpochSeconds
296 },
297 ),
298 schema::PrimitiveType::Decimal
299 | schema::PrimitiveType::Float32
300 | schema::PrimitiveType::Float64 => ValueKind::Decimal,
301 schema::PrimitiveType::Uint32 | schema::PrimitiveType::Uint64 => ValueKind::Uint,
302 schema::PrimitiveType::Int32 | schema::PrimitiveType::Int64 => ValueKind::Int,
303 _ => ValueKind::Enum,
305 }
306}