Fix QT quirk

So far, we used QT interfaces in a wrong way. QT is not thread safe, so
use its signal/slot concept and move network communication stuff to a
separate QThread.

Signed-off-by: Ralf Ramsauer <ralf@ramses-pyramidenbau.de>
This commit is contained in:
Ralf Ramsauer 2016-07-21 22:48:56 +02:00
parent 4ccf8e4f24
commit d1d0d5b160
6 changed files with 171 additions and 161 deletions

View File

@ -100,6 +100,8 @@ set(_DOORLOCK_CLIENT_SRCS
client/mainwindow.h
client/mainwindow.cpp
client/mainwindow.ui
client/network.cpp
client/network.h
client/wave.h
client/wave.cpp)

View File

@ -3,17 +3,11 @@
#include <string>
#include <thread>
#include <boost/asio.hpp>
#include <boost/program_options.hpp>
#include <QApplication>
#include "config.h"
#include "../lib/clientmessage.h"
#include "../lib/logger.h"
#include "../lib/response.h"
#include "mainwindow.h"
// Info about doorlock-client version
@ -22,117 +16,25 @@ const static std::string version =
const static std::string gitversion =
DOORLOCK_GIT_BRANCH "-" DOORLOCK_GIT_COMMIT_HASH;
static Logger &l = Logger::get();
namespace po = boost::program_options;
namespace ba = boost::asio;
using ba::ip::tcp;
static ba::io_service io_service;
const static std::string subscriptionCommand =
"{ \"command\": \"subscribe\"}";
// The receive buffer length of the TCP socket
constexpr static int SOCKET_BUFFERLENGTH = 2048;
static volatile bool app_run = true;
static std::unique_ptr<MainWindow> mainWindow = nullptr;
static void onDoorlockUpdate(const Clientmessage &msg)
{
const auto& doormessage = msg.doormessage();
l("Received message", LogLevel::info);
l((std::string)" token: " + msg.web_address(),
LogLevel::info);
l((std::string)" open: " + std::to_string(msg.isOpen()),
LogLevel::info);
l((std::string)" button lock: " + std::to_string(doormessage.isLockButton),
LogLevel::info);
l((std::string)" button unlock: " + std::to_string(doormessage.isUnlockButton),
LogLevel::info);
l((std::string)" emergency open: " + std::to_string(doormessage.isEmergencyUnlock),
LogLevel::info);
if (mainWindow) {
mainWindow->setClientmessage(msg);
l(LogLevel::info, "Clientmessage successfully set");
} else {
l(LogLevel::error, "No valid UI object!");
}
}
static int doorlock_client(const std::string &hostname,
const unsigned short port)
{
int retval = 0;
try {
tcp::resolver resolver(io_service);
tcp::socket socket(io_service);
tcp::resolver::query query(hostname, std::to_string(port));
tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
tcp::resolver::iterator end;
boost::system::error_code error = ba::error::host_not_found;
std::vector<char> data;
while (error && endpoint_iterator != end) {
socket.close();
socket.connect(*endpoint_iterator++, error);
}
if (error)
throw boost::system::system_error(error);
// After connection is established, send the subscription command
socket.write_some(ba::buffer(subscriptionCommand), error);
if (error)
throw boost::system::system_error(error);
data.resize(SOCKET_BUFFERLENGTH);
std::function<void(void)> receiveMessage = [&] () {
socket.async_read_some(ba::buffer(data),
[&] (const boost::system::error_code &ec,
const size_t length)
{
if (ec) {
throw boost::system::system_error(ec);
}
const auto message = Clientmessage::fromString(
std::string(data.begin(), data.begin()+length));
onDoorlockUpdate(message);
receiveMessage();
});
};
receiveMessage();
io_service.run();
}
catch(const Response &err) {
l(err.message, LogLevel::error);
retval = -1;
}
catch(const std::exception &err) {
l(LogLevel::error, err.what());
retval = -1;
}
io_service.reset();
return retval;
}
int main(int argc, char** argv)
{
std::string hostname;
unsigned short port;
int retval = 0;
Logger &l = Logger::get();
l((std::string)"Hello, this is " + version + " built on " + gitversion,
LogLevel::info);
try {
QApplication app(argc, argv);
std::string hostname;
unsigned short port;
po::variables_map vm;
qRegisterMetaType<Clientmessage>("Clientmessage");
app.setOrganizationName("Binary Kitchen");
app.setApplicationName("doorlock-client");
po::options_description desc("doorlockd (" + version + " built on " + gitversion + ")");
desc.add_options()
("help,h",
@ -144,7 +46,6 @@ int main(int argc, char** argv)
po::value<std::string>(&hostname)->default_value("localhost"),
"IP or name of host running doorlockd");
po::variables_map vm;
po::store(po::command_line_parser(argc, argv).options(desc).run(), vm);
if (vm.count("help"))
@ -154,60 +55,23 @@ int main(int argc, char** argv)
}
po::notify(vm);
l(LogLevel::notice, "Starting doorlock-client");
// Start main GUI
MainWindow mainWindow(hostname, port);
// This routine will never return under normal conditions
retval = app.exec();
mainWindow.hide();
mainWindow.close();
}
catch(const std::exception &e)
{
l(LogLevel::error, e.what());
exit(-1);
retval = -1;
}
l(LogLevel::notice, "Starting doorlock-client");
QApplication app(argc, argv);
app.setOrganizationName("Binary Kitchen");
app.setApplicationName("doorlock-client");
try {
mainWindow = std::unique_ptr<MainWindow>(new MainWindow);
mainWindow->showFullScreen();
}
catch(const std::exception &e)
{
l(LogLevel::error, e.what());
exit(-1);
}
// Start the TCP client as thread
std::thread clientThread = std::thread([&] () {
// If the TCP client returns, an error has occured
// In normal operation, it never returns
while (app_run) {
doorlock_client(hostname, port);
if (app_run) {
l(LogLevel::error, "client aborted, retrying in 5 seconds");
// Todo: Write message to QT frontend
sleep(5);
}
}
mainWindow->hide();
mainWindow->close();
});
// This routine will never return in normal operation
app.exec();
app_run = false;
// Stop the IO service
io_service.stop();
clientThread.join();
if (mainWindow)
mainWindow.reset();
l(LogLevel::notice, "Stopping doorlock-client");
return 0;
return retval;
}

