Torc  0.1
torclogiccontrol.cpp
Go to the documentation of this file.
1 /* Class TorcLogicControl
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 <QtGlobal>
22 
23 // Torc
24 #include "torclogging.h"
25 #include "torcinput.h"
26 #include "torcoutput.h"
27 #include "torclogiccontrol.h"
28 
30 {
31  QString operation = Operation.toUpper();
32 
33  if ("EQUAL" == operation) return TorcLogicControl::Equal;
34  if ("LESSTHAN" == operation) return TorcLogicControl::LessThan;
35  if ("LESSTHANOREQUAL" == operation) return TorcLogicControl::LessThanOrEqual;
36  if ("GREATERTHAN" == operation) return TorcLogicControl::GreaterThan;
37  if ("GREATERTHANOREQUAL" == operation) return TorcLogicControl::GreaterThanOrEqual;
38  if ("ANY" == operation) return TorcLogicControl::Any;
39  if ("ALL" == operation) return TorcLogicControl::All;
40  if ("NONE" == operation) return TorcLogicControl::None;
41  if ("AVERAGE" == operation) return TorcLogicControl::Average;
42  if ("PASSTHROUGH" == operation) return TorcLogicControl::Passthrough;
43  if ("TOGGLE" == operation) return TorcLogicControl::Toggle;
44  if ("INVERT" == operation) return TorcLogicControl::Invert;
45  if ("MAXIMUM" == operation) return TorcLogicControl::Maximum;
46  if ("MINIMUM" == operation) return TorcLogicControl::Minimum;
47  if ("MULTIPLY" == operation) return TorcLogicControl::Multiply;
48  if ("RUNNINGAVERAGE" == operation) return TorcLogicControl::RunningAverage;
49  if ("RUNNINGMAX" == operation) return TorcLogicControl::RunningMax;
50  if ("RUNNINGMIN" == operation) return TorcLogicControl::RunningMin;
51 
52 
54 }
55 
57 {
58  if (Type == TorcLogicControl::Equal ||
66  {
67  return true;
68  }
69 
70  return false;
71 }
72 
73 TorcLogicControl::TorcLogicControl(const QString &Type, const QVariantMap &Details)
74  : TorcControl(TorcControl::Logic, Details),
75  m_operation(TorcLogicControl::StringToOperation(Type)),
76  m_referenceDeviceId(),
77  m_referenceDevice(nullptr),
78  m_inputDevice(nullptr),
79  m_triggerDeviceId(),
80  m_triggerDevice(nullptr),
81  m_average(),
82  m_firstRunningValue(true),
83  m_runningValue(0)
84 {
85  if (m_operation == TorcLogicControl::UnknownLogicType)
86  {
87  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Unrecognised control operation '%1' for device '%2'").arg(Type, uniqueId));
88  return;
89  }
90 
91  // these operations require a valid value to operate against
92  if (IsComplexType(m_operation))
93  {
94  bool found = false;
95  if (Details.contains(QStringLiteral("references")))
96  {
97  QVariantMap reference = Details.value(QStringLiteral("references")).toMap();
98  if (reference.contains(QStringLiteral("device")))
99  {
100  m_referenceDeviceId = reference.value(QStringLiteral("device")).toString().trimmed();
101  // and treat it as a normal input - this ensures TorcControl takes care of all of the input value and
102  // valid logic. We just ensure we know which is the reference device/value.
103  m_inputList.append(m_referenceDeviceId);
104  found = true;
105  }
106  }
107  if (!found)
108  {
109  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Control '%1' has no reference device for operation").arg(uniqueId));
110  return;
111  }
112 
113  if (m_operation == TorcLogicControl::RunningAverage)
114  {
115  // trigger device is used to update the average
116  found = false;
117  if (Details.contains(QStringLiteral("triggers")))
118  {
119  QVariantMap triggers = Details.value(QStringLiteral("triggers")).toMap();
120  if (triggers.contains(QStringLiteral("device")))
121  {
122  m_triggerDeviceId = triggers.value(QStringLiteral("device")).toString().trimmed();
123  m_inputList.append(m_triggerDeviceId);
124  found = true;
125  }
126  }
127 
128  if (!found)
129  {
130  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Control '%1' has no trigger device for updating").arg(uniqueId));
131  return;
132  }
133  }
134  else if (m_operation == TorcLogicControl::RunningMin)
135  {
136  m_runningValue = std::numeric_limits<double>::max();
137  }
138  else if (m_operation == TorcLogicControl::RunningMax)
139  {
140  m_runningValue = std::numeric_limits<double>::min();
141  }
142  }
143 
144  // everything appears to be valid at this stage
145  m_inputList.removeDuplicates();
146  m_parsed = true;
147 }
148 
150 {
151  return TorcControl::Logic;
152 }
153 
155 {
156  QStringList result;
157  QString reference(QStringLiteral("Unknown"));
158  TorcDevice *device = qobject_cast<TorcDevice*>(m_referenceDevice);
159  if (device)
160  {
161  reference = device->GetUserName();
162  if (reference.isEmpty())
163  reference = device->GetUniqueId();
164  }
165 
166  switch (m_operation)
167  {
169  result.append(tr("Equal to '%1'").arg(reference));
170  break;
172  result.append(tr("Less than '%1'").arg(reference));
173  break;
175  result.append(tr("Less than or equal to '%1'").arg(reference));
176  break;
178  result.append(tr("Greater than '%1'").arg(reference));
179  break;
181  result.append(tr("Greater than or equal to '%1'").arg(reference));
182  break;
184  result.append(tr("Any"));
185  break;
187  result.append(tr("All"));
188  break;
190  result.append(tr("None"));
191  break;
193  result.append(tr("Average"));
194  break;
196  result.append(tr("Toggle"));
197  break;
199  result.append(tr("Invert"));
200  break;
202  result.append(tr("Passthrough"));
203  break;
205  result.append(tr("Maximum"));
206  break;
208  result.append(tr("Minimum"));
209  break;
211  result.append(tr("Multiply"));
212  break;
214  result.append(tr("Running average"));
215  break;
217  result.append(tr("Running max"));
218  break;
220  result.append(tr("Running min"));
221  break;
223  result.append(tr("Unknown"));
224  break;
225  }
226 
227  return result;
228 }
229 
231 {
232  QMutexLocker locker(&lock);
233 
234  bool passthrough = false;
235  if ((m_operation == TorcLogicControl::Passthrough) && (m_inputs.size() == 1))
236  {
237  // check the input
238  if (qobject_cast<TorcInput*>(m_inputs.firstKey()))
239  {
240  // and the outputs
241  passthrough = true;
242  QMap<QObject*,QString>::const_iterator it = m_outputs.constBegin();
243  for ( ; it != m_outputs.constEnd(); ++it)
244  passthrough &= (bool)qobject_cast<TorcOutput*>(it.key());
245  }
246  }
247 
248  return passthrough;
249 }
250 
252 {
253  QMutexLocker locker(&lock);
254 
255  // don't repeat validation
256  if (m_validated)
257  return true;
258 
259  // common checks
260  if (!TorcControl::Validate())
261  return false;
262 
263  // we always need one or more outputs
264  if (m_outputs.isEmpty())
265  {
266  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Device '%1' needs at least one output").arg(uniqueId));
267  return false;
268  }
269 
270  switch (m_operation)
271  {
279  {
280  if (m_inputs.size() < 2)
281  {
282  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("%1 has %2 inputs for operation '%3' (needs at least 2) - ignoring.")
283  .arg(uniqueId).arg(m_inputs.size()).arg(GetDescription().join(',')));
284  return false;
285  }
286  }
287  break;
291  {
292  if (m_inputs.size() != 1)
293  {
294  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("%1 has %2 inputs for operation '%3' (must have 1) - ignoring.")
295  .arg(uniqueId).arg(m_inputs.size()).arg(GetDescription().join(',')));
296  return false;
297  }
298  }
299  break;
307  // these should have one defined input and one reference device that is handled as an input
308  {
309  if (m_inputs.size() != 2)
310  {
311  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("%1 has %2 inputs for operation '%3' (must have 1 input and 1 reference) - ignoring.")
312  .arg(uniqueId).arg(m_inputs.size()).arg(GetDescription().join(',')));
313  return false;
314  }
315  }
316  break;
318  // one input, one reference (reset) and one trigger (update)
319  {
320  if (m_inputs.size() != 3)
321  {
322  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("%1 has %2 inputs for operation '%3' (must have 1 input, 1 reference and 1 trigger) - ignoring.")
323  .arg(uniqueId).arg(m_inputs.size()).arg(GetDescription().join(',')));
324  return false;
325  }
326  }
327  break;
329  break; // should be unreachable
330  }
331 
332  // 'reference' devices must be valid if TorcControl::Validate is happy
333  if (IsComplexType(m_operation))
334  {
335  m_referenceDevice = gDeviceList->value(m_referenceDeviceId);
336  if (!m_referenceDevice)
337  return false;
338  if (TorcLogicControl::RunningAverage == m_operation)
339  {
340  m_triggerDevice = gDeviceList->value(m_triggerDeviceId);
341  if (!m_triggerDevice)
342  return false;
343  QList<QObject*> inputs = m_inputs.uniqueKeys();
344  foreach(QObject *device, inputs)
345  {
346  if (device != m_referenceDevice && device != m_triggerDevice)
347  {
348  m_inputDevice = device;
349  break;
350  }
351  }
352  }
353  else
354  {
355  m_inputDevice = m_referenceDevice == m_inputs.firstKey() ? m_inputs.lastKey() : m_inputs.firstKey();
356  }
357  if (!m_inputDevice)
358  return false;
359  }
360 
361  // if we get this far, we can finish the device
362  if (!Finish())
363  return false;
364 
365  return true;
366 }
367 
368 void TorcLogicControl::CalculateOutput(void)
369 {
370  QMutexLocker locker(&lock);
371 
372  double newvalue = value; // no change by default
373 
374  double referencevalue = 0.0;
375  double inputvalue = 0.0;
376  double triggervalue = 0.0;
377 
378  if ((TorcLogicControl::RunningAverage == m_operation) && m_triggerDevice)
379  triggervalue = m_inputValues.value(m_triggerDevice);
380 
381  if (IsComplexType(m_operation) && m_inputDevice && m_referenceDevice)
382  {
383  inputvalue = m_inputValues.value(m_inputDevice);
384  referencevalue = m_inputValues.value(m_referenceDevice);
385  }
386 
387  switch (m_operation)
388  {
390  // We do not update for a change in value - only when triggered. Reference resets.
391  // NB trigger and reset can both be high at the same time - which should probably be avoided
392  if (referencevalue) // reset
393  {
394  m_average.Reset();
395  newvalue = m_average.GetAverage();
396  }
397  // trigger
398  if (triggervalue)
399  {
400  newvalue = m_average.AddValue(inputvalue);
401  }
402  break;
404  // RunningMax/Min are updated each time the input value changes, reset when triggered via reference
405  // and always set on first pass.
406  // We don't reset to max/min double as inputvalue will provide a valid start point.
407  if (referencevalue || m_firstRunningValue || (inputvalue > m_runningValue))
408  {
409  m_runningValue = newvalue = inputvalue;
410  m_firstRunningValue = false;
411  }
412  break;
414  if (referencevalue || m_firstRunningValue || (inputvalue < m_runningValue))
415  {
416  m_runningValue = newvalue = inputvalue;
417  m_firstRunningValue = false;
418  }
419  break;
421  newvalue = m_inputValues.constBegin().value();
422  break;
424  {
425  // must be multiple range/pwm values that are combined
426  // if binary inputs are used, this will equate to the opposite of Any.
427  double start = 1.0;
428  foreach(double next, m_inputValues)
429  start *= next;
430  newvalue = start;
431  }
432  break;
434  // single value ==
435  newvalue = qFuzzyCompare(inputvalue + 1.0, referencevalue + 1.0) ? 1 : 0;
436  break;
438  // single value <
439  newvalue = inputvalue < referencevalue ? 1 : 0;
440  break;
442  // Use qFuzzyCompare for = ?
443  // single input <=
444  newvalue = inputvalue <= referencevalue ? 1 : 0;
445  break;
447  // single input >
448  newvalue = inputvalue > referencevalue ? 1 : 0;
449  break;
451  // single input >=
452  newvalue = inputvalue >= referencevalue ? 1 : 0;
453  break;
455  {
456  // multiple binary (on/off) inputs that must all be 1/On/non-zero
457  bool on = true;
458  foreach (double next, m_inputValues)
459  on &= !qFuzzyCompare(next + 1.0, 1.0);
460  newvalue = on ? 1 : 0;
461  }
462  break;
465  {
466  // multiple binary (on/off) inputs
467  bool on = false;
468  foreach (double next, m_inputValues)
469  on |= !qFuzzyCompare(next + 1.0, 1.0);
470  if (m_operation == TorcLogicControl::Any)
471  newvalue = on ? 1 : 0;
472  else
473  newvalue = on ? 0 : 1;
474  }
475  break;
477  {
478  // does exactly what it says on the tin
479  double average = 0;
480  foreach (double next, m_inputValues)
481  average += next;
482  newvalue = average / m_inputValues.size();
483  }
484  break;
486  {
487  // the output is toggled for every 'rising' input (i.e. when the input changes from
488  // a value that is less than 1 to a value that is greater than or equal to 1
489  if (m_lastInputValues.constBegin().value() < 1.0 && m_inputValues.constBegin().value() >= 1.0)
490  newvalue = value >= 1.0 ? 0.0 : 1.0;
491  }
492  break;
494  {
495  newvalue = m_inputValues.constBegin().value() < 1.0 ? 1.0 : 0.0;
496  }
497  break;
499  {
500  double max = 0;
501  foreach (double next, m_inputValues)
502  if (next > max)
503  max = next;
504  newvalue = max;
505  }
506  break;
508  {
509  double min = qInf();
510  foreach (double next, m_inputValues)
511  if (next < min)
512  min = next;
513  newvalue = min;
514  }
515  break;
517  break;
518  }
519  SetValue(newvalue);
520 }
bool IsPassthrough(void) override
Only certain logic controls can be passthrough.
double GetAverage(void)
Definition: torcmaths.h:38
QString GetUserName(void)
Definition: torcdevice.cpp:168
virtual bool Validate(void)
QString uniqueId
Definition: torcdevice.h:63
TorcLogicControl(const QString &Type, const QVariantMap &Details)
QMap< QObject *, QString > m_inputs
Definition: torccontrol.h:71
bool m_validated
Definition: torccontrol.h:68
QMap< QObject *, double > m_inputValues
Definition: torccontrol.h:73
QMap< QObject *, double > m_lastInputValues
Definition: torccontrol.h:74
bool Validate(void) override
bool m_parsed
Definition: torccontrol.h:67
static QHash< QString, TorcDevice * > * gDeviceList
Definition: torcdevice.h:68
VERBOSE_PREAMBLE true
void Reset(void)
Definition: torcmaths.h:43
void SetValue(double Value) override
double AddValue(T Value)
Definition: torcmaths.h:22
QMutex lock
Definition: torcdevice.h:66
QStringList m_inputList
Definition: torccontrol.h:69
double value
Definition: torcdevice.h:60
static TorcLogicControl::Operation StringToOperation(const QString &Operation)
bool Finish(void)
Finish setup of the control.
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: torclogging.h:20
QStringList GetDescription(void) override
bool IsComplexType(TorcLogicControl::Operation Type)
QMap< QObject *, QString > m_outputs
Definition: torccontrol.h:72
TorcControl::Type GetType(void) const override
QString GetUniqueId(void)
Definition: torcdevice.cpp:162