Merge PR #72: Smart meter
This commit is contained in:
commit
55f5f934d9
@ -118,6 +118,15 @@ QVariant Devices::data(const QModelIndex &index, int role) const
|
||||
if (interfaces.contains("notifications")) {
|
||||
return "notifications";
|
||||
}
|
||||
if (interfaces.contains("smartmeter")) {
|
||||
return "smartmeter";
|
||||
}
|
||||
if (interfaces.contains("heating")) {
|
||||
return "heating";
|
||||
}
|
||||
if (interfaces.contains("evcharger")) {
|
||||
return "evcharger";
|
||||
}
|
||||
return "uncategorized";
|
||||
}
|
||||
|
||||
|
||||
@ -303,7 +303,7 @@ void JsonRpcClient::sendRequest(const QVariantMap &request)
|
||||
{
|
||||
QVariantMap newRequest = request;
|
||||
newRequest.insert("token", m_token);
|
||||
qDebug() << "Sending request" << qUtf8Printable(QJsonDocument::fromVariant(newRequest).toJson());
|
||||
// qDebug() << "Sending request" << qUtf8Printable(QJsonDocument::fromVariant(newRequest).toJson());
|
||||
m_connection->sendData(QJsonDocument::fromVariant(newRequest).toJson(QJsonDocument::Compact) + "\n");
|
||||
}
|
||||
|
||||
@ -340,7 +340,7 @@ void JsonRpcClient::dataReceived(const QByteArray &data)
|
||||
// qWarning() << "Could not parse json data from nymea" << m_receiveBuffer.left(splitIndex) << error.errorString();
|
||||
return;
|
||||
}
|
||||
qDebug() << "received response" << qUtf8Printable(jsonDoc.toJson(QJsonDocument::Indented));
|
||||
// qDebug() << "received response" << qUtf8Printable(jsonDoc.toJson(QJsonDocument::Indented));
|
||||
m_receiveBuffer = m_receiveBuffer.right(m_receiveBuffer.length() - splitIndex - 1);
|
||||
if (!m_receiveBuffer.isEmpty()) {
|
||||
staticMetaObject.invokeMethod(this, "dataReceived", Qt::QueuedConnection, Q_ARG(QByteArray, QByteArray()));
|
||||
|
||||
@ -15,7 +15,7 @@ include(../nymea-remoteproxy/libnymea-remoteproxyclient/libnymea-remoteproxyclie
|
||||
|
||||
|
||||
QT -= gui
|
||||
QT += network websockets bluetooth
|
||||
QT += network websockets bluetooth charts
|
||||
|
||||
LIBS += -lssl -lcrypto
|
||||
|
||||
|
||||
@ -213,7 +213,7 @@ void LogsModel::fetchEarlier(int hours)
|
||||
|
||||
void LogsModel::logsReply(const QVariantMap &data)
|
||||
{
|
||||
qDebug() << "logs reply";// << data;
|
||||
// qDebug() << "logs reply" << data;
|
||||
beginResetModel();
|
||||
qDeleteAll(m_list);
|
||||
m_list.clear();
|
||||
@ -242,7 +242,7 @@ void LogsModel::logsReply(const QVariantMap &data)
|
||||
|
||||
void LogsModel::fetchEarlierReply(const QVariantMap &data)
|
||||
{
|
||||
qDebug() << "logs reply";// << data;
|
||||
// qDebug() << "logs reply" << data;
|
||||
|
||||
QList<QVariant> logEntries = data.value("params").toMap().value("logEntries").toList();
|
||||
QList<LogEntry*> newEntries;
|
||||
|
||||
@ -19,6 +19,7 @@ class LogsModel : public QAbstractListModel
|
||||
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)
|
||||
|
||||
@ -56,6 +57,9 @@ public:
|
||||
QDateTime endTime() const;
|
||||
void setEndTime(const QDateTime &endTime);
|
||||
|
||||
// int paginationCount() const;
|
||||
// void setPaginationCount(int paginationCount);
|
||||
|
||||
Q_INVOKABLE LogEntry* get(int index) const;
|
||||
|
||||
Q_INVOKABLE void notificationReceived(const QVariantMap &data);
|
||||
@ -69,6 +73,7 @@ signals:
|
||||
void typeIdsChanged();
|
||||
void startTimeChanged();
|
||||
void endTimeChanged();
|
||||
// void paginationCountChanged();
|
||||
|
||||
public slots:
|
||||
virtual void update();
|
||||
|
||||
@ -5,22 +5,24 @@
|
||||
|
||||
#include "engine.h"
|
||||
#include "types/logentry.h"
|
||||
#include "logmanager.h"
|
||||
|
||||
LogsModelNg::LogsModelNg(QObject *parent) : QAbstractListModel(parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
JsonRpcClient *LogsModelNg::jsonRpcClient() const
|
||||
Engine *LogsModelNg::engine() const
|
||||
{
|
||||
return m_jsonRpcClient;
|
||||
return m_engine;
|
||||
}
|
||||
|
||||
void LogsModelNg::setJsonRpcClient(JsonRpcClient *jsonRpcClient)
|
||||
void LogsModelNg::setEngine(Engine *engine)
|
||||
{
|
||||
if (m_jsonRpcClient != jsonRpcClient) {
|
||||
m_jsonRpcClient = jsonRpcClient;
|
||||
emit jsonRpcClientChanged();
|
||||
if (m_engine != engine) {
|
||||
m_engine = engine;
|
||||
connect(engine->logManager(), &LogManager::logEntryReceived, this, &LogsModelNg::newLogEntryReceived);
|
||||
emit engineChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@ -92,16 +94,16 @@ void LogsModelNg::setDeviceId(const QString &deviceId)
|
||||
}
|
||||
}
|
||||
|
||||
QString LogsModelNg::typeId() const
|
||||
QStringList LogsModelNg::typeIds() const
|
||||
{
|
||||
return m_typeId;
|
||||
return m_typeIds;
|
||||
}
|
||||
|
||||
void LogsModelNg::setTypeId(const QString &typeId)
|
||||
void LogsModelNg::setTypeIds(const QStringList &typeIds)
|
||||
{
|
||||
if (m_typeId != typeId) {
|
||||
m_typeId = typeId;
|
||||
emit typeIdChanged();
|
||||
if (m_typeIds != typeIds) {
|
||||
m_typeIds = typeIds;
|
||||
emit typeIdsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@ -115,7 +117,6 @@ void LogsModelNg::setStartTime(const QDateTime &startTime)
|
||||
if (m_startTime != startTime) {
|
||||
m_startTime = startTime;
|
||||
emit startTimeChanged();
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
@ -129,80 +130,178 @@ void LogsModelNg::setEndTime(const QDateTime &endTime)
|
||||
if (m_endTime != endTime) {
|
||||
m_endTime = endTime;
|
||||
emit endTimeChanged();
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
void LogsModelNg::update()
|
||||
QtCharts::QXYSeries *LogsModelNg::graphSeries() const
|
||||
{
|
||||
if (!m_jsonRpcClient) {
|
||||
qWarning() << "Cannot update. JsonRpcClient not set";
|
||||
return m_graphSeries;
|
||||
}
|
||||
|
||||
void LogsModelNg::setGraphSeries(QtCharts::QXYSeries *graphSeries)
|
||||
{
|
||||
m_graphSeries = graphSeries;
|
||||
}
|
||||
|
||||
QDateTime LogsModelNg::viewStartTime() const
|
||||
{
|
||||
return m_viewStartTime;
|
||||
}
|
||||
|
||||
void LogsModelNg::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 (canFetchMore()) {
|
||||
fetchMore();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QVariant LogsModelNg::minValue() const
|
||||
{
|
||||
// qDebug() << "returning min value" << m_minValue;
|
||||
return m_minValue;
|
||||
}
|
||||
|
||||
QVariant LogsModelNg::maxValue() const
|
||||
{
|
||||
// qDebug() << "returning max value" << m_maxValue;
|
||||
return m_maxValue;
|
||||
}
|
||||
|
||||
void LogsModelNg::logsReply(const QVariantMap &data)
|
||||
{
|
||||
// qDebug() << "logs reply" << data;
|
||||
|
||||
|
||||
int offset = data.value("params").toMap().value("offset").toInt();
|
||||
int count = data.value("params").toMap().value("count").toInt();
|
||||
|
||||
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 typeId = entryMap.value("typeId").toString();
|
||||
QMetaEnum sourceEnum = QMetaEnum::fromType<LogEntry::LoggingSource>();
|
||||
LogEntry::LoggingSource loggingSource = static_cast<LogEntry::LoggingSource>(sourceEnum.keyToValue(entryMap.value("source").toByteArray()));
|
||||
QMetaEnum loggingEventTypeEnum = QMetaEnum::fromType<LogEntry::LoggingEventType>();
|
||||
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);
|
||||
newBlock.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);
|
||||
QVariant newMin = m_minValue;
|
||||
QVariant newMax = m_maxValue;
|
||||
for (int i = 0; i < newBlock.count(); i++) {
|
||||
LogEntry *entry = newBlock.at(i);
|
||||
m_list.insert(offset + i, entry);
|
||||
|
||||
if (m_graphSeries) {
|
||||
Device *dev = m_engine->deviceManager()->devices()->getDevice(entry->deviceId());
|
||||
if (dev && dev->deviceClass()->stateTypes()->getStateType(entry->typeId())->type() == "Bool") {
|
||||
// We don't want bools painting triangles, add a toggle point to keep lines straight
|
||||
if (i > 0) {
|
||||
LogEntry *newerEntry = newBlock.at(i - 1);
|
||||
if (newerEntry->value().toBool() != entry->value().toBool()) {
|
||||
// qDebug() << "Adding bool line series point:" << (newerEntry->timestamp().addSecs(-1)) << newerEntry->timestamp().addSecs(-1).toMSecsSinceEpoch() << (entry->value().toBool() ? 1 : 0) << "(correction)";
|
||||
m_graphSeries->append(QPointF(newerEntry->timestamp().addSecs(-1).toMSecsSinceEpoch(), entry->value().toBool() ? 1 : 0));
|
||||
}
|
||||
}
|
||||
if (m_graphSeries->count() == 0) {
|
||||
// If it's the first one, make sure we add an ending point at 1
|
||||
// qDebug() << "Adding bool line series point:" << QDateTime::currentDateTime() << QDateTime::currentDateTime().toMSecsSinceEpoch() - 1 << (entry->value().toBool() ? 1 : 0) << "(beginning)";
|
||||
m_graphSeries->append(QPointF(QDateTime::currentDateTime().toMSecsSinceEpoch(), 1));
|
||||
m_graphSeries->append(QPointF(QDateTime::currentDateTime().toMSecsSinceEpoch(), entry->value().toBool() ? 1 : 0));
|
||||
} else if (i == 0) {
|
||||
// Adding a new batch... remove the last appended 1 from the previous batch
|
||||
m_graphSeries->remove(m_graphSeries->count() - 1);
|
||||
}
|
||||
// qDebug() << "Adding bool line series point:" << entry->timestamp() << entry->timestamp().toMSecsSinceEpoch() << (entry->value().toBool() ? 1 : 0);
|
||||
m_graphSeries->append(QPointF(entry->timestamp().toMSecsSinceEpoch(), entry->value().toBool() ? 1 : 0));
|
||||
if (i == newBlock.count() - 1) {
|
||||
// End the batch at 1 again
|
||||
m_graphSeries->append(QPointF(entry->timestamp().addSecs(-1).toMSecsSinceEpoch(), 1));
|
||||
}
|
||||
} else {
|
||||
// if (i > 0) {
|
||||
// LogEntry *newerEntry = newBlock.at(i - 1);
|
||||
// if (newerEntry->value() != entry->value()) {
|
||||
// qDebug() << "Adding line series point:" << (offset + i) << newerEntry->timestamp().toMSecsSinceEpoch() - 1 << (entry->value().toReal()) << "(correction)";
|
||||
// m_graphSeries->append(QPointF(newerEntry->timestamp().toMSecsSinceEpoch() - 1, entry->value().toReal()));
|
||||
// }
|
||||
// }
|
||||
|
||||
if (m_graphSeries->count() == 0) {
|
||||
// qDebug() << "Adding 1st line series point:" << (offset + i) << QDateTime::currentDateTime().toMSecsSinceEpoch() << entry->value().toReal();
|
||||
m_graphSeries->append(QPointF(QDateTime::currentDateTime().toMSecsSinceEpoch(), entry->value().toReal()));
|
||||
}
|
||||
// qDebug() << "Adding line series point:" << (offset + i) << entry->timestamp().toMSecsSinceEpoch() << (entry->value().toReal());
|
||||
m_graphSeries->append(QPointF(entry->timestamp().toMSecsSinceEpoch(), entry->value().toReal()));
|
||||
}
|
||||
}
|
||||
if (!newMin.isValid() || newMin > entry->value()) {
|
||||
newMin = entry->value().toReal();
|
||||
}
|
||||
if (!newMax.isValid() || newMax < entry->value()) {
|
||||
newMax = entry->value().toReal();
|
||||
}
|
||||
}
|
||||
endInsertRows();
|
||||
emit countChanged();
|
||||
// qDebug() << "min" << m_minValue << "max" << m_maxValue << "newMin" << newMin << "newMax" << newMax;
|
||||
if (m_minValue != newMin) {
|
||||
m_minValue = newMin;
|
||||
emit minValueChanged();
|
||||
}
|
||||
if (m_maxValue != newMax) {
|
||||
m_maxValue = newMax;
|
||||
emit maxValueChanged();
|
||||
}
|
||||
|
||||
m_busy = false;
|
||||
emit busyChanged();
|
||||
|
||||
if (m_viewStartTime.isValid() && m_list.count() > 0 && m_list.last()->timestamp() > m_viewStartTime && canFetchMore()) {
|
||||
fetchMore();
|
||||
}
|
||||
}
|
||||
|
||||
void LogsModelNg::fetchMore(const QModelIndex &parent)
|
||||
{
|
||||
Q_UNUSED(parent)
|
||||
// qDebug() << "fetchMore called";
|
||||
|
||||
if (!m_engine) {
|
||||
qWarning() << "Cannot update. Engine not set";
|
||||
return;
|
||||
}
|
||||
if (m_busy) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_startTime.isNull() || m_endTime.isNull()) {
|
||||
// Need both, startTime and endTime set
|
||||
if ((!m_startTime.isNull() && m_endTime.isNull()) || (m_startTime.isNull() && !m_endTime.isNull())) {
|
||||
// Need neither or both, startTime and endTime set
|
||||
return;
|
||||
}
|
||||
|
||||
m_currentFetchStartTime = QDateTime();
|
||||
m_currentFetchEndTime = QDateTime();
|
||||
bool haveData = false;
|
||||
for(int i = 0; i < m_fetchedPeriods.length(); i++) {
|
||||
if (m_fetchedPeriods.at(i).first < m_startTime) {
|
||||
if (m_fetchedPeriods.at(i).second == true) {
|
||||
haveData = true;
|
||||
continue;
|
||||
}
|
||||
if (m_fetchedPeriods.at(i).second == false) {
|
||||
haveData = false;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (m_fetchedPeriods.at(i).first == m_startTime) {
|
||||
if (m_fetchedPeriods.at(i).second == true) {
|
||||
haveData = true;
|
||||
continue;
|
||||
}
|
||||
if (m_fetchedPeriods.at(i).second == false) {
|
||||
m_currentFetchStartTime = m_startTime;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (m_fetchedPeriods.at(i).first > m_startTime) {
|
||||
if (m_fetchedPeriods.at(i).second == true) {
|
||||
if (m_currentFetchStartTime.isNull()) {
|
||||
m_currentFetchStartTime = m_startTime;
|
||||
}
|
||||
m_currentFetchEndTime = m_fetchedPeriods.at(i).first;
|
||||
break;
|
||||
}
|
||||
if (m_fetchedPeriods.at(i).second == false) {
|
||||
if (m_currentFetchStartTime.isNull()) {
|
||||
haveData = false;
|
||||
m_currentFetchStartTime = m_fetchedPeriods.at(i).first;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (haveData) {
|
||||
qDebug() << "all the data is fetched";
|
||||
m_busy = false;
|
||||
emit busyChanged();
|
||||
return;
|
||||
}
|
||||
if (m_currentFetchStartTime.isNull()) {
|
||||
m_currentFetchStartTime = m_startTime;
|
||||
}
|
||||
if (m_currentFetchEndTime.isNull()) {
|
||||
m_currentFetchEndTime = m_endTime;
|
||||
}
|
||||
qDebug() << "Fetching from" << m_currentFetchStartTime << "to" << m_currentFetchEndTime;
|
||||
|
||||
m_busy = true;
|
||||
emit busyChanged();
|
||||
|
||||
@ -212,103 +311,77 @@ void LogsModelNg::update()
|
||||
deviceIds.append(m_deviceId);
|
||||
params.insert("deviceIds", deviceIds);
|
||||
}
|
||||
if (!m_typeId.isEmpty()) {
|
||||
if (!m_typeIds.isEmpty()) {
|
||||
QVariantList typeIds;
|
||||
typeIds.append(m_typeId);
|
||||
foreach (const QString &typeId, m_typeIds) {
|
||||
typeIds.append(typeId);
|
||||
}
|
||||
params.insert("typeIds", typeIds);
|
||||
}
|
||||
QVariantList timeFilters;
|
||||
QVariantMap timeFilter;
|
||||
timeFilter.insert("startDate", m_currentFetchStartTime.toSecsSinceEpoch());
|
||||
timeFilter.insert("endDate", m_currentFetchEndTime.toSecsSinceEpoch());
|
||||
timeFilters.append(timeFilter);
|
||||
params.insert("timeFilters", timeFilters);
|
||||
m_jsonRpcClient->sendCommand("Logging.GetLogEntries", params, this, "logsReply");
|
||||
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());
|
||||
|
||||
m_engine->jsonRpcClient()->sendCommand("Logging.GetLogEntries", params, this, "logsReply");
|
||||
// qDebug() << "GetLogEntries called";
|
||||
}
|
||||
|
||||
void LogsModelNg::logsReply(const QVariantMap &data)
|
||||
bool LogsModelNg::canFetchMore(const QModelIndex &parent) const
|
||||
{
|
||||
qDebug() << "logs reply";// << data;
|
||||
|
||||
// First update the fetched periods information
|
||||
int insertIndex = -1;
|
||||
bool noNeedToInsert = false;
|
||||
for (int i = 0; i < m_fetchedPeriods.count(); i++) {
|
||||
if (m_fetchedPeriods.at(i).first < m_currentFetchStartTime) {
|
||||
// skip it
|
||||
insertIndex = i+1;
|
||||
continue;
|
||||
}
|
||||
if (m_fetchedPeriods.at(i).first == m_currentFetchStartTime) {
|
||||
if (m_fetchedPeriods.at(i).second == false) {
|
||||
// Have an end marker where we start inserting. We can drop the existing end marker and just update the end marker
|
||||
if (m_fetchedPeriods.count() > i+1) {
|
||||
if (m_fetchedPeriods.at(i+1).first < m_currentFetchEndTime) {
|
||||
qWarning() << "Overlap detected!";
|
||||
} else if (m_fetchedPeriods.at(i+1).first == m_currentFetchEndTime) {
|
||||
if (m_fetchedPeriods.at(i+1).second == true) {
|
||||
m_fetchedPeriods.removeAt(i+1);
|
||||
}
|
||||
}
|
||||
}
|
||||
m_fetchedPeriods.removeAt(i);
|
||||
noNeedToInsert = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (m_fetchedPeriods.at(i).first > m_currentFetchStartTime) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!noNeedToInsert) {
|
||||
if (insertIndex == -1) {
|
||||
insertIndex = 0;
|
||||
}
|
||||
m_fetchedPeriods.insert(insertIndex, qMakePair<QDateTime,bool>(m_currentFetchStartTime, true));
|
||||
m_fetchedPeriods.insert(insertIndex+1, qMakePair<QDateTime,bool>(m_currentFetchEndTime, false));
|
||||
}
|
||||
qDebug() << "new fetched periods:" << m_fetchedPeriods << "insertIndex:" << insertIndex;
|
||||
m_busy = false;
|
||||
emit busyChanged();
|
||||
|
||||
|
||||
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 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);
|
||||
newBlock.append(entry);
|
||||
}
|
||||
|
||||
// Now let's find where to insert stuff in the model
|
||||
if (!newBlock.isEmpty()) {
|
||||
int indexToInsert = 0;
|
||||
for (int i = 0; i < m_list.count(); i++) {
|
||||
LogEntry *entry = m_list.at(i);
|
||||
if (entry->timestamp() < newBlock.first()->timestamp()) {
|
||||
continue;
|
||||
}
|
||||
indexToInsert = i;
|
||||
break;
|
||||
}
|
||||
|
||||
beginInsertRows(QModelIndex(), indexToInsert, indexToInsert + newBlock.count() - 1);
|
||||
for (int i = 0; i < newBlock.count(); i++) {
|
||||
m_list.insert(indexToInsert + i, newBlock.at(i));
|
||||
}
|
||||
endInsertRows();
|
||||
emit countChanged();
|
||||
}
|
||||
|
||||
update();
|
||||
Q_UNUSED(parent)
|
||||
// qDebug() << "canFetchMore" << (m_engine && m_canFetchMore);
|
||||
return m_engine && m_canFetchMore;
|
||||
}
|
||||
|
||||
void LogsModelNg::newLogEntryReceived(const QVariantMap &data)
|
||||
{
|
||||
if (!m_live) {
|
||||
return;
|
||||
}
|
||||
|
||||
QVariantMap entryMap = data;
|
||||
QString deviceId = entryMap.value("deviceId").toString();
|
||||
if (!m_deviceId.isNull() && deviceId != m_deviceId) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString typeId = entryMap.value("typeId").toString();
|
||||
if (!m_typeIds.isEmpty() && !m_typeIds.contains(typeId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
beginInsertRows(QModelIndex(), 0, 0);
|
||||
QDateTime timeStamp = QDateTime::fromMSecsSinceEpoch(entryMap.value("timestamp").toLongLong());
|
||||
QMetaEnum sourceEnum = QMetaEnum::fromType<LogEntry::LoggingSource>();
|
||||
LogEntry::LoggingSource loggingSource = static_cast<LogEntry::LoggingSource>(sourceEnum.keyToValue(entryMap.value("source").toByteArray()));
|
||||
QMetaEnum loggingEventTypeEnum = QMetaEnum::fromType<LogEntry::LoggingEventType>();
|
||||
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.prepend(entry);
|
||||
if (m_graphSeries) {
|
||||
m_graphSeries->insert(0, QPointF(entry->timestamp().toMSecsSinceEpoch(), entry->value().toReal()));
|
||||
if (m_minValue > entry->value().toReal()) {
|
||||
m_minValue = entry->value().toReal();
|
||||
emit minValueChanged();
|
||||
}
|
||||
if (m_maxValue < entry->value().toReal()) {
|
||||
m_maxValue = entry->value().toReal();
|
||||
emit maxValueChanged();
|
||||
}
|
||||
|
||||
}
|
||||
endInsertRows();
|
||||
emit countChanged();
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -4,20 +4,27 @@
|
||||
#include <QObject>
|
||||
#include <QAbstractListModel>
|
||||
#include <QDateTime>
|
||||
#include <QLineSeries>
|
||||
|
||||
class LogEntry;
|
||||
class JsonRpcClient;
|
||||
class Engine;
|
||||
|
||||
class LogsModelNg : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(JsonRpcClient* jsonRpcClient READ jsonRpcClient WRITE setJsonRpcClient NOTIFY jsonRpcClientChanged)
|
||||
Q_PROPERTY(Engine* engine READ engine WRITE setEngine NOTIFY engineChanged)
|
||||
Q_PROPERTY(bool busy READ busy NOTIFY busyChanged)
|
||||
Q_PROPERTY(bool live READ live WRITE setLive NOTIFY liveChanged)
|
||||
Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
|
||||
Q_PROPERTY(QString deviceId READ deviceId WRITE setDeviceId NOTIFY deviceIdChanged)
|
||||
Q_PROPERTY(QString typeId READ typeId WRITE setTypeId NOTIFY typeIdChanged)
|
||||
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(QVariant minValue READ minValue NOTIFY minValueChanged)
|
||||
Q_PROPERTY(QVariant maxValue READ maxValue NOTIFY maxValueChanged)
|
||||
|
||||
Q_PROPERTY(QtCharts::QXYSeries *graphSeries READ graphSeries WRITE setGraphSeries NOTIFY graphSeriesChanged)
|
||||
Q_PROPERTY(QDateTime viewStartTime READ viewStartTime WRITE setViewStartTime NOTIFY viewStartTimeChanged)
|
||||
|
||||
public:
|
||||
enum Roles {
|
||||
@ -31,8 +38,8 @@ public:
|
||||
|
||||
explicit LogsModelNg(QObject *parent = nullptr);
|
||||
|
||||
JsonRpcClient *jsonRpcClient() const;
|
||||
void setJsonRpcClient(JsonRpcClient* jsonRpcClient);
|
||||
Engine *engine() const;
|
||||
void setEngine(Engine* jsonRpcClient);
|
||||
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
@ -46,8 +53,8 @@ public:
|
||||
QString deviceId() const;
|
||||
void setDeviceId(const QString &deviceId);
|
||||
|
||||
QString typeId() const;
|
||||
void setTypeId(const QString &typeId);
|
||||
QStringList typeIds() const;
|
||||
void setTypeIds(const QStringList &typeId);
|
||||
|
||||
QDateTime startTime() const;
|
||||
void setStartTime(const QDateTime &startTime);
|
||||
@ -55,34 +62,56 @@ public:
|
||||
QDateTime endTime() const;
|
||||
void setEndTime(const QDateTime &endTime);
|
||||
|
||||
QtCharts::QXYSeries *graphSeries() const;
|
||||
void setGraphSeries(QtCharts::QXYSeries *lineSeries);
|
||||
|
||||
QDateTime viewStartTime() const;
|
||||
void setViewStartTime(const QDateTime &viewStartTime);
|
||||
|
||||
QVariant minValue() const;
|
||||
QVariant maxValue() const;
|
||||
|
||||
protected:
|
||||
virtual void fetchMore(const QModelIndex &parent = QModelIndex()) override;
|
||||
virtual bool canFetchMore(const QModelIndex &parent = QModelIndex()) const override;
|
||||
|
||||
signals:
|
||||
void busyChanged();
|
||||
void liveChanged();
|
||||
void deviceIdChanged();
|
||||
void typeIdChanged();
|
||||
void typeIdsChanged();
|
||||
void countChanged();
|
||||
void startTimeChanged();
|
||||
void endTimeChanged();
|
||||
void jsonRpcClientChanged();
|
||||
void engineChanged();
|
||||
void graphSeriesChanged();
|
||||
void viewStartTimeChanged();
|
||||
void minValueChanged();
|
||||
void maxValueChanged();
|
||||
|
||||
private slots:
|
||||
void newLogEntryReceived(const QVariantMap &data);
|
||||
void logsReply(const QVariantMap &data);
|
||||
|
||||
private:
|
||||
QList<LogEntry*> m_list;
|
||||
|
||||
JsonRpcClient *m_jsonRpcClient = nullptr;
|
||||
Engine *m_engine = nullptr;
|
||||
bool m_busy = false;
|
||||
bool m_live = false;
|
||||
QString m_deviceId;
|
||||
QString m_typeId;
|
||||
QStringList m_typeIds;
|
||||
QDateTime m_startTime;
|
||||
QDateTime m_endTime;
|
||||
QDateTime m_currentFetchStartTime;
|
||||
QDateTime m_currentFetchEndTime;
|
||||
int m_blockSize = 100;
|
||||
bool m_canFetchMore = true;
|
||||
QDateTime m_viewStartTime;
|
||||
QVariant m_minValue;
|
||||
QVariant m_maxValue;
|
||||
|
||||
QtCharts::QXYSeries *m_graphSeries = nullptr;
|
||||
|
||||
QList<QPair<QDateTime, bool> > m_fetchedPeriods;
|
||||
|
||||
void update();
|
||||
Q_INVOKABLE void logsReply(const QVariantMap &data);
|
||||
};
|
||||
|
||||
|
||||
|
||||
@ -148,5 +148,10 @@
|
||||
<file>ui/images/view-collapse.svg</file>
|
||||
<file>ui/images/view-expand.svg</file>
|
||||
<file>ui/images/weather-app-symbolic.svg</file>
|
||||
<file>ui/images/zoom-out.svg</file>
|
||||
<file>ui/images/zoom-in.svg</file>
|
||||
<file>ui/images/smartmeter.svg</file>
|
||||
<file>ui/images/radiator.svg</file>
|
||||
<file>ui/images/ev-charger.svg</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
@ -18,7 +18,7 @@
|
||||
* *
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#include <QGuiApplication>
|
||||
#include <QApplication>
|
||||
#include <QCommandLineParser>
|
||||
#include <QtQml/QQmlContext>
|
||||
#include <QQmlApplicationEngine>
|
||||
@ -58,7 +58,7 @@ int main(int argc, char *argv[])
|
||||
{
|
||||
|
||||
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
|
||||
QGuiApplication application(argc, argv);
|
||||
QApplication application(argc, argv);
|
||||
application.setApplicationName("nymea-app");
|
||||
application.setOrganizationName("nymea");
|
||||
|
||||
@ -71,7 +71,7 @@ int main(int argc, char *argv[])
|
||||
applicationFont.setCapitalization(QFont::MixedCase);
|
||||
applicationFont.setPixelSize(16);
|
||||
applicationFont.setWeight(QFont::Normal);
|
||||
QGuiApplication::setFont(applicationFont);
|
||||
QApplication::setFont(applicationFont);
|
||||
|
||||
QTranslator qtTranslator;
|
||||
qtTranslator.load("qt_" + QLocale::system().name(),
|
||||
|
||||
@ -2,7 +2,7 @@ TEMPLATE=app
|
||||
TARGET=nymea-app
|
||||
include(../config.pri)
|
||||
|
||||
QT += network qml quick quickcontrols2 svg websockets bluetooth
|
||||
QT += network qml quick quickcontrols2 svg websockets bluetooth charts
|
||||
|
||||
INCLUDEPATH += $$top_srcdir/libnymea-common \
|
||||
$$top_srcdir/libnymea-app-core
|
||||
@ -124,3 +124,6 @@ BR=$$BRANDING
|
||||
target.path = /usr/bin
|
||||
INSTALLS += target
|
||||
|
||||
DISTFILES += \
|
||||
ruletemplates/smartmetertemplates.json
|
||||
|
||||
|
||||
@ -57,12 +57,17 @@
|
||||
<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/GenericDeviceStateDetailsPage.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/ConfigureThingPage.qml</file>
|
||||
<file>ui/devicepages/InputTriggerDevicePage.qml</file>
|
||||
@ -127,5 +132,9 @@
|
||||
<file>translations/nymea-app-de_DE.qm</file>
|
||||
<file>translations/nymea-app-en_US.qm</file>
|
||||
<file>../LICENSE</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>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
@ -3,5 +3,6 @@
|
||||
<file>ruletemplates/buttontemplates.json</file>
|
||||
<file>ruletemplates/notificationtemplates.json</file>
|
||||
<file>ruletemplates/accesscontroltemplates.json</file>
|
||||
<file>ruletemplates/smartmetertemplates.json</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
84
nymea-app/ruletemplates/smartmetertemplates.json
Normal file
84
nymea-app/ruletemplates/smartmetertemplates.json
Normal file
@ -0,0 +1,84 @@
|
||||
{
|
||||
"templates": [
|
||||
{
|
||||
"interfaceName": "extendedsmartmeterproducer",
|
||||
"description": "Charge my car while producing energy.",
|
||||
"ruleNameTemplate": "Smart car charging",
|
||||
"stateEvaluatorTemplate": {
|
||||
"stateDescriptorTemplate": {
|
||||
"interfaceName": "extendedsmartmeterproducer",
|
||||
"interfaceState": "currentPower",
|
||||
"operator": "ValueOperatorGreater",
|
||||
"value": 0,
|
||||
"selectionId": 0
|
||||
}
|
||||
},
|
||||
"ruleActionTemplates": [
|
||||
{
|
||||
"interfaceName": "evcharger",
|
||||
"interfaceAction": "power",
|
||||
"selectionId": 1,
|
||||
"params": [
|
||||
{
|
||||
"name": "power",
|
||||
"value": true
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"ruleExitActionTemplates": [
|
||||
{
|
||||
"interfaceName": "evcharger",
|
||||
"interfaceAction": "power",
|
||||
"selectionId": 1,
|
||||
"params": [
|
||||
{
|
||||
"name": "power",
|
||||
"value": false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"interfaceName": "extendedsmartmeterproducer",
|
||||
"description": "Turn on heating while producing energy.",
|
||||
"ruleNameTemplate": "Smart heating",
|
||||
"stateEvaluatorTemplate": {
|
||||
"stateDescriptorTemplate": {
|
||||
"interfaceName": "extendedsmartmeterproducer",
|
||||
"interfaceState": "currentPower",
|
||||
"operator": "ValueOperatorGreater",
|
||||
"value": 0,
|
||||
"selectionId": 0
|
||||
}
|
||||
},
|
||||
"ruleActionTemplates": [
|
||||
{
|
||||
"interfaceName": "heating",
|
||||
"interfaceAction": "power",
|
||||
"selectionId": 1,
|
||||
"params": [
|
||||
{
|
||||
"name": "power",
|
||||
"value": true
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"ruleExitActionTemplates": [
|
||||
{
|
||||
"interfaceName": "heating",
|
||||
"interfaceAction": "power",
|
||||
"selectionId": 1,
|
||||
"params": [
|
||||
{
|
||||
"name": "power",
|
||||
"value": false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
58
nymea-app/ruletemplates/template.json
Normal file
58
nymea-app/ruletemplates/template.json
Normal file
@ -0,0 +1,58 @@
|
||||
{
|
||||
"templates": [
|
||||
{
|
||||
"interfaceName": "",
|
||||
"description": "",
|
||||
"ruleNameTemplate": "%0 ...",
|
||||
"eventDescriptorTemplates": [ // optional
|
||||
{
|
||||
"interfaceName": "",
|
||||
"interfaceEvent": "",
|
||||
"selectionId": 0,
|
||||
"params": [ // optional
|
||||
{
|
||||
"name": "",
|
||||
"value": "", //optional
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"stateEvaluatorTemplate": {
|
||||
"stateDescriptorTemplate": {
|
||||
"interfaceName": "",
|
||||
"interfaceState": "",
|
||||
"selectionId": 1,
|
||||
"operator": "ValueOperatorEquals",
|
||||
"value": true
|
||||
}
|
||||
},
|
||||
"ruleActionTemplates": [
|
||||
{
|
||||
"interfaceName": "",
|
||||
"interfaceAction": "",
|
||||
"selectionId": 2,
|
||||
"params": [
|
||||
{
|
||||
"name": "",
|
||||
"value": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"ruleExitActionTemplates": [
|
||||
{
|
||||
"interfaceName": "",
|
||||
"interfaceAction": "",
|
||||
"selectionId": 2,
|
||||
"params": [
|
||||
{
|
||||
"name": "",
|
||||
"value": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -36,6 +36,7 @@ Page {
|
||||
Connections {
|
||||
target: engine.deviceManager
|
||||
onPairDeviceReply: {
|
||||
busyOverlay.shown = false
|
||||
switch (params["setupMethod"]) {
|
||||
case "SetupMethodPushButton":
|
||||
d.pairingTransactionId = params["pairingTransactionId"];
|
||||
@ -52,9 +53,11 @@ Page {
|
||||
}
|
||||
}
|
||||
onConfirmPairingReply: {
|
||||
busyOverlay.shown = false
|
||||
internalPageStack.push(resultsPage, {success: params["deviceError"] === "DeviceErrorNoError", deviceId: params["deviceId"]})
|
||||
}
|
||||
onAddDeviceReply: {
|
||||
busyOverlay.shown = false;
|
||||
internalPageStack.push(resultsPage, {success: params["deviceError"] === "DeviceErrorNoError", deviceId: params["deviceId"]})
|
||||
}
|
||||
}
|
||||
@ -358,6 +361,8 @@ Page {
|
||||
break;
|
||||
}
|
||||
|
||||
busyOverlay.shown = true;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -448,4 +453,8 @@ Page {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BusyOverlay {
|
||||
id: busyOverlay
|
||||
}
|
||||
}
|
||||
|
||||
@ -53,7 +53,7 @@ ApplicationWindow {
|
||||
rootItem.handleCloseEvent(close)
|
||||
}
|
||||
|
||||
property var supportedInterfaces: ["light", "weather", "sensor", "media", "garagegate", "awning", "shutter", "blind", "accesscontrol", "button", "notifications", "inputtrigger", "outputtrigger", "gateway"]
|
||||
property var supportedInterfaces: ["light", "weather", "sensor", "media", "garagegate", "awning", "shutter", "blind", "heating", "smartmeter", "evcharger", "accesscontrol", "button", "notifications", "inputtrigger", "outputtrigger", "gateway"]
|
||||
function interfaceToString(name) {
|
||||
switch(name) {
|
||||
case "light":
|
||||
@ -93,6 +93,16 @@ ApplicationWindow {
|
||||
return qsTr("Garage gates");
|
||||
case "accesscontrol":
|
||||
return qsTr("Access control");
|
||||
case "smartmeter":
|
||||
case "smartmeterproducer":
|
||||
case "smartmeterconsumer":
|
||||
case "extendedsmartmeterproducer":
|
||||
case "extendedsmartmeterconsumer":
|
||||
return qsTr("Smart meters");
|
||||
case "heating":
|
||||
return qsTr("Heatings");
|
||||
case "evcharger":
|
||||
return qsTr("EV-chargers");
|
||||
case "uncategorized":
|
||||
return qsTr("Uncategorized")
|
||||
default:
|
||||
@ -149,8 +159,6 @@ ApplicationWindow {
|
||||
return Qt.resolvedUrl("images/network-wired-symbolic.svg")
|
||||
case "notifications":
|
||||
return Qt.resolvedUrl("images/notification.svg")
|
||||
case "connectable":
|
||||
return Qt.resolvedUrl("images/stock_link.svg")
|
||||
case "inputtrigger":
|
||||
return Qt.resolvedUrl("images/attention.svg")
|
||||
case "outputtrigger":
|
||||
@ -175,6 +183,20 @@ ApplicationWindow {
|
||||
return Qt.resolvedUrl("images/fingerprint.svg")
|
||||
case "accesscontrol":
|
||||
return Qt.resolvedUrl("images/network-secure.svg");
|
||||
case "smartmeter":
|
||||
case "smartmeterconsumer":
|
||||
case "smartmeterproducer":
|
||||
case "extendedsmartmeterconsumer":
|
||||
case "extendedsmartmeterproducer":
|
||||
return Qt.resolvedUrl("images/smartmeter.svg")
|
||||
case "heating":
|
||||
case "extendedheating":
|
||||
return Qt.resolvedUrl("images/radiator.svg")
|
||||
case "evcharger":
|
||||
case "extendedevcharger":
|
||||
return Qt.resolvedUrl("images/ev-charger.svg")
|
||||
case "connectable":
|
||||
return Qt.resolvedUrl("images/stock_link.svg")
|
||||
default:
|
||||
console.warn("InterfaceToIcon: Unhandled interface", name)
|
||||
}
|
||||
@ -187,7 +209,11 @@ ApplicationWindow {
|
||||
"moisturesensor":"blue",
|
||||
"lightsensor": "orange",
|
||||
"conductivitysensor": "green",
|
||||
"pressuresensor": "grey"
|
||||
"pressuresensor": "grey",
|
||||
"smartmeterproducer": "lightgreen",
|
||||
"smartmeterconsumer": "orange",
|
||||
"extendedsmartmeterproducer": "blue",
|
||||
"extendedsmartmeterconsumer": "blue"
|
||||
}
|
||||
|
||||
function interfaceToColor(name) {
|
||||
@ -244,6 +270,8 @@ ApplicationWindow {
|
||||
page = "NotificationsDevicePage.qml";
|
||||
} else if (interfaceList.indexOf("fingerprintreader") >= 0) {
|
||||
page = "FingerprintReaderDevicePage.qml";
|
||||
} else if (interfaceList.indexOf("smartmeter") >= 0) {
|
||||
page = "SmartMeterDevicePage.qml"
|
||||
} else {
|
||||
page = "GenericDevicePage.qml";
|
||||
}
|
||||
|
||||
@ -81,23 +81,6 @@ Page {
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true; Layout.leftMargin: app.margins; Layout.rightMargin: app.margins
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Graph style")
|
||||
}
|
||||
RadioButton {
|
||||
checked: settings.graphStyle === "bars"
|
||||
text: qsTr("Bars")
|
||||
onClicked: settings.graphStyle = "bars"
|
||||
}
|
||||
RadioButton {
|
||||
checked: settings.graphStyle === "bezier"
|
||||
text: qsTr("Lines")
|
||||
onClicked: settings.graphStyle = "bezier"
|
||||
}
|
||||
}
|
||||
CheckDelegate {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Return to home on idle")
|
||||
|
||||
@ -13,6 +13,7 @@ ToolButton {
|
||||
id: image
|
||||
anchors.fill: parent
|
||||
anchors.margins: app.margins / 2
|
||||
opacity: enabled ? 1 : .5
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
364
nymea-app/ui/customviews/GenericTypeGraph.qml
Normal file
364
nymea-app/ui/customviews/GenericTypeGraph.qml
Normal file
@ -0,0 +1,364 @@
|
||||
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"
|
||||
import QtCharts 2.2
|
||||
|
||||
Item {
|
||||
id: root
|
||||
implicitHeight: width * .6
|
||||
|
||||
property var device: null
|
||||
property var stateType: null
|
||||
property int roundTo: 2
|
||||
readonly property var valueState: device.states.getState(stateType.id)
|
||||
readonly property var deviceClass: engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId);
|
||||
readonly property bool hasConnectable: deviceClass.interfaces.indexOf("connectable") >= 0
|
||||
readonly property var connectedStateType: hasConnectable ? deviceClass.stateTypes.findByName("connected") : null
|
||||
|
||||
property color color: app.accentColor
|
||||
property string iconSource: ""
|
||||
|
||||
LogsModelNg {
|
||||
id: logsModelNg
|
||||
engine: _engine
|
||||
deviceId: root.device.id
|
||||
typeIds: [root.stateType.id]
|
||||
live: true
|
||||
graphSeries: lineSeries1
|
||||
viewStartTime: xAxis.min
|
||||
}
|
||||
|
||||
LogsModelNg {
|
||||
id: connectedLogsModel
|
||||
engine: root.hasConnectable ? _engine : null // don't even try to poll if we don't have a connectable interface
|
||||
deviceId: root.device.id
|
||||
typeIds: [root.connectedStateType.id]
|
||||
live: true
|
||||
graphSeries: connectedLineSeries
|
||||
viewStartTime: xAxis.min
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
RowLayout {
|
||||
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins
|
||||
ColorIcon {
|
||||
Layout.preferredHeight: app.iconSize
|
||||
Layout.preferredWidth: app.iconSize
|
||||
name: root.iconSource
|
||||
visible: root.iconSource.length > 0
|
||||
color: root.color
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: 1.0 * Math.round(root.valueState.value * Math.pow(10, root.roundTo)) / Math.pow(10, root.roundTo) + " " + root.stateType.unitString
|
||||
font.pixelSize: app.largeFont
|
||||
}
|
||||
|
||||
HeaderButton {
|
||||
imageSource: "../images/zoom-out.svg"
|
||||
onClicked: {
|
||||
var newTime = new Date(xAxis.min.getTime() - (xAxis.timeDiff * 1000 / 4))
|
||||
xAxis.min = newTime;
|
||||
}
|
||||
}
|
||||
|
||||
HeaderButton {
|
||||
imageSource: "../images/zoom-in.svg"
|
||||
enabled: xAxis.timeDiff > (60 * 30)
|
||||
onClicked: {
|
||||
var newTime = new Date(Math.min(xAxis.min.getTime() + (xAxis.timeDiff * 1000 / 4), xAxis.max.getTime() - (1000 * 60 * 30)))
|
||||
xAxis.min = newTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ChartView {
|
||||
id: chartView
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
margins.top: 0
|
||||
margins.bottom: 0
|
||||
margins.left: 0
|
||||
margins.right: 0
|
||||
backgroundColor: Material.background
|
||||
legend.visible: false
|
||||
legend.labelColor: app.foregroundColor
|
||||
|
||||
animationDuration: 300
|
||||
animationOptions: ChartView.SeriesAnimations
|
||||
|
||||
ValueAxis {
|
||||
id: yAxis
|
||||
max: logsModelNg.maxValue + Math.abs(logsModelNg.maxValue * .05)
|
||||
min: logsModelNg.minValue - Math.abs(logsModelNg.minValue * .05)
|
||||
labelsFont.pixelSize: app.smallFont
|
||||
labelsColor: app.foregroundColor
|
||||
tickCount: chartView.height / 40
|
||||
color: Qt.rgba(app.foregroundColor.r, app.foregroundColor.g, app.foregroundColor.b, .2)
|
||||
gridLineColor: color
|
||||
}
|
||||
|
||||
ValueAxis {
|
||||
id: connectedAxis
|
||||
min: 0
|
||||
max: 1
|
||||
visible: false
|
||||
}
|
||||
|
||||
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
|
||||
onTimeDiffChanged: print("timeDiff is:", timeDiff)
|
||||
|
||||
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 * 6) + 2000);
|
||||
return date;
|
||||
}
|
||||
max: {
|
||||
var date = new Date();
|
||||
date.setTime(date.getTime() + 2000)
|
||||
return date;
|
||||
}
|
||||
}
|
||||
|
||||
AreaSeries {
|
||||
axisX: xAxis
|
||||
axisY: connectedAxis
|
||||
name: qsTr("Not connected")
|
||||
visible: root.hasConnectable
|
||||
upperSeries: LineSeries {
|
||||
XYPoint {x: xAxis.min.getTime(); y: 1}
|
||||
XYPoint {x: xAxis.max.getTime(); y: 1}
|
||||
}
|
||||
|
||||
lowerSeries: LineSeries {
|
||||
id: connectedLineSeries
|
||||
}
|
||||
color: "#55ff0000"
|
||||
borderWidth: 0
|
||||
}
|
||||
|
||||
AreaSeries {
|
||||
id: mainSeries
|
||||
axisX: xAxis
|
||||
axisY: yAxis
|
||||
name: root.stateType.displayName
|
||||
borderColor: root.color
|
||||
borderWidth: 4
|
||||
lowerSeries: LineSeries {
|
||||
id: lineSeries0
|
||||
XYPoint { x: xAxis.max.getTime(); y: 0 }
|
||||
XYPoint { x: xAxis.min.getTime(); y: 0 }
|
||||
}
|
||||
|
||||
upperSeries: LineSeries {
|
||||
id: lineSeries1
|
||||
onPointAdded: {
|
||||
var newPoint = lineSeries1.at(index)
|
||||
|
||||
if (newPoint.x > lineSeries0.at(0).x) {
|
||||
lineSeries0.replace(0, newPoint.x, 0)
|
||||
}
|
||||
if (newPoint.x < lineSeries0.at(1).x) {
|
||||
lineSeries0.replace(1, newPoint.x, 0)
|
||||
}
|
||||
|
||||
if (newPoint.x <= xAxis.max.getTime() || logsModelNg.busy) {
|
||||
return;
|
||||
}
|
||||
|
||||
var diffMaxToNew = newPoint.x - xAxis.max.getTime();
|
||||
print("diffToNew is", diffMaxToNew)
|
||||
if (diffMaxToNew < 1000 * 60 * 5) {
|
||||
chartView.animationOptions = ChartView.NoAnimation
|
||||
var newMin = xAxis.min.getTime() + diffMaxToNew;
|
||||
xAxis.max = new Date(newPoint.x);
|
||||
xAxis.min = new Date(newMin)
|
||||
chartView.animationOptions = ChartView.SeriesAnimations
|
||||
}
|
||||
}
|
||||
}
|
||||
color: Qt.rgba(root.color.r, root.color.g, root.color.b, .3)
|
||||
onHovered: {
|
||||
markClosestPoint(point)
|
||||
}
|
||||
|
||||
function markClosestPoint(point) {
|
||||
var found = false;
|
||||
if (lineSeries1.count == 1) {
|
||||
selectedHighlights.removePoints(0, selectedHighlights.count)
|
||||
selectedHighlights.append(lineSeries1.at(0).x, lineSeries1.at(1).y)
|
||||
return;
|
||||
}
|
||||
|
||||
var searchIndex = Math.floor(lineSeries1.count / 2)
|
||||
var previousIndex = 0;
|
||||
var nextIndex = lineSeries1.count - 1;
|
||||
|
||||
while (previousIndex + 1 != nextIndex) {
|
||||
if (point.x < lineSeries1.at(searchIndex).x) {
|
||||
previousIndex = searchIndex;
|
||||
} else if (point.x > lineSeries1.at(searchIndex).x) {
|
||||
nextIndex = searchIndex;
|
||||
}
|
||||
searchIndex = previousIndex + Math.floor((nextIndex - previousIndex) / 2);
|
||||
}
|
||||
var diffToPrevious = Math.abs(point.x - lineSeries1.at(previousIndex).x)
|
||||
var diffToNext = Math.abs(point.x - lineSeries1.at(nextIndex).x)
|
||||
var closestPoint = diffToPrevious < diffToNext ? lineSeries1.at(previousIndex) : lineSeries1.at(nextIndex);
|
||||
|
||||
selectedHighlights.removePoints(0, selectedHighlights.count)
|
||||
selectedHighlights.append(closestPoint.x, closestPoint.y)
|
||||
}
|
||||
}
|
||||
|
||||
ScatterSeries {
|
||||
id: selectedHighlights
|
||||
color: root.color
|
||||
markerSize: 10
|
||||
borderWidth: 2
|
||||
borderColor: root.color
|
||||
axisX: xAxis
|
||||
axisY: yAxis
|
||||
pointLabelsVisible: true
|
||||
pointLabelsColor: app.foregroundColor
|
||||
pointLabelsFont.pixelSize: app.smallFont
|
||||
pointLabelsFormat: "@yPoint"
|
||||
pointLabelsClipping: false
|
||||
}
|
||||
|
||||
BusyIndicator {
|
||||
anchors.centerIn: parent
|
||||
visible: logsModelNg.busy
|
||||
}
|
||||
|
||||
|
||||
MouseArea {
|
||||
x: chartView.plotArea.x
|
||||
y: chartView.plotArea.y
|
||||
width: chartView.plotArea.width
|
||||
height: chartView.plotArea.height
|
||||
property int lastX: 0
|
||||
property int lastY: 0
|
||||
preventStealing: false
|
||||
|
||||
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)
|
||||
}
|
||||
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
|
||||
lastY = mouse.y
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
84
nymea-app/ui/customviews/GenericTypeGraphPre110.qml
Normal file
84
nymea-app/ui/customviews/GenericTypeGraphPre110.qml
Normal file
@ -0,0 +1,84 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -6,8 +6,6 @@ import "../components"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
// %1 will be replaced with count
|
||||
property string text
|
||||
|
||||
signal addRuleClicked(var value)
|
||||
|
||||
@ -15,122 +13,74 @@ Item {
|
||||
|
||||
property alias delegate: listView.delegate
|
||||
|
||||
property bool autoscroll: true
|
||||
|
||||
ColumnLayout {
|
||||
ListView {
|
||||
id: listView
|
||||
anchors.fill: parent
|
||||
model: logsModel
|
||||
clip: true
|
||||
|
||||
Label {
|
||||
id: titleLabel
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: app.margins
|
||||
wrapMode: Text.WordWrap
|
||||
text: root.text.arg(logsModel.count).arg((logsModel.endTime.getTime() - logsModel.startTime.getTime())/ 1000 / 60 / 60 /24)
|
||||
}
|
||||
ScrollBar.vertical: ScrollBar {}
|
||||
|
||||
ThinDivider {}
|
||||
|
||||
RulesFilterModel {
|
||||
id: rulesFilterModel
|
||||
rules: engine.ruleManager.rules
|
||||
filterDeviceId: root.logsModel.deviceId
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: listView
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
model: logsModel
|
||||
clip: true
|
||||
onCountChanged: {
|
||||
if (root.autoscroll) {
|
||||
positionViewAtEnd()
|
||||
}
|
||||
}
|
||||
|
||||
onContentYChanged: {
|
||||
onContentYChanged: {
|
||||
if (!engine.jsonRpcClient.ensureServerVersion("1.10")) {
|
||||
if (!logsModel.busy && contentY - originY < 5 * height) {
|
||||
logsModel.fetchEarlier(24)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delegate: SwipeDelegate {
|
||||
id: logEntryDelegate
|
||||
width: parent.width
|
||||
implicitHeight: app.delegateHeight
|
||||
property var device: engine.deviceManager.devices.getDevice(model.deviceId)
|
||||
property var deviceClass: engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId)
|
||||
contentItem: RowLayout {
|
||||
ColorIcon {
|
||||
Layout.preferredHeight: app.iconSize
|
||||
Layout.preferredWidth: height
|
||||
name: "../images/event.svg"
|
||||
color: app.accentColor
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Label {
|
||||
id: timeStampLabel
|
||||
Layout.fillWidth: true
|
||||
text: Qt.formatDateTime(model.timestamp,"dd.MM.yy - hh:mm:ss")
|
||||
}
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: "%1: %2".arg(deviceClass.eventTypes.getEventType(model.typeId).displayName).arg(model.value.trim())
|
||||
elide: Text.ElideRight
|
||||
font.pixelSize: app.smallFont
|
||||
}
|
||||
}
|
||||
// ColorIcon {
|
||||
// Layout.preferredWidth: app.iconSize
|
||||
// Layout.preferredHeight: width
|
||||
// name: "../images/magic.svg"
|
||||
// color: {
|
||||
// for (var i = 0; i < rulesFilterModel.count; i++) {
|
||||
// var rule = rulesFilterModel.get(i);
|
||||
// for (var j = 0; j < rule.eventDescriptors.count; j++) {
|
||||
// var eventDescriptor = rule.eventDescriptors.get(j);
|
||||
// if (eventDescriptor.eventTypeId === root.logsModel.typeId) {
|
||||
// var matching = true;
|
||||
// for (var k = 0; k < eventDescriptor.paramDescriptors.count; k++) {
|
||||
// var paramDescriptor = eventDescriptor.paramDescriptors.get(k);
|
||||
// if (paramDescriptor.value === model.value) {
|
||||
// return app.accentColor;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// return keyColor;
|
||||
// }
|
||||
|
||||
// }
|
||||
delegate: SwipeDelegate {
|
||||
id: logEntryDelegate
|
||||
width: parent.width
|
||||
implicitHeight: app.delegateHeight
|
||||
property var device: engine.deviceManager.devices.getDevice(model.deviceId)
|
||||
property var deviceClass: engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId)
|
||||
contentItem: RowLayout {
|
||||
ColorIcon {
|
||||
Layout.preferredHeight: app.iconSize
|
||||
Layout.preferredWidth: height
|
||||
name: "../images/event.svg"
|
||||
color: app.accentColor
|
||||
}
|
||||
swipe.right: MouseArea {
|
||||
height: logEntryDelegate.height
|
||||
width: height
|
||||
anchors.right: parent.right
|
||||
ColorIcon {
|
||||
anchors.fill: parent
|
||||
anchors.margins: app.margins
|
||||
name: "../images/magic.svg"
|
||||
|
||||
ColumnLayout {
|
||||
Label {
|
||||
id: timeStampLabel
|
||||
Layout.fillWidth: true
|
||||
text: Qt.formatDateTime(model.timestamp,"dd.MM.yy - hh:mm:ss")
|
||||
}
|
||||
onClicked: root.addRuleClicked(model.value)
|
||||
}
|
||||
onClicked: {
|
||||
if (swipe.complete) {
|
||||
swipe.close()
|
||||
} else {
|
||||
swipe.open(SwipeDelegate.Right)
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: "%1: %2".arg(deviceClass.eventTypes.getEventType(model.typeId).displayName).arg(model.value.trim())
|
||||
elide: Text.ElideRight
|
||||
font.pixelSize: app.smallFont
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BusyIndicator {
|
||||
anchors.centerIn: parent
|
||||
visible: root.logsModel.busy
|
||||
running: visible
|
||||
swipe.right: MouseArea {
|
||||
height: logEntryDelegate.height
|
||||
width: height
|
||||
anchors.right: parent.right
|
||||
ColorIcon {
|
||||
anchors.fill: parent
|
||||
anchors.margins: app.margins
|
||||
name: "../images/magic.svg"
|
||||
}
|
||||
onClicked: root.addRuleClicked(model.value)
|
||||
}
|
||||
onClicked: {
|
||||
if (swipe.complete) {
|
||||
swipe.close()
|
||||
} else {
|
||||
swipe.open(SwipeDelegate.Right)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BusyIndicator {
|
||||
anchors.centerIn: parent
|
||||
visible: root.logsModel.busy
|
||||
running: visible
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -304,6 +304,7 @@ ItemDelegate {
|
||||
property var value: null
|
||||
text: root.actionType.displayName
|
||||
onClicked: {
|
||||
print("ActionDelegate: Button clicked")
|
||||
var params = [];
|
||||
print("fooo", root.actionType.paramTypes.count)
|
||||
for (var i = 0; i < root.actionType.paramTypes.count; i++) {
|
||||
|
||||
130
nymea-app/ui/devicelistpages/SmartMeterDeviceListPage.qml
Normal file
130
nymea-app/ui/devicelistpages/SmartMeterDeviceListPage.qml
Normal file
@ -0,0 +1,130 @@
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 2.1
|
||||
import QtQuick.Controls.Material 2.1
|
||||
import QtQuick.Layouts 1.1
|
||||
import Nymea 1.0
|
||||
import "../components"
|
||||
|
||||
DeviceListPageBase {
|
||||
id: root
|
||||
|
||||
header: GuhHeader {
|
||||
text: qsTr("Smart meters")
|
||||
onBackPressed: pageStack.pop()
|
||||
}
|
||||
|
||||
ListView {
|
||||
anchors.fill: parent
|
||||
model: root.devicesProxy
|
||||
|
||||
delegate: ItemDelegate {
|
||||
id: itemDelegate
|
||||
width: parent.width
|
||||
|
||||
property bool inline: width > 500
|
||||
|
||||
property var device: devicesProxy.get(index);
|
||||
property var deviceClass: engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId);
|
||||
|
||||
bottomPadding: index === ListView.view.count - 1 ? topPadding : 0
|
||||
contentItem: Pane {
|
||||
id: contentItem
|
||||
Material.elevation: 2
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
topPadding: 0
|
||||
bottomPadding: 0
|
||||
|
||||
contentItem: ItemDelegate {
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
topPadding: 0
|
||||
bottomPadding: 0
|
||||
contentItem: ColumnLayout {
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: app.mediumFont + app.margins
|
||||
color: Qt.rgba(app.foregroundColor.r, app.foregroundColor.g, app.foregroundColor.b, .05)
|
||||
RowLayout {
|
||||
anchors { verticalCenter: parent.verticalCenter; left: parent.left; right: parent.right; margins: app.margins }
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: model.name
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
ColorIcon {
|
||||
Layout.preferredHeight: app.iconSize * .5
|
||||
Layout.preferredWidth: height
|
||||
name: "../images/battery/battery-020.svg"
|
||||
visible: itemDelegate.deviceClass.interfaces.indexOf("battery") >= 0 && itemDelegate.device.states.getState(itemDelegate.deviceClass.stateTypes.findByName("batteryCritical").id).value === true
|
||||
}
|
||||
ColorIcon {
|
||||
Layout.preferredHeight: app.iconSize * .5
|
||||
Layout.preferredWidth: height
|
||||
name: "../images/dialog-warning-symbolic.svg"
|
||||
visible: itemDelegate.deviceClass.interfaces.indexOf("connectable") >= 0 && itemDelegate.device.states.getState(itemDelegate.deviceClass.stateTypes.findByName("connected").id).value === false
|
||||
color: "red"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
GridLayout {
|
||||
id: dataGrid
|
||||
columns: Math.floor(contentItem.width / 120)
|
||||
Layout.margins: app.margins
|
||||
Repeater {
|
||||
model: ListModel {
|
||||
Component.onCompleted: {
|
||||
if (itemDelegate.deviceClass.interfaces.indexOf("smartmeterproducer") >= 0) {
|
||||
append( {interfaceName: "smartmeterproducer", stateName: "totalEnergyProduced" })
|
||||
}
|
||||
if (itemDelegate.deviceClass.interfaces.indexOf("smartmeterconsumer") >= 0) {
|
||||
append( {interfaceName: "smartmeterconsumer", stateName: "totalEnergyConsumed" })
|
||||
}
|
||||
var added = false;
|
||||
if (itemDelegate.deviceClass.interfaces.indexOf("extendedsmartmeterproducer") >= 0) {
|
||||
append({interfaceName: "extendedsmartmeterconsumer", stateName: "currentPower"});
|
||||
added = true;
|
||||
}
|
||||
if (!added && itemDelegate.deviceClass.interfaces.indexOf("extendedsmartmeterconsumer") >= 0) {
|
||||
append({interfaceName: "extendedsmartmeterconsumer", stateName: "currentPower"});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delegate: RowLayout {
|
||||
id: sensorValueDelegate
|
||||
Layout.preferredWidth: contentItem.width / dataGrid.columns
|
||||
|
||||
property var stateType: itemDelegate.deviceClass.stateTypes.findByName(model.stateName)
|
||||
property var stateValue: stateType ? itemDelegate.device.states.getState(stateType.id) : null
|
||||
|
||||
ColorIcon {
|
||||
Layout.preferredHeight: app.iconSize * .8
|
||||
Layout.preferredWidth: height
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
color: app.interfaceToColor(model.interfaceName)
|
||||
name: app.interfaceToIcon(model.interfaceName)
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: sensorValueDelegate.stateValue
|
||||
? "%1 %2".arg(1.0 * Math.round(sensorValueDelegate.stateValue.value * 100000) / 100000).arg(sensorValueDelegate.stateType.unitString)
|
||||
: ""
|
||||
elide: Text.ElideRight
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
font.pixelSize: app.smallFont
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
onClicked: {
|
||||
enterPage(index, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -5,15 +5,29 @@ import Nymea 1.0
|
||||
import "../components"
|
||||
import "../customviews"
|
||||
|
||||
GenericDevicePage {
|
||||
DevicePageBase {
|
||||
id: root
|
||||
|
||||
|
||||
GenericTypeLogView {
|
||||
anchors.fill: parent
|
||||
text: qsTr("This button has been pressed %1 times in the last %2 days.")
|
||||
|
||||
logsModel: LogsModel {
|
||||
logsModel: engine.jsonRpcClient.ensureServerVersion("1.10") ? logsModelNg : logsModel
|
||||
LogsModelNg {
|
||||
id: logsModelNg
|
||||
engine: _engine
|
||||
deviceId: root.device.id
|
||||
live: true
|
||||
typeIds: {
|
||||
var ret = [];
|
||||
ret.push(root.deviceClass.eventTypes.findByName("pressed").id)
|
||||
if (root.deviceClass.eventTypes.findByName("longPressed")) {
|
||||
ret.push(root.deviceClass.eventTypes.findByName("longPressed").id)
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
LogsModel {
|
||||
id: logsModel
|
||||
engine: _engine
|
||||
deviceId: root.device.id
|
||||
live: true
|
||||
|
||||
@ -26,14 +26,28 @@ DevicePageBase {
|
||||
}
|
||||
}
|
||||
|
||||
// ThinDivider {}
|
||||
ThinDivider {}
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: app.margins
|
||||
text: qsTr("Access log:")
|
||||
}
|
||||
|
||||
GenericTypeLogView {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("%1 fingerprints recognized on this device in the last %2 days.")
|
||||
|
||||
logsModel: LogsModel {
|
||||
logsModel: engine.jsonRpcClient.ensureServerVersion("1.10") ? logsModelNg : logsModel
|
||||
LogsModelNg {
|
||||
id: logsModelNg
|
||||
deviceId: root.device.id
|
||||
engine: _engine
|
||||
live: true
|
||||
typeIds: [root.accessGrantedEventType.id, root.accessDeniedEventType.id];
|
||||
}
|
||||
LogsModel {
|
||||
id: logsModel
|
||||
deviceId: root.device.id
|
||||
engine: _engine
|
||||
live: true
|
||||
|
||||
@ -5,14 +5,22 @@ import Nymea 1.0
|
||||
import "../components"
|
||||
import "../customviews"
|
||||
|
||||
GenericDevicePage {
|
||||
DevicePageBase {
|
||||
id: root
|
||||
|
||||
GenericTypeLogView {
|
||||
anchors.fill: parent
|
||||
text: qsTr("This event has appeared %1 times in the last %2 days.")
|
||||
|
||||
logsModel: LogsModel {
|
||||
logsModel: engine.jsonRpcClient.ensureServerVersion("1.10") ? logsModelNg : logsModel
|
||||
LogsModelNg {
|
||||
id: logsModelNg
|
||||
engine: _engine
|
||||
deviceId: root.device.id
|
||||
live: true
|
||||
typeIds: [root.deviceClass.eventTypes.findByName("triggered").id];
|
||||
}
|
||||
LogsModel {
|
||||
id: logsModel
|
||||
engine: _engine
|
||||
deviceId: root.device.id
|
||||
live: true
|
||||
|
||||
@ -71,18 +71,33 @@ DevicePageBase {
|
||||
|
||||
ThinDivider {}
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: app.margins
|
||||
wrapMode: Text.WordWrap
|
||||
text: qsTr("Sent notifications:")
|
||||
}
|
||||
|
||||
|
||||
GenericTypeLogView {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("%1 notifications sent to this device in the last %2 days.")
|
||||
|
||||
logsModel: LogsModel {
|
||||
logsModel: engine.jsonRpcClient.ensureServerVersion("1.10") ? logsModelNg : logsModel
|
||||
LogsModelNg {
|
||||
id: logsModelNg
|
||||
deviceId: root.device.id
|
||||
engine: _engine
|
||||
typeIds: [root.deviceClass.actionTypes.findByName("notify").id];
|
||||
}
|
||||
|
||||
LogsModel {
|
||||
id: logsModel
|
||||
deviceId: root.device.id
|
||||
live: true
|
||||
engine: _engine
|
||||
Component.onCompleted: update()
|
||||
typeIds: [root.deviceClass.actionTypes.findByName("notify").id];
|
||||
|
||||
}
|
||||
|
||||
delegate: MeaListItemDelegate {
|
||||
|
||||
@ -8,24 +8,16 @@ import "../customviews"
|
||||
DevicePageBase {
|
||||
id: root
|
||||
|
||||
ListView {
|
||||
anchors { fill: parent }
|
||||
model: ListModel {
|
||||
Component.onCompleted: {
|
||||
var supportedInterfaces = ["temperaturesensor", "humiditysensor", "pressuresensor", "moisturesensor", "lightsensor", "conductivitysensor"]
|
||||
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]});
|
||||
}
|
||||
}
|
||||
Loader {
|
||||
anchors.fill: parent
|
||||
Component.onCompleted: {
|
||||
var src
|
||||
if (engine.jsonRpcClient.ensureServerVersion("1.10")) {
|
||||
src = "SensorDevicePagePost110.qml"
|
||||
} else {
|
||||
src = "SensorDevicePagePre110.qml"
|
||||
}
|
||||
}
|
||||
delegate: SensorView {
|
||||
width: parent.width
|
||||
interfaceName: modelData
|
||||
device: root.device
|
||||
deviceClass: root.deviceClass
|
||||
setSource(Qt.resolvedUrl(src), {device: root.device, deviceClass: root.deviceClass})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
38
nymea-app/ui/devicepages/SensorDevicePagePost110.qml
Normal file
38
nymea-app/ui/devicepages/SensorDevicePagePost110.qml
Normal file
@ -0,0 +1,38 @@
|
||||
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 }
|
||||
model: ListModel {
|
||||
Component.onCompleted: {
|
||||
var supportedInterfaces = ["temperaturesensor", "humiditysensor", "pressuresensor", "moisturesensor", "lightsensor", "conductivitysensor"]
|
||||
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: GenericTypeGraph {
|
||||
width: parent.width
|
||||
device: root.device
|
||||
stateType: root.deviceClass.stateTypes.findByName(stateTypeName)
|
||||
color: app.interfaceToColor(modelData)
|
||||
iconSource: app.interfaceToIcon(modelData)
|
||||
|
||||
implicitHeight: width * .6
|
||||
property string stateTypeName: {
|
||||
switch (modelData) {
|
||||
case "lightsensor":
|
||||
return "lightIntensity";
|
||||
default:
|
||||
return modelData.replace("sensor", "");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
31
nymea-app/ui/devicepages/SensorDevicePagePre110.qml
Normal file
31
nymea-app/ui/devicepages/SensorDevicePagePre110.qml
Normal file
@ -0,0 +1,31 @@
|
||||
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"]
|
||||
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
|
||||
}
|
||||
}
|
||||
45
nymea-app/ui/devicepages/SmartMeterDevicePage.qml
Normal file
45
nymea-app/ui/devicepages/SmartMeterDevicePage.qml
Normal file
@ -0,0 +1,45 @@
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 2.1
|
||||
import QtQuick.Layouts 1.1
|
||||
import Nymea 1.0
|
||||
import "../components"
|
||||
import "../customviews"
|
||||
|
||||
DevicePageBase {
|
||||
id: root
|
||||
|
||||
ListView {
|
||||
anchors { fill: parent }
|
||||
model: ListModel {
|
||||
Component.onCompleted: {
|
||||
if (root.deviceClass.interfaces.indexOf("extendedsmartmeterproducer") >= 0
|
||||
|| root.deviceClass.interfaces.indexOf("extendedsmartmeterconsumer") >= 0) {
|
||||
append( {interface: "extendedsmartmeterproducer", stateTypeName: "currentPower" })
|
||||
}
|
||||
if (root.deviceClass.interfaces.indexOf("smartmeterproducer") >= 0) {
|
||||
append( {interface: "smartmeterproducer", stateTypeName: "totalEnergyProduced" })
|
||||
}
|
||||
if (root.deviceClass.interfaces.indexOf("smartmeterconsumer") >= 0) {
|
||||
append( {interface: "smartmeterconsumer", stateTypeName: "totalEnergyConsumed" })
|
||||
}
|
||||
print("shown graphs are", count)
|
||||
}
|
||||
}
|
||||
delegate: ColumnLayout {
|
||||
width: parent.width
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: app.margins; Layout.topMargin: app.margins; Layout.rightMargin: app.rightMargins;
|
||||
text: root.deviceClass.stateTypes.findByName(model.stateTypeName).displayName
|
||||
}
|
||||
GenericTypeGraph {
|
||||
Layout.fillWidth: true
|
||||
device: root.device
|
||||
stateType: root.deviceClass.stateTypes.findByName(model.stateTypeName)
|
||||
color: app.interfaceToColor(model.interface)
|
||||
iconSource: app.interfaceToIcon(model.interface)
|
||||
roundTo: 5
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 2.1
|
||||
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"
|
||||
@ -16,13 +17,15 @@ Page {
|
||||
case "Int":
|
||||
case "Double":
|
||||
return true;
|
||||
case "Bool":
|
||||
return engine.jsonRpcClient.ensureServerVersion("1.10")
|
||||
}
|
||||
print("not showing graph for", root.stateType.type)
|
||||
return false;
|
||||
}
|
||||
|
||||
header: GuhHeader {
|
||||
text: qsTr("History")
|
||||
text: qsTr("History for %1").arg(root.stateType.displayName)
|
||||
onBackPressed: pageStack.pop()
|
||||
}
|
||||
|
||||
@ -37,17 +40,13 @@ Page {
|
||||
typeIds: [root.stateType.id]
|
||||
}
|
||||
|
||||
// LogsModelNg {
|
||||
// id: logsModelNg
|
||||
// deviceId: root.device.id
|
||||
// typeId: root.stateType.id
|
||||
// startTime: {
|
||||
// var date = new Date();
|
||||
// date.setHours(new Date().getHours() - 24)
|
||||
// return date;
|
||||
// }
|
||||
// endTime: new Date();
|
||||
// }
|
||||
LogsModelNg {
|
||||
id: logsModelNg
|
||||
engine: _engine
|
||||
deviceId: root.device.id
|
||||
typeIds: [root.stateType.id]
|
||||
live: true
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
@ -75,9 +74,8 @@ Page {
|
||||
id: logView
|
||||
width: swipeView.width
|
||||
height: swipeView.height
|
||||
text: qsTr("%1, %2 has changed %3 times in the last %4 days").arg(device.name).arg(stateType.displayName)
|
||||
|
||||
logsModel: logsModel
|
||||
logsModel: engine.jsonRpcClient.ensureServerVersion("1.10") ? logsModelNg : logsModel
|
||||
|
||||
onAddRuleClicked: {
|
||||
var rule = engine.ruleManager.createNewRule();
|
||||
@ -94,80 +92,20 @@ Page {
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Loader {
|
||||
id: graphLoader
|
||||
width: swipeView.width
|
||||
height: swipeView.height
|
||||
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
|
||||
Component.onCompleted: {
|
||||
var source;
|
||||
if (engine.jsonRpcClient.ensureServerVersion("1.10")) {
|
||||
source = Qt.resolvedUrl("../customviews/GenericTypeGraph.qml");
|
||||
} else {
|
||||
source = Qt.resolvedUrl("../customviews/GenericTypeGraphPre110.qml");
|
||||
}
|
||||
setSource(source, {device: root.device, stateType: root.stateType})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,36 +8,16 @@ import "../customviews"
|
||||
DevicePageBase {
|
||||
id: root
|
||||
|
||||
Flickable {
|
||||
Loader {
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
contentHeight: content.implicitHeight
|
||||
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"
|
||||
Component.onCompleted: {
|
||||
var src
|
||||
if (engine.jsonRpcClient.ensureServerVersion("1.10")) {
|
||||
src = "WeatherDevicePagePost110.qml"
|
||||
} else {
|
||||
src = "WeatherDevicePagePre110.qml"
|
||||
}
|
||||
setSource(Qt.resolvedUrl(src), {device: root.device, deviceClass: root.deviceClass})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
43
nymea-app/ui/devicepages/WeatherDevicePagePost110.qml
Normal file
43
nymea-app/ui/devicepages/WeatherDevicePagePost110.qml
Normal file
@ -0,0 +1,43 @@
|
||||
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
|
||||
}
|
||||
SensorChart {
|
||||
Layout.fillWidth: true
|
||||
device: root.device
|
||||
deviceClass: root.deviceClass
|
||||
interfaceName: "temperaturesensor"
|
||||
}
|
||||
SensorChart {
|
||||
Layout.fillWidth: true
|
||||
device: root.device
|
||||
deviceClass: root.deviceClass
|
||||
interfaceName: "humiditysensor"
|
||||
}
|
||||
SensorChart {
|
||||
Layout.fillWidth: true
|
||||
device: root.device
|
||||
deviceClass: root.deviceClass
|
||||
interfaceName: "pressuresensor"
|
||||
}
|
||||
}
|
||||
}
|
||||
44
nymea-app/ui/devicepages/WeatherDevicePagePre110.qml
Normal file
44
nymea-app/ui/devicepages/WeatherDevicePagePre110.qml
Normal file
@ -0,0 +1,44 @@
|
||||
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"
|
||||
}
|
||||
}
|
||||
}
|
||||
6537
nymea-app/ui/images/ev-charger.svg
Normal file
6537
nymea-app/ui/images/ev-charger.svg
Normal file
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 184 KiB |
71
nymea-app/ui/images/radiator.svg
Normal file
71
nymea-app/ui/images/radiator.svg
Normal file
@ -0,0 +1,71 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
version="1.1"
|
||||
width="96"
|
||||
viewBox="0 0 96 96.000001"
|
||||
height="96"
|
||||
id="svg4874">
|
||||
<defs
|
||||
id="defs875" />
|
||||
<metadata
|
||||
id="metadata4879">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<path
|
||||
id="rect4199"
|
||||
style="color:#000000;fill:#808080;stroke-width:2.82842588"
|
||||
d="m 45.99995,81.99995 h 4 V 18 h -4 z" />
|
||||
<rect
|
||||
id="rect4782"
|
||||
style="color:#000000;fill:none"
|
||||
transform="rotate(90)"
|
||||
height="96"
|
||||
width="96"
|
||||
y="-96"
|
||||
x="-2.7465819e-06" />
|
||||
<path
|
||||
id="path4179"
|
||||
style="color:#000000;font-variant-ligatures:none;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;text-transform:none;white-space:normal;shape-padding:0;isolation:auto;mix-blend-mode:normal;solid-color:#000000;fill:#808080;color-rendering:auto;image-rendering:auto;shape-rendering:auto"
|
||||
d="m 29.976,12.001 c -5.0328,0.05818 -8.7136,-0.12028 -11.725,1.541 -1.5055,0.83064 -2.6968,2.2356 -3.3555,3.9902 -0.65866,1.7547 -0.89648,3.8383 -0.89648,6.4688 v 52 c 0,2.6304 0.23782,4.7121 0.89648,6.4668 0.65866,1.7546 1.85,3.1596 3.3555,3.9902 3.011,1.6613 6.6918,1.4848 11.725,1.543 h 0.01172 36.023 0.01172 c 5.0328,-0.0582 8.7136,0.11832 11.725,-1.543 1.5055,-0.83064 2.6968,-2.2356 3.3555,-3.9902 0.65866,-1.7547 0.89648,-3.8364 0.89648,-6.4668 v -52 c 0,-2.6304 -0.23782,-4.7141 -0.89648,-6.4688 -0.66,-1.759 -1.851,-3.163 -3.356,-3.994 -3.011,-1.661 -6.692,-1.483 -11.725,-1.541 h -0.01172 -36.023 -0.01172 z m 0.02344,4 h 36 c 5.0383,0.05877 8.3519,0.23688 9.8164,1.0449 0.73364,0.40478 1.1527,0.85295 1.543,1.8926 0.39025,1.0396 0.64062,2.6929 0.64062,5.0625 v 52 c 0,2.3696 -0.25037,4.0229 -0.64062,5.0625 -0.39025,1.0396 -0.80933,1.4878 -1.543,1.8926 -1.4645,0.80804 -4.7782,0.98616 -9.8164,1.0449 H 30.02244 29.999 c -5.0383,-0.0588 -8.3519,-0.23688 -9.8164,-1.0449 -0.73364,-0.40478 -1.1508,-0.85296 -1.541,-1.8926 -0.39025,-1.0396 -0.64258,-2.6929 -0.64258,-5.0625 v -52 c 0,-2.3696 0.25232,-4.0229 0.64258,-5.0625 0.39025,-1.0396 0.80738,-1.4878 1.541,-1.8926 1.4645,-0.80804 4.7782,-0.98616 9.8164,-1.0449 z" />
|
||||
<path
|
||||
d="m 29.99995,81.99995 h 4 V 18 h -4 z"
|
||||
style="color:#000000;fill:#808080;stroke-width:2.82842588"
|
||||
id="path2070" />
|
||||
<path
|
||||
id="path2072"
|
||||
style="color:#000000;fill:#808080;stroke-width:2.82842588"
|
||||
d="m 61.99995,81.99995 h 4 V 18 h -4 z" />
|
||||
<path
|
||||
id="path2074"
|
||||
style="color:#000000;fill:#808080;stroke-width:1.00000155"
|
||||
d="m 16,31.999975 v -4 H 7.999975 v 4 z" />
|
||||
<path
|
||||
d="m 87.999925,31.999975 v -4 H 80 v 4 z"
|
||||
style="color:#000000;fill:#808080;stroke-width:0.99999529"
|
||||
id="path2076" />
|
||||
<path
|
||||
id="path2078"
|
||||
style="color:#000000;fill:#808080;stroke-width:1.06065571"
|
||||
d="m 87.999925,71.999975 v -4 H 79 v 4 z" />
|
||||
<path
|
||||
d="m 16,71.999975 v -4 H 7.999975 v 4 z"
|
||||
style="color:#000000;fill:#808080;stroke-width:1.00000155"
|
||||
id="path2080" />
|
||||
<path
|
||||
id="path2082"
|
||||
style="color:#000000;fill:#808080;stroke-width:0.99999529"
|
||||
d="m 87.999963,26.000013 h -4 v 7.999925 h 4 z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.7 KiB |
202
nymea-app/ui/images/smartmeter.svg
Normal file
202
nymea-app/ui/images/smartmeter.svg
Normal file
@ -0,0 +1,202 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 96 96.000001"
|
||||
version="1.1"
|
||||
id="svg4874"
|
||||
height="96"
|
||||
width="96">
|
||||
<defs
|
||||
id="defs4876" />
|
||||
<metadata
|
||||
id="metadata4879">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
transform="translate(67.857146,-78.50504)"
|
||||
id="layer1">
|
||||
<g
|
||||
style="display:inline"
|
||||
id="g4845"
|
||||
transform="matrix(0,-1,-1,0,373.50506,516.50504)">
|
||||
<g
|
||||
id="g4778"
|
||||
transform="matrix(-0.9996045,0,0,1,575.94296,-611.00001)">
|
||||
<g
|
||||
style="display:inline"
|
||||
id="g4780"
|
||||
transform="matrix(-1,0,0,1,575.99999,611)">
|
||||
<path
|
||||
transform="matrix(0.00413041,-0.62875985,0.62925749,0.0041304,140.84654,636.94341)"
|
||||
d="m 391.66992,387.37109 c -1.95315,3.8e-4 -3.90597,0.74333 -5.39648,2.23243 l -0.002,0.002 c -2.98084,2.9782 -2.98179,7.80618 -0.002,10.7832 2.701,2.69845 34.10981,26.89007 39.70508,28.92188 0.001,-0.001 0.002,-0.002 0.004,-0.002 0.002,-3.2e-4 0.006,0 0.008,0 0.003,2.3e-4 0.004,4.4e-4 0.006,0 0.002,0 0.005,-0.001 0.006,-0.002 -1.9443,-5.61196 -26.23068,-37.00669 -28.93164,-39.70508 -1.48993,-1.4886 -3.44333,-2.23084 -5.39649,-2.23047 z"
|
||||
id="path4197"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.77025032;marker:none;enable-background:accumulate" />
|
||||
<rect
|
||||
transform="scale(-1,1)"
|
||||
y="345.36221"
|
||||
x="-438.00244"
|
||||
height="96"
|
||||
width="96.037987"
|
||||
id="rect4782"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:none;stroke-width:4;marker:none;enable-background:accumulate" />
|
||||
<ellipse
|
||||
ry="40.015827"
|
||||
rx="40"
|
||||
transform="matrix(0,-1,-1,0,0,0)"
|
||||
cy="-389.98346"
|
||||
cx="-393.36221"
|
||||
id="path833"
|
||||
style="fill:none;fill-opacity:1;stroke:#808080;stroke-width:4.00079123;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<rect
|
||||
width="4"
|
||||
transform="matrix(0,-1,-1,0,0,0)"
|
||||
y="-425.51035"
|
||||
x="-395.36221"
|
||||
height="8.0031652"
|
||||
id="rect835"
|
||||
style="fill:#808080;fill-opacity:1;stroke:none;stroke-width:3.78027534;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<rect
|
||||
style="fill:#808080;fill-opacity:1;stroke:none;stroke-width:3.78027534;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect845"
|
||||
height="8.0023737"
|
||||
x="-537.62946"
|
||||
y="-176.48611"
|
||||
transform="matrix(-0.50014835,-0.86593974,-0.86611103,0.49985167,0,0)"
|
||||
width="4.0003958" />
|
||||
<rect
|
||||
width="4.0003958"
|
||||
transform="matrix(0.50014835,-0.86593974,-0.86611103,-0.49985167,0,0)"
|
||||
y="-569.96503"
|
||||
x="-147.76164"
|
||||
height="8.0023737"
|
||||
id="rect851"
|
||||
style="fill:#808080;fill-opacity:1;stroke:none;stroke-width:3.78027534;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<rect
|
||||
style="fill:#808080;fill-opacity:1;stroke:none;stroke-width:3.78027534;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect857"
|
||||
height="8.0007915"
|
||||
x="138.96216"
|
||||
y="-571.14557"
|
||||
transform="matrix(0.86611103,-0.49985167,-0.50014835,-0.86593974,0,0)"
|
||||
width="4.0011868" />
|
||||
<rect
|
||||
width="4.0015826"
|
||||
transform="scale(1,-1)"
|
||||
y="-428.87503"
|
||||
x="387.98267"
|
||||
height="8"
|
||||
id="rect863"
|
||||
style="fill:#808080;fill-opacity:1;stroke:none;stroke-width:3.78027534;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<rect
|
||||
style="fill:#808080;fill-opacity:1;stroke:none;stroke-width:3.78027534;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect869"
|
||||
height="8.0007915"
|
||||
x="532.44104"
|
||||
y="-181.2778"
|
||||
transform="matrix(0.86611103,0.49985166,0.50014835,-0.86593974,0,0)"
|
||||
width="4.0011873" />
|
||||
<rect
|
||||
width="4.0011868"
|
||||
transform="matrix(-0.86611103,0.49985167,0.50014835,0.86593974,0,0)"
|
||||
y="500.11292"
|
||||
x="-142.96335"
|
||||
height="8.0007915"
|
||||
id="rect875"
|
||||
style="fill:#808080;fill-opacity:1;stroke:none;stroke-width:3.78027534;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<rect
|
||||
style="fill:#808080;fill-opacity:1;stroke:none;stroke-width:3.78027534;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect881"
|
||||
height="8"
|
||||
x="-391.98428"
|
||||
y="357.84937"
|
||||
transform="scale(-1,1)"
|
||||
width="4.0015826" />
|
||||
<rect
|
||||
width="4.0011873"
|
||||
transform="matrix(-0.86611103,-0.49985166,-0.50014835,0.86593974,0,0)"
|
||||
y="110.24509"
|
||||
x="-536.44226"
|
||||
height="8.0007915"
|
||||
id="rect909"
|
||||
style="fill:#808080;fill-opacity:1;stroke:none;stroke-width:3.78027534;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<rect
|
||||
style="fill:#808080;fill-opacity:1;stroke:none;stroke-width:1.65583444;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect921"
|
||||
height="4.0002618"
|
||||
x="-196.96432"
|
||||
y="482.22253"
|
||||
transform="matrix(-0.91360523,0.40660236,0.40687094,0.91348565,0,0)"
|
||||
width="1.5353957" />
|
||||
<rect
|
||||
width="1.5354383"
|
||||
transform="matrix(-0.95109244,0.30890642,0.30912758,0.95102058,0,0)"
|
||||
y="458.86975"
|
||||
x="-250.05089"
|
||||
height="4.0001512"
|
||||
id="rect935"
|
||||
style="fill:#808080;fill-opacity:1;stroke:none;stroke-width:1.65583444;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<rect
|
||||
style="fill:#808080;fill-opacity:1;stroke:none;stroke-width:1.65583444;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect941"
|
||||
height="4.0000682"
|
||||
x="-300.40701"
|
||||
y="430.10257"
|
||||
transform="matrix(-0.97816432,0.20783299,0.2079904,0.97813087,0,0)"
|
||||
width="1.53547" />
|
||||
<rect
|
||||
width="1.5354896"
|
||||
transform="matrix(-0.99452619,0.10448756,0.10456937,0.9945176,0,0)"
|
||||
y="396.23618"
|
||||
x="-347.47949"
|
||||
height="4.0000172"
|
||||
id="rect947"
|
||||
style="fill:#808080;fill-opacity:1;stroke:none;stroke-width:1.65583444;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<rect
|
||||
style="fill:#808080;fill-opacity:1;stroke:none;stroke-width:1.65583444;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect953"
|
||||
height="4.0000172"
|
||||
x="-429.74677"
|
||||
y="314.73932"
|
||||
transform="matrix(-0.99452619,-0.1044876,-0.10456937,0.9945176,0,0)"
|
||||
width="1.5354896" />
|
||||
<rect
|
||||
width="1.53547"
|
||||
transform="matrix(-0.97816432,-0.20783303,-0.2079904,0.97813087,0,0)"
|
||||
y="267.99973"
|
||||
x="-464.03812"
|
||||
height="4.0000682"
|
||||
id="rect959"
|
||||
style="fill:#808080;fill-opacity:1;stroke:none;stroke-width:1.65583444;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<rect
|
||||
style="fill:#808080;fill-opacity:1;stroke:none;stroke-width:1.65583444;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect965"
|
||||
height="4.0001512"
|
||||
x="-493.24908"
|
||||
y="217.93295"
|
||||
transform="matrix(-0.95109242,-0.30890647,-0.30912758,0.95102058,0,0)"
|
||||
width="1.5354382" />
|
||||
<rect
|
||||
style="fill:#808080;fill-opacity:1;stroke:none;stroke-width:1.65583444;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect977"
|
||||
height="4.0002618"
|
||||
x="-517.05963"
|
||||
y="165.08612"
|
||||
transform="matrix(-0.91360522,-0.4066024,-0.40687094,0.91348565,0,0)"
|
||||
width="1.5353957" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 8.9 KiB |
171
nymea-app/ui/images/zoom-in.svg
Normal file
171
nymea-app/ui/images/zoom-in.svg
Normal file
@ -0,0 +1,171 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="96"
|
||||
height="96"
|
||||
id="svg4874"
|
||||
version="1.1"
|
||||
inkscape:version="0.91+devel r"
|
||||
viewBox="0 0 96 96.000001"
|
||||
sodipodi:docname="zoom-in.svg">
|
||||
<defs
|
||||
id="defs4876" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="8.7812489"
|
||||
inkscape:cx="44.008543"
|
||||
inkscape:cy="41.064759"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="g4780"
|
||||
showgrid="true"
|
||||
showborder="true"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="true"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:snap-bbox-edge-midpoints="true"
|
||||
inkscape:snap-bbox-midpoints="true"
|
||||
inkscape:object-paths="true"
|
||||
inkscape:snap-intersection-paths="true"
|
||||
inkscape:object-nodes="true"
|
||||
inkscape:snap-smooth-nodes="true"
|
||||
inkscape:snap-midpoints="true"
|
||||
inkscape:snap-object-midpoints="true"
|
||||
inkscape:snap-center="true"
|
||||
showguides="true"
|
||||
inkscape:guide-bbox="true">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid5451"
|
||||
empspacing="8" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="8,-8.0000001"
|
||||
id="guide4063" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="4,-8.0000001"
|
||||
id="guide4065" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="-8,88.000001"
|
||||
id="guide4067" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="-8,92.000001"
|
||||
id="guide4069" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="104,4"
|
||||
id="guide4071" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="-5,8.0000001"
|
||||
id="guide4073" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="92,-8.0000001"
|
||||
id="guide4075" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="88,-8.0000001"
|
||||
id="guide4077" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="-8,84.000001"
|
||||
id="guide4074" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="12,-8.0000001"
|
||||
id="guide4076" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="-5,12"
|
||||
id="guide4078" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="84,-9.0000001"
|
||||
id="guide4080" />
|
||||
<sodipodi:guide
|
||||
position="48,-8.0000001"
|
||||
orientation="1,0"
|
||||
id="guide4170" />
|
||||
<sodipodi:guide
|
||||
position="-8,48"
|
||||
orientation="0,1"
|
||||
id="guide4172" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata4879">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(67.857146,-78.50504)">
|
||||
<g
|
||||
transform="matrix(0,-1,-1,0,373.50506,516.50504)"
|
||||
id="g4845"
|
||||
style="display:inline">
|
||||
<g
|
||||
inkscape:export-ydpi="90"
|
||||
inkscape:export-xdpi="90"
|
||||
inkscape:export-filename="next01.png"
|
||||
transform="matrix(-0.9996045,0,0,1,575.94296,-611.00001)"
|
||||
id="g4778"
|
||||
inkscape:label="Layer 1">
|
||||
<g
|
||||
transform="matrix(-1,0,0,1,575.99999,611)"
|
||||
id="g4780"
|
||||
style="display:inline">
|
||||
<rect
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:none;stroke-width:4;marker:none;enable-background:accumulate"
|
||||
id="rect4782"
|
||||
width="96.037987"
|
||||
height="96"
|
||||
x="-438.00244"
|
||||
y="345.36221"
|
||||
transform="scale(-1,1)" />
|
||||
<path
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:15px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#808080;fill-opacity:1;stroke:none"
|
||||
d="m 364.0904,368.96573 c -0.0215,-0.0161 -0.0354,-0.0404 -0.0567,-0.0566 -0.0253,-0.0201 -0.057,-0.0305 -0.0821,-0.0508 z"
|
||||
id="path4157" />
|
||||
<path
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:15px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#808080;fill-opacity:1;stroke:none"
|
||||
d="m 364.07673,417.80167 -0.13873,0.10742 c 0.0251,-0.0203 0.0569,-0.0307 0.0821,-0.0508 0.0214,-0.0162 0.0353,-0.0405 0.0567,-0.0566 z"
|
||||
id="path4344" />
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3.99999976;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="M 42 6 C 22.14111 6 6 22.142377 6 42 C 6 61.857623 22.14111 77.996094 42 77.996094 C 50.849741 77.996094 58.956757 74.785633 65.230469 69.474609 L 84.876953 89.119141 L 89.121094 84.878906 L 69.474609 65.232422 C 74.78872 58.958425 78 50.851684 78 42 C 78 22.142377 61.85889 6 42 6 z M 42 9.9980469 C 59.69585 9.9980469 73.998047 24.302852 73.998047 42 C 73.998047 59.697148 59.69585 73.998047 42 73.998047 C 24.30415 73.998047 10 59.697148 10 42 C 10 24.302852 24.30415 9.9980469 42 9.9980469 z M 40 24 L 40 39.998047 L 24 39.998047 L 24 43.998047 L 40 43.998047 L 40 60 L 44.001953 60 L 44.001953 43.998047 L 60 43.998047 L 60 39.998047 L 44.001953 39.998047 L 44.001953 24 L 40 24 z "
|
||||
transform="matrix(0,-1,-1.0003957,0,438.00245,441.36222)"
|
||||
id="path4116" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.2 KiB |
171
nymea-app/ui/images/zoom-out.svg
Normal file
171
nymea-app/ui/images/zoom-out.svg
Normal file
@ -0,0 +1,171 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="96"
|
||||
height="96"
|
||||
id="svg4874"
|
||||
version="1.1"
|
||||
inkscape:version="0.91+devel r"
|
||||
viewBox="0 0 96 96.000001"
|
||||
sodipodi:docname="zoom-out.svg">
|
||||
<defs
|
||||
id="defs4876" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="8.7812489"
|
||||
inkscape:cx="33.298221"
|
||||
inkscape:cy="49.3096"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="g4780"
|
||||
showgrid="true"
|
||||
showborder="true"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="true"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:snap-bbox-edge-midpoints="true"
|
||||
inkscape:snap-bbox-midpoints="true"
|
||||
inkscape:object-paths="true"
|
||||
inkscape:snap-intersection-paths="true"
|
||||
inkscape:object-nodes="true"
|
||||
inkscape:snap-smooth-nodes="true"
|
||||
inkscape:snap-midpoints="true"
|
||||
inkscape:snap-object-midpoints="true"
|
||||
inkscape:snap-center="true"
|
||||
showguides="true"
|
||||
inkscape:guide-bbox="true">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid5451"
|
||||
empspacing="8" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="8,-8.0000001"
|
||||
id="guide4063" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="4,-8.0000001"
|
||||
id="guide4065" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="-8,88.000001"
|
||||
id="guide4067" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="-8,92.000001"
|
||||
id="guide4069" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="104,4"
|
||||
id="guide4071" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="-5,8.0000001"
|
||||
id="guide4073" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="92,-8.0000001"
|
||||
id="guide4075" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="88,-8.0000001"
|
||||
id="guide4077" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="-8,84.000001"
|
||||
id="guide4074" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="12,-8.0000001"
|
||||
id="guide4076" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="-5,12"
|
||||
id="guide4078" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="84,-9.0000001"
|
||||
id="guide4080" />
|
||||
<sodipodi:guide
|
||||
position="48,-8.0000001"
|
||||
orientation="1,0"
|
||||
id="guide4170" />
|
||||
<sodipodi:guide
|
||||
position="-8,48"
|
||||
orientation="0,1"
|
||||
id="guide4172" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata4879">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(67.857146,-78.50504)">
|
||||
<g
|
||||
transform="matrix(0,-1,-1,0,373.50506,516.50504)"
|
||||
id="g4845"
|
||||
style="display:inline">
|
||||
<g
|
||||
inkscape:export-ydpi="90"
|
||||
inkscape:export-xdpi="90"
|
||||
inkscape:export-filename="next01.png"
|
||||
transform="matrix(-0.9996045,0,0,1,575.94296,-611.00001)"
|
||||
id="g4778"
|
||||
inkscape:label="Layer 1">
|
||||
<g
|
||||
transform="matrix(-1,0,0,1,575.99999,611)"
|
||||
id="g4780"
|
||||
style="display:inline">
|
||||
<rect
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:none;stroke-width:4;marker:none;enable-background:accumulate"
|
||||
id="rect4782"
|
||||
width="96.037987"
|
||||
height="96"
|
||||
x="-438.00244"
|
||||
y="345.36221"
|
||||
transform="scale(-1,1)" />
|
||||
<path
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:15px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#808080;fill-opacity:1;stroke:none"
|
||||
d="m 364.0904,368.96573 c -0.0215,-0.0161 -0.0354,-0.0404 -0.0567,-0.0566 -0.0253,-0.0201 -0.057,-0.0305 -0.0821,-0.0508 z"
|
||||
id="path4157" />
|
||||
<path
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:15px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#808080;fill-opacity:1;stroke:none"
|
||||
d="m 364.07673,417.80167 -0.13873,0.10742 c 0.0251,-0.0203 0.0569,-0.0307 0.0821,-0.0508 0.0214,-0.0162 0.0353,-0.0405 0.0567,-0.0566 z"
|
||||
id="path4344" />
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3.99999976;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="M 42 6 C 22.14111 6 6 22.142377 6 42 C 6 61.857623 22.14111 77.996094 42 77.996094 C 50.849741 77.996094 58.956757 74.785633 65.230469 69.474609 L 84.876953 89.119141 L 89.121094 84.878906 L 69.474609 65.232422 C 74.78872 58.958425 78 50.851684 78 42 C 78 22.142377 61.85889 6 42 6 z M 42 9.9980469 C 59.69585 9.9980469 73.998047 24.302852 73.998047 42 C 73.998047 59.697148 59.69585 73.998047 42 73.998047 C 24.30415 73.998047 10 59.697148 10 42 C 10 24.302852 24.30415 9.9980469 42 9.9980469 z M 24 39.998047 L 24 43.998047 L 60 43.998047 L 60 39.998047 L 24 39.998047 z "
|
||||
transform="matrix(0,-1,-1.0003957,0,438.00245,441.36222)"
|
||||
id="path4116" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.1 KiB |
@ -25,6 +25,9 @@ MainPageTile {
|
||||
case "light":
|
||||
page = "LightsDeviceListPage.qml"
|
||||
break;
|
||||
case "smartmeter":
|
||||
page ="SmartMeterDeviceListPage.qml";
|
||||
break;
|
||||
default:
|
||||
page = "GenericDeviceListPage.qml"
|
||||
}
|
||||
@ -65,6 +68,10 @@ MainPageTile {
|
||||
switch (model.name) {
|
||||
case "sensor":
|
||||
case "weather":
|
||||
case "smartmeterconsumer":
|
||||
case "smartmeterproducer":
|
||||
case "extendedsmartmeterconsumer":
|
||||
case "extendedsmartmeterproducer":
|
||||
return labelComponent;
|
||||
|
||||
case "light":
|
||||
|
||||
@ -33,7 +33,6 @@ Page {
|
||||
}
|
||||
endTime: new Date()
|
||||
live: true
|
||||
Component.onCompleted: update()
|
||||
onCountChanged: {
|
||||
if (root.autoScroll) {
|
||||
listView.positionViewAtEnd()
|
||||
@ -41,6 +40,12 @@ Page {
|
||||
}
|
||||
}
|
||||
|
||||
LogsModelNg {
|
||||
id: logsModelNg
|
||||
engine: _engine
|
||||
live: true
|
||||
}
|
||||
|
||||
BusyIndicator {
|
||||
anchors.centerIn: listView
|
||||
visible: logsModel.busy
|
||||
@ -48,11 +53,13 @@ Page {
|
||||
|
||||
ListView {
|
||||
id: listView
|
||||
model: logsModel
|
||||
model: engine.jsonRpcClient.ensureServerVersion("1.10") ? logsModelNg : logsModel
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
headerPositioning: ListView.OverlayHeader
|
||||
|
||||
Component.onCompleted: model.update()
|
||||
|
||||
onDraggingChanged: {
|
||||
if (dragging) {
|
||||
root.autoScroll = false;
|
||||
@ -62,8 +69,10 @@ Page {
|
||||
ScrollBar.vertical: ScrollBar {}
|
||||
|
||||
onContentYChanged: {
|
||||
if (!logsModel.busy && contentY - originY < 5 * height) {
|
||||
logsModel.fetchEarlier(1)
|
||||
if (!engine.jsonRpcClient.ensureServerVersion("1.10")) {
|
||||
if (!logsModel.busy && contentY - originY < 5 * height) {
|
||||
logsModel.fetchEarlier(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -49,6 +49,7 @@ parts:
|
||||
- qtbase5-dev
|
||||
- libavahi-client-dev
|
||||
- libavahi-common-dev
|
||||
- libqt5charts5-dev
|
||||
- libqt5svg5-dev
|
||||
- libqt5websockets5-dev
|
||||
- qtconnectivity5-dev
|
||||
@ -60,6 +61,7 @@ parts:
|
||||
- libqt5websockets5
|
||||
- libqt5bluetooth5
|
||||
- libgl1-mesa-dri
|
||||
- qml-module-qtcharts
|
||||
- qml-module-qtquick2
|
||||
- qml-module-qtquick-controls2
|
||||
- qml-module-qtquick-layouts
|
||||
|
||||
Reference in New Issue
Block a user