Add basic structure for USB-RLY82

master
Simon Stürz 2022-03-09 08:21:05 +01:00
parent 750afb77f9
commit f65bf428d9
12 changed files with 1293 additions and 0 deletions

View File

@ -72,6 +72,7 @@ PLUGIN_DIRS = \
udpcommander \
unifi \
usbrelay \
usbrly82 \
wakeonlan \
wemo \
ws2812fx \

23
usbrly82/README.md Normal file
View File

@ -0,0 +1,23 @@
# USB-RLY82 - 2 channel USB relay
--------------------------------
This plugin allows you to control USB relays and receive digital and analog input values from the controller.
In order to set up the the controller in nymea, you only need to plug in the device. All digital and analog channels will be set up automatically.
Each relay board has a unique serial number and therefore it can be clearly identified.
## Supported things
* USB-RLY82
This relay supports:
* 2 Digital outputs (Relays)
* 8 Digital and analog inputs
* the Resolution of the ADC can be configured
## More
Information about the USB relay hardware can be found [here](https://www.robot-electronics.co.uk/usb-rly82.html).
The datasheet can be downloaded from [here](https://www.robot-electronics.co.uk/files/usb-rly82.pdf)

View File

@ -0,0 +1,254 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2022, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of nymea.
* This project including source code and documentation is protected by
* copyright law, and remains the property of nymea GmbH. All rights, including
* reproduction, publication, editing and translation, are reserved. The use of
* this project is subject to the terms of a license agreement to be concluded
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
* under https://nymea.io/license
*
* GNU Lesser General Public License Usage
* Alternatively, this project may be redistributed and/or modified under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; version 3. This project is distributed in the hope that
* it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this project. If not, see <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "plugininfo.h"
#include "integrationpluginusbrly82.h"
IntegrationPluginUsbRly82::IntegrationPluginUsbRly82()
{
}
void IntegrationPluginUsbRly82::init()
{
m_monitor = new SerialPortMonitor(this);
}
void IntegrationPluginUsbRly82::discoverThings(ThingDiscoveryInfo *info)
{
info->finish(Thing::ThingErrorNoError);
}
void IntegrationPluginUsbRly82::startMonitoringAutoThings()
{
// Start seaching for devices which can be discovered and added automatically
connect(m_monitor, &SerialPortMonitor::serialPortAdded, this, &IntegrationPluginUsbRly82::onSerialPortAdded);
connect(m_monitor, &SerialPortMonitor::serialPortRemoved, this, &IntegrationPluginUsbRly82::onSerialPortRemoved);
// Check the initial list of
foreach (const SerialPortMonitor::SerialPortInfo &serialPortInfo, m_monitor->serialPortInfos()) {
onSerialPortAdded(serialPortInfo);
}
}
void IntegrationPluginUsbRly82::setupThing(ThingSetupInfo *info)
{
Thing *thing = info->thing();
qCDebug(dcUsbRly82()) << "Setup thing" << thing;
// Relay
if (info->thing()->thingClassId() == usbRelayThingClassId) {
// Search for the serial port with the given serialnumber
foreach (const SerialPortMonitor::SerialPortInfo &serialPortInfo, m_monitor->serialPortInfos()) {
if (serialPortInfo.serialNumber == thing->paramValue(usbRelayThingSerialNumberParamTypeId).toString()) {
qCDebug(dcUsbRly82()) << "Found serial port for" << thing << serialPortInfo;
UsbRly82 *relay = new UsbRly82(this);
connect(relay, &UsbRly82::availableChanged, thing, [=](bool available){
qCDebug(dcUsbRly82()) << thing << "available changed" << available;
thing->setStateValue("connected", available);
});
connect(relay, &UsbRly82::powerRelay1Changed, thing, [=](bool power){
qCDebug(dcUsbRly82()) << thing << "relay 1 power changed" << power;
thing->setStateValue(usbRelayPowerRelay1StateTypeId, power);
});
connect(relay, &UsbRly82::powerRelay2Changed, thing, [=](bool power){
qCDebug(dcUsbRly82()) << thing << "relay 2 power changed" << power;
thing->setStateValue(usbRelayPowerRelay1StateTypeId, power);
});
connect(relay, &UsbRly82::digitalInputsChanged, thing, [=](){
thing->setStateValue(usbRelayPowerRelay1StateTypeId, UsbRly82::checkBit());
});
if (!relay->connectRelay(serialPortInfo.systemLocation)) {
qCWarning(dcUsbRly82()) << "Setup failed. Could not connect to relay" << thing;
info->finish(Thing::ThingErrorHardwareFailure);
relay->deleteLater();
return;
}
m_relays.insert(thing, relay);
info->finish(Thing::ThingErrorNoError);
return;
}
}
// Does not seem to be available
info->finish(Thing::ThingErrorHardwareNotAvailable);
return;
}
info->finish(Thing::ThingErrorSetupFailed);
}
void IntegrationPluginUsbRly82::postSetupThing(Thing *thing)
{
qCDebug(dcUsbRly82()) << "Post setup thing" << thing;
// if (thing->thingClassId() == usbRelayConnectorThingClassId) {
// // Initialize the states
// UsbRelay *relay = m_relays.key(thing);
// if (!relay) {
// qCWarning(dcUsbRly82()) << "Could not find relay in post setup.";
// return;
// }
// thing->setStateValue(usbRelayConnectorConnectedStateTypeId, relay->connected());
// // Check if we have to create child devices (relays)
// if (myThings().filterByParentId(thing->id()).isEmpty()) {
// ThingDescriptors descriptors;
// for (int i = 0; i < relay->relayCount(); i++) {
// int relayNumber = i + 1;
// ThingDescriptor descriptor(usbRelayThingClassId, QString("Relay %1").arg(relayNumber), QString(), thing->id());
// ParamList params;
// params.append(Param(usbRelayThingRelayNumberParamTypeId, relayNumber));
// descriptor.setParams(params);
// descriptors.append(descriptor);
// }
// emit autoThingsAppeared(descriptors);
// }
// } else if (thing->thingClassId() == usbRelayThingClassId) {
// UsbRelay *relay = getRelayForDevice(thing);
// if (!relay) return;
// // Set the current states
// int relayNumber = thing->paramValue(usbRelayThingRelayNumberParamTypeId).toInt();
// thing->setStateValue(usbRelayConnectedStateTypeId, relay->connected());
// thing->setStateValue(usbRelayPowerStateTypeId, relay->relayPower(relayNumber));
// }
}
void IntegrationPluginUsbRly82::thingRemoved(Thing *thing)
{
qCDebug(dcUsbRly82()) << "Remove thing" << thing;
if (thing->thingClassId() == usbRelayThingClassId) {
UsbRly82 *relay = m_relays.take(thing);
if (!relay) return;
delete relay;
}
}
void IntegrationPluginUsbRly82::executeAction(ThingActionInfo *info)
{
qCDebug(dcUsbRly82()) << "Executing action for thing" << info->thing() << info->action().actionTypeId().toString() << info->action().params();
if (info->thing()->thingClassId() == usbRelayThingClassId) {
Thing *thing = info->thing();
UsbRly82 *relay = m_relays.value(thing);
if (!relay) {
qCWarning(dcUsbRly82()) << "Could execute action because could not find USB relay for" << thing;
info->finish(Thing::ThingErrorHardwareNotAvailable);
return;
}
if (!relay->available()) {
qCWarning(dcUsbRly82()) << "Relay is not connected";
info->finish(Thing::ThingErrorHardwareNotAvailable);
return;
}
if (info->action().actionTypeId() == usbRelayPowerRelay1ActionTypeId) {
bool power = info->action().paramValue(usbRelayPowerRelay1ActionPowerRelay1ParamTypeId).toBool();
UsbRly82Reply *reply = relay->setRelay1Power(power);
connect(reply, &UsbRly82Reply::finished, info, [=](){
if (reply->error() != UsbRly82Reply::ErrorNoError) {
info->finish(Thing::ThingErrorHardwareFailure);
return;
}
info->finish(Thing::ThingErrorNoError);
});
return;
} else if (info->action().actionTypeId() == usbRelayPowerRelay2ActionTypeId) {
bool power = info->action().paramValue(usbRelayPowerRelay2ActionPowerRelay2ParamTypeId).toBool();
UsbRly82Reply *reply = relay->setRelay2Power(power);
connect(reply, &UsbRly82Reply::finished, info, [=](){
if (reply->error() != UsbRly82Reply::ErrorNoError) {
info->finish(Thing::ThingErrorHardwareFailure);
return;
}
info->finish(Thing::ThingErrorNoError);
});
return;
}
info->finish(Thing::ThingErrorActionTypeNotFound);
}
info->finish(Thing::ThingErrorThingClassNotFound);
}
void IntegrationPluginUsbRly82::onSerialPortAdded(const SerialPortMonitor::SerialPortInfo &serialPortInfo)
{
if (serialPortInfo.vendorId == 0x04d8 && serialPortInfo.productId == 0xffee) {
qCDebug(dcUsbRly82()) << "[+] Added" << serialPortInfo;
Things alreadyAddedThings = myThings().filterByThingClassId(usbRelayThingClassId).filterByParam(usbRelayThingSerialNumberParamTypeId, serialPortInfo.serialNumber);
if (alreadyAddedThings.isEmpty()) {
qCDebug(dcUsbRly82()) << "New" << serialPortInfo.product << serialPortInfo.serialNumber << "showed up. Setting up a new thing for this.";
ThingDescriptor descriptor(usbRelayThingClassId, "USB-RLY82");
ParamList params;
params.append(Param(usbRelayThingSerialNumberParamTypeId, serialPortInfo.serialNumber));
descriptor.setParams(params);
emit autoThingsAppeared(ThingDescriptors() << descriptor);
} else {
Thing *relayThing = alreadyAddedThings.first();
if (relayThing) {
qCDebug(dcUsbRly82()) << "Thing already set up for this controller" << relayThing;
UsbRly82 *relay = m_relays.value(relayThing);
if (relay) {
relay->connectRelay(serialPortInfo.systemLocation);
}
}
}
}
}
void IntegrationPluginUsbRly82::onSerialPortRemoved(const SerialPortMonitor::SerialPortInfo &serialPortInfo)
{
if (serialPortInfo.vendorId == 0x04d8 && serialPortInfo.productId == 0xffee) {
qCDebug(dcUsbRly82()) << "[-] Removed" << serialPortInfo;
}
}

View File

@ -0,0 +1,67 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2022, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of nymea.
* This project including source code and documentation is protected by
* copyright law, and remains the property of nymea GmbH. All rights, including
* reproduction, publication, editing and translation, are reserved. The use of
* this project is subject to the terms of a license agreement to be concluded
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
* under https://nymea.io/license
*
* GNU Lesser General Public License Usage
* Alternatively, this project may be redistributed and/or modified under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; version 3. This project is distributed in the hope that
* it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this project. If not, see <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef INTEGRATIONPLUGINUSBRLY82_H
#define INTEGRATIONPLUGINUSBRLY82_H
#include "integrations/integrationplugin.h"
#include "serialportmonitor.h"
#include "usbrly82.h"
class IntegrationPluginUsbRly82: public IntegrationPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginusbrly82.json")
Q_INTERFACES(IntegrationPlugin)
public:
explicit IntegrationPluginUsbRly82();
void init() override;
void startMonitoringAutoThings() override;
void discoverThings(ThingDiscoveryInfo *info) override;
void setupThing(ThingSetupInfo *info) override;
void postSetupThing(Thing *thing) override;
void thingRemoved(Thing *thing) override;
void executeAction(ThingActionInfo *info) override;
private:
SerialPortMonitor *m_monitor = nullptr;
QHash<Thing *, UsbRly82 *> m_relays;
private slots:
void onSerialPortAdded(const SerialPortMonitor::SerialPortInfo &serialPortInfo);
void onSerialPortRemoved(const SerialPortMonitor::SerialPortInfo &serialPortInfo);
};
#endif // INTEGRATIONPLUGINUSBRLY82_H

View File

@ -0,0 +1,245 @@
{
"name": "UsbRly82",
"displayName": "USB-RLY82",
"id": "33d4ebad-104b-4f24-8089-6edd35ba2c95",
"vendors": [
{
"displayName": "Robot electronics",
"name": "robotElectronics",
"id": "c3b972d1-85e6-436c-ae07-a04cbfae604e",
"thingClasses": [
{
"name": "usbRelay",
"displayName": "USB-RLY82",
"id": "0ae5cd75-d42e-4993-8ce8-ced5ba315688",
"setupMethod": "JustAdd",
"createMethods": ["discovery", "auto"],
"interfaces": [ "gateway" ],
"paramTypes": [
{
"id": "5ac5b15f-34cf-4d42-8b9d-6e9866d8f2ed",
"name": "serialNumber",
"displayName": "Serial number",
"type": "QString",
"defaultValue": ""
}
],
"stateTypes": [
{
"id": "b482b0ea-1901-4437-a309-be43833a1ad5",
"name": "connected",
"displayName": "Connected",
"displayNameEvent": "Connected changed",
"type": "bool",
"defaultValue": false,
"cached": false
},
{
"id": "9935ecba-bb56-4c6c-8be1-60bacc40abb7",
"name": "powerRelay1",
"displayName": "Power relay 1",
"displayNameEvent": "Power relay 1 changed",
"displayNameAction": "Set power relay 1",
"type": "bool",
"ioType": "digitalOutput",
"defaultValue": false,
"writable": true,
"cached": true
},
{
"id": "d4b84a73-44dd-4bb9-a0d0-e45e6d2aaf7d",
"name": "powerRelay2",
"displayName": "Power relay 2",
"displayNameEvent": "Power relay 2 changed",
"displayNameAction": "Set power relay 2",
"type": "bool",
"ioType": "digitalOutput",
"defaultValue": false,
"writable": true,
"cached": true
},
{
"id": "ad60f859-11e0-4a80-86fc-1810a5054fb6",
"name": "digitalInputChannel1",
"displayName": "Channel 1",
"displayNameEvent": "Channel 1 input changed",
"type": "bool",
"ioType": "digitalInput",
"defaultValue": false,
"cached": true
},
{
"id": "5038008d-68c7-4cde-845d-335758a05a15",
"name": "digitalInputChannel2",
"displayName": "Channel 2",
"displayNameEvent": "Channel 2 input changed",
"type": "bool",
"ioType": "digitalInput",
"defaultValue": false,
"cached": true
},
{
"id": "73a8c051-5ce8-4909-a75f-f0e88f1ceb61",
"name": "digitalInputChannel3",
"displayName": "Channel 3",
"displayNameEvent": "Channel 3 input changed",
"type": "bool",
"ioType": "digitalInput",
"defaultValue": false,
"cached": true
},
{
"id": "11a3e692-cccc-435b-a9fd-721ae332c90c",
"name": "digitalInputChannel4",
"displayName": "Channel 4",
"displayNameEvent": "Channel 4 input changed",
"type": "bool",
"ioType": "digitalInput",
"defaultValue": false,
"cached": true
},
{
"id": "932cb171-5ba2-4420-bee3-9016dca6498a",
"name": "digitalInputChannel5",
"displayName": "Channel 5",
"displayNameEvent": "Channel 5 input changed",
"type": "bool",
"ioType": "digitalInput",
"defaultValue": false,
"cached": true
},
{
"id": "e10074d7-68cb-4f74-87ea-f0cdf193d207",
"name": "digitalInputChannel6",
"displayName": "Channel 6",
"displayNameEvent": "Channel 6 input changed",
"type": "bool",
"ioType": "digitalInput",
"defaultValue": false,
"cached": true
},
{
"id": "f37aa320-ecfb-4896-bb31-8109606cc5b3",
"name": "digitalInputChannel7",
"displayName": "Channel 7",
"displayNameEvent": "Channel 7 input changed",
"type": "bool",
"ioType": "digitalInput",
"defaultValue": false,
"cached": true
},
{
"id": "32aa1fbd-c128-4258-bb41-22b5b5e150de",
"name": "digitalInputChannel8",
"displayName": "Channel 8",
"displayNameEvent": "Channel 8 input changed",
"type": "bool",
"ioType": "digitalInput",
"defaultValue": false,
"cached": true
},
{
"id": "0dfda321-7da5-4e4c-880e-5b45098398e8",
"name": "analogInputChannel1",
"displayName": "Analog Channel 1",
"displayNameEvent": "Analog channel 1 value changed",
"ioType": "analogInput",
"type": "double",
"minValue": 0,
"maxValue": 1,
"defaultValue": 0
},
{
"id": "02882e2a-f0a6-4042-b91e-c7618c15399e",
"name": "analogInputChannel2",
"displayName": "Analog Channel 2",
"displayNameEvent": "Analog channel 2 value changed",
"ioType": "analogInput",
"type": "double",
"minValue": 0,
"maxValue": 1,
"defaultValue": 0
},
{
"id": "de9172e5-b1c1-4a7e-aebe-819ef22c2c5a",
"name": "analogInputChannel3",
"displayName": "Analog Channel 3",
"displayNameEvent": "Analog channel 3 value changed",
"ioType": "analogInput",
"type": "double",
"minValue": 0,
"maxValue": 1,
"defaultValue": 0
},
{
"id": "875ffbf1-3f11-4935-bd16-de983585baad",
"name": "analogInputChannel4",
"displayName": "Analog Channel 4",
"displayNameEvent": "Analog channel 4 value changed",
"ioType": "analogInput",
"type": "double",
"minValue": 0,
"maxValue": 1,
"defaultValue": 0
},
{
"id": "fa97d343-544d-4322-aacd-4f9f7c21224b",
"name": "analogInputChannel5",
"displayName": "Analog Channel 5",
"displayNameEvent": "Analog channel 5 value changed",
"ioType": "analogInput",
"type": "double",
"minValue": 0,
"maxValue": 1,
"defaultValue": 0
},
{
"id": "51d50bf9-6d40-4d63-a3a2-a0a7c0b5850d",
"name": "analogInputChannel6",
"displayName": "Analog Channel 6",
"displayNameEvent": "Analog channel 6 value changed",
"ioType": "analogInput",
"type": "double",
"minValue": 0,
"maxValue": 1,
"defaultValue": 0
},
{
"id": "25ecf6a9-2007-4768-93a4-28ec81bac179",
"name": "analogInputChannel7",
"displayName": "Analog Channel 7",
"displayNameEvent": "Analog channel 7 value changed",
"ioType": "analogInput",
"type": "double",
"minValue": 0,
"maxValue": 1,
"defaultValue": 0
},
{
"id": "93198a69-4ab6-4fd5-90ab-7f26a67b3b4a",
"name": "analogInputChannel8",
"displayName": "Analog Channel 8",
"displayNameEvent": "Analog channel 8 value changed",
"ioType": "analogInput",
"type": "double",
"minValue": 0,
"maxValue": 1,
"defaultValue": 0
},
{
"id": "e06c88f4-38d9-42cd-bab9-2a07a9a83ce0",
"name": "version",
"displayName": "Version",
"displayNameEvent": "Version changed",
"type": "bool",
"defaultValue": false
}
]
}
]
}
]
}

13
usbrly82/meta.json Normal file
View File

@ -0,0 +1,13 @@
{
"title": "USB-RLY82",
"tagline": "Control relays connected over USB and read digital and analog inputs.",
"icon": "robotelectronics.png",
"stability": "consumer",
"offline": true,
"technologies": [
"usb"
],
"categories": [
"diy"
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,190 @@
#include "serialportmonitor.h"
#include "extern-plugininfo.h"
#include <QSerialPortInfo>
SerialPortMonitor::SerialPortMonitor(QObject *parent) : QObject(parent)
{
m_udev = udev_new();
if (!m_udev) {
qCWarning(dcUsbRly82()) << "SerialPortMonitor: Could not initialize udev";
return;
}
// Read initially all tty devices
struct udev_enumerate *enumerate = udev_enumerate_new(m_udev);
if (!enumerate) {
qCWarning(dcUsbRly82()) << "SerialPortMonitor: Could not create udev enumerate for initial device reading.";
udev_unref(m_udev);
m_udev = nullptr;
return;
}
// We are only interested in FTDI devices
udev_enumerate_add_match_subsystem(enumerate, "tty");
// udev_enumerate_add_match_property(enumerate, "ID_VENDOR_ID", "04d8");
// udev_enumerate_add_match_property(enumerate, "ID_MODEL_ID", "ffee");
if (udev_enumerate_scan_devices(enumerate) < 0) {
qCWarning(dcUsbRly82()) << "SerialPortMonitor: Failed to scan devices from udev enumerate.";
udev_enumerate_unref(enumerate);
enumerate = nullptr;
udev_unref(m_udev);
m_udev = nullptr;
return;
}
qCDebug(dcUsbRly82()) << "SerialPortMonitor: Load initial list of available serial ports...";
struct udev_list_entry *devices = nullptr;
devices = udev_enumerate_get_list_entry(enumerate);
struct udev_list_entry *dev_list_entry = nullptr;
udev_list_entry_foreach(dev_list_entry, devices) {
struct udev_device *device = nullptr;
const char *path;
path = udev_list_entry_get_name(dev_list_entry);
device = udev_device_new_from_syspath(m_udev, path);
// Print properties
struct udev_list_entry *properties = udev_device_get_properties_list_entry(device);
struct udev_list_entry *property_list_entry = nullptr;
udev_list_entry_foreach(property_list_entry, properties) {
qCDebug(dcUsbRly82()) << "SerialPortMonitor: - Property" << udev_list_entry_get_name(property_list_entry) << udev_list_entry_get_value(property_list_entry);
}
QString vendorIdString = QString::fromLatin1(udev_device_get_property_value(device, "ID_VENDOR_ID"));
QString productIdString = QString::fromLatin1(udev_device_get_property_value(device, "ID_MODEL_ID"));
SerialPortInfo info;
info.systemLocation = QString::fromLatin1(udev_device_get_property_value(device,"DEVNAME"));
info.manufacturer = QString::fromLatin1(udev_device_get_property_value(device,"ID_VENDOR_ENC"));
info.product = QString::fromLatin1(udev_device_get_property_value(device,"ID_MODEL_ENC"));
info.serialNumber = QString::fromLatin1(udev_device_get_property_value(device, "ID_SERIAL_SHORT"));
info.vendorId = static_cast<quint16>(vendorIdString.toUInt(nullptr, 16));
info.productId = static_cast<quint16>(productIdString.toUInt(nullptr, 16));
// Clean up this device since we have all information
udev_device_unref(device);
qCDebug(dcUsbRly82()) << "SerialPortMonitor: [+]" << info;
m_serialPortInfos.insert(info.systemLocation, info);
emit serialPortAdded(info);
}
udev_enumerate_unref(enumerate);
enumerate = nullptr;
// Create udev monitor
m_monitor = udev_monitor_new_from_netlink(m_udev, "udev");
if (!m_monitor) {
qCWarning(dcUsbRly82()) << "SerialPortMonitor: Could not initialize udev monitor.";
udev_unref(m_udev);
m_udev = nullptr;
return;
}
// Set monitor filter to tty subsystem
if (udev_monitor_filter_add_match_subsystem_devtype(m_monitor, "tty", nullptr) < 0) {
qCWarning(dcUsbRly82()) << "SerialPortMonitor: Could not set subsystem device type filter to tty.";
udev_monitor_unref(m_monitor);
m_monitor = nullptr;
udev_unref(m_udev);
m_udev = nullptr;
return;
}
// Enable the monitor
if (udev_monitor_enable_receiving(m_monitor) < 0) {
qCWarning(dcUsbRly82()) << "SerialPortMonitor: Could not enable udev monitor.";
udev_monitor_unref(m_monitor);
m_monitor = nullptr;
udev_unref(m_udev);
m_udev = nullptr;
return;
}
// Create socket notifier for read
int socketDescriptor = udev_monitor_get_fd(m_monitor);
m_notifier = new QSocketNotifier(socketDescriptor, QSocketNotifier::Read, this);
connect(m_notifier, &QSocketNotifier::activated, this, [this, socketDescriptor](int socket){
if (socketDescriptor != socket) {
qCWarning(dcUsbRly82()) << "SerialPortMonitor: socket != socketdescriptor";
return;
}
// Create udev device
udev_device *device = udev_monitor_receive_device(m_monitor);
if (!device) {
qCWarning(dcUsbRly82()) << "SerialPortMonitor: Got socket sotification but could not read device information.";
return;
}
QString actionString = QString::fromUtf8(udev_device_get_action(device));
QString vendorIdString = QString::fromUtf8(udev_device_get_property_value(device, "ID_VENDOR_ID"));
QString productIdString = QString::fromUtf8(udev_device_get_property_value(device, "ID_MODEL_ID"));
SerialPortInfo info;
info.systemLocation = QString::fromUtf8(udev_device_get_property_value(device,"DEVNAME"));
info.manufacturer = QString::fromUtf8(udev_device_get_property_value(device,"ID_VENDOR_ENC"));
info.product = QString::fromUtf8(udev_device_get_property_value(device,"ID_MODEL_ENC"));
info.serialNumber = QString::fromUtf8(udev_device_get_property_value(device, "ID_SERIAL_SHORT"));
info.vendorId = static_cast<quint16>(vendorIdString.toUInt(nullptr, 16));
info.productId = static_cast<quint16>(productIdString.toUInt(nullptr, 16));
// Clean udev device
udev_device_unref(device);
// Make sure we know the action
if (actionString.isEmpty())
return;
if (actionString == "add") {
qCDebug(dcUsbRly82()) << "SerialPortMonitor: [+]" << info;
if (!m_serialPortInfos.contains(info.systemLocation)) {
m_serialPortInfos.insert(info.systemLocation, info);
emit serialPortAdded(info);
}
}
if (actionString == "remove") {
qCDebug(dcUsbRly82()) << "SerialPortMonitor: [-]" << info;
if (m_serialPortInfos.contains(info.systemLocation)) {
m_serialPortInfos.remove(info.systemLocation);
emit serialPortRemoved(info);
}
}
});
m_notifier->setEnabled(true);
}
SerialPortMonitor::~SerialPortMonitor()
{
if (m_notifier)
delete m_notifier;
if (m_monitor)
udev_monitor_unref(m_monitor);
if (m_udev)
udev_unref(m_udev);
}
QList<SerialPortMonitor::SerialPortInfo> SerialPortMonitor::serialPortInfos() const
{
return m_serialPortInfos.values();
}
QDebug operator<<(QDebug dbg, const SerialPortMonitor::SerialPortInfo &serialPortInfo)
{
dbg.nospace().noquote() << "SerialPort(" << QString("%1:%2")
.arg(serialPortInfo.vendorId, 4, 16, QLatin1Char('0'))
.arg(serialPortInfo.productId, 4, 16, QLatin1Char('0'))
<< ", " << serialPortInfo.systemLocation
<< ", " << serialPortInfo.manufacturer
<< ", " << serialPortInfo.product << ") ";
return dbg.maybeSpace().maybeQuote();
}

View File

@ -0,0 +1,45 @@
#ifndef SERIALPORTMONITOR_H
#define SERIALPORTMONITOR_H
#include <QHash>
#include <QObject>
#include <QSerialPortInfo>
#include <QSocketNotifier>
#include <libudev.h>
class SerialPortMonitor : public QObject
{
Q_OBJECT
public:
typedef struct SerialPortInfo {
QString manufacturer;
QString product;
QString serialNumber;
QString systemLocation;
quint16 vendorId;
quint16 productId;
} SerialPortInfo;
explicit SerialPortMonitor(QObject *parent = nullptr);
~SerialPortMonitor();
QList<SerialPortInfo> serialPortInfos() const;
signals:
void serialPortAdded(const SerialPortInfo &serialPortInfo);
void serialPortRemoved(const SerialPortInfo &serialPortInfo);
private:
struct udev *m_udev = nullptr;
struct udev_monitor *m_monitor = nullptr;
QSocketNotifier *m_notifier = nullptr;
QHash<QString, SerialPortInfo> m_serialPortInfos;
};
QDebug operator<< (QDebug dbg, const SerialPortMonitor::SerialPortInfo &serialPortInfo);
#endif // SERIALPORTMONITOR_H

333
usbrly82/usbrly82.cpp Normal file
View File

@ -0,0 +1,333 @@
#include "usbrly82.h"
#include "extern-plugininfo.h"
UsbRly82Reply::Error UsbRly82Reply::error() const
{
return m_error;
}
QByteArray UsbRly82Reply::requestData() const
{
return m_requestData;
}
QByteArray UsbRly82Reply::responseData() const
{
return m_responseData;
}
UsbRly82Reply::UsbRly82Reply(QObject *parent) : QObject(parent)
{
m_timer.setSingleShot(true);
m_timer.setInterval(1000);
connect(&m_timer, &QTimer::timeout, this, [this](){
m_error = ErrorTimeout;
emit finished();
});
}
UsbRly82::UsbRly82(QObject *parent) : QObject(parent)
{
qRegisterMetaType<QSerialPort::SerialPortError>();
m_refreshTimer.setInterval(100);
m_refreshTimer.setSingleShot(false);
connect(&m_refreshTimer, &QTimer::timeout, this, &UsbRly82::poll);
}
bool UsbRly82::available() const
{
return m_available;
}
bool UsbRly82::powerRelay1() const
{
return m_powerRelay1;
}
UsbRly82Reply *UsbRly82::setRelay1Power(bool power)
{
UsbRly82Reply *reply;
if (power) {
reply = createReply(QByteArray::fromHex("65"), false);
} else {
reply = createReply(QByteArray::fromHex("6F"), false);
}
sendNextRequest();
return reply;
}
bool UsbRly82::powerRelay2() const
{
return m_powerRelay2;
}
UsbRly82Reply *UsbRly82::setRelay2Power(bool power)
{
UsbRly82Reply *reply;
if (power) {
reply = createReply(QByteArray::fromHex("66"), false);
} else {
reply = createReply(QByteArray::fromHex("70"), false);
}
reply->m_expectsResponse = false;
sendNextRequest();
return reply;
}
quint8 UsbRly82::digitalInputs() const
{
return m_digitalInputs;
}
bool UsbRly82::connectRelay(const QString &serialPort)
{
qCDebug(dcUsbRly82()) << "Connecting to" << serialPort;
if (m_serialPort) {
m_serialPort->close();
delete m_serialPort;
m_serialPort = nullptr;
}
m_available = false;
m_serialPort = new QSerialPort(serialPort, this);
m_serialPort->setBaudRate(19200);
m_serialPort->setStopBits(QSerialPort::OneStop);
m_serialPort->setParity(QSerialPort::NoParity);
if (!m_serialPort->open(QIODevice::ReadWrite)) {
qCWarning(dcUsbRly82()) << "Could not open serial port" << serialPort << m_serialPort->errorString();
m_serialPort->deleteLater();
m_serialPort = nullptr;
emit availableChanged(m_available);
return false;
}
connect(m_serialPort, &QSerialPort::readyRead, this, &UsbRly82::onReadyRead);
connect(m_serialPort, SIGNAL(error(QSerialPort::SerialPortError)), this, SLOT(onError(QSerialPort::SerialPortError)), Qt::QueuedConnection);
// Get serial number
UsbRly82Reply *reply = getSerialNumber();
connect(reply, &UsbRly82Reply::finished, this, [=](){
if (reply->error() != UsbRly82Reply::ErrorNoError) {
qCWarning(dcUsbRly82()) << "Reading serial number finished with error" << reply->error();
return;
}
m_serialNumber = QString::fromUtf8(reply->responseData());
qCDebug(dcUsbRly82()) << "Get serial number finished successfully." << m_serialNumber;
// Get software version
UsbRly82Reply *reply = getSoftwareVersion();
connect(reply, &UsbRly82Reply::finished, this, [=](){
if (reply->error() != UsbRly82Reply::ErrorNoError) {
qCWarning(dcUsbRly82()) << "Reading software version finished with error" << reply->error();
return;
}
m_softwareVersion = QString::fromUtf8(reply->responseData().toHex());
qCDebug(dcUsbRly82()) << "Get software version finished successfully." << m_softwareVersion;
UsbRly82Reply *reply = getRelayStates();
connect(reply, &UsbRly82Reply::finished, this, [=](){
if (reply->error() != UsbRly82Reply::ErrorNoError) {
qCWarning(dcUsbRly82()) << "Reading relay states finished with error" << reply->error();
return;
}
qCDebug(dcUsbRly82()) << "Reading relay states finished successfully." << reply->responseData().toHex();
bool power = checkBit(reply->responseData().at(0), 0);
if (m_powerRelay1 != power) {
m_powerRelay1 = power;
emit powerRelay1Changed(m_powerRelay1);
}
power = checkBit(reply->responseData().at(0), 1);
if (m_powerRelay2 != power) {
m_powerRelay2 = power;
emit powerRelay2Changed(m_powerRelay2);
}
qCDebug(dcUsbRly82()) << "Relay 1:" << m_powerRelay1;
qCDebug(dcUsbRly82()) << "Relay 2:" << m_powerRelay2;
m_available = true;
emit availableChanged(m_available);
m_refreshTimer.start();
});
});
});
return true;
}
void UsbRly82::disconnectRelay()
{
if (m_serialPort) {
qCDebug(dcUsbRly82()) << "Disconnecting from" << m_serialPort->portName();
m_serialPort->close();
delete m_serialPort;
m_serialPort = nullptr;
}
m_refreshTimer.stop();
m_available = false;
emit availableChanged(m_available);
}
UsbRly82Reply *UsbRly82::getSerialNumber()
{
UsbRly82Reply *reply = createReply(QByteArray::fromHex("38"));
sendNextRequest();
return reply;
}
UsbRly82Reply *UsbRly82::getSoftwareVersion()
{
UsbRly82Reply *reply = createReply(QByteArray::fromHex("5A"));
sendNextRequest();
return reply;
}
UsbRly82Reply *UsbRly82::getRelayStates()
{
UsbRly82Reply *reply = createReply(QByteArray::fromHex("5B"));
sendNextRequest();
return reply;
}
UsbRly82Reply *UsbRly82::getDigitalInputs()
{
UsbRly82Reply *reply = createReply(QByteArray::fromHex("5E"));
sendNextRequest();
return reply;
}
UsbRly82Reply *UsbRly82::getAdcValues()
{
UsbRly82Reply *reply = createReply(QByteArray::fromHex("80"));
sendNextRequest();
return reply;
}
UsbRly82Reply *UsbRly82::getAdcReference()
{
UsbRly82Reply *reply = createReply(QByteArray::fromHex("82"));
sendNextRequest();
return reply;
}
UsbRly82Reply *UsbRly82::createReply(const QByteArray &requestData, bool expectsResponse)
{
UsbRly82Reply *reply = new UsbRly82Reply(this);
reply->m_expectsResponse = expectsResponse;
reply->m_requestData = requestData;
connect(reply, &UsbRly82Reply::finished, this, [=](){
if (m_currentReply == reply) {
m_currentReply = nullptr;
sendNextRequest();
}
reply->deleteLater();
});
if (!expectsResponse) {
m_replyQueue.enqueue(reply);
} else {
// Priorize requests without response (like switching the relay)
m_replyQueue.prepend(reply);
}
return reply;
}
void UsbRly82::sendNextRequest()
{
if (m_currentReply)
return;
if (m_replyQueue.isEmpty())
return;
m_currentReply = m_replyQueue.dequeue();
qCDebug(dcUsbRly82()) << "-->" << m_currentReply->requestData().toHex();
m_serialPort->write(m_currentReply->requestData());
if (m_currentReply->m_expectsResponse) {
m_currentReply->m_timer.start(1000);
} else {
// Finish the reply on next event loop
QTimer::singleShot(0, m_currentReply, &UsbRly82Reply::finished);
}
}
bool UsbRly82::checkBit(quint8 byte, uint bitNumber)
{
return ((byte >> bitNumber) & 0x01) == 1;
}
void UsbRly82::onReadyRead()
{
QByteArray data = m_serialPort->readAll();
qCDebug(dcUsbRly82()) << "<--" << data.toHex();
if (m_currentReply) {
m_currentReply->m_responseData = data;
m_currentReply->m_timer.stop();
emit m_currentReply->finished();
} else {
qCWarning(dcUsbRly82()) << "Unexpected data received" << data.toHex();
}
}
void UsbRly82::onError(QSerialPort::SerialPortError error)
{
if (error != QSerialPort::NoError && error != QSerialPort::OpenError && m_serialPort && m_serialPort->isOpen()) {
qCWarning(dcUsbRly82()) << "Serial port error occurred:" << error << m_serialPort->errorString() << "(Is open:" << m_serialPort->isOpen() << ")";
m_available = false;
emit availableChanged(available());
disconnectRelay();
}
}
void UsbRly82::poll()
{
if (m_replyQueue.count() > 10)
return;
UsbRly82Reply *reply = getDigitalInputs();
connect(reply, &UsbRly82Reply::finished, this, [=](){
if (reply->error() != UsbRly82Reply::ErrorNoError) {
qCWarning(dcUsbRly82()) << "Reading digital inputs finished with error" << reply->error();
return;
}
if (reply->responseData().isEmpty())
return;
quint8 digitalInputs = reply->responseData().at(0);
if (m_digitalInputs != digitalInputs) {
qCDebug(dcUsbRly82()) << "Digital inputs changed";
m_digitalInputs = digitalInputs;
emit digitalInputsChanged();
}
});
reply = getAdcValues();
connect(reply, &UsbRly82Reply::finished, this, [=](){
if (reply->error() != UsbRly82Reply::ErrorNoError) {
qCWarning(dcUsbRly82()) << "Reading analog inputs finished with error" << reply->error();
return;
}
qCDebug(dcUsbRly82()) << "Analog inputs";
});
}

106
usbrly82/usbrly82.h Normal file
View File

@ -0,0 +1,106 @@
#ifndef USBRLY82_H
#define USBRLY82_H
#include <QObject>
#include <QTimer>
#include <QQueue>
#include <QSerialPort>
class UsbRly82Reply : public QObject
{
Q_OBJECT
friend class UsbRly82;
public:
enum Error {
ErrorNoError,
ErrorTimeout
};
Q_ENUM(Error)
Error error() const;
QByteArray requestData() const;
QByteArray responseData() const;
signals:
void finished();
private:
explicit UsbRly82Reply(QObject *parent = nullptr);
Error m_error = ErrorNoError;
QTimer m_timer;
bool m_expectsResponse = true;
QByteArray m_requestData;
QByteArray m_responseData;
};
class UsbRly82 : public QObject
{
Q_OBJECT
public:
explicit UsbRly82(QObject *parent = nullptr);
bool available() const;
bool powerRelay1() const;
UsbRly82Reply *setRelay1Power(bool power);
bool powerRelay2() const;
UsbRly82Reply *setRelay2Power(bool power);
quint8 digitalInputs() const;
bool connectRelay(const QString &serialPort);
void disconnectRelay();
UsbRly82Reply *getSerialNumber();
UsbRly82Reply *getSoftwareVersion();
UsbRly82Reply *getRelayStates();
UsbRly82Reply *getDigitalInputs();
UsbRly82Reply *getAdcValues();
UsbRly82Reply *getAdcReference();
static bool checkBit(quint8 byte, uint bitNumber);
signals:
void availableChanged(bool available);
void powerRelay1Changed(bool powerRelay1);
void powerRelay2Changed(bool powerRelay2);
void digitalInputsChanged();
private:
QTimer m_refreshTimer;
QSerialPort *m_serialPort = nullptr;
bool m_available = false;
QString m_serialNumber;
QString m_softwareVersion;
bool m_powerRelay1 = false;
bool m_powerRelay2 = false;
UsbRly82Reply *m_currentReply = nullptr;
QQueue<UsbRly82Reply *> m_replyQueue;
UsbRly82Reply *createReply(const QByteArray &requestData, bool expectsResponse = true);
void sendNextRequest();
quint8 m_digitalInputs = 0x00;
private slots:
void onReadyRead();
void onError(QSerialPort::SerialPortError error);
void poll();
};
#endif // USBRLY82_H

16
usbrly82/usbrly82.pro Normal file
View File

@ -0,0 +1,16 @@
include(../plugins.pri)
QT *= serialport
PKGCONFIG += libudev
HEADERS += \
integrationpluginusbrly82.h \
serialportmonitor.h \
usbrly82.h
SOURCES += \
integrationpluginusbrly82.cpp \
serialportmonitor.cpp \
usbrly82.cpp