diff --git a/debian/control b/debian/control index 0c024b86..d7bb19ad 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.io plugin for USB-RLY82 relay + This package will install the nymea.io integration plugin for USB-RLY82 relay + with 2 relays and 8 analog/digital inputs. + + Package: nymea-plugin-wakeonlan Architecture: any Depends: ${shlibs:Depends}, @@ -1218,6 +1230,7 @@ Depends: nymea-plugin-anel, nymea-plugin-keba, nymea-plugin-unifi, nymea-plugin-usbrelay, + nymea-plugin-usbrly82, nymea-plugin-yamahaavr, Description: Plugins for nymea IoT server - the default plugin collection The nymea daemon is a plugin based IoT (Internet of Things) server. The 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/usbrly82/integrationpluginusbrly82.cpp b/usbrly82/integrationpluginusbrly82.cpp index 28537829..c6bc8ab7 100644 --- a/usbrly82/integrationpluginusbrly82.cpp +++ b/usbrly82/integrationpluginusbrly82.cpp @@ -72,9 +72,21 @@ void IntegrationPluginUsbRly82::setupThing(ThingSetupInfo *info) qCDebug(dcUsbRly82()) << "Found serial port for" << thing << serialPortInfo; UsbRly82 *relay = new UsbRly82(this); + relay->setAnlalogRefreshRate(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()); + + updateDigitalInuts(thing); + + thing->setStateValue(usbRelayVersionStateTypeId, relay->softwareVersion()); + } }); connect(relay, &UsbRly82::powerRelay1Changed, thing, [=](bool power){ @@ -84,15 +96,13 @@ void IntegrationPluginUsbRly82::setupThing(ThingSetupInfo *info) connect(relay, &UsbRly82::powerRelay2Changed, thing, [=](bool power){ qCDebug(dcUsbRly82()) << thing << "relay 2 power changed" << power; - thing->setStateValue(usbRelayPowerRelay1StateTypeId, power); + thing->setStateValue(usbRelayPowerRelay2StateTypeId, power); }); - connect(relay, &UsbRly82::digitalInputsChanged, thing, [=](){ - thing->setStateValue(usbRelayPowerRelay1StateTypeId, UsbRly82::checkBit()); + updateDigitalInuts(thing); }); - if (!relay->connectRelay(serialPortInfo.systemLocation)) { qCWarning(dcUsbRly82()) << "Setup failed. Could not connect to relay" << thing; info->finish(Thing::ThingErrorHardwareFailure); @@ -102,6 +112,14 @@ void IntegrationPluginUsbRly82::setupThing(ThingSetupInfo *info) 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->setAnlalogRefreshRate(value.toUInt()); + } + }); + return; } } @@ -118,43 +136,6 @@ void IntegrationPluginUsbRly82::setupThing(ThingSetupInfo *info) void IntegrationPluginUsbRly82::postSetupThing(Thing *thing) { qCDebug(dcUsbRly82()) << "Post setup thing" << thing; - - // if (thing->thingClassId() == usbRelayConnectorThingClassId) { - - // // Initialize the states - // UsbRelay *relay = m_relays.key(thing); - // if (!relay) { - // qCWarning(dcUsbRly82()) << "Could not find relay in post setup."; - // return; - // } - - // thing->setStateValue(usbRelayConnectorConnectedStateTypeId, relay->connected()); - - // // Check if we have to create child devices (relays) - // if (myThings().filterByParentId(thing->id()).isEmpty()) { - - // ThingDescriptors descriptors; - // for (int i = 0; i < relay->relayCount(); i++) { - // int relayNumber = i + 1; - // ThingDescriptor descriptor(usbRelayThingClassId, QString("Relay %1").arg(relayNumber), QString(), thing->id()); - // ParamList params; - // params.append(Param(usbRelayThingRelayNumberParamTypeId, relayNumber)); - // descriptor.setParams(params); - // descriptors.append(descriptor); - // } - - // emit autoThingsAppeared(descriptors); - // } - // } else if (thing->thingClassId() == usbRelayThingClassId) { - - // UsbRelay *relay = getRelayForDevice(thing); - // if (!relay) return; - - // // Set the current states - // int relayNumber = thing->paramValue(usbRelayThingRelayNumberParamTypeId).toInt(); - // thing->setStateValue(usbRelayConnectedStateTypeId, relay->connected()); - // thing->setStateValue(usbRelayPowerStateTypeId, relay->relayPower(relayNumber)); - // } } void IntegrationPluginUsbRly82::thingRemoved(Thing *thing) @@ -162,8 +143,9 @@ void IntegrationPluginUsbRly82::thingRemoved(Thing *thing) qCDebug(dcUsbRly82()) << "Remove thing" << thing; if (thing->thingClassId() == usbRelayThingClassId) { UsbRly82 *relay = m_relays.take(thing); - if (!relay) return; - delete relay; + if (relay) { + delete relay; + } } } @@ -174,50 +156,50 @@ void IntegrationPluginUsbRly82::executeAction(ThingActionInfo *info) if (info->thing()->thingClassId() == usbRelayThingClassId) { - Thing *thing = info->thing(); - UsbRly82 *relay = m_relays.value(thing); + Thing *thing = info->thing(); + UsbRly82 *relay = m_relays.value(thing); - if (!relay) { - qCWarning(dcUsbRly82()) << "Could execute action because could not find USB relay for" << thing; - info->finish(Thing::ThingErrorHardwareNotAvailable); - return; - } - - if (!relay->available()) { - qCWarning(dcUsbRly82()) << "Relay is not connected"; - info->finish(Thing::ThingErrorHardwareNotAvailable); - return; - } - - if (info->action().actionTypeId() == usbRelayPowerRelay1ActionTypeId) { - bool power = info->action().paramValue(usbRelayPowerRelay1ActionPowerRelay1ParamTypeId).toBool(); - UsbRly82Reply *reply = relay->setRelay1Power(power); - connect(reply, &UsbRly82Reply::finished, info, [=](){ - if (reply->error() != UsbRly82Reply::ErrorNoError) { - info->finish(Thing::ThingErrorHardwareFailure); - return; - } - - info->finish(Thing::ThingErrorNoError); - }); - return; - } else if (info->action().actionTypeId() == usbRelayPowerRelay2ActionTypeId) { - bool power = info->action().paramValue(usbRelayPowerRelay2ActionPowerRelay2ParamTypeId).toBool(); - UsbRly82Reply *reply = relay->setRelay2Power(power); - connect(reply, &UsbRly82Reply::finished, info, [=](){ - if (reply->error() != UsbRly82Reply::ErrorNoError) { - info->finish(Thing::ThingErrorHardwareFailure); - return; - } - - info->finish(Thing::ThingErrorNoError); - }); - return; - } - - info->finish(Thing::ThingErrorActionTypeNotFound); + 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); } @@ -252,3 +234,21 @@ void IntegrationPluginUsbRly82::onSerialPortRemoved(const SerialPortMonitor::Ser qCDebug(dcUsbRly82()) << "[-] Removed" << serialPortInfo; } } + +void IntegrationPluginUsbRly82::updateDigitalInuts(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 index b6cbe587..4c0a694c 100644 --- a/usbrly82/integrationpluginusbrly82.h +++ b/usbrly82/integrationpluginusbrly82.h @@ -62,6 +62,7 @@ private slots: void onSerialPortAdded(const SerialPortMonitor::SerialPortInfo &serialPortInfo); void onSerialPortRemoved(const SerialPortMonitor::SerialPortInfo &serialPortInfo); + void updateDigitalInuts(Thing *thing); }; #endif // INTEGRATIONPLUGINUSBRLY82_H diff --git a/usbrly82/integrationpluginusbrly82.json b/usbrly82/integrationpluginusbrly82.json index 5a4f4b95..87b1d54c 100644 --- a/usbrly82/integrationpluginusbrly82.json +++ b/usbrly82/integrationpluginusbrly82.json @@ -24,6 +24,16 @@ "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", @@ -231,8 +241,8 @@ "name": "version", "displayName": "Version", "displayNameEvent": "Version changed", - "type": "bool", - "defaultValue": false + "type": "QString", + "defaultValue": "" } ] } diff --git a/usbrly82/usbrly82.cpp b/usbrly82/usbrly82.cpp index 0d06de1f..9807d936 100644 --- a/usbrly82/usbrly82.cpp +++ b/usbrly82/usbrly82.cpp @@ -1,6 +1,7 @@ -#include "usbrly82.h" +#include "usbrly82.h" #include "extern-plugininfo.h" +#include UsbRly82Reply::Error UsbRly82Reply::error() const { @@ -27,14 +28,17 @@ UsbRly82Reply::UsbRly82Reply(QObject *parent) : QObject(parent) }); } - - UsbRly82::UsbRly82(QObject *parent) : QObject(parent) { qRegisterMetaType(); - m_refreshTimer.setInterval(100); - m_refreshTimer.setSingleShot(false); - connect(&m_refreshTimer, &QTimer::timeout, this, &UsbRly82::poll); + + 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 @@ -42,6 +46,16 @@ 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; @@ -78,6 +92,25 @@ UsbRly82Reply *UsbRly82::setRelay2Power(bool power) return reply; } +uint UsbRly82::analogRefreshRate() const +{ + return m_analogRefreshRate; +} + +void UsbRly82::setAnlalogRefreshRate(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; @@ -155,10 +188,33 @@ bool UsbRly82::connectRelay(const QString &serialPort) qCDebug(dcUsbRly82()) << "Relay 1:" << m_powerRelay1; qCDebug(dcUsbRly82()) << "Relay 2:" << m_powerRelay2; - m_available = true; - emit availableChanged(m_available); + UsbRly82Reply *reply = getDigitalInputs(); + connect(reply, &UsbRly82Reply::finished, this, [=](){ + if (reply->error() != UsbRly82Reply::ErrorNoError) { + qCWarning(dcUsbRly82()) << "Reading digital inputs finished with error" << reply->error(); + return; + } - m_refreshTimer.start(); + 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."; + } + }); }); }); }); @@ -175,7 +231,8 @@ void UsbRly82::disconnectRelay() m_serialPort = nullptr; } - m_refreshTimer.stop(); + m_digitalRefreshTimer.stop(); + m_analogRefreshTimer.stop(); m_available = false; emit availableChanged(m_available); @@ -241,7 +298,7 @@ UsbRly82Reply *UsbRly82::createReply(const QByteArray &requestData, bool expects if (!expectsResponse) { m_replyQueue.enqueue(reply); } else { - // Priorize requests without response (like switching the relay) + // Prioritize requests without response (like switching the relay) m_replyQueue.prepend(reply); } return reply; @@ -256,7 +313,7 @@ void UsbRly82::sendNextRequest() return; m_currentReply = m_replyQueue.dequeue(); - qCDebug(dcUsbRly82()) << "-->" << m_currentReply->requestData().toHex(); + //qCDebug(dcUsbRly82()) << "-->" << m_currentReply->requestData().toHex(); m_serialPort->write(m_currentReply->requestData()); if (m_currentReply->m_expectsResponse) { m_currentReply->m_timer.start(1000); @@ -274,7 +331,7 @@ bool UsbRly82::checkBit(quint8 byte, uint bitNumber) void UsbRly82::onReadyRead() { QByteArray data = m_serialPort->readAll(); - qCDebug(dcUsbRly82()) << "<--" << data.toHex(); + //qCDebug(dcUsbRly82()) << "<--" << data.toHex(); if (m_currentReply) { m_currentReply->m_responseData = data; @@ -297,37 +354,67 @@ void UsbRly82::onError(QSerialPort::SerialPortError error) } } -void UsbRly82::poll() +void UsbRly82::updateDigitalInputs() { - if (m_replyQueue.count() > 10) + // Make sure the queue does not overflow + if (m_updateDigitalInputsReply) return; - UsbRly82Reply *reply = getDigitalInputs(); - connect(reply, &UsbRly82Reply::finished, this, [=](){ - if (reply->error() != UsbRly82Reply::ErrorNoError) { - qCWarning(dcUsbRly82()) << "Reading digital inputs finished with error" << reply->error(); + 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 (reply->responseData().isEmpty()) + if (m_updateDigitalInputsReply->responseData().isEmpty()) { + m_updateDigitalInputsReply = nullptr; return; + } - quint8 digitalInputs = reply->responseData().at(0); + quint8 digitalInputs = m_updateDigitalInputsReply->responseData().at(0); if (m_digitalInputs != digitalInputs) { - qCDebug(dcUsbRly82()) << "Digital inputs changed"; m_digitalInputs = digitalInputs; emit digitalInputsChanged(); } + + m_updateDigitalInputsReply = nullptr; }); +} +void UsbRly82::updateAnalogInputs() +{ + // Make sure the queue does not overflow + if (m_updateAnalogInputsReply) + return; - reply = getAdcValues(); - connect(reply, &UsbRly82Reply::finished, this, [=](){ - if (reply->error() != UsbRly82Reply::ErrorNoError) { - qCWarning(dcUsbRly82()) << "Reading analog inputs finished with error" << reply->error(); + 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; } - qCDebug(dcUsbRly82()) << "Analog inputs"; + 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 index 8c241aa0..fc96b576 100644 --- a/usbrly82/usbrly82.h +++ b/usbrly82/usbrly82.h @@ -4,6 +4,7 @@ #include #include #include +#include #include class UsbRly82Reply : public QObject @@ -44,6 +45,8 @@ public: explicit UsbRly82(QObject *parent = nullptr); bool available() const; + QString serialNumber() const; + QString softwareVersion() const; bool powerRelay1() const; UsbRly82Reply *setRelay1Power(bool power); @@ -51,6 +54,9 @@ public: bool powerRelay2() const; UsbRly82Reply *setRelay2Power(bool power); + uint analogRefreshRate() const; + void setAnlalogRefreshRate(uint analogRefreshRate); + quint8 digitalInputs() const; bool connectRelay(const QString &serialPort); @@ -66,7 +72,6 @@ public: static bool checkBit(quint8 byte, uint bitNumber); - signals: void availableChanged(bool available); @@ -76,31 +81,38 @@ signals: void digitalInputsChanged(); private: - QTimer m_refreshTimer; + 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 poll(); - + void updateDigitalInputs(); + void updateAnalogInputs(); }; #endif // USBRLY82_H