Merge PR #1010: More work on the new logs

This commit is contained in:
jenkins 2023-04-24 11:39:34 +02:00
commit f687f97e5a
22 changed files with 591 additions and 375 deletions

View File

@ -58,7 +58,7 @@ void NewLogsModel::componentComplete()
bool NewLogsModel::canFetchMore(const QModelIndex &parent) const bool NewLogsModel::canFetchMore(const QModelIndex &parent) const
{ {
Q_UNUSED(parent) Q_UNUSED(parent)
return m_canFetchMore; return m_canFetchMore && m_sources.count() == 1;
} }
void NewLogsModel::fetchMore(const QModelIndex &parent) void NewLogsModel::fetchMore(const QModelIndex &parent)
@ -185,6 +185,19 @@ void NewLogsModel::setSampleRate(SampleRate sampleRate)
} }
} }
Qt::SortOrder NewLogsModel::sortOrder() const
{
return m_sortOrder;
}
void NewLogsModel::setSortOrder(Qt::SortOrder sortOrder)
{
if (m_sortOrder != sortOrder) {
m_sortOrder = sortOrder;
emit sortOrderChanged();
}
}
bool NewLogsModel::busy() const bool NewLogsModel::busy() const
{ {
return m_busy; return m_busy;
@ -217,30 +230,30 @@ NewLogEntry *NewLogsModel::find(const QDateTime &timestamp) const
qint64 diff = timestamp.msecsTo(entry->timestamp()); qint64 diff = timestamp.msecsTo(entry->timestamp());
if (entry->timestamp() > timestamp) { if (entry->timestamp() > timestamp) {
// qCDebug(dcLogEngine()) << "entry is newer than searched:" << entry->timestamp().toString() << timestamp.toString(); // qCDebug(dcLogEngine()) << "entry is newer than searched:" << entry->timestamp().toString() << timestamp.toString();
if (idx == m_list.count() - 1) { if (idx == 0) {
// qCDebug(dcLogEngine()) << "Is oldest."; // qCDebug(dcLogEngine()) << "Is oldest.";
return entry; return entry;
} }
NewLogEntry *previousEntry = m_list.at(idx+1); NewLogEntry *previousEntry = m_list.at(idx-1);
if (previousEntry->timestamp() < timestamp) { if (previousEntry->timestamp() < timestamp) {
qint64 previousDiff = timestamp.msecsTo(previousEntry->timestamp()); qint64 previousDiff = timestamp.msecsTo(previousEntry->timestamp());
// qCDebug(dcLogEngine()) << "time between this and previous:" << entry->timestamp().toString() << previousEntry->timestamp().toString() << (qAbs(previousDiff) < qAbs(diff) ? "next" : "this"); // qCDebug(dcLogEngine()) << "time between this and previous:" << entry->timestamp().toString() << previousEntry->timestamp().toString() << (qAbs(previousDiff) < qAbs(diff) ? "next" : "this");
return qAbs(previousDiff) < qAbs(diff) ? previousEntry : entry; return qAbs(previousDiff) < qAbs(diff) ? previousEntry : entry;
} }
idx += jump; idx -= jump;
} else if (entry->timestamp() < timestamp) { } else if (entry->timestamp() < timestamp) {
// qCDebug(dcLogEngine()) << "entry is older than searched:" << entry->timestamp().toString() << timestamp.toString(); // qCDebug(dcLogEngine()) << "entry is older than searched:" << entry->timestamp().toString() << timestamp.toString();
if (idx == 0) { if (idx == m_list.count() - 1) {
// qCDebug(dcLogEngine()) << "Is newest."; // qCDebug(dcLogEngine()) << "Is newest.";
return entry; return entry;
} }
NewLogEntry *nextEntry = m_list.at(idx-1); NewLogEntry *nextEntry = m_list.at(idx+1);
if (nextEntry->timestamp() > timestamp) { if (nextEntry->timestamp() > timestamp) {
qint64 nextDiff = timestamp.msecsTo(nextEntry->timestamp()); qint64 nextDiff = timestamp.msecsTo(nextEntry->timestamp());
// qCDebug(dcLogEngine()) << "time between next and this:" << nextEntry->timestamp().toString() << "-" << entry->timestamp().toString() << (qAbs(nextDiff) > qAbs(diff) ? "prev" : "this"); // qCDebug(dcLogEngine()) << "time between next and this:" << nextEntry->timestamp().toString() << "-" << entry->timestamp().toString() << (qAbs(nextDiff) > qAbs(diff) ? "prev" : "this");
return qAbs(nextDiff) < qAbs(diff) ? nextEntry : entry; return qAbs(nextDiff) < qAbs(diff) ? nextEntry : entry;
} }
idx -= jump; idx += jump;
} }
jump = qMax(1, jump / 2); jump = qMax(1, jump / 2);
}; };
@ -269,57 +282,52 @@ void NewLogsModel::fetchLogs()
{"filter", m_filter} {"filter", m_filter}
}; };
if (!m_startTime.isNull() && !m_endTime.isNull()) {
QDateTime startTime;
QDateTime endTime;
QDateTime oldestExisting = m_list.count() > 0 ? m_list.last()->timestamp() : QDateTime(); if (m_sampleRate == SampleRateAny) { // Discrete logs
QDateTime newestExisting = m_list.count() > 0 ? m_list.first()->timestamp() : QDateTime();
qCDebug(dcLogEngine()) << "request timeframe: " << m_startTime.toString() << " - " << m_endTime.toString(); if (!m_startTime.isNull() && !m_endTime.isNull()) { // Either specific time frame
qCDebug(dcLogEngine()) << "existing timeframe:" << oldestExisting.toString() << "- " << newestExisting.toString(); params.insert("startTime", m_startTime.toMSecsSinceEpoch());
params.insert("endTime", m_endTime.toMSecsSinceEpoch());
if (oldestExisting.isNull() || newestExisting.isNull()) {
startTime = m_startTime;
endTime = qMin(QDateTime::currentDateTime(), m_endTime);
} else { } else {
params.insert("limit", m_blockSize);
if (m_startTime < oldestExisting) { if (m_list.count() > 0) {
startTime = m_startTime; params.insert("offset", m_lastOffset);
endTime = qMin(QDateTime::currentDateTime(), qMin(m_endTime, oldestExisting)); if (m_lastOffset == 0) {
} else if (newestExisting < m_endTime) { if (m_endTime.isNull()) {
startTime = qMax(m_startTime, newestExisting); m_currentNewest = QDateTime::currentDateTime();
endTime = qMin(QDateTime::currentDateTime(), m_endTime); } else {
} else { m_currentNewest = m_endTime;
// Nothing to do... }
return; }
params.insert("endTime", m_currentNewest.toMSecsSinceEpoch());
m_lastOffset += m_blockSize;
} }
} }
qCDebug(dcLogEngine()) << "Actual request:" << startTime.toString() << " - " << endTime.toString();
params.insert("startTime", startTime.toMSecsSinceEpoch());
params.insert("endTime", endTime.toMSecsSinceEpoch());
QMetaEnum sampleRateEnum = QMetaEnum::fromType<SampleRate>();
params.insert("sampleRate", sampleRateEnum.valueToKey(m_sampleRate));
} else { } else {
params.insert("limit", m_blockSize); if (!m_startTime.isNull() && !m_endTime.isNull()) {
if (m_list.count() > 0) { params.insert("startTime", m_startTime.toMSecsSinceEpoch());
params.insert("offset", m_list.count() - 1); // -1 because we'll fetch the last existing one again as the receiving logic checks if timestamps line up for proper insertion. It will be removed again there params.insert("endTime", m_endTime.toMSecsSinceEpoch());
params.insert("endTime", m_list.first()->timestamp().toMSecsSinceEpoch());
QMetaEnum sampleRateEnum = QMetaEnum::fromType<SampleRate>();
params.insert("sampleRate", sampleRateEnum.valueToKey(m_sampleRate));
} else {
qCWarning(dcLogEngine()) << "startTime and endTime is required when asking for resampling";
return;
} }
} }
// if (!m_startTime.isNull()) { QMetaEnum sortOrderEnum = QMetaEnum::fromType<Qt::SortOrder>();
// params.insert("startTime", m_startTime.toMSecsSinceEpoch()); params.insert("sortOrder", sortOrderEnum.valueToKey(m_sortOrder));
// }
// if (!m_endTime.isNull()) {
// params.insert("endTime", m_endTime.toMSecsSinceEpoch());
// }
qCDebug(dcLogEngine()) << "Fetching logs:" << QJsonDocument::fromVariant(params).toJson(); qCDebug(dcLogEngine()) << "Fetching logs:" << QJsonDocument::fromVariant(params).toJson();
m_engine->jsonRpcClient()->sendCommand("Logging.GetLogEntries", params, this, "logsReply"); m_engine->jsonRpcClient()->sendCommand("Logging.GetLogEntries", params, this, "logsReply");
} }
void NewLogsModel::logsReply(int commandId, const QVariantMap &data) void NewLogsModel::logsReply(int commandId, const QVariantMap &data)
{ {
Q_UNUSED(commandId)
QList<NewLogEntry*> entries; QList<NewLogEntry*> entries;
foreach (const QVariant &entryVariant, data.value("logEntries").toList()) { foreach (const QVariant &entryVariant, data.value("logEntries").toList()) {
@ -329,77 +337,37 @@ void NewLogsModel::logsReply(int commandId, const QVariantMap &data)
QVariantMap values = map.value("values").toMap(); QVariantMap values = map.value("values").toMap();
NewLogEntry *entry = new NewLogEntry(source, timestamp, values, this); NewLogEntry *entry = new NewLogEntry(source, timestamp, values, this);
entries.append(entry); entries.append(entry);
} }
m_canFetchMore = entries.count() >= m_blockSize; m_canFetchMore = entries.count() >= m_blockSize;
qCDebug(dcLogEngine()) << "Logs received:" << entries.count() << "Requested:" << m_blockSize; qCDebug(dcLogEngine()) << "Logs received:" << entries.count();
if (!entries.isEmpty()) { if (entries.empty()) {
qCDebug(dcLogEngine()) << "Logs received:" << entries.first()->timestamp().toString() << " - " << entries.last()->timestamp().toString(); return;
if (m_list.isEmpty()) { }
qCDebug(dcLogEngine()) << "Inserting into emptry model";
beginInsertRows(QModelIndex(), 0, entries.count() - 1);
m_list.append(entries);
endInsertRows();
emit entriesAdded(0, entries);
} else if (entries.last()->timestamp() == m_list.last()->timestamp()) { if (!m_startTime.isNull() && !m_endTime.isNull()) {
qCDebug(dcLogEngine()) << "First item of new list already existing... no new data..."; beginResetModel();
qDeleteAll(entries); QList<NewLogEntry*> oldEntries = m_list;
m_list.clear();
endResetModel();
emit entriesRemoved(0, oldEntries.count());
qDeleteAll(oldEntries);
} else if (entries.last()->timestamp() < m_list.last()->timestamp()) { beginInsertRows(QModelIndex(), 0, entries.count() - 1);
if (entries.first()->timestamp() == m_list.last()->timestamp()) { m_list = entries;
qCDebug(dcLogEngine()) << "Appending received items"; endInsertRows();
beginRemoveRows(QModelIndex(), m_list.count() - 1, m_list.count() - 1); emit entriesAdded(0, entries);
m_list.takeLast()->deleteLater();
endRemoveRows();
emit entriesRemoved(m_list.count(), 1);
int insertIdx = m_list.count(); } else {
beginInsertRows(QModelIndex(), insertIdx, insertIdx + entries.count() - 1); beginInsertRows(QModelIndex(), m_list.count(), m_list.count() + entries.count() - 1);
m_list = m_list + entries; qSort(entries.begin(), entries.end(), [](NewLogEntry *left, NewLogEntry *right){
endInsertRows(); return left->timestamp() > right->timestamp();
emit entriesAdded(insertIdx, entries); });
} else { m_list.append(entries);
// Start of fetched entries does not line up with end of existing entries. Discarding existing entries endInsertRows();
qCDebug(dcLogEngine()) << "Start of fetched entries does not line up with end of existing entries. Discarding existing entries" << entries.first()->timestamp().toString() << " - " << m_list.last()->timestamp().toString(); emit entriesAdded(m_list.count(), entries);
clear();
// If the mismatch is in the visible area, we'll discard everything and fetch again
// Else if the mismatch is outside the visible area, we'll just discard the old data and work with what we received
if ((entries.first()->timestamp() >= m_endTime && entries.last()->timestamp() >= m_endTime)
|| (entries.first()->timestamp() <= m_startTime && entries.last()->timestamp() <= m_endTime)) {
clear();
beginInsertRows(QModelIndex(), 0, entries.count() - 1);
m_list.append(entries);
endInsertRows();
emit entriesAdded(0, entries);
} else {
clear();
fetchLogs();
}
}
} else if (entries.last()->timestamp() == m_list.first()->timestamp()) {
beginRemoveRows(QModelIndex(), 0, 0);
m_list.takeAt(0)->deleteLater();
endRemoveRows();
emit entriesRemoved(0, 1);
qCDebug(dcLogEngine()) << "Prepending received items";
beginInsertRows(QModelIndex(), 0, entries.count() - 1);
m_list = entries + m_list;
endInsertRows();
emit entriesAdded(0, entries);
} else {
// End of fetched entries does not line up with start of existing entries. Discarding existing entries
qCDebug(dcLogEngine()) << "End of fetched entries does not line up with start of existing entries" << m_list.last()->timestamp().toString() << " - " << m_list.first()->timestamp().toString();
clear();
beginInsertRows(QModelIndex(), 0, entries.count() - 1);
m_list.append(entries);
endInsertRows();
emit entriesAdded(0, entries);
}
} }
emit countChanged(); emit countChanged();

View File

@ -19,6 +19,7 @@ class NewLogsModel : public QAbstractListModel, public QQmlParserStatus
Q_PROPERTY(QDateTime startTime READ startTime WRITE setStartTime NOTIFY startTimeChanged) Q_PROPERTY(QDateTime startTime READ startTime WRITE setStartTime NOTIFY startTimeChanged)
Q_PROPERTY(QDateTime endTime READ endTime WRITE setEndTime NOTIFY endTimeChanged) Q_PROPERTY(QDateTime endTime READ endTime WRITE setEndTime NOTIFY endTimeChanged)
Q_PROPERTY(SampleRate sampleRate READ sampleRate WRITE setSampleRate NOTIFY sampleRateChanged) Q_PROPERTY(SampleRate sampleRate READ sampleRate WRITE setSampleRate NOTIFY sampleRateChanged)
Q_PROPERTY(Qt::SortOrder sortOrder READ sortOrder WRITE setSortOrder NOTIFY sortOrderChanged)
Q_PROPERTY(int count READ rowCount NOTIFY countChanged) Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
Q_PROPERTY(bool busy READ busy NOTIFY busyChanged) Q_PROPERTY(bool busy READ busy NOTIFY busyChanged)
@ -79,6 +80,9 @@ public:
SampleRate sampleRate() const; SampleRate sampleRate() const;
void setSampleRate(SampleRate sampleRate); void setSampleRate(SampleRate sampleRate);
Qt::SortOrder sortOrder() const;
void setSortOrder(Qt::SortOrder sortOrder);
bool busy() const; bool busy() const;
Q_INVOKABLE NewLogEntry *get(int index) const; Q_INVOKABLE NewLogEntry *get(int index) const;
@ -101,6 +105,7 @@ signals:
void startTimeChanged(); void startTimeChanged();
void endTimeChanged(); void endTimeChanged();
void sampleRateChanged(); void sampleRateChanged();
void sortOrderChanged();
void entriesAdded(int index, const QList<NewLogEntry*> &entries); void entriesAdded(int index, const QList<NewLogEntry*> &entries);
void entriesRemoved(int index, int count); void entriesRemoved(int index, int count);
@ -113,14 +118,21 @@ private:
QStringList m_sources; QStringList m_sources;
QStringList m_columns; QStringList m_columns;
QVariantMap m_filter; QVariantMap m_filter;
Qt::SortOrder m_sortOrder = Qt::AscendingOrder;
bool m_busy = false;
// For time based sampling
QDateTime m_startTime; QDateTime m_startTime;
QDateTime m_endTime; QDateTime m_endTime;
SampleRate m_sampleRate = SampleRateAny; SampleRate m_sampleRate = SampleRateAny;
// For continuous scrolling lists
bool m_completed = false; bool m_completed = false;
bool m_canFetchMore = true; bool m_canFetchMore = true;
bool m_busy = false; int m_blockSize = 5;
int m_blockSize = 30; int m_lastOffset = 0;
QDateTime m_currentNewest;
QList<NewLogEntry*> m_list; QList<NewLogEntry*> m_list;
}; };

