`, `none`처럼 표시 가치가 낮은 상태와 Wi-Fi 미연결 시의 `0Mbps`, `0MHz`, `-1dBm` 값은 화면에서 제외합니다. Home Assistant 로그에서 속성 크기 경고가 발생하는 활성 알림 목록은 상세 속성을 노출하지 않고 개수만 표시합니다.
+
+Android Companion App의 실제 상태값은 API에서 한국어/영어 표시값으로 변환합니다. 예를 들어 `still`, `cellular`, `foreground_service`, `Transmitting`, `Stopped`, `portrait`, `silent` 같은 값은 화면 언어에 맞춰 표시됩니다.
+
## 운영 메모
이 프로젝트는 위치, 주소, 배터리, 활동 기록을 포함하므로 private 저장소와 개인 접근 환경에서만 운영합니다.
diff --git a/js/main.js b/js/main.js
index c7dde0a..a7423fd 100644
--- a/js/main.js
+++ b/js/main.js
@@ -51,10 +51,14 @@ const deviceLang = (navigator.language || navigator.userLanguage || "en").starts
device: "기기",
displayAudio: "화면 및 오디오",
health: "건강",
+ wellness: "건강/활동",
car: "차량",
storageData: "저장공간 및 데이터",
notifications: "알림",
+ alerts: "알림/통화",
environment: "센서",
+ networkType: "네트워크 방식",
+ wifiState: "Wi-Fi 상태",
wifiBssid: "Wi-Fi BSSID",
mobileDataRoaming: "데이터 로밍",
publicIpAddress: "공인 IP",
@@ -213,10 +217,14 @@ const deviceLang = (navigator.language || navigator.userLanguage || "en").starts
device: "Device",
displayAudio: "Display & Audio",
health: "Health",
+ wellness: "Wellness",
car: "Car",
storageData: "Storage & Data",
notifications: "Notifications",
+ alerts: "Alerts & Calls",
environment: "Sensors",
+ networkType: "Network Type",
+ wifiState: "Wi-Fi State",
wifiBssid: "Wi-Fi BSSID",
mobileDataRoaming: "Data Roaming",
publicIpAddress: "Public IP",
@@ -329,12 +337,15 @@ const deviceLang = (navigator.language || navigator.userLanguage || "en").starts
map: null,
marker: null,
circle: null,
- trackLine: null,
- smallMarkers: [],
- openInfoWindow: null,
- mapClickHandlerAdded: false,
- initialInfoOpened: false,
- };
+ trackLine: null,
+ smallMarkers: [],
+ historyStartMarker: null,
+ historyEndMarker: null,
+ openInfoWindow: null,
+ mapClickHandlerAdded: false,
+ initialInfoOpened: false,
+ historyBoundsFitted: false,
+ };
let langCode = localStorage.getItem("currentLang") || deviceLang,
lastAutoSwitchChecked,
lastUpdateSwitchChecked,
@@ -617,16 +628,17 @@ document.addEventListener('DOMContentLoaded', () => {
};
// ✅ 사용자가 변경했거나 URL에 기간이 있으면 히스토리 모드
- if (userTriggered || hasQS) {
- _overrideRange = range;
- resetHistoryLayer();
- } else {
- _overrideRange = null; // 라이브 모드 유지
- }
-
- try {
- const data = await fetchDeviceData(true, range);
- updateDOMWithData(data);
+ const shouldUseHistory = userTriggered || hasQS;
+ if (shouldUseHistory) {
+ _overrideRange = range;
+ resetHistoryLayer();
+ } else {
+ _overrideRange = null; // 라이브 모드 유지
+ }
+
+ try {
+ const data = await fetchDeviceData(true, shouldUseHistory ? range : null);
+ updateDOMWithData(data);
const last = data.history && data.history[data.history.length - 1];
if (last && mapState.map) {
@@ -1085,128 +1097,122 @@ function resetHistoryLayer() {
mapState.trackLine.setMap(null);
mapState.trackLine = null;
}
- if (mapState.smallMarkers?.length) {
- mapState.smallMarkers.forEach(m => m.setMap?.(null));
- mapState.smallMarkers = [];
- }
- if (mapState.openInfoWindow) {
- mapState.openInfoWindow.close();
- mapState.openInfoWindow = null;
- }
-}
-
-function updateHistoryMarkers(history) {
- // 안전가드
- if (!Array.isArray(history) || history.length === 0) {
- // 표시된 마커를 기준으로 라인만 갱신(필요 시)
- renderTrackFromDisplayedMarkers();
- return;
- }
-
- // time 파싱 헬퍼
- const toTime = (v) => {
- if (v instanceof Date) return v.getTime();
- if (typeof v === 'number' && isFinite(v)) {
- // 초 단위처럼 보이면 ms로 변환
- return v < 2e10 ? v * 1000 : v;
- }
- const t = Date.parse(v);
- return isNaN(t) ? 0 : t;
- };
-
- // ★ history는 스몰마커 생성만에 사용(정렬은 생성 안정성용)
- history.sort((a, b) => toTime(a.time) - toTime(b.time));
-
- // 1) 스몰마커 생성/중복 방지
- history.forEach(pos => {
- const pointLatLng = new naver.maps.LatLng(pos.lat, pos.lng);
- const exist = mapState.smallMarkers.some(marker =>
- marker.getPosition().equals(pointLatLng) && marker.userTime === pos.time
- );
- if (!exist) {
- const smallMarker = new naver.maps.Marker({
- position: pointLatLng,
- map: mapState.map,
- icon: {
- content: `
- `,
- anchor: new naver.maps.Point(18, 18)
- }
- });
- smallMarker.userTime = pos.time; // ⚑ 표시된 마커의 시간 정보를 유지
- mapState.smallMarkers.push(smallMarker);
-
- const infoWindow = new naver.maps.InfoWindow({
- content: `${getRelativeTime(pos.time)}
`
- });
-
- smallMarker.infoWindow = infoWindow;
-
- naver.maps.Event.addListener(smallMarker, "click", () => {
- if (mapState.openInfoWindow && mapState.openInfoWindow !== infoWindow) {
- mapState.openInfoWindow.close();
- }
- if (infoWindow.getMap()) {
- infoWindow.close();
- mapState.openInfoWindow = null;
- } else {
- infoWindow.open(mapState.map, smallMarker);
- mapState.openInfoWindow = infoWindow;
- }
- });
- }
- });
-
- // 2) ★★ 지금 "화면에 표시된 스몰마커들"만으로 폴리라인 갱신 ★★
- renderTrackFromDisplayedMarkers();
-
- // ★ 최초 로드시 마지막(가장 최근) 스몰마커의 InfoWindow 자동 오픈
- if (!mapState.initialInfoOpened && mapState.smallMarkers.length) {
- // 시간순 정렬 후 마지막 선택
- const toTime = (v) => {
- if (v instanceof Date) return v.getTime();
- if (typeof v === 'number' && isFinite(v)) return v < 2e10 ? v * 1000 : v;
- const t = Date.parse(v);
- return isNaN(t) ? 0 : t;
- };
-
- const lastMarker = mapState.smallMarkers
- .slice()
- .sort((a, b) => toTime(a.userTime) - toTime(b.userTime))
- .at(-1);
-
- if (lastMarker && lastMarker.infoWindow) {
- // 이전 열린 창 닫기
- if (mapState.openInfoWindow && mapState.openInfoWindow !== lastMarker.infoWindow) {
- mapState.openInfoWindow.close();
- }
-
- lastMarker.infoWindow.open(mapState.map, lastMarker);
- mapState.openInfoWindow = lastMarker.infoWindow;
-
- // 보기 편하게 살짝 이동
- mapState.map.panTo(lastMarker.getPosition());
- }
-
- mapState.initialInfoOpened = true;
- }
-
- // map 클릭시 열린 InfoWindow 닫기 (1회만 등록)
- if (!mapState.mapClickHandlerAdded) {
- naver.maps.Event.addListener(mapState.map, "click", () => {
- if (mapState.openInfoWindow) {
+ if (mapState.smallMarkers?.length) {
+ mapState.smallMarkers.forEach(m => m.setMap?.(null));
+ mapState.smallMarkers = [];
+ }
+ if (mapState.historyStartMarker) {
+ mapState.historyStartMarker.setMap(null);
+ mapState.historyStartMarker = null;
+ }
+ if (mapState.historyEndMarker) {
+ mapState.historyEndMarker.setMap(null);
+ mapState.historyEndMarker = null;
+ }
+ if (mapState.openInfoWindow) {
+ mapState.openInfoWindow.close();
+ mapState.openInfoWindow = null;
+ }
+ mapState.initialInfoOpened = false;
+ mapState.historyBoundsFitted = false;
+}
+
+function updateHistoryMarkers(history) {
+ if (!Array.isArray(history) || history.length === 0) {
+ return;
+ }
+ if (!mapState.map) return;
+
+ const toTime = (v) => {
+ if (v instanceof Date) return v.getTime();
+ if (typeof v === 'number' && isFinite(v)) return v < 2e10 ? v * 1000 : v;
+ const t = Date.parse(v);
+ return isNaN(t) ? 0 : t;
+ };
+
+ const points = history
+ .filter(pos => hasValidCoords(pos.lat, pos.lng))
+ .sort((a, b) => toTime(a.time) - toTime(b.time))
+ .map(pos => ({ ...pos, latlng: new naver.maps.LatLng(pos.lat, pos.lng) }));
+
+ if (!points.length) return;
+
+ if (mapState.trackLine) mapState.trackLine.setMap(null);
+ mapState.trackLine = new naver.maps.Polyline({
+ map: mapState.map,
+ path: points.map(pos => pos.latlng),
+ strokeColor: '#007AFF',
+ strokeOpacity: 0.85,
+ strokeWeight: 4
+ });
+
+ if (mapState.smallMarkers?.length) {
+ mapState.smallMarkers.forEach(marker => marker.setMap(null));
+ mapState.smallMarkers = [];
+ }
+ if (mapState.historyStartMarker) mapState.historyStartMarker.setMap(null);
+ if (mapState.historyEndMarker) mapState.historyEndMarker.setMap(null);
+
+ const makeMarker = (pos, type = 'point') => {
+ const isEndpoint = type === 'start' || type === 'end';
+ const color = type === 'start' ? '#34C759' : (type === 'end' ? '#FF3B30' : '#007AFF');
+ const label = type === 'start' ? (langCode === 'ko' ? '시작' : 'Start') : (type === 'end' ? (langCode === 'ko' ? '종료' : 'End') : '');
+ const marker = new naver.maps.Marker({
+ position: pos.latlng,
+ map: mapState.map,
+ zIndex: isEndpoint ? 90 : 50,
+ icon: {
+ content: `${label}
`,
+ anchor: new naver.maps.Point(isEndpoint ? 24 : 8, isEndpoint ? 14 : 8)
+ }
+ });
+ marker.userTime = pos.time;
+ marker.infoWindow = new naver.maps.InfoWindow({
+ content: `${label ? `${label}
` : ''}${getRelativeTime(pos.time)}
`
+ });
+ naver.maps.Event.addListener(marker, "click", () => {
+ if (mapState.openInfoWindow && mapState.openInfoWindow !== marker.infoWindow) {
+ mapState.openInfoWindow.close();
+ }
+ if (marker.infoWindow.getMap()) {
+ marker.infoWindow.close();
+ mapState.openInfoWindow = null;
+ } else {
+ marker.infoWindow.open(mapState.map, marker);
+ mapState.openInfoWindow = marker.infoWindow;
+ }
+ });
+ return marker;
+ };
+
+ const first = points[0];
+ const last = points.at(-1);
+ mapState.historyStartMarker = makeMarker(first, 'start');
+ mapState.historyEndMarker = makeMarker(last, 'end');
+
+ const maxDots = 60;
+ const step = Math.max(1, Math.ceil(points.length / maxDots));
+ points.forEach((pos, idx) => {
+ if (idx === 0 || idx === points.length - 1 || idx % step !== 0) return;
+ mapState.smallMarkers.push(makeMarker(pos));
+ });
+
+ if (!mapState.historyBoundsFitted) {
+ const bounds = new naver.maps.LatLngBounds(first.latlng, first.latlng);
+ points.forEach(pos => bounds.extend(pos.latlng));
+ mapState.map.fitBounds(bounds);
+ mapState.historyBoundsFitted = true;
+ }
+
+ if (!mapState.initialInfoOpened && mapState.historyEndMarker?.infoWindow) {
+ mapState.historyEndMarker.infoWindow.open(mapState.map, mapState.historyEndMarker);
+ mapState.openInfoWindow = mapState.historyEndMarker.infoWindow;
+ mapState.initialInfoOpened = true;
+ }
+
+ if (!mapState.mapClickHandlerAdded) {
+ naver.maps.Event.addListener(mapState.map, "click", () => {
+ if (mapState.openInfoWindow) {
mapState.openInfoWindow.close();
mapState.openInfoWindow = null;
}
@@ -1251,20 +1257,16 @@ function updateDOMWithData(data) {
updateActivitySection(data);
// 지도 초기화
- if (!mapState.map) {
- initMap(latlng, data.accuracy);
- if (data.history?.length > 0) updateHistoryMarkers(data.history);
- }
+ if (!mapState.map) {
+ initMap(latlng, data.accuracy);
+ }
// 메인 마커 / 정확도 원 업데이트
mapState.marker.setPosition(latlng);
mapState.circle.setCenter(latlng);
animateCircleRadius(Math.max(data.accuracy || 50, 10));
- // history (있으면) 업데이트
- if (data.history?.length > 0) updateHistoryMarkers(data.history);
-
- // autoPosition
+ // autoPosition
const isChecked = document.getElementById('autoPositionSwitch').checked;
if (isChecked && (lastLat !== data.latitude || lastLng !== data.longitude)) {
mapState.map.panTo(latlng);
@@ -1341,18 +1343,22 @@ function syncUpdateBtn() {
}
}
-function fetchDeviceData(includeLang = true, range = null) {
- const params = new URLSearchParams(includeLang ? { lang: langCode } : { hashonly: 1 });
-
- // 우선순위: 직접 인자(range) > 전역 오버라이드(_overrideRange) > URL 쿼리
- const eff = range || _overrideRange || {};
- const qs = new URLSearchParams(window.location.search);
-
- const s = eff.startTime || qs.get('startTime');
- const e = eff.endTime || qs.get('endTime');
-
- if (s) params.set('startTime', s);
- if (e) params.set('endTime', e);
+function fetchDeviceData(includeLang = true, range = null) {
+ const params = new URLSearchParams(includeLang ? { lang: langCode } : { hashonly: 1 });
+
+ // 우선순위: 직접 인자(range) > 전역 오버라이드(_overrideRange)
+ const eff = range || _overrideRange || {};
+ const hasHistoryRange = includeLang && !!(eff.startTime || eff.endTime);
+
+ const s = eff.startTime;
+ const e = eff.endTime;
+
+ if (s) params.set('startTime', s);
+ if (e) params.set('endTime', e);
+ if (hasHistoryRange) {
+ params.set('history', '1');
+ params.set('maxPoints', '300');
+ }
const url = `https://chaegeon.com/custom/findmydevice/php/api.php?${params.toString()}`;
diff --git a/php/api.php b/php/api.php
index c2ef56a..810d95c 100644
--- a/php/api.php
+++ b/php/api.php
@@ -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);
?>