Add media and energy views

This commit is contained in:
Michael Zanetti 2020-08-27 01:16:03 +02:00
parent 57d2986948
commit 0f9caa03df
51 changed files with 1800 additions and 1424 deletions

View File

@ -343,7 +343,7 @@ void DeviceManager::getConfiguredDevicesResponse(const QVariantMap &params)
QVariantList stateVariantList = deviceVariant.toMap().value("states").toList();
foreach (const QVariant &stateMap, stateVariantList) {
QString stateTypeId = stateMap.toMap().value("stateTypeId").toString();
StateType *st = device->deviceClass()->stateTypes()->getStateType(stateTypeId);
StateType *st = device->thingClass()->stateTypes()->getStateType(stateTypeId);
if (!st) {
qWarning() << "Can't find a statetype for this state";
continue;

View File

@ -72,24 +72,24 @@ QVariant Devices::data(const QModelIndex &index, int role) const
if (index.row() < 0 || index.row() >= m_devices.count())
return QVariant();
Device *device = m_devices.at(index.row());
Device *thing = m_devices.at(index.row());
switch (role) {
case RoleName:
return device->name();
return thing->name();
case RoleId:
return device->id().toString();
return thing->id().toString();
case RoleDeviceClass:
return device->deviceClassId().toString();
return thing->deviceClassId().toString();
case RoleParentDeviceId:
return device->parentDeviceId().toString();
return thing->parentDeviceId().toString();
case RoleSetupStatus:
return device->setupStatus();
return thing->setupStatus();
case RoleSetupDisplayMessage:
return device->setupDisplayMessage();
return thing->setupDisplayMessage();
case RoleInterfaces:
return device->deviceClass()->interfaces();
return thing->thingClass()->interfaces();
case RoleBaseInterface:
return device->deviceClass()->baseInterface();
return thing->thingClass()->baseInterface();
}
return QVariant();
}

View File

@ -592,7 +592,7 @@ void JsonRpcClient::helloReply(const QVariantMap &params)
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) {
qWarning() << "Nymea core doesn't support minimum required version. Required:" << minimumRequiredVersion << "Found:" << m_jsonRpcVersion;
m_connection->disconnect();

View File

@ -65,7 +65,8 @@
#include "types/browseritem.h"
#include "models/logsmodel.h"
#include "models/logsmodelng.h"
#include "models/valuelogsproxymodel.h"
#include "models/barseriesadapter.h"
#include "models/xyseriesadapter.h"
#include "models/interfacesproxy.h"
#include "configuration/nymeaconfiguration.h"
#include "configuration/serverconfiguration.h"
@ -246,8 +247,9 @@ void registerQmlTypes() {
qmlRegisterType<LogsModel>(uri, 1, 0, "LogsModel");
qmlRegisterType<LogsModelNg>(uri, 1, 0, "LogsModelNg");
qmlRegisterType<ValueLogsProxyModel>(uri, 1, 0, "ValueLogsProxyModel");
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<Tags>(uri, 1, 0, "Tags", "Get it from TagsManager");

View File

@ -27,7 +27,9 @@ INCLUDEPATH += $$top_srcdir/QtZeroConf
SOURCES += \
configuration/networkmanager.cpp \
engine.cpp \
models/barseriesadapter.cpp \
models/sortfilterproxymodel.cpp \
models/xyseriesadapter.cpp \
ruletemplates/calendaritemtemplate.cpp \
ruletemplates/timedescriptortemplate.cpp \
ruletemplates/timeeventitemtemplate.cpp \
@ -128,7 +130,6 @@ SOURCES += \
rulemanager.cpp \
models/rulesfiltermodel.cpp \
models/logsmodel.cpp \
models/valuelogsproxymodel.cpp \
logmanager.cpp \
wifisetup/bluetoothdevice.cpp \
wifisetup/bluetoothdeviceinfo.cpp \
@ -163,7 +164,9 @@ SOURCES += \
HEADERS += \
configuration/networkmanager.h \
engine.h \
models/barseriesadapter.h \
models/sortfilterproxymodel.h \
models/xyseriesadapter.h \
ruletemplates/calendaritemtemplate.h \
ruletemplates/timedescriptortemplate.h \
ruletemplates/timeeventitemtemplate.h \
@ -265,7 +268,6 @@ HEADERS += \
rulemanager.h \
models/rulesfiltermodel.h \
models/logsmodel.h \
models/valuelogsproxymodel.h \
logmanager.h \
wifisetup/bluetoothdevice.h \
wifisetup/bluetoothdeviceinfo.h \

View 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;
}

View 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

View File

@ -49,35 +49,35 @@ QVariant DeviceModel::data(const QModelIndex &index, int role) const
return m_list.at(index.row());
}
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) {
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) {
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) {
return TypeEventType;
}
}
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) {
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) {
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) {
return eventType->displayName();
}
}
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 QVariant();
@ -169,22 +169,22 @@ void DeviceModel::updateList()
beginResetModel();
m_list.clear();
if (m_showStates) {
for (int i = 0; i < m_device->deviceClass()->stateTypes()->rowCount(); i++) {
m_list.append(m_device->deviceClass()->stateTypes()->get(i)->id());
for (int i = 0; i < m_device->thingClass()->stateTypes()->rowCount(); i++) {
m_list.append(m_device->thingClass()->stateTypes()->get(i)->id());
}
}
if (m_showActions) {
for (int i = 0; i < m_device->deviceClass()->actionTypes()->rowCount(); i++) {
if (!m_list.contains(m_device->deviceClass()->actionTypes()->get(i)->id())) {
m_list.append(m_device->deviceClass()->actionTypes()->get(i)->id());
for (int i = 0; i < m_device->thingClass()->actionTypes()->rowCount(); i++) {
if (!m_list.contains(m_device->thingClass()->actionTypes()->get(i)->id())) {
m_list.append(m_device->thingClass()->actionTypes()->get(i)->id());
}
}
}
if (m_showEvents) {
for (int i = 0; i < m_device->deviceClass()->eventTypes()->rowCount(); i++) {
if (!m_list.contains(m_device->deviceClass()->eventTypes()->get(i)->id())) {
m_list.append(m_device->deviceClass()->eventTypes()->get(i)->id());
for (int i = 0; i < m_device->thingClass()->eventTypes()->rowCount(); i++) {
if (!m_list.contains(m_device->thingClass()->eventTypes()->get(i)->id())) {
m_list.append(m_device->thingClass()->eventTypes()->get(i)->id());
}
}
}

View File

@ -103,11 +103,11 @@ bool InterfacesProxy::filterAcceptsRow(int source_row, const QModelIndex &source
bool found = false;
for (int i = 0; i < m_devicesFilter->rowCount(); i++) {
Device *d = m_devicesFilter->get(i);
if (!d->deviceClass()) {
if (!d->thingClass()) {
qWarning() << "Cannot find DeviceClass for device:" << d->id() << d->name();
return false;
}
if (d->deviceClass()->interfaces().contains(interfaceName)) {
if (d->thingClass()->interfaces().contains(interfaceName)) {
found = true;
break;
}
@ -121,11 +121,11 @@ bool InterfacesProxy::filterAcceptsRow(int source_row, const QModelIndex &source
bool found = false;
for (int i = 0; i < m_devicesProxyFilter->rowCount(); i++) {
Device *d = m_devicesProxyFilter->get(i);
if (!d->deviceClass()) {
qWarning() << "Cannot find DeviceClass for device:" << d->id() << d->name();
if (!d->thingClass()) {
qWarning() << "Cannot find ThingClass for thing:" << d->id() << d->name();
return false;
}
if (d->deviceClass()->interfaces().contains(interfaceName)) {
if (d->thingClass()->interfaces().contains(interfaceName)) {
found = true;
break;
}

View File

@ -29,14 +29,18 @@
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "logsmodel.h"
#include <QDateTime>
#include <QDebug>
#include <QMetaEnum>
#include <QJsonDocument>
#include "engine.h"
#include "types/logentry.h"
#include "logmanager.h"
#include <QMetaEnum>
LogsModel::LogsModel(QObject *parent) : QAbstractListModel(parent)
{
}
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
{
Q_UNUSED(parent)
@ -72,6 +71,7 @@ QVariant LogsModel::data(const QModelIndex &index, int role) const
case RoleValue:
return m_list.at(index.row())->value();
case RoleThingId:
case RoleDeviceId:
return m_list.at(index.row())->thingId();
case RoleTypeId:
return m_list.at(index.row())->typeId();
@ -88,13 +88,19 @@ QHash<int, QByteArray> LogsModel::roleNames() const
QHash<int, QByteArray> roles;
roles.insert(RoleTimestamp, "timestamp");
roles.insert(RoleValue, "value");
roles.insert(RoleThingId, "deviceId");
roles.insert(RoleThingId, "thingId");
roles.insert(RoleDeviceId, "deviceId");
roles.insert(RoleTypeId, "typeId");
roles.insert(RoleSource, "source");
roles.insert(RoleLoggingEventType, "loggingEventType");
return roles;
}
bool LogsModel::busy() const
{
return m_busy;
}
bool LogsModel::live() const
{
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) {
m_deviceId = deviceId;
emit deviceIdChanged();
if (m_thingId != thingId) {
m_thingId = thingId;
emit thingIdChanged();
}
}
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)
{
if (m_typeIds != typeIds) {
m_typeIds = typeIds;
QList<QUuid> fixedTypeIds;
foreach (const QString &id, typeIds) {
fixedTypeIds.append(QUuid(id));
}
if (m_typeIds != fixedTypeIds) {
m_typeIds = fixedTypeIds;
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
{
if (index >= 0 && index < m_list.count()) {
@ -168,163 +205,172 @@ LogEntry *LogsModel::get(int index) const
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)
{
// qDebug() << "logs reply" << data;
beginResetModel();
qDeleteAll(m_list);
m_list.clear();
int offset = data.value("params").toMap().value("offset").toInt();
int count = data.value("params").toMap().value("count").toInt();
// qDebug() << qUtf8Printable(QJsonDocument::fromVariant(data).toJson());
QList<LogEntry*> newBlock;
QList<QVariant> logEntries = data.value("params").toMap().value("logEntries").toList();
foreach (const QVariant &logEntryVariant, logEntries) {
QVariantMap entryMap = logEntryVariant.toMap();
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();
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>();
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");
LogEntry *entry = new LogEntry(timeStamp, value, deviceId, typeId, loggingSource, loggingEventType, this);
m_list.append(entry);
LogEntry *entry = new LogEntry(timeStamp, value, thingId, typeId, loggingSource, loggingEventType, this);
newBlock.append(entry);
}
endResetModel();
emit countChanged();
// qDebug() << "Received logs from" << offset << "to" << offset + count << "Actual count:" << newBlock.count();
m_busy = false;
emit busyChanged();
}
void LogsModel::fetchEarlierReply(const QVariantMap &data)
{
// qDebug() << "logs reply" << data;
QList<QVariant> logEntries = data.value("params").toMap().value("logEntries").toList();
QList<LogEntry*> newEntries;
foreach (const QVariant &logEntryVariant, logEntries) {
QVariantMap entryMap = logEntryVariant.toMap();
QDateTime timeStamp = QDateTime::fromMSecsSinceEpoch(entryMap.value("timestamp").toLongLong());
QString deviceId = entryMap.value("deviceId").toString();
QString typeId = entryMap.value("typeId").toString();
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);
if (count < m_blockSize) {
m_canFetchMore = false;
}
if (newBlock.isEmpty()) {
m_busy = false;
emit busyChanged();
return;
}
beginInsertRows(QModelIndex(), offset, offset + newBlock.count() - 1);
for (int i = 0; i < newBlock.count(); i++) {
LogEntry *entry = newBlock.at(i);
m_list.insert(offset + i, entry);
emit logEntryAdded(entry);
}
beginInsertRows(QModelIndex(), 0, newEntries.count() - 1);
newEntries.append(m_list);
m_list = newEntries;
endInsertRows();
emit countChanged();
m_busy = false;
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)
{
// qDebug() << "***** model NG" << data << m_live;
if (!m_live) {
return;
}
QVariantMap entryMap = data;
QString deviceId = entryMap.value("deviceId").toString();
if (!m_deviceId.isNull() && deviceId != m_deviceId) {
QUuid thingId;
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;
}
QString typeId = entryMap.value("typeId").toString();
QUuid typeId = entryMap.value("typeId").toUuid();
if (!m_typeIds.isEmpty() && !m_typeIds.contains(typeId)) {
return;
}
beginInsertRows(QModelIndex(), m_list.count(), m_list.count());
beginInsertRows(QModelIndex(), 0, 0);
QDateTime timeStamp = QDateTime::fromMSecsSinceEpoch(entryMap.value("timestamp").toLongLong());
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>();
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");
LogEntry *entry = new LogEntry(timeStamp, value, deviceId, typeId, loggingSource, loggingEventType, this);
m_list.append(entry);
LogEntry *entry = new LogEntry(timeStamp, value, thingId, typeId, loggingSource, loggingEventType, this);
m_list.prepend(entry);
endInsertRows();
emit countChanged();
emit logEntryAdded(entry);
}

View File

@ -32,26 +32,26 @@
#define LOGSMODEL_H
#include <QAbstractListModel>
#include <QQmlParserStatus>
#include "jsonrpc/jsonhandler.h"
#include "types/logentry.h"
class Engine;
class LogsModel : public QAbstractListModel
class LogsModel : public QAbstractListModel, public QQmlParserStatus
{
Q_OBJECT
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(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(QDateTime startTime READ startTime WRITE setStartTime NOTIFY startTimeChanged)
Q_PROPERTY(QDateTime endTime READ endTime WRITE setEndTime NOTIFY endTimeChanged)
// Q_PROPERTY(int paginationCount READ paginationCount WRITE setPaginationCount NOTIFY paginationCountChanged)
Q_PROPERTY(bool live READ live WRITE setLive NOTIFY liveChanged)
Q_PROPERTY(QDateTime viewStartTime READ viewStartTime WRITE setViewStartTime NOTIFY viewStartTimeChanged)
public:
enum Roles {
@ -72,12 +72,16 @@ public:
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role) 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;
void setLive(bool live);
QString deviceId() const;
void setDeviceId(const QString &deviceId);
QUuid thingId() const;
void setThingId(const QUuid &deviceId);
QStringList typeIds() const;
void setTypeIds(const QStringList &typeIds);
@ -88,44 +92,43 @@ public:
QDateTime endTime() const;
void setEndTime(const QDateTime &endTime);
// int paginationCount() const;
// void setPaginationCount(int paginationCount);
QDateTime viewStartTime() const;
void setViewStartTime(const QDateTime &viewStartTime);
Q_INVOKABLE LogEntry* get(int index) const;
Q_INVOKABLE void notificationReceived(const QVariantMap &data);
signals:
void engineChanged();
void busyChanged();
void liveChanged();
void countChanged();
void deviceIdChanged();
void thingIdChanged();
void typeIdsChanged();
void startTimeChanged();
void endTimeChanged();
// void paginationCountChanged();
void viewStartTimeChanged();
public slots:
virtual void update();
virtual void fetchEarlier(int hours);
// virtual void fetchLater(int hours);
void logEntryAdded(LogEntry *entry);
private slots:
virtual void logsReply(const QVariantMap &data);
virtual void fetchEarlierReply(const QVariantMap &data);
void newLogEntryReceived(const QVariantMap &data);
protected:
Engine *m_engine = nullptr;
QList<LogEntry*> m_list;
QString m_deviceId;
QStringList m_typeIds;
QDateTime m_startTime = QDateTime::currentDateTime().addDays(-1);
QDateTime m_endTime = QDateTime::currentDateTime();
QUuid m_thingId;
QList<QUuid> m_typeIds;
QDateTime m_startTime;
QDateTime m_endTime;
QDateTime m_viewStartTime;
bool m_busy = false;
bool m_busy = true;
bool m_live = false;
int m_blockSize = 100;
bool m_canFetchMore = true;
};

View File

@ -278,7 +278,7 @@ void LogsModelNg::logsReply(const QVariantMap &data)
continue;
}
StateType *entryStateType = dev->deviceClass()->stateTypes()->getStateType(entry->typeId());
StateType *entryStateType = dev->thingClass()->stateTypes()->getStateType(entry->typeId());
if (m_graphSeries) {
if (entryStateType->type().toLower() == "bool") {
@ -453,9 +453,9 @@ void LogsModelNg::newLogEntryReceived(const QVariantMap &data)
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
if (m_graphSeries->count() > 1) {
m_graphSeries->removePoints(0, 2);

View File

@ -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();
}

View File

@ -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

View 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;
}

View 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

View File

@ -302,7 +302,7 @@ bool RuleTemplatesFilterModel::filterAcceptsRow(int source_row, const QModelInde
bool found = false;
for (int i = 0; i < m_filterDevicesProxy->rowCount(); i++) {
// 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;
break;
}

View File

@ -169,7 +169,7 @@ void CodeCompletion::update()
if (thingIdExp.exactMatch(blockText)) {
for (int i = 0; i < m_engine->deviceManager()->devices()->rowCount(); 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: \""));
m_model->update(entries);
@ -182,7 +182,7 @@ void CodeCompletion::update()
if (deviceIdExp.exactMatch(blockText)) {
for (int i = 0; i < m_engine->deviceManager()->devices()->rowCount(); 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: \""));
m_model->update(entries);
@ -209,8 +209,8 @@ void CodeCompletion::update()
return;
}
for (int i = 0; i < device->deviceClass()->stateTypes()->rowCount(); i++) {
StateType *stateType = device->deviceClass()->stateTypes()->get(i);
for (int i = 0; i < device->thingClass()->stateTypes()->rowCount(); i++) {
StateType *stateType = device->thingClass()->stateTypes()->get(i);
entries.append(CompletionModel::Entry(stateType->id().toString() + "\" // " + stateType->name(), stateType->name(), "stateType"));
}
blockText.remove(QRegExp(".*stateTypeId: \""));
@ -241,8 +241,8 @@ void CodeCompletion::update()
}
qDebug() << "Device is" << device->name();
for (int i = 0; i < device->deviceClass()->stateTypes()->rowCount(); i++) {
StateType *stateType = device->deviceClass()->stateTypes()->get(i);
for (int i = 0; i < device->thingClass()->stateTypes()->rowCount(); i++) {
StateType *stateType = device->thingClass()->stateTypes()->get(i);
entries.append(CompletionModel::Entry(stateType->name() + "\"", stateType->name(), "stateType"));
}
blockText.remove(QRegExp(".*stateName: \""));
@ -270,8 +270,8 @@ void CodeCompletion::update()
return;
}
for (int i = 0; i < device->deviceClass()->actionTypes()->rowCount(); i++) {
ActionType *actionType = device->deviceClass()->actionTypes()->get(i);
for (int i = 0; i < device->thingClass()->actionTypes()->rowCount(); i++) {
ActionType *actionType = device->thingClass()->actionTypes()->get(i);
entries.append(CompletionModel::Entry(actionType->id().toString() + "\" // " + actionType->name(), actionType->name(), "actionType"));
}
blockText.remove(QRegExp(".*actionTypeId: \""));
@ -299,8 +299,8 @@ void CodeCompletion::update()
return;
}
for (int i = 0; i < device->deviceClass()->actionTypes()->rowCount(); i++) {
ActionType *actionType = device->deviceClass()->actionTypes()->get(i);
for (int i = 0; i < device->thingClass()->actionTypes()->rowCount(); i++) {
ActionType *actionType = device->thingClass()->actionTypes()->get(i);
entries.append(CompletionModel::Entry(actionType->name() + "\"", actionType->name(), "actionType"));
}
blockText.remove(QRegExp(".*actionName: \""));
@ -328,8 +328,8 @@ void CodeCompletion::update()
return;
}
for (int i = 0; i < device->deviceClass()->eventTypes()->rowCount(); i++) {
EventType *eventType = device->deviceClass()->eventTypes()->get(i);
for (int i = 0; i < device->thingClass()->eventTypes()->rowCount(); i++) {
EventType *eventType = device->thingClass()->eventTypes()->get(i);
entries.append(CompletionModel::Entry(eventType->id().toString() + "\" // " + eventType->name(), eventType->name(), "eventType"));
}
blockText.remove(QRegExp(".*eventTypeId: \""));
@ -358,8 +358,8 @@ void CodeCompletion::update()
return;
}
for (int i = 0; i < device->deviceClass()->eventTypes()->rowCount(); i++) {
EventType *eventType = device->deviceClass()->eventTypes()->get(i);
for (int i = 0; i < device->thingClass()->eventTypes()->rowCount(); i++) {
EventType *eventType = device->thingClass()->eventTypes()->get(i);
entries.append(CompletionModel::Entry(eventType->name() + "\"", eventType->name(), "eventType"));
}
blockText.remove(QRegExp(".*eventName: \""));

View File

@ -389,9 +389,15 @@ void SystemController::notificationReceived(const QVariantMap &data)
emit powerManagementAvailableChanged();
emit updateManagementAvailableChanged();
} 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.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 serverTimeZoneChanged();
m_automaticTimeAvailable = data.value("params").toMap().value("automaticTimeAvailable").toBool();

View File

@ -82,7 +82,7 @@ int ThingGroup::executeAction(const QString &actionName, const QVariantList &par
if (device->setupStatus() != Device::DeviceSetupStatusComplete) {
continue;
}
ActionType *actionType = device->deviceClass()->actionTypes()->findByName(actionName);
ActionType *actionType = device->thingClass()->actionTypes()->findByName(actionName);
if (!actionType) {
continue;
}
@ -110,8 +110,8 @@ int ThingGroup::executeAction(const QString &actionName, const QVariantList &par
void ThingGroup::syncStates()
{
for (int i = 0; i < deviceClass()->stateTypes()->rowCount(); i++) {
StateType *stateType = deviceClass()->stateTypes()->get(i);
for (int i = 0; i < thingClass()->stateTypes()->rowCount(); i++) {
StateType *stateType = thingClass()->stateTypes()->get(i);
State *state = states()->getState(stateType->id());
qDebug() << "syncing state" << stateType->name();
@ -121,13 +121,13 @@ void ThingGroup::syncStates()
for (int j = 0; j < m_devices->rowCount(); j++) {
Device *d = m_devices->get(j);
// 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) {
continue;
}
// Skip disconnected things
StateType *connectedStateType = d->deviceClass()->stateTypes()->findByName("connected");
StateType *connectedStateType = d->thingClass()->stateTypes()->findByName("connected");
if (connectedStateType) {
if (!d->stateValue(connectedStateType->id()).toBool()) {
continue;

View File

@ -34,11 +34,11 @@
#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),
m_deviceManager(deviceManager),
m_parentDeviceId(parentDeviceId),
m_deviceClass(deviceClass)
m_parentId(parentId),
m_thingClass(thingClass)
{
}
@ -65,22 +65,22 @@ void Device::setId(const QUuid &id)
QUuid Device::deviceClassId() const
{
return m_deviceClass->id();
return m_thingClass->id();
}
QUuid Device::thingClassId() const
{
return m_deviceClass->id();
return m_thingClass->id();
}
QUuid Device::parentDeviceId() const
{
return m_parentDeviceId;
return m_parentId;
}
bool Device::isChild() const
{
return !m_parentDeviceId.isNull();
return !m_parentId.isNull();
}
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
{
return m_deviceClass;
return m_thingClass;
}
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 &params)
{
ActionType *actionType = m_deviceClass->actionTypes()->findByName(actionName);
ActionType *actionType = m_thingClass->actionTypes()->findByName(actionName);
QVariantList finalParams;
foreach (const QVariant &paramVariant, params) {
@ -209,30 +218,30 @@ int Device::executeAction(const QString &actionName, const QVariantList &params)
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;
for (int i = 0; i < device->deviceClass()->paramTypes()->rowCount(); i++) {
ParamType *pt = device->deviceClass()->paramTypes()->get(i);
Param *p = device->params()->getParam(pt->id().toString());
dbg.nospace() << "Thing: " << thing->name() << " (" << thing->id().toString() << ") Class:" << thing->thingClass()->name() << " (" << thing->thingClassId().toString() << ")" << endl;
for (int i = 0; i < thing->thingClass()->paramTypes()->rowCount(); i++) {
ParamType *pt = thing->thingClass()->paramTypes()->get(i);
Param *p = thing->params()->getParam(pt->id().toString());
if (p) {
dbg << " Param " << i << ": " << pt->id().toString() << ": " << pt->name() << " = " << p->value() << endl;
} else {
dbg << " Param " << i << ": " << pt->id().toString() << ": " << pt->name() << " = " << "*** Unknown value ***" << endl;
}
}
for (int i = 0; i < device->deviceClass()->settingsTypes()->rowCount(); i++) {
ParamType *pt = device->deviceClass()->settingsTypes()->get(i);
Param *p = device->settings()->getParam(pt->id().toString());
for (int i = 0; i < thing->thingClass()->settingsTypes()->rowCount(); i++) {
ParamType *pt = thing->thingClass()->settingsTypes()->get(i);
Param *p = thing->settings()->getParam(pt->id().toString());
if (p) {
dbg << " Setting " << i << ": " << pt->id().toString() << ": " << pt->name() << " = " << p->value() << endl;
} else {
dbg << " Setting " << i << ": " << pt->id().toString() << ": " << pt->name() << " = " << "*** Unknown value ***" << endl;
}
}
for (int i = 0; i < device->deviceClass()->stateTypes()->rowCount(); i++) {
StateType *st = device->deviceClass()->stateTypes()->get(i);
State *s = device->states()->getState(st->id());
for (int i = 0; i < thing->thingClass()->stateTypes()->rowCount(); i++) {
StateType *st = thing->thingClass()->stateTypes()->get(i);
State *s = thing->states()->getState(st->id());
dbg << " State " << i << ": " << st->id() << ": " << st->name() << " = " << s->value() << endl;
}
return dbg;

View File

@ -55,7 +55,7 @@ class Device : public QObject
Q_PROPERTY(Params *params READ params NOTIFY paramsChanged)
Q_PROPERTY(Params *settings READ settings NOTIFY settingsChanged)
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)
public:
@ -67,7 +67,7 @@ public:
};
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;
void setId(const QUuid &id);
@ -93,10 +93,11 @@ public:
States *states() const;
void setStates(States *states);
DeviceClass *deviceClass() const;
DeviceClass *thingClass() const;
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);
void setStateValue(const QUuid &stateTypeId, const QVariant &value);
@ -117,15 +118,15 @@ protected:
DeviceManager *m_deviceManager = nullptr;
QString m_name;
QUuid m_id;
QUuid m_parentDeviceId;
QUuid m_parentId;
DeviceSetupStatus m_setupStatus = DeviceSetupStatusNone;
QString m_setupDisplayMessage;
Params *m_params = nullptr;
Params *m_settings = 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

View File

@ -99,7 +99,7 @@
<file>ui/images/media-seek-forward.svg</file>
<file>ui/images/media-skip-backward.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/network-secure.svg</file>
<file>ui/images/network-vpn.svg</file>

View File

@ -50,18 +50,13 @@
<file>ui/customviews/CustomViewBase.qml</file>
<file>ui/customviews/WeatherView.qml</file>
<file>ui/customviews/MediaControllerView.qml</file>
<file>ui/customviews/SensorView.qml</file>
<file>ui/customviews/NotificationsView.qml</file>
<file>ui/customviews/ExtendedVolumeController.qml</file>
<file>ui/devicepages/MediaDevicePage.qml</file>
<file>ui/devicepages/ButtonDevicePage.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/SensorDevicePage.qml</file>
<file>ui/devicepages/SensorDevicePagePre110.qml</file>
<file>ui/devicepages/SensorDevicePagePost110.qml</file>
<file>ui/devicepages/DevicePageBase.qml</file>
<file>ui/devicepages/InputTriggerDevicePage.qml</file>
<file>ui/devicepages/StateLogPage.qml</file>
@ -134,7 +129,6 @@
<file>../LICENSE.OFL</file>
<file>../LICENSE.OpenSSL</file>
<file>../LICENSE.LGPL3</file>
<file>ui/customviews/GenericTypeGraphPre110.qml</file>
<file>ui/customviews/GenericTypeGraph.qml</file>
<file>ui/devicepages/SmartMeterDevicePage.qml</file>
<file>ui/devicelistpages/SmartMeterDeviceListPage.qml</file>
@ -222,5 +216,8 @@
<file>ui/mainviews/EnergyView.qml</file>
<file>ui/components/MainViewBase.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>
</RCC>

View File

@ -120,6 +120,7 @@ Page {
ListElement { name: "scenes"; source: "ScenesView"; displayName: qsTr("Scenes"); icon: "slideshow" }
ListElement { name: "garages"; source: "GaragesView"; displayName: qsTr("Garages"); icon: "garage/garage-100" }
ListElement { name: "energy"; source: "EnergyView"; displayName: qsTr("Energy"); icon: "smartmeter" }
ListElement { name: "media"; source: "MediaView"; displayName: qsTr("Media"); icon: "media" }
}
ListModel {

View File

@ -259,7 +259,7 @@ ApplicationWindow {
case "mediacontroller":
case "extendedmediacontroller":
case "mediaplayer":
return Qt.resolvedUrl("images/mediaplayer-app-symbolic.svg")
return Qt.resolvedUrl("images/media.svg")
case "powersocket":
return Qt.resolvedUrl("images/powersocket.svg")
case "button":

View File

@ -36,13 +36,13 @@ import Nymea 1.0
Item {
id: root
property Device device: null
property Thing thing: null
readonly property StateType artworkStateType: device ? device.deviceClass.stateTypes.findByName("artwork") : null
readonly property State artworkState: artworkStateType ? device.states.getState(artworkStateType.id) : null
readonly property StateType artworkStateType: thing ? thing.thingClass.stateTypes.findByName("artwork") : 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 State playerTypeState: playerTypeStateType ? device.states.getState(playerTypeStateType.id) : null
readonly property StateType playerTypeStateType: thing ? thing.thingClass.stateTypes.findByName("playerType") : null
readonly property State playerTypeState: playerTypeStateType ? thing.states.getState(playerTypeStateType.id) : null
Pane {
Material.elevation: 2

View 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
}
}
}
}
}

View File

@ -38,15 +38,18 @@ RowLayout {
id: root
implicitHeight: iconSize + app.margins
property Device device: null
property Thing thing: null
property int iconSize: app.iconSize * 1.5
readonly property StateType playbackStateType: device ? device.deviceClass.stateTypes.findByName("playbackStatus") : null
readonly property State playbackState: playbackStateType ? device.states.getState(playbackStateType.id) : null
readonly property StateType playbackStateType: thing ? thing.thingClass.stateTypes.findByName("playbackStatus") : null
readonly property State playbackState: playbackStateType ? thing.states.getState(playbackStateType.id) : null
function executeAction(actionName, params) {
var actionTypeId = device.deviceClass.actionTypes.findByName(actionName).id;
engine.deviceManager.executeAction(device.id, actionTypeId, params)
if (params === undefined) {
params = []
}
var actionTypeId = thing.thingClass.actionTypes.findByName(actionName).id;
engine.thingManager.executeAction(thing.id, actionTypeId, params)
}
Item { Layout.fillWidth: true }
@ -68,7 +71,7 @@ RowLayout {
}
Item { Layout.fillWidth: true }
ProgressButton {
Layout.preferredHeight: root,iconSize
Layout.preferredHeight: root.iconSize
Layout.preferredWidth: height
imageSource: root.playbackState && root.playbackState.value === "Playing" ? "../images/media-playback-pause.svg" : "../images/media-playback-start.svg"
longpressImageSource: "../images/media-playback-stop.svg"

View 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}]);
}
}
}
}
}

View File

@ -64,25 +64,19 @@ ChartView {
function refresh() {
pieSeries.clear();
d.sliceMap = {}
print("calculating", chart.multiplier)
for (var i = 0; i < meters.count; i++) {
var thing = meters.get(i);
print("thing:", thing.name)
var value = 0;
var totalConsumedStateType = thing.thingClass.stateTypes.findByName("totalEnergyConsumed")
if (totalConsumedStateType) {
var totalConsumedState = thing.states.getState(totalConsumedStateType.id)
value = value + (totalConsumedState.value * chart.multiplier)
print("Adding", totalConsumedState.value * chart.multiplier, value)
}
var totalProducedStateType = thing.thingClass.stateTypes.findByName("totalEnergyProduced")
if (totalProducedStateType) {
var totalProducedState = thing.states.getState(totalProducedStateType.id)
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 color = app.accentColor
for (var j = 0; j < i; j+=2) {

View File

@ -36,6 +36,8 @@ Item {
implicitHeight: slider.implicitHeight
implicitWidth: slider.implicitWidth
property alias orientation: slider.orientation
property real value: 0
property alias from: slider.from
property alias to: slider.to
@ -49,6 +51,7 @@ Item {
Slider {
id: slider
anchors.left: parent.left; anchors.right: parent.right
anchors.top: parent.top; anchors.bottom: parent.bottom
from: 0
to: 100
property var lastSentTime: new Date()

View File

@ -143,8 +143,8 @@ Item {
}
}
min: Math.floor(logsModelNg.minValue - Math.abs(logsModelNg.minValue * .05))
onMinChanged: print("min set to", min)
onMaxChanged: print("max set to", min)
onMinChanged: applyNiceNumbers();
onMaxChanged: applyNiceNumbers();
labelsFont.pixelSize: app.smallFont
labelFormat: {
switch (root.stateType.type.toLowerCase()) {

View File

@ -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
}
}
}

View File

@ -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)
}
}
}

View File

@ -227,7 +227,7 @@ MainPageTile {
MediaControls {
iconSize: app.iconSize * 1.2
device: inlineMediaControl.currentDevice
thing: inlineMediaControl.currentDevice
}
}
}

View File

@ -145,7 +145,7 @@ DeviceListPageBase {
}
MediaControls {
visible: itemDelegate.deviceClass.interfaces.indexOf("mediacontroller") >= 0
device: itemDelegate.device
thing: itemDelegate.device
}
}
Item {

View File

@ -141,12 +141,7 @@ Page {
return;
}
var source;
if (engine.jsonRpcClient.ensureServerVersion("1.10")) {
source = Qt.resolvedUrl("../customviews/GenericTypeGraph.qml");
} else {
source = Qt.resolvedUrl("../customviews/GenericTypeGraphPre110.qml");
}
var source = Qt.resolvedUrl("../customviews/GenericTypeGraph.qml");
setSource(source, {device: root.device, stateType: stateType})
}
}

View File

@ -173,7 +173,7 @@ DevicePageBase {
MediaArtworkImage {
Layout.fillHeight: true
Layout.preferredWidth: parent.width / parent.columns
device: root.device
thing: root.device
}
ColumnLayout {
@ -208,7 +208,7 @@ DevicePageBase {
}
MediaControls {
device: root.device
thing: root.device
iconSize: app.iconSize * 2
}
}
@ -220,63 +220,8 @@ DevicePageBase {
Component {
id: browserComponent
Item {
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
}
}
}
}
MediaBrowser {
thing: root.device
}
}
@ -287,7 +232,6 @@ DevicePageBase {
swipeView.currentIndex--;
}
ColumnLayout {
anchors.fill: parent
anchors.margins: app.margins
@ -300,49 +244,13 @@ DevicePageBase {
MediaControls {
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 {
Material.elevation: 1
height: 52
@ -374,64 +282,11 @@ DevicePageBase {
onClicked: swipeView.currentIndex--
}
}
Item {
Layout.fillWidth: true; Layout.fillHeight: true
visible: root.deviceClass.interfaces.indexOf("shufflerepeat") >= 0
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()
}
}
ShuffleRepeatVolumeControl {
Layout.fillWidth: true
thing: root.device
}
Item {
Layout.fillHeight: true
Layout.preferredWidth: swipeView.count > 1 && swipeView.currentIndex < swipeView.count - 1 ? parent.width / 4 : 0

View File

@ -38,16 +38,160 @@ import "../customviews"
DevicePageBase {
id: root
Loader {
anchors.fill: parent
Component.onCompleted: {
var src
if (engine.jsonRpcClient.ensureServerVersion("1.10")) {
src = "SensorDevicePagePost110.qml"
} else {
src = "SensorDevicePagePre110.qml"
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
}
}
setSource(Qt.resolvedUrl(src), {device: root.device, deviceClass: root.deviceClass})
}
}
}

View File

@ -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
}
}
}
}

View File

@ -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
}
}

View File

@ -38,16 +38,55 @@ import "../customviews"
DevicePageBase {
id: root
Loader {
Flickable {
anchors.fill: parent
Component.onCompleted: {
var src
if (engine.jsonRpcClient.ensureServerVersion("1.10")) {
src = "WeatherDevicePagePost110.qml"
} else {
src = "WeatherDevicePagePre110.qml"
clip: true
contentHeight: contentColumn.implicitHeight
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")
}
}
setSource(Qt.resolvedUrl(src), {device: root.device, deviceClass: root.deviceClass})
}
}
}

View File

@ -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")
}
}
}
}

View File

@ -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"
}
}
}

View File

@ -1 +1 @@
../mediaplayer-app-symbolic.svg
../media.svg

View File

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -70,6 +70,7 @@ MainViewBase {
width: parent.width
visible: consumers.count > 0
columns: Math.floor(root.width / 300)
rowSpacing: 0
SmartMeterChart {
Layout.fillWidth: true
@ -78,6 +79,285 @@ MainViewBase {
title: qsTr("Total consumed energy")
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 {
Layout.fillWidth: true
Layout.preferredHeight: width * .7

View File

@ -484,7 +484,7 @@ MainViewBase {
}
// 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
}
}

View 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
}
}

View File

@ -56,23 +56,6 @@ Page {
LogsModel {
id: logsModel
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
}
@ -83,13 +66,11 @@ Page {
ListView {
id: listView
model: engine.jsonRpcClient.ensureServerVersion("1.10") ? logsModelNg : logsModel
model: logsModel
anchors.fill: parent
clip: true
headerPositioning: ListView.OverlayHeader
Component.onCompleted: model.update()
onDraggingChanged: {
if (dragging) {
root.autoScroll = false;
@ -103,14 +84,6 @@ Page {
visible: listView.model.busy
}
onContentYChanged: {
if (!engine.jsonRpcClient.ensureServerVersion("1.10")) {
if (!logsModel.busy && contentY - originY < 5 * height) {
logsModel.fetchEarlier(1)
}
}
}
delegate: ItemDelegate {
id: delegate
width: parent.width