Optimize Find My Device history
This commit is contained in:
+179
-20
@@ -76,6 +76,8 @@ if ($startParam && $endParam) {
|
||||
|
||||
// API URL 생성
|
||||
$historyEndpoint = "/history/period/{$startTime}?filter_entity_id=" . rawurlencode($entities['device']) . "&end_time={$endTime}";
|
||||
$includeHistory = isset($_GET['history']) && $_GET['history'] === '1' && !isset($_GET['hashonly']);
|
||||
$historyMaxPoints = max(20, min(1000, (int)($_GET['maxPoints'] ?? 300)));
|
||||
|
||||
// 공통 요청 함수
|
||||
function getData($entityId) {
|
||||
@@ -111,29 +113,59 @@ function mappedText($state, $lang, $map) {
|
||||
return $map[$lang][$state] ?? $map[$lang][strtolower((string)$state)] ?? $state;
|
||||
}
|
||||
|
||||
function durationText(int $seconds, string $lang): ?string
|
||||
{
|
||||
if ($seconds <= 0) return null;
|
||||
$days = intdiv($seconds, 86400);
|
||||
$seconds %= 86400;
|
||||
$hours = intdiv($seconds, 3600);
|
||||
$seconds %= 3600;
|
||||
$minutes = intdiv($seconds, 60);
|
||||
$seconds %= 60;
|
||||
$parts = [];
|
||||
|
||||
if ($lang === 'ko') {
|
||||
if ($days) $parts[] = $days . '일';
|
||||
if ($hours) $parts[] = $hours . '시간';
|
||||
if ($minutes) $parts[] = $minutes . '분';
|
||||
if ($seconds && !$days && !$hours) $parts[] = $seconds . '초';
|
||||
} else {
|
||||
if ($days) $parts[] = $days . 'd';
|
||||
if ($hours) $parts[] = $hours . 'h';
|
||||
if ($minutes) $parts[] = $minutes . 'm';
|
||||
if ($seconds && !$days && !$hours) $parts[] = $seconds . 's';
|
||||
}
|
||||
|
||||
return implode(' ', $parts) ?: null;
|
||||
}
|
||||
|
||||
function formatSensorValue($value, $definition, $lang, $maps) {
|
||||
if ($value === null) return null;
|
||||
|
||||
if (isset($definition['omit_values'])) {
|
||||
$omitValues = array_map('strval', (array)$definition['omit_values']);
|
||||
if (in_array((string)$value, $omitValues, true)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (($definition['format'] ?? '') === 'bool') {
|
||||
return boolText($value, $lang);
|
||||
}
|
||||
|
||||
if (($definition['format'] ?? '') === 'duration') {
|
||||
if (!is_numeric($value)) return $value;
|
||||
$seconds = (int)$value;
|
||||
if ($seconds <= 0) return null;
|
||||
$days = intdiv($seconds, 86400);
|
||||
$seconds %= 86400;
|
||||
$hours = intdiv($seconds, 3600);
|
||||
$seconds %= 3600;
|
||||
$minutes = intdiv($seconds, 60);
|
||||
$seconds %= 60;
|
||||
$parts = [];
|
||||
if ($days) $parts[] = $days . 'd';
|
||||
if ($hours) $parts[] = $hours . 'h';
|
||||
if ($minutes) $parts[] = $minutes . 'm';
|
||||
if ($seconds && !$days && !$hours) $parts[] = $seconds . 's';
|
||||
return implode(' ', $parts) ?: null;
|
||||
return durationText((int)$value, $lang);
|
||||
}
|
||||
|
||||
if (($definition['format'] ?? '') === 'duration_ms') {
|
||||
if (!is_numeric($value)) return $value;
|
||||
return durationText((int)round(((float)$value) / 1000), $lang);
|
||||
}
|
||||
|
||||
if (($definition['format'] ?? '') === 'duration_min') {
|
||||
if (!is_numeric($value)) return $value;
|
||||
return durationText((int)round(((float)$value) * 60), $lang);
|
||||
}
|
||||
|
||||
if (($definition['format'] ?? '') === 'datetime') {
|
||||
@@ -144,6 +176,14 @@ function formatSensorValue($value, $definition, $lang, $maps) {
|
||||
$value = mappedText($value, $lang, $maps[$definition['map']]);
|
||||
}
|
||||
|
||||
if (isset($definition['scale']) && is_numeric($value)) {
|
||||
$value = (float)$value * (float)$definition['scale'];
|
||||
}
|
||||
|
||||
if (isset($definition['decimals']) && is_numeric($value)) {
|
||||
$value = round((float)$value, (int)$definition['decimals']);
|
||||
}
|
||||
|
||||
if (isset($definition['unit']) && is_numeric($value)) {
|
||||
return $value . $definition['unit'];
|
||||
}
|
||||
@@ -162,7 +202,7 @@ $ascendedData = getData($fascendedUrl);
|
||||
$descendedData = getData($fdescendedUrl);
|
||||
$stepsData = getData($stepsUrl);
|
||||
$distanceData = getData($distanceUrl);
|
||||
$historyData = custom_ha_request('seoul', 'GET', $historyEndpoint) ?? [];
|
||||
$historyData = $includeHistory ? (custom_ha_request('seoul', 'GET', $historyEndpoint) ?? []) : [];
|
||||
$triggerData = getData($triggerUrl);
|
||||
|
||||
// 경로 기록
|
||||
@@ -178,23 +218,80 @@ if (isset($historyData[0])) {
|
||||
}
|
||||
}
|
||||
|
||||
function path_distance_m(array $a, array $b): float
|
||||
{
|
||||
$earth = 6371000;
|
||||
$lat1 = deg2rad((float)$a['lat']);
|
||||
$lat2 = deg2rad((float)$b['lat']);
|
||||
$dLat = $lat2 - $lat1;
|
||||
$dLng = deg2rad((float)$b['lng'] - (float)$a['lng']);
|
||||
$h = sin($dLat / 2) ** 2 + cos($lat1) * cos($lat2) * sin($dLng / 2) ** 2;
|
||||
return 2 * $earth * asin(min(1, sqrt($h)));
|
||||
}
|
||||
|
||||
function simplify_path(array $path, int $maxPoints): array
|
||||
{
|
||||
$count = count($path);
|
||||
if ($count <= $maxPoints) {
|
||||
return $path;
|
||||
}
|
||||
|
||||
$filtered = [];
|
||||
$last = null;
|
||||
foreach ($path as $point) {
|
||||
if ($last === null || path_distance_m($last, $point) >= 15) {
|
||||
$filtered[] = $point;
|
||||
$last = $point;
|
||||
}
|
||||
}
|
||||
|
||||
if (count($filtered) <= $maxPoints) {
|
||||
return $filtered;
|
||||
}
|
||||
|
||||
$sampled = [];
|
||||
$lastIndex = count($filtered) - 1;
|
||||
for ($i = 0; $i < $maxPoints; $i++) {
|
||||
$idx = (int)round($i * $lastIndex / max(1, $maxPoints - 1));
|
||||
$sampled[] = $filtered[$idx];
|
||||
}
|
||||
|
||||
return $sampled;
|
||||
}
|
||||
|
||||
$path = simplify_path($path, $historyMaxPoints);
|
||||
|
||||
$lang = $_GET['lang'] ?? 'en';
|
||||
|
||||
$activityMap = [
|
||||
'ko' => [
|
||||
'Stationary' => '가만히 있음',
|
||||
'still' => '가만히 있음',
|
||||
'Walking' => '걷는 중',
|
||||
'walking' => '걷는 중',
|
||||
'Running' => '달리는 중',
|
||||
'running' => '달리는 중',
|
||||
'Automotive' => '차량 이동 중',
|
||||
'in_vehicle' => '차량 이동 중',
|
||||
'Cycling' => '자전거 이동 중',
|
||||
'on_bicycle' => '자전거 이동 중',
|
||||
'on_foot' => '도보 이동 중',
|
||||
'tilting' => '기울어짐',
|
||||
'Unknown' => '-',
|
||||
],
|
||||
'en' => [
|
||||
'Stationary' => 'Stationary',
|
||||
'still' => 'Stationary',
|
||||
'Walking' => 'Walking',
|
||||
'walking' => 'Walking',
|
||||
'Running' => 'Running',
|
||||
'running' => 'Running',
|
||||
'Automotive' => 'Automotive',
|
||||
'in_vehicle' => 'In vehicle',
|
||||
'Cycling' => 'Cycling',
|
||||
'on_bicycle' => 'On bicycle',
|
||||
'on_foot' => 'On foot',
|
||||
'tilting' => 'Tilting',
|
||||
'Unknown' => '-',
|
||||
]
|
||||
];
|
||||
@@ -217,13 +314,23 @@ $aaccuracyMap = [
|
||||
$connectionMap = [
|
||||
'ko' => [
|
||||
'Wi-Fi' => '와이파이',
|
||||
'wifi' => '와이파이',
|
||||
'Cellular' => '셀룰러',
|
||||
'cellular' => '셀룰러',
|
||||
'ethernet' => '이더넷',
|
||||
'bluetooth' => '블루투스',
|
||||
'vpn' => 'VPN',
|
||||
'none' => '-',
|
||||
'unknown' => '-',
|
||||
],
|
||||
'en' => [
|
||||
'Wi-Fi' => 'Wi-Fi',
|
||||
'wifi' => 'Wi-Fi',
|
||||
'Cellular' => 'Cellular',
|
||||
'cellular' => 'Cellular',
|
||||
'ethernet' => 'Ethernet',
|
||||
'bluetooth' => 'Bluetooth',
|
||||
'vpn' => 'VPN',
|
||||
'none' => '-',
|
||||
'unknown' => '-',
|
||||
]
|
||||
@@ -256,17 +363,61 @@ $batteryStateMap = [
|
||||
|
||||
$androidValueMap = [
|
||||
'chargerType' => [
|
||||
'ko' => ['none' => '없음', 'wireless' => '무선', 'ac' => '유선', 'usb' => 'USB'],
|
||||
'en' => ['none' => 'None', 'wireless' => 'Wireless', 'ac' => 'AC', 'usb' => 'USB'],
|
||||
'ko' => ['none' => '없음', 'wireless' => '무선', 'ac' => '유선', 'usb' => 'USB', 'dock' => '도크'],
|
||||
'en' => ['none' => 'None', 'wireless' => 'Wireless', 'ac' => 'AC', 'usb' => 'USB', 'dock' => 'Dock'],
|
||||
],
|
||||
'batteryHealth' => [
|
||||
'ko' => ['good' => '양호', 'overheat' => '과열', 'dead' => '불량', 'over_voltage' => '과전압', 'cold' => '저온'],
|
||||
'en' => ['good' => 'Good', 'overheat' => 'Overheat', 'dead' => 'Dead', 'over_voltage' => 'Over voltage', 'cold' => 'Cold'],
|
||||
'ko' => ['good' => '양호', 'overheat' => '과열', 'overheated' => '과열', 'dead' => '불량', 'failed' => '오류', 'over_voltage' => '과전압', 'cold' => '저온'],
|
||||
'en' => ['good' => 'Good', 'overheat' => 'Overheated', 'overheated' => 'Overheated', 'dead' => 'Dead', 'failed' => 'Failed', 'over_voltage' => 'Over voltage', 'cold' => 'Cold'],
|
||||
],
|
||||
'phoneState' => [
|
||||
'ko' => ['idle' => '대기', 'ringing' => '수신 중', 'offhook' => '통화 중'],
|
||||
'en' => ['idle' => 'Idle', 'ringing' => 'Ringing', 'offhook' => 'Off hook'],
|
||||
],
|
||||
'networkType' => [
|
||||
'ko' => ['cellular' => '셀룰러', 'wifi' => 'Wi-Fi', 'ethernet' => '이더넷', 'bluetooth' => '블루투스', 'vpn' => 'VPN', 'usb' => 'USB', 'lowpan' => 'LoWPAN', 'wifi_aware' => 'Wi-Fi Aware'],
|
||||
'en' => ['cellular' => 'Cellular', 'wifi' => 'Wi-Fi', 'ethernet' => 'Ethernet', 'bluetooth' => 'Bluetooth', 'vpn' => 'VPN', 'usb' => 'USB', 'lowpan' => 'LoWPAN', 'wifi_aware' => 'Wi-Fi Aware'],
|
||||
],
|
||||
'appStandbyBucket' => [
|
||||
'ko' => ['never' => '제한 없음', 'active' => '활성', 'working_set' => '작업 세트', 'frequent' => '자주 사용', 'rare' => '드묾', 'restricted' => '제한됨'],
|
||||
'en' => ['never' => 'Never', 'active' => 'Active', 'working_set' => 'Working set', 'frequent' => 'Frequent', 'rare' => 'Rare', 'restricted' => 'Restricted'],
|
||||
],
|
||||
'appImportance' => [
|
||||
'ko' => ['foreground' => '포그라운드', 'foreground_service' => '포그라운드 서비스', 'visible' => '표시 중', 'perceptible' => '인지 가능', 'service' => '서비스', 'cached' => '캐시됨', 'gone' => '종료됨'],
|
||||
'en' => ['foreground' => 'Foreground', 'foreground_service' => 'Foreground service', 'visible' => 'Visible', 'perceptible' => 'Perceptible', 'service' => 'Service', 'cached' => 'Cached', 'gone' => 'Gone'],
|
||||
],
|
||||
'screenOrientation' => [
|
||||
'ko' => ['portrait' => '세로', 'landscape' => '가로', 'square' => '정사각형'],
|
||||
'en' => ['portrait' => 'Portrait', 'landscape' => 'Landscape', 'square' => 'Square'],
|
||||
],
|
||||
'doNotDisturb' => [
|
||||
'ko' => ['off' => '꺼짐', 'alarms_only' => '알람만', 'priority_only' => '우선순위만', 'total_silence' => '완전 무음'],
|
||||
'en' => ['off' => 'Off', 'alarms_only' => 'Alarms only', 'priority_only' => 'Priority only', 'total_silence' => 'Total silence'],
|
||||
],
|
||||
'ringerMode' => [
|
||||
'ko' => ['normal' => '일반', 'silent' => '무음', 'vibrate' => '진동'],
|
||||
'en' => ['normal' => 'Normal', 'silent' => 'Silent', 'vibrate' => 'Vibrate'],
|
||||
],
|
||||
'audioMode' => [
|
||||
'ko' => ['normal' => '일반', 'ringing' => '벨 울림', 'in_call' => '통화 중', 'in_communication' => '통신 중', 'call_screening' => '통화 선별', 'call_redirect' => '통화 전환', 'communication_redirect' => '통신 전환'],
|
||||
'en' => ['normal' => 'Normal', 'ringing' => 'Ringing', 'in_call' => 'In call', 'in_communication' => 'In communication', 'call_screening' => 'Call screening', 'call_redirect' => 'Call redirect', 'communication_redirect' => 'Communication redirect'],
|
||||
],
|
||||
'bleTransmitter' => [
|
||||
'ko' => ['Transmitting' => '송신 중', 'Stopped' => '중지됨', 'Bluetooth is turned off' => '블루투스 꺼짐', 'Unable to transmit' => '송신 불가'],
|
||||
'en' => ['Transmitting' => 'Transmitting', 'Stopped' => 'Stopped', 'Bluetooth is turned off' => 'Bluetooth is off', 'Unable to transmit' => 'Unable to transmit'],
|
||||
],
|
||||
'beaconMonitor' => [
|
||||
'ko' => ['Monitoring' => '감시 중', 'Stopped' => '중지됨', 'Bluetooth is turned off' => '블루투스 꺼짐'],
|
||||
'en' => ['Monitoring' => 'Monitoring', 'Stopped' => 'Stopped', 'Bluetooth is turned off' => 'Bluetooth is off'],
|
||||
],
|
||||
'mediaSession' => [
|
||||
'ko' => ['Playing' => '재생 중', 'Paused' => '일시정지', 'Stopped' => '중지됨', 'Buffering' => '버퍼링', 'Connecting' => '연결 중', 'Error' => '오류', 'Fast Forwarding' => '빨리감기', 'None' => '없음', 'Rewinding' => '되감기', 'Skip to Next' => '다음으로 이동', 'Skip to Previous' => '이전으로 이동', 'Skip to Queue Item' => '대기열 항목 이동'],
|
||||
'en' => ['Playing' => 'Playing', 'Paused' => 'Paused', 'Stopped' => 'Stopped', 'Buffering' => 'Buffering', 'Connecting' => 'Connecting', 'Error' => 'Error', 'Fast Forwarding' => 'Fast forwarding', 'None' => 'None', 'Rewinding' => 'Rewinding', 'Skip to Next' => 'Skip to next', 'Skip to Previous' => 'Skip to previous', 'Skip to Queue Item' => 'Skip to queue item'],
|
||||
],
|
||||
'proximity' => [
|
||||
'ko' => ['far' => '멀리', 'near' => '가까이'],
|
||||
'en' => ['far' => 'Far', 'near' => 'Near'],
|
||||
],
|
||||
];
|
||||
|
||||
$triggerMap = [
|
||||
@@ -282,6 +433,10 @@ $triggerMap = [
|
||||
"Watch Context" => "워치 상태 전송",
|
||||
"Geographic Region Entered" => "지정 지역에 들어옴",
|
||||
"Geographic Region Exited" => "지정 지역을 벗어남",
|
||||
"android.intent.action.TIME_TICK" => "Android 시간 틱",
|
||||
"android.intent.action.SCREEN_ON" => "화면 켜짐",
|
||||
"android.intent.action.SCREEN_OFF" => "화면 꺼짐",
|
||||
"io.homeassistant.companion.android.UPDATE_SENSORS" => "센서 업데이트",
|
||||
],
|
||||
'en' => [
|
||||
"Launch" => "Launch",
|
||||
@@ -295,6 +450,10 @@ $triggerMap = [
|
||||
"Watch Context" => "Watch Context",
|
||||
"Geographic Region Entered" => "Geographic Region Entered",
|
||||
"Geographic Region Exited" => "Geographic Region Exited",
|
||||
"android.intent.action.TIME_TICK" => "Android Time Tick",
|
||||
"android.intent.action.SCREEN_ON" => "Screen On",
|
||||
"android.intent.action.SCREEN_OFF" => "Screen Off",
|
||||
"io.homeassistant.companion.android.UPDATE_SENSORS" => "Sensor Update",
|
||||
]
|
||||
];
|
||||
|
||||
@@ -383,6 +542,6 @@ echo json_encode([
|
||||
'aaccuracy' => $aaccuracyMap[$lang][$aaccuracyState] ?? $aaccuracyState ?? null,
|
||||
'connection' => $connectionMap[$lang][$connectionState] ?? $connectionState ?? null,
|
||||
'sensors' => $sensorSections,
|
||||
'history' => $path ?? null,
|
||||
'history' => $includeHistory ? $path : null,
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
?>
|
||||
|
||||
Reference in New Issue
Block a user