Initial private import

This commit is contained in:
seo
2026-06-08 02:33:05 +09:00
commit a2a89998b6
6 changed files with 1793 additions and 0 deletions
+237
View File
@@ -0,0 +1,237 @@
html, body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
background: #f2f2f7;
}
#mySettingContainer {
background: #f2f2f7;
width: 100%;
max-width: 1026px;
margin: 0 auto;
min-height: 100%;
padding: 0;
border-radius: 0;
box-shadow: none;
}
#mySettingContainer #settingsHeader {
display: flex;
align-items: center;
justify-content: center;
position: relative;
padding: 12px;
padding-top: 12px;
font-size: 17px;
color: #000;
}
#mySettingContainer #blogBackLink {
position: absolute;
left: 15px;
color: #007AFF;
text-decoration: none;
font-weight: normal;
cursor: pointer;
}
#mySettingContainer #blogBackLink .backArrow {
font-size: 26px; /* 더 크게 */
position: relative;
top: 1px;
margin-right: 1px;
}
#mySettingContainer #blogBackLink:active {
opacity: 0.5;
}
#mySettingContainer #langToggle {
position: absolute;
right: 15px;
top: 50%;
transform: translateY(-50%);
width: 24px;
height: 24px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
}
#mySettingContainer #langToggle img {
width: 30px;
height: 30px;
opacity: 0.8;
transition: opacity 0.2s ease;
}
#mySettingContainer #langToggle:hover img {
opacity: 1;
}
#mySettingContainer #mapContainer {
height: 500px;
border-radius: 14px;
overflow: hidden;
margin: 0 16px 16px 16px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
#mySettingContainer .settingSection {
background: #fff;
margin: 0 16px 16px 16px;
border-radius: 14px;
overflow: hidden;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
#mySettingContainer .settingRow {
position: relative;
display: flex;
justify-content: space-between;
align-items: center;
height: 36px;
line-height: 36px;
font-size: 14px;
color: #000;
padding: 0 12px;
}
#mySettingContainer .settingRow::after {
content: "";
position: absolute;
bottom: 0;
left: 12px;
right: 0;
height: 1px;
background: #e5e5ea;
}
#mySettingContainer .settingRow:last-child::after {
content: none;
}
#mySettingContainer .settingRow:last-child {
border-bottom: none;
}
#mySettingContainer .settingRow span:last-child {
color: #6e6e73;
}
#mySettingContainer .iosSwitch {
position: relative;
width: 42px;
height: 26px;
line-height: normal;
}
#mySettingContainer .iosSwitch input {
opacity: 0;
width: 0;
height: 0;
}
#mySettingContainer .slider {
position: absolute;
cursor: default;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
border-radius: 20px;
transition: .4s;
}
#mySettingContainer .slider:before {
position: absolute;
content: "";
height: 22px;
width: 22px;
left: 2px;
bottom: 2px;
background-color: white;
border-radius: 50%;
transition: .4s;
}
#mySettingContainer input:checked + .slider {
background-color: #34c759;
}
#mySettingContainer input:checked + .slider:before {
transform: translateX(16px);
}
#mySettingContainer .settingRow.locateRow {
background: #fff;
transition: background 0.3s ease;
}
#mySettingContainer .settingRow.locateRow:active {
background: #d3d3d3;
}
#mySettingContainer .settingRow.locateRow:hover {
background: #d3d3d3;
}
#mySettingContainer .settingTitle {
padding: 4px 12px 4px 28px;
font-size: 11px;
color: #8e8e93;
}
#ios-pwa-tooltip {
opacity: 0;
visibility: hidden;
transition: opacity 0.4s ease, visibility 0.4s ease;
position: fixed;
bottom: 30px;
left: 50%;
transform: translateX(-50%);
background: #ffffff;
color: #000;
border-radius: 12px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
padding: 10px 30px;
font-size: 14px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
z-index: 9999;
box-sizing: border-box;
white-space: nowrap;
text-overflow: ellipsis;
width: fit-content;
max-width: calc(100% - 20px);
text-align: center;
}
#ios-pwa-tooltip::after {
content: "";
position: absolute;
bottom: -10px;
left: 50%;
transform: translateX(-50%);
border-left: 8px solid transparent;
border-right: 8px solid transparent;
border-top: 10px solid #ffffff;
filter: drop-shadow(0px 2px 2px rgba(0, 0, 0, 0.2));
}
#ios-pwa-tooltip .close-btn {
position: absolute;
top: 2px;
right: 7px;
font-size: 14px;
color: #888;
cursor: pointer;
line-height: 1;
}
#ios-pwa-tooltip .close-btn:hover {
color: #333;
}
#ios-pwa-tooltip.visible {
opacity: 1;
visibility: visible;
}
.dth {
border: none;
border-radius: 8px;
padding: 2px 4px;
background: #f2f2f7; /* 기본 연회색 배경 */
color: #007AFF; /* 파란 글자 */
font-size: 14px;
text-align: center;
cursor: pointer;
}
.dth:focus {
outline: none;
background: #e5e5ea; /* 눌렀을 때 조금 진한 회색 */
}
.dth::placeholder {
color: #aaa;
}
+175
View File
@@ -0,0 +1,175 @@
<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>
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

