IoT Devices: Add protection against out-of-date state

This commit is contained in:
Jon Beniston 2022-09-16 14:29:55 +01:00
parent ffa17d3a2d
commit ce5837a392
4 changed files with 296 additions and 215 deletions

View File

@ -154,6 +154,35 @@ void Device::setState(const QString &controlId, const QString &state)
qDebug() << "Device::setState: " << getProtocol() << " doesn't support QString. Can't set " << controlId << " to " << state;
}
void Device::recordGetRequest(void *ptr)
{
m_getRequests.insert(ptr, QDateTime::currentDateTime());
}
void Device::removeGetRequest(void *ptr)
{
m_getRequests.remove(ptr);
}
void Device::recordSetRequest(const QString &id, int guardMS)
{
m_setRequests.insert(id, QDateTime::currentDateTime().addMSecs(guardMS));
}
bool Device::getAfterSet(void *ptr, const QString &id)
{
if (m_getRequests.contains(ptr) && m_setRequests.contains(id))
{
QDateTime getTime = m_getRequests.value(ptr);
QDateTime setTime = m_setRequests.value(id);
return getTime > setTime;
}
else
{
return true;
}
}
const QStringList DeviceDiscoverer::m_typeStrings = {
"Auto",
"Boolean",

View File

@ -139,6 +139,14 @@ signals:
protected:
DeviceDiscoverer::DeviceInfo m_info;
QHash<void *, QDateTime> m_getRequests; // These data and functions help prevent using stale data from slow getStates
QHash<QString, QDateTime> m_setRequests;
void recordGetRequest(void *ptr);
void removeGetRequest(void *ptr);
void recordSetRequest(const QString &id, int guardMS=0);
bool getAfterSet(void *ptr, const QString &id);
};
#endif /* INCLUDE_DEVICE_H */

View File

@ -65,7 +65,8 @@ void HomeAssistantDevice::getState()
QNetworkRequest request(url);
request.setRawHeader("Authorization", "Bearer " + m_apiKey.toLocal8Bit());
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
m_networkManager->get(request);
QNetworkReply *reply = m_networkManager->get(request);
recordGetRequest(reply);
}
}
@ -84,6 +85,10 @@ void HomeAssistantDevice::setState(const QString &controlId, bool state)
document.setObject(object);
m_networkManager->post(request, document.toJson());
// 750ms guard, to try to avoid toggling of widget, while state updates on server
// Perhaps should be a setting
recordSetRequest(controlId, 750);
}
void HomeAssistantDevice::handleReply(QNetworkReply* reply)
@ -92,16 +97,23 @@ void HomeAssistantDevice::handleReply(QNetworkReply* reply)
{
if (!reply->error())
{
QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
QByteArray data = reply->readAll();
QJsonParseError error;
QJsonDocument document = QJsonDocument::fromJson(data, &error);
if (!document.isNull())
{
//qDebug() << "Received " << document;
// POSTs to /api/services return an array, GETs from /api/states return an object
if (document.isObject())
{
QHash<QString, QVariant> status;
QJsonObject obj = document.object();
if (obj.contains(QStringLiteral("entity_id")) && obj.contains(QStringLiteral("state")))
{
QString entityId = obj.value(QStringLiteral("entity_id")).toString();
if (getAfterSet(reply, entityId))
{
QHash<QString, QVariant> status;
QString state = obj.value(QStringLiteral("state")).toString();
bool dOk;
bool iOk;
@ -121,15 +133,18 @@ void HomeAssistantDevice::handleReply(QNetworkReply* reply)
emit deviceUpdated(status);
}
}
}
}
else
{
qDebug() << "HomeAssistantDevice::handleReply: Document is not an object: " << document;
qDebug() << "HomeAssistantDevice::handleReply: Error parsing JSON: " << error.errorString() << " at offset " << error.offset;
}
}
else
{
qDebug() << "HomeAssistantDevice::handleReply: error: " << reply->error();
}
removeGetRequest(reply);
reply->deleteLater();
}
else
@ -294,7 +309,7 @@ void HomeAssistantDeviceDiscoverer::handleReply(QNetworkReply* reply)
}
else
{
qDebug() << "HomeAssistantDeviceDiscoverer::handleReply: Error parson JSON: " << error.errorString() << " at offset " << error.offset;
qDebug() << "HomeAssistantDeviceDiscoverer::handleReply: Error parsing JSON: " << error.errorString() << " at offset " << error.offset;
}
emit deviceList(devices);
}

View File

@ -196,7 +196,9 @@ void TPLinkDevice::getState()
QJsonDocument document;
document.setObject(object);
m_networkManager->post(request, document.toJson());
QNetworkReply *reply = m_networkManager->post(request, document.toJson());
recordGetRequest(reply);
}
void TPLinkDevice::setState(const QString &controlId, bool state)
@ -242,6 +244,8 @@ void TPLinkDevice::setState(const QString &controlId, bool state)
document.setObject(object);
m_networkManager->post(request, document.toJson());
recordSetRequest(controlId);
}
void TPLinkDevice::handleReply(QNetworkReply* reply)
@ -264,7 +268,11 @@ void TPLinkDevice::handleReply(QNetworkReply* reply)
{
if (!reply->error())
{
QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
QByteArray data = reply->readAll();
QJsonParseError error;
QJsonDocument document = QJsonDocument::fromJson(data, &error);
if (!document.isNull())
{
if (document.isObject())
{
//qDebug() << "Received " << document;
@ -291,19 +299,25 @@ void TPLinkDevice::handleReply(QNetworkReply* reply)
QJsonObject childObj = childRef.toObject();
if (childObj.contains(QStringLiteral("state")) && childObj.contains(QStringLiteral("id")))
{
int state = childObj.value(QStringLiteral("state")).toInt();
QString id = childObj.value(QStringLiteral("id")).toString();
if (getAfterSet(reply, id))
{
int state = childObj.value(QStringLiteral("state")).toInt();
status.insert(id, state); // key should match id in discoverer
}
}
}
}
else if (sysInfoObj.contains(QStringLiteral("relay_state")))
{
if (getAfterSet(reply, "switch"))
{
int state = sysInfoObj.value(QStringLiteral("relay_state")).toInt();
status.insert("switch", state); // key should match id in discoverer
}
}
}
}
// KP115 has emeter, but KP105 doesn't
if (responseDataObj.contains(QStringLiteral("emeter")))
{
@ -354,9 +368,15 @@ void TPLinkDevice::handleReply(QNetworkReply* reply)
}
}
else
{
qDebug() << "TPLinkDevice::handleReply: Error parsing JSON: " << error.errorString() << " at offset " << error.offset;
}
}
else
{
qDebug() << "TPLinkDevice::handleReply: error: " << reply->error();
}
removeGetRequest(reply);
reply->deleteLater();
}
else
@ -462,7 +482,11 @@ void TPLinkDeviceDiscoverer::handleReply(QNetworkReply* reply)
{
if (!reply->error())
{
QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
QByteArray data = reply->readAll();
QJsonParseError error;
QJsonDocument document = QJsonDocument::fromJson(data, &error);
if (!document.isNull())
{
if (document.isObject())
{
//qDebug() << "Received " << document;
@ -620,6 +644,11 @@ void TPLinkDeviceDiscoverer::handleReply(QNetworkReply* reply)
}
}
else
{
qDebug() << "TPLinkDeviceDiscoverer::handleReply: Error parsing JSON: " << error.errorString() << " at offset " << error.offset;
}
}
else
{
qDebug() << "TPLinkDeviceDiscoverer::handleReply: error: " << reply->error();
}