-
Tobias Triffterer authored
This parses a command from the server with the contents of all the bins to synchronize the client with the server.
Verifiedfde44896
clientgui.cpp 14.29 KiB
/**
* @file clientgui.cpp
*
* @author Tobias Triffterer
*
* @brief Client Login Window
*
* Rutherford Experiment Lab Course Online
* Copyright © 2021 Ruhr-Universität Bochum, Institut für Experimentalphysik I
* https://www.ep1.ruhr-uni-bochum.de/
*
* This program 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.
*
* This program 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 <QApplication>
#include <QFileDialog>
#include <QFontMetrics>
#include <QMessageBox>
#include <QVBoxLayout>
#include <TFile.h>
#include <TSystem.h>
#include "clientgui.h"
using namespace Fp311Online;
ClientGui::ClientGui(const QString& name, const QString& token, std::unique_ptr<QWebSocket>&& socket)
: _name(name),
_token(token),
_socket(std::move(socket)),
_rootcanvas(this),
_histo(new TH1F("AdcDisplay", "ADC Display", ExperimentState::NumberOfAdcChannels, -0.5, ExperimentState::NumberOfAdcChannels - 0.5))
{
logInfo(QStringLiteral("Initalizing ClientGui for participant ") + _name + QStringLiteral(" using server token ") + token);
_ui.setupUi(this);
QVBoxLayout* layoutRootCanvas = new QVBoxLayout(_ui.wRootHisto);
layoutRootCanvas->addWidget(&_rootcanvas);
_ui.lblParticipantName->setText(_ui.lblParticipantName->text().arg(_name));
_histo->Draw();
connect(_socket.get(), &QWebSocket::textMessageReceived, this, &ClientGui::handleMessageFromServer);
connect(_socket.get(), QOverload<QAbstractSocket::SocketError>::of(&QWebSocket::error), this, &ClientGui::handleSocketError);
connect(_socket.get(), &QWebSocket::disconnected, this, &ClientGui::handleSocketDisconnected);
connect(_ui.cmdTargetFarther, &QPushButton::clicked, std::bind(&ClientGui::sendSimpleActiontoServer, this, Protocol::Action::moveTargetFarther));
connect(_ui.cmdTargetCloser, &QPushButton::clicked, std::bind(&ClientGui::sendSimpleActiontoServer, this, Protocol::Action::moveTargetCloser));
connect(_ui.cmdOpenVacuumValve, &QPushButton::clicked, std::bind(&ClientGui::sendSimpleActiontoServer, this, Protocol::Action::openVacuumValve));
connect(_ui.cmdCloseVacuumValve, &QPushButton::clicked, std::bind(&ClientGui::sendSimpleActiontoServer, this, Protocol::Action::closeVacuumValve));
connect(_ui.cmdTargetClose, &QPushButton::clicked, std::bind(&ClientGui::setBeamholeState, this, ExperimentState::BeamHoleState::Closed));
connect(_ui.cmdTargetOpen, &QPushButton::clicked, std::bind(&ClientGui::setBeamholeState, this, ExperimentState::BeamHoleState::Open));
//connect(_ui.cmdTargetGold, &QPushButton::clicked, std::bind(&ClientGui::setBeamholeState, this, ExperimentState::BeamHoleState::GoldFoil));
connect(_ui.cmdStartAdc, &QPushButton::clicked, std::bind(&ClientGui::sendSimpleActiontoServer, this, Protocol::Action::startAdc));
connect(_ui.cmdStopAdc, &QPushButton::clicked, std::bind(&ClientGui::sendSimpleActiontoServer, this, Protocol::Action::stopAdc));
connect(_ui.cmdSaveData, &QPushButton::clicked, this, &ClientGui::saveRootFile);
connect(_ui.cmdClearHisto, &QPushButton::clicked, this, &ClientGui::clearHistogram);
connect(_ui.cmdUpdateHisto, &QPushButton::clicked, std::bind(&ClientGui::sendSimpleActiontoServer, this, Protocol::Action::sendHistogramUpdate));
connect(_ui.cmdAdcThresholdUp, &QPushButton::clicked, std::bind(&ClientGui::sendSimpleActiontoServer, this, Protocol::Action::adcThresholdUp));
connect(_ui.cmdAdcThresholdDown, &QPushButton::clicked, std::bind(&ClientGui::sendSimpleActiontoServer, this, Protocol::Action::adcThresholdDown));
connect(_ui.cmdTargetGold, &QPushButton::clicked, [this]() -> void {QMessageBox::information(this, tr("Experiment Part not available"), tr("This part of the experiment is currently left out, because it has also been left out in the real experiment in the recent years.\nThis measurement only confirms the calibraton of the setup and takes a long time due to the low activity of the real-world ²⁴¹Am source."), QMessageBox::Ok);});
gStyle->SetCanvasPreferGL(true);
connect(&_rootTimer, &QTimer::timeout, []() -> void {gSystem->ProcessEvents();});
_rootTimer.setInterval(std::chrono::milliseconds(20));
_rootTimer.start();
_ui.pbAdcThreshold->setMinimum(ExperimentState::AdcConversion::minimum);
_ui.pbAdcThreshold->setMaximum(ExperimentState::AdcConversion::maximum);
const QRect textRect = _ui.lblTargetClosed->fontMetrics().boundingRect(QStringLiteral("➤"));
_ui.lblTargetClosed->setMinimumSize(textRect.width() + 2, textRect.height() + 2);
_ui.lblTargetClosed->setMaximumSize(textRect.width() + 2, textRect.height() + 2);
_ui.lblTargetOpen->setMinimumSize(textRect.width() + 2, textRect.height() + 2);
_ui.lblTargetOpen->setMaximumSize(textRect.width() + 2, textRect.height() + 2);
_ui.lblTargetGoldFoil->setMinimumSize(textRect.width() + 2, textRect.height() + 2);
_ui.lblTargetGoldFoil->setMaximumSize(textRect.width() + 2, textRect.height() + 2);
}
void ClientGui::handleMessageFromServer(const QString& message)
{
//logWarning(QStringLiteral("Received: ") + message);
const Protocol::Command command = Protocol::Command::fromString(message);
//logWarning(QStringLiteral("Test: ") + command.toString());
switch (command.action) {
case Protocol::Action::invalid:
case Protocol::Action::authenticate:
case Protocol::Action::storeToken:
case Protocol::Action::sendHistogramUpdate:
case Protocol::Action::moveTargetCloser:
case Protocol::Action::moveTargetFarther:
case Protocol::Action::setTargetState:
case Protocol::Action::openVacuumValve:
case Protocol::Action::closeVacuumValve:
case Protocol::Action::startAdc:
case Protocol::Action::stopAdc:
break;
case Protocol::Action::clearHistogram:
clearHistogram(true);
break;
case Protocol::Action::adcThresholdUp:
case Protocol::Action::adcThresholdDown:
case Protocol::Action::queryServerInformation:
break;
case Protocol::Action::updateExperimentState:
_state.updateFromCommand(command);
displayExperimentState();
break;
case Protocol::Action::updateHistogram:
updateHistogram(command.arguments[QStringLiteral("bincontents")]);
break;
case Protocol::Action::fillHistogram:
fillEventsIntoHistogram(command.arguments[QStringLiteral("adcchannels")]);
break;
case Protocol::Action::error:
default:
return;
}
}
void ClientGui::handleSocketError(QAbstractSocket::SocketError error)
{
logError(QStringLiteral("Socket Error ") + QString::number(error) + ": " + _socket->errorString());
QMessageBox::critical(
this,
tr("Connection Error"),
tr("There is a problem with the connection to the server:\n\n")
+ _socket->errorString()
);
}
void ClientGui::handleSocketDisconnected()
{
logError(QStringLiteral("The websocket connection has been disconnected."));
QMessageBox::critical(
this,
tr("Connection Interrupted"),
tr("The connection to the server has been disconnected, therefore this application cannot continue.\nPlease check your network connection, restart the application and login again.\nAs the simulated experiment is running on the server, there is a good chance that no data has been lost.")
);
QApplication::exit(1);
}
void ClientGui::sendSimpleActiontoServer(const Protocol::Action action)
{
const auto command = Protocol::Command(
action,
Protocol::Command::Arguments{},
_token
);
_socket->sendTextMessage(command.toString());
}
void ClientGui::setBeamholeState(const ExperimentState::BeamHoleState state)
{
const auto command = Protocol::Command(
Protocol::Action::setTargetState,
Protocol::Command::Arguments{std::make_pair(QStringLiteral("newstate"), getBeamholeStateString(state))},
_token
);
_socket->sendTextMessage(command.toString());
}
QString ClientGui::getBeamholeStateString(const ExperimentState::BeamHoleState state)
{
switch (state) {
case ExperimentState::BeamHoleState::Open:
return QStringLiteral("open");
case ExperimentState::BeamHoleState::GoldFoil:
return QStringLiteral("goldfoil");
case ExperimentState::BeamHoleState::Closed:
default:
return QStringLiteral("closed");
}
}
void ClientGui::displayExperimentState()
{
_ui.lblPressureValue->setText(QStringLiteral("%1 hPa\n%2 Torr").arg(_state.pressure).arg(_state.convertHectoPascalToTorr(_state.pressure)));
_ui.lblTargetPosition->setText(tr("%1 mm from detector (%L2 revolutions)").arg(_state.targetPosition).arg((_state.targetPosition - PositionZeroRevolutions) / TargetMovementMillimeterPerRevolution));
_ui.pbValveStatus->setValue(_state.vacuumValve);
_ui.pbAdcThreshold->setValue(_state.adcThreshold);
switch (_state.beamHoleState) {
case ExperimentState::BeamHoleState::Open:
_ui.lblTargetClosed->clear();
_ui.lblTargetGoldFoil->clear();
_ui.lblTargetOpen->setText(QStringLiteral("➤"));
break;
case ExperimentState::BeamHoleState::GoldFoil:
_ui.lblTargetClosed->clear();
_ui.lblTargetOpen->clear();
_ui.lblTargetGoldFoil->setText(QStringLiteral("➤"));
break;
case ExperimentState::BeamHoleState::Closed:
default:
_ui.lblTargetOpen->clear();
_ui.lblTargetGoldFoil->clear();
_ui.lblTargetClosed->setText(QStringLiteral("➤"));
break;
}
switch (_state.adcState) {
case ExperimentState::AdcState::Running:
_ui.lblAdcStopped->clear();
_ui.lblAdcRunning->setText(QStringLiteral("➤"));
_ui.cmdAdcThresholdUp->setEnabled(false);
_ui.cmdAdcThresholdDown->setEnabled(false);
_ui.cmdUpdateHisto->setEnabled(false);
_ui.cmdClearHisto->setEnabled(false);
_ui.cmdFitHisto->setEnabled(false);
_ui.cmdSaveData->setEnabled(false);
_ui.cmdTargetOpen->setEnabled(false);
_ui.cmdTargetClose->setEnabled(false);
_ui.cmdTargetGold->setEnabled(false);
_ui.cmdTargetCloser->setEnabled(false);
_ui.cmdTargetFarther->setEnabled(false);
break;
case ExperimentState::AdcState::Stopped:
default:
_ui.lblAdcRunning->clear();
_ui.lblAdcStopped->setText(QStringLiteral("➤"));
_ui.cmdAdcThresholdUp->setEnabled(true);
_ui.cmdAdcThresholdDown->setEnabled(true);
_ui.cmdUpdateHisto->setEnabled(true);
_ui.cmdClearHisto->setEnabled(true);
_ui.cmdFitHisto->setEnabled(true);
_ui.cmdSaveData->setEnabled(true);
_ui.cmdTargetOpen->setEnabled(true);
_ui.cmdTargetClose->setEnabled(true);
_ui.cmdTargetGold->setEnabled(true);
_ui.cmdTargetCloser->setEnabled(true);
_ui.cmdTargetFarther->setEnabled(true);
break;
}
}
void ClientGui::fillEventsIntoHistogram(const QString& adcchannels)
{
const QStringList channellist = adcchannels.split(QChar(';'), QString::SkipEmptyParts, Qt::CaseInsensitive);
for (QString channelString : channellist) {
bool ok = false;
double channel = channelString.toDouble(&ok);
if (ok)
_histo->Fill(channel);
else
logWarning(QStringLiteral("Cannot convert ADC channel number string ") + channelString + QStringLiteral(" to double."));
}
_rootcanvas.getCanvas()->Modified();
_rootcanvas.getCanvas()->Update();
}
void Fp311Online::ClientGui::updateHistogram(const QString& bincontents)
{
if (_state.adcState == ExperimentState::AdcState::Running)
return;
const QStringList binlist = bincontents.split(QChar(';'), QString::SkipEmptyParts, Qt::CaseInsensitive);
if (binlist.size() != ExperimentState::NumberOfAdcChannels)
{
logError("The number of bins in the “bincontents” argument of the ”updateHistogram” command does not match ExperimentState::NumberOfAdcChannels.");
return;
}
for(int i = 1; i <= ExperimentState::NumberOfAdcChannels; i++)
{
bool ok = false;
double content = binlist[i-1].toDouble(&ok);
if (ok)
_histo->SetBinContent(i, content);
else
logWarning(QStringLiteral("Cannot convert bin content string ") + binlist[i] + QStringLiteral(" to double."));
}
_histo->ResetStats();
_rootcanvas.getCanvas()->Modified();
_rootcanvas.getCanvas()->Update();
}
void ClientGui::clearHistogram(const bool fromServer)
{
for (int i = 0; i < ExperimentState::NumberOfAdcChannels - 1; i++)
_histo->SetBinContent(i, 0);
_histo->ResetStats();
_rootcanvas.getCanvas()->Modified();
_rootcanvas.getCanvas()->Update();
if (!fromServer)
sendSimpleActiontoServer(Protocol::Action::clearHistogram);
}
void Fp311Online::ClientGui::saveRootFile()
{
const QString filename = QFileDialog::getSaveFileName(
this,
tr("Save Content of Histogram to ROOT File"),
QString(),
tr("ROOT files (*.root)")
);
if (filename.isEmpty())
return;
TFile savefile(filename.toUtf8().constData(), "RECREATE");
savefile.cd();
_histo->Write("ADC");
savefile.Close("R");
}
void ClientGui::showEvent(QShowEvent* event)
{
QMainWindow::showEvent(event);
_rootcanvas.getCanvas()->Modified();
_rootcanvas.getCanvas()->Update();
}