Browse Source

- 펌웨어 업데이트 이후에 싱크 문제로 db 초기화가 이루어짐. 해당 부분 disable

master
설예인 1 month ago
parent
commit
2ca0ac1143
  1. 50
      src/migrations.py
  2. 12
      src/network/apply_engine.py
  3. 31
      src/server.py

50
src/migrations.py

@ -14,14 +14,25 @@ _CAN_BAUDRATE_OLD_TO_NEW = {
} }
def _existing_config(db, key):
cfg = db.get_config(key)
if not isinstance(cfg, dict) or not cfg:
return None
return cfg
def migrate_can_baudrate_units(db) -> bool: def migrate_can_baudrate_units(db) -> bool:
"""Migrate device_config.can_baudrate from old units to new (5/22) units. """Migrate device_config.can_baudrate from old units to new (5/22) units.
Returns True if the migration ran, False if previously applied.""" Returns True if the migration ran, False if previously applied."""
if db.get_schema_meta("can_baudrate_units_migrated") == "true": if db.get_schema_meta("can_baudrate_units_migrated") == "true":
return False return False
if _existing_config(db, "device_config") is None:
return False
def _mut(cur): def _mut(cur):
cfg = dict(cur) if isinstance(cur, dict) else {} if not isinstance(cur, dict) or not cur:
return cur
cfg = dict(cur)
new = _CAN_BAUDRATE_OLD_TO_NEW.get(cfg.get("can_baudrate")) new = _CAN_BAUDRATE_OLD_TO_NEW.get(cfg.get("can_baudrate"))
if new is not None: if new is not None:
cfg["can_baudrate"] = new cfg["can_baudrate"] = new
@ -33,7 +44,7 @@ def migrate_can_baudrate_units(db) -> bool:
cfg["can_baudrate"] = int(v.strip()) cfg["can_baudrate"] = int(v.strip())
return cfg return cfg
db.update_config("device_config", _mut, default={}) db.update_config("device_config", _mut)
db.set_schema_meta("can_baudrate_units_migrated", "true") db.set_schema_meta("can_baudrate_units_migrated", "true")
return True return True
@ -53,10 +64,9 @@ def migrate_log_compress_split(db) -> bool:
# write, so a brief read before the atomic updates is not a race surface; # write, so a brief read before the atomic updates is not a race surface;
# the race that mattered was clobbering OTHER device_config keys on write, # the race that mattered was clobbering OTHER device_config keys on write,
# which the atomic pop below now avoids). # which the atomic pop below now avoids).
dev = db.get_config("device_config") or {} dev = _existing_config(db, "device_config")
if not isinstance(dev, dict): if dev is None:
db.set_schema_meta("log_compress_split_migrated", "true") return False
return True
moved = {k: dev[k] for k in _LOG_COMPRESS_KEYS if k in dev} moved = {k: dev[k] for k in _LOG_COMPRESS_KEYS if k in dev}
@ -81,9 +91,8 @@ def migrate_log_compress_split(db) -> bool:
# are preserved — only the two log_compress_* keys are removed). # are preserved — only the two log_compress_* keys are removed).
db.update_config( db.update_config(
"device_config", "device_config",
lambda cur: {k: v for k, v in (cur if isinstance(cur, dict) else {}).items() lambda cur: {k: v for k, v in cur.items()
if k not in _LOG_COMPRESS_KEYS}, if k not in _LOG_COMPRESS_KEYS} if isinstance(cur, dict) and cur else cur,
default={},
) )
db.set_schema_meta("log_compress_split_migrated", "true") db.set_schema_meta("log_compress_split_migrated", "true")
@ -103,16 +112,20 @@ def migrate_port_types(db) -> bool:
dha baseline confirms Java schema Integer (2026-06-04 verify).""" dha baseline confirms Java schema Integer (2026-06-04 verify)."""
if db.get_schema_meta("port_types_migrated") == "true": if db.get_schema_meta("port_types_migrated") == "true":
return False return False
if _existing_config(db, "device_config") is None:
return False
def _mut(cur): def _mut(cur):
cfg = dict(cur) if isinstance(cur, dict) else {} if not isinstance(cur, dict) or not cur:
return cur
cfg = dict(cur)
for k in _PORT_KEYS: for k in _PORT_KEYS:
v = cfg.get(k) v = cfg.get(k)
if isinstance(v, str) and v.strip().lstrip("-").isdigit(): if isinstance(v, str) and v.strip().lstrip("-").isdigit():
cfg[k] = int(v) cfg[k] = int(v)
return cfg return cfg
db.update_config("device_config", _mut, default={}) db.update_config("device_config", _mut)
db.set_schema_meta("port_types_migrated", "true") db.set_schema_meta("port_types_migrated", "true")
return True return True
@ -131,21 +144,28 @@ def migrate_contract_canonical(db) -> bool:
""" """
if db.get_schema_meta("contract_canonical_migrated") == "true": if db.get_schema_meta("contract_canonical_migrated") == "true":
return False return False
if (_existing_config(db, "device_config") is None or
_existing_config(db, "protocol_config") is None):
return False
# device_config: rs485_parity "no" → "none" # device_config: rs485_parity "no" → "none"
def _mut_device(cur): def _mut_device(cur):
cfg = dict(cur) if isinstance(cur, dict) else {} if not isinstance(cur, dict) or not cur:
return cur
cfg = dict(cur)
if cfg.get("rs485_parity") == "no": if cfg.get("rs485_parity") == "no":
cfg["rs485_parity"] = "none" cfg["rs485_parity"] = "none"
return cfg return cfg
db.update_config("device_config", _mut_device, default={}) db.update_config("device_config", _mut_device)
# protocol_config: byte_order camelCase → space + idt float64 → float # protocol_config: byte_order camelCase → space + idt float64 → float
_BYTE_ORDER_MAP = {"littleSwap": "little swap", "bigSwap": "big swap"} _BYTE_ORDER_MAP = {"littleSwap": "little swap", "bigSwap": "big swap"}
def _mut_protocol(cur): def _mut_protocol(cur):
cfg = dict(cur) if isinstance(cur, dict) else {} if not isinstance(cur, dict) or not cur:
return cur
cfg = dict(cur)
for bo_key in ("two_byte_order", "four_byte_order"): for bo_key in ("two_byte_order", "four_byte_order"):
v = cfg.get(bo_key) v = cfg.get(bo_key)
if v in _BYTE_ORDER_MAP: if v in _BYTE_ORDER_MAP:
@ -164,7 +184,7 @@ def migrate_contract_canonical(db) -> bool:
cfg[arr_key] = new_arr cfg[arr_key] = new_arr
return cfg return cfg
db.update_config("protocol_config", _mut_protocol, default={}) db.update_config("protocol_config", _mut_protocol)
db.set_schema_meta("contract_canonical_migrated", "true") db.set_schema_meta("contract_canonical_migrated", "true")
return True return True

