Torc  0.1
torccontrol.cpp
Go to the documentation of this file.
1 /* Class TorcControl
2 *
3 * Copyright (C) Mark Kendall 2015-18
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 */
19 
20 // Qt
21 #include <QMetaMethod>
22 
23 // Torc
24 #include "torclogging.h"
25 #include "torccoreutils.h"
26 #include "torccentral.h"
27 #include "torcinput.h"
28 #include "torcoutput.h"
29 #include "../notify/torcnotification.h"
30 #include "torccontrol.h"
31 
36 bool TorcControl::ParseTimeString(const QString &Time, int &Days, int &Hours,
37  int &Minutes, int &Seconds, quint64 &DurationInSeconds)
38 {
39  bool ok = false;
40  int seconds = 0;
41  int minutes = 0;
42  int hours = 0;
43  int days = 0;
44 
45  // take seconds off the end if present
46  QStringList initialsplit = Time.split('.');
47  QString dayshoursminutes = initialsplit[0];
48  QString secondss = initialsplit.size() > 1 ? initialsplit[1] : QStringLiteral("");
49 
50  // parse seconds (seconds are optional)
51  if (!secondss.isEmpty())
52  {
53  int newseconds = secondss.toInt(&ok);
54  if (!ok)
55  {
56  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Failed to parse seconds from '%1'").arg(secondss));
57  return false;
58  }
59 
60  if (newseconds < 0 || newseconds > 59)
61  {
62  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Invalid seconds value '%1'").arg(newseconds));
63  return false;
64  }
65 
66  seconds = newseconds;
67  }
68 
69  // parse the remainder
70  QStringList secondsplit = dayshoursminutes.split(':');
71  int count = secondsplit.size();
72  if (count > 3)
73  {
74  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Cannot parse time from '%1'").arg(dayshoursminutes));
75  return false;
76  }
77 
78  // days - days are from 1 to 7 (Monday to Sunday), consistent with Qt
79  if (count == 3)
80  {
81  QString dayss = secondsplit[0].trimmed();
82  if (!dayss.isEmpty())
83  {
84  int newdays = dayss.toInt(&ok);
85  if (!ok)
86  {
87  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Failed to parse days from '%1'").arg(secondsplit[2]));
88  return false;
89  }
90 
91  // allow 1-365 and zero
92  if (newdays < 0 || newdays > 365)
93  {
94  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Invalid day value '%1'").arg(newdays));
95  return false;
96  }
97 
98  days = newdays;
99  }
100  }
101 
102  // hours 0-23
103  if (count > 1)
104  {
105  QString hourss = secondsplit[count - 2].trimmed();
106  if (!hourss.isEmpty())
107  {
108  int newhours = hourss.toInt(&ok);
109  if (!ok)
110  {
111  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Failed to parse hours from '%1'").arg(secondsplit[1]));
112  return false;
113  }
114 
115  if (newhours < 0 || newhours > 23)
116  {
117  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Invalid hour value '%1'").arg(newhours));
118  return false;
119  }
120 
121  hours = newhours;
122  }
123  }
124 
125  // minutes 0-59
126  QString minutess = secondsplit[count - 1].trimmed();
127  if (!minutess.isEmpty())
128  {
129  int newminutes = minutess.toInt(&ok);
130  if (!ok)
131  {
132  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Failed to parse minutes from '%1'").arg(secondsplit[0]));
133  return false;
134  }
135 
136  if (newminutes < 0 || newminutes > 59)
137  {
138  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Invalid minute values '%1'").arg(newminutes));
139  return false;
140  }
141 
142  minutes = newminutes;
143  }
144 
145  // all is good in the world
146  Days = days;
147  Hours = hours;
148  Minutes = minutes;
149  Seconds = seconds;
150 
151  // duration
152  DurationInSeconds = seconds + (minutes * 60) + (hours * 60 * 60) + (days * 24 * 60 * 60);
153  return true;
154 }
155 
156 QString TorcControl::DurationToString(int Days, quint64 Duration)
157 {
158  return Days > 0 ? QStringLiteral("%1days %2").arg(Days).arg(QTime(0, 0).addSecs(Duration).toString(QStringLiteral("hh:mm.ss"))) :
159  QStringLiteral("%1").arg(QTime(0, 0).addSecs(Duration).toString(QStringLiteral("hh:mm.ss")));
160 }
161 
162 #define BLACKLIST QStringLiteral("SetValue,SetValid,InputValueChanged,InputValidChanged")
163 
170 TorcControl::TorcControl(TorcControl::Type Type, const QVariantMap &Details)
171  : TorcDevice(false, 0, 0, QStringLiteral("Control"), Details),
172  TorcHTTPService(this, QStringLiteral("%1/%2/%3").arg(CONTROLS_DIRECTORY, TorcCoreUtils::EnumToLowerString<TorcControl::Type>(Type), Details.value(QStringLiteral("name")).toString()),
173  Details.value(QStringLiteral("name")).toString(), TorcControl::staticMetaObject, BLACKLIST),
174  m_parsed(false),
175  m_validated(false),
176  m_inputList(),
177  m_outputList(),
178  m_inputs(),
179  m_outputs(),
180  m_inputValues(),
182  m_inputValids(),
183  m_allInputsValid(false)
184 {
185  // parse inputs
186  QVariantMap inputs = Details.value(QStringLiteral("inputs")).toMap();
187  QVariantMap::const_iterator it = inputs.constBegin();
188  for ( ; it != inputs.constEnd(); ++it)
189  if (it.key() == QStringLiteral("device"))
190  m_inputList.append(it.value().toString().trimmed());
191  m_inputList.removeDuplicates();
192 
193  // parse outputs
194  QVariantMap outputs = Details.value(QStringLiteral("outputs")).toMap();
195  it = outputs.constBegin();
196  for (; it != outputs.constEnd(); ++it)
197  if (it.key() == QStringLiteral("device"))
198  m_outputList.append(it.value().toString().trimmed());
199  m_outputList.removeDuplicates();
200 }
201 
203 {
204  QMutexLocker locker(&lock);
205  // lock the device list as well to ensure device aren't deleted while we're using them
206  QMutexLocker locke2(gDeviceListLock);
207 
208  if (!m_parsed)
209  return false;
210 
211  // validate inputs
212  foreach (QString input, m_inputList)
213  {
214  if (input == uniqueId)
215  {
216  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Control '%1' cannot have itself as input").arg(input));
217  return false;
218  }
219 
220  // valid object
221  if (!gDeviceList->contains(input))
222  {
223  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Failed to find input '%1' for '%2'").arg(input, uniqueId));
224  return false;
225  }
226 
227  QObject *object = gDeviceList->value(input);
228 
229  // if input is a control device, it MUST expect this device as an output
230  TorcControl *control = qobject_cast<TorcControl*>(object);
231  if (control && !control->IsKnownOutput(uniqueId))
232  {
233  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Device '%1' does not recognise '%2' as an output")
234  .arg(input, uniqueId));
235  return false;
236  }
237 
238  m_inputs.insert(object, input);
239  }
240 
241  // validate outputs
242  foreach (QString output, m_outputList)
243  {
244  if (output == uniqueId)
245  {
246  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Control '%1' cannot have itself as output").arg(output));
247  return false;
248  }
249 
250  // valid object?
251  if (!gDeviceList->contains(output))
252  {
253  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Failed to find output '%1' for device %2").arg(output, uniqueId));
254  return false;
255  }
256 
257  QObject *object = gDeviceList->value(output);
258 
259  // if TorcOutput, does it already have an owner
260  TorcOutput* out = qobject_cast<TorcOutput*>(object);
261  if (out && out->HasOwner())
262  {
263  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Output '%1' (for control '%2') already has an owner")
264  .arg(out->GetUniqueId(), uniqueId));
265  return false;
266  }
267 
268  // if output is a control device, it MUST expect this as an input
269  TorcControl* control = qobject_cast<TorcControl*>(object);
270  if (control && !control->IsKnownInput(uniqueId))
271  {
272  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Device '%1' does not recognise '%2' as an input")
273  .arg(output, uniqueId));
274  return false;
275  }
276 
277  m_outputs.insert(object, output);
278  }
279 
280  return true;
281 }
282 
285 {
286  return false;
287 }
288 
290 bool TorcControl::AllowInputs(void) const
291 {
292  return true;
293 }
294 
296 {
297  QMutexLocker locker(&lock);
298  if (userName.isEmpty())
299  return uniqueId;
300  return userName;
301 }
302 
303 bool TorcControl::IsKnownInput(const QString &Input) const
304 {
305  if (Input.isEmpty() || !AllowInputs())
306  return false;
307 
308  return m_inputList.contains(Input);
309 }
310 
311 bool TorcControl::IsKnownOutput(const QString &Output) const
312 {
313  if (Output.isEmpty())
314  return false;
315 
316  return m_outputList.contains(Output);
317 }
318 
326 void TorcControl::Graph(QByteArray* Data)
327 {
328  if (!Data)
329  return;
330 
331  QMutexLocker locker(&lock);
332 
333  bool passthrough = IsPassthrough();
334 
335  if (!passthrough)
336  {
337  QString desc;
338  QStringList source = GetDescription();
339  foreach (const QString &item, source)
340  if (!item.isEmpty())
341  desc.append(QString(DEVICE_LINE_ITEM).arg(item));
342  desc.append(QString(DEVICE_LINE_ITEM).arg(tr("Default %1").arg(GetDefaultValue())));
343  desc.append(QString(DEVICE_LINE_ITEM).arg(tr("Valid %1").arg(GetValid())));
344  desc.append(QString(DEVICE_LINE_ITEM).arg(tr("Value %1").arg(GetValue())));
345 
346  Data->append(QStringLiteral(" \"%1\" [shape=record id=\"%1\" label=<<B>%2</B>%3>];\r\n").arg(uniqueId, userName.isEmpty() ? uniqueId : userName, desc));
347  }
348 
349  QMap<QObject*,QString>::const_iterator it = m_outputs.constBegin();
350  for ( ; it != m_outputs.constEnd(); ++it)
351  {
352  if (qobject_cast<TorcOutput*>(it.key()))
353  {
354  TorcOutput* output = qobject_cast<TorcOutput*>(it.key());
355  QString source = passthrough ? qobject_cast<TorcInput*>(m_inputs.firstKey())->GetUniqueId() : uniqueId;
356  Data->append(QStringLiteral(" \"%1\"->\"%2\"\r\n").arg(source, output->GetUniqueId()));
357  }
358  else if (qobject_cast<TorcControl*>(it.key()))
359  {
360  TorcControl* control = qobject_cast<TorcControl*>(it.key());
361  Data->append(QStringLiteral(" \"%1\"->\"%2\"\r\n").arg(uniqueId, control->GetUniqueId()));
362  }
363  else if (qobject_cast<TorcNotification*>(it.key()))
364  {
365  // handled in TorcNotification
366  }
367  else
368  {
369  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Unknown output type"));
370  }
371  }
372 
373  it = m_inputs.constBegin();
374  for ( ; it != m_inputs.constEnd(); ++it)
375  {
376  if (passthrough)
377  continue;
378 
379  QString inputid;
380  if (qobject_cast<TorcControl*>(it.key()))
381  inputid = qobject_cast<TorcControl*>(it.key())->GetUniqueId();
382  else if (qobject_cast<TorcInput*>(it.key()))
383  inputid = qobject_cast<TorcInput*>(it.key())->GetUniqueId();
384  else
385  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Unknown input type"));
386 
387  Data->append(QStringLiteral(" \"%1\"->\"%2\"\r\n").arg(inputid, uniqueId));
388  }
389 }
390 
407 {
408  QMutexLocker locker(&lock);
409 
410  QMap<QObject*,QString>::const_iterator it = m_outputs.constBegin();
411  for ( ; it != m_outputs.constEnd(); ++it)
412  {
413  // an output must be a type that accepts inputs
414  // i.e. a TorcOutput, most TorcControl types and some TorcNotification types
415  if (qobject_cast<TorcOutput*>(it.key()))
416  {
417  TorcOutput* output = qobject_cast<TorcOutput*>(it.key());
418  // there can be only one owner... though this is already checked in Validate
419  if (!output->SetOwner(this))
420  {
421  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Cannot set control '%1' as owner of output '%2'")
422  .arg(uniqueId, output->GetUniqueId()));
423  return false;
424  }
425 
426  connect(this, &TorcControl::ValueChanged, output, &TorcOutput::SetValue, Qt::UniqueConnection);
427  connect(this, &TorcControl::ValidChanged, output, &TorcOutput::SetValid, Qt::UniqueConnection);
428  }
429  else if (qobject_cast<TorcControl*>(it.key()))
430  {
431  TorcControl* control = qobject_cast<TorcControl*>(it.key());
432 
433  // this will also check whether the control allows inputs
434  if (!control->IsKnownInput(uniqueId))
435  {
436  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Control '%1' does not recognise '%2' as an input")
437  .arg(control->GetUniqueId(), uniqueId));
438  return false;
439  }
440 
441  connect(this, &TorcControl::ValidChanged, control, &TorcControl::InputValidChanged, Qt::UniqueConnection);
442  connect(this, &TorcControl::ValueChanged, control, &TorcControl::InputValueChanged, Qt::UniqueConnection);
443  }
444  else if (qobject_cast<TorcNotification*>(it.key()))
445  {
446  // NB TorcNotification connects itself to the control. We only really check this
447  // to ensure all control outputs are valid and a control may only exist to trigger
448  // a notification.
449  TorcNotification* notification = qobject_cast<TorcNotification*>(it.key());
450  if (!notification->IsKnownInput(uniqueId))
451  {
452  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Notification '%1' does not recognise '%2' as an input")
453  .arg(notification->GetUniqueId(), uniqueId));
454  return false;
455  }
456  }
457  else
458  {
459  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Unknown output type for '%1'").arg(uniqueId));
460  return false;
461  }
462  }
463 
464  it = m_inputs.constBegin();
465  for ( ; it != m_inputs.constEnd(); ++it)
466  {
467  TorcControl *control = qobject_cast<TorcControl*>(it.key());
468  TorcInput *input = qobject_cast<TorcInput*>(it.key());
469 
470  // an input must be a sensor or control
471  if (!control && !input)
472  {
473  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Unknown input type"));
474  return false;
475  }
476 
477  // if input is a control, it must recognise this control as an output
478  if (control && !control->IsKnownOutput(uniqueId))
479  {
480  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Control '%1' does not recognise control '%2' as an output")
481  .arg(control->GetUniqueId(), uniqueId));
482  return false;
483  }
484 
485  if (control)
486  {
487  connect(control, &TorcControl::ValidChanged, this, &TorcControl::InputValidChanged, Qt::UniqueConnection);
488  connect(control, &TorcControl::ValueChanged, this, &TorcControl::InputValueChanged, Qt::UniqueConnection);
489  }
490  else
491  {
492  connect(input, &TorcInput::ValidChanged, this, &TorcControl::InputValidChanged, Qt::UniqueConnection);
493  connect(input, &TorcInput::ValueChanged, this, &TorcControl::InputValueChanged, Qt::UniqueConnection);
494  }
495  m_inputValids.insert(it.key(), false);
496  }
497 
498  LOG(VB_GENERAL, LOG_DEBUG, QStringLiteral("%1: Ready").arg(uniqueId));
499  m_validated = true;
500  return true;
501 }
502 
503 void TorcControl::SubscriberDeleted(QObject *Subscriber)
504 {
506 }
507 
509 {
510  QMutexLocker locker(&lock);
511 
512  if (!m_parsed || !m_validated)
513  return;
514 
515  QObject *input = sender();
516 
517  // a valid input
518  if (!input)
519  return;
520 
521  // a known input
522  if (!m_inputs.contains(input))
523  return;
524 
525  // ignore known values
526  if (m_inputValids.contains(input) && m_inputValids.value(input) == true)
527  if (m_inputValues.contains(input) && qFuzzyCompare(m_inputValues.value(input) + 1.0, Value + 1.0))
528  return;
529 
530  // set the last value if known. Otherwise set to new which will not trigger any change.
531  double lastvalue = m_inputValues.contains(input) ? m_inputValues.value(input) : Value;
532 
533  m_inputValues[input] = Value;
534  m_lastInputValues[input] = lastvalue;
535 
536  // as for sensors, setting an input value is assumed to make the input valid
537  InputValidChangedPriv(input, true);
538 
539  // check for an update to the output
541 }
542 
544 {
545  QMutexLocker locker(&lock);
546 
547  if (!m_parsed || !m_validated)
548  return;
549 
550  bool isvalid = m_validated && m_allInputsValid && (m_inputList.size() == m_inputValues.size());
551 
552  SetValid(isvalid);
553  if (!isvalid)
554  return;
555 
556  // the important bit
557  CalculateOutput();
558 }
559 
561 {
562  QMutexLocker locker(&lock);
563 
564  if (!m_parsed || !m_validated)
565  return;
566 
567  QObject* input = sender();
568  if (input)
569  {
570  InputValidChangedPriv(input, Valid);
572  }
573 }
574 
575 void TorcControl::InputValidChangedPriv(QObject *Input, bool Valid)
576 {
577  if (!m_parsed || !m_validated)
578  return;
579 
580  // a valid input
581  if (!Input)
582  return;
583 
584  // a known input
585  if (!m_inputs.contains(Input))
586  return;
587 
588  // ignore known values
589  if (m_inputValids.contains(Input) && m_inputValids.value(Input) == Valid)
590  return;
591 
592  m_inputValids[Input] = Valid;
593  m_allInputsValid = Valid;
594 
595  if (!Valid)
596  {
597  m_inputValues.remove(Input);
598  m_lastInputValues.remove(Input);
599  }
600  else
601  {
602  foreach(bool inputvalid, m_inputValids)
603  m_allInputsValid &= inputvalid;
604  }
605 }
606 
607 void TorcControl::SetValue(double Value)
608 {
609  QMutexLocker locker(&lock);
610 
611  if (m_parsed && m_validated)
612  TorcDevice::SetValue(Value);
613 }
614 
615 void TorcControl::SetValid(bool Valid)
616 {
617  QMutexLocker locker(&lock);
618 
619  if (m_parsed && m_validated)
620  {
621  // important!!
622  // do this before SetValid as setting a value automatically sets validity.
623  if (!Valid)
625 
626  TorcDevice::SetValid(Valid);
627  }
628 }
629 
630 bool TorcControl::CheckForCircularReferences(const QString &UniqueId, const QString &Path) const
631 {
632  if (UniqueId.isEmpty())
633  {
634  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Invalid UniqueId"));
635  return false;
636  }
637 
638  QString path = Path;
639  if (!path.isEmpty())
640  path += QStringLiteral("->");
641  path += uniqueId;
642 
643  // iterate over the outputs list
644  QMap<QObject*,QString>::const_iterator it = m_outputs.constBegin();
645  for ( ; it != m_outputs.constEnd(); ++it)
646  {
647  // check first for a direct match with the output
648  if (it.value() == UniqueId)
649  {
650  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Circular reference found: %1->%2").arg(path, UniqueId));
651  return false;
652  }
653 
654  // and then ask each output to check
655  TorcControl *control = dynamic_cast<TorcControl*>(it.key());
656  if (control)
657  if (!control->CheckForCircularReferences(UniqueId, path))
658  return false;
659  }
660 
661  return true;
662 }
static bool ParseTimeString(const QString &Time, int &Days, int &Hours, int &Minutes, int &Seconds, quint64 &DurationInSeconds)
Parse a Torc time string into days, hours, minutes and, if present, seconds.
Definition: torccontrol.cpp:36
bool IsKnownInput(const QString &Input) const
#define BLACKLIST
void ValueChanged(double Value)
double GetDefaultValue(void)
Definition: torcdevice.cpp:149
virtual bool Validate(void)
void InputValidChangedPriv(QObject *Input, bool Valid)
QString uniqueId
Definition: torcdevice.h:63
virtual void SetValid(bool Valid)
Definition: torcdevice.cpp:101
void InputValidChanged(bool Valid)
QMap< QObject *, QString > m_inputs
Definition: torccontrol.h:71
bool GetValid(void)
Definition: torcdevice.cpp:135
bool m_validated
Definition: torccontrol.h:68
#define DEVICE_LINE_ITEM
Definition: torcdevice.h:16
virtual void CalculateOutput(void)=0
QMap< QObject *, double > m_inputValues
Definition: torccontrol.h:73
bool m_allInputsValid
Definition: torccontrol.h:76
void InputValueChanged(double Value)
static QMutex * gDeviceListLock
Definition: torcdevice.h:69
virtual bool AllowInputs(void) const
Most controls have an input side.
void SubscriberDeleted(QObject *Subscriber)
QMap< QObject *, double > m_lastInputValues
Definition: torccontrol.h:74
bool m_parsed
Definition: torccontrol.h:67
void SetValid(bool Valid) override
static QHash< QString, TorcDevice * > * gDeviceList
Definition: torcdevice.h:68
double defaultValue
Definition: torcdevice.h:61
virtual void SetValue(double Value)
Definition: torcdevice.cpp:115
void SetValue(double Value) override
QMutex lock
Definition: torcdevice.h:66
QStringList m_inputList
Definition: torccontrol.h:69
static QString DurationToString(int Days, quint64 Duration)
double value
Definition: torcdevice.h:60
bool IsKnownOutput(const QString &Output) const
void HandleSubscriberDeleted(QObject *Subscriber)
virtual bool IsPassthrough(void)
Only certain logic controls can be passthrough.
void Graph(QByteArray *Data)
Add this control to the state graph.
void CheckInputValues(void)
#define CONTROLS_DIRECTORY
Definition: torccontrol.h:13
QString GetUIName(void) override
bool Finish(void)
Finish setup of the control.
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: torclogging.h:20
bool CheckForCircularReferences(const QString &UniqueId, const QString &Path) const
QMap< QObject *, bool > m_inputValids
Definition: torccontrol.h:75
QString EnumToLowerString(T Value)
Definition: torccoreutils.h:18
QString userName
Definition: torcdevice.h:64
void ValidChanged(bool Valid)
bool HasOwner(void)
Definition: torcoutput.cpp:52
QStringList m_outputList
Definition: torccontrol.h:70
QMap< QObject *, QString > m_outputs
Definition: torccontrol.h:72
virtual bool IsKnownInput(const QString &UniqueId)
double GetValue(void)
Definition: torcdevice.cpp:142
bool SetOwner(QObject *Owner)
Definition: torcoutput.cpp:66
TorcControl(TorcControl::Type Type, const QVariantMap &Details)
QString GetUniqueId(void)
Definition: torcdevice.cpp:162