This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Belevo\EnelixEMS\Tests;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class BatterieModulstrukturTest extends TestCase
|
||||
{
|
||||
private const MODULPFAD = __DIR__ . '/../Batterie/';
|
||||
|
||||
public function testModulIstVollstaendigUndMetadatenSindGueltig(): void
|
||||
{
|
||||
foreach (['module.json', 'form.json', 'module.php', 'README.md'] as $datei) {
|
||||
self::assertFileExists(self::MODULPFAD . $datei);
|
||||
}
|
||||
$metadaten = json_decode(
|
||||
(string) file_get_contents(self::MODULPFAD . 'module.json'),
|
||||
true,
|
||||
512,
|
||||
JSON_THROW_ON_ERROR
|
||||
);
|
||||
|
||||
self::assertSame('{437FB683-517F-4FEC-8CCB-FE6B0A62B69E}', $metadaten['id']);
|
||||
self::assertSame('Batterie', $metadaten['name']);
|
||||
self::assertSame('Enelix', $metadaten['vendor']);
|
||||
self::assertSame(3, $metadaten['type']);
|
||||
}
|
||||
|
||||
public function testAllePropertiesSindImFormularEnthalten(): void
|
||||
{
|
||||
$basis = (string) file_get_contents(__DIR__ . '/../libs/VerbraucherBasisTrait.php');
|
||||
$modul = (string) file_get_contents(self::MODULPFAD . 'module.php');
|
||||
preg_match_all(
|
||||
'/RegisterProperty(?:Integer|Float|Boolean|String)\(\'([^\']+)\'/',
|
||||
$basis . $modul,
|
||||
$treffer
|
||||
);
|
||||
$formular = (string) file_get_contents(self::MODULPFAD . 'form.json');
|
||||
foreach (array_values(array_unique($treffer[1])) as $property) {
|
||||
self::assertStringContainsString(
|
||||
'"name": "' . $property . '"',
|
||||
$formular,
|
||||
$property
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function testModulIstEreignisbasiertUndVerwendetVertrag40(): void
|
||||
{
|
||||
$modul = (string) file_get_contents(self::MODULPFAD . 'module.php');
|
||||
|
||||
self::assertStringContainsString('implements VerbraucherSchnittstelle', $modul);
|
||||
self::assertStringContainsString('ManagerdatenEmpfangen(array $daten)', $modul);
|
||||
self::assertStringContainsString('public function MessageSink(', $modul);
|
||||
self::assertStringContainsString('RegisterMessage($neueID, self::VM_UPDATE)', $modul);
|
||||
self::assertStringContainsString("'Betriebsart' =>", $modul);
|
||||
self::assertStringNotContainsString('Timer_Do_UserCalc_Battery', $modul);
|
||||
self::assertStringNotContainsString("RegisterTimer('Regelzyklus'", $modul);
|
||||
self::assertStringNotContainsString("RegisterVariableString('PowerSteps'", $modul);
|
||||
}
|
||||
|
||||
public function testRegisterWerdenUeberAusgewaehlteAktionsvariablenGeschrieben(): void
|
||||
{
|
||||
$modul = (string) file_get_contents(self::MODULPFAD . 'module.php');
|
||||
$formular = (string) file_get_contents(self::MODULPFAD . 'form.json');
|
||||
|
||||
self::assertStringContainsString(
|
||||
'RequestAction($this->ReadPropertyInteger($property), $wert)',
|
||||
$modul
|
||||
);
|
||||
self::assertStringContainsString('FormBatterietyp', $modul);
|
||||
self::assertStringContainsString('UpdateFormField', $modul);
|
||||
foreach ([
|
||||
'ManagementRegisterVariableID',
|
||||
'ModusRegisterVariableID',
|
||||
'LeistungsRegisterVariableID',
|
||||
'LadeleistungRegisterVariableID',
|
||||
'EntladeleistungRegisterVariableID',
|
||||
] as $property) {
|
||||
self::assertStringContainsString('"name": "' . $property . '"', $formular);
|
||||
}
|
||||
}
|
||||
|
||||
public function testLoggingDiagnoseUndAktionsbuttonsSindVorhanden(): void
|
||||
{
|
||||
$modul = (string) file_get_contents(self::MODULPFAD . 'module.php');
|
||||
$formular = (string) file_get_contents(self::MODULPFAD . 'form.json');
|
||||
|
||||
self::assertStringContainsString("ReadPropertyBoolean('LoggingEin')", $modul);
|
||||
self::assertStringContainsString('"name": "DiagnosevariablenAnzeigen"', $formular);
|
||||
self::assertStringContainsString('Messwerte und Angebot aktualisieren', $formular);
|
||||
self::assertStringContainsString('Register auf sicheren Zustand setzen', $formular);
|
||||
}
|
||||
|
||||
public function testManagerKenntDieBatterieModulIDGenauEinmal(): void
|
||||
{
|
||||
$manager = (string) file_get_contents(__DIR__ . '/../Manager/module.php');
|
||||
|
||||
self::assertSame(
|
||||
1,
|
||||
substr_count($manager, '{437FB683-517F-4FEC-8CCB-FE6B0A62B69E}')
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Belevo\EnelixEMS\Tests;
|
||||
|
||||
use Belevo\EnelixEMS\BatterieRegler;
|
||||
use InvalidArgumentException;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class BatterieReglerTest extends TestCase
|
||||
{
|
||||
public function testLeistungsrasterBehaeltGrobeUndFeineSchritte(): void
|
||||
{
|
||||
self::assertSame(
|
||||
[
|
||||
-1000, -750, -500, -250, -200, -150, -100, -50,
|
||||
0, 50, 100, 150, 200, 250, 500, 750, 1000,
|
||||
],
|
||||
BatterieRegler::leistungsraster(1000.0, 1000.0)
|
||||
);
|
||||
}
|
||||
|
||||
public function testHystereseSchaltetAnDenBisherigenGrenzen(): void
|
||||
{
|
||||
self::assertTrue(BatterieRegler::hystereseAktiv(20.0, 20.0, 2.0, false));
|
||||
self::assertTrue(BatterieRegler::hystereseAktiv(21.0, 20.0, 2.0, true));
|
||||
self::assertFalse(BatterieRegler::hystereseAktiv(22.1, 20.0, 2.0, true));
|
||||
}
|
||||
|
||||
public function testPvAngebotEntsprichtDemBisherigenSocVerhalten(): void
|
||||
{
|
||||
$voll = BatterieRegler::leistungsraster(1000.0, 1000.0);
|
||||
|
||||
self::assertSame(
|
||||
$voll,
|
||||
BatterieRegler::leistungsangebot(
|
||||
true, true, false, 50.0, 20.0, 10.0, false,
|
||||
0.0, 0.0, 1000.0, 1000.0
|
||||
)
|
||||
);
|
||||
self::assertSame(
|
||||
[1000],
|
||||
BatterieRegler::leistungsangebot(
|
||||
true, true, false, 19.0, 20.0, 10.0, true,
|
||||
0.0, 0.0, 1000.0, 1000.0
|
||||
)
|
||||
);
|
||||
self::assertSame(
|
||||
[-1000, -750, -500, -250, -200, -150, -100, -50, 0],
|
||||
BatterieRegler::leistungsangebot(
|
||||
true, true, false, 100.0, 20.0, 10.0, false,
|
||||
0.0, 0.0, 1000.0, 1000.0
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function testPeakAngebotBeruecksichtigtNetzUndAktuelleBatterieleistung(): void
|
||||
{
|
||||
self::assertSame(
|
||||
[-500],
|
||||
BatterieRegler::leistungsangebot(
|
||||
true, true, true, 50.0, 20.0, 10.0, false,
|
||||
700.0, 200.0, 1000.0, 1000.0
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function testWechselrichtersteuerungBietetNurNull(): void
|
||||
{
|
||||
self::assertSame(
|
||||
[0],
|
||||
BatterieRegler::leistungsangebot(
|
||||
true, false, false, 50.0, 20.0, 10.0, false,
|
||||
0.0, 0.0, 1000.0, 1000.0
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function testHerstellerunabhaengigeRegisterwerte(): void
|
||||
{
|
||||
self::assertSame(
|
||||
[
|
||||
'Ladeleistung' => 1500,
|
||||
'Entladeleistung' => 0,
|
||||
'Modus' => 0,
|
||||
'Management' => 1,
|
||||
],
|
||||
BatterieRegler::registerwerte(
|
||||
BatterieRegler::TYP_HERSTELLERUNABHAENGIG,
|
||||
BatterieRegler::STEUERUNG_ENELIX,
|
||||
1500
|
||||
)
|
||||
);
|
||||
self::assertSame(
|
||||
[
|
||||
'Ladeleistung' => 0,
|
||||
'Entladeleistung' => 1500,
|
||||
'Modus' => 1,
|
||||
'Management' => 1,
|
||||
],
|
||||
BatterieRegler::registerwerte(
|
||||
BatterieRegler::TYP_HERSTELLERUNABHAENGIG,
|
||||
BatterieRegler::STEUERUNG_ENELIX,
|
||||
-1500
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function testGoodweRegisterwerte(): void
|
||||
{
|
||||
self::assertSame(
|
||||
['Leistung' => 750, 'Modus' => 11, 'Management' => 2],
|
||||
BatterieRegler::registerwerte(
|
||||
BatterieRegler::TYP_GOODWE,
|
||||
BatterieRegler::STEUERUNG_ENELIX,
|
||||
750
|
||||
)
|
||||
);
|
||||
self::assertSame(
|
||||
['Leistung' => 750, 'Modus' => 12, 'Management' => 2],
|
||||
BatterieRegler::registerwerte(
|
||||
BatterieRegler::TYP_GOODWE,
|
||||
BatterieRegler::STEUERUNG_ENELIX,
|
||||
-750
|
||||
)
|
||||
);
|
||||
self::assertSame(
|
||||
['Leistung' => 0, 'Management' => 1],
|
||||
BatterieRegler::registerwerte(
|
||||
BatterieRegler::TYP_GOODWE,
|
||||
BatterieRegler::STEUERUNG_WECHSELRICHTER,
|
||||
-750
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function testSolarEdgeRegisterwerte(): void
|
||||
{
|
||||
self::assertSame(
|
||||
[
|
||||
'Ladeleistung' => 0,
|
||||
'Entladeleistung' => 500,
|
||||
'Modus' => 4,
|
||||
'Management' => 4,
|
||||
],
|
||||
BatterieRegler::registerwerte(
|
||||
BatterieRegler::TYP_SOLAREDGE,
|
||||
BatterieRegler::STEUERUNG_ENELIX,
|
||||
-500
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function testSigenergySkaliertLeistungAufKilowatt(): void
|
||||
{
|
||||
self::assertSame(
|
||||
[
|
||||
'Ladeleistung' => 1.5,
|
||||
'Entladeleistung' => 0.0,
|
||||
'Modus' => 3,
|
||||
'Management' => 1,
|
||||
],
|
||||
BatterieRegler::registerwerte(
|
||||
BatterieRegler::TYP_SIGENERGY,
|
||||
BatterieRegler::STEUERUNG_ENELIX,
|
||||
1500
|
||||
)
|
||||
);
|
||||
self::assertSame(
|
||||
[
|
||||
'Ladeleistung' => 0.0,
|
||||
'Entladeleistung' => 1.5,
|
||||
'Modus' => 6,
|
||||
'Management' => 1,
|
||||
],
|
||||
BatterieRegler::registerwerte(
|
||||
BatterieRegler::TYP_SIGENERGY,
|
||||
BatterieRegler::STEUERUNG_ENELIX,
|
||||
-1500
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function testUngueltigeGrenzenWerdenAbgewiesen(): void
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
|
||||
BatterieRegler::leistungsraster(-1.0, 1000.0);
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,6 @@ final class DokumentationsstrukturTest extends TestCase
|
||||
public function modulReadmes(): array
|
||||
{
|
||||
return [
|
||||
['Batterie'],
|
||||
['Waermepumpe'],
|
||||
];
|
||||
}
|
||||
@@ -29,6 +28,17 @@ final class DokumentationsstrukturTest extends TestCase
|
||||
self::assertStringContainsString('Properties', $inhalt);
|
||||
}
|
||||
|
||||
public function testBatterieDokumentiertDenImplementiertenStand(): void
|
||||
{
|
||||
$inhalt = file_get_contents(__DIR__ . '/../docs/module/Batterie/README.md');
|
||||
self::assertNotFalse($inhalt);
|
||||
self::assertStringContainsString('Status: implementiert', $inhalt);
|
||||
self::assertStringContainsString('Properties', $inhalt);
|
||||
self::assertStringContainsString('Registeradapter', $inhalt);
|
||||
self::assertFileExists(__DIR__ . '/../docs/Schnittstelle-Batterie.md');
|
||||
self::assertFileExists(__DIR__ . '/../docs/migration/Batterie.md');
|
||||
}
|
||||
|
||||
public function testPufferspeicherDokumentiertDenImplementiertenStand(): void
|
||||
{
|
||||
$inhalt = file_get_contents(__DIR__ . '/../docs/module/Pufferspeicher/README.md');
|
||||
|
||||
@@ -3,6 +3,16 @@
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
'Batterie' => [
|
||||
'file' => __DIR__ . '/modules/Batterie.php',
|
||||
'paths' => [
|
||||
'Batterie/*',
|
||||
'libs/BatterieRegler.php',
|
||||
'libs/VerbraucherBasisTrait.php',
|
||||
'libs/VerbraucherSchnittstelle.php',
|
||||
'libs/Nachrichtenvertrag.php',
|
||||
],
|
||||
],
|
||||
'EaseeGateway' => [
|
||||
'file' => __DIR__ . '/modules/EaseeGateway.php',
|
||||
'paths' => [
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Belevo\EnelixEMS\SymconTest\TestContext;
|
||||
|
||||
return static function (TestContext $test): void {
|
||||
$numerischesRegister = static function (
|
||||
TestContext $test,
|
||||
string $name,
|
||||
int $typ = 2
|
||||
): int {
|
||||
$variableID = $test->createVariable(
|
||||
$typ,
|
||||
$name,
|
||||
$typ === 1 ? 0 : 0.0
|
||||
);
|
||||
$skriptID = IPS_CreateScript(0);
|
||||
IPS_SetParent($skriptID, $test->rootId());
|
||||
IPS_SetName($skriptID, $name . ' Aktion');
|
||||
IPS_SetHidden($skriptID, true);
|
||||
IPS_SetScriptContent($skriptID, <<<'PHP'
|
||||
<?php
|
||||
|
||||
SetValue((int) $_IPS['VARIABLE'], $_IPS['VALUE']);
|
||||
PHP
|
||||
);
|
||||
IPS_SetVariableCustomAction($variableID, $skriptID);
|
||||
|
||||
return $variableID;
|
||||
};
|
||||
|
||||
$maxLadenID = $test->createVariable(2, 'Max Laden', 2000.0);
|
||||
$maxEntladenID = $test->createVariable(2, 'Max Entladen', 2000.0);
|
||||
$ladezustandID = $test->createVariable(2, 'Ladezustand', 50.0);
|
||||
$netzleistungID = $test->createVariable(2, 'Netzleistung', 0.0);
|
||||
$istleistungID = $test->createVariable(2, 'Batterieleistung', 0.0);
|
||||
$managementID = $numerischesRegister($test, 'Management', 1);
|
||||
$modusID = $numerischesRegister($test, 'Modus', 1);
|
||||
$leistungID = $numerischesRegister($test, 'Leistung', 2);
|
||||
|
||||
$batterieID = $test->createInstance(
|
||||
'{437FB683-517F-4FEC-8CCB-FE6B0A62B69E}',
|
||||
'Batterie GoodWe',
|
||||
[
|
||||
'Batterietyp' => 2,
|
||||
'Batteriemanagement' => 2,
|
||||
'MaxLadeleistungVariableID' => $maxLadenID,
|
||||
'MaxEntladeleistungVariableID' => $maxEntladenID,
|
||||
'LadezustandVariableID' => $ladezustandID,
|
||||
'NetzleistungVariableID' => $netzleistungID,
|
||||
'IstleistungVariableID' => $istleistungID,
|
||||
'ManagementRegisterVariableID' => $managementID,
|
||||
'ModusRegisterVariableID' => $modusID,
|
||||
'LeistungsRegisterVariableID' => $leistungID,
|
||||
'ReserveLadezustand' => 20.0,
|
||||
'MindestLadezustand' => 10.0,
|
||||
'LadezustandHysterese' => 2.0,
|
||||
'MesswertMaxAlter' => 120,
|
||||
'Aenderungssperre' => 0,
|
||||
'DiagnosevariablenAnzeigen' => true,
|
||||
]
|
||||
);
|
||||
$managerID = $test->createInstance(
|
||||
'{6F771B18-59D4-4C8A-B951-3B2FE9F6A2C4}',
|
||||
'Hilfsmanager Batterie',
|
||||
[
|
||||
'AutomatischeSuche' => false,
|
||||
'VerbraucherZuordnung' => json_encode([
|
||||
['InstanzID' => $batterieID, 'Aktiv' => true],
|
||||
], JSON_THROW_ON_ERROR),
|
||||
]
|
||||
);
|
||||
|
||||
$managerdatenSenden = static function (
|
||||
string $betriebsart,
|
||||
?int $leistung
|
||||
) use ($managerID, $batterieID): void {
|
||||
IPS_RequestAction($batterieID, 'ManagerdatenEmpfangen', json_encode([
|
||||
'Kopf' => [
|
||||
'Version' => '4.0',
|
||||
'AbsenderID' => $managerID,
|
||||
'EmpfaengerID' => $batterieID,
|
||||
'Zeitpunkt' => time(),
|
||||
],
|
||||
'Betriebsart' => $betriebsart,
|
||||
'Sollleistung_W' => $leistung,
|
||||
], JSON_THROW_ON_ERROR));
|
||||
};
|
||||
|
||||
$test->runCase(
|
||||
'Instanz und Diagnosevariablen werden angelegt',
|
||||
static function (TestContext $test) use ($batterieID): void {
|
||||
$test->assertInstanceStatus($batterieID);
|
||||
foreach ([
|
||||
'Aktiv',
|
||||
'Ladestatus',
|
||||
'Istleistung',
|
||||
'Sollleistung',
|
||||
'Ladezustand',
|
||||
'LadezustandGueltig',
|
||||
'HystereseAktiv',
|
||||
'LeistungsangebotDiagnose',
|
||||
'LetzterRegisterbefehl',
|
||||
] as $ident) {
|
||||
$test->objectByIdent($ident, $batterieID);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
$test->runCase(
|
||||
'GoodWe-Laden schreibt Leistung Modus und Management direkt',
|
||||
static function (TestContext $test) use (
|
||||
$batterieID,
|
||||
$managerdatenSenden,
|
||||
$managementID,
|
||||
$modusID,
|
||||
$leistungID
|
||||
): void {
|
||||
IPS_RequestAction($batterieID, 'Aktiv', true);
|
||||
$managerdatenSenden('PV', null);
|
||||
$managerdatenSenden('PV', 500);
|
||||
|
||||
$test->waitUntil(
|
||||
static fn (): bool => (float) GetValue($leistungID) === 500.0
|
||||
&& (int) GetValue($modusID) === 11
|
||||
&& (int) GetValue($managementID) === 2
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
$test->runCase(
|
||||
'GoodWe-Entladen verwendet negativen Managerwert und Modus 12',
|
||||
static function (TestContext $test) use (
|
||||
$managerdatenSenden,
|
||||
$modusID,
|
||||
$leistungID
|
||||
): void {
|
||||
$managerdatenSenden('PV', -500);
|
||||
$test->waitUntil(
|
||||
static fn (): bool => (float) GetValue($leistungID) === 500.0
|
||||
&& (int) GetValue($modusID) === 12
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
$test->runCase(
|
||||
'Istleistungsereignis aktualisiert den Ladestatus ohne Regelzyklus',
|
||||
static function (TestContext $test) use (
|
||||
$batterieID,
|
||||
$istleistungID
|
||||
): void {
|
||||
SetValue($istleistungID, -250.0);
|
||||
$ladestatusID = $test->objectByIdent('Ladestatus', $batterieID);
|
||||
$test->waitUntil(
|
||||
static fn (): bool => (int) GetValue($ladestatusID) === 3
|
||||
);
|
||||
|
||||
SetValue($istleistungID, 250.0);
|
||||
$test->waitUntil(
|
||||
static fn (): bool => (int) GetValue($ladestatusID) === 2
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
$test->runCase(
|
||||
'Wechselrichtersteuerung setzt Leistung auf null und Automatikmodus',
|
||||
static function (TestContext $test) use (
|
||||
$batterieID,
|
||||
$managementID,
|
||||
$leistungID
|
||||
): void {
|
||||
$test->configureInstance($batterieID, ['Batteriemanagement' => 1]);
|
||||
$test->waitUntil(
|
||||
static fn (): bool => (float) GetValue($leistungID) === 0.0
|
||||
&& (int) GetValue($managementID) === 1
|
||||
);
|
||||
}
|
||||
);
|
||||
};
|
||||
@@ -59,6 +59,7 @@ final class SymconTestContractTest extends TestCase
|
||||
{
|
||||
self::assertSame(
|
||||
[
|
||||
'Batterie',
|
||||
'LadestationGateway',
|
||||
'LadestationStandAlone',
|
||||
'Manager',
|
||||
@@ -82,6 +83,7 @@ final class SymconTestContractTest extends TestCase
|
||||
{
|
||||
self::assertSame(
|
||||
[
|
||||
'Batterie',
|
||||
'EaseeGateway',
|
||||
'LadestationGateway',
|
||||
'LadestationStandAlone',
|
||||
|
||||
Reference in New Issue
Block a user