added closable sensor and fixed authentication/connected state
This commit is contained in:
parent
5be1f64faf
commit
baa087e988
@ -1,6 +1,6 @@
|
||||
# Home Connect
|
||||
|
||||
Connects your Home Connect home appliences to nymea.
|
||||
Connects your Home Connect home appliances to nymea.
|
||||
|
||||
## Supported Things
|
||||
|
||||
|
||||
@ -93,7 +93,7 @@ QUrl HomeConnect::getLoginUrl(const QUrl &redirectUrl, const QString &scope)
|
||||
queryParams.addQueryItem("scope", scope);
|
||||
queryParams.addQueryItem("state", QUuid::createUuid().toString());
|
||||
queryParams.addQueryItem("nonce", QUuid::createUuid().toString());
|
||||
m_codeChallenge = QUuid::createUuid().toString().remove('{').remove('}').remove("-");
|
||||
m_codeChallenge = QUuid::createUuid().toString().remove(QRegExp("[{}-]"));
|
||||
queryParams.addQueryItem("code_challenge", m_codeChallenge);
|
||||
queryParams.addQueryItem("code_challenge_method", "plain");
|
||||
url.setQuery(queryParams);
|
||||
@ -107,19 +107,28 @@ void HomeConnect::onRefreshTimeout()
|
||||
getAccessTokenFromRefreshToken(m_refreshToken);
|
||||
}
|
||||
|
||||
bool HomeConnect::checkStatusCode(int status, const QByteArray &payload)
|
||||
bool HomeConnect::checkStatusCode(QNetworkReply *reply, const QByteArray &rawData)
|
||||
{
|
||||
//TODO emit (dis)connected, (un)authenticated,
|
||||
QJsonParseError error;
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(payload, &error);
|
||||
if (error.error != QJsonParseError::NoError) {
|
||||
qCWarning(dcHomeConnect()) << "Received invalide JSON object" << payload;
|
||||
qCWarning(dcHomeConnect()) << "Status" << status;
|
||||
// Check for the internet connection
|
||||
if (reply->error() == QNetworkReply::NetworkError::HostNotFoundError ||
|
||||
reply->error() == QNetworkReply::NetworkError::UnknownNetworkError ||
|
||||
reply->error() == QNetworkReply::NetworkError::TemporaryNetworkFailureError) {
|
||||
qCWarning(dcHomeConnect()) << "Connection error" << reply->errorString();
|
||||
setConnected(false);
|
||||
setAuthenticated(false);
|
||||
return false;
|
||||
} else {
|
||||
setConnected(true);
|
||||
}
|
||||
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
QJsonParseError error;
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(rawData, &error);
|
||||
|
||||
switch (status){
|
||||
case 400:
|
||||
case 200: //The request was successful. Typically returned for successful GET requests.
|
||||
case 204: //The request was successful. Typically returned for successful PUT/DELETE requests with no payload.
|
||||
break;
|
||||
case 400: //Error occurred (e.g. validation error - value is out of range)
|
||||
if(!jsonDoc.toVariant().toMap().contains("error")) {
|
||||
if(jsonDoc.toVariant().toMap().value("error").toString() == "invalid_client") {
|
||||
qWarning(dcHomeConnect()) << "Client token provided doesn’t correspond to client that generated auth code.";
|
||||
@ -131,16 +140,54 @@ bool HomeConnect::checkStatusCode(int status, const QByteArray &payload)
|
||||
qWarning(dcHomeConnect()) << "Expired authorization code.";
|
||||
}
|
||||
}
|
||||
setAuthenticated(false);
|
||||
return false;
|
||||
case 401:
|
||||
qWarning(dcHomeConnect()) << "Client does not have permission to use this API.";
|
||||
setAuthenticated(false);
|
||||
return false;
|
||||
case 403:
|
||||
qCWarning(dcHomeConnect()) << "Forbidden, Scope has not been granted or home appliance is not assigned to HC account";
|
||||
setAuthenticated(false);
|
||||
return false;
|
||||
case 404:
|
||||
qCWarning(dcHomeConnect()) << "Not Found. This resource is not available (e.g. no images on washing machine)";
|
||||
return false;
|
||||
case 405:
|
||||
qWarning(dcHomeConnect()) << "Wrong HTTP method used.";
|
||||
setAuthenticated(false);
|
||||
return false;
|
||||
case 408:
|
||||
qCWarning(dcHomeConnect())<< "Request Timeout, API Server failed to produce an answer or has no connection to backend service";
|
||||
return false;
|
||||
case 409:
|
||||
qCWarning(dcHomeConnect()) << "Conflict - Command/Query cannot be executed for the home appliance, the error response contains the error details";
|
||||
qCWarning(dcHomeConnect()) << "Error" << jsonDoc.toVariant().toMap().value("error").toString();
|
||||
return false;
|
||||
case 415:
|
||||
qCWarning(dcHomeConnect())<< "Unsupported Media Type. The request's Content-Type is not supported";
|
||||
return false;
|
||||
case 429:
|
||||
qCWarning(dcHomeConnect())<< "Too Many Requests, the number of requests for a specific endpoint exceeded the quota of the client";
|
||||
return false;
|
||||
case 500:
|
||||
qCWarning(dcHomeConnect())<< "Internal Server Error, in case of a server configuration error or any errors in resource files";
|
||||
return false;
|
||||
case 503:
|
||||
qCWarning(dcHomeConnect())<< "Service Unavailable,if a required backend service is not available";
|
||||
return false;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (error.error != QJsonParseError::NoError) {
|
||||
qCWarning(dcHomeConnect()) << "Received invalide JSON object" << rawData;
|
||||
qCWarning(dcHomeConnect()) << "Status" << status;
|
||||
setAuthenticated(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
setAuthenticated(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -148,7 +195,7 @@ void HomeConnect::getAccessTokenFromRefreshToken(const QByteArray &refreshToken)
|
||||
{
|
||||
if (refreshToken.isEmpty()) {
|
||||
qWarning(dcHomeConnect) << "No refresh token given!";
|
||||
emit authenticationStatusChanged(false);
|
||||
setAuthenticated(false);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -166,37 +213,31 @@ void HomeConnect::getAccessTokenFromRefreshToken(const QByteArray &refreshToken)
|
||||
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply](){
|
||||
|
||||
|
||||
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
if (status != 200 || reply->error() != QNetworkReply::NoError) {
|
||||
qWarning(dcHomeConnect()) << "Access token error:" << reply->errorString() << reply->readAll();
|
||||
emit authenticationStatusChanged(false);
|
||||
return;
|
||||
}
|
||||
QJsonParseError error;
|
||||
QJsonDocument data = QJsonDocument::fromJson(reply->readAll(), &error);
|
||||
if (error.error != QJsonParseError::NoError) {
|
||||
emit authenticationStatusChanged(false);
|
||||
qCDebug(dcHomeConnect()) << "Received invalide JSON object" << data.toJson();
|
||||
QByteArray rawData = reply->readAll();
|
||||
if (!checkStatusCode(reply, rawData)) {
|
||||
return;
|
||||
}
|
||||
QJsonDocument data = QJsonDocument::fromJson(rawData);
|
||||
|
||||
if(!data.toVariant().toMap().contains("access_token")) {
|
||||
emit authenticationStatusChanged(false);
|
||||
setAuthenticated(false);
|
||||
return;
|
||||
}
|
||||
m_accessToken = data.toVariant().toMap().value("access_token").toByteArray();
|
||||
|
||||
if (data.toVariant().toMap().contains("expires_in")) {
|
||||
int expireTime = data.toVariant().toMap().value("expires_in").toInt();
|
||||
qCDebug(dcHomeConnect) << "Access token expires at" << QDateTime::currentDateTime().addSecs(expireTime).toString();
|
||||
qCDebug(dcHomeConnect) << "Access token expires int" << expireTime << "s, at" << QDateTime::currentDateTime().addSecs(expireTime).toString();
|
||||
if (!m_tokenRefreshTimer) {
|
||||
qWarning(dcHomeConnect()) << "Access token refresh timer not initialized";
|
||||
return;
|
||||
}
|
||||
if (expireTime < 20) {
|
||||
qCWarning(dcHomeConnect()) << "Expire time too short";
|
||||
return;
|
||||
}
|
||||
m_tokenRefreshTimer->start((expireTime - 20) * 1000);
|
||||
}
|
||||
emit authenticationStatusChanged(true);;
|
||||
});
|
||||
}
|
||||
|
||||
@ -227,15 +268,14 @@ void HomeConnect::getAccessTokenFromAuthorizationCode(const QByteArray &authoriz
|
||||
QNetworkReply *reply = m_networkManager->post(request, query.toString().toUtf8());
|
||||
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply](){
|
||||
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
if (status != 200 || reply->error() != QNetworkReply::NoError) {
|
||||
qWarning(dcHomeConnect()) << reply->errorString() << status << reply->readAll();
|
||||
emit authenticationStatusChanged(false);
|
||||
|
||||
QByteArray rawData = reply->readAll();
|
||||
if (!checkStatusCode(reply, rawData)) {
|
||||
return;
|
||||
}
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(reply->readAll());
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(rawData);
|
||||
if(!jsonDoc.toVariant().toMap().contains("access_token") || !jsonDoc.toVariant().toMap().contains("refresh_token") ) {
|
||||
emit authenticationStatusChanged(false);
|
||||
setAuthenticated(false);
|
||||
return;
|
||||
}
|
||||
m_accessToken = jsonDoc.toVariant().toMap().value("access_token").toByteArray();
|
||||
@ -243,15 +283,18 @@ void HomeConnect::getAccessTokenFromAuthorizationCode(const QByteArray &authoriz
|
||||
|
||||
if (jsonDoc.toVariant().toMap().contains("expires_in")) {
|
||||
int expireTime = jsonDoc.toVariant().toMap().value("expires_in").toInt();
|
||||
qCDebug(dcHomeConnect()) << "Token expires at" << QDateTime::currentDateTime().addSecs(expireTime).toString();
|
||||
qCDebug(dcHomeConnect()) << "Token expires in" << expireTime << "s, at" << QDateTime::currentDateTime().addSecs(expireTime).toString();
|
||||
if (!m_tokenRefreshTimer) {
|
||||
qWarning(dcHomeConnect()) << "Token refresh timer not initialized";
|
||||
emit authenticationStatusChanged(false);
|
||||
setAuthenticated(false);
|
||||
return;
|
||||
}
|
||||
if (expireTime < 20) {
|
||||
qCWarning(dcHomeConnect()) << "Expire time too short";
|
||||
return;
|
||||
}
|
||||
m_tokenRefreshTimer->start((expireTime - 20) * 1000);
|
||||
}
|
||||
emit authenticationStatusChanged(true);
|
||||
});
|
||||
}
|
||||
|
||||
@ -267,9 +310,10 @@ void HomeConnect::getHomeAppliances()
|
||||
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply](){
|
||||
|
||||
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
QByteArray rawData = reply->readAll();
|
||||
checkStatusCode(status, rawData);
|
||||
if (!checkStatusCode(reply, rawData)) {
|
||||
return;
|
||||
}
|
||||
QVariantMap dataMap = QJsonDocument::fromJson(rawData).toVariant().toMap().value("data").toMap();
|
||||
|
||||
QList<HomeAppliance> appliances;
|
||||
@ -304,9 +348,10 @@ void HomeConnect::getPrograms(const QString &haId)
|
||||
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||
connect(reply, &QNetworkReply::finished, this, [this, haId, reply]{
|
||||
|
||||
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
QByteArray rawData = reply->readAll();
|
||||
checkStatusCode(status, rawData);
|
||||
if (!checkStatusCode(reply, rawData)) {
|
||||
return;
|
||||
}
|
||||
|
||||
QVariantMap dataMap = QJsonDocument::fromJson(rawData).toVariant().toMap().value("data").toMap();
|
||||
QVariantList programList = dataMap.value("programs").toList();
|
||||
@ -334,9 +379,10 @@ void HomeConnect::getProgramsAvailable(const QString &haId)
|
||||
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||
connect(reply, &QNetworkReply::finished, this, [this, haId, reply]{
|
||||
|
||||
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
QByteArray rawData = reply->readAll();
|
||||
checkStatusCode(status, rawData);
|
||||
if (!checkStatusCode(reply, rawData)) {
|
||||
return;
|
||||
}
|
||||
|
||||
QVariantMap dataMap = QJsonDocument::fromJson(rawData).toVariant().toMap().value("data").toMap();
|
||||
QVariantList programList = dataMap.value("programs").toList();
|
||||
@ -364,9 +410,10 @@ void HomeConnect::getProgramsActive(const QString &haId)
|
||||
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||
connect(reply, &QNetworkReply::finished, this, [this, haId, reply]{
|
||||
|
||||
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
QByteArray rawData = reply->readAll();
|
||||
checkStatusCode(status, rawData);
|
||||
if (!checkStatusCode(reply, rawData)) {
|
||||
return;
|
||||
}
|
||||
|
||||
QVariantMap map = QJsonDocument::fromJson(rawData).toVariant().toMap();
|
||||
QHash<QString, QVariant> options;
|
||||
@ -396,9 +443,10 @@ void HomeConnect::getProgramsSelected(const QString &haId)
|
||||
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||
connect(reply, &QNetworkReply::finished, this, [this, haId, reply]{
|
||||
|
||||
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
QByteArray rawData = reply->readAll();
|
||||
checkStatusCode(status, rawData);
|
||||
if (!checkStatusCode(reply, rawData)) {
|
||||
return;
|
||||
}
|
||||
|
||||
QVariantMap map = QJsonDocument::fromJson(rawData).toVariant().toMap();
|
||||
QHash<QString, QVariant> options;
|
||||
@ -428,10 +476,10 @@ void HomeConnect::getProgramsActiveOption(const QString &haId, const QString &op
|
||||
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply]{
|
||||
|
||||
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
QByteArray rawData = reply->readAll();
|
||||
checkStatusCode(status, rawData);
|
||||
|
||||
if (!checkStatusCode(reply, rawData)) {
|
||||
return;
|
||||
}
|
||||
QVariantMap dataMap = QJsonDocument::fromJson(rawData).toVariant().toMap().value("data").toMap();
|
||||
qCDebug(dcHomeConnect()) << "key" << dataMap.value("key").toString() << "value" << dataMap.value("value").toString() << dataMap.value("unit").toString();
|
||||
});
|
||||
@ -638,9 +686,10 @@ void HomeConnect::getStatus(const QString &haid)
|
||||
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||
connect(reply, &QNetworkReply::finished, this, [this, haid, reply]{
|
||||
|
||||
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
QByteArray rawData = reply->readAll();
|
||||
checkStatusCode(status, rawData);
|
||||
if (!checkStatusCode(reply, rawData)) {
|
||||
return;
|
||||
}
|
||||
|
||||
QVariantMap dataMap = QJsonDocument::fromJson(rawData).toVariant().toMap().value("data").toMap();
|
||||
QHash<QString, QVariant> statusList;
|
||||
@ -676,9 +725,10 @@ void HomeConnect::getSettings(const QString &haid)
|
||||
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||
connect(reply, &QNetworkReply::finished, this, [this, haid, reply]{
|
||||
|
||||
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
QByteArray rawData = reply->readAll();
|
||||
checkStatusCode(status, rawData);
|
||||
if (!checkStatusCode(reply, rawData)) {
|
||||
return;
|
||||
}
|
||||
QVariantMap dataMap = QJsonDocument::fromJson(rawData).toVariant().toMap().value("data").toMap();
|
||||
QVariantList settingsList = dataMap.value("settings").toList();
|
||||
QHash<QString, QVariant> settings;
|
||||
@ -800,21 +850,35 @@ QUuid HomeConnect::sendCommand(const QString &haid, const QString &command)
|
||||
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||
connect(reply, &QNetworkReply::finished, this, [this, commandId, reply]{
|
||||
|
||||
//TODO check status
|
||||
QJsonParseError error;
|
||||
QJsonDocument data = QJsonDocument::fromJson(reply->readAll(), &error);
|
||||
if (error.error != QJsonParseError::NoError) {
|
||||
qCDebug(dcHomeConnect()) << "Send command: Received invalide JSON object";
|
||||
QByteArray rawData = reply->readAll();
|
||||
if (!checkStatusCode(reply, rawData)) {
|
||||
return;
|
||||
}
|
||||
qCDebug(dcHomeConnect()) << "Send command" << data.toJson();
|
||||
if (data.toVariant().toMap().contains("data")) {
|
||||
QVariantMap dataMap = data.toVariant().toMap().value("data").toMap();
|
||||
QVariantMap map = QJsonDocument::fromJson(rawData).toVariant().toMap();
|
||||
qCDebug(dcHomeConnect()) << "Send command" << map;
|
||||
if (map.contains("data")) {
|
||||
QVariantMap dataMap = map.value("data").toMap();
|
||||
qCDebug(dcHomeConnect()) << "key" << dataMap.value("key").toString() << "value" << dataMap.value("value").toString() << dataMap.value("unit").toString();
|
||||
} else if (data.toVariant().toMap().contains("error")) {
|
||||
qCWarning(dcHomeConnect()) << "Send command" << data.toVariant().toMap().value("error").toMap().value("description").toString();
|
||||
} else if (map.contains("error")) {
|
||||
qCWarning(dcHomeConnect()) << "Send command" << map.value("error").toMap().value("description").toString();
|
||||
}
|
||||
emit commandExecuted(commandId, true);
|
||||
});
|
||||
return commandId;
|
||||
}
|
||||
|
||||
void HomeConnect::setAuthenticated(bool state)
|
||||
{
|
||||
if (state != m_authenticated) {
|
||||
m_authenticated = state;
|
||||
emit authenticationStatusChanged(state);
|
||||
}
|
||||
}
|
||||
|
||||
void HomeConnect::setConnected(bool state)
|
||||
{
|
||||
if (state != m_connected) {
|
||||
m_connected = state;
|
||||
emit connectionChanged(state);
|
||||
}
|
||||
}
|
||||
|
||||
@ -159,7 +159,13 @@ private:
|
||||
NetworkAccessManager *m_networkManager = nullptr;
|
||||
QTimer *m_tokenRefreshTimer = nullptr;
|
||||
|
||||
bool checkStatusCode(int status, const QByteArray &payload);
|
||||
void setAuthenticated(bool state);
|
||||
void setConnected(bool state);
|
||||
|
||||
bool m_authenticated = false;
|
||||
bool m_connected = false;
|
||||
|
||||
bool checkStatusCode(QNetworkReply *reply, const QByteArray &rawData);
|
||||
private slots:
|
||||
void onRefreshTimeout();
|
||||
|
||||
|
||||
@ -77,11 +77,11 @@ IntegrationPluginHomeConnect::IntegrationPluginHomeConnect()
|
||||
m_remoteControlActivationStateTypeIds.insert(dishwasherThingClassId, dishwasherRemoteControlActivationStateStateTypeId);
|
||||
m_remoteControlActivationStateTypeIds.insert(washerThingClassId, washerRemoteControlActivationStateStateTypeId);
|
||||
|
||||
m_doorStateTypeIds.insert(dishwasherThingClassId, dishwasherDoorStateStateTypeId);
|
||||
m_doorStateTypeIds.insert(washerThingClassId, washerDoorStateStateTypeId);
|
||||
m_doorStateTypeIds.insert(dryerThingClassId, dryerDoorStateStateTypeId);
|
||||
m_doorStateTypeIds.insert(ovenThingClassId, ovenDoorStateStateTypeId);
|
||||
m_doorStateTypeIds.insert(coffeeMakerThingClassId, coffeeMakerDoorStateStateTypeId);
|
||||
m_doorStateTypeIds.insert(dishwasherThingClassId, dishwasherClosedStateTypeId);
|
||||
m_doorStateTypeIds.insert(washerThingClassId, washerClosedStateTypeId);
|
||||
m_doorStateTypeIds.insert(dryerThingClassId, dryerClosedStateTypeId);
|
||||
m_doorStateTypeIds.insert(ovenThingClassId, ovenClosedStateTypeId);
|
||||
m_doorStateTypeIds.insert(coffeeMakerThingClassId, coffeeMakerClosedStateTypeId);
|
||||
|
||||
m_operationStateTypeIds.insert(ovenThingClassId, ovenOperationStateStateTypeId);
|
||||
m_operationStateTypeIds.insert(dryerThingClassId, dryerOperationStateStateTypeId);
|
||||
@ -140,21 +140,29 @@ IntegrationPluginHomeConnect::IntegrationPluginHomeConnect()
|
||||
|
||||
void IntegrationPluginHomeConnect::startPairing(ThingPairingInfo *info)
|
||||
{
|
||||
if (info->thingClassId() == homeConnectConnectionThingClassId) {
|
||||
if (info->thingClassId() == homeConnectAccountThingClassId) {
|
||||
|
||||
bool simulationMode = configValue(homeConnectPluginSimulationModeParamTypeId).toBool();
|
||||
bool controlEnabled = configValue(homeConnectPluginControlEnabledParamTypeId).toBool();
|
||||
QByteArray clientKey = apiKeyStorage()->requestKey("homeconnect").data("clientKey");
|
||||
QByteArray clientSecret = apiKeyStorage()->requestKey("homeconnect").data("clientSecret");
|
||||
QByteArray clientKey = configValue(homeConnectPluginCustomClientKeyParamTypeId).toByteArray();
|
||||
QByteArray clientSecret = configValue(homeConnectPluginCustomClientSecretParamTypeId).toByteArray();
|
||||
if (clientKey.isEmpty() || clientSecret.isEmpty()) {
|
||||
clientKey = apiKeyStorage()->requestKey("homeconnect").data("clientKey");
|
||||
clientSecret = apiKeyStorage()->requestKey("homeconnect").data("clientSecret");
|
||||
}
|
||||
if (clientKey.isEmpty() || clientSecret.isEmpty()) {
|
||||
info->finish(Thing::ThingErrorAuthenticationFailure, tr("Client key and/or seceret is not available."));
|
||||
return;
|
||||
}
|
||||
HomeConnect *homeConnect = new HomeConnect(hardwareManager()->networkManager(), clientKey, clientSecret, simulationMode, this);
|
||||
QString scope = "IdentifyAppliance Monitor Settings Dishwasher Washer Dryer WasherDryer Refrigerator Freezer WineCooler CoffeeMaker Hood CookProcessor";
|
||||
if (controlEnabled)
|
||||
scope.append(" Control");
|
||||
QUrl url = homeConnect->getLoginUrl(QUrl("https://127.0.0.1:8888"), scope);
|
||||
qCDebug(dcHomeConnect()) << "HomeConnect url:" << url;
|
||||
m_setupHomeConnectConnections.insert(info->thingId(), homeConnect);
|
||||
info->setOAuthUrl(url);
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
m_setupHomeConnectConnections.insert(info->thingId(), homeConnect);
|
||||
} else {
|
||||
qCWarning(dcHomeConnect()) << "Unhandled pairing metod!";
|
||||
info->finish(Thing::ThingErrorCreationMethodNotSupported);
|
||||
@ -165,7 +173,7 @@ void IntegrationPluginHomeConnect::confirmPairing(ThingPairingInfo *info, const
|
||||
{
|
||||
Q_UNUSED(username);
|
||||
|
||||
if (info->thingClassId() == homeConnectConnectionThingClassId) {
|
||||
if (info->thingClassId() == homeConnectAccountThingClassId) {
|
||||
qCDebug(dcHomeConnect()) << "Redirect url is" << secret;
|
||||
QUrl url(secret);
|
||||
QUrlQuery query(url);
|
||||
@ -175,7 +183,6 @@ void IntegrationPluginHomeConnect::confirmPairing(ThingPairingInfo *info, const
|
||||
if (!homeConnect) {
|
||||
qWarning(dcHomeConnect()) << "No HomeConnect connection found for device:" << info->thingName();
|
||||
m_setupHomeConnectConnections.remove(info->thingId());
|
||||
homeConnect->deleteLater();
|
||||
info->finish(Thing::ThingErrorHardwareFailure);
|
||||
return;
|
||||
}
|
||||
@ -211,7 +218,7 @@ void IntegrationPluginHomeConnect::setupThing(ThingSetupInfo *info)
|
||||
{
|
||||
Thing *thing = info->thing();
|
||||
|
||||
if (thing->thingClassId() == homeConnectConnectionThingClassId) {
|
||||
if (thing->thingClassId() == homeConnectAccountThingClassId) {
|
||||
bool simulationMode = configValue(homeConnectPluginSimulationModeParamTypeId).toBool();
|
||||
HomeConnect *homeConnect;
|
||||
if (m_setupHomeConnectConnections.keys().contains(thing->id())) {
|
||||
@ -225,8 +232,20 @@ void IntegrationPluginHomeConnect::setupThing(ThingSetupInfo *info)
|
||||
pluginStorage()->beginGroup(thing->id().toString());
|
||||
QByteArray refreshToken = pluginStorage()->value("refresh_token").toByteArray();
|
||||
pluginStorage()->endGroup();
|
||||
QByteArray clientKey = apiKeyStorage()->requestKey("homeconnect").data("clientKey");
|
||||
QByteArray clientSecret = apiKeyStorage()->requestKey("homeconnect").data("clientSecret");
|
||||
if (refreshToken.isEmpty()) {
|
||||
info->finish(Thing::ThingErrorAuthenticationFailure, tr("Refresh token is not available."));
|
||||
return;
|
||||
}
|
||||
QByteArray clientKey = configValue(homeConnectPluginCustomClientKeyParamTypeId).toByteArray();
|
||||
QByteArray clientSecret = configValue(homeConnectPluginCustomClientSecretParamTypeId).toByteArray();
|
||||
if (clientKey.isEmpty() || clientSecret.isEmpty()) {
|
||||
clientKey = apiKeyStorage()->requestKey("homeconnect").data("clientKey");
|
||||
clientSecret = apiKeyStorage()->requestKey("homeconnect").data("clientSecret");
|
||||
}
|
||||
if (clientKey.isEmpty() || clientSecret.isEmpty()) {
|
||||
info->finish(Thing::ThingErrorAuthenticationFailure, tr("Client key and/or seceret is not available."));
|
||||
return;
|
||||
}
|
||||
homeConnect = new HomeConnect(hardwareManager()->networkManager(), clientKey, clientSecret, simulationMode, this);
|
||||
homeConnect->getAccessTokenFromRefreshToken(refreshToken);
|
||||
m_asyncSetup.insert(homeConnect, info);
|
||||
@ -269,14 +288,14 @@ void IntegrationPluginHomeConnect::postSetupThing(Thing *thing)
|
||||
if (!m_pluginTimer60sec) {
|
||||
m_pluginTimer60sec = hardwareManager()->pluginTimerManager()->registerTimer(60);
|
||||
connect(m_pluginTimer60sec, &PluginTimer::timeout, this, [this]() {
|
||||
foreach (Thing *thing, myThings().filterByThingClassId(homeConnectConnectionThingClassId)) {
|
||||
Q_FOREACH (Thing *thing, myThings().filterByThingClassId(homeConnectAccountThingClassId)) {
|
||||
HomeConnect *homeConnect = m_homeConnectConnections.value(thing);
|
||||
if (!homeConnect) {
|
||||
qWarning(dcHomeConnect()) << "No HomeConnect account found for" << thing->name();
|
||||
continue;
|
||||
}
|
||||
homeConnect->getHomeAppliances();
|
||||
Q_FOREACH(Thing *childThing, myThings().filterByParentId(thing->id())) {
|
||||
Q_FOREACH (Thing *childThing, myThings().filterByParentId(thing->id())) {
|
||||
QString haId = childThing->paramValue(m_idParamTypeIds.value(childThing->thingClassId())).toString();
|
||||
homeConnect->getStatus(haId);
|
||||
homeConnect->getSettings(haId);
|
||||
@ -286,12 +305,12 @@ void IntegrationPluginHomeConnect::postSetupThing(Thing *thing)
|
||||
});
|
||||
}
|
||||
|
||||
if (thing->thingClassId() == homeConnectConnectionThingClassId) {
|
||||
if (thing->thingClassId() == homeConnectAccountThingClassId) {
|
||||
HomeConnect *homeConnect = m_homeConnectConnections.value(thing);
|
||||
homeConnect->getHomeAppliances();
|
||||
homeConnect->connectEventStream();
|
||||
thing->setStateValue(homeConnectConnectionConnectedStateTypeId, true);
|
||||
thing->setStateValue(homeConnectConnectionLoggedInStateTypeId, true);
|
||||
thing->setStateValue(homeConnectAccountConnectedStateTypeId, true);
|
||||
thing->setStateValue(homeConnectAccountLoggedInStateTypeId, true);
|
||||
//TBD Set user name
|
||||
} else if ((thing->thingClassId() == dryerThingClassId) ||
|
||||
(thing->thingClassId() == fridgeThingClassId) ||
|
||||
@ -481,7 +500,7 @@ void IntegrationPluginHomeConnect::executeAction(ThingActionInfo *info)
|
||||
void IntegrationPluginHomeConnect::thingRemoved(Thing *thing)
|
||||
{
|
||||
qCDebug(dcHomeConnect) << "Delete " << thing->name();
|
||||
if (thing->thingClassId() == homeConnectConnectionThingClassId) {
|
||||
if (thing->thingClassId() == homeConnectAccountThingClassId) {
|
||||
m_homeConnectConnections.take(thing)->deleteLater();
|
||||
} else {
|
||||
m_selectedProgram.remove(thing);
|
||||
@ -546,7 +565,6 @@ void IntegrationPluginHomeConnect::executeBrowserItem(BrowserActionInfo *info)
|
||||
if (!homeConnect)
|
||||
return;
|
||||
QString haid = thing->paramValue(m_idParamTypeIds.value(thing->thingClassId())).toString();
|
||||
|
||||
QUuid requestId = homeConnect->selectProgram(haid, info->browserAction().itemId(), QList<HomeConnect::Option> ());
|
||||
m_selectedProgram.insert(thing, info->browserAction().itemId());
|
||||
|
||||
@ -581,8 +599,8 @@ void IntegrationPluginHomeConnect::parseKey(Thing *thing, const QString &key, co
|
||||
thing->setStateValue(ovenTargetTemperatureStateTypeId, value);
|
||||
} else if (key == "BSH.Common.Option.Duration") {
|
||||
thing->setStateValue(ovenDurationStateTypeId, value);
|
||||
} else if (key == "Cooking.Oven.Option.FastPreHeat") {
|
||||
} else if (key == "BSH.Common.Option.StartInRelative") {
|
||||
//} else if (key == "Cooking.Oven.Option.FastPreHeat") {
|
||||
//} else if (key == "BSH.Common.Option.StartInRelative") {
|
||||
} else if (key == "LaundryCare.Washer.Option.Temperature") {
|
||||
thing->setStateValue(washerTemperatureStateTypeId, value.toString().split('.').last()); // Cold, 20, 40, 60°C
|
||||
} else if (key == "LaundryCare.Washer.Option.SpinSpeed") {
|
||||
@ -620,14 +638,13 @@ void IntegrationPluginHomeConnect::parseKey(Thing *thing, const QString &key, co
|
||||
qCDebug(dcHomeConnect()) << "Unkown Coffee temperature string" << temperature;
|
||||
}
|
||||
} else if (key == "Cooking.Common.Option.Hood.VentingLevel") {
|
||||
//TODO
|
||||
} else if (key == "Cooking.Common.Option.Hood.IntensiveLevel") {
|
||||
//TODO
|
||||
} else if (key == "ConsumerProducts.CleaningRobot.Option.ReferenceMapId") {
|
||||
} else if (key == "ConsumerProducts.CleaningRobot.Option.CleaningMode") {
|
||||
thing->setStateValue(hoodVentingLevelStateTypeId, value);
|
||||
//} else if (key == "Cooking.Common.Option.Hood.IntensiveLevel") {
|
||||
//} else if (key == "ConsumerProducts.CleaningRobot.Option.ReferenceMapId") {
|
||||
//} else if (key == "ConsumerProducts.CleaningRobot.Option.CleaningMode") {
|
||||
|
||||
// Program Progress Changes
|
||||
} else if (key == "BSH.Common.Option.ElapsedProgramTime") {
|
||||
//} else if (key == "BSH.Common.Option.ElapsedProgramTime") {
|
||||
} else if (key == "BSH.Common.Option.RemainingProgramTime") {
|
||||
QString time = QDateTime::fromMSecsSinceEpoch(QDateTime::currentMSecsSinceEpoch()+(value.toInt()*1000)).time().toString();
|
||||
thing->setStateValue(m_endTimerStateTypeIds.value(thing->thingClassId()), time);
|
||||
@ -635,7 +652,7 @@ void IntegrationPluginHomeConnect::parseKey(Thing *thing, const QString &key, co
|
||||
if (m_progressStateTypeIds.contains(thing->thingClassId())) {
|
||||
thing->setStateValue(m_progressStateTypeIds.value(thing->thingClassId()), value);
|
||||
}
|
||||
} else if (key == "ConsumerProducts.CleaningRobot.Option.ProcessPhase") {
|
||||
//} else if (key == "ConsumerProducts.CleaningRobot.Option.ProcessPhase") {
|
||||
} else if (key == "BSH.Common.Status.OperationState") {
|
||||
if (m_operationStateTypeIds.contains(thing->thingClassId())) {
|
||||
QString operationState = value.toString();
|
||||
@ -683,12 +700,11 @@ void IntegrationPluginHomeConnect::parseKey(Thing *thing, const QString &key, co
|
||||
if (m_progressStateTypeIds.contains(thing->thingClassId())) {
|
||||
thing->setStateValue(m_progressStateTypeIds.value(thing->thingClassId()), 0);
|
||||
}
|
||||
} else if (key == "BSH.Common.Event.AlarmClockElapsed") {
|
||||
//} else if (key == "BSH.Common.Event.AlarmClockElapsed") {
|
||||
} else if (key == "Cooking.Oven.Event.PreheatFinished") {
|
||||
emitEvent(Event(ovenPreheatFinishedEventTypeId, thing->id()));
|
||||
// Home Appliance State Changes
|
||||
} else if (key == "BSH.Common.Setting.PowerState") {
|
||||
//Ignore
|
||||
//} else if (key == "BSH.Common.Setting.PowerState") {
|
||||
} else if (key == "BSH.Common.Status.RemoteControlActive") {
|
||||
if (m_remoteControlActivationStateTypeIds.contains(thing->thingClassId())) {
|
||||
thing->setStateValue(m_remoteControlActivationStateTypeIds.value(thing->thingClassId()), value);
|
||||
@ -703,7 +719,7 @@ void IntegrationPluginHomeConnect::parseKey(Thing *thing, const QString &key, co
|
||||
}
|
||||
} else if (key == "BSH.Common.Status.DoorState") {
|
||||
if (m_doorStateTypeIds.contains(thing->thingClassId())) {
|
||||
thing->setStateValue(m_doorStateTypeIds.value(thing->thingClassId()), value.toString().split('.').last());
|
||||
thing->setStateValue(m_doorStateTypeIds.value(thing->thingClassId()), value.toString().split('.').last() != "Open");
|
||||
}
|
||||
|
||||
// Home Appliance Events
|
||||
@ -736,32 +752,33 @@ void IntegrationPluginHomeConnect::parseKey(Thing *thing, const QString &key, co
|
||||
|
||||
void IntegrationPluginHomeConnect::parseSettingKey(Thing *thing, const QString &key, const QVariant &value)
|
||||
{
|
||||
if (key.contains("BSH.Common.Setting.PowerState")) {
|
||||
} else if (key.contains("BSH.Common.Setting.TemperatureUnit")) {
|
||||
} else if (key.contains("BSH.Common.Setting.LiquidVolumeUnit")) {
|
||||
} else if (key.contains("Refrigeration.FridgeFreezer.Setting.SetpointTemperatureRefrigerator")) {
|
||||
if (key.contains("Refrigeration.FridgeFreezer.Setting.SetpointTemperatureRefrigerator")) {
|
||||
thing->setStateValue(fridgeFridgeTemperatureSettingStateTypeId, value);
|
||||
} else if (key.contains("Refrigeration.FridgeFreezer.Setting.SetpointTemperatureFreezer")) {
|
||||
thing->setStateValue(fridgeFreezerTemperatureStateTypeId, value);
|
||||
} else if (key.contains("Refrigeration.Common.Setting.BottleCooler.SetpointTemperature")) {
|
||||
} else if (key.contains("Refrigeration.Common.Setting.ChillerLeft.SetpointTemperature")) {
|
||||
} else if (key.contains("Refrigeration.Common.Setting.ChillerCommon.SetpointTemperature")) {
|
||||
} else if (key.contains("Refrigeration.Common.Setting.ChillerRight.SetpointTemperature")) {
|
||||
} else if (key.contains("Refrigeration.Common.Setting.WineCompartment.SetpointTemperature")) {
|
||||
} else if (key.contains("Refrigeration.Common.Setting.WineCompartment2.SetpointTemperature")) {
|
||||
} else if (key.contains("Refrigeration.Common.Setting.WineCompartment3.SetpointTemperature")) {
|
||||
} else if (key.contains("Refrigeration.FridgeFreezer.Setting.SuperModeRefrigerator")) {
|
||||
} else if (key.contains("Refrigeration.FridgeFreezer.Setting.SuperModeFreezer")) {
|
||||
} else if (key.contains("Refrigeration.Common.Setting.EcoMode")) {
|
||||
} else if (key.contains("Refrigeration.Common.Setting.SabbathMode")) {
|
||||
} else if (key.contains("Refrigeration.Common.Setting.VacationMode")) {
|
||||
} else if (key.contains("Refrigeration.Common.Setting.FreshMode")) {
|
||||
} else if (key.contains("Cooking.Common.Setting.Lighting")) {
|
||||
} else if (key.contains("Cooking.Common.Setting.LightingBrightness")) {
|
||||
} else if (key.contains("BSH.Common.Setting.AmbientLightEnabled")) {
|
||||
} else if (key.contains("BSH.Common.Setting.AmbientLightBrightness")) {
|
||||
} else if (key.contains("BSH.Common.Setting.AmbientLightColor")) {
|
||||
} else if (key.contains("BSH.Common.Setting.AmbientLightCustomColor")) {
|
||||
// For future improvements
|
||||
//} else if (key.contains("BSH.Common.Setting.PowerState")) {
|
||||
//} else if (key.contains("BSH.Common.Setting.TemperatureUnit")) {
|
||||
//} else if (key.contains("BSH.Common.Setting.LiquidVolumeUnit")) {
|
||||
//} else if (key.contains("Refrigeration.Common.Setting.BottleCooler.SetpointTemperature")) {
|
||||
//} else if (key.contains("Refrigeration.Common.Setting.ChillerLeft.SetpointTemperature")) {
|
||||
//} else if (key.contains("Refrigeration.Common.Setting.ChillerCommon.SetpointTemperature")) {
|
||||
//} else if (key.contains("Refrigeration.Common.Setting.ChillerRight.SetpointTemperature")) {
|
||||
//} else if (key.contains("Refrigeration.Common.Setting.WineCompartment.SetpointTemperature")) {
|
||||
//} else if (key.contains("Refrigeration.Common.Setting.WineCompartment2.SetpointTemperature")) {
|
||||
//} else if (key.contains("Refrigeration.Common.Setting.WineCompartment3.SetpointTemperature")) {
|
||||
//} else if (key.contains("Refrigeration.FridgeFreezer.Setting.SuperModeRefrigerator")) {
|
||||
//} else if (key.contains("Refrigeration.FridgeFreezer.Setting.SuperModeFreezer")) {
|
||||
//} else if (key.contains("Refrigeration.Common.Setting.EcoMode")) {
|
||||
//} else if (key.contains("Refrigeration.Common.Setting.SabbathMode")) {
|
||||
//} else if (key.contains("Refrigeration.Common.Setting.VacationMode")) {
|
||||
//} else if (key.contains("Refrigeration.Common.Setting.FreshMode")) {
|
||||
//} else if (key.contains("Cooking.Common.Setting.Lighting")) {
|
||||
//} else if (key.contains("Cooking.Common.Setting.LightingBrightness")) {
|
||||
//} else if (key.contains("BSH.Common.Setting.AmbientLightEnabled")) {
|
||||
//} else if (key.contains("BSH.Common.Setting.AmbientLightBrightness")) {
|
||||
//} else if (key.contains("BSH.Common.Setting.AmbientLightColor")) {
|
||||
//} else if (key.contains("BSH.Common.Setting.AmbientLightCustomColor")) {
|
||||
}
|
||||
}
|
||||
|
||||
@ -771,7 +788,7 @@ void IntegrationPluginHomeConnect::onConnectionChanged(bool connected)
|
||||
Thing *thing = m_homeConnectConnections.key(homeConnect);
|
||||
if (!thing)
|
||||
return;
|
||||
thing->setStateValue(homeConnectConnectionConnectedStateTypeId, connected);
|
||||
thing->setStateValue(homeConnectAccountConnectedStateTypeId, connected);
|
||||
if (!connected) {
|
||||
Q_FOREACH(Thing *child, myThings().filterByParentId(thing->id())) {
|
||||
child->setStateValue(m_connectedStateTypeIds.value(child->thingClassId()), connected);
|
||||
@ -796,7 +813,7 @@ void IntegrationPluginHomeConnect::onAuthenticationStatusChanged(bool authentica
|
||||
if (!thing)
|
||||
return;
|
||||
|
||||
thing->setStateValue(homeConnectConnectionLoggedInStateTypeId, authenticated);
|
||||
thing->setStateValue(homeConnectAccountLoggedInStateTypeId, authenticated);
|
||||
if (!authenticated) {
|
||||
//refresh access token needs to be refreshed
|
||||
pluginStorage()->beginGroup(thing->id().toString());
|
||||
|
||||
@ -16,6 +16,20 @@
|
||||
"displayName": "Control enabled",
|
||||
"defaultValue": false,
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"id": "19cedaab-b8a0-4cab-80c7-0cae5fce124d",
|
||||
"name": "customClientKey",
|
||||
"displayName": "Custom client key",
|
||||
"defaultValue": "",
|
||||
"type": "QString"
|
||||
},
|
||||
{
|
||||
"id": "49b828ab-f495-4332-900b-99a9d37565b1",
|
||||
"name": "customClientSecret",
|
||||
"displayName": "Custom client secret",
|
||||
"defaultValue": "",
|
||||
"type": "QString"
|
||||
}
|
||||
],
|
||||
"apiKeys": ["homeconnect"],
|
||||
@ -27,8 +41,8 @@
|
||||
"thingClasses": [
|
||||
{
|
||||
"id": "babc1a39-730a-4516-95bf-ff51a8ce887a",
|
||||
"name": "homeConnectConnection",
|
||||
"displayName": "Home Connect connection",
|
||||
"name": "homeConnectAccount",
|
||||
"displayName": "Home Connect account",
|
||||
"interfaces": ["account"],
|
||||
"createMethods": ["user"],
|
||||
"setupMethod": "oauth",
|
||||
@ -66,7 +80,7 @@
|
||||
"id": "96845b7d-4c20-43a0-a810-ec505df3ee88",
|
||||
"name": "oven",
|
||||
"displayName": "Oven",
|
||||
"interfaces": ["connectable"],
|
||||
"interfaces": ["connectable", "closablesensor"],
|
||||
"createMethods": ["auto"],
|
||||
"browsable": true,
|
||||
"paramTypes": [
|
||||
@ -90,15 +104,11 @@
|
||||
},
|
||||
{
|
||||
"id": "e892ca9e-5b31-41f5-a568-44474091f0f6",
|
||||
"name": "doorState",
|
||||
"displayName": "Door state",
|
||||
"displayNameEvent": "Door state changed",
|
||||
"defaultValue": "Open",
|
||||
"type": "QString",
|
||||
"possibleValues": [
|
||||
"Open",
|
||||
"Closed"
|
||||
]
|
||||
"name": "closed",
|
||||
"displayName": "Door closed",
|
||||
"displayNameEvent": "Door closed changed",
|
||||
"defaultValue": false,
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"id": "c9f9bd33-513f-4834-a504-c2c1611fb4be",
|
||||
@ -241,15 +251,11 @@
|
||||
},
|
||||
{
|
||||
"id": "8cbb3746-7e04-4fc8-93eb-b774b606a057",
|
||||
"name": "doorState",
|
||||
"displayName": "Door state",
|
||||
"displayNameEvent": "Door state changed",
|
||||
"defaultValue": "Open",
|
||||
"type": "QString",
|
||||
"possibleValues": [
|
||||
"Open",
|
||||
"Closed"
|
||||
]
|
||||
"name": "closed",
|
||||
"displayName": "Door closed",
|
||||
"displayNameEvent": "Door closed changed",
|
||||
"defaultValue": false,
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"id": "2b45fbfc-d3d7-4dd4-91f8-4a789405246e",
|
||||
@ -369,15 +375,11 @@
|
||||
},
|
||||
{
|
||||
"id": "5011efa7-9915-4ecc-b717-6ced369abcb7",
|
||||
"name": "doorState",
|
||||
"displayName": "Door state",
|
||||
"displayNameEvent": "Door state changed",
|
||||
"defaultValue": "Open",
|
||||
"type": "QString",
|
||||
"possibleValues": [
|
||||
"Open",
|
||||
"Closed"
|
||||
]
|
||||
"name": "closed",
|
||||
"displayName": "Door closed",
|
||||
"displayNameEvent": "Door closed changed",
|
||||
"defaultValue": false,
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"id": "693fc8d2-d9a9-4109-b8c5-f3d22091500c",
|
||||
@ -540,16 +542,11 @@
|
||||
},
|
||||
{
|
||||
"id": "81aa609e-69c0-4d5c-98f0-46e6f14c7eaa",
|
||||
"name": "doorState",
|
||||
"displayName": "Door state",
|
||||
"displayNameEvent": "Door state changed",
|
||||
"defaultValue": "Open",
|
||||
"type": "QString",
|
||||
"possibleValues": [
|
||||
"Open",
|
||||
"Closed",
|
||||
"Locked"
|
||||
]
|
||||
"name": "closed",
|
||||
"displayName": "Door closed",
|
||||
"displayNameEvent": "Door closed changed",
|
||||
"defaultValue": false,
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"id": "52fde732-ed14-437e-8fbf-461d2ed19654",
|
||||
@ -682,15 +679,11 @@
|
||||
},
|
||||
{
|
||||
"id": "99bba70c-5ead-4076-8b71-720a931668a4",
|
||||
"name": "doorState",
|
||||
"displayName": "Door state",
|
||||
"displayNameEvent": "Door state changed",
|
||||
"defaultValue": "Open",
|
||||
"type": "QString",
|
||||
"possibleValues": [
|
||||
"Open",
|
||||
"Closed"
|
||||
]
|
||||
"name": "closed",
|
||||
"displayName": "Door closed",
|
||||
"displayNameEvent": "Door closed changed",
|
||||
"defaultValue": false,
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"id": "bc13977e-0ea1-4804-af00-311ae62c6c06",
|
||||
@ -785,16 +778,11 @@
|
||||
},
|
||||
{
|
||||
"id": "99296e86-09bf-4b74-b122-ee82b6bfdb62",
|
||||
"name": "doorState",
|
||||
"displayName": "Door state",
|
||||
"displayNameEvent": "Door state changed",
|
||||
"defaultValue": "Open",
|
||||
"type": "QString",
|
||||
"possibleValues": [
|
||||
"Open",
|
||||
"Closed",
|
||||
"Locked"
|
||||
]
|
||||
"name": "closed",
|
||||
"displayName": "Door closed",
|
||||
"displayNameEvent": "Door closed changed",
|
||||
"defaultValue": false,
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"id": "2ad3e2f8-c955-4e1d-b394-1e71a16f03bb",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user