Add air conditioning experience
This commit is contained in:
parent
8a8149951b
commit
79841a5e27
@ -37,7 +37,9 @@ DeviceControlApplication::DeviceControlApplication(int argc, char *argv[]) : QAp
|
||||
m_engine = new Engine(this);
|
||||
|
||||
m_qmlEngine = new QQmlApplicationEngine(this);
|
||||
registerQmlTypes();
|
||||
|
||||
Nymea::Core::registerQmlTypes();
|
||||
|
||||
qmlRegisterSingletonType<PlatformHelper>("Nymea", 1, 0, "PlatformHelper", platformHelperProvider);
|
||||
qmlRegisterSingletonType(QUrl("qrc:///ui/utils/NymeaUtils.qml"), "Nymea", 1, 0, "NymeaUtils" );
|
||||
qmlRegisterType<NfcThingActionWriter>("Nymea", 1, 0, "NfcThingActionWriter");
|
||||
|
||||
35
experiences/airconditioning/airconditioning.pro
Normal file
35
experiences/airconditioning/airconditioning.pro
Normal file
@ -0,0 +1,35 @@
|
||||
TEMPLATE = lib
|
||||
CONFIG += staticlib
|
||||
TARGET = nymea-app-airconditioning
|
||||
|
||||
QT -= gui
|
||||
QT += network websockets bluetooth charts quick
|
||||
|
||||
include(../../shared.pri)
|
||||
|
||||
LIBS += -L$${top_builddir}/libnymea-app/ -lnymea-app
|
||||
|
||||
android: {
|
||||
LIBS += -L$${top_builddir}/libnymea-app/$${ANDROID_TARGET_ARCH}
|
||||
PRE_TARGETDEPS += $$top_builddir/libnymea-app/$${ANDROID_TARGET_ARCH}/libnymea-app.a
|
||||
}
|
||||
|
||||
INCLUDEPATH += $${top_srcdir}/libnymea-app/
|
||||
|
||||
# Input
|
||||
SOURCES += \
|
||||
airconditioningmanager.cpp \
|
||||
zoneinfo.cpp \
|
||||
temperatureschedule.cpp \
|
||||
|
||||
HEADERS += \
|
||||
airconditioningmanager.h \
|
||||
libnymea-app-airconditioning.h \
|
||||
zoneinfo.h \
|
||||
temperatureschedule.h \
|
||||
|
||||
DISTFILES =
|
||||
|
||||
android: {
|
||||
DESTDIR = $${ANDROID_TARGET_ARCH}
|
||||
}
|
||||
23
experiences/airconditioning/airconditioning_plugin.cpp
Normal file
23
experiences/airconditioning/airconditioning_plugin.cpp
Normal file
@ -0,0 +1,23 @@
|
||||
#include "airconditioning_plugin.h"
|
||||
|
||||
#include "airconditioningmanager.h"
|
||||
#include "airconditioningmanager.h"
|
||||
#include "zoneinfo.h"
|
||||
|
||||
#include <qqml.h>
|
||||
#include <QDebug>
|
||||
|
||||
void AirconditioningPlugin::registerTypes(const char *uri)
|
||||
{
|
||||
|
||||
qCritical() << "################# loading plugin";
|
||||
// @uri Nymea.AirConditioning
|
||||
qmlRegisterType<AirConditioningManager>(uri, 1, 0, "AirConditioningManager");
|
||||
qmlRegisterUncreatableType<ZoneInfos>(uri, 1, 0, "ZoneInfos", "Get it from AirConditioningManager");
|
||||
qmlRegisterUncreatableType<ZoneInfo>(uri, 1, 0, "ZoneInfo", "Get it from AirConditioningManager");
|
||||
qmlRegisterUncreatableType<TemperatureSchedule>(uri, 1, 0, "TemperatureSchedule", "Get it from AirConditioningManager");
|
||||
qmlRegisterUncreatableType<TemperatureDaySchedule>(uri, 1, 0, "TemperatureDaySchedule", "Get it from AirConditioningManager");
|
||||
qmlRegisterUncreatableType<TemperatureWeekSchedule>(uri, 1, 0, "TemperatureWeekSchedule", "Get it from AirConditioningManager");
|
||||
|
||||
}
|
||||
|
||||
15
experiences/airconditioning/airconditioning_plugin.h
Normal file
15
experiences/airconditioning/airconditioning_plugin.h
Normal file
@ -0,0 +1,15 @@
|
||||
#ifndef AIRCONDITIONING_PLUGIN_H
|
||||
#define AIRCONDITIONING_PLUGIN_H
|
||||
|
||||
#include <QQmlExtensionPlugin>
|
||||
|
||||
class AirconditioningPlugin : public QQmlExtensionPlugin
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid)
|
||||
|
||||
public:
|
||||
void registerTypes(const char *uri) override;
|
||||
};
|
||||
|
||||
#endif // AIRCONDITIONING_PLUGIN_H
|
||||
389
experiences/airconditioning/airconditioningmanager.cpp
Normal file
389
experiences/airconditioning/airconditioningmanager.cpp
Normal file
@ -0,0 +1,389 @@
|
||||
#include "airconditioningmanager.h"
|
||||
#include "zoneinfo.h"
|
||||
|
||||
#include "engine.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QMetaEnum>
|
||||
|
||||
#include "logging.h"
|
||||
|
||||
NYMEA_LOGGING_CATEGORY(dcAirConditioningExperience, "AirConditioningExperience")
|
||||
|
||||
AirConditioningManager::AirConditioningManager(QObject *parent)
|
||||
: QObject{parent},
|
||||
m_zoneInfos(new ZoneInfos(this))
|
||||
{
|
||||
qRegisterMetaType<ZoneInfo::SetpointOverrideMode>();
|
||||
}
|
||||
|
||||
AirConditioningManager::~AirConditioningManager()
|
||||
{
|
||||
if (m_engine) {
|
||||
m_engine->jsonRpcClient()->unregisterNotificationHandler(this);
|
||||
}
|
||||
}
|
||||
|
||||
Engine *AirConditioningManager::engine() const
|
||||
{
|
||||
return m_engine;
|
||||
}
|
||||
|
||||
void AirConditioningManager::setEngine(Engine *engine)
|
||||
{
|
||||
if (m_engine != engine) {
|
||||
if (m_engine) {
|
||||
m_engine->jsonRpcClient()->unregisterNotificationHandler(this);
|
||||
}
|
||||
|
||||
m_engine = engine;
|
||||
emit engineChanged();
|
||||
|
||||
if (m_engine) {
|
||||
connect(engine, &Engine::destroyed, this, [engine, this]{ if (m_engine == engine) m_engine = nullptr; });
|
||||
m_engine->jsonRpcClient()->registerNotificationHandler(this, "AirConditioning", "notificationReceived");
|
||||
m_engine->jsonRpcClient()->sendCommand("AirConditioning.GetZones", QVariantMap(), this, "getZonesResponse");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ZoneInfos *AirConditioningManager::zoneInfos() const
|
||||
{
|
||||
return m_zoneInfos;
|
||||
}
|
||||
|
||||
int AirConditioningManager::addZone(const QString &name, const QList<QUuid> &thermostats, const QList<QUuid> &windowSensors, const QList<QUuid> &indoorSensors, const QList<QUuid> &outdoorSensors)
|
||||
{
|
||||
QVariantList thermostatIds, windowSensorIds, indoorSensorIds, outdoorSensorIds;
|
||||
foreach (const QUuid &id, thermostats) {
|
||||
thermostatIds.append(id);
|
||||
}
|
||||
foreach (const QUuid &id, windowSensors) {
|
||||
windowSensorIds.append(id);
|
||||
}
|
||||
foreach (const QUuid &id, indoorSensors) {
|
||||
indoorSensorIds.append(id);
|
||||
}
|
||||
foreach (const QUuid &id, outdoorSensors) {
|
||||
outdoorSensorIds.append(id);
|
||||
}
|
||||
QVariantMap params = {
|
||||
{"name", name},
|
||||
{"thermostats", thermostatIds},
|
||||
{"windowSensors", windowSensorIds},
|
||||
{"indoorSensors", indoorSensorIds},
|
||||
{"outdoorSensors", outdoorSensorIds}
|
||||
};
|
||||
return m_engine->jsonRpcClient()->sendCommand("AirConditioning.AddZone", params, this, "addZoneResponse");
|
||||
}
|
||||
|
||||
int AirConditioningManager::removeZone(const QUuid &zoneId)
|
||||
{
|
||||
return m_engine->jsonRpcClient()->sendCommand("AirConditioning.RemoveZone", {{"zoneId", zoneId}}, this, "removeZoneResponse");
|
||||
}
|
||||
|
||||
int AirConditioningManager::setZoneName(const QUuid &zoneId, const QString &name)
|
||||
{
|
||||
QVariantMap params = {
|
||||
{"zoneId", zoneId},
|
||||
{"name", name}
|
||||
};
|
||||
return m_engine->jsonRpcClient()->sendCommand("AirConditioning.SetZoneName", params, this, "setZoneNameResponse");
|
||||
}
|
||||
|
||||
int AirConditioningManager::setZoneStandbySetpoint(const QUuid &zoneId, double standbySetpoint)
|
||||
{
|
||||
QVariantMap params = {
|
||||
{"zoneId", zoneId},
|
||||
{"standbySetpoint", standbySetpoint}
|
||||
};
|
||||
return m_engine->jsonRpcClient()->sendCommand("AirConditioning.SetZoneStandbySetpoint", params, this, "setZoneStandbySetpointResponse");
|
||||
}
|
||||
|
||||
int AirConditioningManager::setZoneSetpointOverride(const QUuid &zoneId, double setpointOverride, ZoneInfo::SetpointOverrideMode mode, uint minutes)
|
||||
{
|
||||
QMetaEnum modeEnum = QMetaEnum::fromType<ZoneInfo::SetpointOverrideMode>();
|
||||
QVariantMap params = {
|
||||
{"zoneId", zoneId},
|
||||
{"setpointOverride", setpointOverride},
|
||||
{"mode", modeEnum.valueToKey(mode)},
|
||||
{"minutes", minutes}
|
||||
};
|
||||
return m_engine->jsonRpcClient()->sendCommand("AirConditioning.SetZoneSetpointOverride", params, this, "setZoneSetpointOverrideResponse");
|
||||
}
|
||||
|
||||
int AirConditioningManager::setZoneWeekSchedule(const QUuid &zoneId, TemperatureWeekSchedule *weekSchedule)
|
||||
{
|
||||
QVariantList weekList;
|
||||
for (int day = 0; day < 7; day++) {
|
||||
TemperatureDaySchedule *daySchedule = weekSchedule->get(day);
|
||||
QVariantList dayList;
|
||||
for (int i = 0; i < daySchedule->rowCount(); i++) {
|
||||
TemperatureSchedule *schedule = daySchedule->get(i);
|
||||
QVariantMap v = {
|
||||
{"startTime", schedule->startTime().toString("hh:mm")},
|
||||
{"endTime", schedule->endTime().toString("hh:mm")},
|
||||
{"temperature", schedule->temperature()}
|
||||
};
|
||||
dayList.append(v);
|
||||
}
|
||||
weekList.append(QVariant::fromValue(dayList));
|
||||
}
|
||||
QVariantMap params = {
|
||||
{"zoneId", zoneId},
|
||||
{"weekSchedule", weekList}
|
||||
};
|
||||
return m_engine->jsonRpcClient()->sendCommand("AirConditioning.SetZoneWeekSchedule", params, this, "setZoneWeekScheduleResponse");
|
||||
}
|
||||
|
||||
int AirConditioningManager::setZoneThings(const QUuid &zoneId, const QList<QUuid> &thermostats, const QList<QUuid> &windowSensors, const QList<QUuid> &indoorSensors, const QList<QUuid> &outdoorSensors)
|
||||
{
|
||||
QVariantList thermostatIds, windowSensorIds, indoorSensorIds, outdoorSensorIds;
|
||||
foreach (const QUuid &thingId, thermostats) {
|
||||
thermostatIds.append(thingId);
|
||||
}
|
||||
foreach (const QUuid &thingId, windowSensors) {
|
||||
windowSensorIds.append(thingId);
|
||||
}
|
||||
foreach (const QUuid &thingId, indoorSensors) {
|
||||
indoorSensorIds.append(thingId);
|
||||
}
|
||||
foreach (const QUuid &thingId, outdoorSensors) {
|
||||
outdoorSensorIds.append(thingId);
|
||||
}
|
||||
QVariantMap params = {
|
||||
{"zoneId", zoneId},
|
||||
{"thermostats", thermostatIds},
|
||||
{"windowSensors", windowSensorIds},
|
||||
{"indoorSensors", indoorSensorIds},
|
||||
{"outdoorSensors", outdoorSensorIds},
|
||||
};
|
||||
return m_engine->jsonRpcClient()->sendCommand("AirConditioning.SetZoneThings", params, this, "setZoneThingsResponse");
|
||||
}
|
||||
|
||||
int AirConditioningManager::addZoneThermostat(const QUuid &zoneId, const QUuid &thermostat)
|
||||
{
|
||||
ZoneInfo *zoneInfo = m_zoneInfos->getZoneInfo(zoneId);
|
||||
if (!zoneInfo) {
|
||||
return -1;
|
||||
}
|
||||
return setZoneThings(zoneId, zoneInfo->thermostats() << thermostat, zoneInfo->windowSensors(), zoneInfo->indoorSensors(), zoneInfo->outdoorSensors());
|
||||
}
|
||||
|
||||
int AirConditioningManager::removeZoneThermostat(const QUuid &zoneId, const QUuid &thermostat)
|
||||
{
|
||||
ZoneInfo *zoneInfo = m_zoneInfos->getZoneInfo(zoneId);
|
||||
if (!zoneInfo) {
|
||||
return -1;
|
||||
}
|
||||
QList<QUuid> thermostats = zoneInfo->thermostats();
|
||||
thermostats.removeAll(thermostat);
|
||||
return setZoneThings(zoneId, thermostats, zoneInfo->windowSensors(), zoneInfo->indoorSensors(), zoneInfo->outdoorSensors());
|
||||
}
|
||||
|
||||
int AirConditioningManager::addZoneWindowSensor(const QUuid &zoneId, const QUuid &windowSensor)
|
||||
{
|
||||
ZoneInfo *zoneInfo = m_zoneInfos->getZoneInfo(zoneId);
|
||||
if (!zoneInfo) {
|
||||
return -1;
|
||||
}
|
||||
return setZoneThings(zoneId, zoneInfo->thermostats(), zoneInfo->windowSensors() << windowSensor, zoneInfo->indoorSensors(), zoneInfo->outdoorSensors());
|
||||
}
|
||||
|
||||
int AirConditioningManager::removeWindowSensor(const QUuid &zoneId, const QUuid &windowSensor)
|
||||
{
|
||||
ZoneInfo *zoneInfo = m_zoneInfos->getZoneInfo(zoneId);
|
||||
if (!zoneInfo) {
|
||||
return -1;
|
||||
}
|
||||
QList<QUuid> windowSensors = zoneInfo->windowSensors();
|
||||
windowSensors.removeAll(windowSensor);
|
||||
return setZoneThings(zoneId, zoneInfo->thermostats(), windowSensors, zoneInfo->indoorSensors(), zoneInfo->outdoorSensors());
|
||||
}
|
||||
|
||||
int AirConditioningManager::addZoneIndoorSensor(const QUuid &zoneId, const QUuid &indoorSensor)
|
||||
{
|
||||
ZoneInfo *zoneInfo = m_zoneInfos->getZoneInfo(zoneId);
|
||||
if (!zoneInfo) {
|
||||
return -1;
|
||||
}
|
||||
return setZoneThings(zoneId, zoneInfo->thermostats(), zoneInfo->windowSensors(), zoneInfo->indoorSensors() << indoorSensor, zoneInfo->outdoorSensors());
|
||||
}
|
||||
|
||||
int AirConditioningManager::removeZoneIndoorSensor(const QUuid &zoneId, const QUuid &indoorSensor)
|
||||
{
|
||||
ZoneInfo *zoneInfo = m_zoneInfos->getZoneInfo(zoneId);
|
||||
if (!zoneInfo) {
|
||||
return -1;
|
||||
}
|
||||
QList<QUuid> indoorSensors = zoneInfo->indoorSensors();
|
||||
indoorSensors.removeAll(indoorSensor);
|
||||
return setZoneThings(zoneId, zoneInfo->thermostats(), zoneInfo->windowSensors(), indoorSensors, zoneInfo->outdoorSensors());
|
||||
|
||||
}
|
||||
|
||||
int AirConditioningManager::addZoneOutdoorSensor(const QUuid &zoneId, const QUuid &outdoorSensor)
|
||||
{
|
||||
ZoneInfo *zoneInfo = m_zoneInfos->getZoneInfo(zoneId);
|
||||
if (!zoneInfo) {
|
||||
return -1;
|
||||
}
|
||||
return setZoneThings(zoneId, zoneInfo->thermostats(), zoneInfo->windowSensors(), zoneInfo->indoorSensors(), zoneInfo->outdoorSensors() << outdoorSensor);
|
||||
}
|
||||
|
||||
int AirConditioningManager::removeZoneOutdoorSensor(const QUuid &zoneId, const QUuid &outdoorSensor)
|
||||
{
|
||||
ZoneInfo *zoneInfo = m_zoneInfos->getZoneInfo(zoneId);
|
||||
if (!zoneInfo) {
|
||||
return -1;
|
||||
}
|
||||
QList<QUuid> outdoorSensors = zoneInfo->outdoorSensors();
|
||||
outdoorSensors.removeAll(outdoorSensor);
|
||||
return setZoneThings(zoneId, zoneInfo->thermostats(), zoneInfo->windowSensors(), zoneInfo->indoorSensors(), outdoorSensors);
|
||||
}
|
||||
|
||||
void AirConditioningManager::notificationReceived(const QVariantMap &data)
|
||||
{
|
||||
QString notification = data.value("notification").toString();
|
||||
QVariantMap params = data.value("params").toMap();
|
||||
if (notification == "AirConditioning.ZoneAdded") {
|
||||
QVariantMap zoneMap = params.value("zone").toMap();
|
||||
m_zoneInfos->addZoneInfo(unpack(zoneMap));
|
||||
|
||||
} else if (notification == "AirConditioning.ZoneRemoved") {
|
||||
QUuid zoneId = params.value("zoneId").toUuid();
|
||||
m_zoneInfos->removeZoneInfo(zoneId);
|
||||
|
||||
} else if (notification == "AirConditioning.ZoneChanged") {
|
||||
QVariantMap zoneMap = params.value("zone").toMap();
|
||||
qCDebug(dcAirConditioningExperience()) << "Zone changed:" << qUtf8Printable(QJsonDocument::fromVariant(zoneMap).toJson());
|
||||
QUuid zoneId = zoneMap.value("id").toUuid();
|
||||
ZoneInfo *zone = m_zoneInfos->getZoneInfo(zoneId);
|
||||
if (!zone) {
|
||||
qCWarning(dcAirConditioningExperience()) << "Received a zone changed notification for a zone we don't know" << zoneId;
|
||||
return;
|
||||
}
|
||||
unpack(zoneMap, zone);
|
||||
} else {
|
||||
qCDebug(dcAirConditioningExperience()) << "Unhandled notification received" << data;
|
||||
}
|
||||
}
|
||||
|
||||
void AirConditioningManager::addZoneResponse(int commandId, const QVariantMap ¶ms)
|
||||
{
|
||||
Q_UNUSED(commandId)
|
||||
qCDebug(dcAirConditioningExperience()) << "Add zone response" << qUtf8Printable(QJsonDocument::fromVariant(params).toJson());
|
||||
QMetaEnum metaEnum = QMetaEnum::fromType<AirConditioningError>();
|
||||
AirConditioningError error = static_cast<AirConditioningError>(metaEnum.keyToValue(params.value("error").toByteArray().data()));
|
||||
emit addZoneReply(commandId, error, params.value("zone").toMap().value("id").toUuid());
|
||||
}
|
||||
|
||||
void AirConditioningManager::removeZoneResponse(int commandId, const QVariantMap ¶ms)
|
||||
{
|
||||
qCDebug(dcAirConditioningExperience()) << "remove zone response" << commandId << params;
|
||||
QMetaEnum metaEnum = QMetaEnum::fromType<AirConditioningError>();
|
||||
AirConditioningError error = static_cast<AirConditioningError>(metaEnum.keyToValue(params.value("error").toByteArray().data()));
|
||||
emit removeZoneReply(commandId, error);
|
||||
}
|
||||
|
||||
void AirConditioningManager::getZonesResponse(int commandId, const QVariantMap ¶ms)
|
||||
{
|
||||
Q_UNUSED(commandId)
|
||||
qCDebug(dcAirConditioningExperience()) << "get zones response:" << qUtf8Printable(QJsonDocument::fromVariant(params).toJson());
|
||||
|
||||
foreach (const QVariant &zoneVariant, params.value("zones").toList()) {
|
||||
m_zoneInfos->addZoneInfo(unpack(zoneVariant.toMap()));
|
||||
}
|
||||
}
|
||||
|
||||
void AirConditioningManager::setZoneNameResponse(int commandId, const QVariantMap ¶ms)
|
||||
{
|
||||
qCDebug(dcAirConditioningExperience()) << "set zone name response" << commandId << params;
|
||||
QMetaEnum metaEnum = QMetaEnum::fromType<AirConditioningError>();
|
||||
AirConditioningError error = static_cast<AirConditioningError>(metaEnum.keyToValue(params.value("error").toByteArray().data()));
|
||||
emit setZoneNameReply(commandId, error);
|
||||
}
|
||||
|
||||
void AirConditioningManager::setZoneStandbySetpointResponse(int commandId, const QVariantMap ¶ms)
|
||||
{
|
||||
QMetaEnum metaEnum = QMetaEnum::fromType<AirConditioningError>();
|
||||
AirConditioningError error = static_cast<AirConditioningError>(metaEnum.keyToValue(params.value("error").toByteArray().data()));
|
||||
emit setZoneStandbySetpointReply(commandId, error);
|
||||
}
|
||||
|
||||
void AirConditioningManager::setZoneSetpointOverrideResponse(int commandId, const QVariantMap ¶ms)
|
||||
{
|
||||
QMetaEnum metaEnum = QMetaEnum::fromType<AirConditioningError>();
|
||||
AirConditioningError error = static_cast<AirConditioningError>(metaEnum.keyToValue(params.value("error").toByteArray().data()));
|
||||
emit setZoneSetpointOverrideReply(commandId, error);
|
||||
|
||||
}
|
||||
|
||||
void AirConditioningManager::setZoneWeekScheduleResponse(int commandId, const QVariantMap ¶ms)
|
||||
{
|
||||
qCDebug(dcAirConditioningExperience()) << "set zone week schedule response" << commandId << params;
|
||||
QMetaEnum metaEnum = QMetaEnum::fromType<AirConditioningError>();
|
||||
AirConditioningError error = static_cast<AirConditioningError>(metaEnum.keyToValue(params.value("error").toByteArray().data()));
|
||||
emit setZoneWeekScheduleReply(commandId, error);
|
||||
|
||||
}
|
||||
|
||||
void AirConditioningManager::setZoneThingsResponse(int commandId, const QVariantMap ¶ms)
|
||||
{
|
||||
qCDebug(dcAirConditioningExperience()) << "set zone things response" << commandId << params;
|
||||
QMetaEnum metaEnum = QMetaEnum::fromType<AirConditioningError>();
|
||||
AirConditioningError error = static_cast<AirConditioningError>(metaEnum.keyToValue(params.value("error").toByteArray().data()));
|
||||
emit setZoneThingsReply(commandId, error);
|
||||
}
|
||||
|
||||
ZoneInfo *AirConditioningManager::unpack(const QVariantMap &zoneMap, ZoneInfo *zone)
|
||||
{
|
||||
QUuid id = zoneMap.value("id").toUuid();
|
||||
|
||||
if (!zone) {
|
||||
zone = new ZoneInfo(id);
|
||||
}
|
||||
zone->setName(zoneMap.value("name").toString());
|
||||
|
||||
QMetaEnum zoneStatusEnum = QMetaEnum::fromType<ZoneInfo::ZoneStatus>();
|
||||
ZoneInfo::ZoneStatus zoneStatus = ZoneInfo::ZoneStatusFlagNone;
|
||||
foreach (const QVariant &flag, zoneMap.value("zoneStatus").toList()) {
|
||||
zoneStatus.setFlag(static_cast<ZoneInfo::ZoneStatusFlag>(zoneStatusEnum.keyToValue(flag.toByteArray())), true);
|
||||
}
|
||||
qCDebug(dcAirConditioningExperience()) << "Zone status:" << zoneStatus;
|
||||
zone->setZoneStatus(zoneStatus);
|
||||
zone->setCurrentSetpoint(zoneMap.value("currentSetpoint").toDouble());
|
||||
zone->setStandbySetpoint(zoneMap.value("standbySetpoint").toDouble());
|
||||
QMetaEnum modeEnum = QMetaEnum::fromType<ZoneInfo::SetpointOverrideMode>();
|
||||
ZoneInfo::SetpointOverrideMode mode = static_cast<ZoneInfo::SetpointOverrideMode>(modeEnum.keyToValue(zoneMap.value("setpointOverrideMode").toByteArray()));
|
||||
QDateTime end = QDateTime::fromSecsSinceEpoch(zoneMap.value("setpointOverrideEnd").toULongLong());
|
||||
zone->setSetpointOverride(zoneMap.value("setpointOverride").toDouble(), mode, end);
|
||||
|
||||
QVariantList weekScheduleList = zoneMap.value("weekSchedule").toList();
|
||||
for (int day = 0; day < qMin(7, weekScheduleList.count()); day++) {
|
||||
QVariant dayVariant = weekScheduleList.at(day);
|
||||
zone->weekSchedule()->get(day)->clear();
|
||||
foreach (const QVariant &scheduleVariant, dayVariant.toList()) {
|
||||
QVariantMap scheduleMap = scheduleVariant.toMap();
|
||||
zone->weekSchedule()->get(day)->createSchedule(scheduleMap.value("startTime").toTime(), scheduleMap.value("endTime").toTime(), scheduleMap.value("temperature").toDouble());
|
||||
}
|
||||
}
|
||||
QList<QUuid> thermostats, windowSensors, indoorSensors, outdoorSensors;
|
||||
foreach (const QVariant &variant, zoneMap.value("thermostats").toList()) {
|
||||
thermostats.append(variant.toUuid());
|
||||
}
|
||||
foreach (const QVariant &variant, zoneMap.value("windowSensors").toList()) {
|
||||
windowSensors.append(variant.toUuid());
|
||||
}
|
||||
foreach (const QVariant &variant, zoneMap.value("indoorSensors").toList()) {
|
||||
indoorSensors.append(variant.toUuid());
|
||||
}
|
||||
foreach (const QVariant &variant, zoneMap.value("outdoorSensors").toList()) {
|
||||
outdoorSensors.append(variant.toUuid());
|
||||
}
|
||||
zone->setThermostats(thermostats);
|
||||
zone->setWindowSensors(windowSensors);
|
||||
zone->setIndoorSensors(indoorSensors);
|
||||
zone->setOutdoorSensors(outdoorSensors);
|
||||
return zone;
|
||||
}
|
||||
81
experiences/airconditioning/airconditioningmanager.h
Normal file
81
experiences/airconditioning/airconditioningmanager.h
Normal file
@ -0,0 +1,81 @@
|
||||
#ifndef AIRCONDITIONINGMANAGER_H
|
||||
#define AIRCONDITIONINGMANAGER_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "zoneinfo.h"
|
||||
|
||||
class Engine;
|
||||
|
||||
class AirConditioningManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(Engine* engine READ engine WRITE setEngine NOTIFY engineChanged)
|
||||
Q_PROPERTY(ZoneInfos* zoneInfos READ zoneInfos CONSTANT)
|
||||
|
||||
public:
|
||||
enum AirConditioningError {
|
||||
AirConditioningErrorNoError,
|
||||
AirConditioningErrorZoneNotFound,
|
||||
AirConditioningErrorInvalidTimeSpec,
|
||||
AirConditioningErrorThingNotFound,
|
||||
AirConditioningErrorInvalidThingType
|
||||
};
|
||||
Q_ENUM(AirConditioningError)
|
||||
|
||||
explicit AirConditioningManager(QObject *parent = nullptr);
|
||||
~AirConditioningManager();
|
||||
|
||||
Engine* engine() const;
|
||||
void setEngine(Engine *engine);
|
||||
|
||||
ZoneInfos *zoneInfos() const;
|
||||
|
||||
Q_INVOKABLE int addZone(const QString &name, const QList<QUuid> &thermostats, const QList<QUuid> &windowSensors, const QList<QUuid> &indoorSensors, const QList<QUuid> &outdoorSensors);
|
||||
Q_INVOKABLE int removeZone(const QUuid &zoneId);
|
||||
Q_INVOKABLE int setZoneName(const QUuid &zoneId, const QString &name);
|
||||
Q_INVOKABLE int setZoneStandbySetpoint(const QUuid &zoneId, double standbySetpoint);
|
||||
Q_INVOKABLE int setZoneSetpointOverride(const QUuid &zoneId, double setpointOverride, ZoneInfo::SetpointOverrideMode mode, uint minutes);
|
||||
Q_INVOKABLE int setZoneWeekSchedule(const QUuid &zoneId, TemperatureWeekSchedule *weekSchedule);
|
||||
Q_INVOKABLE int setZoneThings(const QUuid &zoneId, const QList<QUuid> &thermostats, const QList<QUuid> &windowSensors, const QList<QUuid> &indoorSensors, const QList<QUuid> &outdoorSensors);
|
||||
|
||||
Q_INVOKABLE int addZoneThermostat(const QUuid &zoneId, const QUuid &thermostat);
|
||||
Q_INVOKABLE int removeZoneThermostat(const QUuid &zoneId, const QUuid &thermostat);
|
||||
Q_INVOKABLE int addZoneWindowSensor(const QUuid &zoneId, const QUuid &windowSensor);
|
||||
Q_INVOKABLE int removeWindowSensor(const QUuid &zoneId, const QUuid &windowSensor);
|
||||
Q_INVOKABLE int addZoneIndoorSensor(const QUuid &zoneId, const QUuid &indoorSensor);
|
||||
Q_INVOKABLE int removeZoneIndoorSensor(const QUuid &zoneId, const QUuid &indoorSensor);
|
||||
Q_INVOKABLE int addZoneOutdoorSensor(const QUuid &zoneId, const QUuid &outdoorSensor);
|
||||
Q_INVOKABLE int removeZoneOutdoorSensor(const QUuid &zoneId, const QUuid &outdoorSensor);
|
||||
|
||||
signals:
|
||||
void engineChanged();
|
||||
void addZoneReply(int commandId, AirConditioningError error, const QUuid &zoneId);
|
||||
void removeZoneReply(int commandId, AirConditioningError error);
|
||||
void setZoneNameReply(int commandId, AirConditioningError error);
|
||||
void setZoneStandbySetpointReply(int commandId, AirConditioningError error);
|
||||
void setZoneSetpointOverrideReply(int commandId, AirConditioningError error);
|
||||
void setZoneThingsReply(int commandId, AirConditioningError error);
|
||||
void setZoneWeekScheduleReply(int commandId, AirConditioningError error);
|
||||
|
||||
private slots:
|
||||
void notificationReceived(const QVariantMap &data);
|
||||
|
||||
void addZoneResponse(int commandId, const QVariantMap ¶ms);
|
||||
void removeZoneResponse(int commandId, const QVariantMap ¶ms);
|
||||
void getZonesResponse(int commandId, const QVariantMap ¶ms);
|
||||
void setZoneNameResponse(int commandId, const QVariantMap ¶ms);
|
||||
void setZoneStandbySetpointResponse(int commandId, const QVariantMap ¶ms);
|
||||
void setZoneSetpointOverrideResponse(int commandId, const QVariantMap ¶ms);
|
||||
void setZoneWeekScheduleResponse(int commandId, const QVariantMap ¶ms);
|
||||
void setZoneThingsResponse(int commandId, const QVariantMap ¶ms);
|
||||
|
||||
private:
|
||||
ZoneInfo *unpack(const QVariantMap &zoneMap, ZoneInfo *zone = nullptr);
|
||||
|
||||
private:
|
||||
Engine *m_engine = nullptr;
|
||||
ZoneInfos *m_zoneInfos = nullptr;
|
||||
};
|
||||
|
||||
#endif // AIRCONDITIONINGMANAGER_H
|
||||
29
experiences/airconditioning/libnymea-app-airconditioning.h
Normal file
29
experiences/airconditioning/libnymea-app-airconditioning.h
Normal file
@ -0,0 +1,29 @@
|
||||
#ifndef LIBNYMEAAPPAIRCONDITIONING_H
|
||||
#define LIBNYMEAAPPAIRCONDITIONING_H
|
||||
|
||||
#include "airconditioningmanager.h"
|
||||
#include "zoneinfo.h"
|
||||
|
||||
#include <qqml.h>
|
||||
|
||||
namespace Nymea
|
||||
{
|
||||
namespace AirConditioning
|
||||
{
|
||||
|
||||
void registerQmlTypes() {
|
||||
|
||||
const char uri[] = "Nymea.AirConditioning";
|
||||
// @uri Nymea.AirConditioning
|
||||
qmlRegisterType<AirConditioningManager>(uri, 1, 0, "AirConditioningManager");
|
||||
qmlRegisterUncreatableType<ZoneInfos>(uri, 1, 0, "ZoneInfos", "Get it from AirConditioningManager");
|
||||
qmlRegisterUncreatableType<ZoneInfo>(uri, 1, 0, "ZoneInfo", "Get it from AirConditioningManager");
|
||||
qmlRegisterUncreatableType<TemperatureSchedule>(uri, 1, 0, "TemperatureSchedule", "Get it from AirConditioningManager");
|
||||
qmlRegisterUncreatableType<TemperatureDaySchedule>(uri, 1, 0, "TemperatureDaySchedule", "Get it from AirConditioningManager");
|
||||
qmlRegisterUncreatableType<TemperatureWeekSchedule>(uri, 1, 0, "TemperatureWeekSchedule", "Get it from AirConditioningManager");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
#endif // LIBNYMEAAPPAIRCONDITIONING_H
|
||||
2
experiences/airconditioning/qmldir
Normal file
2
experiences/airconditioning/qmldir
Normal file
@ -0,0 +1,2 @@
|
||||
module Nymea.AirConditioning
|
||||
plugin AirConditioning
|
||||
215
experiences/airconditioning/temperatureschedule.cpp
Normal file
215
experiences/airconditioning/temperatureschedule.cpp
Normal file
@ -0,0 +1,215 @@
|
||||
#include "temperatureschedule.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
TemperatureSchedule::TemperatureSchedule(QObject *parent)
|
||||
: QObject{parent}
|
||||
{
|
||||
// qWarning() << "++++ TempSchedule" << this;
|
||||
|
||||
}
|
||||
|
||||
TemperatureSchedule::~TemperatureSchedule()
|
||||
{
|
||||
// qWarning() << "---- TempSchedule" << this;
|
||||
}
|
||||
|
||||
QTime TemperatureSchedule::startTime() const
|
||||
{
|
||||
return m_startTime;
|
||||
}
|
||||
|
||||
void TemperatureSchedule::setStartTime(const QTime &startTime)
|
||||
{
|
||||
if (m_startTime != startTime) {
|
||||
m_startTime = startTime;
|
||||
emit startTimeChanged();
|
||||
}
|
||||
}
|
||||
|
||||
QTime TemperatureSchedule::endTime() const
|
||||
{
|
||||
return m_endTime;
|
||||
}
|
||||
|
||||
void TemperatureSchedule::setEndTime(const QTime &endTime)
|
||||
{
|
||||
if (m_endTime != endTime) {
|
||||
m_endTime = endTime;
|
||||
emit endTimeChanged();
|
||||
}
|
||||
}
|
||||
|
||||
double TemperatureSchedule::temperature() const
|
||||
{
|
||||
return m_temperature;
|
||||
}
|
||||
|
||||
void TemperatureSchedule::setTemperature(double temperature)
|
||||
{
|
||||
if (m_temperature != temperature) {
|
||||
m_temperature = temperature;
|
||||
emit temperatureChanged();
|
||||
}
|
||||
}
|
||||
|
||||
TemperatureSchedule *TemperatureSchedule::clone() const
|
||||
{
|
||||
TemperatureSchedule *ret = new TemperatureSchedule();
|
||||
ret->setStartTime(m_startTime);
|
||||
ret->setEndTime(m_endTime);
|
||||
ret->setTemperature(m_temperature);
|
||||
return ret;
|
||||
}
|
||||
|
||||
TemperatureDaySchedule::TemperatureDaySchedule(QObject *parent):
|
||||
QAbstractListModel(parent)
|
||||
{
|
||||
// qWarning() << "++++ DaySchedule" << this;
|
||||
|
||||
}
|
||||
|
||||
TemperatureDaySchedule::~TemperatureDaySchedule()
|
||||
{
|
||||
// qWarning() << "---- DaySchedule" << this;
|
||||
}
|
||||
|
||||
QVariant TemperatureDaySchedule::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
switch (role) {
|
||||
case RoleStartTime:
|
||||
return m_list.at(index.row())->startTime();
|
||||
case RoleEndTime:
|
||||
return m_list.at(index.row())->endTime();
|
||||
case RoleTemperature:
|
||||
return m_list.at(index.row())->temperature();
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> TemperatureDaySchedule::roleNames() const
|
||||
{
|
||||
return {
|
||||
{RoleStartTime, "startTime"},
|
||||
{RoleEndTime, "endTime"},
|
||||
{RoleTemperature, "temperature"}
|
||||
};
|
||||
}
|
||||
|
||||
void TemperatureDaySchedule::clear()
|
||||
{
|
||||
beginResetModel();
|
||||
qDeleteAll(m_list);
|
||||
m_list.clear();
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
void TemperatureDaySchedule::addSchedule(TemperatureSchedule *schedule)
|
||||
{
|
||||
schedule->setParent(this);
|
||||
beginInsertRows(QModelIndex(), m_list.count(), m_list.count());
|
||||
m_list.append(schedule);
|
||||
endInsertRows();
|
||||
emit countChanged();
|
||||
}
|
||||
|
||||
TemperatureSchedule *TemperatureDaySchedule::get(int index) const
|
||||
{
|
||||
if (index < 0 || index >= m_list.count()) {
|
||||
return nullptr;
|
||||
}
|
||||
return m_list.at(index);
|
||||
}
|
||||
|
||||
TemperatureDaySchedule *TemperatureDaySchedule::clone() const
|
||||
{
|
||||
// Note: passes ownership to caller (no parent)!
|
||||
TemperatureDaySchedule *ret = new TemperatureDaySchedule();
|
||||
for (int i = 0; i < m_list.count(); i++) {
|
||||
ret->addSchedule(m_list.at(i)->clone());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
TemperatureSchedule *TemperatureDaySchedule::createSchedule(const QTime &startTime, const QTime &endTime, double temperature)
|
||||
{
|
||||
if (startTime >= endTime) {
|
||||
qWarning() << "Starttime is greater endTime. Not creating schedule";
|
||||
return nullptr;
|
||||
}
|
||||
int idx = 0;
|
||||
for (int i = 0; i < m_list.count(); i++) {
|
||||
TemperatureSchedule *existing = m_list.at(i);
|
||||
if (startTime < existing->startTime() && endTime < existing->endTime()) {
|
||||
break;
|
||||
}
|
||||
if (startTime < existing->startTime() && endTime > existing->startTime()) {
|
||||
qWarning() << "Collision detected. Not creating schedule";
|
||||
return nullptr;
|
||||
}
|
||||
if (startTime > existing->startTime() && startTime < existing->endTime()) {
|
||||
qWarning() << "Collision detected. Not creating schedule";
|
||||
return nullptr;
|
||||
}
|
||||
idx = i + 1;
|
||||
|
||||
}
|
||||
TemperatureSchedule *newSchedule = new TemperatureSchedule(this);
|
||||
newSchedule->setStartTime(startTime);
|
||||
newSchedule->setEndTime(endTime);
|
||||
newSchedule->setTemperature(temperature);
|
||||
|
||||
beginInsertRows(QModelIndex(), idx, idx);
|
||||
m_list.insert(idx, newSchedule);
|
||||
endInsertRows();
|
||||
emit countChanged();
|
||||
return newSchedule;
|
||||
}
|
||||
|
||||
void TemperatureDaySchedule::removeSchedule(TemperatureSchedule *schedule)
|
||||
{
|
||||
for (int i = 0; i < m_list.count(); i++) {
|
||||
if (m_list.at(i) == schedule) {
|
||||
beginRemoveRows(QModelIndex(), i, i);
|
||||
m_list.takeAt(i)->deleteLater();
|
||||
endRemoveRows();
|
||||
emit countChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TemperatureWeekSchedule::TemperatureWeekSchedule(QObject *parent):
|
||||
QAbstractListModel(parent)
|
||||
{
|
||||
// qWarning() << "++++ WeekSchedule" << this;
|
||||
|
||||
for (int i = 0; i < 7; i++) {
|
||||
m_list.append(new TemperatureDaySchedule(this));
|
||||
}
|
||||
}
|
||||
|
||||
TemperatureWeekSchedule::~TemperatureWeekSchedule()
|
||||
{
|
||||
// qWarning() << "---- WeekSchedule" << this;
|
||||
}
|
||||
|
||||
TemperatureDaySchedule *TemperatureWeekSchedule::get(int index) const
|
||||
{
|
||||
if (index < 0 || index >= m_list.count()) {
|
||||
return nullptr;
|
||||
}
|
||||
return m_list.at(index);
|
||||
}
|
||||
|
||||
TemperatureWeekSchedule *TemperatureWeekSchedule::clone() const
|
||||
{
|
||||
TemperatureWeekSchedule *weekSchedule = new TemperatureWeekSchedule();
|
||||
for (int day = 0; day < 7; day++) {
|
||||
TemperatureDaySchedule *daySchedule = get(day);
|
||||
for (int i = 0; i < daySchedule->rowCount(); i++) {
|
||||
weekSchedule->get(day)->addSchedule(daySchedule->get(i)->clone());
|
||||
}
|
||||
}
|
||||
return weekSchedule;
|
||||
}
|
||||
|
||||
92
experiences/airconditioning/temperatureschedule.h
Normal file
92
experiences/airconditioning/temperatureschedule.h
Normal file
@ -0,0 +1,92 @@
|
||||
#ifndef TEMPERATURESCHEDULE_H
|
||||
#define TEMPERATURESCHEDULE_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QTime>
|
||||
#include <QAbstractListModel>
|
||||
|
||||
class TemperatureSchedule: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QTime startTime READ startTime WRITE setStartTime NOTIFY startTimeChanged)
|
||||
Q_PROPERTY(QTime endTime READ endTime WRITE setEndTime NOTIFY endTimeChanged)
|
||||
Q_PROPERTY(double temperature READ temperature WRITE setTemperature NOTIFY temperatureChanged)
|
||||
|
||||
public:
|
||||
explicit TemperatureSchedule(QObject *parent = nullptr);
|
||||
~TemperatureSchedule();
|
||||
|
||||
QTime startTime() const;
|
||||
void setStartTime(const QTime &startTime);
|
||||
|
||||
QTime endTime() const;
|
||||
void setEndTime(const QTime &endTime);
|
||||
|
||||
double temperature() const;
|
||||
void setTemperature(double temperature);
|
||||
|
||||
TemperatureSchedule *clone() const;
|
||||
signals:
|
||||
void startTimeChanged();
|
||||
void endTimeChanged();
|
||||
void temperatureChanged();
|
||||
|
||||
private:
|
||||
QTime m_startTime;
|
||||
QTime m_endTime;
|
||||
double m_temperature = 0;
|
||||
|
||||
};
|
||||
|
||||
class TemperatureDaySchedule: public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
|
||||
public:
|
||||
enum Roles {
|
||||
RoleStartTime,
|
||||
RoleEndTime,
|
||||
RoleTemperature
|
||||
};
|
||||
TemperatureDaySchedule(QObject *parent = nullptr);
|
||||
~TemperatureDaySchedule();
|
||||
|
||||
int rowCount(const QModelIndex & = QModelIndex()) const override { return m_list.count(); }
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
void addSchedule(TemperatureSchedule *schedule);
|
||||
Q_INVOKABLE TemperatureSchedule* get(int index) const;
|
||||
|
||||
Q_INVOKABLE TemperatureDaySchedule *clone() const; // Passes ownership to caller
|
||||
Q_INVOKABLE TemperatureSchedule* createSchedule(const QTime &startTime, const QTime &endTime, double temperature);
|
||||
Q_INVOKABLE void removeSchedule(TemperatureSchedule *schedule);
|
||||
Q_INVOKABLE void clear();
|
||||
signals:
|
||||
void countChanged();
|
||||
|
||||
private:
|
||||
QList<TemperatureSchedule*> m_list;
|
||||
};
|
||||
|
||||
class TemperatureWeekSchedule: public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(int count READ rowCount CONSTANT)
|
||||
public:
|
||||
TemperatureWeekSchedule(QObject *parent = nullptr);
|
||||
~TemperatureWeekSchedule();
|
||||
|
||||
int rowCount(const QModelIndex & = QModelIndex()) const override { return m_list.count(); }
|
||||
QVariant data(const QModelIndex &, int) const override { return QVariant(); }
|
||||
QHash<int, QByteArray> roleNames() const override { return QHash<int, QByteArray>(); }
|
||||
|
||||
Q_INVOKABLE TemperatureDaySchedule* get(int index) const;
|
||||
|
||||
Q_INVOKABLE TemperatureWeekSchedule *clone() const; // Passes ownership to caller
|
||||
|
||||
private:
|
||||
QList<TemperatureDaySchedule*> m_list;
|
||||
};
|
||||
|
||||
#endif // TEMPERATURESCHEDULE_H
|
||||
216
experiences/airconditioning/zoneinfo.cpp
Normal file
216
experiences/airconditioning/zoneinfo.cpp
Normal file
@ -0,0 +1,216 @@
|
||||
#include "zoneinfo.h"
|
||||
|
||||
ZoneInfo::ZoneInfo(const QUuid &id, QObject *parent)
|
||||
: QObject{parent},
|
||||
m_id(id)
|
||||
{
|
||||
m_weekSchedule = new TemperatureWeekSchedule(this);
|
||||
}
|
||||
|
||||
QUuid ZoneInfo::id() const
|
||||
{
|
||||
return m_id;
|
||||
}
|
||||
|
||||
QString ZoneInfo::name() const
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
|
||||
void ZoneInfo::setName(const QString &name)
|
||||
{
|
||||
if (m_name != name) {
|
||||
m_name = name;
|
||||
emit nameChanged();
|
||||
}
|
||||
}
|
||||
|
||||
ZoneInfo::ZoneStatus ZoneInfo::zoneStatus() const
|
||||
{
|
||||
return m_zoneStatus;
|
||||
}
|
||||
|
||||
void ZoneInfo::setZoneStatus(ZoneStatus zoneStatus)
|
||||
{
|
||||
if (m_zoneStatus != zoneStatus) {
|
||||
m_zoneStatus = zoneStatus;
|
||||
emit zoneStatusChanged();
|
||||
}
|
||||
}
|
||||
|
||||
double ZoneInfo::currentSetpoint() const
|
||||
{
|
||||
return m_currentSetpoint;
|
||||
}
|
||||
|
||||
void ZoneInfo::setCurrentSetpoint(double currentSetpoint)
|
||||
{
|
||||
if (m_currentSetpoint != currentSetpoint) {
|
||||
m_currentSetpoint = currentSetpoint;
|
||||
emit currentSetpointChanged();
|
||||
}
|
||||
}
|
||||
|
||||
double ZoneInfo::standbySetpoint() const
|
||||
{
|
||||
return m_standbySetpoint;
|
||||
}
|
||||
|
||||
void ZoneInfo::setStandbySetpoint(double standbySetpoint)
|
||||
{
|
||||
if (m_standbySetpoint != standbySetpoint) {
|
||||
m_standbySetpoint = standbySetpoint;
|
||||
emit standbySetpointChanged();
|
||||
}
|
||||
}
|
||||
|
||||
double ZoneInfo::setpointOverride() const
|
||||
{
|
||||
return m_setpointOverride;
|
||||
}
|
||||
|
||||
ZoneInfo::SetpointOverrideMode ZoneInfo::setpointOverrideMode() const
|
||||
{
|
||||
return m_setpointOverrideMode;
|
||||
}
|
||||
|
||||
QDateTime ZoneInfo::setpointOverrideEnd() const
|
||||
{
|
||||
return m_setpointOverrideEnd;
|
||||
}
|
||||
|
||||
void ZoneInfo::setSetpointOverride(double setpointOverride, SetpointOverrideMode mode, const QDateTime &end)
|
||||
{
|
||||
if (m_setpointOverride != setpointOverride || m_setpointOverrideMode != mode || m_setpointOverrideEnd != end) {
|
||||
m_setpointOverride = setpointOverride;
|
||||
m_setpointOverrideMode = mode;
|
||||
m_setpointOverrideEnd = end;
|
||||
emit setpointOverrideChanged();
|
||||
}
|
||||
}
|
||||
|
||||
TemperatureWeekSchedule *ZoneInfo::weekSchedule() const
|
||||
{
|
||||
return m_weekSchedule;
|
||||
}
|
||||
|
||||
QList<QUuid> ZoneInfo::thermostats() const
|
||||
{
|
||||
return m_thermostats;
|
||||
}
|
||||
|
||||
void ZoneInfo::setThermostats(const QList<QUuid> &thermostats)
|
||||
{
|
||||
if (m_thermostats != thermostats) {
|
||||
m_thermostats = thermostats;
|
||||
emit thermostatsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
QList<QUuid> ZoneInfo::windowSensors() const
|
||||
{
|
||||
return m_windowSensors;
|
||||
}
|
||||
|
||||
void ZoneInfo::setWindowSensors(const QList<QUuid> &windowSensors)
|
||||
{
|
||||
if (m_windowSensors != windowSensors) {
|
||||
m_windowSensors = windowSensors;
|
||||
emit windowSensorsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
QList<QUuid> ZoneInfo::indoorSensors() const
|
||||
{
|
||||
return m_indoorSensors;
|
||||
}
|
||||
|
||||
void ZoneInfo::setIndoorSensors(const QList<QUuid> &indoorSensors)
|
||||
{
|
||||
if (m_indoorSensors != indoorSensors) {
|
||||
m_indoorSensors = indoorSensors;
|
||||
emit indoorSensorsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
QList<QUuid> ZoneInfo::outdoorSensors() const
|
||||
{
|
||||
return m_outdoorSensors;
|
||||
}
|
||||
|
||||
void ZoneInfo::setOutdoorSensors(const QList<QUuid> &outdoorSensors)
|
||||
{
|
||||
if (m_outdoorSensors != outdoorSensors) {
|
||||
m_outdoorSensors = outdoorSensors;
|
||||
emit outdoorSensorsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
QVariant ZoneInfos::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
switch (role) {
|
||||
case RoleId:
|
||||
return m_list.at(index.row())->id();
|
||||
case RoleName:
|
||||
return m_list.at(index.row())->name();
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> ZoneInfos::roleNames() const
|
||||
{
|
||||
return {
|
||||
{RoleId, "id"},
|
||||
{RoleName, "name"}
|
||||
};
|
||||
}
|
||||
|
||||
void ZoneInfos::addZoneInfo(ZoneInfo *zoneInfo)
|
||||
{
|
||||
zoneInfo->setParent(this);
|
||||
connect(zoneInfo, &ZoneInfo::nameChanged, this, [=](){
|
||||
QModelIndex idx = index(m_list.indexOf(zoneInfo));
|
||||
emit dataChanged(idx, idx, {RoleName});
|
||||
});
|
||||
beginInsertRows(QModelIndex(), m_list.count(), m_list.count());
|
||||
m_list.append(zoneInfo);
|
||||
endInsertRows();
|
||||
emit countChanged();
|
||||
}
|
||||
|
||||
void ZoneInfos::removeZoneInfo(const QUuid &zoneId)
|
||||
{
|
||||
int idx = -1;
|
||||
for (int i = 0; i < m_list.count(); i++) {
|
||||
ZoneInfo *zone = m_list.at(i);
|
||||
if (zone->id() == zoneId) {
|
||||
idx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (idx < 0) {
|
||||
return;
|
||||
}
|
||||
beginRemoveRows(QModelIndex(), idx, idx);
|
||||
m_list.takeAt(idx)->deleteLater();
|
||||
endRemoveRows();
|
||||
emit countChanged();
|
||||
}
|
||||
|
||||
ZoneInfo *ZoneInfos::get(int index) const
|
||||
{
|
||||
if (index < 0 || index >= m_list.count()) {
|
||||
return nullptr;
|
||||
}
|
||||
return m_list.at(index);
|
||||
}
|
||||
|
||||
ZoneInfo *ZoneInfos::getZoneInfo(const QUuid &zoneId) const
|
||||
{
|
||||
foreach (ZoneInfo *zone, m_list) {
|
||||
if (zone->id() == zoneId) {
|
||||
return zone;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
138
experiences/airconditioning/zoneinfo.h
Normal file
138
experiences/airconditioning/zoneinfo.h
Normal file
@ -0,0 +1,138 @@
|
||||
#ifndef ZONEINFO_H
|
||||
#define ZONEINFO_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QUuid>
|
||||
#include <QAbstractListModel>
|
||||
|
||||
#include "temperatureschedule.h"
|
||||
|
||||
class ZoneInfo : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QUuid id READ id CONSTANT)
|
||||
Q_PROPERTY(ZoneStatus zoneStatus READ zoneStatus NOTIFY zoneStatusChanged)
|
||||
Q_PROPERTY(QString name READ name NOTIFY nameChanged)
|
||||
Q_PROPERTY(double currentSetpoint READ currentSetpoint NOTIFY currentSetpointChanged)
|
||||
Q_PROPERTY(double standbySetpoint READ standbySetpoint NOTIFY standbySetpointChanged)
|
||||
Q_PROPERTY(double setpointOverride READ setpointOverride NOTIFY setpointOverrideChanged)
|
||||
Q_PROPERTY(SetpointOverrideMode setpointOverrideMode READ setpointOverrideMode NOTIFY setpointOverrideChanged)
|
||||
Q_PROPERTY(QDateTime setpointOverrideEnd READ setpointOverrideEnd NOTIFY setpointOverrideChanged)
|
||||
Q_PROPERTY(TemperatureWeekSchedule* weekSchedule READ weekSchedule CONSTANT)
|
||||
Q_PROPERTY(QList<QUuid> thermostats READ thermostats NOTIFY thermostatsChanged)
|
||||
Q_PROPERTY(QList<QUuid> windowSensors READ windowSensors NOTIFY windowSensorsChanged)
|
||||
Q_PROPERTY(QList<QUuid> indoorSensors READ indoorSensors NOTIFY indoorSensorsChanged)
|
||||
Q_PROPERTY(QList<QUuid> outdoorSensors READ outdoorSensors NOTIFY outdoorSensorsChanged)
|
||||
public:
|
||||
enum ZoneStatusFlag {
|
||||
ZoneStatusFlagNone = 0x00,
|
||||
ZoneStatusFlagTimeScheduleActive = 0x01,
|
||||
ZoneStatusFlagSetpointOverrideActive = 0x02,
|
||||
ZoneStatusFlagWindowOpen = 0x10,
|
||||
ZoneStatusFlagBadAir = 0x20,
|
||||
ZoneStatusFlagHighHumidity = 0x40
|
||||
};
|
||||
Q_ENUM(ZoneStatusFlag)
|
||||
Q_DECLARE_FLAGS(ZoneStatus, ZoneStatusFlag)
|
||||
// Q_DECLARE_OPERATORS_FOR_FLAGS(ZoneStatus)
|
||||
Q_FLAG(ZoneStatus)
|
||||
|
||||
enum SetpointOverrideMode {
|
||||
SetpointOverrideModeNone = 0,
|
||||
SetpointOverrideModeTimed,
|
||||
SetpointOverrideModeUnlimited,
|
||||
SetpointOverrideModeEventual
|
||||
};
|
||||
Q_ENUM(SetpointOverrideMode)
|
||||
|
||||
explicit ZoneInfo(const QUuid &id, QObject *parent = nullptr);
|
||||
|
||||
QUuid id() const;
|
||||
|
||||
QString name() const;
|
||||
void setName(const QString &name);
|
||||
|
||||
ZoneStatus zoneStatus() const;
|
||||
void setZoneStatus(ZoneStatus zoneStatus);
|
||||
|
||||
double currentSetpoint() const;
|
||||
void setCurrentSetpoint(double currentSetpoint);
|
||||
|
||||
double standbySetpoint() const;
|
||||
void setStandbySetpoint(double standbySetpoint);
|
||||
|
||||
double setpointOverride() const;
|
||||
SetpointOverrideMode setpointOverrideMode() const;
|
||||
QDateTime setpointOverrideEnd() const;
|
||||
void setSetpointOverride(double setpointOverride, SetpointOverrideMode mode, const QDateTime &end);
|
||||
|
||||
TemperatureWeekSchedule *weekSchedule() const;
|
||||
|
||||
QList<QUuid> thermostats() const;
|
||||
void setThermostats(const QList<QUuid> &thermostats);
|
||||
|
||||
QList<QUuid> windowSensors() const;
|
||||
void setWindowSensors(const QList<QUuid> &windowSensors);
|
||||
|
||||
QList<QUuid> indoorSensors() const;
|
||||
void setIndoorSensors(const QList<QUuid> &indoorSensors);
|
||||
|
||||
QList<QUuid> outdoorSensors() const;
|
||||
void setOutdoorSensors(const QList<QUuid> &outdoorSensors);
|
||||
|
||||
signals:
|
||||
void nameChanged();
|
||||
void zoneStatusChanged();
|
||||
void currentSetpointChanged();
|
||||
void standbySetpointChanged();
|
||||
void setpointOverrideChanged();
|
||||
void thermostatsChanged();
|
||||
void windowSensorsChanged();
|
||||
void indoorSensorsChanged();
|
||||
void outdoorSensorsChanged();
|
||||
|
||||
private:
|
||||
QUuid m_id;
|
||||
ZoneStatus m_zoneStatus = ZoneStatusFlagNone;
|
||||
QString m_name;
|
||||
double m_currentSetpoint = 18;
|
||||
double m_standbySetpoint = 18;
|
||||
double m_setpointOverride = 18;
|
||||
SetpointOverrideMode m_setpointOverrideMode = SetpointOverrideModeNone;
|
||||
QDateTime m_setpointOverrideEnd;
|
||||
TemperatureWeekSchedule *m_weekSchedule = nullptr;
|
||||
QList<QUuid> m_thermostats;
|
||||
QList<QUuid> m_windowSensors;
|
||||
QList<QUuid> m_indoorSensors;
|
||||
QList<QUuid> m_outdoorSensors;
|
||||
};
|
||||
|
||||
class ZoneInfos: public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
|
||||
public:
|
||||
enum Roles {
|
||||
RoleId,
|
||||
RoleName,
|
||||
};
|
||||
ZoneInfos(QObject *parent = nullptr): QAbstractListModel(parent) {}
|
||||
|
||||
int rowCount(const QModelIndex & = QModelIndex()) const override { return m_list.count(); }
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
void addZoneInfo(ZoneInfo *zoneInfo);
|
||||
void removeZoneInfo(const QUuid &zoneId);
|
||||
|
||||
Q_INVOKABLE ZoneInfo* get(int index) const;
|
||||
Q_INVOKABLE ZoneInfo* getZoneInfo(const QUuid &zoneId) const;
|
||||
|
||||
signals:
|
||||
void countChanged();
|
||||
|
||||
private:
|
||||
QList<ZoneInfo*> m_list;
|
||||
};
|
||||
|
||||
#endif // ZONEINFO_H
|
||||
3
experiences/experiences.pro
Normal file
3
experiences/experiences.pro
Normal file
@ -0,0 +1,3 @@
|
||||
TEMPLATE = subdirs
|
||||
|
||||
SUBDIRS += airconditioning
|
||||
@ -35,7 +35,6 @@
|
||||
#include <QUuid>
|
||||
#include <QUrl>
|
||||
#include <QHostAddress>
|
||||
#include <QBluetoothAddress>
|
||||
#include <QObject>
|
||||
#include <QAbstractListModel>
|
||||
#include <QDateTime>
|
||||
|
||||
@ -284,6 +284,7 @@ void EnergyLogs::getLogsResponse(int commandId, const QVariantMap ¶ms)
|
||||
double minValue = 0, maxValue = 0;
|
||||
qCDebug(dcEnergyLogs()) << "Logs response:" << qUtf8Printable(QJsonDocument::fromVariant(params).toJson());
|
||||
QList<EnergyLogEntry*> entries = unpackEntries(params, &minValue, &maxValue);
|
||||
qCDebug(dcEnergyLogs()) << "Energy logs received" << entries.count();
|
||||
|
||||
if (!entries.isEmpty()) {
|
||||
if (m_list.isEmpty()) {
|
||||
@ -455,7 +456,7 @@ void EnergyLogs::fetchLogs()
|
||||
m_fetchingData = true;
|
||||
fetchingDataChanged();
|
||||
|
||||
qCDebug(dcEnergyLogs()) << "Fetching" << m_startTime << m_endTime;
|
||||
qCDebug(dcEnergyLogs()) << "Fetching energy logs:" << qUtf8Printable(QJsonDocument::fromVariant(params).toJson());
|
||||
m_engine->jsonRpcClient()->sendCommand("Energy.Get" + logsName(), params, this, "getLogsResponse");
|
||||
}
|
||||
|
||||
|
||||
@ -67,6 +67,7 @@
|
||||
#include "models/logsmodelng.h"
|
||||
#include "models/barseriesadapter.h"
|
||||
#include "models/xyseriesadapter.h"
|
||||
#include "models/boolseriesadapter.h"
|
||||
#include "models/interfacesproxy.h"
|
||||
#include "configuration/nymeaconfiguration.h"
|
||||
#include "configuration/serverconfiguration.h"
|
||||
@ -147,6 +148,11 @@
|
||||
|
||||
#include <QtQml/qqml.h>
|
||||
|
||||
namespace Nymea
|
||||
{
|
||||
namespace Core
|
||||
{
|
||||
|
||||
static QObject* interfacesModel_provider(QQmlEngine *engine, QJSEngine *scriptEngine)
|
||||
{
|
||||
Q_UNUSED(engine)
|
||||
@ -270,6 +276,7 @@ void registerQmlTypes() {
|
||||
qmlRegisterUncreatableType<LogEntry>(uri, 1, 0, "LogEntry", "Get them from LogsModel");
|
||||
qmlRegisterType<BarSeriesAdapter>(uri, 1, 0, "BarSeriesAdapter");
|
||||
qmlRegisterType<XYSeriesAdapter>(uri, 1, 0, "XYSeriesAdapter");
|
||||
qmlRegisterType<BoolSeriesAdapter>(uri, 1, 0, "BoolSeriesAdapter");
|
||||
|
||||
qmlRegisterUncreatableType<TagsManager>(uri, 1, 0, "TagsManager", "Get it from Engine");
|
||||
qmlRegisterUncreatableType<Tags>(uri, 1, 0, "Tags", "Get it from TagsManager");
|
||||
@ -382,5 +389,6 @@ void registerQmlTypes() {
|
||||
|
||||
qmlRegisterType<SortFilterProxyModel>(uri, 1, 0, "SortFilterProxyModel");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
#endif // LIBNYMEAAPPCORE_H
|
||||
|
||||
@ -27,6 +27,7 @@ SOURCES += \
|
||||
$$PWD/energy/powerbalancelogs.cpp \
|
||||
$$PWD/energy/thingpowerlogs.cpp \
|
||||
$$PWD/connection/tunnelproxytransport.cpp \
|
||||
$$PWD/models/boolseriesadapter.cpp \
|
||||
$$PWD/models/scriptsproxymodel.cpp \
|
||||
$$PWD/pluginconfigmanager.cpp \
|
||||
$$PWD/tagwatcher.cpp \
|
||||
@ -190,6 +191,7 @@ HEADERS += \
|
||||
$$PWD/energy/powerbalancelogs.h \
|
||||
$$PWD/energy/thingpowerlogs.h \
|
||||
$$PWD/connection/tunnelproxytransport.h \
|
||||
$$PWD/models/boolseriesadapter.h \
|
||||
$$PWD/models/scriptsproxymodel.h \
|
||||
$$PWD/pluginconfigmanager.h \
|
||||
$$PWD/tagwatcher.h \
|
||||
|
||||
146
libnymea-app/models/boolseriesadapter.cpp
Normal file
146
libnymea-app/models/boolseriesadapter.cpp
Normal file
@ -0,0 +1,146 @@
|
||||
#include "boolseriesadapter.h"
|
||||
|
||||
BoolSeriesAdapter::BoolSeriesAdapter(QObject *parent)
|
||||
: QObject{parent}
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
LogsModel *BoolSeriesAdapter::logsModel() const
|
||||
{
|
||||
return m_model;
|
||||
}
|
||||
|
||||
void BoolSeriesAdapter::setLogsModel(LogsModel *logsModel)
|
||||
{
|
||||
if (m_model != logsModel) {
|
||||
m_model = logsModel;
|
||||
emit logsModelChanged();
|
||||
// update();
|
||||
connect(logsModel, &LogsModel::logEntryAdded, this, &BoolSeriesAdapter::logEntryAdded);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
QtCharts::QXYSeries *BoolSeriesAdapter::xySeries() const
|
||||
{
|
||||
return m_series;
|
||||
}
|
||||
|
||||
void BoolSeriesAdapter::setXySeries(QtCharts::QXYSeries *series)
|
||||
{
|
||||
if (m_series != series) {
|
||||
m_series = series;
|
||||
emit xySeriesChanged();
|
||||
|
||||
m_series->clear();
|
||||
m_series->append(QDateTime::currentDateTime().addYears(1).toMSecsSinceEpoch(), 0);
|
||||
m_series->append(0, 0);
|
||||
qWarning() << "Initialized series" << m_series->count();
|
||||
}
|
||||
}
|
||||
|
||||
bool BoolSeriesAdapter::inverted() const
|
||||
{
|
||||
return m_inverted;
|
||||
}
|
||||
|
||||
void BoolSeriesAdapter::setInverted(bool inverted)
|
||||
{
|
||||
if (m_inverted != inverted) {
|
||||
m_inverted = inverted;
|
||||
emit invertedChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void BoolSeriesAdapter::logEntryAdded(LogEntry *entry)
|
||||
{
|
||||
if (!m_series) {
|
||||
return;
|
||||
}
|
||||
|
||||
int idx = findIndex(entry->timestamp().toMSecsSinceEpoch());
|
||||
qreal value = entry->value().toBool() != m_inverted ? 1 : 0;
|
||||
|
||||
if (m_series->count() >= 2000) {
|
||||
qCWarning(dcLogEngine()) << "Thing logs too excessively. Discarding entry.";
|
||||
return;
|
||||
}
|
||||
|
||||
// QDebug dbg = qWarning();
|
||||
// dbg << "List before insert:\n";
|
||||
// for (int i = 0; i < m_series->count(); i++) {
|
||||
// dbg << i << QDateTime::fromMSecsSinceEpoch(m_series->at(i).x()) << m_series->at(i).y() << "\n";
|
||||
// }
|
||||
|
||||
// qWarning() << "Inserting" << entry->timestamp() << entry->value() << "real value:" << value << "at" << idx << "total:" << m_series->count();
|
||||
// We're keeping a fake entry at the beginning (timestamp 0) with a static value of 0
|
||||
// and on in the beginning (+1 year from now) for which we'll update the value according to
|
||||
// the newest real entry to continue painting the last value.
|
||||
|
||||
// Update the future value if this is the new newest real entry
|
||||
if (idx == 1) {
|
||||
m_series->replace(0, QDateTime::currentDateTime().addYears(1).toMSecsSinceEpoch(), value);
|
||||
}
|
||||
// If the next older entry is different than this, first insert the other value right before this one
|
||||
if (qFuzzyIsNull(m_series->at(idx).y()) != qFuzzyIsNull(value)) {
|
||||
m_series->insert(idx, QPointF(entry->timestamp().toMSecsSinceEpoch() - 1, !value));
|
||||
}
|
||||
|
||||
m_series->insert(idx, QPointF(entry->timestamp().toMSecsSinceEpoch(), value));
|
||||
|
||||
// If the next newer entry is differnt than this, also insert this value right before the next one
|
||||
if (qFuzzyIsNull(m_series->at(idx-1).y()) != qFuzzyIsNull(value)) {
|
||||
m_series->insert(idx, QPointF(m_series->at(idx-1).x() - 1, value));
|
||||
}
|
||||
|
||||
// qWarning() << "***** series count" << m_series->count();
|
||||
// dbg << "List after insert:\n";
|
||||
// for (int i = 0; i < m_series->count(); i++) {
|
||||
// dbg << i << QDateTime::fromMSecsSinceEpoch(m_series->at(i).x()) << m_series->at(i).y() << "\n";
|
||||
// }
|
||||
}
|
||||
|
||||
quint64 BoolSeriesAdapter::findIndex(qulonglong timestamp)
|
||||
{
|
||||
if (m_series->count() == 2) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// In 99.9% of the cases we'll be prepending (adding live entries) or appending (fetching history)
|
||||
if (timestamp < m_series->at(m_series->count() - 2).x()) {
|
||||
return m_series->count() - 1;
|
||||
}
|
||||
if (timestamp > m_series->at(1).x()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// If for any reason a entry in the middle is added (can't think of one but hey), a binary search will probably do.
|
||||
int idx = m_series->count() / 2;
|
||||
int range = idx;
|
||||
int i = 0;
|
||||
while (true) {
|
||||
qWarning() << "CNT:" << m_series->count()
|
||||
<< "first:" << QDateTime::fromMSecsSinceEpoch(m_series->at(1).x())
|
||||
<< "last:" << QDateTime::fromMSecsSinceEpoch(m_series->at(m_series->count()- 2).x())
|
||||
<< "current:" << idx << QDateTime::fromMSecsSinceEpoch(m_series->at(idx).x())
|
||||
<< "search:" << QDateTime::fromMSecsSinceEpoch(timestamp);
|
||||
if (timestamp >= m_series->at(idx).x() && timestamp < m_series->at(idx-1).x()) {
|
||||
return idx;
|
||||
}
|
||||
if (timestamp <= m_series->at(idx).x() && timestamp > m_series->at(idx+1).x()) {
|
||||
return idx+1;
|
||||
}
|
||||
range = qMax(1, range / 2);
|
||||
if (timestamp > m_series->at(idx).x()) {
|
||||
idx = idx - range;
|
||||
} else {
|
||||
idx = idx + range;
|
||||
}
|
||||
|
||||
if (i++ > 2000) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
49
libnymea-app/models/boolseriesadapter.h
Normal file
49
libnymea-app/models/boolseriesadapter.h
Normal file
@ -0,0 +1,49 @@
|
||||
#ifndef BOOLSERIESADAPTER_H
|
||||
#define BOOLSERIESADAPTER_H
|
||||
|
||||
#include "logsmodel.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QXYSeries>
|
||||
|
||||
class BoolSeriesAdapter : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(LogsModel* logsModel READ logsModel WRITE setLogsModel NOTIFY logsModelChanged)
|
||||
Q_PROPERTY(QtCharts::QXYSeries* xySeries READ xySeries WRITE setXySeries NOTIFY xySeriesChanged)
|
||||
|
||||
Q_PROPERTY(bool inverted READ inverted WRITE setInverted NOTIFY invertedChanged)
|
||||
|
||||
public:
|
||||
explicit BoolSeriesAdapter(QObject *parent = nullptr);
|
||||
|
||||
LogsModel* logsModel() const;
|
||||
void setLogsModel(LogsModel *logsModel);
|
||||
|
||||
QtCharts::QXYSeries* xySeries() const;
|
||||
void setXySeries(QtCharts::QXYSeries *series);
|
||||
|
||||
bool inverted() const;
|
||||
void setInverted(bool inverted);
|
||||
|
||||
signals:
|
||||
void xySeriesChanged();
|
||||
void logsModelChanged();
|
||||
void invertedChanged();
|
||||
|
||||
private slots:
|
||||
void logEntryAdded(LogEntry *entry);
|
||||
|
||||
private:
|
||||
qreal calculateSampleValue(int index);
|
||||
|
||||
quint64 findIndex(qulonglong timestamp);
|
||||
|
||||
private:
|
||||
LogsModel* m_model = nullptr;
|
||||
QtCharts::QXYSeries* m_series = nullptr;
|
||||
bool m_inverted = false;
|
||||
|
||||
};
|
||||
|
||||
#endif // BOOLSERIESADAPTER_H
|
||||
@ -202,7 +202,7 @@ void LogsModel::setViewStartTime(const QDateTime &viewStartTime)
|
||||
m_viewStartTime = viewStartTime;
|
||||
emit viewStartTimeChanged();
|
||||
if (m_list.count() == 0 || m_list.last()->timestamp() > m_viewStartTime) {
|
||||
if (m_canFetchMore) {
|
||||
if (m_canFetchMore) {
|
||||
fetchMore();
|
||||
}
|
||||
}
|
||||
@ -296,7 +296,10 @@ void LogsModel::logsReply(int /*commandId*/, const QVariantMap &data)
|
||||
int offset = data.value("offset").toInt() + m_generatedEntries;
|
||||
int count = data.value("count").toInt();
|
||||
|
||||
// qDebug() << qUtf8Printable(QJsonDocument::fromVariant(data).toJson());
|
||||
qCInfo(dcLogEngine()) << objectName() << "Logs reply:" << m_fetchStartTime.msecsTo(QDateTime::currentDateTime());
|
||||
qCDebug(dcLogEngine()) << objectName() << qUtf8Printable(QJsonDocument::fromVariant(data).toJson());
|
||||
|
||||
m_fetchStartTime = QDateTime::currentDateTime();
|
||||
|
||||
QList<LogEntry*> newBlock;
|
||||
QList<QVariant> logEntries = data.value("logEntries").toList();
|
||||
@ -314,9 +317,9 @@ void LogsModel::logsReply(int /*commandId*/, const QVariantMap &data)
|
||||
|
||||
bool stopProcessing = false;
|
||||
if (m_viewStartTime.isValid() && timeStamp.addSecs(-60) < m_viewStartTime) {
|
||||
timeStamp = m_viewStartTime.addSecs(-60);
|
||||
// timeStamp = m_viewStartTime.addSecs(-60);
|
||||
stopProcessing = true;
|
||||
m_generatedEntries++;
|
||||
// m_generatedEntries++;
|
||||
}
|
||||
LogEntry *entry = new LogEntry(timeStamp, value, thingId, typeId, loggingSource, loggingEventType, errorCode, this);
|
||||
newBlock.append(entry);
|
||||
@ -326,9 +329,9 @@ void LogsModel::logsReply(int /*commandId*/, const QVariantMap &data)
|
||||
}
|
||||
}
|
||||
|
||||
// qCDebug(dcLogEngine()) << objectName() << "Received logs from" << offset << "to" << offset + count << "Actual count:" << newBlock.count();
|
||||
qCInfo(dcLogEngine()) << objectName() << "Received logs from" << offset << "to" << offset + count << "Actual count:" << newBlock.count();
|
||||
|
||||
if (count < m_blockSize) {
|
||||
if (newBlock.count() == count && count < m_blockSize) {
|
||||
m_canFetchMore = false;
|
||||
}
|
||||
|
||||
@ -352,8 +355,10 @@ void LogsModel::logsReply(int /*commandId*/, const QVariantMap &data)
|
||||
|
||||
m_busyInternal = false;
|
||||
|
||||
qCInfo(dcLogEngine()) << objectName() << "Logs fetched" << m_fetchStartTime.msecsTo(QDateTime::currentDateTime());
|
||||
|
||||
if (m_viewStartTime.isValid() && m_list.count() > 0 && m_list.last()->timestamp() > m_viewStartTime && m_canFetchMore) {
|
||||
qCDebug(dcLogEngine()) << objectName() << "Fetching more because of viewStartTime" << m_viewStartTime.toString() << "last" << m_list.last()->timestamp().toString();
|
||||
qCInfo(dcLogEngine()) << objectName() << "Fetching more because of viewStartTime" << m_viewStartTime.toString() << "last" << m_list.last()->timestamp().toString();
|
||||
fetchMore();
|
||||
} else {
|
||||
m_busy = false;
|
||||
@ -410,9 +415,11 @@ void LogsModel::fetchMore(const QModelIndex &parent)
|
||||
params.insert("limit", m_blockSize);
|
||||
params.insert("offset", m_list.count() - m_generatedEntries);
|
||||
|
||||
// qDebug() << "Fetching logs from" << m_startTime.toString() << "to" << m_endTime.toString() << "with offset" << m_list.count() << "and limit" << m_blockSize;
|
||||
qCInfo(dcLogEngine()) << "Fetching logs from:" << m_list.count() - m_generatedEntries << "max" << m_blockSize;
|
||||
qCDebug(dcLogEngine()) << qUtf8Printable(QJsonDocument::fromVariant(params).toJson());
|
||||
|
||||
m_engine->jsonRpcClient()->sendCommand("Logging.GetLogEntries", params, this, "logsReply");
|
||||
m_fetchStartTime = QDateTime::currentDateTime();
|
||||
// qDebug() << "GetLogEntries called";
|
||||
}
|
||||
|
||||
|
||||
@ -144,6 +144,7 @@ protected:
|
||||
|
||||
int m_generatedEntries = 0;
|
||||
|
||||
QDateTime m_fetchStartTime;
|
||||
};
|
||||
|
||||
#endif // LOGSMODEL_H
|
||||
|
||||
@ -247,6 +247,67 @@ LogEntry *LogsModelNg::get(int index) const
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
LogEntry *LogsModelNg::findClosest(const QDateTime &dateTime) const
|
||||
{
|
||||
// qWarning() << "********************Finding closest for:" << dateTime.toString();
|
||||
// foreach (LogEntry *entry, m_list) {
|
||||
// qWarning() << "List entry:" << entry->timestamp().toString();
|
||||
// }
|
||||
if (m_list.isEmpty()) {
|
||||
// qWarning() << "No entries here...";
|
||||
return nullptr;
|
||||
}
|
||||
int newest = 0;
|
||||
int oldest = m_list.count() - 1;
|
||||
LogEntry *entry = nullptr;
|
||||
int step = 0;
|
||||
|
||||
LogEntry *allTimeOldestEntry = m_list.at(oldest);
|
||||
if (dateTime < allTimeOldestEntry->timestamp()) {
|
||||
// qWarning() << "All time oldest is newer than searched";
|
||||
return nullptr;
|
||||
}
|
||||
// qWarning() << "Oldest:" << oldest << "newest:" << newest << "step" << step << "count" << m_list.count();
|
||||
while (oldest >= newest && step < m_list.count()) {
|
||||
LogEntry *oldestEntry = m_list.at(oldest);
|
||||
LogEntry *newestEntry = m_list.at(newest);
|
||||
int middle = (oldest - newest) / 2 + newest;
|
||||
LogEntry *middleEntry = m_list.at(middle);
|
||||
// qWarning() << "Oldest:" << oldest << oldestEntry->timestamp().toString() << oldestEntry->value() << "Middle:" << middle << middleEntry->timestamp().toString() << middleEntry->value() << "Newest:" << newest << newestEntry->timestamp().toString() << newestEntry->value() << ":" << (oldest - newest);
|
||||
if (dateTime <= oldestEntry->timestamp()) {
|
||||
// qWarning() << "Returning oldest";
|
||||
return oldestEntry;
|
||||
}
|
||||
if (dateTime >= newestEntry->timestamp()) {
|
||||
// qWarning() << "Returning newest";
|
||||
return newestEntry;
|
||||
}
|
||||
|
||||
if (dateTime == middleEntry->timestamp()) {
|
||||
// qWarning() << "Returning middle";
|
||||
return middleEntry;
|
||||
}
|
||||
|
||||
if (dateTime < middleEntry->timestamp()) {
|
||||
newest = middle;
|
||||
} else {
|
||||
oldest = middle;
|
||||
}
|
||||
|
||||
if (oldest - newest == 1) {
|
||||
if (oldest > middle) {
|
||||
// qWarning() << "EOL. Returning oldest";
|
||||
return oldestEntry;
|
||||
} else {
|
||||
// qWarning() << "EOL. Returning middle";
|
||||
return middleEntry;
|
||||
}
|
||||
}
|
||||
step++;
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
|
||||
void LogsModelNg::logsReply(int commandId, const QVariantMap &data)
|
||||
{
|
||||
Q_UNUSED(commandId)
|
||||
|
||||
@ -107,6 +107,7 @@ public:
|
||||
QVariant maxValue() const;
|
||||
|
||||
Q_INVOKABLE LogEntry *get(int index) const;
|
||||
Q_INVOKABLE LogEntry *findClosest(const QDateTime &dateTime) const;
|
||||
|
||||
protected:
|
||||
virtual void fetchMore(const QModelIndex &parent = QModelIndex()) override;
|
||||
|
||||
@ -35,6 +35,8 @@ void XYSeriesAdapter::setXySeries(QtCharts::QXYSeries *series)
|
||||
if (m_series != series) {
|
||||
m_series = series;
|
||||
emit xySeriesChanged();
|
||||
|
||||
ensureSamples(QDateTime::currentDateTime(), QDateTime::currentDateTime().addMSecs(2 * 60000));
|
||||
}
|
||||
}
|
||||
|
||||
@ -135,14 +137,14 @@ qreal XYSeriesAdapter::minValue() const
|
||||
|
||||
void XYSeriesAdapter::ensureSamples(const QDateTime &from, const QDateTime &to)
|
||||
{
|
||||
// qWarning() << "Ensuring samples:" << from.toString("yyyy-MM-dd hh:mm:ss") << to.toString("yyyy-MM-dd hh:mm:ss");
|
||||
if (!m_series) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_samples.isEmpty()) {
|
||||
Sample *sample = new Sample();
|
||||
sample->timestamp = from.addSecs(m_sampleRate);
|
||||
sample->timestamp = from.addSecs(m_sampleRate / 2);
|
||||
// qWarning() << "Added first" << from << sample->timestamp;
|
||||
m_newestSample = sample->timestamp;
|
||||
m_oldestSample = m_newestSample;
|
||||
m_samples.append(sample);
|
||||
@ -152,18 +154,26 @@ void XYSeriesAdapter::ensureSamples(const QDateTime &from, const QDateTime &to)
|
||||
while (to > m_newestSample) {
|
||||
Sample *sample = new Sample();
|
||||
sample->timestamp = m_newestSample.addSecs(m_sampleRate);
|
||||
Sample *oldNewest = m_samples.first();
|
||||
if (oldNewest->entries.count() > 0) {
|
||||
sample->startingPoint = oldNewest->entries.last();
|
||||
} else if (oldNewest->startingPoint != nullptr) {
|
||||
sample->startingPoint = oldNewest->startingPoint;
|
||||
}
|
||||
m_newestSample = sample->timestamp;
|
||||
m_samples.prepend(sample);
|
||||
m_series->insert(0, QPointF(sample->timestamp.toMSecsSinceEpoch(), 0));
|
||||
m_series->insert(0, QPointF(sample->timestamp.toMSecsSinceEpoch(), m_series->at(0).y()));
|
||||
}
|
||||
|
||||
while (from < m_oldestSample.addSecs(m_sampleRate)) {
|
||||
while (from < m_oldestSample.addSecs(-m_sampleRate)) {
|
||||
// qWarning() << "Added one before" << from << m_oldestSample.addMSecs(-m_sampleRate) << m_oldestSample;
|
||||
Sample *sample = new Sample();
|
||||
sample->timestamp = m_oldestSample.addSecs(-m_sampleRate);
|
||||
m_oldestSample = sample->timestamp;
|
||||
m_samples.append(sample);
|
||||
m_series->append(sample->timestamp.toMSecsSinceEpoch(), 0);
|
||||
}
|
||||
// qWarning() << "Ensuring samples:" << from.toString("yyyy-MM-dd hh:mm:ss") << to.toString("yyyy-MM-dd hh:mm:ss") << "Oldest:" << m_oldestSample.toString("yyyy-MM-dd hh:mm:ss") << "Newest:" << m_newestSample.toString("yyyy-MM-dd hh:mm:ss") ;
|
||||
}
|
||||
|
||||
void XYSeriesAdapter::logEntryAdded(LogEntry *entry)
|
||||
@ -179,7 +189,7 @@ void XYSeriesAdapter::logEntryAdded(LogEntry *entry)
|
||||
qCWarning(dcLogEngine) << objectName() << "Overflowing integer size for XYSeriesAdapter!";
|
||||
return;
|
||||
}
|
||||
// qCDebug(dcLogEngine()) << objectName() << "Inserting sample at:" << idx << entry->timestamp();
|
||||
// qWarning() << objectName() << "Inserting sample at:" << idx << entry->timestamp().toString("yyyy-MM-dd hh:mm:ss");
|
||||
Sample *sample = m_samples.at(idx);
|
||||
LogEntry *oldLast = nullptr;
|
||||
// In theory we'd need to insert sorted, but only the last one actually matters for subsequent samples
|
||||
@ -194,7 +204,7 @@ void XYSeriesAdapter::logEntryAdded(LogEntry *entry)
|
||||
|
||||
qreal value = calculateSampleValue(idx);
|
||||
m_series->replace(idx, sample->timestamp.toMSecsSinceEpoch(), value);
|
||||
// qWarning() << "sample value added" << idx << entry->timestamp().time().toString("hh:mm:ss") << value;
|
||||
// qWarning() << "sample value updated" << idx << sample->timestamp.toString("yyyy-MM-dd hh:mm:ss") << value;
|
||||
|
||||
if (value < m_minValue) {
|
||||
m_minValue = value;
|
||||
|
||||
@ -75,7 +75,7 @@ private:
|
||||
class Sample {
|
||||
public:
|
||||
QDateTime timestamp; // The timestamp where this sample *ends*
|
||||
QList<LogEntry*> entries; // all log entries in this sample, that is, from timestamp - smaple size to timestamp
|
||||
QVector<LogEntry*> entries; // all log entries in this sample, that is, from timestamp - smaple size to timestamp
|
||||
LogEntry *startingPoint = nullptr; // the starting point for the sample. Normally the last entry of the previous sample
|
||||
};
|
||||
LogsModel* m_model = nullptr;
|
||||
|
||||
@ -95,6 +95,8 @@ QVariant Things::data(const QModelIndex &index, int role) const
|
||||
return thing->thingClass()->interfaces();
|
||||
case RoleBaseInterface:
|
||||
return thing->thingClass()->baseInterface();
|
||||
case RoleMainInterface:
|
||||
return thing->thingClass()->interfaces().count() > 0 ? thing->thingClass()->interfaces().first() : "";
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
@ -167,5 +169,6 @@ QHash<int, QByteArray> Things::roleNames() const
|
||||
roles[RoleSetupDisplayMessage] = "setupDisplayMessage";
|
||||
roles[RoleInterfaces] = "interfaces";
|
||||
roles[RoleBaseInterface] = "baseInterface";
|
||||
roles[RoleMainInterface] = "mainInterface";
|
||||
return roles;
|
||||
}
|
||||
|
||||
@ -52,7 +52,8 @@ public:
|
||||
RoleSetupStatus,
|
||||
RoleSetupDisplayMessage,
|
||||
RoleInterfaces,
|
||||
RoleBaseInterface
|
||||
RoleBaseInterface,
|
||||
RoleMainInterface
|
||||
};
|
||||
Q_ENUM(Roles)
|
||||
|
||||
|
||||
@ -37,6 +37,9 @@ ThingsProxy::ThingsProxy(QObject *parent) :
|
||||
QSortFilterProxyModel(parent)
|
||||
{
|
||||
setSortRole(Things::RoleName);
|
||||
connect(this, &ThingsProxy::countChanged, this, [=](){
|
||||
m_oldCount = rowCount();
|
||||
});
|
||||
}
|
||||
|
||||
Engine *ThingsProxy::engine() const
|
||||
@ -67,7 +70,7 @@ void ThingsProxy::setEngine(Engine *engine)
|
||||
connect(sourceModel(), SIGNAL(countChanged()), this, SIGNAL(countChanged()));
|
||||
connect(sourceModel(), &QAbstractItemModel::dataChanged, this, [this]() {
|
||||
// Only invalidate the filter if we're actually interested in state changes
|
||||
if (!m_sortStateName.isEmpty() || m_filterBatteryCritical || m_filterDisconnected || m_filterUpdates || m_filterSetupFailed) {
|
||||
if (!m_sortStateName.isEmpty() || m_filterBatteryCritical || m_filterDisconnected || m_filterUpdates || m_filterSetupFailed || !m_stateFilter.isEmpty()) {
|
||||
invalidateFilterInternal();
|
||||
}
|
||||
});
|
||||
@ -264,6 +267,28 @@ void ThingsProxy::setHiddenThingClassIds(const QStringList &hiddenThingClassIds)
|
||||
}
|
||||
}
|
||||
|
||||
QStringList ThingsProxy::shownThingIds() const
|
||||
{
|
||||
QStringList ret;
|
||||
foreach (const QUuid &id, m_shownThingIds) {
|
||||
ret << id.toString();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ThingsProxy::setShownThingIds(const QStringList &shownThingIds)
|
||||
{
|
||||
QList<QUuid> uuids;
|
||||
foreach (const QString &str, shownThingIds) {
|
||||
uuids << QUuid(str);
|
||||
}
|
||||
if (m_shownThingIds != uuids) {
|
||||
m_shownThingIds = uuids;
|
||||
emit shownThingIdsChanged();
|
||||
invalidateFilterInternal();
|
||||
}
|
||||
}
|
||||
|
||||
QStringList ThingsProxy::hiddenThingIds() const
|
||||
{
|
||||
QStringList ret;
|
||||
@ -455,6 +480,21 @@ void ThingsProxy::setParamsFilter(const QVariantMap ¶msFilter)
|
||||
}
|
||||
}
|
||||
|
||||
QVariantMap ThingsProxy::stateFilter() const
|
||||
{
|
||||
return m_stateFilter;
|
||||
}
|
||||
|
||||
void ThingsProxy::setStateFilter(const QVariantMap &stateFilter)
|
||||
{
|
||||
if (m_stateFilter != stateFilter) {
|
||||
m_stateFilter = stateFilter;
|
||||
emit stateFilterChanged();
|
||||
|
||||
invalidateFilterInternal();
|
||||
}
|
||||
}
|
||||
|
||||
bool ThingsProxy::groupByInterface() const
|
||||
{
|
||||
return m_groupByInterface;
|
||||
@ -526,9 +566,8 @@ int ThingsProxy::indexOf(Thing *thing) const
|
||||
|
||||
void ThingsProxy::invalidateFilterInternal()
|
||||
{
|
||||
int oldCount = rowCount();
|
||||
invalidateFilter();
|
||||
if (oldCount != rowCount()) {
|
||||
if (m_oldCount != rowCount()) {
|
||||
emit countChanged();
|
||||
}
|
||||
}
|
||||
@ -622,13 +661,12 @@ bool ThingsProxy::filterAcceptsRow(int source_row, const QModelIndex &source_par
|
||||
}
|
||||
|
||||
ThingClass *thingClass = m_engine->thingManager()->thingClasses()->getThingClass(thing->thingClassId());
|
||||
// qDebug() << "Checking thing" << thingClass->name() << thingClass->interfaces();
|
||||
if (!m_shownInterfaces.isEmpty()) {
|
||||
bool foundMatch = false;
|
||||
foreach (const QString &filterInterface, m_shownInterfaces) {
|
||||
if (thingClass->interfaces().contains(filterInterface)) {
|
||||
foundMatch = true;
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!foundMatch) {
|
||||
@ -655,6 +693,12 @@ bool ThingsProxy::filterAcceptsRow(int source_row, const QModelIndex &source_par
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_shownThingIds.isEmpty()) {
|
||||
if (!m_shownThingIds.contains(thing->id())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_hiddenThingIds.contains(thing->id())) {
|
||||
return false;
|
||||
}
|
||||
@ -732,5 +776,14 @@ bool ThingsProxy::filterAcceptsRow(int source_row, const QModelIndex &source_par
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_stateFilter.isEmpty()) {
|
||||
foreach (const QString &stateName, m_stateFilter.keys()) {
|
||||
State *state = thing->stateByName(stateName);
|
||||
if (!state || state->value() != m_stateFilter.value(stateName)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent);
|
||||
}
|
||||
|
||||
@ -56,6 +56,7 @@ class ThingsProxy : public QSortFilterProxyModel
|
||||
|
||||
Q_PROPERTY(QStringList shownThingClassIds READ shownThingClassIds WRITE setShownThingClassIds NOTIFY shownThingClassIdsChanged)
|
||||
Q_PROPERTY(QStringList hiddenThingClassIds READ hiddenThingClassIds WRITE setHiddenThingClassIds NOTIFY hiddenThingClassIdsChanged)
|
||||
Q_PROPERTY(QStringList shownThingIds READ shownThingIds WRITE setShownThingIds NOTIFY shownThingIdsChanged)
|
||||
Q_PROPERTY(QStringList hiddenThingIds READ hiddenThingIds WRITE setHiddenThingIds NOTIFY hiddenThingIdsChanged)
|
||||
|
||||
Q_PROPERTY(QString requiredEventName READ requiredEventName WRITE setRequiredEventName NOTIFY requiredEventNameChanged)
|
||||
@ -81,6 +82,9 @@ class ThingsProxy : public QSortFilterProxyModel
|
||||
// A map of paramName:value pairs, all given need to match
|
||||
Q_PROPERTY(QVariantMap paramsFilter READ paramsFilter WRITE setParamsFilter NOTIFY paramsFilterChanged)
|
||||
|
||||
// A map of stateName:value pairs, all given need to match
|
||||
Q_PROPERTY(QVariantMap stateFilter READ stateFilter WRITE setStateFilter NOTIFY stateFilterChanged)
|
||||
|
||||
Q_PROPERTY(bool groupByInterface READ groupByInterface WRITE setGroupByInterface NOTIFY groupByInterfaceChanged)
|
||||
|
||||
// If set, sorting will happen for the value of the given state. Make sure the filter is set to contain only things that have the given state
|
||||
@ -128,6 +132,9 @@ public:
|
||||
QStringList hiddenThingClassIds() const;
|
||||
void setHiddenThingClassIds(const QStringList &hiddenThingClassIds);
|
||||
|
||||
QStringList shownThingIds() const;
|
||||
void setShownThingIds(const QStringList &shownThingIds);
|
||||
|
||||
QStringList hiddenThingIds() const;
|
||||
void setHiddenThingIds(const QStringList &hiddenThingIds);
|
||||
|
||||
@ -167,6 +174,9 @@ public:
|
||||
QVariantMap paramsFilter() const;
|
||||
void setParamsFilter(const QVariantMap ¶msFilter);
|
||||
|
||||
QVariantMap stateFilter() const;
|
||||
void setStateFilter(const QVariantMap &stateFilter);
|
||||
|
||||
bool groupByInterface() const;
|
||||
void setGroupByInterface(bool groupByInterface);
|
||||
|
||||
@ -192,6 +202,7 @@ signals:
|
||||
void nameFilterChanged();
|
||||
void shownThingClassIdsChanged();
|
||||
void hiddenThingClassIdsChanged();
|
||||
void shownThingIdsChanged();
|
||||
void hiddenThingIdsChanged();
|
||||
void requiredEventNameChanged();
|
||||
void requiredStateNameChanged();
|
||||
@ -205,6 +216,7 @@ signals:
|
||||
void filterSetupFailedChanged();
|
||||
void filterUpdatesChanged();
|
||||
void paramsFilterChanged();
|
||||
void stateFilterChanged();
|
||||
void groupByInterfaceChanged();
|
||||
void sortStateNameChanged();
|
||||
void sortOrderChanged();
|
||||
@ -228,6 +240,7 @@ private:
|
||||
QString m_nameFilter;
|
||||
QList<QUuid> m_shownThingClassIds;
|
||||
QList<QUuid> m_hiddenThingClassIds;
|
||||
QList<QUuid> m_shownThingIds;
|
||||
QList<QUuid> m_hiddenThingIds;
|
||||
|
||||
QString m_requiredEventName;
|
||||
@ -245,11 +258,14 @@ private:
|
||||
bool m_filterUpdates = false;
|
||||
|
||||
QVariantMap m_paramsFilter;
|
||||
QVariantMap m_stateFilter;
|
||||
|
||||
bool m_groupByInterface = false;
|
||||
|
||||
QString m_sortStateName;
|
||||
|
||||
int m_oldCount = 0;
|
||||
|
||||
protected:
|
||||
bool lessThan(const QModelIndex &left, const QModelIndex &right) const Q_DECL_OVERRIDE;
|
||||
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
|
||||
|
||||
@ -122,15 +122,15 @@ bool ZigbeeAdapter::operator==(const ZigbeeAdapter &other) const
|
||||
&& m_baudRate == other.baudRate();
|
||||
}
|
||||
|
||||
QDebug operator<<(QDebug debug, const ZigbeeAdapter &adapter)
|
||||
QDebug operator<<(QDebug dbg, const ZigbeeAdapter &adapter)
|
||||
{
|
||||
debug.nospace() << "ZigbeeAdapter(" << adapter.name() << " - " << adapter.description();
|
||||
debug.nospace() << ", " << adapter.serialPort();
|
||||
dbg.nospace() << "ZigbeeAdapter(" << adapter.name() << " - " << adapter.description();
|
||||
dbg.nospace() << ", " << adapter.serialPort();
|
||||
if (adapter.hardwareRecognized()) {
|
||||
debug.nospace() << " Hardware recognized: " << adapter.backend();
|
||||
debug.nospace() << ", " << adapter.baudRate();
|
||||
dbg.nospace() << " Hardware recognized: " << adapter.backend();
|
||||
dbg.nospace() << ", " << adapter.baudRate();
|
||||
}
|
||||
|
||||
debug.nospace() << ")";
|
||||
return debug.space();
|
||||
dbg.nospace() << ")";
|
||||
return dbg.space();
|
||||
}
|
||||
|
||||
@ -3,8 +3,10 @@ TEMPLATE=subdirs
|
||||
include(shared.pri)
|
||||
message("APP_VERSION: $${APP_VERSION} ($${APP_REVISION})")
|
||||
|
||||
SUBDIRS = libnymea-app nymea-app
|
||||
nymea-app.depends = libnymea-app
|
||||
SUBDIRS = libnymea-app experiences nymea-app
|
||||
|
||||
experiences.depends = libnymea-app
|
||||
nymea-app.depends = libnymea-app experiences
|
||||
|
||||
withtests: {
|
||||
SUBDIRS += tests
|
||||
|
||||
@ -299,5 +299,10 @@
|
||||
<file>ui/images/plus.svg</file>
|
||||
<file>ui/images/minus.svg</file>
|
||||
<file>ui/images/sensors/vibration.svg</file>
|
||||
<file>ui/images/calendar.svg</file>
|
||||
<file>ui/images/sensors/window-closed.svg</file>
|
||||
<file>ui/images/sensors/window-open.svg</file>
|
||||
<file>ui/images/infinity.svg</file>
|
||||
<file>ui/images/edit-paste.svg</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
@ -38,6 +38,7 @@
|
||||
#include <QCommandLineOption>
|
||||
|
||||
#include "libnymea-app-core.h"
|
||||
#include "libnymea-app-airconditioning.h"
|
||||
|
||||
#include "stylecontroller.h"
|
||||
#include "pushnotifications.h"
|
||||
@ -129,10 +130,13 @@ int main(int argc, char *argv[])
|
||||
application.installTranslator(&overlayTranslator);
|
||||
#endif
|
||||
|
||||
registerQmlTypes();
|
||||
Nymea::Core::registerQmlTypes();
|
||||
Nymea::AirConditioning::registerQmlTypes();
|
||||
|
||||
QQmlApplicationEngine *engine = new QQmlApplicationEngine();
|
||||
|
||||
engine->addImportPath(application.applicationDirPath() + "/../experiences/");
|
||||
|
||||
QString defaultStyle;
|
||||
if (parser.isSet(defaultStyleOption)) {
|
||||
defaultStyle = parser.value(defaultStyleOption);
|
||||
|
||||
@ -12,16 +12,22 @@ qtHaveModule(webview) {
|
||||
DEFINES += HAVE_WEBVIEW
|
||||
}
|
||||
|
||||
INCLUDEPATH += $$top_srcdir/libnymea-app
|
||||
LIBS += -L$$top_builddir/libnymea-app/ -lnymea-app
|
||||
INCLUDEPATH += $$top_srcdir/libnymea-app \
|
||||
$$top_srcdir/experiences/airconditioning
|
||||
|
||||
win32:Debug:LIBS += -L$$top_builddir/libnymea-app/debug
|
||||
win32:Release:LIBS += -L$$top_builddir/libnymea-app/release
|
||||
LIBS += -L$$top_builddir/libnymea-app/ -lnymea-app \
|
||||
-L$$top_builddir/experiences/airconditioning -lnymea-app-airconditioning
|
||||
|
||||
win32:Debug:LIBS += -L$$top_builddir/libnymea-app/debug \
|
||||
-L$$top_builddir/experiences/airconditioning/debug
|
||||
win32:Release:LIBS += -L$$top_builddir/libnymea-app/release \
|
||||
-L$$top_builddir/experiences/airconditioning/release
|
||||
win32:CXX_FLAGS += /w
|
||||
|
||||
linux:!android:!nozeroconf:LIBS += -lavahi-client -lavahi-common
|
||||
PRE_TARGETDEPS += ../libnymea-app
|
||||
linux:!android:PRE_TARGETDEPS += $$top_builddir/libnymea-app/libnymea-app.a
|
||||
|
||||
linux:!android:PRE_TARGETDEPS += $$top_builddir/libnymea-app/libnymea-app.a \
|
||||
$$top_builddir/experiences/airconditioning/libnymea-app-airconditioning.a
|
||||
|
||||
HEADERS += \
|
||||
configuredhostsmodel.h \
|
||||
@ -89,8 +95,11 @@ android {
|
||||
platformintegration/android/platformpermissionsandroid.cpp \
|
||||
|
||||
# https://bugreports.qt.io/browse/QTBUG-83165
|
||||
LIBS += -L$${top_builddir}/libnymea-app/$${ANDROID_TARGET_ARCH}
|
||||
PRE_TARGETDEPS += $$top_builddir/libnymea-app/$${ANDROID_TARGET_ARCH}/libnymea-app.a
|
||||
CORE_LIBS += -L$${top_builddir}/libnymea-app/$${ANDROID_TARGET_ARCH}
|
||||
AIRCONDITIONING_LIBS += -L$${top_builddir}/experiences/airconditioning/$${ANDROID_TARGET_ARCH}
|
||||
|
||||
LIBS += $${CORE_LIBS} $${AIRCONDITIONING_LIBS}
|
||||
message("CORE_LIBS: $${CORE_LIBS}")
|
||||
|
||||
versioninfo.files = ../version.txt
|
||||
versioninfo.path = /
|
||||
|
||||
@ -23,7 +23,7 @@
|
||||
<file>ui/components/Graph.qml</file>
|
||||
<file>ui/components/ErrorDialog.qml</file>
|
||||
<file>ui/components/ShutterControls.qml</file>
|
||||
<file>ui/components/MeaDialog.qml</file>
|
||||
<file>ui/components/NymeaDialog.qml</file>
|
||||
<file>ui/components/MainPageTabButton.qml</file>
|
||||
<file>ui/components/AutoSizeMenu.qml</file>
|
||||
<file>ui/components/EmptyViewPlaceholder.qml</file>
|
||||
@ -289,5 +289,25 @@
|
||||
<file>ui/mainviews/energy/ConsumerStatsPage.qml</file>
|
||||
<file>ui/mainviews/energy/ConsumersPieChartPage.qml</file>
|
||||
<file>ui/connection/ManualConnectionEntry.qml</file>
|
||||
<file>ui/mainviews/AirConditioningView.qml</file>
|
||||
<file>ui/mainviews/airconditioning/ZoneView.qml</file>
|
||||
<file>ui/mainviews/airconditioning/TimeSchedulePage.qml</file>
|
||||
<file>ui/mainviews/airconditioning/TemperatureScheduleEditor.qml</file>
|
||||
<file>ui/customviews/SensorView.qml</file>
|
||||
<file>ui/components/MultiSelectionTabs.qml</file>
|
||||
<file>ui/mainviews/airconditioning/ZonePage.qml</file>
|
||||
<file>ui/mainviews/airconditioning/ZonesView.qml</file>
|
||||
<file>ui/mainviews/airconditioning/ACSettingsPage.qml</file>
|
||||
<file>ui/components/StateDial.qml</file>
|
||||
<file>ui/delegates/SensorListDelegate.qml</file>
|
||||
<file>ui/mainviews/airconditioning/ZoneStatusIcons.qml</file>
|
||||
<file>ui/mainviews/airconditioning/BigZoneStatusIcons.qml</file>
|
||||
<file>ui/mainviews/airconditioning/ZoneInfoWrapper.qml</file>
|
||||
<file>ui/mainviews/airconditioning/ACChartsPage.qml</file>
|
||||
<file>ui/mainviews/airconditioning/TimeOverrideDialog.qml</file>
|
||||
<file>ui/mainviews/airconditioning/TooltipDelegate.qml</file>
|
||||
<file>ui/mainviews/airconditioning/EditZonePage.qml</file>
|
||||
<file>ui/mainviews/airconditioning/EditZoneThingsPage.qml</file>
|
||||
<file>ui/mainviews/airconditioning/LegendDelegate.qml</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
@ -174,6 +174,7 @@ Page {
|
||||
ListElement { name: "energy"; source: "EnergyView"; displayName: qsTr("Energy"); icon: "smartmeter"; minVersion: "2.0" }
|
||||
ListElement { name: "media"; source: "MediaView"; displayName: qsTr("Media"); icon: "media"; minVersion: "2.0" }
|
||||
ListElement { name: "dashboard"; source: "DashboardView"; displayName: qsTr("Dashboard"); icon: "dashboard"; minVersion: "5.5" }
|
||||
ListElement { name: "airconditioning"; source: "AirConditioningView"; displayName: qsTr("AC"); icon: "sensors"; minVersion: "6.2" }
|
||||
}
|
||||
|
||||
ListModel {
|
||||
@ -254,10 +255,15 @@ Page {
|
||||
readonly property int scrollOffset: swipeView.currentItem ? swipeView.currentItem.item.contentY : 0
|
||||
readonly property int headerBlurSize: Math.min(headerSize, scrollOffset * 2)
|
||||
|
||||
Background {
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
SwipeView {
|
||||
id: swipeView
|
||||
anchors.fill: parent
|
||||
opacity: d.configOverlay === null ? 1 : 0
|
||||
visible: !engine.thingManager.fetchingData
|
||||
Behavior on opacity { NumberAnimation { duration: 200; easing.type: Easing.InOutQuad } }
|
||||
|
||||
Repeater {
|
||||
@ -820,7 +826,7 @@ Page {
|
||||
|
||||
Component {
|
||||
id: connectionDialogComponent
|
||||
MeaDialog {
|
||||
NymeaDialog {
|
||||
id: connectionDialog
|
||||
title: engine.jsonRpcClient.currentHost.name
|
||||
standardButtons: Dialog.NoButton
|
||||
|
||||
@ -254,6 +254,10 @@ ApplicationWindow {
|
||||
return qsTr("Cleaning robots")
|
||||
case "electricvehicle":
|
||||
return qsTr("Electric cars");
|
||||
case "closablesensor":
|
||||
return qsTr("Door/Window sensors");
|
||||
case "o3sensor":
|
||||
return qsTr("Ozone sensors");
|
||||
case "uncategorized":
|
||||
return qsTr("Uncategorized")
|
||||
default:
|
||||
@ -318,7 +322,7 @@ ApplicationWindow {
|
||||
case "presencesensor":
|
||||
return Qt.resolvedUrl("images/sensors/presence.svg")
|
||||
case "closablesensor":
|
||||
return Qt.resolvedUrl("images/sensors/closable.svg")
|
||||
return Qt.resolvedUrl("images/sensors/window-closed.svg")
|
||||
case "windspeedsensor":
|
||||
return Qt.resolvedUrl("images/sensors/windspeed.svg")
|
||||
case "watersensor":
|
||||
|
||||
@ -20,6 +20,7 @@ Item {
|
||||
property int cornerRadius: 10
|
||||
property int smallCornerRadius: 6
|
||||
|
||||
readonly property int extraSmallMargins: 4
|
||||
readonly property int smallMargins: 8
|
||||
readonly property int margins: 16
|
||||
readonly property int bigMargins: 32
|
||||
@ -89,8 +90,8 @@ Item {
|
||||
// Icon/graph colors for various interfaces
|
||||
property var interfaceColors: {
|
||||
"temperaturesensor": red,
|
||||
"humiditysensor": blue,
|
||||
"moisturesensor": lightBlue,
|
||||
"humiditysensor": lightBlue,
|
||||
"moisturesensor": blue,
|
||||
"lightsensor": yellow,
|
||||
"conductivitysensor": green,
|
||||
"pressuresensor": gray,
|
||||
@ -108,7 +109,7 @@ Item {
|
||||
"smartmeterproducer": lime,
|
||||
"energymeter": darkBlue,
|
||||
"heating" : red,
|
||||
"cooling": lightBlue,
|
||||
"cooling": blue,
|
||||
"thermostat": blue,
|
||||
"irrigation": blue,
|
||||
"windspeedsensor": blue,
|
||||
|
||||
@ -490,7 +490,7 @@ SettingsPageBase {
|
||||
popup.open()
|
||||
return;
|
||||
}
|
||||
var dialog = Qt.createComponent(Qt.resolvedUrl("../components/MeaDialog.qml"));
|
||||
var dialog = Qt.createComponent(Qt.resolvedUrl("../components/NymeaDialog.qml"));
|
||||
var popup = dialog.createObject(app, {headerIcon: "../images/tick.svg", title: qsTr("Yay!"), text: qsTr("Your password has been reset.")})
|
||||
popup.accepted.connect(function() {
|
||||
pageStack.pop(root);
|
||||
@ -556,7 +556,7 @@ SettingsPageBase {
|
||||
|
||||
Component {
|
||||
id : logoutDialogComponent
|
||||
MeaDialog {
|
||||
NymeaDialog {
|
||||
id: logoutDialog
|
||||
title: qsTr("Goodbye")
|
||||
text: qsTr("Sorry to see you go. If you log out you won't be able to connect to %1 systems remotely any more. However, you can come back any time, we'll keep your user account. If you whish to completely delete your account and all the data associated with it, check the box below before hitting ok. If you decide to delete your account, all your personal information will be removed from %1:cloud and cannot be restored.").arg(Configuration.systemName)
|
||||
|
||||
@ -23,7 +23,7 @@ BigTile {
|
||||
id: headerRow
|
||||
visible: root.showHeader
|
||||
width: parent.width
|
||||
Layout.margins: app.margins / 2
|
||||
Layout.margins: Style.margins / 2
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: root.thing.name
|
||||
|
||||
@ -34,7 +34,7 @@ import QtQuick.Layouts 1.1
|
||||
import Nymea 1.0
|
||||
import "../delegates"
|
||||
|
||||
MeaDialog {
|
||||
NymeaDialog {
|
||||
id: root
|
||||
|
||||
property Thing thing
|
||||
|
||||
@ -88,6 +88,7 @@ RowLayout {
|
||||
// return qsTr("%1 installed").arg(thingsProxy.count)
|
||||
}
|
||||
console.warn("InterfaceTile, inlineButtonControl 1: Unhandled interface", model.name)
|
||||
return ""
|
||||
}
|
||||
font.pixelSize: app.smallFont
|
||||
elide: Text.ElideRight
|
||||
|
||||
@ -44,11 +44,6 @@ Item {
|
||||
property bool on: false
|
||||
property alias showOnGradient: opacityMask.visible
|
||||
|
||||
property bool showProgress: false
|
||||
property double progressFrom: 0
|
||||
property double progressTo: 100
|
||||
property double progress: 50
|
||||
|
||||
readonly property Item contentItem: background
|
||||
|
||||
signal clicked()
|
||||
@ -78,7 +73,7 @@ Item {
|
||||
ColorIcon {
|
||||
id: icon
|
||||
anchors.centerIn: background
|
||||
size: Style.hugeIconSize
|
||||
size: Math.min(Style.hugeIconSize, background.width * 0.4)
|
||||
color: root.on ? root.onColor : Style.iconColor
|
||||
Behavior on color { ColorAnimation { duration: Style.animationDuration } }
|
||||
}
|
||||
@ -102,9 +97,4 @@ Item {
|
||||
Behavior on opacity { NumberAnimation { duration: Style.animationDuration } }
|
||||
}
|
||||
|
||||
Item {
|
||||
id: contentContainer
|
||||
anchors.fill: background
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -162,7 +162,7 @@ Dialog {
|
||||
|
||||
Component {
|
||||
id: addManualConnectionComponent
|
||||
MeaDialog {
|
||||
NymeaDialog {
|
||||
id: addManualConnectionDialog
|
||||
standardButtons: Dialog.Ok | Dialog.Cancel
|
||||
property alias rpcUrl: manualEntry.rpcUrl
|
||||
|
||||
@ -37,49 +37,51 @@ import "../utils"
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property Thing thing: null
|
||||
property string stateName: ""
|
||||
property StateType stateType: thing ? thing.thingClass.stateTypes.findByName(stateName) : null
|
||||
property double minValue: 0
|
||||
property double maxValue: 100
|
||||
property double precision: 1
|
||||
property double value: 50
|
||||
property double activeValue: minValue
|
||||
readonly property alias pendingValue: d.pendingValue
|
||||
|
||||
property color color: Style.accentColor
|
||||
property bool on: true
|
||||
property int precision: 1
|
||||
|
||||
readonly property State progressState: thing ? thing.states.getState(stateType.id) : null
|
||||
readonly property State powerState: thing ? thing.stateByName("power") : null
|
||||
|
||||
property int startAngle: 135
|
||||
property int maxAngle: 270
|
||||
readonly property int steps: canvas.roundToPrecision(root.progressState.maxValue - root.progressState.minValue) / root.precision + 1
|
||||
readonly property double stepSize: (root.progressState.maxValue - root.progressState.minValue) / steps
|
||||
readonly property double anglePerStep: maxAngle / steps
|
||||
readonly property int steps: canvas.roundToPrecision(maxValue - minValue) / root.precision + 1
|
||||
readonly property double stepSize: (maxValue - minValue) / (steps - 1)
|
||||
readonly property double anglePerStep: maxAngle / (steps - 1)
|
||||
|
||||
signal pressed()
|
||||
signal released()
|
||||
signal clicked()
|
||||
signal moved(double value)
|
||||
|
||||
ActionQueue {
|
||||
id: actionQueue
|
||||
thing: root.thing
|
||||
stateType: root.stateType
|
||||
QtObject {
|
||||
id: d
|
||||
property double pendingValue: root.value
|
||||
onPendingValueChanged: canvas.requestPaint()
|
||||
}
|
||||
|
||||
ActionQueue {
|
||||
id: powerActionQueue
|
||||
thing: root.thing
|
||||
stateName: "power"
|
||||
property bool pending: false
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root.progressState
|
||||
onValueChanged: {
|
||||
canvas.requestPaint()
|
||||
onValueChanged: {
|
||||
if (d.pending && value == d.pendingValue) {
|
||||
d.pending = false
|
||||
}
|
||||
}
|
||||
|
||||
Binding {
|
||||
target: d
|
||||
property: "pendingValue"
|
||||
value: root.value
|
||||
when: !d.pending
|
||||
}
|
||||
|
||||
Canvas {
|
||||
id: canvas
|
||||
anchors.centerIn: root
|
||||
width: Math.min(root.width, root.height)
|
||||
anchors.centerIn: root
|
||||
width: Math.min(400, Math.min(root.width, root.height))
|
||||
height: width
|
||||
|
||||
property color effectColor: root.on ? root.color : Style.iconColor
|
||||
@ -98,23 +100,25 @@ Item {
|
||||
var center = { x: canvas.width / 2, y: canvas.height / 2 };
|
||||
|
||||
// Step lines
|
||||
var currentValue = actionQueue.pendingValue || root.progressState.value
|
||||
var currentStep;
|
||||
if (root.progressState) {
|
||||
currentStep = roundToPrecision(currentValue - root.progressState.minValue) / root.precision
|
||||
}
|
||||
|
||||
// print("* current step", currentStep, root.steps, currentValue)
|
||||
var currentValue = d.pendingValue
|
||||
var currentStep = roundToPrecision(currentValue - minValue) / root.precision
|
||||
var activeStep = roundToPrecision(root.activeValue - minValue) / root.precision
|
||||
|
||||
for(var step = 0; step < steps; step += root.precision) {
|
||||
var angle = step * anglePerStep + startAngle;
|
||||
var innerRadius = canvas.width * 0.4
|
||||
var outerRadius = canvas.width * 0.5
|
||||
|
||||
if (step <= currentStep) {
|
||||
if (step == currentStep) {
|
||||
ctx.strokeStyle = canvas.effectColor
|
||||
innerRadius = canvas.width * 0.38
|
||||
ctx.lineWidth = 4;
|
||||
} else if (step < currentStep && step >= activeStep) {
|
||||
ctx.strokeStyle = canvas.effectColor
|
||||
ctx.lineWidth = 2;
|
||||
} else if (step > currentStep && step <= activeStep) {
|
||||
ctx.strokeStyle = canvas.effectColor
|
||||
ctx.lineWidth = 2;
|
||||
} else {
|
||||
ctx.strokeStyle = Style.tileOverlayColor;
|
||||
ctx.lineWidth = 1;
|
||||
@ -142,6 +146,7 @@ Item {
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: canvas
|
||||
preventStealing: dragging
|
||||
|
||||
property bool dragging: false
|
||||
property double lastAngle
|
||||
@ -150,12 +155,14 @@ Item {
|
||||
onPressed: {
|
||||
angleDiff = 0
|
||||
lastAngle = calculateAngle(mouseX, mouseY)
|
||||
root.pressed()
|
||||
}
|
||||
|
||||
onReleased: {
|
||||
if (!dragging && root.powerState) {
|
||||
root.released()
|
||||
if (!dragging) {
|
||||
PlatformHelper.vibrate(PlatformHelper.HapticsFeedbackSelection)
|
||||
powerActionQueue.sendValue(!root.powerState.value)
|
||||
root.clicked()
|
||||
}
|
||||
dragging = false
|
||||
}
|
||||
@ -180,11 +187,13 @@ Item {
|
||||
var valueDiff = angleDiff / root.anglePerStep * root.stepSize
|
||||
valueDiff = canvas.roundToPrecision(valueDiff)
|
||||
if (Math.abs(valueDiff) > 0) {
|
||||
var currentValue = actionQueue.pendingValue || root.progressState.value
|
||||
var currentValue = d.pendingValue
|
||||
var newValue = currentValue + valueDiff
|
||||
newValue = Math.min(root.progressState.maxValue, Math.max(root.progressState.minValue, newValue))
|
||||
newValue = Math.min(root.maxValue, Math.max(root.minValue, newValue))
|
||||
if (currentValue !== newValue) {
|
||||
actionQueue.sendValue(newValue)
|
||||
d.pendingValue = newValue;
|
||||
d.pending = true;
|
||||
root.moved(newValue)
|
||||
}
|
||||
var steps = Math.round(valueDiff / root.stepSize)
|
||||
angleDiff -= steps * root.anglePerStep
|
||||
|
||||
@ -32,7 +32,7 @@ import QtQuick 2.8
|
||||
import QtQuick.Controls 2.1
|
||||
import QtQuick.Layouts 1.2
|
||||
|
||||
MeaDialog {
|
||||
NymeaDialog {
|
||||
id: root
|
||||
|
||||
title: qsTr("Oh snap!")
|
||||
|
||||
@ -66,7 +66,7 @@ Item {
|
||||
clickCounter++;
|
||||
if (clickCounter >= 10) {
|
||||
settings.showHiddenOptions = !settings.showHiddenOptions
|
||||
var dialog = Qt.createComponent(Qt.resolvedUrl("../components/MeaDialog.qml"));
|
||||
var dialog = Qt.createComponent(Qt.resolvedUrl("../components/NymeaDialog.qml"));
|
||||
var text = settings.showHiddenOptions
|
||||
? qsTr("Developer options are now enabled. If you have found this by accident, it is most likely not of any use for you. It will just enable some nerdy developer gibberish in the app. Tap the icon another 10 times to disable it again.")
|
||||
: qsTr("Developer options are now disabled.")
|
||||
|
||||
@ -54,10 +54,6 @@ Item {
|
||||
print("handleEvent not implemented in", title)
|
||||
}
|
||||
|
||||
Background {
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
// Prevent scroll events to swipe left/right in case they fall through the grid
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
|
||||
@ -477,7 +477,7 @@ Item {
|
||||
|
||||
Component {
|
||||
id: inputSourceSelectDialogComponent
|
||||
MeaDialog {
|
||||
NymeaDialog {
|
||||
id: inputSourceSelectDialog
|
||||
headerIcon: "../images/state-in.svg"
|
||||
title: qsTr("Select input")
|
||||
@ -505,7 +505,7 @@ Item {
|
||||
|
||||
Component {
|
||||
id: equalizerComponent
|
||||
MeaDialog {
|
||||
NymeaDialog {
|
||||
id: equalizer
|
||||
headerIcon: "../images/media/equalizer.svg"
|
||||
title: qsTr("Equalizer preset")
|
||||
@ -531,7 +531,7 @@ Item {
|
||||
}
|
||||
Component {
|
||||
id: ambeoModeDialogComponent
|
||||
MeaDialog {
|
||||
NymeaDialog {
|
||||
id: ambeoModeDialog
|
||||
standardButtons: Dialog.NoButton
|
||||
ColorIcon {
|
||||
|
||||
@ -28,7 +28,7 @@ RowLayout {
|
||||
tmp = parseInt(root.value)
|
||||
}
|
||||
if (tmp != NaN){
|
||||
root.value = tmp - 1
|
||||
root.value = Math.max(root.from, tmp - 1)
|
||||
root.valueModified(root.value)
|
||||
}
|
||||
}
|
||||
@ -71,7 +71,7 @@ RowLayout {
|
||||
tmp = parseInt(root.value)
|
||||
}
|
||||
if (tmp != NaN){
|
||||
root.value = tmp + 1
|
||||
root.value = Math.min(root.to, tmp + 1)
|
||||
root.valueModified(root.value)
|
||||
}
|
||||
}
|
||||
|
||||
211
nymea-app/ui/components/StateDial.qml
Normal file
211
nymea-app/ui/components/StateDial.qml
Normal file
@ -0,0 +1,211 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2022, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
* This project including source code and documentation is protected by
|
||||
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||||
* reproduction, publication, editing and translation, are reserved. The use of
|
||||
* this project is subject to the terms of a license agreement to be concluded
|
||||
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||
* under https://nymea.io/license
|
||||
*
|
||||
* GNU General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under the
|
||||
* terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, GNU version 3. This project is distributed in the hope that it
|
||||
* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this project. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* For any further details and any questions please contact us under
|
||||
* contact@nymea.io or see our FAQ/Licensing Information on
|
||||
* https://nymea.io/license/faq
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 2.2
|
||||
import Nymea 1.0
|
||||
import QtQuick.Layouts 1.2
|
||||
import "../utils"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property Thing thing: null
|
||||
property string stateName: ""
|
||||
property StateType stateType: thing ? thing.thingClass.stateTypes.findByName(stateName) : null
|
||||
|
||||
property color color: Style.accentColor
|
||||
property bool on: true
|
||||
property int precision: 1
|
||||
|
||||
readonly property State progressState: thing ? thing.states.getState(stateType.id) : null
|
||||
readonly property State powerState: thing ? thing.stateByName("power") : null
|
||||
|
||||
property int startAngle: 135
|
||||
property int maxAngle: 270
|
||||
readonly property int steps: canvas.roundToPrecision(root.progressState.maxValue - root.progressState.minValue) / root.precision + 1
|
||||
readonly property double stepSize: (root.progressState.maxValue - root.progressState.minValue) / steps
|
||||
readonly property double anglePerStep: maxAngle / steps
|
||||
|
||||
|
||||
ActionQueue {
|
||||
id: actionQueue
|
||||
thing: root.thing
|
||||
stateType: root.stateType
|
||||
onPendingValueChanged: canvas.requestPaint()
|
||||
}
|
||||
|
||||
ActionQueue {
|
||||
id: powerActionQueue
|
||||
thing: root.thing
|
||||
stateName: "power"
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root.progressState
|
||||
onValueChanged: {
|
||||
canvas.requestPaint()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Canvas {
|
||||
id: canvas
|
||||
anchors.centerIn: root
|
||||
width: Math.min(root.width, root.height)
|
||||
height: width
|
||||
|
||||
property color effectColor: root.on ? root.color : Style.iconColor
|
||||
Behavior on effectColor { ColorAnimation { duration: Style.animationDuration } }
|
||||
onEffectColorChanged: requestPaint()
|
||||
|
||||
function roundToPrecision(value) {
|
||||
var tmp = Math.round(value / root.precision) * root.precision;
|
||||
return tmp;
|
||||
}
|
||||
|
||||
onPaint: {
|
||||
var ctx = getContext("2d");
|
||||
ctx.reset();
|
||||
|
||||
var center = { x: canvas.width / 2, y: canvas.height / 2 };
|
||||
|
||||
// Step lines
|
||||
var currentValue = actionQueue.pendingValue || root.progressState.value
|
||||
var currentStep;
|
||||
if (root.progressState) {
|
||||
currentStep = roundToPrecision(currentValue - root.progressState.minValue) / root.precision
|
||||
}
|
||||
|
||||
// print("* current step", currentStep, root.steps, currentValue)
|
||||
|
||||
for(var step = 0; step < steps; step += root.precision) {
|
||||
var angle = step * anglePerStep + startAngle;
|
||||
var innerRadius = canvas.width * 0.4
|
||||
var outerRadius = canvas.width * 0.5
|
||||
|
||||
if (step <= currentStep) {
|
||||
ctx.strokeStyle = canvas.effectColor
|
||||
innerRadius = canvas.width * 0.38
|
||||
ctx.lineWidth = 4;
|
||||
} else {
|
||||
ctx.strokeStyle = Style.tileOverlayColor;
|
||||
ctx.lineWidth = 1;
|
||||
}
|
||||
|
||||
ctx.beginPath();
|
||||
// rotate
|
||||
//convert to radians
|
||||
var rad = angle * Math.PI/180;
|
||||
var c = Math.cos(rad);
|
||||
var s = Math.sin(rad);
|
||||
var innerPointX = center.x + (innerRadius * c);
|
||||
var innerPointY = center.y + (innerRadius * s);
|
||||
var outerPointX = center.x + (outerRadius * c);
|
||||
var outerPointY = center.x + (outerRadius * s);
|
||||
|
||||
ctx.moveTo(innerPointX, innerPointY);
|
||||
ctx.lineTo(outerPointX, outerPointY);
|
||||
ctx.stroke();
|
||||
ctx.closePath();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: canvas
|
||||
|
||||
property bool dragging: false
|
||||
property double lastAngle
|
||||
property double angleDiff: 0
|
||||
|
||||
onPressed: {
|
||||
angleDiff = 0
|
||||
lastAngle = calculateAngle(mouseX, mouseY)
|
||||
}
|
||||
|
||||
onReleased: {
|
||||
if (!dragging && root.powerState) {
|
||||
PlatformHelper.vibrate(PlatformHelper.HapticsFeedbackSelection)
|
||||
powerActionQueue.sendValue(!root.powerState.value)
|
||||
}
|
||||
dragging = false
|
||||
}
|
||||
|
||||
onPositionChanged: {
|
||||
var angle = calculateAngle(mouseX, mouseY)
|
||||
var tmpDiff = angle - lastAngle
|
||||
if (tmpDiff > 300) {
|
||||
tmpDiff -= 360
|
||||
}
|
||||
if (tmpDiff < -300) {
|
||||
tmpDiff += 360
|
||||
}
|
||||
|
||||
lastAngle = angle;
|
||||
|
||||
angleDiff += tmpDiff
|
||||
if (Math.abs(angleDiff) > 1) {
|
||||
dragging = true
|
||||
}
|
||||
|
||||
var valueDiff = angleDiff / root.anglePerStep * root.stepSize
|
||||
valueDiff = canvas.roundToPrecision(valueDiff)
|
||||
if (Math.abs(valueDiff) > 0) {
|
||||
var currentValue = actionQueue.pendingValue || root.progressState.value
|
||||
var newValue = currentValue + valueDiff
|
||||
newValue = Math.min(root.progressState.maxValue, Math.max(root.progressState.minValue, newValue))
|
||||
print("newValue", newValue)
|
||||
if (currentValue !== newValue) {
|
||||
actionQueue.sendValue(newValue)
|
||||
}
|
||||
var steps = Math.round(valueDiff / root.stepSize)
|
||||
angleDiff -= steps * root.anglePerStep
|
||||
}
|
||||
}
|
||||
|
||||
function calculateAngle(mouseX, mouseY) {
|
||||
// transform coords to center of dial
|
||||
mouseX -= canvas.width / 2
|
||||
mouseY -= canvas.height / 2
|
||||
|
||||
var rad = Math.atan(mouseY / mouseX);
|
||||
var angle = rad * 180 / Math.PI
|
||||
|
||||
angle += 90;
|
||||
|
||||
if (mouseX < 0 && mouseY >= 0) angle = 180 + angle;
|
||||
if (mouseX < 0 && mouseY < 0) angle = 180 + angle;
|
||||
|
||||
return angle;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -99,7 +99,7 @@ AutoSizeMenu {
|
||||
|
||||
Component {
|
||||
id: addToGroupDialog
|
||||
MeaDialog {
|
||||
NymeaDialog {
|
||||
title: qsTr("Groups for %1").arg(root.thing.name)
|
||||
headerIcon: "../images/groups.svg"
|
||||
// NOTE: If CloseOnPressOutside is active (default) it will break the QtVirtualKeyboard
|
||||
|
||||
@ -28,7 +28,7 @@ RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: -app.margins / 4
|
||||
onClicked: {
|
||||
var dialogComponent = Qt.createComponent("MeaDialog.qml")
|
||||
var dialogComponent = Qt.createComponent("NymeaDialog.qml")
|
||||
var currentVersionState = root.thing.stateByName("currentVersion")
|
||||
var availableVersionState = root.thing.stateByName("availableVersion")
|
||||
var text = qsTr("An update for %1 is available. Do you want to start the update now?").arg(root.thing.name)
|
||||
|
||||
@ -35,7 +35,7 @@ import QtQuick.Layouts 1.3
|
||||
import Nymea 1.0
|
||||
import "../components"
|
||||
|
||||
MeaDialog {
|
||||
NymeaDialog {
|
||||
id: root
|
||||
|
||||
title: qsTr("Insecure connection")
|
||||
|
||||
438
nymea-app/ui/customviews/SensorView.qml
Normal file
438
nymea-app/ui/customviews/SensorView.qml
Normal file
@ -0,0 +1,438 @@
|
||||
import QtQuick 2.3
|
||||
import QtQuick.Layouts 1.1
|
||||
import QtQuick.Controls 2.3
|
||||
import Nymea 1.0
|
||||
import NymeaApp.Utils 1.0
|
||||
import "qrc:/ui/components"
|
||||
import QtGraphicalEffects 1.0
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property Thing thing: null
|
||||
property string interfaceName: ""
|
||||
|
||||
property var interfaceStateMap: {
|
||||
"temperaturesensor": "temperature",
|
||||
"humiditysensor": "humidity",
|
||||
"pressuresensor": "pressure",
|
||||
"moisturesensor": "moisture",
|
||||
"lightsensor": "lightIntensity",
|
||||
"conductivitysensor": "conductivity",
|
||||
"noisesensor": "noise",
|
||||
"cosensor": "co",
|
||||
"co2sensor": "co2",
|
||||
"gassensor": "gasLevel",
|
||||
"presencesensor": "isPresent",
|
||||
"daylightsensor": "daylight",
|
||||
"closablesensor": "closed",
|
||||
"watersensor": "waterDetected",
|
||||
"firesensor": "fireDetected",
|
||||
"waterlevelsensor": "waterLevel",
|
||||
"phsensor": "ph",
|
||||
"o2sensor": "o2saturation",
|
||||
"o3sensor": "o3",
|
||||
"orpsensor": "orp",
|
||||
"vocsensor": "voc",
|
||||
"cosensor": "co",
|
||||
"pm10sensor": "pm10",
|
||||
"pm25sensor": "pm25",
|
||||
"no2sensor": "no2"
|
||||
}
|
||||
|
||||
CircleBackground {
|
||||
id: background
|
||||
anchors.centerIn: parent
|
||||
width: Math.min(parent.width, parent.height) - Style.margins
|
||||
height: width
|
||||
|
||||
readonly property StateType sensorStateType: root.thing ? root.thing.thingClass.stateTypes.findByName(interfaceStateMap[root.interfaceName]) : null
|
||||
readonly property State sensorState: root.thing ? root.thing.stateByName(interfaceStateMap[interfaceName]) : null
|
||||
|
||||
onColor: {
|
||||
if (root.interfaceName == "closablesensor") {
|
||||
return sensorState.value === true ? Style.green : Style.red
|
||||
}
|
||||
return app.interfaceToColor(root.interfaceName)
|
||||
}
|
||||
on: {
|
||||
if (root.interfaceName == "closablesensor") {
|
||||
return true
|
||||
}
|
||||
return sensorStateType && sensorStateType.type.toLowerCase() == "bool" && sensorState.value === true
|
||||
}
|
||||
iconSource: {
|
||||
if (root.interfaceName == "closablesensor") {
|
||||
return sensorState.value === true ? "sensors/window-closed" : "sensors/window-open"
|
||||
}
|
||||
var map = [
|
||||
"presencesensor",
|
||||
"daylightsensor",
|
||||
"firesensor",
|
||||
"watersensor"
|
||||
]
|
||||
return map.indexOf(root.interfaceName) >= 0 ? app.interfaceToIcon(root.interfaceName) : ""
|
||||
}
|
||||
|
||||
Loader {
|
||||
anchors.centerIn: parent
|
||||
width: background.contentItem.width
|
||||
height: background.contentItem.height
|
||||
property StateType stateType: root.thing.thingClass.stateTypes.findByName(interfaceStateMap[root.interfaceName])
|
||||
property State state: root.thing.stateByName(interfaceStateMap[root.interfaceName])
|
||||
property string interfaceName: root.interfaceName
|
||||
property var minValue: {
|
||||
if (["temperaturesensor"].indexOf(root.interfaceName) >= 0) {
|
||||
return Types.toUiValue(-50, Types.UnitDegreeCelsius)
|
||||
}
|
||||
if (["pressuresensor"].indexOf(root.interfaceName) >= 0) {
|
||||
return Types.toUiValue(state.minValue, Types.UnitMilliBar)
|
||||
}
|
||||
|
||||
return state.minValue
|
||||
}
|
||||
property var maxValue: {
|
||||
if (["temperaturesensor"].indexOf(root.interfaceName) >= 0) {
|
||||
return Types.toUiValue(50, Types.UnitDegreeCelsius)
|
||||
}
|
||||
if (["pressuresensor"].indexOf(root.interfaceName) >= 0) {
|
||||
return Types.toUiValue(state.maxValue, Types.UnitMilliBar)
|
||||
}
|
||||
return state.maxValue
|
||||
}
|
||||
|
||||
sourceComponent: {
|
||||
if (stateType.type.toLowerCase() == "bool") {
|
||||
return boolComponent;
|
||||
}
|
||||
|
||||
var progressInterfaces = [
|
||||
"humiditysensor",
|
||||
"o2sensor",
|
||||
"temperaturesensor",
|
||||
"moisturesensor",
|
||||
"conductivitysensor",
|
||||
"gassensor",
|
||||
"lightsensor",
|
||||
"orpsensor",
|
||||
"co2sensor",
|
||||
"phsensor",
|
||||
"pressuresensor",
|
||||
"waterlevelsensor",
|
||||
"windspeedsensor"
|
||||
]
|
||||
if (progressInterfaces.indexOf(root.interfaceName) >= 0) {
|
||||
return progressComponent
|
||||
}
|
||||
var scaleInterfaces = [
|
||||
"vocsensor",
|
||||
"cosensor",
|
||||
"o3sensor",
|
||||
"pm10sensor",
|
||||
"pm25sensor",
|
||||
"no2sensor"
|
||||
]
|
||||
if (scaleInterfaces.indexOf(root.interfaceName) >= 0) {
|
||||
return scaleComponent
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: boolComponent
|
||||
Rectangle {
|
||||
property State state: parent.state
|
||||
|
||||
radius: width / 2
|
||||
color: "transparent"
|
||||
border.color: {
|
||||
if (root.interfaceName == "closablesensor") {
|
||||
return state.value === true ? Style.green : Style.red
|
||||
}
|
||||
return app.interfaceToColor(root.interfaceName)
|
||||
}
|
||||
border.width: width * .1
|
||||
visible: {
|
||||
if (root.interfaceName == "closablesensor") {
|
||||
return true
|
||||
}
|
||||
|
||||
return state.value === true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: progressComponent
|
||||
Canvas {
|
||||
id: progressCanvas
|
||||
property string interfaceName: parent.interfaceName
|
||||
property StateType stateType: parent.stateType
|
||||
property State state: parent.state
|
||||
property var minValue: parent.minValue
|
||||
property var maxValue: parent.maxValue
|
||||
|
||||
property double progress: (progressCanvas.state.value - progressCanvas.minValue) / (progressCanvas.maxValue - progressCanvas.minValue)
|
||||
Behavior on progress { NumberAnimation { duration: Style.slowAnimationDuration; easing.type: Easing.InOutQuad } }
|
||||
onProgressChanged: requestPaint();
|
||||
|
||||
ColumnLayout {
|
||||
anchors.centerIn: parent
|
||||
anchors.verticalCenterOffset: -Style.smallMargins
|
||||
width: parent.width * 0.6
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: Types.toUiValue(progressCanvas.state.value, progressCanvas.stateType.unit).toFixed(1)
|
||||
wrapMode: Text.WordWrap
|
||||
font.pixelSize: Math.min(Style.hugeFont.pixelSize, progressCanvas.height / 8)
|
||||
maximumLineCount: 2
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: Types.toUiUnit(progressCanvas.stateType.unit)
|
||||
font.pixelSize: Math.min(Style.largeFont.pixelSize, progressCanvas.height / 12)
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
onPaint: {
|
||||
var ctx = getContext("2d");
|
||||
ctx.reset();
|
||||
|
||||
ctx.beginPath()
|
||||
ctx.fillStyle = Style.foregroundColor
|
||||
|
||||
ctx.translate(width / 2, height / 2)
|
||||
ctx.rotate(135 * Math.PI / 180)
|
||||
|
||||
ctx.lineCap = "round"
|
||||
ctx.lineWidth = width * .1
|
||||
|
||||
ctx.beginPath()
|
||||
ctx.strokeStyle = Style.tileOverlayColor
|
||||
var startAngle = 0
|
||||
var endAngle = 270
|
||||
var radStart = startAngle * Math.PI/180;
|
||||
var radEnd = endAngle * Math.PI/180;
|
||||
ctx.arc(0, 0, width / 2 - ctx.lineWidth / 2, radStart, radEnd)
|
||||
ctx.stroke()
|
||||
ctx.closePath()
|
||||
|
||||
ctx.beginPath()
|
||||
ctx.strokeStyle = app.interfaceToColor(progressCanvas.interfaceName)
|
||||
radEnd *= progressCanvas.progress
|
||||
ctx.arc(0, 0, width / 2 - ctx.lineWidth / 2, radStart, radEnd)
|
||||
ctx.stroke()
|
||||
ctx.closePath()
|
||||
}
|
||||
|
||||
ColorIcon {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: Style.smallMargins
|
||||
name: app.interfaceToIcon(progressCanvas.interfaceName)
|
||||
size: Math.min(Style.bigIconSize, parent.height / 5)
|
||||
color: app.interfaceToColor(progressCanvas.interfaceName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Component {
|
||||
id: scaleComponent
|
||||
Item {
|
||||
id: scaleCanvas
|
||||
property string interfaceName: parent.interfaceName
|
||||
property StateType stateType: parent.stateType
|
||||
property State state: parent.state
|
||||
property var minValue: parent.minValue
|
||||
property var maxValue: parent.maxValue
|
||||
|
||||
property int scaleWidth: width * .1
|
||||
|
||||
property var scale: {
|
||||
switch (interfaceName) {
|
||||
case "vocsensor":
|
||||
return AirQualityIndex.iaqVoc
|
||||
case "cosensor":
|
||||
return AirQualityIndex.caqiCo
|
||||
case "o3sensor":
|
||||
return AirQualityIndex.caqiO3
|
||||
case "pm10sensor":
|
||||
return AirQualityIndex.caqiPm10
|
||||
case "pm25sensor":
|
||||
return AirQualityIndex.caqiPm25
|
||||
case "no2sensor":
|
||||
return AirQualityIndex.caqiNo2
|
||||
}
|
||||
return baseScale
|
||||
}
|
||||
property var baseScale: [
|
||||
{
|
||||
"value": maxValue,
|
||||
"angle": 270,
|
||||
"color": Style.tileOverlayColor
|
||||
}
|
||||
]
|
||||
|
||||
property var currentIndex: {
|
||||
for (var i = 0; i < scale.length; i++) {
|
||||
if (state.value <= scale[i].value) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
log.warn("Value out of scale!")
|
||||
return -1
|
||||
}
|
||||
|
||||
property double angle: {
|
||||
var baseAngle = 0
|
||||
var baseValue = 0;
|
||||
if (currentIndex > 0) {
|
||||
baseAngle = scale[currentIndex-1].angle
|
||||
baseValue = scale[currentIndex-1].value
|
||||
}
|
||||
var valueRange = scale[currentIndex].value - baseValue
|
||||
var angleRange = scale[currentIndex].angle - baseAngle
|
||||
var progress = (state.value - baseValue) / (scale[currentIndex].value - baseValue)
|
||||
return baseAngle + angleRange * progress
|
||||
}
|
||||
Behavior on angle { NumberAnimation { duration: Style.slowAnimationDuration; easing.type: Easing.InOutQuad } }
|
||||
onAngleChanged: maskCanvas.requestPaint();
|
||||
|
||||
Canvas {
|
||||
id: baseCanvas
|
||||
anchors.fill: parent
|
||||
visible: false
|
||||
onPaint: {
|
||||
var ctx = getContext("2d");
|
||||
ctx.reset();
|
||||
|
||||
ctx.beginPath()
|
||||
ctx.fillStyle = Style.foregroundColor
|
||||
|
||||
ctx.translate(width / 2, height / 2)
|
||||
ctx.rotate(135 * Math.PI / 180)
|
||||
|
||||
ctx.lineCap = "round"
|
||||
ctx.lineWidth = scaleCanvas.scaleWidth
|
||||
|
||||
// paint first rounded
|
||||
ctx.beginPath()
|
||||
ctx.strokeStyle = scaleCanvas.scale[0].color
|
||||
var startAngle = 0
|
||||
var endAngle = scaleCanvas.scale[0].angle * Math.PI/180
|
||||
ctx.arc(0, 0, width / 2 - ctx.lineWidth / 2, startAngle, endAngle)
|
||||
ctx.stroke()
|
||||
ctx.closePath()
|
||||
|
||||
// paint last rounded
|
||||
ctx.beginPath()
|
||||
ctx.strokeStyle = scaleCanvas.scale[scaleCanvas.scale.length - 1].color
|
||||
startAngle = scaleCanvas.scale[scaleCanvas.scale.length - 2].angle * Math.PI/180
|
||||
endAngle = scaleCanvas.scale[scaleCanvas.scale.length - 1].angle * Math.PI/180
|
||||
ctx.arc(0, 0, width / 2 - ctx.lineWidth / 2, startAngle, endAngle)
|
||||
ctx.stroke()
|
||||
ctx.closePath()
|
||||
|
||||
// paint inner parts
|
||||
ctx.lineCap = "butt"
|
||||
for (var i = 1; i < scaleCanvas.scale.length - 1; i++) {
|
||||
ctx.beginPath()
|
||||
ctx.strokeStyle = scaleCanvas.scale[i].color
|
||||
startAngle = scaleCanvas.scale[i - 1].angle * Math.PI/180
|
||||
endAngle = scaleCanvas.scale[i].angle * Math.PI/180
|
||||
ctx.arc(0, 0, width / 2 - ctx.lineWidth / 2, startAngle, endAngle)
|
||||
ctx.stroke()
|
||||
ctx.closePath()
|
||||
}
|
||||
}
|
||||
}
|
||||
Canvas {
|
||||
id: maskCanvas
|
||||
anchors.fill: parent
|
||||
visible: false
|
||||
onPaint: {
|
||||
var ctx = getContext("2d");
|
||||
ctx.reset();
|
||||
|
||||
ctx.beginPath()
|
||||
ctx.fillStyle = Style.foregroundColor
|
||||
|
||||
ctx.translate(width / 2, height / 2)
|
||||
ctx.rotate(135 * Math.PI / 180)
|
||||
|
||||
ctx.lineCap = "round"
|
||||
ctx.lineWidth = width * .1
|
||||
|
||||
ctx.beginPath()
|
||||
ctx.strokeStyle = "#55ffffff"
|
||||
var startAngle = 0
|
||||
var endAngle = 270
|
||||
var radStart = startAngle * Math.PI/180;
|
||||
var radEnd = endAngle * Math.PI/180;
|
||||
ctx.arc(0, 0, width / 2 - ctx.lineWidth / 2, radStart, radEnd)
|
||||
ctx.stroke()
|
||||
ctx.closePath()
|
||||
|
||||
ctx.beginPath()
|
||||
ctx.strokeStyle = "#000000"
|
||||
radEnd = scaleCanvas.angle * Math.PI/180
|
||||
ctx.arc(0, 0, width / 2 - ctx.lineWidth / 2, radStart, radEnd)
|
||||
ctx.stroke()
|
||||
ctx.closePath()
|
||||
}
|
||||
}
|
||||
|
||||
OpacityMask {
|
||||
anchors.fill: parent
|
||||
source: baseCanvas
|
||||
maskSource: maskCanvas
|
||||
}
|
||||
|
||||
|
||||
ColumnLayout {
|
||||
anchors.centerIn: parent
|
||||
anchors.verticalCenterOffset: -Style.smallMargins
|
||||
width: parent.width * 0.6
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: scaleCanvas.scale[scaleCanvas.currentIndex].text
|
||||
font.pixelSize: Math.min(Style.hugeFont.pixelSize, scaleCanvas.height / 8)
|
||||
wrapMode: Text.WordWrap
|
||||
// color: scaleCanvas.scale[scaleCanvas.currentIndex].color
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 2
|
||||
}
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: Types.toUiValue(scaleCanvas.state.value, scaleCanvas.stateType.unit).toFixed(1) + " " + Types.toUiUnit(scaleCanvas.stateType.unit)
|
||||
font.pixelSize: Math.min(Style.largeFont.pixelSize, scaleCanvas.height / 12)
|
||||
wrapMode: Text.WordWrap
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ColorIcon {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: Style.smallMargins
|
||||
// anchors.verticalCenterOffset: scaleCanvas.height / 2 - height
|
||||
name: app.interfaceToIcon(scaleCanvas.interfaceName)
|
||||
size: Math.min(Style.bigIconSize, parent.height / 5)
|
||||
color: app.interfaceToColor(scaleCanvas.interfaceName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,8 +1,11 @@
|
||||
import QtQuick 2.9
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Layouts 1.1
|
||||
import Nymea 1.0
|
||||
import "../utils"
|
||||
import "../components"
|
||||
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
@ -44,6 +47,21 @@ Item {
|
||||
property double stepSize: (root.targetTemperatureState.maxValue - root.targetTemperatureState.minValue) / steps
|
||||
property double anglePerStep: maxAngle / steps
|
||||
|
||||
readonly property double currentValue: actionQueue.pendingValue || root.targetTemperatureState.value
|
||||
readonly property double targetTempStep: roundToPrecision(currentValue - root.targetTemperatureState.minValue) * (1/root.precision)
|
||||
readonly property double currentTempStep: root.temperatureState ? roundToPrecision(root.temperatureState.value - root.targetTemperatureState.minValue) * (1/root.precision) : 0
|
||||
|
||||
|
||||
readonly property double targetTemperature: roundToPrecision(Types.toUiValue(currentValue, root.targetTemperatureStateType.unit))
|
||||
|
||||
readonly property color currentColor: {
|
||||
if (currentTempStep && currentTempStep < targetTempStep) {
|
||||
return app.interfaceToColor("heating");
|
||||
} else if (currentTempStep && currentTempStep > targetTempStep) {
|
||||
return app.interfaceToColor("cooling");
|
||||
}
|
||||
return Style.accentColor;
|
||||
}
|
||||
|
||||
function angleToValue(angle) {
|
||||
var from = root.targetTemperatureState.minValue
|
||||
@ -51,6 +69,30 @@ Item {
|
||||
return (to - from) * angle / maxAngle + from
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.centerIn: parent
|
||||
anchors.verticalCenterOffset: -Style.smallMargins
|
||||
width: parent.width * 0.6
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: canvas.targetTemperature.toFixed(1) + Types.toUiUnit(Types.UnitDegreeCelsius)
|
||||
wrapMode: Text.WordWrap
|
||||
font.pixelSize: Math.min(Style.hugeFont.pixelSize, canvas.height / 8)
|
||||
maximumLineCount: 2
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
elide: Text.ElideRight
|
||||
color: canvas.currentColor
|
||||
}
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: Types.toUiValue(root.temperatureState.value, root.temperatureStateType.unit).toFixed(1) + Types.toUiUnit(Types.UnitDegreeCelsius)
|
||||
font.pixelSize: Math.min(Style.largeFont.pixelSize, canvas.height / 12)
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
|
||||
onPaint: {
|
||||
var ctx = canvas.getContext('2d');
|
||||
ctx.save();
|
||||
@ -76,39 +118,33 @@ Item {
|
||||
ctx.closePath();
|
||||
|
||||
// Step lines
|
||||
var currentValue = actionQueue.pendingValue || root.targetTemperatureState.value
|
||||
var targetTempStep = roundToPrecision(currentValue - root.targetTemperatureState.minValue) * (1/root.precision)
|
||||
var currentTempStep;
|
||||
if (root.temperatureState) {
|
||||
currentTempStep = roundToPrecision(root.temperatureState.value - root.targetTemperatureState.minValue) * (1/root.precision)
|
||||
}
|
||||
|
||||
for(var step = 0; step < steps; step += root.precision) {
|
||||
var angle = step * anglePerStep + startAngle;
|
||||
var innerRadius = canvas.width * 0.4
|
||||
var outerRadius = canvas.width * 0.5
|
||||
|
||||
if (targetTempStep === step) {
|
||||
if (currentTempStep && currentTempStep < targetTempStep) {
|
||||
if (canvas.targetTempStep === step) {
|
||||
if (canvas.currentTempStep && canvas.currentTempStep < canvas.targetTempStep) {
|
||||
ctx.strokeStyle = app.interfaceToColor("heating");
|
||||
} else if (currentTempStep && currentTempStep > targetTempStep) {
|
||||
} else if (canvas.currentTempStep && canvas.currentTempStep > canvas.targetTempStep) {
|
||||
ctx.strokeStyle = app.interfaceToColor("cooling");
|
||||
} else {
|
||||
ctx.strokeStyle = Style.accentColor;
|
||||
}
|
||||
innerRadius = canvas.width * 0.38
|
||||
ctx.lineWidth = 4;
|
||||
} else if (currentTempStep && currentTempStep === step) {
|
||||
if (currentTempStep < targetTempStep) {
|
||||
} else if (canvas.currentTempStep && canvas.currentTempStep === step) {
|
||||
if (canvas.currentTempStep < canvas.targetTempStep) {
|
||||
ctx.strokeStyle = app.interfaceToColor("heating");
|
||||
} else {
|
||||
ctx.strokeStyle = app.interfaceToColor("cooling");
|
||||
}
|
||||
ctx.lineWidth = 3;
|
||||
} else if (currentTempStep && currentTempStep < step && step < targetTempStep) {
|
||||
} else if (canvas.currentTempStep && canvas.currentTempStep < step && step < canvas.targetTempStep) {
|
||||
ctx.strokeStyle = app.interfaceToColor("heating");
|
||||
ctx.lineWidth = 2;
|
||||
} else if (currentTempStep && currentTempStep > step && step > targetTempStep) {
|
||||
} else if (canvas.currentTempStep && canvas.currentTempStep > step && step > canvas.targetTempStep) {
|
||||
ctx.strokeStyle = app.interfaceToColor("cooling");
|
||||
ctx.lineWidth = 2;
|
||||
} else {
|
||||
@ -132,28 +168,6 @@ Item {
|
||||
ctx.stroke();
|
||||
ctx.closePath();
|
||||
}
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.font = "" + Style.hugeFont.pixelSize + "px " + Style.fontFamily;
|
||||
ctx.fillStyle = Style.foregroundColor;
|
||||
var roundedTargetTemp = Types.toUiValue(currentValue, root.targetTemperatureStateType.unit)
|
||||
roundedTargetTemp = roundToPrecision(roundedTargetTemp).toFixed(1) + "°"
|
||||
var size = ctx.measureText(roundedTargetTemp)
|
||||
ctx.text(roundedTargetTemp, center.x - size.width / 2, center.y + Style.hugeFont.pixelSize / 2);
|
||||
ctx.fill();
|
||||
ctx.closePath();
|
||||
|
||||
if (root.temperatureState) {
|
||||
ctx.beginPath();
|
||||
ctx.font = "" + Style.bigFont.pixelSize + "px " + Style.fontFamily;
|
||||
var roundedTemp = Types.toUiValue(root.temperatureState.value, root.temperatureStateType.unit)
|
||||
roundedTemp = roundToPrecision(roundedTemp) + "°"
|
||||
size = ctx.measureText(roundedTemp)
|
||||
ctx.text(roundedTemp, center.x - size.width / 2, center.y + Style.hugeFont.pixelSize + Style.margins);
|
||||
ctx.fill();
|
||||
ctx.closePath();
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
@ -163,10 +177,12 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ColorIcon {
|
||||
width: Style.largeIconSize
|
||||
height: width
|
||||
anchors { bottom: canvas.bottom; horizontalCenter: canvas.horizontalCenter; margins: Style.margins }
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: Style.smallMargins
|
||||
size: Math.min(Style.bigIconSize, parent.height / 5)
|
||||
name: root.heatingOnState && root.heatingOnState.value === true
|
||||
? "../images/thermostat/heating.svg"
|
||||
: root.coolingOnState && root.coolingOnState.value === true
|
||||
|
||||
@ -76,6 +76,7 @@ MainPageTile {
|
||||
switch (iface.name) {
|
||||
case "heating":
|
||||
case "cooling":
|
||||
case "thermostat":
|
||||
case "sensor":
|
||||
page = "SensorsDeviceListPage.qml"
|
||||
break;
|
||||
|
||||
@ -231,6 +231,7 @@ ItemDelegate {
|
||||
|
||||
Component.onCompleted: {
|
||||
print("from:", from, "min", root.paramType.minValue)
|
||||
print("to:", to, "max", root.paramType.maxValue)
|
||||
if (root.value === undefined) {
|
||||
root.value = value
|
||||
}
|
||||
|
||||
167
nymea-app/ui/delegates/SensorListDelegate.qml
Normal file
167
nymea-app/ui/delegates/SensorListDelegate.qml
Normal file
@ -0,0 +1,167 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2020, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
* This project including source code and documentation is protected by
|
||||
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||||
* reproduction, publication, editing and translation, are reserved. The use of
|
||||
* this project is subject to the terms of a license agreement to be concluded
|
||||
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||
* under https://nymea.io/license
|
||||
*
|
||||
* GNU General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under the
|
||||
* terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, GNU version 3. This project is distributed in the hope that it
|
||||
* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this project. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* For any further details and any questions please contact us under
|
||||
* contact@nymea.io or see our FAQ/Licensing Information on
|
||||
* https://nymea.io/license/faq
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 2.1
|
||||
import QtQuick.Controls.Material 2.1
|
||||
import QtQuick.Layouts 1.1
|
||||
import Nymea 1.0
|
||||
import "../components"
|
||||
|
||||
BigThingTile {
|
||||
id: itemDelegate
|
||||
|
||||
contentItem: GridLayout {
|
||||
id: dataGrid
|
||||
columns: Math.floor(contentItem.width / 120)
|
||||
|
||||
Repeater {
|
||||
model: ListModel {
|
||||
ListElement { interfaceName: "temperaturesensor"; stateName: "temperature" }
|
||||
ListElement { interfaceName: "humiditysensor"; stateName: "humidity" }
|
||||
ListElement { interfaceName: "moisturesensor"; stateName: "moisture" }
|
||||
ListElement { interfaceName: "pressuresensor"; stateName: "pressure" }
|
||||
ListElement { interfaceName: "lightsensor"; stateName: "lightIntensity" }
|
||||
ListElement { interfaceName: "conductivitysensor"; stateName: "conductivity" }
|
||||
ListElement { interfaceName: "noisesensor"; stateName: "noise" }
|
||||
ListElement { interfaceName: "cosensor"; stateName: "co" }
|
||||
ListElement { interfaceName: "co2sensor"; stateName: "co2" }
|
||||
ListElement { interfaceName: "gassensor"; stateName: "gasLevel" }
|
||||
ListElement { interfaceName: "daylightsensor"; stateName: "daylight" }
|
||||
ListElement { interfaceName: "presencesensor"; stateName: "isPresent" }
|
||||
ListElement { interfaceName: "vibrationsensor"; stateName: ""; eventName: "vibrationDetected" }
|
||||
ListElement { interfaceName: "closablesensor"; stateName: "closed" }
|
||||
ListElement { interfaceName: "heating"; stateName: "power" }
|
||||
ListElement { interfaceName: "thermostat"; stateName: "targetTemperature" }
|
||||
ListElement { interfaceName: "watersensor"; stateName: "waterDetected" }
|
||||
ListElement { interfaceName: "waterlevelsensor"; stateName: "waterLevel" }
|
||||
ListElement { interfaceName: "firesensor"; stateName: "fireDetected" }
|
||||
ListElement { interfaceName: "o2sensor"; stateName: "o2saturation" }
|
||||
ListElement { interfaceName: "phsensor"; stateName: "ph" }
|
||||
ListElement { interfaceName: "orpsensor"; stateName: "orp" }
|
||||
ListElement { interfaceName: "vocsensor"; stateName: "voc" }
|
||||
ListElement { interfaceName: "pm10sensor"; stateName: "pm10" }
|
||||
ListElement { interfaceName: "pm25sensor"; stateName: "pm25" }
|
||||
ListElement { interfaceName: "no2sensor"; stateName: "no2" }
|
||||
ListElement { interfaceName: "o3sensor"; stateName: "o3" }
|
||||
}
|
||||
|
||||
delegate: RowLayout {
|
||||
id: sensorValueDelegate
|
||||
visible: itemDelegate.thing.thingClass.interfaces.indexOf(model.interfaceName) >= 0
|
||||
Layout.preferredWidth: contentItem.width / dataGrid.columns
|
||||
|
||||
property StateType stateType: itemDelegate.thing.thingClass.stateTypes.findByName(model.stateName)
|
||||
property State stateValue: stateType ? itemDelegate.thing.states.getState(stateType.id) : null
|
||||
|
||||
property EventType eventType: itemDelegate.thing.thingClass.eventTypes.findByName(model.eventName)
|
||||
LogsModel {
|
||||
id: eventLogsModel
|
||||
engine: sensorValueDelegate.eventType != null ? _engine : null
|
||||
thingId: itemDelegate.thing.id
|
||||
typeIds: sensorValueDelegate.eventType != null ? [sensorValueDelegate.eventType.id] : []
|
||||
live: true
|
||||
fetchBlockSize: 1
|
||||
}
|
||||
|
||||
ColorIcon {
|
||||
Layout.preferredHeight: Style.iconSize
|
||||
Layout.preferredWidth: height
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
color: {
|
||||
switch (model.interfaceName) {
|
||||
case "closablesensor":
|
||||
return sensorValueDelegate.stateValue && sensorValueDelegate.stateValue.value === true ? Style.green : Style.red;
|
||||
case "firesensor":
|
||||
return sensorValueDelegate.stateValue && sensorValueDelegate.stateValue.value === true ? Style.red : Style.iconColor;
|
||||
default:
|
||||
return app.interfaceToColor(model.interfaceName)
|
||||
}
|
||||
}
|
||||
name: {
|
||||
switch (model.interfaceName) {
|
||||
case "closablesensor":
|
||||
return sensorValueDelegate.stateValue && sensorValueDelegate.stateValue.value === true ? Qt.resolvedUrl("qrc:/ui/images/sensors/window-closed.svg") : Qt.resolvedUrl("qrc:/ui/images/sensors/window-open.svg");
|
||||
default:
|
||||
return app.interfacesToIcon([model.interfaceName, "sensor"])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
property var unit: sensorValueDelegate.stateType ? sensorValueDelegate.stateType.unit : Types.UnitNone
|
||||
text: {
|
||||
switch (model.interfaceName) {
|
||||
case "closablesensor":
|
||||
return sensorValueDelegate.stateValue && sensorValueDelegate.stateValue.value === true ? qsTr("Closed") : qsTr("Open");
|
||||
case "presencesensor":
|
||||
return sensorValueDelegate.stateValue && sensorValueDelegate.stateValue.value === true ? qsTr("Presence") : qsTr("Vacant");
|
||||
case "daylightsensor":
|
||||
return sensorValueDelegate.stateValue && sensorValueDelegate.stateValue.value === true ? qsTr("Daytime") : qsTr("Nighttime");
|
||||
case "watersensor":
|
||||
return sensorValueDelegate.stateValue && sensorValueDelegate.stateValue.value === true ? qsTr("Wet") : qsTr("Dry");
|
||||
case "firesensor":
|
||||
return sensorValueDelegate.stateValue && sensorValueDelegate.stateValue.value === true ? qsTr("Fire") : qsTr("No fire");
|
||||
case "heating":
|
||||
return sensorValueDelegate.stateValue && sensorValueDelegate.stateValue.value === true ? qsTr("On") : qsTr("Off");
|
||||
case "vibrationsensor": {
|
||||
if (eventLogsModel.count > 0) {
|
||||
return qsTr("Last vibration: %1").arg(eventLogsModel.get(0).timestamp.toLocaleString(Qt.locale(), Locale.ShortFormat))
|
||||
} else {
|
||||
return qsTr("Not moved yet")
|
||||
}
|
||||
}
|
||||
default:
|
||||
return sensorValueDelegate.stateType && sensorValueDelegate.stateType.type.toLowerCase() === "bool"
|
||||
? sensorValueDelegate.stateType.displayName
|
||||
: sensorValueDelegate.stateValue
|
||||
? "%1 %2".arg(Math.round(Types.toUiValue(sensorValueDelegate.stateValue.value, unit) * 100) / 100).arg(Types.toUiUnit(unit))
|
||||
: ""
|
||||
}
|
||||
}
|
||||
elide: Text.ElideRight
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
font.pixelSize: app.smallFont
|
||||
}
|
||||
Led {
|
||||
id: led
|
||||
visible: sensorValueDelegate.stateType && sensorValueDelegate.stateType.type.toLowerCase() === "bool" && ["presencesensor", "daylightsensor", "heating", "closablesensor", "watersensor", "firesensor"].indexOf(model.interfaceName) < 0
|
||||
state: visible && sensorValueDelegate.stateValue.value === true ? "on" : "off"
|
||||
}
|
||||
Item {
|
||||
Layout.preferredWidth: led.width
|
||||
visible: led.visible
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -33,13 +33,18 @@ import QtQuick.Controls 2.1
|
||||
import QtQuick.Controls.Material 2.1
|
||||
import QtQuick.Layouts 1.1
|
||||
import Nymea 1.0
|
||||
import "../components"
|
||||
import "qrc:/ui/components"
|
||||
import "qrc:/ui/delegates"
|
||||
|
||||
ThingsListPageBase {
|
||||
id: root
|
||||
|
||||
header: NymeaHeader {
|
||||
text: root.shownInterfaces.indexOf("heating") >= 0 ? qsTr("Heating") : qsTr("Sensors")
|
||||
text: root.shownInterfaces.indexOf("heating") >= 0
|
||||
? qsTr("Heating")
|
||||
: root.shownInterfaces.indexOf("thermostat") >= 0
|
||||
? qsTr("Thermostats")
|
||||
: qsTr("Sensors")
|
||||
onBackPressed: pageStack.pop()
|
||||
}
|
||||
|
||||
@ -60,7 +65,7 @@ ThingsListPageBase {
|
||||
Repeater {
|
||||
model: root.thingsProxy
|
||||
|
||||
delegate: BigThingTile {
|
||||
delegate: SensorListDelegate {
|
||||
id: itemDelegate
|
||||
Layout.preferredWidth: contentGrid.width / contentGrid.columns
|
||||
thing: root.thingsProxy.getThing(model.id)
|
||||
@ -74,133 +79,6 @@ ThingsListPageBase {
|
||||
enterPage(index)
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: GridLayout {
|
||||
id: dataGrid
|
||||
columns: Math.floor(contentItem.width / 120)
|
||||
|
||||
Repeater {
|
||||
model: ListModel {
|
||||
ListElement { interfaceName: "temperaturesensor"; stateName: "temperature" }
|
||||
ListElement { interfaceName: "humiditysensor"; stateName: "humidity" }
|
||||
ListElement { interfaceName: "moisturesensor"; stateName: "moisture" }
|
||||
ListElement { interfaceName: "pressuresensor"; stateName: "pressure" }
|
||||
ListElement { interfaceName: "lightsensor"; stateName: "lightIntensity" }
|
||||
ListElement { interfaceName: "conductivitysensor"; stateName: "conductivity" }
|
||||
ListElement { interfaceName: "noisesensor"; stateName: "noise" }
|
||||
ListElement { interfaceName: "cosensor"; stateName: "co" }
|
||||
ListElement { interfaceName: "co2sensor"; stateName: "co2" }
|
||||
ListElement { interfaceName: "gassensor"; stateName: "gasLevel" }
|
||||
ListElement { interfaceName: "daylightsensor"; stateName: "daylight" }
|
||||
ListElement { interfaceName: "presencesensor"; stateName: "isPresent" }
|
||||
ListElement { interfaceName: "vibrationsensor"; stateName: ""; eventName: "vibrationDetected" }
|
||||
ListElement { interfaceName: "closablesensor"; stateName: "closed" }
|
||||
ListElement { interfaceName: "heating"; stateName: "power" }
|
||||
ListElement { interfaceName: "thermostat"; stateName: "targetTemperature" }
|
||||
ListElement { interfaceName: "watersensor"; stateName: "waterDetected" }
|
||||
ListElement { interfaceName: "waterlevelsensor"; stateName: "waterLevel" }
|
||||
ListElement { interfaceName: "firesensor"; stateName: "fireDetected" }
|
||||
ListElement { interfaceName: "o2sensor"; stateName: "o2saturation" }
|
||||
ListElement { interfaceName: "phsensor"; stateName: "ph" }
|
||||
ListElement { interfaceName: "orpsensor"; stateName: "orp" }
|
||||
ListElement { interfaceName: "vocsensor"; stateName: "voc" }
|
||||
ListElement { interfaceName: "pm10sensor"; stateName: "pm10" }
|
||||
ListElement { interfaceName: "pm25sensor"; stateName: "pm25" }
|
||||
ListElement { interfaceName: "no2sensor"; stateName: "no2" }
|
||||
ListElement { interfaceName: "o3sensor"; stateName: "o3" }
|
||||
}
|
||||
|
||||
delegate: RowLayout {
|
||||
id: sensorValueDelegate
|
||||
visible: itemDelegate.thing.thingClass.interfaces.indexOf(model.interfaceName) >= 0
|
||||
Layout.preferredWidth: contentItem.width / dataGrid.columns
|
||||
|
||||
property StateType stateType: itemDelegate.thing.thingClass.stateTypes.findByName(model.stateName)
|
||||
property State stateValue: stateType ? itemDelegate.thing.states.getState(stateType.id) : null
|
||||
|
||||
property EventType eventType: itemDelegate.thing.thingClass.eventTypes.findByName(model.eventName)
|
||||
LogsModel {
|
||||
id: eventLogsModel
|
||||
engine: sensorValueDelegate.eventType != null ? _engine : null
|
||||
thingId: itemDelegate.thing.id
|
||||
typeIds: sensorValueDelegate.eventType != null ? [sensorValueDelegate.eventType.id] : []
|
||||
live: true
|
||||
fetchBlockSize: 1
|
||||
}
|
||||
|
||||
ColorIcon {
|
||||
Layout.preferredHeight: Style.iconSize
|
||||
Layout.preferredWidth: height
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
color: {
|
||||
switch (model.interfaceName) {
|
||||
case "closablesensor":
|
||||
return sensorValueDelegate.stateValue && sensorValueDelegate.stateValue.value === true ? Style.green : Style.red;
|
||||
case "firesensor":
|
||||
return sensorValueDelegate.stateValue && sensorValueDelegate.stateValue.value === true ? Style.red : Style.iconColor;
|
||||
default:
|
||||
return app.interfaceToColor(model.interfaceName)
|
||||
}
|
||||
}
|
||||
name: {
|
||||
switch (model.interfaceName) {
|
||||
case "closablesensor":
|
||||
return sensorValueDelegate.stateValue && sensorValueDelegate.stateValue.value === true ? Qt.resolvedUrl("../images/lock-closed.svg") : Qt.resolvedUrl("../images/lock-open.svg");
|
||||
default:
|
||||
return app.interfacesToIcon([model.interfaceName, "sensor"])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
property var unit: sensorValueDelegate.stateType ? sensorValueDelegate.stateType.unit : Types.UnitNone
|
||||
text: {
|
||||
switch (model.interfaceName) {
|
||||
case "closablesensor":
|
||||
return sensorValueDelegate.stateValue && sensorValueDelegate.stateValue.value === true ? qsTr("Closed") : qsTr("Open");
|
||||
case "presencesensor":
|
||||
return sensorValueDelegate.stateValue && sensorValueDelegate.stateValue.value === true ? qsTr("Presence") : qsTr("Vacant");
|
||||
case "daylightsensor":
|
||||
return sensorValueDelegate.stateValue && sensorValueDelegate.stateValue.value === true ? qsTr("Daytime") : qsTr("Nighttime");
|
||||
case "watersensor":
|
||||
return sensorValueDelegate.stateValue && sensorValueDelegate.stateValue.value === true ? qsTr("Wet") : qsTr("Dry");
|
||||
case "firesensor":
|
||||
return sensorValueDelegate.stateValue && sensorValueDelegate.stateValue.value === true ? qsTr("Fire") : qsTr("No fire");
|
||||
case "heating":
|
||||
return sensorValueDelegate.stateValue && sensorValueDelegate.stateValue.value === true ? qsTr("On") : qsTr("Off");
|
||||
case "vibrationsensor": {
|
||||
if (eventLogsModel.count > 0) {
|
||||
return qsTr("Last vibration: %1").arg(eventLogsModel.get(0).timestamp.toLocaleString(Qt.locale(), Locale.ShortFormat))
|
||||
} else {
|
||||
return qsTr("Not moved yet")
|
||||
}
|
||||
}
|
||||
default:
|
||||
return sensorValueDelegate.stateType && sensorValueDelegate.stateType.type.toLowerCase() === "bool"
|
||||
? sensorValueDelegate.stateType.displayName
|
||||
: sensorValueDelegate.stateValue
|
||||
? "%1 %2".arg(Math.round(Types.toUiValue(sensorValueDelegate.stateValue.value, unit) * 100) / 100).arg(Types.toUiUnit(unit))
|
||||
: ""
|
||||
}
|
||||
}
|
||||
elide: Text.ElideRight
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
font.pixelSize: app.smallFont
|
||||
}
|
||||
Led {
|
||||
id: led
|
||||
visible: sensorValueDelegate.stateType && sensorValueDelegate.stateType.type.toLowerCase() === "bool" && ["presencesensor", "daylightsensor", "heating", "closablesensor", "watersensor", "firesensor"].indexOf(model.interfaceName) < 0
|
||||
state: visible && sensorValueDelegate.stateValue.value === true ? "on" : "off"
|
||||
}
|
||||
Item {
|
||||
Layout.preferredWidth: led.width
|
||||
visible: led.visible
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -90,7 +90,7 @@ ThingPageBase {
|
||||
}
|
||||
}
|
||||
|
||||
Dial {
|
||||
StateDial {
|
||||
anchors.centerIn: parent
|
||||
height: background.contentItem.height
|
||||
width: background.contentItem.width
|
||||
|
||||
@ -317,7 +317,7 @@ ThingPageBase {
|
||||
|
||||
Component {
|
||||
id: detailsPopup
|
||||
MeaDialog {
|
||||
NymeaDialog {
|
||||
id: detailsDialog
|
||||
property string timestamp
|
||||
property bool accessGranted
|
||||
|
||||
@ -234,7 +234,7 @@ ThingPageBase {
|
||||
Component {
|
||||
id: detailsPopup
|
||||
|
||||
MeaDialog {
|
||||
NymeaDialog {
|
||||
id: detailsDialog
|
||||
standardButtons: Dialog.NoButton
|
||||
property string timestamp
|
||||
|
||||
@ -111,92 +111,16 @@ ThingPageBase {
|
||||
id: flowRepeater
|
||||
model: sensorsModel
|
||||
|
||||
delegate: Item {
|
||||
delegate: SensorView {
|
||||
width: Math.floor(flow.width / itemsInRow)
|
||||
height: Math.min(400, flow.cellWidth)
|
||||
property int row: Math.floor(index / flow.columns)
|
||||
property int itemsInRow: row < flow.totalRows ? flow.columns : (flowRepeater.count % flow.columns)
|
||||
|
||||
CircleBackground {
|
||||
id: background
|
||||
anchors.centerIn: parent
|
||||
width: Math.min(parent.width, parent.height) - Style.margins
|
||||
height: width
|
||||
|
||||
readonly property StateType sensorStateType: root.thing.thingClass.stateTypes.findByName(interfaceStateMap[modelData])
|
||||
readonly property State sensorState: root.thing.stateByName(interfaceStateMap[modelData])
|
||||
|
||||
onColor: app.interfaceToColor(modelData)
|
||||
on: sensorStateType.type.toLowerCase() == "bool" && sensorState.value === true
|
||||
iconSource: [
|
||||
"closablesensor",
|
||||
"presencesensor",
|
||||
"daylightsensor",
|
||||
"firesensor",
|
||||
"watersensor"
|
||||
].indexOf(modelData) >= 0 ? app.interfaceToIcon(modelData) : ""
|
||||
|
||||
Loader {
|
||||
anchors.centerIn: parent
|
||||
width: background.contentItem.width
|
||||
height: background.contentItem.height
|
||||
property StateType stateType: root.thingClass.stateTypes.findByName(interfaceStateMap[modelData])
|
||||
property State state: root.thing.stateByName(interfaceStateMap[modelData])
|
||||
property string interfaceName: modelData
|
||||
property var minValue: {
|
||||
if (["temperaturesensor"].indexOf(modelData) >= 0) {
|
||||
return Types.toUiValue(-50, Types.UnitDegreeCelsius)
|
||||
}
|
||||
if (["pressuresensor"].indexOf(modelData) >= 0) {
|
||||
return Types.toUiValue(900, Types.UnitMilliBar)
|
||||
}
|
||||
|
||||
return state.minValue
|
||||
}
|
||||
property var maxValue: {
|
||||
if (["temperaturesensor"].indexOf(modelData) >= 0) {
|
||||
return Types.toUiValue(50, Types.UnitDegreeCelsius)
|
||||
}
|
||||
if (["pressuresensor"].indexOf(modelData) >= 0) {
|
||||
return Types.toUiValue(1100, Types.UnitMilliBar)
|
||||
}
|
||||
return state.maxValue
|
||||
}
|
||||
|
||||
sourceComponent: {
|
||||
var progressInterfaces = [
|
||||
"humiditysensor",
|
||||
"o2sensor",
|
||||
"temperaturesensor",
|
||||
"moisturesensor",
|
||||
"conductivitysensor",
|
||||
"gassensor",
|
||||
"lightsensor",
|
||||
"orpsensor",
|
||||
"phsensor",
|
||||
"pressuresensor",
|
||||
"waterlevelsensor",
|
||||
"windspeedsensor"
|
||||
]
|
||||
if (progressInterfaces.indexOf(modelData) >= 0) {
|
||||
return progressComponent
|
||||
}
|
||||
var scaleInterfaces = [
|
||||
"vocsensor",
|
||||
"cosensor",
|
||||
"co2sensor",
|
||||
"o3sensor",
|
||||
"pm10sensor",
|
||||
"pm25sensor",
|
||||
"no2sensor"
|
||||
]
|
||||
if (scaleInterfaces.indexOf(modelData) >= 0) {
|
||||
return scaleComponent
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
thing: root.thing
|
||||
interfaceName: modelData
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -218,7 +142,6 @@ ThingPageBase {
|
||||
property State state: root.thing.stateByName(interfaceStateMap[modelData])
|
||||
property string interfaceName: modelData
|
||||
|
||||
// sourceComponent: stateType && stateType.type.toLowerCase() === "bool" ? boolComponent : graphComponent
|
||||
sourceComponent: graphComponent
|
||||
|
||||
}
|
||||
@ -233,7 +156,12 @@ ThingPageBase {
|
||||
id: graph
|
||||
thing: root.thing
|
||||
color: app.interfaceToColor(interfaceName)
|
||||
iconSource: app.interfaceToIcon(interfaceName)
|
||||
iconSource: {
|
||||
if (graph.interfaceName == "closablesensor") {
|
||||
return graph.state.value === true ? "sensors/window-closed" : "sensors/window-open"
|
||||
}
|
||||
return app.interfaceToIcon(interfaceName)
|
||||
}
|
||||
implicitHeight: width * .6
|
||||
property string interfaceName: parent.interfaceName
|
||||
stateType: parent.stateType
|
||||
@ -260,365 +188,6 @@ ThingPageBase {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: boolComponent
|
||||
GridLayout {
|
||||
id: boolView
|
||||
property string interfaceName: parent.interfaceName
|
||||
property StateType stateType: parent.stateType
|
||||
height: listView.height
|
||||
columns: app.landscape ? 2 : 1
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.minimumWidth: Style.iconSize * 5
|
||||
Layout.rowSpan: app.landscape ? 5 : 1
|
||||
ColorIcon {
|
||||
anchors.centerIn: parent
|
||||
height: Style.iconSize * 4
|
||||
width: height
|
||||
name: {
|
||||
switch (boolView.interfaceName) {
|
||||
case "closablesensor":
|
||||
return thing.states.getState(boolView.stateType.id).value === true ? Qt.resolvedUrl("../images/lock-closed.svg") : Qt.resolvedUrl("../images/lock-open.svg")
|
||||
default:
|
||||
return app.interfaceToIcon(boolView.interfaceName)
|
||||
}
|
||||
}
|
||||
color: {
|
||||
switch (boolView.interfaceName) {
|
||||
case "closablesensor":
|
||||
return thing.states.getState(boolView.stateType.id).value === true ? "green" : "red"
|
||||
default:
|
||||
thing.states.getState(boolView.stateType.id).value === true ? app.interfaceToColor(boolView.interfaceName) : Style.iconColor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
RowLayout {
|
||||
Layout.fillWidth: false
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
property StateType lastSeenStateType: root.thingClass.stateTypes.findByName("lastSeenTime")
|
||||
property State lastSeenState: lastSeenStateType ? root.thing.states.getState(lastSeenStateType.id) : null
|
||||
visible: lastSeenStateType !== null
|
||||
Label {
|
||||
text: qsTr("Last seen:")
|
||||
font.bold: true
|
||||
}
|
||||
Label {
|
||||
text: parent.lastSeenState ? Qt.formatDateTime(new Date(parent.lastSeenState.value * 1000)) : ""
|
||||
}
|
||||
}
|
||||
RowLayout {
|
||||
Layout.fillWidth: false
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
property StateType sunriseStateType: root.thingClass.stateTypes.findByName("sunriseTime")
|
||||
property State sunriseState: sunriseStateType ? root.thing.states.getState(sunriseStateType.id) : null
|
||||
visible: sunriseStateType !== null
|
||||
Label {
|
||||
text: qsTr("Sunrise:")
|
||||
font.bold: true
|
||||
}
|
||||
Label {
|
||||
text: parent.sunriseStateType ? Qt.formatDateTime(new Date(parent.sunriseState.value * 1000)) : ""
|
||||
}
|
||||
}
|
||||
RowLayout {
|
||||
Layout.fillWidth: false
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
property StateType sunsetStateType: root.thingClass.stateTypes.findByName("sunsetTime")
|
||||
property State sunsetState: sunsetStateType ? root.thing.states.getState(sunsetStateType.id) : null
|
||||
visible: sunsetStateType !== null
|
||||
Label {
|
||||
text: qsTr("Sunset:")
|
||||
font.bold: true
|
||||
}
|
||||
Label {
|
||||
text: parent.sunsetStateType ? Qt.formatDateTime(new Date(parent.sunsetState.value * 1000)) : ""
|
||||
}
|
||||
}
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: progressComponent
|
||||
Canvas {
|
||||
id: progressCanvas
|
||||
property string interfaceName: parent.interfaceName
|
||||
property StateType stateType: parent.stateType
|
||||
property State state: parent.state
|
||||
property var minValue: parent.minValue
|
||||
property var maxValue: parent.maxValue
|
||||
|
||||
property double progress: (progressCanvas.state.value - progressCanvas.minValue) / (progressCanvas.maxValue - progressCanvas.minValue)
|
||||
Behavior on progress { NumberAnimation { duration: Style.slowAnimationDuration; easing.type: Easing.InOutQuad } }
|
||||
onProgressChanged: requestPaint();
|
||||
|
||||
ColumnLayout {
|
||||
anchors.centerIn: parent
|
||||
anchors.verticalCenterOffset: -Style.smallMargins
|
||||
width: parent.width * 0.6
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: Types.toUiValue(progressCanvas.state.value, progressCanvas.stateType.unit).toFixed(1)
|
||||
wrapMode: Text.WordWrap
|
||||
font.pixelSize: Math.min(Style.hugeFont.pixelSize, progressCanvas.height / 8)
|
||||
maximumLineCount: 2
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: Types.toUiUnit(progressCanvas.stateType.unit)
|
||||
font.pixelSize: Math.min(Style.largeFont.pixelSize, progressCanvas.height / 12)
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
onPaint: {
|
||||
var ctx = getContext("2d");
|
||||
ctx.reset();
|
||||
|
||||
ctx.beginPath()
|
||||
ctx.fillStyle = Style.foregroundColor
|
||||
|
||||
ctx.translate(width / 2, height / 2)
|
||||
ctx.rotate(135 * Math.PI / 180)
|
||||
|
||||
ctx.lineCap = "round"
|
||||
ctx.lineWidth = width * .1
|
||||
|
||||
ctx.beginPath()
|
||||
ctx.strokeStyle = Style.tileOverlayColor
|
||||
var startAngle = 0
|
||||
var endAngle = 270
|
||||
var radStart = startAngle * Math.PI/180;
|
||||
var radEnd = endAngle * Math.PI/180;
|
||||
ctx.arc(0, 0, width / 2 - ctx.lineWidth / 2, radStart, radEnd)
|
||||
ctx.stroke()
|
||||
ctx.closePath()
|
||||
|
||||
ctx.beginPath()
|
||||
ctx.strokeStyle = app.interfaceToColor(progressCanvas.interfaceName)
|
||||
radEnd *= progressCanvas.progress
|
||||
ctx.arc(0, 0, width / 2 - ctx.lineWidth / 2, radStart, radEnd)
|
||||
ctx.stroke()
|
||||
ctx.closePath()
|
||||
}
|
||||
|
||||
ColorIcon {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: Style.smallMargins
|
||||
name: app.interfaceToIcon(progressCanvas.interfaceName)
|
||||
size: Math.min(Style.bigIconSize, parent.height / 5)
|
||||
color: app.interfaceToColor(progressCanvas.interfaceName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: scaleComponent
|
||||
Item {
|
||||
id: scaleCanvas
|
||||
property string interfaceName: parent.interfaceName
|
||||
property StateType stateType: parent.stateType
|
||||
property State state: parent.state
|
||||
property var minValue: parent.minValue
|
||||
property var maxValue: parent.maxValue
|
||||
|
||||
property int scaleWidth: width * .1
|
||||
|
||||
property var scale: {
|
||||
switch (interfaceName) {
|
||||
case "vocsensor":
|
||||
return AirQualityIndex.iaqVoc
|
||||
case "cosensor":
|
||||
return AirQualityIndex.caqiCo
|
||||
case "o3sensor":
|
||||
return AirQualityIndex.caqiO3
|
||||
case "pm10sensor":
|
||||
return AirQualityIndex.caqiPm10
|
||||
case "pm25sensor":
|
||||
return AirQualityIndex.caqiPm25
|
||||
case "no2sensor":
|
||||
return AirQualityIndex.caqiNo2
|
||||
}
|
||||
return baseScale
|
||||
}
|
||||
property var baseScale: [
|
||||
{
|
||||
"value": maxValue,
|
||||
"angle": 270,
|
||||
"color": Style.tileOverlayColor
|
||||
}
|
||||
]
|
||||
|
||||
property var currentIndex: {
|
||||
for (var i = 0; i < scale.length; i++) {
|
||||
if (state.value <= scale[i].value) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
log.warn("Value out of scale!")
|
||||
return -1
|
||||
}
|
||||
|
||||
property double angle: {
|
||||
var baseAngle = 0
|
||||
var baseValue = 0;
|
||||
if (currentIndex > 0) {
|
||||
baseAngle = scale[currentIndex-1].angle
|
||||
baseValue = scale[currentIndex-1].value
|
||||
}
|
||||
var valueRange = scale[currentIndex].value - baseValue
|
||||
var angleRange = scale[currentIndex].angle - baseAngle
|
||||
var progress = (state.value - baseValue) / (scale[currentIndex].value - baseValue)
|
||||
return baseAngle + angleRange * progress
|
||||
}
|
||||
Behavior on angle { NumberAnimation { duration: Style.slowAnimationDuration; easing.type: Easing.InOutQuad } }
|
||||
onAngleChanged: maskCanvas.requestPaint();
|
||||
|
||||
Canvas {
|
||||
id: baseCanvas
|
||||
anchors.fill: parent
|
||||
visible: false
|
||||
onPaint: {
|
||||
var ctx = getContext("2d");
|
||||
ctx.reset();
|
||||
|
||||
ctx.beginPath()
|
||||
ctx.fillStyle = Style.foregroundColor
|
||||
|
||||
ctx.translate(width / 2, height / 2)
|
||||
ctx.rotate(135 * Math.PI / 180)
|
||||
|
||||
ctx.lineCap = "round"
|
||||
ctx.lineWidth = scaleCanvas.scaleWidth
|
||||
|
||||
// paint first rounded
|
||||
ctx.beginPath()
|
||||
ctx.strokeStyle = scaleCanvas.scale[0].color
|
||||
var startAngle = 0
|
||||
var endAngle = scaleCanvas.scale[0].angle * Math.PI/180
|
||||
ctx.arc(0, 0, width / 2 - ctx.lineWidth / 2, startAngle, endAngle)
|
||||
ctx.stroke()
|
||||
ctx.closePath()
|
||||
|
||||
// paint last rounded
|
||||
ctx.beginPath()
|
||||
ctx.strokeStyle = scaleCanvas.scale[scaleCanvas.scale.length - 1].color
|
||||
startAngle = scaleCanvas.scale[scaleCanvas.scale.length - 2].angle * Math.PI/180
|
||||
endAngle = scaleCanvas.scale[scaleCanvas.scale.length - 1].angle * Math.PI/180
|
||||
ctx.arc(0, 0, width / 2 - ctx.lineWidth / 2, startAngle, endAngle)
|
||||
ctx.stroke()
|
||||
ctx.closePath()
|
||||
|
||||
// paint inner parts
|
||||
ctx.lineCap = "butt"
|
||||
for (var i = 1; i < scaleCanvas.scale.length - 1; i++) {
|
||||
ctx.beginPath()
|
||||
ctx.strokeStyle = scaleCanvas.scale[i].color
|
||||
startAngle = scaleCanvas.scale[i - 1].angle * Math.PI/180
|
||||
endAngle = scaleCanvas.scale[i].angle * Math.PI/180
|
||||
ctx.arc(0, 0, width / 2 - ctx.lineWidth / 2, startAngle, endAngle)
|
||||
ctx.stroke()
|
||||
ctx.closePath()
|
||||
}
|
||||
}
|
||||
}
|
||||
Canvas {
|
||||
id: maskCanvas
|
||||
anchors.fill: parent
|
||||
visible: false
|
||||
onPaint: {
|
||||
var ctx = getContext("2d");
|
||||
ctx.reset();
|
||||
|
||||
ctx.beginPath()
|
||||
ctx.fillStyle = Style.foregroundColor
|
||||
|
||||
ctx.translate(width / 2, height / 2)
|
||||
ctx.rotate(135 * Math.PI / 180)
|
||||
|
||||
ctx.lineCap = "round"
|
||||
ctx.lineWidth = width * .1
|
||||
|
||||
ctx.beginPath()
|
||||
ctx.strokeStyle = "#55ffffff"
|
||||
var startAngle = 0
|
||||
var endAngle = 270
|
||||
var radStart = startAngle * Math.PI/180;
|
||||
var radEnd = endAngle * Math.PI/180;
|
||||
ctx.arc(0, 0, width / 2 - ctx.lineWidth / 2, radStart, radEnd)
|
||||
ctx.stroke()
|
||||
ctx.closePath()
|
||||
|
||||
ctx.beginPath()
|
||||
ctx.strokeStyle = "#000000"
|
||||
radEnd = scaleCanvas.angle * Math.PI/180
|
||||
ctx.arc(0, 0, width / 2 - ctx.lineWidth / 2, radStart, radEnd)
|
||||
ctx.stroke()
|
||||
ctx.closePath()
|
||||
}
|
||||
}
|
||||
|
||||
OpacityMask {
|
||||
anchors.fill: parent
|
||||
source: baseCanvas
|
||||
maskSource: maskCanvas
|
||||
}
|
||||
|
||||
|
||||
ColumnLayout {
|
||||
anchors.centerIn: parent
|
||||
anchors.verticalCenterOffset: -Style.smallMargins
|
||||
width: parent.width * 0.6
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: scaleCanvas.scale[scaleCanvas.currentIndex].text
|
||||
font.pixelSize: Math.min(Style.hugeFont.pixelSize, scaleCanvas.height / 8)
|
||||
wrapMode: Text.WordWrap
|
||||
// color: scaleCanvas.scale[scaleCanvas.currentIndex].color
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 2
|
||||
}
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: Types.toUiValue(scaleCanvas.state.value, scaleCanvas.stateType.unit).toFixed(1) + " " + Types.toUiUnit(scaleCanvas.stateType.unit)
|
||||
font.pixelSize: Math.min(Style.largeFont.pixelSize, scaleCanvas.height / 12)
|
||||
wrapMode: Text.WordWrap
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ColorIcon {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: Style.smallMargins
|
||||
// anchors.verticalCenterOffset: scaleCanvas.height / 2 - height
|
||||
name: app.interfaceToIcon(scaleCanvas.interfaceName)
|
||||
size: Math.min(Style.bigIconSize, parent.height / 5)
|
||||
color: app.interfaceToColor(scaleCanvas.interfaceName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -56,13 +56,14 @@ ThingPageBase {
|
||||
|
||||
GridLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: app.margins
|
||||
anchors.margins: Style.margins
|
||||
columns: app.landscape ? 2 : 1
|
||||
|
||||
CircleBackground {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.margins: Style.bigMargins
|
||||
|
||||
ThermostatController {
|
||||
anchors.centerIn: parent
|
||||
height: Math.min(400, Math.min(parent.height, parent.width))
|
||||
|
||||
@ -39,6 +39,7 @@ ThingPageBase {
|
||||
id: root
|
||||
|
||||
readonly property State powerState: thing.stateByName("power")
|
||||
readonly property State autoState: thing.stateByName("auto")
|
||||
readonly property State flowRateState: thing.stateByName("flowRate")
|
||||
readonly property StateType flowRateStateType: thing.thingClass.stateTypes.findByName("flowRate")
|
||||
|
||||
@ -48,40 +49,103 @@ ThingPageBase {
|
||||
stateName: "power"
|
||||
}
|
||||
|
||||
CircleBackground {
|
||||
id: background
|
||||
GridLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.hugeMargins
|
||||
iconSource: "ventilation"
|
||||
onColor: app.interfaceToColor("ventilation")
|
||||
showOnGradient: root.flowRateState == null
|
||||
on: (actionQueue.pendingValue || powerState.value) === true
|
||||
onClicked: {
|
||||
PlatformHelper.vibrate(PlatformHelper.HapticsFeedbackSelection)
|
||||
actionQueue.sendValue(!root.powerState.value)
|
||||
anchors.margins: Style.margins
|
||||
columns: app.landscape ? 2 : 1
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.margins: Style.bigMargins
|
||||
implicitWidth: 400
|
||||
implicitHeight: 400
|
||||
|
||||
CircleBackground {
|
||||
id: background
|
||||
anchors.fill: parent
|
||||
iconSource: "ventilation"
|
||||
onColor: app.interfaceToColor("ventilation")
|
||||
showOnGradient: root.flowRateState == null
|
||||
on: (actionQueue.pendingValue || powerState.value) === true
|
||||
PropertyAnimation on rotation {
|
||||
running: root.powerState.value === true
|
||||
duration: 2000
|
||||
from: 0
|
||||
to: 360
|
||||
loops: Animation.Infinite
|
||||
onDurationChanged: {
|
||||
running = false;
|
||||
running = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Dial {
|
||||
anchors.centerIn: background
|
||||
height: background.contentItem.height
|
||||
width: background.contentItem.width
|
||||
visible: root.flowRateState
|
||||
on: (actionQueue.pendingValue || powerState.value) === true
|
||||
value: valueActionQueue.pendingValue || flowRateState.value
|
||||
onMoved: valueActionQueue.sendValue(value)
|
||||
color: app.interfaceToColor("ventilation")
|
||||
minValue: root.flowRateState.minValue
|
||||
maxValue: root.flowRateState.maxValue
|
||||
|
||||
onClicked: {
|
||||
PlatformHelper.vibrate(PlatformHelper.HapticsFeedbackSelection)
|
||||
actionQueue.sendValue(!root.powerState.value)
|
||||
}
|
||||
|
||||
ActionQueue {
|
||||
id: valueActionQueue
|
||||
thing: root.thing
|
||||
stateName: "flowRate"
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// StateDial {
|
||||
// anchors.centerIn: background
|
||||
// height: background.contentItem.height
|
||||
// width: background.contentItem.width
|
||||
// visible: root.flowRateState
|
||||
// on: (actionQueue.pendingValue || powerState.value) === true
|
||||
|
||||
// thing: root.thing
|
||||
// stateName: "flowRate"
|
||||
// color: app.interfaceToColor("ventilation")
|
||||
// }
|
||||
}
|
||||
PropertyAnimation on rotation {
|
||||
running: root.powerState.value === true
|
||||
duration: 2000
|
||||
from: 0
|
||||
to: 360
|
||||
loops: Animation.Infinite
|
||||
onDurationChanged: {
|
||||
running = false;
|
||||
running = true;
|
||||
|
||||
ProgressButton {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.margins: Style.bigMargins
|
||||
size: Style.largeIconSize
|
||||
imageSource: ""
|
||||
color: Style.white
|
||||
backgroundColor: Style.accentColor
|
||||
visible: root.autoState
|
||||
busy: autoActionQueue.pendingValue ? autoActionQueue.pendingValue : (root.autoState && root.autoState.value === true)
|
||||
onClicked: autoActionQueue.sendValue(!root.autoState.value)
|
||||
|
||||
Label {
|
||||
anchors.centerIn: parent
|
||||
text: "A"
|
||||
font.pixelSize: parent.height / 2
|
||||
}
|
||||
|
||||
ActionQueue {
|
||||
id: autoActionQueue
|
||||
thing: root.thing
|
||||
stateName: "auto"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Dial {
|
||||
anchors.centerIn: parent
|
||||
height: background.contentItem.height
|
||||
width: background.contentItem.width
|
||||
visible: root.flowRateState
|
||||
on: (actionQueue.pendingValue || powerState.value) === true
|
||||
|
||||
thing: root.thing
|
||||
stateName: "flowRate"
|
||||
color: app.interfaceToColor("ventilation")
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
213
nymea-app/ui/images/calendar.svg
Normal file
213
nymea-app/ui/images/calendar.svg
Normal file
@ -0,0 +1,213 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="96"
|
||||
height="96"
|
||||
id="svg4874"
|
||||
version="1.1"
|
||||
inkscape:version="0.91+devel r"
|
||||
viewBox="0 0 96 96.000001"
|
||||
sodipodi:docname="calendar.svg">
|
||||
<defs
|
||||
id="defs4876" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="13.720701"
|
||||
inkscape:cx="25.570847"
|
||||
inkscape:cy="41.433733"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="g4780"
|
||||
showgrid="true"
|
||||
showborder="true"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="true"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:snap-bbox-edge-midpoints="true"
|
||||
inkscape:snap-bbox-midpoints="true"
|
||||
inkscape:object-paths="true"
|
||||
inkscape:snap-intersection-paths="true"
|
||||
inkscape:object-nodes="true"
|
||||
inkscape:snap-smooth-nodes="true"
|
||||
inkscape:snap-midpoints="true"
|
||||
inkscape:snap-object-midpoints="true"
|
||||
inkscape:snap-center="true"
|
||||
showguides="true"
|
||||
inkscape:guide-bbox="true"
|
||||
inkscape:snap-global="true">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid5451"
|
||||
empspacing="8" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="8,-8.0000001"
|
||||
id="guide4063"
|
||||
inkscape:locked="false" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="4,-8.0000001"
|
||||
id="guide4065"
|
||||
inkscape:locked="false" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="-8,88.000001"
|
||||
id="guide4067"
|
||||
inkscape:locked="false" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="-8,92.000001"
|
||||
id="guide4069"
|
||||
inkscape:locked="false" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="104,4"
|
||||
id="guide4071"
|
||||
inkscape:locked="false" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="-5,8.0000001"
|
||||
id="guide4073"
|
||||
inkscape:locked="false" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="92,-8.0000001"
|
||||
id="guide4075"
|
||||
inkscape:locked="false" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="88,-8.0000001"
|
||||
id="guide4077"
|
||||
inkscape:locked="false" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="-8,84.000001"
|
||||
id="guide4074"
|
||||
inkscape:locked="false" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="12,-8.0000001"
|
||||
id="guide4076"
|
||||
inkscape:locked="false" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="-5,12"
|
||||
id="guide4078"
|
||||
inkscape:locked="false" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="84,-9.0000001"
|
||||
id="guide4080"
|
||||
inkscape:locked="false" />
|
||||
<sodipodi:guide
|
||||
position="48,-8.0000001"
|
||||
orientation="1,0"
|
||||
id="guide4170"
|
||||
inkscape:locked="false" />
|
||||
<sodipodi:guide
|
||||
position="-8,48"
|
||||
orientation="0,1"
|
||||
id="guide4172"
|
||||
inkscape:locked="false" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata4879">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(67.857146,-78.50504)">
|
||||
<g
|
||||
transform="matrix(0,-1,-1,0,373.50506,516.50504)"
|
||||
id="g4845"
|
||||
style="display:inline">
|
||||
<g
|
||||
inkscape:export-ydpi="90"
|
||||
inkscape:export-xdpi="90"
|
||||
inkscape:export-filename="next01.png"
|
||||
transform="matrix(-0.9996045,0,0,1,575.94296,-611.00001)"
|
||||
id="g4778"
|
||||
inkscape:label="Layer 1">
|
||||
<g
|
||||
transform="matrix(-1,0,0,1,575.99999,611)"
|
||||
id="g4780"
|
||||
style="display:inline">
|
||||
<rect
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:none;stroke-width:4;marker:none;enable-background:accumulate"
|
||||
id="rect4782"
|
||||
width="96.037987"
|
||||
height="96"
|
||||
x="-438.00244"
|
||||
y="345.36221"
|
||||
transform="scale(-1,1)" />
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.00079155;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="M 23.976562 8.0019531 C 18.943803 8.0601401 15.26124 7.881546 12.25 9.5429688 C 10.74438 10.37368 9.5531414 11.778707 8.8945312 13.533203 C 8.2359314 15.287699 7.9980469 17.369641 7.9980469 20 L 7.9980469 48.001953 L 7.9980469 76 C 7.9980469 78.630359 8.2359314 80.714254 8.8945312 82.46875 C 9.5531414 84.223246 10.74438 85.62632 12.25 86.457031 C 15.26124 88.118454 18.943803 87.941823 23.976562 88 L 23.988281 88 L 47.998047 88 L 72.009766 88 L 72.021484 88 C 77.054244 87.94182 80.736807 88.118454 83.748047 86.457031 C 85.253667 85.62632 86.444916 84.223246 87.103516 82.46875 C 87.762113 80.714254 88 78.630359 88 76 L 88 48.001953 L 83.998047 48.001953 L 83.998047 76 C 83.998047 78.369642 83.747742 80.022711 83.357422 81.0625 C 82.967112 82.102279 82.547973 82.550368 81.814453 82.955078 C 80.350163 83.762988 77.036307 83.941223 71.998047 84 L 71.974609 84 L 47.998047 84 L 24.021484 84 L 24 84 C 18.96042 83.941263 15.648104 83.763108 14.183594 82.955078 C 13.450074 82.550368 13.030935 82.102279 12.640625 81.0625 C 12.250305 80.022711 12 78.369642 12 76 L 12 48.001953 L 12 32 C 12 29.630358 12.250305 27.977279 12.640625 26.9375 C 13.030935 25.897711 13.450074 25.449632 14.183594 25.044922 C 15.650624 24.235492 18.967304 24.058477 24.021484 24 L 47.998047 24 L 71.998047 24 C 77.036307 24.05877 80.350163 24.237002 81.814453 25.044922 C 82.547973 25.449632 82.967112 25.897711 83.357422 26.9375 C 83.747742 27.977279 83.998047 29.630358 83.998047 32 L 83.998047 48 L 88 48 L 88 20 C 88 17.369641 87.762116 15.287699 87.103516 13.533203 C 86.444916 11.778707 85.253667 10.37368 83.748047 9.5429688 C 80.736807 7.881546 77.054244 8.0601301 72.021484 8.0019531 L 72.009766 8.0019531 L 72 8.0019531 L 72 16 L 66 16 L 66 8.0019531 L 30 8.0019531 L 30 16 L 24 16 L 24 8.0019531 L 23.988281 8.0019531 L 23.976562 8.0019531 z "
|
||||
transform="matrix(0,-1,-1.0003957,0,438.00245,441.36222)"
|
||||
id="path4410" />
|
||||
<path
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:10;marker:none;enable-background:accumulate"
|
||||
d="M 30 0 L 24 1 L 24 8 L 30 8 L 30 0 z M 72 0 L 66 1 L 66 8 L 72 8 L 72 0 z "
|
||||
transform="matrix(0,-1,-1.0003957,0,438.00245,441.36222)"
|
||||
id="path4430" />
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:12.0023737;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 387.98438,405.36133 v 12 h 12.00195 v -12 z"
|
||||
id="path4277"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:12.0023737;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 387.98438,369.36133 v 12 h 12.00195 v -12 z"
|
||||
id="path4279"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:12.0023737;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 387.98438,387.36133 v 12 h 12.00195 v -12 z"
|
||||
id="path4281"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:12.0023737;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 369.97656,405.36133 v 12 h 12.00196 v -12 z"
|
||||
id="path4283"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:12.0023737;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 369.97656,369.36133 v 12 h 12.00196 v -12 z"
|
||||
id="path4285"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:12.0023737;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 369.97656,387.36133 v 12 h 12.00196 v -12 z"
|
||||
id="path4287"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 16 KiB |
194
nymea-app/ui/images/edit-paste.svg
Normal file
194
nymea-app/ui/images/edit-paste.svg
Normal file
@ -0,0 +1,194 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="96"
|
||||
height="96"
|
||||
id="svg4874"
|
||||
version="1.1"
|
||||
inkscape:version="0.91+devel r"
|
||||
viewBox="0 0 96 96.000001"
|
||||
sodipodi:docname="edit-paste.svg">
|
||||
<defs
|
||||
id="defs4876" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="5.6199994"
|
||||
inkscape:cx="-13.932387"
|
||||
inkscape:cy="64.741981"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="g4780"
|
||||
showgrid="true"
|
||||
showborder="true"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="true"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:snap-bbox-edge-midpoints="true"
|
||||
inkscape:snap-bbox-midpoints="true"
|
||||
inkscape:object-paths="true"
|
||||
inkscape:snap-intersection-paths="true"
|
||||
inkscape:object-nodes="true"
|
||||
inkscape:snap-smooth-nodes="true"
|
||||
inkscape:snap-midpoints="true"
|
||||
inkscape:snap-object-midpoints="true"
|
||||
inkscape:snap-center="true"
|
||||
showguides="true"
|
||||
inkscape:guide-bbox="true"
|
||||
inkscape:snap-global="true">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid5451"
|
||||
empspacing="8" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="8,-8.0000001"
|
||||
id="guide4063" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="4,-8.0000001"
|
||||
id="guide4065" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="-8,88.000001"
|
||||
id="guide4067" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="-8,92.000001"
|
||||
id="guide4069" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="104,4"
|
||||
id="guide4071" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="-5,8.0000001"
|
||||
id="guide4073" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="92,-8.0000001"
|
||||
id="guide4075" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="88,-8.0000001"
|
||||
id="guide4077" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="-8,84.000001"
|
||||
id="guide4074" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="12,-8.0000001"
|
||||
id="guide4076" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="-5,12"
|
||||
id="guide4078" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="84,-9.0000001"
|
||||
id="guide4080" />
|
||||
<sodipodi:guide
|
||||
position="48,-8.0000001"
|
||||
orientation="1,0"
|
||||
id="guide4170" />
|
||||
<sodipodi:guide
|
||||
position="-8,48"
|
||||
orientation="0,1"
|
||||
id="guide4172" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata4879">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(67.857146,-78.50504)">
|
||||
<g
|
||||
transform="matrix(0,-1,-1,0,373.50506,516.50504)"
|
||||
id="g4845"
|
||||
style="display:inline">
|
||||
<g
|
||||
inkscape:export-ydpi="90"
|
||||
inkscape:export-xdpi="90"
|
||||
inkscape:export-filename="next01.png"
|
||||
transform="matrix(-0.9996045,0,0,1,575.94296,-611.00001)"
|
||||
id="g4778"
|
||||
inkscape:label="Layer 1">
|
||||
<g
|
||||
transform="matrix(-1,0,0,1,575.99999,611)"
|
||||
id="g4780"
|
||||
style="display:inline">
|
||||
<rect
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:none;stroke-width:4;marker:none;enable-background:accumulate"
|
||||
id="rect4782"
|
||||
width="96.037987"
|
||||
height="96"
|
||||
x="-438.00244"
|
||||
y="345.36221"
|
||||
transform="scale(-1,1)" />
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.00079155;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="M 27.976562 12 C 22.943803 12.058187 19.26124 11.883489 16.25 13.544922 C 14.74438 14.375633 13.553141 15.778707 12.894531 17.533203 C 12.235931 19.287699 11.998047 21.369641 11.998047 24 L 11.998047 72.001953 C 11.998047 74.632312 12.235931 76.712301 12.894531 78.466797 C 13.553141 80.221293 14.74438 81.628273 16.25 82.458984 C 18.839866 83.887918 21.985059 83.954121 26 83.982422 L 26 79.957031 C 22.120071 79.86816 19.436048 79.646109 18.183594 78.955078 C 17.450074 78.550358 17.030935 78.102289 16.640625 77.0625 C 16.250305 76.022721 16 74.371596 16 72.001953 L 16 24 C 16 21.630358 16.250305 19.977279 16.640625 18.9375 C 17.030935 17.897711 17.450074 17.449632 18.183594 17.044922 C 19.650624 16.235502 22.967304 16.058377 28.021484 16 L 44 16 C 41.76772 16 40 14.233606 40 12 L 27.988281 12 L 27.976562 12 z M 44 16 L 59.998047 16 C 65.036307 16.058767 68.350163 16.237012 69.814453 17.044922 C 70.547973 17.449632 70.967112 17.897711 71.357422 18.9375 C 71.747742 19.977279 71.998047 21.630358 71.998047 24 L 71.998047 34 L 76 34 L 76 24 C 76 21.369641 75.762116 19.287699 75.103516 17.533203 C 74.444916 15.778707 73.253667 14.375633 71.748047 13.544922 C 68.736807 11.883489 65.054244 12.058177 60.021484 12 L 60.009766 12 L 47.998047 12 C 47.998047 14.233606 46.23228 16 44 16 z M 32 80 L 32 84 L 34 84 L 34 80 L 32 80 z "
|
||||
transform="matrix(0,-1,-1.0003957,0,438.00245,441.36222)"
|
||||
id="path4412" />
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:none;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.00079107;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 345.9668,357.36133 0,52 60.02343,0 0,-2 0,-50 -60.02343,0 z m 4,4.00195 52.02148,0 0,43.99805 -52.02148,0 0,-43.99805 z"
|
||||
id="rect4154"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path4214"
|
||||
d="m 369.97549,397.36222 -4.00158,0 0,-20 4.00158,0 z"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:6;marker:none;enable-background:accumulate" />
|
||||
<path
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:6;marker:none;enable-background:accumulate"
|
||||
d="m 393.985,397.36222 -4.00158,0 0,-28.0481 4.00158,0 z"
|
||||
id="path4212"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:6;marker:none;enable-background:accumulate"
|
||||
d="m 381.98025,397.36222 -4.00158,0 0,-28.0481 4.00158,0 z"
|
||||
id="path4210"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
|
||||
d="M 32 12 L 32 24 L 56 24 L 56 12 L 47.998047 12 C 47.998047 14.233606 46.23228 16 44 16 C 41.76772 16 40 14.233606 40 12 L 32 12 z "
|
||||
transform="matrix(0,-1,-1.0003957,0,438.00245,441.36222)"
|
||||
id="rect4235" />
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 434,397.36133 c 0,4.39491 -3.60657,8 -8.00195,8 -4.39538,0 -8.00196,-3.60509 -8.00196,-8 0,-4.39492 3.60658,-7.99805 8.00196,-7.99805 4.39538,0 8.00195,3.60313 8.00195,7.99805 z m -4,0 c 0,-2.23228 -1.76746,-3.99805 -4.00195,-3.99805 -2.23449,0 -4.00196,1.76577 -4.00196,3.99805 0,2.23228 1.76747,4 4.00196,4 2.23449,0 4.00195,-1.76772 4.00195,-4 z"
|
||||
id="path4237"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 11 KiB |
175
nymea-app/ui/images/infinity.svg
Normal file
175
nymea-app/ui/images/infinity.svg
Normal file
@ -0,0 +1,175 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="96"
|
||||
height="96"
|
||||
id="svg4874"
|
||||
version="1.1"
|
||||
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
|
||||
viewBox="0 0 96 96.000001"
|
||||
sodipodi:docname="infinity.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<defs
|
||||
id="defs4876" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="6.376014"
|
||||
inkscape:cx="51.99173"
|
||||
inkscape:cy="54.893229"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="g4780"
|
||||
showgrid="false"
|
||||
showborder="true"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="true"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:snap-bbox-edge-midpoints="true"
|
||||
inkscape:snap-bbox-midpoints="true"
|
||||
inkscape:object-paths="true"
|
||||
inkscape:snap-intersection-paths="true"
|
||||
inkscape:object-nodes="true"
|
||||
inkscape:snap-smooth-nodes="true"
|
||||
inkscape:snap-midpoints="true"
|
||||
inkscape:snap-object-midpoints="true"
|
||||
inkscape:snap-center="true"
|
||||
showguides="true"
|
||||
inkscape:guide-bbox="true"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:window-width="1466"
|
||||
inkscape:window-height="933"
|
||||
inkscape:window-x="70"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid5451"
|
||||
empspacing="8" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="8,-8.0000001"
|
||||
id="guide4063" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="4,-8.0000001"
|
||||
id="guide4065" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="-8,88.000001"
|
||||
id="guide4067" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="-8,92.000001"
|
||||
id="guide4069" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="104,4"
|
||||
id="guide4071" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="-5,8.0000001"
|
||||
id="guide4073" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="92,-8.0000001"
|
||||
id="guide4075" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="88,-8.0000001"
|
||||
id="guide4077" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="-8,84.000001"
|
||||
id="guide4074" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="12,-8.0000001"
|
||||
id="guide4076" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="-5,12"
|
||||
id="guide4078" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="84,-9.0000001"
|
||||
id="guide4080" />
|
||||
<sodipodi:guide
|
||||
position="48,-8.0000001"
|
||||
orientation="1,0"
|
||||
id="guide4170" />
|
||||
<sodipodi:guide
|
||||
position="-8,48"
|
||||
orientation="0,1"
|
||||
id="guide4172" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata4879">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(67.857146,-78.50504)">
|
||||
<g
|
||||
transform="matrix(0,-1,-1,0,373.50506,516.50504)"
|
||||
id="g4845"
|
||||
style="display:inline">
|
||||
<g
|
||||
inkscape:export-ydpi="90"
|
||||
inkscape:export-xdpi="90"
|
||||
inkscape:export-filename="next01.png"
|
||||
transform="matrix(-0.9996045,0,0,1,575.94296,-611.00001)"
|
||||
id="g4778"
|
||||
inkscape:label="Layer 1">
|
||||
<g
|
||||
transform="matrix(-1,0,0,1,575.99999,611)"
|
||||
id="g4780"
|
||||
style="display:inline">
|
||||
<rect
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:none;stroke-width:4;marker:none;enable-background:accumulate"
|
||||
id="rect4782"
|
||||
width="96.037987"
|
||||
height="96"
|
||||
x="-438.00244"
|
||||
y="345.36221"
|
||||
transform="scale(-1,1)" />
|
||||
<path
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:15px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#808080;fill-opacity:1;stroke:none"
|
||||
d="m 364.0904,368.96573 c -0.0215,-0.0161 -0.0354,-0.0404 -0.0567,-0.0566 -0.0253,-0.0201 -0.057,-0.0305 -0.0821,-0.0508 z"
|
||||
id="path4157" />
|
||||
<path
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:15px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#808080;fill-opacity:1;stroke:none"
|
||||
d="m 364.07673,417.80167 -0.13873,0.10742 c 0.0251,-0.0203 0.0569,-0.0307 0.0821,-0.0508 0.0214,-0.0162 0.0353,-0.0405 0.0567,-0.0566 z"
|
||||
id="path4344" />
|
||||
<path
|
||||
style="fill:#808080;stroke-width:0.999998;fill-opacity:1"
|
||||
d="m 371.95478,418.30264 c 0.24926,3.13425 0.93495,5.76101 2.17203,8.32048 2.71658,5.62048 7.60074,9.5375 13.08642,10.49507 3.09535,0.54034 6.30444,0.1776 9.12588,-1.03153 1.90511,-0.81642 3.57858,-1.94492 5.1622,-3.48105 0.84546,-0.82012 1.40319,-1.45284 2.08938,-2.37024 2.20576,-2.94909 3.70213,-6.6451 4.21857,-10.41992 0.16497,-1.20584 0.20743,-1.84193 0.20807,-3.11731 0.003,-4.86859 -1.26247,-9.16486 -3.84663,-13.06521 -1.62907,-2.45882 -3.79545,-4.83006 -7.06388,-7.7319 -1.22553,-1.08809 -2.78609,-2.41926 -5.0592,-4.31559 -3.23605,-2.69968 -4.96327,-4.18444 -6.38164,-5.48586 -5.23998,-4.80783 -7.56775,-8.73184 -8.24091,-13.89193 -0.10484,-0.80325 -0.12016,-3.42467 -0.0247,-4.19462 0.49014,-3.94881 2.01911,-7.24057 4.52507,-9.74207 1.44156,-1.43899 2.9441,-2.38759 4.72496,-2.98307 1.1383,-0.38061 2.11473,-0.53885 3.32492,-0.53885 0.4414,0 0.99864,0.0259 1.23824,0.0573 3.50298,0.46163 6.59486,2.53651 8.78395,5.89464 1.33995,2.05555 2.17236,4.37112 2.52957,7.03688 0.10402,0.77665 0.11877,3.44756 0.0233,4.21755 -0.2163,1.74266 -0.60748,3.31581 -1.1992,4.8225 -1.36394,3.47306 -3.61088,6.30686 -8.22394,10.37189 -0.39788,0.35063 -0.71779,0.65237 -0.71086,0.67055 0.005,0.0181 0.47695,0.4206 1.04448,0.8943 1.00693,0.84046 2.28029,1.91591 2.80817,2.37178 l 0.26291,0.227 0.19572,-0.16082 c 1.08304,-0.88997 3.8177,-3.56682 4.82972,-4.72763 1.86172,-2.1354 3.06847,-3.91218 4.08298,-6.01158 2.00131,-4.14141 2.78966,-8.89606 2.21945,-13.3861 -0.4445,-3.50021 -1.65193,-6.76005 -3.54034,-9.55823 -3.0475,-4.51569 -7.50753,-7.38573 -12.43672,-8.00302 -1.24517,-0.15591 -2.89688,-0.13478 -4.20036,0.0536 -4.82084,0.69735 -9.25001,3.67222 -12.21075,8.20137 -1.9621,3.00145 -3.1565,6.57158 -3.48147,10.40634 -0.0321,0.3782 -0.0566,1.26525 -0.0543,1.97124 0.0161,5.0741 1.40517,9.54091 4.20542,13.52364 1.1094,1.57788 2.03567,2.66636 3.71914,4.37041 1.93605,1.95977 3.53183,3.37472 8.28045,7.34227 5.70313,4.76499 7.80641,6.69629 9.61071,8.8249 2.77343,3.27197 4.23575,6.40038 4.74633,10.15421 0.13742,1.01049 0.17299,3.05173 0.0702,4.02251 -0.53219,5.01995 -2.99255,9.44138 -6.59262,11.84745 -1.9309,1.2905 -4.4554,1.96292 -6.68794,1.78137 -1.53195,-0.12456 -2.66833,-0.43993 -4.06799,-1.12884 -2.5496,-1.25492 -4.71691,-3.50238 -6.16332,-6.39123 -2.63176,-5.25627 -2.28598,-11.99321 0.88528,-17.24804 0.64901,-1.07539 1.46719,-2.17354 2.48503,-3.3354 0.96103,-1.097 3.03704,-3.112 4.6482,-4.51163 0.25855,-0.2246 0.47009,-0.42416 0.47009,-0.44346 0,-0.0545 -4.01139,-3.42983 -4.12405,-3.47024 -0.0853,-0.0307 -2.37694,2.093 -3.55767,3.29678 -2.86128,2.91713 -4.71223,5.55335 -5.99019,8.53157 -0.99109,2.30968 -1.59042,4.64899 -1.85367,7.23506 -0.0858,0.84332 -0.12342,3.0609 -0.0645,3.80242 z"
|
||||
id="path2480" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 8.3 KiB |
82
nymea-app/ui/images/sensors/window-closed.svg
Normal file
82
nymea-app/ui/images/sensors/window-closed.svg
Normal file
@ -0,0 +1,82 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
id="svg4874"
|
||||
height="96"
|
||||
viewBox="0 0 96 96.000001"
|
||||
width="96"
|
||||
version="1.1"
|
||||
sodipodi:docname="window-closed.svg"
|
||||
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<defs
|
||||
id="defs9" />
|
||||
<sodipodi:namedview
|
||||
id="namedview7"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
showgrid="true"
|
||||
inkscape:zoom="2.2194323"
|
||||
inkscape:cx="11.489425"
|
||||
inkscape:cy="68.485981"
|
||||
inkscape:window-width="1466"
|
||||
inkscape:window-height="933"
|
||||
inkscape:window-x="70"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg4874">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid861" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata4879">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
id="layer1"
|
||||
transform="translate(67.857 -78.505)">
|
||||
<rect
|
||||
id="rect4782"
|
||||
style="color:#000000;fill:none"
|
||||
transform="rotate(90)"
|
||||
height="96"
|
||||
width="96"
|
||||
y="-28.143"
|
||||
x="78.505" />
|
||||
<path
|
||||
id="path4643"
|
||||
style="color-rendering:auto;text-decoration-color:#000000;color:#000000;font-variant-numeric:normal;shape-rendering:auto;solid-color:#000000;text-decoration-line:none;fill:#808080;font-variant-position:normal;mix-blend-mode:normal;block-progression:tb;font-feature-settings:normal;shape-padding:0;font-variant-alternates:normal;text-indent:0;font-variant-caps:normal;image-rendering:auto;white-space:normal;text-decoration-style:solid;font-variant-ligatures:none;isolation:auto;text-transform:none"
|
||||
d="m-43.869 86.504-0.01172 0.002c-5.0328 0.05818-8.7136-0.12027-11.725 1.541-1.5055 0.83064-2.6968 2.2356-3.3555 3.9902-0.65866 1.7547-0.89648 3.8364-0.89648 6.4668v56.002c0 2.6304 0.23782 4.7121 0.89648 6.4668 0.65866 1.7546 1.85 3.1596 3.3555 3.9902 3.011 1.6613 6.6918 1.4848 11.725 1.543h0.01172 48.023 0.011719c5.0328-0.0582 8.7136 0.11832 11.725-1.543 1.5055-0.83064 2.6968-2.2356 3.3555-3.9902 0.65866-1.7547 0.89648-3.8364 0.89648-6.4668v-56.002c0-2.6304-0.23782-4.7121-0.89648-6.4668-0.66-1.759-1.851-3.163-3.356-3.994-3.011-1.661-6.6922-1.483-11.725-1.541l-0.011719-0.002h-48.023zm0.01172 4h48c5.0383 0.05877 8.3519 0.23688 9.8164 1.0449 0.73364 0.40478 1.1527 0.85491 1.543 1.8945 0.39025 1.0396 0.64062 2.691 0.64062 5.0605v56.002c0 2.3696-0.25037 4.0209-0.64062 5.0606-0.39025 1.0396-0.80933 1.4898-1.543 1.8945-1.4645 0.80804-4.7782 0.98616-9.8164 1.0449h-47.977-0.02344c-5.0383-0.0588-8.3519-0.23688-9.8164-1.0449-0.73364-0.40478-1.1508-0.85491-1.541-1.8945-0.39025-1.0396-0.64258-2.691-0.64258-5.0606v-56.002c0-2.3696 0.25232-4.0209 0.64258-5.0605 0.39025-1.0396 0.80738-1.4898 1.541-1.8945 1.4645-0.80804 4.7782-0.98616 9.8164-1.0449z" />
|
||||
</g>
|
||||
<path
|
||||
style="fill:none;stroke:#808080;stroke-width:4;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 21,46 46.866844,20.621709"
|
||||
id="path859" />
|
||||
<path
|
||||
style="fill:none;stroke:#808080;stroke-width:4;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 21,59.999999 59.999999,21.736601"
|
||||
id="path1215" />
|
||||
<path
|
||||
style="fill:none;stroke:#808080;stroke-width:4;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 35,60 60.866844,34.621709"
|
||||
id="path1217" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.1 KiB |
100
nymea-app/ui/images/sensors/window-open.svg
Normal file
100
nymea-app/ui/images/sensors/window-open.svg
Normal file
@ -0,0 +1,100 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
id="svg4874"
|
||||
height="96"
|
||||
viewBox="0 0 96 96.000001"
|
||||
width="96"
|
||||
version="1.1"
|
||||
sodipodi:docname="window-open.svg"
|
||||
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<defs
|
||||
id="defs9" />
|
||||
<sodipodi:namedview
|
||||
id="namedview7"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
showgrid="true"
|
||||
inkscape:zoom="2.2194323"
|
||||
inkscape:cx="11.489425"
|
||||
inkscape:cy="68.485981"
|
||||
inkscape:window-width="1466"
|
||||
inkscape:window-height="933"
|
||||
inkscape:window-x="70"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg4874">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid861" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata4879">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
id="layer1"
|
||||
transform="translate(67.857 -78.505)">
|
||||
<rect
|
||||
id="rect4782"
|
||||
style="color:#000000;fill:none"
|
||||
transform="rotate(90)"
|
||||
height="96"
|
||||
width="96"
|
||||
y="-28.143"
|
||||
x="78.505" />
|
||||
<path
|
||||
id="path4643"
|
||||
style="color-rendering:auto;text-decoration-color:#000000;color:#000000;font-variant-numeric:normal;shape-rendering:auto;solid-color:#000000;text-decoration-line:none;fill:#808080;font-variant-position:normal;mix-blend-mode:normal;block-progression:tb;font-feature-settings:normal;shape-padding:0;font-variant-alternates:normal;text-indent:0;font-variant-caps:normal;image-rendering:auto;white-space:normal;text-decoration-style:solid;font-variant-ligatures:none;isolation:auto;text-transform:none"
|
||||
d="m-43.869 86.504-0.01172 0.002c-5.0328 0.05818-8.7136-0.12027-11.725 1.541-1.5055 0.83064-2.6968 2.2356-3.3555 3.9902-0.65866 1.7547-0.89648 3.8364-0.89648 6.4668v56.002c0 2.6304 0.23782 4.7121 0.89648 6.4668 0.65866 1.7546 1.85 3.1596 3.3555 3.9902 3.011 1.6613 6.6918 1.4848 11.725 1.543h0.01172 48.023 0.011719c5.0328-0.0582 8.7136 0.11832 11.725-1.543 1.5055-0.83064 2.6968-2.2356 3.3555-3.9902 0.65866-1.7547 0.89648-3.8364 0.89648-6.4668v-56.002c0-2.6304-0.23782-4.7121-0.89648-6.4668-0.66-1.759-1.851-3.163-3.356-3.994-3.011-1.661-6.6922-1.483-11.725-1.541l-0.011719-0.002h-48.023zm0.01172 4h48c5.0383 0.05877 8.3519 0.23688 9.8164 1.0449 0.73364 0.40478 1.1527 0.85491 1.543 1.8945 0.39025 1.0396 0.64062 2.691 0.64062 5.0605v56.002c0 2.3696-0.25037 4.0209-0.64062 5.0606-0.39025 1.0396-0.80933 1.4898-1.543 1.8945-1.4645 0.80804-4.7782 0.98616-9.8164 1.0449h-47.977-0.02344c-5.0383-0.0588-8.3519-0.23688-9.8164-1.0449-0.73364-0.40478-1.1508-0.85491-1.541-1.8945-0.39025-1.0396-0.64258-2.691-0.64258-5.0606v-56.002c0-2.3696 0.25232-4.0209 0.64258-5.0605 0.39025-1.0396 0.80738-1.4898 1.541-1.8945 1.4645-0.80804 4.7782-0.98616 9.8164-1.0449z" />
|
||||
</g>
|
||||
<g
|
||||
id="g1449"
|
||||
transform="matrix(-0.78565286,0,0,0.78565286,72.177621,16.288596)"
|
||||
style="stroke-width:1.27283">
|
||||
<path
|
||||
id="path4237"
|
||||
d="m 15.500006,49.750872 h 43.749997 v -3.5 H 15.500006 Z"
|
||||
style="fill:#808080;stroke-width:1.27283"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
id="path5588-9-2-96-04"
|
||||
d="m 52.254379,37.500085 0.007,20.999999 c 3.193837,-1.460113 6.445249,-3.093913 9.755374,-4.898863 3.279237,-1.809324 6.442449,-3.675437 9.487624,-5.600262 -3.045,-1.886237 -6.208212,-3.734499 -9.487624,-5.543824 -3.312225,-1.805913 -6.565387,-3.457913 -9.760624,-4.95705 z"
|
||||
style="color:#000000;fill:#808080;stroke-width:1.27283"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
<g
|
||||
id="g1504"
|
||||
transform="matrix(0.78565286,0,0,0.78565286,23.822379,4.288596)"
|
||||
style="stroke-width:1.27283">
|
||||
<path
|
||||
id="path1500"
|
||||
d="m 15.500006,49.750872 h 43.749997 v -3.5 H 15.500006 Z"
|
||||
style="fill:#808080;stroke-width:1.27283"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
id="path1502"
|
||||
d="m 52.254379,37.500085 0.007,20.999999 c 3.193837,-1.460113 6.445249,-3.093913 9.755374,-4.898863 3.279237,-1.809324 6.442449,-3.675437 9.487624,-5.600262 -3.045,-1.886237 -6.208212,-3.734499 -9.487624,-5.543824 -3.312225,-1.805913 -6.565387,-3.457913 -9.760624,-4.95705 z"
|
||||
style="color:#000000;fill:#808080;stroke-width:1.27283"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.9 KiB |
@ -275,7 +275,7 @@ Page {
|
||||
return;
|
||||
}
|
||||
print("Rule has changed. Asking for cancellation dialog")
|
||||
var component = Qt.createComponent(Qt.resolvedUrl("../components/MeaDialog.qml"));
|
||||
var component = Qt.createComponent(Qt.resolvedUrl("../components/NymeaDialog.qml"));
|
||||
var popup = component.createObject(root, {headerIcon: "../images/question.svg",
|
||||
title: qsTr("Cancel?"),
|
||||
text: qsTr("Any changes to the rule will be lost."),
|
||||
|
||||
@ -50,7 +50,7 @@ Page {
|
||||
}
|
||||
|
||||
if ((Qt.platform.os == "android" || Qt.platform.os == "ios") && !editorSettings.popupWasShown) {
|
||||
var component = Qt.createComponent(Qt.resolvedUrl("../components/MeaDialog.qml"));
|
||||
var component = Qt.createComponent(Qt.resolvedUrl("../components/NymeaDialog.qml"));
|
||||
var infoPopup = component.createObject(root,
|
||||
{
|
||||
title: qsTr("Did you know..."),
|
||||
@ -75,7 +75,7 @@ Page {
|
||||
pageStack.pop()
|
||||
return;
|
||||
}
|
||||
var comp = Qt.createComponent("../components/MeaDialog.qml");
|
||||
var comp = Qt.createComponent("../components/NymeaDialog.qml");
|
||||
var popup = comp.createObject(root, {
|
||||
title: qsTr("Unsaved changes"),
|
||||
text: qsTr("There are unsaved changes in the script. Do you want to discard the changes?"),
|
||||
|
||||
128
nymea-app/ui/mainviews/AirConditioningView.qml
Normal file
128
nymea-app/ui/mainviews/AirConditioningView.qml
Normal file
@ -0,0 +1,128 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2022, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
* This project including source code and documentation is protected by
|
||||
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||||
* reproduction, publication, editing and translation, are reserved. The use of
|
||||
* this project is subject to the terms of a license agreement to be concluded
|
||||
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||
* under https://nymea.io/license
|
||||
*
|
||||
* GNU General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under the
|
||||
* terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, GNU version 3. This project is distributed in the hope that it
|
||||
* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this project. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* For any further details and any questions please contact us under
|
||||
* contact@nymea.io or see our FAQ/Licensing Information on
|
||||
* https://nymea.io/license/faq
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
import QtQuick 2.8
|
||||
import QtQuick.Controls 2.1
|
||||
import QtQuick.Controls.Material 2.1
|
||||
import QtQuick.Layouts 1.2
|
||||
import QtGraphicalEffects 1.0
|
||||
import QtCharts 2.2
|
||||
import Nymea 1.0
|
||||
import Nymea.AirConditioning 1.0
|
||||
import "qrc:/ui/components"
|
||||
import "qrc:/ui/delegates"
|
||||
import "airconditioning"
|
||||
|
||||
MainViewBase {
|
||||
id: root
|
||||
|
||||
contentY: flickable.contentY + topMargin
|
||||
|
||||
headerButtons: [
|
||||
{
|
||||
iconSource: "/ui/images/configure.svg",
|
||||
color: Style.iconColor,
|
||||
visible: acManager.zoneInfos.count > 0,
|
||||
trigger: function() {
|
||||
pageStack.push("airconditioning/ACSettingsPage.qml", {acManager: acManager});
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
LoggingCategory {
|
||||
id: category
|
||||
name: "AirConditioning"
|
||||
}
|
||||
|
||||
|
||||
ThingsProxy {
|
||||
id: thermostats
|
||||
engine: _engine
|
||||
shownInterfaces: ["thermostat"]
|
||||
}
|
||||
|
||||
|
||||
AirConditioningManager {
|
||||
id: acManager
|
||||
engine: _engine
|
||||
}
|
||||
|
||||
ZonesView {
|
||||
id: flickable
|
||||
anchors.fill: parent
|
||||
topMargin: root.topMargin
|
||||
bottomMargin: root.bottomMargin
|
||||
clip: true
|
||||
acManager: acManager
|
||||
}
|
||||
|
||||
|
||||
EmptyViewPlaceholder {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width - app.margins * 2
|
||||
visible: !engine.thingManager.fetchingData && (!engine.jsonRpcClient.experiences.hasOwnProperty("AirConditioning") || engine.jsonRpcClient.experiences["AirConditioning"] < "0.1")
|
||||
title: qsTr("Air conditioning plugin not installed.")
|
||||
text: qsTr("To set up air conditioning, install the air conditioning plugin.")
|
||||
imageSource: "../images/smartmeter.svg"
|
||||
buttonText: qsTr("Install A/C plugin")
|
||||
buttonVisible: packagesFilterModel.count > 0
|
||||
onButtonClicked: pageStack.push(Qt.resolvedUrl("../system/PackageListPage.qml"), {filter: "nymea-experience-plugin-airconditioning"})
|
||||
PackagesFilterModel {
|
||||
id: packagesFilterModel
|
||||
packages: engine.systemController.packages
|
||||
nameFilter: "nymea-experience-plugin-airconditioning"
|
||||
}
|
||||
}
|
||||
EmptyViewPlaceholder {
|
||||
id: noZonePlaceHolder
|
||||
anchors.centerIn: parent
|
||||
width: parent.width - app.margins * 2
|
||||
visible: engine.jsonRpcClient.experiences["AirConditioning"] >= "0.1" && acManager.zoneInfos.count == 0
|
||||
title: qsTr("No zones configured.")
|
||||
text: qsTr("Start with configuring your zones.")
|
||||
imageSource: "../images/sensors.svg"
|
||||
buttonText: qsTr("Add zone")
|
||||
onButtonClicked: {
|
||||
pendingAddCall = acManager.addZone(qsTr("Zone %1").arg(acManager.zoneInfos.count + 1), [], [], [], [])
|
||||
}
|
||||
property int pendingAddCall: -1
|
||||
|
||||
Connections {
|
||||
target: acManager
|
||||
onAddZoneReply: {
|
||||
if (commandId == noZonePlaceHolder.pendingAddCall) {
|
||||
print("zone added", zoneId)
|
||||
var zone = acManager.zoneInfos.getZoneInfo(zoneId)
|
||||
pageStack.push(Qt.resolvedUrl("airconditioning/EditZonePage.qml"), {acManager: acManager, zone: zone, createNew: true})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
777
nymea-app/ui/mainviews/airconditioning/ACChartsPage.qml
Normal file
777
nymea-app/ui/mainviews/airconditioning/ACChartsPage.qml
Normal file
@ -0,0 +1,777 @@
|
||||
import QtQuick 2.0
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Layouts 1.1
|
||||
import "qrc:/ui/components"
|
||||
import "qrc:/ui/customviews"
|
||||
import Nymea 1.0
|
||||
import Nymea.AirConditioning 1.0
|
||||
import QtCharts 2.3
|
||||
|
||||
Page {
|
||||
id: root
|
||||
property AirConditioningManager acManager: null
|
||||
property ZoneInfoWrapper zoneWrapper: null
|
||||
readonly property ZoneInfo zone: zoneWrapper.zone
|
||||
|
||||
|
||||
header: NymeaHeader {
|
||||
text: root.zone.name
|
||||
|
||||
onBackPressed: {
|
||||
pageStack.pop()
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: lineSeriesComponent
|
||||
LineSeries { }
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
|
||||
property date now: new Date()
|
||||
|
||||
property int range: 60 * 24
|
||||
|
||||
readonly property var startTime: {
|
||||
var date = new Date(now);
|
||||
date.setTime(date.getTime() - range * 60000 + 2000);
|
||||
return date;
|
||||
}
|
||||
|
||||
readonly property var endTime: {
|
||||
var date = new Date(now);
|
||||
date.setTime(date.getTime() + 2000)
|
||||
return date;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ChartView {
|
||||
id: chartView
|
||||
anchors.fill: parent
|
||||
|
||||
backgroundColor: "transparent"
|
||||
margins.left: 0
|
||||
margins.right: 0
|
||||
margins.top: 0
|
||||
margins.bottom: Style.smallIconSize + Style.margins
|
||||
|
||||
legend.visible: false
|
||||
legend.alignment: Qt.AlignBottom
|
||||
legend.font: Style.extraSmallFont
|
||||
legend.labelColor: Style.foregroundColor
|
||||
|
||||
ValueAxis {
|
||||
id: temperatureAxis
|
||||
min: 0
|
||||
max: 50
|
||||
labelFormat: ""
|
||||
gridLineColor: Style.tileOverlayColor
|
||||
labelsVisible: false
|
||||
lineVisible: false
|
||||
titleVisible: false
|
||||
shadesVisible: false
|
||||
}
|
||||
|
||||
ValueAxis {
|
||||
id: humidityAxis
|
||||
min: 0
|
||||
max: 100
|
||||
labelFormat: ""
|
||||
gridLineColor: Style.tileOverlayColor
|
||||
labelsVisible: false
|
||||
lineVisible: false
|
||||
titleVisible: false
|
||||
shadesVisible: false
|
||||
visible: false
|
||||
}
|
||||
|
||||
ValueAxis {
|
||||
id: vocAxis
|
||||
min: 0
|
||||
max: 1000
|
||||
// max: vocRepeater.count > 0 ? vocRepeater.itemAt(0).logsModel.maxValue : 0
|
||||
labelFormat: ""
|
||||
gridLineColor: Style.tileOverlayColor
|
||||
labelsVisible: false
|
||||
lineVisible: false
|
||||
titleVisible: false
|
||||
shadesVisible: false
|
||||
visible: false
|
||||
}
|
||||
|
||||
ValueAxis {
|
||||
id: boolAxis
|
||||
min: 0
|
||||
max: 1
|
||||
labelFormat: ""
|
||||
gridLineColor: Style.tileOverlayColor
|
||||
labelsVisible: false
|
||||
lineVisible: false
|
||||
titleVisible: false
|
||||
shadesVisible: false
|
||||
visible: false
|
||||
}
|
||||
|
||||
Item {
|
||||
id: labelsLayout
|
||||
x: Style.smallMargins
|
||||
y: chartView.plotArea.y
|
||||
height: chartView.plotArea.height
|
||||
width: chartView.plotArea.x - x
|
||||
Repeater {
|
||||
model: temperatureAxis.tickCount
|
||||
delegate: ColumnLayout {
|
||||
y: index == temperatureAxis.tickCount - 1
|
||||
? parent.height - height
|
||||
: index == 0
|
||||
? 0
|
||||
: parent.height / (temperatureAxis.tickCount - 1) * index - height / 2
|
||||
Label {
|
||||
width: parent.width - Style.smallMargins
|
||||
horizontalAlignment: Text.AlignRight
|
||||
text: (temperatureAxis.max - (index * temperatureAxis.max / (temperatureAxis.tickCount - 1))) + "°C"
|
||||
verticalAlignment: Text.AlignTop
|
||||
font: Style.extraSmallFont
|
||||
visible: tempRepeater.count > 0 || thermostatsRepeater.count > 0
|
||||
color: app.interfaceToColor("temperaturesensor")
|
||||
}
|
||||
Label {
|
||||
width: parent.width - Style.smallMargins
|
||||
horizontalAlignment: Text.AlignRight
|
||||
text: (humidityAxis.max - (index * humidityAxis.max / (humidityAxis.tickCount - 1))).toFixed(0) + "%"
|
||||
verticalAlignment: Text.AlignTop
|
||||
font: Style.extraSmallFont
|
||||
visible: humidityRepeater.count > 0
|
||||
color: app.interfaceToColor("humiditysensor")
|
||||
}
|
||||
Label {
|
||||
width: parent.width - Style.smallMargins
|
||||
horizontalAlignment: Text.AlignRight
|
||||
text: (vocAxis.max - (index * vocAxis.max / (vocAxis.tickCount - 1))).toFixed(0) + "ppm"
|
||||
verticalAlignment: Text.AlignTop
|
||||
font: Style.extraSmallFont
|
||||
visible: vocRepeater.count > 0
|
||||
color: app.interfaceToColor("vocsensor")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DateTimeAxis {
|
||||
id: dateTimeAxis
|
||||
min: d.startTime
|
||||
max: d.endTime
|
||||
format: {
|
||||
// switch (selectionTabs.currentValue.sampleRate) {
|
||||
// case EnergyLogs.SampleRate1Min:
|
||||
// case EnergyLogs.SampleRate15Mins:
|
||||
return "hh:mm"
|
||||
// case EnergyLogs.SampleRate1Hour:
|
||||
// case EnergyLogs.SampleRate3Hours:
|
||||
// case EnergyLogs.SampleRate1Day:
|
||||
// return "dd.MM."
|
||||
// }
|
||||
}
|
||||
tickCount: {
|
||||
// switch (selectionTabs.currentValue.sampleRate) {
|
||||
// case EnergyLogs.SampleRate1Min:
|
||||
// case EnergyLogs.SampleRate15Mins:
|
||||
// return root.width > 500 ? 13 : 7
|
||||
// case EnergyLogs.SampleRate1Hour:
|
||||
// return 7
|
||||
// case EnergyLogs.SampleRate3Hours:
|
||||
// case EnergyLogs.SampleRate1Day:
|
||||
return root.width > 500 ? 12 : 6
|
||||
// }
|
||||
}
|
||||
labelsFont: Style.extraSmallFont
|
||||
gridVisible: false
|
||||
minorGridVisible: false
|
||||
lineVisible: false
|
||||
shadesVisible: false
|
||||
labelsColor: Style.foregroundColor
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: thermostatsRepeater
|
||||
model: zoneWrapper.thermostats
|
||||
delegate: Item {
|
||||
id: thermostatDelegate
|
||||
readonly property Thing thing: zoneWrapper.thermostats.get(index)
|
||||
property XYSeries series: null
|
||||
|
||||
readonly property LogsModel logsModel: LogsModel {
|
||||
objectName: "temp: " + thing.name
|
||||
engine: typeIds.length > 0 ? _engine : null
|
||||
thingId: thing.id
|
||||
live: true
|
||||
// graphSeries: series
|
||||
viewStartTime: new Date(d.startTime.getTime() - d.range * 60000)
|
||||
|
||||
fetchBlockSize: 500
|
||||
|
||||
typeIds: {
|
||||
var ret = [];
|
||||
ret.push(thing.thingClass.stateTypes.findByName("temperature").id)
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
XYSeriesAdapter {
|
||||
logsModel: thermostatDelegate.logsModel
|
||||
xySeries: series
|
||||
sampleRate: XYSeriesAdapter.SampleRate10Minutes
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
series = chartView.createSeries(ChartView.SeriesTypeLine, thing.name, dateTimeAxis, temperatureAxis)
|
||||
series.color = app.interfaceToColor("temperaturesensor")
|
||||
series.width = 2
|
||||
|
||||
// series.opacity = Qt.binding(function() {
|
||||
// return d.selectedSeries == null || d.selectedSeries == series ? 1 : 0.3
|
||||
// })
|
||||
series.borderWidth = 0;
|
||||
series.borderColor = series.color
|
||||
}
|
||||
Component.onDestruction: {
|
||||
chartView.removeSeries(series)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: tempRepeater
|
||||
model: zoneWrapper.indoorTempSensors
|
||||
delegate: Item {
|
||||
id: tempDelegate
|
||||
readonly property Thing thing: zoneWrapper.indoorTempSensors.get(index)
|
||||
property XYSeries series: null
|
||||
|
||||
readonly property LogsModel logsModel: LogsModel {
|
||||
objectName: "temp: " + thing.name
|
||||
engine: typeIds.length > 0 ? _engine : null
|
||||
thingId: thing.id
|
||||
live: true
|
||||
// graphSeries: series
|
||||
viewStartTime: new Date(d.startTime.getTime() - d.range * 60000)
|
||||
|
||||
fetchBlockSize: 500
|
||||
|
||||
typeIds: {
|
||||
var ret = [];
|
||||
ret.push(thing.thingClass.stateTypes.findByName("temperature").id)
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
XYSeriesAdapter {
|
||||
logsModel: tempDelegate.logsModel
|
||||
xySeries: series
|
||||
sampleRate: XYSeriesAdapter.SampleRate10Minutes
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
series = chartView.createSeries(ChartView.SeriesTypeLine, thing.name, dateTimeAxis, temperatureAxis)
|
||||
series.color = app.interfaceToColor("temperaturesensor")
|
||||
series.width = 1
|
||||
|
||||
// series.opacity = Qt.binding(function() {
|
||||
// return d.selectedSeries == null || d.selectedSeries == series ? 1 : 0.3
|
||||
// })
|
||||
series.borderWidth = 0;
|
||||
series.borderColor = series.color
|
||||
}
|
||||
Component.onDestruction: {
|
||||
chartView.removeSeries(series)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: humidityRepeater
|
||||
model: zoneWrapper.indoorHumiditySensors
|
||||
delegate: Item {
|
||||
id: humidityDelegate
|
||||
readonly property Thing thing: zoneWrapper.indoorHumiditySensors.get(index)
|
||||
property XYSeries series: null
|
||||
|
||||
readonly property LogsModel logsModel: LogsModel {
|
||||
objectName: "hum: " + thing.name
|
||||
engine: typeIds.length > 0 ? _engine : null
|
||||
thingId: thing.id
|
||||
live: true
|
||||
// graphSeries: series
|
||||
viewStartTime: new Date(d.startTime.getTime() - d.range * 60000)
|
||||
fetchBlockSize: 500
|
||||
|
||||
typeIds: {
|
||||
var ret = [];
|
||||
ret.push(thing.thingClass.stateTypes.findByName("humidity").id)
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
XYSeriesAdapter {
|
||||
logsModel: humidityDelegate.logsModel
|
||||
xySeries: series
|
||||
sampleRate: XYSeriesAdapter.SampleRate10Minutes
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
series = chartView.createSeries(ChartView.SeriesTypeLine, thing.name, dateTimeAxis, humidityAxis)
|
||||
series.color = app.interfaceToColor("humiditysensor")
|
||||
series.width = 1
|
||||
// series.opacity = Qt.binding(function() {
|
||||
// return d.selectedSeries == null || d.selectedSeries == series ? 1 : 0.3
|
||||
// })
|
||||
series.borderWidth = 0;
|
||||
series.borderColor = series.color
|
||||
}
|
||||
Component.onDestruction: {
|
||||
chartView.removeSeries(series)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: vocRepeater
|
||||
model: zoneWrapper.indoorVocSensors
|
||||
delegate: Item {
|
||||
id: vocDelegate
|
||||
readonly property Thing thing: zoneWrapper.indoorVocSensors.get(index)
|
||||
property XYSeries series: null
|
||||
readonly property LogsModel logsModel: LogsModel {
|
||||
objectName: "voc: " + thing.name
|
||||
engine: typeIds.length > 0 ? _engine : null
|
||||
thingId: thing.id
|
||||
live: true
|
||||
// graphSeries: series
|
||||
viewStartTime: new Date(d.startTime.getTime() - d.range * 60000)
|
||||
fetchBlockSize: 500
|
||||
|
||||
typeIds: {
|
||||
var ret = [];
|
||||
ret.push(thing.thingClass.stateTypes.findByName("voc").id)
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
XYSeriesAdapter {
|
||||
logsModel: vocDelegate.logsModel
|
||||
xySeries: series
|
||||
sampleRate: XYSeriesAdapter.SampleRate10Minutes
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
series = chartView.createSeries(ChartView.SeriesTypeLine, thing.name, dateTimeAxis, vocAxis)
|
||||
series.color = app.interfaceToColor("vocsensor")
|
||||
series.width = 1
|
||||
// series.opacity = Qt.binding(function() {
|
||||
// return d.selectedSeries == null || d.selectedSeries == series ? 1 : 0.3
|
||||
// })
|
||||
series.borderWidth = 0;
|
||||
series.borderColor = series.color
|
||||
}
|
||||
Component.onDestruction: {
|
||||
chartView.removeSeries(series)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: zoneWrapper.windowSensors
|
||||
delegate: Item {
|
||||
id: closableDelegate
|
||||
readonly property Thing thing: zoneWrapper.windowSensors.get(index)
|
||||
property AreaSeries series: null
|
||||
|
||||
LineSeries {
|
||||
id: closableUpperSeries
|
||||
}
|
||||
LineSeries {
|
||||
id: closableLowerSeries
|
||||
XYPoint {x: dateTimeAxis.min.getTime(); y: 0}
|
||||
XYPoint {x: dateTimeAxis.max.getTime(); y: 0}
|
||||
}
|
||||
|
||||
readonly property LogsModel logsModel: LogsModel {
|
||||
id: logsModelNg
|
||||
engine: typeIds.length ? _engine : null
|
||||
thingId: thing ? thing.id : ""
|
||||
typeIds: {
|
||||
var ret = [];
|
||||
ret.push(thing.thingClass.stateTypes.findByName("closed").id)
|
||||
return ret;
|
||||
}
|
||||
live: true
|
||||
viewStartTime: new Date(d.startTime.getTime() - d.range * 60000)
|
||||
}
|
||||
|
||||
BoolSeriesAdapter {
|
||||
logsModel: closableDelegate.logsModel
|
||||
xySeries: closableUpperSeries
|
||||
inverted: true
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
series = chartView.createSeries(ChartView.SeriesTypeArea, thing.name, dateTimeAxis, boolAxis)
|
||||
series.lowerSeries = closableLowerSeries
|
||||
series.upperSeries = closableUpperSeries
|
||||
series.color = Style.green
|
||||
series.opacity = 0.1
|
||||
// series.opacity = Qt.binding(function() {
|
||||
// return d.selectedSeries == null || d.selectedSeries == series ? 1 : 0.3
|
||||
// })
|
||||
|
||||
series.borderWidth = 0;
|
||||
series.borderColor = series.color
|
||||
}
|
||||
Component.onDestruction: {
|
||||
chartView.removeSeries(series)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: zoneWrapper.thermostats.count
|
||||
delegate: Item {
|
||||
id: heatingDelegate
|
||||
readonly property Thing thing: zoneWrapper.thermostats.get(index)
|
||||
property AreaSeries series: null
|
||||
|
||||
|
||||
LineSeries {
|
||||
id: heatingUpperSeries
|
||||
}
|
||||
LineSeries {
|
||||
id: heatingLowerSeries
|
||||
XYPoint {x: dateTimeAxis.max.getTime(); y: 0}
|
||||
XYPoint {x: dateTimeAxis.min.getTime(); y: 0}
|
||||
|
||||
}
|
||||
|
||||
readonly property LogsModel logsModel: LogsModel {
|
||||
objectName: "heat: " + thing.name
|
||||
engine: typeIds.length > 0 ? _engine : null
|
||||
thingId: thing ? thing.id : ""
|
||||
typeIds: {
|
||||
var ret = [];
|
||||
var heatingOnStateType = thing.thingClass.stateTypes.findByName("heatingOn")
|
||||
print("**** has heatingOn")
|
||||
if (heatingOnStateType) {
|
||||
print("**** true")
|
||||
ret.push(heatingOnStateType.id)
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
live: true
|
||||
// graphSeries: heatingUpperSeries
|
||||
viewStartTime: dateTimeAxis.min
|
||||
}
|
||||
|
||||
BoolSeriesAdapter {
|
||||
logsModel: heatingDelegate.logsModel
|
||||
xySeries: heatingUpperSeries
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
series = chartView.createSeries(ChartView.SeriesTypeArea, thing.name, dateTimeAxis, boolAxis)
|
||||
series.lowerSeries = heatingLowerSeries
|
||||
series.upperSeries = heatingUpperSeries
|
||||
series.color = Style.red
|
||||
series.opacity = 0.1
|
||||
// series.opacity = Qt.binding(function() {
|
||||
// return d.selectedSeries == null || d.selectedSeries == series ? 1 : 0.3
|
||||
// })
|
||||
|
||||
series.borderWidth = 0;
|
||||
series.borderColor = series.color
|
||||
}
|
||||
Component.onDestruction: {
|
||||
chartView.removeSeries(series)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: chartView.plotArea.x
|
||||
anchors.topMargin: chartView.plotArea.y
|
||||
anchors.rightMargin: chartView.width - chartView.plotArea.width - chartView.plotArea.x
|
||||
anchors.bottomMargin: chartView.height - chartView.plotArea.height - chartView.plotArea.y
|
||||
|
||||
hoverEnabled: true
|
||||
preventStealing: tooltipping || dragging
|
||||
|
||||
property int startMouseX: 0
|
||||
property bool dragging: false
|
||||
property bool tooltipping: false
|
||||
|
||||
property var startDatetime: null
|
||||
|
||||
Timer {
|
||||
interval: 300
|
||||
running: mouseArea.pressed
|
||||
onTriggered: {
|
||||
if (!mouseArea.dragging) {
|
||||
mouseArea.tooltipping = true
|
||||
}
|
||||
}
|
||||
}
|
||||
onReleased: {
|
||||
mouseArea.tooltipping = false;
|
||||
|
||||
if (mouseArea.dragging) {
|
||||
mouseArea.dragging = false;
|
||||
}
|
||||
}
|
||||
|
||||
onPressed: {
|
||||
startMouseX = mouseX
|
||||
startDatetime = d.now
|
||||
}
|
||||
|
||||
|
||||
onDoubleClicked: {
|
||||
if (selectionTabs.currentIndex == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var idx = Math.ceil(mouseArea.mouseX * d.visibleValues / mouseArea.width)
|
||||
var timestamp = new Date(d.startTime.getTime() + (idx * d.sampleRate * 60000))
|
||||
selectionTabs.currentIndex--
|
||||
d.now = new Date(Math.min(new Date().getTime(), timestamp.getTime() + (d.visibleValues / 2) * d.sampleRate * 60000))
|
||||
powerBalanceLogs.fetchLogs()
|
||||
logsLoader.fetchLogs()
|
||||
}
|
||||
|
||||
onMouseXChanged: {
|
||||
if (!pressed || mouseArea.tooltipping) {
|
||||
return;
|
||||
}
|
||||
if (Math.abs(startMouseX - mouseX) < 10) {
|
||||
return;
|
||||
}
|
||||
dragging = true
|
||||
|
||||
var dragDelta = startMouseX - mouseX
|
||||
var totalTime = d.endTime.getTime() - d.startTime.getTime()
|
||||
// dragDelta : timeDelta = width : totalTime
|
||||
var timeDelta = dragDelta * totalTime / mouseArea.width
|
||||
print("dragging", dragDelta, totalTime, mouseArea.width)
|
||||
d.now = new Date(Math.min(new Date(), new Date(startDatetime.getTime() + timeDelta)))
|
||||
}
|
||||
|
||||
onWheel: {
|
||||
startDatetime = d.now
|
||||
var totalTime = d.endTime.getTime() - d.startTime.getTime()
|
||||
// pixelDelta : timeDelta = width : totalTime
|
||||
var timeDelta = wheel.pixelDelta.x * totalTime / mouseArea.width
|
||||
print("wheeling", wheel.pixelDelta.x, totalTime, mouseArea.width)
|
||||
d.now = new Date(Math.min(new Date(), new Date(startDatetime.getTime() - timeDelta)))
|
||||
wheelStopTimer.restart()
|
||||
}
|
||||
Timer {
|
||||
id: wheelStopTimer
|
||||
interval: 300
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
// for (var i = 0; i < consumersRepeater.count; i++) {
|
||||
// if (consumersRepeater.itemAt(i).logs.fetchingData) {
|
||||
// wheelStopTimer.start()
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
|
||||
// powerBalanceLogs.fetchLogs()
|
||||
// logsLoader.fetchLogs()
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
height: parent.height
|
||||
width: 1
|
||||
color: Style.foregroundColor
|
||||
x: Math.min(mouseArea.width - 1, Math.max(0, mouseArea.mouseX))
|
||||
visible: (mouseArea.containsMouse || mouseArea.tooltipping) && !mouseArea.dragging
|
||||
}
|
||||
|
||||
Item {
|
||||
id: tooltips
|
||||
anchors.fill: parent
|
||||
property var timestamp: new Date(((d.endTime.getTime() - d.startTime.getTime()) * mouseArea.mouseX / mouseArea.width) + d.startTime.getTime())
|
||||
property int xOnRight: Math.max(0, mouseArea.mouseX) + Style.smallMargins
|
||||
property int xOnLeft: Math.min(mouseArea.width, mouseArea.mouseX) - Style.smallMargins - tooltipWidth
|
||||
|
||||
property int tooltipWidth: 130
|
||||
property int tooltipX: xOnLeft < 0 ? xOnRight : xOnLeft
|
||||
|
||||
onTimestampChanged: {
|
||||
updateTimer.start();
|
||||
}
|
||||
Timer {
|
||||
id: updateTimer
|
||||
interval: 0
|
||||
onTriggered: tooltips.update()
|
||||
}
|
||||
|
||||
function update() {
|
||||
var ordered = []
|
||||
insert(thermostatTooltipRepeater, ordered);
|
||||
insert(tempTooltipRepeater, ordered);
|
||||
insert(humidityTooltipRepeater, ordered);
|
||||
insert(vocTooltipRepeater, ordered);
|
||||
|
||||
for (var i = ordered.length - 1; i >= 0; i--) {
|
||||
var item = ordered[i]
|
||||
var newY = item.realY
|
||||
|
||||
if (i < ordered.length-1) {
|
||||
var previous = ordered[i+1]
|
||||
newY = Math.min(newY, previous.fixedY - item.height/* - Style.extraSmallMargins*/)
|
||||
}
|
||||
|
||||
ordered[i].fixedY = newY
|
||||
}
|
||||
}
|
||||
|
||||
function insert(repeater, array) {
|
||||
for (var i = 0; i < repeater.count; i++) {
|
||||
var item = repeater.itemAt(i);
|
||||
var insertIdx = 0;
|
||||
while (array.length > insertIdx && item.realY > array[insertIdx].realY) {
|
||||
insertIdx++
|
||||
}
|
||||
array.splice(insertIdx, 0, item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Repeater {
|
||||
id: thermostatTooltipRepeater
|
||||
model: thermostatsRepeater.count
|
||||
|
||||
delegate: TooltipDelegate {
|
||||
visible: (mouseArea.containsMouse || mouseArea.tooltipping) && !mouseArea.dragging
|
||||
thing: thermostatsRepeater.itemAt(index).thing
|
||||
entry: thermostatsRepeater.itemAt(index).logsModel.findClosest(tooltips.timestamp)
|
||||
color: app.interfaceToColor("temperaturesensor")
|
||||
axis: temperatureAxis
|
||||
x: tooltips.tooltipX
|
||||
width: tooltips.tooltipWidth
|
||||
backgroundItem: chartView
|
||||
backgroundRect: Qt.rect(mouseArea.x + x, mouseArea.y + y, width, height)
|
||||
unit: Types.UnitDegreeCelsius
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: tempTooltipRepeater
|
||||
model: tempRepeater.count
|
||||
|
||||
delegate: TooltipDelegate {
|
||||
visible: (mouseArea.containsMouse || mouseArea.tooltipping) && !mouseArea.dragging
|
||||
thing: tempRepeater.itemAt(index).thing
|
||||
entry: tempRepeater.itemAt(index).logsModel.findClosest(tooltips.timestamp)
|
||||
color: app.interfaceToColor("temperaturesensor")
|
||||
axis: temperatureAxis
|
||||
x: tooltips.tooltipX
|
||||
width: tooltips.tooltipWidth
|
||||
backgroundItem: chartView
|
||||
backgroundRect: Qt.rect(mouseArea.x + x, mouseArea.y + y, width, height)
|
||||
unit: Types.UnitDegreeCelsius
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: humidityTooltipRepeater
|
||||
model: humidityRepeater.count
|
||||
|
||||
delegate: TooltipDelegate {
|
||||
visible: (mouseArea.containsMouse || mouseArea.tooltipping) && !mouseArea.dragging
|
||||
thing: humidityRepeater.itemAt(index).thing
|
||||
entry: humidityRepeater.itemAt(index).logsModel.findClosest(tooltips.timestamp)
|
||||
color: app.interfaceToColor("humiditysensor")
|
||||
axis: humidityAxis
|
||||
x: tooltips.tooltipX
|
||||
width: tooltips.tooltipWidth
|
||||
backgroundItem: chartView
|
||||
backgroundRect: Qt.rect(mouseArea.x + x, mouseArea.y + y, width, height)
|
||||
unit: Types.UnitPercentage
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: vocTooltipRepeater
|
||||
model: vocRepeater.count
|
||||
|
||||
delegate: TooltipDelegate {
|
||||
visible: (mouseArea.containsMouse || mouseArea.tooltipping) && !mouseArea.dragging
|
||||
thing: vocRepeater.itemAt(index).thing
|
||||
entry: vocRepeater.itemAt(index).logsModel.findClosest(tooltips.timestamp)
|
||||
color: app.interfaceToColor("vocsensor")
|
||||
axis: vocAxis
|
||||
x: tooltips.tooltipX
|
||||
width: tooltips.tooltipWidth
|
||||
backgroundItem: chartView
|
||||
backgroundRect: Qt.rect(mouseArea.x + x, mouseArea.y + y, width, height)
|
||||
unit: Types.UnitPartsPerMillion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
RowLayout {
|
||||
id: legend
|
||||
anchors { left: parent.left; bottom: parent.bottom; right: parent.right }
|
||||
anchors.leftMargin: chartView.plotArea.x
|
||||
height: Style.smallIconSize
|
||||
anchors.margins: Style.margins
|
||||
|
||||
Repeater {
|
||||
model: thermostatsRepeater.count
|
||||
delegate: LegendDelegate {
|
||||
thing: thermostatsRepeater.itemAt(index).thing
|
||||
iconName: app.interfaceToIcon("thermostat")
|
||||
color: app.interfaceToColor("temperaturesensor")
|
||||
}
|
||||
}
|
||||
Repeater {
|
||||
model: tempRepeater.count
|
||||
delegate: LegendDelegate {
|
||||
thing: tempRepeater.itemAt(index).thing
|
||||
iconName: app.interfaceToIcon("temperaturesensor")
|
||||
color: app.interfaceToColor("temperaturesensor")
|
||||
}
|
||||
}
|
||||
Repeater {
|
||||
model: humidityRepeater.count
|
||||
delegate: LegendDelegate {
|
||||
thing: humidityRepeater.itemAt(index).thing
|
||||
iconName: app.interfaceToIcon("humiditysensor")
|
||||
color: app.interfaceToColor("humiditysensor")
|
||||
}
|
||||
}
|
||||
Repeater {
|
||||
model: vocRepeater.count
|
||||
delegate: LegendDelegate {
|
||||
thing: vocRepeater.itemAt(index).thing
|
||||
iconName: app.interfaceToIcon("vocsensor")
|
||||
color: app.interfaceToColor("vocsensor")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
777
nymea-app/ui/mainviews/airconditioning/ACChartsPage2.qml
Normal file
777
nymea-app/ui/mainviews/airconditioning/ACChartsPage2.qml
Normal file
@ -0,0 +1,777 @@
|
||||
import QtQuick 2.0
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Layouts 1.1
|
||||
import "qrc:/ui/components"
|
||||
import "qrc:/ui/customviews"
|
||||
import Nymea 1.0
|
||||
import Nymea.AirConditioning 1.0
|
||||
import QtCharts 2.3
|
||||
|
||||
Page {
|
||||
id: root
|
||||
property AirConditioningManager acManager: null
|
||||
property ZoneInfoWrapper zoneWrapper: null
|
||||
readonly property ZoneInfo zone: zoneWrapper.zone
|
||||
|
||||
|
||||
header: NymeaHeader {
|
||||
text: root.zone.name
|
||||
|
||||
onBackPressed: {
|
||||
pageStack.pop()
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: lineSeriesComponent
|
||||
LineSeries { }
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
|
||||
property date now: new Date()
|
||||
|
||||
property int range: 60 * 24
|
||||
|
||||
readonly property var startTime: {
|
||||
var date = new Date(now);
|
||||
date.setTime(date.getTime() - range * 60000 + 2000);
|
||||
return date;
|
||||
}
|
||||
|
||||
readonly property var endTime: {
|
||||
var date = new Date(now);
|
||||
date.setTime(date.getTime() + 2000)
|
||||
return date;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ChartView {
|
||||
id: chartView
|
||||
anchors.fill: parent
|
||||
|
||||
backgroundColor: "transparent"
|
||||
margins.left: 0
|
||||
margins.right: 0
|
||||
margins.top: 0
|
||||
margins.bottom: Style.smallIconSize + Style.margins
|
||||
|
||||
legend.visible: false
|
||||
legend.alignment: Qt.AlignBottom
|
||||
legend.font: Style.extraSmallFont
|
||||
legend.labelColor: Style.foregroundColor
|
||||
|
||||
ValueAxis {
|
||||
id: temperatureAxis
|
||||
min: 0
|
||||
max: 50
|
||||
labelFormat: ""
|
||||
gridLineColor: Style.tileOverlayColor
|
||||
labelsVisible: false
|
||||
lineVisible: false
|
||||
titleVisible: false
|
||||
shadesVisible: false
|
||||
}
|
||||
|
||||
ValueAxis {
|
||||
id: humidityAxis
|
||||
min: 0
|
||||
max: 100
|
||||
labelFormat: ""
|
||||
gridLineColor: Style.tileOverlayColor
|
||||
labelsVisible: false
|
||||
lineVisible: false
|
||||
titleVisible: false
|
||||
shadesVisible: false
|
||||
visible: false
|
||||
}
|
||||
|
||||
ValueAxis {
|
||||
id: vocAxis
|
||||
min: 0
|
||||
max: 1000
|
||||
// max: vocRepeater.count > 0 ? vocRepeater.itemAt(0).logsModel.maxValue : 0
|
||||
labelFormat: ""
|
||||
gridLineColor: Style.tileOverlayColor
|
||||
labelsVisible: false
|
||||
lineVisible: false
|
||||
titleVisible: false
|
||||
shadesVisible: false
|
||||
visible: false
|
||||
}
|
||||
|
||||
ValueAxis {
|
||||
id: boolAxis
|
||||
min: 0
|
||||
max: 1
|
||||
labelFormat: ""
|
||||
gridLineColor: Style.tileOverlayColor
|
||||
labelsVisible: false
|
||||
lineVisible: false
|
||||
titleVisible: false
|
||||
shadesVisible: false
|
||||
visible: false
|
||||
}
|
||||
|
||||
Item {
|
||||
id: labelsLayout
|
||||
x: Style.smallMargins
|
||||
y: chartView.plotArea.y
|
||||
height: chartView.plotArea.height
|
||||
width: chartView.plotArea.x - x
|
||||
Repeater {
|
||||
model: temperatureAxis.tickCount
|
||||
delegate: ColumnLayout {
|
||||
y: index == temperatureAxis.tickCount - 1
|
||||
? parent.height - height
|
||||
: index == 0
|
||||
? 0
|
||||
: parent.height / (temperatureAxis.tickCount - 1) * index - height / 2
|
||||
Label {
|
||||
width: parent.width - Style.smallMargins
|
||||
horizontalAlignment: Text.AlignRight
|
||||
text: (temperatureAxis.max - (index * temperatureAxis.max / (temperatureAxis.tickCount - 1))) + "°C"
|
||||
verticalAlignment: Text.AlignTop
|
||||
font: Style.extraSmallFont
|
||||
visible: tempRepeater.count > 0 || thermostatsRepeater.count > 0
|
||||
color: app.interfaceToColor("temperaturesensor")
|
||||
}
|
||||
Label {
|
||||
width: parent.width - Style.smallMargins
|
||||
horizontalAlignment: Text.AlignRight
|
||||
text: (humidityAxis.max - (index * humidityAxis.max / (humidityAxis.tickCount - 1))).toFixed(0) + "%"
|
||||
verticalAlignment: Text.AlignTop
|
||||
font: Style.extraSmallFont
|
||||
visible: humidityRepeater.count > 0
|
||||
color: app.interfaceToColor("humiditysensor")
|
||||
}
|
||||
Label {
|
||||
width: parent.width - Style.smallMargins
|
||||
horizontalAlignment: Text.AlignRight
|
||||
text: (vocAxis.max - (index * vocAxis.max / (vocAxis.tickCount - 1))).toFixed(0) + "ppm"
|
||||
verticalAlignment: Text.AlignTop
|
||||
font: Style.extraSmallFont
|
||||
visible: vocRepeater.count > 0
|
||||
color: app.interfaceToColor("vocsensor")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DateTimeAxis {
|
||||
id: dateTimeAxis
|
||||
min: d.startTime
|
||||
max: d.endTime
|
||||
format: {
|
||||
// switch (selectionTabs.currentValue.sampleRate) {
|
||||
// case EnergyLogs.SampleRate1Min:
|
||||
// case EnergyLogs.SampleRate15Mins:
|
||||
return "hh:mm"
|
||||
// case EnergyLogs.SampleRate1Hour:
|
||||
// case EnergyLogs.SampleRate3Hours:
|
||||
// case EnergyLogs.SampleRate1Day:
|
||||
// return "dd.MM."
|
||||
// }
|
||||
}
|
||||
tickCount: {
|
||||
// switch (selectionTabs.currentValue.sampleRate) {
|
||||
// case EnergyLogs.SampleRate1Min:
|
||||
// case EnergyLogs.SampleRate15Mins:
|
||||
// return root.width > 500 ? 13 : 7
|
||||
// case EnergyLogs.SampleRate1Hour:
|
||||
// return 7
|
||||
// case EnergyLogs.SampleRate3Hours:
|
||||
// case EnergyLogs.SampleRate1Day:
|
||||
return root.width > 500 ? 12 : 6
|
||||
// }
|
||||
}
|
||||
labelsFont: Style.extraSmallFont
|
||||
gridVisible: false
|
||||
minorGridVisible: false
|
||||
lineVisible: false
|
||||
shadesVisible: false
|
||||
labelsColor: Style.foregroundColor
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: thermostatsRepeater
|
||||
model: zoneWrapper.thermostats
|
||||
delegate: Item {
|
||||
id: thermostatDelegate
|
||||
readonly property Thing thing: zoneWrapper.thermostats.get(index)
|
||||
property XYSeries series: null
|
||||
|
||||
readonly property LogsModel logsModel: LogsModel {
|
||||
objectName: "temp: " + thing.name
|
||||
engine: typeIds.length > 0 ? _engine : null
|
||||
thingId: thing.id
|
||||
live: true
|
||||
// graphSeries: series
|
||||
viewStartTime: new Date(d.startTime.getTime() - d.range * 60000)
|
||||
|
||||
fetchBlockSize: 500
|
||||
|
||||
typeIds: {
|
||||
var ret = [];
|
||||
ret.push(thing.thingClass.stateTypes.findByName("temperature").id)
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
XYSeriesAdapter {
|
||||
logsModel: thermostatDelegate.logsModel
|
||||
xySeries: series
|
||||
sampleRate: XYSeriesAdapter.SampleRate10Minutes
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
series = chartView.createSeries(ChartView.SeriesTypeLine, thing.name, dateTimeAxis, temperatureAxis)
|
||||
series.color = app.interfaceToColor("temperaturesensor")
|
||||
series.width = 2
|
||||
|
||||
// series.opacity = Qt.binding(function() {
|
||||
// return d.selectedSeries == null || d.selectedSeries == series ? 1 : 0.3
|
||||
// })
|
||||
series.borderWidth = 0;
|
||||
series.borderColor = series.color
|
||||
}
|
||||
Component.onDestruction: {
|
||||
chartView.removeSeries(series)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: tempRepeater
|
||||
model: zoneWrapper.indoorTempSensors
|
||||
delegate: Item {
|
||||
id: tempDelegate
|
||||
readonly property Thing thing: zoneWrapper.indoorTempSensors.get(index)
|
||||
property XYSeries series: null
|
||||
|
||||
readonly property LogsModel logsModel: LogsModel {
|
||||
objectName: "temp: " + thing.name
|
||||
engine: typeIds.length > 0 ? _engine : null
|
||||
thingId: thing.id
|
||||
live: true
|
||||
// graphSeries: series
|
||||
viewStartTime: new Date(d.startTime.getTime() - d.range * 60000)
|
||||
|
||||
fetchBlockSize: 500
|
||||
|
||||
typeIds: {
|
||||
var ret = [];
|
||||
ret.push(thing.thingClass.stateTypes.findByName("temperature").id)
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
XYSeriesAdapter {
|
||||
logsModel: tempDelegate.logsModel
|
||||
xySeries: series
|
||||
sampleRate: XYSeriesAdapter.SampleRate10Minutes
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
series = chartView.createSeries(ChartView.SeriesTypeLine, thing.name, dateTimeAxis, temperatureAxis)
|
||||
series.color = app.interfaceToColor("temperaturesensor")
|
||||
series.width = 1
|
||||
|
||||
// series.opacity = Qt.binding(function() {
|
||||
// return d.selectedSeries == null || d.selectedSeries == series ? 1 : 0.3
|
||||
// })
|
||||
series.borderWidth = 0;
|
||||
series.borderColor = series.color
|
||||
}
|
||||
Component.onDestruction: {
|
||||
chartView.removeSeries(series)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: humidityRepeater
|
||||
model: zoneWrapper.indoorHumiditySensors
|
||||
delegate: Item {
|
||||
id: humidityDelegate
|
||||
readonly property Thing thing: zoneWrapper.indoorHumiditySensors.get(index)
|
||||
property XYSeries series: null
|
||||
|
||||
readonly property LogsModel logsModel: LogsModel {
|
||||
objectName: "hum: " + thing.name
|
||||
engine: typeIds.length > 0 ? _engine : null
|
||||
thingId: thing.id
|
||||
live: true
|
||||
// graphSeries: series
|
||||
viewStartTime: new Date(d.startTime.getTime() - d.range * 60000)
|
||||
fetchBlockSize: 500
|
||||
|
||||
typeIds: {
|
||||
var ret = [];
|
||||
ret.push(thing.thingClass.stateTypes.findByName("humidity").id)
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
XYSeriesAdapter {
|
||||
logsModel: humidityDelegate.logsModel
|
||||
xySeries: series
|
||||
sampleRate: XYSeriesAdapter.SampleRate10Minutes
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
series = chartView.createSeries(ChartView.SeriesTypeLine, thing.name, dateTimeAxis, humidityAxis)
|
||||
series.color = app.interfaceToColor("humiditysensor")
|
||||
series.width = 1
|
||||
// series.opacity = Qt.binding(function() {
|
||||
// return d.selectedSeries == null || d.selectedSeries == series ? 1 : 0.3
|
||||
// })
|
||||
series.borderWidth = 0;
|
||||
series.borderColor = series.color
|
||||
}
|
||||
Component.onDestruction: {
|
||||
chartView.removeSeries(series)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: vocRepeater
|
||||
model: zoneWrapper.indoorVocSensors
|
||||
delegate: Item {
|
||||
id: vocDelegate
|
||||
readonly property Thing thing: zoneWrapper.indoorVocSensors.get(index)
|
||||
property XYSeries series: null
|
||||
readonly property LogsModel logsModel: LogsModel {
|
||||
objectName: "voc: " + thing.name
|
||||
engine: typeIds.length > 0 ? _engine : null
|
||||
thingId: thing.id
|
||||
live: true
|
||||
// graphSeries: series
|
||||
viewStartTime: new Date(d.startTime.getTime() - d.range * 60000)
|
||||
fetchBlockSize: 500
|
||||
|
||||
typeIds: {
|
||||
var ret = [];
|
||||
ret.push(thing.thingClass.stateTypes.findByName("voc").id)
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
XYSeriesAdapter {
|
||||
logsModel: vocDelegate.logsModel
|
||||
xySeries: series
|
||||
sampleRate: XYSeriesAdapter.SampleRate10Minutes
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
series = chartView.createSeries(ChartView.SeriesTypeLine, thing.name, dateTimeAxis, vocAxis)
|
||||
series.color = app.interfaceToColor("vocsensor")
|
||||
series.width = 1
|
||||
// series.opacity = Qt.binding(function() {
|
||||
// return d.selectedSeries == null || d.selectedSeries == series ? 1 : 0.3
|
||||
// })
|
||||
series.borderWidth = 0;
|
||||
series.borderColor = series.color
|
||||
}
|
||||
Component.onDestruction: {
|
||||
chartView.removeSeries(series)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: zoneWrapper.windowSensors
|
||||
delegate: Item {
|
||||
id: closableDelegate
|
||||
readonly property Thing thing: zoneWrapper.windowSensors.get(index)
|
||||
property AreaSeries series: null
|
||||
|
||||
LineSeries {
|
||||
id: closableUpperSeries
|
||||
}
|
||||
LineSeries {
|
||||
id: closableLowerSeries
|
||||
XYPoint {x: dateTimeAxis.min.getTime(); y: 0}
|
||||
XYPoint {x: dateTimeAxis.max.getTime(); y: 0}
|
||||
}
|
||||
|
||||
readonly property LogsModel logsModel: LogsModel {
|
||||
id: logsModelNg
|
||||
engine: typeIds.length ? _engine : null
|
||||
thingId: thing ? thing.id : ""
|
||||
typeIds: {
|
||||
var ret = [];
|
||||
ret.push(thing.thingClass.stateTypes.findByName("closed").id)
|
||||
return ret;
|
||||
}
|
||||
live: true
|
||||
viewStartTime: new Date(d.startTime.getTime() - d.range * 60000)
|
||||
}
|
||||
|
||||
BoolSeriesAdapter {
|
||||
logsModel: closableDelegate.logsModel
|
||||
xySeries: closableUpperSeries
|
||||
inverted: true
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
series = chartView.createSeries(ChartView.SeriesTypeArea, thing.name, dateTimeAxis, boolAxis)
|
||||
series.lowerSeries = closableLowerSeries
|
||||
series.upperSeries = closableUpperSeries
|
||||
series.color = Style.green
|
||||
series.opacity = 0.1
|
||||
// series.opacity = Qt.binding(function() {
|
||||
// return d.selectedSeries == null || d.selectedSeries == series ? 1 : 0.3
|
||||
// })
|
||||
|
||||
series.borderWidth = 0;
|
||||
series.borderColor = series.color
|
||||
}
|
||||
Component.onDestruction: {
|
||||
chartView.removeSeries(series)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: zoneWrapper.thermostats.count
|
||||
delegate: Item {
|
||||
id: heatingDelegate
|
||||
readonly property Thing thing: zoneWrapper.thermostats.get(index)
|
||||
property AreaSeries series: null
|
||||
|
||||
|
||||
LineSeries {
|
||||
id: heatingUpperSeries
|
||||
}
|
||||
LineSeries {
|
||||
id: heatingLowerSeries
|
||||
XYPoint {x: dateTimeAxis.max.getTime(); y: 0}
|
||||
XYPoint {x: dateTimeAxis.min.getTime(); y: 0}
|
||||
|
||||
}
|
||||
|
||||
readonly property LogsModel logsModel: LogsModel {
|
||||
objectName: "heat: " + thing.name
|
||||
engine: typeIds.length > 0 ? _engine : null
|
||||
thingId: thing ? thing.id : ""
|
||||
typeIds: {
|
||||
var ret = [];
|
||||
var heatingOnStateType = thing.thingClass.stateTypes.findByName("heatingOn")
|
||||
print("**** has heatingOn")
|
||||
if (heatingOnStateType) {
|
||||
print("**** true")
|
||||
ret.push(heatingOnStateType.id)
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
live: true
|
||||
// graphSeries: heatingUpperSeries
|
||||
viewStartTime: dateTimeAxis.min
|
||||
}
|
||||
|
||||
BoolSeriesAdapter {
|
||||
logsModel: heatingDelegate.logsModel
|
||||
xySeries: heatingUpperSeries
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
series = chartView.createSeries(ChartView.SeriesTypeArea, thing.name, dateTimeAxis, boolAxis)
|
||||
series.lowerSeries = heatingLowerSeries
|
||||
series.upperSeries = heatingUpperSeries
|
||||
series.color = Style.red
|
||||
series.opacity = 0.1
|
||||
// series.opacity = Qt.binding(function() {
|
||||
// return d.selectedSeries == null || d.selectedSeries == series ? 1 : 0.3
|
||||
// })
|
||||
|
||||
series.borderWidth = 0;
|
||||
series.borderColor = series.color
|
||||
}
|
||||
Component.onDestruction: {
|
||||
chartView.removeSeries(series)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: chartView.plotArea.x
|
||||
anchors.topMargin: chartView.plotArea.y
|
||||
anchors.rightMargin: chartView.width - chartView.plotArea.width - chartView.plotArea.x
|
||||
anchors.bottomMargin: chartView.height - chartView.plotArea.height - chartView.plotArea.y
|
||||
|
||||
hoverEnabled: true
|
||||
preventStealing: tooltipping || dragging
|
||||
|
||||
property int startMouseX: 0
|
||||
property bool dragging: false
|
||||
property bool tooltipping: false
|
||||
|
||||
property var startDatetime: null
|
||||
|
||||
Timer {
|
||||
interval: 300
|
||||
running: mouseArea.pressed
|
||||
onTriggered: {
|
||||
if (!mouseArea.dragging) {
|
||||
mouseArea.tooltipping = true
|
||||
}
|
||||
}
|
||||
}
|
||||
onReleased: {
|
||||
mouseArea.tooltipping = false;
|
||||
|
||||
if (mouseArea.dragging) {
|
||||
mouseArea.dragging = false;
|
||||
}
|
||||
}
|
||||
|
||||
onPressed: {
|
||||
startMouseX = mouseX
|
||||
startDatetime = d.now
|
||||
}
|
||||
|
||||
|
||||
onDoubleClicked: {
|
||||
if (selectionTabs.currentIndex == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var idx = Math.ceil(mouseArea.mouseX * d.visibleValues / mouseArea.width)
|
||||
var timestamp = new Date(d.startTime.getTime() + (idx * d.sampleRate * 60000))
|
||||
selectionTabs.currentIndex--
|
||||
d.now = new Date(Math.min(new Date().getTime(), timestamp.getTime() + (d.visibleValues / 2) * d.sampleRate * 60000))
|
||||
powerBalanceLogs.fetchLogs()
|
||||
logsLoader.fetchLogs()
|
||||
}
|
||||
|
||||
onMouseXChanged: {
|
||||
if (!pressed || mouseArea.tooltipping) {
|
||||
return;
|
||||
}
|
||||
if (Math.abs(startMouseX - mouseX) < 10) {
|
||||
return;
|
||||
}
|
||||
dragging = true
|
||||
|
||||
var dragDelta = startMouseX - mouseX
|
||||
var totalTime = d.endTime.getTime() - d.startTime.getTime()
|
||||
// dragDelta : timeDelta = width : totalTime
|
||||
var timeDelta = dragDelta * totalTime / mouseArea.width
|
||||
print("dragging", dragDelta, totalTime, mouseArea.width)
|
||||
d.now = new Date(Math.min(new Date(), new Date(startDatetime.getTime() + timeDelta)))
|
||||
}
|
||||
|
||||
onWheel: {
|
||||
startDatetime = d.now
|
||||
var totalTime = d.endTime.getTime() - d.startTime.getTime()
|
||||
// pixelDelta : timeDelta = width : totalTime
|
||||
var timeDelta = wheel.pixelDelta.x * totalTime / mouseArea.width
|
||||
print("wheeling", wheel.pixelDelta.x, totalTime, mouseArea.width)
|
||||
d.now = new Date(Math.min(new Date(), new Date(startDatetime.getTime() - timeDelta)))
|
||||
wheelStopTimer.restart()
|
||||
}
|
||||
Timer {
|
||||
id: wheelStopTimer
|
||||
interval: 300
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
// for (var i = 0; i < consumersRepeater.count; i++) {
|
||||
// if (consumersRepeater.itemAt(i).logs.fetchingData) {
|
||||
// wheelStopTimer.start()
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
|
||||
// powerBalanceLogs.fetchLogs()
|
||||
// logsLoader.fetchLogs()
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
height: parent.height
|
||||
width: 1
|
||||
color: Style.foregroundColor
|
||||
x: Math.min(mouseArea.width - 1, Math.max(0, mouseArea.mouseX))
|
||||
visible: (mouseArea.containsMouse || mouseArea.tooltipping) && !mouseArea.dragging
|
||||
}
|
||||
|
||||
Item {
|
||||
id: tooltips
|
||||
anchors.fill: parent
|
||||
property var timestamp: new Date(((d.endTime.getTime() - d.startTime.getTime()) * mouseArea.mouseX / mouseArea.width) + d.startTime.getTime())
|
||||
property int xOnRight: Math.max(0, mouseArea.mouseX) + Style.smallMargins
|
||||
property int xOnLeft: Math.min(mouseArea.width, mouseArea.mouseX) - Style.smallMargins - tooltipWidth
|
||||
|
||||
property int tooltipWidth: 130
|
||||
property int tooltipX: xOnLeft < 0 ? xOnRight : xOnLeft
|
||||
|
||||
onTimestampChanged: {
|
||||
updateTimer.start();
|
||||
}
|
||||
Timer {
|
||||
id: updateTimer
|
||||
interval: 0
|
||||
onTriggered: tooltips.update()
|
||||
}
|
||||
|
||||
function update() {
|
||||
var ordered = []
|
||||
insert(thermostatTooltipRepeater, ordered);
|
||||
insert(tempTooltipRepeater, ordered);
|
||||
insert(humidityTooltipRepeater, ordered);
|
||||
insert(vocTooltipRepeater, ordered);
|
||||
|
||||
for (var i = ordered.length - 1; i >= 0; i--) {
|
||||
var item = ordered[i]
|
||||
var newY = item.realY
|
||||
|
||||
if (i < ordered.length-1) {
|
||||
var previous = ordered[i+1]
|
||||
newY = Math.min(newY, previous.fixedY - item.height/* - Style.extraSmallMargins*/)
|
||||
}
|
||||
|
||||
ordered[i].fixedY = newY
|
||||
}
|
||||
}
|
||||
|
||||
function insert(repeater, array) {
|
||||
for (var i = 0; i < repeater.count; i++) {
|
||||
var item = repeater.itemAt(i);
|
||||
var insertIdx = 0;
|
||||
while (array.length > insertIdx && item.realY > array[insertIdx].realY) {
|
||||
insertIdx++
|
||||
}
|
||||
array.splice(insertIdx, 0, item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Repeater {
|
||||
id: thermostatTooltipRepeater
|
||||
model: thermostatsRepeater.count
|
||||
|
||||
delegate: TooltipDelegate {
|
||||
visible: (mouseArea.containsMouse || mouseArea.tooltipping) && !mouseArea.dragging
|
||||
thing: thermostatsRepeater.itemAt(index).thing
|
||||
entry: thermostatsRepeater.itemAt(index).logsModel.findClosest(tooltips.timestamp)
|
||||
color: app.interfaceToColor("temperaturesensor")
|
||||
axis: temperatureAxis
|
||||
x: tooltips.tooltipX
|
||||
width: tooltips.tooltipWidth
|
||||
backgroundItem: chartView
|
||||
backgroundRect: Qt.rect(mouseArea.x + x, mouseArea.y + y, width, height)
|
||||
unit: Types.UnitDegreeCelsius
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: tempTooltipRepeater
|
||||
model: tempRepeater.count
|
||||
|
||||
delegate: TooltipDelegate {
|
||||
visible: (mouseArea.containsMouse || mouseArea.tooltipping) && !mouseArea.dragging
|
||||
thing: tempRepeater.itemAt(index).thing
|
||||
entry: tempRepeater.itemAt(index).logsModel.findClosest(tooltips.timestamp)
|
||||
color: app.interfaceToColor("temperaturesensor")
|
||||
axis: temperatureAxis
|
||||
x: tooltips.tooltipX
|
||||
width: tooltips.tooltipWidth
|
||||
backgroundItem: chartView
|
||||
backgroundRect: Qt.rect(mouseArea.x + x, mouseArea.y + y, width, height)
|
||||
unit: Types.UnitDegreeCelsius
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: humidityTooltipRepeater
|
||||
model: humidityRepeater.count
|
||||
|
||||
delegate: TooltipDelegate {
|
||||
visible: (mouseArea.containsMouse || mouseArea.tooltipping) && !mouseArea.dragging
|
||||
thing: humidityRepeater.itemAt(index).thing
|
||||
entry: humidityRepeater.itemAt(index).logsModel.findClosest(tooltips.timestamp)
|
||||
color: app.interfaceToColor("humiditysensor")
|
||||
axis: humidityAxis
|
||||
x: tooltips.tooltipX
|
||||
width: tooltips.tooltipWidth
|
||||
backgroundItem: chartView
|
||||
backgroundRect: Qt.rect(mouseArea.x + x, mouseArea.y + y, width, height)
|
||||
unit: Types.UnitPercentage
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: vocTooltipRepeater
|
||||
model: vocRepeater.count
|
||||
|
||||
delegate: TooltipDelegate {
|
||||
visible: (mouseArea.containsMouse || mouseArea.tooltipping) && !mouseArea.dragging
|
||||
thing: vocRepeater.itemAt(index).thing
|
||||
entry: vocRepeater.itemAt(index).logsModel.findClosest(tooltips.timestamp)
|
||||
color: app.interfaceToColor("vocsensor")
|
||||
axis: vocAxis
|
||||
x: tooltips.tooltipX
|
||||
width: tooltips.tooltipWidth
|
||||
backgroundItem: chartView
|
||||
backgroundRect: Qt.rect(mouseArea.x + x, mouseArea.y + y, width, height)
|
||||
unit: Types.UnitPartsPerMillion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
RowLayout {
|
||||
id: legend
|
||||
anchors { left: parent.left; bottom: parent.bottom; right: parent.right }
|
||||
anchors.leftMargin: chartView.plotArea.x
|
||||
height: Style.smallIconSize
|
||||
anchors.margins: Style.margins
|
||||
|
||||
Repeater {
|
||||
model: thermostatsRepeater.count
|
||||
delegate: LegendDelegate {
|
||||
thing: thermostatsRepeater.itemAt(index).thing
|
||||
iconName: app.interfaceToIcon("thermostat")
|
||||
color: app.interfaceToColor("temperaturesensor")
|
||||
}
|
||||
}
|
||||
Repeater {
|
||||
model: tempRepeater.count
|
||||
delegate: LegendDelegate {
|
||||
thing: tempRepeater.itemAt(index).thing
|
||||
iconName: app.interfaceToIcon("temperaturesensor")
|
||||
color: app.interfaceToColor("temperaturesensor")
|
||||
}
|
||||
}
|
||||
Repeater {
|
||||
model: humidityRepeater.count
|
||||
delegate: LegendDelegate {
|
||||
thing: humidityRepeater.itemAt(index).thing
|
||||
iconName: app.interfaceToIcon("humiditysensor")
|
||||
color: app.interfaceToColor("humiditysensor")
|
||||
}
|
||||
}
|
||||
Repeater {
|
||||
model: vocRepeater.count
|
||||
delegate: LegendDelegate {
|
||||
thing: vocRepeater.itemAt(index).thing
|
||||
iconName: app.interfaceToIcon("vocsensor")
|
||||
color: app.interfaceToColor("vocsensor")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
73
nymea-app/ui/mainviews/airconditioning/ACSettingsPage.qml
Normal file
73
nymea-app/ui/mainviews/airconditioning/ACSettingsPage.qml
Normal file
@ -0,0 +1,73 @@
|
||||
import QtQuick 2.3
|
||||
import QtQuick.Layouts 1.2
|
||||
import QtQuick.Controls 2.2
|
||||
import Nymea 1.0
|
||||
import Nymea.AirConditioning 1.0
|
||||
import "qrc:/ui/components"
|
||||
import "qrc:/ui/delegates"
|
||||
|
||||
SettingsPageBase {
|
||||
id: root
|
||||
title: qsTr("Configure zones")
|
||||
|
||||
property AirConditioningManager acManager: null
|
||||
|
||||
header: NymeaHeader {
|
||||
text: root.title
|
||||
backButtonVisible: true
|
||||
onBackPressed: pageStack.pop()
|
||||
|
||||
HeaderButton {
|
||||
imageSource: "add"
|
||||
onClicked: {
|
||||
createZone();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function createZone() {
|
||||
pendingAddCall = acManager.addZone(qsTr("Zone %1").arg(acManager.zoneInfos.count + 1), [], [], [], [])
|
||||
}
|
||||
property int pendingAddCall: -1
|
||||
Connections {
|
||||
target: acManager
|
||||
|
||||
onAddZoneReply: {
|
||||
if (commandId == pendingAddCall) {
|
||||
print("zone added", zoneId)
|
||||
var zone = acManager.zoneInfos.getZoneInfo(zoneId)
|
||||
pageStack.push(Qt.resolvedUrl("EditZonePage.qml"), {acManager: acManager, zone: zone, createNew: true})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: root.height - root.header.height
|
||||
visible: acManager.zoneInfos.count == 0
|
||||
|
||||
EmptyViewPlaceholder {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width - app.margins * 2
|
||||
title: qsTr("No zones configured.")
|
||||
text: qsTr("Start with configuring your zones.")
|
||||
imageSource: "/ui/images/sensors.svg"
|
||||
buttonText: qsTr("Add zone")
|
||||
onButtonClicked: createZone()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Repeater {
|
||||
model: acManager.zoneInfos
|
||||
|
||||
delegate: NymeaItemDelegate {
|
||||
property ZoneInfo zone: acManager.zoneInfos.get(index)
|
||||
Layout.fillWidth: true
|
||||
text: model.name
|
||||
onClicked: pageStack.push(Qt.resolvedUrl("EditZonePage.qml"), {acManager: root.acManager, zone: zone})
|
||||
}
|
||||
}
|
||||
}
|
||||
162
nymea-app/ui/mainviews/airconditioning/BigZoneStatusIcons.qml
Normal file
162
nymea-app/ui/mainviews/airconditioning/BigZoneStatusIcons.qml
Normal file
@ -0,0 +1,162 @@
|
||||
import QtQuick 2.3
|
||||
import QtQuick.Layouts 1.2
|
||||
import QtQuick.Controls 2.3
|
||||
import "qrc:/ui/components"
|
||||
import Nymea 1.0
|
||||
import NymeaApp.Utils 1.0
|
||||
import Nymea.AirConditioning 1.0
|
||||
|
||||
Item {
|
||||
id: root
|
||||
implicitHeight: layout.implicitHeight
|
||||
implicitWidth: layout.implicitWidth
|
||||
|
||||
property AirConditioningManager acManager: null
|
||||
property ZoneInfoWrapper zoneWrapper: null
|
||||
readonly property ZoneInfo zone: zoneWrapper ? zoneWrapper.zone : null
|
||||
|
||||
property int iconSize: Style.iconSize
|
||||
|
||||
signal clicked(int flag)
|
||||
|
||||
property var zoneStatusModel: [
|
||||
{
|
||||
value: ZoneInfo.ZoneStatusFlagSetpointOverrideActive,
|
||||
icon: "dial",
|
||||
color: Style.iconColor,
|
||||
activeColor: Style.accentColor,
|
||||
text: qsTr("Automatic mode"),
|
||||
activeText: qsTr("Manual mode"),
|
||||
visible: zoneWrapper.thermostats.count > 0,
|
||||
alertVisible: false,
|
||||
onClicked: function() {
|
||||
var comp = Qt.createComponent(Qt.resolvedUrl("TimeOverrideDialog.qml"))
|
||||
var dialog = comp.createObject(app, {acManager: root.acManager, zone: root.zone})
|
||||
dialog.open()
|
||||
}
|
||||
},
|
||||
{
|
||||
value: ZoneInfo.ZoneStatusFlagTimeScheduleActive,
|
||||
icon: "calendar",
|
||||
color: Style.iconColor,
|
||||
activeColor: Style.orange,
|
||||
text: qsTr("Time schedule not active"),
|
||||
activeText: qsTr("Time schedule active"),
|
||||
visible: zoneWrapper.thermostats.count > 0,
|
||||
alertVisible: false,
|
||||
onClicked: function() {
|
||||
pageStack.push(Qt.resolvedUrl("TimeSchedulePage.qml"), {acManager: root.acManager, zone: root.zone})
|
||||
}
|
||||
},
|
||||
{
|
||||
value: ZoneInfo.ZoneStatusFlagWindowOpen,
|
||||
icon: "sensors/window-closed",
|
||||
activeIcon: "sensors/window-open",
|
||||
color: Style.green,
|
||||
activeColor: Style.red,
|
||||
text: qsTr("All windows closed"),
|
||||
activeText: qsTr("%n window(s) open", "", zoneWrapper.openWindows.count),
|
||||
visible: zoneWrapper.windowSensors.count > 0,
|
||||
alertVisible: false
|
||||
},
|
||||
{
|
||||
value: ZoneInfo.ZoneStatusFlagNone,
|
||||
icon: "sensors/temperature",
|
||||
color: Style.iconColor,
|
||||
activeColor: Style.accentColor,
|
||||
text: Types.toUiValue(zoneWrapper.zoneTemperature.toFixed(1), Types.UnitDegreeCelsius) + Types.toUiUnit(Types.UnitDegreeCelsius),
|
||||
visible: zoneWrapper.indoorTempSensors.count > 0 && zoneWrapper.thermostats.count == 0,
|
||||
alertVisible: false
|
||||
},
|
||||
{
|
||||
value: ZoneInfo.ZoneStatusFlagHighHumidity,
|
||||
icon: "sensors/humidity",
|
||||
color: app.interfaceToColor("humiditysensor"),
|
||||
activeColor: app.interfaceToColor("humiditysensor"),
|
||||
text: qsTr("%1% humidity").arg(zoneWrapper.zoneHumidity.toFixed(0)),
|
||||
activeText:qsTr("%1% humidity").arg(zoneWrapper.zoneHumidity.toFixed(0)),
|
||||
visible: zoneWrapper.indoorHumiditySensors.count > 0,
|
||||
alertVisible: (root.zone.zoneStatus & ZoneInfo.ZoneStatusFlagHighHumidity) > 0
|
||||
},
|
||||
{
|
||||
value: ZoneInfo.ZoneStatusFlagBadAir,
|
||||
icon: "weathericons/weather-clouds",
|
||||
color: AirQualityIndex.currentIndex(AirQualityIndex.iaqVoc, zoneWrapper.zoneVOC).color,
|
||||
activeColor: AirQualityIndex.currentIndex(AirQualityIndex.iaqVoc, zoneWrapper.zoneVOC).color,
|
||||
text: AirQualityIndex.currentIndex(AirQualityIndex.iaqVoc, zoneWrapper.zoneVOC).text,
|
||||
activeText: AirQualityIndex.currentIndex(AirQualityIndex.iaqVoc, zoneWrapper.zoneVOC).text,// qsTr("Air quality alert!"),
|
||||
visible: zoneWrapper.indoorVocSensors.count > 0 || zoneWrapper.indoorPm25Sensors.count > 0,
|
||||
alertVisible: (root.zone.zoneStatus & ZoneInfo.ZoneStatusFlagBadAir) > 0
|
||||
}
|
||||
]
|
||||
|
||||
GridLayout {
|
||||
id: layout
|
||||
flow: GridLayout.TopToBottom
|
||||
rows: {
|
||||
var ret = 0;
|
||||
for (var i = 0; i < zoneStatusModel.length; i++) {
|
||||
var entry = zoneStatusModel[i]
|
||||
if (entry.visible) {
|
||||
ret++
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
anchors.fill: parent
|
||||
columnSpacing: Style.smallMargins
|
||||
rowSpacing: Style.smallMargins
|
||||
|
||||
Repeater {
|
||||
model: zoneStatusModel
|
||||
delegate: Item {
|
||||
Layout.fillWidth: false
|
||||
implicitHeight: root.iconSize
|
||||
implicitWidth: root.width / 3
|
||||
visible: entry.visible
|
||||
|
||||
property var entry: zoneStatusModel[index]
|
||||
property bool active: (root.zone.zoneStatus & entry.value) > 0
|
||||
ColorIcon {
|
||||
name: entry.hasOwnProperty("activeIcon") && active ? entry.activeIcon : entry.icon
|
||||
size: root.iconSize
|
||||
color: active ? entry.activeColor : Style.iconColor
|
||||
anchors.right: parent.right
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: entry.onClicked()
|
||||
}
|
||||
}
|
||||
}
|
||||
Repeater {
|
||||
model: zoneStatusModel
|
||||
|
||||
delegate: RowLayout {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: root.iconSize
|
||||
implicitWidth: 100
|
||||
visible: entry.visible
|
||||
|
||||
property var entry: zoneStatusModel[index]
|
||||
property bool active: (root.zone.zoneStatus & entry.value) > 0
|
||||
|
||||
Label {
|
||||
// Layout.alignment: Qt.AlignVCenter
|
||||
text: active ? entry.activeText : entry.text
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
ColorIcon {
|
||||
size: Style.iconSize
|
||||
name: "attention"
|
||||
color: Style.yellow
|
||||
visible: entry.alertVisible
|
||||
}
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
143
nymea-app/ui/mainviews/airconditioning/CreateZoneWizard.qml
Normal file
143
nymea-app/ui/mainviews/airconditioning/CreateZoneWizard.qml
Normal file
@ -0,0 +1,143 @@
|
||||
import QtQuick 2.0
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Layouts 1.1
|
||||
import "qrc:/ui/components"
|
||||
import "qrc:/ui/customviews"
|
||||
import Nymea 1.0
|
||||
import Nymea.AirConditioning 1.0
|
||||
|
||||
WizardPageBase {
|
||||
id: root
|
||||
property AirConditioningManager acManager: null
|
||||
title: qsTr("New zone")
|
||||
|
||||
showBackButton: true
|
||||
showExtraButton: false
|
||||
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
property var thermostats: []
|
||||
property var windowSensors: []
|
||||
property var indoorSensors: []
|
||||
property var outdoorSensors: []
|
||||
}
|
||||
|
||||
onBack: pageStack.pop();
|
||||
|
||||
onNext: {
|
||||
acManager.addZone(nameTextField.text, d.thermostats, d.windowSensors, d.indoorSensors, d.outdoorSensors)
|
||||
pageStack.pop();
|
||||
}
|
||||
|
||||
ThingsProxy {
|
||||
id: thermostatsProxy
|
||||
engine: _engine
|
||||
shownInterfaces: ["thermostat"]
|
||||
}
|
||||
ThingsProxy {
|
||||
id: windowSensorsProxy
|
||||
engine: _engine
|
||||
shownInterfaces: ["closablesensors"]
|
||||
}
|
||||
|
||||
ThingsProxy {
|
||||
id: sensorsProxy
|
||||
engine: _engine
|
||||
shownInterfaces: ["temperaturesensor", "humiditysensor", "vocsensor", "pm25sensor"]
|
||||
hiddenInterfaces: ["thermostat"]
|
||||
}
|
||||
|
||||
content: Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: root.visibleContentHeight
|
||||
|
||||
Flickable {
|
||||
id: flickable
|
||||
anchors.fill: parent
|
||||
contentHeight: contentColumn.height
|
||||
|
||||
|
||||
ColumnLayout {
|
||||
id: contentColumn
|
||||
width: flickable.width
|
||||
|
||||
SettingsPageSectionHeader {
|
||||
text: qsTr("Zone name")
|
||||
}
|
||||
|
||||
NymeaTextField {
|
||||
id: nameTextField
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: Style.margins
|
||||
Layout.rightMargin: Style.margins
|
||||
}
|
||||
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: Style.margins
|
||||
text: qsTr("Select the thermostats that should be part of this zone.")
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
Repeater {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
clip: true
|
||||
|
||||
model: thermostatsProxy
|
||||
|
||||
delegate: CheckDelegate {
|
||||
Layout.fillWidth: true
|
||||
text: model.name
|
||||
checked: d.thermostats.indexOf(model.id) >= 0
|
||||
onClicked: {
|
||||
var tmp = d.thermostats
|
||||
if (checked) {
|
||||
tmp.push(model.id)
|
||||
} else {
|
||||
var idx = tmp.indexOf(model.id);
|
||||
tmp.splice(idx, 1)
|
||||
}
|
||||
d.thermostats = tmp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: Style.margins
|
||||
text: qsTr("Select the sensors that should be part of this zone.")
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
Repeater {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
clip: true
|
||||
|
||||
model: sensorsProxy
|
||||
|
||||
delegate: CheckDelegate {
|
||||
Layout.fillWidth: true
|
||||
text: model.name
|
||||
checked: d.things.indexOf(model.id) >= 0
|
||||
onClicked: {
|
||||
var tmp = d.sensors
|
||||
if (checked) {
|
||||
tmp.push(model.id)
|
||||
} else {
|
||||
var idx = tmp.indexOf(model.id);
|
||||
tmp.splice(idx, 1)
|
||||
}
|
||||
d.sensors = tmp;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
153
nymea-app/ui/mainviews/airconditioning/EditZonePage.qml
Normal file
153
nymea-app/ui/mainviews/airconditioning/EditZonePage.qml
Normal file
@ -0,0 +1,153 @@
|
||||
import QtQuick 2.3
|
||||
import QtQuick.Layouts 1.2
|
||||
import QtQuick.Controls 2.2
|
||||
import Nymea 1.0
|
||||
import Nymea.AirConditioning 1.0
|
||||
import "qrc:/ui/components"
|
||||
import "qrc:/ui/delegates"
|
||||
|
||||
SettingsPageBase {
|
||||
id: editZonePage
|
||||
|
||||
title: qsTr("Edit %1").arg(zone.name)
|
||||
|
||||
property AirConditioningManager acManager: null
|
||||
property ZoneInfo zone: null
|
||||
property bool createNew: false
|
||||
|
||||
busy: d.pendingCommandId != -1
|
||||
QtObject {
|
||||
id: d
|
||||
property int pendingCommandId: -1
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: acManager
|
||||
onSetZoneNameReply: {
|
||||
if (commandId == d.pendingCommandId) {
|
||||
d.pendingCommandId = -1
|
||||
}
|
||||
}
|
||||
|
||||
onRemoveZoneReply: {
|
||||
if (commandId == d.pendingCommandId) {
|
||||
d.pendingCommandId = -1
|
||||
pageStack.pop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SettingsPageSectionHeader {
|
||||
text: qsTr("Zone information")
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: app.margins
|
||||
Layout.rightMargin: app.margins
|
||||
spacing: app.margins
|
||||
|
||||
Label {
|
||||
text: qsTr("Name")
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
TextField {
|
||||
id: nameTextField
|
||||
Layout.fillWidth: true
|
||||
text: zone.name
|
||||
}
|
||||
Button {
|
||||
text: qsTr("OK")
|
||||
visible: nameTextField.displayText !== zone.name
|
||||
onClicked: d.pendingCommandId = acManager.setZoneName(zone.id, nameTextField.displayText)
|
||||
}
|
||||
}
|
||||
|
||||
NymeaItemDelegate {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Assigned things")
|
||||
onClicked: pageStack.push(Qt.resolvedUrl("EditZoneThingsPage.qml"), {acManager: acManager, zone: zone})
|
||||
}
|
||||
|
||||
SettingsPageSectionHeader {
|
||||
text: qsTr("Temperature settings")
|
||||
}
|
||||
|
||||
NymeaItemDelegate {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Base temperature")
|
||||
subText: Types.toUiValue(editZonePage.zone.standbySetpoint, Types.UnitDegreeCelsius) + Types.toUiUnit(Types.UnitDegreeCelsius)
|
||||
onClicked: {
|
||||
var popup = selectBaseTempComponent.createObject(app, {zone: zone})
|
||||
popup.open()
|
||||
}
|
||||
}
|
||||
|
||||
NymeaItemDelegate {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Set time schedule")
|
||||
onClicked: {
|
||||
pageStack.push(Qt.resolvedUrl("TimeSchedulePage.qml"), {acManager: acManager, zone: zone})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// SettingsPageSectionHeader {
|
||||
// text: qsTr("Notification settings")
|
||||
// }
|
||||
|
||||
// SwitchDelegate {
|
||||
// Layout.fillWidth: true
|
||||
// text: qsTr("Bad air")
|
||||
// }
|
||||
// SwitchDelegate {
|
||||
// Layout.fillWidth: true
|
||||
// text: qsTr("Humidity")
|
||||
// }
|
||||
|
||||
Button {
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: Style.margins
|
||||
text: createNew ? qsTr("OK") : qsTr("Remove this zone")
|
||||
onClicked: {
|
||||
if (createNew) {
|
||||
pageStack.pop()
|
||||
} else {
|
||||
d.pendingCommandId = acManager.removeZone(zone.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: selectBaseTempComponent
|
||||
NymeaDialog {
|
||||
id: selectBaseTempDialog
|
||||
|
||||
property ZoneInfo zone: null
|
||||
|
||||
CircleBackground {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: width
|
||||
|
||||
Dial {
|
||||
anchors.fill: parent
|
||||
value: selectBaseTempDialog.zone.standbySetpoint
|
||||
precision: 1
|
||||
minValue: 10
|
||||
maxValue: 40
|
||||
|
||||
onMoved: {
|
||||
acManager.setZoneStandbySetpoint(zone.id, value)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
anchors.centerIn: parent
|
||||
text: Types.toUiValue(zone.standbySetpoint, Types.UnitDegreeCelsius) + Types.toUiUnit(Types.UnitDegreeCelsius)
|
||||
font: Style.bigFont
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
261
nymea-app/ui/mainviews/airconditioning/EditZoneThingsPage.qml
Normal file
261
nymea-app/ui/mainviews/airconditioning/EditZoneThingsPage.qml
Normal file
@ -0,0 +1,261 @@
|
||||
import QtQuick 2.3
|
||||
import QtQuick.Layouts 1.2
|
||||
import QtQuick.Controls 2.2
|
||||
import Nymea 1.0
|
||||
import Nymea.AirConditioning 1.0
|
||||
import "qrc:/ui/components"
|
||||
import "qrc:/ui/delegates"
|
||||
|
||||
SettingsPageBase {
|
||||
id: zoneThingsPage
|
||||
title: qsTr("Things in zone %1").arg(zone.name)
|
||||
|
||||
property AirConditioningManager acManager: null
|
||||
property ZoneInfo zone: null
|
||||
|
||||
ZoneInfoWrapper {
|
||||
id: zoneWrapper
|
||||
zone: zoneThingsPage.zone
|
||||
}
|
||||
|
||||
busy: d.pendingCommandId != -1
|
||||
QtObject {
|
||||
id: d
|
||||
property int pendingCommandId: -1
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: acManager
|
||||
onSetZoneNameReply: {
|
||||
if (commandId == d.pendingCommandId) {
|
||||
d.pendingCommandId = -1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SettingsPageSectionHeader {
|
||||
text: qsTr("Thermostats")
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: zoneWrapper.thermostats
|
||||
delegate: ThingDelegate {
|
||||
Layout.fillWidth: true
|
||||
thing: zoneWrapper.thermostats.get(index)
|
||||
progressive: false
|
||||
canDelete: true
|
||||
onDeleteClicked: {
|
||||
acManager.removeZoneThermostat(zone.id, thing.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
Button {
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: Style.margins
|
||||
text: qsTr("Add thermostat")
|
||||
onClicked: {
|
||||
var page = pageStack.push(selectThingComponent, {
|
||||
acManager: acManager,
|
||||
zone: zone,
|
||||
interfaces: ["thermostat"],
|
||||
hiddenThingIds: zone.thermostats,
|
||||
title: qsTr("Add thermostats"),
|
||||
placeHolderTitle: qsTr("No thermostats installed"),
|
||||
placeHolderText: qsTr("Before a thermostat can be assigned to this zone, it needs to be connected to nymea."),
|
||||
placeHolderButtonText: qsTr("Setup thermostats"),
|
||||
placeHolderFilterInterface: "thermostat"
|
||||
})
|
||||
page.selected.connect(function(thingId) {
|
||||
acManager.addZoneThermostat(zone.id, thingId)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
SettingsPageSectionHeader {
|
||||
text: qsTr("Window sensors")
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: zoneWrapper.windowSensors
|
||||
delegate: ThingDelegate {
|
||||
Layout.fillWidth: true
|
||||
thing: zoneWrapper.windowSensors.get(index)
|
||||
progressive: false
|
||||
canDelete: true
|
||||
onDeleteClicked: {
|
||||
acManager.removeZoneWindowSensor(zone.id, thing.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: Style.margins
|
||||
text: qsTr("Add window sensor")
|
||||
onClicked: {
|
||||
var page = pageStack.push(selectThingComponent, {
|
||||
acManager: acManager,
|
||||
zone: zone,
|
||||
interfaces: ["closablesensor"],
|
||||
hiddenThingIds: zone.windowSensors,
|
||||
title: qsTr("Add window sensors"),
|
||||
placeHolderTitle: qsTr("No window sensors installed"),
|
||||
placeHolderText: qsTr("Before a window sensor can be assigned to this zone, it needs to be connected to nymea."),
|
||||
placeHolderButtonText: qsTr("Setup window sensor"),
|
||||
placeHolderFilterInterface: "closablesensor"
|
||||
})
|
||||
page.selected.connect(function(thingId) {
|
||||
acManager.addZoneWindowSensor(zone.id, thingId)
|
||||
})
|
||||
}
|
||||
}
|
||||
SettingsPageSectionHeader {
|
||||
text: qsTr("Indoor sensors")
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: zoneWrapper.indoorSensors
|
||||
delegate: ThingDelegate {
|
||||
Layout.fillWidth: true
|
||||
thing: zoneWrapper.indoorSensors.get(index)
|
||||
progressive: false
|
||||
canDelete: true
|
||||
onDeleteClicked: {
|
||||
acManager.removeZoneIndoorSensor(zone.id, thing.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: Style.margins
|
||||
text: qsTr("Add indoor sensor")
|
||||
onClicked: {
|
||||
var page = pageStack.push(selectThingComponent, {
|
||||
acManager: acManager,
|
||||
zone: zone,
|
||||
interfaces: ["temperaturesensor", "humiditysensor", "vocsensor", "pm25sensor"],
|
||||
hiddenThingIds: zone.indoorSensors,
|
||||
title: qsTr("Add indoor sensors"),
|
||||
placeHolderTitle: qsTr("No sensors installed"),
|
||||
placeHolderText: qsTr("Before a sensor be assigned to this zone, it needs to be connected to nymea."),
|
||||
placeHolderButtonText: qsTr("Setup sensors"),
|
||||
placeHolderFilterInterface: "sensor"
|
||||
})
|
||||
page.selected.connect(function(thingId) {
|
||||
acManager.addZoneIndoorSensor(zone.id, thingId)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
SettingsPageSectionHeader {
|
||||
text: qsTr("Outdoor sensors")
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: zoneWrapper.outdoorSensors
|
||||
delegate: ThingDelegate {
|
||||
Layout.fillWidth: true
|
||||
thing: zoneWrapper.outdoorSensors.get(index)
|
||||
progressive: false
|
||||
canDelete: true
|
||||
onDeleteClicked: {
|
||||
acManager.removeZoneOutdoorSensor(zone.id, thing.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: Style.margins
|
||||
text: qsTr("Add outdoor sensor")
|
||||
onClicked: {
|
||||
var page = pageStack.push(selectThingComponent, {
|
||||
acManager: acManager,
|
||||
zone: zone,
|
||||
interfaces: ["temperaturesensor", "humiditysensor", "vocsensor", "pm25sensor"],
|
||||
hiddenThingIds: zone.outdoorSensors,
|
||||
title: qsTr("Select outdoor sensors"),
|
||||
placeHolderTitle: qsTr("No sensors installed"),
|
||||
placeHolderText: qsTr("Before a sensor be assigned to this zone, it needs to be connected to nymea."),
|
||||
placeHolderButtonText: qsTr("Setup sensors"),
|
||||
placeHolderFilterInterface: "sensor"
|
||||
})
|
||||
page.selected.connect(function(thingId) {
|
||||
acManager.addZoneOutdoorSensor(zone.id, thingId)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: selectThingComponent
|
||||
SettingsPageBase {
|
||||
id: selectThingPage
|
||||
busy: d.pendingCommandId != -1
|
||||
|
||||
property AirConditioningManager acManager: null
|
||||
property ZoneInfo zone: null
|
||||
property var interfaces: []
|
||||
property var hiddenThingIds: []
|
||||
|
||||
property alias placeHolderTitle: placeHolder.title
|
||||
property alias placeHolderText: placeHolder.text
|
||||
property alias placeHolderButtonText: placeHolder.buttonText
|
||||
property string placeHolderFilterInterface: ""
|
||||
|
||||
signal selected(var thingId)
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
property int pendingCommandId: -1
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: acManager
|
||||
onSetZoneThingsReply: {
|
||||
if (commandId == d.pendingCommandId) {
|
||||
d.pendingCommandId = -1
|
||||
}
|
||||
|
||||
pageStack.pop();
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
|
||||
model: ThingsProxy {
|
||||
id: thingsProxy
|
||||
engine: _engine
|
||||
shownInterfaces: selectThingPage.interfaces
|
||||
hiddenThingIds: selectThingPage.hiddenThingIds
|
||||
}
|
||||
|
||||
delegate: ThingDelegate {
|
||||
Layout.fillWidth: true
|
||||
thing: thingsProxy.get(index)
|
||||
progressive: false
|
||||
onClicked: selectThingPage.selected(thing.id)
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
visible: thingsProxy.count == 0
|
||||
width: selectThingPage.width
|
||||
height: selectThingPage.height - selectThingPage.header.height
|
||||
|
||||
EmptyViewPlaceholder {
|
||||
id: placeHolder
|
||||
anchors.centerIn: parent
|
||||
width: parent.width - app.margins * 2
|
||||
imageSource: app.interfaceToIcon(selectThingPage.placeHolderFilterInterface)
|
||||
buttonText: qsTr("Add things")
|
||||
onButtonClicked: {
|
||||
pageStack.push("/ui/thingconfiguration/NewThingPage.qml", {filterInterface: selectThingPage.placeHolderFilterInterface})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
42
nymea-app/ui/mainviews/airconditioning/LegendDelegate.qml
Normal file
42
nymea-app/ui/mainviews/airconditioning/LegendDelegate.qml
Normal file
@ -0,0 +1,42 @@
|
||||
import QtQuick 2.0
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Layouts 1.1
|
||||
import "qrc:/ui/components"
|
||||
import "qrc:/ui/customviews"
|
||||
import Nymea 1.0
|
||||
import Nymea.AirConditioning 1.0
|
||||
import QtCharts 2.3
|
||||
|
||||
Item {
|
||||
id: root
|
||||
property Thing thing: null
|
||||
property string iconName: ""
|
||||
property color color: "white"
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
// opacity: selfProductionConsumptionSeries.opacity
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: -Style.smallMargins
|
||||
anchors.bottomMargin: -Style.smallMargins
|
||||
// onClicked: d.selectSeries(selfProductionConsumptionSeries)
|
||||
}
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: Style.smallMargins
|
||||
ColorIcon {
|
||||
name: root.iconName
|
||||
size: Style.smallIconSize
|
||||
color: root.color
|
||||
}
|
||||
Label {
|
||||
width: parent.parent.width - x
|
||||
elide: Text.ElideRight
|
||||
visible: root.width > 60
|
||||
text: root.thing.name
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
font: Style.smallFont
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
73
nymea-app/ui/mainviews/airconditioning/SelectSensorsPage.qml
Normal file
73
nymea-app/ui/mainviews/airconditioning/SelectSensorsPage.qml
Normal file
@ -0,0 +1,73 @@
|
||||
import QtQuick 2.0
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Layouts 1.1
|
||||
import "qrc:/ui/components"
|
||||
import "qrc:/ui/customviews"
|
||||
import Nymea 1.0
|
||||
import Nymea.AirConditioning 1.0
|
||||
|
||||
Page {
|
||||
id: root
|
||||
property AirConditioningManager acManager: null
|
||||
property ZoneInfo zoneInfo: null
|
||||
|
||||
readonly property Thing thermostat: engine.thingManager.things.getThing(root.zoneInfo.thermostatId)
|
||||
|
||||
header: NymeaHeader {
|
||||
text: root.thermostat.name
|
||||
|
||||
onBackPressed: {
|
||||
pageStack.pop()
|
||||
}
|
||||
|
||||
HeaderButton {
|
||||
imageSource: "tick"
|
||||
onClicked: {
|
||||
var sensorIds = []
|
||||
acManager.setZoneThings(root.zoneInfo.id, d.checkedThings)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
property var checkedThings: root.zoneInfo.thingIds
|
||||
|
||||
}
|
||||
|
||||
Component.onCompleted: print("***** sensors", root.zoneInfo.thingIds, d.checkedThings)
|
||||
|
||||
GroupedListView {
|
||||
id: sensorsListView
|
||||
anchors.fill: parent
|
||||
|
||||
section.property: "mainInterface"
|
||||
model: ThingsProxy {
|
||||
id: sensorsProxy
|
||||
engine: _engine
|
||||
shownInterfaces: ["thermostat", "closablesensor", "temperaturesensor", "humiditysensor", "vocsensor", "pm25sensor"]
|
||||
// hiddenInterfaces: ["thermostat"]
|
||||
groupByInterface: true
|
||||
}
|
||||
delegate: CheckDelegate {
|
||||
readonly property Thing thing: sensorsProxy.get(index)
|
||||
width: parent.width
|
||||
text: model.name
|
||||
checked: {
|
||||
for (var i = 0; i < d.checkedThings.length; i++) {
|
||||
if (d.checkedThings[i] == model.id) { // Intentionally
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
onClicked: {
|
||||
if (checked) {
|
||||
d.checkedThings.push(model.id)
|
||||
} else {
|
||||
d.checkedThings.splice(d.checkedThings.indexOf(model.id.toString()), 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,406 @@
|
||||
import QtQuick 2.3
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Layouts 1.1
|
||||
import Nymea 1.0
|
||||
import Nymea.AirConditioning 1.0
|
||||
import "qrc:/ui/components"
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
Layout.minimumHeight: 100
|
||||
property string title: ""
|
||||
|
||||
property ZoneInfo zone: null
|
||||
property TemperatureDaySchedule daySchedule: null
|
||||
|
||||
property TemperatureDaySchedule scheduleClipboard: null
|
||||
|
||||
signal copyClicked()
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: root.title
|
||||
}
|
||||
|
||||
ProgressButton {
|
||||
imageSource: paste ? "edit-paste" : "edit-copy"
|
||||
color: root.scheduleClipboard == root.daySchedule ? Style.accentColor : Style.iconColor
|
||||
property bool paste: root.scheduleClipboard != null && root.scheduleClipboard != root.daySchedule
|
||||
onClicked: {
|
||||
if (paste) {
|
||||
daySchedule.clear();
|
||||
for (var i = 0; i < root.scheduleClipboard.count; i++) {
|
||||
var schedule = root.scheduleClipboard.get(i)
|
||||
daySchedule.createSchedule(schedule.startTime, schedule.endTime, schedule.temperature)
|
||||
}
|
||||
} else {
|
||||
root.copyClicked()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
property var freeBlocks: {
|
||||
var ret = []
|
||||
for (var i = 0; i < daySchedule.count; i++) {
|
||||
var previous = i == 0 ? null : daySchedule.get(i - 1)
|
||||
var previousMins = previous ? previous.endTime.getHours() * 60 + previous.endTime.getMinutes() : 0
|
||||
var schedule = daySchedule.get(i)
|
||||
var startMins = schedule.startTime.getHours() * 60 + schedule.startTime.getMinutes()
|
||||
var endMins = schedule.endTime.getHours() * 60 + schedule.endTime.getMinutes()
|
||||
|
||||
if (startMins > previousMins + 60 * 3) {
|
||||
ret.push({startMins: previousMins, endMins: startMins})
|
||||
}
|
||||
}
|
||||
var last = daySchedule.count > 0 ? daySchedule.get(daySchedule.count - 1) : null
|
||||
var lastMins = last ? last.endTime.getHours() * 60 + last.endTime.getMinutes() : 0
|
||||
if (lastMins < (21*60)) {
|
||||
ret.push({startMins: lastMins, endMins: 24*60})
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: slider
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
color: Style.tileBackgroundColor
|
||||
radius: Style.cornerRadius
|
||||
clip: true
|
||||
|
||||
|
||||
Repeater {
|
||||
model: 24
|
||||
delegate: Rectangle {
|
||||
height: parent.height
|
||||
width: 1
|
||||
color: Style.tileOverlayColor
|
||||
x: slider.width / 24 * index
|
||||
visible: index > 0
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: root.daySchedule
|
||||
delegate: Rectangle {
|
||||
id: blockDelegate
|
||||
readonly property TemperatureSchedule schedule: root.daySchedule.get(index)
|
||||
readonly property int startMinutes: schedule.startTime.getHours() * 60 + schedule.startTime.getMinutes()
|
||||
readonly property int endMinutes: schedule.endTime.getHours() * 60 + schedule.endTime.getMinutes()
|
||||
readonly property int totalMinutes: 24 * 60
|
||||
height: slider.height
|
||||
width: endMinutes * slider.width / totalMinutes - x
|
||||
x: startMinutes * slider.width / totalMinutes
|
||||
radius: Style.cornerRadius
|
||||
color: schedule.temperature >= zone.standbySetpoint ? Style.red : Style.blue
|
||||
|
||||
Rectangle {
|
||||
anchors {
|
||||
left: parent.left;
|
||||
leftMargin: Style.extraSmallMargins
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
height: Style.font.pixelSize
|
||||
width: 2
|
||||
radius: width / 2
|
||||
color: Style.white
|
||||
}
|
||||
Rectangle {
|
||||
anchors {
|
||||
right: parent.right
|
||||
rightMargin: Style.extraSmallMargins
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
height: Style.font.pixelSize
|
||||
width: 2
|
||||
radius: width / 2
|
||||
color: Style.white
|
||||
}
|
||||
|
||||
Label {
|
||||
anchors { left: parent.left; right: parent.right; margins: Style.extraSmallMargins; top: parent.top }
|
||||
anchors.leftMargin: parent.width >= implicitWidth + Style.smallMargins ? Style.extraSmallMargins : -(implicitWidth + Style.extraSmallMargins)
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
font: Style.extraSmallFont
|
||||
text: blockDelegate.schedule.startTime.toLocaleTimeString(Qt.locale(), Locale.ShortFormat)
|
||||
}
|
||||
|
||||
Label {
|
||||
anchors { left: parent.left; right: parent.right; margins: Style.smallMargins; verticalCenter: parent.verticalCenter }
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
font: Style.smallFont
|
||||
text: Types.toUiValue(blockDelegate.schedule.temperature, Types.UnitDegreeCelsius) + "°"
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
Label {
|
||||
anchors { left: parent.left; right: parent.right; margins: Style.extraSmallMargins; bottom: parent.bottom }
|
||||
anchors.rightMargin: parent.width >= implicitWidth + Style.smallMargins ? Style.extraSmallMargins : -(implicitWidth + Style.extraSmallMargins)
|
||||
horizontalAlignment: Text.AlignRight
|
||||
font: Style.extraSmallFont
|
||||
text: blockDelegate.schedule.endTime.toLocaleTimeString(Qt.locale(), Locale.ShortFormat)
|
||||
// elide: Text.ElideRight
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
|
||||
property bool moveStart: false
|
||||
property bool moveEnd: false
|
||||
property TemperatureSchedule previousSchedule: null
|
||||
property TemperatureSchedule movedSchedule: null
|
||||
property TemperatureSchedule nextSchedule: null
|
||||
|
||||
property int startMouseX: 0
|
||||
property int startMins: 0
|
||||
property int endMins: 0
|
||||
|
||||
onPressed: {
|
||||
movedSchedule = null
|
||||
startMouseX = mouseX
|
||||
var totalMins = 24 * 60
|
||||
for (var i = 0; i < root.daySchedule.count; i++) {
|
||||
var schedule = root.daySchedule.get(i)
|
||||
print("schedule:", schedule.startTime, schedule.endTime)
|
||||
var startMin = schedule.startTime.getHours() * 60 + schedule.startTime.getMinutes()
|
||||
var startPos = startMin * slider.width / totalMins
|
||||
var endMin = schedule.endTime.getHours() * 60 + schedule.endTime.getMinutes()
|
||||
var endPos = endMin * slider.width / totalMins
|
||||
|
||||
if (Math.abs(startPos - mouseX) < 10) {
|
||||
moveStart = true;
|
||||
print("start")
|
||||
} else if (Math.abs(endPos - mouseX) < 10) {
|
||||
moveEnd = true
|
||||
print("end")
|
||||
} else if (mouseX > startPos && mouseX < endPos) {
|
||||
moveStart = true
|
||||
moveEnd = true
|
||||
print("middle")
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
startMins = startMin
|
||||
endMins = endMin
|
||||
previousSchedule = i > 0 ? root.daySchedule.get(i-1) : null
|
||||
movedSchedule = schedule
|
||||
nextSchedule = i < root.daySchedule.count - 1 ? root.daySchedule.get(i+1) : null
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
onReleased: {
|
||||
moveStart = false;
|
||||
moveEnd = false;
|
||||
preventStealing = false;
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
print("clicked")
|
||||
if (movedSchedule != null && Math.abs(mouseX - startMouseX) < 5) {
|
||||
print("opening")
|
||||
var dialog = editDialogComponent.createObject(root, {schedule: movedSchedule})
|
||||
dialog.open()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
onPositionChanged: {
|
||||
var totalMins = 24 * 60
|
||||
var diffX = mouseX - startMouseX
|
||||
|
||||
// dY : height = dM : total
|
||||
var diffMins = diffX * totalMins / slider.width
|
||||
print("diffX", diffX, "diffMins", diffMins, startMins, startMins + diffMins)
|
||||
|
||||
var newStart = new Date(movedSchedule.startTime);
|
||||
var newEnd = new Date(movedSchedule.endTime);
|
||||
|
||||
var newStartMins = startMins + (moveStart ? diffMins : 0);
|
||||
var newEndMins = endMins + (moveEnd ? diffMins : 0);
|
||||
|
||||
var snapMinutes = 30
|
||||
|
||||
var leftLimit = previousSchedule ? previousSchedule.endTime.getHours() * 60 + previousSchedule.endTime.getMinutes() + snapMinutes : 0
|
||||
var rightLimit = nextSchedule ? nextSchedule.startTime.getHours() * 60 + nextSchedule.startTime.getMinutes() - snapMinutes : totalMins
|
||||
|
||||
|
||||
if (moveStart && !moveEnd) {
|
||||
newStartMins = Math.max(leftLimit, newStartMins)
|
||||
newStartMins = Math.min(newEndMins - 60, newStartMins)
|
||||
} else if (moveEnd && !moveStart) {
|
||||
newEndMins = Math.min(rightLimit, newEndMins)
|
||||
newEndMins = Math.max(newStartMins + 60, newEndMins)
|
||||
} else if (moveStart && moveEnd) {
|
||||
newStartMins = Math.max(leftLimit, newStartMins)
|
||||
newEndMins = Math.min(rightLimit, newEndMins)
|
||||
|
||||
var blockSize = endMins - startMins
|
||||
|
||||
newStartMins = Math.max(leftLimit, Math.min(newEndMins - blockSize, newStartMins))
|
||||
newEndMins = Math.max(newStartMins + blockSize, newEndMins)
|
||||
|
||||
}
|
||||
|
||||
var startSnapOffset = newStartMins % snapMinutes
|
||||
if (startSnapOffset < snapMinutes / 2) {
|
||||
newStartMins -= startSnapOffset
|
||||
} else {
|
||||
newStartMins += (snapMinutes - startSnapOffset)
|
||||
}
|
||||
var endSnapOffset = newEndMins % snapMinutes
|
||||
if (endSnapOffset < snapMinutes / 2) {
|
||||
newEndMins -= endSnapOffset
|
||||
} else {
|
||||
newEndMins += (snapMinutes - endSnapOffset)
|
||||
}
|
||||
print("startSnapOffset", startSnapOffset, "endSnapOff", endSnapOffset, "nes start", newStartMins, newEndMins)
|
||||
|
||||
if (newEndMins == totalMins) {
|
||||
newEndMins -= 1
|
||||
}
|
||||
|
||||
newStart.setHours(0, newStartMins)
|
||||
newEnd.setHours(0, newEndMins)
|
||||
|
||||
if (movedSchedule.startTime.getTime() !== newStart.getTime() || movedSchedule.endTime.getTime() !== newEnd.getTime()) {
|
||||
preventStealing = true;
|
||||
}
|
||||
|
||||
movedSchedule.startTime = newStart
|
||||
movedSchedule.endTime = newEnd
|
||||
|
||||
print("start time is new", newStart.toLocaleTimeString())
|
||||
print("end time is new", newEnd.toLocaleTimeString())
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: d.freeBlocks
|
||||
delegate: Item {
|
||||
id: freeBlockDelegate
|
||||
property var block: d.freeBlocks[index]
|
||||
x: block.startMins * slider.width / (24*60)
|
||||
width: block.endMins * slider.width / (24*60) - x
|
||||
height: slider.height
|
||||
|
||||
ProgressButton {
|
||||
anchors.centerIn: parent
|
||||
imageSource: "add"
|
||||
onClicked: {
|
||||
var startTime = new Date()
|
||||
var endTime = new Date()
|
||||
if (root.daySchedule.count == 0) {
|
||||
startTime.setHours(6, 0, 0)
|
||||
endTime.setHours(18, 0, 0)
|
||||
} else {
|
||||
startTime.setHours(0, freeBlockDelegate.block.startMins + 60, 0)
|
||||
endTime.setHours(0, freeBlockDelegate.block.endMins - 60, 0)
|
||||
}
|
||||
|
||||
root.daySchedule.createSchedule(startTime, endTime, 21)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: editDialogComponent
|
||||
NymeaDialog {
|
||||
id: editDialog
|
||||
x: (parent.width - width) / 2
|
||||
property TemperatureSchedule schedule: null
|
||||
|
||||
standardButtons: Dialog.NoButton
|
||||
|
||||
Dial {
|
||||
id: dial
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: width
|
||||
activeValue: root.zone.standbySetpoint
|
||||
minValue: 10
|
||||
maxValue: 30
|
||||
precision: 0.5
|
||||
value: editDialog.schedule.temperature
|
||||
onMoved: editDialog.schedule.temperature = value
|
||||
color: activeValue <= value ? Style.red : Style.blue
|
||||
ColumnLayout {
|
||||
anchors.centerIn: parent
|
||||
anchors.verticalCenterOffset: -Style.smallMargins
|
||||
width: parent.contentItem.width * 0.6
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: Types.toUiUnit(Types.UnitDegreeCelsius)
|
||||
font.pixelSize: Math.min(Style.smallFont.pixelSize, dial.height / 16)
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: Types.toUiValue(editDialog.schedule.temperature, Types.UnitDegreeCelsius).toFixed(1)
|
||||
font.pixelSize: Math.min(Style.hugeFont.pixelSize, dial.height / 8)
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
color: zone.currentSetpoint > zone.standbySetpoint
|
||||
? Style.red
|
||||
: zone.currentSetpoint < zone.standbySetpoint
|
||||
? Style.blue
|
||||
: Style.foregroundColor
|
||||
}
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: Types.toUiValue(zone.standbySetpoint, Types.UnitDegreeCelsius).toFixed(1)
|
||||
font.pixelSize: Math.min(Style.largeFont.pixelSize, dial.height / 12)
|
||||
wrapMode: Text.WordWrap
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: editDialog.schedule.startTime.toLocaleTimeString(Qt.locale(), Locale.ShortFormat)
|
||||
+ " - "
|
||||
+ editDialog.schedule.endTime.toLocaleTimeString(Qt.locale(), Locale.ShortFormat)
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Button {
|
||||
text: qsTr("Remove")
|
||||
Layout.fillWidth: true
|
||||
onClicked: {
|
||||
root.daySchedule.removeSchedule(editDialog.schedule)
|
||||
editDialog.close()
|
||||
}
|
||||
}
|
||||
Button {
|
||||
text: qsTr("OK")
|
||||
Layout.fillWidth: true
|
||||
onClicked: {
|
||||
editDialog.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
130
nymea-app/ui/mainviews/airconditioning/TimeOverrideDialog.qml
Normal file
130
nymea-app/ui/mainviews/airconditioning/TimeOverrideDialog.qml
Normal file
@ -0,0 +1,130 @@
|
||||
import QtQuick 2.3
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Layouts 1.1
|
||||
import "qrc:/ui/components"
|
||||
import "qrc:/ui/customviews"
|
||||
import "qrc:/ui/delegates"
|
||||
import Nymea 1.0
|
||||
import NymeaApp.Utils 1.0
|
||||
import Nymea.AirConditioning 1.0
|
||||
|
||||
NymeaDialog {
|
||||
id: root
|
||||
standardButtons: Dialog.NoButton
|
||||
|
||||
title: qsTr("Manual mode")
|
||||
text: qsTr("Select how long the manual temperature setpoint should be kept.")
|
||||
|
||||
property AirConditioningManager acManager: null
|
||||
property ZoneInfo zone: null
|
||||
|
||||
RadioButton {
|
||||
id: eventualButton
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Eventual")
|
||||
checked: root.zone.setpointOverrideMode == ZoneInfo.SetpointOverrideModeEventual
|
||||
contentItem: ColumnLayout {
|
||||
width: root.width
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: eventualButton.indicator.width + eventualButton.spacing
|
||||
text: eventualButton.text
|
||||
}
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: eventualButton.indicator.width + eventualButton.spacing
|
||||
wrapMode: Text.WordWrap
|
||||
text: qsTr("Until the temperature is changed by some other event.")
|
||||
font: Style.smallFont
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RadioButton {
|
||||
id: foreverButton
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Forever")
|
||||
checked: root.zone.setpointOverrideMode == ZoneInfo.SetpointOverrideModeUnlimited
|
||||
contentItem: ColumnLayout {
|
||||
width: root.width
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: foreverButton.indicator.width + foreverButton.spacing
|
||||
text: foreverButton.text
|
||||
}
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: foreverButton.indicator.width + foreverButton.spacing
|
||||
wrapMode: Text.WordWrap
|
||||
text: qsTr("Until manually removed or changed.")
|
||||
font: Style.smallFont
|
||||
}
|
||||
}
|
||||
}
|
||||
RadioButton {
|
||||
id: timeButton
|
||||
text: qsTr("Time")
|
||||
checked: root.zone.setpointOverrideMode == ZoneInfo.SetpointOverrideModeTimed
|
||||
contentItem: ColumnLayout {
|
||||
width: root.width
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: timeButton.indicator.width + timeButton.spacing
|
||||
text: timeButton.text
|
||||
}
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: timeButton.indicator.width + timeButton.spacing
|
||||
wrapMode: Text.WordWrap
|
||||
text: qsTr("For a specified amount of time.")
|
||||
font: Style.smallFont
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.leftMargin: timeButton.indicator.width + timeButton.spacing
|
||||
enabled: timeButton.checked
|
||||
SpinBox {
|
||||
from: 30
|
||||
to: 30 * 2 * 12
|
||||
stepSize: 30
|
||||
value: 120
|
||||
textFromValue: function(value) {
|
||||
return Math.floor(value / 60) + ":" + NymeaUtils.pad(value % 60, 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Button {
|
||||
text: qsTr("Remove")
|
||||
onClicked: {
|
||||
acManager.setZoneSetpointOverride(root.zone.id, root.zone.setpointOverride, ZoneInfo.SetpointOverrideModeNone, 0)
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Button {
|
||||
text: qsTr("OK")
|
||||
onClicked: {
|
||||
var mode = ZoneInfo.SetpointOverrideModeEventual
|
||||
if (foreverButton.checked) {
|
||||
mode = ZoneInfo.SetpointOverrideModeUnlimited
|
||||
} else if (timeButton.checked) {
|
||||
mode = ZoneInfo.SetpointOverrideModeTimed
|
||||
}
|
||||
acManager.setZoneSetpointOverride(root.zone.id, root.zone.setpointOverride, mode, 120)
|
||||
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
263
nymea-app/ui/mainviews/airconditioning/TimeSchedulePage.qml
Normal file
263
nymea-app/ui/mainviews/airconditioning/TimeSchedulePage.qml
Normal file
@ -0,0 +1,263 @@
|
||||
import QtQuick 2.0
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Layouts 1.1
|
||||
import "qrc:/ui/components"
|
||||
import "qrc:/ui/customviews"
|
||||
import Nymea 1.0
|
||||
import Nymea.AirConditioning 1.0
|
||||
|
||||
Page {
|
||||
id: root
|
||||
property AirConditioningManager acManager: null
|
||||
property ZoneInfo zone: null
|
||||
|
||||
readonly property TemperatureWeekSchedule weekSchedule: zone.weekSchedule.clone()
|
||||
|
||||
|
||||
header: NymeaHeader {
|
||||
text: root.zone.name
|
||||
|
||||
onBackPressed: {
|
||||
pageStack.pop()
|
||||
}
|
||||
|
||||
HeaderButton {
|
||||
imageSource: "tick"
|
||||
onClicked: {
|
||||
acManager.setZoneWeekSchedule(root.zone.id, root.weekSchedule)
|
||||
pageStack.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Flickable {
|
||||
id: flickable
|
||||
anchors.fill: parent
|
||||
contentWidth: editorItem.width
|
||||
contentHeight: editorItem.height
|
||||
clip: true
|
||||
|
||||
Item {
|
||||
id: editorItem
|
||||
width: flickable.width
|
||||
height: childrenRect.height + Style.margins * 2
|
||||
|
||||
ColumnLayout {
|
||||
anchors { left: parent.left; top: parent.top; right: parent.right; margins: Style.margins }
|
||||
|
||||
// RowLayout {
|
||||
// Label {
|
||||
// text: qsTr("Base temperature: %1 %2").arg(Types.toUiValue(zone.standbySetpoint, Types.UnitDegreeCelsius)).arg(Types.toUiUnit(Types.UnitDegreeCelsius))
|
||||
// }
|
||||
// }
|
||||
|
||||
Repeater {
|
||||
id: scheduleRepeater
|
||||
model: [
|
||||
qsTr("Monday"),
|
||||
qsTr("Tuesday"),
|
||||
qsTr("Wednesday"),
|
||||
qsTr("Thursday"),
|
||||
qsTr("Friday"),
|
||||
qsTr("Saturday"),
|
||||
qsTr("Sunday")
|
||||
]
|
||||
property TemperatureDaySchedule scheduleClipboard: null
|
||||
|
||||
delegate: TemperatureScheduleEditor {
|
||||
title: modelData
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
zone: root.zone
|
||||
daySchedule: weekSchedule.get(index)
|
||||
scheduleClipboard: scheduleRepeater.scheduleClipboard
|
||||
onCopyClicked: {
|
||||
if (scheduleRepeater.scheduleClipboard == daySchedule) {
|
||||
scheduleRepeater.scheduleClipboard = null
|
||||
} else {
|
||||
scheduleRepeater.scheduleClipboard = daySchedule
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CheckBox {
|
||||
// text: qsTr("Use sunday schedule for public holidays.")
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// Item {
|
||||
// id: scheduleEditor
|
||||
// anchors.fill: parent
|
||||
// anchors.margins: Style.margins
|
||||
|
||||
// Rectangle {
|
||||
// id: slider
|
||||
// anchors {
|
||||
// top: parent.top
|
||||
// left: parent.left
|
||||
// bottom: parent.bottom
|
||||
// }
|
||||
// width: Style.largeDelegateHeight
|
||||
// color: Style.tileBackgroundColor
|
||||
// radius: Style.cornerRadius
|
||||
|
||||
|
||||
// Repeater {
|
||||
// model: root.temperatureSchedules
|
||||
// delegate: Rectangle {
|
||||
// readonly property TemperatureSchedule temperatureSchedule: root.temperatureSchedules.get(index)
|
||||
// readonly property int startMinutes: temperatureSchedule.startTime.getHours() * 60 + temperatureSchedule.startTime.getMinutes()
|
||||
// readonly property int endMinutes: temperatureSchedule.endTime.getHours() * 60 + temperatureSchedule.endTime.getMinutes()
|
||||
// readonly property int totalMinutes: 24 * 60
|
||||
// width: scheduleEditor.width
|
||||
// // h : 24 = x : s
|
||||
// y: startMinutes * slider.height / totalMinutes
|
||||
// height: endMinutes * slider.height / totalMinutes - y
|
||||
// radius: Style.cornerRadius
|
||||
// color: Style.tileBackgroundColor
|
||||
// Component.onCompleted: print("**created, startTime", startMinutes, endMinutes, totalMinutes, y, height)
|
||||
// }
|
||||
// }
|
||||
|
||||
// Repeater {
|
||||
// model: 24
|
||||
// delegate: Item {
|
||||
// width: parent.width
|
||||
// height: slider.height / 24
|
||||
// y: height * index
|
||||
// Rectangle {
|
||||
// width: parent.width
|
||||
// height: 1
|
||||
// color: Style.gray
|
||||
// visible: index > 0
|
||||
// }
|
||||
// Label {
|
||||
// width: parent.width
|
||||
// text: {
|
||||
// var d = new Date();
|
||||
// d.setHours(index,0,0);
|
||||
// return d.toLocaleTimeString(Qt.locale(), Locale.ShortFormat)
|
||||
// }
|
||||
// height: parent.height
|
||||
// horizontalAlignment: Text.AlignHCenter
|
||||
// verticalAlignment: Text.AlignVCenter
|
||||
// font: Style.smallFont
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// }
|
||||
// MouseArea {
|
||||
// anchors.fill: parent
|
||||
|
||||
// property bool moveStart: false
|
||||
// property bool moveEnd: false
|
||||
// property TemperatureSchedule movedSchedule: null
|
||||
|
||||
// property int startMouseY: 0
|
||||
// property int startMins: 0
|
||||
// property int endMins: 0
|
||||
|
||||
// onPressed: {
|
||||
// startMouseY = mouseY
|
||||
// var totalMins = 24 * 60
|
||||
// for (var i = 0; i < root.temperatureSchedules.count; i++) {
|
||||
// var schedule = root.temperatureSchedules.get(i)
|
||||
// print("schedule:", schedule.startTime, schedule.endTime)
|
||||
// var startMin = schedule.startTime.getHours() * 60 + schedule.startTime.getMinutes()
|
||||
// var startPos = startMin * slider.height / totalMins
|
||||
// var endMin = schedule.endTime.getHours() * 60 + schedule.endTime.getMinutes()
|
||||
// var endPos = endMin * slider.height / totalMins
|
||||
|
||||
// startMins = startMin
|
||||
// endMins = endMin
|
||||
// movedSchedule = schedule
|
||||
// if (Math.abs(startPos - mouseY) < 10) {
|
||||
// moveStart = true;
|
||||
// print("start")
|
||||
// break;
|
||||
// } else if (Math.abs(endPos - mouseY) < 10) {
|
||||
// moveEnd = true
|
||||
// print("end")
|
||||
// break;
|
||||
// } else if (mouseY > startPos && mouseY < endPos) {
|
||||
// moveStart = true
|
||||
// moveEnd = true
|
||||
// print("middle")
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// onReleased: {
|
||||
// moveStart = false;
|
||||
// moveEnd = false;
|
||||
// movedSchedule = null
|
||||
// }
|
||||
|
||||
// onPositionChanged: {
|
||||
// var totalMins = 24 * 60
|
||||
// var diffY = mouseY - startMouseY
|
||||
// // dY : height = dM : total
|
||||
// var diffMins = diffY * totalMins / slider.height
|
||||
// print("diffY", diffY, "diffMins", diffMins, startMins, startMins + diffMins)
|
||||
|
||||
// var newStart = new Date(movedSchedule.startTime);
|
||||
// var newEnd = new Date(movedSchedule.endTime);
|
||||
|
||||
// var newStartMins = startMins + (moveStart ? diffMins : 0);
|
||||
// var newEndMins = endMins + (moveEnd ? diffMins : 0);
|
||||
|
||||
|
||||
// if (moveStart && !moveEnd) {
|
||||
// newStartMins = Math.max(0, newStartMins)
|
||||
// newStartMins = Math.min(newEndMins - 60, newStartMins)
|
||||
// } else if (moveEnd && !moveStart) {
|
||||
// newEndMins = Math.min(totalMins - 1, newEndMins)
|
||||
// newEndMins = Math.max(newStartMins + 60, newEndMins)
|
||||
// } else if (moveStart && moveEnd) {
|
||||
// newStartMins = Math.max(0, newStartMins)
|
||||
// newEndMins = Math.min(totalMins - 1, newEndMins)
|
||||
|
||||
// var blockSize = endMins - startMins
|
||||
|
||||
// newStartMins = Math.max(0, Math.min(newEndMins - blockSize, newStartMins))
|
||||
// newEndMins = Math.max(newStartMins + blockSize, newEndMins)
|
||||
|
||||
// }
|
||||
|
||||
|
||||
|
||||
// newStart.setHours(0, newStartMins)
|
||||
// newEnd.setHours(0, newEndMins)
|
||||
|
||||
// movedSchedule.startTime = newStart
|
||||
// movedSchedule.endTime = newEnd
|
||||
|
||||
// print("start time is new", newStart.toLocaleTimeString())
|
||||
// print("end time is new", newEnd.toLocaleTimeString())
|
||||
|
||||
// }
|
||||
// }
|
||||
|
||||
// ProgressButton {
|
||||
// anchors.centerIn: parent
|
||||
// imageSource: "add"
|
||||
// onClicked: {
|
||||
// var startTime = new Date()
|
||||
// startTime.setHours(7, 0, 0)
|
||||
// var endTime = new Date()
|
||||
// endTime.setHours(22, 0 , 0)
|
||||
// root.temperatureSchedules.createTemperatureSchedule(startTime, endTime, 21)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
}
|
||||
44
nymea-app/ui/mainviews/airconditioning/TooltipDelegate.qml
Normal file
44
nymea-app/ui/mainviews/airconditioning/TooltipDelegate.qml
Normal file
@ -0,0 +1,44 @@
|
||||
import QtQuick 2.0
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Layouts 1.1
|
||||
import "qrc:/ui/components"
|
||||
import "qrc:/ui/customviews"
|
||||
import Nymea 1.0
|
||||
import Nymea.AirConditioning 1.0
|
||||
import QtCharts 2.3
|
||||
|
||||
NymeaToolTip {
|
||||
width: layout.implicitWidth + Style.smallMargins * 2
|
||||
height: layout.implicitHeight + Style.smallMargins * 2
|
||||
|
||||
property Thing thing: null
|
||||
property LogEntry entry: null
|
||||
property alias color: rect.color
|
||||
property ValueAxis axis: null
|
||||
property int unit: Types.UnitNone
|
||||
|
||||
readonly property int realY: entry ? Math.min(Math.max(mouseArea.height - (entry.value * mouseArea.height / axis.max) - height / 2 /*- Style.margins*/, 0), mouseArea.height - height) : 0
|
||||
property int fixedY: 0
|
||||
y: fixedY // Animated
|
||||
|
||||
RowLayout {
|
||||
id: layout
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.smallMargins
|
||||
|
||||
Rectangle {
|
||||
id: rect
|
||||
width: Style.extraSmallFont.pixelSize
|
||||
height: width
|
||||
}
|
||||
Label {
|
||||
text: "%1: %2%3".arg(thing.name).arg(entry ? round(Types.toUiValue(entry.value, unit)) : "-").arg(Types.toUiUnit(unit))
|
||||
Layout.fillWidth: true
|
||||
font: Style.extraSmallFont
|
||||
elide: Text.ElideMiddle
|
||||
function round(value) {
|
||||
return Math.round(value * 100) / 100
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
31
nymea-app/ui/mainviews/airconditioning/ZoneInfoField.qml
Normal file
31
nymea-app/ui/mainviews/airconditioning/ZoneInfoField.qml
Normal file
@ -0,0 +1,31 @@
|
||||
import QtQuick 2.0
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Layouts 1.1
|
||||
import "qrc:/ui/components"
|
||||
import "qrc:/ui/customviews"
|
||||
import "qrc:/ui/delegates"
|
||||
import Nymea 1.0
|
||||
import Nymea.AirConditioning 1.0
|
||||
|
||||
RowLayout {
|
||||
id: root
|
||||
implicitHeight: Style.bigIconSize
|
||||
|
||||
property alias imageSource: icon.name
|
||||
property alias iconColor: icon.color
|
||||
property alias text: label.text
|
||||
|
||||
property ZoneInfo zone: null
|
||||
property int flag: ZoneInfo.ZoneStatusFlagNone
|
||||
property bool active: zone && ((zone.zoneStatus & flag) > 0)
|
||||
|
||||
ColorIcon {
|
||||
id: icon
|
||||
size: Style.bigIconSize
|
||||
color: root.active ? root.iconColor : Style.iconColor
|
||||
}
|
||||
Label {
|
||||
id: label
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
206
nymea-app/ui/mainviews/airconditioning/ZoneInfoWrapper.qml
Normal file
206
nymea-app/ui/mainviews/airconditioning/ZoneInfoWrapper.qml
Normal file
@ -0,0 +1,206 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2022, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
* This project including source code and documentation is protected by
|
||||
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||||
* reproduction, publication, editing and translation, are reserved. The use of
|
||||
* this project is subject to the terms of a license agreement to be concluded
|
||||
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||
* under https://nymea.io/license
|
||||
*
|
||||
* GNU General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under the
|
||||
* terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, GNU version 3. This project is distributed in the hope that it
|
||||
* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this project. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* For any further details and any questions please contact us under
|
||||
* contact@nymea.io or see our FAQ/Licensing Information on
|
||||
* https://nymea.io/license/faq
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
import QtQuick 2.8
|
||||
import QtQuick.Controls 2.1
|
||||
import QtQuick.Controls.Material 2.1
|
||||
import QtQuick.Layouts 1.2
|
||||
import QtGraphicalEffects 1.0
|
||||
import QtCharts 2.2
|
||||
import Nymea 1.0
|
||||
import Nymea.AirConditioning 1.0
|
||||
import "qrc:/ui/components"
|
||||
import "qrc:/ui/delegates"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property ZoneInfo zone: null
|
||||
|
||||
readonly property double zoneTemperature: d.zoneTemperature
|
||||
readonly property double zoneHumidity: d.zoneHumidity
|
||||
readonly property double zoneVOC: d.zoneVOC
|
||||
|
||||
readonly property ThingsProxy thermostats: ThingsProxy {
|
||||
engine: zone.thermostats.length > 0 ? _engine : null
|
||||
shownThingIds: zone.thermostats
|
||||
}
|
||||
readonly property ThingsProxy heatingThermostats: ThingsProxy {
|
||||
engine: _engine
|
||||
parentProxy: thermostats
|
||||
stateFilter: { "heatingOn": true }
|
||||
}
|
||||
readonly property ThingsProxy coolingThermostats: ThingsProxy {
|
||||
engine: _engine
|
||||
parentProxy: thermostats
|
||||
stateFilter: { "coolingOn": true }
|
||||
}
|
||||
|
||||
readonly property ThingsProxy windowSensors: ThingsProxy {
|
||||
engine: zone.windowSensors.length > 0 ? _engine : null
|
||||
shownThingIds: zone.windowSensors
|
||||
}
|
||||
readonly property ThingsProxy openWindows: ThingsProxy {
|
||||
engine: _engine
|
||||
parentProxy: windowSensors
|
||||
stateFilter: { "closed": false }
|
||||
}
|
||||
|
||||
readonly property ThingsProxy indoorSensors: ThingsProxy {
|
||||
engine: root.zone.indoorSensors.length > 0 ? _engine : null
|
||||
shownThingIds: root.zone.indoorSensors
|
||||
}
|
||||
readonly property ThingsProxy indoorTempSensors: ThingsProxy {
|
||||
id: tempSensors
|
||||
engine: _engine
|
||||
parentProxy: indoorSensors
|
||||
shownInterfaces: ["temperaturesensor"]
|
||||
}
|
||||
readonly property ThingsProxy indoorHumiditySensors: ThingsProxy {
|
||||
engine: _engine
|
||||
parentProxy: indoorSensors
|
||||
shownInterfaces: ["humiditysensor"]
|
||||
}
|
||||
readonly property ThingsProxy indoorVocSensors: ThingsProxy {
|
||||
engine: _engine
|
||||
parentProxy: indoorSensors
|
||||
shownInterfaces: ["vocsensor"]
|
||||
}
|
||||
readonly property ThingsProxy indoorPm25Sensors: ThingsProxy {
|
||||
engine: _engine
|
||||
parentProxy: indoorSensors
|
||||
shownInterfaces: ["pm25sensor"]
|
||||
}
|
||||
|
||||
readonly property ThingsProxy outdoorSensors: ThingsProxy {
|
||||
engine: root.zone.outdoorSensors.length > 0 ? _engine : null
|
||||
shownThingIds: root.zone.outdoorSensors
|
||||
}
|
||||
readonly property ThingsProxy outdoorTempSensors: ThingsProxy {
|
||||
engine: _engine
|
||||
parentProxy: outdoorSensors
|
||||
shownInterfaces: ["temperaturesensor"]
|
||||
}
|
||||
readonly property ThingsProxy outoorHumiditySensors: ThingsProxy {
|
||||
engine: _engine
|
||||
parentProxy: outdoorSensors
|
||||
shownInterfaces: ["humiditysensor"]
|
||||
}
|
||||
readonly property ThingsProxy outdoorPm25Sensors: ThingsProxy {
|
||||
engine: _engine
|
||||
parentProxy: outdoorSensors
|
||||
shownInterfaces: ["pm25sensor"]
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
property double zoneTemperature
|
||||
function updateZoneTemperature() {
|
||||
var value = undefined;
|
||||
if (thermostats.count > 0) {
|
||||
for (var i = 0; i < thermostats.count; i++) {
|
||||
var tempState = thermostats.get(i).stateByName("temperature")
|
||||
if (!tempState) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (value == undefined || tempState.value > value) {
|
||||
value = tempState.value
|
||||
}
|
||||
}
|
||||
}
|
||||
if (value == undefined) {
|
||||
for (var i = 0; i < indoorTempSensors.count; i++) {
|
||||
var t = indoorTempSensors.get(i).stateByName("temperature").value
|
||||
if (value == undefined || t > value) {
|
||||
value = t
|
||||
}
|
||||
}
|
||||
}
|
||||
if (value != undefined) {
|
||||
zoneTemperature = value;
|
||||
}
|
||||
}
|
||||
|
||||
property double zoneHumidity
|
||||
function updateZoneHumidity() {
|
||||
var value = undefined;
|
||||
for (var i = 0; i < indoorHumiditySensors.count; i++) {
|
||||
var t = indoorHumiditySensors.get(i).stateByName("humidity").value
|
||||
if (value == undefined || t > value) {
|
||||
value = t;
|
||||
}
|
||||
}
|
||||
if (value != undefined) {
|
||||
zoneHumidity = value;
|
||||
}
|
||||
}
|
||||
|
||||
property double zoneVOC
|
||||
function updateZoneVOC() {
|
||||
var value = undefined;
|
||||
for (var i = 0; i < indoorVocSensors.count; i++) {
|
||||
var t = indoorVocSensors.get(i).stateByName("voc").value
|
||||
if (value == undefined || t > value) {
|
||||
value = t;
|
||||
}
|
||||
}
|
||||
if (value != undefined) {
|
||||
zoneVOC = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: thingsRepeater
|
||||
model: ThingsProxy {
|
||||
engine: zone.thermostats.length > 0 || zone.indoorSensors.length > 0 ? _engine : null
|
||||
shownThingIds: zone.thermostats + zone.indoorSensors
|
||||
}
|
||||
|
||||
delegate: Item {
|
||||
readonly property Thing thing: indoorTempSensors.get(index)
|
||||
readonly property State temperatureState: thing ? thing.stateByName("temperature") : null
|
||||
property double temp: temperatureState ? temperatureState.value : 0
|
||||
onTempChanged: d.updateZoneTemperature()
|
||||
readonly property State humidityState: thing ? thing.stateByName("humidity") : null
|
||||
property double humidity: humidityState ? humidityState.value : 0
|
||||
onHumidityChanged: d.updateZoneHumidity()
|
||||
readonly property State vocState: thing ? thing.stateByName("voc") : null
|
||||
property double voc: vocState ? vocState.value : 0
|
||||
onVocChanged: d.updateZoneVOC()
|
||||
}
|
||||
onCountChanged: {
|
||||
d.updateZoneTemperature()
|
||||
d.updateZoneHumidity()
|
||||
d.updateZoneVOC()
|
||||
}
|
||||
}
|
||||
}
|
||||
38
nymea-app/ui/mainviews/airconditioning/ZonePage.qml
Normal file
38
nymea-app/ui/mainviews/airconditioning/ZonePage.qml
Normal file
@ -0,0 +1,38 @@
|
||||
import QtQuick 2.0
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Layouts 1.1
|
||||
import "qrc:/ui/components"
|
||||
import "qrc:/ui/customviews"
|
||||
import Nymea 1.0
|
||||
import Nymea.AirConditioning 1.0
|
||||
|
||||
Page {
|
||||
id: root
|
||||
property AirConditioningManager acManager: null
|
||||
property ZoneInfo zone: null
|
||||
|
||||
ZoneInfoWrapper {
|
||||
id: zoneWrapper
|
||||
zone: root.zone
|
||||
}
|
||||
|
||||
|
||||
header: NymeaHeader {
|
||||
text: root.zone.name
|
||||
|
||||
onBackPressed: {
|
||||
pageStack.pop()
|
||||
}
|
||||
|
||||
HeaderButton {
|
||||
imageSource: "chart"
|
||||
onClicked: pageStack.push(Qt.resolvedUrl("ACChartsPage.qml"), {acManager: root.acManager, zoneWrapper: zoneWrapper})
|
||||
}
|
||||
}
|
||||
|
||||
ZoneView {
|
||||
anchors.fill: parent
|
||||
acManager: root.acManager
|
||||
zoneWrapper: zoneWrapper
|
||||
}
|
||||
}
|
||||
67
nymea-app/ui/mainviews/airconditioning/ZoneStatusIcons.qml
Normal file
67
nymea-app/ui/mainviews/airconditioning/ZoneStatusIcons.qml
Normal file
@ -0,0 +1,67 @@
|
||||
import QtQuick 2.3
|
||||
import QtQuick.Layouts 1.2
|
||||
import "qrc:/ui/components"
|
||||
import Nymea 1.0
|
||||
import Nymea.AirConditioning 1.0
|
||||
|
||||
RowLayout {
|
||||
id: root
|
||||
Layout.fillWidth: true
|
||||
|
||||
property ZoneInfo zone: null
|
||||
property int iconSize: Style.iconSize
|
||||
|
||||
signal clicked(int flag)
|
||||
|
||||
Repeater {
|
||||
id: zoneStatusRepeater
|
||||
model: zoneStatusModel
|
||||
property var zoneStatusModel: [
|
||||
{
|
||||
value: ZoneInfo.ZoneStatusFlagSetpointOverrideActive,
|
||||
icon: "dial",
|
||||
activeColor: Style.accentColor
|
||||
},
|
||||
{
|
||||
value: ZoneInfo.ZoneStatusFlagTimeScheduleActive,
|
||||
icon: "calendar",
|
||||
activeColor: Style.orange
|
||||
},
|
||||
{
|
||||
value: ZoneInfo.ZoneStatusFlagWindowOpen,
|
||||
icon: "sensors/window-closed",
|
||||
activeIcon: "sensors/window-open",
|
||||
activeColor: Style.red
|
||||
},
|
||||
{
|
||||
value: ZoneInfo.ZoneStatusFlagHighHumidity,
|
||||
icon: "sensors/humidity",
|
||||
activeColor: Style.lightBlue
|
||||
},
|
||||
{
|
||||
value: ZoneInfo.ZoneStatusFlagBadAir,
|
||||
icon: "weathericons/weather-clouds",
|
||||
activeColor: Style.purple
|
||||
}
|
||||
]
|
||||
delegate: Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Style.bigIconSize
|
||||
property var entry: zoneStatusRepeater.zoneStatusModel[index]
|
||||
ColorIcon {
|
||||
id: zoneStatusIcon
|
||||
anchors.centerIn: parent
|
||||
name: entry.hasOwnProperty("activeIcon") && active ? entry.activeIcon : entry.icon
|
||||
size: root.iconSize
|
||||
property bool active: (root.zone.zoneStatus & entry.value) > 0
|
||||
color: active ? entry.activeColor : Style.iconColor
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
root.clicked(entry.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
182
nymea-app/ui/mainviews/airconditioning/ZoneView.qml
Normal file
182
nymea-app/ui/mainviews/airconditioning/ZoneView.qml
Normal file
@ -0,0 +1,182 @@
|
||||
import QtQuick 2.3
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Layouts 1.1
|
||||
import "qrc:/ui/components"
|
||||
import "qrc:/ui/customviews"
|
||||
import "qrc:/ui/delegates"
|
||||
import Nymea 1.0
|
||||
import NymeaApp.Utils 1.0
|
||||
import Nymea.AirConditioning 1.0
|
||||
|
||||
Item {
|
||||
id: root
|
||||
property AirConditioningManager acManager: null
|
||||
property ZoneInfoWrapper zoneWrapper: null
|
||||
readonly property ZoneInfo zone: zoneWrapper.zone
|
||||
|
||||
Flickable {
|
||||
id: flickable
|
||||
anchors.fill: parent
|
||||
contentHeight: contentLayout.childrenRect.height + Style.margins
|
||||
clip: true
|
||||
|
||||
GridLayout {
|
||||
id: contentLayout
|
||||
width: parent.width
|
||||
columns: app.landscape ? 2 : 1
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: flickable.height / 2
|
||||
implicitWidth: thermostat.implicitWidth
|
||||
visible: zoneWrapper.thermostats.count > 0
|
||||
|
||||
CircleBackground {
|
||||
id: thermostat
|
||||
anchors { fill: parent; leftMargin: Style.hugeMargins; rightMargin: Style.hugeMargins; topMargin: Style.margins; bottomMargin: Style.margins }
|
||||
|
||||
Dial {
|
||||
id: thermostatDial
|
||||
anchors.fill: parent
|
||||
minValue: 10
|
||||
maxValue: 30
|
||||
precision: 0.5
|
||||
value: root.zone.currentSetpoint
|
||||
color: pendingValue < activeValue ? Style.blue : Style.red
|
||||
activeValue: zoneWrapper.zoneTemperature
|
||||
|
||||
onMoved: {
|
||||
acManager.setZoneSetpointOverride(root.zone.id, value, ZoneInfo.SetpointOverrideModeEventual, 0)
|
||||
}
|
||||
onClicked: {
|
||||
var comp = Qt.createComponent(Qt.resolvedUrl("TimeOverrideDialog.qml"))
|
||||
var dialog = comp.createObject(root, {acManager: root.acManager, zone: root.zone})
|
||||
dialog.open()
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.centerIn: parent
|
||||
anchors.verticalCenterOffset: -Style.smallMargins
|
||||
width: parent.contentItem.width * 0.6
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: Types.toUiUnit(Types.UnitDegreeCelsius)
|
||||
font.pixelSize: Math.min(Style.smallFont.pixelSize, thermostat.contentItem.height / 16)
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: Types.toUiValue(zone.currentSetpoint, Types.UnitDegreeCelsius).toFixed(1)
|
||||
font.pixelSize: Math.min(Style.hugeFont.pixelSize, thermostat.contentItem.height / 8)
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
color: zoneWrapper.zoneTemperature == undefined
|
||||
? Stype.foregroundColor
|
||||
: zone.currentSetpoint > zoneWrapper.zoneTemperature
|
||||
? Style.red
|
||||
: zone.currentSetpoint < zoneWrapper.zoneTemperature
|
||||
? Style.blue
|
||||
: Style.foregroundColor
|
||||
}
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: Types.toUiValue(zoneWrapper.zoneTemperature, Types.UnitDegreeCelsius).toFixed(1)
|
||||
font.pixelSize: Math.min(Style.largeFont.pixelSize, thermostat.contentItem.height / 12)
|
||||
wrapMode: Text.WordWrap
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
ColorIcon {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
size: Style.smallIconSize
|
||||
name: {
|
||||
switch (root.zone.setpointOverrideMode) {
|
||||
case ZoneInfo.SetpointOverrideModeUnlimited:
|
||||
return "infinity"
|
||||
case ZoneInfo.SetpointOverrideModeTimed:
|
||||
return "alarm-clock"
|
||||
case ZoneInfo.SetpointOverrideModeEventual:
|
||||
return "event"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColorIcon {
|
||||
anchors.horizontalCenter: thermostatDial.horizontalCenter
|
||||
y: parent.contentItem.y + parent.contentItem.height - height - Style.smallMargins
|
||||
size: Math.min(Style.bigIconSize, thermostatDial.height / 5)
|
||||
name: zoneWrapper.heatingThermostats.count > 0
|
||||
? "../images/thermostat/heating.svg"
|
||||
: zoneWrapper.coolingThermostats.count > 0
|
||||
? "../images/thermostat/cooling.svg"
|
||||
: ""
|
||||
color: zoneWrapper.heatingThermostats.count > 0
|
||||
? app.interfaceToColor("heating")
|
||||
: zoneWrapper.coolingThermostats.count > 0
|
||||
? app.interfaceToColor("cooling")
|
||||
: Style.iconColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: zoneWrapper.thermostats.count > 0 ? flickable.height / 2 : flickable.height
|
||||
implicitWidth: 800
|
||||
implicitHeight: statusIcons.implicitHeight
|
||||
Layout.minimumHeight: statusIcons.implicitHeight
|
||||
|
||||
BigZoneStatusIcons {
|
||||
id: statusIcons
|
||||
acManager: root.acManager
|
||||
zoneWrapper: root.zoneWrapper
|
||||
iconSize: Style.bigIconSize
|
||||
width: parent.width - Style.margins * 2
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
ColorIcon {
|
||||
anchors { horizontalCenter: parent.horizontalCenter; bottom: parent.bottom; bottomMargin: Style.margins }
|
||||
name: "down"
|
||||
opacity: zoneWrapper.indoorSensors.count > 0 && flickable.contentY - flickable.originY <= 0 && !app.landscape ? 1 : 0
|
||||
Behavior on opacity { NumberAnimation { duration: Style.animationDuration } }
|
||||
}
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
Layout.fillWidth: true
|
||||
columns: Math.ceil(width / 600)
|
||||
Layout.columnSpan: contentLayout.columns
|
||||
rowSpacing: 0
|
||||
columnSpacing: 0
|
||||
|
||||
Repeater {
|
||||
model: zoneWrapper.indoorSensors
|
||||
delegate: SensorListDelegate {
|
||||
Layout.fillWidth: true
|
||||
thing: zoneWrapper.indoorSensors.get(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EmptyViewPlaceholder {
|
||||
visible: zoneWrapper.thermostats.count == 0 && zoneWrapper.windowSensors.count == 0 && zoneWrapper.indoorSensors.count == 0
|
||||
anchors.centerIn: parent
|
||||
width: parent.width - app.margins * 2
|
||||
title: qsTr("No things in this zone.")
|
||||
text: qsTr("In order for this zone zo be useful, assign some things to it.")
|
||||
imageSource: "/ui/images/sensors.svg"
|
||||
buttonText: qsTr("Add things")
|
||||
onButtonClicked: {
|
||||
pageStack.push(Qt.resolvedUrl("EditZoneThingsPage.qml"), {acManager: acManager, zone: zone})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
127
nymea-app/ui/mainviews/airconditioning/ZonesView.qml
Normal file
127
nymea-app/ui/mainviews/airconditioning/ZonesView.qml
Normal file
@ -0,0 +1,127 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2022, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
* This project including source code and documentation is protected by
|
||||
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||||
* reproduction, publication, editing and translation, are reserved. The use of
|
||||
* this project is subject to the terms of a license agreement to be concluded
|
||||
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||
* under https://nymea.io/license
|
||||
*
|
||||
* GNU General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under the
|
||||
* terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, GNU version 3. This project is distributed in the hope that it
|
||||
* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this project. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* For any further details and any questions please contact us under
|
||||
* contact@nymea.io or see our FAQ/Licensing Information on
|
||||
* https://nymea.io/license/faq
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
import QtQuick 2.8
|
||||
import QtQuick.Controls 2.1
|
||||
import QtQuick.Controls.Material 2.1
|
||||
import QtQuick.Layouts 1.2
|
||||
import QtGraphicalEffects 1.0
|
||||
import QtCharts 2.2
|
||||
import Nymea 1.0
|
||||
import Nymea.AirConditioning 1.0
|
||||
import "qrc:/ui/components"
|
||||
import "qrc:/ui/delegates"
|
||||
|
||||
Flickable {
|
||||
id: root
|
||||
contentHeight: contentGrid.implicitHeight
|
||||
|
||||
property AirConditioningManager acManager: null
|
||||
|
||||
GridLayout {
|
||||
id: contentGrid
|
||||
width: parent.width - app.margins
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
columns: Math.ceil(width / 600)
|
||||
rowSpacing: 0
|
||||
columnSpacing: 0
|
||||
|
||||
Repeater {
|
||||
model: !engine.thingManager.fetchingData ? acManager.zoneInfos : null
|
||||
|
||||
|
||||
delegate: BigTile {
|
||||
id: zoneDelegate
|
||||
Layout.preferredWidth: contentGrid.width / contentGrid.columns
|
||||
|
||||
readonly property ZoneInfo zone: acManager.zoneInfos.getZoneInfo(model.id)
|
||||
ZoneInfoWrapper {
|
||||
id: zoneWrapper
|
||||
zone: zoneDelegate.zone
|
||||
}
|
||||
|
||||
header: RowLayout {
|
||||
id: headerRow
|
||||
width: parent.width
|
||||
Layout.margins: Style.margins / 2
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: zoneDelegate.zone.name
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
// ThingStatusIcons {
|
||||
// thing: zoneDelegate.thermostat
|
||||
// }
|
||||
}
|
||||
|
||||
contentItem: RowLayout {
|
||||
spacing: Style.margins
|
||||
|
||||
ColumnLayout {
|
||||
|
||||
RowLayout {
|
||||
ColorIcon {
|
||||
name: app.interfaceToIcon("thermostat")
|
||||
size: Style.smallIconSize
|
||||
color: app.interfaceToColor("thermostat")
|
||||
}
|
||||
Label {
|
||||
text: Types.toUiValue(zoneDelegate.zone.currentSetpoint, Types.UnitDegreeCelsius).toFixed(1) + Types.toUiUnit(Types.UnitDegreeCelsius)
|
||||
font: Style.bigFont
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
ColorIcon {
|
||||
name: app.interfaceToIcon("temperaturesensor")
|
||||
size: Style.smallIconSize
|
||||
color: app.interfaceToColor("temperaturesensor")
|
||||
}
|
||||
Label {
|
||||
text: Types.toUiValue(zoneWrapper.zoneTemperature, Types.UnitDegreeCelsius).toFixed(1) + Types.toUiUnit(Types.UnitDegreeCelsius)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ZoneStatusIcons {
|
||||
zone: zoneDelegate.zone
|
||||
onClicked: zoneDelegate.clicked()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
pageStack.push(Qt.resolvedUrl("ZonePage.qml"), {zone: acManager.zoneInfos.get(index), acManager: acManager})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -462,7 +462,7 @@ MainViewBase {
|
||||
Component {
|
||||
id: editDialogComponent
|
||||
|
||||
MeaDialog {
|
||||
NymeaDialog {
|
||||
id: editDialog
|
||||
standardButtons: Dialog.NoButton
|
||||
|
||||
|
||||
@ -38,7 +38,7 @@ import NymeaApp.Utils 1.0
|
||||
import "../../components"
|
||||
import "../../delegates"
|
||||
|
||||
MeaDialog {
|
||||
NymeaDialog {
|
||||
id: root
|
||||
|
||||
title: qsTr("Add item")
|
||||
|
||||
@ -72,7 +72,7 @@ DashboardDelegateBase {
|
||||
|
||||
Component {
|
||||
id: configDialogComponent
|
||||
MeaDialog {
|
||||
NymeaDialog {
|
||||
id: configDialog
|
||||
|
||||
onAccepted: {
|
||||
|
||||
@ -154,7 +154,7 @@ DashboardDelegateBase {
|
||||
|
||||
Component {
|
||||
id: configDialogComponent
|
||||
MeaDialog {
|
||||
NymeaDialog {
|
||||
id: configDialog
|
||||
|
||||
onAccepted: {
|
||||
|
||||
@ -37,6 +37,7 @@ Item {
|
||||
readonly property var startTime: {
|
||||
var date = new Date(fixTime(now));
|
||||
date.setTime(date.getTime() - range * 60000 + 2000);
|
||||
print("startTIme:", date)
|
||||
return date;
|
||||
}
|
||||
|
||||
@ -393,11 +394,11 @@ Item {
|
||||
return toStorageSeries.calculateValue(entry) + Math.max(0, -entry.acquisition)
|
||||
}
|
||||
function addEntry(entry) {
|
||||
print("Adding return entry:", calculateValue(entry))
|
||||
// print("Adding return entry:", calculateValue(entry))
|
||||
returnUpperSeries.append(entry.timestamp.getTime(), calculateValue(entry))
|
||||
}
|
||||
function insertEntry(index, entry) {
|
||||
print("Adding return entry:", entry.acquisition, Math.max(0, -entry.acquisition), toStorageSeries.calculateValue(entry), calculateValue(entry))
|
||||
// print("Adding return entry:", entry.acquisition, Math.max(0, -entry.acquisition), toStorageSeries.calculateValue(entry), calculateValue(entry))
|
||||
returnUpperSeries.insert(index, entry.timestamp.getTime(), calculateValue(entry))
|
||||
}
|
||||
|
||||
|
||||
@ -162,7 +162,7 @@ SettingsPageBase {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Label {
|
||||
text: engine.systemController.serverTime.toLocaleTimeString(Locale.ShortTimeString)
|
||||
text: engine.systemController.serverTime.toLocaleTimeString(Qt.locale(), Locale.ShortFormat)
|
||||
Layout.fillWidth: true
|
||||
horizontalAlignment: Text.AlignRight
|
||||
}
|
||||
@ -226,7 +226,7 @@ SettingsPageBase {
|
||||
text: qsTr("Restart %1").arg(Configuration.systemName)
|
||||
visible: engine.systemController.powerManagementAvailable && engine.jsonRpcClient.ensureServerVersion("5.1")
|
||||
onClicked: {
|
||||
var dialog = Qt.createComponent(Qt.resolvedUrl("../components/MeaDialog.qml"));
|
||||
var dialog = Qt.createComponent(Qt.resolvedUrl("../components/NymeaDialog.qml"));
|
||||
var text = qsTr("Are you sure you want to restart %1 now?").arg(Configuration.systemName)
|
||||
var popup = dialog.createObject(app,
|
||||
{
|
||||
@ -249,7 +249,7 @@ SettingsPageBase {
|
||||
text: qsTr("Reboot %1 system").arg(Configuration.systemName)
|
||||
visible: engine.systemController.powerManagementAvailable
|
||||
onClicked: {
|
||||
var dialog = Qt.createComponent(Qt.resolvedUrl("../components/MeaDialog.qml"));
|
||||
var dialog = Qt.createComponent(Qt.resolvedUrl("../components/NymeaDialog.qml"));
|
||||
var text = qsTr("Are you sure you want to reboot your %1 sytem now?").arg(Configuration.systemName)
|
||||
var popup = dialog.createObject(app,
|
||||
{
|
||||
@ -271,7 +271,7 @@ SettingsPageBase {
|
||||
text: qsTr("Shut down %1 system").arg(Configuration.systemName)
|
||||
visible: engine.systemController.powerManagementAvailable
|
||||
onClicked: {
|
||||
var dialog = Qt.createComponent(Qt.resolvedUrl("../components/MeaDialog.qml"));
|
||||
var dialog = Qt.createComponent(Qt.resolvedUrl("../components/NymeaDialog.qml"));
|
||||
var text = qsTr("Are you sure you want to shut down your %1 sytem now?").arg(Configuration.systemName)
|
||||
var popup = dialog.createObject(app,
|
||||
{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user