$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 ); } }