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 +