DoorOpener.cpp, DoorOpener.h und 10 weitere dateien aktualisiert...

master
Tibor 2 years ago
parent 4a005b866c
commit dd5c8444af

@ -0,0 +1,34 @@
#include "DoorOpener.h"
#include "log.h"
#include <QDebug>
DoorOpener::DoorOpener(QObject* parent)
{
Q_UNUSED(parent);
LOG_DBG("Initializing DoorOpener:");
LOG_DBG("\t - Host: " + _hostname);
LOG_DBG("\t - Port: " + std::to_string(_port));
_client = new QMqttClient();
_client->setHostname(_hostname.c_str());
_client->setPort(_port);
_client->connectToHost();
if (_client->state() == QMqttClient::Connected) {
LOG_INF("Connected to host " + _hostname + ".");
}
else {
LOG_ERR("Unable to connect to host " + _hostname + ".");
}
}
bool DoorOpener::open()
{
LOG_DBG("Trying to send MQTT message to server with topic " + _topic + ".");
qint32 retval { _client->publish(QMqttTopicName(_topic.c_str()), QByteArray(MQTT_ACCESS_TOKEN)) };
LOG_DBG("publish() returned " + std::to_string(retval) + ".");
return 0 == retval;
}

@ -0,0 +1,28 @@
#ifndef DOOROPENER_H
#define DOOROPENER_H
#include <QObject>
#include <QByteArray>
#include <QMqttClient>
#define MQTT_ACCESS_TOKEN "SCJFMzEauiJvaHaQ0f50TM0safyZR0sq_taiOpenHeimDall"
class DoorOpener : public QObject
{
Q_OBJECT
public:
DoorOpener(QObject *parent = nullptr);
~DoorOpener() = default;
public slots:
bool open();
private:
QMqttClient* _client { nullptr };
// TODO
std::string _hostname { "localhost" };
std::uint32_t _port { 1883 };
std::string _topic { "taibsu_at_home/heimdall" };
};
#endif // DOOROPENER_H

@ -1,4 +1,5 @@
#include "QmlMqttClient.h"
#include "log.h"
QmlMqttClient::QmlMqttClient(QObject *parent)
: QMqttClient(parent)
@ -7,8 +8,11 @@ QmlMqttClient::QmlMqttClient(QObject *parent)
QmlMqttSubscription* QmlMqttClient::subscribe(const QString &topic)
{
auto sub = QMqttClient::subscribe(topic, 0);
auto result = new QmlMqttSubscription(sub, this);
LOG_INF("Initializing subscription with topic " + topic.toStdString() + ".");
QMqttSubscription* sub { QMqttClient::subscribe(topic, 0) };
QmlMqttSubscription* result { new QmlMqttSubscription(sub, this) };
return result;
}

BIN
bg.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 821 KiB

@ -1,4 +1,4 @@
QT += quick mqtt
QT += quick mqtt multimedia widgets quicktemplates2
CONFIG += c++17
@ -7,6 +7,7 @@ CONFIG += c++17
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES += \
DoorOpener.cpp \
QmlMqttClient.cpp \
main.cpp
@ -27,4 +28,6 @@ else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
HEADERS += \
QmlMqttClient.h
DoorOpener.h \
QmlMqttClient.h \
log.h

109
log.h

@ -0,0 +1,109 @@
#ifndef LOG_H
#define LOG_H
#include <iostream>
#include <chrono>
#include <ctime>
#include <sstream>
#include <thread>
#define QSTR(x) QObject::tr(x)
namespace tai {
class Log {
public:
~Log() {}
enum class Level {
DEBUG = 0,
INFO,
WARNING,
ERROR,
FATAL
};
static Log& logger() {
static Log _inst;
return _inst;
}
static std::string threadIdToStr(std::thread::id const& tid)
{
std::stringstream ss;
ss << tid;
return ss.str();
}
std::string currentTime()
{
time_t now = time(nullptr);
tm* ltm = localtime(&now);
std::string currentTime =
std::to_string(ltm->tm_mday) + "." +
std::to_string(ltm->tm_mon) + "." +
std::to_string(ltm->tm_year + 1900) + ", ";
if (ltm->tm_hour < 10)
currentTime += "0";
currentTime += std::to_string(ltm->tm_hour) + ":";
if (ltm->tm_min < 10)
currentTime += "0";
currentTime += std::to_string(ltm->tm_min) + ":";
if (ltm->tm_sec < 10)
currentTime += "0";
currentTime += std::to_string(ltm->tm_sec);
return currentTime;
}
void log(std::string const& msg, Level lv = Level::DEBUG) {
std::string log_level;
switch (lv) {
case Level::DEBUG:
log_level = "DEBUG\t";
break;
case Level::INFO:
log_level = "INFO\t";
break;
case Level::WARNING:
log_level = "WARNING\t";
break;
case Level::ERROR:
log_level = "ERROR\t";
break;
case Level::FATAL:
log_level = "FATAL\t";
break;
default:
log_level = "UNKNOWN\t";
break;
}
std::cerr << log_level << " | "
<< "(" << currentTime() << ") "
<< msg << std::endl;
}
private:
Log() {}
Log(const Log&);
Log& operator=(const Log&) = delete;
};
} // namespace tai
#define LOG_DBG(x) tai::Log::logger().log(x, tai::Log::Level::DEBUG)
#define LOG_INF(x) tai::Log::logger().log(x, tai::Log::Level::INFO)
#define LOG_WRN(x) tai::Log::logger().log(x, tai::Log::Level::WARNING)
#define LOG_ERR(x) tai::Log::logger().log(x, tai::Log::Level::ERROR)
#define LOG_FTL(x) tai::Log::logger().log(x, tai::Log::Level::FATAL)
#define LOG_UKN(x) tai::Log::logger().log(x, tai::Log::Level::UNKNOWN)
#endif // LOG_H

