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.

thresholdannotation.w 7.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. @** Threshold Annotations.
  2. \noindent Value annotations are fine for cases where we want to capture the
  3. fact that a control series has changed to some specific value, but there are
  4. times when it is more useful to know when a data series has passed through some
  5. value in a particular direction even if the exact value of interest is never
  6. directly recorded. For example, it would be possible to automatically mark a
  7. point near turnaround by watching for a rate of temperature change ascending
  8. through 0. This could also be set up to match range timers and mark events of
  9. interest that begin at consistent temperatures.
  10. As usual, this is a feature that must be configured on a per roaster basis.
  11. @<Class declarations@>=
  12. class ThresholdAnnotationConfWidget : public BasicDeviceConfigurationWidget
  13. {
  14. Q_OBJECT
  15. public:
  16. Q_INVOKABLE ThresholdAnnotationConfWidget(DeviceTreeModel *model,
  17. const QModelIndex &index);
  18. private slots:
  19. void updateSourceColumn(const QString &source);
  20. void updateThreshold(double value);
  21. void updateDirection(int index);
  22. void updateAnnotation(const QString &note);
  23. };
  24. @ The configuration widget needs to provide fields for determining which data
  25. series should be used to generate the annotation, the value that the
  26. |ThresholdDetector| should use as its trigger, the direction this should fire
  27. on, and the text of the annotation.
  28. @<ThresholdAnnotationConfWidget implementation@>=
  29. ThresholdAnnotationConfWidget::ThresholdAnnotationConfWidget(DeviceTreeModel *model,
  30. const QModelIndex &index)
  31. : BasicDeviceConfigurationWidget(model, index)
  32. {
  33. QFormLayout *layout = new QFormLayout;
  34. QLineEdit *source = new QLineEdit;
  35. layout->addRow(tr("Source column name:"), source);
  36. QDoubleSpinBox *value = new QDoubleSpinBox;
  37. value->setMinimum(-9999.99);
  38. value->setMaximum(9999.99);
  39. value->setDecimals(2);
  40. layout->addRow(tr("Threshold value:"), value);
  41. QComboBox *direction = new QComboBox;
  42. direction->addItem(tr("Ascending"));
  43. direction->addItem(tr("Descending"));
  44. layout->addRow(tr("Direction:"), direction);
  45. QLineEdit *annotation = new QLineEdit;
  46. layout->addRow(tr("Annotation:"), annotation);
  47. @<Get device configuration data for current node@>@;
  48. for(int i = 0; i < configData.size(); i++)
  49. {
  50. node = configData.at(i).toElement();
  51. if(node.attribute("name") == "source")
  52. {
  53. source->setText(node.attribute("value"));
  54. }
  55. else if(node.attribute("name") == "value")
  56. {
  57. value->setValue(node.attribute("value").toDouble());
  58. }
  59. else if(node.attribute("name") == "direction")
  60. {
  61. direction->setCurrentIndex(node.attribute("value").toInt());
  62. }
  63. else if(node.attribute("name") == "annotation")
  64. {
  65. annotation->setText(node.attribute("value"));
  66. }
  67. }
  68. updateSourceColumn(source->text());
  69. updateThreshold(value->value());
  70. updateDirection(direction->currentIndex());
  71. updateAnnotation(annotation->text());
  72. connect(source, SIGNAL(textEdited(QString)), this, SLOT(updateSourceColumn(QString)));
  73. connect(value, SIGNAL(valueChanged(double)), this, SLOT(updateThreshold(double)));
  74. connect(direction, SIGNAL(currentIndexChanged(int)), this, SLOT(updateDirection(int)));
  75. connect(annotation, SIGNAL(textEdited(QString)), this, SLOT(updateAnnotation(QString)));
  76. setLayout(layout);
  77. }
  78. @ Configuration of the model is done as usual.
  79. @<ThresholdAnnotationConfWidget implementation@>=
  80. void ThresholdAnnotationConfWidget::updateSourceColumn(const QString &source)
  81. {
  82. updateAttribute("source", source);
  83. }
  84. void ThresholdAnnotationConfWidget::updateThreshold(double value)
  85. {
  86. updateAttribute("value", QString("%1").arg(value));
  87. }
  88. void ThresholdAnnotationConfWidget::updateDirection(int direction)
  89. {
  90. updateAttribute("direction", QString("%1").arg(direction));
  91. }
  92. void ThresholdAnnotationConfWidget::updateAnnotation(const QString &annotation)
  93. {
  94. updateAttribute("annotation", annotation);
  95. }
  96. @ The configurationwidget is registered with the configuration system as usual.
  97. @<Register device configuration widgets@>=
  98. app.registerDeviceConfigurationWidget("thresholdannotation",
  99. ThresholdAnnotationConfWidget::staticMetaObject);
  100. @ A NodeInserter makes the configuration available.
  101. @<Add annotation control node inserters@>=
  102. NodeInserter *thresholdAnnotationInserter = new NodeInserter(tr("Threshold Annotation"),
  103. tr("Threshold Annotation"),
  104. "thresholdannotation");
  105. annotationMenu->addAction(thresholdAnnotationInserter);
  106. connect(thresholdAnnotationInserter, SIGNAL(triggered(QString, QString)),
  107. this, SLOT(insertChildNode(QString, QString)));
  108. @ While we could use |ThresholdDetector| in the configuration directly, it is
  109. easier to provide another class with the same interface as |AnnotationButton|
  110. to leverage existing code for handling these.
  111. @<Class declarations@>=
  112. class Annotator : public QObject
  113. {@t\1@>@/
  114. Q_OBJECT@;
  115. QString note;
  116. int tc;
  117. int ac;
  118. QTimer t;
  119. public:
  120. Annotator(const QString &text);@/
  121. @t\4@>public slots@t\kern-3pt@>:@/
  122. void setAnnotation(const QString &annotation);
  123. void setTemperatureColumn(int tempcolumn);
  124. void setAnnotationColumn(int annotationcolumn);
  125. void annotate();
  126. private slots:
  127. void catchTimer();
  128. signals:@/
  129. void annotation(QString annotation, int tempcolumn, int notecolumn);@t\2@>@/
  130. }@t\kern-3pt@>;
  131. @ To use this class with a |ThresholdDetector|, simply connect the
  132. |timeForValue()| signal to the |annotate()| slot and use the existing
  133. |AnnotationButton| code to keep the columns up to date.
  134. @<Annotator implementation@>=
  135. Annotator::Annotator(const QString &text) : QObject(NULL), note(text)
  136. {
  137. t.setInterval(0);
  138. t.setSingleShot(true);
  139. connect(&t, SIGNAL(timeout()), this, SLOT(catchTimer()));
  140. }
  141. void Annotator::setAnnotation(const QString &annotation)
  142. {
  143. note = annotation;
  144. }
  145. void Annotator::setTemperatureColumn(int tempcolumn)
  146. {
  147. tc = tempcolumn;
  148. }
  149. void Annotator::setAnnotationColumn(int annotationcolumn)
  150. {
  151. ac = annotationcolumn;
  152. }
  153. @ When connecting a |ThresholdDetector| to an |Annotator| directly, the
  154. annotation can be recorded before the measurement reaches the log. The result
  155. of this is that the annotation appears with the measurement immediately before
  156. the one it should appear next to. To solve this, the annotation is delayed
  157. until the next iteration of the event loop.
  158. @<Annotator implementation@>=
  159. void Annotator::catchTimer()
  160. {
  161. emit annotation(note, tc, ac);
  162. }
  163. void Annotator::annotate()
  164. {
  165. t.start();
  166. }
  167. @ It must be possible to create these from a script.
  168. @<Function prototypes for scripting@>=
  169. QScriptValue constructAnnotator(QScriptContext *context,
  170. QScriptEngine *engine);
  171. void setAnnotatorProperties(QScriptValue value, QScriptEngine *engine);
  172. @ The engine is informed of the constructor.
  173. @<Set up the scripting engine@>=
  174. constructor = engine->newFunction(constructAnnotator);
  175. value = engine->newQMetaObject(&Annotator::staticMetaObject, constructor);
  176. engine->globalObject().setProperty("Annotator", value);
  177. @ The implementation is trivial.
  178. @<Functions for scripting@>=
  179. QScriptValue constructAnnotator(QScriptContext *context, QScriptEngine *engine)
  180. {
  181. QScriptValue object = engine->newQObject(new Annotator(argument<QString>(0, context)));
  182. setAnnotatorProperties(object, engine);
  183. return object;
  184. }
  185. void setAnnotatorProperties(QScriptValue value, QScriptEngine *engine)
  186. {
  187. setQObjectProperties(value, engine);
  188. }