VitalRecorder
A free software to record and analyze biosignal waveforms and vital signs from GE® (Solar 8000®, Dash, Bx50®), Philips® (Intellivue®), Drager® (Infinity®, Nihon Kohden® (PSM®, Primus®), Maquet® (Flow-i®, Servo-i®) ventilators, Fresenius Kabi® (Orchestra®, Agilia®, and Link+®), BBraun® syringe pumps, Medtronic® BIS®, Masimo® Root®, DataQ® ADCs, etc. (Full list of supported devices)The use of the VitalRecorder program is completely free. Download and try the latest version.
(Downloaded 179,810 times since Feb 2017)
OS:
Version:
View All Versions
1.18.44
1.18.44: vr.conf FHIR=1 모드 추가 (자체 FHIR 수신서버 대상 실시간 푸시) + VS 2026 / 새 OpenSSL 인스톨러 빌드 환경 호환 shim. (1) sendweb_thread_func 의 기존 HL7/JSON 양분 분기 (VRApp.cpp:2466) 옆에 bFHIR 분기 추가. 1초 tick 마다 한 vrcode 의 모든 방 Observation 을 하나의 FHIR R4 collection Bundle 로 묶어 기존 WSS 채널로 push (gzip + WSSClient 인프라 그대로 재사용). MLLP 같은 신규 transport 안 도입 — FHIR R5 Subscription 의 websocket channel type 과 동형이라 표준 부합. (2) DISPLAY::get_fhir + TRACK::get_fhir 신규. v2 (vitaldb-fhir-v2) 와 동일한 reference-only 패턴 (Patient.id = m_patient_id, Encounter.id = vrcode-bedname-startsec, Observation.subject/encounter = relative reference) + hybrid keyframe: 첫 tick / m_patient_id 변경 / get_start_time() 변경 / 5분 경과 중 하나라도 발생 시 다음 tick Bundle 에 Patient + Encounter resource 를 inline. 그 외 tick 은 Observation 만. 수신서버가 도중에 새로 connect 해도 최대 5분 안에 자동 동기화 (keyframe heartbeat). DISPLAY 에 m_fhir_pt_sent_at/m_fhir_pt_last_id/m_fhir_enc_last_start 3개 멤버 추가 (m_setting_sent 패턴 그대로 per-display). (3) Observation 인코딩: NUM 은 effectiveDateTime + valueQuantity 각 데이터 포인트당 한 entry, WAV 은 effectivePeriod + valueSampledData (period=1000/srate ms, factor=1, origin=0, data=공백구분 floats) 한 entry. CO2/AWP wav 는 기존 get_json 처럼 25Hz 다운샘플 유지. NUM 은 m_montype 기반 LOINC 매핑 15개 등록 (ECG_HR/PLETH_HR=8867-4, PLETH_SPO2=59408-5, NIBP/IABP SBP/DBP/MBP=8480-6/8462-4/8478-0, BT=8310-5, RR=9279-1, etCO2=19891-1, CVP=8556-0, FETAL_HR=11627-3) + VR 자체 코드 시스템 (https://vitaldb.net/fhir/CodeSystem/track, code = dname/tname) 항상 동반. m_patient_id 미설정 (PATIENT_ID_UNSET) 시 anon-vrcode-bedname identifier 로 fallback. (4) Bundle wrapper: VRApp 가 방별 entries 를 콤마로 합쳐 {resourceType:Bundle, type:collection, timestamp:iso8601Z, entry:[...]} 로 wrap, 기존 gzip → WSS pipeline 동일. ISO 8601 UTC 헬퍼는 std::gmtime_s/gmtime_r 인라인. FHIR id 안전화 (A-Za-z0-9-.) 64자 제한 lambda 각 함수에 inline. (5) 빌드 환경 호환성: VRQt/CMakeLists.txt 에 _SILENCE_EXPERIMENTAL_COROUTINE_DEPRECATION_WARNINGS 추가 — VS 2026 (MSVC 14.51+) 의 STL 이 <experimental/coroutine> deprecation 을 warning → hard static_assert 로 바꾼 것이 cppwinrt 의 winrt::* (Serial.cpp 의 Bluetooth LE) 를 끌어오는 모든 TU 에서 컴파일 실패 시켜서, build_mfc.py 가 이미 CL env 로 주입하던 것과 동치 shim 을 Qt 쪽에도. (6) VRQt/CMakeLists.txt 의 OpenSSL link 경로 시프트. 새 OpenSSL 인스톨러가 libssl_static.lib / libcrypto_static.lib 를 lib/VC/x64/{MT,MTd}/ 에 두고 v1.x 의 libssl64MT.lib / libcrypto64MT.lib 네이밍과 lib/VC/static 경로를 깬다. build_mfc.py 의 %LOCALAPPDATA%/openssl-compat 패턴 (옛 이름으로 복사한 사본 폴더) 을 그대로 따라 CMakeLists 도 그 폴더가 있으면 우선, 없으면 옛 lib/VC/static 으로 fallback. 부트스트랩 스크립트는 build_mfc.py 의 docstring 참조 (한 줄). MFC + Qt + RPi + Ubuntu CLI + AppImage 빌드 모두 통과. 동작 영향: vr.conf 에 FHIR=1 추가 안 한 기존 deployment 는 sendweb 가 종전 JSON 모드 그대로 (HL7=1 만 분기보다 우선) — 0-impact migration.
1.18.43
1.18.43: 멀티베드 console (PACU 10베드 Pi) syslog noise 80,500/hr → ~1,400/hr (-98.3%), cpu_usage 측정 1.16.12 (9f26e36) 이후 8년치 영구 -1 버그 수정, 멀티베드 환경 베드 식별용 [bedname / devicename] 로그 prefix 도입. 동기는 PACU production Pi (1.18.40) 4일치 syslog 분석 — 1.24M 라인 중 vr 라인 836K, idle 8시간 평균 시간당 80,500 라인 (베드 10 × 1초마다 "Waiting for next patient" × syslog 2x 중복). (1) VRApp.cpp:2696-2710 timer_thread_func 의 cpu usage 계산이 `if (totalcpu && m_cpu_total_last && totalcpu > m_cpu_total_last)` 가드 안에서만 m_cpu_total_last 갱신해 첫 호출 시 m_cpu_total_last=0 → else 분기 → -1 → m_cpu_total_last 갱신 안 됨 → 영원히 false → 영원히 -1 데드락. PACU 1,048 샘플 100% -1 실측 확인. m_cpu_total_last/m_cpu_used_last 갱신을 if 밖으로 빼서 첫 호출만 baseline 으로 -1, 두 번째 (60s 후) 부터 정상 측정. /proc/stat 의 iowait/irq/softirq 까지 7개 필드 fscanf 로 정확도 ±5% 개선. 부수: 같은 함수의 cpu_temp 가 `temp / 1000` int 나눗셈으로 소수점 절단되어 PACU 측정값이 정수 50/51/...,/58 9종만 출력 (millidegree 정밀도 손실) → `temp / 1000.0f` 로 fix. ram 계산은 `(totalram + totalswap) * mem_unit` 결과에 다시 mem_unit 곱하던 차원 혼용 (Linux 가 mem_unit=1 이라 우연히 정상 동작했지만 dimensionally wrong) → `(units1 + units2) * mem_unit` 로 정정 + underflow 가드. (2) DISPLAY::log(string/printf) + DEVICE::log(string/printf) 멤버 추가. DISPLAY::log 는 `[bedname] msg`, DEVICE::log 는 m_pdisp 위임 없이 직접 `[bedname / devicename] msg` 한 줄 prefix (nested `[bed] [dev]` 보다 가독성 우선). m_pdisp 또는 bedname null 케이스 safe fallback. (3) show_msg 의 console path TRACE 를 log() 로 교체 → "Waiting for next patient" 자동 [bedname] prefix. ADT patient id unset 도 log() 로. (4) 이미 수동으로 get_bedname() 을 인자로 넘기던 8군데 (DISPLAY ptcon_thread_func / add_trk / add_dev / del_dev / cut-recording, Device add_rec 콜백, FILT stop, VRApp add_tab/del_tab) 를 log() 로 전환 — 이중 prefix 방지 + 일관성. Serial::reading_thread / tcp_thread / open_socket waiting / timeout / interrupted / got-client 6군데 DEVICE::log() 전환 → `[bedname / Intellivue]` 등 자동 prefix, 멀티탭 환경에서 어느 베드 어느 device 출처인지 즉시 식별. (5) wss 재시도 폭주 throttle. WSSClient 에 m_verbose (debug step 로그 게이트) + m_dt_last_err_log (60s window) 추가. connect() 내부 step-by-step TRACE 4개 ("connecting" / "connect error" / "connected") 는 m_verbose 가드, 최종 "wss connect error" 는 60s window throttle (첫 줄 즉시, 그 다음 60s 1회). connect 성공 path 에서 throttle reset 으로 다음 disconnect → 첫 에러 즉시 로깅. sendweb_thread_func 의 "connecting websocket server" 호출자 라인도 같은 60s window, m_bDebug 면 verbose 활성화. PACU idle 8h 측정 7,024/hr (4종 × 1,756) → 약 60/hr (-99%). (6) "Waiting for next patient" / "ADT patient id unset" 의 60s throttle 자체는 feature/wav-mem-retention 브랜치 ptcon_thread_func 재구성 (b75928f 시리즈) 에서 이미 들어가 있어 이 릴리즈에 자동 포함. 종합 영향: idle 시간당 syslog 라인 80,500 → ~1,400 (-98.3%), active 시간당 14,900 → ~700 (-95%). 진단 측면: cpu_usage 가 부활하면서 웹모니터링 json 의 cpu_usage 필드도 한 번도 안 채워지던 게 정상 전송 (VRApp.cpp:2499 의 `if (m_cpu_usage > 0)` 가드가 영원히 false 였음). 별건: syslog 라인 2× 중복은 application 측이 아니라 rsyslog imuxsock + imjournal + journald.ForwardToSyslog=yes 의 Debian Bookworm 전형 misconfig — admin 측 fix (rsyslog.conf 에서 imjournal 주석 처리 + restart) 로 추가 50% 감소 가능 (별도 문서). MFC + Qt + Pi (RPI64) + Ubuntu CLI 빌드 모두 통과.
1.18.41
1.18.41 Hotfix: TCP-server protocol-initiator device (Intellivue/S5/Datex/Hamilton/MPS/Agilia/IAP) 가 open() 직후 보내는 첫 패킷이 wire 에 안 나가던 회귀. 1.18.34 의 PortRouter refactor 가 Serial::open_socket() 의 8초 handoff blocking 을 제거하고 비동기 모델로 전환했는데, protocol-initiator 의 open() 코드가 m_sock=INVALID 상태에서 write 를 시도해 silent fail (Linux 는 m_fd=0 이라 false 반환). Intellivue 케이스 production journald 의 Pi → ESP TX 700K~3M bytes 는 monitor 측 abort 로 늦게 트리거된 AB_SPDU_SI recovery assoc_req 후 request_thread 의 정상 polling 트래픽이지만 일부 베드 (abort 없는 monitor) 는 recovery 도 안 되어 영구 silent (RX 300 bytes). 수정 방향: open() 의 옛 contract ("return 시점에 통신 가능") 복구. (1) Serial::open_socket() TCP-server 분기가 PortRouter::subscribe 후 m_sock_cv 로 첫 client handoff 까지 block (timeout = device read_timeout, 기본 30s) — 옛 모델 그대로. lock_and_open 이 false 면 다음 tick 에서 retry. close 신호로 abort 가능. (2) Serial::close() 는 PortRouter::unsubscribe 안 함 — subscribe 는 device 생애 동안 sticky 라 read_timeout 으로 인한 close→re-open 사이클 동안 listen socket 이 유지되어 1.18.29/1.18.34 의 anti-churn 이익 보존. unsubscribe 는 ~Serial() 에서만. (3) Per-device lifecycle thread (DEVICE::m_thread_restart): 각 device 가 자기 open/close/timeout 재시작을 별도 thread 에서 관리. 옛 모델의 DISPLAY::bed_manager 가 베드의 모든 device 를 sequential iterate 하던 구조에선 한 device 의 blocking open() 이 같은 베드의 다른 device 의 lifecycle 을 차단했는데, per-device thread 로 분리하여 멀티 device 베드도 병렬 진행. add_dev 에서 start_lifecycle, del_dev / ~DISPLAY 에서 stop_lifecycle. (4) Serial::tcp_thread_func 단순화 — outer m_sock_cv wait 패턴 (1.18.34/1.18.39 가 추가했던 것) 제거. open_socket 이 m_sock valid 보장하므로 reader thread 시작 시점에 simple inner recv loop 만. accept_handoff 의 replace (zombie cleanup) 케이스는 recv error 후 m_sock 변경 확인 + retry 로 처리. (5) reading_thread_func 의 `else if (m_sock != INVALID_SOCKET) tcp_thread_func();` 가드 복원 (1.18.39 가 제거). (6) Serial::write 의 silent-fail 케이스에 WARN trace 추가 — 미래 동종 회귀 즉시 노출. (7) PortRouter::subscribe 가 같은 (port, type) 에서 같은 IP filter 또는 둘 이상 catch-all 발견 시 reject (config 실수 원천 차단, 옛 WARN-only 동작 강화). (8) DISPLAY::bed_manager_thread_func 의 restart 블록 제거 — ptcon + adt 만 담당. "Waiting for next patient" TRACE 가 Linux/console 에서 1초 → 60초마다 throttle (별개 journald 부풀림 fix). 영향 범위: Intellivue / S5 / Datex / Hamilton / MPS / Agilia / IAP TCP-server 모드 베드 전부. 1.18.34 이후 우회 방법은 race-win 케이스 (VRN 이 PortRouter::subscribe 직후 connect) 외엔 없었음. MFC + Qt + Pi (RPI64) + Ubuntu Qt + AppImage 빌드 모두 통과.
1.18.40
Hotfix: Drager Medibus 프로토콜 사용 베드에서 1.15.11 이후 vr.service 가 SIGSEGV 로 무한 재시작하던 문제. 1.15.10 (a8c7b2a) 의 "Sampling rate estimation" 리팩터링이 m_ptrk_awp/awf/co2 트랙을 add_trks() 에서의 eager 생성 (default srate=62.5Hz) 에서 realtime data 100 샘플 모인 후 lazy 생성으로 바꿨는데, Medibus 핸드셰이크 순서 (0x51 ICC → 0x52 DeviceID → 0x53 RealtimeConfig 응답 → 0x54 → realtime data 시작) 상 0x53 응답 핸들러 (Drager.cpp:295-307) 가 트랙들이 아직 nullptr 인 시점에 `ptrk->m_srate = 1000000.0/interval` 로 NULL 역참조 → 즉시 SEGV. 그 베드의 vr.service 가 systemd Restart=on-failure 로 재시작 → 다시 connect → 동일 0x53 패킷 받고 동일 SEGV → 무한 루프 (journald 로그상 restart counter 61 까지 누적, code=killed status=11/SEGV). 1.15.9 까진 트랙이 add_trks() 에서 생성되어 0x53 도착 시 항상 non-NULL 이라 안전했음. 수정: (a) add_trks() 에서 m_ptrk_awp/awf/co2 eager 생성 복원 (srate=62.5Hz default) — 0x53 는 ptrk->m_srate 만 갱신 (트랙 재생성 X). (b) on_received() 의 lazy creation + packet-timing srate 추정 블록 제거 — 0x53 가 권위 있는 srate 소스이고 packet 타이밍 기반 추정은 network jitter 에 약해 불안정. (c) 0x53 핸들러에 `if (interval > 0)` guard 추가 — 페이로드 깨졌을 때 div-by-zero / inf srate 방지. 영향 범위: Drager Primus / Apollo / Perseus / Fabius 등 Medibus 사용 마취기 전부. 우회 방법은 1.15.9 다운그레이드 외엔 없었음 (그 베드의 모든 데이터 유실 + 환자 모니터링 불가). MFC + Qt + Pi (RPI64) + Ubuntu Qt + AppImage 빌드 모두 통과.
1.18.39
Fixed: PortRouter (TCP server) 모드에서 reader thread 가 startup 시점에 죽어 client 데이터를 영구히 못 받던 문제. 1.18.34 (9bd252f) 의 PortRouter refactor 가 m_sock 세팅을 dispatch 시점으로 옮겼는데 reading_thread_func (Serial.cpp:354) 의 가드 `else if (m_sock != INVALID_SOCKET) tcp_thread_func();` 는 옛 모델 (open 시점 이미 connect 완료) 가정 그대로라 — 시작 직후 m_sock=INVALID 면 tcp_thread_func 진입 안 하고 reader thread 즉시 종료. 이후 VRN 이 connect → PortRouter::dispatch → accept_handoff 가 m_sock 채우고 m_sock_cv.notify_all() → 그러나 wait 중인 reader 가 없음 (이미 죽음) → kernel recv buffer 에만 데이터 적재. 증상은 ss -tn state established 의 Recv-Q 가 0 이 아닌 값 (수백 bytes) 으로 누적, 베드 화면에는 데이터 안 들어옴. 그 베드의 VRN 이 Serial::open() 의 PortRouter::subscribe 직후~reader thread spawn 사이의 짧은 윈도우 안에 connect 한 경우만 우연히 정상 동작 (race-win). 수정: 가드 제거 — TCP 분기에 항상 진입. tcp_thread_func 자체에 이미 m_sock_cv outer-wait 루프가 있어 m_sock 이 INVALID 인 동안 dispatch 가 깨워줄 때까지 안전하게 대기. Pi 핫스팟에 VRN 여러 대 (e.g. 10.42.0.21~25) 를 같은 (4343, Philips:Intellivue) 키로 묶고 IP filter 로 라우팅하는 시나리오에서 가장 자주 노출. Added: 진단 로그 보강 — PortRouter::accept_loop 의 accept 이벤트, dispatch 의 라우팅 결정 (ip_specific/catch_all subs 수, 어느 dev 가 받았는지), subscribe/unsubscribe 의 entry 생성/append/destroy, reading_thread / tcp_thread 의 시작·wait·wake 라이프사이클 — 이전엔 모두 m_bDebug 게이트 안에 있어서 production journald 에서 안 보였는데, 이제 항상 출력 (이번 같은 dispatch routing 문제 진단을 production 에서 바로 가능). MFC + Qt + Pi (RPI64) + Ubuntu Qt + AppImage 빌드 모두 통과.
1.18.44
1.18.44: vr.conf FHIR=1 모드 추가 (자체 FHIR 수신서버 대상 실시간 푸시) + VS 2026 / 새 OpenSSL 인스톨러 빌드 환경 호환 shim. (1) sendweb_thread_func 의 기존 HL7/JSON 양분 분기 (VRApp.cpp:2466) 옆에 bFHIR 분기 추가. 1초 tick 마다 한 vrcode 의 모든 방 Observation 을 하나의 FHIR R4 collection Bundle 로 묶어 기존 WSS 채널로 push (gzip + WSSClient 인프라 그대로 재사용). MLLP 같은 신규 transport 안 도입 — FHIR R5 Subscription 의 websocket channel type 과 동형이라 표준 부합. (2) DISPLAY::get_fhir + TRACK::get_fhir 신규. v2 (vitaldb-fhir-v2) 와 동일한 reference-only 패턴 (Patient.id = m_patient_id, Encounter.id = vrcode-bedname-startsec, Observation.subject/encounter = relative reference) + hybrid keyframe: 첫 tick / m_patient_id 변경 / get_start_time() 변경 / 5분 경과 중 하나라도 발생 시 다음 tick Bundle 에 Patient + Encounter resource 를 inline. 그 외 tick 은 Observation 만. 수신서버가 도중에 새로 connect 해도 최대 5분 안에 자동 동기화 (keyframe heartbeat). DISPLAY 에 m_fhir_pt_sent_at/m_fhir_pt_last_id/m_fhir_enc_last_start 3개 멤버 추가 (m_setting_sent 패턴 그대로 per-display). (3) Observation 인코딩: NUM 은 effectiveDateTime + valueQuantity 각 데이터 포인트당 한 entry, WAV 은 effectivePeriod + valueSampledData (period=1000/srate ms, factor=1, origin=0, data=공백구분 floats) 한 entry. CO2/AWP wav 는 기존 get_json 처럼 25Hz 다운샘플 유지. NUM 은 m_montype 기반 LOINC 매핑 15개 등록 (ECG_HR/PLETH_HR=8867-4, PLETH_SPO2=59408-5, NIBP/IABP SBP/DBP/MBP=8480-6/8462-4/8478-0, BT=8310-5, RR=9279-1, etCO2=19891-1, CVP=8556-0, FETAL_HR=11627-3) + VR 자체 코드 시스템 (https://vitaldb.net/fhir/CodeSystem/track, code = dname/tname) 항상 동반. m_patient_id 미설정 (PATIENT_ID_UNSET) 시 anon-vrcode-bedname identifier 로 fallback. (4) Bundle wrapper: VRApp 가 방별 entries 를 콤마로 합쳐 {resourceType:Bundle, type:collection, timestamp:iso8601Z, entry:[...]} 로 wrap, 기존 gzip → WSS pipeline 동일. ISO 8601 UTC 헬퍼는 std::gmtime_s/gmtime_r 인라인. FHIR id 안전화 (A-Za-z0-9-.) 64자 제한 lambda 각 함수에 inline. (5) 빌드 환경 호환성: VRQt/CMakeLists.txt 에 _SILENCE_EXPERIMENTAL_COROUTINE_DEPRECATION_WARNINGS 추가 — VS 2026 (MSVC 14.51+) 의 STL 이 <experimental/coroutine> deprecation 을 warning → hard static_assert 로 바꾼 것이 cppwinrt 의 winrt::* (Serial.cpp 의 Bluetooth LE) 를 끌어오는 모든 TU 에서 컴파일 실패 시켜서, build_mfc.py 가 이미 CL env 로 주입하던 것과 동치 shim 을 Qt 쪽에도. (6) VRQt/CMakeLists.txt 의 OpenSSL link 경로 시프트. 새 OpenSSL 인스톨러가 libssl_static.lib / libcrypto_static.lib 를 lib/VC/x64/{MT,MTd}/ 에 두고 v1.x 의 libssl64MT.lib / libcrypto64MT.lib 네이밍과 lib/VC/static 경로를 깬다. build_mfc.py 의 %LOCALAPPDATA%/openssl-compat 패턴 (옛 이름으로 복사한 사본 폴더) 을 그대로 따라 CMakeLists 도 그 폴더가 있으면 우선, 없으면 옛 lib/VC/static 으로 fallback. 부트스트랩 스크립트는 build_mfc.py 의 docstring 참조 (한 줄). MFC + Qt + RPi + Ubuntu CLI + AppImage 빌드 모두 통과. 동작 영향: vr.conf 에 FHIR=1 추가 안 한 기존 deployment 는 sendweb 가 종전 JSON 모드 그대로 (HL7=1 만 분기보다 우선) — 0-impact migration.
1.18.43
1.18.43: 멀티베드 console (PACU 10베드 Pi) syslog noise 80,500/hr → ~1,400/hr (-98.3%), cpu_usage 측정 1.16.12 (9f26e36) 이후 8년치 영구 -1 버그 수정, 멀티베드 환경 베드 식별용 [bedname / devicename] 로그 prefix 도입. 동기는 PACU production Pi (1.18.40) 4일치 syslog 분석 — 1.24M 라인 중 vr 라인 836K, idle 8시간 평균 시간당 80,500 라인 (베드 10 × 1초마다 "Waiting for next patient" × syslog 2x 중복). (1) VRApp.cpp:2696-2710 timer_thread_func 의 cpu usage 계산이 `if (totalcpu && m_cpu_total_last && totalcpu > m_cpu_total_last)` 가드 안에서만 m_cpu_total_last 갱신해 첫 호출 시 m_cpu_total_last=0 → else 분기 → -1 → m_cpu_total_last 갱신 안 됨 → 영원히 false → 영원히 -1 데드락. PACU 1,048 샘플 100% -1 실측 확인. m_cpu_total_last/m_cpu_used_last 갱신을 if 밖으로 빼서 첫 호출만 baseline 으로 -1, 두 번째 (60s 후) 부터 정상 측정. /proc/stat 의 iowait/irq/softirq 까지 7개 필드 fscanf 로 정확도 ±5% 개선. 부수: 같은 함수의 cpu_temp 가 `temp / 1000` int 나눗셈으로 소수점 절단되어 PACU 측정값이 정수 50/51/...,/58 9종만 출력 (millidegree 정밀도 손실) → `temp / 1000.0f` 로 fix. ram 계산은 `(totalram + totalswap) * mem_unit` 결과에 다시 mem_unit 곱하던 차원 혼용 (Linux 가 mem_unit=1 이라 우연히 정상 동작했지만 dimensionally wrong) → `(units1 + units2) * mem_unit` 로 정정 + underflow 가드. (2) DISPLAY::log(string/printf) + DEVICE::log(string/printf) 멤버 추가. DISPLAY::log 는 `[bedname] msg`, DEVICE::log 는 m_pdisp 위임 없이 직접 `[bedname / devicename] msg` 한 줄 prefix (nested `[bed] [dev]` 보다 가독성 우선). m_pdisp 또는 bedname null 케이스 safe fallback. (3) show_msg 의 console path TRACE 를 log() 로 교체 → "Waiting for next patient" 자동 [bedname] prefix. ADT patient id unset 도 log() 로. (4) 이미 수동으로 get_bedname() 을 인자로 넘기던 8군데 (DISPLAY ptcon_thread_func / add_trk / add_dev / del_dev / cut-recording, Device add_rec 콜백, FILT stop, VRApp add_tab/del_tab) 를 log() 로 전환 — 이중 prefix 방지 + 일관성. Serial::reading_thread / tcp_thread / open_socket waiting / timeout / interrupted / got-client 6군데 DEVICE::log() 전환 → `[bedname / Intellivue]` 등 자동 prefix, 멀티탭 환경에서 어느 베드 어느 device 출처인지 즉시 식별. (5) wss 재시도 폭주 throttle. WSSClient 에 m_verbose (debug step 로그 게이트) + m_dt_last_err_log (60s window) 추가. connect() 내부 step-by-step TRACE 4개 ("connecting" / "connect error" / "connected") 는 m_verbose 가드, 최종 "wss connect error" 는 60s window throttle (첫 줄 즉시, 그 다음 60s 1회). connect 성공 path 에서 throttle reset 으로 다음 disconnect → 첫 에러 즉시 로깅. sendweb_thread_func 의 "connecting websocket server" 호출자 라인도 같은 60s window, m_bDebug 면 verbose 활성화. PACU idle 8h 측정 7,024/hr (4종 × 1,756) → 약 60/hr (-99%). (6) "Waiting for next patient" / "ADT patient id unset" 의 60s throttle 자체는 feature/wav-mem-retention 브랜치 ptcon_thread_func 재구성 (b75928f 시리즈) 에서 이미 들어가 있어 이 릴리즈에 자동 포함. 종합 영향: idle 시간당 syslog 라인 80,500 → ~1,400 (-98.3%), active 시간당 14,900 → ~700 (-95%). 진단 측면: cpu_usage 가 부활하면서 웹모니터링 json 의 cpu_usage 필드도 한 번도 안 채워지던 게 정상 전송 (VRApp.cpp:2499 의 `if (m_cpu_usage > 0)` 가드가 영원히 false 였음). 별건: syslog 라인 2× 중복은 application 측이 아니라 rsyslog imuxsock + imjournal + journald.ForwardToSyslog=yes 의 Debian Bookworm 전형 misconfig — admin 측 fix (rsyslog.conf 에서 imjournal 주석 처리 + restart) 로 추가 50% 감소 가능 (별도 문서). MFC + Qt + Pi (RPI64) + Ubuntu CLI 빌드 모두 통과.
1.18.41
1.18.41 Hotfix: TCP-server protocol-initiator device (Intellivue/S5/Datex/Hamilton/MPS/Agilia/IAP) 가 open() 직후 보내는 첫 패킷이 wire 에 안 나가던 회귀. 1.18.34 의 PortRouter refactor 가 Serial::open_socket() 의 8초 handoff blocking 을 제거하고 비동기 모델로 전환했는데, protocol-initiator 의 open() 코드가 m_sock=INVALID 상태에서 write 를 시도해 silent fail (Linux 는 m_fd=0 이라 false 반환). Intellivue 케이스 production journald 의 Pi → ESP TX 700K~3M bytes 는 monitor 측 abort 로 늦게 트리거된 AB_SPDU_SI recovery assoc_req 후 request_thread 의 정상 polling 트래픽이지만 일부 베드 (abort 없는 monitor) 는 recovery 도 안 되어 영구 silent (RX 300 bytes). 수정 방향: open() 의 옛 contract ("return 시점에 통신 가능") 복구. (1) Serial::open_socket() TCP-server 분기가 PortRouter::subscribe 후 m_sock_cv 로 첫 client handoff 까지 block (timeout = device read_timeout, 기본 30s) — 옛 모델 그대로. lock_and_open 이 false 면 다음 tick 에서 retry. close 신호로 abort 가능. (2) Serial::close() 는 PortRouter::unsubscribe 안 함 — subscribe 는 device 생애 동안 sticky 라 read_timeout 으로 인한 close→re-open 사이클 동안 listen socket 이 유지되어 1.18.29/1.18.34 의 anti-churn 이익 보존. unsubscribe 는 ~Serial() 에서만. (3) Per-device lifecycle thread (DEVICE::m_thread_restart): 각 device 가 자기 open/close/timeout 재시작을 별도 thread 에서 관리. 옛 모델의 DISPLAY::bed_manager 가 베드의 모든 device 를 sequential iterate 하던 구조에선 한 device 의 blocking open() 이 같은 베드의 다른 device 의 lifecycle 을 차단했는데, per-device thread 로 분리하여 멀티 device 베드도 병렬 진행. add_dev 에서 start_lifecycle, del_dev / ~DISPLAY 에서 stop_lifecycle. (4) Serial::tcp_thread_func 단순화 — outer m_sock_cv wait 패턴 (1.18.34/1.18.39 가 추가했던 것) 제거. open_socket 이 m_sock valid 보장하므로 reader thread 시작 시점에 simple inner recv loop 만. accept_handoff 의 replace (zombie cleanup) 케이스는 recv error 후 m_sock 변경 확인 + retry 로 처리. (5) reading_thread_func 의 `else if (m_sock != INVALID_SOCKET) tcp_thread_func();` 가드 복원 (1.18.39 가 제거). (6) Serial::write 의 silent-fail 케이스에 WARN trace 추가 — 미래 동종 회귀 즉시 노출. (7) PortRouter::subscribe 가 같은 (port, type) 에서 같은 IP filter 또는 둘 이상 catch-all 발견 시 reject (config 실수 원천 차단, 옛 WARN-only 동작 강화). (8) DISPLAY::bed_manager_thread_func 의 restart 블록 제거 — ptcon + adt 만 담당. "Waiting for next patient" TRACE 가 Linux/console 에서 1초 → 60초마다 throttle (별개 journald 부풀림 fix). 영향 범위: Intellivue / S5 / Datex / Hamilton / MPS / Agilia / IAP TCP-server 모드 베드 전부. 1.18.34 이후 우회 방법은 race-win 케이스 (VRN 이 PortRouter::subscribe 직후 connect) 외엔 없었음. MFC + Qt + Pi (RPI64) + Ubuntu Qt + AppImage 빌드 모두 통과.
1.18.40
Hotfix: Drager Medibus 프로토콜 사용 베드에서 1.15.11 이후 vr.service 가 SIGSEGV 로 무한 재시작하던 문제. 1.15.10 (a8c7b2a) 의 "Sampling rate estimation" 리팩터링이 m_ptrk_awp/awf/co2 트랙을 add_trks() 에서의 eager 생성 (default srate=62.5Hz) 에서 realtime data 100 샘플 모인 후 lazy 생성으로 바꿨는데, Medibus 핸드셰이크 순서 (0x51 ICC → 0x52 DeviceID → 0x53 RealtimeConfig 응답 → 0x54 → realtime data 시작) 상 0x53 응답 핸들러 (Drager.cpp:295-307) 가 트랙들이 아직 nullptr 인 시점에 `ptrk->m_srate = 1000000.0/interval` 로 NULL 역참조 → 즉시 SEGV. 그 베드의 vr.service 가 systemd Restart=on-failure 로 재시작 → 다시 connect → 동일 0x53 패킷 받고 동일 SEGV → 무한 루프 (journald 로그상 restart counter 61 까지 누적, code=killed status=11/SEGV). 1.15.9 까진 트랙이 add_trks() 에서 생성되어 0x53 도착 시 항상 non-NULL 이라 안전했음. 수정: (a) add_trks() 에서 m_ptrk_awp/awf/co2 eager 생성 복원 (srate=62.5Hz default) — 0x53 는 ptrk->m_srate 만 갱신 (트랙 재생성 X). (b) on_received() 의 lazy creation + packet-timing srate 추정 블록 제거 — 0x53 가 권위 있는 srate 소스이고 packet 타이밍 기반 추정은 network jitter 에 약해 불안정. (c) 0x53 핸들러에 `if (interval > 0)` guard 추가 — 페이로드 깨졌을 때 div-by-zero / inf srate 방지. 영향 범위: Drager Primus / Apollo / Perseus / Fabius 등 Medibus 사용 마취기 전부. 우회 방법은 1.15.9 다운그레이드 외엔 없었음 (그 베드의 모든 데이터 유실 + 환자 모니터링 불가). MFC + Qt + Pi (RPI64) + Ubuntu Qt + AppImage 빌드 모두 통과.
1.18.39
Fixed: PortRouter (TCP server) 모드에서 reader thread 가 startup 시점에 죽어 client 데이터를 영구히 못 받던 문제. 1.18.34 (9bd252f) 의 PortRouter refactor 가 m_sock 세팅을 dispatch 시점으로 옮겼는데 reading_thread_func (Serial.cpp:354) 의 가드 `else if (m_sock != INVALID_SOCKET) tcp_thread_func();` 는 옛 모델 (open 시점 이미 connect 완료) 가정 그대로라 — 시작 직후 m_sock=INVALID 면 tcp_thread_func 진입 안 하고 reader thread 즉시 종료. 이후 VRN 이 connect → PortRouter::dispatch → accept_handoff 가 m_sock 채우고 m_sock_cv.notify_all() → 그러나 wait 중인 reader 가 없음 (이미 죽음) → kernel recv buffer 에만 데이터 적재. 증상은 ss -tn state established 의 Recv-Q 가 0 이 아닌 값 (수백 bytes) 으로 누적, 베드 화면에는 데이터 안 들어옴. 그 베드의 VRN 이 Serial::open() 의 PortRouter::subscribe 직후~reader thread spawn 사이의 짧은 윈도우 안에 connect 한 경우만 우연히 정상 동작 (race-win). 수정: 가드 제거 — TCP 분기에 항상 진입. tcp_thread_func 자체에 이미 m_sock_cv outer-wait 루프가 있어 m_sock 이 INVALID 인 동안 dispatch 가 깨워줄 때까지 안전하게 대기. Pi 핫스팟에 VRN 여러 대 (e.g. 10.42.0.21~25) 를 같은 (4343, Philips:Intellivue) 키로 묶고 IP filter 로 라우팅하는 시나리오에서 가장 자주 노출. Added: 진단 로그 보강 — PortRouter::accept_loop 의 accept 이벤트, dispatch 의 라우팅 결정 (ip_specific/catch_all subs 수, 어느 dev 가 받았는지), subscribe/unsubscribe 의 entry 생성/append/destroy, reading_thread / tcp_thread 의 시작·wait·wake 라이프사이클 — 이전엔 모두 m_bDebug 게이트 안에 있어서 production journald 에서 안 보였는데, 이제 항상 출력 (이번 같은 dispatch routing 문제 진단을 production 에서 바로 가능). MFC + Qt + Pi (RPI64) + Ubuntu Qt + AppImage 빌드 모두 통과.
1.18.44
1.18.44: vr.conf FHIR=1 모드 추가 (자체 FHIR 수신서버 대상 실시간 푸시) + VS 2026 / 새 OpenSSL 인스톨러 빌드 환경 호환 shim. (1) sendweb_thread_func 의 기존 HL7/JSON 양분 분기 (VRApp.cpp:2466) 옆에 bFHIR 분기 추가. 1초 tick 마다 한 vrcode 의 모든 방 Observation 을 하나의 FHIR R4 collection Bundle 로 묶어 기존 WSS 채널로 push (gzip + WSSClient 인프라 그대로 재사용). MLLP 같은 신규 transport 안 도입 — FHIR R5 Subscription 의 websocket channel type 과 동형이라 표준 부합. (2) DISPLAY::get_fhir + TRACK::get_fhir 신규. v2 (vitaldb-fhir-v2) 와 동일한 reference-only 패턴 (Patient.id = m_patient_id, Encounter.id = vrcode-bedname-startsec, Observation.subject/encounter = relative reference) + hybrid keyframe: 첫 tick / m_patient_id 변경 / get_start_time() 변경 / 5분 경과 중 하나라도 발생 시 다음 tick Bundle 에 Patient + Encounter resource 를 inline. 그 외 tick 은 Observation 만. 수신서버가 도중에 새로 connect 해도 최대 5분 안에 자동 동기화 (keyframe heartbeat). DISPLAY 에 m_fhir_pt_sent_at/m_fhir_pt_last_id/m_fhir_enc_last_start 3개 멤버 추가 (m_setting_sent 패턴 그대로 per-display). (3) Observation 인코딩: NUM 은 effectiveDateTime + valueQuantity 각 데이터 포인트당 한 entry, WAV 은 effectivePeriod + valueSampledData (period=1000/srate ms, factor=1, origin=0, data=공백구분 floats) 한 entry. CO2/AWP wav 는 기존 get_json 처럼 25Hz 다운샘플 유지. NUM 은 m_montype 기반 LOINC 매핑 15개 등록 (ECG_HR/PLETH_HR=8867-4, PLETH_SPO2=59408-5, NIBP/IABP SBP/DBP/MBP=8480-6/8462-4/8478-0, BT=8310-5, RR=9279-1, etCO2=19891-1, CVP=8556-0, FETAL_HR=11627-3) + VR 자체 코드 시스템 (https://vitaldb.net/fhir/CodeSystem/track, code = dname/tname) 항상 동반. m_patient_id 미설정 (PATIENT_ID_UNSET) 시 anon-vrcode-bedname identifier 로 fallback. (4) Bundle wrapper: VRApp 가 방별 entries 를 콤마로 합쳐 {resourceType:Bundle, type:collection, timestamp:iso8601Z, entry:[...]} 로 wrap, 기존 gzip → WSS pipeline 동일. ISO 8601 UTC 헬퍼는 std::gmtime_s/gmtime_r 인라인. FHIR id 안전화 (A-Za-z0-9-.) 64자 제한 lambda 각 함수에 inline. (5) 빌드 환경 호환성: VRQt/CMakeLists.txt 에 _SILENCE_EXPERIMENTAL_COROUTINE_DEPRECATION_WARNINGS 추가 — VS 2026 (MSVC 14.51+) 의 STL 이 <experimental/coroutine> deprecation 을 warning → hard static_assert 로 바꾼 것이 cppwinrt 의 winrt::* (Serial.cpp 의 Bluetooth LE) 를 끌어오는 모든 TU 에서 컴파일 실패 시켜서, build_mfc.py 가 이미 CL env 로 주입하던 것과 동치 shim 을 Qt 쪽에도. (6) VRQt/CMakeLists.txt 의 OpenSSL link 경로 시프트. 새 OpenSSL 인스톨러가 libssl_static.lib / libcrypto_static.lib 를 lib/VC/x64/{MT,MTd}/ 에 두고 v1.x 의 libssl64MT.lib / libcrypto64MT.lib 네이밍과 lib/VC/static 경로를 깬다. build_mfc.py 의 %LOCALAPPDATA%/openssl-compat 패턴 (옛 이름으로 복사한 사본 폴더) 을 그대로 따라 CMakeLists 도 그 폴더가 있으면 우선, 없으면 옛 lib/VC/static 으로 fallback. 부트스트랩 스크립트는 build_mfc.py 의 docstring 참조 (한 줄). MFC + Qt + RPi + Ubuntu CLI + AppImage 빌드 모두 통과. 동작 영향: vr.conf 에 FHIR=1 추가 안 한 기존 deployment 는 sendweb 가 종전 JSON 모드 그대로 (HL7=1 만 분기보다 우선) — 0-impact migration.
1.18.43
1.18.43: 멀티베드 console (PACU 10베드 Pi) syslog noise 80,500/hr → ~1,400/hr (-98.3%), cpu_usage 측정 1.16.12 (9f26e36) 이후 8년치 영구 -1 버그 수정, 멀티베드 환경 베드 식별용 [bedname / devicename] 로그 prefix 도입. 동기는 PACU production Pi (1.18.40) 4일치 syslog 분석 — 1.24M 라인 중 vr 라인 836K, idle 8시간 평균 시간당 80,500 라인 (베드 10 × 1초마다 "Waiting for next patient" × syslog 2x 중복). (1) VRApp.cpp:2696-2710 timer_thread_func 의 cpu usage 계산이 `if (totalcpu && m_cpu_total_last && totalcpu > m_cpu_total_last)` 가드 안에서만 m_cpu_total_last 갱신해 첫 호출 시 m_cpu_total_last=0 → else 분기 → -1 → m_cpu_total_last 갱신 안 됨 → 영원히 false → 영원히 -1 데드락. PACU 1,048 샘플 100% -1 실측 확인. m_cpu_total_last/m_cpu_used_last 갱신을 if 밖으로 빼서 첫 호출만 baseline 으로 -1, 두 번째 (60s 후) 부터 정상 측정. /proc/stat 의 iowait/irq/softirq 까지 7개 필드 fscanf 로 정확도 ±5% 개선. 부수: 같은 함수의 cpu_temp 가 `temp / 1000` int 나눗셈으로 소수점 절단되어 PACU 측정값이 정수 50/51/...,/58 9종만 출력 (millidegree 정밀도 손실) → `temp / 1000.0f` 로 fix. ram 계산은 `(totalram + totalswap) * mem_unit` 결과에 다시 mem_unit 곱하던 차원 혼용 (Linux 가 mem_unit=1 이라 우연히 정상 동작했지만 dimensionally wrong) → `(units1 + units2) * mem_unit` 로 정정 + underflow 가드. (2) DISPLAY::log(string/printf) + DEVICE::log(string/printf) 멤버 추가. DISPLAY::log 는 `[bedname] msg`, DEVICE::log 는 m_pdisp 위임 없이 직접 `[bedname / devicename] msg` 한 줄 prefix (nested `[bed] [dev]` 보다 가독성 우선). m_pdisp 또는 bedname null 케이스 safe fallback. (3) show_msg 의 console path TRACE 를 log() 로 교체 → "Waiting for next patient" 자동 [bedname] prefix. ADT patient id unset 도 log() 로. (4) 이미 수동으로 get_bedname() 을 인자로 넘기던 8군데 (DISPLAY ptcon_thread_func / add_trk / add_dev / del_dev / cut-recording, Device add_rec 콜백, FILT stop, VRApp add_tab/del_tab) 를 log() 로 전환 — 이중 prefix 방지 + 일관성. Serial::reading_thread / tcp_thread / open_socket waiting / timeout / interrupted / got-client 6군데 DEVICE::log() 전환 → `[bedname / Intellivue]` 등 자동 prefix, 멀티탭 환경에서 어느 베드 어느 device 출처인지 즉시 식별. (5) wss 재시도 폭주 throttle. WSSClient 에 m_verbose (debug step 로그 게이트) + m_dt_last_err_log (60s window) 추가. connect() 내부 step-by-step TRACE 4개 ("connecting" / "connect error" / "connected") 는 m_verbose 가드, 최종 "wss connect error" 는 60s window throttle (첫 줄 즉시, 그 다음 60s 1회). connect 성공 path 에서 throttle reset 으로 다음 disconnect → 첫 에러 즉시 로깅. sendweb_thread_func 의 "connecting websocket server" 호출자 라인도 같은 60s window, m_bDebug 면 verbose 활성화. PACU idle 8h 측정 7,024/hr (4종 × 1,756) → 약 60/hr (-99%). (6) "Waiting for next patient" / "ADT patient id unset" 의 60s throttle 자체는 feature/wav-mem-retention 브랜치 ptcon_thread_func 재구성 (b75928f 시리즈) 에서 이미 들어가 있어 이 릴리즈에 자동 포함. 종합 영향: idle 시간당 syslog 라인 80,500 → ~1,400 (-98.3%), active 시간당 14,900 → ~700 (-95%). 진단 측면: cpu_usage 가 부활하면서 웹모니터링 json 의 cpu_usage 필드도 한 번도 안 채워지던 게 정상 전송 (VRApp.cpp:2499 의 `if (m_cpu_usage > 0)` 가드가 영원히 false 였음). 별건: syslog 라인 2× 중복은 application 측이 아니라 rsyslog imuxsock + imjournal + journald.ForwardToSyslog=yes 의 Debian Bookworm 전형 misconfig — admin 측 fix (rsyslog.conf 에서 imjournal 주석 처리 + restart) 로 추가 50% 감소 가능 (별도 문서). MFC + Qt + Pi (RPI64) + Ubuntu CLI 빌드 모두 통과.
1.18.41
1.18.41 Hotfix: TCP-server protocol-initiator device (Intellivue/S5/Datex/Hamilton/MPS/Agilia/IAP) 가 open() 직후 보내는 첫 패킷이 wire 에 안 나가던 회귀. 1.18.34 의 PortRouter refactor 가 Serial::open_socket() 의 8초 handoff blocking 을 제거하고 비동기 모델로 전환했는데, protocol-initiator 의 open() 코드가 m_sock=INVALID 상태에서 write 를 시도해 silent fail (Linux 는 m_fd=0 이라 false 반환). Intellivue 케이스 production journald 의 Pi → ESP TX 700K~3M bytes 는 monitor 측 abort 로 늦게 트리거된 AB_SPDU_SI recovery assoc_req 후 request_thread 의 정상 polling 트래픽이지만 일부 베드 (abort 없는 monitor) 는 recovery 도 안 되어 영구 silent (RX 300 bytes). 수정 방향: open() 의 옛 contract ("return 시점에 통신 가능") 복구. (1) Serial::open_socket() TCP-server 분기가 PortRouter::subscribe 후 m_sock_cv 로 첫 client handoff 까지 block (timeout = device read_timeout, 기본 30s) — 옛 모델 그대로. lock_and_open 이 false 면 다음 tick 에서 retry. close 신호로 abort 가능. (2) Serial::close() 는 PortRouter::unsubscribe 안 함 — subscribe 는 device 생애 동안 sticky 라 read_timeout 으로 인한 close→re-open 사이클 동안 listen socket 이 유지되어 1.18.29/1.18.34 의 anti-churn 이익 보존. unsubscribe 는 ~Serial() 에서만. (3) Per-device lifecycle thread (DEVICE::m_thread_restart): 각 device 가 자기 open/close/timeout 재시작을 별도 thread 에서 관리. 옛 모델의 DISPLAY::bed_manager 가 베드의 모든 device 를 sequential iterate 하던 구조에선 한 device 의 blocking open() 이 같은 베드의 다른 device 의 lifecycle 을 차단했는데, per-device thread 로 분리하여 멀티 device 베드도 병렬 진행. add_dev 에서 start_lifecycle, del_dev / ~DISPLAY 에서 stop_lifecycle. (4) Serial::tcp_thread_func 단순화 — outer m_sock_cv wait 패턴 (1.18.34/1.18.39 가 추가했던 것) 제거. open_socket 이 m_sock valid 보장하므로 reader thread 시작 시점에 simple inner recv loop 만. accept_handoff 의 replace (zombie cleanup) 케이스는 recv error 후 m_sock 변경 확인 + retry 로 처리. (5) reading_thread_func 의 `else if (m_sock != INVALID_SOCKET) tcp_thread_func();` 가드 복원 (1.18.39 가 제거). (6) Serial::write 의 silent-fail 케이스에 WARN trace 추가 — 미래 동종 회귀 즉시 노출. (7) PortRouter::subscribe 가 같은 (port, type) 에서 같은 IP filter 또는 둘 이상 catch-all 발견 시 reject (config 실수 원천 차단, 옛 WARN-only 동작 강화). (8) DISPLAY::bed_manager_thread_func 의 restart 블록 제거 — ptcon + adt 만 담당. "Waiting for next patient" TRACE 가 Linux/console 에서 1초 → 60초마다 throttle (별개 journald 부풀림 fix). 영향 범위: Intellivue / S5 / Datex / Hamilton / MPS / Agilia / IAP TCP-server 모드 베드 전부. 1.18.34 이후 우회 방법은 race-win 케이스 (VRN 이 PortRouter::subscribe 직후 connect) 외엔 없었음. MFC + Qt + Pi (RPI64) + Ubuntu Qt + AppImage 빌드 모두 통과.
1.18.40
Hotfix: Drager Medibus 프로토콜 사용 베드에서 1.15.11 이후 vr.service 가 SIGSEGV 로 무한 재시작하던 문제. 1.15.10 (a8c7b2a) 의 "Sampling rate estimation" 리팩터링이 m_ptrk_awp/awf/co2 트랙을 add_trks() 에서의 eager 생성 (default srate=62.5Hz) 에서 realtime data 100 샘플 모인 후 lazy 생성으로 바꿨는데, Medibus 핸드셰이크 순서 (0x51 ICC → 0x52 DeviceID → 0x53 RealtimeConfig 응답 → 0x54 → realtime data 시작) 상 0x53 응답 핸들러 (Drager.cpp:295-307) 가 트랙들이 아직 nullptr 인 시점에 `ptrk->m_srate = 1000000.0/interval` 로 NULL 역참조 → 즉시 SEGV. 그 베드의 vr.service 가 systemd Restart=on-failure 로 재시작 → 다시 connect → 동일 0x53 패킷 받고 동일 SEGV → 무한 루프 (journald 로그상 restart counter 61 까지 누적, code=killed status=11/SEGV). 1.15.9 까진 트랙이 add_trks() 에서 생성되어 0x53 도착 시 항상 non-NULL 이라 안전했음. 수정: (a) add_trks() 에서 m_ptrk_awp/awf/co2 eager 생성 복원 (srate=62.5Hz default) — 0x53 는 ptrk->m_srate 만 갱신 (트랙 재생성 X). (b) on_received() 의 lazy creation + packet-timing srate 추정 블록 제거 — 0x53 가 권위 있는 srate 소스이고 packet 타이밍 기반 추정은 network jitter 에 약해 불안정. (c) 0x53 핸들러에 `if (interval > 0)` guard 추가 — 페이로드 깨졌을 때 div-by-zero / inf srate 방지. 영향 범위: Drager Primus / Apollo / Perseus / Fabius 등 Medibus 사용 마취기 전부. 우회 방법은 1.15.9 다운그레이드 외엔 없었음 (그 베드의 모든 데이터 유실 + 환자 모니터링 불가). MFC + Qt + Pi (RPI64) + Ubuntu Qt + AppImage 빌드 모두 통과.
1.18.39
Fixed: PortRouter (TCP server) 모드에서 reader thread 가 startup 시점에 죽어 client 데이터를 영구히 못 받던 문제. 1.18.34 (9bd252f) 의 PortRouter refactor 가 m_sock 세팅을 dispatch 시점으로 옮겼는데 reading_thread_func (Serial.cpp:354) 의 가드 `else if (m_sock != INVALID_SOCKET) tcp_thread_func();` 는 옛 모델 (open 시점 이미 connect 완료) 가정 그대로라 — 시작 직후 m_sock=INVALID 면 tcp_thread_func 진입 안 하고 reader thread 즉시 종료. 이후 VRN 이 connect → PortRouter::dispatch → accept_handoff 가 m_sock 채우고 m_sock_cv.notify_all() → 그러나 wait 중인 reader 가 없음 (이미 죽음) → kernel recv buffer 에만 데이터 적재. 증상은 ss -tn state established 의 Recv-Q 가 0 이 아닌 값 (수백 bytes) 으로 누적, 베드 화면에는 데이터 안 들어옴. 그 베드의 VRN 이 Serial::open() 의 PortRouter::subscribe 직후~reader thread spawn 사이의 짧은 윈도우 안에 connect 한 경우만 우연히 정상 동작 (race-win). 수정: 가드 제거 — TCP 분기에 항상 진입. tcp_thread_func 자체에 이미 m_sock_cv outer-wait 루프가 있어 m_sock 이 INVALID 인 동안 dispatch 가 깨워줄 때까지 안전하게 대기. Pi 핫스팟에 VRN 여러 대 (e.g. 10.42.0.21~25) 를 같은 (4343, Philips:Intellivue) 키로 묶고 IP filter 로 라우팅하는 시나리오에서 가장 자주 노출. Added: 진단 로그 보강 — PortRouter::accept_loop 의 accept 이벤트, dispatch 의 라우팅 결정 (ip_specific/catch_all subs 수, 어느 dev 가 받았는지), subscribe/unsubscribe 의 entry 생성/append/destroy, reading_thread / tcp_thread 의 시작·wait·wake 라이프사이클 — 이전엔 모두 m_bDebug 게이트 안에 있어서 production journald 에서 안 보였는데, 이제 항상 출력 (이번 같은 dispatch routing 문제 진단을 production 에서 바로 가능). MFC + Qt + Pi (RPI64) + Ubuntu Qt + AppImage 빌드 모두 통과.
1.18.40
Hotfix: Drager Medibus 프로토콜 사용 베드에서 1.15.11 이후 vr.service 가 SIGSEGV 로 무한 재시작하던 문제. 1.15.10 (a8c7b2a) 의 "Sampling rate estimation" 리팩터링이 m_ptrk_awp/awf/co2 트랙을 add_trks() 에서의 eager 생성 (default srate=62.5Hz) 에서 realtime data 100 샘플 모인 후 lazy 생성으로 바꿨는데, Medibus 핸드셰이크 순서 (0x51 ICC → 0x52 DeviceID → 0x53 RealtimeConfig 응답 → 0x54 → realtime data 시작) 상 0x53 응답 핸들러 (Drager.cpp:295-307) 가 트랙들이 아직 nullptr 인 시점에 `ptrk->m_srate = 1000000.0/interval` 로 NULL 역참조 → 즉시 SEGV. 그 베드의 vr.service 가 systemd Restart=on-failure 로 재시작 → 다시 connect → 동일 0x53 패킷 받고 동일 SEGV → 무한 루프 (journald 로그상 restart counter 61 까지 누적, code=killed status=11/SEGV). 1.15.9 까진 트랙이 add_trks() 에서 생성되어 0x53 도착 시 항상 non-NULL 이라 안전했음. 수정: (a) add_trks() 에서 m_ptrk_awp/awf/co2 eager 생성 복원 (srate=62.5Hz default) — 0x53 는 ptrk->m_srate 만 갱신 (트랙 재생성 X). (b) on_received() 의 lazy creation + packet-timing srate 추정 블록 제거 — 0x53 가 권위 있는 srate 소스이고 packet 타이밍 기반 추정은 network jitter 에 약해 불안정. (c) 0x53 핸들러에 `if (interval > 0)` guard 추가 — 페이로드 깨졌을 때 div-by-zero / inf srate 방지. 영향 범위: Drager Primus / Apollo / Perseus / Fabius 등 Medibus 사용 마취기 전부. 우회 방법은 1.15.9 다운그레이드 외엔 없었음 (그 베드의 모든 데이터 유실 + 환자 모니터링 불가). MFC + Qt + Pi (RPI64) + Ubuntu Qt + AppImage 빌드 모두 통과.
1.18.37
Refactored: 필터 다이얼로그의 python 런타임 — pyvital → openvital 0.3.0 로 교체 + 동봉 python.zip 슬림화. 핵심 동기 셋: (1) pyvital 0.6.0 이 tensorflow+torch+keras 를 hard-dep 으로 끌어들여 PyPI upgrade 가 사실상 깨짐, (2) 기존 zip 121 MB 안에 botocore/aiobotocore/s3fs/pyarrow/pandas/aiohttp 등 vitaldb 클라우드 업로드용 deps 가 ~250 MB unpacked 차지 — 필터 서버는 한 번도 쓴 적 없는 dead weight, (3) sanic 25.x 의 multi-process worker 가 openvital __main__.py 를 fork 후 재import 하면서 worker 가 module-top app instance 를 못 찾아 "Sanic app name not found" 로 죽음 — 단순 endpoint 2개 로컬호스트 서버에 async stack 자체가 과함. 해결: openvital 0.3.0 (commit b6f445b) 가 sanic 을 stdlib http.server (BaseHTTPRequestHandler + HTTPServer) 로 50 LOC 재작성 — wire protocol (GET / 필터목록 JSON, POST /<modname> gzip 본문) 동일해 VR 클라이언트 변경 0줄. 단일 스레드로 기존 sanic event loop semantics (cfgs/default_cfgs lock-free 변경) 보존. build_python_zip.py (NEW) 가 Python 3.11.9 embed + numpy + openecg + openvital (local checkout 우선, fallback PyPI) 만으로 27 MB zip 빌드 — 기존 121 MB 대비 78% 감소. ML 필터 (sv_dlapco/abp_hpi/ecg_classifier/ecg_beat_noise_detector) 는 [Hybrid 모델] 사용자가 Add filter 다이얼로그의 "Install ML filters" 버튼 누를 때 silent pip install openvital[all] (cmd 창 미노출 — VRApp::install_pip_package + run_pip_silent 이 자체 progress 다이얼로그에 stdout heartbeat 흘림). 버튼 라벨은 "Upgrade pyvital" → "Install ML filters" 로 의도 명시. openvital __main__.py 가 missing extras 를 graceful skip 하므로 base 환경에서도 11개 필터 (ECG QRS detector, HRV, MTWA, annotator, eeg_fft, nirs_cox, pkpd_3comp, pleth_dpop/ptt/pvi, resp_compliance) 즉시 사용 가능. Migration: 기존 사용자의 user_dir/python 폴더는 pyvital + 옛 deps 가 그대로 남아있고 vitaldb.net/python.zip 만 새로 올리면 (a) 기존 폴더 삭제 후 install_python 으로 새 zip 받으면 깔끔, (b) 폴더 유지 시 server 가 openvital 못 찾아 필터 다이얼로그 에러 → release note 에 명시 필요. MFC + Qt 양쪽 코드: VRGui.rc 의 IDC_UPGRADE 버튼 라벨 + DlgAddFilter.cpp 의 dist-info prefix 매칭 (pyvital- 8자 → openvital- 10자) + QtDlgAddFilter.cpp 도 동일 변경 + onUpgrade 가 cmd /k QProcess::startDetached 대신 theApp.install_pip_package("openvital[all]") 호출 (Linux Qt 빌드는 #ifdef _WIN32 가드). VRApp.cpp:318 의 python -m pyvital → -m openvital. install_pip_package 는 anonymous namespace 의 is_safe_pkg_spec 화이트리스트 검증 + run_pip_silent (CreateProcessA + CREATE_NO_WINDOW + 자식 stdout pipe) + CDlgProgress / QtDlgProgress 자체 progress UI. 빌드 검증: python -m openvital 이 GET / 로 11 필터 반환, sanic import 부재 확인. zip 27.5 MB.
1.18.33
Fixed: Demo device never started since 1.18.11 (bed_manager skipped empty port_name as passive but Demo has no port and still needs lock_and_open); Fixed: Qt fit-to-window button rendered at top-left (0,0) instead of right-aligned in bottom nav bar; Fixed: Qt crash when closing tab / deleting device / deleting filter - paint dereferenced freed pointer before panel timer rebuilt items (added MFC-style stale-pointer guard to QtDeviceItem and QtFilterItem); Added: macOS DMG main window now shown on launch (restoreWindowPos had no Mac/Linux branch); Added: macOS Homebrew dylibs (openssl) bundled in Contents/Frameworks; Added: macOS .app bundle now notarized + stapled (drag-to-Applications no longer needs xattr -cr on first launch); Changed: Qt MSI ~1MB smaller (dropped unused iconengines and imageformats plugins)
1.18.31
Added: drug slot shows DOSE_AMOUNT (mg etc) in preference to VOL (mL) when available; Added: DOSE_RATE / DOSE_AMOUNT montypes bound to BBraun tracks for monitor view; Added: HL7 MDC_DOSE_DRUG_DELIV_TOTAL unit picked up dynamically from UCUM
1.18.44
1.18.44: vr.conf FHIR=1 모드 추가 (자체 FHIR 수신서버 대상 실시간 푸시) + VS 2026 / 새 OpenSSL 인스톨러 빌드 환경 호환 shim. (1) sendweb_thread_func 의 기존 HL7/JSON 양분 분기 (VRApp.cpp:2466) 옆에 bFHIR 분기 추가. 1초 tick 마다 한 vrcode 의 모든 방 Observation 을 하나의 FHIR R4 collection Bundle 로 묶어 기존 WSS 채널로 push (gzip + WSSClient 인프라 그대로 재사용). MLLP 같은 신규 transport 안 도입 — FHIR R5 Subscription 의 websocket channel type 과 동형이라 표준 부합. (2) DISPLAY::get_fhir + TRACK::get_fhir 신규. v2 (vitaldb-fhir-v2) 와 동일한 reference-only 패턴 (Patient.id = m_patient_id, Encounter.id = vrcode-bedname-startsec, Observation.subject/encounter = relative reference) + hybrid keyframe: 첫 tick / m_patient_id 변경 / get_start_time() 변경 / 5분 경과 중 하나라도 발생 시 다음 tick Bundle 에 Patient + Encounter resource 를 inline. 그 외 tick 은 Observation 만. 수신서버가 도중에 새로 connect 해도 최대 5분 안에 자동 동기화 (keyframe heartbeat). DISPLAY 에 m_fhir_pt_sent_at/m_fhir_pt_last_id/m_fhir_enc_last_start 3개 멤버 추가 (m_setting_sent 패턴 그대로 per-display). (3) Observation 인코딩: NUM 은 effectiveDateTime + valueQuantity 각 데이터 포인트당 한 entry, WAV 은 effectivePeriod + valueSampledData (period=1000/srate ms, factor=1, origin=0, data=공백구분 floats) 한 entry. CO2/AWP wav 는 기존 get_json 처럼 25Hz 다운샘플 유지. NUM 은 m_montype 기반 LOINC 매핑 15개 등록 (ECG_HR/PLETH_HR=8867-4, PLETH_SPO2=59408-5, NIBP/IABP SBP/DBP/MBP=8480-6/8462-4/8478-0, BT=8310-5, RR=9279-1, etCO2=19891-1, CVP=8556-0, FETAL_HR=11627-3) + VR 자체 코드 시스템 (https://vitaldb.net/fhir/CodeSystem/track, code = dname/tname) 항상 동반. m_patient_id 미설정 (PATIENT_ID_UNSET) 시 anon-vrcode-bedname identifier 로 fallback. (4) Bundle wrapper: VRApp 가 방별 entries 를 콤마로 합쳐 {resourceType:Bundle, type:collection, timestamp:iso8601Z, entry:[...]} 로 wrap, 기존 gzip → WSS pipeline 동일. ISO 8601 UTC 헬퍼는 std::gmtime_s/gmtime_r 인라인. FHIR id 안전화 (A-Za-z0-9-.) 64자 제한 lambda 각 함수에 inline. (5) 빌드 환경 호환성: VRQt/CMakeLists.txt 에 _SILENCE_EXPERIMENTAL_COROUTINE_DEPRECATION_WARNINGS 추가 — VS 2026 (MSVC 14.51+) 의 STL 이 <experimental/coroutine> deprecation 을 warning → hard static_assert 로 바꾼 것이 cppwinrt 의 winrt::* (Serial.cpp 의 Bluetooth LE) 를 끌어오는 모든 TU 에서 컴파일 실패 시켜서, build_mfc.py 가 이미 CL env 로 주입하던 것과 동치 shim 을 Qt 쪽에도. (6) VRQt/CMakeLists.txt 의 OpenSSL link 경로 시프트. 새 OpenSSL 인스톨러가 libssl_static.lib / libcrypto_static.lib 를 lib/VC/x64/{MT,MTd}/ 에 두고 v1.x 의 libssl64MT.lib / libcrypto64MT.lib 네이밍과 lib/VC/static 경로를 깬다. build_mfc.py 의 %LOCALAPPDATA%/openssl-compat 패턴 (옛 이름으로 복사한 사본 폴더) 을 그대로 따라 CMakeLists 도 그 폴더가 있으면 우선, 없으면 옛 lib/VC/static 으로 fallback. 부트스트랩 스크립트는 build_mfc.py 의 docstring 참조 (한 줄). MFC + Qt + RPi + Ubuntu CLI + AppImage 빌드 모두 통과. 동작 영향: vr.conf 에 FHIR=1 추가 안 한 기존 deployment 는 sendweb 가 종전 JSON 모드 그대로 (HL7=1 만 분기보다 우선) — 0-impact migration.
1.18.43
1.18.43: 멀티베드 console (PACU 10베드 Pi) syslog noise 80,500/hr → ~1,400/hr (-98.3%), cpu_usage 측정 1.16.12 (9f26e36) 이후 8년치 영구 -1 버그 수정, 멀티베드 환경 베드 식별용 [bedname / devicename] 로그 prefix 도입. 동기는 PACU production Pi (1.18.40) 4일치 syslog 분석 — 1.24M 라인 중 vr 라인 836K, idle 8시간 평균 시간당 80,500 라인 (베드 10 × 1초마다 "Waiting for next patient" × syslog 2x 중복). (1) VRApp.cpp:2696-2710 timer_thread_func 의 cpu usage 계산이 `if (totalcpu && m_cpu_total_last && totalcpu > m_cpu_total_last)` 가드 안에서만 m_cpu_total_last 갱신해 첫 호출 시 m_cpu_total_last=0 → else 분기 → -1 → m_cpu_total_last 갱신 안 됨 → 영원히 false → 영원히 -1 데드락. PACU 1,048 샘플 100% -1 실측 확인. m_cpu_total_last/m_cpu_used_last 갱신을 if 밖으로 빼서 첫 호출만 baseline 으로 -1, 두 번째 (60s 후) 부터 정상 측정. /proc/stat 의 iowait/irq/softirq 까지 7개 필드 fscanf 로 정확도 ±5% 개선. 부수: 같은 함수의 cpu_temp 가 `temp / 1000` int 나눗셈으로 소수점 절단되어 PACU 측정값이 정수 50/51/...,/58 9종만 출력 (millidegree 정밀도 손실) → `temp / 1000.0f` 로 fix. ram 계산은 `(totalram + totalswap) * mem_unit` 결과에 다시 mem_unit 곱하던 차원 혼용 (Linux 가 mem_unit=1 이라 우연히 정상 동작했지만 dimensionally wrong) → `(units1 + units2) * mem_unit` 로 정정 + underflow 가드. (2) DISPLAY::log(string/printf) + DEVICE::log(string/printf) 멤버 추가. DISPLAY::log 는 `[bedname] msg`, DEVICE::log 는 m_pdisp 위임 없이 직접 `[bedname / devicename] msg` 한 줄 prefix (nested `[bed] [dev]` 보다 가독성 우선). m_pdisp 또는 bedname null 케이스 safe fallback. (3) show_msg 의 console path TRACE 를 log() 로 교체 → "Waiting for next patient" 자동 [bedname] prefix. ADT patient id unset 도 log() 로. (4) 이미 수동으로 get_bedname() 을 인자로 넘기던 8군데 (DISPLAY ptcon_thread_func / add_trk / add_dev / del_dev / cut-recording, Device add_rec 콜백, FILT stop, VRApp add_tab/del_tab) 를 log() 로 전환 — 이중 prefix 방지 + 일관성. Serial::reading_thread / tcp_thread / open_socket waiting / timeout / interrupted / got-client 6군데 DEVICE::log() 전환 → `[bedname / Intellivue]` 등 자동 prefix, 멀티탭 환경에서 어느 베드 어느 device 출처인지 즉시 식별. (5) wss 재시도 폭주 throttle. WSSClient 에 m_verbose (debug step 로그 게이트) + m_dt_last_err_log (60s window) 추가. connect() 내부 step-by-step TRACE 4개 ("connecting" / "connect error" / "connected") 는 m_verbose 가드, 최종 "wss connect error" 는 60s window throttle (첫 줄 즉시, 그 다음 60s 1회). connect 성공 path 에서 throttle reset 으로 다음 disconnect → 첫 에러 즉시 로깅. sendweb_thread_func 의 "connecting websocket server" 호출자 라인도 같은 60s window, m_bDebug 면 verbose 활성화. PACU idle 8h 측정 7,024/hr (4종 × 1,756) → 약 60/hr (-99%). (6) "Waiting for next patient" / "ADT patient id unset" 의 60s throttle 자체는 feature/wav-mem-retention 브랜치 ptcon_thread_func 재구성 (b75928f 시리즈) 에서 이미 들어가 있어 이 릴리즈에 자동 포함. 종합 영향: idle 시간당 syslog 라인 80,500 → ~1,400 (-98.3%), active 시간당 14,900 → ~700 (-95%). 진단 측면: cpu_usage 가 부활하면서 웹모니터링 json 의 cpu_usage 필드도 한 번도 안 채워지던 게 정상 전송 (VRApp.cpp:2499 의 `if (m_cpu_usage > 0)` 가드가 영원히 false 였음). 별건: syslog 라인 2× 중복은 application 측이 아니라 rsyslog imuxsock + imjournal + journald.ForwardToSyslog=yes 의 Debian Bookworm 전형 misconfig — admin 측 fix (rsyslog.conf 에서 imjournal 주석 처리 + restart) 로 추가 50% 감소 가능 (별도 문서). MFC + Qt + Pi (RPI64) + Ubuntu CLI 빌드 모두 통과.
1.18.41
1.18.41 Hotfix: TCP-server protocol-initiator device (Intellivue/S5/Datex/Hamilton/MPS/Agilia/IAP) 가 open() 직후 보내는 첫 패킷이 wire 에 안 나가던 회귀. 1.18.34 의 PortRouter refactor 가 Serial::open_socket() 의 8초 handoff blocking 을 제거하고 비동기 모델로 전환했는데, protocol-initiator 의 open() 코드가 m_sock=INVALID 상태에서 write 를 시도해 silent fail (Linux 는 m_fd=0 이라 false 반환). Intellivue 케이스 production journald 의 Pi → ESP TX 700K~3M bytes 는 monitor 측 abort 로 늦게 트리거된 AB_SPDU_SI recovery assoc_req 후 request_thread 의 정상 polling 트래픽이지만 일부 베드 (abort 없는 monitor) 는 recovery 도 안 되어 영구 silent (RX 300 bytes). 수정 방향: open() 의 옛 contract ("return 시점에 통신 가능") 복구. (1) Serial::open_socket() TCP-server 분기가 PortRouter::subscribe 후 m_sock_cv 로 첫 client handoff 까지 block (timeout = device read_timeout, 기본 30s) — 옛 모델 그대로. lock_and_open 이 false 면 다음 tick 에서 retry. close 신호로 abort 가능. (2) Serial::close() 는 PortRouter::unsubscribe 안 함 — subscribe 는 device 생애 동안 sticky 라 read_timeout 으로 인한 close→re-open 사이클 동안 listen socket 이 유지되어 1.18.29/1.18.34 의 anti-churn 이익 보존. unsubscribe 는 ~Serial() 에서만. (3) Per-device lifecycle thread (DEVICE::m_thread_restart): 각 device 가 자기 open/close/timeout 재시작을 별도 thread 에서 관리. 옛 모델의 DISPLAY::bed_manager 가 베드의 모든 device 를 sequential iterate 하던 구조에선 한 device 의 blocking open() 이 같은 베드의 다른 device 의 lifecycle 을 차단했는데, per-device thread 로 분리하여 멀티 device 베드도 병렬 진행. add_dev 에서 start_lifecycle, del_dev / ~DISPLAY 에서 stop_lifecycle. (4) Serial::tcp_thread_func 단순화 — outer m_sock_cv wait 패턴 (1.18.34/1.18.39 가 추가했던 것) 제거. open_socket 이 m_sock valid 보장하므로 reader thread 시작 시점에 simple inner recv loop 만. accept_handoff 의 replace (zombie cleanup) 케이스는 recv error 후 m_sock 변경 확인 + retry 로 처리. (5) reading_thread_func 의 `else if (m_sock != INVALID_SOCKET) tcp_thread_func();` 가드 복원 (1.18.39 가 제거). (6) Serial::write 의 silent-fail 케이스에 WARN trace 추가 — 미래 동종 회귀 즉시 노출. (7) PortRouter::subscribe 가 같은 (port, type) 에서 같은 IP filter 또는 둘 이상 catch-all 발견 시 reject (config 실수 원천 차단, 옛 WARN-only 동작 강화). (8) DISPLAY::bed_manager_thread_func 의 restart 블록 제거 — ptcon + adt 만 담당. "Waiting for next patient" TRACE 가 Linux/console 에서 1초 → 60초마다 throttle (별개 journald 부풀림 fix). 영향 범위: Intellivue / S5 / Datex / Hamilton / MPS / Agilia / IAP TCP-server 모드 베드 전부. 1.18.34 이후 우회 방법은 race-win 케이스 (VRN 이 PortRouter::subscribe 직후 connect) 외엔 없었음. MFC + Qt + Pi (RPI64) + Ubuntu Qt + AppImage 빌드 모두 통과.
1.18.40
Hotfix: Drager Medibus 프로토콜 사용 베드에서 1.15.11 이후 vr.service 가 SIGSEGV 로 무한 재시작하던 문제. 1.15.10 (a8c7b2a) 의 "Sampling rate estimation" 리팩터링이 m_ptrk_awp/awf/co2 트랙을 add_trks() 에서의 eager 생성 (default srate=62.5Hz) 에서 realtime data 100 샘플 모인 후 lazy 생성으로 바꿨는데, Medibus 핸드셰이크 순서 (0x51 ICC → 0x52 DeviceID → 0x53 RealtimeConfig 응답 → 0x54 → realtime data 시작) 상 0x53 응답 핸들러 (Drager.cpp:295-307) 가 트랙들이 아직 nullptr 인 시점에 `ptrk->m_srate = 1000000.0/interval` 로 NULL 역참조 → 즉시 SEGV. 그 베드의 vr.service 가 systemd Restart=on-failure 로 재시작 → 다시 connect → 동일 0x53 패킷 받고 동일 SEGV → 무한 루프 (journald 로그상 restart counter 61 까지 누적, code=killed status=11/SEGV). 1.15.9 까진 트랙이 add_trks() 에서 생성되어 0x53 도착 시 항상 non-NULL 이라 안전했음. 수정: (a) add_trks() 에서 m_ptrk_awp/awf/co2 eager 생성 복원 (srate=62.5Hz default) — 0x53 는 ptrk->m_srate 만 갱신 (트랙 재생성 X). (b) on_received() 의 lazy creation + packet-timing srate 추정 블록 제거 — 0x53 가 권위 있는 srate 소스이고 packet 타이밍 기반 추정은 network jitter 에 약해 불안정. (c) 0x53 핸들러에 `if (interval > 0)` guard 추가 — 페이로드 깨졌을 때 div-by-zero / inf srate 방지. 영향 범위: Drager Primus / Apollo / Perseus / Fabius 등 Medibus 사용 마취기 전부. 우회 방법은 1.15.9 다운그레이드 외엔 없었음 (그 베드의 모든 데이터 유실 + 환자 모니터링 불가). MFC + Qt + Pi (RPI64) + Ubuntu Qt + AppImage 빌드 모두 통과.
1.18.39
Fixed: PortRouter (TCP server) 모드에서 reader thread 가 startup 시점에 죽어 client 데이터를 영구히 못 받던 문제. 1.18.34 (9bd252f) 의 PortRouter refactor 가 m_sock 세팅을 dispatch 시점으로 옮겼는데 reading_thread_func (Serial.cpp:354) 의 가드 `else if (m_sock != INVALID_SOCKET) tcp_thread_func();` 는 옛 모델 (open 시점 이미 connect 완료) 가정 그대로라 — 시작 직후 m_sock=INVALID 면 tcp_thread_func 진입 안 하고 reader thread 즉시 종료. 이후 VRN 이 connect → PortRouter::dispatch → accept_handoff 가 m_sock 채우고 m_sock_cv.notify_all() → 그러나 wait 중인 reader 가 없음 (이미 죽음) → kernel recv buffer 에만 데이터 적재. 증상은 ss -tn state established 의 Recv-Q 가 0 이 아닌 값 (수백 bytes) 으로 누적, 베드 화면에는 데이터 안 들어옴. 그 베드의 VRN 이 Serial::open() 의 PortRouter::subscribe 직후~reader thread spawn 사이의 짧은 윈도우 안에 connect 한 경우만 우연히 정상 동작 (race-win). 수정: 가드 제거 — TCP 분기에 항상 진입. tcp_thread_func 자체에 이미 m_sock_cv outer-wait 루프가 있어 m_sock 이 INVALID 인 동안 dispatch 가 깨워줄 때까지 안전하게 대기. Pi 핫스팟에 VRN 여러 대 (e.g. 10.42.0.21~25) 를 같은 (4343, Philips:Intellivue) 키로 묶고 IP filter 로 라우팅하는 시나리오에서 가장 자주 노출. Added: 진단 로그 보강 — PortRouter::accept_loop 의 accept 이벤트, dispatch 의 라우팅 결정 (ip_specific/catch_all subs 수, 어느 dev 가 받았는지), subscribe/unsubscribe 의 entry 생성/append/destroy, reading_thread / tcp_thread 의 시작·wait·wake 라이프사이클 — 이전엔 모두 m_bDebug 게이트 안에 있어서 production journald 에서 안 보였는데, 이제 항상 출력 (이번 같은 dispatch routing 문제 진단을 production 에서 바로 가능). MFC + Qt + Pi (RPI64) + Ubuntu Qt + AppImage 빌드 모두 통과.
Apple Silicon (arm64) only · Requires macOS 11 or later · Developer ID signed & notarized
If you use the VitalRecorder in your research, please cite the following publication:
Lee HC, Jung CW. VitalRecorder-a free research tool for automatic recording of
high-resolution time-synchronised physiological data from multiple anaesthesia devices. Sci Rep.
2018 Jan 24;8(1):1527. doi:
10.1038/s41598-018-20062-4. Available from: https://www.nature.com/articles/s41598-018-20062-4