implement node js bridge, mostly working, some issues still
This commit is contained in:
parent
275e3b3921
commit
0f262116b6
@ -39,13 +39,17 @@ void AWSConnector::connect2AWS(const QString &endpoint, const QString &clientId,
|
||||
m_client->SetAutoReconnectEnabled(true);
|
||||
m_clientId = clientId;
|
||||
|
||||
// subscribe to pairing api topics
|
||||
subscribe({QString("%1/pair/response").arg(m_clientId),
|
||||
QString("%1/pair/list/response").arg(m_clientId)
|
||||
});
|
||||
|
||||
m_connectingFuture = QtConcurrent::run([&]() {
|
||||
ResponseCode rc = m_client->Connect(std::chrono::milliseconds(30000), true, mqtt::Version::MQTT_3_1_1, std::chrono::seconds(60), Utf8String::Create(m_clientId.toStdString()), nullptr, nullptr, nullptr);
|
||||
if (rc == ResponseCode::MQTT_CONNACK_CONNECTION_ACCEPTED) {
|
||||
qCDebug(dcCloud) << "Connected to AWS.";
|
||||
emit connected();
|
||||
} else {
|
||||
qCWarning(dcCloud) << "Error connecting to AWS. Response code:" << QString::fromStdString(ResponseHelper::ToString(rc));
|
||||
qCWarning(dcAWS) << "Error connecting to AWS. Response code:" << QString::fromStdString(ResponseHelper::ToString(rc));
|
||||
m_client.reset();
|
||||
m_networkConnection.reset();
|
||||
}
|
||||
@ -66,19 +70,36 @@ bool AWSConnector::isConnected() const
|
||||
return m_connectingFuture.isFinished() && m_networkConnection && m_client && m_client->IsConnected();
|
||||
}
|
||||
|
||||
void AWSConnector::pairDevice(const QString &idToken, const QString &authToken, const QString &cognitoUserId)
|
||||
{
|
||||
QVariantMap map;
|
||||
map.insert("idToken", idToken);
|
||||
map.insert("authToken", authToken);
|
||||
map.insert("cognitoUserId", cognitoUserId);
|
||||
map.insert("id", ++m_transactionId);
|
||||
map.insert("timestamp", QDateTime::currentMSecsSinceEpoch());
|
||||
publish(QString("%1/pair").arg(m_clientId), map);
|
||||
m_pairingRequests.insert(m_transactionId, cognitoUserId);
|
||||
}
|
||||
|
||||
void AWSConnector::sendWebRtcHandshakeMessage(const QString &sessionId, const QVariantMap &map)
|
||||
{
|
||||
publish(sessionId + "/reply", map);
|
||||
}
|
||||
|
||||
|
||||
quint16 AWSConnector::publish(const QString &topic, const QVariantMap &message)
|
||||
{
|
||||
if (!isConnected()) {
|
||||
qCWarning(dcCloud()) << "Can't publish to AWS: Not connected.";
|
||||
qCWarning(dcAWS()) << "Can't publish to AWS: Not connected.";
|
||||
return -1;
|
||||
}
|
||||
QString fullTopic = QString("%1/%2").arg(m_clientId, topic);
|
||||
QString fullTopic = topic;
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromVariant(message);
|
||||
|
||||
uint16_t packetId = 0;
|
||||
ResponseCode res = m_client->PublishAsync(Utf8String::Create(fullTopic.toStdString()), false, false, mqtt::QoS::QOS1, jsonDoc.toJson().toStdString(), &publishCallback, packetId);
|
||||
qCDebug(dcCloud()) << "publish call queued with status:" << QString::fromStdString(ResponseHelper::ToString(res)) << packetId;
|
||||
qCDebug(dcAWS()) << "publish call queued with status:" << QString::fromStdString(ResponseHelper::ToString(res)) << packetId << "for topic" << topic << jsonDoc.toJson();
|
||||
s_requestMap.insert(packetId, this);
|
||||
return packetId;
|
||||
}
|
||||
@ -88,33 +109,42 @@ void AWSConnector::subscribe(const QStringList &topics)
|
||||
m_subscribedTopics.append(topics);
|
||||
|
||||
if (!isConnected()) {
|
||||
qCDebug(dcCloud()) << "Can't subscribe to AWS: Not connected. Subscription will happen upon next connection.";
|
||||
qCDebug(dcAWS()) << "Can't subscribe to AWS: Not connected. Subscription will happen upon next connection.";
|
||||
return;
|
||||
}
|
||||
subscribeInternally(topics);
|
||||
doSubscribe(topics);
|
||||
}
|
||||
|
||||
void AWSConnector::onConnected()
|
||||
{
|
||||
qCDebug(dcAWS()) << "AWS connected";
|
||||
if (!m_subscribedTopics.isEmpty()) {
|
||||
subscribeInternally(m_subscribedTopics);
|
||||
doSubscribe(m_subscribedTopics);
|
||||
}
|
||||
retrievePairedDeviceInfo();
|
||||
}
|
||||
|
||||
void AWSConnector::subscribeInternally(const QStringList &topics)
|
||||
void AWSConnector::retrievePairedDeviceInfo()
|
||||
{
|
||||
QVariantMap params;
|
||||
params.insert("timestamp", QDateTime::currentMSecsSinceEpoch());
|
||||
params.insert("id", ++m_transactionId);
|
||||
publish(QString("%1/pair/list").arg(m_clientId), params);
|
||||
}
|
||||
|
||||
void AWSConnector::doSubscribe(const QStringList &topics)
|
||||
{
|
||||
util::Vector<std::shared_ptr<mqtt::Subscription>> subscription_list;
|
||||
foreach (const QString &topic, topics) {
|
||||
QString finalTopic = QString("%1/%2").arg(m_clientId, topic);
|
||||
qCDebug(dcCloud()) << "topic to subscribe is" << finalTopic << "is valid topic:" << Subscription::IsValidTopicName(finalTopic.toStdString());
|
||||
auto subscription = mqtt::Subscription::Create(Utf8String::Create(finalTopic.toStdString()), mqtt::QoS::QOS1, &onSubscriptionReceivedCallback, std::shared_ptr<SubscriptionHandlerContextData>(this));
|
||||
qCDebug(dcAWS()) << "topic to subscribe is" << topic << "is valid topic:" << Subscription::IsValidTopicName(topic.toStdString());
|
||||
auto subscription = mqtt::Subscription::Create(Utf8String::Create(topic.toStdString()), mqtt::QoS::QOS1, &onSubscriptionReceivedCallback, std::shared_ptr<SubscriptionHandlerContextData>(this));
|
||||
subscription_list.push_back(subscription);
|
||||
}
|
||||
|
||||
|
||||
uint16_t packetId;
|
||||
ResponseCode res = m_client->SubscribeAsync(subscription_list, subscribeCallback, packetId);
|
||||
qCDebug(dcCloud()) << "subscribe call queued with status:" << QString::fromStdString(ResponseHelper::ToString(res)) << packetId;
|
||||
qCDebug(dcAWS()) << "subscribe call queued with status:" << QString::fromStdString(ResponseHelper::ToString(res)) << packetId;
|
||||
s_requestMap.insert(packetId, this);
|
||||
}
|
||||
|
||||
@ -122,24 +152,22 @@ void AWSConnector::publishCallback(uint16_t actionId, ResponseCode rc)
|
||||
{
|
||||
AWSConnector* obj = s_requestMap.take(actionId);
|
||||
if (!obj) {
|
||||
qCWarning(dcCloud())<< "Received a response callback but don't have an object waiting for it.";
|
||||
qCWarning(dcAWS())<< "Received a response callback but don't have an object waiting for it.";
|
||||
return;
|
||||
}
|
||||
|
||||
switch (rc) {
|
||||
case ResponseCode::SUCCESS:
|
||||
emit obj->responseReceived(actionId, true);
|
||||
qCDebug(dcCloud()) << "Successfully published" << actionId;
|
||||
qCDebug(dcAWS()) << "Successfully published" << actionId;
|
||||
break;
|
||||
default:
|
||||
qCDebug(dcCloud())<< "Error publishing data to AWS:" << QString::fromStdString(ResponseHelper::ToString(rc));
|
||||
emit obj->responseReceived(actionId, false);
|
||||
qCDebug(dcAWS())<< "Error publishing data to AWS:" << QString::fromStdString(ResponseHelper::ToString(rc));
|
||||
}
|
||||
}
|
||||
|
||||
void AWSConnector::subscribeCallback(uint16_t actionId, ResponseCode rc)
|
||||
{
|
||||
qCDebug(dcCloud()) << "subscribed to topic" << actionId << QString::fromStdString(ResponseHelper::ToString(rc));
|
||||
qCDebug(dcAWS()) << "subscribed to topic" << actionId << QString::fromStdString(ResponseHelper::ToString(rc));
|
||||
}
|
||||
|
||||
ResponseCode AWSConnector::onSubscriptionReceivedCallback(util::String topic_name, util::String payload, std::shared_ptr<SubscriptionHandlerContextData> p_app_handler_data)
|
||||
@ -147,20 +175,56 @@ ResponseCode AWSConnector::onSubscriptionReceivedCallback(util::String topic_nam
|
||||
QJsonParseError error;
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(QByteArray::fromStdString(payload), &error);
|
||||
if (error.error != QJsonParseError::NoError) {
|
||||
qCDebug(dcCloud()) << "Failed to parse JSON from AWS subscription on topic" << QString::fromStdString(topic_name) << ":" << error.errorString() << "\n" << QString::fromStdString(payload);
|
||||
qCDebug(dcAWS()) << "Failed to parse JSON from AWS subscription on topic" << QString::fromStdString(topic_name) << ":" << error.errorString() << "\n" << QString::fromStdString(payload);
|
||||
return ResponseCode::JSON_PARSING_ERROR;
|
||||
}
|
||||
|
||||
AWSConnector *connector = dynamic_cast<AWSConnector*>(p_app_handler_data.get());
|
||||
QString topic = QString::fromStdString(topic_name);
|
||||
topic.remove(QRegExp("^" + connector->m_clientId + "/"));
|
||||
emit connector->subscriptionReceived(topic, jsonDoc.toVariant().toMap());
|
||||
if (topic == QString("%1/pair/response").arg(connector->m_clientId)) {
|
||||
int statusCode = jsonDoc.toVariant().toMap().value("status").toInt();
|
||||
int id = jsonDoc.toVariant().toMap().value("id").toInt();
|
||||
QString cognitoUserId = connector->m_pairingRequests.take(id);
|
||||
if (!cognitoUserId.isEmpty()) {
|
||||
qCDebug(dcAWS()) << "Pairing response for id:" << cognitoUserId << statusCode;
|
||||
emit connector->devicePaired(cognitoUserId, statusCode);
|
||||
qCDebug(dcAWS()) << "subbbbing";
|
||||
connector->subscribe({QString("eu-west-1:%1/listeningPeer/#").arg(cognitoUserId)});
|
||||
} else {
|
||||
qCWarning(dcAWS()) << "Received a pairing response for a transaction we didn't start";
|
||||
}
|
||||
} else if (topic == QString("%1/pair/list/response").arg(connector->m_clientId)) {
|
||||
qCDebug(dcAWS) << "have device pairings:" << jsonDoc.toVariant().toMap().value("pairings").toList();
|
||||
QStringList topics;
|
||||
foreach (const QVariant &cognitoId, jsonDoc.toVariant().toMap().value("pairings").toList()) {
|
||||
topics << QString("eu-west-1:%1/listeningPeer/#").arg(cognitoId.toString());
|
||||
}
|
||||
connector->subscribe(topics);
|
||||
} else if (topic.contains("listeningPeer") && !topic.contains("reply")) {
|
||||
static QStringList dupes;
|
||||
QString id = jsonDoc.toVariant().toMap().value("id").toString();
|
||||
QString type = jsonDoc.toVariant().toMap().value("type").toString();
|
||||
if (dupes.contains(id+type)) {
|
||||
qCDebug(dcAWS()) << "Dropping duplicate packet";
|
||||
return ResponseCode::SUCCESS;
|
||||
}
|
||||
dupes.append(id+type);
|
||||
|
||||
// if (type != "offer") return ResponseCode::SUCCESS;
|
||||
|
||||
qCDebug(dcAWS) << "received webrtc handshake message" << topic << jsonDoc.toJson();
|
||||
connector->webRtcHandshakeMessageReceived(topic, jsonDoc.toVariant().toMap());
|
||||
} else if (topic.contains("listeningPeer") && topic.contains("reply")) {
|
||||
// silently drop our own things (should not be subscribed to that in the first place)
|
||||
} else {
|
||||
qCWarning(dcAWS) << "Unhandled subscription received!" << topic << QString::fromStdString(payload);
|
||||
}
|
||||
return ResponseCode::SUCCESS;
|
||||
}
|
||||
|
||||
ResponseCode AWSConnector::onDisconnected(util::String mqtt_client_id, std::shared_ptr<DisconnectCallbackContextData> p_app_handler_data)
|
||||
{
|
||||
Q_UNUSED(p_app_handler_data)
|
||||
qCDebug(dcCloud()) << "disconnected" << QString::fromStdString(mqtt_client_id);
|
||||
qCDebug(dcAWS()) << "disconnected" << QString::fromStdString(mqtt_client_id);
|
||||
return ResponseCode::SUCCESS;
|
||||
}
|
||||
|
||||
@ -19,20 +19,23 @@ public:
|
||||
void disconnectAWS();
|
||||
bool isConnected() const;
|
||||
|
||||
quint16 publish(const QString &topic, const QVariantMap &message);
|
||||
void pairDevice(const QString &idToken, const QString &authToken, const QString &cognitoUserId);
|
||||
|
||||
void subscribe(const QStringList &topics);
|
||||
void sendWebRtcHandshakeMessage(const QString &sessionId, const QVariantMap &map);
|
||||
|
||||
signals:
|
||||
void connected();
|
||||
void responseReceived(quint16 id, bool success);
|
||||
void subscriptionReceived(const QString &topic, const QVariantMap &data);
|
||||
void devicePaired(const QString &cognritoUserId, int errorCode);
|
||||
void webRtcHandshakeMessageReceived(const QString &transactionId, const QVariantMap &data);
|
||||
|
||||
private slots:
|
||||
void onConnected();
|
||||
void retrievePairedDeviceInfo();
|
||||
|
||||
private:
|
||||
void subscribeInternally(const QStringList &topcis);
|
||||
quint16 publish(const QString &topic, const QVariantMap &message);
|
||||
void subscribe(const QStringList &topics);
|
||||
void doSubscribe(const QStringList &topcis);
|
||||
static void publishCallback(uint16_t actionId, awsiotsdk::ResponseCode rc);
|
||||
static void subscribeCallback(uint16_t actionId, awsiotsdk::ResponseCode rc);
|
||||
static awsiotsdk::ResponseCode onSubscriptionReceivedCallback(awsiotsdk::util::String topic_name, awsiotsdk::util::String payload,
|
||||
@ -48,6 +51,9 @@ private:
|
||||
QFuture<void> m_connectingFuture;
|
||||
QStringList m_subscribedTopics;
|
||||
|
||||
int m_transactionId = 0;
|
||||
QHash<quint16, QString> m_pairingRequests;
|
||||
|
||||
static QHash<quint16, AWSConnector*> s_requestMap;
|
||||
};
|
||||
|
||||
|
||||
@ -7,7 +7,8 @@
|
||||
CloudManager::CloudManager(QObject *parent) : QObject(parent)
|
||||
{
|
||||
m_awsConnector = new AWSConnector(this);
|
||||
connect(m_awsConnector, &AWSConnector::subscriptionReceived, this, &CloudManager::subscriptionReceived);
|
||||
connect(m_awsConnector, &AWSConnector::devicePaired, this, &CloudManager::onPairingFinished);
|
||||
connect(m_awsConnector, &AWSConnector::webRtcHandshakeMessageReceived, this, &CloudManager::onAWSWebRtcHandshakeMessageReceived);
|
||||
|
||||
// Extract the machine id so we have a unique identifier for this machine
|
||||
// TODO: this only works for debian based systems, perhaps we should find something more general
|
||||
@ -16,11 +17,13 @@ CloudManager::CloudManager(QObject *parent) : QObject(parent)
|
||||
m_deviceId = QString::fromLatin1(f.readAll()).trimmed();
|
||||
qCDebug(dcCloud()) << "Device ID is:" << m_deviceId;
|
||||
setEnabled(true);
|
||||
m_awsConnector->subscribe({"pair/response"});
|
||||
} else {
|
||||
qWarning(dcCloud()) << "Failed to open /etc/machine-id for reading. Cloud connection will not work.";
|
||||
}
|
||||
|
||||
m_janusConnector = new JanusConnector(this);
|
||||
connect(m_janusConnector, &JanusConnector::webRtcHandshakeMessageReceived, this, &CloudManager::onJanusWebRtcHandshakeMessageReceived);
|
||||
|
||||
connect(GuhCore::instance()->networkManager(), &NetworkManager::stateChanged, this, &CloudManager::onlineStateChanged);
|
||||
}
|
||||
|
||||
@ -83,21 +86,16 @@ void CloudManager::setEnabled(bool enabled)
|
||||
}
|
||||
}
|
||||
|
||||
int CloudManager::pairDevice(const QString &idToken, const QString &authToken, const QString &cognitoId)
|
||||
void CloudManager::pairDevice(const QString &idToken, const QString &authToken, const QString &cognitoId)
|
||||
{
|
||||
QVariantMap map;
|
||||
map.insert("id", ++m_id);
|
||||
map.insert("idToken", idToken);
|
||||
map.insert("authToken", authToken);
|
||||
map.insert("cognitoId", cognitoId);
|
||||
m_awsConnector->publish("pair", map);
|
||||
return m_id;
|
||||
m_awsConnector->pairDevice(idToken, authToken, cognitoId);
|
||||
}
|
||||
|
||||
void CloudManager::connect2aws()
|
||||
{
|
||||
m_awsConnector->connect2AWS(GuhCore::instance()->configuration()->cloudServerUrl(),
|
||||
m_deviceId,
|
||||
"1e10fb7e-d9d9-4145-88dd-2d3caf623c18",
|
||||
// m_deviceId,
|
||||
GuhCore::instance()->configuration()->cloudCertificateCA(),
|
||||
GuhCore::instance()->configuration()->cloudCertificate(),
|
||||
GuhCore::instance()->configuration()->cloudCertificateKey()
|
||||
@ -114,10 +112,17 @@ void CloudManager::onlineStateChanged()
|
||||
}
|
||||
}
|
||||
|
||||
void CloudManager::subscriptionReceived(const QString &topic, const QVariantMap &message)
|
||||
void CloudManager::onPairingFinished(const QString &cognitoUserId, int errorCode)
|
||||
{
|
||||
qCDebug(dcCloud()) << "subscription reveiv ed" << topic << message;
|
||||
if (topic == "pair/response") {
|
||||
emit pairingReply(message.value("id").toInt(), message.value("status").toInt());
|
||||
}
|
||||
emit pairingReply(cognitoUserId, errorCode);
|
||||
}
|
||||
|
||||
void CloudManager::onAWSWebRtcHandshakeMessageReceived(const QString &transactionId, const QVariantMap &data)
|
||||
{
|
||||
m_janusConnector->sendWebRtcHandshakeMessage(transactionId, data);
|
||||
}
|
||||
|
||||
void CloudManager::onJanusWebRtcHandshakeMessageReceived(const QString &transactionId, const QVariantMap &data)
|
||||
{
|
||||
m_awsConnector->sendWebRtcHandshakeMessage(transactionId, data);
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
#define CLOUDMANAGER_H
|
||||
|
||||
#include "awsconnector.h"
|
||||
#include "janusconnector.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QTimer>
|
||||
@ -20,24 +21,26 @@ public:
|
||||
bool enabled() const;
|
||||
void setEnabled(bool enabled);
|
||||
|
||||
int pairDevice(const QString &idToken, const QString &authToken, const QString &cognitoId);
|
||||
void pairDevice(const QString &idToken, const QString &authToken, const QString &cognitoId);
|
||||
|
||||
signals:
|
||||
void pairingReply(int pairingTransactionId, int status);
|
||||
void pairingReply(QString cognitoUserId, int status);
|
||||
|
||||
private:
|
||||
void connect2aws();
|
||||
|
||||
private slots:
|
||||
void onlineStateChanged();
|
||||
void subscriptionReceived(const QString &topic, const QVariantMap &message);
|
||||
void onPairingFinished(const QString &cognitoUserId, int errorCode);
|
||||
void onAWSWebRtcHandshakeMessageReceived(const QString &transactionId, const QVariantMap &data);
|
||||
void onJanusWebRtcHandshakeMessageReceived(const QString &transactionId, const QVariantMap &data);
|
||||
|
||||
private:
|
||||
QNetworkSession *m_networkSession;
|
||||
QTimer m_reconnectTimer;
|
||||
bool m_enabled = false;
|
||||
AWSConnector *m_awsConnector = nullptr;
|
||||
int m_id = 0; // id for transactions. e.g. pairDevice
|
||||
JanusConnector *m_janusConnector = nullptr;
|
||||
|
||||
QString m_serverUrl;
|
||||
QString m_deviceId;
|
||||
|
||||
@ -88,6 +88,7 @@ public:
|
||||
|
||||
QString sslCertificate() const;
|
||||
QString sslCertificateKey() const;
|
||||
void setSslCertificate(const QString &certificate, const QString &certificateKey);
|
||||
|
||||
// TCP server
|
||||
QHash<QString, ServerConfiguration> tcpServerConfigurations() const;
|
||||
|
||||
362
libguh-core/janusconnector.cpp
Normal file
362
libguh-core/janusconnector.cpp
Normal file
@ -0,0 +1,362 @@
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
|
||||
#include "janusconnector.h"
|
||||
#include "loggingcategories.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QUuid>
|
||||
|
||||
JanusConnector::JanusConnector(QObject *parent) : QObject(parent)
|
||||
{
|
||||
m_socket = new QLocalSocket(this);
|
||||
typedef void (QLocalSocket:: *errorSignal)(QLocalSocket::LocalSocketError);
|
||||
connect(m_socket, static_cast<errorSignal>(&QLocalSocket::error), this, &JanusConnector::onError);
|
||||
connect(m_socket, &QLocalSocket::disconnected, this, &JanusConnector::onDisconnected);
|
||||
connect(m_socket, &QLocalSocket::readyRead, this, &JanusConnector::onReadyRead);
|
||||
connect(m_socket, &QLocalSocket::stateChanged, [&](QLocalSocket::LocalSocketState state) {
|
||||
qCWarning(dcJanus()) << "state changed" << state;
|
||||
});
|
||||
connect(m_socket, &QLocalSocket::readChannelFinished, [&]() {
|
||||
qCWarning(dcJanus()) << "readchannel finished";
|
||||
});
|
||||
connect(&m_socketTimeoutTimer, &QTimer::timeout, this, &JanusConnector::heartbeat);
|
||||
m_socketTimeoutTimer.setInterval(5000);
|
||||
}
|
||||
|
||||
bool JanusConnector::connectToJanus()
|
||||
{
|
||||
|
||||
int sock = socket (PF_UNIX, SOCK_SEQPACKET, 0);
|
||||
if (sock < 0) {
|
||||
qCWarning(dcJanus) << "Failed to create socket";
|
||||
return false;
|
||||
}
|
||||
|
||||
struct sockaddr_un name;
|
||||
name.sun_family = AF_UNIX;
|
||||
strncpy (name.sun_path, "/tmp/janusapi", 13);
|
||||
name.sun_path[13] = '\0';
|
||||
int ret = ::connect(sock, (const sockaddr*)&name, sizeof(name));
|
||||
if (ret < 0) {
|
||||
qCWarning(dcJanus()) << "Error connecting to socket";
|
||||
return false;
|
||||
}
|
||||
|
||||
m_socket->setSocketDescriptor(sock);
|
||||
|
||||
m_socketTimeoutTimer.start();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void JanusConnector::createSession(JanusConnector::WebRtcSession *session)
|
||||
{
|
||||
// Open the session
|
||||
qCDebug(dcJanus()) << "Creating new janus session:" << session;
|
||||
QString transactionId = QUuid::createUuid().toString();
|
||||
QVariantMap map;
|
||||
map.insert("transaction", transactionId);
|
||||
map.insert("janus", "create");
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromVariant(map);
|
||||
m_pendingRequests.insert(transactionId, session);
|
||||
writeToJanus(jsonDoc.toJson());
|
||||
}
|
||||
|
||||
void JanusConnector::sendWebRtcHandshakeMessage(const QString &sessionId, const QVariantMap &message)
|
||||
{
|
||||
if (!m_socket->isOpen()) {
|
||||
if (!connectToJanus()) {
|
||||
qCWarning(dcJanus()) << "Failed to establish a connection to Janus. Cannot send WebRtcHandshake.";
|
||||
return;
|
||||
}
|
||||
}
|
||||
QString messageType = message.value("type").toString();
|
||||
QString transactionId = message.value("id").toString();
|
||||
|
||||
// Do we have a session for this transaction?
|
||||
if (!m_sessions.contains(sessionId)) {
|
||||
|
||||
// create a new session and queue the actual offer call
|
||||
WebRtcSession *session = new WebRtcSession();
|
||||
session->sessionId = sessionId;
|
||||
if (messageType == "offer") {
|
||||
session->offer = message;
|
||||
session->offerSent = false;
|
||||
} else if (messageType == "trickle") {
|
||||
session->trickles.append(message);
|
||||
} else {
|
||||
qCWarning(dcJanus()) << "Unhandled webrtc handshake message type" << messageType << message;
|
||||
}
|
||||
m_sessions.insert(sessionId, session);
|
||||
|
||||
if (messageType == "offer") {
|
||||
createSession(session);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
WebRtcSession *session = m_sessions.value(sessionId);
|
||||
if (messageType == "offer") {
|
||||
session->offer = message;
|
||||
session->offerSent = false;
|
||||
createSession(session);
|
||||
} else if (messageType == "trickle") {
|
||||
m_sessions.value(sessionId)->trickles.append(message);
|
||||
} else if (messageType == "webrtcup") {
|
||||
// If we got the webrtc up from Janus already, directly reply with an ack
|
||||
if (session->webRtcConnected) {
|
||||
QVariantMap ack;
|
||||
ack.insert("id", message.value("id").toString());
|
||||
ack.insert("type", "ack");
|
||||
// emit webRtcHandshakeMessageReceived(session->sessionId, ack);
|
||||
} else {
|
||||
// otherwise store the request and reply when we get the webrtcup
|
||||
session->webRtcUp = message;
|
||||
}
|
||||
} else {
|
||||
qCWarning(dcJanus()) << "Unhandled message type:" << messageType << message;
|
||||
}
|
||||
|
||||
processQueue();
|
||||
}
|
||||
|
||||
void JanusConnector::processQueue()
|
||||
{
|
||||
if (!m_socket->isOpen()) {
|
||||
qCWarning(dcJanus()) << "janus socket not open. Cannot process queue";
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (WebRtcSession* session, m_sessions) {
|
||||
if (session->connectedToJanus) {
|
||||
if (!session->offerSent) {
|
||||
QVariantMap janusMessage;
|
||||
janusMessage.insert("janus", "message");
|
||||
janusMessage.insert("transaction", session->offer.value("id").toString());
|
||||
janusMessage.insert("session_id", session->janusSessionId);
|
||||
janusMessage.insert("handle_id", session->janusChannelId);
|
||||
QVariantMap body;
|
||||
body.insert("request", "setup");
|
||||
janusMessage.insert("body", body);
|
||||
janusMessage.insert("jsep", session->offer.value("jsep"));
|
||||
|
||||
m_pendingRequests.insert(session->offer.value("id").toString(), session);
|
||||
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromVariant(janusMessage);
|
||||
qCDebug(dcJanus()) << "Sending offer message to session" << session << jsonDoc.toJson();
|
||||
writeToJanus(jsonDoc.toJson());
|
||||
session->offerSent = true;
|
||||
return;
|
||||
}
|
||||
while (!session->trickles.isEmpty()) {
|
||||
QVariantMap input = session->trickles.takeFirst().toMap();
|
||||
QVariantMap janusMessage;
|
||||
janusMessage.insert("janus", "trickle");
|
||||
janusMessage.insert("transaction", input.value("id").toString());
|
||||
janusMessage.insert("session_id", session->janusSessionId);
|
||||
janusMessage.insert("handle_id", session->janusChannelId);
|
||||
janusMessage.insert("candidate", input.value("candidate"));
|
||||
|
||||
m_pendingRequests.insert(input.value("id").toString(), session);
|
||||
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromVariant(janusMessage);
|
||||
qCDebug(dcJanus()) << "sending trickle message" << jsonDoc.toJson();
|
||||
m_socket->write(jsonDoc.toJson());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void JanusConnector::onDisconnected()
|
||||
{
|
||||
qCDebug(dcJanus) << "disconnected from Janus";
|
||||
}
|
||||
|
||||
void JanusConnector::onError(QLocalSocket::LocalSocketError socketError)
|
||||
{
|
||||
qCDebug(dcJanus) << "error in janus connection" << socketError << m_socket->errorString();
|
||||
}
|
||||
|
||||
void JanusConnector::onTextMessageReceived(const QString &message)
|
||||
{
|
||||
qCDebug(dcJanus) << "text message received from Janus" << message;
|
||||
}
|
||||
|
||||
void JanusConnector::onReadyRead()
|
||||
{
|
||||
QByteArray data = m_socket->readAll();
|
||||
qCDebug(dcJanus()) << "incoming data" << data;
|
||||
QJsonParseError error;
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
|
||||
if (error.error != QJsonParseError::NoError) {
|
||||
qWarning(dcJanus()) << "Cannot parse packet received by Janus:" << error.error << error.errorString();
|
||||
return;
|
||||
}
|
||||
|
||||
QVariantMap map = jsonDoc.toVariant().toMap();
|
||||
if (map.value("janus").toString() == "error") {
|
||||
qCWarning(dcJanus()) << "An error happened in the janus connection:" << map.value("error").toMap().value("reason").toString();
|
||||
return;
|
||||
}
|
||||
|
||||
if (map.value("janus").toString() == "timeout") {
|
||||
quint64 sessionId = map.value("session_id").toLongLong();
|
||||
foreach (WebRtcSession *session, m_sessions) {
|
||||
if (session->matchJanusSessionId(sessionId)) {
|
||||
qCDebug(dcJanus()) << "Session" << session << "timed out. Removing session";
|
||||
m_sessions.remove(session->sessionId);
|
||||
delete session;
|
||||
return;
|
||||
}
|
||||
}
|
||||
qCWarning(dcJanus()) << "Received a timeout event but don't have a session for it";
|
||||
return;
|
||||
}
|
||||
|
||||
if (map.value("janus").toString() == "webrtcup") {
|
||||
quint64 sessionId = map.value("session_id").toLongLong();
|
||||
foreach (WebRtcSession *session, m_sessions) {
|
||||
if (session->matchJanusSessionId(sessionId)) {
|
||||
qCDebug(dcJanus()) << "Session" << session << "is up now!";
|
||||
session->webRtcConnected = true;
|
||||
if (!session->webRtcUp.isEmpty()) {
|
||||
QVariantMap ack;
|
||||
ack.insert("id", session->webRtcUp.value("id").toString());
|
||||
ack.insert("type", "ack");
|
||||
emit webRtcHandshakeMessageReceived(session->sessionId, ack);
|
||||
}
|
||||
delete session;
|
||||
return;
|
||||
}
|
||||
}
|
||||
qCWarning(dcJanus()) << "Received a webrtcup event but don't have a session for it";
|
||||
return;
|
||||
}
|
||||
|
||||
// as of now, everything must be part of a transaction
|
||||
if (!map.contains("transaction")) {
|
||||
qCWarning(dcJanus) << "Unhandled message from Janus (missing transaction):" << data;
|
||||
return;
|
||||
}
|
||||
|
||||
QString transactionId = map.value("transaction").toString();
|
||||
WebRtcSession *session = m_pendingRequests.value(transactionId);
|
||||
if (!session) {
|
||||
if (transactionId == "pingety") {
|
||||
qCDebug(dcJanus()) << "Received PONG from Janus";
|
||||
return;
|
||||
}
|
||||
qCWarning(dcJanus()) << "received a janus message for a session we don't know...";
|
||||
return;
|
||||
}
|
||||
|
||||
if (session->janusSessionId == -1) {
|
||||
// This should be a create session response
|
||||
if (map.value("janus").toString() == "success") {
|
||||
session->janusSessionId = map.value("data").toMap().value("id").toLongLong();
|
||||
// oooohhhkaaay... now, this is gonna be dirty... So, Janus' session id is like a freakin huge number
|
||||
// so that QVariant stores it in a double instead of a longlong, which could cause rounding errors when converting it
|
||||
// back to to a long. Let's grep the raw data for the parsed session id and if not found, try to correct it one down.
|
||||
if (!data.contains(QByteArray::number(session->janusSessionId)) && data.contains(QByteArray::number(session->janusSessionId-1))) {
|
||||
session->janusSessionId--;
|
||||
qCDebug(dcJanus()) << "corrected session id after rounding error";
|
||||
}
|
||||
qCDebug(dcJanus()) << "Session" << session << "established";// << data << map;
|
||||
|
||||
createChannel(session);
|
||||
return;
|
||||
}
|
||||
qCWarning(dcJanus()) << "Error establishing session";
|
||||
delete m_sessions.take(session->sessionId);
|
||||
return;
|
||||
}
|
||||
|
||||
qint64 janusSessionId = map.value("session_id").toLongLong();
|
||||
if (!session->matchJanusSessionId(janusSessionId)) {
|
||||
qCWarning(dcJanus) << "Transaction id and session id not matching!" << session->janusSessionId << "!=" << map.value("session_id").toLongLong();
|
||||
return;
|
||||
}
|
||||
|
||||
if (session->janusChannelId == -1) {
|
||||
if (map.value("janus").toString() == "success") {
|
||||
session->janusChannelId = map.value("data").toMap().value("id").toLongLong();
|
||||
if (!data.contains(QByteArray::number(session->janusChannelId)) && data.contains(QByteArray::number(session->janusChannelId-1))) {
|
||||
session->janusChannelId--;
|
||||
qCDebug(dcJanus()) << "Corrected channel id after rounding error";
|
||||
}
|
||||
qCDebug(dcJanus()) << "Channel for session" << session << "established";// << data << map;
|
||||
session->connectedToJanus = true;
|
||||
processQueue();
|
||||
return;
|
||||
}
|
||||
qCWarning(dcJanus()) << "Error establishing channel" << session << data;
|
||||
return;
|
||||
}
|
||||
|
||||
if (map.value("janus").toString() == "event" && map.value("jsep").toMap().value("type").toString() == "answer") {
|
||||
qCDebug(dcJanus()) << "Emitting handshake event" << data;
|
||||
QVariantMap reply;
|
||||
reply.insert("id", transactionId);
|
||||
reply.insert("type", "answer");
|
||||
reply.insert("jsep", map.value("jsep"));
|
||||
emit webRtcHandshakeMessageReceived(session->sessionId, reply);
|
||||
return;
|
||||
}
|
||||
|
||||
if (map.value("janus").toString() == "ack") {
|
||||
QVariantMap ackReply;
|
||||
ackReply.insert("id", transactionId);
|
||||
ackReply.insert("type", "ack");
|
||||
emit webRtcHandshakeMessageReceived(session->sessionId, ackReply);
|
||||
return;
|
||||
}
|
||||
|
||||
qCWarning(dcJanus()) << "Unhandled Janus message:" << data;
|
||||
}
|
||||
|
||||
void JanusConnector::heartbeat()
|
||||
{
|
||||
QVariantMap map;
|
||||
map.insert("janus", "ping");
|
||||
map.insert("transaction", "pingety");
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromVariant(map);
|
||||
qCDebug(dcJanus()) << "Sending PING to Janus";
|
||||
m_socket->write(jsonDoc.toJson());
|
||||
m_socket->flush();
|
||||
}
|
||||
|
||||
void JanusConnector::createChannel(WebRtcSession *session)
|
||||
{
|
||||
QVariantMap attachPluginMessage;
|
||||
attachPluginMessage.insert("janus", "attach");
|
||||
attachPluginMessage.insert("session_id", session->janusSessionId);
|
||||
QString transactionId = QUuid::createUuid().toString();
|
||||
m_pendingRequests.insert(transactionId, session);
|
||||
attachPluginMessage.insert("transaction", transactionId);
|
||||
attachPluginMessage.insert("plugin", "janus.plugin.guhio");
|
||||
attachPluginMessage.insert("opaque_id", "guhio-" + QUuid::createUuid().toString());
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromVariant(attachPluginMessage);
|
||||
qCDebug(dcJanus()) << "Establishing channel for session" << session->sessionId;
|
||||
writeToJanus(jsonDoc.toJson());
|
||||
}
|
||||
|
||||
void JanusConnector::writeToJanus(const QByteArray &data)
|
||||
{
|
||||
qCDebug(dcJanus()) << "Writing to janus" << data;
|
||||
m_socket->write(data);
|
||||
m_socket->flush();
|
||||
}
|
||||
|
||||
QDebug operator<<(QDebug debug, const JanusConnector::WebRtcSession &session)
|
||||
{
|
||||
debug.nospace() << session.sessionId << " (Janus session: " << session.janusSessionId << " channel: " << session.janusChannelId << " connected to Janus: " << session.connectedToJanus << " WebRTC connected: " << session.webRtcConnected << ") ";
|
||||
return debug;
|
||||
}
|
||||
|
||||
QDebug operator<<(QDebug debug, JanusConnector::WebRtcSession *session)
|
||||
{
|
||||
debug.nospace() << *session;
|
||||
return debug;
|
||||
}
|
||||
69
libguh-core/janusconnector.h
Normal file
69
libguh-core/janusconnector.h
Normal file
@ -0,0 +1,69 @@
|
||||
#ifndef JANUSCONNECTOR_H
|
||||
#define JANUSCONNECTOR_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QLocalSocket>
|
||||
#include <QTimer>
|
||||
|
||||
class JanusConnector : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
class WebRtcSession {
|
||||
public:
|
||||
QString sessionId; // This should be unique but persistent for each remote device
|
||||
qint64 janusSessionId = -1; // the session id for the janus session.
|
||||
qint64 janusChannelId = -1;
|
||||
bool connectedToJanus = false;
|
||||
|
||||
QVariantMap offer;
|
||||
bool offerSent = false;
|
||||
QVariantList trickles;
|
||||
QVariantMap webRtcUp;
|
||||
bool webRtcConnected = false;
|
||||
|
||||
bool matchJanusSessionId(qint64 id) {
|
||||
return (janusSessionId == id) || (janusSessionId == id +1) || (janusSessionId == id -1);
|
||||
}
|
||||
};
|
||||
|
||||
explicit JanusConnector(QObject *parent = nullptr);
|
||||
|
||||
|
||||
void sendWebRtcHandshakeMessage(const QString &sessionId, const QVariantMap &message);
|
||||
|
||||
signals:
|
||||
void connected();
|
||||
void webRtcHandshakeMessageReceived(const QString &sessionId, const QVariantMap &message);
|
||||
|
||||
private slots:
|
||||
void onDisconnected();
|
||||
void onError(QLocalSocket::LocalSocketError socketError);
|
||||
void onTextMessageReceived(const QString &message);
|
||||
void onReadyRead();
|
||||
void heartbeat();
|
||||
void processQueue();
|
||||
|
||||
private:
|
||||
QHash<QString, WebRtcSession*> m_pendingRequests;
|
||||
|
||||
bool connectToJanus();
|
||||
void createSession(WebRtcSession *session);
|
||||
void createChannel(WebRtcSession *session);
|
||||
void writeToJanus(const QByteArray &data);
|
||||
|
||||
|
||||
private:
|
||||
|
||||
QLocalSocket *m_socket = nullptr;
|
||||
QTimer m_socketTimeoutTimer;
|
||||
|
||||
QHash<QString, WebRtcSession*> m_sessions;
|
||||
|
||||
QStringList m_wantedAcks;
|
||||
};
|
||||
|
||||
QDebug operator<<(QDebug debug, const JanusConnector::WebRtcSession &session);
|
||||
QDebug operator<<(QDebug debug, JanusConnector::WebRtcSession *session);
|
||||
|
||||
#endif // JANUSCONNECTOR_H
|
||||
@ -263,13 +263,14 @@ JsonReply *JsonRPCServer::RemoveToken(const QVariantMap ¶ms)
|
||||
|
||||
JsonReply *JsonRPCServer::SetupRemoteAccess(const QVariantMap ¶ms)
|
||||
{
|
||||
|
||||
int id = GuhCore::instance()->cloudManager()->pairDevice(params.value("idToken").toString(), params.value("authToken").toString(), params.value("cognitoId").toString());
|
||||
qWarning() << "waiting for id" << id;
|
||||
QString idToken = params.value("idToken").toString();
|
||||
QString authToken = params.value("authToken").toString();
|
||||
QString cognitoUserId = params.value("cognitoId").toString();
|
||||
GuhCore::instance()->cloudManager()->pairDevice(idToken, authToken, cognitoUserId);
|
||||
JsonReply *reply = createAsyncReply("SetupRemoteAccess");
|
||||
m_pairingRequests.insert(id, reply);
|
||||
connect(reply, &JsonReply::finished, [this, id](){
|
||||
m_pairingRequests.remove(id);
|
||||
m_pairingRequests.insert(cognitoUserId, reply);
|
||||
connect(reply, &JsonReply::finished, [this, cognitoUserId](){
|
||||
m_pairingRequests.remove(cognitoUserId);
|
||||
});
|
||||
return reply;
|
||||
}
|
||||
@ -496,14 +497,13 @@ void JsonRPCServer::asyncReplyFinished()
|
||||
reply->deleteLater();
|
||||
}
|
||||
|
||||
void JsonRPCServer::pairingFinished(int pairingTransactionId, int status)
|
||||
void JsonRPCServer::pairingFinished(QString cognitoUserId, int status)
|
||||
{
|
||||
qWarning() << "pairing finished" << pairingTransactionId << status;
|
||||
JsonReply *reply = m_pairingRequests.take(pairingTransactionId);
|
||||
qWarning() << "pairing finished" << cognitoUserId << status;
|
||||
JsonReply *reply = m_pairingRequests.take(cognitoUserId);
|
||||
if (!reply) {
|
||||
return;
|
||||
}
|
||||
qWarning() << "blubbbbb";
|
||||
QVariantMap returns;
|
||||
returns.insert("status", status);
|
||||
reply->setData(returns);
|
||||
|
||||
@ -81,7 +81,7 @@ private slots:
|
||||
|
||||
void asyncReplyFinished();
|
||||
|
||||
void pairingFinished(int pairingTransactionId, int status);
|
||||
void pairingFinished(QString cognitoUserId, int status);
|
||||
|
||||
private:
|
||||
QMap<TransportInterface*, bool> m_interfaces;
|
||||
@ -91,7 +91,7 @@ private:
|
||||
// clientId, notificationsEnabled
|
||||
QHash<QUuid, bool> m_clients;
|
||||
|
||||
QHash<int, JsonReply*> m_pairingRequests;
|
||||
QHash<QString, JsonReply*> m_pairingRequests;
|
||||
|
||||
int m_notificationId;
|
||||
|
||||
|
||||
@ -70,7 +70,7 @@ HEADERS += $$top_srcdir/libguh-core/guhcore.h \
|
||||
$$top_srcdir/libguh-core/awsconnector.h \
|
||||
$$top_srcdir/libguh-core/cloudconnector.h \
|
||||
$$top_srcdir/libguh-core/MbedTLS/MbedTLSConnection.hpp \
|
||||
|
||||
$$top_srcdir/libguh-core/janusconnector.h \
|
||||
|
||||
SOURCES += $$top_srcdir/libguh-core/guhcore.cpp \
|
||||
$$top_srcdir/libguh-core/tcpserver.cpp \
|
||||
@ -127,3 +127,4 @@ SOURCES += $$top_srcdir/libguh-core/guhcore.cpp \
|
||||
$$top_srcdir/libguh-core/awsconnector.cpp \
|
||||
$$top_srcdir/libguh-core/cloudconnector.cpp \
|
||||
$$top_srcdir/libguh-core/MbedTLS/MbedTLSConnection.cpp \
|
||||
$$top_srcdir/libguh-core/janusconnector.cpp \
|
||||
|
||||
@ -91,6 +91,7 @@ void WebSocketServer::sendData(const QUuid &clientId, const QByteArray &data)
|
||||
QWebSocket *client = 0;
|
||||
client = m_clientList.value(clientId);
|
||||
if (client) {
|
||||
qCDebug(dcWebSocketServer()) << "Sending data to client" << data;
|
||||
client->sendTextMessage(data + '\n');
|
||||
}
|
||||
}
|
||||
|
||||
@ -39,3 +39,5 @@ Q_LOGGING_CATEGORY(dcAvahi, "Avahi")
|
||||
Q_LOGGING_CATEGORY(dcCloud, "Cloud")
|
||||
Q_LOGGING_CATEGORY(dcNetworkManager, "NetworkManager")
|
||||
Q_LOGGING_CATEGORY(dcUserManager, "UserManager")
|
||||
Q_LOGGING_CATEGORY(dcAWS, "AWS")
|
||||
Q_LOGGING_CATEGORY(dcJanus, "Janus")
|
||||
|
||||
@ -47,5 +47,7 @@ Q_DECLARE_LOGGING_CATEGORY(dcAvahi)
|
||||
Q_DECLARE_LOGGING_CATEGORY(dcCloud)
|
||||
Q_DECLARE_LOGGING_CATEGORY(dcNetworkManager)
|
||||
Q_DECLARE_LOGGING_CATEGORY(dcUserManager)
|
||||
Q_DECLARE_LOGGING_CATEGORY(dcAWS)
|
||||
Q_DECLARE_LOGGING_CATEGORY(dcJanus)
|
||||
|
||||
#endif // LOGGINGCATEGORYS_H
|
||||
|
||||
@ -127,6 +127,8 @@ int main(int argc, char *argv[])
|
||||
s_loggingFilters.insert("Cloud", true);
|
||||
s_loggingFilters.insert("NetworkManager", true);
|
||||
s_loggingFilters.insert("UserManager", true);
|
||||
s_loggingFilters.insert("AWS", false);
|
||||
s_loggingFilters.insert("Janus", false);
|
||||
|
||||
QHash<QString, bool> loggingFiltersPlugins;
|
||||
foreach (const QJsonObject &pluginMetadata, DeviceManager::pluginsMetadata()) {
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
<context>
|
||||
<name>main</name>
|
||||
<message>
|
||||
<location filename="../server/main.cpp" line="150"/>
|
||||
<location filename="../server/main.cpp" line="152"/>
|
||||
<source>
|
||||
guh ( /[guːh]/ ) is an open source IoT (Internet of Things) server,
|
||||
which allows to control a lot of different devices from many different
|
||||
@ -23,12 +23,12 @@ Szenen undVerhaltensweisen des Systems festzulegen.
|
||||
</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../server/main.cpp" line="162"/>
|
||||
<location filename="../server/main.cpp" line="164"/>
|
||||
<source>Run guhd in the foreground, not as daemon.</source>
|
||||
<translation>Starte guhd im Vordergrund, nicht als Service.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../server/main.cpp" line="165"/>
|
||||
<location filename="../server/main.cpp" line="167"/>
|
||||
<source>Debug categories to enable. Prefix with "No" to disable. Warnings from all categories will be printed unless explicitly muted with "NoWarnings".
|
||||
|
||||
Categories are:</source>
|
||||
@ -36,17 +36,17 @@ Categories are:</source>
|
||||
Es gibt folgende Kategorien:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../server/main.cpp" line="182"/>
|
||||
<location filename="../server/main.cpp" line="184"/>
|
||||
<source>Enables all debug categories. This parameter overrides all debug category parameters.</source>
|
||||
<translation>Aktiviere alle Debug-Kategorien. Dieser Parameter überschreibt alle anderen Debug-Kategorien Parameter.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../server/main.cpp" line="187"/>
|
||||
<location filename="../server/main.cpp" line="189"/>
|
||||
<source>Specify a log file to write to, If this option is not specified, logs will be printed to the standard output.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../server/main.cpp" line="219"/>
|
||||
<location filename="../server/main.cpp" line="221"/>
|
||||
<source>No such debug category:</source>
|
||||
<translation>Diese Debug-Kategorie existiert nicht:</translation>
|
||||
</message>
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
<context>
|
||||
<name>main</name>
|
||||
<message>
|
||||
<location filename="../server/main.cpp" line="150"/>
|
||||
<location filename="../server/main.cpp" line="152"/>
|
||||
<source>
|
||||
guh ( /[guːh]/ ) is an open source IoT (Internet of Things) server,
|
||||
which allows to control a lot of different devices from many different
|
||||
@ -23,12 +23,12 @@ for your environment.
|
||||
</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../server/main.cpp" line="162"/>
|
||||
<location filename="../server/main.cpp" line="164"/>
|
||||
<source>Run guhd in the foreground, not as daemon.</source>
|
||||
<translation>Run guhd in the foreground, not as daemon.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../server/main.cpp" line="165"/>
|
||||
<location filename="../server/main.cpp" line="167"/>
|
||||
<source>Debug categories to enable. Prefix with "No" to disable. Warnings from all categories will be printed unless explicitly muted with "NoWarnings".
|
||||
|
||||
Categories are:</source>
|
||||
@ -37,17 +37,17 @@ Categories are:</source>
|
||||
Categories are:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../server/main.cpp" line="182"/>
|
||||
<location filename="../server/main.cpp" line="184"/>
|
||||
<source>Enables all debug categories. This parameter overrides all debug category parameters.</source>
|
||||
<translation>Enables all debug categories. This parameter overrides all debug category parameters.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../server/main.cpp" line="187"/>
|
||||
<location filename="../server/main.cpp" line="189"/>
|
||||
<source>Specify a log file to write to, If this option is not specified, logs will be printed to the standard output.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../server/main.cpp" line="219"/>
|
||||
<location filename="../server/main.cpp" line="221"/>
|
||||
<source>No such debug category:</source>
|
||||
<translation>No such debug category:</translation>
|
||||
</message>
|
||||
|
||||
Reference in New Issue
Block a user