Add support for push notifications
This commit is contained in:
parent
8d186ba5e5
commit
2e6a4f218c
@ -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 ¶ms)
|
||||
|
||||
JsonReply *AirConditioningJsonHandler::AddZone(const QVariantMap ¶ms)
|
||||
{
|
||||
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 ¶ms)
|
||||
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 ¶ms)
|
||||
{
|
||||
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)}});
|
||||
}
|
||||
|
||||
@ -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> ¬ifications)
|
||||
{
|
||||
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 ¬ificationThingId, 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> ¬ifications)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@ -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> ¬ifications);
|
||||
// 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> ¬ificationThigns);
|
||||
|
||||
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> ¬ifications);
|
||||
|
||||
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
101
notifications.cpp
Normal 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
37
notifications.h
Normal 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
|
||||
@ -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
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
|
||||
@ -46,6 +46,9 @@ public:
|
||||
void setTargetTemperature(double targetTemperature, bool force = false);
|
||||
void setWindowOpen(bool windowOpen);
|
||||
|
||||
bool hasTemperatureSensor() const;
|
||||
double temperature() const;
|
||||
|
||||
signals:
|
||||
|
||||
private:
|
||||
|
||||
50
zoneinfo.cpp
50
zoneinfo.cpp
@ -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> ¬ifications)
|
||||
{
|
||||
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;
|
||||
|
||||
25
zoneinfo.h
25
zoneinfo.h
@ -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> ¬ifications);
|
||||
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user