diff --git a/nymea-plugins.pro b/nymea-plugins.pro
index 781fa5bc..8f528f5a 100644
--- a/nymea-plugins.pro
+++ b/nymea-plugins.pro
@@ -72,6 +72,7 @@ PLUGIN_DIRS = \
udpcommander \
unifi \
usbrelay \
+ usbrly82 \
wakeonlan \
wemo \
ws2812fx \
diff --git a/usbrly82/README.md b/usbrly82/README.md
new file mode 100644
index 00000000..78ef0b00
--- /dev/null
+++ b/usbrly82/README.md
@@ -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)
diff --git a/usbrly82/integrationpluginusbrly82.cpp b/usbrly82/integrationpluginusbrly82.cpp
new file mode 100644
index 00000000..28537829
--- /dev/null
+++ b/usbrly82/integrationpluginusbrly82.cpp
@@ -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 .
+*
+* 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;
+ }
+}
diff --git a/usbrly82/integrationpluginusbrly82.h b/usbrly82/integrationpluginusbrly82.h
new file mode 100644
index 00000000..b6cbe587
--- /dev/null
+++ b/usbrly82/integrationpluginusbrly82.h
@@ -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 .
+*
+* 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 m_relays;
+
+private slots:
+ void onSerialPortAdded(const SerialPortMonitor::SerialPortInfo &serialPortInfo);
+ void onSerialPortRemoved(const SerialPortMonitor::SerialPortInfo &serialPortInfo);
+
+};
+
+#endif // INTEGRATIONPLUGINUSBRLY82_H
diff --git a/usbrly82/integrationpluginusbrly82.json b/usbrly82/integrationpluginusbrly82.json
new file mode 100644
index 00000000..5a4f4b95
--- /dev/null
+++ b/usbrly82/integrationpluginusbrly82.json
@@ -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
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
+
+
+
diff --git a/usbrly82/meta.json b/usbrly82/meta.json
new file mode 100644
index 00000000..d928b015
--- /dev/null
+++ b/usbrly82/meta.json
@@ -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"
+ ]
+}
diff --git a/usbrly82/robotelectronics.png b/usbrly82/robotelectronics.png
new file mode 100644
index 00000000..b53c7adb
Binary files /dev/null and b/usbrly82/robotelectronics.png differ
diff --git a/usbrly82/serialportmonitor.cpp b/usbrly82/serialportmonitor.cpp
new file mode 100644
index 00000000..3649795a
--- /dev/null
+++ b/usbrly82/serialportmonitor.cpp
@@ -0,0 +1,190 @@
+#include "serialportmonitor.h"
+#include "extern-plugininfo.h"
+
+#include
+
+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(vendorIdString.toUInt(nullptr, 16));
+ info.productId = static_cast(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(vendorIdString.toUInt(nullptr, 16));
+ info.productId = static_cast(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::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();
+}
diff --git a/usbrly82/serialportmonitor.h b/usbrly82/serialportmonitor.h
new file mode 100644
index 00000000..2737cadf
--- /dev/null
+++ b/usbrly82/serialportmonitor.h
@@ -0,0 +1,45 @@
+#ifndef SERIALPORTMONITOR_H
+#define SERIALPORTMONITOR_H
+
+#include
+#include
+#include
+#include
+
+#include
+
+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 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 m_serialPortInfos;
+
+};
+
+QDebug operator<< (QDebug dbg, const SerialPortMonitor::SerialPortInfo &serialPortInfo);
+
+
+#endif // SERIALPORTMONITOR_H
diff --git a/usbrly82/usbrly82.cpp b/usbrly82/usbrly82.cpp
new file mode 100644
index 00000000..0d06de1f
--- /dev/null
+++ b/usbrly82/usbrly82.cpp
@@ -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();
+ 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";
+ });
+}
diff --git a/usbrly82/usbrly82.h b/usbrly82/usbrly82.h
new file mode 100644
index 00000000..8c241aa0
--- /dev/null
+++ b/usbrly82/usbrly82.h
@@ -0,0 +1,106 @@
+#ifndef USBRLY82_H
+#define USBRLY82_H
+
+#include
+#include
+#include
+#include
+
+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 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
diff --git a/usbrly82/usbrly82.pro b/usbrly82/usbrly82.pro
new file mode 100644
index 00000000..0c808803
--- /dev/null
+++ b/usbrly82/usbrly82.pro
@@ -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
+