이번 전달은 v1.12.1 위에 **v1.12.2·v1.12.3·v1.12.4** 를 함께 반영한 것입니다.
### v1.12.4 — 프로토콜 레지스터 필드를 디바이스 계약에 정합 (Modbus/OPC-UA)
- **Modbus `idt` 공백형 저장**: 레지스터 입력 데이터타입을 디바이스 config-reader 가 기대하는 공백형으로 저장합니다(`unsignedInteger`→`unsigned integer`, `unsignedShort`→`unsigned short`). UI 라벨은 그대로 두고 저장 시에만 변환하며, 로드 시 역변환합니다. 이전에는 camelCase 로 저장돼 **Modbus 설정 전체가 로드 실패**할 수 있었습니다.
- **Modbus·OPC-UA 폼에서 `Shift`/`Expr`/`Mask` 제거**: 해당 프로토콜에서 디바이스가 사용하지 않는 필드입니다(`Expr` 은 저장되나 값에 미적용, `Shift`/`Mask` 는 레지스터 필드가 아님). **CAN 은 유지**(CAN 에서는 실제 적용됨).
- **ODT `string`**: Modbus·CAN 에서 제거(디바이스가 Modbus 는 거부, CAN 은 출력 없음), OPC-UA 는 유지(완전 지원). 잘못된 저장은 400 으로 거부.
- 기존 디바이스 DB 의 위 표기는 **부팅 시 1회 자동 정합**(멱등 마이그레이션, 이전 계약-정합 마이그레이션을 거친 디바이스에서도 실행되도록 별도 플래그).
- dpworldapp 공유 DB·계약·타입 무변경(레지스터 표기 정합만). 설치 경로(`/usr/lib/web-configurator`)·기본 포트(`:9090`) 유지.
- **레지스터 테이블 UI 정돈**: 3개 매핑 표(Modbus/OPC-UA/CAN)의 컬럼 헤더를 축약어 없이 풀네임으로 통일(`Output Type`·`Default Value`·`Input Type`·`Namespace`·`Node ID`·`Address`). CAN Baud Rate 드롭다운에서 미사용 `NONE` 항목을 표시에서 숨김(데이터 계약·firmware 값 검증은 유지).
펌웨어 업데이트 직후 동기화 문제로 `device_config`/`protocol_config` 가 비어 있을 때, **웹이 그 값을 생성·덮어쓰지 않도록** 하여 dpworldapp 의 시딩을 선점하지 않습니다. 단일 판정(`config_initialized`)을 아래에 일관 적용합니다.
| 경로 | 미초기화 시 동작 |
|---|---|
| 설정 조회(GET) | 빈 `{}` 를 정상 설정처럼 주지 않고 **204** 반환 |
| 설정 저장(POST) | **409 `not_initialized`** — 빈 폼 저장으로 실제 설정을 덮어쓰는 것 차단 |
| 네트워크 즉시적용 | DB 미러 쓰기는 **skip**(경고 기록)하되 **렌더 파일 적용은 계속**(연결 복구 보존) |
| 마이그레이션·서버 시작 | DB 부재/락에도 **크래시 없이 degraded** 로 계속 |
> ※ 협력사 자체 DB-init fix(`2ca0ac1`)를 **포함·강화**한 것입니다. 특히 미초기화 시 **예외를 던져 fresh device 를 중단시키던 부분**을, 렌더는 적용하고 DB 쓰기만 건너뛰는 **fail-soft** 로 대체했습니다.
### v1.12.3 — Wi-Fi AP 채널 선택 + 네트워크 즉시적용 검증
- **Wi-Fi AP DFS 인지 채널 선택**: STA(내부 클라이언트)가 DFS/NO-IR 5GHz 채널(52–64·100–144)에 있어도 AP 가 그 채널을 맹목 추종하지 않고 **비-DFS 채널을 선택**합니다(2.4GHz STA 는 ch1–13 만 추종). AP 상태에 라디오-다운 사유(`country_unset`/`channel_dfs`/`mcc_bi`)를 추가해 진단을 명확히 했습니다.
- **네트워크 즉시적용**: 기존 방식(`systemctl start dpworld-network-apply.service`)을 **유지**합니다. 디바이스 실측으로 정상 동작을 재확인했고, 관련 내부 재작업 시도는 검토 후 **원복**했습니다(순변화 없음).
- (참고) 즉시적용마다 boot-seed 스크립트가 한 번 더 실행되는 부수효과가 있으나 적용 자체는 성공합니다. 상세/불변식(하드닝본 필수 등)은 `BSP-INTEGRATION.md` 를 참조하세요.
| **텔레메트리 Uplink (신규)** | dpworldapp 클라우드 텔레메트리 송신을 웹 UI 에서 Wi-Fi↔Ethernet 전환. 선택한 인터페이스의 게이트웨이를 통해 대상 서버 IP 에 대한 **호스트 라우트(/32)** 를 설치하는 방식 — relay·dpworldapp·OS 네트워크 파일 무변경. 적용은 비동기로 즉시 반환하고, 워치독이 라우트 유실 시 **자가복원**합니다. 관리 인터페이스가 끊기지 않도록 안전장치를 포함합니다. |
| **라우팅 투명성 + 사전 경고 (신규)** | 서버별 라우팅 상태와 실제 송신 인터페이스를 표시하고, 인터페이스 선택의 영향(로컬 LAN 호스트 가림 등)을 **적용 전에 경고**. 읽기 전용 — 포워딩/NAT 를 열지 않습니다. |
| **전체 소스 리뷰 41건 반영 (안정화)** | 정직성·안전성·구조 개선. 대표적으로: 점검 전 상태를 정상으로 표시하던 대시보드 경로 수정, 설정 로드 실패 시 빈 값 저장으로 인한 설정 손실 차단, 로그 자동정리의 마지막 아카이브 과삭제 방지, 중복된 경로/보안 로직 통합. **dpworldapp 공유 DB·계약·타입은 무변경.** |
- **nginx optional**: `deploy/nginx.conf`는 BSP에서 `:80` reverse proxy를 제공할 때 쓰는 sample입니다. `scripts/deploy.ps1`는 nginx를 설치/수정하지 않으며, 디바이스가 앱을 `:9090`으로 직접 접근하면 없어도 됩니다. sample을 쓸 경우 `proxy_pass`를 앱 포트 `:9090`으로 맞추세요.
- **런타임 의존성**: python3 stdlib만 필요 (RDEPENDS 목록은 `BSP-INTEGRATION.md` 참조). 외부 패키지 없음.