Typica is a free program for professional coffee roasters. https://typica.us
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

scales.w 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498
  1. @** Collecting Measurements from Scales.
  2. \noindent When a computer connected scale is available, it can be useful to
  3. eliminate manual transcription from the data entry for batches. In general it
  4. is difficult to determine where a measurement should go automatically, but
  5. there are a number of situations where the ability to drag a measurement from a
  6. label and drop it to whatever input widget is appropriate would be a useful
  7. operation to support.
  8. To support this, we need to subclass |QLabel| to allow it to initiate a drag
  9. and drop operation.
  10. @(draglabel.h@>=
  11. #ifndef TypicaDragLabelInclude
  12. #define TypicaDragLabelInclude
  13. #include <QLabel>
  14. class DragLabel : public QLabel
  15. {
  16. Q_OBJECT
  17. public:
  18. explicit DragLabel(const QString &labelText, QWidget *parent = NULL);
  19. protected:
  20. void mousePressEvent(QMouseEvent *event);
  21. };
  22. #endif
  23. @ The font size of the label is increased by default to make it easier to
  24. manipulate on a touch screen. Otherwise, there is little to do in this class.
  25. @(draglabel.cpp@>=
  26. #include "draglabel.h"
  27. #include <QDrag>
  28. #include <QMouseEvent>
  29. DragLabel::DragLabel(const QString &labelText, QWidget *parent) :
  30. QLabel(labelText, parent)
  31. {
  32. QFont labelFont = font();
  33. labelFont.setPointSize(14);
  34. setFont(labelFont);
  35. }
  36. void DragLabel::mousePressEvent(QMouseEvent *event)
  37. {
  38. if(event->button() == Qt::LeftButton)
  39. {
  40. QDrag *drag = new QDrag(this);
  41. QMimeData *mimeData = new QMimeData;
  42. mimeData->setText(text());
  43. drag->setMimeData(mimeData);
  44. drag->exec();
  45. }
  46. }
  47. @ We require the ability to create these labels from the host environment.
  48. First we include the appropriate header.
  49. @<Header files to include@>=
  50. #include "draglabel.h"
  51. @ Next, a pair of function prototypes.
  52. @<Function prototypes for scripting@>=
  53. QScriptValue constructDragLabel(QScriptContext *context, QScriptEngine *engine);
  54. void setDragLabelProperties(QScriptValue value, QScriptEngine *engine);
  55. @ These are made known to the host environment as usual.
  56. @<Set up the scripting engine@>=
  57. constructor = engine->newFunction(constructDragLabel);
  58. value = engine->newQMetaObject(&DragLabel::staticMetaObject, constructor);
  59. engine->globalObject().setProperty("DragLabel", value);
  60. @ The implementation is trivial.
  61. @<Functions for scripting@>=
  62. QScriptValue constructDragLabel(QScriptContext *context, QScriptEngine *engine)
  63. {
  64. QScriptValue object;
  65. QString labelText = "";
  66. if(context->argumentCount() == 1)
  67. {
  68. labelText = argument<QString>(0, context);
  69. }
  70. object = engine->newQObject(new DragLabel(labelText));
  71. setDragLabelProperties(object, engine);
  72. return object;
  73. }
  74. void setDragLabelProperties(QScriptValue value, QScriptEngine *engine)
  75. {
  76. setQLabelProperties(value, engine);
  77. }
  78. @ An object is also required to communicate with a scale. This is responsible
  79. for setting up a connection over a serial port, sending commands out to the
  80. scale, buffering and interpreting the response, and signaling new measurements.
  81. @(scale.h@>=
  82. #ifndef TypicaScaleInclude
  83. #define TypicaScaleInclude
  84. #include "3rdparty/qextserialport/src/qextserialport.h"
  85. #include "units.h"
  86. class SerialScale : public QextSerialPort
  87. {
  88. Q_OBJECT
  89. public:
  90. SerialScale(const QString &port);
  91. public slots:
  92. void tare();
  93. void weigh();
  94. signals:
  95. void newMeasurement(double weight, Units::Unit unit);
  96. private slots:
  97. void dataAvailable();
  98. private:
  99. QByteArray responseBuffer;
  100. };
  101. #endif
  102. @ The constructor tells the port that this should be event driven and connects
  103. a signal to buffer data..
  104. @(scale.cpp@>=
  105. #include "scale.h"
  106. #include <QStringList>
  107. SerialScale::SerialScale(const QString &port) :
  108. QextSerialPort(port, QextSerialPort::EventDriven)
  109. {
  110. connect(this, SIGNAL(readyRead()), this, SLOT(dataAvailable()));
  111. }
  112. @ The |dataAvailable| method handles buffering incoming data and processing
  113. responses when they have come in. Serial port communications are likely to be
  114. very slow in comparison to everything else so it is likely that only one
  115. character will come in at a time.
  116. Note that this currently only understands single line output and a limited
  117. selection of units.
  118. @(scale.cpp@>=
  119. void SerialScale::dataAvailable()
  120. {
  121. responseBuffer.append(readAll());
  122. if(responseBuffer.contains("\x0D"))
  123. {
  124. if(responseBuffer.contains("!"))
  125. {
  126. responseBuffer.clear();
  127. }
  128. else
  129. {
  130. @<Process weight measurement@>@;
  131. responseBuffer.clear();
  132. }
  133. }
  134. }
  135. @ Each line of data consists of an optional sign character possibly followed
  136. by a space followed by characters representing a number followed by a
  137. space followed by characters indicating a unit. This may be preceeded and
  138. followed by a variable amount of white space. To process a new measurement, we
  139. must remove the excess white space, split the number from the unit, prepend the
  140. sign to the number if it is present, convert the string representing the number
  141. to a numeric type, and determine which unit the measurement is in.
  142. \medskip
  143. \settabs 8 \columns
  144. \+&&&{\tt |"lb"|}&|Units::Pound|\cr
  145. \+&&&{\tt |"kg"|}&|Units::Kilogram|\cr
  146. \+&&&{\tt |"g"|}&|Units::Gram|\cr
  147. \+&&&{\tt |"oz"|}&|Units::Ounce|\cr
  148. \smallskip
  149. \centerline{Table \secno: Unit Strings and Representative Unit Enumeration}
  150. \medskip
  151. @<Process weight measurement@>=
  152. QStringList responseParts = QString(responseBuffer.simplified()).split(' ');
  153. if(responseParts.size() > 2)
  154. {
  155. responseParts.removeFirst();
  156. responseParts.replace(0, QString("-%1").arg(responseParts[0]));
  157. }
  158. double weight = responseParts[0].toDouble();
  159. Units::Unit unit = Units::Unitless;
  160. if(responseParts[1] == "lb")
  161. {
  162. unit = Units::Pound;
  163. }
  164. else if(responseParts[1] == "kg")
  165. {
  166. unit = Units::Kilogram;
  167. }
  168. else if(responseParts[1] == "g")
  169. {
  170. unit = Units::Gram;
  171. }
  172. else if(responseParts[1] == "oz")
  173. {
  174. unit = Units::Ounce;
  175. }
  176. emit newMeasurement(weight, unit);
  177. @ Two methods are used to send commands to the scale. I am unsure of how well
  178. standardized remote key operation of scales are. The class may need to be
  179. extended to support more devices.
  180. @(scale.cpp@>=
  181. void SerialScale::tare()
  182. {
  183. write("!KT\x0D");
  184. }
  185. void SerialScale::weigh()
  186. {
  187. write("!KP\x0D");
  188. }
  189. @ This must be available to the host environment.
  190. @<Function prototypes for scripting@>=
  191. QScriptValue constructSerialScale(QScriptContext *context, QScriptEngine *engine);
  192. void setSerialScaleProperties(QScriptValue value, QScriptEngine *engine);
  193. @ These functions are made known to the scripting engine in the usual way.
  194. @<Set up the scripting engine@>=
  195. constructor = engine->newFunction(constructSerialScale);
  196. value = engine->newQMetaObject(&SerialScale::staticMetaObject, constructor);
  197. engine->globalObject().setProperty("SerialScale", value);
  198. @ If we are to set up the serial ports from the host environment, a few
  199. enumerated types must be made known to the meta-object system.
  200. @<Class declarations@>=
  201. Q_DECLARE_METATYPE(BaudRateType)
  202. Q_DECLARE_METATYPE(DataBitsType)
  203. Q_DECLARE_METATYPE(ParityType)
  204. Q_DECLARE_METATYPE(StopBitsType)
  205. Q_DECLARE_METATYPE(FlowType)
  206. @ For each of these, a pair of functions converts values to script values and
  207. back. This is a very annoying aspect of the version of QextSerialPort currently
  208. used by \pn{}.
  209. @<Function prototypes for scripting@>=
  210. QScriptValue BaudRateType_toScriptValue(QScriptEngine *engine, const BaudRateType &value);
  211. void BaudRateType_fromScriptValue(const QScriptValue &sv, BaudRateType &value);
  212. QScriptValue DataBitsType_toScriptValue(QScriptEngine *engine, const DataBitsType &value);
  213. void DataBitsType_fromScriptValue(const QScriptValue &sv, DataBitsType &value);
  214. QScriptValue ParityType_toScriptValue(QScriptEngine *engine, const ParityType &value);
  215. void ParityType_fromScriptValue(const QScriptValue &sv, ParityType &value);
  216. QScriptValue StopBitsType_toScriptValue(QScriptEngine *engine, const StopBitsType &value);
  217. void StopBitsType_fromScriptValue(const QScriptValue &sv, StopBitsType &value);
  218. QScriptValue FlowType_toScriptValue(QScriptEngine *engine, const FlowType &value);
  219. void FlowType_fromScriptValue(const QScriptValue &sv, FlowType &value);
  220. @ These are implemented thusly.
  221. @<Functions for scripting@>=
  222. QScriptValue BaudRateType_toScriptValue(QScriptEngine *engine, const BaudRateType &value)
  223. {
  224. return engine->newVariant(QVariant((int)(value)));
  225. }
  226. void BaudRateType_fromScriptValue(const QScriptValue &sv, BaudRateType &value)
  227. {
  228. value = (BaudRateType)(sv.toVariant().toInt());
  229. }
  230. QScriptValue DataBitsType_toScriptValue(QScriptEngine *engine, const DataBitsType &value)
  231. {
  232. return engine->newVariant(QVariant((int)(value)));
  233. }
  234. void DataBitsType_fromScriptValue(const QScriptValue &sv, DataBitsType &value)
  235. {
  236. value = (DataBitsType)(sv.toVariant().toInt());
  237. }
  238. QScriptValue ParityType_toScriptValue(QScriptEngine *engine, const ParityType &value)
  239. {
  240. return engine->newVariant(QVariant((int)(value)));
  241. }
  242. void ParityType_fromScriptValue(const QScriptValue &sv, ParityType &value)
  243. {
  244. value = (ParityType)(sv.toVariant().toInt());
  245. }
  246. QScriptValue StopBitsType_toScriptValue(QScriptEngine *engine, const StopBitsType &value)
  247. {
  248. return engine->newVariant(QVariant((int)(value)));
  249. }
  250. void StopBitsType_fromScriptValue(const QScriptValue &sv, StopBitsType &value)
  251. {
  252. value = (StopBitsType)(sv.toVariant().toInt());
  253. }
  254. QScriptValue FlowType_toScriptValue(QScriptEngine *engine, const FlowType &value)
  255. {
  256. return engine->newVariant(QVariant((int)(value)));
  257. }
  258. void FlowType_fromScriptValue(const QScriptValue &sv, FlowType &value)
  259. {
  260. value = (FlowType)(sv.toVariant().toInt());
  261. }
  262. @ These conversion functions are then registered.
  263. @<Set up the scripting engine@>=
  264. qScriptRegisterMetaType(engine, BaudRateType_toScriptValue, BaudRateType_fromScriptValue);
  265. qScriptRegisterMetaType(engine, DataBitsType_toScriptValue, DataBitsType_fromScriptValue);
  266. qScriptRegisterMetaType(engine, ParityType_toScriptValue, ParityType_fromScriptValue);
  267. qScriptRegisterMetaType(engine, StopBitsType_toScriptValue, StopBitsType_fromScriptValue);
  268. qScriptRegisterMetaType(engine, FlowType_toScriptValue, FlowType_fromScriptValue);
  269. @ In order to make this class available to the host environment, we must also
  270. include the appropriate header file.
  271. @<Header files to include@>=
  272. #include "scale.h"
  273. @ Most of the properties of interest should be added automatically, however
  274. there are non-slot methods in |QIODevice| that we require.
  275. @<Functions for scripting@>=
  276. void setSerialScaleProperties(QScriptValue value, QScriptEngine *engine)
  277. {
  278. setQIODeviceProperties(value, engine);
  279. }
  280. @ The script constructor should seem familiar.
  281. @<Functions for scripting@>=
  282. QScriptValue constructSerialScale(QScriptContext *context, QScriptEngine *engine)
  283. {
  284. QScriptValue object;
  285. if(context->argumentCount() == 1)
  286. {
  287. object = engine->newQObject(new SerialScale(argument<QString>(0, context)));
  288. setSerialScaleProperties(object, engine);
  289. }
  290. else
  291. {
  292. context->throwError("Incorrect number of arguments passed to "
  293. "SerialScale. The constructor takes one string "
  294. "as an argument specifying a port name.");
  295. }
  296. return object;
  297. }
  298. @ In order to allow configuration of scales from within \pn{}, a configuration
  299. widget must be provided.
  300. @<Class declarations@>=
  301. class SerialScaleConfWidget : public BasicDeviceConfigurationWidget
  302. {
  303. Q_OBJECT
  304. public:
  305. Q_INVOKABLE SerialScaleConfWidget(DeviceTreeModel *model,
  306. const QModelIndex &index);
  307. private slots:
  308. void updatePort(const QString &newPort);
  309. void updateBaudRate(const QString &rate);
  310. void updateParity(int index);
  311. void updateFlowControl(int index);
  312. void updateStopBits(int index);
  313. private:
  314. PortSelector *port;
  315. BaudSelector *baud;
  316. ParitySelector *parity;
  317. FlowSelector *flow;
  318. StopSelector *stop;
  319. };
  320. @ This is very similar to other configuration widgets.
  321. @<SerialScaleConfWidget implementation@>=
  322. SerialScaleConfWidget::SerialScaleConfWidget(DeviceTreeModel *model,
  323. const QModelIndex &index)
  324. : BasicDeviceConfigurationWidget(model, index),
  325. port(new PortSelector), baud(new BaudSelector), parity(new ParitySelector),
  326. flow(new FlowSelector), stop(new StopSelector)
  327. {
  328. QFormLayout *layout = new QFormLayout;
  329. layout->addRow(tr("Port:"), port);
  330. connect(port, SIGNAL(currentIndexChanged(QString)),
  331. this, SLOT(updatePort(QString)));
  332. connect(port, SIGNAL(editTextChanged(QString)),
  333. this, SLOT(updatePort(QString)));
  334. layout->addRow(tr("Baud:"), baud);
  335. connect(baud, SIGNAL(currentIndexChanged(QString)),
  336. this, SLOT(updateBaudRate(QString)));
  337. layout->addRow(tr("Parity:"), parity);
  338. connect(parity, SIGNAL(currentIndexChanged(int)),
  339. this, SLOT(updateParity(int)));
  340. layout->addRow(tr("Flow Control:"), flow);
  341. connect(flow, SIGNAL(currentIndexChanged(int)),
  342. this, SLOT(updateFlowControl(int)));
  343. layout->addRow(tr("Stop Bits:"), stop);
  344. connect(stop, SIGNAL(currentIndexChanged(int)),
  345. this, SLOT(updateStopBits(int)));
  346. @<Get device configuration data for current node@>@;
  347. for(int i = 0; i < configData.size(); i++)
  348. {
  349. node = configData.at(i).toElement();
  350. if(node.attribute("name") == "port")
  351. {
  352. int j = port->findText(node.attribute("value"));
  353. if(j >= 0)
  354. {
  355. port->setCurrentIndex(j);
  356. }
  357. else
  358. {
  359. port->insertItem(0, node.attribute("value"));
  360. port->setCurrentIndex(0);
  361. }
  362. }
  363. else if(node.attribute("name") == "baudrate")
  364. {
  365. baud->setCurrentIndex(baud->findText(node.attribute("value")));
  366. }
  367. else if(node.attribute("name") == "parity")
  368. {
  369. parity->setCurrentIndex(parity->findData(node.attribute("value")));
  370. }
  371. else if(node.attribute("name") == "flowcontrol")
  372. {
  373. flow->setCurrentIndex(flow->findData(node.attribute("value")));
  374. }
  375. else if(node.attribute("name") == "stopbits")
  376. {
  377. stop->setCurrentIndex(stop->findData(node.attribute("value")));
  378. }
  379. }
  380. updatePort(port->currentText());
  381. updateBaudRate(baud->currentText());
  382. updateParity(parity->currentIndex());
  383. updateFlowControl(flow->currentIndex());
  384. updateStopBits(stop->currentIndex());
  385. setLayout(layout);
  386. }
  387. @ Update methods are the same as were used in |ModbusRtuPortConfWidget|.
  388. @<SerialScaleConfWidget implementation@>=
  389. void SerialScaleConfWidget::updatePort(const QString &newPort)
  390. {
  391. updateAttribute("port", newPort);
  392. }
  393. void SerialScaleConfWidget::updateBaudRate(const QString &rate)
  394. {
  395. updateAttribute("baudrate", rate);
  396. }
  397. void SerialScaleConfWidget::updateParity(int index)
  398. {
  399. updateAttribute("parity", parity->itemData(index).toString());
  400. }
  401. void SerialScaleConfWidget::updateFlowControl(int index)
  402. {
  403. updateAttribute("flowcontrol", flow->itemData(index).toString());
  404. }
  405. void SerialScaleConfWidget::updateStopBits(int index)
  406. {
  407. updateAttribute("stopbits", stop->itemData(index).toString());
  408. }
  409. @ The configuration widget is registered with the configuration system.
  410. @<Register device configuration widgets@>=
  411. app.registerDeviceConfigurationWidget("scale", SerialScaleConfWidget::staticMetaObject);
  412. @ A |NodeInserter| is also added.
  413. @<Register top level device configuration nodes@>=
  414. inserter = new NodeInserter(tr("Serial Scale"), tr("Scale"), "scale", NULL);
  415. topLevelNodeInserters.append(inserter);