@ -1,7 +1,9 @@
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include "log.h"
#include "QmlMqttClient.h"
#include "DoorOpener.h"
int main(int argc, char *argv[])
{
@ -9,19 +11,34 @@ int main(int argc, char *argv[])
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
// Logging configuration
std::string const& logfile { "heimdall-client.log" };
if (!freopen(logfile.c_str(), "a", stderr)) {
// we can still write to stderr.
LOG_ERR("Unable to create log file! Continuing without log.");
}
LOG_INF("------------------\n");
LOG_INF("Hello from heimdall client.");
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
if (!obj && url == objUrl) {
LOG_FTL("Unable to load " + url.toString().toStdString() + ". Exiting.");
QCoreApplication::exit(-1);
}
}, Qt::QueuedConnection);
qmlRegisterType<QmlMqttClient>("MqttClient", 1, 0, "MqttClient");
qmlRegisterUncreatableType<QmlMqttSubscription>("MqttClient", 1, 0, "MqttSubscription", QLatin1String("Subscriptions are read-only"));
qmlRegisterType<DoorOpener>("DoorOpener", 1, 0, "DoorOpener");
engine.load(url);
return app.exec();

@ -2,151 +2,294 @@ import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.1
import QtQuick.Layouts 1.1
import QtMultimedia 5.12
import MqttClient 1.0
import DoorOpener 1.0
Window {
width: 360
height: 640
visible: true
title: qsTr("taiBell Qt Client")
property var tempSubscription: 0
MqttClient {
id: client
hostname: hostnameField.text
port: portField.text
}
ListModel {
id: messageModel
}
function addMessage(payload)
{
messageModel.insert(0, {"payload" : payload})
if (messageModel.count >= 100)
messageModel.remove(99)
}
GridLayout {
anchors.fill: parent
anchors.margins: 10
columns: 2
Label {
text: "Hostname:"
enabled: client.state === MqttClient.Disconnected
}
TextField {
id: hostnameField
Layout.fillWidth: true
text: "test.mosquitto.org"
placeholderText: "<Enter host running MQTT broker>"
enabled: client.state === MqttClient.Disconnected
}
Label {
text: "Port:"
enabled: client.state === MqttClient.Disconnected
}
TextField {
id: portField
Layout.fillWidth: true
text: "1883"
placeholderText: "<Port>"
inputMethodHints: Qt.ImhDigitsOnly
enabled: client.state === MqttClient.Disconnected
}
Button {
id: connectButton
Layout.columnSpan: 2
Layout.fillWidth: true
text: client.state === MqttClient.Connected ? "Disconnect" : "Connect"
onClicked: {
if (client.state === MqttClient.Connected) {
client.disconnectFromHost()
messageModel.clear()
tempSubscription.destroy()
tempSubscription = 0
} else
client.connectToHost()
}
}
RowLayout {
enabled: client.state === MqttClient.Connected
Layout.columnSpan: 2
Layout.fillWidth: true
Label {
text: "Topic:"
}
TextField {
id: subField
placeholderText: "<Subscription topic>"
Layout.fillWidth: true
enabled: tempSubscription === 0
}
Button {
id: subButton
text: "Subscribe"
visible: tempSubscription === 0
onClicked: {
if (subField.text.length === 0) {
console.log("No topic specified to subscribe to.")
return
}
tempSubscription = client.subscribe(subField.text)
tempSubscription.messageReceived.connect(addMessage)
}
}
}
ListView {
id: messageView
model: messageModel
height: 300
width: 200
Layout.columnSpan: 2
Layout.fillHeight: true
Layout.fillWidth: true
clip: true
delegate: Rectangle {
width: messageView.width
height: 30
color: index % 2 ? "#DDDDDD" : "#888888"
radius: 5
Text {
text: payload
anchors.centerIn: parent
}
}
}
Label {
function stateToString(value) {
if (value === 0)
return "Disconnected"
else if (value === 1)
return "Connecting"
else if (value === 2)
return "Connected"
else
return "Unknown"
}
Layout.columnSpan: 2
Layout.fillWidth: true
color: "#333333"
text: "Status:" + stateToString(client.state) + "(" + client.state + ")"
enabled: client.state === MqttClient.Connected
}
}
width: 360
height: 640
visible: true
title: qsTr("heimdall")
// background image
Image {
source: "qrc:/bg.jpg"
anchors.fill: parent
fillMode: Image.Pad
opacity: .25
}
DoorOpener {
id: doorOpener
}
// mqtt client
MqttClient {
id: client
// TODO
property string _host: "localhost"
property string _port: "1883"
property string _topic: "taibsu_at_home/heimdall"
hostname: _host
port: _port
Component.onCompleted: {
connectToHost()
}
onConnected: {
subscribe(_topic)
}
onDisconnected: {
video.stop()
}
onStateChanged: {
stateIndicator.color = getState()
stateIndicatorText.text = connected ? "✔" : "𐄂"
}
onMessageReceived: {
video.play()
}
Component.onDestruction: {
disconnectFromHost()
}
function getState() {
switch (state) {
case MqttClient.Disconnected:
return "red"
case MqttClient.Connecting:
return "yellow"
case MqttClient.Connected:
return "green"
default:
return "gray"
}
}
}
// caption
Label {
id: caption
// text: "Heimdall - Welcome Home"
text: "Et hat jeklingelt. Kieke:"
font {
family: "Open Sans MS"
bold: true
capitalization: Font.SmallCaps
pointSize: 14
}
anchors {
top: parent.top
topMargin: 10
horizontalCenter: parent.horizontalCenter
}
}
// webcam stream
Video {
id: video
objectName: "vplayer"
width: parent.width - 20
height: 200
autoPlay: client.connected
source: "rtsp://localhost:8554/mystream" // TODO
onPlaying: vplaceholder.visible = false
Image {
id: vplaceholder
width: parent.width
height: parent.height
source: "qrc:/test_picture.png"
}
anchors {
top: caption.bottom
topMargin: 10
horizontalCenter: parent.horizontalCenter
}
onErrorChanged: {
console.log("error: " + video.errorString)
}
MouseArea {
anchors.fill: parent
onClicked: {
video.muted = !video.muted
}
}
focus: true
Image {
id: muteIndicator
source: "mute_white.png"
width: 64
height: width
visible: video.muted
anchors.centerIn: parent
}
} // Video
// Buttons
Item {
width: parent.width
height: 50
anchors {
top: video.bottom
topMargin: 10
centerIn: parent
}
Button {
id: btnTalk
objectName: "btnTalk"
text: "Sprechen"
anchors.left: parent.left
anchors.leftMargin: 10
onClicked: {
bgrTalk.start()
}
Rectangle {
id: bgrect_talk
width: parent.width
height: parent.height
color: "white"
}
SequentialAnimation {
id: bgrTalk
PropertyAnimation {
target: bgrect_talk
property: "color"
to: "darkseagreen"
duration: 500
}
PropertyAnimation {
target: bgrect_talk
property: "color"
to: "white"
duration: 500
easing.type: Easing.InCirc
}
}
}
Button {
id: btnOpen
objectName: "btnOpen"
text: "Öffnen"
property var bgColor: "darkred"
anchors.right: parent.right
anchors.rightMargin: 10
onClicked: {
var opened = doorOpener.open()
if (opened) {
bgColor = "forestgreen"
}
bgrOpened.start()
console.log("color: " + bgColor)
}
Rectangle {
id: bgrect_open
width: parent.width
height: parent.height
color: "white"
}
SequentialAnimation {
id: bgrOpened
PropertyAnimation {
target: bgrect_open
property: "color"
to: btnOpen.bgColor
duration: 500
}
PropertyAnimation {
target: bgrect_open
property: "color"
to: "white"
duration: 500
easing.type: Easing.InCirc
}
}
}
}
Item {
anchors {
left: parent.left
leftMargin: 10
bottom: parent.bottom
bottomMargin: 10
}
width: parent.width - 20
height: 25
Rectangle {
id: stateIndicator
width: 75
height: 20
color: "gray"
Text {
id: stateIndicatorText
text: "?"
color: "white"
anchors.centerIn: parent
}
}
Button {
anchors.right: parent.right
text: "Reconnect"
onPressed: {
client.disconnectFromHost()
vplaceholder.visible = true
client.connectToHost()
}
width: 75
height: 20
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

@ -1,5 +1,9 @@
<RCC>
<qresource prefix="/">
<file>main.qml</file>
<file>mute.png</file>
<file>mute_white.png</file>
<file>bg.jpg</file>
<file>test_picture.png</file>
</qresource>
</RCC>

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB