// 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 "integrationpluginmennekes.h"
#include "plugininfo.h"
#include "amtronecudiscovery.h"
#include "amtronhcc3discovery.h"
#include "amtroncompact20discovery.h"

#include <network/networkdevicediscovery.h>
#include <hardwaremanager.h>

QHash<AmtronCompact20ModbusRtuConnection::SolarChargingMode, QString> solarChargingModeMap {
    {AmtronCompact20ModbusRtuConnection::SolarChargingModeOff, "Off"},
    {AmtronCompact20ModbusRtuConnection::SolarChargingModeStandard, "Standard"},
    {AmtronCompact20ModbusRtuConnection::SolarChargingModeSunshine, "Sunshine"},
    {AmtronCompact20ModbusRtuConnection::SolarChargingModeSunshinePlus, "Sunshine+"}
};


IntegrationPluginMennekes::IntegrationPluginMennekes()
{

}

void IntegrationPluginMennekes::discoverThings(ThingDiscoveryInfo *info)
{

    if (info->thingClassId() == amtronECUThingClassId) {
        if (!hardwareManager()->networkDeviceDiscovery()->available()) {
            qCWarning(dcMennekes()) << "The network discovery is not available on this platform.";
            info->finish(Thing::ThingErrorUnsupportedFeature, QT_TR_NOOP("The network device discovery is not available."));
            return;
        }

        AmtronECUDiscovery *discovery = new AmtronECUDiscovery(hardwareManager()->networkDeviceDiscovery(), info);
        connect(discovery, &AmtronECUDiscovery::discoveryFinished, info, [=](){
            foreach (const AmtronECUDiscovery::Result &result, discovery->discoveryResults()) {

                QString name = "AMTRON Charge Control/Professional";
                QString description = result.model.isEmpty() ? result.networkDeviceInfo.address().toString() :
                                          result.model + " (" + result.networkDeviceInfo.address().toString() + ")";
                if (result.model.startsWith("CC")) {
                    name = "AMTRON Charge Control";
                } else if (result.model.startsWith("P")) {
                    name = "AMTRON Professional";
                } else {
                    qCWarning(dcMennekes()) << "Unknown Amtron model:" << result.model;
                }
                ThingDescriptor descriptor(amtronECUThingClassId, name, description);
                qCDebug(dcMennekes()) << "Discovered:" << descriptor.title() << descriptor.description();

                ParamList params;
                params << Param(amtronECUThingMacAddressParamTypeId, result.networkDeviceInfo.thingParamValueMacAddress());
                params << Param(amtronECUThingHostNameParamTypeId, result.networkDeviceInfo.thingParamValueHostName());
                params << Param(amtronECUThingAddressParamTypeId, result.networkDeviceInfo.thingParamValueAddress());
                // Note: if we discover also the port and modbusaddress, we must fill them in from the discovery here, for now everywhere the defaults...
                descriptor.setParams(params);

                // Check if we already have set up this device
                Thing *existingThing = myThings().findByParams(params);
                if (existingThing) {
                    qCDebug(dcMennekes()) << "This wallbox already exists in the system:" << result.networkDeviceInfo;
                    descriptor.setThingId(existingThing->id());
                }
                info->addThingDescriptor(descriptor);
            }

            info->finish(Thing::ThingErrorNoError);
        });

        discovery->startDiscovery();

    } else if (info->thingClassId() == amtronHCC3ThingClassId) {
        if (!hardwareManager()->networkDeviceDiscovery()->available()) {
            qCWarning(dcMennekes()) << "The network discovery is not available on this platform.";
            info->finish(Thing::ThingErrorUnsupportedFeature, QT_TR_NOOP("The network device discovery is not available."));
            return;
        }

        AmtronHCC3Discovery *discovery = new AmtronHCC3Discovery(hardwareManager()->networkDeviceDiscovery(), info);
        connect(discovery, &AmtronHCC3Discovery::discoveryFinished, info, [=](){
            foreach (const AmtronHCC3Discovery::AmtronDiscoveryResult &result, discovery->discoveryResults()) {

                if (result.serialNumber.isEmpty()) {
                    qCWarning(dcMennekes()) << "Unable to read Amtron serial number:" << result.serialNumber << result.wallboxName;
                    continue;
                }

                QString description = "Serial: " + result.serialNumber + " - " + result.networkDeviceInfo.address().toString();
                ThingDescriptor descriptor(amtronHCC3ThingClassId, result.wallboxName, description);
                qCDebug(dcMennekes()) << "Discovered:" << descriptor.title() << descriptor.description();

                ParamList params;
                params << Param(amtronHCC3ThingMacAddressParamTypeId, result.networkDeviceInfo.thingParamValueMacAddress());
                params << Param(amtronHCC3ThingHostNameParamTypeId, result.networkDeviceInfo.thingParamValueHostName());
                params << Param(amtronHCC3ThingAddressParamTypeId, result.networkDeviceInfo.thingParamValueAddress());
                // Note: if we discover also the port and modbusaddress, we must fill them in from the discovery here, for now everywhere the defaults...
                descriptor.setParams(params);

                // Check if we already have set up this device
                Thing *existingThing = myThings().findByParams(params);
                if (existingThing) {
                    qCDebug(dcMennekes()) << "This wallbox already exists in the system:" << result.networkDeviceInfo;
                    descriptor.setThingId(existingThing->id());
                }

                info->addThingDescriptor(descriptor);
            }

            info->finish(Thing::ThingErrorNoError);
        });

        discovery->startDiscovery();

    } else if (info->thingClassId() == amtronCompact20ThingClassId) {
        AmtronCompact20Discovery *discovery = new AmtronCompact20Discovery(hardwareManager()->modbusRtuResource(), info);
        connect(discovery, &AmtronCompact20Discovery::discoveryFinished, info, [this, info, discovery](bool modbusMasterAvailable){
            if (!modbusMasterAvailable) {
                info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("No modbus RTU master with appropriate settings found. Please set up a modbus RTU master with a baudrate of 57600, 8 data bits, 2 stop bits and no parity first."));
                return;
            }

            qCInfo(dcMennekes()) << "Discovery results:" << discovery->discoveryResults().length();

            foreach (const AmtronCompact20Discovery::Result &result, discovery->discoveryResults()) {
                ThingDescriptor descriptor(amtronCompact20ThingClassId, "AMTRON Compact 2.0s", QString("Slave ID: %1, Serial: %2").arg(result.slaveId).arg(result.serialNumber));

                ParamList params{
                    {amtronCompact20ThingModbusMasterUuidParamTypeId, result.modbusRtuMasterId},
                    {amtronCompact20ThingSlaveAddressParamTypeId, result.slaveId}
                };
                descriptor.setParams(params);

                Thing *existingThing = myThings().findByParams(params);
                if (existingThing) {
                    descriptor.setThingId(existingThing->id());
                }
                info->addThingDescriptor(descriptor);
            }

            info->finish(Thing::ThingErrorNoError);
        });

        discovery->startDiscovery();
    }
}

void IntegrationPluginMennekes::setupThing(ThingSetupInfo *info)
{
    Thing *thing = info->thing();
    qCDebug(dcMennekes()) << "Setup" << thing << thing->params();

    if (thing->thingClassId() == amtronECUThingClassId) {

        if (m_amtronECUConnections.contains(thing)) {
            qCDebug(dcMennekes()) << "Reconfiguring existing thing" << thing->name();
            m_amtronECUConnections.take(thing)->deleteLater();

            if (m_monitors.contains(thing)) {
                hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing));
            }
        }

        qCDebug(dcMennekes()) << "Setting up MAC address based ModbusTcp connection for" << thing;
        NetworkDeviceMonitor *monitor = hardwareManager()->networkDeviceDiscovery()->registerMonitor(thing);
        if (!monitor) {
            qCWarning(dcMennekes()) << "Unable to register monitor. Probably incomplete paramters. Please reconfigure the thing.";
            info->finish(Thing::ThingErrorInvalidParameter, QT_TR_NOOP("Parameters incomplete. Please reconfigure the thing."));
            return;
        }

        m_monitors.insert(thing, monitor);

        // Make sure the monitor gets cleaned up when setup gets aborted
        connect(info, &ThingSetupInfo::aborted, monitor, [this, thing](){
            if (m_monitors.contains(thing)) {
                qCDebug(dcMennekes()) << "Unregistering monitor because setup has been aborted.";
                hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing));
            }
        });

        // If this is the initial setup, wait for the monitor to be reachable and make sure
        // we have an IP address, otherwise let the monitor do his work
        if (info->isInitialSetup()) {
            // Continue with setup only if we know that the network device is reachable
            if (monitor->reachable()) {
                setupAmtronECUConnection(info);
            } else {
                // otherwise wait until we reach the networkdevice before setting up the device
                qCDebug(dcMennekes()) << "Network device" << thing->name() << "is not reachable yet. Continue with the setup once reachable.";
                connect(monitor, &NetworkDeviceMonitor::reachableChanged, info, [=](bool reachable){
                    if (reachable) {
                        qCDebug(dcMennekes()) << "Network device" << thing->name() << "is now reachable. Continue with the setup...";
                        setupAmtronECUConnection(info);
                    }
                });
            }
        } else {
            // Continue setup with monitor...
            setupAmtronECUConnection(info);
        }
        return;
    }

    if (info->thing()->thingClassId() == amtronHCC3ThingClassId) {
        if (m_amtronHCC3Connections.contains(thing)) {
            qCDebug(dcMennekes()) << "Reconfiguring existing thing" << thing->name();
            m_amtronHCC3Connections.take(thing)->deleteLater();

            if (m_monitors.contains(thing)) {
                hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing));
            }
        }

        qCDebug(dcMennekes()) << "Setting up MAC address based ModbusTcp connection for" << thing;
        NetworkDeviceMonitor *monitor = hardwareManager()->networkDeviceDiscovery()->registerMonitor(thing);
        if (!monitor) {
            qCWarning(dcMennekes()) << "Unable to register monitor. Probably incomplete paramters. Please reconfigure the thing.";
            info->finish(Thing::ThingErrorInvalidParameter, QT_TR_NOOP("Parameters incomplete. Please reconfigure the thing."));
            return;
        }

        m_monitors.insert(thing, monitor);

        // Make sure the monitor gets cleaned up when setup gets aborted
        connect(info, &ThingSetupInfo::aborted, monitor, [this, thing](){
            if (m_monitors.contains(thing)) {
                qCDebug(dcMennekes()) << "Unregistering monitor because setup has been aborted.";
                hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing));
            }
        });

        // If this is the initial setup, wait for the monitor to be reachable and make sure
        // we have an IP address, otherwise let the monitor do his work
        if (info->isInitialSetup()) {
            // Continue with setup only if we know that the network device is reachable
            if (monitor->reachable()) {
                setupAmtronHCC3Connection(info);
            } else {
                // otherwise wait until we reach the networkdevice before setting up the device
                qCDebug(dcMennekes()) << "Network device" << thing->name() << "is not reachable yet. Continue with the setup once reachable.";
                connect(monitor, &NetworkDeviceMonitor::reachableChanged, info, [=](bool reachable){
                    if (reachable) {
                        qCDebug(dcMennekes()) << "Network device" << thing->name() << "is now reachable. Continue with the setup...";
                        setupAmtronHCC3Connection(info);
                    }
                });
            }
        } else {
            // Continue setup with monitor...
            setupAmtronHCC3Connection(info);
        }

        return;
    }

    if (info->thing()->thingClassId() == amtronCompact20ThingClassId) {
        if (m_amtronCompact20Connections.contains(thing)) {
            qCDebug(dcMennekes()) << "Reconfiguring existing thing" << thing->name();
            m_amtronCompact20Connections.take(thing)->deleteLater();
        }

        setupAmtronCompact20Connection(info);
        return;
    }
}

void IntegrationPluginMennekes::postSetupThing(Thing *thing)
{
    Q_UNUSED(thing)
    if (!m_pluginTimer) {
        qCDebug(dcMennekes()) << "Starting plugin timer...";
        m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(2);
        connect(m_pluginTimer, &PluginTimer::timeout, this, [this] {
            foreach(AmtronECU *connection, m_amtronECUConnections) {
                qCDebug(dcMennekes()) << "Updating connection" << connection->modbusTcpMaster()->hostAddress().toString();
                connection->update();
            }
            foreach(AmtronHCC3ModbusTcpConnection *connection, m_amtronHCC3Connections) {
                qCDebug(dcMennekes()) << "Updating connection" << connection->modbusTcpMaster()->hostAddress().toString();
                connection->update();
            }
            foreach(AmtronCompact20ModbusRtuConnection *connection, m_amtronCompact20Connections) {
                qCDebug(dcMennekes()) << "Updating connection" << connection->modbusRtuMaster()->serialPort() << connection->slaveId();
                connection->update();
                connection->setHeartbeat(0x55AA);
            }
        });

        m_pluginTimer->start();
    }
}

void IntegrationPluginMennekes::executeAction(ThingActionInfo *info)
{
    if (info->thing()->thingClassId() == amtronECUThingClassId) {
        AmtronECU *amtronECUConnection = m_amtronECUConnections.value(info->thing());

        if (info->action().actionTypeId() == amtronECUPowerActionTypeId) {
            bool power = info->action().paramValue(amtronECUPowerActionPowerParamTypeId).toBool();
            double maxChargingCurrent = info->thing()->stateValue(amtronECUMaxChargingCurrentStateTypeId).toDouble();
            int effectiveCurrent = power ? qRound(maxChargingCurrent) : 0;
            qCInfo(dcMennekes()) << "Executing power action:" << power << "max current:" << maxChargingCurrent << "-> effective current" << effectiveCurrent;
            QModbusReply *reply = amtronECUConnection->setHemsCurrentLimit(effectiveCurrent);
            if (!reply) {
                qCWarning(dcMennekes()) << "Could not execute action:" << amtronECUConnection->modbusTcpMaster()->errorString();
                info->finish(Thing::ThingErrorHardwareFailure);
                return;
            }

            connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
            connect(reply, &QModbusReply::finished, info, [info, reply, power](){
                if (reply->error() == QModbusDevice::NoError) {
                    info->thing()->setStateValue(amtronECUPowerStateTypeId, power);
                    info->finish(Thing::ThingErrorNoError);
                } else {
                    qCWarning(dcMennekes()) << "Error setting cp availability:" << reply->error() << reply->errorString();
                    info->finish(Thing::ThingErrorHardwareFailure);
                }
            });
        }

        if (info->action().actionTypeId() == amtronECUMaxChargingCurrentActionTypeId) {
            bool power = info->thing()->stateValue(amtronECUPowerStateTypeId).toBool();
            int maxChargingCurrent = qRound(info->action().paramValue(amtronECUMaxChargingCurrentActionMaxChargingCurrentParamTypeId).toDouble());
            int effectiveCurrent = power ? maxChargingCurrent : 0;
            qCInfo(dcMennekes()) << "Executing max current action:" << maxChargingCurrent << "Power is" << power << "-> effective:" << effectiveCurrent;
            QModbusReply *reply = amtronECUConnection->setHemsCurrentLimit(effectiveCurrent);
            if (!reply) {
                qCWarning(dcMennekes()) << "Could not execute action:" << amtronECUConnection->modbusTcpMaster()->errorString();
                info->finish(Thing::ThingErrorHardwareFailure);
                return;
            }

            connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
            connect(reply, &QModbusReply::finished, info, [info, reply, maxChargingCurrent](){
                if (reply->error() == QModbusDevice::NoError) {
                    info->thing()->setStateValue(amtronECUMaxChargingCurrentStateTypeId, maxChargingCurrent);
                    info->finish(Thing::ThingErrorNoError);
                } else {
                    info->finish(Thing::ThingErrorHardwareFailure);
                }
            });
        }
    }

    if (info->thing()->thingClassId() == amtronHCC3ThingClassId) {
        AmtronHCC3ModbusTcpConnection *amtronHCC3Connection = m_amtronHCC3Connections.value(info->thing());

        if (info->action().actionTypeId() == amtronHCC3PowerActionTypeId) {
            bool power = info->action().paramValue(amtronHCC3PowerActionPowerParamTypeId).toBool();

            AmtronHCC3ModbusTcpConnection::ChargeState chargeState;
            if (power) {
                // When turning on, we'll need to either start or resume, depending on the current state
                if (amtronHCC3Connection->amtronState() == AmtronHCC3ModbusTcpConnection::AmtronStatePaused) {
                    chargeState = AmtronHCC3ModbusTcpConnection::ChargeStateContinue;
                } else {
                    chargeState = AmtronHCC3ModbusTcpConnection::ChargeStateStart;
                }
            } else {
                // We'll just use Pause as a Terminate command would requre an EV re-plug in order to resume.
                chargeState = AmtronHCC3ModbusTcpConnection::ChargeStatePause;
            }

            QModbusReply *reply = amtronHCC3Connection->setChangeChargeState(chargeState);
            if (!reply) {
                qCWarning(dcMennekes()) << "Could not execute action:" << amtronHCC3Connection->modbusTcpMaster()->errorString();
                info->finish(Thing::ThingErrorHardwareFailure);
                return;
            }

            connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
            connect(reply, &QModbusReply::finished, info, [info, reply, power](){
                if (reply->error() == QModbusDevice::NoError) {
                    info->thing()->setStateValue(amtronHCC3PowerStateTypeId, power);
                    info->finish(Thing::ThingErrorNoError);
                } else {
                    qCWarning(dcMennekes()) << "Error setting charge state:" << reply->error() << reply->errorString();
                    info->finish(Thing::ThingErrorHardwareFailure);
                }
            });
        }
        if (info->action().actionTypeId() == amtronHCC3MaxChargingCurrentActionTypeId) {
            int maxChargingCurrent = info->action().paramValue(amtronHCC3MaxChargingCurrentActionMaxChargingCurrentParamTypeId).toInt();
            QModbusReply *reply = amtronHCC3Connection->setCustomerCurrentLimitation(maxChargingCurrent);
            if (!reply) {
                qCWarning(dcMennekes()) << "Could not execute action:" << amtronHCC3Connection->modbusTcpMaster()->errorString();
                info->finish(Thing::ThingErrorHardwareFailure);
                return;
            }

            connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
            connect(reply, &QModbusReply::finished, info, [info, reply, maxChargingCurrent](){
                if (reply->error() == QModbusDevice::NoError) {
                    info->thing()->setStateValue(amtronHCC3MaxChargingCurrentStateTypeId, maxChargingCurrent);
                    info->finish(Thing::ThingErrorNoError);
                } else {
                    info->finish(Thing::ThingErrorHardwareFailure);
                }
            });
        }
    }

    if (info->thing()->thingClassId() == amtronCompact20ThingClassId) {
        AmtronCompact20ModbusRtuConnection *amtronCompact20Connection = m_amtronCompact20Connections.value(info->thing());

        if (info->action().actionTypeId() == amtronCompact20PowerActionTypeId) {
            bool power = info->action().paramValue(amtronCompact20PowerActionPowerParamTypeId).toBool();

            ModbusRtuReply *reply = amtronCompact20Connection->setChargingReleaseEnergyManager(power ? 1 : 0);
            // Note: modbus RTU replies delete them self on finished
            connect(reply, &ModbusRtuReply::finished, info, [info, reply, power](){
                if (reply->error() == ModbusRtuReply::NoError) {
                    info->thing()->setStateValue(amtronCompact20PowerStateTypeId, power);
                    info->finish(Thing::ThingErrorNoError);
                } else {
                    qCWarning(dcMennekes()) << "Error setting charge state:" << reply->error() << reply->errorString();
                    info->finish(Thing::ThingErrorHardwareFailure);
                }
            });
        }
        if (info->action().actionTypeId() == amtronCompact20MaxChargingCurrentActionTypeId) {
            double maxChargingCurrent = info->action().paramValue(amtronCompact20MaxChargingCurrentActionMaxChargingCurrentParamTypeId).toDouble();
            float value = static_cast<float>(maxChargingCurrent);

            // Note: in firmwares up to 1.0.2 there's an issue that it cannot be exactly 6A, must be 6.01 or so
            if (maxChargingCurrent == 6) {
                value = 6.01;
            }

            ModbusRtuReply *reply = amtronCompact20Connection->setChargingCurrentEnergyManager(value);
            // Note: modbus RTU replies delete them self on finished
            connect(reply, &ModbusRtuReply::finished, info, [info, reply, maxChargingCurrent](){
                if (reply->error() == ModbusRtuReply::NoError) {
                    info->thing()->setStateValue(amtronCompact20MaxChargingCurrentStateTypeId, maxChargingCurrent);
                    info->finish(Thing::ThingErrorNoError);
                } else {
                    info->finish(Thing::ThingErrorHardwareFailure);
                }
            });
        }
        if (info->action().actionTypeId() == amtronCompact20DesiredPhaseCountActionTypeId) {
            int desiredPhaseCount = info->action().paramValue(amtronCompact20DesiredPhaseCountActionDesiredPhaseCountParamTypeId).toInt();

            ModbusRtuReply *reply = amtronCompact20Connection->setRequestedPhases(desiredPhaseCount == 1 ? AmtronCompact20ModbusRtuConnection::PhaseModeSingle : AmtronCompact20ModbusRtuConnection::PhaseModeAll);
            // Note: modbus RTU replies delete them self on finished
            connect(reply, &ModbusRtuReply::finished, info, [info, reply, desiredPhaseCount](){
                if (reply->error() == ModbusRtuReply::NoError) {
                    info->thing()->setStateValue(amtronCompact20DesiredPhaseCountStateTypeId, desiredPhaseCount);
                    info->finish(Thing::ThingErrorNoError);
                } else {
                    info->finish(Thing::ThingErrorHardwareFailure);
                }
            });
        }
        if (info->action().actionTypeId() == amtronCompact20SolarChargingModeActionTypeId) {
            QString solarChargingMode = info->action().paramValue(amtronCompact20SolarChargingModeActionSolarChargingModeParamTypeId).toString();
            ModbusRtuReply *reply = amtronCompact20Connection->setSolarChargingMode(solarChargingModeMap.key(solarChargingMode));
            // Note: modbus RTU replies delete them self on finished
            connect(reply, &ModbusRtuReply::finished, info, [info, reply, solarChargingMode](){
                if (reply->error() == ModbusRtuReply::NoError) {
                    info->thing()->setStateValue(amtronCompact20SolarChargingModeStateTypeId, solarChargingMode);
                    info->finish(Thing::ThingErrorNoError);
                } else {
                    info->finish(Thing::ThingErrorHardwareFailure);
                }
            });
        }
    }
}

