RegisterPropertyInteger('ReadWorkMs', 1500); $this->RegisterPropertyInteger('ReportWorkMs', 1500); $this->RegisterPropertyString('LifecycleMode', 'baseline'); $this->RegisterAttributeInteger('ReadRuns', 0); $this->RegisterAttributeInteger('ReportRuns', 0); $this->RegisterAttributeInteger('CallbackEnds', 0); $this->RegisterAttributeInteger('ScriptEnds', 0); $this->RegisterTimer('Meldezyklus', 0, "IPS_RequestAction(\$_IPS['TARGET'], 'Report', 0); ENLTMR_MarkScriptEnd(\$_IPS['TARGET']);"); $this->RegisterTimer('Rueckmeldesampler', 0, "IPS_RequestAction(\$_IPS['TARGET'], 'Read', 0); ENLTMR_MarkScriptEnd(\$_IPS['TARGET']);"); } public function ApplyChanges(): void { parent::ApplyChanges(); $lock = 'ENELIX.TimerProbe.Battery.' . $this->InstanceID; if (!IPS_SemaphoreEnter($lock, 1000)) throw new RuntimeException('Probe battery busy'); IPS_SemaphoreLeave($lock); $this->SetTimerInterval('Meldezyklus', 5000); $this->SetTimerInterval('Rueckmeldesampler', 1000); $this->SetStatus(102); } public function Destroy(): void { IPS_LogMessage('ENELIXTimerLifecycleProbe', 'Destroy entry ' . $this->InstanceID); // Isolated experiment only: determine whether unload can drain native timers. if (IPS_InstanceExists($this->InstanceID) && $this->ReadPropertyString('LifecycleMode') === 'drain') { $this->SetTimerInterval('Meldezyklus', 0); $this->SetTimerInterval('Rueckmeldesampler', 0); $deadline = microtime(true) + 2.5; do { $running = false; foreach (IPS_GetTimerList() as $id) { $timer = IPS_GetTimer($id); if ($timer['InstanceID'] === $this->InstanceID && $timer['Running']) $running = true; } if ($running) usleep(20000); } while ($running && microtime(true) < $deadline); IPS_LogMessage('ENELIXTimerLifecycleProbe', 'Destroy drain running=' . (int) $running); } parent::Destroy(); } public function Tick(string $ident): void { $this->RequestAction($ident, 0); } public function StopProbe(): void { $this->SetTimerInterval('Meldezyklus', 0); $this->SetTimerInterval('Rueckmeldesampler', 0); } public function RequestAction($Ident, $Value): void { if (!in_array($Ident, ['Read', 'Report'], true)) throw new InvalidArgumentException('Unknown probe action'); $lock = 'ENELIX.TimerProbe.' . ($Ident === 'Read' ? 'Feedback.' : 'Battery.') . $this->InstanceID; if (!IPS_SemaphoreEnter($lock, 0)) return; try { usleep(max(0, min(2000, $this->ReadPropertyInteger($Ident . 'WorkMs'))) * 1000); $this->WriteAttributeInteger($Ident . 'Runs', $this->ReadAttributeInteger($Ident . 'Runs') + 1); } finally { IPS_SemaphoreLeave($lock); } $this->WriteAttributeInteger('CallbackEnds', $this->ReadAttributeInteger('CallbackEnds') + 1); } public function MarkScriptEnd(): void { $this->WriteAttributeInteger('ScriptEnds', $this->ReadAttributeInteger('ScriptEnds') + 1); } public function ReadProbeState(): string { return json_encode(['readRuns' => $this->ReadAttributeInteger('ReadRuns'), 'callbackEnds' => $this->ReadAttributeInteger('CallbackEnds'), 'scriptEnds' => $this->ReadAttributeInteger('ScriptEnds'), 'reportRuns' => $this->ReadAttributeInteger('ReportRuns')], JSON_THROW_ON_ERROR); } }