Support Galaxy sensor fields and remove debug file
This commit is contained in:
@@ -1,13 +1,14 @@
|
|||||||
# Find My Device
|
# Find My Device
|
||||||
|
|
||||||
Home Assistant에 기록된 Fold7 위치, 배터리, 활동 상태, 이동 경로를 지도에 표시하는 개인용 나의 찾기 페이지입니다.
|
Home Assistant에 기록된 Galaxy 기기의 위치, 배터리, 활동 상태, 이동 경로를 지도에 표시하는 개인용 나의 찾기 페이지입니다.
|
||||||
|
|
||||||
## 기능
|
## 기능
|
||||||
|
|
||||||
- 현재 위치 지도 표시
|
- 현재 위치 지도 표시
|
||||||
- GPS 정확도 원 표시
|
- GPS 정확도 원 표시
|
||||||
- 최근 24시간 및 사용자 지정 기간 이동 경로 표시
|
- 최근 24시간 및 사용자 지정 기간 이동 경로 표시
|
||||||
- 배터리, 워치 배터리, 활동 상태, 걸음 수, 이동 거리 표시
|
- 배터리, 충전 상태, 배터리 건강, 온도, 전력, 활동 상태, 걸음 수, 이동 거리 표시
|
||||||
|
- Wi-Fi, 모바일 데이터, 전화 상태, 화면 활성, Doze, 절전, 잠금 상태 표시
|
||||||
- 자동 위치 갱신 웹훅 제어
|
- 자동 위치 갱신 웹훅 제어
|
||||||
- 강제 업데이트 요청
|
- 강제 업데이트 요청
|
||||||
- 한국어/영어 전환
|
- 한국어/영어 전환
|
||||||
@@ -15,7 +16,6 @@ Home Assistant에 기록된 Fold7 위치, 배터리, 활동 상태, 이동 경
|
|||||||
|
|
||||||
## 구조
|
## 구조
|
||||||
|
|
||||||
- `debug.html`: 페이지 삽입용 HTML 조각
|
|
||||||
- `php/api.php`: Home Assistant 상태/히스토리 조회 API
|
- `php/api.php`: Home Assistant 상태/히스토리 조회 API
|
||||||
- `js/main.js`: 지도 렌더링, 상태 갱신, 히스토리 표시
|
- `js/main.js`: 지도 렌더링, 상태 갱신, 히스토리 표시
|
||||||
- `css/style.css`: iOS 설정 화면 스타일
|
- `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: "현재 위치 자동 이동",
|
autoCurrentUpdate: "현재 위치 자동 이동",
|
||||||
autoUpdate: "자동 업데이트",
|
autoUpdate: "자동 업데이트",
|
||||||
forceUpdate: "업데이트 하기",
|
forceUpdate: "업데이트 하기",
|
||||||
battery: "배터리",
|
battery: "배터리",
|
||||||
batteryState: "배터리 상태",
|
batteryState: "배터리 상태",
|
||||||
batteryLevel: "배터리 잔량",
|
batteryLevel: "배터리 잔량",
|
||||||
watchbatteryState: "워치 배터리 상태",
|
watchbatteryState: "워치 배터리 상태",
|
||||||
watchbatteryLevel: "워치 배터리 잔량",
|
watchbatteryLevel: "워치 배터리 잔량",
|
||||||
activity: "활동",
|
isCharging: "충전 여부",
|
||||||
currentActivity: "현재 활동",
|
chargerType: "충전 방식",
|
||||||
activityAccuracy: "활동 정확도",
|
batteryHealth: "배터리 건강",
|
||||||
|
batteryTemperature: "배터리 온도",
|
||||||
|
batteryPower: "배터리 전력",
|
||||||
|
remainingChargeTime: "충전 남은 시간",
|
||||||
|
batteryCycleCount: "배터리 사이클",
|
||||||
|
activity: "활동",
|
||||||
|
currentActivity: "현재 활동",
|
||||||
|
activityAccuracy: "활동 정확도",
|
||||||
distance: "도보로 움직인 거리",
|
distance: "도보로 움직인 거리",
|
||||||
steps: "걸음 수",
|
steps: "걸음 수",
|
||||||
ascended: "오른 계단",
|
ascended: "오른 계단",
|
||||||
descended: "내려간 계단",
|
descended: "내려간 계단",
|
||||||
etc: "기타",
|
etc: "기타",
|
||||||
connection: "연결 방법",
|
connection: "연결 방법",
|
||||||
editPage: "페이지 수정",
|
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: "눌러 홈 화면에 추가해 바로 보기",
|
pwa: "눌러 홈 화면에 추가해 바로 보기",
|
||||||
history: "기록",
|
history: "기록",
|
||||||
startTime: "시작",
|
startTime: "시작",
|
||||||
@@ -50,21 +68,39 @@ const deviceLang = (navigator.language || navigator.userLanguage || "en").starts
|
|||||||
autoCurrentUpdate: "Current Location Auto Update",
|
autoCurrentUpdate: "Current Location Auto Update",
|
||||||
autoUpdate: "Auto Update",
|
autoUpdate: "Auto Update",
|
||||||
forceUpdate: "Force Update",
|
forceUpdate: "Force Update",
|
||||||
battery: "Battery",
|
battery: "Battery",
|
||||||
batteryState: "Battery State",
|
batteryState: "Battery State",
|
||||||
batteryLevel: "Battery Level",
|
batteryLevel: "Battery Level",
|
||||||
watchbatteryState: "Watch Battery State",
|
watchbatteryState: "Watch Battery State",
|
||||||
watchbatteryLevel: "Watch Battery Level",
|
watchbatteryLevel: "Watch Battery Level",
|
||||||
activity: "Activity",
|
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",
|
currentActivity: "Current Activity",
|
||||||
activityAccuracy: "Activity Confidence",
|
activityAccuracy: "Activity Confidence",
|
||||||
distance: "Distance walked",
|
distance: "Distance walked",
|
||||||
steps: "Steps",
|
steps: "Steps",
|
||||||
ascended: "Floors Ascended",
|
ascended: "Floors Ascended",
|
||||||
descended: "Floors Descended",
|
descended: "Floors Descended",
|
||||||
etc: "Others",
|
etc: "Others",
|
||||||
connection: "Network Type",
|
connection: "Network Type",
|
||||||
editPage: "Edit Page",
|
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!",
|
pwa: "Add to Home Screen for quick access!",
|
||||||
history: "History",
|
history: "History",
|
||||||
startTime: "Start",
|
startTime: "Start",
|
||||||
@@ -538,29 +574,133 @@ function setCookieHours(name, value, hours) {
|
|||||||
document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}; expires=${d.toUTCString()}; path=/; SameSite=Lax`;
|
document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}; expires=${d.toUTCString()}; path=/; SameSite=Lax`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateDOM(data) {
|
function updateDOM(data) {
|
||||||
const textMap = {
|
const textMap = {
|
||||||
currentPosition: data.address,
|
currentPosition: data.address,
|
||||||
lastUpdateTime: data.updated ? getRelativeTime(data.updated) : "-",
|
lastUpdateTime: data.updated ? getRelativeTime(data.updated) : "-",
|
||||||
nowActivity: data.activity,
|
nowActivity: data.activity,
|
||||||
connectionType: data.connection,
|
connectionType: data.connection,
|
||||||
updateTrigger: data.trigger,
|
updateTrigger: data.trigger,
|
||||||
batteryStatus: data.state,
|
batteryStatus: data.state,
|
||||||
Battery: data.level > 0 ? `${data.level}%` : "-",
|
Battery: data.level > 0 ? `${data.level}%` : "-",
|
||||||
WbatteryStatus: data.wstate,
|
WbatteryStatus: data.wstate,
|
||||||
WBattery: data.wlevel > 0 ? `${data.wlevel}%` : "-",
|
WBattery: data.wlevel > 0 ? `${data.wlevel}%` : "-",
|
||||||
todaySteps: data.steps > 0 ? `${formatNumber(data.steps)}${langCode === "ko" ? "걸음" : "steps"}` : "-",
|
todaySteps: data.steps > 0 ? `${formatNumber(data.steps)}${langCode === "ko" ? "걸음" : "steps"}` : "-",
|
||||||
todayAscended: data.ascended > 0 ? `${formatNumber(data.ascended)}${langCode === "ko" ? "층" : "floors"}` : "-",
|
todayAscended: data.ascended > 0 ? `${formatNumber(data.ascended)}${langCode === "ko" ? "층" : "floors"}` : "-",
|
||||||
todayDescended: data.descended > 0 ? `${formatNumber(data.descended)}${langCode === "ko" ? "층" : "floors"}` : "-",
|
todayDescended: data.descended > 0 ? `${formatNumber(data.descended)}${langCode === "ko" ? "층" : "floors"}` : "-",
|
||||||
todayDistance: data.distance > 0 ? `${formatNumber(data.distance)}m` : "-",
|
todayDistance: data.distance > 0 ? `${formatNumber(data.distance)}m` : "-",
|
||||||
accuracyValue: data.accuracy > 0 ? `${formatNumber(data.accuracy)}m` : "-"
|
accuracyValue: data.accuracy > 0 ? `${formatNumber(data.accuracy)}m` : "-"
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const [id, value] of Object.entries(textMap)) {
|
for (const [id, value] of Object.entries(textMap)) {
|
||||||
const el = document.getElementById(id);
|
const el = document.getElementById(id);
|
||||||
if (el) el.textContent = value || "-";
|
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) {
|
function updateActivitySection(data) {
|
||||||
const validActivities = ["Stationary", "Walking", "Running", "In Vehicle", "Cycling", "가만히 있음", "걷는 중", "달리는 중", "차량 이동 중", "자전거 이동 중"];
|
const validActivities = ["Stationary", "Walking", "Running", "In Vehicle", "Cycling", "가만히 있음", "걷는 중", "달리는 중", "차량 이동 중", "자전거 이동 중"];
|
||||||
@@ -1006,18 +1146,19 @@ function debounce(fn, delay = 400) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetDisplayValues() {
|
function resetDisplayValues() {
|
||||||
const ids = [
|
const ids = [
|
||||||
'currentPosition', 'accuracyValue', 'lastUpdateTime', 'updateTrigger',
|
'currentPosition', 'accuracyValue', 'lastUpdateTime', 'updateTrigger',
|
||||||
'batteryStatus', 'Battery', 'WbatteryStatus', 'WBattery',
|
'batteryStatus', 'Battery', 'WbatteryStatus', 'WBattery',
|
||||||
'nowActivity', 'nowactivityAccuracy', 'todayDistance',
|
'nowActivity', 'nowactivityAccuracy', 'todayDistance',
|
||||||
'todaySteps', 'todayAscended', 'todayDescended', 'connectionType'
|
'todaySteps', 'todayAscended', 'todayDescended', 'connectionType'
|
||||||
];
|
];
|
||||||
ids.forEach(id => {
|
ids.forEach(id => {
|
||||||
const el = document.getElementById(id);
|
const el = document.getElementById(id);
|
||||||
if (el) el.textContent = '-';
|
if (el) el.textContent = '-';
|
||||||
});
|
});
|
||||||
}
|
document.querySelectorAll('[id^="galaxy_"]').forEach(row => row.remove());
|
||||||
|
}
|
||||||
|
|
||||||
function triggerForceUpdate() {
|
function triggerForceUpdate() {
|
||||||
callDeviceWebhook('force_update');
|
callDeviceWebhook('force_update');
|
||||||
|
|||||||
+105
-10
@@ -42,8 +42,6 @@ $deviceUrl = $entities['device'];
|
|||||||
$geoUrl = $entities['geo'];
|
$geoUrl = $entities['geo'];
|
||||||
$batteryStateUrl = $entities['battery_state'];
|
$batteryStateUrl = $entities['battery_state'];
|
||||||
$batteryLevelUrl = $entities['battery_level'];
|
$batteryLevelUrl = $entities['battery_level'];
|
||||||
$watchBatteryStateUrl = $entities['watch_battery_state'];
|
|
||||||
$watchBatteryLevelUrl = $entities['watch_battery_level'];
|
|
||||||
$activityUrl = $entities['activity'];
|
$activityUrl = $entities['activity'];
|
||||||
$connectionUrl = $entities['connection'];
|
$connectionUrl = $entities['connection'];
|
||||||
$fascendedUrl = $entities['daily_floors'];
|
$fascendedUrl = $entities['daily_floors'];
|
||||||
@@ -77,13 +75,39 @@ function getData($entityId) {
|
|||||||
return custom_ha_state('seoul', $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);
|
$deviceData = getData($deviceUrl);
|
||||||
$geoData = getData($geoUrl);
|
$geoData = getData($geoUrl);
|
||||||
$batteryStateData = getData($batteryStateUrl);
|
$batteryStateData = getData($batteryStateUrl);
|
||||||
$batteryLevelData = getData($batteryLevelUrl);
|
$batteryLevelData = getData($batteryLevelUrl);
|
||||||
$watchBatteryStateData = getData($watchBatteryStateUrl);
|
|
||||||
$watchBatteryLevelData = getData($watchBatteryLevelUrl);
|
|
||||||
$activityData = getData($activityUrl);
|
$activityData = getData($activityUrl);
|
||||||
$connectionData = getData($connectionUrl);
|
$connectionData = getData($connectionUrl);
|
||||||
$ascendedData = getData($fascendedUrl);
|
$ascendedData = getData($fascendedUrl);
|
||||||
@@ -92,6 +116,27 @@ $stepsData = getData($stepsUrl);
|
|||||||
$distanceData = getData($distanceUrl);
|
$distanceData = getData($distanceUrl);
|
||||||
$historyData = custom_ha_request('seoul', 'GET', $historyEndpoint) ?? [];
|
$historyData = custom_ha_request('seoul', 'GET', $historyEndpoint) ?? [];
|
||||||
$triggerData = getData($triggerUrl);
|
$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 = [];
|
$path = [];
|
||||||
@@ -160,18 +205,43 @@ $connectionMap = [
|
|||||||
$batteryStateMap = [
|
$batteryStateMap = [
|
||||||
'ko' => [
|
'ko' => [
|
||||||
'Charging' => '충전 중',
|
'Charging' => '충전 중',
|
||||||
|
'Discharging' => '방전 중',
|
||||||
'Not Charging' => '충전중이 아님',
|
'Not Charging' => '충전중이 아님',
|
||||||
'Full' => '가득참',
|
'Full' => '가득참',
|
||||||
|
'charging' => '충전 중',
|
||||||
|
'discharging' => '방전 중',
|
||||||
|
'not_charging' => '충전중이 아님',
|
||||||
|
'full' => '가득참',
|
||||||
'Unknown' => '-',
|
'Unknown' => '-',
|
||||||
],
|
],
|
||||||
'en' => [
|
'en' => [
|
||||||
'Charging' => 'Charging',
|
'Charging' => 'Charging',
|
||||||
|
'Discharging' => 'Discharging',
|
||||||
'Not Charging' => 'Not Charging',
|
'Not Charging' => 'Not Charging',
|
||||||
'Full' => 'Full',
|
'Full' => 'Full',
|
||||||
|
'charging' => 'Charging',
|
||||||
|
'discharging' => 'Discharging',
|
||||||
|
'not_charging' => 'Not Charging',
|
||||||
|
'full' => 'Full',
|
||||||
'Unknown' => '-',
|
'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 = [
|
$triggerMap = [
|
||||||
'ko' => [
|
'ko' => [
|
||||||
"Launch" => "앱 실행",
|
"Launch" => "앱 실행",
|
||||||
@@ -202,10 +272,9 @@ $triggerMap = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
$activityState = $activityData['state'] ?? null;
|
$activityState = $activityData['state'] ?? null;
|
||||||
$aaccuracyState = $activityData['attributes']['Confidence'] ?? null;
|
$aaccuracyState = $activityData['attributes']['Confidence'] ?? $activityData['attributes']['confidence'] ?? null;
|
||||||
$connectionState = $connectionData['state'] ?? null;
|
$connectionState = $connectionData['state'] ?? null;
|
||||||
$batteryState = $batteryStateData['state'] ?? null;
|
$batteryState = $batteryStateData['state'] ?? null;
|
||||||
$watchBatteryState = $watchBatteryStateData['state'] ?? null;
|
|
||||||
|
|
||||||
$address = null;
|
$address = null;
|
||||||
|
|
||||||
@@ -224,6 +293,34 @@ if (($deviceData['state'] ?? null) !== 'not_home') {
|
|||||||
$address = trim($locality . ' ' . ($geoData['attributes']['Name'] ?? ''));
|
$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') {
|
if (isset($_GET['hashonly']) && $_GET['hashonly'] == '1') {
|
||||||
$raw = json_encode([
|
$raw = json_encode([
|
||||||
@@ -235,8 +332,6 @@ if (isset($_GET['hashonly']) && $_GET['hashonly'] == '1') {
|
|||||||
'address' => $address,
|
'address' => $address,
|
||||||
'state' => $batteryStateMap[$lang][$batteryState] ?? $batteryState ?? null,
|
'state' => $batteryStateMap[$lang][$batteryState] ?? $batteryState ?? null,
|
||||||
'level' => $batteryLevelData['state'] ?? null,
|
'level' => $batteryLevelData['state'] ?? null,
|
||||||
'wstate' => $batteryStateMap[$lang][$watchBatteryState] ?? $watchBatteryState ?? null,
|
|
||||||
'wlevel' => $watchBatteryLevelData['state'] ?? null,
|
|
||||||
'ascended' => $ascendedData['state'] ?? null,
|
'ascended' => $ascendedData['state'] ?? null,
|
||||||
'descended' => $descendedData['state'] ?? null,
|
'descended' => $descendedData['state'] ?? null,
|
||||||
'steps' => $stepsData['state'] ?? null,
|
'steps' => $stepsData['state'] ?? null,
|
||||||
@@ -244,6 +339,7 @@ if (isset($_GET['hashonly']) && $_GET['hashonly'] == '1') {
|
|||||||
'activity' => $activityMap[$lang][$activityState] ?? $activityState ?? null,
|
'activity' => $activityMap[$lang][$activityState] ?? $activityState ?? null,
|
||||||
'aaccuracy' => $aaccuracyMap[$lang][$aaccuracyState] ?? $aaccuracyState ?? null,
|
'aaccuracy' => $aaccuracyMap[$lang][$aaccuracyState] ?? $aaccuracyState ?? null,
|
||||||
'connection' => $connectionMap[$lang][$connectionState] ?? $connectionState ?? null,
|
'connection' => $connectionMap[$lang][$connectionState] ?? $connectionState ?? null,
|
||||||
|
'galaxy' => $galaxy,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
echo json_encode(['hash' => hash('sha256', $raw)]);
|
echo json_encode(['hash' => hash('sha256', $raw)]);
|
||||||
@@ -259,8 +355,6 @@ echo json_encode([
|
|||||||
'address' => $address,
|
'address' => $address,
|
||||||
'state' => $batteryStateMap[$lang][$batteryState] ?? $batteryState ?? null,
|
'state' => $batteryStateMap[$lang][$batteryState] ?? $batteryState ?? null,
|
||||||
'level' => $batteryLevelData['state'] ?? null,
|
'level' => $batteryLevelData['state'] ?? null,
|
||||||
'wstate' => $batteryStateMap[$lang][$watchBatteryState] ?? $watchBatteryState ?? null,
|
|
||||||
'wlevel' => $watchBatteryLevelData['state'] ?? null,
|
|
||||||
'ascended' => $ascendedData['state'] ?? null,
|
'ascended' => $ascendedData['state'] ?? null,
|
||||||
'descended' => $descendedData['state'] ?? null,
|
'descended' => $descendedData['state'] ?? null,
|
||||||
'steps' => $stepsData['state'] ?? null,
|
'steps' => $stepsData['state'] ?? null,
|
||||||
@@ -268,6 +362,7 @@ echo json_encode([
|
|||||||
'activity' => $activityMap[$lang][$activityState] ?? $activityState ?? null,
|
'activity' => $activityMap[$lang][$activityState] ?? $activityState ?? null,
|
||||||
'aaccuracy' => $aaccuracyMap[$lang][$aaccuracyState] ?? $aaccuracyState ?? null,
|
'aaccuracy' => $aaccuracyMap[$lang][$aaccuracyState] ?? $aaccuracyState ?? null,
|
||||||
'connection' => $connectionMap[$lang][$connectionState] ?? $connectionState ?? null,
|
'connection' => $connectionMap[$lang][$connectionState] ?? $connectionState ?? null,
|
||||||
|
'galaxy' => $galaxy,
|
||||||
'history' => $path ?? null,
|
'history' => $path ?? null,
|
||||||
], JSON_UNESCAPED_UNICODE);
|
], JSON_UNESCAPED_UNICODE);
|
||||||
?>
|
?>
|
||||||
|
|||||||
Reference in New Issue
Block a user