307 lines
11 KiB
PHP
307 lines
11 KiB
PHP
<?php
|
|
// Apache 환경 차단
|
|
if (isset($_SERVER['SERVER_SOFTWARE']) && stripos($_SERVER['SERVER_SOFTWARE'], 'Apache') !== false) {
|
|
http_response_code(403);
|
|
header('Content-Type: application/json');
|
|
echo json_encode(['error' => '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);
|
|
?>
|