Move AppLogController into libnymea-app

This commit is contained in:
Michael Zanetti 2021-03-13 14:31:43 +01:00
parent f71c1fc219
commit f36b36f8da
37 changed files with 989 additions and 455 deletions

View File

@ -0,0 +1,380 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2020, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of nymea.
* This project including source code and documentation is protected by
* copyright law, and remains the property of nymea GmbH. All rights, including
* reproduction, publication, editing and translation, are reserved. The use of
* this project is subject to the terms of a license agreement to be concluded
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
* under https://nymea.io/license
*
* GNU General Public License Usage
* Alternatively, this project may be redistributed and/or modified under the
* terms of the GNU General Public License as published by the Free Software
* Foundation, GNU version 3. This project 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 project. If not, see <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "applogcontroller.h"
#include <QStandardPaths>
#include <QDebug>
#include <QSettings>
#include <QGuiApplication>
#include <QDir>
#include <QMutexLocker>
#include "logging.h"
QtMessageHandler AppLogController::s_oldLogMessageHandler = nullptr;
AppLogController::LogLevel AppLogController::qtMsgTypeToLogLevel(QtMsgType msgType)
{
switch (msgType) {
case QtDebugMsg:
return LogLevelDebug;
case QtInfoMsg:
return LogLevelInfo;
case QtWarningMsg:
return LogLevelWarning;
default:
return LogLevelCritical;
}
}
QtMsgType AppLogController::logLevelToQtMsgType(AppLogController::LogLevel logLevel)
{
switch (logLevel) {
case LogLevelDebug:
return QtDebugMsg;
case LogLevelInfo:
return QtInfoMsg;
case LogLevelWarning:
return QtWarningMsg;
default:
return QtCriticalMsg;
}
}
QObject *AppLogController::appLogControllerProvider(QQmlEngine *engine, QJSEngine *scriptEngine)
{
Q_UNUSED(engine)
Q_UNUSED(scriptEngine)
return instance();
}
AppLogController *AppLogController::instance()
{
static AppLogController* thiz = nullptr;
if (!thiz) {
thiz = new AppLogController();
}
return thiz;
}
AppLogController::AppLogController(QObject *parent) : QObject(parent)
{
m_loggingCategories = new LoggingCategories(this);
QSettings settings;
settings.beginGroup("LoggingLevels");
foreach (const QString &category, nymeaLoggingCategories()) {
m_logLevels[category] = static_cast<LogLevel>(settings.value(category, LogLevelInfo).toInt());
}
settings.endGroup();
updateFilters();
// Finally, install the logMessageHandler
s_oldLogMessageHandler = qInstallMessageHandler(&logMessageHandler);
if (enabled()) {
openLogFile();
}
}
bool AppLogController::enabled() const
{
QSettings settings;
return settings.value("AppLoggingEnabled", false).toBool();
}
void AppLogController::setEnabled(bool enabled)
{
if (enabled == this->enabled()) {
return;
}
if (enabled) {
openLogFile();
} else {
m_logFile.close();
}
QSettings settings;
settings.setValue("AppLoggingEnabled", enabled);
emit enabledChanged();
}
LoggingCategories *AppLogController::loggingCategories() const
{
return m_loggingCategories;
}
AppLogController::LogLevel AppLogController::logLevel(const QString &category) const
{
return m_logLevels.value(category);
}
void AppLogController::setLogLevel(const QString &category, AppLogController::LogLevel logLevel)
{
m_logLevels[category] = logLevel;
QSettings settings;
settings.beginGroup("LoggingLevels");
settings.setValue(category, logLevel);
settings.endGroup();
emit categoryChanged(category, logLevel);
}
QString AppLogController::logPath() const
{
return QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/logs/";
}
QString AppLogController::currentLogFile() const
{
return logPath() + "/" + QGuiApplication::applicationName() + ".log";
}
QStringList AppLogController::logFiles() const
{
QDir dir(logPath());
QStringList files;
foreach (const QString &file, dir.entryList({QGuiApplication::applicationName() + ".log*"})) {
files.append(logPath() + "/" + file);
}
return files;
}
QString AppLogController::exportLogs()
{
QFile f(logPath() + "/" + QGuiApplication::applicationName() + "-logs.txt");
if (!f.open(QFile::WriteOnly)) {
return QString();
}
foreach (const QString &logFile, logFiles()) {
QFile l(logFile);
if (!l.open(QFile::ReadOnly)) {
continue;
}
f.write("\n******** App start ********\n");
f.write(logFile.toUtf8() + "\n");
f.write(l.readAll());
}
f.close();
return f.fileName();
}
void AppLogController::logMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message)
{
s_oldLogMessageHandler(type, context, message);
QMetaObject::invokeMethod(instance(), "append", Q_ARG(QString, context.category), Q_ARG(QString, message), Q_ARG(AppLogController::LogLevel, qtMsgTypeToLogLevel(type)));
}
void AppLogController::append(const QString &category, const QString &message, LogLevel level)
{
if (m_logLevels.value(category) < level) {
return;
}
if (m_logFile.isOpen()) {
QHash<LogLevel, QString> t = {
{LogLevelDebug, "D"},
{LogLevelInfo, "I"},
{LogLevelWarning, "W"},
{LogLevelCritical, "C"}
};
QString line = QString("%0: %1: %2\n").arg(t.value(level), category, message);
m_logFile.write(line.toUtf8());
m_logFile.flush();
}
emit messageAdded(category, message, level);
}
void AppLogController::updateFilters()
{
QStringList loggingRules = {"*.warn=false", "*.info=false", "*.debug=false"};
// Load the rules from nymead.conf file and append them to the rules
foreach (const QString &category, nymeaLoggingCategories()) {
LogLevel level = m_logLevels.value(category, LogLevelWarning);
loggingRules << QString("%1.debug=%2").arg(category).arg(level >= LogLevelDebug ? "true" : "false");
loggingRules << QString("%1.info=%2").arg(category).arg(level >= LogLevelInfo ? "true" : "false");
loggingRules << QString("%1.warn=%2").arg(category).arg(level >= LogLevelWarning ? "true" : "false");
}
QLoggingCategory::setFilterRules(loggingRules.join('\n'));
}
void AppLogController::openLogFile()
{
// Make sure log dir exists
if (!QDir().mkpath(logPath())) {
qWarning() << "Cannot create cache location. Logging will not work.";
}
// Rotate old log files, keeping the last 5
for (int i = 4; i > 0; i--) {
if (QFile::exists(currentLogFile() + "." + QString::number(i))) {
if (QFile::exists(currentLogFile() + "." + QString::number(i + 1))) {
QFile::remove(currentLogFile() + "." + QString::number(i + 1));
}
QFile::rename(currentLogFile() + "." + QString::number(i), currentLogFile() + "." + QString::number(i + 1));
}
}
if (QFile::exists(currentLogFile())) {
QFile::rename(currentLogFile(), currentLogFile() + ".1");
}
m_logFile.setFileName(currentLogFile());
if (!m_logFile.open(QFile::ReadWrite | QFile::Truncate)) {
qWarning() << "Cannot open logfile for writing.";
} else {
qDebug() << "App log opened at" << m_logFile.fileName();
}
}
LogMessages::LogMessages(QObject *parent):
QAbstractListModel(parent)
{
QFile f(AppLogController::instance()->currentLogFile());
if (!f.open(QFile::ReadOnly)) {
return;
}
QHash<QString, AppLogController::LogLevel> map = {
{"C", AppLogController::LogLevelCritical},
{"W", AppLogController::LogLevelWarning},
{"I", AppLogController::LogLevelInfo},
{"D", AppLogController::LogLevelDebug}
};
while (!f.atEnd()) {
QByteArray line = f.readLine().trimmed();
QList<QByteArray> parts = line.split(':');
if (parts.length() < 2) {
continue;
}
LogMessage message;
message.level = map.value(parts.takeFirst());
message.category = parts.takeFirst();
message.message = parts.join(":");
m_messages.append(message);
}
connect(AppLogController::instance(), &AppLogController::messageAdded, this, [=](const QString &category, const QString &message, AppLogController::LogLevel level){
beginInsertRows(QModelIndex(), m_messages.count(), m_messages.count());
LogMessage msg;
msg.category = category;
msg.message = message;
msg.level = level;
m_messages.append(msg);
endInsertRows();
});
}
int LogMessages::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return m_messages.count();
}
QVariant LogMessages::data(const QModelIndex &index, int role) const
{
switch (role) {
case RoleCategory:
return m_messages.at(index.row()).category;
case RoleMessage:
return m_messages.at(index.row()).message;
case RoleLevel:
return m_messages.at(index.row()).level;
case RoleText:
return m_messages.at(index.row()).category + ": " + m_messages.at(index.row()).message;
}
return QVariant();
}
QHash<int, QByteArray> LogMessages::roleNames() const
{
QHash<int, QByteArray> roles;
roles.insert(RoleCategory, "category");
roles.insert(RoleMessage, "message");
roles.insert(RoleLevel, "level");
roles.insert(RoleText, "text");
return roles;
}
void LogMessages::append(const QString &category, const QString &message, AppLogController::LogLevel level)
{
beginInsertRows(QModelIndex(), m_messages.count(), m_messages.count());
LogMessage msg;
msg.category = category;
msg.message = message;
msg.level = level;
m_messages.append(msg);
endInsertRows();
int maxEntries = 1024;
if (m_messages.size() > maxEntries) {
beginRemoveRows(QModelIndex(), 0, 0);
m_messages.removeFirst();
endRemoveRows();
}
}
LoggingCategories::LoggingCategories(AppLogController *parent):
QAbstractListModel(parent),
m_controller(parent)
{
connect(m_controller, &AppLogController::categoryChanged, this, [=](const QString &category) {
QModelIndex idx = index(nymeaLoggingCategories().indexOf(category));
emit dataChanged(idx, idx, {RoleLevel});
});
}
int LoggingCategories::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return nymeaLoggingCategories().count();
}
QVariant LoggingCategories::data(const QModelIndex &index, int role) const
{
switch (role) {
case RoleName:
return nymeaLoggingCategories().at(index.row());
case RoleLevel:
return m_controller->logLevel(nymeaLoggingCategories().at(index.row()));
}
return QVariant();
}
QHash<int, QByteArray> LoggingCategories::roleNames() const
{
QHash<int, QByteArray> roles;
roles.insert(RoleName, "name");
roles.insert(RoleLevel, "logLevel");
return roles;
}

View File

@ -37,60 +37,120 @@
#include <QAbstractListModel>
#include <QMutex>
class AppLogController : public QAbstractListModel
class LogMessages;
class LoggingCategories;
class AppLogController : public QObject
{
Q_OBJECT
Q_PROPERTY(bool canWriteLogs READ canWriteLogs CONSTANT)
Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged)
Q_PROPERTY(QString logFile READ logFile CONSTANT)
Q_PROPERTY(LoggingCategories* loggingCategories READ loggingCategories CONSTANT)
public:
enum Type {
TypeInfo,
TypeWarning
// Note: QtMsgType is sorted in a way that we can't compare for >= etc
enum LogLevel {
LogLevelCritical = 0,
LogLevelWarning = 1,
LogLevelInfo = 2,
LogLevelDebug = 3
};
Q_ENUM(Type)
enum Roles {
RoleText,
RoleType
};
Q_ENUM(Roles)
Q_ENUM(LogLevel)
static LogLevel qtMsgTypeToLogLevel(QtMsgType msgType);
static QtMsgType logLevelToQtMsgType(LogLevel logLevel);
static QObject* appLogControllerProvider(QQmlEngine *engine, QJSEngine *scriptEngine);
static AppLogController* instance();
bool canWriteLogs() const;
bool enabled() const;
void setEnabled(bool enabled);
int rowCount(const QModelIndex &parent) const override;
QVariant data(const QModelIndex &parent, int role) const override;
QHash<int, QByteArray> roleNames() const override;
LoggingCategories* loggingCategories() const;
LogLevel logLevel(const QString &category) const;
Q_INVOKABLE void setLogLevel(const QString &category, LogLevel logLevel);
Q_INVOKABLE QString logPath() const;
Q_INVOKABLE QString currentLogFile() const;
Q_INVOKABLE QStringList logFiles() const;
Q_INVOKABLE void toClipboard();
QString logFile() const;
Q_INVOKABLE QString exportLogs();
signals:
void enabledChanged();
void logToModelChanged();
void categoryChanged(const QString &category, LogLevel level);
void messageAdded(const QString &category, const QString &message, LogLevel level);
private:
explicit AppLogController(QObject *parent = nullptr);
static QtMessageHandler s_oldLogMessageHandler;
static void logMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message);
void append(const QString &message, Type type = TypeInfo);
Q_INVOKABLE void append(const QString &category, const QString &message, AppLogController::LogLevel level);
void activate();
void deactivate();
void updateFilters();
void openLogFile();
QHash<QString, LogLevel> m_logLevels;
QFile m_logFile;
QStringList m_buffer;
QList<Type> m_types;
QMutex m_mutex;
QFile m_logFile;
LoggingCategories *m_loggingCategories = nullptr;
};
Q_DECLARE_METATYPE(AppLogController::LogLevel)
class LogMessages: public QAbstractListModel
{
Q_OBJECT
public:
enum Roles {
RoleCategory,
RoleMessage,
RoleLevel,
RoleText
};
Q_ENUM(Roles)
struct LogMessage {
QString category;
QString message;
AppLogController::LogLevel level;
};
LogMessages(QObject *parent = nullptr);
int rowCount(const QModelIndex &parent) const override;
QVariant data(const QModelIndex &index, int role) const override;
QHash<int, QByteArray> roleNames() const override;
void append(const QString &category, const QString &message, AppLogController::LogLevel level);
private:
QList<LogMessage> m_messages;
};
class LoggingCategories: public QAbstractListModel
{
Q_OBJECT
public:
enum Roles {
RoleName,
RoleLevel
};
Q_ENUM(Roles)
LoggingCategories(AppLogController *parent);
int rowCount(const QModelIndex &parent) const override;
QVariant data(const QModelIndex &index, int role) const override;
QHash<int, QByteArray> roleNames() const override;
private:
AppLogController *m_controller = nullptr;
};
#endif // APPLOGCONTROLLER_H

