added sunspec test server script

master
Boernsman 2021-01-19 18:12:59 +01:00
parent 0e3abab229
commit 6383b60ce3
6 changed files with 183 additions and 55 deletions

View File

@ -45,13 +45,16 @@ void IntegrationPluginSunSpec::setupThing(ThingSetupInfo *info)
if (thing->thingClassId() == sunspecConnectionThingClassId) { if (thing->thingClassId() == sunspecConnectionThingClassId) {
QHostAddress address = QHostAddress(info->thing()->paramValue(sunspecConnectionThingIpAddressParamTypeId).toString()); QHostAddress address = QHostAddress(info->thing()->paramValue(sunspecConnectionThingIpAddressParamTypeId).toString());
int port = info->thing()->paramValue(sunspecConnectionThingPortParamTypeId).toInt();
//int slaveId = info->thing()->paramValue(sunspecConnectionThingSlaveIdParamTypeId).toInt();
SunSpec *sunSpec; SunSpec *sunSpec;
if (m_sunSpecConnections.contains(thing)) { if (m_sunSpecConnections.contains(thing)) {
qCDebug(dcSunSpec()) << "Reconfigure SunSpec connection with new address" << address; qCDebug(dcSunSpec()) << "Reconfigure SunSpec connection with new address" << address;
sunSpec = m_sunSpecConnections.value(thing); sunSpec = m_sunSpecConnections.value(thing);
sunSpec->setHostAddress(address); sunSpec->setHostAddress(address);
} else { } else {
sunSpec = new SunSpec(address, 502, this); sunSpec = new SunSpec(address, port, this);
m_sunSpecConnections.insert(info->thing(), sunSpec); m_sunSpecConnections.insert(info->thing(), sunSpec);
} }
@ -59,18 +62,24 @@ void IntegrationPluginSunSpec::setupThing(ThingSetupInfo *info)
qCWarning(dcSunSpec()) << "Error connecting to SunSpec device"; qCWarning(dcSunSpec()) << "Error connecting to SunSpec device";
return info->finish(Thing::ThingErrorHardwareNotAvailable); return info->finish(Thing::ThingErrorHardwareNotAvailable);
} }
connect(sunSpec, &SunSpec::connectionStateChanged, info, [info] (bool status) { connect(sunSpec, &SunSpec::connectionStateChanged, info, [sunSpec, info] (bool status) {
qCDebug(dcSunSpec()) << "Modbus Inverter init finished" << status; qCDebug(dcSunSpec()) << "Modbus connection init finished" << status;
if (status) { sunSpec->findBaseRegister();
connect(sunSpec, &SunSpec::foundBaseRegister, info, [info] (uint modbusAddress) {
qCDebug(dcSunSpec()) << "Found base register" << modbusAddress;
info->finish(Thing::ThingErrorNoError); info->finish(Thing::ThingErrorNoError);
} });
}); });
connect(info, &ThingSetupInfo::aborted, sunSpec, &SunSpec::deleteLater); connect(info, &ThingSetupInfo::aborted, sunSpec, &SunSpec::deleteLater);
connect(sunSpec, &SunSpec::destroyed, [this, info] { connect(sunSpec, &SunSpec::destroyed, [this, info] {
m_sunSpecConnections.remove(info->thing()); 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) { } else if (thing->thingClassId() == sunspecInverterThingClassId) {
QHostAddress address = QHostAddress(info->thing()->paramValue(sunspecInverterThingIpAddressParamTypeId).toString()); 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); 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); SunSpecInverter *sunSpecInverter = m_sunSpecInverters.take(thing);
if (!sunSpecInverter) if (!sunSpecInverter)
return; return;
@ -304,6 +323,9 @@ void IntegrationPluginSunSpec::onRefreshTimer()
{ {
qCDebug(dcSunSpec()) << "On refresh timer"; qCDebug(dcSunSpec()) << "On refresh timer";
//get data //get data
foreach (SunSpec *connections, m_sunSpecConnections) {
connections->readCommonMap();
}
foreach (SunSpecInverter *inverter, m_sunSpecInverters) { foreach (SunSpecInverter *inverter, m_sunSpecInverters) {
inverter->getInverterMap(); inverter->getInverterMap();
} }
@ -338,6 +360,27 @@ void IntegrationPluginSunSpec::onConnectionStateChanged(bool status)
qCDebug(dcSunSpec()) << "Connection state changed" << 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<quint16> data)
{
Q_UNUSED(data)
SunSpec *connection = static_cast<SunSpec *>(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) void IntegrationPluginSunSpec::onWriteRequestExecuted(QUuid requestId, bool success)
{ {
qCDebug(dcSunSpec()) << "Write request executed" << requestId << success; qCDebug(dcSunSpec()) << "Write request executed" << requestId << success;

View File

@ -73,6 +73,10 @@ private slots:
void onPluginConfigurationChanged(const ParamTypeId &paramTypeId, const QVariant &value); void onPluginConfigurationChanged(const ParamTypeId &paramTypeId, const QVariant &value);
void onConnectionStateChanged(bool status); void onConnectionStateChanged(bool status);
void onMapHeaderReceived(uint modbusAddress, SunSpec::BlockId mapId, uint mapLength);
void onMapReceived(SunSpec::BlockId mapId, uint mapLength, QVector<quint16> data);
void onWriteRequestExecuted(QUuid requestId, bool success); void onWriteRequestExecuted(QUuid requestId, bool success);
void onWriteRequestError(QUuid requestId, const QString &error); void onWriteRequestError(QUuid requestId, const QString &error);

View File

@ -10,6 +10,23 @@
"type": "int", "type": "int",
"unit": "Seconds", "unit": "Seconds",
"defaultValue": 15 "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": [ "vendors": [
@ -32,28 +49,18 @@
"type": "QString" "type": "QString"
}, },
{ {
"id": "04970315-ed3a-45ce-98fc-35ae3c4eb27b", "id": "1fa4fc9c-f6be-47c7-928a-bcefc1142eec",
"name":"manufacturer", "name":"port",
"displayName": "Manufacturer", "displayName": "Port",
"type": "QString", "type": "int",
"readOnly": true, "defaultValue": 502
"defaultValue": "Unkown"
}, },
{ {
"id": "58146c26-17d3-458e-a13f-d7f306c20c44", "id": "953064e0-4675-4538-a9a2-fa22ce2f347c",
"name":"deviceModel", "name":"slaveId",
"displayName": "Device model", "displayName": "Slave ID",
"type": "QString", "type": "int",
"readOnly": true, "defaultValue": 1
"defaultValue": "Unkown"
},
{
"id": "6ed498e1-37ca-4bb7-bac7-463509c7784e",
"name":"serialNumber",
"displayName": "Serial number",
"type": "QString",
"readOnly": true,
"defaultValue": "Unkown"
} }
], ],
"stateTypes":[ "stateTypes":[
@ -65,6 +72,30 @@
"type": "bool", "type": "bool",
"defaultValue": false, "defaultValue": false,
"cached": 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"
} }
] ]
}, },

