settings->for('dryer'); return filled($settings['url'] ?? null) && filled($settings['api_key'] ?? null); } public function dashboardUrl(): string { $url = rtrim((string) ($this->settings->for('dryer')['url'] ?? ''), '/'); return preg_replace('#/api/v1$#', '', $url) ?? $url; } private function apiKey(): string { return (string) ($this->settings->for('dryer')['api_key'] ?? ''); } private function url(string $path): string { $base = $this->settings->for('dryer')['url'] ?? null; if (! $this->configured()) { throw new RuntimeException('Dryer URL and API key must be configured.'); } $base = rtrim($base, '/'); if (! str_ends_with($base, '/api/v1')) { $base .= '/api/v1'; } return $base.'/'.$path; } public function status(): array { return Http::withHeaders(['X-API-Key' => $this->apiKey()])->acceptJson() ->connectTimeout(3)->timeout(7)->get($this->url('status'))->throw()->json() ?? []; } public function history(): array { return Http::withHeaders(['X-API-Key' => $this->apiKey()])->acceptJson() ->connectTimeout(3)->timeout(7)->get($this->url('history'), ['limit' => 60])->throw()->json() ?? []; } public function start(string $profile, int $targetTemperature, int $dryingTime): array { return $this->command('cycles/start', [ 'profile' => $profile, 'targetC' => $targetTemperature, 'durationMinutes' => $dryingTime, ]); } public function stop(): array { return $this->command('cycles/stop'); } public function acknowledge(): array { return $this->command('faults/acknowledge'); } private function command(string $path, array $payload = []): array { return Http::withHeaders(['X-API-Key' => $this->apiKey()])->acceptJson() ->connectTimeout(3)->timeout(7)->post($this->url($path), [ 'requestId' => 'dashboard-'.bin2hex(random_bytes(12)), ...$payload, ])->throw()->json() ?? []; } }