Merge PR #1010: More work on the new logs
This commit is contained in:
commit
f687f97e5a
@ -58,7 +58,7 @@ void NewLogsModel::componentComplete()
|
||||
bool NewLogsModel::canFetchMore(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent)
|
||||
return m_canFetchMore;
|
||||
return m_canFetchMore && m_sources.count() == 1;
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
return m_busy;
|
||||
@ -217,30 +230,30 @@ NewLogEntry *NewLogsModel::find(const QDateTime ×tamp) const
|
||||
qint64 diff = timestamp.msecsTo(entry->timestamp());
|
||||
if (entry->timestamp() > timestamp) {
|
||||
// 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.";
|
||||
return entry;
|
||||
}
|
||||
NewLogEntry *previousEntry = m_list.at(idx+1);
|
||||
NewLogEntry *previousEntry = m_list.at(idx-1);
|
||||
if (previousEntry->timestamp() < 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");
|
||||
return qAbs(previousDiff) < qAbs(diff) ? previousEntry : entry;
|
||||
}
|
||||
idx += jump;
|
||||
idx -= jump;
|
||||
} else if (entry->timestamp() < timestamp) {
|
||||
// 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.";
|
||||
return entry;
|
||||
}
|
||||
NewLogEntry *nextEntry = m_list.at(idx-1);
|
||||
NewLogEntry *nextEntry = m_list.at(idx+1);
|
||||
if (nextEntry->timestamp() > 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");
|
||||
return qAbs(nextDiff) < qAbs(diff) ? nextEntry : entry;
|
||||
}
|
||||
idx -= jump;
|
||||
idx += jump;
|
||||
}
|
||||
jump = qMax(1, jump / 2);
|
||||
};
|
||||
@ -269,57 +282,52 @@ void NewLogsModel::fetchLogs()
|
||||
{"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();
|
||||
QDateTime newestExisting = m_list.count() > 0 ? m_list.first()->timestamp() : QDateTime();
|
||||
qCDebug(dcLogEngine()) << "request timeframe: " << m_startTime.toString() << " - " << m_endTime.toString();
|
||||
qCDebug(dcLogEngine()) << "existing timeframe:" << oldestExisting.toString() << "- " << newestExisting.toString();
|
||||
if (m_sampleRate == SampleRateAny) { // Discrete logs
|
||||
|
||||
if (!m_startTime.isNull() && !m_endTime.isNull()) { // Either specific time frame
|
||||
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 {
|
||||
|
||||
if (m_startTime < oldestExisting) {
|
||||
startTime = m_startTime;
|
||||
endTime = qMin(QDateTime::currentDateTime(), qMin(m_endTime, oldestExisting));
|
||||
} else if (newestExisting < m_endTime) {
|
||||
startTime = qMax(m_startTime, newestExisting);
|
||||
endTime = qMin(QDateTime::currentDateTime(), m_endTime);
|
||||
} else {
|
||||
// Nothing to do...
|
||||
return;
|
||||
params.insert("limit", m_blockSize);
|
||||
if (m_list.count() > 0) {
|
||||
params.insert("offset", m_lastOffset);
|
||||
if (m_lastOffset == 0) {
|
||||
if (m_endTime.isNull()) {
|
||||
m_currentNewest = QDateTime::currentDateTime();
|
||||
} else {
|
||||
m_currentNewest = m_endTime;
|
||||
}
|
||||
}
|
||||
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 {
|
||||
params.insert("limit", m_blockSize);
|
||||
if (m_list.count() > 0) {
|
||||
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_list.first()->timestamp().toMSecsSinceEpoch());
|
||||
if (!m_startTime.isNull() && !m_endTime.isNull()) {
|
||||
params.insert("startTime", m_startTime.toMSecsSinceEpoch());
|
||||
params.insert("endTime", m_endTime.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()) {
|
||||
// params.insert("startTime", m_startTime.toMSecsSinceEpoch());
|
||||
// }
|
||||
// if (!m_endTime.isNull()) {
|
||||
// params.insert("endTime", m_endTime.toMSecsSinceEpoch());
|
||||
// }
|
||||
QMetaEnum sortOrderEnum = QMetaEnum::fromType<Qt::SortOrder>();
|
||||
params.insert("sortOrder", sortOrderEnum.valueToKey(m_sortOrder));
|
||||
|
||||
qCDebug(dcLogEngine()) << "Fetching logs:" << QJsonDocument::fromVariant(params).toJson();
|
||||
m_engine->jsonRpcClient()->sendCommand("Logging.GetLogEntries", params, this, "logsReply");
|
||||
}
|
||||
|
||||
void NewLogsModel::logsReply(int commandId, const QVariantMap &data)
|
||||
{
|
||||
Q_UNUSED(commandId)
|
||||
|
||||
QList<NewLogEntry*> entries;
|
||||
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();
|
||||
NewLogEntry *entry = new NewLogEntry(source, timestamp, values, this);
|
||||
entries.append(entry);
|
||||
|
||||
}
|
||||
|
||||
m_canFetchMore = entries.count() >= m_blockSize;
|
||||
qCDebug(dcLogEngine()) << "Logs received:" << entries.count() << "Requested:" << m_blockSize;
|
||||
qCDebug(dcLogEngine()) << "Logs received:" << entries.count();
|
||||
|
||||
if (!entries.isEmpty()) {
|
||||
qCDebug(dcLogEngine()) << "Logs received:" << entries.first()->timestamp().toString() << " - " << entries.last()->timestamp().toString();
|
||||
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);
|
||||
if (entries.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
} else if (entries.last()->timestamp() == m_list.last()->timestamp()) {
|
||||
qCDebug(dcLogEngine()) << "First item of new list already existing... no new data...";
|
||||
qDeleteAll(entries);
|
||||
if (!m_startTime.isNull() && !m_endTime.isNull()) {
|
||||
beginResetModel();
|
||||
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()) {
|
||||
if (entries.first()->timestamp() == m_list.last()->timestamp()) {
|
||||
qCDebug(dcLogEngine()) << "Appending received items";
|
||||
beginRemoveRows(QModelIndex(), m_list.count() - 1, m_list.count() - 1);
|
||||
m_list.takeLast()->deleteLater();
|
||||
endRemoveRows();
|
||||
emit entriesRemoved(m_list.count(), 1);
|
||||
beginInsertRows(QModelIndex(), 0, entries.count() - 1);
|
||||
m_list = entries;
|
||||
endInsertRows();
|
||||
emit entriesAdded(0, entries);
|
||||
|
||||
int insertIdx = m_list.count();
|
||||
beginInsertRows(QModelIndex(), insertIdx, insertIdx + entries.count() - 1);
|
||||
m_list = m_list + entries;
|
||||
endInsertRows();
|
||||
emit entriesAdded(insertIdx, entries);
|
||||
} else {
|
||||
// Start of fetched entries does not line up with end of existing entries. Discarding existing entries
|
||||
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();
|
||||
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);
|
||||
}
|
||||
} else {
|
||||
beginInsertRows(QModelIndex(), m_list.count(), m_list.count() + entries.count() - 1);
|
||||
qSort(entries.begin(), entries.end(), [](NewLogEntry *left, NewLogEntry *right){
|
||||
return left->timestamp() > right->timestamp();
|
||||
});
|
||||
m_list.append(entries);
|
||||
endInsertRows();
|
||||
emit entriesAdded(m_list.count(), entries);
|
||||
}
|
||||
|
||||
emit countChanged();
|
||||
|
||||
@ -19,6 +19,7 @@ class NewLogsModel : public QAbstractListModel, public QQmlParserStatus
|
||||
Q_PROPERTY(QDateTime startTime READ startTime WRITE setStartTime NOTIFY startTimeChanged)
|
||||
Q_PROPERTY(QDateTime endTime READ endTime WRITE setEndTime NOTIFY endTimeChanged)
|
||||
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(bool busy READ busy NOTIFY busyChanged)
|
||||
@ -79,6 +80,9 @@ public:
|
||||
SampleRate sampleRate() const;
|
||||
void setSampleRate(SampleRate sampleRate);
|
||||
|
||||
Qt::SortOrder sortOrder() const;
|
||||
void setSortOrder(Qt::SortOrder sortOrder);
|
||||
|
||||
bool busy() const;
|
||||
|
||||
Q_INVOKABLE NewLogEntry *get(int index) const;
|
||||
@ -101,6 +105,7 @@ signals:
|
||||
void startTimeChanged();
|
||||
void endTimeChanged();
|
||||
void sampleRateChanged();
|
||||
void sortOrderChanged();
|
||||
|
||||
void entriesAdded(int index, const QList<NewLogEntry*> &entries);
|
||||
void entriesRemoved(int index, int count);
|
||||
@ -113,14 +118,21 @@ private:
|
||||
QStringList m_sources;
|
||||
QStringList m_columns;
|
||||
QVariantMap m_filter;
|
||||
Qt::SortOrder m_sortOrder = Qt::AscendingOrder;
|
||||
|
||||
bool m_busy = false;
|
||||
|
||||
// For time based sampling
|
||||
QDateTime m_startTime;
|
||||
QDateTime m_endTime;
|
||||
SampleRate m_sampleRate = SampleRateAny;
|
||||
|
||||
// For continuous scrolling lists
|
||||
bool m_completed = false;
|
||||
bool m_canFetchMore = true;
|
||||
bool m_busy = false;
|
||||
int m_blockSize = 30;
|
||||
int m_blockSize = 5;
|
||||
int m_lastOffset = 0;
|
||||
QDateTime m_currentNewest;
|
||||
|
||||
QList<NewLogEntry*> m_list;
|
||||
};
|
||||
|
||||
@ -643,6 +643,11 @@ int ThingManager::executeBrowserItemAction(const QUuid &thingId, const QString &
|
||||
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)
|
||||
{
|
||||
QVariantMap data;
|
||||
@ -694,6 +699,12 @@ void ThingManager::disconnectIOResponse(int commandId, const QVariantMap ¶ms
|
||||
qDebug() << "DisconnectIO response" << commandId << qUtf8Printable(QJsonDocument::fromVariant(params).toJson());
|
||||
}
|
||||
|
||||
void ThingManager::setStateLoggingResponse(int commandId, const QVariantMap ¶ms)
|
||||
{
|
||||
Q_UNUSED(commandId)
|
||||
qCDebug(dcThingManager()) << "Set state logging response" << qUtf8Printable(QJsonDocument::fromVariant(params).toJson());
|
||||
}
|
||||
|
||||
Vendor *ThingManager::unpackVendor(const QVariantMap &vendorMap)
|
||||
{
|
||||
Vendor *v = new Vendor(vendorMap.value("id").toString(), vendorMap.value("name").toString());
|
||||
|
||||
@ -98,6 +98,7 @@ public:
|
||||
Q_INVOKABLE BrowserItem* browserItem(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 ¶ms = 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 disconnectIO(const QUuid &ioConnectionId);
|
||||
@ -123,6 +124,7 @@ private:
|
||||
Q_INVOKABLE void getIOConnectionsResponse(int commandId, const QVariantMap ¶ms);
|
||||
Q_INVOKABLE void connectIOResponse(int commandId, const QVariantMap ¶ms);
|
||||
Q_INVOKABLE void disconnectIOResponse(int commandId, const QVariantMap ¶ms);
|
||||
Q_INVOKABLE void setStateLoggingResponse(int commandId, const QVariantMap ¶ms);
|
||||
|
||||
public slots:
|
||||
ThingGroup* createGroup(Interface *interface, ThingsProxy *things);
|
||||
|
||||
@ -50,7 +50,6 @@
|
||||
<file>ui/devicepages/ShutterDevicePage.qml</file>
|
||||
<file>ui/devicepages/GarageThingPage.qml</file>
|
||||
<file>ui/devicepages/AwningThingPage.qml</file>
|
||||
<file>ui/devicepages/NotificationsDevicePage.qml</file>
|
||||
<file>ui/devicepages/LightThingPage.qml</file>
|
||||
<file>ui/devicepages/FingerprintReaderDevicePage.qml</file>
|
||||
<file>ui/devicepages/DeviceLogPage.qml</file>
|
||||
@ -312,5 +311,6 @@
|
||||
<file>ui/mainviews/airconditioning/LegendDelegate.qml</file>
|
||||
<file>ui/customviews/StateChart.qml</file>
|
||||
<file>ui/devicepages/ThingLogPage.qml</file>
|
||||
<file>ui/devicepages/NotificationsThingPage.qml</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
@ -55,10 +55,8 @@ Item {
|
||||
id: logsModel
|
||||
engine: root.thing && root.stateType ? _engine : null
|
||||
source: root.thing ? "state-" + thing.id + "-" + root.stateType.name : ""
|
||||
// columns: [root.stateType.name]
|
||||
// filter: root.stateType ? ({state: root.stateType.name}) : ({})
|
||||
startTime: new Date(d.startTime.getTime() - d.range * 60000)
|
||||
endTime: new Date(d.endTime.getTime() + d.range * 60000)
|
||||
startTime: new Date(d.startTime.getTime() - d.range * 1.1 * 60000)
|
||||
endTime: new Date(d.endTime.getTime() + d.range * 1.1 * 60000)
|
||||
sampleRate: d.sampleRate
|
||||
|
||||
property double minValue
|
||||
@ -66,11 +64,11 @@ Item {
|
||||
|
||||
onEntriesAdded: {
|
||||
print("**** entries added", index, entries.length, "entries in series:", valueSeries.count, "in model", logsModel.count)
|
||||
if (valueSeries.count == 0) {
|
||||
print("adding zero item", new Date())
|
||||
valueSeries.insert(0, new Date(), 0)
|
||||
zeroSeries.ensureValue(new Date())
|
||||
}
|
||||
// if (valueSeries.count == 0) {
|
||||
// print("adding zero item", new Date())
|
||||
// valueSeries.insert(0, new Date(), 0)
|
||||
// zeroSeries.ensureValue(new Date())
|
||||
// }
|
||||
|
||||
for (var i = 0; i < entries.length; i++) {
|
||||
var entry = entries[i]
|
||||
@ -83,17 +81,17 @@ Item {
|
||||
value = false;
|
||||
}
|
||||
// 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 + 1
|
||||
valueSeries.insert(insertIdx, entry.timestamp, value)
|
||||
valueSeries.insert(insertIdx + 1, entry.timestamp.getTime() - 500, !value)
|
||||
var insertIdx = (index + i) * 2
|
||||
valueSeries.insert(insertIdx, entry.timestamp.getTime() - 500, !value)
|
||||
valueSeries.insert(insertIdx+1, 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))
|
||||
}
|
||||
|
||||
// valueSeries.removePoints(0, 1);
|
||||
// if (insertIdx == 0) {
|
||||
// // first index, we'll have to update the "now" value
|
||||
// valueSeries.insert(0, entry.timestamp.getTime() + 2000, value)
|
||||
// zeroSeries.ensureValue(new Date(entry.timestamp.getTime() + 2000))
|
||||
// }
|
||||
|
||||
} else {
|
||||
var value = entry.values[root.stateType.name]
|
||||
@ -104,25 +102,27 @@ Item {
|
||||
minValue = minValue == undefined ? value : Math.min(minValue, value)
|
||||
maxValue = maxValue == undefined ? value : Math.max(maxValue, value)
|
||||
|
||||
var insertIdx = (index + i) + 1
|
||||
var insertIdx = index + i
|
||||
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)
|
||||
}
|
||||
onEntriesRemoved: {
|
||||
print("removing:", index, count, valueSeries.count)
|
||||
if (root.stateType.type.toLowerCase() == "bool") {
|
||||
valueSeries.removePoints((index * 2) + 1, count * 2)
|
||||
valueSeries.removePoints((index * 2) /*+ 1*/, count * 2)
|
||||
} else {
|
||||
valueSeries.removePoints(index + 1, count)
|
||||
valueSeries.removePoints(index /*+ 1*/, count)
|
||||
}
|
||||
|
||||
zeroSeries.shrink()
|
||||
@ -291,24 +291,24 @@ Item {
|
||||
borderWidth: 2
|
||||
lowerSeries: LineSeries {
|
||||
id: zeroSeries
|
||||
XYPoint { x: dateTimeAxis.max.getTime(); y: 0 }
|
||||
XYPoint { x: dateTimeAxis.min.getTime(); y: 0 }
|
||||
XYPoint { x: dateTimeAxis.max.getTime(); y: 0 }
|
||||
function ensureValue(timestamp) {
|
||||
if (count == 0) {
|
||||
append(timestamp, 0)
|
||||
} else if (count == 1) {
|
||||
if (timestamp.getTime() < at(0).x) {
|
||||
append(timestamp, 0)
|
||||
} else {
|
||||
insert(0, timestamp, 0)
|
||||
} else {
|
||||
append(timestamp, 0)
|
||||
}
|
||||
} else {
|
||||
if (timestamp.getTime() < at(1).x) {
|
||||
remove(1)
|
||||
append(timestamp, 0)
|
||||
} else if (timestamp.getTime() > at(0).x) {
|
||||
if (timestamp.getTime() < at(0).x) {
|
||||
remove(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)
|
||||
|
||||
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
|
||||
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 xOnLeft: Math.min(entryX, mouseArea.width) - Style.smallMargins - width
|
||||
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)
|
||||
|
||||
width: tooltipLayout.implicitWidth + Style.smallMargins * 2
|
||||
@ -497,16 +497,11 @@ Item {
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
property double value: toolTip.entry
|
||||
? (toolTip.entry.acquisition >= 0 ? toolTip.entry.consumption : Math.max(0, -toolTip.entry.production))
|
||||
: 0
|
||||
property bool translate: value >= 1000
|
||||
property double translatedValue: value / (translate ? 1000 : 1)
|
||||
text: toolTip.entry == null
|
||||
? ""
|
||||
text: toolTip.value === null
|
||||
? qsTr("No data")
|
||||
: root.stateType.type.toLowerCase() == "bool"
|
||||
? 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
|
||||
}
|
||||
|
||||
|
||||
@ -44,54 +44,161 @@ ThingPageBase {
|
||||
|
||||
readonly property State powerState: thing ? thing.stateByName("power") : null
|
||||
|
||||
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"
|
||||
}
|
||||
|
||||
GenericTypeLogView {
|
||||
id: logView
|
||||
Loader {
|
||||
anchors.fill: parent
|
||||
visible: !root.isVirtual
|
||||
|
||||
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;
|
||||
sourceComponent: {
|
||||
if (engine.jsonRpcClient.ensureServerVersion("8.0")) {
|
||||
return logViewComponent
|
||||
} else {
|
||||
return logViewComponentPre80
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
Component {
|
||||
id: logViewComponent
|
||||
|
||||
ListView {
|
||||
id: logView
|
||||
anchors.fill: parent
|
||||
ScrollBar.vertical: ScrollBar {}
|
||||
|
||||
model: NewLogsModel {
|
||||
id: logsModel
|
||||
engine: _engine
|
||||
sources: ["event-" + root.thing.id + "-pressed", "event-" + root.thing.id + "-longPressed"]
|
||||
// live: true
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -80,8 +80,11 @@ ThingPageBase {
|
||||
Layout.fillWidth: true
|
||||
topPadding: model.type === ThingModel.TypeActionType ? app.margins / 2 : 0
|
||||
bottomPadding: 0
|
||||
|
||||
contentItem: Loader {
|
||||
id: inlineLoader
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Style.smallDelegateHeight
|
||||
sourceComponent: {
|
||||
switch (model.type) {
|
||||
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)
|
||||
swipe.right: RowLayout {
|
||||
height: delegate.height
|
||||
|
||||
@ -43,13 +43,13 @@ ThingPageBase {
|
||||
readonly property State powerState: thing.stateByName("power")
|
||||
|
||||
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 StateType ctStateType: thingClass.stateTypes.findByName("colorTemperature")
|
||||
readonly property StateType ctStateType: thing.thingClass.stateTypes.findByName("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) +
|
||||
(brightnessState !== null ? 1 : 0) +
|
||||
|
||||
@ -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 {
|
||||
id: logView
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
clip: true
|
||||
|
||||
BusyIndicator {
|
||||
anchors.centerIn: parent
|
||||
visible: logsModel.busy
|
||||
running: visible
|
||||
}
|
||||
|
||||
model: LogsModel {
|
||||
id: logsModel
|
||||
thingId: root.thing.id
|
||||
@ -206,7 +227,7 @@ ThingPageBase {
|
||||
{
|
||||
timestamp: model.timestamp,
|
||||
notificationTitle: itemDelegate.title,
|
||||
notificationBody: itemDelegate.text,
|
||||
notificationBody: itemDelegate.tet,
|
||||
errorCode: model.errorCode
|
||||
});
|
||||
popup.open();
|
||||
@ -225,10 +246,98 @@ ThingPageBase {
|
||||
}
|
||||
}
|
||||
|
||||
BusyIndicator {
|
||||
anchors.centerIn: parent
|
||||
visible: logsModel.busy
|
||||
running: visible
|
||||
Component {
|
||||
id: logViewComponent
|
||||
ListView {
|
||||
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 {
|
||||
@ -248,7 +357,7 @@ ThingPageBase {
|
||||
|
||||
Label {
|
||||
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
|
||||
}
|
||||
Label {
|
||||
@ -266,7 +375,7 @@ ThingPageBase {
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.topMargin: app.margins
|
||||
Layout.topMargin: Style.margins
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Title")
|
||||
font.bold: true
|
||||
@ -73,7 +73,7 @@ ThingPageBase {
|
||||
Component.onCompleted: {
|
||||
var supportedInterfaces = Object.keys(interfaceStateMap)
|
||||
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]});
|
||||
}
|
||||
}
|
||||
@ -138,7 +138,7 @@ ThingPageBase {
|
||||
Layout.fillWidth: true
|
||||
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 string interfaceName: modelData
|
||||
|
||||
|
||||
@ -41,9 +41,9 @@ ThingPageBase {
|
||||
|
||||
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 isProducer: root.thing && root.thingClass.interfaces.indexOf("smartmeterproducer") >= 0
|
||||
readonly property bool isBattery: root.thing && root.thingClass.interfaces.indexOf("energystorage") >= 0
|
||||
readonly property bool isEvCharger: itemDelegate.thing.thingClass.interfaces.indexOf("evcharger") >= 0
|
||||
readonly property bool isProducer: root.thing && root.thing.thingClass.interfaces.indexOf("smartmeterproducer") >= 0
|
||||
readonly property bool isBattery: root.thing && root.thing.thingClass.interfaces.indexOf("energystorage") >= 0
|
||||
readonly property bool isEvCharger: root.thing && root.thing.thingClass.interfaces.indexOf("evcharger") >= 0
|
||||
|
||||
|
||||
readonly property State currentPowerState: root.thing.stateByName("currentPower")
|
||||
|
||||
@ -58,14 +58,30 @@ Page {
|
||||
header: NymeaHeader {
|
||||
text: qsTr("History for %1").arg(root.stateType.displayName)
|
||||
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 {
|
||||
id: logsModel
|
||||
engine: _engine
|
||||
columns: [root.stateType.name]
|
||||
source: "states-" + root.thing.id
|
||||
filter: ({state: root.stateType.name})
|
||||
source: "state-" + root.thing.id + "-" + root.stateType.name
|
||||
sortOrder: Qt.DescendingOrder
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
@ -75,13 +91,21 @@ Page {
|
||||
GridLayout {
|
||||
anchors.fill: parent
|
||||
columns: app.landscape ? 2 : 1
|
||||
visible: root.isLogged
|
||||
|
||||
StateChart {
|
||||
Loader {
|
||||
Layout.fillWidth: true
|
||||
thing: root.thing
|
||||
stateType: root.stateType
|
||||
active: root.canShowGraph
|
||||
|
||||
sourceComponent: Component {
|
||||
StateChart {
|
||||
thing: root.thing
|
||||
stateType: root.stateType
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ListView {
|
||||
id: listView
|
||||
Layout.fillWidth: true
|
||||
@ -100,61 +124,6 @@ Page {
|
||||
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 {
|
||||
@ -166,9 +135,9 @@ Page {
|
||||
buttonText: qsTr("Enable logging")
|
||||
visible: !root.isLogged
|
||||
onButtonClicked: {
|
||||
|
||||
print("enabming logging")
|
||||
engine.thingManager.setStateLogging(root.thing.id, root.stateType.id, true)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -44,7 +44,7 @@ ThingPageBase {
|
||||
|
||||
readonly property StateType targetTemperatureStateType: thing.thingClass.stateTypes.findByName("targetTemperature")
|
||||
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 StateType temperatureStateType: thing.thingClass.stateTypes.findByName("temperature")
|
||||
readonly property State temperatureState: temperatureStateType ? thing.states.getState(temperatureStateType.id) : null
|
||||
|
||||
@ -47,41 +47,51 @@ Page {
|
||||
|
||||
HeaderButton {
|
||||
imageSource: "../images/filters.svg"
|
||||
color: logsModelNg.filterEnabled ? Style.accentColor : Style.iconColor
|
||||
onClicked: logsModelNg.filterEnabled = !logsModelNg.filterEnabled
|
||||
color: logsModel.filterEnabled ? Style.accentColor : Style.iconColor
|
||||
onClicked: logsModel.filterEnabled = !logsModel.filterEnabled
|
||||
visible: root.filterTypeIds.length === 0
|
||||
}
|
||||
}
|
||||
|
||||
NewLogsModel {
|
||||
id: logsModelNg
|
||||
id: logsModel
|
||||
engine: _engine
|
||||
columns: [root.stateType.name]
|
||||
sources: ["states-" + root.thing.id, "events-" + root.thing.id, "actions-" + root.thing.id]
|
||||
filter: {
|
||||
if (!filterEnabled) {
|
||||
return ({})
|
||||
// columns: [root.stateType.name]
|
||||
sources: {
|
||||
var ret = []
|
||||
if (filterEnabled) {
|
||||
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) {
|
||||
return ({state: filterTypeName})
|
||||
|
||||
for (var i = 0; i < root.thing.thingClass.stateTypes.count; i++) {
|
||||
var stateType = root.thing.thingClass.stateTypes.get(i)
|
||||
ret.push("state-" + root.thing.id + "-" + stateType.name)
|
||||
}
|
||||
if (isEventFilter) {
|
||||
return ({event: filterTypeName})
|
||||
for (var i = 0; i < root.thing.thingClass.eventTypes.count; i++) {
|
||||
var eventType = root.thing.thingClass.eventTypes.get(i)
|
||||
ret.push("event-" + root.thing.id + "-" + eventType.name)
|
||||
}
|
||||
if (isActionFilter) {
|
||||
return ({action: filterTypeName})
|
||||
for (var i = 0; i < root.thing.thingClass.actionTypes.count; i++) {
|
||||
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 bool isStateFilter: thing.thingClass.stateTypes.findByName(filterTypeName) !== null
|
||||
property bool isEventFilter: thing.thingClass.eventTypes.findByName(filterTypeName) !== null
|
||||
property bool isActionFilter: thing.thingClass.actionTypes.findByName(filterTypeName) !== null
|
||||
|
||||
onFilterChanged: {
|
||||
logsModelNg.clear()
|
||||
logsModelNg.fetchLogs()
|
||||
onSourcesChanged: {
|
||||
logsModel.clear()
|
||||
logsModel.fetchLogs()
|
||||
}
|
||||
|
||||
// thingId: root.thing.id
|
||||
@ -109,7 +119,7 @@ Page {
|
||||
anchors { left: parent.left; top: parent.top; right: parent.right }
|
||||
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
|
||||
|
||||
leftPadding: 0; rightPadding: 0; topPadding: 0; bottomPadding: 0
|
||||
@ -180,22 +190,22 @@ Page {
|
||||
ListView {
|
||||
anchors { left: parent.left; top: graphLoader.bottom; right: parent.right; bottom: parent.bottom }
|
||||
clip: true
|
||||
model: logsModelNg
|
||||
model: logsModel
|
||||
ScrollBar.vertical: ScrollBar {}
|
||||
|
||||
BusyIndicator {
|
||||
anchors.centerIn: parent
|
||||
visible: logsModelNg.busy
|
||||
visible: logsModel.busy
|
||||
}
|
||||
|
||||
delegate: ItemDelegate {
|
||||
id: entryDelegate
|
||||
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 EventType eventType: entry && entry.values.hasOwnProperty("event") ? root.thing.thingClass.eventTypes.findByName(entry.values.event) : null
|
||||
property ActionType actionType: entry && entry.values.hasOwnProperty("action") ? root.thing.thingClass.actionTypes.findByName(entry.values.action) : null
|
||||
property StateType stateType: entry && entry.source.indexOf("state-") == 0 ? root.thing.thingClass.stateTypes.findByName(entry.source.replace(/.*-.*-/, "")) : null
|
||||
property EventType eventType: entry && entry.source.indexOf("event-") == 0 ? root.thing.thingClass.eventTypes.findByName(entry.source.replace(/.*-.*-/, "")) : null
|
||||
property ActionType actionType: entry && entry.source.indexOf("action-") == 0 ? root.thing.thingClass.actionTypes.findByName(entry.source.replace(/.*-.*-/, "")) : null
|
||||
|
||||
contentItem: RowLayout {
|
||||
ColorIcon {
|
||||
@ -280,7 +290,7 @@ Page {
|
||||
when: entryDelegate.stateType != null
|
||||
target: valueLoader.item;
|
||||
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 {
|
||||
when: entryDelegate.stateType != null
|
||||
|
||||
@ -37,9 +37,8 @@ import "../components"
|
||||
Page {
|
||||
id: root
|
||||
property Thing thing: null
|
||||
readonly property ThingClass thingClass: thing.thingClass
|
||||
|
||||
property bool showLogsButton: true
|
||||
property bool showLogsButton: false
|
||||
property bool showDetailsButton: true
|
||||
property bool showBrowserButton: true
|
||||
property bool popStackOnBackButton: true
|
||||
@ -59,7 +58,7 @@ Page {
|
||||
|
||||
HeaderButton {
|
||||
imageSource: "../images/folder.svg"
|
||||
visible: root.thingClass.browsable && root.showBrowserButton
|
||||
visible: root.thing.thingClass.browsable && root.showBrowserButton
|
||||
onClicked: {
|
||||
pageStack.push(Qt.resolvedUrl("DeviceBrowserPage.qml"), {thing: root.thing})
|
||||
}
|
||||
|
||||
@ -71,7 +71,7 @@ ThingPageBase {
|
||||
id: tempComponent
|
||||
StateChart {
|
||||
thing: root.thing
|
||||
stateType: root.thingClass.stateTypes.findByName("temperature")
|
||||
stateType: root.thing.thingClass.stateTypes.findByName("temperature")
|
||||
color: app.interfaceToColor("temperaturesensor")
|
||||
}
|
||||
}
|
||||
@ -82,7 +82,7 @@ ThingPageBase {
|
||||
GenericTypeGraph {
|
||||
Layout.fillWidth: true
|
||||
thing: root.thing
|
||||
stateType: root.thingClass.stateTypes.findByName("temperature")
|
||||
stateType: root.thing.thingClass.stateTypes.findByName("temperature")
|
||||
iconSource: app.interfaceToIcon("temperaturesensor")
|
||||
color: app.interfaceToColor("temperaturesensor")
|
||||
}
|
||||
@ -102,7 +102,7 @@ ThingPageBase {
|
||||
id: humidityComponent
|
||||
StateChart {
|
||||
thing: root.thing
|
||||
stateType: root.thingClass.stateTypes.findByName("humidity")
|
||||
stateType: root.thing.thingClass.stateTypes.findByName("humidity")
|
||||
color: app.interfaceToColor("humiditysensor")
|
||||
}
|
||||
}
|
||||
@ -112,7 +112,7 @@ ThingPageBase {
|
||||
GenericTypeGraph {
|
||||
Layout.fillWidth: true
|
||||
thing: root.thing
|
||||
stateType: root.thingClass.stateTypes.findByName("humidity")
|
||||
stateType: root.thing.thingClass.stateTypes.findByName("humidity")
|
||||
iconSource: app.interfaceToIcon("humiditysensor")
|
||||
color: app.interfaceToColor("humiditysensor")
|
||||
}
|
||||
@ -132,7 +132,7 @@ ThingPageBase {
|
||||
id: pressureComponent
|
||||
StateChart {
|
||||
thing: root.thing
|
||||
stateType: root.thingClass.stateTypes.findByName("pressure")
|
||||
stateType: root.thing.thingClass.stateTypes.findByName("pressure")
|
||||
color: app.interfaceToColor("pressuresensor")
|
||||
}
|
||||
}
|
||||
@ -142,7 +142,7 @@ ThingPageBase {
|
||||
GenericTypeGraph {
|
||||
Layout.fillWidth: true
|
||||
thing: root.thing
|
||||
stateType: root.thingClass.stateTypes.findByName("pressure")
|
||||
stateType: root.thing.thingClass.stateTypes.findByName("pressure")
|
||||
iconSource: app.interfaceToIcon("pressuresensor")
|
||||
color: app.interfaceToColor("pressuresensor")
|
||||
}
|
||||
@ -162,7 +162,7 @@ ThingPageBase {
|
||||
id: windSpeedComponent
|
||||
StateChart {
|
||||
thing: root.thing
|
||||
stateType: root.thingClass.stateTypes.findByName("windSpeed")
|
||||
stateType: root.thing.thingClass.stateTypes.findByName("windSpeed")
|
||||
color: app.interfaceToColor("windspeedsensor")
|
||||
}
|
||||
}
|
||||
@ -172,7 +172,7 @@ ThingPageBase {
|
||||
GenericTypeGraph {
|
||||
Layout.fillWidth: true
|
||||
thing: root.thing
|
||||
stateType: root.thingClass.stateTypes.findByName("windSpeed")
|
||||
stateType: root.thing.thingClass.stateTypes.findByName("windSpeed")
|
||||
iconSource: app.interfaceToIcon("windspeedsensor")
|
||||
color: app.interfaceToColor("windspeedsensor")
|
||||
}
|
||||
|
||||
@ -207,8 +207,7 @@ Page {
|
||||
readonly property NewLogsModel logsModel: NewLogsModel {
|
||||
objectName: "temp: " + thing.name
|
||||
engine: _engine
|
||||
source: "states-" + thing.id
|
||||
filter: ({state: "temperature"})
|
||||
source: "state-" + thing.id + "-temperature"
|
||||
startTime: new Date(d.startTime.getTime() - d.range * 60000)
|
||||
endTime: new Date(d.endTime.getTime() + d.range * 60000)
|
||||
sampleRate: d.sampleRate
|
||||
@ -258,8 +257,7 @@ Page {
|
||||
readonly property NewLogsModel logsModel: NewLogsModel {
|
||||
objectName: "temp: " + thing.name
|
||||
engine: _engine
|
||||
source: "states-" + thing.id
|
||||
filter: ({state: "temperature"})
|
||||
source: "state-" + thing.id + "-temperature"
|
||||
startTime: new Date(d.startTime.getTime() - d.range * 60000)
|
||||
endTime: new Date(d.endTime.getTime() + d.range * 60000)
|
||||
sampleRate: d.sampleRate
|
||||
@ -309,8 +307,7 @@ Page {
|
||||
readonly property NewLogsModel logsModel: NewLogsModel {
|
||||
objectName: "hum: " + thing.name
|
||||
engine: _engine
|
||||
source: "states-" + thing.id
|
||||
filter: ({state: "humidity"})
|
||||
source: "state-" + thing.id + "-humidity"
|
||||
startTime: new Date(d.startTime.getTime() - d.range * 60000)
|
||||
endTime: new Date(d.endTime.getTime() + d.range * 60000)
|
||||
sampleRate: d.sampleRate
|
||||
@ -344,38 +341,37 @@ Page {
|
||||
Component.onDestruction: {
|
||||
chartView.removeSeries(series)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: vocRepeater
|
||||
// model: zoneWrapper.indoorVocSensors
|
||||
model: zoneWrapper.indoorVocSensors
|
||||
delegate: Item {
|
||||
id: vocDelegate
|
||||
readonly property Thing thing: zoneWrapper.indoorVocSensors.get(index)
|
||||
property XYSeries series: null
|
||||
readonly property LogsModel logsModel: LogsModel {
|
||||
readonly property NewLogsModel logsModel: NewLogsModel {
|
||||
objectName: "voc: " + thing.name
|
||||
engine: typeIds.length > 0 ? _engine : null
|
||||
thingId: thing.id
|
||||
sourceFilter: LogsModel.SourceStates
|
||||
live: true
|
||||
// graphSeries: series
|
||||
viewStartTime: new Date(d.startTime.getTime() - d.range * 60000)
|
||||
fetchBlockSize: 500
|
||||
|
||||
typeIds: {
|
||||
var ret = [];
|
||||
ret.push(thing.thingClass.stateTypes.findByName("voc").id)
|
||||
return ret;
|
||||
engine: _engine
|
||||
source: "state-" + thing.id + "-voc"
|
||||
startTime: new Date(d.startTime.getTime() - d.range * 60000)
|
||||
endTime: new Date(d.endTime.getTime() + d.range * 60000)
|
||||
sampleRate: d.sampleRate
|
||||
onEntriesAdded: {
|
||||
for (var i = 0; i < entries.length; i++) {
|
||||
var entry = entries[i]
|
||||
var value = entry.values["voc"]
|
||||
if (value == null) {
|
||||
value = 0;
|
||||
}
|
||||
series.insert(index + i, entry.timestamp, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
XYSeriesAdapter {
|
||||
logsModel: vocDelegate.logsModel
|
||||
xySeries: series
|
||||
sampleRate: XYSeriesAdapter.SampleRate10Minutes
|
||||
onEntriesRemoved: {
|
||||
series.removePoints(index, count)
|
||||
}
|
||||
Component.onCompleted: fetchLogs()
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
@ -411,18 +407,13 @@ Page {
|
||||
XYPoint {x: dateTimeAxis.max.getTime(); y: 0}
|
||||
}
|
||||
|
||||
readonly property LogsModel logsModel: LogsModel {
|
||||
readonly property NewLogsModel logsModel: NewLogsModel {
|
||||
id: logsModelNg
|
||||
engine: typeIds.length ? _engine : null
|
||||
thingId: thing ? thing.id : ""
|
||||
typeIds: {
|
||||
var ret = [];
|
||||
ret.push(thing.thingClass.stateTypes.findByName("closed").id)
|
||||
return ret;
|
||||
}
|
||||
sourceFilter: LogsModel.SourceStates
|
||||
live: true
|
||||
viewStartTime: new Date(d.startTime.getTime() - d.range * 60000)
|
||||
source: "state-" + thing.id + "-closed"
|
||||
startTime: new Date(d.startTime.getTime() - d.range * 60000)
|
||||
endTime: new Date(d.endTime.getTime() + d.range * 60000)
|
||||
sampleRate: d.sampleRate
|
||||
}
|
||||
|
||||
BoolSeriesAdapter {
|
||||
@ -675,8 +666,9 @@ Page {
|
||||
delegate: TooltipDelegate {
|
||||
visible: (mouseArea.containsMouse || mouseArea.tooltipping) && !mouseArea.dragging
|
||||
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")
|
||||
valueName: "temperature"
|
||||
axis: temperatureAxis
|
||||
x: tooltips.tooltipX
|
||||
width: tooltips.tooltipWidth
|
||||
@ -693,7 +685,8 @@ Page {
|
||||
delegate: TooltipDelegate {
|
||||
visible: (mouseArea.containsMouse || mouseArea.tooltipping) && !mouseArea.dragging
|
||||
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")
|
||||
axis: temperatureAxis
|
||||
x: tooltips.tooltipX
|
||||
@ -711,8 +704,9 @@ Page {
|
||||
delegate: TooltipDelegate {
|
||||
visible: (mouseArea.containsMouse || mouseArea.tooltipping) && !mouseArea.dragging
|
||||
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")
|
||||
valueName: "humidity"
|
||||
axis: humidityAxis
|
||||
x: tooltips.tooltipX
|
||||
width: tooltips.tooltipWidth
|
||||
@ -729,7 +723,8 @@ Page {
|
||||
delegate: TooltipDelegate {
|
||||
visible: (mouseArea.containsMouse || mouseArea.tooltipping) && !mouseArea.dragging
|
||||
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")
|
||||
axis: vocAxis
|
||||
x: tooltips.tooltipX
|
||||
|
||||
@ -8,16 +8,19 @@ import Nymea.AirConditioning 1.0
|
||||
import QtCharts 2.3
|
||||
|
||||
NymeaToolTip {
|
||||
id: root
|
||||
width: layout.implicitWidth + Style.smallMargins * 2
|
||||
height: layout.implicitHeight + Style.smallMargins * 2
|
||||
|
||||
property Thing thing: null
|
||||
property LogEntry entry: null
|
||||
property NewLogEntry entry: null
|
||||
property string valueName: ""
|
||||
property alias color: rect.color
|
||||
property ValueAxis axis: null
|
||||
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
|
||||
y: fixedY // Animated
|
||||
|
||||
@ -32,7 +35,7 @@ NymeaToolTip {
|
||||
height: width
|
||||
}
|
||||
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
|
||||
font: Style.extraSmallFont
|
||||
elide: Text.ElideMiddle
|
||||
|
||||
@ -37,7 +37,7 @@ Item {
|
||||
}
|
||||
|
||||
onEntriesRemoved: {
|
||||
consumptionUpperSeries.removePoints(index, count)
|
||||
consumptionUpperSeries.removePoints(index, Math.min(count, consumptionUpperSeries.count))
|
||||
zeroSeries.shrink()
|
||||
}
|
||||
}
|
||||
@ -423,8 +423,12 @@ Item {
|
||||
|
||||
|
||||
// Remove the leading 0-value entry
|
||||
lowerSeries.removePoints(0, 1);
|
||||
upperSeries.removePoints(0, 1);
|
||||
if (lowerSeries.count > 0) {
|
||||
lowerSeries.removePoints(0, 1);
|
||||
}
|
||||
if (upperSeries.count > 0) {
|
||||
upperSeries.removePoints(0, 1);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -472,12 +476,25 @@ Item {
|
||||
}
|
||||
|
||||
onEntriesRemoved: {
|
||||
// Remove the leading 0-value entry
|
||||
consumerDelegate.lowerSeries.removePoints(0, 1);
|
||||
consumerDelegate.upperSeries.removePoints(0, 1);
|
||||
// Note QtCharts crash when calling removePoints() for points that don't exist.
|
||||
// Additionally it may decide to ignore values we add, e.g. if we try to add an Inf or undefined value for whatever reason
|
||||
// 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)
|
||||
consumerDelegate.upperSeries.removePoints(index, count)
|
||||
// Remove the leading 0-value entry
|
||||
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
|
||||
consumerDelegate.lowerSeries.insert(0, consumerDelegate.series.upperSeries.at(0).x, 0)
|
||||
|
||||
@ -123,13 +123,17 @@ Item {
|
||||
}
|
||||
|
||||
onEntriesRemoved: {
|
||||
acquisitionUpperSeries.removePoints(index, count)
|
||||
returnUpperSeries.removePoints(index, count)
|
||||
fromStorageUpperSeries.removePoints(index, count)
|
||||
toStorageUpperSeries.removePoints(index, count)
|
||||
selfProductionConsumptionUpperSeries.removePoints(index, count)
|
||||
productionSeries.removePoints(index, count)
|
||||
// consumptionSeries.removePoints(index, count)
|
||||
// Note QtCharts crash when calling removePoints() for points that don't exist.
|
||||
// Additionally it may decide to ignore values we add, e.g. if we try to add an Inf or undefined value for whatever reason
|
||||
// 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
|
||||
|
||||
acquisitionUpperSeries.removePoints(index, Math.min(count, acquisitionUpperSeries.count - index))
|
||||
returnUpperSeries.removePoints(index, Math.min(count, returnUpperSeries.count - index))
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,7 +54,7 @@ Item {
|
||||
} else if (interfaceList.indexOf("awning") >= 0) {
|
||||
page = "AwningThingPage.qml";
|
||||
} else if (interfaceList.indexOf("notifications") >= 0) {
|
||||
page = "NotificationsDevicePage.qml";
|
||||
page = "NotificationsThingPage.qml";
|
||||
} else if (interfaceList.indexOf("fingerprintreader") >= 0) {
|
||||
page = "FingerprintReaderDevicePage.qml";
|
||||
} else if (interfaceList.indexOf("evcharger") >= 0) {
|
||||
|
||||
Reference in New Issue
Block a user