rework the generic device page

This commit is contained in:
Michael Zanetti 2018-12-10 00:29:14 +01:00
parent eaaf7300cd
commit f1d29034b0
26 changed files with 1126 additions and 343 deletions

View File

@ -239,7 +239,7 @@ void DeviceManager::getConfiguredDevicesResponse(const QVariantMap &params)
value.convert(QVariant::Int);
}
device->setStateValue(stateTypeId, value);
qDebug() << "Set device state value:" << device->stateValue(stateTypeId) << value;
// qDebug() << "Set device state value:" << device->stateValue(stateTypeId) << value;
}
devices()->addDevice(device);
}
@ -297,7 +297,7 @@ void DeviceManager::editDeviceResponse(const QVariantMap &params)
void DeviceManager::executeActionResponse(const QVariantMap &params)
{
qDebug() << "Execute Action response" << params;
emit executeActionReply(params.value("params").toMap());
emit executeActionReply(params);
}
void DeviceManager::savePluginConfig(const QUuid &pluginId)
@ -366,7 +366,7 @@ void DeviceManager::editDevice(const QUuid &deviceId, const QString &name)
m_jsonClient->sendCommand("Devices.EditDevice", params, this, "editDeviceResponse");
}
void DeviceManager::executeAction(const QUuid &deviceId, const QUuid &actionTypeId, const QVariantList &params)
int DeviceManager::executeAction(const QUuid &deviceId, const QUuid &actionTypeId, const QVariantList &params)
{
qDebug() << "JsonRpc: execute action " << deviceId.toString() << actionTypeId.toString() << params;
QVariantMap p;
@ -377,5 +377,5 @@ void DeviceManager::executeAction(const QUuid &deviceId, const QUuid &actionType
}
qDebug() << "Params:" << p;
m_jsonClient->sendCommand("Actions.ExecuteAction", p, this, "executeActionResponse");
return m_jsonClient->sendCommand("Actions.ExecuteAction", p, this, "executeActionResponse");
}

View File

@ -69,7 +69,7 @@ public:
Q_INVOKABLE void confirmPairing(const QUuid &pairingTransactionId, const QString &secret = QString());
Q_INVOKABLE void removeDevice(const QUuid &deviceId, RemovePolicy policy = RemovePolicyNone);
Q_INVOKABLE void editDevice(const QUuid &deviceId, const QString &name);
Q_INVOKABLE void executeAction(const QUuid &deviceId, const QUuid &actionTypeId, const QVariantList &params = QVariantList());
Q_INVOKABLE int executeAction(const QUuid &deviceId, const QUuid &actionTypeId, const QVariantList &params = QVariantList());
private:
Q_INVOKABLE void notificationReceived(const QVariantMap &data);
@ -98,6 +98,7 @@ signals:
void editDeviceReply(const QVariantMap &params);
void executeActionReply(const QVariantMap &params);
void fetchingDataChanged();
void notificationReceived(const QString &deviceId, const QString &eventTypeId, const QVariantList &params);
private:
Vendors *m_vendors;

View File

@ -91,7 +91,7 @@ void ZeroconfDiscovery::serviceEntryAdded(const QZeroConfService &entry)
version = txtRecord.second;
}
}
// qDebug() << "avahi service entry added" << serverName << uuid << sslEnabled;
qDebug() << "avahi service entry added" << serverName << uuid << sslEnabled;
DiscoveryDevice* device = m_discoveryModel->find(uuid);

View File

@ -56,15 +56,15 @@ void JsonRpcClient::registerNotificationHandler(JsonHandler *handler, const QStr
m_notificationHandlers.insert(handler->nameSpace(), qMakePair<JsonHandler*, QString>(handler, method));
}
void JsonRpcClient::sendCommand(const QString &method, const QVariantMap &params, QObject *caller, const QString &callbackMethod)
int JsonRpcClient::sendCommand(const QString &method, const QVariantMap &params, QObject *caller, const QString &callbackMethod)
{
JsonRpcReply *reply = createReply(method, params, caller, callbackMethod);
m_replies.insert(reply->commandId(), reply);
sendRequest(reply->requestMap());
return reply->commandId();
}
void JsonRpcClient::sendCommand(const QString &method, QObject *caller, const QString &callbackMethod)
int JsonRpcClient::sendCommand(const QString &method, QObject *caller, const QString &callbackMethod)
{
return sendCommand(method, QVariantMap(), caller, callbackMethod);
}

View File

@ -60,8 +60,8 @@ public:
void registerNotificationHandler(JsonHandler *handler, const QString &method);
void sendCommand(const QString &method, const QVariantMap &params, QObject *caller = nullptr, const QString &callbackMethod = QString());
void sendCommand(const QString &method, QObject *caller = nullptr, const QString &callbackMethod = QString());
int sendCommand(const QString &method, const QVariantMap &params, QObject *caller = nullptr, const QString &callbackMethod = QString());
int sendCommand(const QString &method, QObject *caller = nullptr, const QString &callbackMethod = QString());
void setConnection(NymeaConnection *connection);
bool connected() const;

View File

