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());
|
requestId = keba->displayMessage(action.param(kebaDisplayActionMessageParamTypeId).value().toByteArray());
|
||||||
} else if (action.actionTypeId() == kebaOutputX2ActionTypeId) {
|
} else if (action.actionTypeId() == kebaOutputX2ActionTypeId) {
|
||||||
requestId = keba->setOutputX2(action.param(kebaOutputX2ActionOutputX2ParamTypeId).value().toBool());
|
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) {
|
} else if (action.actionTypeId() == kebaFailsafeModeActionTypeId) {
|
||||||
int timeout = 0;
|
int timeout = 0;
|
||||||
if (action.param(kebaFailsafeModeActionFailsafeModeParamTypeId).value().toBool()) {
|
if (action.param(kebaFailsafeModeActionFailsafeModeParamTypeId).value().toBool()) {
|
||||||
|
|
@ -426,6 +429,7 @@ void IntegrationPluginKeba::setupKeba(ThingSetupInfo *info, const QHostAddress &
|
||||||
case KebaProductInfo::SeriesX4G:
|
case KebaProductInfo::SeriesX4G:
|
||||||
case KebaProductInfo::SeriesSpecial:
|
case KebaProductInfo::SeriesSpecial:
|
||||||
qCDebug(dcKeba()) << "The keba" << productInformation.series() << "is capable of communicating using UDP";
|
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;
|
supported = true;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
@ -623,6 +627,12 @@ void IntegrationPluginKeba::onReportTwoReceived(const KeContact::ReportTwo &repo
|
||||||
thing->setStateValue("outputX2", reportTwo.output);
|
thing->setStateValue("outputX2", reportTwo.output);
|
||||||
thing->setStateValue("input", reportTwo.input);
|
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);
|
thing->setStateValue("uptime", reportTwo.seconds / 60);
|
||||||
} else {
|
} else {
|
||||||
qCWarning(dcKeba()) << "Received report but the serial number didn't match";
|
qCWarning(dcKeba()) << "Received report but the serial number didn't match";
|
||||||
|
|
|
||||||
|
|
@ -299,6 +299,35 @@
|
||||||
"writable": true,
|
"writable": true,
|
||||||
"defaultValue": false
|
"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",
|
"id": "ba600276-8b36-4404-b8ec-415245e5bc15",
|
||||||
"name": "input",
|
"name": "input",
|
||||||
|
|
@ -376,6 +405,23 @@
|
||||||
"defaultValue": ""
|
"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": [
|
"eventTypes": [
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ KebaProductInfo::KebaProductInfo(const QString &productString) :
|
||||||
// KC-P30-EC220112-000-DE
|
// KC-P30-EC220112-000-DE
|
||||||
// KC-P30-EC2404B2-M0A-GE
|
// KC-P30-EC2404B2-M0A-GE
|
||||||
// KC-P30-EC2204U2-E00-PV
|
// 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;
|
qCDebug(dcKeba()) << "Parsing product information from" << productString.count() << productString;
|
||||||
if (m_productString.count() < 19) {
|
if (m_productString.count() < 19) {
|
||||||
|
|
@ -95,7 +96,27 @@ KebaProductInfo::KebaProductInfo(const QString &productString) :
|
||||||
qCDebug(dcKeba()) << "Connector type:" << m_connectorType;
|
qCDebug(dcKeba()) << "Connector type:" << m_connectorType;
|
||||||
|
|
||||||
QChar connectorCurrentValue = descriptor.at(3);
|
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;
|
m_current = Current13A;
|
||||||
} else if (connectorCurrentValue.isDigit() && connectorTypeValue == QChar('2')) {
|
} else if (connectorCurrentValue.isDigit() && connectorTypeValue == QChar('2')) {
|
||||||
m_current = Current16A;
|
m_current = Current16A;
|
||||||
|
|
|
||||||
|
|
@ -327,6 +327,29 @@ void KeContact::getReport1XX(int reportNumber)
|
||||||
getReport(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)
|
QUuid KeContact::setOutputX2(bool state)
|
||||||
{
|
{
|
||||||
if (!m_dataLayer) {
|
if (!m_dataLayer) {
|
||||||
|
|
@ -500,6 +523,8 @@ void KeContact::onReceivedDatagram(const QHostAddress &address, const QByteArray
|
||||||
reportTwo.setEnergy = data.value("Setenergy").toInt() / 10000.00;
|
reportTwo.setEnergy = data.value("Setenergy").toInt() / 10000.00;
|
||||||
reportTwo.output = data.value("Output").toInt();
|
reportTwo.output = data.value("Output").toInt();
|
||||||
reportTwo.input= data.value("Input").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.serialNumber = data.value("Serial").toString();
|
||||||
reportTwo.seconds = data.value("Sec").toInt();
|
reportTwo.seconds = data.value("Sec").toInt();
|
||||||
// Not documented:
|
// Not documented:
|
||||||
|
|
|
||||||
|
|
@ -138,6 +138,8 @@ public:
|
||||||
double setEnergy; //Shows the set energy limit
|
double setEnergy; //Shows the set energy limit
|
||||||
bool output; //State of the output X2.
|
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.
|
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
|
QString serialNumber; //Serial number
|
||||||
int seconds; //Current system clock since restart of the charging station.
|
int seconds; //Current system clock since restart of the charging station.
|
||||||
};
|
};
|
||||||
|
|
@ -198,6 +200,7 @@ public:
|
||||||
|
|
||||||
// Command “currtime”
|
// Command “currtime”
|
||||||
QUuid setOutputX2(bool state); // Command “output”
|
QUuid setOutputX2(bool state); // Command “output”
|
||||||
|
QUuid setPhaseSwitch(int phaseCount); // Commands “x2src 4” + “x2 0/1” (IEC 61851: 5 min cooldown between switches)
|
||||||
|
|
||||||
private:
|
private:
|
||||||
KeContactDataLayer *m_dataLayer = nullptr;
|
KeContactDataLayer *m_dataLayer = nullptr;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue