1use alloc::vec::Vec;
19
20use crate::desc::{
21 Access, AddressSpace, DateTimeEncoding, FieldDesc, NaDesc, PointDesc, Rational, ScaleMode,
22 ScaleRefDesc, SelectorCaseDesc, SelectorDesc, StorageType, StringPadding, StringTermination,
23 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 u8).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>| -> (u16, u8) {
214 m.map(|m| {
215 (
216 m.offset as u16,
217 if m.length_words == 0 {
218 1
219 } else {
220 m.length_words as u8
221 },
222 )
223 })
224 .unwrap_or((0, 1))
225 };
226 let (mo, mw) = sub(c.and_then(|c| c.mantissa.as_deref()));
227 let (eo, ew) = sub(c.and_then(|c| c.exponent.as_deref()));
228 return ValueKind::Composed {
229 base: c.map(|c| c.base).unwrap_or(0),
230 mantissa_offset: mo,
231 mantissa_words: mw,
232 exponent_offset: eo,
233 exponent_words: ew,
234 };
235 }
236
237 if !bufs.flags.is_empty()
238 || matches!(
239 p.value_type.as_ref().and_then(|v| v.kind.as_ref()),
240 Some(schema::value_type::Kind::Flags(_))
241 )
242 {
243 return ValueKind::Flags(&bufs.flags);
244 }
245
246 if !bufs.fields.is_empty() {
247 return ValueKind::Fields(&bufs.fields);
248 }
249
250 if storage == StorageType::StringAscii || storage == StorageType::StringUtf8 {
251 let enc = p.mapping.as_ref().and_then(|m| m.string_encoding.as_ref());
252 return ValueKind::Str {
253 padding: match enc.map(|e| e.padding()) {
254 Some(schema::Padding::Null) => StringPadding::Null,
255 Some(schema::Padding::Space) => StringPadding::Space,
256 _ => StringPadding::None,
257 },
258 termination: match enc.map(|e| e.termination()) {
259 Some(schema::Termination::NullTerminated) => StringTermination::NullTerminated,
260 _ => StringTermination::FixedLength,
261 },
262 };
263 }
264 if storage == StorageType::BytesRaw {
265 return ValueKind::Bytes;
266 }
267
268 let prim = match p.value_type.as_ref().and_then(|v| v.kind.as_ref()) {
269 Some(schema::value_type::Kind::Primitive(v)) => {
270 schema::PrimitiveType::try_from(*v).unwrap_or(schema::PrimitiveType::Unspecified)
271 }
272 _ => schema::PrimitiveType::Unspecified,
273 };
274
275 match prim {
276 schema::PrimitiveType::Bool => ValueKind::Bool,
277 schema::PrimitiveType::Datetime => ValueKind::DateTime(
278 if p.datetime.as_ref().map(|d| d.encoding()) == Some(schema::DateTimeEncoding::EpochMs)
279 {
280 DateTimeEncoding::EpochMillis
281 } else {
282 DateTimeEncoding::EpochSeconds
284 },
285 ),
286 schema::PrimitiveType::Decimal
287 | schema::PrimitiveType::Float32
288 | schema::PrimitiveType::Float64 => ValueKind::Decimal,
289 schema::PrimitiveType::Uint32 | schema::PrimitiveType::Uint64 => ValueKind::Uint,
290 schema::PrimitiveType::Int32 | schema::PrimitiveType::Int64 => ValueKind::Int,
291 _ => ValueKind::Enum,
293 }
294}