void IntegrationPluginMennekes::thingRemoved(Thing *thing)
{
    if (thing->thingClassId() == amtronECUThingClassId && m_amtronECUConnections.contains(thing)) {
        AmtronECU *connection = m_amtronECUConnections.take(thing);
        delete connection;
    }

    if (thing->thingClassId() == amtronHCC3ThingClassId && m_amtronHCC3Connections.contains(thing)) {
        AmtronHCC3ModbusTcpConnection *connection = m_amtronHCC3Connections.take(thing);
        delete connection;
    }

    if (thing->thingClassId() == amtronCompact20ThingClassId && m_amtronCompact20Connections.contains(thing)) {
        AmtronCompact20ModbusRtuConnection *connection = m_amtronCompact20Connections.take(thing);
        delete connection;
    }

    // Unregister related hardware resources
    if (m_monitors.contains(thing))
        hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing));

    if (myThings().isEmpty() && m_pluginTimer) {
        hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer);
        m_pluginTimer = nullptr;
    }
}

void IntegrationPluginMennekes::updateECUPhaseCount(Thing *thing)
{
    AmtronECU *amtronECUConnection = m_amtronECUConnections.value(thing);
    int phaseCount = 0;
    qCDebug(dcMennekes()) << "Phases: L1" << amtronECUConnection->meterCurrentL1()
                          << "L2" << amtronECUConnection->meterCurrentL2()
                          << "L3" << amtronECUConnection->meterCurrentL3();

    // the current idles on some 5 - 10 mA when not charging...
    // We want to detect the phases we're actually charging on. Checking the current flow for that if it's > 500mA
    // If no phase is charging, let's count all phases that are not 0 instead (to determine how many phases are connected at the wallbox)

    if (amtronECUConnection->meterCurrentL1() > 500) {
        phaseCount++;
    }
    if (amtronECUConnection->meterCurrentL2() > 500) {
        phaseCount++;
    }
    if (amtronECUConnection->meterCurrentL3() > 500) {
        phaseCount++;
    }
    qCDebug(dcMennekes()) << "Actively charging phases:" << phaseCount;

    if (amtronECUConnection->detectedVersion() == AmtronECU::VersionNew) {

        // Only available since 5.22
        if (phaseCount == 0) {
            if (amtronECUConnection->meterVoltageL1() > 0) {
                phaseCount++;
            }
            if (amtronECUConnection->meterVoltageL2() > 0) {
                phaseCount++;
            }
            if (amtronECUConnection->meterVoltageL3() > 0) {
                phaseCount++;
            }
            qCDebug(dcMennekes()) << "Connected phases:" << phaseCount;
        }
    } else if (amtronECUConnection->detectedVersion() == AmtronECU::VersionOld) {
        if (phaseCount == 0) {
            qCDebug(dcMennekes()) << "Could not detect phases in use. Default to 1 phase.";
            phaseCount = 1;
        }
    }

    thing->setStateValue(amtronECUPhaseCountStateTypeId, phaseCount);
}

