mirror of
				https://github.com/f4exb/sdrangel.git
				synced 2025-10-31 13:00:26 -04:00 
			
		
		
		
	util/iot: Add API for accessing IoT / Smart Home devices.
This commit is contained in:
		
							parent
							
								
									83a94fc375
								
							
						
					
					
						commit
						a4cd8af538
					
				| @ -207,6 +207,10 @@ set(sdrbase_SOURCES | ||||
|     util/timeutil.cpp | ||||
|     util/visa.cpp | ||||
|     util/weather.cpp | ||||
|     util/iot/device.cpp | ||||
|     util/iot/homeassistant.cpp | ||||
|     util/iot/tplink.cpp | ||||
|     util/iot/visa.cpp | ||||
| 
 | ||||
|     plugin/plugininterface.cpp | ||||
|     plugin/pluginapi.cpp | ||||
| @ -426,6 +430,10 @@ set(sdrbase_HEADERS | ||||
|     util/timeutil.h | ||||
|     util/visa.h | ||||
|     util/weather.h | ||||
|     util/iot/device.h | ||||
|     util/iot/homeassistant.h | ||||
|     util/iot/tplink.h | ||||
|     util/iot/visa.h | ||||
| 
 | ||||
|     webapi/webapiadapter.h | ||||
|     webapi/webapiadapterbase.h | ||||
|  | ||||
							
								
								
									
										529
									
								
								sdrbase/util/iot/device.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										529
									
								
								sdrbase/util/iot/device.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,529 @@ | ||||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| // Copyright (C) 2022 Jon Beniston, M7RCE                                        //
 | ||||
| //                                                                               //
 | ||||
| // This program 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 as version 3 of the License, or                  //
 | ||||
| // (at your option) any later version.                                           //
 | ||||
| //                                                                               //
 | ||||
| // This program 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 V3 for more details.                               //
 | ||||
| //                                                                               //
 | ||||
| // You should have received a copy of the GNU General Public License             //
 | ||||
| // along with this program. If not, see <http://www.gnu.org/licenses/>.          //
 | ||||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| 
 | ||||
| #include <QDebug> | ||||
| #include <QUrl> | ||||
| #include <QUrlQuery> | ||||
| #include <QNetworkReply> | ||||
| #include <QJsonDocument> | ||||
| #include <QJsonObject> | ||||
| #include <QRegularExpression> | ||||
| 
 | ||||
| #include "util/simpleserializer.h" | ||||
| #include "util/iot/device.h" | ||||
| #include "util/iot/tplink.h" | ||||
| #include "util/iot/homeassistant.h" | ||||
| #include "util/iot/visa.h" | ||||
| 
 | ||||
| Device::Device(DeviceDiscoverer::DeviceInfo *info) | ||||
| { | ||||
|     if (info) { | ||||
|         m_info = *info; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| Device* Device::create(const QHash<QString, QVariant>& settings, const QString& protocol, DeviceDiscoverer::DeviceInfo *info) | ||||
| { | ||||
|     if (checkSettings(settings, protocol)) | ||||
|     { | ||||
|         if (protocol == "TPLink") | ||||
|         { | ||||
|             if (settings.contains("deviceId")) | ||||
|             { | ||||
|                 return new TPLinkDevice(settings.value("username").toString(), | ||||
|                                         settings.value("password").toString(), | ||||
|                                         settings.value("deviceId").toString(), | ||||
|                                         info); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 qDebug() << "Device::create: A deviceId is required for: " << protocol; | ||||
|                 return nullptr; | ||||
|             } | ||||
|         } | ||||
|         else if (protocol == "HomeAssistant") | ||||
|         { | ||||
|             if (checkSettings(settings, protocol)) | ||||
|             { | ||||
|                 if (settings.contains("deviceId")) | ||||
|                 { | ||||
|                     return new HomeAssistantDevice(settings.value("apiKey").toString(), | ||||
|                                                    settings.value("url").toString(), | ||||
|                                                    settings.value("deviceId").toString(), | ||||
|                                                    settings.value("controlIds").toStringList(), | ||||
|                                                    settings.value("sensorIds").toStringList(), | ||||
|                                                    info); | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     qDebug() << "Device::create: A deviceId is required for: " << protocol; | ||||
|                     return nullptr; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         else if (protocol == "VISA") | ||||
|         { | ||||
|             return new VISADevice(settings, | ||||
|                                   settings.value("deviceId").toString(), | ||||
|                                   settings.value("controlIds").toStringList(), | ||||
|                                   settings.value("sensorIds").toStringList(), | ||||
|                                   info); | ||||
|         } | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         return nullptr; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| bool Device::checkSettings(const QHash<QString, QVariant>& settings, const QString& protocol) | ||||
| { | ||||
|     if (protocol == "TPLink") | ||||
|     { | ||||
|         if (settings.contains("username") && settings.contains("password")) | ||||
|         { | ||||
|             return true; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             qDebug() << "Device::checkSettings: A username and password are required for: " << protocol; | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
|     else if (protocol == "HomeAssistant") | ||||
|     { | ||||
|         if (settings.contains("apiKey")) | ||||
|         { | ||||
|             if (settings.contains("url")) | ||||
|             { | ||||
|                 return true; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 qDebug() << "Device::checkSettings: A host url is required for: " << protocol; | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             qDebug() << "Device::checkSettings: An apiKey is required for: " << protocol; | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
|     else if (protocol == "VISA") | ||||
|     { | ||||
|         return true; | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         qDebug() << "Device::checkSettings: Unsupported protocol: " << protocol; | ||||
|         return false; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| const QStringList DeviceDiscoverer::m_typeStrings = { | ||||
|     "Auto", | ||||
|     "Boolean", | ||||
|     "Integer", | ||||
|     "Float", | ||||
|     "String", | ||||
|     "List", | ||||
|     "Button" | ||||
| }; | ||||
| 
 | ||||
| const QStringList DeviceDiscoverer::m_widgetTypeStrings = { | ||||
|     "Spin box", | ||||
|     "Dial", | ||||
|     "Slider" | ||||
| }; | ||||
| 
 | ||||
| DeviceDiscoverer::DeviceDiscoverer() | ||||
| { | ||||
| } | ||||
| 
 | ||||
| DeviceDiscoverer *DeviceDiscoverer::getDiscoverer(const QHash<QString, QVariant>& settings, const QString& protocol) | ||||
| { | ||||
|     if (Device::checkSettings(settings, protocol)) | ||||
|     { | ||||
|         if (protocol == "TPLink") | ||||
|         { | ||||
|             return new TPLinkDeviceDiscoverer(settings.value("username").toString(), settings.value("password").toString()); | ||||
|         } | ||||
|         else if (protocol == "HomeAssistant") | ||||
|         { | ||||
|             return new HomeAssistantDeviceDiscoverer(settings.value("apiKey").toString(), settings.value("url").toString()); | ||||
|         } | ||||
|         else if (protocol == "VISA") | ||||
|         { | ||||
|             return new VISADeviceDiscoverer(settings.value("resourceFilter").toString()); | ||||
|         } | ||||
|     } | ||||
|     return nullptr; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| DeviceDiscoverer::DeviceInfo::DeviceInfo() | ||||
| { | ||||
| } | ||||
| 
 | ||||
| DeviceDiscoverer::DeviceInfo::DeviceInfo(const DeviceInfo &info) | ||||
| { | ||||
|     m_name = info.m_name; | ||||
|     m_id = info.m_id; | ||||
|     m_model = info.m_model; | ||||
|     // Take deep-copy of controls and sensors
 | ||||
|     for (auto const control : info.m_controls) { | ||||
|         ControlInfo *ci = control->clone(); | ||||
|         m_controls.append(ci); | ||||
|     } | ||||
|     for (auto const sensor : info.m_sensors) { | ||||
|         m_sensors.append(sensor->clone()); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| DeviceDiscoverer::DeviceInfo& DeviceDiscoverer::DeviceInfo::operator=(const DeviceInfo &info) | ||||
| { | ||||
|     m_name = info.m_name; | ||||
|     m_id = info.m_id; | ||||
|     m_model = info.m_model; | ||||
|     qDeleteAll(m_controls); | ||||
|     m_controls.clear(); | ||||
|     qDeleteAll(m_sensors); | ||||
|     m_sensors.clear(); | ||||
|     // Take deep-copy of controls and sensors
 | ||||
|     for (auto const control : info.m_controls) { | ||||
|         m_controls.append(control->clone()); | ||||
|     } | ||||
|     for (auto const sensor : info.m_sensors) { | ||||
|         m_sensors.append(sensor->clone()); | ||||
|     } | ||||
|     return *this; | ||||
| } | ||||
| 
 | ||||
| DeviceDiscoverer::DeviceInfo::~DeviceInfo() | ||||
| { | ||||
|     qDeleteAll(m_controls); | ||||
|     m_controls.clear(); | ||||
|     qDeleteAll(m_sensors); | ||||
|     m_sensors.clear(); | ||||
| } | ||||
| 
 | ||||
| DeviceDiscoverer::DeviceInfo::operator QString() const | ||||
| { | ||||
|     QString controls; | ||||
|     QString sensors; | ||||
| 
 | ||||
|     for (auto control : m_controls) { | ||||
|         controls.append((QString)*control); | ||||
|     } | ||||
|     for (auto sensor : m_sensors) { | ||||
|         sensors.append((QString)*sensor); | ||||
|     } | ||||
| 
 | ||||
|     return QString("DeviceInfo: m_name: %1 m_id: %2 m_model: %3 m_controls: %4 m_sensors: %5") | ||||
|                 .arg(m_name) | ||||
|                 .arg(m_id) | ||||
|                 .arg(m_model) | ||||
|                 .arg(controls) | ||||
|                 .arg(sensors); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| DeviceDiscoverer::ControlInfo::ControlInfo() : | ||||
|     m_type(AUTO), | ||||
|     m_min(-1000000), | ||||
|     m_max(1000000), | ||||
|     m_scale(1.0f), | ||||
|     m_precision(3), | ||||
|     m_widgetType(SPIN_BOX) | ||||
| { | ||||
| } | ||||
| 
 | ||||
| DeviceDiscoverer::ControlInfo::operator QString() const | ||||
| { | ||||
|     return QString("ControlInfo: m_name: %1 m_id: %2 m_type: %3") | ||||
|                 .arg(m_name) | ||||
|                 .arg(m_id) | ||||
|                 .arg(DeviceDiscoverer::m_typeStrings[m_type]); | ||||
| } | ||||
| 
 | ||||
| DeviceDiscoverer::ControlInfo *DeviceDiscoverer::ControlInfo::clone() const | ||||
| { | ||||
|     return new ControlInfo(*this); | ||||
| } | ||||
| 
 | ||||
| QByteArray DeviceDiscoverer::ControlInfo::serialize() const | ||||
| { | ||||
|     SimpleSerializer s(1); | ||||
| 
 | ||||
|     s.writeString(1, m_name); | ||||
|     s.writeString(2, m_id); | ||||
|     s.writeS32(3, (int)m_type); | ||||
|     s.writeFloat(4, m_min); | ||||
|     s.writeFloat(5, m_max); | ||||
|     s.writeFloat(6, m_scale); | ||||
|     s.writeS32(7, m_precision); | ||||
|     s.writeList(8, m_values); | ||||
|     s.writeS32(9, (int)m_widgetType); | ||||
|     s.writeString(10, m_units); | ||||
| 
 | ||||
|     return s.final(); | ||||
| } | ||||
| 
 | ||||
| bool DeviceDiscoverer::ControlInfo::deserialize(const QByteArray& data) | ||||
| { | ||||
|     SimpleDeserializer d(data); | ||||
| 
 | ||||
|     if (!d.isValid()) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     if (d.getVersion() == 1) | ||||
|     { | ||||
|         d.readString(1, &m_name); | ||||
|         d.readString(2, &m_id); | ||||
|         d.readS32(3, (int*)&m_type); | ||||
|         d.readFloat(4, &m_min); | ||||
|         d.readFloat(5, &m_max); | ||||
|         d.readFloat(6, &m_scale, 1.0f); | ||||
|         d.readS32(7, &m_precision, 3); | ||||
|         d.readList(8, &m_values); | ||||
|         d.readS32(9, (int *)&m_widgetType); | ||||
|         d.readString(10, &m_units); | ||||
|         return true; | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         return false; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| DeviceDiscoverer::SensorInfo::operator QString() const | ||||
| { | ||||
|     return QString("SensorInfo: m_name: %1 m_id: %2 m_type: %3") | ||||
|                 .arg(m_name) | ||||
|                 .arg(m_id) | ||||
|                 .arg(DeviceDiscoverer::m_typeStrings[m_type]); | ||||
| } | ||||
| 
 | ||||
| DeviceDiscoverer::SensorInfo *DeviceDiscoverer::SensorInfo::clone() const | ||||
| { | ||||
|     return new SensorInfo(*this); | ||||
| } | ||||
| 
 | ||||
| QByteArray DeviceDiscoverer::SensorInfo::serialize() const | ||||
| { | ||||
|     SimpleSerializer s(1); | ||||
| 
 | ||||
|     s.writeString(1, m_name); | ||||
|     s.writeString(2, m_id); | ||||
|     s.writeS32(3, (int)m_type); | ||||
|     s.writeString(4, m_units); | ||||
| 
 | ||||
|     return s.final(); | ||||
| } | ||||
| 
 | ||||
| bool DeviceDiscoverer::SensorInfo::deserialize(const QByteArray& data) | ||||
| { | ||||
|     SimpleDeserializer d(data); | ||||
| 
 | ||||
|     if (!d.isValid()) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     if (d.getVersion() == 1) | ||||
|     { | ||||
|         d.readString(1, &m_name); | ||||
|         d.readString(2, &m_id); | ||||
|         d.readS32(3, (int*)&m_type); | ||||
|         d.readString(4, &m_units); | ||||
|         return true; | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         return false; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| QByteArray DeviceDiscoverer::DeviceInfo::serialize() const | ||||
| { | ||||
|     SimpleSerializer s(1); | ||||
| 
 | ||||
|     s.writeString(1, m_name); | ||||
|     s.writeString(2, m_id); | ||||
|     s.writeString(3, m_model); | ||||
|     s.writeList(10, m_controls); | ||||
|     s.writeList(11, m_sensors); | ||||
| 
 | ||||
|     return s.final(); | ||||
| } | ||||
| 
 | ||||
| bool DeviceDiscoverer::DeviceInfo::deserialize(const QByteArray& data) | ||||
| { | ||||
|     SimpleDeserializer d(data); | ||||
| 
 | ||||
|     if (!d.isValid()) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     if (d.getVersion() == 1) | ||||
|     { | ||||
|         QByteArray blob; | ||||
| 
 | ||||
|         d.readString(1, &m_name); | ||||
|         d.readString(2, &m_id); | ||||
|         d.readString(3, &m_model); | ||||
|         d.readList(10, &m_controls); | ||||
|         d.readList(11, &m_sensors); | ||||
|         return true; | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         return false; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| DeviceDiscoverer::ControlInfo *DeviceDiscoverer::DeviceInfo::getControl(const QString &id) const | ||||
| { | ||||
|     for (auto c : m_controls) | ||||
|     { | ||||
|         if (c->m_id == id) { | ||||
|             return c; | ||||
|         } | ||||
|     } | ||||
|     return nullptr; | ||||
| } | ||||
| 
 | ||||
| DeviceDiscoverer::SensorInfo *DeviceDiscoverer::DeviceInfo::getSensor(const QString &id) const | ||||
| { | ||||
|     for (auto s : m_sensors) | ||||
|     { | ||||
|         if (s->m_id == id) { | ||||
|             return s; | ||||
|         } | ||||
|     } | ||||
|     return nullptr; | ||||
| } | ||||
| 
 | ||||
| void DeviceDiscoverer::DeviceInfo::deleteControl(const QString &id) | ||||
| { | ||||
|     for (int i = 0; i < m_controls.size(); i++) | ||||
|     { | ||||
|         if (m_controls[i]->m_id == id) | ||||
|         { | ||||
|             delete m_controls.takeAt(i); | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void DeviceDiscoverer::DeviceInfo::deleteSensor(const QString &id) | ||||
| { | ||||
|     for (int i = 0; i < m_sensors.size(); i++) | ||||
|     { | ||||
|         if (m_sensors[i]->m_id == id) | ||||
|         { | ||||
|             delete m_sensors.takeAt(i); | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| QDataStream& operator<<(QDataStream& out, const DeviceDiscoverer::ControlInfo* control) | ||||
| { | ||||
|     int typeId; | ||||
|     if (const VISADevice::VISAControl* c = dynamic_cast<const VISADevice::VISAControl *>(control)) { | ||||
|         typeId = 1; | ||||
|     } else { | ||||
|         typeId = 0; | ||||
|     } | ||||
|     out << typeId; | ||||
|     out << control->serialize(); | ||||
|     return out; | ||||
| } | ||||
| 
 | ||||
| QDataStream& operator>>(QDataStream& in, DeviceDiscoverer::ControlInfo*& control) | ||||
| { | ||||
|     QByteArray data; | ||||
|     int typeId; | ||||
|     in >> typeId; | ||||
|     if (typeId == 1) { | ||||
|         control = new VISADevice::VISAControl(); | ||||
|     } else { | ||||
|         control = new DeviceDiscoverer::ControlInfo(); | ||||
|     } | ||||
|     in >> data; | ||||
|     control->deserialize(data); | ||||
|     return in; | ||||
| } | ||||
| 
 | ||||
| QDataStream& operator<<(QDataStream& out, const DeviceDiscoverer::SensorInfo* sensor) | ||||
| { | ||||
|     int typeId; | ||||
|     if (const VISADevice::VISASensor* s = dynamic_cast<const VISADevice::VISASensor *>(sensor)) { | ||||
|         typeId = 1; | ||||
|     } else { | ||||
|         typeId = 0; | ||||
|     } | ||||
|     out << typeId; | ||||
|     out << sensor->serialize(); | ||||
|     return out; | ||||
| } | ||||
| 
 | ||||
| QDataStream& operator>>(QDataStream& in, DeviceDiscoverer::SensorInfo*& sensor) | ||||
| { | ||||
| 
 | ||||
|     QByteArray data; | ||||
|     int typeId; | ||||
|     in >> typeId; | ||||
|     if (typeId == 1) { | ||||
|         sensor = new VISADevice::VISASensor(); | ||||
|     } else { | ||||
|         sensor = new DeviceDiscoverer::SensorInfo(); | ||||
|     } | ||||
|     in >> data; | ||||
|     sensor->deserialize(data); | ||||
|     return in; | ||||
| } | ||||
| 
 | ||||
| QDataStream& operator<<(QDataStream& out, const VISADevice::VISASensor &sensor) | ||||
| { | ||||
|     out << sensor.serialize(); | ||||
|     return out; | ||||
| } | ||||
| 
 | ||||
| QDataStream& operator>>(QDataStream& in, VISADevice::VISASensor& sensor) | ||||
| { | ||||
|     QByteArray data; | ||||
|     in >> data; | ||||
|     sensor.deserialize(data); | ||||
|     return in; | ||||
| } | ||||
| 
 | ||||
| QDataStream& operator<<(QDataStream& out, const VISADevice::VISAControl &control) | ||||
| { | ||||
|     out << control.serialize(); | ||||
|     return out; | ||||
| } | ||||
| 
 | ||||
| QDataStream& operator>>(QDataStream& in, VISADevice::VISAControl& control) | ||||
| { | ||||
|     QByteArray data; | ||||
|     in >> data; | ||||
|     control.deserialize(data); | ||||
|     return in; | ||||
| } | ||||
							
								
								
									
										142
									
								
								sdrbase/util/iot/device.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										142
									
								
								sdrbase/util/iot/device.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,142 @@ | ||||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| // Copyright (C) 2022 Jon Beniston, M7RCE                                        //
 | ||||
| //                                                                               //
 | ||||
| // This program 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 as version 3 of the License, or                  //
 | ||||
| // (at your option) any later version.                                           //
 | ||||
| //                                                                               //
 | ||||
| // This program 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 V3 for more details.                               //
 | ||||
| //                                                                               //
 | ||||
| // You should have received a copy of the GNU General Public License             //
 | ||||
| // along with this program. If not, see <http://www.gnu.org/licenses/>.          //
 | ||||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| 
 | ||||
| #ifndef INCLUDE_DEVICE_H | ||||
| #define INCLUDE_DEVICE_H | ||||
| 
 | ||||
| #include <QtCore> | ||||
| 
 | ||||
| #include "export.h" | ||||
| 
 | ||||
| class QNetworkAccessManager; | ||||
| class QNetworkReply; | ||||
| 
 | ||||
| class SDRBASE_API DeviceDiscoverer : public QObject | ||||
| { | ||||
|     Q_OBJECT | ||||
| public: | ||||
|     enum Type { | ||||
|         AUTO, | ||||
|         BOOL, | ||||
|         INT, | ||||
|         FLOAT, | ||||
|         STRING, | ||||
|         LIST, | ||||
|         BUTTON | ||||
|     }; | ||||
|     enum WidgetType { | ||||
|         SPIN_BOX, | ||||
|         DIAL, | ||||
|         SLIDER | ||||
|     }; | ||||
| 
 | ||||
|     struct SDRBASE_API ControlInfo { | ||||
|         QString m_name; | ||||
|         QString m_id; | ||||
|         Type m_type;            // Data type
 | ||||
|         float m_min;            // Min/max when m_type=INT/FLOAT
 | ||||
|         float m_max; | ||||
|         float m_scale; | ||||
|         int m_precision; | ||||
|         QStringList m_values;   // Allowed values when m_type==LIST or label for button when m_type==BUTTON
 | ||||
|         WidgetType m_widgetType;// For m_type==FLOAT
 | ||||
|         QString m_units; | ||||
| 
 | ||||
|         ControlInfo(); | ||||
|         operator QString() const; | ||||
|         virtual ControlInfo *clone() const; | ||||
|         virtual QByteArray serialize() const; | ||||
|         virtual bool deserialize(const QByteArray& data); | ||||
|     }; | ||||
| 
 | ||||
|     struct SDRBASE_API SensorInfo { | ||||
|         QString m_name; | ||||
|         QString m_id; | ||||
|         Type m_type; | ||||
|         QString m_units;        // W/Watts etc
 | ||||
| 
 | ||||
|         operator QString() const; | ||||
|         virtual SensorInfo *clone() const; | ||||
|         virtual QByteArray serialize() const; | ||||
|         virtual bool deserialize(const QByteArray& data); | ||||
|     }; | ||||
| 
 | ||||
|     struct SDRBASE_API DeviceInfo { | ||||
|         QString m_name;     // User friendly name
 | ||||
|         QString m_id;       // ID for the device used by the API
 | ||||
|         QString m_model;    // Model name
 | ||||
|         QList<ControlInfo *> m_controls; | ||||
|         QList<SensorInfo *> m_sensors; | ||||
| 
 | ||||
|         DeviceInfo(); | ||||
|         DeviceInfo(const DeviceInfo &info); | ||||
|         ~DeviceInfo(); | ||||
|         DeviceInfo& operator=(const DeviceInfo &info); | ||||
|         operator QString() const; | ||||
|         QByteArray serialize() const; | ||||
|         bool deserialize(const QByteArray& data); | ||||
|         ControlInfo *getControl(const QString &id) const; | ||||
|         SensorInfo *getSensor(const QString &id) const; | ||||
|         void deleteControl(const QString &id); | ||||
|         void deleteSensor(const QString &id); | ||||
|     }; | ||||
| 
 | ||||
| protected: | ||||
|     DeviceDiscoverer(); | ||||
| 
 | ||||
| public: | ||||
|     static DeviceDiscoverer *getDiscoverer(const QHash<QString, QVariant>& settings, const QString& protocol="TPLink"); | ||||
|     static const QStringList m_typeStrings; | ||||
|     static const QStringList m_widgetTypeStrings; | ||||
| 
 | ||||
|     virtual void getDevices() = 0; | ||||
| 
 | ||||
| signals: | ||||
|     void deviceList(const QList<DeviceInfo> &devices); | ||||
|     void error(const QString &msg); | ||||
| }; | ||||
| 
 | ||||
| class SDRBASE_API Device : public QObject | ||||
| { | ||||
|     Q_OBJECT | ||||
| protected: | ||||
|     Device(DeviceDiscoverer::DeviceInfo *info=nullptr); | ||||
| 
 | ||||
| public: | ||||
| 
 | ||||
|     static Device* create(const QHash<QString, QVariant>& settings, const QString& protocol="TPLink", DeviceDiscoverer::DeviceInfo *info=nullptr); | ||||
|     static bool checkSettings(const QHash<QString, QVariant>& settings, const QString& protocol); | ||||
| 
 | ||||
|     virtual void getState() = 0; | ||||
|     virtual void setState(const QString &controlId, bool state) {} | ||||
|     virtual void setState(const QString &controlId, int state) {} | ||||
|     virtual void setState(const QString &controlId, float state) {} | ||||
|     virtual void setState(const QString &controlId, const QString &state) {} | ||||
|     virtual QString getProtocol() const = 0; | ||||
|     virtual QString getDeviceId() const = 0; | ||||
| 
 | ||||
| signals: | ||||
|     void deviceUpdated(QHash<QString, QVariant>);   // Called when new state available. Hash keys are control and sensor IDs
 | ||||
|     void deviceUnavailable();                       // Called when device is unavailable. error() isn't signalled, as we expect devices to come and go
 | ||||
|     void error(const QString &msg);                 // Called on terminal error, such as invalid authentication details
 | ||||
| 
 | ||||
| protected: | ||||
|     DeviceDiscoverer::DeviceInfo m_info; | ||||
| 
 | ||||
| }; | ||||
| 
 | ||||
| #endif /* INCLUDE_DEVICE_H */ | ||||
							
								
								
									
										317
									
								
								sdrbase/util/iot/homeassistant.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										317
									
								
								sdrbase/util/iot/homeassistant.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,317 @@ | ||||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| // Copyright (C) 2022 Jon Beniston, M7RCE                                        //
 | ||||
| //                                                                               //
 | ||||
| // This program 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 as version 3 of the License, or                  //
 | ||||
| // (at your option) any later version.                                           //
 | ||||
| //                                                                               //
 | ||||
| // This program 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 V3 for more details.                               //
 | ||||
| //                                                                               //
 | ||||
| // You should have received a copy of the GNU General Public License             //
 | ||||
| // along with this program. If not, see <http://www.gnu.org/licenses/>.          //
 | ||||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| 
 | ||||
| #include <QDebug> | ||||
| #include <QUrl> | ||||
| #include <QUrlQuery> | ||||
| #include <QNetworkReply> | ||||
| #include <QJsonDocument> | ||||
| #include <QJsonObject> | ||||
| #include <QRegularExpression> | ||||
| 
 | ||||
| #include "util/iot/homeassistant.h" | ||||
| #include "util/simpleserializer.h" | ||||
| 
 | ||||
| HomeAssistantDevice::HomeAssistantDevice(const QString& apiKey, const QString& url, const QString &deviceId, | ||||
|                                         const QStringList &controls, const QStringList &sensors, | ||||
|                                         DeviceDiscoverer::DeviceInfo *info) : | ||||
|     Device(info), | ||||
|     m_apiKey(apiKey), | ||||
|     m_url(url), | ||||
|     m_deviceId(deviceId) | ||||
| { | ||||
|     m_entities = controls; | ||||
|     m_entities.append(sensors); | ||||
|     m_networkManager = new QNetworkAccessManager(); | ||||
|     QObject::connect( | ||||
|         m_networkManager, | ||||
|         &QNetworkAccessManager::finished, | ||||
|         this, | ||||
|         &HomeAssistantDevice::handleReply | ||||
|     ); | ||||
| } | ||||
| 
 | ||||
| HomeAssistantDevice::~HomeAssistantDevice() | ||||
| { | ||||
|     QObject::disconnect( | ||||
|         m_networkManager, | ||||
|         &QNetworkAccessManager::finished, | ||||
|         this, | ||||
|         &HomeAssistantDevice::handleReply | ||||
|     ); | ||||
|     delete m_networkManager; | ||||
| } | ||||
| 
 | ||||
| void HomeAssistantDevice::getState() | ||||
| { | ||||
|     // Get state for all entities of the device
 | ||||
|     for (auto entity : m_entities) | ||||
|     { | ||||
|         QUrl url(m_url + "/api/states/" + entity); | ||||
|         QNetworkRequest request(url); | ||||
|         request.setRawHeader("Authorization", "Bearer " + m_apiKey.toLocal8Bit()); | ||||
|         request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); | ||||
|         m_networkManager->get(request); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void HomeAssistantDevice::setState(const QString &controlId, bool state) | ||||
| { | ||||
|     QString domain = controlId.left(controlId.indexOf(".")); | ||||
|     QUrl url(m_url + "/api/services/" + domain + "/turn_" + (state ? "on" : "off")); | ||||
|     QNetworkRequest request(url); | ||||
|     request.setRawHeader("Authorization", "Bearer " + m_apiKey.toLocal8Bit()); | ||||
|     request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); | ||||
| 
 | ||||
|     QJsonObject object { | ||||
|         {"entity_id", controlId} | ||||
|     }; | ||||
|     QJsonDocument document; | ||||
|     document.setObject(object); | ||||
| 
 | ||||
|     m_networkManager->post(request, document.toJson()); | ||||
| } | ||||
| 
 | ||||
| void HomeAssistantDevice::handleReply(QNetworkReply* reply) | ||||
| { | ||||
|     if (reply) | ||||
|     { | ||||
|         if (!reply->error()) | ||||
|         { | ||||
|             QJsonDocument document = QJsonDocument::fromJson(reply->readAll()); | ||||
|             //qDebug() << "Received " << document;
 | ||||
|             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(); | ||||
|                     QString state = obj.value(QStringLiteral("state")).toString(); | ||||
|                     bool dOk; | ||||
|                     bool iOk; | ||||
|                     int i = state.toInt(&iOk); | ||||
|                     double d = state.toDouble(&dOk); | ||||
|                     if ((state == "on") || (state == "playing")) { | ||||
|                         status.insert(entityId, 1); | ||||
|                     } else if ((state == "off") || (state == "paused")) { | ||||
|                         status.insert(entityId, 0); | ||||
|                     } else if (iOk) { | ||||
|                         status.insert(entityId, i); | ||||
|                     } else if (dOk) { | ||||
|                         status.insert(entityId, d); | ||||
|                     } else { | ||||
|                         status.insert(entityId, state); | ||||
|                     } | ||||
|                     emit deviceUpdated(status); | ||||
|                 } | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 qDebug() << "HomeAssistantDevice::handleReply: Document is not an object: " << document; | ||||
|             } | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             qDebug() << "HomeAssistantDevice::handleReply: error: " << reply->error(); | ||||
|         } | ||||
|         reply->deleteLater(); | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         qDebug() << "HomeAssistantDevice::handleReply: reply is null"; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| HomeAssistantDeviceDiscoverer::HomeAssistantDeviceDiscoverer(const QString& apiKey, const QString& url) : | ||||
|     m_apiKey(apiKey), | ||||
|     m_url(url) | ||||
| { | ||||
|     m_networkManager = new QNetworkAccessManager(); | ||||
|     QObject::connect( | ||||
|         m_networkManager, | ||||
|         &QNetworkAccessManager::finished, | ||||
|         this, | ||||
|         &HomeAssistantDeviceDiscoverer::handleReply | ||||
|     ); | ||||
| } | ||||
| 
 | ||||
| HomeAssistantDeviceDiscoverer::~HomeAssistantDeviceDiscoverer() | ||||
| { | ||||
|     QObject::disconnect( | ||||
|         m_networkManager, | ||||
|         &QNetworkAccessManager::finished, | ||||
|         this, | ||||
|         &HomeAssistantDeviceDiscoverer::handleReply | ||||
|     ); | ||||
|     delete m_networkManager; | ||||
| } | ||||
| 
 | ||||
| void HomeAssistantDeviceDiscoverer::getDevices() | ||||
| { | ||||
|     QUrl url(m_url+ "/api/template"); | ||||
| 
 | ||||
|     QNetworkRequest request(url); | ||||
|     request.setRawHeader("Authorization", "Bearer " + m_apiKey.toLocal8Bit()); | ||||
|     request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); | ||||
| 
 | ||||
|     // Use templates to get a list of devices and associated entities
 | ||||
|     QString tpl = | ||||
|     "{% set devices = states | map(attribute='entity_id') | map('device_id') | unique | reject('eq',None)| list %}\n" | ||||
|     "{%- set ns = namespace(devices = []) %}\n" | ||||
|     "{%- for device in devices %}\n" | ||||
|     "  {%- set entities = device_entities(device) | list %}\n" | ||||
|     "  {%- if entities %}\n" | ||||
|     "    {%- set ens = namespace(entityobjs = []) %}\n" | ||||
|     "    {%- for entity in entities %}\n" | ||||
|     "      {%- set entityobj = {'entity_id': entity, 'name': state_attr(entity,'friendly_name'), 'unit_of_measurement': state_attr(entity,'unit_of_measurement')} %}\n" | ||||
|     "      {%- set ens.entityobjs = ens.entityobjs + [ entityobj ] %}\n" | ||||
|     "    {%- endfor %}\n" | ||||
|     "    {%- set obj = {'device_id': device, 'name': device_attr(device,'name'), 'name_by_user': device_attr(device,'name_by_user'), 'model': device_attr(device,'model'), 'entities': ens.entityobjs } %}\n" | ||||
|     "    {%- set ns.devices = ns.devices + [ obj ] %}\n" | ||||
|     "  {%- endif %}\n" | ||||
|     "{%- endfor %}\n" | ||||
|     "{{ ns.devices | tojson }}"; | ||||
| 
 | ||||
|     QJsonObject object { | ||||
|         {"template", tpl} | ||||
|     }; | ||||
|     QJsonDocument document; | ||||
|     document.setObject(object); | ||||
| 
 | ||||
|     m_networkManager->post(request, document.toJson()); | ||||
| } | ||||
| 
 | ||||
| void HomeAssistantDeviceDiscoverer::handleReply(QNetworkReply* reply) | ||||
| { | ||||
|     if (reply) | ||||
|     { | ||||
|         if (!reply->error()) | ||||
|         { | ||||
|             QList<DeviceInfo> devices; | ||||
|             QByteArray data = reply->readAll(); | ||||
|             //qDebug() << "Received " << data;
 | ||||
|             QJsonParseError error; | ||||
|             QJsonDocument document = QJsonDocument::fromJson(data, &error); | ||||
|             if (!document.isNull()) | ||||
|             { | ||||
|                 if (document.isArray()) | ||||
|                 { | ||||
|                     for (auto deviceRef : document.array()) | ||||
|                     { | ||||
|                         QJsonObject deviceObj = deviceRef.toObject(); | ||||
|                         if (deviceObj.contains(QStringLiteral("device_id")) && deviceObj.contains(QStringLiteral("entities"))) | ||||
|                         { | ||||
|                             QJsonArray entitiesArray = deviceObj.value(QStringLiteral("entities")).toArray(); | ||||
|                             if (entitiesArray.size() > 0) | ||||
|                             { | ||||
|                                 DeviceInfo info; | ||||
|                                 info.m_id = deviceObj.value(QStringLiteral("device_id")).toString(); | ||||
| 
 | ||||
|                                 if (deviceObj.contains(QStringLiteral("name_by_user"))) { | ||||
|                                     info.m_name = deviceObj.value(QStringLiteral("name_by_user")).toString(); | ||||
|                                 } | ||||
|                                 if (info.m_name.isEmpty() && deviceObj.contains(QStringLiteral("name"))) { | ||||
|                                     info.m_name = deviceObj.value(QStringLiteral("name")).toString(); | ||||
|                                 } | ||||
|                                 if (deviceObj.contains(QStringLiteral("model"))) { | ||||
|                                     info.m_model = deviceObj.value(QStringLiteral("model")).toString(); | ||||
|                                 } | ||||
| 
 | ||||
|                                 for (auto entityRef : entitiesArray) | ||||
|                                 { | ||||
|                                     QJsonObject entityObj = entityRef.toObject(); | ||||
|                                     QString entity = entityObj.value(QStringLiteral("entity_id")).toString(); | ||||
|                                     QString name = entityObj.value(QStringLiteral("name")).toString(); | ||||
|                                     QString domain = entity.left(entity.indexOf('.')); | ||||
|                                     if (domain == "binary_sensor") | ||||
|                                     { | ||||
|                                         SensorInfo *sensorInfo = new SensorInfo(); | ||||
|                                         sensorInfo->m_name = name; | ||||
|                                         sensorInfo->m_id = entity; | ||||
|                                         sensorInfo->m_type = DeviceDiscoverer::BOOL; | ||||
|                                         sensorInfo->m_units = entityObj.value(QStringLiteral("unit_of_measurement")).toString(); | ||||
|                                         info.m_sensors.append(sensorInfo); | ||||
|                                     } | ||||
|                                     else if (domain == "sensor") | ||||
|                                     { | ||||
|                                         SensorInfo *sensorInfo = new SensorInfo(); | ||||
|                                         sensorInfo->m_name = name; | ||||
|                                         sensorInfo->m_id = entity; | ||||
|                                         sensorInfo->m_type = DeviceDiscoverer::FLOAT; // FIXME: Auto?
 | ||||
|                                         sensorInfo->m_units = entityObj.value(QStringLiteral("unit_of_measurement")).toString(); | ||||
|                                         info.m_sensors.append(sensorInfo); | ||||
|                                     } | ||||
|                                     else if ((domain == "switch") || (domain == "light") || (domain == "media_player")) // Entities that support turn_on/turn_off
 | ||||
|                                     { | ||||
|                                         ControlInfo *controlInfo = new ControlInfo(); | ||||
|                                         controlInfo->m_name = name; | ||||
|                                         controlInfo->m_id = entity; | ||||
|                                         controlInfo->m_type = DeviceDiscoverer::BOOL; | ||||
|                                         info.m_controls.append(controlInfo); | ||||
|                                     } | ||||
|                                     else | ||||
|                                     { | ||||
|                                         qDebug() << "HomeAssistantDeviceDiscoverer::handleReply: Unsupported domain: " << domain; | ||||
|                                     } | ||||
|                                 } | ||||
| 
 | ||||
|                                 if ((info.m_controls.size() > 0) || (info.m_sensors.size() > 0)) | ||||
|                                 { | ||||
|                                     devices.append(info); | ||||
|                                 } | ||||
|                             } | ||||
|                             else | ||||
|                             { | ||||
|                                 //qDebug() << "HomeAssistantDeviceDiscoverer::handleReply: No entities " << deviceObj.value(QStringLiteral("device_id")).toString();
 | ||||
|                             } | ||||
|                         } | ||||
|                         else | ||||
|                         { | ||||
|                             //qDebug() << "HomeAssistantDeviceDiscoverer::handleReply: device_id or entities missing";
 | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     qDebug() << "HomeAssistantDeviceDiscoverer::handleReply: Document is not an array: " << document; | ||||
|                 } | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 qDebug() << "HomeAssistantDeviceDiscoverer::handleReply: Error parson JSON: " << error.errorString() << " at offset " << error.offset; | ||||
|             } | ||||
|             emit deviceList(devices); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             qDebug() << "HomeAssistantDeviceDiscoverer::handleReply: error: " << reply->error() << ":" << reply->errorString(); | ||||
|             // Get QNetworkReply::AuthenticationRequiredError if token is invalid
 | ||||
|             if (reply->error() == QNetworkReply::AuthenticationRequiredError) { | ||||
|                 emit error("Home Assistant: Authentication failed. Check access token is valid."); | ||||
|             } else { | ||||
|                 emit error(QString("Home Assistant: Network error. %1").arg(reply->errorString())); | ||||
|             } | ||||
|         } | ||||
|         reply->deleteLater(); | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         qDebug() << "HomeAssistantDeviceDiscoverer::handleReply: reply is null"; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										70
									
								
								sdrbase/util/iot/homeassistant.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								sdrbase/util/iot/homeassistant.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,70 @@ | ||||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| // Copyright (C) 2022 Jon Beniston, M7RCE                                        //
 | ||||
| //                                                                               //
 | ||||
| // This program 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 as version 3 of the License, or                  //
 | ||||
| // (at your option) any later version.                                           //
 | ||||
| //                                                                               //
 | ||||
| // This program 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 V3 for more details.                               //
 | ||||
| //                                                                               //
 | ||||
| // You should have received a copy of the GNU General Public License             //
 | ||||
| // along with this program. If not, see <http://www.gnu.org/licenses/>.          //
 | ||||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| 
 | ||||
| #ifndef INCLUDE_IOT_HOMEASSISTANT_H | ||||
| #define INCLUDE_IOT_HOMEASSISTANT_H | ||||
| 
 | ||||
| #include "util/iot/device.h" | ||||
| 
 | ||||
| // Supports Home Assistant devices - https://www.home-assistant.io/
 | ||||
| class SDRBASE_API HomeAssistantDevice : public Device { | ||||
|     Q_OBJECT | ||||
| public: | ||||
| 
 | ||||
|     HomeAssistantDevice(const QString& apiKey, const QString& url, const QString &deviceId, | ||||
|                         const QStringList &controls, const QStringList &sensors, | ||||
|                         DeviceDiscoverer::DeviceInfo *info=nullptr); | ||||
|     ~HomeAssistantDevice(); | ||||
|     virtual void getState() override; | ||||
|     virtual void setState(const QString &controlId, bool state) override; | ||||
|     virtual QString getProtocol() const override { return "HomeAssistant"; } | ||||
|     virtual QString getDeviceId() const override { return m_deviceId; } | ||||
| 
 | ||||
| private: | ||||
| 
 | ||||
|     QString m_deviceId; | ||||
|     QStringList m_entities; // List of entities that are part of the device, to get state for (controls and sensors)
 | ||||
|     QString m_apiKey;       // Bearer token
 | ||||
|     QString m_url;          // Typically http://homeassistant.local:8123
 | ||||
|     QNetworkAccessManager *m_networkManager; | ||||
| 
 | ||||
| public slots: | ||||
|     void handleReply(QNetworkReply* reply); | ||||
| 
 | ||||
| }; | ||||
| 
 | ||||
| class SDRBASE_API HomeAssistantDeviceDiscoverer : public DeviceDiscoverer { | ||||
|     Q_OBJECT | ||||
| public: | ||||
| 
 | ||||
|     HomeAssistantDeviceDiscoverer(const QString& apiKey, const QString& url); | ||||
|     ~HomeAssistantDeviceDiscoverer(); | ||||
|     virtual void getDevices() override; | ||||
| 
 | ||||
| private: | ||||
| 
 | ||||
|     QString m_deviceId; | ||||
|     QString m_apiKey;       // Bearer token
 | ||||
|     QString m_url;          // Typically http://homeassistant.local:8123
 | ||||
|     QNetworkAccessManager *m_networkManager; | ||||
| 
 | ||||
| public slots: | ||||
|     void handleReply(QNetworkReply* reply); | ||||
| 
 | ||||
| }; | ||||
| 
 | ||||
| #endif /* INCLUDE_IOT_HOMEASSISTANT_H */ | ||||
							
								
								
									
										634
									
								
								sdrbase/util/iot/tplink.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										634
									
								
								sdrbase/util/iot/tplink.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,634 @@ | ||||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| // Copyright (C) 2022 Jon Beniston, M7RCE                                        //
 | ||||
| //                                                                               //
 | ||||
| // This program 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 as version 3 of the License, or                  //
 | ||||
| // (at your option) any later version.                                           //
 | ||||
| //                                                                               //
 | ||||
| // This program 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 V3 for more details.                               //
 | ||||
| //                                                                               //
 | ||||
| // You should have received a copy of the GNU General Public License             //
 | ||||
| // along with this program. If not, see <http://www.gnu.org/licenses/>.          //
 | ||||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| 
 | ||||
| 
 | ||||
| #include <QDebug> | ||||
| #include <QUrl> | ||||
| #include <QUrlQuery> | ||||
| #include <QNetworkReply> | ||||
| #include <QJsonDocument> | ||||
| #include <QJsonObject> | ||||
| #include <QRegularExpression> | ||||
| 
 | ||||
| #include "util/iot/tplink.h" | ||||
| #include "util/simpleserializer.h" | ||||
| 
 | ||||
| const QString TPLinkCommon::m_url = "https://wap.tplinkcloud.com"; | ||||
| 
 | ||||
| TPLinkCommon::TPLinkCommon(const QString& username, const QString &password) : | ||||
|     m_loggedIn(false), | ||||
|     m_outstandingRequest(false), | ||||
|     m_username(username), | ||||
|     m_password(password), | ||||
|     m_networkManager(nullptr) | ||||
| { | ||||
| } | ||||
| 
 | ||||
| void TPLinkCommon::login() | ||||
| { | ||||
|     QUrl url(m_url); | ||||
| 
 | ||||
|     QNetworkRequest request(url); | ||||
|     request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); | ||||
|     QJsonObject params { | ||||
|         {"appType", "Kasa_Android"}, | ||||
|         {"cloudUserName", m_username}, | ||||
|         {"cloudPassword", m_password}, | ||||
|         {"terminalUUID", "9cc4653e-338f-48e4-b8ca-6ed3f67631e4"} | ||||
|     }; | ||||
|     QJsonObject object { | ||||
|         {"method", "login"}, | ||||
|         {"params", params} | ||||
|     }; | ||||
|     QJsonDocument document; | ||||
|     document.setObject(object); | ||||
| 
 | ||||
|     m_networkManager->post(request, document.toJson()); | ||||
| } | ||||
| 
 | ||||
| void TPLinkCommon::handleLoginReply(QNetworkReply* reply, QString &errorMessage) | ||||
| { | ||||
|     if (reply) | ||||
|     { | ||||
|         if (!reply->error()) | ||||
|         { | ||||
|             QJsonDocument document = QJsonDocument::fromJson(reply->readAll()); | ||||
|             if (document.isObject()) | ||||
|             { | ||||
|                 //qDebug() << "Received " << document;
 | ||||
|                 if (!m_loggedIn) | ||||
|                 { | ||||
|                     QJsonObject obj = document.object(); | ||||
|                     if (obj.contains(QStringLiteral("error_code"))) | ||||
|                     { | ||||
|                         int errorCode = obj.value(QStringLiteral("error_code")).toInt(); | ||||
|                         if (!errorCode) | ||||
|                         { | ||||
|                             if (obj.contains(QStringLiteral("result"))) | ||||
|                             { | ||||
|                                 QJsonObject result = obj.value(QStringLiteral("result")).toObject(); | ||||
|                                 if (result.contains(QStringLiteral("token"))) | ||||
|                                 { | ||||
|                                     m_loggedIn = true; | ||||
|                                     m_token = result.value(QStringLiteral("token")).toString(); | ||||
|                                 } | ||||
|                                 else | ||||
|                                 { | ||||
|                                     qDebug() << "TPLinkDevice::handleReply: Object doesn't contain a token: " << result; | ||||
|                                 } | ||||
|                             } | ||||
|                             else | ||||
|                             { | ||||
|                                 qDebug() << "TPLinkDevice::handleReply: Object doesn't contain a result object: " << obj; | ||||
|                             } | ||||
|                         } | ||||
|                         else | ||||
|                         { | ||||
|                             qDebug() << "TPLinkDevice::handleReply: Non-zero error_code while logging in: " << errorCode; | ||||
|                             if (obj.contains(QStringLiteral("msg"))) | ||||
|                             { | ||||
|                                 QString msg = obj.value(QStringLiteral("msg")).toString(); | ||||
|                                 qDebug() << "TPLinkDevice::handleReply: Error message: " << msg; | ||||
|                                 // Typical msg is "Incorrect email or password"
 | ||||
|                                 errorMessage = QString("TP-Link: Failed to log in. %1").arg(msg); | ||||
|                             } | ||||
|                             else | ||||
|                             { | ||||
|                                 errorMessage = QString("TP-Link: Failed to log in. Error code: %1").arg(errorCode); | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         qDebug() << "TPLinkDevice::handleReply: Object doesn't contain an error_code: " << obj; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 qDebug() << "TPLinkDevice::handleReply: Document is not an object: " << document; | ||||
|             } | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             qDebug() << "TPLinkDevice::handleReply: error: " << reply->error(); | ||||
|         } | ||||
|         reply->deleteLater(); | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         qDebug() << "TPLinkDevice::handleReply: reply is null"; | ||||
|     } | ||||
| 
 | ||||
|     if (!m_loggedIn && errorMessage.isEmpty()) { | ||||
|         errorMessage = "TP-Link: Failed to log in."; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| TPLinkDevice::TPLinkDevice(const QString& username, const QString &password, const QString &deviceId, DeviceDiscoverer::DeviceInfo *info) : | ||||
|     Device(info), | ||||
|     TPLinkCommon(username, password), | ||||
|     m_deviceId(deviceId) | ||||
| { | ||||
|     m_networkManager = new QNetworkAccessManager(); | ||||
|     QObject::connect( | ||||
|         m_networkManager, | ||||
|         &QNetworkAccessManager::finished, | ||||
|         this, | ||||
|         &TPLinkDevice::handleReply | ||||
|     ); | ||||
|     login(); | ||||
| } | ||||
| 
 | ||||
| TPLinkDevice::~TPLinkDevice() | ||||
| { | ||||
|     QObject::disconnect( | ||||
|         m_networkManager, | ||||
|         &QNetworkAccessManager::finished, | ||||
|         this, | ||||
|         &TPLinkDevice::handleReply | ||||
|     ); | ||||
|     delete m_networkManager; | ||||
| } | ||||
| 
 | ||||
| void TPLinkDevice::getState() | ||||
| { | ||||
|     if (!m_loggedIn) | ||||
|     { | ||||
|         m_outstandingRequest = true; | ||||
|         return; | ||||
|     } | ||||
|     QUrl url(m_url); | ||||
| 
 | ||||
|     QNetworkRequest request(url); | ||||
|     request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); | ||||
|     QJsonObject system; | ||||
|     system.insert("get_sysinfo", QJsonValue()); | ||||
|     QJsonObject emeter; | ||||
|     emeter.insert("get_realtime", QJsonValue()); | ||||
|     QJsonObject requestData { | ||||
|         {"system", system}, | ||||
|         {"emeter", emeter} | ||||
|     }; | ||||
|     QJsonObject params { | ||||
|         {"deviceId", m_deviceId}, | ||||
|         {"requestData", requestData}, | ||||
|         {"token", m_token} | ||||
|     }; | ||||
|     QJsonObject object { | ||||
|         {"method", "passthrough"}, | ||||
|         {"params", params} | ||||
|     }; | ||||
|     QJsonDocument document; | ||||
|     document.setObject(object); | ||||
| 
 | ||||
|     m_networkManager->post(request, document.toJson()); | ||||
| } | ||||
| 
 | ||||
| void TPLinkDevice::setState(const QString &controlId, bool state) | ||||
| { | ||||
|     if (!m_loggedIn) | ||||
|     { | ||||
|         // Should we queue these and apply after logged in?
 | ||||
|         qDebug() << "TPLinkDevice::setState: Unable to set state for " << controlId << " to " << state << " as not yet logged in"; | ||||
|         return; | ||||
|     } | ||||
|     QUrl url(m_url); | ||||
| 
 | ||||
|     QNetworkRequest request(url); | ||||
|     request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); | ||||
|     QJsonObject stateObj { | ||||
|         {"state", (int)state} | ||||
|     }; | ||||
|     QJsonObject system { | ||||
|         {"set_relay_state", stateObj} | ||||
|     }; | ||||
|     QJsonObject requestData { | ||||
|         {"system", system} | ||||
|     }; | ||||
|     if (controlId != "switch") { | ||||
|         QJsonArray childIds { | ||||
|             controlId | ||||
|         }; | ||||
|         QJsonObject context { | ||||
|             {"child_ids", childIds} | ||||
|         }; | ||||
|         requestData.insert("context", QJsonValue(context)); | ||||
|     } | ||||
|     QJsonObject params { | ||||
|         {"deviceId", m_deviceId}, | ||||
|         {"requestData", requestData}, | ||||
|         {"token", m_token} | ||||
|     }; | ||||
|     QJsonObject object { | ||||
|         {"method", "passthrough"}, | ||||
|         {"params", params} | ||||
|     }; | ||||
|     QJsonDocument document; | ||||
|     document.setObject(object); | ||||
| 
 | ||||
|     m_networkManager->post(request, document.toJson()); | ||||
| } | ||||
| 
 | ||||
| void TPLinkDevice::handleReply(QNetworkReply* reply) | ||||
| { | ||||
|     if (!m_loggedIn) | ||||
|     { | ||||
|         QString errorMessage; | ||||
|         TPLinkCommon::handleLoginReply(reply, errorMessage); | ||||
|         if (!errorMessage.isEmpty()) | ||||
|         { | ||||
|             emit error(errorMessage); | ||||
|         } | ||||
|         else if (m_outstandingRequest) | ||||
|         { | ||||
|             m_outstandingRequest = true; | ||||
|             getState(); | ||||
|         } | ||||
|     } | ||||
|     else if (reply) | ||||
|     { | ||||
|         if (!reply->error()) | ||||
|         { | ||||
|             QJsonDocument document = QJsonDocument::fromJson(reply->readAll()); | ||||
|             if (document.isObject()) | ||||
|             { | ||||
|                 //qDebug() << "Received " << document;
 | ||||
|                 QJsonObject obj = document.object(); | ||||
|                 if (obj.contains(QStringLiteral("result"))) | ||||
|                 { | ||||
|                     QJsonObject resultObj = obj.value(QStringLiteral("result")).toObject(); | ||||
|                     QHash<QString, QVariant> status; | ||||
| 
 | ||||
|                     if (resultObj.contains(QStringLiteral("responseData"))) | ||||
|                     { | ||||
|                         QJsonObject responseDataObj = resultObj.value(QStringLiteral("responseData")).toObject(); | ||||
|                         if (responseDataObj.contains(QStringLiteral("system"))) | ||||
|                         { | ||||
|                             QJsonObject systemObj = responseDataObj.value(QStringLiteral("system")).toObject(); | ||||
|                             if (systemObj.contains(QStringLiteral("get_sysinfo"))) | ||||
|                             { | ||||
|                                 QJsonObject sysInfoObj = systemObj.value(QStringLiteral("get_sysinfo")).toObject(); | ||||
|                                 if (sysInfoObj.contains(QStringLiteral("child_num"))) | ||||
|                                 { | ||||
|                                     int childNum = sysInfoObj.value(QStringLiteral("child_num")).toInt(); | ||||
|                                     QJsonArray children = sysInfoObj.value(QStringLiteral("children")).toArray(); | ||||
|                                     for (auto childRef : children) | ||||
|                                     { | ||||
|                                         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(); | ||||
|                                             status.insert(id, state); // key should match id in discoverer
 | ||||
|                                         } | ||||
|                                     } | ||||
|                                 } | ||||
|                                 else if (sysInfoObj.contains(QStringLiteral("relay_state"))) | ||||
|                                 { | ||||
|                                     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"))) | ||||
|                         { | ||||
|                             QJsonObject emeterObj = responseDataObj.value(QStringLiteral("emeter")).toObject(); | ||||
|                             if (emeterObj.contains(QStringLiteral("get_realtime"))) | ||||
|                             { | ||||
|                                 QJsonObject realtimeObj = emeterObj.value(QStringLiteral("get_realtime")).toObject(); | ||||
|                                 if (realtimeObj.contains(QStringLiteral("current_ma"))) | ||||
|                                 { | ||||
|                                     double current = realtimeObj.value(QStringLiteral("current_ma")).toDouble(); | ||||
|                                     status.insert("current", current / 1000.0); | ||||
|                                 } | ||||
|                                 if (realtimeObj.contains(QStringLiteral("voltage_mv"))) | ||||
|                                 { | ||||
|                                     double voltage = realtimeObj.value(QStringLiteral("voltage_mv")).toDouble(); | ||||
|                                     status.insert("voltage", voltage / 1000.0); | ||||
|                                 } | ||||
|                                 if (realtimeObj.contains(QStringLiteral("power_mw"))) | ||||
|                                 { | ||||
|                                     double power = realtimeObj.value(QStringLiteral("power_mw")).toDouble(); | ||||
|                                     status.insert("power", power / 1000.0); | ||||
|                                 } | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
| 
 | ||||
|                     emit deviceUpdated(status); | ||||
|                 } | ||||
|                 else if (obj.contains(QStringLiteral("error_code"))) | ||||
|                 { | ||||
|                     // If a device isn't available, we can get:
 | ||||
|                     //    {"error_code":-20002,"msg":"Request timeout"}
 | ||||
|                     //    {"error_code":-20571,"msg":"Device is offline"}
 | ||||
|                     int errorCode = obj.value(QStringLiteral("error_code")).toInt(); | ||||
|                     QString msg = obj.value(QStringLiteral("msg")).toString(); | ||||
|                     qDebug() << "TPLinkDevice::handleReply: Error code: " << errorCode << " " << msg; | ||||
| 
 | ||||
|                     emit deviceUnavailable(); | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     qDebug() << "TPLinkDevice::handleReply: Object doesn't contain a result or error_code: " << obj; | ||||
|                 } | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 qDebug() << "TPLinkDevice::handleReply: Document is not an object: " << document; | ||||
|             } | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             qDebug() << "TPLinkDevice::handleReply: error: " << reply->error(); | ||||
|         } | ||||
|         reply->deleteLater(); | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         qDebug() << "TPLinkDevice::handleReply: reply is null"; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| TPLinkDeviceDiscoverer::TPLinkDeviceDiscoverer(const QString& username, const QString &password) : | ||||
|     TPLinkCommon(username, password) | ||||
| { | ||||
|     m_networkManager = new QNetworkAccessManager(); | ||||
|     QObject::connect( | ||||
|         m_networkManager, | ||||
|         &QNetworkAccessManager::finished, | ||||
|         this, | ||||
|         &TPLinkDeviceDiscoverer::handleReply | ||||
|     ); | ||||
|     login(); | ||||
| } | ||||
| 
 | ||||
| TPLinkDeviceDiscoverer::~TPLinkDeviceDiscoverer() | ||||
| { | ||||
|     QObject::disconnect( | ||||
|         m_networkManager, | ||||
|         &QNetworkAccessManager::finished, | ||||
|         this, | ||||
|         &TPLinkDeviceDiscoverer::handleReply | ||||
|     ); | ||||
|     delete m_networkManager; | ||||
| } | ||||
| 
 | ||||
| void TPLinkDeviceDiscoverer::getDevices() | ||||
| { | ||||
|     if (!m_loggedIn) | ||||
|     { | ||||
|         m_outstandingRequest = true; | ||||
|         return; | ||||
|     } | ||||
|     QUrl url(m_url); | ||||
| 
 | ||||
|     QNetworkRequest request(url); | ||||
|     request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); | ||||
|     QJsonObject params { | ||||
|         {"token", m_token} | ||||
|     }; | ||||
|     QJsonObject object { | ||||
|         {"method", "getDeviceList"}, | ||||
|         {"params", params} | ||||
|     }; | ||||
|     QJsonDocument document; | ||||
|     document.setObject(object); | ||||
| 
 | ||||
|     m_networkManager->post(request, document.toJson()); | ||||
| } | ||||
| 
 | ||||
| void TPLinkDeviceDiscoverer::getState(const QString &deviceId) | ||||
| { | ||||
|     QUrl url(m_url); | ||||
| 
 | ||||
|     QNetworkRequest request(url); | ||||
|     request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); | ||||
|     QJsonObject system; | ||||
|     system.insert("get_sysinfo", QJsonValue()); | ||||
|     QJsonObject emeter; | ||||
|     emeter.insert("get_realtime", QJsonValue()); | ||||
|     QJsonObject requestData { | ||||
|         {"system", system}, | ||||
|         {"emeter", emeter} | ||||
|     }; | ||||
|     QJsonObject params { | ||||
|         {"deviceId", deviceId}, | ||||
|         {"requestData", requestData}, | ||||
|         {"token", m_token} | ||||
|     }; | ||||
|     QJsonObject object { | ||||
|         {"method", "passthrough"}, | ||||
|         {"params", params} | ||||
|     }; | ||||
|     QJsonDocument document; | ||||
|     document.setObject(object); | ||||
| 
 | ||||
|     m_getStateReplies.insert(m_networkManager->post(request, document.toJson()), deviceId); | ||||
| } | ||||
| 
 | ||||
| void TPLinkDeviceDiscoverer::handleReply(QNetworkReply* reply) | ||||
| { | ||||
|     if (!m_loggedIn) | ||||
|     { | ||||
|         QString errorMessage; | ||||
|         TPLinkCommon::handleLoginReply(reply, errorMessage); | ||||
|         if (!errorMessage.isEmpty()) | ||||
|         { | ||||
|             emit error(errorMessage); | ||||
|         } | ||||
|         else if (m_outstandingRequest) | ||||
|         { | ||||
|             m_outstandingRequest = false; | ||||
|             getDevices(); | ||||
|         } | ||||
|     } | ||||
|     else if (reply) | ||||
|     { | ||||
|         if (!reply->error()) | ||||
|         { | ||||
|             QJsonDocument document = QJsonDocument::fromJson(reply->readAll()); | ||||
|             if (document.isObject()) | ||||
|             { | ||||
|                 //qDebug() << "Received " << document;
 | ||||
|                 QJsonObject obj = document.object(); | ||||
| 
 | ||||
|                 if (m_getStateReplies.contains(reply)) | ||||
|                 { | ||||
|                     // Reply for getState
 | ||||
|                     m_getStateReplies.remove(reply); | ||||
|                     QJsonObject resultObj = obj.value(QStringLiteral("result")).toObject(); | ||||
|                     if (resultObj.contains(QStringLiteral("responseData"))) | ||||
|                     { | ||||
|                         QJsonObject responseDataObj = resultObj.value(QStringLiteral("responseData")).toObject(); | ||||
|                         if (responseDataObj.contains(QStringLiteral("system"))) | ||||
|                         { | ||||
|                             DeviceInfo info; | ||||
|                             QJsonObject systemObj = responseDataObj.value(QStringLiteral("system")).toObject(); | ||||
|                             if (systemObj.contains(QStringLiteral("get_sysinfo"))) | ||||
|                             { | ||||
|                                 QJsonObject sysInfoObj = systemObj.value(QStringLiteral("get_sysinfo")).toObject(); | ||||
|                                 if (sysInfoObj.contains(QStringLiteral("alias"))) { | ||||
|                                     info.m_name = sysInfoObj.value(QStringLiteral("alias")).toString(); | ||||
|                                 } | ||||
|                                 if (sysInfoObj.contains(QStringLiteral("model"))) { | ||||
|                                     info.m_model = sysInfoObj.value(QStringLiteral("model")).toString(); | ||||
|                                 } | ||||
|                                 if (sysInfoObj.contains(QStringLiteral("deviceId"))) { | ||||
|                                     info.m_id = sysInfoObj.value(QStringLiteral("deviceId")).toString(); | ||||
|                                 } | ||||
|                                 if (sysInfoObj.contains(QStringLiteral("child_num"))) | ||||
|                                 { | ||||
|                                     int childNum = sysInfoObj.value(QStringLiteral("child_num")).toInt(); | ||||
|                                     QJsonArray children = sysInfoObj.value(QStringLiteral("children")).toArray(); | ||||
|                                     int child = 1; | ||||
|                                     for (auto childRef : children) | ||||
|                                     { | ||||
|                                         QJsonObject childObj = childRef.toObject(); | ||||
|                                         ControlInfo *controlInfo = new ControlInfo(); | ||||
|                                         controlInfo->m_id = childObj.value(QStringLiteral("id")).toString(); | ||||
|                                         if (childObj.contains(QStringLiteral("alias"))) { | ||||
|                                             controlInfo->m_name = childObj.value(QStringLiteral("alias")).toString(); | ||||
|                                         } | ||||
|                                         controlInfo->m_type = DeviceDiscoverer::BOOL; | ||||
|                                         info.m_controls.append(controlInfo); | ||||
|                                         child++; | ||||
|                                     } | ||||
|                                 } | ||||
|                                 else if (sysInfoObj.contains(QStringLiteral("relay_state"))) | ||||
|                                 { | ||||
|                                     ControlInfo *controlInfo = new ControlInfo(); | ||||
|                                     controlInfo->m_id = "switch"; | ||||
|                                     if (sysInfoObj.contains(QStringLiteral("alias"))) { | ||||
|                                         controlInfo->m_name = sysInfoObj.value(QStringLiteral("alias")).toString(); | ||||
|                                     } | ||||
|                                     controlInfo->m_type = DeviceDiscoverer::BOOL; | ||||
|                                     info.m_controls.append(controlInfo); | ||||
|                                 } | ||||
|                             } | ||||
|                             else | ||||
|                             { | ||||
|                                 qDebug() << "TPLinkDeviceDiscoverer::handleReply: get_sysinfo missing"; | ||||
|                             } | ||||
|                             // KP115 has energy meter, but KP105 doesn't. KP105 will have emeter object, but without get_realtime sub-object
 | ||||
|                             if (responseDataObj.contains(QStringLiteral("emeter"))) | ||||
|                             { | ||||
|                                 QJsonObject emeterObj = responseDataObj.value(QStringLiteral("emeter")).toObject(); | ||||
|                                 if (emeterObj.contains(QStringLiteral("get_realtime"))) | ||||
|                                 { | ||||
|                                     QJsonObject realtimeObj = emeterObj.value(QStringLiteral("get_realtime")).toObject(); | ||||
|                                     if (realtimeObj.contains(QStringLiteral("current_ma"))) | ||||
|                                     { | ||||
|                                         SensorInfo *currentSensorInfo = new SensorInfo(); | ||||
|                                         currentSensorInfo->m_name = "Current"; | ||||
|                                         currentSensorInfo->m_id = "current"; | ||||
|                                         currentSensorInfo->m_type = DeviceDiscoverer::FLOAT; | ||||
|                                         currentSensorInfo->m_units = "A"; | ||||
|                                         info.m_sensors.append(currentSensorInfo); | ||||
|                                     } | ||||
|                                     if (realtimeObj.contains(QStringLiteral("voltage_mv"))) | ||||
|                                     { | ||||
|                                         SensorInfo *voltageSensorInfo = new SensorInfo(); | ||||
|                                         voltageSensorInfo->m_name = "Voltage"; | ||||
|                                         voltageSensorInfo->m_id = "voltage"; | ||||
|                                         voltageSensorInfo->m_type = DeviceDiscoverer::FLOAT; | ||||
|                                         voltageSensorInfo->m_units = "V"; | ||||
|                                         info.m_sensors.append(voltageSensorInfo); | ||||
|                                     } | ||||
|                                     if (realtimeObj.contains(QStringLiteral("power_mw"))) | ||||
|                                     { | ||||
|                                         SensorInfo *powerSensorInfo = new SensorInfo(); | ||||
|                                         powerSensorInfo->m_name = "Power"; | ||||
|                                         powerSensorInfo->m_id = "power"; | ||||
|                                         powerSensorInfo->m_type = DeviceDiscoverer::FLOAT; | ||||
|                                         powerSensorInfo->m_units = "W"; | ||||
|                                         info.m_sensors.append(powerSensorInfo); | ||||
|                                     } | ||||
|                                 } | ||||
|                             } | ||||
|                             if (info.m_controls.size() > 0) { | ||||
|                                 m_devices.append(info); | ||||
|                             } else { | ||||
|                                 qDebug() << "TPLinkDeviceDiscoverer::handleReply: No controls in info"; | ||||
|                             } | ||||
| 
 | ||||
|                         } | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         qDebug() << "TPLinkDeviceDiscoverer::handleReply: No responseData"; | ||||
|                     } | ||||
| 
 | ||||
|                     if (m_getStateReplies.size() == 0) | ||||
|                     { | ||||
|                         emit deviceList(m_devices); | ||||
|                         m_devices.clear(); | ||||
|                     } | ||||
| 
 | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     // Reply for getDevice
 | ||||
|                     if (obj.contains(QStringLiteral("result"))) | ||||
|                     { | ||||
|                         QJsonObject resultObj = obj.value(QStringLiteral("result")).toObject(); | ||||
|                         if (resultObj.contains(QStringLiteral("deviceList"))) | ||||
|                         { | ||||
|                             QJsonArray deviceArray = resultObj.value(QStringLiteral("deviceList")).toArray(); | ||||
|                             for (auto deviceRef : deviceArray) | ||||
|                             { | ||||
|                                 QJsonObject deviceObj = deviceRef.toObject(); | ||||
|                                 if (deviceObj.contains(QStringLiteral("deviceId")) && deviceObj.contains(QStringLiteral("deviceType"))) | ||||
|                                 { | ||||
|                                     // In order to discover what controls and sensors a device has, we need to get sysinfo
 | ||||
|                                     getState(deviceObj.value(QStringLiteral("deviceId")).toString()); | ||||
|                                 } | ||||
|                                 else | ||||
|                                 { | ||||
|                                     qDebug() << "TPLinkDeviceDiscoverer::handleReply: deviceList element doesn't contain a deviceId: " << deviceObj; | ||||
|                                 } | ||||
|                             } | ||||
|                         } | ||||
|                         else | ||||
|                         { | ||||
|                             qDebug() << "TPLinkDeviceDiscoverer::handleReply: result doesn't contain a deviceList: " << resultObj; | ||||
|                         } | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         qDebug() << "TPLinkDeviceDiscoverer::handleReply: Object doesn't contain a result: " << obj; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 qDebug() << "TPLinkDeviceDiscoverer::handleReply: Document is not an object: " << document; | ||||
|             } | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             qDebug() << "TPLinkDeviceDiscoverer::handleReply: error: " << reply->error(); | ||||
|         } | ||||
|         reply->deleteLater(); | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         qDebug() << "TPLinkDeviceDiscoverer::handleReply: reply is null"; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										77
									
								
								sdrbase/util/iot/tplink.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								sdrbase/util/iot/tplink.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,77 @@ | ||||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| // Copyright (C) 2022 Jon Beniston, M7RCE                                        //
 | ||||
| //                                                                               //
 | ||||
| // This program 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 as version 3 of the License, or                  //
 | ||||
| // (at your option) any later version.                                           //
 | ||||
| //                                                                               //
 | ||||
| // This program 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 V3 for more details.                               //
 | ||||
| //                                                                               //
 | ||||
| // You should have received a copy of the GNU General Public License             //
 | ||||
| // along with this program. If not, see <http://www.gnu.org/licenses/>.          //
 | ||||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| 
 | ||||
| #ifndef INCLUDE_IOT_TPLINK_H | ||||
| #define INCLUDE_IOT_TPLINK_H | ||||
| 
 | ||||
| #include "util/iot/device.h" | ||||
| 
 | ||||
| class SDRBASE_API TPLinkCommon { | ||||
| protected: | ||||
|     TPLinkCommon(const QString& username, const QString &password); | ||||
|     void login(); | ||||
|     void handleLoginReply(QNetworkReply* reply, QString &errorMessage); | ||||
| 
 | ||||
|     bool m_loggedIn; | ||||
|     bool m_outstandingRequest;      // Issue getState / getDevices after logged in
 | ||||
|     QString m_username; | ||||
|     QString m_password; | ||||
|     QString m_token; | ||||
|     QNetworkAccessManager *m_networkManager; | ||||
| 
 | ||||
|     static const QString m_url; | ||||
| }; | ||||
| 
 | ||||
| // Supports TPLink's Kasa plugs - https://www.tp-link.com/uk/smarthome/
 | ||||
| class SDRBASE_API TPLinkDevice : public Device, TPLinkCommon { | ||||
|     Q_OBJECT | ||||
| public: | ||||
| 
 | ||||
|     TPLinkDevice(const QString& username, const QString &password, const QString &deviceId, DeviceDiscoverer::DeviceInfo *info=nullptr); | ||||
|     ~TPLinkDevice(); | ||||
|     virtual void getState() override; | ||||
|     virtual void setState(const QString &controlId, bool state) override; | ||||
|     virtual QString getProtocol() const override { return "TPLink"; } | ||||
|     virtual QString getDeviceId() const override { return m_deviceId; } | ||||
| 
 | ||||
| private: | ||||
| 
 | ||||
|     QString m_deviceId; | ||||
| 
 | ||||
| public slots: | ||||
|     void handleReply(QNetworkReply* reply); | ||||
| }; | ||||
| 
 | ||||
| class SDRBASE_API TPLinkDeviceDiscoverer : public DeviceDiscoverer, TPLinkCommon { | ||||
|     Q_OBJECT | ||||
| public: | ||||
| 
 | ||||
|     TPLinkDeviceDiscoverer(const QString& username, const QString &password); | ||||
|     ~TPLinkDeviceDiscoverer(); | ||||
|     virtual void getDevices() override; | ||||
| 
 | ||||
| private: | ||||
|     void getState(const QString &deviceId); | ||||
| 
 | ||||
|     QHash<QNetworkReply *, QString> m_getStateReplies; | ||||
|     QList<DeviceInfo> m_devices; | ||||
| 
 | ||||
| public slots: | ||||
|     void handleReply(QNetworkReply* reply); | ||||
| }; | ||||
| 
 | ||||
| #endif /* INCLUDE_IOT_TPLINK_H */ | ||||
							
								
								
									
										506
									
								
								sdrbase/util/iot/visa.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										506
									
								
								sdrbase/util/iot/visa.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,506 @@ | ||||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| // Copyright (C) 2022 Jon Beniston, M7RCE                                        //
 | ||||
| //                                                                               //
 | ||||
| // This program 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 as version 3 of the License, or                  //
 | ||||
| // (at your option) any later version.                                           //
 | ||||
| //                                                                               //
 | ||||
| // This program 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 V3 for more details.                               //
 | ||||
| //                                                                               //
 | ||||
| // You should have received a copy of the GNU General Public License             //
 | ||||
| // along with this program. If not, see <http://www.gnu.org/licenses/>.          //
 | ||||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| 
 | ||||
| #include <QDebug> | ||||
| #include <QUrl> | ||||
| #include <QUrlQuery> | ||||
| #include <QNetworkReply> | ||||
| #include <QJsonDocument> | ||||
| #include <QJsonObject> | ||||
| #include <QRegularExpression> | ||||
| 
 | ||||
| #include "util/iot/visa.h" | ||||
| #include "util/visa.h" | ||||
| #include "util/simpleserializer.h" | ||||
| 
 | ||||
| VISADevice::VISADevice(const QHash<QString, QVariant> settings, const QString &deviceId, | ||||
|                        const QStringList &controls, const QStringList &sensors, | ||||
|                        DeviceDiscoverer::DeviceInfo *info) : | ||||
|     Device(info), | ||||
|     m_deviceId(deviceId), | ||||
|     m_session(0), | ||||
|     m_controls(controls), | ||||
|     m_sensors(sensors) | ||||
| { | ||||
|     m_visa.openDefault(); | ||||
| 
 | ||||
|     QHashIterator<QString, QVariant> itr(settings); | ||||
|     while (itr.hasNext()) | ||||
|     { | ||||
|         itr.next(); | ||||
|         QString key = itr.key(); | ||||
|         QVariant value = itr.value(); | ||||
| 
 | ||||
|         if ((key == "deviceId") || (key == "controlIds") || (key == "sensorIds")) | ||||
|         { | ||||
|             // Nothing to do here
 | ||||
|         } | ||||
|         else if (key == "logIO") | ||||
|         { | ||||
|             m_visa.setDebugIO(value.toBool()); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             qDebug() << "VISADevice::VISADevice: Unsupported setting key: " << key << " value: " << value; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     open(); | ||||
| } | ||||
| 
 | ||||
| VISADevice::~VISADevice() | ||||
| { | ||||
|     m_visa.close(m_session); | ||||
|     m_visa.closeDefault(); | ||||
| } | ||||
| 
 | ||||
| bool VISADevice::open() | ||||
| { | ||||
|     if (!m_session) { | ||||
|         m_session = m_visa.open(m_deviceId); | ||||
|     } | ||||
|     if (!m_session) { | ||||
|         emit deviceUnavailable(); | ||||
|     } | ||||
|     return m_session != 0; | ||||
| } | ||||
| 
 | ||||
| bool VISADevice::convertToBool(const QString &string, bool &ok) | ||||
| { | ||||
|     QString lower = string.trimmed().toLower(); | ||||
|     if ((lower == "0") || (lower == "false") || (lower == "off")) | ||||
|     { | ||||
|         ok = true; | ||||
|         return false; | ||||
|     } | ||||
|     else if ((lower == "1") || (lower == "true") || (lower == "on")) | ||||
|     { | ||||
|         ok = true; | ||||
|         return true; | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         ok = false; | ||||
|         return false; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void VISADevice::convert(QHash<QString, QVariant> &status, const QString &id, DeviceDiscoverer::Type type, const QString &state) | ||||
| { | ||||
|     if (type == DeviceDiscoverer::BOOL) | ||||
|     { | ||||
|         bool ok; | ||||
|         bool value = convertToBool(state, ok); | ||||
|         if (ok) { | ||||
|             status.insert(id, value); | ||||
|         } else { | ||||
|             status.insert(id, "error"); | ||||
|         } | ||||
|     } | ||||
|     else if (type == DeviceDiscoverer::INT) | ||||
|     { | ||||
|         bool ok; | ||||
|         int value = state.toInt(&ok); | ||||
|         if (ok) { | ||||
|             status.insert(id, value); | ||||
|         } else { | ||||
|             status.insert(id, "error"); | ||||
|         } | ||||
|     } | ||||
|     else if (type == DeviceDiscoverer::FLOAT) | ||||
|     { | ||||
|         bool ok; | ||||
|         float value = state.toFloat(&ok); | ||||
|         if (ok) { | ||||
|             status.insert(id, value); | ||||
|         } else { | ||||
|             status.insert(id, "error"); | ||||
|         } | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         status.insert(id, state); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void VISADevice::getState() | ||||
| { | ||||
|     if (open()) | ||||
|     { | ||||
|         QHash<QString, QVariant> status; | ||||
| 
 | ||||
|         for (auto c : m_info.m_controls) | ||||
|         { | ||||
|             if (m_controls.contains(c->m_id)) | ||||
|             { | ||||
|                 VISAControl *control = reinterpret_cast<VISAControl *>(c); | ||||
|                 QString cmds = control->m_getState.trimmed(); | ||||
|                 if (!cmds.isEmpty()) | ||||
|                 { | ||||
|                     bool error; | ||||
|                     QStringList results = m_visa.processCommands(m_session, cmds, &error); | ||||
|                     if (!error && (results.size() > 0)) | ||||
|                     { | ||||
|                         // Take last returned value as the state
 | ||||
|                         QString state = results[results.size()-1].trimmed(); | ||||
|                         convert(status, control->m_id, control->m_type, state); | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         status.insert(control->m_id, "error"); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         for (auto s : m_info.m_sensors) | ||||
|         { | ||||
|             if (m_sensors.contains(s->m_id)) | ||||
|             { | ||||
|                 VISASensor *sensor = reinterpret_cast<VISASensor *>(s); | ||||
|                 QString cmds = sensor->m_getState.trimmed(); | ||||
|                 if (!cmds.isEmpty()) | ||||
|                 { | ||||
|                     bool error; | ||||
|                     QStringList results = m_visa.processCommands(m_session, cmds, &error); | ||||
|                     if (!error && (results.size() > 0)) | ||||
|                     { | ||||
|                         // Take last returned value as the state
 | ||||
|                         QString state = results[results.size()-1].trimmed(); | ||||
|                         convert(status, sensor->m_id, sensor->m_type, state); | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         status.insert(sensor->m_id, "error"); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         emit deviceUpdated(status); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void VISADevice::setState(const QString &controlId, bool state) | ||||
| { | ||||
|     if (open()) | ||||
|     { | ||||
|         for (auto c : m_info.m_controls) | ||||
|         { | ||||
|             VISAControl *control = reinterpret_cast<VISAControl *>(c); | ||||
|             if (control->m_id == controlId) | ||||
|             { | ||||
|                 QString commands = QString::asprintf(control->m_setState.toUtf8(), (int)state); | ||||
|                 bool error; | ||||
|                 m_visa.processCommands(m_session, commands, &error); | ||||
|                 if (error) { | ||||
|                     qDebug() << "VISADevice::setState: Failed to set state of " << controlId; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void VISADevice::setState(const QString &controlId, int state) | ||||
| { | ||||
|     if (open()) | ||||
|     { | ||||
|         for (auto c : m_info.m_controls) | ||||
|         { | ||||
|             VISAControl *control = reinterpret_cast<VISAControl *>(c); | ||||
|             if (control->m_id == controlId) | ||||
|             { | ||||
|                 QString commands = QString::asprintf(control->m_setState.toUtf8(), state); | ||||
|                 bool error; | ||||
|                 m_visa.processCommands(m_session, commands, &error); | ||||
|                 if (error) { | ||||
|                     qDebug() << "VISADevice::setState: Failed to set state of " << controlId; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void VISADevice::setState(const QString &controlId, float state) | ||||
| { | ||||
|     if (open()) | ||||
|     { | ||||
|         for (auto c : m_info.m_controls) | ||||
|         { | ||||
|             VISAControl *control = reinterpret_cast<VISAControl *>(c); | ||||
|             if (control->m_id == controlId) | ||||
|             { | ||||
|                 QString commands = QString::asprintf(control->m_setState.toUtf8(), state); | ||||
|                 bool error; | ||||
|                 m_visa.processCommands(m_session, commands, &error); | ||||
|                 if (error) { | ||||
|                     qDebug() << "VISADevice::setState: Failed to set state of " << controlId; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void VISADevice::setState(const QString &controlId, const QString &state) | ||||
| { | ||||
|     if (open()) | ||||
|     { | ||||
|         for (auto c : m_info.m_controls) | ||||
|         { | ||||
|             VISAControl *control = reinterpret_cast<VISAControl *>(c); | ||||
|             if (control->m_id == controlId) | ||||
|             { | ||||
|                 QString commands = QString::asprintf(control->m_setState.toUtf8(), state); | ||||
|                 bool error; | ||||
|                 m_visa.processCommands(m_session, commands, &error); | ||||
|                 if (error) { | ||||
|                     qDebug() << "VISADevice::setState: Failed to set state of " << controlId; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| VISADeviceDiscoverer::VISADeviceDiscoverer(const QString& resourceFilter) : | ||||
|     m_resourceFilter(resourceFilter) | ||||
| { | ||||
|     m_session = m_visa.openDefault(); | ||||
| } | ||||
| 
 | ||||
| VISADeviceDiscoverer::~VISADeviceDiscoverer() | ||||
| { | ||||
|     m_visa.closeDefault(); | ||||
| } | ||||
| 
 | ||||
| void VISADeviceDiscoverer::getDevices() | ||||
| { | ||||
|     QRegularExpression *filterP = nullptr; | ||||
|     QRegularExpression filter(m_resourceFilter); | ||||
|     if (!m_resourceFilter.trimmed().isEmpty()) { | ||||
|         filterP = &filter; | ||||
|     } | ||||
| 
 | ||||
|     // Get list of VISA instruments
 | ||||
|     QList<VISA::Instrument> instruments = m_visa.instruments(filterP); | ||||
| 
 | ||||
|     // Convert to list of devices
 | ||||
|     QList<DeviceInfo> devices; | ||||
|     for (auto const &instrument : instruments) | ||||
|     { | ||||
|         DeviceInfo info; | ||||
|         info.m_name = instrument.m_model; | ||||
|         info.m_id = instrument.m_resource; | ||||
|         info.m_model = instrument.m_model; | ||||
| 
 | ||||
|         if ((info.m_name == "DP832") || (info.m_name == "DP832A")) | ||||
|         { | ||||
|             for (int i = 1; i <= 3; i++) | ||||
|             { | ||||
|                 VISADevice::VISAControl *output = new VISADevice::VISAControl(); | ||||
|                 output->m_name = QString("CH%1").arg(i); | ||||
|                 output->m_id = QString("control.ch%1").arg(i); | ||||
|                 output->m_type = BOOL; | ||||
|                 output->m_getState = QString(":OUTPUT? CH%1").arg(i); | ||||
|                 output->m_setState = QString(":OUTPUT CH%1,%d").arg(i); | ||||
|                 info.m_controls.append(output); | ||||
| 
 | ||||
|                 VISADevice::VISAControl *setVoltage = new VISADevice::VISAControl(); | ||||
|                 setVoltage->m_name = QString("V%1").arg(i); | ||||
|                 setVoltage->m_id = QString("control.voltage%1").arg(i); | ||||
|                 setVoltage->m_type = FLOAT; | ||||
|                 setVoltage->m_min = 0.0f; | ||||
|                 setVoltage->m_max = i == 3 ? 5.0f : 30.0f; | ||||
|                 setVoltage->m_scale = 1.0f; | ||||
|                 setVoltage->m_precision = 3; | ||||
|                 setVoltage->m_widgetType = SPIN_BOX; | ||||
|                 setVoltage->m_units = "V"; | ||||
|                 setVoltage->m_getState = QString(":SOURCE%1:VOLTage?").arg(i); | ||||
|                 setVoltage->m_setState = QString(":SOURCE%1:VOLTage %f").arg(i); | ||||
|                 info.m_controls.append(setVoltage); | ||||
| 
 | ||||
|                 VISADevice::VISAControl *setCurrent = new VISADevice::VISAControl(); | ||||
|                 setCurrent->m_name = QString("i%1").arg(i); | ||||
|                 setCurrent->m_id = QString("control.current%1").arg(i); | ||||
|                 setCurrent->m_type = FLOAT; | ||||
|                 setCurrent->m_min = 0.0f; | ||||
|                 setCurrent->m_max = 3.0f; | ||||
|                 setCurrent->m_scale = 1.0f; | ||||
|                 setCurrent->m_precision = 3; | ||||
|                 setCurrent->m_widgetType = SPIN_BOX; | ||||
|                 setCurrent->m_units = "A"; | ||||
|                 setCurrent->m_getState = QString(":SOURCE%1:CURRent?").arg(i); | ||||
|                 setCurrent->m_setState = QString(":SOURCE%1:CURRent %f").arg(i); | ||||
|                 info.m_controls.append(setCurrent); | ||||
| 
 | ||||
|                 VISADevice::VISASensor *voltage = new VISADevice::VISASensor(); | ||||
|                 voltage->m_name = QString("V%1").arg(i); | ||||
|                 voltage->m_id = QString("sensor.voltage%1").arg(i); | ||||
|                 voltage->m_type = FLOAT; | ||||
|                 voltage->m_units = "V"; | ||||
|                 voltage->m_getState = QString(":MEASure:VOLTage? CH%1").arg(i); | ||||
|                 info.m_sensors.append(voltage); | ||||
| 
 | ||||
|                 VISADevice::VISASensor *current = new VISADevice::VISASensor(); | ||||
|                 current->m_name = QString("i%1").arg(i); | ||||
|                 current->m_id = QString("sensor.current%1").arg(i); | ||||
|                 current->m_type = FLOAT; | ||||
|                 current->m_units = "A"; | ||||
|                 current->m_getState = QString(":MEASure:CURRent? CH%1").arg(i); | ||||
|                 info.m_sensors.append(current); | ||||
| 
 | ||||
|                 VISADevice::VISASensor *power = new VISADevice::VISASensor(); | ||||
|                 power->m_name = QString("P%1").arg(i); | ||||
|                 power->m_id = QString("sensor.power%1").arg(i); | ||||
|                 power->m_type = FLOAT; | ||||
|                 power->m_units = "W"; | ||||
|                 power->m_getState = QString(":MEASure:POWEr? CH%1").arg(i); | ||||
|                 info.m_sensors.append(power); | ||||
|             } | ||||
|         } | ||||
|         else if (info.m_name == "SSA3032X") | ||||
|         { | ||||
|             VISADevice::VISAControl *frequency = new VISADevice::VISAControl(); | ||||
|             frequency->m_name = "Frequency"; | ||||
|             frequency->m_id = "control.frequency"; | ||||
|             frequency->m_type = FLOAT; | ||||
|             frequency->m_min = 0.0f; | ||||
|             frequency->m_max = 3.2e3f; | ||||
|             frequency->m_scale = 1e6f; | ||||
|             frequency->m_precision = 6; | ||||
|             frequency->m_widgetType = SPIN_BOX; | ||||
|             frequency->m_units = "MHz"; | ||||
|             frequency->m_getState = ":FREQuency:CENTer?"; | ||||
|             frequency->m_setState = ":FREQuency:CENTer %f"; | ||||
|             info.m_controls.append(frequency); | ||||
| 
 | ||||
|             VISADevice::VISAControl *span = new VISADevice::VISAControl(); | ||||
|             span->m_name = "Span"; | ||||
|             span->m_id = "control.span"; | ||||
|             span->m_type = FLOAT; | ||||
|             span->m_min = 0.0f; | ||||
|             span->m_max = 3.2e3f; | ||||
|             span->m_scale = 1e6; | ||||
|             span->m_precision = 3; | ||||
|             span->m_widgetType = SPIN_BOX; | ||||
|             span->m_units = "MHz"; | ||||
|             span->m_getState = ":FREQuency:SPAN?"; | ||||
|             span->m_setState = ":FREQuency:SPAN %f"; | ||||
|             info.m_controls.append(span); | ||||
| 
 | ||||
|             VISADevice::VISAControl *markerX = new VISADevice::VISAControl(); | ||||
|             markerX->m_name = "Marker X"; | ||||
|             markerX->m_id = "control.markerx"; | ||||
|             markerX->m_type = FLOAT; | ||||
|             markerX->m_min = 0.0f; | ||||
|             markerX->m_max = 3.2e3f; | ||||
|             markerX->m_scale = 1e6; | ||||
|             markerX->m_precision = 6; | ||||
|             markerX->m_widgetType = SPIN_BOX; | ||||
|             markerX->m_units = "MHz"; | ||||
|             markerX->m_getState = ":CALCulate:MARKer1:X?"; | ||||
|             markerX->m_setState = ":CALCulate:MARKer1:X %f"; | ||||
|             info.m_controls.append(markerX); | ||||
| 
 | ||||
|             VISADevice::VISASensor *markerY = new VISADevice::VISASensor(); | ||||
|             markerY->m_name = "Marker Y"; | ||||
|             markerY->m_id = "sensor.markery"; | ||||
|             markerY->m_type = FLOAT; | ||||
|             markerY->m_units = "dBm"; | ||||
|             markerY->m_getState = ":CALCulate:MARKer1:Y?"; | ||||
|             info.m_sensors.append(markerY); | ||||
|         } | ||||
| 
 | ||||
|         devices.append(info); | ||||
|     } | ||||
|     emit deviceList(devices); | ||||
| } | ||||
| 
 | ||||
| DeviceDiscoverer::ControlInfo *VISADevice::VISAControl::clone() const | ||||
| { | ||||
|     return new VISAControl(*this); | ||||
| } | ||||
| 
 | ||||
| QByteArray VISADevice::VISAControl::serialize() const | ||||
| { | ||||
|     SimpleSerializer s(1); | ||||
| 
 | ||||
|     s.writeBlob(1, ControlInfo::serialize()); | ||||
|     s.writeString(2, m_getState); | ||||
|     s.writeString(3, m_setState); | ||||
| 
 | ||||
|     return s.final(); | ||||
| } | ||||
| 
 | ||||
| bool VISADevice::VISAControl::deserialize(const QByteArray& data) | ||||
| { | ||||
|     SimpleDeserializer d(data); | ||||
| 
 | ||||
|     if (!d.isValid()) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     if (d.getVersion() == 1) | ||||
|     { | ||||
|         QByteArray blob; | ||||
| 
 | ||||
|         d.readBlob(1, &blob); | ||||
|         ControlInfo::deserialize(blob); | ||||
|         d.readString(2, &m_getState); | ||||
|         d.readString(3, &m_setState); | ||||
|         return true; | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         return false; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| DeviceDiscoverer::SensorInfo *VISADevice::VISASensor::clone() const | ||||
| { | ||||
|     return new VISASensor(*this); | ||||
| } | ||||
| 
 | ||||
| QByteArray VISADevice::VISASensor::serialize() const | ||||
| { | ||||
|     SimpleSerializer s(1); | ||||
| 
 | ||||
|     s.writeBlob(1, SensorInfo::serialize()); | ||||
|     s.writeString(2, m_getState); | ||||
|     return s.final(); | ||||
| } | ||||
| 
 | ||||
| bool VISADevice::VISASensor::deserialize(const QByteArray& data) | ||||
| { | ||||
|     SimpleDeserializer d(data); | ||||
| 
 | ||||
|     if (!d.isValid()) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     if (d.getVersion() == 1) | ||||
|     { | ||||
|         QByteArray blob; | ||||
| 
 | ||||
|         d.readBlob(1, &blob); | ||||
|         SensorInfo::deserialize(blob); | ||||
|         d.readString(2, &m_getState); | ||||
|         return true; | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         return false; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										84
									
								
								sdrbase/util/iot/visa.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								sdrbase/util/iot/visa.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,84 @@ | ||||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| // Copyright (C) 2022 Jon Beniston, M7RCE                                        //
 | ||||
| //                                                                               //
 | ||||
| // This program 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 as version 3 of the License, or                  //
 | ||||
| // (at your option) any later version.                                           //
 | ||||
| //                                                                               //
 | ||||
| // This program 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 V3 for more details.                               //
 | ||||
| //                                                                               //
 | ||||
| // You should have received a copy of the GNU General Public License             //
 | ||||
| // along with this program. If not, see <http://www.gnu.org/licenses/>.          //
 | ||||
| ///////////////////////////////////////////////////////////////////////////////////
 | ||||
| 
 | ||||
| #ifndef INCLUDE_IOT_VISA_H | ||||
| #define INCLUDE_IOT_VISA_H | ||||
| 
 | ||||
| #include "util/iot/device.h" | ||||
| #include "util/visa.h" | ||||
| 
 | ||||
| class SDRBASE_API VISADevice : public Device { | ||||
|     Q_OBJECT | ||||
| public: | ||||
| 
 | ||||
|     struct SDRBASE_API VISAControl : public DeviceDiscoverer::ControlInfo { | ||||
|         QString m_getState; | ||||
|         QString m_setState; | ||||
| 
 | ||||
|         virtual DeviceDiscoverer::ControlInfo *clone() const override; | ||||
|         virtual QByteArray serialize() const override; | ||||
|         virtual bool deserialize(const QByteArray& data) override; | ||||
|     }; | ||||
| 
 | ||||
|     struct SDRBASE_API VISASensor : public DeviceDiscoverer::SensorInfo { | ||||
|         QString m_getState; | ||||
| 
 | ||||
|         virtual DeviceDiscoverer::SensorInfo *clone() const override; | ||||
|         virtual QByteArray serialize() const override; | ||||
|         virtual bool deserialize(const QByteArray& data) override; | ||||
|     }; | ||||
| 
 | ||||
|     VISADevice(const QHash<QString, QVariant> settings, const QString &deviceId, | ||||
|                const QStringList &controls, const QStringList &sensors, | ||||
|                DeviceDiscoverer::DeviceInfo *info=nullptr); | ||||
|     ~VISADevice(); | ||||
|     virtual void getState() override; | ||||
|     virtual void setState(const QString &controlId, bool state) override; | ||||
|     virtual void setState(const QString &controlId, int state) override; | ||||
|     virtual void setState(const QString &controlId, float state) override; | ||||
|     virtual void setState(const QString &controlId, const QString &state) override; | ||||
|     virtual QString getProtocol() const override { return "VISA"; } | ||||
|     virtual QString getDeviceId() const override { return m_deviceId; } | ||||
| 
 | ||||
| private: | ||||
|     QString m_deviceId;             // VISA resource (E.g. USB0::0xcafe...)
 | ||||
|     VISA m_visa; | ||||
|     ViSession m_session; | ||||
|     QStringList m_controls;         // Control IDs for getState
 | ||||
|     QStringList m_sensors;          // Sensor IDs for getState
 | ||||
|     bool m_debugIO; | ||||
| 
 | ||||
|     bool open(); | ||||
|     bool convertToBool(const QString &string, bool &ok); | ||||
|     void convert(QHash<QString, QVariant> &status, const QString &id, DeviceDiscoverer::Type type, const QString &state); | ||||
| }; | ||||
| 
 | ||||
| class SDRBASE_API VISADeviceDiscoverer : public DeviceDiscoverer { | ||||
|     Q_OBJECT | ||||
| public: | ||||
| 
 | ||||
|     VISADeviceDiscoverer(const QString &resourceFilter = ""); | ||||
|     ~VISADeviceDiscoverer(); | ||||
|     virtual void getDevices() override; | ||||
| 
 | ||||
| private: | ||||
|     VISA m_visa; | ||||
|     ViSession m_session; | ||||
|     QString m_resourceFilter; | ||||
| }; | ||||
| 
 | ||||
| #endif /* INCLUDE_IOT_VISA_H */ | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user