@ -55,6 +55,7 @@
#include "ruletemplates/ruleactiontemplate.h"
#include "ruletemplates/ruleactionparamtemplate.h"
#include "connection/awsclient.h"
#include "models/devicemodel.h"
#include <QtQml/qqml.h>
@ -114,6 +115,8 @@ void registerQmlTypes() {
qmlRegisterType<DeviceClassesProxy>(uri, 1, 0, "DeviceClassesProxy");
qmlRegisterType<DeviceDiscovery>(uri, 1, 0, "DeviceDiscovery");
qmlRegisterType<DeviceModel>(uri, 1, 0, "DeviceModel");
qmlRegisterUncreatableType<RuleManager>(uri, 1, 0, "RuleManager", "Get it from the Engine");
qmlRegisterUncreatableType<Rules>(uri, 1, 0, "Rules", "Get it from RuleManager");
qmlRegisterUncreatableType<Rule>(uri, 1, 0, "Rule", "Get it from Rules");

View File

@ -83,7 +83,8 @@ SOURCES += \
configuration/nymeaconfiguration.cpp \
models/mqttpolicies.cpp \
configuration/mqttpolicy.cpp \
configuration/mqttpolicies.cpp
configuration/mqttpolicies.cpp \
models/devicemodel.cpp
HEADERS += \
engine.h \
@ -145,7 +146,8 @@ HEADERS += \
configuration/serverconfigurations.h \
configuration/nymeaconfiguration.h \
configuration/mqttpolicy.h \
configuration/mqttpolicies.h
configuration/mqttpolicies.h \
models/devicemodel.h
unix {
target.path = /usr/lib

View File

@ -0,0 +1,164 @@
#include "devicemodel.h"
#include "types/statetype.h"
DeviceModel::DeviceModel(QObject *parent) : QAbstractListModel(parent)
{
}
int DeviceModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return m_list.count();
}
QVariant DeviceModel::data(const QModelIndex &index, int role) const
{
if (role == RoleId) {
return m_list.at(index.row());
}
if (role == RoleType) {
StateType* stateType = m_device->deviceClass()->stateTypes()->getStateType(m_list.at(index.row()));
if (stateType) {
return TypeStateType;
}
ActionType* actionType = m_device->deviceClass()->actionTypes()->getActionType(m_list.at(index.row()));
if (actionType) {
return TypeActionType;
}
EventType* eventType = m_device->deviceClass()->eventTypes()->getEventType(m_list.at(index.row()));
if (eventType) {
return TypeEventType;
}
}
if (role == RoleDisplayName) {
StateType* stateType = m_device->deviceClass()->stateTypes()->getStateType(m_list.at(index.row()));
if (stateType) {
return stateType->displayName();
}
ActionType* actionType = m_device->deviceClass()->actionTypes()->getActionType(m_list.at(index.row()));
if (actionType) {
return actionType->displayName();
}
EventType* eventType = m_device->deviceClass()->eventTypes()->getEventType(m_list.at(index.row()));
if (eventType) {
return eventType->displayName();
}
}
if (role == RoleWritable) {
ActionType* actionType = m_device->deviceClass()->actionTypes()->getActionType(m_list.at(index.row()));
return actionType != nullptr;
}
return QVariant();
}
QHash<int, QByteArray> DeviceModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles.insert(RoleId, "id");
roles.insert(RoleType, "type");
roles.insert(RoleDisplayName, "displayName");
roles.insert(RoleWritable, "writable");
return roles;
}
QVariant DeviceModel::getData(int index, int role) const
{
if (index < 0 || index >= m_list.count()) {
return QVariant();
}
return data(this->index(index), role);
}
Device *DeviceModel::device() const
{
return m_device;
}
void DeviceModel::setDevice(Device *device)
{
if (m_device != device) {
m_device = device;
emit deviceChanged();
updateList();
}
}
bool DeviceModel::showStates() const
{
return m_showStates;
}
void DeviceModel::setShowStates(bool showStates)
{
if (m_showStates != showStates) {
m_showStates = showStates;
emit showStatesChanged();
updateList();
}
}
bool DeviceModel::showActions() const
{
return m_showActions;
}
void DeviceModel::setShowActions(bool showActions)
{
if (m_showActions != showActions) {
m_showActions = showActions;
emit showActionsChanged();
updateList();
}
}
bool DeviceModel::showEvents() const
{
return m_showEvents;
}
void DeviceModel::setShowEvents(bool showEvents)
{
if (m_showEvents != showEvents) {
m_showEvents = showEvents;
emit showEventsChanged();
updateList();
}
}
void DeviceModel::updateList()
{
if (!m_device) {
beginResetModel();
m_list.clear();
endResetModel();
emit countChanged();
return;
}
beginResetModel();
m_list.clear();
if (m_showStates) {
for (int i = 0; i < m_device->deviceClass()->stateTypes()->rowCount(); i++) {
m_list.append(m_device->deviceClass()->stateTypes()->get(i)->id());
}
}
if (m_showActions) {
for (int i = 0; i < m_device->deviceClass()->actionTypes()->rowCount(); i++) {
if (!m_list.contains(m_device->deviceClass()->actionTypes()->get(i)->id())) {
m_list.append(m_device->deviceClass()->actionTypes()->get(i)->id());
}
}
}
if (m_showEvents) {
for (int i = 0; i < m_device->deviceClass()->eventTypes()->rowCount(); i++) {
if (!m_list.contains(m_device->deviceClass()->eventTypes()->get(i)->id())) {
m_list.append(m_device->deviceClass()->eventTypes()->get(i)->id());
}
}
}
endResetModel();
emit countChanged();
}

View File

@ -0,0 +1,77 @@
#ifndef DEVICEMODEL_H
#define DEVICEMODEL_H
#include <QObject>
#include "types/device.h"
#include "types/deviceclass.h"
class DeviceModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
Q_PROPERTY(Device* device READ device WRITE setDevice NOTIFY deviceChanged)
Q_PROPERTY(bool showStates READ showStates WRITE setShowStates NOTIFY showStatesChanged)
Q_PROPERTY(bool showActions READ showActions WRITE setShowActions NOTIFY showActionsChanged)
Q_PROPERTY(bool showEvents READ showEvents WRITE setShowEvents NOTIFY showEventsChanged)
public:
enum Roles {
RoleId,
RoleType,
RoleDisplayName,
RoleWritable
};
Q_ENUM(Roles)
enum Type {
TypeStateType,
TypeActionType,
TypeEventType
};
Q_ENUM(Type)
explicit DeviceModel(QObject *parent = nullptr);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role) const override;
QHash<int, QByteArray> roleNames() const override;
Q_INVOKABLE QVariant getData(int index, int role) const;
Device* device() const;
void setDevice(Device *device);
bool showStates() const;
void setShowStates(bool showStates);
bool showActions() const;
void setShowActions(bool showActions);
bool showEvents() const;
void setShowEvents(bool showEvents);
signals:
void deviceChanged();
void countChanged();
bool showStatesChanged();
bool showActionsChanged();
bool showEventsChanged();
private:
void updateList();
private:
Device *m_device = nullptr;
bool m_showStates = true;
bool m_showActions = true;
bool m_showEvents = true;
QList<QString> m_list;
};
#endif // DEVICEMODEL_H

View File

