diff --git a/server/SHServ/Controllers/DevTestController.php b/server/SHServ/Controllers/DevTestController.php new file mode 100644 index 0000000..6d5b683 --- /dev/null +++ b/server/SHServ/Controllers/DevTestController.php @@ -0,0 +1,21 @@ + scan_range("192.168.68.2", "192.168.68.254"); + + echo "
"; + var_dump($devices); + echo "
"; + var_dump($device_scanner -> filter_by_status($devices, "setup")); + } + + +} \ No newline at end of file diff --git a/server/SHServ/Models/Devices.php b/server/SHServ/Models/Devices.php new file mode 100644 index 0000000..59ee3be --- /dev/null +++ b/server/SHServ/Models/Devices.php @@ -0,0 +1,9 @@ + router -> uri("/test/device-scanner", function($args){ - $device_scanner = new Tools\DeviceScanner(); - $devices = $device_scanner -> scan_range("192.168.68.2", "192.168.68.254"); - var_dump($device_scanner -> filter_by_status($devices, "normal")); - }); + if(FCONF["devmode"]) { + $this -> router -> uri("/dev/test/device-scanner.html", "{$this -> cn}\\DevTestController@device_scanner"); + $this -> router -> uri("/dev/test/device-api-base/about.html", "{$this -> cn}\\DevTestController@device_api_base__about"); + $this -> router -> uri("/dev/test/device-api-base/set_token.html", "{$this -> cn}\\DevTestController@device_api_base__set_token"); + $this -> router -> uri("/dev/test/device-api-base/reboot.html", "{$this -> cn}\\DevTestController@device_api_base__reboot"); + $this -> router -> uri("/dev/test/device-api-base/action.html", "{$this -> cn}\\DevTestController@device_api_base__post_action"); + $this -> router -> uri("/dev/test/device-api-base/set_device_name.html", "{$this -> cn}\\DevTestController@device_api_base__set_device_name"); + $this -> router -> uri("/dev/test/device-api-base/reset.html", "{$this -> cn}\\DevTestController@device_api_base__reset"); + } } protected function get_routes() { diff --git a/server/SHServ/Tools/DeviceAPI/Base.php b/server/SHServ/Tools/DeviceAPI/Base.php new file mode 100644 index 0000000..33d4637 --- /dev/null +++ b/server/SHServ/Tools/DeviceAPI/Base.php @@ -0,0 +1,253 @@ +ip_address = $ip_address; + $this->base_url = rtrim('http://' . $ip_address, '/'); + $this->token = $token; + } + + /** + * Локально обновить токен (без запроса к устройству). + */ + public function set_local_token(string $token): void { + $this->token = $token; + } + + /** + * Получить текущий сохранённый токен. + */ + public function get_local_token(): ?string { + return $this->token; + } + + /** + * GET /about (доступен всегда, без токена). + */ + public function get_about(): array { + return $this->request('GET', '/about'); + } + + /** + * GET /status + * + * В режиме normal требует токен, в setup может не требоваться. + * Токен отправляем, если он есть, но жёстко не требуем. + */ + public function get_status(): array { + return $this->request('GET', '/status'); + } + + /** + * POST /action + * + * В normal-режиме требует токен. + * + * @param string $action + * @param array $params + */ + public function post_action(string $action, array $params = []): array { + $body = [ + 'action' => $action, + 'params' => (object)$params, // чтобы пустой массив ушёл как {} + ]; + + return $this->request('POST', '/action', $body, true); + } + + /** + * POST /set_token + * + * В setup-режиме не требует токен, в normal — требует. + * Мы отправляем токен, если он есть, но не делаем жёсткую проверку. + * При успешном ответе можно локально обновить токен клиента. + */ + public function remote_set_token(string $token): array { + $response = $this->request('POST', '/set_token', [ + 'token' => $token, + ]); + + if (($response['http_code'] ?? 0) === 200 && ($response['data']['status'] ?? null) === 'ok') { + $this->token = $token; + } + + return $response; + } + + /** + * POST /reboot + * + * Требует токен в normal-режиме. + */ + public function reboot(): array { + return $this->request('POST', '/reboot', (object)[], true); + } + + /** + * POST /reset + * + * Требует токен в normal-режиме. + */ + public function reset(): array { + return $this->request('POST', '/reset', (object)[], true); + } + + /** + * POST /set_device_name + * + * Требует токен в normal-режиме. + */ + public function set_device_name(string $device_name): array { + return $this->request('POST', '/set_device_name', [ + 'device_name' => $device_name, + ], true); + } + + /** + * Базовый HTTP-запрос к устройству. + * + * @param string $method GET|POST + * @param string $path Например, '/about' + * @param array|object|null $body Тело запроса для POST + * @param bool $require_token Обязателен ли токен для этого вызова + * + * @return array{ + * http_code:int, + * headers:array, + * raw:string|null, + * data:array |null, + * error:string|null + * } + */ + private function request(string $method, string $path, $body = null, bool $require_token = false): array { + if ($require_token && $this->token === null) { + throw new \LogicException('Token is required for this endpoint but not set in DeviceApiClient'); + } + + $url = $this->base_url . $path; + + $ch = curl_init(); + if ($ch === false) { + throw new \RuntimeException('Failed to initialize cURL'); + } + + $headers = [ + 'Accept: application/json', + ]; + + if ($this->token !== null) { + $headers[] = 'Authorization: Bearer ' . $this->token; + } + + if (strtoupper($method) === 'POST') { + curl_setopt($ch, CURLOPT_POST, true); + + if ($body !== null) { + $json_body = json_encode($body, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); + if ($json_body === false) { + curl_close($ch); + throw new \RuntimeException('Failed to encode JSON body: ' . json_last_error_msg()); + } + curl_setopt($ch, CURLOPT_POSTFIELDS, $json_body); + $headers[] = 'Content-Type: application/json'; + } + } else { + curl_setopt($ch, CURLOPT_HTTPGET, true); + } + + curl_setopt_array($ch, [ + CURLOPT_URL => $url, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HEADER => true, + CURLOPT_TIMEOUT => 5, + CURLOPT_HTTPHEADER => $headers, + ]); + + $raw_response = curl_exec($ch); + + if ($raw_response === false) { + $error_message = curl_error($ch); + curl_close($ch); + + return [ + 'http_code' => 0, + 'headers' => [], + 'raw' => null, + 'data' => null, + 'error' => $error_message, + ]; + } + + $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); + $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + + curl_close($ch); + + $raw_headers = substr($raw_response, 0, $header_size); + $raw_body = substr($raw_response, $header_size); + + $parsed_headers = $this->parse_headers($raw_headers); + + $decoded = null; + $error = null; + + if ($raw_body !== '') { + $decoded = json_decode($raw_body, true); + if (!is_array($decoded)) { + $error = 'Invalid JSON response from device'; + $decoded = null; + } + } + + return [ + 'http_code' => (int)$http_code, + 'headers' => $parsed_headers, + 'raw' => $raw_body, + 'data' => $decoded, + 'error' => $error, + ]; + } + + /** + * Простейший парсер HTTP-заголовков. + * + * @param string $raw_headers + * + * @return array + */ + private function parse_headers(string $raw_headers): array + { + $headers = []; + + $lines = preg_split("/\r\n|\n|\r/", trim($raw_headers)); + if (!is_array($lines)) { + return $headers; + } + + foreach ($lines as $line) { + if (strpos($line, ':') === false) { + continue; + } + + [$name, $value] = explode(':', $line, 2); + $name = trim($name); + $value = trim($value); + + if ($name !== '') { + $headers[$name] = $value; + } + } + + return $headers; + } +} diff --git a/server/SHServ/Tools/DeviceAPI/TestActions/Base_test.php b/server/SHServ/Tools/DeviceAPI/TestActions/Base_test.php new file mode 100644 index 0000000..f0b3191 --- /dev/null +++ b/server/SHServ/Tools/DeviceAPI/TestActions/Base_test.php @@ -0,0 +1,56 @@ + get_about(); + + echo " "; + var_dump($about); + } + + public function device_api_base__set_token() { + $device_base_api = new Base("192.168.68.7"); + $result = $device_base_api -> remote_set_token("mytoken123"); + + echo ""; + var_dump($result); + } + + public function device_api_base__reboot() { + $device_base_api = new Base("192.168.68.7", "mytoken123"); + $result = $device_base_api -> reboot(); + + echo ""; + var_dump($result); + } + + public function device_api_base__post_action() { + $device_base_api = new Base("192.168.68.7", "mytoken123"); + $result = $device_base_api -> post_action("toggle_channel", ["channel" => 0]); + + echo ""; + var_dump($result); + } + + public function device_api_base__set_device_name() { + $device_base_api = new Base("192.168.68.7", "mytoken123"); + $result = $device_base_api -> set_device_name("Dev Sample"); + + echo ""; + var_dump($result); + } + + public function device_api_base__reset() { + $device_base_api = new Base("192.168.68.7", "mytoken123"); + $result = $device_base_api -> reset(); + + echo ""; + var_dump($result); + } +} \ No newline at end of file