From 6383b60ce3e03e3924e4c43b5219747ecd348870 Mon Sep 17 00:00:00 2001 From: Boernsman Date: Tue, 19 Jan 2021 18:12:59 +0100 Subject: [PATCH] added sunspec test server script --- sunspec/integrationpluginsunspec.cpp | 57 ++++++++++++++++--- sunspec/integrationpluginsunspec.h | 4 ++ sunspec/integrationpluginsunspec.json | 71 ++++++++++++++++------- sunspec/sunspec.cpp | 81 ++++++++++++++++++--------- sunspec/sunspec.h | 6 ++ sunspec/test/sunspec_server.sh | 19 +++++++ 6 files changed, 183 insertions(+), 55 deletions(-) create mode 100755 sunspec/test/sunspec_server.sh diff --git a/sunspec/integrationpluginsunspec.cpp b/sunspec/integrationpluginsunspec.cpp index fdd3f2b..127adb5 100644 --- a/sunspec/integrationpluginsunspec.cpp +++ b/sunspec/integrationpluginsunspec.cpp @@ -45,13 +45,16 @@ void IntegrationPluginSunSpec::setupThing(ThingSetupInfo *info) if (thing->thingClassId() == sunspecConnectionThingClassId) { QHostAddress address = QHostAddress(info->thing()->paramValue(sunspecConnectionThingIpAddressParamTypeId).toString()); + int port = info->thing()->paramValue(sunspecConnectionThingPortParamTypeId).toInt(); + //int slaveId = info->thing()->paramValue(sunspecConnectionThingSlaveIdParamTypeId).toInt(); + SunSpec *sunSpec; if (m_sunSpecConnections.contains(thing)) { qCDebug(dcSunSpec()) << "Reconfigure SunSpec connection with new address" << address; sunSpec = m_sunSpecConnections.value(thing); sunSpec->setHostAddress(address); } else { - sunSpec = new SunSpec(address, 502, this); + sunSpec = new SunSpec(address, port, this); m_sunSpecConnections.insert(info->thing(), sunSpec); } @@ -59,18 +62,24 @@ void IntegrationPluginSunSpec::setupThing(ThingSetupInfo *info) qCWarning(dcSunSpec()) << "Error connecting to SunSpec device"; return info->finish(Thing::ThingErrorHardwareNotAvailable); } - connect(sunSpec, &SunSpec::connectionStateChanged, info, [info] (bool status) { - qCDebug(dcSunSpec()) << "Modbus Inverter init finished" << status; - if (status) { + connect(sunSpec, &SunSpec::connectionStateChanged, info, [sunSpec, info] (bool status) { + qCDebug(dcSunSpec()) << "Modbus connection init finished" << status; + sunSpec->findBaseRegister(); + connect(sunSpec, &SunSpec::foundBaseRegister, info, [info] (uint modbusAddress) { + qCDebug(dcSunSpec()) << "Found base register" << modbusAddress; info->finish(Thing::ThingErrorNoError); - } + }); }); connect(info, &ThingSetupInfo::aborted, sunSpec, &SunSpec::deleteLater); connect(sunSpec, &SunSpec::destroyed, [this, info] { m_sunSpecConnections.remove(info->thing()); }); - //connect(sunSpec, &SunSpecInverter::inverterDataReceived, this, &IntegrationPluginSunSpec::onInverterDataReceived); + connect(sunSpec, &SunSpec::connectionStateChanged, this, [thing] (bool status) { + thing->setStateValue(sunspecConnectionThingClassId, status); + }); + connect(sunSpec, &SunSpec::mapHeaderReceived, this, &IntegrationPluginSunSpec::onMapHeaderReceived); + connect(sunSpec, &SunSpec::mapReceived, this, &IntegrationPluginSunSpec::onMapReceived); } else if (thing->thingClassId() == sunspecInverterThingClassId) { QHostAddress address = QHostAddress(info->thing()->paramValue(sunspecInverterThingIpAddressParamTypeId).toString()); @@ -145,7 +154,17 @@ void IntegrationPluginSunSpec::postSetupThing(Thing *thing) connect(m_refreshTimer, &PluginTimer::timeout, this, &IntegrationPluginSunSpec::onRefreshTimer); } - if (thing->thingClassId() == sunspecInverterThingClassId) { + if (thing->thingClassId() == sunspecConnectionThingClassId) { + SunSpec *connection = m_sunSpecConnections.take(thing); + if (!connection) + return; + connection->readCommonMap(); + thing->setParamValue(sunspecConnectionManufacturerStateTypeId, connection->manufacturer()); + thing->setParamValue(sunspecInverterThingSerialNumberParamTypeId, connection->serialNumber()); + thing->setParamValue(sunspecInverterThingDeviceModelParamTypeId, connection->deviceModel()); + + + } else if (thing->thingClassId() == sunspecInverterThingClassId) { SunSpecInverter *sunSpecInverter = m_sunSpecInverters.take(thing); if (!sunSpecInverter) return; @@ -304,6 +323,9 @@ void IntegrationPluginSunSpec::onRefreshTimer() { qCDebug(dcSunSpec()) << "On refresh timer"; //get data + foreach (SunSpec *connections, m_sunSpecConnections) { + connections->readCommonMap(); + } foreach (SunSpecInverter *inverter, m_sunSpecInverters) { inverter->getInverterMap(); } @@ -338,6 +360,27 @@ void IntegrationPluginSunSpec::onConnectionStateChanged(bool status) qCDebug(dcSunSpec()) << "Connection state changed" << status; } +void IntegrationPluginSunSpec::onMapHeaderReceived(uint modbusAddress, SunSpec::BlockId mapId, uint mapLength) +{ + qCDebug(dcSunSpec()) << "On map header received" << modbusAddress << mapId << mapLength; +} + +void IntegrationPluginSunSpec::onMapReceived(SunSpec::BlockId mapId, uint mapLength, QVector data) +{ + Q_UNUSED(data) + SunSpec *connection = static_cast(sender()); + Thing *thing = m_sunSpecConnections.key(connection); + if (!thing) + return; + + qCDebug(dcSunSpec()) << "On map received" << mapId << mapLength; + if (mapId == SunSpec::BlockIdCommon && thing->thingClassId() == sunspecConnectionThingClassId) { + thing->setStateValue(sunspecConnectionManufacturerStateTypeId, connection->manufacturer()); + thing->setStateValue(sunspecConnectionSerialNumberStateTypeId, connection->serialNumber()); + thing->setStateValue(sunspecConnectionDeviceModelStateTypeId, connection->deviceModel()); + } +} + void IntegrationPluginSunSpec::onWriteRequestExecuted(QUuid requestId, bool success) { qCDebug(dcSunSpec()) << "Write request executed" << requestId << success; diff --git a/sunspec/integrationpluginsunspec.h b/sunspec/integrationpluginsunspec.h index 8b46bdb..c4aad1f 100644 --- a/sunspec/integrationpluginsunspec.h +++ b/sunspec/integrationpluginsunspec.h @@ -73,6 +73,10 @@ private slots: void onPluginConfigurationChanged(const ParamTypeId ¶mTypeId, const QVariant &value); void onConnectionStateChanged(bool status); + + void onMapHeaderReceived(uint modbusAddress, SunSpec::BlockId mapId, uint mapLength); + void onMapReceived(SunSpec::BlockId mapId, uint mapLength, QVector data); + void onWriteRequestExecuted(QUuid requestId, bool success); void onWriteRequestError(QUuid requestId, const QString &error); diff --git a/sunspec/integrationpluginsunspec.json b/sunspec/integrationpluginsunspec.json index e07b66b..209da62 100644 --- a/sunspec/integrationpluginsunspec.json +++ b/sunspec/integrationpluginsunspec.json @@ -10,6 +10,23 @@ "type": "int", "unit": "Seconds", "defaultValue": 15 + }, + { + "id": "1a8895a0-c746-48af-9307-3a4636f24cc2", + "name": "timeout", + "displayName": "Timout", + "type": "uint", + "unit": "MilliSeconds", + "defaultValue": 500 + }, + { + "id": "9a4bfe01-315f-4ee7-98a9-f16b08ba12ad", + "name": "numberOfRetries", + "displayName": "Number of retries", + "type": "uint", + "defaultValue": 3, + "minValue": 1, + "maxValue": 10 } ], "vendors": [ @@ -32,28 +49,18 @@ "type": "QString" }, { - "id": "04970315-ed3a-45ce-98fc-35ae3c4eb27b", - "name":"manufacturer", - "displayName": "Manufacturer", - "type": "QString", - "readOnly": true, - "defaultValue": "Unkown" + "id": "1fa4fc9c-f6be-47c7-928a-bcefc1142eec", + "name":"port", + "displayName": "Port", + "type": "int", + "defaultValue": 502 }, { - "id": "58146c26-17d3-458e-a13f-d7f306c20c44", - "name":"deviceModel", - "displayName": "Device model", - "type": "QString", - "readOnly": true, - "defaultValue": "Unkown" - }, - { - "id": "6ed498e1-37ca-4bb7-bac7-463509c7784e", - "name":"serialNumber", - "displayName": "Serial number", - "type": "QString", - "readOnly": true, - "defaultValue": "Unkown" + "id": "953064e0-4675-4538-a9a2-fa22ce2f347c", + "name":"slaveId", + "displayName": "Slave ID", + "type": "int", + "defaultValue": 1 } ], "stateTypes":[ @@ -65,6 +72,30 @@ "type": "bool", "defaultValue": false, "cached": false + }, + { + "id": "04970315-ed3a-45ce-98fc-35ae3c4eb27b", + "name":"manufacturer", + "displayName": "Manufacturer", + "displayNameEvent": "Manufacturer changed", + "type": "QString", + "defaultValue": "Unkown" + }, + { + "id": "58146c26-17d3-458e-a13f-d7f306c20c44", + "name":"deviceModel", + "displayName": "Device model", + "displayNameEvent": "Device model changed", + "type": "QString", + "defaultValue": "Unkown" + }, + { + "id": "6ed498e1-37ca-4bb7-bac7-463509c7784e", + "name":"serialNumber", + "displayName": "Serial number", + "displayNameEvent": "Serial number changed", + "type": "QString", + "defaultValue": "Unkown" } ] }, diff --git a/sunspec/sunspec.cpp b/sunspec/sunspec.cpp index 694e807..1947a8a 100644 --- a/sunspec/sunspec.cpp +++ b/sunspec/sunspec.cpp @@ -52,7 +52,7 @@ SunSpec::~SunSpec() bool SunSpec::connectModbus() { - return m_modbusTcpClient->connectDevice();; + return m_modbusTcpClient->connectDevice(); } void SunSpec::setHostAddress(const QHostAddress &hostAddress) @@ -63,6 +63,27 @@ void SunSpec::setHostAddress(const QHostAddress &hostAddress) } } +void SunSpec::setPort(uint port) +{ + m_port = port; + m_modbusTcpClient->setConnectionParameter(QModbusDevice::NetworkPortParameter, port); +} + +void SunSpec::setSlaveId(uint slaveId) +{ + m_slaveId = slaveId; +} + +void SunSpec::setTimeout(uint milliSeconds) +{ + m_modbusTcpClient->setTimeout(milliSeconds); +} + +void SunSpec::setNumberOfRetries(uint retries) +{ + m_modbusTcpClient->setNumberOfRetries(retries); +} + QString SunSpec::manufacturer() { return m_manufacturer; @@ -80,39 +101,43 @@ QString SunSpec::serialNumber() void SunSpec::findBaseRegister() { - qCDebug(dcSunSpec()) << "find base register"; + qCDebug(dcSunSpec()) << "Find base register"; + QList validBaseRegisters; + validBaseRegisters.append(0); + validBaseRegisters.append(40000); + validBaseRegisters.append(50000); - QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, 40000, 2); + Q_FOREACH (int baseRegister, validBaseRegisters) { + QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, baseRegister, 2); + if (QModbusReply *reply = m_modbusTcpClient->sendReadRequest(request, m_slaveId)) { + if (!reply->isFinished()) { + connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); + connect(reply, &QModbusReply::finished, this, [reply, this] { - if (QModbusReply *reply = m_modbusTcpClient->sendReadRequest(request, m_slaveId)) { - if (!reply->isFinished()) { - connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); - connect(reply, &QModbusReply::finished, this, [reply, this] { - - if (reply->error() == QModbusDevice::NoError) { - const QModbusDataUnit unit = reply->result(); - //uint modbusAddress = unit.startAddress(); TODO - if ((unit.value(0) << 16 | unit.value(1)) == 0x53756e53) { - //Well-known value. Uniquely identifies this as a SunSpec Modbus Map - qCDebug(dcSunSpec()) << "Found start of modbus map"; - //emit foundBaseRegister(modbusAddress); + if (reply->error() == QModbusDevice::NoError) { + const QModbusDataUnit unit = reply->result(); + uint modbusAddress = unit.startAddress(); + if ((unit.value(0) << 16 | unit.value(1)) == 0x53756e53) { + //Well-known value. Uniquely identifies this as a SunSpec Modbus Map + qCDebug(dcSunSpec()) << "Found start of modbus map" << modbusAddress; + m_baseRegister = modbusAddress; + emit foundBaseRegister(modbusAddress); + } else { + qCWarning(dcSunSpec()) << "Got reply on base register, but value didn't mach 0x53756e53"; + } + } else { + qCWarning(dcSunSpec()) << "Find base register read response error:" << reply->error(); } - } else { - qCWarning(dcSunSpec()) << "Read response error:" << reply->error(); - } - }); - connect(reply, &QModbusReply::errorOccurred, this, [reply] (QModbusDevice::Error error) { - qCWarning(dcSunSpec()) << "Modbus reply error:" << error; - reply->finished(); // To make sure it will be deleted - }); + }); + } else { + qCWarning(dcSunSpec()) << "Find base register eead error: " << m_modbusTcpClient->errorString(); + delete reply; // broadcast replies return immediately + return; + } } else { - qCWarning(dcSunSpec()) << "Read error: " << m_modbusTcpClient->errorString(); - delete reply; // broadcast replies return immediately + qCWarning(dcSunSpec()) << "Find base register read error: " << m_modbusTcpClient->errorString(); return; } - } else { - qCWarning(dcSunSpec()) << "Read error: " << m_modbusTcpClient->errorString(); - return; } } diff --git a/sunspec/sunspec.h b/sunspec/sunspec.h index 5a84ebe..73d75de 100644 --- a/sunspec/sunspec.h +++ b/sunspec/sunspec.h @@ -173,6 +173,11 @@ public: ~SunSpec(); bool connectModbus(); void setHostAddress(const QHostAddress &hostAddress); + void setPort(uint port); + void setSlaveId(uint slaveId); + void setTimeout(uint milliSeconds); + void setNumberOfRetries(uint retries); + QString manufacturer(); QString deviceModel(); QString serialNumber(); @@ -207,6 +212,7 @@ signals: void connectionStateChanged(bool status); void requestExecuted(QUuid requetId, bool success); + void foundBaseRegister(int modbusAddress); void foundModbusMap(BlockId mapId, int modbusStartRegister); void modbusMapSearchFinished(const QList &mapIds, uint modbusStartRegister, const QString &error); diff --git a/sunspec/test/sunspec_server.sh b/sunspec/test/sunspec_server.sh new file mode 100755 index 0000000..fb09118 --- /dev/null +++ b/sunspec/test/sunspec_server.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +if ! command -v suns &> /dev/null +then + echo "suns could not be found" + echo "... installing" + sudo apt update + sudo apt install libmodbus-dev flex bison git + + if [ ! -d "sunspec"]; then + git clone https://github.com/Boernsman/sunspec.git + fi + cd ./sunspec/src + make + sudo make install +fi + +echo "Startin sunspec test server" +sudo suns -s -vvvv -m models/test/composite_superdevice.model