diff --git a/keba/integrationpluginkeba.cpp b/keba/integrationpluginkeba.cpp index 2f7e9f76..75bdd2e8 100644 --- a/keba/integrationpluginkeba.cpp +++ b/keba/integrationpluginkeba.cpp @@ -289,6 +289,9 @@ void IntegrationPluginKeba::executeAction(ThingActionInfo *info) requestId = keba->displayMessage(action.param(kebaDisplayActionMessageParamTypeId).value().toByteArray()); } else if (action.actionTypeId() == kebaOutputX2ActionTypeId) { requestId = keba->setOutputX2(action.param(kebaOutputX2ActionOutputX2ParamTypeId).value().toBool()); + } else if (action.actionTypeId() == kebaSetPhaseCountActionTypeId) { + int phaseCount = action.paramValue(kebaSetPhaseCountActionPhaseCountParamTypeId).toInt(); + requestId = keba->setPhaseSwitch(phaseCount); } else if (action.actionTypeId() == kebaFailsafeModeActionTypeId) { int timeout = 0; if (action.param(kebaFailsafeModeActionFailsafeModeParamTypeId).value().toBool()) { @@ -426,6 +429,7 @@ void IntegrationPluginKeba::setupKeba(ThingSetupInfo *info, const QHostAddress & case KebaProductInfo::SeriesX4G: case KebaProductInfo::SeriesSpecial: qCDebug(dcKeba()) << "The keba" << productInformation.series() << "is capable of communicating using UDP"; + qCDebug(dcKeba()) << "Series Special detected:" << productInformation.productString().right(2) << "(PV-Edition ou autre variante spéciale)"; supported = true; break; default: @@ -623,6 +627,12 @@ void IntegrationPluginKeba::onReportTwoReceived(const KeContact::ReportTwo &repo thing->setStateValue("outputX2", reportTwo.output); thing->setStateValue("input", reportTwo.input); + // Phase switch state (PV-Edition / SeriesSpecial) + thing->setStateValue("phaseSwitchSource", reportTwo.x2PhaseSwitchSource); + thing->setStateValue("phaseSwitchActive", reportTwo.x2PhaseSwitchSource == 4); + // x2PhaseSwitch: 0 = 1 phase, 1 = 3 phases + thing->setStateValue("desiredPhaseCount", reportTwo.x2PhaseSwitch == 1 ? 3 : 1); + thing->setStateValue("uptime", reportTwo.seconds / 60); } else { qCWarning(dcKeba()) << "Received report but the serial number didn't match"; diff --git a/keba/integrationpluginkeba.json b/keba/integrationpluginkeba.json index 77f9dddd..05214081 100644 --- a/keba/integrationpluginkeba.json +++ b/keba/integrationpluginkeba.json @@ -299,6 +299,35 @@ "writable": true, "defaultValue": false }, + { + "id": "f8e39ab6-53a7-45a4-96df-8644cbd57036", + "name": "phaseSwitchSource", + "displayName": "Phase switch source", + "displayNameEvent": "Phase switch source changed", + "type": "int", + "defaultValue": 0, + "cached": true + }, + { + "id": "bea5e94f-95e0-44ad-a37b-5111415fc093", + "name": "phaseSwitchActive", + "displayName": "Phase switch active", + "displayNameEvent": "Phase switch active changed", + "type": "bool", + "defaultValue": false, + "cached": true + }, + { + "id": "c8830dc8-4f12-4aeb-be07-e5024bbe34b4", + "name": "desiredPhaseCount", + "displayName": "Desired phase count", + "displayNameEvent": "Desired phase count changed", + "type": "uint", + "defaultValue": 3, + "minValue": 1, + "maxValue": 3, + "cached": true + }, { "id": "ba600276-8b36-4404-b8ec-415245e5bc15", "name": "input", @@ -376,6 +405,23 @@ "defaultValue": "" } ] + }, + { + "id": "f6bedafe-ade4-47ec-894a-b9114da9ad38", + "name": "setPhaseCount", + "displayName": "Set phase count", + "paramTypes": [ + { + "id": "39ff68f6-1cd9-4395-8d87-ccb2c69436b3", + "name": "phaseCount", + "displayName": "Phase count", + "type": "int", + "defaultValue": 3, + "minValue": 1, + "maxValue": 3, + "allowedValues": [1, 3] + } + ] } ], "eventTypes": [ diff --git a/keba/kebaproductinfo.cpp b/keba/kebaproductinfo.cpp index 0addceeb..34660d97 100644 --- a/keba/kebaproductinfo.cpp +++ b/keba/kebaproductinfo.cpp @@ -35,6 +35,7 @@ KebaProductInfo::KebaProductInfo(const QString &productString) : // KC-P30-EC220112-000-DE // KC-P30-EC2404B2-M0A-GE // KC-P30-EC2204U2-E00-PV + // KC-P30-ESS400U2-E00-PV (PV-Edition, Shutter T2, 32A, triphasé, marché FR) qCDebug(dcKeba()) << "Parsing product information from" << productString.count() << productString; if (m_productString.count() < 19) { @@ -95,7 +96,27 @@ KebaProductInfo::KebaProductInfo(const QString &productString) : qCDebug(dcKeba()) << "Connector type:" << m_connectorType; QChar connectorCurrentValue = descriptor.at(3); - if (connectorCurrentValue.isDigit() && connectorTypeValue == QChar('1')) { + if (m_connectorType == Shutter) { + // Shutter: le courant est encodé directement en position 3 + // (indépendant de connectorTypeValue qui vaut 'S') + if (connectorCurrentValue == QChar('4')) { + m_current = Current32A; + qCDebug(dcKeba()) << "Current (Shutter): 32A"; + } else if (connectorCurrentValue == QChar('3')) { + m_current = Current20A; + qCDebug(dcKeba()) << "Current (Shutter): 20A"; + } else if (connectorCurrentValue == QChar('2')) { + m_current = Current16A; + qCDebug(dcKeba()) << "Current (Shutter): 16A"; + } else if (connectorCurrentValue == QChar('1')) { + m_current = Current13A; + qCDebug(dcKeba()) << "Current (Shutter): 13A"; + } else { + qCWarning(dcKeba()) << "Current (Shutter): valeur inconnue" << connectorCurrentValue; + m_isValid = false; + return; + } + } else if (connectorCurrentValue.isDigit() && connectorTypeValue == QChar('1')) { m_current = Current13A; } else if (connectorCurrentValue.isDigit() && connectorTypeValue == QChar('2')) { m_current = Current16A; diff --git a/keba/kecontact.cpp b/keba/kecontact.cpp index c6138f57..f378d19b 100644 --- a/keba/kecontact.cpp +++ b/keba/kecontact.cpp @@ -327,6 +327,29 @@ void KeContact::getReport1XX(int reportNumber) getReport(reportNumber); } +QUuid KeContact::setPhaseSwitch(int phaseCount) +{ + if (!m_dataLayer) { + qCWarning(dcKeba()) << "UDP socket not initialized"; + setReachable(false); + return QUuid(); + } + + // 1. Activate UDP phase control source + KeContactRequest activateRequest(QUuid::createUuid(), "x2src 4"); + m_requestQueue.enqueue(activateRequest); + + // 2. Set phase count: x2 0 = 1 phase, x2 1 = 3 phases + // Note: IEC 61851 requires a 5 min cooldown between phase switches + QByteArray cmd = QString("x2 %1").arg(phaseCount == 3 ? 1 : 0).toLatin1(); + KeContactRequest phaseRequest(QUuid::createUuid(), cmd); + qCDebug(dcKeba()) << "Phase switch:" << phaseCount << "phases. Datagram:" << cmd; + m_requestQueue.enqueue(phaseRequest); + + sendNextCommand(); + return phaseRequest.requestId(); +} + QUuid KeContact::setOutputX2(bool state) { if (!m_dataLayer) { @@ -500,6 +523,8 @@ void KeContact::onReceivedDatagram(const QHostAddress &address, const QByteArray reportTwo.setEnergy = data.value("Setenergy").toInt() / 10000.00; reportTwo.output = data.value("Output").toInt(); reportTwo.input= data.value("Input").toInt(); + reportTwo.x2PhaseSwitchSource = data.value("X2 phaseSwitch source").toInt(); + reportTwo.x2PhaseSwitch = data.value("X2 phaseSwitch").toInt(); reportTwo.serialNumber = data.value("Serial").toString(); reportTwo.seconds = data.value("Sec").toInt(); // Not documented: diff --git a/keba/kecontact.h b/keba/kecontact.h index 39871067..7bcb0100 100644 --- a/keba/kecontact.h +++ b/keba/kecontact.h @@ -138,6 +138,8 @@ public: double setEnergy; //Shows the set energy limit bool output; //State of the output X2. bool input; //State of the potential free Enable input X1. When using the input, please pay attention to the information in the installation manual. + int x2PhaseSwitchSource = 0; //Source of phase switching control (4 = UDP) + int x2PhaseSwitch = 0; //Current phase state (0 = 1 phase, 1 = 3 phases) QString serialNumber; //Serial number int seconds; //Current system clock since restart of the charging station. }; @@ -198,6 +200,7 @@ public: // Command “currtime” QUuid setOutputX2(bool state); // Command “output” + QUuid setPhaseSwitch(int phaseCount); // Commands “x2src 4” + “x2 0/1” (IEC 61851: 5 min cooldown between switches) private: KeContactDataLayer *m_dataLayer = nullptr;