Keba: fix Shutter/PV-Edition parser + add phase switching (x2/x2src UDP)
- Fix: product code parser fails for Shutter connector type (KC-P30-ESS400U2-E00-PV)
Current rating was compared against connectorTypeValue ('S') instead of
connectorCurrentValue ('4') -> m_isValid = false for all PV-Edition models
- Add: phase switching support via UDP x2src/x2 commands (doc V2.04 §3.2.14-15)
New states: phaseSwitchSource, phaseSwitchActive, desiredPhaseCount
New action: setPhaseCount (1 or 3 phases, 5 min cooldown per IEC 61851)
Added KeContact::setPhaseSwitch() queuing both commands with built-in 200ms pause
master
parent
22656bdeec
commit
e7be9b51af
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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": [
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue