This commit is contained in:
+244
-2
@@ -5,7 +5,9 @@ declare(strict_types=1);
|
||||
require_once __DIR__ . '/../libs/ManagerSchnittstelle.php';
|
||||
require_once __DIR__ . '/../libs/Nachrichtenvertrag.php';
|
||||
require_once __DIR__ . '/../libs/ManagerRegler.php';
|
||||
require_once __DIR__ . '/../libs/Lizenzpruefung.php';
|
||||
|
||||
use Belevo\EnelixEMS\Lizenzpruefung;
|
||||
use Belevo\EnelixEMS\ManagerRegler;
|
||||
use Belevo\EnelixEMS\ManagerSchnittstelle;
|
||||
use Belevo\EnelixEMS\Nachrichtenvertrag;
|
||||
@@ -15,6 +17,9 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
||||
private const STATUS_AKTIV = 102;
|
||||
private const STATUS_MESSWERT_UNGUELTIG = 201;
|
||||
private const STATUS_KONFIGURATION_UNGUELTIG = 202;
|
||||
private const STATUS_LIZENZ_UNGUELTIG = 203;
|
||||
private const LIZENZ_ENDPOINT = 'https://license.enelix.ch/api/v1/licenses/activate';
|
||||
private const LIZENZ_RETRY_SEKUNDEN = 3600;
|
||||
private const VM_UPDATE = 10603;
|
||||
private const VERBRAUCHER_MODUL_IDS = [
|
||||
'{B7C54AF4-AD7D-4FE4-B75D-203693906251}',
|
||||
@@ -61,6 +66,8 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
||||
$this->RegisterPropertyFloat('Umschaltdifferenz', 5.0);
|
||||
$this->RegisterPropertyString('PrognoseAnschluss', '{}');
|
||||
$this->RegisterPropertyString('SDLAnschluss', '{}');
|
||||
$this->RegisterPropertyString('Lizenzcode', '');
|
||||
// Verdeckte Altproperty fuer bestehende Instanzkonfigurationen.
|
||||
$this->RegisterPropertyString('LizenzAnschluss', '{}');
|
||||
$this->RegisterPropertyString('StoermeldeAnschluss', '{}');
|
||||
$this->RegisterPropertyBoolean('DiagnosevariablenAnzeigen', false);
|
||||
@@ -74,6 +81,14 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
||||
$this->RegisterAttributeString('VerbraucherCache', '{}');
|
||||
$this->RegisterAttributeString('LetzteSollwerte', '{}');
|
||||
$this->RegisterAttributeInteger('RegistrierteNetzleistungVariableID', 0);
|
||||
$this->RegisterAttributeString('LizenzInstallationID', '');
|
||||
$this->RegisterAttributeString('LizenzLease', '{}');
|
||||
$this->RegisterAttributeString('LizenzcodeHash', '');
|
||||
$this->RegisterAttributeInteger('LizenzNaechsterVersuch', 0);
|
||||
$this->RegisterAttributeString('LizenzLetzterFehler', '');
|
||||
if ($this->ReadAttributeString('LizenzInstallationID') === '') {
|
||||
$this->WriteAttributeString('LizenzInstallationID', $this->erzeugeUuidV4());
|
||||
}
|
||||
|
||||
// Die alten Timer bleiben deaktiviert registriert, damit bestehende Instanzen sauber migrieren.
|
||||
$this->RegisterTimer('Regelzyklus', 0, '');
|
||||
@@ -108,6 +123,11 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
||||
$this->SetTimerInterval('KeepAlive', $this->ReadPropertyInteger('KeepAlive') * 1000);
|
||||
$this->setzeDiagnosewert('Monatsgrenzen', $this->ReadPropertyString('Monatsgrenzen'));
|
||||
$this->aktualisiereAnschlussstatus();
|
||||
if (!$this->aktualisiereLizenzfreigabe($this->ReadPropertyString('Lizenzcode'), false)) {
|
||||
$this->SetValue('Betriebsart', ManagerRegler::BETRIEBSART_INAKTIV);
|
||||
$this->SetStatus(self::STATUS_LIZENZ_UNGUELTIG);
|
||||
return;
|
||||
}
|
||||
$this->SetStatus(self::STATUS_AKTIV);
|
||||
}
|
||||
|
||||
@@ -124,6 +144,18 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
||||
$monatsgrenzenSichtbar = $lastspitzenmodus === 2
|
||||
&& $this->GetBuffer('MonatsgrenzenSichtbar') === '1';
|
||||
|
||||
$this->setzeFormularParameter(
|
||||
$formular['elements'],
|
||||
'LizenzInstallationID',
|
||||
'caption',
|
||||
'Installations-ID: ' . $this->ReadAttributeString('LizenzInstallationID')
|
||||
);
|
||||
$this->setzeFormularParameter(
|
||||
$formular['elements'],
|
||||
'LizenzInformation',
|
||||
'caption',
|
||||
$this->lizenzStatustext()
|
||||
);
|
||||
$this->setzeFormularParameter($formular['elements'], 'VerbraucherZuordnung', 'visible', !$automatisch);
|
||||
$this->setzeFormularParameter(
|
||||
$formular['elements'],
|
||||
@@ -199,6 +231,14 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
||||
$this->regeln((bool) $wert);
|
||||
return;
|
||||
|
||||
case 'FormLizenzPruefen':
|
||||
if (!is_string($wert)) {
|
||||
throw new InvalidArgumentException('Der Lizenzcode muss als Text uebergeben werden.');
|
||||
}
|
||||
$this->aktualisiereLizenzfreigabe($wert, true);
|
||||
$this->UpdateFormField('LizenzInformation', 'caption', $this->lizenzStatustext($wert));
|
||||
return;
|
||||
|
||||
case 'FormAutomatischeSuche':
|
||||
$automatisch = (bool) $wert;
|
||||
$this->UpdateFormField('VerbraucherZuordnung', 'visible', !$automatisch);
|
||||
@@ -298,6 +338,13 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
||||
|
||||
private function regeln(bool $erneuern): void
|
||||
{
|
||||
if (!$this->aktualisiereLizenzfreigabe($this->ReadPropertyString('Lizenzcode'), false)) {
|
||||
$this->SetValue('Betriebsart', ManagerRegler::BETRIEBSART_INAKTIV);
|
||||
$this->setzeDiagnosewert('Verteilbudget', 0.0);
|
||||
$this->SetStatus(self::STATUS_LIZENZ_UNGUELTIG);
|
||||
return;
|
||||
}
|
||||
|
||||
[$netzleistung, $netzleistungGueltig] = $this->leseNetzleistung();
|
||||
$this->SetValue('Netzleistung', $netzleistung);
|
||||
$this->setzeDiagnosewert('NetzleistungGueltig', $netzleistungGueltig);
|
||||
@@ -685,12 +732,207 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
||||
}
|
||||
}
|
||||
|
||||
private function aktualisiereLizenzfreigabe(string $lizenzcode, bool $erzwingen): bool
|
||||
{
|
||||
$normalisiert = Lizenzpruefung::normalisiereCode($lizenzcode);
|
||||
if (!Lizenzpruefung::istLizenzcodeGueltig($normalisiert)) {
|
||||
$fehler = $normalisiert === '' ? 'Lizenzcode fehlt.' : 'Lizenzcode ist ungueltig.';
|
||||
$this->WriteAttributeString('LizenzLetzterFehler', $fehler);
|
||||
$this->setzeDiagnosewert('Lizenzstatus', $fehler);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$installationID = $this->ReadAttributeString('LizenzInstallationID');
|
||||
if ($installationID === '') {
|
||||
$installationID = $this->erzeugeUuidV4();
|
||||
$this->WriteAttributeString('LizenzInstallationID', $installationID);
|
||||
}
|
||||
$codeHash = hash('sha256', $normalisiert);
|
||||
$gespeicherterHash = $this->ReadAttributeString('LizenzcodeHash');
|
||||
$lease = $this->leseLizenzLease();
|
||||
$codePasst = $gespeicherterHash !== '' && hash_equals($gespeicherterHash, $codeHash);
|
||||
$gueltig = $codePasst && Lizenzpruefung::istLeaseGueltig(
|
||||
$lease,
|
||||
$installationID,
|
||||
$this->ReadPropertyInteger('Lastspitzenmodus')
|
||||
);
|
||||
$faellig = !$gueltig || Lizenzpruefung::erneuerungFaellig($lease);
|
||||
$jetzt = time();
|
||||
|
||||
if (!$erzwingen
|
||||
&& (!$faellig || $jetzt < $this->ReadAttributeInteger('LizenzNaechsterVersuch'))
|
||||
) {
|
||||
$this->setzeDiagnosewert('Lizenzstatus', $this->lizenzStatustext($lizenzcode));
|
||||
|
||||
return $gueltig;
|
||||
}
|
||||
|
||||
$this->WriteAttributeInteger('LizenzNaechsterVersuch', $jetzt + self::LIZENZ_RETRY_SEKUNDEN);
|
||||
try {
|
||||
$neueLease = $this->rufeLizenzserverAuf($normalisiert, $installationID);
|
||||
Lizenzpruefung::pruefeLease(
|
||||
$neueLease,
|
||||
$installationID,
|
||||
$this->ReadPropertyInteger('Lastspitzenmodus')
|
||||
);
|
||||
$this->WriteAttributeString(
|
||||
'LizenzLease',
|
||||
json_encode($neueLease, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES)
|
||||
);
|
||||
$this->WriteAttributeString('LizenzcodeHash', $codeHash);
|
||||
$this->WriteAttributeInteger(
|
||||
'LizenzNaechsterVersuch',
|
||||
Lizenzpruefung::erneuernAb($neueLease)
|
||||
);
|
||||
$this->WriteAttributeString('LizenzLetzterFehler', '');
|
||||
$this->setzeDiagnosewert('Lizenzstatus', $this->lizenzStatustext($lizenzcode));
|
||||
|
||||
return true;
|
||||
} catch (Throwable $fehler) {
|
||||
$meldung = substr($fehler->getMessage(), 0, 500);
|
||||
$this->WriteAttributeString('LizenzLetzterFehler', $meldung);
|
||||
$this->setzeDiagnosewert('Lizenzstatus', $this->lizenzStatustext($lizenzcode));
|
||||
$this->protokolliere('Lizenzpruefung', $meldung);
|
||||
|
||||
return $gueltig;
|
||||
}
|
||||
}
|
||||
|
||||
/** @return array<string, mixed> */
|
||||
private function leseLizenzLease(): array
|
||||
{
|
||||
try {
|
||||
$lease = json_decode(
|
||||
$this->ReadAttributeString('LizenzLease'),
|
||||
true,
|
||||
512,
|
||||
JSON_THROW_ON_ERROR
|
||||
);
|
||||
|
||||
return is_array($lease) ? $lease : [];
|
||||
} catch (Throwable $fehler) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
private function lizenzStatustext(?string $lizenzcode = null): string
|
||||
{
|
||||
$code = Lizenzpruefung::normalisiereCode(
|
||||
$lizenzcode ?? $this->ReadPropertyString('Lizenzcode')
|
||||
);
|
||||
if ($code === '') {
|
||||
return 'Lizenzcode fehlt.';
|
||||
}
|
||||
if (!Lizenzpruefung::istLizenzcodeGueltig($code)) {
|
||||
return 'Lizenzcode ist ungueltig.';
|
||||
}
|
||||
|
||||
$codeHash = hash('sha256', $code);
|
||||
$gespeicherterHash = $this->ReadAttributeString('LizenzcodeHash');
|
||||
$fehler = $this->ReadAttributeString('LizenzLetzterFehler');
|
||||
if ($gespeicherterHash === '' || !hash_equals($gespeicherterHash, $codeHash)) {
|
||||
return $fehler !== '' ? 'Nicht freigegeben: ' . $fehler : 'Lizenz noch nicht bestaetigt.';
|
||||
}
|
||||
|
||||
$lease = $this->leseLizenzLease();
|
||||
try {
|
||||
Lizenzpruefung::pruefeLease(
|
||||
$lease,
|
||||
$this->ReadAttributeString('LizenzInstallationID'),
|
||||
$this->ReadPropertyInteger('Lastspitzenmodus')
|
||||
);
|
||||
$status = ($lease['development'] ?? false) === true
|
||||
? 'Aktiv (Entwicklung)'
|
||||
: 'Aktiv';
|
||||
$status .= ' bis ' . date('d.m.Y H:i', Lizenzpruefung::offlineBis($lease));
|
||||
if ($fehler !== '') {
|
||||
$status .= '; Erneuerung fehlgeschlagen: ' . $fehler;
|
||||
}
|
||||
|
||||
return $status;
|
||||
} catch (Throwable $ungueltig) {
|
||||
return 'Nicht freigegeben: ' . $ungueltig->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
/** @return array<string, mixed> */
|
||||
private function rufeLizenzserverAuf(string $lizenzcode, string $installationID): array
|
||||
{
|
||||
$nutzlast = json_encode([
|
||||
'code' => $lizenzcode,
|
||||
'installationId' => $installationID,
|
||||
], JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES);
|
||||
$curl = curl_init(self::LIZENZ_ENDPOINT);
|
||||
if ($curl === false) {
|
||||
throw new RuntimeException('Die HTTPS-Anfrage konnte nicht vorbereitet werden.');
|
||||
}
|
||||
|
||||
try {
|
||||
curl_setopt_array($curl, [
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => $nutzlast,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_CONNECTTIMEOUT => 5,
|
||||
CURLOPT_TIMEOUT => 10,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
'Accept: application/json',
|
||||
'Content-Type: application/json',
|
||||
],
|
||||
CURLOPT_USERAGENT => 'Enelix-EMS-Manager/0.1',
|
||||
]);
|
||||
$antwort = curl_exec($curl);
|
||||
if ($antwort === false) {
|
||||
throw new RuntimeException('Lizenzserver nicht erreichbar: ' . curl_error($curl));
|
||||
}
|
||||
if (strlen($antwort) > 65536) {
|
||||
throw new RuntimeException('Die Antwort des Lizenzservers ist zu gross.');
|
||||
}
|
||||
$httpStatus = (int) curl_getinfo($curl, CURLINFO_HTTP_CODE);
|
||||
} finally {
|
||||
curl_close($curl);
|
||||
}
|
||||
|
||||
try {
|
||||
$daten = json_decode($antwort, true, 512, JSON_THROW_ON_ERROR);
|
||||
} catch (Throwable $fehler) {
|
||||
throw new RuntimeException('Der Lizenzserver lieferte kein gueltiges JSON.', 0, $fehler);
|
||||
}
|
||||
if (!is_array($daten)) {
|
||||
throw new RuntimeException('Der Lizenzserver lieferte kein JSON-Objekt.');
|
||||
}
|
||||
if ($httpStatus !== 200) {
|
||||
$meldung = is_string($daten['error'] ?? null)
|
||||
? $daten['error']
|
||||
: 'Unbekannter Fehler';
|
||||
throw new RuntimeException('Lizenzserver HTTP ' . $httpStatus . ': ' . $meldung);
|
||||
}
|
||||
|
||||
return $daten;
|
||||
}
|
||||
|
||||
private function erzeugeUuidV4(): string
|
||||
{
|
||||
$bytes = random_bytes(16);
|
||||
$bytes[6] = chr((ord($bytes[6]) & 0x0f) | 0x40);
|
||||
$bytes[8] = chr((ord($bytes[8]) & 0x3f) | 0x80);
|
||||
$hex = bin2hex($bytes);
|
||||
|
||||
return sprintf(
|
||||
'%s-%s-%s-%s-%s',
|
||||
substr($hex, 0, 8),
|
||||
substr($hex, 8, 4),
|
||||
substr($hex, 12, 4),
|
||||
substr($hex, 16, 4),
|
||||
substr($hex, 20, 12)
|
||||
);
|
||||
}
|
||||
|
||||
private function aktualisiereAnschlussstatus(): void
|
||||
{
|
||||
foreach ([
|
||||
'PrognoseAnschluss' => 'Prognosestatus',
|
||||
'SDLAnschluss' => 'SDLStatus',
|
||||
'LizenzAnschluss' => 'Lizenzstatus',
|
||||
'StoermeldeAnschluss' => 'Stoerueberwachungsstatus',
|
||||
] as $property => $variable) {
|
||||
$konfiguration = json_decode($this->ReadPropertyString($property), true, 512, JSON_THROW_ON_ERROR);
|
||||
@@ -714,7 +956,7 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
||||
$this->leseAktiveVerbraucherIDs('VerbraucherZuordnung');
|
||||
$this->leseAktiveVerbraucherIDs('AutomatischeVerbraucherZuordnung');
|
||||
$this->dekodiereMonatsgrenzen($this->ReadPropertyString('Monatsgrenzen'));
|
||||
foreach (['PrognoseAnschluss', 'SDLAnschluss', 'LizenzAnschluss', 'StoermeldeAnschluss'] as $property) {
|
||||
foreach (['PrognoseAnschluss', 'SDLAnschluss', 'StoermeldeAnschluss'] as $property) {
|
||||
$wert = json_decode($this->ReadPropertyString($property), true, 512, JSON_THROW_ON_ERROR);
|
||||
if (!is_array($wert)) {
|
||||
throw new InvalidArgumentException($property . ' muss ein JSON-Objekt sein.');
|
||||
|
||||
Reference in New Issue
Block a user