12
src/network/apply_engine.py

@ -130,20 +130,24 @@ class ApplyEngine:
def _write_db(self, fields): def _write_db(self, fields):
def mut(cur): def mut(cur):
base = cur if isinstance(cur, dict) else {} if not isinstance(cur, dict) or not cur:
raise RuntimeError("device_config is not initialized")
base = cur
out = dict(base); out.update(fields) # partial-merge (§4.1) out = dict(base); out.update(fields) # partial-merge (§4.1)
return out return out
self.db.update_config("device_config", mut, default={}) self.db.update_config("device_config", mut)
def _write_db_restore(self, present, absent): def _write_db_restore(self, present, absent):
"""I1: 롤백 전용 — present 키 복원 + 스냅샷 시점 부재 키 pop.""" """I1: 롤백 전용 — present 키 복원 + 스냅샷 시점 부재 키 pop."""
def mut(cur): def mut(cur):
base = cur if isinstance(cur, dict) else {} if not isinstance(cur, dict) or not cur:
raise RuntimeError("device_config is not initialized")
base = cur
out = dict(base); out.update(present) out = dict(base); out.update(present)
for k in absent: for k in absent:
out.pop(k, None) out.pop(k, None)
return out return out
self.db.update_config("device_config", mut, default={}) self.db.update_config("device_config", mut)
def _run(self, argv, timeout, apply_id, must_ok=False): def _run(self, argv, timeout, apply_id, must_ok=False):
t0 = self.clock() t0 = self.clock()

