/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Copyright (C) 2015 Simon Stürz * * * * This file is part of guh. * * * * Guh is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, version 2 of the License. * * * * Guh 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with guh. If not, see . * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /*! \page netatmo.html \title Netatmo \brief Plugin for the Netatmo weather stations. \ingroup plugins \ingroup guh-plugins This plugin allows to receive data from you netatmo weather station. \chapter Plugin properties Following JSON file contains the definition and the description of all available \l{DeviceClass}{DeviceClasses} and \l{Vendor}{Vendors} of this \l{DevicePlugin}. For more details how to read this JSON file please check out the documentation for \l{The plugin JSON File}. \quotefile plugins/deviceplugins/netatmo/devicepluginnetatmo.json */ #include "devicepluginnetatmo.h" #include "plugin/device.h" #include "plugininfo.h" #include #include #include DevicePluginNetatmo::DevicePluginNetatmo() { } DeviceManager::HardwareResources DevicePluginNetatmo::requiredHardware() const { return DeviceManager::HardwareResourceNetworkManager | DeviceManager::HardwareResourceTimer; } DeviceManager::DeviceSetupStatus DevicePluginNetatmo::setupDevice(Device *device) { if (device->deviceClassId() == connectionDeviceClassId) { qCDebug(dcNetatmo) << "Setup netatmo connection"; OAuth2 *authentication = new OAuth2("561c015d49c75f0d1cce6e13", "GuvKkdtu7JQlPD47qTTepRR9hQ0CUPAj4Tae3Ohcq", this); authentication->setUrl(QUrl("https://api.netatmo.net/oauth2/token")); authentication->setUsername(device->paramValue(usernameParamTypeId).toString()); authentication->setPassword(device->paramValue(passwordParamTypeId).toString()); authentication->setScope("read_station read_thermostat write_thermostat"); m_authentications.insert(authentication, device); m_asyncSetups.append(device); connect(authentication, &OAuth2::authenticationChanged, this, &DevicePluginNetatmo::onAuthenticationChanged); authentication->startAuthentication(); return DeviceManager::DeviceSetupStatusAsync; } else if (device->deviceClassId() == indoorDeviceClassId) { qCDebug(dcNetatmo) << "Setup netatmo indoor base station" << device->params(); NetatmoBaseStation *indoor = new NetatmoBaseStation(device->paramValue(nameParamTypeId).toString(), device->paramValue(macParamTypeId).toString(), device->paramValue(connectionParamTypeId).toString(), this); device->setParentId(DeviceId(indoor->connectionId())); m_indoorDevices.insert(indoor, device); connect(indoor, SIGNAL(statesChanged()), this, SLOT(onIndoorStatesChanged())); return DeviceManager::DeviceSetupStatusSuccess; } else if (device->deviceClassId() == outdoorDeviceClassId) { qCDebug(dcNetatmo) << "Setup netatmo outdoor module" << device->params(); NetatmoOutdoorModule *outdoor = new NetatmoOutdoorModule(device->paramValue(nameParamTypeId).toString(), device->paramValue(macParamTypeId).toString(), device->paramValue(connectionParamTypeId).toString(), device->paramValue(baseStationParamTypeId).toString(),this); device->setParentId(DeviceId(outdoor->connectionId())); m_outdoorDevices.insert(outdoor, device); connect(outdoor, SIGNAL(statesChanged()), this, SLOT(onOutdoorStatesChanged())); return DeviceManager::DeviceSetupStatusSuccess; } return DeviceManager::DeviceSetupStatusFailure; } void DevicePluginNetatmo::deviceRemoved(Device *device) { if (device->deviceClassId() == connectionDeviceClassId) { 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(); } } void DevicePluginNetatmo::networkManagerReplyReady(QNetworkReply *reply) { 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(availableStateTypeId, false); reply->deleteLater(); 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(); reply->deleteLater(); return; } //qCDebug(dcNetatmo) << jsonDoc.toJson(); processRefreshData(jsonDoc.toVariant().toMap(), device->id().toString()); } reply->deleteLater(); } void DevicePluginNetatmo::guhTimer() { foreach (OAuth2 *authentication, m_authentications.keys()) { if (authentication->authenticated()) { refreshData(m_authentications.value(authentication), authentication->token()); } else { authentication->startAuthentication(); } } } DeviceManager::DeviceError DevicePluginNetatmo::executeAction(Device *device, const Action &action) { Q_UNUSED(device) Q_UNUSED(action) return DeviceManager::DeviceErrorNoError; } void DevicePluginNetatmo::refreshData(Device *device, const QString &token) { QUrlQuery query; query.addQueryItem("access_token", token); QUrl url("https://api.netatmo.com/api/devicelist"); url.setQuery(query); QNetworkReply *reply = networkManagerGet(QNetworkRequest(url)); m_refreshRequest.insert(reply, device); } void DevicePluginNetatmo::processRefreshData(const QVariantMap &data, const QString &connectionId) { 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, "Indoor Station", deviceMap.value("station_name").toString()); ParamList params; params.append(Param(nameParamTypeId, deviceMap.value("station_name").toString())); params.append(Param(macParamTypeId, deviceMap.value("_id").toString())); params.append(Param(connectionParamTypeId, connectionId)); descriptor.setParams(params); emit autoDevicesAppeared(indoorDeviceClassId, QList() << descriptor); } else { if (m_indoorDevices.values().contains(indoorDevice)) { m_indoorDevices.key(indoorDevice)->updateStates(deviceMap); } } } } } // check modules if (data.value("body").toMap().contains("modules")) { QVariantList modulesList = data.value("body").toMap().value("modules").toList(); // check devices 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()); ParamList params; params.append(Param(nameParamTypeId, moduleMap.value("module_name").toString())); params.append(Param(macParamTypeId, moduleMap.value("_id").toString())); params.append(Param(connectionParamTypeId, connectionId)); params.append(Param(baseStationParamTypeId, moduleMap.value("main_device").toString())); descriptor.setParams(params); emit autoDevicesAppeared(outdoorDeviceClassId, QList() << 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(macParamTypeId).toString() == macAddress) { return device; } } } return 0; } Device *DevicePluginNetatmo::findOutdoorDevice(const QString &macAddress) { foreach (Device *device, myDevices()) { if (device->deviceClassId() == outdoorDeviceClassId) { if (device->paramValue(macParamTypeId).toString() == macAddress) { return device; } } } return 0; } void DevicePluginNetatmo::onAuthenticationChanged() { OAuth2 *authentication = static_cast(sender()); Device *device = m_authentications.value(authentication); if (!device) return; // set the available state device->setStateValue(availableStateTypeId, authentication->authenticated()); // check if this is was a setup athentication if (m_asyncSetups.contains(device)) { if (authentication->authenticated()) { m_asyncSetups.removeAll(device); emit deviceSetupFinished(device, DeviceManager::DeviceSetupStatusSuccess); refreshData(device, authentication->token()); } else { emit deviceSetupFinished(device, DeviceManager::DeviceSetupStatusFailure); } } } void DevicePluginNetatmo::onIndoorStatesChanged() { NetatmoBaseStation *indoor = static_cast(sender()); Device *device = m_indoorDevices.value(indoor); device->setStateValue(updateTimeStateTypeId, indoor->lastUpdate()); device->setStateValue(temperatureStateTypeId, indoor->temperature()); device->setStateValue(temperatureMinStateTypeId, indoor->minTemperature()); device->setStateValue(temperatureMaxStateTypeId, indoor->maxTemperature()); device->setStateValue(pressureStateTypeId, indoor->pressure()); device->setStateValue(humidityStateTypeId, indoor->humidity()); device->setStateValue(co2StateTypeId, indoor->co2()); device->setStateValue(noiseStateTypeId, indoor->noise()); device->setStateValue(wifiStrengthStateTypeId, indoor->wifiStrength()); } void DevicePluginNetatmo::onOutdoorStatesChanged() { NetatmoOutdoorModule *outdoor = static_cast(sender()); Device *device = m_outdoorDevices.value(outdoor); device->setStateValue(updateTimeStateTypeId, outdoor->lastUpdate()); device->setStateValue(temperatureStateTypeId, outdoor->temperature()); device->setStateValue(temperatureMinStateTypeId, outdoor->minTemperature()); device->setStateValue(temperatureMaxStateTypeId, outdoor->maxTemperature()); device->setStateValue(humidityStateTypeId, outdoor->humidity()); device->setStateValue(signalStrengthStateTypeId, outdoor->signalStrength()); device->setStateValue(batteryStateTypeId, outdoor->battery()); }