# HG changeset patch # User Michiel Broek # Date 1656697931 -7200 # Node ID c1bb6b197763afeeca692b37ebc9464746d80039 # Parent d053ffbbf3e9eb8cd17beb80e6206c476792e6ad Implemented profile control. Added stage edit. Added manual control. Added temperature setting. diff -r d053ffbbf3e9 -r c1bb6b197763 src/DetailFermenter.cpp --- a/src/DetailFermenter.cpp Thu Jun 30 21:05:30 2022 +0200 +++ b/src/DetailFermenter.cpp Fri Jul 01 19:52:11 2022 +0200 @@ -20,6 +20,12 @@ #include "MainWindow.h" +/* + * Results are available via MySQL and websockets. Because we initialize using + * MySQL we only use that for the results and up to date status. + * Commands are send via websockets only. + */ + DetailFermenter::DetailFermenter(int id, QWidget *parent) : QDialog(parent), ui(new Ui::DetailFermenter) { QSqlQuery query; @@ -57,12 +63,23 @@ ui->codePick->addItem(query.value("code").toString()+" - "+query.value("name").toString()); } + ui->profilePick->addItem(tr("Erase profile")); + query.exec("SELECT name FROM profile_fermentation ORDER BY name"); + while (query.next()) { + ui->profilePick->addItem(query.value("name").toString()); + } + connect(ui->loEdit, QOverload::of(&QDoubleSpinBox::valueChanged), this, &DetailFermenter::lo_changed); connect(ui->hiEdit, QOverload::of(&QDoubleSpinBox::valueChanged), this, &DetailFermenter::hi_changed); connect(ui->heatSwitch, SIGNAL(clicked()), this, SLOT(heat_switched())); connect(ui->coolSwitch, SIGNAL(clicked()), this, SLOT(cool_switched())); connect(ui->fanSwitch, SIGNAL(clicked()), this, SLOT(fan_switched())); + connect(ui->modeButton1, SIGNAL(clicked()), this, SLOT(button1_pressed())); + connect(ui->modeButton2, SIGNAL(clicked()), this, SLOT(button2_pressed())); connect(ui->modeEdit, QOverload::of(&QComboBox::currentIndexChanged), this, &DetailFermenter::mode_changed); + connect(ui->stageEdit, QOverload::of(&QComboBox::currentIndexChanged), this, &DetailFermenter::stage_changed); + connect(ui->codePick, QOverload::of(&QComboBox::currentIndexChanged), this, &DetailFermenter::code_changed); + connect(ui->profilePick, QOverload::of(&QComboBox::currentIndexChanged), this, &DetailFermenter::profile_changed); connect(parent, SIGNAL(updateFermenter(QString)), this, SLOT(refreshFermenter(QString))); emit refreshTable(); } @@ -72,7 +89,14 @@ { QSqlQuery query; - qDebug() << "refreshTable fermenter" << this->recno; + qDebug() << "refreshTable fermenter rec:" << this->recno; + + QIcon icon_done, icon_start, icon_abort, icon_pause, icon_cont; + icon_done.addFile(QString::fromUtf8(":icons/silk/accept.png"), QSize(), QIcon::Normal, QIcon::Off); + icon_start.addFile(QString::fromUtf8(":icons/silk/resultset_next.png"), QSize(), QIcon::Normal, QIcon::Off); + icon_abort.addFile(QString::fromUtf8(":icons/silk/bomb.png"), QSize(), QIcon::Normal, QIcon::Off); + icon_pause.addFile(QString::fromUtf8(":icons/silk/cup.png"), QSize(), QIcon::Normal, QIcon::Off); + icon_cont.addFile(QString::fromUtf8(":icons/silk/cup_go.png"), QSize(), QIcon::Normal, QIcon::Off); query.prepare("SELECT * FROM mon_fermenters WHERE record = :recno"); query.bindValue(":recno", this->recno); @@ -81,6 +105,9 @@ const QSignalBlocker blocker1(ui->codePick); const QSignalBlocker blocker2(ui->modeEdit); + const QSignalBlocker blocker3(ui->loEdit); + const QSignalBlocker blocker4(ui->hiEdit); + const QSignalBlocker blocker5(ui->stageEdit); _node = query.value("node").toString(); _alias = query.value("alias").toString(); @@ -156,8 +183,10 @@ ui->hiEdit->setReadOnly(true); ui->hiEdit->setButtonSymbols(QAbstractSpinBox::NoButtons); } - ui->loEdit->setValue(query.value("setpoint_low").toDouble()); - ui->hiEdit->setValue(query.value("setpoint_high").toDouble()); + lo_set = query.value("setpoint_low").toDouble(); + hi_set = query.value("setpoint_high").toDouble(); + ui->loEdit->setValue(lo_set); + ui->hiEdit->setValue(hi_set); ui->switchBox->show(); ui->heatLED->setChecked((query.value("heater_state").toInt() != 0) ? true:false); @@ -168,13 +197,73 @@ ui->heatSwitch->show(); ui->coolSwitch->show(); ui->fanSwitch->show(); + heat_state = (query.value("heater_state").toInt()) ? true:false; + cool_state = (query.value("cooler_state").toInt()) ? true:false; + fan_state = (query.value("fan_state").toInt()) ? true:false; + ui->heatSwitch->setChecked(heat_state); + ui->coolSwitch->setChecked(cool_state); + ui->fanSwitch->setChecked(fan_state); + // Copy state values to variables and set the switches. } else { ui->heatSwitch->hide(); ui->coolSwitch->hide(); ui->fanSwitch->hide(); + heat_state = cool_state = fan_state = false; } - //webSocket->sendTextMessage(QString("dd")); + if (query.value("profile_name").toString() == "") { + qobject_cast(ui->modeEdit->model())->item(4)->setEnabled(false); + } else { + qobject_cast(ui->modeEdit->model())->item(4)->setEnabled(true); + } + ui->profileEdit->setText(query.value("profile_name").toString()); + + if (query.value("mode").toString() == "PROFILE") { + _profile = query.value("profile_state").toString(); // So we know the profile state anywhere. + qDebug() << "profile state" << query.value("profile_state").toString(); + if (query.value("profile_state").toString() == "OFF") { + ui->profilePick->show(); + ui->profileShow->hide(); // Both on the same location. + ui->modeButton1->show(); + ui->modeButton1->setText(tr("Start")); + ui->modeButton1->setIcon(icon_start); + ui->modeButton2->hide(); + } else if (query.value("profile_state").toString() == "RUN") { + ui->profilePick->hide(); + ui->profileShow->show(); + ui->profileShow->setText(QString(tr("Profile active %1% done")).arg(query.value("profile_percent").toDouble())); + ui->modeButton1->show(); + ui->modeButton1->setText(tr("Abort")); + ui->modeButton1->setIcon(icon_abort); + ui->modeButton2->show(); + ui->modeButton2->setText(tr("Pause")); + ui->modeButton2->setIcon(icon_pause); + } else if (query.value("profile_state").toString() == "PAUSE") { + ui->profilePick->hide(); + ui->profileShow->show(); + ui->profileShow->setText(QString(tr("Profile paused %1% done")).arg(query.value("profile_percent").toDouble())); + ui->modeButton1->show(); + ui->modeButton1->setText(tr("Abort")); + ui->modeButton1->setIcon(icon_abort); + ui->modeButton2->show(); + ui->modeButton2->setText(tr("Continue")); + ui->modeButton2->setIcon(icon_cont); + } else if (query.value("profile_state").toString() == "DONE") { + ui->profilePick->hide(); + ui->profileShow->show(); + ui->profileShow->setText(QString(tr("Profile ready"))); + ui->modeButton1->show(); + ui->modeButton1->setText(tr("Profile Ok")); + ui->modeButton1->setIcon(icon_done); + ui->modeButton2->hide(); + } + } else { + ui->profilePick->show(); + ui->profileShow->hide(); // Both on the same location. + ui->modeButton1->hide(); + ui->modeButton2->hide(); + _profile = QString(""); + } ui->thermoBox->show(); if (query.value("air_state").toString() == "OK") { @@ -231,40 +320,175 @@ void DetailFermenter::lo_changed(double val) { - qDebug() << "lo_changed" << val; + double hi = ui->hiEdit->value(); + + if (val >= hi) + hi = val + 0.1; + QString msg = QString("{\"device\":\"fermenters\",\"node\":\""+_node+"\",\"unit\":\""+_alias+"\",\"setpoint_low\":%1,\"setpoint_high\":%2}") + .arg(val, 2, 'f', 1, '0').arg(hi, 2, 'f', 1, '0'); + qDebug() << "lo_changed" << val << msg; + webSocket->sendTextMessage(msg); } void DetailFermenter::hi_changed(double val) { - qDebug() << "hi_changed" << val; + double lo = ui->loEdit->value(); + + if (val <= lo) + lo = val - 0.1; + QString msg = QString("{\"device\":\"fermenters\",\"node\":\""+_node+"\",\"unit\":\""+_alias+"\",\"setpoint_low\":%1,\"setpoint_high\":%2}") + .arg(lo, 2, 'f', 1, '0').arg(val, 2, 'f', 1, '0'); + qDebug() << "hi_changed" << val << msg; + webSocket->sendTextMessage(msg); +} + + +void DetailFermenter::send_switches() +{ + QString msg=QString("{\"device\":\"fermenters\",\"node\":\""+_node+"\",\"unit\":\""+_alias+"\",\"heater_state\":%1,\"cooler_state\":%2,\"fan_state\":%3}") + .arg((heat_state)?100:0).arg((cool_state)?100:0).arg((fan_state)?100:0); + qDebug() << msg; + webSocket->sendTextMessage(msg); } void DetailFermenter::heat_switched() { - qDebug() << "heat_switched" << heat_state; + heat_state = !heat_state; + cool_state = false; + send_switches(); } void DetailFermenter::cool_switched() { - qDebug() << "cool_switched" << cool_state; + cool_state = !cool_state; + heat_state = false; + send_switches(); } void DetailFermenter::fan_switched() { - qDebug() << "fan_switched" << fan_state; + fan_state = !fan_state; + send_switches(); +} + + +void DetailFermenter::button1_pressed() +{ + qDebug() << "button1" << _profile; + if (_profile == "OFF") { + QString msg = QString("{\"device\":\"fermenters\",\"node\":\"" + _node + "\",\"unit\":\"" + _alias + "\",\"profile\":{\"command\":\"start\"}}"); + qDebug() << msg; + webSocket->sendTextMessage(msg); + return; + } + + if ((_profile == "RUN") || (_profile == "PAUSE")) { + // use popup + int rc = QMessageBox::warning(this, tr("Profile running"), tr("Profile is active, really abort?"), + QMessageBox::Yes | QMessageBox::No, QMessageBox::No); + if (rc == QMessageBox::Yes) { + QString msg = QString("{\"device\":\"fermenters\",\"node\":\"" + _node + "\",\"unit\":\"" + _alias + "\",\"profile\":{\"command\":\"abort\"}}"); + qDebug() << msg; + webSocket->sendTextMessage(msg); + } + return; + } + + if (_profile == "DONE") { + QString msg = QString("{\"device\":\"fermenters\",\"node\":\"" + _node + "\",\"unit\":\"" + _alias + "\",\"profile\":{\"command\":\"done\"}}"); + qDebug() << msg; + webSocket->sendTextMessage(msg); + } +} + + +void DetailFermenter::button2_pressed() +{ + qDebug() << "button2" << _profile; + + if ((_profile == "RUN") || (_profile == "PAUSE")) { + QString msg = QString("{\"device\":\"fermenters\",\"node\":\"" + _node + "\",\"unit\":\"" + _alias + "\",\"profile\":{\"command\":\"pause\"}}"); + qDebug() << msg; + webSocket->sendTextMessage(msg); + } } void DetailFermenter::mode_changed(int val) { - qDebug() << "mode_changed" << val; - QStringList mode ({ "OFF", "NONE", "FRIDGE", "BEER", "PROFiLE" }); + QStringList mode ({ "OFF", "NONE", "FRIDGE", "BEER", "PROFILE" }); QString msg = QString("{\"device\":\"fermenters\",\"node\":\"" + _node + "\",\"unit\":\"" + _alias + "\",\"mode\":\"" + mode[val] + "\"}"); - qDebug() << msg; + qDebug() << "mode_changed" << val << msg; + webSocket->sendTextMessage(msg); +} + + +void DetailFermenter::stage_changed(int val) +{ + QStringList stage ({ "PRIMARY", "SECONDARY", "TERTIARY", "CARBONATION" }); + QString msg = QString("{\"device\":\"fermenters\",\"node\":\"" + _node + "\",\"unit\":\"" + _alias + "\",\"stage\":\"" + stage[val] + "\"}"); + qDebug() << "stage_changed" << val << msg; webSocket->sendTextMessage(msg); } + +void DetailFermenter::code_changed(int val) +{ + QString msg = QString(""); + qDebug() << "code_changed" << val << msg; +} + + +void DetailFermenter::profile_changed(int val) +{ + QString payload; + QSqlQuery query; + QJsonParseError parseError; + + if (val == 0) { + payload = QString("\"profile\":null"); + } else { + query.exec("SELECT * FROM profile_fermentation ORDER BY name"); + for (int i = 0; i < val; i++) { + query.next(); + } + payload = QString("\"profile\":{\"uuid\":\""+query.value("uuid").toString()+"\",\"name\":\""+query.value("name").toString()); + payload.append(QString("\",\"inittemp\":{\"low\":") + QString("%1").arg(query.value("inittemp_lo").toDouble(), 2, 'f', 1, '0' )); + payload.append(QString(",\"high\":") + QString("%1").arg(query.value("inittemp_hi").toDouble(), 2, 'f', 1, '0' ) + QString("},")); + payload.append(QString("\"fridgemode\":") + QString("%1").arg(query.value("fridgemode").toInt()) ); + payload.append(QString(",\"steps\":[")); + + const auto& s_json = query.value("steps").toString(); + if (! s_json.trimmed().isEmpty()) { + const auto& formattedJson = QString("%1").arg(s_json); + QJsonDocument steps = QJsonDocument::fromJson(formattedJson.toUtf8(), &parseError); + if (parseError.error != QJsonParseError::NoError) { + qWarning() << "Parse error: " << parseError.errorString() << "at" << parseError.offset ; + } else if (steps.isArray()) { + for (int i = 0; i < steps.array().size(); i++) { + QJsonObject obj = steps.array().at(i).toObject(); + if (i > 0) + payload.append(QString(",")); + payload.append(QString("{\"steptime\":%1").arg(obj["steptime"].toString().toDouble())); + payload.append(QString(",\"resttime\":%1").arg(obj["resttime"].toString().toDouble())); + payload.append(QString(",\"target_lo\":%1").arg(obj["target_lo"].toString().toDouble())); + payload.append(QString(",\"target_hi\":%1").arg(obj["target_hi"].toString().toDouble())); + payload.append(QString(",\"fridgemode\":%1").arg(obj["fridgemode"].toString().toInt())); + payload.append(QString(",\"name\":\"") + obj["name"].toString() + QString("\"}")); + } + } + } + + payload.append(QString("]}")); +// qDebug() << query.value("steps").toString(); + } + QString msg = QString("{\"device\":\"fermenters\",\"node\":\"" + _node + "\",\"unit\":\"" + _alias + "\",%1}").arg(payload); + qDebug() << "profile_changed" << val << msg; + webSocket->sendTextMessage(msg); +} + + diff -r d053ffbbf3e9 -r c1bb6b197763 src/DetailFermenter.h --- a/src/DetailFermenter.h Thu Jun 30 21:05:30 2022 +0200 +++ b/src/DetailFermenter.h Fri Jul 01 19:52:11 2022 +0200 @@ -33,18 +33,26 @@ void heat_switched(); void cool_switched(); void fan_switched(); + void button1_pressed(); + void button2_pressed(); void mode_changed(int val); + void stage_changed(int val); + void code_changed(int val); + void profile_changed(int val); public slots: void refreshFermenter(QString); private: Ui::DetailFermenter *ui; - QString _node, _alias; + QString _node, _alias, _profile; int recno; + double lo_set = 0, hi_set = 0; bool heat_state = false; bool cool_state = false; bool fan_state = false; + + void send_switches(); }; #endif diff -r d053ffbbf3e9 -r c1bb6b197763 ui/DetailFermenter.ui --- a/ui/DetailFermenter.ui Thu Jun 30 21:05:30 2022 +0200 +++ b/ui/DetailFermenter.ui Fri Jul 01 19:52:11 2022 +0200 @@ -19,10 +19,10 @@ - 720 - 10 + 740 + 0 261 - 111 + 121 @@ -190,9 +190,9 @@ - 20 - 10 - 681 + 0 + 0 + 721 231 @@ -201,7 +201,7 @@ 10 40 - 121 + 141 20 @@ -217,12 +217,12 @@ 10 70 - 121 + 141 20 - System: + System and unit: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -233,7 +233,7 @@ 10 100 - 121 + 141 20 @@ -249,12 +249,12 @@ 10 130 - 121 + 141 20 - Mode: + Working mode: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -265,12 +265,12 @@ 10 160 - 121 + 141 20 - Stage: + Fermentation stage: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -281,7 +281,7 @@ 10 190 - 121 + 141 20 @@ -297,7 +297,7 @@ 15 10 - 651 + 691 25 @@ -318,7 +318,7 @@ - 140 + 160 40 271 23 @@ -334,7 +334,7 @@ - 140 + 160 70 181 23 @@ -350,7 +350,7 @@ - 550 + 560 70 101 23 @@ -363,7 +363,7 @@ - 140 + 160 100 381 23 @@ -376,17 +376,20 @@ - 550 + 560 100 - 101 + 141 23 + + QComboBox::AdjustToContentsOnFirstShow + - 140 + 160 130 121 23 @@ -396,7 +399,7 @@ - 140 + 160 160 121 23 @@ -406,7 +409,7 @@ - 140 + 160 190 271 23 @@ -419,9 +422,9 @@ - 550 + 530 190 - 101 + 171 23 @@ -432,9 +435,9 @@ - 440 + 439 130 - 80 + 101 23 @@ -445,9 +448,9 @@ - 550 + 560 130 - 80 + 101 23 @@ -455,6 +458,19 @@ PushButton + + + + 560 + 190 + 141 + 23 + + + + Choose profile: + + @@ -462,17 +478,17 @@ - 20 - 250 - 681 - 271 + 0 + 240 + 721 + 291 - 290 - 160 + 310 + 180 101 20 @@ -487,9 +503,9 @@ - 70 - 240 - 111 + 100 + 260 + 71 20 @@ -503,9 +519,9 @@ - 500 - 240 - 111 + 550 + 260 + 71 20 @@ -522,33 +538,27 @@ - 269 + 280 10 - 141 - 141 + 161 + 161 - Shows the pressure - - - The bar meter widget displays the pressure attached to it + Shows the chiller temperature - 440 + 460 10 - 231 - 231 + 251 + 251 - Shows the pressure - - - The bar meter widget displays the pressure attached to it + Shows the beer temperature @@ -569,15 +579,12 @@ 10 10 - 231 - 231 + 251 + 251 - Shows the pressure - - - The bar meter widget displays the pressure attached to it + Shows the air temperature false @@ -600,8 +607,8 @@ - 720 - 140 + 740 + 130 261 101 @@ -680,8 +687,8 @@ - 720 - 250 + 740 + 240 261 121 @@ -824,6 +831,11 @@ ... + + + :/icons/silk/control_stop_blue.png + :/icons/silk/control_play_blue.png:/icons/silk/control_stop_blue.png + true @@ -843,9 +855,17 @@ ... + + + :/icons/silk/control_stop_blue.png + :/icons/silk/control_play_blue.png:/icons/silk/control_stop_blue.png + true + + Qt::ToolButtonIconOnly + @@ -859,6 +879,11 @@ ... + + + :/icons/silk/control_stop_blue.png + :/icons/silk/control_play_blue.png:/icons/silk/control_stop_blue.png + true @@ -867,17 +892,17 @@ - 720 - 380 + 740 + 370 261 - 141 + 161 90 - 100 + 120 80 23 @@ -913,7 +938,7 @@ 90 - 60 + 70 80 23