Tests / test (push) Successful in 1m1s
Approved by Daniel Haefliger for develop and beta. Author dh_Agent, authenticated account dh. Preserve published battery, charging and overall Energy Pie changes. No deployment or plant control authorization.
328 lines
20 KiB
PHP
328 lines
20 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Belevo\EnelixEMS;
|
|
|
|
if (PHP_SAPI !== 'cli' || function_exists('IPS_GetKernelVersion')) {
|
|
throw new \RuntimeException('Standalone PHP CLI only; never load mocks into Symcon.');
|
|
}
|
|
|
|
require_once __DIR__ . '/../../libs/NetzfahrplanV4Einrichtung.php';
|
|
require_once __DIR__ . '/../../libs/NetzfahrplanV4RueckmeldungEinrichtung.php';
|
|
require_once __DIR__ . '/../../libs/BatterieNetzfahrplanV4RueckmeldungTrait.php';
|
|
|
|
$count = 0;
|
|
function feedbackSetupCheck(bool $ok, string $label): void
|
|
{
|
|
global $count;
|
|
if (!$ok) throw new \RuntimeException('FAIL ' . $label);
|
|
++$count;
|
|
echo 'PASS ' . $label . PHP_EOL;
|
|
}
|
|
function feedbackSetupRefuses(callable $call, string $label): void
|
|
{
|
|
try { $call(); }
|
|
catch (\Throwable $error) { feedbackSetupCheck(true, $label); return; }
|
|
throw new \RuntimeException('Expected refusal: ' . $label);
|
|
}
|
|
function feedbackSetupFixture(): array
|
|
{
|
|
$assets = [['ID' => 'battery', 'WechselrichterID' => 'battery-wr', 'Kopplung' => 'ac',
|
|
'LeistungVariableID' => 103, 'SOCVariableID' => 104, 'Leistungsfaktor' => 1.0]];
|
|
$objects = [
|
|
101 => ['parentID' => 201, 'ident' => 'Grid', 'type' => 2],
|
|
102 => ['parentID' => 202, 'ident' => 'PV', 'type' => 2],
|
|
103 => ['parentID' => 203, 'ident' => 'Battery', 'type' => 2],
|
|
];
|
|
$installationId = '11111111-2222-4333-8444-555555555555';
|
|
$setup = NetzfahrplanV4Einrichtung::erstellen($installationId, 900,
|
|
['NetzleistungVariableID' => 101, 'Netzleistungsfaktor' => 1.0, 'MesswertMaxAlter' => 60],
|
|
[['ID' => 'pv-wr', 'Typ' => 'pv', 'IstleistungVariableID' => 102, 'Istleistungsfaktor' => 1000],
|
|
['ID' => 'battery-wr', 'Typ' => 'battery']],
|
|
$assets, static fn(int $id): array => $objects[$id]);
|
|
return ['capture' => $setup['capture'], 'installationId' => $installationId,
|
|
'managerId' => 900, 'batteryId' => 901, 'assets' => $assets,
|
|
'controllers' => [['InstanzID' => 901, 'IstleistungVariableID' => 103,
|
|
'LadezustandVariableID' => 104, 'NetzleistungVariableID' => 101, 'MesswertMaxAlter' => 60]]];
|
|
}
|
|
function feedbackSetupCreate(array $f): array
|
|
{
|
|
return NetzfahrplanV4RueckmeldungEinrichtung::erstellen($f['capture'], $f['installationId'],
|
|
$f['managerId'], $f['batteryId'], $f['assets'], $f['controllers']);
|
|
}
|
|
|
|
$f = feedbackSetupFixture();
|
|
$config = feedbackSetupCreate($f);
|
|
feedbackSetupCheck($config === feedbackSetupCreate($f), 'automatic feedback deterministic');
|
|
feedbackSetupCheck($config['installationId'] === $f['installationId'] && $config['managerId'] === 900
|
|
&& $config['batteryInstanceId'] === 901 && $config['assetId'] === 'battery', 'single exact plant and controller binding');
|
|
feedbackSetupCheck($config['mode'] === 'physical_sum' && !$config['allowEstimatedForTrial'], 'only separately metered physical feedback');
|
|
feedbackSetupCheck(count($config['sources']) === 2 && array_column($config['sources'], 'variableId') === [103, 101],
|
|
'battery and grid sampled exactly once without PV double count');
|
|
feedbackSetupCheck(array_column($config['sources'], 'role') === ['physical', 'grid'], 'no invented request or gateway sources');
|
|
feedbackSetupCheck($config['maxAgeSeconds'] === 60 && $config['maxSkewSeconds'] === 30, 'existing strict freshness defaults');
|
|
feedbackSetupCheck($config['idleToleranceW'] === 0.0 && $config['trackingToleranceW'] === 0.0, 'no relaxed tracking tolerance');
|
|
feedbackSetupCheck(!str_contains(json_encode($config), 'watchdog') && !str_contains(json_encode($config), 'canDispatch')
|
|
&& !str_contains(json_encode($config), 'confirmedAt'), 'setup creates neither confirmation nor control permission');
|
|
$roundtrip = json_decode(json_encode($f), true);
|
|
feedbackSetupCheck(feedbackSetupCreate($roundtrip) === $config, 'JSON float normalization stable');
|
|
$changed = $f;
|
|
$changed['controllers'][0]['MesswertMaxAlter'] = 8;
|
|
feedbackSetupCheck(feedbackSetupCreate($changed)['maxAgeSeconds'] === 8
|
|
&& feedbackSetupCreate($changed)['maxSkewSeconds'] === 8, 'controller stricter freshness preserved');
|
|
$changed = $f;
|
|
$changed['controllers'][0]['MesswertMaxAlter'] = 600;
|
|
feedbackSetupCheck(feedbackSetupCreate($changed)['maxAgeSeconds'] === 60, 'large local setting cannot relax feedback freshness');
|
|
$changed = $f;
|
|
foreach ($changed['capture']['accounting']['sources'] as &$source) {
|
|
if ($source['role'] === 'physical_storage') $source['maxAgeSeconds'] = 7;
|
|
}
|
|
unset($source);
|
|
feedbackSetupCheck(feedbackSetupCreate($changed)['maxAgeSeconds'] === 7, 'capture source stricter freshness preserved');
|
|
$changed = $f;
|
|
$changed['assets'][0]['Name'] = 'private name';
|
|
$changed['controllers'][0]['Password'] = 'do-not-export';
|
|
feedbackSetupCheck(feedbackSetupCreate($changed) === $config, 'only allowlisted nonsecret fields leave setup');
|
|
$changed = $f;
|
|
$changed['installationId'] = $changed['capture']['installationId'] = '99999999-2222-4333-8444-555555555555';
|
|
feedbackSetupCheck(NetzfahrplanV4Rueckmeldung::fingerprint(feedbackSetupCreate($changed))
|
|
!== NetzfahrplanV4Rueckmeldung::fingerprint($config), 'distinct plant cannot share feedback identity');
|
|
$changed = $f;
|
|
foreach ($changed['capture']['accounting']['sources'] as &$source) {
|
|
if ($source['role'] === 'physical_storage') $source['parentId'] = 205;
|
|
}
|
|
unset($source);
|
|
feedbackSetupCheck(NetzfahrplanV4Rueckmeldung::fingerprint(feedbackSetupCreate($changed))
|
|
!== NetzfahrplanV4Rueckmeldung::fingerprint($config), 'changed physical identity changes fingerprint');
|
|
|
|
foreach ([
|
|
static function (array &$x): void { $x['managerId']++; },
|
|
static function (array &$x): void { $x['batteryId']++; },
|
|
static function (array &$x): void { $x['assets'] = []; },
|
|
static function (array &$x): void { $x['assets'][] = $x['assets'][0]; },
|
|
static function (array &$x): void { $x['controllers'] = []; },
|
|
static function (array &$x): void { $x['controllers'][] = $x['controllers'][0]; },
|
|
static function (array &$x): void { $x['controllers'][0]['Aktiv'] = false; },
|
|
static function (array &$x): void { $x['controllers'][0]['IstleistungVariableID'] = 105; },
|
|
static function (array &$x): void { $x['controllers'][0]['LadezustandVariableID'] = 105; },
|
|
static function (array &$x): void { $x['controllers'][0]['NetzleistungVariableID'] = 105; },
|
|
static function (array &$x): void { $x['controllers'][0]['MesswertMaxAlter'] = 0; },
|
|
static function (array &$x): void { unset($x['assets'][0]['SOCVariableID']); },
|
|
static function (array &$x): void { $x['assets'][0]['SOCVariableID'] = $x['controllers'][0]['LadezustandVariableID'] = 103; },
|
|
static function (array &$x): void { $x['assets'][0]['Virtuell'] = true; },
|
|
static function (array &$x): void { $x['assets'][0]['SDLAktiv'] = true; },
|
|
static function (array &$x): void { $x['assets'][0]['Kopplung'] = 'dc'; },
|
|
static function (array &$x): void { $x['assets'][0]['ID'] = 'different'; },
|
|
static function (array &$x): void { $x['assets'][0]['Leistungsfaktor'] = -1; },
|
|
static function (array &$x): void { unset($x['capture']['accounting']['storageMode']); },
|
|
] as $index => $change) {
|
|
$changed = $f;
|
|
$change($changed);
|
|
feedbackSetupRefuses(static fn() => feedbackSetupCreate($changed), 'ambiguous or incompatible binding ' . $index);
|
|
}
|
|
foreach (['grid', 'physical_storage'] as $role) {
|
|
foreach ([-1, 1000] as $factor) {
|
|
$changed = $f;
|
|
foreach ($changed['capture']['accounting']['sources'] as &$source) {
|
|
if ($source['role'] === $role) $source['factorToW'] = $factor;
|
|
}
|
|
unset($source);
|
|
feedbackSetupRefuses(static fn() => feedbackSetupCreate($changed), 'controller raw W units ' . $role . ' ' . $factor);
|
|
}
|
|
}
|
|
$changed = $f;
|
|
$changed['capture']['accounting']['sources'][] = [
|
|
'key' => 'other', 'role' => 'reference', 'variableId' => 105, 'parentId' => 205,
|
|
'ident' => 'Other', 'factorToW' => 1.0, 'maxAgeSeconds' => 60, 'dependsOn' => [],
|
|
];
|
|
feedbackSetupRefuses(static fn() => feedbackSetupCreate($changed), 'specialist accounting roles require explicit mapping');
|
|
$changed = $f;
|
|
$changed['capture']['accounting']['sources'][0]['dependsOn'] = ['grid'];
|
|
feedbackSetupRefuses(static fn() => feedbackSetupCreate($changed), 'derived physical source cannot become automatic feedback');
|
|
$changed = $f;
|
|
$changed['capture']['extraSources'][] = [
|
|
'key' => 'soc', 'variableId' => 104, 'parentId' => 203, 'ident' => 'SOC', 'unit' => 'percent',
|
|
];
|
|
feedbackSetupRefuses(static fn() => feedbackSetupCreate($changed), 'specialist extra sources require explicit mapping');
|
|
|
|
$GLOBALS['feedbackTestNow'] = 1791450000;
|
|
$GLOBALS['feedbackTestDeviceCalls'] = 0;
|
|
$GLOBALS['feedbackTestGetterCalls'] = 0;
|
|
$GLOBALS['feedbackTestManagerRunning'] = false;
|
|
$GLOBALS['feedbackTestGetterConfig'] = '';
|
|
$GLOBALS['feedbackTestReadSucceeds'] = true;
|
|
$GLOBALS['feedbackTestModules'] = [201 => NetzfahrplanV4Geraeteabruf::MBUS_DEVICE, 203 => NetzfahrplanV4Geraeteabruf::MODBUS_DEVICE];
|
|
$GLOBALS['feedbackTestObjects'] = [101 => ['ParentID' => 201, 'ObjectIdent' => 'Grid'], 103 => ['ParentID' => 203, 'ObjectIdent' => 'Battery']];
|
|
$GLOBALS['feedbackTestValues'] = [101 => 1000, 103 => 250];
|
|
function time(): int { return $GLOBALS['feedbackTestNow']; }
|
|
function usleep(int $duration): void {}
|
|
function ENELIX_GetV4RueckmeldungKonfiguration(int $manager, int $battery): string
|
|
{
|
|
++$GLOBALS['feedbackTestGetterCalls'];
|
|
if ($GLOBALS['feedbackTestManagerRunning']) throw new \RuntimeException('forbidden Manager -> battery cache -> Manager cycle');
|
|
if ($manager !== 900 || $battery !== 901) throw new \RuntimeException('wrong getter invocation');
|
|
return $GLOBALS['feedbackTestGetterConfig'];
|
|
}
|
|
function IPS_SemaphoreEnter(string $key, int $timeout): bool { return true; }
|
|
function IPS_SemaphoreLeave(string $key): void {}
|
|
function IPS_VariableExists(int $id): bool { return isset($GLOBALS['feedbackTestObjects'][$id]); }
|
|
function IPS_GetVariable(int $id): array
|
|
{
|
|
return ['VariableUpdated' => time() - 120, 'VariableChanged' => time() - 180];
|
|
}
|
|
function IPS_GetObject(int $id): array { return $GLOBALS['feedbackTestObjects'][$id]; }
|
|
function GetValue(int $id): int { return $GLOBALS['feedbackTestValues'][$id]; }
|
|
function IPS_InstanceExists(int $id): bool { return isset($GLOBALS['feedbackTestModules'][$id]); }
|
|
function IPS_GetInstance(int $id): array { return ['ModuleInfo' => ['ModuleID' => $GLOBALS['feedbackTestModules'][$id]]]; }
|
|
function MBUS_UpdateValues(int $id): bool
|
|
{
|
|
++$GLOBALS['feedbackTestDeviceCalls'];
|
|
return $GLOBALS['feedbackTestReadSucceeds'];
|
|
}
|
|
function ModBus_RequestRead(int $id): bool { return MBUS_UpdateValues($id); }
|
|
|
|
final class FeedbackSetupBattery
|
|
{
|
|
use BatterieNetzfahrplanV4RueckmeldungTrait;
|
|
|
|
public int $InstanceID = 901;
|
|
public array $managers = [], $buffers = [], $timers = [];
|
|
public string $property = '{}';
|
|
public function register(): void { $this->registriereV4Rueckmeldung(); }
|
|
public function configure(): void { $this->konfiguriereV4Rueckmeldung(); }
|
|
public function tick(): void { $this->aktualisiereV4Rueckmeldung(); }
|
|
public function effective(): string { return $this->effektiveV4RueckmeldungKonfiguration(); }
|
|
private function zugeordneteManagerIDs(): array
|
|
{
|
|
if ($GLOBALS['feedbackTestManagerRunning']) throw new \RuntimeException('cache must not inspect Manager assignment');
|
|
return $this->managers;
|
|
}
|
|
protected function ReadPropertyString(string $key): string { return $this->property; }
|
|
protected function RegisterPropertyString(string $key, string $value): void {}
|
|
protected function RegisterTimer(string $key, int $interval, string $script): void { $this->timers[$key] = compact('interval', 'script'); }
|
|
protected function SetTimerInterval(string $key, int $interval): void { $this->timers[$key]['interval'] = $interval; }
|
|
protected function SetBuffer(string $key, string $value): void { $this->buffers[$key] = $value; }
|
|
protected function GetBuffer(string $key): string { return $this->buffers[$key] ?? ''; }
|
|
}
|
|
|
|
$battery = new FeedbackSetupBattery();
|
|
$battery->register();
|
|
$timerScript = $battery->timers['NetzfahrplanV4RueckmeldungAktualisieren']['script'];
|
|
feedbackSetupCheck($timerScript === "ENELIX_BatterieTimerAktion(\$_IPS['TARGET'], 'NetzfahrplanV4RueckmeldungAktualisieren', false);",
|
|
'native timer wrapper unchanged');
|
|
$battery->configure();
|
|
$battery->tick();
|
|
feedbackSetupCheck($battery->timers['NetzfahrplanV4RueckmeldungAktualisieren']['interval'] === 1000,
|
|
'existing sampler discovers assignment after battery setup');
|
|
feedbackSetupCheck($GLOBALS['feedbackTestDeviceCalls'] === 0 && $GLOBALS['feedbackTestGetterCalls'] === 0,
|
|
'unassigned battery neither queries manager nor reads devices');
|
|
$battery->managers = [900];
|
|
$battery->tick();
|
|
feedbackSetupCheck($GLOBALS['feedbackTestDeviceCalls'] === 0, 'empty manager config when learning off performs no device I/O');
|
|
$GLOBALS['feedbackTestGetterConfig'] = json_encode($config, JSON_THROW_ON_ERROR | JSON_PRESERVE_ZERO_FRACTION);
|
|
feedbackSetupRefuses(static fn() => $battery->GetV4BatterieRueckmeldungCache(), 'assignment alone never produces fake fresh sample');
|
|
$battery->tick();
|
|
feedbackSetupCheck($GLOBALS['feedbackTestDeviceCalls'] === 2, 'late manager assignment uses existing sampler without ApplyChanges');
|
|
$sample = json_decode($battery->GetV4BatterieRueckmeldungCache(), true);
|
|
feedbackSetupCheck($sample['batteryW'] === 250 && $sample['gridW'] === 1000, 'confirmed physical snapshot values');
|
|
feedbackSetupCheck($sample['deviceReadConfirmed'] && $sample['separatelyMeasured'] && !$sample['estimated'], 'real adapter response required');
|
|
feedbackSetupCheck($sample['sourceVariableOldestAt'] === time() - 120 && $sample['sourceOldestAt'] === time(),
|
|
'original unchanged variable timestamps retained separately from device confirmation');
|
|
feedbackSetupCheck(!$sample['canDispatch'] && !$sample['gatewayWatchdogVerified'], 'physical sampler never invents permission or watchdog');
|
|
$buffers = $battery->buffers;
|
|
$calls = $GLOBALS['feedbackTestDeviceCalls'];
|
|
$getters = $GLOBALS['feedbackTestGetterCalls'];
|
|
$GLOBALS['feedbackTestManagerRunning'] = true;
|
|
$battery->GetV4BatterieRueckmeldungCache();
|
|
feedbackSetupCheck($battery->buffers === $buffers && $GLOBALS['feedbackTestDeviceCalls'] === $calls,
|
|
'cache getter remains device I/O free and never renews timestamps');
|
|
feedbackSetupCheck($GLOBALS['feedbackTestGetterCalls'] === $getters,
|
|
'Manager -> battery cache never calls back into Manager or assignment lookup');
|
|
$GLOBALS['feedbackTestManagerRunning'] = false;
|
|
$context = json_decode($buffers['V4ConfirmedFeedbackContext'], true);
|
|
feedbackSetupCheck($context['autoMode'] && $context['propertyHash'] === hash('sha256', '{}')
|
|
&& json_decode($context['configuration'], true) === $config, 'sampler stores exact automatic probe context');
|
|
foreach ([
|
|
['autoMode', false], ['propertyHash', str_repeat('0', 64)], ['configuration', '{}'],
|
|
] as [$key, $value]) {
|
|
$brokenContext = $context;
|
|
$brokenContext[$key] = $value;
|
|
$battery->buffers['V4ConfirmedFeedbackContext'] = json_encode($brokenContext);
|
|
feedbackSetupRefuses(static fn() => $battery->GetV4BatterieRueckmeldungCache(), 'cached context drift refused: ' . $key);
|
|
$battery->buffers = $buffers;
|
|
}
|
|
foreach (['installationId', 'assetId', 'managerId', 'batteryInstanceId', 'configHash'] as $key) {
|
|
$brokenSample = $sample;
|
|
$brokenSample[$key] = is_int($brokenSample[$key]) ? $brokenSample[$key] + 1 : 'wrong';
|
|
$battery->buffers['V4ConfirmedFeedback'] = json_encode($brokenSample);
|
|
feedbackSetupRefuses(static fn() => $battery->GetV4BatterieRueckmeldungCache(), 'cached probe binding drift refused: ' . $key);
|
|
$battery->buffers = $buffers;
|
|
}
|
|
$battery->property = json_encode($config);
|
|
feedbackSetupRefuses(static fn() => $battery->GetV4BatterieRueckmeldungCache(), 'automatic to explicit property change invalidates old sample');
|
|
$battery->property = '{}';
|
|
$GLOBALS['feedbackTestNow'] += 3;
|
|
feedbackSetupRefuses(static fn() => $battery->GetV4BatterieRueckmeldungCache(), 'automatic cache expires after two seconds without contacting Manager');
|
|
$GLOBALS['feedbackTestNow'] -= 3;
|
|
$battery->buffers['V4ConfirmedFeedbackMono'] = (string) (hrtime(true) / 1e9 - 3);
|
|
feedbackSetupRefuses(static fn() => $battery->GetV4BatterieRueckmeldungCache(), 'automatic monotonic cache TTL remains two seconds');
|
|
$battery->buffers = $buffers;
|
|
$changedConfig = $config;
|
|
$changedConfig['maxAgeSeconds'] = 10;
|
|
$GLOBALS['feedbackTestGetterConfig'] = json_encode($changedConfig);
|
|
$getters = $GLOBALS['feedbackTestGetterCalls'];
|
|
feedbackSetupCheck(json_decode($battery->GetV4BatterieRueckmeldungCache(), true)['configHash'] === $sample['configHash']
|
|
&& $GLOBALS['feedbackTestGetterCalls'] === $getters,
|
|
'still-fresh probe stays bound to sampled configuration; Manager drift is observed only by sampler');
|
|
$battery->tick();
|
|
feedbackSetupCheck(json_decode($battery->GetV4BatterieRueckmeldungCache(), true)['configHash']
|
|
=== NetzfahrplanV4Rueckmeldung::fingerprint($changedConfig), 'next confirmed sample adopts changed automatic config');
|
|
$GLOBALS['feedbackTestGetterConfig'] = '';
|
|
$calls = $GLOBALS['feedbackTestDeviceCalls'];
|
|
$battery->tick();
|
|
feedbackSetupCheck($GLOBALS['feedbackTestDeviceCalls'] === $calls && $battery->buffers['V4ConfirmedFeedback'] === '',
|
|
'learning off clears automatic cache without device I/O');
|
|
$GLOBALS['feedbackTestGetterConfig'] = json_encode($config);
|
|
$battery->managers = [900, 902];
|
|
$getters = $GLOBALS['feedbackTestGetterCalls'];
|
|
$battery->tick();
|
|
feedbackSetupCheck($GLOBALS['feedbackTestDeviceCalls'] === $calls && $GLOBALS['feedbackTestGetterCalls'] === $getters,
|
|
'ambiguous manager assignment does not choose or read a device');
|
|
$battery->managers = [900];
|
|
$battery->property = " \n" . json_encode($config, JSON_THROW_ON_ERROR | JSON_PRESERVE_ZERO_FRACTION) . " \n";
|
|
$explicit = $battery->property;
|
|
$getters = $GLOBALS['feedbackTestGetterCalls'];
|
|
feedbackSetupCheck($battery->effective() === $explicit, 'explicit specialist configuration retained byte for byte');
|
|
$battery->tick();
|
|
feedbackSetupCheck($battery->property === $explicit && $GLOBALS['feedbackTestGetterCalls'] === $getters,
|
|
'explicit mapping never calls manager or rewrites properties');
|
|
feedbackSetupCheck($battery->buffers['V4ConfirmedFeedbackConfig'] === hash('sha256', $explicit),
|
|
'existing explicit configuration cache hashing unchanged');
|
|
$GLOBALS['feedbackTestManagerRunning'] = true;
|
|
$battery->GetV4BatterieRueckmeldungCache();
|
|
$GLOBALS['feedbackTestManagerRunning'] = false;
|
|
feedbackSetupCheck($GLOBALS['feedbackTestGetterCalls'] === $getters, 'explicit cache also performs no Manager callbacks');
|
|
$battery->property .= ' ';
|
|
feedbackSetupRefuses(static fn() => $battery->GetV4BatterieRueckmeldungCache(), 'explicit property byte drift invalidates probe');
|
|
$battery->property = '{}';
|
|
$badBinding = $config;
|
|
$badBinding['managerId'] = 902;
|
|
$GLOBALS['feedbackTestGetterConfig'] = json_encode($badBinding);
|
|
$calls = $GLOBALS['feedbackTestDeviceCalls'];
|
|
feedbackSetupRefuses(static fn() => $battery->tick(), 'manager returned wrong binding refused');
|
|
feedbackSetupCheck($GLOBALS['feedbackTestDeviceCalls'] === $calls, 'wrong automatic binding refused before device I/O');
|
|
$GLOBALS['feedbackTestGetterConfig'] = json_encode($config);
|
|
$GLOBALS['feedbackTestReadSucceeds'] = false;
|
|
feedbackSetupRefuses(static fn() => $battery->tick(), 'failed physical device response is never confirmed');
|
|
feedbackSetupCheck($battery->buffers['V4ConfirmedFeedback'] === '', 'failed response clears old confirmed sample');
|
|
$GLOBALS['feedbackTestReadSucceeds'] = true;
|
|
$GLOBALS['feedbackTestModules'] = [201 => 'unsupported', 203 => 'unsupported'];
|
|
$calls = $GLOBALS['feedbackTestDeviceCalls'];
|
|
feedbackSetupRefuses(static fn() => $battery->tick(), 'virtual or unsupported source parent refused');
|
|
feedbackSetupCheck($GLOBALS['feedbackTestDeviceCalls'] === $calls, 'unsupported parents not refreshed');
|
|
feedbackSetupCheck($battery->timers['NetzfahrplanV4RueckmeldungAktualisieren']['script'] === $timerScript,
|
|
'configuration and sampling never replace native timer callback');
|
|
|
|
echo 'TOTAL ' . $count . ' feedback setup checks passed' . PHP_EOL;
|