mirror of
				https://github.com/f4exb/sdrangel.git
				synced 2025-11-04 05:30:32 -05: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