void IntegrationPluginMennekes::updateCompact20PhaseCount(Thing *thing)
{
    AmtronCompact20ModbusRtuConnection *connection = m_amtronCompact20Connections.value(thing);

    // Using detected phases if detection completed (0 = undetected). Could be 1, 2 or 3
    if (connection->detectedEVPhases() > 0) {
        thing->setStateValue(amtronCompact20PhaseCountStateTypeId, connection->detectedEVPhases());
    } else {
        thing->setStateValue(amtronCompact20PhaseCountStateTypeId, connection->switchedPhases() == AmtronCompact20ModbusRtuConnection::PhaseModeAll ? 3 : 1);
    }
}

void IntegrationPluginMennekes::setupAmtronECUConnection(ThingSetupInfo *info)
{
    Thing *thing = info->thing();
    NetworkDeviceMonitor *monitor = m_monitors.value(thing);

    qCDebug(dcMennekes()) << "Creating Amtron ECU connection using" << monitor;
    AmtronECU *amtronECUConnection = new AmtronECU(monitor->address(), 502, 0xff, this);
    connect(info, &ThingSetupInfo::aborted, amtronECUConnection, &ModbusTcpMaster::deleteLater);

    // Reconnect on monitor reachable changed
    connect(monitor, &NetworkDeviceMonitor::reachableChanged, thing, [=](bool reachable){
        qCDebug(dcMennekes()) << "Network device monitor reachable changed for" << thing->name() << reachable;
        if (!thing->setupComplete())
            return;

        if (reachable && !thing->stateValue("connected").toBool()) {
            amtronECUConnection->modbusTcpMaster()->setHostAddress(monitor->networkDeviceInfo().address());
            amtronECUConnection->connectDevice();
        } else if (!reachable) {
            // Note: We disable autoreconnect explicitly and we will
            // connect the device once the monitor says it is reachable again
            amtronECUConnection->disconnectDevice();
        }
    });


    connect(amtronECUConnection, &AmtronECU::reachableChanged, thing, [this, thing, amtronECUConnection](bool reachable){
        qCDebug(dcMennekes()) << "Reachable changed to" << reachable << "for" << thing;
        if (reachable) {
            amtronECUConnection->initialize();
        } else {
            thing->setStateValue(amtronECUConnectedStateTypeId, false);

            // Note: sometimes the device does not repond any more on requests, but the connection is still
            // up and a reconnect fixes this behavior. Let us try to reconnect in case the TCP connection is up
            // or the monitor is reachable
            bool monitorReachable = false;
            if (m_monitors.contains(thing))
                monitorReachable = m_monitors.value(thing)->reachable();

            if (amtronECUConnection->modbusTcpMaster()->connected() || monitorReachable) {
                qCWarning(dcMennekes()) << "The amtron ECU connection is not reachable any more, but the device seems to be still reachable. Trying to reconnect...";
                amtronECUConnection->modbusTcpMaster()->reconnectDevice();
            }
        }
    });

    connect(amtronECUConnection, &AmtronECU::initializationFinished, thing, [=](bool success){
        if (!thing->setupComplete())
            return;

        if (success) {
            thing->setStateValue(amtronECUConnectedStateTypeId, true);
            thing->setStateValue(amtronECUFirmwareVersionStateTypeId, QString::fromUtf8(QByteArray::fromHex(QByteArray::number(amtronECUConnection->firmwareVersion(), 16))));
            amtronECUConnection->update();
        } else {
            thing->setStateValue(amtronECUConnectedStateTypeId, false);
            // Try once to reconnect the device
            amtronECUConnection->reconnectDevice();
        }
    });

    connect(amtronECUConnection, &AmtronECU::updateFinished, thing, [this, amtronECUConnection, thing](){
        qCDebug(dcMennekes()) << "Amtron ECU update finished:" << thing->name() << amtronECUConnection;
        updateECUPhaseCount(thing);

        // Firmware >= 5.12

        if (amtronECUConnection->cpSignalState() != AmtronECU::CPSignalStateE) {
            // State E (Off): don't update as the wallbox goes to this state for a few seconds regardless of the actual plugged in state.
            qCDebug(dcMennekes()) << "CP signal state changed" << amtronECUConnection->cpSignalState();
            thing->setStateValue(amtronECUPluggedInStateTypeId, amtronECUConnection->cpSignalState() >= AmtronECU::CPSignalStateB);
        }

        thing->setStateMinValue(amtronECUMaxChargingCurrentStateTypeId, amtronECUConnection->minCurrentLimit());

        qCDebug(dcMennekes()) << "HEMS current limit:" << amtronECUConnection->hemsCurrentLimit();
        if (amtronECUConnection->hemsCurrentLimit() == 0) {
            thing->setStateValue(amtronECUPowerStateTypeId, false);
        } else {
            thing->setStateValue(amtronECUPowerStateTypeId, true);
            thing->setStateValue(amtronECUMaxChargingCurrentStateTypeId, amtronECUConnection->hemsCurrentLimit());
        }

        if (amtronECUConnection->detectedVersion() == AmtronECU::VersionOld) {
            // Note: version < 5.22 has no totals, we need to sum them up
            int totalPower = 0;

            if (amtronECUConnection->meterPowerL1() != 0xffffffff) {
                totalPower += amtronECUConnection->meterPowerL1();
            }

            if (amtronECUConnection->meterPowerL2() != 0xffffffff) {
                totalPower += amtronECUConnection->meterPowerL2();
            }

            if (amtronECUConnection->meterPowerL3() != 0xffffffff) {
                totalPower += amtronECUConnection->meterPowerL3();
            }

            thing->setStateValue(amtronECUCurrentPowerStateTypeId, totalPower);
            thing->setStateValue(amtronECUChargingStateTypeId, totalPower > 0);


            double totalEnergy = 0; // Wh
            if (amtronECUConnection->meterEnergyL1() != 0xffffffff) {
                totalEnergy += amtronECUConnection->meterEnergyL1();
            }

            if (amtronECUConnection->meterEnergyL2() != 0xffffffff) {
                totalEnergy += amtronECUConnection->meterEnergyL2();
            }

            if (amtronECUConnection->meterEnergyL3() != 0xffffffff) {
                totalEnergy += amtronECUConnection->meterEnergyL3();
            }

            totalEnergy /= 1000.0; // Convert Wh to kWh
            thing->setStateValue(amtronECUTotalEnergyConsumedStateTypeId, qRound(totalEnergy * 100.0) / 100.0); // rounded to 2 as it changes on every update
        }
    });

    connect(amtronECUConnection, &AmtronECU::signalledCurrentChanged, thing, [](quint16 signalledCurrent) {
        qCDebug(dcMennekes()) << "Signalled current changed:" << signalledCurrent;
    });

    // From here only for firmware version >= 5.22, otherwise these signal will never be emitted, but if the user updates to 5.22, they start working

    connect(amtronECUConnection, &AmtronECU::maxCurrentLimitChanged, thing, [this, thing](quint16 maxCurrentLimit) {
        qCDebug(dcMennekes()) << "max current limit changed:" << maxCurrentLimit;
        // If the vehicle or cable are not capable of reporting the maximum, this will be 0
        // We'll reset to the max defined in the json file in that case
        if (maxCurrentLimit == 0) {
            maxCurrentLimit = supportedThings().findById(amtronECUThingClassId).stateTypes().findById(amtronECUMaxChargingCurrentStateTypeId).maxValue().toUInt();
        }
        thing->setStateMaxValue(amtronECUMaxChargingCurrentStateTypeId, maxCurrentLimit);
    });

    connect(amtronECUConnection, &AmtronECU::meterTotoalEnergyChanged, thing, [thing](quint32 meterTotalEnergy) {
        qCDebug(dcMennekes()) << "meter total energy changed:" << meterTotalEnergy;
        thing->setStateValue(amtronECUTotalEnergyConsumedStateTypeId, qRound(meterTotalEnergy / 10.0) / 100.0); // rounded to 2 as it changes on every update
    });

    connect(amtronECUConnection, &AmtronECU::meterTotalPowerChanged, thing, [thing](quint32 meterTotalPower) {
        qCDebug(dcMennekes()) << "meter total power changed:" << meterTotalPower;
        thing->setStateValue(amtronECUCurrentPowerStateTypeId, meterTotalPower);
        thing->setStateValue(amtronECUChargingStateTypeId, meterTotalPower > 0);
    });

    connect(amtronECUConnection, &AmtronECU::chargedEnergyChanged, thing, [thing](quint32 chargedEnergy) {
        qCDebug(dcMennekes()) << "charged energy changed:" << chargedEnergy;
        thing->setStateValue(amtronECUSessionEnergyStateTypeId, qRound(chargedEnergy / 10.0) / 100.0); // rounded to 2 as it changes on every update
    });

    m_amtronECUConnections.insert(thing, amtronECUConnection);
    info->finish(Thing::ThingErrorNoError);

    if (monitor->reachable())
        amtronECUConnection->connectDevice();

}

