327 lines
14 KiB
C++
327 lines
14 KiB
C++
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
*
|
|
* 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 "speedwiremeter.h"
|
|
#include "extern-plugininfo.h"
|
|
|
|
#include "sma.h"
|
|
|
|
SpeedwireMeter::SpeedwireMeter(SpeedwireInterface *speedwireInterface, quint16 modelId, quint32 serialNumber, QObject *parent) :
|
|
QObject(parent),
|
|
m_speedwireInterface(speedwireInterface),
|
|
m_modelId(modelId),
|
|
m_serialNumber(serialNumber)
|
|
{
|
|
connect(m_speedwireInterface, &SpeedwireInterface::dataReceived, this, &SpeedwireMeter::processData);
|
|
|
|
// Reachable timestamp
|
|
m_timer.setInterval(5000);
|
|
m_timer.setSingleShot(false);
|
|
connect(&m_timer, &QTimer::timeout, this, &SpeedwireMeter::evaluateReachable);
|
|
}
|
|
|
|
bool SpeedwireMeter::reachable() const
|
|
{
|
|
return m_reachable;
|
|
}
|
|
|
|
double SpeedwireMeter::currentPower() const
|
|
{
|
|
return m_currentPower;
|
|
}
|
|
|
|
double SpeedwireMeter::totalEnergyProduced() const
|
|
{
|
|
return m_totalEnergyProduced;
|
|
}
|
|
|
|
double SpeedwireMeter::totalEnergyConsumed() const
|
|
{
|
|
return m_totalEnergyConsumed;
|
|
}
|
|
|
|
double SpeedwireMeter::energyConsumedPhaseA() const
|
|
{
|
|
return m_energyConsumedPhaseA;
|
|
}
|
|
|
|
double SpeedwireMeter::energyConsumedPhaseB() const
|
|
{
|
|
return m_energyConsumedPhaseB;
|
|
}
|
|
|
|
double SpeedwireMeter::energyConsumedPhaseC() const
|
|
{
|
|
return m_energyConsumedPhaseC;
|
|
}
|
|
|
|
double SpeedwireMeter::energyProducedPhaseA() const
|
|
{
|
|
return m_energyProducedPhaseA;
|
|
}
|
|
|
|
double SpeedwireMeter::energyProducedPhaseB() const
|
|
{
|
|
return m_energyProducedPhaseB;
|
|
}
|
|
|
|
double SpeedwireMeter::energyProducedPhaseC() const
|
|
{
|
|
return m_energyProducedPhaseC;
|
|
}
|
|
|
|
double SpeedwireMeter::currentPowerPhaseA() const
|
|
{
|
|
return m_currentPowerPhaseA;
|
|
}
|
|
|
|
double SpeedwireMeter::currentPowerPhaseB() const
|
|
{
|
|
return m_currentPowerPhaseB;
|
|
}
|
|
|
|
double SpeedwireMeter::currentPowerPhaseC() const
|
|
{
|
|
return m_currentPowerPhaseC;
|
|
}
|
|
|
|
double SpeedwireMeter::voltagePhaseA() const
|
|
{
|
|
return m_voltagePhaseA;
|
|
}
|
|
|
|
double SpeedwireMeter::voltagePhaseB() const
|
|
{
|
|
return m_voltagePhaseB;
|
|
}
|
|
|
|
double SpeedwireMeter::voltagePhaseC() const
|
|
{
|
|
return m_voltagePhaseC;
|
|
}
|
|
|
|
double SpeedwireMeter::amperePhaseA() const
|
|
{
|
|
return m_amperePhaseA;
|
|
}
|
|
|
|
double SpeedwireMeter::amperePhaseB() const
|
|
{
|
|
return m_amperePhaseB;
|
|
}
|
|
|
|
double SpeedwireMeter::amperePhaseC() const
|
|
{
|
|
return m_amperePhaseC;
|
|
}
|
|
|
|
QString SpeedwireMeter::softwareVersion() const
|
|
{
|
|
return m_softwareVersion;
|
|
}
|
|
|
|
void SpeedwireMeter::evaluateReachable()
|
|
{
|
|
// Note: the meter sends every second the data on the multicast
|
|
// If the meter has not sent data within the last 5 seconds it seems not to be reachable
|
|
bool reachable = false;
|
|
if (QDateTime::currentDateTime().toMSecsSinceEpoch() - m_lastSeenTimestamp < 5000) {
|
|
reachable = true;
|
|
}
|
|
|
|
if (m_reachable != reachable) {
|
|
qCDebug(dcSma()) << "Meter: reachable changed to" << reachable;
|
|
m_reachable = reachable;
|
|
emit reachableChanged(m_reachable);
|
|
}
|
|
|
|
// Restart the timer
|
|
if (m_reachable) {
|
|
m_timer.start();
|
|
} else {
|
|
// Reachable will be triggered automatically once data arrives
|
|
// No need to run the timer all the time
|
|
m_timer.stop();
|
|
}
|
|
}
|
|
|
|
void SpeedwireMeter::processData(const QHostAddress &senderAddress, quint16 senderPort, const QByteArray &data, bool multicast)
|
|
{
|
|
Q_UNUSED(multicast)
|
|
|
|
QDataStream stream(data);
|
|
stream.setByteOrder(QDataStream::BigEndian);
|
|
|
|
Speedwire::Header header = Speedwire::parseHeader(stream);
|
|
if (!header.isValid()) {
|
|
qCDebug(dcSma()) << "Meter: Datagram header is not valid. Ignoring data...";
|
|
return;
|
|
}
|
|
|
|
if (header.protocolId != Speedwire::ProtocolIdMeter)
|
|
return;
|
|
|
|
quint16 modelId;
|
|
quint32 serialNumber;
|
|
stream >> modelId >> serialNumber;
|
|
|
|
// Make sure this payload belongs to us
|
|
if (m_modelId != modelId || serialNumber != m_serialNumber)
|
|
return;
|
|
|
|
//qCDebug(dcSma()) << "Meter: data received" << data.toHex();
|
|
qCDebug(dcSma()).noquote() << "Meter: Measurements received from" << QString("%1:%2").arg(senderAddress.toString()).arg(senderPort) << "Serial number:" << serialNumber << "Model ID:" << modelId;
|
|
|
|
// Make sure the rate is at max 1Hz, some meters send much more data, which creates an uneccessary load
|
|
if (QDateTime::currentDateTime().toMSecsSinceEpoch() - m_lastSeenTimestamp < 1000)
|
|
return;
|
|
|
|
// Parse the packet data
|
|
// Timestamp e618a416
|
|
qCDebug(dcSma()) << "Meter: ======================= Meter measurements";
|
|
quint32 timestamp;
|
|
stream >> timestamp;
|
|
qCDebug(dcSma()) << "Meter: Timestamp:" << timestamp << QDateTime::fromMSecsSinceEpoch(static_cast<qulonglong>(timestamp) * 1000);
|
|
|
|
// Obis data
|
|
//00 01 04 00 00000000 00 01 08 00 0000002139122910 00 02 04 00 00004415 00 02 08 00 0000001575a137d8 00 03 04 00 00000000 00 03 08 00 00000003debed0e8 00040400000017c6000408000000001008c2070000090400000000000009080000000027c77bed20000a04000000481d000a08000000001722823410000d0400000003b00015040000000000001508000000000d1e1e0e3000160400000015120016080000000006c5a2d8b800170400000000000017080000000001bd6f680000180400000007990018080000000004def712b8001d040000000000001d08000000000eeefaafd0001e040000001666001e0800000000074b38bf88001f040000000a300020040000037bcb00210400000003ad0029040000000000002908000000000a9b1afec8002a040000001a81002a08000000000803e62b88002b040000000000002b080000000001511459b8002c0400000006d5002c0800000000052c8455b80031040000000000003108000000000cf83b37100032040000001b5f0032080000000008a6e257f80033040000000c3f003404000003747900350400000003c8003d040000000000003d08000000000a53d0ba08003e040000001482003e080000000007800fd188003f040000000000003f080000000001185820c8004004000000095800400800000000064563b1900045040000000000004508000000000d26d3eae0004604000000168900460800000000082b4fc5a80047040000000a440048040000037ed1004904000000038e90000000 01020852 00000000
|
|
while (!stream.atEnd()) {
|
|
quint8 measurementChannel;
|
|
quint8 measurementIndex;
|
|
quint8 measurmentType;
|
|
quint8 measurmentTariff;
|
|
|
|
stream >> measurementChannel >> measurementIndex >> measurmentType >> measurmentTariff;
|
|
|
|
if (measurmentType == 4) {
|
|
qint32 measurement;
|
|
stream >> measurement;
|
|
|
|
if (measurementIndex == 1 && measurement != 0) {
|
|
m_currentPower = measurement / 10.0;
|
|
qCDebug(dcSma()) << "Meter: Current power" << m_currentPower << "W";
|
|
} else if (measurementIndex == 2 && measurement != 0) {
|
|
m_currentPower = -measurement / 10.0;
|
|
qCDebug(dcSma()) << "Meter: Current power" << m_currentPower << "W";
|
|
} else if (measurementIndex == 21 && measurement != 0) {
|
|
m_currentPowerPhaseA = measurement / 10.0;
|
|
qCDebug(dcSma()) << "Meter: Current power phase A" << m_currentPowerPhaseA << "W";
|
|
} else if (measurementIndex == 22 && measurement != 0) {
|
|
m_currentPowerPhaseA = -measurement / 10.0;
|
|
qCDebug(dcSma()) << "Meter: Current power phase A" << m_currentPowerPhaseA << "W";
|
|
} else if (measurementIndex == 41 && measurement != 0) {
|
|
m_currentPowerPhaseB = measurement / 10.0;
|
|
qCDebug(dcSma()) << "Meter: Current power phase B" << m_currentPowerPhaseB << "W";
|
|
} else if (measurementIndex == 42 && measurement != 0) {
|
|
m_currentPowerPhaseB = -measurement / 10.0;
|
|
qCDebug(dcSma()) << "Meter: Current power phase B" << m_currentPowerPhaseB << "W";
|
|
} else if (measurementIndex == 61 && measurement != 0) {
|
|
m_currentPowerPhaseC = measurement / 10.0;
|
|
qCDebug(dcSma()) << "Meter: Current power phase C" << m_currentPowerPhaseC << "W";
|
|
} else if (measurementIndex == 62 && measurement != 0) {
|
|
m_currentPowerPhaseC = -measurement / 10.0;
|
|
qCDebug(dcSma()) << "Meter: Current power phase C" << m_currentPowerPhaseC << "W";
|
|
} else if (measurementIndex == 31) {
|
|
m_amperePhaseA = measurement / 1000.0;
|
|
qCDebug(dcSma()) << "Meter: Ampere phase A" << m_amperePhaseA << "A";
|
|
} else if (measurementIndex == 51) {
|
|
m_amperePhaseB = measurement / 1000.0;
|
|
qCDebug(dcSma()) << "Meter: Ampere phase B" << m_amperePhaseB << "A";
|
|
} else if (measurementIndex == 71) {
|
|
m_amperePhaseC = measurement / 1000.0;
|
|
qCDebug(dcSma()) << "Meter: Ampere phase C" << m_amperePhaseC << "A";
|
|
} else if (measurementIndex == 32) {
|
|
m_voltagePhaseA = measurement / 1000.0;
|
|
qCDebug(dcSma()) << "Meter: Voltage phase A" << m_voltagePhaseA << "V";
|
|
} else if (measurementIndex == 52) {
|
|
m_voltagePhaseB = measurement / 1000.0;
|
|
qCDebug(dcSma()) << "Meter: Voltage phase B" << m_voltagePhaseB << "V";
|
|
} else if (measurementIndex == 72) {
|
|
m_voltagePhaseC = measurement / 1000.0;
|
|
qCDebug(dcSma()) << "Meter: Voltage phase C" << m_voltagePhaseC << "V";
|
|
} else {
|
|
// qCDebug(dcSma()) << "Meter: --> Channel:" << measurementChannel << "Index:" << measurementIndex << "Type:" << measurmentType << "Rate:" << measurmentTariff;
|
|
// qCDebug(dcSma()) << "Meter: Value:" << measurement;
|
|
}
|
|
|
|
|
|
} else if (measurmentType == 8) {
|
|
qint64 measurement;
|
|
stream >> measurement;
|
|
|
|
if (measurementIndex == 1 && measurement != 0) {
|
|
m_totalEnergyConsumed = measurement / 3600000.0;
|
|
qCDebug(dcSma()) << "Meter: Total energy consumed" << m_totalEnergyConsumed << "kWh";
|
|
} else if (measurementIndex == 2 && measurement != 0) {
|
|
m_totalEnergyProduced = measurement / 3600000.0;
|
|
qCDebug(dcSma()) << "Meter: Total energy produced" << m_totalEnergyProduced << "kWh";
|
|
} else if (measurementIndex == 21 && measurement != 0) {
|
|
m_energyConsumedPhaseA = measurement / 3600000.0;
|
|
qCDebug(dcSma()) << "Meter: Energy consumed phase A" << m_energyConsumedPhaseA << "kWh";
|
|
} else if (measurementIndex == 41 && measurement != 0) {
|
|
m_energyConsumedPhaseB = measurement / 3600000.0;
|
|
qCDebug(dcSma()) << "Meter: Energy consumed phase B" << m_energyConsumedPhaseB << "kWh";
|
|
} else if (measurementIndex == 61 && measurement != 0) {
|
|
m_energyConsumedPhaseC = measurement / 3600000.0;
|
|
qCDebug(dcSma()) << "Meter: Energy consumed phase C" << m_energyConsumedPhaseC << "kWh";
|
|
} else if (measurementIndex == 22 && measurement != 0) {
|
|
m_energyProducedPhaseA = measurement / 3600000.0;
|
|
qCDebug(dcSma()) << "Meter: Energy produced phase A" << m_energyProducedPhaseA << "kWh";
|
|
} else if (measurementIndex == 42 && measurement != 0) {
|
|
m_energyProducedPhaseB = measurement / 3600000.0;
|
|
qCDebug(dcSma()) << "Meter: Energy produced phase B" << m_energyProducedPhaseB << "kWh";
|
|
} else if (measurementIndex == 62 && measurement != 0) {
|
|
m_energyProducedPhaseC = measurement / 3600000.0;
|
|
qCDebug(dcSma()) << "Meter: Energy produced phase C" << m_energyProducedPhaseC << "kWh";
|
|
} else {
|
|
// qCDebug(dcSma()) << "Meter: --> Channel:" << measurementChannel << "Index:" << measurementIndex << "Type:" << measurmentType << "Rate:" << measurmentTariff;
|
|
// qCDebug(dcSma()) << "Meter: Value:" << measurement;
|
|
}
|
|
|
|
} if (measurementChannel == 144 && measurementIndex == 0 && measurmentType == 0 && measurmentTariff == 0) {
|
|
// Software version
|
|
// 90000000 01 02 08 52
|
|
quint32 versionData;
|
|
stream >> versionData;
|
|
m_softwareVersion = Sma::buildSoftwareVersionString(versionData);
|
|
qCDebug(dcSma()) << "Meter: Software version" << m_softwareVersion;
|
|
} else if (measurementChannel == 0 && measurementIndex == 0 && measurmentType == 0 && measurmentTariff == 0) {
|
|
// 00 00 00 00
|
|
//qCDebug(dcSma()) << "Meter: End of data reached.";
|
|
}
|
|
}
|
|
|
|
// Save the current timestamp for reachable evaluation
|
|
m_lastSeenTimestamp = QDateTime::currentDateTime().toMSecsSinceEpoch();
|
|
evaluateReachable();
|
|
|
|
emit valuesUpdated();
|
|
}
|