├── .gitattributes
├── .gitignore
├── MTCClient
├── Client.cs
├── Component.cs
├── DataItem.cs
├── DataItemSample.cs
├── Device.cs
├── IClient.cs
├── IComponent.cs
├── IDataItem.cs
├── IDataItemSample.cs
├── IDevice.cs
├── MTCItemBase.cs
├── MTConnectSharp.csproj
├── ParseUtility.cs
└── packages.config
├── MTConnectSharp.sln
├── README.md
└── TestConsoleApp
├── App.config
├── ExampleConsoleApp.csproj
└── Program.cs
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.sln.docstates
8 |
9 | # Build results
10 | [Dd]ebug/
11 | [Dd]ebugPublic/
12 | [Rr]elease/
13 | x64/
14 | build/
15 | bld/
16 | [Bb]in/
17 | [Oo]bj/
18 |
19 | # Roslyn cache directories
20 | *.ide/
21 |
22 | # MSTest test Results
23 | [Tt]est[Rr]esult*/
24 | [Bb]uild[Ll]og.*
25 |
26 | #NUNIT
27 | *.VisualState.xml
28 | TestResult.xml
29 |
30 | # Build Results of an ATL Project
31 | [Dd]ebugPS/
32 | [Rr]eleasePS/
33 | dlldata.c
34 |
35 | *_i.c
36 | *_p.c
37 | *_i.h
38 | *.ilk
39 | *.meta
40 | *.obj
41 | *.pch
42 | *.pdb
43 | *.pgc
44 | *.pgd
45 | *.rsp
46 | *.sbr
47 | *.tlb
48 | *.tli
49 | *.tlh
50 | *.tmp
51 | *.tmp_proj
52 | *.log
53 | *.vspscc
54 | *.vssscc
55 | .builds
56 | *.pidb
57 | *.svclog
58 | *.scc
59 |
60 | # Chutzpah Test files
61 | _Chutzpah*
62 |
63 | # Visual C++ cache files
64 | ipch/
65 | *.aps
66 | *.ncb
67 | *.opensdf
68 | *.sdf
69 | *.cachefile
70 |
71 | # Visual Studio profiler
72 | *.psess
73 | *.vsp
74 | *.vspx
75 |
76 | # TFS 2012 Local Workspace
77 | $tf/
78 |
79 | # Guidance Automation Toolkit
80 | *.gpState
81 |
82 | # ReSharper is a .NET coding add-in
83 | _ReSharper*/
84 | *.[Rr]e[Ss]harper
85 | *.DotSettings.user
86 |
87 | # JustCode is a .NET coding addin-in
88 | .JustCode
89 |
90 | # TeamCity is a build add-in
91 | _TeamCity*
92 |
93 | # DotCover is a Code Coverage Tool
94 | *.dotCover
95 |
96 | # NCrunch
97 | _NCrunch_*
98 | .*crunch*.local.xml
99 |
100 | # MightyMoose
101 | *.mm.*
102 | AutoTest.Net/
103 |
104 | # Web workbench (sass)
105 | .sass-cache/
106 |
107 | # Installshield output folder
108 | [Ee]xpress/
109 |
110 | # DocProject is a documentation generator add-in
111 | DocProject/buildhelp/
112 | DocProject/Help/*.HxT
113 | DocProject/Help/*.HxC
114 | DocProject/Help/*.hhc
115 | DocProject/Help/*.hhk
116 | DocProject/Help/*.hhp
117 | DocProject/Help/Html2
118 | DocProject/Help/html
119 |
120 | # Click-Once directory
121 | publish/
122 |
123 | # Publish Web Output
124 | *.[Pp]ublish.xml
125 | *.azurePubxml
126 | ## TODO: Comment the next line if you want to checkin your
127 | ## web deploy settings but do note that will include unencrypted
128 | ## passwords
129 | #*.pubxml
130 |
131 | # NuGet Packages Directory
132 | packages/*
133 | ## TODO: If the tool you use requires repositories.config
134 | ## uncomment the next line
135 | #!packages/repositories.config
136 |
137 | # Enable "build/" folder in the NuGet Packages folder since
138 | # NuGet packages use it for MSBuild targets.
139 | # This line needs to be after the ignore of the build folder
140 | # (and the packages folder if the line above has been uncommented)
141 | !packages/build/
142 |
143 | # Windows Azure Build Output
144 | csx/
145 | *.build.csdef
146 |
147 | # Windows Store app package directory
148 | AppPackages/
149 |
150 | # Others
151 | sql/
152 | *.Cache
153 | ClientBin/
154 | [Ss]tyle[Cc]op.*
155 | ~$*
156 | *~
157 | *.dbmdl
158 | *.dbproj.schemaview
159 | *.pfx
160 | *.publishsettings
161 | node_modules/
162 |
163 | # RIA/Silverlight projects
164 | Generated_Code/
165 |
166 | # Backup & report files from converting an old project file
167 | # to a newer Visual Studio version. Backup files are not needed,
168 | # because we have git ;-)
169 | _UpgradeReport_Files/
170 | Backup*/
171 | UpgradeLog*.XML
172 | UpgradeLog*.htm
173 |
174 | # SQL Server files
175 | *.mdf
176 | *.ldf
177 |
178 | # Business Intelligence projects
179 | *.rdl.data
180 | *.bim.layout
181 | *.bim_*.settings
182 |
183 | # Microsoft Fakes
184 | FakesAssemblies/
185 |
186 | # LightSwitch generated files
187 | GeneratedArtifacts/
188 | _Pvt_Extensions/
189 | ModelManifest.xml
--------------------------------------------------------------------------------
/MTCClient/Client.cs:
--------------------------------------------------------------------------------
1 | using RestSharp;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Collections.ObjectModel;
5 | using System.IO;
6 | using System.Linq;
7 | using System.Timers;
8 | using System.Xml.Linq;
9 |
10 | namespace MTConnectSharp
11 | {
12 | ///
13 | /// Connects to a single agent and streams data from it.
14 | ///
15 | public class MTConnectClient : IMTConnectClient, IDisposable
16 | {
17 | ///
18 | /// The probe response has been recieved and parsed
19 | ///
20 | public event EventHandler ProbeCompleted;
21 |
22 | ///
23 | /// The base uri of the agent
24 | ///
25 | public string AgentUri { get; set; }
26 |
27 | ///
28 | /// Time in milliseconds between sample queries when simulating a streaming connection
29 | ///
30 | public TimeSpan UpdateInterval { get; set; }
31 |
32 | ///
33 | /// Devices on the connected agent
34 | ///
35 | public ReadOnlyObservableCollection Devices
36 | {
37 | get;
38 | private set;
39 | }
40 | private ObservableCollection _devices;
41 |
42 | ///
43 | /// Dictionary Reference to all data items by id for better performance when streaming
44 | ///
45 | private Dictionary _dataItemsDictionary = new Dictionary();
46 |
47 | ///
48 | /// RestSharp RestClient
49 | ///
50 | private RestClient _restClient;
51 |
52 | ///
53 | /// Not actually parsing multipart stream - this timer fires sample queries to simulate streaming
54 | ///
55 | private Timer _streamingTimer;
56 |
57 | ///
58 | /// Last sequence number read from current or sample
59 | ///
60 | private long _lastSequence;
61 |
62 | private bool _probeStarted = false;
63 | private bool _probeCompleted = false;
64 |
65 | ///
66 | /// Initializes a new Client
67 | ///
68 | public MTConnectClient()
69 | {
70 | UpdateInterval = TimeSpan.FromMilliseconds(2000);
71 |
72 | _devices = new ObservableCollection();
73 | Devices = new ReadOnlyObservableCollection(_devices);
74 | }
75 |
76 | ///
77 | /// Starts sample polling and updating DataItem values as they change
78 | ///
79 | public void StartStreaming()
80 | {
81 | if (_streamingTimer?.Enabled == true)
82 | {
83 | return;
84 | }
85 |
86 | GetCurrentState();
87 |
88 | _streamingTimer = new Timer(UpdateInterval.TotalMilliseconds);
89 | _streamingTimer.Elapsed += StreamingTimerElapsed;
90 | _streamingTimer.Start();
91 | }
92 |
93 | ///
94 | /// Stops sample polling
95 | ///
96 | public void StopStreaming()
97 | {
98 | _streamingTimer.Stop();
99 | }
100 |
101 | ///
102 | /// Gets current response and updates DataItems
103 | ///
104 | public void GetCurrentState()
105 | {
106 | if (!_probeCompleted)
107 | {
108 | throw new InvalidOperationException("Cannot get DataItem values. Agent has not been probed yet.");
109 | }
110 |
111 | var request = new RestRequest
112 | {
113 | Resource = "current"
114 | };
115 | _restClient.ExecuteAsync(request, (a) => ParseStream(a));
116 | }
117 |
118 | ///
119 | /// Gets probe response from the agent and populates the devices collection
120 | ///
121 | public void Probe()
122 | {
123 | if (_probeStarted && !_probeCompleted)
124 | {
125 | throw new InvalidOperationException("Cannot start a new Probe when one is still running.");
126 | }
127 |
128 | _restClient = new RestClient
129 | {
130 | BaseUrl = new Uri(AgentUri)
131 | };
132 |
133 | var request = new RestRequest
134 | {
135 | Resource = "probe"
136 | };
137 |
138 | try
139 | {
140 | _probeStarted = true;
141 | _restClient.ExecuteAsync(request, r => ParseProbeResponse(r));
142 | }
143 | catch (Exception ex)
144 | {
145 | _probeStarted = false;
146 | throw new Exception("Probe request failed.\nAgent Uri: " + AgentUri, ex);
147 | }
148 | }
149 |
150 | ///
151 | /// Parses IRestResponse from a probe command into a Device collection
152 | ///
153 | /// An IRestResponse from a probe command
154 | private void ParseProbeResponse(IRestResponse response)
155 | {
156 | var xdoc = XDocument.Load(new StringReader(response.Content));
157 | if (_devices.Any())
158 | _devices.Clear();
159 |
160 | _devices.AddRange(xdoc.Descendants()
161 | .Where(d => d.Name.LocalName == "Devices")
162 | .Take(1) // needed?
163 | .SelectMany(d => d.Elements())
164 | .Select(d => new Device(d)));
165 |
166 | BuildDataItemDictionary();
167 |
168 | _probeCompleted = true;
169 | _probeStarted = false;
170 | ProbeCompletedHandler();
171 | }
172 |
173 | ///
174 | /// Loads DataItemRefList with all data items from all devices
175 | ///
176 | private void BuildDataItemDictionary()
177 | {
178 | _dataItemsDictionary = _devices.SelectMany(d =>
179 | d.DataItems.Concat(GetAllDataItems(d.Components))
180 | ).ToDictionary(i => i.Id, i => i);
181 | }
182 |
183 | ///
184 | /// Recursive function to get DataItems list from a Component collection
185 | ///
186 | /// Collection of Components
187 | /// Collection of DataItems from passed Component collection
188 | private static List GetAllDataItems(IReadOnlyList components)
189 | {
190 | var queue = new Queue(components);
191 | var dataItems = new List();
192 | while(queue.Count > 0)
193 | {
194 | var component = queue.Dequeue();
195 | foreach (var c in component.Components)
196 | queue.Enqueue(c);
197 | dataItems.AddRange(component.DataItems);
198 | }
199 | return dataItems;
200 | }
201 |
202 | private void StreamingTimerElapsed(object sender, ElapsedEventArgs e)
203 | {
204 | var request = new RestRequest
205 | {
206 | Resource = "sample"
207 | };
208 | request.AddParameter("at", _lastSequence + 1);
209 | _restClient.ExecuteAsync(request, r => ParseStream(r));
210 | }
211 |
212 | ///
213 | /// Parses response from a current or sample request, updates changed data items and fires events
214 | ///
215 | /// IRestResponse from the MTConnect request
216 | private void ParseStream(IRestResponse response)
217 | {
218 | using (StringReader sr = new StringReader(response.Content))
219 | {
220 | var xdoc = XDocument.Load(sr);
221 |
222 | _lastSequence = Convert.ToInt64(xdoc.Descendants().First(e => e.Name.LocalName == "Header")
223 | .Attribute("lastSequence").Value);
224 |
225 | var xmlDataItems = xdoc.Descendants()
226 | .Where(e => e.Attributes().Any(a => a.Name.LocalName == "dataItemId"));
227 | if (xmlDataItems.Any())
228 | {
229 | var dataItems = xmlDataItems.Select(e => new {
230 | id = e.Attribute("dataItemId").Value,
231 | timestamp = DateTime.Parse(e.Attribute("timestamp").Value, null,
232 | System.Globalization.DateTimeStyles.RoundtripKind),
233 | value = e.Value
234 | })
235 | .OrderBy(i => i.timestamp)
236 | .ToList();
237 |
238 | foreach (var item in dataItems)
239 | {
240 | var dataItem = _dataItemsDictionary[item.id];
241 | var sample = new DataItemSample(item.value.ToString(), item.timestamp);
242 | dataItem.AddSample(sample);
243 | }
244 | }
245 | }
246 | }
247 |
248 | private void ProbeCompletedHandler()
249 | {
250 | ProbeCompleted?.Invoke(this, new EventArgs());
251 | }
252 |
253 | ///
254 | /// Disposes unmanaged resources
255 | ///
256 | public void Dispose()
257 | {
258 | _streamingTimer?.Dispose();
259 | }
260 | }
261 | }
262 |
--------------------------------------------------------------------------------
/MTCClient/Component.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Collections.ObjectModel;
3 | using System.Xml.Linq;
4 |
5 | namespace MTConnectSharp
6 | {
7 | ///
8 | /// Represents a component in the probe response document
9 | ///
10 | public class Component : MTCItemBase, IComponent
11 | {
12 | ///
13 | /// The component type
14 | ///
15 | public string Type { get; set; }
16 |
17 | ///
18 | /// Value of the nativeName attribute
19 | ///
20 | public string NativeName { get; set; }
21 |
22 | ///
23 | /// The DataItems which belong to this component
24 | ///
25 | private ObservableCollection _dataItems;
26 |
27 | ///
28 | /// The Components which belong to this component
29 | ///
30 | private ObservableCollection _components;
31 |
32 | ///
33 | /// Array of the DataItems collection for COM Interop
34 | ///
35 | public ReadOnlyObservableCollection DataItems
36 | {
37 | get; private set;
38 | }
39 |
40 | ///
41 | /// Array of the Components collection for COM Interop
42 | ///
43 | public ReadOnlyObservableCollection Components
44 | {
45 | get;
46 | private set;
47 | }
48 |
49 | ///
50 | /// Creates a new component
51 | ///
52 | internal Component(XElement xmlComponent)
53 | {
54 | Type = xmlComponent.Name?.ToString() ?? string.Empty;
55 | Id = ParseUtility.GetAttribute(xmlComponent, "id");
56 | Name = ParseUtility.GetAttribute(xmlComponent, "name");
57 | if (string.IsNullOrEmpty(Name))
58 | Name = Id;
59 | NativeName = ParseUtility.GetAttribute(xmlComponent, "nativeName");
60 |
61 | _dataItems = new ObservableCollection(ParseUtility.GetDataItems(xmlComponent));
62 | DataItems = new ReadOnlyObservableCollection(_dataItems);
63 |
64 | _components = new ObservableCollection(ParseUtility.GetComponents(xmlComponent));
65 | Components = new ReadOnlyObservableCollection(_components);
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/MTCClient/DataItem.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.ObjectModel;
4 | using System.Linq;
5 | using System.Xml.Linq;
6 |
7 | namespace MTConnectSharp
8 | {
9 | ///
10 | /// Represents a DataItem in the MTConnect probe response
11 | ///
12 | public class DataItem : MTCItemBase, IDataItem
13 | {
14 | ///
15 | /// DataItemSample collection as a Queue for correct circular buffer behavior
16 | ///
17 | private ObservableCollection _dataItemSamples = new ObservableCollection();
18 |
19 | ///
20 | /// Value of the category attribute
21 | ///
22 | public string Category { get; set; }
23 |
24 | ///
25 | /// Value of the type attribute
26 | ///
27 | public string Type { get; set; }
28 |
29 | ///
30 | /// Value of the subType attribute
31 | ///
32 | public string SubType { get; set; }
33 |
34 | ///
35 | /// Value of the units attribute
36 | ///
37 | public string Units { get; set; }
38 |
39 | ///
40 | /// Value of the nativeUnits attribute
41 | ///
42 | public string NativeUnits { get; set; }
43 |
44 | ///
45 | /// The maximum number of samples to keep in the value buffer
46 | ///
47 | public int BufferSize { get; set; }
48 |
49 | ///
50 | /// The value immediately before the value
51 | ///
52 | public DataItemSample PreviousSample
53 | {
54 | get
55 | {
56 | return _dataItemSamples.Skip(Math.Max(1, _dataItemSamples.Count - 1)).FirstOrDefault();
57 | }
58 | }
59 |
60 | ///
61 | /// The current value of this DataItem
62 | ///
63 | public DataItemSample CurrentSample
64 | {
65 | get
66 | {
67 | return _dataItemSamples.Last();
68 | }
69 | }
70 |
71 | ///
72 | /// Every value in the buffer
73 | ///
74 | public ReadOnlyObservableCollection SampleHistory
75 | {
76 | get;
77 | private set;
78 | }
79 |
80 | ///
81 | /// Creates a new DataItem
82 | ///
83 | /// The XElement which defines the DataItem
84 | internal DataItem(XElement xmlDataItem)
85 | {
86 | SampleHistory = new ReadOnlyObservableCollection(_dataItemSamples);
87 |
88 | BufferSize = 100;
89 | Id = ParseUtility.GetAttribute(xmlDataItem, "id");
90 | Name = ParseUtility.GetAttribute(xmlDataItem, "name");
91 | Category = ParseUtility.GetAttribute(xmlDataItem, "category");
92 | Type = ParseUtility.GetAttribute(xmlDataItem, "type");
93 | SubType = ParseUtility.GetAttribute(xmlDataItem, "subType");
94 | Units = ParseUtility.GetAttribute(xmlDataItem, "units");
95 | NativeUnits = ParseUtility.GetAttribute(xmlDataItem, "nativeUnits");
96 | }
97 |
98 | ///
99 | /// Adds a sample to the value buffer and removes the oldest value if the buffer is full
100 | ///
101 | /// The new sample to add
102 | internal void AddSample(DataItemSample newSample)
103 | {
104 | _dataItemSamples.Add(newSample);
105 | _dataItemSamples.RemoveRange(0, Math.Max(0, _dataItemSamples.Count - BufferSize));
106 | }
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/MTCClient/DataItemSample.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace MTConnectSharp
4 | {
5 | ///
6 | /// A single value from a current or sample response
7 | ///
8 | public class DataItemSample : MTConnectSharp.IDataItemSample
9 | {
10 | ///
11 | /// The value of the sample
12 | ///
13 | public string Value { get; set; }
14 |
15 | ///
16 | /// The timestamp of the sample
17 | ///
18 | public DateTime TimeStamp { get; set; }
19 |
20 | ///
21 | /// Creates a new sample with the current time as the timestamp
22 | ///
23 | /// Value of the sample
24 | internal DataItemSample(string value)
25 | {
26 | TimeStamp = DateTime.Now;
27 | Value = value;
28 | }
29 |
30 | ///
31 | /// Creates a new sample
32 | ///
33 | /// Value of the sample
34 | /// Timestamp of the sample
35 | internal DataItemSample(string value, DateTime timestamp)
36 | {
37 | TimeStamp = timestamp;
38 | Value = value;
39 | }
40 |
41 | ///
42 | /// Returns the Value
43 | ///
44 | public override string ToString()
45 | {
46 | return Value;
47 | }
48 | }
49 |
50 | }
51 |
52 |
--------------------------------------------------------------------------------
/MTCClient/Device.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Collections.ObjectModel;
3 | using System.Linq;
4 | using System.Xml.Linq;
5 |
6 | namespace MTConnectSharp
7 | {
8 | ///
9 | /// Represents a device in the MTConnect probe response
10 | ///
11 | public class Device : MTCItemBase, IDevice
12 | {
13 | ///
14 | /// Description of the device
15 | ///
16 | public string Description { get; set; }
17 |
18 | ///
19 | /// Manufacturer of the device
20 | ///
21 | public string Manufacturer { get; set; }
22 |
23 | ///
24 | /// Serial number of the device
25 | ///
26 | public string SerialNumber { get; set; }
27 |
28 | ///
29 | /// The DataItems which are direct children of the device
30 | ///
31 | private ObservableCollection _dataItems = new ObservableCollection();
32 |
33 | ///
34 | /// The components which are direct children of the device
35 | ///
36 | private ObservableCollection _components = new ObservableCollection();
37 |
38 | ///
39 | /// Array of the DataItems collection for COM Interop
40 | ///
41 | public ReadOnlyObservableCollection DataItems
42 | {
43 | get;
44 | private set;
45 | }
46 |
47 | ///
48 | /// Array of the Components collection for COM Interop
49 | ///
50 | public ReadOnlyObservableCollection Components
51 | {
52 | get;
53 | private set;
54 | }
55 |
56 | ///
57 | /// Creates a new device from an MTConnect XML device node
58 | ///
59 | /// The MTConnect XML node which defines the device
60 | internal Device(XElement xElem = null)
61 | {
62 | DataItems = new ReadOnlyObservableCollection(_dataItems);
63 | Components = new ReadOnlyObservableCollection(_components);
64 |
65 | if (xElem?.Name.LocalName == "Device")
66 | {
67 | // Populate basic fields
68 | Id = ParseUtility.GetAttribute(xElem, "id");
69 | Name = ParseUtility.GetAttribute(xElem, "name");
70 |
71 | var descXml = xElem.Descendants().First(x => x.Name.LocalName == "Description");
72 | Description = descXml.Value ?? string.Empty;
73 | Manufacturer = ParseUtility.GetAttribute(descXml, "manufacturer");
74 | SerialNumber = ParseUtility.GetAttribute(descXml, "serialNumber");
75 |
76 | _dataItems.AddRange(ParseUtility.GetDataItems(xElem));
77 | _components.AddRange(ParseUtility.GetComponents(xElem));
78 | }
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/MTCClient/IClient.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.ObjectModel;
4 |
5 | namespace MTConnectSharp
6 | {
7 | public interface IMTConnectClient
8 | {
9 | string AgentUri { get; set; }
10 | void Probe();
11 | void StartStreaming();
12 | void StopStreaming();
13 | void GetCurrentState();
14 | ReadOnlyObservableCollection Devices { get; }
15 | TimeSpan UpdateInterval { get; set; }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/MTCClient/IComponent.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.ObjectModel;
2 |
3 | namespace MTConnectSharp
4 | {
5 | public interface IComponent
6 | {
7 | ReadOnlyObservableCollection Components { get; }
8 | ReadOnlyObservableCollection DataItems { get; }
9 | string Type { get; }
10 | string Id { get; }
11 | string Name { get; }
12 | string LongName { get; }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/MTCClient/IDataItem.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.ObjectModel;
2 |
3 | namespace MTConnectSharp
4 | {
5 | public interface IDataItem
6 | {
7 | int BufferSize { get; set; }
8 | DataItemSample PreviousSample { get; }
9 | DataItemSample CurrentSample { get; }
10 | ReadOnlyObservableCollection SampleHistory { get; }
11 | string Id { get; }
12 | string Name { get; }
13 | string LongName { get; }
14 | string Category { get; }
15 | string Type { get; }
16 | string SubType { get; }
17 | string Units { get; }
18 | string NativeUnits { get; }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/MTCClient/IDataItemSample.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace MTConnectSharp
4 | {
5 | public interface IDataItemSample
6 | {
7 | DateTime TimeStamp { get; }
8 | string ToString();
9 | string Value { get; }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/MTCClient/IDevice.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.ObjectModel;
2 |
3 | namespace MTConnectSharp
4 | {
5 | public interface IDevice
6 | {
7 | ReadOnlyObservableCollection Components { get; }
8 | ReadOnlyObservableCollection DataItems { get; }
9 | string Description { get; }
10 | string Manufacturer { get; }
11 | string SerialNumber { get; }
12 | string Id { get; }
13 | string Name { get; }
14 | string LongName { get; }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/MTCClient/MTCItemBase.cs:
--------------------------------------------------------------------------------
1 | namespace MTConnectSharp
2 | {
3 | ///
4 | /// The base MTConnect Item Class
5 | ///
6 | public abstract class MTCItemBase
7 | {
8 | ///
9 | /// Value of the id attribute
10 | ///
11 | public string Id { get; set; }
12 |
13 | ///
14 | /// Value of the name attribute
15 | ///
16 | public string Name { get; set; }
17 |
18 | ///
19 | /// Name and id of the item as a formatted string
20 | ///
21 | public string LongName
22 | {
23 | get
24 | {
25 | return $"{Name}({Id})";
26 | }
27 | }
28 |
29 | ///
30 | /// Formatted string describing the item
31 | ///
32 | public override string ToString()
33 | {
34 | try
35 | {
36 | return $"{Name}({Id})";
37 | }
38 | catch
39 | {
40 | return base.ToString();
41 | }
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/MTCClient/MTConnectSharp.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Library
5 | netcoreapp2.1
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/MTCClient/ParseUtility.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Collections.ObjectModel;
3 | using System.Linq;
4 | using System.Xml.Linq;
5 |
6 | namespace MTConnectSharp
7 | {
8 | internal static class ParseUtility
9 | {
10 | public static void AddRange(this ObservableCollection collection, IEnumerable items)
11 | {
12 | foreach (var i in items) collection.Add(i);
13 | }
14 |
15 | public static void RemoveRange(this ObservableCollection collection, int start, int count)
16 | {
17 | for(var i = 0; i < count && i < collection.Count; ++i)
18 | collection.RemoveAt(start);
19 | }
20 |
21 | ///
22 | /// Gets a named attribute from an XElement
23 | ///
24 | /// The XElement to parse
25 | /// The attribute name
26 | /// Attribute value or an empty string if not found
27 | public static string GetAttribute(XElement xElem, string Attribute)
28 | {
29 | if (xElem.Attribute(Attribute) != null)
30 | {
31 | return xElem.Attribute(Attribute).Value;
32 | }
33 | return string.Empty;
34 | }
35 |
36 | ///
37 | /// Builds a collection of DataItems from an XElement
38 | ///
39 | /// The XElement to parse
40 | /// A Collection of DataItems
41 | public static List GetDataItems(XElement xElem)
42 | {
43 | return xElem.Elements()
44 | .Where(e => e.Name.LocalName == "DataItems")
45 | .Take(1) // needed?
46 | .SelectMany(d => d.Elements())
47 | .Select(d => new DataItem(d))
48 | .ToList();
49 | }
50 |
51 | ///
52 | /// Builds a collection of Components from an XElement
53 | ///
54 | /// The XElement to parse
55 | /// A Collection of Components
56 | public static List GetComponents(XElement xElem)
57 | {
58 | return xElem.Elements()
59 | .Where(e => e.Name.LocalName == "Components")
60 | .Take(1) // needed?
61 | .SelectMany(d => d.Elements())
62 | .Select(d => new Component(d))
63 | .ToList();
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/MTCClient/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/MTConnectSharp.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.28010.2046
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MTConnectSharp", "MTCClient\MTConnectSharp.csproj", "{130CF922-9476-4459-952D-69B519B3822C}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExampleConsoleApp", "TestConsoleApp\ExampleConsoleApp.csproj", "{5D2099EA-FE35-4653-8978-5056626934CC}"
9 | EndProject
10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F0D9D817-054E-4CDE-8D1A-C60F9D80A835}"
11 | ProjectSection(SolutionItems) = preProject
12 | README.md = README.md
13 | EndProjectSection
14 | EndProject
15 | Global
16 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
17 | Debug|Any CPU = Debug|Any CPU
18 | Release|Any CPU = Release|Any CPU
19 | EndGlobalSection
20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
21 | {130CF922-9476-4459-952D-69B519B3822C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
22 | {130CF922-9476-4459-952D-69B519B3822C}.Debug|Any CPU.Build.0 = Debug|Any CPU
23 | {130CF922-9476-4459-952D-69B519B3822C}.Release|Any CPU.ActiveCfg = Release|Any CPU
24 | {130CF922-9476-4459-952D-69B519B3822C}.Release|Any CPU.Build.0 = Release|Any CPU
25 | {5D2099EA-FE35-4653-8978-5056626934CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
26 | {5D2099EA-FE35-4653-8978-5056626934CC}.Debug|Any CPU.Build.0 = Debug|Any CPU
27 | {5D2099EA-FE35-4653-8978-5056626934CC}.Release|Any CPU.ActiveCfg = Release|Any CPU
28 | {5D2099EA-FE35-4653-8978-5056626934CC}.Release|Any CPU.Build.0 = Release|Any CPU
29 | EndGlobalSection
30 | GlobalSection(SolutionProperties) = preSolution
31 | HideSolutionNode = FALSE
32 | EndGlobalSection
33 | GlobalSection(ExtensibilityGlobals) = postSolution
34 | SolutionGuid = {C4E245DE-9EA3-45DA-B7E0-0B77136E5D74}
35 | EndGlobalSection
36 | EndGlobal
37 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MTConnectSharp
2 | A Simple MTConnect C# Client Library
3 |
4 | Dependencies: RestSharp open source C# REST client library, referenced in the package.config file.
5 |
6 | This library was originally written to monitor individual data items as they changed to better facilitate rule development in the early days of MTconnect. As such it was written to be as simple as possible, so it does not support much beyond simple probe parsing and quasi-streaming the CDATA of dataitem tags as they change.
7 |
8 | The TestConsoleApp project provides a basic example of streaming data from agent.mtconnect.org.
9 |
10 | Because MTConnect is an HTTP protocol, this library handles all communication asynchronously. There are events which an application can subscribe to and react accordingly after successful responses.
11 |
12 | ## Usage Example
13 |
14 | The entry point is the `MTConnectClient` class, which can be initialized via the constructor:
15 |
16 | var client = new MTConnectSharp.MTConnectClient()
17 | {
18 | AgentUri = "http://agent.mtconnect.org",
19 | UpdateInterval = TimeSpan.FromSeconds(.5)
20 | };
21 |
22 | The above code block initializes the client, probes the agent and populate the `Devices` collection of the `MTConnectClient`. When the probe parsing is complete the `ProbeCompleted` event fires.
23 |
24 | The `Devices` collection is observable and may contain `Component`s and `DataItem`s, and `Component`s may contain `Component`s and `DataItem`s, but `DataItem`s do not have child `DataItem`s or `Component`s, only `DataItemSample`s, which provide `Value`s and `Timestamp`s. All collections are exposed readonly observable collections to prevent modification and support change notification.
25 |
26 | After the probe is complete, we can start reading data items. There are two ways to initiate a data pull from the Agent: current and sample. `GetCurrentState()` sends a current command and processes the data, and `StartStreaming()` begins with a current, then requests samples at the `UpdateInterval` to get values for any DataItems which have changed.
27 |
28 | **Do not use repeated `GetCurrentState()` calls for streaming. It will burden the Agent and the client unneccessarily.**
29 |
30 | The `ReadOnlyObservableCollection` members of `Device` and `Component` will fire a `CollectionChanged` event when a new `DataItemSample` is added to or removed from its buffer.
31 |
32 | ## Known Issues ##
33 |
34 | * There are no tests. This project needs tests before further refactoring and features can be added.
35 | * Since this version is using the Net Core framework, there is no support for COM/VBA.
36 | * Potential error points do not have exception wrappers to help identify them when debugging.
37 |
38 | ## Future Enhancements ##
39 |
40 | * Add support for detailed events and conditions separate from sample-based data items
41 | * Support assets and asset change events
42 |
--------------------------------------------------------------------------------
/TestConsoleApp/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/TestConsoleApp/ExampleConsoleApp.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | netcoreapp2.1
6 | latest
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/TestConsoleApp/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.ObjectModel;
4 | using System.Collections.Specialized;
5 | using System.Linq;
6 | using Terminal.Gui;
7 |
8 | namespace TestConsoleApp
9 | {
10 | ///
11 | /// Basic example using MTCSharp to stream data from an agent
12 | ///
13 | class Program
14 | {
15 | static HashSet Registered = new HashSet();
16 |
17 | static Dictionary> Hellos = new Dictionary>();
18 | static Dictionary> Goodbyes = new Dictionary>();
19 |
20 | static void AddHelloGoodbye(Action hello, Action goodbye)
21 | {
22 | Hellos.Add(typeof(T), o => hello((T)o));
23 | Goodbyes.Add(typeof(T), o => goodbye((T)o));
24 | }
25 |
26 | static Dictionary Handlers =
27 | new Dictionary();
28 |
29 | static NotifyCollectionChangedEventHandler MakeHandler()
30 | {
31 | var t = typeof(T);
32 | if (!Handlers.ContainsKey(t))
33 | {
34 | Handlers.Add(t, (sender, info) =>
35 | {
36 | switch (info.Action)
37 | {
38 | case NotifyCollectionChangedAction.Add:
39 | case NotifyCollectionChangedAction.Remove:
40 | case NotifyCollectionChangedAction.Replace:
41 | case NotifyCollectionChangedAction.Reset:
42 | if (info.NewItems != null)
43 | {
44 | foreach (T item in info.NewItems)
45 | {
46 | Hellos[typeof(T)](item);
47 | }
48 | }
49 | if (info.OldItems != null)
50 | {
51 | foreach (T item in info.OldItems)
52 | {
53 | Goodbyes[typeof(T)](item);
54 | }
55 | }
56 | break;
57 |
58 | case NotifyCollectionChangedAction.Move:
59 | break;
60 | }
61 | });
62 | }
63 |
64 | return Handlers[t];
65 | }
66 |
67 | static void Attach(ReadOnlyObservableCollection collection)
68 | {
69 | if (!Registered.Contains(collection))
70 | {
71 | Registered.Add(collection);
72 | (collection as INotifyCollectionChanged).CollectionChanged += MakeHandler();
73 | }
74 | foreach (var t in collection)
75 | {
76 | Hellos[typeof(T)](t);
77 | }
78 | }
79 |
80 | static void Detach(ReadOnlyObservableCollection collection)
81 | {
82 | if (Registered.Contains(collection))
83 | {
84 | Registered.Remove(collection);
85 | (collection as INotifyCollectionChanged).CollectionChanged -= MakeHandler();
86 | }
87 | foreach (var t in collection)
88 | {
89 | Goodbyes[typeof(T)](t);
90 | }
91 | }
92 |
93 | static void Main(string[] args)
94 | {
95 | Console.WriteLine("Hello World!");
96 | var client = new MTConnectSharp.MTConnectClient()
97 | {
98 | AgentUri = "http://agent.mtconnect.org",
99 | UpdateInterval = TimeSpan.FromSeconds(.5)
100 | };
101 |
102 | client.ProbeCompleted += (sender, info) => {
103 | var items = client.Devices
104 | .SelectMany(d => d.DataItems.Select(i => new { d = d.LongName, i = i.LongName }))
105 | .ToArray();
106 |
107 | Console.WriteLine($"Number of DataItems: {items.Count()}");
108 |
109 | client.StartStreaming();
110 | };
111 |
112 | AddHelloGoodbye(
113 | d => { Attach(d.Components); Attach(d.DataItems); },
114 | d => { Detach(d.Components); Detach(d.DataItems); }
115 | );
116 | AddHelloGoodbye(
117 | c => { Attach(c.Components); Attach(c.DataItems); },
118 | c => { Detach(c.Components); Detach(c.DataItems); }
119 | );
120 | AddHelloGoodbye(
121 | i => Attach(i.SampleHistory),
122 | i => Detach(i.SampleHistory)
123 | );
124 | var samples = 0L;
125 | AddHelloGoodbye(
126 | s => samples++,
127 | s => samples--
128 | );
129 |
130 | /* UI */
131 | Application.Init();
132 | var top = Application.Top;
133 | var win = new Window(new Rect(0, 1, top.Frame.Width, top.Frame.Height - 1), "MyApp");
134 | top.Add(win);
135 |
136 | var count = new Label(14, 1, " ");
137 | win.Add(new Label(3, 1, "Samples:"), count);
138 | Application.MainLoop.AddTimeout(TimeSpan.FromSeconds(.25), m => {
139 | count.Text = samples.ToString();
140 | return true;
141 | });
142 |
143 | Attach(client.Devices);
144 | client.Probe();
145 |
146 | Application.Run();
147 | }
148 | }
149 | }
150 |
--------------------------------------------------------------------------------