Return full findmydevice history paths

This commit is contained in:
seo
2026-06-17 19:20:28 +09:00
parent 689b8013b9
commit 8bbe43b8c4
3 changed files with 9 additions and 48 deletions
+4 -2
View File
@@ -42,9 +42,9 @@ Home Assistant에 기록된 Galaxy Z Fold7의 위치, 배터리, 활동 상태,
## 히스토리와 지도 ## 히스토리와 지도
실시간 화면은 현재 위치와 상태를 조회하고, 기본 기간 입력값에 해당하는 최근 하루 이동 기록도 지도에 함께 표시합니다. 기간 입력을 변경하면 해당 범위의 이동 기록을 `history=1` 옵션으로 다시 요청합니다. 서버는 Home Assistant 위치 기록을 5m 미만 미세 이동 기준으로 정리한 뒤 최대 800개 지점으로 샘플링합니다. 실시간 화면은 현재 위치와 상태를 조회하고, 기본 기간 입력값에 해당하는 최근 하루 이동 기록도 지도에 함께 표시합니다. 기간 입력을 변경하면 해당 범위의 이동 기록을 `history=1` 옵션으로 다시 요청합니다. 서버는 기간이 길어도 Home Assistant 히스토리 API를 실행하고, 반환된 위치 기록을 샘플링하거나 최대 개수로 자르지 않고 응답합니다.
Naver Map에는 전체 경로선을 먼저 그리고 시작/종료 마커와 최대 180개의 중간 포인트를 표시합니다. 기록 조회 시 지도는 경로 전체가 보이도록 자동으로 범위를 맞추며, 종료 지점 정보창을 먼저 열어 최근 위치를 바로 확인할 수 있게 합니다. Naver Map에는 응답받은 전체 경로선을 먼저 그리고 시작/종료 마커와 중간 포인트를 표시합니다. 기록 조회 시 지도는 경로 전체가 보이도록 자동으로 범위를 맞추며, 종료 지점 정보창을 먼저 열어 최근 위치를 바로 확인할 수 있게 합니다.
지도 기능은 NAVER 지도 API v3의 기본 컨트롤, 지도 유형, `TrafficLayer`, `StreetLayer`, `Polyline`, `PointingIcon`, `Circle`, `fitBounds`를 활용합니다. 지도 아래 도구 패널에서 일반/위성 지도 전환, 교통 레이어, 거리뷰 가능 구역 레이어, 경로 전체 보기, 경로 재생, GPS 튐 보정, Naver 지도 열기를 제어합니다. 지도 기능은 NAVER 지도 API v3의 기본 컨트롤, 지도 유형, `TrafficLayer`, `StreetLayer`, `Polyline`, `PointingIcon`, `Circle`, `fitBounds`를 활용합니다. 지도 아래 도구 패널에서 일반/위성 지도 전환, 교통 레이어, 거리뷰 가능 구역 레이어, 경로 전체 보기, 경로 재생, GPS 튐 보정, Naver 지도 열기를 제어합니다.
@@ -68,4 +68,6 @@ Android Companion App의 실제 상태값은 API에서 한국어/영어 표시
## 운영 메모 ## 운영 메모
긴 기간 히스토리 조회는 Home Assistant recorder 데이터량과 웹서버/프록시 timeout 영향을 받을 수 있습니다. findmydevice API는 자체 포인트 수 제한과 curl timeout을 걸지 않으므로, 가능한 범위에서는 Home Assistant가 반환한 전체 결과를 화면에 출력합니다.
이 프로젝트는 위치, 주소, 배터리, 활동 기록을 포함하므로 private 저장소와 개인 접근 환경에서만 운영합니다. 이 프로젝트는 위치, 주소, 배터리, 활동 기록을 포함하므로 private 저장소와 개인 접근 환경에서만 운영합니다.
-1
View File
@@ -2188,7 +2188,6 @@ function fetchDeviceData(includeLang = true, range = null) {
if (e) params.set('endTime', e); if (e) params.set('endTime', e);
if (hasHistoryRange) { if (hasHistoryRange) {
params.set('history', '1'); params.set('history', '1');
params.set('maxPoints', '800');
} }
const url = `https://chaegeon.com/custom/findmydevice/php/api.php?${params.toString()}`; const url = `https://chaegeon.com/custom/findmydevice/php/api.php?${params.toString()}`;
+5 -45
View File
@@ -14,6 +14,10 @@ header('Content-Type: application/json; charset=utf-8');
header('Cache-Control: no-store, no-cache, must-revalidate'); header('Cache-Control: no-store, no-cache, must-revalidate');
header('Expires: 0'); header('Expires: 0');
if (function_exists('set_time_limit')) {
@set_time_limit(0);
}
custom_require_auth(); custom_require_auth();
$requestData = custom_read_request_data(); $requestData = custom_read_request_data();
@@ -77,7 +81,6 @@ if ($startParam && $endParam) {
// API URL 생성 // API URL 생성
$historyEndpoint = "/history/period/{$startTime}?filter_entity_id=" . rawurlencode($entities['device']) . "&end_time={$endTime}"; $historyEndpoint = "/history/period/{$startTime}?filter_entity_id=" . rawurlencode($entities['device']) . "&end_time={$endTime}";
$includeHistory = isset($_GET['history']) && $_GET['history'] === '1' && !isset($_GET['hashonly']); $includeHistory = isset($_GET['history']) && $_GET['history'] === '1' && !isset($_GET['hashonly']);
$historyMaxPoints = max(20, min(1200, (int)($_GET['maxPoints'] ?? 800)));
// 공통 요청 함수 // 공통 요청 함수
function getData($entityId) { function getData($entityId) {
@@ -202,7 +205,7 @@ $ascendedData = getData($fascendedUrl);
$descendedData = getData($fdescendedUrl); $descendedData = getData($fdescendedUrl);
$stepsData = getData($stepsUrl); $stepsData = getData($stepsUrl);
$distanceData = getData($distanceUrl); $distanceData = getData($distanceUrl);
$historyData = $includeHistory ? (custom_ha_request('seoul', 'GET', $historyEndpoint) ?? []) : []; $historyData = $includeHistory ? (custom_ha_request('seoul', 'GET', $historyEndpoint, null, 0) ?? []) : [];
$triggerData = getData($triggerUrl); $triggerData = getData($triggerUrl);
// 경로 기록 // 경로 기록
@@ -218,49 +221,6 @@ 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) >= 5) {
$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'; $lang = $_GET['lang'] ?? 'en';
$activityMap = [ $activityMap = [