Skip to main content

ethercat_esi/parser/
mod.rs

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        // TODO
108    },
109    HideType {
110        // TODO
111    },
112    GroupType {
113        // TODO
114    },
115    URL {
116        // TODO
117    },
118    Profile {
119        // TODO
120    },
121    Eeprom {
122        // TODO
123    },
124    Fmmu {
125        // TODO
126    },
127    Image16x14(String),
128    ImageFile16x14(String),
129    ImageData16x14(String),
130    Mailbox {
131        // TODO
132    },
133    Dc {
134        // TODO
135    },
136    Slots {
137        // TODO
138    },
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    // TODO
207}
208
209#[allow(non_snake_case)]
210#[derive(Debug, Deserialize, PartialEq)]
211pub struct Mailbox {
212    // TODO
213}
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}