/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 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; }); }