feat(sensor): 센서별 표시 라벨(위치·관리주체) 서버측 오버라이드

SENSOR_LABELS(config.local.php) 로 센서별 위치·관리주체를 서버에서 지정하고,
sensor_label() 헬퍼로 SMS·대시보드 표시를 통일한다(Pi 재배포 불필요). SMS 본문
라벨을 '센서'→'관리주체'로 변경. 배포 번들에 3개 센서(서버실 안/문서고/밖) 예시
SENSOR_LABELS 를 주입.
This commit is contained in:
cw2007 2026-07-11 11:22:40 +09:00
parent a72ea18130
commit 0966f6d32b
3 changed files with 68 additions and 25 deletions

View file

@ -264,6 +264,50 @@ $metric_sensor_ids = array_values(array_filter(
));
define('METRIC_SENSOR_IDS', $metric_sensor_ids !== [] ? $metric_sensor_ids : [2]);
// ─── 센서별 표시 라벨 오버라이드 (서버측) ────────────────────
// SMS·대시보드에 표시할 센서별 "위치"와 "관리주체(표시명)"를 서버에서 지정한다
// (Pi 재배포 없이 config.local.php 만 바꾸면 됨). 지정이 없으면 Pi 가 보고한
// device_location / sensor_name 을 그대로 쓴다. 예:
// 'SENSOR_LABELS' => [
// 1 => ['location' => '서버실(안)', 'name' => '정보기반'],
// 2 => ['location' => '문서고', 'name' => '조직혁신'],
// 3 => ['location' => '서버실(밖)', 'name' => '고객센터'],
// ]
$sensor_labels_raw = cfg('SENSOR_LABELS', []);
$sensor_labels = [];
if (is_array($sensor_labels_raw)) {
foreach ($sensor_labels_raw as $sid => $lbl) {
$sid = (int)$sid;
if ($sid < 1 || $sid > 255 || !is_array($lbl)) {
continue;
}
$entry = [];
if (isset($lbl['location']) && is_string($lbl['location']) && $lbl['location'] !== '') {
$entry['location'] = $lbl['location'];
}
if (isset($lbl['name']) && is_string($lbl['name']) && $lbl['name'] !== '') {
$entry['name'] = $lbl['name'];
}
if ($entry !== []) {
$sensor_labels[$sid] = $entry;
}
}
}
define('SENSOR_LABELS', $sensor_labels);
/**
* 센서 표시 라벨 결정: SENSOR_LABELS(서버 오버라이드) 우선, 없으면 보고값(Pi) 폴백.
* SMS·대시보드 등 사람이 보는 모든 곳에서 센서의 위치/관리주체를 이 헬퍼로 얻는다.
*
* @return array{0:string,1:string} [위치, 관리주체(표시명)]
*/
function sensor_label(int $sensor_id, ?string $reported_location, ?string $reported_name): array {
$ov = SENSOR_LABELS[$sensor_id] ?? [];
$loc = $ov['location'] ?? ((($reported_location ?? '') !== '') ? $reported_location : '알 수 없음');
$name = $ov['name'] ?? ((($reported_name ?? '') !== '') ? $reported_name : "센서{$sensor_id}");
return [$loc, $name];
}
// ─── 대시보드 세션 인증 ──────────────────────────────────
define('ADMIN_USER', cfg('ADMIN_USER', 'admin'));
define('ADMIN_PASSWORD_HASH', cfg('ADMIN_PASSWORD_HASH', '')); // bcrypt 해시

View file

