1 | using System;
|
2 | using System.Collections.Generic;
|
3 | using System.Collections.Concurrent;
|
4 | using System.Data;
|
5 | using System.Linq;
|
6 | using System.IO.Ports;
|
7 |
|
8 | /// <summary>
|
9 | /// Stellt Methoden und Funktionen zur Nutzung des optoNCDT ILR 1181 Laser-Distanzmesser bereit.
|
10 | /// </summary>
|
11 | namespace DriverExtension
|
12 | {
|
13 | public class LaserDistanceMeter : IDisposable
|
14 | {
|
15 | // SerialPort _serialPort = new SerialPort();
|
16 | Measurement _measurement = new Measurement();
|
17 | optoNCDT _distanceMeter;
|
18 |
|
19 | bool _continuous = false;
|
20 |
|
21 | public event EventHandler<Measurement> PrecisionMeasurementCompleted;
|
22 | public event EventHandler<Measurement> SerialDataArrived;
|
23 | public event EventHandler<Measurement> SingeShotDataArrived;
|
24 | public event EventHandler<string> LaserDistanceMeterException;
|
25 |
|
26 | /// <summary>
|
27 | /// Standardkonstruktor
|
28 | /// </summary>
|
29 | public LaserDistanceMeter()
|
30 | {
|
31 | try
|
32 | {
|
33 | _distanceMeter = new optoNCDT();
|
34 |
|
35 | _distanceMeter.OnValueReceived += LDM_OnValueReceived;
|
36 | _distanceMeter.OnSeriesMeasurementCompleted += LDM_OnSeriesMeasurementCompleted;
|
37 | }
|
38 | catch (Exception ex)
|
39 | {
|
40 | throw ex;
|
41 | }
|
42 | }
|
43 |
|
44 | /// <summary>
|
45 | /// Standardkonstruktor
|
46 | /// </summary>
|
47 | /// <param name="serialPortName">Erwartet den Namen eines gültigen COM-Port.</param>
|
48 | public LaserDistanceMeter(string serialPortName)
|
49 | {
|
50 | try
|
51 | {
|
52 | _distanceMeter = new optoNCDT(serialPortName);
|
53 |
|
54 | _distanceMeter.OnValueReceived += LDM_OnValueReceived;
|
55 | _distanceMeter.OnSeriesMeasurementCompleted += LDM_OnSeriesMeasurementCompleted;
|
56 | }
|
57 | catch (Exception ex)
|
58 | {
|
59 | throw ex;
|
60 | }
|
61 | }
|
62 |
|
63 | /// <summary>
|
64 | /// Ereignis, das nach erfolgter Serienmessung aufgerufen wird.
|
65 | /// </summary>
|
66 | private void LDM_OnSeriesMeasurementCompleted()
|
67 | {
|
68 | if (PrecisionMeasurementCompleted != null)
|
69 | PrecisionMeasurementCompleted(this, _measurement);
|
70 | }
|
71 |
|
72 | /// <summary>
|
73 | /// Wird aufgerufen, wenn die serielle Schnittstelle Daten empfangen hat, die als Messwerte ausgewertet werden konnten.
|
74 | /// </summary>
|
75 | private void LDM_OnValueReceived()
|
76 | {
|
77 | ConcurrentQueue<optoNCDT.ComPortDataAnalyzer> buffer;
|
78 | optoNCDT.ComPortDataAnalyzer data;
|
79 |
|
80 | _distanceMeter.Queue(out buffer);
|
81 |
|
82 | while (buffer.TryDequeue(out data))
|
83 | {
|
84 | List<double> lstBuffer = data.Values;
|
85 |
|
86 | _measurement.Values.AddRange(lstBuffer);
|
87 |
|
88 | string err = _distanceMeter.GetErrorDescription;
|
89 |
|
90 | if (err != null)
|
91 | {
|
92 | if (LaserDistanceMeterException != null)
|
93 | LaserDistanceMeterException(this, err);
|
94 | }
|
95 | else
|
96 | {
|
97 | if (_continuous)
|
98 | {
|
99 | if (SerialDataArrived != null)
|
100 | SerialDataArrived(this, _measurement);
|
101 | } else
|
102 | {
|
103 | if (SingeShotDataArrived != null)
|
104 | SingeShotDataArrived(this, _measurement);
|
105 | }
|
106 | }
|
107 | }
|
108 | }
|
109 |
|
110 | /// <summary>
|
111 | /// Führt eine Einzelmessung durch.
|
112 | /// Achtung: Die Messgenauigkeit ist stark eingeschränkt!
|
113 | /// </summary>
|
114 | public void SingleShot()
|
115 | {
|
116 | // Felder bereinigen
|
117 | Clear();
|
118 |
|
119 | // Einzelmessung
|
120 | _continuous = false;
|
121 |
|
122 | // Einzelmessung durchführen
|
123 | _distanceMeter.OneShot();
|
124 | }
|
125 |
|
126 | /// <summary>
|
127 | /// Löscht alle aktuell vorhandenen Messwerte
|
128 | /// </summary>
|
129 | public void Clear()
|
130 | {
|
131 | _measurement.Values.Clear();
|
132 | }
|
133 |
|
134 | /// <summary>
|
135 | /// Setzt den Offset des optoNCDT 1181 auf die aktuelle Position.
|
136 | /// </summary>
|
137 | public void Zeroize()
|
138 | {
|
139 | _distanceMeter.SetNull();
|
140 | }
|
141 |
|
142 | /// <summary>
|
143 | /// Startet eine einzelne Präzisionsmessung.
|
144 | /// </summary>
|
145 | public void StartPrecisionMeasurement()
|
146 | {
|
147 | this.Clear();
|
148 | _distanceMeter.EnableAutoStop(5);
|
149 | _distanceMeter.DistanceTracking();
|
150 | }
|
151 |
|
152 | /// <summary>
|
153 | /// Startet eine Intervallmessung.
|
154 | /// </summary>
|
155 | public void StartContinuousMeasurement()
|
156 | {
|
157 | _continuous = true;
|
158 |
|
159 | this.Clear();
|
160 | _distanceMeter.DistanceTracking();
|
161 | }
|
162 |
|
163 | /// <summary>
|
164 | /// Beendet eine Intervallmessung.
|
165 | /// </summary>
|
166 | public void StopContinuousMeasurement()
|
167 | {
|
168 | _continuous = false;
|
169 |
|
170 | this.Clear();
|
171 | _distanceMeter.Stop();
|
172 | }
|
173 |
|
174 | /// <summary>
|
175 | /// Schließt die Verbindung zum optoNCDT ILR 1181.
|
176 | /// </summary>
|
177 | public void Dispose()
|
178 | {
|
179 | _distanceMeter.Stop();
|
180 | _distanceMeter.Dispose();
|
181 | }
|
182 | }
|
183 |
|
184 | /// <summary>
|
185 | /// Hilfsobjekt zur einfachen Handhabung der Messdaten
|
186 | /// </summary>
|
187 | public class Measurement
|
188 | {
|
189 | List<double> _measuredValues = new List<double>();
|
190 |
|
191 | double _min, _max, _diff, _avg, _mdn;
|
192 | double _fmin, _fmax, _fdiff, _favg, _fmdn;
|
193 |
|
194 | /// <summary>
|
195 | /// Hält die Messwerte, die übergeben wurden.
|
196 | /// </summary>
|
197 | public List<double> Values { get { return _measuredValues; } set { _measuredValues = value; } }
|
198 | /// <summary>
|
199 | /// Gibt den Minimalwert alle Elemente von <see cref="Values"/> zurück.
|
200 | /// </summary>
|
201 | public double Min { get { Calc(); return _min; } set { _min = value; } }
|
202 | /// <summary>
|
203 | /// Gibt den Maximalwert alle Elemente von <see cref="Values"/> zurück.
|
204 | /// </summary>
|
205 | public double Max { get { Calc(); return _max; } set { _max = value; } }
|
206 | /// <summary>
|
207 | /// Gibt die Differenz zwischen <see cref="Max"/> und <see cref="Min"/> zurück.
|
208 | /// </summary>
|
209 | public double Diff { get { Calc(); return _diff; } set { _diff = value; } }
|
210 | /// <summary>
|
211 | /// Gibt den Mittelwert alle Elemente von <see cref="Values"/> zurück.
|
212 | /// </summary>
|
213 | public double Avg { get { Calc(); return _avg; } set { _avg = value; } }
|
214 | /// <summary>
|
215 | /// Gibt den Median alle Elemente von <see cref="Values"/> zurück.
|
216 | /// </summary>
|
217 | public double Mdn { get { Calc(); return _mdn; } set { _mdn = value; } }
|
218 |
|
219 | /// <summary>
|
220 | /// Gibt den Minimalwert der letzten zehn Elemente von <see cref="Values"/> zurück.
|
221 | /// </summary>
|
222 | public double FloatingMin { get { Calc(); return _fmin; } set { _fmin = value; } }
|
223 | /// <summary>
|
224 | /// Gibt den Maximalwert der letzten zehn Elemente von <see cref="Values"/> zurück.
|
225 | /// </summary>
|
226 | public double FloatingMax { get { Calc(); return _fmax; } set { _fmax = value; } }
|
227 | /// <summary>
|
228 | /// Gibt die Differenz zwischen <see cref="Max"/> und <see cref="Min"/> zurück.
|
229 | /// </summary>
|
230 | public double FloatingDiff { get { Calc(); return _fdiff; } set { _fdiff = value; } }
|
231 | /// <summary>
|
232 | /// Gibt den Mittelwert der letzten zehn Elemente von <see cref="Values"/> zurück.
|
233 | /// </summary>
|
234 | public double FloatingAvg { get { Calc(); return _favg; } set { _favg = value; } }
|
235 | /// <summary>
|
236 | /// Gibt den Median der letzten zehn Elemente von <see cref="Values"/> zurück.
|
237 | /// </summary>
|
238 | public double FloatingMdn { get { Calc(); return _fmdn; } set { _fmdn = value; } }
|
239 |
|
240 | /// <summary>
|
241 | /// Führt die Berechnungen der einzelnen Parameter durch.
|
242 | /// </summary>
|
243 | private void Calc()
|
244 | {
|
245 | if (_measuredValues.Count == 0)
|
246 | return;
|
247 |
|
248 | // Berechnung über alle Werte
|
249 | _min = _measuredValues.Min();
|
250 | _max = _measuredValues.Max();
|
251 | _diff = _max - _min;
|
252 | _avg = _measuredValues.Average();
|
253 |
|
254 | List<double> median = _measuredValues;
|
255 |
|
256 | _mdn = Median(median.ToArray());
|
257 |
|
258 | // Berechnung über die letzten 10 Werte
|
259 | int offset = 0;
|
260 | int count = 0;
|
261 |
|
262 | offset = _measuredValues.Count < 5 ? 0 : _measuredValues.Count - 5;
|
263 | count = _measuredValues.Count < 5 ? _measuredValues.Count : 5;
|
264 |
|
265 | List<double> buffer = _measuredValues.GetRange(offset, count);
|
266 |
|
267 | _fmin = buffer.Min();
|
268 | _fmax = buffer.Max();
|
269 | _fdiff = _fmax - _fmin;
|
270 | _avg = buffer.Average();
|
271 |
|
272 | List<double> floatingMedian = buffer;
|
273 |
|
274 | _fmdn = Median(floatingMedian.ToArray());
|
275 | }
|
276 |
|
277 | /// <summary>
|
278 | /// Berechnet den Median aus einem Array.
|
279 | /// </summary>
|
280 | /// <param name="xs"></param>
|
281 | /// <returns></returns>
|
282 | private double Median(double[] xs)
|
283 | {
|
284 | var ys = xs.OrderBy(x => x).ToList();
|
285 | double mid = (ys.Count - 1) / 2.0;
|
286 | return (ys[(int)(mid)] + ys[(int)(mid + 0.5)]) / 2;
|
287 | }
|
288 | }
|
289 |
|
290 | /// <summary>
|
291 | /// Diese Klasse regelt den Zugriff auf das optoNCDT ILR 1181.
|
292 | /// </summary>
|
293 | sealed class optoNCDT : IDisposable
|
294 | {
|
295 | #region Fields
|
296 |
|
297 | /// <summary>
|
298 | /// Instanz des ComPortDataAnalyzer-Objektes.
|
299 | /// </summary>
|
300 | ComPortDataAnalyzer _comPortDataAnalyzer = new ComPortDataAnalyzer();
|
301 |
|
302 | /// <summary>
|
303 | /// Threadsichere <see cref="ConcurrentQueue{T}"/> zur Übergabe der Daten zwischen Listener-, ausführendem, und UI-Thread.
|
304 | /// </summary>
|
305 | ConcurrentQueue<ComPortDataAnalyzer> _dataQueue = new ConcurrentQueue<ComPortDataAnalyzer>();
|
306 |
|
307 | /// <summary>
|
308 | /// Hält, ob die Messung automatisch beendet wird (Intervallmessung).
|
309 | /// </summary>
|
310 | private bool _autoStop = false;
|
311 |
|
312 | /// <summary>
|
313 | /// Gibt die Anzahl an Messwerten an, ab der eine Serienmessung beendet wird.
|
314 | /// </summary>
|
315 | private int _autoStopNumOfMeasurements;
|
316 |
|
317 | /// <summary>
|
318 | /// Messwertzähler
|
319 | /// </summary>
|
320 | private int _autoStopCounter = 0;
|
321 |
|
322 | /// <summary>
|
323 | /// Signalisiert das Ende eines Befehls.
|
324 | /// </summary>
|
325 | private string _endOfCommand = "\r";
|
326 |
|
327 | /// <summary>
|
328 | /// Gibt an, ob Daten empfangen wurden. Falls true, finden sich die Werte in <see cref="_comPortDataAnalyzer.Data"/>.
|
329 | /// </summary>
|
330 | private bool _dataReceived = false;
|
331 |
|
332 | /// <summary>
|
333 | /// Beinhaltet die dem Fehlercode entsprechende Fehlerbeschreibung des optoNCDT Laser-Entfernungsmesser.
|
334 | /// </summary>
|
335 | private string _errorDescription = null;
|
336 |
|
337 | /// <summary>
|
338 | /// Speichert das aktive <see cref="SerialPort"/>-Objekt.
|
339 | /// </summary>
|
340 | private SerialPort _serialPort;
|
341 |
|
342 | /// <summary>
|
343 | /// Hält, ob ein Port gefunden wurde, und damit aktiv bleiben soll.
|
344 | /// </summary>
|
345 | private bool _dontClosePort = false;
|
346 |
|
347 | /// <summary>
|
348 | /// Hält, ob es sich um den Initialisierungsvorgang handelt.
|
349 | /// </summary>
|
350 | private bool _isInit = true;
|
351 | #endregion
|
352 |
|
353 | #region Constructors
|
354 |
|
355 | /// <summary>
|
356 | /// Erstellt eine neue Instanz der <see cref="optoNCDT"/>-Klasse.
|
357 | /// </summary>
|
358 | /// <param name="serialPort">Erwartet einen gültigen Bezeichner für eine serielle Schnittstelle.</param>
|
359 | public optoNCDT(string serialPort)
|
360 | {
|
361 | _serialPort = new SerialPort();
|
362 |
|
363 | // Events bei Dateneingang feuern
|
364 | _serialPort.DataReceived += SerialPort_DataReceived;
|
365 |
|
366 | // COM-Port vorbereiten
|
367 | SetUpSerialPort();
|
368 |
|
369 | // Alle Ports durchlaufen
|
370 | _serialPort.PortName = serialPort;
|
371 |
|
372 | try
|
373 | {
|
374 | // Versuche den COM-Port zu öffnen
|
375 | _serialPort.Open();
|
376 |
|
377 | // Befehl "id" schreiben, um Type des Sensors zu ermitteln
|
378 | _serialPort.Write("id" + _endOfCommand);
|
379 |
|
380 | int delayBeforeTrigger = 4000;
|
381 | int timeElapsed = 0;
|
382 | bool timeOut = false;
|
383 |
|
384 | // Warten, bis Daten angekommen sind
|
385 | while (_dataReceived == false && timeOut == false)
|
386 | {
|
387 | System.Threading.Thread.Sleep(delayBeforeTrigger / 100);
|
388 |
|
389 | timeElapsed += delayBeforeTrigger / 100;
|
390 |
|
391 | // Wenn mittlerweile Daten empfangen wurden -> abbrechen
|
392 | if (_dataReceived) break;
|
393 |
|
394 | // Timeout?
|
395 | if (timeElapsed >= delayBeforeTrigger)
|
396 | timeOut = true;
|
397 | }
|
398 |
|
399 | // Auf empfangene Daten prüfen
|
400 | if (_dataReceived)
|
401 | {
|
402 | ComPortDataAnalyzer data;
|
403 |
|
404 | _dataQueue.TryDequeue(out data);
|
405 |
|
406 | // Auswerten, ob die Daten von dem Laser-Distanzmesser stammen
|
407 | if (data.Data.Left(7) == "ILR1181" || IsErrorCode(data.Data))
|
408 | {
|
409 | // Ausgewählten Port nicht schließen
|
410 | _dontClosePort = true;
|
411 |
|
412 | }
|
413 | }
|
414 |
|
415 | // Auf timeout prüfen
|
416 | if (timeOut)
|
417 | {
|
418 | throw new optoNCDTTimeoutException();
|
419 | }
|
420 |
|
421 | // Puffer leeren
|
422 | _dataReceived = false;
|
423 |
|
424 | }
|
425 | catch (Exception ex)
|
426 | {
|
427 | throw ex;
|
428 | }
|
429 | finally
|
430 | {
|
431 | // Port nicht gefunden?
|
432 | if (_dontClosePort == false)
|
433 | {
|
434 | // Falschen COM-Port schließen
|
435 | _serialPort.Close();
|
436 | }
|
437 | else
|
438 | {
|
439 | // Port gefunden -> Initialisierung fertig
|
440 | _isInit = false;
|
441 | }
|
442 | }
|
443 | }
|
444 |
|
445 | /// <summary>
|
446 | /// Erstellt eine neue Instanz der <see cref="optoNCDT"/>-Klasse.
|
447 | /// </summary>
|
448 | public optoNCDT()
|
449 | {
|
450 | // Hält, ob der Port mit angeschlossenem LDM gefunden wurde
|
451 | bool portFoundSuccessfully = false;
|
452 |
|
453 | _serialPort = new SerialPort();
|
454 |
|
455 | // Events bei Dateneingang feuern
|
456 | _serialPort.DataReceived += SerialPort_DataReceived;
|
457 |
|
458 | // COM-Port vorbereiten
|
459 | SetUpSerialPort();
|
460 |
|
461 | foreach (string port in GetAvailableComPorts().OrderBy(x => x).ToList())
|
462 | {
|
463 | // Abbrechen?
|
464 | if (portFoundSuccessfully)
|
465 | break;
|
466 |
|
467 | // Alle Ports durchlaufen
|
468 | _serialPort.PortName = port;
|
469 |
|
470 | try
|
471 | {
|
472 | // Versuche den COM-Port zu öffnen
|
473 | _serialPort.Open();
|
474 |
|
475 | // Befehl "id" schreiben, um Type des Sensors zu ermitteln
|
476 | _serialPort.Write("id" + _endOfCommand);
|
477 |
|
478 | int delayBeforeTrigger = 4000;
|
479 | int timeElapsed = 0;
|
480 | bool timeOut = false;
|
481 |
|
482 | // Warten, bis Daten angekommen sind
|
483 | while (_dataReceived == false && timeOut == false)
|
484 | {
|
485 | System.Threading.Thread.Sleep(delayBeforeTrigger / 100);
|
486 |
|
487 | timeElapsed += delayBeforeTrigger / 100;
|
488 |
|
489 | // Wenn mittlerweile Daten empfangen wurden -> abbrechen
|
490 | if (_dataReceived) break;
|
491 |
|
492 | // Timeout?
|
493 | if (timeElapsed >= delayBeforeTrigger)
|
494 | timeOut = true;
|
495 | }
|
496 |
|
497 | // Auf empfangene Daten prüfen
|
498 | if (_dataReceived)
|
499 | {
|
500 | ComPortDataAnalyzer data;
|
501 |
|
502 | _dataQueue.TryDequeue(out data);
|
503 |
|
504 | // Auswerten, ob die Daten von dem Laser-Distanzmesser stammen
|
505 | if (data.Data.Left(7) == "ILR1181" || IsErrorCode(data.Data))
|
506 | {
|
507 | // Ausgewählten Port nicht schließen
|
508 | _dontClosePort = true;
|
509 |
|
510 | // Weitern Suchvorgang abbrechen
|
511 | portFoundSuccessfully = true;
|
512 | }
|
513 | }
|
514 |
|
515 | // Auf timeout prüfen
|
516 | if (timeOut)
|
517 | {
|
518 | throw new optoNCDTTimeoutException();
|
519 | }
|
520 |
|
521 | // Puffer leeren
|
522 | _dataReceived = false;
|
523 |
|
524 | }
|
525 | catch (Exception ex)
|
526 | {
|
527 | throw ex;
|
528 | }
|
529 | finally
|
530 | {
|
531 | // Port nicht gefunden?
|
532 | if (_dontClosePort == false)
|
533 | {
|
534 | // Falschen COM-Port schließen
|
535 | _serialPort.Close();
|
536 | }
|
537 | else
|
538 | {
|
539 | // Port gefunden -> Initialisierung fertig
|
540 | _isInit = false;
|
541 | }
|
542 | }
|
543 | }
|
544 | }
|
545 | #endregion
|
546 |
|
547 | #region Destructors
|
548 |
|
549 | /// <summary>
|
550 | /// Alle Verbindungen schließen und Daten verwerfen
|
551 | /// </summary>
|
552 | public void Dispose()
|
553 | {
|
554 | // Stopp-Befehl senden
|
555 | Stop();
|
556 |
|
557 | // Puffer leeren
|
558 | _serialPort.DiscardOutBuffer();
|
559 | _serialPort.DiscardInBuffer();
|
560 |
|
561 | // Ereignisdelegat abmelden
|
562 | _serialPort.DataReceived -= SerialPort_DataReceived;
|
563 |
|
564 | // Seriellen Port schließen und verwerfen
|
565 | _serialPort.Close();
|
566 | _serialPort.Dispose();
|
567 | }
|
568 | #endregion
|
569 |
|
570 | #region Delegates
|
571 |
|
572 | /// <summary>
|
573 | /// Delegat, der beim Auslösen des <see cref="OnSeriesMeasurementCompleted"/>-Event aufgerufen wird.
|
574 | /// </summary>
|
575 | public delegate void SeriesMeasurementCompleted();
|
576 |
|
577 | /// <summary>
|
578 | /// Delegat, der beim Auslösen des <see cref="OnValueReceived"/>-Event aufgerufen wird.
|
579 | /// </summary>
|
580 | public delegate void ValueReceived();
|
581 |
|
582 | #endregion
|
583 |
|
584 | #region Events
|
585 |
|
586 | /// <summary>
|
587 | /// Wird ausgelöst, wenn Werte über die RS232-Schnittstelle empfangen wurden.
|
588 | /// </summary>
|
589 | public event ValueReceived OnValueReceived;
|
590 |
|
591 | /// <summary>
|
592 | /// Wird aufgerufen, wenn die Serienmessung abgeschlossen wurde.
|
593 | /// </summary>
|
594 | public event SeriesMeasurementCompleted OnSeriesMeasurementCompleted;
|
595 |
|
596 | #endregion
|
597 |
|
598 | #region Properties
|
599 |
|
600 | /// <summary>
|
601 | /// Gibt die Beschreibung des zuletzt aufgetretenen Fehler zurück.
|
602 | /// </summary>
|
603 | public string GetErrorDescription
|
604 | {
|
605 | get { return _errorDescription; }
|
606 | }
|
607 |
|
608 | /// <summary>
|
609 | /// Liefert den Gerätenamen des angeschlossenen Gerätes.
|
610 | /// </summary>
|
611 | public string GetDeviceName
|
612 | {
|
613 | get { return "ILR1181"; }
|
614 | }
|
615 |
|
616 | #endregion
|
617 |
|
618 | #region Methods
|
619 |
|
620 | /// <summary>
|
621 | /// Führt eine Einzelmessung durch.
|
622 | /// </summary>
|
623 | public void OneShot()
|
624 | {
|
625 | _serialPort.WriteLine("dm" + _endOfCommand);
|
626 | }
|
627 |
|
628 | /// <summary>
|
629 | /// Führt das Distanztracking aus.
|
630 | /// </summary>
|
631 | public void DistanceTracking()
|
632 | {
|
633 | _serialPort.WriteLine("dt" + _endOfCommand);
|
634 | }
|
635 |
|
636 | /// <summary>
|
637 | /// Setzt den Nullpunkt des LDM.
|
638 | /// </summary>
|
639 | public void SetNull()
|
640 | {
|
641 | _serialPort.WriteLine("so" + _endOfCommand);
|
642 | }
|
643 |
|
644 | /// <summary>
|
645 | /// Beendet die Serienaufnahme.
|
646 | /// </summary>
|
647 | public void Stop()
|
648 | {
|
649 | _serialPort.WriteLine("lo" + _endOfCommand);
|
650 | }
|
651 |
|
652 | /// <summary>
|
653 | /// Liefert ein Objekt aus der <see cref="ComPortDataAnalyzer"/>-Queue.
|
654 | /// </summary>
|
655 | /// <param name="retval">Referenz auf ein <see cref="ComPortDataAnalyzer"/>-Objekt.</param>
|
656 | /// <returns>True, falls ein Element aus der Queue gelesen wurde.</returns>
|
657 | public bool Queue(out ComPortDataAnalyzer retval)
|
658 | {
|
659 | return _dataQueue.TryDequeue(out retval);
|
660 | }
|
661 |
|
662 | /// <summary>
|
663 | /// Liefert eine Referenz auf eine <see cref="ConcurrentQueue{T}"/>.
|
664 | /// </summary>
|
665 | /// <param name="retval"></param>
|
666 | public void Queue(out ConcurrentQueue<ComPortDataAnalyzer> retval)
|
667 | {
|
668 | retval = _dataQueue;
|
669 | }
|
670 |
|
671 | /// <summary>
|
672 | /// Aktiviert die Autostop-Funktion für Serienmessungen.
|
673 | /// </summary>
|
674 | /// <param name="numOfMeasurements">Die Anzahl an Messungen, nach der die Datenerfassung angehalten werden soll.</param>
|
675 | public void EnableAutoStop(int numOfMeasurements = 10)
|
676 | {
|
677 | _autoStopNumOfMeasurements = numOfMeasurements;
|
678 | _autoStopCounter = 0;
|
679 | _autoStop = true;
|
680 | }
|
681 |
|
682 | /// <summary>
|
683 | /// Liefert alle verfügbaren COM-Ports zurück.
|
684 | /// </summary>
|
685 | /// <returns></returns>
|
686 | private string[] GetAvailableComPorts()
|
687 | {
|
688 | return SerialPort.GetPortNames();
|
689 | }
|
690 |
|
691 | /// <summary>
|
692 | /// Setzt alle Einstellung zur Nutzung des COM-Port
|
693 | /// </summary>
|
694 | private void SetUpSerialPort()
|
695 | {
|
696 | _serialPort.ReadTimeout = 1000;
|
697 | _serialPort.WriteTimeout = 1000;
|
698 | _serialPort.BaudRate = 9600;
|
699 | _serialPort.DataBits = 8;
|
700 | _serialPort.StopBits = StopBits.One;
|
701 | _serialPort.Parity = Parity.None;
|
702 | _serialPort.DiscardNull = true;
|
703 | }
|
704 |
|
705 | /// <summary>
|
706 | /// Prüft, ob es sich bei einem String um einen Fehlercode handelt.
|
707 | /// </summary>
|
708 | /// <param name="stringToMatch">Der String, der untersucht werden soll.</param>
|
709 | /// <returns>Wahr, falls es sich um einen gültigen Fehlercode handelt.</returns>
|
710 | private bool IsErrorCode(string stringToMatch)
|
711 | {
|
712 | try
|
713 | {
|
714 | if (stringToMatch.Left(1) == "E")
|
715 | {
|
716 | string substring = stringToMatch.Substring(1, 2);
|
717 | int number;
|
718 |
|
719 | if (int.TryParse(substring, out number))
|
720 | {
|
721 | string substring2;
|
722 | substring2 = stringToMatch.Substring(3, 2);
|
723 |
|
724 | if (substring2 == "\r\n")
|
725 | {
|
726 | return true;
|
727 | }
|
728 | }
|
729 | }
|
730 | }
|
731 | catch
|
732 | {
|
733 | return false;
|
734 | }
|
735 |
|
736 | return false;
|
737 | }
|
738 |
|
739 | /// <summary>
|
740 | /// Ereignis, welches beim Eintreffen von Daten am COM-Port ausgelöst wird.
|
741 | /// </summary>
|
742 | /// <param name="sender"></param>
|
743 | /// <param name="e"></param>
|
744 | private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
|
745 | {
|
746 | // Hält, ob die Datenaufzeichnung der Serienmessung abgeschlossen ist
|
747 | bool aquisitionFinished = false;
|
748 |
|
749 | // Lokale Instanz des Com
|
750 | ComPortDataAnalyzer comPortDataAnalyzer = new ComPortDataAnalyzer();
|
751 |
|
752 | // Lesepuffer der Daten
|
753 | string readBuffer;
|
754 |
|
755 | SerialPort sp = (SerialPort)sender;
|
756 |
|
757 | string[] lines;
|
758 |
|
759 | // Seriellen puffer für weiteren Zugriff sperren und fortfahren
|
760 | lock (_comPortDataAnalyzer)
|
761 | {
|
762 | // Daten in Puffer schreiben
|
763 | readBuffer = sp.ReadExisting();
|
764 | _comPortDataAnalyzer.Data += readBuffer;
|
765 |
|
766 | // Warten auf CRLF
|
767 | if (_comPortDataAnalyzer.Data.Right(2) == "\r\n")
|
768 | {
|
769 | // Prüfen, ob der letzte Datenblock eine Fehlermeldung ist
|
770 | lines = _comPortDataAnalyzer.Data.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
|
771 |
|
772 | // Letzten Eintrag extrahieren
|
773 | string lastEntry = lines.Last().ToString();
|
774 |
|
775 | // Fehlercode - falls vorhanden - umwandeln
|
776 | if (lastEntry.IsErrorCode())
|
777 | {
|
778 | // Fehlercode parsen
|
779 | _errorDescription = lastEntry.ToErrorDescription();
|
780 | }
|
781 | else {
|
782 | _errorDescription = null;
|
783 | }
|
784 |
|
785 | // Angeben, dass Daten empfangen wurden
|
786 | _dataReceived = true;
|
787 |
|
788 | // Autostop durchführen?
|
789 | if (_autoStop)
|
790 | {
|
791 | // Zähler aktualisieren
|
792 | _autoStopCounter += lines.Count();
|
793 |
|
794 | // Prüfen, ob Zählerendstand erreicht wurde
|
795 | if (_autoStopCounter >= _autoStopNumOfMeasurements)
|
796 | {
|
797 | // Messdatenaufnahme beenden
|
798 | _autoStop = false;
|
799 | _autoStopCounter = 0;
|
800 | Stop();
|
801 |
|
802 | // angeben, dass die Messung vollständig ist
|
803 | aquisitionFinished = true;
|
804 | }
|
805 | }
|
806 | }
|
807 | else {
|
808 | // Die empfangenen Daten sind unvollständig, da der Abschlus "\r\n" fehlt
|
809 | _dataReceived = false;
|
810 | }
|
811 | }
|
812 |
|
813 | // Prüfen, ob Daten *inklusive* EOB empfangen wurden
|
814 | if (_dataReceived)
|
815 | {
|
816 | // Daten aus lokalem Hilfsobjekt übernehmen
|
817 | comPortDataAnalyzer.Data = _comPortDataAnalyzer.Data;
|
818 |
|
819 | // Puffer leeren
|
820 | _comPortDataAnalyzer.Data = "";
|
821 |
|
822 | // ComPortDataAnalyzer-Objekt in die Queue einfügen
|
823 | _dataQueue.Enqueue(comPortDataAnalyzer);
|
824 |
|
825 | // Wird der COM-Port initialisiert?
|
826 | if (_isInit == false)
|
827 | {
|
828 | // Empfangsereignis auslösen
|
829 | OnValueReceived?.Invoke();
|
830 |
|
831 | // Ereignis ausführen, wenn die Messung vollständig ist
|
832 | if (aquisitionFinished)
|
833 | OnSeriesMeasurementCompleted?.Invoke();
|
834 | }
|
835 | }
|
836 | }
|
837 |
|
838 | #endregion
|
839 |
|
840 | #region Classes
|
841 |
|
842 | /// <summary>
|
843 | /// Fehler wird ausgelöst, wenn ein Timeout bei der Suche nach einem Laser-Distanzsensor auftritt
|
844 | /// </summary>
|
845 | [Serializable]
|
846 | private class optoNCDTTimeoutException : Exception
|
847 | {
|
848 | public optoNCDTTimeoutException() : base() { }
|
849 | }
|
850 |
|
851 | /// <summary>
|
852 | /// Fehler wird ausgelöst, wenn kein Laser-Distanzsensor gefunden werden kann
|
853 | /// </summary>
|
854 | [Serializable]
|
855 | public class optoNCDTNotFoundException : Exception
|
856 | {
|
857 | public optoNCDTNotFoundException() : base() { }
|
858 | }
|
859 |
|
860 | /// <summary>
|
861 | /// Hilfsobjekt zur Datenverarbeitung und -auswertung.
|
862 | /// </summary>
|
863 | public class ComPortDataAnalyzer
|
864 | {
|
865 | /// <summary>
|
866 | /// Hält lokal in der Klasse die empfangenen Daten
|
867 | /// </summary>
|
868 | string _data = "";
|
869 | List<double> _values = new List<double>();
|
870 |
|
871 | /// <summary>
|
872 | /// Hält die vom COM-Port empfangenen Daten
|
873 | /// </summary>
|
874 | public string Data
|
875 | {
|
876 | get { return _data; }
|
877 | set { _data = value; ParseData(); }
|
878 | }
|
879 |
|
880 | /// <summary>
|
881 | /// Gibt die aus den empfangenen Daten geparsten Messwerte zurück.
|
882 | /// </summary>
|
883 | public List<double> Values
|
884 | {
|
885 | get { return _values; }
|
886 | }
|
887 |
|
888 | /// <summary>
|
889 | /// Wandelt die empfangenen Daten in eine <see cref="List{T}"/> um.
|
890 | /// </summary>
|
891 | public void ParseData()
|
892 | {
|
893 | double numBuffer;
|
894 |
|
895 | _values.Clear();
|
896 |
|
897 | // Sind Daten vorhanden?
|
898 | if (_data != "")
|
899 | {
|
900 | // Daten am EOB-Separator auftrennen
|
901 | string[] lines = _data.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
|
902 |
|
903 | // Alle Einzelwerte durchlaufen
|
904 | foreach (string line in lines)
|
905 | {
|
906 | // Prüfen, ob es sich um Messwerte handelt
|
907 | if (double.TryParse(line, System.Globalization.NumberStyles.Number, System.Globalization.CultureInfo.InvariantCulture, out numBuffer))
|
908 | {
|
909 | // Wert in Liste einfügen
|
910 | _values.Add(numBuffer);
|
911 | }
|
912 | }
|
913 | }
|
914 | }
|
915 | }
|
916 |
|
917 | #endregion
|
918 | }
|
919 |
|
920 | /// <summary>
|
921 | /// Erweiterung der String-Klasse um einige zusätzliche Funktionen
|
922 | /// </summary>
|
923 | public static class StringExtensions
|
924 | {
|
925 | /// <summary>
|
926 | /// Wandelt einen String mit einem optoNCDT ILR 1181 / 1182 Fehler in eine Fehlerbeschreibung um.
|
927 | /// </summary>
|
928 | /// <param name="value">Der String, der geprüft werden soll.</param>
|
929 | /// <returns>Gibt die Fehlerbeschreibung zurück.</returns>
|
930 | public static string ToErrorDescription(this string value)
|
931 | {
|
932 | int errorCode = int.Parse(value.Right(2));
|
933 | string errorDescription;
|
934 |
|
935 | switch (errorCode)
|
936 | {
|
937 | case 15:
|
938 | errorDescription = "Zu schwache Reflexe; Abstand Sensor (Vorderkante) gegen Ziel < 0,1m.";
|
939 | break;
|
940 | case 16:
|
941 | errorDescription = "Zu starke Reflexe.";
|
942 | break;
|
943 | case 17:
|
944 | errorDescription = "Gleichlicht zu stark (zum Beispiel Sonneneinstrahlung).";
|
945 | break;
|
946 | case 18:
|
947 | errorDescription = "Nur im DX-Mode (50 Hz): zu große Abweichungen zwischen gemessenem und vorberechnetem Wert.";
|
948 | break;
|
949 | case 19:
|
950 | errorDescription = "Nur im DX-Mode (50 Hz): Verfahrgeschwindigkeit > 10 m/s.";
|
951 | break;
|
952 | case 23:
|
953 | errorDescription = "Temperatur unter -10°C.";
|
954 | break;
|
955 | case 24:
|
956 | errorDescription = "Temperatur über +60°C.";
|
957 | break;
|
958 | case 31:
|
959 | errorDescription = "Prüfsumme EEPROM falsch; Hardware-Fehler.";
|
960 | break;
|
961 | case 51:
|
962 | errorDescription = "Avalanche-Spannung der Laserdiode konnte nicht eingestellt werden; Ursache kann\n1. Fremdlich oder\n2. Hardware-Fehler sein.";
|
963 | break;
|
964 | case 52:
|
965 | errorDescription = "Laserstrom zu hoch / defekter Laser.";
|
966 | break;
|
967 | case 53:
|
968 | errorDescription = "Ein oder mehrere Parameter im EEPROM nicht gesetzt (Folge: Division durch 0).";
|
969 | break;
|
970 | case 54:
|
971 | errorDescription = "Hardwarefehler (PLL).";
|
972 | break;
|
973 | case 55:
|
974 | errorDescription = "Hardwarefehler.";
|
975 | break;
|
976 | case 61:
|
977 | errorDescription = "Verwendeter Parameter unzulässig; ungültiges Komando gesendet.";
|
978 | break;
|
979 | case 62:
|
980 | errorDescription = "1. Hardwarefehler\n2. Falscher Wert in Schnittstellenkommunikation (Paritätsfehler SIO).";
|
981 | break;
|
982 | case 63:
|
983 | errorDescription = "Überlauf SIO.";
|
984 | break;
|
985 | case 64:
|
986 | errorDescription = "Framing-Error SIO.";
|
987 | break;
|
988 | default:
|
989 | errorDescription = "Es trat ein unbekannter Fehler auf.";
|
990 | break;
|
991 | }
|
992 |
|
993 | return errorDescription;
|
994 | }
|
995 |
|
996 | /// <summary>
|
997 | /// Prüft, ob es sich um einen Fehler der optoNCDT ILR 1181 / 1182 handelt.
|
998 | /// </summary>
|
999 | /// <param name="value">Der String, der geprüft werden soll.</param>
|
1000 | /// <returns>Wahr, falls es sich um einen gültigen Fehlercode handelt.</returns>
|
1001 | public static bool IsErrorCode(this string value)
|
1002 | {
|
1003 | Int16 errorCode;
|
1004 |
|
1005 | // Stimmt das Format?
|
1006 | if (value.Length != 3)
|
1007 | return false;
|
1008 |
|
1009 | if (value.Left(1) == "E" && Int16.TryParse(value.Substring(1, 2), out errorCode))
|
1010 | {
|
1011 | // Es handelt sich um das Format "E##"
|
1012 | return true;
|
1013 | }
|
1014 | else {
|
1015 |
|
1016 | return false;
|
1017 | }
|
1018 | }
|
1019 |
|
1020 | /// <summary>
|
1021 | /// Gibt die angegebene Anzahl von Zeichen von links aus einem String zurück.
|
1022 | /// </summary>
|
1023 | /// <param name="value">Der String, der bearbeitet werden soll.</param>
|
1024 | /// <param name="maxLength">Die Länge in Zeichen, die zurückgegeben werden soll.</param>
|
1025 | /// <returns></returns>
|
1026 | public static string Left(this string value, int maxLength)
|
1027 | {
|
1028 | if (string.IsNullOrEmpty(value)) return value;
|
1029 | maxLength = Math.Abs(maxLength);
|
1030 |
|
1031 | return (value.Length <= maxLength
|
1032 | ? value
|
1033 | : value.Substring(0, maxLength)
|
1034 | );
|
1035 | }
|
1036 |
|
1037 | /// <summary>
|
1038 | /// Gibt die angegebene Anzahl von Zeichen von rechts aus einem String zurück.
|
1039 | /// </summary>
|
1040 | /// <param name="value">Der String, der bearbeitet werden soll.</param>
|
1041 | /// <param name="maxLength">Die Länge in Zeichen, die zurückgegeben werden soll.</param>
|
1042 | /// <returns></returns>
|
1043 | public static string Right(this string value, int maxLength)
|
1044 | {
|
1045 | if (string.IsNullOrEmpty(value)) return value;
|
1046 | maxLength = Math.Abs(maxLength);
|
1047 |
|
1048 | return (value.Length <= maxLength
|
1049 | ? value
|
1050 | : value.Substring(value.Length - maxLength, maxLength)
|
1051 | );
|
1052 | }
|
1053 |
|
1054 | /// <summary>
|
1055 | /// Konvertiert eine Zeichenfolge in einen HEX-String
|
1056 | /// </summary>
|
1057 | /// <param name="value">String, der konvertiert werden soll.</param>
|
1058 | /// <returns>Liefert den String in der Form 0x00..0xFF zurück.</returns>
|
1059 | public static string ToHex(this string value)
|
1060 | {
|
1061 | char[] charValues = value.ToCharArray();
|
1062 | string hexOutput = "";
|
1063 |
|
1064 |
|
1065 | foreach (char _eachChar in charValues)
|
1066 | {
|
1067 | int val = Convert.ToInt32(_eachChar);
|
1068 | string buffer = String.Format("{0:X}", val);
|
1069 |
|
1070 | if (buffer.Length == 1)
|
1071 | buffer = "0" + buffer;
|
1072 |
|
1073 | hexOutput += "0x" + buffer + " ";
|
1074 | }
|
1075 |
|
1076 | return hexOutput;
|
1077 | }
|
1078 | }
|
1079 | }
|