fix(compat): preserve PHP 8.0 support without changing control guards
Tests / test (push) Successful in 59s
Tests / test (push) Successful in 59s
Replace trait constants and array_is_list usage with equivalent PHP 8.0 constructs. Add strict list/expiry regressions and make isolated reflection tests PHP 8.0 compatible. No control activation, threshold or configuration change.
This commit is contained in:
+1
-1
@@ -2108,7 +2108,7 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
|||||||
private function leseAnlagenKonfigurationsliste(string $property): array
|
private function leseAnlagenKonfigurationsliste(string $property): array
|
||||||
{
|
{
|
||||||
$liste = json_decode($this->ReadPropertyString($property), true, 512, JSON_THROW_ON_ERROR);
|
$liste = json_decode($this->ReadPropertyString($property), true, 512, JSON_THROW_ON_ERROR);
|
||||||
if (!is_array($liste) || !array_is_list($liste)) {
|
if (!is_array($liste) || array_keys($liste) !== array_keys(array_values($liste))) {
|
||||||
throw new InvalidArgumentException($property . ' muss eine JSON-Liste sein.');
|
throw new InvalidArgumentException($property . ' muss eine JSON-Liste sein.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,8 +12,16 @@ require_once __DIR__ . '/NetzfahrplanV4Regeltest.php';
|
|||||||
/** Locally approved, time-boxed operation of validated V4 shadow plans on a test plant. */
|
/** Locally approved, time-boxed operation of validated V4 shadow plans on a test plant. */
|
||||||
trait ManagerNetzfahrplanV4AktivtestTrait
|
trait ManagerNetzfahrplanV4AktivtestTrait
|
||||||
{
|
{
|
||||||
private const V4_AKTIVTEST_DAUER_SEKUNDEN = 172800;
|
// Trait constants require PHP 8.2; keep these fixed values compatible with PHP 8.0.
|
||||||
private const V4_AKTIVTEST_WATCHDOG_VERZICHT = 'explicit_test_plant_watchdog_waiver';
|
private static function v4AktivtestDauerSekunden(): int
|
||||||
|
{
|
||||||
|
return 172800;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function v4AktivtestWatchdogVerzicht(): string
|
||||||
|
{
|
||||||
|
return 'explicit_test_plant_watchdog_waiver';
|
||||||
|
}
|
||||||
|
|
||||||
private function registriereV4ManagerAktivtest(): void
|
private function registriereV4ManagerAktivtest(): void
|
||||||
{
|
{
|
||||||
@@ -78,12 +86,12 @@ trait ManagerNetzfahrplanV4AktivtestTrait
|
|||||||
$until = $this->ReadAttributeInteger('NetzfahrplanV4AktivtestEnde');
|
$until = $this->ReadAttributeInteger('NetzfahrplanV4AktivtestEnde');
|
||||||
if ($start === 0 && $until === 0) {
|
if ($start === 0 && $until === 0) {
|
||||||
$start = $now;
|
$start = $now;
|
||||||
$until = $now + self::V4_AKTIVTEST_DAUER_SEKUNDEN;
|
$until = $now + self::v4AktivtestDauerSekunden();
|
||||||
$this->WriteAttributeInteger('NetzfahrplanV4AktivtestBeginn', $start);
|
$this->WriteAttributeInteger('NetzfahrplanV4AktivtestBeginn', $start);
|
||||||
$this->WriteAttributeInteger('NetzfahrplanV4AktivtestEnde', $until);
|
$this->WriteAttributeInteger('NetzfahrplanV4AktivtestEnde', $until);
|
||||||
}
|
}
|
||||||
if ($start <= 0 || $until <= $now
|
if ($start <= 0 || $until <= $now
|
||||||
|| $until - $start > self::V4_AKTIVTEST_DAUER_SEKUNDEN) {
|
|| $until - $start > self::v4AktivtestDauerSekunden()) {
|
||||||
throw new RuntimeException('Die einmalige 48-Stunden-Aktivtestfreigabe ist abgelaufen.');
|
throw new RuntimeException('Die einmalige 48-Stunden-Aktivtestfreigabe ist abgelaufen.');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,7 +111,7 @@ trait ManagerNetzfahrplanV4AktivtestTrait
|
|||||||
'maxChargeW' => $maxChargeW,
|
'maxChargeW' => $maxChargeW,
|
||||||
'maxDischargeW' => $maxDischargeW,
|
'maxDischargeW' => $maxDischargeW,
|
||||||
'watchdogWaived' => true,
|
'watchdogWaived' => true,
|
||||||
'actuatorWatchdogEvidenceId' => self::V4_AKTIVTEST_WATCHDOG_VERZICHT,
|
'actuatorWatchdogEvidenceId' => self::v4AktivtestWatchdogVerzicht(),
|
||||||
'feedbackConfigHash' => $operation['localFeedback'][$asset['ID']]['configHash'] ?? '',
|
'feedbackConfigHash' => $operation['localFeedback'][$asset['ID']]['configHash'] ?? '',
|
||||||
'gridLimits' => NetzfahrplanV4Planpruefung::kontext($operation)['limits'],
|
'gridLimits' => NetzfahrplanV4Planpruefung::kontext($operation)['limits'],
|
||||||
'sequence' => 1,
|
'sequence' => 1,
|
||||||
@@ -206,7 +214,7 @@ trait ManagerNetzfahrplanV4AktivtestTrait
|
|||||||
'maxChargeW' => $session['maxChargeW'],
|
'maxChargeW' => $session['maxChargeW'],
|
||||||
'maxDischargeW' => $session['maxDischargeW'],
|
'maxDischargeW' => $session['maxDischargeW'],
|
||||||
'accountingEvidenceId' => $quality['accountingEvidenceId'] ?? null,
|
'accountingEvidenceId' => $quality['accountingEvidenceId'] ?? null,
|
||||||
'actuatorWatchdogEvidenceId' => self::V4_AKTIVTEST_WATCHDOG_VERZICHT,
|
'actuatorWatchdogEvidenceId' => self::v4AktivtestWatchdogVerzicht(),
|
||||||
'acceptEstimatedPeak' => true,
|
'acceptEstimatedPeak' => true,
|
||||||
'controlContext' => $plan['controlContext'] ?? null,
|
'controlContext' => $plan['controlContext'] ?? null,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ trait ManagerSDLTrait
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
$alt = json_decode(IPS_GetProperty($instanzID, 'Config'), true, 512, JSON_THROW_ON_ERROR);
|
$alt = json_decode(IPS_GetProperty($instanzID, 'Config'), true, 512, JSON_THROW_ON_ERROR);
|
||||||
if (!is_array($alt) || !array_is_list($alt)) {
|
if (!is_array($alt) || array_keys($alt) !== array_keys(array_values($alt))) {
|
||||||
throw new RuntimeException('Die bestehende Energiefluss-Konfiguration ist keine Liste.');
|
throw new RuntimeException('Die bestehende Energiefluss-Konfiguration ist keine Liste.');
|
||||||
}
|
}
|
||||||
$neu = [];
|
$neu = [];
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ final class NetzfahrplanV4Rueckmeldung
|
|||||||
|
|
||||||
private static function ordered(array $v): array
|
private static function ordered(array $v): array
|
||||||
{
|
{
|
||||||
if (!array_is_list($v)) ksort($v, SORT_STRING);
|
if (array_keys($v) !== array_keys(array_values($v))) ksort($v, SORT_STRING);
|
||||||
foreach ($v as &$x) if (is_array($x)) $x=self::ordered($x);
|
foreach ($v as &$x) if (is_array($x)) $x=self::ordered($x);
|
||||||
unset($x);return $v;
|
unset($x);return $v;
|
||||||
}
|
}
|
||||||
@@ -40,7 +40,7 @@ final class NetzfahrplanV4Rueckmeldung
|
|||||||
}
|
}
|
||||||
$c['idleToleranceW']=self::number($c['idleToleranceW'],0,100);
|
$c['idleToleranceW']=self::number($c['idleToleranceW'],0,100);
|
||||||
$c['trackingToleranceW']=self::number($c['trackingToleranceW'],0,500);
|
$c['trackingToleranceW']=self::number($c['trackingToleranceW'],0,500);
|
||||||
if (!is_array($c['sources']) || !array_is_list($c['sources']) || count($c['sources'])<2 || count($c['sources'])>40) throw new InvalidArgumentException('feedback_sources_invalid');
|
if (!is_array($c['sources']) || array_keys($c['sources']) !== array_keys(array_values($c['sources'])) || count($c['sources'])<2 || count($c['sources'])>40) throw new InvalidArgumentException('feedback_sources_invalid');
|
||||||
$ids=[];$names=[];$roles=[];
|
$ids=[];$names=[];$roles=[];
|
||||||
foreach ($c['sources'] as &$s) {
|
foreach ($c['sources'] as &$s) {
|
||||||
$fields=['key','role','variableId','parentId','ident','factorToW'];
|
$fields=['key','role','variableId','parentId','ident','factorToW'];
|
||||||
|
|||||||
@@ -19,6 +19,6 @@ final class NetzfahrplanV4RegeltestTest extends TestCase
|
|||||||
fclose($pipes[1]);fclose($pipes[2]);
|
fclose($pipes[1]);fclose($pipes[2]);
|
||||||
self::assertSame(0, proc_close($process), $out . $err);
|
self::assertSame(0, proc_close($process), $out . $err);
|
||||||
self::assertSame('', $err);
|
self::assertSame('', $err);
|
||||||
self::assertStringContainsString('TOTAL 96 checks passed.', $out);
|
self::assertStringContainsString('TOTAL 101 checks passed.', $out);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
if (function_exists('IPS_GetKernelVersion')) {
|
||||||
|
throw new RuntimeException('Offline list checks must never run in Symcon.');
|
||||||
|
}
|
||||||
|
require_once __DIR__ . '/../Support/ManagerSDLRuntime.php';
|
||||||
|
require_once __DIR__ . '/../../Manager/module.php';
|
||||||
|
require_once __DIR__ . '/../../libs/NetzfahrplanV4Rueckmeldung.php';
|
||||||
|
|
||||||
|
use Belevo\EnelixEMS\NetzfahrplanV4Rueckmeldung as Feedback;
|
||||||
|
|
||||||
|
$count = 0;
|
||||||
|
function verify(bool $ok, string $name): void
|
||||||
|
{
|
||||||
|
global $count;
|
||||||
|
if (!$ok) throw new RuntimeException('FAIL ' . $name);
|
||||||
|
++$count;
|
||||||
|
}
|
||||||
|
function invokePrivate(object $object, string $name, array $arguments)
|
||||||
|
{
|
||||||
|
$method = new ReflectionMethod($object, $name);
|
||||||
|
if (PHP_VERSION_ID < 80100) $method->setAccessible(true);
|
||||||
|
return $method->invokeArgs($object, $arguments);
|
||||||
|
}
|
||||||
|
function accepted(callable $call, string $message): bool
|
||||||
|
{
|
||||||
|
try { $call(); return true; }
|
||||||
|
catch (RuntimeException | InvalidArgumentException $error) {
|
||||||
|
if ($error->getMessage() !== $message) throw $error;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$row = ['ID' => 'example', 'VariableID' => 12345];
|
||||||
|
$cases = [
|
||||||
|
'empty' => [[], true],
|
||||||
|
'one' => [[$row], true],
|
||||||
|
'two' => [[$row, $row], true],
|
||||||
|
'sparse' => [[0 => $row, 2 => $row], false],
|
||||||
|
'one-based' => [[1 => $row, 2 => $row], false],
|
||||||
|
'out-of-order' => [[1 => $row, 0 => $row], false],
|
||||||
|
'negative-key' => [[-1 => $row, 0 => $row], false],
|
||||||
|
'string-key' => [['source' => $row], false],
|
||||||
|
'mixed-keys' => [[0 => $row, 'source' => $row], false],
|
||||||
|
'scalar' => [false, false],
|
||||||
|
'null' => [null, false],
|
||||||
|
];
|
||||||
|
foreach ($cases as $name => [$value, $expected]) {
|
||||||
|
ManagerSDLRuntime::reset();
|
||||||
|
$manager = new Manager(42);
|
||||||
|
$manager->Create();
|
||||||
|
$json = json_encode($value, JSON_THROW_ON_ERROR);
|
||||||
|
$manager->testProperties['AnlagenBatterien'] = $json;
|
||||||
|
$result = accepted(static function () use ($manager, $value, $expected): void {
|
||||||
|
$actual = invokePrivate($manager, 'leseAnlagenKonfigurationsliste', ['AnlagenBatterien']);
|
||||||
|
if ($expected) verify($actual === $value, 'valid manager list unchanged');
|
||||||
|
}, 'AnlagenBatterien muss eine JSON-Liste sein.');
|
||||||
|
verify($result === $expected, 'manager list: ' . $name);
|
||||||
|
|
||||||
|
ManagerSDLRuntime::child(42, 'EnergieflussSDL');
|
||||||
|
ManagerSDLRuntime::$properties[700]['Config'] = $json;
|
||||||
|
$result = accepted(static fn() => invokePrivate($manager, 'synchronisiereSDLEnergiefluss', [700]),
|
||||||
|
'Die bestehende Energiefluss-Konfiguration ist keine Liste.');
|
||||||
|
verify($result === $expected, 'SDL list: ' . $name);
|
||||||
|
verify(ManagerSDLRuntime::$writes === [], 'list validation cannot change SDL configuration');
|
||||||
|
verify(ManagerSDLRuntime::$properties[700]['Config'] === $json, 'SDL source remains byte-identical');
|
||||||
|
}
|
||||||
|
|
||||||
|
$config = ['version'=>1, 'installationId'=>'e3a08f9e-af12-4695-99bd-8b51c0520021',
|
||||||
|
'assetId'=>'ev', 'managerId'=>17004, 'batteryInstanceId'=>44234, 'mode'=>'physical_sum',
|
||||||
|
'allowEstimatedForTrial'=>false, 'maxSkewSeconds'=>30, 'maxAgeSeconds'=>60,
|
||||||
|
'idleToleranceW'=>50.0, 'trackingToleranceW'=>200.0, 'sources'=>[]];
|
||||||
|
foreach (['grid', 'physical'] as $i => $role) {
|
||||||
|
$config['sources'][] = ['key'=>'source'.$i, 'role'=>$role, 'variableId'=>100+$i,
|
||||||
|
'parentId'=>200, 'ident'=>'value'.$i, 'factorToW'=>1];
|
||||||
|
}
|
||||||
|
$hash = Feedback::fingerprint($config);
|
||||||
|
$reordered = array_reverse($config, true);
|
||||||
|
$reordered['sources'] = array_reverse($config['sources']);
|
||||||
|
verify(Feedback::fingerprint($reordered) === $hash, 'canonical configuration fingerprint unchanged');
|
||||||
|
foreach ([[], [1, 2], [0, 2], [1, 0], ['grid', 'physical']] as $keys) {
|
||||||
|
$bad = $config;
|
||||||
|
$bad['sources'] = $keys === [] ? [] : array_combine($keys, $config['sources']);
|
||||||
|
verify(!accepted(static fn() => Feedback::configuration($bad), 'feedback_sources_invalid'),
|
||||||
|
'malformed feedback source keys remain rejected');
|
||||||
|
}
|
||||||
|
echo 'PHP80_LISTS_OK ' . $count . " checks; real methods with isolated runtime only.\n";
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Belevo\EnelixEMS\Tests;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
final class Php80ListsTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testListValidationIsIdenticalOnSupportedPhpVersions(): void
|
||||||
|
{
|
||||||
|
$pipes = [];
|
||||||
|
$process = proc_open([PHP_BINARY, __DIR__ . '/Php80Lists/checks.php'],
|
||||||
|
[0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']], $pipes);
|
||||||
|
self::assertIsResource($process);
|
||||||
|
fclose($pipes[0]);
|
||||||
|
$out = stream_get_contents($pipes[1]);
|
||||||
|
$err = stream_get_contents($pipes[2]);
|
||||||
|
fclose($pipes[1]);
|
||||||
|
fclose($pipes[2]);
|
||||||
|
self::assertSame(0, proc_close($process), $out . $err);
|
||||||
|
self::assertSame('', $err);
|
||||||
|
self::assertStringContainsString('PHP80_LISTS_OK ', $out);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -210,6 +210,9 @@ namespace {
|
|||||||
$bad=$r;$bad['controlledTrial']['acceptEstimatedPeak']=false;rejects(fn()=>Gate::befehl($bad,$op,['b'=>0.],$local,$now,$now),'peak uncertainty needs explicit acceptance');
|
$bad=$r;$bad['controlledTrial']['acceptEstimatedPeak']=false;rejects(fn()=>Gate::befehl($bad,$op,['b'=>0.],$local,$now,$now),'peak uncertainty needs explicit acceptance');
|
||||||
[$m,$b]=activeFixture();$m->V4ManagerAktivtestSchalten(true);
|
[$m,$b]=activeFixture();$m->V4ManagerAktivtestSchalten(true);
|
||||||
check($m->active()&&$b->active()&&$m->vars['NetzfahrplanV4Aktivtest'],'single runtime switch starts bounded active test');
|
check($m->active()&&$b->active()&&$m->vars['NetzfahrplanV4Aktivtest'],'single runtime switch starts bounded active test');
|
||||||
|
$activeSession=json_decode($m->buf['V4ManagerTrialSession'],true);
|
||||||
|
check($activeSession['actuatorWatchdogEvidenceId']==='explicit_test_plant_watchdog_waiver',
|
||||||
|
'active session retains exact explicit watchdog waiver identifier');
|
||||||
$allocation=tick($m);
|
$allocation=tick($m);
|
||||||
check(end($b->writes)===3000&&!isset($allocation['Sollwerte'][44234]),'active test uses guarded command path');
|
check(end($b->writes)===3000&&!isset($allocation['Sollwerte'][44234]),'active test uses guarded command path');
|
||||||
check($m->command()['executionMeaning']==='explicit_time_boxed_test_plant_operation','active command is explicitly distinguished');
|
check($m->command()['executionMeaning']==='explicit_time_boxed_test_plant_operation','active command is explicitly distinguished');
|
||||||
@@ -231,6 +234,19 @@ namespace {
|
|||||||
check($m->active()&&$b->active(),'local active authority refreshes while plan and measurements remain valid');
|
check($m->active()&&$b->active(),'local active authority refreshes while plan and measurements remain valid');
|
||||||
$m->V4ManagerAktivtestSchalten(false);
|
$m->V4ManagerAktivtestSchalten(false);
|
||||||
check(!$m->active()&&!$b->active()&&!$m->vars['NetzfahrplanV4Aktivtest']&&end($b->writes)===0,'switch off revokes session and requests zero');
|
check(!$m->active()&&!$b->active()&&!$m->vars['NetzfahrplanV4Aktivtest']&&end($b->writes)===0,'switch off revokes session and requests zero');
|
||||||
|
[$m,$b]=activeFixture();
|
||||||
|
$m->attrs['NetzfahrplanV4AktivtestBeginn']=$clock-172800;
|
||||||
|
$m->attrs['NetzfahrplanV4AktivtestEnde']=$clock;
|
||||||
|
rejects(fn()=>$m->V4ManagerAktivtestSchalten(true),'active window rejects its exact expiry boundary');
|
||||||
|
check($m->attrs['NetzfahrplanV4AktivtestBeginn']===$clock-172800
|
||||||
|
&&$m->attrs['NetzfahrplanV4AktivtestEnde']===$clock&&!$m->active()&&!$b->active(),
|
||||||
|
'expired active window is never renewed or armed');
|
||||||
|
[$m,$b]=activeFixture();
|
||||||
|
$m->attrs['NetzfahrplanV4AktivtestBeginn']=$clock-1;
|
||||||
|
$m->attrs['NetzfahrplanV4AktivtestEnde']=$clock+172800;
|
||||||
|
rejects(fn()=>$m->V4ManagerAktivtestSchalten(true),'active window rejects even one second beyond 48 hours');
|
||||||
|
check($b->writes===[]&&!$m->active()&&!$b->active(),
|
||||||
|
'oversized active window cannot cause register writes or arm either session');
|
||||||
[$m,$b]=activeFixture();$m->props['NetzfahrplanV4WatchdogVerzichtErlaubt']=false;
|
[$m,$b]=activeFixture();$m->props['NetzfahrplanV4WatchdogVerzichtErlaubt']=false;
|
||||||
rejects(fn()=>$m->V4ManagerAktivtestSchalten(true),'active test requires explicit watchdog waiver property');
|
rejects(fn()=>$m->V4ManagerAktivtestSchalten(true),'active test requires explicit watchdog waiver property');
|
||||||
[$m,$b]=activeFixture();$b->props['NetzfahrplanV4WatchdogVerzichtErlaubt']=false;
|
[$m,$b]=activeFixture();$b->props['NetzfahrplanV4WatchdogVerzichtErlaubt']=false;
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ $battery->props = [
|
|||||||
'LeistungsRegisterVariableID' => 3, 'Aenderungssperre' => 0,
|
'LeistungsRegisterVariableID' => 3, 'Aenderungssperre' => 0,
|
||||||
];
|
];
|
||||||
$method = new ReflectionMethod(Batterie::class, 'schreibeRegister');
|
$method = new ReflectionMethod(Batterie::class, 'schreibeRegister');
|
||||||
|
if (PHP_VERSION_ID < 80100) $method->setAccessible(true);
|
||||||
$accepted = false; $writes = [];
|
$accepted = false; $writes = [];
|
||||||
try { $method->invoke($battery, 12000, false); throw new LogicException('FALSE was accepted'); }
|
try { $method->invoke($battery, 12000, false); throw new LogicException('FALSE was accepted'); }
|
||||||
catch (RuntimeException $error) {
|
catch (RuntimeException $error) {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ class IPSModule {
|
|||||||
require __DIR__.'/../../Batterie/module.php';
|
require __DIR__.'/../../Batterie/module.php';
|
||||||
use Belevo\EnelixEMS\Nachrichtenvertrag as N;
|
use Belevo\EnelixEMS\Nachrichtenvertrag as N;
|
||||||
$b=new Batterie();$read=new ReflectionMethod(Batterie::class,'baueVerbraucherdaten');$count=0;
|
$b=new Batterie();$read=new ReflectionMethod(Batterie::class,'baueVerbraucherdaten');$count=0;
|
||||||
|
if (PHP_VERSION_ID < 80100) $read->setAccessible(true);
|
||||||
function ck($v,$s){global$count;if(!$v)throw new RuntimeException('FAIL '.$s);$count++;echo "PASS $s\n";}
|
function ck($v,$s){global$count;if(!$v)throw new RuntimeException('FAIL '.$s);$count++;echo "PASS $s\n";}
|
||||||
$d=$read->invoke($b,17004);ck($d['Leistungsquelle']===N::LEISTUNGSQUELLE_BERECHNET,'partition source quality independent of hidden diagnostic variables');
|
$d=$read->invoke($b,17004);ck($d['Leistungsquelle']===N::LEISTUNGSQUELLE_BERECHNET,'partition source quality independent of hidden diagnostic variables');
|
||||||
$b->buffers['V4FeedbackEstimated']='0';$d=$read->invoke($b,17004);ck($d['Leistungsquelle']===N::LEISTUNGSQUELLE_GEMESSEN,'ordinary physical source marked measured');
|
$b->buffers['V4FeedbackEstimated']='0';$d=$read->invoke($b,17004);ck($d['Leistungsquelle']===N::LEISTUNGSQUELLE_GEMESSEN,'ordinary physical source marked measured');
|
||||||
|
|||||||
Reference in New Issue
Block a user