Add support for push notifications

This commit is contained in:
Michael Zanetti 2023-02-25 22:04:09 +01:00
parent 8d186ba5e5
commit 2e6a4f218c
10 changed files with 406 additions and 60 deletions

View File

@ -63,6 +63,7 @@ AirConditioningJsonHandler::AirConditioningJsonHandler(AirConditioningManager *m
params.insert("o:windowSensors", QVariantList() << enumValueName(Uuid));
params.insert("o:indoorSensors", QVariantList() << enumValueName(Uuid));
params.insert("o:outdoorSensors", QVariantList() << enumValueName(Uuid));
params.insert("o:notifications", QVariantList() << enumValueName(Uuid));
returns.insert("airConditioningError", enumRef<AirConditioningManager::AirConditioningError>());
returns.insert("o:zone", objectRef<ZoneInfo>());
registerMethod("AddZone", description, params, returns);
@ -106,10 +107,11 @@ AirConditioningJsonHandler::AirConditioningJsonHandler(AirConditioningManager *m
params.clear(); returns.clear();
description = "Set Zone things";
params.insert("zoneId", enumValueName(Uuid));
params.insert("thermostats", QVariantList() << enumValueName(Uuid));
params.insert("windowSensors", QVariantList() << enumValueName(Uuid));
params.insert("indoorSensors", QVariantList() << enumValueName(Uuid));
params.insert("outdoorSensors", QVariantList() << enumValueName(Uuid));
params.insert("o:thermostats", QVariantList() << enumValueName(Uuid));
params.insert("o:windowSensors", QVariantList() << enumValueName(Uuid));
params.insert("o:indoorSensors", QVariantList() << enumValueName(Uuid));
params.insert("o:outdoorSensors", QVariantList() << enumValueName(Uuid));
params.insert("o:notifications", QVariantList() << enumValueName(Uuid));
returns.insert("airConditioningError", enumRef<AirConditioningManager::AirConditioningError>());
registerMethod("SetZoneThings", description, params, returns);
@ -161,7 +163,7 @@ JsonReply *AirConditioningJsonHandler::GetZones(const QVariantMap &params)
JsonReply *AirConditioningJsonHandler::AddZone(const QVariantMap &params)
{
QList<ThingId> thermostats, windowSensors, indoorSensors, outdoorSensors;
QList<ThingId> thermostats, windowSensors, indoorSensors, outdoorSensors, notifications;
foreach (const QVariant &id, params.value("thermostats").toList()) {
thermostats.append(id.toUuid());
}
@ -174,7 +176,10 @@ JsonReply *AirConditioningJsonHandler::AddZone(const QVariantMap &params)
foreach (const QVariant &id, params.value("outdoorSensors").toList()) {
outdoorSensors.append(id.toUuid());
}
QPair<AirConditioningManager::AirConditioningError, ZoneInfo> status = m_manager->addZone(params.value("name").toString(), thermostats, windowSensors, indoorSensors, outdoorSensors);
foreach (const QVariant &id, params.value("notificatiosn").toList()) {
notifications.append(id.toUuid());
}
QPair<AirConditioningManager::AirConditioningError, ZoneInfo> status = m_manager->addZone(params.value("name").toString(), thermostats, windowSensors, indoorSensors, outdoorSensors, notifications);
QVariantMap ret = {
{"airConditioningError", enumValueName(status.first)}
};
@ -230,19 +235,50 @@ JsonReply *AirConditioningJsonHandler::SetZoneWeekSchedule(const QVariantMap &pa
JsonReply *AirConditioningJsonHandler::SetZoneThings(const QVariantMap &params)
{
QUuid zoneId = params.value("zoneId").toUuid();
QList<ThingId> thermostats, windowSensors, indoorSensors, outdoorSensors;
foreach (const QVariant &variant, params.value("thermostats").toList()) {
thermostats.append(ThingId(variant.toUuid()));
ZoneInfo zone = m_manager->zone(zoneId);
QList<ThingId> thermostats, windowSensors, indoorSensors, outdoorSensors, notifications;
if (params.contains("thermostats")) {
foreach (const QVariant &variant, params.value("thermostats").toList()) {
thermostats.append(ThingId(variant.toUuid()));
}
} else {
thermostats = zone.thermostats();
}
foreach (const QVariant &variant, params.value("windowSensors").toList()) {
windowSensors.append(ThingId(variant.toUuid()));
if (params.contains("windowSensors")) {
foreach (const QVariant &variant, params.value("windowSensors").toList()) {
windowSensors.append(ThingId(variant.toUuid()));
}
} else {
windowSensors = zone.windowSensors();
}
foreach (const QVariant &variant, params.value("indoorSensors").toList()) {
indoorSensors.append(ThingId(variant.toUuid()));
if (params.contains("indoorSensors")) {
foreach (const QVariant &variant, params.value("indoorSensors").toList()) {
indoorSensors.append(ThingId(variant.toUuid()));
}
} else {
indoorSensors = zone.indoorSensors();
}
foreach (const QVariant &variant, params.value("outdoorSensors").toList()) {
outdoorSensors.append(ThingId(variant.toUuid()));
if (params.contains("outdoorSensors")) {
foreach (const QVariant &variant, params.value("outdoorSensors").toList()) {
outdoorSensors.append(ThingId(variant.toUuid()));
}
} else {
outdoorSensors = zone.outdoorSensors();
}
AirConditioningManager::AirConditioningError status = m_manager->setZoneThings(zoneId, thermostats, windowSensors, indoorSensors, outdoorSensors);
if (params.contains("notifications")) {
foreach (const QVariant &variant, params.value("notifications").toList()) {
notifications.append(ThingId(variant.toUuid()));
}
} else {
notifications = zone.notifications();
}
AirConditioningManager::AirConditioningError status = m_manager->setZoneThings(zoneId, thermostats, windowSensors, indoorSensors, outdoorSensors, notifications);
return createReply({{"airConditioningError", enumValueName(status)}});
}

View File

@ -52,6 +52,9 @@ AirConditioningManager::AirConditioningManager(ThingManager *thingManager, QObje
if (thing->thingClass().interfaces().contains("thermostat")) {
m_thermostats.insert(thing->id(), new Thermostat(m_thingManager, thing, this));
}
if (thing->thingClass().interfaces().contains("notifications")) {
m_notifications.insert(thing->id(), new Notifications(m_thingManager, thing, this));
}
}
loadZones();
@ -71,17 +74,17 @@ ZoneInfos AirConditioningManager::zones() const
return m_zones.values();
}
ZoneInfo AirConditioningManager::zone(const ThingId &thermostatId)
ZoneInfo AirConditioningManager::zone(const QUuid &zoneId)
{
return m_zones.value(thermostatId);
return m_zones.value(zoneId);
}
QPair<AirConditioningManager::AirConditioningError, ZoneInfo> AirConditioningManager::addZone(const QString &name, const QList<ThingId> &thermostats, const QList<ThingId> windowSensors, const QList<ThingId> indoorSensors, const QList<ThingId> outdoorSensors)
QPair<AirConditioningManager::AirConditioningError, ZoneInfo> AirConditioningManager::addZone(const QString &name, const QList<ThingId> &thermostats, const QList<ThingId> windowSensors, const QList<ThingId> indoorSensors, const QList<ThingId> outdoorSensors, const QList<ThingId> notifications)
{
ZoneInfo zone(QUuid::createUuid());
zone.setName(name);
zone.setWeekSchedule(TemperatureWeekSchedule::create());
AirConditioningError status = verifyThingIds(thermostats, windowSensors, indoorSensors, outdoorSensors);
AirConditioningError status = verifyThingIds(thermostats, windowSensors, indoorSensors, outdoorSensors, notifications);
if (status != AirConditioningErrorNoError) {
qCWarning(dcAirConditioning()) << "Invalid thing id" << status << "in" << thermostats;
return qMakePair<AirConditioningError, ZoneInfo>(status, ZoneInfo());
@ -91,6 +94,7 @@ QPair<AirConditioningManager::AirConditioningError, ZoneInfo> AirConditioningMan
zone.setWindowSensors(windowSensors);
zone.setIndoorSensors(indoorSensors);
zone.setOutdoorSensors(outdoorSensors);
zone.setNotifications(notifications);
m_zones.insert(zone.id(), zone);
saveZones();
@ -179,12 +183,12 @@ AirConditioningManager::AirConditioningError AirConditioningManager::setZoneWeek
return AirConditioningErrorNoError;
}
AirConditioningManager::AirConditioningError AirConditioningManager::setZoneThings(const QUuid &zoneId, const QList<ThingId> &thermostats, const QList<ThingId> &windowSensors, const QList<ThingId> &indoorSensors, const QList<ThingId> &outdoorSensors)
AirConditioningManager::AirConditioningError AirConditioningManager::setZoneThings(const QUuid &zoneId, const QList<ThingId> &thermostats, const QList<ThingId> &windowSensors, const QList<ThingId> &indoorSensors, const QList<ThingId> &outdoorSensors, const QList<ThingId> &notifications)
{
if (!m_zones.contains(zoneId)) {
return AirConditioningErrorZoneNotFound;
}
AirConditioningError status = verifyThingIds(thermostats, windowSensors, indoorSensors, outdoorSensors);
AirConditioningError status = verifyThingIds(thermostats, windowSensors, indoorSensors, outdoorSensors, notifications);
if (status != AirConditioningErrorNoError) {
return status;
}
@ -192,8 +196,9 @@ AirConditioningManager::AirConditioningError AirConditioningManager::setZoneThin
m_zones[zoneId].setWindowSensors(windowSensors);
m_zones[zoneId].setIndoorSensors(indoorSensors);
m_zones[zoneId].setOutdoorSensors(outdoorSensors);
m_zones[zoneId].setNotifications(notifications);
saveZones();
qCDebug(dcAirConditioning()) << "Zone things set. Thermostats:" << thermostats << "Window sensors:" << windowSensors << "indoor sensors:" << indoorSensors << "outdoor sensors:" << outdoorSensors;
qCDebug(dcAirConditioning()) << "Zone things set. Thermostats:" << thermostats << "Window sensors:" << windowSensors << "indoor sensors:" << indoorSensors << "outdoor sensors:" << outdoorSensors << "notifications:" << notifications;
emit zoneChanged(m_zones.value(zoneId));
updateZone(zoneId);
return AirConditioningErrorNoError;
@ -219,6 +224,10 @@ void AirConditioningManager::onThingAdded(Thing *thing)
qCInfo(dcAirConditioning()) << "Thermostat added:" << thing;
m_thermostats.insert(thing->id(), new Thermostat(m_thingManager, thing, this));
}
if (thing->thingClass().interfaces().contains("notifications")) {
qCInfo(dcAirConditioning()) << "Notifications added:" << thing;
m_notifications.insert(thing->id(), new Notifications(m_thingManager, thing, this));
}
}
void AirConditioningManager::onThingRemoved(const ThingId &thingId)
@ -228,6 +237,7 @@ void AirConditioningManager::onThingRemoved(const ThingId &thingId)
QList<ThingId> windowSensors = m_zones.value(zone.id()).windowSensors();
QList<ThingId> indoorSensors = m_zones.value(zone.id()).indoorSensors();
QList<ThingId> outdoorSensors = m_zones.value(zone.id()).outdoorSensors();
QList<ThingId> notifications = m_zones.value(zone.id()).notifications();
bool changed = false;
if (thermostats.contains(thingId)) {
thermostats.removeAll(thingId);
@ -245,28 +255,37 @@ void AirConditioningManager::onThingRemoved(const ThingId &thingId)
outdoorSensors.removeAll(thingId);
changed = true;
}
if (notifications.contains(thingId)) {
notifications.removeAll(thingId);
changed = true;
}
if (changed) {
setZoneThings(zone.id(), thermostats, windowSensors, indoorSensors, outdoorSensors);
setZoneThings(zone.id(), thermostats, windowSensors, indoorSensors, outdoorSensors, notifications);
}
}
}
void AirConditioningManager::onThingStateChaged(Thing *thing, const StateTypeId &stateTypeId, const QVariant &value, const QVariant &minValue, const QVariant &maxValue)
{
Q_UNUSED(stateTypeId)
Q_UNUSED(value)
Q_UNUSED(minValue)
Q_UNUSED(maxValue)
// We'll only want to immediately react on window open/close changes. Anything else is good enough once per minute
if (!thing->thingClass().interfaces().contains("closablesensor")) {
return;
}
StateType stateType = thing->thingClass().getStateType(stateTypeId);
foreach (const ZoneInfo &zone, m_zones) {
if (zone.windowSensors().contains(thing->id())) {
qCDebug(dcAirConditioning()) << "Window sensor in zone" << zone.name() << "changed";
bool changed = false;
if (zone.windowSensors().contains(thing->id()) && stateType.name() == "closed") {
qCDebug(dcAirConditioning()) << "Window sensor in zone" << zone.name() << "changed" << value;
changed = true;
}
if (zone.thermostats().contains(thing->id()) && stateType.name() == "temperature") {
qCDebug(dcAirConditioning()) << "Thermostat temperature sensor in zone" << zone.name() << "changed" << value;
changed = true;
}
if (zone.indoorSensors().contains(thing->id()) && QStringList{"temperature", "humidity", "voc", "pm25"}.contains(stateType.name())) {
qCDebug(dcAirConditioning()) << "Sensor for" << stateType.name() << "in zone" << zone.name() << "changed" << value;
changed = true;
}
if (changed) {
updateZone(zone.id());
}
}
@ -274,7 +293,7 @@ void AirConditioningManager::onThingStateChaged(Thing *thing, const StateTypeId
void AirConditioningManager::onActionExecuted(const Action &action, Thing::ThingError status)
{
if (action.triggeredBy() == Action::TriggeredByUser) {
if (action.triggeredBy() == Action::TriggeredByUser && status == Thing::ThingErrorNoError) {
Thing *thing = m_thingManager->findConfiguredThing(action.thingId());
if (thing && thing->thingClass().interfaces().contains("thermostat")) {
if (thing->thingClass().actionTypes().findById(action.actionTypeId()).name() == "targetTemperature") {
@ -353,46 +372,67 @@ void AirConditioningManager::updateZone(const QUuid &zoneId)
qCDebug(dcAirConditioning()) << "Window open" << windowOpen << "Override active:" << overrideActive << "Time schedule active:" << timeScheduleActive << "target:" << targetTemp;
bool highHumidity = false;
bool badAir = false;
// To determine the zone temperature we'll first check the thermostats if they have a temp sensor and use the highest value
// If no thermstats with temp sensors are available, we'll use the highest temp value from the indoor sensors.
bool tempFromThermostat = false;
bool tempFromSensors = false;
double temperature = 0;
foreach (const ThingId &thingId, zone.thermostats()) {
Thermostat *thermostat = m_thermostats.value(thingId);
if (thermostat) {
qCDebug(dcAirConditioning()) << "Setting window open" << windowOpen << " and target temp" << targetTemp;
thermostat->setWindowOpen(windowOpen);
thermostat->setTargetTemperature(targetTemp);
if (thermostat->hasTemperatureSensor()) {
qCDebug(dcAirConditioning()) << "Thermostat has temperature sensor:" << thermostat->temperature();
if (!tempFromThermostat) {
temperature = thermostat->temperature();
tempFromThermostat = true;
}
temperature = qMax(temperature, thermostat->temperature());
}
}
}
double humidity = 0;
uint voc = 0;
double pm25 = 0;
foreach (const ThingId &thingId, zone.indoorSensors()) {
Thing *thing = m_thingManager->findConfiguredThing(thingId);
if (thing->thingClass().interfaces().contains("humiditysensor")) {
if (thing->stateValue("humidity").toDouble() >= 60) { // > 60 over longer periods of time may cause mould
highHumidity = true;
if (!tempFromThermostat) {
if (thing->thingClass().interfaces().contains("temperaturesensor")) {
if (!tempFromSensors) {
temperature = thing->stateValue("temperature").toDouble();
tempFromSensors = true;
} else {
temperature = qMax(temperature, thing->stateValue("temperature").toDouble());
}
}
}
if (thing->thingClass().interfaces().contains("humiditysensor")) {
humidity = qMax(humidity, thing->stateValue("humidity").toDouble());
}
if (thing->thingClass().interfaces().contains("vocsensor")) {
if (thing->stateValue("voc").toDouble() >= 660) { // Moderate as of IAQ
badAir = true;
}
voc = qMax(voc, thing->stateValue("voc").toUInt());
}
if (thing->thingClass().interfaces().contains("pm25sensor")) {
if (thing->stateValue("pm25").toDouble() >= 25) { // Moderate as of CAQI
badAir = true;
}
pm25 = qMax(pm25, thing->stateValue("pm25").toDouble());
}
}
ZoneInfo::ZoneStatus newStatus = ZoneInfo::ZoneStatusFlagNone;
newStatus.setFlag(ZoneInfo::ZoneStatusFlagWindowOpen, windowOpen);
newStatus.setFlag(ZoneInfo::ZoneStatusFlagSetpointOverrideActive, overrideActive);
newStatus.setFlag(ZoneInfo::ZoneStatusFlagTimeScheduleActive, timeScheduleActive);
newStatus.setFlag(ZoneInfo::ZoneStatusFlagHighHumidity, highHumidity);
newStatus.setFlag(ZoneInfo::ZoneStatusFlagBadAir, badAir);
newStatus.setFlag(ZoneInfo::ZoneStatusFlagHighHumidity, humidity >= 65); // > 60 over longer periods of time may cause mould, 70 will cause mould
newStatus.setFlag(ZoneInfo::ZoneStatusFlagBadAir, voc >= 660 || pm25 >= 25); // VOC: 660 Moderate as of IAQ, PM25: 25 Moderate as of CAQI
if (zone.setpointOverrideMode() == ZoneInfo::SetpointOverrideModeEventual &&
newStatus != m_eventualOverrideCache.value(zone.id())) {
@ -403,11 +443,30 @@ void AirConditioningManager::updateZone(const QUuid &zoneId)
}
if (targetTemp != zone.currentSetpoint() || newStatus != zone.zoneStatus()) {
qCDebug(dcAirConditioning()) << "Modifying Zone: setpoint:" << targetTemp << "status:" << newStatus;
if (targetTemp != zone.currentSetpoint()
|| newStatus != zone.zoneStatus()
|| temperature != zone.temperature()
|| humidity != zone.humidity()
|| voc != zone.voc()
|| pm25 != zone.pm25()
) {
qCDebug(dcAirConditioning()) << "Modifying Zone: setpoint:" << targetTemp << "status:" << newStatus << "temp:" << temperature << "humidity:" << humidity << "VOC:" << voc << "PM25:" << pm25;
m_zones[zone.id()].setCurrentSetpoint(targetTemp);
m_zones[zone.id()].setZoneStatus(newStatus);
m_zones[zone.id()].setTemperature(temperature);
m_zones[zone.id()].setHumidity(humidity);
m_zones[zone.id()].setVoc(voc);
m_zones[zone.id()].setPm25(pm25);
emit zoneChanged(m_zones.value(zone.id()));
foreach (const ThingId &notificationThingId, zone.notifications()) {
Notifications *notifications = m_notifications.value(notificationThingId);
if (!notifications) {
qCWarning(dcAirConditioning()) << "Stale notification thing id in zone!" << notificationThingId << m_notifications.keys();
continue;
}
notifications->update(m_zones[zone.id()]);
}
}
}
@ -415,6 +474,7 @@ void AirConditioningManager::loadZones()
{
qCDebug(dcAirConditioning()) << "Loading zones";
QSettings settings(NymeaSettings::settingsPath() + "/airconditioning.conf", QSettings::IniFormat);
settings.beginGroup("zones");
qCDebug(dcAirConditioning()) << "child groups of zones" << settings.childKeys() << settings.childGroups();
foreach (const QString &key, settings.childGroups()) {
@ -447,8 +507,7 @@ void AirConditioningManager::loadZones()
settings.endGroup(); // weekSchedule
zone.setWeekSchedule(weekSchedule);
qCDebug(dcAirConditioning()) << "Loading thermostats" << settings.value("thermostats").toStringList() << settings.value("thermostats").toList();
QList<ThingId> thermostats, windowSensors, indoorSensors, outdoorSensors;
QList<ThingId> thermostats, windowSensors, indoorSensors, outdoorSensors, notifications;
foreach (const QString &thingId, settings.value("thermostats").toStringList()) {
thermostats.append(ThingId(thingId));
}
@ -465,8 +524,12 @@ void AirConditioningManager::loadZones()
outdoorSensors.append(ThingId(thingId));
}
zone.setOutdoorSensors(outdoorSensors);
foreach (const QString &thingId, settings.value("notifications").toStringList()) {
notifications.append(ThingId(thingId));
}
zone.setNotifications(notifications);
qCDebug(dcAirConditioning()) << "Zone Loaded:" << zone.thermostats();
qCDebug(dcAirConditioning()) << "Zone Loaded:" << zone.thermostats() << zone.notifications();
m_zones.insert(zoneId, zone);
settings.endGroup(); // zone
}
@ -504,7 +567,7 @@ void AirConditioningManager::saveZones()
}
settings.endGroup(); // weekSchedule
QStringList thermostats, windowSensors, indoorSensors, outdoorSensors;
QStringList thermostats, windowSensors, indoorSensors, outdoorSensors, notifications;
foreach (const ThingId &thingId, zone.thermostats()) {
thermostats.append(thingId.toString());
}
@ -522,13 +585,18 @@ void AirConditioningManager::saveZones()
}
settings.setValue("outdoorSensors", outdoorSensors);
foreach (const ThingId &thingId, zone.notifications()) {
notifications.append(thingId.toString());
}
settings.setValue("notifications", notifications);
settings.endGroup(); // zone
}
settings.endGroup();
}
AirConditioningManager::AirConditioningError AirConditioningManager::verifyThingIds(const QList<ThingId> &thermostats, const QList<ThingId> &windowSensors, const QList<ThingId> &indoorSensors, const QList<ThingId> &outdoorSensors)
AirConditioningManager::AirConditioningError AirConditioningManager::verifyThingIds(const QList<ThingId> &thermostats, const QList<ThingId> &windowSensors, const QList<ThingId> &indoorSensors, const QList<ThingId> &outdoorSensors, const QList<ThingId> &notifications)
{
foreach (const QUuid &thingId, thermostats) {
Thing *thing = m_thingManager->findConfiguredThing(thingId);
@ -566,6 +634,17 @@ AirConditioningManager::AirConditioningError AirConditioningManager::verifyThing
return AirConditioningErrorInvalidThingType;
}
}
foreach (const QUuid &thingId, notifications) {
Thing *thing = m_thingManager->findConfiguredThing(thingId);
if (!thing) {
qCWarning(dcAirConditioning()) << "No thing with id" << thingId;
return AirConditioningErrorThingNotFound;
}
if (!thing->thingClass().interfaces().contains("notifications")) {
qCWarning(dcAirConditioning()) << "Not a notification thing:" << thing->name();
return AirConditioningErrorInvalidThingType;
}
}
return AirConditioningErrorNoError;
}

View File

@ -39,6 +39,7 @@
#include "zoneinfo.h"
#include "thermostat.h"
#include "notifications.h"
class AirConditioningManager : public QObject
{
@ -56,8 +57,8 @@ public:
explicit AirConditioningManager(ThingManager *thingManager, QObject *parent = nullptr);
ZoneInfos zones() const;
ZoneInfo zone(const ThingId &thermostatId);
QPair<AirConditioningManager::AirConditioningError, ZoneInfo> addZone(const QString &name, const QList<ThingId> &thermostats, const QList<ThingId> windowSensors, const QList<ThingId> indoorSensors, const QList<ThingId> outdoorSensors);
ZoneInfo zone(const QUuid &thermostatId);
QPair<AirConditioningManager::AirConditioningError, ZoneInfo> addZone(const QString &name, const QList<ThingId> &thermostats, const QList<ThingId> windowSensors, const QList<ThingId> indoorSensors, const QList<ThingId> outdoorSensors, const QList<ThingId> notifications);
AirConditioningError removeZone(const QUuid &zoneId);
AirConditioningError setZoneName(const QUuid &zoneId, const QString &name);
@ -65,7 +66,7 @@ public:
AirConditioningError setZoneSetpointOverride(const QUuid &zoneId, double setpoint, ZoneInfo::SetpointOverrideMode mode, uint minutes);
AirConditioningError setZoneWeekSchedules(const QUuid &zoneId, const TemperatureWeekSchedule &temperatureWeekSchedule);
AirConditioningError setZoneThings(const QUuid &zoneId, const QList<ThingId> &thermostats, const QList<ThingId> &windowSensors, const QList<ThingId> &indoorSensors, const QList<ThingId> &outdoorSensors);
AirConditioningError setZoneThings(const QUuid &zoneId, const QList<ThingId> &thermostats, const QList<ThingId> &windowSensors, const QList<ThingId> &indoorSensors, const QList<ThingId> &outdoorSensors, const QList<ThingId> &notifications);
// AirConditioningError addThing(const QUuid &zoneId, const ThingId &thingId);
// AirConditioningError removeThing(const QUuid &zoneId, const ThingId &thingId);
@ -74,6 +75,7 @@ signals:
void zoneAdded(const ZoneInfo &zone);
void zoneRemoved(const QUuid &zoneId);
void zoneChanged(const ZoneInfo &zoneInfo);
void notificationThingsChanged(const QList<ThingId> &notificationThigns);
private slots:
void onThingAdded(Thing *thing);
@ -88,7 +90,7 @@ private:
void loadZones();
void saveZones();
AirConditioningError verifyThingIds(const QList<ThingId> &thermostats, const QList<ThingId> &windowSensors, const QList<ThingId> &indoorSensors, const QList<ThingId> &outdoorSensors);
AirConditioningError verifyThingIds(const QList<ThingId> &thermostats, const QList<ThingId> &windowSensors, const QList<ThingId> &indoorSensors, const QList<ThingId> &outdoorSensors, const QList<ThingId> &notifications);
private:
ThingManager *m_thingManager = nullptr;
@ -97,6 +99,7 @@ private:
QHash<ThingId, Thermostat*> m_thermostats;
QHash<QUuid, ZoneInfo> m_zones;
QHash<QUuid, ZoneInfo::ZoneStatus> m_eventualOverrideCache;
QHash<ThingId, Notifications*> m_notifications;
QDateTime m_lastUpdateTime;
};

101
notifications.cpp Normal file
View File

@ -0,0 +1,101 @@
#include "notifications.h"
#include <QUrlQuery>
Notifications::Notifications(ThingManager *thingManager, Thing *thing, QObject *parent)
: QObject{parent},
m_thingManager(thingManager),
m_thing(thing)
{
m_clearTimer.setInterval(30*60*1000);
m_clearTimer.setSingleShot(true);
connect(&m_clearTimer, &QTimer::timeout, this, [this](){
m_isShown = false;
});
}
void Notifications::update(const ZoneInfo &zone)
{
bool supportsUpdate = m_thing->thingClassId() == ThingClassId("f0dd4c03-0aca-42cc-8f34-9902457b05de")
&& m_thing->paramValue("service").toString() == "FB-GCM"
// Updating is only supported with versions that have the notificationId param
&& !m_thing->thingClass().actionTypes().findByName("notify").paramTypes().findByName("notificationId").id().isNull();
QString notificationId = "humidityalert-" + zone.id().toString();
QString title = "High humidity alert";
QString text = QString("Humidity in zone %1: %2 %").arg(zone.name()).arg(zone.humidity());
if (zone.zoneStatus().testFlag(ZoneInfo::ZoneStatusFlagHighHumidity)) {
if (!m_isShown) {
// show
updateNotification(notificationId, title, text, false, false);
} else if (supportsUpdate) {
// update
updateNotification(notificationId, title, text, false, false);
}
} else {
if (m_isShown && supportsUpdate) {
// remove
updateNotification(notificationId, title, text, false, true);
}
}
notificationId = "airalert-" + zone.id().toString();
title = "Bad air alert";
text = QString("Bad air in zone %1: %2").arg(zone.name());
QStringList airValues;
if (zone.voc() >= 660) {
airValues.append(QString("%1 ppm").arg(zone.voc()));
}
if (zone.pm25() >= 25) {
airValues.append(QString("%1 µg/m³").arg(zone.pm25()));
}
text = text.arg(airValues.join(","));
if (zone.zoneStatus().testFlag(ZoneInfo::ZoneStatusFlagBadAir)) {
if (!m_isShown) {
// show
updateNotification(notificationId, title, text, false, false);
} else if (supportsUpdate) {
// update
updateNotification(notificationId, title, text, false, false);
}
} else {
if (m_isShown && supportsUpdate) {
// remove
updateNotification(notificationId, title, text, false, true);
}
}
}
void Notifications::updateNotification(const QString &id, const QString &title, const QString &text, bool sound, bool remove)
{
ActionType actionType = m_thing->thingClass().actionTypes().findByName("notify");
Action action(actionType.id(), m_thing->id(), Action::TriggeredByRule);
ParamList params = ParamList{
Param(actionType.paramTypes().findByName("title").id(), title),
Param(actionType.paramTypes().findByName("body").id(), text),
};
if (m_thing->thingClassId() == ThingClassId("f0dd4c03-0aca-42cc-8f34-9902457b05de")) {
QUrlQuery data;
data.addQueryItem("open", "airconditioning");
params.append(Param(actionType.paramTypes().findByName("data").id(), data.toString()));
// For backwards compatibility, only add those if the plugin already has them
if (!m_thing->thingClass().actionTypes().findByName("notify").paramTypes().findByName("notificationId").id().isNull()) {
params.append(Param(actionType.paramTypes().findByName("notificationId").id(), id));
params.append(Param(actionType.paramTypes().findByName("sound").id(), sound));
params.append(Param(actionType.paramTypes().findByName("remove").id(), remove));
}
}
action.setParams(params);
ThingActionInfo *info = m_thingManager->executeAction(action);
connect(info, &ThingActionInfo::finished, this, [=](){
if (info->status() == Thing::ThingErrorNoError) {
m_isShown = !remove;
m_clearTimer.start();
}
});
}

37
notifications.h Normal file
View File

@ -0,0 +1,37 @@
#ifndef NOTIFICATIONS_H
#define NOTIFICATIONS_H
#include <QObject>
#include <QTimer>
#include <integrations/thing.h>
#include <integrations/thingmanager.h>
#include "zoneinfo.h"
class Notifications : public QObject
{
Q_OBJECT
public:
explicit Notifications(ThingManager *thingManager, Thing *thing, QObject *parent = nullptr);
void update(const ZoneInfo &zone);
signals:
private:
void updateNotification(const QString &id, const QString &title, const QString &text, bool sound, bool remove);
private:
ThingManager *m_thingManager = nullptr;
Thing *m_thing = nullptr;
ZoneInfo::ZoneStatus m_zoneStatus;
bool m_isShown = false;
// For devices that don't support updates/removals, we'll assume after some time that it's gone and we may need to show again
QTimer m_clearTimer;
};
#endif // NOTIFICATIONS_H

View File

@ -12,6 +12,7 @@ include(../config.pri)
HEADERS += experiencepluginairconditioning.h \
airconditioningjsonhandler.h \
airconditioningmanager.h \
notifications.h \
temperatureschedule.h \
thermostat.h \
zoneinfo.h
@ -19,6 +20,7 @@ HEADERS += experiencepluginairconditioning.h \
SOURCES += experiencepluginairconditioning.cpp \
airconditioningjsonhandler.cpp \
airconditioningmanager.cpp \
notifications.cpp \
temperatureschedule.cpp \
thermostat.cpp \
zoneinfo.cpp

View File

@ -128,3 +128,13 @@ void Thermostat::setWindowOpen(bool windowOpen)
}
}
bool Thermostat::hasTemperatureSensor() const
{
return m_thing->thingClass().interfaces().contains("temperaturesensor");
}
double Thermostat::temperature() const
{
return m_thing->stateValue("temperature").toDouble();
}

View File

@ -46,6 +46,9 @@ public:
void setTargetTemperature(double targetTemperature, bool force = false);
void setWindowOpen(bool windowOpen);
bool hasTemperatureSensor() const;
double temperature() const;
signals:
private:

View File

@ -139,6 +139,16 @@ void ZoneInfo::setOutdoorSensors(const QList<ThingId> &outdoorSensors)
m_outdoorSensors = outdoorSensors;
}
QList<ThingId> ZoneInfo::notifications() const
{
return m_notifications;
}
void ZoneInfo::setNotifications(const QList<ThingId> &notifications)
{
m_notifications = notifications;
}
ZoneInfo::ZoneStatus ZoneInfo::zoneStatus() const
{
return m_zoneStatus;
@ -154,6 +164,46 @@ void ZoneInfo::setZoneStatusFlag(ZoneStatusFlag flag, bool set)
m_zoneStatus.setFlag(flag, set);
}
double ZoneInfo::temperature() const
{
return m_temperature;
}
void ZoneInfo::setTemperature(double temperature)
{
m_temperature = temperature;
}
double ZoneInfo::humidity() const
{
return m_humidity;
}
void ZoneInfo::setHumidity(double humidity)
{
m_humidity = humidity;
}
uint ZoneInfo::voc() const
{
return m_voc;
}
void ZoneInfo::setVoc(uint voc)
{
m_voc = voc;
}
double ZoneInfo::pm25() const
{
return m_pm25;
}
void ZoneInfo::setPm25(double pm25)
{
m_pm25 = pm25;
}
TemperatureWeekSchedule ZoneInfo::weekSchedule() const
{
return m_weekSchedule;

View File

@ -53,7 +53,12 @@ class ZoneInfo
Q_PROPERTY(QList<ThingId> windowSensors READ windowSensors)
Q_PROPERTY(QList<ThingId> indoorSensors READ indoorSensors)
Q_PROPERTY(QList<ThingId> outdoorSensors READ outdoorSensors)
Q_PROPERTY(QList<ThingId> notifications READ notifications)
Q_PROPERTY(ZoneStatus zoneStatus READ zoneStatus)
Q_PROPERTY(double temperature READ temperature)
Q_PROPERTY(double humidity READ humidity)
Q_PROPERTY(uint voc READ voc)
Q_PROPERTY(double pm25 READ pm25)
Q_PROPERTY(TemperatureWeekSchedule weekSchedule READ weekSchedule)
public:
@ -109,10 +114,25 @@ public:
QList<ThingId> outdoorSensors() const;
void setOutdoorSensors(const QList<ThingId> &outdoorSensors);
QList<ThingId> notifications() const;
void setNotifications(const QList<ThingId> &notifications);
ZoneInfo::ZoneStatus zoneStatus() const;
void setZoneStatus(ZoneStatus zoneStatus);
void setZoneStatusFlag(ZoneStatusFlag flag, bool set);
double temperature() const;
void setTemperature(double temperature);
double humidity() const;
void setHumidity(double humidity);
uint voc() const;
void setVoc(uint voc);
double pm25() const;
void setPm25(double pm25);
TemperatureWeekSchedule weekSchedule() const;
void setWeekSchedule(const TemperatureWeekSchedule &weekSchedule);
@ -128,7 +148,12 @@ private:
QList<ThingId> m_windowSensors;
QList<ThingId> m_indoorSensors;
QList<ThingId> m_outdoorSensors;
QList<ThingId> m_notifications;
ZoneStatus m_zoneStatus = ZoneStatusFlagNone;
double m_temperature = 0;
double m_humidity = 0;
uint m_voc = 0;
double m_pm25 = 0;
TemperatureWeekSchedule m_weekSchedule;
};
Q_DECLARE_METATYPE(ZoneInfo)