+1
View File
@@ -0,0 +1 @@
<svg class="svg-icon" style="width: 1em; height: 1em;vertical-align: middle;fill: currentColor;overflow: hidden;" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M512 129.94444444h-0.36736111H511.08159723c-210.68159723 0.55104167-381.13715277 171.37395833-381.13715279 382.05555556s170.45555556 381.50451389 381.13715279 382.05555556h0.91840277c211.04895833 0 382.05555556-171.00659723 382.05555556-382.05555556S723.04895833 129.94444444 512 129.94444444z m15.24548611 228.68229167c40.59340277-1.10208333 79.90104167-6.42881944 117.92291666-15.61284722 11.38819444 45.00173611 18.55173611 96.98333333 19.65381946 153.92430555H527.24548611V358.62673611z m0-30.67465278V163.00694444c41.14444444 11.38819444 83.02361111 66.30868056 109.47361112 150.61805556-35.26666667 8.44930556-71.81909723 13.225-109.47361112 14.32708333z m-30.49097222-165.3125v165.49618056c-38.021875-1.10208333-74.94166667-6.06145833-110.39201389-14.69444445 26.81736111-84.86041667 68.88020833-140.14826389 110.39201389-150.80173611z m0 195.98715278v138.12777778H358.259375c1.10208333-56.94097223 8.265625-108.92256944 19.65381944-153.92430556 38.20555556 9.18402777 78.06423611 14.69444444 118.84131945 15.79652778zM327.40104167 496.75451389H160.98645833c3.30625-78.61527777 32.69513889-150.61805556 79.53368056-207.55902778 33.98090277 18.73541667 70.16597223 34.16458333 108.00416667 45.55277778-12.49027777 48.67534723-19.8375 103.59583333-21.12326389 162.00625z m0 30.49097222c1.10208333 58.22673611 8.44930556 113.33090277 20.93958333 162.00625a571.99043402 571.99043402 0 0 0-108.00416667 45.55277778c-46.83854167-56.94097223-76.04375-128.94375-79.53368056-207.55902778H327.40104167z m30.85833333 0h138.49513889v137.94409722C455.97743056 666.29166667 416.11875 671.80208333 377.91319444 681.16979167c-11.38819444-45.00173611-18.55173611-96.98333333-19.65381944-153.92430556z m138.49513889 168.61875v165.67986112c-41.51180556-10.83715277-83.57465277-65.94131944-110.39201389-150.80173612 35.45034723-8.81666667 72.37013889-13.77604167 110.39201389-14.878125zM527.24548611 860.99305556V695.68055556c37.65451389 1.10208333 74.20694444 6.06145833 109.65729166 14.51076388-26.63368056 84.49305556-68.32916667 139.41354167-109.65729166 150.80173612z m0-195.80347223V527.24548611h137.57673612c-1.10208333 56.75729167-8.265625 108.73888889-19.65381946 153.740625-38.021875-9.18402777-77.32951389-14.69444444-117.92291666-15.79652778z m168.25138889-137.94409722h167.51666667c-3.30625 78.61527777-32.69513889 150.434375-79.53368056 207.55902778-34.34826389-18.91909723-70.71701389-34.34826389-108.92256944-45.92013889 12.49027777-48.67534723 19.8375-103.41215277 20.93958333-161.63888889z m0-30.49097222c-1.10208333-58.22673611-8.44930556-113.14722223-20.75590277-161.82256945 38.20555556-11.571875 74.57430556-27.00104167 108.73888888-45.73645833 46.83854167 56.94097223 76.22743056 128.94375 79.53368056 207.74270833H695.496875z m67.04340277-231.07013889c-30.49097223 16.16388889-62.45138889 29.57256944-96.06493054 39.675-17.81701389-57.49201389-42.98125-104.33055556-72.55381946-135.18888889 65.02291667 15.61284723 123.06597223 49.41006944 168.61875 95.51388889zM428.97638889 170.53784723c-29.57256944 30.67465277-54.553125 77.51319444-72.18645833 134.63784721-33.24618056-10.10243056-65.02291667-23.32743056-95.14652779-39.49131944 45.00173611-45.92013889 102.67743056-79.53368056 167.33298612-95.14652777zM261.45972223 758.13194444c30.12361111-16.16388889 62.08402777-29.38888889 95.14652777-39.49131944 17.81701389 57.30833333 42.79756944 103.96319444 72.37013889 134.82152777-64.65555556-15.61284723-122.33125-49.22638889-167.51666666-95.33020833z m332.46180554 95.69756945c29.75625-30.85833333 54.73680556-77.696875 72.7375-135.37256945 33.61354167 10.10243056 65.57395833 23.51111111 96.06493056 39.675-45.55277777 46.2875-103.77951389 80.08472223-168.80243056 95.69756945z" /></svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

