diff --git a/debian/control b/debian/control
index 0c024b86..d8f4fab2 100644
--- a/debian/control
+++ b/debian/control
@@ -846,6 +846,18 @@ Description: nymea.io plugin for USB relay
This package will install the nymea.io plugin for USB relay
+Package: nymea-plugin-usbrly82
+Architecture: any
+Multi-Arch: same
+Section: libs
+Depends: ${shlibs:Depends},
+ ${misc:Depends},
+ libudev1,
+Description: nymea integration plugin for USB-RLY82 relay
+ This package will install the nymea integration plugin for USB-RLY82 relay
+ with 2 relays and 8 analog/digital inputs.
+
+
Package: nymea-plugin-wakeonlan
Architecture: any
Depends: ${shlibs:Depends},
diff --git a/debian/nymea-plugin-usbrly82.install.in b/debian/nymea-plugin-usbrly82.install.in
new file mode 100644
index 00000000..e9da1cc7
--- /dev/null
+++ b/debian/nymea-plugin-usbrly82.install.in
@@ -0,0 +1,2 @@
+usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginusbrly82.so
+usbrly82/translations/*qm usr/share/nymea/translations/
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..cd7202e3
--- /dev/null
+++ b/usbrly82/integrationpluginusbrly82.cpp
@@ -0,0 +1,258 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* 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;
+
+ // Handle reconfigure
+ if (m_relays.contains(thing)) {
+ m_relays.take(thing)->deleteLater();
+ }
+
+ UsbRly82 *relay = new UsbRly82(this);
+ relay->setAnalogRefreshRate(thing->setting(usbRelaySettingsAnalogRefreshRateParamTypeId).toUInt());
+
+ connect(relay, &UsbRly82::availableChanged, thing, [=](bool available){
+ qCDebug(dcUsbRly82()) << thing << "available changed" << available;
+ thing->setStateValue("connected", available);
+
+ if (available) {
+ // Set the already fetched information
+ thing->setStateValue(usbRelayPowerRelay1StateTypeId, relay->powerRelay1());
+ thing->setStateValue(usbRelayPowerRelay2StateTypeId, relay->powerRelay2());
+
+ updateDigitalInputs(thing);
+
+ thing->setStateValue(usbRelayVersionStateTypeId, relay->softwareVersion());
+ }
+ });
+
+ 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(usbRelayPowerRelay2StateTypeId, power);
+ });
+
+ connect(relay, &UsbRly82::digitalInputsChanged, thing, [=](){
+ updateDigitalInputs(thing);
+ });
+
+ 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);
+
+ connect(thing, &Thing::settingChanged, this, [=](const ParamTypeId ¶mTypeId, const QVariant &value){
+ if (paramTypeId == usbRelaySettingsAnalogRefreshRateParamTypeId) {
+ qCDebug(dcUsbRly82()) << "Refrsh rat changed for" << thing << value.toUInt() << "ms";
+ relay->setAnalogRefreshRate(value.toUInt());
+ }
+ });
+
+ 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;
+}
+
+void IntegrationPluginUsbRly82::thingRemoved(Thing *thing)
+{
+ qCDebug(dcUsbRly82()) << "Remove thing" << thing;
+ if (thing->thingClassId() == usbRelayThingClassId) {
+ UsbRly82 *relay = m_relays.take(thing);
+ if (relay) {
+ 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()) << "Cannot execute action. Relay is not available" << thing;
+ 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;
+ }
+}
+
+void IntegrationPluginUsbRly82::updateDigitalInputs(Thing *thing)
+{
+ UsbRly82 *relay = m_relays.value(thing);
+ if (!relay)
+ return;
+
+ qCDebug(dcUsbRly82()) << thing << "digital inputs changed:" << QString("%1").arg(relay->digitalInputs(), 8, 2, QChar('0'));
+
+ thing->setStateValue(usbRelayDigitalInputChannel1StateTypeId, UsbRly82::checkBit(relay->digitalInputs(), 0));
+ thing->setStateValue(usbRelayDigitalInputChannel2StateTypeId, UsbRly82::checkBit(relay->digitalInputs(), 1));
+ thing->setStateValue(usbRelayDigitalInputChannel3StateTypeId, UsbRly82::checkBit(relay->digitalInputs(), 2));
+ thing->setStateValue(usbRelayDigitalInputChannel4StateTypeId, UsbRly82::checkBit(relay->digitalInputs(), 3));
+ thing->setStateValue(usbRelayDigitalInputChannel5StateTypeId, UsbRly82::checkBit(relay->digitalInputs(), 4));
+ thing->setStateValue(usbRelayDigitalInputChannel6StateTypeId, UsbRly82::checkBit(relay->digitalInputs(), 5));
+ thing->setStateValue(usbRelayDigitalInputChannel7StateTypeId, UsbRly82::checkBit(relay->digitalInputs(), 6));
+ thing->setStateValue(usbRelayDigitalInputChannel8StateTypeId, UsbRly82::checkBit(relay->digitalInputs(), 7));
+}
diff --git a/usbrly82/integrationpluginusbrly82.h b/usbrly82/integrationpluginusbrly82.h
new file mode 100644
index 00000000..811c337e
--- /dev/null
+++ b/usbrly82/integrationpluginusbrly82.h
@@ -0,0 +1,68 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* 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);
+
+ void updateDigitalInputs(Thing *thing);
+};
+
+#endif // INTEGRATIONPLUGINUSBRLY82_H
diff --git a/usbrly82/integrationpluginusbrly82.json b/usbrly82/integrationpluginusbrly82.json
new file mode 100644
index 00000000..87b1d54c
--- /dev/null
+++ b/usbrly82/integrationpluginusbrly82.json
@@ -0,0 +1,255 @@
+{
+ "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": ""
+ }
+ ],
+ "settingsTypes": [
+ {
+ "id": "e426f3b1-e9bf-4bef-bc01-2aba6326d265",
+ "name": "analogRefreshRate",
+ "displayName": "Analog refresh interval",
+ "type": "uint",
+ "unit": "MilliSeconds",
+ "defaultValue": 1000
+ }
+ ],
+ "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": "QString",
+ "defaultValue": ""
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
+
+
+
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..a69cf318
--- /dev/null
+++ b/usbrly82/serialportmonitor.cpp
@@ -0,0 +1,220 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* 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 "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..b87e8134
--- /dev/null
+++ b/usbrly82/serialportmonitor.h
@@ -0,0 +1,75 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* 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 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/translations/33d4ebad-104b-4f24-8089-6edd35ba2c95-en_US.ts b/usbrly82/translations/33d4ebad-104b-4f24-8089-6edd35ba2c95-en_US.ts
new file mode 100644
index 00000000..128f1433
--- /dev/null
+++ b/usbrly82/translations/33d4ebad-104b-4f24-8089-6edd35ba2c95-en_US.ts
@@ -0,0 +1,352 @@
+
+
+
+
+ UsbRly82
+
+
+
+ Analog Channel 1
+ The name of the ParamType (ThingClass: usbRelay, EventType: analogInputChannel1, ID: {0dfda321-7da5-4e4c-880e-5b45098398e8})
+----------
+The name of the StateType ({0dfda321-7da5-4e4c-880e-5b45098398e8}) of ThingClass usbRelay
+
+
+
+
+
+ Analog Channel 2
+ The name of the ParamType (ThingClass: usbRelay, EventType: analogInputChannel2, ID: {02882e2a-f0a6-4042-b91e-c7618c15399e})
+----------
+The name of the StateType ({02882e2a-f0a6-4042-b91e-c7618c15399e}) of ThingClass usbRelay
+
+
+
+
+
+ Analog Channel 3
+ The name of the ParamType (ThingClass: usbRelay, EventType: analogInputChannel3, ID: {de9172e5-b1c1-4a7e-aebe-819ef22c2c5a})
+----------
+The name of the StateType ({de9172e5-b1c1-4a7e-aebe-819ef22c2c5a}) of ThingClass usbRelay
+
+
+
+
+
+ Analog Channel 4
+ The name of the ParamType (ThingClass: usbRelay, EventType: analogInputChannel4, ID: {875ffbf1-3f11-4935-bd16-de983585baad})
+----------
+The name of the StateType ({875ffbf1-3f11-4935-bd16-de983585baad}) of ThingClass usbRelay
+
+
+
+
+
+ Analog Channel 5
+ The name of the ParamType (ThingClass: usbRelay, EventType: analogInputChannel5, ID: {fa97d343-544d-4322-aacd-4f9f7c21224b})
+----------
+The name of the StateType ({fa97d343-544d-4322-aacd-4f9f7c21224b}) of ThingClass usbRelay
+
+
+
+
+
+ Analog Channel 6
+ The name of the ParamType (ThingClass: usbRelay, EventType: analogInputChannel6, ID: {51d50bf9-6d40-4d63-a3a2-a0a7c0b5850d})
+----------
+The name of the StateType ({51d50bf9-6d40-4d63-a3a2-a0a7c0b5850d}) of ThingClass usbRelay
+
+
+
+
+
+ Analog Channel 7
+ The name of the ParamType (ThingClass: usbRelay, EventType: analogInputChannel7, ID: {25ecf6a9-2007-4768-93a4-28ec81bac179})
+----------
+The name of the StateType ({25ecf6a9-2007-4768-93a4-28ec81bac179}) of ThingClass usbRelay
+
+
+
+
+
+ Analog Channel 8
+ The name of the ParamType (ThingClass: usbRelay, EventType: analogInputChannel8, ID: {93198a69-4ab6-4fd5-90ab-7f26a67b3b4a})
+----------
+The name of the StateType ({93198a69-4ab6-4fd5-90ab-7f26a67b3b4a}) of ThingClass usbRelay
+
+
+
+
+ Analog channel 1 value changed
+ The name of the EventType ({0dfda321-7da5-4e4c-880e-5b45098398e8}) of ThingClass usbRelay
+
+
+
+
+ Analog channel 2 value changed
+ The name of the EventType ({02882e2a-f0a6-4042-b91e-c7618c15399e}) of ThingClass usbRelay
+
+
+
+
+ Analog channel 3 value changed
+ The name of the EventType ({de9172e5-b1c1-4a7e-aebe-819ef22c2c5a}) of ThingClass usbRelay
+
+
+
+
+ Analog channel 4 value changed
+ The name of the EventType ({875ffbf1-3f11-4935-bd16-de983585baad}) of ThingClass usbRelay
+
+
+
+
+ Analog channel 5 value changed
+ The name of the EventType ({fa97d343-544d-4322-aacd-4f9f7c21224b}) of ThingClass usbRelay
+
+
+
+
+ Analog channel 6 value changed
+ The name of the EventType ({51d50bf9-6d40-4d63-a3a2-a0a7c0b5850d}) of ThingClass usbRelay
+
+
+
+
+ Analog channel 7 value changed
+ The name of the EventType ({25ecf6a9-2007-4768-93a4-28ec81bac179}) of ThingClass usbRelay
+
+
+
+
+ Analog channel 8 value changed
+ The name of the EventType ({93198a69-4ab6-4fd5-90ab-7f26a67b3b4a}) of ThingClass usbRelay
+
+
+
+
+ Analog refresh interval
+ The name of the ParamType (ThingClass: usbRelay, Type: settings, ID: {e426f3b1-e9bf-4bef-bc01-2aba6326d265})
+
+
+
+
+
+ Channel 1
+ The name of the ParamType (ThingClass: usbRelay, EventType: digitalInputChannel1, ID: {ad60f859-11e0-4a80-86fc-1810a5054fb6})
+----------
+The name of the StateType ({ad60f859-11e0-4a80-86fc-1810a5054fb6}) of ThingClass usbRelay
+
+
+
+
+ Channel 1 input changed
+ The name of the EventType ({ad60f859-11e0-4a80-86fc-1810a5054fb6}) of ThingClass usbRelay
+
+
+
+
+
+ Channel 2
+ The name of the ParamType (ThingClass: usbRelay, EventType: digitalInputChannel2, ID: {5038008d-68c7-4cde-845d-335758a05a15})
+----------
+The name of the StateType ({5038008d-68c7-4cde-845d-335758a05a15}) of ThingClass usbRelay
+
+
+
+
+ Channel 2 input changed
+ The name of the EventType ({5038008d-68c7-4cde-845d-335758a05a15}) of ThingClass usbRelay
+
+
+
+
+
+ Channel 3
+ The name of the ParamType (ThingClass: usbRelay, EventType: digitalInputChannel3, ID: {73a8c051-5ce8-4909-a75f-f0e88f1ceb61})
+----------
+The name of the StateType ({73a8c051-5ce8-4909-a75f-f0e88f1ceb61}) of ThingClass usbRelay
+
+
+
+
+ Channel 3 input changed
+ The name of the EventType ({73a8c051-5ce8-4909-a75f-f0e88f1ceb61}) of ThingClass usbRelay
+
+
+
+
+
+ Channel 4
+ The name of the ParamType (ThingClass: usbRelay, EventType: digitalInputChannel4, ID: {11a3e692-cccc-435b-a9fd-721ae332c90c})
+----------
+The name of the StateType ({11a3e692-cccc-435b-a9fd-721ae332c90c}) of ThingClass usbRelay
+
+
+
+
+ Channel 4 input changed
+ The name of the EventType ({11a3e692-cccc-435b-a9fd-721ae332c90c}) of ThingClass usbRelay
+
+
+
+
+
+ Channel 5
+ The name of the ParamType (ThingClass: usbRelay, EventType: digitalInputChannel5, ID: {932cb171-5ba2-4420-bee3-9016dca6498a})
+----------
+The name of the StateType ({932cb171-5ba2-4420-bee3-9016dca6498a}) of ThingClass usbRelay
+
+
+
+
+ Channel 5 input changed
+ The name of the EventType ({932cb171-5ba2-4420-bee3-9016dca6498a}) of ThingClass usbRelay
+
+
+
+
+
+ Channel 6
+ The name of the ParamType (ThingClass: usbRelay, EventType: digitalInputChannel6, ID: {e10074d7-68cb-4f74-87ea-f0cdf193d207})
+----------
+The name of the StateType ({e10074d7-68cb-4f74-87ea-f0cdf193d207}) of ThingClass usbRelay
+
+
+
+
+ Channel 6 input changed
+ The name of the EventType ({e10074d7-68cb-4f74-87ea-f0cdf193d207}) of ThingClass usbRelay
+
+
+
+
+
+ Channel 7
+ The name of the ParamType (ThingClass: usbRelay, EventType: digitalInputChannel7, ID: {f37aa320-ecfb-4896-bb31-8109606cc5b3})
+----------
+The name of the StateType ({f37aa320-ecfb-4896-bb31-8109606cc5b3}) of ThingClass usbRelay
+
+
+
+
+ Channel 7 input changed
+ The name of the EventType ({f37aa320-ecfb-4896-bb31-8109606cc5b3}) of ThingClass usbRelay
+
+
+
+
+
+ Channel 8
+ The name of the ParamType (ThingClass: usbRelay, EventType: digitalInputChannel8, ID: {32aa1fbd-c128-4258-bb41-22b5b5e150de})
+----------
+The name of the StateType ({32aa1fbd-c128-4258-bb41-22b5b5e150de}) of ThingClass usbRelay
+
+
+
+
+ Channel 8 input changed
+ The name of the EventType ({32aa1fbd-c128-4258-bb41-22b5b5e150de}) of ThingClass usbRelay
+
+
+
+
+
+ Connected
+ The name of the ParamType (ThingClass: usbRelay, EventType: connected, ID: {b482b0ea-1901-4437-a309-be43833a1ad5})
+----------
+The name of the StateType ({b482b0ea-1901-4437-a309-be43833a1ad5}) of ThingClass usbRelay
+
+
+
+
+ Connected changed
+ The name of the EventType ({b482b0ea-1901-4437-a309-be43833a1ad5}) of ThingClass usbRelay
+
+
+
+
+
+
+ Power relay 1
+ The name of the ParamType (ThingClass: usbRelay, ActionType: powerRelay1, ID: {9935ecba-bb56-4c6c-8be1-60bacc40abb7})
+----------
+The name of the ParamType (ThingClass: usbRelay, EventType: powerRelay1, ID: {9935ecba-bb56-4c6c-8be1-60bacc40abb7})
+----------
+The name of the StateType ({9935ecba-bb56-4c6c-8be1-60bacc40abb7}) of ThingClass usbRelay
+
+
+
+
+ Power relay 1 changed
+ The name of the EventType ({9935ecba-bb56-4c6c-8be1-60bacc40abb7}) of ThingClass usbRelay
+
+
+
+
+
+
+ Power relay 2
+ The name of the ParamType (ThingClass: usbRelay, ActionType: powerRelay2, ID: {d4b84a73-44dd-4bb9-a0d0-e45e6d2aaf7d})
+----------
+The name of the ParamType (ThingClass: usbRelay, EventType: powerRelay2, ID: {d4b84a73-44dd-4bb9-a0d0-e45e6d2aaf7d})
+----------
+The name of the StateType ({d4b84a73-44dd-4bb9-a0d0-e45e6d2aaf7d}) of ThingClass usbRelay
+
+
+
+
+ Power relay 2 changed
+ The name of the EventType ({d4b84a73-44dd-4bb9-a0d0-e45e6d2aaf7d}) of ThingClass usbRelay
+
+
+
+
+ Robot electronics
+ The name of the vendor ({c3b972d1-85e6-436c-ae07-a04cbfae604e})
+
+
+
+
+ Serial number
+ The name of the ParamType (ThingClass: usbRelay, Type: thing, ID: {5ac5b15f-34cf-4d42-8b9d-6e9866d8f2ed})
+
+
+
+
+ Set power relay 1
+ The name of the ActionType ({9935ecba-bb56-4c6c-8be1-60bacc40abb7}) of ThingClass usbRelay
+
+
+
+
+ Set power relay 2
+ The name of the ActionType ({d4b84a73-44dd-4bb9-a0d0-e45e6d2aaf7d}) of ThingClass usbRelay
+
+
+
+
+
+ USB-RLY82
+ The name of the ThingClass ({0ae5cd75-d42e-4993-8ce8-ced5ba315688})
+----------
+The name of the plugin UsbRly82 ({33d4ebad-104b-4f24-8089-6edd35ba2c95})
+
+
+
+
+
+ Version
+ The name of the ParamType (ThingClass: usbRelay, EventType: version, ID: {e06c88f4-38d9-42cd-bab9-2a07a9a83ce0})
+----------
+The name of the StateType ({e06c88f4-38d9-42cd-bab9-2a07a9a83ce0}) of ThingClass usbRelay
+
+
+
+
+ Version changed
+ The name of the EventType ({e06c88f4-38d9-42cd-bab9-2a07a9a83ce0}) of ThingClass usbRelay
+
+
+
+
diff --git a/usbrly82/usbrly82.cpp b/usbrly82/usbrly82.cpp
new file mode 100644
index 00000000..a2387e24
--- /dev/null
+++ b/usbrly82/usbrly82.cpp
@@ -0,0 +1,471 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* 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 "usbrly82.h"
+#include "extern-plugininfo.h"
+
+#include
+
+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_digitalRefreshTimer.setInterval(50);
+ m_digitalRefreshTimer.setSingleShot(false);
+ connect(&m_digitalRefreshTimer, &QTimer::timeout, this, &UsbRly82::updateDigitalInputs);
+
+ m_analogRefreshTimer.setInterval(m_analogRefreshRate);
+ m_analogRefreshTimer.setSingleShot(false);
+ connect(&m_analogRefreshTimer, &QTimer::timeout, this, &UsbRly82::updateAnalogInputs);
+}
+
+bool UsbRly82::available() const
+{
+ return m_available;
+}
+
+QString UsbRly82::serialNumber() const
+{
+ return m_serialNumber;
+}
+
+QString UsbRly82::softwareVersion() const
+{
+ return m_softwareVersion;
+}
+
+bool UsbRly82::powerRelay1() const
+{
+ return m_powerRelay1;
+}
+
+UsbRly82Reply *UsbRly82::setRelay1Power(bool power)
+{
+ UsbRly82Reply *reply;
+ if (power) {
+ reply = createReply(QByteArray::fromHex("65"), false);
+ connect(reply, &UsbRly82Reply::finished, this, [=](){
+ if (reply->error() == UsbRly82Reply::ErrorNoError) {
+ emit powerRelay1Changed(true);
+ }
+ });
+ } else {
+ reply = createReply(QByteArray::fromHex("6F"), false);
+ connect(reply, &UsbRly82Reply::finished, this, [=](){
+ if (reply->error() == UsbRly82Reply::ErrorNoError) {
+ emit powerRelay1Changed(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);
+ connect(reply, &UsbRly82Reply::finished, this, [=](){
+ if (reply->error() == UsbRly82Reply::ErrorNoError) {
+ emit powerRelay2Changed(true);
+ }
+ });
+ } else {
+ reply = createReply(QByteArray::fromHex("70"), false);
+ connect(reply, &UsbRly82Reply::finished, this, [=](){
+ if (reply->error() == UsbRly82Reply::ErrorNoError) {
+ emit powerRelay2Changed(false);
+ }
+ });
+ }
+
+ sendNextRequest();
+ return reply;
+}
+
+uint UsbRly82::analogRefreshRate() const
+{
+ return m_analogRefreshRate;
+}
+
+void UsbRly82::setAnalogRefreshRate(uint analogRefreshRate)
+{
+ m_analogRefreshRate = analogRefreshRate;
+ if (m_analogRefreshRate == 0) {
+ qCDebug(dcUsbRly82()) << "Refresh rate set to 0. Auto refreshing analog inputs disabled.";
+ m_analogRefreshTimer.stop();
+ } else {
+ m_analogRefreshTimer.setInterval(m_analogRefreshRate);
+ if (m_available) {
+ m_analogRefreshTimer.start();
+ }
+ }
+}
+
+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;
+
+ 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();
+ }
+
+ m_available = true;
+ emit availableChanged(m_available);
+
+ m_digitalRefreshTimer.start();
+ if (m_analogRefreshRate != 0) {
+ m_analogRefreshTimer.start(m_analogRefreshRate);
+ } else {
+ qCDebug(dcUsbRly82()) << "Refresh rate set to 0. Auto refreshing analog inputs disabled.";
+ }
+ });
+ });
+ });
+ });
+
+ 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_digitalRefreshTimer.stop();
+ m_analogRefreshTimer.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 {
+ // Prioritize 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::updateDigitalInputs()
+{
+ // Make sure the queue does not overflow
+ if (m_updateDigitalInputsReply)
+ return;
+
+ m_updateDigitalInputsReply = getDigitalInputs();
+ connect(m_updateDigitalInputsReply, &UsbRly82Reply::finished, this, [=](){
+
+ if (m_updateDigitalInputsReply->error() != UsbRly82Reply::ErrorNoError) {
+ qCWarning(dcUsbRly82()) << "Reading digital inputs finished with error" << m_updateDigitalInputsReply->error();
+ m_updateDigitalInputsReply = nullptr;
+ return;
+ }
+
+ if (m_updateDigitalInputsReply->responseData().isEmpty()) {
+ m_updateDigitalInputsReply = nullptr;
+ return;
+ }
+
+ quint8 digitalInputs = m_updateDigitalInputsReply->responseData().at(0);
+ if (m_digitalInputs != digitalInputs) {
+ m_digitalInputs = digitalInputs;
+ emit digitalInputsChanged();
+ }
+
+ m_updateDigitalInputsReply = nullptr;
+ });
+}
+
+void UsbRly82::updateAnalogInputs()
+{
+ // Make sure the queue does not overflow
+ if (m_updateAnalogInputsReply)
+ return;
+
+ m_updateAnalogInputsReply = getAdcValues();
+ connect(m_updateAnalogInputsReply, &UsbRly82Reply::finished, this, [=](){
+
+ if (m_updateAnalogInputsReply->error() != UsbRly82Reply::ErrorNoError) {
+ qCWarning(dcUsbRly82()) << "Reading analog inputs finished with error" << m_updateAnalogInputsReply->error();
+ m_updateAnalogInputsReply = nullptr;
+ return;
+ }
+
+ if (m_updateAnalogInputsReply->responseData().count() != 16) {
+ qCWarning(dcUsbRly82()) << "Reading analog inputs response returned invalid size" << m_updateAnalogInputsReply->responseData().count() << "(should be 16)";
+ m_updateAnalogInputsReply = nullptr;
+ return;
+ }
+
+ //qCDebug(dcUsbRly82()) << "Analog inputs" << m_updateAnalogInputsReply->responseData().toHex();
+ QDataStream stream(m_updateAnalogInputsReply->responseData());
+ quint16 value = 0;
+ for (int i = 0; i < 8; i++) {
+ stream >> value;
+ m_analogValues.insert(i, value);
+ //qCDebug(dcUsbRly82()) << "Channel" << i << ":" << value;
+ }
+
+ m_updateAnalogInputsReply = nullptr;
+ });
+}
+
diff --git a/usbrly82/usbrly82.h b/usbrly82/usbrly82.h
new file mode 100644
index 00000000..69075371
--- /dev/null
+++ b/usbrly82/usbrly82.h
@@ -0,0 +1,150 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* 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 USBRLY82_H
+#define USBRLY82_H
+
+#include
+#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;
+ QString serialNumber() const;
+ QString softwareVersion() const;
+
+ bool powerRelay1() const;
+ UsbRly82Reply *setRelay1Power(bool power);
+
+ bool powerRelay2() const;
+ UsbRly82Reply *setRelay2Power(bool power);
+
+ uint analogRefreshRate() const;
+ void setAnalogRefreshRate(uint analogRefreshRate);
+
+ 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_digitalRefreshTimer;
+ QTimer m_analogRefreshTimer;
+ QSerialPort *m_serialPort = nullptr;
+
+ bool m_available = false;
+
+ QString m_serialNumber;
+ QString m_softwareVersion;
+
+ uint m_analogRefreshRate = 1000;
+
+ bool m_powerRelay1 = false;
+ bool m_powerRelay2 = false;
+
+ UsbRly82Reply *m_currentReply = nullptr;
+ QQueue m_replyQueue;
+
+ UsbRly82Reply *m_updateDigitalInputsReply = nullptr;
+ UsbRly82Reply *m_updateAnalogInputsReply = nullptr;
+
+ UsbRly82Reply *createReply(const QByteArray &requestData, bool expectsResponse = true);
+ void sendNextRequest();
+
+ quint8 m_digitalInputs = 0x00;
+ QHash m_analogValues;
+
+private slots:
+ void onReadyRead();
+ void onError(QSerialPort::SerialPortError error);
+
+ void updateDigitalInputs();
+ void updateAnalogInputs();
+};
+
+Q_DECLARE_METATYPE(QSerialPort::SerialPortError)
+
+#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
+