1use serde::Deserialize;
2use std::{
3 convert::TryInto,
4 io::{Error, ErrorKind, Result},
5};
6
7mod conversions;
8
9#[allow(non_snake_case)]
10#[derive(Debug, Deserialize, PartialEq)]
11pub struct EtherCATInfo {
12 Version: Option<String>,
13 InfoReference: Option<String>,
14 Vendor: Vendor,
15 Descriptions: Option<Descriptions>,
16 Modules: Option<Modules>,
17}
18
19#[allow(non_snake_case)]
20#[derive(Debug, Deserialize, PartialEq)]
21struct Vendor {
22 FileVersion: Option<u32>,
23 Id: String,
24 Name: Option<String>,
25 Comment: Option<String>,
26 URL: Option<String>,
27 DescriptionURL: Option<String>,
28 Image16x14: Option<String>,
29 ImageFile16x14: Option<String>,
30 ImageData16x14: Option<String>,
31}
32
33#[allow(non_snake_case)]
34#[derive(Debug, Deserialize, PartialEq)]
35pub struct Descriptions {
36 Groups: Option<Groups>,
37 Devices: Devices,
38 Modules: Option<Modules>,
39}
40
41#[allow(non_snake_case)]
42#[derive(Debug, Deserialize, PartialEq)]
43pub struct Groups {
44 #[serde(rename = "$value")]
45 items: Option<Vec<Group>>,
46}
47
48#[allow(non_snake_case)]
49#[derive(Debug, Deserialize, PartialEq)]
50pub struct Devices {
51 #[serde(rename = "$value")]
52 items: Option<Vec<Device>>,
53}
54
55#[allow(non_snake_case)]
56#[derive(Debug, Deserialize, PartialEq)]
57pub struct Modules {
58 #[serde(rename = "$value")]
59 items: Option<Vec<Module>>,
60}
61
62#[allow(non_snake_case)]
63#[derive(Debug, Deserialize, PartialEq)]
64pub struct Group {
65 SortOrder: Option<i32>,
66 ParentGroup: Option<String>,
67 #[serde(rename = "$value")]
68 items: Vec<GroupProperty>,
69}
70
71#[allow(non_snake_case)]
72#[derive(Debug, Deserialize, PartialEq)]
73pub enum GroupProperty {
74 Type(String),
75 Name(Name),
76 Comment(String),
77 Image16x14(String),
78 ImageFile16x14(String),
79 ImageData16x14(String),
80}
81
82#[allow(non_snake_case)]
83#[derive(Debug, Deserialize, PartialEq)]
84pub struct Device {
85 Physics: Option<String>,
86 #[serde(rename = "$value")]
87 items: Vec<DeviceProperty>,
88}
89
90#[allow(non_snake_case)]
91#[derive(Debug, Deserialize, PartialEq)]
92pub struct Name {
93 LcId: Option<String>,
94 #[serde(rename = "$value")]
95 value: String,
96}
97
98#[allow(non_snake_case)]
99#[derive(Debug, Deserialize, PartialEq)]
100pub enum DeviceProperty {
101 Type(DeviceType),
102 Name(Name),
103 RxPdo(Vec<RxPdo>),
104 TxPdo(Vec<TxPdo>),
105 Sm(Vec<Sm>),
106 Info {
107 },
109 HideType {
110 },
112 GroupType {
113 },
115 URL {
116 },
118 Profile {
119 },
121 Eeprom {
122 },
124 Fmmu {
125 },
127 Image16x14(String),
128 ImageFile16x14(String),
129 ImageData16x14(String),
130 Mailbox {
131 },
133 Dc {
134 },
136 Slots {
137 },
139}
140
141#[allow(non_snake_case)]
142#[derive(Debug, Deserialize, PartialEq)]
143pub struct DeviceType {
144 ModulePdoGroup: Option<String>,
145 ProductCode: String,
146 RevisionNo: String,
147 #[serde(rename = "$value")]
148 Description: String,
149}
150
151#[allow(non_snake_case)]
152#[derive(Debug, Clone, Deserialize, PartialEq)]
153pub struct Sm {
154 Enable: Option<u8>,
155 StartAddress: String,
156 ControlByte: String,
157 DefaultSize: Option<String>,
158}
159
160#[allow(non_snake_case)]
161#[derive(Debug, Clone, Deserialize, PartialEq)]
162pub struct Entry {
163 Index: Index,
164 SubIndex: Option<String>,
165 BitLen: usize,
166 Name: Option<String>,
167 DataType: Option<String>,
168}
169
170pub type RxPdo = Pdo;
171pub type TxPdo = Pdo;
172
173#[allow(non_snake_case)]
174#[derive(Debug, Clone, Deserialize, PartialEq)]
175pub struct Pdo {
176 Sm: u8,
177 Fixed: Option<String>,
178 Mandatory: Option<String>,
179 Index: Index,
180 Name: Option<String>,
181 Entry: Vec<Entry>,
182}
183
184#[allow(non_snake_case)]
185#[derive(Debug, Clone, Deserialize, PartialEq)]
186pub struct Index {
187 DependOnSlot: Option<usize>,
188 #[serde(rename = "$value")]
189 value: String,
190}
191
192#[allow(non_snake_case)]
193#[derive(Debug, Deserialize, PartialEq)]
194pub struct Module {
195 Type: String,
196 Name: Option<String>,
197 TxPdo: Option<Pdo>,
198 RxPdo: Option<Pdo>,
199 Mailbox: Mailbox,
200 Profile: Profile,
201}
202
203#[allow(non_snake_case)]
204#[derive(Debug, Deserialize, PartialEq)]
205pub struct Profile {
206 }
208
209#[allow(non_snake_case)]
210#[derive(Debug, Deserialize, PartialEq)]
211pub struct Mailbox {
212 }
214
215#[cfg(test)]
216mod tests {
217 use super::*;
218 use serde_xml_rs::from_str;
219 use std::{fs::File, io::prelude::*};
220
221 #[test]
222 fn ethercat_info() {
223 let s = r##"
224 <EtherCATInfo Version="1.11" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="EtherCATInfo.xsd">
225 <InfoReference>FooBar.xml</InfoReference>
226 <Vendor FileVersion="0099">
227 <Id>#x00000000</Id>
228 <Name>Vendor Foo</Name>
229 <ImageData16x14>7D</ImageData16x14>
230 </Vendor>
231 <Descriptions>
232 <Groups/>
233 <Devices/>
234 </Descriptions>
235 </EtherCATInfo>
236 "##;
237 let info: EtherCATInfo = from_str(s).unwrap();
238
239 assert_eq!(
240 info,
241 EtherCATInfo {
242 Version: Some("1.11".to_string()),
243 InfoReference: Some("FooBar.xml".to_string()),
244 Vendor: Vendor {
245 FileVersion: Some(99),
246 Id: "#x00000000".to_string(),
247 Name: Some("Vendor Foo".to_string()),
248 Comment: None,
249 URL: None,
250 DescriptionURL: None,
251 Image16x14: None,
252 ImageFile16x14: None,
253 ImageData16x14: Some("7D".to_string()),
254 },
255 Descriptions: Some(Descriptions {
256 Groups: Some(Groups { items: None }),
257 Devices: Devices { items: None },
258 Modules: None,
259 }),
260 Modules: None,
261 }
262 );
263 }
264
265 #[test]
266 fn ethercat_info_crated_by_beckhoff() {
267 let mut file = File::open("tests/fixtures/Beckhoff_EK11xx.xml").unwrap();
268 let mut xml_string = String::new();
269 file.read_to_string(&mut xml_string).unwrap();
270 let _: EtherCATInfo = from_str(&xml_string).unwrap();
271 }
272
273 #[test]
274 fn ethercat_info_crated_by_weidmueller() {
275 let mut file = File::open("tests/fixtures/Weidmueller_UR20_FBC.xml").unwrap();
276 let mut xml_string = String::new();
277 file.read_to_string(&mut xml_string).unwrap();
278 let _: EtherCATInfo = from_str(&xml_string).unwrap();
279 }
280
281 #[test]
282 fn ethercat_info_crated_by_weidmueller_module_information() {
283 let mut file = File::open("tests/fixtures/Weidmueller_UR20_IO.xml").unwrap();
284 let mut xml_string = String::new();
285 file.read_to_string(&mut xml_string).unwrap();
286 let _: EtherCATInfo = from_str(&xml_string).unwrap();
287 }
288
289 #[test]
290 fn ethercat_info_crated_by_igh() {
291 let mut file = File::open("tests/fixtures/Weidmueller_UR20_FBC_from_IgH.xml").unwrap();
292 let mut xml_string = String::new();
293 file.read_to_string(&mut xml_string).unwrap();
294 let _: EtherCATInfo = from_str(&xml_string).unwrap();
295 }
296
297 #[test]
298 fn vendor() {
299 let s = r##"
300 <Vendor FileVersion="0045">
301 <Id>#x00000999</Id>
302 <Name>Vendor Name</Name>
303 <ImageData16x14>7D7D7D7</ImageData16x14>
304 </Vendor>"##;
305 let vendor: Vendor = from_str(s).unwrap();
306
307 assert_eq!(
308 vendor,
309 Vendor {
310 FileVersion: Some(45),
311 Id: "#x00000999".to_string(),
312 Name: Some("Vendor Name".to_string()),
313 Comment: None,
314 URL: None,
315 DescriptionURL: None,
316 Image16x14: None,
317 ImageFile16x14: None,
318 ImageData16x14: Some("7D7D7D7".to_string()),
319 }
320 )
321 }
322
323 #[test]
324 fn descriptions() {
325 let s = r##"
326 <Descriptions>
327 <Groups>
328 <Group SortOrder="0">
329 <Type>Coupler</Type>
330 <Name>Coupler</Name>
331 <ImageData16x14>44</ImageData16x14>
332 </Group>
333 </Groups>
334 <Devices></Devices>
335 </Descriptions>"##;
336 let descriptions: Descriptions = from_str(s).unwrap();
337 assert_eq!(
338 descriptions,
339 Descriptions {
340 Groups: Some(Groups {
341 items: Some(vec![Group {
342 SortOrder: Some(0),
343 ParentGroup: None,
344 items: vec![
345 GroupProperty::Type("Coupler".to_string()),
346 GroupProperty::Name(Name {
347 LcId: None,
348 value: "Coupler".to_string(),
349 }),
350 GroupProperty::ImageData16x14("44".to_string()),
351 ]
352 }]),
353 }),
354 Devices: Devices { items: None },
355 Modules: None,
356 }
357 );
358 }
359
360 #[test]
361 fn entry() {
362 let s = r##"
363 <Entry>
364 <Index>#xf200</Index>
365 <SubIndex>2</SubIndex>
366 <BitLen>1</BitLen>
367 <Name></Name>
368 <DataType>BOOL</DataType>
369 </Entry>"##;
370 let entry: Entry = from_str(s).unwrap();
371 assert_eq!(
372 entry,
373 Entry {
374 Index: Index {
375 DependOnSlot: None,
376 value: "#xf200".to_string(),
377 },
378 SubIndex: Some("2".into()),
379 BitLen: 1,
380 Name: Some("".to_string()),
381 DataType: Some("BOOL".to_string()),
382 }
383 );
384 }
385
386 #[test]
387 fn rx_pdo() {
388 let s = r##"
389 <RxPdo Sm="2" Fixed="1" Mandatory="true">
390 <Index>#x16ff</Index>
391 <Name></Name>
392 <Entry>
393 <Index>#xf200</Index>
394 <SubIndex>3</SubIndex>
395 <BitLen>1</BitLen>
396 <Name></Name>
397 <DataType>BOOL</DataType>
398 </Entry>
399 </RxPdo>"##;
400 let pdo: RxPdo = from_str(s).unwrap();
401 assert_eq!(
402 pdo,
403 RxPdo {
404 Sm: 2,
405 Fixed: Some("1".to_string()),
406 Mandatory: Some("true".to_string()),
407 Index: Index {
408 DependOnSlot: None,
409 value: "#x16ff".to_string(),
410 },
411 Name: Some("".to_string()),
412 Entry: vec![Entry {
413 Index: Index {
414 DependOnSlot: None,
415 value: "#xf200".to_string(),
416 },
417 SubIndex: Some("3".into()),
418 BitLen: 1,
419 Name: Some("".to_string()),
420 DataType: Some("BOOL".to_string()),
421 }]
422 }
423 );
424 }
425
426 #[test]
427 fn device() {
428 let s = r##"
429 <Device>
430 <Type ProductCode="#x45" RevisionNo="#x001">Foo</Type>
431 <Name>Bar</Name>
432 <Sm Enable="1" StartAddress="#x1000" ControlByte="#x26" DefaultSize="512" />
433 <Sm Enable="1" StartAddress="#x1400" ControlByte="#x22" DefaultSize="#x200" />
434 <Sm StartAddress="#x1800" ControlByte="#x64" />
435 <Sm Enable="0" StartAddress="#x2400" ControlByte="#x20" DefaultSize="0" />
436 </Device>"##;
437 let device: Device = from_str(s).unwrap();
438 assert_eq!(
439 device,
440 Device {
441 Physics: None,
442 items: vec![
443 DeviceProperty::Type(DeviceType {
444 Description: "Foo".to_string(),
445 ModulePdoGroup: None,
446 ProductCode: "#x45".to_string(),
447 RevisionNo: "#x001".to_string(),
448 }),
449 DeviceProperty::Name(Name {
450 LcId: None,
451 value: "Bar".to_string()
452 }),
453 DeviceProperty::Sm(vec![
454 Sm {
455 Enable: Some(1),
456 StartAddress: "#x1000".to_string(),
457 ControlByte: "#x26".to_string(),
458 DefaultSize: Some("512".to_string()),
459 },
460 Sm {
461 Enable: Some(1),
462 StartAddress: "#x1400".to_string(),
463 ControlByte: "#x22".to_string(),
464 DefaultSize: Some("#x200".to_string()),
465 },
466 Sm {
467 Enable: None,
468 StartAddress: "#x1800".to_string(),
469 ControlByte: "#x64".to_string(),
470 DefaultSize: None,
471 },
472 Sm {
473 Enable: Some(0),
474 StartAddress: "#x2400".to_string(),
475 ControlByte: "#x20".to_string(),
476 DefaultSize: Some("0".to_string()),
477 }
478 ]),
479 ]
480 }
481 );
482 }
483}