diff --git a/eq-3/eqivabluetooth.cpp b/eq-3/eqivabluetooth.cpp index f4555819..78e22720 100644 --- a/eq-3/eqivabluetooth.cpp +++ b/eq-3/eqivabluetooth.cpp @@ -158,17 +158,6 @@ bool EqivaBluetooth::available() const return m_available; } -bool EqivaBluetooth::enabled() const -{ - return m_enabled; -} - -int EqivaBluetooth::setEnabled(bool enabled) -{ - emit enabledChanged(); - return setTargetTemperature(enabled ? m_cachedTargetTemp : 4.5); -} - bool EqivaBluetooth::locked() const { return m_locked; @@ -199,7 +188,7 @@ int EqivaBluetooth::setBoostEnabled(bool enabled) qreal EqivaBluetooth::targetTemperature() const { - return m_enabled ? m_targetTemp : m_cachedTargetTemp; + return m_targetTemp; } int EqivaBluetooth::setTargetTemperature(qreal targetTemperature) @@ -211,7 +200,6 @@ int EqivaBluetooth::setTargetTemperature(qreal targetTemperature) stream << static_cast(4.5 * 2); // 4.5 degrees is off } else { stream << static_cast(targetTemperature * 2); // Temperature *2 (thing only supports .5 precision) - m_cachedTargetTemp = targetTemperature; } return enqueue("SetTargetTemperature", data); } @@ -418,11 +406,10 @@ void EqivaBluetooth::characteristicChanged(const QLowEnergyCharacteristic &info, quint8 mode = (lockAndMode & 0x0F); m_targetTemp = 1.0 * rawTemp / 2; - m_enabled = m_targetTemp >= 5; if (m_targetTemp < 5) { m_targetTemp = 5; } - qCDebug(dcEQ3()) << m_name << "Status notification received: Enabled:" << m_enabled << "Temp:" << m_targetTemp << "Keylock:" << m_locked << "Window open:" << m_windowOpen << "Mode:" << mode << "Valve open:" << m_valveOpen << "Boost:" << m_boostEnabled << "Battery critical" << m_batteryCritical; + qCDebug(dcEQ3()) << m_name << "Status notification received: Enabled:" << "Temp:" << m_targetTemp << "Keylock:" << m_locked << "Window open:" << m_windowOpen << "Mode:" << mode << "Valve open:" << m_valveOpen << "Boost:" << m_boostEnabled << "Battery critical" << m_batteryCritical; m_boostEnabled = false; switch (mode) { @@ -449,7 +436,6 @@ void EqivaBluetooth::characteristicChanged(const QLowEnergyCharacteristic &info, break; } - emit enabledChanged(); emit lockedChanged(); emit boostEnabledChanged(); emit modeChanged(); diff --git a/eq-3/eqivabluetooth.h b/eq-3/eqivabluetooth.h index da202201..8a58b357 100644 --- a/eq-3/eqivabluetooth.h +++ b/eq-3/eqivabluetooth.h @@ -74,7 +74,6 @@ public: signals: void availableChanged(); - void enabledChanged(); void lockedChanged(); void boostEnabledChanged(); void modeChanged(); @@ -107,11 +106,9 @@ private: QString m_name; bool m_available = false; - bool m_enabled = false; bool m_locked = false; bool m_boostEnabled = false; qreal m_targetTemp = 0; - qreal m_cachedTargetTemp = 0; Mode m_mode = ModeAuto; bool m_windowOpen = false; quint8 m_valveOpen = 0; diff --git a/eq-3/integrationplugineq-3.cpp b/eq-3/integrationplugineq-3.cpp index 185bd257..f8124ac8 100644 --- a/eq-3/integrationplugineq-3.cpp +++ b/eq-3/integrationplugineq-3.cpp @@ -175,9 +175,9 @@ void IntegrationPluginEQ3::setupThing(ThingSetupInfo *info) thing->setStateValue(eqivaBluetoothConnectedStateTypeId, eqivaDevice->available()); }); // Power state - thing->setStateValue(eqivaBluetoothPowerStateTypeId, eqivaDevice->enabled()); - connect(eqivaDevice, &EqivaBluetooth::enabledChanged, thing, [thing, eqivaDevice](){ - thing->setStateValue(eqivaBluetoothPowerStateTypeId, eqivaDevice->enabled()); + thing->setStateValue(eqivaBluetoothHeatingOnStateTypeId, eqivaDevice->valveOpen() > 0); + connect(eqivaDevice, &EqivaBluetooth::valveOpenChanged, thing, [thing, eqivaDevice](){ + thing->setStateValue(eqivaBluetoothHeatingOnStateTypeId, eqivaDevice->valveOpen() > 0); }); // Boost state thing->setStateValue(eqivaBluetoothBoostStateTypeId, eqivaDevice->boostEnabled()); @@ -343,9 +343,7 @@ void IntegrationPluginEQ3::executeAction(ThingActionInfo *info) int commandId; EqivaBluetooth *eqivaDevice = m_eqivaDevices.value(thing); - if (action.actionTypeId() == eqivaBluetoothPowerActionTypeId) { - commandId = eqivaDevice->setEnabled(action.param(eqivaBluetoothPowerActionPowerParamTypeId).value().toBool()); - } else if (action.actionTypeId() == eqivaBluetoothTargetTemperatureActionTypeId) { + if (action.actionTypeId() == eqivaBluetoothTargetTemperatureActionTypeId) { commandId = eqivaDevice->setTargetTemperature(action.param(eqivaBluetoothTargetTemperatureActionTargetTemperatureParamTypeId).value().toReal()); } else if (action.actionTypeId() == eqivaBluetoothLockActionTypeId) { commandId = eqivaDevice->setLocked(action.param(eqivaBluetoothLockActionLockParamTypeId).value().toBool()); diff --git a/eq-3/integrationplugineq-3.json b/eq-3/integrationplugineq-3.json index 6ae043e6..36df24ba 100644 --- a/eq-3/integrationplugineq-3.json +++ b/eq-3/integrationplugineq-3.json @@ -533,7 +533,7 @@ "id": "3c51327b-a823-4479-bd4b-f4ba64267ed8", "name": "eqivaBluetooth", "displayName": "Eqiva Bluetooth Smart Radiator Thermostat", - "interfaces": ["heating", "thermostat", "wirelessconnectable", "battery"], + "interfaces": ["thermostat", "wirelessconnectable", "battery"], "createMethods": ["discovery"], "setupMethod": "JustAdd", "paramTypes": [ @@ -555,13 +555,11 @@ }, { "id": "cc5700f3-28b0-4653-b46d-770a99e6cea7", - "name": "power", - "displayName": "On", - "displayNameEvent": "On changed", - "displayNameAction": "Turn on/off", + "name": "heatingOn", + "displayName": "Heating", + "displayNameEvent": "Heating changed", "type": "bool", - "defaultValue": false, - "writable": true + "defaultValue": false }, { "id": "5e9035ed-317d-42ee-b7f4-2996c75ba939", diff --git a/genericthings/integrationplugingenericthings.cpp b/genericthings/integrationplugingenericthings.cpp index cb96c4a7..601ecb3f 100644 --- a/genericthings/integrationplugingenericthings.cpp +++ b/genericthings/integrationplugingenericthings.cpp @@ -642,12 +642,19 @@ void IntegrationPluginGenericThings::executeAction(ThingActionInfo *info) Q_ASSERT_X(false, "executeAction", QString("Unhandled actionTypeId: %1").arg(action.actionTypeId().toString()).toUtf8()); } else if (thing->thingClassId() == thermostatThingClassId) { - if (action.actionTypeId() == thermostatTemperatureSensorInputActionTypeId) { - thing->setStateValue(thermostatTemperatureSensorInputStateTypeId, action.param(thermostatTemperatureSensorInputActionTemperatureSensorInputParamTypeId).value()); + if (action.actionTypeId() == thermostatTemperatureActionTypeId) { + thing->setStateValue(thermostatTemperatureStateTypeId, action.param(thermostatTemperatureActionTemperatureParamTypeId).value()); } else if (action.actionTypeId() == thermostatTargetTemperatureActionTypeId) { - thing->setStateValue(thermostatTargetTemperatureStateTypeId, action.param(thermostatTargetTemperatureActionTargetTemperatureParamTypeId).value()); - } else if (action.actionTypeId() == thermostatPowerActionTypeId) { - thing->setStateValue(thermostatPowerStateTypeId, action.param(thermostatPowerActionPowerParamTypeId).value()); + double minSetting = thing->setting(thermostatSettingsMinTargetTemperatureParamTypeId).toDouble(); + double maxSetting = thing->setting(thermostatSettingsMaxTargetTemperatureParamTypeId).toDouble(); + double newTemp = action.param(thermostatTargetTemperatureActionTargetTemperatureParamTypeId).value().toDouble(); + newTemp = qMax(newTemp, minSetting); + newTemp = qMin(newTemp, maxSetting); + thing->setStateValue(thermostatTargetTemperatureStateTypeId, newTemp); + } else if (action.actionTypeId() == thermostatHeatingOnActionTypeId) { + thing->setStateValue(thermostatHeatingOnStateTypeId, action.param(thermostatHeatingOnActionHeatingOnParamTypeId).value()); + } else if (action.actionTypeId() == thermostatCoolingOnActionTypeId) { + thing->setStateValue(thermostatCoolingOnStateTypeId, action.param(thermostatCoolingOnActionCoolingOnParamTypeId).value()); } else { Q_ASSERT_X(false, "executeAction", QString("Unhandled actionTypeId: %1").arg(action.actionTypeId().toString()).toUtf8()); } @@ -655,6 +662,24 @@ void IntegrationPluginGenericThings::executeAction(ThingActionInfo *info) info->finish(Thing::ThingErrorNoError); return; + } else if (thing->thingClassId() == heatingThingClassId) { + if (action.actionTypeId() == heatingPowerActionTypeId) { + thing->setStateValue(heatingPowerStateTypeId, action.paramValue(heatingPowerActionPowerParamTypeId).toBool()); + } else { + Q_ASSERT_X(false, "executeAction", QString("Unhandled actionTypeId: %1").arg(action.actionTypeId().toString()).toUtf8()); + } + info->finish(Thing::ThingErrorNoError); + return; + + } else if (thing->thingClassId() == coolingThingClassId) { + if (action.actionTypeId() == coolingPowerActionTypeId) { + thing->setStateValue(coolingPowerStateTypeId, action.paramValue(coolingPowerActionPowerParamTypeId).toBool()); + } else { + Q_ASSERT_X(false, "executeAction", QString("Unhandled actionTypeId: %1").arg(action.actionTypeId().toString()).toUtf8()); + } + info->finish(Thing::ThingErrorNoError); + return; + } else if (thing->thingClassId() == sgReadyThingClassId) { if (action.actionTypeId() == sgReadyRelay1ActionTypeId) { thing->setStateValue(sgReadyRelay1StateTypeId, action.param(sgReadyRelay1ActionRelay1ParamTypeId).value()); @@ -851,15 +876,17 @@ void IntegrationPluginGenericThings::moveBlindToAngle(Action action, Thing *thin void IntegrationPluginGenericThings::thermostatCheckPowerOutputState(Thing *thing) { double targetTemperature = thing->stateValue(thermostatTargetTemperatureStateTypeId).toDouble(); - double actualTemperature = thing->stateValue(thermostatTemperatureSensorInputStateTypeId).toDouble(); + double actualTemperature = thing->stateValue(thermostatTemperatureStateTypeId).toDouble(); double temperatureDifference = thing->setting(thermostatSettingsTemperatureDifferenceParamTypeId).toDouble(); if (actualTemperature <= (targetTemperature-temperatureDifference)) { - thing->setStateValue(thermostatPowerStateTypeId, true); + thing->setStateValue(thermostatHeatingOnStateTypeId, true); } else if (actualTemperature >= targetTemperature) { - thing->setStateValue(thermostatPowerStateTypeId, false); - } else { - //Keep actual state - //Possible improvement add boost action where powerState = true is forced inside the hysteresis + thing->setStateValue(thermostatHeatingOnStateTypeId, false); + } + if (actualTemperature >= (targetTemperature+temperatureDifference)) { + thing->setStateValue(thermostatCoolingOnStateTypeId, true); + } else if (actualTemperature <= targetTemperature) { + thing->setStateValue(thermostatCoolingOnStateTypeId, false); } } diff --git a/genericthings/integrationplugingenericthings.json b/genericthings/integrationplugingenericthings.json index 54a6097c..2f17bc8c 100644 --- a/genericthings/integrationplugingenericthings.json +++ b/genericthings/integrationplugingenericthings.json @@ -1035,7 +1035,7 @@ "name": "thermostat", "displayName": "Generic thermostat", "createMethods": ["user"], - "interfaces": ["thermostat", "heating"], + "interfaces": ["thermostat"], "settingsTypes": [ { "id": "64bf308f-a543-4e02-b787-1a1714c1f978", @@ -1045,12 +1045,32 @@ "unit": "DegreeCelsius", "minValue": 0.00, "defaultValue": 2.00 + }, + { + "id": "67451c97-50e1-4ea6-ac43-4386fbd26698", + "name": "minTargetTemperature", + "displayName": "Minimum temperature", + "type": "double", + "unit": "DegreeCelsius", + "minValue": -20, + "maxValue": 49, + "defaultValue": -20 + }, + { + "id": "85608dd5-7e67-4c98-9e62-b97411681048", + "name": "maxTargetTemperature", + "displayName": "Maximum temperature", + "type": "double", + "unit": "DegreeCelsius", + "minValue": -19, + "maxValue": 50, + "defaultValue": 50 } ], "stateTypes": [ { "id": "0f808803-0e63-47df-b024-9685998ba663", - "name": "temperatureSensorInput", + "name": "temperature", "displayName": "Temperature sensor input", "displayNameEvent": "Temperature sensor input changed", "displayNameAction": "Set temperature sensor input", @@ -1078,10 +1098,61 @@ }, { "id": "1f6a0c39-4417-4e31-86db-9926cf81c345", + "name": "heatingOn", + "displayName": "Heating On/off", + "displayNameEvent": "Heating turned on/off", + "displayNameAction": "Turn heating on/off", + "type": "bool", + "defaultValue": false, + "writable": true, + "ioType": "digitalInput" + }, + { + "id": "cab7d4bd-f612-4d12-b3a4-0649e189810f", + "name": "coolingOn", + "displayName": "Cooling On/off", + "displayNameEvent": "Cooling turned on/off", + "displayNameAction": "Turn cooling on/off", + "type": "bool", + "defaultValue": false, + "writable": true, + "ioType": "digitalInput" + } + ] + }, + { + "id": "e808fc5b-12fb-46d4-bb5d-25a4586bc0ba", + "name": "heating", + "displayName": "Generic heater", + "createMethods": ["user"], + "interfaces": ["heating"], + "stateTypes": [ + { + "id": "bb6b5e3a-d4d9-4440-a098-0720c14ad679", "name": "power", - "displayName": "On/off", - "displayNameEvent": "Turned on/off", - "displayNameAction": "Turn on/off", + "displayName": "Heater on/off", + "displayNameEvent": "Heater turned on/off", + "displayNameAction": "Turn heater on/off", + "type": "bool", + "defaultValue": false, + "writable": true, + "ioType": "digitalInput" + } + ] + }, + { + "id": "09edbc07-d382-48a4-9b16-99992014aff9", + "name": "cooling", + "displayName": "Generic cooler", + "createMethods": ["user"], + "interfaces": ["cooling"], + "stateTypes": [ + { + "id": "918cfd2c-6692-4faa-acc6-18ebf93611ec", + "name": "power", + "displayName": "Cooler on/off", + "displayNameEvent": "Cooler turned on/off", + "displayNameAction": "Turn cooler on/off", "type": "bool", "defaultValue": false, "writable": true, diff --git a/simulation/integrationpluginsimulation.cpp b/simulation/integrationpluginsimulation.cpp index 0aa1cc46..216d1456 100644 --- a/simulation/integrationpluginsimulation.cpp +++ b/simulation/integrationpluginsimulation.cpp @@ -70,7 +70,6 @@ void IntegrationPluginSimulation::setupThing(ThingSetupInfo *info) thing->thingClassId() == extendedBlindThingClassId || thing->thingClassId() == rollerShutterThingClassId || thing->thingClassId() == fingerPrintSensorThingClassId || - thing->thingClassId() == thermostatThingClassId || thing->thingClassId() == barcodeScannerThingClassId || thing->thingClassId() == contactSensorThingClassId) { m_simulationTimers.insert(thing, new QTimer(thing)); @@ -82,6 +81,48 @@ void IntegrationPluginSimulation::setupThing(ThingSetupInfo *info) if (thing->thingClassId() == barcodeScannerThingClassId) { m_simulationTimers.value(thing)->start(10000); } + if (thing->thingClassId() == thermostatThingClassId) { + QTimer *t = new QTimer(thing); + connect(t, &QTimer::timeout, thing, [thing](){ + double targetTemp = thing->stateValue(thermostatTargetTemperatureStateTypeId).toDouble(); + double currentTemp = thing->stateValue(thermostatTemperatureStateTypeId).toDouble(); + bool heatingOn = thing->stateValue(thermostatHeatingOnStateTypeId).toBool(); + bool coolingOn = thing->stateValue(thermostatCoolingOnStateTypeId).toBool(); + bool boost = thing->stateValue(thermostatBoostStateTypeId).toBool(); + + // When we're heating, temp increases slowly until it's up on par with target temp + if (heatingOn) { + double diff = targetTemp - currentTemp; + currentTemp += 0.005 + diff * (boost ? 0.2 : 0.1); + if (currentTemp >= targetTemp) { + thing->setStateValue(thermostatHeatingOnStateTypeId, false); + } + } else { + // Decrease 1% per interval to simulate drop of temperature (assuming it's cold outside) + currentTemp = currentTemp * 0.995; + + // Start heating when we're more than 2 degrees lower than what we should be + if (currentTemp < targetTemp - 2) { + thing->setStateValue(thermostatHeatingOnStateTypeId, true); + } + } + + if (coolingOn) { + double diff = targetTemp - currentTemp; + currentTemp += diff * 0.1; + if (currentTemp <= targetTemp) { + thing->setStateValue(thermostatCoolingOnStateTypeId, false); + } + } else { + if (currentTemp > targetTemp + 2) { + thing->setStateValue(thermostatCoolingOnStateTypeId, true); + } + } + + thing->setStateValue(thermostatTemperatureStateTypeId, currentTemp); + }); + t->start(10000); + } if (thing->thingClassId() == contactSensorThingClassId) { m_simulationTimers.value(thing)->start(10000); @@ -166,29 +207,20 @@ void IntegrationPluginSimulation::executeAction(ThingActionInfo *info) } if (thing->thingClassId() == thermostatThingClassId) { - if (action.actionTypeId() == thermostatPowerActionTypeId) { - bool power = action.param(thermostatPowerActionPowerParamTypeId).value().toBool(); - if (!power && thing->stateValue(thermostatBoostStateTypeId).toBool()) { - thing->setStateValue(thermostatBoostStateTypeId, false); - } - qCDebug(dcSimulation()) << "Set power" << power << "for thermostat device" << thing->name(); - thing->setStateValue(thermostatPowerStateTypeId, power); - return info->finish(Thing::ThingErrorNoError); - } if (action.actionTypeId() == thermostatBoostActionTypeId) { bool boost = action.param(thermostatBoostActionBoostParamTypeId).value().toBool(); - if (boost && !thing->stateValue(thermostatPowerStateTypeId).toBool()) { - thing->setStateValue(thermostatPowerStateTypeId, true); - } qCDebug(dcSimulation()) << "Set boost" << boost << "for thermostat device" << thing->name(); thing->setStateValue(thermostatBoostStateTypeId, boost); - m_simulationTimers.value(thing)->start(5 * 60 * 1000); + QTimer *t = new QTimer(thing); + t->setInterval(5 * 60 * 1000); + t->setSingleShot(true); + connect(t, &QTimer::timeout, t, &QTimer::deleteLater); + connect(t, &QTimer::timeout, thing, [thing](){ + thing->setStateValue(thermostatBoostStateTypeId, false); + }); return info->finish(Thing::ThingErrorNoError); } if (action.actionTypeId() == thermostatTargetTemperatureActionTypeId) { - if (!thing->stateValue(thermostatPowerStateTypeId).toBool()) { - thing->setStateValue(thermostatPowerStateTypeId, true); - } double targetTemp = action.param(thermostatTargetTemperatureActionTargetTemperatureParamTypeId).value().toDouble(); qCDebug(dcSimulation()) << "Set targetTemp" << targetTemp << "for thermostat device" << thing->name(); thing->setStateValue(thermostatTargetTemperatureStateTypeId, targetTemp); diff --git a/simulation/integrationpluginsimulation.json b/simulation/integrationpluginsimulation.json index 5580fc3f..34130db3 100644 --- a/simulation/integrationpluginsimulation.json +++ b/simulation/integrationpluginsimulation.json @@ -161,7 +161,7 @@ "name": "heating", "displayName": "Heating", "createMethods": ["user"], - "interfaces": ["extendedheating"], + "interfaces": ["heating"], "paramTypes": [ ], "stateTypes": [ { @@ -228,7 +228,7 @@ "name": "thermostat", "displayName": "Thermostat", "createMethods": ["user"], - "interfaces": ["heating", "thermostat"], + "interfaces": ["thermostat"], "paramTypes": [ ], "stateTypes": [ { @@ -239,20 +239,35 @@ "displayNameAction": "Set target temperature", "type": "double", "unit": "DegreeCelsius", - "defaultValue": 5, + "defaultValue": 21, "minValue": 5, - "maxValue": 30, + "maxValue": 35, "writable": true }, + { + "id": "f3df52f0-ee1d-4163-a7b5-95d8f22b8841", + "name": "temperature", + "displayName": "Current temperature", + "displayNameEvent": "Current temperature changed", + "type": "double", + "unit": "DegreeCelsius", + "defaultValue": 18 + }, { "id": "94a7d50c-df3b-4590-a89e-9dae40ad84fa", - "name": "power", - "displayName": "Power", - "displayNameEvent": "Power changed", - "displayNameAction": "Set power", + "name": "heatingOn", + "displayName": "Heating on", + "displayNameEvent": "Heating turned on/off", "type": "bool", - "defaultValue": false, - "writable": true + "defaultValue": false + }, + { + "id": "4c696205-392a-45ed-aab5-3b7f97ddf286", + "name": "coolingOn", + "displayName": "Cooling on", + "displayNameEvent": "Cooling turned on/off", + "type": "bool", + "defaultValue": false }, { "id": "f892f660-87ff-458a-bfa0-5af08591233e", @@ -812,7 +827,7 @@ "name": "heatingRod", "displayName": "Heating Rod", "createMethods": ["user"], - "interfaces": ["connectable", "extendedheating", "temperaturesensor"], + "interfaces": ["connectable", "heating", "temperaturesensor"], "paramTypes": [ ], "stateTypes": [ { diff --git a/tado/integrationplugintado.json b/tado/integrationplugintado.json index d50901e6..32bba975 100644 --- a/tado/integrationplugintado.json +++ b/tado/integrationplugintado.json @@ -46,7 +46,7 @@ "id": "1a7bb944-fb9c-490a-8a4c-794b27282292", "name": "zone", "displayName": "Zone", - "interfaces": ["heating", "temperaturesensor", "humiditysensor", "connectable"], + "interfaces": ["thermostat", "heating", "temperaturesensor", "humiditysensor", "connectable"], "createMethods": ["auto"], "paramTypes": [ {