+1074
View File
File diff suppressed because it is too large Load Diff
+306
View File
@@ -0,0 +1,306 @@
<?php
// Apache 환경 차단
if (isset($_SERVER['SERVER_SOFTWARE']) && stripos($_SERVER['SERVER_SOFTWARE'], 'Apache') !== false) {
http_response_code(403);
header('Content-Type: application/json');
echo json_encode(['error' => 'Access forbidden: Apache environment is not allowed']);
exit;
}
// POST 전용 검사
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405); // Method Not Allowed
header('Content-Type: application/json');
echo json_encode(['error' => 'Method Not Allowed']);
exit;
}
// JSON 요청 검사
$content_type = $_SERVER['CONTENT_TYPE'] ?? '';
if (stripos($content_type, 'application/json') === false) {
http_response_code(415); // Unsupported Media Type
header('Content-Type: application/json');
echo json_encode(['error' => 'Unsupported Content-Type. application/json required']);
exit;
}
function get_authorization_header() {
if (!empty($_SERVER['HTTP_AUTHORIZATION'])) {
return trim($_SERVER['HTTP_AUTHORIZATION']);
} elseif (function_exists('apache_request_headers')) {
foreach (apache_request_headers() as $key => $value) {
if (strcasecmp($key, 'Authorization') === 0) {
return trim($value);
}
}
}
return null;
}
$auth_header = get_authorization_header();
if (!$auth_header || !preg_match('/^Bearer\s+(\S+)$/', $auth_header, $matches)) {
http_response_code(401);
header('Content-Type: application/json');
echo json_encode(['error' => 'Unauthorized: Missing or invalid Authorization header']);
exit;
}
$allowed_password = 'XBv9z1IGqPUdtVf7DABFA16H0E154LJS';
$password = $matches[1];
if ($password !== $allowed_password) {
http_response_code(401);
header('Content-Type: application/json');
echo json_encode(['error' => 'Unauthorized: Invalid token']);
exit;
}
header('Content-Type: application/json; charset=utf-8');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Expires: 0');
$baseUrl = "https://ha.seoul.chaegeon.com/api/states";
$headers = [
"Authorization: Bearer REDACTED_HA_TOKEN",
"Content-Type: application/json"
];
// 엔드포인트 URL
$deviceUrl = "$baseUrl/device_tracker.seocaegeonyi_z_fold7";
$geoUrl = "$baseUrl/sensor.seocaegeonyi_z_fold7_geocoded_location";
$batteryStateUrl = "$baseUrl/sensor.seocaegeonyi_z_fold7_battery_state";
$batteryLevelUrl = "$baseUrl/sensor.seocaegeonyi_z_fold7_battery_level";
$watchBatteryStateUrl = "$baseUrl/sensor.seocaegeonyi_z_fold7_watch_battery_state";
$watchBatteryLevelUrl = "$baseUrl/sensor.seocaegeonyi_z_fold7_watch_battery_level";
$activityUrl = "$baseUrl/sensor.seocaegeonyi_z_fold7_detected_activity";
$connectionUrl = "$baseUrl/sensor.seocaegeonyi_z_fold7_network_type";
$fascendedUrl = "$baseUrl/sensor.seocaegeonyi_z_fold7_daily_floors";
$fdescendedUrl = "$baseUrl/sensor.seocaegeonyi_z_fold7_daily_floors";
$stepsUrl = "$baseUrl/sensor.seocaegeonyi_z_fold7_daily_steps";
$distanceUrl = "$baseUrl/sensor.seocaegeonyi_z_fold7_daily_distance";
$triggerUrl = "$baseUrl/sensor.seocaegeonyi_z_fold7_last_update_trigger";
// GET 파라미터 읽기
$startParam = $_GET['startTime'] ?? null;
$endParam = $_GET['endTime'] ?? null;
// Home Assistant에서 인식 가능한 UTC 포맷
$haFormat = 'Y-m-d\TH:i:s\Z';
if ($startParam && $endParam) {
// 전달받은 값을 DateTime으로 변환 후 UTC 포맷으로 변환
$startTime = (new DateTime($startParam))->setTimezone(new DateTimeZone('UTC'))->format($haFormat);
$endTime = (new DateTime($endParam))->setTimezone(new DateTimeZone('UTC'))->format($haFormat);
} else {
// 기본값: 최근 24시간
$startTime = (new DateTime('-24 hours'))->setTimezone(new DateTimeZone('UTC'))->format($haFormat);
$endTime = (new DateTime())->setTimezone(new DateTimeZone('UTC'))->format($haFormat);
}
// API URL 생성
$historyUrl = "https://ha.seoul.chaegeon.com/api/history/period/{$startTime}?filter_entity_id=device_tracker.seocaegeonyi_z_fold7&end_time={$endTime}";
// 공통 요청 함수
function getData($url, $headers) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
$response = curl_exec($ch);
curl_close($ch);
return json_decode($response, true);
}
// 데이터 불러오기
$deviceData = getData($deviceUrl, $headers);
$geoData = getData($geoUrl, $headers);
$batteryStateData = getData($batteryStateUrl, $headers);
$batteryLevelData = getData($batteryLevelUrl, $headers);
$watchBatteryStateData = getData($watchBatteryStateUrl, $headers);
$watchBatteryLevelData = getData($watchBatteryLevelUrl, $headers);
$activityData = getData($activityUrl, $headers);
$connectionData = getData($connectionUrl, $headers);
$ascendedData = getData($fascendedUrl, $headers);
$descendedData = getData($fdescendedUrl, $headers);
$stepsData = getData($stepsUrl, $headers);
$distanceData = getData($distanceUrl, $headers);
$historyData = getData($historyUrl, $headers);
$triggerData = getData($triggerUrl, $headers);
// 경로 기록
$path = [];
if (isset($historyData[0])) {
foreach ($historyData[0] as $item) {
$lat = $item['attributes']['latitude'] ?? null;
$lng = $item['attributes']['longitude'] ?? null;
$time = $item['last_changed'] ?? null;
if ($lat && $lng) {
$path[] = ['lat' => $lat, 'lng' => $lng, 'time' => $time];
}
}
}
$lang = $_GET['lang'] ?? 'en';
$activityMap = [
'ko' => [
'Stationary' => '가만히 있음',
'Walking' => '걷는 중',
'Running' => '달리는 중',
'Automotive' => '차량 이동 중',
'Cycling' => '자전거 이동 중',
'Unknown' => '-',
],
'en' => [
'Stationary' => 'Stationary',
'Walking' => 'Walking',
'Running' => 'Running',
'Automotive' => 'Automotive',
'Cycling' => 'Cycling',
'Unknown' => '-',
]
];
$aaccuracyMap = [
'ko' => [
'Low' => '낮음',
'Medium' => '중간',
'High' => '높음',
'Unknown' => '-',
],
'en' => [
'Low' => 'Low',
'Medium' => 'Medium',
'High' => 'High',
'Unknown' => '-',
]
];
$connectionMap = [
'ko' => [
'Wi-Fi' => '와이파이',
'Cellular' => '셀룰러',
'none' => '-',
'unknown' => '-',
],
'en' => [
'Wi-Fi' => 'Wi-Fi',
'Cellular' => 'Cellular',
'none' => '-',
'unknown' => '-',
]
];
$batteryStateMap = [
'ko' => [
'Charging' => '충전 중',
'Not Charging' => '충전중이 아님',
'Full' => '가득참',
'Unknown' => '-',
],
'en' => [
'Charging' => 'Charging',
'Not Charging' => 'Not Charging',
'Full' => 'Full',
'Unknown' => '-',
]
];
$triggerMap = [
'ko' => [
"Launch" => "앱 실행",
"Background Fetch" => "백그라운드 갱신",
"Push Notification" => "푸시",
"Periodic" => "주기적",
"Significant Location Change" => "중요한 위치 변화",
"Manual" => "수동",
"Signaled" => "시그널 됨",
"Siri" => "Siri 실행",
"Watch Context" => "워치 상태 전송",
"Geographic Region Entered" => "지정 지역에 들어옴",
"Geographic Region Exited" => "지정 지역을 벗어남",
],
'en' => [
"Launch" => "Launch",
"Background Fetch" => "Background Fetch",
"Push Notification" => "Push Notification",
"Periodic" => "Periodic",
"Significant Location Change" => "Significant Location Change",
"Manual" => "Manual",
"Signaled" => "Signaled",
"Siri" => "Siri",
"Watch Context" => "Watch Context",
"Geographic Region Entered" => "Geographic Region Entered",
"Geographic Region Exited" => "Geographic Region Exited",
]
];
$activityState = $activityData['state'] ?? null;
$aaccuracyState = $activityData['attributes']['Confidence'] ?? null;
$connectionState = $connectionData['state'] ?? null;
$batteryState = $batteryStateData['state'] ?? null;
$watchBatteryState = $watchBatteryStateData['state'] ?? null;
$address = null;
if (($deviceData['state'] ?? null) !== 'not_home') {
$address = $deviceData['state'];
} else {
// Locality 값 확인
$locality = $geoData['attributes']['Locality'] ?? null;
// Locality가 없거나 N/A면 Sub Administrative Area로 대체
if (empty($locality) || $locality === 'N/A') {
$locality = $geoData['attributes']['Sub Administrative Area'] ?? '';
}
// Sub Administrative Area 도 없을 경우 Name만 사용
$address = trim($locality . ' ' . ($geoData['attributes']['Name'] ?? ''));
}
// 해시 요청만 들어온 경우
if (isset($_GET['hashonly']) && $_GET['hashonly'] == '1') {
$raw = json_encode([
'latitude' => $deviceData['attributes']['latitude'] ?? null,
'longitude' => $deviceData['attributes']['longitude'] ?? null,
'accuracy' => $deviceData['attributes']['gps_accuracy'] ?? null,
'updated' => $deviceData['last_changed'] ?? null,
'trigger' => $triggerMap[$lang][$triggerData['state']] ?? $triggerData['state'] ?? null,
'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,
'distance' => $distanceData['state'] ?? null,
'activity' => $activityMap[$lang][$activityState] ?? $activityState ?? null,
'aaccuracy' => $aaccuracyMap[$lang][$aaccuracyState] ?? $aaccuracyState ?? null,
'connection' => $connectionMap[$lang][$connectionState] ?? $connectionState ?? null,
]);
echo json_encode(['hash' => hash('sha256', $raw)]);
exit;
}
echo json_encode([
'latitude' => $deviceData['attributes']['latitude'] ?? null,
'longitude' => $deviceData['attributes']['longitude'] ?? null,
'accuracy' => $deviceData['attributes']['gps_accuracy'] ?? null,
'updated' => $deviceData['last_changed'] ?? null,
'trigger' => $triggerMap[$lang][$triggerData['state']] ?? $triggerData['state'] ?? null,
'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,
'distance' => $distanceData['state'] ?? null,
'activity' => $activityMap[$lang][$activityState] ?? $activityState ?? null,
'aaccuracy' => $aaccuracyMap[$lang][$aaccuracyState] ?? $aaccuracyState ?? null,
'connection' => $connectionMap[$lang][$connectionState] ?? $connectionState ?? null,
'history' => $path ?? null,
], JSON_UNESCAPED_UNICODE);
?>