// SPDX-License-Identifier: GPL-3.0-or-later /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Copyright (C) 2025, ETM-Schurig SARL * * This file is part of nymea-plugins-modbus. * * nymea-plugins-modbus is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * nymea-plugins-modbus 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with nymea-plugins-modbus. If not, see . * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "integrationpluginwavesharerelayed8.h" #include "plugininfo.h" #include "hardwaremanager.h" #include "hardware/modbus/modbusrtuhardwareresource.h" IntegrationPluginWaveshareRelayD8::IntegrationPluginWaveshareRelayD8() { } void IntegrationPluginWaveshareRelayD8::discoverThings(ThingDiscoveryInfo *info) { foreach (ModbusRtuMaster *modbusMaster, hardwareManager()->modbusRtuResource()->modbusRtuMasters()) { qCDebug(dcWaveshareRelayD8()) << "Found RTU master resource" << modbusMaster; if (modbusMaster->connected() && modbusMaster->baudrate() == 9600 && modbusMaster->dataBits() == QSerialPort::Data8 && modbusMaster->stopBits() == QSerialPort::OneStop && modbusMaster->parity() == QSerialPort::NoParity) { ParamList parameters; ThingDescriptor thingDescriptor(waveshareRelayD8ThingClassId, "Waveshare Relay D8", modbusMaster->serialPort()); parameters.append(Param(waveshareRelayD8ThingRtuMasterParamTypeId, modbusMaster->modbusUuid().toString())); thingDescriptor.setParams(parameters); info->addThingDescriptor(thingDescriptor); } else { qCDebug(dcWaveshareRelayD8()) << "Skipping RTU master (not connected or wrong settings):" << modbusMaster; } } QString displayMessage; if (info->thingDescriptors().isEmpty()) { displayMessage = QT_TR_NOOP("Please configure a Modbus RTU master with 9600 baud, 8 data bits, 1 stop bit and no parity."); } info->finish(Thing::ThingErrorNoError, displayMessage); } void IntegrationPluginWaveshareRelayD8::setupThing(ThingSetupInfo *info) { Thing *thing = info->thing(); qCDebug(dcWaveshareRelayD8()) << "Setup" << thing << thing->params(); if (m_connections.contains(thing)) { qCDebug(dcWaveshareRelayD8()) << "Reconfiguring existing thing" << thing->name(); m_connections.take(thing)->deleteLater(); } QUuid rtuMasterUuid = thing->paramValue(waveshareRelayD8ThingRtuMasterParamTypeId).toUuid(); ModbusRtuMaster *master = hardwareManager()->modbusRtuResource()->getModbusRtuMaster(rtuMasterUuid); if (!master) { info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("The Modbus RTU master is not available.")); return; } quint16 slaveAddress = static_cast( thing->paramValue(waveshareRelayD8ThingSlaveAddressParamTypeId).toUInt()); WaveshareRelayD8ModbusRtuConnection *connection = new WaveshareRelayD8ModbusRtuConnection(master, slaveAddress, this); connect(info, &ThingSetupInfo::aborted, connection, &WaveshareRelayD8ModbusRtuConnection::deleteLater); // Reachability connect(connection, &WaveshareRelayD8ModbusRtuConnection::reachableChanged, thing, [connection, thing](bool reachable) { qCDebug(dcWaveshareRelayD8()) << thing->name() << "reachable changed:" << reachable; if (reachable) { connection->initialize(); } else { thing->setStateValue(waveshareRelayD8ConnectedStateTypeId, false); } }); // Initialization connect(connection, &WaveshareRelayD8ModbusRtuConnection::initializationFinished, info, [=](bool success) { qCDebug(dcWaveshareRelayD8()) << "Initialization finished:" << success; if (success) { m_connections.insert(thing, connection); info->finish(Thing::ThingErrorNoError); } else { delete connection; info->finish(Thing::ThingErrorHardwareNotAvailable); } }); connect(connection, &WaveshareRelayD8ModbusRtuConnection::initializationFinished, thing, [=](bool success) { if (success) { thing->setStateValue(waveshareRelayD8ConnectedStateTypeId, true); } }); // Relay state changes (bulk read FC01 → individual signals) connect(connection, &WaveshareRelayD8ModbusRtuConnection::relay1Changed, thing, [thing](quint16 value) { thing->setStateValue(waveshareRelayD8Relay1StateStateTypeId, value != 0); }); connect(connection, &WaveshareRelayD8ModbusRtuConnection::relay2Changed, thing, [thing](quint16 value) { thing->setStateValue(waveshareRelayD8Relay2StateStateTypeId, value != 0); }); connect(connection, &WaveshareRelayD8ModbusRtuConnection::relay3Changed, thing, [thing](quint16 value) { thing->setStateValue(waveshareRelayD8Relay3StateStateTypeId, value != 0); }); connect(connection, &WaveshareRelayD8ModbusRtuConnection::relay4Changed, thing, [thing](quint16 value) { thing->setStateValue(waveshareRelayD8Relay4StateStateTypeId, value != 0); }); connect(connection, &WaveshareRelayD8ModbusRtuConnection::relay5Changed, thing, [thing](quint16 value) { thing->setStateValue(waveshareRelayD8Relay5StateStateTypeId, value != 0); }); connect(connection, &WaveshareRelayD8ModbusRtuConnection::relay6Changed, thing, [thing](quint16 value) { thing->setStateValue(waveshareRelayD8Relay6StateStateTypeId, value != 0); }); connect(connection, &WaveshareRelayD8ModbusRtuConnection::relay7Changed, thing, [thing](quint16 value) { thing->setStateValue(waveshareRelayD8Relay7StateStateTypeId, value != 0); }); connect(connection, &WaveshareRelayD8ModbusRtuConnection::relay8Changed, thing, [thing](quint16 value) { thing->setStateValue(waveshareRelayD8Relay8StateStateTypeId, value != 0); }); // Opto input state changes (bulk read FC02 → individual signals + event) connect(connection, &WaveshareRelayD8ModbusRtuConnection::input1Changed, thing, [this, thing](quint16 value) { bool state = value != 0; thing->setStateValue(waveshareRelayD8Input1StateStateTypeId, state); emitInputChangedEvent(thing, 1, state); }); connect(connection, &WaveshareRelayD8ModbusRtuConnection::input2Changed, thing, [this, thing](quint16 value) { bool state = value != 0; thing->setStateValue(waveshareRelayD8Input2StateStateTypeId, state); emitInputChangedEvent(thing, 2, state); }); connect(connection, &WaveshareRelayD8ModbusRtuConnection::input3Changed, thing, [this, thing](quint16 value) { bool state = value != 0; thing->setStateValue(waveshareRelayD8Input3StateStateTypeId, state); emitInputChangedEvent(thing, 3, state); }); connect(connection, &WaveshareRelayD8ModbusRtuConnection::input4Changed, thing, [this, thing](quint16 value) { bool state = value != 0; thing->setStateValue(waveshareRelayD8Input4StateStateTypeId, state); emitInputChangedEvent(thing, 4, state); }); connect(connection, &WaveshareRelayD8ModbusRtuConnection::input5Changed, thing, [this, thing](quint16 value) { bool state = value != 0; thing->setStateValue(waveshareRelayD8Input5StateStateTypeId, state); emitInputChangedEvent(thing, 5, state); }); connect(connection, &WaveshareRelayD8ModbusRtuConnection::input6Changed, thing, [this, thing](quint16 value) { bool state = value != 0; thing->setStateValue(waveshareRelayD8Input6StateStateTypeId, state); emitInputChangedEvent(thing, 6, state); }); connect(connection, &WaveshareRelayD8ModbusRtuConnection::input7Changed, thing, [this, thing](quint16 value) { bool state = value != 0; thing->setStateValue(waveshareRelayD8Input7StateStateTypeId, state); emitInputChangedEvent(thing, 7, state); }); connect(connection, &WaveshareRelayD8ModbusRtuConnection::input8Changed, thing, [this, thing](quint16 value) { bool state = value != 0; thing->setStateValue(waveshareRelayD8Input8StateStateTypeId, state); emitInputChangedEvent(thing, 8, state); }); connection->update(); } void IntegrationPluginWaveshareRelayD8::postSetupThing(Thing *thing) { Q_UNUSED(thing) if (!m_pluginTimer) { qCDebug(dcWaveshareRelayD8()) << "Starting plugin timer (5s)..."; m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(5); connect(m_pluginTimer, &PluginTimer::timeout, this, [this] { foreach (WaveshareRelayD8ModbusRtuConnection *connection, m_connections) { if (connection->reachable()) { connection->update(); } } }); m_pluginTimer->start(); } } void IntegrationPluginWaveshareRelayD8::thingRemoved(Thing *thing) { WaveshareRelayD8ModbusRtuConnection *connection = m_connections.take(thing); delete connection; if (myThings().isEmpty() && m_pluginTimer) { hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer); m_pluginTimer = nullptr; } } void IntegrationPluginWaveshareRelayD8::executeAction(ThingActionInfo *info) { Thing *thing = info->thing(); Action action = info->action(); WaveshareRelayD8ModbusRtuConnection *connection = m_connections.value(thing); if (!connection || !connection->reachable()) { info->finish(Thing::ThingErrorHardwareNotAvailable); return; } if (action.actionTypeId() == waveshareRelayD8SetRelayActionTypeId) { int channel = action.paramValue(waveshareRelayD8SetRelayActionChannelParamTypeId).toInt(); quint16 value = action.paramValue(waveshareRelayD8SetRelayActionStateParamTypeId).toBool() ? 1 : 0; ModbusRtuReply *reply = nullptr; switch (channel) { case 1: reply = connection->setRelay1(value); break; case 2: reply = connection->setRelay2(value); break; case 3: reply = connection->setRelay3(value); break; case 4: reply = connection->setRelay4(value); break; case 5: reply = connection->setRelay5(value); break; case 6: reply = connection->setRelay6(value); break; case 7: reply = connection->setRelay7(value); break; case 8: reply = connection->setRelay8(value); break; default: qCWarning(dcWaveshareRelayD8()) << "Invalid channel:" << channel; info->finish(Thing::ThingErrorInvalidParameter); return; } if (!reply) { info->finish(Thing::ThingErrorHardwareFailure); return; } connect(reply, &ModbusRtuReply::finished, info, [reply, info]() { if (reply->error() == ModbusRtuReply::NoError) { info->finish(Thing::ThingErrorNoError); } else { qCWarning(dcWaveshareRelayD8()) << "setRelay write error:" << reply->errorString(); info->finish(Thing::ThingErrorHardwareFailure); } }); } else if (action.actionTypeId() == waveshareRelayD8SetAllRelaysActionTypeId) { quint16 value = action.paramValue(waveshareRelayD8SetAllRelaysActionStateParamTypeId).toBool() ? 1 : 0; QVector values(8, value); // FC0F: write multiple coils at once (addresses 0-7) ModbusRtuReply *reply = connection->modbusRtuMaster()->writeCoils( connection->slaveId(), 0, values); if (!reply) { info->finish(Thing::ThingErrorHardwareFailure); return; } connect(reply, &ModbusRtuReply::finished, info, [reply, info]() { if (reply->error() == ModbusRtuReply::NoError) { info->finish(Thing::ThingErrorNoError); } else { qCWarning(dcWaveshareRelayD8()) << "setAllRelays write error:" << reply->errorString(); info->finish(Thing::ThingErrorHardwareFailure); } }); } else { info->finish(Thing::ThingErrorActionTypeNotFound); } } void IntegrationPluginWaveshareRelayD8::emitInputChangedEvent(Thing *thing, int channel, bool state) { Event event; event.setEventTypeId(waveshareRelayD8InputChangedEventTypeId); event.setThingId(thing->id()); ParamList params; params << Param(waveshareRelayD8InputChangedEventChannelParamTypeId, channel); params << Param(waveshareRelayD8InputChangedEventStateParamTypeId, state); event.setParams(params); emit emitEvent(event); }