View File

@ -643,6 +643,11 @@ int ThingManager::executeBrowserItemAction(const QUuid &thingId, const QString &
return m_jsonClient->sendCommand("Integrations.ExecuteBrowserItemAction", data, this, "executeBrowserItemActionResponse"); return m_jsonClient->sendCommand("Integrations.ExecuteBrowserItemAction", data, this, "executeBrowserItemActionResponse");
} }
int ThingManager::setStateLogging(const QUuid &thingId, const QUuid &stateTypeId, bool enabled)
{
return m_jsonClient->sendCommand("Integrations.SetStateLogging", {{"thingId", thingId}, {"stateTypeId", stateTypeId}, {"enabled", enabled}}, this, "setStateLoggingResponse");
}
int ThingManager::connectIO(const QUuid &inputThingId, const QUuid &inputStateTypeId, const QUuid &outputThingId, const QUuid &outputStateTypeId, bool inverted) int ThingManager::connectIO(const QUuid &inputThingId, const QUuid &inputStateTypeId, const QUuid &outputThingId, const QUuid &outputStateTypeId, bool inverted)
{ {
QVariantMap data; QVariantMap data;
@ -694,6 +699,12 @@ void ThingManager::disconnectIOResponse(int commandId, const QVariantMap &params
qDebug() << "DisconnectIO response" << commandId << qUtf8Printable(QJsonDocument::fromVariant(params).toJson()); qDebug() << "DisconnectIO response" << commandId << qUtf8Printable(QJsonDocument::fromVariant(params).toJson());
} }
void ThingManager::setStateLoggingResponse(int commandId, const QVariantMap &params)
{
Q_UNUSED(commandId)
qCDebug(dcThingManager()) << "Set state logging response" << qUtf8Printable(QJsonDocument::fromVariant(params).toJson());
}
Vendor *ThingManager::unpackVendor(const QVariantMap &vendorMap) Vendor *ThingManager::unpackVendor(const QVariantMap &vendorMap)
{ {
Vendor *v = new Vendor(vendorMap.value("id").toString(), vendorMap.value("name").toString()); Vendor *v = new Vendor(vendorMap.value("id").toString(), vendorMap.value("name").toString());

View File

@ -98,6 +98,7 @@ public:
Q_INVOKABLE BrowserItem* browserItem(const QUuid &thingId, const QString &itemId); Q_INVOKABLE BrowserItem* browserItem(const QUuid &thingId, const QString &itemId);
Q_INVOKABLE int executeBrowserItem(const QUuid &thingId, const QString &itemId); Q_INVOKABLE int executeBrowserItem(const QUuid &thingId, const QString &itemId);
Q_INVOKABLE int executeBrowserItemAction(const QUuid &thingId, const QString &itemId, const QUuid &actionTypeId, const QVariantList &params = QVariantList()); Q_INVOKABLE int executeBrowserItemAction(const QUuid &thingId, const QString &itemId, const QUuid &actionTypeId, const QVariantList &params = QVariantList());
Q_INVOKABLE int setStateLogging(const QUuid &thingId, const QUuid &stateTypeId, bool enabled);
Q_INVOKABLE int connectIO(const QUuid &inputThingId, const QUuid &inputStateTypeId, const QUuid &outputThingId, const QUuid &outputStateTypeId, bool inverted); Q_INVOKABLE int connectIO(const QUuid &inputThingId, const QUuid &inputStateTypeId, const QUuid &outputThingId, const QUuid &outputStateTypeId, bool inverted);
Q_INVOKABLE int disconnectIO(const QUuid &ioConnectionId); Q_INVOKABLE int disconnectIO(const QUuid &ioConnectionId);
@ -123,6 +124,7 @@ private:
Q_INVOKABLE void getIOConnectionsResponse(int commandId, const QVariantMap &params); Q_INVOKABLE void getIOConnectionsResponse(int commandId, const QVariantMap &params);
Q_INVOKABLE void connectIOResponse(int commandId, const QVariantMap &params); Q_INVOKABLE void connectIOResponse(int commandId, const QVariantMap &params);
Q_INVOKABLE void disconnectIOResponse(int commandId, const QVariantMap &params); Q_INVOKABLE void disconnectIOResponse(int commandId, const QVariantMap &params);
Q_INVOKABLE void setStateLoggingResponse(int commandId, const QVariantMap &params);
public slots: public slots:
ThingGroup* createGroup(Interface *interface, ThingsProxy *things); ThingGroup* createGroup(Interface *interface, ThingsProxy *things);

View File

@ -50,7 +50,6 @@
<file>ui/devicepages/ShutterDevicePage.qml</file> <file>ui/devicepages/ShutterDevicePage.qml</file>
<file>ui/devicepages/GarageThingPage.qml</file> <file>ui/devicepages/GarageThingPage.qml</file>
<file>ui/devicepages/AwningThingPage.qml</file> <file>ui/devicepages/AwningThingPage.qml</file>
<file>ui/devicepages/NotificationsDevicePage.qml</file>
<file>ui/devicepages/LightThingPage.qml</file> <file>ui/devicepages/LightThingPage.qml</file>
<file>ui/devicepages/FingerprintReaderDevicePage.qml</file> <file>ui/devicepages/FingerprintReaderDevicePage.qml</file>
<file>ui/devicepages/DeviceLogPage.qml</file> <file>ui/devicepages/DeviceLogPage.qml</file>
@ -312,5 +311,6 @@
<file>ui/mainviews/airconditioning/LegendDelegate.qml</file> <file>ui/mainviews/airconditioning/LegendDelegate.qml</file>
<file>ui/customviews/StateChart.qml</file> <file>ui/customviews/StateChart.qml</file>
<file>ui/devicepages/ThingLogPage.qml</file> <file>ui/devicepages/ThingLogPage.qml</file>
<file>ui/devicepages/NotificationsThingPage.qml</file>
</qresource> </qresource>
</RCC> </RCC>

View File

@ -55,10 +55,8 @@ Item {
id: logsModel id: logsModel
engine: root.thing && root.stateType ? _engine : null engine: root.thing && root.stateType ? _engine : null
source: root.thing ? "state-" + thing.id + "-" + root.stateType.name : "" source: root.thing ? "state-" + thing.id + "-" + root.stateType.name : ""
// columns: [root.stateType.name] startTime: new Date(d.startTime.getTime() - d.range * 1.1 * 60000)
// filter: root.stateType ? ({state: root.stateType.name}) : ({}) endTime: new Date(d.endTime.getTime() + d.range * 1.1 * 60000)
startTime: new Date(d.startTime.getTime() - d.range * 60000)
endTime: new Date(d.endTime.getTime() + d.range * 60000)
sampleRate: d.sampleRate sampleRate: d.sampleRate
property double minValue property double minValue
@ -66,11 +64,11 @@ Item {
onEntriesAdded: { onEntriesAdded: {
print("**** entries added", index, entries.length, "entries in series:", valueSeries.count, "in model", logsModel.count) print("**** entries added", index, entries.length, "entries in series:", valueSeries.count, "in model", logsModel.count)
if (valueSeries.count == 0) { // if (valueSeries.count == 0) {
print("adding zero item", new Date()) // print("adding zero item", new Date())
valueSeries.insert(0, new Date(), 0) // valueSeries.insert(0, new Date(), 0)
zeroSeries.ensureValue(new Date()) // zeroSeries.ensureValue(new Date())
} // }
for (var i = 0; i < entries.length; i++) { for (var i = 0; i < entries.length; i++) {
var entry = entries[i] var entry = entries[i]
@ -83,17 +81,17 @@ Item {
value = false; value = false;
} }
// for booleans, we'll insert the opposite value right before the new one so the position is doubled // for booleans, we'll insert the opposite value right before the new one so the position is doubled
// +1 because the is the "new" value at the beginning var insertIdx = (index + i) * 2
var insertIdx = (index + i) * 2 + 1 valueSeries.insert(insertIdx, entry.timestamp.getTime() - 500, !value)
valueSeries.insert(insertIdx, entry.timestamp, value) valueSeries.insert(insertIdx+1, entry.timestamp, value)
valueSeries.insert(insertIdx + 1, entry.timestamp.getTime() - 500, !value)
if (insertIdx == 1) {
// first index, we'll have to update the "now" value // valueSeries.removePoints(0, 1);
valueSeries.removePoints(0, 1); // if (insertIdx == 0) {
valueSeries.insert(0, entry.timestamp.getTime() + 2000, value) // // first index, we'll have to update the "now" value
zeroSeries.ensureValue(new Date(entry.timestamp.getTime() + 2000)) // valueSeries.insert(0, entry.timestamp.getTime() + 2000, value)
} // zeroSeries.ensureValue(new Date(entry.timestamp.getTime() + 2000))
// }
} else { } else {
var value = entry.values[root.stateType.name] var value = entry.values[root.stateType.name]
@ -104,25 +102,27 @@ Item {
minValue = minValue == undefined ? value : Math.min(minValue, value) minValue = minValue == undefined ? value : Math.min(minValue, value)
maxValue = maxValue == undefined ? value : Math.max(maxValue, value) maxValue = maxValue == undefined ? value : Math.max(maxValue, value)
var insertIdx = (index + i) + 1 var insertIdx = index + i
valueSeries.insert(insertIdx, entry.timestamp, value) valueSeries.insert(insertIdx, entry.timestamp, value)
if (insertIdx == 1) {
// first index, we'll have to update the "now" value
valueSeries.removePoints(0, 1);
valueSeries.insert(0, entry.timestamp.getTime() + 2000, value)
zeroSeries.ensureValue(new Date(entry.timestamp.getTime() + 2000))
}
} }
} }
if (root.stateType.type.toLowerCase() == "bool") {
var last = valueSeries.at(valueSeries.count-1);
if (last.x < d.endTime) {
valueSeries.append(d.endTime, last.y)
zeroSeries.ensureValue(d.endTime)
}
}
print("added entries. now in series:", valueSeries.count) print("added entries. now in series:", valueSeries.count)
} }
onEntriesRemoved: { onEntriesRemoved: {
print("removing:", index, count, valueSeries.count) print("removing:", index, count, valueSeries.count)
if (root.stateType.type.toLowerCase() == "bool") { if (root.stateType.type.toLowerCase() == "bool") {
valueSeries.removePoints((index * 2) + 1, count * 2) valueSeries.removePoints((index * 2) /*+ 1*/, count * 2)
} else { } else {
valueSeries.removePoints(index + 1, count) valueSeries.removePoints(index /*+ 1*/, count)
} }
zeroSeries.shrink() zeroSeries.shrink()
@ -291,24 +291,24 @@ Item {
borderWidth: 2 borderWidth: 2
lowerSeries: LineSeries { lowerSeries: LineSeries {
id: zeroSeries id: zeroSeries
XYPoint { x: dateTimeAxis.max.getTime(); y: 0 }
XYPoint { x: dateTimeAxis.min.getTime(); y: 0 } XYPoint { x: dateTimeAxis.min.getTime(); y: 0 }
XYPoint { x: dateTimeAxis.max.getTime(); y: 0 }
function ensureValue(timestamp) { function ensureValue(timestamp) {
if (count == 0) { if (count == 0) {
append(timestamp, 0) append(timestamp, 0)
} else if (count == 1) { } else if (count == 1) {
if (timestamp.getTime() < at(0).x) { if (timestamp.getTime() < at(0).x) {
append(timestamp, 0)
} else {
insert(0, timestamp, 0) insert(0, timestamp, 0)
} else {
append(timestamp, 0)
} }
} else { } else {
if (timestamp.getTime() < at(1).x) { if (timestamp.getTime() < at(0).x) {
remove(1)
append(timestamp, 0)
} else if (timestamp.getTime() > at(0).x) {
remove(0) remove(0)
insert(0, timestamp, 0) insert(0, timestamp, 0)
} else if (timestamp.getTime() > at(1).x) {
remove(1)
append(timestamp, 0)
} }
} }
} }
@ -468,14 +468,14 @@ Item {
backgroundRect: Qt.rect(mouseArea.x + toolTip.x, mouseArea.y + toolTip.y, toolTip.width, toolTip.height) backgroundRect: Qt.rect(mouseArea.x + toolTip.x, mouseArea.y + toolTip.y, toolTip.width, toolTip.height)
property var timestamp: new Date(d.startTime.getTime() + (mouseArea.mouseX * (d.endTime.getTime() - d.startTime.getTime()) / mouseArea.width) ) property var timestamp: new Date(d.startTime.getTime() + (mouseArea.mouseX * (d.endTime.getTime() - d.startTime.getTime()) / mouseArea.width) )
property NewLogEntry entry: logsModel.find(timestamp) property NewLogEntry entry: logsModel.count > 0 ? logsModel.find(timestamp) : null
// eX : eT = w : duration // eX : eT = w : duration
property int entryX: entry ? (entry.timestamp.getTime() - d.startTime.getTime()) * mouseArea.width / (d.endTime.getTime() - d.startTime.getTime()) : 0 property int entryX: entry ? (entry.timestamp.getTime() - d.startTime.getTime()) * mouseArea.width / (d.endTime.getTime() - d.startTime.getTime()) : 0
property int xOnRight: Math.max(0, entryX) + Style.smallMargins property int xOnRight: Math.max(0, entryX) + Style.smallMargins
property int xOnLeft: Math.min(entryX, mouseArea.width) - Style.smallMargins - width property int xOnLeft: Math.min(entryX, mouseArea.width) - Style.smallMargins - width
x: xOnRight + width < mouseArea.width ? xOnRight : xOnLeft x: xOnRight + width < mouseArea.width ? xOnRight : xOnLeft
property double value: toolTip.entry ? entry.values[root.stateType.name] : 0 property var value: entry ? entry.values[root.stateType.name] : null
y: Math.min(Math.max(mouseArea.height - (value * mouseArea.height / valueAxis.max) - height - Style.margins, 0), mouseArea.height - height) y: Math.min(Math.max(mouseArea.height - (value * mouseArea.height / valueAxis.max) - height - Style.margins, 0), mouseArea.height - height)
width: tooltipLayout.implicitWidth + Style.smallMargins * 2 width: tooltipLayout.implicitWidth + Style.smallMargins * 2
@ -497,16 +497,11 @@ Item {
Label { Label {
Layout.fillWidth: true Layout.fillWidth: true
elide: Text.ElideRight elide: Text.ElideRight
property double value: toolTip.entry text: toolTip.value === null
? (toolTip.entry.acquisition >= 0 ? toolTip.entry.consumption : Math.max(0, -toolTip.entry.production)) ? qsTr("No data")
: 0
property bool translate: value >= 1000
property double translatedValue: value / (translate ? 1000 : 1)
text: toolTip.entry == null
? ""
: root.stateType.type.toLowerCase() == "bool" : root.stateType.type.toLowerCase() == "bool"
? root.stateType.displayName + ": " + (toolTip.value ? qsTr("Yes") : qsTr("No")) ? root.stateType.displayName + ": " + (toolTip.value ? qsTr("Yes") : qsTr("No"))
: Types.toUiValue(toolTip.entry.values[root.stateType.name], root.stateType.unit).toFixed(root.roundTo) + Types.toUiUnit(root.stateType.unit) : Types.toUiValue(toolTip.value, root.stateType.unit).toFixed(root.roundTo) + Types.toUiUnit(root.stateType.unit)
font: Style.smallFont font: Style.smallFont
} }

View File

@ -44,54 +44,161 @@ ThingPageBase {
readonly property State powerState: thing ? thing.stateByName("power") : null readonly property State powerState: thing ? thing.stateByName("power") : null
EmptyViewPlaceholder { Loader {
anchors { left: parent.left; right: parent.right; margins: app.margins }
anchors.verticalCenter: parent.verticalCenter
title: qsTr("This switch has not been used yet.")
text: qsTr("Press a button on the switch to see logs appearing here.")
visible: !logsModel.busy && logsModel.count === 0 && !root.isVirtual
buttonVisible: false
imageSource: "../images/system-shutdown.svg"
}
GenericTypeLogView {
id: logView
anchors.fill: parent anchors.fill: parent
visible: !root.isVirtual visible: !root.isVirtual
sourceComponent: {
logsModel: LogsModel { if (engine.jsonRpcClient.ensureServerVersion("8.0")) {
id: logsModel return logViewComponent
engine: _engine } else {
thingId: root.thing.id return logViewComponentPre80
live: true
typeIds: {
var ret = [];
ret.push(root.thing.thingClass.eventTypes.findByName("pressed").id)
if (root.thing.thingClass.eventTypes.findByName("longPressed")) {
ret.push(root.thing.thingClass.eventTypes.findByName("longPressed").id)
}
return ret;
} }
} }
}
onAddRuleClicked: { Component {
var value = logView.logsModel.get(index).value id: logViewComponent
var typeId = logView.logsModel.get(index).typeId
var rule = engine.ruleManager.createNewRule(); ListView {
var eventDescriptor = rule.eventDescriptors.createNewEventDescriptor(); id: logView
eventDescriptor.thingId = root.thing.id; anchors.fill: parent
var eventType = root.thing.thingClass.eventTypes.getEventType(typeId); ScrollBar.vertical: ScrollBar {}
eventDescriptor.eventTypeId = eventType.id;
rule.name = root.thing.name + " - " + eventType.displayName; model: NewLogsModel {
if (eventType.paramTypes.count === 1) { id: logsModel
var paramType = eventType.paramTypes.get(0); engine: _engine
eventDescriptor.paramDescriptors.setParamDescriptor(paramType.id, value, ParamDescriptor.ValueOperatorEquals); sources: ["event-" + root.thing.id + "-pressed", "event-" + root.thing.id + "-longPressed"]
rule.eventDescriptors.addEventDescriptor(eventDescriptor); // live: true
rule.name = rule.name + " - " + value }
delegate: NymeaItemDelegate {
id: entryDelegate
width: logView.width
property NewLogEntry entry: logsModel.get(index)
property EventType eventType: {
switch (entry.source) {
case "event-" + root.thing.id + "-pressed":
return root.thing.thingClass.eventTypes.findByName("pressed")
case "event-" + root.thing.id + "-longPressed":
return root.thing.thingClass.eventTypes.findByName("longPressed")
}
return null
}
contentItem: ColumnLayout {
RowLayout {
Label {
text: entryDelegate.eventType.displayName
Layout.fillWidth: true
elide: Text.ElideRight
font: Style.smallFont
}
Label {
text: Qt.formatDateTime(model.timestamp,"dd.MM.yy hh:mm:ss")
elide: Text.ElideRight
font.pixelSize: app.smallFont
enabled: false
}
}
Label {
Layout.fillWidth: true
text: {
var ret = []
var values = JSON.parse(entry.values.params)
for (var i = 0; i < entryDelegate.eventType.paramTypes.count; i++) {
var paramType = entryDelegate.eventType.paramTypes.get(i)
ret.push(paramType.displayName + ": " + Types.toUiValue(values[paramType.name], paramType.unit) + " " + Types.toUiUnit(paramType.unit))
}
return ret.join(", ")
}
}
}
}
// onAddRuleClicked: {
// var value = logView.logsModel.get(index).value
// var typeId = logView.logsModel.get(index).typeId
// var rule = engine.ruleManager.createNewRule();
// var eventDescriptor = rule.eventDescriptors.createNewEventDescriptor();
// eventDescriptor.thingId = root.thing.id;
// var eventType = root.thing.thingClass.eventTypes.getEventType(typeId);
// eventDescriptor.eventTypeId = eventType.id;
// rule.name = root.thing.name + " - " + eventType.displayName;
// if (eventType.paramTypes.count === 1) {
// var paramType = eventType.paramTypes.get(0);
// eventDescriptor.paramDescriptors.setParamDescriptor(paramType.id, value, ParamDescriptor.ValueOperatorEquals);
// rule.eventDescriptors.addEventDescriptor(eventDescriptor);
// rule.name = rule.name + " - " + value
// }
// var rulePage = pageStack.push(Qt.resolvedUrl("../magic/ThingRulesPage.qml"), {thing: root.thing});
// rulePage.addRule(rule);
// }
EmptyViewPlaceholder {
anchors { left: parent.left; right: parent.right; margins: app.margins }
anchors.verticalCenter: parent.verticalCenter
title: qsTr("This switch has not been used yet.")
text: qsTr("Press a button on the switch to see logs appearing here.")
visible: !logsModel.busy && logsModel.count === 0
buttonVisible: false
imageSource: "../images/system-shutdown.svg"
}
}
}
Component {
id: logViewComponentPre80
GenericTypeLogView {
id: logView
anchors.fill: parent
logsModel: LogsModel {
id: logsModel
engine: _engine
thingId: root.thing.id
live: true
typeIds: {
var ret = [];
ret.push(root.thing.thingClass.eventTypes.findByName("pressed").id)
if (root.thing.thingClass.eventTypes.findByName("longPressed")) {
ret.push(root.thing.thingClass.eventTypes.findByName("longPressed").id)
}
return ret;
}
}
onAddRuleClicked: {
var value = logView.logsModel.get(index).value
var typeId = logView.logsModel.get(index).typeId
var rule = engine.ruleManager.createNewRule();
var eventDescriptor = rule.eventDescriptors.createNewEventDescriptor();
eventDescriptor.thingId = root.thing.id;
var eventType = root.thing.thingClass.eventTypes.getEventType(typeId);
eventDescriptor.eventTypeId = eventType.id;
rule.name = root.thing.name + " - " + eventType.displayName;
if (eventType.paramTypes.count === 1) {
var paramType = eventType.paramTypes.get(0);
eventDescriptor.paramDescriptors.setParamDescriptor(paramType.id, value, ParamDescriptor.ValueOperatorEquals);
rule.eventDescriptors.addEventDescriptor(eventDescriptor);
rule.name = rule.name + " - " + value
}
var rulePage = pageStack.push(Qt.resolvedUrl("../magic/ThingRulesPage.qml"), {thing: root.thing});
rulePage.addRule(rule);
}
EmptyViewPlaceholder {
anchors { left: parent.left; right: parent.right; margins: app.margins }
anchors.verticalCenter: parent.verticalCenter
title: qsTr("This switch has not been used yet.")
text: qsTr("Press a button on the switch to see logs appearing here.")
visible: !logsModel.busy && logsModel.count === 0 && !root.isVirtual
buttonVisible: false
imageSource: "../images/system-shutdown.svg"
} }
var rulePage = pageStack.push(Qt.resolvedUrl("../magic/ThingRulesPage.qml"), {thing: root.thing});
rulePage.addRule(rule);
} }
} }

View File

@ -80,8 +80,11 @@ ThingPageBase {
Layout.fillWidth: true Layout.fillWidth: true
topPadding: model.type === ThingModel.TypeActionType ? app.margins / 2 : 0 topPadding: model.type === ThingModel.TypeActionType ? app.margins / 2 : 0
bottomPadding: 0 bottomPadding: 0
contentItem: Loader { contentItem: Loader {
id: inlineLoader id: inlineLoader
Layout.fillWidth: true
Layout.preferredHeight: Style.smallDelegateHeight
sourceComponent: { sourceComponent: {
switch (model.type) { switch (model.type) {
case ThingModel.TypeStateType: case ThingModel.TypeStateType:
@ -113,7 +116,19 @@ ThingPageBase {
} }
} }
onClicked: swipe.close() onClicked: {
print("clicked")
if (swipe.complete) {
swipe.close()
} else {
swipe.open(SwipeDelegate.Right)
}
}
Connections {
target: flickable
onContentYChanged: if (swipe.completed) swipe.close()
}
onPressAndHold: swipe.open(SwipeDelegate.Right) onPressAndHold: swipe.open(SwipeDelegate.Right)
swipe.right: RowLayout { swipe.right: RowLayout {
height: delegate.height height: delegate.height

View File

@ -43,13 +43,13 @@ ThingPageBase {
readonly property State powerState: thing.stateByName("power") readonly property State powerState: thing.stateByName("power")
readonly property State brightnessState: thing.stateByName("brightness") readonly property State brightnessState: thing.stateByName("brightness")
readonly property ActionType brightnessActionType: thingClass.actionTypes.findByName("brightness"); readonly property ActionType brightnessActionType: thing.thingClass.actionTypes.findByName("brightness");
readonly property State colorState: thing.stateByName("color") readonly property State colorState: thing.stateByName("color")
readonly property StateType ctStateType: thingClass.stateTypes.findByName("colorTemperature") readonly property StateType ctStateType: thing.thingClass.stateTypes.findByName("colorTemperature")
readonly property State ctState: thing.stateByName("colorTemperature") readonly property State ctState: thing.stateByName("colorTemperature")
readonly property ActionType ctActionType: thingClass.actionTypes.findByName("colorTemperature") readonly property ActionType ctActionType: thing.thingClass.actionTypes.findByName("colorTemperature")
readonly property int statesCount: (powerState !== null ? 1 : 0) + readonly property int statesCount: (powerState !== null ? 1 : 0) +
(brightnessState !== null ? 1 : 0) + (brightnessState !== null ? 1 : 0) +

View File

@ -136,12 +136,33 @@ ThingPageBase {
} }
Loader {
Layout.fillWidth: true
Layout.fillHeight: true
sourceComponent: {
if (engine.jsonRpcClient.ensureServerVersion("8.0")) {
return logViewComponent
} else {
return logViewComponentPre80
}
}
}
}
Component {
id: logViewComponentPre80
ListView { ListView {
id: logView id: logView
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
clip: true clip: true
BusyIndicator {
anchors.centerIn: parent
visible: logsModel.busy
running: visible
}
model: LogsModel { model: LogsModel {
id: logsModel id: logsModel
thingId: root.thing.id thingId: root.thing.id
@ -206,7 +227,7 @@ ThingPageBase {
{ {
timestamp: model.timestamp, timestamp: model.timestamp,
notificationTitle: itemDelegate.title, notificationTitle: itemDelegate.title,
notificationBody: itemDelegate.text, notificationBody: itemDelegate.tet,
errorCode: model.errorCode errorCode: model.errorCode
}); });
popup.open(); popup.open();
@ -225,10 +246,98 @@ ThingPageBase {
} }
} }
BusyIndicator { Component {
anchors.centerIn: parent id: logViewComponent
visible: logsModel.busy ListView {
running: visible id: logView
Layout.fillHeight: true
Layout.fillWidth: true
clip: true
BusyIndicator {
anchors.centerIn: parent
visible: logsModel.busy
running: visible
}
model: NewLogsModel {
id: logsModel
engine: _engine
// live: true
source: "action-" + root.thing.id + "-notify"
}
delegate: BigTile {
id: itemDelegate
showHeader: false
width: logView.width - app.margins
anchors.horizontalCenter: parent.horizontalCenter
property var params: JSON.parse(model.values.params)
contentItem: RowLayout {
ColumnLayout {
Label {
Layout.fillWidth: true
text: itemDelegate.params.title
elide: Text.ElideRight
}
GridLayout {
Layout.fillWidth: true
columns: textLabel.implicitWidth + dateLayout.implicitWidth < width ? 2 : 1
Label {
id: textLabel
Layout.fillWidth: true
text: itemDelegate.params.body
font.pixelSize: app.smallFont
wrapMode: Text.WordWrap
}
RowLayout {
id: dateLayout
Layout.fillWidth: true
spacing: app.margins / 2
Label {
Layout.fillWidth: true
horizontalAlignment: Text.AlignRight
text: Qt.formatDateTime(model.timestamp)
font.pixelSize: app.extraSmallFont
}
ColorIcon {
Layout.preferredWidth: Style.smallIconSize
Layout.preferredHeight: Style.smallIconSize
name: "../images/dialog-warning-symbolic.svg"
color: "red"
visible: model.values.status !== "ThingErrorNoError"
}
}
}
}
}
onClicked: {
var popup = detailsPopup.createObject(root,
{
timestamp: model.timestamp,
notificationTitle: itemDelegate.params.title,
notificationBody: itemDelegate.params.body,
errorCode: model.status
});
popup.open();
}
}
EmptyViewPlaceholder {
anchors.centerIn: parent
width: parent.width - app.margins * 2
title: qsTr("No messages sent yet.")
text: qsTr("Sent messages will appear here.")
imageSource: "../images/messaging-app-symbolic.svg"
buttonVisible: false
visible: logsModel.count == 0 && !logsModel.busy
}
}
} }
Component { Component {
@ -248,7 +357,7 @@ ThingPageBase {
Label { Label {
Layout.fillWidth: true Layout.fillWidth: true
text: detailsDialog.errorCode == "" ? qsTr("Date sent") : qsTr("Sending failed") text: detailsDialog.errorCode == "" || detailsDialog.errorCode == "ThingErrorNoError" ? qsTr("Date sent") : qsTr("Sending failed")
font.bold: true font.bold: true
} }
Label { Label {
@ -266,7 +375,7 @@ ThingPageBase {
} }
Label { Label {
Layout.topMargin: app.margins Layout.topMargin: Style.margins
Layout.fillWidth: true Layout.fillWidth: true
text: qsTr("Title") text: qsTr("Title")
font.bold: true font.bold: true

View File

@ -73,7 +73,7 @@ ThingPageBase {
Component.onCompleted: { Component.onCompleted: {
var supportedInterfaces = Object.keys(interfaceStateMap) var supportedInterfaces = Object.keys(interfaceStateMap)
for (var i = 0; i < supportedInterfaces.length; i++) { for (var i = 0; i < supportedInterfaces.length; i++) {
if (root.thingClass.interfaces.indexOf(supportedInterfaces[i]) >= 0) { if (root.thing.thingClass.interfaces.indexOf(supportedInterfaces[i]) >= 0) {
append({name: supportedInterfaces[i]}); append({name: supportedInterfaces[i]});
} }
} }
@ -138,7 +138,7 @@ ThingPageBase {
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: item.implicitHeight Layout.preferredHeight: item.implicitHeight
property StateType stateType: root.thingClass.stateTypes.findByName(interfaceStateMap[modelData]) property StateType stateType: root.thing.thingClass.stateTypes.findByName(interfaceStateMap[modelData])
property State state: root.thing.stateByName(interfaceStateMap[modelData]) property State state: root.thing.stateByName(interfaceStateMap[modelData])
property string interfaceName: modelData property string interfaceName: modelData

View File

@ -41,9 +41,9 @@ ThingPageBase {
readonly property bool isEnergyMeter: root.thing && root.thing.thingClass.interfaces.indexOf("energymeter") >= 0 readonly property bool isEnergyMeter: root.thing && root.thing.thingClass.interfaces.indexOf("energymeter") >= 0
readonly property bool isConsumer: root.thing && root.thing.thingClass.interfaces.indexOf("smartmeterconsumer") >= 0 readonly property bool isConsumer: root.thing && root.thing.thingClass.interfaces.indexOf("smartmeterconsumer") >= 0
readonly property bool isProducer: root.thing && root.thingClass.interfaces.indexOf("smartmeterproducer") >= 0 readonly property bool isProducer: root.thing && root.thing.thingClass.interfaces.indexOf("smartmeterproducer") >= 0
readonly property bool isBattery: root.thing && root.thingClass.interfaces.indexOf("energystorage") >= 0 readonly property bool isBattery: root.thing && root.thing.thingClass.interfaces.indexOf("energystorage") >= 0
readonly property bool isEvCharger: itemDelegate.thing.thingClass.interfaces.indexOf("evcharger") >= 0 readonly property bool isEvCharger: root.thing && root.thing.thingClass.interfaces.indexOf("evcharger") >= 0
readonly property State currentPowerState: root.thing.stateByName("currentPower") readonly property State currentPowerState: root.thing.stateByName("currentPower")

View File

@ -58,14 +58,30 @@ Page {
header: NymeaHeader { header: NymeaHeader {
text: qsTr("History for %1").arg(root.stateType.displayName) text: qsTr("History for %1").arg(root.stateType.displayName)
onBackPressed: pageStack.pop() onBackPressed: pageStack.pop()
HeaderButton {
imageSource: "delete"
onClicked: {
var popup = deleteLogsComponent.createObject(root)
popup.open()
}
Component {
id: deleteLogsComponent
NymeaDialog {
title: qsTr("Remove logs?")
text: qsTr("Do you want to remove the log for this state and disable logging?")
onAccepted: engine.thingManager.setStateLogging(root.thing.id, root.stateType.id, false)
}
}
}
} }
NewLogsModel { NewLogsModel {
id: logsModel id: logsModel
engine: _engine engine: _engine
columns: [root.stateType.name] source: "state-" + root.thing.id + "-" + root.stateType.name
source: "states-" + root.thing.id sortOrder: Qt.DescendingOrder
filter: ({state: root.stateType.name})
} }
Component.onCompleted: { Component.onCompleted: {
@ -75,13 +91,21 @@ Page {
GridLayout { GridLayout {
anchors.fill: parent anchors.fill: parent
columns: app.landscape ? 2 : 1 columns: app.landscape ? 2 : 1
visible: root.isLogged
StateChart { Loader {
Layout.fillWidth: true Layout.fillWidth: true
thing: root.thing active: root.canShowGraph
stateType: root.stateType
sourceComponent: Component {
StateChart {
thing: root.thing
stateType: root.stateType
}
}
} }
ListView { ListView {
id: listView id: listView
Layout.fillWidth: true Layout.fillWidth: true
@ -100,61 +124,6 @@ Page {
Component.onCompleted: print("delegate:", JSON.stringify(entry.values), root.stateType.name, entry.values[root.stateType.name]) Component.onCompleted: print("delegate:", JSON.stringify(entry.values), root.stateType.name, entry.values[root.stateType.name])
} }
} }
// TabBar {
// id: tabBar
// Layout.fillWidth: true
// visible: root.canShowGraph
// TabButton {
// text: qsTr("Log")
// }
// TabButton {
// text: qsTr("Graph")
// }
// }
// SwipeView {
// id: swipeView
// Layout.fillWidth: true
// Layout.fillHeight: true
// currentIndex: tabBar.currentIndex
// interactive: false
// GenericTypeLogView {
// id: logView
// width: swipeView.width
// height: swipeView.height
// logsModel: logsModelNg
// onAddRuleClicked: {
// var value = logView.logsModel.get(index).value
// var typeId = logView.logsModel.get(index).typeId
// var rule = engine.ruleManager.createNewRule();
// var stateEvaluator = rule.createStateEvaluator();
// stateEvaluator.stateDescriptor.thingId = thing.id;
// stateEvaluator.stateDescriptor.stateTypeId = typeId;
// stateEvaluator.stateDescriptor.value = value;
// stateEvaluator.stateDescriptor.valueOperator = StateDescriptor.ValueOperatorEquals;
// rule.setStateEvaluator(stateEvaluator);
// rule.name = root.thing.name + " - " + stateType.displayName + " = " + value;
// var rulePage = pageStack.push(Qt.resolvedUrl("../magic/ThingRulesPage.qml"), {thing: root.thing});
// rulePage.addRule(rule);
// }
// }
// Loader {
// id: graphLoader
// width: swipeView.width
// height: swipeView.height
// Component.onCompleted: {
// var source;
// source = Qt.resolvedUrl("../customviews/GenericTypeGraph.qml");
// setSource(source, {thing: root.thing, stateType: root.stateType})
// }
// }
// }
} }
EmptyViewPlaceholder { EmptyViewPlaceholder {
@ -166,9 +135,9 @@ Page {
buttonText: qsTr("Enable logging") buttonText: qsTr("Enable logging")
visible: !root.isLogged visible: !root.isLogged
onButtonClicked: { onButtonClicked: {
print("enabming logging")
engine.thingManager.setStateLogging(root.thing.id, root.stateType.id, true)
} }
} }
} }

View File

@ -44,7 +44,7 @@ ThingPageBase {
readonly property StateType targetTemperatureStateType: thing.thingClass.stateTypes.findByName("targetTemperature") readonly property StateType targetTemperatureStateType: thing.thingClass.stateTypes.findByName("targetTemperature")
readonly property State targetTemperatureState: targetTemperatureStateType ? thing.states.getState(targetTemperatureStateType.id) : null readonly property State targetTemperatureState: targetTemperatureStateType ? thing.states.getState(targetTemperatureStateType.id) : null
readonly property StateType powerStateType: thingClass.stateTypes.findByName("power") readonly property StateType powerStateType: thing.thingClass.stateTypes.findByName("power")
readonly property State powerState: powerStateType ? thing.states.getState(powerStateType.id) : null readonly property State powerState: powerStateType ? thing.states.getState(powerStateType.id) : null
readonly property StateType temperatureStateType: thing.thingClass.stateTypes.findByName("temperature") readonly property StateType temperatureStateType: thing.thingClass.stateTypes.findByName("temperature")
readonly property State temperatureState: temperatureStateType ? thing.states.getState(temperatureStateType.id) : null readonly property State temperatureState: temperatureStateType ? thing.states.getState(temperatureStateType.id) : null

View File

@ -47,41 +47,51 @@ Page {
HeaderButton { HeaderButton {
imageSource: "../images/filters.svg" imageSource: "../images/filters.svg"
color: logsModelNg.filterEnabled ? Style.accentColor : Style.iconColor color: logsModel.filterEnabled ? Style.accentColor : Style.iconColor
onClicked: logsModelNg.filterEnabled = !logsModelNg.filterEnabled onClicked: logsModel.filterEnabled = !logsModel.filterEnabled
visible: root.filterTypeIds.length === 0 visible: root.filterTypeIds.length === 0
} }
} }
NewLogsModel { NewLogsModel {
id: logsModelNg id: logsModel
engine: _engine engine: _engine
columns: [root.stateType.name] // columns: [root.stateType.name]
sources: ["states-" + root.thing.id, "events-" + root.thing.id, "actions-" + root.thing.id] sources: {
filter: { var ret = []
if (!filterEnabled) { if (filterEnabled) {
return ({}) if (isStateFilter) {
ret.push("state-" + root.thing.id + "-" + filterTypeName)
} else if (isEventFilter) {
ret.push("event-" + root.thing.id + "-" + filterTypeName)
} else if (isActionFilter) {
ret.push("action-" + root.thing.id + "-" + filterTypeName)
}
return ret;
} }
print("*** filter updated", isStateFilter, isEventFilter, isActionFilter, filterTypeName, thing.thingClass.stateTypes.findByName(filterTypeName))
if (isStateFilter) { for (var i = 0; i < root.thing.thingClass.stateTypes.count; i++) {
return ({state: filterTypeName}) var stateType = root.thing.thingClass.stateTypes.get(i)
ret.push("state-" + root.thing.id + "-" + stateType.name)
} }
if (isEventFilter) { for (var i = 0; i < root.thing.thingClass.eventTypes.count; i++) {
return ({event: filterTypeName}) var eventType = root.thing.thingClass.eventTypes.get(i)
ret.push("event-" + root.thing.id + "-" + eventType.name)
} }
if (isActionFilter) { for (var i = 0; i < root.thing.thingClass.actionTypes.count; i++) {
return ({action: filterTypeName}) var actionType = root.thing.thingClass.actionTypes.get(i)
ret.push("action-" + root.thing.id + "-" + actionType.name)
} }
return ({}) return ret;
} }
property string filterTypeName: filterDeviceModel.getData(filterComboBox.currentIndex, ThingModel.RoleName) property string filterTypeName: filterDeviceModel.getData(filterComboBox.currentIndex, ThingModel.RoleName)
property bool isStateFilter: thing.thingClass.stateTypes.findByName(filterTypeName) !== null property bool isStateFilter: thing.thingClass.stateTypes.findByName(filterTypeName) !== null
property bool isEventFilter: thing.thingClass.eventTypes.findByName(filterTypeName) !== null property bool isEventFilter: thing.thingClass.eventTypes.findByName(filterTypeName) !== null
property bool isActionFilter: thing.thingClass.actionTypes.findByName(filterTypeName) !== null property bool isActionFilter: thing.thingClass.actionTypes.findByName(filterTypeName) !== null
onFilterChanged: { onSourcesChanged: {
logsModelNg.clear() logsModel.clear()
logsModelNg.fetchLogs() logsModel.fetchLogs()
} }
// thingId: root.thing.id // thingId: root.thing.id
@ -109,7 +119,7 @@ Page {
anchors { left: parent.left; top: parent.top; right: parent.right } anchors { left: parent.left; top: parent.top; right: parent.right }
Behavior on height { NumberAnimation { duration: 120; easing.type: Easing.InOutQuad } } Behavior on height { NumberAnimation { duration: 120; easing.type: Easing.InOutQuad } }
height: logsModelNg.filterEnabled ? implicitHeight + app.margins * 2 : 0 height: logsModel.filterEnabled ? implicitHeight + app.margins * 2 : 0
Material.elevation: 1 Material.elevation: 1
leftPadding: 0; rightPadding: 0; topPadding: 0; bottomPadding: 0 leftPadding: 0; rightPadding: 0; topPadding: 0; bottomPadding: 0
@ -180,22 +190,22 @@ Page {
ListView { ListView {
anchors { left: parent.left; top: graphLoader.bottom; right: parent.right; bottom: parent.bottom } anchors { left: parent.left; top: graphLoader.bottom; right: parent.right; bottom: parent.bottom }
clip: true clip: true
model: logsModelNg model: logsModel
ScrollBar.vertical: ScrollBar {} ScrollBar.vertical: ScrollBar {}
BusyIndicator { BusyIndicator {
anchors.centerIn: parent anchors.centerIn: parent
visible: logsModelNg.busy visible: logsModel.busy
} }
delegate: ItemDelegate { delegate: ItemDelegate {
id: entryDelegate id: entryDelegate
width: parent.width width: parent.width
property NewLogEntry entry: logsModelNg.get(index) property NewLogEntry entry: logsModel.get(index)
property StateType stateType: entry && entry.values.hasOwnProperty("state") ? root.thing.thingClass.stateTypes.findByName(entry.values.state) : null property StateType stateType: entry && entry.source.indexOf("state-") == 0 ? root.thing.thingClass.stateTypes.findByName(entry.source.replace(/.*-.*-/, "")) : null
property EventType eventType: entry && entry.values.hasOwnProperty("event") ? root.thing.thingClass.eventTypes.findByName(entry.values.event) : null property EventType eventType: entry && entry.source.indexOf("event-") == 0 ? root.thing.thingClass.eventTypes.findByName(entry.source.replace(/.*-.*-/, "")) : null
property ActionType actionType: entry && entry.values.hasOwnProperty("action") ? root.thing.thingClass.actionTypes.findByName(entry.values.action) : null property ActionType actionType: entry && entry.source.indexOf("action-") == 0 ? root.thing.thingClass.actionTypes.findByName(entry.source.replace(/.*-.*-/, "")) : null
contentItem: RowLayout { contentItem: RowLayout {
ColorIcon { ColorIcon {
@ -280,7 +290,7 @@ Page {
when: entryDelegate.stateType != null when: entryDelegate.stateType != null
target: valueLoader.item; target: valueLoader.item;
property: "value"; property: "value";
value: entryDelegate.stateType ? Types.toUiValue(entry.values[entry.values.state], entryDelegate.stateType.unit) : "" value: entryDelegate.stateType ? Types.toUiValue(entry.values[entryDelegate.stateType.name], entryDelegate.stateType.unit) : ""
} }
Binding { Binding {
when: entryDelegate.stateType != null when: entryDelegate.stateType != null

View File

@ -37,9 +37,8 @@ import "../components"
Page { Page {
id: root id: root
property Thing thing: null property Thing thing: null
readonly property ThingClass thingClass: thing.thingClass
property bool showLogsButton: true property bool showLogsButton: false
property bool showDetailsButton: true property bool showDetailsButton: true
property bool showBrowserButton: true property bool showBrowserButton: true
property bool popStackOnBackButton: true property bool popStackOnBackButton: true
@ -59,7 +58,7 @@ Page {
HeaderButton { HeaderButton {
imageSource: "../images/folder.svg" imageSource: "../images/folder.svg"
visible: root.thingClass.browsable && root.showBrowserButton visible: root.thing.thingClass.browsable && root.showBrowserButton
onClicked: { onClicked: {
pageStack.push(Qt.resolvedUrl("DeviceBrowserPage.qml"), {thing: root.thing}) pageStack.push(Qt.resolvedUrl("DeviceBrowserPage.qml"), {thing: root.thing})
} }

View File

@ -71,7 +71,7 @@ ThingPageBase {
id: tempComponent id: tempComponent
StateChart { StateChart {
thing: root.thing thing: root.thing
stateType: root.thingClass.stateTypes.findByName("temperature") stateType: root.thing.thingClass.stateTypes.findByName("temperature")
color: app.interfaceToColor("temperaturesensor") color: app.interfaceToColor("temperaturesensor")
} }
} }
@ -82,7 +82,7 @@ ThingPageBase {
GenericTypeGraph { GenericTypeGraph {
Layout.fillWidth: true Layout.fillWidth: true
thing: root.thing thing: root.thing
stateType: root.thingClass.stateTypes.findByName("temperature") stateType: root.thing.thingClass.stateTypes.findByName("temperature")
iconSource: app.interfaceToIcon("temperaturesensor") iconSource: app.interfaceToIcon("temperaturesensor")
color: app.interfaceToColor("temperaturesensor") color: app.interfaceToColor("temperaturesensor")
} }
@ -102,7 +102,7 @@ ThingPageBase {
id: humidityComponent id: humidityComponent
StateChart { StateChart {
thing: root.thing thing: root.thing
stateType: root.thingClass.stateTypes.findByName("humidity") stateType: root.thing.thingClass.stateTypes.findByName("humidity")
color: app.interfaceToColor("humiditysensor") color: app.interfaceToColor("humiditysensor")
} }
} }
@ -112,7 +112,7 @@ ThingPageBase {
GenericTypeGraph { GenericTypeGraph {
Layout.fillWidth: true Layout.fillWidth: true
thing: root.thing thing: root.thing
stateType: root.thingClass.stateTypes.findByName("humidity") stateType: root.thing.thingClass.stateTypes.findByName("humidity")
iconSource: app.interfaceToIcon("humiditysensor") iconSource: app.interfaceToIcon("humiditysensor")
color: app.interfaceToColor("humiditysensor") color: app.interfaceToColor("humiditysensor")
} }
@ -132,7 +132,7 @@ ThingPageBase {
id: pressureComponent id: pressureComponent
StateChart { StateChart {
thing: root.thing thing: root.thing
stateType: root.thingClass.stateTypes.findByName("pressure") stateType: root.thing.thingClass.stateTypes.findByName("pressure")
color: app.interfaceToColor("pressuresensor") color: app.interfaceToColor("pressuresensor")
} }
} }
@ -142,7 +142,7 @@ ThingPageBase {
GenericTypeGraph { GenericTypeGraph {
Layout.fillWidth: true Layout.fillWidth: true
thing: root.thing thing: root.thing
stateType: root.thingClass.stateTypes.findByName("pressure") stateType: root.thing.thingClass.stateTypes.findByName("pressure")
iconSource: app.interfaceToIcon("pressuresensor") iconSource: app.interfaceToIcon("pressuresensor")
color: app.interfaceToColor("pressuresensor") color: app.interfaceToColor("pressuresensor")
} }
@ -162,7 +162,7 @@ ThingPageBase {
id: windSpeedComponent id: windSpeedComponent
StateChart { StateChart {
thing: root.thing thing: root.thing
stateType: root.thingClass.stateTypes.findByName("windSpeed") stateType: root.thing.thingClass.stateTypes.findByName("windSpeed")
color: app.interfaceToColor("windspeedsensor") color: app.interfaceToColor("windspeedsensor")
} }
} }
@ -172,7 +172,7 @@ ThingPageBase {
GenericTypeGraph { GenericTypeGraph {
Layout.fillWidth: true Layout.fillWidth: true
thing: root.thing thing: root.thing
stateType: root.thingClass.stateTypes.findByName("windSpeed") stateType: root.thing.thingClass.stateTypes.findByName("windSpeed")
iconSource: app.interfaceToIcon("windspeedsensor") iconSource: app.interfaceToIcon("windspeedsensor")
color: app.interfaceToColor("windspeedsensor") color: app.interfaceToColor("windspeedsensor")
} }

View File

@ -207,8 +207,7 @@ Page {
readonly property NewLogsModel logsModel: NewLogsModel { readonly property NewLogsModel logsModel: NewLogsModel {
objectName: "temp: " + thing.name objectName: "temp: " + thing.name
engine: _engine engine: _engine
source: "states-" + thing.id source: "state-" + thing.id + "-temperature"
filter: ({state: "temperature"})
startTime: new Date(d.startTime.getTime() - d.range * 60000) startTime: new Date(d.startTime.getTime() - d.range * 60000)
endTime: new Date(d.endTime.getTime() + d.range * 60000) endTime: new Date(d.endTime.getTime() + d.range * 60000)
sampleRate: d.sampleRate sampleRate: d.sampleRate
@ -258,8 +257,7 @@ Page {
readonly property NewLogsModel logsModel: NewLogsModel { readonly property NewLogsModel logsModel: NewLogsModel {
objectName: "temp: " + thing.name objectName: "temp: " + thing.name
engine: _engine engine: _engine
source: "states-" + thing.id source: "state-" + thing.id + "-temperature"
filter: ({state: "temperature"})
startTime: new Date(d.startTime.getTime() - d.range * 60000) startTime: new Date(d.startTime.getTime() - d.range * 60000)
endTime: new Date(d.endTime.getTime() + d.range * 60000) endTime: new Date(d.endTime.getTime() + d.range * 60000)
sampleRate: d.sampleRate sampleRate: d.sampleRate
@ -309,8 +307,7 @@ Page {
readonly property NewLogsModel logsModel: NewLogsModel { readonly property NewLogsModel logsModel: NewLogsModel {
objectName: "hum: " + thing.name objectName: "hum: " + thing.name
engine: _engine engine: _engine
source: "states-" + thing.id source: "state-" + thing.id + "-humidity"
filter: ({state: "humidity"})
startTime: new Date(d.startTime.getTime() - d.range * 60000) startTime: new Date(d.startTime.getTime() - d.range * 60000)
endTime: new Date(d.endTime.getTime() + d.range * 60000) endTime: new Date(d.endTime.getTime() + d.range * 60000)
sampleRate: d.sampleRate sampleRate: d.sampleRate
@ -344,38 +341,37 @@ Page {
Component.onDestruction: { Component.onDestruction: {
chartView.removeSeries(series) chartView.removeSeries(series)
} }
} }
} }
Repeater { Repeater {
id: vocRepeater id: vocRepeater
// model: zoneWrapper.indoorVocSensors model: zoneWrapper.indoorVocSensors
delegate: Item { delegate: Item {
id: vocDelegate id: vocDelegate
readonly property Thing thing: zoneWrapper.indoorVocSensors.get(index) readonly property Thing thing: zoneWrapper.indoorVocSensors.get(index)
property XYSeries series: null property XYSeries series: null
readonly property LogsModel logsModel: LogsModel { readonly property NewLogsModel logsModel: NewLogsModel {
objectName: "voc: " + thing.name objectName: "voc: " + thing.name
engine: typeIds.length > 0 ? _engine : null engine: _engine
thingId: thing.id source: "state-" + thing.id + "-voc"
sourceFilter: LogsModel.SourceStates startTime: new Date(d.startTime.getTime() - d.range * 60000)
live: true endTime: new Date(d.endTime.getTime() + d.range * 60000)
// graphSeries: series sampleRate: d.sampleRate
viewStartTime: new Date(d.startTime.getTime() - d.range * 60000) onEntriesAdded: {
fetchBlockSize: 500 for (var i = 0; i < entries.length; i++) {
var entry = entries[i]
typeIds: { var value = entry.values["voc"]
var ret = []; if (value == null) {
ret.push(thing.thingClass.stateTypes.findByName("voc").id) value = 0;
return ret; }
series.insert(index + i, entry.timestamp, value)
}
} }
} onEntriesRemoved: {
series.removePoints(index, count)
XYSeriesAdapter { }
logsModel: vocDelegate.logsModel Component.onCompleted: fetchLogs()
xySeries: series
sampleRate: XYSeriesAdapter.SampleRate10Minutes
} }
Component.onCompleted: { Component.onCompleted: {
@ -411,18 +407,13 @@ Page {
XYPoint {x: dateTimeAxis.max.getTime(); y: 0} XYPoint {x: dateTimeAxis.max.getTime(); y: 0}
} }
readonly property LogsModel logsModel: LogsModel { readonly property NewLogsModel logsModel: NewLogsModel {
id: logsModelNg id: logsModelNg
engine: typeIds.length ? _engine : null engine: typeIds.length ? _engine : null
thingId: thing ? thing.id : "" source: "state-" + thing.id + "-closed"
typeIds: { startTime: new Date(d.startTime.getTime() - d.range * 60000)
var ret = []; endTime: new Date(d.endTime.getTime() + d.range * 60000)
ret.push(thing.thingClass.stateTypes.findByName("closed").id) sampleRate: d.sampleRate
return ret;
}
sourceFilter: LogsModel.SourceStates
live: true
viewStartTime: new Date(d.startTime.getTime() - d.range * 60000)
} }
BoolSeriesAdapter { BoolSeriesAdapter {
@ -675,8 +666,9 @@ Page {
delegate: TooltipDelegate { delegate: TooltipDelegate {
visible: (mouseArea.containsMouse || mouseArea.tooltipping) && !mouseArea.dragging visible: (mouseArea.containsMouse || mouseArea.tooltipping) && !mouseArea.dragging
thing: thermostatsRepeater.itemAt(index).thing thing: thermostatsRepeater.itemAt(index).thing
entry: thermostatsRepeater.itemAt(index).logsModel.findClosest(tooltips.timestamp) entry: thermostatsRepeater.itemAt(index).logsModel.find(tooltips.timestamp)
color: app.interfaceToColor("temperaturesensor") color: app.interfaceToColor("temperaturesensor")
valueName: "temperature"
axis: temperatureAxis axis: temperatureAxis
x: tooltips.tooltipX x: tooltips.tooltipX
width: tooltips.tooltipWidth width: tooltips.tooltipWidth
@ -693,7 +685,8 @@ Page {
delegate: TooltipDelegate { delegate: TooltipDelegate {
visible: (mouseArea.containsMouse || mouseArea.tooltipping) && !mouseArea.dragging visible: (mouseArea.containsMouse || mouseArea.tooltipping) && !mouseArea.dragging
thing: tempRepeater.itemAt(index).thing thing: tempRepeater.itemAt(index).thing
entry: tempRepeater.itemAt(index).logsModel.findClosest(tooltips.timestamp) entry: tempRepeater.itemAt(index).logsModel.find(tooltips.timestamp)
valueName: "temperature"
color: app.interfaceToColor("temperaturesensor") color: app.interfaceToColor("temperaturesensor")
axis: temperatureAxis axis: temperatureAxis
x: tooltips.tooltipX x: tooltips.tooltipX
@ -711,8 +704,9 @@ Page {
delegate: TooltipDelegate { delegate: TooltipDelegate {
visible: (mouseArea.containsMouse || mouseArea.tooltipping) && !mouseArea.dragging visible: (mouseArea.containsMouse || mouseArea.tooltipping) && !mouseArea.dragging
thing: humidityRepeater.itemAt(index).thing thing: humidityRepeater.itemAt(index).thing
entry: humidityRepeater.itemAt(index).logsModel.findClosest(tooltips.timestamp) entry: humidityRepeater.itemAt(index).logsModel.find(tooltips.timestamp)
color: app.interfaceToColor("humiditysensor") color: app.interfaceToColor("humiditysensor")
valueName: "humidity"
axis: humidityAxis axis: humidityAxis
x: tooltips.tooltipX x: tooltips.tooltipX
width: tooltips.tooltipWidth width: tooltips.tooltipWidth
@ -729,7 +723,8 @@ Page {
delegate: TooltipDelegate { delegate: TooltipDelegate {
visible: (mouseArea.containsMouse || mouseArea.tooltipping) && !mouseArea.dragging visible: (mouseArea.containsMouse || mouseArea.tooltipping) && !mouseArea.dragging
thing: vocRepeater.itemAt(index).thing thing: vocRepeater.itemAt(index).thing
entry: vocRepeater.itemAt(index).logsModel.findClosest(tooltips.timestamp) entry: vocRepeater.itemAt(index).logsModel.find(tooltips.timestamp)
valueName: "voc"
color: app.interfaceToColor("vocsensor") color: app.interfaceToColor("vocsensor")
axis: vocAxis axis: vocAxis
x: tooltips.tooltipX x: tooltips.tooltipX

View File

@ -8,16 +8,19 @@ import Nymea.AirConditioning 1.0
import QtCharts 2.3 import QtCharts 2.3
NymeaToolTip { NymeaToolTip {
id: root
width: layout.implicitWidth + Style.smallMargins * 2 width: layout.implicitWidth + Style.smallMargins * 2
height: layout.implicitHeight + Style.smallMargins * 2 height: layout.implicitHeight + Style.smallMargins * 2
property Thing thing: null property Thing thing: null
property LogEntry entry: null property NewLogEntry entry: null
property string valueName: ""
property alias color: rect.color property alias color: rect.color
property ValueAxis axis: null property ValueAxis axis: null
property int unit: Types.UnitNone property int unit: Types.UnitNone
readonly property int realY: entry ? Math.min(Math.max(mouseArea.height - (entry.value * mouseArea.height / axis.max) - height / 2 /*- Style.margins*/, 0), mouseArea.height - height) : 0 readonly property var value: entry.values[valueName]
readonly property int realY: entry ? Math.min(Math.max(mouseArea.height - (root.value * mouseArea.height / axis.max) - height / 2 /*- Style.margins*/, 0), mouseArea.height - height) : 0
property int fixedY: 0 property int fixedY: 0
y: fixedY // Animated y: fixedY // Animated
@ -32,7 +35,7 @@ NymeaToolTip {
height: width height: width
} }
Label { Label {
text: "%1: %2%3".arg(thing.name).arg(entry ? round(Types.toUiValue(entry.value, unit)) : "-").arg(Types.toUiUnit(unit)) text: "%1: %2%3".arg(thing.name).arg(entry ? round(Types.toUiValue(root.value, unit)) : "-").arg(Types.toUiUnit(unit))
Layout.fillWidth: true Layout.fillWidth: true
font: Style.extraSmallFont font: Style.extraSmallFont
elide: Text.ElideMiddle elide: Text.ElideMiddle

View File

@ -37,7 +37,7 @@ Item {
} }
onEntriesRemoved: { onEntriesRemoved: {
consumptionUpperSeries.removePoints(index, count) consumptionUpperSeries.removePoints(index, Math.min(count, consumptionUpperSeries.count))
zeroSeries.shrink() zeroSeries.shrink()
} }
} }
@ -423,8 +423,12 @@ Item {
// Remove the leading 0-value entry // Remove the leading 0-value entry
lowerSeries.removePoints(0, 1); if (lowerSeries.count > 0) {
upperSeries.removePoints(0, 1); lowerSeries.removePoints(0, 1);
}
if (upperSeries.count > 0) {
upperSeries.removePoints(0, 1);
}
@ -472,12 +476,25 @@ Item {
} }
onEntriesRemoved: { onEntriesRemoved: {
// Remove the leading 0-value entry // Note QtCharts crash when calling removePoints() for points that don't exist.
consumerDelegate.lowerSeries.removePoints(0, 1); // Additionally it may decide to ignore values we add, e.g. if we try to add an Inf or undefined value for whatever reason
consumerDelegate.upperSeries.removePoints(0, 1); // So, even though in theory the series should always 1:1 reflect the model, it may not do so in practice and we'll have to make sure not crash here
consumerDelegate.lowerSeries.removePoints(index, count) // Remove the leading 0-value entry
consumerDelegate.upperSeries.removePoints(index, count) if (consumerDelegate.lowerSeries.count > 0) {
consumerDelegate.lowerSeries.removePoints(0, 1);
}
if (consumerDelegate.upperSeries.count > 0) {
consumerDelegate.upperSeries.removePoints(0, 1);
}
print("removing:", index, count, "from", consumerDelegate.lowerSeries.count, consumerDelegate.upperSeries.count)
if (consumerDelegate.lowerSeries.count >= index + count) {
consumerDelegate.lowerSeries.removePoints(index, count)
}
if (consumerDelegate.upperSeries.count >= index + count) {
consumerDelegate.upperSeries.removePoints(index, count)
}
// Add the leading 0-value entry back // Add the leading 0-value entry back
consumerDelegate.lowerSeries.insert(0, consumerDelegate.series.upperSeries.at(0).x, 0) consumerDelegate.lowerSeries.insert(0, consumerDelegate.series.upperSeries.at(0).x, 0)

View File

@ -123,13 +123,17 @@ Item {
} }
onEntriesRemoved: { onEntriesRemoved: {
acquisitionUpperSeries.removePoints(index, count) // Note QtCharts crash when calling removePoints() for points that don't exist.
returnUpperSeries.removePoints(index, count) // Additionally it may decide to ignore values we add, e.g. if we try to add an Inf or undefined value for whatever reason
fromStorageUpperSeries.removePoints(index, count) // So, even though in theory the series should always 1:1 reflect the model, it may not do so in practice and we'll have to make sure not crash here
toStorageUpperSeries.removePoints(index, count)
selfProductionConsumptionUpperSeries.removePoints(index, count) acquisitionUpperSeries.removePoints(index, Math.min(count, acquisitionUpperSeries.count - index))
productionSeries.removePoints(index, count) returnUpperSeries.removePoints(index, Math.min(count, returnUpperSeries.count - index))
// consumptionSeries.removePoints(index, count) fromStorageUpperSeries.removePoints(index, Math.min(count, fromStorageUpperSeries.count - index))
toStorageUpperSeries.removePoints(index, Math.min(count, toStorageUpperSeries.count -index))
selfProductionConsumptionUpperSeries.removePoints(index, Math.min(count, selfProductionConsumptionUpperSeries.count - index))
productionSeries.removePoints(index, Math.min(count, productionSeries.count - index))
// consumptionSeries.removePoints(index, Math.min(count, consumptionSeries.count - index))
zeroSeries.shrink() zeroSeries.shrink()
} }
} }

View File

@ -54,7 +54,7 @@ Item {
} else if (interfaceList.indexOf("awning") >= 0) { } else if (interfaceList.indexOf("awning") >= 0) {
page = "AwningThingPage.qml"; page = "AwningThingPage.qml";
} else if (interfaceList.indexOf("notifications") >= 0) { } else if (interfaceList.indexOf("notifications") >= 0) {
page = "NotificationsDevicePage.qml"; page = "NotificationsThingPage.qml";
} else if (interfaceList.indexOf("fingerprintreader") >= 0) { } else if (interfaceList.indexOf("fingerprintreader") >= 0) {
page = "FingerprintReaderDevicePage.qml"; page = "FingerprintReaderDevicePage.qml";
} else if (interfaceList.indexOf("evcharger") >= 0) { } else if (interfaceList.indexOf("evcharger") >= 0) {