Add media and energy views
This commit is contained in:
parent
57d2986948
commit
0f9caa03df
@ -343,7 +343,7 @@ void DeviceManager::getConfiguredDevicesResponse(const QVariantMap ¶ms)
|
|||||||
QVariantList stateVariantList = deviceVariant.toMap().value("states").toList();
|
QVariantList stateVariantList = deviceVariant.toMap().value("states").toList();
|
||||||
foreach (const QVariant &stateMap, stateVariantList) {
|
foreach (const QVariant &stateMap, stateVariantList) {
|
||||||
QString stateTypeId = stateMap.toMap().value("stateTypeId").toString();
|
QString stateTypeId = stateMap.toMap().value("stateTypeId").toString();
|
||||||
StateType *st = device->deviceClass()->stateTypes()->getStateType(stateTypeId);
|
StateType *st = device->thingClass()->stateTypes()->getStateType(stateTypeId);
|
||||||
if (!st) {
|
if (!st) {
|
||||||
qWarning() << "Can't find a statetype for this state";
|
qWarning() << "Can't find a statetype for this state";
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@ -72,24 +72,24 @@ QVariant Devices::data(const QModelIndex &index, int role) const
|
|||||||
if (index.row() < 0 || index.row() >= m_devices.count())
|
if (index.row() < 0 || index.row() >= m_devices.count())
|
||||||
return QVariant();
|
return QVariant();
|
||||||
|
|
||||||
Device *device = m_devices.at(index.row());
|
Device *thing = m_devices.at(index.row());
|
||||||
switch (role) {
|
switch (role) {
|
||||||
case RoleName:
|
case RoleName:
|
||||||
return device->name();
|
return thing->name();
|
||||||
case RoleId:
|
case RoleId:
|
||||||
return device->id().toString();
|
return thing->id().toString();
|
||||||
case RoleDeviceClass:
|
case RoleDeviceClass:
|
||||||
return device->deviceClassId().toString();
|
return thing->deviceClassId().toString();
|
||||||
case RoleParentDeviceId:
|
case RoleParentDeviceId:
|
||||||
return device->parentDeviceId().toString();
|
return thing->parentDeviceId().toString();
|
||||||
case RoleSetupStatus:
|
case RoleSetupStatus:
|
||||||
return device->setupStatus();
|
return thing->setupStatus();
|
||||||
case RoleSetupDisplayMessage:
|
case RoleSetupDisplayMessage:
|
||||||
return device->setupDisplayMessage();
|
return thing->setupDisplayMessage();
|
||||||
case RoleInterfaces:
|
case RoleInterfaces:
|
||||||
return device->deviceClass()->interfaces();
|
return thing->thingClass()->interfaces();
|
||||||
case RoleBaseInterface:
|
case RoleBaseInterface:
|
||||||
return device->deviceClass()->baseInterface();
|
return thing->thingClass()->baseInterface();
|
||||||
}
|
}
|
||||||
return QVariant();
|
return QVariant();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -592,7 +592,7 @@ void JsonRpcClient::helloReply(const QVariantMap ¶ms)
|
|||||||
|
|
||||||
qDebug() << "Handshake reply:" << "Protocol version:" << protoVersionString << "InitRequired:" << m_initialSetupRequired << "AuthRequired:" << m_authenticationRequired << "PushButtonAvailable:" << m_pushButtonAuthAvailable;;
|
qDebug() << "Handshake reply:" << "Protocol version:" << protoVersionString << "InitRequired:" << m_initialSetupRequired << "AuthRequired:" << m_authenticationRequired << "PushButtonAvailable:" << m_pushButtonAuthAvailable;;
|
||||||
|
|
||||||
QVersionNumber minimumRequiredVersion = QVersionNumber(1, 0);
|
QVersionNumber minimumRequiredVersion = QVersionNumber(1, 10);
|
||||||
if (m_jsonRpcVersion < minimumRequiredVersion) {
|
if (m_jsonRpcVersion < minimumRequiredVersion) {
|
||||||
qWarning() << "Nymea core doesn't support minimum required version. Required:" << minimumRequiredVersion << "Found:" << m_jsonRpcVersion;
|
qWarning() << "Nymea core doesn't support minimum required version. Required:" << minimumRequiredVersion << "Found:" << m_jsonRpcVersion;
|
||||||
m_connection->disconnect();
|
m_connection->disconnect();
|
||||||
|
|||||||
@ -65,7 +65,8 @@
|
|||||||
#include "types/browseritem.h"
|
#include "types/browseritem.h"
|
||||||
#include "models/logsmodel.h"
|
#include "models/logsmodel.h"
|
||||||
#include "models/logsmodelng.h"
|
#include "models/logsmodelng.h"
|
||||||
#include "models/valuelogsproxymodel.h"
|
#include "models/barseriesadapter.h"
|
||||||
|
#include "models/xyseriesadapter.h"
|
||||||
#include "models/interfacesproxy.h"
|
#include "models/interfacesproxy.h"
|
||||||
#include "configuration/nymeaconfiguration.h"
|
#include "configuration/nymeaconfiguration.h"
|
||||||
#include "configuration/serverconfiguration.h"
|
#include "configuration/serverconfiguration.h"
|
||||||
@ -246,8 +247,9 @@ void registerQmlTypes() {
|
|||||||
|
|
||||||
qmlRegisterType<LogsModel>(uri, 1, 0, "LogsModel");
|
qmlRegisterType<LogsModel>(uri, 1, 0, "LogsModel");
|
||||||
qmlRegisterType<LogsModelNg>(uri, 1, 0, "LogsModelNg");
|
qmlRegisterType<LogsModelNg>(uri, 1, 0, "LogsModelNg");
|
||||||
qmlRegisterType<ValueLogsProxyModel>(uri, 1, 0, "ValueLogsProxyModel");
|
|
||||||
qmlRegisterUncreatableType<LogEntry>(uri, 1, 0, "LogEntry", "Get them from LogsModel");
|
qmlRegisterUncreatableType<LogEntry>(uri, 1, 0, "LogEntry", "Get them from LogsModel");
|
||||||
|
qmlRegisterType<BarSeriesAdapter>(uri, 1, 0, "BarSeriesAdapter");
|
||||||
|
qmlRegisterType<XYSeriesAdapter>(uri, 1, 0, "XYSeriesAdapter");
|
||||||
|
|
||||||
qmlRegisterUncreatableType<TagsManager>(uri, 1, 0, "TagsManager", "Get it from Engine");
|
qmlRegisterUncreatableType<TagsManager>(uri, 1, 0, "TagsManager", "Get it from Engine");
|
||||||
qmlRegisterUncreatableType<Tags>(uri, 1, 0, "Tags", "Get it from TagsManager");
|
qmlRegisterUncreatableType<Tags>(uri, 1, 0, "Tags", "Get it from TagsManager");
|
||||||
|
|||||||
@ -27,7 +27,9 @@ INCLUDEPATH += $$top_srcdir/QtZeroConf
|
|||||||
SOURCES += \
|
SOURCES += \
|
||||||
configuration/networkmanager.cpp \
|
configuration/networkmanager.cpp \
|
||||||
engine.cpp \
|
engine.cpp \
|
||||||
|
models/barseriesadapter.cpp \
|
||||||
models/sortfilterproxymodel.cpp \
|
models/sortfilterproxymodel.cpp \
|
||||||
|
models/xyseriesadapter.cpp \
|
||||||
ruletemplates/calendaritemtemplate.cpp \
|
ruletemplates/calendaritemtemplate.cpp \
|
||||||
ruletemplates/timedescriptortemplate.cpp \
|
ruletemplates/timedescriptortemplate.cpp \
|
||||||
ruletemplates/timeeventitemtemplate.cpp \
|
ruletemplates/timeeventitemtemplate.cpp \
|
||||||
@ -128,7 +130,6 @@ SOURCES += \
|
|||||||
rulemanager.cpp \
|
rulemanager.cpp \
|
||||||
models/rulesfiltermodel.cpp \
|
models/rulesfiltermodel.cpp \
|
||||||
models/logsmodel.cpp \
|
models/logsmodel.cpp \
|
||||||
models/valuelogsproxymodel.cpp \
|
|
||||||
logmanager.cpp \
|
logmanager.cpp \
|
||||||
wifisetup/bluetoothdevice.cpp \
|
wifisetup/bluetoothdevice.cpp \
|
||||||
wifisetup/bluetoothdeviceinfo.cpp \
|
wifisetup/bluetoothdeviceinfo.cpp \
|
||||||
@ -163,7 +164,9 @@ SOURCES += \
|
|||||||
HEADERS += \
|
HEADERS += \
|
||||||
configuration/networkmanager.h \
|
configuration/networkmanager.h \
|
||||||
engine.h \
|
engine.h \
|
||||||
|
models/barseriesadapter.h \
|
||||||
models/sortfilterproxymodel.h \
|
models/sortfilterproxymodel.h \
|
||||||
|
models/xyseriesadapter.h \
|
||||||
ruletemplates/calendaritemtemplate.h \
|
ruletemplates/calendaritemtemplate.h \
|
||||||
ruletemplates/timedescriptortemplate.h \
|
ruletemplates/timedescriptortemplate.h \
|
||||||
ruletemplates/timeeventitemtemplate.h \
|
ruletemplates/timeeventitemtemplate.h \
|
||||||
@ -265,7 +268,6 @@ HEADERS += \
|
|||||||
rulemanager.h \
|
rulemanager.h \
|
||||||
models/rulesfiltermodel.h \
|
models/rulesfiltermodel.h \
|
||||||
models/logsmodel.h \
|
models/logsmodel.h \
|
||||||
models/valuelogsproxymodel.h \
|
|
||||||
logmanager.h \
|
logmanager.h \
|
||||||
wifisetup/bluetoothdevice.h \
|
wifisetup/bluetoothdevice.h \
|
||||||
wifisetup/bluetoothdeviceinfo.h \
|
wifisetup/bluetoothdeviceinfo.h \
|
||||||
|
|||||||
203
libnymea-app/models/barseriesadapter.cpp
Normal file
203
libnymea-app/models/barseriesadapter.cpp
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
#include "barseriesadapter.h"
|
||||||
|
|
||||||
|
BarSeriesAdapter::BarSeriesAdapter(QObject *parent) : QObject(parent)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
LogsModel *BarSeriesAdapter::logsModel() const
|
||||||
|
{
|
||||||
|
return m_logsModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BarSeriesAdapter::setLogsModel(LogsModel *logsModel)
|
||||||
|
{
|
||||||
|
if (m_logsModel != logsModel) {
|
||||||
|
m_logsModel = logsModel;
|
||||||
|
emit logsModelChanged();
|
||||||
|
update();
|
||||||
|
connect(logsModel, &LogsModel::logEntryAdded, this, &BarSeriesAdapter::logEntryAdded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QtCharts::QAbstractBarSeries *BarSeriesAdapter::barSeries() const
|
||||||
|
{
|
||||||
|
return m_barSeries;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BarSeriesAdapter::setBarSeries(QtCharts::QAbstractBarSeries *barSeries)
|
||||||
|
{
|
||||||
|
if (m_barSeries != barSeries) {
|
||||||
|
m_barSeries = barSeries;
|
||||||
|
emit barSeriesChanged();
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BarSeriesAdapter::Interval BarSeriesAdapter::interval() const
|
||||||
|
{
|
||||||
|
return m_interval;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BarSeriesAdapter::setInterval(BarSeriesAdapter::Interval interval)
|
||||||
|
{
|
||||||
|
if (m_interval != interval) {
|
||||||
|
m_interval = interval;
|
||||||
|
emit intervalChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BarSeriesAdapter::update()
|
||||||
|
{
|
||||||
|
if (!m_barSeries || !m_logsModel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_set = new QtCharts::QBarSet(m_barSeries->name());
|
||||||
|
m_barSeries->append(m_set);
|
||||||
|
|
||||||
|
for (int i = 0; i < m_logsModel->rowCount(); i++) {
|
||||||
|
LogEntry *entry = m_logsModel->get(i);
|
||||||
|
qDebug() << "have entry" << entry->timestamp().toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BarSeriesAdapter::ensureSlots(const QDateTime &start, const QDateTime &end)
|
||||||
|
{
|
||||||
|
if (!m_barSeries || !m_logsModel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QDateTime startTime = start;
|
||||||
|
switch (m_interval) {
|
||||||
|
case IntervalMinutes:
|
||||||
|
startTime.setTime(QTime(startTime.time().hour(), startTime.time().minute()));
|
||||||
|
break;
|
||||||
|
case IntervalHours:
|
||||||
|
startTime.setTime(QTime(startTime.time().hour(), 0));
|
||||||
|
break;
|
||||||
|
case IntervalDays:
|
||||||
|
startTime.setTime(QTime(0, 0));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
QDateTime endTime = end;
|
||||||
|
if (!endTime.isValid()) {
|
||||||
|
endTime = QDateTime::currentDateTime();
|
||||||
|
}
|
||||||
|
endTime.setTime(QTime(endTime.time().hour(), endTime.time().minute()));
|
||||||
|
|
||||||
|
QDateTime oldestExistingSlot;
|
||||||
|
if (m_timeslots.isEmpty()) {
|
||||||
|
oldestExistingSlot = endTime;
|
||||||
|
} else {
|
||||||
|
oldestExistingSlot = m_timeslots.first().datetime;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startTime < oldestExistingSlot) {
|
||||||
|
long duration = oldestExistingSlot.toMSecsSinceEpoch() - startTime.toMSecsSinceEpoch();
|
||||||
|
long slotCount = duration / (m_interval * 1000);
|
||||||
|
qDebug() << "Need" << slotCount << "new slots appended";
|
||||||
|
|
||||||
|
for (int i = 0; i < slotCount; i++) {
|
||||||
|
QDateTime slotTime = oldestExistingSlot.addSecs(-m_interval * (i + 1));
|
||||||
|
// qDebug() << "Adding" << slotTime.toString();
|
||||||
|
TimeSlot timeslot;
|
||||||
|
timeslot.datetime = slotTime;
|
||||||
|
m_set->insert(0, 0);
|
||||||
|
m_timeslots.prepend(timeslot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QDateTime newestExistingSlot;
|
||||||
|
if (m_timeslots.isEmpty()) {
|
||||||
|
newestExistingSlot = startTime;
|
||||||
|
} else {
|
||||||
|
newestExistingSlot = m_timeslots.last().datetime;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (endTime > newestExistingSlot) {
|
||||||
|
long duration = endTime.toMSecsSinceEpoch() - newestExistingSlot.toMSecsSinceEpoch();
|
||||||
|
long slotCount = duration / (m_interval * 1000);
|
||||||
|
// qDebug() << "Need" << slotCount << "new slots prepended";
|
||||||
|
|
||||||
|
for (int i = 0; i < slotCount; i++) {
|
||||||
|
QDateTime slotTime = newestExistingSlot.addSecs(m_interval * (i + 1));
|
||||||
|
TimeSlot timeslot;
|
||||||
|
timeslot.datetime = slotTime;
|
||||||
|
m_set->append(0);
|
||||||
|
m_timeslots.append(timeslot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_timeslots.isEmpty()) {
|
||||||
|
// qDebug() << "Need to initialize list with 1 entry";
|
||||||
|
TimeSlot timeslot;
|
||||||
|
timeslot.datetime = startTime;
|
||||||
|
m_set->append(0);
|
||||||
|
m_timeslots.append(timeslot);
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Ensuring slots from" << start << "to" << end << "oldest" << oldestExistingSlot << "newest" << newestExistingSlot;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void BarSeriesAdapter::logEntryAdded(LogEntry *entry)
|
||||||
|
{
|
||||||
|
qDebug() << "****" << m_barSeries << m_logsModel;
|
||||||
|
if (!m_barSeries || !m_logsModel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ensureSlots(QDateTime::fromMSecsSinceEpoch(qMin(m_logsModel->startTime().toMSecsSinceEpoch(), entry->timestamp().toMSecsSinceEpoch())), QDateTime::fromMSecsSinceEpoch(qMax(m_logsModel->endTime().toMSecsSinceEpoch(), entry->timestamp().toMSecsSinceEpoch())));
|
||||||
|
|
||||||
|
QDateTime timestamp = entry->timestamp();
|
||||||
|
|
||||||
|
QDateTime timeSlotStart = timestamp;
|
||||||
|
switch (m_interval) {
|
||||||
|
case IntervalMinutes:
|
||||||
|
timeSlotStart.setTime(QTime(timestamp.time().hour(), timestamp.time().minute()));
|
||||||
|
break;
|
||||||
|
case IntervalHours:
|
||||||
|
timeSlotStart.setTime(QTime(timestamp.time().hour(), 0));
|
||||||
|
break;
|
||||||
|
case IntervalDays:
|
||||||
|
timeSlotStart.setTime(QTime(0, 0));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// qDebug() << "Item time:" << timeSlotStart;
|
||||||
|
|
||||||
|
TimeSlot first = m_timeslots.first();
|
||||||
|
TimeSlot last = m_timeslots.last();
|
||||||
|
long slotIdx = (timeSlotStart.toMSecsSinceEpoch() - first.datetime.toMSecsSinceEpoch()) / (m_interval * 1000);
|
||||||
|
qDebug() << "first" << first.datetime.toString();
|
||||||
|
qDebug() << "last" << last.datetime.toString();
|
||||||
|
qDebug() << "this" << timeSlotStart.toString();
|
||||||
|
qDebug() << "idx" << slotIdx;
|
||||||
|
|
||||||
|
|
||||||
|
m_timeslots[slotIdx].entries.append(entry);
|
||||||
|
m_set->replace(slotIdx, m_timeslots[slotIdx].value());
|
||||||
|
qDebug() << "Adding entry" << entry->timestamp() << "timestlot" << timeSlotStart << "at" << slotIdx << "value" << m_timeslots[slotIdx].value();
|
||||||
|
|
||||||
|
// if (!m_timeslots.contains(timeSlotStart)) {
|
||||||
|
// TimeSlot timeslot;
|
||||||
|
// timeslot.startTime = timeSlotStart;
|
||||||
|
|
||||||
|
|
||||||
|
// }
|
||||||
|
// TimeSlot timeslot = m_timeslots.value(timeSlotStart);
|
||||||
|
// timeslot.entries.append(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
qreal BarSeriesAdapter::TimeSlot::value() const
|
||||||
|
{
|
||||||
|
qreal value = 0;
|
||||||
|
foreach (LogEntry *entry, entries) {
|
||||||
|
value += entry->value().toDouble();
|
||||||
|
}
|
||||||
|
if (entries.count() > 1) {
|
||||||
|
value /= entries.count();
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
66
libnymea-app/models/barseriesadapter.h
Normal file
66
libnymea-app/models/barseriesadapter.h
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
#ifndef BARSERIESADAPTER_H
|
||||||
|
#define BARSERIESADAPTER_H
|
||||||
|
|
||||||
|
#include "logsmodel.h"
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QBarSeries>
|
||||||
|
#include <QBarSet>
|
||||||
|
|
||||||
|
class BarSeriesAdapter : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
Q_PROPERTY(LogsModel* logsModel READ logsModel WRITE setLogsModel NOTIFY logsModelChanged)
|
||||||
|
Q_PROPERTY(QtCharts::QAbstractBarSeries* barSeries READ barSeries WRITE setBarSeries NOTIFY barSeriesChanged)
|
||||||
|
|
||||||
|
Q_PROPERTY(Interval interval READ interval WRITE setInterval NOTIFY intervalChanged)
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum Interval {
|
||||||
|
IntervalMinutes = 60,
|
||||||
|
IntervalHours = 60 * 60,
|
||||||
|
IntervalDays = 24 * 60 * 60
|
||||||
|
};
|
||||||
|
Q_ENUM(Interval)
|
||||||
|
|
||||||
|
explicit BarSeriesAdapter(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
LogsModel *logsModel() const;
|
||||||
|
void setLogsModel(LogsModel *logsModel);
|
||||||
|
|
||||||
|
QtCharts::QAbstractBarSeries *barSeries() const;
|
||||||
|
void setBarSeries(QtCharts::QAbstractBarSeries *barSeries);
|
||||||
|
|
||||||
|
Interval interval() const;
|
||||||
|
void setInterval(Interval interval);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void logsModelChanged();
|
||||||
|
void barSeriesChanged();
|
||||||
|
void intervalChanged();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void update();
|
||||||
|
|
||||||
|
void ensureSlots(const QDateTime &start, const QDateTime &end);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void logEntryAdded(LogEntry *entry);
|
||||||
|
|
||||||
|
private:
|
||||||
|
class TimeSlot {
|
||||||
|
public:
|
||||||
|
QDateTime datetime;
|
||||||
|
QList<LogEntry*> entries;
|
||||||
|
qreal value() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
LogsModel *m_logsModel = nullptr;
|
||||||
|
QtCharts::QAbstractBarSeries *m_barSeries = nullptr;
|
||||||
|
QtCharts::QBarSet *m_set = nullptr;
|
||||||
|
Interval m_interval = IntervalMinutes;
|
||||||
|
|
||||||
|
QList<TimeSlot> m_timeslots;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // BARSERIESADAPTER_H
|
||||||
@ -49,35 +49,35 @@ QVariant DeviceModel::data(const QModelIndex &index, int role) const
|
|||||||
return m_list.at(index.row());
|
return m_list.at(index.row());
|
||||||
}
|
}
|
||||||
if (role == RoleType) {
|
if (role == RoleType) {
|
||||||
StateType* stateType = m_device->deviceClass()->stateTypes()->getStateType(m_list.at(index.row()));
|
StateType* stateType = m_device->thingClass()->stateTypes()->getStateType(m_list.at(index.row()));
|
||||||
if (stateType) {
|
if (stateType) {
|
||||||
return TypeStateType;
|
return TypeStateType;
|
||||||
}
|
}
|
||||||
ActionType* actionType = m_device->deviceClass()->actionTypes()->getActionType(m_list.at(index.row()));
|
ActionType* actionType = m_device->thingClass()->actionTypes()->getActionType(m_list.at(index.row()));
|
||||||
if (actionType) {
|
if (actionType) {
|
||||||
return TypeActionType;
|
return TypeActionType;
|
||||||
}
|
}
|
||||||
EventType* eventType = m_device->deviceClass()->eventTypes()->getEventType(m_list.at(index.row()));
|
EventType* eventType = m_device->thingClass()->eventTypes()->getEventType(m_list.at(index.row()));
|
||||||
if (eventType) {
|
if (eventType) {
|
||||||
return TypeEventType;
|
return TypeEventType;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (role == RoleDisplayName) {
|
if (role == RoleDisplayName) {
|
||||||
StateType* stateType = m_device->deviceClass()->stateTypes()->getStateType(m_list.at(index.row()));
|
StateType* stateType = m_device->thingClass()->stateTypes()->getStateType(m_list.at(index.row()));
|
||||||
if (stateType) {
|
if (stateType) {
|
||||||
return stateType->displayName();
|
return stateType->displayName();
|
||||||
}
|
}
|
||||||
ActionType* actionType = m_device->deviceClass()->actionTypes()->getActionType(m_list.at(index.row()));
|
ActionType* actionType = m_device->thingClass()->actionTypes()->getActionType(m_list.at(index.row()));
|
||||||
if (actionType) {
|
if (actionType) {
|
||||||
return actionType->displayName();
|
return actionType->displayName();
|
||||||
}
|
}
|
||||||
EventType* eventType = m_device->deviceClass()->eventTypes()->getEventType(m_list.at(index.row()));
|
EventType* eventType = m_device->thingClass()->eventTypes()->getEventType(m_list.at(index.row()));
|
||||||
if (eventType) {
|
if (eventType) {
|
||||||
return eventType->displayName();
|
return eventType->displayName();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (role == RoleWritable) {
|
if (role == RoleWritable) {
|
||||||
ActionType* actionType = m_device->deviceClass()->actionTypes()->getActionType(m_list.at(index.row()));
|
ActionType* actionType = m_device->thingClass()->actionTypes()->getActionType(m_list.at(index.row()));
|
||||||
return actionType != nullptr;
|
return actionType != nullptr;
|
||||||
}
|
}
|
||||||
return QVariant();
|
return QVariant();
|
||||||
@ -169,22 +169,22 @@ void DeviceModel::updateList()
|
|||||||
beginResetModel();
|
beginResetModel();
|
||||||
m_list.clear();
|
m_list.clear();
|
||||||
if (m_showStates) {
|
if (m_showStates) {
|
||||||
for (int i = 0; i < m_device->deviceClass()->stateTypes()->rowCount(); i++) {
|
for (int i = 0; i < m_device->thingClass()->stateTypes()->rowCount(); i++) {
|
||||||
m_list.append(m_device->deviceClass()->stateTypes()->get(i)->id());
|
m_list.append(m_device->thingClass()->stateTypes()->get(i)->id());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_showActions) {
|
if (m_showActions) {
|
||||||
for (int i = 0; i < m_device->deviceClass()->actionTypes()->rowCount(); i++) {
|
for (int i = 0; i < m_device->thingClass()->actionTypes()->rowCount(); i++) {
|
||||||
if (!m_list.contains(m_device->deviceClass()->actionTypes()->get(i)->id())) {
|
if (!m_list.contains(m_device->thingClass()->actionTypes()->get(i)->id())) {
|
||||||
m_list.append(m_device->deviceClass()->actionTypes()->get(i)->id());
|
m_list.append(m_device->thingClass()->actionTypes()->get(i)->id());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (m_showEvents) {
|
if (m_showEvents) {
|
||||||
for (int i = 0; i < m_device->deviceClass()->eventTypes()->rowCount(); i++) {
|
for (int i = 0; i < m_device->thingClass()->eventTypes()->rowCount(); i++) {
|
||||||
if (!m_list.contains(m_device->deviceClass()->eventTypes()->get(i)->id())) {
|
if (!m_list.contains(m_device->thingClass()->eventTypes()->get(i)->id())) {
|
||||||
m_list.append(m_device->deviceClass()->eventTypes()->get(i)->id());
|
m_list.append(m_device->thingClass()->eventTypes()->get(i)->id());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -103,11 +103,11 @@ bool InterfacesProxy::filterAcceptsRow(int source_row, const QModelIndex &source
|
|||||||
bool found = false;
|
bool found = false;
|
||||||
for (int i = 0; i < m_devicesFilter->rowCount(); i++) {
|
for (int i = 0; i < m_devicesFilter->rowCount(); i++) {
|
||||||
Device *d = m_devicesFilter->get(i);
|
Device *d = m_devicesFilter->get(i);
|
||||||
if (!d->deviceClass()) {
|
if (!d->thingClass()) {
|
||||||
qWarning() << "Cannot find DeviceClass for device:" << d->id() << d->name();
|
qWarning() << "Cannot find DeviceClass for device:" << d->id() << d->name();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (d->deviceClass()->interfaces().contains(interfaceName)) {
|
if (d->thingClass()->interfaces().contains(interfaceName)) {
|
||||||
found = true;
|
found = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -121,11 +121,11 @@ bool InterfacesProxy::filterAcceptsRow(int source_row, const QModelIndex &source
|
|||||||
bool found = false;
|
bool found = false;
|
||||||
for (int i = 0; i < m_devicesProxyFilter->rowCount(); i++) {
|
for (int i = 0; i < m_devicesProxyFilter->rowCount(); i++) {
|
||||||
Device *d = m_devicesProxyFilter->get(i);
|
Device *d = m_devicesProxyFilter->get(i);
|
||||||
if (!d->deviceClass()) {
|
if (!d->thingClass()) {
|
||||||
qWarning() << "Cannot find DeviceClass for device:" << d->id() << d->name();
|
qWarning() << "Cannot find ThingClass for thing:" << d->id() << d->name();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (d->deviceClass()->interfaces().contains(interfaceName)) {
|
if (d->thingClass()->interfaces().contains(interfaceName)) {
|
||||||
found = true;
|
found = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,14 +29,18 @@
|
|||||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||||
|
|
||||||
#include "logsmodel.h"
|
#include "logsmodel.h"
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QMetaEnum>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
|
||||||
#include "engine.h"
|
#include "engine.h"
|
||||||
|
#include "types/logentry.h"
|
||||||
#include "logmanager.h"
|
#include "logmanager.h"
|
||||||
|
|
||||||
#include <QMetaEnum>
|
|
||||||
|
|
||||||
LogsModel::LogsModel(QObject *parent) : QAbstractListModel(parent)
|
LogsModel::LogsModel(QObject *parent) : QAbstractListModel(parent)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Engine *LogsModel::engine() const
|
Engine *LogsModel::engine() const
|
||||||
@ -53,11 +57,6 @@ void LogsModel::setEngine(Engine *engine)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool LogsModel::busy() const
|
|
||||||
{
|
|
||||||
return m_busy;
|
|
||||||
}
|
|
||||||
|
|
||||||
int LogsModel::rowCount(const QModelIndex &parent) const
|
int LogsModel::rowCount(const QModelIndex &parent) const
|
||||||
{
|
{
|
||||||
Q_UNUSED(parent)
|
Q_UNUSED(parent)
|
||||||
@ -72,6 +71,7 @@ QVariant LogsModel::data(const QModelIndex &index, int role) const
|
|||||||
case RoleValue:
|
case RoleValue:
|
||||||
return m_list.at(index.row())->value();
|
return m_list.at(index.row())->value();
|
||||||
case RoleThingId:
|
case RoleThingId:
|
||||||
|
case RoleDeviceId:
|
||||||
return m_list.at(index.row())->thingId();
|
return m_list.at(index.row())->thingId();
|
||||||
case RoleTypeId:
|
case RoleTypeId:
|
||||||
return m_list.at(index.row())->typeId();
|
return m_list.at(index.row())->typeId();
|
||||||
@ -88,13 +88,19 @@ QHash<int, QByteArray> LogsModel::roleNames() const
|
|||||||
QHash<int, QByteArray> roles;
|
QHash<int, QByteArray> roles;
|
||||||
roles.insert(RoleTimestamp, "timestamp");
|
roles.insert(RoleTimestamp, "timestamp");
|
||||||
roles.insert(RoleValue, "value");
|
roles.insert(RoleValue, "value");
|
||||||
roles.insert(RoleThingId, "deviceId");
|
roles.insert(RoleThingId, "thingId");
|
||||||
|
roles.insert(RoleDeviceId, "deviceId");
|
||||||
roles.insert(RoleTypeId, "typeId");
|
roles.insert(RoleTypeId, "typeId");
|
||||||
roles.insert(RoleSource, "source");
|
roles.insert(RoleSource, "source");
|
||||||
roles.insert(RoleLoggingEventType, "loggingEventType");
|
roles.insert(RoleLoggingEventType, "loggingEventType");
|
||||||
return roles;
|
return roles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool LogsModel::busy() const
|
||||||
|
{
|
||||||
|
return m_busy;
|
||||||
|
}
|
||||||
|
|
||||||
bool LogsModel::live() const
|
bool LogsModel::live() const
|
||||||
{
|
{
|
||||||
return m_live;
|
return m_live;
|
||||||
@ -108,29 +114,42 @@ void LogsModel::setLive(bool live)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QString LogsModel::deviceId() const
|
QUuid LogsModel::thingId() const
|
||||||
{
|
{
|
||||||
return m_deviceId;
|
return m_thingId;
|
||||||
}
|
}
|
||||||
|
|
||||||
void LogsModel::setDeviceId(const QString &deviceId)
|
void LogsModel::setThingId(const QUuid &thingId)
|
||||||
{
|
{
|
||||||
if (m_deviceId != deviceId) {
|
if (m_thingId != thingId) {
|
||||||
m_deviceId = deviceId;
|
m_thingId = thingId;
|
||||||
emit deviceIdChanged();
|
emit thingIdChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList LogsModel::typeIds() const
|
QStringList LogsModel::typeIds() const
|
||||||
{
|
{
|
||||||
return m_typeIds;
|
QStringList strings;
|
||||||
|
foreach (const QUuid &id, m_typeIds) {
|
||||||
|
strings.append(id.toString());
|
||||||
|
}
|
||||||
|
return strings;
|
||||||
}
|
}
|
||||||
|
|
||||||
void LogsModel::setTypeIds(const QStringList &typeIds)
|
void LogsModel::setTypeIds(const QStringList &typeIds)
|
||||||
{
|
{
|
||||||
if (m_typeIds != typeIds) {
|
QList<QUuid> fixedTypeIds;
|
||||||
m_typeIds = typeIds;
|
foreach (const QString &id, typeIds) {
|
||||||
|
fixedTypeIds.append(QUuid(id));
|
||||||
|
}
|
||||||
|
if (m_typeIds != fixedTypeIds) {
|
||||||
|
m_typeIds = fixedTypeIds;
|
||||||
emit typeIdsChanged();
|
emit typeIdsChanged();
|
||||||
|
beginResetModel();
|
||||||
|
qDeleteAll(m_list);
|
||||||
|
m_list.clear();
|
||||||
|
endResetModel();
|
||||||
|
fetchMore();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,6 +179,24 @@ void LogsModel::setEndTime(const QDateTime &endTime)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QDateTime LogsModel::viewStartTime() const
|
||||||
|
{
|
||||||
|
return m_viewStartTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogsModel::setViewStartTime(const QDateTime &viewStartTime)
|
||||||
|
{
|
||||||
|
if (m_viewStartTime != viewStartTime) {
|
||||||
|
m_viewStartTime = viewStartTime;
|
||||||
|
emit viewStartTimeChanged();
|
||||||
|
if (m_list.count() == 0 || m_list.last()->timestamp() > m_viewStartTime) {
|
||||||
|
if (m_canFetchMore) {
|
||||||
|
fetchMore();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LogEntry *LogsModel::get(int index) const
|
LogEntry *LogsModel::get(int index) const
|
||||||
{
|
{
|
||||||
if (index >= 0 && index < m_list.count()) {
|
if (index >= 0 && index < m_list.count()) {
|
||||||
@ -168,163 +205,172 @@ LogEntry *LogsModel::get(int index) const
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void LogsModel::notificationReceived(const QVariantMap &data)
|
|
||||||
{
|
|
||||||
qDebug() << "KLogModel notificatiion" << data;
|
|
||||||
}
|
|
||||||
|
|
||||||
void LogsModel::update()
|
|
||||||
{
|
|
||||||
if (!m_engine) {
|
|
||||||
qWarning() << "LogsModel: Can't update, no engine set";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (m_busy) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
m_busy = true;
|
|
||||||
emit busyChanged();
|
|
||||||
|
|
||||||
QVariantMap params;
|
|
||||||
if (!m_deviceId.isEmpty()) {
|
|
||||||
QVariantList deviceIds;
|
|
||||||
deviceIds.append(m_deviceId);
|
|
||||||
params.insert("deviceIds", deviceIds);
|
|
||||||
}
|
|
||||||
if (!m_typeIds.isEmpty()) {
|
|
||||||
QVariantList typeIds;
|
|
||||||
foreach (const QString &typeId, m_typeIds) {
|
|
||||||
typeIds.append(typeId);
|
|
||||||
}
|
|
||||||
params.insert("typeIds", typeIds);
|
|
||||||
}
|
|
||||||
QVariantList timeFilters;
|
|
||||||
QVariantMap timeFilter;
|
|
||||||
timeFilter.insert("startDate", m_startTime.toSecsSinceEpoch());
|
|
||||||
timeFilter.insert("endDate", m_endTime.toSecsSinceEpoch());
|
|
||||||
timeFilters.append(timeFilter);
|
|
||||||
params.insert("timeFilters", timeFilters);
|
|
||||||
m_engine->jsonRpcClient()->sendCommand("Logging.GetLogEntries", params, this, "logsReply");
|
|
||||||
}
|
|
||||||
|
|
||||||
void LogsModel::fetchEarlier(int hours)
|
|
||||||
{
|
|
||||||
if (!m_engine) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (m_busy) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
m_busy = true;
|
|
||||||
emit busyChanged();
|
|
||||||
|
|
||||||
QVariantMap params;
|
|
||||||
if (!m_deviceId.isEmpty()) {
|
|
||||||
QVariantList deviceIds;
|
|
||||||
deviceIds.append(m_deviceId);
|
|
||||||
params.insert("deviceIds", deviceIds);
|
|
||||||
}
|
|
||||||
if (!m_typeIds.isEmpty()) {
|
|
||||||
QVariantList typeIds;
|
|
||||||
foreach (const QString &typeId, m_typeIds) {
|
|
||||||
typeIds.append(typeId);
|
|
||||||
}
|
|
||||||
params.insert("typeIds", typeIds);
|
|
||||||
}
|
|
||||||
QVariantList timeFilters;
|
|
||||||
QVariantMap timeFilter;
|
|
||||||
timeFilter.insert("endDate", m_startTime.toSecsSinceEpoch());
|
|
||||||
m_startTime = m_startTime.addSecs(-60*60*hours);
|
|
||||||
timeFilter.insert("startDate", m_startTime.toSecsSinceEpoch());
|
|
||||||
timeFilters.append(timeFilter);
|
|
||||||
params.insert("timeFilters", timeFilters);
|
|
||||||
m_engine->jsonRpcClient()->sendCommand("Logging.GetLogEntries", params, this, "fetchEarlierReply");
|
|
||||||
}
|
|
||||||
|
|
||||||
void LogsModel::logsReply(const QVariantMap &data)
|
void LogsModel::logsReply(const QVariantMap &data)
|
||||||
{
|
{
|
||||||
// qDebug() << "logs reply" << data;
|
int offset = data.value("params").toMap().value("offset").toInt();
|
||||||
beginResetModel();
|
int count = data.value("params").toMap().value("count").toInt();
|
||||||
qDeleteAll(m_list);
|
|
||||||
m_list.clear();
|
|
||||||
|
|
||||||
|
// qDebug() << qUtf8Printable(QJsonDocument::fromVariant(data).toJson());
|
||||||
|
|
||||||
|
QList<LogEntry*> newBlock;
|
||||||
QList<QVariant> logEntries = data.value("params").toMap().value("logEntries").toList();
|
QList<QVariant> logEntries = data.value("params").toMap().value("logEntries").toList();
|
||||||
foreach (const QVariant &logEntryVariant, logEntries) {
|
foreach (const QVariant &logEntryVariant, logEntries) {
|
||||||
QVariantMap entryMap = logEntryVariant.toMap();
|
QVariantMap entryMap = logEntryVariant.toMap();
|
||||||
QDateTime timeStamp = QDateTime::fromMSecsSinceEpoch(entryMap.value("timestamp").toLongLong());
|
QDateTime timeStamp = QDateTime::fromMSecsSinceEpoch(entryMap.value("timestamp").toLongLong());
|
||||||
QString deviceId = entryMap.value("deviceId").toString();
|
QString thingId;
|
||||||
|
if (m_engine->jsonRpcClient()->ensureServerVersion("5.0")) {
|
||||||
|
thingId = entryMap.value("thingId").toString();
|
||||||
|
} else {
|
||||||
|
thingId = entryMap.value("deviceId").toString();
|
||||||
|
}
|
||||||
QString typeId = entryMap.value("typeId").toString();
|
QString typeId = entryMap.value("typeId").toString();
|
||||||
QMetaEnum sourceEnum = QMetaEnum::fromType<LogEntry::LoggingSource>();
|
QMetaEnum sourceEnum = QMetaEnum::fromType<LogEntry::LoggingSource>();
|
||||||
LogEntry::LoggingSource loggingSource = (LogEntry::LoggingSource)sourceEnum.keyToValue(entryMap.value("source").toByteArray());
|
LogEntry::LoggingSource loggingSource = static_cast<LogEntry::LoggingSource>(sourceEnum.keyToValue(entryMap.value("source").toByteArray()));
|
||||||
QMetaEnum loggingEventTypeEnum = QMetaEnum::fromType<LogEntry::LoggingEventType>();
|
QMetaEnum loggingEventTypeEnum = QMetaEnum::fromType<LogEntry::LoggingEventType>();
|
||||||
LogEntry::LoggingEventType loggingEventType = (LogEntry::LoggingEventType)loggingEventTypeEnum.keyToValue(entryMap.value("eventType").toByteArray());
|
LogEntry::LoggingEventType loggingEventType = static_cast<LogEntry::LoggingEventType>(loggingEventTypeEnum.keyToValue(entryMap.value("eventType").toByteArray()));
|
||||||
QVariant value = loggingEventType == LogEntry::LoggingEventTypeActiveChange ? entryMap.value("active").toBool() : entryMap.value("value");
|
QVariant value = loggingEventType == LogEntry::LoggingEventTypeActiveChange ? entryMap.value("active").toBool() : entryMap.value("value");
|
||||||
LogEntry *entry = new LogEntry(timeStamp, value, deviceId, typeId, loggingSource, loggingEventType, this);
|
LogEntry *entry = new LogEntry(timeStamp, value, thingId, typeId, loggingSource, loggingEventType, this);
|
||||||
m_list.append(entry);
|
newBlock.append(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
endResetModel();
|
// qDebug() << "Received logs from" << offset << "to" << offset + count << "Actual count:" << newBlock.count();
|
||||||
emit countChanged();
|
|
||||||
|
|
||||||
m_busy = false;
|
if (count < m_blockSize) {
|
||||||
emit busyChanged();
|
m_canFetchMore = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void LogsModel::fetchEarlierReply(const QVariantMap &data)
|
if (newBlock.isEmpty()) {
|
||||||
{
|
m_busy = false;
|
||||||
// qDebug() << "logs reply" << data;
|
emit busyChanged();
|
||||||
|
return;
|
||||||
QList<QVariant> logEntries = data.value("params").toMap().value("logEntries").toList();
|
}
|
||||||
QList<LogEntry*> newEntries;
|
|
||||||
foreach (const QVariant &logEntryVariant, logEntries) {
|
beginInsertRows(QModelIndex(), offset, offset + newBlock.count() - 1);
|
||||||
QVariantMap entryMap = logEntryVariant.toMap();
|
for (int i = 0; i < newBlock.count(); i++) {
|
||||||
QDateTime timeStamp = QDateTime::fromMSecsSinceEpoch(entryMap.value("timestamp").toLongLong());
|
LogEntry *entry = newBlock.at(i);
|
||||||
QString deviceId = entryMap.value("deviceId").toString();
|
m_list.insert(offset + i, entry);
|
||||||
QString typeId = entryMap.value("typeId").toString();
|
emit logEntryAdded(entry);
|
||||||
QMetaEnum sourceEnum = QMetaEnum::fromType<LogEntry::LoggingSource>();
|
|
||||||
LogEntry::LoggingSource loggingSource = (LogEntry::LoggingSource)sourceEnum.keyToValue(entryMap.value("source").toByteArray());
|
|
||||||
QMetaEnum loggingEventTypeEnum = QMetaEnum::fromType<LogEntry::LoggingEventType>();
|
|
||||||
LogEntry::LoggingEventType loggingEventType = (LogEntry::LoggingEventType)loggingEventTypeEnum.keyToValue(entryMap.value("eventType").toByteArray());
|
|
||||||
QVariant value = loggingEventType == LogEntry::LoggingEventTypeActiveChange ? entryMap.value("active").toBool() : entryMap.value("value");
|
|
||||||
LogEntry *entry = new LogEntry(timeStamp, value, deviceId, typeId, loggingSource, loggingEventType, this);
|
|
||||||
newEntries.append(entry);
|
|
||||||
}
|
}
|
||||||
beginInsertRows(QModelIndex(), 0, newEntries.count() - 1);
|
|
||||||
newEntries.append(m_list);
|
|
||||||
m_list = newEntries;
|
|
||||||
endInsertRows();
|
endInsertRows();
|
||||||
emit countChanged();
|
emit countChanged();
|
||||||
|
|
||||||
m_busy = false;
|
m_busy = false;
|
||||||
emit busyChanged();
|
emit busyChanged();
|
||||||
|
|
||||||
|
if (m_viewStartTime.isValid() && m_list.count() > 0 && m_list.last()->timestamp() > m_viewStartTime && m_canFetchMore) {
|
||||||
|
fetchMore();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogsModel::fetchMore(const QModelIndex &parent)
|
||||||
|
{
|
||||||
|
Q_UNUSED(parent)
|
||||||
|
|
||||||
|
if (!m_engine) {
|
||||||
|
qWarning() << "Cannot update. Engine not set";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (m_busy) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((!m_startTime.isNull() && m_endTime.isNull()) || (m_startTime.isNull() && !m_endTime.isNull())) {
|
||||||
|
qDebug() << "Need neither or both, startTime and endTime set";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_busy = true;
|
||||||
|
emit busyChanged();
|
||||||
|
|
||||||
|
|
||||||
|
QVariantMap params;
|
||||||
|
if (!m_thingId.isNull()) {
|
||||||
|
QVariantList thingIds;
|
||||||
|
thingIds.append(m_thingId);
|
||||||
|
if (m_engine->jsonRpcClient()->ensureServerVersion("5.0")) {
|
||||||
|
params.insert("thingIds", thingIds);
|
||||||
|
} else {
|
||||||
|
params.insert("deviceIds", thingIds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!m_typeIds.isEmpty()) {
|
||||||
|
QVariantList typeIds;
|
||||||
|
foreach (const QUuid &typeId, m_typeIds) {
|
||||||
|
typeIds.append(typeId);
|
||||||
|
}
|
||||||
|
params.insert("typeIds", typeIds);
|
||||||
|
}
|
||||||
|
if (!m_startTime.isNull() && !m_endTime.isNull()) {
|
||||||
|
QVariantList timeFilters;
|
||||||
|
QVariantMap timeFilter;
|
||||||
|
timeFilter.insert("startDate", m_startTime.toSecsSinceEpoch());
|
||||||
|
timeFilter.insert("endDate", m_endTime.toSecsSinceEpoch());
|
||||||
|
timeFilters.append(timeFilter);
|
||||||
|
params.insert("timeFilters", timeFilters);
|
||||||
|
}
|
||||||
|
|
||||||
|
params.insert("limit", m_blockSize);
|
||||||
|
params.insert("offset", m_list.count());
|
||||||
|
|
||||||
|
qDebug() << "Fetching logs from" << m_startTime.toString() << "to" << m_endTime.toString() << "with offset" << m_list.count() << "and limit" << m_blockSize;
|
||||||
|
|
||||||
|
m_engine->jsonRpcClient()->sendCommand("Logging.GetLogEntries", params, this, "logsReply");
|
||||||
|
// qDebug() << "GetLogEntries called";
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogsModel::classBegin()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogsModel::componentComplete()
|
||||||
|
{
|
||||||
|
m_busy = false;
|
||||||
|
fetchMore();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LogsModel::canFetchMore(const QModelIndex &parent) const
|
||||||
|
{
|
||||||
|
Q_UNUSED(parent)
|
||||||
|
// qDebug() << "canFetchMore" << (m_engine && m_canFetchMore);
|
||||||
|
return m_engine && m_canFetchMore;
|
||||||
}
|
}
|
||||||
|
|
||||||
void LogsModel::newLogEntryReceived(const QVariantMap &data)
|
void LogsModel::newLogEntryReceived(const QVariantMap &data)
|
||||||
{
|
{
|
||||||
|
// qDebug() << "***** model NG" << data << m_live;
|
||||||
if (!m_live) {
|
if (!m_live) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariantMap entryMap = data;
|
QVariantMap entryMap = data;
|
||||||
QString deviceId = entryMap.value("deviceId").toString();
|
QUuid thingId;
|
||||||
if (!m_deviceId.isNull() && deviceId != m_deviceId) {
|
if (m_engine->jsonRpcClient()->ensureServerVersion("5.0")) {
|
||||||
|
thingId = entryMap.value("deviceId").toUuid();
|
||||||
|
} else {
|
||||||
|
thingId = entryMap.value("thingId").toUuid();
|
||||||
|
}
|
||||||
|
if (!m_thingId.isNull() && thingId != m_thingId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString typeId = entryMap.value("typeId").toString();
|
QUuid typeId = entryMap.value("typeId").toUuid();
|
||||||
if (!m_typeIds.isEmpty() && !m_typeIds.contains(typeId)) {
|
if (!m_typeIds.isEmpty() && !m_typeIds.contains(typeId)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
beginInsertRows(QModelIndex(), m_list.count(), m_list.count());
|
beginInsertRows(QModelIndex(), 0, 0);
|
||||||
QDateTime timeStamp = QDateTime::fromMSecsSinceEpoch(entryMap.value("timestamp").toLongLong());
|
QDateTime timeStamp = QDateTime::fromMSecsSinceEpoch(entryMap.value("timestamp").toLongLong());
|
||||||
QMetaEnum sourceEnum = QMetaEnum::fromType<LogEntry::LoggingSource>();
|
QMetaEnum sourceEnum = QMetaEnum::fromType<LogEntry::LoggingSource>();
|
||||||
LogEntry::LoggingSource loggingSource = (LogEntry::LoggingSource)sourceEnum.keyToValue(entryMap.value("source").toByteArray());
|
LogEntry::LoggingSource loggingSource = static_cast<LogEntry::LoggingSource>(sourceEnum.keyToValue(entryMap.value("source").toByteArray()));
|
||||||
QMetaEnum loggingEventTypeEnum = QMetaEnum::fromType<LogEntry::LoggingEventType>();
|
QMetaEnum loggingEventTypeEnum = QMetaEnum::fromType<LogEntry::LoggingEventType>();
|
||||||
LogEntry::LoggingEventType loggingEventType = (LogEntry::LoggingEventType)loggingEventTypeEnum.keyToValue(entryMap.value("eventType").toByteArray());
|
LogEntry::LoggingEventType loggingEventType = static_cast<LogEntry::LoggingEventType>(loggingEventTypeEnum.keyToValue(entryMap.value("eventType").toByteArray()));
|
||||||
QVariant value = loggingEventType == LogEntry::LoggingEventTypeActiveChange ? entryMap.value("active").toBool() : entryMap.value("value");
|
QVariant value = loggingEventType == LogEntry::LoggingEventTypeActiveChange ? entryMap.value("active").toBool() : entryMap.value("value");
|
||||||
LogEntry *entry = new LogEntry(timeStamp, value, deviceId, typeId, loggingSource, loggingEventType, this);
|
LogEntry *entry = new LogEntry(timeStamp, value, thingId, typeId, loggingSource, loggingEventType, this);
|
||||||
m_list.append(entry);
|
m_list.prepend(entry);
|
||||||
endInsertRows();
|
endInsertRows();
|
||||||
emit countChanged();
|
emit countChanged();
|
||||||
|
|
||||||
|
emit logEntryAdded(entry);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -32,26 +32,26 @@
|
|||||||
#define LOGSMODEL_H
|
#define LOGSMODEL_H
|
||||||
|
|
||||||
#include <QAbstractListModel>
|
#include <QAbstractListModel>
|
||||||
|
#include <QQmlParserStatus>
|
||||||
|
|
||||||
#include "jsonrpc/jsonhandler.h"
|
#include "jsonrpc/jsonhandler.h"
|
||||||
#include "types/logentry.h"
|
#include "types/logentry.h"
|
||||||
|
|
||||||
class Engine;
|
class Engine;
|
||||||
|
|
||||||
class LogsModel : public QAbstractListModel
|
class LogsModel : public QAbstractListModel, public QQmlParserStatus
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_PROPERTY(Engine* engine READ engine WRITE setEngine NOTIFY engineChanged)
|
Q_PROPERTY(Engine* engine READ engine WRITE setEngine NOTIFY engineChanged)
|
||||||
|
|
||||||
Q_PROPERTY(bool busy READ busy NOTIFY busyChanged)
|
|
||||||
Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
|
Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
|
||||||
Q_PROPERTY(QString deviceId READ deviceId WRITE setDeviceId NOTIFY deviceIdChanged)
|
Q_PROPERTY(bool busy READ busy NOTIFY busyChanged)
|
||||||
|
Q_PROPERTY(bool live READ live WRITE setLive NOTIFY liveChanged)
|
||||||
|
Q_PROPERTY(QUuid thingId READ thingId WRITE setThingId NOTIFY thingIdChanged)
|
||||||
Q_PROPERTY(QStringList typeIds READ typeIds WRITE setTypeIds NOTIFY typeIdsChanged)
|
Q_PROPERTY(QStringList typeIds READ typeIds WRITE setTypeIds NOTIFY typeIdsChanged)
|
||||||
Q_PROPERTY(QDateTime startTime READ startTime WRITE setStartTime NOTIFY startTimeChanged)
|
Q_PROPERTY(QDateTime startTime READ startTime WRITE setStartTime NOTIFY startTimeChanged)
|
||||||
Q_PROPERTY(QDateTime endTime READ endTime WRITE setEndTime NOTIFY endTimeChanged)
|
Q_PROPERTY(QDateTime endTime READ endTime WRITE setEndTime NOTIFY endTimeChanged)
|
||||||
// Q_PROPERTY(int paginationCount READ paginationCount WRITE setPaginationCount NOTIFY paginationCountChanged)
|
Q_PROPERTY(QDateTime viewStartTime READ viewStartTime WRITE setViewStartTime NOTIFY viewStartTimeChanged)
|
||||||
|
|
||||||
Q_PROPERTY(bool live READ live WRITE setLive NOTIFY liveChanged)
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
enum Roles {
|
enum Roles {
|
||||||
@ -72,12 +72,16 @@ public:
|
|||||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||||
QVariant data(const QModelIndex &index, int role) const override;
|
QVariant data(const QModelIndex &index, int role) const override;
|
||||||
QHash<int, QByteArray> roleNames() const override;
|
QHash<int, QByteArray> roleNames() const override;
|
||||||
|
bool canFetchMore(const QModelIndex &parent) const override;
|
||||||
|
void fetchMore(const QModelIndex &parent = QModelIndex()) override;
|
||||||
|
void classBegin() override;
|
||||||
|
void componentComplete() override;
|
||||||
|
|
||||||
bool live() const;
|
bool live() const;
|
||||||
void setLive(bool live);
|
void setLive(bool live);
|
||||||
|
|
||||||
QString deviceId() const;
|
QUuid thingId() const;
|
||||||
void setDeviceId(const QString &deviceId);
|
void setThingId(const QUuid &deviceId);
|
||||||
|
|
||||||
QStringList typeIds() const;
|
QStringList typeIds() const;
|
||||||
void setTypeIds(const QStringList &typeIds);
|
void setTypeIds(const QStringList &typeIds);
|
||||||
@ -88,44 +92,43 @@ public:
|
|||||||
QDateTime endTime() const;
|
QDateTime endTime() const;
|
||||||
void setEndTime(const QDateTime &endTime);
|
void setEndTime(const QDateTime &endTime);
|
||||||
|
|
||||||
// int paginationCount() const;
|
QDateTime viewStartTime() const;
|
||||||
// void setPaginationCount(int paginationCount);
|
void setViewStartTime(const QDateTime &viewStartTime);
|
||||||
|
|
||||||
Q_INVOKABLE LogEntry* get(int index) const;
|
Q_INVOKABLE LogEntry* get(int index) const;
|
||||||
|
|
||||||
Q_INVOKABLE void notificationReceived(const QVariantMap &data);
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void engineChanged();
|
void engineChanged();
|
||||||
void busyChanged();
|
void busyChanged();
|
||||||
void liveChanged();
|
void liveChanged();
|
||||||
void countChanged();
|
void countChanged();
|
||||||
void deviceIdChanged();
|
void thingIdChanged();
|
||||||
void typeIdsChanged();
|
void typeIdsChanged();
|
||||||
void startTimeChanged();
|
void startTimeChanged();
|
||||||
void endTimeChanged();
|
void endTimeChanged();
|
||||||
// void paginationCountChanged();
|
void viewStartTimeChanged();
|
||||||
|
|
||||||
public slots:
|
void logEntryAdded(LogEntry *entry);
|
||||||
virtual void update();
|
|
||||||
virtual void fetchEarlier(int hours);
|
|
||||||
// virtual void fetchLater(int hours);
|
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
virtual void logsReply(const QVariantMap &data);
|
virtual void logsReply(const QVariantMap &data);
|
||||||
virtual void fetchEarlierReply(const QVariantMap &data);
|
|
||||||
void newLogEntryReceived(const QVariantMap &data);
|
void newLogEntryReceived(const QVariantMap &data);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Engine *m_engine = nullptr;
|
Engine *m_engine = nullptr;
|
||||||
QList<LogEntry*> m_list;
|
QList<LogEntry*> m_list;
|
||||||
QString m_deviceId;
|
QUuid m_thingId;
|
||||||
QStringList m_typeIds;
|
QList<QUuid> m_typeIds;
|
||||||
QDateTime m_startTime = QDateTime::currentDateTime().addDays(-1);
|
QDateTime m_startTime;
|
||||||
QDateTime m_endTime = QDateTime::currentDateTime();
|
QDateTime m_endTime;
|
||||||
|
QDateTime m_viewStartTime;
|
||||||
|
|
||||||
bool m_busy = false;
|
bool m_busy = true;
|
||||||
bool m_live = false;
|
bool m_live = false;
|
||||||
|
int m_blockSize = 100;
|
||||||
|
|
||||||
|
bool m_canFetchMore = true;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -278,7 +278,7 @@ void LogsModelNg::logsReply(const QVariantMap &data)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
StateType *entryStateType = dev->deviceClass()->stateTypes()->getStateType(entry->typeId());
|
StateType *entryStateType = dev->thingClass()->stateTypes()->getStateType(entry->typeId());
|
||||||
|
|
||||||
if (m_graphSeries) {
|
if (m_graphSeries) {
|
||||||
if (entryStateType->type().toLower() == "bool") {
|
if (entryStateType->type().toLower() == "bool") {
|
||||||
@ -453,9 +453,9 @@ void LogsModelNg::newLogEntryReceived(const QVariantMap &data)
|
|||||||
|
|
||||||
Device *dev = m_engine->thingManager()->devices()->getDevice(entry->thingId());
|
Device *dev = m_engine->thingManager()->devices()->getDevice(entry->thingId());
|
||||||
|
|
||||||
StateType *entryStateType = dev->deviceClass()->stateTypes()->getStateType(entry->typeId());
|
StateType *entryStateType = dev->thingClass()->stateTypes()->getStateType(entry->typeId());
|
||||||
|
|
||||||
if (dev && dev->deviceClass()->stateTypes()->getStateType(entry->typeId())->type().toLower() == "bool") {
|
if (dev && dev->thingClass()->stateTypes()->getStateType(entry->typeId())->type().toLower() == "bool") {
|
||||||
// First, remove the 2 rightmost (newest on the timeline) values. They're the ones in the future we added to extend the graph and making it end at 1
|
// First, remove the 2 rightmost (newest on the timeline) values. They're the ones in the future we added to extend the graph and making it end at 1
|
||||||
if (m_graphSeries->count() > 1) {
|
if (m_graphSeries->count() > 1) {
|
||||||
m_graphSeries->removePoints(0, 2);
|
m_graphSeries->removePoints(0, 2);
|
||||||
|
|||||||
@ -1,182 +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 "valuelogsproxymodel.h"
|
|
||||||
|
|
||||||
#include <QDebug>
|
|
||||||
|
|
||||||
ValueLogsProxyModel::ValueLogsProxyModel(QObject *parent) : LogsModel(parent)
|
|
||||||
{
|
|
||||||
m_minimumValue = QVariant(0);
|
|
||||||
m_maximumValue = QVariant(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ValueLogsProxyModel::update()
|
|
||||||
{
|
|
||||||
// modify starttime to add a day earlier so we have more chances to have meaningful data right from the start
|
|
||||||
m_startTime = m_startTime.addDays(-1);
|
|
||||||
LogsModel::update();
|
|
||||||
m_startTime = m_startTime.addDays(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
ValueLogsProxyModel::Average ValueLogsProxyModel::average() const
|
|
||||||
{
|
|
||||||
return m_average;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ValueLogsProxyModel::setAverage(ValueLogsProxyModel::Average average)
|
|
||||||
{
|
|
||||||
if (m_average != average) {
|
|
||||||
m_average = average;
|
|
||||||
emit averageChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariant ValueLogsProxyModel::minimumValue() const
|
|
||||||
{
|
|
||||||
return m_minimumValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariant ValueLogsProxyModel::maximumValue() const
|
|
||||||
{
|
|
||||||
return m_maximumValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ValueLogsProxyModel::logsReply(const QVariantMap &data)
|
|
||||||
{
|
|
||||||
|
|
||||||
qDebug() << "logs reply";
|
|
||||||
|
|
||||||
beginResetModel();
|
|
||||||
|
|
||||||
m_minimumValue = QVariant();
|
|
||||||
m_maximumValue = QVariant();
|
|
||||||
|
|
||||||
int stepSize = 1;
|
|
||||||
switch (m_average) {
|
|
||||||
case AverageMonth:
|
|
||||||
stepSize *= 30;
|
|
||||||
// fall through
|
|
||||||
case AverageDay:
|
|
||||||
stepSize *= 8;
|
|
||||||
// fall through
|
|
||||||
case AverageDayTime:
|
|
||||||
stepSize *= 3;
|
|
||||||
// fall through
|
|
||||||
case AverageHourly:
|
|
||||||
stepSize *= 4;
|
|
||||||
// fall through
|
|
||||||
case AverageQuarterHour:
|
|
||||||
stepSize *= 15;
|
|
||||||
// fall through
|
|
||||||
case AverageMinute:
|
|
||||||
stepSize *= 60;
|
|
||||||
}
|
|
||||||
int totalSlots = startTime().secsTo(endTime()) / stepSize;
|
|
||||||
qDebug() << "slots" << totalSlots;
|
|
||||||
|
|
||||||
QHash<int, QList<QVariant> > entries;
|
|
||||||
|
|
||||||
QList<QVariant> logEntries = data.value("params").toMap().value("logEntries").toList();
|
|
||||||
|
|
||||||
QVariant startValue;
|
|
||||||
for (int i = 0; i < logEntries.count(); i++) {
|
|
||||||
QVariantMap entryMap = logEntries.at(i).toMap();
|
|
||||||
QDateTime entryTimestamp = QDateTime::fromMSecsSinceEpoch(entryMap.value("timestamp").toLongLong());
|
|
||||||
int slot = startTime().secsTo(entryTimestamp) / stepSize;
|
|
||||||
if (slot < 0) {
|
|
||||||
// We're before the actual starttime (see update()). store the most recent value
|
|
||||||
startValue = entryMap.value("value");
|
|
||||||
// qDebug() << "have new startvalue" << startValue << entryTimestamp;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
QList<QVariant> tmp = entries[slot];
|
|
||||||
QVariant value = entryMap.value("value");
|
|
||||||
value.convert(QVariant::Double);
|
|
||||||
tmp.append(value);
|
|
||||||
entries[slot] = tmp;
|
|
||||||
// qDebug() << "adding value to slot" << slot << entryMap.value("value") << QDateTime::fromMSecsSinceEpoch(entryMap.value("timestamp").toLongLong());
|
|
||||||
}
|
|
||||||
if (!startValue.isNull() && entries[0].isEmpty()) {
|
|
||||||
QList<QVariant> tmp;
|
|
||||||
tmp.append(startValue);
|
|
||||||
entries[0] = tmp;
|
|
||||||
}
|
|
||||||
// qDebug() << "slotsize:" << stepSize << entries.keys();
|
|
||||||
|
|
||||||
qDeleteAll(m_list);
|
|
||||||
m_list.clear();
|
|
||||||
for (int i = 0; i <= totalSlots; i++) {
|
|
||||||
QVariant avg = 0;
|
|
||||||
int counter = 0;
|
|
||||||
foreach (const QVariant &value, entries[i]) {
|
|
||||||
avg = avg.toDouble() + value.toDouble();
|
|
||||||
counter++;
|
|
||||||
}
|
|
||||||
if (counter > 0) {
|
|
||||||
avg = avg.toDouble() / counter;
|
|
||||||
} else if (entries[i-1].count() > 0) {
|
|
||||||
avg = entries[i-1].last().toDouble();
|
|
||||||
} else if (m_list.count() > 0){
|
|
||||||
avg = m_list.last()->value().toDouble();
|
|
||||||
} else {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
LogEntry *entry = new LogEntry(startTime().addSecs(stepSize * i)/*.addSecs(stepSize * .5)*/, avg, m_deviceId, QString(), LogEntry::LoggingSourceStates, LogEntry::LoggingEventTypeTrigger, this);
|
|
||||||
m_list.append(entry);
|
|
||||||
|
|
||||||
if (m_minimumValue.isNull() || entry->value() < m_minimumValue) {
|
|
||||||
m_minimumValue = qRound(entry->value().toDouble());
|
|
||||||
}
|
|
||||||
if (m_maximumValue.isNull() || entry->value() > m_maximumValue) {
|
|
||||||
m_maximumValue = qRound(entry->value().toDouble());
|
|
||||||
}
|
|
||||||
// qDebug() << "filling slot" << i << "average:" << avg << entry->timestamp().toString() << "min:" << m_minimumValue << "max:" << m_maximumValue;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
endResetModel();
|
|
||||||
|
|
||||||
if (m_minimumValue.isNull()) {
|
|
||||||
m_minimumValue = 0;
|
|
||||||
}
|
|
||||||
if (m_maximumValue.isNull()) {
|
|
||||||
m_maximumValue = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
emit minimumValueChanged();
|
|
||||||
emit maximumValueChanged();
|
|
||||||
emit countChanged();
|
|
||||||
qDebug() << "min" << minimumValue() << "max" << maximumValue();
|
|
||||||
|
|
||||||
m_busy = false;
|
|
||||||
emit busyChanged();
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,83 +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
|
|
||||||
*
|
|
||||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
||||||
|
|
||||||
#ifndef VALUELOGSPROXYMODEL_H
|
|
||||||
#define VALUELOGSPROXYMODEL_H
|
|
||||||
|
|
||||||
#include <QAbstractListModel>
|
|
||||||
|
|
||||||
#include "logsmodel.h"
|
|
||||||
|
|
||||||
class ValueLogsProxyModel : public LogsModel
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
Q_PROPERTY(Average average READ average WRITE setAverage NOTIFY averageChanged)
|
|
||||||
|
|
||||||
Q_PROPERTY(QVariant minimumValue READ minimumValue NOTIFY minimumValueChanged)
|
|
||||||
Q_PROPERTY(QVariant maximumValue READ maximumValue NOTIFY maximumValueChanged)
|
|
||||||
|
|
||||||
public:
|
|
||||||
enum Average {
|
|
||||||
AverageMonth,
|
|
||||||
AverageDay,
|
|
||||||
AverageDayTime,
|
|
||||||
AverageHourly,
|
|
||||||
AverageQuarterHour,
|
|
||||||
AverageMinute
|
|
||||||
};
|
|
||||||
Q_ENUM(Average)
|
|
||||||
|
|
||||||
explicit ValueLogsProxyModel(QObject *parent = nullptr);
|
|
||||||
|
|
||||||
void update() override;
|
|
||||||
|
|
||||||
Average average() const;
|
|
||||||
void setAverage(Average average);
|
|
||||||
|
|
||||||
QVariant minimumValue() const;
|
|
||||||
QVariant maximumValue() const;
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void averageChanged();
|
|
||||||
|
|
||||||
void minimumValueChanged();
|
|
||||||
void maximumValueChanged();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void logsReply(const QVariantMap &data) override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
Average m_average = AverageHourly;
|
|
||||||
|
|
||||||
QVariant m_minimumValue;
|
|
||||||
QVariant m_maximumValue;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // VALUELOGSPROXYMODEL_H
|
|
||||||
178
libnymea-app/models/xyseriesadapter.cpp
Normal file
178
libnymea-app/models/xyseriesadapter.cpp
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
#include "xyseriesadapter.h"
|
||||||
|
|
||||||
|
XYSeriesAdapter::XYSeriesAdapter(QObject *parent) : QObject(parent)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
LogsModel *XYSeriesAdapter::logsModel() const
|
||||||
|
{
|
||||||
|
return m_model;
|
||||||
|
}
|
||||||
|
|
||||||
|
void XYSeriesAdapter::setLogsModel(LogsModel *logsModel)
|
||||||
|
{
|
||||||
|
if (m_model != logsModel) {
|
||||||
|
m_model = logsModel;
|
||||||
|
emit logsModelChanged();
|
||||||
|
// update();
|
||||||
|
connect(logsModel, &LogsModel::logEntryAdded, this, &XYSeriesAdapter::logEntryAdded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QtCharts::QXYSeries *XYSeriesAdapter::xySeries() const
|
||||||
|
{
|
||||||
|
return m_series;
|
||||||
|
}
|
||||||
|
|
||||||
|
void XYSeriesAdapter::setXySeries(QtCharts::QXYSeries *series)
|
||||||
|
{
|
||||||
|
if (m_series != series) {
|
||||||
|
m_series = series;
|
||||||
|
emit xySeriesChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QtCharts::QXYSeries *XYSeriesAdapter::baseSeries() const
|
||||||
|
{
|
||||||
|
return m_baseSeries;
|
||||||
|
}
|
||||||
|
|
||||||
|
void XYSeriesAdapter::setBaseSeries(QtCharts::QXYSeries *series)
|
||||||
|
{
|
||||||
|
if (m_baseSeries != series) {
|
||||||
|
m_baseSeries = series;
|
||||||
|
emit baseSeriesChanged();
|
||||||
|
|
||||||
|
connect(m_baseSeries, &QtCharts::QXYSeries::pointAdded, this, [=](int index){
|
||||||
|
if (m_series->count() > index) {
|
||||||
|
m_series->replace(index, m_series->at(index).x(), calculateSampleValue(index));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
connect(m_baseSeries, &QtCharts::QXYSeries::pointReplaced, this, [=](int index){
|
||||||
|
if (m_series->count() > index) {
|
||||||
|
m_series->replace(index, m_series->at(index).x(), calculateSampleValue(index));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
XYSeriesAdapter::SampleRate XYSeriesAdapter::sampleRate() const
|
||||||
|
{
|
||||||
|
return m_sampleRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
void XYSeriesAdapter::setSampleRate(XYSeriesAdapter::SampleRate sampleRate)
|
||||||
|
{
|
||||||
|
if (m_sampleRate != sampleRate) {
|
||||||
|
m_sampleRate = sampleRate;
|
||||||
|
emit sampleRateChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool XYSeriesAdapter::smooth() const
|
||||||
|
{
|
||||||
|
return m_smooth;
|
||||||
|
}
|
||||||
|
|
||||||
|
void XYSeriesAdapter::setSmooth(bool smooth)
|
||||||
|
{
|
||||||
|
if (m_smooth != smooth) {
|
||||||
|
m_smooth = smooth;
|
||||||
|
emit smoothChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
qreal XYSeriesAdapter::maxValue() const
|
||||||
|
{
|
||||||
|
return m_maxValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
qreal XYSeriesAdapter::minValue() const
|
||||||
|
{
|
||||||
|
return m_minValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
void XYSeriesAdapter::logEntryAdded(LogEntry *entry)
|
||||||
|
{
|
||||||
|
if (!m_series) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_samples.isEmpty()) {
|
||||||
|
Sample *sample = new Sample();
|
||||||
|
sample->timestamp = entry->timestamp().addSecs(m_sampleRate);
|
||||||
|
sample->entries.append(entry);
|
||||||
|
sample->last = entry;
|
||||||
|
m_newestSample = sample->timestamp;
|
||||||
|
m_oldestSample = m_newestSample;
|
||||||
|
m_samples.append(sample);
|
||||||
|
m_series->insert(0, QPointF(sample->timestamp.toMSecsSinceEpoch(), entry->value().toDouble()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (entry->timestamp() > m_newestSample) {
|
||||||
|
Sample *sample = new Sample();
|
||||||
|
sample->timestamp = m_newestSample.addSecs(m_sampleRate);
|
||||||
|
sample->last = m_samples.value(0)->last;
|
||||||
|
m_newestSample = sample->timestamp;
|
||||||
|
m_samples.prepend(sample);
|
||||||
|
m_series->insert(0, QPointF(sample->timestamp.toMSecsSinceEpoch(), sample->last->value().toDouble()));
|
||||||
|
}
|
||||||
|
|
||||||
|
while (entry->timestamp() < m_oldestSample.addSecs(m_sampleRate)) {
|
||||||
|
Sample *sample = new Sample();
|
||||||
|
sample->timestamp = m_oldestSample.addSecs(-m_sampleRate);
|
||||||
|
sample->last = entry;
|
||||||
|
m_oldestSample = sample->timestamp;
|
||||||
|
m_samples.append(sample);
|
||||||
|
m_series->append(sample->timestamp.toMSecsSinceEpoch(), sample->last->value().toDouble());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int idx = entry->timestamp().secsTo(m_newestSample) / m_sampleRate;
|
||||||
|
if (idx > m_samples.count()) {
|
||||||
|
qWarning() << "Overflowing integer size for XYSeriesAdapter!";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Sample *sample = m_samples.at(static_cast<int>(idx));
|
||||||
|
sample->entries.append(entry);
|
||||||
|
|
||||||
|
qreal value = calculateSampleValue(idx);
|
||||||
|
m_series->replace(idx, sample->timestamp.toMSecsSinceEpoch(), value);
|
||||||
|
|
||||||
|
if (value < m_minValue) {
|
||||||
|
m_minValue = value;
|
||||||
|
emit minValueChanged();
|
||||||
|
}
|
||||||
|
if (value > m_maxValue) {
|
||||||
|
m_maxValue = value;
|
||||||
|
emit maxValueChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
qreal XYSeriesAdapter::calculateSampleValue(int index)
|
||||||
|
{
|
||||||
|
Sample *sample = m_samples.at(index);
|
||||||
|
qreal value = 0;
|
||||||
|
int count = 0;
|
||||||
|
if (m_samples.length() > index + 1) {
|
||||||
|
Sample *previousSample = m_samples.at(static_cast<int>(index) + 1);
|
||||||
|
value = previousSample->last->value().toDouble();
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
foreach (LogEntry *entry, sample->entries) {
|
||||||
|
value += entry->value().toDouble();
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count > 1) {
|
||||||
|
value /= count;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_baseSeries && m_baseSeries->count() > index) {
|
||||||
|
value += m_baseSeries->at(index).y();
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
87
libnymea-app/models/xyseriesadapter.h
Normal file
87
libnymea-app/models/xyseriesadapter.h
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
#ifndef XYSERIESADAPTER_H
|
||||||
|
#define XYSERIESADAPTER_H
|
||||||
|
|
||||||
|
#include "logsmodel.h"
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QXYSeries>
|
||||||
|
|
||||||
|
class XYSeriesAdapter : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
Q_PROPERTY(LogsModel* logsModel READ logsModel WRITE setLogsModel NOTIFY logsModelChanged)
|
||||||
|
Q_PROPERTY(QtCharts::QXYSeries* xySeries READ xySeries WRITE setXySeries NOTIFY xySeriesChanged)
|
||||||
|
Q_PROPERTY(QtCharts::QXYSeries* baseSeries READ baseSeries WRITE setBaseSeries NOTIFY baseSeriesChanged)
|
||||||
|
|
||||||
|
Q_PROPERTY(SampleRate sampleRate READ sampleRate WRITE setSampleRate NOTIFY sampleRateChanged)
|
||||||
|
Q_PROPERTY(bool smooth READ smooth WRITE setSmooth NOTIFY smoothChanged)
|
||||||
|
|
||||||
|
Q_PROPERTY(qreal maxValue READ maxValue NOTIFY maxValueChanged)
|
||||||
|
Q_PROPERTY(qreal minValue READ minValue NOTIFY minValueChanged)
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum SampleRate {
|
||||||
|
SampleRateSecond = 1,
|
||||||
|
SampleRateMinute = 60,
|
||||||
|
SampleRateHour = 60 * 60,
|
||||||
|
SampleRateDays = 24 * 60 * 60
|
||||||
|
};
|
||||||
|
Q_ENUM(SampleRate)
|
||||||
|
|
||||||
|
explicit XYSeriesAdapter(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
LogsModel* logsModel() const;
|
||||||
|
void setLogsModel(LogsModel *logsModel);
|
||||||
|
|
||||||
|
QtCharts::QXYSeries* xySeries() const;
|
||||||
|
void setXySeries(QtCharts::QXYSeries *series);
|
||||||
|
|
||||||
|
QtCharts::QXYSeries* baseSeries() const;
|
||||||
|
void setBaseSeries(QtCharts::QXYSeries *series);
|
||||||
|
|
||||||
|
SampleRate sampleRate() const;
|
||||||
|
void setSampleRate(SampleRate sampleRate);
|
||||||
|
|
||||||
|
bool smooth() const;
|
||||||
|
void setSmooth(bool smooth);
|
||||||
|
|
||||||
|
qreal maxValue() const;
|
||||||
|
qreal minValue() const;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void xySeriesChanged();
|
||||||
|
void logsModelChanged();
|
||||||
|
void baseSeriesChanged();
|
||||||
|
void sampleRateChanged();
|
||||||
|
void smoothChanged();
|
||||||
|
void maxValueChanged();
|
||||||
|
void minValueChanged();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void logEntryAdded(LogEntry *entry);
|
||||||
|
|
||||||
|
private:
|
||||||
|
qreal calculateSampleValue(int index);
|
||||||
|
|
||||||
|
private:
|
||||||
|
class Sample {
|
||||||
|
public:
|
||||||
|
QDateTime timestamp; // The timestamp where this sample *ends*
|
||||||
|
QList<LogEntry*> entries; // all log entries in this sample, that is, from timestamp - m_sampleRate
|
||||||
|
LogEntry *last = nullptr;
|
||||||
|
};
|
||||||
|
LogsModel* m_model = nullptr;
|
||||||
|
QtCharts::QXYSeries* m_series = nullptr;
|
||||||
|
QtCharts::QXYSeries* m_baseSeries = nullptr;
|
||||||
|
SampleRate m_sampleRate = SampleRateSecond;
|
||||||
|
bool m_smooth = true;
|
||||||
|
|
||||||
|
QVector<Sample*> m_samples;
|
||||||
|
QDateTime m_newestSample;
|
||||||
|
QDateTime m_oldestSample;
|
||||||
|
|
||||||
|
qreal m_maxValue = 0;
|
||||||
|
qreal m_minValue = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // XYSERIESADAPTER_H
|
||||||
@ -302,7 +302,7 @@ bool RuleTemplatesFilterModel::filterAcceptsRow(int source_row, const QModelInde
|
|||||||
bool found = false;
|
bool found = false;
|
||||||
for (int i = 0; i < m_filterDevicesProxy->rowCount(); i++) {
|
for (int i = 0; i < m_filterDevicesProxy->rowCount(); i++) {
|
||||||
// qDebug() << "Checking device:" << m_filterDevicesProxy->get(i)->deviceClass()->interfaces();
|
// qDebug() << "Checking device:" << m_filterDevicesProxy->get(i)->deviceClass()->interfaces();
|
||||||
if (m_filterDevicesProxy->get(i)->deviceClass()->interfaces().contains(toBeFound)) {
|
if (m_filterDevicesProxy->get(i)->thingClass()->interfaces().contains(toBeFound)) {
|
||||||
found = true;
|
found = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -169,7 +169,7 @@ void CodeCompletion::update()
|
|||||||
if (thingIdExp.exactMatch(blockText)) {
|
if (thingIdExp.exactMatch(blockText)) {
|
||||||
for (int i = 0; i < m_engine->deviceManager()->devices()->rowCount(); i++) {
|
for (int i = 0; i < m_engine->deviceManager()->devices()->rowCount(); i++) {
|
||||||
Device *dev = m_engine->deviceManager()->devices()->get(i);
|
Device *dev = m_engine->deviceManager()->devices()->get(i);
|
||||||
entries.append(CompletionModel::Entry(dev->id().toString() + "\" // " + dev->name(), dev->name(), "thing", dev->deviceClass()->interfaces().join(",")));
|
entries.append(CompletionModel::Entry(dev->id().toString() + "\" // " + dev->name(), dev->name(), "thing", dev->thingClass()->interfaces().join(",")));
|
||||||
}
|
}
|
||||||
blockText.remove(QRegExp(".*thingId: \""));
|
blockText.remove(QRegExp(".*thingId: \""));
|
||||||
m_model->update(entries);
|
m_model->update(entries);
|
||||||
@ -182,7 +182,7 @@ void CodeCompletion::update()
|
|||||||
if (deviceIdExp.exactMatch(blockText)) {
|
if (deviceIdExp.exactMatch(blockText)) {
|
||||||
for (int i = 0; i < m_engine->deviceManager()->devices()->rowCount(); i++) {
|
for (int i = 0; i < m_engine->deviceManager()->devices()->rowCount(); i++) {
|
||||||
Device *dev = m_engine->deviceManager()->devices()->get(i);
|
Device *dev = m_engine->deviceManager()->devices()->get(i);
|
||||||
entries.append(CompletionModel::Entry(dev->id().toString() + "\" // " + dev->name(), dev->name(), "thing", dev->deviceClass()->interfaces().join(",")));
|
entries.append(CompletionModel::Entry(dev->id().toString() + "\" // " + dev->name(), dev->name(), "thing", dev->thingClass()->interfaces().join(",")));
|
||||||
}
|
}
|
||||||
blockText.remove(QRegExp(".*deviceId: \""));
|
blockText.remove(QRegExp(".*deviceId: \""));
|
||||||
m_model->update(entries);
|
m_model->update(entries);
|
||||||
@ -209,8 +209,8 @@ void CodeCompletion::update()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < device->deviceClass()->stateTypes()->rowCount(); i++) {
|
for (int i = 0; i < device->thingClass()->stateTypes()->rowCount(); i++) {
|
||||||
StateType *stateType = device->deviceClass()->stateTypes()->get(i);
|
StateType *stateType = device->thingClass()->stateTypes()->get(i);
|
||||||
entries.append(CompletionModel::Entry(stateType->id().toString() + "\" // " + stateType->name(), stateType->name(), "stateType"));
|
entries.append(CompletionModel::Entry(stateType->id().toString() + "\" // " + stateType->name(), stateType->name(), "stateType"));
|
||||||
}
|
}
|
||||||
blockText.remove(QRegExp(".*stateTypeId: \""));
|
blockText.remove(QRegExp(".*stateTypeId: \""));
|
||||||
@ -241,8 +241,8 @@ void CodeCompletion::update()
|
|||||||
}
|
}
|
||||||
qDebug() << "Device is" << device->name();
|
qDebug() << "Device is" << device->name();
|
||||||
|
|
||||||
for (int i = 0; i < device->deviceClass()->stateTypes()->rowCount(); i++) {
|
for (int i = 0; i < device->thingClass()->stateTypes()->rowCount(); i++) {
|
||||||
StateType *stateType = device->deviceClass()->stateTypes()->get(i);
|
StateType *stateType = device->thingClass()->stateTypes()->get(i);
|
||||||
entries.append(CompletionModel::Entry(stateType->name() + "\"", stateType->name(), "stateType"));
|
entries.append(CompletionModel::Entry(stateType->name() + "\"", stateType->name(), "stateType"));
|
||||||
}
|
}
|
||||||
blockText.remove(QRegExp(".*stateName: \""));
|
blockText.remove(QRegExp(".*stateName: \""));
|
||||||
@ -270,8 +270,8 @@ void CodeCompletion::update()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < device->deviceClass()->actionTypes()->rowCount(); i++) {
|
for (int i = 0; i < device->thingClass()->actionTypes()->rowCount(); i++) {
|
||||||
ActionType *actionType = device->deviceClass()->actionTypes()->get(i);
|
ActionType *actionType = device->thingClass()->actionTypes()->get(i);
|
||||||
entries.append(CompletionModel::Entry(actionType->id().toString() + "\" // " + actionType->name(), actionType->name(), "actionType"));
|
entries.append(CompletionModel::Entry(actionType->id().toString() + "\" // " + actionType->name(), actionType->name(), "actionType"));
|
||||||
}
|
}
|
||||||
blockText.remove(QRegExp(".*actionTypeId: \""));
|
blockText.remove(QRegExp(".*actionTypeId: \""));
|
||||||
@ -299,8 +299,8 @@ void CodeCompletion::update()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < device->deviceClass()->actionTypes()->rowCount(); i++) {
|
for (int i = 0; i < device->thingClass()->actionTypes()->rowCount(); i++) {
|
||||||
ActionType *actionType = device->deviceClass()->actionTypes()->get(i);
|
ActionType *actionType = device->thingClass()->actionTypes()->get(i);
|
||||||
entries.append(CompletionModel::Entry(actionType->name() + "\"", actionType->name(), "actionType"));
|
entries.append(CompletionModel::Entry(actionType->name() + "\"", actionType->name(), "actionType"));
|
||||||
}
|
}
|
||||||
blockText.remove(QRegExp(".*actionName: \""));
|
blockText.remove(QRegExp(".*actionName: \""));
|
||||||
@ -328,8 +328,8 @@ void CodeCompletion::update()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < device->deviceClass()->eventTypes()->rowCount(); i++) {
|
for (int i = 0; i < device->thingClass()->eventTypes()->rowCount(); i++) {
|
||||||
EventType *eventType = device->deviceClass()->eventTypes()->get(i);
|
EventType *eventType = device->thingClass()->eventTypes()->get(i);
|
||||||
entries.append(CompletionModel::Entry(eventType->id().toString() + "\" // " + eventType->name(), eventType->name(), "eventType"));
|
entries.append(CompletionModel::Entry(eventType->id().toString() + "\" // " + eventType->name(), eventType->name(), "eventType"));
|
||||||
}
|
}
|
||||||
blockText.remove(QRegExp(".*eventTypeId: \""));
|
blockText.remove(QRegExp(".*eventTypeId: \""));
|
||||||
@ -358,8 +358,8 @@ void CodeCompletion::update()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < device->deviceClass()->eventTypes()->rowCount(); i++) {
|
for (int i = 0; i < device->thingClass()->eventTypes()->rowCount(); i++) {
|
||||||
EventType *eventType = device->deviceClass()->eventTypes()->get(i);
|
EventType *eventType = device->thingClass()->eventTypes()->get(i);
|
||||||
entries.append(CompletionModel::Entry(eventType->name() + "\"", eventType->name(), "eventType"));
|
entries.append(CompletionModel::Entry(eventType->name() + "\"", eventType->name(), "eventType"));
|
||||||
}
|
}
|
||||||
blockText.remove(QRegExp(".*eventName: \""));
|
blockText.remove(QRegExp(".*eventName: \""));
|
||||||
|
|||||||
@ -389,9 +389,15 @@ void SystemController::notificationReceived(const QVariantMap &data)
|
|||||||
emit powerManagementAvailableChanged();
|
emit powerManagementAvailableChanged();
|
||||||
emit updateManagementAvailableChanged();
|
emit updateManagementAvailableChanged();
|
||||||
} else if (notification == "System.TimeConfigurationChanged") {
|
} else if (notification == "System.TimeConfigurationChanged") {
|
||||||
qDebug() << "System time configuration changed";
|
qDebug() << "System time configuration changed" << data.value("params").toMap().value("timeZone").toByteArray();
|
||||||
m_serverTime = QDateTime::fromSecsSinceEpoch(data.value("params").toMap().value("time").toUInt());
|
m_serverTime = QDateTime::fromSecsSinceEpoch(data.value("params").toMap().value("time").toUInt());
|
||||||
m_serverTime.setTimeZone(QTimeZone(data.value("params").toMap().value("timeZone").toByteArray()));
|
|
||||||
|
// NOTE: Ideally we'd just set the TimeZone of our serverTime prooperly, however, there's a bug on Android
|
||||||
|
// Which doesn't allow to create QTimeZone objects by IANA id.... So, let's keep that separated in a string
|
||||||
|
// https://bugreports.qt.io/browse/QTBUG-83438
|
||||||
|
// m_serverTime.setTimeZone(QTimeZone(data.value("params").toMap().value("timeZone").toByteArray()));
|
||||||
|
m_serverTimeZone = data.value("params").toMap().value("timeZone").toString();
|
||||||
|
|
||||||
emit serverTimeChanged();
|
emit serverTimeChanged();
|
||||||
emit serverTimeZoneChanged();
|
emit serverTimeZoneChanged();
|
||||||
m_automaticTimeAvailable = data.value("params").toMap().value("automaticTimeAvailable").toBool();
|
m_automaticTimeAvailable = data.value("params").toMap().value("automaticTimeAvailable").toBool();
|
||||||
|
|||||||
@ -82,7 +82,7 @@ int ThingGroup::executeAction(const QString &actionName, const QVariantList &par
|
|||||||
if (device->setupStatus() != Device::DeviceSetupStatusComplete) {
|
if (device->setupStatus() != Device::DeviceSetupStatusComplete) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
ActionType *actionType = device->deviceClass()->actionTypes()->findByName(actionName);
|
ActionType *actionType = device->thingClass()->actionTypes()->findByName(actionName);
|
||||||
if (!actionType) {
|
if (!actionType) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -110,8 +110,8 @@ int ThingGroup::executeAction(const QString &actionName, const QVariantList &par
|
|||||||
|
|
||||||
void ThingGroup::syncStates()
|
void ThingGroup::syncStates()
|
||||||
{
|
{
|
||||||
for (int i = 0; i < deviceClass()->stateTypes()->rowCount(); i++) {
|
for (int i = 0; i < thingClass()->stateTypes()->rowCount(); i++) {
|
||||||
StateType *stateType = deviceClass()->stateTypes()->get(i);
|
StateType *stateType = thingClass()->stateTypes()->get(i);
|
||||||
State *state = states()->getState(stateType->id());
|
State *state = states()->getState(stateType->id());
|
||||||
|
|
||||||
qDebug() << "syncing state" << stateType->name();
|
qDebug() << "syncing state" << stateType->name();
|
||||||
@ -121,13 +121,13 @@ void ThingGroup::syncStates()
|
|||||||
for (int j = 0; j < m_devices->rowCount(); j++) {
|
for (int j = 0; j < m_devices->rowCount(); j++) {
|
||||||
Device *d = m_devices->get(j);
|
Device *d = m_devices->get(j);
|
||||||
// Skip things that don't have the required state
|
// Skip things that don't have the required state
|
||||||
StateType *ds = d->deviceClass()->stateTypes()->findByName(stateType->name());
|
StateType *ds = d->thingClass()->stateTypes()->findByName(stateType->name());
|
||||||
if (!ds) {
|
if (!ds) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip disconnected things
|
// Skip disconnected things
|
||||||
StateType *connectedStateType = d->deviceClass()->stateTypes()->findByName("connected");
|
StateType *connectedStateType = d->thingClass()->stateTypes()->findByName("connected");
|
||||||
if (connectedStateType) {
|
if (connectedStateType) {
|
||||||
if (!d->stateValue(connectedStateType->id()).toBool()) {
|
if (!d->stateValue(connectedStateType->id()).toBool()) {
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@ -34,11 +34,11 @@
|
|||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
|
||||||
Device::Device(DeviceManager *deviceManager, DeviceClass *deviceClass, const QUuid &parentDeviceId, QObject *parent) :
|
Device::Device(DeviceManager *deviceManager, DeviceClass *thingClass, const QUuid &parentId, QObject *parent) :
|
||||||
QObject(parent),
|
QObject(parent),
|
||||||
m_deviceManager(deviceManager),
|
m_deviceManager(deviceManager),
|
||||||
m_parentDeviceId(parentDeviceId),
|
m_parentId(parentId),
|
||||||
m_deviceClass(deviceClass)
|
m_thingClass(thingClass)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,22 +65,22 @@ void Device::setId(const QUuid &id)
|
|||||||
|
|
||||||
QUuid Device::deviceClassId() const
|
QUuid Device::deviceClassId() const
|
||||||
{
|
{
|
||||||
return m_deviceClass->id();
|
return m_thingClass->id();
|
||||||
}
|
}
|
||||||
|
|
||||||
QUuid Device::thingClassId() const
|
QUuid Device::thingClassId() const
|
||||||
{
|
{
|
||||||
return m_deviceClass->id();
|
return m_thingClass->id();
|
||||||
}
|
}
|
||||||
|
|
||||||
QUuid Device::parentDeviceId() const
|
QUuid Device::parentDeviceId() const
|
||||||
{
|
{
|
||||||
return m_parentDeviceId;
|
return m_parentId;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Device::isChild() const
|
bool Device::isChild() const
|
||||||
{
|
{
|
||||||
return !m_parentDeviceId.isNull();
|
return !m_parentId.isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
Device::DeviceSetupStatus Device::setupStatus() const
|
Device::DeviceSetupStatus Device::setupStatus() const
|
||||||
@ -153,14 +153,23 @@ void Device::setStates(States *states)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DeviceClass *Device::deviceClass() const
|
State *Device::state(const QUuid &stateTypeId) const
|
||||||
{
|
{
|
||||||
return m_deviceClass;
|
return m_states->getState(stateTypeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
State *Device::stateByName(const QString &stateName) const
|
||||||
|
{
|
||||||
|
StateType *st = m_thingClass->stateTypes()->findByName(stateName);
|
||||||
|
if (!st) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return m_states->getState(st->id());
|
||||||
}
|
}
|
||||||
|
|
||||||
DeviceClass *Device::thingClass() const
|
DeviceClass *Device::thingClass() const
|
||||||
{
|
{
|
||||||
return m_deviceClass;
|
return m_thingClass;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Device::hasState(const QUuid &stateTypeId)
|
bool Device::hasState(const QUuid &stateTypeId)
|
||||||
@ -195,7 +204,7 @@ void Device::setStateValue(const QUuid &stateTypeId, const QVariant &value)
|
|||||||
|
|
||||||
int Device::executeAction(const QString &actionName, const QVariantList ¶ms)
|
int Device::executeAction(const QString &actionName, const QVariantList ¶ms)
|
||||||
{
|
{
|
||||||
ActionType *actionType = m_deviceClass->actionTypes()->findByName(actionName);
|
ActionType *actionType = m_thingClass->actionTypes()->findByName(actionName);
|
||||||
|
|
||||||
QVariantList finalParams;
|
QVariantList finalParams;
|
||||||
foreach (const QVariant ¶mVariant, params) {
|
foreach (const QVariant ¶mVariant, params) {
|
||||||
@ -209,30 +218,30 @@ int Device::executeAction(const QString &actionName, const QVariantList ¶ms)
|
|||||||
return m_deviceManager->executeAction(m_id, actionType->id(), finalParams);
|
return m_deviceManager->executeAction(m_id, actionType->id(), finalParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
QDebug operator<<(QDebug &dbg, Device *device)
|
QDebug operator<<(QDebug &dbg, Device *thing)
|
||||||
{
|
{
|
||||||
dbg.nospace() << "Device: " << device->name() << " (" << device->id().toString() << ") Class:" << device->deviceClass()->name() << " (" << device->deviceClassId().toString() << ")" << endl;
|
dbg.nospace() << "Thing: " << thing->name() << " (" << thing->id().toString() << ") Class:" << thing->thingClass()->name() << " (" << thing->thingClassId().toString() << ")" << endl;
|
||||||
for (int i = 0; i < device->deviceClass()->paramTypes()->rowCount(); i++) {
|
for (int i = 0; i < thing->thingClass()->paramTypes()->rowCount(); i++) {
|
||||||
ParamType *pt = device->deviceClass()->paramTypes()->get(i);
|
ParamType *pt = thing->thingClass()->paramTypes()->get(i);
|
||||||
Param *p = device->params()->getParam(pt->id().toString());
|
Param *p = thing->params()->getParam(pt->id().toString());
|
||||||
if (p) {
|
if (p) {
|
||||||
dbg << " Param " << i << ": " << pt->id().toString() << ": " << pt->name() << " = " << p->value() << endl;
|
dbg << " Param " << i << ": " << pt->id().toString() << ": " << pt->name() << " = " << p->value() << endl;
|
||||||
} else {
|
} else {
|
||||||
dbg << " Param " << i << ": " << pt->id().toString() << ": " << pt->name() << " = " << "*** Unknown value ***" << endl;
|
dbg << " Param " << i << ": " << pt->id().toString() << ": " << pt->name() << " = " << "*** Unknown value ***" << endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (int i = 0; i < device->deviceClass()->settingsTypes()->rowCount(); i++) {
|
for (int i = 0; i < thing->thingClass()->settingsTypes()->rowCount(); i++) {
|
||||||
ParamType *pt = device->deviceClass()->settingsTypes()->get(i);
|
ParamType *pt = thing->thingClass()->settingsTypes()->get(i);
|
||||||
Param *p = device->settings()->getParam(pt->id().toString());
|
Param *p = thing->settings()->getParam(pt->id().toString());
|
||||||
if (p) {
|
if (p) {
|
||||||
dbg << " Setting " << i << ": " << pt->id().toString() << ": " << pt->name() << " = " << p->value() << endl;
|
dbg << " Setting " << i << ": " << pt->id().toString() << ": " << pt->name() << " = " << p->value() << endl;
|
||||||
} else {
|
} else {
|
||||||
dbg << " Setting " << i << ": " << pt->id().toString() << ": " << pt->name() << " = " << "*** Unknown value ***" << endl;
|
dbg << " Setting " << i << ": " << pt->id().toString() << ": " << pt->name() << " = " << "*** Unknown value ***" << endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (int i = 0; i < device->deviceClass()->stateTypes()->rowCount(); i++) {
|
for (int i = 0; i < thing->thingClass()->stateTypes()->rowCount(); i++) {
|
||||||
StateType *st = device->deviceClass()->stateTypes()->get(i);
|
StateType *st = thing->thingClass()->stateTypes()->get(i);
|
||||||
State *s = device->states()->getState(st->id());
|
State *s = thing->states()->getState(st->id());
|
||||||
dbg << " State " << i << ": " << st->id() << ": " << st->name() << " = " << s->value() << endl;
|
dbg << " State " << i << ": " << st->id() << ": " << st->name() << " = " << s->value() << endl;
|
||||||
}
|
}
|
||||||
return dbg;
|
return dbg;
|
||||||
|
|||||||
@ -55,7 +55,7 @@ class Device : public QObject
|
|||||||
Q_PROPERTY(Params *params READ params NOTIFY paramsChanged)
|
Q_PROPERTY(Params *params READ params NOTIFY paramsChanged)
|
||||||
Q_PROPERTY(Params *settings READ settings NOTIFY settingsChanged)
|
Q_PROPERTY(Params *settings READ settings NOTIFY settingsChanged)
|
||||||
Q_PROPERTY(States *states READ states NOTIFY statesChanged)
|
Q_PROPERTY(States *states READ states NOTIFY statesChanged)
|
||||||
Q_PROPERTY(DeviceClass *deviceClass READ deviceClass CONSTANT)
|
Q_PROPERTY(DeviceClass *deviceClass READ thingClass CONSTANT)
|
||||||
Q_PROPERTY(DeviceClass *thingClass READ thingClass CONSTANT)
|
Q_PROPERTY(DeviceClass *thingClass READ thingClass CONSTANT)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@ -67,7 +67,7 @@ public:
|
|||||||
};
|
};
|
||||||
Q_ENUM(DeviceSetupStatus)
|
Q_ENUM(DeviceSetupStatus)
|
||||||
|
|
||||||
explicit Device(DeviceManager *deviceManager, DeviceClass *deviceClass, const QUuid &parentDeviceId = QUuid(), QObject *parent = nullptr);
|
explicit Device(DeviceManager *deviceManager, DeviceClass *thingClass, const QUuid &parentId = QUuid(), QObject *parent = nullptr);
|
||||||
|
|
||||||
QUuid id() const;
|
QUuid id() const;
|
||||||
void setId(const QUuid &id);
|
void setId(const QUuid &id);
|
||||||
@ -93,10 +93,11 @@ public:
|
|||||||
States *states() const;
|
States *states() const;
|
||||||
void setStates(States *states);
|
void setStates(States *states);
|
||||||
|
|
||||||
DeviceClass *deviceClass() const;
|
|
||||||
DeviceClass *thingClass() const;
|
DeviceClass *thingClass() const;
|
||||||
|
|
||||||
Q_INVOKABLE bool hasState(const QUuid &stateTypeId);
|
Q_INVOKABLE bool hasState(const QUuid &stateTypeId);
|
||||||
|
Q_INVOKABLE State *state(const QUuid &stateTypeId) const;
|
||||||
|
Q_INVOKABLE State *stateByName(const QString &stateName) const;
|
||||||
|
|
||||||
Q_INVOKABLE QVariant stateValue(const QUuid &stateTypeId);
|
Q_INVOKABLE QVariant stateValue(const QUuid &stateTypeId);
|
||||||
void setStateValue(const QUuid &stateTypeId, const QVariant &value);
|
void setStateValue(const QUuid &stateTypeId, const QVariant &value);
|
||||||
@ -117,15 +118,15 @@ protected:
|
|||||||
DeviceManager *m_deviceManager = nullptr;
|
DeviceManager *m_deviceManager = nullptr;
|
||||||
QString m_name;
|
QString m_name;
|
||||||
QUuid m_id;
|
QUuid m_id;
|
||||||
QUuid m_parentDeviceId;
|
QUuid m_parentId;
|
||||||
DeviceSetupStatus m_setupStatus = DeviceSetupStatusNone;
|
DeviceSetupStatus m_setupStatus = DeviceSetupStatusNone;
|
||||||
QString m_setupDisplayMessage;
|
QString m_setupDisplayMessage;
|
||||||
Params *m_params = nullptr;
|
Params *m_params = nullptr;
|
||||||
Params *m_settings = nullptr;
|
Params *m_settings = nullptr;
|
||||||
States *m_states = nullptr;
|
States *m_states = nullptr;
|
||||||
DeviceClass *m_deviceClass = nullptr;
|
DeviceClass *m_thingClass = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
QDebug operator<<(QDebug &dbg, Device* device);
|
QDebug operator<<(QDebug &dbg, Device* thing);
|
||||||
|
|
||||||
#endif // DEVICE_H
|
#endif // DEVICE_H
|
||||||
|
|||||||
@ -99,7 +99,7 @@
|
|||||||
<file>ui/images/media-seek-forward.svg</file>
|
<file>ui/images/media-seek-forward.svg</file>
|
||||||
<file>ui/images/media-skip-backward.svg</file>
|
<file>ui/images/media-skip-backward.svg</file>
|
||||||
<file>ui/images/media-skip-forward.svg</file>
|
<file>ui/images/media-skip-forward.svg</file>
|
||||||
<file>ui/images/mediaplayer-app-symbolic.svg</file>
|
<file>ui/images/media.svg</file>
|
||||||
<file>ui/images/navigation-menu.svg</file>
|
<file>ui/images/navigation-menu.svg</file>
|
||||||
<file>ui/images/network-secure.svg</file>
|
<file>ui/images/network-secure.svg</file>
|
||||||
<file>ui/images/network-vpn.svg</file>
|
<file>ui/images/network-vpn.svg</file>
|
||||||
|
|||||||
@ -50,18 +50,13 @@
|
|||||||
<file>ui/customviews/CustomViewBase.qml</file>
|
<file>ui/customviews/CustomViewBase.qml</file>
|
||||||
<file>ui/customviews/WeatherView.qml</file>
|
<file>ui/customviews/WeatherView.qml</file>
|
||||||
<file>ui/customviews/MediaControllerView.qml</file>
|
<file>ui/customviews/MediaControllerView.qml</file>
|
||||||
<file>ui/customviews/SensorView.qml</file>
|
|
||||||
<file>ui/customviews/NotificationsView.qml</file>
|
<file>ui/customviews/NotificationsView.qml</file>
|
||||||
<file>ui/customviews/ExtendedVolumeController.qml</file>
|
<file>ui/customviews/ExtendedVolumeController.qml</file>
|
||||||
<file>ui/devicepages/MediaDevicePage.qml</file>
|
<file>ui/devicepages/MediaDevicePage.qml</file>
|
||||||
<file>ui/devicepages/ButtonDevicePage.qml</file>
|
<file>ui/devicepages/ButtonDevicePage.qml</file>
|
||||||
<file>ui/devicepages/GenericDevicePage.qml</file>
|
<file>ui/devicepages/GenericDevicePage.qml</file>
|
||||||
<file>ui/devicepages/WeatherDevicePagePre110.qml</file>
|
|
||||||
<file>ui/devicepages/WeatherDevicePagePost110.qml</file>
|
|
||||||
<file>ui/devicepages/WeatherDevicePage.qml</file>
|
<file>ui/devicepages/WeatherDevicePage.qml</file>
|
||||||
<file>ui/devicepages/SensorDevicePage.qml</file>
|
<file>ui/devicepages/SensorDevicePage.qml</file>
|
||||||
<file>ui/devicepages/SensorDevicePagePre110.qml</file>
|
|
||||||
<file>ui/devicepages/SensorDevicePagePost110.qml</file>
|
|
||||||
<file>ui/devicepages/DevicePageBase.qml</file>
|
<file>ui/devicepages/DevicePageBase.qml</file>
|
||||||
<file>ui/devicepages/InputTriggerDevicePage.qml</file>
|
<file>ui/devicepages/InputTriggerDevicePage.qml</file>
|
||||||
<file>ui/devicepages/StateLogPage.qml</file>
|
<file>ui/devicepages/StateLogPage.qml</file>
|
||||||
@ -134,7 +129,6 @@
|
|||||||
<file>../LICENSE.OFL</file>
|
<file>../LICENSE.OFL</file>
|
||||||
<file>../LICENSE.OpenSSL</file>
|
<file>../LICENSE.OpenSSL</file>
|
||||||
<file>../LICENSE.LGPL3</file>
|
<file>../LICENSE.LGPL3</file>
|
||||||
<file>ui/customviews/GenericTypeGraphPre110.qml</file>
|
|
||||||
<file>ui/customviews/GenericTypeGraph.qml</file>
|
<file>ui/customviews/GenericTypeGraph.qml</file>
|
||||||
<file>ui/devicepages/SmartMeterDevicePage.qml</file>
|
<file>ui/devicepages/SmartMeterDevicePage.qml</file>
|
||||||
<file>ui/devicelistpages/SmartMeterDeviceListPage.qml</file>
|
<file>ui/devicelistpages/SmartMeterDeviceListPage.qml</file>
|
||||||
@ -222,5 +216,8 @@
|
|||||||
<file>ui/mainviews/EnergyView.qml</file>
|
<file>ui/mainviews/EnergyView.qml</file>
|
||||||
<file>ui/components/MainViewBase.qml</file>
|
<file>ui/components/MainViewBase.qml</file>
|
||||||
<file>ui/components/SmartMeterChart.qml</file>
|
<file>ui/components/SmartMeterChart.qml</file>
|
||||||
|
<file>ui/mainviews/MediaView.qml</file>
|
||||||
|
<file>ui/components/ShuffleRepeatVolumeControl.qml</file>
|
||||||
|
<file>ui/components/MediaBrowser.qml</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
|||||||
@ -120,6 +120,7 @@ Page {
|
|||||||
ListElement { name: "scenes"; source: "ScenesView"; displayName: qsTr("Scenes"); icon: "slideshow" }
|
ListElement { name: "scenes"; source: "ScenesView"; displayName: qsTr("Scenes"); icon: "slideshow" }
|
||||||
ListElement { name: "garages"; source: "GaragesView"; displayName: qsTr("Garages"); icon: "garage/garage-100" }
|
ListElement { name: "garages"; source: "GaragesView"; displayName: qsTr("Garages"); icon: "garage/garage-100" }
|
||||||
ListElement { name: "energy"; source: "EnergyView"; displayName: qsTr("Energy"); icon: "smartmeter" }
|
ListElement { name: "energy"; source: "EnergyView"; displayName: qsTr("Energy"); icon: "smartmeter" }
|
||||||
|
ListElement { name: "media"; source: "MediaView"; displayName: qsTr("Media"); icon: "media" }
|
||||||
}
|
}
|
||||||
|
|
||||||
ListModel {
|
ListModel {
|
||||||
|
|||||||
@ -259,7 +259,7 @@ ApplicationWindow {
|
|||||||
case "mediacontroller":
|
case "mediacontroller":
|
||||||
case "extendedmediacontroller":
|
case "extendedmediacontroller":
|
||||||
case "mediaplayer":
|
case "mediaplayer":
|
||||||
return Qt.resolvedUrl("images/mediaplayer-app-symbolic.svg")
|
return Qt.resolvedUrl("images/media.svg")
|
||||||
case "powersocket":
|
case "powersocket":
|
||||||
return Qt.resolvedUrl("images/powersocket.svg")
|
return Qt.resolvedUrl("images/powersocket.svg")
|
||||||
case "button":
|
case "button":
|
||||||
|
|||||||
@ -36,13 +36,13 @@ import Nymea 1.0
|
|||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
property Device device: null
|
property Thing thing: null
|
||||||
|
|
||||||
readonly property StateType artworkStateType: device ? device.deviceClass.stateTypes.findByName("artwork") : null
|
readonly property StateType artworkStateType: thing ? thing.thingClass.stateTypes.findByName("artwork") : null
|
||||||
readonly property State artworkState: artworkStateType ? device.states.getState(artworkStateType.id) : null
|
readonly property State artworkState: artworkStateType ? thing.states.getState(artworkStateType.id) : null
|
||||||
|
|
||||||
readonly property StateType playerTypeStateType: device ? device.deviceClass.stateTypes.findByName("playerType") : null
|
readonly property StateType playerTypeStateType: thing ? thing.thingClass.stateTypes.findByName("playerType") : null
|
||||||
readonly property State playerTypeState: playerTypeStateType ? device.states.getState(playerTypeStateType.id) : null
|
readonly property State playerTypeState: playerTypeStateType ? thing.states.getState(playerTypeStateType.id) : null
|
||||||
|
|
||||||
Pane {
|
Pane {
|
||||||
Material.elevation: 2
|
Material.elevation: 2
|
||||||
|
|||||||
101
nymea-app/ui/components/MediaBrowser.qml
Normal file
101
nymea-app/ui/components/MediaBrowser.qml
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
|
*
|
||||||
|
* 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.5
|
||||||
|
import QtQuick.Controls 2.1
|
||||||
|
import QtQuick.Controls.Material 2.1
|
||||||
|
import QtQuick.Layouts 1.1
|
||||||
|
import QtGraphicalEffects 1.0
|
||||||
|
import Nymea 1.0
|
||||||
|
import "../delegates"
|
||||||
|
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property Thing thing: null
|
||||||
|
|
||||||
|
function backPressed() {
|
||||||
|
if (internalPageStack.depth > 1) {
|
||||||
|
internalPageStack.pop();
|
||||||
|
} else {
|
||||||
|
swipeView.currentIndex--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StackView {
|
||||||
|
id: internalPageStack
|
||||||
|
anchors.fill: parent
|
||||||
|
initialItem: internalBrowserPage
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: internalBrowserPage
|
||||||
|
ListView {
|
||||||
|
id: listView
|
||||||
|
model: browserItems
|
||||||
|
ScrollBar.vertical: ScrollBar {}
|
||||||
|
|
||||||
|
property string nodeId: ""
|
||||||
|
|
||||||
|
// Need to keep a explicit property here or the GC will eat it too early
|
||||||
|
property BrowserItems browserItems: null
|
||||||
|
Component.onCompleted: {
|
||||||
|
browserItems = engine.thingManager.browseDevice(root.thing.id, nodeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate: BrowserItemDelegate {
|
||||||
|
iconName: "../images/browser/" + (model.mediaIcon && model.mediaIcon !== "MediaBrowserIconNone" ? model.mediaIcon : model.icon) + ".svg"
|
||||||
|
busy: d.pendingItemId === model.id
|
||||||
|
device: root.thing
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
print("clicked:", model.id)
|
||||||
|
if (model.executable) {
|
||||||
|
root.executeBrowserItem(model.id)
|
||||||
|
} else if (model.browsable) {
|
||||||
|
internalPageStack.push(internalBrowserPage, {device: root.thing, nodeId: model.id})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onContextMenuActionTriggered: {
|
||||||
|
root.executeBrowserItemAction(model.id, actionTypeId, params)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BusyIndicator {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
running: listView.model.busy
|
||||||
|
visible: running
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -38,15 +38,18 @@ RowLayout {
|
|||||||
id: root
|
id: root
|
||||||
implicitHeight: iconSize + app.margins
|
implicitHeight: iconSize + app.margins
|
||||||
|
|
||||||
property Device device: null
|
property Thing thing: null
|
||||||
property int iconSize: app.iconSize * 1.5
|
property int iconSize: app.iconSize * 1.5
|
||||||
|
|
||||||
readonly property StateType playbackStateType: device ? device.deviceClass.stateTypes.findByName("playbackStatus") : null
|
readonly property StateType playbackStateType: thing ? thing.thingClass.stateTypes.findByName("playbackStatus") : null
|
||||||
readonly property State playbackState: playbackStateType ? device.states.getState(playbackStateType.id) : null
|
readonly property State playbackState: playbackStateType ? thing.states.getState(playbackStateType.id) : null
|
||||||
|
|
||||||
function executeAction(actionName, params) {
|
function executeAction(actionName, params) {
|
||||||
var actionTypeId = device.deviceClass.actionTypes.findByName(actionName).id;
|
if (params === undefined) {
|
||||||
engine.deviceManager.executeAction(device.id, actionTypeId, params)
|
params = []
|
||||||
|
}
|
||||||
|
var actionTypeId = thing.thingClass.actionTypes.findByName(actionName).id;
|
||||||
|
engine.thingManager.executeAction(thing.id, actionTypeId, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
Item { Layout.fillWidth: true }
|
Item { Layout.fillWidth: true }
|
||||||
@ -68,7 +71,7 @@ RowLayout {
|
|||||||
}
|
}
|
||||||
Item { Layout.fillWidth: true }
|
Item { Layout.fillWidth: true }
|
||||||
ProgressButton {
|
ProgressButton {
|
||||||
Layout.preferredHeight: root,iconSize
|
Layout.preferredHeight: root.iconSize
|
||||||
Layout.preferredWidth: height
|
Layout.preferredWidth: height
|
||||||
imageSource: root.playbackState && root.playbackState.value === "Playing" ? "../images/media-playback-pause.svg" : "../images/media-playback-start.svg"
|
imageSource: root.playbackState && root.playbackState.value === "Playing" ? "../images/media-playback-pause.svg" : "../images/media-playback-start.svg"
|
||||||
longpressImageSource: "../images/media-playback-stop.svg"
|
longpressImageSource: "../images/media-playback-stop.svg"
|
||||||
|
|||||||
145
nymea-app/ui/components/ShuffleRepeatVolumeControl.qml
Normal file
145
nymea-app/ui/components/ShuffleRepeatVolumeControl.qml
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
|
*
|
||||||
|
* 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.8
|
||||||
|
import QtQuick.Controls 2.1
|
||||||
|
import QtQuick.Controls.Material 2.1
|
||||||
|
import QtQuick.Layouts 1.2
|
||||||
|
import QtCharts 2.2
|
||||||
|
import Nymea 1.0
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property Thing thing: null
|
||||||
|
|
||||||
|
property State repeatState: thing.stateByName("repeat")
|
||||||
|
property State shuffleState: thing.stateByName("shuffle")
|
||||||
|
|
||||||
|
property State volumeState: thing.stateByName("volume")
|
||||||
|
property State muteState: thing.stateByName("mute")
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.preferredHeight: app.iconSize
|
||||||
|
Layout.fillWidth: true
|
||||||
|
visible: root.repeatState !== null
|
||||||
|
|
||||||
|
HeaderButton {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
imageSource: root.repeatState.value === "One" ? "../images/media-playlist-repeat-one.svg" : "../images/media-playlist-repeat.svg"
|
||||||
|
color: root.repeatState.value === "None" ? keyColor : app.accentColor
|
||||||
|
property var allowedValues: ["None", "All", "One"]
|
||||||
|
onClicked: {
|
||||||
|
var params = []
|
||||||
|
var param = {}
|
||||||
|
param["paramTypeId"] = root.repeatState.stateTypeId;
|
||||||
|
param["value"] = allowedValues[(allowedValues.indexOf(root.repeatState.value) + 1) % 3]
|
||||||
|
params.push(param)
|
||||||
|
engine.thingManager.executeAction(root.thing.id, root.repeatState.stateTypeId, params)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.preferredHeight: app.iconSize
|
||||||
|
Layout.fillWidth: true
|
||||||
|
visible: root.shuffleState !== null
|
||||||
|
|
||||||
|
HeaderButton {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
imageSource: "../images/media-playlist-shuffle.svg"
|
||||||
|
color: root.shuffleState.value === true ? app.accentColor: keyColor
|
||||||
|
onClicked: {
|
||||||
|
var params = []
|
||||||
|
var param = {}
|
||||||
|
param["paramTypeId"] = root.shuffleState.stateTypeId
|
||||||
|
param["value"] = !root.shuffleState.value
|
||||||
|
params.push(param)
|
||||||
|
engine.thingManager.executeAction(root.thing.id, root.shuffleState.stateTypeId, params)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: volumeButtonContainer
|
||||||
|
Layout.fillWidth: true; Layout.fillHeight: true
|
||||||
|
HeaderButton {
|
||||||
|
id: volumeButton
|
||||||
|
anchors.centerIn: parent
|
||||||
|
imageSource: "../images/audio-speakers-symbolic.svg"
|
||||||
|
onClicked: {
|
||||||
|
print(volumeButton.x, volumeButton.y)
|
||||||
|
print(Qt.point(volumeButton.x, volumeButton.y))
|
||||||
|
print(volumeButton.mapToItem(root, volumeButton.x,0))
|
||||||
|
var buttonPosition = root.mapFromItem(volumeButtonContainer, volumeButton.x, 0)
|
||||||
|
var sliderHeight = 200
|
||||||
|
var props = {}
|
||||||
|
props["x"] = buttonPosition.x
|
||||||
|
props["y"] = buttonPosition.y - sliderHeight
|
||||||
|
props["height"] = sliderHeight
|
||||||
|
var sliderPane = volumeSliderPaneComponent.createObject(root, props)
|
||||||
|
sliderPane.open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: volumeSliderPaneComponent
|
||||||
|
Dialog {
|
||||||
|
id: volumeSliderDialog
|
||||||
|
|
||||||
|
leftPadding: 0
|
||||||
|
topPadding: app.margins / 2
|
||||||
|
rightPadding: 0
|
||||||
|
bottomPadding: app.margins / 2
|
||||||
|
modal: true
|
||||||
|
|
||||||
|
property int pendingVolumeValue: -1
|
||||||
|
|
||||||
|
contentItem: ColumnLayout {
|
||||||
|
ThrottledSlider {
|
||||||
|
Layout.fillHeight: true
|
||||||
|
from: 0
|
||||||
|
to: 100
|
||||||
|
value: root.volumeState.value
|
||||||
|
orientation: Qt.Vertical
|
||||||
|
onMoved: engine.thingManager.executeAction(root.thing.id, root.volumeState.stateTypeId, [{paramTypeId: root.volumeState.stateTypeId, value: value}])
|
||||||
|
}
|
||||||
|
|
||||||
|
HeaderButton {
|
||||||
|
imageSource: "../images/audio-speakers-muted-symbolic.svg"
|
||||||
|
color: root.muteState.value === true ? app.accentColor : keyColor
|
||||||
|
onClicked: engine.thingManager.executeAction(root.thing.id, root.muteState.stateTypeId, [{paramTypeId: root.muteState.stateTypeId, value: !root.muteState.value}]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -64,25 +64,19 @@ ChartView {
|
|||||||
function refresh() {
|
function refresh() {
|
||||||
pieSeries.clear();
|
pieSeries.clear();
|
||||||
d.sliceMap = {}
|
d.sliceMap = {}
|
||||||
print("calculating", chart.multiplier)
|
|
||||||
for (var i = 0; i < meters.count; i++) {
|
for (var i = 0; i < meters.count; i++) {
|
||||||
var thing = meters.get(i);
|
var thing = meters.get(i);
|
||||||
print("thing:", thing.name)
|
|
||||||
var value = 0;
|
var value = 0;
|
||||||
var totalConsumedStateType = thing.thingClass.stateTypes.findByName("totalEnergyConsumed")
|
var totalConsumedStateType = thing.thingClass.stateTypes.findByName("totalEnergyConsumed")
|
||||||
if (totalConsumedStateType) {
|
if (totalConsumedStateType) {
|
||||||
var totalConsumedState = thing.states.getState(totalConsumedStateType.id)
|
var totalConsumedState = thing.states.getState(totalConsumedStateType.id)
|
||||||
value = value + (totalConsumedState.value * chart.multiplier)
|
value = value + (totalConsumedState.value * chart.multiplier)
|
||||||
print("Adding", totalConsumedState.value * chart.multiplier, value)
|
|
||||||
}
|
}
|
||||||
var totalProducedStateType = thing.thingClass.stateTypes.findByName("totalEnergyProduced")
|
var totalProducedStateType = thing.thingClass.stateTypes.findByName("totalEnergyProduced")
|
||||||
if (totalProducedStateType) {
|
if (totalProducedStateType) {
|
||||||
var totalProducedState = thing.states.getState(totalProducedStateType.id)
|
var totalProducedState = thing.states.getState(totalProducedStateType.id)
|
||||||
value = value - (totalProducedState.value * chart.multiplier)
|
value = value - (totalProducedState.value * chart.multiplier)
|
||||||
print("removing", totalProducedState.value * chart.multiplier, value)
|
|
||||||
}
|
}
|
||||||
print("consumed", totalConsumedState.value, "produced", totalProducedState.value)
|
|
||||||
print("value", value)
|
|
||||||
var slice = pieSeries.append(thing.name, Math.max(0, value))
|
var slice = pieSeries.append(thing.name, Math.max(0, value))
|
||||||
var color = app.accentColor
|
var color = app.accentColor
|
||||||
for (var j = 0; j < i; j+=2) {
|
for (var j = 0; j < i; j+=2) {
|
||||||
|
|||||||
@ -36,6 +36,8 @@ Item {
|
|||||||
implicitHeight: slider.implicitHeight
|
implicitHeight: slider.implicitHeight
|
||||||
implicitWidth: slider.implicitWidth
|
implicitWidth: slider.implicitWidth
|
||||||
|
|
||||||
|
property alias orientation: slider.orientation
|
||||||
|
|
||||||
property real value: 0
|
property real value: 0
|
||||||
property alias from: slider.from
|
property alias from: slider.from
|
||||||
property alias to: slider.to
|
property alias to: slider.to
|
||||||
@ -49,6 +51,7 @@ Item {
|
|||||||
Slider {
|
Slider {
|
||||||
id: slider
|
id: slider
|
||||||
anchors.left: parent.left; anchors.right: parent.right
|
anchors.left: parent.left; anchors.right: parent.right
|
||||||
|
anchors.top: parent.top; anchors.bottom: parent.bottom
|
||||||
from: 0
|
from: 0
|
||||||
to: 100
|
to: 100
|
||||||
property var lastSentTime: new Date()
|
property var lastSentTime: new Date()
|
||||||
|
|||||||
@ -143,8 +143,8 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
min: Math.floor(logsModelNg.minValue - Math.abs(logsModelNg.minValue * .05))
|
min: Math.floor(logsModelNg.minValue - Math.abs(logsModelNg.minValue * .05))
|
||||||
onMinChanged: print("min set to", min)
|
onMinChanged: applyNiceNumbers();
|
||||||
onMaxChanged: print("max set to", min)
|
onMaxChanged: applyNiceNumbers();
|
||||||
labelsFont.pixelSize: app.smallFont
|
labelsFont.pixelSize: app.smallFont
|
||||||
labelFormat: {
|
labelFormat: {
|
||||||
switch (root.stateType.type.toLowerCase()) {
|
switch (root.stateType.type.toLowerCase()) {
|
||||||
|
|||||||
@ -1,114 +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
|
|
||||||
*
|
|
||||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
||||||
|
|
||||||
import QtQuick 2.9
|
|
||||||
import QtQuick.Controls 2.2
|
|
||||||
import QtQuick.Controls.Material 2.2
|
|
||||||
import QtQuick.Layouts 1.1
|
|
||||||
import Nymea 1.0
|
|
||||||
import "../components"
|
|
||||||
import "../customviews"
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
property var device: null
|
|
||||||
property var stateType: null
|
|
||||||
|
|
||||||
TabBar {
|
|
||||||
id: zoomTabBar
|
|
||||||
Layout.fillWidth: true
|
|
||||||
TabButton {
|
|
||||||
text: qsTr("6 h")
|
|
||||||
property int avg: ValueLogsProxyModel.AverageQuarterHour
|
|
||||||
property date startTime: {
|
|
||||||
var date = new Date();
|
|
||||||
date.setHours(new Date().getHours() - 6)
|
|
||||||
date.setMinutes(0)
|
|
||||||
date.setSeconds(0)
|
|
||||||
return date;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
TabButton {
|
|
||||||
text: qsTr("24 h")
|
|
||||||
property int avg: ValueLogsProxyModel.AverageHourly
|
|
||||||
property date startTime: {
|
|
||||||
var date = new Date();
|
|
||||||
date.setHours(new Date().getHours() - 24);
|
|
||||||
date.setMinutes(0)
|
|
||||||
date.setSeconds(0)
|
|
||||||
return date;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
TabButton {
|
|
||||||
text: qsTr("7 d")
|
|
||||||
property int avg: ValueLogsProxyModel.AverageDayTime
|
|
||||||
property date startTime: {
|
|
||||||
var date = new Date();
|
|
||||||
date.setDate(new Date().getDate() - 7);
|
|
||||||
date.setHours(0)
|
|
||||||
date.setMinutes(0)
|
|
||||||
date.setSeconds(0)
|
|
||||||
return date;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Graph {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
mode: settings.graphStyle
|
|
||||||
color: app.accentColor
|
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: updateTimer
|
|
||||||
interval: 10
|
|
||||||
repeat: false
|
|
||||||
onTriggered: {
|
|
||||||
graphModel.update()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
model: ValueLogsProxyModel {
|
|
||||||
id: graphModel
|
|
||||||
deviceId: root.device.id
|
|
||||||
typeIds: [stateType.id]
|
|
||||||
average: zoomTabBar.currentItem.avg
|
|
||||||
startTime: zoomTabBar.currentItem.startTime
|
|
||||||
Component.onCompleted: updateTimer.start();
|
|
||||||
onAverageChanged: updateTimer.start()
|
|
||||||
onStartTimeChanged: updateTimer.start();
|
|
||||||
engine: _engine
|
|
||||||
|
|
||||||
// Live doesn't work yet with ValueLogsProxyModel
|
|
||||||
// live: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,145 +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
|
|
||||||
*
|
|
||||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
||||||
|
|
||||||
import QtQuick 2.5
|
|
||||||
import QtQuick.Controls 2.1
|
|
||||||
import QtQuick.Controls.Material 2.1
|
|
||||||
import QtQuick.Layouts 1.3
|
|
||||||
import "../components"
|
|
||||||
import Nymea 1.0
|
|
||||||
|
|
||||||
CustomViewBase {
|
|
||||||
id: root
|
|
||||||
implicitHeight: grid.implicitHeight + app.margins * 2
|
|
||||||
|
|
||||||
property string interfaceName
|
|
||||||
|
|
||||||
readonly property string stateTypeName: {
|
|
||||||
switch (interfaceName) {
|
|
||||||
case "lightsensor":
|
|
||||||
return "lightIntensity";
|
|
||||||
default:
|
|
||||||
return interfaceName.replace("sensor", "");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly property var stateType: deviceClass.stateTypes.findByName(stateTypeName)
|
|
||||||
readonly property var deviceState: device.states.getState(stateType.id)
|
|
||||||
|
|
||||||
ValueLogsProxyModel {
|
|
||||||
id: logsModel
|
|
||||||
engine: _engine
|
|
||||||
deviceId: root.device.id
|
|
||||||
typeIds: [stateType.id]
|
|
||||||
average: zoomTabBar.currentItem.avg
|
|
||||||
startTime: zoomTabBar.currentItem.startTime
|
|
||||||
Component.onCompleted: updateTimer.start();
|
|
||||||
onAverageChanged: updateTimer.start()
|
|
||||||
onStartTimeChanged: updateTimer.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: updateTimer
|
|
||||||
interval: 10
|
|
||||||
repeat: false
|
|
||||||
onTriggered: {
|
|
||||||
print("updating:", logsModel.startTime)
|
|
||||||
logsModel.update()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
id: grid
|
|
||||||
anchors { left: parent.left; top: parent.top; right: parent.right }
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.leftMargin: app.margins
|
|
||||||
Layout.rightMargin: app.margins
|
|
||||||
spacing: app.margins
|
|
||||||
ColorIcon {
|
|
||||||
name: app.interfaceToIcon(root.interfaceName)
|
|
||||||
height: app.iconSize
|
|
||||||
width: height
|
|
||||||
color: app.interfaceToColor(root.interfaceName)
|
|
||||||
}
|
|
||||||
Label {
|
|
||||||
text: Types.toUiValue(deviceState.value, stateType.unit) + " " + Types.toUiUnit(stateType.unit)
|
|
||||||
font.pixelSize: app.largeFont
|
|
||||||
}
|
|
||||||
|
|
||||||
TabBar {
|
|
||||||
id: zoomTabBar
|
|
||||||
Layout.fillWidth: true
|
|
||||||
TabButton {
|
|
||||||
text: qsTr("6 h")
|
|
||||||
property int avg: ValueLogsProxyModel.AverageQuarterHour
|
|
||||||
property date startTime: {
|
|
||||||
var date = new Date();
|
|
||||||
date.setHours(new Date().getHours() - 6)
|
|
||||||
date.setMinutes(0)
|
|
||||||
date.setSeconds(0)
|
|
||||||
return date;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
TabButton {
|
|
||||||
text: qsTr("24 h")
|
|
||||||
property int avg: ValueLogsProxyModel.AverageHourly
|
|
||||||
property date startTime: {
|
|
||||||
var date = new Date();
|
|
||||||
date.setHours(new Date().getHours() - 24);
|
|
||||||
date.setMinutes(0)
|
|
||||||
date.setSeconds(0)
|
|
||||||
return date;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
TabButton {
|
|
||||||
text: qsTr("7 d")
|
|
||||||
property int avg: ValueLogsProxyModel.AverageDayTime
|
|
||||||
property date startTime: {
|
|
||||||
var date = new Date();
|
|
||||||
date.setDate(new Date().getDate() - 7);
|
|
||||||
date.setHours(0)
|
|
||||||
date.setMinutes(0)
|
|
||||||
date.setSeconds(0)
|
|
||||||
return date;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Graph {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.preferredHeight: 200
|
|
||||||
model: logsModel
|
|
||||||
mode: settings.graphStyle
|
|
||||||
color: app.interfaceToColor(root.interfaceName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -227,7 +227,7 @@ MainPageTile {
|
|||||||
|
|
||||||
MediaControls {
|
MediaControls {
|
||||||
iconSize: app.iconSize * 1.2
|
iconSize: app.iconSize * 1.2
|
||||||
device: inlineMediaControl.currentDevice
|
thing: inlineMediaControl.currentDevice
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -145,7 +145,7 @@ DeviceListPageBase {
|
|||||||
}
|
}
|
||||||
MediaControls {
|
MediaControls {
|
||||||
visible: itemDelegate.deviceClass.interfaces.indexOf("mediacontroller") >= 0
|
visible: itemDelegate.deviceClass.interfaces.indexOf("mediacontroller") >= 0
|
||||||
device: itemDelegate.device
|
thing: itemDelegate.device
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Item {
|
Item {
|
||||||
|
|||||||
@ -141,12 +141,7 @@ Page {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var source;
|
var source = Qt.resolvedUrl("../customviews/GenericTypeGraph.qml");
|
||||||
if (engine.jsonRpcClient.ensureServerVersion("1.10")) {
|
|
||||||
source = Qt.resolvedUrl("../customviews/GenericTypeGraph.qml");
|
|
||||||
} else {
|
|
||||||
source = Qt.resolvedUrl("../customviews/GenericTypeGraphPre110.qml");
|
|
||||||
}
|
|
||||||
setSource(source, {device: root.device, stateType: stateType})
|
setSource(source, {device: root.device, stateType: stateType})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -173,7 +173,7 @@ DevicePageBase {
|
|||||||
MediaArtworkImage {
|
MediaArtworkImage {
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
Layout.preferredWidth: parent.width / parent.columns
|
Layout.preferredWidth: parent.width / parent.columns
|
||||||
device: root.device
|
thing: root.device
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
@ -208,7 +208,7 @@ DevicePageBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
MediaControls {
|
MediaControls {
|
||||||
device: root.device
|
thing: root.device
|
||||||
iconSize: app.iconSize * 2
|
iconSize: app.iconSize * 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -220,63 +220,8 @@ DevicePageBase {
|
|||||||
Component {
|
Component {
|
||||||
id: browserComponent
|
id: browserComponent
|
||||||
|
|
||||||
Item {
|
MediaBrowser {
|
||||||
|
thing: root.device
|
||||||
function backPressed() {
|
|
||||||
if (internalPageStack.depth > 1) {
|
|
||||||
internalPageStack.pop();
|
|
||||||
} else {
|
|
||||||
swipeView.currentIndex--
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
StackView {
|
|
||||||
id: internalPageStack
|
|
||||||
anchors.fill: parent
|
|
||||||
initialItem: internalBrowserPage
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: internalBrowserPage
|
|
||||||
ListView {
|
|
||||||
id: listView
|
|
||||||
model: browserItems
|
|
||||||
ScrollBar.vertical: ScrollBar {}
|
|
||||||
|
|
||||||
property string nodeId: ""
|
|
||||||
|
|
||||||
// Need to keep a explicit property here or the GC will eat it too early
|
|
||||||
property BrowserItems browserItems: null
|
|
||||||
Component.onCompleted: {
|
|
||||||
browserItems = engine.deviceManager.browseDevice(root.device.id, nodeId);
|
|
||||||
}
|
|
||||||
|
|
||||||
delegate: BrowserItemDelegate {
|
|
||||||
iconName: "../images/browser/" + (model.mediaIcon && model.mediaIcon !== "MediaBrowserIconNone" ? model.mediaIcon : model.icon) + ".svg"
|
|
||||||
busy: d.pendingItemId === model.id
|
|
||||||
device: root.device
|
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
print("clicked:", model.id)
|
|
||||||
if (model.executable) {
|
|
||||||
root.executeBrowserItem(model.id)
|
|
||||||
} else if (model.browsable) {
|
|
||||||
internalPageStack.push(internalBrowserPage, {device: root.device, nodeId: model.id})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onContextMenuActionTriggered: {
|
|
||||||
root.executeBrowserItemAction(model.id, actionTypeId, params)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
BusyIndicator {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
running: listView.model.busy
|
|
||||||
visible: running
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -287,7 +232,6 @@ DevicePageBase {
|
|||||||
swipeView.currentIndex--;
|
swipeView.currentIndex--;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: app.margins
|
anchors.margins: app.margins
|
||||||
@ -300,49 +244,13 @@ DevicePageBase {
|
|||||||
|
|
||||||
MediaControls {
|
MediaControls {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
device: root.device
|
thing: root.device
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: volumeSliderPaneComponent
|
|
||||||
Dialog {
|
|
||||||
|
|
||||||
leftPadding: 0
|
|
||||||
topPadding: app.margins / 2
|
|
||||||
rightPadding: 0
|
|
||||||
bottomPadding: app.margins / 2
|
|
||||||
modal: true
|
|
||||||
|
|
||||||
contentItem: ColumnLayout {
|
|
||||||
Slider {
|
|
||||||
Layout.fillHeight: true
|
|
||||||
orientation: Qt.Vertical
|
|
||||||
from: 0
|
|
||||||
to: 100
|
|
||||||
value: d.pendingVolumeValue != -1 ? d.pendingVolumeValue : root.stateValue("volume")
|
|
||||||
onMoved: root.adjustVolume(value)
|
|
||||||
}
|
|
||||||
HeaderButton {
|
|
||||||
imageSource: "../images/audio-speakers-muted-symbolic.svg"
|
|
||||||
color: root.stateValue("mute") ? app.accentColor : keyColor
|
|
||||||
onClicked: {
|
|
||||||
var params = []
|
|
||||||
var muteParam = {}
|
|
||||||
muteParam["paramTypeId"] = root.deviceClass.actionTypes.findByName("mute").id
|
|
||||||
muteParam["value"] = !root.stateValue("mute");
|
|
||||||
params.push(muteParam)
|
|
||||||
root.executeAction("mute", params);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
footer: Pane {
|
footer: Pane {
|
||||||
Material.elevation: 1
|
Material.elevation: 1
|
||||||
height: 52
|
height: 52
|
||||||
@ -374,64 +282,11 @@ DevicePageBase {
|
|||||||
onClicked: swipeView.currentIndex--
|
onClicked: swipeView.currentIndex--
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Item {
|
ShuffleRepeatVolumeControl {
|
||||||
Layout.fillWidth: true; Layout.fillHeight: true
|
Layout.fillWidth: true
|
||||||
visible: root.deviceClass.interfaces.indexOf("shufflerepeat") >= 0
|
thing: root.device
|
||||||
HeaderButton {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
imageSource: root.stateValue("repeat") === "One" ? "../images/media-playlist-repeat-one.svg" : "../images/media-playlist-repeat.svg"
|
|
||||||
color: root.stateValue("repeat") === "None" ? keyColor : app.accentColor
|
|
||||||
property var allowedValues: ["None", "All", "One"]
|
|
||||||
onClicked: {
|
|
||||||
var params = []
|
|
||||||
var param = {}
|
|
||||||
param["paramTypeId"] = root.deviceClass.actionTypes.findByName("repeat").id;
|
|
||||||
param["value"] = allowedValues[(allowedValues.indexOf(root.stateValue("repeat")) + 1) % 3]
|
|
||||||
params.push(param)
|
|
||||||
root.executeAction("repeat", params)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Item {
|
|
||||||
Layout.fillWidth: true; Layout.fillHeight: true
|
|
||||||
visible: root.deviceClass.interfaces.indexOf("shufflerepeat") >= 0
|
|
||||||
HeaderButton {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
imageSource: "../images/media-playlist-shuffle.svg"
|
|
||||||
color: root.stateValue("shuffle") ? app.accentColor: keyColor
|
|
||||||
onClicked: {
|
|
||||||
var params = []
|
|
||||||
var param = {}
|
|
||||||
param["paramTypeId"] = root.deviceClass.actionTypes.findByName("shuffle").id;
|
|
||||||
param["value"] = !root.stateValue("shuffle")
|
|
||||||
params.push(param)
|
|
||||||
root.executeAction("shuffle", params)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Item {
|
|
||||||
id: volumeButtonContainer
|
|
||||||
Layout.fillWidth: true; Layout.fillHeight: true
|
|
||||||
HeaderButton {
|
|
||||||
id: volumeButton
|
|
||||||
anchors.centerIn: parent
|
|
||||||
imageSource: "../images/audio-speakers-symbolic.svg"
|
|
||||||
onClicked: {
|
|
||||||
print("...");
|
|
||||||
print(volumeButton.x, volumeButton.y)
|
|
||||||
print(Qt.point(volumeButton.x, volumeButton.y))
|
|
||||||
print(volumeButton.mapToItem(root, volumeButton.x,0))
|
|
||||||
var buttonPosition = root.mapFromItem(volumeButtonContainer, volumeButton.x, 0)
|
|
||||||
var sliderHeight = 200
|
|
||||||
var props = {}
|
|
||||||
props["x"] = buttonPosition.x
|
|
||||||
props["y"] = root.height - sliderHeight - root.footer.height
|
|
||||||
props["height"] = sliderHeight
|
|
||||||
var sliderPane = volumeSliderPaneComponent.createObject(root, props)
|
|
||||||
sliderPane.open()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
Layout.preferredWidth: swipeView.count > 1 && swipeView.currentIndex < swipeView.count - 1 ? parent.width / 4 : 0
|
Layout.preferredWidth: swipeView.count > 1 && swipeView.currentIndex < swipeView.count - 1 ? parent.width / 4 : 0
|
||||||
|
|||||||
@ -38,16 +38,160 @@ import "../customviews"
|
|||||||
DevicePageBase {
|
DevicePageBase {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
Loader {
|
Flickable {
|
||||||
anchors.fill: parent
|
id: listView
|
||||||
Component.onCompleted: {
|
anchors { fill: parent }
|
||||||
var src
|
interactive: contentHeight > height
|
||||||
if (engine.jsonRpcClient.ensureServerVersion("1.10")) {
|
contentHeight: contentGrid.implicitHeight
|
||||||
src = "SensorDevicePagePost110.qml"
|
|
||||||
} else {
|
GridLayout {
|
||||||
src = "SensorDevicePagePre110.qml"
|
id: contentGrid
|
||||||
|
width: parent.width
|
||||||
|
columns: width / 300
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: ListModel {
|
||||||
|
Component.onCompleted: {
|
||||||
|
var supportedInterfaces = ["temperaturesensor", "humiditysensor", "pressuresensor", "moisturesensor", "lightsensor", "conductivitysensor", "noisesensor", "co2sensor", "presencesensor", "daylightsensor", "closablesensor"]
|
||||||
|
for (var i = 0; i < supportedInterfaces.length; i++) {
|
||||||
|
if (root.deviceClass.interfaces.indexOf(supportedInterfaces[i]) >= 0) {
|
||||||
|
append({name: supportedInterfaces[i]});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate: Loader {
|
||||||
|
id: loader
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: item.implicitHeight
|
||||||
|
|
||||||
|
property StateType stateType: root.deviceClass.stateTypes.findByName(interfaceStateMap[modelData])
|
||||||
|
property string interfaceName: modelData
|
||||||
|
|
||||||
|
// sourceComponent: stateType && stateType.type.toLowerCase() === "bool" ? boolComponent : graphComponent
|
||||||
|
sourceComponent: graphComponent
|
||||||
|
|
||||||
|
property var interfaceStateMap: {
|
||||||
|
"temperaturesensor": "temperature",
|
||||||
|
"humiditysensor": "humidity",
|
||||||
|
"pressuresensor": "pressure",
|
||||||
|
"moisturesensor": "moisture",
|
||||||
|
"lightsensor": "lightIntensity",
|
||||||
|
"conductivitysensor": "conductivity",
|
||||||
|
"noisesensor": "noise",
|
||||||
|
"co2sensor": "co2",
|
||||||
|
"presencesensor": "isPresent",
|
||||||
|
"daylightsensor": "daylight",
|
||||||
|
"closablesensor": "closed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: graphComponent
|
||||||
|
|
||||||
|
GenericTypeGraph {
|
||||||
|
device: root.device
|
||||||
|
color: app.interfaceToColor(interfaceName)
|
||||||
|
iconSource: app.interfaceToIcon(interfaceName)
|
||||||
|
implicitHeight: width * .6
|
||||||
|
property string interfaceName: parent.interfaceName
|
||||||
|
stateType: parent.stateType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: boolComponent
|
||||||
|
GridLayout {
|
||||||
|
id: boolView
|
||||||
|
property string interfaceName: parent.interfaceName
|
||||||
|
property StateType stateType: parent.stateType
|
||||||
|
height: listView.height
|
||||||
|
columns: app.landscape ? 2 : 1
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
Layout.minimumWidth: app.iconSize * 5
|
||||||
|
Layout.rowSpan: app.landscape ? 5 : 1
|
||||||
|
ColorIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
height: app.iconSize * 4
|
||||||
|
width: height
|
||||||
|
name: {
|
||||||
|
switch (boolView.interfaceName) {
|
||||||
|
case "closablesensor":
|
||||||
|
return device.states.getState(boolView.stateType.id).value === true ? Qt.resolvedUrl("../images/lock-closed.svg") : Qt.resolvedUrl("../images/lock-open.svg")
|
||||||
|
default:
|
||||||
|
return app.interfaceToIcon(boolView.interfaceName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
color: {
|
||||||
|
switch (boolView.interfaceName) {
|
||||||
|
case "closablesensor":
|
||||||
|
return device.states.getState(boolView.stateType.id).value === true ? "green" : "red"
|
||||||
|
default:
|
||||||
|
device.states.getState(boolView.stateType.id).value === true ? app.interfaceToColor(boolView.interfaceName) : keyColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Item {
|
||||||
|
Layout.fillHeight: true
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: false
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
property StateType lastSeenStateType: root.deviceClass.stateTypes.findByName("lastSeenTime")
|
||||||
|
property State lastSeenState: lastSeenStateType ? root.device.states.getState(lastSeenStateType.id) : null
|
||||||
|
visible: lastSeenStateType !== null
|
||||||
|
Label {
|
||||||
|
text: qsTr("Last seen:")
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
text: parent.lastSeenState ? Qt.formatDateTime(new Date(parent.lastSeenState.value * 1000)) : ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: false
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
property StateType sunriseStateType: root.deviceClass.stateTypes.findByName("sunriseTime")
|
||||||
|
property State sunriseState: sunriseStateType ? root.device.states.getState(sunriseStateType.id) : null
|
||||||
|
visible: sunriseStateType !== null
|
||||||
|
Label {
|
||||||
|
text: qsTr("Sunrise:")
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
text: parent.sunriseStateType ? Qt.formatDateTime(new Date(parent.sunriseState.value * 1000)) : ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: false
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
property StateType sunsetStateType: root.deviceClass.stateTypes.findByName("sunsetTime")
|
||||||
|
property State sunsetState: sunsetStateType ? root.device.states.getState(sunsetStateType.id) : null
|
||||||
|
visible: sunsetStateType !== null
|
||||||
|
Label {
|
||||||
|
text: qsTr("Sunset:")
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
text: parent.sunsetStateType ? Qt.formatDateTime(new Date(parent.sunsetState.value * 1000)) : ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Item {
|
||||||
|
Layout.fillHeight: true
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
setSource(Qt.resolvedUrl(src), {device: root.device, deviceClass: root.deviceClass})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,192 +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
|
|
||||||
*
|
|
||||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
||||||
|
|
||||||
import QtQuick 2.5
|
|
||||||
import QtQuick.Controls 2.1
|
|
||||||
import QtQuick.Layouts 1.1
|
|
||||||
import Nymea 1.0
|
|
||||||
import "../components"
|
|
||||||
import "../customviews"
|
|
||||||
|
|
||||||
Flickable {
|
|
||||||
id: listView
|
|
||||||
anchors { fill: parent }
|
|
||||||
interactive: contentHeight > height
|
|
||||||
contentHeight: contentGrid.implicitHeight
|
|
||||||
|
|
||||||
GridLayout {
|
|
||||||
id: contentGrid
|
|
||||||
width: parent.width
|
|
||||||
columns: width / 300
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: ListModel {
|
|
||||||
Component.onCompleted: {
|
|
||||||
var supportedInterfaces = ["temperaturesensor", "humiditysensor", "pressuresensor", "moisturesensor", "lightsensor", "conductivitysensor", "noisesensor", "co2sensor", "presencesensor", "daylightsensor", "closablesensor"]
|
|
||||||
for (var i = 0; i < supportedInterfaces.length; i++) {
|
|
||||||
if (root.deviceClass.interfaces.indexOf(supportedInterfaces[i]) >= 0) {
|
|
||||||
append({name: supportedInterfaces[i]});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
delegate: Loader {
|
|
||||||
id: loader
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.preferredHeight: item.implicitHeight
|
|
||||||
|
|
||||||
property StateType stateType: root.deviceClass.stateTypes.findByName(interfaceStateMap[modelData])
|
|
||||||
property string interfaceName: modelData
|
|
||||||
|
|
||||||
// sourceComponent: stateType && stateType.type.toLowerCase() === "bool" ? boolComponent : graphComponent
|
|
||||||
sourceComponent: graphComponent
|
|
||||||
|
|
||||||
property var interfaceStateMap: {
|
|
||||||
"temperaturesensor": "temperature",
|
|
||||||
"humiditysensor": "humidity",
|
|
||||||
"pressuresensor": "pressure",
|
|
||||||
"moisturesensor": "moisture",
|
|
||||||
"lightsensor": "lightIntensity",
|
|
||||||
"conductivitysensor": "conductivity",
|
|
||||||
"noisesensor": "noise",
|
|
||||||
"co2sensor": "co2",
|
|
||||||
"presencesensor": "isPresent",
|
|
||||||
"daylightsensor": "daylight",
|
|
||||||
"closablesensor": "closed"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: graphComponent
|
|
||||||
|
|
||||||
GenericTypeGraph {
|
|
||||||
device: root.device
|
|
||||||
color: app.interfaceToColor(interfaceName)
|
|
||||||
iconSource: app.interfaceToIcon(interfaceName)
|
|
||||||
implicitHeight: width * .6
|
|
||||||
property string interfaceName: parent.interfaceName
|
|
||||||
stateType: parent.stateType
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: boolComponent
|
|
||||||
GridLayout {
|
|
||||||
id: boolView
|
|
||||||
property string interfaceName: parent.interfaceName
|
|
||||||
property StateType stateType: parent.stateType
|
|
||||||
height: listView.height
|
|
||||||
columns: app.landscape ? 2 : 1
|
|
||||||
Item {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
Layout.minimumWidth: app.iconSize * 5
|
|
||||||
Layout.rowSpan: app.landscape ? 5 : 1
|
|
||||||
ColorIcon {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
height: app.iconSize * 4
|
|
||||||
width: height
|
|
||||||
name: {
|
|
||||||
switch (boolView.interfaceName) {
|
|
||||||
case "closablesensor":
|
|
||||||
return device.states.getState(boolView.stateType.id).value === true ? Qt.resolvedUrl("../images/lock-closed.svg") : Qt.resolvedUrl("../images/lock-open.svg")
|
|
||||||
default:
|
|
||||||
return app.interfaceToIcon(boolView.interfaceName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
color: {
|
|
||||||
switch (boolView.interfaceName) {
|
|
||||||
case "closablesensor":
|
|
||||||
return device.states.getState(boolView.stateType.id).value === true ? "green" : "red"
|
|
||||||
default:
|
|
||||||
device.states.getState(boolView.stateType.id).value === true ? app.interfaceToColor(boolView.interfaceName) : keyColor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Item {
|
|
||||||
Layout.fillHeight: true
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: false
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
property StateType lastSeenStateType: root.deviceClass.stateTypes.findByName("lastSeenTime")
|
|
||||||
property State lastSeenState: lastSeenStateType ? root.device.states.getState(lastSeenStateType.id) : null
|
|
||||||
visible: lastSeenStateType !== null
|
|
||||||
Label {
|
|
||||||
text: qsTr("Last seen:")
|
|
||||||
font.bold: true
|
|
||||||
}
|
|
||||||
Label {
|
|
||||||
text: parent.lastSeenState ? Qt.formatDateTime(new Date(parent.lastSeenState.value * 1000)) : ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: false
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
property StateType sunriseStateType: root.deviceClass.stateTypes.findByName("sunriseTime")
|
|
||||||
property State sunriseState: sunriseStateType ? root.device.states.getState(sunriseStateType.id) : null
|
|
||||||
visible: sunriseStateType !== null
|
|
||||||
Label {
|
|
||||||
text: qsTr("Sunrise:")
|
|
||||||
font.bold: true
|
|
||||||
}
|
|
||||||
Label {
|
|
||||||
text: parent.sunriseStateType ? Qt.formatDateTime(new Date(parent.sunriseState.value * 1000)) : ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: false
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
property StateType sunsetStateType: root.deviceClass.stateTypes.findByName("sunsetTime")
|
|
||||||
property State sunsetState: sunsetStateType ? root.device.states.getState(sunsetStateType.id) : null
|
|
||||||
visible: sunsetStateType !== null
|
|
||||||
Label {
|
|
||||||
text: qsTr("Sunset:")
|
|
||||||
font.bold: true
|
|
||||||
}
|
|
||||||
Label {
|
|
||||||
text: parent.sunsetStateType ? Qt.formatDateTime(new Date(parent.sunsetState.value * 1000)) : ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Item {
|
|
||||||
Layout.fillHeight: true
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,61 +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
|
|
||||||
*
|
|
||||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
||||||
|
|
||||||
import QtQuick 2.5
|
|
||||||
import QtQuick.Controls 2.1
|
|
||||||
import QtQuick.Layouts 1.1
|
|
||||||
import Nymea 1.0
|
|
||||||
import "../components"
|
|
||||||
import "../customviews"
|
|
||||||
|
|
||||||
ListView {
|
|
||||||
anchors { fill: parent }
|
|
||||||
|
|
||||||
property var device
|
|
||||||
property var deviceClass
|
|
||||||
|
|
||||||
model: ListModel {
|
|
||||||
Component.onCompleted: {
|
|
||||||
var supportedInterfaces = ["temperaturesensor", "humiditysensor", "pressuresensor", "moisturesensor", "lightsensor", "conductivitysensor", "noisesensor", "co2sensor"]
|
|
||||||
for (var i = 0; i < supportedInterfaces.length; i++) {
|
|
||||||
print("checking", root.deviceClass.name, root.deviceClass.interfaces)
|
|
||||||
if (root.deviceClass.interfaces.indexOf(supportedInterfaces[i]) >= 0) {
|
|
||||||
append({name: supportedInterfaces[i]});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
delegate: SensorView {
|
|
||||||
width: parent.width
|
|
||||||
interfaceName: modelData
|
|
||||||
device: root.device
|
|
||||||
deviceClass: root.deviceClass
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -38,16 +38,55 @@ import "../customviews"
|
|||||||
DevicePageBase {
|
DevicePageBase {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
Loader {
|
Flickable {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
Component.onCompleted: {
|
clip: true
|
||||||
var src
|
contentHeight: contentColumn.implicitHeight
|
||||||
if (engine.jsonRpcClient.ensureServerVersion("1.10")) {
|
|
||||||
src = "WeatherDevicePagePost110.qml"
|
ColumnLayout {
|
||||||
} else {
|
id: contentColumn
|
||||||
src = "WeatherDevicePagePre110.qml"
|
width: parent.width
|
||||||
|
|
||||||
|
WeatherView {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
device: root.device
|
||||||
|
deviceClass: root.deviceClass
|
||||||
|
}
|
||||||
|
|
||||||
|
GridLayout {
|
||||||
|
id: content
|
||||||
|
Layout.fillWidth: true
|
||||||
|
columns: Math.min(width / 300, 4)
|
||||||
|
|
||||||
|
GenericTypeGraph {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
device: root.device
|
||||||
|
stateType: root.deviceClass.stateTypes.findByName("temperature")
|
||||||
|
iconSource: app.interfaceToIcon("temperaturesensor")
|
||||||
|
color: app.interfaceToColor("temperaturesensor")
|
||||||
|
}
|
||||||
|
GenericTypeGraph {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
device: root.device
|
||||||
|
stateType: root.deviceClass.stateTypes.findByName("humidity")
|
||||||
|
iconSource: app.interfaceToIcon("humiditysensor")
|
||||||
|
color: app.interfaceToColor("humiditysensor")
|
||||||
|
}
|
||||||
|
GenericTypeGraph {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
device: root.device
|
||||||
|
stateType: root.deviceClass.stateTypes.findByName("pressure")
|
||||||
|
iconSource: app.interfaceToIcon("pressuresensor")
|
||||||
|
color: app.interfaceToColor("pressuresensor")
|
||||||
|
}
|
||||||
|
GenericTypeGraph {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
device: root.device
|
||||||
|
stateType: root.deviceClass.stateTypes.findByName("windSpeed")
|
||||||
|
iconSource: app.interfaceToIcon("windspeedsensor")
|
||||||
|
color: app.interfaceToColor("windspeedsensor")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
setSource(Qt.resolvedUrl(src), {device: root.device, deviceClass: root.deviceClass})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,92 +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
|
|
||||||
*
|
|
||||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
||||||
|
|
||||||
import QtQuick 2.5
|
|
||||||
import QtQuick.Controls 2.1
|
|
||||||
import QtQuick.Layouts 1.1
|
|
||||||
import Nymea 1.0
|
|
||||||
import "../components"
|
|
||||||
import "../customviews"
|
|
||||||
|
|
||||||
Flickable {
|
|
||||||
anchors.fill: parent
|
|
||||||
clip: true
|
|
||||||
contentHeight: contentColumn.implicitHeight
|
|
||||||
|
|
||||||
property var device
|
|
||||||
property var deviceClass
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
id: contentColumn
|
|
||||||
width: parent.width
|
|
||||||
|
|
||||||
WeatherView {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
device: root.device
|
|
||||||
deviceClass: root.deviceClass
|
|
||||||
}
|
|
||||||
|
|
||||||
GridLayout {
|
|
||||||
id: content
|
|
||||||
Layout.fillWidth: true
|
|
||||||
columns: Math.min(width / 300, 4)
|
|
||||||
|
|
||||||
GenericTypeGraph {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
device: root.device
|
|
||||||
stateType: root.deviceClass.stateTypes.findByName("temperature")
|
|
||||||
iconSource: app.interfaceToIcon("temperaturesensor")
|
|
||||||
color: app.interfaceToColor("temperaturesensor")
|
|
||||||
}
|
|
||||||
GenericTypeGraph {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
device: root.device
|
|
||||||
stateType: root.deviceClass.stateTypes.findByName("humidity")
|
|
||||||
iconSource: app.interfaceToIcon("humiditysensor")
|
|
||||||
color: app.interfaceToColor("humiditysensor")
|
|
||||||
}
|
|
||||||
GenericTypeGraph {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
device: root.device
|
|
||||||
stateType: root.deviceClass.stateTypes.findByName("pressure")
|
|
||||||
iconSource: app.interfaceToIcon("pressuresensor")
|
|
||||||
color: app.interfaceToColor("pressuresensor")
|
|
||||||
}
|
|
||||||
GenericTypeGraph {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
device: root.device
|
|
||||||
stateType: root.deviceClass.stateTypes.findByName("windSpeed")
|
|
||||||
iconSource: app.interfaceToIcon("windspeedsensor")
|
|
||||||
color: app.interfaceToColor("windspeedsensor")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,74 +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
|
|
||||||
*
|
|
||||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
||||||
|
|
||||||
import QtQuick 2.5
|
|
||||||
import QtQuick.Controls 2.1
|
|
||||||
import QtQuick.Layouts 1.1
|
|
||||||
import Nymea 1.0
|
|
||||||
import "../components"
|
|
||||||
import "../customviews"
|
|
||||||
|
|
||||||
|
|
||||||
Flickable {
|
|
||||||
anchors.fill: parent
|
|
||||||
clip: true
|
|
||||||
contentHeight: content.implicitHeight
|
|
||||||
|
|
||||||
property var device
|
|
||||||
property var deviceClass
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
id: content
|
|
||||||
width: parent.width
|
|
||||||
WeatherView {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
device: root.device
|
|
||||||
deviceClass: root.deviceClass
|
|
||||||
}
|
|
||||||
SensorView {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
device: root.device
|
|
||||||
deviceClass: root.deviceClass
|
|
||||||
interfaceName: "temperaturesensor"
|
|
||||||
}
|
|
||||||
SensorView {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
device: root.device
|
|
||||||
deviceClass: root.deviceClass
|
|
||||||
interfaceName: "humiditysensor"
|
|
||||||
}
|
|
||||||
SensorView {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
device: root.device
|
|
||||||
deviceClass: root.deviceClass
|
|
||||||
interfaceName: "pressuresensor"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1 +1 @@
|
|||||||
../mediaplayer-app-symbolic.svg
|
../media.svg
|
||||||
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
@ -70,6 +70,7 @@ MainViewBase {
|
|||||||
width: parent.width
|
width: parent.width
|
||||||
visible: consumers.count > 0
|
visible: consumers.count > 0
|
||||||
columns: Math.floor(root.width / 300)
|
columns: Math.floor(root.width / 300)
|
||||||
|
rowSpacing: 0
|
||||||
|
|
||||||
SmartMeterChart {
|
SmartMeterChart {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
@ -78,6 +79,285 @@ MainViewBase {
|
|||||||
title: qsTr("Total consumed energy")
|
title: qsTr("Total consumed energy")
|
||||||
visible: consumers.count > 0
|
visible: consumers.count > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ChartView {
|
||||||
|
id: chartView
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: width * .75
|
||||||
|
legend.alignment: Qt.AlignBottom
|
||||||
|
legend.font.pixelSize: app.smallFont
|
||||||
|
legend.visible: false
|
||||||
|
backgroundColor: app.backgroundColor
|
||||||
|
|
||||||
|
property var startTime: xAxis.min
|
||||||
|
property var endTime: xAxis.max
|
||||||
|
|
||||||
|
property int sampleRate: XYSeriesAdapter.SampleRateMinute
|
||||||
|
|
||||||
|
BusyIndicator {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
visible: running
|
||||||
|
running: {
|
||||||
|
for (var i = 0; i < consumersRepeater.count; i++) {
|
||||||
|
if (consumersRepeater.itemAt(i).model.busy) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
id: consumersRepeater
|
||||||
|
model: consumers
|
||||||
|
|
||||||
|
delegate: Item {
|
||||||
|
id: consumer
|
||||||
|
property Thing thing: consumers.get(index)
|
||||||
|
|
||||||
|
property var model: LogsModel {
|
||||||
|
id: logsModel
|
||||||
|
engine: _engine
|
||||||
|
thingId: consumer.thing.id
|
||||||
|
typeIds: [consumer.thing.thingClass.stateTypes.findByName("currentPower").id]
|
||||||
|
viewStartTime: xAxis.min
|
||||||
|
live: true
|
||||||
|
}
|
||||||
|
property XYSeriesAdapter adapter: XYSeriesAdapter {
|
||||||
|
id: seriesAdapter
|
||||||
|
logsModel: logsModel
|
||||||
|
sampleRate: chartView.sampleRate
|
||||||
|
xySeries: upperSeries
|
||||||
|
}
|
||||||
|
property XYSeries lineSeries: LineSeries {
|
||||||
|
id: upperSeries
|
||||||
|
onPointAdded: {
|
||||||
|
var newPoint = upperSeries.at(index)
|
||||||
|
|
||||||
|
if (newPoint.x > lowerSeries.at(0).x) {
|
||||||
|
lowerSeries.replace(0, newPoint.x, 0)
|
||||||
|
}
|
||||||
|
if (newPoint.x < lowerSeries.at(1).x) {
|
||||||
|
lowerSeries.replace(1, newPoint.x, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LineSeries {
|
||||||
|
id: lowerSeries
|
||||||
|
XYPoint { x: xAxis.max.getTime(); y: 0 }
|
||||||
|
XYPoint { x: xAxis.min.getTime(); y: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
print("creating series")
|
||||||
|
var areaSeries = chartView.createSeries(ChartView.SeriesTypeArea, consumer.thing.name, xAxis, yAxis)
|
||||||
|
areaSeries.upperSeries = upperSeries;
|
||||||
|
if (index > 0) {
|
||||||
|
areaSeries.lowerSeries = consumersRepeater.itemAt(index - 1).lineSeries
|
||||||
|
seriesAdapter.baseSeries = consumersRepeater.itemAt(index - 1).lineSeries
|
||||||
|
} else {
|
||||||
|
areaSeries.lowerSeries = lowerSeries;
|
||||||
|
}
|
||||||
|
|
||||||
|
var color = app.accentColor
|
||||||
|
for (var j = 0; j < index; j+=2) {
|
||||||
|
if (index % 2 == 0) {
|
||||||
|
color = Qt.lighter(color, 1.2);
|
||||||
|
} else {
|
||||||
|
color = Qt.darker(color, 1.2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
areaSeries.color = color;
|
||||||
|
areaSeries.borderColor = color;
|
||||||
|
areaSeries.borderWidth = 0;
|
||||||
|
|
||||||
|
seriesAdapter.xySeries = series;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ValueAxis {
|
||||||
|
id: yAxis
|
||||||
|
readonly property XYSeriesAdapter adapter: consumersRepeater.itemAt(consumersRepeater.count - 1).adapter;
|
||||||
|
max: Math.ceil(adapter.maxValue + Math.abs(adapter.maxValue * .05))
|
||||||
|
min: Math.floor(adapter.minValue - Math.abs(adapter.minValue * .05))
|
||||||
|
onMinChanged: applyNiceNumbers();
|
||||||
|
onMaxChanged: applyNiceNumbers();
|
||||||
|
labelsFont.pixelSize: app.smallFont
|
||||||
|
labelFormat: {
|
||||||
|
return "%d";
|
||||||
|
// switch (root.stateType.type.toLowerCase()) {
|
||||||
|
// case "bool":
|
||||||
|
// return "x";
|
||||||
|
// default:
|
||||||
|
// return "%d";
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
labelsColor: app.foregroundColor
|
||||||
|
// tickCount: root.stateType.type.toLowerCase() === "bool" ? 2 : chartView.height / 40
|
||||||
|
color: Qt.rgba(app.foregroundColor.r, app.foregroundColor.g, app.foregroundColor.b, .2)
|
||||||
|
gridLineColor: color
|
||||||
|
}
|
||||||
|
|
||||||
|
DateTimeAxis {
|
||||||
|
id: xAxis
|
||||||
|
gridVisible: false
|
||||||
|
color: Qt.rgba(app.foregroundColor.r, app.foregroundColor.g, app.foregroundColor.b, .2)
|
||||||
|
tickCount: chartView.width / 70
|
||||||
|
labelsFont.pixelSize: app.smallFont
|
||||||
|
labelsColor: app.foregroundColor
|
||||||
|
property int timeDiff: (xAxis.max.getTime() - xAxis.min.getTime()) / 1000
|
||||||
|
|
||||||
|
function getTimeSpanString() {
|
||||||
|
var td = timeDiff
|
||||||
|
if (td < 60) {
|
||||||
|
return qsTr("%1 seconds").arg(Math.round(td));
|
||||||
|
}
|
||||||
|
td = td / 60
|
||||||
|
if (td < 60) {
|
||||||
|
return qsTr("%1 minutes").arg(Math.round(td));
|
||||||
|
}
|
||||||
|
td = td / 60
|
||||||
|
if (td < 48) {
|
||||||
|
return qsTr("%1 hours").arg(Math.round(td));
|
||||||
|
}
|
||||||
|
td = td / 24;
|
||||||
|
if (td < 14) {
|
||||||
|
return qsTr("%1 days").arg(Math.round(td));
|
||||||
|
}
|
||||||
|
td = td / 7
|
||||||
|
if (td < 9) {
|
||||||
|
return qsTr("%1 weeks").arg(Math.round(td));
|
||||||
|
}
|
||||||
|
td = td * 7 / 30
|
||||||
|
if (td < 24) {
|
||||||
|
return qsTr("%1 months").arg(Math.round(td));
|
||||||
|
}
|
||||||
|
td = td * 30 / 356
|
||||||
|
return qsTr("%1 years").arg(Math.round(td))
|
||||||
|
}
|
||||||
|
|
||||||
|
titleText: {
|
||||||
|
if (xAxis.min.getYear() === xAxis.max.getYear()
|
||||||
|
&& xAxis.min.getMonth() === xAxis.max.getMonth()
|
||||||
|
&& xAxis.min.getDate() === xAxis.max.getDate()) {
|
||||||
|
return Qt.formatDate(xAxis.min) + " (" + getTimeSpanString() + ")"
|
||||||
|
}
|
||||||
|
return Qt.formatDate(xAxis.min) + " - " + Qt.formatDate(xAxis.max) + " (" + getTimeSpanString() + ")"
|
||||||
|
}
|
||||||
|
titleBrush: app.foregroundColor
|
||||||
|
format: {
|
||||||
|
if (timeDiff < 60) { // one minute
|
||||||
|
return "mm:ss"
|
||||||
|
}
|
||||||
|
if (timeDiff < 60 * 60) { // one hour
|
||||||
|
return "hh:mm"
|
||||||
|
}
|
||||||
|
if (timeDiff < 60 * 60 * 24 * 2) { // two day
|
||||||
|
return "hh:mm"
|
||||||
|
}
|
||||||
|
if (timeDiff < 60 * 60 * 24 * 7) { // one week
|
||||||
|
return "ddd hh:mm"
|
||||||
|
}
|
||||||
|
if (timeDiff < 60 * 60 * 24 * 7 * 30) { // one month
|
||||||
|
return "dd.MM."
|
||||||
|
}
|
||||||
|
return "MMM yy"
|
||||||
|
}
|
||||||
|
|
||||||
|
min: {
|
||||||
|
var date = new Date();
|
||||||
|
date.setTime(date.getTime() - (1000 * 60 * 60 * 24) + 2000);
|
||||||
|
return date;
|
||||||
|
}
|
||||||
|
max: {
|
||||||
|
var date = new Date();
|
||||||
|
date.setTime(date.getTime() + 2000)
|
||||||
|
return date;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: scrollMouseArea
|
||||||
|
x: chartView.plotArea.x
|
||||||
|
y: chartView.plotArea.y
|
||||||
|
width: chartView.plotArea.width
|
||||||
|
height: chartView.plotArea.height
|
||||||
|
property int lastX: 0
|
||||||
|
property int startX: 0
|
||||||
|
preventStealing: false
|
||||||
|
|
||||||
|
property bool autoScroll: true
|
||||||
|
|
||||||
|
function scrollRightLimited(dx) {
|
||||||
|
chartView.animationOptions = ChartView.NoAnimation
|
||||||
|
var now = new Date()
|
||||||
|
// if we're already at the limit, don't even start scrolling
|
||||||
|
if (dx < 0 || xAxis.max < now) {
|
||||||
|
chartView.scrollRight(dx)
|
||||||
|
}
|
||||||
|
// figure out if we scrolled too far
|
||||||
|
var overshoot = xAxis.max.getTime() - now.getTime()
|
||||||
|
// print("overshoot is:", overshoot, "oldMax", xAxis.max, "newMax", now, "oldMin", xAxis.min, "newMin", new Date(xAxis.min.getTime() - overshoot))
|
||||||
|
if (overshoot > 0) {
|
||||||
|
var range = xAxis.max - xAxis.min
|
||||||
|
xAxis.max = now
|
||||||
|
xAxis.min = new Date(xAxis.max.getTime() - range)
|
||||||
|
}
|
||||||
|
// If the user scrolled closer than 5 pixels to the right edge, enable autoscroll
|
||||||
|
autoScroll = overshoot > -5;
|
||||||
|
|
||||||
|
chartView.animationOptions = ChartView.SeriesAnimations
|
||||||
|
}
|
||||||
|
|
||||||
|
function zoomInLimited(dy) {
|
||||||
|
chartView.animationOptions = ChartView.NoAnimation
|
||||||
|
var oldMax = xAxis.max;
|
||||||
|
chartView.scrollRight(dy);
|
||||||
|
xAxis.min = new Date(xAxis.min.getTime() - xAxis.timeDiff * 1000 * 2)
|
||||||
|
chartView.animationOptions = ChartView.SeriesAnimations
|
||||||
|
}
|
||||||
|
|
||||||
|
onPressed: {
|
||||||
|
lastX = mouse.x
|
||||||
|
startX = mouse.x
|
||||||
|
preventStealing = true
|
||||||
|
}
|
||||||
|
onClicked: {
|
||||||
|
// var pt = chartView.mapToValue(Qt.point(mouse.x + chartView.plotArea.x, mouse.y + chartView.plotArea.y), mainSeries)
|
||||||
|
// mainSeries.markClosestPoint(pt)
|
||||||
|
}
|
||||||
|
|
||||||
|
onWheel: {
|
||||||
|
scrollRightLimited(-wheel.pixelDelta.x)
|
||||||
|
// zoomInLimited(wheel.pixelDelta.y)
|
||||||
|
}
|
||||||
|
|
||||||
|
onPositionChanged: {
|
||||||
|
if (lastX !== mouse.x) {
|
||||||
|
scrollRightLimited(lastX - mouseX)
|
||||||
|
lastX = mouse.x
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Math.abs(startX - mouse.x) > 10) {
|
||||||
|
preventStealing = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onReleased: preventStealing = false;
|
||||||
|
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
running: scrollMouseArea.autoScroll
|
||||||
|
interval: 1000
|
||||||
|
repeat: true
|
||||||
|
onTriggered: {
|
||||||
|
scrollMouseArea.scrollRightLimited(10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SmartMeterChart {
|
SmartMeterChart {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: width * .7
|
Layout.preferredHeight: width * .7
|
||||||
|
|||||||
@ -484,7 +484,7 @@ MainViewBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// involve count in the statement to make the binding re-evaluate when the group is changed
|
// involve count in the statement to make the binding re-evaluate when the group is changed
|
||||||
device: mediaControllers.count > 0 ? mediaControllers.get(0) : null
|
thing: mediaControllers.count > 0 ? mediaControllers.get(0) : null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
186
nymea-app/ui/mainviews/MediaView.qml
Normal file
186
nymea-app/ui/mainviews/MediaView.qml
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
|
*
|
||||||
|
* 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.8
|
||||||
|
import QtQuick.Controls 2.1
|
||||||
|
import QtQuick.Controls.Material 2.1
|
||||||
|
import QtQuick.Layouts 1.2
|
||||||
|
import QtCharts 2.2
|
||||||
|
import Nymea 1.0
|
||||||
|
import "../components"
|
||||||
|
import "../delegates"
|
||||||
|
|
||||||
|
MainViewBase {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
ThingsProxy {
|
||||||
|
id: mediaDevices
|
||||||
|
engine: _engine
|
||||||
|
shownInterfaces: ["mediaplayer"]
|
||||||
|
}
|
||||||
|
|
||||||
|
EmptyViewPlaceholder {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: parent.width - app.margins * 2
|
||||||
|
visible: !engine.thingManager.fetchingData && mediaDevices.count == 0
|
||||||
|
title: qsTr("There are no media players set up.")
|
||||||
|
text: qsTr("Connect your media players in order to control them from here.")
|
||||||
|
imageSource: "../images/media.svg"
|
||||||
|
buttonText: qsTr("Add things")
|
||||||
|
}
|
||||||
|
|
||||||
|
SwipeView {
|
||||||
|
id: swipeView
|
||||||
|
anchors.fill: parent
|
||||||
|
currentIndex: pageIndicator.currentIndex
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: mediaDevices
|
||||||
|
delegate: Item {
|
||||||
|
id: playerDelegate
|
||||||
|
height: swipeView.height
|
||||||
|
width: swipeView.width
|
||||||
|
property Thing thing: mediaDevices.get(index)
|
||||||
|
property State titleState: thing.stateByName("title")
|
||||||
|
property State artistState: thing.stateByName("artist")
|
||||||
|
property State collectionState: thing.stateByName("collection")
|
||||||
|
|
||||||
|
GridLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: app.margins
|
||||||
|
columns: 1
|
||||||
|
rowSpacing: app.margins
|
||||||
|
|
||||||
|
MediaArtworkImage {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
thing: playerDelegate.thing
|
||||||
|
}
|
||||||
|
ColumnLayout {
|
||||||
|
spacing: app.margins
|
||||||
|
Label {
|
||||||
|
text: playerDelegate.titleState.value
|
||||||
|
Layout.fillWidth: true
|
||||||
|
elide: Text.ElideRight
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
font.pixelSize: app.largeFont
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
text: playerDelegate.artistState.value
|
||||||
|
Layout.fillWidth: true
|
||||||
|
elide: Text.ElideRight
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
text: playerDelegate.collectionState.value
|
||||||
|
Layout.fillWidth: true
|
||||||
|
elide: Text.ElideRight
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MediaControls {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
thing: playerDelegate.thing
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.preferredHeight: app.iconSize
|
||||||
|
Layout.fillWidth: true
|
||||||
|
visible: playerDelegate.thing.thingClass.browsable
|
||||||
|
|
||||||
|
HeaderButton {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
imageSource: "../images/navigationpad.svg"
|
||||||
|
onClicked: {
|
||||||
|
pageStack.push(navigationPadPage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Component {
|
||||||
|
id: navigationPadPage
|
||||||
|
Page {
|
||||||
|
header: NymeaHeader { text: playerDelegate.thing.name; onBackPressed: pageStack.pop() }
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: app.margins
|
||||||
|
spacing: app.margins
|
||||||
|
|
||||||
|
NavigationPad { Layout.fillWidth: true; Layout.fillHeight: true; device: playerDelegate.thing }
|
||||||
|
MediaControls { Layout.fillWidth: true; thing: playerDelegate.thing }
|
||||||
|
ShuffleRepeatVolumeControl { Layout.fillWidth: true; Layout.fillHeight: false; Layout.preferredHeight: app.iconSize; thing: playerDelegate.thing }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ShuffleRepeatVolumeControl {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: false
|
||||||
|
Layout.preferredHeight: app.iconSize
|
||||||
|
thing: playerDelegate.thing
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.preferredHeight: app.iconSize
|
||||||
|
Layout.fillWidth: true
|
||||||
|
visible: playerDelegate.thing.thingClass.interfaces.indexOf("navigationpad") >= 0
|
||||||
|
|
||||||
|
HeaderButton {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
imageSource: "../images/folder-symbolic.svg"
|
||||||
|
onClicked: {
|
||||||
|
pageStack.push(browserPage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Component {
|
||||||
|
id: browserPage
|
||||||
|
Page {
|
||||||
|
header: NymeaHeader { text: playerDelegate.thing.name; onBackPressed: pageStack.pop() }
|
||||||
|
MediaBrowser { anchors.fill: parent; thing: playerDelegate.thing }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PageIndicator {
|
||||||
|
id: pageIndicator
|
||||||
|
count: swipeView.count
|
||||||
|
currentIndex: swipeView.currentIndex
|
||||||
|
interactive: true
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -56,23 +56,6 @@ Page {
|
|||||||
LogsModel {
|
LogsModel {
|
||||||
id: logsModel
|
id: logsModel
|
||||||
engine: _engine
|
engine: _engine
|
||||||
startTime: {
|
|
||||||
var date = new Date();
|
|
||||||
date.setHours(new Date().getHours() - 2);
|
|
||||||
return date;
|
|
||||||
}
|
|
||||||
endTime: new Date()
|
|
||||||
live: true
|
|
||||||
onCountChanged: {
|
|
||||||
if (root.autoScroll) {
|
|
||||||
listView.positionViewAtEnd()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LogsModelNg {
|
|
||||||
id: logsModelNg
|
|
||||||
engine: _engine
|
|
||||||
live: true
|
live: true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,13 +66,11 @@ Page {
|
|||||||
|
|
||||||
ListView {
|
ListView {
|
||||||
id: listView
|
id: listView
|
||||||
model: engine.jsonRpcClient.ensureServerVersion("1.10") ? logsModelNg : logsModel
|
model: logsModel
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
clip: true
|
clip: true
|
||||||
headerPositioning: ListView.OverlayHeader
|
headerPositioning: ListView.OverlayHeader
|
||||||
|
|
||||||
Component.onCompleted: model.update()
|
|
||||||
|
|
||||||
onDraggingChanged: {
|
onDraggingChanged: {
|
||||||
if (dragging) {
|
if (dragging) {
|
||||||
root.autoScroll = false;
|
root.autoScroll = false;
|
||||||
@ -103,14 +84,6 @@ Page {
|
|||||||
visible: listView.model.busy
|
visible: listView.model.busy
|
||||||
}
|
}
|
||||||
|
|
||||||
onContentYChanged: {
|
|
||||||
if (!engine.jsonRpcClient.ensureServerVersion("1.10")) {
|
|
||||||
if (!logsModel.busy && contentY - originY < 5 * height) {
|
|
||||||
logsModel.fetchEarlier(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
delegate: ItemDelegate {
|
delegate: ItemDelegate {
|
||||||
id: delegate
|
id: delegate
|
||||||
width: parent.width
|
width: parent.width
|
||||||
|
|||||||
Reference in New Issue
Block a user