@ -182,7 +182,7 @@ function send_sms_cafe24(string $receiver, string $message): array {
* @return bool 전원 발송 성공 여부
*/
function sms_dispatch_to_recipients(PDO $db, array $sensor_data, string $message, string $logLabel, ?array $recipients = null): bool {
$name = $sensor_data['sensor_name'] ?? "센서{$sensor_data['sensor_id']}";
[, $name] = sensor_label((int)($sensor_data['sensor_id'] ?? 0), null, $sensor_data['sensor_name'] ?? null);
$allSuccess = true;
$lastRemaining = null; // 게이트웨이가 알려준 최신 잔여 건수(잔액 사전경보용)
$targets = $recipients ?? SMS_RECIPIENTS; // 기본 1차, 에스컬레이션은 TIER2 전달
@ -520,14 +520,13 @@ function send_metric_alert_sms(array $sensor_data, string $kind, float $value):
// ── 메시지 구성 ──────────────────────────────────────────
$dt = date('Y-m-d H:i:s');
$name = $sensor_data['sensor_name'] ?? "센서{$sensor_id}";
$loc = $sensor_data['device_location'] ?? '알 수 없음';
[$loc, $name] = sensor_label((int)$sensor_data['sensor_id'], $sensor_data['device_location'] ?? null, $sensor_data['sensor_name'] ?? null);
$valStr = metric_value_str($value);
$thrStr = metric_value_str((float)$threshold);
$message = "{$tag}\n"
. "위치: {$loc}\n"
. "센서: {$name}\n"
. "관리주체: {$name}\n"
. "{$metricName}: {$valStr}{$unit} (임계 {$thrStr}{$unit} {$dir})\n"
. "시각: {$dt}";
@ -555,12 +554,11 @@ function send_metric_recovery_sms(array $sensor_data, float $temp_c, float $rh):
$db = get_db();
$dt = date('Y-m-d H:i:s');
$name = $sensor_data['sensor_name'] ?? "센서{$sensor_data['sensor_id']}";
$loc = $sensor_data['device_location'] ?? '알 수 없음';
[$loc, $name] = sensor_label((int)$sensor_data['sensor_id'], $sensor_data['device_location'] ?? null, $sensor_data['sensor_name'] ?? null);
$message = SMS_TAG_RECOVERY . "\n"
. "위치: {$loc}\n"
. "센서: {$name}\n"
. "관리주체: {$name}\n"
. "온도 " . metric_value_str($temp_c) . "℃ / 습도 " . metric_value_str($rh) . "%\n"
. "시각: {$dt}\n"
. "온습도가 정상 범위로 복귀했습니다.";
@ -632,12 +630,11 @@ function send_leak_alert_sms(array $sensor_data): bool {
// ── 메시지 구성 ──
$dt = date('Y-m-d H:i:s');
$name = $sensor_data['sensor_name'] ?? "센서{$sensor_id}";
$loc = $sensor_data['device_location'] ?? '알 수 없음';
[$loc, $name] = sensor_label((int)$sensor_data['sensor_id'], $sensor_data['device_location'] ?? null, $sensor_data['sensor_name'] ?? null);
$message = SMS_TAG_LEAK_ALERT . "\n"
. "위치: {$loc}\n"
. "센서: {$name}\n"
. "관리주체: {$name}\n"
. "누수가 감지되었습니다. 즉시 확인하세요!\n"
. "시각: {$dt}";
@ -685,12 +682,11 @@ function send_leak_recovery_sms(array $sensor_data): bool {
}
$dt = date('Y-m-d H:i:s');
$name = $sensor_data['sensor_name'] ?? "센서{$sensor_id}";
$loc = $sensor_data['device_location'] ?? '알 수 없음';
[$loc, $name] = sensor_label((int)$sensor_data['sensor_id'], $sensor_data['device_location'] ?? null, $sensor_data['sensor_name'] ?? null);
$message = SMS_TAG_LEAK_RECOVERY . "\n"
. "위치: {$loc}\n"
. "센서: {$name}\n"
. "관리주체: {$name}\n"
. "누수가 해소(복구)되었습니다.\n"
. "시각: {$dt}";
@ -707,13 +703,12 @@ function send_device_offline_sms(array $sensor_data): bool {
$db = get_db();
$dt = date('Y-m-d H:i:s');
$name = $sensor_data['sensor_name'] ?? "센서{$sensor_data['sensor_id']}";
$loc = $sensor_data['device_location'] ?? '알 수 없음';
[$loc, $name] = sensor_label((int)$sensor_data['sensor_id'], $sensor_data['device_location'] ?? null, $sensor_data['sensor_name'] ?? null);
$last = $sensor_data['last_seen'] ?? '알 수 없음';
$message = SMS_TAG_DEVICE_OFFLINE . "\n"
. "위치: {$loc}\n"
. "센서: {$name}\n"
. "관리주체: {$name}\n"
. "마지막 통신: {$last}\n"
. "감지 시각: {$dt}\n"
. "장비 상태를 확인하세요!";
@ -731,12 +726,11 @@ function send_device_online_sms(array $sensor_data): bool {
$db = get_db();
$dt = date('Y-m-d H:i:s');
$name = $sensor_data['sensor_name'] ?? "센서{$sensor_data['sensor_id']}";
$loc = $sensor_data['device_location'] ?? '알 수 없음';
[$loc, $name] = sensor_label((int)$sensor_data['sensor_id'], $sensor_data['device_location'] ?? null, $sensor_data['sensor_name'] ?? null);
$message = SMS_TAG_DEVICE_ONLINE . "\n"
. "위치: {$loc}\n"
. "센서: {$name}\n"
. "관리주체: {$name}\n"
. "복구 시각: {$dt}\n"
. "통신이 정상 복구되었습니다.";
@ -920,11 +914,10 @@ function maybe_escalate_alerts(PDO $db): void {
continue;
}
$firstLine = strtok($msg, "\n");
$name = $s['sensor_name'] ?: "센서{$sid}";
$loc = $s['device_location'] ?: '알 수 없음';
[$loc, $name] = sensor_label($sid, $s['device_location'] ?? null, $s['sensor_name'] ?? null);
$mins = (int)floor((time() - strtotime((string)$row['sent_at'])) / 60);
$message = SMS_TAG_ESCALATION . "\n"
. "위치: {$loc}\n센서: {$name}\n"
. "위치: {$loc}\n관리주체: {$name}\n"
. "{$firstLine} 미복귀 {$mins}분 경과\n"
. "1차 미대응 — 확인 바랍니다.\n시각: " . date('Y-m-d H:i:s');
$info = ['sensor_id' => $sid, 'sensor_name' => $name, 'device_id' => $s['device_id'], 'device_location' => $loc];
@ -969,11 +962,10 @@ function maybe_detect_stuck_sensors(PDO $db): void {
if (recent_tag_within($db, $sid, SMS_TAG_SENSOR_STUCK, STUCK_ALERT_COOLDOWN_SEC)) {
continue; // 쿨다운
}
$name = $s['sensor_name'] ?: "센서{$sid}";
$loc = $s['device_location'] ?: '알 수 없음';
[$loc, $name] = sensor_label($sid, $s['device_location'] ?? null, $s['sensor_name'] ?? null);
$tval = number_format((float)$rows[0]['temperature_c'], 2);
$hval = number_format((float)$rows[0]['humidity_percent'], 2);
$message = SMS_TAG_SENSOR_STUCK . "\n위치: {$loc}\n센서: {$name}\n"
$message = SMS_TAG_SENSOR_STUCK . "\n위치: {$loc}\n관리주체: {$name}\n"
. "최근 {$n}회 측정값 고착(온도 {$tval}℃ / 습도 {$hval}%)\n"
. "센서/배선 점검 필요\n시각: " . date('Y-m-d H:i:s');
$info = ['sensor_id' => $sid, 'sensor_name' => $name, 'device_id' => $s['device_id'], 'device_location' => $loc];

View file

@ -309,6 +309,13 @@ return [
// -- leak module sensor_id (Site 1 + new Site 3) --
'LEAK_SENSOR_IDS' => [1, 3],
// -- per-sensor display labels (location + managing entity) shown in SMS/dashboard --
'SENSOR_LABELS' => [
1 => ['location' => '서버실(안)', 'name' => '정보기반'],
2 => ['location' => '문서고', 'name' => '조직혁신'],
3 => ['location' => '서버실(밖)', 'name' => '고객센터'],
],
// -- temperature/humidity thresholds (recommended operational values) --
'METRIC_TEMP_HIGH_C' => 27,
'METRIC_TEMP_LOW_C' => 10,