void IntegrationPluginMennekes::setupAmtronHCC3Connection(ThingSetupInfo *info)
{
    Thing *thing = info->thing();
    NetworkDeviceMonitor *monitor = m_monitors.value(thing);

    qCDebug(dcMennekes()) << "Creating Amtron HHC3 connection for" << monitor;
    AmtronHCC3ModbusTcpConnection *amtronHCC3Connection = new AmtronHCC3ModbusTcpConnection(monitor->address(), 502, 0xff, this);
    connect(info, &ThingSetupInfo::aborted, amtronHCC3Connection, &ModbusTcpMaster::deleteLater);

    connect(monitor, &NetworkDeviceMonitor::reachableChanged, thing, [=](bool reachable){
        qCDebug(dcMennekes()) << "Network device monitor reachable changed for" << thing->name() << reachable;
        if (!thing->setupComplete())
            return;

        if (reachable && !thing->stateValue("connected").toBool()) {
            amtronHCC3Connection->modbusTcpMaster()->setHostAddress(monitor->networkDeviceInfo().address());
            amtronHCC3Connection->connectDevice();
        } else if (!reachable) {
            // Note: We disable autoreconnect explicitly and we will
            // connect the device once the monitor says it is reachable again
            amtronHCC3Connection->disconnectDevice();
        }
    });

    connect(amtronHCC3Connection, &AmtronHCC3ModbusTcpConnection::reachableChanged, thing, [thing, amtronHCC3Connection](bool reachable){
        qCDebug(dcMennekes()) << "Reachable changed to" << reachable << "for" << thing;
        if (reachable) {
            amtronHCC3Connection->initialize();
        } else {
            thing->setStateValue(amtronHCC3ConnectedStateTypeId, false);
        }
    });

    connect(amtronHCC3Connection, &AmtronHCC3ModbusTcpConnection::initializationFinished, thing, [=](bool success){
        if (!thing->setupComplete())
            return;

        if (success) {
            thing->setStateValue(amtronHCC3ConnectedStateTypeId, true);
        } else {
            thing->setStateValue(amtronHCC3ConnectedStateTypeId, false);
            // Try once to reconnect the device
            amtronHCC3Connection->reconnectDevice();
        }
    });

    connect(amtronHCC3Connection, &AmtronHCC3ModbusTcpConnection::updateFinished, thing, [amtronHCC3Connection, thing](){
        qCDebug(dcMennekes()) << "Amtron HCC3 update finished:" << thing->name() << amtronHCC3Connection;
        thing->setStateMaxValue(amtronHCC3MaxChargingCurrentStateTypeId, amtronHCC3Connection->installationCurrent());
    });

    connect(amtronHCC3Connection, &AmtronHCC3ModbusTcpConnection::cpSignalStateChanged, thing, [thing](AmtronHCC3ModbusTcpConnection::CPSignalState cpSignalState) {
        qCInfo(dcMennekes()) << "CP signal state changed" << cpSignalState;
        thing->setStateValue(amtronHCC3PluggedInStateTypeId, cpSignalState >= AmtronHCC3ModbusTcpConnection::CPSignalStateB1);
    });

    connect(amtronHCC3Connection, &AmtronHCC3ModbusTcpConnection::phaseCountChanged, thing, [thing](quint16 phaseCount) {
        qCInfo(dcMennekes()) << "Phase count changed:" << phaseCount;
        if (phaseCount > 0) {
            thing->setStateValue(amtronHCC3PhaseCountStateTypeId, phaseCount);
        }
    });

    connect(amtronHCC3Connection, &AmtronHCC3ModbusTcpConnection::amtronStateChanged, thing, [thing](AmtronHCC3ModbusTcpConnection::AmtronState amtronState) {
        qCInfo(dcMennekes()) << "Amtron state changed:" << amtronState;
        switch (amtronState) {
        case AmtronHCC3ModbusTcpConnection::AmtronStateIdle:
        case AmtronHCC3ModbusTcpConnection::AmtronStateStandByAuthorize:
        case AmtronHCC3ModbusTcpConnection::AmtronStateStandbyConnect:
        case AmtronHCC3ModbusTcpConnection::AmtronStatePaused:
        case AmtronHCC3ModbusTcpConnection::AmtronStateTerminated:
        case AmtronHCC3ModbusTcpConnection::AmtronStateError:
            thing->setStateValue(amtronHCC3ChargingStateTypeId, false);
            break;
        case AmtronHCC3ModbusTcpConnection::AmtronStateCharging:
            thing->setStateValue(amtronHCC3ChargingStateTypeId, true);
            break;
        }
    });

    connect(amtronHCC3Connection, &AmtronHCC3ModbusTcpConnection::actualPowerConsumptionChanged, thing, [thing](quint32 actualPowerConsumption) {
        qCInfo(dcMennekes()) << "Actual power consumption changed:" << actualPowerConsumption;
        thing->setStateValue(amtronHCC3CurrentPowerStateTypeId, actualPowerConsumption);
    });

    connect(amtronHCC3Connection, &AmtronHCC3ModbusTcpConnection::chargingSessionMeterChanged, thing, [thing](quint32 chargingSessionMeter) {
        thing->setStateValue(amtronHCC3SessionEnergyStateTypeId, chargingSessionMeter / 1000.0);
        // Don't have a total... still providing it for the interface, the energy experience will deal with the value resetting frequently
        thing->setStateValue(amtronHCC3TotalEnergyConsumedStateTypeId, chargingSessionMeter / 1000.0);
    });

    connect(amtronHCC3Connection, &AmtronHCC3ModbusTcpConnection::customerCurrentLimitationChanged, thing, [thing](quint16 customerCurrentLimitation) {
        thing->setStateValue(amtronHCC3MaxChargingCurrentStateTypeId, customerCurrentLimitation);
    });

    m_amtronHCC3Connections.insert(thing, amtronHCC3Connection);
    info->finish(Thing::ThingErrorNoError);

    if (monitor->reachable())
        amtronHCC3Connection->connectDevice();

}

