# 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())) " ```