View File

@ -41,9 +41,12 @@
#include <QPointer>
#include "sigv4utils.h"
#include "logging.h"
AWSClient* AWSClient::s_instance = nullptr;
NYMEA_LOGGING_CATEGORY(dcCloud, "Cloud")
// This is Symantec's root CA certificate and most platforms should
// have this in their certificate storage already, but as we can't
// be certain about the core's setup, let's deploy it ourselves.
@ -187,11 +190,11 @@ bool AWSClient::confirmationPending() const
void AWSClient::login(const QString &username, const QString &password)
{
if (m_usedConfig.isEmpty()) {
qDebug() << "AWS config not set. Not logging in.";
qCInfo(dcCloud()) << "AWS config not set. Not logging in.";
return;
}
if (m_loginInProgress) {
qWarning() << "Login already pending...";
qCDebug(dcCloud()) << "Login already pending...";
return;
}
m_loginInProgress = true;
@ -229,7 +232,7 @@ void AWSClient::login(const QString &username, const QString &password)
QJsonDocument jsonDoc = QJsonDocument::fromVariant(params);
QByteArray payload = jsonDoc.toJson(QJsonDocument::Compact);
qDebug() << "Logging in to AWS as user:" << username << "with config" << m_usedConfig;
qCInfo(dcCloud()) << "Logging in to AWS as user:" << username << "with config" << m_usedConfig;
QNetworkReply *reply = m_nam->post(request, payload);
connect(reply, &QNetworkReply::finished, this, [this, reply, username, password]() {
@ -237,18 +240,18 @@ void AWSClient::login(const QString &username, const QString &password)
m_loginInProgress = false;
if (reply->error() != QNetworkReply::NoError) {
if (reply->error() == QNetworkReply::HostNotFoundError) {
qDebug() << "Error logging in to aws due to network connection.";
qCWarning(dcCloud()) << "Error logging in to aws due to network connection.";
emit loginResult(LoginErrorNetworkError);
return;
}
if (reply->error() == QNetworkReply::ProtocolInvalidOperationError) {
qWarning() << "Looks like a wrong password.";
qCWarning(dcCloud()) << "Looks like a wrong password.";
m_username.clear();
m_password.clear();
emit loginResult(LoginErrorInvalidUserOrPass);
return;
}
qWarning() << "Error logging in to aws. Error:" << reply->error() << reply->errorString();
qCWarning(dcCloud()) << "Error logging in to aws. Error:" << reply->error() << reply->errorString();
emit loginResult(LoginErrorUnknownError);
return;
}
@ -256,7 +259,7 @@ void AWSClient::login(const QString &username, const QString &password)
QJsonParseError error;
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
if (error.error != QJsonParseError::NoError) {
qWarning() << "Failed to parse AWS login response" << error.errorString();
qCWarning(dcCloud()) << "Failed to parse AWS login response" << error.errorString();
m_username.clear();
m_password.clear();
emit loginResult(LoginErrorUnknownError);
@ -272,7 +275,7 @@ void AWSClient::login(const QString &username, const QString &password)
// qDebug() << "AWS ID token" << m_idToken;
QList<QByteArray> jwtParts = m_idToken.split('.');
if (jwtParts.count() != 3) {
qWarning() << "Error: JWT token doesn't have 3 parts. Cannot retrieve AWS Cognito ID.";
qCWarning(dcCloud()) << "Error: JWT token doesn't have 3 parts. Cannot retrieve AWS Cognito ID.";
return;
}
// qDebug() << "decoded header:" << QByteArray::fromBase64(jwtParts.at(0));
@ -331,13 +334,13 @@ void AWSClient::signup(const QString &username, const QString &password)
QJsonDocument jsonDoc = QJsonDocument::fromVariant(params);
QByteArray payload = jsonDoc.toJson(QJsonDocument::Compact);
qDebug() << "Signing up to AWS as user:" << username << payload;
qCInfo(dcCloud()) << "Signing up to AWS as user:" << username << payload;
QNetworkReply *reply = m_nam->post(request, payload);
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
QByteArray data = reply->readAll();
reply->deleteLater();
qDebug() << "AWS signup reply:" << data;
qCDebug(dcCloud()) << "AWS signup reply:" << data;
if (reply->error() == QNetworkReply::ProtocolInvalidOperationError) {
emit signupResult(LoginErrorInvalidUserOrPass);
@ -345,7 +348,7 @@ void AWSClient::signup(const QString &username, const QString &password)
}
if (reply->error() != QNetworkReply::NoError) {
qWarning() << "Error signing up to aws:" << reply->error() << reply->errorString();
qCWarning(dcCloud()) << "Error signing up to aws:" << reply->error() << reply->errorString();
m_username.clear();
m_password.clear();
emit signupResult(LoginErrorUnknownError);
@ -382,13 +385,13 @@ void AWSClient::confirmRegistration(const QString &code)
QJsonDocument jsonDoc = QJsonDocument::fromVariant(params);
QByteArray payload = jsonDoc.toJson(QJsonDocument::Compact);
qDebug() << "Confirming registration for user:" << m_username;
qCInfo(dcCloud()) << "Confirming registration for user:" << m_username;
QNetworkReply *reply = m_nam->post(request, payload);
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
QByteArray data = reply->readAll();
reply->deleteLater();
qDebug() << "AWS signup reply:" << data;
qCDebug(dcCloud()) << "AWS signup reply:" << data;
if (reply->error() == QNetworkReply::ProtocolInvalidOperationError) {
QJsonParseError error;
@ -405,7 +408,7 @@ void AWSClient::confirmRegistration(const QString &code)
}
if (reply->error() != QNetworkReply::NoError) {
qWarning() << "Error confirming registration:" << reply->error() << reply->errorString();
qCWarning(dcCloud()) << "Error confirming registration:" << reply->error() << reply->errorString();
emit confirmationResult(LoginErrorUnknownError);
return;
}
@ -440,7 +443,7 @@ void AWSClient::forgotPassword(const QString &username)
QJsonDocument jsonDoc = QJsonDocument::fromVariant(params);
QByteArray payload = jsonDoc.toJson(QJsonDocument::Compact);
qDebug() << "Forgot password for user:" << username << payload;
qCInfo(dcCloud()) << "Forgot password for user:" << username << payload;
QNetworkReply *reply = m_nam->post(request, payload);
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
@ -456,12 +459,12 @@ void AWSClient::forgotPassword(const QString &username)
}
if (reply->error() != QNetworkReply::NoError) {
qWarning() << "Error calling ForgotPassword:" << reply->error() << reply->errorString() << data;
qCWarning(dcCloud()) << "Error calling ForgotPassword:" << reply->error() << reply->errorString() << data;
emit forgotPasswordResult(LoginErrorUnknownError);
return;
}
qDebug() << "AWS forgotPassword success:" << data;
qCInfo(dcCloud()) << "AWS forgotPassword success:" << data;
emit forgotPasswordResult(LoginErrorNoError);
});
@ -491,7 +494,8 @@ void AWSClient::confirmForgotPassword(const QString &username, const QString &co
QJsonDocument jsonDoc = QJsonDocument::fromVariant(params);
QByteArray payload = jsonDoc.toJson(QJsonDocument::Compact);
qDebug() << "ConfirmForgotPassword for user:" << username << payload;
qCInfo(dcCloud()) << "Resetting password for user:" << username;
qCDebug(dcCloud()) << "Reset password payload:" << payload;
QNetworkReply *reply = m_nam->post(request, payload);
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
@ -499,12 +503,12 @@ void AWSClient::confirmForgotPassword(const QString &username, const QString &co
reply->deleteLater();
if (reply->error() != QNetworkReply::NoError) {
qWarning() << "Error calling ConfirmForgotPassword:" << reply->error() << reply->errorString() << data;
qCWarning(dcCloud()) << "Error calling ConfirmForgotPassword:" << reply->error() << reply->errorString() << data;
emit confirmForgotPasswordResult(LoginErrorUnknownError);
return;
}
qDebug() << "AWS ConfirmForgotPassword success:" << data;
qCInfo(dcCloud()) << "Password reset successfully.";
emit confirmForgotPasswordResult(LoginErrorNoError);
});
@ -513,26 +517,26 @@ void AWSClient::confirmForgotPassword(const QString &username, const QString &co
void AWSClient::deleteAccount()
{
if (!isLoggedIn()) {
qWarning() << "Not logged in at AWS. Can't delete account";
qCWarning(dcCloud()) << "Not logged in at AWS. Can't delete account";
return;
}
if (tokensExpired()) {
qDebug() << "Cannot unpair device. Need to refresh our tokens";
qCInfo(dcCloud()) << "Cannot unpair device. Need to refresh our tokens";
refreshAccessToken();
QueuedCall::enqueue(m_callQueue, QueuedCall("deleteAccount"));
return;
}
qDebug() << "Deleting account";
qCInfo(dcCloud()) << "Deleting account";
QUrl url(QString("https://%1/users/profiles/%2").arg(m_configs.value(m_usedConfig).apiEndpoint).arg(m_userId));
QNetworkRequest request(url);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setRawHeader("x-api-idToken", m_idToken);
qDebug() << "DELETE" << url.toString();
qDebug() << "HEADERS:";
qCDebug(dcCloud()) << "DELETE" << url.toString();
qCDebug(dcCloud()) << "HEADERS:";
foreach (const QByteArray &hdr, request.rawHeaderList()) {
qDebug() << hdr << ":" << request.rawHeader(hdr);
qCDebug(dcCloud()) << hdr << ":" << request.rawHeader(hdr);
}
QNetworkReply *reply = m_nam->deleteResource(request);
@ -540,36 +544,36 @@ void AWSClient::deleteAccount()
reply->deleteLater();
QByteArray data = reply->readAll();
if (reply->error() != QNetworkReply::NoError) {
qWarning() << "Error deleting cloud user account:" << reply->error() << reply->errorString() << qUtf8Printable(data);
qCWarning(dcCloud()) << "Error deleting cloud user account:" << reply->error() << reply->errorString() << qUtf8Printable(data);
emit deleteAccountResult(LoginErrorUnknownError);
return;
}
QJsonParseError error;
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
if (error.error != QJsonParseError::NoError) {
qWarning() << "Failed to parse JSON from server" << error.errorString() << qUtf8Printable(data);
qCWarning(dcCloud()) << "Failed to parse JSON from server" << error.errorString() << qUtf8Printable(data);
emit deleteAccountResult(LoginErrorUnknownError);
return;
}
emit deleteAccountResult(LoginErrorNoError);
logout();
qDebug() << "Account deleted" << data;
qCInfo(dcCloud()) << "Account deleted" << data;
});
}
void AWSClient::unpairDevice(const QString &coreId)
{
if (!isLoggedIn()) {
qWarning() << "Not logged in at AWS. Can't unpair device";
qCWarning(dcCloud()) << "Not logged in at AWS. Can't unpair device";
return;
}
if (tokensExpired()) {
qDebug() << "Cannot unpair device. Need to refresh our tokens";
qCInfo(dcCloud()) << "Cannot unpair device. Need to refresh our tokens";
refreshAccessToken();
QueuedCall::enqueue(m_callQueue, QueuedCall("unpairDevice", coreId));
return;
}
qDebug() << "unpairing device";
qCInfo(dcCloud()) << "Unpairing device" << coreId << "from user" << m_username;
QUrl url(QString("https://%1/users/devices/%2").arg(m_configs.value(m_usedConfig).apiEndpoint).arg(coreId));
QNetworkRequest request(url);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
@ -591,7 +595,7 @@ void AWSClient::unpairDevice(const QString &coreId)
qWarning() << "Failed to parse JSON from server" << error.errorString() << qUtf8Printable(data);
return;
}
qDebug() << "Device unpaired" << data;
qCInfo(dcCloud()) << "Device" << coreId << "unpaired from user" << m_username;
m_devices->remove(coreId);
});
@ -628,19 +632,19 @@ void AWSClient::getId()
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
reply->deleteLater();
if (reply->error() != QNetworkReply::NoError) {
qWarning() << "Error calling GetId" << reply->error() << reply->errorString();
qCWarning(dcCloud()) << "Error calling GetId" << reply->error() << reply->errorString();
return;
}
QByteArray data = reply->readAll();
QJsonParseError error;
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
if (error.error != QJsonParseError::NoError) {
qWarning() << "Error parsing json reply for GetId" << error.errorString();
qCWarning(dcCloud()) << "Error parsing json reply for GetId" << error.errorString();
return;
}
m_identityId = jsonDoc.toVariant().toMap().value("IdentityId").toByteArray();
// qDebug() << "Received cognito identity id" << m_identityId;// << qUtf8Printable(data);
qCDebug(dcCloud()) << "Received cognito identity id" << m_identityId;// << qUtf8Printable(data);
getCredentialsForIdentity(m_identityId);
});
@ -649,11 +653,11 @@ void AWSClient::getId()
void AWSClient::registerPushNotificationEndpoint(const QString &registrationId, const QString &deviceDisplayName, const QString mobileDeviceId, const QString &mobileDeviceManufacturer, const QString &mobileDeviceModel)
{
if (!isLoggedIn()) {
qWarning() << "Not logged in at AWS. Can't register push endpoint";
qCWarning(dcCloud()) << "Not logged in at AWS. Can't register push endpoint";
return;
}
if (tokensExpired()) {
qDebug() << "Cannot register push endpoint. Need to refresh our tokens";
qCInfo(dcCloud()) << "Cannot register push endpoint. Need to refresh our tokens";
QueuedCall::enqueue(m_callQueue, QueuedCall("registerPushNotificationEndpoint", registrationId, deviceDisplayName, mobileDeviceId, mobileDeviceManufacturer, mobileDeviceModel));
refreshAccessToken();
return;
@ -681,7 +685,7 @@ void AWSClient::registerPushNotificationEndpoint(const QString &registrationId,
QJsonDocument jsonDoc = QJsonDocument::fromVariant(payload);
qDebug() << "Registering push notification endpoint" << qUtf8Printable(QJsonDocument::fromVariant(payload).toJson());
qCInfo(dcCloud()) << "Registering push notification endpoint" << qUtf8Printable(QJsonDocument::fromVariant(payload).toJson());
// qDebug() << "POST" << url.toString();
// qDebug() << "HEADERS:";
// foreach (const QByteArray &hdr, request.rawHeaderList()) {
@ -694,10 +698,10 @@ void AWSClient::registerPushNotificationEndpoint(const QString &registrationId,
reply->deleteLater();
QByteArray data = reply->readAll();
if (reply->error() != QNetworkReply::NoError) {
qWarning() << "Error registering push notification endpoint:" << reply->error() << reply->errorString() << qUtf8Printable(data);
qCWarning(dcCloud()) << "Error registering push notification endpoint:" << reply->error() << reply->errorString() << qUtf8Printable(data);
return;
}
qDebug() << "Push notification endpoint registered" << data;
qCInfo(dcCloud()) << "Push notification endpoint registered" << data;
});
}
@ -722,27 +726,27 @@ void AWSClient::fetchCertificate(const QString &uuid, std::function<void(const Q
request.setRawHeader("X-api-deviceId", fixedUuid.toUtf8());
request.setRawHeader("X-api-serialId", "69696969");
QNetworkReply *reply = m_nam->get(request);
qDebug() << "Fetching certificate for vendor:" << m_configs.value(m_usedConfig).certificateVendorId << "device id:" << fixedUuid;
qCInfo(dcCloud()) << "Fetching certificate for vendor:" << m_configs.value(m_usedConfig).certificateVendorId << "device id:" << fixedUuid;
connect(reply, &QNetworkReply::finished, this, [this, reply, callback]() {
reply->deleteLater();
QByteArray data = reply->readAll();
if (reply->error() != QNetworkReply::NoError) {
qWarning() << "Error deploying certificate" << data;
qCWarning(dcCloud()) << "Error deploying certificate" << data;
return;
}
QJsonParseError error;
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
if (error.error != QJsonParseError::NoError) {
qWarning() << "Error parsing certificate json" << data;
qCWarning(dcCloud()) << "Error parsing certificate json" << data;
return;
}
QByteArray certificate = jsonDoc.toVariant().toMap().value("certificatePem").toByteArray();
QByteArray publicKey = jsonDoc.toVariant().toMap().value("keyPair").toMap().value("PublicKey").toByteArray();
QByteArray privateKey = jsonDoc.toVariant().toMap().value("keyPair").toMap().value("PrivateKey").toByteArray();
qDebug() << "Certificate received" << certificate;
qDebug() << "Public key" << publicKey;
qDebug() << "Private key" << privateKey;
qCDebug(dcCloud()) << "Certificate received" << certificate;
qCDebug(dcCloud()) << "Public key" << publicKey;
qCDebug(dcCloud()) << "Private key" << privateKey;
callback(rootCA, certificate, publicKey, privateKey, m_configs.value(m_usedConfig).mqttEndpoint);
});
@ -769,10 +773,10 @@ void AWSClient::setConfig(const QString &config)
if (m_usedConfig != fixedConfig) {
if (!m_configs.contains(fixedConfig)) {
qWarning() << "AWS: Config" << fixedConfig << "not known. Not switching AWS config";
qCWarning(dcCloud()) << "AWS: Config" << fixedConfig << "not known. Not switching AWS config";
return;
}
qDebug() << "Setting AWS configuration to" << fixedConfig;
qCInfo(dcCloud()) << "Setting AWS configuration to" << fixedConfig;
m_usedConfig = fixedConfig;
emit configChanged();
}
@ -814,7 +818,7 @@ void AWSClient::getCredentialsForIdentity(const QString &identityId)
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
reply->deleteLater();
if (reply->error() != QNetworkReply::NoError) {
qWarning() << "Error calling GetCredentialsForIdentity" << reply->errorString();
qCWarning(dcCloud()) << "Error calling GetCredentialsForIdentity" << reply->errorString();
emit loginResult(LoginErrorUnknownError);
return;
}
@ -822,7 +826,7 @@ void AWSClient::getCredentialsForIdentity(const QString &identityId)
QJsonParseError error;
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
if (error.error != QJsonParseError::NoError) {
qWarning() << "Error parsing JSON reply from GetCredentialsForIdentity" << error.errorString();
qCWarning(dcCloud()) << "Error parsing JSON reply from GetCredentialsForIdentity" << error.errorString();
emit loginResult(LoginErrorUnknownError);
return;
}
@ -832,14 +836,11 @@ void AWSClient::getCredentialsForIdentity(const QString &identityId)
m_secretKey = credentialsMap.value("SecretKey").toByteArray();
m_sessionToken = credentialsMap.value("SessionToken").toByteArray();
m_sessionTokenExpiry = QDateTime::fromSecsSinceEpoch(credentialsMap.value("Expiration").toLongLong());
qDebug() << "AWS Credentials for Identity received.";// << data;
qDebug() << "AWS login successful. Userid:" << m_userId;
qCInfo(dcCloud()) << "AWS login successful. Userid:" << m_userId;
QSettings settings;
qDebug() << "settings has:" << settings.childGroups();
bool newLogin = !settings.childGroups().contains("cloud");
settings.remove("cloud");
@ -860,7 +861,7 @@ void AWSClient::getCredentialsForIdentity(const QString &identityId)
emit loginResult(LoginErrorNoError);
if (newLogin) {
qDebug() << "new login!";
qCInfo(dcCloud()) << "New login!";
emit isLoggedInChanged();
}
@ -890,11 +891,11 @@ bool AWSClient::tokensExpired() const
bool AWSClient::postToMQTT(const QString &coreId, const QString &nonce, QObject* sender, std::function<void (bool)> callback)
{
if (!isLoggedIn()) {
qWarning() << "Cannot post to MQTT. Not logged in to AWS";
qCWarning(dcCloud()) << "Cannot post to MQTT. Not logged in to AWS";
return false;
}
if (tokensExpired()) {
qDebug() << "Cannot post to MQTT. Need to refresh the tokens first";
qCDebug(dcCloud()) << "Cannot post to MQTT. Need to refresh the tokens first";
refreshAccessToken();
QueuedCall::enqueue(m_callQueue, QueuedCall("postToMQTT", coreId, nonce, sender, callback));
return true; // So far it looks we're doing ok... let's return true
@ -929,7 +930,7 @@ bool AWSClient::postToMQTT(const QString &coreId, const QString &nonce, QObject*
// Workaround MQTT broker url weirdness as described above
request.setUrl("https://" + m_configs.value(m_usedConfig).mqttEndpoint + path1);
qDebug() << "Posting to MQTT:" << request.url().toString();
qCDebug(dcCloud()) << "Posting to MQTT:" << request.url().toString();
// qDebug() << "HEADERS:";
// foreach (const QByteArray &headerName, request.rawHeaderList()) {
// qDebug() << headerName << ":" << request.rawHeader(headerName);
@ -948,23 +949,23 @@ bool AWSClient::postToMQTT(const QString &coreId, const QString &nonce, QObject*
QByteArray data = reply->readAll();
// qDebug() << "MQTT post reply" << data;
if (senderWatcher.isNull()) {
qDebug() << "Request object disappeared. Discarding MQTT reply...";
qCDebug(dcCloud()) << "Request object disappeared. Discarding MQTT reply...";
return;
}
if (reply->error() != QNetworkReply::NoError) {
qWarning() << "MQTT Network reply error" << reply->error() << reply->errorString();
qCWarning(dcCloud()) << "MQTT Network reply error" << reply->error() << reply->errorString();
callback(false);
return;
}
QJsonParseError error;
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
if (error.error != QJsonParseError::NoError) {
qWarning() << "Failed to parse MQTT reply" << error.error << error.errorString() << data;
qCWarning(dcCloud()) << "Failed to parse MQTT reply" << error.error << error.errorString() << data;
callback(false);
return;
}
if (jsonDoc.toVariant().toMap().value("message").toString() != "OK") {
qWarning() << "Something went wrong posting to MQTT:" << jsonDoc.toVariant().toMap().value("message").toString();
qCWarning(dcCloud()) << "Something went wrong posting to MQTT:" << jsonDoc.toVariant().toMap().value("message").toString();
callback(false);
return;
}
@ -976,18 +977,22 @@ bool AWSClient::postToMQTT(const QString &coreId, const QString &nonce, QObject*
void AWSClient::fetchDevices()
{
if (m_usedConfig.isEmpty()) {
qCWarning(dcCloud()) << "Cloud environment not set. Not fetching cloud devices";
return;
}
if (!isLoggedIn()) {
qWarning() << "Not logged in at AWS. Can't fetch paired devices";
qCWarning(dcCloud()) << "Not logged in at AWS. Can't fetch paired devices";
return;
}
if (tokensExpired()) {
qDebug() << "Cannot fetch devices. Need to refresh our tokens";
qCDebug(dcCloud()) << "Cannot fetch devices. Need to refresh our tokens";
refreshAccessToken();
QueuedCall::enqueue(m_callQueue, QueuedCall("fetchDevices"));
return;
}
// qDebug() << "Fetching cloud devices";
QUrl url(QString("https://%1/users/devices").arg(m_configs.value(m_usedConfig).apiEndpoint));
qCDebug(dcCloud()) << "Fetching cloud devices" << url.toString();
QNetworkRequest request(url);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setRawHeader("x-api-idToken", m_idToken);
@ -999,13 +1004,13 @@ void AWSClient::fetchDevices()
m_devices->setBusy(false);
QByteArray data = reply->readAll();
if (reply->error() != QNetworkReply::NoError) {
qWarning() << "Error fetching cloud devices:" << reply->error() << reply->errorString() << qUtf8Printable(data);
qCWarning(dcCloud()) << "Error fetching cloud devices:" << reply->error() << reply->errorString() << qUtf8Printable(data);
return;
}
QJsonParseError error;
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
if (error.error != QJsonParseError::NoError) {
qWarning() << "Failed to parse JSON from server" << error.errorString() << qUtf8Printable(data);
qCWarning(dcCloud()) << "Failed to parse JSON from server" << error.errorString() << qUtf8Printable(data);
return;
}
QList<QUuid> actualDevices;
@ -1013,7 +1018,7 @@ void AWSClient::fetchDevices()
QString deviceId = entry.toMap().value("deviceId").toString();
QString name = entry.toMap().value("name").toString();
bool online = entry.toMap().value("online").toBool();
qDebug() << "Have cloud device:" << deviceId << name << "online:" << online;
qCDebug(dcCloud()) << "Have cloud device:" << deviceId << name << "online:" << online;
AWSDevice *d = m_devices->getDevice(deviceId);
if (!d) {
@ -1044,7 +1049,7 @@ void AWSClient::fetchDevices()
void AWSClient::refreshAccessToken()
{
if (!isLoggedIn()) {
qDebug() << "Cannot refresh tokens. Not logged in to AWS";
qCWarning(dcCloud()) << "Cannot refresh tokens. Not logged in to AWS";
return;
}
@ -1111,7 +1116,7 @@ void AWSClient::refreshAccessToken()
// settings.setValue("idToken", m_idToken);
// settings.setValue("refreshToken", m_refreshToken);
qDebug() << "AWS login successful" << qUtf8Printable(jsonDoc.toJson(QJsonDocument::Indented));
qCInfo(dcCloud()) << "AWS login successful" << qUtf8Printable(jsonDoc.toJson(QJsonDocument::Indented));
emit isLoggedInChanged();
});
@ -1217,7 +1222,7 @@ void AWSDevices::remove(const QString &uuid)
}
}
if (idx == -1) {
qWarning() << "Cannot remove AWS with id" << uuid << "as there is no such device";
qCWarning(dcCloud()) << "Cannot remove AWS with id" << uuid << "as there is no such device";
return;
}
beginRemoveRows(QModelIndex(), idx, idx);

View File

@ -35,6 +35,10 @@
#include <QXmlStreamReader>
#include <QNetworkInterface>
#include "logging.h"
NYMEA_LOGGING_CATEGORY(dcUPnP, "UPnP")
UpnpDiscovery::UpnpDiscovery(NymeaHosts *nymeaHosts, QObject *parent) :
QObject(parent),
m_nymeaHosts(nymeaHosts)
@ -61,10 +65,10 @@ UpnpDiscovery::UpnpDiscovery(NymeaHosts *nymeaHosts, QObject *parent) :
}
if (port == 65535 || socket->state() != QUdpSocket::BoundState) {
socket->deleteLater();
qWarning() << "UPnP: Discovery could not bind to interface" << netAddressEntry.ip();
qCWarning(dcUPnP()) << "Discovery could not bind to interface" << netAddressEntry.ip();
continue;
}
qDebug() << "UPnP: Discovering on" << netAddressEntry.ip() << port;
qCInfo(dcUPnP()) << "Discovering on" << netAddressEntry.ip() << port;
m_sockets.append(socket);
connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(error(QAbstractSocket::SocketError)));
connect(socket, &QUdpSocket::readyRead, this, &UpnpDiscovery::readData);
@ -86,11 +90,11 @@ bool UpnpDiscovery::available() const
void UpnpDiscovery::discover()
{
if (!available()) {
qWarning() << "UPnP: UPnP not available. Discovery not started.";
qCWarning(dcUPnP()) << "UPnP not available. Discovery not started.";
return;
}
qDebug() << "UPNP: Discovery started...";
qCInfo(dcUPnP()) << "Discovery started...";
m_repeatTimer.start();
m_foundDevices.clear();
writeDiscoveryPacket();
@ -99,7 +103,7 @@ void UpnpDiscovery::discover()
void UpnpDiscovery::stopDiscovery()
{
qDebug() << "UPNP: Discovery stopped.";
qCInfo(dcUPnP()) << "Discovery stopped.";
m_repeatTimer.stop();
emit discoveringChanged();
}
@ -112,13 +116,13 @@ void UpnpDiscovery::writeDiscoveryPacket()
"MX:2\r\n"
"ST: ssdp:all\r\n\r\n");
// qDebug() << "sending discovery package";
qCDebug(dcUPnP()) << "sending discovery package";
foreach (QUdpSocket* socket, m_sockets) {
qint64 ret = socket->writeDatagram(ssdpSearchMessage, QHostAddress("239.255.255.250"), 1900);
if (ret != ssdpSearchMessage.length()) {
qWarning() << "UPnP: Error sending SSDP query on socket" << socket->localAddress();
// Leaving a debug message because this happens on many platforms and spams logs.
qCDebug(dcUPnP()) << "Error sending SSDP query on socket" << socket->localAddress();
}
}
}
@ -140,7 +144,7 @@ void UpnpDiscovery::readData()
data.resize(socket->pendingDatagramSize());
socket->readDatagram(data.data(), data.size(), &hostAddress, &port);
// qDebug() << "Received UPnP datagram:" << data;
qCDebug(dcUPnP()) << "Received UPnP datagram:" << data;
// if the data contains the HTTP OK header...
if (data.contains("HTTP/1.1 200 OK")) {
@ -168,7 +172,7 @@ void UpnpDiscovery::readData()
if (!m_foundDevices.contains(location) && isNymea) {
m_foundDevices.append(location);
// qDebug() << "Getting server data from:" << location;
qCDebug(dcUPnP()) << "Getting server data from:" << location;
QNetworkReply *reply = m_networkAccessManager->get(QNetworkRequest(location));
connect(reply, &QNetworkReply::sslErrors, [reply](const QList<QSslError> &errors){
reply->ignoreSslErrors(errors);
@ -186,7 +190,7 @@ void UpnpDiscovery::networkReplyFinished(QNetworkReply *reply)
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (reply->error() != QNetworkReply::NoError || status != 200) {
qWarning() << "UPnP: Error fetching discovery data:" << status << reply->error() << reply->errorString();
qCWarning(dcUPnP()) << "UPnP: Error fetching discovery data:" << status << reply->error() << reply->errorString();
return;
}
@ -244,13 +248,13 @@ void UpnpDiscovery::networkReplyFinished(QNetworkReply *reply)
}
}
qDebug() << "UPnP: Discovered device" << name << discoveredAddress << version << connections /*<< data*/;
qCDebug(dcUPnP()) << "Discovered device" << name << discoveredAddress << version << connections /*<< data*/;
NymeaHost* device = m_nymeaHosts->find(uuid);
if (!device) {
device = new NymeaHost(m_nymeaHosts);
device->setUuid(uuid);
qDebug() << "UPnP: Adding new host to model";
qCInfo(dcUPnP()) << "Adding new host to model" << device->name() << device->uuid();
m_nymeaHosts->addHost(device);
}
device->setName(name);
@ -258,7 +262,7 @@ void UpnpDiscovery::networkReplyFinished(QNetworkReply *reply)
foreach (const QUrl &url, connections) {
Connection *connection = device->connections()->find(url);
if (!connection) {
qDebug() << "UPnP: Adding new connection to host:" << device->name() << url;
qCInfo(dcUPnP()) << "Adding new connection to host:" << device->name() << url;
bool sslEnabled = url.scheme() == "nymeas" || url.scheme() == "wss";
QString displayName = QString("%1:%2").arg(url.host()).arg(url.port());
Connection::BearerType bearerType = QHostAddress(url.host()).isLoopback() ? Connection::BearerTypeLoopback : Connection::BearerTypeLan;
@ -266,7 +270,7 @@ void UpnpDiscovery::networkReplyFinished(QNetworkReply *reply)
connection->setOnline(true);
device->connections()->addConnection(connection);
} else {
qDebug() << "UPnP: Setting connection online:" << device->name() << url.toString();
qCInfo(dcUPnP()) << "Setting connection online:" << device->name() << url.toString();
connection->setOnline(true);
}
}

View File

@ -33,6 +33,9 @@
#include <QUuid>
#include "../nymeahost.h"
#include "logging.h"
NYMEA_LOGGING_CATEGORY(dcZeroConf, "ZeroConf")
ZeroconfDiscovery::ZeroconfDiscovery(NymeaHosts *nymeaHosts, QObject *parent) :
QObject(parent),
@ -50,9 +53,9 @@ ZeroconfDiscovery::ZeroconfDiscovery(NymeaHosts *nymeaHosts, QObject *parent) :
if (m_zeroconfJsonRPC->isValid()) {
m_zeroconfJsonRPC->startBrowser("_jsonrpc._tcp", QAbstractSocket::IPv4Protocol);
qDebug() << "ZeroConf: Created service browser for _jsonrpc._tcp:" << m_zeroconfJsonRPC->browserExists();
qCInfo(dcZeroConf()) << "Created service browser for _jsonrpc._tcp:" << m_zeroconfJsonRPC->browserExists();
} else {
qWarning() << "Zeroconf init failed for _jsonprc._tcp";
qCWarning(dcZeroConf()) << "Failed to initialize service broeser for _jsonprc._tcp";
}
m_zeroconfWebSocket = new QZeroConf(this);
@ -61,19 +64,19 @@ ZeroconfDiscovery::ZeroconfDiscovery(NymeaHosts *nymeaHosts, QObject *parent) :
connect(m_zeroconfWebSocket, &QZeroConf::serviceRemoved, this, &ZeroconfDiscovery::serviceEntryRemoved);
if (m_zeroconfWebSocket->isValid()) {
m_zeroconfWebSocket->startBrowser("_ws._tcp", QAbstractSocket::IPv4Protocol);
qDebug() << "ZeroConf: Created service browser for _ws._tcp:" << m_zeroconfWebSocket->browserExists();
qCInfo(dcZeroConf()) << "Created service browser for _ws._tcp:" << m_zeroconfWebSocket->browserExists();
} else {
qWarning() << "Zeroconf init failed for _ws._tcp";
qCWarning(dcZeroConf()) << "Failed to initialize service browserr for _ws._tcp";
}
#else
qDebug() << "Zeroconf support not compiled in. Zeroconf will not be available.";
qCInfo(dcZeroConf()) << "Zeroconf support not compiled in. Zeroconf will not be available.";
#endif
}
ZeroconfDiscovery::~ZeroconfDiscovery()
{
qDebug() << "ZeroConf: Shutting down service browsers";
qCInfo(dcZeroConf()) << "Shutting down service browsers";
}
bool ZeroconfDiscovery::available() const
@ -95,21 +98,21 @@ void ZeroconfDiscovery::serviceEntryAdded(const QZeroConfService &entry)
{
if (!entry->name().startsWith("nymea")) {
// Skip non-nymea services altogether
qDebug() << "Skipping Avahi entry:" << entry << entry->ip() << entry->txt() << entry->type();
qCDebug(dcZeroConf()) << "Skipping service entry:" << entry << entry->ip() << entry->txt() << entry->type();
return;
}
if (entry->ip().isNull()) {
// Skip entries that don't have an ip address at all for some reason
qDebug() << "Skipping Avahi entry:" << entry << entry->ip() << entry->txt() << entry->type();
qCDebug(dcZeroConf()) << "Skipping service entry:" << entry << entry->ip() << entry->txt() << entry->type();
return;
}
if (entry->ip().toString().startsWith("fe80")) {
// Skip link-local-IPv6 results
qDebug() << "Skipping Avahi entry:" << entry << entry->ip() << entry->txt() << entry->type();
qCDebug(dcZeroConf()) << "Skipping service entry:" << entry << entry->ip() << entry->txt() << entry->type();
return;
}
// qDebug() << "zeroconf service discovered" << entry->type() << entry->name() << " IP:" << entry->ip().toString() << entry->txt();
qCDebug(dcZeroConf()) << "Service discovered" << entry->type() << entry->name() << " IP:" << entry->ip().toString() << entry->txt();
QString uuid;
bool sslEnabled = false;
@ -130,14 +133,14 @@ void ZeroconfDiscovery::serviceEntryAdded(const QZeroConfService &entry)
version = txtRecord.second;
}
}
// qDebug() << "avahi service entry added" << serverName << uuid << sslEnabled;
qCDebug(dcZeroConf()) << "Service entry added" << serverName << uuid << sslEnabled;
NymeaHost* host = m_nymeaHosts->find(uuid);
if (!host) {
host = new NymeaHost(m_nymeaHosts);
host->setUuid(uuid);
qDebug() << "ZeroConf: Adding new host:" << serverName << uuid;
qCInfo(dcZeroConf()) << "Adding new host:" << serverName << uuid;
m_nymeaHosts->addHost(host);
}
host->setName(serverName);
@ -153,14 +156,14 @@ void ZeroconfDiscovery::serviceEntryAdded(const QZeroConfService &entry)
url.setPort(entry->port());
Connection *connection = host->connections()->find(url);
if (!connection) {
qDebug() << "Zeroconf: Adding new connection to host:" << host->name() << url.toString();
qCInfo(dcZeroConf()) << "Adding new connection to host:" << host->name() << url.toString();
QString displayName = QString("%1:%2").arg(url.host()).arg(url.port());
Connection::BearerType bearerType = QHostAddress(url.host()).isLoopback() ? Connection::BearerTypeLoopback : Connection::BearerTypeLan;
connection = new Connection(url, bearerType, sslEnabled, displayName);
connection->setOnline(true);
host->connections()->addConnection(connection);
} else {
qDebug() << "Zeroconf: Setting connection online:" << host->name() << url.toString();
qCInfo(dcZeroConf()) << "Setting connection online:" << host->name() << url.toString();
connection->setOnline(true);
}
}
@ -191,7 +194,7 @@ void ZeroconfDiscovery::serviceEntryRemoved(const QZeroConfService &entry)
}
}
// qDebug() << "Zeroconf: Service entry removed" << entry->name();
qCDebug(dcZeroConf()) << "Service entry removed" << entry->name();
NymeaHost* host = m_nymeaHosts->find(uuid);
if (!host) {
@ -213,7 +216,7 @@ void ZeroconfDiscovery::serviceEntryRemoved(const QZeroConfService &entry)
return;
}
qDebug() << "Zeroconf: Setting connection offline:" << host->name() << url.toString();
qCInfo(dcZeroConf()) << "Setting connection offline:" << host->name() << url.toString();
connection->setOnline(false);
}
#endif

