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.

scales.w 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560
  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. void setWeighCommand(const QString &command);
  95. void setCommandTerminator(const QString &terminator);
  96. signals:
  97. void newMeasurement(double weight, Units::Unit unit);
  98. private slots:
  99. void dataAvailable();
  100. private:
  101. QByteArray responseBuffer;
  102. QByteArray weighCommand;
  103. QByteArray commandTerminator;
  104. };
  105. #endif
  106. @ The constructor tells the port that this should be event driven and connects
  107. a signal to buffer data..
  108. @(scale.cpp@>=
  109. #include "scale.h"
  110. #include <QStringList>
  111. SerialScale::SerialScale(const QString &port) :
  112. QextSerialPort(port, QextSerialPort::EventDriven)
  113. {
  114. connect(this, SIGNAL(readyRead()), this, SLOT(dataAvailable()));
  115. }
  116. @ The |dataAvailable| method handles buffering incoming data and processing
  117. responses when they have come in. Serial port communications are likely to be
  118. very slow in comparison to everything else so it is likely that only one
  119. character will come in at a time.
  120. Note that this currently only understands single line output and a limited
  121. selection of units.
  122. @(scale.cpp@>=
  123. void SerialScale::dataAvailable()
  124. {
  125. responseBuffer.append(readAll());
  126. if(responseBuffer.contains("\x0D"))
  127. {
  128. if(responseBuffer.contains("!"))
  129. {
  130. responseBuffer.clear();
  131. }
  132. else
  133. {
  134. @<Process weight measurement@>@;
  135. responseBuffer.clear();
  136. }
  137. }
  138. }
  139. @ Each line of data consists of an optional sign character possibly followed
  140. by a space followed by characters representing a number followed by a
  141. space followed by characters indicating a unit. This may be preceeded and
  142. followed by a variable amount of white space. To process a new measurement, we
  143. must remove the excess white space, split the number from the unit, prepend the
  144. sign to the number if it is present, convert the string representing the number
  145. to a numeric type, and determine which unit the measurement is in.
  146. \medskip
  147. \settabs 8 \columns
  148. \+&&&{\tt |"lb"|}&|Units::Pound|\cr
  149. \+&&&{\tt |"kg"|}&|Units::Kilogram|\cr
  150. \+&&&{\tt |"g"|}&|Units::Gram|\cr
  151. \+&&&{\tt |"oz"|}&|Units::Ounce|\cr
  152. \smallskip
  153. \centerline{Table \secno: Unit Strings and Representative Unit Enumeration}
  154. \medskip
  155. @<Process weight measurement@>=
  156. QStringList responseParts = QString(responseBuffer.simplified()).split(' ');
  157. if(responseParts.size() > 2)
  158. {
  159. responseParts.removeFirst();
  160. responseParts.replace(0, QString("-%1").arg(responseParts[0]));
  161. }
  162. double weight = responseParts[0].toDouble();
  163. Units::Unit unit = Units::Unitless;
  164. if(responseParts[1].compare("lb", Qt::CaseInsensitive) == 0)
  165. {
  166. unit = Units::Pound;
  167. }
  168. else if(responseParts[1].compare("kg", Qt::CaseInsensitive) == 0)
  169. {
  170. unit = Units::Kilogram;
  171. }
  172. else if(responseParts[1].compare("g", Qt::CaseInsensitive) == 0)
  173. {
  174. unit = Units::Gram;
  175. }
  176. else if(responseParts[1].compare("oz", Qt::CaseInsensitive) == 0)
  177. {
  178. unit = Units::Ounce;
  179. }
  180. emit newMeasurement(weight, unit);
  181. @ Two methods are used to send commands to the scale. I am unsure of how well
  182. standardized remote key operation of scales are. The class may need to be
  183. extended to support more devices.
  184. @(scale.cpp@>=
  185. void SerialScale::tare()
  186. {
  187. write("!KT\x0D");
  188. }
  189. void SerialScale::weigh()
  190. {
  191. //write("!KP\x0D");
  192. write(weighCommand + commandTerminator);
  193. }
  194. void SerialScale::setWeighCommand(const QString &command)
  195. {
  196. weighCommand = command.toAscii();
  197. }
  198. void SerialScale::setCommandTerminator(const QString &terminator)
  199. {
  200. if(terminator == "CRLF")
  201. {
  202. commandTerminator = "\x0D\x0A";
  203. }
  204. else if(terminator == "CR")
  205. {
  206. commandTerminator = "\x0D";
  207. }
  208. else if(terminator == "LF")
  209. {
  210. commandTerminator = "\x0A";
  211. }
  212. }
  213. @ This must be available to the host environment.
  214. @<Function prototypes for scripting@>=
  215. QScriptValue constructSerialScale(QScriptContext *context, QScriptEngine *engine);
  216. void setSerialScaleProperties(QScriptValue value, QScriptEngine *engine);
  217. @ These functions are made known to the scripting engine in the usual way.
  218. @<Set up the scripting engine@>=
  219. constructor = engine->newFunction(constructSerialScale);
  220. value = engine->newQMetaObject(&SerialScale::staticMetaObject, constructor);
  221. engine->globalObject().setProperty("SerialScale", value);
  222. @ If we are to set up the serial ports from the host environment, a few
  223. enumerated types must be made known to the meta-object system.
  224. @<Class declarations@>=
  225. Q_DECLARE_METATYPE(BaudRateType)
  226. Q_DECLARE_METATYPE(DataBitsType)
  227. Q_DECLARE_METATYPE(ParityType)
  228. Q_DECLARE_METATYPE(StopBitsType)
  229. Q_DECLARE_METATYPE(FlowType)
  230. @ For each of these, a pair of functions converts values to script values and
  231. back. This is a very annoying aspect of the version of QextSerialPort currently
  232. used by \pn{}.
  233. @<Function prototypes for scripting@>=
  234. QScriptValue BaudRateType_toScriptValue(QScriptEngine *engine, const BaudRateType &value);
  235. void BaudRateType_fromScriptValue(const QScriptValue &sv, BaudRateType &value);
  236. QScriptValue DataBitsType_toScriptValue(QScriptEngine *engine, const DataBitsType &value);
  237. void DataBitsType_fromScriptValue(const QScriptValue &sv, DataBitsType &value);
  238. QScriptValue ParityType_toScriptValue(QScriptEngine *engine, const ParityType &value);
  239. void ParityType_fromScriptValue(const QScriptValue &sv, ParityType &value);
  240. QScriptValue StopBitsType_toScriptValue(QScriptEngine *engine, const StopBitsType &value);
  241. void StopBitsType_fromScriptValue(const QScriptValue &sv, StopBitsType &value);
  242. QScriptValue FlowType_toScriptValue(QScriptEngine *engine, const FlowType &value);
  243. void FlowType_fromScriptValue(const QScriptValue &sv, FlowType &value);
  244. @ These are implemented thusly.
  245. @<Functions for scripting@>=
  246. QScriptValue BaudRateType_toScriptValue(QScriptEngine *engine, const BaudRateType &value)
  247. {
  248. return engine->newVariant(QVariant((int)(value)));
  249. }
  250. void BaudRateType_fromScriptValue(const QScriptValue &sv, BaudRateType &value)
  251. {
  252. value = (BaudRateType)(sv.toVariant().toInt());
  253. }
  254. QScriptValue DataBitsType_toScriptValue(QScriptEngine *engine, const DataBitsType &value)
  255. {
  256. return engine->newVariant(QVariant((int)(value)));
  257. }
  258. void DataBitsType_fromScriptValue(const QScriptValue &sv, DataBitsType &value)
  259. {
  260. value = (DataBitsType)(sv.toVariant().toInt());
  261. }
  262. QScriptValue ParityType_toScriptValue(QScriptEngine *engine, const ParityType &value)
  263. {
  264. return engine->newVariant(QVariant((int)(value)));
  265. }
  266. void ParityType_fromScriptValue(const QScriptValue &sv, ParityType &value)
  267. {
  268. value = (ParityType)(sv.toVariant().toInt());
  269. }
  270. QScriptValue StopBitsType_toScriptValue(QScriptEngine *engine, const StopBitsType &value)
  271. {
  272. return engine->newVariant(QVariant((int)(value)));
  273. }
  274. void StopBitsType_fromScriptValue(const QScriptValue &sv, StopBitsType &value)
  275. {
  276. value = (StopBitsType)(sv.toVariant().toInt());
  277. }
  278. QScriptValue FlowType_toScriptValue(QScriptEngine *engine, const FlowType &value)
  279. {
  280. return engine->newVariant(QVariant((int)(value)));
  281. }
  282. void FlowType_fromScriptValue(const QScriptValue &sv, FlowType &value)
  283. {
  284. value = (FlowType)(sv.toVariant().toInt());
  285. }
  286. @ These conversion functions are then registered.
  287. @<Set up the scripting engine@>=
  288. qScriptRegisterMetaType(engine, BaudRateType_toScriptValue, BaudRateType_fromScriptValue);
  289. qScriptRegisterMetaType(engine, DataBitsType_toScriptValue, DataBitsType_fromScriptValue);
  290. qScriptRegisterMetaType(engine, ParityType_toScriptValue, ParityType_fromScriptValue);
  291. qScriptRegisterMetaType(engine, StopBitsType_toScriptValue, StopBitsType_fromScriptValue);
  292. qScriptRegisterMetaType(engine, FlowType_toScriptValue, FlowType_fromScriptValue);
  293. @ In order to make this class available to the host environment, we must also
  294. include the appropriate header file.
  295. @<Header files to include@>=
  296. #include "scale.h"
  297. @ Most of the properties of interest should be added automatically, however
  298. there are non-slot methods in |QIODevice| that we require.
  299. @<Functions for scripting@>=
  300. void setSerialScaleProperties(QScriptValue value, QScriptEngine *engine)
  301. {
  302. setQIODeviceProperties(value, engine);
  303. }
  304. @ The script constructor should seem familiar.
  305. @<Functions for scripting@>=
  306. QScriptValue constructSerialScale(QScriptContext *context, QScriptEngine *engine)
  307. {
  308. QScriptValue object;
  309. if(context->argumentCount() == 1)
  310. {
  311. object = engine->newQObject(new SerialScale(argument<QString>(0, context)));
  312. setSerialScaleProperties(object, engine);
  313. }
  314. else
  315. {
  316. context->throwError("Incorrect number of arguments passed to "
  317. "SerialScale. The constructor takes one string "
  318. "as an argument specifying a port name.");
  319. }
  320. return object;
  321. }
  322. @ In order to allow configuration of scales from within \pn{}, a configuration
  323. widget must be provided.
  324. @<Class declarations@>=
  325. class SerialScaleConfWidget : public BasicDeviceConfigurationWidget
  326. {
  327. Q_OBJECT
  328. public:
  329. Q_INVOKABLE SerialScaleConfWidget(DeviceTreeModel *model,
  330. const QModelIndex &index);
  331. private slots:
  332. void updatePort(const QString &newPort);
  333. void updateBaudRate(const QString &rate);
  334. void updateParity(int index);
  335. void updateFlowControl(int index);
  336. void updateStopBits(int index);
  337. void updateWeighCommand(const QString &command);
  338. void updateCommandTerminator(const QString &terminator);
  339. private:
  340. PortSelector *port;
  341. BaudSelector *baud;
  342. ParitySelector *parity;
  343. FlowSelector *flow;
  344. StopSelector *stop;
  345. QLineEdit *weighcommand;
  346. QComboBox *commandterminator;
  347. };
  348. @ This is very similar to other configuration widgets.
  349. @<SerialScaleConfWidget implementation@>=
  350. SerialScaleConfWidget::SerialScaleConfWidget(DeviceTreeModel *model,
  351. const QModelIndex &index)
  352. : BasicDeviceConfigurationWidget(model, index),
  353. port(new PortSelector), baud(new BaudSelector), parity(new ParitySelector),
  354. flow(new FlowSelector), stop(new StopSelector),
  355. weighcommand(new QLineEdit("!KP")), commandterminator(new QComboBox)
  356. {
  357. QFormLayout *layout = new QFormLayout;
  358. layout->addRow(tr("Port:"), port);
  359. connect(port, SIGNAL(currentIndexChanged(QString)),
  360. this, SLOT(updatePort(QString)));
  361. connect(port, SIGNAL(editTextChanged(QString)),
  362. this, SLOT(updatePort(QString)));
  363. layout->addRow(tr("Baud:"), baud);
  364. connect(baud, SIGNAL(currentIndexChanged(QString)),
  365. this, SLOT(updateBaudRate(QString)));
  366. layout->addRow(tr("Parity:"), parity);
  367. connect(parity, SIGNAL(currentIndexChanged(int)),
  368. this, SLOT(updateParity(int)));
  369. layout->addRow(tr("Flow Control:"), flow);
  370. connect(flow, SIGNAL(currentIndexChanged(int)),
  371. this, SLOT(updateFlowControl(int)));
  372. layout->addRow(tr("Stop Bits:"), stop);
  373. connect(stop, SIGNAL(currentIndexChanged(int)),
  374. this, SLOT(updateStopBits(int)));
  375. layout->addRow(tr("Weigh Command:"), weighcommand);
  376. connect(weighcommand, SIGNAL(textChanged(QString)),
  377. this, SLOT(updateWeighCommand(QString)));
  378. commandterminator->addItem("CRLF");
  379. commandterminator->addItem("CR");
  380. commandterminator->addItem("LF");
  381. layout->addRow(tr("Command Terminator:"), commandterminator);
  382. connect(commandterminator, SIGNAL(currentIndexChanged(QString)),
  383. this, SLOT(updateCommandTerminator(QString)));
  384. @<Get device configuration data for current node@>@;
  385. for(int i = 0; i < configData.size(); i++)
  386. {
  387. node = configData.at(i).toElement();
  388. if(node.attribute("name") == "port")
  389. {
  390. int j = port->findText(node.attribute("value"));
  391. if(j >= 0)
  392. {
  393. port->setCurrentIndex(j);
  394. }
  395. else
  396. {
  397. port->insertItem(0, node.attribute("value"));
  398. port->setCurrentIndex(0);
  399. }
  400. }
  401. else if(node.attribute("name") == "baudrate")
  402. {
  403. baud->setCurrentIndex(baud->findText(node.attribute("value")));
  404. }
  405. else if(node.attribute("name") == "parity")
  406. {
  407. parity->setCurrentIndex(parity->findData(node.attribute("value")));
  408. }
  409. else if(node.attribute("name") == "flowcontrol")
  410. {
  411. flow->setCurrentIndex(flow->findData(node.attribute("value")));
  412. }
  413. else if(node.attribute("name") == "stopbits")
  414. {
  415. stop->setCurrentIndex(stop->findData(node.attribute("value")));
  416. }
  417. else if(node.attribute("name") == "weighcommand")
  418. {
  419. weighcommand->setText(node.attribute("value"));
  420. }
  421. else if(node.attribute("name") == "commandterminator")
  422. {
  423. commandterminator->setCurrentIndex(
  424. commandterminator->findText(node.attribute("value")));
  425. }
  426. }
  427. updatePort(port->currentText());
  428. updateBaudRate(baud->currentText());
  429. updateParity(parity->currentIndex());
  430. updateFlowControl(flow->currentIndex());
  431. updateStopBits(stop->currentIndex());
  432. updateWeighCommand(weighcommand->text());
  433. updateCommandTerminator(commandterminator->currentText());
  434. setLayout(layout);
  435. }
  436. @ Update methods are the same as were used in |ModbusRtuPortConfWidget|.
  437. @<SerialScaleConfWidget implementation@>=
  438. void SerialScaleConfWidget::updatePort(const QString &newPort)
  439. {
  440. updateAttribute("port", newPort);
  441. }
  442. void SerialScaleConfWidget::updateBaudRate(const QString &rate)
  443. {
  444. updateAttribute("baudrate", rate);
  445. }
  446. void SerialScaleConfWidget::updateParity(int index)
  447. {
  448. updateAttribute("parity", parity->itemData(index).toString());
  449. }
  450. void SerialScaleConfWidget::updateFlowControl(int index)
  451. {
  452. updateAttribute("flowcontrol", flow->itemData(index).toString());
  453. }
  454. void SerialScaleConfWidget::updateStopBits(int index)
  455. {
  456. updateAttribute("stopbits", stop->itemData(index).toString());
  457. }
  458. void SerialScaleConfWidget::updateWeighCommand(const QString &command)
  459. {
  460. updateAttribute("weighcommand", command);
  461. }
  462. void SerialScaleConfWidget::updateCommandTerminator(const QString &terminator)
  463. {
  464. updateAttribute("commandterminator", terminator);
  465. }
  466. @ The configuration widget is registered with the configuration system.
  467. @<Register device configuration widgets@>=
  468. app.registerDeviceConfigurationWidget("scale", SerialScaleConfWidget::staticMetaObject);
  469. @ A |NodeInserter| is also added.
  470. @<Register top level device configuration nodes@>=
  471. inserter = new NodeInserter(tr("Serial Scale"), tr("Scale"), "scale", NULL);
  472. topLevelNodeInserters.append(inserter);