Support Galaxy sensor fields and remove debug file
This commit is contained in:
@@ -1,13 +1,14 @@
|
||||
# Find My Device
|
||||
|
||||
Home Assistant에 기록된 Fold7 위치, 배터리, 활동 상태, 이동 경로를 지도에 표시하는 개인용 나의 찾기 페이지입니다.
|
||||
Home Assistant에 기록된 Galaxy 기기의 위치, 배터리, 활동 상태, 이동 경로를 지도에 표시하는 개인용 나의 찾기 페이지입니다.
|
||||
|
||||
## 기능
|
||||
|
||||
- 현재 위치 지도 표시
|
||||
- GPS 정확도 원 표시
|
||||
- 최근 24시간 및 사용자 지정 기간 이동 경로 표시
|
||||
- 배터리, 워치 배터리, 활동 상태, 걸음 수, 이동 거리 표시
|
||||
- 배터리, 충전 상태, 배터리 건강, 온도, 전력, 활동 상태, 걸음 수, 이동 거리 표시
|
||||
- Wi-Fi, 모바일 데이터, 전화 상태, 화면 활성, Doze, 절전, 잠금 상태 표시
|
||||
- 자동 위치 갱신 웹훅 제어
|
||||
- 강제 업데이트 요청
|
||||
- 한국어/영어 전환
|
||||
@@ -15,7 +16,6 @@ Home Assistant에 기록된 Fold7 위치, 배터리, 활동 상태, 이동 경
|
||||
|
||||
## 구조
|
||||
|
||||
- `debug.html`: 페이지 삽입용 HTML 조각
|
||||
- `php/api.php`: Home Assistant 상태/히스토리 조회 API
|
||||
- `js/main.js`: 지도 렌더링, 상태 갱신, 히스토리 표시
|
||||
- `css/style.css`: iOS 설정 화면 스타일
|
||||
|
||||
-175
@@ -1,175 +0,0 @@
|
||||
<meta charset="UTF-8">
|
||||
<link href="/custom/findmydevice/css/style.css" rel="stylesheet" />
|
||||
<div id="mySettingContainer" style="user-select: none;">
|
||||
<div id="settingsHeader">
|
||||
<a id="blogBackLink"></a>
|
||||
<span class="headerTitle"></span>
|
||||
<div id="langToggle">
|
||||
<img src="/custom/findmydevice/img/language-empty.svg" alt="Language Toggle">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="toggleContent" style="padding-bottom:80px;">
|
||||
<div id="mapContainer">
|
||||
<div id="map" style="width:100%; height:100%;"></div>
|
||||
</div>
|
||||
|
||||
<div class="settingTitle">
|
||||
<span id="titleSettings"></span>
|
||||
</div>
|
||||
<div class="settingSection">
|
||||
<div class="settingRow" id="wakeLockRow">
|
||||
<span id="labelKeepScreenOn"></span>
|
||||
<label class="iosSwitch">
|
||||
<input type="checkbox" id="wakeLockSwitch">
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settingTitle">
|
||||
<span id="titleHistory"></span>
|
||||
</div>
|
||||
|
||||
<div class="settingSection">
|
||||
<div class="settingRow">
|
||||
<span id="labelStartTime"></span>
|
||||
<input type="datetime-local" id="rangeStart" class="dth">
|
||||
</div>
|
||||
<div class="settingRow">
|
||||
<span id="labelEndTime"></span>
|
||||
<input type="datetime-local" id="rangeEnd" class="dth">
|
||||
</div>
|
||||
<div class="settingRow locateRow" id="HistoryResetBtn">
|
||||
<span style="color:#007AFF; cursor:default;" id="labelHistoryReset"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settingTitle">
|
||||
<span id="titleLocation"></span>
|
||||
</div>
|
||||
<div class="settingSection">
|
||||
<div class="settingRow">
|
||||
<span id="labelCurrentPosition"></span>
|
||||
<span id="currentPosition"></span>
|
||||
</div>
|
||||
<div class="settingRow">
|
||||
<span id="labelGpsAccuracy"></span>
|
||||
<span id="accuracyValue"></span>
|
||||
</div>
|
||||
<div class="settingRow" id="autoLocateBtn">
|
||||
<span id="labelCurrentAutoPosition"></span>
|
||||
<label class="iosSwitch">
|
||||
<input type="checkbox" id="autoPositionSwitch">
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="settingRow locateRow" id="locateBtn">
|
||||
<span style="color:#007AFF; cursor:default;" id="labelMoveToCurrent"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settingTitle">
|
||||
<span id="titleUpdate"></span>
|
||||
</div>
|
||||
<div class="settingSection">
|
||||
<div class="settingRow">
|
||||
<span id="labelLastUpdate"></span>
|
||||
<span id="lastUpdateTime"></span>
|
||||
</div>
|
||||
<div class="settingRow">
|
||||
<span id="labelUpdateTrigger"></span>
|
||||
<span id="updateTrigger"></span>
|
||||
</div>
|
||||
<div class="settingRow" id="autoUpdateBtn">
|
||||
<span id="labelAutoUpdate"></span>
|
||||
<label class="iosSwitch">
|
||||
<input type="checkbox" id="autoUpdateSwitch">
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="settingRow locateRow" id="updateBtn">
|
||||
<span style="color:#007AFF; cursor:default;" id="labelForceUpdate"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settingTitle">
|
||||
<span id="titleBattery"></span>
|
||||
</div>
|
||||
<div class="settingSection">
|
||||
<div class="settingRow">
|
||||
<span id="labelBatteryState"></span>
|
||||
<span id="batteryStatus"></span>
|
||||
</div>
|
||||
<div class="settingRow">
|
||||
<span id="labelBatteryLevel"></span>
|
||||
<span id="Battery"></span>
|
||||
</div>
|
||||
<div class="settingRow">
|
||||
<span id="labelWatchBatteryState"></span>
|
||||
<span id="WbatteryStatus"></span>
|
||||
</div>
|
||||
<div class="settingRow">
|
||||
<span id="labelWatchBatteryLevel"></span>
|
||||
<span id="WBattery"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settingTitle">
|
||||
<span id="titleActivity"></span>
|
||||
</div>
|
||||
<div class="settingSection">
|
||||
<div class="settingRow">
|
||||
<span id="labelCurrentActivity"></span>
|
||||
<span id="nowActivity"></span>
|
||||
</div>
|
||||
<div class="settingRow" id="existnowActivity">
|
||||
<span id="labelActivityAccuracy"></span>
|
||||
<span id="nowactivityAccuracy"></span>
|
||||
</div>
|
||||
<div class="settingRow">
|
||||
<span id="labelDistance"></span>
|
||||
<span id="todayDistance"></span>
|
||||
</div>
|
||||
<div class="settingRow">
|
||||
<span id="labelSteps"></span>
|
||||
<span id="todaySteps"></span>
|
||||
</div>
|
||||
<div class="settingRow">
|
||||
<span id="labelAscended"></span>
|
||||
<span id="todayAscended"></span>
|
||||
</div>
|
||||
<div class="settingRow">
|
||||
<span id="labelDescended"></span>
|
||||
<span id="todayDescended"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settingTitle">
|
||||
<span id="titleEtc"></span>
|
||||
</div>
|
||||
<div class="settingSection">
|
||||
<div class="settingRow">
|
||||
<span id="labelConnection"></span>
|
||||
<span id="connectionType"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="ios-pwa-tooltip">
|
||||
<span class="close-btn" id="close-tooltip">✕</span>
|
||||
<span
|
||||
style="width:18px;height:18px;display:inline-block;vertical-align:middle;margin-right:5px;transform:translateZ(0);will-change:transform;">
|
||||
<svg height="18" viewbox="0 0 50 50" width="18" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M30.3 13.7L25 8.4l-5.3 5.3-1.4-1.4L25 5.6l6.7 6.7z"></path>
|
||||
<path d="M24 7h2v21h-2z"></path>
|
||||
<path
|
||||
d="M35 40H15c-1.7 0-3-1.3-3-3V19c0-1.7 1.3-3 3-3h7v2h-7c-.6 0-1 .4-1 1v18c0 .6.4 1 1 1h20c.6 0 1-.4 1-1V19c0-.6-.4-1-1-1h-7v-2h7c1.7 0 3 1.3 3 3v18c0 1.7-1.3 3-3 3z">
|
||||
</path>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<script src="https://oapi.map.naver.com/openapi/v3/maps.js?ncpKeyId=g3btu2qfeb"></script>
|
||||
<script src="/custom/findmydevice/js/main.js"></script>
|
||||
+186
-45
@@ -15,21 +15,39 @@ const deviceLang = (navigator.language || navigator.userLanguage || "en").starts
|
||||
autoCurrentUpdate: "현재 위치 자동 이동",
|
||||
autoUpdate: "자동 업데이트",
|
||||
forceUpdate: "업데이트 하기",
|
||||
battery: "배터리",
|
||||
batteryState: "배터리 상태",
|
||||
batteryLevel: "배터리 잔량",
|
||||
watchbatteryState: "워치 배터리 상태",
|
||||
watchbatteryLevel: "워치 배터리 잔량",
|
||||
activity: "활동",
|
||||
currentActivity: "현재 활동",
|
||||
activityAccuracy: "활동 정확도",
|
||||
battery: "배터리",
|
||||
batteryState: "배터리 상태",
|
||||
batteryLevel: "배터리 잔량",
|
||||
watchbatteryState: "워치 배터리 상태",
|
||||
watchbatteryLevel: "워치 배터리 잔량",
|
||||
isCharging: "충전 여부",
|
||||
chargerType: "충전 방식",
|
||||
batteryHealth: "배터리 건강",
|
||||
batteryTemperature: "배터리 온도",
|
||||
batteryPower: "배터리 전력",
|
||||
remainingChargeTime: "충전 남은 시간",
|
||||
batteryCycleCount: "배터리 사이클",
|
||||
activity: "활동",
|
||||
currentActivity: "현재 활동",
|
||||
activityAccuracy: "활동 정확도",
|
||||
distance: "도보로 움직인 거리",
|
||||
steps: "걸음 수",
|
||||
ascended: "오른 계단",
|
||||
descended: "내려간 계단",
|
||||
etc: "기타",
|
||||
connection: "연결 방법",
|
||||
editPage: "페이지 수정",
|
||||
etc: "기타",
|
||||
connection: "연결 방법",
|
||||
wifiConnection: "Wi-Fi",
|
||||
wifiIp: "Wi-Fi IP",
|
||||
wifiLinkSpeed: "Wi-Fi 속도",
|
||||
wifiFrequency: "Wi-Fi 주파수",
|
||||
wifiSignalStrength: "Wi-Fi 신호",
|
||||
mobileData: "모바일 데이터",
|
||||
phoneState: "전화 상태",
|
||||
interactive: "화면 활성",
|
||||
dozeMode: "Doze 모드",
|
||||
powerSave: "절전 모드",
|
||||
deviceLocked: "잠금 상태",
|
||||
editPage: "페이지 수정",
|
||||
pwa: "눌러 홈 화면에 추가해 바로 보기",
|
||||
history: "기록",
|
||||
startTime: "시작",
|
||||
@@ -50,21 +68,39 @@ const deviceLang = (navigator.language || navigator.userLanguage || "en").starts
|
||||
autoCurrentUpdate: "Current Location Auto Update",
|
||||
autoUpdate: "Auto Update",
|
||||
forceUpdate: "Force Update",
|
||||
battery: "Battery",
|
||||
batteryState: "Battery State",
|
||||
batteryLevel: "Battery Level",
|
||||
watchbatteryState: "Watch Battery State",
|
||||
watchbatteryLevel: "Watch Battery Level",
|
||||
activity: "Activity",
|
||||
battery: "Battery",
|
||||
batteryState: "Battery State",
|
||||
batteryLevel: "Battery Level",
|
||||
watchbatteryState: "Watch Battery State",
|
||||
watchbatteryLevel: "Watch Battery Level",
|
||||
isCharging: "Charging",
|
||||
chargerType: "Charger Type",
|
||||
batteryHealth: "Battery Health",
|
||||
batteryTemperature: "Battery Temp",
|
||||
batteryPower: "Battery Power",
|
||||
remainingChargeTime: "Remaining Charge",
|
||||
batteryCycleCount: "Battery Cycles",
|
||||
activity: "Activity",
|
||||
currentActivity: "Current Activity",
|
||||
activityAccuracy: "Activity Confidence",
|
||||
distance: "Distance walked",
|
||||
steps: "Steps",
|
||||
ascended: "Floors Ascended",
|
||||
descended: "Floors Descended",
|
||||
etc: "Others",
|
||||
connection: "Network Type",
|
||||
editPage: "Edit Page",
|
||||
etc: "Others",
|
||||
connection: "Network Type",
|
||||
wifiConnection: "Wi-Fi",
|
||||
wifiIp: "Wi-Fi IP",
|
||||
wifiLinkSpeed: "Wi-Fi Speed",
|
||||
wifiFrequency: "Wi-Fi Frequency",
|
||||
wifiSignalStrength: "Wi-Fi Signal",
|
||||
mobileData: "Mobile Data",
|
||||
phoneState: "Phone State",
|
||||
interactive: "Interactive",
|
||||
dozeMode: "Doze Mode",
|
||||
powerSave: "Power Save",
|
||||
deviceLocked: "Device Locked",
|
||||
editPage: "Edit Page",
|
||||
pwa: "Add to Home Screen for quick access!",
|
||||
history: "History",
|
||||
startTime: "Start",
|
||||
@@ -538,29 +574,133 @@ function setCookieHours(name, value, hours) {
|
||||
document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}; expires=${d.toUTCString()}; path=/; SameSite=Lax`;
|
||||
}
|
||||
|
||||
function updateDOM(data) {
|
||||
const textMap = {
|
||||
currentPosition: data.address,
|
||||
function updateDOM(data) {
|
||||
const textMap = {
|
||||
currentPosition: data.address,
|
||||
lastUpdateTime: data.updated ? getRelativeTime(data.updated) : "-",
|
||||
nowActivity: data.activity,
|
||||
connectionType: data.connection,
|
||||
updateTrigger: data.trigger,
|
||||
batteryStatus: data.state,
|
||||
Battery: data.level > 0 ? `${data.level}%` : "-",
|
||||
WbatteryStatus: data.wstate,
|
||||
WBattery: data.wlevel > 0 ? `${data.wlevel}%` : "-",
|
||||
todaySteps: data.steps > 0 ? `${formatNumber(data.steps)}${langCode === "ko" ? "걸음" : "steps"}` : "-",
|
||||
batteryStatus: data.state,
|
||||
Battery: data.level > 0 ? `${data.level}%` : "-",
|
||||
WbatteryStatus: data.wstate,
|
||||
WBattery: data.wlevel > 0 ? `${data.wlevel}%` : "-",
|
||||
todaySteps: data.steps > 0 ? `${formatNumber(data.steps)}${langCode === "ko" ? "걸음" : "steps"}` : "-",
|
||||
todayAscended: data.ascended > 0 ? `${formatNumber(data.ascended)}${langCode === "ko" ? "층" : "floors"}` : "-",
|
||||
todayDescended: data.descended > 0 ? `${formatNumber(data.descended)}${langCode === "ko" ? "층" : "floors"}` : "-",
|
||||
todayDistance: data.distance > 0 ? `${formatNumber(data.distance)}m` : "-",
|
||||
accuracyValue: data.accuracy > 0 ? `${formatNumber(data.accuracy)}m` : "-"
|
||||
};
|
||||
|
||||
for (const [id, value] of Object.entries(textMap)) {
|
||||
const el = document.getElementById(id);
|
||||
if (el) el.textContent = value || "-";
|
||||
}
|
||||
}
|
||||
for (const [id, value] of Object.entries(textMap)) {
|
||||
const el = document.getElementById(id);
|
||||
if (el) el.textContent = value || "-";
|
||||
}
|
||||
|
||||
toggleOptionalRow('WbatteryStatus', data.wstate);
|
||||
toggleOptionalRow('WBattery', data.wlevel);
|
||||
updateGalaxyRows(data.galaxy || {});
|
||||
}
|
||||
|
||||
function updateGalaxyRows(galaxy) {
|
||||
const t = translations[langCode] || translations.ko;
|
||||
const rows = [
|
||||
['isCharging', t.isCharging, galaxy.isCharging],
|
||||
['chargerType', t.chargerType, galaxy.chargerType],
|
||||
['batteryHealth', t.batteryHealth, galaxy.batteryHealth],
|
||||
['batteryTemperature', t.batteryTemperature, formatWithUnit(galaxy.batteryTemperature, '°C')],
|
||||
['batteryPower', t.batteryPower, formatWithUnit(galaxy.batteryPower, 'W')],
|
||||
['remainingChargeTime', t.remainingChargeTime, formatDuration(galaxy.remainingChargeTime)],
|
||||
['batteryCycleCount', t.batteryCycleCount, galaxy.batteryCycleCount],
|
||||
['wifiConnection', t.wifiConnection, galaxy.wifiConnection],
|
||||
['wifiIp', t.wifiIp, galaxy.wifiIp],
|
||||
['wifiLinkSpeed', t.wifiLinkSpeed, formatWithUnit(galaxy.wifiLinkSpeed, 'Mbps')],
|
||||
['wifiFrequency', t.wifiFrequency, formatWithUnit(galaxy.wifiFrequency, 'MHz')],
|
||||
['wifiSignalStrength', t.wifiSignalStrength, formatWithUnit(galaxy.wifiSignalStrength, 'dBm')],
|
||||
['mobileData', t.mobileData, galaxy.mobileData],
|
||||
['phoneState', t.phoneState, galaxy.phoneState],
|
||||
['interactive', t.interactive, galaxy.interactive],
|
||||
['dozeMode', t.dozeMode, galaxy.dozeMode],
|
||||
['powerSave', t.powerSave, galaxy.powerSave],
|
||||
['deviceLocked', t.deviceLocked, galaxy.deviceLocked],
|
||||
];
|
||||
|
||||
rows.forEach(([key, label, value]) => {
|
||||
const targetSection = ['isCharging', 'chargerType', 'batteryHealth', 'batteryTemperature', 'batteryPower', 'remainingChargeTime', 'batteryCycleCount'].includes(key)
|
||||
? findSectionByTitle(langCode === 'ko' ? '배터리' : 'Battery')
|
||||
: findSectionByTitle(langCode === 'ko' ? '기타' : 'Others');
|
||||
upsertOptionalRow(targetSection, `galaxy_${key}`, label, value);
|
||||
});
|
||||
}
|
||||
|
||||
function findSectionByTitle(titleText) {
|
||||
const titles = document.querySelectorAll('.settingTitle');
|
||||
for (const title of titles) {
|
||||
if (title.textContent.trim() === titleText) {
|
||||
const section = title.nextElementSibling;
|
||||
if (section && section.classList.contains('settingSection')) return section;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function upsertOptionalRow(section, id, label, value) {
|
||||
if (!section) return;
|
||||
const displayValue = normalizeDisplayValue(value);
|
||||
let row = document.getElementById(id);
|
||||
|
||||
if (!displayValue) {
|
||||
if (row) row.remove();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!row) {
|
||||
row = document.createElement('div');
|
||||
row.className = 'settingRow';
|
||||
row.id = id;
|
||||
row.innerHTML = `<span></span><span></span>`;
|
||||
section.appendChild(row);
|
||||
}
|
||||
|
||||
row.children[0].textContent = label;
|
||||
row.children[1].textContent = displayValue;
|
||||
}
|
||||
|
||||
function toggleOptionalRow(valueId, value) {
|
||||
const el = document.getElementById(valueId);
|
||||
const row = el?.closest('.settingRow');
|
||||
if (!row) return;
|
||||
row.style.display = normalizeDisplayValue(value) ? '' : 'none';
|
||||
}
|
||||
|
||||
function normalizeDisplayValue(value) {
|
||||
if (value === null || typeof value === 'undefined' || value === '' || value === '-' || value === 'unknown' || value === 'unavailable') {
|
||||
return '';
|
||||
}
|
||||
return String(value);
|
||||
}
|
||||
|
||||
function formatWithUnit(value, unit) {
|
||||
const normalized = normalizeDisplayValue(value);
|
||||
if (!normalized) return '';
|
||||
return /[a-zA-Z%°]/.test(normalized) ? normalized : `${normalized}${unit}`;
|
||||
}
|
||||
|
||||
function formatDuration(value) {
|
||||
const normalized = normalizeDisplayValue(value);
|
||||
if (!normalized) return '';
|
||||
const seconds = Number(normalized);
|
||||
if (!Number.isFinite(seconds)) return normalized;
|
||||
if (seconds <= 0) return '0s';
|
||||
const h = Math.floor(seconds / 3600);
|
||||
const m = Math.floor((seconds % 3600) / 60);
|
||||
const s = Math.floor(seconds % 60);
|
||||
return [
|
||||
h ? `${h}h` : '',
|
||||
m ? `${m}m` : '',
|
||||
s && !h ? `${s}s` : '',
|
||||
].filter(Boolean).join(' ');
|
||||
}
|
||||
|
||||
function updateActivitySection(data) {
|
||||
const validActivities = ["Stationary", "Walking", "Running", "In Vehicle", "Cycling", "가만히 있음", "걷는 중", "달리는 중", "차량 이동 중", "자전거 이동 중"];
|
||||
@@ -1006,18 +1146,19 @@ function debounce(fn, delay = 400) {
|
||||
};
|
||||
}
|
||||
|
||||
function resetDisplayValues() {
|
||||
const ids = [
|
||||
'currentPosition', 'accuracyValue', 'lastUpdateTime', 'updateTrigger',
|
||||
'batteryStatus', 'Battery', 'WbatteryStatus', 'WBattery',
|
||||
'nowActivity', 'nowactivityAccuracy', 'todayDistance',
|
||||
'todaySteps', 'todayAscended', 'todayDescended', 'connectionType'
|
||||
];
|
||||
ids.forEach(id => {
|
||||
const el = document.getElementById(id);
|
||||
if (el) el.textContent = '-';
|
||||
});
|
||||
}
|
||||
function resetDisplayValues() {
|
||||
const ids = [
|
||||
'currentPosition', 'accuracyValue', 'lastUpdateTime', 'updateTrigger',
|
||||
'batteryStatus', 'Battery', 'WbatteryStatus', 'WBattery',
|
||||
'nowActivity', 'nowactivityAccuracy', 'todayDistance',
|
||||
'todaySteps', 'todayAscended', 'todayDescended', 'connectionType'
|
||||
];
|
||||
ids.forEach(id => {
|
||||
const el = document.getElementById(id);
|
||||
if (el) el.textContent = '-';
|
||||
});
|
||||
document.querySelectorAll('[id^="galaxy_"]').forEach(row => row.remove());
|
||||
}
|
||||
|
||||
function triggerForceUpdate() {
|
||||
callDeviceWebhook('force_update');
|
||||
|
||||
+105
-10
@@ -42,8 +42,6 @@ $deviceUrl = $entities['device'];
|
||||
$geoUrl = $entities['geo'];
|
||||
$batteryStateUrl = $entities['battery_state'];
|
||||
$batteryLevelUrl = $entities['battery_level'];
|
||||
$watchBatteryStateUrl = $entities['watch_battery_state'];
|
||||
$watchBatteryLevelUrl = $entities['watch_battery_level'];
|
||||
$activityUrl = $entities['activity'];
|
||||
$connectionUrl = $entities['connection'];
|
||||
$fascendedUrl = $entities['daily_floors'];
|
||||
@@ -77,13 +75,39 @@ function getData($entityId) {
|
||||
return custom_ha_state('seoul', $entityId) ?? [];
|
||||
}
|
||||
|
||||
function getEntityData($entities, $key) {
|
||||
if (empty($entities[$key])) {
|
||||
return [];
|
||||
}
|
||||
return getData($entities[$key]);
|
||||
}
|
||||
|
||||
function stateValue($data) {
|
||||
$state = $data['state'] ?? null;
|
||||
if ($state === null || $state === 'unknown' || $state === 'unavailable' || $state === '<not connected>' || $state === '<not available>') {
|
||||
return null;
|
||||
}
|
||||
return $state;
|
||||
}
|
||||
|
||||
function boolText($state, $lang) {
|
||||
if ($state === null) return null;
|
||||
if ($lang === 'ko') {
|
||||
return $state === 'on' ? '켜짐' : ($state === 'off' ? '꺼짐' : $state);
|
||||
}
|
||||
return $state === 'on' ? 'On' : ($state === 'off' ? 'Off' : $state);
|
||||
}
|
||||
|
||||
function mappedText($state, $lang, $map) {
|
||||
if ($state === null) return null;
|
||||
return $map[$lang][$state] ?? $map[$lang][strtolower((string)$state)] ?? $state;
|
||||
}
|
||||
|
||||
// 데이터 불러오기
|
||||
$deviceData = getData($deviceUrl);
|
||||
$geoData = getData($geoUrl);
|
||||
$batteryStateData = getData($batteryStateUrl);
|
||||
$batteryLevelData = getData($batteryLevelUrl);
|
||||
$watchBatteryStateData = getData($watchBatteryStateUrl);
|
||||
$watchBatteryLevelData = getData($watchBatteryLevelUrl);
|
||||
$activityData = getData($activityUrl);
|
||||
$connectionData = getData($connectionUrl);
|
||||
$ascendedData = getData($fascendedUrl);
|
||||
@@ -92,6 +116,27 @@ $stepsData = getData($stepsUrl);
|
||||
$distanceData = getData($distanceUrl);
|
||||
$historyData = custom_ha_request('seoul', 'GET', $historyEndpoint) ?? [];
|
||||
$triggerData = getData($triggerUrl);
|
||||
$galaxyData = [
|
||||
'isCharging' => getEntityData($entities, 'is_charging'),
|
||||
'chargerType' => getEntityData($entities, 'charger_type'),
|
||||
'batteryHealth' => getEntityData($entities, 'battery_health'),
|
||||
'batteryTemperature' => getEntityData($entities, 'battery_temperature'),
|
||||
'batteryPower' => getEntityData($entities, 'battery_power'),
|
||||
'remainingChargeTime' => getEntityData($entities, 'remaining_charge_time'),
|
||||
'batteryCycleCount' => getEntityData($entities, 'battery_cycle_count'),
|
||||
'wifiConnection' => getEntityData($entities, 'wifi_connection'),
|
||||
'wifiBssid' => getEntityData($entities, 'wifi_bssid'),
|
||||
'wifiIp' => getEntityData($entities, 'wifi_ip'),
|
||||
'wifiLinkSpeed' => getEntityData($entities, 'wifi_link_speed'),
|
||||
'wifiFrequency' => getEntityData($entities, 'wifi_frequency'),
|
||||
'wifiSignalStrength' => getEntityData($entities, 'wifi_signal_strength'),
|
||||
'mobileData' => getEntityData($entities, 'mobile_data'),
|
||||
'phoneState' => getEntityData($entities, 'phone_state'),
|
||||
'interactive' => getEntityData($entities, 'interactive'),
|
||||
'dozeMode' => getEntityData($entities, 'doze_mode'),
|
||||
'powerSave' => getEntityData($entities, 'power_save'),
|
||||
'deviceLocked' => getEntityData($entities, 'device_locked'),
|
||||
];
|
||||
|
||||
// 경로 기록
|
||||
$path = [];
|
||||
@@ -160,18 +205,43 @@ $connectionMap = [
|
||||
$batteryStateMap = [
|
||||
'ko' => [
|
||||
'Charging' => '충전 중',
|
||||
'Discharging' => '방전 중',
|
||||
'Not Charging' => '충전중이 아님',
|
||||
'Full' => '가득참',
|
||||
'charging' => '충전 중',
|
||||
'discharging' => '방전 중',
|
||||
'not_charging' => '충전중이 아님',
|
||||
'full' => '가득참',
|
||||
'Unknown' => '-',
|
||||
],
|
||||
'en' => [
|
||||
'Charging' => 'Charging',
|
||||
'Discharging' => 'Discharging',
|
||||
'Not Charging' => 'Not Charging',
|
||||
'Full' => 'Full',
|
||||
'charging' => 'Charging',
|
||||
'discharging' => 'Discharging',
|
||||
'not_charging' => 'Not Charging',
|
||||
'full' => 'Full',
|
||||
'Unknown' => '-',
|
||||
]
|
||||
];
|
||||
|
||||
$androidValueMap = [
|
||||
'chargerType' => [
|
||||
'ko' => ['none' => '없음', 'wireless' => '무선', 'ac' => '유선', 'usb' => 'USB'],
|
||||
'en' => ['none' => 'None', 'wireless' => 'Wireless', 'ac' => 'AC', 'usb' => 'USB'],
|
||||
],
|
||||
'batteryHealth' => [
|
||||
'ko' => ['good' => '양호', 'overheat' => '과열', 'dead' => '불량', 'over_voltage' => '과전압', 'cold' => '저온'],
|
||||
'en' => ['good' => 'Good', 'overheat' => 'Overheat', 'dead' => 'Dead', 'over_voltage' => 'Over voltage', 'cold' => 'Cold'],
|
||||
],
|
||||
'phoneState' => [
|
||||
'ko' => ['idle' => '대기', 'ringing' => '수신 중', 'offhook' => '통화 중'],
|
||||
'en' => ['idle' => 'Idle', 'ringing' => 'Ringing', 'offhook' => 'Off hook'],
|
||||
],
|
||||
];
|
||||
|
||||
$triggerMap = [
|
||||
'ko' => [
|
||||
"Launch" => "앱 실행",
|
||||
@@ -202,10 +272,9 @@ $triggerMap = [
|
||||
];
|
||||
|
||||
$activityState = $activityData['state'] ?? null;
|
||||
$aaccuracyState = $activityData['attributes']['Confidence'] ?? null;
|
||||
$aaccuracyState = $activityData['attributes']['Confidence'] ?? $activityData['attributes']['confidence'] ?? null;
|
||||
$connectionState = $connectionData['state'] ?? null;
|
||||
$batteryState = $batteryStateData['state'] ?? null;
|
||||
$watchBatteryState = $watchBatteryStateData['state'] ?? null;
|
||||
|
||||
$address = null;
|
||||
|
||||
@@ -224,6 +293,34 @@ if (($deviceData['state'] ?? null) !== 'not_home') {
|
||||
$address = trim($locality . ' ' . ($geoData['attributes']['Name'] ?? ''));
|
||||
}
|
||||
|
||||
$galaxy = [
|
||||
'isCharging' => boolText(stateValue($galaxyData['isCharging']), $lang),
|
||||
'chargerType' => mappedText(stateValue($galaxyData['chargerType']), $lang, $androidValueMap['chargerType']),
|
||||
'batteryHealth' => mappedText(stateValue($galaxyData['batteryHealth']), $lang, $androidValueMap['batteryHealth']),
|
||||
'batteryTemperature' => stateValue($galaxyData['batteryTemperature']),
|
||||
'batteryPower' => stateValue($galaxyData['batteryPower']),
|
||||
'remainingChargeTime' => stateValue($galaxyData['remainingChargeTime']),
|
||||
'batteryCycleCount' => stateValue($galaxyData['batteryCycleCount']),
|
||||
'wifiConnection' => stateValue($galaxyData['wifiConnection']),
|
||||
'wifiBssid' => stateValue($galaxyData['wifiBssid']),
|
||||
'wifiIp' => stateValue($galaxyData['wifiIp']),
|
||||
'wifiLinkSpeed' => stateValue($galaxyData['wifiLinkSpeed']),
|
||||
'wifiFrequency' => stateValue($galaxyData['wifiFrequency']),
|
||||
'wifiSignalStrength' => stateValue($galaxyData['wifiSignalStrength']),
|
||||
'mobileData' => boolText(stateValue($galaxyData['mobileData']), $lang),
|
||||
'phoneState' => mappedText(stateValue($galaxyData['phoneState']), $lang, $androidValueMap['phoneState']),
|
||||
'interactive' => boolText(stateValue($galaxyData['interactive']), $lang),
|
||||
'dozeMode' => boolText(stateValue($galaxyData['dozeMode']), $lang),
|
||||
'powerSave' => boolText(stateValue($galaxyData['powerSave']), $lang),
|
||||
'deviceLocked' => boolText(stateValue($galaxyData['deviceLocked']), $lang),
|
||||
];
|
||||
|
||||
if (!$galaxy['wifiConnection']) {
|
||||
foreach (['wifiBssid', 'wifiIp', 'wifiLinkSpeed', 'wifiFrequency', 'wifiSignalStrength'] as $wifiKey) {
|
||||
$galaxy[$wifiKey] = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 해시 요청만 들어온 경우
|
||||
if (isset($_GET['hashonly']) && $_GET['hashonly'] == '1') {
|
||||
$raw = json_encode([
|
||||
@@ -235,8 +332,6 @@ if (isset($_GET['hashonly']) && $_GET['hashonly'] == '1') {
|
||||
'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,
|
||||
@@ -244,6 +339,7 @@ if (isset($_GET['hashonly']) && $_GET['hashonly'] == '1') {
|
||||
'activity' => $activityMap[$lang][$activityState] ?? $activityState ?? null,
|
||||
'aaccuracy' => $aaccuracyMap[$lang][$aaccuracyState] ?? $aaccuracyState ?? null,
|
||||
'connection' => $connectionMap[$lang][$connectionState] ?? $connectionState ?? null,
|
||||
'galaxy' => $galaxy,
|
||||
]);
|
||||
|
||||
echo json_encode(['hash' => hash('sha256', $raw)]);
|
||||
@@ -259,8 +355,6 @@ echo json_encode([
|
||||
'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,
|
||||
@@ -268,6 +362,7 @@ echo json_encode([
|
||||
'activity' => $activityMap[$lang][$activityState] ?? $activityState ?? null,
|
||||
'aaccuracy' => $aaccuracyMap[$lang][$aaccuracyState] ?? $aaccuracyState ?? null,
|
||||
'connection' => $connectionMap[$lang][$connectionState] ?? $connectionState ?? null,
|
||||
'galaxy' => $galaxy,
|
||||
'history' => $path ?? null,
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
?>
|
||||
|
||||
Reference in New Issue
Block a user