@ -104,6 +104,11 @@ void LogsModelNg::setTypeIds(const QStringList &typeIds)
if (m_typeIds != typeIds) {
m_typeIds = typeIds;
emit typeIdsChanged();
beginResetModel();
qDeleteAll(m_list);
m_list.clear();
endResetModel();
fetchMore();
}
}

View File

@ -35,7 +35,7 @@ class State : public QObject
Q_PROPERTY(QVariant value READ value NOTIFY valueChanged)
public:
explicit State(const QUuid &deviceId, const QUuid &stateTypeId, const QVariant &value, QObject *parent = 0);
explicit State(const QUuid &deviceId, const QUuid &stateTypeId, const QVariant &value, QObject *parent = nullptr);
QUuid deviceId() const;
QUuid stateTypeId() const;

View File

@ -36,7 +36,9 @@ QList<StateType *> StateTypes::stateTypes()
StateType *StateTypes::get(int index) const
{
qDebug() << "returning" << m_stateTypes.at(index);
if (index < 0 || index >= m_stateTypes.count()) {
return nullptr;
}
return m_stateTypes.at(index);
}

View File

@ -60,7 +60,6 @@
<file>ui/customviews/ExtendedVolumeController.qml</file>
<file>ui/devicepages/MediaDevicePage.qml</file>
<file>ui/devicepages/ButtonDevicePage.qml</file>
<file>ui/devicepages/GenericDeviceStateDetailsPage.qml</file>
<file>ui/devicepages/GenericDevicePage.qml</file>
<file>ui/devicepages/WeatherDevicePagePre110.qml</file>
<file>ui/devicepages/WeatherDevicePagePost110.qml</file>
@ -137,5 +136,7 @@
<file>ui/system/MqttBrokerSettingsPage.qml</file>
<file>ui/system/ServerConfigurationDialog.qml</file>
<file>ui/system/MqttPolicyPage.qml</file>
<file>ui/devicepages/DeviceLogPage.qml</file>
<file>ui/components/Led.qml</file>
</qresource>
</RCC>

View File