void IntegrationPluginMennekes::setupAmtronCompact20Connection(ThingSetupInfo *info)
{
    Thing *thing = info->thing();

    uint slaveId = thing->paramValue(amtronCompact20ThingSlaveAddressParamTypeId).toUInt();
    if (slaveId > 254 || slaveId == 0) {
        qCWarning(dcMennekes()) << "Setup failed, slave ID is not valid" << slaveId;
        info->finish(Thing::ThingErrorSetupFailed, QT_TR_NOOP("The Modbus address not valid. It must be a value between 1 and 254."));
        return;
    }

    QUuid uuid = thing->paramValue(amtronCompact20ThingModbusMasterUuidParamTypeId).toUuid();
    if (!hardwareManager()->modbusRtuResource()->hasModbusRtuMaster(uuid)) {
        qCWarning(dcMennekes()) << "Setup failed, hardware manager not available";
        info->finish(Thing::ThingErrorSetupFailed, QT_TR_NOOP("The Modbus RTU resource is not available."));
        return;
    }

    AmtronCompact20ModbusRtuConnection *compact20Connection = new AmtronCompact20ModbusRtuConnection(hardwareManager()->modbusRtuResource()->getModbusRtuMaster(uuid), slaveId, this);
    connect(info, &ThingSetupInfo::aborted, compact20Connection, &ModbusRtuMaster::deleteLater);
    m_amtronCompact20Connections.insert(thing, compact20Connection);

    connect(info, &ThingSetupInfo::aborted, this, [=](){
        m_amtronCompact20Connections.take(info->thing())->deleteLater();
    });

    connect(compact20Connection, &AmtronCompact20ModbusRtuConnection::reachableChanged, thing, [compact20Connection, thing](bool reachable){
        qCDebug(dcMennekes()) << "Reachable state changed" << reachable;
        if (reachable) {
            compact20Connection->initialize();
        } else {
            thing->setStateValue("connected", false);
        }
    });

    connect(compact20Connection, &AmtronCompact20ModbusRtuConnection::initializationFinished, info, [=](bool success){
        qCDebug(dcMennekes()) << "Initialisation finished" << success;
        if (info->isInitialSetup() && !success) {
            m_amtronCompact20Connections.take(info->thing())->deleteLater();
            info->finish(Thing::ThingErrorHardwareNotAvailable);
            return;
        }

        info->finish(Thing::ThingErrorNoError);

        if (success) {
            qCDebug(dcMennekes) << "Firmware version:" << compact20Connection->firmwareVersion();
            info->thing()->setStateValue(amtronCompact20CurrentVersionStateTypeId, compact20Connection->firmwareVersion());
            info->thing()->setStateValue(amtronCompact20PowerStateTypeId, compact20Connection->chargingReleaseEnergyManager() == 1);
            info->thing()->setStateValue(amtronCompact20MaxChargingCurrentStateTypeId, qRound(compact20Connection->chargingCurrentEnergyManager()));
            info->thing()->setStateValue(amtronCompact20SolarChargingModeStateTypeId, solarChargingModeMap.value(compact20Connection->solarChargingMode()));
        }
    });

    connect(compact20Connection, &AmtronCompact20ModbusRtuConnection::reachableChanged, thing, [=](bool reachable){
        thing->setStateValue(amtronCompact20ConnectedStateTypeId, reachable);
    });


    connect(compact20Connection, &AmtronCompact20ModbusRtuConnection::updateFinished, thing, [this, compact20Connection, thing](){
        qCDebug(dcMennekes()) << "Update finished:" << thing->name() << compact20Connection;
        updateCompact20PhaseCount(thing);

        quint16 maxCurrentEVSE = compact20Connection->maxCurrentEVSE();
        quint16 maxCurrentSession = compact20Connection->maxCurrentSession();
        if (maxCurrentSession > 0 && maxCurrentEVSE > 0) {
            thing->setStateMaxValue(amtronCompact20MaxChargingCurrentStateTypeId, qMin(maxCurrentEVSE, maxCurrentSession));
        } else if (maxCurrentSession > 0){
            thing->setStateMaxValue(amtronCompact20MaxChargingCurrentStateTypeId, maxCurrentSession);
        } else if (maxCurrentEVSE) {
            thing->setStateMaxValue(amtronCompact20MaxChargingCurrentStateTypeId, maxCurrentEVSE);
        }
    });

    connect(compact20Connection, &AmtronCompact20ModbusRtuConnection::cpSignalStateChanged, thing, [thing](AmtronCompact20ModbusRtuConnection::CPSignalState cpSignalState){
        qCDebug(dcMennekes()) << "CP signal state changed:" << thing->name() << cpSignalState;
        // Note: using EVSE state register instead
        //        switch (cpSignalState) {
        //        case AmtronCompact20ModbusRtuConnection::CPSignalStateA1:
        //        case AmtronCompact20ModbusRtuConnection::CPSignalStateA2:
        //            thing->setStateValue(amtronCompact20PluggedInStateTypeId, false);
        //            thing->setStateValue(amtronCompact20ChargingStateTypeId, false);
        //            break;
        //        case AmtronCompact20ModbusRtuConnection::CPSignalStateB1:
        //        case AmtronCompact20ModbusRtuConnection::CPSignalStateB2:
        //            thing->setStateValue(amtronCompact20PluggedInStateTypeId, true);
        //            thing->setStateValue(amtronCompact20ChargingStateTypeId, false);
        //            break;
        //        case AmtronCompact20ModbusRtuConnection::CPSignalStateC1:
        //        case AmtronCompact20ModbusRtuConnection::CPSignalStateC2:
        //        case AmtronCompact20ModbusRtuConnection::CPSignalStateD1:
        //        case AmtronCompact20ModbusRtuConnection::CPSignalStateD2:
        //            thing->setStateValue(amtronCompact20PluggedInStateTypeId, true);
        //            thing->setStateValue(amtronCompact20ChargingStateTypeId, true);
        //            break;
        //        case AmtronCompact20ModbusRtuConnection::CPSignalStateE:
        //        case AmtronCompact20ModbusRtuConnection::CPSignalStateF:
        //            qCWarning(dcMennekes()) << "Wallbox in Error state!";
        //            thing->setStateValue(amtronCompact20PluggedInStateTypeId, false);
        //            thing->setStateValue(amtronCompact20ChargingStateTypeId, false);
        //            break;
        //        }
    });

    connect(compact20Connection, &AmtronCompact20ModbusRtuConnection::evseStateChanged, thing, [thing](AmtronCompact20ModbusRtuConnection::EvseState evseState){
        qCDebug(dcMennekes()) << "EVSE state changed:" << thing->name() << evseState;
        switch (evseState) {
        case AmtronCompact20ModbusRtuConnection::EvseStateNotInitialized:
        case AmtronCompact20ModbusRtuConnection::EvseStateIdle:
            thing->setStateValue(amtronCompact20PluggedInStateTypeId, false);
            thing->setStateValue(amtronCompact20ChargingStateTypeId, false);
            break;
        case AmtronCompact20ModbusRtuConnection::EvseStateEvConnected:
        case AmtronCompact20ModbusRtuConnection::EvseStatePreconditionsValidButNotCharging:
        case AmtronCompact20ModbusRtuConnection::EvseStateReadyToCharge:
            thing->setStateValue(amtronCompact20PluggedInStateTypeId, true);
            thing->setStateValue(amtronCompact20ChargingStateTypeId, false);
            break;
        case AmtronCompact20ModbusRtuConnection::EvseStateCharging:
            thing->setStateValue(amtronCompact20PluggedInStateTypeId, true);
            thing->setStateValue(amtronCompact20ChargingStateTypeId, true);
            break;
        case AmtronCompact20ModbusRtuConnection::EvseStateError:
        case AmtronCompact20ModbusRtuConnection::EvseStateServiceMode:
            thing->setStateValue(amtronCompact20PluggedInStateTypeId, false);
            thing->setStateValue(amtronCompact20ChargingStateTypeId, false);
            break;
        }
    });

    connect(compact20Connection, &AmtronCompact20ModbusRtuConnection::chargingCurrentEnergyManagerChanged, thing, [thing](float chargingCurrent){
        qCInfo(dcMennekes()) << thing->name() << "charging current energy manager changed:" << chargingCurrent;
        if (chargingCurrent >= 6) {
            thing->setStateValue(amtronCompact20MaxChargingCurrentStateTypeId, qRound(chargingCurrent));
        }
    });

    connect(compact20Connection, &AmtronCompact20ModbusRtuConnection::switchedPhasesChanged, thing, [thing](AmtronCompact20ModbusRtuConnection::PhaseMode phaseMode){
        qCInfo(dcMennekes()) << thing->name() << "switched EV phases changed:" << phaseMode;
    });
    connect(compact20Connection, &AmtronCompact20ModbusRtuConnection::detectedEVPhasesChanged, thing, [thing](quint16 detectedEvPhases){
        qCInfo(dcMennekes()) << thing->name() << "detected EV phases changed:" << detectedEvPhases;
    });

    connect(compact20Connection, &AmtronCompact20ModbusRtuConnection::requestedPhasesChanged, thing, [thing](AmtronCompact20ModbusRtuConnection::PhaseMode phaseMode){
        thing->setStateValue(amtronCompact20DesiredPhaseCountStateTypeId, phaseMode == AmtronCompact20ModbusRtuConnection::PhaseModeAll ? 3 : 1);
    });

    connect(compact20Connection, &AmtronCompact20ModbusRtuConnection::powerOverallChanged, thing, [thing](double powerOverall){
        // The wallbox may measure non
        if (powerOverall < 20) {
            powerOverall = 0;
        }
        thing->setStateValue(amtronCompact20CurrentPowerStateTypeId, powerOverall);
    });
    connect(compact20Connection, &AmtronCompact20ModbusRtuConnection::chargedEnergyTotalChanged, thing, [thing](double chargedEnergyTotal){
        thing->setStateValue(amtronCompact20TotalEnergyConsumedStateTypeId, chargedEnergyTotal);
    });
    connect(compact20Connection, &AmtronCompact20ModbusRtuConnection::chargedEnergySessionChanged, thing, [thing](double chargedEnergySession){
        thing->setStateValue(amtronCompact20SessionEnergyStateTypeId, chargedEnergySession);
    });

    connect(compact20Connection, &AmtronCompact20ModbusRtuConnection::chargingReleaseEnergyManagerChanged, thing, [thing](quint16 chargingReleaseEnergyManager){
        thing->setStateValue(amtronCompact20PowerStateTypeId, chargingReleaseEnergyManager == 1);
    });

    connect(compact20Connection, &AmtronCompact20ModbusRtuConnection::solarChargingModeChanged, thing, [thing](AmtronCompact20ModbusRtuConnection::SolarChargingMode solarChargingMode){
        thing->setStateValue(amtronCompact20SolarChargingModeStateTypeId, solarChargingModeMap.value(solarChargingMode));
    });

}