View File

@ -2,8 +2,11 @@
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "network.h"
MainWindow::MainWindow(QWidget *parent) :
MainWindow::MainWindow(const std::string &hostname,
const unsigned short port,
QWidget* parent) :
QWidget(parent),
ui(new Ui::MainWindow),
_soundLock(Wave(SOUND_LOCK)),
@ -15,6 +18,12 @@ MainWindow::MainWindow(QWidget *parent) :
{
ui->setupUi(this);
_LED(false);
NetworkThread* nw = new NetworkThread(hostname, port);
connect(nw, SIGNAL(new_clientmessage(Clientmessage)),
SLOT(setClientmessage(Clientmessage)));
connect(nw, SIGNAL(finished()),
nw, SLOT(deleteLater()));
nw->start();
}
MainWindow::~MainWindow()

View File

@ -4,6 +4,7 @@
#include <QWidget>
#include "../lib/clientmessage.h"
#include "../lib/logger.h"
#include "wave.h"
namespace Ui {
@ -15,13 +16,16 @@ class MainWindow : public QWidget
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
explicit MainWindow(const std::string &hostname,
const unsigned short port,
QWidget *parent = 0);
MainWindow(const MainWindow &rhs);
MainWindow &operator =(const MainWindow &rhs);
~MainWindow();
public slots:
void setClientmessage(const Clientmessage &msg);
private:

View File

@ -0,0 +1,94 @@
#include "network.h"
#include "../lib/response.h"
namespace ba = boost::asio;
using ba::ip::tcp;
const std::string NetworkThread::_subscription_command = "{ \"command\": \"subscribe\"}";
NetworkThread::NetworkThread(const std::string &hostname,
const unsigned short port) :
QThread(),
_l(Logger::get()),
_hostname(hostname),
_port(port),
_io_service()
{
}
void NetworkThread::run() {
do {
try {
tcp::resolver resolver(_io_service);
tcp::socket socket(_io_service);
tcp::resolver::query query(_hostname, std::to_string(_port));
tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
tcp::resolver::iterator end;
boost::system::error_code error = ba::error::host_not_found;
std::vector<char> data;
while (error && endpoint_iterator != end) {
socket.close();
socket.connect(*endpoint_iterator++, error);
}
if (error)
throw boost::system::system_error(error);
// After connection is established, send the subscription command
socket.write_some(ba::buffer(_subscription_command), error);
if (error)
throw boost::system::system_error(error);
data.resize(_SOCKET_BUFFERLENGTH);
std::function<void(void)> receiveMessage = [&] () {
socket.async_read_some(ba::buffer(data),
[&] (const boost::system::error_code &ec,
const size_t length)
{
if (ec) {
throw boost::system::system_error(ec);
}
const auto msg = Clientmessage::fromString(
std::string(data.begin(), data.begin()+length));
// Received valid Clientmessage
// Log it!
const auto& doormessage = msg.doormessage();
_l("Received message", LogLevel::info);
_l((std::string)" token: " + msg.web_address(),
LogLevel::info);
_l((std::string)" open: " + std::to_string(msg.isOpen()),
LogLevel::info);
_l((std::string)" button lock: " + std::to_string(doormessage.isLockButton),
LogLevel::info);
_l((std::string)" button unlock: " + std::to_string(doormessage.isUnlockButton),
LogLevel::info);
_l((std::string)" emergency open: " + std::to_string(doormessage.isEmergencyUnlock),
LogLevel::info);
// Emit the new message
emit new_clientmessage(msg);
// Wait for new message
receiveMessage();
});
};
receiveMessage();
_io_service.run();
}
catch(const Response &err) {
_l(err.message, LogLevel::error);
}
catch(const std::exception &err) {
_l(LogLevel::error, err.what());
}
sleep(1);
} while(1);
_io_service.reset();
}

View File

@ -0,0 +1,37 @@
#ifndef NETWORK_H
#define NETWORK_H
#include <string>
#include <boost/asio.hpp>
#include <QThread>
#include "../lib/clientmessage.h"
#include "../lib/logger.h"
class NetworkThread : public QThread {
Q_OBJECT
public:
NetworkThread(const std::string &hostname,
const unsigned short port);
private:
void run();
Logger &_l;
const std::string _hostname;
const unsigned short _port;
boost::asio::io_service _io_service;
const static std::string _subscription_command;
// The receive buffer length of the TCP socket
constexpr static int _SOCKET_BUFFERLENGTH = {2048};
signals:
void new_clientmessage(Clientmessage msg);
};
#endif