@ -123,7 +123,7 @@ Page {
print("should setup", deviceClass.name, deviceClass.setupMethod, deviceClass.createMethods, deviceClass["discoveryParamTypes"].count)
}
swipe.enabled: true// deviceClass.createMethods.indexOf("CreateMethodUser") !== -1
swipe.enabled: deviceClass.createMethods.indexOf("CreateMethodUser") !== -1
swipe.right: MouseArea {
height: deviceClassDelegate.height
width: height

View File

@ -74,9 +74,10 @@ Item {
}
Engine {
id: engine
id: engineObject
}
property alias _engine: engine
readonly property Engine engine: engineObject
readonly property Engine _engine: engineObject // In case a child cannot use "engine"
property int connectionTabIndex: index
onConnectionTabIndexChanged: tabSettings.lastConnectedHost = engine.connection.url

View File

@ -4,13 +4,28 @@ Item {
id: root
property color color: actionState
property Component touchDelegate: null
property bool pressed: mouseArea.pressed
property bool hovered: mouseArea.containsMouse
property variant lights
property bool active: true
property Component touchDelegate: Rectangle {
height: 15
width: height
radius: height / 2
color: app.accentColor
Rectangle {
color: root.hovered || root.pressed ? "#11000000" : "transparent"
anchors.centerIn: parent
height: 30
width: height
radius: width / 2
Behavior on color { ColorAnimation { duration: 200 } }
}
}
function calculateXy(color) {
if (!color.hasOwnProperty("r")) {
// Qt 4.8's color doesn't have r,g,b properties
@ -189,7 +204,7 @@ Item {
x: item ? Math.max(0, Math.min(point.x - width * .5, parent.width - item.width)) : 0
y: item ? Math.max(0, Math.min(point.y - height * .5, parent.height - item.height)) : 0
sourceComponent: root.touchDelegate
visible: mouseArea.draggedItem != touchDelegateLoader && root.active
visible: mouseArea.draggedItem !== touchDelegateLoader && root.active
// Behavior on x {
// enabled: !mouseArea.pressed
// NumberAnimation {}
@ -199,6 +214,6 @@ Item {
Loader {
id: dndItem
sourceComponent: root.touchDelegate
visible: mouseArea.draggedItem != undefined && root.active
visible: mouseArea.draggedItem !== undefined && root.active
}
}

View File

@ -5,13 +5,14 @@ import QtQuick.Layouts 1.3
MenuItem {
id: root
property alias iconSource: icon.name
implicitWidth: 200
contentItem: RowLayout {
spacing: app.margins
ColorIcon {
id: icon
height: parent.height
width: height
Layout.preferredHeight: app.iconSize
Layout.preferredWidth: height
}
Label {
id: label

View File

@ -0,0 +1,18 @@
import QtQuick 2.9
Item {
id: root
implicitHeight: app.iconSize * .8
implicitWidth: height
property bool on: false
Rectangle {
height: Math.min(parent.height, parent.height)
width: height
radius: width / 2
color: root.on ? "lightgreen" : "lightgray"
border.width: 1
border.color: app.foregroundColor
}
}

View File

@ -11,8 +11,8 @@ Item {
id: root
implicitHeight: width * .6
property var device: null
property var stateType: null
property Device device: null
property StateType stateType: null
property int roundTo: 2
readonly property var valueState: device.states.getState(stateType.id)
readonly property var deviceClass: engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId);
@ -55,12 +55,20 @@ Item {
color: root.color
}
Led {
visible: root.stateType.type.toLowerCase() === "bool"
on: root.valueState.value === true
}
Label {
Layout.fillWidth: true
text: 1.0 * Math.round(root.valueState.value * Math.pow(10, root.roundTo)) / Math.pow(10, root.roundTo) + " " + root.stateType.unitString
text: root.stateType.type.toLowerCase() === "bool"
? root.stateType.displayName
: 1.0 * Math.round(root.valueState.value * Math.pow(10, root.roundTo)) / Math.pow(10, root.roundTo) + " " + root.stateType.unitString
font.pixelSize: app.largeFont
}
HeaderButton {
imageSource: "../images/zoom-out.svg"
onClicked: {
@ -96,11 +104,19 @@ Item {
ValueAxis {
id: yAxis
max: logsModelNg.maxValue + Math.abs(logsModelNg.maxValue * .05)
min: logsModelNg.minValue - Math.abs(logsModelNg.minValue * .05)
max: Math.ceil(logsModelNg.maxValue + Math.abs(logsModelNg.maxValue * .05))
min: Math.floor(logsModelNg.minValue - Math.abs(logsModelNg.minValue * .05))
labelsFont.pixelSize: app.smallFont
labelFormat: {
switch (root.stateType.type.toLowerCase()) {
case "bool":
return "x";
default:
return "%d";
}
}
labelsColor: app.foregroundColor
tickCount: chartView.height / 40
tickCount: root.stateType.type.toLowerCase() === "bool" ? 2 : chartView.height / 40
color: Qt.rgba(app.foregroundColor.r, app.foregroundColor.g, app.foregroundColor.b, .2)
gridLineColor: color
}

View File

@ -8,7 +8,7 @@ import "../components"
ItemDelegate {
id: root
property ActionType actionType: null
property var actionType: null
property var actionState: null
signal executeAction(var params)
@ -294,23 +294,6 @@ ItemDelegate {
root.executeAction(params)
}
}
touchDelegate: Rectangle {
height: 15
width: height
radius: height / 2
color: Material.accent
Rectangle {
color: colorPicker.hovered || colorPicker.pressed ? "#11000000" : "transparent"
anchors.centerIn: parent
height: 30
width: height
radius: width / 2
Behavior on color { ColorAnimation { duration: 200 } }
}
}
}
}

View File

@ -143,14 +143,20 @@ ItemDelegate {
Component {
id: spinnerComponent
SpinBox {
value: root.param.value
from: root.paramType.minValue
to: root.paramType.maxValue
value: root.param.value ? root.param.value : 0
from: root.paramType.minValue ? root.paramType.minValue : -999999999
to: root.paramType.maxValue ? root.paramType.maxValue : 999999999
editable: true
width: 150
onValueModified: root.param.value = value
textFromValue: function(value) {
return value
}
Component.onCompleted: {
if (root.value === undefined) {
root.value = value
}
}
}
}
@ -173,6 +179,11 @@ ItemDelegate {
root.param.value = root.paramType.allowedValues[index]
print("setting value to", root.param.value)
}
Component.onCompleted: {
if (root.value === undefined) {
root.value = model[0]
}
}
}
}

View File

@ -78,9 +78,7 @@ DeviceListPageBase {
Rectangle {
anchors { left: parent.left; top: parent.top; bottom: parent.bottom }
width: app.margins / 2
color: itemDelegate.connectedState !== null && itemDelegate.connectedState.value === false ?
"red"
: itemDelegate.colorStateType ? itemDelegate.colorState.value : "#00000000"
color: itemDelegate.colorStateType ? itemDelegate.colorState.value : "#00000000"
}
contentItem: ColumnLayout {
@ -105,22 +103,12 @@ DeviceListPageBase {
// source: icon
// }
Glow {
anchors.fill: icon
radius: 1
samples: 17
color: app.backgroundColor
source: icon
}
ColorIcon {
id: icon
anchors.fill: parent
color: app.accentColor
// anchors.margins: app.margins / 4
// color: itemDelegate.connectedState !== null && itemDelegate.connectedState.value === false ?
// "red"
// : itemDelegate.colorStateType ? itemDelegate.colorState.value : "#00000000"
color: itemDelegate.connectedState !== null && itemDelegate.connectedState.value === false
? "red"
: app.accentColor
name: itemDelegate.connectedState !== null && itemDelegate.connectedState.value === false ?
"../images/dialog-warning-symbolic.svg"
: itemDelegate.powerState.value === true ? "../images/light-on.svg" : "../images/light-off.svg"

View File

@ -0,0 +1,291 @@
import QtQuick 2.9
import QtQuick.Controls 2.2
import QtQuick.Controls.Material 2.2
import QtQuick.Layouts 1.1
import Nymea 1.0
import "../components"
import "../customviews"
Page {
id: root
property Device device: null
property var filterTypeIds: []
header: GuhHeader {
text: qsTr("History for %1").arg(root.device.name)
onBackPressed: pageStack.pop()
HeaderButton {
imageSource: "../images/filters.svg"
color: logsModelNg.filterEnabled ? app.accentColor : keyColor
onClicked: logsModelNg.filterEnabled = !logsModelNg.filterEnabled
visible: root.filterTypeIds.length === 0
}
}
LogsModelNg {
id: logsModelNg
engine: _engine
deviceId: root.device.id
typeIds: root.filterTypeIds.length > 0
? root.filterTypeIds
: filterEnabled
? [filterDeviceModel.getData(filterComboBox.currentIndex, DeviceModel.RoleId)]
: []
live: true
property bool filterEnabled: false
}
DeviceModel {
id: filterDeviceModel
device: root.device
}
Pane {
id: filterPane
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
Material.elevation: 1
leftPadding: 0; rightPadding: 0; topPadding: 0; bottomPadding: 0
contentItem: Rectangle {
color: app.primaryColor
clip: true
RowLayout {
anchors.fill: parent
anchors.margins: app.margins
spacing: app.margins
Label {
text: qsTr("Filter by")
}
ComboBox {
id: filterComboBox
Layout.fillWidth: true
textRole: "displayName"
model: filterDeviceModel
}
}
}
}
Loader {
id: graphLoader
anchors {
left: parent.left
top: filterPane.bottom
right: parent.right
}
readonly property StateType stateType: root.device.deviceClass.stateTypes.getStateType(root.filterTypeIds[0])
readonly property bool canShowGraph: {
if (stateType === null) {
return false
}
if (stateType.unit === Types.UnitUnixTime) {
return false;
}
switch (stateType.type) {
case "Int":
case "Double":
return true;
case "Bool":
return engine.jsonRpcClient.ensureServerVersion("1.10")
}
print("not showing graph for", root.stateType.type)
return false;
}
Component.onCompleted: {
if (root.filterTypeIds.length === 0) {
return;
}
if (!canShowGraph) {
return;
}
var source;
if (engine.jsonRpcClient.ensureServerVersion("1.10")) {
source = Qt.resolvedUrl("../customviews/GenericTypeGraph.qml");
} else {
source = Qt.resolvedUrl("../customviews/GenericTypeGraphPre110.qml");
}
setSource(source, {device: root.device, stateType: stateType})
}
}
ListView {
anchors { left: parent.left; top: graphLoader.bottom; right: parent.right; bottom: parent.bottom }
clip: true
model: logsModelNg
ScrollBar.vertical: ScrollBar {}
BusyIndicator {
anchors.centerIn: parent
visible: logsModelNg.busy
}
delegate: ItemDelegate {
id: entryDelegate
width: parent.width
property StateType stateType: model.source === LogEntry.LoggingSourceStates ? root.device.deviceClass.stateTypes.getStateType(model.typeId) : null
property EventType eventType: model.source === LogEntry.LoggingSourceEvents || model.source === LogEntry.LoggingSourceStates ? root.device.deviceClass.eventTypes.getEventType(model.typeId) : null
property ActionType actionType: model.source === LogEntry.LoggingSourceActions ? root.device.deviceClass.actionTypes.getActionType(model.typeId) : null
contentItem: RowLayout {
ColorIcon {
Layout.preferredWidth: app.iconSize
Layout.preferredHeight: width
Layout.alignment: Qt.AlignVCenter
color: {
switch (model.source) {
case LogEntry.LoggingSourceStates:
case LogEntry.LoggingSourceSystem:
case LogEntry.LoggingSourceActions:
case LogEntry.LoggingSourceEvents:
return app.accentColor
case LogEntry.LoggingSourceRules:
if (model.loggingEventType === LogEntry.LoggingEventTypeActiveChange) {
return model.value === true ? "green" : keyColor
}
return app.accentColor
}
}
name: {
switch (model.source) {
case LogEntry.LoggingSourceStates:
return "../images/state.svg"
case LogEntry.LoggingSourceSystem:
return "../images/system-shutdown.svg"
case LogEntry.LoggingSourceActions:
return "../images/action.svg"
case LogEntry.LoggingSourceEvents:
return "../images/event.svg"
case LogEntry.LoggingSourceRules:
return "../images/magic.svg"
}
}
}
ColumnLayout {
RowLayout {
Label {
text: {
switch (model.source) {
case LogEntry.LoggingSourceStates:
// return entryDelegate.stateType.displayName
case LogEntry.LoggingSourceEvents:
return entryDelegate.eventType.displayName
case LogEntry.LoggingSourceActions:
return entryDelegate.actionType.displayName
}
}
Layout.fillWidth: true
elide: Text.ElideRight
}
Label {
text: Qt.formatDateTime(model.timestamp,"dd.MM.yy hh:mm:ss")
elide: Text.ElideRight
font.pixelSize: app.smallFont
enabled: false
}
}
RowLayout {
Loader {
id: valueLoader
Layout.fillWidth: true
sourceComponent: {
switch (model.source) {
case LogEntry.LoggingSourceStates:
switch (entryDelegate.stateType.type.toLowerCase()) {
case "bool":
return boolComponent;
case "color":
return colorComponent
default:
if (entryDelegate.stateType.unit == Types.UnitUnixTime) {
return dateTimeComponent
}
return labelComponent
}
case LogEntry.LoggingSourceActions:
break;
case LogEntry.LoggingSourceEvents:
break;
}
return labelComponent
}
Binding { target: valueLoader.item; property: "value"; value: model.value }
Binding {
target: entryDelegate.stateType && valueLoader.item.hasOwnProperty("unitString") ? valueLoader.item : null;
property: "unitString"
value: entryDelegate.stateType ? entryDelegate.stateType.unitString : ""
}
}
}
}
}
}
}
Component {
id: labelComponent
Label {
property var value
property string unitString
text: value + " " + unitString
font.pixelSize: app.smallFont
elide: Text.ElideRight
}
}
Component {
id: dateTimeComponent
Label {
property var value
text: Qt.formatDateTime(new Date(value * 1000), Qt.DefaultLocaleShortDate)
}
}
Component {
id: boolComponent
Item {
id: boolLed
property var value
Led {
implicitHeight: app.smallFont
on: boolLed.value === "true"
}
}
}
Component {
id: colorComponent
Item {
property var value
implicitHeight: app.smallFont
Rectangle {
height: parent.height
width: height * 2
color: parent.value
// radius: width / 2
border.color: app.foregroundColor
border.width: 1
}
}
}
}

View File

@ -6,8 +6,11 @@ import "../components"
Page {
id: root
property var device: null
readonly property var deviceClass: engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId)
property Device device: null
readonly property DeviceClass deviceClass: engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId)
property bool showLogsButton: true
property bool showDetailsButton: true
default property alias data: contentItem.data
@ -35,7 +38,13 @@ Page {
Component.onCompleted: {
thingMenu.addItem(menuEntryComponent.createObject(thingMenu, {text: qsTr("Magic"), iconSource: "../images/magic.svg", functionName: "openDeviceMagicPage"}))
thingMenu.addItem(menuEntryComponent.createObject(thingMenu, {text: qsTr("Thing details"), iconSource: "../images/info.svg", functionName: "openDeviceInfoPage"}))
if (root.showDetailsButton) {
thingMenu.addItem(menuEntryComponent.createObject(thingMenu, {text: qsTr("Thing details"), iconSource: "../images/configure.svg", functionName: "openGenericDevicePage"}))
}
if (root.showLogsButton) {
thingMenu.addItem(menuEntryComponent.createObject(thingMenu, {text: qsTr("Thing logs"), iconSource: "../images/logs.svg", functionName: "openDeviceLogPage"}))
}
if (engine.jsonRpcClient.ensureServerVersion(1.6)) {
thingMenu.addItem(menuEntryComponent.createObject(thingMenu,
{
@ -48,8 +57,8 @@ Page {
function openDeviceMagicPage() {
pageStack.push(Qt.resolvedUrl("../magic/DeviceRulesPage.qml"), {device: root.device})
}
function openDeviceInfoPage() {
pageStack.push(Qt.resolvedUrl("GenericDeviceStateDetailsPage.qml"), {device: root.device})
function openGenericDevicePage() {
pageStack.push(Qt.resolvedUrl("GenericDevicePage.qml"), {device: root.device})
}
function toggleFavorite() {
if (favoritesProxy.count === 0) {
@ -58,6 +67,9 @@ Page {
engine.tagsManager.untagDevice(root.device.id, "favorites")
}
}
function openDeviceLogPage() {
pageStack.push(Qt.resolvedUrl("DeviceLogPage.qml"), {device: root.device });
}
Component {
id: menuEntryComponent

View File

@ -1,134 +1,487 @@
import QtQuick 2.5
import QtQuick.Controls 2.1
import QtQuick.Controls 2.2
import QtQuick.Controls.Material 2.2
import QtGraphicalEffects 1.0
import QtQuick.Layouts 1.1
import Nymea 1.0
import "../components"
import "../delegates"
DevicePageBase {
id: root
showDetailsButton: false
Flickable {
function executeAction(actionTypeId, params) {
return engine.deviceManager.executeAction(root.device.id, actionTypeId, params)
}
ListView {
id: flickable
anchors.fill: parent
contentHeight: contentColumn.height
contentWidth: parent.width
clip: true
property var d: root.device
property var dc: root.deviceClass
section.property: "type"
section.delegate: ListSectionHeader {
text: {
switch (parseInt(section)) {
case DeviceModel.TypeStateType:
return qsTr("States")
case DeviceModel.TypeActionType:
return qsTr("Actions")
case DeviceModel.TypeEventType:
return qsTr("Events")
}
}
}
ColumnLayout {
id: contentColumn
model: DeviceModel {
device: root.device
}
delegate: SwipeDelegate {
id: delegate
width: parent.width
// spacing: app.margins
Repeater {
id: interfaceViewsRepeater
property bool unhandledInterface: false
readonly property StateType stateType: model.type === DeviceModel.TypeStateType ? root.deviceClass.stateTypes.getStateType(model.id) : null
readonly property ActionType actionType: model.writable ? root.deviceClass.actionTypes.getActionType(model.id) : null
readonly property EventType eventType: model.type === DeviceModel.TypeEventType ? root.deviceClass.eventTypes.getEventType(model.id) : null
// model: deviceClass.interfaces
delegate: Loader {
id: stateViewLoader
Layout.fillWidth: true
source: {
var src = "";
var options = []
switch (modelData) {
case "weather":
src = "WeatherView.qml";
break;
case "mediacontroller":
src = "MediaControllerView.qml"
break;
case "extendedvolumecontroller":
src = "ExtendedVolumeController.qml"
break;
case "temperaturesensor":
case "humiditysensor":
case "moisturesensor":
case "lightsensor":
case "conductivitysensor":
case "pressuresensor":
case "noisesensor":
case "co2sensor":
src = "SensorView.qml"
options.interfaceName = modelData;
break;
case "notifications":
src = "NotificationsView.qml"
break;
case "battery":
case "connectable":
// Handled by our base class
break;
case "sensor":
case "media":
// Ignore interfaces without any states/actions
break;
default:
print("WARNING: Unhandled interface", modelData)
interfaceViewsRepeater.unhandledInterface = true
Layout.fillWidth: true
topPadding: model.type === DeviceModel.TypeActionType ? app.margins / 2 : 0
bottomPadding: 0
contentItem: Loader {
id: inlineLoader
sourceComponent: {
switch (model.type) {
case DeviceModel.TypeStateType:
return stateComponent;
case DeviceModel.TypeActionType:
return actionComponent;
case DeviceModel.TypeEventType:
return eventComponent;
}
}
Binding {
target: inlineLoader.item
when: model.type === DeviceModel.TypeStateType
property: "stateType"
value: delegate.stateType
}
Binding {
target: inlineLoader.item
when: model.type === DeviceModel.TypeActionType
property: "actionType"
value: delegate.actionType
}
Binding {
target: inlineLoader.item
when: model.type === DeviceModel.TypeEventType
property: "eventType"
value: delegate.eventType
}
}
onClicked: pageStack.push(Qt.resolvedUrl("DeviceLogPage.qml"), {device: root.device, filterTypeIds: [model.id]})
swipe.right: RowLayout {
height: delegate.height
anchors.right: parent.right
MouseArea {
Layout.fillHeight: true
Layout.preferredWidth: height
ColorIcon {
anchors.fill: parent
anchors.margins: app.margins
name: "../images/logs.svg"
}
onClicked: {
pageStack.push(Qt.resolvedUrl("DeviceLogPage.qml"), {device: root.device, filterTypeIds: [model.id]})
}
}
}
}
}
Component {
id: stateComponent
RowLayout {
id: stateDelegate
property StateType stateType: null
readonly property State deviceState: stateType ? root.device.states.getState(stateType.id) : null
readonly property bool writable: root.deviceClass.actionTypes.getActionType(stateType.id) !== null
Label {
Layout.fillWidth: true
text: stateDelegate.stateType.displayName
elide: Text.ElideRight
}
Loader {
id: stateDelegateLoader
sourceComponent: {
switch (stateType.type.toLowerCase()) {
case "string":
if (stateDelegate.writable) {
if (stateDelegate.stateType.allowedValues !== undefined) {
return comboBoxComponent
}
return textFieldComponent;
} else {
return labelComponent;
}
case "stringlist":
return listComponent;
case "bool":
if (stateDelegate.writable) {
return switchComponent;
} else {
return ledComponent;
}
case "int":
case "double":
if (stateDelegate.stateType.unit === Types.UnitUnixTime) {
return dateTimeComponent;
}
return Qt.resolvedUrl("../customviews/" + src);
if (stateDelegate.writable) {
return spinBoxComponent;
}
return numberLabelComponent;
case "color":
return colorComponent;
default:
print("Unhandled state type", stateType.displayName, stateType.type)
}
Binding {
target: stateViewLoader.item ? stateViewLoader.item : null
property: "device"
value: device
print("GenericDevicePage: unhandled entry", stateDelegate.stateType.displayName)
}
}
Label {
visible: stateDelegate.stateType.unit !== Types.UnitUnixTime && stateDelegate.stateType.unitString.length > 0
text: stateDelegate.stateType.unitString
}
Binding {
target: stateDelegateLoader.item
property: "value"
value: root.device.states.getState(stateDelegate.stateType.id).value
}
Binding {
target: stateDelegateLoader.item && stateDelegateLoader.item.hasOwnProperty("possibleValues") ? stateDelegateLoader.item : null
property: "possibleValues"
value: stateDelegate.stateType.allowedValues
}
Binding {
target: stateDelegateLoader.item && stateDelegateLoader.item.hasOwnProperty("from") ? stateDelegateLoader.item : null
property: "from"
value: stateDelegate.stateType.minValue !== undefined ? stateDelegate.stateType.minValue : -999999999999
}
Binding {
target: stateDelegateLoader.item && stateDelegateLoader.item.hasOwnProperty("to") ? stateDelegateLoader.item : null
property: "to"
value: stateDelegate.stateType.maxValue !== undefined ? stateDelegate.stateType.maxValue : 999999999999
}
Binding {
target: stateDelegateLoader.item && stateDelegateLoader.item.hasOwnProperty("actionTypeId") ? stateDelegateLoader.item : null
property: "actionTypeId"
value: stateDelegate.stateType.id
}
Connections {
target: stateDelegateLoader.item && stateDelegateLoader.item.hasOwnProperty("changed") ? stateDelegateLoader.item : null
onChanged: {
var params = []
var param1 = {}
param1["paramTypeId"] = stateDelegate.stateType.id
param1["value"] = value;
params.push(param1)
root.executeAction(stateDelegate.stateType.id, params);
}
}
}
}
Component {
id: actionComponent
RowLayout {
id: actionDelegate
property ActionType actionType: null
property int pendingActionId: -1
property bool lastSuccess: false
Connections {
target: engine.deviceManager
onExecuteActionReply: {
if (params["id"] === actionDelegate.pendingActionId) {
print("blubb!")
pendingTimer.start();
actionDelegate.lastSuccess = params["params"]["deviceError"] === "DeviceErrorNoError"
actionDelegate.pendingActionId = -1
}
Binding {
target: stateViewLoader.item ? stateViewLoader.item : null
property: "deviceClass"
value: deviceClass
}
}
Timer { id: pendingTimer; interval: 1000; repeat: false; running: false }
Button {
text: actionType.displayName
Layout.fillWidth: true
onClicked: {
if (actionDelegate.actionType.paramTypes.count === 0) {
actionDelegate.pendingActionId = root.executeAction(actionDelegate.actionType.id, [])
} else {
var dialog = paramsDialogComponent.createObject(root, { actionType: actionDelegate.actionType })
dialog.open()
}
Binding {
target: stateViewLoader.item ? stateViewLoader.item : null
property: "interfaceName"
value: modelData
}
Component {
id: paramsDialogComponent
Dialog {
id: paramsDialog
modal: true
width: parent.width - app.margins * 2
x: (parent.width - width) / 2
y: (parent.height - height) / 2
padding: 0
property ActionType actionType: null
contentItem: ColumnLayout {
Repeater {
id: paramsRepeater
model: paramsDialog.actionType.paramTypes
delegate: ParamDelegate {
Layout.fillWidth: true
paramType: paramsDialog.actionType.paramTypes.get(index)
}
}
RowLayout {
Layout.margins: app.margins
spacing: app.margins
Button {
text: qsTr("Cancel")
Layout.fillWidth: true
onClicked: paramsDialog.close()
}
Button {
text: qsTr("OK")
Layout.fillWidth: true
onClicked: {
var params = []
for (var i = 0; i < paramsRepeater.count; i++) {
var param = {}
param["paramTypeId"] = paramsRepeater.itemAt(i).paramType.id
param["value"] = paramsRepeater.itemAt(i).value
params.push(param)
}
actionDelegate.pendingActionId = root.executeAction(paramsDialog.actionType.id, params);
paramsDialog.close();
}
}
}
}
}
}
}
Item {
Layout.preferredHeight: preferredSize
Layout.preferredWidth: preferredSize
property int preferredSize: actionDelegate.pendingActionId !== -1 || pendingTimer.running ? app.iconSize : 0
Behavior on preferredSize { NumberAnimation { duration: 100 } }
Repeater {
model: interfaceViewsRepeater.count == 0 || interfaceViewsRepeater.unhandledInterface ? deviceClass.actionTypes : null
delegate: ItemDelegate {
Layout.fillWidth: true
Layout.preferredHeight: delegateLoader.height
BusyIndicator {
anchors.fill: parent
visible: actionDelegate.pendingActionId !== -1
}
Loader {
id: delegateLoader
width: parent.width
property var actionType: deviceClass.actionTypes.get(index)
property var actionValue: device.hasState(actionType.id) ? device.states.getState(actionType.id).value : null
source: Qt.resolvedUrl("../delegates/ActionDelegate.qml")
ColorIcon {
anchors.fill: parent
visible: actionDelegate.pendingActionId === -1
name: actionDelegate.lastSuccess ? "../images/tick.svg" : "../images/close.svg"
color: actionDelegate.lastSuccess ? "green" : "red"
}
}
}
}
Binding {
target: delegateLoader.item ? delegateLoader.item : null
property: "actionType"
value: delegateLoader.actionType
}
Binding {
target: delegateLoader.item ? delegateLoader.item : null
property: "actionState"
value: delegateLoader.actionValue
Component {
id: eventComponent
RowLayout {
id: eventComponentItem
property EventType eventType: null
Label {
Layout.fillWidth: true
text: eventComponentItem.eventType.displayName
}
Rectangle {
id: flashlight
Layout.preferredHeight: app.iconSize * .8
Layout.preferredWidth: height
color: "lightgray"
radius: width / 2
border.color: app.foregroundColor
border.width: 1
SequentialAnimation on color {
id: flashlightAnimation
running: false
ColorAnimation { to: "lightgreen"; duration: 100 }
ColorAnimation { to: "lightgray"; duration: 500 }
}
}
LogsModelNg {
engine: _engine
live: true
deviceId: root.device.id
typeIds: eventComponentItem.eventType.id
onCountChanged: {
flashlightAnimation.start()
}
}
}
}
Component {
id: ledComponent
Led {
property bool value
on: value === true
}
}
Component {
id: labelComponent
Label {
property var value
text: value
}
}
Component {
id: numberLabelComponent
Label {
property var value
text: Math.round(value * 100) / 100
}
}
Component {
id: textFieldComponent
TextField {
property var value
text: value
}
}
Component {
id: listComponent
Label {
property var value
text: value.join(", ")
}
}
Component {
id: checkBoxComponent
CheckBox {
property var value
checked: value === true
enabled: false
}
}
Component {
id: switchComponent
Switch {
property var value
signal changed(var value)
checked: value === true
onClicked: {
changed(checked)
}
}
}
Component {
id: spinBoxComponent
SpinBox {
width: 150
signal changed(var value)
stepSize: (to - from) / 10
editable: true
onValueModified: {
changed(value)
}
}
}
Component {
id: comboBoxComponent
ComboBox {
property var value
property var possibleValues
signal changed(var value)
model: possibleValues
onActivated: changed(model[index])
}
}
Component {
id: colorComponent
Item {
id: colorComponentItem
implicitWidth: app.iconSize * 2
implicitHeight: app.iconSize
property var value
signal changed(var value)
Pane {
anchors.fill: parent
topPadding: 0
bottomPadding: 0
leftPadding: 0
rightPadding: 0
Material.elevation: 1
contentItem: Rectangle {
color: colorComponentItem.value
MouseArea {
anchors.fill: parent
onClicked: {
var pos = colorComponentItem.mapToItem(root, 0, colorComponentItem.height)
print("opening", colorComponentItem.value)
var colorPicker = colorPickerComponent.createObject(root, {preferredY: pos.y, colorValue: colorComponentItem.value })
colorPicker.open()
}
}
}
}
property int commandId: 0
Connections {
target: delegateLoader.item ? delegateLoader.item : null
onExecuteAction: {
engine.deviceManager.executeAction(root.device.id, model.id, params)
}
}
Connections {
target: engine.jsonRpcClient
onResponseReceived: {
if (commandId == delegateLoader.commandId) {
print("response:", response["error"])
}
Component {
id: colorPickerComponent
Dialog {
id: colorPickerDialog
modal: true
x: (parent.width - width) / 2
y: Math.min(preferredY, parent.height - height)
width: parent.width - app.margins * 2
height: 200
padding: app.margins
property var colorValue
property int preferredY: 0
contentItem: ColorPicker {
color: colorPickerDialog.colorValue
property var lastSentTime: new Date()
onColorChanged: {
var currentTime = new Date();
if (pressed && currentTime - lastSentTime > 200) {
colorComponentItem.changed(color);
}
}
}
@ -136,4 +489,12 @@ DevicePageBase {
}
}
}
Component {
id: dateTimeComponent
Label {
property var value
text: Qt.formatDateTime(new Date(value * 1000), Qt.DefaultLocaleShortDate)
}
}
}

View File

@ -1,169 +0,0 @@
import QtQuick 2.5
import QtQuick.Controls 2.1
import QtQuick.Layouts 1.1
import Nymea 1.0
import "../components"
Page {
id: root
property var device
readonly property var deviceClass: engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId)
property bool readOnly: true
header: GuhHeader {
text: "Details for " + root.device.name
onBackPressed: pageStack.pop()
}
Flickable {
anchors.fill: parent
contentHeight: statesColumn.height + app.margins*2
ColumnLayout {
id: statesColumn
anchors { left: parent.left; top: parent.top; right: parent.right; margins: app.margins }
spacing: app.margins
Repeater {
model: deviceClass.stateTypes
delegate: RowLayout {
width: parent.width
height: app.largeFont
property var stateType: deviceClass.stateTypes.get(index)
Label {
id: stateLabel
Layout.preferredWidth: parent.width / 2
text: displayName
elide: Text.ElideRight
}
Loader {
id: placeHolder
Layout.fillWidth: true
sourceComponent: {
var writable = deviceClass.actionTypes.getActionType(id) !== null;
if (root.readOnly || !writable) {
return labelComponent;
}
switch (stateType.type) {
case "Bool":
return boolComponent;
case "Int":
case "Double":
if (stateType.minValue !== undefined && stateType.maxValue !== undefined) {
return sliderComponent;
}
return textFieldComponent;
case "String":
return textFieldComponent;
case "Color":
return colorPreviewComponent;
}
console.warn("DeviceStateDetailsPage: Type delegate not implemented", stateType.type)
return null;
}
}
ColorIcon {
Layout.fillHeight: true
Layout.preferredWidth: height
name: "../images/info.svg"
MouseArea {
anchors.fill: parent
anchors.margins: -app.margins / 2
onClicked: {
pageStack.push(Qt.resolvedUrl("StateLogPage.qml"),
{device: root.device, stateType: stateType})
}
}
}
Binding {
target: placeHolder.item
when: placeHolder.item
property: "value"
value: device.states.getState(id).value
}
// Binding {
// target: placeHolder.item
// when: placeHolder.item
// property: "enabled"
// value: deviceClass.actionTypes.getActionType(id) !== null
// }
Binding {
target: placeHolder.item
when: placeHolder.item
property: "stateTypeId"
value: id
}
// Label {
// id: valueLable
// Layout.fillWidth: true
// text: device.states.getState(id).value + " " + deviceClass.stateTypes.getStateType(id).unitString
// }
}
}
}
}
Component {
id: labelComponent
Label {
property var value: ""
property var stateTypeId: null
property var unitString: deviceClass.stateTypes.getStateType(stateTypeId).unitString
text: unitString === "datetime" ? Qt.formatDateTime(new Date(value * 1000), Qt.DefaultLocaleShortDate) : value + " " + unitString
horizontalAlignment: Text.AlignHCenter
elide: Text.ElideRight
}
}
Component {
id: textFieldComponent
TextField {
property var value: ""
property var stateTypeId: null
text: value
onEditingFinished: {
executeAction(stateTypeId, text)
}
}
}
Component {
id: boolComponent
Switch {
property var value: false
property var stateTypeId: null
checked: value
onClicked: executeAction(stateTypeId, checked)
}
}
Component {
id: colorPreviewComponent
Rectangle {
property var value: "blue"
property var stateTypeId: null
color: value
implicitHeight: app.mediumFont
implicitWidth: height
radius: height / 4
}
}
function executeAction(stateTypeId, value) {
var paramList = []
var muteParam = {}
muteParam["paramTypeId"] = stateTypeId;
muteParam["value"] = value;
paramList.push(muteParam)
engine.deviceManager.executeAction(root.device.id, stateTypeId, paramList)
}
}