Typica is a free program for professional coffee roasters. https://typica.us
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

mergeseries.w 8.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. @** Difference and Mean.
  2. \noindent Some roasters find value in comparing the difference between
  3. measurements on different data series. For example, the difference between
  4. intake and exhaust air or the difference between air and seed temperatures.
  5. These can be expressed as an additional relative temperature series. Similarly,
  6. a roaster might have multiple thermocouples where neither is individually as
  7. reliable as taking the mean between the two. The difference and mean series
  8. allow either of these to be calculated automatically.
  9. Since these are very similar structurally, a common base class is provided
  10. for performing some calculation based on two inputs. Difference and mean
  11. series then only need to override the |calculate()| method.
  12. @<Class declarations@>=
  13. class MergeSeries : public QObject
  14. {
  15. Q_OBJECT
  16. public:
  17. MergeSeries();
  18. public slots:
  19. void in1(Measurement measure);
  20. void in2(Measurement measure);
  21. signals:
  22. void newData(Measurement measure);
  23. protected:
  24. virtual void calculate(Measurement m1, Measurement m2) = 0;
  25. private:
  26. Measurement last1;
  27. Measurement last2;
  28. bool received1;
  29. bool received2;
  30. };
  31. class DifferenceSeries : public MergeSeries
  32. {
  33. Q_OBJECT
  34. public:
  35. DifferenceSeries();
  36. protected:
  37. void calculate(Measurement m1, Measurement m2);
  38. };
  39. class MeanSeries : public MergeSeries
  40. {
  41. Q_OBJECT
  42. public:
  43. MeanSeries();
  44. protected:
  45. void calculate(Measurement m1, Measurement m2);
  46. };
  47. @ Classes derived from |MergeSeries| will wait until there is a measurement
  48. from both series and then call |calculate()| with the most recent measurement
  49. from each. This allows the use of measurements from sources with different
  50. sample rates, though there is no guarantee that measurements with the closest
  51. timestamps will be paired. This is done to minimize latency on the series.
  52. @<MergeSeries implementation@>=
  53. void MergeSeries::in1(Measurement measure)
  54. {
  55. last1 = measure;
  56. received1 = true;
  57. if(received1 && received2)
  58. {
  59. calculate(last1, last2);
  60. received1 = false;
  61. received2 = false;
  62. }
  63. }
  64. void MergeSeries::in2(Measurement measure)
  65. {
  66. last2 = measure;
  67. received2 = true;
  68. if(received1 && received2)
  69. {
  70. calculate(last1, last2);
  71. received1 = false;
  72. received2 = false;
  73. }
  74. }
  75. @ The constructor just needs to set the initial |bool|s to |false|.
  76. @<MergeSeries implementation@>=
  77. MergeSeries::MergeSeries() : QObject(NULL), received1(false), received2(false)
  78. {
  79. /* Nothing needs to be done here. */
  80. }
  81. @ The calculations will emit a new |Measurement| with the calculated
  82. temperature value. If the measurement times are different, the highest value
  83. is used. A difference needs to be treated as a relative measurement whereas a
  84. mean does not.
  85. @<MergeSeries implementation@>=
  86. void DifferenceSeries::calculate(Measurement m1, Measurement m2)
  87. {
  88. Measurement outval(m1.temperature() - m2.temperature(),
  89. (m1.time() > m2.time() ? m1.time() : m2.time()));
  90. outval.insert("relative", true);
  91. emit newData(outval);
  92. }
  93. void MeanSeries::calculate(Measurement m1, Measurement m2)
  94. {
  95. Measurement outval((m1.temperature() + m2.temperature()) / 2,
  96. (m1.time() > m2.time() ? m1.time() : m2.time()));
  97. emit newData(outval);
  98. }
  99. @ Nothing special needs to happen in the constructors.
  100. @<MergeSeries implementation@>=
  101. DifferenceSeries::DifferenceSeries() : MergeSeries()
  102. {
  103. /* Nothing needs to be done here. */
  104. }
  105. MeanSeries::MeanSeries() : MergeSeries()
  106. {
  107. /* Nothing needs to be done here. */
  108. }
  109. @ The base class does not need to be exposed to the host environment, but the
  110. derived classes do.
  111. @<Function prototypes for scripting@>=
  112. QScriptValue constructDifferenceSeries(QScriptContext *context,
  113. QScriptEngine *engine);
  114. QScriptValue constructMeanSeries(QScriptContext *context,
  115. QScriptEngine *engine);
  116. @ The constructors are registered in the usual way.
  117. @<Set up the scripting engine@>=
  118. constructor = engine->newFunction(constructDifferenceSeries);
  119. value = engine->newQMetaObject(&DifferenceSeries::staticMetaObject,
  120. constructor);
  121. engine->globalObject().setProperty("DifferenceSeries", value);
  122. constructor = engine->newFunction(constructMeanSeries);
  123. value = engine->newQMetaObject(&MeanSeries::staticMetaObject, constructor);
  124. engine->globalObject().setProperty("MeanSeries", value);
  125. @ The constructors are trivial.
  126. @<Functions for scripting@>=
  127. QScriptValue constructDifferenceSeries(QScriptContext *, QScriptEngine *engine)
  128. {
  129. QScriptValue object = engine->newQObject(new DifferenceSeries);
  130. setQObjectProperties(object, engine);
  131. return object;
  132. }
  133. QScriptValue constructMeanSeries(QScriptContext *, QScriptEngine *engine)
  134. {
  135. QScriptValue object = engine->newQObject(new MeanSeries);
  136. setQObjectProperties(object, engine);
  137. return object;
  138. }
  139. @ Both of these require configuration, however since these are structurally
  140. identical, rather than create multiple configuration widgets I have opted to
  141. instead have a selector to choose between the two options.
  142. @<Class declarations@>=
  143. class MergeSeriesConfWidget : public BasicDeviceConfigurationWidget
  144. {
  145. Q_OBJECT
  146. public:
  147. Q_INVOKABLE MergeSeriesConfWidget(DeviceTreeModel *model,
  148. const QModelIndex &index);
  149. private slots:
  150. void updateColumn1(const QString &column);
  151. void updateColumn2(const QString &column);
  152. void updateOutput(const QString &column);
  153. void updateType(int type);
  154. };
  155. @ The constructor sets up the user interface.
  156. @<MergeSeriesConfWidget implementation@>=
  157. MergeSeriesConfWidget::MergeSeriesConfWidget(DeviceTreeModel *model,
  158. const QModelIndex &index)
  159. : BasicDeviceConfigurationWidget(model, index)
  160. {
  161. QFormLayout *layout = new QFormLayout;
  162. QComboBox *type = new QComboBox;
  163. type->addItem(tr("Difference"), QVariant("Difference"));
  164. type->addItem(tr("Mean"), QVariant("Mean"));
  165. layout->addRow(tr("Series type:"), type);
  166. QLineEdit *column1 = new QLineEdit;
  167. layout->addRow(tr("First input column name:"), column1);
  168. QLineEdit *column2 = new QLineEdit;
  169. layout->addRow(tr("Second input column name:"), column2);
  170. QLineEdit *output = new QLineEdit;
  171. layout->addRow(tr("Output column name:"), output);
  172. @<Get device configuration data for current node@>@;
  173. for(int i = 0; i < configData.size(); i++)
  174. {
  175. node = configData.at(i).toElement();
  176. if(node.attribute("name") == "type")
  177. {
  178. type->setCurrentIndex(type->findData(node.attribute("value")));
  179. }
  180. else if(node.attribute("name") == "column1")
  181. {
  182. column1->setText(node.attribute("value"));
  183. }
  184. else if(node.attribute("name") == "column2")
  185. {
  186. column2->setText(node.attribute("value"));
  187. }
  188. else if(node.attribute("name") == "output")
  189. {
  190. output->setText(node.attribute("value"));
  191. }
  192. }
  193. updateColumn1(column1->text());
  194. updateColumn2(column2->text());
  195. updateOutput(output->text());
  196. updateType(type->currentIndex());
  197. connect(column1, SIGNAL(textEdited(QString)), this, SLOT(updateColumn1(QString)));
  198. connect(column2, SIGNAL(textEdited(QString)), this, SLOT(updateColumn2(QString)));
  199. connect(output, SIGNAL(textEdited(QString)), this, SLOT(updateOutput(QString)));
  200. connect(type, SIGNAL(currentIndexChanged(int)), this, SLOT(updateType(int)));
  201. setLayout(layout);
  202. }
  203. @ The update methods are trivial.
  204. @<MergeSeriesConfWidget implementation@>=
  205. void MergeSeriesConfWidget::updateColumn1(const QString &column)
  206. {
  207. updateAttribute("column1", column);
  208. }
  209. void MergeSeriesConfWidget::updateColumn2(const QString &column)
  210. {
  211. updateAttribute("column2", column);
  212. }
  213. void MergeSeriesConfWidget::updateOutput(const QString &column)
  214. {
  215. updateAttribute("output", column);
  216. }
  217. void MergeSeriesConfWidget::updateType(int index)
  218. {
  219. switch(index)
  220. {
  221. case 0:
  222. updateAttribute("type", "Difference");
  223. break;
  224. case 1:
  225. updateAttribute("type", "Mean");
  226. break;
  227. default:
  228. break;
  229. }
  230. }
  231. @ This is registered with the configuration system.
  232. @<Register device configuration widgets@>=
  233. app.registerDeviceConfigurationWidget("mergeseries",
  234. MergeSeriesConfWidget::staticMetaObject);
  235. @ This is accessed through the advanced features menu.
  236. @<Add node inserters to advanced features menu@>=
  237. NodeInserter *mergeSeriesInserter = new NodeInserter(tr("Merge Series"),
  238. tr("Merge"),
  239. "mergeseries");
  240. connect(mergeSeriesInserter, SIGNAL(triggered(QString, QString)),
  241. this, SLOT(insertChildNode(QString, QString)));
  242. advancedMenu->addAction(mergeSeriesInserter);
  243. @ The class implementations are currently expanded into |"typica.cpp"|.
  244. @<Class implementations@>=
  245. @<MergeSeriesConfWidget implementation@>
  246. @<MergeSeries implementation@>