Files
custom-findmydevice/php/api.php
T

369 lines
14 KiB
PHP

<?php
require_once __DIR__ . '/../../common/auth.php';
require_once __DIR__ . '/../../common/ha.php';
// 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;
}
header('Content-Type: application/json; charset=utf-8');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Expires: 0');
custom_require_auth();
$requestData = custom_read_request_data();
if (isset($_GET['action']) && $_GET['action'] === 'webhook') {
$name = (string)($requestData['name'] ?? '');
$allowed = [
'auto_on' => 'find_auto_on',
'auto_off' => 'find_auto_off',
'auto_tick' => 'find_auto_tick',
'force_update' => 'find_force_update',
];
if (!isset($allowed[$name])) {
custom_json(['result' => 'invalid_webhook'], 400);
}
$ok = custom_ha_webhook($allowed[$name]);
custom_json(['result' => $ok ? 'ok' : 'fail'], $ok ? 200 : 502);
}
$config = custom_config();
$entities = $config['entities'];
// 엔드포인트 URL
$deviceUrl = $entities['device'];
$geoUrl = $entities['geo'];
$batteryStateUrl = $entities['battery_state'];
$batteryLevelUrl = $entities['battery_level'];
$activityUrl = $entities['activity'];
$connectionUrl = $entities['connection'];
$fascendedUrl = $entities['daily_floors'];
$fdescendedUrl = $entities['daily_floors'];
$stepsUrl = $entities['daily_steps'];
$distanceUrl = $entities['daily_distance'];
$triggerUrl = $entities['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 생성
$historyEndpoint = "/history/period/{$startTime}?filter_entity_id=" . rawurlencode($entities['device']) . "&end_time={$endTime}";
// 공통 요청 함수
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);
$activityData = getData($activityUrl);
$connectionData = getData($connectionUrl);
$ascendedData = getData($fascendedUrl);
$descendedData = getData($fdescendedUrl);
$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 = [];
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' => '충전 중',
'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" => "앱 실행",
"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'] ?? $activityData['attributes']['confidence'] ?? null;
$connectionState = $connectionData['state'] ?? null;
$batteryState = $batteryStateData['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'] ?? ''));
}
$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([
'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,
'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,
'galaxy' => $galaxy,
]);
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,
'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,
'galaxy' => $galaxy,
'history' => $path ?? null,
], JSON_UNESCAPED_UNICODE);
?>