'Access forbidden: Apache environment is not allowed']); exit; } // POST 전용 검사 if ($_SERVER['REQUEST_METHOD'] !== 'POST') { http_response_code(405); // Method Not Allowed header('Content-Type: application/json'); echo json_encode(['error' => 'Method Not Allowed']); exit; } // JSON 요청 검사 $content_type = $_SERVER['CONTENT_TYPE'] ?? ''; if (stripos($content_type, 'application/json') === false) { http_response_code(415); // Unsupported Media Type header('Content-Type: application/json'); echo json_encode(['error' => 'Unsupported Content-Type. application/json required']); exit; } function get_authorization_header() { if (!empty($_SERVER['HTTP_AUTHORIZATION'])) { return trim($_SERVER['HTTP_AUTHORIZATION']); } elseif (function_exists('apache_request_headers')) { foreach (apache_request_headers() as $key => $value) { if (strcasecmp($key, 'Authorization') === 0) { return trim($value); } } } return null; } $auth_header = get_authorization_header(); if (!$auth_header || !preg_match('/^Bearer\s+(\S+)$/', $auth_header, $matches)) { http_response_code(401); header('Content-Type: application/json'); echo json_encode(['error' => 'Unauthorized: Missing or invalid Authorization header']); exit; } $allowed_password = 'XBv9z1IGqPUdtVf7DABFA16H0E154LJS'; $password = $matches[1]; if ($password !== $allowed_password) { http_response_code(401); header('Content-Type: application/json'); echo json_encode(['error' => 'Unauthorized: Invalid token']); exit; } header('Content-Type: application/json; charset=utf-8'); header('Cache-Control: no-store, no-cache, must-revalidate'); header('Expires: 0'); $baseUrl = "https://ha.seoul.chaegeon.com/api/states"; $headers = [ "Authorization: Bearer REDACTED_HA_TOKEN", "Content-Type: application/json" ]; // 엔드포인트 URL $deviceUrl = "$baseUrl/device_tracker.seocaegeonyi_z_fold7"; $geoUrl = "$baseUrl/sensor.seocaegeonyi_z_fold7_geocoded_location"; $batteryStateUrl = "$baseUrl/sensor.seocaegeonyi_z_fold7_battery_state"; $batteryLevelUrl = "$baseUrl/sensor.seocaegeonyi_z_fold7_battery_level"; $watchBatteryStateUrl = "$baseUrl/sensor.seocaegeonyi_z_fold7_watch_battery_state"; $watchBatteryLevelUrl = "$baseUrl/sensor.seocaegeonyi_z_fold7_watch_battery_level"; $activityUrl = "$baseUrl/sensor.seocaegeonyi_z_fold7_detected_activity"; $connectionUrl = "$baseUrl/sensor.seocaegeonyi_z_fold7_network_type"; $fascendedUrl = "$baseUrl/sensor.seocaegeonyi_z_fold7_daily_floors"; $fdescendedUrl = "$baseUrl/sensor.seocaegeonyi_z_fold7_daily_floors"; $stepsUrl = "$baseUrl/sensor.seocaegeonyi_z_fold7_daily_steps"; $distanceUrl = "$baseUrl/sensor.seocaegeonyi_z_fold7_daily_distance"; $triggerUrl = "$baseUrl/sensor.seocaegeonyi_z_fold7_last_update_trigger"; // GET 파라미터 읽기 $startParam = $_GET['startTime'] ?? null; $endParam = $_GET['endTime'] ?? null; // Home Assistant에서 인식 가능한 UTC 포맷 $haFormat = 'Y-m-d\TH:i:s\Z'; if ($startParam && $endParam) { // 전달받은 값을 DateTime으로 변환 후 UTC 포맷으로 변환 $startTime = (new DateTime($startParam))->setTimezone(new DateTimeZone('UTC'))->format($haFormat); $endTime = (new DateTime($endParam))->setTimezone(new DateTimeZone('UTC'))->format($haFormat); } else { // 기본값: 최근 24시간 $startTime = (new DateTime('-24 hours'))->setTimezone(new DateTimeZone('UTC'))->format($haFormat); $endTime = (new DateTime())->setTimezone(new DateTimeZone('UTC'))->format($haFormat); } // API URL 생성 $historyUrl = "https://ha.seoul.chaegeon.com/api/history/period/{$startTime}?filter_entity_id=device_tracker.seocaegeonyi_z_fold7&end_time={$endTime}"; // 공통 요청 함수 function getData($url, $headers) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); $response = curl_exec($ch); curl_close($ch); return json_decode($response, true); } // 데이터 불러오기 $deviceData = getData($deviceUrl, $headers); $geoData = getData($geoUrl, $headers); $batteryStateData = getData($batteryStateUrl, $headers); $batteryLevelData = getData($batteryLevelUrl, $headers); $watchBatteryStateData = getData($watchBatteryStateUrl, $headers); $watchBatteryLevelData = getData($watchBatteryLevelUrl, $headers); $activityData = getData($activityUrl, $headers); $connectionData = getData($connectionUrl, $headers); $ascendedData = getData($fascendedUrl, $headers); $descendedData = getData($fdescendedUrl, $headers); $stepsData = getData($stepsUrl, $headers); $distanceData = getData($distanceUrl, $headers); $historyData = getData($historyUrl, $headers); $triggerData = getData($triggerUrl, $headers); // 경로 기록 $path = []; if (isset($historyData[0])) { foreach ($historyData[0] as $item) { $lat = $item['attributes']['latitude'] ?? null; $lng = $item['attributes']['longitude'] ?? null; $time = $item['last_changed'] ?? null; if ($lat && $lng) { $path[] = ['lat' => $lat, 'lng' => $lng, 'time' => $time]; } } } $lang = $_GET['lang'] ?? 'en'; $activityMap = [ 'ko' => [ 'Stationary' => '가만히 있음', 'Walking' => '걷는 중', 'Running' => '달리는 중', 'Automotive' => '차량 이동 중', 'Cycling' => '자전거 이동 중', 'Unknown' => '-', ], 'en' => [ 'Stationary' => 'Stationary', 'Walking' => 'Walking', 'Running' => 'Running', 'Automotive' => 'Automotive', 'Cycling' => 'Cycling', 'Unknown' => '-', ] ]; $aaccuracyMap = [ 'ko' => [ 'Low' => '낮음', 'Medium' => '중간', 'High' => '높음', 'Unknown' => '-', ], 'en' => [ 'Low' => 'Low', 'Medium' => 'Medium', 'High' => 'High', 'Unknown' => '-', ] ]; $connectionMap = [ 'ko' => [ 'Wi-Fi' => '와이파이', 'Cellular' => '셀룰러', 'none' => '-', 'unknown' => '-', ], 'en' => [ 'Wi-Fi' => 'Wi-Fi', 'Cellular' => 'Cellular', 'none' => '-', 'unknown' => '-', ] ]; $batteryStateMap = [ 'ko' => [ 'Charging' => '충전 중', 'Not Charging' => '충전중이 아님', 'Full' => '가득참', 'Unknown' => '-', ], 'en' => [ 'Charging' => 'Charging', 'Not Charging' => 'Not Charging', 'Full' => 'Full', 'Unknown' => '-', ] ]; $triggerMap = [ 'ko' => [ "Launch" => "앱 실행", "Background Fetch" => "백그라운드 갱신", "Push Notification" => "푸시", "Periodic" => "주기적", "Significant Location Change" => "중요한 위치 변화", "Manual" => "수동", "Signaled" => "시그널 됨", "Siri" => "Siri 실행", "Watch Context" => "워치 상태 전송", "Geographic Region Entered" => "지정 지역에 들어옴", "Geographic Region Exited" => "지정 지역을 벗어남", ], 'en' => [ "Launch" => "Launch", "Background Fetch" => "Background Fetch", "Push Notification" => "Push Notification", "Periodic" => "Periodic", "Significant Location Change" => "Significant Location Change", "Manual" => "Manual", "Signaled" => "Signaled", "Siri" => "Siri", "Watch Context" => "Watch Context", "Geographic Region Entered" => "Geographic Region Entered", "Geographic Region Exited" => "Geographic Region Exited", ] ]; $activityState = $activityData['state'] ?? null; $aaccuracyState = $activityData['attributes']['Confidence'] ?? null; $connectionState = $connectionData['state'] ?? null; $batteryState = $batteryStateData['state'] ?? null; $watchBatteryState = $watchBatteryStateData['state'] ?? null; $address = null; if (($deviceData['state'] ?? null) !== 'not_home') { $address = $deviceData['state']; } else { // Locality 값 확인 $locality = $geoData['attributes']['Locality'] ?? null; // Locality가 없거나 N/A면 Sub Administrative Area로 대체 if (empty($locality) || $locality === 'N/A') { $locality = $geoData['attributes']['Sub Administrative Area'] ?? ''; } // Sub Administrative Area 도 없을 경우 Name만 사용 $address = trim($locality . ' ' . ($geoData['attributes']['Name'] ?? '')); } // 해시 요청만 들어온 경우 if (isset($_GET['hashonly']) && $_GET['hashonly'] == '1') { $raw = json_encode([ 'latitude' => $deviceData['attributes']['latitude'] ?? null, 'longitude' => $deviceData['attributes']['longitude'] ?? null, 'accuracy' => $deviceData['attributes']['gps_accuracy'] ?? null, 'updated' => $deviceData['last_changed'] ?? null, 'trigger' => $triggerMap[$lang][$triggerData['state']] ?? $triggerData['state'] ?? null, 'address' => $address, 'state' => $batteryStateMap[$lang][$batteryState] ?? $batteryState ?? null, 'level' => $batteryLevelData['state'] ?? null, 'wstate' => $batteryStateMap[$lang][$watchBatteryState] ?? $watchBatteryState ?? null, 'wlevel' => $watchBatteryLevelData['state'] ?? null, 'ascended' => $ascendedData['state'] ?? null, 'descended' => $descendedData['state'] ?? null, 'steps' => $stepsData['state'] ?? null, 'distance' => $distanceData['state'] ?? null, 'activity' => $activityMap[$lang][$activityState] ?? $activityState ?? null, 'aaccuracy' => $aaccuracyMap[$lang][$aaccuracyState] ?? $aaccuracyState ?? null, 'connection' => $connectionMap[$lang][$connectionState] ?? $connectionState ?? null, ]); echo json_encode(['hash' => hash('sha256', $raw)]); exit; } echo json_encode([ 'latitude' => $deviceData['attributes']['latitude'] ?? null, 'longitude' => $deviceData['attributes']['longitude'] ?? null, 'accuracy' => $deviceData['attributes']['gps_accuracy'] ?? null, 'updated' => $deviceData['last_changed'] ?? null, 'trigger' => $triggerMap[$lang][$triggerData['state']] ?? $triggerData['state'] ?? null, 'address' => $address, 'state' => $batteryStateMap[$lang][$batteryState] ?? $batteryState ?? null, 'level' => $batteryLevelData['state'] ?? null, 'wstate' => $batteryStateMap[$lang][$watchBatteryState] ?? $watchBatteryState ?? null, 'wlevel' => $watchBatteryLevelData['state'] ?? null, 'ascended' => $ascendedData['state'] ?? null, 'descended' => $descendedData['state'] ?? null, 'steps' => $stepsData['state'] ?? null, 'distance' => $distanceData['state'] ?? null, 'activity' => $activityMap[$lang][$activityState] ?? $activityState ?? null, 'aaccuracy' => $aaccuracyMap[$lang][$aaccuracyState] ?? $aaccuracyState ?? null, 'connection' => $connectionMap[$lang][$connectionState] ?? $connectionState ?? null, 'history' => $path ?? null, ], JSON_UNESCAPED_UNICODE); ?>