You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

244 lines
7.3 KiB

# dpworldapp 호환성 명세서
> Web Configurator 교체 시 dpworldapp과의 호환성을 보장하기 위한 계약(Contract) 문서.
---
## 1. 개요
dpworldapp(`/usr/bin/dpworldapp`)은 IoT 장비의 메인 애플리케이션으로, Web Configurator가 저장한 설정을 SQLite DB에서 직접 읽어 사용합니다.
**핵심 원칙**: dpworldapp은 **일체 변경하지 않으므로**, 새 Web Configurator는 기존 Java 앱이 생성하는 것과 **비트 단위까지 호환되는** 데이터를 생성해야 합니다.
---
## 2. 공유 인터페이스
### 2.1 SQLite DB 파일 경로
```
~/db/dynamic_data.db (= /home/root/db/dynamic_data.db)
```
- **절대 변경 불가**: dpworldapp이 이 경로를 하드코딩하고 있을 가능성 높음
- busy_timeout: `5000ms` (동시 접근 시 잠금 대기)
### 2.2 board_config 테이블
```sql
-- 테이블 구조 (변경 불가)
CREATE TABLE board_config (
key TEXT PRIMARY KEY NOT NULL,
value TEXT NOT NULL
);
```
| key 값 | 역할 | 쓰기 주체 | 읽기 주체 |
|--------|------|----------|----------|
| `device_config` | 디바이스 네트워크/하드웨어 설정 | Web Configurator | dpworldapp |
| `protocol_config` | 프로토콜/매핑 설정 | Web Configurator | dpworldapp |
| `log_config` | 로그 관리(auto-compress/cleanup 등) — **Python 전용** | Web Configurator | (dpworldapp 미사용) |
| `net_config` | Network Apply Engine watchdog 설정(kill-switch 등) — **Python 전용** | Web Configurator | (dpworldapp 미사용) |
| `ap_config` | Wi-Fi AP(`ap0`) 설정 — **Python 전용** | Web Configurator | (dpworldapp 미사용) |
> [!NOTE]
> `board_config` 의 전체 허용 키는 위 5개다(`db_manager.py:43` `ALLOWED_KEYS`).
> dpworldapp 이 읽는 것은 `device_config` / `protocol_config` 둘 뿐이고,
> `log_config` / `net_config` / `ap_config` 는 Web Configurator 자체 기능을 위한
> **Python 전용 키**로 dpworldapp 은 읽지 않는다(별도 key ownership 정책 —
> dpworldapp 공유 키를 오염시키지 않기 위함). 한편 네트워크 적용 엔진이
> dpworldapp 과 byte-exact 호환을 위해 생성하는 `network_config.json` 은
> `board_config` 행이 아니라 `/home/root/network/` 의 **파일**이다(혼동 주의).
### 2.3 event_history 테이블
```sql
CREATE TABLE event_history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
data TEXT NOT NULL
);
```
| 역할 | 쓰기 주체 | 읽기 주체 |
|------|----------|----------|
| 이벤트/경고 이력 | dpworldapp | (선택적 조회 가능) |
> [!CAUTION]
> Web Configurator는 `event_history`에 **절대 쓰지 않습니다**. dpworldapp 전용.
---
## 3. device_config JSON 스키마
dpworldapp이 읽는 JSON의 정확한 필드명과 타입:
```json
{
"wifi_static": "on",
"wifi_ip": "10.227.231.38",
"wifi_netmask": "255.255.255.0",
"wifi_gateway": "10.227.231.1",
"wifi_dns1": "10.226.4.4",
"wifi_dns2": "10.226.4.5",
"wifi_country_code": "KR",
"eth_ip": "192.168.55.44",
"eth_netmask": "255.255.255.0",
"eth_gateway": "192.168.55.1",
"protocol_server_ip": "10.226.22.48",
"protocol_server_port": 20101,
"update_server_ip": "10.226.22.244",
"update_server_port": 9999,
"rtcm_server_ip": "10.226.22.48",
"rtcm_server_port": 20104,
"opc_ua_server_ip": "192.168.55.11",
"opc_ua_server_port": 53530,
"modbus_server_ip": "192.168.55.100",
"modbus_server_port": 502,
"lte_server_ip": "...",
"lte_server_port": 0,
"can_bus_type": "extended",
"can_baudrate": 1000,
"rs485_mode": "half",
"rs485_baudrate": 9600,
"rs485_parity": "...",
"lte_ip": "...",
"lte_netmask": "...",
"lte_gateway": "...",
"log_save": "on",
"log_max_size": 100,
"log_max_duration": 1,
"WIFI_SSID": [
{
"wifi_ssid": "KROF01-IT-DEV-Y5",
"wifi_passwd": "password",
"wifi_security": "wpa/wpa2"
}
]
}
```
### 타입 규칙
| 필드 패턴 | 타입 | 예시 |
|-----------|------|------|
| `*_port` | `Integer` (JSON number) | `20101` |
| `*_baudrate` | `Integer` | `9600` |
| `log_max_size`, `log_max_duration` | `Integer` | `100` |
| `wifi_static`, `log_save` | `String` (`"on"` / `"off"`) | `"on"` |
| 나머지 | `String` | `"192.168.55.44"` |
> [!NOTE]
> **dpworldapp-owned 필드 (Web Configurator 미작성):** `imu_remap_x` /
> `imu_remap_x_sign` / `imu_remap_y` / `imu_remap_y_sign` / `imu_remap_z` /
> `imu_remap_z_sign` 6개 IMU remap 필드는 **v1.4.5 부터 Web Configurator 가
> 더 이상 작성하지 않는다**(UI 에서 제거됨, src 에 참조 0). dpworldapp 이
> 자체적으로 소유·관리하는 값이므로 위 스키마 예시에서 의도적으로 제외했다.
> Web Configurator 의 partial-merge POST 는 기존 행을 통째로 덮어쓰지 않으므로
> (`/setting/device` 부분 병합), dpworldapp 이 기록한 이 키들은 보존된다.
---
## 4. protocol_config JSON 스키마
```json
{
"dev_type": "RTLS",
"equipment": "ITV",
"equipment_id": "01",
"MEID": "7000",
"protocol": "OPC_UA",
"version": "v1.0",
"can_input": "on",
"speed_data": "CAN",
"dr_on": "on",
"heading_on": "on",
"heading_imu_on": "on",
"fix_mode_on": "on",
"analog_input_level": "6",
"ai0": "0", "ai1": "1", "ai2": "0", "ai3": "0",
"di0": "0", "di1": "0", "di2": "0", "di3": "0",
"two_byte_order": "big",
"four_byte_order": "big",
"CAN": [
{
"field": "CAN1",
"id": "0x18FEFC28",
"shift": 0,
"mask": "0xffff",
"expr": "x*0.05+10.0",
"odt": "integer",
"dv": "-9"
}
],
"OPC_UA": [
{
"field": "TMP1",
"ns": "3",
"addr": "1001",
"shift": 0,
"expr": "x*10.0",
"odt": "float64",
"dv": "-9"
}
],
"MODBUS": [
{
"field": "TEMP",
"addr": "100",
"idt": "integer",
"odt": "float64",
"dv": "-9",
"shift": 0,
"expr": "x*0.1",
"mask": "0xffff"
}
]
}
```
### 프로토콜별 조건부 필드 존재 규칙
| 조건 | 결과 |
|------|------|
| `protocol == "OPC_UA"` | `OPC_UA[]` 포함, `MODBUS` 제외 (null → JSON에서 생략) |
| `protocol == "MODBUS"` | `MODBUS[]` 포함, `OPC_UA` 제외 |
| `protocol == "NONE"` | 둘 다 제외 |
| `can_input == "on"` | `CAN[]` 포함 |
| `can_input == "off"` | `CAN` 제외 |
---
## 5. 호환성 체크리스트
### 배포 전 필수 확인
- [ ] `~/db/dynamic_data.db` 경로 동일
- [ ] `board_config` 테이블 구조 동일
- [ ] `key` 값 문자열 동일 (`"device_config"`, `"protocol_config"`)
- [ ] JSON 필드명 snake_case 동일
- [ ] Integer 필드가 JSON number 타입으로 저장
- [ ] null/None 필드가 JSON에서 제외됨
- [ ] MEID가 항상 문자열로 저장
- [ ] 비활성 프로토콜 매핑이 JSON에서 제외됨
- [ ] `busy_timeout=5000` 설정됨
- [ ] `event_history` 테이블에 쓰기 없음
### 검증 방법
```bash
# 1. 기존 Java 앱으로 설정 저장 후 DB 덤프
sqlite3 ~/db/dynamic_data.db "SELECT value FROM board_config WHERE key='device_config'" > java_device.json
# 2. 새 Python 앱으로 동일 설정 저장 후 DB 덤프
sqlite3 ~/db/dynamic_data.db "SELECT value FROM board_config WHERE key='device_config'" > python_device.json
# 3. JSON 비교 (필드 순서 무시)
python3 -c "
import json
a = json.loads(open('java_device.json').read())
b = json.loads(open('python_device.json').read())
print('MATCH' if a == b else 'MISMATCH')
print('Diff keys:', set(a.keys()) ^ set(b.keys()))
"
```