// SPDX-License-Identifier: GPL-3.0-or-later

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright (C) 2013 - 2024, nymea GmbH
* Copyright (C) 2024 - 2025, chargebyte austria GmbH
*
* This file is part of nymea-plugins-modbus.
*
* nymea-plugins-modbus is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* nymea-plugins-modbus 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with nymea-plugins-modbus. If not, see <https://www.gnu.org/licenses/>.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#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::currentMSecsSinceEpoch() - 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::currentMSecsSinceEpoch() - 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::currentMSecsSinceEpoch();
    evaluateReachable();

    emit valuesUpdated();
}
