feat(control): connect physical feedback to bounded V4 command and fallback paths

This commit is contained in:
ENELIX Agent
2026-10-03 11:40:54 +00:00
parent 8eb9688942
commit 9878197aea
24 changed files with 891 additions and 7 deletions
@@ -8,6 +8,7 @@ use RuntimeException;
use Throwable;
require_once __DIR__ . '/NetzfahrplanV4Planpruefung.php';
require_once __DIR__ . '/NetzfahrplanV4Rueckmeldung.php';
/** Independent preview only: no SendData, consumer RequestAction or legacy plan writes. */
trait ManagerNetzfahrplanV4EmpfangTrait
@@ -158,6 +159,31 @@ trait ManagerNetzfahrplanV4EmpfangTrait
$topology = json_decode($this->ReadPropertyString('AnlagenBatterien'), true, 64, JSON_THROW_ON_ERROR);
$actual = [];$now = time();
foreach ($topology as $asset) {
$controllers=[];
foreach ($this->aktiveVerbraucherIDs() as $controller) {
if (IPS_InstanceExists($controller)
&& IPS_GetInstance($controller)['ModuleInfo']['ModuleID']==='{437FB683-517F-4FEC-8CCB-FE6B0A62B69E}'
&& IPS_GetProperty($controller,'IstleistungVariableID')===($asset['LeistungVariableID']??null)
&& IPS_GetProperty($controller,'LadezustandVariableID')===($asset['SOCVariableID']??null)) $controllers[]=$controller;
}
if (count($controllers)>1) throw new RuntimeException('feedback_controller_ambiguous');
if (count($controllers)===1 && function_exists('ENELIX_GetV4BatterieRueckmeldung')) {
$controller=$controllers[0];$configured=IPS_GetProperty($controller,'NetzfahrplanV4RueckmeldungKonfiguration');
if (is_string($configured) && !in_array(trim($configured),['','{}'],true)) {
if (count($topology)!==1) throw new RuntimeException('feedback_multi_asset_adapter_required');
$f=json_decode(ENELIX_GetV4BatterieRueckmeldung($controller),true,32,JSON_THROW_ON_ERROR);
if (($f['installationId']??null)!==$this->ReadAttributeString('LizenzInstallationID')
|| ($f['assetId']??null)!==$asset['ID'] || ($f['managerId']??null)!==$this->InstanceID
|| ($f['batteryInstanceId']??null)!==$controller || !is_numeric($f['batteryW']??null)
|| !is_numeric($f['gridW']??null) || !is_int($f['checkedAt']??null) || $now-$f['checkedAt']>2 || $f['checkedAt']>$now) {
throw new RuntimeException('feedback_invalid_no_legacy_fallback');
}
$actual[$asset['ID']]=$f['batteryW'];$d['operation']['gridW']=$f['gridW'];
$d['operation']['localFeedback'][$asset['ID']]=$f;
continue;
}
}
// Legacy value remains display-only until an explicit physical feedback mapping exists.
$id = (int) ($asset['LeistungVariableID'] ?? 0);
if (!IPS_VariableExists($id)) throw new RuntimeException('Batterie-Istleistung fehlt.');
$before = IPS_GetVariable($id);$v = GetValue($id);$after = IPS_GetVariable($id);
@@ -189,6 +215,8 @@ trait ManagerNetzfahrplanV4EmpfangTrait
$checked = NetzfahrplanV4Planpruefung::pruefen($envelope['response'],
$this->ReadAttributeString('LizenzInstallationID'), $operation, time(), $envelope['receivedAt']);
$out = NetzfahrplanV4Planpruefung::vorschau($checked, $operation, $actual, time());
$out['localFeedback'] = $operation['localFeedback'] ?? [];
$out['feedbackMeaning'] = isset($operation['localFeedback']) ? 'fresh_physical_or_explicit_partition_model' : 'legacy_display_only_not_for_trial';
$out['reception'] = json_decode($this->ReadAttributeString('NetzfahrplanV4EmpfangStatus'), true);
} catch (Throwable $error) {
$out = ['status' => 'unavailable', 'reason' => substr($error->getMessage(), 0, 300)];