View File

@ -52,7 +52,7 @@ SunSpec::~SunSpec()
bool SunSpec::connectModbus() bool SunSpec::connectModbus()
{ {
return m_modbusTcpClient->connectDevice();; return m_modbusTcpClient->connectDevice();
} }
void SunSpec::setHostAddress(const QHostAddress &hostAddress) 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() QString SunSpec::manufacturer()
{ {
return m_manufacturer; return m_manufacturer;
@ -80,39 +101,43 @@ QString SunSpec::serialNumber()
void SunSpec::findBaseRegister() void SunSpec::findBaseRegister()
{ {
qCDebug(dcSunSpec()) << "find base register"; qCDebug(dcSunSpec()) << "Find base register";
QList<int> 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->error() == QModbusDevice::NoError) {
if (!reply->isFinished()) { const QModbusDataUnit unit = reply->result();
connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); uint modbusAddress = unit.startAddress();
connect(reply, &QModbusReply::finished, this, [reply, this] { if ((unit.value(0) << 16 | unit.value(1)) == 0x53756e53) {
//Well-known value. Uniquely identifies this as a SunSpec Modbus Map
if (reply->error() == QModbusDevice::NoError) { qCDebug(dcSunSpec()) << "Found start of modbus map" << modbusAddress;
const QModbusDataUnit unit = reply->result(); m_baseRegister = modbusAddress;
//uint modbusAddress = unit.startAddress(); TODO emit foundBaseRegister(modbusAddress);
if ((unit.value(0) << 16 | unit.value(1)) == 0x53756e53) { } else {
//Well-known value. Uniquely identifies this as a SunSpec Modbus Map qCWarning(dcSunSpec()) << "Got reply on base register, but value didn't mach 0x53756e53";
qCDebug(dcSunSpec()) << "Found start of modbus map"; }
//emit foundBaseRegister(modbusAddress); } else {
qCWarning(dcSunSpec()) << "Find base register read response error:" << reply->error();
} }
} else { });
qCWarning(dcSunSpec()) << "Read response error:" << reply->error(); } else {
} qCWarning(dcSunSpec()) << "Find base register eead error: " << m_modbusTcpClient->errorString();
}); delete reply; // broadcast replies return immediately
connect(reply, &QModbusReply::errorOccurred, this, [reply] (QModbusDevice::Error error) { return;
qCWarning(dcSunSpec()) << "Modbus reply error:" << error; }
reply->finished(); // To make sure it will be deleted
});
} else { } else {
qCWarning(dcSunSpec()) << "Read error: " << m_modbusTcpClient->errorString(); qCWarning(dcSunSpec()) << "Find base register read error: " << m_modbusTcpClient->errorString();
delete reply; // broadcast replies return immediately
return; return;
} }
} else {
qCWarning(dcSunSpec()) << "Read error: " << m_modbusTcpClient->errorString();
return;
} }
} }

View File

@ -173,6 +173,11 @@ public:
~SunSpec(); ~SunSpec();
bool connectModbus(); bool connectModbus();
void setHostAddress(const QHostAddress &hostAddress); void setHostAddress(const QHostAddress &hostAddress);
void setPort(uint port);
void setSlaveId(uint slaveId);
void setTimeout(uint milliSeconds);
void setNumberOfRetries(uint retries);
QString manufacturer(); QString manufacturer();
QString deviceModel(); QString deviceModel();
QString serialNumber(); QString serialNumber();
@ -207,6 +212,7 @@ signals:
void connectionStateChanged(bool status); void connectionStateChanged(bool status);
void requestExecuted(QUuid requetId, bool success); void requestExecuted(QUuid requetId, bool success);
void foundBaseRegister(int modbusAddress);
void foundModbusMap(BlockId mapId, int modbusStartRegister); void foundModbusMap(BlockId mapId, int modbusStartRegister);
void modbusMapSearchFinished(const QList<BlockId> &mapIds, uint modbusStartRegister, const QString &error); void modbusMapSearchFinished(const QList<BlockId> &mapIds, uint modbusStartRegister, const QString &error);

19
sunspec/test/sunspec_server.sh Executable file
View File

@ -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