added sunspec test server script

pull/6/head
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) {
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<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)
{
qCDebug(dcSunSpec()) << "Write request executed" << requestId << success;

View File

@ -73,6 +73,10 @@ private slots:
void onPluginConfigurationChanged(const ParamTypeId &paramTypeId, const QVariant &value);
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 onWriteRequestError(QUuid requestId, const QString &error);

View File

@ -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"
}
]
},

View File

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

View File

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