Merge PR #417: Make plugin issues more visible to the user

This commit is contained in:
Jenkins nymea 2020-09-05 22:41:10 +02:00
commit 1d4676b50f
28 changed files with 364 additions and 161 deletions

View File

@ -457,7 +457,7 @@ void DeviceManager::savePluginConfig(const QUuid &pluginId)
ThingGroup *DeviceManager::createGroup(Interface *interface, DevicesProxy *things) ThingGroup *DeviceManager::createGroup(Interface *interface, DevicesProxy *things)
{ {
ThingGroup* group = new ThingGroup(this, interface->createDeviceClass(), things, this); ThingGroup* group = new ThingGroup(this, interface->createDeviceClass(), things, this);
group->setSetupStatus(Device::DeviceSetupStatusComplete, QString()); group->setSetupStatus(Device::ThingSetupStatusComplete, QString());
return group; return group;
} }

View File

@ -40,46 +40,52 @@ Devices::Devices(QObject *parent) :
QList<Device *> Devices::devices() QList<Device *> Devices::devices()
{ {
return m_devices; return m_things;
} }
Device *Devices::get(int index) const Device *Devices::get(int index) const
{ {
if (index < 0 || index >= m_devices.count()) { if (index < 0 || index >= m_things.count()) {
return nullptr; return nullptr;
} }
return m_devices.at(index); return m_things.at(index);
} }
Device *Devices::getDevice(const QUuid &deviceId) const Device *Devices::getThing(const QUuid &thingId) const
{ {
foreach (Device *device, m_devices) { foreach (Device *thing, m_things) {
if (device->id() == deviceId) { if (thing->id() == thingId) {
return device; return thing;
} }
} }
return nullptr; return nullptr;
} }
Device *Devices::getDevice(const QUuid &deviceId) const
{
return getThing(deviceId);
}
int Devices::rowCount(const QModelIndex &parent) const int Devices::rowCount(const QModelIndex &parent) const
{ {
Q_UNUSED(parent) Q_UNUSED(parent)
return m_devices.count(); return m_things.count();
} }
QVariant Devices::data(const QModelIndex &index, int role) const QVariant Devices::data(const QModelIndex &index, int role) const
{ {
if (index.row() < 0 || index.row() >= m_devices.count()) if (index.row() < 0 || index.row() >= m_things.count())
return QVariant(); return QVariant();
Device *thing = m_devices.at(index.row()); Device *thing = m_things.at(index.row());
switch (role) { switch (role) {
case RoleName: case RoleName:
return thing->name(); return thing->name();
case RoleId: case RoleId:
return thing->id().toString(); return thing->id().toString();
case RoleDeviceClass: case RoleDeviceClass:
return thing->deviceClassId().toString(); case RoleThingClass:
return thing->thingClassId().toString();
case RoleParentDeviceId: case RoleParentDeviceId:
return thing->parentDeviceId().toString(); return thing->parentDeviceId().toString();
case RoleSetupStatus: case RoleSetupStatus:
@ -97,22 +103,22 @@ QVariant Devices::data(const QModelIndex &index, int role) const
void Devices::addDevice(Device *device) void Devices::addDevice(Device *device)
{ {
device->setParent(this); device->setParent(this);
beginInsertRows(QModelIndex(), m_devices.count(), m_devices.count()); beginInsertRows(QModelIndex(), m_things.count(), m_things.count());
// qDebug() << "Devices: add device" << device->name(); // qDebug() << "Devices: add device" << device->name();
m_devices.append(device); m_things.append(device);
endInsertRows(); endInsertRows();
connect(device, &Device::nameChanged, this, [device, this]() { connect(device, &Device::nameChanged, this, [device, this]() {
int idx = m_devices.indexOf(device); int idx = m_things.indexOf(device);
if (idx < 0) return; if (idx < 0) return;
emit dataChanged(index(idx), index(idx), {RoleName}); emit dataChanged(index(idx), index(idx), {RoleName});
}); });
connect(device, &Device::setupStatusChanged, this, [device, this]() { connect(device, &Device::setupStatusChanged, this, [device, this]() {
int idx = m_devices.indexOf(device); int idx = m_things.indexOf(device);
if (idx < 0) return; if (idx < 0) return;
emit dataChanged(index(idx), index(idx), {RoleSetupStatus, RoleSetupDisplayMessage}); emit dataChanged(index(idx), index(idx), {RoleSetupStatus, RoleSetupDisplayMessage});
}); });
connect(device->states(), &States::dataChanged, this, [device, this]() { connect(device->states(), &States::dataChanged, this, [device, this]() {
int idx = m_devices.indexOf(device); int idx = m_things.indexOf(device);
if (idx < 0) return; if (idx < 0) return;
emit dataChanged(index(idx), index(idx)); emit dataChanged(index(idx), index(idx));
}); });
@ -122,10 +128,10 @@ void Devices::addDevice(Device *device)
void Devices::removeDevice(Device *device) void Devices::removeDevice(Device *device)
{ {
int index = m_devices.indexOf(device); int index = m_things.indexOf(device);
beginRemoveRows(QModelIndex(), index, index); beginRemoveRows(QModelIndex(), index, index);
qDebug() << "Devices: removed device" << device->name(); qDebug() << "Devices: removed device" << device->name();
m_devices.takeAt(index)->deleteLater(); m_things.takeAt(index)->deleteLater();
endRemoveRows(); endRemoveRows();
emit countChanged(); emit countChanged();
emit thingRemoved(device); emit thingRemoved(device);
@ -134,8 +140,8 @@ void Devices::removeDevice(Device *device)
void Devices::clearModel() void Devices::clearModel()
{ {
beginResetModel(); beginResetModel();
qDeleteAll(m_devices); qDeleteAll(m_things);
m_devices.clear(); m_things.clear();
endResetModel(); endResetModel();
emit countChanged(); emit countChanged();
} }
@ -146,6 +152,7 @@ QHash<int, QByteArray> Devices::roleNames() const
roles[RoleName] = "name"; roles[RoleName] = "name";
roles[RoleId] = "id"; roles[RoleId] = "id";
roles[RoleDeviceClass] = "deviceClassId"; roles[RoleDeviceClass] = "deviceClassId";
roles[RoleThingClass] = "thingClassId";
roles[RoleParentDeviceId] = "parentDeviceId"; roles[RoleParentDeviceId] = "parentDeviceId";
roles[RoleSetupStatus] = "setupStatus"; roles[RoleSetupStatus] = "setupStatus";
roles[RoleSetupDisplayMessage] = "setupDisplayMessage"; roles[RoleSetupDisplayMessage] = "setupDisplayMessage";

View File

@ -46,6 +46,7 @@ public:
RoleId, RoleId,
RoleParentDeviceId, RoleParentDeviceId,
RoleDeviceClass, RoleDeviceClass,
RoleThingClass,
RoleSetupStatus, RoleSetupStatus,
RoleSetupDisplayMessage, RoleSetupDisplayMessage,
RoleInterfaces, RoleInterfaces,
@ -58,6 +59,7 @@ public:
QList<Device *> devices(); QList<Device *> devices();
Q_INVOKABLE Device *get(int index) const; Q_INVOKABLE Device *get(int index) const;
Q_INVOKABLE Device *getThing(const QUuid &thingId) const;
Q_INVOKABLE Device *getDevice(const QUuid &deviceId) const; Q_INVOKABLE Device *getDevice(const QUuid &deviceId) const;
int rowCount(const QModelIndex & parent = QModelIndex()) const; int rowCount(const QModelIndex & parent = QModelIndex()) const;
@ -77,7 +79,7 @@ signals:
void thingRemoved(Device *device); void thingRemoved(Device *device);
private: private:
QList<Device *> m_devices; QList<Device *> m_things;
}; };

View File

@ -293,6 +293,21 @@ void DevicesProxy::setFilterDisconnected(bool filterDisconnected)
} }
} }
bool DevicesProxy::filterSetupFailed() const
{
return m_filterSetupFailed;
}
void DevicesProxy::setFilterSetupFailed(bool filterSetupFailed)
{
if (m_filterSetupFailed != filterSetupFailed) {
m_filterSetupFailed = filterSetupFailed;
emit filterSetupFailedChanged();
invalidateFilter();
emit countChanged();
}
}
bool DevicesProxy::groupByInterface() const bool DevicesProxy::groupByInterface() const
{ {
return m_groupByInterface; return m_groupByInterface;
@ -314,14 +329,19 @@ Device *DevicesProxy::get(int index) const
} }
Device *DevicesProxy::getDevice(const QUuid &deviceId) const Device *DevicesProxy::getDevice(const QUuid &deviceId) const
{
return getThing(deviceId);
}
Device *DevicesProxy::getThing(const QUuid &thingId) const
{ {
Devices *d = qobject_cast<Devices*>(sourceModel()); Devices *d = qobject_cast<Devices*>(sourceModel());
if (d) { if (d) {
return d->getDevice(deviceId); return d->getThing(thingId);
} }
DevicesProxy *dp = qobject_cast<DevicesProxy*>(sourceModel()); DevicesProxy *dp = qobject_cast<DevicesProxy*>(sourceModel());
if (dp) { if (dp) {
return dp->getDevice(deviceId); return dp->getThing(thingId);
} }
return nullptr; return nullptr;
} }
@ -426,6 +446,12 @@ bool DevicesProxy::filterAcceptsRow(int source_row, const QModelIndex &source_pa
} }
} }
if (m_filterSetupFailed) {
if (device->setupStatus() != Device::ThingSetupStatusFailed) {
return false;
}
}
if (!m_nameFilter.isEmpty()) { if (!m_nameFilter.isEmpty()) {
if (!device->name().toLower().contains(m_nameFilter.toLower().trimmed())) { if (!device->name().toLower().contains(m_nameFilter.toLower().trimmed())) {
return false; return false;

View File

@ -65,6 +65,8 @@ class DevicesProxy : public QSortFilterProxyModel
// Setting this to true will imply filtering for "connectable" interface // Setting this to true will imply filtering for "connectable" interface
Q_PROPERTY(bool filterDisconnected READ filterDisconnected WRITE setFilterDisconnected NOTIFY filterDisconnectedChanged) Q_PROPERTY(bool filterDisconnected READ filterDisconnected WRITE setFilterDisconnected NOTIFY filterDisconnectedChanged)
Q_PROPERTY(bool filterSetupFailed READ filterSetupFailed WRITE setFilterSetupFailed NOTIFY filterSetupFailedChanged)
Q_PROPERTY(bool groupByInterface READ groupByInterface WRITE setGroupByInterface NOTIFY groupByInterfaceChanged) Q_PROPERTY(bool groupByInterface READ groupByInterface WRITE setGroupByInterface NOTIFY groupByInterfaceChanged)
public: public:
@ -115,11 +117,15 @@ public:
bool filterDisconnected() const; bool filterDisconnected() const;
void setFilterDisconnected(bool filterDisconnected); void setFilterDisconnected(bool filterDisconnected);
bool filterSetupFailed() const;
void setFilterSetupFailed(bool filterSetupFailed);
bool groupByInterface() const; bool groupByInterface() const;
void setGroupByInterface(bool groupByInterface); void setGroupByInterface(bool groupByInterface);
Q_INVOKABLE Device *get(int index) const; Q_INVOKABLE Device *get(int index) const;
Q_INVOKABLE Device *getDevice(const QUuid &deviceId) const; Q_INVOKABLE Device *getDevice(const QUuid &deviceId) const;
Q_INVOKABLE Device *getThing(const QUuid &thingId) const;
signals: signals:
void engineChanged(); void engineChanged();
@ -137,6 +143,7 @@ signals:
void showAnalogOutputsChanged(); void showAnalogOutputsChanged();
void filterBatteryCriticalChanged(); void filterBatteryCriticalChanged();
void filterDisconnectedChanged(); void filterDisconnectedChanged();
void filterSetupFailedChanged();
void groupByInterfaceChanged(); void groupByInterfaceChanged();
void countChanged(); void countChanged();
@ -160,6 +167,7 @@ private:
bool m_filterBatteryCritical = false; bool m_filterBatteryCritical = false;
bool m_filterDisconnected = false; bool m_filterDisconnected = false;
bool m_filterSetupFailed = false;
bool m_groupByInterface = false; bool m_groupByInterface = false;

View File

@ -250,16 +250,16 @@ Device* JsonTypes::unpackDevice(DeviceManager *deviceManager, const QVariantMap
QString setupStatus = deviceMap.value("setupStatus").toString(); QString setupStatus = deviceMap.value("setupStatus").toString();
QString setupDisplayMessage = deviceMap.value("setupDisplayMessage").toString(); QString setupDisplayMessage = deviceMap.value("setupDisplayMessage").toString();
if (setupStatus == "DeviceSetupStatusNone" || setupStatus == "ThingSetupStatusNone") { if (setupStatus == "DeviceSetupStatusNone" || setupStatus == "ThingSetupStatusNone") {
device->setSetupStatus(Device::DeviceSetupStatusNone, setupDisplayMessage); device->setSetupStatus(Device::ThingSetupStatusNone, setupDisplayMessage);
} else if (setupStatus == "DeviceSetupStatusInProgress" || setupStatus == "ThingSetupStatusInProgress") { } else if (setupStatus == "DeviceSetupStatusInProgress" || setupStatus == "ThingSetupStatusInProgress") {
device->setSetupStatus(Device::DeviceSetupStatusInProgress, setupDisplayMessage); device->setSetupStatus(Device::ThingSetupStatusInProgress, setupDisplayMessage);
} else if (setupStatus == "DeviceSetupStatusComplete" || setupStatus == "ThingSetupStatusComplete") { } else if (setupStatus == "DeviceSetupStatusComplete" || setupStatus == "ThingSetupStatusComplete") {
device->setSetupStatus(Device::DeviceSetupStatusComplete, setupDisplayMessage); device->setSetupStatus(Device::ThingSetupStatusComplete, setupDisplayMessage);
} else if (setupStatus == "DeviceSetupStatusFailed" || setupStatus == "ThingSetupStatusFailed") { } else if (setupStatus == "DeviceSetupStatusFailed" || setupStatus == "ThingSetupStatusFailed") {
device->setSetupStatus(Device::DeviceSetupStatusFailed, setupDisplayMessage); device->setSetupStatus(Device::ThingSetupStatusFailed, setupDisplayMessage);
} }
} else { } else {
device->setSetupStatus(deviceMap.value("setupComplete").toBool() ? Device::DeviceSetupStatusComplete : Device::DeviceSetupStatusNone, QString()); device->setSetupStatus(deviceMap.value("setupComplete").toBool() ? Device::ThingSetupStatusComplete : Device::ThingSetupStatusNone, QString());
} }
Params *params = device->params(); Params *params = device->params();

View File

@ -187,6 +187,8 @@ void registerQmlTypes() {
qmlRegisterType<InterfacesModel>(uri, 1, 0, "InterfacesModel"); qmlRegisterType<InterfacesModel>(uri, 1, 0, "InterfacesModel");
qmlRegisterType<InterfacesSortModel>(uri, 1, 0, "InterfacesSortModel"); qmlRegisterType<InterfacesSortModel>(uri, 1, 0, "InterfacesSortModel");
qmlRegisterUncreatableType<DeviceClass>(uri, 1, 0, "ThingClass", "Can't create this in QML. Get it from the ThingClasses.");
qmlRegisterUncreatableType<DeviceClasses>(uri, 1, 0, "ThingClasses", "Can't create this in QML. Get it from the ThingManager.");
qmlRegisterUncreatableType<DeviceClass>(uri, 1, 0, "DeviceClass", "Can't create this in QML. Get it from the DeviceClasses."); qmlRegisterUncreatableType<DeviceClass>(uri, 1, 0, "DeviceClass", "Can't create this in QML. Get it from the DeviceClasses.");
qmlRegisterUncreatableType<DeviceClasses>(uri, 1, 0, "DeviceClasses", "Can't create this in QML. Get it from the DeviceManager."); qmlRegisterUncreatableType<DeviceClasses>(uri, 1, 0, "DeviceClasses", "Can't create this in QML. Get it from the DeviceManager.");
qmlRegisterType<DeviceClassesProxy>(uri, 1, 0, "DeviceClassesProxy"); qmlRegisterType<DeviceClassesProxy>(uri, 1, 0, "DeviceClassesProxy");

View File

@ -36,8 +36,8 @@
ThingGroup::ThingGroup(DeviceManager *deviceManager, DeviceClass *deviceClass, DevicesProxy *devices, QObject *parent): ThingGroup::ThingGroup(DeviceManager *deviceManager, DeviceClass *deviceClass, DevicesProxy *devices, QObject *parent):
Device(deviceManager, deviceClass, QUuid::createUuid(), parent), Device(deviceManager, deviceClass, QUuid::createUuid(), parent),
m_deviceManager(deviceManager), m_thingManager(deviceManager),
m_devices(devices) m_things(devices)
{ {
deviceClass->setParent(this); deviceClass->setParent(this);
@ -52,11 +52,11 @@ ThingGroup::ThingGroup(DeviceManager *deviceManager, DeviceClass *deviceClass, D
syncStates(); syncStates();
setName(deviceClass->displayName()); setName(deviceClass->displayName());
connect(devices, &DevicesProxy::dataChanged, this, [this](const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles){ connect(devices, &DevicesProxy::dataChanged, this, [this](const QModelIndex &/*topLeft*/, const QModelIndex &/*bottomRight*/, const QVector<int> &/*roles*/){
syncStates(); syncStates();
}); });
connect(m_deviceManager, &DeviceManager::executeActionReply, this, [this](const QVariantMap &params){ connect(m_thingManager, &DeviceManager::executeActionReply, this, [this](const QVariantMap &params){
int returningId = params.value("id").toInt(); int returningId = params.value("id").toInt();
foreach (int id, m_pendingActions.keys()) { foreach (int id, m_pendingActions.keys()) {
if (m_pendingActions.value(id).contains(returningId)) { if (m_pendingActions.value(id).contains(returningId)) {
@ -77,9 +77,9 @@ int ThingGroup::executeAction(const QString &actionName, const QVariantList &par
QList<int> pendingIds; QList<int> pendingIds;
qDebug() << "Execute action for group:" << this; qDebug() << "Execute action for group:" << this;
for (int i = 0; i < m_devices->rowCount(); i++) { for (int i = 0; i < m_things->rowCount(); i++) {
Device *device = m_devices->get(i); Device *device = m_things->get(i);
if (device->setupStatus() != Device::DeviceSetupStatusComplete) { if (device->setupStatus() != Device::ThingSetupStatusComplete) {
continue; continue;
} }
ActionType *actionType = device->thingClass()->actionTypes()->findByName(actionName); ActionType *actionType = device->thingClass()->actionTypes()->findByName(actionName);
@ -101,7 +101,7 @@ int ThingGroup::executeAction(const QString &actionName, const QVariantList &par
qDebug() << "Initial params" << params; qDebug() << "Initial params" << params;
qDebug() << "Executing" << device->id() << actionType->name() << finalParams; qDebug() << "Executing" << device->id() << actionType->name() << finalParams;
int id = m_deviceManager->executeAction(device->id(), actionType->id(), finalParams); int id = m_thingManager->executeAction(device->id(), actionType->id(), finalParams);
pendingIds.append(id); pendingIds.append(id);
} }
m_pendingActions.insert(++m_idCounter, pendingIds); m_pendingActions.insert(++m_idCounter, pendingIds);
@ -118,8 +118,8 @@ void ThingGroup::syncStates()
QVariant value; QVariant value;
int count = 0; int count = 0;
for (int j = 0; j < m_devices->rowCount(); j++) { for (int j = 0; j < m_things->rowCount(); j++) {
Device *d = m_devices->get(j); Device *d = m_things->get(j);
// Skip things that don't have the required state // Skip things that don't have the required state
StateType *ds = d->thingClass()->stateTypes()->findByName(stateType->name()); StateType *ds = d->thingClass()->stateTypes()->findByName(stateType->name());
if (!ds) { if (!ds) {

View File

@ -53,8 +53,8 @@ signals:
void actionExecutionFinished(int id, const QString &status); void actionExecutionFinished(int id, const QString &status);
private: private:
DeviceManager* m_deviceManager = nullptr; DeviceManager* m_thingManager = nullptr;
DevicesProxy* m_devices = nullptr; DevicesProxy* m_things = nullptr;
int m_idCounter = 0; int m_idCounter = 0;
QHash<int, QList<int>> m_pendingActions; QHash<int, QList<int>> m_pendingActions;

View File

@ -34,9 +34,9 @@
#include <QDebug> #include <QDebug>
Device::Device(DeviceManager *deviceManager, DeviceClass *thingClass, const QUuid &parentId, QObject *parent) : Device::Device(DeviceManager *thingManager, DeviceClass *thingClass, const QUuid &parentId, QObject *parent) :
QObject(parent), QObject(parent),
m_deviceManager(deviceManager), m_thingManager(thingManager),
m_parentId(parentId), m_parentId(parentId),
m_thingClass(thingClass) m_thingClass(thingClass)
{ {
@ -83,7 +83,7 @@ bool Device::isChild() const
return !m_parentId.isNull(); return !m_parentId.isNull();
} }
Device::DeviceSetupStatus Device::setupStatus() const Device::ThingSetupStatus Device::setupStatus() const
{ {
return m_setupStatus; return m_setupStatus;
} }
@ -93,7 +93,7 @@ QString Device::setupDisplayMessage() const
return m_setupDisplayMessage; return m_setupDisplayMessage;
} }
void Device::setSetupStatus(Device::DeviceSetupStatus setupStatus, const QString &displayMessage) void Device::setSetupStatus(Device::ThingSetupStatus setupStatus, const QString &displayMessage)
{ {
if (m_setupStatus != setupStatus || m_setupDisplayMessage != displayMessage) { if (m_setupStatus != setupStatus || m_setupDisplayMessage != displayMessage) {
m_setupStatus = setupStatus; m_setupStatus = setupStatus;
@ -215,7 +215,7 @@ int Device::executeAction(const QString &actionName, const QVariantList &params)
} }
finalParams.append(param); finalParams.append(param);
} }
return m_deviceManager->executeAction(m_id, actionType->id(), finalParams); return m_thingManager->executeAction(m_id, actionType->id(), finalParams);
} }
QDebug operator<<(QDebug &dbg, Device *thing) QDebug operator<<(QDebug &dbg, Device *thing)

View File

@ -50,7 +50,7 @@ class Device : public QObject
Q_PROPERTY(QUuid parentDeviceId READ parentDeviceId CONSTANT) Q_PROPERTY(QUuid parentDeviceId READ parentDeviceId CONSTANT)
Q_PROPERTY(bool isChild READ isChild CONSTANT) Q_PROPERTY(bool isChild READ isChild CONSTANT)
Q_PROPERTY(QString name READ name NOTIFY nameChanged) Q_PROPERTY(QString name READ name NOTIFY nameChanged)
Q_PROPERTY(DeviceSetupStatus setupStatus READ setupStatus NOTIFY setupStatusChanged) Q_PROPERTY(ThingSetupStatus setupStatus READ setupStatus NOTIFY setupStatusChanged)
Q_PROPERTY(QString setupDisplayMessage READ setupDisplayMessage NOTIFY setupStatusChanged) Q_PROPERTY(QString setupDisplayMessage READ setupDisplayMessage NOTIFY setupStatusChanged)
Q_PROPERTY(Params *params READ params NOTIFY paramsChanged) Q_PROPERTY(Params *params READ params NOTIFY paramsChanged)
Q_PROPERTY(Params *settings READ settings NOTIFY settingsChanged) Q_PROPERTY(Params *settings READ settings NOTIFY settingsChanged)
@ -59,15 +59,15 @@ class Device : public QObject
Q_PROPERTY(DeviceClass *thingClass READ thingClass CONSTANT) Q_PROPERTY(DeviceClass *thingClass READ thingClass CONSTANT)
public: public:
enum DeviceSetupStatus { enum ThingSetupStatus {
DeviceSetupStatusNone, ThingSetupStatusNone,
DeviceSetupStatusInProgress, ThingSetupStatusInProgress,
DeviceSetupStatusComplete, ThingSetupStatusComplete,
DeviceSetupStatusFailed ThingSetupStatusFailed
}; };
Q_ENUM(DeviceSetupStatus) Q_ENUM(ThingSetupStatus)
explicit Device(DeviceManager *deviceManager, DeviceClass *thingClass, const QUuid &parentId = QUuid(), QObject *parent = nullptr); explicit Device(DeviceManager *thingManager, DeviceClass *thingClass, const QUuid &parentId = QUuid(), QObject *parent = nullptr);
QUuid id() const; QUuid id() const;
void setId(const QUuid &id); void setId(const QUuid &id);
@ -80,9 +80,9 @@ public:
QUuid parentDeviceId() const; QUuid parentDeviceId() const;
bool isChild() const; bool isChild() const;
DeviceSetupStatus setupStatus() const; Device::ThingSetupStatus setupStatus() const;
QString setupDisplayMessage() const; QString setupDisplayMessage() const;
void setSetupStatus(DeviceSetupStatus setupStatus, const QString &displayMessage); void setSetupStatus(Device::ThingSetupStatus setupStatus, const QString &displayMessage);
Params *params() const; Params *params() const;
void setParams(Params *params); void setParams(Params *params);
@ -115,11 +115,11 @@ signals:
private: private:
protected: protected:
DeviceManager *m_deviceManager = nullptr; DeviceManager *m_thingManager = nullptr;
QString m_name; QString m_name;
QUuid m_id; QUuid m_id;
QUuid m_parentId; QUuid m_parentId;
DeviceSetupStatus m_setupStatus = DeviceSetupStatusNone; ThingSetupStatus m_setupStatus = ThingSetupStatusNone;
QString m_setupDisplayMessage; QString m_setupDisplayMessage;
Params *m_params = nullptr; Params *m_params = nullptr;
Params *m_settings = nullptr; Params *m_settings = nullptr;

View File

@ -125,8 +125,8 @@ int main(int argc, char *argv[])
PushNotifications::instance()->connectClient(); PushNotifications::instance()->connectClient();
qmlRegisterSingletonType<PushNotifications>("Nymea", 1, 0, "PushNotifications", PushNotifications::pushNotificationsProvider); qmlRegisterSingletonType<PushNotifications>("Nymea", 1, 0, "PushNotifications", PushNotifications::pushNotificationsProvider);
qmlRegisterSingletonType<AppLogController>("Nymea", 1, 0, "AppLogController", AppLogController::appLogControllerProvider); qmlRegisterSingletonType<AppLogController>("Nymea", 1, 0, "AppLogController", AppLogController::appLogControllerProvider);
qmlRegisterSingletonType(QUrl("qrc:///ui/utils/NymeaUtils.qml"), "Nymea", 1, 0, "NymeaUtils" );
#ifdef BRANDING #ifdef BRANDING
engine->rootContext()->setContextProperty("appBranding", BRANDING); engine->rootContext()->setContextProperty("appBranding", BRANDING);

View File

@ -160,3 +160,8 @@ BR=$$BRANDING
target.path = /usr/bin target.path = /usr/bin
INSTALLS += target INSTALLS += target
contains(ANDROID_TARGET_ARCH,) {
ANDROID_ABIS = \
armeabi-v7a
}

View File

@ -218,5 +218,9 @@
<file>ui/mainviews/MediaView.qml</file> <file>ui/mainviews/MediaView.qml</file>
<file>ui/components/ShuffleRepeatVolumeControl.qml</file> <file>ui/components/ShuffleRepeatVolumeControl.qml</file>
<file>ui/components/MediaBrowser.qml</file> <file>ui/components/MediaBrowser.qml</file>
<file>ui/utils/NymeaUtils.qml</file>
<file>ui/components/ConnectionStatusIcon.qml</file>
<file>ui/components/BatteryStatusIcon.qml</file>
<file>ui/components/SetupStatusIcon.qml</file>
</qresource> </qresource>
</RCC> </RCC>

View File

@ -0,0 +1,28 @@
import QtQuick 2.9
import Nymea 1.0
ColorIcon {
id: root
property Thing thing: null
readonly property bool hasBattery: batteryCriticalState !== null
readonly property bool hasBatteryLevel: batteryLevelState !== null
readonly property bool isCritical: batteryCriticalState && batteryCriticalState.value === true
readonly property int batteryLevel: batteryLevelState ? batteryLevelState.value : 0
readonly property State batteryCriticalState: thing.stateByName("batteryCritical")
readonly property State batteryLevelState: thing.stateByName("batteryLevel")
name: {
if (!hasBatteryLevel) {
if (isCritical) {
return "../images/battery/battery-020.svg"
}
return "../images/battery/battery-100.svg"
}
var rounded = Math.round(batteryLevel / 10) * 10
return "../images/battery/battery-" + NymeaUtils.pad(rounded, 3)
}
}

View File

@ -0,0 +1,35 @@
import QtQuick 2.9
import Nymea 1.0
ColorIcon {
id: root
property Thing thing: null
readonly property bool isConnected: connectedState === null || connectedState.value === true
readonly property bool isWireless: thing.thingClass.interfaces.indexOf("wirelessconnectable") >= 0
readonly property bool hasSignalStrength: signalStrengthState !== null
readonly property State connectedState: thing.stateByName("connected")
readonly property State signalStrengthState: thing.stateByName("signalStrength")
name: {
if (!isWireless) {
return connectedState && connectedState.value === true ? "../images/network-wired.svg" : "../images/network-wired-offline.svg"
}
if (connectedState && connectedState.value === false) {
return "../images/network-wifi-offline.svg"
}
if (signalStrengthState && signalStrengthState.value === -1) {
return "../images/network-wifi.svg"
}
return "../images/nm-signal-" + NymeaUtils.pad(Math.round(signalStrengthState.value * 4 / 100) * 25, 2) + ".svg"
}
color: connectedState && connectedState.value === false
? "red"
: signalStrengthState && signalStrengthState.value < 20
? "orange" : keyColor
}

View File

@ -32,6 +32,7 @@ import QtQuick 2.9
import QtQuick.Controls 2.2 import QtQuick.Controls 2.2
import QtQuick.Controls.Material 2.2 import QtQuick.Controls.Material 2.2
import QtQuick.Layouts 1.3 import QtQuick.Layouts 1.3
import Nymea 1.0
Item { Item {
id: root id: root
@ -42,6 +43,9 @@ Item {
property alias backgroundImage: background.source property alias backgroundImage: background.source
property string text property string text
property bool disconnected: false property bool disconnected: false
property bool isWireless: false
property int signalStrength: 0
property int setupStatus: Thing.ThingSetupStatusNone
property bool batteryCritical: false property bool batteryCritical: false
property alias contentItem: innerContent.children property alias contentItem: innerContent.children
@ -143,18 +147,26 @@ Item {
Row { Row {
id: quickAlertPane id: quickAlertPane
anchors { top: parent.top; right: parent.right; margins: app.margins } anchors { top: parent.top; right: parent.right; margins: app.margins }
spacing: app.margins / 2
ColorIcon { ColorIcon {
height: app.iconSize / 2 height: app.iconSize / 2
width: height width: height
name: "../images/dialog-warning-symbolic.svg" name: root.isWireless ? "../images/network-wifi-offline.svg" : "../images/network-wired-offline.svg"
color: "red" color: root.disconnected ? "red" : "orange"
visible: root.disconnected visible: root.setupStatus == Thing.ThingSetupStatusComplete && (root.disconnected || (root.isWireless && root.signalStrength < 20))
}
ColorIcon {
height: app.iconSize / 2
width: height
name: root.setupStatus === Thing.ThingSetupStatusFailed ? "../images/dialog-warning-symbolic.svg" : "../images/settings.svg"
color: root.setupStatus === Thing.ThingSetupStatusFailed ? "red" : keyColor
visible: root.setupStatus === Thing.ThingSetupStatusFailed || root.setupStatus === Thing.ThingSetupStatusInProgress
} }
ColorIcon { ColorIcon {
height: app.iconSize / 2 height: app.iconSize / 2
width: height width: height
name: "../images/battery/battery-010.svg" name: "../images/battery/battery-010.svg"
visible: root.batteryCritical visible: root.setupStatus == Thing.ThingSetupStatusComplete && root.batteryCritical
} }
} }
} }

View File

@ -41,8 +41,7 @@ RowLayout {
property Thing thing: null property Thing thing: null
property int iconSize: app.iconSize * 1.5 property int iconSize: app.iconSize * 1.5
readonly property StateType playbackStateType: thing ? thing.thingClass.stateTypes.findByName("playbackStatus") : null readonly property State playbackState: thing.stateByName("playbackStatus")
readonly property State playbackState: playbackStateType ? thing.states.getState(playbackStateType.id) : null
function executeAction(actionName, params) { function executeAction(actionName, params) {
if (params === undefined) { if (params === undefined) {
@ -58,7 +57,7 @@ RowLayout {
Layout.preferredWidth: height Layout.preferredWidth: height
imageSource: "../images/media-skip-backward.svg" imageSource: "../images/media-skip-backward.svg"
longpressImageSource: "../images/media-seek-backward.svg" longpressImageSource: "../images/media-seek-backward.svg"
enabled: root.playbackState.value !== "Stopped" enabled: root.playbackState && root.playbackState.value !== "Stopped"
opacity: enabled ? 1 : .5 opacity: enabled ? 1 : .5
repeat: true repeat: true
@ -75,7 +74,7 @@ RowLayout {
Layout.preferredWidth: height Layout.preferredWidth: height
imageSource: root.playbackState && root.playbackState.value === "Playing" ? "../images/media-playback-pause.svg" : "../images/media-playback-start.svg" imageSource: root.playbackState && root.playbackState.value === "Playing" ? "../images/media-playback-pause.svg" : "../images/media-playback-start.svg"
longpressImageSource: "../images/media-playback-stop.svg" longpressImageSource: "../images/media-playback-stop.svg"
longpressEnabled: root.playbackState.value !== "Stopped" longpressEnabled: root.playbackState && root.playbackState.value !== "Stopped"
onClicked: { onClicked: {
if (root.playbackState.value === "Playing") { if (root.playbackState.value === "Playing") {
@ -95,7 +94,7 @@ RowLayout {
Layout.preferredWidth: height Layout.preferredWidth: height
imageSource: "../images/media-skip-forward.svg" imageSource: "../images/media-skip-forward.svg"
longpressImageSource: "../images/media-seek-forward.svg" longpressImageSource: "../images/media-seek-forward.svg"
enabled: root.playbackState.value !== "Stopped" enabled: root.playbackState && root.playbackState.value !== "Stopped"
opacity: enabled ? 1 : .5 opacity: enabled ? 1 : .5
repeat: true repeat: true
onClicked: { onClicked: {

View File

@ -0,0 +1,16 @@
import QtQuick 2.9
import Nymea 1.0
ColorIcon {
id: root
property Thing thing: null
readonly property int setupStatus: thing.setupStatus
readonly property bool setupInProgress: setupStatus == Thing.ThingSetupStatusInProgress
readonly property bool setupFailed: setupStatus == Thing.ThingSetupStatusFailed
name: setupFailed ? "../images/dialog-warning-symbolic.svg"
: setupInProgress ? "../images/settings.svg" : "../images/tick.svg"
color: setupFailed ? "red" : keyColor
}

View File

@ -41,7 +41,9 @@ MainPageTile {
iconName: iface ? interfaceToIcon(iface.name) : interfaceToIcon("uncategorized") iconName: iface ? interfaceToIcon(iface.name) : interfaceToIcon("uncategorized")
iconColor: app.accentColor iconColor: app.accentColor
disconnected: devicesSubProxyConnectables.count > 0 disconnected: devicesSubProxyConnectables.count > 0
isWireless: devicesSubProxyConnectables.count > 0 && devicesSubProxyConnectables.get(0).thingClass.interfaces.indexOf("wirelessconnectable") >= 0
batteryCritical: devicesSubProxyBattery.count > 0 batteryCritical: devicesSubProxyBattery.count > 0
setupStatus: thingsSubProxySetupFailure.count > 0 ? Thing.ThingSetupStatusFailed : Thing.ThingSetupStatusComplete
property Interface iface: null property Interface iface: null
property alias filterTagId: devicesProxy.filterTagId property alias filterTagId: devicesProxy.filterTagId
@ -118,6 +120,12 @@ MainPageTile {
parentProxy: devicesProxy parentProxy: devicesProxy
filterBatteryCritical: true filterBatteryCritical: true
} }
DevicesProxy {
id: thingsSubProxySetupFailure
engine: _engine
parentProxy: devicesProxy
filterSetupFailed: true
}
property int currentDeviceIndex: 0 property int currentDeviceIndex: 0
readonly property Device currentDevice: devicesProxy.get(currentDeviceIndex) readonly property Device currentDevice: devicesProxy.get(currentDeviceIndex)

View File

@ -37,22 +37,34 @@ import Nymea 1.0
NymeaListItemDelegate { NymeaListItemDelegate {
id: root id: root
width: parent.width width: parent.width
iconName: device && device.deviceClass ? app.interfacesToIcon(device.deviceClass.interfaces) : "" iconName: thing && thing.thingClass ? app.interfacesToIcon(thing.thingClass.interfaces) : ""
text: device ? device.name : "" text: thing ? thing.name : ""
progressive: true progressive: true
secondaryIconName: batteryCritical ? "../images/battery/battery-010.svg" : "" secondaryIconName: batteryCritical ? "../images/battery/battery-010.svg" : ""
tertiaryIconName: disconnected ? "../images/dialog-warning-symbolic.svg" : "" tertiaryIconName: thing.setupStatus == Thing.ThingSetupStatusFailed
tertiaryIconColor: "red" ? "../images/dialog-warning-symbolic.svg"
: thing.setupStatus == Thing.ThingSetupStatusInProgress
? "../images/settings.svg"
: disconnected
? isWireless
? "../images/network-wifi-offline.svg" : "../images/network-wired-offline.svg"
: ""
tertiaryIconColor: thing.setupStatus == Thing.ThingSetupStatusInProgress ? iconKeyColor : "red"
property Device device: null property Device device: null
property Thing thing: device
readonly property bool hasBatteryInterface: device && device.deviceClass.interfaces.indexOf("battery") > 0 readonly property bool hasBatteryInterface: thing && thing.thingClass.interfaces.indexOf("battery") > 0
readonly property StateType batteryCriticalStateType: hasBatteryInterface ? device.deviceClass.stateTypes.findByName("batteryCritical") : null readonly property StateType batteryCriticalStateType: hasBatteryInterface ? thing.thingClass.stateTypes.findByName("batteryCritical") : null
readonly property State batteryCriticalState: batteryCriticalStateType ? device.states.getState(batteryCriticalStateType.id) : null readonly property State batteryCriticalState: batteryCriticalStateType ? thing.states.getState(batteryCriticalStateType.id) : null
readonly property bool batteryCritical: batteryCriticalState && batteryCriticalState.value === true readonly property bool batteryCritical: batteryCriticalState && batteryCriticalState.value === true
readonly property bool hasConnectableInterface: device && device.deviceClass.interfaces.indexOf("connectable") > 0 readonly property bool hasConnectableInterface: thing && thing.thingClass.interfaces.indexOf("connectable") > 0
readonly property StateType connectedStateType: hasConnectableInterface ? device.deviceClass.stateTypes.findByName("connected") : null readonly property StateType connectedStateType: hasConnectableInterface ? thing.thingClass.stateTypes.findByName("connected") : null
readonly property State connectedState: connectedStateType ? device.states.getState(connectedStateType.id) : null readonly property State connectedState: connectedStateType ? thing.states.getState(connectedStateType.id) : null
readonly property bool disconnected: connectedState && connectedState.value === false ? true : false readonly property bool disconnected: connectedState && connectedState.value === false ? true : false
readonly property bool isWireless: root.thing.thingClass.interfaces.indexOf("wirelessconnectable") >= 0
} }

View File

@ -40,14 +40,18 @@ MainPageTile {
text: device.name.toUpperCase() text: device.name.toUpperCase()
iconName: app.interfacesToIcon(deviceClass.interfaces) iconName: app.interfacesToIcon(deviceClass.interfaces)
iconColor: app.accentColor iconColor: app.accentColor
isWireless: deviceClass.interfaces.indexOf("wirelessconnectable") >= 0
batteryCritical: batteryCriticalState && batteryCriticalState.value === true batteryCritical: batteryCriticalState && batteryCriticalState.value === true
disconnected: connectedState && connectedState.value === false disconnected: connectedState && connectedState.value === false
signalStrength: signalStrengthState ? signalStrengthState.value : -1
setupStatus: device.setupStatus
backgroundImage: artworkState && artworkState.value.length > 0 ? artworkState.value : "" backgroundImage: artworkState && artworkState.value.length > 0 ? artworkState.value : ""
property Device device: null property Device device: null
readonly property DeviceClass deviceClass: device ? engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId) : null readonly property DeviceClass deviceClass: device ? engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId) : null
readonly property State connectedState: deviceClass.interfaces.indexOf("connectable") >= 0 ? device.states.getState(deviceClass.stateTypes.findByName("connected").id) : null readonly property State connectedState: deviceClass.interfaces.indexOf("connectable") >= 0 ? device.states.getState(deviceClass.stateTypes.findByName("connected").id) : null
readonly property State signalStrengthState: device.stateByName("signalStrength")
readonly property State batteryCriticalState: deviceClass.interfaces.indexOf("battery") >= 0 ? device.states.getState(deviceClass.stateTypes.findByName("batteryCritical").id) : null readonly property State batteryCriticalState: deviceClass.interfaces.indexOf("battery") >= 0 ? device.states.getState(deviceClass.stateTypes.findByName("batteryCritical").id) : null
readonly property State artworkState: deviceClass.interfaces.indexOf("mediametadataprovider") >= 0 ? device.states.getState(deviceClass.stateTypes.findByName("artwork").id) : null readonly property State artworkState: deviceClass.interfaces.indexOf("mediametadataprovider") >= 0 ? device.states.getState(deviceClass.stateTypes.findByName("artwork").id) : null

View File

@ -38,32 +38,32 @@ import "../components"
Page { Page {
id: root id: root
property alias shownInterfaces: devicesProxyInternal.shownInterfaces property alias shownInterfaces: thingsProxyInternal.shownInterfaces
property alias hiddenInterfaces: devicesProxyInternal.hiddenInterfaces property alias hiddenInterfaces: thingsProxyInternal.hiddenInterfaces
property alias filterTagId: devicesProxyInternal.filterTagId property alias filterTagId: thingsProxyInternal.filterTagId
Component.onCompleted: { Component.onCompleted: {
if (devicesProxyInternal.count === 1) { if (thingsProxyInternal.count === 1) {
enterPage(0, true) enterPage(0, true)
} }
} }
property var devicesProxy: devicesProxyInternal property var devicesProxy: thingsProxyInternal
property var thingsProxy: thingsProxyInternal
function enterPage(index, replace) { function enterPage(index, replace) {
var device = devicesProxy.get(index); var thing = thingsProxy.get(index);
var deviceClass = engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId);
var page = app.interfaceListToDevicePage(root.shownInterfaces); var page = app.interfaceListToDevicePage(root.shownInterfaces);
// var page = "GenericDevicePage.qml"; // var page = "GenericDevicePage.qml";
if (replace) { if (replace) {
pageStack.replace(Qt.resolvedUrl("../devicepages/" + page), {device: devicesProxy.get(index)}) pageStack.replace(Qt.resolvedUrl("../devicepages/" + page), {thing: thingsProxy.get(index)})
} else { } else {
pageStack.push(Qt.resolvedUrl("../devicepages/" + page), {device: devicesProxy.get(index)}) pageStack.push(Qt.resolvedUrl("../devicepages/" + page), {thing: thingsProxy.get(index)})
} }
} }
DevicesProxy { DevicesProxy {
id: devicesProxyInternal id: thingsProxyInternal
engine: _engine engine: _engine
} }
} }

View File

@ -64,14 +64,13 @@ DeviceListPageBase {
property bool inline: width > 500 property bool inline: width > 500
property Device device: devicesProxy.getDevice(model.id) property Thing thing: thingsProxy.getThing(model.id)
property DeviceClass deviceClass: device.deviceClass
readonly property StateType playbackStateType: deviceClass.stateTypes.findByName("playbackStatus") readonly property StateType playbackStateType: thing.thingClass.stateTypes.findByName("playbackStatus")
readonly property State playbackState: playbackStateType ? device.states.getState(playbackStateType.id) : null readonly property State playbackState: thing.stateByName("playbackStatus")
readonly property StateType playerTypeStateType: deviceClass.stateTypes.findByName("playerType") readonly property StateType playerTypeStateType: thing.thingClass.stateTypes.findByName("playerType")
readonly property State playerTypeState: playerTypeStateType ? device.states.getState(playerTypeStateType.id) : null readonly property State playerTypeState: thing.stateByName("playerType")
bottomPadding: index === root.devicesProxy.count - 1 ? topPadding : 0 bottomPadding: index === root.devicesProxy.count - 1 ? topPadding : 0
contentItem: Pane { contentItem: Pane {
@ -100,18 +99,23 @@ DeviceListPageBase {
text: model.name text: model.name
elide: Text.ElideRight elide: Text.ElideRight
} }
ColorIcon { BatteryStatusIcon {
Layout.preferredHeight: app.iconSize * .5 Layout.preferredHeight: app.iconSize * .5
Layout.preferredWidth: height Layout.preferredWidth: height
name: "../images/battery/battery-020.svg" thing: itemDelegate.thing
visible: itemDelegate.deviceClass.interfaces.indexOf("battery") >= 0 && itemDelegate.device.states.getState(itemDelegate.deviceClass.stateTypes.findByName("batteryCritical").id).value === true visible: itemDelegate.thing.setupStatus == Thing.ThingSetupStatusComplete && (hasBatteryLevel || isCritical)
} }
ColorIcon { ConnectionStatusIcon {
Layout.preferredHeight: app.iconSize * .5 Layout.preferredHeight: app.iconSize * .5
Layout.preferredWidth: height Layout.preferredWidth: height
name: "../images/dialog-warning-symbolic.svg" thing: itemDelegate.thing
visible: itemDelegate.deviceClass.interfaces.indexOf("connectable") >= 0 && itemDelegate.device.states.getState(itemDelegate.deviceClass.stateTypes.findByName("connected").id).value === false visible: itemDelegate.thing.setupStatus == Thing.ThingSetupStatusComplete && (hasSignalStrength || !isConnected)
color: "red" }
SetupStatusIcon {
Layout.preferredHeight: app.iconSize * .5
Layout.preferredWidth: height
thing: itemDelegate.thing
visible: setupFailed || setupInProgress
} }
} }
@ -124,28 +128,28 @@ DeviceListPageBase {
Layout.fillWidth: true Layout.fillWidth: true
text: itemDelegate.playbackState.value === "Stopped" ? text: itemDelegate.playbackState.value === "Stopped" ?
qsTr("No playback") qsTr("No playback")
: itemDelegate.device.states.getState(itemDelegate.deviceClass.stateTypes.findByName("title").id).value : itemDelegate.thing.stateByName("title").value
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
// font.pixelSize: app.largeFont // font.pixelSize: app.largeFont
elide: Text.ElideRight elide: Text.ElideRight
} }
Label { Label {
Layout.fillWidth: true Layout.fillWidth: true
text: itemDelegate.device.states.getState(itemDelegate.deviceClass.stateTypes.findByName("artist").id).value text: itemDelegate.thing.stateByName("artist").value
font.pixelSize: app.smallFont font.pixelSize: app.smallFont
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
elide: Text.ElideRight elide: Text.ElideRight
} }
Label { Label {
Layout.fillWidth: true Layout.fillWidth: true
text: itemDelegate.device.states.getState(itemDelegate.deviceClass.stateTypes.findByName("collection").id).value text: itemDelegate.thing.stateByName("collection").value
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
font.pixelSize: app.smallFont font.pixelSize: app.smallFont
elide: Text.ElideRight elide: Text.ElideRight
} }
MediaControls { MediaControls {
visible: itemDelegate.deviceClass.interfaces.indexOf("mediacontroller") >= 0 visible: itemDelegate.thing.thingClass.interfaces.indexOf("mediacontroller") >= 0
thing: itemDelegate.device thing: itemDelegate.thing
} }
} }
Item { Item {
@ -159,8 +163,7 @@ DeviceListPageBase {
id: artworkImage id: artworkImage
width: artworkImage.sourceSize.width * height / artworkImage.sourceSize.height width: artworkImage.sourceSize.width * height / artworkImage.sourceSize.height
anchors { top: parent.top; right: parent.right; bottom: parent.bottom } anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
readonly property StateType artworkStateType: device ? device.deviceClass.stateTypes.findByName("artwork") : null readonly property State artworkState: thing.stateByName("artwork")
readonly property State artworkState: artworkStateType ? device.states.getState(artworkStateType.id) : null
source: artworkState ? artworkState.value : "" source: artworkState ? artworkState.value : ""
} }
Rectangle { Rectangle {

View File

@ -45,7 +45,7 @@ DeviceListPageBase {
ListView { ListView {
anchors.fill: parent anchors.fill: parent
model: root.devicesProxy model: root.thingsProxy
delegate: ItemDelegate { delegate: ItemDelegate {
id: itemDelegate id: itemDelegate
@ -53,8 +53,7 @@ DeviceListPageBase {
property bool inline: width > 500 property bool inline: width > 500
property Device device: devicesProxy.getDevice(model.id) property Thing thing: thingsProxy.getThing(model.id)
property DeviceClass deviceClass: device.deviceClass
bottomPadding: index === ListView.view.count - 1 ? topPadding : 0 bottomPadding: index === ListView.view.count - 1 ? topPadding : 0
contentItem: Pane { contentItem: Pane {
@ -82,21 +81,25 @@ DeviceListPageBase {
text: model.name text: model.name
elide: Text.ElideRight elide: Text.ElideRight
} }
ColorIcon { BatteryStatusIcon {
Layout.preferredHeight: app.iconSize * .5 Layout.preferredHeight: app.iconSize * .5
Layout.preferredWidth: height Layout.preferredWidth: height
name: "../images/battery/battery-020.svg" thing: itemDelegate.thing
visible: itemDelegate.deviceClass.interfaces.indexOf("battery") >= 0 && itemDelegate.device.states.getState(itemDelegate.deviceClass.stateTypes.findByName("batteryCritical").id).value === true visible: itemDelegate.thing.setupStatus == Thing.ThingSetupStatusComplete && (hasBatteryLevel || isCritical)
} }
ColorIcon { ConnectionStatusIcon {
Layout.preferredHeight: app.iconSize * .5 Layout.preferredHeight: app.iconSize * .5
Layout.preferredWidth: height Layout.preferredWidth: height
name: "../images/dialog-warning-symbolic.svg" thing: itemDelegate.thing
visible: itemDelegate.deviceClass.interfaces.indexOf("connectable") >= 0 && itemDelegate.device.states.getState(itemDelegate.deviceClass.stateTypes.findByName("connected").id).value === false visible: itemDelegate.thing.setupStatus == Thing.ThingSetupStatusComplete && (isWireless || !isConnected)
color: "red" }
SetupStatusIcon {
Layout.preferredHeight: app.iconSize * .5
Layout.preferredWidth: height
thing: itemDelegate.thing
visible: setupFailed || setupInProgress
} }
} }
} }
GridLayout { GridLayout {
id: dataGrid id: dataGrid
@ -121,11 +124,11 @@ DeviceListPageBase {
delegate: RowLayout { delegate: RowLayout {
id: sensorValueDelegate id: sensorValueDelegate
visible: itemDelegate.deviceClass.interfaces.indexOf(model.interfaceName) >= 0 visible: itemDelegate.thing.thingClass.interfaces.indexOf(model.interfaceName) >= 0
Layout.preferredWidth: contentItem.width / dataGrid.columns Layout.preferredWidth: contentItem.width / dataGrid.columns
property StateType stateType: itemDelegate.deviceClass.stateTypes.findByName(model.stateName) property StateType stateType: itemDelegate.thing.thingClass.stateTypes.findByName(model.stateName)
property State stateValue: stateType ? itemDelegate.device.states.getState(stateType.id) : null property State stateValue: stateType ? itemDelegate.thing.states.getState(stateType.id) : null
ColorIcon { ColorIcon {
Layout.preferredHeight: app.iconSize * .8 Layout.preferredHeight: app.iconSize * .8

View File

@ -53,8 +53,7 @@ DeviceListPageBase {
property bool inline: width > 500 property bool inline: width > 500
property Device device: devicesProxy.getDevice(model.id) property Thing thing: thingsProxy.getThing(model.id)
property DeviceClass deviceClass: device.deviceClass
bottomPadding: index === ListView.view.count - 1 ? topPadding : 0 bottomPadding: index === ListView.view.count - 1 ? topPadding : 0
contentItem: Pane { contentItem: Pane {
@ -82,18 +81,23 @@ DeviceListPageBase {
text: model.name text: model.name
elide: Text.ElideRight elide: Text.ElideRight
} }
ColorIcon { BatteryStatusIcon {
Layout.preferredHeight: app.iconSize * .5 Layout.preferredHeight: app.iconSize * .5
Layout.preferredWidth: height Layout.preferredWidth: height
name: "../images/battery/battery-020.svg" thing: itemDelegate.thing
visible: itemDelegate.deviceClass.interfaces.indexOf("battery") >= 0 && itemDelegate.device.states.getState(itemDelegate.deviceClass.stateTypes.findByName("batteryCritical").id).value === true visible: thing.setupStatus == Thing.ThingSetupStatusComplete && (isCritical || hasBatteryLevel)
} }
ColorIcon { ConnectionStatusIcon {
Layout.preferredHeight: app.iconSize * .5 Layout.preferredHeight: app.iconSize * .5
Layout.preferredWidth: height Layout.preferredWidth: height
name: "../images/dialog-warning-symbolic.svg" thing: itemDelegate.thing
visible: itemDelegate.deviceClass.interfaces.indexOf("connectable") >= 0 && itemDelegate.device.states.getState(itemDelegate.deviceClass.stateTypes.findByName("connected").id).value === false visible: thing.setupStatus == Thing.ThingSetupStatusComplete && (isWireless || !isConnected)
color: "red" }
SetupStatusIcon {
Layout.preferredHeight: app.iconSize * .5
Layout.preferredWidth: height
thing: itemDelegate.thing
visible: setupFailed || setupInProgress
} }
} }
@ -105,18 +109,18 @@ DeviceListPageBase {
Repeater { Repeater {
model: ListModel { model: ListModel {
Component.onCompleted: { Component.onCompleted: {
if (itemDelegate.deviceClass.interfaces.indexOf("smartmeterproducer") >= 0) { if (itemDelegate.thing.thingClass.interfaces.indexOf("smartmeterproducer") >= 0) {
append( {interfaceName: "smartmeterproducer", stateName: "totalEnergyProduced" }) append( {interfaceName: "smartmeterproducer", stateName: "totalEnergyProduced" })
} }
if (itemDelegate.deviceClass.interfaces.indexOf("smartmeterconsumer") >= 0) { if (itemDelegate.thing.thingClass.interfaces.indexOf("smartmeterconsumer") >= 0) {
append( {interfaceName: "smartmeterconsumer", stateName: "totalEnergyConsumed" }) append( {interfaceName: "smartmeterconsumer", stateName: "totalEnergyConsumed" })
} }
var added = false; var added = false;
if (itemDelegate.deviceClass.interfaces.indexOf("extendedsmartmeterproducer") >= 0) { if (itemDelegate.thing.thingClass.interfaces.indexOf("extendedsmartmeterproducer") >= 0) {
append({interfaceName: "extendedsmartmeterconsumer", stateName: "currentPower"}); append({interfaceName: "extendedsmartmeterconsumer", stateName: "currentPower"});
added = true; added = true;
} }
if (!added && itemDelegate.deviceClass.interfaces.indexOf("extendedsmartmeterconsumer") >= 0) { if (!added && itemDelegate.thing.thingClass.interfaces.indexOf("extendedsmartmeterconsumer") >= 0) {
append({interfaceName: "extendedsmartmeterconsumer", stateName: "currentPower"}); append({interfaceName: "extendedsmartmeterconsumer", stateName: "currentPower"});
} }
} }
@ -126,8 +130,8 @@ DeviceListPageBase {
id: sensorValueDelegate id: sensorValueDelegate
Layout.preferredWidth: contentItem.width / dataGrid.columns Layout.preferredWidth: contentItem.width / dataGrid.columns
property var stateType: itemDelegate.deviceClass.stateTypes.findByName(model.stateName) property StateType stateType: itemDelegate.thing.thingClass.stateTypes.findByName(model.stateName)
property var stateValue: stateType ? itemDelegate.device.states.getState(stateType.id) : null property State stateValue: stateType ? itemDelegate.thing.states.getState(stateType.id) : null
ColorIcon { ColorIcon {
Layout.preferredHeight: app.iconSize * .8 Layout.preferredHeight: app.iconSize * .8

View File

@ -36,10 +36,11 @@ import "../components"
Page { Page {
id: root id: root
property Device device: null property Thing thing: null
readonly property DeviceClass deviceClass: device.deviceClass readonly property ThingClass thingClass: thing.thingClass
readonly property Device thing: device property alias device: root.thing
property alias deviceClass: root.thingClass
property bool showLogsButton: true property bool showLogsButton: true
property bool showDetailsButton: true property bool showDetailsButton: true
@ -51,7 +52,7 @@ Page {
signal backPressed() signal backPressed()
header: NymeaHeader { header: NymeaHeader {
text: device.name text: root.thing.name
onBackPressed: { onBackPressed: {
root.backPressed(); root.backPressed();
if (root.popStackOnBackButton) { if (root.popStackOnBackButton) {
@ -61,7 +62,7 @@ Page {
HeaderButton { HeaderButton {
imageSource: "../images/folder-symbolic.svg" imageSource: "../images/folder-symbolic.svg"
visible: root.deviceClass.browsable && root.showBrowserButton visible: root.thingClass.browsable && root.showBrowserButton
onClicked: { onClicked: {
pageStack.push(Qt.resolvedUrl("DeviceBrowserPage.qml"), {device: root.device}) pageStack.push(Qt.resolvedUrl("DeviceBrowserPage.qml"), {device: root.device})
} }
@ -229,11 +230,11 @@ Page {
visible: setupInProgress || setupFailure || batteryState !== null || (connectedState !== null && connectedState.value === false) visible: setupInProgress || setupFailure || batteryState !== null || (connectedState !== null && connectedState.value === false)
height: visible ? contentRow.implicitHeight : 0 height: visible ? contentRow.implicitHeight : 0
anchors { left: parent.left; top: parent.top; right: parent.right } anchors { left: parent.left; top: parent.top; right: parent.right }
property bool setupInProgress: device.setupStatus == Device.DeviceSetupStatusInProgress property bool setupInProgress: root.thing.setupStatus == Thing.ThingSetupStatusInProgress
property bool setupFailure: device.setupStatus == Device.DeviceSetupStatusFailed property bool setupFailure: root.thing.setupStatus == Thing.ThingSetupStatusFailed
property var batteryState: deviceClass.interfaces.indexOf("battery") >= 0 ? device.states.getState(deviceClass.stateTypes.findByName("batteryLevel").id) : null property State batteryState: root.thing.stateByName("batteryLevel")
property var batteryCriticalState: deviceClass.interfaces.indexOf("battery") >= 0 ? device.states.getState(deviceClass.stateTypes.findByName("batteryCritical").id) : null property State batteryCriticalState: root.thing.stateByName("batteryCritical")
property var connectedState: deviceClass.interfaces.indexOf("connectable") >= 0 ? device.states.getState(deviceClass.stateTypes.findByName("connected").id) : null property State connectedState: root.thing.stateByName("connected")
property bool alertState: setupFailure || property bool alertState: setupFailure ||
(connectedState !== null && connectedState.value === false) || (connectedState !== null && connectedState.value === false) ||
(batteryCriticalState !== null && batteryCriticalState.value === true) (batteryCriticalState !== null && batteryCriticalState.value === true)
@ -261,22 +262,28 @@ Page {
color: "white" color: "white"
} }
ColorIcon { BatteryStatusIcon {
height: app.iconSize / 2
width: height
visible: infoPane.setupInProgress || infoPane.setupFailure || (infoPane.connectedState !== null && infoPane.connectedState.value === false)
color: "white"
name: infoPane.setupInProgress ?
"../images/settings.svg"
: "../images/dialog-warning-symbolic.svg"
}
ColorIcon {
height: app.iconSize / 2 height: app.iconSize / 2
width: height * 1.23 width: height * 1.23
name: infoPane.batteryState !== null ? "../images/battery/battery-" + ("00" + (Math.floor(infoPane.batteryState.value / 10) * 10)).slice(-3) + ".svg" : "" thing: root.thing
visible: infoPane.batteryState !== null
color: infoPane.alertState ? "white" : keyColor color: infoPane.alertState ? "white" : keyColor
visible: thing.setupStatus == Thing.ThingSetupStatusComplete && (hasBatteryLevel || isCritical)
}
ConnectionStatusIcon {
height: app.iconSize / 2
width: height
thing: root.thing
color: infoPane.alertState ? "white" : keyColor
visible: thing.setupStatus == Thing.ThingSetupStatusComplete && (hasSignalStrength || !isConnected)
}
SetupStatusIcon {
height: app.iconSize / 2
width: height
thing: root.thing
color: infoPane.alertState ? "white" : keyColor
visible: setupFailed || setupInProgress
} }
} }
} }

View File

@ -0,0 +1,18 @@
pragma Singleton
import QtQuick 2.9
Item {
id: root
function pad(num, size) {
var trimmedNum = Math.floor(num)
var decimals = num - trimmedNum
var trimmedStr = "" + trimmedNum
var str = "000000000" + trimmedNum;
str = str.substr(str.length - Math.max(size, trimmedStr.length));
if (decimals !== 0) {
str += "." + (num - trimmedNum);
}
return str;
}
}