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 184,017 times since Feb 2017)
OS:
Version:
View All Versions
1.19.27
1.19.27 Changed: 자동 판별이 지난번에 그 자리에 있던 장비를 가장 먼저 시험한다. 종전에는 지난 결과를 후보 순서를 바꾸는 데만 썼고, 그 앞에서 회선 설정(통신 속도·형식) 측정을 먼저 끝냈다. 그 측정이 조용한 회선에서는 48가지 조합을 다 확인하느라 72초를 쓴다. 방의 장비 구성은 거의 바뀌지 않으므로 지난 답보다 강한 단서가 없고, 빗나가더라도 후보 하나를 더 시험하는 값만 든다. Changed: 드물게 말하는 장비를 판별할 수 있게 됐다. 후보 하나를 시험하는 시간이 30초로 고정돼 있었는데, 근이완 감시장치(TwitchView, TOFScan)는 5분에 한 번 측정하므로 30초 안에 아무 말도 하지 않는 것이 정상이다. 그래서 이 장비들은 자동 판별로 영영 잡히지 않았다(한 수술방에서 21일 동안 지난 답을 기억한 채 확인에 계속 실패하고 있었다). 이제 각 장비가 스스로 정한 “이만큼은 조용할 수 있다” 는 값(최대 6분)만큼 듣는다. 겨룰 상대가 없는 단독 시험이므로 자료가 나오면 그 즉시 끝내며, 포트를 동시에 보게 된 1.19.26 덕분에 긴 대기가 다른 포트를 막지 않는다. 1.19.26 Changed: 시리얼 장비 자동 판별이 훨씬 빨리 끝난다. 종전에는 정답을 이미 찾은 뒤에도 남은 후보를 하나당 30초씩 끝까지 시험했다. 환자가 없는 아침에는 모니터가 트랙을 두어 개밖에 채우지 않아 “충분히 찾았다” 는 판정에 도달하지 못했기 때문이다(실측: 정답이 나온 시각은 기동 113초인데 확정은 275초). 이제 네 가지 경우에 그 자리에서 확정한다 — 지난 기동에서 이 포트의 답이었던 장비가 이번에도 자료를 낼 때, 회선 실측으로 후보가 하나만 남았을 때, 트랙이 10개 이상 열렸을 때, 그리고 1위가 2위를 3배 넘게 앞설 때. 확정 근거를 로그에 함께 남긴다. Changed: 여러 시리얼 포트를 동시에 살펴본다. 종전에는 한 포트씩 차례로 봐서 뒤쪽 포트는 앞 포트의 판별이 끝나기를 통째로 기다렸다(실측: 다섯 번째 포트가 기동 590초, 대부분이 대기 시간). 포트는 서로 독립된 장치라 동시에 열어도 간섭하지 않는다. 메모리를 위해 한 번에 3개까지만 본다. 같은 종류의 장비가 두 포트에서 동시에 잡히면 자료를 더 많이 낸 쪽만 남긴다(한 베드에 환자 모니터 하나, 마취기 하나라는 기존 규칙 그대로). Note: 1.19.25 설명 정정 — SyncPulseSerial 이 구형 Arduino UNO 에서 한 값도 기록되지 않던 것을 “프레임이 한 칸씩 밀린다” 고 적었는데 정확하지 않다. 실제로는 모으던 앞부분이 매번 버려져 여섯 자리가 완성되는 순간이 오지 않는 것이다. 또 같은 항목에서 Windows 수신 대기 방식을 원인으로 단정했으나, 가상 직렬 포트로 A/B 시험한 결과 그 경로에서는 재현되지 않았다 — 실제로 문제를 고친 것은 메시지 경계를 시각이 아니라 내용으로 찾도록 바꾼 쪽이다. 1.19.25 Fixed: SyncPulseSerial(동기 펄스 생성기)에서 1초 간격의 펄스가 2초마다 하나씩만 기록되거나, 보드 종류에 따라 아예 한 값도 기록되지 않던 문제. 두 증상 모두 원인이 하나다 — 이 장비는 시작·끝을 알리는 문자 없이 6자리 숫자만 흘려보내므로 VR 이 “잠깐의 침묵” 을 메시지 경계로 삼고 있었는데, 침묵은 회선이 아니라 컴퓨터 사정으로도 생긴다. 운영체제가 6바이트를 몇 번에 나눠 올려주고 그 사이가 벌어지면 앞자리가 버려져 이후 모든 값이 한 칸씩 밀리고, 한 번 밀리면 스스로 회복하지 못해 아무것도 기록되지 않는다(구형 Arduino UNO 에서 재현). 반대로 두 메시지가 한꺼번에 올라오면 둘 다 같은 시각으로 찍혀 1초짜리 펄스가 2초마다 하나 있는 것처럼 보인다(UNO R4 에서 재현). 이제 경계를 시간이 아니라 내용으로 찾는다 — 카운터가 정확히 1씩 늘어나는 자리를 스스로 찾아 맞추므로 회선이 어떻게 쪼개져 들어오든 영향을 받지 않고, 어긋나도 다음 펄스에서 다시 맞춘다. 한 번에 여러 펄스가 올라온 경우에는 카운터 간격만큼 시각을 되돌려 각각 제 시각에 기록한다. 세 가지 조건(정상·쪼개짐·뭉침) 재현 시험으로 확인했다 (Nord University AniLab 제보). Fixed: Windows 에서 데이터를 띄엄띄엄 보내는 시리얼 장비의 값이 최대 한 주기 늦게 들어오던 문제. 문자 도착 알림을 기다리는 동안이 아니라 그 직전 대기 중에 데이터가 통째로 들어와 버리면, 새로 도착하는 문자가 없어 알림이 오지 않고 이미 들어온 데이터가 다음 전송이 올 때까지 그대로 대기했다. 이제 알림이 없어도 수신 버퍼를 직접 확인한다. 초당 수십~수백 바이트를 보내는 대부분의 장비에서는 드러나지 않던 문제이지만, 1초에 한 번처럼 드물게 보내는 장비에서는 두 전송이 한 번에 처리되어 시각이 뭉쳤다. 1.19.24 Fixed: Windows 에서 SyncPulseSerial(Arduino UNO R4)의 데이터가 전혀 들어오지 않거나 한참 뒤에야 간헐적으로 들어오던 문제. Arduino 의 USB 시리얼은 컴퓨터 쪽이 준비 신호(DTR)를 올려야만 데이터를 내보내는데, VR 이 Windows 에서 포트를 열 때 이 신호를 내린 채로 열고 있었다. 시리얼 터미널 프로그램들은 이 신호를 기본으로 올리기 때문에 터미널에서는 잘 보이는데 VR 에서만 안 보이는 형태로 나타났다 (Win11/Win7 3대 실측, Nord University AniLab 제보). 장비 연결 상태는 정상으로 표시되므로 화면만 봐서는 알 수 없는 문제였다. 다른 시리얼 장비들의 동작은 그대로다 — 이 신호에 민감한 장비가 있을 수 있어 Arduino 계열 드라이버에서만 올린다. 1.19.23 Fixed: SyncPulseSerial 의 PULSE 값이 화면에 보이지 않던 문제. 값은 정상적으로 수신되고 파일에도 기록되고 있었는데, 화면 눈금 범위가 카운터의 최대값(864000)으로 잡혀 있어 테스트 초반의 작은 값들이 트랙 맨 바닥 1픽셀에 눌려 보이지 않았다. 이제 매초 가운데에 뚜렷한 선으로 표시된다. 기록된 값 자체는 이전 버전에서도 온전했다(표시만의 문제). 1.19.22 Added: Dräger 마취기를 기종 이름으로 고를 수 있다. 장비 목록에 Fabius 가 추가됐고, 목록에 없는 Dräger 를 쓰는 곳을 위해 Medibus 라는 일반 항목도 함께 넣었다. Primus, Fabius, Medibus 는 통신 방식이 완전히 같고 이름만 다르므로 어느 것을 골라도 동작은 같지만, 실제 기종을 고르면 설정 파일과 화면이 무엇이 연결돼 있는지 스스로 설명하게 된다. 기존에 Primus 로 설정해 둔 곳은 그대로 두면 된다. Changed: 시리얼 장비 자동 판별이 마취기를 특정 기종이 아니라 통신 방식 이름(Medibus)으로 인식한다. 같은 통신 방식을 쓰는 기종들은 회선만 봐서는 구별할 수 없으므로, 근거 없이 모델명을 단정하지 않는 편이 낫다고 판단했다. 기종 이름이 필요하면 설정에서 직접 고르면 된다. 1.19.21 Added: Dräger 장비가 스스로 알려주는 기종명을 로그에 남긴다. 설정 파일의 장비 이름은 사람이 적어 넣은 것이라 실제 기종을 보장하지 않는데, 기종에 따라 낼 수 있는 파형이 다르므로(예: Fabius 는 기도압과 유량은 있고 CO2 가 없다) 무엇이 연결돼 있는지 아는 것이 문제 해결의 출발점이다. 통신이 시작될 때 한 번 남는다. 1.19.20 Fixed: Dräger 마취기 가운데 파형 기능이 없는 기종에서 데이터가 전혀 수집되지 않던 문제. 연결은 된 것처럼 보이지만 아무 값도 들어오지 않는다. VR 이 장비를 확인한 뒤 파형 설정을 물어보고 그 답을 받아야만 데이터 요청을 시작하도록 돼 있었는데, 파형 기능이 없는 기종은 그 질문에 답하지 않으므로 요청이 영영 시작되지 않았다. 연결 유지 신호도 함께 멎어 장비가 통신을 끊고 다시 연결하기를 반복한다. 이제 장비 확인 직후부터 데이터를 요청한다. 파형 응답이 없으면 그 기종의 정상 동작이므로 숫자값만 수집하며 로그에 한 번 알린다. Fabius GS 는 규격상 파형 기능이 없는 대표적인 기종이다 (University of Washington 제보). Added: SyncPulseSerial 동기 펄스 생성기 지원. 여러 장비의 기록을 나중에 맞추기 위한 공통 번호를 시리얼로 보내는 장치로, 1초마다 6자리 카운터를 보낸다. PULSE 트랙에 그 번호가 기록된다. 시작이나 끝을 알리는 문자가 없는 형식이라, 메시지 사이의 침묵을 경계로 삼고 카운터가 1씩 늘어나는지 확인해 맞는 장비인지 판단한다. 자동 판별 대상에도 포함돼 있고, 환자 모니터와 같은 자리에 함께 연결해도 서로 방해하지 않는다. 1.19.19 Added: Dräger 인공호흡기·마취기가 파형에 함께 실어 보내는 호흡 위상 신호를 기록한다. 장비가 흡기와 호기 전환 시점을 직접 알려주는 것이라 파형에서 역산하는 것보다 정확하다. VENT_PHASE 트랙에 흡기 시작 1, 호기 시작 0 으로 남는다. 시각은 파형과 같은 시간축을 쓴다. 종전에는 이 신호를 읽지 않고 버렸다. Added: 환기 모드를 기록한다(VENT_MODE). 모드는 VC-CMV, PC-BIPAP, SPN-CPAP 처럼 압력조절인지 용적조절인지를 알려주어 파형 해석에 직접 쓰인다. 장비가 띄우고 있는 다른 안내 문구도 VENT_MSG 로 남는다. VR 이 그동안 이 정보를 담은 명령을 아예 보내지 않고 있었다 — 이제 측정값·설정과 함께 번갈아 요청한다. 표기가 장비나 언어에 따라 달라도 되도록 문구가 아니라 규격 코드로 판별한다. Added: 파형이 서로 맞는지 확인할 근거를 로그에 남긴다. 용적은 유량을 적분한 것이므로 둘의 관계를 수치로 기록해 둔다. 다만 이것으로 파형 이름을 바꾸거나 경고하지는 않는다 — 용적조절 환기에서는 기도압도 용적에 거의 비례해서 이 수치만으로는 둘을 구별할 수 없기 때문이다. 1.19.18 Added: Y케이블로 엿보는 Dräger 연결에서 파형 종류를 자동으로 알아낸다. Y케이블 배선에 따라 장비가 보내는 것만 보이는 방과, 반대편 장치가 보내는 것까지 보이는 방이 있는데, 후자에서는 그 장치가 파형을 요청하는 내용이 VR 에도 보인다. 거기에 순서가 그대로 들어 있으므로 추정 없이 확정한다 (실제로 수술장 두 방을 재보니 한 방은 양방향, 한 방은 단방향이었다). 단방향인 방은 종전대로 wavs= 로 적어주거나 도착 순서 추정을 쓴다. Fixed: 엿보는 연결인데도 VR 이 자기가 고른 파형 순서를 적용해 이름이 어긋날 수 있던 문제 — 회선에 다른 장치가 있는지를 통신 내용으로 판별해(보낸 적 없는 명령의 응답이 보이면 다른 장치가 있는 것이다) 그런 경우 순서를 스스로 정하지 않는다. 어느 모드로 동작 중인지는 로그에 남는다. 1.19.17 Added: Y케이블로 엿보기만 하는 Dräger 연결에서 파형 이름을 확정할 수 있다. 이런 연결은 VR 이 장비에 아무것도 보낼 수 없어 어떤 파형이 어떤 순서로 오는지 물어볼 수가 없고, 그래서 도착 순서대로 추정해 기록해 왔다(이름이 실제와 다를 수 있었다). 이제 vr.conf 의 장비 항목에 wavs= 로 그 방의 순서를 적어두면 그대로 사용한다. 순서는 그 장비에 연결된 다른 장치가 정한 것이라 병원에서 알 수 있는 값이다. 예: wavs=AWP,AWF. 적지 않으면 종전과 같이 도착 순서로 추정하며, 추정 중이라는 사실과 확정하는 방법을 로그에 남긴다. 직결 연결에서는 종전대로 VR 이 직접 물어보므로 이 설정이 없어도 정확하다. Added: 회선에서 처음 보는 종류의 통신 프레임을 한 번씩 로그에 남긴다. 어떤 장치가 무엇을 주고받는지 현장에서 확인할 수 있어 연결 문제 진단에 쓴다. 1.19.16 Fixed: 1.19.15 에서 Y케이블로 엿보기만 하는 연결(서울대병원 수술장의 Dräger 전부가 이 방식)의 파형이 기록되지 않던 문제 — 1.19.15 는 VR 이 직접 요청한 파형만 기록하도록 바뀌었는데, 관찰 전용 연결에서는 VR 이 장비에 아무것도 보낼 수 없어(송신선이 끊겨 있다) 요청 자체를 못 하므로 들어오는 파형을 전부 버렸다. 이제 이런 연결에서는 1.19.14 이전과 같이 도착 순서대로 기록한다. 다른 장치가 설정해 둔 순서는 알 수 없으므로 이름이 실제와 다를 수 있고, 그 사실을 로그에 함께 남긴다. 1.19.15 를 이미 설치한 곳은 이 버전으로 올려야 한다. 1.19.15 Fixed: Dräger 마취기·인공호흡기(MEDIBUS)에서 파형이 엉뚱한 이름으로 기록되던 문제. 장비가 보내는 파형 묶음의 첫 바이트에는 “몇 번째로 요청한 파형이 들어있는가” 가 비트로 표시되는데, VR 은 그 비트를 세기만 하고 도착 순서대로 미리 정해둔 트랙(CO2, AWP, AWF)에 넣었다. 장비가 요청한 파형 중 일부만 보내면 그만큼 밀려 기록돼, 예를 들어 기도압(AWP)만 보내는 인공호흡기에서 그 파형이 CO2 로 저장됐다 (Evita V600 실측 제보). 이제 비트를 해석해 요청한 순서와 맞춰 넣는다. Fixed: 요청하는 파형이 CO2 와 기도압 두 가지로 고정돼 있어, 장비가 유량(Flow)이나 용적(Volume) 파형을 제공해도 받지 못했다. 이제 장비에게 먼저 제공 가능한 파형 목록을 물어보고 그중에서 요청한다. Added: vr.conf 의 장비 설정에 wavs= 로 받을 파형을 고를 수 있다 (예: wavs=AWP,AWF,VOL,CO2). 비워두면 장비가 제공하는 것을 앞에서부터 최대 4개까지 자동으로 받는다. 4개인 이유는 한 묶음에 최대 4개까지만 표시할 수 있기 때문이다. 지원 이름: AWP AWF PLETH VOL O2 CO2 CO2_KPA CO2_PCT AGENT HAL ENF ISO DES SEV PTRACH AWF_DEV (16진 코드도 가능). 파형을 늘리면 회선 대역을 더 쓰므로 9600bps 의 80% 를 넘으면 로그로 경고한다. 어떤 파형이 실제로 들어오고 있는지도 로그에 남겨 현장에서 확인할 수 있다. 1.19.14 Added: 서버에서 VR 로 내려보내는 명령(원격 시간 동기, 원격 업그레이드, 재시작, 설정 변경 등)을 다시 사용할 수 있다. 1.18.52 에서 이 경로를 통째로 막았는데, 시간 동기와 원격 업그레이드가 꼭 필요한 기관이 있어 되살렸다. 다만 원격으로 프로그램을 바꾸거나 설정을 덮어쓰는 것은 그 자체로 위험하므로 기본값은 여전히 전부 차단이고, vr.conf 에 SERVER_CONTROL 로 허용할 명령을 직접 적어야 동작한다 (예: SERVER_CONTROL=dt 는 시간 동기만, SERVER_CONTROL=dt,update 는 시간 동기와 업그레이드만, SERVER_CONTROL=1 은 전부 허용). 사용 가능한 명령은 update restart reboot edit_conf new_bed del_bed edit_bed dt add_event. 시간 동기는 예전과 달리 녹화 중에는 시계를 건너뛰지 않는다 — 녹화 중 시계를 갑자기 옮기면 파형에 공백이 생기거나 기록이 겹치므로, 녹화 중이면 서서히 당겨 맞추고(슬루) 녹화가 없을 때만 한번에 맞춘다. 2초 이내 차이는 무시한다. 예전 구현이 10분 이상 차이날 때 프로그램을 강제 종료하던 동작도 없앴다. 1.19.13 Fixed: 웹 모니터링 화면에서 EEG 파형이 0.1초 정도씩 끊겨 보이던 문제 — 화면 전송용으로 파형을 줄일 때 원본 주파수가 나누어떨어지지 않으면 일부 구간이 비어 그만큼 빈칸으로 보였다. 나누어떨어지는 비율로만 줄이도록 수정. 녹화 파일에는 원래 문제가 없었다(표시만의 문제). 1.19.12 Fixed: Dräger 마취기 연동 안정화 보완 — 시리얼 회선 자동판별 중 잡음이 잘못된 데이터로 오인될 여지를 차단. 1.19.11 Fixed: Dräger 마취기(Atlan 등 MEDIBUS 장비)에서 통신 오류 알람(COM1 failure)이 반복되고 파형이 전혀 기록되지 않던 문제. 원인 세 가지를 모두 수정했다. (1) 장비는 3초 안에 아무 신호라도 받지 못하면 연결을 끊는데, VR 이 5초마다 데이터를 요청하는 것 외에 보내는 것이 없어 매번 연결이 끊기고 재접속을 반복했다 — 규격이 정한 연결 유지 신호(NOP)를 2초마다 보내도록 했다(데이터 요청 주기는 그대로). (2) 장비가 응답 도중에 다른 명령을 끼워 보내는 것이 규격상 허용되는데, VR 이 그때 받던 응답을 통째로 버려 파형 설정 응답을 매번 놓쳤다 — 끼어든 부분을 건너뛰고 원래 응답을 이어받도록 했다. (3) 장비가 보내는 요청 하나에 VR 이 응답하지 않아 통신이 끊기던 것도 수정. 파형·수치가 정상 기록되고 알람이 사라진다. 1.19.10 Fixed: 하루 한 번의 자동 재시작 시점에 수집이 통째로 멎어 사람이 손을 대기 전까지 복구되지 않던 문제(or-106, 104분 무데이터) — 종료 과정이 끝나지 않아 프로그램이 죽지 않았고, 죽지 않으니 자동 재시작도 일어나지 않았다. 주원인은 시리얼 회선 자동판별(AUTO_DETECT)이 종료 신호를 보지 않고 스윕(보레이트 6종 x 포맷 8종)을 끝까지 돌던 것으로, 무음 회선에서 종료에 2분이 걸려 정상 종료 유예시간을 넘겼다. 이제 종료 즉시 판별을 중단한다(실측 2분 -> 3초). 함께 종료 중 스레드 정리가 서로를 기다릴 수 있던 두 곳을 제거하고, 그럼에도 종료가 20초 안에 끝나지 않으면 강제 종료해 반드시 재시작되도록 안전장치를 넣었다. 녹화 파일은 5초마다 저장되므로 유실되지 않는다. 1.19.9 Fixed: 큰 녹화 파일(약 100MB 이상)을 서버로 올릴 때 프로그램이 메모리 부족으로 강제 종료되고, 재시작 후 같은 파일을 다시 시도하다 무한 반복되던 문제 — 업로드 본문을 메모리에 통째로 만들지 않고 디스크에서 흘려보내도록 변경(100MB 파일 기준 최대 메모리 208MB → 14MB). 큰 파일이 한 개라도 쌓이면 그 장비의 이후 업로드가 전부 막히던 증상이 사라진다. 1.19.8 Fixed: 자동 파일 자르기(CUT_FILE) 를 쓰는 베드에서 환자가 계속 연결돼 있는데도 녹화 파일이 PT_WAITING_TIME(기본 5분) 마다 잘리던 문제 — 웹 모니터링 전송 후 메모리를 비우는 과정에서 환자 연결 판정에 쓰이는 최근 구간까지 지워져 "환자가 나갔다" 로 오판하던 것. 이제 판정에 필요한 구간은 보존한다. 파일이 5분마다 끊기고 그때마다 수 초가 유실되던 증상이 사라진다. 1.19.7 Added: GE Corometrics(분만 태아감시) 모체 vitals 지원 — 기존엔 태아 심박(HR1/HR2)·자궁수축(UACT)만 기록됐으나 이제 같은 장비가 함께 보내는 모체 SpO2/맥박(PR)/NIBP(수축·이완·평균)도 한 파일에 기록. Fixed: 프로세스 종료(서비스 매니저의 정상 종료 요청, SIGTERM) 시 즉시 종료되지 않고 강제 종료되던 문제 — 이제 SIGTERM 을 받아 녹화 버퍼를 flush 하고 스레드를 정리한 뒤 정상 종료. 1.19.5 Fixed: CUT_HOURLY 가 동작하지 않는 것처럼 보이던 문제의 실체 — k3s 컨테이너에서 기본 저장 경로가 $HOME/recordings(임시 레이어)로 잡혀, 매일 자정 재시작 때 업로드 안 된 하루치 녹화가 통째로 사라짐. hostPath 마운트 /recordings 가 있으면 그것을 기본 저장 경로로 사용. Fixed: 종료(daily restart) 시 멀티베드 TCP 노드가 terminate(exit=133)/SIGSEGV(139) 로 죽던 문제. Linux finalize 가 device 스레드를 정지하지 않고 반환해 exit() 전역 소멸자가 살아있는 PortRouter accept_thread 를 파괴하며 std::terminate — DISPLAY::quiesce_threads + PortRouter::shutdown 으로 정리 후 종료 (코어 백트레이스로 확정, 로컬 재현 후 수정 검증). Fixed: Link+/Agilia 파서가 시리얼 노이즈(size byte 0 또는 헤더 없는 잡음 8byte)에 역전된 반복자 범위로 vector 를 생성하다 std::length_error 로 죽던 문제(or-110) — sentinel 상태 파싱 봉쇄 + 재동기화. Fixed: FILENAME_TEMPLATE 의 %Y(4자리 연도)가 "0126" 으로 나오던 문제 (tm_year+1900 누락). 1.19.4 Fixed: Link+ 등 재접속이 잦은 장비에서 시리얼 포트를 재오픈할 때 "terminate called without an active exception" 으로 프로세스가 죽던 문제(exit=133). reading_thread_func 이 스스로 반환해도 std::thread 객체는 joinable 로 남는데, open() 이 join 없이 새 스레드를 대입해 std::terminate 가 호출됨 → 스레드 생성 전에 이전 reading/parsing 스레드를 join 후 재생성. Fixed: headless/AUTO_DETECT 모드에서 실제 장비가 없을 때 Demo 장비가 생성돼 가짜 vital 트랙 38종이 업로드되던 문제 → --console 또는 AUTO_DETECT 시 Demo 미생성. 1.19.3 Fixed: 여러 베드를 한 프로세스가 TCP 로 수집하는 구성에서 클라이언트 재접속이 잦으면 프로세스가 SIGSEGV 로 죽던 문제(accept_handoff 가 replace 시 옛 소켓을 즉시 close 해 recv 중인 reader 와 경합, fd 번호 재사용). tcp reader 를 poll(200ms) 게이트 뒤의 recv 로 재구성하고 replace 는 shutdown 후 소유권을 reader/close 로 단일화. SNUH 회복실 VRN 15대에서 재현. Fixed: ICU 등 Intellivue 다수 베드에서 반복되던 SIGSEGV(139) 두 계열의 use-after-free. (1) DISPLAY::add_rec 이 레코드를 트랙에 push 한 뒤 저장했는데, push 직후 p 가 다른 스레드에 공유되고 Linux 의 메모리 보존 루프(600초 이전 레코드 delete)가 시계 어긋난 모니터에서 방금 넣은 레코드를 즉시 evict 해, 이어지는 p->save() 가 해제된 메모리에 가상 호출 → 저장을 push 앞으로 옮겨 공유 후 p 미접근. (2) DISPLAY::del_trk 이 트랙을 목록에서 뺀 직후 raw delete 해, 파싱 스레드의 캐시 포인터·장비 이름맵·recorded_trks 가 죽은 포인터를 참조 (특히 IAP 채널변경의 del 직후 add 가 해제 포인터 반환) → DEVICE 규칙처럼 살아있는 동안 삭제 금지, ~DISPLAY 에서 일괄 해제. picu-11 코어 덤프로 크래시 지점(add_rec vtable 간접호출) 확정. SNUH fleet ~100 노드 실배포에서 기대 크래시 다수 대비 실제 0, 미배포 노드는 계속 크래시. Fixed: Linux from_utf16 이 locale 의존·비스레드안전 wcstombs 를 쓰고 UTF-16 을 UTF-32 wchar_t 로 오해석하던 문제 → 수동 UTF-8 인코더(서로게이트 페어 재조립, 무효값 U+FFFD)로 교체. Added: AUTO_DETECT=1(vr.conf) 시리얼 장비 자동 판별. 회선 측정(보레이트·프레임폭·패리티) → 후보 필터(3축 일치, 베드당 클래스 배타, 포트 사전확률, 판별캐시) → 트랙 수확량 경쟁. 드라이버 무수정, 미설정 시 유휴 대기라 기존 설정 무영향. SNUH 수술장·ICU 실측 오탐 0.
1.19.26
1.19.26 Changed: 시리얼 장비 자동 판별이 훨씬 빨리 끝난다. 종전에는 정답을 이미 찾은 뒤에도 남은 후보를 하나당 30초씩 끝까지 시험했다. 환자가 없는 아침에는 모니터가 트랙을 두어 개밖에 채우지 않아 “충분히 찾았다” 는 판정에 도달하지 못했기 때문이다(실측: 정답이 나온 시각은 기동 113초인데 확정은 275초). 이제 네 가지 경우에 그 자리에서 확정한다 — 지난 기동에서 이 포트의 답이었던 장비가 이번에도 자료를 낼 때, 회선 실측으로 후보가 하나만 남았을 때, 트랙이 10개 이상 열렸을 때, 그리고 1위가 2위를 3배 넘게 앞설 때. 확정 근거를 로그에 함께 남긴다. Changed: 여러 시리얼 포트를 동시에 살펴본다. 종전에는 한 포트씩 차례로 봐서 뒤쪽 포트는 앞 포트의 판별이 끝나기를 통째로 기다렸다(실측: 다섯 번째 포트가 기동 590초, 대부분이 대기 시간). 포트는 서로 독립된 장치라 동시에 열어도 간섭하지 않는다. 메모리를 위해 한 번에 3개까지만 본다. 같은 종류의 장비가 두 포트에서 동시에 잡히면 자료를 더 많이 낸 쪽만 남긴다(한 베드에 환자 모니터 하나, 마취기 하나라는 기존 규칙 그대로). Note: 1.19.25 설명 정정 — SyncPulseSerial 이 구형 Arduino UNO 에서 한 값도 기록되지 않던 것을 “프레임이 한 칸씩 밀린다” 고 적었는데 정확하지 않다. 실제로는 모으던 앞부분이 매번 버려져 여섯 자리가 완성되는 순간이 오지 않는 것이다. 또 같은 항목에서 Windows 수신 대기 방식을 원인으로 단정했으나, 가상 직렬 포트로 A/B 시험한 결과 그 경로에서는 재현되지 않았다 — 실제로 문제를 고친 것은 메시지 경계를 시각이 아니라 내용으로 찾도록 바꾼 쪽이다. 1.19.25 Fixed: SyncPulseSerial(동기 펄스 생성기)에서 1초 간격의 펄스가 2초마다 하나씩만 기록되거나, 보드 종류에 따라 아예 한 값도 기록되지 않던 문제. 두 증상 모두 원인이 하나다 — 이 장비는 시작·끝을 알리는 문자 없이 6자리 숫자만 흘려보내므로 VR 이 “잠깐의 침묵” 을 메시지 경계로 삼고 있었는데, 침묵은 회선이 아니라 컴퓨터 사정으로도 생긴다. 운영체제가 6바이트를 몇 번에 나눠 올려주고 그 사이가 벌어지면 앞자리가 버려져 이후 모든 값이 한 칸씩 밀리고, 한 번 밀리면 스스로 회복하지 못해 아무것도 기록되지 않는다(구형 Arduino UNO 에서 재현). 반대로 두 메시지가 한꺼번에 올라오면 둘 다 같은 시각으로 찍혀 1초짜리 펄스가 2초마다 하나 있는 것처럼 보인다(UNO R4 에서 재현). 이제 경계를 시간이 아니라 내용으로 찾는다 — 카운터가 정확히 1씩 늘어나는 자리를 스스로 찾아 맞추므로 회선이 어떻게 쪼개져 들어오든 영향을 받지 않고, 어긋나도 다음 펄스에서 다시 맞춘다. 한 번에 여러 펄스가 올라온 경우에는 카운터 간격만큼 시각을 되돌려 각각 제 시각에 기록한다. 세 가지 조건(정상·쪼개짐·뭉침) 재현 시험으로 확인했다 (Nord University AniLab 제보). Fixed: Windows 에서 데이터를 띄엄띄엄 보내는 시리얼 장비의 값이 최대 한 주기 늦게 들어오던 문제. 문자 도착 알림을 기다리는 동안이 아니라 그 직전 대기 중에 데이터가 통째로 들어와 버리면, 새로 도착하는 문자가 없어 알림이 오지 않고 이미 들어온 데이터가 다음 전송이 올 때까지 그대로 대기했다. 이제 알림이 없어도 수신 버퍼를 직접 확인한다. 초당 수십~수백 바이트를 보내는 대부분의 장비에서는 드러나지 않던 문제이지만, 1초에 한 번처럼 드물게 보내는 장비에서는 두 전송이 한 번에 처리되어 시각이 뭉쳤다. 1.19.24 Fixed: Windows 에서 SyncPulseSerial(Arduino UNO R4)의 데이터가 전혀 들어오지 않거나 한참 뒤에야 간헐적으로 들어오던 문제. Arduino 의 USB 시리얼은 컴퓨터 쪽이 준비 신호(DTR)를 올려야만 데이터를 내보내는데, VR 이 Windows 에서 포트를 열 때 이 신호를 내린 채로 열고 있었다. 시리얼 터미널 프로그램들은 이 신호를 기본으로 올리기 때문에 터미널에서는 잘 보이는데 VR 에서만 안 보이는 형태로 나타났다 (Win11/Win7 3대 실측, Nord University AniLab 제보). 장비 연결 상태는 정상으로 표시되므로 화면만 봐서는 알 수 없는 문제였다. 다른 시리얼 장비들의 동작은 그대로다 — 이 신호에 민감한 장비가 있을 수 있어 Arduino 계열 드라이버에서만 올린다. 1.19.23 Fixed: SyncPulseSerial 의 PULSE 값이 화면에 보이지 않던 문제. 값은 정상적으로 수신되고 파일에도 기록되고 있었는데, 화면 눈금 범위가 카운터의 최대값(864000)으로 잡혀 있어 테스트 초반의 작은 값들이 트랙 맨 바닥 1픽셀에 눌려 보이지 않았다. 이제 매초 가운데에 뚜렷한 선으로 표시된다. 기록된 값 자체는 이전 버전에서도 온전했다(표시만의 문제). 1.19.22 Added: Dräger 마취기를 기종 이름으로 고를 수 있다. 장비 목록에 Fabius 가 추가됐고, 목록에 없는 Dräger 를 쓰는 곳을 위해 Medibus 라는 일반 항목도 함께 넣었다. Primus, Fabius, Medibus 는 통신 방식이 완전히 같고 이름만 다르므로 어느 것을 골라도 동작은 같지만, 실제 기종을 고르면 설정 파일과 화면이 무엇이 연결돼 있는지 스스로 설명하게 된다. 기존에 Primus 로 설정해 둔 곳은 그대로 두면 된다. Changed: 시리얼 장비 자동 판별이 마취기를 특정 기종이 아니라 통신 방식 이름(Medibus)으로 인식한다. 같은 통신 방식을 쓰는 기종들은 회선만 봐서는 구별할 수 없으므로, 근거 없이 모델명을 단정하지 않는 편이 낫다고 판단했다. 기종 이름이 필요하면 설정에서 직접 고르면 된다. 1.19.21 Added: Dräger 장비가 스스로 알려주는 기종명을 로그에 남긴다. 설정 파일의 장비 이름은 사람이 적어 넣은 것이라 실제 기종을 보장하지 않는데, 기종에 따라 낼 수 있는 파형이 다르므로(예: Fabius 는 기도압과 유량은 있고 CO2 가 없다) 무엇이 연결돼 있는지 아는 것이 문제 해결의 출발점이다. 통신이 시작될 때 한 번 남는다. 1.19.20 Fixed: Dräger 마취기 가운데 파형 기능이 없는 기종에서 데이터가 전혀 수집되지 않던 문제. 연결은 된 것처럼 보이지만 아무 값도 들어오지 않는다. VR 이 장비를 확인한 뒤 파형 설정을 물어보고 그 답을 받아야만 데이터 요청을 시작하도록 돼 있었는데, 파형 기능이 없는 기종은 그 질문에 답하지 않으므로 요청이 영영 시작되지 않았다. 연결 유지 신호도 함께 멎어 장비가 통신을 끊고 다시 연결하기를 반복한다. 이제 장비 확인 직후부터 데이터를 요청한다. 파형 응답이 없으면 그 기종의 정상 동작이므로 숫자값만 수집하며 로그에 한 번 알린다. Fabius GS 는 규격상 파형 기능이 없는 대표적인 기종이다 (University of Washington 제보). Added: SyncPulseSerial 동기 펄스 생성기 지원. 여러 장비의 기록을 나중에 맞추기 위한 공통 번호를 시리얼로 보내는 장치로, 1초마다 6자리 카운터를 보낸다. PULSE 트랙에 그 번호가 기록된다. 시작이나 끝을 알리는 문자가 없는 형식이라, 메시지 사이의 침묵을 경계로 삼고 카운터가 1씩 늘어나는지 확인해 맞는 장비인지 판단한다. 자동 판별 대상에도 포함돼 있고, 환자 모니터와 같은 자리에 함께 연결해도 서로 방해하지 않는다. 1.19.19 Added: Dräger 인공호흡기·마취기가 파형에 함께 실어 보내는 호흡 위상 신호를 기록한다. 장비가 흡기와 호기 전환 시점을 직접 알려주는 것이라 파형에서 역산하는 것보다 정확하다. VENT_PHASE 트랙에 흡기 시작 1, 호기 시작 0 으로 남는다. 시각은 파형과 같은 시간축을 쓴다. 종전에는 이 신호를 읽지 않고 버렸다. Added: 환기 모드를 기록한다(VENT_MODE). 모드는 VC-CMV, PC-BIPAP, SPN-CPAP 처럼 압력조절인지 용적조절인지를 알려주어 파형 해석에 직접 쓰인다. 장비가 띄우고 있는 다른 안내 문구도 VENT_MSG 로 남는다. VR 이 그동안 이 정보를 담은 명령을 아예 보내지 않고 있었다 — 이제 측정값·설정과 함께 번갈아 요청한다. 표기가 장비나 언어에 따라 달라도 되도록 문구가 아니라 규격 코드로 판별한다. Added: 파형이 서로 맞는지 확인할 근거를 로그에 남긴다. 용적은 유량을 적분한 것이므로 둘의 관계를 수치로 기록해 둔다. 다만 이것으로 파형 이름을 바꾸거나 경고하지는 않는다 — 용적조절 환기에서는 기도압도 용적에 거의 비례해서 이 수치만으로는 둘을 구별할 수 없기 때문이다. 1.19.18 Added: Y케이블로 엿보는 Dräger 연결에서 파형 종류를 자동으로 알아낸다. Y케이블 배선에 따라 장비가 보내는 것만 보이는 방과, 반대편 장치가 보내는 것까지 보이는 방이 있는데, 후자에서는 그 장치가 파형을 요청하는 내용이 VR 에도 보인다. 거기에 순서가 그대로 들어 있으므로 추정 없이 확정한다 (실제로 수술장 두 방을 재보니 한 방은 양방향, 한 방은 단방향이었다). 단방향인 방은 종전대로 wavs= 로 적어주거나 도착 순서 추정을 쓴다. Fixed: 엿보는 연결인데도 VR 이 자기가 고른 파형 순서를 적용해 이름이 어긋날 수 있던 문제 — 회선에 다른 장치가 있는지를 통신 내용으로 판별해(보낸 적 없는 명령의 응답이 보이면 다른 장치가 있는 것이다) 그런 경우 순서를 스스로 정하지 않는다. 어느 모드로 동작 중인지는 로그에 남는다. 1.19.17 Added: Y케이블로 엿보기만 하는 Dräger 연결에서 파형 이름을 확정할 수 있다. 이런 연결은 VR 이 장비에 아무것도 보낼 수 없어 어떤 파형이 어떤 순서로 오는지 물어볼 수가 없고, 그래서 도착 순서대로 추정해 기록해 왔다(이름이 실제와 다를 수 있었다). 이제 vr.conf 의 장비 항목에 wavs= 로 그 방의 순서를 적어두면 그대로 사용한다. 순서는 그 장비에 연결된 다른 장치가 정한 것이라 병원에서 알 수 있는 값이다. 예: wavs=AWP,AWF. 적지 않으면 종전과 같이 도착 순서로 추정하며, 추정 중이라는 사실과 확정하는 방법을 로그에 남긴다. 직결 연결에서는 종전대로 VR 이 직접 물어보므로 이 설정이 없어도 정확하다. Added: 회선에서 처음 보는 종류의 통신 프레임을 한 번씩 로그에 남긴다. 어떤 장치가 무엇을 주고받는지 현장에서 확인할 수 있어 연결 문제 진단에 쓴다. 1.19.16 Fixed: 1.19.15 에서 Y케이블로 엿보기만 하는 연결(서울대병원 수술장의 Dräger 전부가 이 방식)의 파형이 기록되지 않던 문제 — 1.19.15 는 VR 이 직접 요청한 파형만 기록하도록 바뀌었는데, 관찰 전용 연결에서는 VR 이 장비에 아무것도 보낼 수 없어(송신선이 끊겨 있다) 요청 자체를 못 하므로 들어오는 파형을 전부 버렸다. 이제 이런 연결에서는 1.19.14 이전과 같이 도착 순서대로 기록한다. 다른 장치가 설정해 둔 순서는 알 수 없으므로 이름이 실제와 다를 수 있고, 그 사실을 로그에 함께 남긴다. 1.19.15 를 이미 설치한 곳은 이 버전으로 올려야 한다. 1.19.15 Fixed: Dräger 마취기·인공호흡기(MEDIBUS)에서 파형이 엉뚱한 이름으로 기록되던 문제. 장비가 보내는 파형 묶음의 첫 바이트에는 “몇 번째로 요청한 파형이 들어있는가” 가 비트로 표시되는데, VR 은 그 비트를 세기만 하고 도착 순서대로 미리 정해둔 트랙(CO2, AWP, AWF)에 넣었다. 장비가 요청한 파형 중 일부만 보내면 그만큼 밀려 기록돼, 예를 들어 기도압(AWP)만 보내는 인공호흡기에서 그 파형이 CO2 로 저장됐다 (Evita V600 실측 제보). 이제 비트를 해석해 요청한 순서와 맞춰 넣는다. Fixed: 요청하는 파형이 CO2 와 기도압 두 가지로 고정돼 있어, 장비가 유량(Flow)이나 용적(Volume) 파형을 제공해도 받지 못했다. 이제 장비에게 먼저 제공 가능한 파형 목록을 물어보고 그중에서 요청한다. Added: vr.conf 의 장비 설정에 wavs= 로 받을 파형을 고를 수 있다 (예: wavs=AWP,AWF,VOL,CO2). 비워두면 장비가 제공하는 것을 앞에서부터 최대 4개까지 자동으로 받는다. 4개인 이유는 한 묶음에 최대 4개까지만 표시할 수 있기 때문이다. 지원 이름: AWP AWF PLETH VOL O2 CO2 CO2_KPA CO2_PCT AGENT HAL ENF ISO DES SEV PTRACH AWF_DEV (16진 코드도 가능). 파형을 늘리면 회선 대역을 더 쓰므로 9600bps 의 80% 를 넘으면 로그로 경고한다. 어떤 파형이 실제로 들어오고 있는지도 로그에 남겨 현장에서 확인할 수 있다. 1.19.14 Added: 서버에서 VR 로 내려보내는 명령(원격 시간 동기, 원격 업그레이드, 재시작, 설정 변경 등)을 다시 사용할 수 있다. 1.18.52 에서 이 경로를 통째로 막았는데, 시간 동기와 원격 업그레이드가 꼭 필요한 기관이 있어 되살렸다. 다만 원격으로 프로그램을 바꾸거나 설정을 덮어쓰는 것은 그 자체로 위험하므로 기본값은 여전히 전부 차단이고, vr.conf 에 SERVER_CONTROL 로 허용할 명령을 직접 적어야 동작한다 (예: SERVER_CONTROL=dt 는 시간 동기만, SERVER_CONTROL=dt,update 는 시간 동기와 업그레이드만, SERVER_CONTROL=1 은 전부 허용). 사용 가능한 명령은 update restart reboot edit_conf new_bed del_bed edit_bed dt add_event. 시간 동기는 예전과 달리 녹화 중에는 시계를 건너뛰지 않는다 — 녹화 중 시계를 갑자기 옮기면 파형에 공백이 생기거나 기록이 겹치므로, 녹화 중이면 서서히 당겨 맞추고(슬루) 녹화가 없을 때만 한번에 맞춘다. 2초 이내 차이는 무시한다. 예전 구현이 10분 이상 차이날 때 프로그램을 강제 종료하던 동작도 없앴다. 1.19.13 Fixed: 웹 모니터링 화면에서 EEG 파형이 0.1초 정도씩 끊겨 보이던 문제 — 화면 전송용으로 파형을 줄일 때 원본 주파수가 나누어떨어지지 않으면 일부 구간이 비어 그만큼 빈칸으로 보였다. 나누어떨어지는 비율로만 줄이도록 수정. 녹화 파일에는 원래 문제가 없었다(표시만의 문제). 1.19.12 Fixed: Dräger 마취기 연동 안정화 보완 — 시리얼 회선 자동판별 중 잡음이 잘못된 데이터로 오인될 여지를 차단. 1.19.11 Fixed: Dräger 마취기(Atlan 등 MEDIBUS 장비)에서 통신 오류 알람(COM1 failure)이 반복되고 파형이 전혀 기록되지 않던 문제. 원인 세 가지를 모두 수정했다. (1) 장비는 3초 안에 아무 신호라도 받지 못하면 연결을 끊는데, VR 이 5초마다 데이터를 요청하는 것 외에 보내는 것이 없어 매번 연결이 끊기고 재접속을 반복했다 — 규격이 정한 연결 유지 신호(NOP)를 2초마다 보내도록 했다(데이터 요청 주기는 그대로). (2) 장비가 응답 도중에 다른 명령을 끼워 보내는 것이 규격상 허용되는데, VR 이 그때 받던 응답을 통째로 버려 파형 설정 응답을 매번 놓쳤다 — 끼어든 부분을 건너뛰고 원래 응답을 이어받도록 했다. (3) 장비가 보내는 요청 하나에 VR 이 응답하지 않아 통신이 끊기던 것도 수정. 파형·수치가 정상 기록되고 알람이 사라진다. 1.19.10 Fixed: 하루 한 번의 자동 재시작 시점에 수집이 통째로 멎어 사람이 손을 대기 전까지 복구되지 않던 문제(or-106, 104분 무데이터) — 종료 과정이 끝나지 않아 프로그램이 죽지 않았고, 죽지 않으니 자동 재시작도 일어나지 않았다. 주원인은 시리얼 회선 자동판별(AUTO_DETECT)이 종료 신호를 보지 않고 스윕(보레이트 6종 x 포맷 8종)을 끝까지 돌던 것으로, 무음 회선에서 종료에 2분이 걸려 정상 종료 유예시간을 넘겼다. 이제 종료 즉시 판별을 중단한다(실측 2분 -> 3초). 함께 종료 중 스레드 정리가 서로를 기다릴 수 있던 두 곳을 제거하고, 그럼에도 종료가 20초 안에 끝나지 않으면 강제 종료해 반드시 재시작되도록 안전장치를 넣었다. 녹화 파일은 5초마다 저장되므로 유실되지 않는다. 1.19.9 Fixed: 큰 녹화 파일(약 100MB 이상)을 서버로 올릴 때 프로그램이 메모리 부족으로 강제 종료되고, 재시작 후 같은 파일을 다시 시도하다 무한 반복되던 문제 — 업로드 본문을 메모리에 통째로 만들지 않고 디스크에서 흘려보내도록 변경(100MB 파일 기준 최대 메모리 208MB → 14MB). 큰 파일이 한 개라도 쌓이면 그 장비의 이후 업로드가 전부 막히던 증상이 사라진다. 1.19.8 Fixed: 자동 파일 자르기(CUT_FILE) 를 쓰는 베드에서 환자가 계속 연결돼 있는데도 녹화 파일이 PT_WAITING_TIME(기본 5분) 마다 잘리던 문제 — 웹 모니터링 전송 후 메모리를 비우는 과정에서 환자 연결 판정에 쓰이는 최근 구간까지 지워져 "환자가 나갔다" 로 오판하던 것. 이제 판정에 필요한 구간은 보존한다. 파일이 5분마다 끊기고 그때마다 수 초가 유실되던 증상이 사라진다. 1.19.7 Added: GE Corometrics(분만 태아감시) 모체 vitals 지원 — 기존엔 태아 심박(HR1/HR2)·자궁수축(UACT)만 기록됐으나 이제 같은 장비가 함께 보내는 모체 SpO2/맥박(PR)/NIBP(수축·이완·평균)도 한 파일에 기록. Fixed: 프로세스 종료(서비스 매니저의 정상 종료 요청, SIGTERM) 시 즉시 종료되지 않고 강제 종료되던 문제 — 이제 SIGTERM 을 받아 녹화 버퍼를 flush 하고 스레드를 정리한 뒤 정상 종료. 1.19.5 Fixed: CUT_HOURLY 가 동작하지 않는 것처럼 보이던 문제의 실체 — k3s 컨테이너에서 기본 저장 경로가 $HOME/recordings(임시 레이어)로 잡혀, 매일 자정 재시작 때 업로드 안 된 하루치 녹화가 통째로 사라짐. hostPath 마운트 /recordings 가 있으면 그것을 기본 저장 경로로 사용. Fixed: 종료(daily restart) 시 멀티베드 TCP 노드가 terminate(exit=133)/SIGSEGV(139) 로 죽던 문제. Linux finalize 가 device 스레드를 정지하지 않고 반환해 exit() 전역 소멸자가 살아있는 PortRouter accept_thread 를 파괴하며 std::terminate — DISPLAY::quiesce_threads + PortRouter::shutdown 으로 정리 후 종료 (코어 백트레이스로 확정, 로컬 재현 후 수정 검증). Fixed: Link+/Agilia 파서가 시리얼 노이즈(size byte 0 또는 헤더 없는 잡음 8byte)에 역전된 반복자 범위로 vector 를 생성하다 std::length_error 로 죽던 문제(or-110) — sentinel 상태 파싱 봉쇄 + 재동기화. Fixed: FILENAME_TEMPLATE 의 %Y(4자리 연도)가 "0126" 으로 나오던 문제 (tm_year+1900 누락). 1.19.4 Fixed: Link+ 등 재접속이 잦은 장비에서 시리얼 포트를 재오픈할 때 "terminate called without an active exception" 으로 프로세스가 죽던 문제(exit=133). reading_thread_func 이 스스로 반환해도 std::thread 객체는 joinable 로 남는데, open() 이 join 없이 새 스레드를 대입해 std::terminate 가 호출됨 → 스레드 생성 전에 이전 reading/parsing 스레드를 join 후 재생성. Fixed: headless/AUTO_DETECT 모드에서 실제 장비가 없을 때 Demo 장비가 생성돼 가짜 vital 트랙 38종이 업로드되던 문제 → --console 또는 AUTO_DETECT 시 Demo 미생성. 1.19.3 Fixed: 여러 베드를 한 프로세스가 TCP 로 수집하는 구성에서 클라이언트 재접속이 잦으면 프로세스가 SIGSEGV 로 죽던 문제(accept_handoff 가 replace 시 옛 소켓을 즉시 close 해 recv 중인 reader 와 경합, fd 번호 재사용). tcp reader 를 poll(200ms) 게이트 뒤의 recv 로 재구성하고 replace 는 shutdown 후 소유권을 reader/close 로 단일화. SNUH 회복실 VRN 15대에서 재현. Fixed: ICU 등 Intellivue 다수 베드에서 반복되던 SIGSEGV(139) 두 계열의 use-after-free. (1) DISPLAY::add_rec 이 레코드를 트랙에 push 한 뒤 저장했는데, push 직후 p 가 다른 스레드에 공유되고 Linux 의 메모리 보존 루프(600초 이전 레코드 delete)가 시계 어긋난 모니터에서 방금 넣은 레코드를 즉시 evict 해, 이어지는 p->save() 가 해제된 메모리에 가상 호출 → 저장을 push 앞으로 옮겨 공유 후 p 미접근. (2) DISPLAY::del_trk 이 트랙을 목록에서 뺀 직후 raw delete 해, 파싱 스레드의 캐시 포인터·장비 이름맵·recorded_trks 가 죽은 포인터를 참조 (특히 IAP 채널변경의 del 직후 add 가 해제 포인터 반환) → DEVICE 규칙처럼 살아있는 동안 삭제 금지, ~DISPLAY 에서 일괄 해제. picu-11 코어 덤프로 크래시 지점(add_rec vtable 간접호출) 확정. SNUH fleet ~100 노드 실배포에서 기대 크래시 다수 대비 실제 0, 미배포 노드는 계속 크래시. Fixed: Linux from_utf16 이 locale 의존·비스레드안전 wcstombs 를 쓰고 UTF-16 을 UTF-32 wchar_t 로 오해석하던 문제 → 수동 UTF-8 인코더(서로게이트 페어 재조립, 무효값 U+FFFD)로 교체. Added: AUTO_DETECT=1(vr.conf) 시리얼 장비 자동 판별. 회선 측정(보레이트·프레임폭·패리티) → 후보 필터(3축 일치, 베드당 클래스 배타, 포트 사전확률, 판별캐시) → 트랙 수확량 경쟁. 드라이버 무수정, 미설정 시 유휴 대기라 기존 설정 무영향. SNUH 수술장·ICU 실측 오탐 0.
1.19.25
1.19.25 Fixed: SyncPulseSerial(동기 펄스 생성기)에서 1초 간격의 펄스가 2초마다 하나씩만 기록되거나, 보드 종류에 따라 아예 한 값도 기록되지 않던 문제. 두 증상 모두 원인이 하나다 — 이 장비는 시작·끝을 알리는 문자 없이 6자리 숫자만 흘려보내므로 VR 이 “잠깐의 침묵” 을 메시지 경계로 삼고 있었는데, 침묵은 회선이 아니라 컴퓨터 사정으로도 생긴다. 운영체제가 6바이트를 몇 번에 나눠 올려주고 그 사이가 벌어지면 앞자리가 버려져 이후 모든 값이 한 칸씩 밀리고, 한 번 밀리면 스스로 회복하지 못해 아무것도 기록되지 않는다(구형 Arduino UNO 에서 재현). 반대로 두 메시지가 한꺼번에 올라오면 둘 다 같은 시각으로 찍혀 1초짜리 펄스가 2초마다 하나 있는 것처럼 보인다(UNO R4 에서 재현). 이제 경계를 시간이 아니라 내용으로 찾는다 — 카운터가 정확히 1씩 늘어나는 자리를 스스로 찾아 맞추므로 회선이 어떻게 쪼개져 들어오든 영향을 받지 않고, 어긋나도 다음 펄스에서 다시 맞춘다. 한 번에 여러 펄스가 올라온 경우에는 카운터 간격만큼 시각을 되돌려 각각 제 시각에 기록한다. 세 가지 조건(정상·쪼개짐·뭉침) 재현 시험으로 확인했다 (Nord University AniLab 제보). Fixed: Windows 에서 데이터를 띄엄띄엄 보내는 시리얼 장비의 값이 최대 한 주기 늦게 들어오던 문제. 문자 도착 알림을 기다리는 동안이 아니라 그 직전 대기 중에 데이터가 통째로 들어와 버리면, 새로 도착하는 문자가 없어 알림이 오지 않고 이미 들어온 데이터가 다음 전송이 올 때까지 그대로 대기했다. 이제 알림이 없어도 수신 버퍼를 직접 확인한다. 초당 수십~수백 바이트를 보내는 대부분의 장비에서는 드러나지 않던 문제이지만, 1초에 한 번처럼 드물게 보내는 장비에서는 두 전송이 한 번에 처리되어 시각이 뭉쳤다. 1.19.24 Fixed: Windows 에서 SyncPulseSerial(Arduino UNO R4)의 데이터가 전혀 들어오지 않거나 한참 뒤에야 간헐적으로 들어오던 문제. Arduino 의 USB 시리얼은 컴퓨터 쪽이 준비 신호(DTR)를 올려야만 데이터를 내보내는데, VR 이 Windows 에서 포트를 열 때 이 신호를 내린 채로 열고 있었다. 시리얼 터미널 프로그램들은 이 신호를 기본으로 올리기 때문에 터미널에서는 잘 보이는데 VR 에서만 안 보이는 형태로 나타났다 (Win11/Win7 3대 실측, Nord University AniLab 제보). 장비 연결 상태는 정상으로 표시되므로 화면만 봐서는 알 수 없는 문제였다. 다른 시리얼 장비들의 동작은 그대로다 — 이 신호에 민감한 장비가 있을 수 있어 Arduino 계열 드라이버에서만 올린다. 1.19.23 Fixed: SyncPulseSerial 의 PULSE 값이 화면에 보이지 않던 문제. 값은 정상적으로 수신되고 파일에도 기록되고 있었는데, 화면 눈금 범위가 카운터의 최대값(864000)으로 잡혀 있어 테스트 초반의 작은 값들이 트랙 맨 바닥 1픽셀에 눌려 보이지 않았다. 이제 매초 가운데에 뚜렷한 선으로 표시된다. 기록된 값 자체는 이전 버전에서도 온전했다(표시만의 문제). 1.19.22 Added: Dräger 마취기를 기종 이름으로 고를 수 있다. 장비 목록에 Fabius 가 추가됐고, 목록에 없는 Dräger 를 쓰는 곳을 위해 Medibus 라는 일반 항목도 함께 넣었다. Primus, Fabius, Medibus 는 통신 방식이 완전히 같고 이름만 다르므로 어느 것을 골라도 동작은 같지만, 실제 기종을 고르면 설정 파일과 화면이 무엇이 연결돼 있는지 스스로 설명하게 된다. 기존에 Primus 로 설정해 둔 곳은 그대로 두면 된다. Changed: 시리얼 장비 자동 판별이 마취기를 특정 기종이 아니라 통신 방식 이름(Medibus)으로 인식한다. 같은 통신 방식을 쓰는 기종들은 회선만 봐서는 구별할 수 없으므로, 근거 없이 모델명을 단정하지 않는 편이 낫다고 판단했다. 기종 이름이 필요하면 설정에서 직접 고르면 된다. 1.19.21 Added: Dräger 장비가 스스로 알려주는 기종명을 로그에 남긴다. 설정 파일의 장비 이름은 사람이 적어 넣은 것이라 실제 기종을 보장하지 않는데, 기종에 따라 낼 수 있는 파형이 다르므로(예: Fabius 는 기도압과 유량은 있고 CO2 가 없다) 무엇이 연결돼 있는지 아는 것이 문제 해결의 출발점이다. 통신이 시작될 때 한 번 남는다. 1.19.20 Fixed: Dräger 마취기 가운데 파형 기능이 없는 기종에서 데이터가 전혀 수집되지 않던 문제. 연결은 된 것처럼 보이지만 아무 값도 들어오지 않는다. VR 이 장비를 확인한 뒤 파형 설정을 물어보고 그 답을 받아야만 데이터 요청을 시작하도록 돼 있었는데, 파형 기능이 없는 기종은 그 질문에 답하지 않으므로 요청이 영영 시작되지 않았다. 연결 유지 신호도 함께 멎어 장비가 통신을 끊고 다시 연결하기를 반복한다. 이제 장비 확인 직후부터 데이터를 요청한다. 파형 응답이 없으면 그 기종의 정상 동작이므로 숫자값만 수집하며 로그에 한 번 알린다. Fabius GS 는 규격상 파형 기능이 없는 대표적인 기종이다 (University of Washington 제보). Added: SyncPulseSerial 동기 펄스 생성기 지원. 여러 장비의 기록을 나중에 맞추기 위한 공통 번호를 시리얼로 보내는 장치로, 1초마다 6자리 카운터를 보낸다. PULSE 트랙에 그 번호가 기록된다. 시작이나 끝을 알리는 문자가 없는 형식이라, 메시지 사이의 침묵을 경계로 삼고 카운터가 1씩 늘어나는지 확인해 맞는 장비인지 판단한다. 자동 판별 대상에도 포함돼 있고, 환자 모니터와 같은 자리에 함께 연결해도 서로 방해하지 않는다. 1.19.19 Added: Dräger 인공호흡기·마취기가 파형에 함께 실어 보내는 호흡 위상 신호를 기록한다. 장비가 흡기와 호기 전환 시점을 직접 알려주는 것이라 파형에서 역산하는 것보다 정확하다. VENT_PHASE 트랙에 흡기 시작 1, 호기 시작 0 으로 남는다. 시각은 파형과 같은 시간축을 쓴다. 종전에는 이 신호를 읽지 않고 버렸다. Added: 환기 모드를 기록한다(VENT_MODE). 모드는 VC-CMV, PC-BIPAP, SPN-CPAP 처럼 압력조절인지 용적조절인지를 알려주어 파형 해석에 직접 쓰인다. 장비가 띄우고 있는 다른 안내 문구도 VENT_MSG 로 남는다. VR 이 그동안 이 정보를 담은 명령을 아예 보내지 않고 있었다 — 이제 측정값·설정과 함께 번갈아 요청한다. 표기가 장비나 언어에 따라 달라도 되도록 문구가 아니라 규격 코드로 판별한다. Added: 파형이 서로 맞는지 확인할 근거를 로그에 남긴다. 용적은 유량을 적분한 것이므로 둘의 관계를 수치로 기록해 둔다. 다만 이것으로 파형 이름을 바꾸거나 경고하지는 않는다 — 용적조절 환기에서는 기도압도 용적에 거의 비례해서 이 수치만으로는 둘을 구별할 수 없기 때문이다. 1.19.18 Added: Y케이블로 엿보는 Dräger 연결에서 파형 종류를 자동으로 알아낸다. Y케이블 배선에 따라 장비가 보내는 것만 보이는 방과, 반대편 장치가 보내는 것까지 보이는 방이 있는데, 후자에서는 그 장치가 파형을 요청하는 내용이 VR 에도 보인다. 거기에 순서가 그대로 들어 있으므로 추정 없이 확정한다 (실제로 수술장 두 방을 재보니 한 방은 양방향, 한 방은 단방향이었다). 단방향인 방은 종전대로 wavs= 로 적어주거나 도착 순서 추정을 쓴다. Fixed: 엿보는 연결인데도 VR 이 자기가 고른 파형 순서를 적용해 이름이 어긋날 수 있던 문제 — 회선에 다른 장치가 있는지를 통신 내용으로 판별해(보낸 적 없는 명령의 응답이 보이면 다른 장치가 있는 것이다) 그런 경우 순서를 스스로 정하지 않는다. 어느 모드로 동작 중인지는 로그에 남는다. 1.19.17 Added: Y케이블로 엿보기만 하는 Dräger 연결에서 파형 이름을 확정할 수 있다. 이런 연결은 VR 이 장비에 아무것도 보낼 수 없어 어떤 파형이 어떤 순서로 오는지 물어볼 수가 없고, 그래서 도착 순서대로 추정해 기록해 왔다(이름이 실제와 다를 수 있었다). 이제 vr.conf 의 장비 항목에 wavs= 로 그 방의 순서를 적어두면 그대로 사용한다. 순서는 그 장비에 연결된 다른 장치가 정한 것이라 병원에서 알 수 있는 값이다. 예: wavs=AWP,AWF. 적지 않으면 종전과 같이 도착 순서로 추정하며, 추정 중이라는 사실과 확정하는 방법을 로그에 남긴다. 직결 연결에서는 종전대로 VR 이 직접 물어보므로 이 설정이 없어도 정확하다. Added: 회선에서 처음 보는 종류의 통신 프레임을 한 번씩 로그에 남긴다. 어떤 장치가 무엇을 주고받는지 현장에서 확인할 수 있어 연결 문제 진단에 쓴다. 1.19.16 Fixed: 1.19.15 에서 Y케이블로 엿보기만 하는 연결(서울대병원 수술장의 Dräger 전부가 이 방식)의 파형이 기록되지 않던 문제 — 1.19.15 는 VR 이 직접 요청한 파형만 기록하도록 바뀌었는데, 관찰 전용 연결에서는 VR 이 장비에 아무것도 보낼 수 없어(송신선이 끊겨 있다) 요청 자체를 못 하므로 들어오는 파형을 전부 버렸다. 이제 이런 연결에서는 1.19.14 이전과 같이 도착 순서대로 기록한다. 다른 장치가 설정해 둔 순서는 알 수 없으므로 이름이 실제와 다를 수 있고, 그 사실을 로그에 함께 남긴다. 1.19.15 를 이미 설치한 곳은 이 버전으로 올려야 한다. 1.19.15 Fixed: Dräger 마취기·인공호흡기(MEDIBUS)에서 파형이 엉뚱한 이름으로 기록되던 문제. 장비가 보내는 파형 묶음의 첫 바이트에는 “몇 번째로 요청한 파형이 들어있는가” 가 비트로 표시되는데, VR 은 그 비트를 세기만 하고 도착 순서대로 미리 정해둔 트랙(CO2, AWP, AWF)에 넣었다. 장비가 요청한 파형 중 일부만 보내면 그만큼 밀려 기록돼, 예를 들어 기도압(AWP)만 보내는 인공호흡기에서 그 파형이 CO2 로 저장됐다 (Evita V600 실측 제보). 이제 비트를 해석해 요청한 순서와 맞춰 넣는다. Fixed: 요청하는 파형이 CO2 와 기도압 두 가지로 고정돼 있어, 장비가 유량(Flow)이나 용적(Volume) 파형을 제공해도 받지 못했다. 이제 장비에게 먼저 제공 가능한 파형 목록을 물어보고 그중에서 요청한다. Added: vr.conf 의 장비 설정에 wavs= 로 받을 파형을 고를 수 있다 (예: wavs=AWP,AWF,VOL,CO2). 비워두면 장비가 제공하는 것을 앞에서부터 최대 4개까지 자동으로 받는다. 4개인 이유는 한 묶음에 최대 4개까지만 표시할 수 있기 때문이다. 지원 이름: AWP AWF PLETH VOL O2 CO2 CO2_KPA CO2_PCT AGENT HAL ENF ISO DES SEV PTRACH AWF_DEV (16진 코드도 가능). 파형을 늘리면 회선 대역을 더 쓰므로 9600bps 의 80% 를 넘으면 로그로 경고한다. 어떤 파형이 실제로 들어오고 있는지도 로그에 남겨 현장에서 확인할 수 있다. 1.19.14 Added: 서버에서 VR 로 내려보내는 명령(원격 시간 동기, 원격 업그레이드, 재시작, 설정 변경 등)을 다시 사용할 수 있다. 1.18.52 에서 이 경로를 통째로 막았는데, 시간 동기와 원격 업그레이드가 꼭 필요한 기관이 있어 되살렸다. 다만 원격으로 프로그램을 바꾸거나 설정을 덮어쓰는 것은 그 자체로 위험하므로 기본값은 여전히 전부 차단이고, vr.conf 에 SERVER_CONTROL 로 허용할 명령을 직접 적어야 동작한다 (예: SERVER_CONTROL=dt 는 시간 동기만, SERVER_CONTROL=dt,update 는 시간 동기와 업그레이드만, SERVER_CONTROL=1 은 전부 허용). 사용 가능한 명령은 update restart reboot edit_conf new_bed del_bed edit_bed dt add_event. 시간 동기는 예전과 달리 녹화 중에는 시계를 건너뛰지 않는다 — 녹화 중 시계를 갑자기 옮기면 파형에 공백이 생기거나 기록이 겹치므로, 녹화 중이면 서서히 당겨 맞추고(슬루) 녹화가 없을 때만 한번에 맞춘다. 2초 이내 차이는 무시한다. 예전 구현이 10분 이상 차이날 때 프로그램을 강제 종료하던 동작도 없앴다. 1.19.13 Fixed: 웹 모니터링 화면에서 EEG 파형이 0.1초 정도씩 끊겨 보이던 문제 — 화면 전송용으로 파형을 줄일 때 원본 주파수가 나누어떨어지지 않으면 일부 구간이 비어 그만큼 빈칸으로 보였다. 나누어떨어지는 비율로만 줄이도록 수정. 녹화 파일에는 원래 문제가 없었다(표시만의 문제). 1.19.12 Fixed: Dräger 마취기 연동 안정화 보완 — 시리얼 회선 자동판별 중 잡음이 잘못된 데이터로 오인될 여지를 차단. 1.19.11 Fixed: Dräger 마취기(Atlan 등 MEDIBUS 장비)에서 통신 오류 알람(COM1 failure)이 반복되고 파형이 전혀 기록되지 않던 문제. 원인 세 가지를 모두 수정했다. (1) 장비는 3초 안에 아무 신호라도 받지 못하면 연결을 끊는데, VR 이 5초마다 데이터를 요청하는 것 외에 보내는 것이 없어 매번 연결이 끊기고 재접속을 반복했다 — 규격이 정한 연결 유지 신호(NOP)를 2초마다 보내도록 했다(데이터 요청 주기는 그대로). (2) 장비가 응답 도중에 다른 명령을 끼워 보내는 것이 규격상 허용되는데, VR 이 그때 받던 응답을 통째로 버려 파형 설정 응답을 매번 놓쳤다 — 끼어든 부분을 건너뛰고 원래 응답을 이어받도록 했다. (3) 장비가 보내는 요청 하나에 VR 이 응답하지 않아 통신이 끊기던 것도 수정. 파형·수치가 정상 기록되고 알람이 사라진다. 1.19.10 Fixed: 하루 한 번의 자동 재시작 시점에 수집이 통째로 멎어 사람이 손을 대기 전까지 복구되지 않던 문제(or-106, 104분 무데이터) — 종료 과정이 끝나지 않아 프로그램이 죽지 않았고, 죽지 않으니 자동 재시작도 일어나지 않았다. 주원인은 시리얼 회선 자동판별(AUTO_DETECT)이 종료 신호를 보지 않고 스윕(보레이트 6종 x 포맷 8종)을 끝까지 돌던 것으로, 무음 회선에서 종료에 2분이 걸려 정상 종료 유예시간을 넘겼다. 이제 종료 즉시 판별을 중단한다(실측 2분 -> 3초). 함께 종료 중 스레드 정리가 서로를 기다릴 수 있던 두 곳을 제거하고, 그럼에도 종료가 20초 안에 끝나지 않으면 강제 종료해 반드시 재시작되도록 안전장치를 넣었다. 녹화 파일은 5초마다 저장되므로 유실되지 않는다. 1.19.9 Fixed: 큰 녹화 파일(약 100MB 이상)을 서버로 올릴 때 프로그램이 메모리 부족으로 강제 종료되고, 재시작 후 같은 파일을 다시 시도하다 무한 반복되던 문제 — 업로드 본문을 메모리에 통째로 만들지 않고 디스크에서 흘려보내도록 변경(100MB 파일 기준 최대 메모리 208MB → 14MB). 큰 파일이 한 개라도 쌓이면 그 장비의 이후 업로드가 전부 막히던 증상이 사라진다. 1.19.8 Fixed: 자동 파일 자르기(CUT_FILE) 를 쓰는 베드에서 환자가 계속 연결돼 있는데도 녹화 파일이 PT_WAITING_TIME(기본 5분) 마다 잘리던 문제 — 웹 모니터링 전송 후 메모리를 비우는 과정에서 환자 연결 판정에 쓰이는 최근 구간까지 지워져 "환자가 나갔다" 로 오판하던 것. 이제 판정에 필요한 구간은 보존한다. 파일이 5분마다 끊기고 그때마다 수 초가 유실되던 증상이 사라진다. 1.19.7 Added: GE Corometrics(분만 태아감시) 모체 vitals 지원 — 기존엔 태아 심박(HR1/HR2)·자궁수축(UACT)만 기록됐으나 이제 같은 장비가 함께 보내는 모체 SpO2/맥박(PR)/NIBP(수축·이완·평균)도 한 파일에 기록. Fixed: 프로세스 종료(서비스 매니저의 정상 종료 요청, SIGTERM) 시 즉시 종료되지 않고 강제 종료되던 문제 — 이제 SIGTERM 을 받아 녹화 버퍼를 flush 하고 스레드를 정리한 뒤 정상 종료. 1.19.5 Fixed: CUT_HOURLY 가 동작하지 않는 것처럼 보이던 문제의 실체 — k3s 컨테이너에서 기본 저장 경로가 $HOME/recordings(임시 레이어)로 잡혀, 매일 자정 재시작 때 업로드 안 된 하루치 녹화가 통째로 사라짐. hostPath 마운트 /recordings 가 있으면 그것을 기본 저장 경로로 사용. Fixed: 종료(daily restart) 시 멀티베드 TCP 노드가 terminate(exit=133)/SIGSEGV(139) 로 죽던 문제. Linux finalize 가 device 스레드를 정지하지 않고 반환해 exit() 전역 소멸자가 살아있는 PortRouter accept_thread 를 파괴하며 std::terminate — DISPLAY::quiesce_threads + PortRouter::shutdown 으로 정리 후 종료 (코어 백트레이스로 확정, 로컬 재현 후 수정 검증). Fixed: Link+/Agilia 파서가 시리얼 노이즈(size byte 0 또는 헤더 없는 잡음 8byte)에 역전된 반복자 범위로 vector 를 생성하다 std::length_error 로 죽던 문제(or-110) — sentinel 상태 파싱 봉쇄 + 재동기화. Fixed: FILENAME_TEMPLATE 의 %Y(4자리 연도)가 "0126" 으로 나오던 문제 (tm_year+1900 누락). 1.19.4 Fixed: Link+ 등 재접속이 잦은 장비에서 시리얼 포트를 재오픈할 때 "terminate called without an active exception" 으로 프로세스가 죽던 문제(exit=133). reading_thread_func 이 스스로 반환해도 std::thread 객체는 joinable 로 남는데, open() 이 join 없이 새 스레드를 대입해 std::terminate 가 호출됨 → 스레드 생성 전에 이전 reading/parsing 스레드를 join 후 재생성. Fixed: headless/AUTO_DETECT 모드에서 실제 장비가 없을 때 Demo 장비가 생성돼 가짜 vital 트랙 38종이 업로드되던 문제 → --console 또는 AUTO_DETECT 시 Demo 미생성. 1.19.3 Fixed: 여러 베드를 한 프로세스가 TCP 로 수집하는 구성에서 클라이언트 재접속이 잦으면 프로세스가 SIGSEGV 로 죽던 문제(accept_handoff 가 replace 시 옛 소켓을 즉시 close 해 recv 중인 reader 와 경합, fd 번호 재사용). tcp reader 를 poll(200ms) 게이트 뒤의 recv 로 재구성하고 replace 는 shutdown 후 소유권을 reader/close 로 단일화. SNUH 회복실 VRN 15대에서 재현. Fixed: ICU 등 Intellivue 다수 베드에서 반복되던 SIGSEGV(139) 두 계열의 use-after-free. (1) DISPLAY::add_rec 이 레코드를 트랙에 push 한 뒤 저장했는데, push 직후 p 가 다른 스레드에 공유되고 Linux 의 메모리 보존 루프(600초 이전 레코드 delete)가 시계 어긋난 모니터에서 방금 넣은 레코드를 즉시 evict 해, 이어지는 p->save() 가 해제된 메모리에 가상 호출 → 저장을 push 앞으로 옮겨 공유 후 p 미접근. (2) DISPLAY::del_trk 이 트랙을 목록에서 뺀 직후 raw delete 해, 파싱 스레드의 캐시 포인터·장비 이름맵·recorded_trks 가 죽은 포인터를 참조 (특히 IAP 채널변경의 del 직후 add 가 해제 포인터 반환) → DEVICE 규칙처럼 살아있는 동안 삭제 금지, ~DISPLAY 에서 일괄 해제. picu-11 코어 덤프로 크래시 지점(add_rec vtable 간접호출) 확정. SNUH fleet ~100 노드 실배포에서 기대 크래시 다수 대비 실제 0, 미배포 노드는 계속 크래시. Fixed: Linux from_utf16 이 locale 의존·비스레드안전 wcstombs 를 쓰고 UTF-16 을 UTF-32 wchar_t 로 오해석하던 문제 → 수동 UTF-8 인코더(서로게이트 페어 재조립, 무효값 U+FFFD)로 교체. Added: AUTO_DETECT=1(vr.conf) 시리얼 장비 자동 판별. 회선 측정(보레이트·프레임폭·패리티) → 후보 필터(3축 일치, 베드당 클래스 배타, 포트 사전확률, 판별캐시) → 트랙 수확량 경쟁. 드라이버 무수정, 미설정 시 유휴 대기라 기존 설정 무영향. SNUH 수술장·ICU 실측 오탐 0.
1.19.24
1.19.24 Fixed: Windows 에서 SyncPulseSerial(Arduino UNO R4)의 데이터가 전혀 들어오지 않거나 한참 뒤에야 간헐적으로 들어오던 문제. Arduino 의 USB 시리얼은 컴퓨터 쪽이 준비 신호(DTR)를 올려야만 데이터를 내보내는데, VR 이 Windows 에서 포트를 열 때 이 신호를 내린 채로 열고 있었다. 시리얼 터미널 프로그램들은 이 신호를 기본으로 올리기 때문에 터미널에서는 잘 보이는데 VR 에서만 안 보이는 형태로 나타났다 (Win11/Win7 3대 실측, Nord University AniLab 제보). 장비 연결 상태는 정상으로 표시되므로 화면만 봐서는 알 수 없는 문제였다. 다른 시리얼 장비들의 동작은 그대로다 — 이 신호에 민감한 장비가 있을 수 있어 Arduino 계열 드라이버에서만 올린다. 1.19.23 Fixed: SyncPulseSerial 의 PULSE 값이 화면에 보이지 않던 문제. 값은 정상적으로 수신되고 파일에도 기록되고 있었는데, 화면 눈금 범위가 카운터의 최대값(864000)으로 잡혀 있어 테스트 초반의 작은 값들이 트랙 맨 바닥 1픽셀에 눌려 보이지 않았다. 이제 매초 가운데에 뚜렷한 선으로 표시된다. 기록된 값 자체는 이전 버전에서도 온전했다(표시만의 문제). 1.19.22 Added: Dräger 마취기를 기종 이름으로 고를 수 있다. 장비 목록에 Fabius 가 추가됐고, 목록에 없는 Dräger 를 쓰는 곳을 위해 Medibus 라는 일반 항목도 함께 넣었다. Primus, Fabius, Medibus 는 통신 방식이 완전히 같고 이름만 다르므로 어느 것을 골라도 동작은 같지만, 실제 기종을 고르면 설정 파일과 화면이 무엇이 연결돼 있는지 스스로 설명하게 된다. 기존에 Primus 로 설정해 둔 곳은 그대로 두면 된다. Changed: 시리얼 장비 자동 판별이 마취기를 특정 기종이 아니라 통신 방식 이름(Medibus)으로 인식한다. 같은 통신 방식을 쓰는 기종들은 회선만 봐서는 구별할 수 없으므로, 근거 없이 모델명을 단정하지 않는 편이 낫다고 판단했다. 기종 이름이 필요하면 설정에서 직접 고르면 된다. 1.19.21 Added: Dräger 장비가 스스로 알려주는 기종명을 로그에 남긴다. 설정 파일의 장비 이름은 사람이 적어 넣은 것이라 실제 기종을 보장하지 않는데, 기종에 따라 낼 수 있는 파형이 다르므로(예: Fabius 는 기도압과 유량은 있고 CO2 가 없다) 무엇이 연결돼 있는지 아는 것이 문제 해결의 출발점이다. 통신이 시작될 때 한 번 남는다. 1.19.20 Fixed: Dräger 마취기 가운데 파형 기능이 없는 기종에서 데이터가 전혀 수집되지 않던 문제. 연결은 된 것처럼 보이지만 아무 값도 들어오지 않는다. VR 이 장비를 확인한 뒤 파형 설정을 물어보고 그 답을 받아야만 데이터 요청을 시작하도록 돼 있었는데, 파형 기능이 없는 기종은 그 질문에 답하지 않으므로 요청이 영영 시작되지 않았다. 연결 유지 신호도 함께 멎어 장비가 통신을 끊고 다시 연결하기를 반복한다. 이제 장비 확인 직후부터 데이터를 요청한다. 파형 응답이 없으면 그 기종의 정상 동작이므로 숫자값만 수집하며 로그에 한 번 알린다. Fabius GS 는 규격상 파형 기능이 없는 대표적인 기종이다 (University of Washington 제보). Added: SyncPulseSerial 동기 펄스 생성기 지원. 여러 장비의 기록을 나중에 맞추기 위한 공통 번호를 시리얼로 보내는 장치로, 1초마다 6자리 카운터를 보낸다. PULSE 트랙에 그 번호가 기록된다. 시작이나 끝을 알리는 문자가 없는 형식이라, 메시지 사이의 침묵을 경계로 삼고 카운터가 1씩 늘어나는지 확인해 맞는 장비인지 판단한다. 자동 판별 대상에도 포함돼 있고, 환자 모니터와 같은 자리에 함께 연결해도 서로 방해하지 않는다. 1.19.19 Added: Dräger 인공호흡기·마취기가 파형에 함께 실어 보내는 호흡 위상 신호를 기록한다. 장비가 흡기와 호기 전환 시점을 직접 알려주는 것이라 파형에서 역산하는 것보다 정확하다. VENT_PHASE 트랙에 흡기 시작 1, 호기 시작 0 으로 남는다. 시각은 파형과 같은 시간축을 쓴다. 종전에는 이 신호를 읽지 않고 버렸다. Added: 환기 모드를 기록한다(VENT_MODE). 모드는 VC-CMV, PC-BIPAP, SPN-CPAP 처럼 압력조절인지 용적조절인지를 알려주어 파형 해석에 직접 쓰인다. 장비가 띄우고 있는 다른 안내 문구도 VENT_MSG 로 남는다. VR 이 그동안 이 정보를 담은 명령을 아예 보내지 않고 있었다 — 이제 측정값·설정과 함께 번갈아 요청한다. 표기가 장비나 언어에 따라 달라도 되도록 문구가 아니라 규격 코드로 판별한다. Added: 파형이 서로 맞는지 확인할 근거를 로그에 남긴다. 용적은 유량을 적분한 것이므로 둘의 관계를 수치로 기록해 둔다. 다만 이것으로 파형 이름을 바꾸거나 경고하지는 않는다 — 용적조절 환기에서는 기도압도 용적에 거의 비례해서 이 수치만으로는 둘을 구별할 수 없기 때문이다. 1.19.18 Added: Y케이블로 엿보는 Dräger 연결에서 파형 종류를 자동으로 알아낸다. Y케이블 배선에 따라 장비가 보내는 것만 보이는 방과, 반대편 장치가 보내는 것까지 보이는 방이 있는데, 후자에서는 그 장치가 파형을 요청하는 내용이 VR 에도 보인다. 거기에 순서가 그대로 들어 있으므로 추정 없이 확정한다 (실제로 수술장 두 방을 재보니 한 방은 양방향, 한 방은 단방향이었다). 단방향인 방은 종전대로 wavs= 로 적어주거나 도착 순서 추정을 쓴다. Fixed: 엿보는 연결인데도 VR 이 자기가 고른 파형 순서를 적용해 이름이 어긋날 수 있던 문제 — 회선에 다른 장치가 있는지를 통신 내용으로 판별해(보낸 적 없는 명령의 응답이 보이면 다른 장치가 있는 것이다) 그런 경우 순서를 스스로 정하지 않는다. 어느 모드로 동작 중인지는 로그에 남는다. 1.19.17 Added: Y케이블로 엿보기만 하는 Dräger 연결에서 파형 이름을 확정할 수 있다. 이런 연결은 VR 이 장비에 아무것도 보낼 수 없어 어떤 파형이 어떤 순서로 오는지 물어볼 수가 없고, 그래서 도착 순서대로 추정해 기록해 왔다(이름이 실제와 다를 수 있었다). 이제 vr.conf 의 장비 항목에 wavs= 로 그 방의 순서를 적어두면 그대로 사용한다. 순서는 그 장비에 연결된 다른 장치가 정한 것이라 병원에서 알 수 있는 값이다. 예: wavs=AWP,AWF. 적지 않으면 종전과 같이 도착 순서로 추정하며, 추정 중이라는 사실과 확정하는 방법을 로그에 남긴다. 직결 연결에서는 종전대로 VR 이 직접 물어보므로 이 설정이 없어도 정확하다. Added: 회선에서 처음 보는 종류의 통신 프레임을 한 번씩 로그에 남긴다. 어떤 장치가 무엇을 주고받는지 현장에서 확인할 수 있어 연결 문제 진단에 쓴다. 1.19.16 Fixed: 1.19.15 에서 Y케이블로 엿보기만 하는 연결(서울대병원 수술장의 Dräger 전부가 이 방식)의 파형이 기록되지 않던 문제 — 1.19.15 는 VR 이 직접 요청한 파형만 기록하도록 바뀌었는데, 관찰 전용 연결에서는 VR 이 장비에 아무것도 보낼 수 없어(송신선이 끊겨 있다) 요청 자체를 못 하므로 들어오는 파형을 전부 버렸다. 이제 이런 연결에서는 1.19.14 이전과 같이 도착 순서대로 기록한다. 다른 장치가 설정해 둔 순서는 알 수 없으므로 이름이 실제와 다를 수 있고, 그 사실을 로그에 함께 남긴다. 1.19.15 를 이미 설치한 곳은 이 버전으로 올려야 한다. 1.19.15 Fixed: Dräger 마취기·인공호흡기(MEDIBUS)에서 파형이 엉뚱한 이름으로 기록되던 문제. 장비가 보내는 파형 묶음의 첫 바이트에는 “몇 번째로 요청한 파형이 들어있는가” 가 비트로 표시되는데, VR 은 그 비트를 세기만 하고 도착 순서대로 미리 정해둔 트랙(CO2, AWP, AWF)에 넣었다. 장비가 요청한 파형 중 일부만 보내면 그만큼 밀려 기록돼, 예를 들어 기도압(AWP)만 보내는 인공호흡기에서 그 파형이 CO2 로 저장됐다 (Evita V600 실측 제보). 이제 비트를 해석해 요청한 순서와 맞춰 넣는다. Fixed: 요청하는 파형이 CO2 와 기도압 두 가지로 고정돼 있어, 장비가 유량(Flow)이나 용적(Volume) 파형을 제공해도 받지 못했다. 이제 장비에게 먼저 제공 가능한 파형 목록을 물어보고 그중에서 요청한다. Added: vr.conf 의 장비 설정에 wavs= 로 받을 파형을 고를 수 있다 (예: wavs=AWP,AWF,VOL,CO2). 비워두면 장비가 제공하는 것을 앞에서부터 최대 4개까지 자동으로 받는다. 4개인 이유는 한 묶음에 최대 4개까지만 표시할 수 있기 때문이다. 지원 이름: AWP AWF PLETH VOL O2 CO2 CO2_KPA CO2_PCT AGENT HAL ENF ISO DES SEV PTRACH AWF_DEV (16진 코드도 가능). 파형을 늘리면 회선 대역을 더 쓰므로 9600bps 의 80% 를 넘으면 로그로 경고한다. 어떤 파형이 실제로 들어오고 있는지도 로그에 남겨 현장에서 확인할 수 있다. 1.19.14 Added: 서버에서 VR 로 내려보내는 명령(원격 시간 동기, 원격 업그레이드, 재시작, 설정 변경 등)을 다시 사용할 수 있다. 1.18.52 에서 이 경로를 통째로 막았는데, 시간 동기와 원격 업그레이드가 꼭 필요한 기관이 있어 되살렸다. 다만 원격으로 프로그램을 바꾸거나 설정을 덮어쓰는 것은 그 자체로 위험하므로 기본값은 여전히 전부 차단이고, vr.conf 에 SERVER_CONTROL 로 허용할 명령을 직접 적어야 동작한다 (예: SERVER_CONTROL=dt 는 시간 동기만, SERVER_CONTROL=dt,update 는 시간 동기와 업그레이드만, SERVER_CONTROL=1 은 전부 허용). 사용 가능한 명령은 update restart reboot edit_conf new_bed del_bed edit_bed dt add_event. 시간 동기는 예전과 달리 녹화 중에는 시계를 건너뛰지 않는다 — 녹화 중 시계를 갑자기 옮기면 파형에 공백이 생기거나 기록이 겹치므로, 녹화 중이면 서서히 당겨 맞추고(슬루) 녹화가 없을 때만 한번에 맞춘다. 2초 이내 차이는 무시한다. 예전 구현이 10분 이상 차이날 때 프로그램을 강제 종료하던 동작도 없앴다. 1.19.13 Fixed: 웹 모니터링 화면에서 EEG 파형이 0.1초 정도씩 끊겨 보이던 문제 — 화면 전송용으로 파형을 줄일 때 원본 주파수가 나누어떨어지지 않으면 일부 구간이 비어 그만큼 빈칸으로 보였다. 나누어떨어지는 비율로만 줄이도록 수정. 녹화 파일에는 원래 문제가 없었다(표시만의 문제). 1.19.12 Fixed: Dräger 마취기 연동 안정화 보완 — 시리얼 회선 자동판별 중 잡음이 잘못된 데이터로 오인될 여지를 차단. 1.19.11 Fixed: Dräger 마취기(Atlan 등 MEDIBUS 장비)에서 통신 오류 알람(COM1 failure)이 반복되고 파형이 전혀 기록되지 않던 문제. 원인 세 가지를 모두 수정했다. (1) 장비는 3초 안에 아무 신호라도 받지 못하면 연결을 끊는데, VR 이 5초마다 데이터를 요청하는 것 외에 보내는 것이 없어 매번 연결이 끊기고 재접속을 반복했다 — 규격이 정한 연결 유지 신호(NOP)를 2초마다 보내도록 했다(데이터 요청 주기는 그대로). (2) 장비가 응답 도중에 다른 명령을 끼워 보내는 것이 규격상 허용되는데, VR 이 그때 받던 응답을 통째로 버려 파형 설정 응답을 매번 놓쳤다 — 끼어든 부분을 건너뛰고 원래 응답을 이어받도록 했다. (3) 장비가 보내는 요청 하나에 VR 이 응답하지 않아 통신이 끊기던 것도 수정. 파형·수치가 정상 기록되고 알람이 사라진다. 1.19.10 Fixed: 하루 한 번의 자동 재시작 시점에 수집이 통째로 멎어 사람이 손을 대기 전까지 복구되지 않던 문제(or-106, 104분 무데이터) — 종료 과정이 끝나지 않아 프로그램이 죽지 않았고, 죽지 않으니 자동 재시작도 일어나지 않았다. 주원인은 시리얼 회선 자동판별(AUTO_DETECT)이 종료 신호를 보지 않고 스윕(보레이트 6종 x 포맷 8종)을 끝까지 돌던 것으로, 무음 회선에서 종료에 2분이 걸려 정상 종료 유예시간을 넘겼다. 이제 종료 즉시 판별을 중단한다(실측 2분 -> 3초). 함께 종료 중 스레드 정리가 서로를 기다릴 수 있던 두 곳을 제거하고, 그럼에도 종료가 20초 안에 끝나지 않으면 강제 종료해 반드시 재시작되도록 안전장치를 넣었다. 녹화 파일은 5초마다 저장되므로 유실되지 않는다. 1.19.9 Fixed: 큰 녹화 파일(약 100MB 이상)을 서버로 올릴 때 프로그램이 메모리 부족으로 강제 종료되고, 재시작 후 같은 파일을 다시 시도하다 무한 반복되던 문제 — 업로드 본문을 메모리에 통째로 만들지 않고 디스크에서 흘려보내도록 변경(100MB 파일 기준 최대 메모리 208MB → 14MB). 큰 파일이 한 개라도 쌓이면 그 장비의 이후 업로드가 전부 막히던 증상이 사라진다. 1.19.8 Fixed: 자동 파일 자르기(CUT_FILE) 를 쓰는 베드에서 환자가 계속 연결돼 있는데도 녹화 파일이 PT_WAITING_TIME(기본 5분) 마다 잘리던 문제 — 웹 모니터링 전송 후 메모리를 비우는 과정에서 환자 연결 판정에 쓰이는 최근 구간까지 지워져 "환자가 나갔다" 로 오판하던 것. 이제 판정에 필요한 구간은 보존한다. 파일이 5분마다 끊기고 그때마다 수 초가 유실되던 증상이 사라진다. 1.19.7 Added: GE Corometrics(분만 태아감시) 모체 vitals 지원 — 기존엔 태아 심박(HR1/HR2)·자궁수축(UACT)만 기록됐으나 이제 같은 장비가 함께 보내는 모체 SpO2/맥박(PR)/NIBP(수축·이완·평균)도 한 파일에 기록. Fixed: 프로세스 종료(서비스 매니저의 정상 종료 요청, SIGTERM) 시 즉시 종료되지 않고 강제 종료되던 문제 — 이제 SIGTERM 을 받아 녹화 버퍼를 flush 하고 스레드를 정리한 뒤 정상 종료. 1.19.5 Fixed: CUT_HOURLY 가 동작하지 않는 것처럼 보이던 문제의 실체 — k3s 컨테이너에서 기본 저장 경로가 $HOME/recordings(임시 레이어)로 잡혀, 매일 자정 재시작 때 업로드 안 된 하루치 녹화가 통째로 사라짐. hostPath 마운트 /recordings 가 있으면 그것을 기본 저장 경로로 사용. Fixed: 종료(daily restart) 시 멀티베드 TCP 노드가 terminate(exit=133)/SIGSEGV(139) 로 죽던 문제. Linux finalize 가 device 스레드를 정지하지 않고 반환해 exit() 전역 소멸자가 살아있는 PortRouter accept_thread 를 파괴하며 std::terminate — DISPLAY::quiesce_threads + PortRouter::shutdown 으로 정리 후 종료 (코어 백트레이스로 확정, 로컬 재현 후 수정 검증). Fixed: Link+/Agilia 파서가 시리얼 노이즈(size byte 0 또는 헤더 없는 잡음 8byte)에 역전된 반복자 범위로 vector 를 생성하다 std::length_error 로 죽던 문제(or-110) — sentinel 상태 파싱 봉쇄 + 재동기화. Fixed: FILENAME_TEMPLATE 의 %Y(4자리 연도)가 "0126" 으로 나오던 문제 (tm_year+1900 누락). 1.19.4 Fixed: Link+ 등 재접속이 잦은 장비에서 시리얼 포트를 재오픈할 때 "terminate called without an active exception" 으로 프로세스가 죽던 문제(exit=133). reading_thread_func 이 스스로 반환해도 std::thread 객체는 joinable 로 남는데, open() 이 join 없이 새 스레드를 대입해 std::terminate 가 호출됨 → 스레드 생성 전에 이전 reading/parsing 스레드를 join 후 재생성. Fixed: headless/AUTO_DETECT 모드에서 실제 장비가 없을 때 Demo 장비가 생성돼 가짜 vital 트랙 38종이 업로드되던 문제 → --console 또는 AUTO_DETECT 시 Demo 미생성. 1.19.3 Fixed: 여러 베드를 한 프로세스가 TCP 로 수집하는 구성에서 클라이언트 재접속이 잦으면 프로세스가 SIGSEGV 로 죽던 문제(accept_handoff 가 replace 시 옛 소켓을 즉시 close 해 recv 중인 reader 와 경합, fd 번호 재사용). tcp reader 를 poll(200ms) 게이트 뒤의 recv 로 재구성하고 replace 는 shutdown 후 소유권을 reader/close 로 단일화. SNUH 회복실 VRN 15대에서 재현. Fixed: ICU 등 Intellivue 다수 베드에서 반복되던 SIGSEGV(139) 두 계열의 use-after-free. (1) DISPLAY::add_rec 이 레코드를 트랙에 push 한 뒤 저장했는데, push 직후 p 가 다른 스레드에 공유되고 Linux 의 메모리 보존 루프(600초 이전 레코드 delete)가 시계 어긋난 모니터에서 방금 넣은 레코드를 즉시 evict 해, 이어지는 p->save() 가 해제된 메모리에 가상 호출 → 저장을 push 앞으로 옮겨 공유 후 p 미접근. (2) DISPLAY::del_trk 이 트랙을 목록에서 뺀 직후 raw delete 해, 파싱 스레드의 캐시 포인터·장비 이름맵·recorded_trks 가 죽은 포인터를 참조 (특히 IAP 채널변경의 del 직후 add 가 해제 포인터 반환) → DEVICE 규칙처럼 살아있는 동안 삭제 금지, ~DISPLAY 에서 일괄 해제. picu-11 코어 덤프로 크래시 지점(add_rec vtable 간접호출) 확정. SNUH fleet ~100 노드 실배포에서 기대 크래시 다수 대비 실제 0, 미배포 노드는 계속 크래시. Fixed: Linux from_utf16 이 locale 의존·비스레드안전 wcstombs 를 쓰고 UTF-16 을 UTF-32 wchar_t 로 오해석하던 문제 → 수동 UTF-8 인코더(서로게이트 페어 재조립, 무효값 U+FFFD)로 교체. Added: AUTO_DETECT=1(vr.conf) 시리얼 장비 자동 판별. 회선 측정(보레이트·프레임폭·패리티) → 후보 필터(3축 일치, 베드당 클래스 배타, 포트 사전확률, 판별캐시) → 트랙 수확량 경쟁. 드라이버 무수정, 미설정 시 유휴 대기라 기존 설정 무영향. SNUH 수술장·ICU 실측 오탐 0.
1.19.23
1.19.23 Fixed: SyncPulseSerial 의 PULSE 값이 화면에 보이지 않던 문제. 값은 정상적으로 수신되고 파일에도 기록되고 있었는데, 화면 눈금 범위가 카운터의 최대값(864000)으로 잡혀 있어 테스트 초반의 작은 값들이 트랙 맨 바닥 1픽셀에 눌려 보이지 않았다. 이제 매초 가운데에 뚜렷한 선으로 표시된다. 기록된 값 자체는 이전 버전에서도 온전했다(표시만의 문제). 1.19.22 Added: Dräger 마취기를 기종 이름으로 고를 수 있다. 장비 목록에 Fabius 가 추가됐고, 목록에 없는 Dräger 를 쓰는 곳을 위해 Medibus 라는 일반 항목도 함께 넣었다. Primus, Fabius, Medibus 는 통신 방식이 완전히 같고 이름만 다르므로 어느 것을 골라도 동작은 같지만, 실제 기종을 고르면 설정 파일과 화면이 무엇이 연결돼 있는지 스스로 설명하게 된다. 기존에 Primus 로 설정해 둔 곳은 그대로 두면 된다. Changed: 시리얼 장비 자동 판별이 마취기를 특정 기종이 아니라 통신 방식 이름(Medibus)으로 인식한다. 같은 통신 방식을 쓰는 기종들은 회선만 봐서는 구별할 수 없으므로, 근거 없이 모델명을 단정하지 않는 편이 낫다고 판단했다. 기종 이름이 필요하면 설정에서 직접 고르면 된다. 1.19.21 Added: Dräger 장비가 스스로 알려주는 기종명을 로그에 남긴다. 설정 파일의 장비 이름은 사람이 적어 넣은 것이라 실제 기종을 보장하지 않는데, 기종에 따라 낼 수 있는 파형이 다르므로(예: Fabius 는 기도압과 유량은 있고 CO2 가 없다) 무엇이 연결돼 있는지 아는 것이 문제 해결의 출발점이다. 통신이 시작될 때 한 번 남는다. 1.19.20 Fixed: Dräger 마취기 가운데 파형 기능이 없는 기종에서 데이터가 전혀 수집되지 않던 문제. 연결은 된 것처럼 보이지만 아무 값도 들어오지 않는다. VR 이 장비를 확인한 뒤 파형 설정을 물어보고 그 답을 받아야만 데이터 요청을 시작하도록 돼 있었는데, 파형 기능이 없는 기종은 그 질문에 답하지 않으므로 요청이 영영 시작되지 않았다. 연결 유지 신호도 함께 멎어 장비가 통신을 끊고 다시 연결하기를 반복한다. 이제 장비 확인 직후부터 데이터를 요청한다. 파형 응답이 없으면 그 기종의 정상 동작이므로 숫자값만 수집하며 로그에 한 번 알린다. Fabius GS 는 규격상 파형 기능이 없는 대표적인 기종이다 (University of Washington 제보). Added: SyncPulseSerial 동기 펄스 생성기 지원. 여러 장비의 기록을 나중에 맞추기 위한 공통 번호를 시리얼로 보내는 장치로, 1초마다 6자리 카운터를 보낸다. PULSE 트랙에 그 번호가 기록된다. 시작이나 끝을 알리는 문자가 없는 형식이라, 메시지 사이의 침묵을 경계로 삼고 카운터가 1씩 늘어나는지 확인해 맞는 장비인지 판단한다. 자동 판별 대상에도 포함돼 있고, 환자 모니터와 같은 자리에 함께 연결해도 서로 방해하지 않는다. 1.19.19 Added: Dräger 인공호흡기·마취기가 파형에 함께 실어 보내는 호흡 위상 신호를 기록한다. 장비가 흡기와 호기 전환 시점을 직접 알려주는 것이라 파형에서 역산하는 것보다 정확하다. VENT_PHASE 트랙에 흡기 시작 1, 호기 시작 0 으로 남는다. 시각은 파형과 같은 시간축을 쓴다. 종전에는 이 신호를 읽지 않고 버렸다. Added: 환기 모드를 기록한다(VENT_MODE). 모드는 VC-CMV, PC-BIPAP, SPN-CPAP 처럼 압력조절인지 용적조절인지를 알려주어 파형 해석에 직접 쓰인다. 장비가 띄우고 있는 다른 안내 문구도 VENT_MSG 로 남는다. VR 이 그동안 이 정보를 담은 명령을 아예 보내지 않고 있었다 — 이제 측정값·설정과 함께 번갈아 요청한다. 표기가 장비나 언어에 따라 달라도 되도록 문구가 아니라 규격 코드로 판별한다. Added: 파형이 서로 맞는지 확인할 근거를 로그에 남긴다. 용적은 유량을 적분한 것이므로 둘의 관계를 수치로 기록해 둔다. 다만 이것으로 파형 이름을 바꾸거나 경고하지는 않는다 — 용적조절 환기에서는 기도압도 용적에 거의 비례해서 이 수치만으로는 둘을 구별할 수 없기 때문이다. 1.19.18 Added: Y케이블로 엿보는 Dräger 연결에서 파형 종류를 자동으로 알아낸다. Y케이블 배선에 따라 장비가 보내는 것만 보이는 방과, 반대편 장치가 보내는 것까지 보이는 방이 있는데, 후자에서는 그 장치가 파형을 요청하는 내용이 VR 에도 보인다. 거기에 순서가 그대로 들어 있으므로 추정 없이 확정한다 (실제로 수술장 두 방을 재보니 한 방은 양방향, 한 방은 단방향이었다). 단방향인 방은 종전대로 wavs= 로 적어주거나 도착 순서 추정을 쓴다. Fixed: 엿보는 연결인데도 VR 이 자기가 고른 파형 순서를 적용해 이름이 어긋날 수 있던 문제 — 회선에 다른 장치가 있는지를 통신 내용으로 판별해(보낸 적 없는 명령의 응답이 보이면 다른 장치가 있는 것이다) 그런 경우 순서를 스스로 정하지 않는다. 어느 모드로 동작 중인지는 로그에 남는다. 1.19.17 Added: Y케이블로 엿보기만 하는 Dräger 연결에서 파형 이름을 확정할 수 있다. 이런 연결은 VR 이 장비에 아무것도 보낼 수 없어 어떤 파형이 어떤 순서로 오는지 물어볼 수가 없고, 그래서 도착 순서대로 추정해 기록해 왔다(이름이 실제와 다를 수 있었다). 이제 vr.conf 의 장비 항목에 wavs= 로 그 방의 순서를 적어두면 그대로 사용한다. 순서는 그 장비에 연결된 다른 장치가 정한 것이라 병원에서 알 수 있는 값이다. 예: wavs=AWP,AWF. 적지 않으면 종전과 같이 도착 순서로 추정하며, 추정 중이라는 사실과 확정하는 방법을 로그에 남긴다. 직결 연결에서는 종전대로 VR 이 직접 물어보므로 이 설정이 없어도 정확하다. Added: 회선에서 처음 보는 종류의 통신 프레임을 한 번씩 로그에 남긴다. 어떤 장치가 무엇을 주고받는지 현장에서 확인할 수 있어 연결 문제 진단에 쓴다. 1.19.16 Fixed: 1.19.15 에서 Y케이블로 엿보기만 하는 연결(서울대병원 수술장의 Dräger 전부가 이 방식)의 파형이 기록되지 않던 문제 — 1.19.15 는 VR 이 직접 요청한 파형만 기록하도록 바뀌었는데, 관찰 전용 연결에서는 VR 이 장비에 아무것도 보낼 수 없어(송신선이 끊겨 있다) 요청 자체를 못 하므로 들어오는 파형을 전부 버렸다. 이제 이런 연결에서는 1.19.14 이전과 같이 도착 순서대로 기록한다. 다른 장치가 설정해 둔 순서는 알 수 없으므로 이름이 실제와 다를 수 있고, 그 사실을 로그에 함께 남긴다. 1.19.15 를 이미 설치한 곳은 이 버전으로 올려야 한다. 1.19.15 Fixed: Dräger 마취기·인공호흡기(MEDIBUS)에서 파형이 엉뚱한 이름으로 기록되던 문제. 장비가 보내는 파형 묶음의 첫 바이트에는 “몇 번째로 요청한 파형이 들어있는가” 가 비트로 표시되는데, VR 은 그 비트를 세기만 하고 도착 순서대로 미리 정해둔 트랙(CO2, AWP, AWF)에 넣었다. 장비가 요청한 파형 중 일부만 보내면 그만큼 밀려 기록돼, 예를 들어 기도압(AWP)만 보내는 인공호흡기에서 그 파형이 CO2 로 저장됐다 (Evita V600 실측 제보). 이제 비트를 해석해 요청한 순서와 맞춰 넣는다. Fixed: 요청하는 파형이 CO2 와 기도압 두 가지로 고정돼 있어, 장비가 유량(Flow)이나 용적(Volume) 파형을 제공해도 받지 못했다. 이제 장비에게 먼저 제공 가능한 파형 목록을 물어보고 그중에서 요청한다. Added: vr.conf 의 장비 설정에 wavs= 로 받을 파형을 고를 수 있다 (예: wavs=AWP,AWF,VOL,CO2). 비워두면 장비가 제공하는 것을 앞에서부터 최대 4개까지 자동으로 받는다. 4개인 이유는 한 묶음에 최대 4개까지만 표시할 수 있기 때문이다. 지원 이름: AWP AWF PLETH VOL O2 CO2 CO2_KPA CO2_PCT AGENT HAL ENF ISO DES SEV PTRACH AWF_DEV (16진 코드도 가능). 파형을 늘리면 회선 대역을 더 쓰므로 9600bps 의 80% 를 넘으면 로그로 경고한다. 어떤 파형이 실제로 들어오고 있는지도 로그에 남겨 현장에서 확인할 수 있다. 1.19.14 Added: 서버에서 VR 로 내려보내는 명령(원격 시간 동기, 원격 업그레이드, 재시작, 설정 변경 등)을 다시 사용할 수 있다. 1.18.52 에서 이 경로를 통째로 막았는데, 시간 동기와 원격 업그레이드가 꼭 필요한 기관이 있어 되살렸다. 다만 원격으로 프로그램을 바꾸거나 설정을 덮어쓰는 것은 그 자체로 위험하므로 기본값은 여전히 전부 차단이고, vr.conf 에 SERVER_CONTROL 로 허용할 명령을 직접 적어야 동작한다 (예: SERVER_CONTROL=dt 는 시간 동기만, SERVER_CONTROL=dt,update 는 시간 동기와 업그레이드만, SERVER_CONTROL=1 은 전부 허용). 사용 가능한 명령은 update restart reboot edit_conf new_bed del_bed edit_bed dt add_event. 시간 동기는 예전과 달리 녹화 중에는 시계를 건너뛰지 않는다 — 녹화 중 시계를 갑자기 옮기면 파형에 공백이 생기거나 기록이 겹치므로, 녹화 중이면 서서히 당겨 맞추고(슬루) 녹화가 없을 때만 한번에 맞춘다. 2초 이내 차이는 무시한다. 예전 구현이 10분 이상 차이날 때 프로그램을 강제 종료하던 동작도 없앴다. 1.19.13 Fixed: 웹 모니터링 화면에서 EEG 파형이 0.1초 정도씩 끊겨 보이던 문제 — 화면 전송용으로 파형을 줄일 때 원본 주파수가 나누어떨어지지 않으면 일부 구간이 비어 그만큼 빈칸으로 보였다. 나누어떨어지는 비율로만 줄이도록 수정. 녹화 파일에는 원래 문제가 없었다(표시만의 문제). 1.19.12 Fixed: Dräger 마취기 연동 안정화 보완 — 시리얼 회선 자동판별 중 잡음이 잘못된 데이터로 오인될 여지를 차단. 1.19.11 Fixed: Dräger 마취기(Atlan 등 MEDIBUS 장비)에서 통신 오류 알람(COM1 failure)이 반복되고 파형이 전혀 기록되지 않던 문제. 원인 세 가지를 모두 수정했다. (1) 장비는 3초 안에 아무 신호라도 받지 못하면 연결을 끊는데, VR 이 5초마다 데이터를 요청하는 것 외에 보내는 것이 없어 매번 연결이 끊기고 재접속을 반복했다 — 규격이 정한 연결 유지 신호(NOP)를 2초마다 보내도록 했다(데이터 요청 주기는 그대로). (2) 장비가 응답 도중에 다른 명령을 끼워 보내는 것이 규격상 허용되는데, VR 이 그때 받던 응답을 통째로 버려 파형 설정 응답을 매번 놓쳤다 — 끼어든 부분을 건너뛰고 원래 응답을 이어받도록 했다. (3) 장비가 보내는 요청 하나에 VR 이 응답하지 않아 통신이 끊기던 것도 수정. 파형·수치가 정상 기록되고 알람이 사라진다. 1.19.10 Fixed: 하루 한 번의 자동 재시작 시점에 수집이 통째로 멎어 사람이 손을 대기 전까지 복구되지 않던 문제(or-106, 104분 무데이터) — 종료 과정이 끝나지 않아 프로그램이 죽지 않았고, 죽지 않으니 자동 재시작도 일어나지 않았다. 주원인은 시리얼 회선 자동판별(AUTO_DETECT)이 종료 신호를 보지 않고 스윕(보레이트 6종 x 포맷 8종)을 끝까지 돌던 것으로, 무음 회선에서 종료에 2분이 걸려 정상 종료 유예시간을 넘겼다. 이제 종료 즉시 판별을 중단한다(실측 2분 -> 3초). 함께 종료 중 스레드 정리가 서로를 기다릴 수 있던 두 곳을 제거하고, 그럼에도 종료가 20초 안에 끝나지 않으면 강제 종료해 반드시 재시작되도록 안전장치를 넣었다. 녹화 파일은 5초마다 저장되므로 유실되지 않는다. 1.19.9 Fixed: 큰 녹화 파일(약 100MB 이상)을 서버로 올릴 때 프로그램이 메모리 부족으로 강제 종료되고, 재시작 후 같은 파일을 다시 시도하다 무한 반복되던 문제 — 업로드 본문을 메모리에 통째로 만들지 않고 디스크에서 흘려보내도록 변경(100MB 파일 기준 최대 메모리 208MB → 14MB). 큰 파일이 한 개라도 쌓이면 그 장비의 이후 업로드가 전부 막히던 증상이 사라진다. 1.19.8 Fixed: 자동 파일 자르기(CUT_FILE) 를 쓰는 베드에서 환자가 계속 연결돼 있는데도 녹화 파일이 PT_WAITING_TIME(기본 5분) 마다 잘리던 문제 — 웹 모니터링 전송 후 메모리를 비우는 과정에서 환자 연결 판정에 쓰이는 최근 구간까지 지워져 "환자가 나갔다" 로 오판하던 것. 이제 판정에 필요한 구간은 보존한다. 파일이 5분마다 끊기고 그때마다 수 초가 유실되던 증상이 사라진다. 1.19.7 Added: GE Corometrics(분만 태아감시) 모체 vitals 지원 — 기존엔 태아 심박(HR1/HR2)·자궁수축(UACT)만 기록됐으나 이제 같은 장비가 함께 보내는 모체 SpO2/맥박(PR)/NIBP(수축·이완·평균)도 한 파일에 기록. Fixed: 프로세스 종료(서비스 매니저의 정상 종료 요청, SIGTERM) 시 즉시 종료되지 않고 강제 종료되던 문제 — 이제 SIGTERM 을 받아 녹화 버퍼를 flush 하고 스레드를 정리한 뒤 정상 종료. 1.19.5 Fixed: CUT_HOURLY 가 동작하지 않는 것처럼 보이던 문제의 실체 — k3s 컨테이너에서 기본 저장 경로가 $HOME/recordings(임시 레이어)로 잡혀, 매일 자정 재시작 때 업로드 안 된 하루치 녹화가 통째로 사라짐. hostPath 마운트 /recordings 가 있으면 그것을 기본 저장 경로로 사용. Fixed: 종료(daily restart) 시 멀티베드 TCP 노드가 terminate(exit=133)/SIGSEGV(139) 로 죽던 문제. Linux finalize 가 device 스레드를 정지하지 않고 반환해 exit() 전역 소멸자가 살아있는 PortRouter accept_thread 를 파괴하며 std::terminate — DISPLAY::quiesce_threads + PortRouter::shutdown 으로 정리 후 종료 (코어 백트레이스로 확정, 로컬 재현 후 수정 검증). Fixed: Link+/Agilia 파서가 시리얼 노이즈(size byte 0 또는 헤더 없는 잡음 8byte)에 역전된 반복자 범위로 vector 를 생성하다 std::length_error 로 죽던 문제(or-110) — sentinel 상태 파싱 봉쇄 + 재동기화. Fixed: FILENAME_TEMPLATE 의 %Y(4자리 연도)가 "0126" 으로 나오던 문제 (tm_year+1900 누락). 1.19.4 Fixed: Link+ 등 재접속이 잦은 장비에서 시리얼 포트를 재오픈할 때 "terminate called without an active exception" 으로 프로세스가 죽던 문제(exit=133). reading_thread_func 이 스스로 반환해도 std::thread 객체는 joinable 로 남는데, open() 이 join 없이 새 스레드를 대입해 std::terminate 가 호출됨 → 스레드 생성 전에 이전 reading/parsing 스레드를 join 후 재생성. Fixed: headless/AUTO_DETECT 모드에서 실제 장비가 없을 때 Demo 장비가 생성돼 가짜 vital 트랙 38종이 업로드되던 문제 → --console 또는 AUTO_DETECT 시 Demo 미생성. 1.19.3 Fixed: 여러 베드를 한 프로세스가 TCP 로 수집하는 구성에서 클라이언트 재접속이 잦으면 프로세스가 SIGSEGV 로 죽던 문제(accept_handoff 가 replace 시 옛 소켓을 즉시 close 해 recv 중인 reader 와 경합, fd 번호 재사용). tcp reader 를 poll(200ms) 게이트 뒤의 recv 로 재구성하고 replace 는 shutdown 후 소유권을 reader/close 로 단일화. SNUH 회복실 VRN 15대에서 재현. Fixed: ICU 등 Intellivue 다수 베드에서 반복되던 SIGSEGV(139) 두 계열의 use-after-free. (1) DISPLAY::add_rec 이 레코드를 트랙에 push 한 뒤 저장했는데, push 직후 p 가 다른 스레드에 공유되고 Linux 의 메모리 보존 루프(600초 이전 레코드 delete)가 시계 어긋난 모니터에서 방금 넣은 레코드를 즉시 evict 해, 이어지는 p->save() 가 해제된 메모리에 가상 호출 → 저장을 push 앞으로 옮겨 공유 후 p 미접근. (2) DISPLAY::del_trk 이 트랙을 목록에서 뺀 직후 raw delete 해, 파싱 스레드의 캐시 포인터·장비 이름맵·recorded_trks 가 죽은 포인터를 참조 (특히 IAP 채널변경의 del 직후 add 가 해제 포인터 반환) → DEVICE 규칙처럼 살아있는 동안 삭제 금지, ~DISPLAY 에서 일괄 해제. picu-11 코어 덤프로 크래시 지점(add_rec vtable 간접호출) 확정. SNUH fleet ~100 노드 실배포에서 기대 크래시 다수 대비 실제 0, 미배포 노드는 계속 크래시. Fixed: Linux from_utf16 이 locale 의존·비스레드안전 wcstombs 를 쓰고 UTF-16 을 UTF-32 wchar_t 로 오해석하던 문제 → 수동 UTF-8 인코더(서로게이트 페어 재조립, 무효값 U+FFFD)로 교체. Added: AUTO_DETECT=1(vr.conf) 시리얼 장비 자동 판별. 회선 측정(보레이트·프레임폭·패리티) → 후보 필터(3축 일치, 베드당 클래스 배타, 포트 사전확률, 판별캐시) → 트랙 수확량 경쟁. 드라이버 무수정, 미설정 시 유휴 대기라 기존 설정 무영향. SNUH 수술장·ICU 실측 오탐 0.
1.19.27
1.19.27 Changed: 자동 판별이 지난번에 그 자리에 있던 장비를 가장 먼저 시험한다. 종전에는 지난 결과를 후보 순서를 바꾸는 데만 썼고, 그 앞에서 회선 설정(통신 속도·형식) 측정을 먼저 끝냈다. 그 측정이 조용한 회선에서는 48가지 조합을 다 확인하느라 72초를 쓴다. 방의 장비 구성은 거의 바뀌지 않으므로 지난 답보다 강한 단서가 없고, 빗나가더라도 후보 하나를 더 시험하는 값만 든다. Changed: 드물게 말하는 장비를 판별할 수 있게 됐다. 후보 하나를 시험하는 시간이 30초로 고정돼 있었는데, 근이완 감시장치(TwitchView, TOFScan)는 5분에 한 번 측정하므로 30초 안에 아무 말도 하지 않는 것이 정상이다. 그래서 이 장비들은 자동 판별로 영영 잡히지 않았다(한 수술방에서 21일 동안 지난 답을 기억한 채 확인에 계속 실패하고 있었다). 이제 각 장비가 스스로 정한 “이만큼은 조용할 수 있다” 는 값(최대 6분)만큼 듣는다. 겨룰 상대가 없는 단독 시험이므로 자료가 나오면 그 즉시 끝내며, 포트를 동시에 보게 된 1.19.26 덕분에 긴 대기가 다른 포트를 막지 않는다. 1.19.26 Changed: 시리얼 장비 자동 판별이 훨씬 빨리 끝난다. 종전에는 정답을 이미 찾은 뒤에도 남은 후보를 하나당 30초씩 끝까지 시험했다. 환자가 없는 아침에는 모니터가 트랙을 두어 개밖에 채우지 않아 “충분히 찾았다” 는 판정에 도달하지 못했기 때문이다(실측: 정답이 나온 시각은 기동 113초인데 확정은 275초). 이제 네 가지 경우에 그 자리에서 확정한다 — 지난 기동에서 이 포트의 답이었던 장비가 이번에도 자료를 낼 때, 회선 실측으로 후보가 하나만 남았을 때, 트랙이 10개 이상 열렸을 때, 그리고 1위가 2위를 3배 넘게 앞설 때. 확정 근거를 로그에 함께 남긴다. Changed: 여러 시리얼 포트를 동시에 살펴본다. 종전에는 한 포트씩 차례로 봐서 뒤쪽 포트는 앞 포트의 판별이 끝나기를 통째로 기다렸다(실측: 다섯 번째 포트가 기동 590초, 대부분이 대기 시간). 포트는 서로 독립된 장치라 동시에 열어도 간섭하지 않는다. 메모리를 위해 한 번에 3개까지만 본다. 같은 종류의 장비가 두 포트에서 동시에 잡히면 자료를 더 많이 낸 쪽만 남긴다(한 베드에 환자 모니터 하나, 마취기 하나라는 기존 규칙 그대로). Note: 1.19.25 설명 정정 — SyncPulseSerial 이 구형 Arduino UNO 에서 한 값도 기록되지 않던 것을 “프레임이 한 칸씩 밀린다” 고 적었는데 정확하지 않다. 실제로는 모으던 앞부분이 매번 버려져 여섯 자리가 완성되는 순간이 오지 않는 것이다. 또 같은 항목에서 Windows 수신 대기 방식을 원인으로 단정했으나, 가상 직렬 포트로 A/B 시험한 결과 그 경로에서는 재현되지 않았다 — 실제로 문제를 고친 것은 메시지 경계를 시각이 아니라 내용으로 찾도록 바꾼 쪽이다. 1.19.25 Fixed: SyncPulseSerial(동기 펄스 생성기)에서 1초 간격의 펄스가 2초마다 하나씩만 기록되거나, 보드 종류에 따라 아예 한 값도 기록되지 않던 문제. 두 증상 모두 원인이 하나다 — 이 장비는 시작·끝을 알리는 문자 없이 6자리 숫자만 흘려보내므로 VR 이 “잠깐의 침묵” 을 메시지 경계로 삼고 있었는데, 침묵은 회선이 아니라 컴퓨터 사정으로도 생긴다. 운영체제가 6바이트를 몇 번에 나눠 올려주고 그 사이가 벌어지면 앞자리가 버려져 이후 모든 값이 한 칸씩 밀리고, 한 번 밀리면 스스로 회복하지 못해 아무것도 기록되지 않는다(구형 Arduino UNO 에서 재현). 반대로 두 메시지가 한꺼번에 올라오면 둘 다 같은 시각으로 찍혀 1초짜리 펄스가 2초마다 하나 있는 것처럼 보인다(UNO R4 에서 재현). 이제 경계를 시간이 아니라 내용으로 찾는다 — 카운터가 정확히 1씩 늘어나는 자리를 스스로 찾아 맞추므로 회선이 어떻게 쪼개져 들어오든 영향을 받지 않고, 어긋나도 다음 펄스에서 다시 맞춘다. 한 번에 여러 펄스가 올라온 경우에는 카운터 간격만큼 시각을 되돌려 각각 제 시각에 기록한다. 세 가지 조건(정상·쪼개짐·뭉침) 재현 시험으로 확인했다 (Nord University AniLab 제보). Fixed: Windows 에서 데이터를 띄엄띄엄 보내는 시리얼 장비의 값이 최대 한 주기 늦게 들어오던 문제. 문자 도착 알림을 기다리는 동안이 아니라 그 직전 대기 중에 데이터가 통째로 들어와 버리면, 새로 도착하는 문자가 없어 알림이 오지 않고 이미 들어온 데이터가 다음 전송이 올 때까지 그대로 대기했다. 이제 알림이 없어도 수신 버퍼를 직접 확인한다. 초당 수십~수백 바이트를 보내는 대부분의 장비에서는 드러나지 않던 문제이지만, 1초에 한 번처럼 드물게 보내는 장비에서는 두 전송이 한 번에 처리되어 시각이 뭉쳤다. 1.19.24 Fixed: Windows 에서 SyncPulseSerial(Arduino UNO R4)의 데이터가 전혀 들어오지 않거나 한참 뒤에야 간헐적으로 들어오던 문제. Arduino 의 USB 시리얼은 컴퓨터 쪽이 준비 신호(DTR)를 올려야만 데이터를 내보내는데, VR 이 Windows 에서 포트를 열 때 이 신호를 내린 채로 열고 있었다. 시리얼 터미널 프로그램들은 이 신호를 기본으로 올리기 때문에 터미널에서는 잘 보이는데 VR 에서만 안 보이는 형태로 나타났다 (Win11/Win7 3대 실측, Nord University AniLab 제보). 장비 연결 상태는 정상으로 표시되므로 화면만 봐서는 알 수 없는 문제였다. 다른 시리얼 장비들의 동작은 그대로다 — 이 신호에 민감한 장비가 있을 수 있어 Arduino 계열 드라이버에서만 올린다. 1.19.23 Fixed: SyncPulseSerial 의 PULSE 값이 화면에 보이지 않던 문제. 값은 정상적으로 수신되고 파일에도 기록되고 있었는데, 화면 눈금 범위가 카운터의 최대값(864000)으로 잡혀 있어 테스트 초반의 작은 값들이 트랙 맨 바닥 1픽셀에 눌려 보이지 않았다. 이제 매초 가운데에 뚜렷한 선으로 표시된다. 기록된 값 자체는 이전 버전에서도 온전했다(표시만의 문제). 1.19.22 Added: Dräger 마취기를 기종 이름으로 고를 수 있다. 장비 목록에 Fabius 가 추가됐고, 목록에 없는 Dräger 를 쓰는 곳을 위해 Medibus 라는 일반 항목도 함께 넣었다. Primus, Fabius, Medibus 는 통신 방식이 완전히 같고 이름만 다르므로 어느 것을 골라도 동작은 같지만, 실제 기종을 고르면 설정 파일과 화면이 무엇이 연결돼 있는지 스스로 설명하게 된다. 기존에 Primus 로 설정해 둔 곳은 그대로 두면 된다. Changed: 시리얼 장비 자동 판별이 마취기를 특정 기종이 아니라 통신 방식 이름(Medibus)으로 인식한다. 같은 통신 방식을 쓰는 기종들은 회선만 봐서는 구별할 수 없으므로, 근거 없이 모델명을 단정하지 않는 편이 낫다고 판단했다. 기종 이름이 필요하면 설정에서 직접 고르면 된다. 1.19.21 Added: Dräger 장비가 스스로 알려주는 기종명을 로그에 남긴다. 설정 파일의 장비 이름은 사람이 적어 넣은 것이라 실제 기종을 보장하지 않는데, 기종에 따라 낼 수 있는 파형이 다르므로(예: Fabius 는 기도압과 유량은 있고 CO2 가 없다) 무엇이 연결돼 있는지 아는 것이 문제 해결의 출발점이다. 통신이 시작될 때 한 번 남는다. 1.19.20 Fixed: Dräger 마취기 가운데 파형 기능이 없는 기종에서 데이터가 전혀 수집되지 않던 문제. 연결은 된 것처럼 보이지만 아무 값도 들어오지 않는다. VR 이 장비를 확인한 뒤 파형 설정을 물어보고 그 답을 받아야만 데이터 요청을 시작하도록 돼 있었는데, 파형 기능이 없는 기종은 그 질문에 답하지 않으므로 요청이 영영 시작되지 않았다. 연결 유지 신호도 함께 멎어 장비가 통신을 끊고 다시 연결하기를 반복한다. 이제 장비 확인 직후부터 데이터를 요청한다. 파형 응답이 없으면 그 기종의 정상 동작이므로 숫자값만 수집하며 로그에 한 번 알린다. Fabius GS 는 규격상 파형 기능이 없는 대표적인 기종이다 (University of Washington 제보). Added: SyncPulseSerial 동기 펄스 생성기 지원. 여러 장비의 기록을 나중에 맞추기 위한 공통 번호를 시리얼로 보내는 장치로, 1초마다 6자리 카운터를 보낸다. PULSE 트랙에 그 번호가 기록된다. 시작이나 끝을 알리는 문자가 없는 형식이라, 메시지 사이의 침묵을 경계로 삼고 카운터가 1씩 늘어나는지 확인해 맞는 장비인지 판단한다. 자동 판별 대상에도 포함돼 있고, 환자 모니터와 같은 자리에 함께 연결해도 서로 방해하지 않는다. 1.19.19 Added: Dräger 인공호흡기·마취기가 파형에 함께 실어 보내는 호흡 위상 신호를 기록한다. 장비가 흡기와 호기 전환 시점을 직접 알려주는 것이라 파형에서 역산하는 것보다 정확하다. VENT_PHASE 트랙에 흡기 시작 1, 호기 시작 0 으로 남는다. 시각은 파형과 같은 시간축을 쓴다. 종전에는 이 신호를 읽지 않고 버렸다. Added: 환기 모드를 기록한다(VENT_MODE). 모드는 VC-CMV, PC-BIPAP, SPN-CPAP 처럼 압력조절인지 용적조절인지를 알려주어 파형 해석에 직접 쓰인다. 장비가 띄우고 있는 다른 안내 문구도 VENT_MSG 로 남는다. VR 이 그동안 이 정보를 담은 명령을 아예 보내지 않고 있었다 — 이제 측정값·설정과 함께 번갈아 요청한다. 표기가 장비나 언어에 따라 달라도 되도록 문구가 아니라 규격 코드로 판별한다. Added: 파형이 서로 맞는지 확인할 근거를 로그에 남긴다. 용적은 유량을 적분한 것이므로 둘의 관계를 수치로 기록해 둔다. 다만 이것으로 파형 이름을 바꾸거나 경고하지는 않는다 — 용적조절 환기에서는 기도압도 용적에 거의 비례해서 이 수치만으로는 둘을 구별할 수 없기 때문이다. 1.19.18 Added: Y케이블로 엿보는 Dräger 연결에서 파형 종류를 자동으로 알아낸다. Y케이블 배선에 따라 장비가 보내는 것만 보이는 방과, 반대편 장치가 보내는 것까지 보이는 방이 있는데, 후자에서는 그 장치가 파형을 요청하는 내용이 VR 에도 보인다. 거기에 순서가 그대로 들어 있으므로 추정 없이 확정한다 (실제로 수술장 두 방을 재보니 한 방은 양방향, 한 방은 단방향이었다). 단방향인 방은 종전대로 wavs= 로 적어주거나 도착 순서 추정을 쓴다. Fixed: 엿보는 연결인데도 VR 이 자기가 고른 파형 순서를 적용해 이름이 어긋날 수 있던 문제 — 회선에 다른 장치가 있는지를 통신 내용으로 판별해(보낸 적 없는 명령의 응답이 보이면 다른 장치가 있는 것이다) 그런 경우 순서를 스스로 정하지 않는다. 어느 모드로 동작 중인지는 로그에 남는다. 1.19.17 Added: Y케이블로 엿보기만 하는 Dräger 연결에서 파형 이름을 확정할 수 있다. 이런 연결은 VR 이 장비에 아무것도 보낼 수 없어 어떤 파형이 어떤 순서로 오는지 물어볼 수가 없고, 그래서 도착 순서대로 추정해 기록해 왔다(이름이 실제와 다를 수 있었다). 이제 vr.conf 의 장비 항목에 wavs= 로 그 방의 순서를 적어두면 그대로 사용한다. 순서는 그 장비에 연결된 다른 장치가 정한 것이라 병원에서 알 수 있는 값이다. 예: wavs=AWP,AWF. 적지 않으면 종전과 같이 도착 순서로 추정하며, 추정 중이라는 사실과 확정하는 방법을 로그에 남긴다. 직결 연결에서는 종전대로 VR 이 직접 물어보므로 이 설정이 없어도 정확하다. Added: 회선에서 처음 보는 종류의 통신 프레임을 한 번씩 로그에 남긴다. 어떤 장치가 무엇을 주고받는지 현장에서 확인할 수 있어 연결 문제 진단에 쓴다. 1.19.16 Fixed: 1.19.15 에서 Y케이블로 엿보기만 하는 연결(서울대병원 수술장의 Dräger 전부가 이 방식)의 파형이 기록되지 않던 문제 — 1.19.15 는 VR 이 직접 요청한 파형만 기록하도록 바뀌었는데, 관찰 전용 연결에서는 VR 이 장비에 아무것도 보낼 수 없어(송신선이 끊겨 있다) 요청 자체를 못 하므로 들어오는 파형을 전부 버렸다. 이제 이런 연결에서는 1.19.14 이전과 같이 도착 순서대로 기록한다. 다른 장치가 설정해 둔 순서는 알 수 없으므로 이름이 실제와 다를 수 있고, 그 사실을 로그에 함께 남긴다. 1.19.15 를 이미 설치한 곳은 이 버전으로 올려야 한다. 1.19.15 Fixed: Dräger 마취기·인공호흡기(MEDIBUS)에서 파형이 엉뚱한 이름으로 기록되던 문제. 장비가 보내는 파형 묶음의 첫 바이트에는 “몇 번째로 요청한 파형이 들어있는가” 가 비트로 표시되는데, VR 은 그 비트를 세기만 하고 도착 순서대로 미리 정해둔 트랙(CO2, AWP, AWF)에 넣었다. 장비가 요청한 파형 중 일부만 보내면 그만큼 밀려 기록돼, 예를 들어 기도압(AWP)만 보내는 인공호흡기에서 그 파형이 CO2 로 저장됐다 (Evita V600 실측 제보). 이제 비트를 해석해 요청한 순서와 맞춰 넣는다. Fixed: 요청하는 파형이 CO2 와 기도압 두 가지로 고정돼 있어, 장비가 유량(Flow)이나 용적(Volume) 파형을 제공해도 받지 못했다. 이제 장비에게 먼저 제공 가능한 파형 목록을 물어보고 그중에서 요청한다. Added: vr.conf 의 장비 설정에 wavs= 로 받을 파형을 고를 수 있다 (예: wavs=AWP,AWF,VOL,CO2). 비워두면 장비가 제공하는 것을 앞에서부터 최대 4개까지 자동으로 받는다. 4개인 이유는 한 묶음에 최대 4개까지만 표시할 수 있기 때문이다. 지원 이름: AWP AWF PLETH VOL O2 CO2 CO2_KPA CO2_PCT AGENT HAL ENF ISO DES SEV PTRACH AWF_DEV (16진 코드도 가능). 파형을 늘리면 회선 대역을 더 쓰므로 9600bps 의 80% 를 넘으면 로그로 경고한다. 어떤 파형이 실제로 들어오고 있는지도 로그에 남겨 현장에서 확인할 수 있다. 1.19.14 Added: 서버에서 VR 로 내려보내는 명령(원격 시간 동기, 원격 업그레이드, 재시작, 설정 변경 등)을 다시 사용할 수 있다. 1.18.52 에서 이 경로를 통째로 막았는데, 시간 동기와 원격 업그레이드가 꼭 필요한 기관이 있어 되살렸다. 다만 원격으로 프로그램을 바꾸거나 설정을 덮어쓰는 것은 그 자체로 위험하므로 기본값은 여전히 전부 차단이고, vr.conf 에 SERVER_CONTROL 로 허용할 명령을 직접 적어야 동작한다 (예: SERVER_CONTROL=dt 는 시간 동기만, SERVER_CONTROL=dt,update 는 시간 동기와 업그레이드만, SERVER_CONTROL=1 은 전부 허용). 사용 가능한 명령은 update restart reboot edit_conf new_bed del_bed edit_bed dt add_event. 시간 동기는 예전과 달리 녹화 중에는 시계를 건너뛰지 않는다 — 녹화 중 시계를 갑자기 옮기면 파형에 공백이 생기거나 기록이 겹치므로, 녹화 중이면 서서히 당겨 맞추고(슬루) 녹화가 없을 때만 한번에 맞춘다. 2초 이내 차이는 무시한다. 예전 구현이 10분 이상 차이날 때 프로그램을 강제 종료하던 동작도 없앴다. 1.19.13 Fixed: 웹 모니터링 화면에서 EEG 파형이 0.1초 정도씩 끊겨 보이던 문제 — 화면 전송용으로 파형을 줄일 때 원본 주파수가 나누어떨어지지 않으면 일부 구간이 비어 그만큼 빈칸으로 보였다. 나누어떨어지는 비율로만 줄이도록 수정. 녹화 파일에는 원래 문제가 없었다(표시만의 문제). 1.19.12 Fixed: Dräger 마취기 연동 안정화 보완 — 시리얼 회선 자동판별 중 잡음이 잘못된 데이터로 오인될 여지를 차단. 1.19.11 Fixed: Dräger 마취기(Atlan 등 MEDIBUS 장비)에서 통신 오류 알람(COM1 failure)이 반복되고 파형이 전혀 기록되지 않던 문제. 원인 세 가지를 모두 수정했다. (1) 장비는 3초 안에 아무 신호라도 받지 못하면 연결을 끊는데, VR 이 5초마다 데이터를 요청하는 것 외에 보내는 것이 없어 매번 연결이 끊기고 재접속을 반복했다 — 규격이 정한 연결 유지 신호(NOP)를 2초마다 보내도록 했다(데이터 요청 주기는 그대로). (2) 장비가 응답 도중에 다른 명령을 끼워 보내는 것이 규격상 허용되는데, VR 이 그때 받던 응답을 통째로 버려 파형 설정 응답을 매번 놓쳤다 — 끼어든 부분을 건너뛰고 원래 응답을 이어받도록 했다. (3) 장비가 보내는 요청 하나에 VR 이 응답하지 않아 통신이 끊기던 것도 수정. 파형·수치가 정상 기록되고 알람이 사라진다. 1.19.10 Fixed: 하루 한 번의 자동 재시작 시점에 수집이 통째로 멎어 사람이 손을 대기 전까지 복구되지 않던 문제(or-106, 104분 무데이터) — 종료 과정이 끝나지 않아 프로그램이 죽지 않았고, 죽지 않으니 자동 재시작도 일어나지 않았다. 주원인은 시리얼 회선 자동판별(AUTO_DETECT)이 종료 신호를 보지 않고 스윕(보레이트 6종 x 포맷 8종)을 끝까지 돌던 것으로, 무음 회선에서 종료에 2분이 걸려 정상 종료 유예시간을 넘겼다. 이제 종료 즉시 판별을 중단한다(실측 2분 -> 3초). 함께 종료 중 스레드 정리가 서로를 기다릴 수 있던 두 곳을 제거하고, 그럼에도 종료가 20초 안에 끝나지 않으면 강제 종료해 반드시 재시작되도록 안전장치를 넣었다. 녹화 파일은 5초마다 저장되므로 유실되지 않는다. 1.19.9 Fixed: 큰 녹화 파일(약 100MB 이상)을 서버로 올릴 때 프로그램이 메모리 부족으로 강제 종료되고, 재시작 후 같은 파일을 다시 시도하다 무한 반복되던 문제 — 업로드 본문을 메모리에 통째로 만들지 않고 디스크에서 흘려보내도록 변경(100MB 파일 기준 최대 메모리 208MB → 14MB). 큰 파일이 한 개라도 쌓이면 그 장비의 이후 업로드가 전부 막히던 증상이 사라진다. 1.19.8 Fixed: 자동 파일 자르기(CUT_FILE) 를 쓰는 베드에서 환자가 계속 연결돼 있는데도 녹화 파일이 PT_WAITING_TIME(기본 5분) 마다 잘리던 문제 — 웹 모니터링 전송 후 메모리를 비우는 과정에서 환자 연결 판정에 쓰이는 최근 구간까지 지워져 "환자가 나갔다" 로 오판하던 것. 이제 판정에 필요한 구간은 보존한다. 파일이 5분마다 끊기고 그때마다 수 초가 유실되던 증상이 사라진다. 1.19.7 Added: GE Corometrics(분만 태아감시) 모체 vitals 지원 — 기존엔 태아 심박(HR1/HR2)·자궁수축(UACT)만 기록됐으나 이제 같은 장비가 함께 보내는 모체 SpO2/맥박(PR)/NIBP(수축·이완·평균)도 한 파일에 기록. Fixed: 프로세스 종료(서비스 매니저의 정상 종료 요청, SIGTERM) 시 즉시 종료되지 않고 강제 종료되던 문제 — 이제 SIGTERM 을 받아 녹화 버퍼를 flush 하고 스레드를 정리한 뒤 정상 종료. 1.19.5 Fixed: CUT_HOURLY 가 동작하지 않는 것처럼 보이던 문제의 실체 — k3s 컨테이너에서 기본 저장 경로가 $HOME/recordings(임시 레이어)로 잡혀, 매일 자정 재시작 때 업로드 안 된 하루치 녹화가 통째로 사라짐. hostPath 마운트 /recordings 가 있으면 그것을 기본 저장 경로로 사용. Fixed: 종료(daily restart) 시 멀티베드 TCP 노드가 terminate(exit=133)/SIGSEGV(139) 로 죽던 문제. Linux finalize 가 device 스레드를 정지하지 않고 반환해 exit() 전역 소멸자가 살아있는 PortRouter accept_thread 를 파괴하며 std::terminate — DISPLAY::quiesce_threads + PortRouter::shutdown 으로 정리 후 종료 (코어 백트레이스로 확정, 로컬 재현 후 수정 검증). Fixed: Link+/Agilia 파서가 시리얼 노이즈(size byte 0 또는 헤더 없는 잡음 8byte)에 역전된 반복자 범위로 vector 를 생성하다 std::length_error 로 죽던 문제(or-110) — sentinel 상태 파싱 봉쇄 + 재동기화. Fixed: FILENAME_TEMPLATE 의 %Y(4자리 연도)가 "0126" 으로 나오던 문제 (tm_year+1900 누락). 1.19.4 Fixed: Link+ 등 재접속이 잦은 장비에서 시리얼 포트를 재오픈할 때 "terminate called without an active exception" 으로 프로세스가 죽던 문제(exit=133). reading_thread_func 이 스스로 반환해도 std::thread 객체는 joinable 로 남는데, open() 이 join 없이 새 스레드를 대입해 std::terminate 가 호출됨 → 스레드 생성 전에 이전 reading/parsing 스레드를 join 후 재생성. Fixed: headless/AUTO_DETECT 모드에서 실제 장비가 없을 때 Demo 장비가 생성돼 가짜 vital 트랙 38종이 업로드되던 문제 → --console 또는 AUTO_DETECT 시 Demo 미생성. 1.19.3 Fixed: 여러 베드를 한 프로세스가 TCP 로 수집하는 구성에서 클라이언트 재접속이 잦으면 프로세스가 SIGSEGV 로 죽던 문제(accept_handoff 가 replace 시 옛 소켓을 즉시 close 해 recv 중인 reader 와 경합, fd 번호 재사용). tcp reader 를 poll(200ms) 게이트 뒤의 recv 로 재구성하고 replace 는 shutdown 후 소유권을 reader/close 로 단일화. SNUH 회복실 VRN 15대에서 재현. Fixed: ICU 등 Intellivue 다수 베드에서 반복되던 SIGSEGV(139) 두 계열의 use-after-free. (1) DISPLAY::add_rec 이 레코드를 트랙에 push 한 뒤 저장했는데, push 직후 p 가 다른 스레드에 공유되고 Linux 의 메모리 보존 루프(600초 이전 레코드 delete)가 시계 어긋난 모니터에서 방금 넣은 레코드를 즉시 evict 해, 이어지는 p->save() 가 해제된 메모리에 가상 호출 → 저장을 push 앞으로 옮겨 공유 후 p 미접근. (2) DISPLAY::del_trk 이 트랙을 목록에서 뺀 직후 raw delete 해, 파싱 스레드의 캐시 포인터·장비 이름맵·recorded_trks 가 죽은 포인터를 참조 (특히 IAP 채널변경의 del 직후 add 가 해제 포인터 반환) → DEVICE 규칙처럼 살아있는 동안 삭제 금지, ~DISPLAY 에서 일괄 해제. picu-11 코어 덤프로 크래시 지점(add_rec vtable 간접호출) 확정. SNUH fleet ~100 노드 실배포에서 기대 크래시 다수 대비 실제 0, 미배포 노드는 계속 크래시. Fixed: Linux from_utf16 이 locale 의존·비스레드안전 wcstombs 를 쓰고 UTF-16 을 UTF-32 wchar_t 로 오해석하던 문제 → 수동 UTF-8 인코더(서로게이트 페어 재조립, 무효값 U+FFFD)로 교체. Added: AUTO_DETECT=1(vr.conf) 시리얼 장비 자동 판별. 회선 측정(보레이트·프레임폭·패리티) → 후보 필터(3축 일치, 베드당 클래스 배타, 포트 사전확률, 판별캐시) → 트랙 수확량 경쟁. 드라이버 무수정, 미설정 시 유휴 대기라 기존 설정 무영향. SNUH 수술장·ICU 실측 오탐 0.
1.19.26
1.19.26 Changed: 시리얼 장비 자동 판별이 훨씬 빨리 끝난다. 종전에는 정답을 이미 찾은 뒤에도 남은 후보를 하나당 30초씩 끝까지 시험했다. 환자가 없는 아침에는 모니터가 트랙을 두어 개밖에 채우지 않아 “충분히 찾았다” 는 판정에 도달하지 못했기 때문이다(실측: 정답이 나온 시각은 기동 113초인데 확정은 275초). 이제 네 가지 경우에 그 자리에서 확정한다 — 지난 기동에서 이 포트의 답이었던 장비가 이번에도 자료를 낼 때, 회선 실측으로 후보가 하나만 남았을 때, 트랙이 10개 이상 열렸을 때, 그리고 1위가 2위를 3배 넘게 앞설 때. 확정 근거를 로그에 함께 남긴다. Changed: 여러 시리얼 포트를 동시에 살펴본다. 종전에는 한 포트씩 차례로 봐서 뒤쪽 포트는 앞 포트의 판별이 끝나기를 통째로 기다렸다(실측: 다섯 번째 포트가 기동 590초, 대부분이 대기 시간). 포트는 서로 독립된 장치라 동시에 열어도 간섭하지 않는다. 메모리를 위해 한 번에 3개까지만 본다. 같은 종류의 장비가 두 포트에서 동시에 잡히면 자료를 더 많이 낸 쪽만 남긴다(한 베드에 환자 모니터 하나, 마취기 하나라는 기존 규칙 그대로). Note: 1.19.25 설명 정정 — SyncPulseSerial 이 구형 Arduino UNO 에서 한 값도 기록되지 않던 것을 “프레임이 한 칸씩 밀린다” 고 적었는데 정확하지 않다. 실제로는 모으던 앞부분이 매번 버려져 여섯 자리가 완성되는 순간이 오지 않는 것이다. 또 같은 항목에서 Windows 수신 대기 방식을 원인으로 단정했으나, 가상 직렬 포트로 A/B 시험한 결과 그 경로에서는 재현되지 않았다 — 실제로 문제를 고친 것은 메시지 경계를 시각이 아니라 내용으로 찾도록 바꾼 쪽이다. 1.19.25 Fixed: SyncPulseSerial(동기 펄스 생성기)에서 1초 간격의 펄스가 2초마다 하나씩만 기록되거나, 보드 종류에 따라 아예 한 값도 기록되지 않던 문제. 두 증상 모두 원인이 하나다 — 이 장비는 시작·끝을 알리는 문자 없이 6자리 숫자만 흘려보내므로 VR 이 “잠깐의 침묵” 을 메시지 경계로 삼고 있었는데, 침묵은 회선이 아니라 컴퓨터 사정으로도 생긴다. 운영체제가 6바이트를 몇 번에 나눠 올려주고 그 사이가 벌어지면 앞자리가 버려져 이후 모든 값이 한 칸씩 밀리고, 한 번 밀리면 스스로 회복하지 못해 아무것도 기록되지 않는다(구형 Arduino UNO 에서 재현). 반대로 두 메시지가 한꺼번에 올라오면 둘 다 같은 시각으로 찍혀 1초짜리 펄스가 2초마다 하나 있는 것처럼 보인다(UNO R4 에서 재현). 이제 경계를 시간이 아니라 내용으로 찾는다 — 카운터가 정확히 1씩 늘어나는 자리를 스스로 찾아 맞추므로 회선이 어떻게 쪼개져 들어오든 영향을 받지 않고, 어긋나도 다음 펄스에서 다시 맞춘다. 한 번에 여러 펄스가 올라온 경우에는 카운터 간격만큼 시각을 되돌려 각각 제 시각에 기록한다. 세 가지 조건(정상·쪼개짐·뭉침) 재현 시험으로 확인했다 (Nord University AniLab 제보). Fixed: Windows 에서 데이터를 띄엄띄엄 보내는 시리얼 장비의 값이 최대 한 주기 늦게 들어오던 문제. 문자 도착 알림을 기다리는 동안이 아니라 그 직전 대기 중에 데이터가 통째로 들어와 버리면, 새로 도착하는 문자가 없어 알림이 오지 않고 이미 들어온 데이터가 다음 전송이 올 때까지 그대로 대기했다. 이제 알림이 없어도 수신 버퍼를 직접 확인한다. 초당 수십~수백 바이트를 보내는 대부분의 장비에서는 드러나지 않던 문제이지만, 1초에 한 번처럼 드물게 보내는 장비에서는 두 전송이 한 번에 처리되어 시각이 뭉쳤다. 1.19.24 Fixed: Windows 에서 SyncPulseSerial(Arduino UNO R4)의 데이터가 전혀 들어오지 않거나 한참 뒤에야 간헐적으로 들어오던 문제. Arduino 의 USB 시리얼은 컴퓨터 쪽이 준비 신호(DTR)를 올려야만 데이터를 내보내는데, VR 이 Windows 에서 포트를 열 때 이 신호를 내린 채로 열고 있었다. 시리얼 터미널 프로그램들은 이 신호를 기본으로 올리기 때문에 터미널에서는 잘 보이는데 VR 에서만 안 보이는 형태로 나타났다 (Win11/Win7 3대 실측, Nord University AniLab 제보). 장비 연결 상태는 정상으로 표시되므로 화면만 봐서는 알 수 없는 문제였다. 다른 시리얼 장비들의 동작은 그대로다 — 이 신호에 민감한 장비가 있을 수 있어 Arduino 계열 드라이버에서만 올린다. 1.19.23 Fixed: SyncPulseSerial 의 PULSE 값이 화면에 보이지 않던 문제. 값은 정상적으로 수신되고 파일에도 기록되고 있었는데, 화면 눈금 범위가 카운터의 최대값(864000)으로 잡혀 있어 테스트 초반의 작은 값들이 트랙 맨 바닥 1픽셀에 눌려 보이지 않았다. 이제 매초 가운데에 뚜렷한 선으로 표시된다. 기록된 값 자체는 이전 버전에서도 온전했다(표시만의 문제). 1.19.22 Added: Dräger 마취기를 기종 이름으로 고를 수 있다. 장비 목록에 Fabius 가 추가됐고, 목록에 없는 Dräger 를 쓰는 곳을 위해 Medibus 라는 일반 항목도 함께 넣었다. Primus, Fabius, Medibus 는 통신 방식이 완전히 같고 이름만 다르므로 어느 것을 골라도 동작은 같지만, 실제 기종을 고르면 설정 파일과 화면이 무엇이 연결돼 있는지 스스로 설명하게 된다. 기존에 Primus 로 설정해 둔 곳은 그대로 두면 된다. Changed: 시리얼 장비 자동 판별이 마취기를 특정 기종이 아니라 통신 방식 이름(Medibus)으로 인식한다. 같은 통신 방식을 쓰는 기종들은 회선만 봐서는 구별할 수 없으므로, 근거 없이 모델명을 단정하지 않는 편이 낫다고 판단했다. 기종 이름이 필요하면 설정에서 직접 고르면 된다. 1.19.21 Added: Dräger 장비가 스스로 알려주는 기종명을 로그에 남긴다. 설정 파일의 장비 이름은 사람이 적어 넣은 것이라 실제 기종을 보장하지 않는데, 기종에 따라 낼 수 있는 파형이 다르므로(예: Fabius 는 기도압과 유량은 있고 CO2 가 없다) 무엇이 연결돼 있는지 아는 것이 문제 해결의 출발점이다. 통신이 시작될 때 한 번 남는다. 1.19.20 Fixed: Dräger 마취기 가운데 파형 기능이 없는 기종에서 데이터가 전혀 수집되지 않던 문제. 연결은 된 것처럼 보이지만 아무 값도 들어오지 않는다. VR 이 장비를 확인한 뒤 파형 설정을 물어보고 그 답을 받아야만 데이터 요청을 시작하도록 돼 있었는데, 파형 기능이 없는 기종은 그 질문에 답하지 않으므로 요청이 영영 시작되지 않았다. 연결 유지 신호도 함께 멎어 장비가 통신을 끊고 다시 연결하기를 반복한다. 이제 장비 확인 직후부터 데이터를 요청한다. 파형 응답이 없으면 그 기종의 정상 동작이므로 숫자값만 수집하며 로그에 한 번 알린다. Fabius GS 는 규격상 파형 기능이 없는 대표적인 기종이다 (University of Washington 제보). Added: SyncPulseSerial 동기 펄스 생성기 지원. 여러 장비의 기록을 나중에 맞추기 위한 공통 번호를 시리얼로 보내는 장치로, 1초마다 6자리 카운터를 보낸다. PULSE 트랙에 그 번호가 기록된다. 시작이나 끝을 알리는 문자가 없는 형식이라, 메시지 사이의 침묵을 경계로 삼고 카운터가 1씩 늘어나는지 확인해 맞는 장비인지 판단한다. 자동 판별 대상에도 포함돼 있고, 환자 모니터와 같은 자리에 함께 연결해도 서로 방해하지 않는다. 1.19.19 Added: Dräger 인공호흡기·마취기가 파형에 함께 실어 보내는 호흡 위상 신호를 기록한다. 장비가 흡기와 호기 전환 시점을 직접 알려주는 것이라 파형에서 역산하는 것보다 정확하다. VENT_PHASE 트랙에 흡기 시작 1, 호기 시작 0 으로 남는다. 시각은 파형과 같은 시간축을 쓴다. 종전에는 이 신호를 읽지 않고 버렸다. Added: 환기 모드를 기록한다(VENT_MODE). 모드는 VC-CMV, PC-BIPAP, SPN-CPAP 처럼 압력조절인지 용적조절인지를 알려주어 파형 해석에 직접 쓰인다. 장비가 띄우고 있는 다른 안내 문구도 VENT_MSG 로 남는다. VR 이 그동안 이 정보를 담은 명령을 아예 보내지 않고 있었다 — 이제 측정값·설정과 함께 번갈아 요청한다. 표기가 장비나 언어에 따라 달라도 되도록 문구가 아니라 규격 코드로 판별한다. Added: 파형이 서로 맞는지 확인할 근거를 로그에 남긴다. 용적은 유량을 적분한 것이므로 둘의 관계를 수치로 기록해 둔다. 다만 이것으로 파형 이름을 바꾸거나 경고하지는 않는다 — 용적조절 환기에서는 기도압도 용적에 거의 비례해서 이 수치만으로는 둘을 구별할 수 없기 때문이다. 1.19.18 Added: Y케이블로 엿보는 Dräger 연결에서 파형 종류를 자동으로 알아낸다. Y케이블 배선에 따라 장비가 보내는 것만 보이는 방과, 반대편 장치가 보내는 것까지 보이는 방이 있는데, 후자에서는 그 장치가 파형을 요청하는 내용이 VR 에도 보인다. 거기에 순서가 그대로 들어 있으므로 추정 없이 확정한다 (실제로 수술장 두 방을 재보니 한 방은 양방향, 한 방은 단방향이었다). 단방향인 방은 종전대로 wavs= 로 적어주거나 도착 순서 추정을 쓴다. Fixed: 엿보는 연결인데도 VR 이 자기가 고른 파형 순서를 적용해 이름이 어긋날 수 있던 문제 — 회선에 다른 장치가 있는지를 통신 내용으로 판별해(보낸 적 없는 명령의 응답이 보이면 다른 장치가 있는 것이다) 그런 경우 순서를 스스로 정하지 않는다. 어느 모드로 동작 중인지는 로그에 남는다. 1.19.17 Added: Y케이블로 엿보기만 하는 Dräger 연결에서 파형 이름을 확정할 수 있다. 이런 연결은 VR 이 장비에 아무것도 보낼 수 없어 어떤 파형이 어떤 순서로 오는지 물어볼 수가 없고, 그래서 도착 순서대로 추정해 기록해 왔다(이름이 실제와 다를 수 있었다). 이제 vr.conf 의 장비 항목에 wavs= 로 그 방의 순서를 적어두면 그대로 사용한다. 순서는 그 장비에 연결된 다른 장치가 정한 것이라 병원에서 알 수 있는 값이다. 예: wavs=AWP,AWF. 적지 않으면 종전과 같이 도착 순서로 추정하며, 추정 중이라는 사실과 확정하는 방법을 로그에 남긴다. 직결 연결에서는 종전대로 VR 이 직접 물어보므로 이 설정이 없어도 정확하다. Added: 회선에서 처음 보는 종류의 통신 프레임을 한 번씩 로그에 남긴다. 어떤 장치가 무엇을 주고받는지 현장에서 확인할 수 있어 연결 문제 진단에 쓴다. 1.19.16 Fixed: 1.19.15 에서 Y케이블로 엿보기만 하는 연결(서울대병원 수술장의 Dräger 전부가 이 방식)의 파형이 기록되지 않던 문제 — 1.19.15 는 VR 이 직접 요청한 파형만 기록하도록 바뀌었는데, 관찰 전용 연결에서는 VR 이 장비에 아무것도 보낼 수 없어(송신선이 끊겨 있다) 요청 자체를 못 하므로 들어오는 파형을 전부 버렸다. 이제 이런 연결에서는 1.19.14 이전과 같이 도착 순서대로 기록한다. 다른 장치가 설정해 둔 순서는 알 수 없으므로 이름이 실제와 다를 수 있고, 그 사실을 로그에 함께 남긴다. 1.19.15 를 이미 설치한 곳은 이 버전으로 올려야 한다. 1.19.15 Fixed: Dräger 마취기·인공호흡기(MEDIBUS)에서 파형이 엉뚱한 이름으로 기록되던 문제. 장비가 보내는 파형 묶음의 첫 바이트에는 “몇 번째로 요청한 파형이 들어있는가” 가 비트로 표시되는데, VR 은 그 비트를 세기만 하고 도착 순서대로 미리 정해둔 트랙(CO2, AWP, AWF)에 넣었다. 장비가 요청한 파형 중 일부만 보내면 그만큼 밀려 기록돼, 예를 들어 기도압(AWP)만 보내는 인공호흡기에서 그 파형이 CO2 로 저장됐다 (Evita V600 실측 제보). 이제 비트를 해석해 요청한 순서와 맞춰 넣는다. Fixed: 요청하는 파형이 CO2 와 기도압 두 가지로 고정돼 있어, 장비가 유량(Flow)이나 용적(Volume) 파형을 제공해도 받지 못했다. 이제 장비에게 먼저 제공 가능한 파형 목록을 물어보고 그중에서 요청한다. Added: vr.conf 의 장비 설정에 wavs= 로 받을 파형을 고를 수 있다 (예: wavs=AWP,AWF,VOL,CO2). 비워두면 장비가 제공하는 것을 앞에서부터 최대 4개까지 자동으로 받는다. 4개인 이유는 한 묶음에 최대 4개까지만 표시할 수 있기 때문이다. 지원 이름: AWP AWF PLETH VOL O2 CO2 CO2_KPA CO2_PCT AGENT HAL ENF ISO DES SEV PTRACH AWF_DEV (16진 코드도 가능). 파형을 늘리면 회선 대역을 더 쓰므로 9600bps 의 80% 를 넘으면 로그로 경고한다. 어떤 파형이 실제로 들어오고 있는지도 로그에 남겨 현장에서 확인할 수 있다. 1.19.14 Added: 서버에서 VR 로 내려보내는 명령(원격 시간 동기, 원격 업그레이드, 재시작, 설정 변경 등)을 다시 사용할 수 있다. 1.18.52 에서 이 경로를 통째로 막았는데, 시간 동기와 원격 업그레이드가 꼭 필요한 기관이 있어 되살렸다. 다만 원격으로 프로그램을 바꾸거나 설정을 덮어쓰는 것은 그 자체로 위험하므로 기본값은 여전히 전부 차단이고, vr.conf 에 SERVER_CONTROL 로 허용할 명령을 직접 적어야 동작한다 (예: SERVER_CONTROL=dt 는 시간 동기만, SERVER_CONTROL=dt,update 는 시간 동기와 업그레이드만, SERVER_CONTROL=1 은 전부 허용). 사용 가능한 명령은 update restart reboot edit_conf new_bed del_bed edit_bed dt add_event. 시간 동기는 예전과 달리 녹화 중에는 시계를 건너뛰지 않는다 — 녹화 중 시계를 갑자기 옮기면 파형에 공백이 생기거나 기록이 겹치므로, 녹화 중이면 서서히 당겨 맞추고(슬루) 녹화가 없을 때만 한번에 맞춘다. 2초 이내 차이는 무시한다. 예전 구현이 10분 이상 차이날 때 프로그램을 강제 종료하던 동작도 없앴다. 1.19.13 Fixed: 웹 모니터링 화면에서 EEG 파형이 0.1초 정도씩 끊겨 보이던 문제 — 화면 전송용으로 파형을 줄일 때 원본 주파수가 나누어떨어지지 않으면 일부 구간이 비어 그만큼 빈칸으로 보였다. 나누어떨어지는 비율로만 줄이도록 수정. 녹화 파일에는 원래 문제가 없었다(표시만의 문제). 1.19.12 Fixed: Dräger 마취기 연동 안정화 보완 — 시리얼 회선 자동판별 중 잡음이 잘못된 데이터로 오인될 여지를 차단. 1.19.11 Fixed: Dräger 마취기(Atlan 등 MEDIBUS 장비)에서 통신 오류 알람(COM1 failure)이 반복되고 파형이 전혀 기록되지 않던 문제. 원인 세 가지를 모두 수정했다. (1) 장비는 3초 안에 아무 신호라도 받지 못하면 연결을 끊는데, VR 이 5초마다 데이터를 요청하는 것 외에 보내는 것이 없어 매번 연결이 끊기고 재접속을 반복했다 — 규격이 정한 연결 유지 신호(NOP)를 2초마다 보내도록 했다(데이터 요청 주기는 그대로). (2) 장비가 응답 도중에 다른 명령을 끼워 보내는 것이 규격상 허용되는데, VR 이 그때 받던 응답을 통째로 버려 파형 설정 응답을 매번 놓쳤다 — 끼어든 부분을 건너뛰고 원래 응답을 이어받도록 했다. (3) 장비가 보내는 요청 하나에 VR 이 응답하지 않아 통신이 끊기던 것도 수정. 파형·수치가 정상 기록되고 알람이 사라진다. 1.19.10 Fixed: 하루 한 번의 자동 재시작 시점에 수집이 통째로 멎어 사람이 손을 대기 전까지 복구되지 않던 문제(or-106, 104분 무데이터) — 종료 과정이 끝나지 않아 프로그램이 죽지 않았고, 죽지 않으니 자동 재시작도 일어나지 않았다. 주원인은 시리얼 회선 자동판별(AUTO_DETECT)이 종료 신호를 보지 않고 스윕(보레이트 6종 x 포맷 8종)을 끝까지 돌던 것으로, 무음 회선에서 종료에 2분이 걸려 정상 종료 유예시간을 넘겼다. 이제 종료 즉시 판별을 중단한다(실측 2분 -> 3초). 함께 종료 중 스레드 정리가 서로를 기다릴 수 있던 두 곳을 제거하고, 그럼에도 종료가 20초 안에 끝나지 않으면 강제 종료해 반드시 재시작되도록 안전장치를 넣었다. 녹화 파일은 5초마다 저장되므로 유실되지 않는다. 1.19.9 Fixed: 큰 녹화 파일(약 100MB 이상)을 서버로 올릴 때 프로그램이 메모리 부족으로 강제 종료되고, 재시작 후 같은 파일을 다시 시도하다 무한 반복되던 문제 — 업로드 본문을 메모리에 통째로 만들지 않고 디스크에서 흘려보내도록 변경(100MB 파일 기준 최대 메모리 208MB → 14MB). 큰 파일이 한 개라도 쌓이면 그 장비의 이후 업로드가 전부 막히던 증상이 사라진다. 1.19.8 Fixed: 자동 파일 자르기(CUT_FILE) 를 쓰는 베드에서 환자가 계속 연결돼 있는데도 녹화 파일이 PT_WAITING_TIME(기본 5분) 마다 잘리던 문제 — 웹 모니터링 전송 후 메모리를 비우는 과정에서 환자 연결 판정에 쓰이는 최근 구간까지 지워져 "환자가 나갔다" 로 오판하던 것. 이제 판정에 필요한 구간은 보존한다. 파일이 5분마다 끊기고 그때마다 수 초가 유실되던 증상이 사라진다. 1.19.7 Added: GE Corometrics(분만 태아감시) 모체 vitals 지원 — 기존엔 태아 심박(HR1/HR2)·자궁수축(UACT)만 기록됐으나 이제 같은 장비가 함께 보내는 모체 SpO2/맥박(PR)/NIBP(수축·이완·평균)도 한 파일에 기록. Fixed: 프로세스 종료(서비스 매니저의 정상 종료 요청, SIGTERM) 시 즉시 종료되지 않고 강제 종료되던 문제 — 이제 SIGTERM 을 받아 녹화 버퍼를 flush 하고 스레드를 정리한 뒤 정상 종료. 1.19.5 Fixed: CUT_HOURLY 가 동작하지 않는 것처럼 보이던 문제의 실체 — k3s 컨테이너에서 기본 저장 경로가 $HOME/recordings(임시 레이어)로 잡혀, 매일 자정 재시작 때 업로드 안 된 하루치 녹화가 통째로 사라짐. hostPath 마운트 /recordings 가 있으면 그것을 기본 저장 경로로 사용. Fixed: 종료(daily restart) 시 멀티베드 TCP 노드가 terminate(exit=133)/SIGSEGV(139) 로 죽던 문제. Linux finalize 가 device 스레드를 정지하지 않고 반환해 exit() 전역 소멸자가 살아있는 PortRouter accept_thread 를 파괴하며 std::terminate — DISPLAY::quiesce_threads + PortRouter::shutdown 으로 정리 후 종료 (코어 백트레이스로 확정, 로컬 재현 후 수정 검증). Fixed: Link+/Agilia 파서가 시리얼 노이즈(size byte 0 또는 헤더 없는 잡음 8byte)에 역전된 반복자 범위로 vector 를 생성하다 std::length_error 로 죽던 문제(or-110) — sentinel 상태 파싱 봉쇄 + 재동기화. Fixed: FILENAME_TEMPLATE 의 %Y(4자리 연도)가 "0126" 으로 나오던 문제 (tm_year+1900 누락). 1.19.4 Fixed: Link+ 등 재접속이 잦은 장비에서 시리얼 포트를 재오픈할 때 "terminate called without an active exception" 으로 프로세스가 죽던 문제(exit=133). reading_thread_func 이 스스로 반환해도 std::thread 객체는 joinable 로 남는데, open() 이 join 없이 새 스레드를 대입해 std::terminate 가 호출됨 → 스레드 생성 전에 이전 reading/parsing 스레드를 join 후 재생성. Fixed: headless/AUTO_DETECT 모드에서 실제 장비가 없을 때 Demo 장비가 생성돼 가짜 vital 트랙 38종이 업로드되던 문제 → --console 또는 AUTO_DETECT 시 Demo 미생성. 1.19.3 Fixed: 여러 베드를 한 프로세스가 TCP 로 수집하는 구성에서 클라이언트 재접속이 잦으면 프로세스가 SIGSEGV 로 죽던 문제(accept_handoff 가 replace 시 옛 소켓을 즉시 close 해 recv 중인 reader 와 경합, fd 번호 재사용). tcp reader 를 poll(200ms) 게이트 뒤의 recv 로 재구성하고 replace 는 shutdown 후 소유권을 reader/close 로 단일화. SNUH 회복실 VRN 15대에서 재현. Fixed: ICU 등 Intellivue 다수 베드에서 반복되던 SIGSEGV(139) 두 계열의 use-after-free. (1) DISPLAY::add_rec 이 레코드를 트랙에 push 한 뒤 저장했는데, push 직후 p 가 다른 스레드에 공유되고 Linux 의 메모리 보존 루프(600초 이전 레코드 delete)가 시계 어긋난 모니터에서 방금 넣은 레코드를 즉시 evict 해, 이어지는 p->save() 가 해제된 메모리에 가상 호출 → 저장을 push 앞으로 옮겨 공유 후 p 미접근. (2) DISPLAY::del_trk 이 트랙을 목록에서 뺀 직후 raw delete 해, 파싱 스레드의 캐시 포인터·장비 이름맵·recorded_trks 가 죽은 포인터를 참조 (특히 IAP 채널변경의 del 직후 add 가 해제 포인터 반환) → DEVICE 규칙처럼 살아있는 동안 삭제 금지, ~DISPLAY 에서 일괄 해제. picu-11 코어 덤프로 크래시 지점(add_rec vtable 간접호출) 확정. SNUH fleet ~100 노드 실배포에서 기대 크래시 다수 대비 실제 0, 미배포 노드는 계속 크래시. Fixed: Linux from_utf16 이 locale 의존·비스레드안전 wcstombs 를 쓰고 UTF-16 을 UTF-32 wchar_t 로 오해석하던 문제 → 수동 UTF-8 인코더(서로게이트 페어 재조립, 무효값 U+FFFD)로 교체. Added: AUTO_DETECT=1(vr.conf) 시리얼 장비 자동 판별. 회선 측정(보레이트·프레임폭·패리티) → 후보 필터(3축 일치, 베드당 클래스 배타, 포트 사전확률, 판별캐시) → 트랙 수확량 경쟁. 드라이버 무수정, 미설정 시 유휴 대기라 기존 설정 무영향. SNUH 수술장·ICU 실측 오탐 0.
1.19.25
1.19.25 Fixed: SyncPulseSerial(동기 펄스 생성기)에서 1초 간격의 펄스가 2초마다 하나씩만 기록되거나, 보드 종류에 따라 아예 한 값도 기록되지 않던 문제. 두 증상 모두 원인이 하나다 — 이 장비는 시작·끝을 알리는 문자 없이 6자리 숫자만 흘려보내므로 VR 이 “잠깐의 침묵” 을 메시지 경계로 삼고 있었는데, 침묵은 회선이 아니라 컴퓨터 사정으로도 생긴다. 운영체제가 6바이트를 몇 번에 나눠 올려주고 그 사이가 벌어지면 앞자리가 버려져 이후 모든 값이 한 칸씩 밀리고, 한 번 밀리면 스스로 회복하지 못해 아무것도 기록되지 않는다(구형 Arduino UNO 에서 재현). 반대로 두 메시지가 한꺼번에 올라오면 둘 다 같은 시각으로 찍혀 1초짜리 펄스가 2초마다 하나 있는 것처럼 보인다(UNO R4 에서 재현). 이제 경계를 시간이 아니라 내용으로 찾는다 — 카운터가 정확히 1씩 늘어나는 자리를 스스로 찾아 맞추므로 회선이 어떻게 쪼개져 들어오든 영향을 받지 않고, 어긋나도 다음 펄스에서 다시 맞춘다. 한 번에 여러 펄스가 올라온 경우에는 카운터 간격만큼 시각을 되돌려 각각 제 시각에 기록한다. 세 가지 조건(정상·쪼개짐·뭉침) 재현 시험으로 확인했다 (Nord University AniLab 제보). Fixed: Windows 에서 데이터를 띄엄띄엄 보내는 시리얼 장비의 값이 최대 한 주기 늦게 들어오던 문제. 문자 도착 알림을 기다리는 동안이 아니라 그 직전 대기 중에 데이터가 통째로 들어와 버리면, 새로 도착하는 문자가 없어 알림이 오지 않고 이미 들어온 데이터가 다음 전송이 올 때까지 그대로 대기했다. 이제 알림이 없어도 수신 버퍼를 직접 확인한다. 초당 수십~수백 바이트를 보내는 대부분의 장비에서는 드러나지 않던 문제이지만, 1초에 한 번처럼 드물게 보내는 장비에서는 두 전송이 한 번에 처리되어 시각이 뭉쳤다. 1.19.24 Fixed: Windows 에서 SyncPulseSerial(Arduino UNO R4)의 데이터가 전혀 들어오지 않거나 한참 뒤에야 간헐적으로 들어오던 문제. Arduino 의 USB 시리얼은 컴퓨터 쪽이 준비 신호(DTR)를 올려야만 데이터를 내보내는데, VR 이 Windows 에서 포트를 열 때 이 신호를 내린 채로 열고 있었다. 시리얼 터미널 프로그램들은 이 신호를 기본으로 올리기 때문에 터미널에서는 잘 보이는데 VR 에서만 안 보이는 형태로 나타났다 (Win11/Win7 3대 실측, Nord University AniLab 제보). 장비 연결 상태는 정상으로 표시되므로 화면만 봐서는 알 수 없는 문제였다. 다른 시리얼 장비들의 동작은 그대로다 — 이 신호에 민감한 장비가 있을 수 있어 Arduino 계열 드라이버에서만 올린다. 1.19.23 Fixed: SyncPulseSerial 의 PULSE 값이 화면에 보이지 않던 문제. 값은 정상적으로 수신되고 파일에도 기록되고 있었는데, 화면 눈금 범위가 카운터의 최대값(864000)으로 잡혀 있어 테스트 초반의 작은 값들이 트랙 맨 바닥 1픽셀에 눌려 보이지 않았다. 이제 매초 가운데에 뚜렷한 선으로 표시된다. 기록된 값 자체는 이전 버전에서도 온전했다(표시만의 문제). 1.19.22 Added: Dräger 마취기를 기종 이름으로 고를 수 있다. 장비 목록에 Fabius 가 추가됐고, 목록에 없는 Dräger 를 쓰는 곳을 위해 Medibus 라는 일반 항목도 함께 넣었다. Primus, Fabius, Medibus 는 통신 방식이 완전히 같고 이름만 다르므로 어느 것을 골라도 동작은 같지만, 실제 기종을 고르면 설정 파일과 화면이 무엇이 연결돼 있는지 스스로 설명하게 된다. 기존에 Primus 로 설정해 둔 곳은 그대로 두면 된다. Changed: 시리얼 장비 자동 판별이 마취기를 특정 기종이 아니라 통신 방식 이름(Medibus)으로 인식한다. 같은 통신 방식을 쓰는 기종들은 회선만 봐서는 구별할 수 없으므로, 근거 없이 모델명을 단정하지 않는 편이 낫다고 판단했다. 기종 이름이 필요하면 설정에서 직접 고르면 된다. 1.19.21 Added: Dräger 장비가 스스로 알려주는 기종명을 로그에 남긴다. 설정 파일의 장비 이름은 사람이 적어 넣은 것이라 실제 기종을 보장하지 않는데, 기종에 따라 낼 수 있는 파형이 다르므로(예: Fabius 는 기도압과 유량은 있고 CO2 가 없다) 무엇이 연결돼 있는지 아는 것이 문제 해결의 출발점이다. 통신이 시작될 때 한 번 남는다. 1.19.20 Fixed: Dräger 마취기 가운데 파형 기능이 없는 기종에서 데이터가 전혀 수집되지 않던 문제. 연결은 된 것처럼 보이지만 아무 값도 들어오지 않는다. VR 이 장비를 확인한 뒤 파형 설정을 물어보고 그 답을 받아야만 데이터 요청을 시작하도록 돼 있었는데, 파형 기능이 없는 기종은 그 질문에 답하지 않으므로 요청이 영영 시작되지 않았다. 연결 유지 신호도 함께 멎어 장비가 통신을 끊고 다시 연결하기를 반복한다. 이제 장비 확인 직후부터 데이터를 요청한다. 파형 응답이 없으면 그 기종의 정상 동작이므로 숫자값만 수집하며 로그에 한 번 알린다. Fabius GS 는 규격상 파형 기능이 없는 대표적인 기종이다 (University of Washington 제보). Added: SyncPulseSerial 동기 펄스 생성기 지원. 여러 장비의 기록을 나중에 맞추기 위한 공통 번호를 시리얼로 보내는 장치로, 1초마다 6자리 카운터를 보낸다. PULSE 트랙에 그 번호가 기록된다. 시작이나 끝을 알리는 문자가 없는 형식이라, 메시지 사이의 침묵을 경계로 삼고 카운터가 1씩 늘어나는지 확인해 맞는 장비인지 판단한다. 자동 판별 대상에도 포함돼 있고, 환자 모니터와 같은 자리에 함께 연결해도 서로 방해하지 않는다. 1.19.19 Added: Dräger 인공호흡기·마취기가 파형에 함께 실어 보내는 호흡 위상 신호를 기록한다. 장비가 흡기와 호기 전환 시점을 직접 알려주는 것이라 파형에서 역산하는 것보다 정확하다. VENT_PHASE 트랙에 흡기 시작 1, 호기 시작 0 으로 남는다. 시각은 파형과 같은 시간축을 쓴다. 종전에는 이 신호를 읽지 않고 버렸다. Added: 환기 모드를 기록한다(VENT_MODE). 모드는 VC-CMV, PC-BIPAP, SPN-CPAP 처럼 압력조절인지 용적조절인지를 알려주어 파형 해석에 직접 쓰인다. 장비가 띄우고 있는 다른 안내 문구도 VENT_MSG 로 남는다. VR 이 그동안 이 정보를 담은 명령을 아예 보내지 않고 있었다 — 이제 측정값·설정과 함께 번갈아 요청한다. 표기가 장비나 언어에 따라 달라도 되도록 문구가 아니라 규격 코드로 판별한다. Added: 파형이 서로 맞는지 확인할 근거를 로그에 남긴다. 용적은 유량을 적분한 것이므로 둘의 관계를 수치로 기록해 둔다. 다만 이것으로 파형 이름을 바꾸거나 경고하지는 않는다 — 용적조절 환기에서는 기도압도 용적에 거의 비례해서 이 수치만으로는 둘을 구별할 수 없기 때문이다. 1.19.18 Added: Y케이블로 엿보는 Dräger 연결에서 파형 종류를 자동으로 알아낸다. Y케이블 배선에 따라 장비가 보내는 것만 보이는 방과, 반대편 장치가 보내는 것까지 보이는 방이 있는데, 후자에서는 그 장치가 파형을 요청하는 내용이 VR 에도 보인다. 거기에 순서가 그대로 들어 있으므로 추정 없이 확정한다 (실제로 수술장 두 방을 재보니 한 방은 양방향, 한 방은 단방향이었다). 단방향인 방은 종전대로 wavs= 로 적어주거나 도착 순서 추정을 쓴다. Fixed: 엿보는 연결인데도 VR 이 자기가 고른 파형 순서를 적용해 이름이 어긋날 수 있던 문제 — 회선에 다른 장치가 있는지를 통신 내용으로 판별해(보낸 적 없는 명령의 응답이 보이면 다른 장치가 있는 것이다) 그런 경우 순서를 스스로 정하지 않는다. 어느 모드로 동작 중인지는 로그에 남는다. 1.19.17 Added: Y케이블로 엿보기만 하는 Dräger 연결에서 파형 이름을 확정할 수 있다. 이런 연결은 VR 이 장비에 아무것도 보낼 수 없어 어떤 파형이 어떤 순서로 오는지 물어볼 수가 없고, 그래서 도착 순서대로 추정해 기록해 왔다(이름이 실제와 다를 수 있었다). 이제 vr.conf 의 장비 항목에 wavs= 로 그 방의 순서를 적어두면 그대로 사용한다. 순서는 그 장비에 연결된 다른 장치가 정한 것이라 병원에서 알 수 있는 값이다. 예: wavs=AWP,AWF. 적지 않으면 종전과 같이 도착 순서로 추정하며, 추정 중이라는 사실과 확정하는 방법을 로그에 남긴다. 직결 연결에서는 종전대로 VR 이 직접 물어보므로 이 설정이 없어도 정확하다. Added: 회선에서 처음 보는 종류의 통신 프레임을 한 번씩 로그에 남긴다. 어떤 장치가 무엇을 주고받는지 현장에서 확인할 수 있어 연결 문제 진단에 쓴다. 1.19.16 Fixed: 1.19.15 에서 Y케이블로 엿보기만 하는 연결(서울대병원 수술장의 Dräger 전부가 이 방식)의 파형이 기록되지 않던 문제 — 1.19.15 는 VR 이 직접 요청한 파형만 기록하도록 바뀌었는데, 관찰 전용 연결에서는 VR 이 장비에 아무것도 보낼 수 없어(송신선이 끊겨 있다) 요청 자체를 못 하므로 들어오는 파형을 전부 버렸다. 이제 이런 연결에서는 1.19.14 이전과 같이 도착 순서대로 기록한다. 다른 장치가 설정해 둔 순서는 알 수 없으므로 이름이 실제와 다를 수 있고, 그 사실을 로그에 함께 남긴다. 1.19.15 를 이미 설치한 곳은 이 버전으로 올려야 한다. 1.19.15 Fixed: Dräger 마취기·인공호흡기(MEDIBUS)에서 파형이 엉뚱한 이름으로 기록되던 문제. 장비가 보내는 파형 묶음의 첫 바이트에는 “몇 번째로 요청한 파형이 들어있는가” 가 비트로 표시되는데, VR 은 그 비트를 세기만 하고 도착 순서대로 미리 정해둔 트랙(CO2, AWP, AWF)에 넣었다. 장비가 요청한 파형 중 일부만 보내면 그만큼 밀려 기록돼, 예를 들어 기도압(AWP)만 보내는 인공호흡기에서 그 파형이 CO2 로 저장됐다 (Evita V600 실측 제보). 이제 비트를 해석해 요청한 순서와 맞춰 넣는다. Fixed: 요청하는 파형이 CO2 와 기도압 두 가지로 고정돼 있어, 장비가 유량(Flow)이나 용적(Volume) 파형을 제공해도 받지 못했다. 이제 장비에게 먼저 제공 가능한 파형 목록을 물어보고 그중에서 요청한다. Added: vr.conf 의 장비 설정에 wavs= 로 받을 파형을 고를 수 있다 (예: wavs=AWP,AWF,VOL,CO2). 비워두면 장비가 제공하는 것을 앞에서부터 최대 4개까지 자동으로 받는다. 4개인 이유는 한 묶음에 최대 4개까지만 표시할 수 있기 때문이다. 지원 이름: AWP AWF PLETH VOL O2 CO2 CO2_KPA CO2_PCT AGENT HAL ENF ISO DES SEV PTRACH AWF_DEV (16진 코드도 가능). 파형을 늘리면 회선 대역을 더 쓰므로 9600bps 의 80% 를 넘으면 로그로 경고한다. 어떤 파형이 실제로 들어오고 있는지도 로그에 남겨 현장에서 확인할 수 있다. 1.19.14 Added: 서버에서 VR 로 내려보내는 명령(원격 시간 동기, 원격 업그레이드, 재시작, 설정 변경 등)을 다시 사용할 수 있다. 1.18.52 에서 이 경로를 통째로 막았는데, 시간 동기와 원격 업그레이드가 꼭 필요한 기관이 있어 되살렸다. 다만 원격으로 프로그램을 바꾸거나 설정을 덮어쓰는 것은 그 자체로 위험하므로 기본값은 여전히 전부 차단이고, vr.conf 에 SERVER_CONTROL 로 허용할 명령을 직접 적어야 동작한다 (예: SERVER_CONTROL=dt 는 시간 동기만, SERVER_CONTROL=dt,update 는 시간 동기와 업그레이드만, SERVER_CONTROL=1 은 전부 허용). 사용 가능한 명령은 update restart reboot edit_conf new_bed del_bed edit_bed dt add_event. 시간 동기는 예전과 달리 녹화 중에는 시계를 건너뛰지 않는다 — 녹화 중 시계를 갑자기 옮기면 파형에 공백이 생기거나 기록이 겹치므로, 녹화 중이면 서서히 당겨 맞추고(슬루) 녹화가 없을 때만 한번에 맞춘다. 2초 이내 차이는 무시한다. 예전 구현이 10분 이상 차이날 때 프로그램을 강제 종료하던 동작도 없앴다. 1.19.13 Fixed: 웹 모니터링 화면에서 EEG 파형이 0.1초 정도씩 끊겨 보이던 문제 — 화면 전송용으로 파형을 줄일 때 원본 주파수가 나누어떨어지지 않으면 일부 구간이 비어 그만큼 빈칸으로 보였다. 나누어떨어지는 비율로만 줄이도록 수정. 녹화 파일에는 원래 문제가 없었다(표시만의 문제). 1.19.12 Fixed: Dräger 마취기 연동 안정화 보완 — 시리얼 회선 자동판별 중 잡음이 잘못된 데이터로 오인될 여지를 차단. 1.19.11 Fixed: Dräger 마취기(Atlan 등 MEDIBUS 장비)에서 통신 오류 알람(COM1 failure)이 반복되고 파형이 전혀 기록되지 않던 문제. 원인 세 가지를 모두 수정했다. (1) 장비는 3초 안에 아무 신호라도 받지 못하면 연결을 끊는데, VR 이 5초마다 데이터를 요청하는 것 외에 보내는 것이 없어 매번 연결이 끊기고 재접속을 반복했다 — 규격이 정한 연결 유지 신호(NOP)를 2초마다 보내도록 했다(데이터 요청 주기는 그대로). (2) 장비가 응답 도중에 다른 명령을 끼워 보내는 것이 규격상 허용되는데, VR 이 그때 받던 응답을 통째로 버려 파형 설정 응답을 매번 놓쳤다 — 끼어든 부분을 건너뛰고 원래 응답을 이어받도록 했다. (3) 장비가 보내는 요청 하나에 VR 이 응답하지 않아 통신이 끊기던 것도 수정. 파형·수치가 정상 기록되고 알람이 사라진다. 1.19.10 Fixed: 하루 한 번의 자동 재시작 시점에 수집이 통째로 멎어 사람이 손을 대기 전까지 복구되지 않던 문제(or-106, 104분 무데이터) — 종료 과정이 끝나지 않아 프로그램이 죽지 않았고, 죽지 않으니 자동 재시작도 일어나지 않았다. 주원인은 시리얼 회선 자동판별(AUTO_DETECT)이 종료 신호를 보지 않고 스윕(보레이트 6종 x 포맷 8종)을 끝까지 돌던 것으로, 무음 회선에서 종료에 2분이 걸려 정상 종료 유예시간을 넘겼다. 이제 종료 즉시 판별을 중단한다(실측 2분 -> 3초). 함께 종료 중 스레드 정리가 서로를 기다릴 수 있던 두 곳을 제거하고, 그럼에도 종료가 20초 안에 끝나지 않으면 강제 종료해 반드시 재시작되도록 안전장치를 넣었다. 녹화 파일은 5초마다 저장되므로 유실되지 않는다. 1.19.9 Fixed: 큰 녹화 파일(약 100MB 이상)을 서버로 올릴 때 프로그램이 메모리 부족으로 강제 종료되고, 재시작 후 같은 파일을 다시 시도하다 무한 반복되던 문제 — 업로드 본문을 메모리에 통째로 만들지 않고 디스크에서 흘려보내도록 변경(100MB 파일 기준 최대 메모리 208MB → 14MB). 큰 파일이 한 개라도 쌓이면 그 장비의 이후 업로드가 전부 막히던 증상이 사라진다. 1.19.8 Fixed: 자동 파일 자르기(CUT_FILE) 를 쓰는 베드에서 환자가 계속 연결돼 있는데도 녹화 파일이 PT_WAITING_TIME(기본 5분) 마다 잘리던 문제 — 웹 모니터링 전송 후 메모리를 비우는 과정에서 환자 연결 판정에 쓰이는 최근 구간까지 지워져 "환자가 나갔다" 로 오판하던 것. 이제 판정에 필요한 구간은 보존한다. 파일이 5분마다 끊기고 그때마다 수 초가 유실되던 증상이 사라진다. 1.19.7 Added: GE Corometrics(분만 태아감시) 모체 vitals 지원 — 기존엔 태아 심박(HR1/HR2)·자궁수축(UACT)만 기록됐으나 이제 같은 장비가 함께 보내는 모체 SpO2/맥박(PR)/NIBP(수축·이완·평균)도 한 파일에 기록. Fixed: 프로세스 종료(서비스 매니저의 정상 종료 요청, SIGTERM) 시 즉시 종료되지 않고 강제 종료되던 문제 — 이제 SIGTERM 을 받아 녹화 버퍼를 flush 하고 스레드를 정리한 뒤 정상 종료. 1.19.5 Fixed: CUT_HOURLY 가 동작하지 않는 것처럼 보이던 문제의 실체 — k3s 컨테이너에서 기본 저장 경로가 $HOME/recordings(임시 레이어)로 잡혀, 매일 자정 재시작 때 업로드 안 된 하루치 녹화가 통째로 사라짐. hostPath 마운트 /recordings 가 있으면 그것을 기본 저장 경로로 사용. Fixed: 종료(daily restart) 시 멀티베드 TCP 노드가 terminate(exit=133)/SIGSEGV(139) 로 죽던 문제. Linux finalize 가 device 스레드를 정지하지 않고 반환해 exit() 전역 소멸자가 살아있는 PortRouter accept_thread 를 파괴하며 std::terminate — DISPLAY::quiesce_threads + PortRouter::shutdown 으로 정리 후 종료 (코어 백트레이스로 확정, 로컬 재현 후 수정 검증). Fixed: Link+/Agilia 파서가 시리얼 노이즈(size byte 0 또는 헤더 없는 잡음 8byte)에 역전된 반복자 범위로 vector 를 생성하다 std::length_error 로 죽던 문제(or-110) — sentinel 상태 파싱 봉쇄 + 재동기화. Fixed: FILENAME_TEMPLATE 의 %Y(4자리 연도)가 "0126" 으로 나오던 문제 (tm_year+1900 누락). 1.19.4 Fixed: Link+ 등 재접속이 잦은 장비에서 시리얼 포트를 재오픈할 때 "terminate called without an active exception" 으로 프로세스가 죽던 문제(exit=133). reading_thread_func 이 스스로 반환해도 std::thread 객체는 joinable 로 남는데, open() 이 join 없이 새 스레드를 대입해 std::terminate 가 호출됨 → 스레드 생성 전에 이전 reading/parsing 스레드를 join 후 재생성. Fixed: headless/AUTO_DETECT 모드에서 실제 장비가 없을 때 Demo 장비가 생성돼 가짜 vital 트랙 38종이 업로드되던 문제 → --console 또는 AUTO_DETECT 시 Demo 미생성. 1.19.3 Fixed: 여러 베드를 한 프로세스가 TCP 로 수집하는 구성에서 클라이언트 재접속이 잦으면 프로세스가 SIGSEGV 로 죽던 문제(accept_handoff 가 replace 시 옛 소켓을 즉시 close 해 recv 중인 reader 와 경합, fd 번호 재사용). tcp reader 를 poll(200ms) 게이트 뒤의 recv 로 재구성하고 replace 는 shutdown 후 소유권을 reader/close 로 단일화. SNUH 회복실 VRN 15대에서 재현. Fixed: ICU 등 Intellivue 다수 베드에서 반복되던 SIGSEGV(139) 두 계열의 use-after-free. (1) DISPLAY::add_rec 이 레코드를 트랙에 push 한 뒤 저장했는데, push 직후 p 가 다른 스레드에 공유되고 Linux 의 메모리 보존 루프(600초 이전 레코드 delete)가 시계 어긋난 모니터에서 방금 넣은 레코드를 즉시 evict 해, 이어지는 p->save() 가 해제된 메모리에 가상 호출 → 저장을 push 앞으로 옮겨 공유 후 p 미접근. (2) DISPLAY::del_trk 이 트랙을 목록에서 뺀 직후 raw delete 해, 파싱 스레드의 캐시 포인터·장비 이름맵·recorded_trks 가 죽은 포인터를 참조 (특히 IAP 채널변경의 del 직후 add 가 해제 포인터 반환) → DEVICE 규칙처럼 살아있는 동안 삭제 금지, ~DISPLAY 에서 일괄 해제. picu-11 코어 덤프로 크래시 지점(add_rec vtable 간접호출) 확정. SNUH fleet ~100 노드 실배포에서 기대 크래시 다수 대비 실제 0, 미배포 노드는 계속 크래시. Fixed: Linux from_utf16 이 locale 의존·비스레드안전 wcstombs 를 쓰고 UTF-16 을 UTF-32 wchar_t 로 오해석하던 문제 → 수동 UTF-8 인코더(서로게이트 페어 재조립, 무효값 U+FFFD)로 교체. Added: AUTO_DETECT=1(vr.conf) 시리얼 장비 자동 판별. 회선 측정(보레이트·프레임폭·패리티) → 후보 필터(3축 일치, 베드당 클래스 배타, 포트 사전확률, 판별캐시) → 트랙 수확량 경쟁. 드라이버 무수정, 미설정 시 유휴 대기라 기존 설정 무영향. SNUH 수술장·ICU 실측 오탐 0.
1.19.24
1.19.24 Fixed: Windows 에서 SyncPulseSerial(Arduino UNO R4)의 데이터가 전혀 들어오지 않거나 한참 뒤에야 간헐적으로 들어오던 문제. Arduino 의 USB 시리얼은 컴퓨터 쪽이 준비 신호(DTR)를 올려야만 데이터를 내보내는데, VR 이 Windows 에서 포트를 열 때 이 신호를 내린 채로 열고 있었다. 시리얼 터미널 프로그램들은 이 신호를 기본으로 올리기 때문에 터미널에서는 잘 보이는데 VR 에서만 안 보이는 형태로 나타났다 (Win11/Win7 3대 실측, Nord University AniLab 제보). 장비 연결 상태는 정상으로 표시되므로 화면만 봐서는 알 수 없는 문제였다. 다른 시리얼 장비들의 동작은 그대로다 — 이 신호에 민감한 장비가 있을 수 있어 Arduino 계열 드라이버에서만 올린다. 1.19.23 Fixed: SyncPulseSerial 의 PULSE 값이 화면에 보이지 않던 문제. 값은 정상적으로 수신되고 파일에도 기록되고 있었는데, 화면 눈금 범위가 카운터의 최대값(864000)으로 잡혀 있어 테스트 초반의 작은 값들이 트랙 맨 바닥 1픽셀에 눌려 보이지 않았다. 이제 매초 가운데에 뚜렷한 선으로 표시된다. 기록된 값 자체는 이전 버전에서도 온전했다(표시만의 문제). 1.19.22 Added: Dräger 마취기를 기종 이름으로 고를 수 있다. 장비 목록에 Fabius 가 추가됐고, 목록에 없는 Dräger 를 쓰는 곳을 위해 Medibus 라는 일반 항목도 함께 넣었다. Primus, Fabius, Medibus 는 통신 방식이 완전히 같고 이름만 다르므로 어느 것을 골라도 동작은 같지만, 실제 기종을 고르면 설정 파일과 화면이 무엇이 연결돼 있는지 스스로 설명하게 된다. 기존에 Primus 로 설정해 둔 곳은 그대로 두면 된다. Changed: 시리얼 장비 자동 판별이 마취기를 특정 기종이 아니라 통신 방식 이름(Medibus)으로 인식한다. 같은 통신 방식을 쓰는 기종들은 회선만 봐서는 구별할 수 없으므로, 근거 없이 모델명을 단정하지 않는 편이 낫다고 판단했다. 기종 이름이 필요하면 설정에서 직접 고르면 된다. 1.19.21 Added: Dräger 장비가 스스로 알려주는 기종명을 로그에 남긴다. 설정 파일의 장비 이름은 사람이 적어 넣은 것이라 실제 기종을 보장하지 않는데, 기종에 따라 낼 수 있는 파형이 다르므로(예: Fabius 는 기도압과 유량은 있고 CO2 가 없다) 무엇이 연결돼 있는지 아는 것이 문제 해결의 출발점이다. 통신이 시작될 때 한 번 남는다. 1.19.20 Fixed: Dräger 마취기 가운데 파형 기능이 없는 기종에서 데이터가 전혀 수집되지 않던 문제. 연결은 된 것처럼 보이지만 아무 값도 들어오지 않는다. VR 이 장비를 확인한 뒤 파형 설정을 물어보고 그 답을 받아야만 데이터 요청을 시작하도록 돼 있었는데, 파형 기능이 없는 기종은 그 질문에 답하지 않으므로 요청이 영영 시작되지 않았다. 연결 유지 신호도 함께 멎어 장비가 통신을 끊고 다시 연결하기를 반복한다. 이제 장비 확인 직후부터 데이터를 요청한다. 파형 응답이 없으면 그 기종의 정상 동작이므로 숫자값만 수집하며 로그에 한 번 알린다. Fabius GS 는 규격상 파형 기능이 없는 대표적인 기종이다 (University of Washington 제보). Added: SyncPulseSerial 동기 펄스 생성기 지원. 여러 장비의 기록을 나중에 맞추기 위한 공통 번호를 시리얼로 보내는 장치로, 1초마다 6자리 카운터를 보낸다. PULSE 트랙에 그 번호가 기록된다. 시작이나 끝을 알리는 문자가 없는 형식이라, 메시지 사이의 침묵을 경계로 삼고 카운터가 1씩 늘어나는지 확인해 맞는 장비인지 판단한다. 자동 판별 대상에도 포함돼 있고, 환자 모니터와 같은 자리에 함께 연결해도 서로 방해하지 않는다. 1.19.19 Added: Dräger 인공호흡기·마취기가 파형에 함께 실어 보내는 호흡 위상 신호를 기록한다. 장비가 흡기와 호기 전환 시점을 직접 알려주는 것이라 파형에서 역산하는 것보다 정확하다. VENT_PHASE 트랙에 흡기 시작 1, 호기 시작 0 으로 남는다. 시각은 파형과 같은 시간축을 쓴다. 종전에는 이 신호를 읽지 않고 버렸다. Added: 환기 모드를 기록한다(VENT_MODE). 모드는 VC-CMV, PC-BIPAP, SPN-CPAP 처럼 압력조절인지 용적조절인지를 알려주어 파형 해석에 직접 쓰인다. 장비가 띄우고 있는 다른 안내 문구도 VENT_MSG 로 남는다. VR 이 그동안 이 정보를 담은 명령을 아예 보내지 않고 있었다 — 이제 측정값·설정과 함께 번갈아 요청한다. 표기가 장비나 언어에 따라 달라도 되도록 문구가 아니라 규격 코드로 판별한다. Added: 파형이 서로 맞는지 확인할 근거를 로그에 남긴다. 용적은 유량을 적분한 것이므로 둘의 관계를 수치로 기록해 둔다. 다만 이것으로 파형 이름을 바꾸거나 경고하지는 않는다 — 용적조절 환기에서는 기도압도 용적에 거의 비례해서 이 수치만으로는 둘을 구별할 수 없기 때문이다. 1.19.18 Added: Y케이블로 엿보는 Dräger 연결에서 파형 종류를 자동으로 알아낸다. Y케이블 배선에 따라 장비가 보내는 것만 보이는 방과, 반대편 장치가 보내는 것까지 보이는 방이 있는데, 후자에서는 그 장치가 파형을 요청하는 내용이 VR 에도 보인다. 거기에 순서가 그대로 들어 있으므로 추정 없이 확정한다 (실제로 수술장 두 방을 재보니 한 방은 양방향, 한 방은 단방향이었다). 단방향인 방은 종전대로 wavs= 로 적어주거나 도착 순서 추정을 쓴다. Fixed: 엿보는 연결인데도 VR 이 자기가 고른 파형 순서를 적용해 이름이 어긋날 수 있던 문제 — 회선에 다른 장치가 있는지를 통신 내용으로 판별해(보낸 적 없는 명령의 응답이 보이면 다른 장치가 있는 것이다) 그런 경우 순서를 스스로 정하지 않는다. 어느 모드로 동작 중인지는 로그에 남는다. 1.19.17 Added: Y케이블로 엿보기만 하는 Dräger 연결에서 파형 이름을 확정할 수 있다. 이런 연결은 VR 이 장비에 아무것도 보낼 수 없어 어떤 파형이 어떤 순서로 오는지 물어볼 수가 없고, 그래서 도착 순서대로 추정해 기록해 왔다(이름이 실제와 다를 수 있었다). 이제 vr.conf 의 장비 항목에 wavs= 로 그 방의 순서를 적어두면 그대로 사용한다. 순서는 그 장비에 연결된 다른 장치가 정한 것이라 병원에서 알 수 있는 값이다. 예: wavs=AWP,AWF. 적지 않으면 종전과 같이 도착 순서로 추정하며, 추정 중이라는 사실과 확정하는 방법을 로그에 남긴다. 직결 연결에서는 종전대로 VR 이 직접 물어보므로 이 설정이 없어도 정확하다. Added: 회선에서 처음 보는 종류의 통신 프레임을 한 번씩 로그에 남긴다. 어떤 장치가 무엇을 주고받는지 현장에서 확인할 수 있어 연결 문제 진단에 쓴다. 1.19.16 Fixed: 1.19.15 에서 Y케이블로 엿보기만 하는 연결(서울대병원 수술장의 Dräger 전부가 이 방식)의 파형이 기록되지 않던 문제 — 1.19.15 는 VR 이 직접 요청한 파형만 기록하도록 바뀌었는데, 관찰 전용 연결에서는 VR 이 장비에 아무것도 보낼 수 없어(송신선이 끊겨 있다) 요청 자체를 못 하므로 들어오는 파형을 전부 버렸다. 이제 이런 연결에서는 1.19.14 이전과 같이 도착 순서대로 기록한다. 다른 장치가 설정해 둔 순서는 알 수 없으므로 이름이 실제와 다를 수 있고, 그 사실을 로그에 함께 남긴다. 1.19.15 를 이미 설치한 곳은 이 버전으로 올려야 한다. 1.19.15 Fixed: Dräger 마취기·인공호흡기(MEDIBUS)에서 파형이 엉뚱한 이름으로 기록되던 문제. 장비가 보내는 파형 묶음의 첫 바이트에는 “몇 번째로 요청한 파형이 들어있는가” 가 비트로 표시되는데, VR 은 그 비트를 세기만 하고 도착 순서대로 미리 정해둔 트랙(CO2, AWP, AWF)에 넣었다. 장비가 요청한 파형 중 일부만 보내면 그만큼 밀려 기록돼, 예를 들어 기도압(AWP)만 보내는 인공호흡기에서 그 파형이 CO2 로 저장됐다 (Evita V600 실측 제보). 이제 비트를 해석해 요청한 순서와 맞춰 넣는다. Fixed: 요청하는 파형이 CO2 와 기도압 두 가지로 고정돼 있어, 장비가 유량(Flow)이나 용적(Volume) 파형을 제공해도 받지 못했다. 이제 장비에게 먼저 제공 가능한 파형 목록을 물어보고 그중에서 요청한다. Added: vr.conf 의 장비 설정에 wavs= 로 받을 파형을 고를 수 있다 (예: wavs=AWP,AWF,VOL,CO2). 비워두면 장비가 제공하는 것을 앞에서부터 최대 4개까지 자동으로 받는다. 4개인 이유는 한 묶음에 최대 4개까지만 표시할 수 있기 때문이다. 지원 이름: AWP AWF PLETH VOL O2 CO2 CO2_KPA CO2_PCT AGENT HAL ENF ISO DES SEV PTRACH AWF_DEV (16진 코드도 가능). 파형을 늘리면 회선 대역을 더 쓰므로 9600bps 의 80% 를 넘으면 로그로 경고한다. 어떤 파형이 실제로 들어오고 있는지도 로그에 남겨 현장에서 확인할 수 있다. 1.19.14 Added: 서버에서 VR 로 내려보내는 명령(원격 시간 동기, 원격 업그레이드, 재시작, 설정 변경 등)을 다시 사용할 수 있다. 1.18.52 에서 이 경로를 통째로 막았는데, 시간 동기와 원격 업그레이드가 꼭 필요한 기관이 있어 되살렸다. 다만 원격으로 프로그램을 바꾸거나 설정을 덮어쓰는 것은 그 자체로 위험하므로 기본값은 여전히 전부 차단이고, vr.conf 에 SERVER_CONTROL 로 허용할 명령을 직접 적어야 동작한다 (예: SERVER_CONTROL=dt 는 시간 동기만, SERVER_CONTROL=dt,update 는 시간 동기와 업그레이드만, SERVER_CONTROL=1 은 전부 허용). 사용 가능한 명령은 update restart reboot edit_conf new_bed del_bed edit_bed dt add_event. 시간 동기는 예전과 달리 녹화 중에는 시계를 건너뛰지 않는다 — 녹화 중 시계를 갑자기 옮기면 파형에 공백이 생기거나 기록이 겹치므로, 녹화 중이면 서서히 당겨 맞추고(슬루) 녹화가 없을 때만 한번에 맞춘다. 2초 이내 차이는 무시한다. 예전 구현이 10분 이상 차이날 때 프로그램을 강제 종료하던 동작도 없앴다. 1.19.13 Fixed: 웹 모니터링 화면에서 EEG 파형이 0.1초 정도씩 끊겨 보이던 문제 — 화면 전송용으로 파형을 줄일 때 원본 주파수가 나누어떨어지지 않으면 일부 구간이 비어 그만큼 빈칸으로 보였다. 나누어떨어지는 비율로만 줄이도록 수정. 녹화 파일에는 원래 문제가 없었다(표시만의 문제). 1.19.12 Fixed: Dräger 마취기 연동 안정화 보완 — 시리얼 회선 자동판별 중 잡음이 잘못된 데이터로 오인될 여지를 차단. 1.19.11 Fixed: Dräger 마취기(Atlan 등 MEDIBUS 장비)에서 통신 오류 알람(COM1 failure)이 반복되고 파형이 전혀 기록되지 않던 문제. 원인 세 가지를 모두 수정했다. (1) 장비는 3초 안에 아무 신호라도 받지 못하면 연결을 끊는데, VR 이 5초마다 데이터를 요청하는 것 외에 보내는 것이 없어 매번 연결이 끊기고 재접속을 반복했다 — 규격이 정한 연결 유지 신호(NOP)를 2초마다 보내도록 했다(데이터 요청 주기는 그대로). (2) 장비가 응답 도중에 다른 명령을 끼워 보내는 것이 규격상 허용되는데, VR 이 그때 받던 응답을 통째로 버려 파형 설정 응답을 매번 놓쳤다 — 끼어든 부분을 건너뛰고 원래 응답을 이어받도록 했다. (3) 장비가 보내는 요청 하나에 VR 이 응답하지 않아 통신이 끊기던 것도 수정. 파형·수치가 정상 기록되고 알람이 사라진다. 1.19.10 Fixed: 하루 한 번의 자동 재시작 시점에 수집이 통째로 멎어 사람이 손을 대기 전까지 복구되지 않던 문제(or-106, 104분 무데이터) — 종료 과정이 끝나지 않아 프로그램이 죽지 않았고, 죽지 않으니 자동 재시작도 일어나지 않았다. 주원인은 시리얼 회선 자동판별(AUTO_DETECT)이 종료 신호를 보지 않고 스윕(보레이트 6종 x 포맷 8종)을 끝까지 돌던 것으로, 무음 회선에서 종료에 2분이 걸려 정상 종료 유예시간을 넘겼다. 이제 종료 즉시 판별을 중단한다(실측 2분 -> 3초). 함께 종료 중 스레드 정리가 서로를 기다릴 수 있던 두 곳을 제거하고, 그럼에도 종료가 20초 안에 끝나지 않으면 강제 종료해 반드시 재시작되도록 안전장치를 넣었다. 녹화 파일은 5초마다 저장되므로 유실되지 않는다. 1.19.9 Fixed: 큰 녹화 파일(약 100MB 이상)을 서버로 올릴 때 프로그램이 메모리 부족으로 강제 종료되고, 재시작 후 같은 파일을 다시 시도하다 무한 반복되던 문제 — 업로드 본문을 메모리에 통째로 만들지 않고 디스크에서 흘려보내도록 변경(100MB 파일 기준 최대 메모리 208MB → 14MB). 큰 파일이 한 개라도 쌓이면 그 장비의 이후 업로드가 전부 막히던 증상이 사라진다. 1.19.8 Fixed: 자동 파일 자르기(CUT_FILE) 를 쓰는 베드에서 환자가 계속 연결돼 있는데도 녹화 파일이 PT_WAITING_TIME(기본 5분) 마다 잘리던 문제 — 웹 모니터링 전송 후 메모리를 비우는 과정에서 환자 연결 판정에 쓰이는 최근 구간까지 지워져 "환자가 나갔다" 로 오판하던 것. 이제 판정에 필요한 구간은 보존한다. 파일이 5분마다 끊기고 그때마다 수 초가 유실되던 증상이 사라진다. 1.19.7 Added: GE Corometrics(분만 태아감시) 모체 vitals 지원 — 기존엔 태아 심박(HR1/HR2)·자궁수축(UACT)만 기록됐으나 이제 같은 장비가 함께 보내는 모체 SpO2/맥박(PR)/NIBP(수축·이완·평균)도 한 파일에 기록. Fixed: 프로세스 종료(서비스 매니저의 정상 종료 요청, SIGTERM) 시 즉시 종료되지 않고 강제 종료되던 문제 — 이제 SIGTERM 을 받아 녹화 버퍼를 flush 하고 스레드를 정리한 뒤 정상 종료. 1.19.5 Fixed: CUT_HOURLY 가 동작하지 않는 것처럼 보이던 문제의 실체 — k3s 컨테이너에서 기본 저장 경로가 $HOME/recordings(임시 레이어)로 잡혀, 매일 자정 재시작 때 업로드 안 된 하루치 녹화가 통째로 사라짐. hostPath 마운트 /recordings 가 있으면 그것을 기본 저장 경로로 사용. Fixed: 종료(daily restart) 시 멀티베드 TCP 노드가 terminate(exit=133)/SIGSEGV(139) 로 죽던 문제. Linux finalize 가 device 스레드를 정지하지 않고 반환해 exit() 전역 소멸자가 살아있는 PortRouter accept_thread 를 파괴하며 std::terminate — DISPLAY::quiesce_threads + PortRouter::shutdown 으로 정리 후 종료 (코어 백트레이스로 확정, 로컬 재현 후 수정 검증). Fixed: Link+/Agilia 파서가 시리얼 노이즈(size byte 0 또는 헤더 없는 잡음 8byte)에 역전된 반복자 범위로 vector 를 생성하다 std::length_error 로 죽던 문제(or-110) — sentinel 상태 파싱 봉쇄 + 재동기화. Fixed: FILENAME_TEMPLATE 의 %Y(4자리 연도)가 "0126" 으로 나오던 문제 (tm_year+1900 누락). 1.19.4 Fixed: Link+ 등 재접속이 잦은 장비에서 시리얼 포트를 재오픈할 때 "terminate called without an active exception" 으로 프로세스가 죽던 문제(exit=133). reading_thread_func 이 스스로 반환해도 std::thread 객체는 joinable 로 남는데, open() 이 join 없이 새 스레드를 대입해 std::terminate 가 호출됨 → 스레드 생성 전에 이전 reading/parsing 스레드를 join 후 재생성. Fixed: headless/AUTO_DETECT 모드에서 실제 장비가 없을 때 Demo 장비가 생성돼 가짜 vital 트랙 38종이 업로드되던 문제 → --console 또는 AUTO_DETECT 시 Demo 미생성. 1.19.3 Fixed: 여러 베드를 한 프로세스가 TCP 로 수집하는 구성에서 클라이언트 재접속이 잦으면 프로세스가 SIGSEGV 로 죽던 문제(accept_handoff 가 replace 시 옛 소켓을 즉시 close 해 recv 중인 reader 와 경합, fd 번호 재사용). tcp reader 를 poll(200ms) 게이트 뒤의 recv 로 재구성하고 replace 는 shutdown 후 소유권을 reader/close 로 단일화. SNUH 회복실 VRN 15대에서 재현. Fixed: ICU 등 Intellivue 다수 베드에서 반복되던 SIGSEGV(139) 두 계열의 use-after-free. (1) DISPLAY::add_rec 이 레코드를 트랙에 push 한 뒤 저장했는데, push 직후 p 가 다른 스레드에 공유되고 Linux 의 메모리 보존 루프(600초 이전 레코드 delete)가 시계 어긋난 모니터에서 방금 넣은 레코드를 즉시 evict 해, 이어지는 p->save() 가 해제된 메모리에 가상 호출 → 저장을 push 앞으로 옮겨 공유 후 p 미접근. (2) DISPLAY::del_trk 이 트랙을 목록에서 뺀 직후 raw delete 해, 파싱 스레드의 캐시 포인터·장비 이름맵·recorded_trks 가 죽은 포인터를 참조 (특히 IAP 채널변경의 del 직후 add 가 해제 포인터 반환) → DEVICE 규칙처럼 살아있는 동안 삭제 금지, ~DISPLAY 에서 일괄 해제. picu-11 코어 덤프로 크래시 지점(add_rec vtable 간접호출) 확정. SNUH fleet ~100 노드 실배포에서 기대 크래시 다수 대비 실제 0, 미배포 노드는 계속 크래시. Fixed: Linux from_utf16 이 locale 의존·비스레드안전 wcstombs 를 쓰고 UTF-16 을 UTF-32 wchar_t 로 오해석하던 문제 → 수동 UTF-8 인코더(서로게이트 페어 재조립, 무효값 U+FFFD)로 교체. Added: AUTO_DETECT=1(vr.conf) 시리얼 장비 자동 판별. 회선 측정(보레이트·프레임폭·패리티) → 후보 필터(3축 일치, 베드당 클래스 배타, 포트 사전확률, 판별캐시) → 트랙 수확량 경쟁. 드라이버 무수정, 미설정 시 유휴 대기라 기존 설정 무영향. SNUH 수술장·ICU 실측 오탐 0.
1.19.23
1.19.23 Fixed: SyncPulseSerial 의 PULSE 값이 화면에 보이지 않던 문제. 값은 정상적으로 수신되고 파일에도 기록되고 있었는데, 화면 눈금 범위가 카운터의 최대값(864000)으로 잡혀 있어 테스트 초반의 작은 값들이 트랙 맨 바닥 1픽셀에 눌려 보이지 않았다. 이제 매초 가운데에 뚜렷한 선으로 표시된다. 기록된 값 자체는 이전 버전에서도 온전했다(표시만의 문제). 1.19.22 Added: Dräger 마취기를 기종 이름으로 고를 수 있다. 장비 목록에 Fabius 가 추가됐고, 목록에 없는 Dräger 를 쓰는 곳을 위해 Medibus 라는 일반 항목도 함께 넣었다. Primus, Fabius, Medibus 는 통신 방식이 완전히 같고 이름만 다르므로 어느 것을 골라도 동작은 같지만, 실제 기종을 고르면 설정 파일과 화면이 무엇이 연결돼 있는지 스스로 설명하게 된다. 기존에 Primus 로 설정해 둔 곳은 그대로 두면 된다. Changed: 시리얼 장비 자동 판별이 마취기를 특정 기종이 아니라 통신 방식 이름(Medibus)으로 인식한다. 같은 통신 방식을 쓰는 기종들은 회선만 봐서는 구별할 수 없으므로, 근거 없이 모델명을 단정하지 않는 편이 낫다고 판단했다. 기종 이름이 필요하면 설정에서 직접 고르면 된다. 1.19.21 Added: Dräger 장비가 스스로 알려주는 기종명을 로그에 남긴다. 설정 파일의 장비 이름은 사람이 적어 넣은 것이라 실제 기종을 보장하지 않는데, 기종에 따라 낼 수 있는 파형이 다르므로(예: Fabius 는 기도압과 유량은 있고 CO2 가 없다) 무엇이 연결돼 있는지 아는 것이 문제 해결의 출발점이다. 통신이 시작될 때 한 번 남는다. 1.19.20 Fixed: Dräger 마취기 가운데 파형 기능이 없는 기종에서 데이터가 전혀 수집되지 않던 문제. 연결은 된 것처럼 보이지만 아무 값도 들어오지 않는다. VR 이 장비를 확인한 뒤 파형 설정을 물어보고 그 답을 받아야만 데이터 요청을 시작하도록 돼 있었는데, 파형 기능이 없는 기종은 그 질문에 답하지 않으므로 요청이 영영 시작되지 않았다. 연결 유지 신호도 함께 멎어 장비가 통신을 끊고 다시 연결하기를 반복한다. 이제 장비 확인 직후부터 데이터를 요청한다. 파형 응답이 없으면 그 기종의 정상 동작이므로 숫자값만 수집하며 로그에 한 번 알린다. Fabius GS 는 규격상 파형 기능이 없는 대표적인 기종이다 (University of Washington 제보). Added: SyncPulseSerial 동기 펄스 생성기 지원. 여러 장비의 기록을 나중에 맞추기 위한 공통 번호를 시리얼로 보내는 장치로, 1초마다 6자리 카운터를 보낸다. PULSE 트랙에 그 번호가 기록된다. 시작이나 끝을 알리는 문자가 없는 형식이라, 메시지 사이의 침묵을 경계로 삼고 카운터가 1씩 늘어나는지 확인해 맞는 장비인지 판단한다. 자동 판별 대상에도 포함돼 있고, 환자 모니터와 같은 자리에 함께 연결해도 서로 방해하지 않는다. 1.19.19 Added: Dräger 인공호흡기·마취기가 파형에 함께 실어 보내는 호흡 위상 신호를 기록한다. 장비가 흡기와 호기 전환 시점을 직접 알려주는 것이라 파형에서 역산하는 것보다 정확하다. VENT_PHASE 트랙에 흡기 시작 1, 호기 시작 0 으로 남는다. 시각은 파형과 같은 시간축을 쓴다. 종전에는 이 신호를 읽지 않고 버렸다. Added: 환기 모드를 기록한다(VENT_MODE). 모드는 VC-CMV, PC-BIPAP, SPN-CPAP 처럼 압력조절인지 용적조절인지를 알려주어 파형 해석에 직접 쓰인다. 장비가 띄우고 있는 다른 안내 문구도 VENT_MSG 로 남는다. VR 이 그동안 이 정보를 담은 명령을 아예 보내지 않고 있었다 — 이제 측정값·설정과 함께 번갈아 요청한다. 표기가 장비나 언어에 따라 달라도 되도록 문구가 아니라 규격 코드로 판별한다. Added: 파형이 서로 맞는지 확인할 근거를 로그에 남긴다. 용적은 유량을 적분한 것이므로 둘의 관계를 수치로 기록해 둔다. 다만 이것으로 파형 이름을 바꾸거나 경고하지는 않는다 — 용적조절 환기에서는 기도압도 용적에 거의 비례해서 이 수치만으로는 둘을 구별할 수 없기 때문이다. 1.19.18 Added: Y케이블로 엿보는 Dräger 연결에서 파형 종류를 자동으로 알아낸다. Y케이블 배선에 따라 장비가 보내는 것만 보이는 방과, 반대편 장치가 보내는 것까지 보이는 방이 있는데, 후자에서는 그 장치가 파형을 요청하는 내용이 VR 에도 보인다. 거기에 순서가 그대로 들어 있으므로 추정 없이 확정한다 (실제로 수술장 두 방을 재보니 한 방은 양방향, 한 방은 단방향이었다). 단방향인 방은 종전대로 wavs= 로 적어주거나 도착 순서 추정을 쓴다. Fixed: 엿보는 연결인데도 VR 이 자기가 고른 파형 순서를 적용해 이름이 어긋날 수 있던 문제 — 회선에 다른 장치가 있는지를 통신 내용으로 판별해(보낸 적 없는 명령의 응답이 보이면 다른 장치가 있는 것이다) 그런 경우 순서를 스스로 정하지 않는다. 어느 모드로 동작 중인지는 로그에 남는다. 1.19.17 Added: Y케이블로 엿보기만 하는 Dräger 연결에서 파형 이름을 확정할 수 있다. 이런 연결은 VR 이 장비에 아무것도 보낼 수 없어 어떤 파형이 어떤 순서로 오는지 물어볼 수가 없고, 그래서 도착 순서대로 추정해 기록해 왔다(이름이 실제와 다를 수 있었다). 이제 vr.conf 의 장비 항목에 wavs= 로 그 방의 순서를 적어두면 그대로 사용한다. 순서는 그 장비에 연결된 다른 장치가 정한 것이라 병원에서 알 수 있는 값이다. 예: wavs=AWP,AWF. 적지 않으면 종전과 같이 도착 순서로 추정하며, 추정 중이라는 사실과 확정하는 방법을 로그에 남긴다. 직결 연결에서는 종전대로 VR 이 직접 물어보므로 이 설정이 없어도 정확하다. Added: 회선에서 처음 보는 종류의 통신 프레임을 한 번씩 로그에 남긴다. 어떤 장치가 무엇을 주고받는지 현장에서 확인할 수 있어 연결 문제 진단에 쓴다. 1.19.16 Fixed: 1.19.15 에서 Y케이블로 엿보기만 하는 연결(서울대병원 수술장의 Dräger 전부가 이 방식)의 파형이 기록되지 않던 문제 — 1.19.15 는 VR 이 직접 요청한 파형만 기록하도록 바뀌었는데, 관찰 전용 연결에서는 VR 이 장비에 아무것도 보낼 수 없어(송신선이 끊겨 있다) 요청 자체를 못 하므로 들어오는 파형을 전부 버렸다. 이제 이런 연결에서는 1.19.14 이전과 같이 도착 순서대로 기록한다. 다른 장치가 설정해 둔 순서는 알 수 없으므로 이름이 실제와 다를 수 있고, 그 사실을 로그에 함께 남긴다. 1.19.15 를 이미 설치한 곳은 이 버전으로 올려야 한다. 1.19.15 Fixed: Dräger 마취기·인공호흡기(MEDIBUS)에서 파형이 엉뚱한 이름으로 기록되던 문제. 장비가 보내는 파형 묶음의 첫 바이트에는 “몇 번째로 요청한 파형이 들어있는가” 가 비트로 표시되는데, VR 은 그 비트를 세기만 하고 도착 순서대로 미리 정해둔 트랙(CO2, AWP, AWF)에 넣었다. 장비가 요청한 파형 중 일부만 보내면 그만큼 밀려 기록돼, 예를 들어 기도압(AWP)만 보내는 인공호흡기에서 그 파형이 CO2 로 저장됐다 (Evita V600 실측 제보). 이제 비트를 해석해 요청한 순서와 맞춰 넣는다. Fixed: 요청하는 파형이 CO2 와 기도압 두 가지로 고정돼 있어, 장비가 유량(Flow)이나 용적(Volume) 파형을 제공해도 받지 못했다. 이제 장비에게 먼저 제공 가능한 파형 목록을 물어보고 그중에서 요청한다. Added: vr.conf 의 장비 설정에 wavs= 로 받을 파형을 고를 수 있다 (예: wavs=AWP,AWF,VOL,CO2). 비워두면 장비가 제공하는 것을 앞에서부터 최대 4개까지 자동으로 받는다. 4개인 이유는 한 묶음에 최대 4개까지만 표시할 수 있기 때문이다. 지원 이름: AWP AWF PLETH VOL O2 CO2 CO2_KPA CO2_PCT AGENT HAL ENF ISO DES SEV PTRACH AWF_DEV (16진 코드도 가능). 파형을 늘리면 회선 대역을 더 쓰므로 9600bps 의 80% 를 넘으면 로그로 경고한다. 어떤 파형이 실제로 들어오고 있는지도 로그에 남겨 현장에서 확인할 수 있다. 1.19.14 Added: 서버에서 VR 로 내려보내는 명령(원격 시간 동기, 원격 업그레이드, 재시작, 설정 변경 등)을 다시 사용할 수 있다. 1.18.52 에서 이 경로를 통째로 막았는데, 시간 동기와 원격 업그레이드가 꼭 필요한 기관이 있어 되살렸다. 다만 원격으로 프로그램을 바꾸거나 설정을 덮어쓰는 것은 그 자체로 위험하므로 기본값은 여전히 전부 차단이고, vr.conf 에 SERVER_CONTROL 로 허용할 명령을 직접 적어야 동작한다 (예: SERVER_CONTROL=dt 는 시간 동기만, SERVER_CONTROL=dt,update 는 시간 동기와 업그레이드만, SERVER_CONTROL=1 은 전부 허용). 사용 가능한 명령은 update restart reboot edit_conf new_bed del_bed edit_bed dt add_event. 시간 동기는 예전과 달리 녹화 중에는 시계를 건너뛰지 않는다 — 녹화 중 시계를 갑자기 옮기면 파형에 공백이 생기거나 기록이 겹치므로, 녹화 중이면 서서히 당겨 맞추고(슬루) 녹화가 없을 때만 한번에 맞춘다. 2초 이내 차이는 무시한다. 예전 구현이 10분 이상 차이날 때 프로그램을 강제 종료하던 동작도 없앴다. 1.19.13 Fixed: 웹 모니터링 화면에서 EEG 파형이 0.1초 정도씩 끊겨 보이던 문제 — 화면 전송용으로 파형을 줄일 때 원본 주파수가 나누어떨어지지 않으면 일부 구간이 비어 그만큼 빈칸으로 보였다. 나누어떨어지는 비율로만 줄이도록 수정. 녹화 파일에는 원래 문제가 없었다(표시만의 문제). 1.19.12 Fixed: Dräger 마취기 연동 안정화 보완 — 시리얼 회선 자동판별 중 잡음이 잘못된 데이터로 오인될 여지를 차단. 1.19.11 Fixed: Dräger 마취기(Atlan 등 MEDIBUS 장비)에서 통신 오류 알람(COM1 failure)이 반복되고 파형이 전혀 기록되지 않던 문제. 원인 세 가지를 모두 수정했다. (1) 장비는 3초 안에 아무 신호라도 받지 못하면 연결을 끊는데, VR 이 5초마다 데이터를 요청하는 것 외에 보내는 것이 없어 매번 연결이 끊기고 재접속을 반복했다 — 규격이 정한 연결 유지 신호(NOP)를 2초마다 보내도록 했다(데이터 요청 주기는 그대로). (2) 장비가 응답 도중에 다른 명령을 끼워 보내는 것이 규격상 허용되는데, VR 이 그때 받던 응답을 통째로 버려 파형 설정 응답을 매번 놓쳤다 — 끼어든 부분을 건너뛰고 원래 응답을 이어받도록 했다. (3) 장비가 보내는 요청 하나에 VR 이 응답하지 않아 통신이 끊기던 것도 수정. 파형·수치가 정상 기록되고 알람이 사라진다. 1.19.10 Fixed: 하루 한 번의 자동 재시작 시점에 수집이 통째로 멎어 사람이 손을 대기 전까지 복구되지 않던 문제(or-106, 104분 무데이터) — 종료 과정이 끝나지 않아 프로그램이 죽지 않았고, 죽지 않으니 자동 재시작도 일어나지 않았다. 주원인은 시리얼 회선 자동판별(AUTO_DETECT)이 종료 신호를 보지 않고 스윕(보레이트 6종 x 포맷 8종)을 끝까지 돌던 것으로, 무음 회선에서 종료에 2분이 걸려 정상 종료 유예시간을 넘겼다. 이제 종료 즉시 판별을 중단한다(실측 2분 -> 3초). 함께 종료 중 스레드 정리가 서로를 기다릴 수 있던 두 곳을 제거하고, 그럼에도 종료가 20초 안에 끝나지 않으면 강제 종료해 반드시 재시작되도록 안전장치를 넣었다. 녹화 파일은 5초마다 저장되므로 유실되지 않는다. 1.19.9 Fixed: 큰 녹화 파일(약 100MB 이상)을 서버로 올릴 때 프로그램이 메모리 부족으로 강제 종료되고, 재시작 후 같은 파일을 다시 시도하다 무한 반복되던 문제 — 업로드 본문을 메모리에 통째로 만들지 않고 디스크에서 흘려보내도록 변경(100MB 파일 기준 최대 메모리 208MB → 14MB). 큰 파일이 한 개라도 쌓이면 그 장비의 이후 업로드가 전부 막히던 증상이 사라진다. 1.19.8 Fixed: 자동 파일 자르기(CUT_FILE) 를 쓰는 베드에서 환자가 계속 연결돼 있는데도 녹화 파일이 PT_WAITING_TIME(기본 5분) 마다 잘리던 문제 — 웹 모니터링 전송 후 메모리를 비우는 과정에서 환자 연결 판정에 쓰이는 최근 구간까지 지워져 "환자가 나갔다" 로 오판하던 것. 이제 판정에 필요한 구간은 보존한다. 파일이 5분마다 끊기고 그때마다 수 초가 유실되던 증상이 사라진다. 1.19.7 Added: GE Corometrics(분만 태아감시) 모체 vitals 지원 — 기존엔 태아 심박(HR1/HR2)·자궁수축(UACT)만 기록됐으나 이제 같은 장비가 함께 보내는 모체 SpO2/맥박(PR)/NIBP(수축·이완·평균)도 한 파일에 기록. Fixed: 프로세스 종료(서비스 매니저의 정상 종료 요청, SIGTERM) 시 즉시 종료되지 않고 강제 종료되던 문제 — 이제 SIGTERM 을 받아 녹화 버퍼를 flush 하고 스레드를 정리한 뒤 정상 종료. 1.19.5 Fixed: CUT_HOURLY 가 동작하지 않는 것처럼 보이던 문제의 실체 — k3s 컨테이너에서 기본 저장 경로가 $HOME/recordings(임시 레이어)로 잡혀, 매일 자정 재시작 때 업로드 안 된 하루치 녹화가 통째로 사라짐. hostPath 마운트 /recordings 가 있으면 그것을 기본 저장 경로로 사용. Fixed: 종료(daily restart) 시 멀티베드 TCP 노드가 terminate(exit=133)/SIGSEGV(139) 로 죽던 문제. Linux finalize 가 device 스레드를 정지하지 않고 반환해 exit() 전역 소멸자가 살아있는 PortRouter accept_thread 를 파괴하며 std::terminate — DISPLAY::quiesce_threads + PortRouter::shutdown 으로 정리 후 종료 (코어 백트레이스로 확정, 로컬 재현 후 수정 검증). Fixed: Link+/Agilia 파서가 시리얼 노이즈(size byte 0 또는 헤더 없는 잡음 8byte)에 역전된 반복자 범위로 vector 를 생성하다 std::length_error 로 죽던 문제(or-110) — sentinel 상태 파싱 봉쇄 + 재동기화. Fixed: FILENAME_TEMPLATE 의 %Y(4자리 연도)가 "0126" 으로 나오던 문제 (tm_year+1900 누락). 1.19.4 Fixed: Link+ 등 재접속이 잦은 장비에서 시리얼 포트를 재오픈할 때 "terminate called without an active exception" 으로 프로세스가 죽던 문제(exit=133). reading_thread_func 이 스스로 반환해도 std::thread 객체는 joinable 로 남는데, open() 이 join 없이 새 스레드를 대입해 std::terminate 가 호출됨 → 스레드 생성 전에 이전 reading/parsing 스레드를 join 후 재생성. Fixed: headless/AUTO_DETECT 모드에서 실제 장비가 없을 때 Demo 장비가 생성돼 가짜 vital 트랙 38종이 업로드되던 문제 → --console 또는 AUTO_DETECT 시 Demo 미생성. 1.19.3 Fixed: 여러 베드를 한 프로세스가 TCP 로 수집하는 구성에서 클라이언트 재접속이 잦으면 프로세스가 SIGSEGV 로 죽던 문제(accept_handoff 가 replace 시 옛 소켓을 즉시 close 해 recv 중인 reader 와 경합, fd 번호 재사용). tcp reader 를 poll(200ms) 게이트 뒤의 recv 로 재구성하고 replace 는 shutdown 후 소유권을 reader/close 로 단일화. SNUH 회복실 VRN 15대에서 재현. Fixed: ICU 등 Intellivue 다수 베드에서 반복되던 SIGSEGV(139) 두 계열의 use-after-free. (1) DISPLAY::add_rec 이 레코드를 트랙에 push 한 뒤 저장했는데, push 직후 p 가 다른 스레드에 공유되고 Linux 의 메모리 보존 루프(600초 이전 레코드 delete)가 시계 어긋난 모니터에서 방금 넣은 레코드를 즉시 evict 해, 이어지는 p->save() 가 해제된 메모리에 가상 호출 → 저장을 push 앞으로 옮겨 공유 후 p 미접근. (2) DISPLAY::del_trk 이 트랙을 목록에서 뺀 직후 raw delete 해, 파싱 스레드의 캐시 포인터·장비 이름맵·recorded_trks 가 죽은 포인터를 참조 (특히 IAP 채널변경의 del 직후 add 가 해제 포인터 반환) → DEVICE 규칙처럼 살아있는 동안 삭제 금지, ~DISPLAY 에서 일괄 해제. picu-11 코어 덤프로 크래시 지점(add_rec vtable 간접호출) 확정. SNUH fleet ~100 노드 실배포에서 기대 크래시 다수 대비 실제 0, 미배포 노드는 계속 크래시. Fixed: Linux from_utf16 이 locale 의존·비스레드안전 wcstombs 를 쓰고 UTF-16 을 UTF-32 wchar_t 로 오해석하던 문제 → 수동 UTF-8 인코더(서로게이트 페어 재조립, 무효값 U+FFFD)로 교체. Added: AUTO_DETECT=1(vr.conf) 시리얼 장비 자동 판별. 회선 측정(보레이트·프레임폭·패리티) → 후보 필터(3축 일치, 베드당 클래스 배타, 포트 사전확률, 판별캐시) → 트랙 수확량 경쟁. 드라이버 무수정, 미설정 시 유휴 대기라 기존 설정 무영향. SNUH 수술장·ICU 실측 오탐 0.
1.19.27
1.19.27 Changed: 자동 판별이 지난번에 그 자리에 있던 장비를 가장 먼저 시험한다. 종전에는 지난 결과를 후보 순서를 바꾸는 데만 썼고, 그 앞에서 회선 설정(통신 속도·형식) 측정을 먼저 끝냈다. 그 측정이 조용한 회선에서는 48가지 조합을 다 확인하느라 72초를 쓴다. 방의 장비 구성은 거의 바뀌지 않으므로 지난 답보다 강한 단서가 없고, 빗나가더라도 후보 하나를 더 시험하는 값만 든다. Changed: 드물게 말하는 장비를 판별할 수 있게 됐다. 후보 하나를 시험하는 시간이 30초로 고정돼 있었는데, 근이완 감시장치(TwitchView, TOFScan)는 5분에 한 번 측정하므로 30초 안에 아무 말도 하지 않는 것이 정상이다. 그래서 이 장비들은 자동 판별로 영영 잡히지 않았다(한 수술방에서 21일 동안 지난 답을 기억한 채 확인에 계속 실패하고 있었다). 이제 각 장비가 스스로 정한 “이만큼은 조용할 수 있다” 는 값(최대 6분)만큼 듣는다. 겨룰 상대가 없는 단독 시험이므로 자료가 나오면 그 즉시 끝내며, 포트를 동시에 보게 된 1.19.26 덕분에 긴 대기가 다른 포트를 막지 않는다. 1.19.26 Changed: 시리얼 장비 자동 판별이 훨씬 빨리 끝난다. 종전에는 정답을 이미 찾은 뒤에도 남은 후보를 하나당 30초씩 끝까지 시험했다. 환자가 없는 아침에는 모니터가 트랙을 두어 개밖에 채우지 않아 “충분히 찾았다” 는 판정에 도달하지 못했기 때문이다(실측: 정답이 나온 시각은 기동 113초인데 확정은 275초). 이제 네 가지 경우에 그 자리에서 확정한다 — 지난 기동에서 이 포트의 답이었던 장비가 이번에도 자료를 낼 때, 회선 실측으로 후보가 하나만 남았을 때, 트랙이 10개 이상 열렸을 때, 그리고 1위가 2위를 3배 넘게 앞설 때. 확정 근거를 로그에 함께 남긴다. Changed: 여러 시리얼 포트를 동시에 살펴본다. 종전에는 한 포트씩 차례로 봐서 뒤쪽 포트는 앞 포트의 판별이 끝나기를 통째로 기다렸다(실측: 다섯 번째 포트가 기동 590초, 대부분이 대기 시간). 포트는 서로 독립된 장치라 동시에 열어도 간섭하지 않는다. 메모리를 위해 한 번에 3개까지만 본다. 같은 종류의 장비가 두 포트에서 동시에 잡히면 자료를 더 많이 낸 쪽만 남긴다(한 베드에 환자 모니터 하나, 마취기 하나라는 기존 규칙 그대로). Note: 1.19.25 설명 정정 — SyncPulseSerial 이 구형 Arduino UNO 에서 한 값도 기록되지 않던 것을 “프레임이 한 칸씩 밀린다” 고 적었는데 정확하지 않다. 실제로는 모으던 앞부분이 매번 버려져 여섯 자리가 완성되는 순간이 오지 않는 것이다. 또 같은 항목에서 Windows 수신 대기 방식을 원인으로 단정했으나, 가상 직렬 포트로 A/B 시험한 결과 그 경로에서는 재현되지 않았다 — 실제로 문제를 고친 것은 메시지 경계를 시각이 아니라 내용으로 찾도록 바꾼 쪽이다. 1.19.25 Fixed: SyncPulseSerial(동기 펄스 생성기)에서 1초 간격의 펄스가 2초마다 하나씩만 기록되거나, 보드 종류에 따라 아예 한 값도 기록되지 않던 문제. 두 증상 모두 원인이 하나다 — 이 장비는 시작·끝을 알리는 문자 없이 6자리 숫자만 흘려보내므로 VR 이 “잠깐의 침묵” 을 메시지 경계로 삼고 있었는데, 침묵은 회선이 아니라 컴퓨터 사정으로도 생긴다. 운영체제가 6바이트를 몇 번에 나눠 올려주고 그 사이가 벌어지면 앞자리가 버려져 이후 모든 값이 한 칸씩 밀리고, 한 번 밀리면 스스로 회복하지 못해 아무것도 기록되지 않는다(구형 Arduino UNO 에서 재현). 반대로 두 메시지가 한꺼번에 올라오면 둘 다 같은 시각으로 찍혀 1초짜리 펄스가 2초마다 하나 있는 것처럼 보인다(UNO R4 에서 재현). 이제 경계를 시간이 아니라 내용으로 찾는다 — 카운터가 정확히 1씩 늘어나는 자리를 스스로 찾아 맞추므로 회선이 어떻게 쪼개져 들어오든 영향을 받지 않고, 어긋나도 다음 펄스에서 다시 맞춘다. 한 번에 여러 펄스가 올라온 경우에는 카운터 간격만큼 시각을 되돌려 각각 제 시각에 기록한다. 세 가지 조건(정상·쪼개짐·뭉침) 재현 시험으로 확인했다 (Nord University AniLab 제보). Fixed: Windows 에서 데이터를 띄엄띄엄 보내는 시리얼 장비의 값이 최대 한 주기 늦게 들어오던 문제. 문자 도착 알림을 기다리는 동안이 아니라 그 직전 대기 중에 데이터가 통째로 들어와 버리면, 새로 도착하는 문자가 없어 알림이 오지 않고 이미 들어온 데이터가 다음 전송이 올 때까지 그대로 대기했다. 이제 알림이 없어도 수신 버퍼를 직접 확인한다. 초당 수십~수백 바이트를 보내는 대부분의 장비에서는 드러나지 않던 문제이지만, 1초에 한 번처럼 드물게 보내는 장비에서는 두 전송이 한 번에 처리되어 시각이 뭉쳤다. 1.19.24 Fixed: Windows 에서 SyncPulseSerial(Arduino UNO R4)의 데이터가 전혀 들어오지 않거나 한참 뒤에야 간헐적으로 들어오던 문제. Arduino 의 USB 시리얼은 컴퓨터 쪽이 준비 신호(DTR)를 올려야만 데이터를 내보내는데, VR 이 Windows 에서 포트를 열 때 이 신호를 내린 채로 열고 있었다. 시리얼 터미널 프로그램들은 이 신호를 기본으로 올리기 때문에 터미널에서는 잘 보이는데 VR 에서만 안 보이는 형태로 나타났다 (Win11/Win7 3대 실측, Nord University AniLab 제보). 장비 연결 상태는 정상으로 표시되므로 화면만 봐서는 알 수 없는 문제였다. 다른 시리얼 장비들의 동작은 그대로다 — 이 신호에 민감한 장비가 있을 수 있어 Arduino 계열 드라이버에서만 올린다. 1.19.23 Fixed: SyncPulseSerial 의 PULSE 값이 화면에 보이지 않던 문제. 값은 정상적으로 수신되고 파일에도 기록되고 있었는데, 화면 눈금 범위가 카운터의 최대값(864000)으로 잡혀 있어 테스트 초반의 작은 값들이 트랙 맨 바닥 1픽셀에 눌려 보이지 않았다. 이제 매초 가운데에 뚜렷한 선으로 표시된다. 기록된 값 자체는 이전 버전에서도 온전했다(표시만의 문제). 1.19.22 Added: Dräger 마취기를 기종 이름으로 고를 수 있다. 장비 목록에 Fabius 가 추가됐고, 목록에 없는 Dräger 를 쓰는 곳을 위해 Medibus 라는 일반 항목도 함께 넣었다. Primus, Fabius, Medibus 는 통신 방식이 완전히 같고 이름만 다르므로 어느 것을 골라도 동작은 같지만, 실제 기종을 고르면 설정 파일과 화면이 무엇이 연결돼 있는지 스스로 설명하게 된다. 기존에 Primus 로 설정해 둔 곳은 그대로 두면 된다. Changed: 시리얼 장비 자동 판별이 마취기를 특정 기종이 아니라 통신 방식 이름(Medibus)으로 인식한다. 같은 통신 방식을 쓰는 기종들은 회선만 봐서는 구별할 수 없으므로, 근거 없이 모델명을 단정하지 않는 편이 낫다고 판단했다. 기종 이름이 필요하면 설정에서 직접 고르면 된다. 1.19.21 Added: Dräger 장비가 스스로 알려주는 기종명을 로그에 남긴다. 설정 파일의 장비 이름은 사람이 적어 넣은 것이라 실제 기종을 보장하지 않는데, 기종에 따라 낼 수 있는 파형이 다르므로(예: Fabius 는 기도압과 유량은 있고 CO2 가 없다) 무엇이 연결돼 있는지 아는 것이 문제 해결의 출발점이다. 통신이 시작될 때 한 번 남는다. 1.19.20 Fixed: Dräger 마취기 가운데 파형 기능이 없는 기종에서 데이터가 전혀 수집되지 않던 문제. 연결은 된 것처럼 보이지만 아무 값도 들어오지 않는다. VR 이 장비를 확인한 뒤 파형 설정을 물어보고 그 답을 받아야만 데이터 요청을 시작하도록 돼 있었는데, 파형 기능이 없는 기종은 그 질문에 답하지 않으므로 요청이 영영 시작되지 않았다. 연결 유지 신호도 함께 멎어 장비가 통신을 끊고 다시 연결하기를 반복한다. 이제 장비 확인 직후부터 데이터를 요청한다. 파형 응답이 없으면 그 기종의 정상 동작이므로 숫자값만 수집하며 로그에 한 번 알린다. Fabius GS 는 규격상 파형 기능이 없는 대표적인 기종이다 (University of Washington 제보). Added: SyncPulseSerial 동기 펄스 생성기 지원. 여러 장비의 기록을 나중에 맞추기 위한 공통 번호를 시리얼로 보내는 장치로, 1초마다 6자리 카운터를 보낸다. PULSE 트랙에 그 번호가 기록된다. 시작이나 끝을 알리는 문자가 없는 형식이라, 메시지 사이의 침묵을 경계로 삼고 카운터가 1씩 늘어나는지 확인해 맞는 장비인지 판단한다. 자동 판별 대상에도 포함돼 있고, 환자 모니터와 같은 자리에 함께 연결해도 서로 방해하지 않는다. 1.19.19 Added: Dräger 인공호흡기·마취기가 파형에 함께 실어 보내는 호흡 위상 신호를 기록한다. 장비가 흡기와 호기 전환 시점을 직접 알려주는 것이라 파형에서 역산하는 것보다 정확하다. VENT_PHASE 트랙에 흡기 시작 1, 호기 시작 0 으로 남는다. 시각은 파형과 같은 시간축을 쓴다. 종전에는 이 신호를 읽지 않고 버렸다. Added: 환기 모드를 기록한다(VENT_MODE). 모드는 VC-CMV, PC-BIPAP, SPN-CPAP 처럼 압력조절인지 용적조절인지를 알려주어 파형 해석에 직접 쓰인다. 장비가 띄우고 있는 다른 안내 문구도 VENT_MSG 로 남는다. VR 이 그동안 이 정보를 담은 명령을 아예 보내지 않고 있었다 — 이제 측정값·설정과 함께 번갈아 요청한다. 표기가 장비나 언어에 따라 달라도 되도록 문구가 아니라 규격 코드로 판별한다. Added: 파형이 서로 맞는지 확인할 근거를 로그에 남긴다. 용적은 유량을 적분한 것이므로 둘의 관계를 수치로 기록해 둔다. 다만 이것으로 파형 이름을 바꾸거나 경고하지는 않는다 — 용적조절 환기에서는 기도압도 용적에 거의 비례해서 이 수치만으로는 둘을 구별할 수 없기 때문이다. 1.19.18 Added: Y케이블로 엿보는 Dräger 연결에서 파형 종류를 자동으로 알아낸다. Y케이블 배선에 따라 장비가 보내는 것만 보이는 방과, 반대편 장치가 보내는 것까지 보이는 방이 있는데, 후자에서는 그 장치가 파형을 요청하는 내용이 VR 에도 보인다. 거기에 순서가 그대로 들어 있으므로 추정 없이 확정한다 (실제로 수술장 두 방을 재보니 한 방은 양방향, 한 방은 단방향이었다). 단방향인 방은 종전대로 wavs= 로 적어주거나 도착 순서 추정을 쓴다. Fixed: 엿보는 연결인데도 VR 이 자기가 고른 파형 순서를 적용해 이름이 어긋날 수 있던 문제 — 회선에 다른 장치가 있는지를 통신 내용으로 판별해(보낸 적 없는 명령의 응답이 보이면 다른 장치가 있는 것이다) 그런 경우 순서를 스스로 정하지 않는다. 어느 모드로 동작 중인지는 로그에 남는다. 1.19.17 Added: Y케이블로 엿보기만 하는 Dräger 연결에서 파형 이름을 확정할 수 있다. 이런 연결은 VR 이 장비에 아무것도 보낼 수 없어 어떤 파형이 어떤 순서로 오는지 물어볼 수가 없고, 그래서 도착 순서대로 추정해 기록해 왔다(이름이 실제와 다를 수 있었다). 이제 vr.conf 의 장비 항목에 wavs= 로 그 방의 순서를 적어두면 그대로 사용한다. 순서는 그 장비에 연결된 다른 장치가 정한 것이라 병원에서 알 수 있는 값이다. 예: wavs=AWP,AWF. 적지 않으면 종전과 같이 도착 순서로 추정하며, 추정 중이라는 사실과 확정하는 방법을 로그에 남긴다. 직결 연결에서는 종전대로 VR 이 직접 물어보므로 이 설정이 없어도 정확하다. Added: 회선에서 처음 보는 종류의 통신 프레임을 한 번씩 로그에 남긴다. 어떤 장치가 무엇을 주고받는지 현장에서 확인할 수 있어 연결 문제 진단에 쓴다. 1.19.16 Fixed: 1.19.15 에서 Y케이블로 엿보기만 하는 연결(서울대병원 수술장의 Dräger 전부가 이 방식)의 파형이 기록되지 않던 문제 — 1.19.15 는 VR 이 직접 요청한 파형만 기록하도록 바뀌었는데, 관찰 전용 연결에서는 VR 이 장비에 아무것도 보낼 수 없어(송신선이 끊겨 있다) 요청 자체를 못 하므로 들어오는 파형을 전부 버렸다. 이제 이런 연결에서는 1.19.14 이전과 같이 도착 순서대로 기록한다. 다른 장치가 설정해 둔 순서는 알 수 없으므로 이름이 실제와 다를 수 있고, 그 사실을 로그에 함께 남긴다. 1.19.15 를 이미 설치한 곳은 이 버전으로 올려야 한다. 1.19.15 Fixed: Dräger 마취기·인공호흡기(MEDIBUS)에서 파형이 엉뚱한 이름으로 기록되던 문제. 장비가 보내는 파형 묶음의 첫 바이트에는 “몇 번째로 요청한 파형이 들어있는가” 가 비트로 표시되는데, VR 은 그 비트를 세기만 하고 도착 순서대로 미리 정해둔 트랙(CO2, AWP, AWF)에 넣었다. 장비가 요청한 파형 중 일부만 보내면 그만큼 밀려 기록돼, 예를 들어 기도압(AWP)만 보내는 인공호흡기에서 그 파형이 CO2 로 저장됐다 (Evita V600 실측 제보). 이제 비트를 해석해 요청한 순서와 맞춰 넣는다. Fixed: 요청하는 파형이 CO2 와 기도압 두 가지로 고정돼 있어, 장비가 유량(Flow)이나 용적(Volume) 파형을 제공해도 받지 못했다. 이제 장비에게 먼저 제공 가능한 파형 목록을 물어보고 그중에서 요청한다. Added: vr.conf 의 장비 설정에 wavs= 로 받을 파형을 고를 수 있다 (예: wavs=AWP,AWF,VOL,CO2). 비워두면 장비가 제공하는 것을 앞에서부터 최대 4개까지 자동으로 받는다. 4개인 이유는 한 묶음에 최대 4개까지만 표시할 수 있기 때문이다. 지원 이름: AWP AWF PLETH VOL O2 CO2 CO2_KPA CO2_PCT AGENT HAL ENF ISO DES SEV PTRACH AWF_DEV (16진 코드도 가능). 파형을 늘리면 회선 대역을 더 쓰므로 9600bps 의 80% 를 넘으면 로그로 경고한다. 어떤 파형이 실제로 들어오고 있는지도 로그에 남겨 현장에서 확인할 수 있다. 1.19.14 Added: 서버에서 VR 로 내려보내는 명령(원격 시간 동기, 원격 업그레이드, 재시작, 설정 변경 등)을 다시 사용할 수 있다. 1.18.52 에서 이 경로를 통째로 막았는데, 시간 동기와 원격 업그레이드가 꼭 필요한 기관이 있어 되살렸다. 다만 원격으로 프로그램을 바꾸거나 설정을 덮어쓰는 것은 그 자체로 위험하므로 기본값은 여전히 전부 차단이고, vr.conf 에 SERVER_CONTROL 로 허용할 명령을 직접 적어야 동작한다 (예: SERVER_CONTROL=dt 는 시간 동기만, SERVER_CONTROL=dt,update 는 시간 동기와 업그레이드만, SERVER_CONTROL=1 은 전부 허용). 사용 가능한 명령은 update restart reboot edit_conf new_bed del_bed edit_bed dt add_event. 시간 동기는 예전과 달리 녹화 중에는 시계를 건너뛰지 않는다 — 녹화 중 시계를 갑자기 옮기면 파형에 공백이 생기거나 기록이 겹치므로, 녹화 중이면 서서히 당겨 맞추고(슬루) 녹화가 없을 때만 한번에 맞춘다. 2초 이내 차이는 무시한다. 예전 구현이 10분 이상 차이날 때 프로그램을 강제 종료하던 동작도 없앴다. 1.19.13 Fixed: 웹 모니터링 화면에서 EEG 파형이 0.1초 정도씩 끊겨 보이던 문제 — 화면 전송용으로 파형을 줄일 때 원본 주파수가 나누어떨어지지 않으면 일부 구간이 비어 그만큼 빈칸으로 보였다. 나누어떨어지는 비율로만 줄이도록 수정. 녹화 파일에는 원래 문제가 없었다(표시만의 문제). 1.19.12 Fixed: Dräger 마취기 연동 안정화 보완 — 시리얼 회선 자동판별 중 잡음이 잘못된 데이터로 오인될 여지를 차단. 1.19.11 Fixed: Dräger 마취기(Atlan 등 MEDIBUS 장비)에서 통신 오류 알람(COM1 failure)이 반복되고 파형이 전혀 기록되지 않던 문제. 원인 세 가지를 모두 수정했다. (1) 장비는 3초 안에 아무 신호라도 받지 못하면 연결을 끊는데, VR 이 5초마다 데이터를 요청하는 것 외에 보내는 것이 없어 매번 연결이 끊기고 재접속을 반복했다 — 규격이 정한 연결 유지 신호(NOP)를 2초마다 보내도록 했다(데이터 요청 주기는 그대로). (2) 장비가 응답 도중에 다른 명령을 끼워 보내는 것이 규격상 허용되는데, VR 이 그때 받던 응답을 통째로 버려 파형 설정 응답을 매번 놓쳤다 — 끼어든 부분을 건너뛰고 원래 응답을 이어받도록 했다. (3) 장비가 보내는 요청 하나에 VR 이 응답하지 않아 통신이 끊기던 것도 수정. 파형·수치가 정상 기록되고 알람이 사라진다. 1.19.10 Fixed: 하루 한 번의 자동 재시작 시점에 수집이 통째로 멎어 사람이 손을 대기 전까지 복구되지 않던 문제(or-106, 104분 무데이터) — 종료 과정이 끝나지 않아 프로그램이 죽지 않았고, 죽지 않으니 자동 재시작도 일어나지 않았다. 주원인은 시리얼 회선 자동판별(AUTO_DETECT)이 종료 신호를 보지 않고 스윕(보레이트 6종 x 포맷 8종)을 끝까지 돌던 것으로, 무음 회선에서 종료에 2분이 걸려 정상 종료 유예시간을 넘겼다. 이제 종료 즉시 판별을 중단한다(실측 2분 -> 3초). 함께 종료 중 스레드 정리가 서로를 기다릴 수 있던 두 곳을 제거하고, 그럼에도 종료가 20초 안에 끝나지 않으면 강제 종료해 반드시 재시작되도록 안전장치를 넣었다. 녹화 파일은 5초마다 저장되므로 유실되지 않는다. 1.19.9 Fixed: 큰 녹화 파일(약 100MB 이상)을 서버로 올릴 때 프로그램이 메모리 부족으로 강제 종료되고, 재시작 후 같은 파일을 다시 시도하다 무한 반복되던 문제 — 업로드 본문을 메모리에 통째로 만들지 않고 디스크에서 흘려보내도록 변경(100MB 파일 기준 최대 메모리 208MB → 14MB). 큰 파일이 한 개라도 쌓이면 그 장비의 이후 업로드가 전부 막히던 증상이 사라진다. 1.19.8 Fixed: 자동 파일 자르기(CUT_FILE) 를 쓰는 베드에서 환자가 계속 연결돼 있는데도 녹화 파일이 PT_WAITING_TIME(기본 5분) 마다 잘리던 문제 — 웹 모니터링 전송 후 메모리를 비우는 과정에서 환자 연결 판정에 쓰이는 최근 구간까지 지워져 "환자가 나갔다" 로 오판하던 것. 이제 판정에 필요한 구간은 보존한다. 파일이 5분마다 끊기고 그때마다 수 초가 유실되던 증상이 사라진다. 1.19.7 Added: GE Corometrics(분만 태아감시) 모체 vitals 지원 — 기존엔 태아 심박(HR1/HR2)·자궁수축(UACT)만 기록됐으나 이제 같은 장비가 함께 보내는 모체 SpO2/맥박(PR)/NIBP(수축·이완·평균)도 한 파일에 기록. Fixed: 프로세스 종료(서비스 매니저의 정상 종료 요청, SIGTERM) 시 즉시 종료되지 않고 강제 종료되던 문제 — 이제 SIGTERM 을 받아 녹화 버퍼를 flush 하고 스레드를 정리한 뒤 정상 종료. 1.19.5 Fixed: CUT_HOURLY 가 동작하지 않는 것처럼 보이던 문제의 실체 — k3s 컨테이너에서 기본 저장 경로가 $HOME/recordings(임시 레이어)로 잡혀, 매일 자정 재시작 때 업로드 안 된 하루치 녹화가 통째로 사라짐. hostPath 마운트 /recordings 가 있으면 그것을 기본 저장 경로로 사용. Fixed: 종료(daily restart) 시 멀티베드 TCP 노드가 terminate(exit=133)/SIGSEGV(139) 로 죽던 문제. Linux finalize 가 device 스레드를 정지하지 않고 반환해 exit() 전역 소멸자가 살아있는 PortRouter accept_thread 를 파괴하며 std::terminate — DISPLAY::quiesce_threads + PortRouter::shutdown 으로 정리 후 종료 (코어 백트레이스로 확정, 로컬 재현 후 수정 검증). Fixed: Link+/Agilia 파서가 시리얼 노이즈(size byte 0 또는 헤더 없는 잡음 8byte)에 역전된 반복자 범위로 vector 를 생성하다 std::length_error 로 죽던 문제(or-110) — sentinel 상태 파싱 봉쇄 + 재동기화. Fixed: FILENAME_TEMPLATE 의 %Y(4자리 연도)가 "0126" 으로 나오던 문제 (tm_year+1900 누락). 1.19.4 Fixed: Link+ 등 재접속이 잦은 장비에서 시리얼 포트를 재오픈할 때 "terminate called without an active exception" 으로 프로세스가 죽던 문제(exit=133). reading_thread_func 이 스스로 반환해도 std::thread 객체는 joinable 로 남는데, open() 이 join 없이 새 스레드를 대입해 std::terminate 가 호출됨 → 스레드 생성 전에 이전 reading/parsing 스레드를 join 후 재생성. Fixed: headless/AUTO_DETECT 모드에서 실제 장비가 없을 때 Demo 장비가 생성돼 가짜 vital 트랙 38종이 업로드되던 문제 → --console 또는 AUTO_DETECT 시 Demo 미생성. 1.19.3 Fixed: 여러 베드를 한 프로세스가 TCP 로 수집하는 구성에서 클라이언트 재접속이 잦으면 프로세스가 SIGSEGV 로 죽던 문제(accept_handoff 가 replace 시 옛 소켓을 즉시 close 해 recv 중인 reader 와 경합, fd 번호 재사용). tcp reader 를 poll(200ms) 게이트 뒤의 recv 로 재구성하고 replace 는 shutdown 후 소유권을 reader/close 로 단일화. SNUH 회복실 VRN 15대에서 재현. Fixed: ICU 등 Intellivue 다수 베드에서 반복되던 SIGSEGV(139) 두 계열의 use-after-free. (1) DISPLAY::add_rec 이 레코드를 트랙에 push 한 뒤 저장했는데, push 직후 p 가 다른 스레드에 공유되고 Linux 의 메모리 보존 루프(600초 이전 레코드 delete)가 시계 어긋난 모니터에서 방금 넣은 레코드를 즉시 evict 해, 이어지는 p->save() 가 해제된 메모리에 가상 호출 → 저장을 push 앞으로 옮겨 공유 후 p 미접근. (2) DISPLAY::del_trk 이 트랙을 목록에서 뺀 직후 raw delete 해, 파싱 스레드의 캐시 포인터·장비 이름맵·recorded_trks 가 죽은 포인터를 참조 (특히 IAP 채널변경의 del 직후 add 가 해제 포인터 반환) → DEVICE 규칙처럼 살아있는 동안 삭제 금지, ~DISPLAY 에서 일괄 해제. picu-11 코어 덤프로 크래시 지점(add_rec vtable 간접호출) 확정. SNUH fleet ~100 노드 실배포에서 기대 크래시 다수 대비 실제 0, 미배포 노드는 계속 크래시. Fixed: Linux from_utf16 이 locale 의존·비스레드안전 wcstombs 를 쓰고 UTF-16 을 UTF-32 wchar_t 로 오해석하던 문제 → 수동 UTF-8 인코더(서로게이트 페어 재조립, 무효값 U+FFFD)로 교체. Added: AUTO_DETECT=1(vr.conf) 시리얼 장비 자동 판별. 회선 측정(보레이트·프레임폭·패리티) → 후보 필터(3축 일치, 베드당 클래스 배타, 포트 사전확률, 판별캐시) → 트랙 수확량 경쟁. 드라이버 무수정, 미설정 시 유휴 대기라 기존 설정 무영향. SNUH 수술장·ICU 실측 오탐 0.
1.19.26
1.19.26 Changed: 시리얼 장비 자동 판별이 훨씬 빨리 끝난다. 종전에는 정답을 이미 찾은 뒤에도 남은 후보를 하나당 30초씩 끝까지 시험했다. 환자가 없는 아침에는 모니터가 트랙을 두어 개밖에 채우지 않아 “충분히 찾았다” 는 판정에 도달하지 못했기 때문이다(실측: 정답이 나온 시각은 기동 113초인데 확정은 275초). 이제 네 가지 경우에 그 자리에서 확정한다 — 지난 기동에서 이 포트의 답이었던 장비가 이번에도 자료를 낼 때, 회선 실측으로 후보가 하나만 남았을 때, 트랙이 10개 이상 열렸을 때, 그리고 1위가 2위를 3배 넘게 앞설 때. 확정 근거를 로그에 함께 남긴다. Changed: 여러 시리얼 포트를 동시에 살펴본다. 종전에는 한 포트씩 차례로 봐서 뒤쪽 포트는 앞 포트의 판별이 끝나기를 통째로 기다렸다(실측: 다섯 번째 포트가 기동 590초, 대부분이 대기 시간). 포트는 서로 독립된 장치라 동시에 열어도 간섭하지 않는다. 메모리를 위해 한 번에 3개까지만 본다. 같은 종류의 장비가 두 포트에서 동시에 잡히면 자료를 더 많이 낸 쪽만 남긴다(한 베드에 환자 모니터 하나, 마취기 하나라는 기존 규칙 그대로). Note: 1.19.25 설명 정정 — SyncPulseSerial 이 구형 Arduino UNO 에서 한 값도 기록되지 않던 것을 “프레임이 한 칸씩 밀린다” 고 적었는데 정확하지 않다. 실제로는 모으던 앞부분이 매번 버려져 여섯 자리가 완성되는 순간이 오지 않는 것이다. 또 같은 항목에서 Windows 수신 대기 방식을 원인으로 단정했으나, 가상 직렬 포트로 A/B 시험한 결과 그 경로에서는 재현되지 않았다 — 실제로 문제를 고친 것은 메시지 경계를 시각이 아니라 내용으로 찾도록 바꾼 쪽이다. 1.19.25 Fixed: SyncPulseSerial(동기 펄스 생성기)에서 1초 간격의 펄스가 2초마다 하나씩만 기록되거나, 보드 종류에 따라 아예 한 값도 기록되지 않던 문제. 두 증상 모두 원인이 하나다 — 이 장비는 시작·끝을 알리는 문자 없이 6자리 숫자만 흘려보내므로 VR 이 “잠깐의 침묵” 을 메시지 경계로 삼고 있었는데, 침묵은 회선이 아니라 컴퓨터 사정으로도 생긴다. 운영체제가 6바이트를 몇 번에 나눠 올려주고 그 사이가 벌어지면 앞자리가 버려져 이후 모든 값이 한 칸씩 밀리고, 한 번 밀리면 스스로 회복하지 못해 아무것도 기록되지 않는다(구형 Arduino UNO 에서 재현). 반대로 두 메시지가 한꺼번에 올라오면 둘 다 같은 시각으로 찍혀 1초짜리 펄스가 2초마다 하나 있는 것처럼 보인다(UNO R4 에서 재현). 이제 경계를 시간이 아니라 내용으로 찾는다 — 카운터가 정확히 1씩 늘어나는 자리를 스스로 찾아 맞추므로 회선이 어떻게 쪼개져 들어오든 영향을 받지 않고, 어긋나도 다음 펄스에서 다시 맞춘다. 한 번에 여러 펄스가 올라온 경우에는 카운터 간격만큼 시각을 되돌려 각각 제 시각에 기록한다. 세 가지 조건(정상·쪼개짐·뭉침) 재현 시험으로 확인했다 (Nord University AniLab 제보). Fixed: Windows 에서 데이터를 띄엄띄엄 보내는 시리얼 장비의 값이 최대 한 주기 늦게 들어오던 문제. 문자 도착 알림을 기다리는 동안이 아니라 그 직전 대기 중에 데이터가 통째로 들어와 버리면, 새로 도착하는 문자가 없어 알림이 오지 않고 이미 들어온 데이터가 다음 전송이 올 때까지 그대로 대기했다. 이제 알림이 없어도 수신 버퍼를 직접 확인한다. 초당 수십~수백 바이트를 보내는 대부분의 장비에서는 드러나지 않던 문제이지만, 1초에 한 번처럼 드물게 보내는 장비에서는 두 전송이 한 번에 처리되어 시각이 뭉쳤다. 1.19.24 Fixed: Windows 에서 SyncPulseSerial(Arduino UNO R4)의 데이터가 전혀 들어오지 않거나 한참 뒤에야 간헐적으로 들어오던 문제. Arduino 의 USB 시리얼은 컴퓨터 쪽이 준비 신호(DTR)를 올려야만 데이터를 내보내는데, VR 이 Windows 에서 포트를 열 때 이 신호를 내린 채로 열고 있었다. 시리얼 터미널 프로그램들은 이 신호를 기본으로 올리기 때문에 터미널에서는 잘 보이는데 VR 에서만 안 보이는 형태로 나타났다 (Win11/Win7 3대 실측, Nord University AniLab 제보). 장비 연결 상태는 정상으로 표시되므로 화면만 봐서는 알 수 없는 문제였다. 다른 시리얼 장비들의 동작은 그대로다 — 이 신호에 민감한 장비가 있을 수 있어 Arduino 계열 드라이버에서만 올린다. 1.19.23 Fixed: SyncPulseSerial 의 PULSE 값이 화면에 보이지 않던 문제. 값은 정상적으로 수신되고 파일에도 기록되고 있었는데, 화면 눈금 범위가 카운터의 최대값(864000)으로 잡혀 있어 테스트 초반의 작은 값들이 트랙 맨 바닥 1픽셀에 눌려 보이지 않았다. 이제 매초 가운데에 뚜렷한 선으로 표시된다. 기록된 값 자체는 이전 버전에서도 온전했다(표시만의 문제). 1.19.22 Added: Dräger 마취기를 기종 이름으로 고를 수 있다. 장비 목록에 Fabius 가 추가됐고, 목록에 없는 Dräger 를 쓰는 곳을 위해 Medibus 라는 일반 항목도 함께 넣었다. Primus, Fabius, Medibus 는 통신 방식이 완전히 같고 이름만 다르므로 어느 것을 골라도 동작은 같지만, 실제 기종을 고르면 설정 파일과 화면이 무엇이 연결돼 있는지 스스로 설명하게 된다. 기존에 Primus 로 설정해 둔 곳은 그대로 두면 된다. Changed: 시리얼 장비 자동 판별이 마취기를 특정 기종이 아니라 통신 방식 이름(Medibus)으로 인식한다. 같은 통신 방식을 쓰는 기종들은 회선만 봐서는 구별할 수 없으므로, 근거 없이 모델명을 단정하지 않는 편이 낫다고 판단했다. 기종 이름이 필요하면 설정에서 직접 고르면 된다. 1.19.21 Added: Dräger 장비가 스스로 알려주는 기종명을 로그에 남긴다. 설정 파일의 장비 이름은 사람이 적어 넣은 것이라 실제 기종을 보장하지 않는데, 기종에 따라 낼 수 있는 파형이 다르므로(예: Fabius 는 기도압과 유량은 있고 CO2 가 없다) 무엇이 연결돼 있는지 아는 것이 문제 해결의 출발점이다. 통신이 시작될 때 한 번 남는다. 1.19.20 Fixed: Dräger 마취기 가운데 파형 기능이 없는 기종에서 데이터가 전혀 수집되지 않던 문제. 연결은 된 것처럼 보이지만 아무 값도 들어오지 않는다. VR 이 장비를 확인한 뒤 파형 설정을 물어보고 그 답을 받아야만 데이터 요청을 시작하도록 돼 있었는데, 파형 기능이 없는 기종은 그 질문에 답하지 않으므로 요청이 영영 시작되지 않았다. 연결 유지 신호도 함께 멎어 장비가 통신을 끊고 다시 연결하기를 반복한다. 이제 장비 확인 직후부터 데이터를 요청한다. 파형 응답이 없으면 그 기종의 정상 동작이므로 숫자값만 수집하며 로그에 한 번 알린다. Fabius GS 는 규격상 파형 기능이 없는 대표적인 기종이다 (University of Washington 제보). Added: SyncPulseSerial 동기 펄스 생성기 지원. 여러 장비의 기록을 나중에 맞추기 위한 공통 번호를 시리얼로 보내는 장치로, 1초마다 6자리 카운터를 보낸다. PULSE 트랙에 그 번호가 기록된다. 시작이나 끝을 알리는 문자가 없는 형식이라, 메시지 사이의 침묵을 경계로 삼고 카운터가 1씩 늘어나는지 확인해 맞는 장비인지 판단한다. 자동 판별 대상에도 포함돼 있고, 환자 모니터와 같은 자리에 함께 연결해도 서로 방해하지 않는다. 1.19.19 Added: Dräger 인공호흡기·마취기가 파형에 함께 실어 보내는 호흡 위상 신호를 기록한다. 장비가 흡기와 호기 전환 시점을 직접 알려주는 것이라 파형에서 역산하는 것보다 정확하다. VENT_PHASE 트랙에 흡기 시작 1, 호기 시작 0 으로 남는다. 시각은 파형과 같은 시간축을 쓴다. 종전에는 이 신호를 읽지 않고 버렸다. Added: 환기 모드를 기록한다(VENT_MODE). 모드는 VC-CMV, PC-BIPAP, SPN-CPAP 처럼 압력조절인지 용적조절인지를 알려주어 파형 해석에 직접 쓰인다. 장비가 띄우고 있는 다른 안내 문구도 VENT_MSG 로 남는다. VR 이 그동안 이 정보를 담은 명령을 아예 보내지 않고 있었다 — 이제 측정값·설정과 함께 번갈아 요청한다. 표기가 장비나 언어에 따라 달라도 되도록 문구가 아니라 규격 코드로 판별한다. Added: 파형이 서로 맞는지 확인할 근거를 로그에 남긴다. 용적은 유량을 적분한 것이므로 둘의 관계를 수치로 기록해 둔다. 다만 이것으로 파형 이름을 바꾸거나 경고하지는 않는다 — 용적조절 환기에서는 기도압도 용적에 거의 비례해서 이 수치만으로는 둘을 구별할 수 없기 때문이다. 1.19.18 Added: Y케이블로 엿보는 Dräger 연결에서 파형 종류를 자동으로 알아낸다. Y케이블 배선에 따라 장비가 보내는 것만 보이는 방과, 반대편 장치가 보내는 것까지 보이는 방이 있는데, 후자에서는 그 장치가 파형을 요청하는 내용이 VR 에도 보인다. 거기에 순서가 그대로 들어 있으므로 추정 없이 확정한다 (실제로 수술장 두 방을 재보니 한 방은 양방향, 한 방은 단방향이었다). 단방향인 방은 종전대로 wavs= 로 적어주거나 도착 순서 추정을 쓴다. Fixed: 엿보는 연결인데도 VR 이 자기가 고른 파형 순서를 적용해 이름이 어긋날 수 있던 문제 — 회선에 다른 장치가 있는지를 통신 내용으로 판별해(보낸 적 없는 명령의 응답이 보이면 다른 장치가 있는 것이다) 그런 경우 순서를 스스로 정하지 않는다. 어느 모드로 동작 중인지는 로그에 남는다. 1.19.17 Added: Y케이블로 엿보기만 하는 Dräger 연결에서 파형 이름을 확정할 수 있다. 이런 연결은 VR 이 장비에 아무것도 보낼 수 없어 어떤 파형이 어떤 순서로 오는지 물어볼 수가 없고, 그래서 도착 순서대로 추정해 기록해 왔다(이름이 실제와 다를 수 있었다). 이제 vr.conf 의 장비 항목에 wavs= 로 그 방의 순서를 적어두면 그대로 사용한다. 순서는 그 장비에 연결된 다른 장치가 정한 것이라 병원에서 알 수 있는 값이다. 예: wavs=AWP,AWF. 적지 않으면 종전과 같이 도착 순서로 추정하며, 추정 중이라는 사실과 확정하는 방법을 로그에 남긴다. 직결 연결에서는 종전대로 VR 이 직접 물어보므로 이 설정이 없어도 정확하다. Added: 회선에서 처음 보는 종류의 통신 프레임을 한 번씩 로그에 남긴다. 어떤 장치가 무엇을 주고받는지 현장에서 확인할 수 있어 연결 문제 진단에 쓴다. 1.19.16 Fixed: 1.19.15 에서 Y케이블로 엿보기만 하는 연결(서울대병원 수술장의 Dräger 전부가 이 방식)의 파형이 기록되지 않던 문제 — 1.19.15 는 VR 이 직접 요청한 파형만 기록하도록 바뀌었는데, 관찰 전용 연결에서는 VR 이 장비에 아무것도 보낼 수 없어(송신선이 끊겨 있다) 요청 자체를 못 하므로 들어오는 파형을 전부 버렸다. 이제 이런 연결에서는 1.19.14 이전과 같이 도착 순서대로 기록한다. 다른 장치가 설정해 둔 순서는 알 수 없으므로 이름이 실제와 다를 수 있고, 그 사실을 로그에 함께 남긴다. 1.19.15 를 이미 설치한 곳은 이 버전으로 올려야 한다. 1.19.15 Fixed: Dräger 마취기·인공호흡기(MEDIBUS)에서 파형이 엉뚱한 이름으로 기록되던 문제. 장비가 보내는 파형 묶음의 첫 바이트에는 “몇 번째로 요청한 파형이 들어있는가” 가 비트로 표시되는데, VR 은 그 비트를 세기만 하고 도착 순서대로 미리 정해둔 트랙(CO2, AWP, AWF)에 넣었다. 장비가 요청한 파형 중 일부만 보내면 그만큼 밀려 기록돼, 예를 들어 기도압(AWP)만 보내는 인공호흡기에서 그 파형이 CO2 로 저장됐다 (Evita V600 실측 제보). 이제 비트를 해석해 요청한 순서와 맞춰 넣는다. Fixed: 요청하는 파형이 CO2 와 기도압 두 가지로 고정돼 있어, 장비가 유량(Flow)이나 용적(Volume) 파형을 제공해도 받지 못했다. 이제 장비에게 먼저 제공 가능한 파형 목록을 물어보고 그중에서 요청한다. Added: vr.conf 의 장비 설정에 wavs= 로 받을 파형을 고를 수 있다 (예: wavs=AWP,AWF,VOL,CO2). 비워두면 장비가 제공하는 것을 앞에서부터 최대 4개까지 자동으로 받는다. 4개인 이유는 한 묶음에 최대 4개까지만 표시할 수 있기 때문이다. 지원 이름: AWP AWF PLETH VOL O2 CO2 CO2_KPA CO2_PCT AGENT HAL ENF ISO DES SEV PTRACH AWF_DEV (16진 코드도 가능). 파형을 늘리면 회선 대역을 더 쓰므로 9600bps 의 80% 를 넘으면 로그로 경고한다. 어떤 파형이 실제로 들어오고 있는지도 로그에 남겨 현장에서 확인할 수 있다. 1.19.14 Added: 서버에서 VR 로 내려보내는 명령(원격 시간 동기, 원격 업그레이드, 재시작, 설정 변경 등)을 다시 사용할 수 있다. 1.18.52 에서 이 경로를 통째로 막았는데, 시간 동기와 원격 업그레이드가 꼭 필요한 기관이 있어 되살렸다. 다만 원격으로 프로그램을 바꾸거나 설정을 덮어쓰는 것은 그 자체로 위험하므로 기본값은 여전히 전부 차단이고, vr.conf 에 SERVER_CONTROL 로 허용할 명령을 직접 적어야 동작한다 (예: SERVER_CONTROL=dt 는 시간 동기만, SERVER_CONTROL=dt,update 는 시간 동기와 업그레이드만, SERVER_CONTROL=1 은 전부 허용). 사용 가능한 명령은 update restart reboot edit_conf new_bed del_bed edit_bed dt add_event. 시간 동기는 예전과 달리 녹화 중에는 시계를 건너뛰지 않는다 — 녹화 중 시계를 갑자기 옮기면 파형에 공백이 생기거나 기록이 겹치므로, 녹화 중이면 서서히 당겨 맞추고(슬루) 녹화가 없을 때만 한번에 맞춘다. 2초 이내 차이는 무시한다. 예전 구현이 10분 이상 차이날 때 프로그램을 강제 종료하던 동작도 없앴다. 1.19.13 Fixed: 웹 모니터링 화면에서 EEG 파형이 0.1초 정도씩 끊겨 보이던 문제 — 화면 전송용으로 파형을 줄일 때 원본 주파수가 나누어떨어지지 않으면 일부 구간이 비어 그만큼 빈칸으로 보였다. 나누어떨어지는 비율로만 줄이도록 수정. 녹화 파일에는 원래 문제가 없었다(표시만의 문제). 1.19.12 Fixed: Dräger 마취기 연동 안정화 보완 — 시리얼 회선 자동판별 중 잡음이 잘못된 데이터로 오인될 여지를 차단. 1.19.11 Fixed: Dräger 마취기(Atlan 등 MEDIBUS 장비)에서 통신 오류 알람(COM1 failure)이 반복되고 파형이 전혀 기록되지 않던 문제. 원인 세 가지를 모두 수정했다. (1) 장비는 3초 안에 아무 신호라도 받지 못하면 연결을 끊는데, VR 이 5초마다 데이터를 요청하는 것 외에 보내는 것이 없어 매번 연결이 끊기고 재접속을 반복했다 — 규격이 정한 연결 유지 신호(NOP)를 2초마다 보내도록 했다(데이터 요청 주기는 그대로). (2) 장비가 응답 도중에 다른 명령을 끼워 보내는 것이 규격상 허용되는데, VR 이 그때 받던 응답을 통째로 버려 파형 설정 응답을 매번 놓쳤다 — 끼어든 부분을 건너뛰고 원래 응답을 이어받도록 했다. (3) 장비가 보내는 요청 하나에 VR 이 응답하지 않아 통신이 끊기던 것도 수정. 파형·수치가 정상 기록되고 알람이 사라진다. 1.19.10 Fixed: 하루 한 번의 자동 재시작 시점에 수집이 통째로 멎어 사람이 손을 대기 전까지 복구되지 않던 문제(or-106, 104분 무데이터) — 종료 과정이 끝나지 않아 프로그램이 죽지 않았고, 죽지 않으니 자동 재시작도 일어나지 않았다. 주원인은 시리얼 회선 자동판별(AUTO_DETECT)이 종료 신호를 보지 않고 스윕(보레이트 6종 x 포맷 8종)을 끝까지 돌던 것으로, 무음 회선에서 종료에 2분이 걸려 정상 종료 유예시간을 넘겼다. 이제 종료 즉시 판별을 중단한다(실측 2분 -> 3초). 함께 종료 중 스레드 정리가 서로를 기다릴 수 있던 두 곳을 제거하고, 그럼에도 종료가 20초 안에 끝나지 않으면 강제 종료해 반드시 재시작되도록 안전장치를 넣었다. 녹화 파일은 5초마다 저장되므로 유실되지 않는다. 1.19.9 Fixed: 큰 녹화 파일(약 100MB 이상)을 서버로 올릴 때 프로그램이 메모리 부족으로 강제 종료되고, 재시작 후 같은 파일을 다시 시도하다 무한 반복되던 문제 — 업로드 본문을 메모리에 통째로 만들지 않고 디스크에서 흘려보내도록 변경(100MB 파일 기준 최대 메모리 208MB → 14MB). 큰 파일이 한 개라도 쌓이면 그 장비의 이후 업로드가 전부 막히던 증상이 사라진다. 1.19.8 Fixed: 자동 파일 자르기(CUT_FILE) 를 쓰는 베드에서 환자가 계속 연결돼 있는데도 녹화 파일이 PT_WAITING_TIME(기본 5분) 마다 잘리던 문제 — 웹 모니터링 전송 후 메모리를 비우는 과정에서 환자 연결 판정에 쓰이는 최근 구간까지 지워져 "환자가 나갔다" 로 오판하던 것. 이제 판정에 필요한 구간은 보존한다. 파일이 5분마다 끊기고 그때마다 수 초가 유실되던 증상이 사라진다. 1.19.7 Added: GE Corometrics(분만 태아감시) 모체 vitals 지원 — 기존엔 태아 심박(HR1/HR2)·자궁수축(UACT)만 기록됐으나 이제 같은 장비가 함께 보내는 모체 SpO2/맥박(PR)/NIBP(수축·이완·평균)도 한 파일에 기록. Fixed: 프로세스 종료(서비스 매니저의 정상 종료 요청, SIGTERM) 시 즉시 종료되지 않고 강제 종료되던 문제 — 이제 SIGTERM 을 받아 녹화 버퍼를 flush 하고 스레드를 정리한 뒤 정상 종료. 1.19.5 Fixed: CUT_HOURLY 가 동작하지 않는 것처럼 보이던 문제의 실체 — k3s 컨테이너에서 기본 저장 경로가 $HOME/recordings(임시 레이어)로 잡혀, 매일 자정 재시작 때 업로드 안 된 하루치 녹화가 통째로 사라짐. hostPath 마운트 /recordings 가 있으면 그것을 기본 저장 경로로 사용. Fixed: 종료(daily restart) 시 멀티베드 TCP 노드가 terminate(exit=133)/SIGSEGV(139) 로 죽던 문제. Linux finalize 가 device 스레드를 정지하지 않고 반환해 exit() 전역 소멸자가 살아있는 PortRouter accept_thread 를 파괴하며 std::terminate — DISPLAY::quiesce_threads + PortRouter::shutdown 으로 정리 후 종료 (코어 백트레이스로 확정, 로컬 재현 후 수정 검증). Fixed: Link+/Agilia 파서가 시리얼 노이즈(size byte 0 또는 헤더 없는 잡음 8byte)에 역전된 반복자 범위로 vector 를 생성하다 std::length_error 로 죽던 문제(or-110) — sentinel 상태 파싱 봉쇄 + 재동기화. Fixed: FILENAME_TEMPLATE 의 %Y(4자리 연도)가 "0126" 으로 나오던 문제 (tm_year+1900 누락). 1.19.4 Fixed: Link+ 등 재접속이 잦은 장비에서 시리얼 포트를 재오픈할 때 "terminate called without an active exception" 으로 프로세스가 죽던 문제(exit=133). reading_thread_func 이 스스로 반환해도 std::thread 객체는 joinable 로 남는데, open() 이 join 없이 새 스레드를 대입해 std::terminate 가 호출됨 → 스레드 생성 전에 이전 reading/parsing 스레드를 join 후 재생성. Fixed: headless/AUTO_DETECT 모드에서 실제 장비가 없을 때 Demo 장비가 생성돼 가짜 vital 트랙 38종이 업로드되던 문제 → --console 또는 AUTO_DETECT 시 Demo 미생성. 1.19.3 Fixed: 여러 베드를 한 프로세스가 TCP 로 수집하는 구성에서 클라이언트 재접속이 잦으면 프로세스가 SIGSEGV 로 죽던 문제(accept_handoff 가 replace 시 옛 소켓을 즉시 close 해 recv 중인 reader 와 경합, fd 번호 재사용). tcp reader 를 poll(200ms) 게이트 뒤의 recv 로 재구성하고 replace 는 shutdown 후 소유권을 reader/close 로 단일화. SNUH 회복실 VRN 15대에서 재현. Fixed: ICU 등 Intellivue 다수 베드에서 반복되던 SIGSEGV(139) 두 계열의 use-after-free. (1) DISPLAY::add_rec 이 레코드를 트랙에 push 한 뒤 저장했는데, push 직후 p 가 다른 스레드에 공유되고 Linux 의 메모리 보존 루프(600초 이전 레코드 delete)가 시계 어긋난 모니터에서 방금 넣은 레코드를 즉시 evict 해, 이어지는 p->save() 가 해제된 메모리에 가상 호출 → 저장을 push 앞으로 옮겨 공유 후 p 미접근. (2) DISPLAY::del_trk 이 트랙을 목록에서 뺀 직후 raw delete 해, 파싱 스레드의 캐시 포인터·장비 이름맵·recorded_trks 가 죽은 포인터를 참조 (특히 IAP 채널변경의 del 직후 add 가 해제 포인터 반환) → DEVICE 규칙처럼 살아있는 동안 삭제 금지, ~DISPLAY 에서 일괄 해제. picu-11 코어 덤프로 크래시 지점(add_rec vtable 간접호출) 확정. SNUH fleet ~100 노드 실배포에서 기대 크래시 다수 대비 실제 0, 미배포 노드는 계속 크래시. Fixed: Linux from_utf16 이 locale 의존·비스레드안전 wcstombs 를 쓰고 UTF-16 을 UTF-32 wchar_t 로 오해석하던 문제 → 수동 UTF-8 인코더(서로게이트 페어 재조립, 무효값 U+FFFD)로 교체. Added: AUTO_DETECT=1(vr.conf) 시리얼 장비 자동 판별. 회선 측정(보레이트·프레임폭·패리티) → 후보 필터(3축 일치, 베드당 클래스 배타, 포트 사전확률, 판별캐시) → 트랙 수확량 경쟁. 드라이버 무수정, 미설정 시 유휴 대기라 기존 설정 무영향. SNUH 수술장·ICU 실측 오탐 0.
1.19.25
1.19.25 Fixed: SyncPulseSerial(동기 펄스 생성기)에서 1초 간격의 펄스가 2초마다 하나씩만 기록되거나, 보드 종류에 따라 아예 한 값도 기록되지 않던 문제. 두 증상 모두 원인이 하나다 — 이 장비는 시작·끝을 알리는 문자 없이 6자리 숫자만 흘려보내므로 VR 이 “잠깐의 침묵” 을 메시지 경계로 삼고 있었는데, 침묵은 회선이 아니라 컴퓨터 사정으로도 생긴다. 운영체제가 6바이트를 몇 번에 나눠 올려주고 그 사이가 벌어지면 앞자리가 버려져 이후 모든 값이 한 칸씩 밀리고, 한 번 밀리면 스스로 회복하지 못해 아무것도 기록되지 않는다(구형 Arduino UNO 에서 재현). 반대로 두 메시지가 한꺼번에 올라오면 둘 다 같은 시각으로 찍혀 1초짜리 펄스가 2초마다 하나 있는 것처럼 보인다(UNO R4 에서 재현). 이제 경계를 시간이 아니라 내용으로 찾는다 — 카운터가 정확히 1씩 늘어나는 자리를 스스로 찾아 맞추므로 회선이 어떻게 쪼개져 들어오든 영향을 받지 않고, 어긋나도 다음 펄스에서 다시 맞춘다. 한 번에 여러 펄스가 올라온 경우에는 카운터 간격만큼 시각을 되돌려 각각 제 시각에 기록한다. 세 가지 조건(정상·쪼개짐·뭉침) 재현 시험으로 확인했다 (Nord University AniLab 제보). Fixed: Windows 에서 데이터를 띄엄띄엄 보내는 시리얼 장비의 값이 최대 한 주기 늦게 들어오던 문제. 문자 도착 알림을 기다리는 동안이 아니라 그 직전 대기 중에 데이터가 통째로 들어와 버리면, 새로 도착하는 문자가 없어 알림이 오지 않고 이미 들어온 데이터가 다음 전송이 올 때까지 그대로 대기했다. 이제 알림이 없어도 수신 버퍼를 직접 확인한다. 초당 수십~수백 바이트를 보내는 대부분의 장비에서는 드러나지 않던 문제이지만, 1초에 한 번처럼 드물게 보내는 장비에서는 두 전송이 한 번에 처리되어 시각이 뭉쳤다. 1.19.24 Fixed: Windows 에서 SyncPulseSerial(Arduino UNO R4)의 데이터가 전혀 들어오지 않거나 한참 뒤에야 간헐적으로 들어오던 문제. Arduino 의 USB 시리얼은 컴퓨터 쪽이 준비 신호(DTR)를 올려야만 데이터를 내보내는데, VR 이 Windows 에서 포트를 열 때 이 신호를 내린 채로 열고 있었다. 시리얼 터미널 프로그램들은 이 신호를 기본으로 올리기 때문에 터미널에서는 잘 보이는데 VR 에서만 안 보이는 형태로 나타났다 (Win11/Win7 3대 실측, Nord University AniLab 제보). 장비 연결 상태는 정상으로 표시되므로 화면만 봐서는 알 수 없는 문제였다. 다른 시리얼 장비들의 동작은 그대로다 — 이 신호에 민감한 장비가 있을 수 있어 Arduino 계열 드라이버에서만 올린다. 1.19.23 Fixed: SyncPulseSerial 의 PULSE 값이 화면에 보이지 않던 문제. 값은 정상적으로 수신되고 파일에도 기록되고 있었는데, 화면 눈금 범위가 카운터의 최대값(864000)으로 잡혀 있어 테스트 초반의 작은 값들이 트랙 맨 바닥 1픽셀에 눌려 보이지 않았다. 이제 매초 가운데에 뚜렷한 선으로 표시된다. 기록된 값 자체는 이전 버전에서도 온전했다(표시만의 문제). 1.19.22 Added: Dräger 마취기를 기종 이름으로 고를 수 있다. 장비 목록에 Fabius 가 추가됐고, 목록에 없는 Dräger 를 쓰는 곳을 위해 Medibus 라는 일반 항목도 함께 넣었다. Primus, Fabius, Medibus 는 통신 방식이 완전히 같고 이름만 다르므로 어느 것을 골라도 동작은 같지만, 실제 기종을 고르면 설정 파일과 화면이 무엇이 연결돼 있는지 스스로 설명하게 된다. 기존에 Primus 로 설정해 둔 곳은 그대로 두면 된다. Changed: 시리얼 장비 자동 판별이 마취기를 특정 기종이 아니라 통신 방식 이름(Medibus)으로 인식한다. 같은 통신 방식을 쓰는 기종들은 회선만 봐서는 구별할 수 없으므로, 근거 없이 모델명을 단정하지 않는 편이 낫다고 판단했다. 기종 이름이 필요하면 설정에서 직접 고르면 된다. 1.19.21 Added: Dräger 장비가 스스로 알려주는 기종명을 로그에 남긴다. 설정 파일의 장비 이름은 사람이 적어 넣은 것이라 실제 기종을 보장하지 않는데, 기종에 따라 낼 수 있는 파형이 다르므로(예: Fabius 는 기도압과 유량은 있고 CO2 가 없다) 무엇이 연결돼 있는지 아는 것이 문제 해결의 출발점이다. 통신이 시작될 때 한 번 남는다. 1.19.20 Fixed: Dräger 마취기 가운데 파형 기능이 없는 기종에서 데이터가 전혀 수집되지 않던 문제. 연결은 된 것처럼 보이지만 아무 값도 들어오지 않는다. VR 이 장비를 확인한 뒤 파형 설정을 물어보고 그 답을 받아야만 데이터 요청을 시작하도록 돼 있었는데, 파형 기능이 없는 기종은 그 질문에 답하지 않으므로 요청이 영영 시작되지 않았다. 연결 유지 신호도 함께 멎어 장비가 통신을 끊고 다시 연결하기를 반복한다. 이제 장비 확인 직후부터 데이터를 요청한다. 파형 응답이 없으면 그 기종의 정상 동작이므로 숫자값만 수집하며 로그에 한 번 알린다. Fabius GS 는 규격상 파형 기능이 없는 대표적인 기종이다 (University of Washington 제보). Added: SyncPulseSerial 동기 펄스 생성기 지원. 여러 장비의 기록을 나중에 맞추기 위한 공통 번호를 시리얼로 보내는 장치로, 1초마다 6자리 카운터를 보낸다. PULSE 트랙에 그 번호가 기록된다. 시작이나 끝을 알리는 문자가 없는 형식이라, 메시지 사이의 침묵을 경계로 삼고 카운터가 1씩 늘어나는지 확인해 맞는 장비인지 판단한다. 자동 판별 대상에도 포함돼 있고, 환자 모니터와 같은 자리에 함께 연결해도 서로 방해하지 않는다. 1.19.19 Added: Dräger 인공호흡기·마취기가 파형에 함께 실어 보내는 호흡 위상 신호를 기록한다. 장비가 흡기와 호기 전환 시점을 직접 알려주는 것이라 파형에서 역산하는 것보다 정확하다. VENT_PHASE 트랙에 흡기 시작 1, 호기 시작 0 으로 남는다. 시각은 파형과 같은 시간축을 쓴다. 종전에는 이 신호를 읽지 않고 버렸다. Added: 환기 모드를 기록한다(VENT_MODE). 모드는 VC-CMV, PC-BIPAP, SPN-CPAP 처럼 압력조절인지 용적조절인지를 알려주어 파형 해석에 직접 쓰인다. 장비가 띄우고 있는 다른 안내 문구도 VENT_MSG 로 남는다. VR 이 그동안 이 정보를 담은 명령을 아예 보내지 않고 있었다 — 이제 측정값·설정과 함께 번갈아 요청한다. 표기가 장비나 언어에 따라 달라도 되도록 문구가 아니라 규격 코드로 판별한다. Added: 파형이 서로 맞는지 확인할 근거를 로그에 남긴다. 용적은 유량을 적분한 것이므로 둘의 관계를 수치로 기록해 둔다. 다만 이것으로 파형 이름을 바꾸거나 경고하지는 않는다 — 용적조절 환기에서는 기도압도 용적에 거의 비례해서 이 수치만으로는 둘을 구별할 수 없기 때문이다. 1.19.18 Added: Y케이블로 엿보는 Dräger 연결에서 파형 종류를 자동으로 알아낸다. Y케이블 배선에 따라 장비가 보내는 것만 보이는 방과, 반대편 장치가 보내는 것까지 보이는 방이 있는데, 후자에서는 그 장치가 파형을 요청하는 내용이 VR 에도 보인다. 거기에 순서가 그대로 들어 있으므로 추정 없이 확정한다 (실제로 수술장 두 방을 재보니 한 방은 양방향, 한 방은 단방향이었다). 단방향인 방은 종전대로 wavs= 로 적어주거나 도착 순서 추정을 쓴다. Fixed: 엿보는 연결인데도 VR 이 자기가 고른 파형 순서를 적용해 이름이 어긋날 수 있던 문제 — 회선에 다른 장치가 있는지를 통신 내용으로 판별해(보낸 적 없는 명령의 응답이 보이면 다른 장치가 있는 것이다) 그런 경우 순서를 스스로 정하지 않는다. 어느 모드로 동작 중인지는 로그에 남는다. 1.19.17 Added: Y케이블로 엿보기만 하는 Dräger 연결에서 파형 이름을 확정할 수 있다. 이런 연결은 VR 이 장비에 아무것도 보낼 수 없어 어떤 파형이 어떤 순서로 오는지 물어볼 수가 없고, 그래서 도착 순서대로 추정해 기록해 왔다(이름이 실제와 다를 수 있었다). 이제 vr.conf 의 장비 항목에 wavs= 로 그 방의 순서를 적어두면 그대로 사용한다. 순서는 그 장비에 연결된 다른 장치가 정한 것이라 병원에서 알 수 있는 값이다. 예: wavs=AWP,AWF. 적지 않으면 종전과 같이 도착 순서로 추정하며, 추정 중이라는 사실과 확정하는 방법을 로그에 남긴다. 직결 연결에서는 종전대로 VR 이 직접 물어보므로 이 설정이 없어도 정확하다. Added: 회선에서 처음 보는 종류의 통신 프레임을 한 번씩 로그에 남긴다. 어떤 장치가 무엇을 주고받는지 현장에서 확인할 수 있어 연결 문제 진단에 쓴다. 1.19.16 Fixed: 1.19.15 에서 Y케이블로 엿보기만 하는 연결(서울대병원 수술장의 Dräger 전부가 이 방식)의 파형이 기록되지 않던 문제 — 1.19.15 는 VR 이 직접 요청한 파형만 기록하도록 바뀌었는데, 관찰 전용 연결에서는 VR 이 장비에 아무것도 보낼 수 없어(송신선이 끊겨 있다) 요청 자체를 못 하므로 들어오는 파형을 전부 버렸다. 이제 이런 연결에서는 1.19.14 이전과 같이 도착 순서대로 기록한다. 다른 장치가 설정해 둔 순서는 알 수 없으므로 이름이 실제와 다를 수 있고, 그 사실을 로그에 함께 남긴다. 1.19.15 를 이미 설치한 곳은 이 버전으로 올려야 한다. 1.19.15 Fixed: Dräger 마취기·인공호흡기(MEDIBUS)에서 파형이 엉뚱한 이름으로 기록되던 문제. 장비가 보내는 파형 묶음의 첫 바이트에는 “몇 번째로 요청한 파형이 들어있는가” 가 비트로 표시되는데, VR 은 그 비트를 세기만 하고 도착 순서대로 미리 정해둔 트랙(CO2, AWP, AWF)에 넣었다. 장비가 요청한 파형 중 일부만 보내면 그만큼 밀려 기록돼, 예를 들어 기도압(AWP)만 보내는 인공호흡기에서 그 파형이 CO2 로 저장됐다 (Evita V600 실측 제보). 이제 비트를 해석해 요청한 순서와 맞춰 넣는다. Fixed: 요청하는 파형이 CO2 와 기도압 두 가지로 고정돼 있어, 장비가 유량(Flow)이나 용적(Volume) 파형을 제공해도 받지 못했다. 이제 장비에게 먼저 제공 가능한 파형 목록을 물어보고 그중에서 요청한다. Added: vr.conf 의 장비 설정에 wavs= 로 받을 파형을 고를 수 있다 (예: wavs=AWP,AWF,VOL,CO2). 비워두면 장비가 제공하는 것을 앞에서부터 최대 4개까지 자동으로 받는다. 4개인 이유는 한 묶음에 최대 4개까지만 표시할 수 있기 때문이다. 지원 이름: AWP AWF PLETH VOL O2 CO2 CO2_KPA CO2_PCT AGENT HAL ENF ISO DES SEV PTRACH AWF_DEV (16진 코드도 가능). 파형을 늘리면 회선 대역을 더 쓰므로 9600bps 의 80% 를 넘으면 로그로 경고한다. 어떤 파형이 실제로 들어오고 있는지도 로그에 남겨 현장에서 확인할 수 있다. 1.19.14 Added: 서버에서 VR 로 내려보내는 명령(원격 시간 동기, 원격 업그레이드, 재시작, 설정 변경 등)을 다시 사용할 수 있다. 1.18.52 에서 이 경로를 통째로 막았는데, 시간 동기와 원격 업그레이드가 꼭 필요한 기관이 있어 되살렸다. 다만 원격으로 프로그램을 바꾸거나 설정을 덮어쓰는 것은 그 자체로 위험하므로 기본값은 여전히 전부 차단이고, vr.conf 에 SERVER_CONTROL 로 허용할 명령을 직접 적어야 동작한다 (예: SERVER_CONTROL=dt 는 시간 동기만, SERVER_CONTROL=dt,update 는 시간 동기와 업그레이드만, SERVER_CONTROL=1 은 전부 허용). 사용 가능한 명령은 update restart reboot edit_conf new_bed del_bed edit_bed dt add_event. 시간 동기는 예전과 달리 녹화 중에는 시계를 건너뛰지 않는다 — 녹화 중 시계를 갑자기 옮기면 파형에 공백이 생기거나 기록이 겹치므로, 녹화 중이면 서서히 당겨 맞추고(슬루) 녹화가 없을 때만 한번에 맞춘다. 2초 이내 차이는 무시한다. 예전 구현이 10분 이상 차이날 때 프로그램을 강제 종료하던 동작도 없앴다. 1.19.13 Fixed: 웹 모니터링 화면에서 EEG 파형이 0.1초 정도씩 끊겨 보이던 문제 — 화면 전송용으로 파형을 줄일 때 원본 주파수가 나누어떨어지지 않으면 일부 구간이 비어 그만큼 빈칸으로 보였다. 나누어떨어지는 비율로만 줄이도록 수정. 녹화 파일에는 원래 문제가 없었다(표시만의 문제). 1.19.12 Fixed: Dräger 마취기 연동 안정화 보완 — 시리얼 회선 자동판별 중 잡음이 잘못된 데이터로 오인될 여지를 차단. 1.19.11 Fixed: Dräger 마취기(Atlan 등 MEDIBUS 장비)에서 통신 오류 알람(COM1 failure)이 반복되고 파형이 전혀 기록되지 않던 문제. 원인 세 가지를 모두 수정했다. (1) 장비는 3초 안에 아무 신호라도 받지 못하면 연결을 끊는데, VR 이 5초마다 데이터를 요청하는 것 외에 보내는 것이 없어 매번 연결이 끊기고 재접속을 반복했다 — 규격이 정한 연결 유지 신호(NOP)를 2초마다 보내도록 했다(데이터 요청 주기는 그대로). (2) 장비가 응답 도중에 다른 명령을 끼워 보내는 것이 규격상 허용되는데, VR 이 그때 받던 응답을 통째로 버려 파형 설정 응답을 매번 놓쳤다 — 끼어든 부분을 건너뛰고 원래 응답을 이어받도록 했다. (3) 장비가 보내는 요청 하나에 VR 이 응답하지 않아 통신이 끊기던 것도 수정. 파형·수치가 정상 기록되고 알람이 사라진다. 1.19.10 Fixed: 하루 한 번의 자동 재시작 시점에 수집이 통째로 멎어 사람이 손을 대기 전까지 복구되지 않던 문제(or-106, 104분 무데이터) — 종료 과정이 끝나지 않아 프로그램이 죽지 않았고, 죽지 않으니 자동 재시작도 일어나지 않았다. 주원인은 시리얼 회선 자동판별(AUTO_DETECT)이 종료 신호를 보지 않고 스윕(보레이트 6종 x 포맷 8종)을 끝까지 돌던 것으로, 무음 회선에서 종료에 2분이 걸려 정상 종료 유예시간을 넘겼다. 이제 종료 즉시 판별을 중단한다(실측 2분 -> 3초). 함께 종료 중 스레드 정리가 서로를 기다릴 수 있던 두 곳을 제거하고, 그럼에도 종료가 20초 안에 끝나지 않으면 강제 종료해 반드시 재시작되도록 안전장치를 넣었다. 녹화 파일은 5초마다 저장되므로 유실되지 않는다. 1.19.9 Fixed: 큰 녹화 파일(약 100MB 이상)을 서버로 올릴 때 프로그램이 메모리 부족으로 강제 종료되고, 재시작 후 같은 파일을 다시 시도하다 무한 반복되던 문제 — 업로드 본문을 메모리에 통째로 만들지 않고 디스크에서 흘려보내도록 변경(100MB 파일 기준 최대 메모리 208MB → 14MB). 큰 파일이 한 개라도 쌓이면 그 장비의 이후 업로드가 전부 막히던 증상이 사라진다. 1.19.8 Fixed: 자동 파일 자르기(CUT_FILE) 를 쓰는 베드에서 환자가 계속 연결돼 있는데도 녹화 파일이 PT_WAITING_TIME(기본 5분) 마다 잘리던 문제 — 웹 모니터링 전송 후 메모리를 비우는 과정에서 환자 연결 판정에 쓰이는 최근 구간까지 지워져 "환자가 나갔다" 로 오판하던 것. 이제 판정에 필요한 구간은 보존한다. 파일이 5분마다 끊기고 그때마다 수 초가 유실되던 증상이 사라진다. 1.19.7 Added: GE Corometrics(분만 태아감시) 모체 vitals 지원 — 기존엔 태아 심박(HR1/HR2)·자궁수축(UACT)만 기록됐으나 이제 같은 장비가 함께 보내는 모체 SpO2/맥박(PR)/NIBP(수축·이완·평균)도 한 파일에 기록. Fixed: 프로세스 종료(서비스 매니저의 정상 종료 요청, SIGTERM) 시 즉시 종료되지 않고 강제 종료되던 문제 — 이제 SIGTERM 을 받아 녹화 버퍼를 flush 하고 스레드를 정리한 뒤 정상 종료. 1.19.5 Fixed: CUT_HOURLY 가 동작하지 않는 것처럼 보이던 문제의 실체 — k3s 컨테이너에서 기본 저장 경로가 $HOME/recordings(임시 레이어)로 잡혀, 매일 자정 재시작 때 업로드 안 된 하루치 녹화가 통째로 사라짐. hostPath 마운트 /recordings 가 있으면 그것을 기본 저장 경로로 사용. Fixed: 종료(daily restart) 시 멀티베드 TCP 노드가 terminate(exit=133)/SIGSEGV(139) 로 죽던 문제. Linux finalize 가 device 스레드를 정지하지 않고 반환해 exit() 전역 소멸자가 살아있는 PortRouter accept_thread 를 파괴하며 std::terminate — DISPLAY::quiesce_threads + PortRouter::shutdown 으로 정리 후 종료 (코어 백트레이스로 확정, 로컬 재현 후 수정 검증). Fixed: Link+/Agilia 파서가 시리얼 노이즈(size byte 0 또는 헤더 없는 잡음 8byte)에 역전된 반복자 범위로 vector 를 생성하다 std::length_error 로 죽던 문제(or-110) — sentinel 상태 파싱 봉쇄 + 재동기화. Fixed: FILENAME_TEMPLATE 의 %Y(4자리 연도)가 "0126" 으로 나오던 문제 (tm_year+1900 누락). 1.19.4 Fixed: Link+ 등 재접속이 잦은 장비에서 시리얼 포트를 재오픈할 때 "terminate called without an active exception" 으로 프로세스가 죽던 문제(exit=133). reading_thread_func 이 스스로 반환해도 std::thread 객체는 joinable 로 남는데, open() 이 join 없이 새 스레드를 대입해 std::terminate 가 호출됨 → 스레드 생성 전에 이전 reading/parsing 스레드를 join 후 재생성. Fixed: headless/AUTO_DETECT 모드에서 실제 장비가 없을 때 Demo 장비가 생성돼 가짜 vital 트랙 38종이 업로드되던 문제 → --console 또는 AUTO_DETECT 시 Demo 미생성. 1.19.3 Fixed: 여러 베드를 한 프로세스가 TCP 로 수집하는 구성에서 클라이언트 재접속이 잦으면 프로세스가 SIGSEGV 로 죽던 문제(accept_handoff 가 replace 시 옛 소켓을 즉시 close 해 recv 중인 reader 와 경합, fd 번호 재사용). tcp reader 를 poll(200ms) 게이트 뒤의 recv 로 재구성하고 replace 는 shutdown 후 소유권을 reader/close 로 단일화. SNUH 회복실 VRN 15대에서 재현. Fixed: ICU 등 Intellivue 다수 베드에서 반복되던 SIGSEGV(139) 두 계열의 use-after-free. (1) DISPLAY::add_rec 이 레코드를 트랙에 push 한 뒤 저장했는데, push 직후 p 가 다른 스레드에 공유되고 Linux 의 메모리 보존 루프(600초 이전 레코드 delete)가 시계 어긋난 모니터에서 방금 넣은 레코드를 즉시 evict 해, 이어지는 p->save() 가 해제된 메모리에 가상 호출 → 저장을 push 앞으로 옮겨 공유 후 p 미접근. (2) DISPLAY::del_trk 이 트랙을 목록에서 뺀 직후 raw delete 해, 파싱 스레드의 캐시 포인터·장비 이름맵·recorded_trks 가 죽은 포인터를 참조 (특히 IAP 채널변경의 del 직후 add 가 해제 포인터 반환) → DEVICE 규칙처럼 살아있는 동안 삭제 금지, ~DISPLAY 에서 일괄 해제. picu-11 코어 덤프로 크래시 지점(add_rec vtable 간접호출) 확정. SNUH fleet ~100 노드 실배포에서 기대 크래시 다수 대비 실제 0, 미배포 노드는 계속 크래시. Fixed: Linux from_utf16 이 locale 의존·비스레드안전 wcstombs 를 쓰고 UTF-16 을 UTF-32 wchar_t 로 오해석하던 문제 → 수동 UTF-8 인코더(서로게이트 페어 재조립, 무효값 U+FFFD)로 교체. Added: AUTO_DETECT=1(vr.conf) 시리얼 장비 자동 판별. 회선 측정(보레이트·프레임폭·패리티) → 후보 필터(3축 일치, 베드당 클래스 배타, 포트 사전확률, 판별캐시) → 트랙 수확량 경쟁. 드라이버 무수정, 미설정 시 유휴 대기라 기존 설정 무영향. SNUH 수술장·ICU 실측 오탐 0.
1.19.24
1.19.24 Fixed: Windows 에서 SyncPulseSerial(Arduino UNO R4)의 데이터가 전혀 들어오지 않거나 한참 뒤에야 간헐적으로 들어오던 문제. Arduino 의 USB 시리얼은 컴퓨터 쪽이 준비 신호(DTR)를 올려야만 데이터를 내보내는데, VR 이 Windows 에서 포트를 열 때 이 신호를 내린 채로 열고 있었다. 시리얼 터미널 프로그램들은 이 신호를 기본으로 올리기 때문에 터미널에서는 잘 보이는데 VR 에서만 안 보이는 형태로 나타났다 (Win11/Win7 3대 실측, Nord University AniLab 제보). 장비 연결 상태는 정상으로 표시되므로 화면만 봐서는 알 수 없는 문제였다. 다른 시리얼 장비들의 동작은 그대로다 — 이 신호에 민감한 장비가 있을 수 있어 Arduino 계열 드라이버에서만 올린다. 1.19.23 Fixed: SyncPulseSerial 의 PULSE 값이 화면에 보이지 않던 문제. 값은 정상적으로 수신되고 파일에도 기록되고 있었는데, 화면 눈금 범위가 카운터의 최대값(864000)으로 잡혀 있어 테스트 초반의 작은 값들이 트랙 맨 바닥 1픽셀에 눌려 보이지 않았다. 이제 매초 가운데에 뚜렷한 선으로 표시된다. 기록된 값 자체는 이전 버전에서도 온전했다(표시만의 문제). 1.19.22 Added: Dräger 마취기를 기종 이름으로 고를 수 있다. 장비 목록에 Fabius 가 추가됐고, 목록에 없는 Dräger 를 쓰는 곳을 위해 Medibus 라는 일반 항목도 함께 넣었다. Primus, Fabius, Medibus 는 통신 방식이 완전히 같고 이름만 다르므로 어느 것을 골라도 동작은 같지만, 실제 기종을 고르면 설정 파일과 화면이 무엇이 연결돼 있는지 스스로 설명하게 된다. 기존에 Primus 로 설정해 둔 곳은 그대로 두면 된다. Changed: 시리얼 장비 자동 판별이 마취기를 특정 기종이 아니라 통신 방식 이름(Medibus)으로 인식한다. 같은 통신 방식을 쓰는 기종들은 회선만 봐서는 구별할 수 없으므로, 근거 없이 모델명을 단정하지 않는 편이 낫다고 판단했다. 기종 이름이 필요하면 설정에서 직접 고르면 된다. 1.19.21 Added: Dräger 장비가 스스로 알려주는 기종명을 로그에 남긴다. 설정 파일의 장비 이름은 사람이 적어 넣은 것이라 실제 기종을 보장하지 않는데, 기종에 따라 낼 수 있는 파형이 다르므로(예: Fabius 는 기도압과 유량은 있고 CO2 가 없다) 무엇이 연결돼 있는지 아는 것이 문제 해결의 출발점이다. 통신이 시작될 때 한 번 남는다. 1.19.20 Fixed: Dräger 마취기 가운데 파형 기능이 없는 기종에서 데이터가 전혀 수집되지 않던 문제. 연결은 된 것처럼 보이지만 아무 값도 들어오지 않는다. VR 이 장비를 확인한 뒤 파형 설정을 물어보고 그 답을 받아야만 데이터 요청을 시작하도록 돼 있었는데, 파형 기능이 없는 기종은 그 질문에 답하지 않으므로 요청이 영영 시작되지 않았다. 연결 유지 신호도 함께 멎어 장비가 통신을 끊고 다시 연결하기를 반복한다. 이제 장비 확인 직후부터 데이터를 요청한다. 파형 응답이 없으면 그 기종의 정상 동작이므로 숫자값만 수집하며 로그에 한 번 알린다. Fabius GS 는 규격상 파형 기능이 없는 대표적인 기종이다 (University of Washington 제보). Added: SyncPulseSerial 동기 펄스 생성기 지원. 여러 장비의 기록을 나중에 맞추기 위한 공통 번호를 시리얼로 보내는 장치로, 1초마다 6자리 카운터를 보낸다. PULSE 트랙에 그 번호가 기록된다. 시작이나 끝을 알리는 문자가 없는 형식이라, 메시지 사이의 침묵을 경계로 삼고 카운터가 1씩 늘어나는지 확인해 맞는 장비인지 판단한다. 자동 판별 대상에도 포함돼 있고, 환자 모니터와 같은 자리에 함께 연결해도 서로 방해하지 않는다. 1.19.19 Added: Dräger 인공호흡기·마취기가 파형에 함께 실어 보내는 호흡 위상 신호를 기록한다. 장비가 흡기와 호기 전환 시점을 직접 알려주는 것이라 파형에서 역산하는 것보다 정확하다. VENT_PHASE 트랙에 흡기 시작 1, 호기 시작 0 으로 남는다. 시각은 파형과 같은 시간축을 쓴다. 종전에는 이 신호를 읽지 않고 버렸다. Added: 환기 모드를 기록한다(VENT_MODE). 모드는 VC-CMV, PC-BIPAP, SPN-CPAP 처럼 압력조절인지 용적조절인지를 알려주어 파형 해석에 직접 쓰인다. 장비가 띄우고 있는 다른 안내 문구도 VENT_MSG 로 남는다. VR 이 그동안 이 정보를 담은 명령을 아예 보내지 않고 있었다 — 이제 측정값·설정과 함께 번갈아 요청한다. 표기가 장비나 언어에 따라 달라도 되도록 문구가 아니라 규격 코드로 판별한다. Added: 파형이 서로 맞는지 확인할 근거를 로그에 남긴다. 용적은 유량을 적분한 것이므로 둘의 관계를 수치로 기록해 둔다. 다만 이것으로 파형 이름을 바꾸거나 경고하지는 않는다 — 용적조절 환기에서는 기도압도 용적에 거의 비례해서 이 수치만으로는 둘을 구별할 수 없기 때문이다. 1.19.18 Added: Y케이블로 엿보는 Dräger 연결에서 파형 종류를 자동으로 알아낸다. Y케이블 배선에 따라 장비가 보내는 것만 보이는 방과, 반대편 장치가 보내는 것까지 보이는 방이 있는데, 후자에서는 그 장치가 파형을 요청하는 내용이 VR 에도 보인다. 거기에 순서가 그대로 들어 있으므로 추정 없이 확정한다 (실제로 수술장 두 방을 재보니 한 방은 양방향, 한 방은 단방향이었다). 단방향인 방은 종전대로 wavs= 로 적어주거나 도착 순서 추정을 쓴다. Fixed: 엿보는 연결인데도 VR 이 자기가 고른 파형 순서를 적용해 이름이 어긋날 수 있던 문제 — 회선에 다른 장치가 있는지를 통신 내용으로 판별해(보낸 적 없는 명령의 응답이 보이면 다른 장치가 있는 것이다) 그런 경우 순서를 스스로 정하지 않는다. 어느 모드로 동작 중인지는 로그에 남는다. 1.19.17 Added: Y케이블로 엿보기만 하는 Dräger 연결에서 파형 이름을 확정할 수 있다. 이런 연결은 VR 이 장비에 아무것도 보낼 수 없어 어떤 파형이 어떤 순서로 오는지 물어볼 수가 없고, 그래서 도착 순서대로 추정해 기록해 왔다(이름이 실제와 다를 수 있었다). 이제 vr.conf 의 장비 항목에 wavs= 로 그 방의 순서를 적어두면 그대로 사용한다. 순서는 그 장비에 연결된 다른 장치가 정한 것이라 병원에서 알 수 있는 값이다. 예: wavs=AWP,AWF. 적지 않으면 종전과 같이 도착 순서로 추정하며, 추정 중이라는 사실과 확정하는 방법을 로그에 남긴다. 직결 연결에서는 종전대로 VR 이 직접 물어보므로 이 설정이 없어도 정확하다. Added: 회선에서 처음 보는 종류의 통신 프레임을 한 번씩 로그에 남긴다. 어떤 장치가 무엇을 주고받는지 현장에서 확인할 수 있어 연결 문제 진단에 쓴다. 1.19.16 Fixed: 1.19.15 에서 Y케이블로 엿보기만 하는 연결(서울대병원 수술장의 Dräger 전부가 이 방식)의 파형이 기록되지 않던 문제 — 1.19.15 는 VR 이 직접 요청한 파형만 기록하도록 바뀌었는데, 관찰 전용 연결에서는 VR 이 장비에 아무것도 보낼 수 없어(송신선이 끊겨 있다) 요청 자체를 못 하므로 들어오는 파형을 전부 버렸다. 이제 이런 연결에서는 1.19.14 이전과 같이 도착 순서대로 기록한다. 다른 장치가 설정해 둔 순서는 알 수 없으므로 이름이 실제와 다를 수 있고, 그 사실을 로그에 함께 남긴다. 1.19.15 를 이미 설치한 곳은 이 버전으로 올려야 한다. 1.19.15 Fixed: Dräger 마취기·인공호흡기(MEDIBUS)에서 파형이 엉뚱한 이름으로 기록되던 문제. 장비가 보내는 파형 묶음의 첫 바이트에는 “몇 번째로 요청한 파형이 들어있는가” 가 비트로 표시되는데, VR 은 그 비트를 세기만 하고 도착 순서대로 미리 정해둔 트랙(CO2, AWP, AWF)에 넣었다. 장비가 요청한 파형 중 일부만 보내면 그만큼 밀려 기록돼, 예를 들어 기도압(AWP)만 보내는 인공호흡기에서 그 파형이 CO2 로 저장됐다 (Evita V600 실측 제보). 이제 비트를 해석해 요청한 순서와 맞춰 넣는다. Fixed: 요청하는 파형이 CO2 와 기도압 두 가지로 고정돼 있어, 장비가 유량(Flow)이나 용적(Volume) 파형을 제공해도 받지 못했다. 이제 장비에게 먼저 제공 가능한 파형 목록을 물어보고 그중에서 요청한다. Added: vr.conf 의 장비 설정에 wavs= 로 받을 파형을 고를 수 있다 (예: wavs=AWP,AWF,VOL,CO2). 비워두면 장비가 제공하는 것을 앞에서부터 최대 4개까지 자동으로 받는다. 4개인 이유는 한 묶음에 최대 4개까지만 표시할 수 있기 때문이다. 지원 이름: AWP AWF PLETH VOL O2 CO2 CO2_KPA CO2_PCT AGENT HAL ENF ISO DES SEV PTRACH AWF_DEV (16진 코드도 가능). 파형을 늘리면 회선 대역을 더 쓰므로 9600bps 의 80% 를 넘으면 로그로 경고한다. 어떤 파형이 실제로 들어오고 있는지도 로그에 남겨 현장에서 확인할 수 있다. 1.19.14 Added: 서버에서 VR 로 내려보내는 명령(원격 시간 동기, 원격 업그레이드, 재시작, 설정 변경 등)을 다시 사용할 수 있다. 1.18.52 에서 이 경로를 통째로 막았는데, 시간 동기와 원격 업그레이드가 꼭 필요한 기관이 있어 되살렸다. 다만 원격으로 프로그램을 바꾸거나 설정을 덮어쓰는 것은 그 자체로 위험하므로 기본값은 여전히 전부 차단이고, vr.conf 에 SERVER_CONTROL 로 허용할 명령을 직접 적어야 동작한다 (예: SERVER_CONTROL=dt 는 시간 동기만, SERVER_CONTROL=dt,update 는 시간 동기와 업그레이드만, SERVER_CONTROL=1 은 전부 허용). 사용 가능한 명령은 update restart reboot edit_conf new_bed del_bed edit_bed dt add_event. 시간 동기는 예전과 달리 녹화 중에는 시계를 건너뛰지 않는다 — 녹화 중 시계를 갑자기 옮기면 파형에 공백이 생기거나 기록이 겹치므로, 녹화 중이면 서서히 당겨 맞추고(슬루) 녹화가 없을 때만 한번에 맞춘다. 2초 이내 차이는 무시한다. 예전 구현이 10분 이상 차이날 때 프로그램을 강제 종료하던 동작도 없앴다. 1.19.13 Fixed: 웹 모니터링 화면에서 EEG 파형이 0.1초 정도씩 끊겨 보이던 문제 — 화면 전송용으로 파형을 줄일 때 원본 주파수가 나누어떨어지지 않으면 일부 구간이 비어 그만큼 빈칸으로 보였다. 나누어떨어지는 비율로만 줄이도록 수정. 녹화 파일에는 원래 문제가 없었다(표시만의 문제). 1.19.12 Fixed: Dräger 마취기 연동 안정화 보완 — 시리얼 회선 자동판별 중 잡음이 잘못된 데이터로 오인될 여지를 차단. 1.19.11 Fixed: Dräger 마취기(Atlan 등 MEDIBUS 장비)에서 통신 오류 알람(COM1 failure)이 반복되고 파형이 전혀 기록되지 않던 문제. 원인 세 가지를 모두 수정했다. (1) 장비는 3초 안에 아무 신호라도 받지 못하면 연결을 끊는데, VR 이 5초마다 데이터를 요청하는 것 외에 보내는 것이 없어 매번 연결이 끊기고 재접속을 반복했다 — 규격이 정한 연결 유지 신호(NOP)를 2초마다 보내도록 했다(데이터 요청 주기는 그대로). (2) 장비가 응답 도중에 다른 명령을 끼워 보내는 것이 규격상 허용되는데, VR 이 그때 받던 응답을 통째로 버려 파형 설정 응답을 매번 놓쳤다 — 끼어든 부분을 건너뛰고 원래 응답을 이어받도록 했다. (3) 장비가 보내는 요청 하나에 VR 이 응답하지 않아 통신이 끊기던 것도 수정. 파형·수치가 정상 기록되고 알람이 사라진다. 1.19.10 Fixed: 하루 한 번의 자동 재시작 시점에 수집이 통째로 멎어 사람이 손을 대기 전까지 복구되지 않던 문제(or-106, 104분 무데이터) — 종료 과정이 끝나지 않아 프로그램이 죽지 않았고, 죽지 않으니 자동 재시작도 일어나지 않았다. 주원인은 시리얼 회선 자동판별(AUTO_DETECT)이 종료 신호를 보지 않고 스윕(보레이트 6종 x 포맷 8종)을 끝까지 돌던 것으로, 무음 회선에서 종료에 2분이 걸려 정상 종료 유예시간을 넘겼다. 이제 종료 즉시 판별을 중단한다(실측 2분 -> 3초). 함께 종료 중 스레드 정리가 서로를 기다릴 수 있던 두 곳을 제거하고, 그럼에도 종료가 20초 안에 끝나지 않으면 강제 종료해 반드시 재시작되도록 안전장치를 넣었다. 녹화 파일은 5초마다 저장되므로 유실되지 않는다. 1.19.9 Fixed: 큰 녹화 파일(약 100MB 이상)을 서버로 올릴 때 프로그램이 메모리 부족으로 강제 종료되고, 재시작 후 같은 파일을 다시 시도하다 무한 반복되던 문제 — 업로드 본문을 메모리에 통째로 만들지 않고 디스크에서 흘려보내도록 변경(100MB 파일 기준 최대 메모리 208MB → 14MB). 큰 파일이 한 개라도 쌓이면 그 장비의 이후 업로드가 전부 막히던 증상이 사라진다. 1.19.8 Fixed: 자동 파일 자르기(CUT_FILE) 를 쓰는 베드에서 환자가 계속 연결돼 있는데도 녹화 파일이 PT_WAITING_TIME(기본 5분) 마다 잘리던 문제 — 웹 모니터링 전송 후 메모리를 비우는 과정에서 환자 연결 판정에 쓰이는 최근 구간까지 지워져 "환자가 나갔다" 로 오판하던 것. 이제 판정에 필요한 구간은 보존한다. 파일이 5분마다 끊기고 그때마다 수 초가 유실되던 증상이 사라진다. 1.19.7 Added: GE Corometrics(분만 태아감시) 모체 vitals 지원 — 기존엔 태아 심박(HR1/HR2)·자궁수축(UACT)만 기록됐으나 이제 같은 장비가 함께 보내는 모체 SpO2/맥박(PR)/NIBP(수축·이완·평균)도 한 파일에 기록. Fixed: 프로세스 종료(서비스 매니저의 정상 종료 요청, SIGTERM) 시 즉시 종료되지 않고 강제 종료되던 문제 — 이제 SIGTERM 을 받아 녹화 버퍼를 flush 하고 스레드를 정리한 뒤 정상 종료. 1.19.5 Fixed: CUT_HOURLY 가 동작하지 않는 것처럼 보이던 문제의 실체 — k3s 컨테이너에서 기본 저장 경로가 $HOME/recordings(임시 레이어)로 잡혀, 매일 자정 재시작 때 업로드 안 된 하루치 녹화가 통째로 사라짐. hostPath 마운트 /recordings 가 있으면 그것을 기본 저장 경로로 사용. Fixed: 종료(daily restart) 시 멀티베드 TCP 노드가 terminate(exit=133)/SIGSEGV(139) 로 죽던 문제. Linux finalize 가 device 스레드를 정지하지 않고 반환해 exit() 전역 소멸자가 살아있는 PortRouter accept_thread 를 파괴하며 std::terminate — DISPLAY::quiesce_threads + PortRouter::shutdown 으로 정리 후 종료 (코어 백트레이스로 확정, 로컬 재현 후 수정 검증). Fixed: Link+/Agilia 파서가 시리얼 노이즈(size byte 0 또는 헤더 없는 잡음 8byte)에 역전된 반복자 범위로 vector 를 생성하다 std::length_error 로 죽던 문제(or-110) — sentinel 상태 파싱 봉쇄 + 재동기화. Fixed: FILENAME_TEMPLATE 의 %Y(4자리 연도)가 "0126" 으로 나오던 문제 (tm_year+1900 누락). 1.19.4 Fixed: Link+ 등 재접속이 잦은 장비에서 시리얼 포트를 재오픈할 때 "terminate called without an active exception" 으로 프로세스가 죽던 문제(exit=133). reading_thread_func 이 스스로 반환해도 std::thread 객체는 joinable 로 남는데, open() 이 join 없이 새 스레드를 대입해 std::terminate 가 호출됨 → 스레드 생성 전에 이전 reading/parsing 스레드를 join 후 재생성. Fixed: headless/AUTO_DETECT 모드에서 실제 장비가 없을 때 Demo 장비가 생성돼 가짜 vital 트랙 38종이 업로드되던 문제 → --console 또는 AUTO_DETECT 시 Demo 미생성. 1.19.3 Fixed: 여러 베드를 한 프로세스가 TCP 로 수집하는 구성에서 클라이언트 재접속이 잦으면 프로세스가 SIGSEGV 로 죽던 문제(accept_handoff 가 replace 시 옛 소켓을 즉시 close 해 recv 중인 reader 와 경합, fd 번호 재사용). tcp reader 를 poll(200ms) 게이트 뒤의 recv 로 재구성하고 replace 는 shutdown 후 소유권을 reader/close 로 단일화. SNUH 회복실 VRN 15대에서 재현. Fixed: ICU 등 Intellivue 다수 베드에서 반복되던 SIGSEGV(139) 두 계열의 use-after-free. (1) DISPLAY::add_rec 이 레코드를 트랙에 push 한 뒤 저장했는데, push 직후 p 가 다른 스레드에 공유되고 Linux 의 메모리 보존 루프(600초 이전 레코드 delete)가 시계 어긋난 모니터에서 방금 넣은 레코드를 즉시 evict 해, 이어지는 p->save() 가 해제된 메모리에 가상 호출 → 저장을 push 앞으로 옮겨 공유 후 p 미접근. (2) DISPLAY::del_trk 이 트랙을 목록에서 뺀 직후 raw delete 해, 파싱 스레드의 캐시 포인터·장비 이름맵·recorded_trks 가 죽은 포인터를 참조 (특히 IAP 채널변경의 del 직후 add 가 해제 포인터 반환) → DEVICE 규칙처럼 살아있는 동안 삭제 금지, ~DISPLAY 에서 일괄 해제. picu-11 코어 덤프로 크래시 지점(add_rec vtable 간접호출) 확정. SNUH fleet ~100 노드 실배포에서 기대 크래시 다수 대비 실제 0, 미배포 노드는 계속 크래시. Fixed: Linux from_utf16 이 locale 의존·비스레드안전 wcstombs 를 쓰고 UTF-16 을 UTF-32 wchar_t 로 오해석하던 문제 → 수동 UTF-8 인코더(서로게이트 페어 재조립, 무효값 U+FFFD)로 교체. Added: AUTO_DETECT=1(vr.conf) 시리얼 장비 자동 판별. 회선 측정(보레이트·프레임폭·패리티) → 후보 필터(3축 일치, 베드당 클래스 배타, 포트 사전확률, 판별캐시) → 트랙 수확량 경쟁. 드라이버 무수정, 미설정 시 유휴 대기라 기존 설정 무영향. SNUH 수술장·ICU 실측 오탐 0.
1.19.23
1.19.23 Fixed: SyncPulseSerial 의 PULSE 값이 화면에 보이지 않던 문제. 값은 정상적으로 수신되고 파일에도 기록되고 있었는데, 화면 눈금 범위가 카운터의 최대값(864000)으로 잡혀 있어 테스트 초반의 작은 값들이 트랙 맨 바닥 1픽셀에 눌려 보이지 않았다. 이제 매초 가운데에 뚜렷한 선으로 표시된다. 기록된 값 자체는 이전 버전에서도 온전했다(표시만의 문제). 1.19.22 Added: Dräger 마취기를 기종 이름으로 고를 수 있다. 장비 목록에 Fabius 가 추가됐고, 목록에 없는 Dräger 를 쓰는 곳을 위해 Medibus 라는 일반 항목도 함께 넣었다. Primus, Fabius, Medibus 는 통신 방식이 완전히 같고 이름만 다르므로 어느 것을 골라도 동작은 같지만, 실제 기종을 고르면 설정 파일과 화면이 무엇이 연결돼 있는지 스스로 설명하게 된다. 기존에 Primus 로 설정해 둔 곳은 그대로 두면 된다. Changed: 시리얼 장비 자동 판별이 마취기를 특정 기종이 아니라 통신 방식 이름(Medibus)으로 인식한다. 같은 통신 방식을 쓰는 기종들은 회선만 봐서는 구별할 수 없으므로, 근거 없이 모델명을 단정하지 않는 편이 낫다고 판단했다. 기종 이름이 필요하면 설정에서 직접 고르면 된다. 1.19.21 Added: Dräger 장비가 스스로 알려주는 기종명을 로그에 남긴다. 설정 파일의 장비 이름은 사람이 적어 넣은 것이라 실제 기종을 보장하지 않는데, 기종에 따라 낼 수 있는 파형이 다르므로(예: Fabius 는 기도압과 유량은 있고 CO2 가 없다) 무엇이 연결돼 있는지 아는 것이 문제 해결의 출발점이다. 통신이 시작될 때 한 번 남는다. 1.19.20 Fixed: Dräger 마취기 가운데 파형 기능이 없는 기종에서 데이터가 전혀 수집되지 않던 문제. 연결은 된 것처럼 보이지만 아무 값도 들어오지 않는다. VR 이 장비를 확인한 뒤 파형 설정을 물어보고 그 답을 받아야만 데이터 요청을 시작하도록 돼 있었는데, 파형 기능이 없는 기종은 그 질문에 답하지 않으므로 요청이 영영 시작되지 않았다. 연결 유지 신호도 함께 멎어 장비가 통신을 끊고 다시 연결하기를 반복한다. 이제 장비 확인 직후부터 데이터를 요청한다. 파형 응답이 없으면 그 기종의 정상 동작이므로 숫자값만 수집하며 로그에 한 번 알린다. Fabius GS 는 규격상 파형 기능이 없는 대표적인 기종이다 (University of Washington 제보). Added: SyncPulseSerial 동기 펄스 생성기 지원. 여러 장비의 기록을 나중에 맞추기 위한 공통 번호를 시리얼로 보내는 장치로, 1초마다 6자리 카운터를 보낸다. PULSE 트랙에 그 번호가 기록된다. 시작이나 끝을 알리는 문자가 없는 형식이라, 메시지 사이의 침묵을 경계로 삼고 카운터가 1씩 늘어나는지 확인해 맞는 장비인지 판단한다. 자동 판별 대상에도 포함돼 있고, 환자 모니터와 같은 자리에 함께 연결해도 서로 방해하지 않는다. 1.19.19 Added: Dräger 인공호흡기·마취기가 파형에 함께 실어 보내는 호흡 위상 신호를 기록한다. 장비가 흡기와 호기 전환 시점을 직접 알려주는 것이라 파형에서 역산하는 것보다 정확하다. VENT_PHASE 트랙에 흡기 시작 1, 호기 시작 0 으로 남는다. 시각은 파형과 같은 시간축을 쓴다. 종전에는 이 신호를 읽지 않고 버렸다. Added: 환기 모드를 기록한다(VENT_MODE). 모드는 VC-CMV, PC-BIPAP, SPN-CPAP 처럼 압력조절인지 용적조절인지를 알려주어 파형 해석에 직접 쓰인다. 장비가 띄우고 있는 다른 안내 문구도 VENT_MSG 로 남는다. VR 이 그동안 이 정보를 담은 명령을 아예 보내지 않고 있었다 — 이제 측정값·설정과 함께 번갈아 요청한다. 표기가 장비나 언어에 따라 달라도 되도록 문구가 아니라 규격 코드로 판별한다. Added: 파형이 서로 맞는지 확인할 근거를 로그에 남긴다. 용적은 유량을 적분한 것이므로 둘의 관계를 수치로 기록해 둔다. 다만 이것으로 파형 이름을 바꾸거나 경고하지는 않는다 — 용적조절 환기에서는 기도압도 용적에 거의 비례해서 이 수치만으로는 둘을 구별할 수 없기 때문이다. 1.19.18 Added: Y케이블로 엿보는 Dräger 연결에서 파형 종류를 자동으로 알아낸다. Y케이블 배선에 따라 장비가 보내는 것만 보이는 방과, 반대편 장치가 보내는 것까지 보이는 방이 있는데, 후자에서는 그 장치가 파형을 요청하는 내용이 VR 에도 보인다. 거기에 순서가 그대로 들어 있으므로 추정 없이 확정한다 (실제로 수술장 두 방을 재보니 한 방은 양방향, 한 방은 단방향이었다). 단방향인 방은 종전대로 wavs= 로 적어주거나 도착 순서 추정을 쓴다. Fixed: 엿보는 연결인데도 VR 이 자기가 고른 파형 순서를 적용해 이름이 어긋날 수 있던 문제 — 회선에 다른 장치가 있는지를 통신 내용으로 판별해(보낸 적 없는 명령의 응답이 보이면 다른 장치가 있는 것이다) 그런 경우 순서를 스스로 정하지 않는다. 어느 모드로 동작 중인지는 로그에 남는다. 1.19.17 Added: Y케이블로 엿보기만 하는 Dräger 연결에서 파형 이름을 확정할 수 있다. 이런 연결은 VR 이 장비에 아무것도 보낼 수 없어 어떤 파형이 어떤 순서로 오는지 물어볼 수가 없고, 그래서 도착 순서대로 추정해 기록해 왔다(이름이 실제와 다를 수 있었다). 이제 vr.conf 의 장비 항목에 wavs= 로 그 방의 순서를 적어두면 그대로 사용한다. 순서는 그 장비에 연결된 다른 장치가 정한 것이라 병원에서 알 수 있는 값이다. 예: wavs=AWP,AWF. 적지 않으면 종전과 같이 도착 순서로 추정하며, 추정 중이라는 사실과 확정하는 방법을 로그에 남긴다. 직결 연결에서는 종전대로 VR 이 직접 물어보므로 이 설정이 없어도 정확하다. Added: 회선에서 처음 보는 종류의 통신 프레임을 한 번씩 로그에 남긴다. 어떤 장치가 무엇을 주고받는지 현장에서 확인할 수 있어 연결 문제 진단에 쓴다. 1.19.16 Fixed: 1.19.15 에서 Y케이블로 엿보기만 하는 연결(서울대병원 수술장의 Dräger 전부가 이 방식)의 파형이 기록되지 않던 문제 — 1.19.15 는 VR 이 직접 요청한 파형만 기록하도록 바뀌었는데, 관찰 전용 연결에서는 VR 이 장비에 아무것도 보낼 수 없어(송신선이 끊겨 있다) 요청 자체를 못 하므로 들어오는 파형을 전부 버렸다. 이제 이런 연결에서는 1.19.14 이전과 같이 도착 순서대로 기록한다. 다른 장치가 설정해 둔 순서는 알 수 없으므로 이름이 실제와 다를 수 있고, 그 사실을 로그에 함께 남긴다. 1.19.15 를 이미 설치한 곳은 이 버전으로 올려야 한다. 1.19.15 Fixed: Dräger 마취기·인공호흡기(MEDIBUS)에서 파형이 엉뚱한 이름으로 기록되던 문제. 장비가 보내는 파형 묶음의 첫 바이트에는 “몇 번째로 요청한 파형이 들어있는가” 가 비트로 표시되는데, VR 은 그 비트를 세기만 하고 도착 순서대로 미리 정해둔 트랙(CO2, AWP, AWF)에 넣었다. 장비가 요청한 파형 중 일부만 보내면 그만큼 밀려 기록돼, 예를 들어 기도압(AWP)만 보내는 인공호흡기에서 그 파형이 CO2 로 저장됐다 (Evita V600 실측 제보). 이제 비트를 해석해 요청한 순서와 맞춰 넣는다. Fixed: 요청하는 파형이 CO2 와 기도압 두 가지로 고정돼 있어, 장비가 유량(Flow)이나 용적(Volume) 파형을 제공해도 받지 못했다. 이제 장비에게 먼저 제공 가능한 파형 목록을 물어보고 그중에서 요청한다. Added: vr.conf 의 장비 설정에 wavs= 로 받을 파형을 고를 수 있다 (예: wavs=AWP,AWF,VOL,CO2). 비워두면 장비가 제공하는 것을 앞에서부터 최대 4개까지 자동으로 받는다. 4개인 이유는 한 묶음에 최대 4개까지만 표시할 수 있기 때문이다. 지원 이름: AWP AWF PLETH VOL O2 CO2 CO2_KPA CO2_PCT AGENT HAL ENF ISO DES SEV PTRACH AWF_DEV (16진 코드도 가능). 파형을 늘리면 회선 대역을 더 쓰므로 9600bps 의 80% 를 넘으면 로그로 경고한다. 어떤 파형이 실제로 들어오고 있는지도 로그에 남겨 현장에서 확인할 수 있다. 1.19.14 Added: 서버에서 VR 로 내려보내는 명령(원격 시간 동기, 원격 업그레이드, 재시작, 설정 변경 등)을 다시 사용할 수 있다. 1.18.52 에서 이 경로를 통째로 막았는데, 시간 동기와 원격 업그레이드가 꼭 필요한 기관이 있어 되살렸다. 다만 원격으로 프로그램을 바꾸거나 설정을 덮어쓰는 것은 그 자체로 위험하므로 기본값은 여전히 전부 차단이고, vr.conf 에 SERVER_CONTROL 로 허용할 명령을 직접 적어야 동작한다 (예: SERVER_CONTROL=dt 는 시간 동기만, SERVER_CONTROL=dt,update 는 시간 동기와 업그레이드만, SERVER_CONTROL=1 은 전부 허용). 사용 가능한 명령은 update restart reboot edit_conf new_bed del_bed edit_bed dt add_event. 시간 동기는 예전과 달리 녹화 중에는 시계를 건너뛰지 않는다 — 녹화 중 시계를 갑자기 옮기면 파형에 공백이 생기거나 기록이 겹치므로, 녹화 중이면 서서히 당겨 맞추고(슬루) 녹화가 없을 때만 한번에 맞춘다. 2초 이내 차이는 무시한다. 예전 구현이 10분 이상 차이날 때 프로그램을 강제 종료하던 동작도 없앴다. 1.19.13 Fixed: 웹 모니터링 화면에서 EEG 파형이 0.1초 정도씩 끊겨 보이던 문제 — 화면 전송용으로 파형을 줄일 때 원본 주파수가 나누어떨어지지 않으면 일부 구간이 비어 그만큼 빈칸으로 보였다. 나누어떨어지는 비율로만 줄이도록 수정. 녹화 파일에는 원래 문제가 없었다(표시만의 문제). 1.19.12 Fixed: Dräger 마취기 연동 안정화 보완 — 시리얼 회선 자동판별 중 잡음이 잘못된 데이터로 오인될 여지를 차단. 1.19.11 Fixed: Dräger 마취기(Atlan 등 MEDIBUS 장비)에서 통신 오류 알람(COM1 failure)이 반복되고 파형이 전혀 기록되지 않던 문제. 원인 세 가지를 모두 수정했다. (1) 장비는 3초 안에 아무 신호라도 받지 못하면 연결을 끊는데, VR 이 5초마다 데이터를 요청하는 것 외에 보내는 것이 없어 매번 연결이 끊기고 재접속을 반복했다 — 규격이 정한 연결 유지 신호(NOP)를 2초마다 보내도록 했다(데이터 요청 주기는 그대로). (2) 장비가 응답 도중에 다른 명령을 끼워 보내는 것이 규격상 허용되는데, VR 이 그때 받던 응답을 통째로 버려 파형 설정 응답을 매번 놓쳤다 — 끼어든 부분을 건너뛰고 원래 응답을 이어받도록 했다. (3) 장비가 보내는 요청 하나에 VR 이 응답하지 않아 통신이 끊기던 것도 수정. 파형·수치가 정상 기록되고 알람이 사라진다. 1.19.10 Fixed: 하루 한 번의 자동 재시작 시점에 수집이 통째로 멎어 사람이 손을 대기 전까지 복구되지 않던 문제(or-106, 104분 무데이터) — 종료 과정이 끝나지 않아 프로그램이 죽지 않았고, 죽지 않으니 자동 재시작도 일어나지 않았다. 주원인은 시리얼 회선 자동판별(AUTO_DETECT)이 종료 신호를 보지 않고 스윕(보레이트 6종 x 포맷 8종)을 끝까지 돌던 것으로, 무음 회선에서 종료에 2분이 걸려 정상 종료 유예시간을 넘겼다. 이제 종료 즉시 판별을 중단한다(실측 2분 -> 3초). 함께 종료 중 스레드 정리가 서로를 기다릴 수 있던 두 곳을 제거하고, 그럼에도 종료가 20초 안에 끝나지 않으면 강제 종료해 반드시 재시작되도록 안전장치를 넣었다. 녹화 파일은 5초마다 저장되므로 유실되지 않는다. 1.19.9 Fixed: 큰 녹화 파일(약 100MB 이상)을 서버로 올릴 때 프로그램이 메모리 부족으로 강제 종료되고, 재시작 후 같은 파일을 다시 시도하다 무한 반복되던 문제 — 업로드 본문을 메모리에 통째로 만들지 않고 디스크에서 흘려보내도록 변경(100MB 파일 기준 최대 메모리 208MB → 14MB). 큰 파일이 한 개라도 쌓이면 그 장비의 이후 업로드가 전부 막히던 증상이 사라진다. 1.19.8 Fixed: 자동 파일 자르기(CUT_FILE) 를 쓰는 베드에서 환자가 계속 연결돼 있는데도 녹화 파일이 PT_WAITING_TIME(기본 5분) 마다 잘리던 문제 — 웹 모니터링 전송 후 메모리를 비우는 과정에서 환자 연결 판정에 쓰이는 최근 구간까지 지워져 "환자가 나갔다" 로 오판하던 것. 이제 판정에 필요한 구간은 보존한다. 파일이 5분마다 끊기고 그때마다 수 초가 유실되던 증상이 사라진다. 1.19.7 Added: GE Corometrics(분만 태아감시) 모체 vitals 지원 — 기존엔 태아 심박(HR1/HR2)·자궁수축(UACT)만 기록됐으나 이제 같은 장비가 함께 보내는 모체 SpO2/맥박(PR)/NIBP(수축·이완·평균)도 한 파일에 기록. Fixed: 프로세스 종료(서비스 매니저의 정상 종료 요청, SIGTERM) 시 즉시 종료되지 않고 강제 종료되던 문제 — 이제 SIGTERM 을 받아 녹화 버퍼를 flush 하고 스레드를 정리한 뒤 정상 종료. 1.19.5 Fixed: CUT_HOURLY 가 동작하지 않는 것처럼 보이던 문제의 실체 — k3s 컨테이너에서 기본 저장 경로가 $HOME/recordings(임시 레이어)로 잡혀, 매일 자정 재시작 때 업로드 안 된 하루치 녹화가 통째로 사라짐. hostPath 마운트 /recordings 가 있으면 그것을 기본 저장 경로로 사용. Fixed: 종료(daily restart) 시 멀티베드 TCP 노드가 terminate(exit=133)/SIGSEGV(139) 로 죽던 문제. Linux finalize 가 device 스레드를 정지하지 않고 반환해 exit() 전역 소멸자가 살아있는 PortRouter accept_thread 를 파괴하며 std::terminate — DISPLAY::quiesce_threads + PortRouter::shutdown 으로 정리 후 종료 (코어 백트레이스로 확정, 로컬 재현 후 수정 검증). Fixed: Link+/Agilia 파서가 시리얼 노이즈(size byte 0 또는 헤더 없는 잡음 8byte)에 역전된 반복자 범위로 vector 를 생성하다 std::length_error 로 죽던 문제(or-110) — sentinel 상태 파싱 봉쇄 + 재동기화. Fixed: FILENAME_TEMPLATE 의 %Y(4자리 연도)가 "0126" 으로 나오던 문제 (tm_year+1900 누락). 1.19.4 Fixed: Link+ 등 재접속이 잦은 장비에서 시리얼 포트를 재오픈할 때 "terminate called without an active exception" 으로 프로세스가 죽던 문제(exit=133). reading_thread_func 이 스스로 반환해도 std::thread 객체는 joinable 로 남는데, open() 이 join 없이 새 스레드를 대입해 std::terminate 가 호출됨 → 스레드 생성 전에 이전 reading/parsing 스레드를 join 후 재생성. Fixed: headless/AUTO_DETECT 모드에서 실제 장비가 없을 때 Demo 장비가 생성돼 가짜 vital 트랙 38종이 업로드되던 문제 → --console 또는 AUTO_DETECT 시 Demo 미생성. 1.19.3 Fixed: 여러 베드를 한 프로세스가 TCP 로 수집하는 구성에서 클라이언트 재접속이 잦으면 프로세스가 SIGSEGV 로 죽던 문제(accept_handoff 가 replace 시 옛 소켓을 즉시 close 해 recv 중인 reader 와 경합, fd 번호 재사용). tcp reader 를 poll(200ms) 게이트 뒤의 recv 로 재구성하고 replace 는 shutdown 후 소유권을 reader/close 로 단일화. SNUH 회복실 VRN 15대에서 재현. Fixed: ICU 등 Intellivue 다수 베드에서 반복되던 SIGSEGV(139) 두 계열의 use-after-free. (1) DISPLAY::add_rec 이 레코드를 트랙에 push 한 뒤 저장했는데, push 직후 p 가 다른 스레드에 공유되고 Linux 의 메모리 보존 루프(600초 이전 레코드 delete)가 시계 어긋난 모니터에서 방금 넣은 레코드를 즉시 evict 해, 이어지는 p->save() 가 해제된 메모리에 가상 호출 → 저장을 push 앞으로 옮겨 공유 후 p 미접근. (2) DISPLAY::del_trk 이 트랙을 목록에서 뺀 직후 raw delete 해, 파싱 스레드의 캐시 포인터·장비 이름맵·recorded_trks 가 죽은 포인터를 참조 (특히 IAP 채널변경의 del 직후 add 가 해제 포인터 반환) → DEVICE 규칙처럼 살아있는 동안 삭제 금지, ~DISPLAY 에서 일괄 해제. picu-11 코어 덤프로 크래시 지점(add_rec vtable 간접호출) 확정. SNUH fleet ~100 노드 실배포에서 기대 크래시 다수 대비 실제 0, 미배포 노드는 계속 크래시. Fixed: Linux from_utf16 이 locale 의존·비스레드안전 wcstombs 를 쓰고 UTF-16 을 UTF-32 wchar_t 로 오해석하던 문제 → 수동 UTF-8 인코더(서로게이트 페어 재조립, 무효값 U+FFFD)로 교체. Added: AUTO_DETECT=1(vr.conf) 시리얼 장비 자동 판별. 회선 측정(보레이트·프레임폭·패리티) → 후보 필터(3축 일치, 베드당 클래스 배타, 포트 사전확률, 판별캐시) → 트랙 수확량 경쟁. 드라이버 무수정, 미설정 시 유휴 대기라 기존 설정 무영향. SNUH 수술장·ICU 실측 오탐 0.
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.19.27
1.19.27 Changed: 자동 판별이 지난번에 그 자리에 있던 장비를 가장 먼저 시험한다. 종전에는 지난 결과를 후보 순서를 바꾸는 데만 썼고, 그 앞에서 회선 설정(통신 속도·형식) 측정을 먼저 끝냈다. 그 측정이 조용한 회선에서는 48가지 조합을 다 확인하느라 72초를 쓴다. 방의 장비 구성은 거의 바뀌지 않으므로 지난 답보다 강한 단서가 없고, 빗나가더라도 후보 하나를 더 시험하는 값만 든다. Changed: 드물게 말하는 장비를 판별할 수 있게 됐다. 후보 하나를 시험하는 시간이 30초로 고정돼 있었는데, 근이완 감시장치(TwitchView, TOFScan)는 5분에 한 번 측정하므로 30초 안에 아무 말도 하지 않는 것이 정상이다. 그래서 이 장비들은 자동 판별로 영영 잡히지 않았다(한 수술방에서 21일 동안 지난 답을 기억한 채 확인에 계속 실패하고 있었다). 이제 각 장비가 스스로 정한 “이만큼은 조용할 수 있다” 는 값(최대 6분)만큼 듣는다. 겨룰 상대가 없는 단독 시험이므로 자료가 나오면 그 즉시 끝내며, 포트를 동시에 보게 된 1.19.26 덕분에 긴 대기가 다른 포트를 막지 않는다. 1.19.26 Changed: 시리얼 장비 자동 판별이 훨씬 빨리 끝난다. 종전에는 정답을 이미 찾은 뒤에도 남은 후보를 하나당 30초씩 끝까지 시험했다. 환자가 없는 아침에는 모니터가 트랙을 두어 개밖에 채우지 않아 “충분히 찾았다” 는 판정에 도달하지 못했기 때문이다(실측: 정답이 나온 시각은 기동 113초인데 확정은 275초). 이제 네 가지 경우에 그 자리에서 확정한다 — 지난 기동에서 이 포트의 답이었던 장비가 이번에도 자료를 낼 때, 회선 실측으로 후보가 하나만 남았을 때, 트랙이 10개 이상 열렸을 때, 그리고 1위가 2위를 3배 넘게 앞설 때. 확정 근거를 로그에 함께 남긴다. Changed: 여러 시리얼 포트를 동시에 살펴본다. 종전에는 한 포트씩 차례로 봐서 뒤쪽 포트는 앞 포트의 판별이 끝나기를 통째로 기다렸다(실측: 다섯 번째 포트가 기동 590초, 대부분이 대기 시간). 포트는 서로 독립된 장치라 동시에 열어도 간섭하지 않는다. 메모리를 위해 한 번에 3개까지만 본다. 같은 종류의 장비가 두 포트에서 동시에 잡히면 자료를 더 많이 낸 쪽만 남긴다(한 베드에 환자 모니터 하나, 마취기 하나라는 기존 규칙 그대로). Note: 1.19.25 설명 정정 — SyncPulseSerial 이 구형 Arduino UNO 에서 한 값도 기록되지 않던 것을 “프레임이 한 칸씩 밀린다” 고 적었는데 정확하지 않다. 실제로는 모으던 앞부분이 매번 버려져 여섯 자리가 완성되는 순간이 오지 않는 것이다. 또 같은 항목에서 Windows 수신 대기 방식을 원인으로 단정했으나, 가상 직렬 포트로 A/B 시험한 결과 그 경로에서는 재현되지 않았다 — 실제로 문제를 고친 것은 메시지 경계를 시각이 아니라 내용으로 찾도록 바꾼 쪽이다. 1.19.25 Fixed: SyncPulseSerial(동기 펄스 생성기)에서 1초 간격의 펄스가 2초마다 하나씩만 기록되거나, 보드 종류에 따라 아예 한 값도 기록되지 않던 문제. 두 증상 모두 원인이 하나다 — 이 장비는 시작·끝을 알리는 문자 없이 6자리 숫자만 흘려보내므로 VR 이 “잠깐의 침묵” 을 메시지 경계로 삼고 있었는데, 침묵은 회선이 아니라 컴퓨터 사정으로도 생긴다. 운영체제가 6바이트를 몇 번에 나눠 올려주고 그 사이가 벌어지면 앞자리가 버려져 이후 모든 값이 한 칸씩 밀리고, 한 번 밀리면 스스로 회복하지 못해 아무것도 기록되지 않는다(구형 Arduino UNO 에서 재현). 반대로 두 메시지가 한꺼번에 올라오면 둘 다 같은 시각으로 찍혀 1초짜리 펄스가 2초마다 하나 있는 것처럼 보인다(UNO R4 에서 재현). 이제 경계를 시간이 아니라 내용으로 찾는다 — 카운터가 정확히 1씩 늘어나는 자리를 스스로 찾아 맞추므로 회선이 어떻게 쪼개져 들어오든 영향을 받지 않고, 어긋나도 다음 펄스에서 다시 맞춘다. 한 번에 여러 펄스가 올라온 경우에는 카운터 간격만큼 시각을 되돌려 각각 제 시각에 기록한다. 세 가지 조건(정상·쪼개짐·뭉침) 재현 시험으로 확인했다 (Nord University AniLab 제보). Fixed: Windows 에서 데이터를 띄엄띄엄 보내는 시리얼 장비의 값이 최대 한 주기 늦게 들어오던 문제. 문자 도착 알림을 기다리는 동안이 아니라 그 직전 대기 중에 데이터가 통째로 들어와 버리면, 새로 도착하는 문자가 없어 알림이 오지 않고 이미 들어온 데이터가 다음 전송이 올 때까지 그대로 대기했다. 이제 알림이 없어도 수신 버퍼를 직접 확인한다. 초당 수십~수백 바이트를 보내는 대부분의 장비에서는 드러나지 않던 문제이지만, 1초에 한 번처럼 드물게 보내는 장비에서는 두 전송이 한 번에 처리되어 시각이 뭉쳤다. 1.19.24 Fixed: Windows 에서 SyncPulseSerial(Arduino UNO R4)의 데이터가 전혀 들어오지 않거나 한참 뒤에야 간헐적으로 들어오던 문제. Arduino 의 USB 시리얼은 컴퓨터 쪽이 준비 신호(DTR)를 올려야만 데이터를 내보내는데, VR 이 Windows 에서 포트를 열 때 이 신호를 내린 채로 열고 있었다. 시리얼 터미널 프로그램들은 이 신호를 기본으로 올리기 때문에 터미널에서는 잘 보이는데 VR 에서만 안 보이는 형태로 나타났다 (Win11/Win7 3대 실측, Nord University AniLab 제보). 장비 연결 상태는 정상으로 표시되므로 화면만 봐서는 알 수 없는 문제였다. 다른 시리얼 장비들의 동작은 그대로다 — 이 신호에 민감한 장비가 있을 수 있어 Arduino 계열 드라이버에서만 올린다. 1.19.23 Fixed: SyncPulseSerial 의 PULSE 값이 화면에 보이지 않던 문제. 값은 정상적으로 수신되고 파일에도 기록되고 있었는데, 화면 눈금 범위가 카운터의 최대값(864000)으로 잡혀 있어 테스트 초반의 작은 값들이 트랙 맨 바닥 1픽셀에 눌려 보이지 않았다. 이제 매초 가운데에 뚜렷한 선으로 표시된다. 기록된 값 자체는 이전 버전에서도 온전했다(표시만의 문제). 1.19.22 Added: Dräger 마취기를 기종 이름으로 고를 수 있다. 장비 목록에 Fabius 가 추가됐고, 목록에 없는 Dräger 를 쓰는 곳을 위해 Medibus 라는 일반 항목도 함께 넣었다. Primus, Fabius, Medibus 는 통신 방식이 완전히 같고 이름만 다르므로 어느 것을 골라도 동작은 같지만, 실제 기종을 고르면 설정 파일과 화면이 무엇이 연결돼 있는지 스스로 설명하게 된다. 기존에 Primus 로 설정해 둔 곳은 그대로 두면 된다. Changed: 시리얼 장비 자동 판별이 마취기를 특정 기종이 아니라 통신 방식 이름(Medibus)으로 인식한다. 같은 통신 방식을 쓰는 기종들은 회선만 봐서는 구별할 수 없으므로, 근거 없이 모델명을 단정하지 않는 편이 낫다고 판단했다. 기종 이름이 필요하면 설정에서 직접 고르면 된다. 1.19.21 Added: Dräger 장비가 스스로 알려주는 기종명을 로그에 남긴다. 설정 파일의 장비 이름은 사람이 적어 넣은 것이라 실제 기종을 보장하지 않는데, 기종에 따라 낼 수 있는 파형이 다르므로(예: Fabius 는 기도압과 유량은 있고 CO2 가 없다) 무엇이 연결돼 있는지 아는 것이 문제 해결의 출발점이다. 통신이 시작될 때 한 번 남는다. 1.19.20 Fixed: Dräger 마취기 가운데 파형 기능이 없는 기종에서 데이터가 전혀 수집되지 않던 문제. 연결은 된 것처럼 보이지만 아무 값도 들어오지 않는다. VR 이 장비를 확인한 뒤 파형 설정을 물어보고 그 답을 받아야만 데이터 요청을 시작하도록 돼 있었는데, 파형 기능이 없는 기종은 그 질문에 답하지 않으므로 요청이 영영 시작되지 않았다. 연결 유지 신호도 함께 멎어 장비가 통신을 끊고 다시 연결하기를 반복한다. 이제 장비 확인 직후부터 데이터를 요청한다. 파형 응답이 없으면 그 기종의 정상 동작이므로 숫자값만 수집하며 로그에 한 번 알린다. Fabius GS 는 규격상 파형 기능이 없는 대표적인 기종이다 (University of Washington 제보). Added: SyncPulseSerial 동기 펄스 생성기 지원. 여러 장비의 기록을 나중에 맞추기 위한 공통 번호를 시리얼로 보내는 장치로, 1초마다 6자리 카운터를 보낸다. PULSE 트랙에 그 번호가 기록된다. 시작이나 끝을 알리는 문자가 없는 형식이라, 메시지 사이의 침묵을 경계로 삼고 카운터가 1씩 늘어나는지 확인해 맞는 장비인지 판단한다. 자동 판별 대상에도 포함돼 있고, 환자 모니터와 같은 자리에 함께 연결해도 서로 방해하지 않는다. 1.19.19 Added: Dräger 인공호흡기·마취기가 파형에 함께 실어 보내는 호흡 위상 신호를 기록한다. 장비가 흡기와 호기 전환 시점을 직접 알려주는 것이라 파형에서 역산하는 것보다 정확하다. VENT_PHASE 트랙에 흡기 시작 1, 호기 시작 0 으로 남는다. 시각은 파형과 같은 시간축을 쓴다. 종전에는 이 신호를 읽지 않고 버렸다. Added: 환기 모드를 기록한다(VENT_MODE). 모드는 VC-CMV, PC-BIPAP, SPN-CPAP 처럼 압력조절인지 용적조절인지를 알려주어 파형 해석에 직접 쓰인다. 장비가 띄우고 있는 다른 안내 문구도 VENT_MSG 로 남는다. VR 이 그동안 이 정보를 담은 명령을 아예 보내지 않고 있었다 — 이제 측정값·설정과 함께 번갈아 요청한다. 표기가 장비나 언어에 따라 달라도 되도록 문구가 아니라 규격 코드로 판별한다. Added: 파형이 서로 맞는지 확인할 근거를 로그에 남긴다. 용적은 유량을 적분한 것이므로 둘의 관계를 수치로 기록해 둔다. 다만 이것으로 파형 이름을 바꾸거나 경고하지는 않는다 — 용적조절 환기에서는 기도압도 용적에 거의 비례해서 이 수치만으로는 둘을 구별할 수 없기 때문이다. 1.19.18 Added: Y케이블로 엿보는 Dräger 연결에서 파형 종류를 자동으로 알아낸다. Y케이블 배선에 따라 장비가 보내는 것만 보이는 방과, 반대편 장치가 보내는 것까지 보이는 방이 있는데, 후자에서는 그 장치가 파형을 요청하는 내용이 VR 에도 보인다. 거기에 순서가 그대로 들어 있으므로 추정 없이 확정한다 (실제로 수술장 두 방을 재보니 한 방은 양방향, 한 방은 단방향이었다). 단방향인 방은 종전대로 wavs= 로 적어주거나 도착 순서 추정을 쓴다. Fixed: 엿보는 연결인데도 VR 이 자기가 고른 파형 순서를 적용해 이름이 어긋날 수 있던 문제 — 회선에 다른 장치가 있는지를 통신 내용으로 판별해(보낸 적 없는 명령의 응답이 보이면 다른 장치가 있는 것이다) 그런 경우 순서를 스스로 정하지 않는다. 어느 모드로 동작 중인지는 로그에 남는다. 1.19.17 Added: Y케이블로 엿보기만 하는 Dräger 연결에서 파형 이름을 확정할 수 있다. 이런 연결은 VR 이 장비에 아무것도 보낼 수 없어 어떤 파형이 어떤 순서로 오는지 물어볼 수가 없고, 그래서 도착 순서대로 추정해 기록해 왔다(이름이 실제와 다를 수 있었다). 이제 vr.conf 의 장비 항목에 wavs= 로 그 방의 순서를 적어두면 그대로 사용한다. 순서는 그 장비에 연결된 다른 장치가 정한 것이라 병원에서 알 수 있는 값이다. 예: wavs=AWP,AWF. 적지 않으면 종전과 같이 도착 순서로 추정하며, 추정 중이라는 사실과 확정하는 방법을 로그에 남긴다. 직결 연결에서는 종전대로 VR 이 직접 물어보므로 이 설정이 없어도 정확하다. Added: 회선에서 처음 보는 종류의 통신 프레임을 한 번씩 로그에 남긴다. 어떤 장치가 무엇을 주고받는지 현장에서 확인할 수 있어 연결 문제 진단에 쓴다. 1.19.16 Fixed: 1.19.15 에서 Y케이블로 엿보기만 하는 연결(서울대병원 수술장의 Dräger 전부가 이 방식)의 파형이 기록되지 않던 문제 — 1.19.15 는 VR 이 직접 요청한 파형만 기록하도록 바뀌었는데, 관찰 전용 연결에서는 VR 이 장비에 아무것도 보낼 수 없어(송신선이 끊겨 있다) 요청 자체를 못 하므로 들어오는 파형을 전부 버렸다. 이제 이런 연결에서는 1.19.14 이전과 같이 도착 순서대로 기록한다. 다른 장치가 설정해 둔 순서는 알 수 없으므로 이름이 실제와 다를 수 있고, 그 사실을 로그에 함께 남긴다. 1.19.15 를 이미 설치한 곳은 이 버전으로 올려야 한다. 1.19.15 Fixed: Dräger 마취기·인공호흡기(MEDIBUS)에서 파형이 엉뚱한 이름으로 기록되던 문제. 장비가 보내는 파형 묶음의 첫 바이트에는 “몇 번째로 요청한 파형이 들어있는가” 가 비트로 표시되는데, VR 은 그 비트를 세기만 하고 도착 순서대로 미리 정해둔 트랙(CO2, AWP, AWF)에 넣었다. 장비가 요청한 파형 중 일부만 보내면 그만큼 밀려 기록돼, 예를 들어 기도압(AWP)만 보내는 인공호흡기에서 그 파형이 CO2 로 저장됐다 (Evita V600 실측 제보). 이제 비트를 해석해 요청한 순서와 맞춰 넣는다. Fixed: 요청하는 파형이 CO2 와 기도압 두 가지로 고정돼 있어, 장비가 유량(Flow)이나 용적(Volume) 파형을 제공해도 받지 못했다. 이제 장비에게 먼저 제공 가능한 파형 목록을 물어보고 그중에서 요청한다. Added: vr.conf 의 장비 설정에 wavs= 로 받을 파형을 고를 수 있다 (예: wavs=AWP,AWF,VOL,CO2). 비워두면 장비가 제공하는 것을 앞에서부터 최대 4개까지 자동으로 받는다. 4개인 이유는 한 묶음에 최대 4개까지만 표시할 수 있기 때문이다. 지원 이름: AWP AWF PLETH VOL O2 CO2 CO2_KPA CO2_PCT AGENT HAL ENF ISO DES SEV PTRACH AWF_DEV (16진 코드도 가능). 파형을 늘리면 회선 대역을 더 쓰므로 9600bps 의 80% 를 넘으면 로그로 경고한다. 어떤 파형이 실제로 들어오고 있는지도 로그에 남겨 현장에서 확인할 수 있다. 1.19.14 Added: 서버에서 VR 로 내려보내는 명령(원격 시간 동기, 원격 업그레이드, 재시작, 설정 변경 등)을 다시 사용할 수 있다. 1.18.52 에서 이 경로를 통째로 막았는데, 시간 동기와 원격 업그레이드가 꼭 필요한 기관이 있어 되살렸다. 다만 원격으로 프로그램을 바꾸거나 설정을 덮어쓰는 것은 그 자체로 위험하므로 기본값은 여전히 전부 차단이고, vr.conf 에 SERVER_CONTROL 로 허용할 명령을 직접 적어야 동작한다 (예: SERVER_CONTROL=dt 는 시간 동기만, SERVER_CONTROL=dt,update 는 시간 동기와 업그레이드만, SERVER_CONTROL=1 은 전부 허용). 사용 가능한 명령은 update restart reboot edit_conf new_bed del_bed edit_bed dt add_event. 시간 동기는 예전과 달리 녹화 중에는 시계를 건너뛰지 않는다 — 녹화 중 시계를 갑자기 옮기면 파형에 공백이 생기거나 기록이 겹치므로, 녹화 중이면 서서히 당겨 맞추고(슬루) 녹화가 없을 때만 한번에 맞춘다. 2초 이내 차이는 무시한다. 예전 구현이 10분 이상 차이날 때 프로그램을 강제 종료하던 동작도 없앴다. 1.19.13 Fixed: 웹 모니터링 화면에서 EEG 파형이 0.1초 정도씩 끊겨 보이던 문제 — 화면 전송용으로 파형을 줄일 때 원본 주파수가 나누어떨어지지 않으면 일부 구간이 비어 그만큼 빈칸으로 보였다. 나누어떨어지는 비율로만 줄이도록 수정. 녹화 파일에는 원래 문제가 없었다(표시만의 문제). 1.19.12 Fixed: Dräger 마취기 연동 안정화 보완 — 시리얼 회선 자동판별 중 잡음이 잘못된 데이터로 오인될 여지를 차단. 1.19.11 Fixed: Dräger 마취기(Atlan 등 MEDIBUS 장비)에서 통신 오류 알람(COM1 failure)이 반복되고 파형이 전혀 기록되지 않던 문제. 원인 세 가지를 모두 수정했다. (1) 장비는 3초 안에 아무 신호라도 받지 못하면 연결을 끊는데, VR 이 5초마다 데이터를 요청하는 것 외에 보내는 것이 없어 매번 연결이 끊기고 재접속을 반복했다 — 규격이 정한 연결 유지 신호(NOP)를 2초마다 보내도록 했다(데이터 요청 주기는 그대로). (2) 장비가 응답 도중에 다른 명령을 끼워 보내는 것이 규격상 허용되는데, VR 이 그때 받던 응답을 통째로 버려 파형 설정 응답을 매번 놓쳤다 — 끼어든 부분을 건너뛰고 원래 응답을 이어받도록 했다. (3) 장비가 보내는 요청 하나에 VR 이 응답하지 않아 통신이 끊기던 것도 수정. 파형·수치가 정상 기록되고 알람이 사라진다. 1.19.10 Fixed: 하루 한 번의 자동 재시작 시점에 수집이 통째로 멎어 사람이 손을 대기 전까지 복구되지 않던 문제(or-106, 104분 무데이터) — 종료 과정이 끝나지 않아 프로그램이 죽지 않았고, 죽지 않으니 자동 재시작도 일어나지 않았다. 주원인은 시리얼 회선 자동판별(AUTO_DETECT)이 종료 신호를 보지 않고 스윕(보레이트 6종 x 포맷 8종)을 끝까지 돌던 것으로, 무음 회선에서 종료에 2분이 걸려 정상 종료 유예시간을 넘겼다. 이제 종료 즉시 판별을 중단한다(실측 2분 -> 3초). 함께 종료 중 스레드 정리가 서로를 기다릴 수 있던 두 곳을 제거하고, 그럼에도 종료가 20초 안에 끝나지 않으면 강제 종료해 반드시 재시작되도록 안전장치를 넣었다. 녹화 파일은 5초마다 저장되므로 유실되지 않는다. 1.19.9 Fixed: 큰 녹화 파일(약 100MB 이상)을 서버로 올릴 때 프로그램이 메모리 부족으로 강제 종료되고, 재시작 후 같은 파일을 다시 시도하다 무한 반복되던 문제 — 업로드 본문을 메모리에 통째로 만들지 않고 디스크에서 흘려보내도록 변경(100MB 파일 기준 최대 메모리 208MB → 14MB). 큰 파일이 한 개라도 쌓이면 그 장비의 이후 업로드가 전부 막히던 증상이 사라진다. 1.19.8 Fixed: 자동 파일 자르기(CUT_FILE) 를 쓰는 베드에서 환자가 계속 연결돼 있는데도 녹화 파일이 PT_WAITING_TIME(기본 5분) 마다 잘리던 문제 — 웹 모니터링 전송 후 메모리를 비우는 과정에서 환자 연결 판정에 쓰이는 최근 구간까지 지워져 "환자가 나갔다" 로 오판하던 것. 이제 판정에 필요한 구간은 보존한다. 파일이 5분마다 끊기고 그때마다 수 초가 유실되던 증상이 사라진다. 1.19.7 Added: GE Corometrics(분만 태아감시) 모체 vitals 지원 — 기존엔 태아 심박(HR1/HR2)·자궁수축(UACT)만 기록됐으나 이제 같은 장비가 함께 보내는 모체 SpO2/맥박(PR)/NIBP(수축·이완·평균)도 한 파일에 기록. Fixed: 프로세스 종료(서비스 매니저의 정상 종료 요청, SIGTERM) 시 즉시 종료되지 않고 강제 종료되던 문제 — 이제 SIGTERM 을 받아 녹화 버퍼를 flush 하고 스레드를 정리한 뒤 정상 종료. 1.19.5 Fixed: CUT_HOURLY 가 동작하지 않는 것처럼 보이던 문제의 실체 — k3s 컨테이너에서 기본 저장 경로가 $HOME/recordings(임시 레이어)로 잡혀, 매일 자정 재시작 때 업로드 안 된 하루치 녹화가 통째로 사라짐. hostPath 마운트 /recordings 가 있으면 그것을 기본 저장 경로로 사용. Fixed: 종료(daily restart) 시 멀티베드 TCP 노드가 terminate(exit=133)/SIGSEGV(139) 로 죽던 문제. Linux finalize 가 device 스레드를 정지하지 않고 반환해 exit() 전역 소멸자가 살아있는 PortRouter accept_thread 를 파괴하며 std::terminate — DISPLAY::quiesce_threads + PortRouter::shutdown 으로 정리 후 종료 (코어 백트레이스로 확정, 로컬 재현 후 수정 검증). Fixed: Link+/Agilia 파서가 시리얼 노이즈(size byte 0 또는 헤더 없는 잡음 8byte)에 역전된 반복자 범위로 vector 를 생성하다 std::length_error 로 죽던 문제(or-110) — sentinel 상태 파싱 봉쇄 + 재동기화. Fixed: FILENAME_TEMPLATE 의 %Y(4자리 연도)가 "0126" 으로 나오던 문제 (tm_year+1900 누락). 1.19.4 Fixed: Link+ 등 재접속이 잦은 장비에서 시리얼 포트를 재오픈할 때 "terminate called without an active exception" 으로 프로세스가 죽던 문제(exit=133). reading_thread_func 이 스스로 반환해도 std::thread 객체는 joinable 로 남는데, open() 이 join 없이 새 스레드를 대입해 std::terminate 가 호출됨 → 스레드 생성 전에 이전 reading/parsing 스레드를 join 후 재생성. Fixed: headless/AUTO_DETECT 모드에서 실제 장비가 없을 때 Demo 장비가 생성돼 가짜 vital 트랙 38종이 업로드되던 문제 → --console 또는 AUTO_DETECT 시 Demo 미생성. 1.19.3 Fixed: 여러 베드를 한 프로세스가 TCP 로 수집하는 구성에서 클라이언트 재접속이 잦으면 프로세스가 SIGSEGV 로 죽던 문제(accept_handoff 가 replace 시 옛 소켓을 즉시 close 해 recv 중인 reader 와 경합, fd 번호 재사용). tcp reader 를 poll(200ms) 게이트 뒤의 recv 로 재구성하고 replace 는 shutdown 후 소유권을 reader/close 로 단일화. SNUH 회복실 VRN 15대에서 재현. Fixed: ICU 등 Intellivue 다수 베드에서 반복되던 SIGSEGV(139) 두 계열의 use-after-free. (1) DISPLAY::add_rec 이 레코드를 트랙에 push 한 뒤 저장했는데, push 직후 p 가 다른 스레드에 공유되고 Linux 의 메모리 보존 루프(600초 이전 레코드 delete)가 시계 어긋난 모니터에서 방금 넣은 레코드를 즉시 evict 해, 이어지는 p->save() 가 해제된 메모리에 가상 호출 → 저장을 push 앞으로 옮겨 공유 후 p 미접근. (2) DISPLAY::del_trk 이 트랙을 목록에서 뺀 직후 raw delete 해, 파싱 스레드의 캐시 포인터·장비 이름맵·recorded_trks 가 죽은 포인터를 참조 (특히 IAP 채널변경의 del 직후 add 가 해제 포인터 반환) → DEVICE 규칙처럼 살아있는 동안 삭제 금지, ~DISPLAY 에서 일괄 해제. picu-11 코어 덤프로 크래시 지점(add_rec vtable 간접호출) 확정. SNUH fleet ~100 노드 실배포에서 기대 크래시 다수 대비 실제 0, 미배포 노드는 계속 크래시. Fixed: Linux from_utf16 이 locale 의존·비스레드안전 wcstombs 를 쓰고 UTF-16 을 UTF-32 wchar_t 로 오해석하던 문제 → 수동 UTF-8 인코더(서로게이트 페어 재조립, 무효값 U+FFFD)로 교체. Added: AUTO_DETECT=1(vr.conf) 시리얼 장비 자동 판별. 회선 측정(보레이트·프레임폭·패리티) → 후보 필터(3축 일치, 베드당 클래스 배타, 포트 사전확률, 판별캐시) → 트랙 수확량 경쟁. 드라이버 무수정, 미설정 시 유휴 대기라 기존 설정 무영향. SNUH 수술장·ICU 실측 오탐 0.
1.19.26
1.19.26 Changed: 시리얼 장비 자동 판별이 훨씬 빨리 끝난다. 종전에는 정답을 이미 찾은 뒤에도 남은 후보를 하나당 30초씩 끝까지 시험했다. 환자가 없는 아침에는 모니터가 트랙을 두어 개밖에 채우지 않아 “충분히 찾았다” 는 판정에 도달하지 못했기 때문이다(실측: 정답이 나온 시각은 기동 113초인데 확정은 275초). 이제 네 가지 경우에 그 자리에서 확정한다 — 지난 기동에서 이 포트의 답이었던 장비가 이번에도 자료를 낼 때, 회선 실측으로 후보가 하나만 남았을 때, 트랙이 10개 이상 열렸을 때, 그리고 1위가 2위를 3배 넘게 앞설 때. 확정 근거를 로그에 함께 남긴다. Changed: 여러 시리얼 포트를 동시에 살펴본다. 종전에는 한 포트씩 차례로 봐서 뒤쪽 포트는 앞 포트의 판별이 끝나기를 통째로 기다렸다(실측: 다섯 번째 포트가 기동 590초, 대부분이 대기 시간). 포트는 서로 독립된 장치라 동시에 열어도 간섭하지 않는다. 메모리를 위해 한 번에 3개까지만 본다. 같은 종류의 장비가 두 포트에서 동시에 잡히면 자료를 더 많이 낸 쪽만 남긴다(한 베드에 환자 모니터 하나, 마취기 하나라는 기존 규칙 그대로). Note: 1.19.25 설명 정정 — SyncPulseSerial 이 구형 Arduino UNO 에서 한 값도 기록되지 않던 것을 “프레임이 한 칸씩 밀린다” 고 적었는데 정확하지 않다. 실제로는 모으던 앞부분이 매번 버려져 여섯 자리가 완성되는 순간이 오지 않는 것이다. 또 같은 항목에서 Windows 수신 대기 방식을 원인으로 단정했으나, 가상 직렬 포트로 A/B 시험한 결과 그 경로에서는 재현되지 않았다 — 실제로 문제를 고친 것은 메시지 경계를 시각이 아니라 내용으로 찾도록 바꾼 쪽이다. 1.19.25 Fixed: SyncPulseSerial(동기 펄스 생성기)에서 1초 간격의 펄스가 2초마다 하나씩만 기록되거나, 보드 종류에 따라 아예 한 값도 기록되지 않던 문제. 두 증상 모두 원인이 하나다 — 이 장비는 시작·끝을 알리는 문자 없이 6자리 숫자만 흘려보내므로 VR 이 “잠깐의 침묵” 을 메시지 경계로 삼고 있었는데, 침묵은 회선이 아니라 컴퓨터 사정으로도 생긴다. 운영체제가 6바이트를 몇 번에 나눠 올려주고 그 사이가 벌어지면 앞자리가 버려져 이후 모든 값이 한 칸씩 밀리고, 한 번 밀리면 스스로 회복하지 못해 아무것도 기록되지 않는다(구형 Arduino UNO 에서 재현). 반대로 두 메시지가 한꺼번에 올라오면 둘 다 같은 시각으로 찍혀 1초짜리 펄스가 2초마다 하나 있는 것처럼 보인다(UNO R4 에서 재현). 이제 경계를 시간이 아니라 내용으로 찾는다 — 카운터가 정확히 1씩 늘어나는 자리를 스스로 찾아 맞추므로 회선이 어떻게 쪼개져 들어오든 영향을 받지 않고, 어긋나도 다음 펄스에서 다시 맞춘다. 한 번에 여러 펄스가 올라온 경우에는 카운터 간격만큼 시각을 되돌려 각각 제 시각에 기록한다. 세 가지 조건(정상·쪼개짐·뭉침) 재현 시험으로 확인했다 (Nord University AniLab 제보). Fixed: Windows 에서 데이터를 띄엄띄엄 보내는 시리얼 장비의 값이 최대 한 주기 늦게 들어오던 문제. 문자 도착 알림을 기다리는 동안이 아니라 그 직전 대기 중에 데이터가 통째로 들어와 버리면, 새로 도착하는 문자가 없어 알림이 오지 않고 이미 들어온 데이터가 다음 전송이 올 때까지 그대로 대기했다. 이제 알림이 없어도 수신 버퍼를 직접 확인한다. 초당 수십~수백 바이트를 보내는 대부분의 장비에서는 드러나지 않던 문제이지만, 1초에 한 번처럼 드물게 보내는 장비에서는 두 전송이 한 번에 처리되어 시각이 뭉쳤다. 1.19.24 Fixed: Windows 에서 SyncPulseSerial(Arduino UNO R4)의 데이터가 전혀 들어오지 않거나 한참 뒤에야 간헐적으로 들어오던 문제. Arduino 의 USB 시리얼은 컴퓨터 쪽이 준비 신호(DTR)를 올려야만 데이터를 내보내는데, VR 이 Windows 에서 포트를 열 때 이 신호를 내린 채로 열고 있었다. 시리얼 터미널 프로그램들은 이 신호를 기본으로 올리기 때문에 터미널에서는 잘 보이는데 VR 에서만 안 보이는 형태로 나타났다 (Win11/Win7 3대 실측, Nord University AniLab 제보). 장비 연결 상태는 정상으로 표시되므로 화면만 봐서는 알 수 없는 문제였다. 다른 시리얼 장비들의 동작은 그대로다 — 이 신호에 민감한 장비가 있을 수 있어 Arduino 계열 드라이버에서만 올린다. 1.19.23 Fixed: SyncPulseSerial 의 PULSE 값이 화면에 보이지 않던 문제. 값은 정상적으로 수신되고 파일에도 기록되고 있었는데, 화면 눈금 범위가 카운터의 최대값(864000)으로 잡혀 있어 테스트 초반의 작은 값들이 트랙 맨 바닥 1픽셀에 눌려 보이지 않았다. 이제 매초 가운데에 뚜렷한 선으로 표시된다. 기록된 값 자체는 이전 버전에서도 온전했다(표시만의 문제). 1.19.22 Added: Dräger 마취기를 기종 이름으로 고를 수 있다. 장비 목록에 Fabius 가 추가됐고, 목록에 없는 Dräger 를 쓰는 곳을 위해 Medibus 라는 일반 항목도 함께 넣었다. Primus, Fabius, Medibus 는 통신 방식이 완전히 같고 이름만 다르므로 어느 것을 골라도 동작은 같지만, 실제 기종을 고르면 설정 파일과 화면이 무엇이 연결돼 있는지 스스로 설명하게 된다. 기존에 Primus 로 설정해 둔 곳은 그대로 두면 된다. Changed: 시리얼 장비 자동 판별이 마취기를 특정 기종이 아니라 통신 방식 이름(Medibus)으로 인식한다. 같은 통신 방식을 쓰는 기종들은 회선만 봐서는 구별할 수 없으므로, 근거 없이 모델명을 단정하지 않는 편이 낫다고 판단했다. 기종 이름이 필요하면 설정에서 직접 고르면 된다. 1.19.21 Added: Dräger 장비가 스스로 알려주는 기종명을 로그에 남긴다. 설정 파일의 장비 이름은 사람이 적어 넣은 것이라 실제 기종을 보장하지 않는데, 기종에 따라 낼 수 있는 파형이 다르므로(예: Fabius 는 기도압과 유량은 있고 CO2 가 없다) 무엇이 연결돼 있는지 아는 것이 문제 해결의 출발점이다. 통신이 시작될 때 한 번 남는다. 1.19.20 Fixed: Dräger 마취기 가운데 파형 기능이 없는 기종에서 데이터가 전혀 수집되지 않던 문제. 연결은 된 것처럼 보이지만 아무 값도 들어오지 않는다. VR 이 장비를 확인한 뒤 파형 설정을 물어보고 그 답을 받아야만 데이터 요청을 시작하도록 돼 있었는데, 파형 기능이 없는 기종은 그 질문에 답하지 않으므로 요청이 영영 시작되지 않았다. 연결 유지 신호도 함께 멎어 장비가 통신을 끊고 다시 연결하기를 반복한다. 이제 장비 확인 직후부터 데이터를 요청한다. 파형 응답이 없으면 그 기종의 정상 동작이므로 숫자값만 수집하며 로그에 한 번 알린다. Fabius GS 는 규격상 파형 기능이 없는 대표적인 기종이다 (University of Washington 제보). Added: SyncPulseSerial 동기 펄스 생성기 지원. 여러 장비의 기록을 나중에 맞추기 위한 공통 번호를 시리얼로 보내는 장치로, 1초마다 6자리 카운터를 보낸다. PULSE 트랙에 그 번호가 기록된다. 시작이나 끝을 알리는 문자가 없는 형식이라, 메시지 사이의 침묵을 경계로 삼고 카운터가 1씩 늘어나는지 확인해 맞는 장비인지 판단한다. 자동 판별 대상에도 포함돼 있고, 환자 모니터와 같은 자리에 함께 연결해도 서로 방해하지 않는다. 1.19.19 Added: Dräger 인공호흡기·마취기가 파형에 함께 실어 보내는 호흡 위상 신호를 기록한다. 장비가 흡기와 호기 전환 시점을 직접 알려주는 것이라 파형에서 역산하는 것보다 정확하다. VENT_PHASE 트랙에 흡기 시작 1, 호기 시작 0 으로 남는다. 시각은 파형과 같은 시간축을 쓴다. 종전에는 이 신호를 읽지 않고 버렸다. Added: 환기 모드를 기록한다(VENT_MODE). 모드는 VC-CMV, PC-BIPAP, SPN-CPAP 처럼 압력조절인지 용적조절인지를 알려주어 파형 해석에 직접 쓰인다. 장비가 띄우고 있는 다른 안내 문구도 VENT_MSG 로 남는다. VR 이 그동안 이 정보를 담은 명령을 아예 보내지 않고 있었다 — 이제 측정값·설정과 함께 번갈아 요청한다. 표기가 장비나 언어에 따라 달라도 되도록 문구가 아니라 규격 코드로 판별한다. Added: 파형이 서로 맞는지 확인할 근거를 로그에 남긴다. 용적은 유량을 적분한 것이므로 둘의 관계를 수치로 기록해 둔다. 다만 이것으로 파형 이름을 바꾸거나 경고하지는 않는다 — 용적조절 환기에서는 기도압도 용적에 거의 비례해서 이 수치만으로는 둘을 구별할 수 없기 때문이다. 1.19.18 Added: Y케이블로 엿보는 Dräger 연결에서 파형 종류를 자동으로 알아낸다. Y케이블 배선에 따라 장비가 보내는 것만 보이는 방과, 반대편 장치가 보내는 것까지 보이는 방이 있는데, 후자에서는 그 장치가 파형을 요청하는 내용이 VR 에도 보인다. 거기에 순서가 그대로 들어 있으므로 추정 없이 확정한다 (실제로 수술장 두 방을 재보니 한 방은 양방향, 한 방은 단방향이었다). 단방향인 방은 종전대로 wavs= 로 적어주거나 도착 순서 추정을 쓴다. Fixed: 엿보는 연결인데도 VR 이 자기가 고른 파형 순서를 적용해 이름이 어긋날 수 있던 문제 — 회선에 다른 장치가 있는지를 통신 내용으로 판별해(보낸 적 없는 명령의 응답이 보이면 다른 장치가 있는 것이다) 그런 경우 순서를 스스로 정하지 않는다. 어느 모드로 동작 중인지는 로그에 남는다. 1.19.17 Added: Y케이블로 엿보기만 하는 Dräger 연결에서 파형 이름을 확정할 수 있다. 이런 연결은 VR 이 장비에 아무것도 보낼 수 없어 어떤 파형이 어떤 순서로 오는지 물어볼 수가 없고, 그래서 도착 순서대로 추정해 기록해 왔다(이름이 실제와 다를 수 있었다). 이제 vr.conf 의 장비 항목에 wavs= 로 그 방의 순서를 적어두면 그대로 사용한다. 순서는 그 장비에 연결된 다른 장치가 정한 것이라 병원에서 알 수 있는 값이다. 예: wavs=AWP,AWF. 적지 않으면 종전과 같이 도착 순서로 추정하며, 추정 중이라는 사실과 확정하는 방법을 로그에 남긴다. 직결 연결에서는 종전대로 VR 이 직접 물어보므로 이 설정이 없어도 정확하다. Added: 회선에서 처음 보는 종류의 통신 프레임을 한 번씩 로그에 남긴다. 어떤 장치가 무엇을 주고받는지 현장에서 확인할 수 있어 연결 문제 진단에 쓴다. 1.19.16 Fixed: 1.19.15 에서 Y케이블로 엿보기만 하는 연결(서울대병원 수술장의 Dräger 전부가 이 방식)의 파형이 기록되지 않던 문제 — 1.19.15 는 VR 이 직접 요청한 파형만 기록하도록 바뀌었는데, 관찰 전용 연결에서는 VR 이 장비에 아무것도 보낼 수 없어(송신선이 끊겨 있다) 요청 자체를 못 하므로 들어오는 파형을 전부 버렸다. 이제 이런 연결에서는 1.19.14 이전과 같이 도착 순서대로 기록한다. 다른 장치가 설정해 둔 순서는 알 수 없으므로 이름이 실제와 다를 수 있고, 그 사실을 로그에 함께 남긴다. 1.19.15 를 이미 설치한 곳은 이 버전으로 올려야 한다. 1.19.15 Fixed: Dräger 마취기·인공호흡기(MEDIBUS)에서 파형이 엉뚱한 이름으로 기록되던 문제. 장비가 보내는 파형 묶음의 첫 바이트에는 “몇 번째로 요청한 파형이 들어있는가” 가 비트로 표시되는데, VR 은 그 비트를 세기만 하고 도착 순서대로 미리 정해둔 트랙(CO2, AWP, AWF)에 넣었다. 장비가 요청한 파형 중 일부만 보내면 그만큼 밀려 기록돼, 예를 들어 기도압(AWP)만 보내는 인공호흡기에서 그 파형이 CO2 로 저장됐다 (Evita V600 실측 제보). 이제 비트를 해석해 요청한 순서와 맞춰 넣는다. Fixed: 요청하는 파형이 CO2 와 기도압 두 가지로 고정돼 있어, 장비가 유량(Flow)이나 용적(Volume) 파형을 제공해도 받지 못했다. 이제 장비에게 먼저 제공 가능한 파형 목록을 물어보고 그중에서 요청한다. Added: vr.conf 의 장비 설정에 wavs= 로 받을 파형을 고를 수 있다 (예: wavs=AWP,AWF,VOL,CO2). 비워두면 장비가 제공하는 것을 앞에서부터 최대 4개까지 자동으로 받는다. 4개인 이유는 한 묶음에 최대 4개까지만 표시할 수 있기 때문이다. 지원 이름: AWP AWF PLETH VOL O2 CO2 CO2_KPA CO2_PCT AGENT HAL ENF ISO DES SEV PTRACH AWF_DEV (16진 코드도 가능). 파형을 늘리면 회선 대역을 더 쓰므로 9600bps 의 80% 를 넘으면 로그로 경고한다. 어떤 파형이 실제로 들어오고 있는지도 로그에 남겨 현장에서 확인할 수 있다. 1.19.14 Added: 서버에서 VR 로 내려보내는 명령(원격 시간 동기, 원격 업그레이드, 재시작, 설정 변경 등)을 다시 사용할 수 있다. 1.18.52 에서 이 경로를 통째로 막았는데, 시간 동기와 원격 업그레이드가 꼭 필요한 기관이 있어 되살렸다. 다만 원격으로 프로그램을 바꾸거나 설정을 덮어쓰는 것은 그 자체로 위험하므로 기본값은 여전히 전부 차단이고, vr.conf 에 SERVER_CONTROL 로 허용할 명령을 직접 적어야 동작한다 (예: SERVER_CONTROL=dt 는 시간 동기만, SERVER_CONTROL=dt,update 는 시간 동기와 업그레이드만, SERVER_CONTROL=1 은 전부 허용). 사용 가능한 명령은 update restart reboot edit_conf new_bed del_bed edit_bed dt add_event. 시간 동기는 예전과 달리 녹화 중에는 시계를 건너뛰지 않는다 — 녹화 중 시계를 갑자기 옮기면 파형에 공백이 생기거나 기록이 겹치므로, 녹화 중이면 서서히 당겨 맞추고(슬루) 녹화가 없을 때만 한번에 맞춘다. 2초 이내 차이는 무시한다. 예전 구현이 10분 이상 차이날 때 프로그램을 강제 종료하던 동작도 없앴다. 1.19.13 Fixed: 웹 모니터링 화면에서 EEG 파형이 0.1초 정도씩 끊겨 보이던 문제 — 화면 전송용으로 파형을 줄일 때 원본 주파수가 나누어떨어지지 않으면 일부 구간이 비어 그만큼 빈칸으로 보였다. 나누어떨어지는 비율로만 줄이도록 수정. 녹화 파일에는 원래 문제가 없었다(표시만의 문제). 1.19.12 Fixed: Dräger 마취기 연동 안정화 보완 — 시리얼 회선 자동판별 중 잡음이 잘못된 데이터로 오인될 여지를 차단. 1.19.11 Fixed: Dräger 마취기(Atlan 등 MEDIBUS 장비)에서 통신 오류 알람(COM1 failure)이 반복되고 파형이 전혀 기록되지 않던 문제. 원인 세 가지를 모두 수정했다. (1) 장비는 3초 안에 아무 신호라도 받지 못하면 연결을 끊는데, VR 이 5초마다 데이터를 요청하는 것 외에 보내는 것이 없어 매번 연결이 끊기고 재접속을 반복했다 — 규격이 정한 연결 유지 신호(NOP)를 2초마다 보내도록 했다(데이터 요청 주기는 그대로). (2) 장비가 응답 도중에 다른 명령을 끼워 보내는 것이 규격상 허용되는데, VR 이 그때 받던 응답을 통째로 버려 파형 설정 응답을 매번 놓쳤다 — 끼어든 부분을 건너뛰고 원래 응답을 이어받도록 했다. (3) 장비가 보내는 요청 하나에 VR 이 응답하지 않아 통신이 끊기던 것도 수정. 파형·수치가 정상 기록되고 알람이 사라진다. 1.19.10 Fixed: 하루 한 번의 자동 재시작 시점에 수집이 통째로 멎어 사람이 손을 대기 전까지 복구되지 않던 문제(or-106, 104분 무데이터) — 종료 과정이 끝나지 않아 프로그램이 죽지 않았고, 죽지 않으니 자동 재시작도 일어나지 않았다. 주원인은 시리얼 회선 자동판별(AUTO_DETECT)이 종료 신호를 보지 않고 스윕(보레이트 6종 x 포맷 8종)을 끝까지 돌던 것으로, 무음 회선에서 종료에 2분이 걸려 정상 종료 유예시간을 넘겼다. 이제 종료 즉시 판별을 중단한다(실측 2분 -> 3초). 함께 종료 중 스레드 정리가 서로를 기다릴 수 있던 두 곳을 제거하고, 그럼에도 종료가 20초 안에 끝나지 않으면 강제 종료해 반드시 재시작되도록 안전장치를 넣었다. 녹화 파일은 5초마다 저장되므로 유실되지 않는다. 1.19.9 Fixed: 큰 녹화 파일(약 100MB 이상)을 서버로 올릴 때 프로그램이 메모리 부족으로 강제 종료되고, 재시작 후 같은 파일을 다시 시도하다 무한 반복되던 문제 — 업로드 본문을 메모리에 통째로 만들지 않고 디스크에서 흘려보내도록 변경(100MB 파일 기준 최대 메모리 208MB → 14MB). 큰 파일이 한 개라도 쌓이면 그 장비의 이후 업로드가 전부 막히던 증상이 사라진다. 1.19.8 Fixed: 자동 파일 자르기(CUT_FILE) 를 쓰는 베드에서 환자가 계속 연결돼 있는데도 녹화 파일이 PT_WAITING_TIME(기본 5분) 마다 잘리던 문제 — 웹 모니터링 전송 후 메모리를 비우는 과정에서 환자 연결 판정에 쓰이는 최근 구간까지 지워져 "환자가 나갔다" 로 오판하던 것. 이제 판정에 필요한 구간은 보존한다. 파일이 5분마다 끊기고 그때마다 수 초가 유실되던 증상이 사라진다. 1.19.7 Added: GE Corometrics(분만 태아감시) 모체 vitals 지원 — 기존엔 태아 심박(HR1/HR2)·자궁수축(UACT)만 기록됐으나 이제 같은 장비가 함께 보내는 모체 SpO2/맥박(PR)/NIBP(수축·이완·평균)도 한 파일에 기록. Fixed: 프로세스 종료(서비스 매니저의 정상 종료 요청, SIGTERM) 시 즉시 종료되지 않고 강제 종료되던 문제 — 이제 SIGTERM 을 받아 녹화 버퍼를 flush 하고 스레드를 정리한 뒤 정상 종료. 1.19.5 Fixed: CUT_HOURLY 가 동작하지 않는 것처럼 보이던 문제의 실체 — k3s 컨테이너에서 기본 저장 경로가 $HOME/recordings(임시 레이어)로 잡혀, 매일 자정 재시작 때 업로드 안 된 하루치 녹화가 통째로 사라짐. hostPath 마운트 /recordings 가 있으면 그것을 기본 저장 경로로 사용. Fixed: 종료(daily restart) 시 멀티베드 TCP 노드가 terminate(exit=133)/SIGSEGV(139) 로 죽던 문제. Linux finalize 가 device 스레드를 정지하지 않고 반환해 exit() 전역 소멸자가 살아있는 PortRouter accept_thread 를 파괴하며 std::terminate — DISPLAY::quiesce_threads + PortRouter::shutdown 으로 정리 후 종료 (코어 백트레이스로 확정, 로컬 재현 후 수정 검증). Fixed: Link+/Agilia 파서가 시리얼 노이즈(size byte 0 또는 헤더 없는 잡음 8byte)에 역전된 반복자 범위로 vector 를 생성하다 std::length_error 로 죽던 문제(or-110) — sentinel 상태 파싱 봉쇄 + 재동기화. Fixed: FILENAME_TEMPLATE 의 %Y(4자리 연도)가 "0126" 으로 나오던 문제 (tm_year+1900 누락). 1.19.4 Fixed: Link+ 등 재접속이 잦은 장비에서 시리얼 포트를 재오픈할 때 "terminate called without an active exception" 으로 프로세스가 죽던 문제(exit=133). reading_thread_func 이 스스로 반환해도 std::thread 객체는 joinable 로 남는데, open() 이 join 없이 새 스레드를 대입해 std::terminate 가 호출됨 → 스레드 생성 전에 이전 reading/parsing 스레드를 join 후 재생성. Fixed: headless/AUTO_DETECT 모드에서 실제 장비가 없을 때 Demo 장비가 생성돼 가짜 vital 트랙 38종이 업로드되던 문제 → --console 또는 AUTO_DETECT 시 Demo 미생성. 1.19.3 Fixed: 여러 베드를 한 프로세스가 TCP 로 수집하는 구성에서 클라이언트 재접속이 잦으면 프로세스가 SIGSEGV 로 죽던 문제(accept_handoff 가 replace 시 옛 소켓을 즉시 close 해 recv 중인 reader 와 경합, fd 번호 재사용). tcp reader 를 poll(200ms) 게이트 뒤의 recv 로 재구성하고 replace 는 shutdown 후 소유권을 reader/close 로 단일화. SNUH 회복실 VRN 15대에서 재현. Fixed: ICU 등 Intellivue 다수 베드에서 반복되던 SIGSEGV(139) 두 계열의 use-after-free. (1) DISPLAY::add_rec 이 레코드를 트랙에 push 한 뒤 저장했는데, push 직후 p 가 다른 스레드에 공유되고 Linux 의 메모리 보존 루프(600초 이전 레코드 delete)가 시계 어긋난 모니터에서 방금 넣은 레코드를 즉시 evict 해, 이어지는 p->save() 가 해제된 메모리에 가상 호출 → 저장을 push 앞으로 옮겨 공유 후 p 미접근. (2) DISPLAY::del_trk 이 트랙을 목록에서 뺀 직후 raw delete 해, 파싱 스레드의 캐시 포인터·장비 이름맵·recorded_trks 가 죽은 포인터를 참조 (특히 IAP 채널변경의 del 직후 add 가 해제 포인터 반환) → DEVICE 규칙처럼 살아있는 동안 삭제 금지, ~DISPLAY 에서 일괄 해제. picu-11 코어 덤프로 크래시 지점(add_rec vtable 간접호출) 확정. SNUH fleet ~100 노드 실배포에서 기대 크래시 다수 대비 실제 0, 미배포 노드는 계속 크래시. Fixed: Linux from_utf16 이 locale 의존·비스레드안전 wcstombs 를 쓰고 UTF-16 을 UTF-32 wchar_t 로 오해석하던 문제 → 수동 UTF-8 인코더(서로게이트 페어 재조립, 무효값 U+FFFD)로 교체. Added: AUTO_DETECT=1(vr.conf) 시리얼 장비 자동 판별. 회선 측정(보레이트·프레임폭·패리티) → 후보 필터(3축 일치, 베드당 클래스 배타, 포트 사전확률, 판별캐시) → 트랙 수확량 경쟁. 드라이버 무수정, 미설정 시 유휴 대기라 기존 설정 무영향. SNUH 수술장·ICU 실측 오탐 0.
1.19.25
1.19.25 Fixed: SyncPulseSerial(동기 펄스 생성기)에서 1초 간격의 펄스가 2초마다 하나씩만 기록되거나, 보드 종류에 따라 아예 한 값도 기록되지 않던 문제. 두 증상 모두 원인이 하나다 — 이 장비는 시작·끝을 알리는 문자 없이 6자리 숫자만 흘려보내므로 VR 이 “잠깐의 침묵” 을 메시지 경계로 삼고 있었는데, 침묵은 회선이 아니라 컴퓨터 사정으로도 생긴다. 운영체제가 6바이트를 몇 번에 나눠 올려주고 그 사이가 벌어지면 앞자리가 버려져 이후 모든 값이 한 칸씩 밀리고, 한 번 밀리면 스스로 회복하지 못해 아무것도 기록되지 않는다(구형 Arduino UNO 에서 재현). 반대로 두 메시지가 한꺼번에 올라오면 둘 다 같은 시각으로 찍혀 1초짜리 펄스가 2초마다 하나 있는 것처럼 보인다(UNO R4 에서 재현). 이제 경계를 시간이 아니라 내용으로 찾는다 — 카운터가 정확히 1씩 늘어나는 자리를 스스로 찾아 맞추므로 회선이 어떻게 쪼개져 들어오든 영향을 받지 않고, 어긋나도 다음 펄스에서 다시 맞춘다. 한 번에 여러 펄스가 올라온 경우에는 카운터 간격만큼 시각을 되돌려 각각 제 시각에 기록한다. 세 가지 조건(정상·쪼개짐·뭉침) 재현 시험으로 확인했다 (Nord University AniLab 제보). Fixed: Windows 에서 데이터를 띄엄띄엄 보내는 시리얼 장비의 값이 최대 한 주기 늦게 들어오던 문제. 문자 도착 알림을 기다리는 동안이 아니라 그 직전 대기 중에 데이터가 통째로 들어와 버리면, 새로 도착하는 문자가 없어 알림이 오지 않고 이미 들어온 데이터가 다음 전송이 올 때까지 그대로 대기했다. 이제 알림이 없어도 수신 버퍼를 직접 확인한다. 초당 수십~수백 바이트를 보내는 대부분의 장비에서는 드러나지 않던 문제이지만, 1초에 한 번처럼 드물게 보내는 장비에서는 두 전송이 한 번에 처리되어 시각이 뭉쳤다. 1.19.24 Fixed: Windows 에서 SyncPulseSerial(Arduino UNO R4)의 데이터가 전혀 들어오지 않거나 한참 뒤에야 간헐적으로 들어오던 문제. Arduino 의 USB 시리얼은 컴퓨터 쪽이 준비 신호(DTR)를 올려야만 데이터를 내보내는데, VR 이 Windows 에서 포트를 열 때 이 신호를 내린 채로 열고 있었다. 시리얼 터미널 프로그램들은 이 신호를 기본으로 올리기 때문에 터미널에서는 잘 보이는데 VR 에서만 안 보이는 형태로 나타났다 (Win11/Win7 3대 실측, Nord University AniLab 제보). 장비 연결 상태는 정상으로 표시되므로 화면만 봐서는 알 수 없는 문제였다. 다른 시리얼 장비들의 동작은 그대로다 — 이 신호에 민감한 장비가 있을 수 있어 Arduino 계열 드라이버에서만 올린다. 1.19.23 Fixed: SyncPulseSerial 의 PULSE 값이 화면에 보이지 않던 문제. 값은 정상적으로 수신되고 파일에도 기록되고 있었는데, 화면 눈금 범위가 카운터의 최대값(864000)으로 잡혀 있어 테스트 초반의 작은 값들이 트랙 맨 바닥 1픽셀에 눌려 보이지 않았다. 이제 매초 가운데에 뚜렷한 선으로 표시된다. 기록된 값 자체는 이전 버전에서도 온전했다(표시만의 문제). 1.19.22 Added: Dräger 마취기를 기종 이름으로 고를 수 있다. 장비 목록에 Fabius 가 추가됐고, 목록에 없는 Dräger 를 쓰는 곳을 위해 Medibus 라는 일반 항목도 함께 넣었다. Primus, Fabius, Medibus 는 통신 방식이 완전히 같고 이름만 다르므로 어느 것을 골라도 동작은 같지만, 실제 기종을 고르면 설정 파일과 화면이 무엇이 연결돼 있는지 스스로 설명하게 된다. 기존에 Primus 로 설정해 둔 곳은 그대로 두면 된다. Changed: 시리얼 장비 자동 판별이 마취기를 특정 기종이 아니라 통신 방식 이름(Medibus)으로 인식한다. 같은 통신 방식을 쓰는 기종들은 회선만 봐서는 구별할 수 없으므로, 근거 없이 모델명을 단정하지 않는 편이 낫다고 판단했다. 기종 이름이 필요하면 설정에서 직접 고르면 된다. 1.19.21 Added: Dräger 장비가 스스로 알려주는 기종명을 로그에 남긴다. 설정 파일의 장비 이름은 사람이 적어 넣은 것이라 실제 기종을 보장하지 않는데, 기종에 따라 낼 수 있는 파형이 다르므로(예: Fabius 는 기도압과 유량은 있고 CO2 가 없다) 무엇이 연결돼 있는지 아는 것이 문제 해결의 출발점이다. 통신이 시작될 때 한 번 남는다. 1.19.20 Fixed: Dräger 마취기 가운데 파형 기능이 없는 기종에서 데이터가 전혀 수집되지 않던 문제. 연결은 된 것처럼 보이지만 아무 값도 들어오지 않는다. VR 이 장비를 확인한 뒤 파형 설정을 물어보고 그 답을 받아야만 데이터 요청을 시작하도록 돼 있었는데, 파형 기능이 없는 기종은 그 질문에 답하지 않으므로 요청이 영영 시작되지 않았다. 연결 유지 신호도 함께 멎어 장비가 통신을 끊고 다시 연결하기를 반복한다. 이제 장비 확인 직후부터 데이터를 요청한다. 파형 응답이 없으면 그 기종의 정상 동작이므로 숫자값만 수집하며 로그에 한 번 알린다. Fabius GS 는 규격상 파형 기능이 없는 대표적인 기종이다 (University of Washington 제보). Added: SyncPulseSerial 동기 펄스 생성기 지원. 여러 장비의 기록을 나중에 맞추기 위한 공통 번호를 시리얼로 보내는 장치로, 1초마다 6자리 카운터를 보낸다. PULSE 트랙에 그 번호가 기록된다. 시작이나 끝을 알리는 문자가 없는 형식이라, 메시지 사이의 침묵을 경계로 삼고 카운터가 1씩 늘어나는지 확인해 맞는 장비인지 판단한다. 자동 판별 대상에도 포함돼 있고, 환자 모니터와 같은 자리에 함께 연결해도 서로 방해하지 않는다. 1.19.19 Added: Dräger 인공호흡기·마취기가 파형에 함께 실어 보내는 호흡 위상 신호를 기록한다. 장비가 흡기와 호기 전환 시점을 직접 알려주는 것이라 파형에서 역산하는 것보다 정확하다. VENT_PHASE 트랙에 흡기 시작 1, 호기 시작 0 으로 남는다. 시각은 파형과 같은 시간축을 쓴다. 종전에는 이 신호를 읽지 않고 버렸다. Added: 환기 모드를 기록한다(VENT_MODE). 모드는 VC-CMV, PC-BIPAP, SPN-CPAP 처럼 압력조절인지 용적조절인지를 알려주어 파형 해석에 직접 쓰인다. 장비가 띄우고 있는 다른 안내 문구도 VENT_MSG 로 남는다. VR 이 그동안 이 정보를 담은 명령을 아예 보내지 않고 있었다 — 이제 측정값·설정과 함께 번갈아 요청한다. 표기가 장비나 언어에 따라 달라도 되도록 문구가 아니라 규격 코드로 판별한다. Added: 파형이 서로 맞는지 확인할 근거를 로그에 남긴다. 용적은 유량을 적분한 것이므로 둘의 관계를 수치로 기록해 둔다. 다만 이것으로 파형 이름을 바꾸거나 경고하지는 않는다 — 용적조절 환기에서는 기도압도 용적에 거의 비례해서 이 수치만으로는 둘을 구별할 수 없기 때문이다. 1.19.18 Added: Y케이블로 엿보는 Dräger 연결에서 파형 종류를 자동으로 알아낸다. Y케이블 배선에 따라 장비가 보내는 것만 보이는 방과, 반대편 장치가 보내는 것까지 보이는 방이 있는데, 후자에서는 그 장치가 파형을 요청하는 내용이 VR 에도 보인다. 거기에 순서가 그대로 들어 있으므로 추정 없이 확정한다 (실제로 수술장 두 방을 재보니 한 방은 양방향, 한 방은 단방향이었다). 단방향인 방은 종전대로 wavs= 로 적어주거나 도착 순서 추정을 쓴다. Fixed: 엿보는 연결인데도 VR 이 자기가 고른 파형 순서를 적용해 이름이 어긋날 수 있던 문제 — 회선에 다른 장치가 있는지를 통신 내용으로 판별해(보낸 적 없는 명령의 응답이 보이면 다른 장치가 있는 것이다) 그런 경우 순서를 스스로 정하지 않는다. 어느 모드로 동작 중인지는 로그에 남는다. 1.19.17 Added: Y케이블로 엿보기만 하는 Dräger 연결에서 파형 이름을 확정할 수 있다. 이런 연결은 VR 이 장비에 아무것도 보낼 수 없어 어떤 파형이 어떤 순서로 오는지 물어볼 수가 없고, 그래서 도착 순서대로 추정해 기록해 왔다(이름이 실제와 다를 수 있었다). 이제 vr.conf 의 장비 항목에 wavs= 로 그 방의 순서를 적어두면 그대로 사용한다. 순서는 그 장비에 연결된 다른 장치가 정한 것이라 병원에서 알 수 있는 값이다. 예: wavs=AWP,AWF. 적지 않으면 종전과 같이 도착 순서로 추정하며, 추정 중이라는 사실과 확정하는 방법을 로그에 남긴다. 직결 연결에서는 종전대로 VR 이 직접 물어보므로 이 설정이 없어도 정확하다. Added: 회선에서 처음 보는 종류의 통신 프레임을 한 번씩 로그에 남긴다. 어떤 장치가 무엇을 주고받는지 현장에서 확인할 수 있어 연결 문제 진단에 쓴다. 1.19.16 Fixed: 1.19.15 에서 Y케이블로 엿보기만 하는 연결(서울대병원 수술장의 Dräger 전부가 이 방식)의 파형이 기록되지 않던 문제 — 1.19.15 는 VR 이 직접 요청한 파형만 기록하도록 바뀌었는데, 관찰 전용 연결에서는 VR 이 장비에 아무것도 보낼 수 없어(송신선이 끊겨 있다) 요청 자체를 못 하므로 들어오는 파형을 전부 버렸다. 이제 이런 연결에서는 1.19.14 이전과 같이 도착 순서대로 기록한다. 다른 장치가 설정해 둔 순서는 알 수 없으므로 이름이 실제와 다를 수 있고, 그 사실을 로그에 함께 남긴다. 1.19.15 를 이미 설치한 곳은 이 버전으로 올려야 한다. 1.19.15 Fixed: Dräger 마취기·인공호흡기(MEDIBUS)에서 파형이 엉뚱한 이름으로 기록되던 문제. 장비가 보내는 파형 묶음의 첫 바이트에는 “몇 번째로 요청한 파형이 들어있는가” 가 비트로 표시되는데, VR 은 그 비트를 세기만 하고 도착 순서대로 미리 정해둔 트랙(CO2, AWP, AWF)에 넣었다. 장비가 요청한 파형 중 일부만 보내면 그만큼 밀려 기록돼, 예를 들어 기도압(AWP)만 보내는 인공호흡기에서 그 파형이 CO2 로 저장됐다 (Evita V600 실측 제보). 이제 비트를 해석해 요청한 순서와 맞춰 넣는다. Fixed: 요청하는 파형이 CO2 와 기도압 두 가지로 고정돼 있어, 장비가 유량(Flow)이나 용적(Volume) 파형을 제공해도 받지 못했다. 이제 장비에게 먼저 제공 가능한 파형 목록을 물어보고 그중에서 요청한다. Added: vr.conf 의 장비 설정에 wavs= 로 받을 파형을 고를 수 있다 (예: wavs=AWP,AWF,VOL,CO2). 비워두면 장비가 제공하는 것을 앞에서부터 최대 4개까지 자동으로 받는다. 4개인 이유는 한 묶음에 최대 4개까지만 표시할 수 있기 때문이다. 지원 이름: AWP AWF PLETH VOL O2 CO2 CO2_KPA CO2_PCT AGENT HAL ENF ISO DES SEV PTRACH AWF_DEV (16진 코드도 가능). 파형을 늘리면 회선 대역을 더 쓰므로 9600bps 의 80% 를 넘으면 로그로 경고한다. 어떤 파형이 실제로 들어오고 있는지도 로그에 남겨 현장에서 확인할 수 있다. 1.19.14 Added: 서버에서 VR 로 내려보내는 명령(원격 시간 동기, 원격 업그레이드, 재시작, 설정 변경 등)을 다시 사용할 수 있다. 1.18.52 에서 이 경로를 통째로 막았는데, 시간 동기와 원격 업그레이드가 꼭 필요한 기관이 있어 되살렸다. 다만 원격으로 프로그램을 바꾸거나 설정을 덮어쓰는 것은 그 자체로 위험하므로 기본값은 여전히 전부 차단이고, vr.conf 에 SERVER_CONTROL 로 허용할 명령을 직접 적어야 동작한다 (예: SERVER_CONTROL=dt 는 시간 동기만, SERVER_CONTROL=dt,update 는 시간 동기와 업그레이드만, SERVER_CONTROL=1 은 전부 허용). 사용 가능한 명령은 update restart reboot edit_conf new_bed del_bed edit_bed dt add_event. 시간 동기는 예전과 달리 녹화 중에는 시계를 건너뛰지 않는다 — 녹화 중 시계를 갑자기 옮기면 파형에 공백이 생기거나 기록이 겹치므로, 녹화 중이면 서서히 당겨 맞추고(슬루) 녹화가 없을 때만 한번에 맞춘다. 2초 이내 차이는 무시한다. 예전 구현이 10분 이상 차이날 때 프로그램을 강제 종료하던 동작도 없앴다. 1.19.13 Fixed: 웹 모니터링 화면에서 EEG 파형이 0.1초 정도씩 끊겨 보이던 문제 — 화면 전송용으로 파형을 줄일 때 원본 주파수가 나누어떨어지지 않으면 일부 구간이 비어 그만큼 빈칸으로 보였다. 나누어떨어지는 비율로만 줄이도록 수정. 녹화 파일에는 원래 문제가 없었다(표시만의 문제). 1.19.12 Fixed: Dräger 마취기 연동 안정화 보완 — 시리얼 회선 자동판별 중 잡음이 잘못된 데이터로 오인될 여지를 차단. 1.19.11 Fixed: Dräger 마취기(Atlan 등 MEDIBUS 장비)에서 통신 오류 알람(COM1 failure)이 반복되고 파형이 전혀 기록되지 않던 문제. 원인 세 가지를 모두 수정했다. (1) 장비는 3초 안에 아무 신호라도 받지 못하면 연결을 끊는데, VR 이 5초마다 데이터를 요청하는 것 외에 보내는 것이 없어 매번 연결이 끊기고 재접속을 반복했다 — 규격이 정한 연결 유지 신호(NOP)를 2초마다 보내도록 했다(데이터 요청 주기는 그대로). (2) 장비가 응답 도중에 다른 명령을 끼워 보내는 것이 규격상 허용되는데, VR 이 그때 받던 응답을 통째로 버려 파형 설정 응답을 매번 놓쳤다 — 끼어든 부분을 건너뛰고 원래 응답을 이어받도록 했다. (3) 장비가 보내는 요청 하나에 VR 이 응답하지 않아 통신이 끊기던 것도 수정. 파형·수치가 정상 기록되고 알람이 사라진다. 1.19.10 Fixed: 하루 한 번의 자동 재시작 시점에 수집이 통째로 멎어 사람이 손을 대기 전까지 복구되지 않던 문제(or-106, 104분 무데이터) — 종료 과정이 끝나지 않아 프로그램이 죽지 않았고, 죽지 않으니 자동 재시작도 일어나지 않았다. 주원인은 시리얼 회선 자동판별(AUTO_DETECT)이 종료 신호를 보지 않고 스윕(보레이트 6종 x 포맷 8종)을 끝까지 돌던 것으로, 무음 회선에서 종료에 2분이 걸려 정상 종료 유예시간을 넘겼다. 이제 종료 즉시 판별을 중단한다(실측 2분 -> 3초). 함께 종료 중 스레드 정리가 서로를 기다릴 수 있던 두 곳을 제거하고, 그럼에도 종료가 20초 안에 끝나지 않으면 강제 종료해 반드시 재시작되도록 안전장치를 넣었다. 녹화 파일은 5초마다 저장되므로 유실되지 않는다. 1.19.9 Fixed: 큰 녹화 파일(약 100MB 이상)을 서버로 올릴 때 프로그램이 메모리 부족으로 강제 종료되고, 재시작 후 같은 파일을 다시 시도하다 무한 반복되던 문제 — 업로드 본문을 메모리에 통째로 만들지 않고 디스크에서 흘려보내도록 변경(100MB 파일 기준 최대 메모리 208MB → 14MB). 큰 파일이 한 개라도 쌓이면 그 장비의 이후 업로드가 전부 막히던 증상이 사라진다. 1.19.8 Fixed: 자동 파일 자르기(CUT_FILE) 를 쓰는 베드에서 환자가 계속 연결돼 있는데도 녹화 파일이 PT_WAITING_TIME(기본 5분) 마다 잘리던 문제 — 웹 모니터링 전송 후 메모리를 비우는 과정에서 환자 연결 판정에 쓰이는 최근 구간까지 지워져 "환자가 나갔다" 로 오판하던 것. 이제 판정에 필요한 구간은 보존한다. 파일이 5분마다 끊기고 그때마다 수 초가 유실되던 증상이 사라진다. 1.19.7 Added: GE Corometrics(분만 태아감시) 모체 vitals 지원 — 기존엔 태아 심박(HR1/HR2)·자궁수축(UACT)만 기록됐으나 이제 같은 장비가 함께 보내는 모체 SpO2/맥박(PR)/NIBP(수축·이완·평균)도 한 파일에 기록. Fixed: 프로세스 종료(서비스 매니저의 정상 종료 요청, SIGTERM) 시 즉시 종료되지 않고 강제 종료되던 문제 — 이제 SIGTERM 을 받아 녹화 버퍼를 flush 하고 스레드를 정리한 뒤 정상 종료. 1.19.5 Fixed: CUT_HOURLY 가 동작하지 않는 것처럼 보이던 문제의 실체 — k3s 컨테이너에서 기본 저장 경로가 $HOME/recordings(임시 레이어)로 잡혀, 매일 자정 재시작 때 업로드 안 된 하루치 녹화가 통째로 사라짐. hostPath 마운트 /recordings 가 있으면 그것을 기본 저장 경로로 사용. Fixed: 종료(daily restart) 시 멀티베드 TCP 노드가 terminate(exit=133)/SIGSEGV(139) 로 죽던 문제. Linux finalize 가 device 스레드를 정지하지 않고 반환해 exit() 전역 소멸자가 살아있는 PortRouter accept_thread 를 파괴하며 std::terminate — DISPLAY::quiesce_threads + PortRouter::shutdown 으로 정리 후 종료 (코어 백트레이스로 확정, 로컬 재현 후 수정 검증). Fixed: Link+/Agilia 파서가 시리얼 노이즈(size byte 0 또는 헤더 없는 잡음 8byte)에 역전된 반복자 범위로 vector 를 생성하다 std::length_error 로 죽던 문제(or-110) — sentinel 상태 파싱 봉쇄 + 재동기화. Fixed: FILENAME_TEMPLATE 의 %Y(4자리 연도)가 "0126" 으로 나오던 문제 (tm_year+1900 누락). 1.19.4 Fixed: Link+ 등 재접속이 잦은 장비에서 시리얼 포트를 재오픈할 때 "terminate called without an active exception" 으로 프로세스가 죽던 문제(exit=133). reading_thread_func 이 스스로 반환해도 std::thread 객체는 joinable 로 남는데, open() 이 join 없이 새 스레드를 대입해 std::terminate 가 호출됨 → 스레드 생성 전에 이전 reading/parsing 스레드를 join 후 재생성. Fixed: headless/AUTO_DETECT 모드에서 실제 장비가 없을 때 Demo 장비가 생성돼 가짜 vital 트랙 38종이 업로드되던 문제 → --console 또는 AUTO_DETECT 시 Demo 미생성. 1.19.3 Fixed: 여러 베드를 한 프로세스가 TCP 로 수집하는 구성에서 클라이언트 재접속이 잦으면 프로세스가 SIGSEGV 로 죽던 문제(accept_handoff 가 replace 시 옛 소켓을 즉시 close 해 recv 중인 reader 와 경합, fd 번호 재사용). tcp reader 를 poll(200ms) 게이트 뒤의 recv 로 재구성하고 replace 는 shutdown 후 소유권을 reader/close 로 단일화. SNUH 회복실 VRN 15대에서 재현. Fixed: ICU 등 Intellivue 다수 베드에서 반복되던 SIGSEGV(139) 두 계열의 use-after-free. (1) DISPLAY::add_rec 이 레코드를 트랙에 push 한 뒤 저장했는데, push 직후 p 가 다른 스레드에 공유되고 Linux 의 메모리 보존 루프(600초 이전 레코드 delete)가 시계 어긋난 모니터에서 방금 넣은 레코드를 즉시 evict 해, 이어지는 p->save() 가 해제된 메모리에 가상 호출 → 저장을 push 앞으로 옮겨 공유 후 p 미접근. (2) DISPLAY::del_trk 이 트랙을 목록에서 뺀 직후 raw delete 해, 파싱 스레드의 캐시 포인터·장비 이름맵·recorded_trks 가 죽은 포인터를 참조 (특히 IAP 채널변경의 del 직후 add 가 해제 포인터 반환) → DEVICE 규칙처럼 살아있는 동안 삭제 금지, ~DISPLAY 에서 일괄 해제. picu-11 코어 덤프로 크래시 지점(add_rec vtable 간접호출) 확정. SNUH fleet ~100 노드 실배포에서 기대 크래시 다수 대비 실제 0, 미배포 노드는 계속 크래시. Fixed: Linux from_utf16 이 locale 의존·비스레드안전 wcstombs 를 쓰고 UTF-16 을 UTF-32 wchar_t 로 오해석하던 문제 → 수동 UTF-8 인코더(서로게이트 페어 재조립, 무효값 U+FFFD)로 교체. Added: AUTO_DETECT=1(vr.conf) 시리얼 장비 자동 판별. 회선 측정(보레이트·프레임폭·패리티) → 후보 필터(3축 일치, 베드당 클래스 배타, 포트 사전확률, 판별캐시) → 트랙 수확량 경쟁. 드라이버 무수정, 미설정 시 유휴 대기라 기존 설정 무영향. SNUH 수술장·ICU 실측 오탐 0.
1.19.24
1.19.24 Fixed: Windows 에서 SyncPulseSerial(Arduino UNO R4)의 데이터가 전혀 들어오지 않거나 한참 뒤에야 간헐적으로 들어오던 문제. Arduino 의 USB 시리얼은 컴퓨터 쪽이 준비 신호(DTR)를 올려야만 데이터를 내보내는데, VR 이 Windows 에서 포트를 열 때 이 신호를 내린 채로 열고 있었다. 시리얼 터미널 프로그램들은 이 신호를 기본으로 올리기 때문에 터미널에서는 잘 보이는데 VR 에서만 안 보이는 형태로 나타났다 (Win11/Win7 3대 실측, Nord University AniLab 제보). 장비 연결 상태는 정상으로 표시되므로 화면만 봐서는 알 수 없는 문제였다. 다른 시리얼 장비들의 동작은 그대로다 — 이 신호에 민감한 장비가 있을 수 있어 Arduino 계열 드라이버에서만 올린다. 1.19.23 Fixed: SyncPulseSerial 의 PULSE 값이 화면에 보이지 않던 문제. 값은 정상적으로 수신되고 파일에도 기록되고 있었는데, 화면 눈금 범위가 카운터의 최대값(864000)으로 잡혀 있어 테스트 초반의 작은 값들이 트랙 맨 바닥 1픽셀에 눌려 보이지 않았다. 이제 매초 가운데에 뚜렷한 선으로 표시된다. 기록된 값 자체는 이전 버전에서도 온전했다(표시만의 문제). 1.19.22 Added: Dräger 마취기를 기종 이름으로 고를 수 있다. 장비 목록에 Fabius 가 추가됐고, 목록에 없는 Dräger 를 쓰는 곳을 위해 Medibus 라는 일반 항목도 함께 넣었다. Primus, Fabius, Medibus 는 통신 방식이 완전히 같고 이름만 다르므로 어느 것을 골라도 동작은 같지만, 실제 기종을 고르면 설정 파일과 화면이 무엇이 연결돼 있는지 스스로 설명하게 된다. 기존에 Primus 로 설정해 둔 곳은 그대로 두면 된다. Changed: 시리얼 장비 자동 판별이 마취기를 특정 기종이 아니라 통신 방식 이름(Medibus)으로 인식한다. 같은 통신 방식을 쓰는 기종들은 회선만 봐서는 구별할 수 없으므로, 근거 없이 모델명을 단정하지 않는 편이 낫다고 판단했다. 기종 이름이 필요하면 설정에서 직접 고르면 된다. 1.19.21 Added: Dräger 장비가 스스로 알려주는 기종명을 로그에 남긴다. 설정 파일의 장비 이름은 사람이 적어 넣은 것이라 실제 기종을 보장하지 않는데, 기종에 따라 낼 수 있는 파형이 다르므로(예: Fabius 는 기도압과 유량은 있고 CO2 가 없다) 무엇이 연결돼 있는지 아는 것이 문제 해결의 출발점이다. 통신이 시작될 때 한 번 남는다. 1.19.20 Fixed: Dräger 마취기 가운데 파형 기능이 없는 기종에서 데이터가 전혀 수집되지 않던 문제. 연결은 된 것처럼 보이지만 아무 값도 들어오지 않는다. VR 이 장비를 확인한 뒤 파형 설정을 물어보고 그 답을 받아야만 데이터 요청을 시작하도록 돼 있었는데, 파형 기능이 없는 기종은 그 질문에 답하지 않으므로 요청이 영영 시작되지 않았다. 연결 유지 신호도 함께 멎어 장비가 통신을 끊고 다시 연결하기를 반복한다. 이제 장비 확인 직후부터 데이터를 요청한다. 파형 응답이 없으면 그 기종의 정상 동작이므로 숫자값만 수집하며 로그에 한 번 알린다. Fabius GS 는 규격상 파형 기능이 없는 대표적인 기종이다 (University of Washington 제보). Added: SyncPulseSerial 동기 펄스 생성기 지원. 여러 장비의 기록을 나중에 맞추기 위한 공통 번호를 시리얼로 보내는 장치로, 1초마다 6자리 카운터를 보낸다. PULSE 트랙에 그 번호가 기록된다. 시작이나 끝을 알리는 문자가 없는 형식이라, 메시지 사이의 침묵을 경계로 삼고 카운터가 1씩 늘어나는지 확인해 맞는 장비인지 판단한다. 자동 판별 대상에도 포함돼 있고, 환자 모니터와 같은 자리에 함께 연결해도 서로 방해하지 않는다. 1.19.19 Added: Dräger 인공호흡기·마취기가 파형에 함께 실어 보내는 호흡 위상 신호를 기록한다. 장비가 흡기와 호기 전환 시점을 직접 알려주는 것이라 파형에서 역산하는 것보다 정확하다. VENT_PHASE 트랙에 흡기 시작 1, 호기 시작 0 으로 남는다. 시각은 파형과 같은 시간축을 쓴다. 종전에는 이 신호를 읽지 않고 버렸다. Added: 환기 모드를 기록한다(VENT_MODE). 모드는 VC-CMV, PC-BIPAP, SPN-CPAP 처럼 압력조절인지 용적조절인지를 알려주어 파형 해석에 직접 쓰인다. 장비가 띄우고 있는 다른 안내 문구도 VENT_MSG 로 남는다. VR 이 그동안 이 정보를 담은 명령을 아예 보내지 않고 있었다 — 이제 측정값·설정과 함께 번갈아 요청한다. 표기가 장비나 언어에 따라 달라도 되도록 문구가 아니라 규격 코드로 판별한다. Added: 파형이 서로 맞는지 확인할 근거를 로그에 남긴다. 용적은 유량을 적분한 것이므로 둘의 관계를 수치로 기록해 둔다. 다만 이것으로 파형 이름을 바꾸거나 경고하지는 않는다 — 용적조절 환기에서는 기도압도 용적에 거의 비례해서 이 수치만으로는 둘을 구별할 수 없기 때문이다. 1.19.18 Added: Y케이블로 엿보는 Dräger 연결에서 파형 종류를 자동으로 알아낸다. Y케이블 배선에 따라 장비가 보내는 것만 보이는 방과, 반대편 장치가 보내는 것까지 보이는 방이 있는데, 후자에서는 그 장치가 파형을 요청하는 내용이 VR 에도 보인다. 거기에 순서가 그대로 들어 있으므로 추정 없이 확정한다 (실제로 수술장 두 방을 재보니 한 방은 양방향, 한 방은 단방향이었다). 단방향인 방은 종전대로 wavs= 로 적어주거나 도착 순서 추정을 쓴다. Fixed: 엿보는 연결인데도 VR 이 자기가 고른 파형 순서를 적용해 이름이 어긋날 수 있던 문제 — 회선에 다른 장치가 있는지를 통신 내용으로 판별해(보낸 적 없는 명령의 응답이 보이면 다른 장치가 있는 것이다) 그런 경우 순서를 스스로 정하지 않는다. 어느 모드로 동작 중인지는 로그에 남는다. 1.19.17 Added: Y케이블로 엿보기만 하는 Dräger 연결에서 파형 이름을 확정할 수 있다. 이런 연결은 VR 이 장비에 아무것도 보낼 수 없어 어떤 파형이 어떤 순서로 오는지 물어볼 수가 없고, 그래서 도착 순서대로 추정해 기록해 왔다(이름이 실제와 다를 수 있었다). 이제 vr.conf 의 장비 항목에 wavs= 로 그 방의 순서를 적어두면 그대로 사용한다. 순서는 그 장비에 연결된 다른 장치가 정한 것이라 병원에서 알 수 있는 값이다. 예: wavs=AWP,AWF. 적지 않으면 종전과 같이 도착 순서로 추정하며, 추정 중이라는 사실과 확정하는 방법을 로그에 남긴다. 직결 연결에서는 종전대로 VR 이 직접 물어보므로 이 설정이 없어도 정확하다. Added: 회선에서 처음 보는 종류의 통신 프레임을 한 번씩 로그에 남긴다. 어떤 장치가 무엇을 주고받는지 현장에서 확인할 수 있어 연결 문제 진단에 쓴다. 1.19.16 Fixed: 1.19.15 에서 Y케이블로 엿보기만 하는 연결(서울대병원 수술장의 Dräger 전부가 이 방식)의 파형이 기록되지 않던 문제 — 1.19.15 는 VR 이 직접 요청한 파형만 기록하도록 바뀌었는데, 관찰 전용 연결에서는 VR 이 장비에 아무것도 보낼 수 없어(송신선이 끊겨 있다) 요청 자체를 못 하므로 들어오는 파형을 전부 버렸다. 이제 이런 연결에서는 1.19.14 이전과 같이 도착 순서대로 기록한다. 다른 장치가 설정해 둔 순서는 알 수 없으므로 이름이 실제와 다를 수 있고, 그 사실을 로그에 함께 남긴다. 1.19.15 를 이미 설치한 곳은 이 버전으로 올려야 한다. 1.19.15 Fixed: Dräger 마취기·인공호흡기(MEDIBUS)에서 파형이 엉뚱한 이름으로 기록되던 문제. 장비가 보내는 파형 묶음의 첫 바이트에는 “몇 번째로 요청한 파형이 들어있는가” 가 비트로 표시되는데, VR 은 그 비트를 세기만 하고 도착 순서대로 미리 정해둔 트랙(CO2, AWP, AWF)에 넣었다. 장비가 요청한 파형 중 일부만 보내면 그만큼 밀려 기록돼, 예를 들어 기도압(AWP)만 보내는 인공호흡기에서 그 파형이 CO2 로 저장됐다 (Evita V600 실측 제보). 이제 비트를 해석해 요청한 순서와 맞춰 넣는다. Fixed: 요청하는 파형이 CO2 와 기도압 두 가지로 고정돼 있어, 장비가 유량(Flow)이나 용적(Volume) 파형을 제공해도 받지 못했다. 이제 장비에게 먼저 제공 가능한 파형 목록을 물어보고 그중에서 요청한다. Added: vr.conf 의 장비 설정에 wavs= 로 받을 파형을 고를 수 있다 (예: wavs=AWP,AWF,VOL,CO2). 비워두면 장비가 제공하는 것을 앞에서부터 최대 4개까지 자동으로 받는다. 4개인 이유는 한 묶음에 최대 4개까지만 표시할 수 있기 때문이다. 지원 이름: AWP AWF PLETH VOL O2 CO2 CO2_KPA CO2_PCT AGENT HAL ENF ISO DES SEV PTRACH AWF_DEV (16진 코드도 가능). 파형을 늘리면 회선 대역을 더 쓰므로 9600bps 의 80% 를 넘으면 로그로 경고한다. 어떤 파형이 실제로 들어오고 있는지도 로그에 남겨 현장에서 확인할 수 있다. 1.19.14 Added: 서버에서 VR 로 내려보내는 명령(원격 시간 동기, 원격 업그레이드, 재시작, 설정 변경 등)을 다시 사용할 수 있다. 1.18.52 에서 이 경로를 통째로 막았는데, 시간 동기와 원격 업그레이드가 꼭 필요한 기관이 있어 되살렸다. 다만 원격으로 프로그램을 바꾸거나 설정을 덮어쓰는 것은 그 자체로 위험하므로 기본값은 여전히 전부 차단이고, vr.conf 에 SERVER_CONTROL 로 허용할 명령을 직접 적어야 동작한다 (예: SERVER_CONTROL=dt 는 시간 동기만, SERVER_CONTROL=dt,update 는 시간 동기와 업그레이드만, SERVER_CONTROL=1 은 전부 허용). 사용 가능한 명령은 update restart reboot edit_conf new_bed del_bed edit_bed dt add_event. 시간 동기는 예전과 달리 녹화 중에는 시계를 건너뛰지 않는다 — 녹화 중 시계를 갑자기 옮기면 파형에 공백이 생기거나 기록이 겹치므로, 녹화 중이면 서서히 당겨 맞추고(슬루) 녹화가 없을 때만 한번에 맞춘다. 2초 이내 차이는 무시한다. 예전 구현이 10분 이상 차이날 때 프로그램을 강제 종료하던 동작도 없앴다. 1.19.13 Fixed: 웹 모니터링 화면에서 EEG 파형이 0.1초 정도씩 끊겨 보이던 문제 — 화면 전송용으로 파형을 줄일 때 원본 주파수가 나누어떨어지지 않으면 일부 구간이 비어 그만큼 빈칸으로 보였다. 나누어떨어지는 비율로만 줄이도록 수정. 녹화 파일에는 원래 문제가 없었다(표시만의 문제). 1.19.12 Fixed: Dräger 마취기 연동 안정화 보완 — 시리얼 회선 자동판별 중 잡음이 잘못된 데이터로 오인될 여지를 차단. 1.19.11 Fixed: Dräger 마취기(Atlan 등 MEDIBUS 장비)에서 통신 오류 알람(COM1 failure)이 반복되고 파형이 전혀 기록되지 않던 문제. 원인 세 가지를 모두 수정했다. (1) 장비는 3초 안에 아무 신호라도 받지 못하면 연결을 끊는데, VR 이 5초마다 데이터를 요청하는 것 외에 보내는 것이 없어 매번 연결이 끊기고 재접속을 반복했다 — 규격이 정한 연결 유지 신호(NOP)를 2초마다 보내도록 했다(데이터 요청 주기는 그대로). (2) 장비가 응답 도중에 다른 명령을 끼워 보내는 것이 규격상 허용되는데, VR 이 그때 받던 응답을 통째로 버려 파형 설정 응답을 매번 놓쳤다 — 끼어든 부분을 건너뛰고 원래 응답을 이어받도록 했다. (3) 장비가 보내는 요청 하나에 VR 이 응답하지 않아 통신이 끊기던 것도 수정. 파형·수치가 정상 기록되고 알람이 사라진다. 1.19.10 Fixed: 하루 한 번의 자동 재시작 시점에 수집이 통째로 멎어 사람이 손을 대기 전까지 복구되지 않던 문제(or-106, 104분 무데이터) — 종료 과정이 끝나지 않아 프로그램이 죽지 않았고, 죽지 않으니 자동 재시작도 일어나지 않았다. 주원인은 시리얼 회선 자동판별(AUTO_DETECT)이 종료 신호를 보지 않고 스윕(보레이트 6종 x 포맷 8종)을 끝까지 돌던 것으로, 무음 회선에서 종료에 2분이 걸려 정상 종료 유예시간을 넘겼다. 이제 종료 즉시 판별을 중단한다(실측 2분 -> 3초). 함께 종료 중 스레드 정리가 서로를 기다릴 수 있던 두 곳을 제거하고, 그럼에도 종료가 20초 안에 끝나지 않으면 강제 종료해 반드시 재시작되도록 안전장치를 넣었다. 녹화 파일은 5초마다 저장되므로 유실되지 않는다. 1.19.9 Fixed: 큰 녹화 파일(약 100MB 이상)을 서버로 올릴 때 프로그램이 메모리 부족으로 강제 종료되고, 재시작 후 같은 파일을 다시 시도하다 무한 반복되던 문제 — 업로드 본문을 메모리에 통째로 만들지 않고 디스크에서 흘려보내도록 변경(100MB 파일 기준 최대 메모리 208MB → 14MB). 큰 파일이 한 개라도 쌓이면 그 장비의 이후 업로드가 전부 막히던 증상이 사라진다. 1.19.8 Fixed: 자동 파일 자르기(CUT_FILE) 를 쓰는 베드에서 환자가 계속 연결돼 있는데도 녹화 파일이 PT_WAITING_TIME(기본 5분) 마다 잘리던 문제 — 웹 모니터링 전송 후 메모리를 비우는 과정에서 환자 연결 판정에 쓰이는 최근 구간까지 지워져 "환자가 나갔다" 로 오판하던 것. 이제 판정에 필요한 구간은 보존한다. 파일이 5분마다 끊기고 그때마다 수 초가 유실되던 증상이 사라진다. 1.19.7 Added: GE Corometrics(분만 태아감시) 모체 vitals 지원 — 기존엔 태아 심박(HR1/HR2)·자궁수축(UACT)만 기록됐으나 이제 같은 장비가 함께 보내는 모체 SpO2/맥박(PR)/NIBP(수축·이완·평균)도 한 파일에 기록. Fixed: 프로세스 종료(서비스 매니저의 정상 종료 요청, SIGTERM) 시 즉시 종료되지 않고 강제 종료되던 문제 — 이제 SIGTERM 을 받아 녹화 버퍼를 flush 하고 스레드를 정리한 뒤 정상 종료. 1.19.5 Fixed: CUT_HOURLY 가 동작하지 않는 것처럼 보이던 문제의 실체 — k3s 컨테이너에서 기본 저장 경로가 $HOME/recordings(임시 레이어)로 잡혀, 매일 자정 재시작 때 업로드 안 된 하루치 녹화가 통째로 사라짐. hostPath 마운트 /recordings 가 있으면 그것을 기본 저장 경로로 사용. Fixed: 종료(daily restart) 시 멀티베드 TCP 노드가 terminate(exit=133)/SIGSEGV(139) 로 죽던 문제. Linux finalize 가 device 스레드를 정지하지 않고 반환해 exit() 전역 소멸자가 살아있는 PortRouter accept_thread 를 파괴하며 std::terminate — DISPLAY::quiesce_threads + PortRouter::shutdown 으로 정리 후 종료 (코어 백트레이스로 확정, 로컬 재현 후 수정 검증). Fixed: Link+/Agilia 파서가 시리얼 노이즈(size byte 0 또는 헤더 없는 잡음 8byte)에 역전된 반복자 범위로 vector 를 생성하다 std::length_error 로 죽던 문제(or-110) — sentinel 상태 파싱 봉쇄 + 재동기화. Fixed: FILENAME_TEMPLATE 의 %Y(4자리 연도)가 "0126" 으로 나오던 문제 (tm_year+1900 누락). 1.19.4 Fixed: Link+ 등 재접속이 잦은 장비에서 시리얼 포트를 재오픈할 때 "terminate called without an active exception" 으로 프로세스가 죽던 문제(exit=133). reading_thread_func 이 스스로 반환해도 std::thread 객체는 joinable 로 남는데, open() 이 join 없이 새 스레드를 대입해 std::terminate 가 호출됨 → 스레드 생성 전에 이전 reading/parsing 스레드를 join 후 재생성. Fixed: headless/AUTO_DETECT 모드에서 실제 장비가 없을 때 Demo 장비가 생성돼 가짜 vital 트랙 38종이 업로드되던 문제 → --console 또는 AUTO_DETECT 시 Demo 미생성. 1.19.3 Fixed: 여러 베드를 한 프로세스가 TCP 로 수집하는 구성에서 클라이언트 재접속이 잦으면 프로세스가 SIGSEGV 로 죽던 문제(accept_handoff 가 replace 시 옛 소켓을 즉시 close 해 recv 중인 reader 와 경합, fd 번호 재사용). tcp reader 를 poll(200ms) 게이트 뒤의 recv 로 재구성하고 replace 는 shutdown 후 소유권을 reader/close 로 단일화. SNUH 회복실 VRN 15대에서 재현. Fixed: ICU 등 Intellivue 다수 베드에서 반복되던 SIGSEGV(139) 두 계열의 use-after-free. (1) DISPLAY::add_rec 이 레코드를 트랙에 push 한 뒤 저장했는데, push 직후 p 가 다른 스레드에 공유되고 Linux 의 메모리 보존 루프(600초 이전 레코드 delete)가 시계 어긋난 모니터에서 방금 넣은 레코드를 즉시 evict 해, 이어지는 p->save() 가 해제된 메모리에 가상 호출 → 저장을 push 앞으로 옮겨 공유 후 p 미접근. (2) DISPLAY::del_trk 이 트랙을 목록에서 뺀 직후 raw delete 해, 파싱 스레드의 캐시 포인터·장비 이름맵·recorded_trks 가 죽은 포인터를 참조 (특히 IAP 채널변경의 del 직후 add 가 해제 포인터 반환) → DEVICE 규칙처럼 살아있는 동안 삭제 금지, ~DISPLAY 에서 일괄 해제. picu-11 코어 덤프로 크래시 지점(add_rec vtable 간접호출) 확정. SNUH fleet ~100 노드 실배포에서 기대 크래시 다수 대비 실제 0, 미배포 노드는 계속 크래시. Fixed: Linux from_utf16 이 locale 의존·비스레드안전 wcstombs 를 쓰고 UTF-16 을 UTF-32 wchar_t 로 오해석하던 문제 → 수동 UTF-8 인코더(서로게이트 페어 재조립, 무효값 U+FFFD)로 교체. Added: AUTO_DETECT=1(vr.conf) 시리얼 장비 자동 판별. 회선 측정(보레이트·프레임폭·패리티) → 후보 필터(3축 일치, 베드당 클래스 배타, 포트 사전확률, 판별캐시) → 트랙 수확량 경쟁. 드라이버 무수정, 미설정 시 유휴 대기라 기존 설정 무영향. SNUH 수술장·ICU 실측 오탐 0.
1.19.23
1.19.23 Fixed: SyncPulseSerial 의 PULSE 값이 화면에 보이지 않던 문제. 값은 정상적으로 수신되고 파일에도 기록되고 있었는데, 화면 눈금 범위가 카운터의 최대값(864000)으로 잡혀 있어 테스트 초반의 작은 값들이 트랙 맨 바닥 1픽셀에 눌려 보이지 않았다. 이제 매초 가운데에 뚜렷한 선으로 표시된다. 기록된 값 자체는 이전 버전에서도 온전했다(표시만의 문제). 1.19.22 Added: Dräger 마취기를 기종 이름으로 고를 수 있다. 장비 목록에 Fabius 가 추가됐고, 목록에 없는 Dräger 를 쓰는 곳을 위해 Medibus 라는 일반 항목도 함께 넣었다. Primus, Fabius, Medibus 는 통신 방식이 완전히 같고 이름만 다르므로 어느 것을 골라도 동작은 같지만, 실제 기종을 고르면 설정 파일과 화면이 무엇이 연결돼 있는지 스스로 설명하게 된다. 기존에 Primus 로 설정해 둔 곳은 그대로 두면 된다. Changed: 시리얼 장비 자동 판별이 마취기를 특정 기종이 아니라 통신 방식 이름(Medibus)으로 인식한다. 같은 통신 방식을 쓰는 기종들은 회선만 봐서는 구별할 수 없으므로, 근거 없이 모델명을 단정하지 않는 편이 낫다고 판단했다. 기종 이름이 필요하면 설정에서 직접 고르면 된다. 1.19.21 Added: Dräger 장비가 스스로 알려주는 기종명을 로그에 남긴다. 설정 파일의 장비 이름은 사람이 적어 넣은 것이라 실제 기종을 보장하지 않는데, 기종에 따라 낼 수 있는 파형이 다르므로(예: Fabius 는 기도압과 유량은 있고 CO2 가 없다) 무엇이 연결돼 있는지 아는 것이 문제 해결의 출발점이다. 통신이 시작될 때 한 번 남는다. 1.19.20 Fixed: Dräger 마취기 가운데 파형 기능이 없는 기종에서 데이터가 전혀 수집되지 않던 문제. 연결은 된 것처럼 보이지만 아무 값도 들어오지 않는다. VR 이 장비를 확인한 뒤 파형 설정을 물어보고 그 답을 받아야만 데이터 요청을 시작하도록 돼 있었는데, 파형 기능이 없는 기종은 그 질문에 답하지 않으므로 요청이 영영 시작되지 않았다. 연결 유지 신호도 함께 멎어 장비가 통신을 끊고 다시 연결하기를 반복한다. 이제 장비 확인 직후부터 데이터를 요청한다. 파형 응답이 없으면 그 기종의 정상 동작이므로 숫자값만 수집하며 로그에 한 번 알린다. Fabius GS 는 규격상 파형 기능이 없는 대표적인 기종이다 (University of Washington 제보). Added: SyncPulseSerial 동기 펄스 생성기 지원. 여러 장비의 기록을 나중에 맞추기 위한 공통 번호를 시리얼로 보내는 장치로, 1초마다 6자리 카운터를 보낸다. PULSE 트랙에 그 번호가 기록된다. 시작이나 끝을 알리는 문자가 없는 형식이라, 메시지 사이의 침묵을 경계로 삼고 카운터가 1씩 늘어나는지 확인해 맞는 장비인지 판단한다. 자동 판별 대상에도 포함돼 있고, 환자 모니터와 같은 자리에 함께 연결해도 서로 방해하지 않는다. 1.19.19 Added: Dräger 인공호흡기·마취기가 파형에 함께 실어 보내는 호흡 위상 신호를 기록한다. 장비가 흡기와 호기 전환 시점을 직접 알려주는 것이라 파형에서 역산하는 것보다 정확하다. VENT_PHASE 트랙에 흡기 시작 1, 호기 시작 0 으로 남는다. 시각은 파형과 같은 시간축을 쓴다. 종전에는 이 신호를 읽지 않고 버렸다. Added: 환기 모드를 기록한다(VENT_MODE). 모드는 VC-CMV, PC-BIPAP, SPN-CPAP 처럼 압력조절인지 용적조절인지를 알려주어 파형 해석에 직접 쓰인다. 장비가 띄우고 있는 다른 안내 문구도 VENT_MSG 로 남는다. VR 이 그동안 이 정보를 담은 명령을 아예 보내지 않고 있었다 — 이제 측정값·설정과 함께 번갈아 요청한다. 표기가 장비나 언어에 따라 달라도 되도록 문구가 아니라 규격 코드로 판별한다. Added: 파형이 서로 맞는지 확인할 근거를 로그에 남긴다. 용적은 유량을 적분한 것이므로 둘의 관계를 수치로 기록해 둔다. 다만 이것으로 파형 이름을 바꾸거나 경고하지는 않는다 — 용적조절 환기에서는 기도압도 용적에 거의 비례해서 이 수치만으로는 둘을 구별할 수 없기 때문이다. 1.19.18 Added: Y케이블로 엿보는 Dräger 연결에서 파형 종류를 자동으로 알아낸다. Y케이블 배선에 따라 장비가 보내는 것만 보이는 방과, 반대편 장치가 보내는 것까지 보이는 방이 있는데, 후자에서는 그 장치가 파형을 요청하는 내용이 VR 에도 보인다. 거기에 순서가 그대로 들어 있으므로 추정 없이 확정한다 (실제로 수술장 두 방을 재보니 한 방은 양방향, 한 방은 단방향이었다). 단방향인 방은 종전대로 wavs= 로 적어주거나 도착 순서 추정을 쓴다. Fixed: 엿보는 연결인데도 VR 이 자기가 고른 파형 순서를 적용해 이름이 어긋날 수 있던 문제 — 회선에 다른 장치가 있는지를 통신 내용으로 판별해(보낸 적 없는 명령의 응답이 보이면 다른 장치가 있는 것이다) 그런 경우 순서를 스스로 정하지 않는다. 어느 모드로 동작 중인지는 로그에 남는다. 1.19.17 Added: Y케이블로 엿보기만 하는 Dräger 연결에서 파형 이름을 확정할 수 있다. 이런 연결은 VR 이 장비에 아무것도 보낼 수 없어 어떤 파형이 어떤 순서로 오는지 물어볼 수가 없고, 그래서 도착 순서대로 추정해 기록해 왔다(이름이 실제와 다를 수 있었다). 이제 vr.conf 의 장비 항목에 wavs= 로 그 방의 순서를 적어두면 그대로 사용한다. 순서는 그 장비에 연결된 다른 장치가 정한 것이라 병원에서 알 수 있는 값이다. 예: wavs=AWP,AWF. 적지 않으면 종전과 같이 도착 순서로 추정하며, 추정 중이라는 사실과 확정하는 방법을 로그에 남긴다. 직결 연결에서는 종전대로 VR 이 직접 물어보므로 이 설정이 없어도 정확하다. Added: 회선에서 처음 보는 종류의 통신 프레임을 한 번씩 로그에 남긴다. 어떤 장치가 무엇을 주고받는지 현장에서 확인할 수 있어 연결 문제 진단에 쓴다. 1.19.16 Fixed: 1.19.15 에서 Y케이블로 엿보기만 하는 연결(서울대병원 수술장의 Dräger 전부가 이 방식)의 파형이 기록되지 않던 문제 — 1.19.15 는 VR 이 직접 요청한 파형만 기록하도록 바뀌었는데, 관찰 전용 연결에서는 VR 이 장비에 아무것도 보낼 수 없어(송신선이 끊겨 있다) 요청 자체를 못 하므로 들어오는 파형을 전부 버렸다. 이제 이런 연결에서는 1.19.14 이전과 같이 도착 순서대로 기록한다. 다른 장치가 설정해 둔 순서는 알 수 없으므로 이름이 실제와 다를 수 있고, 그 사실을 로그에 함께 남긴다. 1.19.15 를 이미 설치한 곳은 이 버전으로 올려야 한다. 1.19.15 Fixed: Dräger 마취기·인공호흡기(MEDIBUS)에서 파형이 엉뚱한 이름으로 기록되던 문제. 장비가 보내는 파형 묶음의 첫 바이트에는 “몇 번째로 요청한 파형이 들어있는가” 가 비트로 표시되는데, VR 은 그 비트를 세기만 하고 도착 순서대로 미리 정해둔 트랙(CO2, AWP, AWF)에 넣었다. 장비가 요청한 파형 중 일부만 보내면 그만큼 밀려 기록돼, 예를 들어 기도압(AWP)만 보내는 인공호흡기에서 그 파형이 CO2 로 저장됐다 (Evita V600 실측 제보). 이제 비트를 해석해 요청한 순서와 맞춰 넣는다. Fixed: 요청하는 파형이 CO2 와 기도압 두 가지로 고정돼 있어, 장비가 유량(Flow)이나 용적(Volume) 파형을 제공해도 받지 못했다. 이제 장비에게 먼저 제공 가능한 파형 목록을 물어보고 그중에서 요청한다. Added: vr.conf 의 장비 설정에 wavs= 로 받을 파형을 고를 수 있다 (예: wavs=AWP,AWF,VOL,CO2). 비워두면 장비가 제공하는 것을 앞에서부터 최대 4개까지 자동으로 받는다. 4개인 이유는 한 묶음에 최대 4개까지만 표시할 수 있기 때문이다. 지원 이름: AWP AWF PLETH VOL O2 CO2 CO2_KPA CO2_PCT AGENT HAL ENF ISO DES SEV PTRACH AWF_DEV (16진 코드도 가능). 파형을 늘리면 회선 대역을 더 쓰므로 9600bps 의 80% 를 넘으면 로그로 경고한다. 어떤 파형이 실제로 들어오고 있는지도 로그에 남겨 현장에서 확인할 수 있다. 1.19.14 Added: 서버에서 VR 로 내려보내는 명령(원격 시간 동기, 원격 업그레이드, 재시작, 설정 변경 등)을 다시 사용할 수 있다. 1.18.52 에서 이 경로를 통째로 막았는데, 시간 동기와 원격 업그레이드가 꼭 필요한 기관이 있어 되살렸다. 다만 원격으로 프로그램을 바꾸거나 설정을 덮어쓰는 것은 그 자체로 위험하므로 기본값은 여전히 전부 차단이고, vr.conf 에 SERVER_CONTROL 로 허용할 명령을 직접 적어야 동작한다 (예: SERVER_CONTROL=dt 는 시간 동기만, SERVER_CONTROL=dt,update 는 시간 동기와 업그레이드만, SERVER_CONTROL=1 은 전부 허용). 사용 가능한 명령은 update restart reboot edit_conf new_bed del_bed edit_bed dt add_event. 시간 동기는 예전과 달리 녹화 중에는 시계를 건너뛰지 않는다 — 녹화 중 시계를 갑자기 옮기면 파형에 공백이 생기거나 기록이 겹치므로, 녹화 중이면 서서히 당겨 맞추고(슬루) 녹화가 없을 때만 한번에 맞춘다. 2초 이내 차이는 무시한다. 예전 구현이 10분 이상 차이날 때 프로그램을 강제 종료하던 동작도 없앴다. 1.19.13 Fixed: 웹 모니터링 화면에서 EEG 파형이 0.1초 정도씩 끊겨 보이던 문제 — 화면 전송용으로 파형을 줄일 때 원본 주파수가 나누어떨어지지 않으면 일부 구간이 비어 그만큼 빈칸으로 보였다. 나누어떨어지는 비율로만 줄이도록 수정. 녹화 파일에는 원래 문제가 없었다(표시만의 문제). 1.19.12 Fixed: Dräger 마취기 연동 안정화 보완 — 시리얼 회선 자동판별 중 잡음이 잘못된 데이터로 오인될 여지를 차단. 1.19.11 Fixed: Dräger 마취기(Atlan 등 MEDIBUS 장비)에서 통신 오류 알람(COM1 failure)이 반복되고 파형이 전혀 기록되지 않던 문제. 원인 세 가지를 모두 수정했다. (1) 장비는 3초 안에 아무 신호라도 받지 못하면 연결을 끊는데, VR 이 5초마다 데이터를 요청하는 것 외에 보내는 것이 없어 매번 연결이 끊기고 재접속을 반복했다 — 규격이 정한 연결 유지 신호(NOP)를 2초마다 보내도록 했다(데이터 요청 주기는 그대로). (2) 장비가 응답 도중에 다른 명령을 끼워 보내는 것이 규격상 허용되는데, VR 이 그때 받던 응답을 통째로 버려 파형 설정 응답을 매번 놓쳤다 — 끼어든 부분을 건너뛰고 원래 응답을 이어받도록 했다. (3) 장비가 보내는 요청 하나에 VR 이 응답하지 않아 통신이 끊기던 것도 수정. 파형·수치가 정상 기록되고 알람이 사라진다. 1.19.10 Fixed: 하루 한 번의 자동 재시작 시점에 수집이 통째로 멎어 사람이 손을 대기 전까지 복구되지 않던 문제(or-106, 104분 무데이터) — 종료 과정이 끝나지 않아 프로그램이 죽지 않았고, 죽지 않으니 자동 재시작도 일어나지 않았다. 주원인은 시리얼 회선 자동판별(AUTO_DETECT)이 종료 신호를 보지 않고 스윕(보레이트 6종 x 포맷 8종)을 끝까지 돌던 것으로, 무음 회선에서 종료에 2분이 걸려 정상 종료 유예시간을 넘겼다. 이제 종료 즉시 판별을 중단한다(실측 2분 -> 3초). 함께 종료 중 스레드 정리가 서로를 기다릴 수 있던 두 곳을 제거하고, 그럼에도 종료가 20초 안에 끝나지 않으면 강제 종료해 반드시 재시작되도록 안전장치를 넣었다. 녹화 파일은 5초마다 저장되므로 유실되지 않는다. 1.19.9 Fixed: 큰 녹화 파일(약 100MB 이상)을 서버로 올릴 때 프로그램이 메모리 부족으로 강제 종료되고, 재시작 후 같은 파일을 다시 시도하다 무한 반복되던 문제 — 업로드 본문을 메모리에 통째로 만들지 않고 디스크에서 흘려보내도록 변경(100MB 파일 기준 최대 메모리 208MB → 14MB). 큰 파일이 한 개라도 쌓이면 그 장비의 이후 업로드가 전부 막히던 증상이 사라진다. 1.19.8 Fixed: 자동 파일 자르기(CUT_FILE) 를 쓰는 베드에서 환자가 계속 연결돼 있는데도 녹화 파일이 PT_WAITING_TIME(기본 5분) 마다 잘리던 문제 — 웹 모니터링 전송 후 메모리를 비우는 과정에서 환자 연결 판정에 쓰이는 최근 구간까지 지워져 "환자가 나갔다" 로 오판하던 것. 이제 판정에 필요한 구간은 보존한다. 파일이 5분마다 끊기고 그때마다 수 초가 유실되던 증상이 사라진다. 1.19.7 Added: GE Corometrics(분만 태아감시) 모체 vitals 지원 — 기존엔 태아 심박(HR1/HR2)·자궁수축(UACT)만 기록됐으나 이제 같은 장비가 함께 보내는 모체 SpO2/맥박(PR)/NIBP(수축·이완·평균)도 한 파일에 기록. Fixed: 프로세스 종료(서비스 매니저의 정상 종료 요청, SIGTERM) 시 즉시 종료되지 않고 강제 종료되던 문제 — 이제 SIGTERM 을 받아 녹화 버퍼를 flush 하고 스레드를 정리한 뒤 정상 종료. 1.19.5 Fixed: CUT_HOURLY 가 동작하지 않는 것처럼 보이던 문제의 실체 — k3s 컨테이너에서 기본 저장 경로가 $HOME/recordings(임시 레이어)로 잡혀, 매일 자정 재시작 때 업로드 안 된 하루치 녹화가 통째로 사라짐. hostPath 마운트 /recordings 가 있으면 그것을 기본 저장 경로로 사용. Fixed: 종료(daily restart) 시 멀티베드 TCP 노드가 terminate(exit=133)/SIGSEGV(139) 로 죽던 문제. Linux finalize 가 device 스레드를 정지하지 않고 반환해 exit() 전역 소멸자가 살아있는 PortRouter accept_thread 를 파괴하며 std::terminate — DISPLAY::quiesce_threads + PortRouter::shutdown 으로 정리 후 종료 (코어 백트레이스로 확정, 로컬 재현 후 수정 검증). Fixed: Link+/Agilia 파서가 시리얼 노이즈(size byte 0 또는 헤더 없는 잡음 8byte)에 역전된 반복자 범위로 vector 를 생성하다 std::length_error 로 죽던 문제(or-110) — sentinel 상태 파싱 봉쇄 + 재동기화. Fixed: FILENAME_TEMPLATE 의 %Y(4자리 연도)가 "0126" 으로 나오던 문제 (tm_year+1900 누락). 1.19.4 Fixed: Link+ 등 재접속이 잦은 장비에서 시리얼 포트를 재오픈할 때 "terminate called without an active exception" 으로 프로세스가 죽던 문제(exit=133). reading_thread_func 이 스스로 반환해도 std::thread 객체는 joinable 로 남는데, open() 이 join 없이 새 스레드를 대입해 std::terminate 가 호출됨 → 스레드 생성 전에 이전 reading/parsing 스레드를 join 후 재생성. Fixed: headless/AUTO_DETECT 모드에서 실제 장비가 없을 때 Demo 장비가 생성돼 가짜 vital 트랙 38종이 업로드되던 문제 → --console 또는 AUTO_DETECT 시 Demo 미생성. 1.19.3 Fixed: 여러 베드를 한 프로세스가 TCP 로 수집하는 구성에서 클라이언트 재접속이 잦으면 프로세스가 SIGSEGV 로 죽던 문제(accept_handoff 가 replace 시 옛 소켓을 즉시 close 해 recv 중인 reader 와 경합, fd 번호 재사용). tcp reader 를 poll(200ms) 게이트 뒤의 recv 로 재구성하고 replace 는 shutdown 후 소유권을 reader/close 로 단일화. SNUH 회복실 VRN 15대에서 재현. Fixed: ICU 등 Intellivue 다수 베드에서 반복되던 SIGSEGV(139) 두 계열의 use-after-free. (1) DISPLAY::add_rec 이 레코드를 트랙에 push 한 뒤 저장했는데, push 직후 p 가 다른 스레드에 공유되고 Linux 의 메모리 보존 루프(600초 이전 레코드 delete)가 시계 어긋난 모니터에서 방금 넣은 레코드를 즉시 evict 해, 이어지는 p->save() 가 해제된 메모리에 가상 호출 → 저장을 push 앞으로 옮겨 공유 후 p 미접근. (2) DISPLAY::del_trk 이 트랙을 목록에서 뺀 직후 raw delete 해, 파싱 스레드의 캐시 포인터·장비 이름맵·recorded_trks 가 죽은 포인터를 참조 (특히 IAP 채널변경의 del 직후 add 가 해제 포인터 반환) → DEVICE 규칙처럼 살아있는 동안 삭제 금지, ~DISPLAY 에서 일괄 해제. picu-11 코어 덤프로 크래시 지점(add_rec vtable 간접호출) 확정. SNUH fleet ~100 노드 실배포에서 기대 크래시 다수 대비 실제 0, 미배포 노드는 계속 크래시. Fixed: Linux from_utf16 이 locale 의존·비스레드안전 wcstombs 를 쓰고 UTF-16 을 UTF-32 wchar_t 로 오해석하던 문제 → 수동 UTF-8 인코더(서로게이트 페어 재조립, 무효값 U+FFFD)로 교체. Added: AUTO_DETECT=1(vr.conf) 시리얼 장비 자동 판별. 회선 측정(보레이트·프레임폭·패리티) → 후보 필터(3축 일치, 베드당 클래스 배타, 포트 사전확률, 판별캐시) → 트랙 수확량 경쟁. 드라이버 무수정, 미설정 시 유휴 대기라 기존 설정 무영향. SNUH 수술장·ICU 실측 오탐 0.
Apple Silicon (arm64) only · Requires macOS 11 or later · Developer ID signed & notarized
Download Raspberry Pi Image
Preinstalled SD card image (.img.xz) · Flash to an SD card with Raspberry Pi Imager or balenaEtcher
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