'FORBIDDEN_IP', 'request_id' => $requestId, 'client_ip' => $clientIp ], 403); } $params = ($requestMethod === 'POST') ? $_POST : $_GET; $token = $params['token'] ?? ''; if (!hash_equals(AUTH_TOKEN, $token)) { json_exit([ 'error' => 'FORBIDDEN_TOKEN', 'request_id' => $requestId ], 403); } $pdo = db(); if (($params['log'] ?? '') == '1') { $limit = isset($params['limit']) ? (int)$params['limit'] : 50; $logs = db_logs($pdo, $limit); json_exit([ 'request_id' => $requestId, 'limit' => $limit, 'count' => count($logs), 'logs' => $logs ]); } $cmdRequested = $params['cmd'] ?? 'se'; $cmd = normalize_cmd($cmdRequested); if ($cmd === 'se') { $latest = db_latest($pdo); if (!$latest) { json_exit([ 'request_id' => $requestId, 'error' => 'no_latest_data' ], 500); } $ageSeconds = max(0, time() - strtotime((string)$latest['ts'])); $latestUsage = db_latest_usage($pdo); $staleReason = null; if ($ageSeconds > 30 && $latestUsage && (int)$latestUsage['tcp_ok'] === 0) { $staleReason = (string)($latestUsage['tcp_error'] ?? 'tcp_failed'); } json_exit([ 'request_id' => $requestId, 'cmd_requested' => 'se', 'cmd' => $latest['cmd'], 'ts' => $latest['ts'], 'meta' => [ 'age_seconds' => $ageSeconds, 'stale' => $ageSeconds > 30, 'stale_reason' => $staleReason, 'latest_tcp' => $latestUsage, ], 'raw_full' => $latest['raw_full'], 'raw_trim' => $latest['raw_trim'], 'data' => [ 'boundary' => (int)$latest['boundary'], 'engine' => (int)$latest['engine'], 'driving' => (int)$latest['driving'], 'battery_voltage' => (float)$latest['battery_voltage'], 'door_fl' => (int)$latest['door_fl'], 'door_fr' => (int)$latest['door_fr'], 'door_rl' => (int)$latest['door_rl'], 'door_rr' => (int)$latest['door_rr'], 'door_trunk' => (int)$latest['door_trunk'], 'remote_start_preparing' => (int)$latest['remote_start_preparing'], 'remote_start_running' => (int)$latest['remote_start_running'], 'remote_start_remaining' => $latest['remote_start_remaining'], 'hazard' => (int)$latest['hazard'], ] ]); } if (!in_array($cmd, CONTROL_CMD, true)) { json_exit([ 'error' => 'INVALID_CMD', 'request_id' => $requestId, 'cmd' => $cmd ], 400); } if ($requestMethod !== 'POST') { json_exit([ 'error' => 'METHOD_NOT_ALLOWED_USE_POST', 'request_id' => $requestId, 'cmd' => $cmd ], 405); } $tcpMs = 0; $connectMs = 0; $readMs = 0; $tcpError = ''; $trimError = ''; $sentBytes = 0; $receivedBytes = 0; $rawFull = tcp_request( $cmd, $tcpMs, $connectMs, $readMs, $tcpError, 'api_control', $requestId, $sentBytes, $receivedBytes ); $execMs = (int)round((microtime(true) - $tsStart) * 1000); if ($rawFull === '') { json_exit([ 'request_id' => $requestId, 'cmd_requested' => $cmdRequested, 'cmd' => $cmd, 'ts' => date('Y-m-d H:i:s'), 'exec_ms' => $execMs, 'client_ip' => $clientIp, 'ua' => $userAgent, 'tcp_ok' => 0, 'tcp_ms' => $tcpMs, 'connect_ms' => $connectMs, 'read_ms' => $readMs, 'sent_bytes' => $sentBytes, 'received_bytes' => $receivedBytes, 'total_bytes' => $sentBytes + $receivedBytes, 'tcp_error' => ($tcpError !== '' ? $tcpError : 'timeout_or_empty'), 'error' => 'CONTROL_CMD_SEND_FAILED' ], 502); } $rawTrim = make_trim($rawFull, $trimError); $data = ($rawTrim !== '') ? parse_trim($rawTrim) : null; if ($rawTrim !== '' && $data !== null && is_valid_status_data($data)) { db_insert_status( $pdo, $cmd, $rawFull, $rawTrim, $data ); } json_exit([ 'request_id' => $requestId, 'cmd_requested' => $cmdRequested, 'cmd' => $cmd, 'ts' => date('Y-m-d H:i:s'), 'exec_ms' => $execMs, 'client_ip' => $clientIp, 'ua' => $userAgent, 'tcp_ok' => 1, 'tcp_ms' => $tcpMs, 'connect_ms' => $connectMs, 'read_ms' => $readMs, 'sent_bytes' => $sentBytes, 'received_bytes' => $receivedBytes, 'total_bytes' => $sentBytes + $receivedBytes, 'tcp_error' => $tcpError, 'accepted' => true, 'ack_only' => ($rawTrim === ''), 'raw_full' => $rawFull, 'raw_trim' => $rawTrim, 'data' => $data ]);