Update Websocket und weiteres.
This commit is contained in:
+123
-20
@@ -22,7 +22,8 @@ class Ladestation_OCPP extends IPSModule
|
||||
private const ATTR_CAPABILITIES = 'Capabilities';
|
||||
private const ATTR_LAST_EMS_UPDATE = 'LastEmsUpdate';
|
||||
private const ATTR_LAST_OCPP_HEARTBEAT = 'LastOcppHeartbeat';
|
||||
private const ATTR_PENDING_COMMAND = 'PendingCommand';
|
||||
private const ATTR_PENDING_COMMANDS = 'PendingCommands';
|
||||
private const ATTR_LAST_ENERGY_AGGREGATION = 'LastEnergyAggregation';
|
||||
|
||||
public function Create()
|
||||
{
|
||||
@@ -48,7 +49,7 @@ class Ladestation_OCPP extends IPSModule
|
||||
$this->RegisterPropertyInteger('MeterPowerIntervalSeconds', 1);
|
||||
$this->RegisterPropertyInteger('MeterEnergyIntervalSeconds', 60);
|
||||
$this->RegisterPropertyInteger('IdleCounterMax', 2);
|
||||
$this->RegisterPropertyInteger('Interval', 1);
|
||||
$this->RegisterPropertyInteger('Interval', 2);
|
||||
$this->RegisterPropertyInteger('PhaseSwitchHoldSeconds', 120);
|
||||
$this->RegisterPropertyInteger('PhaseSwitchPauseSeconds', 30);
|
||||
$this->RegisterPropertyBoolean('AllowAutomaticPhaseSwitch', false);
|
||||
@@ -65,7 +66,8 @@ class Ladestation_OCPP extends IPSModule
|
||||
$this->RegisterAttributeString(self::ATTR_CAPABILITIES, json_encode(CapabilityModel::defaults()));
|
||||
$this->RegisterAttributeInteger(self::ATTR_LAST_EMS_UPDATE, 0);
|
||||
$this->RegisterAttributeInteger(self::ATTR_LAST_OCPP_HEARTBEAT, 0);
|
||||
$this->RegisterAttributeString(self::ATTR_PENDING_COMMAND, json_encode([]));
|
||||
$this->RegisterAttributeString(self::ATTR_PENDING_COMMANDS, json_encode([]));
|
||||
$this->RegisterAttributeInteger(self::ATTR_LAST_ENERGY_AGGREGATION, 0);
|
||||
|
||||
$this->registerEmsVariables();
|
||||
$this->registerControlVariables();
|
||||
@@ -73,7 +75,7 @@ class Ladestation_OCPP extends IPSModule
|
||||
$this->registerMeterVariables();
|
||||
$this->registerDiagnosticVariables();
|
||||
|
||||
$this->RegisterTimer('Timer_Do_UserCalc', 1000, 'IPS_RequestAction(' . $this->InstanceID . ', "Do_UserCalc", "");');
|
||||
$this->RegisterTimer('Timer_Do_UserCalc', 2000, 'IPS_RequestAction(' . $this->InstanceID . ', "Do_UserCalc", "");');
|
||||
$this->RegisterTimer('Timer_StatusWatchdog', 5000, 'IPS_RequestAction(' . $this->InstanceID . ', "StatusWatchdog", "");');
|
||||
$this->RegisterTimer('Timer_EMSWatchdog', 5000, 'IPS_RequestAction(' . $this->InstanceID . ', "EMSWatchdog", "");');
|
||||
$this->RegisterTimer('Timer_OCPPWatchdog', 5000, 'IPS_RequestAction(' . $this->InstanceID . ', "OCPPWatchdog", "");');
|
||||
@@ -86,7 +88,7 @@ class Ladestation_OCPP extends IPSModule
|
||||
{
|
||||
parent::ApplyChanges();
|
||||
|
||||
$interval = max(1, $this->ReadPropertyInteger('Interval'));
|
||||
$interval = max(2, $this->ReadPropertyInteger('Interval'));
|
||||
$this->SetTimerInterval('Timer_Do_UserCalc', $interval * 1000);
|
||||
$this->SetTimerInterval('Timer_StatusWatchdog', 5000);
|
||||
$this->SetTimerInterval('Timer_EMSWatchdog', 5000);
|
||||
@@ -621,6 +623,7 @@ class Ladestation_OCPP extends IPSModule
|
||||
|
||||
private function queueOcppCommandInternal(string $action, array $payload, int $retryCount, bool $force): void
|
||||
{
|
||||
$this->processPendingCommands();
|
||||
$version = $this->effectiveOcppVersion();
|
||||
if ($action === 'SetChargingProfile' && !$force && !$this->canUseSmartCharging()) {
|
||||
$this->SetValue('LetzterCommandStatus', 'SetChargingProfile blockiert: Capability nicht bestaetigt.');
|
||||
@@ -630,13 +633,17 @@ class Ladestation_OCPP extends IPSModule
|
||||
$message = OCPPMessage::call($action, $payload, $version, $this->ReadPropertyString('ChargePointId'));
|
||||
$this->SetValue('LetzterCommandTimestamp', time());
|
||||
$this->SetValue('LetzterCommandStatus', 'Queued OCPP: ' . $action);
|
||||
$this->WriteAttributeString(self::ATTR_PENDING_COMMAND, json_encode([
|
||||
$pendingCommands = $this->readPendingCommands();
|
||||
$pendingCommands[$message->uniqueId] = [
|
||||
'uniqueId' => $message->uniqueId,
|
||||
'action' => $action,
|
||||
'payload' => $payload,
|
||||
'retryCount' => $retryCount,
|
||||
'timestamp' => time()
|
||||
]));
|
||||
'timestamp' => time(),
|
||||
'ackDeadline' => time() + max(5, $this->ReadPropertyInteger('CommandAckTimeoutSeconds')),
|
||||
'status' => 'pending'
|
||||
];
|
||||
$this->writePendingCommands($pendingCommands);
|
||||
|
||||
$this->queueOcppMessage($message);
|
||||
}
|
||||
@@ -654,6 +661,8 @@ class Ladestation_OCPP extends IPSModule
|
||||
|
||||
private function runWatchdogs(): void
|
||||
{
|
||||
$this->processPendingCommands();
|
||||
|
||||
$manager = new FailSafeManager();
|
||||
$result = $manager->evaluate([
|
||||
'lastEmsUpdate' => $this->ReadAttributeInteger(self::ATTR_LAST_EMS_UPDATE),
|
||||
@@ -815,9 +824,21 @@ class Ladestation_OCPP extends IPSModule
|
||||
if ($this->GetValue('LetzterMeterValueZeitpunkt') > 0) {
|
||||
return;
|
||||
}
|
||||
$now = time();
|
||||
$lastAggregation = $this->ReadAttributeInteger(self::ATTR_LAST_ENERGY_AGGREGATION);
|
||||
if ($lastAggregation <= 0) {
|
||||
$this->WriteAttributeInteger(self::ATTR_LAST_ENERGY_AGGREGATION, $now);
|
||||
return;
|
||||
}
|
||||
$seconds = max(0, $now - $lastAggregation);
|
||||
if ($seconds <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$energy = (float)$this->GetValue('Bezogene_Energie');
|
||||
$energy += (float)$this->GetValue('Ladeleistung_Effektiv') * (max(1, $this->ReadPropertyInteger('MeterEnergyIntervalSeconds')) / 3600);
|
||||
$energy += ((float)$this->GetValue('Ladeleistung_Effektiv') * $seconds) / 3600.0;
|
||||
$this->SetValue('Bezogene_Energie', $energy);
|
||||
$this->WriteAttributeInteger(self::ATTR_LAST_ENERGY_AGGREGATION, $now);
|
||||
}
|
||||
|
||||
private function extractIdToken(array $payload): string
|
||||
@@ -970,16 +991,19 @@ class Ladestation_OCPP extends IPSModule
|
||||
|
||||
private function handleCallResult(OCPPMessage $message): void
|
||||
{
|
||||
$pending = $this->readPendingCommand();
|
||||
if (($pending['uniqueId'] ?? '') !== $message->uniqueId) {
|
||||
$pendingCommands = $this->readPendingCommands();
|
||||
if (!isset($pendingCommands[$message->uniqueId])) {
|
||||
$this->SetValue('LetzterCommandStatus', 'CallResult ohne passenden Pending Command: ' . $message->uniqueId);
|
||||
return;
|
||||
}
|
||||
|
||||
$pending = $pendingCommands[$message->uniqueId];
|
||||
unset($pendingCommands[$message->uniqueId]);
|
||||
$this->writePendingCommands($pendingCommands);
|
||||
|
||||
$action = (string)($pending['action'] ?? '');
|
||||
$status = (string)($message->payload['status'] ?? 'Accepted');
|
||||
$this->SetValue('LetzterCommandStatus', $action . ' bestaetigt: ' . $status);
|
||||
$this->WriteAttributeString(self::ATTR_PENDING_COMMAND, json_encode([]));
|
||||
|
||||
if ($action === 'SetChargingProfile') {
|
||||
if ($status === 'Accepted') {
|
||||
@@ -997,7 +1021,13 @@ class Ladestation_OCPP extends IPSModule
|
||||
|
||||
private function handleCallError(OCPPMessage $message): void
|
||||
{
|
||||
$pending = $this->readPendingCommand();
|
||||
$pendingCommands = $this->readPendingCommands();
|
||||
$pending = $pendingCommands[$message->uniqueId] ?? [];
|
||||
if (isset($pendingCommands[$message->uniqueId])) {
|
||||
unset($pendingCommands[$message->uniqueId]);
|
||||
$this->writePendingCommands($pendingCommands);
|
||||
}
|
||||
|
||||
$action = (string)($pending['action'] ?? 'Unknown');
|
||||
$code = (string)($message->payload['errorCode'] ?? $message->action);
|
||||
$description = (string)($message->payload['errorDescription'] ?? '');
|
||||
@@ -1010,7 +1040,7 @@ class Ladestation_OCPP extends IPSModule
|
||||
return;
|
||||
}
|
||||
|
||||
$this->WriteAttributeString(self::ATTR_PENDING_COMMAND, json_encode([]));
|
||||
$this->writePendingCommands($pendingCommands);
|
||||
}
|
||||
|
||||
private function retryOrBlockSmartCharging(array $pending, string $reason): void
|
||||
@@ -1019,9 +1049,22 @@ class Ladestation_OCPP extends IPSModule
|
||||
$limit = max(0, $this->ReadPropertyInteger('SmartChargingRetryLimit'));
|
||||
if ($retryCount < $limit) {
|
||||
$next = $retryCount + 1;
|
||||
$delay = min(300, (int)pow(2, $next) * 5);
|
||||
$retryKey = 'retry-' . str_replace('.', '', uniqid('', true));
|
||||
$pendingCommands = $this->readPendingCommands();
|
||||
$pendingCommands[$retryKey] = [
|
||||
'uniqueId' => $retryKey,
|
||||
'action' => 'SetChargingProfile',
|
||||
'payload' => (array)($pending['payload'] ?? []),
|
||||
'retryCount' => $next,
|
||||
'timestamp' => time(),
|
||||
'nextRetryAt' => time() + $delay,
|
||||
'status' => 'retry_wait',
|
||||
'reason' => $reason
|
||||
];
|
||||
$this->writePendingCommands($pendingCommands);
|
||||
$this->SetValue('WattpilotProfileRetries', $next);
|
||||
$this->SetValue('LetzterCommandStatus', 'SetChargingProfile Retry ' . $next . '/' . $limit . ': ' . $reason);
|
||||
$this->queueOcppCommandInternal('SetChargingProfile', (array)($pending['payload'] ?? []), $next, true);
|
||||
$this->SetValue('LetzterCommandStatus', 'SetChargingProfile Retry ' . $next . '/' . $limit . ' in ' . $delay . 's: ' . $reason);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1030,7 +1073,6 @@ class Ladestation_OCPP extends IPSModule
|
||||
$this->writeCapabilities($capabilities);
|
||||
$this->SetValue('LetztesChargingProfileAccepted', false);
|
||||
$this->SetValue('WattpilotSmartChargingBlocked', true);
|
||||
$this->WriteAttributeString(self::ATTR_PENDING_COMMAND, json_encode([]));
|
||||
$this->setDiagnostic('Warnung', 'Wattpilot Gen1', 'SmartCharging nach ' . $limit . ' Retries deaktiviert: ' . $reason);
|
||||
}
|
||||
|
||||
@@ -1082,10 +1124,71 @@ class Ladestation_OCPP extends IPSModule
|
||||
$this->SetValue('Capability_SimpleAmpereProfiles', (bool)($capabilities['simpleAmpereProfiles'] ?? false));
|
||||
}
|
||||
|
||||
private function readPendingCommand(): array
|
||||
private function readPendingCommands(): array
|
||||
{
|
||||
$pending = json_decode($this->ReadAttributeString(self::ATTR_PENDING_COMMAND), true);
|
||||
return is_array($pending) ? $pending : [];
|
||||
$pending = json_decode($this->ReadAttributeString(self::ATTR_PENDING_COMMANDS), true);
|
||||
if (!is_array($pending)) {
|
||||
return [];
|
||||
}
|
||||
if (isset($pending['uniqueId'])) {
|
||||
return [(string)$pending['uniqueId'] => $pending];
|
||||
}
|
||||
return $pending;
|
||||
}
|
||||
|
||||
private function writePendingCommands(array $pendingCommands): void
|
||||
{
|
||||
$this->WriteAttributeString(self::ATTR_PENDING_COMMANDS, json_encode($pendingCommands));
|
||||
}
|
||||
|
||||
private function processPendingCommands(): void
|
||||
{
|
||||
$pendingCommands = $this->readPendingCommands();
|
||||
if (empty($pendingCommands)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$now = time();
|
||||
$changed = false;
|
||||
foreach ($pendingCommands as $uniqueId => $pending) {
|
||||
$status = (string)($pending['status'] ?? 'pending');
|
||||
if ($status === 'retry_wait') {
|
||||
if ($now >= (int)($pending['nextRetryAt'] ?? 0)) {
|
||||
unset($pendingCommands[$uniqueId]);
|
||||
$this->writePendingCommands($pendingCommands);
|
||||
$this->queueOcppCommandInternal(
|
||||
(string)($pending['action'] ?? 'SetChargingProfile'),
|
||||
(array)($pending['payload'] ?? []),
|
||||
(int)($pending['retryCount'] ?? 0),
|
||||
true
|
||||
);
|
||||
$pendingCommands = $this->readPendingCommands();
|
||||
$changed = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($status === 'pending' && $now > (int)($pending['ackDeadline'] ?? 0)) {
|
||||
unset($pendingCommands[$uniqueId]);
|
||||
$changed = true;
|
||||
if ((string)($pending['action'] ?? '') === 'SetChargingProfile') {
|
||||
$this->writePendingCommands($pendingCommands);
|
||||
$this->retryOrBlockSmartCharging($pending, 'ACK timeout');
|
||||
$pendingCommands = $this->readPendingCommands();
|
||||
} else {
|
||||
$this->SetValue('LetzterCommandStatus', (string)($pending['action'] ?? 'Command') . ' ACK timeout');
|
||||
}
|
||||
}
|
||||
|
||||
if ($now - (int)($pending['timestamp'] ?? $now) > 3600) {
|
||||
unset($pendingCommands[$uniqueId]);
|
||||
$changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($changed) {
|
||||
$this->writePendingCommands($pendingCommands);
|
||||
}
|
||||
}
|
||||
|
||||
private function updateMeterCapabilities(array $values): void
|
||||
|
||||
Reference in New Issue
Block a user