src/DetailFermenter.cpp

Thu, 18 Aug 2022 20:34:15 +0200

author
Michiel Broek <mbroek@mbse.eu>
date
Thu, 18 Aug 2022 20:34:15 +0200
changeset 401
583148eb6e01
parent 391
42936c86cbac
child 414
6582a2a95583
permissions
-rw-r--r--

Init est_carb field for new products.

/**
 * DetailFermenter.cpp is part of bmsapp.
 *
 * bmsapp is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * bmsapp is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#include "DetailFermenter.h"
#include "ChartFermenter.h"
#include "Webcam.h"
#include "../ui/ui_DetailFermenter.h"
#include "global.h"
#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;

    qDebug() << "DetailFermenter record:" << id;
    ui->setupUi(this);
    this->recno = id;
    setWindowFlags(Qt::Window | Qt::WindowTitleHint | Qt::CustomizeWindowHint);
    setWindowTitle(tr("BMSapp - Details Fermenter"));

    ui->airThermo->setMaximum(40.0);
    ui->airThermo->setNominal(15.0);
    ui->airThermo->setCritical(20.0);
    ui->airThermo->setSuffix(QString("°C"));

    ui->beerThermo->setMaximum(40.0);
    ui->beerThermo->setNominal(15.0);
    ui->beerThermo->setCritical(20.0);
    ui->beerThermo->setSuffix(QString("°C"));

    ui->chillerThermo->setMinimum(-15.0);
    ui->chillerThermo->setMaximum(25.0);
    ui->chillerThermo->setNominal(0.0);
    ui->chillerThermo->setCritical(15.0);
    ui->chillerThermo->setSuffix(QString("°C"));

    ui->modeEdit->addItem("OFF");
    ui->modeEdit->addItem("NONE");
    ui->modeEdit->addItem("FRIDGE");
    ui->modeEdit->addItem("BEER");
    ui->modeEdit->addItem("PROFILE");

    ui->stageEdit->addItem("PRIMARY");
    ui->stageEdit->addItem("SECONDARY");
    ui->stageEdit->addItem("TERTIARY");
    ui->stageEdit->addItem("CARBONATION");

    ui->codePick->addItem("Free - Dummy");	// Will be replaced later
    query.exec("SELECT code,name FROM products WHERE stage='1' OR stage='2' OR stage='3' OR stage='4' OR stage='5' OR stage='6' OR stage='7' ORDER BY code");
    while (query.next()) {
	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<double>::of(&QDoubleSpinBox::valueChanged), this, &DetailFermenter::lo_changed);
    connect(ui->hiEdit, QOverload<double>::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<int>::of(&QComboBox::currentIndexChanged), this, &DetailFermenter::mode_changed);
    connect(ui->stageEdit, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &DetailFermenter::stage_changed);
    connect(ui->codePick, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &DetailFermenter::code_changed);
    connect(ui->profilePick, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &DetailFermenter::profile_changed);
    connect(ui->logButton, SIGNAL(clicked()), this, SLOT(on_ChartButton_clicked()));
    connect(ui->camButton, SIGNAL(clicked()), this, SLOT(on_WebcamButton_clicked()));
    connect(parent, SIGNAL(updateFermenter(QString)), this, SLOT(refreshFermenter(QString)));
    emit refreshTable();
}


void DetailFermenter::refreshTable()
{
    QSqlQuery query;

    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);
    query.exec();
    if (query.next()) {

	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();
	_uuid = query.value("uuid").toString();
	_beercode = query.value("beercode").toString();
	_beername = query.value("beername").toString();
	_webcam_url = query.value("webcam_url").toString();
	_webcam_light = query.value("webcam_light").toInt();

	ui->uuidEdit->setText(_uuid);
	ui->systemEdit->setText(_node+"/"+_alias);
	ui->codePick->setItemText(0, _alias.toUpper()+" - "+_alias);

	if (query.value("online").toInt()) {
	    ui->statusEdit->setText(tr("Online"));
            ui->codeEdit->setText(_beercode+" - "+_beername);
	    ui->airThermo->setNominal(query.value("yeast_lo").toDouble());
	    ui->airThermo->setCritical(query.value("yeast_hi").toDouble());
	    ui->beerThermo->setNominal(query.value("yeast_lo").toDouble());
            ui->beerThermo->setCritical(query.value("yeast_hi").toDouble());

	    ui->modeEdit->show();
	    if (query.value("mode").toString() == "OFF")
	    	ui->modeEdit->setCurrentIndex(0);
            else if (query.value("mode").toString() == "NONE")
	    	ui->modeEdit->setCurrentIndex(1);
	    else if (query.value("mode").toString() == "FRIDGE")
            	ui->modeEdit->setCurrentIndex(2);
	    else if (query.value("mode").toString() == "BEER")
            	ui->modeEdit->setCurrentIndex(3);
	    else if (query.value("mode").toString() == "PROFILE")
            	ui->modeEdit->setCurrentIndex(4);

	    ui->stageEdit->show();
	    if (query.value("stage").toString() == "PRIMARY")
		ui->stageEdit->setCurrentIndex(0);
	    else if (query.value("stage").toString() == "SECONDARY")
                ui->stageEdit->setCurrentIndex(1);
	    else if (query.value("stage").toString() == "TERTIARY")
                ui->stageEdit->setCurrentIndex(2);
	    else if (query.value("stage").toString() == "CARBONATION")
                ui->stageEdit->setCurrentIndex(3);

	    if (query.value("door_address").toString() != "") {
		ui->doorLED->show();
                ui->doorLabel->show();
		ui->doorLED->setChecked((query.value("door_state").toInt() != 0) ? true:false);
	    } else {
		ui->doorLED->hide();
		ui->doorLabel->hide();
	    }

	    if (query.value("light_address").toString() != "") {
                ui->lightLED->show();
                ui->lightLabel->show();
                ui->lightLED->setChecked((query.value("light_state").toInt() != 0) ? true:false);
            } else {
                ui->lightLED->hide();
                ui->lightLabel->hide();
            }

	    if (query.value("mode").toString() == "OFF") {
	    	ui->powerLED->setChecked(false);
		ui->codePick->show();
	    } else {
	    	ui->powerLED->setChecked(true);
		ui->codePick->hide();
	    }
	    ui->alarmLED->setChecked((query.value("alarm").toInt() != 0) ? true:false);

	    ui->tempsetBox->show();
	    if ((query.value("mode").toString() == "FRIDGE") || (query.value("mode").toString() == "BEER")) {
		ui->loEdit->setReadOnly(false);
		ui->loEdit->setButtonSymbols(QAbstractSpinBox::UpDownArrows);
		ui->hiEdit->setReadOnly(false);
                ui->hiEdit->setButtonSymbols(QAbstractSpinBox::UpDownArrows);
	    } else {
		ui->loEdit->setReadOnly(true);
		ui->loEdit->setButtonSymbols(QAbstractSpinBox::NoButtons);
		ui->hiEdit->setReadOnly(true);
                ui->hiEdit->setButtonSymbols(QAbstractSpinBox::NoButtons);
	    }
	    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);
	    ui->coolLED->setChecked((query.value("cooler_state").toInt() != 0) ? true:false);
	    ui->fanLED->setChecked((query.value("fan_state").toInt() != 0) ? true:false);

	    if (query.value("mode").toString() == "NONE") {
		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;
	    }

	    if (query.value("profile_name").toString() == "") {
		qobject_cast<QStandardItemModel*>(ui->modeEdit->model())->item(4)->setEnabled(false);
	    } else {
		qobject_cast<QStandardItemModel*>(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("");
	    }

	    if (_webcam_url == "") {
		ui->camButton->hide();
	    } else {
		ui->camButton->show();
	    }

	    ui->thermoBox->show();
	    if (query.value("air_state").toString() == "OK") {
	    	ui->airThermo->setValue(query.value("air_temperature").toDouble());
	    }
	    if (query.value("beer_state").toString() == "OK") {
                ui->beerThermo->setValue(query.value("beer_temperature").toDouble());
            }
	    if (query.value("chiller_state").toString() == "OK") {
		ui->chillerThermo->setValue(query.value("chiller_temperature").toDouble());
	    }

	} else {
	    /* Offline */
	    ui->statusEdit->setText(tr("Offline"));
	    ui->powerLED->setChecked(false);
	    ui->alarmLED->setChecked(true);
	    ui->codePick->hide();
	    ui->modeEdit->hide();
	    ui->stageEdit->hide();
	    ui->thermoBox->hide();
	    ui->tempsetBox->hide();
	    ui->switchBox->hide();
	    ui->camButton->hide();
	    ui->logButton->hide();
	}
    }

}


DetailFermenter::~DetailFermenter()
{
    qDebug() << "DetailFermenter done";
    delete ui;
    emit entry_changed();
}


/*
 * Receive signals destined for all fermenters.
 * Check if the signal is for us.
 */
void DetailFermenter::refreshFermenter(QString data)
{
    if (_node+"/"+_alias == data) {
	emit refreshTable();
    }
}


void DetailFermenter::on_ChartButton_clicked()
{
    ChartFermenter dialog(_beercode, _beername, this);
}


void DetailFermenter::on_WebcamButton_clicked()
{
    QString msg = QString("{\"device\":\"fermenters\",\"node\":\""+_node+"\",\"unit\":\""+_alias+"\",\"light_state\":100}");
    webSocket->sendTextMessage(msg);
    Webcam dialog(_webcam_url, this);
}


void DetailFermenter::on_quitButton_clicked()
{
    this->close();
    this->setResult(1);
}


void DetailFermenter::lo_changed(double 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)
{
    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()
{
    heat_state = !heat_state;
    cool_state = false;
    send_switches();
}


void DetailFermenter::cool_switched()
{
    cool_state = !cool_state;
    heat_state = false;
    send_switches();
}


void DetailFermenter::fan_switched()
{
    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")) {
	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)
{
    QStringList mode ({ "OFF", "NONE", "FRIDGE", "BEER", "PROFILE" });
    QString msg = QString("{\"device\":\"fermenters\",\"node\":\"" + _node + "\",\"unit\":\"" + _alias + "\",\"mode\":\"" + mode[val] + "\"}");
    //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)
{
    QJsonParseError parseError;
    QSqlQuery query;

    QString msg = QString("{\"device\":\"fermenters\",\"node\":\"" + _node + "\",\"unit\":\"" + _alias + "\",");
    if (val == 0) {
	msg.append(QString("\"beeruuid\":\"") + _uuid + "\",");
	msg.append(QString("\"beercode\":\"") + _alias.toUpper() + "\",");
	msg.append(QString("\"beername\":\"") + _alias + "\",");
	msg.append(QString("\"yeast_lo\":12.0,"));
	msg.append(QString("\"yeast_hi\":24.0}"));

    } else {
	query.exec("SELECT code,name,uuid,stage,json_yeasts FROM products WHERE stage='1' OR stage='2' OR stage='3' OR stage='4' OR stage='5' OR stage='6' OR stage='7' ORDER BY code");
	for (int i = 0; i < val; i++) {
            query.next();
        }
	double yl = 0;
	double yh = 40;
	const auto& y_json = query.value("json_yeasts").toString();
	if (! y_json.trimmed().isEmpty()) {
            const auto& formattedJson = QString("%1").arg(y_json);
            QJsonDocument yeasts = QJsonDocument::fromJson(formattedJson.toUtf8(),  &parseError);
            if (parseError.error != QJsonParseError::NoError) {
                qWarning() << "Parse error: " << parseError.errorString() << "at" << parseError.offset ;
            } else if (yeasts.isArray()) {
                for (int i = 0; i < yeasts.array().size(); i++) {
                    QJsonObject obj = yeasts.array().at(i).toObject();
		    if (obj["y_use"].toInt() == 0) {	// Primary yeast
			if (obj["y_min_temperature"].toDouble() > yl)
			    yl = obj["y_min_temperature"].toDouble();
			if (obj["y_max_temperature"].toDouble() < yh)
			    yh = obj["y_max_temperature"].toDouble();
		    }
		}
            }
        }
	msg.append(QString("\"beeruuid\":\"") + query.value("uuid").toString() + "\",");
	msg.append(QString("\"beercode\":\"") + query.value("code").toString() + "\",");
	msg.append(QString("\"beername\":\"") + query.value("name").toString() + "\",");
	msg.append(QString("\"yeast_lo\":%1,").arg(yl));
	msg.append(QString("\"yeast_hi\":%1}").arg(yh));
    }

    qDebug() << "code_changed" << val << msg;
    webSocket->sendTextMessage(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);
}

mercurial