WiFi 보정값 표시 방식 정리
This commit is contained in:
@@ -19,7 +19,7 @@ Control은 라즈베리파이/리눅스 호스트의 팬과 시스템 상태를
|
|||||||
- WiFi client 목록과 2.4G/5G client 수 표시
|
- WiFi client 목록과 2.4G/5G client 수 표시
|
||||||
- 5G 외부 WiFi 모듈이 연결 시간을 제공하지 않는 경우 서버 관측 기반 연결 시간 보정
|
- 5G 외부 WiFi 모듈이 연결 시간을 제공하지 않는 경우 서버 관측 기반 연결 시간 보정
|
||||||
- WiFi 호스트명이 비어 있으면 MAC 기반 임시명을 표시하고, MAC별 수동 호스트명을 DB에 저장해 이후에도 유지
|
- WiFi 호스트명이 비어 있으면 MAC 기반 임시명을 표시하고, MAC별 수동 호스트명을 DB에 저장해 이후에도 유지
|
||||||
- WiFi 신호/송신 속도/수신 속도가 비어 있으면 대역, dBm, 송수신 속도, 5G RTL8822BU 특성을 기준으로 추정값 표시
|
- WiFi 신호/송신 속도/수신 속도가 비어 있으면 대역, dBm, 송수신 속도, 5G RTL8822BU 특성을 기준으로 결측값 보정
|
||||||
- System Notice 최근 이력 표시
|
- System Notice 최근 이력 표시
|
||||||
- process CPU/MEM 후보는 프로세스 상세 접힘 영역에서 표시
|
- process CPU/MEM 후보는 프로세스 상세 접힘 영역에서 표시
|
||||||
- `/etc/systemd/system/*.service` 기준 사용자 생성 서비스의 서비스명, 현재 상태, enable 상태, PID, restart 횟수, 최근 journal 로그를 진단 접힘 영역에서 표시
|
- `/etc/systemd/system/*.service` 기준 사용자 생성 서비스의 서비스명, 현재 상태, enable 상태, PID, restart 횟수, 최근 journal 로그를 진단 접힘 영역에서 표시
|
||||||
@@ -146,7 +146,7 @@ control-wifi-observe.service
|
|||||||
|
|
||||||
호스트명은 dnsmasq lease 값을 우선 사용합니다. lease hostname이 `N/A`이면 MAC 주소 끝 6자리 기반 임시명 `기기-XXXXXX`을 표시하고, 표의 호스트명을 클릭해 저장한 수동 이름은 `wifi_client_aliases`에 보관해 이후에도 우선 표시합니다.
|
호스트명은 dnsmasq lease 값을 우선 사용합니다. lease hostname이 `N/A`이면 MAC 주소 끝 6자리 기반 임시명 `기기-XXXXXX`을 표시하고, 표의 호스트명을 클릭해 저장한 수동 이름은 `wifi_client_aliases`에 보관해 이후에도 우선 표시합니다.
|
||||||
|
|
||||||
신호/송신 속도/수신 속도는 실제 `iw station dump` 값이 있으면 그대로 표시합니다. 값이 비어 있으면 `추정` 표시를 붙여 보정합니다. 2.4G는 일반 802.11n 계열 링크 속도와 dBm 범위를 기준으로, 5G는 Realtek RTL8822BU/802.11ac 계열 동글의 최대 1300Mbps 특성을 참고해 dBm과 링크 속도 사이를 추정합니다.
|
신호/송신 속도/수신 속도는 실제 `iw station dump` 값이 있으면 그대로 표시합니다. 값이 비어 있으면 2.4G는 일반 802.11n 계열 링크 속도와 dBm 범위를 기준으로, 5G는 Realtek RTL8822BU/802.11ac 계열 동글의 최대 1300Mbps 특성을 참고해 dBm과 링크 속도 사이를 보정합니다. 화면에는 별도 표시 없이 숫자만 보여 N/A 노출을 줄입니다.
|
||||||
|
|
||||||
## 처리 흐름
|
## 처리 흐름
|
||||||
|
|
||||||
@@ -156,7 +156,7 @@ control-wifi-observe.service
|
|||||||
4. 팬 조작은 상태 저장, 정책 적용, 로그 저장 순서로 처리합니다.
|
4. 팬 조작은 상태 저장, 정책 적용, 로그 저장 순서로 처리합니다.
|
||||||
5. WiFi client 목록은 `iw station dump`와 dnsmasq lease를 조합하고, 5G 연결 시간이 `N/A`이면 MAC 기준 최초 감지 시간을 DB에 저장해 경과 시간을 계산합니다.
|
5. WiFi client 목록은 `iw station dump`와 dnsmasq lease를 조합하고, 5G 연결 시간이 `N/A`이면 MAC 기준 최초 감지 시간을 DB에 저장해 경과 시간을 계산합니다.
|
||||||
6. WiFi hostname이 비어 있으면 MAC 기반 임시명을 표시하고, 수동 저장한 MAC별 이름은 DB에서 우선 적용합니다.
|
6. WiFi hostname이 비어 있으면 MAC 기반 임시명을 표시하고, 수동 저장한 MAC별 이름은 DB에서 우선 적용합니다.
|
||||||
7. WiFi 신호/송신 속도/수신 속도 값이 비어 있으면 실제로 존재하는 다른 무선 지표와 대역별 기준으로 추정값을 채웁니다.
|
7. WiFi 신호/송신 속도/수신 속도 값이 비어 있으면 실제로 존재하는 다른 무선 지표와 대역별 기준으로 값을 채웁니다.
|
||||||
8. `control-wifi-observe.timer`는 사용자 접속과 무관하게 10초마다 5G 관측 세션을 갱신합니다.
|
8. `control-wifi-observe.timer`는 사용자 접속과 무관하게 10초마다 5G 관측 세션을 갱신합니다.
|
||||||
9. WakeLock 버튼은 활성 상태를 초록색 버튼으로 표시합니다.
|
9. WakeLock 버튼은 활성 상태를 초록색 버튼으로 표시합니다.
|
||||||
10. Reboot 버튼은 `재부팅` 단어 입력과 관리자 암호 재입력을 모두 통과한 뒤 서버 API로 재부팅을 요청합니다.
|
10. Reboot 버튼은 `재부팅` 단어 입력과 관리자 암호 재입력을 모두 통과한 뒤 서버 API로 재부팅을 요청합니다.
|
||||||
@@ -179,7 +179,7 @@ control-wifi-observe.service
|
|||||||
- `json_out()`: API JSON 응답 표준화
|
- `json_out()`: API JSON 응답 표준화
|
||||||
- `apply_observed_wifi_connected_time()`: 5G WiFi 연결 시간이 없는 client에 서버 관측 경과 시간 적용
|
- `apply_observed_wifi_connected_time()`: 5G WiFi 연결 시간이 없는 client에 서버 관측 경과 시간 적용
|
||||||
- `wifi_client_aliases()`, `save_wifi_client_alias()`: MAC별 WiFi 호스트명 조회와 저장
|
- `wifi_client_aliases()`, `save_wifi_client_alias()`: MAC별 WiFi 호스트명 조회와 저장
|
||||||
- `apply_wifi_estimates()`: WiFi 신호/송신 속도/수신 속도 결측값 추정
|
- `apply_wifi_estimates()`: WiFi 신호/송신 속도/수신 속도 결측값 보정
|
||||||
- `send_ha_notify()`: HA webhook 알림 발송, 서울 우선/목포 fallback, 성공/실패 로그 저장
|
- `send_ha_notify()`: HA webhook 알림 발송, 서울 우선/목포 fallback, 성공/실패 로그 저장
|
||||||
- `ha_notify_due()`: 동일 tag 기준 쿨다운 판정
|
- `ha_notify_due()`: 동일 tag 기준 쿨다운 판정
|
||||||
- `clear_ha_notification()`: 같은 tag의 Android 알림 제거 명령 발송
|
- `clear_ha_notification()`: 같은 tag의 Android 알림 제거 명령 발송
|
||||||
|
|||||||
+2
-2
@@ -2192,7 +2192,7 @@ function format_estimated_mbps(float $mbps): string
|
|||||||
? (string)(int)round($mbps)
|
? (string)(int)round($mbps)
|
||||||
: number_format($mbps, 1);
|
: number_format($mbps, 1);
|
||||||
|
|
||||||
return $value . ' MBit/s (추정)';
|
return $value . ' MBit/s';
|
||||||
}
|
}
|
||||||
|
|
||||||
function apply_wifi_estimates(array $clients): array
|
function apply_wifi_estimates(array $clients): array
|
||||||
@@ -2208,7 +2208,7 @@ function apply_wifi_estimates(array $clients): array
|
|||||||
$signalDbm = $sourceRate > 0
|
$signalDbm = $sourceRate > 0
|
||||||
? estimate_wifi_signal_from_rate($band, $sourceRate)
|
? estimate_wifi_signal_from_rate($band, $sourceRate)
|
||||||
: ($band === '5G' ? -67 : -70);
|
: ($band === '5G' ? -67 : -70);
|
||||||
$client['signal'] = $signalDbm . ' dBm (추정)';
|
$client['signal'] = $signalDbm . ' dBm';
|
||||||
$client['signal_source'] = $sourceRate > 0 ? 'estimated_from_rate' : 'estimated_default';
|
$client['signal_source'] = $sourceRate > 0 ? 'estimated_from_rate' : 'estimated_default';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -828,14 +828,10 @@
|
|||||||
function wifiValueTitle(row, key) {
|
function wifiValueTitle(row, key) {
|
||||||
const source = row?.[`${key}_source`];
|
const source = row?.[`${key}_source`];
|
||||||
if (!source || source === 'iw' || source === 'lease') return '';
|
if (!source || source === 'iw' || source === 'lease') return '';
|
||||||
|
if (source.startsWith('estimated_')) return '';
|
||||||
const labels = {
|
const labels = {
|
||||||
alias: 'DB 저장 이름',
|
alias: 'DB 저장 이름',
|
||||||
generated: 'MAC 기반 임시 이름',
|
generated: 'MAC 기반 임시 이름',
|
||||||
estimated_from_rate: '속도 기반 추정',
|
|
||||||
estimated_from_signal: '신호 기반 추정',
|
|
||||||
estimated_from_rx: '수신 속도 기반 추정',
|
|
||||||
estimated_from_tx: '송신 속도 기반 추정',
|
|
||||||
estimated_default: '대역 기본 추정',
|
|
||||||
};
|
};
|
||||||
return labels[source] || source;
|
return labels[source] || source;
|
||||||
}
|
}
|
||||||
@@ -846,10 +842,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function wifiMetricCell(row, key) {
|
function wifiMetricCell(row, key) {
|
||||||
const title = wifiValueTitle(row, key);
|
return `<td>${escapeHtml(row[key] || '-')}</td>`;
|
||||||
const className = title ? ' class="wifi-estimated"' : '';
|
|
||||||
const titleAttr = title ? ` title="${attr(title)}"` : '';
|
|
||||||
return `<td${className}${titleAttr}>${escapeHtml(row[key] || '-')}</td>`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function editWifiHostname(mac, currentName) {
|
async function editWifiHostname(mac, currentName) {
|
||||||
|
|||||||
+1
-1
@@ -104,7 +104,7 @@ html[data-theme="light"] .btn.secondary{background:#d9e2ef;color:#172033}html[da
|
|||||||
.btn:disabled{opacity:.55;cursor:not-allowed}
|
.btn:disabled{opacity:.55;cursor:not-allowed}
|
||||||
.status-list{display:grid;gap:10px}.status-row{display:grid;grid-template-columns:112px minmax(0,1fr);gap:12px;align-items:baseline;padding:10px 12px;background:var(--card2);border-radius:14px}.status-key{color:var(--sub);font-size:13px}.status-value{overflow:visible;white-space:normal;word-break:break-word;font-variant-numeric:tabular-nums}
|
.status-list{display:grid;gap:10px}.status-row{display:grid;grid-template-columns:112px minmax(0,1fr);gap:12px;align-items:baseline;padding:10px 12px;background:var(--card2);border-radius:14px}.status-key{color:var(--sub);font-size:13px}.status-value{overflow:visible;white-space:normal;word-break:break-word;font-variant-numeric:tabular-nums}
|
||||||
.table-wrap{max-width:100%;overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch;border-radius:16px;border:1px solid var(--line)}.wifi-table{width:100%;min-width:1080px;table-layout:fixed;border-collapse:collapse}.wifi-table th,.wifi-table td{padding:11px 13px;border-bottom:1px solid rgba(128,145,170,.18);text-align:left;font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.wifi-table th{position:sticky;top:0;background:var(--table-head);z-index:1;font-weight:500}.wifi-table tr:hover{background:rgba(128,145,170,.09)}.wifi-table .col-band{width:72px}.wifi-table .col-host{width:240px}.wifi-table .col-ip{width:132px}.wifi-table .col-mac{width:170px}.wifi-table .col-signal{width:100px}.wifi-table .col-rate{width:120px}.wifi-table .col-time{width:130px}
|
.table-wrap{max-width:100%;overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch;border-radius:16px;border:1px solid var(--line)}.wifi-table{width:100%;min-width:1080px;table-layout:fixed;border-collapse:collapse}.wifi-table th,.wifi-table td{padding:11px 13px;border-bottom:1px solid rgba(128,145,170,.18);text-align:left;font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.wifi-table th{position:sticky;top:0;background:var(--table-head);z-index:1;font-weight:500}.wifi-table tr:hover{background:rgba(128,145,170,.09)}.wifi-table .col-band{width:72px}.wifi-table .col-host{width:240px}.wifi-table .col-ip{width:132px}.wifi-table .col-mac{width:170px}.wifi-table .col-signal{width:100px}.wifi-table .col-rate{width:120px}.wifi-table .col-time{width:130px}
|
||||||
.wifi-host-edit{appearance:none;border:0;background:transparent;color:inherit;font:inherit;max-width:100%;padding:0;margin:0;cursor:pointer;text-align:left;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-decoration:underline;text-decoration-style:dotted;text-underline-offset:3px}.wifi-host-edit:hover{color:var(--blue)}.wifi-estimated{color:var(--yellow)}
|
.wifi-host-edit{appearance:none;border:0;background:transparent;color:inherit;font:inherit;max-width:100%;padding:0;margin:0;cursor:pointer;text-align:left;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-decoration:underline;text-decoration-style:dotted;text-underline-offset:3px}.wifi-host-edit:hover{color:var(--blue)}
|
||||||
.chart-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:14px}.chart-box{height:280px;background:var(--input);border:1px solid rgba(84,101,128,.58);border-radius:16px;padding:12px 12px 18px}.chart-box h3{margin:0 0 8px;color:var(--sub);font-size:13px}.chart-canvas{position:relative;height:calc(100% - 27px);min-height:0}.chart-canvas canvas{width:100%!important;height:100%!important}.chart-secondary{margin-top:14px}
|
.chart-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:14px}.chart-box{height:280px;background:var(--input);border:1px solid rgba(84,101,128,.58);border-radius:16px;padding:12px 12px 18px}.chart-box h3{margin:0 0 8px;color:var(--sub);font-size:13px}.chart-canvas{position:relative;height:calc(100% - 27px);min-height:0}.chart-canvas canvas{width:100%!important;height:100%!important}.chart-secondary{margin-top:14px}
|
||||||
.details-panel{border:1px solid rgba(84,101,128,.52);border-radius:16px;background:rgba(128,145,170,.06);padding:0;margin-top:14px;overflow:hidden}.details-panel>summary{cursor:pointer;list-style:none;display:flex;align-items:center;justify-content:space-between;gap:12px;min-height:48px;padding:0 14px;color:var(--text);font-weight:500}.details-panel>summary::-webkit-details-marker{display:none}.details-panel>summary::after{content:"+";display:grid;place-items:center;width:24px;height:24px;border:1px solid var(--line);border-radius:999px;color:var(--sub);font-weight:700}.details-panel[open]>summary::after{content:"-"}.details-body{padding:0 14px 14px}.section-note{margin:0;color:var(--sub);font-size:12px}
|
.details-panel{border:1px solid rgba(84,101,128,.52);border-radius:16px;background:rgba(128,145,170,.06);padding:0;margin-top:14px;overflow:hidden}.details-panel>summary{cursor:pointer;list-style:none;display:flex;align-items:center;justify-content:space-between;gap:12px;min-height:48px;padding:0 14px;color:var(--text);font-weight:500}.details-panel>summary::-webkit-details-marker{display:none}.details-panel>summary::after{content:"+";display:grid;place-items:center;width:24px;height:24px;border:1px solid var(--line);border-radius:999px;color:var(--sub);font-weight:700}.details-panel[open]>summary::after{content:"-"}.details-body{padding:0 14px 14px}.section-note{margin:0;color:var(--sub);font-size:12px}
|
||||||
.resource-card{margin-top:18px}.resource-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px;margin-bottom:12px}.resource-head h2{margin:0}.baseline-pill{color:var(--sub);font-size:12px;font-variant-numeric:tabular-nums;text-align:right;white-space:nowrap}.spike-log-box{margin-top:16px}.spike-log-box h3{margin:0 0 9px;color:var(--text);font-size:14px;font-weight:500}.spike-log-list{display:grid;gap:8px;max-height:260px;overflow:auto;padding-right:4px}.spike-log-list.compact{max-height:180px}.spike-log-item{display:grid;gap:4px;padding:10px 12px;border:1px solid var(--notice-border);background:var(--notice-bg);border-radius:12px}.spike-log-item.latest{border:1px solid var(--notice-border-strong);background:var(--notice-bg-strong);box-shadow:0 0 0 1px var(--notice-border) inset;}.spike-log-item.alert{border-color:rgba(255,95,87,.58);background:rgba(150,38,38,.18)}.spike-log-item.alert strong{color:#ffb4ad}.spike-log-item strong{font-size:13px;font-weight:500;color:var(--notice-title)}.spike-log-item span{font-size:12px;color:var(--notice-copy);font-variant-numeric:tabular-nums}.spike-log-empty{padding:10px 12px;border-radius:12px;background:var(--card2);color:var(--sub);font-size:13px}.service-list{display:grid;gap:10px;max-height:520px;overflow:auto;padding-right:4px}.service-item{border:1px solid var(--line);border-radius:12px;background:var(--card2);padding:12px;display:grid;gap:9px}.service-head{display:flex;justify-content:space-between;gap:10px;align-items:flex-start}.service-title{display:grid;gap:3px;min-width:0}.service-name{font-size:14px;color:var(--text);font-weight:600;word-break:break-word}.service-desc,.service-meta{font-size:12px;color:var(--sub);font-variant-numeric:tabular-nums;word-break:break-word}.service-state{border:1px solid var(--line);border-radius:999px;padding:4px 9px;font-size:12px;color:var(--sub);white-space:nowrap}.service-state.active{color:#bbf7d0;border-color:rgba(34,197,94,.35);background:rgba(22,163,74,.14)}.service-state.failed{color:#fecaca;border-color:rgba(239,68,68,.4);background:rgba(185,28,28,.16)}.service-log{margin:0;max-height:160px;overflow:auto;border:1px solid var(--line);border-radius:10px;background:var(--code-bg);color:var(--mono-text);padding:10px;font:11px/1.45 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;white-space:pre-wrap;word-break:break-word}.dmesg-head{display:flex;align-items:center;justify-content:space-between;gap:10px}.dmesg-meta{font-size:12px;color:var(--sub);font-variant-numeric:tabular-nums}.dmesg-log{margin:10px 0 0;max-height:420px;overflow:auto;border:1px solid var(--line);border-radius:12px;background:var(--code-bg);color:var(--mono-text);padding:12px;font:12px/1.45 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;white-space:pre-wrap;word-break:break-word}.dmesg-log[hidden]{display:none}
|
.resource-card{margin-top:18px}.resource-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px;margin-bottom:12px}.resource-head h2{margin:0}.baseline-pill{color:var(--sub);font-size:12px;font-variant-numeric:tabular-nums;text-align:right;white-space:nowrap}.spike-log-box{margin-top:16px}.spike-log-box h3{margin:0 0 9px;color:var(--text);font-size:14px;font-weight:500}.spike-log-list{display:grid;gap:8px;max-height:260px;overflow:auto;padding-right:4px}.spike-log-list.compact{max-height:180px}.spike-log-item{display:grid;gap:4px;padding:10px 12px;border:1px solid var(--notice-border);background:var(--notice-bg);border-radius:12px}.spike-log-item.latest{border:1px solid var(--notice-border-strong);background:var(--notice-bg-strong);box-shadow:0 0 0 1px var(--notice-border) inset;}.spike-log-item.alert{border-color:rgba(255,95,87,.58);background:rgba(150,38,38,.18)}.spike-log-item.alert strong{color:#ffb4ad}.spike-log-item strong{font-size:13px;font-weight:500;color:var(--notice-title)}.spike-log-item span{font-size:12px;color:var(--notice-copy);font-variant-numeric:tabular-nums}.spike-log-empty{padding:10px 12px;border-radius:12px;background:var(--card2);color:var(--sub);font-size:13px}.service-list{display:grid;gap:10px;max-height:520px;overflow:auto;padding-right:4px}.service-item{border:1px solid var(--line);border-radius:12px;background:var(--card2);padding:12px;display:grid;gap:9px}.service-head{display:flex;justify-content:space-between;gap:10px;align-items:flex-start}.service-title{display:grid;gap:3px;min-width:0}.service-name{font-size:14px;color:var(--text);font-weight:600;word-break:break-word}.service-desc,.service-meta{font-size:12px;color:var(--sub);font-variant-numeric:tabular-nums;word-break:break-word}.service-state{border:1px solid var(--line);border-radius:999px;padding:4px 9px;font-size:12px;color:var(--sub);white-space:nowrap}.service-state.active{color:#bbf7d0;border-color:rgba(34,197,94,.35);background:rgba(22,163,74,.14)}.service-state.failed{color:#fecaca;border-color:rgba(239,68,68,.4);background:rgba(185,28,28,.16)}.service-log{margin:0;max-height:160px;overflow:auto;border:1px solid var(--line);border-radius:10px;background:var(--code-bg);color:var(--mono-text);padding:10px;font:11px/1.45 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;white-space:pre-wrap;word-break:break-word}.dmesg-head{display:flex;align-items:center;justify-content:space-between;gap:10px}.dmesg-meta{font-size:12px;color:var(--sub);font-variant-numeric:tabular-nums}.dmesg-log{margin:10px 0 0;max-height:420px;overflow:auto;border:1px solid var(--line);border-radius:12px;background:var(--code-bg);color:var(--mono-text);padding:12px;font:12px/1.45 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;white-space:pre-wrap;word-break:break-word}.dmesg-log[hidden]{display:none}
|
||||||
|
|||||||
Reference in New Issue
Block a user