31
src/server.py

@ -809,6 +809,14 @@ class ConfigHandler(BaseHTTPRequestHandler):
# v1.4.0.3: case-only alias normalization (e.g. "ON"→"on", "WPA/WPA2"→"wpa/wpa2") # v1.4.0.3: case-only alias normalization (e.g. "ON"→"on", "WPA/WPA2"→"wpa/wpa2")
# must run before enum validation so aliases pass validation correctly # must run before enum validation so aliases pass validation correctly
current_device = db.get_config("device_config")
if not isinstance(current_device, dict) or not current_device:
self._send_json_response(
{"ok": False, "errors": ["device_config is not initialized"]},
409,
)
return
data = normalize_device_input(data) data = normalize_device_input(data)
# v1.4.4: Super_Relay 소유 키 strict reject (silent passthrough 방지) # v1.4.4: Super_Relay 소유 키 strict reject (silent passthrough 방지)
@ -890,10 +898,12 @@ class ConfigHandler(BaseHTTPRequestHandler):
_cleaned_device = cleaned # closure capture _cleaned_device = cleaned # closure capture
def _merge_device(current): def _merge_device(current):
base = current if isinstance(current, dict) else {} if not isinstance(current, dict) or not current:
return current
base = current
return {**base, **_cleaned_device} return {**base, **_cleaned_device}
merged_device = db.update_config("device_config", _merge_device, default={}) merged_device = db.update_config("device_config", _merge_device)
if log_part: if log_part:
_log_part = log_part # closure capture _log_part = log_part # closure capture
@ -938,6 +948,14 @@ class ConfigHandler(BaseHTTPRequestHandler):
return return
# v1.4.0.3: case-only alias normalization (e.g. can_input "ON"→"on") # v1.4.0.3: case-only alias normalization (e.g. can_input "ON"→"on")
current_protocol = db.get_config("protocol_config")
if not isinstance(current_protocol, dict) or not current_protocol:
self._send_json_response(
{"ok": False, "errors": ["protocol_config is not initialized"]},
409,
)
return
data = normalize_protocol_input(data) data = normalize_protocol_input(data)
# v1.4.4: Super_Relay 소유 키 strict reject (silent passthrough 방지) # v1.4.4: Super_Relay 소유 키 strict reject (silent passthrough 방지)
@ -1001,10 +1019,9 @@ class ConfigHandler(BaseHTTPRequestHandler):
_cleaned_protocol = cleaned # closure capture _cleaned_protocol = cleaned # closure capture
def _merge_protocol(current): def _merge_protocol(current):
base = current if isinstance(current, dict) else {} if not isinstance(current, dict) or not current:
if not isinstance(base, dict): return current
print(f"[WARN] non-dict protocol_config detected (type={type(base).__name__}), falling back to empty") base = current
base = {}
result = {**base, **_cleaned_protocol} result = {**base, **_cleaned_protocol}
# v1.4.6.8 C1: partial-merge AFTER process_protocol_config to preserve arrays # v1.4.6.8 C1: partial-merge AFTER process_protocol_config to preserve arrays
result = process_protocol_config(result) result = process_protocol_config(result)
@ -1014,7 +1031,7 @@ class ConfigHandler(BaseHTTPRequestHandler):
result.pop("odo_direction", None) result.pop("odo_direction", None)
return result return result
merged = db.update_config("protocol_config", _merge_protocol, default={}) merged = db.update_config("protocol_config", _merge_protocol)
self._send_json_response({ self._send_json_response({
"success": True, "success": True,

Loading…
Cancel
Save