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 177,953 times since Feb 2017)
OS:
Version:
View All Versions
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.38
Fixed: 웹모니터링 우클릭 → 디바이스 세팅 (websocket edit_bed 명령) 으로 베드의 device/filter 를 변경할 때 베드 이름이 매번 "_2" suffix 로 자동 리네임되어 vr.conf (Pi: /data/vr.conf) 에 잘못된 이름으로 저장되던 문제. 원인은 1.18.22 (cda8aa5) 에서 add_bed 에 추가된 bedname dedup ("최후 방어선" 으로 forward_frame auto-create / 수동 편집된 conf 의 중복 이름 처리용). edit_bed 핸들러는 옛 pdel 이 m_pdisps 에 그대로 있는 상태에서 같은 bedname 으로 new DISPLAY 를 add_bed 하고 그 다음 del_bed(pdel) 하는 순서였는데, add_bed 가 dup 감지해 새 pdisp 를 bedname_2 로 자동 리네임 → save_settings 가 [BED/bedname_2] 로 직렬화 → 사용자 입장에선 "설정 적용 안됨" 으로 보였다. 추가로 옛/새 DISPLAY 가 잠깐 m_pdisps 에 공존하면서 같은 device 포트를 동시에 들고 있으려 해 로그가 dup 으로 찍히는 부수 증상도 있었음. 수정: del_bed(pdel) 를 add_bed(pdisp) 보다 앞으로 이동 — 옛 베드의 device 포트가 깨끗이 release 된 뒤 새 pdisp 가 같은 포트를 reopen, dedup 발동 안 함. 다른 베드의 recording 은 영향 없음 (인메모리 hot swap 설계 유지). 부수: edit_bed 핸들러의 데드 코드 (`break` 뒤의 unreachable olddevs populate 루프 + `if (olddevs.find(name) == olddevs.end())` 항상-true 래퍼) 정리. edit_conf 와 save_settings 는 손대지 않음. MFC + Qt + Pi (RPI64) 빌드 모두 통과.
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.36
Fixed: 3-파티션 레이아웃 (boot/rootfs/data) 에서 websocket edit_conf / update 명령이 read-only fs 로 실패하던 문제. vr.conf 는 /data 폴더가 있으면 /data/vr.conf 로 자동 정착 — get_conf_path() 가 write target 으로 결정, load_settings() 는 primary 가 없으면 legacy /boot/firmware/vr.conf 또는 /boot/vr.conf 에서 fallback read 후 다음 save 부터 primary 로 자동 이전 (마이그레이션 step 없음, legacy 파일은 read-only fs 라 무해). binary update (upgrade_now) 는 RAII BootRwGuard 가 dirname(module_path) 를 잠시 rw remount, scope 종료 시 sync() + MS_REMOUNT|MS_RDONLY 로 자동 ro 복귀 — vfat 노출 윈도우 최소화 + exception/return 어디서든 복구 보장. statvfs ST_RDONLY 검사로 이미 rw 인 fs (x86 dev) 에서는 no-op, non-Linux (macOS) 는 stub 으로 통과. save_settings() 의 silent EROFS 실패가 TRACE 로 가시화 (이전엔 무시되어 conf 수정 안 먹는 원인 추적 불가). 부수: PortRouter.cpp 가 commit 9bd252f 이후 CMakeLists 의 RPI sources 목록에서 누락되어 Pi 빌드 link 에러 — 추가.
1.18.35
Added: alarm parsing for Mindray HL7 (eGateway ORU^R40) and Nihon Kohden (NealTime HL7GW + ORF, BSM serial D-port, EGA UDP) - emits ALARM_STATUS / ALARM_MESSAGE / ALARM_PRIORITY tracks (same 3-track schema already used by Philips IntelliVue, GE Solar/Dash, GE/Datex S5); type info preserved via [T]/[A]/[ARR] message prefixes; Mindray facet 1/5/6/7 walker with 92-entry MDC+MNDRY alarm code lookup; NK HL7GW parses OBR-4=EVENT with arrhythmia/technical disambiguation by OBX-5; NK BSM serial decodes per-parameter * flags (edge-triggered to avoid 1Hz flooding) plus 32-bit Table 9 arrhythmia bitmap, D19 alarm-suspend nibble, and the VPC CODE byte; NK EGA UDP extracts Priority + Tech Alarm + Arrhythmia from JSON Vital Sign packets with edge-trigger cache for start/end inference; ACK^R40 reply added for Mindray alert messages; simulators gained periodic alarm cycling for end-to-end smoke testing
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.38
Fixed: 웹모니터링 우클릭 → 디바이스 세팅 (websocket edit_bed 명령) 으로 베드의 device/filter 를 변경할 때 베드 이름이 매번 "_2" suffix 로 자동 리네임되어 vr.conf (Pi: /data/vr.conf) 에 잘못된 이름으로 저장되던 문제. 원인은 1.18.22 (cda8aa5) 에서 add_bed 에 추가된 bedname dedup ("최후 방어선" 으로 forward_frame auto-create / 수동 편집된 conf 의 중복 이름 처리용). edit_bed 핸들러는 옛 pdel 이 m_pdisps 에 그대로 있는 상태에서 같은 bedname 으로 new DISPLAY 를 add_bed 하고 그 다음 del_bed(pdel) 하는 순서였는데, add_bed 가 dup 감지해 새 pdisp 를 bedname_2 로 자동 리네임 → save_settings 가 [BED/bedname_2] 로 직렬화 → 사용자 입장에선 "설정 적용 안됨" 으로 보였다. 추가로 옛/새 DISPLAY 가 잠깐 m_pdisps 에 공존하면서 같은 device 포트를 동시에 들고 있으려 해 로그가 dup 으로 찍히는 부수 증상도 있었음. 수정: del_bed(pdel) 를 add_bed(pdisp) 보다 앞으로 이동 — 옛 베드의 device 포트가 깨끗이 release 된 뒤 새 pdisp 가 같은 포트를 reopen, dedup 발동 안 함. 다른 베드의 recording 은 영향 없음 (인메모리 hot swap 설계 유지). 부수: edit_bed 핸들러의 데드 코드 (`break` 뒤의 unreachable olddevs populate 루프 + `if (olddevs.find(name) == olddevs.end())` 항상-true 래퍼) 정리. edit_conf 와 save_settings 는 손대지 않음. MFC + Qt + Pi (RPI64) 빌드 모두 통과.
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.36
Fixed: 3-파티션 레이아웃 (boot/rootfs/data) 에서 websocket edit_conf / update 명령이 read-only fs 로 실패하던 문제. vr.conf 는 /data 폴더가 있으면 /data/vr.conf 로 자동 정착 — get_conf_path() 가 write target 으로 결정, load_settings() 는 primary 가 없으면 legacy /boot/firmware/vr.conf 또는 /boot/vr.conf 에서 fallback read 후 다음 save 부터 primary 로 자동 이전 (마이그레이션 step 없음, legacy 파일은 read-only fs 라 무해). binary update (upgrade_now) 는 RAII BootRwGuard 가 dirname(module_path) 를 잠시 rw remount, scope 종료 시 sync() + MS_REMOUNT|MS_RDONLY 로 자동 ro 복귀 — vfat 노출 윈도우 최소화 + exception/return 어디서든 복구 보장. statvfs ST_RDONLY 검사로 이미 rw 인 fs (x86 dev) 에서는 no-op, non-Linux (macOS) 는 stub 으로 통과. save_settings() 의 silent EROFS 실패가 TRACE 로 가시화 (이전엔 무시되어 conf 수정 안 먹는 원인 추적 불가). 부수: PortRouter.cpp 가 commit 9bd252f 이후 CMakeLists 의 RPI sources 목록에서 누락되어 Pi 빌드 link 에러 — 추가.
1.18.35
Added: alarm parsing for Mindray HL7 (eGateway ORU^R40) and Nihon Kohden (NealTime HL7GW + ORF, BSM serial D-port, EGA UDP) - emits ALARM_STATUS / ALARM_MESSAGE / ALARM_PRIORITY tracks (same 3-track schema already used by Philips IntelliVue, GE Solar/Dash, GE/Datex S5); type info preserved via [T]/[A]/[ARR] message prefixes; Mindray facet 1/5/6/7 walker with 92-entry MDC+MNDRY alarm code lookup; NK HL7GW parses OBR-4=EVENT with arrhythmia/technical disambiguation by OBX-5; NK BSM serial decodes per-parameter * flags (edge-triggered to avoid 1Hz flooding) plus 32-bit Table 9 arrhythmia bitmap, D19 alarm-suspend nibble, and the VPC CODE byte; NK EGA UDP extracts Priority + Tech Alarm + Arrhythmia from JSON Vital Sign packets with edge-trigger cache for start/end inference; ACK^R40 reply added for Mindray alert messages; simulators gained periodic alarm cycling for end-to-end smoke testing
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.38
Fixed: 웹모니터링 우클릭 → 디바이스 세팅 (websocket edit_bed 명령) 으로 베드의 device/filter 를 변경할 때 베드 이름이 매번 "_2" suffix 로 자동 리네임되어 vr.conf (Pi: /data/vr.conf) 에 잘못된 이름으로 저장되던 문제. 원인은 1.18.22 (cda8aa5) 에서 add_bed 에 추가된 bedname dedup ("최후 방어선" 으로 forward_frame auto-create / 수동 편집된 conf 의 중복 이름 처리용). edit_bed 핸들러는 옛 pdel 이 m_pdisps 에 그대로 있는 상태에서 같은 bedname 으로 new DISPLAY 를 add_bed 하고 그 다음 del_bed(pdel) 하는 순서였는데, add_bed 가 dup 감지해 새 pdisp 를 bedname_2 로 자동 리네임 → save_settings 가 [BED/bedname_2] 로 직렬화 → 사용자 입장에선 "설정 적용 안됨" 으로 보였다. 추가로 옛/새 DISPLAY 가 잠깐 m_pdisps 에 공존하면서 같은 device 포트를 동시에 들고 있으려 해 로그가 dup 으로 찍히는 부수 증상도 있었음. 수정: del_bed(pdel) 를 add_bed(pdisp) 보다 앞으로 이동 — 옛 베드의 device 포트가 깨끗이 release 된 뒤 새 pdisp 가 같은 포트를 reopen, dedup 발동 안 함. 다른 베드의 recording 은 영향 없음 (인메모리 hot swap 설계 유지). 부수: edit_bed 핸들러의 데드 코드 (`break` 뒤의 unreachable olddevs populate 루프 + `if (olddevs.find(name) == olddevs.end())` 항상-true 래퍼) 정리. edit_conf 와 save_settings 는 손대지 않음. MFC + Qt + Pi (RPI64) 빌드 모두 통과.
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.36
Fixed: 3-파티션 레이아웃 (boot/rootfs/data) 에서 websocket edit_conf / update 명령이 read-only fs 로 실패하던 문제. vr.conf 는 /data 폴더가 있으면 /data/vr.conf 로 자동 정착 — get_conf_path() 가 write target 으로 결정, load_settings() 는 primary 가 없으면 legacy /boot/firmware/vr.conf 또는 /boot/vr.conf 에서 fallback read 후 다음 save 부터 primary 로 자동 이전 (마이그레이션 step 없음, legacy 파일은 read-only fs 라 무해). binary update (upgrade_now) 는 RAII BootRwGuard 가 dirname(module_path) 를 잠시 rw remount, scope 종료 시 sync() + MS_REMOUNT|MS_RDONLY 로 자동 ro 복귀 — vfat 노출 윈도우 최소화 + exception/return 어디서든 복구 보장. statvfs ST_RDONLY 검사로 이미 rw 인 fs (x86 dev) 에서는 no-op, non-Linux (macOS) 는 stub 으로 통과. save_settings() 의 silent EROFS 실패가 TRACE 로 가시화 (이전엔 무시되어 conf 수정 안 먹는 원인 추적 불가). 부수: PortRouter.cpp 가 commit 9bd252f 이후 CMakeLists 의 RPI sources 목록에서 누락되어 Pi 빌드 link 에러 — 추가.
1.18.35
Added: alarm parsing for Mindray HL7 (eGateway ORU^R40) and Nihon Kohden (NealTime HL7GW + ORF, BSM serial D-port, EGA UDP) - emits ALARM_STATUS / ALARM_MESSAGE / ALARM_PRIORITY tracks (same 3-track schema already used by Philips IntelliVue, GE Solar/Dash, GE/Datex S5); type info preserved via [T]/[A]/[ARR] message prefixes; Mindray facet 1/5/6/7 walker with 92-entry MDC+MNDRY alarm code lookup; NK HL7GW parses OBR-4=EVENT with arrhythmia/technical disambiguation by OBX-5; NK BSM serial decodes per-parameter * flags (edge-triggered to avoid 1Hz flooding) plus 32-bit Table 9 arrhythmia bitmap, D19 alarm-suspend nibble, and the VPC CODE byte; NK EGA UDP extracts Priority + Tech Alarm + Arrhythmia from JSON Vital Sign packets with edge-trigger cache for start/end inference; ACK^R40 reply added for Mindray alert messages; simulators gained periodic alarm cycling for end-to-end smoke testing
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.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.38
Fixed: 웹모니터링 우클릭 → 디바이스 세팅 (websocket edit_bed 명령) 으로 베드의 device/filter 를 변경할 때 베드 이름이 매번 "_2" suffix 로 자동 리네임되어 vr.conf (Pi: /data/vr.conf) 에 잘못된 이름으로 저장되던 문제. 원인은 1.18.22 (cda8aa5) 에서 add_bed 에 추가된 bedname dedup ("최후 방어선" 으로 forward_frame auto-create / 수동 편집된 conf 의 중복 이름 처리용). edit_bed 핸들러는 옛 pdel 이 m_pdisps 에 그대로 있는 상태에서 같은 bedname 으로 new DISPLAY 를 add_bed 하고 그 다음 del_bed(pdel) 하는 순서였는데, add_bed 가 dup 감지해 새 pdisp 를 bedname_2 로 자동 리네임 → save_settings 가 [BED/bedname_2] 로 직렬화 → 사용자 입장에선 "설정 적용 안됨" 으로 보였다. 추가로 옛/새 DISPLAY 가 잠깐 m_pdisps 에 공존하면서 같은 device 포트를 동시에 들고 있으려 해 로그가 dup 으로 찍히는 부수 증상도 있었음. 수정: del_bed(pdel) 를 add_bed(pdisp) 보다 앞으로 이동 — 옛 베드의 device 포트가 깨끗이 release 된 뒤 새 pdisp 가 같은 포트를 reopen, dedup 발동 안 함. 다른 베드의 recording 은 영향 없음 (인메모리 hot swap 설계 유지). 부수: edit_bed 핸들러의 데드 코드 (`break` 뒤의 unreachable olddevs populate 루프 + `if (olddevs.find(name) == olddevs.end())` 항상-true 래퍼) 정리. edit_conf 와 save_settings 는 손대지 않음. MFC + Qt + Pi (RPI64) 빌드 모두 통과.
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.36
Fixed: 3-파티션 레이아웃 (boot/rootfs/data) 에서 websocket edit_conf / update 명령이 read-only fs 로 실패하던 문제. vr.conf 는 /data 폴더가 있으면 /data/vr.conf 로 자동 정착 — get_conf_path() 가 write target 으로 결정, load_settings() 는 primary 가 없으면 legacy /boot/firmware/vr.conf 또는 /boot/vr.conf 에서 fallback read 후 다음 save 부터 primary 로 자동 이전 (마이그레이션 step 없음, legacy 파일은 read-only fs 라 무해). binary update (upgrade_now) 는 RAII BootRwGuard 가 dirname(module_path) 를 잠시 rw remount, scope 종료 시 sync() + MS_REMOUNT|MS_RDONLY 로 자동 ro 복귀 — vfat 노출 윈도우 최소화 + exception/return 어디서든 복구 보장. statvfs ST_RDONLY 검사로 이미 rw 인 fs (x86 dev) 에서는 no-op, non-Linux (macOS) 는 stub 으로 통과. save_settings() 의 silent EROFS 실패가 TRACE 로 가시화 (이전엔 무시되어 conf 수정 안 먹는 원인 추적 불가). 부수: PortRouter.cpp 가 commit 9bd252f 이후 CMakeLists 의 RPI sources 목록에서 누락되어 Pi 빌드 link 에러 — 추가.
1.18.35
Added: alarm parsing for Mindray HL7 (eGateway ORU^R40) and Nihon Kohden (NealTime HL7GW + ORF, BSM serial D-port, EGA UDP) - emits ALARM_STATUS / ALARM_MESSAGE / ALARM_PRIORITY tracks (same 3-track schema already used by Philips IntelliVue, GE Solar/Dash, GE/Datex S5); type info preserved via [T]/[A]/[ARR] message prefixes; Mindray facet 1/5/6/7 walker with 92-entry MDC+MNDRY alarm code lookup; NK HL7GW parses OBR-4=EVENT with arrhythmia/technical disambiguation by OBX-5; NK BSM serial decodes per-parameter * flags (edge-triggered to avoid 1Hz flooding) plus 32-bit Table 9 arrhythmia bitmap, D19 alarm-suspend nibble, and the VPC CODE byte; NK EGA UDP extracts Priority + Tech Alarm + Arrhythmia from JSON Vital Sign packets with edge-trigger cache for start/end inference; ACK^R40 reply added for Mindray alert messages; simulators gained periodic alarm cycling for end-to-end smoke testing
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