feat(manager): integrate forecast controls and merge V4 development
Tests / test (push) Failing after 49s
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.
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
<?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
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user