Merge PR #21: New Plugin: M-Tec
This commit is contained in:
commit
b540f72760
16
debian/control
vendored
16
debian/control
vendored
@ -94,6 +94,22 @@ Description: nymea.io plugin for my-pv heating rods
|
||||
This package will install the nymea.io plugin for my-pv
|
||||
|
||||
|
||||
Package: nymea-plugin-mtec
|
||||
Architecture: any
|
||||
Section: libs
|
||||
Depends: ${shlibs:Depends},
|
||||
${misc:Depends},
|
||||
nymea-plugins-modbus-translations
|
||||
Description: nymea.io plugin for M-TEC heat pumps
|
||||
The nymea daemon is a plugin based IoT (Internet of Things) server. The
|
||||
server works like a translator for devices, things and services and
|
||||
allows them to interact.
|
||||
With the powerful rule engine you are able to connect any device available
|
||||
in the system and create individual scenes and behaviors for your environment.
|
||||
.
|
||||
This package will install the nymea.io plugin for M-TEC heat pumps
|
||||
|
||||
|
||||
Package: nymea-plugin-sunspec
|
||||
Architecture: any
|
||||
Depends: ${shlibs:Depends},
|
||||
|
||||
1
debian/nymea-plugin-mtec.install.in
vendored
Normal file
1
debian/nymea-plugin-mtec.install.in
vendored
Normal file
@ -0,0 +1 @@
|
||||
usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginmtec.so
|
||||
@ -34,59 +34,131 @@
|
||||
NYMEA_LOGGING_CATEGORY(dcModbusTCP, "ModbusTCP")
|
||||
|
||||
ModbusTCPMaster::ModbusTCPMaster(const QHostAddress &hostAddress, uint port, QObject *parent) :
|
||||
QObject(parent)
|
||||
QObject(parent),
|
||||
m_hostAddress(hostAddress),
|
||||
m_port(port)
|
||||
{
|
||||
m_modbusTcpClient = new QModbusTcpClient(this);
|
||||
m_modbusTcpClient->setConnectionParameter(QModbusDevice::NetworkPortParameter, port);
|
||||
m_modbusTcpClient->setConnectionParameter(QModbusDevice::NetworkAddressParameter, hostAddress.toString());
|
||||
m_modbusTcpClient->setTimeout(1000);
|
||||
m_modbusTcpClient->setNumberOfRetries(3);
|
||||
m_modbusTcpClient->setConnectionParameter(QModbusDevice::NetworkPortParameter, m_port);
|
||||
m_modbusTcpClient->setConnectionParameter(QModbusDevice::NetworkAddressParameter, m_hostAddress.toString());
|
||||
m_modbusTcpClient->setTimeout(m_timeout);
|
||||
m_modbusTcpClient->setNumberOfRetries(m_numberOfRetries);
|
||||
|
||||
connect(m_modbusTcpClient, &QModbusTcpClient::stateChanged, this, &ModbusTCPMaster::onModbusStateChanged);
|
||||
connect(m_modbusTcpClient, &QModbusRtuSerialMaster::errorOccurred, this, &ModbusTCPMaster::onModbusErrorOccurred);
|
||||
|
||||
m_reconnectTimer = new QTimer(this);
|
||||
m_reconnectTimer->setSingleShot(true);
|
||||
connect(m_reconnectTimer, &QTimer::timeout, this, &ModbusTCPMaster::onReconnectTimer);
|
||||
m_reconnectTimer->setInterval(4000);
|
||||
connect(m_reconnectTimer, &QTimer::timeout, this, &ModbusTCPMaster::connectDevice);
|
||||
}
|
||||
|
||||
ModbusTCPMaster::~ModbusTCPMaster()
|
||||
{
|
||||
if (!m_modbusTcpClient) {
|
||||
m_modbusTcpClient->disconnectDevice();
|
||||
m_modbusTcpClient->deleteLater();
|
||||
}
|
||||
if (!m_reconnectTimer) {
|
||||
if (m_reconnectTimer) {
|
||||
m_reconnectTimer->stop();
|
||||
m_reconnectTimer->deleteLater();
|
||||
}
|
||||
|
||||
if (m_modbusTcpClient) {
|
||||
disconnectDevice();
|
||||
}
|
||||
}
|
||||
|
||||
QHostAddress ModbusTCPMaster::hostAddress() const
|
||||
{
|
||||
return m_hostAddress;
|
||||
}
|
||||
|
||||
uint ModbusTCPMaster::port() const
|
||||
{
|
||||
return m_port;
|
||||
}
|
||||
|
||||
void ModbusTCPMaster::setPort(uint port)
|
||||
{
|
||||
m_port = port;
|
||||
}
|
||||
|
||||
void ModbusTCPMaster::setHostAddress(const QHostAddress &hostAddress)
|
||||
{
|
||||
m_hostAddress = hostAddress;
|
||||
}
|
||||
|
||||
bool ModbusTCPMaster::connectDevice() {
|
||||
// TCP connection to target device
|
||||
qCDebug(dcModbusTCP()) << "Setting up TCP connecion";
|
||||
|
||||
if (!m_modbusTcpClient)
|
||||
return false;
|
||||
|
||||
return m_modbusTcpClient->connectDevice();
|
||||
// Only connect if we are in the unconnected state
|
||||
if (m_modbusTcpClient->state() == QModbusDevice::UnconnectedState) {
|
||||
qCDebug(dcModbusTCP()) << "Connecting modbus TCP client to" << QString("%1:%2").arg(m_hostAddress.toString()).arg(m_port);
|
||||
m_modbusTcpClient->setConnectionParameter(QModbusDevice::NetworkPortParameter, m_port);
|
||||
m_modbusTcpClient->setConnectionParameter(QModbusDevice::NetworkAddressParameter, m_hostAddress.toString());
|
||||
m_modbusTcpClient->setTimeout(m_timeout);
|
||||
m_modbusTcpClient->setNumberOfRetries(m_numberOfRetries);
|
||||
return m_modbusTcpClient->connectDevice();
|
||||
} else if (m_modbusTcpClient->state() != QModbusDevice::ConnectedState) {
|
||||
// Restart the timer in case of connecting not finished yet or closing
|
||||
m_reconnectTimer->start();
|
||||
} else {
|
||||
qCWarning(dcModbusTCP()) << "Connect modbus TCP device" << QString("%1:%2").arg(m_hostAddress.toString()).arg(m_port) << "called, but the socket is currently in the" << m_modbusTcpClient->state();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ModbusTCPMaster::connected()
|
||||
void ModbusTCPMaster::disconnectDevice()
|
||||
{
|
||||
return (m_modbusTcpClient->state() == QModbusDevice::State::ConnectedState);
|
||||
if (!m_modbusTcpClient)
|
||||
return;
|
||||
|
||||
// Stop the reconnect timer since disconnect was explicitly called
|
||||
m_reconnectTimer->stop();
|
||||
m_modbusTcpClient->disconnectDevice();
|
||||
}
|
||||
|
||||
bool ModbusTCPMaster::reconnectDevice()
|
||||
{
|
||||
qCWarning(dcModbusTCP()) << "Reconnecting modbus TCP device" << QString("%1:%2").arg(m_hostAddress.toString()).arg(m_port);
|
||||
if (!m_modbusTcpClient)
|
||||
return false;
|
||||
|
||||
disconnectDevice();
|
||||
return connectDevice();
|
||||
}
|
||||
|
||||
bool ModbusTCPMaster::connected() const
|
||||
{
|
||||
return m_connected;
|
||||
}
|
||||
|
||||
int ModbusTCPMaster::numberOfRetries() const
|
||||
{
|
||||
return m_modbusTcpClient->numberOfRetries();
|
||||
}
|
||||
|
||||
void ModbusTCPMaster::setNumberOfRetries(int number)
|
||||
{
|
||||
m_numberOfRetries = number;
|
||||
m_modbusTcpClient->setNumberOfRetries(number);
|
||||
}
|
||||
|
||||
int ModbusTCPMaster::timeout() const
|
||||
{
|
||||
return m_modbusTcpClient->timeout();
|
||||
}
|
||||
|
||||
void ModbusTCPMaster::setTimeout(int timeout)
|
||||
{
|
||||
m_timeout = timeout;
|
||||
m_modbusTcpClient->setTimeout(timeout);
|
||||
}
|
||||
|
||||
int ModbusTCPMaster::timeout()
|
||||
{
|
||||
return m_modbusTcpClient->timeout();
|
||||
}
|
||||
|
||||
QString ModbusTCPMaster::errorString() const
|
||||
{
|
||||
return m_modbusTcpClient->errorString();
|
||||
@ -97,74 +169,44 @@ QModbusDevice::Error ModbusTCPMaster::error() const
|
||||
return m_modbusTcpClient->error();
|
||||
}
|
||||
|
||||
uint ModbusTCPMaster::port()
|
||||
{
|
||||
return m_modbusTcpClient->connectionParameter(QModbusDevice::NetworkPortParameter).toUInt();
|
||||
}
|
||||
|
||||
bool ModbusTCPMaster::setHostAddress(const QHostAddress &hostAddress)
|
||||
{
|
||||
m_modbusTcpClient->setConnectionParameter(QModbusDevice::NetworkAddressParameter, hostAddress.toString());
|
||||
return connectDevice();
|
||||
}
|
||||
|
||||
bool ModbusTCPMaster::setPort(uint port)
|
||||
{
|
||||
m_modbusTcpClient->setConnectionParameter(QModbusDevice::NetworkPortParameter, port);
|
||||
return connectDevice();
|
||||
}
|
||||
|
||||
void ModbusTCPMaster::onReconnectTimer()
|
||||
{
|
||||
if(!m_modbusTcpClient->connectDevice()) {
|
||||
m_reconnectTimer->start(10000);
|
||||
}
|
||||
}
|
||||
|
||||
QHostAddress ModbusTCPMaster::hostAddress()
|
||||
{
|
||||
return QHostAddress(m_modbusTcpClient->connectionParameter(QModbusDevice::NetworkAddressParameter).toString());
|
||||
}
|
||||
|
||||
QUuid ModbusTCPMaster::readCoil(uint slaveAddress, uint registerAddress, uint size)
|
||||
{
|
||||
if (!m_modbusTcpClient) {
|
||||
return "";
|
||||
return QUuid();
|
||||
}
|
||||
QUuid requestId = QUuid::createUuid();
|
||||
|
||||
QUuid requestId = QUuid::createUuid();
|
||||
QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::Coils, registerAddress, size);
|
||||
|
||||
if (QModbusReply *reply = m_modbusTcpClient->sendReadRequest(request, slaveAddress)) {
|
||||
if (!reply->isFinished()) {
|
||||
connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
|
||||
connect(reply, &QModbusReply::finished, this, [reply, requestId, this] {
|
||||
|
||||
if (reply->error() == QModbusDevice::NoError) {
|
||||
emit readRequestExecuted(requestId, true);
|
||||
const QModbusDataUnit unit = reply->result();
|
||||
uint modbusAddress = unit.startAddress();
|
||||
emit receivedCoil(reply->serverAddress(), modbusAddress, unit.values());
|
||||
|
||||
} else {
|
||||
emit readRequestExecuted(requestId, false);
|
||||
qCWarning(dcModbusTCP()) << "Read response error:" << reply->error();
|
||||
}
|
||||
});
|
||||
connect(reply, &QModbusReply::errorOccurred, this, [reply, requestId, this] (QModbusDevice::Error error){
|
||||
|
||||
connect(reply, &QModbusReply::errorOccurred, this, [reply, requestId, this] (QModbusDevice::Error error){
|
||||
qCWarning(dcModbusTCP()) << "Modbus reply error:" << error;
|
||||
emit readRequestError(requestId, reply->errorString());
|
||||
reply->finished(); // To make sure it will be deleted
|
||||
emit reply->finished(); // To make sure it will be deleted
|
||||
});
|
||||
|
||||
QTimer::singleShot(200, reply, &QModbusReply::deleteLater);
|
||||
} else {
|
||||
delete reply; // broadcast replies return immediately
|
||||
return "";
|
||||
return QUuid();
|
||||
}
|
||||
} else {
|
||||
qCWarning(dcModbusTCP()) << "Read error: " << m_modbusTcpClient->errorString();
|
||||
return "";
|
||||
return QUuid();
|
||||
}
|
||||
return requestId;
|
||||
}
|
||||
@ -172,51 +214,70 @@ QUuid ModbusTCPMaster::readCoil(uint slaveAddress, uint registerAddress, uint si
|
||||
QUuid ModbusTCPMaster::writeHoldingRegisters(uint slaveAddress, uint registerAddress, const QVector<quint16> &values)
|
||||
{
|
||||
if (!m_modbusTcpClient) {
|
||||
return "";
|
||||
return QUuid();
|
||||
}
|
||||
|
||||
QUuid requestId = QUuid::createUuid();
|
||||
QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, registerAddress, values.length());
|
||||
request.setValues(values);
|
||||
|
||||
if (QModbusReply *reply = m_modbusTcpClient->sendWriteRequest(request, slaveAddress)) {
|
||||
if (!reply->isFinished()) {
|
||||
connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
|
||||
connect(reply, &QModbusReply::finished, this, [reply, requestId, this] {
|
||||
|
||||
if (reply->error() == QModbusDevice::NoError) {
|
||||
writeRequestExecuted(requestId, true);
|
||||
emit writeRequestExecuted(requestId, true);
|
||||
const QModbusDataUnit unit = reply->result();
|
||||
uint modbusAddress = unit.startAddress();
|
||||
emit receivedHoldingRegister(reply->serverAddress(), modbusAddress, unit.values());
|
||||
|
||||
} else {
|
||||
writeRequestExecuted(requestId, false);
|
||||
emit writeRequestExecuted(requestId, false);
|
||||
qCWarning(dcModbusTCP()) << "Read response error:" << reply->error();
|
||||
}
|
||||
reply->deleteLater();
|
||||
});
|
||||
connect(reply, &QModbusReply::errorOccurred, this, [reply, requestId, this] (QModbusDevice::Error error){
|
||||
|
||||
connect(reply, &QModbusReply::errorOccurred, this, [reply, requestId, this] (QModbusDevice::Error error){
|
||||
qCWarning(dcModbusTCP()) << "Modbus replay error:" << error;
|
||||
emit writeRequestError(requestId, reply->errorString());
|
||||
reply->finished(); // To make sure it will be deleted
|
||||
emit reply->finished(); // To make sure it will be deleted
|
||||
});
|
||||
|
||||
QTimer::singleShot(2000, reply, &QModbusReply::deleteLater);
|
||||
} else {
|
||||
delete reply; // broadcast replies return immediately
|
||||
return "";
|
||||
return QUuid();
|
||||
}
|
||||
} else {
|
||||
qCWarning(dcModbusTCP()) << "Read error: " << m_modbusTcpClient->errorString();
|
||||
return "";
|
||||
return QUuid();
|
||||
}
|
||||
return requestId;
|
||||
}
|
||||
|
||||
QModbusReply *ModbusTCPMaster::sendRawRequest(const QModbusRequest &request, int serverAddress)
|
||||
{
|
||||
return m_modbusTcpClient->sendRawRequest(request, serverAddress);
|
||||
}
|
||||
|
||||
QModbusReply *ModbusTCPMaster::sendReadRequest(const QModbusDataUnit &read, int serverAddress)
|
||||
{
|
||||
return m_modbusTcpClient->sendReadRequest(read, serverAddress);
|
||||
}
|
||||
|
||||
QModbusReply *ModbusTCPMaster::sendReadWriteRequest(const QModbusDataUnit &read, const QModbusDataUnit &write, int serverAddress)
|
||||
{
|
||||
return m_modbusTcpClient->sendReadWriteRequest(read, write, serverAddress);
|
||||
}
|
||||
|
||||
QModbusReply *ModbusTCPMaster::sendWriteRequest(const QModbusDataUnit &write, int serverAddress)
|
||||
{
|
||||
return m_modbusTcpClient->sendWriteRequest(write, serverAddress);
|
||||
}
|
||||
|
||||
QUuid ModbusTCPMaster::readDiscreteInput(uint slaveAddress, uint registerAddress, uint size)
|
||||
{
|
||||
if (!m_modbusTcpClient) {
|
||||
return "";
|
||||
return QUuid();
|
||||
}
|
||||
QUuid requestId = QUuid::createUuid();
|
||||
|
||||
@ -226,33 +287,32 @@ QUuid ModbusTCPMaster::readDiscreteInput(uint slaveAddress, uint registerAddress
|
||||
if (!reply->isFinished()) {
|
||||
connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
|
||||
connect(reply, &QModbusReply::finished, this, [reply, requestId, this] {
|
||||
|
||||
if (reply->error() == QModbusDevice::NoError) {
|
||||
emit readRequestExecuted(requestId, true);
|
||||
const QModbusDataUnit unit = reply->result();
|
||||
uint modbusAddress = unit.startAddress();
|
||||
emit receivedDiscreteInput(reply->serverAddress(), modbusAddress, unit.values());
|
||||
|
||||
} else {
|
||||
emit readRequestExecuted(requestId, false);
|
||||
qCWarning(dcModbusTCP()) << "Read response error:" << reply->error();
|
||||
}
|
||||
});
|
||||
connect(reply, &QModbusReply::errorOccurred, this, [requestId, this] (QModbusDevice::Error error){
|
||||
|
||||
connect(reply, &QModbusReply::errorOccurred, this, [requestId, this] (QModbusDevice::Error error){
|
||||
qCWarning(dcModbusTCP()) << "Modbus replay error:" << error;
|
||||
QModbusReply *reply = qobject_cast<QModbusReply *>(sender());
|
||||
emit readRequestError(requestId, reply->errorString());
|
||||
reply->finished(); // To make sure it will be deleted
|
||||
emit reply->finished(); // To make sure it will be deleted
|
||||
});
|
||||
|
||||
QTimer::singleShot(2000, reply, &QModbusReply::deleteLater);
|
||||
} else {
|
||||
delete reply; // broadcast replies return immediately
|
||||
return "";
|
||||
return QUuid();
|
||||
}
|
||||
} else {
|
||||
qCWarning(dcModbusTCP()) << "Read error: " << m_modbusTcpClient->errorString();
|
||||
return "";
|
||||
return QUuid();
|
||||
}
|
||||
return requestId;
|
||||
}
|
||||
@ -260,10 +320,10 @@ QUuid ModbusTCPMaster::readDiscreteInput(uint slaveAddress, uint registerAddress
|
||||
QUuid ModbusTCPMaster::readInputRegister(uint slaveAddress, uint registerAddress, uint size)
|
||||
{
|
||||
if (!m_modbusTcpClient) {
|
||||
return "";
|
||||
return QUuid();
|
||||
}
|
||||
QUuid requestId = QUuid::createUuid();
|
||||
|
||||
QUuid requestId = QUuid::createUuid();
|
||||
QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::InputRegisters, registerAddress, size);
|
||||
|
||||
if (QModbusReply *reply = m_modbusTcpClient->sendReadRequest(request, slaveAddress)) {
|
||||
@ -276,26 +336,27 @@ QUuid ModbusTCPMaster::readInputRegister(uint slaveAddress, uint registerAddress
|
||||
const QModbusDataUnit unit = reply->result();
|
||||
uint modbusAddress = unit.startAddress();
|
||||
emit receivedInputRegister(reply->serverAddress(), modbusAddress, unit.values());
|
||||
|
||||
} else {
|
||||
emit readRequestExecuted(requestId, false);
|
||||
qCWarning(dcModbusTCP()) << "Read response error:" << reply->error();
|
||||
}
|
||||
});
|
||||
connect(reply, &QModbusReply::errorOccurred, this, [reply, requestId, this] (QModbusDevice::Error error){
|
||||
|
||||
connect(reply, &QModbusReply::errorOccurred, this, [reply, requestId, this] (QModbusDevice::Error error){
|
||||
qCWarning(dcModbusTCP()) << "Modbus reply error:" << error;
|
||||
emit readRequestError(requestId, reply->errorString());
|
||||
reply->finished(); // To make sure it will be deleted
|
||||
emit reply->finished(); // To make sure it will be deleted
|
||||
});
|
||||
|
||||
QTimer::singleShot(2000, reply, &QModbusReply::deleteLater);
|
||||
|
||||
} else {
|
||||
delete reply; // broadcast replies return immediately
|
||||
return "";
|
||||
return QUuid();
|
||||
}
|
||||
} else {
|
||||
qCWarning(dcModbusTCP()) << "Read error: " << m_modbusTcpClient->errorString();
|
||||
return "";
|
||||
return QUuid();
|
||||
}
|
||||
return requestId;
|
||||
}
|
||||
@ -303,10 +364,10 @@ QUuid ModbusTCPMaster::readInputRegister(uint slaveAddress, uint registerAddress
|
||||
QUuid ModbusTCPMaster::readHoldingRegister(uint slaveAddress, uint registerAddress, uint size)
|
||||
{
|
||||
if (!m_modbusTcpClient) {
|
||||
return "";
|
||||
return QUuid();
|
||||
}
|
||||
QUuid requestId = QUuid::createUuid();
|
||||
|
||||
QUuid requestId = QUuid::createUuid();
|
||||
QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, registerAddress, size);
|
||||
|
||||
if (QModbusReply *reply = m_modbusTcpClient->sendReadRequest(request, slaveAddress)) {
|
||||
@ -327,20 +388,22 @@ QUuid ModbusTCPMaster::readHoldingRegister(uint slaveAddress, uint registerAddre
|
||||
}
|
||||
reply->deleteLater();
|
||||
});
|
||||
|
||||
connect(reply, &QModbusReply::errorOccurred, this, [reply, requestId, this] (QModbusDevice::Error error){
|
||||
|
||||
qCWarning(dcModbusTCP()) << "Modbus reply error:" << error;
|
||||
emit readRequestError(requestId, reply->errorString());
|
||||
reply->finished(); // To make sure it will be deleted
|
||||
emit reply->finished(); // To make sure it will be deleted
|
||||
});
|
||||
|
||||
QTimer::singleShot(2000, reply, &QModbusReply::deleteLater);
|
||||
} else {
|
||||
delete reply; // broadcast replies return immediately
|
||||
return "";
|
||||
return QUuid();
|
||||
}
|
||||
} else {
|
||||
qCWarning(dcModbusTCP()) << "Read error: " << m_modbusTcpClient->errorString();
|
||||
return "";
|
||||
return QUuid();
|
||||
}
|
||||
return requestId;
|
||||
}
|
||||
@ -353,10 +416,10 @@ QUuid ModbusTCPMaster::writeCoil(uint slaveAddress, uint registerAddress, bool v
|
||||
QUuid ModbusTCPMaster::writeCoils(uint slaveAddress, uint registerAddress, const QVector<quint16> &values)
|
||||
{
|
||||
if (!m_modbusTcpClient) {
|
||||
return "";
|
||||
return QUuid();
|
||||
}
|
||||
QUuid requestId = QUuid::createUuid();
|
||||
|
||||
QUuid requestId = QUuid::createUuid();
|
||||
QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::Coils, registerAddress, values.length());
|
||||
request.setValues(values);
|
||||
|
||||
@ -377,20 +440,21 @@ QUuid ModbusTCPMaster::writeCoils(uint slaveAddress, uint registerAddress, const
|
||||
}
|
||||
reply->deleteLater();
|
||||
});
|
||||
connect(reply, &QModbusReply::errorOccurred, this, [reply, requestId, this] (QModbusDevice::Error error){
|
||||
|
||||
connect(reply, &QModbusReply::errorOccurred, this, [reply, requestId, this] (QModbusDevice::Error error){
|
||||
qCWarning(dcModbusTCP()) << "Modbus reply error:" << error;
|
||||
emit writeRequestError(requestId, reply->errorString());
|
||||
reply->finished(); // To make sure it will be deleted
|
||||
emit reply->finished(); // To make sure it will be deleted
|
||||
});
|
||||
|
||||
QTimer::singleShot(2000, reply, &QModbusReply::deleteLater);
|
||||
} else {
|
||||
delete reply; // broadcast replies return immediately
|
||||
return "";
|
||||
return QUuid();
|
||||
}
|
||||
} else {
|
||||
qCWarning(dcModbusTCP()) << "Read error: " << m_modbusTcpClient->errorString();
|
||||
return "";
|
||||
return QUuid();
|
||||
}
|
||||
return requestId;
|
||||
}
|
||||
@ -400,19 +464,25 @@ QUuid ModbusTCPMaster::writeHoldingRegister(uint slaveAddress, uint registerAddr
|
||||
return writeHoldingRegisters(slaveAddress, registerAddress, QVector<quint16>() << value);
|
||||
}
|
||||
|
||||
|
||||
void ModbusTCPMaster::onModbusErrorOccurred(QModbusDevice::Error error)
|
||||
{
|
||||
qCWarning(dcModbusTCP()) << "An error occured" << error;
|
||||
}
|
||||
|
||||
|
||||
void ModbusTCPMaster::onModbusStateChanged(QModbusDevice::State state)
|
||||
{
|
||||
qCDebug(dcModbusTCP()) << "Connection state changed for" << m_hostAddress << state;
|
||||
bool connected = (state == QModbusDevice::ConnectedState);
|
||||
if (!connected) {
|
||||
//try to reconnect in 10 seconds
|
||||
m_reconnectTimer->start(10000);
|
||||
if (m_connected != connected) {
|
||||
m_connected = connected;
|
||||
emit connectionStateChanged(m_connected);
|
||||
}
|
||||
|
||||
// If the socket is connected, stop the reconnect timer...
|
||||
// If the socket is unconnected (not connecting and not closing), start the reconnect timer
|
||||
if (m_connected) {
|
||||
m_reconnectTimer->stop();
|
||||
} else if (state == QModbusDevice::UnconnectedState) {
|
||||
m_reconnectTimer->start();
|
||||
}
|
||||
emit connectionStateChanged(connected);
|
||||
}
|
||||
|
||||
@ -37,8 +37,6 @@
|
||||
#include <QTimer>
|
||||
#include <QUuid>
|
||||
|
||||
Q_DECLARE_LOGGING_CATEGORY(dcModbusTcp)
|
||||
|
||||
class ModbusTCPMaster : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
@ -46,10 +44,22 @@ public:
|
||||
explicit ModbusTCPMaster(const QHostAddress &hostAddress, uint port, QObject *parent = nullptr);
|
||||
~ModbusTCPMaster();
|
||||
|
||||
bool connectDevice();
|
||||
bool connected();
|
||||
// If you change the hostaddress, make sure to reconnectDevice afterwards
|
||||
QHostAddress hostAddress() const;
|
||||
void setHostAddress(const QHostAddress &hostAddress);
|
||||
|
||||
// If you change the port, make sure to reconnectDevice afterwards
|
||||
uint port() const;
|
||||
void setPort(uint port);
|
||||
|
||||
bool connected() const;
|
||||
|
||||
int numberOfRetries() const;
|
||||
void setNumberOfRetries(int number);
|
||||
|
||||
int timeout() const;
|
||||
void setTimeout(int timeout);
|
||||
int timeout();
|
||||
|
||||
QString errorString() const;
|
||||
QModbusDevice::Error error() const;
|
||||
@ -65,18 +75,28 @@ public:
|
||||
QUuid writeHoldingRegister(uint slaveAddress, uint registerAddress, quint16 value);
|
||||
QUuid writeHoldingRegisters(uint slaveAddress, uint registerAddress, const QVector<quint16> &values);
|
||||
|
||||
QHostAddress hostAddress();
|
||||
uint port();
|
||||
bool setHostAddress(const QHostAddress &hostAddress);
|
||||
bool setPort(uint port);
|
||||
// Generic requests
|
||||
QModbusReply *sendRawRequest(const QModbusRequest &request, int serverAddress);
|
||||
QModbusReply *sendReadRequest(const QModbusDataUnit &read, int serverAddress);
|
||||
QModbusReply *sendReadWriteRequest(const QModbusDataUnit &read, const QModbusDataUnit &write, int serverAddress);
|
||||
QModbusReply *sendWriteRequest(const QModbusDataUnit &write, int serverAddress);
|
||||
|
||||
public slots:
|
||||
bool connectDevice();
|
||||
void disconnectDevice();
|
||||
bool reconnectDevice();
|
||||
|
||||
private:
|
||||
QTimer *m_reconnectTimer = nullptr;
|
||||
QModbusTcpClient *m_modbusTcpClient;
|
||||
QModbusTcpClient *m_modbusTcpClient = nullptr;
|
||||
|
||||
QHostAddress m_hostAddress;
|
||||
uint m_port;
|
||||
int m_timeout = 1000;
|
||||
int m_numberOfRetries = 3;
|
||||
bool m_connected = false;
|
||||
|
||||
private slots:
|
||||
void onReconnectTimer();
|
||||
|
||||
void onModbusErrorOccurred(QModbusDevice::Error error);
|
||||
void onModbusStateChanged(QModbusDevice::State state);
|
||||
|
||||
@ -85,9 +105,9 @@ signals:
|
||||
|
||||
void writeRequestExecuted(const QUuid &requestId, bool success);
|
||||
void writeRequestError(const QUuid &requestId, const QString &error);
|
||||
void readRequestError(const QUuid &requestId, const QString &error);
|
||||
|
||||
void readRequestExecuted(const QUuid &requestId, bool success);
|
||||
void readRequestError(const QUuid &requestId, const QString &error);
|
||||
|
||||
void receivedCoil(uint slaveAddress, uint modbusRegister, const QVector<quint16> &values);
|
||||
void receivedDiscreteInput(uint slaveAddress, uint modbusRegister, const QVector<quint16> &values);
|
||||
|
||||
345
mtec/integrationpluginmtec.cpp
Normal file
345
mtec/integrationpluginmtec.cpp
Normal file
@ -0,0 +1,345 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2021, 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 "network/networkdevicediscovery.h"
|
||||
#include "integrationpluginmtec.h"
|
||||
#include "plugininfo.h"
|
||||
|
||||
IntegrationPluginMTec::IntegrationPluginMTec()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void IntegrationPluginMTec::discoverThings(ThingDiscoveryInfo *info)
|
||||
{
|
||||
if (!hardwareManager()->networkDeviceDiscovery()->available()) {
|
||||
qCWarning(dcMTec()) << "The network discovery is not available on this platform.";
|
||||
info->finish(Thing::ThingErrorUnsupportedFeature, QT_TR_NOOP("The network device discovery is not available."));
|
||||
return;
|
||||
}
|
||||
|
||||
// Perform a network device discovery and filter for "go-eCharger" hosts
|
||||
NetworkDeviceDiscoveryReply *discoveryReply = hardwareManager()->networkDeviceDiscovery()->discover();
|
||||
connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, this, [=](){
|
||||
foreach (const NetworkDeviceInfo &networkDeviceInfo, discoveryReply->networkDeviceInfos()) {
|
||||
|
||||
qCDebug(dcMTec()) << "Found" << networkDeviceInfo;
|
||||
|
||||
QString title;
|
||||
if (networkDeviceInfo.hostName().isEmpty()) {
|
||||
title = networkDeviceInfo.address().toString();
|
||||
} else {
|
||||
title = networkDeviceInfo.hostName() + " (" + networkDeviceInfo.address().toString() + ")";
|
||||
}
|
||||
|
||||
QString description;
|
||||
if (networkDeviceInfo.macAddressManufacturer().isEmpty()) {
|
||||
description = networkDeviceInfo.macAddress();
|
||||
} else {
|
||||
description = networkDeviceInfo.macAddress() + " (" + networkDeviceInfo.macAddressManufacturer() + ")";
|
||||
}
|
||||
|
||||
ThingDescriptor descriptor(mtecThingClassId, title, description);
|
||||
ParamList params;
|
||||
params << Param(mtecThingIpAddressParamTypeId, networkDeviceInfo.address().toString());
|
||||
params << Param(mtecThingMacAddressParamTypeId, networkDeviceInfo.macAddress());
|
||||
descriptor.setParams(params);
|
||||
|
||||
// Check if we already have set up this device
|
||||
Things existingThings = myThings().filterByParam(mtecThingMacAddressParamTypeId, networkDeviceInfo.macAddress());
|
||||
if (existingThings.count() == 1) {
|
||||
qCDebug(dcMTec()) << "This heat pump already exists in the system!" << networkDeviceInfo;
|
||||
descriptor.setThingId(existingThings.first()->id());
|
||||
}
|
||||
|
||||
info->addThingDescriptor(descriptor);
|
||||
}
|
||||
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
});
|
||||
}
|
||||
|
||||
void IntegrationPluginMTec::setupThing(ThingSetupInfo *info)
|
||||
{
|
||||
Thing *thing = info->thing();
|
||||
qCDebug(dcMTec()) << "Setup" << thing;
|
||||
|
||||
if (thing->thingClassId() == mtecThingClassId) {
|
||||
QHostAddress hostAddress = QHostAddress(thing->paramValue(mtecThingIpAddressParamTypeId).toString());
|
||||
if (hostAddress.isNull()) {
|
||||
info->finish(Thing::ThingErrorInvalidParameter, QT_TR_NOOP("No IP address given"));
|
||||
return;
|
||||
}
|
||||
|
||||
qCDebug(dcMTec()) << "Using ip address" << hostAddress.toString();
|
||||
|
||||
MTec *mtec = new MTec(hostAddress, this);
|
||||
connect(mtec, &MTec::connectedChanged, thing, [=](bool connected){
|
||||
qCDebug(dcMTec()) << thing << "Connected changed to" << connected;
|
||||
thing->setStateValue(mtecConnectedStateTypeId, connected);
|
||||
});
|
||||
|
||||
connect(mtec, &MTec::roomTemperatureChanged, thing, [=](double roomTemperature){
|
||||
qCDebug(dcMTec()) << thing << "Room temperature" << roomTemperature << "°C";
|
||||
thing->setStateValue(mtecTemperatureStateTypeId, roomTemperature);
|
||||
});
|
||||
|
||||
connect(mtec, &MTec::targetRoomTemperatureChanged, thing, [=](double targetRoomTemperature){
|
||||
qCDebug(dcMTec()) << thing << "Target room temperature" << targetRoomTemperature << "°C";
|
||||
thing->setStateValue(mtecTargetTemperatureStateTypeId, targetRoomTemperature);
|
||||
});
|
||||
|
||||
connect(mtec, &MTec::waterTankTopTemperatureChanged, thing, [=](double waterTankTopTemperature){
|
||||
qCDebug(dcMTec()) << thing << "Water tank top temperature" << waterTankTopTemperature << "°C";
|
||||
thing->setStateValue(mtecWaterTankTopTemperatureStateTypeId, waterTankTopTemperature);
|
||||
});
|
||||
|
||||
connect(mtec, &MTec::bufferTankTemperatureChanged, thing, [=](double bufferTankTemperature){
|
||||
qCDebug(dcMTec()) << thing << "Buffer tank temperature" << bufferTankTemperature << "°C";
|
||||
thing->setStateValue(mtecBufferTankTemperatureStateTypeId, bufferTankTemperature);
|
||||
});
|
||||
|
||||
connect(mtec, &MTec::totalAccumulatedHeatingEnergyChanged, thing, [=](double totalAccumulatedHeatingEnergy){
|
||||
qCDebug(dcMTec()) << thing << "Total accumulated heating energy" << totalAccumulatedHeatingEnergy << "kWh";
|
||||
thing->setStateValue(mtecTotalAccumulatedHeatingEnergyStateTypeId, totalAccumulatedHeatingEnergy);
|
||||
});
|
||||
|
||||
connect(mtec, &MTec::totalAccumulatedElectricalEnergyChanged, thing, [=](double totalAccumulatedElectricalEnergy){
|
||||
qCDebug(dcMTec()) << thing << "Total accumulated electrical energy" << totalAccumulatedElectricalEnergy << "kWh";
|
||||
thing->setStateValue(mtecTotalAccumulatedElectricalEnergyStateTypeId, totalAccumulatedElectricalEnergy);
|
||||
});
|
||||
|
||||
connect(mtec, &MTec::heatPumpStateChanged, thing, [=](MTec::HeatpumpState heatPumpState){
|
||||
qCDebug(dcMTec()) << thing << "Heat pump state" << heatPumpState;
|
||||
switch (heatPumpState) {
|
||||
case MTec::HeatpumpStateStandby:
|
||||
thing->setStateValue(mtecHeatPumpStateStateTypeId, "Standby");
|
||||
thing->setStateValue(mtecHeatingOnStateTypeId, false);
|
||||
thing->setStateValue(mtecCoolingOnStateTypeId, false);
|
||||
break;
|
||||
case MTec::HeatpumpStatePreRun:
|
||||
thing->setStateValue(mtecHeatPumpStateStateTypeId, "Pre run");
|
||||
thing->setStateValue(mtecHeatingOnStateTypeId, false);
|
||||
thing->setStateValue(mtecCoolingOnStateTypeId, false);
|
||||
break;
|
||||
case MTec::HeatpumpStateAutomaticHeat:
|
||||
thing->setStateValue(mtecHeatPumpStateStateTypeId, "Automatic heat");
|
||||
thing->setStateValue(mtecHeatingOnStateTypeId, true);
|
||||
thing->setStateValue(mtecCoolingOnStateTypeId, false);
|
||||
break;
|
||||
case MTec::HeatpumpStateDefrost:
|
||||
thing->setStateValue(mtecHeatPumpStateStateTypeId, "Defrost");
|
||||
thing->setStateValue(mtecHeatingOnStateTypeId, false);
|
||||
thing->setStateValue(mtecCoolingOnStateTypeId, false);
|
||||
break;
|
||||
case MTec::HeatpumpStateAutomaticCool:
|
||||
thing->setStateValue(mtecHeatPumpStateStateTypeId, "Automatic cool");
|
||||
thing->setStateValue(mtecHeatingOnStateTypeId, false);
|
||||
thing->setStateValue(mtecCoolingOnStateTypeId, true);
|
||||
break;
|
||||
case MTec::HeatpumpStatePostRun:
|
||||
thing->setStateValue(mtecHeatingOnStateTypeId, false);
|
||||
thing->setStateValue(mtecHeatPumpStateStateTypeId, "Post run");
|
||||
thing->setStateValue(mtecCoolingOnStateTypeId, false);
|
||||
break;
|
||||
case MTec::HeatpumpStateSaftyShutdown:
|
||||
thing->setStateValue(mtecHeatingOnStateTypeId, false);
|
||||
thing->setStateValue(mtecHeatPumpStateStateTypeId, "Safty shutdown");
|
||||
thing->setStateValue(mtecCoolingOnStateTypeId, false);
|
||||
break;
|
||||
case MTec::HeatpumpStateError:
|
||||
thing->setStateValue(mtecHeatingOnStateTypeId, false);
|
||||
thing->setStateValue(mtecHeatPumpStateStateTypeId, "Error");
|
||||
thing->setStateValue(mtecCoolingOnStateTypeId, false);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
connect(mtec, &MTec::heatMeterPowerConsumptionChanged, thing, [=](double heatMeterPowerConsumption){
|
||||
qCDebug(dcMTec()) << thing << "Heat meter power consumption" << heatMeterPowerConsumption << "W";
|
||||
thing->setStateValue(mtecHeatMeterPowerConsumptionStateTypeId, heatMeterPowerConsumption);
|
||||
});
|
||||
|
||||
connect(mtec, &MTec::energyMeterPowerConsumptionChanged, thing, [=](double energyMeterPowerConsumption){
|
||||
qCDebug(dcMTec()) << thing << "Energy meter power consumption" << energyMeterPowerConsumption << "W";
|
||||
thing->setStateValue(mtecEnergyMeterPowerConsumptionStateTypeId, energyMeterPowerConsumption);
|
||||
});
|
||||
|
||||
connect(mtec, &MTec::actualExcessEnergySmartHomeChanged, thing, [=](double actualExcessEnergySmartHome){
|
||||
qCDebug(dcMTec()) << thing << "Smart home energy" << actualExcessEnergySmartHome << "W";
|
||||
thing->setStateValue(mtecSmartHomeEnergyStateTypeId, actualExcessEnergySmartHome);
|
||||
});
|
||||
|
||||
connect(mtec, &MTec::actualExcessEnergySmartHomeElectricityMeterChanged, thing, [=](double actualExcessEnergySmartHomeElectricityMeter){
|
||||
qCDebug(dcMTec()) << thing << "Smart home energy electrical meter" << actualExcessEnergySmartHomeElectricityMeter << "W";
|
||||
thing->setStateValue(mtecSmartHomeEnergyElectricityMeterStateTypeId, actualExcessEnergySmartHomeElectricityMeter);
|
||||
});
|
||||
|
||||
connect(mtec, &MTec::actualOutdoorTemperatureChanged, thing, [=](double actualOutdoorTemperature){
|
||||
qCDebug(dcMTec()) << thing << "Outdoor temperature" << actualOutdoorTemperature << "°C";
|
||||
thing->setStateValue(mtecOutdoorTemperatureStateTypeId, actualOutdoorTemperature);
|
||||
});
|
||||
|
||||
m_mtecConnections.insert(thing, mtec);
|
||||
|
||||
// TODO: start timer and give 15 seconds until connected, since the controler is down for ~10 seconds after a disconnect
|
||||
|
||||
if (!mtec->connectDevice()) {
|
||||
qCWarning(dcMTec()) << "Initial connect returned false. Lets wait 15 seconds until the connection can be established.";
|
||||
}
|
||||
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
}
|
||||
}
|
||||
|
||||
void IntegrationPluginMTec::postSetupThing(Thing *thing)
|
||||
{
|
||||
if (thing->thingClassId() == mtecThingClassId) {
|
||||
MTec *mtec = m_mtecConnections.value(thing);
|
||||
if (mtec) {
|
||||
update(thing);
|
||||
}
|
||||
|
||||
if (!m_pluginTimer) {
|
||||
qCDebug(dcMTec()) << "Starting plugin timer...";
|
||||
m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(10);
|
||||
connect(m_pluginTimer, &PluginTimer::timeout, this, [this] {
|
||||
foreach (Thing *thing, myThings().filterByThingClassId(mtecThingClassId)) {
|
||||
update(thing);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IntegrationPluginMTec::thingRemoved(Thing *thing)
|
||||
{
|
||||
if (m_mtecConnections.contains(thing)) {
|
||||
MTec *mtec = m_mtecConnections.take(thing);
|
||||
if (mtec) {
|
||||
mtec->disconnectDevice();
|
||||
mtec->deleteLater();
|
||||
}
|
||||
}
|
||||
|
||||
if (myThings().isEmpty()) {
|
||||
hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer);
|
||||
m_pluginTimer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void IntegrationPluginMTec::executeAction(ThingActionInfo *info)
|
||||
{
|
||||
Thing *thing = info->thing();
|
||||
Action action = info->action();
|
||||
|
||||
MTec *mtec = m_mtecConnections.value(thing);
|
||||
if (!mtec) {
|
||||
qCWarning(dcMTec()) << "Could not execute action because the MTec connection could not be found for" << thing;
|
||||
info->finish(Thing::ThingErrorHardwareNotAvailable);
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure we are connected
|
||||
if (!mtec->connected()) {
|
||||
qCWarning(dcMTec()) << "Could not execute action because the MTec connection is not connected" << thing;
|
||||
info->finish(Thing::ThingErrorHardwareNotAvailable);
|
||||
return;
|
||||
}
|
||||
|
||||
if (action.actionTypeId() == mtecTargetTemperatureActionTypeId) {
|
||||
double targetTemperature = action.paramValue(mtecTargetTemperatureActionTargetTemperatureParamTypeId).toDouble();
|
||||
qCDebug(dcMTec()) << "Setting target temperature" << targetTemperature << "°C";
|
||||
QModbusReply *reply = mtec->setTargetRoomTemperature(targetTemperature);
|
||||
if (!reply) {
|
||||
qCWarning(dcMTec()) << "Failed to send modbus request" << thing;
|
||||
info->finish(Thing::ThingErrorHardwareFailure);
|
||||
return;
|
||||
}
|
||||
|
||||
connect(reply, &QModbusReply::finished, this, [=]() {
|
||||
reply->deleteLater();
|
||||
if (reply->error() == QModbusDevice::NoError) {
|
||||
qCDebug(dcMTec()) << "Setting target temperature" << targetTemperature << "°C" << "finished successfully";
|
||||
thing->setStateValue(mtecTargetTemperatureStateTypeId, targetTemperature);
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
} else {
|
||||
info->finish(Thing::ThingErrorHardwareFailure);
|
||||
}
|
||||
});
|
||||
|
||||
connect(reply, &QModbusReply::errorOccurred, this, [=](QModbusDevice::Error error) {
|
||||
qCWarning(dcMTec()) << thing << "Action execution finished due to modbus replay error:" << error;
|
||||
reply->deleteLater();
|
||||
info->finish(Thing::ThingErrorHardwareFailure);
|
||||
});
|
||||
} else if (action.actionTypeId() == mtecSmartHomeEnergyActionTypeId) {
|
||||
quint16 energy = action.paramValue(mtecSmartHomeEnergyActionSmartHomeEnergyParamTypeId).toUInt();
|
||||
qCDebug(dcMTec()) << "Setting smart home energy to" << energy << "W";
|
||||
QModbusReply *reply = mtec->setSmartHomeEnergy(energy);
|
||||
if (!reply) {
|
||||
qCWarning(dcMTec()) << "Failed to send modbus request" << thing;
|
||||
info->finish(Thing::ThingErrorHardwareFailure);
|
||||
return;
|
||||
}
|
||||
|
||||
connect(reply, &QModbusReply::finished, this, [=]() {
|
||||
reply->deleteLater();
|
||||
if (reply->error() == QModbusDevice::NoError) {
|
||||
qCDebug(dcMTec()) << "Setting smart home energy" << energy << "W" << "finished successfully";
|
||||
thing->setStateValue(mtecSmartHomeEnergyStateTypeId, energy);
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
} else {
|
||||
info->finish(Thing::ThingErrorHardwareFailure);
|
||||
}
|
||||
});
|
||||
|
||||
connect(reply, &QModbusReply::errorOccurred, this, [=](QModbusDevice::Error error) {
|
||||
qCWarning(dcMTec()) << thing << "Action execution finished due to modbus replay error:" << error;
|
||||
reply->deleteLater();
|
||||
info->finish(Thing::ThingErrorHardwareFailure);
|
||||
});
|
||||
} else {
|
||||
Q_ASSERT_X(false, "executeAction", QString("Unhandled action: %1").arg(action.actionTypeId().toString()).toUtf8());
|
||||
}
|
||||
}
|
||||
|
||||
void IntegrationPluginMTec::update(Thing *thing)
|
||||
{
|
||||
if (thing->thingClassId() == mtecThingClassId) {
|
||||
qCDebug(dcMTec()) << "Updating thing" << thing;
|
||||
MTec *mtec = m_mtecConnections.value(thing);
|
||||
if (!mtec) return;
|
||||
mtec->updateValues();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
68
mtec/integrationpluginmtec.h
Normal file
68
mtec/integrationpluginmtec.h
Normal file
@ -0,0 +1,68 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2020, 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
#ifndef INTEGRATIONPLUGINMTEC_H
|
||||
#define INTEGRATIONPLUGINMTEC_H
|
||||
|
||||
#include "integrations/integrationplugin.h"
|
||||
#include "plugintimer.h"
|
||||
|
||||
#include "mtec.h"
|
||||
|
||||
#include <QUuid>
|
||||
|
||||
class IntegrationPluginMTec: public IntegrationPlugin
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginmtec.json")
|
||||
Q_INTERFACES(IntegrationPlugin)
|
||||
|
||||
public:
|
||||
/** Constructor */
|
||||
explicit IntegrationPluginMTec();
|
||||
|
||||
void discoverThings(ThingDiscoveryInfo *info) override;
|
||||
void setupThing(ThingSetupInfo *info) override;
|
||||
void postSetupThing(Thing *thing) override;
|
||||
void thingRemoved(Thing *thing) override;
|
||||
void executeAction(ThingActionInfo *info) override;
|
||||
|
||||
private:
|
||||
PluginTimer *m_pluginTimer = nullptr;
|
||||
QHash<Thing *, MTec *> m_mtecConnections;
|
||||
|
||||
private slots:
|
||||
void update(Thing *thing);
|
||||
|
||||
};
|
||||
|
||||
#endif // INTEGRATIONPLUGINMTEC_H
|
||||
|
||||
|
||||
206
mtec/integrationpluginmtec.json
Normal file
206
mtec/integrationpluginmtec.json
Normal file
@ -0,0 +1,206 @@
|
||||
{
|
||||
"name": "MTec",
|
||||
"displayName": "M-Tec",
|
||||
"id": "07cd316b-1e2c-40cf-8358-88d7407506ae",
|
||||
"vendors": [
|
||||
{
|
||||
"name": "MTec",
|
||||
"displayName": "M-Tec",
|
||||
"id": "04d3fa7c-e469-4a79-a119-155426e5a846",
|
||||
"thingClasses": [
|
||||
{
|
||||
"name": "mtec",
|
||||
"displayName": "MTec",
|
||||
"id": "451e38d8-50d5-4ae9-8d9f-21af9347128d",
|
||||
"createMethods": ["discovery", "user"],
|
||||
"interfaces": ["thermostat", "connectable"],
|
||||
"paramTypes": [
|
||||
{
|
||||
"id": "f1c43b1e-cffe-4d30-bda0-c23ed648dd71",
|
||||
"name": "ipAddress",
|
||||
"displayName": "IP address",
|
||||
"type": "QString",
|
||||
"inputType": "IPv4Address",
|
||||
"defaultValue": "127.0.0.1"
|
||||
},
|
||||
{
|
||||
"id": "906f6099-d0e1-4297-a2b3-f8ec4482c578",
|
||||
"name":"macAddress",
|
||||
"displayName": "MAC address",
|
||||
"type": "QString",
|
||||
"inputType": "MacAddress",
|
||||
"defaultValue": ""
|
||||
}
|
||||
],
|
||||
"stateTypes": [
|
||||
{
|
||||
"id": "8d64954a-855d-44ea-8bc9-88a71ab47b6b",
|
||||
"name": "connected",
|
||||
"displayName": "Connected",
|
||||
"displayNameEvent": "Connected changed",
|
||||
"type": "bool",
|
||||
"defaultValue": false,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "1e2037c8-09dc-4396-974c-efa9c486aa65",
|
||||
"name": "heatPumpState",
|
||||
"displayName": "Heat pump state",
|
||||
"displayNameEvent": "Heat pump state changed",
|
||||
"type": "QString",
|
||||
"possibleValues": [
|
||||
"Standby",
|
||||
"Pre run",
|
||||
"Automatic heat",
|
||||
"Defrost",
|
||||
"Automatic cool",
|
||||
"Post run",
|
||||
"Safty shutdown",
|
||||
"Error"
|
||||
],
|
||||
"defaultValue": "Standby",
|
||||
"suggestLogging": true
|
||||
},
|
||||
{
|
||||
"id": "9b538cb9-f7a3-471e-8d3b-09f6370a571c",
|
||||
"name": "targetTemperature",
|
||||
"displayName": "Target room temperature (heat circuit 0)",
|
||||
"displayNameEvent": "Target room temperature changed (heat circuit 0=",
|
||||
"displayNameAction": "Set target room temperature (heat circuit 0)",
|
||||
"unit": "DegreeCelsius",
|
||||
"type": "double",
|
||||
"writable": true,
|
||||
"minValue": 10,
|
||||
"maxValue": 30,
|
||||
"defaultValue": 20,
|
||||
"suggestLogging": true
|
||||
},
|
||||
{
|
||||
"id": "b22ac9bb-3842-497c-bd93-f8bea6670e32",
|
||||
"name": "temperature",
|
||||
"displayName": "Room temperature heat circuit 0",
|
||||
"displayNameEvent": "Room temperature heat circuit 0 changed",
|
||||
"unit": "DegreeCelsius",
|
||||
"type": "double",
|
||||
"defaultValue": 20,
|
||||
"suggestLogging": true
|
||||
},
|
||||
{
|
||||
"id": "07465fbb-6949-4bd1-90d5-acf2d80c161d",
|
||||
"name": "heatingOn",
|
||||
"displayName": "Heating on",
|
||||
"displayNameEvent": "Heating turned on/off",
|
||||
"type": "bool",
|
||||
"defaultValue": false,
|
||||
"suggestLogging": true
|
||||
},
|
||||
{
|
||||
"id": "8b407c1d-b84f-48d4-9961-b29bc58fff0e",
|
||||
"name": "coolingOn",
|
||||
"displayName": "Cooling on",
|
||||
"displayNameEvent": "Cooling turned on/off",
|
||||
"type": "bool",
|
||||
"defaultValue": false,
|
||||
"suggestLogging": true
|
||||
},
|
||||
{
|
||||
"id": "d0c8f168-49b5-47ca-9988-c9922be38dd5",
|
||||
"name": "outdoorTemperature",
|
||||
"displayName": "Outdoor temperature",
|
||||
"displayNameEvent": "Outdoor temperature changed",
|
||||
"unit": "DegreeCelsius",
|
||||
"type": "double",
|
||||
"defaultValue": 0,
|
||||
"suggestLogging": true
|
||||
},
|
||||
{
|
||||
"id": "545f94d6-f4fd-48fe-bf3b-f193e5cb76e7",
|
||||
"name": "waterTankTopTemperature",
|
||||
"displayName": "Water tank top temperature",
|
||||
"displayNameEvent": "Water tank top temperature changed",
|
||||
"unit": "DegreeCelsius",
|
||||
"type": "double",
|
||||
"defaultValue": 0,
|
||||
"suggestLogging": true
|
||||
},
|
||||
{
|
||||
"id": "a98e37f8-dcdc-4c4c-aecf-07f376321849",
|
||||
"name": "bufferTankTemperature",
|
||||
"displayName": "Buffer tank temperature",
|
||||
"displayNameEvent": "Buffer tank temperature changed",
|
||||
"unit": "DegreeCelsius",
|
||||
"type": "double",
|
||||
"defaultValue": 0,
|
||||
"suggestLogging": true
|
||||
},
|
||||
{
|
||||
"id": "7d087af8-cdbe-463e-a9bb-7a7a79471963",
|
||||
"name": "totalAccumulatedHeatingEnergy",
|
||||
"displayName": "Total accumulated heating energy",
|
||||
"displayNameEvent": "Total accumulated heating energy changed",
|
||||
"type": "double",
|
||||
"unit": "KiloWattHour",
|
||||
"defaultValue": 0,
|
||||
"suggestLogging": true
|
||||
},
|
||||
{
|
||||
"id": "c67c79cf-7369-409f-b170-16c4ece9d25a",
|
||||
"name": "totalAccumulatedElectricalEnergy",
|
||||
"displayName": "Total accumulated electrical energy",
|
||||
"displayNameEvent": "Total accumulated electrical energy changed",
|
||||
"type": "double",
|
||||
"unit": "KiloWattHour",
|
||||
"defaultValue": 0,
|
||||
"suggestLogging": true
|
||||
},
|
||||
{
|
||||
"id": "581abddc-90d6-4dea-a43c-63b117b335fe",
|
||||
"name": "heatMeterPowerConsumption",
|
||||
"displayName": "Heat meter power consumption",
|
||||
"displayNameEvent": "Heat meter power consumption changed",
|
||||
"type": "double",
|
||||
"unit": "Watt",
|
||||
"defaultValue": 0,
|
||||
"suggestLogging": true
|
||||
},
|
||||
{
|
||||
"id": "fd52a97e-f94d-4529-b479-b74e61f75a89",
|
||||
"name": "energyMeterPowerConsumption",
|
||||
"displayName": "Energy meter power consumption",
|
||||
"displayNameEvent": "Energy meter power consumption changed",
|
||||
"type": "double",
|
||||
"unit": "Watt",
|
||||
"defaultValue": 0,
|
||||
"suggestLogging": true
|
||||
},
|
||||
{
|
||||
"id": "b646ea10-ea7e-4eba-bfda-8e3cd38370a7",
|
||||
"name": "smartHomeEnergy",
|
||||
"displayName": "Smart home energy",
|
||||
"displayNameEvent": "Smart home energy changed",
|
||||
"displayNameAction": "Set smart home energy",
|
||||
"type": "uint",
|
||||
"unit": "Watt",
|
||||
"minValue": 0,
|
||||
"maxValue": 20000,
|
||||
"defaultValue": 0,
|
||||
"writable": true,
|
||||
"suggestLogging": true
|
||||
},
|
||||
{
|
||||
"id": "a7734474-30db-435c-985a-105fb3ea5a86",
|
||||
"name": "smartHomeEnergyElectricityMeter",
|
||||
"displayName": "Smart home energy consumed",
|
||||
"displayNameEvent": "Smart home energy consumed changed",
|
||||
"type": "double",
|
||||
"unit": "Watt",
|
||||
"defaultValue": 0,
|
||||
"suggestLogging": true
|
||||
}
|
||||
],
|
||||
"actionTypes": [ ]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
202
mtec/mtec.cpp
Normal file
202
mtec/mtec.cpp
Normal file
@ -0,0 +1,202 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2021, 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 "mtec.h"
|
||||
#include "extern-plugininfo.h"
|
||||
|
||||
MTec::MTec(const QHostAddress &address, QObject *parent) :
|
||||
QObject(parent),
|
||||
m_hostAddress(address)
|
||||
{
|
||||
m_modbusMaster = new ModbusTCPMaster(address, 502, this);
|
||||
m_modbusMaster->setTimeout(2000);
|
||||
m_modbusMaster->setNumberOfRetries(5);
|
||||
|
||||
qCDebug(dcMTec()) << "Created ModbusTCPMaster for" << address.toString();
|
||||
connect(m_modbusMaster, &ModbusTCPMaster::connectionStateChanged, this, &MTec::connectedChanged);
|
||||
connect(m_modbusMaster, &ModbusTCPMaster::receivedHoldingRegister, this, &MTec::onReceivedHoldingRegister);
|
||||
connect(m_modbusMaster, &ModbusTCPMaster::readRequestError, this, &MTec::onModbusError);
|
||||
connect(m_modbusMaster, &ModbusTCPMaster::writeRequestError, this, &MTec::onModbusError);
|
||||
}
|
||||
|
||||
MTec::~MTec()
|
||||
{
|
||||
m_modbusMaster->disconnectDevice();
|
||||
}
|
||||
|
||||
QHostAddress MTec::hostAddress() const
|
||||
{
|
||||
return m_hostAddress;
|
||||
}
|
||||
|
||||
bool MTec::connected() const
|
||||
{
|
||||
return m_modbusMaster->connected();
|
||||
}
|
||||
|
||||
QModbusReply *MTec::setTargetRoomTemperature(double targetRoomTemperature)
|
||||
{
|
||||
QVector<quint16> values;
|
||||
values << static_cast<quint16>(qRound(targetRoomTemperature * 10));
|
||||
|
||||
QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, RegisterTargetRoomTemperature, values.length());
|
||||
request.setValues(values);
|
||||
QModbusReply *reply = m_modbusMaster->sendWriteRequest(request, MTec::ModbusUnitID);
|
||||
return reply;
|
||||
}
|
||||
|
||||
QModbusReply *MTec::setSmartHomeEnergy(quint16 smartHomeEnergy)
|
||||
{
|
||||
QVector<quint16> values;
|
||||
values << smartHomeEnergy;
|
||||
QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, RegisterActualExcessEnergySmartHome, values.length());
|
||||
request.setValues(values);
|
||||
QModbusReply *reply = m_modbusMaster->sendWriteRequest(request, MTec::ModbusUnitID);
|
||||
return reply;
|
||||
}
|
||||
|
||||
bool MTec::connectDevice()
|
||||
{
|
||||
return m_modbusMaster->connectDevice();
|
||||
}
|
||||
|
||||
void MTec::disconnectDevice()
|
||||
{
|
||||
m_modbusMaster->disconnectDevice();
|
||||
}
|
||||
|
||||
void MTec::updateValues()
|
||||
{
|
||||
if (!m_modbusMaster->connected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_modbusMaster->readHoldingRegister(MTec::ModbusUnitID, RegisterRoomTemperature, 1);
|
||||
}
|
||||
|
||||
void MTec::onModbusError()
|
||||
{
|
||||
qCWarning(dcMTec()) << "Modbus error occured" << m_modbusMaster->errorString();
|
||||
}
|
||||
|
||||
void MTec::onReceivedHoldingRegister(int slaveAddress, int modbusRegister, const QVector<quint16> &value)
|
||||
{
|
||||
Q_UNUSED(slaveAddress);
|
||||
|
||||
switch (modbusRegister) {
|
||||
case RegisterRoomTemperature:
|
||||
if (value.length() == 1) {
|
||||
m_roomTemperature = value[0] / 10.0;
|
||||
emit roomTemperatureChanged(m_roomTemperature);
|
||||
}
|
||||
m_modbusMaster->readHoldingRegister(MTec::ModbusUnitID, RegisterTargetRoomTemperature, 1);
|
||||
break;
|
||||
case RegisterTargetRoomTemperature:
|
||||
if (value.length() == 1) {
|
||||
m_targetRoomTemperature = value[0] / 10.0;
|
||||
emit targetRoomTemperatureChanged(m_targetRoomTemperature);
|
||||
}
|
||||
m_modbusMaster->readHoldingRegister(MTec::ModbusUnitID, RegisterHotWaterTankTemperature, 1);
|
||||
break;
|
||||
case RegisterHotWaterTankTemperature:
|
||||
if (value.length() == 1) {
|
||||
m_waterTankTopTemperature = value[0] / 10.0;
|
||||
emit waterTankTopTemperatureChanged(m_waterTankTopTemperature);
|
||||
}
|
||||
m_modbusMaster->readHoldingRegister(MTec::ModbusUnitID, RegisterBufferTankTemperature, 1);
|
||||
break;
|
||||
case RegisterBufferTankTemperature:
|
||||
if (value.length() == 1) {
|
||||
m_bufferTankTemperature = value[0] / 10.0;
|
||||
emit bufferTankTemperatureChanged(m_bufferTankTemperature);
|
||||
}
|
||||
m_modbusMaster->readHoldingRegister(MTec::ModbusUnitID, RegisterTotalAccumulatedHeatingEnergy, 1);
|
||||
break;
|
||||
case RegisterTotalAccumulatedHeatingEnergy:
|
||||
if (value.length() == 1) {
|
||||
m_totalAccumulatedElectricalEnergy = value[0];
|
||||
emit totalAccumulatedElectricalEnergyChanged(m_totalAccumulatedElectricalEnergy);
|
||||
}
|
||||
m_modbusMaster->readHoldingRegister(MTec::ModbusUnitID, RegisterTotalAccumulatedElectricalEnergy, 1);
|
||||
break;
|
||||
case RegisterTotalAccumulatedElectricalEnergy:
|
||||
if (value.length() == 1) {
|
||||
m_totalAccumulatedElectricalEnergy = value[0];
|
||||
emit totalAccumulatedElectricalEnergyChanged(m_totalAccumulatedElectricalEnergy);
|
||||
}
|
||||
m_modbusMaster->readHoldingRegister(MTec::ModbusUnitID, RegisterHeatpumpState, 1);
|
||||
break;
|
||||
case RegisterHeatpumpState:
|
||||
if (value.length() == 1) {
|
||||
m_heatPumpState = static_cast<HeatpumpState>(value[0]);
|
||||
emit heatPumpStateChanged(m_heatPumpState);
|
||||
}
|
||||
m_modbusMaster->readHoldingRegister(MTec::ModbusUnitID, RegisterHeatMeterPowerConsumption, 1);
|
||||
break;
|
||||
case RegisterHeatMeterPowerConsumption:
|
||||
if (value.length() == 1) {
|
||||
m_heatMeterPowerConsumption = value[0];
|
||||
emit heatMeterPowerConsumptionChanged(m_heatMeterPowerConsumption);
|
||||
}
|
||||
m_modbusMaster->readHoldingRegister(MTec::ModbusUnitID, RegisterEnergyMeterPowerConsumption, 1);
|
||||
break;
|
||||
case RegisterEnergyMeterPowerConsumption:
|
||||
if (value.length() == 1) {
|
||||
m_energyMeterPowerConsumption = value[0];
|
||||
emit energyMeterPowerConsumptionChanged(m_energyMeterPowerConsumption);
|
||||
}
|
||||
m_modbusMaster->readHoldingRegister(MTec::ModbusUnitID, RegisterActualExcessEnergySmartHome, 1);
|
||||
break;
|
||||
case RegisterActualExcessEnergySmartHome:
|
||||
if (value.length() == 1) {
|
||||
m_actualExcessEnergySmartHome = value[0];
|
||||
emit actualExcessEnergySmartHomeChanged(m_actualExcessEnergySmartHome);
|
||||
}
|
||||
m_modbusMaster->readHoldingRegister(MTec::ModbusUnitID, RegisterActualExcessEnergySmartHomeElectricityMeter, 1);
|
||||
break;
|
||||
case RegisterActualExcessEnergySmartHomeElectricityMeter:
|
||||
if (value.length() == 1) {
|
||||
m_actualExcessEnergySmartHomeElectricityMeter = value[0];
|
||||
emit actualExcessEnergySmartHomeChanged(m_actualExcessEnergySmartHome);
|
||||
}
|
||||
m_modbusMaster->readHoldingRegister(MTec::ModbusUnitID, RegisterActualOutdoorTemperature, 1);
|
||||
break;
|
||||
case RegisterActualOutdoorTemperature:
|
||||
if (value.length() == 1) {
|
||||
m_actualOutdoorTemperature = value[0] / 10.0;
|
||||
emit actualOutdoorTemperatureChanged(m_actualOutdoorTemperature);
|
||||
}
|
||||
|
||||
// TODO: set initialized
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
164
mtec/mtec.h
Normal file
164
mtec/mtec.h
Normal file
@ -0,0 +1,164 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2021, 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#ifndef MTEC_H
|
||||
#define MTEC_H
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QObject>
|
||||
|
||||
#include "../modbus/modbustcpmaster.h"
|
||||
|
||||
class MTec : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum HeatpumpState {
|
||||
HeatpumpStateStandby = 0,
|
||||
HeatpumpStatePreRun = 1,
|
||||
HeatpumpStateAutomaticHeat = 2,
|
||||
HeatpumpStateDefrost = 3,
|
||||
HeatpumpStateAutomaticCool = 4,
|
||||
HeatpumpStatePostRun = 5,
|
||||
HeatpumpStateSaftyShutdown = 7,
|
||||
HeatpumpStateError = 8
|
||||
};
|
||||
Q_ENUM(HeatpumpState)
|
||||
|
||||
explicit MTec(const QHostAddress &address, QObject *parent = nullptr);
|
||||
~MTec();
|
||||
|
||||
QHostAddress hostAddress() const;
|
||||
bool connected() const;
|
||||
|
||||
QModbusReply *setTargetRoomTemperature(double targetRoomTemperature);
|
||||
QModbusReply *setSmartHomeEnergy(quint16 smartHomeEnergy);
|
||||
|
||||
public slots:
|
||||
bool connectDevice();
|
||||
void disconnectDevice();
|
||||
|
||||
void updateValues();
|
||||
|
||||
private:
|
||||
/** Modbus Unit ID (undocumented, guessing 1 for now) */
|
||||
static const quint16 ModbusUnitID = 1;
|
||||
|
||||
/** The following modbus addresses can be read: */
|
||||
enum Register {
|
||||
/* R APPL.CtrlAppl.sParam.heatCircuit[0].tempRoom.values.actValue
|
||||
* Actual room temperature [1/10°C]. */
|
||||
RegisterRoomTemperature = 1,
|
||||
|
||||
/* RW APPL.CtrlAppl.sParam.heatCircuit[0].param.normalSetTemp
|
||||
* Room set temperature for heating circuit [1/10°C]. */
|
||||
RegisterTargetRoomTemperature = 4,
|
||||
|
||||
/* R APPL.CtrlAppl.sParam.hotWaterTank[0].topTemp.values.actValue
|
||||
* Hot water tank top temperature [1/10°C]. */
|
||||
RegisterHotWaterTankTemperature = 401,
|
||||
|
||||
/* R APPL.CtrlAppl.sParam.bufferTank[0].topTemp.values.actValue
|
||||
* Buffer Actual top temperature [1/10°C]. */
|
||||
RegisterBufferTankTemperature = 601,
|
||||
|
||||
/* R APPL.CtrlAppl.sStatisticalData.heatpump[0].consumption.energy
|
||||
* Total accumulated heating energy [kWh] */
|
||||
RegisterTotalAccumulatedHeatingEnergy = 701,
|
||||
|
||||
/* R APPL.CtrlAppl.sStatisticalData.heatpump[0].consumption.electricalenergy
|
||||
* Total accumulated electrical energy [kWh] */
|
||||
RegisterTotalAccumulatedElectricalEnergy = 702,
|
||||
|
||||
/* R APPL.CtrlAppl.sParam.heatpump[0].values.heatpumpState */
|
||||
RegisterHeatpumpState = 703,
|
||||
|
||||
/* R APPL.CtrlAppl.sParam.heatpump[0].HeatMeter.values.power
|
||||
* Actual power consumtion [W] */
|
||||
RegisterHeatMeterPowerConsumption = 706,
|
||||
|
||||
/* R APPL.CtrlAppl.sParam.heatpump[0].ElectricEnergyMeter.values.power
|
||||
* Actual power consumtion [W] */
|
||||
RegisterEnergyMeterPowerConsumption = 707,
|
||||
|
||||
/* RW APPL.CtrlAppl.sIOModule.Virt[0].param.sensor[0]
|
||||
* Acutal excess energy given from Smart home System [W] */
|
||||
RegisterActualExcessEnergySmartHome = 1000,
|
||||
|
||||
/* R APPL.CtrlAppl.sParam.photovoltaics.ElectricEnergyMeter.values.power
|
||||
* Acutal excess energy given from Electricity Meter [W] */
|
||||
RegisterActualExcessEnergySmartHomeElectricityMeter = 1002,
|
||||
|
||||
/* R APPL.CtrlAppl.sParam.outdoorTemp.values.actValue
|
||||
* Actual exterior temperature [°C] */
|
||||
RegisterActualOutdoorTemperature = 1502,
|
||||
|
||||
};
|
||||
|
||||
QHostAddress m_hostAddress;
|
||||
ModbusTCPMaster *m_modbusMaster = nullptr;
|
||||
|
||||
double m_roomTemperature = 0;
|
||||
double m_targetRoomTemperature = 0;
|
||||
double m_waterTankTopTemperature = 0;
|
||||
double m_bufferTankTemperature = 0;
|
||||
double m_totalAccumulatedHeatingEnergy = 0;
|
||||
double m_totalAccumulatedElectricalEnergy = 0;
|
||||
HeatpumpState m_heatPumpState = HeatpumpStateStandby;
|
||||
double m_heatMeterPowerConsumption = 0;
|
||||
double m_energyMeterPowerConsumption = 0;
|
||||
double m_actualExcessEnergySmartHome = 0;
|
||||
double m_actualExcessEnergySmartHomeElectricityMeter = 0;
|
||||
double m_actualOutdoorTemperature = 0;
|
||||
|
||||
signals:
|
||||
void connectedChanged(bool connected);
|
||||
|
||||
void roomTemperatureChanged(double roomTemperature);
|
||||
void targetRoomTemperatureChanged(double targetRoomTemperature);
|
||||
void waterTankTopTemperatureChanged(double waterTankTopTemperature);
|
||||
void bufferTankTemperatureChanged(double bufferTankTemperature);
|
||||
void totalAccumulatedHeatingEnergyChanged(double totalAccumulatedHeatingEnergy);
|
||||
void totalAccumulatedElectricalEnergyChanged(double totalAccumulatedElectricalEnergy);
|
||||
void heatPumpStateChanged(HeatpumpState heatPumpState);
|
||||
void heatMeterPowerConsumptionChanged(double heatMeterPowerConsumption);
|
||||
void energyMeterPowerConsumptionChanged(double energyMeterPowerConsumption);
|
||||
void actualExcessEnergySmartHomeChanged(double actualExcessEnergySmartHome);
|
||||
void actualExcessEnergySmartHomeElectricityMeterChanged(double actualExcessEnergySmartHomeElectricityMeter);
|
||||
void actualOutdoorTemperatureChanged(double actualOutdoorTemperature);
|
||||
|
||||
private slots:
|
||||
void onModbusError();
|
||||
void onReceivedHoldingRegister(int slaveAddress, int modbusRegister, const QVector<quint16> &value);
|
||||
|
||||
};
|
||||
|
||||
#endif // MTEC_H
|
||||
|
||||
16
mtec/mtec.pro
Normal file
16
mtec/mtec.pro
Normal file
@ -0,0 +1,16 @@
|
||||
include(../plugins.pri)
|
||||
|
||||
QT += \
|
||||
network \
|
||||
serialbus \
|
||||
|
||||
SOURCES += \
|
||||
mtec.cpp \
|
||||
integrationpluginmtec.cpp \
|
||||
../modbus/modbustcpmaster.cpp
|
||||
|
||||
HEADERS += \
|
||||
mtec.h \
|
||||
integrationpluginmtec.h \
|
||||
../modbus/modbustcpmaster.h \
|
||||
|
||||
146
mtec/translations/07cd316b-1e2c-40cf-8358-88d7407506ae-en_US.ts
Normal file
146
mtec/translations/07cd316b-1e2c-40cf-8358-88d7407506ae-en_US.ts
Normal file
@ -0,0 +1,146 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS>
|
||||
<TS version="2.1">
|
||||
<context>
|
||||
<name>IntegrationPluginMTec</name>
|
||||
<message>
|
||||
<location filename="../integrationpluginmtec.cpp" line="64"/>
|
||||
<source>No IP address given</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../integrationpluginmtec.cpp" line="85"/>
|
||||
<source>IP address already in use by another thing.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MTec</name>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/mtec/plugininfo.h" line="50"/>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/mtec/plugininfo.h" line="53"/>
|
||||
<source>Actual excess energy from Electricity Meter</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: mtec, EventType: actualExcessEnergyElectricityMeter, ID: {fd94d39c-0db6-497f-a0a5-6c5452cbcaaf})
|
||||
----------
|
||||
The name of the StateType ({fd94d39c-0db6-497f-a0a5-6c5452cbcaaf}) of ThingClass mtec</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/mtec/plugininfo.h" line="56"/>
|
||||
<source>Actual excess energy from Electricity Meter changed</source>
|
||||
<extracomment>The name of the EventType ({fd94d39c-0db6-497f-a0a5-6c5452cbcaaf}) of ThingClass mtec</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/mtec/plugininfo.h" line="59"/>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/mtec/plugininfo.h" line="62"/>
|
||||
<source>Actual excess energy from Smart home System</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: mtec, EventType: actualExcessEnergySmartHome, ID: {663718fa-807e-4d85-bd78-61a65f8c0b5e})
|
||||
----------
|
||||
The name of the StateType ({663718fa-807e-4d85-bd78-61a65f8c0b5e}) of ThingClass mtec</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/mtec/plugininfo.h" line="65"/>
|
||||
<source>Actual excess energy from Smart home System changed</source>
|
||||
<extracomment>The name of the EventType ({663718fa-807e-4d85-bd78-61a65f8c0b5e}) of ThingClass mtec</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/mtec/plugininfo.h" line="68"/>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/mtec/plugininfo.h" line="71"/>
|
||||
<source>Actual power consumption</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: mtec, EventType: actualPowerConsumption, ID: {c67c79cf-7369-409f-b170-16c4ece9d25a})
|
||||
----------
|
||||
The name of the StateType ({c67c79cf-7369-409f-b170-16c4ece9d25a}) of ThingClass mtec</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/mtec/plugininfo.h" line="74"/>
|
||||
<source>Actual power consumption changed</source>
|
||||
<extracomment>The name of the EventType ({c67c79cf-7369-409f-b170-16c4ece9d25a}) of ThingClass mtec</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/mtec/plugininfo.h" line="77"/>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/mtec/plugininfo.h" line="80"/>
|
||||
<source>Connected</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: mtec, EventType: connected, ID: {8d64954a-855d-44ea-8bc9-88a71ab47b6b})
|
||||
----------
|
||||
The name of the StateType ({8d64954a-855d-44ea-8bc9-88a71ab47b6b}) of ThingClass mtec</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/mtec/plugininfo.h" line="83"/>
|
||||
<source>Connected changed</source>
|
||||
<extracomment>The name of the EventType ({8d64954a-855d-44ea-8bc9-88a71ab47b6b}) of ThingClass mtec</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/mtec/plugininfo.h" line="86"/>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/mtec/plugininfo.h" line="89"/>
|
||||
<source>Control of the heat source by an external control [100%]</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: mtec, EventType: externalSetValueScaling, ID: {087c0296-705b-483a-b1e9-7ce08202c035})
|
||||
----------
|
||||
The name of the StateType ({087c0296-705b-483a-b1e9-7ce08202c035}) of ThingClass mtec</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/mtec/plugininfo.h" line="92"/>
|
||||
<source>Control of the heat source by an external control [100%] changed</source>
|
||||
<extracomment>The name of the EventType ({087c0296-705b-483a-b1e9-7ce08202c035}) of ThingClass mtec</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/mtec/plugininfo.h" line="95"/>
|
||||
<source>IP address</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: mtec, Type: thing, ID: {f1c43b1e-cffe-4d30-bda0-c23ed648dd71})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/mtec/plugininfo.h" line="98"/>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/mtec/plugininfo.h" line="101"/>
|
||||
<source>M-Tec</source>
|
||||
<extracomment>The name of the vendor ({04d3fa7c-e469-4a79-a119-155426e5a846})
|
||||
----------
|
||||
The name of the plugin MTec ({07cd316b-1e2c-40cf-8358-88d7407506ae})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/mtec/plugininfo.h" line="104"/>
|
||||
<source>MTec</source>
|
||||
<extracomment>The name of the ThingClass ({451e38d8-50d5-4ae9-8d9f-21af9347128d})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/mtec/plugininfo.h" line="107"/>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/mtec/plugininfo.h" line="110"/>
|
||||
<source>Request external heat source</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: mtec, EventType: requestExternalHeatSource, ID: {90b17788-ce63-47e3-b97d-1b025a41ce35})
|
||||
----------
|
||||
The name of the StateType ({90b17788-ce63-47e3-b97d-1b025a41ce35}) of ThingClass mtec</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/mtec/plugininfo.h" line="113"/>
|
||||
<source>Request external heat source changed</source>
|
||||
<extracomment>The name of the EventType ({90b17788-ce63-47e3-b97d-1b025a41ce35}) of ThingClass mtec</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/mtec/plugininfo.h" line="116"/>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/mtec/plugininfo.h" line="119"/>
|
||||
<source>Status</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: mtec, EventType: status, ID: {9bf5f8d6-116a-4399-a728-51470a3a5620})
|
||||
----------
|
||||
The name of the StateType ({9bf5f8d6-116a-4399-a728-51470a3a5620}) of ThingClass mtec</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/mtec/plugininfo.h" line="122"/>
|
||||
<source>Status changed</source>
|
||||
<extracomment>The name of the EventType ({9bf5f8d6-116a-4399-a728-51470a3a5620}) of ThingClass mtec</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
||||
@ -4,6 +4,7 @@ PLUGIN_DIRS = \
|
||||
drexelundweiss \
|
||||
energymeters \
|
||||
modbuscommander \
|
||||
mtec \
|
||||
mypv \
|
||||
sunspec \
|
||||
unipi \
|
||||
|
||||
Reference in New Issue
Block a user