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.

valueannotation.w 9.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. @** Annotations for Values.
  2. \noindent In circumstances where a control setting is logged but this setting
  3. changes infrequently and has a small number of possible values, it is sometimes
  4. useful to not log the control values throughout the roast but rather simply add
  5. an annotation when the control value changes. It will commonly be desirable to
  6. also provide an annotation representing the current state at the start of every
  7. batch.
  8. To support this feature, there must be a configuration widget which can be used
  9. to identify which data series should be monitored and what annotations should
  10. be produced for what values.
  11. @<Class declarations@>=
  12. class ValueAnnotationConfWidget : public BasicDeviceConfigurationWidget
  13. {
  14. Q_OBJECT
  15. public:
  16. Q_INVOKABLE ValueAnnotationConfWidget(DeviceTreeModel *model,
  17. const QModelIndex &index);
  18. private slots:
  19. void updateSourceColumn(const QString &source);
  20. void updateAnnotations();
  21. void updateStart(bool noteOnStart);
  22. private:
  23. SaltModel *tablemodel;
  24. };
  25. @ The constructor sets up the configuration interface requesting a source
  26. column name, if an annotation should be emitted at the start of the batch, and
  27. what annotations should be produced for what values.
  28. @<ValueAnnotationConfWidget implementation@>=
  29. ValueAnnotationConfWidget::ValueAnnotationConfWidget(DeviceTreeModel *model,
  30. const QModelIndex &index)
  31. : BasicDeviceConfigurationWidget(model, index),
  32. tablemodel(new SaltModel(2))
  33. {
  34. QFormLayout *layout = new QFormLayout;
  35. QLineEdit *source = new QLineEdit;
  36. layout->addRow(tr("Source column name:"), source);
  37. QCheckBox *noteOnStart = new QCheckBox(tr("Produce Start State Annotation"));
  38. noteOnStart->setChecked(true);
  39. layout->addRow(noteOnStart);
  40. tablemodel->setHeaderData(0, Qt::Horizontal, "Value");
  41. tablemodel->setHeaderData(1, Qt::Horizontal, "Annotation");
  42. QTableView *annotationTable = new QTableView;
  43. annotationTable->setModel(tablemodel);
  44. NumericDelegate *delegate = new NumericDelegate;
  45. annotationTable->setItemDelegateForColumn(0, delegate);
  46. layout->addRow(tr("Annotations for values:"), annotationTable);
  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") == "emitOnStart")
  56. {
  57. noteOnStart->setChecked(node.attribute("value") == "true" ? true : false);
  58. }
  59. else if(node.attribute("name") == "measuredValues")
  60. {
  61. @<Convert numeric array literal to list@>@;
  62. int column = 0;
  63. @<Populate model column from list@>@;
  64. }
  65. else if(node.attribute("name") == "annotations")
  66. {
  67. @<Convert string array literal to list@>@;
  68. int column = 1;
  69. @<Populate model column from string list@>@;
  70. }
  71. }
  72. updateSourceColumn(source->text());
  73. updateStart(noteOnStart->isChecked());
  74. updateAnnotations();
  75. connect(source, SIGNAL(textEdited(QString)), this, SLOT(updateSourceColumn(QString)));
  76. connect(noteOnStart, SIGNAL(toggled(bool)), this, SLOT(updateStart(bool)));
  77. connect(tablemodel, SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(updateAnnotations()));
  78. setLayout(layout);
  79. }
  80. @ While we can re-use code for handling numeric array literals, string array
  81. literals need to be handled a little differently.
  82. @<Convert string array literal to list@>=
  83. QString data = node.attribute("value");
  84. if(data.length() > 3)
  85. {
  86. data.chop(2);
  87. data = data.remove(0, 2);
  88. }
  89. QStringList itemList = data.split(",");
  90. for(int i = 0; i < itemList.size(); i++)
  91. {
  92. itemList[i] = itemList[i].simplified();
  93. }
  94. @ Populating the model must also be done a little differently.
  95. @<Populate model column from string list@>=
  96. for(int i = 0; i < itemList.size(); i++)
  97. {
  98. tablemodel->setData(tablemodel->index(i, column),
  99. QVariant(itemList.at(i)),
  100. Qt::DisplayRole);
  101. }
  102. @ To update the table data, the measued values and annotations are saved in
  103. separate lists.
  104. @<ValueAnnotationConfWidget implementation@>=
  105. void ValueAnnotationConfWidget::updateAnnotations()
  106. {
  107. updateAttribute("measuredValues", tablemodel->arrayLiteral(0, Qt::DisplayRole));
  108. updateAttribute("annotations", tablemodel->arrayLiteral(1, Qt::DisplayRole));
  109. }
  110. @ The other settings are updated based on values passed through the parameter
  111. to the update method.
  112. @<ValueAnnotationConfWidget implementation@>=
  113. void ValueAnnotationConfWidget::updateSourceColumn(const QString &source)
  114. {
  115. updateAttribute("source", source);
  116. }
  117. void ValueAnnotationConfWidget::updateStart(bool noteOnStart)
  118. {
  119. updateAttribute("emitOnStart", noteOnStart ? "true" : "false");
  120. }
  121. @ The widget is registered with the configuration system.
  122. @<Register device configuration widgets@>=
  123. app.registerDeviceConfigurationWidget("valueannotation",
  124. ValueAnnotationConfWidget::staticMetaObject);
  125. @ A NodeInserter is needed to make this widget available.
  126. @<Add annotation control node inserters@>=
  127. NodeInserter *valueAnnotationInserter = new NodeInserter(tr("Value Annotation"),
  128. tr("Value Annotation"),
  129. "valueannotation");
  130. annotationMenu->addAction(valueAnnotationInserter);
  131. connect(valueAnnotationInserter, SIGNAL(triggered(QString, QString)),
  132. this, SLOT(insertChildNode(QString, QString)));
  133. @ While it is possible to implement this feature with |ThresholdDetector|
  134. objects, the code to handle these would be difficult to understand and there
  135. would be excessive overhead in moving measurements through all of these.
  136. Instead, we create a new class that watches for any sort of measurement
  137. change and produces the annotation signals directly.
  138. As measured values are represented as a |double|, a small value should be
  139. provided such that comparisons are not against the value directly but instead
  140. are against the value plus or minus this other small value.
  141. Method names have been chosen to be compatible with the |AnnotationButton|
  142. class.
  143. @<Class declarations@>=
  144. class ValueAnnotation : public QObject
  145. {
  146. Q_OBJECT
  147. public:
  148. ValueAnnotation();
  149. Q_INVOKABLE void setAnnotation(double value, const QString &annotation);
  150. public slots:
  151. void newMeasurement(Measurement measure);
  152. void annotate();
  153. void setAnnotationColumn(int column);
  154. void setTemperatureColumn(int column);
  155. void setTolerance(double epsilon);
  156. signals:
  157. void annotation(QString annotation, int tempcolumn, int notecolumn);
  158. private:
  159. int lastIndex;
  160. int annotationColumn;
  161. int measurementColumn;
  162. QList<double> values;
  163. QStringList annotations;
  164. double tolerance;
  165. };
  166. @ Most of the work of this class happens in the |newMeasurement| method. This
  167. compares the latest measurement with every value that has an associated
  168. annotation. If the value is near enough to a value in the list, the index of
  169. that value is compared with the index of the previous annotation (if any) and
  170. if the indices are different, the appropriate annotation is emitted.
  171. @<ValueAnnotation implementation@>=
  172. void ValueAnnotation::newMeasurement(Measurement measure)
  173. {
  174. for(int i = 0; i < values.size(); i++)
  175. {
  176. if(measure.temperature() > values.at(i) - tolerance &&
  177. measure.temperature() < values.at(i) + tolerance)
  178. {
  179. if(i != lastIndex)
  180. {
  181. lastIndex = i;
  182. emit annotation(annotations.at(i), measurementColumn, annotationColumn);
  183. }
  184. }
  185. }
  186. }
  187. @ Another method is used to emit an annotation matching the current state at
  188. the start of a batch if that is desired. This will not produce any output if
  189. no state has yet been matched.
  190. @<ValueAnnotation implementation@>=
  191. void ValueAnnotation::annotate()
  192. {
  193. if(lastIndex > -1)
  194. {
  195. emit annotation(annotations.at(lastIndex), measurementColumn, annotationColumn);
  196. }
  197. }
  198. @ Values and annotations are added to separate lists with new mappings always
  199. appended. Entries are never removed from these lists.
  200. @<ValueAnnotation implementation@>=
  201. void ValueAnnotation::setAnnotation(double value, const QString &annotation)
  202. {
  203. values.append(value);
  204. annotations.append(annotation);
  205. }
  206. @ The remaining setter methods are trivial similarly trivial.
  207. @<ValueAnnotation implementation@>=
  208. void ValueAnnotation::setAnnotationColumn(int column)
  209. {
  210. annotationColumn = column;
  211. }
  212. void ValueAnnotation::setTemperatureColumn(int column)
  213. {
  214. measurementColumn = column;
  215. }
  216. void ValueAnnotation::setTolerance(double epsilon)
  217. {
  218. tolerance = epsilon;
  219. }
  220. @ This just leaves a trivial constructor.
  221. @<ValueAnnotation implementation@>=
  222. ValueAnnotation::ValueAnnotation() : QObject(),
  223. lastIndex(-1), annotationColumn(2), measurementColumn(1), tolerance(0.05)
  224. {
  225. /* Nothing needs to be done here. */
  226. }
  227. @ This class is exposed to the host environment in the usual way. First with
  228. function prototypes.
  229. @<Function prototypes for scripting@>=
  230. QScriptValue constructValueAnnotation(QScriptContext *context, QScriptEngine *engine);
  231. void setValueAnnotationProperties(QScriptValue value, QScriptEngine *engine);
  232. @ Then setting up a new property of the global object.
  233. @<Set up the scripting engine@>=
  234. constructor = engine->newFunction(constructValueAnnotation);
  235. value = engine->newQMetaObject(&ValueAnnotation::staticMetaObject, constructor);
  236. engine->globalObject().setProperty("ValueAnnotation", value);
  237. @ The implementation of the functions also proceeds as usual.
  238. @<Functions for scripting@>=
  239. QScriptValue constructValueAnnotation(QScriptContext *, QScriptEngine *engine)
  240. {
  241. QScriptValue object = engine->newQObject(new ValueAnnotation);
  242. setValueAnnotationProperties(object, engine);
  243. return object;
  244. }
  245. void setValueAnnotationProperties(QScriptValue value, QScriptEngine *engine)
  246. {
  247. setQObjectProperties(value, engine);
  248. }