View File

@ -44,6 +44,9 @@
#include <QGuiApplication>
#include "nymeatransportinterface.h"
#include "logging.h"
NYMEA_LOGGING_CATEGORY(dcNymeaConnection, "NymeaConnection")
NymeaConnection::NymeaConnection(QObject *parent) : QObject(parent)
{
@ -51,18 +54,18 @@ NymeaConnection::NymeaConnection(QObject *parent) : QObject(parent)
QObject::connect(m_networkConfigManager, &QNetworkConfigurationManager::configurationAdded, this, [this](const QNetworkConfiguration &config){
Q_UNUSED(config)
// qDebug() << "Network configuration added:" << config.name() << config.bearerTypeName() << config.purpose();
qCDebug(dcNymeaConnection()) << "Network configuration added:" << config.name() << config.bearerTypeName() << config.purpose();
updateActiveBearers();
});
QObject::connect(m_networkConfigManager, &QNetworkConfigurationManager::configurationRemoved, this, [this](const QNetworkConfiguration &config){
Q_UNUSED(config)
// qDebug() << "Network configuration removed:" << config.name() << config.bearerTypeName() << config.purpose();
qCDebug(dcNymeaConnection()) << "Network configuration removed:" << config.name() << config.bearerTypeName() << config.purpose();
updateActiveBearers();
});
QGuiApplication *app = static_cast<QGuiApplication*>(QGuiApplication::instance());
QObject::connect(app, &QGuiApplication::applicationStateChanged, this, [this](Qt::ApplicationState /*state*/) {
// qDebug() << "Application state changed to:" << state;
QObject::connect(app, &QGuiApplication::applicationStateChanged, this, [this](Qt::ApplicationState state) {
qCDebug(dcNymeaConnection()) << "Application state changed to:" << state;
updateActiveBearers();
});
@ -116,11 +119,11 @@ void NymeaConnection::setCurrentHost(NymeaHost *host)
emit currentHostChanged();
if (!m_currentHost) {
qDebug() << "No current host.";
qCInfo(dcNymeaConnection()) << "Current host cleared. Not connecting.";
return;
}
qDebug() << "Nymea host is" << m_currentHost->name() << m_currentHost->uuid();
qCInfo(dcNymeaConnection()) << "Nymea host set to:" << m_currentHost->name() << m_currentHost->uuid();
connect(m_currentHost, &NymeaHost::connectionChanged, this, &NymeaConnection::hostConnectionsUpdated);
@ -132,9 +135,6 @@ void NymeaConnection::setCurrentHost(NymeaHost *host)
Connection *NymeaConnection::currentConnection() const
{
qDebug() << "Current connection:" << m_currentHost << m_currentTransport << m_transportCandidates.count();
qDebug() << m_transportCandidates.keys();
qDebug() << m_transportCandidates.value(m_currentTransport);
if (!m_currentHost || !m_currentTransport) {
return nullptr;
}
@ -143,11 +143,10 @@ Connection *NymeaConnection::currentConnection() const
void NymeaConnection::sendData(const QByteArray &data)
{
// qDebug() << "sending data:" << data;
if (connected()) {
m_currentTransport->sendData(data);
} else {
qWarning() << "Connection: Not connected. Cannot send.";
qCWarning(dcNymeaConnection()) << "Connection: Not connected. Cannot send.";
}
}
@ -155,19 +154,19 @@ void NymeaConnection::onSslErrors(const QList<QSslError> &errors)
{
NymeaTransportInterface *transport = qobject_cast<NymeaTransportInterface*>(sender());
qDebug() << "SSL errors for url:" << transport->url();
qCDebug(dcNymeaConnection()) << "SSL errors for url:" << transport->url();
QList<QSslError> ignoredErrors;
foreach (const QSslError &error, errors) {
qDebug() << error.errorString();
if (error.error() == QSslError::HostNameMismatch) {
qDebug() << "Ignoring host mismatch on certificate.";
qCInfo(dcNymeaConnection()) << "Ignoring host mismatch on certificate.";
ignoredErrors.append(error);
} else if (error.error() == QSslError::SelfSignedCertificate || error.error() == QSslError::CertificateUntrusted) {
qDebug() << "Ignoring self signed certificate.";
qCInfo(dcNymeaConnection()) << "Ignoring self signed certificate.";
ignoredErrors.append(error);
} else {
// Reject the connection on all other errors...
qDebug() << "SSL Error:" << error.errorString() << error.certificate();
qCritical(dcNymeaConnection()) << "SSL Error:" << error.errorString() << error.certificate();
}
}
if (ignoredErrors == errors) {
@ -213,7 +212,7 @@ void NymeaConnection::onError(QAbstractSocket::SocketError error)
}
if (transport == m_currentTransport) {
qDebug() << "Current transport failed:" << error;
qCCritical(dcNymeaConnection()) << "Current transport failed:" << error;
// The current transport failed, forward the error
m_connectionStatus = errorStatus;
emit connectionStatusChanged();
@ -226,9 +225,9 @@ void NymeaConnection::onError(QAbstractSocket::SocketError error)
m_transportCandidates.remove(transport);
transport->deleteLater();
}
qDebug() << "A transport error happened for" << transport->url() << error << "(Still trying on" << m_transportCandidates.count() << "connections)";
qCWarning(dcNymeaConnection()) << "A transport error happened for" << transport->url() << error << "(Still trying on" << m_transportCandidates.count() << "connections)";
foreach (Connection *c, m_transportCandidates) {
qDebug() << "Connection candidate:" << c->url();
qCDebug(dcNymeaConnection()) << "Connection candidate:" << c->url();
}
if (m_transportCandidates.isEmpty()) {
m_connectionStatus = errorStatus;
@ -237,6 +236,7 @@ void NymeaConnection::onError(QAbstractSocket::SocketError error)
if (m_connectionStatus != ConnectionStatusSslUntrusted) {
QTimer::singleShot(1000, this, [this](){
if (m_currentHost) {
qCInfo(dcNymeaConnection()) << "Reconnecting...";
connectInternal(m_currentHost);
}
});
@ -250,20 +250,18 @@ void NymeaConnection::onConnected()
NymeaTransportInterface* newTransport = qobject_cast<NymeaTransportInterface*>(sender());
if (!m_currentTransport) {
m_currentTransport = newTransport;
qDebug() << "NymeaConnection: Connected to" << m_currentHost->name() << "via" << m_currentTransport->url() << m_currentTransport->isEncrypted();
qCInfo(dcNymeaConnection()) << "Connected to" << m_currentHost->name() << "via" << m_currentTransport->url() << m_currentTransport->isEncrypted();
emit currentConnectionChanged();
emit connectedChanged(true);
return;
}
if (m_currentTransport != newTransport) {
qDebug() << "Alternative connection established:" << newTransport->url();
// In theory, we could roam from one connection to another.
// However, in practice it turns out there are too many issues for this to be reliable
// So lets just tear down any alternative connection that comes up again.
qDebug() << "Dropping alternative connection again...";
qCInfo(dcNymeaConnection()) << "Dropping successfully established alternative connection to" << newTransport->url() << "again...";
m_transportCandidates.remove(newTransport);
newTransport->deleteLater();
@ -288,23 +286,24 @@ void NymeaConnection::onDisconnected()
{
NymeaTransportInterface* t = qobject_cast<NymeaTransportInterface*>(sender());
if (m_currentTransport != t) {
qWarning() << "NymeaConnection: An inactive transport for url" << t->url() << "disconnected... Cleaning up...";
qCDebug(dcNymeaConnection()) << "An inactive transport for url" << t->url() << "disconnected... Cleaning up...";
if (m_transportCandidates.contains(t)) {
m_transportCandidates.remove(t);
}
t->deleteLater();
qCInfo(dcNymeaConnection()) << "Current transport:" << m_currentTransport << "Remaining connections:" << m_transportCandidates.count() << "Current host:" << m_currentHost;
if (!m_currentTransport && m_transportCandidates.isEmpty()) {
qDebug() << "Last connection dropped.";
qCWarning(dcNymeaConnection()) << "Last connection dropped.";
QTimer::singleShot(1000, this, [this](){
if (m_currentHost && m_connectionStatus != ConnectionStatusSslUntrusted) {
qDebug() << "Trying to reconnect..";
qCInfo(dcNymeaConnection()) << "Trying to reconnect..";
connectInternal(m_currentHost);
}
});
}
qDebug() << "Current transport:" << m_currentTransport << "Remaining connections:" << m_transportCandidates.count() << "Current host:" << m_currentHost;
return;
}
m_transportCandidates.remove(m_currentTransport);
@ -313,7 +312,7 @@ void NymeaConnection::onDisconnected()
foreach (NymeaTransportInterface *candidate, m_transportCandidates.keys()) {
if (candidate->connectionState() == NymeaTransportInterface::ConnectionStateConnected) {
qDebug() << "Alternative connection is still up. Roaming to:" << candidate->url();
qCInfo(dcNymeaConnection()) << "Alternative connection is still up. Roaming to:" << candidate->url();
m_currentTransport = candidate;
break;
}
@ -322,7 +321,7 @@ void NymeaConnection::onDisconnected()
emit currentConnectionChanged();
if (!m_currentTransport) {
qDebug() << "NymeaConnection: disconnected.";
qCInfo(dcNymeaConnection()) << "Disconnected.";
emit connectedChanged(false);
}
@ -334,6 +333,7 @@ void NymeaConnection::onDisconnected()
if (m_connectionStatus != ConnectionStatusSslUntrusted) {
QTimer::singleShot(1000, this, [this](){
if (m_currentHost) {
qCInfo(dcNymeaConnection()) << "Trying to reconnect after disconnect...";
connectInternal(m_currentHost);
}
});
@ -344,9 +344,9 @@ void NymeaConnection::updateActiveBearers()
{
NymeaConnection::BearerTypes availableBearerTypes;
QList<QNetworkConfiguration> configs = m_networkConfigManager->allConfigurations(QNetworkConfiguration::Active);
// qDebug() << "Network configuations:" << configs.count();
qCDebug(dcNymeaConnection()) << "Network configuations:" << configs.count();
foreach (const QNetworkConfiguration &config, configs) {
// qDebug() << "Active network config:" << config.name() << config.bearerTypeFamily() << config.bearerTypeName();
qCDebug(dcNymeaConnection()) << "Active network config:" << config.name() << config.bearerTypeFamily() << config.bearerTypeName();
// NOTE: iOS doesn't correctly report bearer types. It'll be Unknown all the time. Let's hardcode it to WiFi for that...
#if defined(Q_OS_IOS)
@ -357,27 +357,27 @@ void NymeaConnection::updateActiveBearers()
}
if (availableBearerTypes == NymeaConnection::BearerTypeNone) {
// This is just debug info... On some platform bearer management seems a bit broken, so let's get some infos right away...
qDebug() << "No active bearer available. Inactive bearers are:";
qCDebug(dcNymeaConnection()) << "No active bearer available. Inactive bearers are:";
QList<QNetworkConfiguration> configs = m_networkConfigManager->allConfigurations();
foreach (const QNetworkConfiguration &config, configs) {
qDebug() << "Inactive network config:" << config.name() << config.bearerTypeFamily() << config.bearerTypeName();
qCDebug(dcNymeaConnection()) << "Inactive network config:" << config.name() << config.bearerTypeFamily() << config.bearerTypeName();
}
qWarning() << "Updating network manager";
qCDebug(dcNymeaConnection()) << "Updating network manager";
m_networkConfigManager->updateConfigurations();
}
if (m_availableBearerTypes != availableBearerTypes) {
// qDebug() << "Available Bearer Types changed:" << availableBearerTypes;
qCInfo(dcNymeaConnection()) << "Available Bearer Types changed to:" << availableBearerTypes;
m_availableBearerTypes = availableBearerTypes;
emit availableBearerTypesChanged();
} else {
// qDebug() << "Available Bearer Types:" << availableBearerTypes;
qCDebug(dcNymeaConnection()) << "Available Bearer Types:" << availableBearerTypes;
}
if (!m_currentHost) {
// No host set... Nothing to do...
qDebug() << "No current host... Nothing to do...";
qCInfo(dcNymeaConnection()) << "No current host... Nothing to do...";
return;
}
@ -392,14 +392,17 @@ void NymeaConnection::updateActiveBearers()
if (!m_currentTransport) {
// There's a host but no connection. Try connecting now...
qDebug() << "There's a host but no connection. Trying to connect now...";
qCInfo(dcNymeaConnection()) << "There's a host but no connection. Trying to connect now...";
connectInternal(m_currentHost);
}
}
void NymeaConnection::hostConnectionsUpdated()
{
connectInternal(m_currentHost);
if (!m_currentTransport) {
qCInfo(dcNymeaConnection()) << "Possible connections for host" << m_currentHost->name() << "updated.";
connectInternal(m_currentHost);
}
}
void NymeaConnection::registerTransport(NymeaTransportInterfaceFactory *transportFactory)
@ -418,10 +421,10 @@ void NymeaConnection::connectToHost(NymeaHost *nymeaHost, Connection *connection
m_preferredConnection = nullptr;
if (connection) {
if (nymeaHost->connections()->find(connection->url())) {
qDebug() << "Setting preferred connection to" << connection->url();
qCInfo(dcNymeaConnection()) << "Setting preferred connection to" << connection->url();
m_preferredConnection = connection;
} else {
qWarning() << "Connection" << connection << "is not a candidate for" << nymeaHost->name() << "Not setting preferred connection.";
qCWarning(dcNymeaConnection()) << "Connection" << connection << "is not a candidate for" << nymeaHost->name() << "Not setting preferred connection.";
}
}
@ -432,47 +435,48 @@ void NymeaConnection::connectInternal(NymeaHost *host)
{
if (m_preferredConnection) {
if (isConnectionBearerAvailable(m_preferredConnection->bearerType())) {
qDebug() << "Preferred connection is set. Using" << m_preferredConnection->url();
qCInfo(dcNymeaConnection()) << "Preferred connection is set. Using" << m_preferredConnection->url();
connectInternal(m_preferredConnection);
return;
}
qDebug() << "Preferred connection set but no bearer available for it.";
qCWarning(dcNymeaConnection()) << "Preferred connection set but no bearer available for it.";
}
Connection *loopbackConnection = host->connections()->bestMatch(Connection::BearerTypeLoopback);
if (loopbackConnection) {
qDebug() << "Best candidate Loopback connection:" << loopbackConnection->url();
qCDebug(dcNymeaConnection()) << "Best candidate Loopback connection:" << loopbackConnection->url();
connectInternal(loopbackConnection);
} else if (m_availableBearerTypes.testFlag(NymeaConnection::BearerTypeWiFi)
|| m_availableBearerTypes.testFlag(NymeaConnection::BearerTypeEthernet)) {
Connection* lanConnection = host->connections()->bestMatch(Connection::BearerTypeLan | Connection::BearerTypeWan);
if (lanConnection) {
qDebug() << "Best candidate LAN/WAN connection:" << lanConnection->url();
qCDebug(dcNymeaConnection()) << "Best candidate LAN/WAN connection:" << lanConnection->url();
connectInternal(lanConnection);
} else {
qDebug() << "No available LAN/WAN connection to" << host->name();
qCDebug(dcNymeaConnection()) << "No available LAN/WAN connection to" << host->name();
}
} else if (m_availableBearerTypes.testFlag(NymeaConnection::BearerTypeMobileData)) {
Connection* wanConnection = host->connections()->bestMatch(Connection::BearerTypeWan);
if (wanConnection) {
qDebug() << "Best candidate WAN connection:" << wanConnection->url();
qCDebug(dcNymeaConnection()) << "Best candidate WAN connection:" << wanConnection->url();
connectInternal(wanConnection);
} else {
qDebug() << "No available WAN connection to" << host->name();
qCDebug(dcNymeaConnection()) << "No available WAN connection to" << host->name();
}
}
Connection* cloudConnection = host->connections()->bestMatch(Connection::BearerTypeCloud);
if (cloudConnection) {
qDebug() << "Best candidate Cloud connection:" << cloudConnection->url();
qCDebug(dcNymeaConnection()) << "Best candidate Cloud connection:" << cloudConnection->url();
connectInternal(cloudConnection);
} else {
qDebug() << "No available Cloud connection to" << host->name();
qCDebug(dcNymeaConnection()) << "No available Cloud connection to" << host->name();
}
if (m_transportCandidates.isEmpty()) {
qCWarning(dcNymeaConnection()) << "No available bearers available for host:" << host->name() << host->uuid();
m_connectionStatus = ConnectionStatusNoBearerAvailable;
} else {
m_connectionStatus = ConnectionStatusConnecting;
@ -483,12 +487,12 @@ void NymeaConnection::connectInternal(NymeaHost *host)
bool NymeaConnection::connectInternal(Connection *connection)
{
if (!m_transportFactories.contains(connection->url().scheme())) {
qWarning() << "Cannot connect to urls of scheme" << connection->url().scheme() << "Supported schemes are" << m_transportFactories.keys();
qCCritical(dcNymeaConnection()) << "Cannot connect to urls of scheme" << connection->url().scheme() << "Supported schemes are" << m_transportFactories.keys();
return false;
}
if (m_transportCandidates.values().contains(connection)) {
qDebug() << "Already have a connection (or connection attempt) for" << connection->url();
qCInfo(dcNymeaConnection()) << "Already have a connection (or connection attempt) for" << connection->url();
return false;
}
@ -511,7 +515,7 @@ bool NymeaConnection::connectInternal(Connection *connection)
// }
m_transportCandidates.insert(newTransport, connection);
qDebug() << "Connecting to:" << connection->url() << newTransport << m_transportCandidates.value(newTransport);
qCInfo(dcNymeaConnection()) << "Connecting to:" << connection->url() << newTransport << m_transportCandidates.value(newTransport);
return newTransport->connect(connection->url());
}
@ -519,6 +523,7 @@ NymeaConnection::BearerType NymeaConnection::qBearerTypeToNymeaBearerType(QNetwo
{
switch (type) {
case QNetworkConfiguration::BearerUnknown:
// Unable to determine the connection type. Assume it's something we can establish any connection type on
return BearerTypeAll;
case QNetworkConfiguration::BearerEthernet:
return BearerTypeEthernet;

View File

@ -126,6 +126,7 @@
#include "zigbee/zigbeeadaptersproxy.h"
#include "zigbee/zigbeenetwork.h"
#include "zigbee/zigbeenetworks.h"
#include "applogcontroller.h"
#include <QtQml/qqml.h>
@ -158,6 +159,9 @@ void registerQmlTypes() {
qmlRegisterType<Engine>(uri, 1, 0, "Engine");
qmlRegisterSingletonType<AppLogController>("Nymea", 1, 0, "AppLogController", AppLogController::appLogControllerProvider);
qmlRegisterType<LogMessages>("Nymea", 1, 0, "LogMessages");
qmlRegisterUncreatableType<ThingManager>(uri, 1, 0, "ThingManager", "Can't create this in QML. Get it from the Engine.");
qmlRegisterUncreatableType<JsonRpcClient>(uri, 1, 0, "JsonRpcClient", "Can't create this in QML. Get it from the Engine.");
qmlRegisterUncreatableType<NymeaConnection>(uri, 1, 0, "NymeaConnection", "Can't create this in QML. Get it from the Engine.");

View File

@ -20,7 +20,9 @@ INCLUDEPATH += \
$$top_srcdir/QtZeroConf
SOURCES += \
$$PWD/wifisetup/btwifisetup.cpp \
$${PWD}/logging.cpp \
$${PWD}/applogcontroller.cpp \
$${PWD}/wifisetup/btwifisetup.cpp \
$${PWD}/configuration/networkmanager.cpp \
$${PWD}/engine.cpp \
$${PWD}/models/barseriesadapter.cpp \
@ -163,7 +165,9 @@ SOURCES += \
HEADERS += \
$$PWD/wifisetup/btwifisetup.h \
$${PWD}/logging.h \
$${PWD}/applogcontroller.h \
$${PWD}/wifisetup/btwifisetup.h \
$${PWD}/configuration/networkmanager.h \
$${PWD}/engine.h \
$${PWD}/models/barseriesadapter.h \

6
libnymea-app/logging.cpp Normal file
View File

@ -0,0 +1,6 @@
#include "logging.h"
QStringList& nymeaLoggingCategories() {
static QStringList _nymeaLoggingCategories;
return _nymeaLoggingCategories;
}

20
libnymea-app/logging.h Normal file
View File

@ -0,0 +1,20 @@
#ifndef LOGGING_H
#define LOGGING_H
#include <QLoggingCategory>
QStringList& nymeaLoggingCategories();
#define NYMEA_LOGGING_CATEGORY(name, string) \
class NymeaLoggingCategory##name: public QLoggingCategory { \
public: \
NymeaLoggingCategory##name(): QLoggingCategory(string) { nymeaLoggingCategories().append(string); } \
}; \
static NymeaLoggingCategory##name s_##name; \
const QLoggingCategory &name() \
{ \
return s_##name; \
} \
#endif // LOGGING_H

View File

@ -41,6 +41,9 @@
#include <QStandardPaths>
#include <QJsonDocument>
#include "logging.h"
NYMEA_LOGGING_CATEGORY(dcThingManager, "ThingManager")
ThingManager::ThingManager(JsonRpcClient* jsonclient, QObject *parent) :
JsonHandler(parent),
m_vendors(new Vendors(this)),
@ -122,7 +125,9 @@ void ThingManager::notificationReceived(const QVariantMap &data)
if (notification == "Integrations.StateChanged") {
Thing *thing = m_things->getThing(data.value("params").toMap().value("thingId").toUuid());
if (!thing) {
qWarning() << "Thing state change notification received for an unknown thing";
if (!m_fetchingData) {
qCWarning(dcThingManager()) << "Thing state change notification received for an unknown thing";
}
return;
}
QUuid stateTypeId = data.value("params").toMap().value("stateTypeId").toUuid();
@ -188,7 +193,9 @@ void ThingManager::notificationReceived(const QVariantMap &data)
Thing *thing = m_things->getThing(thingId);
if (!thing) {
qWarning() << "received an event from a thing we don't know..." << thingId << qUtf8Printable(QJsonDocument::fromVariant(data).toJson());
if (!m_fetchingData) {
qCWarning(dcThingManager()) << "received an event from a thing we don't know..." << thingId << qUtf8Printable(QJsonDocument::fromVariant(data).toJson());
}
return;
}
// qDebug() << "Event received" << thingId.toString() << eventTypeId.toString() << qUtf8Printable(QJsonDocument::fromVariant(event).toJson());

View File

@ -1,219 +0,0 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2020, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of nymea.
* This project including source code and documentation is protected by
* copyright law, and remains the property of nymea GmbH. All rights, including
* reproduction, publication, editing and translation, are reserved. The use of
* this project is subject to the terms of a license agreement to be concluded
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
* under https://nymea.io/license
*
* GNU General Public License Usage
* Alternatively, this project may be redistributed and/or modified under the
* terms of the GNU General Public License as published by the Free Software
* Foundation, GNU version 3. This project 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 project. If not, see <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "applogcontroller.h"
#include <QStandardPaths>
#include <QDebug>
#include <QSettings>
#include <QClipboard>
#include <QGuiApplication>
#include <QDir>
#include <QMutexLocker>
QtMessageHandler AppLogController::s_oldLogMessageHandler = nullptr;
QObject *AppLogController::appLogControllerProvider(QQmlEngine *engine, QJSEngine *scriptEngine)
{
Q_UNUSED(engine)
Q_UNUSED(scriptEngine)
return instance();
}
AppLogController *AppLogController::instance()
{
static AppLogController* thiz = nullptr;
if (!thiz) {
thiz = new AppLogController();
}
return thiz;
}
AppLogController::AppLogController(QObject *parent) : QAbstractListModel(parent)
{
QString path = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
QString fileName = path + "/nymea-app.log";
m_logFile.setFileName(fileName);
QByteArray oldContent;
if (QFile::exists(fileName)) {
if (QFile::exists(fileName + ".old")) {
QFile::remove(fileName + ".old");
}
QFile::rename(fileName, fileName + ".old");
QFile oldFile(fileName + ".old");
if (oldFile.open(QFile::ReadOnly)) {
oldFile.seek(qMax((long long)0, oldFile.size() - 1024 * 1024));
oldContent = oldFile.readAll();
oldFile.close();
m_buffer.append(QString(oldContent).split('\n'));
for (int i = 0; i < m_buffer.count(); i++) {
m_types.append(TypeInfo);
}
m_types.append(TypeWarning);
m_buffer.append("**** App restart ****");
oldContent.append("\n\n**** App restart ****\n\n");
}
}
QDir dir(path);
if (!dir.exists()) {
if (!dir.mkpath(path)) {
qWarning() << "Cannot create cache location. Logging will not work.";
return;
}
}
if (!m_logFile.open(QFile::ReadWrite | QFile::Truncate)) {
qWarning() << "Cannot open logfile for writing.";
return;
}
qDebug() << "App log opened at" << fileName;
m_logFile.write(oldContent);
if (enabled()) {
activate();
}
}
bool AppLogController::canWriteLogs() const
{
return m_logFile.isOpen();
}
bool AppLogController::enabled() const
{
QSettings settings;
return settings.value("AppLoggingEnabled", false).toBool();
}
void AppLogController::setEnabled(bool enabled)
{
if (enabled == this->enabled()) {
return;
}
if (enabled) {
if (!canWriteLogs()) {
qWarning() << "Cannot write log file. Not enabling logging.";
return;
}
activate();
} else {
deactivate();
}
QSettings settings;
settings.setValue("AppLoggingEnabled", enabled);
emit enabledChanged();
}
int AppLogController::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return m_buffer.count();
}
QVariant AppLogController::data(const QModelIndex &index, int role) const
{
switch (role) {
case RoleText:
return m_buffer.at(index.row());
case RoleType:
return m_types.at(index.row());
}
return QVariant();
}
QHash<int, QByteArray> AppLogController::roleNames() const
{
QHash<int, QByteArray> roles;
roles.insert(RoleText, "text");
roles.insert(RoleType, "type");
return roles;
}
void AppLogController::toClipboard()
{
m_logFile.seek(0);
QByteArray completeLog = m_logFile.readAll();
QGuiApplication::clipboard()->setText(completeLog);
}
QString AppLogController::logFile() const
{
return m_logFile.fileName();
}
void AppLogController::logMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message)
{
s_oldLogMessageHandler(type, context, message);
instance()->append(message, type == QtWarningMsg ? TypeWarning : TypeInfo);
}
void AppLogController::append(const QString &message, AppLogController::Type type)
{
QMutexLocker locker(&m_mutex);
QString finalMessage = message + "\n";
m_logFile.write(finalMessage.toUtf8());
m_logFile.flush();
beginInsertRows(QModelIndex(), m_buffer.count(), m_buffer.count());
m_buffer.append(message);
m_types.append(type);
endInsertRows();
int maxEntries = 1024;
if (m_buffer.size() > maxEntries) {
beginRemoveRows(QModelIndex(), 0, 0);
m_buffer.removeFirst();
m_types.removeFirst();
endRemoveRows();
}
}
void AppLogController::activate()
{
qDebug() << "Activating log file writing to" << m_logFile.fileName();
s_oldLogMessageHandler = qInstallMessageHandler(&logMessageHandler);
}
void AppLogController::deactivate()
{
qInstallMessageHandler(s_oldLogMessageHandler);
s_oldLogMessageHandler = nullptr;
}

View File

@ -42,12 +42,15 @@
#include "stylecontroller.h"
#include "pushnotifications.h"
#include "applogcontroller.h"
#include "ruletemplates/messages.h"
#include "nfchelper.h"
#include "nfcthingactionwriter.h"
#include "platformhelper.h"
#include "logging.h"
NYMEA_LOGGING_CATEGORY(dcApplication, "Application")
NYMEA_LOGGING_CATEGORY(qml, "qml")
int main(int argc, char *argv[])
{
@ -94,15 +97,15 @@ int main(int argc, char *argv[])
qtTranslator.load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath));
application.installTranslator(&qtTranslator);
qDebug() << "nymea:app" << APP_VERSION << "running on" << QSysInfo::machineHostName() << QSysInfo::prettyProductName() << QSysInfo::productType() << QSysInfo::productVersion();
qDebug() << "Locale info:" << QLocale() << QLocale().name() << QLocale().language() << QLocale().system();
qCInfo(dcApplication()) << "nymea:app" << APP_VERSION << "running on" << QSysInfo::machineHostName() << QSysInfo::prettyProductName() << QSysInfo::productType() << QSysInfo::productVersion();
qCInfo(dcApplication()) << "Locale info:" << QLocale() << QLocale().name() << QLocale().language() << QLocale().system();
QTranslator appTranslator;
bool translationResult = appTranslator.load("nymea-app-" + QLocale().name(), ":/translations/");
if (translationResult) {
qDebug() << "Loaded translation for locale" << QLocale();
qCDebug(dcApplication()) << "Loaded translation for locale" << QLocale();
} else {
qWarning() << "Failed to load translations for locale" << QLocale();
qCInfo(dcApplication()) << "Failed to load translations for locale" << QLocale();
}
application.installTranslator(&appTranslator);
@ -116,7 +119,7 @@ int main(int argc, char *argv[])
#else
StyleController styleController(parser.value(defaultStyleOption));
if (parser.isSet(styleOption)) {
qDebug() << "Setting style to" << parser.value(styleOption);
qCInfo(dcApplication()) << "Setting style to" << parser.value(styleOption);
styleController.lockToStyle(parser.value(styleOption));
}
#endif
@ -128,7 +131,7 @@ int main(int argc, char *argv[])
QFontDatabase::addApplicationFont(fi.absoluteFilePath());
}
foreach (const QFileInfo &fi, QDir(":/styles/" + styleController.currentStyle() + "/fonts/").entryInfoList()) {
qDebug() << "Adding style font:" << fi.absoluteFilePath();
qCDebug(dcApplication()) << "Adding style font:" << fi.absoluteFilePath();
QFontDatabase::addApplicationFont(fi.absoluteFilePath());
}
@ -141,7 +144,6 @@ int main(int argc, char *argv[])
qmlRegisterType<NfcThingActionWriter>("Nymea", 1, 0, "NfcThingActionWriter");
qmlRegisterSingletonType<PushNotifications>("Nymea", 1, 0, "PushNotifications", PushNotifications::pushNotificationsProvider);
qmlRegisterSingletonType<AppLogController>("Nymea", 1, 0, "AppLogController", AppLogController::appLogControllerProvider);
qmlRegisterSingletonType(QUrl("qrc:///ui/utils/NymeaUtils.qml"), "Nymea", 1, 0, "NymeaUtils" );
engine->rootContext()->setContextProperty("appVersion", APP_VERSION);

View File

@ -24,7 +24,6 @@ HEADERS += \
pushnotifications.h \
platformhelper.h \
platformintegration/generic/platformhelpergeneric.h \
applogcontroller.h \
ruletemplates/messages.h
SOURCES += main.cpp \
@ -37,7 +36,6 @@ SOURCES += main.cpp \
pushnotifications.cpp \
platformhelper.cpp \
platformintegration/generic/platformhelpergeneric.cpp \
applogcontroller.cpp
RESOURCES += resources.qrc \
ruletemplates.qrc \

View File

@ -32,6 +32,8 @@
#include <QApplication>
#include <QClipboard>
#include <QDesktopServices>
#include <QUrl>
#if defined Q_OS_ANDROID
#include <QtAndroidExtras/QtAndroid>
@ -44,6 +46,9 @@
#include "platformintegration/generic/platformhelpergeneric.h"
#endif
#include "logging.h"
NYMEA_LOGGING_CATEGORY(dcPlatformIntegration, "PlatformIntegration")
PlatformHelper* PlatformHelper::s_instance = nullptr;
PlatformHelper::PlatformHelper(QObject *parent) : QObject(parent)
@ -183,6 +188,11 @@ QString PlatformHelper::fromClipBoard()
return QApplication::clipboard()->text();
}
void PlatformHelper::shareFile(const QString &fileName)
{
QDesktopServices::openUrl(QUrl(fileName));
}
QObject *PlatformHelper::platformHelperProvider(QQmlEngine *engine, QJSEngine *scriptEngine)
{
Q_UNUSED(engine)

View File

@ -91,6 +91,8 @@ public:
Q_INVOKABLE virtual void toClipBoard(const QString &text);
Q_INVOKABLE virtual QString fromClipBoard();
Q_INVOKABLE virtual void shareFile(const QString &fileName);
static QObject *platformHelperProvider(QQmlEngine *engine, QJSEngine *scriptEngine);
signals:
void permissionsRequestFinished();

View File

@ -1,4 +1,7 @@
package io.guh.nymeaapp;
import java.io.File;
import android.util.Log;
import android.content.Intent;
import android.content.Context;
@ -7,6 +10,8 @@ import android.os.Build;
import android.telephony.TelephonyManager;
import android.provider.Settings.Secure;
import android.os.Vibrator;
import android.net.Uri;
import android.support.v4.content.FileProvider;
public class NymeaAppActivity extends org.qtproject.qt5.android.bindings.QtActivity
{
@ -48,4 +53,17 @@ public class NymeaAppActivity extends org.qtproject.qt5.android.bindings.QtActiv
Vibrator v = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
v.vibrate(duration);
}
public void shareFile(String fileName) {
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.setType("text/plain");
Uri uri = FileProvider.getUriForFile(getApplicationContext(), getPackageName() + ".fileprovider", new File(fileName));
sendIntent.putExtra(Intent.EXTRA_STREAM, uri);
if (sendIntent.resolveActivity(getPackageManager()) != null) {
startActivity(sendIntent);
} else {
Log.d(TAG, "Intent not resolved");
}
}
}

View File

@ -175,7 +175,14 @@ void PlatformHelperAndroid::setBottomPanelColor(const QColor &color)
PlatformHelper::setBottomPanelColor(color);
if (QtAndroid::androidSdkVersion() < 21)
return;
return;
}
void PlatformHelperAndroid::shareFile(const QString &fileName)
{
QtAndroid::androidActivity().callMethod<void>("shareFile", "(Ljava/lang/String;)V",
QAndroidJniObject::fromString(fileName).object<jstring>()
);
}
void PlatformHelperAndroid::permissionRequestFinished(const QtAndroid::PermissionResultMap &result)

View File

@ -62,6 +62,8 @@ public:
void setTopPanelTheme(Theme theme);
void setBottomPanelColor(const QColor &color) override;
void shareFile(const QString &fileName) override;
private:
static void permissionRequestFinished(const QtAndroid::PermissionResultMap &);
};

View File

@ -30,6 +30,10 @@
#include "platformhelpergeneric.h"
#include "logging.h"
Q_DECLARE_LOGGING_CATEGORY(dcPlatformIntergration)
PlatformHelperGeneric::PlatformHelperGeneric(QObject *parent) : PlatformHelper(parent)
{
m_piHelper = new ScreenHelper(this);

View File

@ -36,32 +36,35 @@
#include <QSettings>
#include <QFileInfo>
#include <QDir>
#include <QLoggingCategory>
Q_DECLARE_LOGGING_CATEGORY(dcPlatformIntegration)
ScreenHelper::ScreenHelper(QObject *parent) : QObject(parent)
{
// Try generic backlight
QDir backlightDir("/sys/class/backlight");
foreach (const QFileInfo &fi, backlightDir.entryInfoList({"*_backlight"}, QDir::Dirs)) {
qDebug() << "Checking backlight directory:" << fi.absoluteFilePath();
qCDebug(dcPlatformIntegration()) << "Checking backlight directory:" << fi.absoluteFilePath();
m_powerFile.setFileName(fi.absoluteFilePath() + "/bl_power");
m_brightnessFile.setFileName(fi.absoluteFilePath() + "/brightness");
if (!m_powerFile.open(QFile::ReadWrite | QFile::Text)) {
qWarning() << "Cannot open" << m_powerFile.fileName() << "for writing";
qCDebug(dcPlatformIntegration()) << "Cannot open" << m_powerFile.fileName() << "for writing";
continue;
}
if (!m_brightnessFile.open(QFile::ReadWrite | QFile::Text)) {
qWarning() << "Cannot open" << m_brightnessFile.fileName() << "for writing";
qCDebug(dcPlatformIntegration()) << "Cannot open" << m_brightnessFile.fileName() << "for writing";
continue;
}
QFile maxBrightnessFile(fi.absoluteFilePath() + "/max_brightness");
if (!maxBrightnessFile.open(QFile::ReadOnly)) {
qWarning() << "Cannot open" << m_brightnessFile.fileName() << "for reading";
qCDebug(dcPlatformIntegration()) << "Cannot open" << m_brightnessFile.fileName() << "for reading";
continue;
}
bool ok;
m_maxBrightness = maxBrightnessFile.readLine().trimmed().toInt(&ok);
if (!ok) {
qWarning() << "Error reading max brightness value from" << maxBrightnessFile.fileName();
qCDebug(dcPlatformIntegration()) << "Error reading max brightness value from" << maxBrightnessFile.fileName();
m_maxBrightness = -1;
continue;
}
@ -70,15 +73,15 @@ ScreenHelper::ScreenHelper(QObject *parent) : QObject(parent)
}
if (!m_powerFile.isOpen() || !m_brightnessFile.isOpen()) {
qWarning() << "No backlight support on this platform";
qCInfo(dcPlatformIntegration()) << "No backlight support on this platform";
return;
}
qDebug() << "Backlight control enabled on" << m_powerFile.fileName();
qCInfo(dcPlatformIntegration()) << "Backlight control enabled on" << m_powerFile.fileName();
bool ok;
int currentBrightness = m_brightnessFile.readLine().trimmed().toInt(&ok);
m_currentBrightness = currentBrightness * 100 / m_maxBrightness;
qDebug().nospace() << "Brigness: Absolute: " << currentBrightness << "/" << m_maxBrightness << " Percentage:" << m_currentBrightness;
qCInfo(dcPlatformIntegration()).nospace() << "Brigness: Absolute: " << currentBrightness << "/" << m_maxBrightness << " Percentage:" << m_currentBrightness;
screenOn();
@ -190,20 +193,20 @@ bool ScreenHelper::eventFilter(QObject *watched, QEvent *event)
void ScreenHelper::screenOn()
{
qDebug() << "Turning screen on";
qCInfo(dcPlatformIntegration()) << "Turning screen on";
int ret = m_powerFile.write("0\n");
m_powerFile.flush();
if (ret < 0) {
qWarning() << "Failed to power on screen";
qCWarning(dcPlatformIntegration()) << "Failed to power on screen";
}
}
void ScreenHelper::screenOff()
{
qDebug() << "Turning screen off";
qCInfo(dcPlatformIntegration()) << "Turning screen off";
int ret = m_powerFile.write("1\n");
m_powerFile.flush();
if (ret < 0) {
qWarning() << "Failed to power off screen";
qCWarning(dcPlatformIntegration()) << "Failed to power off screen";
}
}

View File

@ -57,6 +57,8 @@ public:
void setTopPanelColor(const QColor &color) override;
void setBottomPanelColor(const QColor &color) override;
void shareFile(const QString &fileName) override;
private:
// defined in platformhelperios.mm
QString readKeyChainEntry(const QString &service, const QString &key);

View File

@ -232,5 +232,6 @@
<file>ui/customviews/ThermostatController.qml</file>
<file>ui/devicepages/ThermostatDevicePage.qml</file>
<file>ui/components/BigThingTile.qml</file>
<file>ui/appsettings/LoggingCategories.qml</file>
</qresource>
</RCC>

View File

@ -53,7 +53,7 @@ Drawer {
}
Label {
Layout.fillWidth: true
text: root.currentEngine.jsonRpcClient.currentConnection.url
text: root.currentEngine.jsonRpcClient.currentConnection ? root.currentEngine.jsonRpcClient.currentConnection.url : ""
font.pixelSize: app.extraSmallFont
enabled: false
}

View File

@ -110,6 +110,12 @@ ApplicationWindow {
value: settings.units === "metric" ? Types.UnitSystemMetric : Types.UnitSystemImperial
}
Binding {
target: AWSClient
property: "config"
value: "cloudEnvironment" in app ? app.cloudEnvironment : settings.cloudEnvironment
}
property alias mainMenu: m
MainMenu {
id: m

View File

@ -157,12 +157,6 @@ Item {
property int connectionTabIndex: index
// onConnectionTabIndexChanged: tabSettings.lastConnectedHost = engine.jsonRpcClient.url
Binding {
target: AWSClient
property: "config"
value: "cloudEnvironment" in app ? app.cloudEnvironment : settings.cloudEnvironment
}
Binding {
target: nymeaDiscovery
property: "discovering"
@ -469,6 +463,7 @@ Item {
}
RowLayout {
Layout.fillWidth: true
visible: settings.showConnectionTabs
spacing: 0
@ -477,6 +472,7 @@ Item {
Layout.fillWidth: true
Material.elevation: 2
position: TabBar.Footer
property int tabWidth: Math.max(150, root.width / tabModel.count)
Repeater {
model: tabModel.count
@ -486,7 +482,7 @@ Item {
property var engine: mainRepeater.itemAt(index)._engine
property string serverName: engine.nymeaConfiguration.serverName
Material.elevation: index
width: Math.max(150, tabbar.width / tabModel.count)
width: tabbar.tabWidth
Rectangle {
anchors.fill: parent

View File

@ -17,10 +17,39 @@ Item {
property color tileOverlayIconColor: iconColor
property int tileRadius: 6
readonly property int smallMargins: 8
readonly property int margins: 16
readonly property int smallDelegateHeight: 50
readonly property int delegateHeight: 60
// Note: Font files need to be provided in a "fonts" folder in the style
property string fontFamily: "Ubuntu"
// Fonts
readonly property font extraSmallFont: Qt.font({
family: "Ubuntu",
pixelSize: 10
})
readonly property font smallFont: Qt.font({
family: "Ubuntu",
pixelSize: 13
})
readonly property font font: Qt.font({
family: "Ubuntu",
pixelSize: 16
})
readonly property font largeFont: Qt.font({
family: "Ubuntu",
pixelSize: 20
})
readonly property font hugeFont: Qt.font({
family: "Ubuntu",
pixelSize: 40
})
// Icon/graph colors for various interfaces
property var interfaceColors: {
"temperaturesensor": "red",

View File

@ -36,12 +36,15 @@ import "../components"
Page {
header: NymeaHeader {
text: qsTr("App log")
text: qsTr("Application logs")
backButtonVisible: true
onBackPressed: pageStack.pop()
HeaderButton {
imageSource: "../images/edit-copy.svg"
onClicked: AppLogController.toClipboard()
imageSource: "../images/state-out.svg"
onClicked: {
var exportedFile = AppLogController.exportLogs()
PlatformHelper.shareFile(exportedFile)
}
}
}
@ -51,14 +54,29 @@ Page {
ScrollBar.vertical: ScrollBar {}
model: AppLogController
delegate: Text {
model: LogMessages {
}
delegate: Label {
width: listView.width
maximumLineCount: 2
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
text: model.text
color: model.type === AppLogController.TypeWarning ? "red" : Style.foregroundColor
font.pixelSize: app.smallFont
color: {
switch (model.level) {
case AppLogController.LogLevelCritical:
return "red";
case AppLogController.LogLevelWarning:
return "orange";
case AppLogController.LogLevelInfo:
return Style.foregroundColor;
case AppLogController.LogLevelDebug:
return Qt.tint(Style.foregroundColor, Qt.rgba(Style.backgroundColor.r, Style.backgroundColor.g, Style.backgroundColor.b, .4))
}
}
font: Style.smallFont
}
}
}

View File

@ -42,21 +42,25 @@ SettingsPageBase {
text: qsTr("Logging")
}
CheckDelegate {
text: qsTr("Enable app logging")
enabled: AppLogController.canWriteLogs
SwitchDelegate {
text: qsTr("Application logs enabled")
checked: AppLogController.enabled
onCheckedChanged: AppLogController.enabled = checked;
onCheckedChanged: AppLogController.enabled = checked
Layout.fillWidth: true
}
NymeaSwipeDelegate {
NymeaItemDelegate {
Layout.fillWidth: true
text: qsTr("View log")
text: qsTr("View live log")
onClicked: pageStack.push(Qt.resolvedUrl("../appsettings/AppLogPage.qml"))
enabled: AppLogController.enabled
visible: AppLogController.enabled
}
NymeaItemDelegate {
Layout.fillWidth: true
text: qsTr("Configure logging categories")
onClicked: pageStack.push(Qt.resolvedUrl("../appsettings/LoggingCategories.qml"))
}
SettingsPageSectionHeader {
text: qsTr("Advanced options")

View File

@ -0,0 +1,130 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2020, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of nymea.
* This project including source code and documentation is protected by
* copyright law, and remains the property of nymea GmbH. All rights, including
* reproduction, publication, editing and translation, are reserved. The use of
* this project is subject to the terms of a license agreement to be concluded
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
* under https://nymea.io/license
*
* GNU General Public License Usage
* Alternatively, this project may be redistributed and/or modified under the
* terms of the GNU General Public License as published by the Free Software
* Foundation, GNU version 3. This project 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 project. If not, see <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
import QtQuick 2.9
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import Nymea 1.0
import "../components"
SettingsPageBase {
header: NymeaHeader {
text: qsTr("Logging categories")
backButtonVisible: true
onBackPressed: pageStack.pop()
}
RowLayout {
Layout.margins: Style.margins
Item {
Layout.fillWidth: true
}
Label {
Layout.preferredWidth: Style.smallDelegateHeight
horizontalAlignment: Text.AlignHCenter
text: qsTr("Critical")
elide: Text.ElideRight
font: Style.smallFont
}
Label {
Layout.preferredWidth: Style.smallDelegateHeight
horizontalAlignment: Text.AlignHCenter
text: qsTr("Warning")
elide: Text.ElideRight
font: Style.smallFont
}
Label {
Layout.preferredWidth: Style.smallDelegateHeight
horizontalAlignment: Text.AlignHCenter
text: qsTr("Info")
elide: Text.ElideRight
font: Style.smallFont
}
Label {
Layout.preferredWidth: Style.smallDelegateHeight
horizontalAlignment: Text.AlignHCenter
text: qsTr("Debug")
elide: Text.ElideRight
font: Style.smallFont
}
}
ThinDivider {}
Repeater {
model: AppLogController.loggingCategories
delegate: ItemDelegate {
Layout.fillWidth: true
Layout.preferredHeight: Style.smallDelegateHeight
contentItem: RowLayout {
height: parent.height
Label {
Layout.fillWidth: true
text: model.name
elide: Text.ElideRight
}
RadioButton {
Layout.fillHeight: true
Layout.preferredWidth: Style.smallDelegateHeight
checked: model.logLevel === AppLogController.LogLevelCritical
onClicked: AppLogController.setLogLevel(model.name, AppLogController.LogLevelCritical)
}
RadioButton {
Layout.fillHeight: true
Layout.preferredWidth: Style.smallDelegateHeight
checked: model.logLevel === AppLogController.LogLevelWarning
onClicked: AppLogController.setLogLevel(model.name, AppLogController.LogLevelWarning)
}
RadioButton {
Layout.fillHeight: true
Layout.preferredWidth: Style.smallDelegateHeight
checked: model.logLevel === AppLogController.LogLevelInfo
onClicked: AppLogController.setLogLevel(model.name, AppLogController.LogLevelInfo)
}
RadioButton {
Layout.fillHeight: true
Layout.preferredWidth: Style.smallDelegateHeight
checked: model.logLevel === AppLogController.LogLevelDebug
onClicked: AppLogController.setLogLevel(model.name, AppLogController.LogLevelDebug)
}
// Slider {
// from: 0
// to: 3
// stepSize: 1
// Layout.preferredWidth: 200
// value: model.logLevel
// onMoved: {
// AppLogController.setLogLevel(model.name, value)
// }
// }
}
}
}
}

View File

@ -41,7 +41,6 @@ import Nymea 1.0
ItemDelegate {
id: root
implicitWidth: 200
property string subText
property bool progressive: true

View File

@ -158,6 +158,10 @@ MainPageTile {
rightMargin: app.margins / 2
}
sourceComponent: {
if (!root.iface) {
return ""
}
switch (iface.name) {
case "sensor":
case "weather":

View File

@ -182,9 +182,9 @@ MainViewBase {
ValueAxis {
id: yAxis
readonly property XYSeriesAdapter adapter: consumersRepeater.itemAt(consumersRepeater.count - 1).adapter;
max: Math.ceil(Math.max(adapter.maxValue * 0.95, adapter.maxValue * 1.05))
min: Math.floor(Math.min(adapter.minValue * 0.95, adapter.minValue * 1.05))
readonly property XYSeriesAdapter adapter: consumersRepeater.count > 0 ? consumersRepeater.itemAt(consumersRepeater.count - 1).adapter : null
max: adapter ? Math.ceil(Math.max(adapter.maxValue * 0.95, adapter.maxValue * 1.05)) : 1
min: adapter ? Math.floor(Math.min(adapter.minValue * 0.95, adapter.minValue * 1.05)) : 0
// This seems to crash occationally
// onMinChanged: applyNiceNumbers();
// onMaxChanged: applyNiceNumbers();

View File

@ -1,6 +1,5 @@
pragma Singleton
import QtQuick 2.9
import Nymea 1.0
Item {
id: root

View File

@ -142,6 +142,16 @@
</intent-filter>
</service>
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="io.guh.nymeaapp.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application>
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="29"/>

View File

@ -0,0 +1,3 @@
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<cache-path name="cache" path="." />
</paths>

View File

@ -128,3 +128,11 @@ void PlatformHelperIOS::setBottomPanelColorInternal(const QColor &color)
app.windows.firstObject.backgroundColor = [UIColor colorWithRed:color.redF() green:color.greenF() blue:color.blueF() alpha:color.alphaF()];
}
void PlatformHelperIOS::shareFile(const QString &fileName)
{
UIActivityViewController *activityController = [[UIActivityViewController alloc] initWithActivityItems:@[[NSURL fileURLWithPath:fileName.toNSString()]] applicationActivities:nil];
UIViewController *qtController = [[UIApplication sharedApplication].keyWindow rootViewController];
[qtController presentViewController:activityController animated:YES completion:nil];
}