/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Copyright (C) 2015 Simon Stürz * * * * This file is part of nymea. * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public * * License as published by the Free Software Foundation; either * * version 2.1 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * Lesser General Public License for more details. * * * * You should have received a copy of the GNU Lesser General Public * * License along with this library; If not, see * * . * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "devicepluginnetatmo.h" #include "devices/device.h" #include "plugininfo.h" #include "network/networkaccessmanager.h" #include #include #include DevicePluginNetatmo::DevicePluginNetatmo() { } DevicePluginNetatmo::~DevicePluginNetatmo() { } void DevicePluginNetatmo::init() { } void DevicePluginNetatmo::setupDevice(DeviceSetupInfo *info) { Device *device = info->device(); if (device->deviceClassId() == netatmoConnectionDeviceClassId) { qCDebug(dcNetatmo) << "Setup netatmo connection" << device->name() << device->params(); if (!m_pluginTimer) { m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(600); connect(m_pluginTimer, &PluginTimer::timeout, this, &DevicePluginNetatmo::onPluginTimer); } OAuth2 *authentication = new OAuth2("561c015d49c75f0d1cce6e13", "GuvKkdtu7JQlPD47qTTepRR9hQ0CUPAj4Tae3Ohcq", this); authentication->setUrl(QUrl("https://api.netatmo.net/oauth2/token")); authentication->setUsername(device->paramValue(netatmoConnectionDeviceUsernameParamTypeId).toString()); authentication->setPassword(device->paramValue(netatmoConnectionDevicePasswordParamTypeId).toString()); authentication->setScope("read_station read_thermostat write_thermostat"); m_authentications.insert(authentication, device); // Update device connected state based on OAuth connected state connect(authentication, &OAuth2::authenticationChanged, device, [this, device, authentication](){ device->setStateValue(netatmoConnectionConnectedStateTypeId, authentication->authenticated()); if (authentication->authenticated()) { refreshData(device, authentication->token()); } }); authentication->startAuthentication(); // Report device setup finished when authentication reports success connect(authentication, &OAuth2::authenticationChanged, info, [info, authentication](){ if (!authentication->authenticated()) { authentication->deleteLater(); info->finish(Device::DeviceErrorAuthenticationFailure, QT_TR_NOOP("Error logging in to Netatmo server.")); return; } info->finish(Device::DeviceErrorNoError); }); return; } else if (device->deviceClassId() == indoorDeviceClassId) { qCDebug(dcNetatmo) << "Setup netatmo indoor base station" << device->params(); NetatmoBaseStation *indoor = new NetatmoBaseStation(device->paramValue(indoorDeviceNameParamTypeId).toString(), device->paramValue(indoorDeviceMacParamTypeId).toString(), this); m_indoorDevices.insert(indoor, device); connect(indoor, SIGNAL(statesChanged()), this, SLOT(onIndoorStatesChanged())); return info->finish(Device::DeviceErrorNoError); } else if (device->deviceClassId() == outdoorDeviceClassId) { qCDebug(dcNetatmo) << "Setup netatmo outdoor module" << device->params(); // Migrate device parameters after changing param type UUIDs in 0.14. QMap migrationMap; migrationMap.insert("a97d256c-e159-4aa0-bc71-6bd7cd0688b3", outdoorDeviceNameParamTypeId); migrationMap.insert("157d470a-e579-4d0e-b879-6b5bfa8e34ae", outdoorDeviceMacParamTypeId); ParamList migratedParams; foreach (const Param &oldParam, device->params()) { QString oldId = oldParam.paramTypeId().toString(); oldId.remove(QRegExp("[{}]")); if (migrationMap.contains(oldId)) { ParamTypeId newId = migrationMap.value(oldId); QVariant oldValue = oldParam.value(); qCDebug(dcNetatmo()) << "Migrating netatmo outdoor station param:" << oldId << "->" << newId << ":" << oldValue; Param newParam(newId, oldValue); migratedParams << newParam; } else { migratedParams << oldParam; } } device->setParams(migratedParams); // Migration done NetatmoOutdoorModule *outdoor = new NetatmoOutdoorModule(device->paramValue(outdoorDeviceNameParamTypeId).toString(), device->paramValue(outdoorDeviceMacParamTypeId).toString(), device->paramValue(outdoorDeviceBaseStationParamTypeId).toString(), this); m_outdoorDevices.insert(outdoor, device); connect(outdoor, SIGNAL(statesChanged()), this, SLOT(onOutdoorStatesChanged())); return info->finish(Device::DeviceErrorNoError); } qCWarning(dcNetatmo()) << "Unhandled device class in setupDevice"; } void DevicePluginNetatmo::deviceRemoved(Device *device) { if (device->deviceClassId() == netatmoConnectionDeviceClassId) { OAuth2 * authentication = m_authentications.key(device); m_authentications.remove(authentication); authentication->deleteLater(); } else if (device->deviceClassId() == indoorDeviceClassId) { NetatmoBaseStation *indoor = m_indoorDevices.key(device); m_indoorDevices.remove(indoor); indoor->deleteLater(); } else if (device->deviceClassId() == outdoorDeviceClassId) { NetatmoOutdoorModule *outdoor = m_outdoorDevices.key(device); m_outdoorDevices.remove(outdoor); outdoor->deleteLater(); } if (myDevices().isEmpty() && m_pluginTimer) { m_pluginTimer->deleteLater(); m_pluginTimer = nullptr; } } void DevicePluginNetatmo::postSetupDevice(Device *device) { if (device->deviceClassId() == indoorDeviceClassId) { QString stationId = device->paramValue(indoorDeviceMacParamTypeId).toString(); if (m_indoorStationInitData.contains(stationId) && m_indoorDevices.values().contains(device)) { m_indoorDevices.key(device)->updateStates(m_indoorStationInitData.take(stationId)); } } else if (device->deviceClassId() == outdoorDeviceClassId) { QString stationId = device->paramValue(outdoorDeviceMacParamTypeId).toString(); if (m_outdoorStationInitData.contains(stationId) && m_outdoorDevices.values().contains(device)) { m_outdoorDevices.key(device)->updateStates(m_outdoorStationInitData.take(stationId)); } } } void DevicePluginNetatmo::refreshData(Device *device, const QString &token) { QUrlQuery query; query.addQueryItem("access_token", token); QUrl url("https://api.netatmo.com/api/getstationsdata"); url.setQuery(query); QNetworkReply *reply = hardwareManager()->networkManager()->get(QNetworkRequest(url)); connect(reply, &QNetworkReply::finished, this, &DevicePluginNetatmo::onNetworkReplyFinished); m_refreshRequest.insert(reply, device); } void DevicePluginNetatmo::processRefreshData(const QVariantMap &data, Device *connectionDevice) { // Process the data if (data.contains("body")) { // check devices if (data.value("body").toMap().contains("devices")) { QVariantList deviceList = data.value("body").toMap().value("devices").toList(); // check devices foreach (QVariant deviceVariant, deviceList) { QVariantMap deviceMap = deviceVariant.toMap(); // we support currently only NAMain devices if (deviceMap.value("type").toString() == "NAMain") { Device *indoorDevice = findIndoorDevice(deviceMap.value("_id").toString()); // check if we have to create the device (auto) if (!indoorDevice) { DeviceDescriptor descriptor(indoorDeviceClassId, deviceMap.value("module_name").toString(), deviceMap.value("station_name").toString(), connectionDevice->id()); ParamList params; params.append(Param(indoorDeviceNameParamTypeId, deviceMap.value("station_name").toString())); params.append(Param(indoorDeviceMacParamTypeId, deviceMap.value("_id").toString())); descriptor.setParams(params); m_indoorStationInitData.insert(deviceMap.value("_id").toString(), deviceMap); emit autoDevicesAppeared({descriptor}); } else { if (m_indoorDevices.values().contains(indoorDevice)) { m_indoorDevices.key(indoorDevice)->updateStates(deviceMap); } } } // check modules if (deviceMap.contains("modules")) { QVariantList modulesList = deviceMap.value("modules").toList(); foreach (QVariant moduleVariant, modulesList) { QVariantMap moduleMap = moduleVariant.toMap(); // we support currently only NAModule1 if (moduleMap.value("type").toString() == "NAModule1") { Device *outdoorDevice = findOutdoorDevice(moduleMap.value("_id").toString()); // check if we have to create the device (auto) if (!outdoorDevice) { DeviceDescriptor descriptor(outdoorDeviceClassId, "Outdoor Module", moduleMap.value("module_name").toString(), connectionDevice->id()); ParamList params; params.append(Param(outdoorDeviceNameParamTypeId, moduleMap.value("module_name").toString())); params.append(Param(outdoorDeviceMacParamTypeId, moduleMap.value("_id").toString())); params.append(Param(outdoorDeviceBaseStationParamTypeId, deviceMap.value("_id").toString())); descriptor.setParams(params); m_outdoorStationInitData.insert(moduleMap.value("_id").toString(), moduleMap); emit autoDevicesAppeared({descriptor}); } else { if (m_outdoorDevices.values().contains(outdoorDevice)) { m_outdoorDevices.key(outdoorDevice)->updateStates(moduleMap); } } } } } } } } } Device *DevicePluginNetatmo::findIndoorDevice(const QString &macAddress) { foreach (Device *device, myDevices()) { if (device->deviceClassId() == indoorDeviceClassId) { if (device->paramValue(indoorDeviceMacParamTypeId).toString() == macAddress) { return device; } } } return nullptr; } Device *DevicePluginNetatmo::findOutdoorDevice(const QString &macAddress) { foreach (Device *device, myDevices()) { if (device->deviceClassId() == outdoorDeviceClassId) { if (device->paramValue(outdoorDeviceMacParamTypeId).toString() == macAddress) { return device; } } } return nullptr; } void DevicePluginNetatmo::onPluginTimer() { foreach (OAuth2 *authentication, m_authentications.keys()) { if (authentication->authenticated()) { refreshData(m_authentications.value(authentication), authentication->token()); } else { authentication->startAuthentication(); } } } void DevicePluginNetatmo::onNetworkReplyFinished() { QNetworkReply *reply = static_cast(sender()); reply->deleteLater(); int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); // update values request if (m_refreshRequest.keys().contains(reply)) { Device *device = m_refreshRequest.take(reply); // check HTTP status code if (status != 200) { qCWarning(dcNetatmo) << "Device list reply HTTP error:" << status << reply->errorString(); device->setStateValue(netatmoConnectionConnectedStateTypeId, false); return; } // check JSON file QJsonParseError error; QJsonDocument jsonDoc = QJsonDocument::fromJson(reply->readAll(), &error); if (error.error != QJsonParseError::NoError) { qCWarning(dcNetatmo) << "Device list reply JSON error:" << error.errorString(); return; } qCDebug(dcNetatmo) << qUtf8Printable(jsonDoc.toJson()); processRefreshData(jsonDoc.toVariant().toMap(), device); } } void DevicePluginNetatmo::onAuthenticationChanged() { } void DevicePluginNetatmo::onIndoorStatesChanged() { NetatmoBaseStation *indoor = static_cast(sender()); Device *device = m_indoorDevices.value(indoor); device->setStateValue(indoorUpdateTimeStateTypeId, indoor->lastUpdate()); device->setStateValue(indoorTemperatureStateTypeId, indoor->temperature()); device->setStateValue(indoorTemperatureMinStateTypeId, indoor->minTemperature()); device->setStateValue(indoorTemperatureMaxStateTypeId, indoor->maxTemperature()); device->setStateValue(indoorPressureStateTypeId, indoor->pressure()); device->setStateValue(indoorHumidityStateTypeId, indoor->humidity()); device->setStateValue(indoorCo2StateTypeId, indoor->co2()); device->setStateValue(indoorNoiseStateTypeId, indoor->noise()); device->setStateValue(indoorWifiStrengthStateTypeId, indoor->wifiStrength()); } void DevicePluginNetatmo::onOutdoorStatesChanged() { NetatmoOutdoorModule *outdoor = static_cast(sender()); Device *device = m_outdoorDevices.value(outdoor); device->setStateValue(outdoorUpdateTimeStateTypeId, outdoor->lastUpdate()); device->setStateValue(outdoorTemperatureStateTypeId, outdoor->temperature()); device->setStateValue(outdoorTemperatureMinStateTypeId, outdoor->minTemperature()); device->setStateValue(outdoorTemperatureMaxStateTypeId, outdoor->maxTemperature()); device->setStateValue(outdoorHumidityStateTypeId, outdoor->humidity()); device->setStateValue(outdoorSignalStrengthStateTypeId, outdoor->signalStrength()); device->setStateValue(outdoorBatteryLevelStateTypeId, outdoor->battery()); device->setStateValue(outdoorBatteryCriticalStateTypeId, outdoor->battery() < 10); }