PcElectric: Update action handling based on single register

Signed-off-by: Simon Stürz <simon.stuerz@nymea.io>
This commit is contained in:
Simon Stürz 2024-10-21 16:52:10 +02:00
parent 8d85307f46
commit b6cdaeffa0
5 changed files with 177 additions and 64 deletions

View File

@ -306,7 +306,7 @@
"registerType": "holdingRegister",
"description": "Write charging current",
"unit": "mA",
"access": "WO"
"access": "RW"
},
{
"id": "chargingCurrentOffline",

View File

@ -139,6 +139,7 @@ void IntegrationPluginPcElectric::setupThing(ThingSetupInfo *info)
void IntegrationPluginPcElectric::postSetupThing(Thing *thing)
{
qCDebug(dcPcElectric()) << "Post setup thing" << thing->name();
if (!m_refreshTimer) {
m_refreshTimer = hardwareManager()->pluginTimerManager()->registerTimer(1);
connect(m_refreshTimer, &PluginTimer::timeout, this, [this] {
@ -152,6 +153,14 @@ void IntegrationPluginPcElectric::postSetupThing(Thing *thing)
qCDebug(dcPcElectric()) << "Starting refresh timer...";
m_refreshTimer->start();
}
PceWallbox::ChargingCurrentState chargingCurrentState;
chargingCurrentState.power = thing->stateValue(ev11PowerStateTypeId).toBool();
chargingCurrentState.maxChargingCurrent = thing->stateValue(ev11MaxChargingCurrentStateTypeId).toDouble();
chargingCurrentState.desiredPhaseCount = thing->stateValue(ev11DesiredPhaseCountStateTypeId).toDouble();
qCDebug(dcPcElectric()) << "Initialize charging current state with cached values" << chargingCurrentState;
m_chargingCurrentStateBuffer[thing] = chargingCurrentState;
}
void IntegrationPluginPcElectric::thingRemoved(Thing *thing)
@ -167,6 +176,9 @@ void IntegrationPluginPcElectric::thingRemoved(Thing *thing)
if (m_initialUpdate.contains(thing))
m_initialUpdate.remove(thing);
if (m_chargingCurrentStateBuffer.contains(thing))
m_chargingCurrentStateBuffer.remove(thing);
// Unregister related hardware resources
if (m_monitors.contains(thing))
hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing));
@ -190,91 +202,81 @@ void IntegrationPluginPcElectric::executeAction(ThingActionInfo *info)
}
if (info->action().actionTypeId() == ev11PowerActionTypeId) {
bool power = info->action().paramValue(ev11PowerActionPowerParamTypeId).toBool();
quint16 chargingCurrent = 0;
if (power) {
chargingCurrent = thing->stateValue(ev11MaxChargingCurrentStateTypeId).toUInt() * 1000;
if (thing->stateValue(ev11DesiredPhaseCountStateTypeId).toUInt() == 3) {
// If 3 phase charging is enabled, we set the first bit
chargingCurrent |= static_cast<quint16>(1) << 15;
}
}
qCDebug(dcPcElectric()) << "Writing charging current register" << chargingCurrent << "mA";
QueuedModbusReply *reply = connection->setChargingCurrent(chargingCurrent);
connect(reply, &QueuedModbusReply::finished, info, [reply, info, thing, power, chargingCurrent](){
bool power = info->action().paramValue(ev11PowerActionPowerParamTypeId).toBool();
qCDebug(dcPcElectric()) << "Set charging enabled to" << power;
// Update buffer
m_chargingCurrentStateBuffer[thing].power = power;
quint16 registerValue = PceWallbox::deriveRegisterFromStates(m_chargingCurrentStateBuffer.value(thing));
qCDebug(dcPcElectric()) << "Writing charging current register" << registerValue;
QueuedModbusReply *reply = connection->setChargingCurrent(registerValue);
connect(reply, &QueuedModbusReply::finished, info, [reply, info, thing, power, registerValue](){
if (reply->error() != QModbusDevice::NoError) {
qCWarning(dcPcElectric()) << "Could not set power state to" << power << "(" << chargingCurrent << "mA)" << reply->errorString();
qCWarning(dcPcElectric()) << "Could not set power state to" << power << "(" << registerValue << ")" << reply->errorString();
info->finish(Thing::ThingErrorHardwareFailure);
return;
}
qCDebug(dcPcElectric()) << "Successfully set power state to" << power << "(" << chargingCurrent << "mA)";
qCDebug(dcPcElectric()) << "Successfully set power state to" << power << "(" << registerValue << ")";
thing->setStateValue(ev11PowerStateTypeId, power);
info->finish(Thing::ThingErrorNoError);
});
return;
} else if (info->action().actionTypeId() == ev11MaxChargingCurrentActionTypeId) {
uint desiredChargingCurrent = info->action().paramValue(ev11MaxChargingCurrentActionMaxChargingCurrentParamTypeId).toUInt();
qCDebug(dcPcElectric()) << "Set max charging current to" << desiredChargingCurrent << "A";
if (thing->stateValue(ev11PowerStateTypeId).toBool()) {
// The charging is enabled, let's write the value to the wallbox
quint16 finalChargingCurrent = static_cast<quint16>(desiredChargingCurrent * 1000);
if (thing->stateValue(ev11DesiredPhaseCountStateTypeId).toUInt() == 3) {
// If 3 phase charging is enabled, we set the first bit
finalChargingCurrent |= static_cast<quint16>(1) << 15;
// Update buffer
m_chargingCurrentStateBuffer[thing].maxChargingCurrent = desiredChargingCurrent;
quint16 registerValue = PceWallbox::deriveRegisterFromStates(m_chargingCurrentStateBuffer.value(thing));
qCDebug(dcPcElectric()) << "Writing charging current register" << registerValue;
QueuedModbusReply *reply = connection->setChargingCurrent(registerValue);
connect(reply, &QueuedModbusReply::finished, info, [reply, info, thing, desiredChargingCurrent](){
if (reply->error() != QModbusDevice::NoError) {
qCWarning(dcPcElectric()) << "Could not set charging current to" << desiredChargingCurrent << reply->errorString();
info->finish(Thing::ThingErrorHardwareFailure);
return;
}
qCDebug(dcPcElectric()) << "Writing charging current register" << finalChargingCurrent << "mA";
QueuedModbusReply *reply = connection->setChargingCurrent(finalChargingCurrent);
connect(reply, &QueuedModbusReply::finished, info, [reply, info, thing, desiredChargingCurrent](){
if (reply->error() != QModbusDevice::NoError) {
qCWarning(dcPcElectric()) << "Could not set charging current to" << desiredChargingCurrent << "mA" << reply->errorString();
info->finish(Thing::ThingErrorHardwareFailure);
return;
}
qCDebug(dcPcElectric()) << "Successfully set charging current to" << desiredChargingCurrent << "mA";
thing->setStateValue(ev11MaxChargingCurrentStateTypeId, desiredChargingCurrent);
info->finish(Thing::ThingErrorNoError);
});
} else {
// Save the value in the state, but do not send the value to the wallbox since the power state is reflected using the charging current...
qCDebug(dcPcElectric()) << "Setting charging current to" << desiredChargingCurrent << "without synching to wallbox since the power state is false";
qCDebug(dcPcElectric()) << "Successfully set charging current (" << desiredChargingCurrent << ")";
thing->setStateValue(ev11MaxChargingCurrentStateTypeId, desiredChargingCurrent);
info->finish(Thing::ThingErrorNoError);
}
return;
} else if (info->action().actionTypeId() == ev11DesiredPhaseCountActionTypeId) {
uint desiredPhaseCount = info->action().paramValue(ev11DesiredPhaseCountActionDesiredPhaseCountParamTypeId).toUInt();
qCDebug(dcPcElectric()) << "Desried phase count changed" << desiredPhaseCount;
thing->setStateValue(ev11DesiredPhaseCountStateTypeId, desiredPhaseCount);
info->finish(Thing::ThingErrorNoError);
});
// Update the max charging current according to the new desired phase count
if (thing->stateValue(ev11PowerStateTypeId).toBool()) {
uint chargingCurrent = thing->stateValue(ev11MaxChargingCurrentStateTypeId).toUInt();
quint16 finalChargingCurrent = static_cast<quint16>(chargingCurrent * 1000);
if (thing->stateValue(ev11DesiredPhaseCountStateTypeId).toUInt() == 3) {
// If 3 phase charging is enabled, we set the first bit
finalChargingCurrent |= static_cast<quint16>(1) << 15;
return;
} else if (info->action().actionTypeId() == ev11DesiredPhaseCountActionTypeId) {
uint desiredPhaseCount = info->action().paramValue(ev11DesiredPhaseCountActionDesiredPhaseCountParamTypeId).toUInt();
qCDebug(dcPcElectric()) << "Set desried phase count to" << desiredPhaseCount;
// Update buffer
m_chargingCurrentStateBuffer[thing].desiredPhaseCount = desiredPhaseCount;
quint16 registerValue = PceWallbox::deriveRegisterFromStates(m_chargingCurrentStateBuffer.value(thing));
qCDebug(dcPcElectric()) << "Writing charging current register" << registerValue;
QueuedModbusReply *reply = connection->setChargingCurrent(registerValue);
connect(reply, &QueuedModbusReply::finished, info, [reply, info, thing, desiredPhaseCount](){
if (reply->error() != QModbusDevice::NoError) {
qCWarning(dcPcElectric()) << "Could not set desired phase count to" << desiredPhaseCount << reply->errorString();
info->finish(Thing::ThingErrorHardwareFailure);
return;
}
qCDebug(dcPcElectric()) << "Writing charging current register" << finalChargingCurrent << "mA";
QueuedModbusReply *reply = connection->setChargingCurrent(finalChargingCurrent);
connect(reply, &QueuedModbusReply::finished, info, [reply, finalChargingCurrent](){
if (reply->error() != QModbusDevice::NoError) {
qCWarning(dcPcElectric()) << "Could not set charging current to" << finalChargingCurrent << "mA" << reply->errorString();
return;
}
qCDebug(dcPcElectric()) << "Successfully set phase count (" << desiredPhaseCount << ")";
thing->setStateValue(ev11DesiredPhaseCountStateTypeId, desiredPhaseCount);
info->finish(Thing::ThingErrorNoError);
});
qCDebug(dcPcElectric()) << "Successfully set charging current to" << finalChargingCurrent << "mA";
});
}
return;
}
Q_ASSERT_X(false, "IntegrationPluginPcElectric::executeAction", QString("Unhandled action: %1").arg(info->action().actionTypeId().toString()).toLocal8Bit());
}
@ -336,7 +338,7 @@ void IntegrationPluginPcElectric::setupConnection(ThingSetupInfo *info)
thing->setStateMaxValue(ev11MaxChargingCurrentStateTypeId, connection->maxChargingCurrentDip() / 1000);
thing->setStateValue(ev11PluggedInStateTypeId, connection->chargingState() >= PceWallbox::ChargingStateB1 &&
connection->chargingState() < PceWallbox::ChargingStateError);
connection->chargingState() < PceWallbox::ChargingStateError);
thing->setStateValue(ev11ChargingStateTypeId, connection->chargingState() == PceWallbox::ChargingStateC2);
if (connection->chargingRelayState() != EV11ModbusTcpConnection::ChargingRelayStateNoCharging) {
@ -383,6 +385,19 @@ void IntegrationPluginPcElectric::setupConnection(ThingSetupInfo *info)
if (m_initialUpdate.value(thing)) {
m_initialUpdate[thing] = false;
qCDebug(dcPcElectric()) << "Update initial charger states from charging current register...";
PceWallbox::ChargingCurrentState chargingCurrentState = PceWallbox::deriveStatesFromRegister(connection->chargingCurrent());
qCDebug(dcPcElectric()) << chargingCurrentState;
thing->setStateValue(ev11PowerStateTypeId, chargingCurrentState.power);
thing->setStateValue(ev11DesiredPhaseCountStateTypeId, chargingCurrentState.desiredPhaseCount);
if (chargingCurrentState.power) {
thing->setStateValue(ev11MaxChargingCurrentStateTypeId, chargingCurrentState.maxChargingCurrent);
}
m_chargingCurrentStateBuffer[thing] = chargingCurrentState;
qCDebug(dcPcElectric()) << "Updating initial settings after connecting...";
thing->setSettingValue(ev11SettingsLedBrightnessParamTypeId, connection->ledBrightness());
@ -451,3 +466,4 @@ void IntegrationPluginPcElectric::setupConnection(ThingSetupInfo *info)
if (monitor->reachable())
connection->connectDevice();
}

View File

@ -32,6 +32,7 @@
#define INTEGRATIONPLUGINPCELECTRIC_H
#include <QObject>
#include <QDebug>
#include <integrations/integrationplugin.h>
#include <network/networkdevicediscovery.h>
@ -59,12 +60,22 @@ public:
private:
PluginTimer *m_refreshTimer = nullptr;
QHash<Thing *, PceWallbox *> m_connections;
QHash<Thing *, NetworkDeviceMonitor *> m_monitors;
QHash<Thing *, bool> m_initialUpdate;
// We need to buffer the desired power / current / phase count states because all 3 states
// will be represented by one register (200 - chaegingCurrent). If all 3 actions get executed, they might
// overwrite each other, since the action gets started right the way, but the request gets queued.
// If the actions would be queued, there would be still the issue with the order of the actions
// (set power to false and then set charging current would always enable charging in the end).
QHash<Thing *, PceWallbox::ChargingCurrentState> m_chargingCurrentStateBuffer;
void setupConnection(ThingSetupInfo *info);
};
#endif // INTEGRATIONPLUGINPCELECTRIC_H

View File

@ -107,6 +107,41 @@ bool PceWallbox::update()
enqueueRequest(reply);
// charging current register. Contains
// - power state
// - chargingcurrent (if power is true)
// - phases (if power is true)
bool chargingCurrentQueued = false;
foreach (QueuedModbusReply *r, m_queue) {
if (r->dataUnit().startAddress() == chargingCurrentDataUnit().startAddress()) {
chargingCurrentQueued = true;
break;
}
}
if (!chargingCurrentQueued) {
reply = new QueuedModbusReply(QueuedModbusReply::RequestTypeRead, chargingCurrentDataUnit(), this);
connect(reply, &QueuedModbusReply::finished, reply, &QueuedModbusReply::deleteLater);
connect(reply, &QueuedModbusReply::finished, this, [this, reply](){
if (m_currentReply == reply)
m_currentReply = nullptr;
if (reply->error() != QModbusDevice::NoError) {
QTimer::singleShot(0, this, &PceWallbox::sendNextRequest);
return;
}
const QModbusDataUnit unit = reply->reply()->result();
const QVector<quint16> values = unit.values();
processChargingCurrentRegisterValues(values);
QTimer::singleShot(0, this, &PceWallbox::sendNextRequest);
});
enqueueRequest(reply);
}
// Digital input
bool digitalInputAlreadyQueued = false;
foreach (QueuedModbusReply *r, m_queue) {
@ -255,6 +290,38 @@ void PceWallbox::gracefullDeleteLater()
}
}
quint16 PceWallbox::deriveRegisterFromStates(PceWallbox::ChargingCurrentState state)
{
quint16 registerValue = 0;
if (!state.power)
return registerValue; // 0
registerValue = state.maxChargingCurrent * 1000; // convert to mA
if (state.desiredPhaseCount > 1) {
registerValue |= static_cast<quint16>(1) << 15;
}
return registerValue;
}
PceWallbox::ChargingCurrentState PceWallbox::deriveStatesFromRegister(quint16 registerValue)
{
PceWallbox::ChargingCurrentState chargingCurrentState;
chargingCurrentState.power = (registerValue != 0);
// Only set max charging current if power, otherwise we use default 6A
if (chargingCurrentState.power) {
bool threePhaseCharging = (registerValue & (1 << 15));
chargingCurrentState.desiredPhaseCount = (threePhaseCharging ? 3 : 1);
chargingCurrentState.maxChargingCurrent = (registerValue & 0x7FFF) / 1000.0;
}
return chargingCurrentState;
}
void PceWallbox::sendHeartbeat()
{
if (m_aboutToDelete)
@ -349,3 +416,10 @@ void PceWallbox::cleanupQueue()
qDeleteAll(m_queue);
m_queue.clear();
}
QDebug operator<<(QDebug debug, const PceWallbox::ChargingCurrentState &chargingCurrentState)
{
QDebugStateSaver saver(debug);
debug.nospace() << "ChargingCurrentState(" << chargingCurrentState.power << ", " << chargingCurrentState.maxChargingCurrent << " [A], " << chargingCurrentState.desiredPhaseCount << ')';
return debug;
}

View File

@ -33,6 +33,7 @@
#include <QTimer>
#include <QQueue>
#include <QDebug>
#include <QObject>
#include <queuedmodbusreply.h>
@ -43,6 +44,12 @@ class PceWallbox : public EV11ModbusTcpConnection
{
Q_OBJECT
public:
typedef struct ChargingCurrentState {
bool power = false;
double maxChargingCurrent = 6;
uint desiredPhaseCount = 3;
} ChargingCurrentState;
explicit PceWallbox(const QHostAddress &hostAddress, uint port, quint16 slaveId, QObject *parent = nullptr);
bool update() override;
@ -54,7 +61,6 @@ public:
QueuedModbusReply *setDigitalInputMode(DigitalInputMode digitalInputMode);
// Note: the modbus implementation of the wallbox gets stuck if a Modbus request has been sent
// and we disconnect the socket before the response has arrived. Only a reboot of the wallbox
// fixes the broken communication afterwards. This method waits for the current request before closing the
@ -62,6 +68,9 @@ public:
// IMPORTNAT: do not use the object after this call, this is a temporary workaround
void gracefullDeleteLater();
static quint16 deriveRegisterFromStates(PceWallbox::ChargingCurrentState state);
static PceWallbox::ChargingCurrentState deriveStatesFromRegister(quint16 registerValue);
private slots:
void sendHeartbeat();
void sendNextRequest();
@ -78,4 +87,7 @@ private:
void cleanupQueue();
};
QDebug operator<<(QDebug debug, const PceWallbox::ChargingCurrentState &chargingCurrentState);
#endif // PCEWALLBOX_H