Tests / test (push) Failing after 49s
Merge the V4 development history with current manager, SDL and setpoint fixes. Retain guarded trial behavior and consolidate controls in Prognose / Forecast. Exclude the unverified accounting-evidence change; no runtime deployment or new dispatch permission. Validated: 357 PHPUnit tests / 1758 assertions, 150 PHP syntax checks, 310 backend tests, 16 portal tests and isolated UI/receiver checks. develop and beta publication explicitly approved by Daniel Haefliger.
116 lines
4.5 KiB
PHP
116 lines
4.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Belevo\EnelixEMS;
|
|
|
|
use InvalidArgumentException;
|
|
|
|
require_once __DIR__.'/NetzfahrplanV4Rueckmeldung.php';
|
|
|
|
/**
|
|
* Binds values to explicitly successful device reads.
|
|
*
|
|
* Transport calls, object metadata and the clock are injected so the complete
|
|
* coordination can be tested without IP-Symcon or real devices.
|
|
*/
|
|
final class NetzfahrplanV4Geraeteabruf
|
|
{
|
|
public const MBUS_DEVICE = '{B53BE2E5-892D-4537-94AC-EAC68A469188}';
|
|
public const MODBUS_DEVICE = '{90BF9234-B44C-7EB3-280B-FBD6075ADDAA}';
|
|
public const MODBUS_ADDRESS = '{CB197E50-273D-4535-8C91-BB35273E3CA5}';
|
|
|
|
public static function snapshot(
|
|
array $config,
|
|
callable $moduleId,
|
|
callable $refresh,
|
|
callable $reader,
|
|
callable $clock,
|
|
?callable $pause = null
|
|
): array {
|
|
$config = NetzfahrplanV4Rueckmeldung::configuration($config);
|
|
$groups = [];
|
|
$sourcesById = [];
|
|
foreach ($config['sources'] as $source) {
|
|
$sourcesById[$source['variableId']] = $source;
|
|
if (in_array($source['role'], ['grid', 'physical'], true)) {
|
|
$groups[$source['parentId']][] = $source;
|
|
}
|
|
}
|
|
|
|
$snapshot = [];
|
|
foreach ($groups as $parentId => $sources) {
|
|
$module = $moduleId($parentId);
|
|
if (!in_array($module, [self::MBUS_DEVICE, self::MODBUS_DEVICE, self::MODBUS_ADDRESS], true)) {
|
|
throw new InvalidArgumentException('feedback_refresh_module_unsupported:' . $sources[0]['key']);
|
|
}
|
|
$confirmed = false;
|
|
for ($attempt = 1; $attempt <= 4; ++$attempt) {
|
|
if ($refresh($parentId, $module) === true) {
|
|
$confirmed = true;
|
|
break;
|
|
}
|
|
if ($attempt < 4 && $pause !== null) {
|
|
$pause(200000);
|
|
}
|
|
}
|
|
if (!$confirmed) {
|
|
throw new InvalidArgumentException('feedback_device_read_failed:' . $sources[0]['key']);
|
|
}
|
|
$confirmedAt = $clock();
|
|
if (!is_int($confirmedAt) || $confirmedAt < 1) {
|
|
throw new InvalidArgumentException('feedback_confirmation_time_invalid');
|
|
}
|
|
foreach ($sources as $source) {
|
|
$record = $reader($source['variableId']);
|
|
if (!is_array($record)) {
|
|
throw new InvalidArgumentException('feedback_reader_invalid:' . $source['key']);
|
|
}
|
|
$record['confirmedAt'] = $confirmedAt;
|
|
$record['confirmedModuleId'] = $module;
|
|
$snapshot[$source['variableId']] = $record;
|
|
}
|
|
}
|
|
|
|
foreach ($config['sources'] as $source) {
|
|
if (!isset($snapshot[$source['variableId']])) {
|
|
$record = $reader($source['variableId']);
|
|
if (!is_array($record)) {
|
|
throw new InvalidArgumentException('feedback_reader_invalid:' . $source['key']);
|
|
}
|
|
$snapshot[$source['variableId']] = $record;
|
|
}
|
|
}
|
|
|
|
foreach ($snapshot as $variableId => $record) {
|
|
$again = $reader($variableId);
|
|
// The variable may legitimately receive a newer process value immediately after
|
|
// the explicit device read. Keep the confirmed snapshot, but require the source
|
|
// identity to remain stable so a reconfigured/reparented variable is never accepted.
|
|
$identityKeys = ['parentID', 'ident'];
|
|
if (!is_array($again) || array_intersect_key($again, array_flip($identityKeys))
|
|
!== array_intersect_key($record, array_flip($identityKeys))) {
|
|
throw new InvalidArgumentException(
|
|
'feedback_source_changed_after_confirmed_read:' . $sourcesById[$variableId]['key']
|
|
);
|
|
}
|
|
}
|
|
|
|
$checkedAt = $clock();
|
|
if (!is_int($checkedAt) || $checkedAt < 1) {
|
|
throw new InvalidArgumentException('feedback_confirmation_time_invalid');
|
|
}
|
|
foreach ($snapshot as $record) {
|
|
if (isset($record['confirmedAt']) && $record['confirmedAt'] > $checkedAt) {
|
|
throw new InvalidArgumentException('feedback_confirmation_time_invalid');
|
|
}
|
|
}
|
|
|
|
return NetzfahrplanV4Rueckmeldung::read(
|
|
$config,
|
|
static fn(int $variableId): array => $snapshot[$variableId],
|
|
$checkedAt
|
|
);
|
|
}
|
|
}
|