Initial private import
This commit is contained in:
+237
@@ -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
@@ -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>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 109 KiB |
@@ -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
File diff suppressed because it is too large
Load Diff
+306
@@ -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);
|
||||
?>
|
||||
Reference in New Issue
Block a user