VitalRecorder – All Versions
You can browse all available versions by OS and download any version directly.
Total downloads since Feb 2017: 185,243
| Version | Date | Changes | |
|---|---|---|---|
| 1.19.36 | 2026-10-13 | 1.19.36 Added: Windows 에서 아이콘으로 실행해도 로그가 설정 폴더의 vr.log 에 남는다. 지금까지는 로그가 화면에 출력되는 곳이 없으면 그대로 사라져서, 장비가 요청을 거절한 이유처럼 문제를 가리는 데 필요한 기록을 현장에서 볼 방법이 없었다. 줄마다 날짜와 시각이 붙고, 10MB 를 넘으면 시작할 때 vr.log.1 로 넘긴다. --debug 로 실행하거나 출력을 다른 파일로 돌린 경우는 종전대로 그쪽으로 나간다. Fixed: VitalRecorder Qt 에서 트랙 목록의 세로 스크롤바를 마우스로 움직일 수 없던 문제. 스크롤바가 그려지기만 하고 마우스 입력을 받지 않아 방향키로만 이동할 수 있었다. 이제 막대를 끌거나 빈 곳을 눌러 이동할 수 있다. Fixed: VitalRecorder Qt 에서 장비 편집 창이 검은 바탕에 검은 글씨로 나와 읽기 어렵던 문제. 장비 추가 창과 같은 밝은 모양으로 나온다. 창의 최소 폭도 넓혀 Intellivue 파형 선택 항목이 잘리지 않는다. Fixed: VitalRecorder Qt 에서 장비 설정을 바꾸면 오른쪽 장비 목록에 빈 칸이 남던 문제. 그 빈 칸의 편집 버튼을 누르면 같은 장비가 하나 더 생기기도 했다. (Nord University 제보) | |
| 1.19.35 | 2026-10-08 | 1.19.35 Fixed: Edwards 혈역학 모니터(HemoSphere 등)의 값이 Philips Intellivue 를 거쳐 들어올 때 연속 심박출량(CO_CTS), 연속 심장지수(CI_CTS), 일회박출량 변이(VOL_BLD_STROKE_VAR, SVV)가 웹 모니터링 화면에 나오지 않던 문제. 값은 정상으로 수집·저장되고 있었지만, 웹 화면이 어느 칸에 표시할지 정하는 분류(모니터 유형)가 지정되지 않아 표시 대상에서 빠졌다. 이제 각각 CO, CI, SVV 로 분류해 다른 장비의 같은 값과 같은 자리에 나온다. 이미 만들어진 녹화 파일에는 소급되지 않는다. | |
| 1.19.34 | 2026-10-06 | 1.19.34 Fixed: Nihon Kohden Vismo PVM-4700 시리즈(PVM-4761 등)와 구형 BSM-3000/6000, CSM-1500/1700 에서 시리얼로 아무 값도 들어오지 않던 문제. VR 은 확장 수치·심방세동까지 받으려고 ?D;EALL 요청의 Option 을 2 로 보내는데, 이 기종들은 규격상 Option 0~1 만 받아들여 매 요청을 !E;04(인자 미지원)로 거절했고, VR 은 그 거절을 읽지 않아 1초마다 같은 요청을 되풀이했다 (강남차병원 PVM-4761 실측 2026-09-07: 5분 동안 거절 응답만 300회). 이제 거절을 받으면 Option 을 한 단계씩 내려 다시 요청하고(2→1→0), 부정맥 비트 해석표도 그 Option 에 맞는 표(규격 Table 7/8/9)로 바꾼다. 어느 Option 으로 내려갔는지 로그에 남긴다. 측정하지 않은 파라미터에 대한 응답(!E;80/81, 예: NIBP 미측정)은 정상이므로 조용히 넘기고, 그 밖의 에러 응답은 60초에 한 번 로그에 남긴다. 가짜 모니터로 2→1 폴백과 HR/PR/SpO2/RR/체온 수집을 확인했다. 파형은 이 경로(RS-232C)로는 규격상 오지 않는다. PVM-4700 은 QI-470P 인터페이스의 RS-232C 커넥터와 YS-089P 계열 케이블을 쓴다 (0614-905457H). Added: 장비 목록에 Nihon Kohden : PVM 을 따로 두었다. BSM 과 같은 통신 규격이지만 PVM-4700 은 처음부터 지원하는 범위로 요청하므로 거절 응답 없이 바로 값이 들어온다. vr.conf 에는 type=PVM 으로 적는다. 기존에 BSM 으로 설정한 PVM 도 위의 자동 하향 덕분에 그대로 동작한다. | |
| 1.19.33 | 2026-10-05 | 1.19.33 Added: Philips Intellivue 가 VR 의 요청을 거절하면 로그에 남긴다. 어떤 요청(파형 폴, 파형 우선순위 목록, 수치 폴, 환자정보 등)이 어떤 이유(ACCESS_DENIED, SET_LIST_ERROR, INVALID_ARGUMENT_VALUE 등)로 거절됐는지 적는다. 종전에는 이 응답이 디버그 빌드에서만 보이는 흔적으로 처리돼 현장 로그에 아무것도 남지 않았고, 그래서 “수치는 들어오는데 파형만 없다” 는 상황에서 VR 이 요청을 안 한 것인지 모니터가 거부한 것인지를 로그만으로 가릴 수 없었다 (Nord University 실험실, 1.19.22~1.19.32 동일 증상). Added: 파형 폴을 보내는데 파형 묶음이 30초 넘게 돌아오지 않으면 5분에 한 번 경고를 남긴다. 요청한 파형 목록을 함께 적어 모니터 Data Export 설정과 바로 대조할 수 있다. 같은 코드의 거절은 60초에 한 번만 적는다. 1.19.32 Added: Philips Intellivue 의 파형 자동 추가를 끌 수 있다 (auto_wavs=0, 장비 설정 창의 체크박스). 종전에는 설정 창에서 고른 파형에 더해 수치가 들어오는 파형(ECG 한 리드, PLETH, ABP/ART/AoP, CVP, ICP, CO2, 기도압·유량)을 자동으로 보탰고 이것을 막을 방법이 없었다. 침습압이 여럿인 실험실 구성에서는 그 합이 시리얼 회선이 감당할 양을 넘고, 그러면 모니터는 파형을 일부만 줄이는 것이 아니라 통째로 멈춘다 — 수치는 계속 들어오는데 파형만 몇 초 만에 사라지는 모습이 된다 (Nord University 실측: 파형 8종이 5초 뒤 멎음, 설정 창에 적힌 한계 “ECG 3개 또는 비ECG 8개” 를 ECG 2개+6개로 넘긴 경우). 이제 체크를 끄면 고른 것만 요청한다. 기본값은 종전과 같이 자동 추가다. Added: Intellivue 에 요청하는 파형 목록이 바뀔 때마다 로그에 남긴다. 파형이 안 들어올 때 요청을 안 한 것인지, 요청했는데 모니터가 안 주는 것인지를 로그만으로 가를 수 있다. 1.19.31 Fixed: Windows 에서 시리얼 장비의 값이 두 번에 한 번씩 약 0.23초 늦게 기록되던 문제. 1초에 한 번 보내는 SyncPulseSerial(동기 펄스 생성기)에서 펄스 간격이 1초가 아니라 0.77초와 1.23초로 번갈아 기록됐다 (Nord University AniLab 실측 9/5·9/10, 두 펄스를 합하면 정확히 2.000초). 장비 쪽은 정확했다 — 같은 회선을 시리얼 터미널로 받으면 ±1ms 로 1초마다 들어온다. 원인은 VR 이 문자 도착을 기다리기 전에 매번 100ms 를 쉬었고, 그 100ms 안에 들어온 데이터는 Windows 가 알려주지 않아 200ms 뒤 확인 때에야 읽혔다는 것이다. 이 대기 한 바퀴(약 310ms)와 1초 주기가 맞물려 한 프레임은 즉시, 다음 프레임은 230ms 늦게 읽는 패턴이 고정됐다. 이제 기다리기 전에 수신 버퍼를 먼저 확인하고 쉬지 않는다. 도착 즉시 읽으므로 기록 시각의 오차는 USB 폴링 수준(수 ms)만 남는다. 초당 수십 바이트 이상 보내는 일반 환자 모니터에서는 이 지연이 파형 시각에 묻혀 드러나지 않았다. Note: 1.19.25 설명 정정 — Windows 수신 대기가 원인이 아니라고 적었으나(가상 직렬 포트 A/B 에서 재현되지 않았다), 실제 USB 시리얼에서는 위와 같이 재현된다. 가상 포트는 문자 도착 알림 방식이 달라 시험이 빗나갔다. 1.19.30 Fixed: 기기 시각이 크게 어긋났을 때 저절로 맞춰지지 않고 그대로 유지되던 문제. 원주세브란스 EICU09 에서 실제보다 55분 늦은 상태가 계속됐고, 그 결과 해당 병상이 화면에서 자료 없음으로 보였다 (웹 화면은 마지막으로 자료가 들어온 지 1분이 넘은 병상을 끊긴 것으로 본다). 서버가 알려주는 시각으로 맞추는 기능 자체는 동작하고 있었다. 다만 녹화 중에는 파형에 공백이 생기지 않도록 시계를 한 번에 옮기지 않고 아주 천천히 당기도록 돼 있었는데, 그 속도가 하루에 약 43초라 55분을 되돌리려면 두 달이 넘게 걸린다. 천천히 당기는 방식은 이미 가까운 시계를 가깝게 유지하는 수단이지 벌어진 것을 따라잡는 수단이 아니라는 점이 코드에 반영돼 있지 않았다. 이제 두 경우에는 녹화 중이라도 한 번에 맞춘다 — 차이가 1분을 넘어 천천히 당겨서는 따라잡을 수 없을 때, 그리고 SAVE_FILE=0 으로 파일을 만들지 않아 지켜야 할 기록이 애초에 없을 때. 어느 쪽이든 맞춘 이유를 로그에 남긴다. ⚠ 시계를 따로 갖고 있지 않은 장비(라즈베리파이)는 전원이 꺼지면 시각을 잃는다. 기관의 시각 서버(NTP)를 함께 물려 두면 이 기능에 기대지 않아도 된다. 1.19.29 Added: 녹화 파일을 만들지 않고 웹 모니터링만 하는 운용 방식. vr.conf 에 SAVE_FILE=0 을 넣으면 .vital 파일을 만들지 않는다. 무선 대역폭이 부족해 파일을 남기지 않기로 한 기관을 위한 것이다. 종전에는 저장 폴더를 없는 경로로 지정해 ‘열기 실패’ 로 같은 효과를 내는 편법이 쓰였는데, 그 방법은 파일을 만들 때마다 오류가 로그에 쌓이고 화면에는 녹화 중으로 보였다. ⚠ 이 방식에서는 네트워크가 끊긴 구간의 자료가 영구히 사라진다 — 지금까지는 파일이 전송 실패의 완충재 역할을 했다. Changed: 웹 모니터링 전송량이 25~35% 줄었다. 파형 값을 늘 소수점 셋째 자리까지 보내고 있었는데, 화면은 그 트랙의 표시 범위 안에서 그리므로 범위의 1% 보다 고운 눈금은 어차피 구분되지 않는다 (심전도는 표시 범위가 4 mV 인데 0.001 단위로 보내 화면이 나타낼 수 있는 것의 40배를 보내고 있었다). 게다가 그 여분의 자릿수는 규칙성이 없어 압축도 되지 않아, 전송량의 4분의 1 이상을 차지했다 (중환자실 3개 병상 실측 26~35%). 이제 표시 범위에 맞춰 자릿수를 정한다. 값의 단위와 의미는 그대로이고 녹화 파일에는 원래 정밀도로 저장되므로 화면과 파일 모두 달라지는 것이 없으며, 서버나 웹 화면을 함께 고칠 필요도 없다. 예전 방식이 필요하면 WEB_WAV_PREC=3 으로 고정할 수 있다. Fixed: 녹화 파일을 만들지 못했을 때 아무 알림 없이 지나가던 문제. 디스크가 가득 찼거나 저장 폴더에 쓸 수 없으면 파일이 만들어지지 않는데, 화면에는 ‘녹화 중’ 으로 표시되고 자료는 조용히 버려졌다. 이제 로그에 경고를 남긴다. 1.19.28 Fixed: USB 로 연결한 장비의 수집이 어느 순간 조용히 멎고, 프로그램을 다시 켜야만 돌아오던 문제. 전원이 잠깐 흔들리면 USB 장비가 스스로 연결을 다시 맺는데 그때 운영체제가 새 포트 번호를 붙인다 (예: ttyACM0 이 ttyACM1 로). VR 은 처음 열었던 번호를 계속 붙들고 있어서, 장비도 케이블도 멀쩡한데 값만 들어오지 않는다 — 화면상 연결 상태도 정상으로 보여 눈으로는 알 수 없었다 (수술방의 Link+ 주입펌프에서 확인). Added: 포트 자리에 포트 번호 대신 장비의 고유 신원을 적을 수 있다. 형식은 20d0:0002 처럼 네 자리 두 개이며, 이 값은 장비에 박혀 있어 다시 연결되어도 바뀌지 않는다. 이렇게 적어 두면 VR 이 포트를 열 때마다 그 신원을 가진 자리를 다시 찾으므로, 번호가 바뀌어도 저절로 따라간다. 기존처럼 포트 번호를 적어 둔 곳은 그대로 동작한다. Changed: Link+ 는 포트를 적지 않으면 이 신원을 기본값으로 쓴다. 꽂기만 하면 잡히고, 다시 연결되어도 따라간다. 포트를 직접 적어 둔 곳은 적은 값이 우선이다. Changed: 자동 판별이 USB 신원을 먼저 본다. 고유 신원으로 자기를 밝히는 장비는 통신 속도·형식을 재 볼 필요가 없으므로 조용한 회선에서 72초가 걸리던 측정을 건너뛰고 바로 확정한다. 이렇게 확정한 장비는 포트를 번호가 아니라 신원으로 기록한다. 여러 장비가 함께 쓰는 범용 USB 변환 케이블(FTDI 등)은 신원이 케이블의 것이지 장비의 것이 아니므로 종전대로 측정해서 판별한다. 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.32 | 2026-10-05 | 1.19.32 Added: Philips Intellivue 의 파형 자동 추가를 끌 수 있다 (auto_wavs=0, 장비 설정 창의 체크박스). 종전에는 설정 창에서 고른 파형에 더해 수치가 들어오는 파형(ECG 한 리드, PLETH, ABP/ART/AoP, CVP, ICP, CO2, 기도압·유량)을 자동으로 보탰고 이것을 막을 방법이 없었다. 침습압이 여럿인 실험실 구성에서는 그 합이 시리얼 회선이 감당할 양을 넘고, 그러면 모니터는 파형을 일부만 줄이는 것이 아니라 통째로 멈춘다 — 수치는 계속 들어오는데 파형만 몇 초 만에 사라지는 모습이 된다 (Nord University 실측: 파형 8종이 5초 뒤 멎음, 설정 창에 적힌 한계 “ECG 3개 또는 비ECG 8개” 를 ECG 2개+6개로 넘긴 경우). 이제 체크를 끄면 고른 것만 요청한다. 기본값은 종전과 같이 자동 추가다. Added: Intellivue 에 요청하는 파형 목록이 바뀔 때마다 로그에 남긴다. 파형이 안 들어올 때 요청을 안 한 것인지, 요청했는데 모니터가 안 주는 것인지를 로그만으로 가를 수 있다. 1.19.31 Fixed: Windows 에서 시리얼 장비의 값이 두 번에 한 번씩 약 0.23초 늦게 기록되던 문제. 1초에 한 번 보내는 SyncPulseSerial(동기 펄스 생성기)에서 펄스 간격이 1초가 아니라 0.77초와 1.23초로 번갈아 기록됐다 (Nord University AniLab 실측 9/5·9/10, 두 펄스를 합하면 정확히 2.000초). 장비 쪽은 정확했다 — 같은 회선을 시리얼 터미널로 받으면 ±1ms 로 1초마다 들어온다. 원인은 VR 이 문자 도착을 기다리기 전에 매번 100ms 를 쉬었고, 그 100ms 안에 들어온 데이터는 Windows 가 알려주지 않아 200ms 뒤 확인 때에야 읽혔다는 것이다. 이 대기 한 바퀴(약 310ms)와 1초 주기가 맞물려 한 프레임은 즉시, 다음 프레임은 230ms 늦게 읽는 패턴이 고정됐다. 이제 기다리기 전에 수신 버퍼를 먼저 확인하고 쉬지 않는다. 도착 즉시 읽으므로 기록 시각의 오차는 USB 폴링 수준(수 ms)만 남는다. 초당 수십 바이트 이상 보내는 일반 환자 모니터에서는 이 지연이 파형 시각에 묻혀 드러나지 않았다. Note: 1.19.25 설명 정정 — Windows 수신 대기가 원인이 아니라고 적었으나(가상 직렬 포트 A/B 에서 재현되지 않았다), 실제 USB 시리얼에서는 위와 같이 재현된다. 가상 포트는 문자 도착 알림 방식이 달라 시험이 빗나갔다. 1.19.30 Fixed: 기기 시각이 크게 어긋났을 때 저절로 맞춰지지 않고 그대로 유지되던 문제. 원주세브란스 EICU09 에서 실제보다 55분 늦은 상태가 계속됐고, 그 결과 해당 병상이 화면에서 자료 없음으로 보였다 (웹 화면은 마지막으로 자료가 들어온 지 1분이 넘은 병상을 끊긴 것으로 본다). 서버가 알려주는 시각으로 맞추는 기능 자체는 동작하고 있었다. 다만 녹화 중에는 파형에 공백이 생기지 않도록 시계를 한 번에 옮기지 않고 아주 천천히 당기도록 돼 있었는데, 그 속도가 하루에 약 43초라 55분을 되돌리려면 두 달이 넘게 걸린다. 천천히 당기는 방식은 이미 가까운 시계를 가깝게 유지하는 수단이지 벌어진 것을 따라잡는 수단이 아니라는 점이 코드에 반영돼 있지 않았다. 이제 두 경우에는 녹화 중이라도 한 번에 맞춘다 — 차이가 1분을 넘어 천천히 당겨서는 따라잡을 수 없을 때, 그리고 SAVE_FILE=0 으로 파일을 만들지 않아 지켜야 할 기록이 애초에 없을 때. 어느 쪽이든 맞춘 이유를 로그에 남긴다. ⚠ 시계를 따로 갖고 있지 않은 장비(라즈베리파이)는 전원이 꺼지면 시각을 잃는다. 기관의 시각 서버(NTP)를 함께 물려 두면 이 기능에 기대지 않아도 된다. 1.19.29 Added: 녹화 파일을 만들지 않고 웹 모니터링만 하는 운용 방식. vr.conf 에 SAVE_FILE=0 을 넣으면 .vital 파일을 만들지 않는다. 무선 대역폭이 부족해 파일을 남기지 않기로 한 기관을 위한 것이다. 종전에는 저장 폴더를 없는 경로로 지정해 ‘열기 실패’ 로 같은 효과를 내는 편법이 쓰였는데, 그 방법은 파일을 만들 때마다 오류가 로그에 쌓이고 화면에는 녹화 중으로 보였다. ⚠ 이 방식에서는 네트워크가 끊긴 구간의 자료가 영구히 사라진다 — 지금까지는 파일이 전송 실패의 완충재 역할을 했다. Changed: 웹 모니터링 전송량이 25~35% 줄었다. 파형 값을 늘 소수점 셋째 자리까지 보내고 있었는데, 화면은 그 트랙의 표시 범위 안에서 그리므로 범위의 1% 보다 고운 눈금은 어차피 구분되지 않는다 (심전도는 표시 범위가 4 mV 인데 0.001 단위로 보내 화면이 나타낼 수 있는 것의 40배를 보내고 있었다). 게다가 그 여분의 자릿수는 규칙성이 없어 압축도 되지 않아, 전송량의 4분의 1 이상을 차지했다 (중환자실 3개 병상 실측 26~35%). 이제 표시 범위에 맞춰 자릿수를 정한다. 값의 단위와 의미는 그대로이고 녹화 파일에는 원래 정밀도로 저장되므로 화면과 파일 모두 달라지는 것이 없으며, 서버나 웹 화면을 함께 고칠 필요도 없다. 예전 방식이 필요하면 WEB_WAV_PREC=3 으로 고정할 수 있다. Fixed: 녹화 파일을 만들지 못했을 때 아무 알림 없이 지나가던 문제. 디스크가 가득 찼거나 저장 폴더에 쓸 수 없으면 파일이 만들어지지 않는데, 화면에는 ‘녹화 중’ 으로 표시되고 자료는 조용히 버려졌다. 이제 로그에 경고를 남긴다. 1.19.28 Fixed: USB 로 연결한 장비의 수집이 어느 순간 조용히 멎고, 프로그램을 다시 켜야만 돌아오던 문제. 전원이 잠깐 흔들리면 USB 장비가 스스로 연결을 다시 맺는데 그때 운영체제가 새 포트 번호를 붙인다 (예: ttyACM0 이 ttyACM1 로). VR 은 처음 열었던 번호를 계속 붙들고 있어서, 장비도 케이블도 멀쩡한데 값만 들어오지 않는다 — 화면상 연결 상태도 정상으로 보여 눈으로는 알 수 없었다 (수술방의 Link+ 주입펌프에서 확인). Added: 포트 자리에 포트 번호 대신 장비의 고유 신원을 적을 수 있다. 형식은 20d0:0002 처럼 네 자리 두 개이며, 이 값은 장비에 박혀 있어 다시 연결되어도 바뀌지 않는다. 이렇게 적어 두면 VR 이 포트를 열 때마다 그 신원을 가진 자리를 다시 찾으므로, 번호가 바뀌어도 저절로 따라간다. 기존처럼 포트 번호를 적어 둔 곳은 그대로 동작한다. Changed: Link+ 는 포트를 적지 않으면 이 신원을 기본값으로 쓴다. 꽂기만 하면 잡히고, 다시 연결되어도 따라간다. 포트를 직접 적어 둔 곳은 적은 값이 우선이다. Changed: 자동 판별이 USB 신원을 먼저 본다. 고유 신원으로 자기를 밝히는 장비는 통신 속도·형식을 재 볼 필요가 없으므로 조용한 회선에서 72초가 걸리던 측정을 건너뛰고 바로 확정한다. 이렇게 확정한 장비는 포트를 번호가 아니라 신원으로 기록한다. 여러 장비가 함께 쓰는 범용 USB 변환 케이블(FTDI 등)은 신원이 케이블의 것이지 장비의 것이 아니므로 종전대로 측정해서 판별한다. 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.31 | 2026-09-27 | 1.19.31 Fixed: Windows 에서 시리얼 장비의 값이 두 번에 한 번씩 약 0.23초 늦게 기록되던 문제. 1초에 한 번 보내는 SyncPulseSerial(동기 펄스 생성기)에서 펄스 간격이 1초가 아니라 0.77초와 1.23초로 번갈아 기록됐다 (Nord University AniLab 실측 9/5·9/10, 두 펄스를 합하면 정확히 2.000초). 장비 쪽은 정확했다 — 같은 회선을 시리얼 터미널로 받으면 ±1ms 로 1초마다 들어온다. 원인은 VR 이 문자 도착을 기다리기 전에 매번 100ms 를 쉬었고, 그 100ms 안에 들어온 데이터는 Windows 가 알려주지 않아 200ms 뒤 확인 때에야 읽혔다는 것이다. 이 대기 한 바퀴(약 310ms)와 1초 주기가 맞물려 한 프레임은 즉시, 다음 프레임은 230ms 늦게 읽는 패턴이 고정됐다. 이제 기다리기 전에 수신 버퍼를 먼저 확인하고 쉬지 않는다. 도착 즉시 읽으므로 기록 시각의 오차는 USB 폴링 수준(수 ms)만 남는다. 초당 수십 바이트 이상 보내는 일반 환자 모니터에서는 이 지연이 파형 시각에 묻혀 드러나지 않았다. Note: 1.19.25 설명 정정 — Windows 수신 대기가 원인이 아니라고 적었으나(가상 직렬 포트 A/B 에서 재현되지 않았다), 실제 USB 시리얼에서는 위와 같이 재현된다. 가상 포트는 문자 도착 알림 방식이 달라 시험이 빗나갔다. 1.19.30 Fixed: 기기 시각이 크게 어긋났을 때 저절로 맞춰지지 않고 그대로 유지되던 문제. 원주세브란스 EICU09 에서 실제보다 55분 늦은 상태가 계속됐고, 그 결과 해당 병상이 화면에서 자료 없음으로 보였다 (웹 화면은 마지막으로 자료가 들어온 지 1분이 넘은 병상을 끊긴 것으로 본다). 서버가 알려주는 시각으로 맞추는 기능 자체는 동작하고 있었다. 다만 녹화 중에는 파형에 공백이 생기지 않도록 시계를 한 번에 옮기지 않고 아주 천천히 당기도록 돼 있었는데, 그 속도가 하루에 약 43초라 55분을 되돌리려면 두 달이 넘게 걸린다. 천천히 당기는 방식은 이미 가까운 시계를 가깝게 유지하는 수단이지 벌어진 것을 따라잡는 수단이 아니라는 점이 코드에 반영돼 있지 않았다. 이제 두 경우에는 녹화 중이라도 한 번에 맞춘다 — 차이가 1분을 넘어 천천히 당겨서는 따라잡을 수 없을 때, 그리고 SAVE_FILE=0 으로 파일을 만들지 않아 지켜야 할 기록이 애초에 없을 때. 어느 쪽이든 맞춘 이유를 로그에 남긴다. ⚠ 시계를 따로 갖고 있지 않은 장비(라즈베리파이)는 전원이 꺼지면 시각을 잃는다. 기관의 시각 서버(NTP)를 함께 물려 두면 이 기능에 기대지 않아도 된다. 1.19.29 Added: 녹화 파일을 만들지 않고 웹 모니터링만 하는 운용 방식. vr.conf 에 SAVE_FILE=0 을 넣으면 .vital 파일을 만들지 않는다. 무선 대역폭이 부족해 파일을 남기지 않기로 한 기관을 위한 것이다. 종전에는 저장 폴더를 없는 경로로 지정해 ‘열기 실패’ 로 같은 효과를 내는 편법이 쓰였는데, 그 방법은 파일을 만들 때마다 오류가 로그에 쌓이고 화면에는 녹화 중으로 보였다. ⚠ 이 방식에서는 네트워크가 끊긴 구간의 자료가 영구히 사라진다 — 지금까지는 파일이 전송 실패의 완충재 역할을 했다. Changed: 웹 모니터링 전송량이 25~35% 줄었다. 파형 값을 늘 소수점 셋째 자리까지 보내고 있었는데, 화면은 그 트랙의 표시 범위 안에서 그리므로 범위의 1% 보다 고운 눈금은 어차피 구분되지 않는다 (심전도는 표시 범위가 4 mV 인데 0.001 단위로 보내 화면이 나타낼 수 있는 것의 40배를 보내고 있었다). 게다가 그 여분의 자릿수는 규칙성이 없어 압축도 되지 않아, 전송량의 4분의 1 이상을 차지했다 (중환자실 3개 병상 실측 26~35%). 이제 표시 범위에 맞춰 자릿수를 정한다. 값의 단위와 의미는 그대로이고 녹화 파일에는 원래 정밀도로 저장되므로 화면과 파일 모두 달라지는 것이 없으며, 서버나 웹 화면을 함께 고칠 필요도 없다. 예전 방식이 필요하면 WEB_WAV_PREC=3 으로 고정할 수 있다. Fixed: 녹화 파일을 만들지 못했을 때 아무 알림 없이 지나가던 문제. 디스크가 가득 찼거나 저장 폴더에 쓸 수 없으면 파일이 만들어지지 않는데, 화면에는 ‘녹화 중’ 으로 표시되고 자료는 조용히 버려졌다. 이제 로그에 경고를 남긴다. 1.19.28 Fixed: USB 로 연결한 장비의 수집이 어느 순간 조용히 멎고, 프로그램을 다시 켜야만 돌아오던 문제. 전원이 잠깐 흔들리면 USB 장비가 스스로 연결을 다시 맺는데 그때 운영체제가 새 포트 번호를 붙인다 (예: ttyACM0 이 ttyACM1 로). VR 은 처음 열었던 번호를 계속 붙들고 있어서, 장비도 케이블도 멀쩡한데 값만 들어오지 않는다 — 화면상 연결 상태도 정상으로 보여 눈으로는 알 수 없었다 (수술방의 Link+ 주입펌프에서 확인). Added: 포트 자리에 포트 번호 대신 장비의 고유 신원을 적을 수 있다. 형식은 20d0:0002 처럼 네 자리 두 개이며, 이 값은 장비에 박혀 있어 다시 연결되어도 바뀌지 않는다. 이렇게 적어 두면 VR 이 포트를 열 때마다 그 신원을 가진 자리를 다시 찾으므로, 번호가 바뀌어도 저절로 따라간다. 기존처럼 포트 번호를 적어 둔 곳은 그대로 동작한다. Changed: Link+ 는 포트를 적지 않으면 이 신원을 기본값으로 쓴다. 꽂기만 하면 잡히고, 다시 연결되어도 따라간다. 포트를 직접 적어 둔 곳은 적은 값이 우선이다. Changed: 자동 판별이 USB 신원을 먼저 본다. 고유 신원으로 자기를 밝히는 장비는 통신 속도·형식을 재 볼 필요가 없으므로 조용한 회선에서 72초가 걸리던 측정을 건너뛰고 바로 확정한다. 이렇게 확정한 장비는 포트를 번호가 아니라 신원으로 기록한다. 여러 장비가 함께 쓰는 범용 USB 변환 케이블(FTDI 등)은 신원이 케이블의 것이지 장비의 것이 아니므로 종전대로 측정해서 판별한다. 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.30 | 2026-09-16 | 1.19.30 Fixed: 기기 시각이 크게 어긋났을 때 저절로 맞춰지지 않고 그대로 유지되던 문제. 원주세브란스 EICU09 에서 실제보다 55분 늦은 상태가 계속됐고, 그 결과 해당 병상이 화면에서 자료 없음으로 보였다 (웹 화면은 마지막으로 자료가 들어온 지 1분이 넘은 병상을 끊긴 것으로 본다). 서버가 알려주는 시각으로 맞추는 기능 자체는 동작하고 있었다. 다만 녹화 중에는 파형에 공백이 생기지 않도록 시계를 한 번에 옮기지 않고 아주 천천히 당기도록 돼 있었는데, 그 속도가 하루에 약 43초라 55분을 되돌리려면 두 달이 넘게 걸린다. 천천히 당기는 방식은 이미 가까운 시계를 가깝게 유지하는 수단이지 벌어진 것을 따라잡는 수단이 아니라는 점이 코드에 반영돼 있지 않았다. 이제 두 경우에는 녹화 중이라도 한 번에 맞춘다 — 차이가 1분을 넘어 천천히 당겨서는 따라잡을 수 없을 때, 그리고 SAVE_FILE=0 으로 파일을 만들지 않아 지켜야 할 기록이 애초에 없을 때. 어느 쪽이든 맞춘 이유를 로그에 남긴다. ⚠ 시계를 따로 갖고 있지 않은 장비(라즈베리파이)는 전원이 꺼지면 시각을 잃는다. 기관의 시각 서버(NTP)를 함께 물려 두면 이 기능에 기대지 않아도 된다. 1.19.29 Added: 녹화 파일을 만들지 않고 웹 모니터링만 하는 운용 방식. vr.conf 에 SAVE_FILE=0 을 넣으면 .vital 파일을 만들지 않는다. 무선 대역폭이 부족해 파일을 남기지 않기로 한 기관을 위한 것이다. 종전에는 저장 폴더를 없는 경로로 지정해 ‘열기 실패’ 로 같은 효과를 내는 편법이 쓰였는데, 그 방법은 파일을 만들 때마다 오류가 로그에 쌓이고 화면에는 녹화 중으로 보였다. ⚠ 이 방식에서는 네트워크가 끊긴 구간의 자료가 영구히 사라진다 — 지금까지는 파일이 전송 실패의 완충재 역할을 했다. Changed: 웹 모니터링 전송량이 25~35% 줄었다. 파형 값을 늘 소수점 셋째 자리까지 보내고 있었는데, 화면은 그 트랙의 표시 범위 안에서 그리므로 범위의 1% 보다 고운 눈금은 어차피 구분되지 않는다 (심전도는 표시 범위가 4 mV 인데 0.001 단위로 보내 화면이 나타낼 수 있는 것의 40배를 보내고 있었다). 게다가 그 여분의 자릿수는 규칙성이 없어 압축도 되지 않아, 전송량의 4분의 1 이상을 차지했다 (중환자실 3개 병상 실측 26~35%). 이제 표시 범위에 맞춰 자릿수를 정한다. 값의 단위와 의미는 그대로이고 녹화 파일에는 원래 정밀도로 저장되므로 화면과 파일 모두 달라지는 것이 없으며, 서버나 웹 화면을 함께 고칠 필요도 없다. 예전 방식이 필요하면 WEB_WAV_PREC=3 으로 고정할 수 있다. Fixed: 녹화 파일을 만들지 못했을 때 아무 알림 없이 지나가던 문제. 디스크가 가득 찼거나 저장 폴더에 쓸 수 없으면 파일이 만들어지지 않는데, 화면에는 ‘녹화 중’ 으로 표시되고 자료는 조용히 버려졌다. 이제 로그에 경고를 남긴다. 1.19.28 Fixed: USB 로 연결한 장비의 수집이 어느 순간 조용히 멎고, 프로그램을 다시 켜야만 돌아오던 문제. 전원이 잠깐 흔들리면 USB 장비가 스스로 연결을 다시 맺는데 그때 운영체제가 새 포트 번호를 붙인다 (예: ttyACM0 이 ttyACM1 로). VR 은 처음 열었던 번호를 계속 붙들고 있어서, 장비도 케이블도 멀쩡한데 값만 들어오지 않는다 — 화면상 연결 상태도 정상으로 보여 눈으로는 알 수 없었다 (수술방의 Link+ 주입펌프에서 확인). Added: 포트 자리에 포트 번호 대신 장비의 고유 신원을 적을 수 있다. 형식은 20d0:0002 처럼 네 자리 두 개이며, 이 값은 장비에 박혀 있어 다시 연결되어도 바뀌지 않는다. 이렇게 적어 두면 VR 이 포트를 열 때마다 그 신원을 가진 자리를 다시 찾으므로, 번호가 바뀌어도 저절로 따라간다. 기존처럼 포트 번호를 적어 둔 곳은 그대로 동작한다. Changed: Link+ 는 포트를 적지 않으면 이 신원을 기본값으로 쓴다. 꽂기만 하면 잡히고, 다시 연결되어도 따라간다. 포트를 직접 적어 둔 곳은 적은 값이 우선이다. Changed: 자동 판별이 USB 신원을 먼저 본다. 고유 신원으로 자기를 밝히는 장비는 통신 속도·형식을 재 볼 필요가 없으므로 조용한 회선에서 72초가 걸리던 측정을 건너뛰고 바로 확정한다. 이렇게 확정한 장비는 포트를 번호가 아니라 신원으로 기록한다. 여러 장비가 함께 쓰는 범용 USB 변환 케이블(FTDI 등)은 신원이 케이블의 것이지 장비의 것이 아니므로 종전대로 측정해서 판별한다. 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.29 | 2026-09-15 | 1.19.29 Added: 녹화 파일을 만들지 않고 웹 모니터링만 하는 운용 방식. vr.conf 에 SAVE_FILE=0 을 넣으면 .vital 파일을 만들지 않는다. 무선 대역폭이 부족해 파일을 남기지 않기로 한 기관을 위한 것이다. 종전에는 저장 폴더를 없는 경로로 지정해 ‘열기 실패’ 로 같은 효과를 내는 편법이 쓰였는데, 그 방법은 파일을 만들 때마다 오류가 로그에 쌓이고 화면에는 녹화 중으로 보였다. ⚠ 이 방식에서는 네트워크가 끊긴 구간의 자료가 영구히 사라진다 — 지금까지는 파일이 전송 실패의 완충재 역할을 했다. Changed: 웹 모니터링 전송량이 25~35% 줄었다. 파형 값을 늘 소수점 셋째 자리까지 보내고 있었는데, 화면은 그 트랙의 표시 범위 안에서 그리므로 범위의 1% 보다 고운 눈금은 어차피 구분되지 않는다 (심전도는 표시 범위가 4 mV 인데 0.001 단위로 보내 화면이 나타낼 수 있는 것의 40배를 보내고 있었다). 게다가 그 여분의 자릿수는 규칙성이 없어 압축도 되지 않아, 전송량의 4분의 1 이상을 차지했다 (중환자실 3개 병상 실측 26~35%). 이제 표시 범위에 맞춰 자릿수를 정한다. 값의 단위와 의미는 그대로이고 녹화 파일에는 원래 정밀도로 저장되므로 화면과 파일 모두 달라지는 것이 없으며, 서버나 웹 화면을 함께 고칠 필요도 없다. 예전 방식이 필요하면 WEB_WAV_PREC=3 으로 고정할 수 있다. Fixed: 녹화 파일을 만들지 못했을 때 아무 알림 없이 지나가던 문제. 디스크가 가득 찼거나 저장 폴더에 쓸 수 없으면 파일이 만들어지지 않는데, 화면에는 ‘녹화 중’ 으로 표시되고 자료는 조용히 버려졌다. 이제 로그에 경고를 남긴다. 1.19.28 Fixed: USB 로 연결한 장비의 수집이 어느 순간 조용히 멎고, 프로그램을 다시 켜야만 돌아오던 문제. 전원이 잠깐 흔들리면 USB 장비가 스스로 연결을 다시 맺는데 그때 운영체제가 새 포트 번호를 붙인다 (예: ttyACM0 이 ttyACM1 로). VR 은 처음 열었던 번호를 계속 붙들고 있어서, 장비도 케이블도 멀쩡한데 값만 들어오지 않는다 — 화면상 연결 상태도 정상으로 보여 눈으로는 알 수 없었다 (수술방의 Link+ 주입펌프에서 확인). Added: 포트 자리에 포트 번호 대신 장비의 고유 신원을 적을 수 있다. 형식은 20d0:0002 처럼 네 자리 두 개이며, 이 값은 장비에 박혀 있어 다시 연결되어도 바뀌지 않는다. 이렇게 적어 두면 VR 이 포트를 열 때마다 그 신원을 가진 자리를 다시 찾으므로, 번호가 바뀌어도 저절로 따라간다. 기존처럼 포트 번호를 적어 둔 곳은 그대로 동작한다. Changed: Link+ 는 포트를 적지 않으면 이 신원을 기본값으로 쓴다. 꽂기만 하면 잡히고, 다시 연결되어도 따라간다. 포트를 직접 적어 둔 곳은 적은 값이 우선이다. Changed: 자동 판별이 USB 신원을 먼저 본다. 고유 신원으로 자기를 밝히는 장비는 통신 속도·형식을 재 볼 필요가 없으므로 조용한 회선에서 72초가 걸리던 측정을 건너뛰고 바로 확정한다. 이렇게 확정한 장비는 포트를 번호가 아니라 신원으로 기록한다. 여러 장비가 함께 쓰는 범용 USB 변환 케이블(FTDI 등)은 신원이 케이블의 것이지 장비의 것이 아니므로 종전대로 측정해서 판별한다. 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.27 | 2026-09-05 | 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 | 2026-09-05 | 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 | 2026-09-04 | 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 | 2026-09-02 | 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 | 2026-09-01 | 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.22 | 2026-08-30 | 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.21 | 2026-08-29 | 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.20 | 2026-08-29 | 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.19 | 2026-08-27 | 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.18 | 2026-08-27 | 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.17 | 2026-08-27 | 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.16 | 2026-08-27 | 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.15 | 2026-08-27 | 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.14 | 2026-08-27 | 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.13 | 2026-08-27 | 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.9 | 2026-08-14 | 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.8 | 2026-08-14 | 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.7 | 2026-08-12 | 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.5 | 2026-08-06 | 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.4 | 2026-08-05 | 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.3 | 2026-08-04 | 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.0 | 2026-08-03 | 1.19.0 Fixed: 여러 베드를 한 프로세스가 TCP 로 수집하는 구성에서 클라이언트 재접속이 잦으면 프로세스 전체가 SIGSEGV 로 죽던 문제. accept_handoff 가 교체(replace) 시 옛 소켓을 즉시 closesocket 했는데, m_sock_mtx 는 fd 번호만 보호할 뿐 fd 의 수명은 보호하지 않아 reader 가 그 fd 로 recv 중일 수 있었다. close 는 blocking recv 를 깨우지도 않고 fd 번호를 즉시 재사용 가능하게 만들어, 다음 접속이 같은 번호를 받으면 reader 가 남의 세션을 읽었다. SNUH 회복실(VRN 15대, 최다 베드는 하루 785회 재접속)에서 실제로 크래시 후 9시간 48분 전 베드 무수집. 수정 세 겹: (1) tcp reader 를 poll(POLLIN,200ms) 게이트 뒤의 recv 로 재구성 - recv 가 무기한 블로킹되지 않으므로 close/replace 가 늦어도 200ms 안에, shutdown 이 동반되면 즉시 감지된다. fd 에 O_NONBLOCK 은 걸지 않는다(같은 fd 로 송신하는 write_transport 의 부분 전송 방지). (2) replace 는 closesocket 대신 shutdown 만 하고 옛 fd 를 retired 목록에 넣으며, 닫기는 깨어난 reader 또는 Serial::close 가 정확히 한 번 수행 - fd 번호 재사용 창이 사라진다. (3) Serial::close 가 join 전에 shutdown 으로 reader 를 깨우고(RCVTIMEO 는 설정된 적이 없어 종전에는 피어가 끊을 때까지 join 이 매달렸다), m_sock 접근을 전부 락으로 감싼다. has_socket 의 무락 읽기와 udp_thread_func 의 이중 close 도 같은 계열이라 함께 수정. Added: AUTO_DETECT=1 (vr.conf) - 시리얼 포트에 무엇이 물려 있는지 스스로 알아내 즉시 수집을 시작한다. 판별용 프로브 바이트를 따로 두지 않고 후보 드라이버를 실제로 열어 유효 레코드 도달 여부로 판정하므로 장비별 판별 코드가 없고 드라이버는 무수정이다. 4단계: 회선 측정(커널 프레이밍/패리티/브레이크 카운터로 보레이트와 프레임 폭 확정) -> 패리티 추론(10비트 프레임의 7E1/7O1 대 8N1 모호성을 MSB 상관으로 해소) -> 후보 필터(보레이트+프레임폭+데이터비트 3축 일치, 베드당 환자모니터 1대/마취기 1대 배타, 포트 위치 사전확률, 지난 판별 결과 캐시) -> 남으면 트랙 수확량으로 경쟁. SNUH 수술장 9방 + ICU 3베드 실측에서 Intellivue/MedibusX/Datex-Ohmeda/Primus 를 오탐 0 으로 판별했고, 표준과 다른 포트에 꽂힌 장비(P3/P4 의 Intellivue)도 찾아냈다. AUTO_DETECT 가 없으면 스캐너는 유휴 대기만 하므로 기존 설정에 영향이 없다. | |
| 1.18.52 | 2026-07-31 | 1.18.52 Changed: VR 기본 운영이 k3s 로 이관됨에 따라 서버→VR 하향 제어 경로를 제거. VitalServer 로는 데이터를 일방 push 만 하고, websocket(socket.io) 으로 내려오던 원격 명령 update/restart/reboot/edit_conf/new_bed/del_bed/edit_bed/dt/add_event 를 더 이상 처리하지 않는다 (원격 재부팅·바이너리 교체·conf 덮어쓰기·베드 구성변경 봉쇄). 구현: sendweb_thread_func 의 socket.io 42 이벤트 dispatch 와 on_sio_msg 함수를 통째로 제거. engine.io ping(2->3) 및 ws 레벨 ping/pong 은 연결 유지를 위해 그대로 응답하므로 데이터 업링크·웹모니터링은 종전과 동일. 서버는 VR 버전을 알므로 52+ 로는 명령을 보내지 않고, 받아도 무시. Fixed: --console 모드 (MFC) 에서 stdout 이 이미 유효한 파이프/파일 (> log.txt, ssh 파이프, cmd 상속 핸들) 인데도 무조건 _open_osfhandle+_dup2 로 재배선하면서 그 OS 핸들을 닫아버려, 초기화 이후 모든 로그가 조용히 사라지던 문제 (콘솔 모드 서버 배포에서 진단 불능의 원인, 1.18.7 부터 잠재). stdout 핸들이 유효하면 (GetFileType != UNKNOWN) 재배선하지 않고, 핸들이 없을 때만 (Explorer/schtasks 실행) AttachConsole 로 부모 콘솔에 붙인다. 실측: 종전 초기화 후 로그 침묵 -> 수정 후 지속 출력. (앞서 1.18.51 의 VRQt qDebug->stdout 통일과 함께 세 빌드의 --debug 로그 일관성 완성.) | |
| 1.18.51 | 2026-07-30 | 1.18.51 Fixed: VRQt (Qt 빌드) 에서 --debug 옵션을 켜도 콘솔에 로그가 전혀 출력되지 않던 문제. 원인: Qt 빌드의 TRACE 가 qDebug() 로 매핑되어 있었는데 (UTIL.h, 1.14.4 부터), Windows 에서 Qt 로깅은 GUI 앱이면 콘솔/파이프가 아니라 디버거 (OutputDebugString) 로만 보내므로 VRQt 만 --debug 콘솔 출력이 구조적으로 불가능했다 (MFC 는 stdafx.h 의 #undef TRACE 로 printf 버전을 사용해 정상). 수정: TRACE 를 세 빌드 (MFC/Qt/CLI) 공통 fprintf(stdout)+fflush 로 통일 - qDebug 직접 사용처는 코드에 없음을 확인. 이제 --debug 동작과 리다이렉트 (> log.txt) 가 전 빌드에서 일관되고, cmd 대화형 실행 시에는 기존 AttachConsole/AllocConsole 로직 (1.18.30) 이 콘솔을 만들어 준다. terapc 실측 검증: 종전 VRQt stdout 0 chars → 수정 후 4,690 chars 정상 출력, Vital.exe (MFC) 는 종전과 동일 정상. 추가 방어: --debug 의 콘솔 확보 검사에 GetFileType 추가 - 일부 런처/셸이 non-NULL 무효 stdout 핸들을 상속시키면 NULL 검사만으로는 AttachConsole 을 건너뛰어 로그가 사라질 수 있던 엣지 케이스 (MFC/Qt 공통). | |
| 1.18.50 | 2026-07-30 | 1.18.50 Fixed: 장비 연결이 한번 끊어지면 다시 붙지 않던 문제 (Link+ 현장 리포트, Intellivue 늦은 전원-온과 공통 근본 원인). 근본 원인: DEVICE::open() 의 초기화 호출 set_rx_time(0) 이 값 0 인데도 OPENING→ACTIVE 로 승격시키고 m_dtStatus 까지 0 으로 만들어, (1) 모든 장비가 open 즉시 가짜-ACTIVE (OPENING 상태가 사실상 부재), (2) restart 워치독의 dtlast(rx ?: status_time) 가 0 → read_timeout 조건이 영구 거짓 → 재연결 시 상대 무응답이면 (Link+ rack 재부팅/세션 잔존, 세션 프레임 1회 전송 후 무응답) 재시도 없이 영구 고착. 1.18.35 이하의 rec_timeout (상태 무관 60s 강제 재시작) 이 이 잠재 버그를 우연히 가리고 있었고 1.18.36 전환으로 노출된 것. 수정 3건: (1) set_rx_time 은 실수신 (dt>0) 일 때만 ACTIVE 승격 - 상태 표시도 정직해짐 (open 후 첫 수신까지 "con"), (2) restart 워치독에 OPENING-stuck 분기 추가 - open 후 read_timeout 내 첫 수신이 없으면 재시작해 협상 프레임 재전송, tx_time>0 (protocol-initiator) 조건으로 passive/keyword-sub 디바이스는 제외해 유휴 churn 없음, (3) tx_time 을 모든 transport (socket/fd/COM) 에서 갱신 (기존 Windows COM 만). 영향 장비: Link+/Agilia, Intellivue, S5, Datex, Hamilton 등 open 시 세션/assoc 을 시작하는 모든 protocol-initiator. E2E 검증: 무응답 서버 재현에서 종전 접속 1회 후 영구 침묵 → 수정 후 30초마다 재접속 반복, 장비 복구 시 자동 연결. Mindray 멀티베드 회귀 테스트 PASS (ACK 75/75, tier1 라우팅, probe 거절, stuck 오발동 0). Intellivue 1.18.48 assoc-retry (5s) 는 유지 - 이번 워치독은 30s 백스톱으로 공존. | |
| 1.18.49 | 2026-07-29 | 1.18.49 Fixed: (1) Mindray eGateway HL7 멀티베드 (catch-all + #키워드 sub) 에서 전 베드가 readonly=1 이면 데이터가 전혀 수집되지 않고 게이트웨이가 수초마다 재접속하던 문제. 원인 셋 - (a) readonly catch-all 이 open_socket 에서 조기 반환해 reader/parser 스레드가 아예 생성되지 않음 (아무도 안 읽는 소켓에 데이터가 쌓이다 게이트웨이가 연결 재수립 반복; PortRouter 리팩토링 시 readonly 체크가 blocking accept 앞으로 이동하며 생긴 1.18.36 회귀 - 1.18.31 은 accept 가 먼저라 우연히 동작), (b) readonly 가 HL7 ACK 송신까지 차단 (eGateway 는 메시지마다 ACK 요구), (c) sibling 베드 메시지는 broadcast 후 ACK 없이 return. 수정 - readonly 는 송신만 차단하고 수신은 정상화 (broadcast-only keyword sub 만 소켓 미보유), write_protocol 경로 신설로 ACK 는 readonly 우회, catch-all 이 sibling 메시지도 대신 ACK (R01/R40 타입 구분), broadcast 수신 sub 의 rx_time 갱신으로 상태표시 정상화, PortRouter takeover 는 기존 소켓이 stale (10초+ 무수신) 일 때만 허용해 게이트웨이 health-probe 접속이 live 데이터 소켓을 뺏지 못하게 함. 시뮬레이터 E2E 검증: ORU 75건 -> ACK 75건 (100%), probe 5회 전부 무해 거절, SU-2/SU-4 tier1 라우팅 정상. (2) Getinge/MAQUET Flow-i (FCI 프로토콜) tidal volume 이 실제의 절반으로 기록되던 문제. 원인: FCI 프로토콜 v0005 부터 TV 채널 (101/102) gain 이 0.1 -> 0.2 로 변경됐는데 (Flow-i communication interface v0007 문서), VR 은 open 시 SPVE0005 로 v5 를 요청하면서 파싱은 구버전 gain 0.1 을 적용. 병원 vital 파일 실측 (Flow-i 직결 TV 201.4/188.2 vs 필립스 경유 403.0/376.6, MV 는 정상) 으로 검증 - TV_INSP/TV_EXP gain 0.2 적용. MV 는 v3+ 스케일 (0.01) 이 이미 반영돼 있어 변경 없음. (3) Flow-i 설정 파라미터 미지원 (m_ch_settings 빈 맵) - v5+ 에 존재하는 수치 설정 채널 11종 등록: SET_RR(411), SET_INSP_TM(413), SET_IE(418), SET_MV(421), SET_PC(423), SET_PEEP(424), SET_FRESH_FLOW(428), SET_FRESH_O2(430), SET_AGENT_PERC(434), SET_PS(436), SET_SIMV_RR(437). gain 은 문서 표기 그대로. 주의: Set Tidal Volume (채널 422) 은 FCI v5 에서 프로토콜 자체에서 삭제되어 직접 수신 불가 - 볼륨 모드에서 SET_MV/SET_RR 로 파생 가능. 설정 채널은 문서 기반 구현으로 실기 검증 대기 상태. | |
| 1.18.48 | 2026-07-24 | 1.18.48 Fixed: VR 이 먼저 실행된 상태에서 Philips Intellivue 모니터를 나중에 켜면 웨이브가 영원히 수집되지 않던 문제 (VR 재시작 전까지 모니터로 아무 요청도 안 나감). 원인: Intellivue::open() 은 assoc_req 를 딱 한 번 보내는데, 시리얼(MIB)/UDP 는 모니터가 꺼져 있어도 포트가 열리므로 이 요청이 응답 없이 사라진다. Philips 는 자기가 먼저 말을 걸지 않는 프로토콜이고, 첫 수신이 있어야 디바이스 상태가 OPENING→ACTIVE 로 승격되는데 수신이 없으니 OPENING 에 고정 - DEVICE::restart_thread_func 워치독은 is_closed(재오픈)/is_active(read_timeout 30s) 두 분기만 있어 OPENING 은 어느 쪽에도 안 걸림 → assoc 재전송도 재시작도 영원히 없음. 수정: Intellivue 에 assoc 재시도 스레드 추가 - 세션 확립(MDS_CREAT 수신, m_bSessionUp atomic 플래그) 전까지 5초 간격으로 assoc_req 재전송, 최근 5초 내 수신이 있으면(assoc 진행 중) 재전송 보류. 모니터를 나중에 켜도 최대 5초 내 연결. 세션 확립 후에는 재시도 루프 종료 - 기존 동작(AB_SPDU 시 재연결, read_timeout 재시작)과 충돌 없음. 모니터가 켜져 있던 기존 시나리오는 첫 assoc_req 에 바로 응답하므로 동작 변화 없음 - 0-impact. | |
| 1.18.47 | 2026-07-24 | 1.18.47 Fixed: 파일 업로드(및 Http 클래스를 쓰는 모든 경로 - upload_vital, getvr 업데이트체크, ADT 조회, 필터서버)가 SERVER_IP/UPLOAD_SERVER_IP 에 "https://host:port" 풀 URL 형식을 넣으면 "Couldn't resolve host name" 으로 전부 실패하던 문제. 원인은 src/Https.cpp 의 Http 생성자 - 1.18.46 의 wss.cpp 수정과 동일한 버그의 나머지 반쪽. 첫 콜론을 포트 구분자로 오인해 "https://eicuvital.ywmc.or.kr:4432" 입력 시 m_strHost 가 문자 그대로 "https" 가 되어 DNS 조회 실패 (웹모니터링 wss 는 1.18.46 에서 이미 수정되어 정상, 파일 업로드만 실패하는 비대칭 발생 - 현장에서는 업로드 루프의 실패시 break 정책과 겹쳐 "베드명에 # 가 있으면 전송이 안 된다" 로 오인 관찰됨. 실제로는 # 파일이 디렉토리 순서상 항상 첫 시도라 거기서 host 오류로 중단 → 뒤 파일 전체가 막힌 것. 베드명 특수문자는 업로드 URL 과 무관). 수정: Http 생성자에 wss 와 동일한 선행 스킴(https/http) 파싱 추가 - 스킴이 TLS 여부를 결정 (포트 무관, 4432 같은 커스텀 포트에서 https 정상), "https://host" 포트 생략 시 443, 경로 붙은 입력도 host 만 추출. 기존 "host" / "host:443" 형식은 종전과 완전 동일 동작 - 0-impact. Windows(WinINet)/Linux(curl) 공통 생성자라 모든 플랫폼에 적용. | |
| 1.18.46 | 2026-07-23 | 1.18.46 Fixed: WSSClient 가 "https/wss 스킴 + 비표준 포트" 조합을 제대로 처리하지 못하던 문제 (커스텀 포트의 wss 서버에 연결 불가). 원인은 src/wss.cpp 의 WSSClient 생성자 두 곳 - (1) SERVER_IP 파싱이 첫 콜론을 포트 구분자로 써서 "https://host:4432" 를 넣으면 "https" 의 콜론을 먼저 잡아 m_host 가 "https" 가 되고 포트 파싱이 깨져 getaddrinfo 실패, (2) m_secure = (m_port == 443) 로 TLS 여부를 오직 포트 443 여부로만 판단해 4432 같은 커스텀 포트는 무조건 평문 ws 로 붙어 wss 전용 서버에서 실패. 수정: 생성자가 선행 URL 스킴(https/wss/http/ws)을 먼저 파싱해 스킴이 있으면 스킴이 TLS 를 결정(포트 무관)하고, 스킴이 없으면 기존 포트 443 휴리스틱을 유지(하위호환). 이제 SERVER_IP=https://eicuvital.ywmc.or.kr:4432 (또는 wss://...) 형식이 비표준 포트에서도 wss 로 정상 연결되며, SNI/인증서 검증은 도메인(m_host)을 쓰므로 와일드카드 인증서도 정상 매칭. 기존 host / host:443 설정은 종전대로 secure 유지 - 0-impact. | |
| 1.18.45 | 2026-06-16 | 1.18.45 Fixed: Windows VR 에서 사용자가 외부에서 vr.conf 를 멀티베드 설정으로 교체 후 VR 을 재실행하면, 첫 실행에서는 멀티탭이 정상 생성되지만 다음 실행에서 모든 탭이 사라지던 문제. 원인은 VRApp.cpp:1110 의 VRCODE 미설정 분기 — 새 vr.conf 가 VRCODE 라인을 포함하지 않으면 load_settings() 가 신규 VRCODE 를 issue_code() 로 발급하고 즉시 save_settings() 를 호출하는데, 이 시점은 load_settings() 가 파싱한 베드들이 로컬 newbeds 벡터에만 있고 아직 m_pdisps 로 옮겨지기 전 (set_beds(newbeds) 는 initialize() 가 load_settings 반환 후 라인 1328 에서 호출). 결과: save_settings() 가 빈 m_pdisps 를 iterate 하며 vr.conf 를 글로벌 설정만으로 덮어써 모든 [BED/...] 섹션이 디스크에서 사라짐. 첫 실행은 메모리의 newbeds 로 UI 멀티탭이 그려져 정상으로 보이지만 (사용자가 "멀티탭은 정상적으로 생성되지만" 으로 표현), VR 종료/재실행 시 디스크 conf 에는 BED 섹션이 없으므로 다음 load_settings 가 빈 베드 리스트를 반환 → 탭 전부 사라짐. 수정: VRApp.h 에 m_vrcode_newly_issued bool 플래그 추가, load_settings 의 즉시 save 를 플래그 set 만 으로 교체, initialize() 의 set_beds(newbeds) 직후 (m_pdisps 가 채워진 시점) 플래그가 켜져 있으면 한 번만 save_settings() 호출 → BED 섹션이 정상 직렬화됨. Linux/Pi 분기 (m_serial_number 가 채워져 있는 경우) 는 원래도 save 안 했으므로 영향 없음, VRCODE 가 이미 conf 에 있는 모든 기존 deployment 도 분기 미진입으로 0-impact. m_conf_from_stdin / m_bDebug 모드는 save_settings 내부에서 이미 early return 이라 그대로 안전. MFC + Qt 빌드 모두 통과 (이번 변경 관련 0 warning). | |
| 1.18.44 | 2026-06-12 | 1.18.44: vr.conf FHIR=1 모드 추가 (자체 FHIR 수신서버 대상 실시간 푸시) + VS 2026 / 새 OpenSSL 인스톨러 빌드 환경 호환 shim. (1) sendweb_thread_func 의 기존 HL7/JSON 양분 분기 (VRApp.cpp:2466) 옆에 bFHIR 분기 추가. 1초 tick 마다 한 vrcode 의 모든 방 Observation 을 하나의 FHIR R4 collection Bundle 로 묶어 기존 WSS 채널로 push (gzip + WSSClient 인프라 그대로 재사용). MLLP 같은 신규 transport 안 도입 — FHIR R5 Subscription 의 websocket channel type 과 동형이라 표준 부합. (2) DISPLAY::get_fhir + TRACK::get_fhir 신규. v2 (vitaldb-fhir-v2) 와 동일한 reference-only 패턴 (Patient.id = m_patient_id, Encounter.id = vrcode-bedname-startsec, Observation.subject/encounter = relative reference) + hybrid keyframe: 첫 tick / m_patient_id 변경 / get_start_time() 변경 / 5분 경과 중 하나라도 발생 시 다음 tick Bundle 에 Patient + Encounter resource 를 inline. 그 외 tick 은 Observation 만. 수신서버가 도중에 새로 connect 해도 최대 5분 안에 자동 동기화 (keyframe heartbeat). DISPLAY 에 m_fhir_pt_sent_at/m_fhir_pt_last_id/m_fhir_enc_last_start 3개 멤버 추가 (m_setting_sent 패턴 그대로 per-display). (3) Observation 인코딩: NUM 은 effectiveDateTime + valueQuantity 각 데이터 포인트당 한 entry, WAV 은 effectivePeriod + valueSampledData (period=1000/srate ms, factor=1, origin=0, data=공백구분 floats) 한 entry. CO2/AWP wav 는 기존 get_json 처럼 25Hz 다운샘플 유지. NUM 은 m_montype 기반 LOINC 매핑 15개 등록 (ECG_HR/PLETH_HR=8867-4, PLETH_SPO2=59408-5, NIBP/IABP SBP/DBP/MBP=8480-6/8462-4/8478-0, BT=8310-5, RR=9279-1, etCO2=19891-1, CVP=8556-0, FETAL_HR=11627-3) + VR 자체 코드 시스템 (https://vitaldb.net/fhir/CodeSystem/track, code = dname/tname) 항상 동반. m_patient_id 미설정 (PATIENT_ID_UNSET) 시 anon-vrcode-bedname identifier 로 fallback. (4) Bundle wrapper: VRApp 가 방별 entries 를 콤마로 합쳐 {resourceType:Bundle, type:collection, timestamp:iso8601Z, entry:[...]} 로 wrap, 기존 gzip → WSS pipeline 동일. ISO 8601 UTC 헬퍼는 std::gmtime_s/gmtime_r 인라인. FHIR id 안전화 (A-Za-z0-9-.) 64자 제한 lambda 각 함수에 inline. (5) 빌드 환경 호환성: VRQt/CMakeLists.txt 에 _SILENCE_EXPERIMENTAL_COROUTINE_DEPRECATION_WARNINGS 추가 — VS 2026 (MSVC 14.51+) 의 STL 이 <experimental/coroutine> deprecation 을 warning → hard static_assert 로 바꾼 것이 cppwinrt 의 winrt::* (Serial.cpp 의 Bluetooth LE) 를 끌어오는 모든 TU 에서 컴파일 실패 시켜서, build_mfc.py 가 이미 CL env 로 주입하던 것과 동치 shim 을 Qt 쪽에도. (6) VRQt/CMakeLists.txt 의 OpenSSL link 경로 시프트. 새 OpenSSL 인스톨러가 libssl_static.lib / libcrypto_static.lib 를 lib/VC/x64/{MT,MTd}/ 에 두고 v1.x 의 libssl64MT.lib / libcrypto64MT.lib 네이밍과 lib/VC/static 경로를 깬다. build_mfc.py 의 %LOCALAPPDATA%/openssl-compat 패턴 (옛 이름으로 복사한 사본 폴더) 을 그대로 따라 CMakeLists 도 그 폴더가 있으면 우선, 없으면 옛 lib/VC/static 으로 fallback. 부트스트랩 스크립트는 build_mfc.py 의 docstring 참조 (한 줄). MFC + Qt + RPi + Ubuntu CLI + AppImage 빌드 모두 통과. 동작 영향: vr.conf 에 FHIR=1 추가 안 한 기존 deployment 는 sendweb 가 종전 JSON 모드 그대로 (HL7=1 만 분기보다 우선) — 0-impact migration. | |
| 1.18.43 | 2026-05-21 | 1.18.43: 멀티베드 console (PACU 10베드 Pi) syslog noise 80,500/hr → ~1,400/hr (-98.3%), cpu_usage 측정 1.16.12 (9f26e36) 이후 8년치 영구 -1 버그 수정, 멀티베드 환경 베드 식별용 [bedname / devicename] 로그 prefix 도입. 동기는 PACU production Pi (1.18.40) 4일치 syslog 분석 — 1.24M 라인 중 vr 라인 836K, idle 8시간 평균 시간당 80,500 라인 (베드 10 × 1초마다 "Waiting for next patient" × syslog 2x 중복). (1) VRApp.cpp:2696-2710 timer_thread_func 의 cpu usage 계산이 `if (totalcpu && m_cpu_total_last && totalcpu > m_cpu_total_last)` 가드 안에서만 m_cpu_total_last 갱신해 첫 호출 시 m_cpu_total_last=0 → else 분기 → -1 → m_cpu_total_last 갱신 안 됨 → 영원히 false → 영원히 -1 데드락. PACU 1,048 샘플 100% -1 실측 확인. m_cpu_total_last/m_cpu_used_last 갱신을 if 밖으로 빼서 첫 호출만 baseline 으로 -1, 두 번째 (60s 후) 부터 정상 측정. /proc/stat 의 iowait/irq/softirq 까지 7개 필드 fscanf 로 정확도 ±5% 개선. 부수: 같은 함수의 cpu_temp 가 `temp / 1000` int 나눗셈으로 소수점 절단되어 PACU 측정값이 정수 50/51/...,/58 9종만 출력 (millidegree 정밀도 손실) → `temp / 1000.0f` 로 fix. ram 계산은 `(totalram + totalswap) * mem_unit` 결과에 다시 mem_unit 곱하던 차원 혼용 (Linux 가 mem_unit=1 이라 우연히 정상 동작했지만 dimensionally wrong) → `(units1 + units2) * mem_unit` 로 정정 + underflow 가드. (2) DISPLAY::log(string/printf) + DEVICE::log(string/printf) 멤버 추가. DISPLAY::log 는 `[bedname] msg`, DEVICE::log 는 m_pdisp 위임 없이 직접 `[bedname / devicename] msg` 한 줄 prefix (nested `[bed] [dev]` 보다 가독성 우선). m_pdisp 또는 bedname null 케이스 safe fallback. (3) show_msg 의 console path TRACE 를 log() 로 교체 → "Waiting for next patient" 자동 [bedname] prefix. ADT patient id unset 도 log() 로. (4) 이미 수동으로 get_bedname() 을 인자로 넘기던 8군데 (DISPLAY ptcon_thread_func / add_trk / add_dev / del_dev / cut-recording, Device add_rec 콜백, FILT stop, VRApp add_tab/del_tab) 를 log() 로 전환 — 이중 prefix 방지 + 일관성. Serial::reading_thread / tcp_thread / open_socket waiting / timeout / interrupted / got-client 6군데 DEVICE::log() 전환 → `[bedname / Intellivue]` 등 자동 prefix, 멀티탭 환경에서 어느 베드 어느 device 출처인지 즉시 식별. (5) wss 재시도 폭주 throttle. WSSClient 에 m_verbose (debug step 로그 게이트) + m_dt_last_err_log (60s window) 추가. connect() 내부 step-by-step TRACE 4개 ("connecting" / "connect error" / "connected") 는 m_verbose 가드, 최종 "wss connect error" 는 60s window throttle (첫 줄 즉시, 그 다음 60s 1회). connect 성공 path 에서 throttle reset 으로 다음 disconnect → 첫 에러 즉시 로깅. sendweb_thread_func 의 "connecting websocket server" 호출자 라인도 같은 60s window, m_bDebug 면 verbose 활성화. PACU idle 8h 측정 7,024/hr (4종 × 1,756) → 약 60/hr (-99%). (6) "Waiting for next patient" / "ADT patient id unset" 의 60s throttle 자체는 feature/wav-mem-retention 브랜치 ptcon_thread_func 재구성 (b75928f 시리즈) 에서 이미 들어가 있어 이 릴리즈에 자동 포함. 종합 영향: idle 시간당 syslog 라인 80,500 → ~1,400 (-98.3%), active 시간당 14,900 → ~700 (-95%). 진단 측면: cpu_usage 가 부활하면서 웹모니터링 json 의 cpu_usage 필드도 한 번도 안 채워지던 게 정상 전송 (VRApp.cpp:2499 의 `if (m_cpu_usage > 0)` 가드가 영원히 false 였음). 별건: syslog 라인 2× 중복은 application 측이 아니라 rsyslog imuxsock + imjournal + journald.ForwardToSyslog=yes 의 Debian Bookworm 전형 misconfig — admin 측 fix (rsyslog.conf 에서 imjournal 주석 처리 + restart) 로 추가 50% 감소 가능 (별도 문서). MFC + Qt + Pi (RPI64) + Ubuntu CLI 빌드 모두 통과. | |
| 1.18.41 | 2026-05-13 | 1.18.41 Hotfix: TCP-server protocol-initiator device (Intellivue/S5/Datex/Hamilton/MPS/Agilia/IAP) 가 open() 직후 보내는 첫 패킷이 wire 에 안 나가던 회귀. 1.18.34 의 PortRouter refactor 가 Serial::open_socket() 의 8초 handoff blocking 을 제거하고 비동기 모델로 전환했는데, protocol-initiator 의 open() 코드가 m_sock=INVALID 상태에서 write 를 시도해 silent fail (Linux 는 m_fd=0 이라 false 반환). Intellivue 케이스 production journald 의 Pi → ESP TX 700K~3M bytes 는 monitor 측 abort 로 늦게 트리거된 AB_SPDU_SI recovery assoc_req 후 request_thread 의 정상 polling 트래픽이지만 일부 베드 (abort 없는 monitor) 는 recovery 도 안 되어 영구 silent (RX 300 bytes). 수정 방향: open() 의 옛 contract ("return 시점에 통신 가능") 복구. (1) Serial::open_socket() TCP-server 분기가 PortRouter::subscribe 후 m_sock_cv 로 첫 client handoff 까지 block (timeout = device read_timeout, 기본 30s) — 옛 모델 그대로. lock_and_open 이 false 면 다음 tick 에서 retry. close 신호로 abort 가능. (2) Serial::close() 는 PortRouter::unsubscribe 안 함 — subscribe 는 device 생애 동안 sticky 라 read_timeout 으로 인한 close→re-open 사이클 동안 listen socket 이 유지되어 1.18.29/1.18.34 의 anti-churn 이익 보존. unsubscribe 는 ~Serial() 에서만. (3) Per-device lifecycle thread (DEVICE::m_thread_restart): 각 device 가 자기 open/close/timeout 재시작을 별도 thread 에서 관리. 옛 모델의 DISPLAY::bed_manager 가 베드의 모든 device 를 sequential iterate 하던 구조에선 한 device 의 blocking open() 이 같은 베드의 다른 device 의 lifecycle 을 차단했는데, per-device thread 로 분리하여 멀티 device 베드도 병렬 진행. add_dev 에서 start_lifecycle, del_dev / ~DISPLAY 에서 stop_lifecycle. (4) Serial::tcp_thread_func 단순화 — outer m_sock_cv wait 패턴 (1.18.34/1.18.39 가 추가했던 것) 제거. open_socket 이 m_sock valid 보장하므로 reader thread 시작 시점에 simple inner recv loop 만. accept_handoff 의 replace (zombie cleanup) 케이스는 recv error 후 m_sock 변경 확인 + retry 로 처리. (5) reading_thread_func 의 `else if (m_sock != INVALID_SOCKET) tcp_thread_func();` 가드 복원 (1.18.39 가 제거). (6) Serial::write 의 silent-fail 케이스에 WARN trace 추가 — 미래 동종 회귀 즉시 노출. (7) PortRouter::subscribe 가 같은 (port, type) 에서 같은 IP filter 또는 둘 이상 catch-all 발견 시 reject (config 실수 원천 차단, 옛 WARN-only 동작 강화). (8) DISPLAY::bed_manager_thread_func 의 restart 블록 제거 — ptcon + adt 만 담당. "Waiting for next patient" TRACE 가 Linux/console 에서 1초 → 60초마다 throttle (별개 journald 부풀림 fix). 영향 범위: Intellivue / S5 / Datex / Hamilton / MPS / Agilia / IAP TCP-server 모드 베드 전부. 1.18.34 이후 우회 방법은 race-win 케이스 (VRN 이 PortRouter::subscribe 직후 connect) 외엔 없었음. MFC + Qt + Pi (RPI64) + Ubuntu Qt + AppImage 빌드 모두 통과. | |
| 1.18.40 | 2026-05-12 | Hotfix: Drager Medibus 프로토콜 사용 베드에서 1.15.11 이후 vr.service 가 SIGSEGV 로 무한 재시작하던 문제. 1.15.10 (a8c7b2a) 의 "Sampling rate estimation" 리팩터링이 m_ptrk_awp/awf/co2 트랙을 add_trks() 에서의 eager 생성 (default srate=62.5Hz) 에서 realtime data 100 샘플 모인 후 lazy 생성으로 바꿨는데, Medibus 핸드셰이크 순서 (0x51 ICC → 0x52 DeviceID → 0x53 RealtimeConfig 응답 → 0x54 → realtime data 시작) 상 0x53 응답 핸들러 (Drager.cpp:295-307) 가 트랙들이 아직 nullptr 인 시점에 `ptrk->m_srate = 1000000.0/interval` 로 NULL 역참조 → 즉시 SEGV. 그 베드의 vr.service 가 systemd Restart=on-failure 로 재시작 → 다시 connect → 동일 0x53 패킷 받고 동일 SEGV → 무한 루프 (journald 로그상 restart counter 61 까지 누적, code=killed status=11/SEGV). 1.15.9 까진 트랙이 add_trks() 에서 생성되어 0x53 도착 시 항상 non-NULL 이라 안전했음. 수정: (a) add_trks() 에서 m_ptrk_awp/awf/co2 eager 생성 복원 (srate=62.5Hz default) — 0x53 는 ptrk->m_srate 만 갱신 (트랙 재생성 X). (b) on_received() 의 lazy creation + packet-timing srate 추정 블록 제거 — 0x53 가 권위 있는 srate 소스이고 packet 타이밍 기반 추정은 network jitter 에 약해 불안정. (c) 0x53 핸들러에 `if (interval > 0)` guard 추가 — 페이로드 깨졌을 때 div-by-zero / inf srate 방지. 영향 범위: Drager Primus / Apollo / Perseus / Fabius 등 Medibus 사용 마취기 전부. 우회 방법은 1.15.9 다운그레이드 외엔 없었음 (그 베드의 모든 데이터 유실 + 환자 모니터링 불가). MFC + Qt + Pi (RPI64) + Ubuntu Qt + AppImage 빌드 모두 통과. | |
| 1.18.39 | 2026-05-08 | Fixed: PortRouter (TCP server) 모드에서 reader thread 가 startup 시점에 죽어 client 데이터를 영구히 못 받던 문제. 1.18.34 (9bd252f) 의 PortRouter refactor 가 m_sock 세팅을 dispatch 시점으로 옮겼는데 reading_thread_func (Serial.cpp:354) 의 가드 `else if (m_sock != INVALID_SOCKET) tcp_thread_func();` 는 옛 모델 (open 시점 이미 connect 완료) 가정 그대로라 — 시작 직후 m_sock=INVALID 면 tcp_thread_func 진입 안 하고 reader thread 즉시 종료. 이후 VRN 이 connect → PortRouter::dispatch → accept_handoff 가 m_sock 채우고 m_sock_cv.notify_all() → 그러나 wait 중인 reader 가 없음 (이미 죽음) → kernel recv buffer 에만 데이터 적재. 증상은 ss -tn state established 의 Recv-Q 가 0 이 아닌 값 (수백 bytes) 으로 누적, 베드 화면에는 데이터 안 들어옴. 그 베드의 VRN 이 Serial::open() 의 PortRouter::subscribe 직후~reader thread spawn 사이의 짧은 윈도우 안에 connect 한 경우만 우연히 정상 동작 (race-win). 수정: 가드 제거 — TCP 분기에 항상 진입. tcp_thread_func 자체에 이미 m_sock_cv outer-wait 루프가 있어 m_sock 이 INVALID 인 동안 dispatch 가 깨워줄 때까지 안전하게 대기. Pi 핫스팟에 VRN 여러 대 (e.g. 10.42.0.21~25) 를 같은 (4343, Philips:Intellivue) 키로 묶고 IP filter 로 라우팅하는 시나리오에서 가장 자주 노출. Added: 진단 로그 보강 — PortRouter::accept_loop 의 accept 이벤트, dispatch 의 라우팅 결정 (ip_specific/catch_all subs 수, 어느 dev 가 받았는지), subscribe/unsubscribe 의 entry 생성/append/destroy, reading_thread / tcp_thread 의 시작·wait·wake 라이프사이클 — 이전엔 모두 m_bDebug 게이트 안에 있어서 production journald 에서 안 보였는데, 이제 항상 출력 (이번 같은 dispatch routing 문제 진단을 production 에서 바로 가능). MFC + Qt + Pi (RPI64) + Ubuntu Qt + AppImage 빌드 모두 통과. | |
| 1.18.38 | 2026-05-08 | Fixed: 웹모니터링 우클릭 → 디바이스 세팅 (websocket edit_bed 명령) 으로 베드의 device/filter 를 변경할 때 베드 이름이 매번 "_2" suffix 로 자동 리네임되어 vr.conf (Pi: /data/vr.conf) 에 잘못된 이름으로 저장되던 문제. 원인은 1.18.22 (cda8aa5) 에서 add_bed 에 추가된 bedname dedup ("최후 방어선" 으로 forward_frame auto-create / 수동 편집된 conf 의 중복 이름 처리용). edit_bed 핸들러는 옛 pdel 이 m_pdisps 에 그대로 있는 상태에서 같은 bedname 으로 new DISPLAY 를 add_bed 하고 그 다음 del_bed(pdel) 하는 순서였는데, add_bed 가 dup 감지해 새 pdisp 를 bedname_2 로 자동 리네임 → save_settings 가 [BED/bedname_2] 로 직렬화 → 사용자 입장에선 "설정 적용 안됨" 으로 보였다. 추가로 옛/새 DISPLAY 가 잠깐 m_pdisps 에 공존하면서 같은 device 포트를 동시에 들고 있으려 해 로그가 dup 으로 찍히는 부수 증상도 있었음. 수정: del_bed(pdel) 를 add_bed(pdisp) 보다 앞으로 이동 — 옛 베드의 device 포트가 깨끗이 release 된 뒤 새 pdisp 가 같은 포트를 reopen, dedup 발동 안 함. 다른 베드의 recording 은 영향 없음 (인메모리 hot swap 설계 유지). 부수: edit_bed 핸들러의 데드 코드 (`break` 뒤의 unreachable olddevs populate 루프 + `if (olddevs.find(name) == olddevs.end())` 항상-true 래퍼) 정리. edit_conf 와 save_settings 는 손대지 않음. MFC + Qt + Pi (RPI64) 빌드 모두 통과. | |
| 1.18.37 | 2026-05-08 | Refactored: 필터 다이얼로그의 python 런타임 — pyvital → openvital 0.3.0 로 교체 + 동봉 python.zip 슬림화. 핵심 동기 셋: (1) pyvital 0.6.0 이 tensorflow+torch+keras 를 hard-dep 으로 끌어들여 PyPI upgrade 가 사실상 깨짐, (2) 기존 zip 121 MB 안에 botocore/aiobotocore/s3fs/pyarrow/pandas/aiohttp 등 vitaldb 클라우드 업로드용 deps 가 ~250 MB unpacked 차지 — 필터 서버는 한 번도 쓴 적 없는 dead weight, (3) sanic 25.x 의 multi-process worker 가 openvital __main__.py 를 fork 후 재import 하면서 worker 가 module-top app instance 를 못 찾아 "Sanic app name not found" 로 죽음 — 단순 endpoint 2개 로컬호스트 서버에 async stack 자체가 과함. 해결: openvital 0.3.0 (commit b6f445b) 가 sanic 을 stdlib http.server (BaseHTTPRequestHandler + HTTPServer) 로 50 LOC 재작성 — wire protocol (GET / 필터목록 JSON, POST /<modname> gzip 본문) 동일해 VR 클라이언트 변경 0줄. 단일 스레드로 기존 sanic event loop semantics (cfgs/default_cfgs lock-free 변경) 보존. build_python_zip.py (NEW) 가 Python 3.11.9 embed + numpy + openecg + openvital (local checkout 우선, fallback PyPI) 만으로 27 MB zip 빌드 — 기존 121 MB 대비 78% 감소. ML 필터 (sv_dlapco/abp_hpi/ecg_classifier/ecg_beat_noise_detector) 는 [Hybrid 모델] 사용자가 Add filter 다이얼로그의 "Install ML filters" 버튼 누를 때 silent pip install openvital[all] (cmd 창 미노출 — VRApp::install_pip_package + run_pip_silent 이 자체 progress 다이얼로그에 stdout heartbeat 흘림). 버튼 라벨은 "Upgrade pyvital" → "Install ML filters" 로 의도 명시. openvital __main__.py 가 missing extras 를 graceful skip 하므로 base 환경에서도 11개 필터 (ECG QRS detector, HRV, MTWA, annotator, eeg_fft, nirs_cox, pkpd_3comp, pleth_dpop/ptt/pvi, resp_compliance) 즉시 사용 가능. Migration: 기존 사용자의 user_dir/python 폴더는 pyvital + 옛 deps 가 그대로 남아있고 vitaldb.net/python.zip 만 새로 올리면 (a) 기존 폴더 삭제 후 install_python 으로 새 zip 받으면 깔끔, (b) 폴더 유지 시 server 가 openvital 못 찾아 필터 다이얼로그 에러 → release note 에 명시 필요. MFC + Qt 양쪽 코드: VRGui.rc 의 IDC_UPGRADE 버튼 라벨 + DlgAddFilter.cpp 의 dist-info prefix 매칭 (pyvital- 8자 → openvital- 10자) + QtDlgAddFilter.cpp 도 동일 변경 + onUpgrade 가 cmd /k QProcess::startDetached 대신 theApp.install_pip_package("openvital[all]") 호출 (Linux Qt 빌드는 #ifdef _WIN32 가드). VRApp.cpp:318 의 python -m pyvital → -m openvital. install_pip_package 는 anonymous namespace 의 is_safe_pkg_spec 화이트리스트 검증 + run_pip_silent (CreateProcessA + CREATE_NO_WINDOW + 자식 stdout pipe) + CDlgProgress / QtDlgProgress 자체 progress UI. 빌드 검증: python -m openvital 이 GET / 로 11 필터 반환, sanic import 부재 확인. zip 27.5 MB. | |
| 1.18.36 | 2026-05-07 | Fixed: 3-파티션 레이아웃 (boot/rootfs/data) 에서 websocket edit_conf / update 명령이 read-only fs 로 실패하던 문제. vr.conf 는 /data 폴더가 있으면 /data/vr.conf 로 자동 정착 — get_conf_path() 가 write target 으로 결정, load_settings() 는 primary 가 없으면 legacy /boot/firmware/vr.conf 또는 /boot/vr.conf 에서 fallback read 후 다음 save 부터 primary 로 자동 이전 (마이그레이션 step 없음, legacy 파일은 read-only fs 라 무해). binary update (upgrade_now) 는 RAII BootRwGuard 가 dirname(module_path) 를 잠시 rw remount, scope 종료 시 sync() + MS_REMOUNT|MS_RDONLY 로 자동 ro 복귀 — vfat 노출 윈도우 최소화 + exception/return 어디서든 복구 보장. statvfs ST_RDONLY 검사로 이미 rw 인 fs (x86 dev) 에서는 no-op, non-Linux (macOS) 는 stub 으로 통과. save_settings() 의 silent EROFS 실패가 TRACE 로 가시화 (이전엔 무시되어 conf 수정 안 먹는 원인 추적 불가). 부수: PortRouter.cpp 가 commit 9bd252f 이후 CMakeLists 의 RPI sources 목록에서 누락되어 Pi 빌드 link 에러 — 추가. | |
| 1.18.35 | 2026-05-01 | Added: alarm parsing for Mindray HL7 (eGateway ORU^R40) and Nihon Kohden (NealTime HL7GW + ORF, BSM serial D-port, EGA UDP) - emits ALARM_STATUS / ALARM_MESSAGE / ALARM_PRIORITY tracks (same 3-track schema already used by Philips IntelliVue, GE Solar/Dash, GE/Datex S5); type info preserved via [T]/[A]/[ARR] message prefixes; Mindray facet 1/5/6/7 walker with 92-entry MDC+MNDRY alarm code lookup; NK HL7GW parses OBR-4=EVENT with arrhythmia/technical disambiguation by OBX-5; NK BSM serial decodes per-parameter * flags (edge-triggered to avoid 1Hz flooding) plus 32-bit Table 9 arrhythmia bitmap, D19 alarm-suspend nibble, and the VPC CODE byte; NK EGA UDP extracts Priority + Tech Alarm + Arrhythmia from JSON Vital Sign packets with edge-trigger cache for start/end inference; ACK^R40 reply added for Mindray alert messages; simulators gained periodic alarm cycling for end-to-end smoke testing | |
| 1.18.34 | 2026-04-27 | Added: Linux Desktop Qt GUI build (VRQt) - first GUI build for Linux distributed as a self-contained AppImage (~32MB, Qt6 runtime + libGL/libEGL/libGLX/libOpenGL/libGLESv2/libGLdispatch bundled inside) so users on minimal Ubuntu/Debian/Fedora can run it without installing Qt or GL system packages; tested on AWS clean Ubuntu 22.04 cloud image; build via build_ubuntu_appimage.py which is now invoked by deploy.py; Fixed: Linux Qt right-side device/filter panels rendered with white background below cards because the inner listWidget did not autoFillBackground from parent palette on Linux Qt (Windows/macOS implicitly inherit, X11/Wayland do not); explicit stylesheet on listWidget; Added: Linux installation section in user manuals (EN/KO/ZH/ES) covering AppImage chmod+run flow and dialout group note for serial/USB device access | |
| 1.18.33 | 2026-04-25 | 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.32 | 2026-04-23 | Fixed: same-port multi-client TCP - primary now runs a continuous accept loop and hands off each connection to the matching sub by IP filter (previously second connection got rejected/listened closed); Added: @IP and #keyword port filters are now mutually exclusive per bed (IP wins, keyword dropped with WARN); Added: WARN log when two beds on the same port share an identical IP filter; Added: catch-all primary keeps its single connection and rejects extras (preserves broadcast mode) | |
| 1.18.31 | 2026-04-22 | Added: drug slot shows DOSE_AMOUNT (mg etc) in preference to VOL (mL) when available; Added: DOSE_RATE / DOSE_AMOUNT montypes bound to BBraun tracks for monitor view; Added: HL7 MDC_DOSE_DRUG_DELIV_TOTAL unit picked up dynamically from UCUM | |
| 1.18.30 | 2026-04-21 | Changed: monitor view pump layout - 8 slots with CE or RATE (CE-less pumps show infused volume as small annotation); Reverted: BBraun minimal=1 (1.18.29) - was dropping data from file, monitor view redesign handles display clutter instead | |
| 1.18.29 | 2026-04-21 | Fixed: BBraun multi-rack auto-tab data drop, primary TCP churn, save_settings lock race, TCP reader spin on peer close; Added: BBraun minimal=1 option (display only drug name / rate / volume) | |
| 1.18.28 | 2026-04-20 | Fixed: vr.conf on Pi/Linux now looked up next to executable (supports /boot/firmware); deploy.py --store auto-submits MSIX to Microsoft Store | |
| 1.18.27 | 2026-04-18 | Fixed: vital file periodic flush (5000s→5s bug), Fit-to-Window icon redesigned to match navigation button style | |
| 1.18.26 | 2026-04-18 | Fixed: periodic .vital file flush interval was 5000 seconds instead of 5 (unit-bug since comment said seconds but value was milliseconds). Data now actually reaches disk every 5 s during recording instead of only on close. | |
| 1.18.25 | 2026-04-18 | Fixed: BBraun HL7 rec_timeout raised 10s→60s to prevent primary churn during DoseLink R42→R01 phase transition and long idle periods | |
| 1.18.24 | 2026-04-18 | Fixed: GUI silently exits when another recorder running (now falls back to viewer mode), .vital file association written to HKCU (no admin required, auto-updates exe path on every run) | |
| 1.18.23 | 2026-04-18 | Fixed: atof decimal-separator bug on non-English Windows locales (rates <1.0 recorded as 0 in Norwegian/German/French etc.), BBraun HL7 pump limit raised from 8 to 16 | |
| 1.18.22 | 2026-04-18 | Fixed: multi-bed TCP port bind race on restart (Mindray/BBraun HL7 auto-reconnect without manual Add device) | |
| 1.18.21 | 2026-04-18 | Fixed: port name filter (#keyword@ip) lost on save, BBraun HL7 VMD-block parsing with single dt, Fit-to-Window button sizing (MFC) and added to Qt | |
| 1.18.20 | 2026-04-15 | BBraun: comprehensive protocol parsing (dose rate, infusion time, syringe, weight, drug amounts, delivery status) | |
| 1.18.19 | 2026-04-13 | Fixed: BBraun HL7 multi-pump identification (rack serial filtering), multi-bed forwarding, port filter parsing | |
| 1.18.18 | 2026-04-10 | Added: GE alarm limits, S5 alarm/ext3/ext1 parsing | |
| 1.18.17 | 2026-04-04 | Changed: TRACE debug-only guard, show_msg for file errors, deploy sequential builds | |
| 1.18.16 | 2026-04-04 | Added: TCP port filtering (#keyword@ip), frame forwarding for multi-bed. Fixed: --debug conf file mistaken as vital file, device type ambiguity in forwarding | |
| 1.18.15 | 2026-04-04 | Changed: Port to Port Name, keyword filter AND/OR (#kw1 kw2#kw3), IP postfix matching (@10.1), MEKICS SerialFramed | |
| 1.18.14 | 2026-04-03 | Added: TCP port filtering (#keyword@ip), Fixed: devtypes not sent on boot | |
| 1.18.13 | 2026-04-02 | Changed: Covidien company name to Medtronic (BIS, INVOS) | |
| 1.18.12 | 2026-04-02 | Bug Fixed: NihonKohden EGA SpO2 waveform not displayed (channel name mapping) | |
| 1.18.11 | 2026-03-31 | Added: Mindray HL7 multi-bed separation via PV1, auto-tab creation for EGA/Mindray | |
| 1.18.10 | 2026-03-31 | Added: HL7 ZBR segment now includes ver, os, arch, dgmt | |
| 1.18.8 | 2026-03-25 | Added: --demo mode for console operation with demo signals | |
| 1.18.7 | 2026-03-25 | Added: --console mode for headless operation on Windows | |
| 1.18.6 | 2026-03-25 | Added: Mindray eGateway HL7 protocol (IHE PCD-01, HL7 v2.6 over MLLP, waveform + numeric + infusion pump) | |
| 1.18.4 | 2026-03-24 | Added: BBraun SpaceCom HL7 protocol (HL7 v2.6 over MLLP, TCP server mode) | |
| 1.18.3 | 2026-03-24 | Added: HL7 OBX-14 per-track dtstart, ZBR segment with dtcase/dtapp timestamps | |
| 1.18.2 | 2026-03-24 | Added: HL7 ZBR segment with dtcase (recording start) and dtapp (app start) timestamps | |
| 1.18.1 | 2026-03-23 | Fixed: HL7 OBX-3 format (code^device/track@srate), OBX-7 refrange (min^max), NA null sample gaps | |
| 1.17.6 | 2026-03-22 | Bug fixed: Resource ID conflicts WiX installer cleanup | |
| 1.17.5 | 2026-03-21 | Bug fixed: Vital packed file loading MFC device button icon padding | |
| 1.17.4 | 2026-03-19 | Bug fixed: NirsitON EEG 250Hz VRQt device minimize/rename | |
| 1.17.3 | 2026-03-18 | Bug fixed: BBraun protocol NirsitON protocol VRQt | |
| 1.17.2 | 2026-03-14 | Added: NirsitOn NDI protocol support. | |
| 1.17.1 | 2026-03-12 | Added: VRQt | |
| 1.16.13 | 2026-03-07 | Added: New Setup File with VRQt | |
| 1.16.12 | 2026-03-06 | Added: Vector Type Icons | |
| 1.16.10 | 2026-03-02 | Bug fixed: Autodetecting BBraun Stuffing feature. | |
| 1.16.8 | 2026-02-27 | Bug fixed: Masimo IAP protocol. | |
| 1.16.6 | 2026-02-13 | Added: Servo-u ventilator protocol | |
| 1.16.5 | 2026-02-09 | Bug Fixed: NihonKohden HL7GW Pleth waveform gain correction. | |
| 1.16.4 | 2026-01-20 | Bug Fixed: Serial communication. | |
| 1.16.3 | 2026-01-14 | Bug Fixed: Masimo IAP Protocol. | |
| 1.16.2 | 2026-01-03 | Added: BLE Support for Movesense devices on Raspberry Pi | |
| 1.16.1 | 2026-01-01 | Added: BLE Support for Movesense devices on Raspberry Pi and Linux versions. | |
| 1.16.0 | 2025-12-31 | Added: BLE Support for Movesense devices | |
| 1.15.16 | 2025-09-18 | Bug Fixed: Restoring last window position. | |
| 1.15.15 | 2025-09-05 | Added: CPU optimization Bug fixed: Masimo IAP protocol. | |
| 1.15.14 | 2025-09-04 | Added: CPU optimization | |
| 1.15.13 | 2025-09-04 | Bug Fixed: Masimo IAP Protocol. | |
| 1.15.12 | 2025-09-04 | Bug Fixed: Hardware handshake for Link+ connection. | |
| 1.15.11 | 2025-09-01 | Added: Recording alarm messages for Drager anesthesia machines | |
| 1.15.10 | 2025-07-14 | Bug Fixed: Sampling rate estimation in Medibus protocol. | |
| 1.15.9 | 2025-06-03 | Bug Fixed: EEG waveform labels from Philips monitors. | |
| 1.15.8 | 2025-05-26 | Added: EM_UPLOAD option to upload vital files to server immediately | |
| 1.15.7 | 2025-05-26 | Bug Fixed: Websocket connection error | |
| 1.15.6 | 2025-05-19 | Bug Fixed: S5 checksum error | |
| 1.15.5 | 2025-04-17 | Bug Fixed: saving the raw data | |
| 1.15.4 | 2025-04-15 | Added: Recording alarms from Philips monitor | |
| 1.15.3 | 2025-04-14 | Bug fixed: saving the raw data | |
| 1.15.2 | 2025-03-12 | Fixed: bug in opening file | |
| 1.15.1 | 2025-01-30 | Supporting for Fresenius Kabi Conox, Minor Bug fixes | |
| 1.14.13 | 2025-01-18 | Fixed bug in handling non-ascii filepath | |
| 1.14.12 | 2024-11-20 | Fixed: bug in 64bit ARM Architecture | |
| 1.14.11 | 2024-10-24 | Added: feature for updating to a specific version | |
| 1.14.10 | 2024-10-22 | Fixed: bug in json streamming | |
| 1.14.9 | 2024-10-17 | Added: montype for drug rates | |
| 1.14.6 | 2024-09-20 | Added: montype for drug rates | |
| 1.14.5 | 2024-09-13 | Added: auto cutting by any of hr or spo2 | |
| 1.14.4 | 2024-09-12 | Fixed: bug in serial port mapping | |
| 1.14.3 | 2024-09-10 | Added: temperature monitoring Fixed: bug in cpu usage calculation | |
| 1.14.2 | 2024-08-29 | Added: DI-245 protocol | |
| 1.14.1 | 2024-07-26 | Added: GE cardiac output parameters | |
| 1.14.0 | 2024-07-25 | Added: GE Canvas device | |
| 1.13.10 | 2024-07-17 | Fixed: NirsitOn protocol bug | |
| 1.13.9 | 2024-06-27 | Bug fix for file loading | |
| 1.13.8 | 2024-06-05 | Bug fix for file loading | |
| 1.13.7 | 2024-06-04 | Bug fix for file loading | |
| 1.13.6 | 2024-06-03 | Bug fix for the NirsitOn+ protocols with 32Hz | |
| 1.13.5 | 2024-05-30 | Added support for the NirsitOn+ protocols with 32Hz | |
| 1.13.4 | 2024-05-23 | Added fetal heart rate and uterine activity | |
| 1.13.3 | 2024-05-23 | Tested for BIS vista protocol | |
| 1.13.2 | 2024-04-26 | Support for BIS vista protocol | |
| 1.13.1 | 2024-04-26 | Support for BIS vista protocol | |
| 1.12.12 | 2024-04-09 | Deployment script updated | |
| 1.12.11 | 2024-04-06 | Deployment script updated | |
| 1.12.10 | 2024-02-25 | Deployment script updated | |
| 1.12.8.0 | 2024-02-25 | Added support for GE Coro fetal monitor | |
| 1.12.8 | 2024-02-25 | Added support for GE Coro fetal monitor | |
| 1.12.7.0 | 2024-02-24 | Added support for GE Coro fetal monitor | |
| 1.12.6.0 | 2024-01-05 | Minor bug fix | |
| 1.12.5.0 | 2023-12-22 | Minor bug fix | |
| 1.12.4.0 | 2023-12-22 | Minor bug fix Added support of Pleth(Left, Right) and Aortic pressure for the Intellivue protocol Added option for the cut by heart rate Added option for setting the waveform by its name - Intellivue: I, II, III, V, aVR, aVL, avF, MCL, MCL1, V1, V2, V3, V4, V5, V6, PLETH, ABP, ART, CVP, FAP, PAP, ICP, EEG, CO2, RESP, AWF, AWP - S5: ECG1, ECG2, EEGBIS, EEGENT, EEGENT400, IABP1, IABP2, IABP3, IABP4, IABP5, IABP6, IABP7, IABP8, EEG1, EEG2, PLETH, CO2, O2, N2O, AWP | |
| 1.12.1.0 | 2023-12-11 | Minor bug fixes Improvement of the Bx50 protocol Improvement of the NIHONKOHDEN protocol | |
| 1.12.0.0 | 2023-12-05 | Supported protocol for Drager B1x5M. | |
| 1.11.20.0 | 2023-11-07 | Supported protocol for Daiwha TCI pump. | |
| 1.11.19.0 | 2023-10-23 | Additional pleth waveforms for the Philips protocol Minor Bug fixes | |
| 1.11.18.0 | 2023-09-14 | Bug fix for EGA protocol Minor Bug fixes | |
| 1.11.17.0 | 2023-09-08 | Bug fix for EGA protocol | |
| 1.11.16.0 | 2023-08-30 | Bug fix for EGA protocol | |
| 1.11.15.0 | 2023-08-29 | Bug fix for EGA protocol | |
| 1.11.14.0 | 2023-08-29 | Bug fix for EGA protocol | |
| 1.11.13.0 | 2023-08-28 | Bug fix for EGA protocol | |
| 1.11.12.0 | 2023-08-08 | EGA protocol supported PulsioFlex protocol supported | |
| 1.11.11.0 | 2023-07-12 | Fixed bug for TOFCuff | |
| 1.11.10.0 | 2023-06-01 | Fixed bug for VitalConnect | |
| 1.11.9.0 | 2023-05-30 | Fixed bug for BUTTON | |
| 1.11.8.0 | 2023-05-27 | Fixed bug for MEKICS Fixed bug for remote update | |
| 1.11.7.0 | 2023-05-24 | Added support for NirsitOn+ | |
| 1.11.6.0 | 2023-05-23 | Added support for NirsitOn+ | |
| 1.11.5.0 | 2023-05-22 | Added support for NirsitOn+ | |
| 1.11.4.0 | 2023-05-22 | Added support for NirsitOn+ | |
| 1.11.3.0 | 2023-05-21 | Added support for SNUSensor | |
| 1.11.2.0 | 2023-05-16 | Added support for SNUSensor | |
| 1.11.1.0 | 2023-05-03 | Added support for TOFcuff | |
| 1.11.0.0 | 2023-03-29 | Added support for TOFcuff | |
| 1.10.21.0 | 2023-02-16 | Added support for TOFcuff | |
| 1.10.20.0 | 2023-02-05 | Added support for file cutting based on SpO2 | |
| 1.10.19.0 | 2023-01-12 | Added support for file cutting based on SpO2 | |
| 1.10.18.0 | 2022-12-16 | Added support for AU protocol | |
| 1.10.17.0 | 2022-12-16 | Added support for AU protocol | |
| 1.10.16.0 | 2022-12-14 | Added support for AU protocol | |
| 1.10.15.0 | 2022-12-12 | Added support for AU protocol | |
| 1.10.14.0 | 2022-12-08 | Added support for AU protocol | |
| 1.10.13.0 | 2022-12-07 | Added support for AU protocol | |
| 1.10.12.0 | 2022-12-05 | Added support for AU protocol | |
| 1.10.11.0 | 2022-12-05 | Added support for AU protocol | |
| 1.10.10.0 | 2022-12-02 | Added support for AU protocol | |
| 1.10.9.0 | 2022-12-01 | Fixed bug for Link+ protocol | |
| 1.10.8.0 | 2022-11-29 | Fixed bug for Servo-i Ventilator Fixed bug for Hamilton Ventilator | |
| 1.10.7.0 | 2022-11-08 | Improved support for Nihon kohden ADT | |
| 1.10.6.0 | 2022-11-04 | Improved support for Nihon kohden HL7GW | |
| 1.10.4.0 | 2022-11-02 | Hamiltonian ventilator supported | |
| 1.10.3.0 | 2022-10-21 | Hamiltonian ventilator supported | |
| 1.10.2.0 | 2022-10-14 | Hamiltonian ventilator supported | |
| 1.10.0.0 | 2022-09-15 | Minor bug fix Added input dialog for patient ID when intranet server is used | |
| 1.9.11.0 | 2022-07-29 | Minor bug fix Added input dialog for patient ID when intranet server is used | |
| 1.9.10.0 | 2022-06-26 | Minor bug fix Added input dialog for patient ID when intranet server is used | |
| 1.9.9.0 | 2022-06-03 | Minor bug fix Added input dialog for patient ID when intranet server is used | |
| 1.9.8.0 | 2022-05-17 | Minor bug fix VitalSync compatible with VitalDB Platform API | |
| 1.9.7.0 | 2022-03-25 | Display patient id on the title bar Support for barcode reader Minor bug fix | |
| 1.9.6.0 | 2022-03-25 | Additional Parameter (RPVI) for Masimo Root Bug fix for VitlaUtils | |
| 1.9.4.0 | 2022-03-10 | Batch running of filter on VitalUtils | |
| 1.9.3.0 | 2022-03-09 | Local filter server as default Python Installer | |
| 1.9.2.0 | 2022-02-17 | Added ABP montype to ClearSight | |
| 1.9.1.0 | 2022-02-16 | Supported MedibusX protocol | |
| 1.9.0.0 | 2022-02-14 | Supported LiDCO protocol | |
| 1.8.22.0 | 2022-02-10 | Added filter server running on localhost Moved VC setting dialog to the Setting dialog Fixed minor bugs | |
| 1.8.21.1 | 2022-02-07 | Fixed a bug with the Intellivue | |
| 1.8.21.0 | 2022-01-21 | Fixed a bug with the Intellivue | |
| 1.8.20.1 | 2022-01-15 | Fixed a bug with the Intellivue | |
| 1.8.20.0 | 2022-01-13 | Requesting O2 and N2O waves to the Bx50. Fixed a bug with the channel names of FIO2 and FIO2 of the Bx50. | |
| 1.8.19.5 | 2021-12-17 | Bug fix for Intellivue protorol | |
| 1.8.19.4 | 2021-12-09 | Bug fix for Intellivue protorol | |
| 1.8.19.0 | 2021-11-15 | Bug fix for Intellivue protorol | |
| 1.8.18.0 | 2021-10-09 | New Installer | |
| 1.8.17.2 | 2021-10-06 | New Installer |
| Version | Date | Changes | |
|---|---|---|---|
| 1.18.40 | 2026-05-12 | 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 | 2026-05-08 | 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 | 2026-04-25 | 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 | 2026-04-22 | 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 |
| Version | Date | Changes | |
|---|---|---|---|
| 1.15.10 | 2025-07-14 | Bug Fixed: Sampling rate estimation in Medibus protocol. | |
| 1.15.9 | 2025-06-03 | Bug Fixed: EEG waveform labels from Philips monitors. | |
| 1.15.8 | 2025-05-26 | Added: EM_UPLOAD option to upload vital files to server immediately | |
| 1.15.7 | 2025-05-26 | Bug Fixed: Websocket connection error | |
| 1.15.6 | 2025-05-19 | Bug Fixed: S5 checksum error | |
| 1.15.5 | 2025-04-17 | Bug Fixed: saving the raw data | |
| 1.15.4 | 2025-04-15 | Added: Recording alarms from Philips monitor | |
| 1.15.3 | 2025-04-14 | Bug fixed: saving the raw data | |
| 1.15.2 | 2025-03-12 | Fixed: bug in opening file | |
| 1.15.1 | 2025-01-30 | Supporting for Fresenius Kabi Conox, Minor Bug fixes | |
| 1.14.13 | 2025-01-18 | Fixed bug in handling non-ascii filepath | |
| 1.14.12 | 2024-11-20 | Fixed: bug in 64bit ARM Architecture | |
| 1.14.11 | 2024-10-24 | Added: feature for updating to a specific version | |
| 1.14.10 | 2024-10-22 | Fixed: bug in json streamming | |
| 1.14.9 | 2024-10-17 | Added: montype for drug rates | |
| 1.14.6 | 2024-09-20 | Added: montype for drug rates | |
| 1.14.5 | 2024-09-13 | Added: auto cutting by any of hr or spo2 | |
| 1.14.4 | 2024-09-12 | Fixed: bug in serial port mapping | |
| 1.14.3 | 2024-09-10 | Added: temperature monitoring Fixed: bug in cpu usage calculation | |
| 1.14.2 | 2024-08-29 | Added: DI-245 protocol | |
| 1.14.1 | 2024-07-26 | Added: GE cardiac output parameters | |
| 1.14.0 | 2024-07-25 | Added: GE Canvas device | |
| 1.13.10 | 2024-07-17 | Fixed: NirsitOn protocol bug | |
| 1.13.9 | 2024-06-27 | Bug fix for file loading | |
| 1.13.8 | 2024-06-05 | Bug fix for file loading | |
| 1.13.7 | 2024-06-04 | Bug fix for file loading | |
| 1.13.6 | 2024-06-03 | Bug fix for the NirsitOn+ protocols with 32Hz | |
| 1.13.5 | 2024-05-30 | Added support for the NirsitOn+ protocols with 32Hz | |
| 1.13.4 | 2024-05-23 | Added fetal heart rate and uterine activity | |
| 1.13.3 | 2024-05-23 | Tested for BIS vista protocol | |
| 1.13.2 | 2024-04-26 | Support for BIS vista protocol | |
| 1.13.1 | 2024-04-26 | Support for BIS vista protocol | |
| 1.12.12 | 2024-04-09 | Deployment script updated | |
| 1.12.11 | 2024-04-06 | Deployment script updated | |
| 1.12.10 | 2024-02-25 | Deployment script updated | |
| 1.12.8 | 2024-02-25 | Added support for GE Coro fetal monitor | |
| 1.12.7.0 | 2024-02-24 | Added support for GE Coro fetal monitor | |
| 1.12.6.0 | 2024-01-05 | Minor bug fix | |
| 1.12.5.0 | 2023-12-22 | Minor bug fix | |
| 1.12.4.0 | 2023-12-22 | Minor bug fix Added support of Pleth(Left, Right) and Aortic pressure for the Intellivue protocol Added option for the cut by heart rate Added option for setting the waveform by its name - Intellivue: I, II, III, V, aVR, aVL, avF, MCL, MCL1, V1, V2, V3, V4, V5, V6, PLETH, ABP, ART, CVP, FAP, PAP, ICP, EEG, CO2, RESP, AWF, AWP - S5: ECG1, ECG2, EEGBIS, EEGENT, EEGENT400, IABP1, IABP2, IABP3, IABP4, IABP5, IABP6, IABP7, IABP8, EEG1, EEG2, PLETH, CO2, O2, N2O, AWP | |
| 1.12.3.0 | 2023-12-12 | Minor bug fixes Improvement of the Bx50 protocol Improvement of the NIHONKOHDEN protocol | |
| 1.12.2.0 | 2023-12-11 | Minor bug fixes Improvement of the Bx50 protocol Improvement of the NIHONKOHDEN protocol | |
| 1.12.1.0 | 2023-12-11 | Minor bug fixes Improvement of the Bx50 protocol Improvement of the NIHONKOHDEN protocol | |
| 1.12.0.0 | 2023-12-05 | Supported protocol for Drager B1x5M. | |
| 1.11.20.0 | 2023-11-07 | Supported protocol for Daiwha TCI pump. | |
| 1.11.18.0 | 2023-09-14 | Bug fix for EGA protocol Minor Bug fixes | |
| 1.11.17.0 | 2023-09-08 | Bug fix for EGA protocol | |
| 1.11.11.0 | 2023-07-12 | Fixed bug for TOFCuff | |
| 1.11.10.0 | 2023-06-01 | Fixed bug for VitalConnect | |
| 1.11.9.0 | 2023-05-30 | Fixed bug for BUTTON | |
| 1.11.8.0 | 2023-05-27 | Fixed bug for MEKICS Fixed bug for remote update | |
| 1.11.7.0 | 2023-05-24 | Added support for NirsitOn+ | |
| 1.11.6.0 | 2023-05-23 | Added support for NirsitOn+ | |
| 1.11.4.0 | 2023-05-22 | Added support for NirsitOn+ | |
| 1.11.3.0 | 2023-05-21 | Added support for SNUSensor | |
| 1.11.1.0 | 2023-05-03 | Added support for TOFcuff | |
| 1.10.19.0 | 2023-01-12 | Added support for file cutting based on SpO2 | |
| 1.10.17.0 | 2022-12-16 | Added support for AU protocol | |
| 1.10.15.0 | 2022-12-12 | Added support for AU protocol | |
| 1.10.13.0 | 2022-12-07 | Added support for AU protocol | |
| 1.10.11.0 | 2022-12-05 | Added support for AU protocol | |
| 1.10.10.0 | 2022-12-02 | Added support for AU protocol | |
| 1.10.9.0 | 2022-12-01 | Fixed bug for Link+ protocol | |
| 1.10.8.0 | 2022-11-29 | Fixed bug for Servo-i Ventilator Fixed bug for Hamilton Ventilator | |
| 1.10.7.0 | 2022-11-08 | Improved support for Nihon kohden ADT | |
| 1.10.6.0 | 2022-11-04 | Improved support for Nihon kohden HL7GW | |
| 1.10.4.0 | 2022-11-02 | Hamiltonian ventilator supported | |
| 1.10.3.0 | 2022-10-21 | Hamiltonian ventilator supported | |
| 1.10.2.0 | 2022-10-14 | Hamiltonian ventilator supported | |
| 1.10.1.0 | 2022-10-07 | Minor bug fix Added input dialog for patient ID when intranet server is used | |
| 1.10.0.0 | 2022-09-15 | Minor bug fix Added input dialog for patient ID when intranet server is used | |
| 1.9.11.0 | 2022-07-29 | Minor bug fix Added input dialog for patient ID when intranet server is used | |
| 1.9.10.0 | 2022-06-26 | Minor bug fix Added input dialog for patient ID when intranet server is used | |
| 1.9.8.0 | 2022-05-17 | Minor bug fix VitalSync compatible with VitalDB Platform API | |
| 1.9.7.0 | 2022-03-25 | Display patient id on the title bar Support for barcode reader Minor bug fix | |
| 1.9.6.0 | 2022-03-25 | Additional Parameter (RPVI) for Masimo Root Bug fix for VitlaUtils | |
| 1.9.2.0 | 2022-02-17 | Added ABP montype to ClearSight | |
| 1.9.1.0 | 2022-02-16 | Supported MedibusX protocol | |
| 1.9.0.0 | 2022-02-14 | Supported LiDCO protocol | |
| 1.8.22.0 | 2022-02-10 | Added filter server running on localhost Moved VC setting dialog to the Setting dialog Fixed minor bugs | |
| 1.8.21.1 | 2022-02-07 | Fixed a bug with the Intellivue | |
| 1.8.21.0 | 2022-01-21 | Fixed a bug with the Intellivue | |
| 1.8.20.1 | 2022-01-15 | Fixed a bug with the Intellivue | |
| 1.8.20.0 | 2022-01-13 | Requesting O2 and N2O waves to the Bx50. Fixed a bug with the channel names of FIO2 and FIO2 of the Bx50. | |
| 1.8.19.6 | 2021-12-24 | ADT_SERVER_IP | |
| 1.8.19.5 | 2021-12-17 | Bug fix for Intellivue protorol |
| Version | Date | Changes | |
|---|---|---|---|
| 1.19.36 | 2026-10-13 | 1.19.36 Added: Windows 에서 아이콘으로 실행해도 로그가 설정 폴더의 vr.log 에 남는다. 지금까지는 로그가 화면에 출력되는 곳이 없으면 그대로 사라져서, 장비가 요청을 거절한 이유처럼 문제를 가리는 데 필요한 기록을 현장에서 볼 방법이 없었다. 줄마다 날짜와 시각이 붙고, 10MB 를 넘으면 시작할 때 vr.log.1 로 넘긴다. --debug 로 실행하거나 출력을 다른 파일로 돌린 경우는 종전대로 그쪽으로 나간다. Fixed: VitalRecorder Qt 에서 트랙 목록의 세로 스크롤바를 마우스로 움직일 수 없던 문제. 스크롤바가 그려지기만 하고 마우스 입력을 받지 않아 방향키로만 이동할 수 있었다. 이제 막대를 끌거나 빈 곳을 눌러 이동할 수 있다. Fixed: VitalRecorder Qt 에서 장비 편집 창이 검은 바탕에 검은 글씨로 나와 읽기 어렵던 문제. 장비 추가 창과 같은 밝은 모양으로 나온다. 창의 최소 폭도 넓혀 Intellivue 파형 선택 항목이 잘리지 않는다. Fixed: VitalRecorder Qt 에서 장비 설정을 바꾸면 오른쪽 장비 목록에 빈 칸이 남던 문제. 그 빈 칸의 편집 버튼을 누르면 같은 장비가 하나 더 생기기도 했다. (Nord University 제보) | |
| 1.19.35 | 2026-10-08 | 1.19.35 Fixed: Edwards 혈역학 모니터(HemoSphere 등)의 값이 Philips Intellivue 를 거쳐 들어올 때 연속 심박출량(CO_CTS), 연속 심장지수(CI_CTS), 일회박출량 변이(VOL_BLD_STROKE_VAR, SVV)가 웹 모니터링 화면에 나오지 않던 문제. 값은 정상으로 수집·저장되고 있었지만, 웹 화면이 어느 칸에 표시할지 정하는 분류(모니터 유형)가 지정되지 않아 표시 대상에서 빠졌다. 이제 각각 CO, CI, SVV 로 분류해 다른 장비의 같은 값과 같은 자리에 나온다. 이미 만들어진 녹화 파일에는 소급되지 않는다. | |
| 1.19.34 | 2026-10-06 | 1.19.34 Fixed: Nihon Kohden Vismo PVM-4700 시리즈(PVM-4761 등)와 구형 BSM-3000/6000, CSM-1500/1700 에서 시리얼로 아무 값도 들어오지 않던 문제. VR 은 확장 수치·심방세동까지 받으려고 ?D;EALL 요청의 Option 을 2 로 보내는데, 이 기종들은 규격상 Option 0~1 만 받아들여 매 요청을 !E;04(인자 미지원)로 거절했고, VR 은 그 거절을 읽지 않아 1초마다 같은 요청을 되풀이했다 (강남차병원 PVM-4761 실측 2026-09-07: 5분 동안 거절 응답만 300회). 이제 거절을 받으면 Option 을 한 단계씩 내려 다시 요청하고(2→1→0), 부정맥 비트 해석표도 그 Option 에 맞는 표(규격 Table 7/8/9)로 바꾼다. 어느 Option 으로 내려갔는지 로그에 남긴다. 측정하지 않은 파라미터에 대한 응답(!E;80/81, 예: NIBP 미측정)은 정상이므로 조용히 넘기고, 그 밖의 에러 응답은 60초에 한 번 로그에 남긴다. 가짜 모니터로 2→1 폴백과 HR/PR/SpO2/RR/체온 수집을 확인했다. 파형은 이 경로(RS-232C)로는 규격상 오지 않는다. PVM-4700 은 QI-470P 인터페이스의 RS-232C 커넥터와 YS-089P 계열 케이블을 쓴다 (0614-905457H). Added: 장비 목록에 Nihon Kohden : PVM 을 따로 두었다. BSM 과 같은 통신 규격이지만 PVM-4700 은 처음부터 지원하는 범위로 요청하므로 거절 응답 없이 바로 값이 들어온다. vr.conf 에는 type=PVM 으로 적는다. 기존에 BSM 으로 설정한 PVM 도 위의 자동 하향 덕분에 그대로 동작한다. | |
| 1.19.33 | 2026-10-05 | 1.19.33 Added: Philips Intellivue 가 VR 의 요청을 거절하면 로그에 남긴다. 어떤 요청(파형 폴, 파형 우선순위 목록, 수치 폴, 환자정보 등)이 어떤 이유(ACCESS_DENIED, SET_LIST_ERROR, INVALID_ARGUMENT_VALUE 등)로 거절됐는지 적는다. 종전에는 이 응답이 디버그 빌드에서만 보이는 흔적으로 처리돼 현장 로그에 아무것도 남지 않았고, 그래서 “수치는 들어오는데 파형만 없다” 는 상황에서 VR 이 요청을 안 한 것인지 모니터가 거부한 것인지를 로그만으로 가릴 수 없었다 (Nord University 실험실, 1.19.22~1.19.32 동일 증상). Added: 파형 폴을 보내는데 파형 묶음이 30초 넘게 돌아오지 않으면 5분에 한 번 경고를 남긴다. 요청한 파형 목록을 함께 적어 모니터 Data Export 설정과 바로 대조할 수 있다. 같은 코드의 거절은 60초에 한 번만 적는다. 1.19.32 Added: Philips Intellivue 의 파형 자동 추가를 끌 수 있다 (auto_wavs=0, 장비 설정 창의 체크박스). 종전에는 설정 창에서 고른 파형에 더해 수치가 들어오는 파형(ECG 한 리드, PLETH, ABP/ART/AoP, CVP, ICP, CO2, 기도압·유량)을 자동으로 보탰고 이것을 막을 방법이 없었다. 침습압이 여럿인 실험실 구성에서는 그 합이 시리얼 회선이 감당할 양을 넘고, 그러면 모니터는 파형을 일부만 줄이는 것이 아니라 통째로 멈춘다 — 수치는 계속 들어오는데 파형만 몇 초 만에 사라지는 모습이 된다 (Nord University 실측: 파형 8종이 5초 뒤 멎음, 설정 창에 적힌 한계 “ECG 3개 또는 비ECG 8개” 를 ECG 2개+6개로 넘긴 경우). 이제 체크를 끄면 고른 것만 요청한다. 기본값은 종전과 같이 자동 추가다. Added: Intellivue 에 요청하는 파형 목록이 바뀔 때마다 로그에 남긴다. 파형이 안 들어올 때 요청을 안 한 것인지, 요청했는데 모니터가 안 주는 것인지를 로그만으로 가를 수 있다. 1.19.31 Fixed: Windows 에서 시리얼 장비의 값이 두 번에 한 번씩 약 0.23초 늦게 기록되던 문제. 1초에 한 번 보내는 SyncPulseSerial(동기 펄스 생성기)에서 펄스 간격이 1초가 아니라 0.77초와 1.23초로 번갈아 기록됐다 (Nord University AniLab 실측 9/5·9/10, 두 펄스를 합하면 정확히 2.000초). 장비 쪽은 정확했다 — 같은 회선을 시리얼 터미널로 받으면 ±1ms 로 1초마다 들어온다. 원인은 VR 이 문자 도착을 기다리기 전에 매번 100ms 를 쉬었고, 그 100ms 안에 들어온 데이터는 Windows 가 알려주지 않아 200ms 뒤 확인 때에야 읽혔다는 것이다. 이 대기 한 바퀴(약 310ms)와 1초 주기가 맞물려 한 프레임은 즉시, 다음 프레임은 230ms 늦게 읽는 패턴이 고정됐다. 이제 기다리기 전에 수신 버퍼를 먼저 확인하고 쉬지 않는다. 도착 즉시 읽으므로 기록 시각의 오차는 USB 폴링 수준(수 ms)만 남는다. 초당 수십 바이트 이상 보내는 일반 환자 모니터에서는 이 지연이 파형 시각에 묻혀 드러나지 않았다. Note: 1.19.25 설명 정정 — Windows 수신 대기가 원인이 아니라고 적었으나(가상 직렬 포트 A/B 에서 재현되지 않았다), 실제 USB 시리얼에서는 위와 같이 재현된다. 가상 포트는 문자 도착 알림 방식이 달라 시험이 빗나갔다. 1.19.30 Fixed: 기기 시각이 크게 어긋났을 때 저절로 맞춰지지 않고 그대로 유지되던 문제. 원주세브란스 EICU09 에서 실제보다 55분 늦은 상태가 계속됐고, 그 결과 해당 병상이 화면에서 자료 없음으로 보였다 (웹 화면은 마지막으로 자료가 들어온 지 1분이 넘은 병상을 끊긴 것으로 본다). 서버가 알려주는 시각으로 맞추는 기능 자체는 동작하고 있었다. 다만 녹화 중에는 파형에 공백이 생기지 않도록 시계를 한 번에 옮기지 않고 아주 천천히 당기도록 돼 있었는데, 그 속도가 하루에 약 43초라 55분을 되돌리려면 두 달이 넘게 걸린다. 천천히 당기는 방식은 이미 가까운 시계를 가깝게 유지하는 수단이지 벌어진 것을 따라잡는 수단이 아니라는 점이 코드에 반영돼 있지 않았다. 이제 두 경우에는 녹화 중이라도 한 번에 맞춘다 — 차이가 1분을 넘어 천천히 당겨서는 따라잡을 수 없을 때, 그리고 SAVE_FILE=0 으로 파일을 만들지 않아 지켜야 할 기록이 애초에 없을 때. 어느 쪽이든 맞춘 이유를 로그에 남긴다. ⚠ 시계를 따로 갖고 있지 않은 장비(라즈베리파이)는 전원이 꺼지면 시각을 잃는다. 기관의 시각 서버(NTP)를 함께 물려 두면 이 기능에 기대지 않아도 된다. 1.19.29 Added: 녹화 파일을 만들지 않고 웹 모니터링만 하는 운용 방식. vr.conf 에 SAVE_FILE=0 을 넣으면 .vital 파일을 만들지 않는다. 무선 대역폭이 부족해 파일을 남기지 않기로 한 기관을 위한 것이다. 종전에는 저장 폴더를 없는 경로로 지정해 ‘열기 실패’ 로 같은 효과를 내는 편법이 쓰였는데, 그 방법은 파일을 만들 때마다 오류가 로그에 쌓이고 화면에는 녹화 중으로 보였다. ⚠ 이 방식에서는 네트워크가 끊긴 구간의 자료가 영구히 사라진다 — 지금까지는 파일이 전송 실패의 완충재 역할을 했다. Changed: 웹 모니터링 전송량이 25~35% 줄었다. 파형 값을 늘 소수점 셋째 자리까지 보내고 있었는데, 화면은 그 트랙의 표시 범위 안에서 그리므로 범위의 1% 보다 고운 눈금은 어차피 구분되지 않는다 (심전도는 표시 범위가 4 mV 인데 0.001 단위로 보내 화면이 나타낼 수 있는 것의 40배를 보내고 있었다). 게다가 그 여분의 자릿수는 규칙성이 없어 압축도 되지 않아, 전송량의 4분의 1 이상을 차지했다 (중환자실 3개 병상 실측 26~35%). 이제 표시 범위에 맞춰 자릿수를 정한다. 값의 단위와 의미는 그대로이고 녹화 파일에는 원래 정밀도로 저장되므로 화면과 파일 모두 달라지는 것이 없으며, 서버나 웹 화면을 함께 고칠 필요도 없다. 예전 방식이 필요하면 WEB_WAV_PREC=3 으로 고정할 수 있다. Fixed: 녹화 파일을 만들지 못했을 때 아무 알림 없이 지나가던 문제. 디스크가 가득 찼거나 저장 폴더에 쓸 수 없으면 파일이 만들어지지 않는데, 화면에는 ‘녹화 중’ 으로 표시되고 자료는 조용히 버려졌다. 이제 로그에 경고를 남긴다. 1.19.28 Fixed: USB 로 연결한 장비의 수집이 어느 순간 조용히 멎고, 프로그램을 다시 켜야만 돌아오던 문제. 전원이 잠깐 흔들리면 USB 장비가 스스로 연결을 다시 맺는데 그때 운영체제가 새 포트 번호를 붙인다 (예: ttyACM0 이 ttyACM1 로). VR 은 처음 열었던 번호를 계속 붙들고 있어서, 장비도 케이블도 멀쩡한데 값만 들어오지 않는다 — 화면상 연결 상태도 정상으로 보여 눈으로는 알 수 없었다 (수술방의 Link+ 주입펌프에서 확인). Added: 포트 자리에 포트 번호 대신 장비의 고유 신원을 적을 수 있다. 형식은 20d0:0002 처럼 네 자리 두 개이며, 이 값은 장비에 박혀 있어 다시 연결되어도 바뀌지 않는다. 이렇게 적어 두면 VR 이 포트를 열 때마다 그 신원을 가진 자리를 다시 찾으므로, 번호가 바뀌어도 저절로 따라간다. 기존처럼 포트 번호를 적어 둔 곳은 그대로 동작한다. Changed: Link+ 는 포트를 적지 않으면 이 신원을 기본값으로 쓴다. 꽂기만 하면 잡히고, 다시 연결되어도 따라간다. 포트를 직접 적어 둔 곳은 적은 값이 우선이다. Changed: 자동 판별이 USB 신원을 먼저 본다. 고유 신원으로 자기를 밝히는 장비는 통신 속도·형식을 재 볼 필요가 없으므로 조용한 회선에서 72초가 걸리던 측정을 건너뛰고 바로 확정한다. 이렇게 확정한 장비는 포트를 번호가 아니라 신원으로 기록한다. 여러 장비가 함께 쓰는 범용 USB 변환 케이블(FTDI 등)은 신원이 케이블의 것이지 장비의 것이 아니므로 종전대로 측정해서 판별한다. 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.32 | 2026-10-05 | 1.19.32 Added: Philips Intellivue 의 파형 자동 추가를 끌 수 있다 (auto_wavs=0, 장비 설정 창의 체크박스). 종전에는 설정 창에서 고른 파형에 더해 수치가 들어오는 파형(ECG 한 리드, PLETH, ABP/ART/AoP, CVP, ICP, CO2, 기도압·유량)을 자동으로 보탰고 이것을 막을 방법이 없었다. 침습압이 여럿인 실험실 구성에서는 그 합이 시리얼 회선이 감당할 양을 넘고, 그러면 모니터는 파형을 일부만 줄이는 것이 아니라 통째로 멈춘다 — 수치는 계속 들어오는데 파형만 몇 초 만에 사라지는 모습이 된다 (Nord University 실측: 파형 8종이 5초 뒤 멎음, 설정 창에 적힌 한계 “ECG 3개 또는 비ECG 8개” 를 ECG 2개+6개로 넘긴 경우). 이제 체크를 끄면 고른 것만 요청한다. 기본값은 종전과 같이 자동 추가다. Added: Intellivue 에 요청하는 파형 목록이 바뀔 때마다 로그에 남긴다. 파형이 안 들어올 때 요청을 안 한 것인지, 요청했는데 모니터가 안 주는 것인지를 로그만으로 가를 수 있다. 1.19.31 Fixed: Windows 에서 시리얼 장비의 값이 두 번에 한 번씩 약 0.23초 늦게 기록되던 문제. 1초에 한 번 보내는 SyncPulseSerial(동기 펄스 생성기)에서 펄스 간격이 1초가 아니라 0.77초와 1.23초로 번갈아 기록됐다 (Nord University AniLab 실측 9/5·9/10, 두 펄스를 합하면 정확히 2.000초). 장비 쪽은 정확했다 — 같은 회선을 시리얼 터미널로 받으면 ±1ms 로 1초마다 들어온다. 원인은 VR 이 문자 도착을 기다리기 전에 매번 100ms 를 쉬었고, 그 100ms 안에 들어온 데이터는 Windows 가 알려주지 않아 200ms 뒤 확인 때에야 읽혔다는 것이다. 이 대기 한 바퀴(약 310ms)와 1초 주기가 맞물려 한 프레임은 즉시, 다음 프레임은 230ms 늦게 읽는 패턴이 고정됐다. 이제 기다리기 전에 수신 버퍼를 먼저 확인하고 쉬지 않는다. 도착 즉시 읽으므로 기록 시각의 오차는 USB 폴링 수준(수 ms)만 남는다. 초당 수십 바이트 이상 보내는 일반 환자 모니터에서는 이 지연이 파형 시각에 묻혀 드러나지 않았다. Note: 1.19.25 설명 정정 — Windows 수신 대기가 원인이 아니라고 적었으나(가상 직렬 포트 A/B 에서 재현되지 않았다), 실제 USB 시리얼에서는 위와 같이 재현된다. 가상 포트는 문자 도착 알림 방식이 달라 시험이 빗나갔다. 1.19.30 Fixed: 기기 시각이 크게 어긋났을 때 저절로 맞춰지지 않고 그대로 유지되던 문제. 원주세브란스 EICU09 에서 실제보다 55분 늦은 상태가 계속됐고, 그 결과 해당 병상이 화면에서 자료 없음으로 보였다 (웹 화면은 마지막으로 자료가 들어온 지 1분이 넘은 병상을 끊긴 것으로 본다). 서버가 알려주는 시각으로 맞추는 기능 자체는 동작하고 있었다. 다만 녹화 중에는 파형에 공백이 생기지 않도록 시계를 한 번에 옮기지 않고 아주 천천히 당기도록 돼 있었는데, 그 속도가 하루에 약 43초라 55분을 되돌리려면 두 달이 넘게 걸린다. 천천히 당기는 방식은 이미 가까운 시계를 가깝게 유지하는 수단이지 벌어진 것을 따라잡는 수단이 아니라는 점이 코드에 반영돼 있지 않았다. 이제 두 경우에는 녹화 중이라도 한 번에 맞춘다 — 차이가 1분을 넘어 천천히 당겨서는 따라잡을 수 없을 때, 그리고 SAVE_FILE=0 으로 파일을 만들지 않아 지켜야 할 기록이 애초에 없을 때. 어느 쪽이든 맞춘 이유를 로그에 남긴다. ⚠ 시계를 따로 갖고 있지 않은 장비(라즈베리파이)는 전원이 꺼지면 시각을 잃는다. 기관의 시각 서버(NTP)를 함께 물려 두면 이 기능에 기대지 않아도 된다. 1.19.29 Added: 녹화 파일을 만들지 않고 웹 모니터링만 하는 운용 방식. vr.conf 에 SAVE_FILE=0 을 넣으면 .vital 파일을 만들지 않는다. 무선 대역폭이 부족해 파일을 남기지 않기로 한 기관을 위한 것이다. 종전에는 저장 폴더를 없는 경로로 지정해 ‘열기 실패’ 로 같은 효과를 내는 편법이 쓰였는데, 그 방법은 파일을 만들 때마다 오류가 로그에 쌓이고 화면에는 녹화 중으로 보였다. ⚠ 이 방식에서는 네트워크가 끊긴 구간의 자료가 영구히 사라진다 — 지금까지는 파일이 전송 실패의 완충재 역할을 했다. Changed: 웹 모니터링 전송량이 25~35% 줄었다. 파형 값을 늘 소수점 셋째 자리까지 보내고 있었는데, 화면은 그 트랙의 표시 범위 안에서 그리므로 범위의 1% 보다 고운 눈금은 어차피 구분되지 않는다 (심전도는 표시 범위가 4 mV 인데 0.001 단위로 보내 화면이 나타낼 수 있는 것의 40배를 보내고 있었다). 게다가 그 여분의 자릿수는 규칙성이 없어 압축도 되지 않아, 전송량의 4분의 1 이상을 차지했다 (중환자실 3개 병상 실측 26~35%). 이제 표시 범위에 맞춰 자릿수를 정한다. 값의 단위와 의미는 그대로이고 녹화 파일에는 원래 정밀도로 저장되므로 화면과 파일 모두 달라지는 것이 없으며, 서버나 웹 화면을 함께 고칠 필요도 없다. 예전 방식이 필요하면 WEB_WAV_PREC=3 으로 고정할 수 있다. Fixed: 녹화 파일을 만들지 못했을 때 아무 알림 없이 지나가던 문제. 디스크가 가득 찼거나 저장 폴더에 쓸 수 없으면 파일이 만들어지지 않는데, 화면에는 ‘녹화 중’ 으로 표시되고 자료는 조용히 버려졌다. 이제 로그에 경고를 남긴다. 1.19.28 Fixed: USB 로 연결한 장비의 수집이 어느 순간 조용히 멎고, 프로그램을 다시 켜야만 돌아오던 문제. 전원이 잠깐 흔들리면 USB 장비가 스스로 연결을 다시 맺는데 그때 운영체제가 새 포트 번호를 붙인다 (예: ttyACM0 이 ttyACM1 로). VR 은 처음 열었던 번호를 계속 붙들고 있어서, 장비도 케이블도 멀쩡한데 값만 들어오지 않는다 — 화면상 연결 상태도 정상으로 보여 눈으로는 알 수 없었다 (수술방의 Link+ 주입펌프에서 확인). Added: 포트 자리에 포트 번호 대신 장비의 고유 신원을 적을 수 있다. 형식은 20d0:0002 처럼 네 자리 두 개이며, 이 값은 장비에 박혀 있어 다시 연결되어도 바뀌지 않는다. 이렇게 적어 두면 VR 이 포트를 열 때마다 그 신원을 가진 자리를 다시 찾으므로, 번호가 바뀌어도 저절로 따라간다. 기존처럼 포트 번호를 적어 둔 곳은 그대로 동작한다. Changed: Link+ 는 포트를 적지 않으면 이 신원을 기본값으로 쓴다. 꽂기만 하면 잡히고, 다시 연결되어도 따라간다. 포트를 직접 적어 둔 곳은 적은 값이 우선이다. Changed: 자동 판별이 USB 신원을 먼저 본다. 고유 신원으로 자기를 밝히는 장비는 통신 속도·형식을 재 볼 필요가 없으므로 조용한 회선에서 72초가 걸리던 측정을 건너뛰고 바로 확정한다. 이렇게 확정한 장비는 포트를 번호가 아니라 신원으로 기록한다. 여러 장비가 함께 쓰는 범용 USB 변환 케이블(FTDI 등)은 신원이 케이블의 것이지 장비의 것이 아니므로 종전대로 측정해서 판별한다. 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.31 | 2026-09-27 | 1.19.31 Fixed: Windows 에서 시리얼 장비의 값이 두 번에 한 번씩 약 0.23초 늦게 기록되던 문제. 1초에 한 번 보내는 SyncPulseSerial(동기 펄스 생성기)에서 펄스 간격이 1초가 아니라 0.77초와 1.23초로 번갈아 기록됐다 (Nord University AniLab 실측 9/5·9/10, 두 펄스를 합하면 정확히 2.000초). 장비 쪽은 정확했다 — 같은 회선을 시리얼 터미널로 받으면 ±1ms 로 1초마다 들어온다. 원인은 VR 이 문자 도착을 기다리기 전에 매번 100ms 를 쉬었고, 그 100ms 안에 들어온 데이터는 Windows 가 알려주지 않아 200ms 뒤 확인 때에야 읽혔다는 것이다. 이 대기 한 바퀴(약 310ms)와 1초 주기가 맞물려 한 프레임은 즉시, 다음 프레임은 230ms 늦게 읽는 패턴이 고정됐다. 이제 기다리기 전에 수신 버퍼를 먼저 확인하고 쉬지 않는다. 도착 즉시 읽으므로 기록 시각의 오차는 USB 폴링 수준(수 ms)만 남는다. 초당 수십 바이트 이상 보내는 일반 환자 모니터에서는 이 지연이 파형 시각에 묻혀 드러나지 않았다. Note: 1.19.25 설명 정정 — Windows 수신 대기가 원인이 아니라고 적었으나(가상 직렬 포트 A/B 에서 재현되지 않았다), 실제 USB 시리얼에서는 위와 같이 재현된다. 가상 포트는 문자 도착 알림 방식이 달라 시험이 빗나갔다. 1.19.30 Fixed: 기기 시각이 크게 어긋났을 때 저절로 맞춰지지 않고 그대로 유지되던 문제. 원주세브란스 EICU09 에서 실제보다 55분 늦은 상태가 계속됐고, 그 결과 해당 병상이 화면에서 자료 없음으로 보였다 (웹 화면은 마지막으로 자료가 들어온 지 1분이 넘은 병상을 끊긴 것으로 본다). 서버가 알려주는 시각으로 맞추는 기능 자체는 동작하고 있었다. 다만 녹화 중에는 파형에 공백이 생기지 않도록 시계를 한 번에 옮기지 않고 아주 천천히 당기도록 돼 있었는데, 그 속도가 하루에 약 43초라 55분을 되돌리려면 두 달이 넘게 걸린다. 천천히 당기는 방식은 이미 가까운 시계를 가깝게 유지하는 수단이지 벌어진 것을 따라잡는 수단이 아니라는 점이 코드에 반영돼 있지 않았다. 이제 두 경우에는 녹화 중이라도 한 번에 맞춘다 — 차이가 1분을 넘어 천천히 당겨서는 따라잡을 수 없을 때, 그리고 SAVE_FILE=0 으로 파일을 만들지 않아 지켜야 할 기록이 애초에 없을 때. 어느 쪽이든 맞춘 이유를 로그에 남긴다. ⚠ 시계를 따로 갖고 있지 않은 장비(라즈베리파이)는 전원이 꺼지면 시각을 잃는다. 기관의 시각 서버(NTP)를 함께 물려 두면 이 기능에 기대지 않아도 된다. 1.19.29 Added: 녹화 파일을 만들지 않고 웹 모니터링만 하는 운용 방식. vr.conf 에 SAVE_FILE=0 을 넣으면 .vital 파일을 만들지 않는다. 무선 대역폭이 부족해 파일을 남기지 않기로 한 기관을 위한 것이다. 종전에는 저장 폴더를 없는 경로로 지정해 ‘열기 실패’ 로 같은 효과를 내는 편법이 쓰였는데, 그 방법은 파일을 만들 때마다 오류가 로그에 쌓이고 화면에는 녹화 중으로 보였다. ⚠ 이 방식에서는 네트워크가 끊긴 구간의 자료가 영구히 사라진다 — 지금까지는 파일이 전송 실패의 완충재 역할을 했다. Changed: 웹 모니터링 전송량이 25~35% 줄었다. 파형 값을 늘 소수점 셋째 자리까지 보내고 있었는데, 화면은 그 트랙의 표시 범위 안에서 그리므로 범위의 1% 보다 고운 눈금은 어차피 구분되지 않는다 (심전도는 표시 범위가 4 mV 인데 0.001 단위로 보내 화면이 나타낼 수 있는 것의 40배를 보내고 있었다). 게다가 그 여분의 자릿수는 규칙성이 없어 압축도 되지 않아, 전송량의 4분의 1 이상을 차지했다 (중환자실 3개 병상 실측 26~35%). 이제 표시 범위에 맞춰 자릿수를 정한다. 값의 단위와 의미는 그대로이고 녹화 파일에는 원래 정밀도로 저장되므로 화면과 파일 모두 달라지는 것이 없으며, 서버나 웹 화면을 함께 고칠 필요도 없다. 예전 방식이 필요하면 WEB_WAV_PREC=3 으로 고정할 수 있다. Fixed: 녹화 파일을 만들지 못했을 때 아무 알림 없이 지나가던 문제. 디스크가 가득 찼거나 저장 폴더에 쓸 수 없으면 파일이 만들어지지 않는데, 화면에는 ‘녹화 중’ 으로 표시되고 자료는 조용히 버려졌다. 이제 로그에 경고를 남긴다. 1.19.28 Fixed: USB 로 연결한 장비의 수집이 어느 순간 조용히 멎고, 프로그램을 다시 켜야만 돌아오던 문제. 전원이 잠깐 흔들리면 USB 장비가 스스로 연결을 다시 맺는데 그때 운영체제가 새 포트 번호를 붙인다 (예: ttyACM0 이 ttyACM1 로). VR 은 처음 열었던 번호를 계속 붙들고 있어서, 장비도 케이블도 멀쩡한데 값만 들어오지 않는다 — 화면상 연결 상태도 정상으로 보여 눈으로는 알 수 없었다 (수술방의 Link+ 주입펌프에서 확인). Added: 포트 자리에 포트 번호 대신 장비의 고유 신원을 적을 수 있다. 형식은 20d0:0002 처럼 네 자리 두 개이며, 이 값은 장비에 박혀 있어 다시 연결되어도 바뀌지 않는다. 이렇게 적어 두면 VR 이 포트를 열 때마다 그 신원을 가진 자리를 다시 찾으므로, 번호가 바뀌어도 저절로 따라간다. 기존처럼 포트 번호를 적어 둔 곳은 그대로 동작한다. Changed: Link+ 는 포트를 적지 않으면 이 신원을 기본값으로 쓴다. 꽂기만 하면 잡히고, 다시 연결되어도 따라간다. 포트를 직접 적어 둔 곳은 적은 값이 우선이다. Changed: 자동 판별이 USB 신원을 먼저 본다. 고유 신원으로 자기를 밝히는 장비는 통신 속도·형식을 재 볼 필요가 없으므로 조용한 회선에서 72초가 걸리던 측정을 건너뛰고 바로 확정한다. 이렇게 확정한 장비는 포트를 번호가 아니라 신원으로 기록한다. 여러 장비가 함께 쓰는 범용 USB 변환 케이블(FTDI 등)은 신원이 케이블의 것이지 장비의 것이 아니므로 종전대로 측정해서 판별한다. 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.30 | 2026-09-16 | 1.19.30 Fixed: 기기 시각이 크게 어긋났을 때 저절로 맞춰지지 않고 그대로 유지되던 문제. 원주세브란스 EICU09 에서 실제보다 55분 늦은 상태가 계속됐고, 그 결과 해당 병상이 화면에서 자료 없음으로 보였다 (웹 화면은 마지막으로 자료가 들어온 지 1분이 넘은 병상을 끊긴 것으로 본다). 서버가 알려주는 시각으로 맞추는 기능 자체는 동작하고 있었다. 다만 녹화 중에는 파형에 공백이 생기지 않도록 시계를 한 번에 옮기지 않고 아주 천천히 당기도록 돼 있었는데, 그 속도가 하루에 약 43초라 55분을 되돌리려면 두 달이 넘게 걸린다. 천천히 당기는 방식은 이미 가까운 시계를 가깝게 유지하는 수단이지 벌어진 것을 따라잡는 수단이 아니라는 점이 코드에 반영돼 있지 않았다. 이제 두 경우에는 녹화 중이라도 한 번에 맞춘다 — 차이가 1분을 넘어 천천히 당겨서는 따라잡을 수 없을 때, 그리고 SAVE_FILE=0 으로 파일을 만들지 않아 지켜야 할 기록이 애초에 없을 때. 어느 쪽이든 맞춘 이유를 로그에 남긴다. ⚠ 시계를 따로 갖고 있지 않은 장비(라즈베리파이)는 전원이 꺼지면 시각을 잃는다. 기관의 시각 서버(NTP)를 함께 물려 두면 이 기능에 기대지 않아도 된다. 1.19.29 Added: 녹화 파일을 만들지 않고 웹 모니터링만 하는 운용 방식. vr.conf 에 SAVE_FILE=0 을 넣으면 .vital 파일을 만들지 않는다. 무선 대역폭이 부족해 파일을 남기지 않기로 한 기관을 위한 것이다. 종전에는 저장 폴더를 없는 경로로 지정해 ‘열기 실패’ 로 같은 효과를 내는 편법이 쓰였는데, 그 방법은 파일을 만들 때마다 오류가 로그에 쌓이고 화면에는 녹화 중으로 보였다. ⚠ 이 방식에서는 네트워크가 끊긴 구간의 자료가 영구히 사라진다 — 지금까지는 파일이 전송 실패의 완충재 역할을 했다. Changed: 웹 모니터링 전송량이 25~35% 줄었다. 파형 값을 늘 소수점 셋째 자리까지 보내고 있었는데, 화면은 그 트랙의 표시 범위 안에서 그리므로 범위의 1% 보다 고운 눈금은 어차피 구분되지 않는다 (심전도는 표시 범위가 4 mV 인데 0.001 단위로 보내 화면이 나타낼 수 있는 것의 40배를 보내고 있었다). 게다가 그 여분의 자릿수는 규칙성이 없어 압축도 되지 않아, 전송량의 4분의 1 이상을 차지했다 (중환자실 3개 병상 실측 26~35%). 이제 표시 범위에 맞춰 자릿수를 정한다. 값의 단위와 의미는 그대로이고 녹화 파일에는 원래 정밀도로 저장되므로 화면과 파일 모두 달라지는 것이 없으며, 서버나 웹 화면을 함께 고칠 필요도 없다. 예전 방식이 필요하면 WEB_WAV_PREC=3 으로 고정할 수 있다. Fixed: 녹화 파일을 만들지 못했을 때 아무 알림 없이 지나가던 문제. 디스크가 가득 찼거나 저장 폴더에 쓸 수 없으면 파일이 만들어지지 않는데, 화면에는 ‘녹화 중’ 으로 표시되고 자료는 조용히 버려졌다. 이제 로그에 경고를 남긴다. 1.19.28 Fixed: USB 로 연결한 장비의 수집이 어느 순간 조용히 멎고, 프로그램을 다시 켜야만 돌아오던 문제. 전원이 잠깐 흔들리면 USB 장비가 스스로 연결을 다시 맺는데 그때 운영체제가 새 포트 번호를 붙인다 (예: ttyACM0 이 ttyACM1 로). VR 은 처음 열었던 번호를 계속 붙들고 있어서, 장비도 케이블도 멀쩡한데 값만 들어오지 않는다 — 화면상 연결 상태도 정상으로 보여 눈으로는 알 수 없었다 (수술방의 Link+ 주입펌프에서 확인). Added: 포트 자리에 포트 번호 대신 장비의 고유 신원을 적을 수 있다. 형식은 20d0:0002 처럼 네 자리 두 개이며, 이 값은 장비에 박혀 있어 다시 연결되어도 바뀌지 않는다. 이렇게 적어 두면 VR 이 포트를 열 때마다 그 신원을 가진 자리를 다시 찾으므로, 번호가 바뀌어도 저절로 따라간다. 기존처럼 포트 번호를 적어 둔 곳은 그대로 동작한다. Changed: Link+ 는 포트를 적지 않으면 이 신원을 기본값으로 쓴다. 꽂기만 하면 잡히고, 다시 연결되어도 따라간다. 포트를 직접 적어 둔 곳은 적은 값이 우선이다. Changed: 자동 판별이 USB 신원을 먼저 본다. 고유 신원으로 자기를 밝히는 장비는 통신 속도·형식을 재 볼 필요가 없으므로 조용한 회선에서 72초가 걸리던 측정을 건너뛰고 바로 확정한다. 이렇게 확정한 장비는 포트를 번호가 아니라 신원으로 기록한다. 여러 장비가 함께 쓰는 범용 USB 변환 케이블(FTDI 등)은 신원이 케이블의 것이지 장비의 것이 아니므로 종전대로 측정해서 판별한다. 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.29 | 2026-09-15 | 1.19.29 Added: 녹화 파일을 만들지 않고 웹 모니터링만 하는 운용 방식. vr.conf 에 SAVE_FILE=0 을 넣으면 .vital 파일을 만들지 않는다. 무선 대역폭이 부족해 파일을 남기지 않기로 한 기관을 위한 것이다. 종전에는 저장 폴더를 없는 경로로 지정해 ‘열기 실패’ 로 같은 효과를 내는 편법이 쓰였는데, 그 방법은 파일을 만들 때마다 오류가 로그에 쌓이고 화면에는 녹화 중으로 보였다. ⚠ 이 방식에서는 네트워크가 끊긴 구간의 자료가 영구히 사라진다 — 지금까지는 파일이 전송 실패의 완충재 역할을 했다. Changed: 웹 모니터링 전송량이 25~35% 줄었다. 파형 값을 늘 소수점 셋째 자리까지 보내고 있었는데, 화면은 그 트랙의 표시 범위 안에서 그리므로 범위의 1% 보다 고운 눈금은 어차피 구분되지 않는다 (심전도는 표시 범위가 4 mV 인데 0.001 단위로 보내 화면이 나타낼 수 있는 것의 40배를 보내고 있었다). 게다가 그 여분의 자릿수는 규칙성이 없어 압축도 되지 않아, 전송량의 4분의 1 이상을 차지했다 (중환자실 3개 병상 실측 26~35%). 이제 표시 범위에 맞춰 자릿수를 정한다. 값의 단위와 의미는 그대로이고 녹화 파일에는 원래 정밀도로 저장되므로 화면과 파일 모두 달라지는 것이 없으며, 서버나 웹 화면을 함께 고칠 필요도 없다. 예전 방식이 필요하면 WEB_WAV_PREC=3 으로 고정할 수 있다. Fixed: 녹화 파일을 만들지 못했을 때 아무 알림 없이 지나가던 문제. 디스크가 가득 찼거나 저장 폴더에 쓸 수 없으면 파일이 만들어지지 않는데, 화면에는 ‘녹화 중’ 으로 표시되고 자료는 조용히 버려졌다. 이제 로그에 경고를 남긴다. 1.19.28 Fixed: USB 로 연결한 장비의 수집이 어느 순간 조용히 멎고, 프로그램을 다시 켜야만 돌아오던 문제. 전원이 잠깐 흔들리면 USB 장비가 스스로 연결을 다시 맺는데 그때 운영체제가 새 포트 번호를 붙인다 (예: ttyACM0 이 ttyACM1 로). VR 은 처음 열었던 번호를 계속 붙들고 있어서, 장비도 케이블도 멀쩡한데 값만 들어오지 않는다 — 화면상 연결 상태도 정상으로 보여 눈으로는 알 수 없었다 (수술방의 Link+ 주입펌프에서 확인). Added: 포트 자리에 포트 번호 대신 장비의 고유 신원을 적을 수 있다. 형식은 20d0:0002 처럼 네 자리 두 개이며, 이 값은 장비에 박혀 있어 다시 연결되어도 바뀌지 않는다. 이렇게 적어 두면 VR 이 포트를 열 때마다 그 신원을 가진 자리를 다시 찾으므로, 번호가 바뀌어도 저절로 따라간다. 기존처럼 포트 번호를 적어 둔 곳은 그대로 동작한다. Changed: Link+ 는 포트를 적지 않으면 이 신원을 기본값으로 쓴다. 꽂기만 하면 잡히고, 다시 연결되어도 따라간다. 포트를 직접 적어 둔 곳은 적은 값이 우선이다. Changed: 자동 판별이 USB 신원을 먼저 본다. 고유 신원으로 자기를 밝히는 장비는 통신 속도·형식을 재 볼 필요가 없으므로 조용한 회선에서 72초가 걸리던 측정을 건너뛰고 바로 확정한다. 이렇게 확정한 장비는 포트를 번호가 아니라 신원으로 기록한다. 여러 장비가 함께 쓰는 범용 USB 변환 케이블(FTDI 등)은 신원이 케이블의 것이지 장비의 것이 아니므로 종전대로 측정해서 판별한다. 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.27 | 2026-09-05 | 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 | 2026-09-05 | 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 | 2026-09-04 | 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 | 2026-09-02 | 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 | 2026-09-01 | 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.22 | 2026-08-30 | 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.21 | 2026-08-29 | 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.20 | 2026-08-29 | 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.19 | 2026-08-27 | 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.18 | 2026-08-27 | 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.17 | 2026-08-27 | 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.16 | 2026-08-27 | 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.15 | 2026-08-27 | 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.14 | 2026-08-27 | 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.13 | 2026-08-27 | 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.9 | 2026-08-14 | 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.8 | 2026-08-14 | 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.7 | 2026-08-12 | 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.5 | 2026-08-06 | 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.4 | 2026-08-05 | 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.3 | 2026-08-04 | 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.0 | 2026-08-03 | 1.19.0 Fixed: 여러 베드를 한 프로세스가 TCP 로 수집하는 구성에서 클라이언트 재접속이 잦으면 프로세스 전체가 SIGSEGV 로 죽던 문제. accept_handoff 가 교체(replace) 시 옛 소켓을 즉시 closesocket 했는데, m_sock_mtx 는 fd 번호만 보호할 뿐 fd 의 수명은 보호하지 않아 reader 가 그 fd 로 recv 중일 수 있었다. close 는 blocking recv 를 깨우지도 않고 fd 번호를 즉시 재사용 가능하게 만들어, 다음 접속이 같은 번호를 받으면 reader 가 남의 세션을 읽었다. SNUH 회복실(VRN 15대, 최다 베드는 하루 785회 재접속)에서 실제로 크래시 후 9시간 48분 전 베드 무수집. 수정 세 겹: (1) tcp reader 를 poll(POLLIN,200ms) 게이트 뒤의 recv 로 재구성 - recv 가 무기한 블로킹되지 않으므로 close/replace 가 늦어도 200ms 안에, shutdown 이 동반되면 즉시 감지된다. fd 에 O_NONBLOCK 은 걸지 않는다(같은 fd 로 송신하는 write_transport 의 부분 전송 방지). (2) replace 는 closesocket 대신 shutdown 만 하고 옛 fd 를 retired 목록에 넣으며, 닫기는 깨어난 reader 또는 Serial::close 가 정확히 한 번 수행 - fd 번호 재사용 창이 사라진다. (3) Serial::close 가 join 전에 shutdown 으로 reader 를 깨우고(RCVTIMEO 는 설정된 적이 없어 종전에는 피어가 끊을 때까지 join 이 매달렸다), m_sock 접근을 전부 락으로 감싼다. has_socket 의 무락 읽기와 udp_thread_func 의 이중 close 도 같은 계열이라 함께 수정. Added: AUTO_DETECT=1 (vr.conf) - 시리얼 포트에 무엇이 물려 있는지 스스로 알아내 즉시 수집을 시작한다. 판별용 프로브 바이트를 따로 두지 않고 후보 드라이버를 실제로 열어 유효 레코드 도달 여부로 판정하므로 장비별 판별 코드가 없고 드라이버는 무수정이다. 4단계: 회선 측정(커널 프레이밍/패리티/브레이크 카운터로 보레이트와 프레임 폭 확정) -> 패리티 추론(10비트 프레임의 7E1/7O1 대 8N1 모호성을 MSB 상관으로 해소) -> 후보 필터(보레이트+프레임폭+데이터비트 3축 일치, 베드당 환자모니터 1대/마취기 1대 배타, 포트 위치 사전확률, 지난 판별 결과 캐시) -> 남으면 트랙 수확량으로 경쟁. SNUH 수술장 9방 + ICU 3베드 실측에서 Intellivue/MedibusX/Datex-Ohmeda/Primus 를 오탐 0 으로 판별했고, 표준과 다른 포트에 꽂힌 장비(P3/P4 의 Intellivue)도 찾아냈다. AUTO_DETECT 가 없으면 스캐너는 유휴 대기만 하므로 기존 설정에 영향이 없다. | |
| 1.18.52 | 2026-07-31 | 1.18.52 Changed: VR 기본 운영이 k3s 로 이관됨에 따라 서버→VR 하향 제어 경로를 제거. VitalServer 로는 데이터를 일방 push 만 하고, websocket(socket.io) 으로 내려오던 원격 명령 update/restart/reboot/edit_conf/new_bed/del_bed/edit_bed/dt/add_event 를 더 이상 처리하지 않는다 (원격 재부팅·바이너리 교체·conf 덮어쓰기·베드 구성변경 봉쇄). 구현: sendweb_thread_func 의 socket.io 42 이벤트 dispatch 와 on_sio_msg 함수를 통째로 제거. engine.io ping(2->3) 및 ws 레벨 ping/pong 은 연결 유지를 위해 그대로 응답하므로 데이터 업링크·웹모니터링은 종전과 동일. 서버는 VR 버전을 알므로 52+ 로는 명령을 보내지 않고, 받아도 무시. Fixed: --console 모드 (MFC) 에서 stdout 이 이미 유효한 파이프/파일 (> log.txt, ssh 파이프, cmd 상속 핸들) 인데도 무조건 _open_osfhandle+_dup2 로 재배선하면서 그 OS 핸들을 닫아버려, 초기화 이후 모든 로그가 조용히 사라지던 문제 (콘솔 모드 서버 배포에서 진단 불능의 원인, 1.18.7 부터 잠재). stdout 핸들이 유효하면 (GetFileType != UNKNOWN) 재배선하지 않고, 핸들이 없을 때만 (Explorer/schtasks 실행) AttachConsole 로 부모 콘솔에 붙인다. 실측: 종전 초기화 후 로그 침묵 -> 수정 후 지속 출력. (앞서 1.18.51 의 VRQt qDebug->stdout 통일과 함께 세 빌드의 --debug 로그 일관성 완성.) | |
| 1.18.51 | 2026-07-30 | 1.18.51 Fixed: VRQt (Qt 빌드) 에서 --debug 옵션을 켜도 콘솔에 로그가 전혀 출력되지 않던 문제. 원인: Qt 빌드의 TRACE 가 qDebug() 로 매핑되어 있었는데 (UTIL.h, 1.14.4 부터), Windows 에서 Qt 로깅은 GUI 앱이면 콘솔/파이프가 아니라 디버거 (OutputDebugString) 로만 보내므로 VRQt 만 --debug 콘솔 출력이 구조적으로 불가능했다 (MFC 는 stdafx.h 의 #undef TRACE 로 printf 버전을 사용해 정상). 수정: TRACE 를 세 빌드 (MFC/Qt/CLI) 공통 fprintf(stdout)+fflush 로 통일 - qDebug 직접 사용처는 코드에 없음을 확인. 이제 --debug 동작과 리다이렉트 (> log.txt) 가 전 빌드에서 일관되고, cmd 대화형 실행 시에는 기존 AttachConsole/AllocConsole 로직 (1.18.30) 이 콘솔을 만들어 준다. terapc 실측 검증: 종전 VRQt stdout 0 chars → 수정 후 4,690 chars 정상 출력, Vital.exe (MFC) 는 종전과 동일 정상. 추가 방어: --debug 의 콘솔 확보 검사에 GetFileType 추가 - 일부 런처/셸이 non-NULL 무효 stdout 핸들을 상속시키면 NULL 검사만으로는 AttachConsole 을 건너뛰어 로그가 사라질 수 있던 엣지 케이스 (MFC/Qt 공통). | |
| 1.18.50 | 2026-07-30 | 1.18.50 Fixed: 장비 연결이 한번 끊어지면 다시 붙지 않던 문제 (Link+ 현장 리포트, Intellivue 늦은 전원-온과 공통 근본 원인). 근본 원인: DEVICE::open() 의 초기화 호출 set_rx_time(0) 이 값 0 인데도 OPENING→ACTIVE 로 승격시키고 m_dtStatus 까지 0 으로 만들어, (1) 모든 장비가 open 즉시 가짜-ACTIVE (OPENING 상태가 사실상 부재), (2) restart 워치독의 dtlast(rx ?: status_time) 가 0 → read_timeout 조건이 영구 거짓 → 재연결 시 상대 무응답이면 (Link+ rack 재부팅/세션 잔존, 세션 프레임 1회 전송 후 무응답) 재시도 없이 영구 고착. 1.18.35 이하의 rec_timeout (상태 무관 60s 강제 재시작) 이 이 잠재 버그를 우연히 가리고 있었고 1.18.36 전환으로 노출된 것. 수정 3건: (1) set_rx_time 은 실수신 (dt>0) 일 때만 ACTIVE 승격 - 상태 표시도 정직해짐 (open 후 첫 수신까지 "con"), (2) restart 워치독에 OPENING-stuck 분기 추가 - open 후 read_timeout 내 첫 수신이 없으면 재시작해 협상 프레임 재전송, tx_time>0 (protocol-initiator) 조건으로 passive/keyword-sub 디바이스는 제외해 유휴 churn 없음, (3) tx_time 을 모든 transport (socket/fd/COM) 에서 갱신 (기존 Windows COM 만). 영향 장비: Link+/Agilia, Intellivue, S5, Datex, Hamilton 등 open 시 세션/assoc 을 시작하는 모든 protocol-initiator. E2E 검증: 무응답 서버 재현에서 종전 접속 1회 후 영구 침묵 → 수정 후 30초마다 재접속 반복, 장비 복구 시 자동 연결. Mindray 멀티베드 회귀 테스트 PASS (ACK 75/75, tier1 라우팅, probe 거절, stuck 오발동 0). Intellivue 1.18.48 assoc-retry (5s) 는 유지 - 이번 워치독은 30s 백스톱으로 공존. | |
| 1.18.49 | 2026-07-29 | 1.18.49 Fixed: (1) Mindray eGateway HL7 멀티베드 (catch-all + #키워드 sub) 에서 전 베드가 readonly=1 이면 데이터가 전혀 수집되지 않고 게이트웨이가 수초마다 재접속하던 문제. 원인 셋 - (a) readonly catch-all 이 open_socket 에서 조기 반환해 reader/parser 스레드가 아예 생성되지 않음 (아무도 안 읽는 소켓에 데이터가 쌓이다 게이트웨이가 연결 재수립 반복; PortRouter 리팩토링 시 readonly 체크가 blocking accept 앞으로 이동하며 생긴 1.18.36 회귀 - 1.18.31 은 accept 가 먼저라 우연히 동작), (b) readonly 가 HL7 ACK 송신까지 차단 (eGateway 는 메시지마다 ACK 요구), (c) sibling 베드 메시지는 broadcast 후 ACK 없이 return. 수정 - readonly 는 송신만 차단하고 수신은 정상화 (broadcast-only keyword sub 만 소켓 미보유), write_protocol 경로 신설로 ACK 는 readonly 우회, catch-all 이 sibling 메시지도 대신 ACK (R01/R40 타입 구분), broadcast 수신 sub 의 rx_time 갱신으로 상태표시 정상화, PortRouter takeover 는 기존 소켓이 stale (10초+ 무수신) 일 때만 허용해 게이트웨이 health-probe 접속이 live 데이터 소켓을 뺏지 못하게 함. 시뮬레이터 E2E 검증: ORU 75건 -> ACK 75건 (100%), probe 5회 전부 무해 거절, SU-2/SU-4 tier1 라우팅 정상. (2) Getinge/MAQUET Flow-i (FCI 프로토콜) tidal volume 이 실제의 절반으로 기록되던 문제. 원인: FCI 프로토콜 v0005 부터 TV 채널 (101/102) gain 이 0.1 -> 0.2 로 변경됐는데 (Flow-i communication interface v0007 문서), VR 은 open 시 SPVE0005 로 v5 를 요청하면서 파싱은 구버전 gain 0.1 을 적용. 병원 vital 파일 실측 (Flow-i 직결 TV 201.4/188.2 vs 필립스 경유 403.0/376.6, MV 는 정상) 으로 검증 - TV_INSP/TV_EXP gain 0.2 적용. MV 는 v3+ 스케일 (0.01) 이 이미 반영돼 있어 변경 없음. (3) Flow-i 설정 파라미터 미지원 (m_ch_settings 빈 맵) - v5+ 에 존재하는 수치 설정 채널 11종 등록: SET_RR(411), SET_INSP_TM(413), SET_IE(418), SET_MV(421), SET_PC(423), SET_PEEP(424), SET_FRESH_FLOW(428), SET_FRESH_O2(430), SET_AGENT_PERC(434), SET_PS(436), SET_SIMV_RR(437). gain 은 문서 표기 그대로. 주의: Set Tidal Volume (채널 422) 은 FCI v5 에서 프로토콜 자체에서 삭제되어 직접 수신 불가 - 볼륨 모드에서 SET_MV/SET_RR 로 파생 가능. 설정 채널은 문서 기반 구현으로 실기 검증 대기 상태. | |
| 1.18.48 | 2026-07-24 | 1.18.48 Fixed: VR 이 먼저 실행된 상태에서 Philips Intellivue 모니터를 나중에 켜면 웨이브가 영원히 수집되지 않던 문제 (VR 재시작 전까지 모니터로 아무 요청도 안 나감). 원인: Intellivue::open() 은 assoc_req 를 딱 한 번 보내는데, 시리얼(MIB)/UDP 는 모니터가 꺼져 있어도 포트가 열리므로 이 요청이 응답 없이 사라진다. Philips 는 자기가 먼저 말을 걸지 않는 프로토콜이고, 첫 수신이 있어야 디바이스 상태가 OPENING→ACTIVE 로 승격되는데 수신이 없으니 OPENING 에 고정 - DEVICE::restart_thread_func 워치독은 is_closed(재오픈)/is_active(read_timeout 30s) 두 분기만 있어 OPENING 은 어느 쪽에도 안 걸림 → assoc 재전송도 재시작도 영원히 없음. 수정: Intellivue 에 assoc 재시도 스레드 추가 - 세션 확립(MDS_CREAT 수신, m_bSessionUp atomic 플래그) 전까지 5초 간격으로 assoc_req 재전송, 최근 5초 내 수신이 있으면(assoc 진행 중) 재전송 보류. 모니터를 나중에 켜도 최대 5초 내 연결. 세션 확립 후에는 재시도 루프 종료 - 기존 동작(AB_SPDU 시 재연결, read_timeout 재시작)과 충돌 없음. 모니터가 켜져 있던 기존 시나리오는 첫 assoc_req 에 바로 응답하므로 동작 변화 없음 - 0-impact. | |
| 1.18.47 | 2026-07-24 | 1.18.47 Fixed: 파일 업로드(및 Http 클래스를 쓰는 모든 경로 - upload_vital, getvr 업데이트체크, ADT 조회, 필터서버)가 SERVER_IP/UPLOAD_SERVER_IP 에 "https://host:port" 풀 URL 형식을 넣으면 "Couldn't resolve host name" 으로 전부 실패하던 문제. 원인은 src/Https.cpp 의 Http 생성자 - 1.18.46 의 wss.cpp 수정과 동일한 버그의 나머지 반쪽. 첫 콜론을 포트 구분자로 오인해 "https://eicuvital.ywmc.or.kr:4432" 입력 시 m_strHost 가 문자 그대로 "https" 가 되어 DNS 조회 실패 (웹모니터링 wss 는 1.18.46 에서 이미 수정되어 정상, 파일 업로드만 실패하는 비대칭 발생 - 현장에서는 업로드 루프의 실패시 break 정책과 겹쳐 "베드명에 # 가 있으면 전송이 안 된다" 로 오인 관찰됨. 실제로는 # 파일이 디렉토리 순서상 항상 첫 시도라 거기서 host 오류로 중단 → 뒤 파일 전체가 막힌 것. 베드명 특수문자는 업로드 URL 과 무관). 수정: Http 생성자에 wss 와 동일한 선행 스킴(https/http) 파싱 추가 - 스킴이 TLS 여부를 결정 (포트 무관, 4432 같은 커스텀 포트에서 https 정상), "https://host" 포트 생략 시 443, 경로 붙은 입력도 host 만 추출. 기존 "host" / "host:443" 형식은 종전과 완전 동일 동작 - 0-impact. Windows(WinINet)/Linux(curl) 공통 생성자라 모든 플랫폼에 적용. | |
| 1.18.46 | 2026-07-23 | 1.18.46 Fixed: WSSClient 가 "https/wss 스킴 + 비표준 포트" 조합을 제대로 처리하지 못하던 문제 (커스텀 포트의 wss 서버에 연결 불가). 원인은 src/wss.cpp 의 WSSClient 생성자 두 곳 - (1) SERVER_IP 파싱이 첫 콜론을 포트 구분자로 써서 "https://host:4432" 를 넣으면 "https" 의 콜론을 먼저 잡아 m_host 가 "https" 가 되고 포트 파싱이 깨져 getaddrinfo 실패, (2) m_secure = (m_port == 443) 로 TLS 여부를 오직 포트 443 여부로만 판단해 4432 같은 커스텀 포트는 무조건 평문 ws 로 붙어 wss 전용 서버에서 실패. 수정: 생성자가 선행 URL 스킴(https/wss/http/ws)을 먼저 파싱해 스킴이 있으면 스킴이 TLS 를 결정(포트 무관)하고, 스킴이 없으면 기존 포트 443 휴리스틱을 유지(하위호환). 이제 SERVER_IP=https://eicuvital.ywmc.or.kr:4432 (또는 wss://...) 형식이 비표준 포트에서도 wss 로 정상 연결되며, SNI/인증서 검증은 도메인(m_host)을 쓰므로 와일드카드 인증서도 정상 매칭. 기존 host / host:443 설정은 종전대로 secure 유지 - 0-impact. | |
| 1.18.45 | 2026-06-16 | 1.18.45 Fixed: Windows VR 에서 사용자가 외부에서 vr.conf 를 멀티베드 설정으로 교체 후 VR 을 재실행하면, 첫 실행에서는 멀티탭이 정상 생성되지만 다음 실행에서 모든 탭이 사라지던 문제. 원인은 VRApp.cpp:1110 의 VRCODE 미설정 분기 — 새 vr.conf 가 VRCODE 라인을 포함하지 않으면 load_settings() 가 신규 VRCODE 를 issue_code() 로 발급하고 즉시 save_settings() 를 호출하는데, 이 시점은 load_settings() 가 파싱한 베드들이 로컬 newbeds 벡터에만 있고 아직 m_pdisps 로 옮겨지기 전 (set_beds(newbeds) 는 initialize() 가 load_settings 반환 후 라인 1328 에서 호출). 결과: save_settings() 가 빈 m_pdisps 를 iterate 하며 vr.conf 를 글로벌 설정만으로 덮어써 모든 [BED/...] 섹션이 디스크에서 사라짐. 첫 실행은 메모리의 newbeds 로 UI 멀티탭이 그려져 정상으로 보이지만 (사용자가 "멀티탭은 정상적으로 생성되지만" 으로 표현), VR 종료/재실행 시 디스크 conf 에는 BED 섹션이 없으므로 다음 load_settings 가 빈 베드 리스트를 반환 → 탭 전부 사라짐. 수정: VRApp.h 에 m_vrcode_newly_issued bool 플래그 추가, load_settings 의 즉시 save 를 플래그 set 만 으로 교체, initialize() 의 set_beds(newbeds) 직후 (m_pdisps 가 채워진 시점) 플래그가 켜져 있으면 한 번만 save_settings() 호출 → BED 섹션이 정상 직렬화됨. Linux/Pi 분기 (m_serial_number 가 채워져 있는 경우) 는 원래도 save 안 했으므로 영향 없음, VRCODE 가 이미 conf 에 있는 모든 기존 deployment 도 분기 미진입으로 0-impact. m_conf_from_stdin / m_bDebug 모드는 save_settings 내부에서 이미 early return 이라 그대로 안전. MFC + Qt 빌드 모두 통과 (이번 변경 관련 0 warning). | |
| 1.18.44 | 2026-06-12 | 1.18.44: vr.conf FHIR=1 모드 추가 (자체 FHIR 수신서버 대상 실시간 푸시) + VS 2026 / 새 OpenSSL 인스톨러 빌드 환경 호환 shim. (1) sendweb_thread_func 의 기존 HL7/JSON 양분 분기 (VRApp.cpp:2466) 옆에 bFHIR 분기 추가. 1초 tick 마다 한 vrcode 의 모든 방 Observation 을 하나의 FHIR R4 collection Bundle 로 묶어 기존 WSS 채널로 push (gzip + WSSClient 인프라 그대로 재사용). MLLP 같은 신규 transport 안 도입 — FHIR R5 Subscription 의 websocket channel type 과 동형이라 표준 부합. (2) DISPLAY::get_fhir + TRACK::get_fhir 신규. v2 (vitaldb-fhir-v2) 와 동일한 reference-only 패턴 (Patient.id = m_patient_id, Encounter.id = vrcode-bedname-startsec, Observation.subject/encounter = relative reference) + hybrid keyframe: 첫 tick / m_patient_id 변경 / get_start_time() 변경 / 5분 경과 중 하나라도 발생 시 다음 tick Bundle 에 Patient + Encounter resource 를 inline. 그 외 tick 은 Observation 만. 수신서버가 도중에 새로 connect 해도 최대 5분 안에 자동 동기화 (keyframe heartbeat). DISPLAY 에 m_fhir_pt_sent_at/m_fhir_pt_last_id/m_fhir_enc_last_start 3개 멤버 추가 (m_setting_sent 패턴 그대로 per-display). (3) Observation 인코딩: NUM 은 effectiveDateTime + valueQuantity 각 데이터 포인트당 한 entry, WAV 은 effectivePeriod + valueSampledData (period=1000/srate ms, factor=1, origin=0, data=공백구분 floats) 한 entry. CO2/AWP wav 는 기존 get_json 처럼 25Hz 다운샘플 유지. NUM 은 m_montype 기반 LOINC 매핑 15개 등록 (ECG_HR/PLETH_HR=8867-4, PLETH_SPO2=59408-5, NIBP/IABP SBP/DBP/MBP=8480-6/8462-4/8478-0, BT=8310-5, RR=9279-1, etCO2=19891-1, CVP=8556-0, FETAL_HR=11627-3) + VR 자체 코드 시스템 (https://vitaldb.net/fhir/CodeSystem/track, code = dname/tname) 항상 동반. m_patient_id 미설정 (PATIENT_ID_UNSET) 시 anon-vrcode-bedname identifier 로 fallback. (4) Bundle wrapper: VRApp 가 방별 entries 를 콤마로 합쳐 {resourceType:Bundle, type:collection, timestamp:iso8601Z, entry:[...]} 로 wrap, 기존 gzip → WSS pipeline 동일. ISO 8601 UTC 헬퍼는 std::gmtime_s/gmtime_r 인라인. FHIR id 안전화 (A-Za-z0-9-.) 64자 제한 lambda 각 함수에 inline. (5) 빌드 환경 호환성: VRQt/CMakeLists.txt 에 _SILENCE_EXPERIMENTAL_COROUTINE_DEPRECATION_WARNINGS 추가 — VS 2026 (MSVC 14.51+) 의 STL 이 <experimental/coroutine> deprecation 을 warning → hard static_assert 로 바꾼 것이 cppwinrt 의 winrt::* (Serial.cpp 의 Bluetooth LE) 를 끌어오는 모든 TU 에서 컴파일 실패 시켜서, build_mfc.py 가 이미 CL env 로 주입하던 것과 동치 shim 을 Qt 쪽에도. (6) VRQt/CMakeLists.txt 의 OpenSSL link 경로 시프트. 새 OpenSSL 인스톨러가 libssl_static.lib / libcrypto_static.lib 를 lib/VC/x64/{MT,MTd}/ 에 두고 v1.x 의 libssl64MT.lib / libcrypto64MT.lib 네이밍과 lib/VC/static 경로를 깬다. build_mfc.py 의 %LOCALAPPDATA%/openssl-compat 패턴 (옛 이름으로 복사한 사본 폴더) 을 그대로 따라 CMakeLists 도 그 폴더가 있으면 우선, 없으면 옛 lib/VC/static 으로 fallback. 부트스트랩 스크립트는 build_mfc.py 의 docstring 참조 (한 줄). MFC + Qt + RPi + Ubuntu CLI + AppImage 빌드 모두 통과. 동작 영향: vr.conf 에 FHIR=1 추가 안 한 기존 deployment 는 sendweb 가 종전 JSON 모드 그대로 (HL7=1 만 분기보다 우선) — 0-impact migration. | |
| 1.18.43 | 2026-05-21 | 1.18.43: 멀티베드 console (PACU 10베드 Pi) syslog noise 80,500/hr → ~1,400/hr (-98.3%), cpu_usage 측정 1.16.12 (9f26e36) 이후 8년치 영구 -1 버그 수정, 멀티베드 환경 베드 식별용 [bedname / devicename] 로그 prefix 도입. 동기는 PACU production Pi (1.18.40) 4일치 syslog 분석 — 1.24M 라인 중 vr 라인 836K, idle 8시간 평균 시간당 80,500 라인 (베드 10 × 1초마다 "Waiting for next patient" × syslog 2x 중복). (1) VRApp.cpp:2696-2710 timer_thread_func 의 cpu usage 계산이 `if (totalcpu && m_cpu_total_last && totalcpu > m_cpu_total_last)` 가드 안에서만 m_cpu_total_last 갱신해 첫 호출 시 m_cpu_total_last=0 → else 분기 → -1 → m_cpu_total_last 갱신 안 됨 → 영원히 false → 영원히 -1 데드락. PACU 1,048 샘플 100% -1 실측 확인. m_cpu_total_last/m_cpu_used_last 갱신을 if 밖으로 빼서 첫 호출만 baseline 으로 -1, 두 번째 (60s 후) 부터 정상 측정. /proc/stat 의 iowait/irq/softirq 까지 7개 필드 fscanf 로 정확도 ±5% 개선. 부수: 같은 함수의 cpu_temp 가 `temp / 1000` int 나눗셈으로 소수점 절단되어 PACU 측정값이 정수 50/51/...,/58 9종만 출력 (millidegree 정밀도 손실) → `temp / 1000.0f` 로 fix. ram 계산은 `(totalram + totalswap) * mem_unit` 결과에 다시 mem_unit 곱하던 차원 혼용 (Linux 가 mem_unit=1 이라 우연히 정상 동작했지만 dimensionally wrong) → `(units1 + units2) * mem_unit` 로 정정 + underflow 가드. (2) DISPLAY::log(string/printf) + DEVICE::log(string/printf) 멤버 추가. DISPLAY::log 는 `[bedname] msg`, DEVICE::log 는 m_pdisp 위임 없이 직접 `[bedname / devicename] msg` 한 줄 prefix (nested `[bed] [dev]` 보다 가독성 우선). m_pdisp 또는 bedname null 케이스 safe fallback. (3) show_msg 의 console path TRACE 를 log() 로 교체 → "Waiting for next patient" 자동 [bedname] prefix. ADT patient id unset 도 log() 로. (4) 이미 수동으로 get_bedname() 을 인자로 넘기던 8군데 (DISPLAY ptcon_thread_func / add_trk / add_dev / del_dev / cut-recording, Device add_rec 콜백, FILT stop, VRApp add_tab/del_tab) 를 log() 로 전환 — 이중 prefix 방지 + 일관성. Serial::reading_thread / tcp_thread / open_socket waiting / timeout / interrupted / got-client 6군데 DEVICE::log() 전환 → `[bedname / Intellivue]` 등 자동 prefix, 멀티탭 환경에서 어느 베드 어느 device 출처인지 즉시 식별. (5) wss 재시도 폭주 throttle. WSSClient 에 m_verbose (debug step 로그 게이트) + m_dt_last_err_log (60s window) 추가. connect() 내부 step-by-step TRACE 4개 ("connecting" / "connect error" / "connected") 는 m_verbose 가드, 최종 "wss connect error" 는 60s window throttle (첫 줄 즉시, 그 다음 60s 1회). connect 성공 path 에서 throttle reset 으로 다음 disconnect → 첫 에러 즉시 로깅. sendweb_thread_func 의 "connecting websocket server" 호출자 라인도 같은 60s window, m_bDebug 면 verbose 활성화. PACU idle 8h 측정 7,024/hr (4종 × 1,756) → 약 60/hr (-99%). (6) "Waiting for next patient" / "ADT patient id unset" 의 60s throttle 자체는 feature/wav-mem-retention 브랜치 ptcon_thread_func 재구성 (b75928f 시리즈) 에서 이미 들어가 있어 이 릴리즈에 자동 포함. 종합 영향: idle 시간당 syslog 라인 80,500 → ~1,400 (-98.3%), active 시간당 14,900 → ~700 (-95%). 진단 측면: cpu_usage 가 부활하면서 웹모니터링 json 의 cpu_usage 필드도 한 번도 안 채워지던 게 정상 전송 (VRApp.cpp:2499 의 `if (m_cpu_usage > 0)` 가드가 영원히 false 였음). 별건: syslog 라인 2× 중복은 application 측이 아니라 rsyslog imuxsock + imjournal + journald.ForwardToSyslog=yes 의 Debian Bookworm 전형 misconfig — admin 측 fix (rsyslog.conf 에서 imjournal 주석 처리 + restart) 로 추가 50% 감소 가능 (별도 문서). MFC + Qt + Pi (RPI64) + Ubuntu CLI 빌드 모두 통과. | |
| 1.18.41 | 2026-05-13 | 1.18.41 Hotfix: TCP-server protocol-initiator device (Intellivue/S5/Datex/Hamilton/MPS/Agilia/IAP) 가 open() 직후 보내는 첫 패킷이 wire 에 안 나가던 회귀. 1.18.34 의 PortRouter refactor 가 Serial::open_socket() 의 8초 handoff blocking 을 제거하고 비동기 모델로 전환했는데, protocol-initiator 의 open() 코드가 m_sock=INVALID 상태에서 write 를 시도해 silent fail (Linux 는 m_fd=0 이라 false 반환). Intellivue 케이스 production journald 의 Pi → ESP TX 700K~3M bytes 는 monitor 측 abort 로 늦게 트리거된 AB_SPDU_SI recovery assoc_req 후 request_thread 의 정상 polling 트래픽이지만 일부 베드 (abort 없는 monitor) 는 recovery 도 안 되어 영구 silent (RX 300 bytes). 수정 방향: open() 의 옛 contract ("return 시점에 통신 가능") 복구. (1) Serial::open_socket() TCP-server 분기가 PortRouter::subscribe 후 m_sock_cv 로 첫 client handoff 까지 block (timeout = device read_timeout, 기본 30s) — 옛 모델 그대로. lock_and_open 이 false 면 다음 tick 에서 retry. close 신호로 abort 가능. (2) Serial::close() 는 PortRouter::unsubscribe 안 함 — subscribe 는 device 생애 동안 sticky 라 read_timeout 으로 인한 close→re-open 사이클 동안 listen socket 이 유지되어 1.18.29/1.18.34 의 anti-churn 이익 보존. unsubscribe 는 ~Serial() 에서만. (3) Per-device lifecycle thread (DEVICE::m_thread_restart): 각 device 가 자기 open/close/timeout 재시작을 별도 thread 에서 관리. 옛 모델의 DISPLAY::bed_manager 가 베드의 모든 device 를 sequential iterate 하던 구조에선 한 device 의 blocking open() 이 같은 베드의 다른 device 의 lifecycle 을 차단했는데, per-device thread 로 분리하여 멀티 device 베드도 병렬 진행. add_dev 에서 start_lifecycle, del_dev / ~DISPLAY 에서 stop_lifecycle. (4) Serial::tcp_thread_func 단순화 — outer m_sock_cv wait 패턴 (1.18.34/1.18.39 가 추가했던 것) 제거. open_socket 이 m_sock valid 보장하므로 reader thread 시작 시점에 simple inner recv loop 만. accept_handoff 의 replace (zombie cleanup) 케이스는 recv error 후 m_sock 변경 확인 + retry 로 처리. (5) reading_thread_func 의 `else if (m_sock != INVALID_SOCKET) tcp_thread_func();` 가드 복원 (1.18.39 가 제거). (6) Serial::write 의 silent-fail 케이스에 WARN trace 추가 — 미래 동종 회귀 즉시 노출. (7) PortRouter::subscribe 가 같은 (port, type) 에서 같은 IP filter 또는 둘 이상 catch-all 발견 시 reject (config 실수 원천 차단, 옛 WARN-only 동작 강화). (8) DISPLAY::bed_manager_thread_func 의 restart 블록 제거 — ptcon + adt 만 담당. "Waiting for next patient" TRACE 가 Linux/console 에서 1초 → 60초마다 throttle (별개 journald 부풀림 fix). 영향 범위: Intellivue / S5 / Datex / Hamilton / MPS / Agilia / IAP TCP-server 모드 베드 전부. 1.18.34 이후 우회 방법은 race-win 케이스 (VRN 이 PortRouter::subscribe 직후 connect) 외엔 없었음. MFC + Qt + Pi (RPI64) + Ubuntu Qt + AppImage 빌드 모두 통과. | |
| 1.18.40 | 2026-05-12 | Hotfix: Drager Medibus 프로토콜 사용 베드에서 1.15.11 이후 vr.service 가 SIGSEGV 로 무한 재시작하던 문제. 1.15.10 (a8c7b2a) 의 "Sampling rate estimation" 리팩터링이 m_ptrk_awp/awf/co2 트랙을 add_trks() 에서의 eager 생성 (default srate=62.5Hz) 에서 realtime data 100 샘플 모인 후 lazy 생성으로 바꿨는데, Medibus 핸드셰이크 순서 (0x51 ICC → 0x52 DeviceID → 0x53 RealtimeConfig 응답 → 0x54 → realtime data 시작) 상 0x53 응답 핸들러 (Drager.cpp:295-307) 가 트랙들이 아직 nullptr 인 시점에 `ptrk->m_srate = 1000000.0/interval` 로 NULL 역참조 → 즉시 SEGV. 그 베드의 vr.service 가 systemd Restart=on-failure 로 재시작 → 다시 connect → 동일 0x53 패킷 받고 동일 SEGV → 무한 루프 (journald 로그상 restart counter 61 까지 누적, code=killed status=11/SEGV). 1.15.9 까진 트랙이 add_trks() 에서 생성되어 0x53 도착 시 항상 non-NULL 이라 안전했음. 수정: (a) add_trks() 에서 m_ptrk_awp/awf/co2 eager 생성 복원 (srate=62.5Hz default) — 0x53 는 ptrk->m_srate 만 갱신 (트랙 재생성 X). (b) on_received() 의 lazy creation + packet-timing srate 추정 블록 제거 — 0x53 가 권위 있는 srate 소스이고 packet 타이밍 기반 추정은 network jitter 에 약해 불안정. (c) 0x53 핸들러에 `if (interval > 0)` guard 추가 — 페이로드 깨졌을 때 div-by-zero / inf srate 방지. 영향 범위: Drager Primus / Apollo / Perseus / Fabius 등 Medibus 사용 마취기 전부. 우회 방법은 1.15.9 다운그레이드 외엔 없었음 (그 베드의 모든 데이터 유실 + 환자 모니터링 불가). MFC + Qt + Pi (RPI64) + Ubuntu Qt + AppImage 빌드 모두 통과. | |
| 1.18.39 | 2026-05-08 | Fixed: PortRouter (TCP server) 모드에서 reader thread 가 startup 시점에 죽어 client 데이터를 영구히 못 받던 문제. 1.18.34 (9bd252f) 의 PortRouter refactor 가 m_sock 세팅을 dispatch 시점으로 옮겼는데 reading_thread_func (Serial.cpp:354) 의 가드 `else if (m_sock != INVALID_SOCKET) tcp_thread_func();` 는 옛 모델 (open 시점 이미 connect 완료) 가정 그대로라 — 시작 직후 m_sock=INVALID 면 tcp_thread_func 진입 안 하고 reader thread 즉시 종료. 이후 VRN 이 connect → PortRouter::dispatch → accept_handoff 가 m_sock 채우고 m_sock_cv.notify_all() → 그러나 wait 중인 reader 가 없음 (이미 죽음) → kernel recv buffer 에만 데이터 적재. 증상은 ss -tn state established 의 Recv-Q 가 0 이 아닌 값 (수백 bytes) 으로 누적, 베드 화면에는 데이터 안 들어옴. 그 베드의 VRN 이 Serial::open() 의 PortRouter::subscribe 직후~reader thread spawn 사이의 짧은 윈도우 안에 connect 한 경우만 우연히 정상 동작 (race-win). 수정: 가드 제거 — TCP 분기에 항상 진입. tcp_thread_func 자체에 이미 m_sock_cv outer-wait 루프가 있어 m_sock 이 INVALID 인 동안 dispatch 가 깨워줄 때까지 안전하게 대기. Pi 핫스팟에 VRN 여러 대 (e.g. 10.42.0.21~25) 를 같은 (4343, Philips:Intellivue) 키로 묶고 IP filter 로 라우팅하는 시나리오에서 가장 자주 노출. Added: 진단 로그 보강 — PortRouter::accept_loop 의 accept 이벤트, dispatch 의 라우팅 결정 (ip_specific/catch_all subs 수, 어느 dev 가 받았는지), subscribe/unsubscribe 의 entry 생성/append/destroy, reading_thread / tcp_thread 의 시작·wait·wake 라이프사이클 — 이전엔 모두 m_bDebug 게이트 안에 있어서 production journald 에서 안 보였는데, 이제 항상 출력 (이번 같은 dispatch routing 문제 진단을 production 에서 바로 가능). MFC + Qt + Pi (RPI64) + Ubuntu Qt + AppImage 빌드 모두 통과. | |
| 1.18.38 | 2026-05-08 | Fixed: 웹모니터링 우클릭 → 디바이스 세팅 (websocket edit_bed 명령) 으로 베드의 device/filter 를 변경할 때 베드 이름이 매번 "_2" suffix 로 자동 리네임되어 vr.conf (Pi: /data/vr.conf) 에 잘못된 이름으로 저장되던 문제. 원인은 1.18.22 (cda8aa5) 에서 add_bed 에 추가된 bedname dedup ("최후 방어선" 으로 forward_frame auto-create / 수동 편집된 conf 의 중복 이름 처리용). edit_bed 핸들러는 옛 pdel 이 m_pdisps 에 그대로 있는 상태에서 같은 bedname 으로 new DISPLAY 를 add_bed 하고 그 다음 del_bed(pdel) 하는 순서였는데, add_bed 가 dup 감지해 새 pdisp 를 bedname_2 로 자동 리네임 → save_settings 가 [BED/bedname_2] 로 직렬화 → 사용자 입장에선 "설정 적용 안됨" 으로 보였다. 추가로 옛/새 DISPLAY 가 잠깐 m_pdisps 에 공존하면서 같은 device 포트를 동시에 들고 있으려 해 로그가 dup 으로 찍히는 부수 증상도 있었음. 수정: del_bed(pdel) 를 add_bed(pdisp) 보다 앞으로 이동 — 옛 베드의 device 포트가 깨끗이 release 된 뒤 새 pdisp 가 같은 포트를 reopen, dedup 발동 안 함. 다른 베드의 recording 은 영향 없음 (인메모리 hot swap 설계 유지). 부수: edit_bed 핸들러의 데드 코드 (`break` 뒤의 unreachable olddevs populate 루프 + `if (olddevs.find(name) == olddevs.end())` 항상-true 래퍼) 정리. edit_conf 와 save_settings 는 손대지 않음. MFC + Qt + Pi (RPI64) 빌드 모두 통과. | |
| 1.18.37 | 2026-05-08 | Refactored: 필터 다이얼로그의 python 런타임 — pyvital → openvital 0.3.0 로 교체 + 동봉 python.zip 슬림화. 핵심 동기 셋: (1) pyvital 0.6.0 이 tensorflow+torch+keras 를 hard-dep 으로 끌어들여 PyPI upgrade 가 사실상 깨짐, (2) 기존 zip 121 MB 안에 botocore/aiobotocore/s3fs/pyarrow/pandas/aiohttp 등 vitaldb 클라우드 업로드용 deps 가 ~250 MB unpacked 차지 — 필터 서버는 한 번도 쓴 적 없는 dead weight, (3) sanic 25.x 의 multi-process worker 가 openvital __main__.py 를 fork 후 재import 하면서 worker 가 module-top app instance 를 못 찾아 "Sanic app name not found" 로 죽음 — 단순 endpoint 2개 로컬호스트 서버에 async stack 자체가 과함. 해결: openvital 0.3.0 (commit b6f445b) 가 sanic 을 stdlib http.server (BaseHTTPRequestHandler + HTTPServer) 로 50 LOC 재작성 — wire protocol (GET / 필터목록 JSON, POST /<modname> gzip 본문) 동일해 VR 클라이언트 변경 0줄. 단일 스레드로 기존 sanic event loop semantics (cfgs/default_cfgs lock-free 변경) 보존. build_python_zip.py (NEW) 가 Python 3.11.9 embed + numpy + openecg + openvital (local checkout 우선, fallback PyPI) 만으로 27 MB zip 빌드 — 기존 121 MB 대비 78% 감소. ML 필터 (sv_dlapco/abp_hpi/ecg_classifier/ecg_beat_noise_detector) 는 [Hybrid 모델] 사용자가 Add filter 다이얼로그의 "Install ML filters" 버튼 누를 때 silent pip install openvital[all] (cmd 창 미노출 — VRApp::install_pip_package + run_pip_silent 이 자체 progress 다이얼로그에 stdout heartbeat 흘림). 버튼 라벨은 "Upgrade pyvital" → "Install ML filters" 로 의도 명시. openvital __main__.py 가 missing extras 를 graceful skip 하므로 base 환경에서도 11개 필터 (ECG QRS detector, HRV, MTWA, annotator, eeg_fft, nirs_cox, pkpd_3comp, pleth_dpop/ptt/pvi, resp_compliance) 즉시 사용 가능. Migration: 기존 사용자의 user_dir/python 폴더는 pyvital + 옛 deps 가 그대로 남아있고 vitaldb.net/python.zip 만 새로 올리면 (a) 기존 폴더 삭제 후 install_python 으로 새 zip 받으면 깔끔, (b) 폴더 유지 시 server 가 openvital 못 찾아 필터 다이얼로그 에러 → release note 에 명시 필요. MFC + Qt 양쪽 코드: VRGui.rc 의 IDC_UPGRADE 버튼 라벨 + DlgAddFilter.cpp 의 dist-info prefix 매칭 (pyvital- 8자 → openvital- 10자) + QtDlgAddFilter.cpp 도 동일 변경 + onUpgrade 가 cmd /k QProcess::startDetached 대신 theApp.install_pip_package("openvital[all]") 호출 (Linux Qt 빌드는 #ifdef _WIN32 가드). VRApp.cpp:318 의 python -m pyvital → -m openvital. install_pip_package 는 anonymous namespace 의 is_safe_pkg_spec 화이트리스트 검증 + run_pip_silent (CreateProcessA + CREATE_NO_WINDOW + 자식 stdout pipe) + CDlgProgress / QtDlgProgress 자체 progress UI. 빌드 검증: python -m openvital 이 GET / 로 11 필터 반환, sanic import 부재 확인. zip 27.5 MB. | |
| 1.18.36 | 2026-05-07 | Fixed: 3-파티션 레이아웃 (boot/rootfs/data) 에서 websocket edit_conf / update 명령이 read-only fs 로 실패하던 문제. vr.conf 는 /data 폴더가 있으면 /data/vr.conf 로 자동 정착 — get_conf_path() 가 write target 으로 결정, load_settings() 는 primary 가 없으면 legacy /boot/firmware/vr.conf 또는 /boot/vr.conf 에서 fallback read 후 다음 save 부터 primary 로 자동 이전 (마이그레이션 step 없음, legacy 파일은 read-only fs 라 무해). binary update (upgrade_now) 는 RAII BootRwGuard 가 dirname(module_path) 를 잠시 rw remount, scope 종료 시 sync() + MS_REMOUNT|MS_RDONLY 로 자동 ro 복귀 — vfat 노출 윈도우 최소화 + exception/return 어디서든 복구 보장. statvfs ST_RDONLY 검사로 이미 rw 인 fs (x86 dev) 에서는 no-op, non-Linux (macOS) 는 stub 으로 통과. save_settings() 의 silent EROFS 실패가 TRACE 로 가시화 (이전엔 무시되어 conf 수정 안 먹는 원인 추적 불가). 부수: PortRouter.cpp 가 commit 9bd252f 이후 CMakeLists 의 RPI sources 목록에서 누락되어 Pi 빌드 link 에러 — 추가. | |
| 1.18.35 | 2026-05-01 | Added: alarm parsing for Mindray HL7 (eGateway ORU^R40) and Nihon Kohden (NealTime HL7GW + ORF, BSM serial D-port, EGA UDP) - emits ALARM_STATUS / ALARM_MESSAGE / ALARM_PRIORITY tracks (same 3-track schema already used by Philips IntelliVue, GE Solar/Dash, GE/Datex S5); type info preserved via [T]/[A]/[ARR] message prefixes; Mindray facet 1/5/6/7 walker with 92-entry MDC+MNDRY alarm code lookup; NK HL7GW parses OBR-4=EVENT with arrhythmia/technical disambiguation by OBX-5; NK BSM serial decodes per-parameter * flags (edge-triggered to avoid 1Hz flooding) plus 32-bit Table 9 arrhythmia bitmap, D19 alarm-suspend nibble, and the VPC CODE byte; NK EGA UDP extracts Priority + Tech Alarm + Arrhythmia from JSON Vital Sign packets with edge-trigger cache for start/end inference; ACK^R40 reply added for Mindray alert messages; simulators gained periodic alarm cycling for end-to-end smoke testing | |
| 1.18.34 | 2026-04-27 | Added: Linux Desktop Qt GUI build (VRQt) - first GUI build for Linux distributed as a self-contained AppImage (~32MB, Qt6 runtime + libGL/libEGL/libGLX/libOpenGL/libGLESv2/libGLdispatch bundled inside) so users on minimal Ubuntu/Debian/Fedora can run it without installing Qt or GL system packages; tested on AWS clean Ubuntu 22.04 cloud image; build via build_ubuntu_appimage.py which is now invoked by deploy.py; Fixed: Linux Qt right-side device/filter panels rendered with white background below cards because the inner listWidget did not autoFillBackground from parent palette on Linux Qt (Windows/macOS implicitly inherit, X11/Wayland do not); explicit stylesheet on listWidget; Added: Linux installation section in user manuals (EN/KO/ZH/ES) covering AppImage chmod+run flow and dialout group note for serial/USB device access | |
| 1.18.33 | 2026-04-25 | 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.32 | 2026-04-23 | Fixed: same-port multi-client TCP - primary now runs a continuous accept loop and hands off each connection to the matching sub by IP filter (previously second connection got rejected/listened closed); Added: @IP and #keyword port filters are now mutually exclusive per bed (IP wins, keyword dropped with WARN); Added: WARN log when two beds on the same port share an identical IP filter; Added: catch-all primary keeps its single connection and rejects extras (preserves broadcast mode) | |
| 1.18.31 | 2026-04-22 | Added: drug slot shows DOSE_AMOUNT (mg etc) in preference to VOL (mL) when available; Added: DOSE_RATE / DOSE_AMOUNT montypes bound to BBraun tracks for monitor view; Added: HL7 MDC_DOSE_DRUG_DELIV_TOTAL unit picked up dynamically from UCUM | |
| 1.18.30 | 2026-04-21 | Changed: monitor view pump layout - 8 slots with CE or RATE (CE-less pumps show infused volume as small annotation); Reverted: BBraun minimal=1 (1.18.29) - was dropping data from file, monitor view redesign handles display clutter instead | |
| 1.18.29 | 2026-04-21 | Fixed: BBraun multi-rack auto-tab data drop, primary TCP churn, save_settings lock race, TCP reader spin on peer close; Added: BBraun minimal=1 option (display only drug name / rate / volume) | |
| 1.18.28 | 2026-04-20 | Fixed: vr.conf on Pi/Linux now looked up next to executable (supports /boot/firmware); deploy.py --store auto-submits MSIX to Microsoft Store | |
| 1.18.27 | 2026-04-18 | Fixed: vital file periodic flush (5000s→5s bug), Fit-to-Window icon redesigned to match navigation button style | |
| 1.18.26 | 2026-04-18 | Fixed: periodic .vital file flush interval was 5000 seconds instead of 5 (unit-bug since comment said seconds but value was milliseconds). Data now actually reaches disk every 5 s during recording instead of only on close. | |
| 1.18.25 | 2026-04-18 | Fixed: BBraun HL7 rec_timeout raised 10s→60s to prevent primary churn during DoseLink R42→R01 phase transition and long idle periods | |
| 1.18.24 | 2026-04-18 | Fixed: GUI silently exits when another recorder running (now falls back to viewer mode), .vital file association written to HKCU (no admin required, auto-updates exe path on every run) | |
| 1.18.23 | 2026-04-18 | Fixed: atof decimal-separator bug on non-English Windows locales (rates <1.0 recorded as 0 in Norwegian/German/French etc.), BBraun HL7 pump limit raised from 8 to 16 | |
| 1.18.22 | 2026-04-18 | Fixed: multi-bed TCP port bind race on restart (Mindray/BBraun HL7 auto-reconnect without manual Add device) | |
| 1.18.21 | 2026-04-18 | Fixed: port name filter (#keyword@ip) lost on save, BBraun HL7 VMD-block parsing with single dt, Fit-to-Window button sizing (MFC) and added to Qt | |
| 1.18.20 | 2026-04-15 | BBraun: comprehensive protocol parsing (dose rate, infusion time, syringe, weight, drug amounts, delivery status) | |
| 1.18.19 | 2026-04-13 | Fixed: BBraun HL7 multi-pump identification (rack serial filtering), multi-bed forwarding, port filter parsing | |
| 1.18.18 | 2026-04-10 | Added: GE alarm limits, S5 alarm/ext3/ext1 parsing | |
| 1.18.17 | 2026-04-04 | Changed: TRACE debug-only guard, show_msg for file errors, deploy sequential builds | |
| 1.18.16 | 2026-04-04 | Added: TCP port filtering (#keyword@ip), frame forwarding for multi-bed. Fixed: --debug conf file mistaken as vital file, device type ambiguity in forwarding | |
| 1.18.15 | 2026-04-04 | Changed: Port to Port Name, keyword filter AND/OR (#kw1 kw2#kw3), IP postfix matching (@10.1), MEKICS SerialFramed | |
| 1.18.14 | 2026-04-03 | Added: TCP port filtering (#keyword@ip), Fixed: devtypes not sent on boot | |
| 1.18.13 | 2026-04-02 | Changed: Covidien company name to Medtronic (BIS, INVOS) | |
| 1.18.12 | 2026-04-02 | Bug Fixed: NihonKohden EGA SpO2 waveform not displayed (channel name mapping) | |
| 1.18.11 | 2026-03-31 | Added: Mindray HL7 multi-bed separation via PV1, auto-tab creation for EGA/Mindray | |
| 1.18.10 | 2026-03-31 | Added: HL7 ZBR segment now includes ver, os, arch, dgmt | |
| 1.18.8 | 2026-03-25 | Added: --demo mode for console operation with demo signals | |
| 1.18.7 | 2026-03-25 | Added: --console mode for headless operation on Windows | |
| 1.18.6 | 2026-03-25 | Added: Mindray eGateway HL7 protocol (IHE PCD-01, HL7 v2.6 over MLLP, waveform + numeric + infusion pump) | |
| 1.18.4 | 2026-03-24 | Added: BBraun SpaceCom HL7 protocol (HL7 v2.6 over MLLP, TCP server mode) | |
| 1.18.3 | 2026-03-24 | Added: HL7 OBX-14 per-track dtstart, ZBR segment with dtcase/dtapp timestamps | |
| 1.18.2 | 2026-03-24 | Added: HL7 ZBR segment with dtcase (recording start) and dtapp (app start) timestamps | |
| 1.18.1 | 2026-03-23 | Fixed: HL7 OBX-3 format (code^device/track@srate), OBX-7 refrange (min^max), NA null sample gaps | |
| 1.17.6 | 2026-03-22 | Bug fixed: Resource ID conflicts WiX installer cleanup | |
| 1.17.5 | 2026-03-21 | Bug fixed: Vital packed file loading MFC device button icon padding | |
| 1.17.4 | 2026-03-19 | Bug fixed: NirsitON EEG 250Hz VRQt device minimize/rename | |
| 1.17.3 | 2026-03-18 | Bug fixed: BBraun protocol NirsitON protocol VRQt | |
| 1.17.2 | 2026-03-14 | Added: NirsitOn NDI protocol support. | |
| 1.17.1 | 2026-03-12 | Added: VRQt | |
| 1.16.13 | 2026-03-07 | Added: New Setup File with VRQt | |
| 1.16.12 | 2026-03-06 | Added: Vector Type Icons | |
| 1.16.10 | 2026-03-02 | Bug fixed: Autodetecting BBraun Stuffing feature. | |
| 1.16.8 | 2026-02-27 | Bug fixed: Masimo IAP protocol. | |
| 1.16.6 | 2026-02-13 | Added: Servo-u ventilator protocol | |
| 1.16.5 | 2026-02-09 | Bug Fixed: NihonKohden HL7GW Pleth waveform gain correction. | |
| 1.16.4 | 2026-01-20 | Bug Fixed: Serial communication. | |
| 1.16.3 | 2026-01-14 | Bug Fixed: Masimo IAP Protocol. | |
| 1.16.2 | 2026-01-03 | Added: BLE Support for Movesense devices on Raspberry Pi | |
| 1.16.1 | 2026-01-01 | Added: BLE Support for Movesense devices on Raspberry Pi and Linux versions. | |
| 1.16.0 | 2025-12-31 | Added: BLE Support for Movesense devices | |
| 1.15.16 | 2025-09-18 | Bug Fixed: Restoring last window position. | |
| 1.15.15 | 2025-09-05 | Added: CPU optimization Bug fixed: Masimo IAP protocol. | |
| 1.15.14 | 2025-09-04 | Added: CPU optimization | |
| 1.15.13 | 2025-09-04 | Bug Fixed: Masimo IAP Protocol. | |
| 1.15.12 | 2025-09-04 | Bug Fixed: Hardware handshake for Link+ connection. | |
| 1.15.11 | 2025-09-01 | Added: Recording alarm messages for Drager anesthesia machines | |
| 1.15.10 | 2025-07-14 | Bug Fixed: Sampling rate estimation in Medibus protocol. | |
| 1.15.9 | 2025-06-03 | Bug Fixed: EEG waveform labels from Philips monitors. | |
| 1.15.8 | 2025-05-26 | Added: EM_UPLOAD option to upload vital files to server immediately | |
| 1.15.7 | 2025-05-26 | Bug Fixed: Websocket connection error | |
| 1.15.6 | 2025-05-19 | Bug Fixed: S5 checksum error | |
| 1.15.5 | 2025-04-17 | Bug Fixed: saving the raw data | |
| 1.15.4 | 2025-04-15 | Added: Recording alarms from Philips monitor | |
| 1.15.3 | 2025-04-14 | Bug fixed: saving the raw data | |
| 1.15.2 | 2025-03-12 | Fixed: bug in opening file | |
| 1.15.1 | 2025-01-30 | Supporting for Fresenius Kabi Conox, Minor Bug fixes | |
| 1.14.13 | 2025-01-18 | Fixed bug in handling non-ascii filepath | |
| 1.14.12 | 2024-11-20 | Fixed: bug in 64bit ARM Architecture | |
| 1.14.11 | 2024-10-24 | Added: feature for updating to a specific version | |
| 1.14.10 | 2024-10-22 | Fixed: bug in json streamming | |
| 1.14.9 | 2024-10-17 | Added: montype for drug rates |
| Version | Date | Changes | |
|---|---|---|---|
| 1.19.36 | 2026-10-13 | 1.19.36 Added: Windows 에서 아이콘으로 실행해도 로그가 설정 폴더의 vr.log 에 남는다. 지금까지는 로그가 화면에 출력되는 곳이 없으면 그대로 사라져서, 장비가 요청을 거절한 이유처럼 문제를 가리는 데 필요한 기록을 현장에서 볼 방법이 없었다. 줄마다 날짜와 시각이 붙고, 10MB 를 넘으면 시작할 때 vr.log.1 로 넘긴다. --debug 로 실행하거나 출력을 다른 파일로 돌린 경우는 종전대로 그쪽으로 나간다. Fixed: VitalRecorder Qt 에서 트랙 목록의 세로 스크롤바를 마우스로 움직일 수 없던 문제. 스크롤바가 그려지기만 하고 마우스 입력을 받지 않아 방향키로만 이동할 수 있었다. 이제 막대를 끌거나 빈 곳을 눌러 이동할 수 있다. Fixed: VitalRecorder Qt 에서 장비 편집 창이 검은 바탕에 검은 글씨로 나와 읽기 어렵던 문제. 장비 추가 창과 같은 밝은 모양으로 나온다. 창의 최소 폭도 넓혀 Intellivue 파형 선택 항목이 잘리지 않는다. Fixed: VitalRecorder Qt 에서 장비 설정을 바꾸면 오른쪽 장비 목록에 빈 칸이 남던 문제. 그 빈 칸의 편집 버튼을 누르면 같은 장비가 하나 더 생기기도 했다. (Nord University 제보) | |
| 1.19.35 | 2026-10-08 | 1.19.35 Fixed: Edwards 혈역학 모니터(HemoSphere 등)의 값이 Philips Intellivue 를 거쳐 들어올 때 연속 심박출량(CO_CTS), 연속 심장지수(CI_CTS), 일회박출량 변이(VOL_BLD_STROKE_VAR, SVV)가 웹 모니터링 화면에 나오지 않던 문제. 값은 정상으로 수집·저장되고 있었지만, 웹 화면이 어느 칸에 표시할지 정하는 분류(모니터 유형)가 지정되지 않아 표시 대상에서 빠졌다. 이제 각각 CO, CI, SVV 로 분류해 다른 장비의 같은 값과 같은 자리에 나온다. 이미 만들어진 녹화 파일에는 소급되지 않는다. | |
| 1.19.34 | 2026-10-06 | 1.19.34 Fixed: Nihon Kohden Vismo PVM-4700 시리즈(PVM-4761 등)와 구형 BSM-3000/6000, CSM-1500/1700 에서 시리얼로 아무 값도 들어오지 않던 문제. VR 은 확장 수치·심방세동까지 받으려고 ?D;EALL 요청의 Option 을 2 로 보내는데, 이 기종들은 규격상 Option 0~1 만 받아들여 매 요청을 !E;04(인자 미지원)로 거절했고, VR 은 그 거절을 읽지 않아 1초마다 같은 요청을 되풀이했다 (강남차병원 PVM-4761 실측 2026-09-07: 5분 동안 거절 응답만 300회). 이제 거절을 받으면 Option 을 한 단계씩 내려 다시 요청하고(2→1→0), 부정맥 비트 해석표도 그 Option 에 맞는 표(규격 Table 7/8/9)로 바꾼다. 어느 Option 으로 내려갔는지 로그에 남긴다. 측정하지 않은 파라미터에 대한 응답(!E;80/81, 예: NIBP 미측정)은 정상이므로 조용히 넘기고, 그 밖의 에러 응답은 60초에 한 번 로그에 남긴다. 가짜 모니터로 2→1 폴백과 HR/PR/SpO2/RR/체온 수집을 확인했다. 파형은 이 경로(RS-232C)로는 규격상 오지 않는다. PVM-4700 은 QI-470P 인터페이스의 RS-232C 커넥터와 YS-089P 계열 케이블을 쓴다 (0614-905457H). Added: 장비 목록에 Nihon Kohden : PVM 을 따로 두었다. BSM 과 같은 통신 규격이지만 PVM-4700 은 처음부터 지원하는 범위로 요청하므로 거절 응답 없이 바로 값이 들어온다. vr.conf 에는 type=PVM 으로 적는다. 기존에 BSM 으로 설정한 PVM 도 위의 자동 하향 덕분에 그대로 동작한다. | |
| 1.19.33 | 2026-10-05 | 1.19.33 Added: Philips Intellivue 가 VR 의 요청을 거절하면 로그에 남긴다. 어떤 요청(파형 폴, 파형 우선순위 목록, 수치 폴, 환자정보 등)이 어떤 이유(ACCESS_DENIED, SET_LIST_ERROR, INVALID_ARGUMENT_VALUE 등)로 거절됐는지 적는다. 종전에는 이 응답이 디버그 빌드에서만 보이는 흔적으로 처리돼 현장 로그에 아무것도 남지 않았고, 그래서 “수치는 들어오는데 파형만 없다” 는 상황에서 VR 이 요청을 안 한 것인지 모니터가 거부한 것인지를 로그만으로 가릴 수 없었다 (Nord University 실험실, 1.19.22~1.19.32 동일 증상). Added: 파형 폴을 보내는데 파형 묶음이 30초 넘게 돌아오지 않으면 5분에 한 번 경고를 남긴다. 요청한 파형 목록을 함께 적어 모니터 Data Export 설정과 바로 대조할 수 있다. 같은 코드의 거절은 60초에 한 번만 적는다. 1.19.32 Added: Philips Intellivue 의 파형 자동 추가를 끌 수 있다 (auto_wavs=0, 장비 설정 창의 체크박스). 종전에는 설정 창에서 고른 파형에 더해 수치가 들어오는 파형(ECG 한 리드, PLETH, ABP/ART/AoP, CVP, ICP, CO2, 기도압·유량)을 자동으로 보탰고 이것을 막을 방법이 없었다. 침습압이 여럿인 실험실 구성에서는 그 합이 시리얼 회선이 감당할 양을 넘고, 그러면 모니터는 파형을 일부만 줄이는 것이 아니라 통째로 멈춘다 — 수치는 계속 들어오는데 파형만 몇 초 만에 사라지는 모습이 된다 (Nord University 실측: 파형 8종이 5초 뒤 멎음, 설정 창에 적힌 한계 “ECG 3개 또는 비ECG 8개” 를 ECG 2개+6개로 넘긴 경우). 이제 체크를 끄면 고른 것만 요청한다. 기본값은 종전과 같이 자동 추가다. Added: Intellivue 에 요청하는 파형 목록이 바뀔 때마다 로그에 남긴다. 파형이 안 들어올 때 요청을 안 한 것인지, 요청했는데 모니터가 안 주는 것인지를 로그만으로 가를 수 있다. 1.19.31 Fixed: Windows 에서 시리얼 장비의 값이 두 번에 한 번씩 약 0.23초 늦게 기록되던 문제. 1초에 한 번 보내는 SyncPulseSerial(동기 펄스 생성기)에서 펄스 간격이 1초가 아니라 0.77초와 1.23초로 번갈아 기록됐다 (Nord University AniLab 실측 9/5·9/10, 두 펄스를 합하면 정확히 2.000초). 장비 쪽은 정확했다 — 같은 회선을 시리얼 터미널로 받으면 ±1ms 로 1초마다 들어온다. 원인은 VR 이 문자 도착을 기다리기 전에 매번 100ms 를 쉬었고, 그 100ms 안에 들어온 데이터는 Windows 가 알려주지 않아 200ms 뒤 확인 때에야 읽혔다는 것이다. 이 대기 한 바퀴(약 310ms)와 1초 주기가 맞물려 한 프레임은 즉시, 다음 프레임은 230ms 늦게 읽는 패턴이 고정됐다. 이제 기다리기 전에 수신 버퍼를 먼저 확인하고 쉬지 않는다. 도착 즉시 읽으므로 기록 시각의 오차는 USB 폴링 수준(수 ms)만 남는다. 초당 수십 바이트 이상 보내는 일반 환자 모니터에서는 이 지연이 파형 시각에 묻혀 드러나지 않았다. Note: 1.19.25 설명 정정 — Windows 수신 대기가 원인이 아니라고 적었으나(가상 직렬 포트 A/B 에서 재현되지 않았다), 실제 USB 시리얼에서는 위와 같이 재현된다. 가상 포트는 문자 도착 알림 방식이 달라 시험이 빗나갔다. 1.19.30 Fixed: 기기 시각이 크게 어긋났을 때 저절로 맞춰지지 않고 그대로 유지되던 문제. 원주세브란스 EICU09 에서 실제보다 55분 늦은 상태가 계속됐고, 그 결과 해당 병상이 화면에서 자료 없음으로 보였다 (웹 화면은 마지막으로 자료가 들어온 지 1분이 넘은 병상을 끊긴 것으로 본다). 서버가 알려주는 시각으로 맞추는 기능 자체는 동작하고 있었다. 다만 녹화 중에는 파형에 공백이 생기지 않도록 시계를 한 번에 옮기지 않고 아주 천천히 당기도록 돼 있었는데, 그 속도가 하루에 약 43초라 55분을 되돌리려면 두 달이 넘게 걸린다. 천천히 당기는 방식은 이미 가까운 시계를 가깝게 유지하는 수단이지 벌어진 것을 따라잡는 수단이 아니라는 점이 코드에 반영돼 있지 않았다. 이제 두 경우에는 녹화 중이라도 한 번에 맞춘다 — 차이가 1분을 넘어 천천히 당겨서는 따라잡을 수 없을 때, 그리고 SAVE_FILE=0 으로 파일을 만들지 않아 지켜야 할 기록이 애초에 없을 때. 어느 쪽이든 맞춘 이유를 로그에 남긴다. ⚠ 시계를 따로 갖고 있지 않은 장비(라즈베리파이)는 전원이 꺼지면 시각을 잃는다. 기관의 시각 서버(NTP)를 함께 물려 두면 이 기능에 기대지 않아도 된다. 1.19.29 Added: 녹화 파일을 만들지 않고 웹 모니터링만 하는 운용 방식. vr.conf 에 SAVE_FILE=0 을 넣으면 .vital 파일을 만들지 않는다. 무선 대역폭이 부족해 파일을 남기지 않기로 한 기관을 위한 것이다. 종전에는 저장 폴더를 없는 경로로 지정해 ‘열기 실패’ 로 같은 효과를 내는 편법이 쓰였는데, 그 방법은 파일을 만들 때마다 오류가 로그에 쌓이고 화면에는 녹화 중으로 보였다. ⚠ 이 방식에서는 네트워크가 끊긴 구간의 자료가 영구히 사라진다 — 지금까지는 파일이 전송 실패의 완충재 역할을 했다. Changed: 웹 모니터링 전송량이 25~35% 줄었다. 파형 값을 늘 소수점 셋째 자리까지 보내고 있었는데, 화면은 그 트랙의 표시 범위 안에서 그리므로 범위의 1% 보다 고운 눈금은 어차피 구분되지 않는다 (심전도는 표시 범위가 4 mV 인데 0.001 단위로 보내 화면이 나타낼 수 있는 것의 40배를 보내고 있었다). 게다가 그 여분의 자릿수는 규칙성이 없어 압축도 되지 않아, 전송량의 4분의 1 이상을 차지했다 (중환자실 3개 병상 실측 26~35%). 이제 표시 범위에 맞춰 자릿수를 정한다. 값의 단위와 의미는 그대로이고 녹화 파일에는 원래 정밀도로 저장되므로 화면과 파일 모두 달라지는 것이 없으며, 서버나 웹 화면을 함께 고칠 필요도 없다. 예전 방식이 필요하면 WEB_WAV_PREC=3 으로 고정할 수 있다. Fixed: 녹화 파일을 만들지 못했을 때 아무 알림 없이 지나가던 문제. 디스크가 가득 찼거나 저장 폴더에 쓸 수 없으면 파일이 만들어지지 않는데, 화면에는 ‘녹화 중’ 으로 표시되고 자료는 조용히 버려졌다. 이제 로그에 경고를 남긴다. 1.19.28 Fixed: USB 로 연결한 장비의 수집이 어느 순간 조용히 멎고, 프로그램을 다시 켜야만 돌아오던 문제. 전원이 잠깐 흔들리면 USB 장비가 스스로 연결을 다시 맺는데 그때 운영체제가 새 포트 번호를 붙인다 (예: ttyACM0 이 ttyACM1 로). VR 은 처음 열었던 번호를 계속 붙들고 있어서, 장비도 케이블도 멀쩡한데 값만 들어오지 않는다 — 화면상 연결 상태도 정상으로 보여 눈으로는 알 수 없었다 (수술방의 Link+ 주입펌프에서 확인). Added: 포트 자리에 포트 번호 대신 장비의 고유 신원을 적을 수 있다. 형식은 20d0:0002 처럼 네 자리 두 개이며, 이 값은 장비에 박혀 있어 다시 연결되어도 바뀌지 않는다. 이렇게 적어 두면 VR 이 포트를 열 때마다 그 신원을 가진 자리를 다시 찾으므로, 번호가 바뀌어도 저절로 따라간다. 기존처럼 포트 번호를 적어 둔 곳은 그대로 동작한다. Changed: Link+ 는 포트를 적지 않으면 이 신원을 기본값으로 쓴다. 꽂기만 하면 잡히고, 다시 연결되어도 따라간다. 포트를 직접 적어 둔 곳은 적은 값이 우선이다. Changed: 자동 판별이 USB 신원을 먼저 본다. 고유 신원으로 자기를 밝히는 장비는 통신 속도·형식을 재 볼 필요가 없으므로 조용한 회선에서 72초가 걸리던 측정을 건너뛰고 바로 확정한다. 이렇게 확정한 장비는 포트를 번호가 아니라 신원으로 기록한다. 여러 장비가 함께 쓰는 범용 USB 변환 케이블(FTDI 등)은 신원이 케이블의 것이지 장비의 것이 아니므로 종전대로 측정해서 판별한다. 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.32 | 2026-10-05 | 1.19.32 Added: Philips Intellivue 의 파형 자동 추가를 끌 수 있다 (auto_wavs=0, 장비 설정 창의 체크박스). 종전에는 설정 창에서 고른 파형에 더해 수치가 들어오는 파형(ECG 한 리드, PLETH, ABP/ART/AoP, CVP, ICP, CO2, 기도압·유량)을 자동으로 보탰고 이것을 막을 방법이 없었다. 침습압이 여럿인 실험실 구성에서는 그 합이 시리얼 회선이 감당할 양을 넘고, 그러면 모니터는 파형을 일부만 줄이는 것이 아니라 통째로 멈춘다 — 수치는 계속 들어오는데 파형만 몇 초 만에 사라지는 모습이 된다 (Nord University 실측: 파형 8종이 5초 뒤 멎음, 설정 창에 적힌 한계 “ECG 3개 또는 비ECG 8개” 를 ECG 2개+6개로 넘긴 경우). 이제 체크를 끄면 고른 것만 요청한다. 기본값은 종전과 같이 자동 추가다. Added: Intellivue 에 요청하는 파형 목록이 바뀔 때마다 로그에 남긴다. 파형이 안 들어올 때 요청을 안 한 것인지, 요청했는데 모니터가 안 주는 것인지를 로그만으로 가를 수 있다. 1.19.31 Fixed: Windows 에서 시리얼 장비의 값이 두 번에 한 번씩 약 0.23초 늦게 기록되던 문제. 1초에 한 번 보내는 SyncPulseSerial(동기 펄스 생성기)에서 펄스 간격이 1초가 아니라 0.77초와 1.23초로 번갈아 기록됐다 (Nord University AniLab 실측 9/5·9/10, 두 펄스를 합하면 정확히 2.000초). 장비 쪽은 정확했다 — 같은 회선을 시리얼 터미널로 받으면 ±1ms 로 1초마다 들어온다. 원인은 VR 이 문자 도착을 기다리기 전에 매번 100ms 를 쉬었고, 그 100ms 안에 들어온 데이터는 Windows 가 알려주지 않아 200ms 뒤 확인 때에야 읽혔다는 것이다. 이 대기 한 바퀴(약 310ms)와 1초 주기가 맞물려 한 프레임은 즉시, 다음 프레임은 230ms 늦게 읽는 패턴이 고정됐다. 이제 기다리기 전에 수신 버퍼를 먼저 확인하고 쉬지 않는다. 도착 즉시 읽으므로 기록 시각의 오차는 USB 폴링 수준(수 ms)만 남는다. 초당 수십 바이트 이상 보내는 일반 환자 모니터에서는 이 지연이 파형 시각에 묻혀 드러나지 않았다. Note: 1.19.25 설명 정정 — Windows 수신 대기가 원인이 아니라고 적었으나(가상 직렬 포트 A/B 에서 재현되지 않았다), 실제 USB 시리얼에서는 위와 같이 재현된다. 가상 포트는 문자 도착 알림 방식이 달라 시험이 빗나갔다. 1.19.30 Fixed: 기기 시각이 크게 어긋났을 때 저절로 맞춰지지 않고 그대로 유지되던 문제. 원주세브란스 EICU09 에서 실제보다 55분 늦은 상태가 계속됐고, 그 결과 해당 병상이 화면에서 자료 없음으로 보였다 (웹 화면은 마지막으로 자료가 들어온 지 1분이 넘은 병상을 끊긴 것으로 본다). 서버가 알려주는 시각으로 맞추는 기능 자체는 동작하고 있었다. 다만 녹화 중에는 파형에 공백이 생기지 않도록 시계를 한 번에 옮기지 않고 아주 천천히 당기도록 돼 있었는데, 그 속도가 하루에 약 43초라 55분을 되돌리려면 두 달이 넘게 걸린다. 천천히 당기는 방식은 이미 가까운 시계를 가깝게 유지하는 수단이지 벌어진 것을 따라잡는 수단이 아니라는 점이 코드에 반영돼 있지 않았다. 이제 두 경우에는 녹화 중이라도 한 번에 맞춘다 — 차이가 1분을 넘어 천천히 당겨서는 따라잡을 수 없을 때, 그리고 SAVE_FILE=0 으로 파일을 만들지 않아 지켜야 할 기록이 애초에 없을 때. 어느 쪽이든 맞춘 이유를 로그에 남긴다. ⚠ 시계를 따로 갖고 있지 않은 장비(라즈베리파이)는 전원이 꺼지면 시각을 잃는다. 기관의 시각 서버(NTP)를 함께 물려 두면 이 기능에 기대지 않아도 된다. 1.19.29 Added: 녹화 파일을 만들지 않고 웹 모니터링만 하는 운용 방식. vr.conf 에 SAVE_FILE=0 을 넣으면 .vital 파일을 만들지 않는다. 무선 대역폭이 부족해 파일을 남기지 않기로 한 기관을 위한 것이다. 종전에는 저장 폴더를 없는 경로로 지정해 ‘열기 실패’ 로 같은 효과를 내는 편법이 쓰였는데, 그 방법은 파일을 만들 때마다 오류가 로그에 쌓이고 화면에는 녹화 중으로 보였다. ⚠ 이 방식에서는 네트워크가 끊긴 구간의 자료가 영구히 사라진다 — 지금까지는 파일이 전송 실패의 완충재 역할을 했다. Changed: 웹 모니터링 전송량이 25~35% 줄었다. 파형 값을 늘 소수점 셋째 자리까지 보내고 있었는데, 화면은 그 트랙의 표시 범위 안에서 그리므로 범위의 1% 보다 고운 눈금은 어차피 구분되지 않는다 (심전도는 표시 범위가 4 mV 인데 0.001 단위로 보내 화면이 나타낼 수 있는 것의 40배를 보내고 있었다). 게다가 그 여분의 자릿수는 규칙성이 없어 압축도 되지 않아, 전송량의 4분의 1 이상을 차지했다 (중환자실 3개 병상 실측 26~35%). 이제 표시 범위에 맞춰 자릿수를 정한다. 값의 단위와 의미는 그대로이고 녹화 파일에는 원래 정밀도로 저장되므로 화면과 파일 모두 달라지는 것이 없으며, 서버나 웹 화면을 함께 고칠 필요도 없다. 예전 방식이 필요하면 WEB_WAV_PREC=3 으로 고정할 수 있다. Fixed: 녹화 파일을 만들지 못했을 때 아무 알림 없이 지나가던 문제. 디스크가 가득 찼거나 저장 폴더에 쓸 수 없으면 파일이 만들어지지 않는데, 화면에는 ‘녹화 중’ 으로 표시되고 자료는 조용히 버려졌다. 이제 로그에 경고를 남긴다. 1.19.28 Fixed: USB 로 연결한 장비의 수집이 어느 순간 조용히 멎고, 프로그램을 다시 켜야만 돌아오던 문제. 전원이 잠깐 흔들리면 USB 장비가 스스로 연결을 다시 맺는데 그때 운영체제가 새 포트 번호를 붙인다 (예: ttyACM0 이 ttyACM1 로). VR 은 처음 열었던 번호를 계속 붙들고 있어서, 장비도 케이블도 멀쩡한데 값만 들어오지 않는다 — 화면상 연결 상태도 정상으로 보여 눈으로는 알 수 없었다 (수술방의 Link+ 주입펌프에서 확인). Added: 포트 자리에 포트 번호 대신 장비의 고유 신원을 적을 수 있다. 형식은 20d0:0002 처럼 네 자리 두 개이며, 이 값은 장비에 박혀 있어 다시 연결되어도 바뀌지 않는다. 이렇게 적어 두면 VR 이 포트를 열 때마다 그 신원을 가진 자리를 다시 찾으므로, 번호가 바뀌어도 저절로 따라간다. 기존처럼 포트 번호를 적어 둔 곳은 그대로 동작한다. Changed: Link+ 는 포트를 적지 않으면 이 신원을 기본값으로 쓴다. 꽂기만 하면 잡히고, 다시 연결되어도 따라간다. 포트를 직접 적어 둔 곳은 적은 값이 우선이다. Changed: 자동 판별이 USB 신원을 먼저 본다. 고유 신원으로 자기를 밝히는 장비는 통신 속도·형식을 재 볼 필요가 없으므로 조용한 회선에서 72초가 걸리던 측정을 건너뛰고 바로 확정한다. 이렇게 확정한 장비는 포트를 번호가 아니라 신원으로 기록한다. 여러 장비가 함께 쓰는 범용 USB 변환 케이블(FTDI 등)은 신원이 케이블의 것이지 장비의 것이 아니므로 종전대로 측정해서 판별한다. 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.31 | 2026-09-27 | 1.19.31 Fixed: Windows 에서 시리얼 장비의 값이 두 번에 한 번씩 약 0.23초 늦게 기록되던 문제. 1초에 한 번 보내는 SyncPulseSerial(동기 펄스 생성기)에서 펄스 간격이 1초가 아니라 0.77초와 1.23초로 번갈아 기록됐다 (Nord University AniLab 실측 9/5·9/10, 두 펄스를 합하면 정확히 2.000초). 장비 쪽은 정확했다 — 같은 회선을 시리얼 터미널로 받으면 ±1ms 로 1초마다 들어온다. 원인은 VR 이 문자 도착을 기다리기 전에 매번 100ms 를 쉬었고, 그 100ms 안에 들어온 데이터는 Windows 가 알려주지 않아 200ms 뒤 확인 때에야 읽혔다는 것이다. 이 대기 한 바퀴(약 310ms)와 1초 주기가 맞물려 한 프레임은 즉시, 다음 프레임은 230ms 늦게 읽는 패턴이 고정됐다. 이제 기다리기 전에 수신 버퍼를 먼저 확인하고 쉬지 않는다. 도착 즉시 읽으므로 기록 시각의 오차는 USB 폴링 수준(수 ms)만 남는다. 초당 수십 바이트 이상 보내는 일반 환자 모니터에서는 이 지연이 파형 시각에 묻혀 드러나지 않았다. Note: 1.19.25 설명 정정 — Windows 수신 대기가 원인이 아니라고 적었으나(가상 직렬 포트 A/B 에서 재현되지 않았다), 실제 USB 시리얼에서는 위와 같이 재현된다. 가상 포트는 문자 도착 알림 방식이 달라 시험이 빗나갔다. 1.19.30 Fixed: 기기 시각이 크게 어긋났을 때 저절로 맞춰지지 않고 그대로 유지되던 문제. 원주세브란스 EICU09 에서 실제보다 55분 늦은 상태가 계속됐고, 그 결과 해당 병상이 화면에서 자료 없음으로 보였다 (웹 화면은 마지막으로 자료가 들어온 지 1분이 넘은 병상을 끊긴 것으로 본다). 서버가 알려주는 시각으로 맞추는 기능 자체는 동작하고 있었다. 다만 녹화 중에는 파형에 공백이 생기지 않도록 시계를 한 번에 옮기지 않고 아주 천천히 당기도록 돼 있었는데, 그 속도가 하루에 약 43초라 55분을 되돌리려면 두 달이 넘게 걸린다. 천천히 당기는 방식은 이미 가까운 시계를 가깝게 유지하는 수단이지 벌어진 것을 따라잡는 수단이 아니라는 점이 코드에 반영돼 있지 않았다. 이제 두 경우에는 녹화 중이라도 한 번에 맞춘다 — 차이가 1분을 넘어 천천히 당겨서는 따라잡을 수 없을 때, 그리고 SAVE_FILE=0 으로 파일을 만들지 않아 지켜야 할 기록이 애초에 없을 때. 어느 쪽이든 맞춘 이유를 로그에 남긴다. ⚠ 시계를 따로 갖고 있지 않은 장비(라즈베리파이)는 전원이 꺼지면 시각을 잃는다. 기관의 시각 서버(NTP)를 함께 물려 두면 이 기능에 기대지 않아도 된다. 1.19.29 Added: 녹화 파일을 만들지 않고 웹 모니터링만 하는 운용 방식. vr.conf 에 SAVE_FILE=0 을 넣으면 .vital 파일을 만들지 않는다. 무선 대역폭이 부족해 파일을 남기지 않기로 한 기관을 위한 것이다. 종전에는 저장 폴더를 없는 경로로 지정해 ‘열기 실패’ 로 같은 효과를 내는 편법이 쓰였는데, 그 방법은 파일을 만들 때마다 오류가 로그에 쌓이고 화면에는 녹화 중으로 보였다. ⚠ 이 방식에서는 네트워크가 끊긴 구간의 자료가 영구히 사라진다 — 지금까지는 파일이 전송 실패의 완충재 역할을 했다. Changed: 웹 모니터링 전송량이 25~35% 줄었다. 파형 값을 늘 소수점 셋째 자리까지 보내고 있었는데, 화면은 그 트랙의 표시 범위 안에서 그리므로 범위의 1% 보다 고운 눈금은 어차피 구분되지 않는다 (심전도는 표시 범위가 4 mV 인데 0.001 단위로 보내 화면이 나타낼 수 있는 것의 40배를 보내고 있었다). 게다가 그 여분의 자릿수는 규칙성이 없어 압축도 되지 않아, 전송량의 4분의 1 이상을 차지했다 (중환자실 3개 병상 실측 26~35%). 이제 표시 범위에 맞춰 자릿수를 정한다. 값의 단위와 의미는 그대로이고 녹화 파일에는 원래 정밀도로 저장되므로 화면과 파일 모두 달라지는 것이 없으며, 서버나 웹 화면을 함께 고칠 필요도 없다. 예전 방식이 필요하면 WEB_WAV_PREC=3 으로 고정할 수 있다. Fixed: 녹화 파일을 만들지 못했을 때 아무 알림 없이 지나가던 문제. 디스크가 가득 찼거나 저장 폴더에 쓸 수 없으면 파일이 만들어지지 않는데, 화면에는 ‘녹화 중’ 으로 표시되고 자료는 조용히 버려졌다. 이제 로그에 경고를 남긴다. 1.19.28 Fixed: USB 로 연결한 장비의 수집이 어느 순간 조용히 멎고, 프로그램을 다시 켜야만 돌아오던 문제. 전원이 잠깐 흔들리면 USB 장비가 스스로 연결을 다시 맺는데 그때 운영체제가 새 포트 번호를 붙인다 (예: ttyACM0 이 ttyACM1 로). VR 은 처음 열었던 번호를 계속 붙들고 있어서, 장비도 케이블도 멀쩡한데 값만 들어오지 않는다 — 화면상 연결 상태도 정상으로 보여 눈으로는 알 수 없었다 (수술방의 Link+ 주입펌프에서 확인). Added: 포트 자리에 포트 번호 대신 장비의 고유 신원을 적을 수 있다. 형식은 20d0:0002 처럼 네 자리 두 개이며, 이 값은 장비에 박혀 있어 다시 연결되어도 바뀌지 않는다. 이렇게 적어 두면 VR 이 포트를 열 때마다 그 신원을 가진 자리를 다시 찾으므로, 번호가 바뀌어도 저절로 따라간다. 기존처럼 포트 번호를 적어 둔 곳은 그대로 동작한다. Changed: Link+ 는 포트를 적지 않으면 이 신원을 기본값으로 쓴다. 꽂기만 하면 잡히고, 다시 연결되어도 따라간다. 포트를 직접 적어 둔 곳은 적은 값이 우선이다. Changed: 자동 판별이 USB 신원을 먼저 본다. 고유 신원으로 자기를 밝히는 장비는 통신 속도·형식을 재 볼 필요가 없으므로 조용한 회선에서 72초가 걸리던 측정을 건너뛰고 바로 확정한다. 이렇게 확정한 장비는 포트를 번호가 아니라 신원으로 기록한다. 여러 장비가 함께 쓰는 범용 USB 변환 케이블(FTDI 등)은 신원이 케이블의 것이지 장비의 것이 아니므로 종전대로 측정해서 판별한다. 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.30 | 2026-09-16 | 1.19.30 Fixed: 기기 시각이 크게 어긋났을 때 저절로 맞춰지지 않고 그대로 유지되던 문제. 원주세브란스 EICU09 에서 실제보다 55분 늦은 상태가 계속됐고, 그 결과 해당 병상이 화면에서 자료 없음으로 보였다 (웹 화면은 마지막으로 자료가 들어온 지 1분이 넘은 병상을 끊긴 것으로 본다). 서버가 알려주는 시각으로 맞추는 기능 자체는 동작하고 있었다. 다만 녹화 중에는 파형에 공백이 생기지 않도록 시계를 한 번에 옮기지 않고 아주 천천히 당기도록 돼 있었는데, 그 속도가 하루에 약 43초라 55분을 되돌리려면 두 달이 넘게 걸린다. 천천히 당기는 방식은 이미 가까운 시계를 가깝게 유지하는 수단이지 벌어진 것을 따라잡는 수단이 아니라는 점이 코드에 반영돼 있지 않았다. 이제 두 경우에는 녹화 중이라도 한 번에 맞춘다 — 차이가 1분을 넘어 천천히 당겨서는 따라잡을 수 없을 때, 그리고 SAVE_FILE=0 으로 파일을 만들지 않아 지켜야 할 기록이 애초에 없을 때. 어느 쪽이든 맞춘 이유를 로그에 남긴다. ⚠ 시계를 따로 갖고 있지 않은 장비(라즈베리파이)는 전원이 꺼지면 시각을 잃는다. 기관의 시각 서버(NTP)를 함께 물려 두면 이 기능에 기대지 않아도 된다. 1.19.29 Added: 녹화 파일을 만들지 않고 웹 모니터링만 하는 운용 방식. vr.conf 에 SAVE_FILE=0 을 넣으면 .vital 파일을 만들지 않는다. 무선 대역폭이 부족해 파일을 남기지 않기로 한 기관을 위한 것이다. 종전에는 저장 폴더를 없는 경로로 지정해 ‘열기 실패’ 로 같은 효과를 내는 편법이 쓰였는데, 그 방법은 파일을 만들 때마다 오류가 로그에 쌓이고 화면에는 녹화 중으로 보였다. ⚠ 이 방식에서는 네트워크가 끊긴 구간의 자료가 영구히 사라진다 — 지금까지는 파일이 전송 실패의 완충재 역할을 했다. Changed: 웹 모니터링 전송량이 25~35% 줄었다. 파형 값을 늘 소수점 셋째 자리까지 보내고 있었는데, 화면은 그 트랙의 표시 범위 안에서 그리므로 범위의 1% 보다 고운 눈금은 어차피 구분되지 않는다 (심전도는 표시 범위가 4 mV 인데 0.001 단위로 보내 화면이 나타낼 수 있는 것의 40배를 보내고 있었다). 게다가 그 여분의 자릿수는 규칙성이 없어 압축도 되지 않아, 전송량의 4분의 1 이상을 차지했다 (중환자실 3개 병상 실측 26~35%). 이제 표시 범위에 맞춰 자릿수를 정한다. 값의 단위와 의미는 그대로이고 녹화 파일에는 원래 정밀도로 저장되므로 화면과 파일 모두 달라지는 것이 없으며, 서버나 웹 화면을 함께 고칠 필요도 없다. 예전 방식이 필요하면 WEB_WAV_PREC=3 으로 고정할 수 있다. Fixed: 녹화 파일을 만들지 못했을 때 아무 알림 없이 지나가던 문제. 디스크가 가득 찼거나 저장 폴더에 쓸 수 없으면 파일이 만들어지지 않는데, 화면에는 ‘녹화 중’ 으로 표시되고 자료는 조용히 버려졌다. 이제 로그에 경고를 남긴다. 1.19.28 Fixed: USB 로 연결한 장비의 수집이 어느 순간 조용히 멎고, 프로그램을 다시 켜야만 돌아오던 문제. 전원이 잠깐 흔들리면 USB 장비가 스스로 연결을 다시 맺는데 그때 운영체제가 새 포트 번호를 붙인다 (예: ttyACM0 이 ttyACM1 로). VR 은 처음 열었던 번호를 계속 붙들고 있어서, 장비도 케이블도 멀쩡한데 값만 들어오지 않는다 — 화면상 연결 상태도 정상으로 보여 눈으로는 알 수 없었다 (수술방의 Link+ 주입펌프에서 확인). Added: 포트 자리에 포트 번호 대신 장비의 고유 신원을 적을 수 있다. 형식은 20d0:0002 처럼 네 자리 두 개이며, 이 값은 장비에 박혀 있어 다시 연결되어도 바뀌지 않는다. 이렇게 적어 두면 VR 이 포트를 열 때마다 그 신원을 가진 자리를 다시 찾으므로, 번호가 바뀌어도 저절로 따라간다. 기존처럼 포트 번호를 적어 둔 곳은 그대로 동작한다. Changed: Link+ 는 포트를 적지 않으면 이 신원을 기본값으로 쓴다. 꽂기만 하면 잡히고, 다시 연결되어도 따라간다. 포트를 직접 적어 둔 곳은 적은 값이 우선이다. Changed: 자동 판별이 USB 신원을 먼저 본다. 고유 신원으로 자기를 밝히는 장비는 통신 속도·형식을 재 볼 필요가 없으므로 조용한 회선에서 72초가 걸리던 측정을 건너뛰고 바로 확정한다. 이렇게 확정한 장비는 포트를 번호가 아니라 신원으로 기록한다. 여러 장비가 함께 쓰는 범용 USB 변환 케이블(FTDI 등)은 신원이 케이블의 것이지 장비의 것이 아니므로 종전대로 측정해서 판별한다. 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.29 | 2026-09-15 | 1.19.29 Added: 녹화 파일을 만들지 않고 웹 모니터링만 하는 운용 방식. vr.conf 에 SAVE_FILE=0 을 넣으면 .vital 파일을 만들지 않는다. 무선 대역폭이 부족해 파일을 남기지 않기로 한 기관을 위한 것이다. 종전에는 저장 폴더를 없는 경로로 지정해 ‘열기 실패’ 로 같은 효과를 내는 편법이 쓰였는데, 그 방법은 파일을 만들 때마다 오류가 로그에 쌓이고 화면에는 녹화 중으로 보였다. ⚠ 이 방식에서는 네트워크가 끊긴 구간의 자료가 영구히 사라진다 — 지금까지는 파일이 전송 실패의 완충재 역할을 했다. Changed: 웹 모니터링 전송량이 25~35% 줄었다. 파형 값을 늘 소수점 셋째 자리까지 보내고 있었는데, 화면은 그 트랙의 표시 범위 안에서 그리므로 범위의 1% 보다 고운 눈금은 어차피 구분되지 않는다 (심전도는 표시 범위가 4 mV 인데 0.001 단위로 보내 화면이 나타낼 수 있는 것의 40배를 보내고 있었다). 게다가 그 여분의 자릿수는 규칙성이 없어 압축도 되지 않아, 전송량의 4분의 1 이상을 차지했다 (중환자실 3개 병상 실측 26~35%). 이제 표시 범위에 맞춰 자릿수를 정한다. 값의 단위와 의미는 그대로이고 녹화 파일에는 원래 정밀도로 저장되므로 화면과 파일 모두 달라지는 것이 없으며, 서버나 웹 화면을 함께 고칠 필요도 없다. 예전 방식이 필요하면 WEB_WAV_PREC=3 으로 고정할 수 있다. Fixed: 녹화 파일을 만들지 못했을 때 아무 알림 없이 지나가던 문제. 디스크가 가득 찼거나 저장 폴더에 쓸 수 없으면 파일이 만들어지지 않는데, 화면에는 ‘녹화 중’ 으로 표시되고 자료는 조용히 버려졌다. 이제 로그에 경고를 남긴다. 1.19.28 Fixed: USB 로 연결한 장비의 수집이 어느 순간 조용히 멎고, 프로그램을 다시 켜야만 돌아오던 문제. 전원이 잠깐 흔들리면 USB 장비가 스스로 연결을 다시 맺는데 그때 운영체제가 새 포트 번호를 붙인다 (예: ttyACM0 이 ttyACM1 로). VR 은 처음 열었던 번호를 계속 붙들고 있어서, 장비도 케이블도 멀쩡한데 값만 들어오지 않는다 — 화면상 연결 상태도 정상으로 보여 눈으로는 알 수 없었다 (수술방의 Link+ 주입펌프에서 확인). Added: 포트 자리에 포트 번호 대신 장비의 고유 신원을 적을 수 있다. 형식은 20d0:0002 처럼 네 자리 두 개이며, 이 값은 장비에 박혀 있어 다시 연결되어도 바뀌지 않는다. 이렇게 적어 두면 VR 이 포트를 열 때마다 그 신원을 가진 자리를 다시 찾으므로, 번호가 바뀌어도 저절로 따라간다. 기존처럼 포트 번호를 적어 둔 곳은 그대로 동작한다. Changed: Link+ 는 포트를 적지 않으면 이 신원을 기본값으로 쓴다. 꽂기만 하면 잡히고, 다시 연결되어도 따라간다. 포트를 직접 적어 둔 곳은 적은 값이 우선이다. Changed: 자동 판별이 USB 신원을 먼저 본다. 고유 신원으로 자기를 밝히는 장비는 통신 속도·형식을 재 볼 필요가 없으므로 조용한 회선에서 72초가 걸리던 측정을 건너뛰고 바로 확정한다. 이렇게 확정한 장비는 포트를 번호가 아니라 신원으로 기록한다. 여러 장비가 함께 쓰는 범용 USB 변환 케이블(FTDI 등)은 신원이 케이블의 것이지 장비의 것이 아니므로 종전대로 측정해서 판별한다. 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.27 | 2026-09-05 | 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 | 2026-09-05 | 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 | 2026-09-04 | 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 | 2026-09-02 | 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 | 2026-09-01 | 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.22 | 2026-08-30 | 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.21 | 2026-08-29 | 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.20 | 2026-08-29 | 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.19 | 2026-08-27 | 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.18 | 2026-08-27 | 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.17 | 2026-08-27 | 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.16 | 2026-08-27 | 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.15 | 2026-08-27 | 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.14 | 2026-08-27 | 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.13 | 2026-08-27 | 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.9 | 2026-08-14 | 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.8 | 2026-08-14 | 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.7 | 2026-08-12 | 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.5 | 2026-08-06 | 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.4 | 2026-08-05 | 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.3 | 2026-08-04 | 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.0 | 2026-08-03 | 1.19.0 Fixed: 여러 베드를 한 프로세스가 TCP 로 수집하는 구성에서 클라이언트 재접속이 잦으면 프로세스 전체가 SIGSEGV 로 죽던 문제. accept_handoff 가 교체(replace) 시 옛 소켓을 즉시 closesocket 했는데, m_sock_mtx 는 fd 번호만 보호할 뿐 fd 의 수명은 보호하지 않아 reader 가 그 fd 로 recv 중일 수 있었다. close 는 blocking recv 를 깨우지도 않고 fd 번호를 즉시 재사용 가능하게 만들어, 다음 접속이 같은 번호를 받으면 reader 가 남의 세션을 읽었다. SNUH 회복실(VRN 15대, 최다 베드는 하루 785회 재접속)에서 실제로 크래시 후 9시간 48분 전 베드 무수집. 수정 세 겹: (1) tcp reader 를 poll(POLLIN,200ms) 게이트 뒤의 recv 로 재구성 - recv 가 무기한 블로킹되지 않으므로 close/replace 가 늦어도 200ms 안에, shutdown 이 동반되면 즉시 감지된다. fd 에 O_NONBLOCK 은 걸지 않는다(같은 fd 로 송신하는 write_transport 의 부분 전송 방지). (2) replace 는 closesocket 대신 shutdown 만 하고 옛 fd 를 retired 목록에 넣으며, 닫기는 깨어난 reader 또는 Serial::close 가 정확히 한 번 수행 - fd 번호 재사용 창이 사라진다. (3) Serial::close 가 join 전에 shutdown 으로 reader 를 깨우고(RCVTIMEO 는 설정된 적이 없어 종전에는 피어가 끊을 때까지 join 이 매달렸다), m_sock 접근을 전부 락으로 감싼다. has_socket 의 무락 읽기와 udp_thread_func 의 이중 close 도 같은 계열이라 함께 수정. Added: AUTO_DETECT=1 (vr.conf) - 시리얼 포트에 무엇이 물려 있는지 스스로 알아내 즉시 수집을 시작한다. 판별용 프로브 바이트를 따로 두지 않고 후보 드라이버를 실제로 열어 유효 레코드 도달 여부로 판정하므로 장비별 판별 코드가 없고 드라이버는 무수정이다. 4단계: 회선 측정(커널 프레이밍/패리티/브레이크 카운터로 보레이트와 프레임 폭 확정) -> 패리티 추론(10비트 프레임의 7E1/7O1 대 8N1 모호성을 MSB 상관으로 해소) -> 후보 필터(보레이트+프레임폭+데이터비트 3축 일치, 베드당 환자모니터 1대/마취기 1대 배타, 포트 위치 사전확률, 지난 판별 결과 캐시) -> 남으면 트랙 수확량으로 경쟁. SNUH 수술장 9방 + ICU 3베드 실측에서 Intellivue/MedibusX/Datex-Ohmeda/Primus 를 오탐 0 으로 판별했고, 표준과 다른 포트에 꽂힌 장비(P3/P4 의 Intellivue)도 찾아냈다. AUTO_DETECT 가 없으면 스캐너는 유휴 대기만 하므로 기존 설정에 영향이 없다. | |
| 1.18.52 | 2026-07-31 | 1.18.52 Changed: VR 기본 운영이 k3s 로 이관됨에 따라 서버→VR 하향 제어 경로를 제거. VitalServer 로는 데이터를 일방 push 만 하고, websocket(socket.io) 으로 내려오던 원격 명령 update/restart/reboot/edit_conf/new_bed/del_bed/edit_bed/dt/add_event 를 더 이상 처리하지 않는다 (원격 재부팅·바이너리 교체·conf 덮어쓰기·베드 구성변경 봉쇄). 구현: sendweb_thread_func 의 socket.io 42 이벤트 dispatch 와 on_sio_msg 함수를 통째로 제거. engine.io ping(2->3) 및 ws 레벨 ping/pong 은 연결 유지를 위해 그대로 응답하므로 데이터 업링크·웹모니터링은 종전과 동일. 서버는 VR 버전을 알므로 52+ 로는 명령을 보내지 않고, 받아도 무시. Fixed: --console 모드 (MFC) 에서 stdout 이 이미 유효한 파이프/파일 (> log.txt, ssh 파이프, cmd 상속 핸들) 인데도 무조건 _open_osfhandle+_dup2 로 재배선하면서 그 OS 핸들을 닫아버려, 초기화 이후 모든 로그가 조용히 사라지던 문제 (콘솔 모드 서버 배포에서 진단 불능의 원인, 1.18.7 부터 잠재). stdout 핸들이 유효하면 (GetFileType != UNKNOWN) 재배선하지 않고, 핸들이 없을 때만 (Explorer/schtasks 실행) AttachConsole 로 부모 콘솔에 붙인다. 실측: 종전 초기화 후 로그 침묵 -> 수정 후 지속 출력. (앞서 1.18.51 의 VRQt qDebug->stdout 통일과 함께 세 빌드의 --debug 로그 일관성 완성.) | |
| 1.18.51 | 2026-07-30 | 1.18.51 Fixed: VRQt (Qt 빌드) 에서 --debug 옵션을 켜도 콘솔에 로그가 전혀 출력되지 않던 문제. 원인: Qt 빌드의 TRACE 가 qDebug() 로 매핑되어 있었는데 (UTIL.h, 1.14.4 부터), Windows 에서 Qt 로깅은 GUI 앱이면 콘솔/파이프가 아니라 디버거 (OutputDebugString) 로만 보내므로 VRQt 만 --debug 콘솔 출력이 구조적으로 불가능했다 (MFC 는 stdafx.h 의 #undef TRACE 로 printf 버전을 사용해 정상). 수정: TRACE 를 세 빌드 (MFC/Qt/CLI) 공통 fprintf(stdout)+fflush 로 통일 - qDebug 직접 사용처는 코드에 없음을 확인. 이제 --debug 동작과 리다이렉트 (> log.txt) 가 전 빌드에서 일관되고, cmd 대화형 실행 시에는 기존 AttachConsole/AllocConsole 로직 (1.18.30) 이 콘솔을 만들어 준다. terapc 실측 검증: 종전 VRQt stdout 0 chars → 수정 후 4,690 chars 정상 출력, Vital.exe (MFC) 는 종전과 동일 정상. 추가 방어: --debug 의 콘솔 확보 검사에 GetFileType 추가 - 일부 런처/셸이 non-NULL 무효 stdout 핸들을 상속시키면 NULL 검사만으로는 AttachConsole 을 건너뛰어 로그가 사라질 수 있던 엣지 케이스 (MFC/Qt 공통). | |
| 1.18.50 | 2026-07-30 | 1.18.50 Fixed: 장비 연결이 한번 끊어지면 다시 붙지 않던 문제 (Link+ 현장 리포트, Intellivue 늦은 전원-온과 공통 근본 원인). 근본 원인: DEVICE::open() 의 초기화 호출 set_rx_time(0) 이 값 0 인데도 OPENING→ACTIVE 로 승격시키고 m_dtStatus 까지 0 으로 만들어, (1) 모든 장비가 open 즉시 가짜-ACTIVE (OPENING 상태가 사실상 부재), (2) restart 워치독의 dtlast(rx ?: status_time) 가 0 → read_timeout 조건이 영구 거짓 → 재연결 시 상대 무응답이면 (Link+ rack 재부팅/세션 잔존, 세션 프레임 1회 전송 후 무응답) 재시도 없이 영구 고착. 1.18.35 이하의 rec_timeout (상태 무관 60s 강제 재시작) 이 이 잠재 버그를 우연히 가리고 있었고 1.18.36 전환으로 노출된 것. 수정 3건: (1) set_rx_time 은 실수신 (dt>0) 일 때만 ACTIVE 승격 - 상태 표시도 정직해짐 (open 후 첫 수신까지 "con"), (2) restart 워치독에 OPENING-stuck 분기 추가 - open 후 read_timeout 내 첫 수신이 없으면 재시작해 협상 프레임 재전송, tx_time>0 (protocol-initiator) 조건으로 passive/keyword-sub 디바이스는 제외해 유휴 churn 없음, (3) tx_time 을 모든 transport (socket/fd/COM) 에서 갱신 (기존 Windows COM 만). 영향 장비: Link+/Agilia, Intellivue, S5, Datex, Hamilton 등 open 시 세션/assoc 을 시작하는 모든 protocol-initiator. E2E 검증: 무응답 서버 재현에서 종전 접속 1회 후 영구 침묵 → 수정 후 30초마다 재접속 반복, 장비 복구 시 자동 연결. Mindray 멀티베드 회귀 테스트 PASS (ACK 75/75, tier1 라우팅, probe 거절, stuck 오발동 0). Intellivue 1.18.48 assoc-retry (5s) 는 유지 - 이번 워치독은 30s 백스톱으로 공존. | |
| 1.18.49 | 2026-07-29 | 1.18.49 Fixed: (1) Mindray eGateway HL7 멀티베드 (catch-all + #키워드 sub) 에서 전 베드가 readonly=1 이면 데이터가 전혀 수집되지 않고 게이트웨이가 수초마다 재접속하던 문제. 원인 셋 - (a) readonly catch-all 이 open_socket 에서 조기 반환해 reader/parser 스레드가 아예 생성되지 않음 (아무도 안 읽는 소켓에 데이터가 쌓이다 게이트웨이가 연결 재수립 반복; PortRouter 리팩토링 시 readonly 체크가 blocking accept 앞으로 이동하며 생긴 1.18.36 회귀 - 1.18.31 은 accept 가 먼저라 우연히 동작), (b) readonly 가 HL7 ACK 송신까지 차단 (eGateway 는 메시지마다 ACK 요구), (c) sibling 베드 메시지는 broadcast 후 ACK 없이 return. 수정 - readonly 는 송신만 차단하고 수신은 정상화 (broadcast-only keyword sub 만 소켓 미보유), write_protocol 경로 신설로 ACK 는 readonly 우회, catch-all 이 sibling 메시지도 대신 ACK (R01/R40 타입 구분), broadcast 수신 sub 의 rx_time 갱신으로 상태표시 정상화, PortRouter takeover 는 기존 소켓이 stale (10초+ 무수신) 일 때만 허용해 게이트웨이 health-probe 접속이 live 데이터 소켓을 뺏지 못하게 함. 시뮬레이터 E2E 검증: ORU 75건 -> ACK 75건 (100%), probe 5회 전부 무해 거절, SU-2/SU-4 tier1 라우팅 정상. (2) Getinge/MAQUET Flow-i (FCI 프로토콜) tidal volume 이 실제의 절반으로 기록되던 문제. 원인: FCI 프로토콜 v0005 부터 TV 채널 (101/102) gain 이 0.1 -> 0.2 로 변경됐는데 (Flow-i communication interface v0007 문서), VR 은 open 시 SPVE0005 로 v5 를 요청하면서 파싱은 구버전 gain 0.1 을 적용. 병원 vital 파일 실측 (Flow-i 직결 TV 201.4/188.2 vs 필립스 경유 403.0/376.6, MV 는 정상) 으로 검증 - TV_INSP/TV_EXP gain 0.2 적용. MV 는 v3+ 스케일 (0.01) 이 이미 반영돼 있어 변경 없음. (3) Flow-i 설정 파라미터 미지원 (m_ch_settings 빈 맵) - v5+ 에 존재하는 수치 설정 채널 11종 등록: SET_RR(411), SET_INSP_TM(413), SET_IE(418), SET_MV(421), SET_PC(423), SET_PEEP(424), SET_FRESH_FLOW(428), SET_FRESH_O2(430), SET_AGENT_PERC(434), SET_PS(436), SET_SIMV_RR(437). gain 은 문서 표기 그대로. 주의: Set Tidal Volume (채널 422) 은 FCI v5 에서 프로토콜 자체에서 삭제되어 직접 수신 불가 - 볼륨 모드에서 SET_MV/SET_RR 로 파생 가능. 설정 채널은 문서 기반 구현으로 실기 검증 대기 상태. | |
| 1.18.48 | 2026-07-24 | 1.18.48 Fixed: VR 이 먼저 실행된 상태에서 Philips Intellivue 모니터를 나중에 켜면 웨이브가 영원히 수집되지 않던 문제 (VR 재시작 전까지 모니터로 아무 요청도 안 나감). 원인: Intellivue::open() 은 assoc_req 를 딱 한 번 보내는데, 시리얼(MIB)/UDP 는 모니터가 꺼져 있어도 포트가 열리므로 이 요청이 응답 없이 사라진다. Philips 는 자기가 먼저 말을 걸지 않는 프로토콜이고, 첫 수신이 있어야 디바이스 상태가 OPENING→ACTIVE 로 승격되는데 수신이 없으니 OPENING 에 고정 - DEVICE::restart_thread_func 워치독은 is_closed(재오픈)/is_active(read_timeout 30s) 두 분기만 있어 OPENING 은 어느 쪽에도 안 걸림 → assoc 재전송도 재시작도 영원히 없음. 수정: Intellivue 에 assoc 재시도 스레드 추가 - 세션 확립(MDS_CREAT 수신, m_bSessionUp atomic 플래그) 전까지 5초 간격으로 assoc_req 재전송, 최근 5초 내 수신이 있으면(assoc 진행 중) 재전송 보류. 모니터를 나중에 켜도 최대 5초 내 연결. 세션 확립 후에는 재시도 루프 종료 - 기존 동작(AB_SPDU 시 재연결, read_timeout 재시작)과 충돌 없음. 모니터가 켜져 있던 기존 시나리오는 첫 assoc_req 에 바로 응답하므로 동작 변화 없음 - 0-impact. | |
| 1.18.47 | 2026-07-24 | 1.18.47 Fixed: 파일 업로드(및 Http 클래스를 쓰는 모든 경로 - upload_vital, getvr 업데이트체크, ADT 조회, 필터서버)가 SERVER_IP/UPLOAD_SERVER_IP 에 "https://host:port" 풀 URL 형식을 넣으면 "Couldn't resolve host name" 으로 전부 실패하던 문제. 원인은 src/Https.cpp 의 Http 생성자 - 1.18.46 의 wss.cpp 수정과 동일한 버그의 나머지 반쪽. 첫 콜론을 포트 구분자로 오인해 "https://eicuvital.ywmc.or.kr:4432" 입력 시 m_strHost 가 문자 그대로 "https" 가 되어 DNS 조회 실패 (웹모니터링 wss 는 1.18.46 에서 이미 수정되어 정상, 파일 업로드만 실패하는 비대칭 발생 - 현장에서는 업로드 루프의 실패시 break 정책과 겹쳐 "베드명에 # 가 있으면 전송이 안 된다" 로 오인 관찰됨. 실제로는 # 파일이 디렉토리 순서상 항상 첫 시도라 거기서 host 오류로 중단 → 뒤 파일 전체가 막힌 것. 베드명 특수문자는 업로드 URL 과 무관). 수정: Http 생성자에 wss 와 동일한 선행 스킴(https/http) 파싱 추가 - 스킴이 TLS 여부를 결정 (포트 무관, 4432 같은 커스텀 포트에서 https 정상), "https://host" 포트 생략 시 443, 경로 붙은 입력도 host 만 추출. 기존 "host" / "host:443" 형식은 종전과 완전 동일 동작 - 0-impact. Windows(WinINet)/Linux(curl) 공통 생성자라 모든 플랫폼에 적용. | |
| 1.18.46 | 2026-07-23 | 1.18.46 Fixed: WSSClient 가 "https/wss 스킴 + 비표준 포트" 조합을 제대로 처리하지 못하던 문제 (커스텀 포트의 wss 서버에 연결 불가). 원인은 src/wss.cpp 의 WSSClient 생성자 두 곳 - (1) SERVER_IP 파싱이 첫 콜론을 포트 구분자로 써서 "https://host:4432" 를 넣으면 "https" 의 콜론을 먼저 잡아 m_host 가 "https" 가 되고 포트 파싱이 깨져 getaddrinfo 실패, (2) m_secure = (m_port == 443) 로 TLS 여부를 오직 포트 443 여부로만 판단해 4432 같은 커스텀 포트는 무조건 평문 ws 로 붙어 wss 전용 서버에서 실패. 수정: 생성자가 선행 URL 스킴(https/wss/http/ws)을 먼저 파싱해 스킴이 있으면 스킴이 TLS 를 결정(포트 무관)하고, 스킴이 없으면 기존 포트 443 휴리스틱을 유지(하위호환). 이제 SERVER_IP=https://eicuvital.ywmc.or.kr:4432 (또는 wss://...) 형식이 비표준 포트에서도 wss 로 정상 연결되며, SNI/인증서 검증은 도메인(m_host)을 쓰므로 와일드카드 인증서도 정상 매칭. 기존 host / host:443 설정은 종전대로 secure 유지 - 0-impact. | |
| 1.18.45 | 2026-06-16 | 1.18.45 Fixed: Windows VR 에서 사용자가 외부에서 vr.conf 를 멀티베드 설정으로 교체 후 VR 을 재실행하면, 첫 실행에서는 멀티탭이 정상 생성되지만 다음 실행에서 모든 탭이 사라지던 문제. 원인은 VRApp.cpp:1110 의 VRCODE 미설정 분기 — 새 vr.conf 가 VRCODE 라인을 포함하지 않으면 load_settings() 가 신규 VRCODE 를 issue_code() 로 발급하고 즉시 save_settings() 를 호출하는데, 이 시점은 load_settings() 가 파싱한 베드들이 로컬 newbeds 벡터에만 있고 아직 m_pdisps 로 옮겨지기 전 (set_beds(newbeds) 는 initialize() 가 load_settings 반환 후 라인 1328 에서 호출). 결과: save_settings() 가 빈 m_pdisps 를 iterate 하며 vr.conf 를 글로벌 설정만으로 덮어써 모든 [BED/...] 섹션이 디스크에서 사라짐. 첫 실행은 메모리의 newbeds 로 UI 멀티탭이 그려져 정상으로 보이지만 (사용자가 "멀티탭은 정상적으로 생성되지만" 으로 표현), VR 종료/재실행 시 디스크 conf 에는 BED 섹션이 없으므로 다음 load_settings 가 빈 베드 리스트를 반환 → 탭 전부 사라짐. 수정: VRApp.h 에 m_vrcode_newly_issued bool 플래그 추가, load_settings 의 즉시 save 를 플래그 set 만 으로 교체, initialize() 의 set_beds(newbeds) 직후 (m_pdisps 가 채워진 시점) 플래그가 켜져 있으면 한 번만 save_settings() 호출 → BED 섹션이 정상 직렬화됨. Linux/Pi 분기 (m_serial_number 가 채워져 있는 경우) 는 원래도 save 안 했으므로 영향 없음, VRCODE 가 이미 conf 에 있는 모든 기존 deployment 도 분기 미진입으로 0-impact. m_conf_from_stdin / m_bDebug 모드는 save_settings 내부에서 이미 early return 이라 그대로 안전. MFC + Qt 빌드 모두 통과 (이번 변경 관련 0 warning). | |
| 1.18.44 | 2026-06-12 | 1.18.44: vr.conf FHIR=1 모드 추가 (자체 FHIR 수신서버 대상 실시간 푸시) + VS 2026 / 새 OpenSSL 인스톨러 빌드 환경 호환 shim. (1) sendweb_thread_func 의 기존 HL7/JSON 양분 분기 (VRApp.cpp:2466) 옆에 bFHIR 분기 추가. 1초 tick 마다 한 vrcode 의 모든 방 Observation 을 하나의 FHIR R4 collection Bundle 로 묶어 기존 WSS 채널로 push (gzip + WSSClient 인프라 그대로 재사용). MLLP 같은 신규 transport 안 도입 — FHIR R5 Subscription 의 websocket channel type 과 동형이라 표준 부합. (2) DISPLAY::get_fhir + TRACK::get_fhir 신규. v2 (vitaldb-fhir-v2) 와 동일한 reference-only 패턴 (Patient.id = m_patient_id, Encounter.id = vrcode-bedname-startsec, Observation.subject/encounter = relative reference) + hybrid keyframe: 첫 tick / m_patient_id 변경 / get_start_time() 변경 / 5분 경과 중 하나라도 발생 시 다음 tick Bundle 에 Patient + Encounter resource 를 inline. 그 외 tick 은 Observation 만. 수신서버가 도중에 새로 connect 해도 최대 5분 안에 자동 동기화 (keyframe heartbeat). DISPLAY 에 m_fhir_pt_sent_at/m_fhir_pt_last_id/m_fhir_enc_last_start 3개 멤버 추가 (m_setting_sent 패턴 그대로 per-display). (3) Observation 인코딩: NUM 은 effectiveDateTime + valueQuantity 각 데이터 포인트당 한 entry, WAV 은 effectivePeriod + valueSampledData (period=1000/srate ms, factor=1, origin=0, data=공백구분 floats) 한 entry. CO2/AWP wav 는 기존 get_json 처럼 25Hz 다운샘플 유지. NUM 은 m_montype 기반 LOINC 매핑 15개 등록 (ECG_HR/PLETH_HR=8867-4, PLETH_SPO2=59408-5, NIBP/IABP SBP/DBP/MBP=8480-6/8462-4/8478-0, BT=8310-5, RR=9279-1, etCO2=19891-1, CVP=8556-0, FETAL_HR=11627-3) + VR 자체 코드 시스템 (https://vitaldb.net/fhir/CodeSystem/track, code = dname/tname) 항상 동반. m_patient_id 미설정 (PATIENT_ID_UNSET) 시 anon-vrcode-bedname identifier 로 fallback. (4) Bundle wrapper: VRApp 가 방별 entries 를 콤마로 합쳐 {resourceType:Bundle, type:collection, timestamp:iso8601Z, entry:[...]} 로 wrap, 기존 gzip → WSS pipeline 동일. ISO 8601 UTC 헬퍼는 std::gmtime_s/gmtime_r 인라인. FHIR id 안전화 (A-Za-z0-9-.) 64자 제한 lambda 각 함수에 inline. (5) 빌드 환경 호환성: VRQt/CMakeLists.txt 에 _SILENCE_EXPERIMENTAL_COROUTINE_DEPRECATION_WARNINGS 추가 — VS 2026 (MSVC 14.51+) 의 STL 이 <experimental/coroutine> deprecation 을 warning → hard static_assert 로 바꾼 것이 cppwinrt 의 winrt::* (Serial.cpp 의 Bluetooth LE) 를 끌어오는 모든 TU 에서 컴파일 실패 시켜서, build_mfc.py 가 이미 CL env 로 주입하던 것과 동치 shim 을 Qt 쪽에도. (6) VRQt/CMakeLists.txt 의 OpenSSL link 경로 시프트. 새 OpenSSL 인스톨러가 libssl_static.lib / libcrypto_static.lib 를 lib/VC/x64/{MT,MTd}/ 에 두고 v1.x 의 libssl64MT.lib / libcrypto64MT.lib 네이밍과 lib/VC/static 경로를 깬다. build_mfc.py 의 %LOCALAPPDATA%/openssl-compat 패턴 (옛 이름으로 복사한 사본 폴더) 을 그대로 따라 CMakeLists 도 그 폴더가 있으면 우선, 없으면 옛 lib/VC/static 으로 fallback. 부트스트랩 스크립트는 build_mfc.py 의 docstring 참조 (한 줄). MFC + Qt + RPi + Ubuntu CLI + AppImage 빌드 모두 통과. 동작 영향: vr.conf 에 FHIR=1 추가 안 한 기존 deployment 는 sendweb 가 종전 JSON 모드 그대로 (HL7=1 만 분기보다 우선) — 0-impact migration. | |
| 1.18.43 | 2026-05-21 | 1.18.43: 멀티베드 console (PACU 10베드 Pi) syslog noise 80,500/hr → ~1,400/hr (-98.3%), cpu_usage 측정 1.16.12 (9f26e36) 이후 8년치 영구 -1 버그 수정, 멀티베드 환경 베드 식별용 [bedname / devicename] 로그 prefix 도입. 동기는 PACU production Pi (1.18.40) 4일치 syslog 분석 — 1.24M 라인 중 vr 라인 836K, idle 8시간 평균 시간당 80,500 라인 (베드 10 × 1초마다 "Waiting for next patient" × syslog 2x 중복). (1) VRApp.cpp:2696-2710 timer_thread_func 의 cpu usage 계산이 `if (totalcpu && m_cpu_total_last && totalcpu > m_cpu_total_last)` 가드 안에서만 m_cpu_total_last 갱신해 첫 호출 시 m_cpu_total_last=0 → else 분기 → -1 → m_cpu_total_last 갱신 안 됨 → 영원히 false → 영원히 -1 데드락. PACU 1,048 샘플 100% -1 실측 확인. m_cpu_total_last/m_cpu_used_last 갱신을 if 밖으로 빼서 첫 호출만 baseline 으로 -1, 두 번째 (60s 후) 부터 정상 측정. /proc/stat 의 iowait/irq/softirq 까지 7개 필드 fscanf 로 정확도 ±5% 개선. 부수: 같은 함수의 cpu_temp 가 `temp / 1000` int 나눗셈으로 소수점 절단되어 PACU 측정값이 정수 50/51/...,/58 9종만 출력 (millidegree 정밀도 손실) → `temp / 1000.0f` 로 fix. ram 계산은 `(totalram + totalswap) * mem_unit` 결과에 다시 mem_unit 곱하던 차원 혼용 (Linux 가 mem_unit=1 이라 우연히 정상 동작했지만 dimensionally wrong) → `(units1 + units2) * mem_unit` 로 정정 + underflow 가드. (2) DISPLAY::log(string/printf) + DEVICE::log(string/printf) 멤버 추가. DISPLAY::log 는 `[bedname] msg`, DEVICE::log 는 m_pdisp 위임 없이 직접 `[bedname / devicename] msg` 한 줄 prefix (nested `[bed] [dev]` 보다 가독성 우선). m_pdisp 또는 bedname null 케이스 safe fallback. (3) show_msg 의 console path TRACE 를 log() 로 교체 → "Waiting for next patient" 자동 [bedname] prefix. ADT patient id unset 도 log() 로. (4) 이미 수동으로 get_bedname() 을 인자로 넘기던 8군데 (DISPLAY ptcon_thread_func / add_trk / add_dev / del_dev / cut-recording, Device add_rec 콜백, FILT stop, VRApp add_tab/del_tab) 를 log() 로 전환 — 이중 prefix 방지 + 일관성. Serial::reading_thread / tcp_thread / open_socket waiting / timeout / interrupted / got-client 6군데 DEVICE::log() 전환 → `[bedname / Intellivue]` 등 자동 prefix, 멀티탭 환경에서 어느 베드 어느 device 출처인지 즉시 식별. (5) wss 재시도 폭주 throttle. WSSClient 에 m_verbose (debug step 로그 게이트) + m_dt_last_err_log (60s window) 추가. connect() 내부 step-by-step TRACE 4개 ("connecting" / "connect error" / "connected") 는 m_verbose 가드, 최종 "wss connect error" 는 60s window throttle (첫 줄 즉시, 그 다음 60s 1회). connect 성공 path 에서 throttle reset 으로 다음 disconnect → 첫 에러 즉시 로깅. sendweb_thread_func 의 "connecting websocket server" 호출자 라인도 같은 60s window, m_bDebug 면 verbose 활성화. PACU idle 8h 측정 7,024/hr (4종 × 1,756) → 약 60/hr (-99%). (6) "Waiting for next patient" / "ADT patient id unset" 의 60s throttle 자체는 feature/wav-mem-retention 브랜치 ptcon_thread_func 재구성 (b75928f 시리즈) 에서 이미 들어가 있어 이 릴리즈에 자동 포함. 종합 영향: idle 시간당 syslog 라인 80,500 → ~1,400 (-98.3%), active 시간당 14,900 → ~700 (-95%). 진단 측면: cpu_usage 가 부활하면서 웹모니터링 json 의 cpu_usage 필드도 한 번도 안 채워지던 게 정상 전송 (VRApp.cpp:2499 의 `if (m_cpu_usage > 0)` 가드가 영원히 false 였음). 별건: syslog 라인 2× 중복은 application 측이 아니라 rsyslog imuxsock + imjournal + journald.ForwardToSyslog=yes 의 Debian Bookworm 전형 misconfig — admin 측 fix (rsyslog.conf 에서 imjournal 주석 처리 + restart) 로 추가 50% 감소 가능 (별도 문서). MFC + Qt + Pi (RPI64) + Ubuntu CLI 빌드 모두 통과. | |
| 1.18.41 | 2026-05-13 | 1.18.41 Hotfix: TCP-server protocol-initiator device (Intellivue/S5/Datex/Hamilton/MPS/Agilia/IAP) 가 open() 직후 보내는 첫 패킷이 wire 에 안 나가던 회귀. 1.18.34 의 PortRouter refactor 가 Serial::open_socket() 의 8초 handoff blocking 을 제거하고 비동기 모델로 전환했는데, protocol-initiator 의 open() 코드가 m_sock=INVALID 상태에서 write 를 시도해 silent fail (Linux 는 m_fd=0 이라 false 반환). Intellivue 케이스 production journald 의 Pi → ESP TX 700K~3M bytes 는 monitor 측 abort 로 늦게 트리거된 AB_SPDU_SI recovery assoc_req 후 request_thread 의 정상 polling 트래픽이지만 일부 베드 (abort 없는 monitor) 는 recovery 도 안 되어 영구 silent (RX 300 bytes). 수정 방향: open() 의 옛 contract ("return 시점에 통신 가능") 복구. (1) Serial::open_socket() TCP-server 분기가 PortRouter::subscribe 후 m_sock_cv 로 첫 client handoff 까지 block (timeout = device read_timeout, 기본 30s) — 옛 모델 그대로. lock_and_open 이 false 면 다음 tick 에서 retry. close 신호로 abort 가능. (2) Serial::close() 는 PortRouter::unsubscribe 안 함 — subscribe 는 device 생애 동안 sticky 라 read_timeout 으로 인한 close→re-open 사이클 동안 listen socket 이 유지되어 1.18.29/1.18.34 의 anti-churn 이익 보존. unsubscribe 는 ~Serial() 에서만. (3) Per-device lifecycle thread (DEVICE::m_thread_restart): 각 device 가 자기 open/close/timeout 재시작을 별도 thread 에서 관리. 옛 모델의 DISPLAY::bed_manager 가 베드의 모든 device 를 sequential iterate 하던 구조에선 한 device 의 blocking open() 이 같은 베드의 다른 device 의 lifecycle 을 차단했는데, per-device thread 로 분리하여 멀티 device 베드도 병렬 진행. add_dev 에서 start_lifecycle, del_dev / ~DISPLAY 에서 stop_lifecycle. (4) Serial::tcp_thread_func 단순화 — outer m_sock_cv wait 패턴 (1.18.34/1.18.39 가 추가했던 것) 제거. open_socket 이 m_sock valid 보장하므로 reader thread 시작 시점에 simple inner recv loop 만. accept_handoff 의 replace (zombie cleanup) 케이스는 recv error 후 m_sock 변경 확인 + retry 로 처리. (5) reading_thread_func 의 `else if (m_sock != INVALID_SOCKET) tcp_thread_func();` 가드 복원 (1.18.39 가 제거). (6) Serial::write 의 silent-fail 케이스에 WARN trace 추가 — 미래 동종 회귀 즉시 노출. (7) PortRouter::subscribe 가 같은 (port, type) 에서 같은 IP filter 또는 둘 이상 catch-all 발견 시 reject (config 실수 원천 차단, 옛 WARN-only 동작 강화). (8) DISPLAY::bed_manager_thread_func 의 restart 블록 제거 — ptcon + adt 만 담당. "Waiting for next patient" TRACE 가 Linux/console 에서 1초 → 60초마다 throttle (별개 journald 부풀림 fix). 영향 범위: Intellivue / S5 / Datex / Hamilton / MPS / Agilia / IAP TCP-server 모드 베드 전부. 1.18.34 이후 우회 방법은 race-win 케이스 (VRN 이 PortRouter::subscribe 직후 connect) 외엔 없었음. MFC + Qt + Pi (RPI64) + Ubuntu Qt + AppImage 빌드 모두 통과. | |
| 1.18.40 | 2026-05-12 | Hotfix: Drager Medibus 프로토콜 사용 베드에서 1.15.11 이후 vr.service 가 SIGSEGV 로 무한 재시작하던 문제. 1.15.10 (a8c7b2a) 의 "Sampling rate estimation" 리팩터링이 m_ptrk_awp/awf/co2 트랙을 add_trks() 에서의 eager 생성 (default srate=62.5Hz) 에서 realtime data 100 샘플 모인 후 lazy 생성으로 바꿨는데, Medibus 핸드셰이크 순서 (0x51 ICC → 0x52 DeviceID → 0x53 RealtimeConfig 응답 → 0x54 → realtime data 시작) 상 0x53 응답 핸들러 (Drager.cpp:295-307) 가 트랙들이 아직 nullptr 인 시점에 `ptrk->m_srate = 1000000.0/interval` 로 NULL 역참조 → 즉시 SEGV. 그 베드의 vr.service 가 systemd Restart=on-failure 로 재시작 → 다시 connect → 동일 0x53 패킷 받고 동일 SEGV → 무한 루프 (journald 로그상 restart counter 61 까지 누적, code=killed status=11/SEGV). 1.15.9 까진 트랙이 add_trks() 에서 생성되어 0x53 도착 시 항상 non-NULL 이라 안전했음. 수정: (a) add_trks() 에서 m_ptrk_awp/awf/co2 eager 생성 복원 (srate=62.5Hz default) — 0x53 는 ptrk->m_srate 만 갱신 (트랙 재생성 X). (b) on_received() 의 lazy creation + packet-timing srate 추정 블록 제거 — 0x53 가 권위 있는 srate 소스이고 packet 타이밍 기반 추정은 network jitter 에 약해 불안정. (c) 0x53 핸들러에 `if (interval > 0)` guard 추가 — 페이로드 깨졌을 때 div-by-zero / inf srate 방지. 영향 범위: Drager Primus / Apollo / Perseus / Fabius 등 Medibus 사용 마취기 전부. 우회 방법은 1.15.9 다운그레이드 외엔 없었음 (그 베드의 모든 데이터 유실 + 환자 모니터링 불가). MFC + Qt + Pi (RPI64) + Ubuntu Qt + AppImage 빌드 모두 통과. | |
| 1.18.39 | 2026-05-08 | Fixed: PortRouter (TCP server) 모드에서 reader thread 가 startup 시점에 죽어 client 데이터를 영구히 못 받던 문제. 1.18.34 (9bd252f) 의 PortRouter refactor 가 m_sock 세팅을 dispatch 시점으로 옮겼는데 reading_thread_func (Serial.cpp:354) 의 가드 `else if (m_sock != INVALID_SOCKET) tcp_thread_func();` 는 옛 모델 (open 시점 이미 connect 완료) 가정 그대로라 — 시작 직후 m_sock=INVALID 면 tcp_thread_func 진입 안 하고 reader thread 즉시 종료. 이후 VRN 이 connect → PortRouter::dispatch → accept_handoff 가 m_sock 채우고 m_sock_cv.notify_all() → 그러나 wait 중인 reader 가 없음 (이미 죽음) → kernel recv buffer 에만 데이터 적재. 증상은 ss -tn state established 의 Recv-Q 가 0 이 아닌 값 (수백 bytes) 으로 누적, 베드 화면에는 데이터 안 들어옴. 그 베드의 VRN 이 Serial::open() 의 PortRouter::subscribe 직후~reader thread spawn 사이의 짧은 윈도우 안에 connect 한 경우만 우연히 정상 동작 (race-win). 수정: 가드 제거 — TCP 분기에 항상 진입. tcp_thread_func 자체에 이미 m_sock_cv outer-wait 루프가 있어 m_sock 이 INVALID 인 동안 dispatch 가 깨워줄 때까지 안전하게 대기. Pi 핫스팟에 VRN 여러 대 (e.g. 10.42.0.21~25) 를 같은 (4343, Philips:Intellivue) 키로 묶고 IP filter 로 라우팅하는 시나리오에서 가장 자주 노출. Added: 진단 로그 보강 — PortRouter::accept_loop 의 accept 이벤트, dispatch 의 라우팅 결정 (ip_specific/catch_all subs 수, 어느 dev 가 받았는지), subscribe/unsubscribe 의 entry 생성/append/destroy, reading_thread / tcp_thread 의 시작·wait·wake 라이프사이클 — 이전엔 모두 m_bDebug 게이트 안에 있어서 production journald 에서 안 보였는데, 이제 항상 출력 (이번 같은 dispatch routing 문제 진단을 production 에서 바로 가능). MFC + Qt + Pi (RPI64) + Ubuntu Qt + AppImage 빌드 모두 통과. | |
| 1.18.38 | 2026-05-08 | Fixed: 웹모니터링 우클릭 → 디바이스 세팅 (websocket edit_bed 명령) 으로 베드의 device/filter 를 변경할 때 베드 이름이 매번 "_2" suffix 로 자동 리네임되어 vr.conf (Pi: /data/vr.conf) 에 잘못된 이름으로 저장되던 문제. 원인은 1.18.22 (cda8aa5) 에서 add_bed 에 추가된 bedname dedup ("최후 방어선" 으로 forward_frame auto-create / 수동 편집된 conf 의 중복 이름 처리용). edit_bed 핸들러는 옛 pdel 이 m_pdisps 에 그대로 있는 상태에서 같은 bedname 으로 new DISPLAY 를 add_bed 하고 그 다음 del_bed(pdel) 하는 순서였는데, add_bed 가 dup 감지해 새 pdisp 를 bedname_2 로 자동 리네임 → save_settings 가 [BED/bedname_2] 로 직렬화 → 사용자 입장에선 "설정 적용 안됨" 으로 보였다. 추가로 옛/새 DISPLAY 가 잠깐 m_pdisps 에 공존하면서 같은 device 포트를 동시에 들고 있으려 해 로그가 dup 으로 찍히는 부수 증상도 있었음. 수정: del_bed(pdel) 를 add_bed(pdisp) 보다 앞으로 이동 — 옛 베드의 device 포트가 깨끗이 release 된 뒤 새 pdisp 가 같은 포트를 reopen, dedup 발동 안 함. 다른 베드의 recording 은 영향 없음 (인메모리 hot swap 설계 유지). 부수: edit_bed 핸들러의 데드 코드 (`break` 뒤의 unreachable olddevs populate 루프 + `if (olddevs.find(name) == olddevs.end())` 항상-true 래퍼) 정리. edit_conf 와 save_settings 는 손대지 않음. MFC + Qt + Pi (RPI64) 빌드 모두 통과. | |
| 1.18.37 | 2026-05-08 | Refactored: 필터 다이얼로그의 python 런타임 — pyvital → openvital 0.3.0 로 교체 + 동봉 python.zip 슬림화. 핵심 동기 셋: (1) pyvital 0.6.0 이 tensorflow+torch+keras 를 hard-dep 으로 끌어들여 PyPI upgrade 가 사실상 깨짐, (2) 기존 zip 121 MB 안에 botocore/aiobotocore/s3fs/pyarrow/pandas/aiohttp 등 vitaldb 클라우드 업로드용 deps 가 ~250 MB unpacked 차지 — 필터 서버는 한 번도 쓴 적 없는 dead weight, (3) sanic 25.x 의 multi-process worker 가 openvital __main__.py 를 fork 후 재import 하면서 worker 가 module-top app instance 를 못 찾아 "Sanic app name not found" 로 죽음 — 단순 endpoint 2개 로컬호스트 서버에 async stack 자체가 과함. 해결: openvital 0.3.0 (commit b6f445b) 가 sanic 을 stdlib http.server (BaseHTTPRequestHandler + HTTPServer) 로 50 LOC 재작성 — wire protocol (GET / 필터목록 JSON, POST /<modname> gzip 본문) 동일해 VR 클라이언트 변경 0줄. 단일 스레드로 기존 sanic event loop semantics (cfgs/default_cfgs lock-free 변경) 보존. build_python_zip.py (NEW) 가 Python 3.11.9 embed + numpy + openecg + openvital (local checkout 우선, fallback PyPI) 만으로 27 MB zip 빌드 — 기존 121 MB 대비 78% 감소. ML 필터 (sv_dlapco/abp_hpi/ecg_classifier/ecg_beat_noise_detector) 는 [Hybrid 모델] 사용자가 Add filter 다이얼로그의 "Install ML filters" 버튼 누를 때 silent pip install openvital[all] (cmd 창 미노출 — VRApp::install_pip_package + run_pip_silent 이 자체 progress 다이얼로그에 stdout heartbeat 흘림). 버튼 라벨은 "Upgrade pyvital" → "Install ML filters" 로 의도 명시. openvital __main__.py 가 missing extras 를 graceful skip 하므로 base 환경에서도 11개 필터 (ECG QRS detector, HRV, MTWA, annotator, eeg_fft, nirs_cox, pkpd_3comp, pleth_dpop/ptt/pvi, resp_compliance) 즉시 사용 가능. Migration: 기존 사용자의 user_dir/python 폴더는 pyvital + 옛 deps 가 그대로 남아있고 vitaldb.net/python.zip 만 새로 올리면 (a) 기존 폴더 삭제 후 install_python 으로 새 zip 받으면 깔끔, (b) 폴더 유지 시 server 가 openvital 못 찾아 필터 다이얼로그 에러 → release note 에 명시 필요. MFC + Qt 양쪽 코드: VRGui.rc 의 IDC_UPGRADE 버튼 라벨 + DlgAddFilter.cpp 의 dist-info prefix 매칭 (pyvital- 8자 → openvital- 10자) + QtDlgAddFilter.cpp 도 동일 변경 + onUpgrade 가 cmd /k QProcess::startDetached 대신 theApp.install_pip_package("openvital[all]") 호출 (Linux Qt 빌드는 #ifdef _WIN32 가드). VRApp.cpp:318 의 python -m pyvital → -m openvital. install_pip_package 는 anonymous namespace 의 is_safe_pkg_spec 화이트리스트 검증 + run_pip_silent (CreateProcessA + CREATE_NO_WINDOW + 자식 stdout pipe) + CDlgProgress / QtDlgProgress 자체 progress UI. 빌드 검증: python -m openvital 이 GET / 로 11 필터 반환, sanic import 부재 확인. zip 27.5 MB. | |
| 1.18.36 | 2026-05-07 | Fixed: 3-파티션 레이아웃 (boot/rootfs/data) 에서 websocket edit_conf / update 명령이 read-only fs 로 실패하던 문제. vr.conf 는 /data 폴더가 있으면 /data/vr.conf 로 자동 정착 — get_conf_path() 가 write target 으로 결정, load_settings() 는 primary 가 없으면 legacy /boot/firmware/vr.conf 또는 /boot/vr.conf 에서 fallback read 후 다음 save 부터 primary 로 자동 이전 (마이그레이션 step 없음, legacy 파일은 read-only fs 라 무해). binary update (upgrade_now) 는 RAII BootRwGuard 가 dirname(module_path) 를 잠시 rw remount, scope 종료 시 sync() + MS_REMOUNT|MS_RDONLY 로 자동 ro 복귀 — vfat 노출 윈도우 최소화 + exception/return 어디서든 복구 보장. statvfs ST_RDONLY 검사로 이미 rw 인 fs (x86 dev) 에서는 no-op, non-Linux (macOS) 는 stub 으로 통과. save_settings() 의 silent EROFS 실패가 TRACE 로 가시화 (이전엔 무시되어 conf 수정 안 먹는 원인 추적 불가). 부수: PortRouter.cpp 가 commit 9bd252f 이후 CMakeLists 의 RPI sources 목록에서 누락되어 Pi 빌드 link 에러 — 추가. | |
| 1.18.35 | 2026-05-01 | Added: alarm parsing for Mindray HL7 (eGateway ORU^R40) and Nihon Kohden (NealTime HL7GW + ORF, BSM serial D-port, EGA UDP) - emits ALARM_STATUS / ALARM_MESSAGE / ALARM_PRIORITY tracks (same 3-track schema already used by Philips IntelliVue, GE Solar/Dash, GE/Datex S5); type info preserved via [T]/[A]/[ARR] message prefixes; Mindray facet 1/5/6/7 walker with 92-entry MDC+MNDRY alarm code lookup; NK HL7GW parses OBR-4=EVENT with arrhythmia/technical disambiguation by OBX-5; NK BSM serial decodes per-parameter * flags (edge-triggered to avoid 1Hz flooding) plus 32-bit Table 9 arrhythmia bitmap, D19 alarm-suspend nibble, and the VPC CODE byte; NK EGA UDP extracts Priority + Tech Alarm + Arrhythmia from JSON Vital Sign packets with edge-trigger cache for start/end inference; ACK^R40 reply added for Mindray alert messages; simulators gained periodic alarm cycling for end-to-end smoke testing | |
| 1.18.34 | 2026-04-27 | Added: Linux Desktop Qt GUI build (VRQt) - first GUI build for Linux distributed as a self-contained AppImage (~32MB, Qt6 runtime + libGL/libEGL/libGLX/libOpenGL/libGLESv2/libGLdispatch bundled inside) so users on minimal Ubuntu/Debian/Fedora can run it without installing Qt or GL system packages; tested on AWS clean Ubuntu 22.04 cloud image; build via build_ubuntu_appimage.py which is now invoked by deploy.py; Fixed: Linux Qt right-side device/filter panels rendered with white background below cards because the inner listWidget did not autoFillBackground from parent palette on Linux Qt (Windows/macOS implicitly inherit, X11/Wayland do not); explicit stylesheet on listWidget; Added: Linux installation section in user manuals (EN/KO/ZH/ES) covering AppImage chmod+run flow and dialout group note for serial/USB device access | |
| 1.18.33 | 2026-04-25 | 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.32 | 2026-04-23 | Fixed: same-port multi-client TCP - primary now runs a continuous accept loop and hands off each connection to the matching sub by IP filter (previously second connection got rejected/listened closed); Added: @IP and #keyword port filters are now mutually exclusive per bed (IP wins, keyword dropped with WARN); Added: WARN log when two beds on the same port share an identical IP filter; Added: catch-all primary keeps its single connection and rejects extras (preserves broadcast mode) | |
| 1.18.31 | 2026-04-22 | Added: drug slot shows DOSE_AMOUNT (mg etc) in preference to VOL (mL) when available; Added: DOSE_RATE / DOSE_AMOUNT montypes bound to BBraun tracks for monitor view; Added: HL7 MDC_DOSE_DRUG_DELIV_TOTAL unit picked up dynamically from UCUM | |
| 1.18.30 | 2026-04-21 | Changed: monitor view pump layout - 8 slots with CE or RATE (CE-less pumps show infused volume as small annotation); Reverted: BBraun minimal=1 (1.18.29) - was dropping data from file, monitor view redesign handles display clutter instead | |
| 1.18.29 | 2026-04-21 | Fixed: BBraun multi-rack auto-tab data drop, primary TCP churn, save_settings lock race, TCP reader spin on peer close; Added: BBraun minimal=1 option (display only drug name / rate / volume) | |
| 1.18.28 | 2026-04-20 | Fixed: vr.conf on Pi/Linux now looked up next to executable (supports /boot/firmware); deploy.py --store auto-submits MSIX to Microsoft Store | |
| 1.18.27 | 2026-04-18 | Fixed: vital file periodic flush (5000s→5s bug), Fit-to-Window icon redesigned to match navigation button style | |
| 1.18.26 | 2026-04-18 | Fixed: periodic .vital file flush interval was 5000 seconds instead of 5 (unit-bug since comment said seconds but value was milliseconds). Data now actually reaches disk every 5 s during recording instead of only on close. | |
| 1.18.25 | 2026-04-18 | Fixed: BBraun HL7 rec_timeout raised 10s→60s to prevent primary churn during DoseLink R42→R01 phase transition and long idle periods | |
| 1.18.24 | 2026-04-18 | Fixed: GUI silently exits when another recorder running (now falls back to viewer mode), .vital file association written to HKCU (no admin required, auto-updates exe path on every run) | |
| 1.18.23 | 2026-04-18 | Fixed: atof decimal-separator bug on non-English Windows locales (rates <1.0 recorded as 0 in Norwegian/German/French etc.), BBraun HL7 pump limit raised from 8 to 16 | |
| 1.18.22 | 2026-04-18 | Fixed: multi-bed TCP port bind race on restart (Mindray/BBraun HL7 auto-reconnect without manual Add device) | |
| 1.18.21 | 2026-04-18 | Fixed: port name filter (#keyword@ip) lost on save, BBraun HL7 VMD-block parsing with single dt, Fit-to-Window button sizing (MFC) and added to Qt | |
| 1.18.20 | 2026-04-15 | BBraun: comprehensive protocol parsing (dose rate, infusion time, syringe, weight, drug amounts, delivery status) | |
| 1.18.19 | 2026-04-13 | Fixed: BBraun HL7 multi-pump identification (rack serial filtering), multi-bed forwarding, port filter parsing | |
| 1.18.18 | 2026-04-10 | Added: GE alarm limits, S5 alarm/ext3/ext1 parsing | |
| 1.18.17 | 2026-04-04 | Changed: TRACE debug-only guard, show_msg for file errors, deploy sequential builds | |
| 1.18.16 | 2026-04-04 | Added: TCP port filtering (#keyword@ip), frame forwarding for multi-bed. Fixed: --debug conf file mistaken as vital file, device type ambiguity in forwarding | |
| 1.18.15 | 2026-04-04 | Changed: Port to Port Name, keyword filter AND/OR (#kw1 kw2#kw3), IP postfix matching (@10.1), MEKICS SerialFramed | |
| 1.18.14 | 2026-04-03 | Added: TCP port filtering (#keyword@ip), Fixed: devtypes not sent on boot | |
| 1.18.13 | 2026-04-02 | Changed: Covidien company name to Medtronic (BIS, INVOS) | |
| 1.18.12 | 2026-04-02 | Bug Fixed: NihonKohden EGA SpO2 waveform not displayed (channel name mapping) | |
| 1.18.11 | 2026-03-31 | Added: Mindray HL7 multi-bed separation via PV1, auto-tab creation for EGA/Mindray | |
| 1.18.10 | 2026-03-31 | Added: HL7 ZBR segment now includes ver, os, arch, dgmt | |
| 1.18.8 | 2026-03-25 | Added: --demo mode for console operation with demo signals | |
| 1.18.7 | 2026-03-25 | Added: --console mode for headless operation on Windows | |
| 1.18.6 | 2026-03-25 | Added: Mindray eGateway HL7 protocol (IHE PCD-01, HL7 v2.6 over MLLP, waveform + numeric + infusion pump) | |
| 1.18.4 | 2026-03-24 | Added: BBraun SpaceCom HL7 protocol (HL7 v2.6 over MLLP, TCP server mode) | |
| 1.18.3 | 2026-03-24 | Added: HL7 OBX-14 per-track dtstart, ZBR segment with dtcase/dtapp timestamps | |
| 1.18.2 | 2026-03-24 | Added: HL7 ZBR segment with dtcase (recording start) and dtapp (app start) timestamps | |
| 1.18.1 | 2026-03-23 | Fixed: HL7 OBX-3 format (code^device/track@srate), OBX-7 refrange (min^max), NA null sample gaps | |
| 1.17.6 | 2026-03-22 | Bug fixed: Resource ID conflicts WiX installer cleanup | |
| 1.17.5 | 2026-03-21 | Bug fixed: Vital packed file loading MFC device button icon padding | |
| 1.17.4 | 2026-03-19 | Bug fixed: NirsitON EEG 250Hz VRQt device minimize/rename | |
| 1.17.3 | 2026-03-18 | Bug fixed: BBraun protocol NirsitON protocol VRQt | |
| 1.17.2 | 2026-03-14 | Added: NirsitOn NDI protocol support. | |
| 1.17.1 | 2026-03-12 | Added: VRQt | |
| 1.16.13 | 2026-03-07 | Added: New Setup File with VRQt | |
| 1.16.12 | 2026-03-06 | Added: Vector Type Icons | |
| 1.16.10 | 2026-03-02 | Bug fixed: Autodetecting BBraun Stuffing feature. | |
| 1.16.8 | 2026-02-27 | Bug fixed: Masimo IAP protocol. | |
| 1.16.4 | 2026-01-20 | Bug Fixed: Serial communication. | |
| 1.16.3 | 2026-01-14 | Bug Fixed: Masimo IAP Protocol. | |
| 1.16.2 | 2026-01-03 | Added: BLE Support for Movesense devices on Raspberry Pi | |
| 1.16.1 | 2026-01-01 | Added: BLE Support for Movesense devices on Raspberry Pi and Linux versions. | |
| 1.16.0 | 2025-12-31 | Added: BLE Support for Movesense devices | |
| 1.15.16 | 2025-09-18 | Bug Fixed: Restoring last window position. | |
| 1.15.15 | 2025-09-05 | Added: CPU optimization Bug fixed: Masimo IAP protocol. | |
| 1.15.14 | 2025-09-04 | Added: CPU optimization | |
| 1.15.13 | 2025-09-04 | Bug Fixed: Masimo IAP Protocol. | |
| 1.15.12 | 2025-09-04 | Bug Fixed: Hardware handshake for Link+ connection. | |
| 1.15.11 | 2025-09-01 | Added: Recording alarm messages for Drager anesthesia machines | |
| 1.15.10 | 2025-07-14 | Bug Fixed: Sampling rate estimation in Medibus protocol. | |
| 1.15.9 | 2025-06-03 | Bug Fixed: EEG waveform labels from Philips monitors. | |
| 1.15.8 | 2025-05-26 | Added: EM_UPLOAD option to upload vital files to server immediately | |
| 1.15.7 | 2025-05-26 | Bug Fixed: Websocket connection error | |
| 1.15.6 | 2025-05-19 | Bug Fixed: S5 checksum error | |
| 1.15.5 | 2025-04-17 | Bug Fixed: saving the raw data | |
| 1.15.4 | 2025-04-15 | Added: Recording alarms from Philips monitor | |
| 1.15.3 | 2025-04-14 | Bug fixed: saving the raw data | |
| 1.15.2 | 2025-03-12 | Fixed: bug in opening file | |
| 1.15.1 | 2025-01-30 | Supporting for Fresenius Kabi Conox, Minor Bug fixes | |
| 1.14.13 | 2025-01-18 | Fixed bug in handling non-ascii filepath | |
| 1.14.12 | 2024-11-20 | Fixed: bug in 64bit ARM Architecture | |
| 1.14.11 | 2024-10-24 | Added: feature for updating to a specific version | |
| 1.14.10 | 2024-10-22 | Fixed: bug in json streamming | |
| 1.14.9 | 2024-10-17 | Added: montype for drug rates | |
| 1.14.6 | 2024-09-20 | Added: montype for drug rates | |
| 1.14.5 | 2024-09-13 | Added: auto cutting by any of hr or spo2 | |
| 1.14.4 | 2024-09-12 | Fixed: bug in serial port mapping |
| Version | Date | Changes | |
|---|---|---|---|
| 1.19.36 | 2026-10-13 | 1.19.36 Added: Windows 에서 아이콘으로 실행해도 로그가 설정 폴더의 vr.log 에 남는다. 지금까지는 로그가 화면에 출력되는 곳이 없으면 그대로 사라져서, 장비가 요청을 거절한 이유처럼 문제를 가리는 데 필요한 기록을 현장에서 볼 방법이 없었다. 줄마다 날짜와 시각이 붙고, 10MB 를 넘으면 시작할 때 vr.log.1 로 넘긴다. --debug 로 실행하거나 출력을 다른 파일로 돌린 경우는 종전대로 그쪽으로 나간다. Fixed: VitalRecorder Qt 에서 트랙 목록의 세로 스크롤바를 마우스로 움직일 수 없던 문제. 스크롤바가 그려지기만 하고 마우스 입력을 받지 않아 방향키로만 이동할 수 있었다. 이제 막대를 끌거나 빈 곳을 눌러 이동할 수 있다. Fixed: VitalRecorder Qt 에서 장비 편집 창이 검은 바탕에 검은 글씨로 나와 읽기 어렵던 문제. 장비 추가 창과 같은 밝은 모양으로 나온다. 창의 최소 폭도 넓혀 Intellivue 파형 선택 항목이 잘리지 않는다. Fixed: VitalRecorder Qt 에서 장비 설정을 바꾸면 오른쪽 장비 목록에 빈 칸이 남던 문제. 그 빈 칸의 편집 버튼을 누르면 같은 장비가 하나 더 생기기도 했다. (Nord University 제보) | |
| 1.19.35 | 2026-10-08 | 1.19.35 Fixed: Edwards 혈역학 모니터(HemoSphere 등)의 값이 Philips Intellivue 를 거쳐 들어올 때 연속 심박출량(CO_CTS), 연속 심장지수(CI_CTS), 일회박출량 변이(VOL_BLD_STROKE_VAR, SVV)가 웹 모니터링 화면에 나오지 않던 문제. 값은 정상으로 수집·저장되고 있었지만, 웹 화면이 어느 칸에 표시할지 정하는 분류(모니터 유형)가 지정되지 않아 표시 대상에서 빠졌다. 이제 각각 CO, CI, SVV 로 분류해 다른 장비의 같은 값과 같은 자리에 나온다. 이미 만들어진 녹화 파일에는 소급되지 않는다. | |
| 1.19.34 | 2026-10-06 | 1.19.34 Fixed: Nihon Kohden Vismo PVM-4700 시리즈(PVM-4761 등)와 구형 BSM-3000/6000, CSM-1500/1700 에서 시리얼로 아무 값도 들어오지 않던 문제. VR 은 확장 수치·심방세동까지 받으려고 ?D;EALL 요청의 Option 을 2 로 보내는데, 이 기종들은 규격상 Option 0~1 만 받아들여 매 요청을 !E;04(인자 미지원)로 거절했고, VR 은 그 거절을 읽지 않아 1초마다 같은 요청을 되풀이했다 (강남차병원 PVM-4761 실측 2026-09-07: 5분 동안 거절 응답만 300회). 이제 거절을 받으면 Option 을 한 단계씩 내려 다시 요청하고(2→1→0), 부정맥 비트 해석표도 그 Option 에 맞는 표(규격 Table 7/8/9)로 바꾼다. 어느 Option 으로 내려갔는지 로그에 남긴다. 측정하지 않은 파라미터에 대한 응답(!E;80/81, 예: NIBP 미측정)은 정상이므로 조용히 넘기고, 그 밖의 에러 응답은 60초에 한 번 로그에 남긴다. 가짜 모니터로 2→1 폴백과 HR/PR/SpO2/RR/체온 수집을 확인했다. 파형은 이 경로(RS-232C)로는 규격상 오지 않는다. PVM-4700 은 QI-470P 인터페이스의 RS-232C 커넥터와 YS-089P 계열 케이블을 쓴다 (0614-905457H). Added: 장비 목록에 Nihon Kohden : PVM 을 따로 두었다. BSM 과 같은 통신 규격이지만 PVM-4700 은 처음부터 지원하는 범위로 요청하므로 거절 응답 없이 바로 값이 들어온다. vr.conf 에는 type=PVM 으로 적는다. 기존에 BSM 으로 설정한 PVM 도 위의 자동 하향 덕분에 그대로 동작한다. | |
| 1.19.33 | 2026-10-05 | 1.19.33 Added: Philips Intellivue 가 VR 의 요청을 거절하면 로그에 남긴다. 어떤 요청(파형 폴, 파형 우선순위 목록, 수치 폴, 환자정보 등)이 어떤 이유(ACCESS_DENIED, SET_LIST_ERROR, INVALID_ARGUMENT_VALUE 등)로 거절됐는지 적는다. 종전에는 이 응답이 디버그 빌드에서만 보이는 흔적으로 처리돼 현장 로그에 아무것도 남지 않았고, 그래서 “수치는 들어오는데 파형만 없다” 는 상황에서 VR 이 요청을 안 한 것인지 모니터가 거부한 것인지를 로그만으로 가릴 수 없었다 (Nord University 실험실, 1.19.22~1.19.32 동일 증상). Added: 파형 폴을 보내는데 파형 묶음이 30초 넘게 돌아오지 않으면 5분에 한 번 경고를 남긴다. 요청한 파형 목록을 함께 적어 모니터 Data Export 설정과 바로 대조할 수 있다. 같은 코드의 거절은 60초에 한 번만 적는다. 1.19.32 Added: Philips Intellivue 의 파형 자동 추가를 끌 수 있다 (auto_wavs=0, 장비 설정 창의 체크박스). 종전에는 설정 창에서 고른 파형에 더해 수치가 들어오는 파형(ECG 한 리드, PLETH, ABP/ART/AoP, CVP, ICP, CO2, 기도압·유량)을 자동으로 보탰고 이것을 막을 방법이 없었다. 침습압이 여럿인 실험실 구성에서는 그 합이 시리얼 회선이 감당할 양을 넘고, 그러면 모니터는 파형을 일부만 줄이는 것이 아니라 통째로 멈춘다 — 수치는 계속 들어오는데 파형만 몇 초 만에 사라지는 모습이 된다 (Nord University 실측: 파형 8종이 5초 뒤 멎음, 설정 창에 적힌 한계 “ECG 3개 또는 비ECG 8개” 를 ECG 2개+6개로 넘긴 경우). 이제 체크를 끄면 고른 것만 요청한다. 기본값은 종전과 같이 자동 추가다. Added: Intellivue 에 요청하는 파형 목록이 바뀔 때마다 로그에 남긴다. 파형이 안 들어올 때 요청을 안 한 것인지, 요청했는데 모니터가 안 주는 것인지를 로그만으로 가를 수 있다. 1.19.31 Fixed: Windows 에서 시리얼 장비의 값이 두 번에 한 번씩 약 0.23초 늦게 기록되던 문제. 1초에 한 번 보내는 SyncPulseSerial(동기 펄스 생성기)에서 펄스 간격이 1초가 아니라 0.77초와 1.23초로 번갈아 기록됐다 (Nord University AniLab 실측 9/5·9/10, 두 펄스를 합하면 정확히 2.000초). 장비 쪽은 정확했다 — 같은 회선을 시리얼 터미널로 받으면 ±1ms 로 1초마다 들어온다. 원인은 VR 이 문자 도착을 기다리기 전에 매번 100ms 를 쉬었고, 그 100ms 안에 들어온 데이터는 Windows 가 알려주지 않아 200ms 뒤 확인 때에야 읽혔다는 것이다. 이 대기 한 바퀴(약 310ms)와 1초 주기가 맞물려 한 프레임은 즉시, 다음 프레임은 230ms 늦게 읽는 패턴이 고정됐다. 이제 기다리기 전에 수신 버퍼를 먼저 확인하고 쉬지 않는다. 도착 즉시 읽으므로 기록 시각의 오차는 USB 폴링 수준(수 ms)만 남는다. 초당 수십 바이트 이상 보내는 일반 환자 모니터에서는 이 지연이 파형 시각에 묻혀 드러나지 않았다. Note: 1.19.25 설명 정정 — Windows 수신 대기가 원인이 아니라고 적었으나(가상 직렬 포트 A/B 에서 재현되지 않았다), 실제 USB 시리얼에서는 위와 같이 재현된다. 가상 포트는 문자 도착 알림 방식이 달라 시험이 빗나갔다. 1.19.30 Fixed: 기기 시각이 크게 어긋났을 때 저절로 맞춰지지 않고 그대로 유지되던 문제. 원주세브란스 EICU09 에서 실제보다 55분 늦은 상태가 계속됐고, 그 결과 해당 병상이 화면에서 자료 없음으로 보였다 (웹 화면은 마지막으로 자료가 들어온 지 1분이 넘은 병상을 끊긴 것으로 본다). 서버가 알려주는 시각으로 맞추는 기능 자체는 동작하고 있었다. 다만 녹화 중에는 파형에 공백이 생기지 않도록 시계를 한 번에 옮기지 않고 아주 천천히 당기도록 돼 있었는데, 그 속도가 하루에 약 43초라 55분을 되돌리려면 두 달이 넘게 걸린다. 천천히 당기는 방식은 이미 가까운 시계를 가깝게 유지하는 수단이지 벌어진 것을 따라잡는 수단이 아니라는 점이 코드에 반영돼 있지 않았다. 이제 두 경우에는 녹화 중이라도 한 번에 맞춘다 — 차이가 1분을 넘어 천천히 당겨서는 따라잡을 수 없을 때, 그리고 SAVE_FILE=0 으로 파일을 만들지 않아 지켜야 할 기록이 애초에 없을 때. 어느 쪽이든 맞춘 이유를 로그에 남긴다. ⚠ 시계를 따로 갖고 있지 않은 장비(라즈베리파이)는 전원이 꺼지면 시각을 잃는다. 기관의 시각 서버(NTP)를 함께 물려 두면 이 기능에 기대지 않아도 된다. 1.19.29 Added: 녹화 파일을 만들지 않고 웹 모니터링만 하는 운용 방식. vr.conf 에 SAVE_FILE=0 을 넣으면 .vital 파일을 만들지 않는다. 무선 대역폭이 부족해 파일을 남기지 않기로 한 기관을 위한 것이다. 종전에는 저장 폴더를 없는 경로로 지정해 ‘열기 실패’ 로 같은 효과를 내는 편법이 쓰였는데, 그 방법은 파일을 만들 때마다 오류가 로그에 쌓이고 화면에는 녹화 중으로 보였다. ⚠ 이 방식에서는 네트워크가 끊긴 구간의 자료가 영구히 사라진다 — 지금까지는 파일이 전송 실패의 완충재 역할을 했다. Changed: 웹 모니터링 전송량이 25~35% 줄었다. 파형 값을 늘 소수점 셋째 자리까지 보내고 있었는데, 화면은 그 트랙의 표시 범위 안에서 그리므로 범위의 1% 보다 고운 눈금은 어차피 구분되지 않는다 (심전도는 표시 범위가 4 mV 인데 0.001 단위로 보내 화면이 나타낼 수 있는 것의 40배를 보내고 있었다). 게다가 그 여분의 자릿수는 규칙성이 없어 압축도 되지 않아, 전송량의 4분의 1 이상을 차지했다 (중환자실 3개 병상 실측 26~35%). 이제 표시 범위에 맞춰 자릿수를 정한다. 값의 단위와 의미는 그대로이고 녹화 파일에는 원래 정밀도로 저장되므로 화면과 파일 모두 달라지는 것이 없으며, 서버나 웹 화면을 함께 고칠 필요도 없다. 예전 방식이 필요하면 WEB_WAV_PREC=3 으로 고정할 수 있다. Fixed: 녹화 파일을 만들지 못했을 때 아무 알림 없이 지나가던 문제. 디스크가 가득 찼거나 저장 폴더에 쓸 수 없으면 파일이 만들어지지 않는데, 화면에는 ‘녹화 중’ 으로 표시되고 자료는 조용히 버려졌다. 이제 로그에 경고를 남긴다. 1.19.28 Fixed: USB 로 연결한 장비의 수집이 어느 순간 조용히 멎고, 프로그램을 다시 켜야만 돌아오던 문제. 전원이 잠깐 흔들리면 USB 장비가 스스로 연결을 다시 맺는데 그때 운영체제가 새 포트 번호를 붙인다 (예: ttyACM0 이 ttyACM1 로). VR 은 처음 열었던 번호를 계속 붙들고 있어서, 장비도 케이블도 멀쩡한데 값만 들어오지 않는다 — 화면상 연결 상태도 정상으로 보여 눈으로는 알 수 없었다 (수술방의 Link+ 주입펌프에서 확인). Added: 포트 자리에 포트 번호 대신 장비의 고유 신원을 적을 수 있다. 형식은 20d0:0002 처럼 네 자리 두 개이며, 이 값은 장비에 박혀 있어 다시 연결되어도 바뀌지 않는다. 이렇게 적어 두면 VR 이 포트를 열 때마다 그 신원을 가진 자리를 다시 찾으므로, 번호가 바뀌어도 저절로 따라간다. 기존처럼 포트 번호를 적어 둔 곳은 그대로 동작한다. Changed: Link+ 는 포트를 적지 않으면 이 신원을 기본값으로 쓴다. 꽂기만 하면 잡히고, 다시 연결되어도 따라간다. 포트를 직접 적어 둔 곳은 적은 값이 우선이다. Changed: 자동 판별이 USB 신원을 먼저 본다. 고유 신원으로 자기를 밝히는 장비는 통신 속도·형식을 재 볼 필요가 없으므로 조용한 회선에서 72초가 걸리던 측정을 건너뛰고 바로 확정한다. 이렇게 확정한 장비는 포트를 번호가 아니라 신원으로 기록한다. 여러 장비가 함께 쓰는 범용 USB 변환 케이블(FTDI 등)은 신원이 케이블의 것이지 장비의 것이 아니므로 종전대로 측정해서 판별한다. 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.32 | 2026-10-05 | 1.19.32 Added: Philips Intellivue 의 파형 자동 추가를 끌 수 있다 (auto_wavs=0, 장비 설정 창의 체크박스). 종전에는 설정 창에서 고른 파형에 더해 수치가 들어오는 파형(ECG 한 리드, PLETH, ABP/ART/AoP, CVP, ICP, CO2, 기도압·유량)을 자동으로 보탰고 이것을 막을 방법이 없었다. 침습압이 여럿인 실험실 구성에서는 그 합이 시리얼 회선이 감당할 양을 넘고, 그러면 모니터는 파형을 일부만 줄이는 것이 아니라 통째로 멈춘다 — 수치는 계속 들어오는데 파형만 몇 초 만에 사라지는 모습이 된다 (Nord University 실측: 파형 8종이 5초 뒤 멎음, 설정 창에 적힌 한계 “ECG 3개 또는 비ECG 8개” 를 ECG 2개+6개로 넘긴 경우). 이제 체크를 끄면 고른 것만 요청한다. 기본값은 종전과 같이 자동 추가다. Added: Intellivue 에 요청하는 파형 목록이 바뀔 때마다 로그에 남긴다. 파형이 안 들어올 때 요청을 안 한 것인지, 요청했는데 모니터가 안 주는 것인지를 로그만으로 가를 수 있다. 1.19.31 Fixed: Windows 에서 시리얼 장비의 값이 두 번에 한 번씩 약 0.23초 늦게 기록되던 문제. 1초에 한 번 보내는 SyncPulseSerial(동기 펄스 생성기)에서 펄스 간격이 1초가 아니라 0.77초와 1.23초로 번갈아 기록됐다 (Nord University AniLab 실측 9/5·9/10, 두 펄스를 합하면 정확히 2.000초). 장비 쪽은 정확했다 — 같은 회선을 시리얼 터미널로 받으면 ±1ms 로 1초마다 들어온다. 원인은 VR 이 문자 도착을 기다리기 전에 매번 100ms 를 쉬었고, 그 100ms 안에 들어온 데이터는 Windows 가 알려주지 않아 200ms 뒤 확인 때에야 읽혔다는 것이다. 이 대기 한 바퀴(약 310ms)와 1초 주기가 맞물려 한 프레임은 즉시, 다음 프레임은 230ms 늦게 읽는 패턴이 고정됐다. 이제 기다리기 전에 수신 버퍼를 먼저 확인하고 쉬지 않는다. 도착 즉시 읽으므로 기록 시각의 오차는 USB 폴링 수준(수 ms)만 남는다. 초당 수십 바이트 이상 보내는 일반 환자 모니터에서는 이 지연이 파형 시각에 묻혀 드러나지 않았다. Note: 1.19.25 설명 정정 — Windows 수신 대기가 원인이 아니라고 적었으나(가상 직렬 포트 A/B 에서 재현되지 않았다), 실제 USB 시리얼에서는 위와 같이 재현된다. 가상 포트는 문자 도착 알림 방식이 달라 시험이 빗나갔다. 1.19.30 Fixed: 기기 시각이 크게 어긋났을 때 저절로 맞춰지지 않고 그대로 유지되던 문제. 원주세브란스 EICU09 에서 실제보다 55분 늦은 상태가 계속됐고, 그 결과 해당 병상이 화면에서 자료 없음으로 보였다 (웹 화면은 마지막으로 자료가 들어온 지 1분이 넘은 병상을 끊긴 것으로 본다). 서버가 알려주는 시각으로 맞추는 기능 자체는 동작하고 있었다. 다만 녹화 중에는 파형에 공백이 생기지 않도록 시계를 한 번에 옮기지 않고 아주 천천히 당기도록 돼 있었는데, 그 속도가 하루에 약 43초라 55분을 되돌리려면 두 달이 넘게 걸린다. 천천히 당기는 방식은 이미 가까운 시계를 가깝게 유지하는 수단이지 벌어진 것을 따라잡는 수단이 아니라는 점이 코드에 반영돼 있지 않았다. 이제 두 경우에는 녹화 중이라도 한 번에 맞춘다 — 차이가 1분을 넘어 천천히 당겨서는 따라잡을 수 없을 때, 그리고 SAVE_FILE=0 으로 파일을 만들지 않아 지켜야 할 기록이 애초에 없을 때. 어느 쪽이든 맞춘 이유를 로그에 남긴다. ⚠ 시계를 따로 갖고 있지 않은 장비(라즈베리파이)는 전원이 꺼지면 시각을 잃는다. 기관의 시각 서버(NTP)를 함께 물려 두면 이 기능에 기대지 않아도 된다. 1.19.29 Added: 녹화 파일을 만들지 않고 웹 모니터링만 하는 운용 방식. vr.conf 에 SAVE_FILE=0 을 넣으면 .vital 파일을 만들지 않는다. 무선 대역폭이 부족해 파일을 남기지 않기로 한 기관을 위한 것이다. 종전에는 저장 폴더를 없는 경로로 지정해 ‘열기 실패’ 로 같은 효과를 내는 편법이 쓰였는데, 그 방법은 파일을 만들 때마다 오류가 로그에 쌓이고 화면에는 녹화 중으로 보였다. ⚠ 이 방식에서는 네트워크가 끊긴 구간의 자료가 영구히 사라진다 — 지금까지는 파일이 전송 실패의 완충재 역할을 했다. Changed: 웹 모니터링 전송량이 25~35% 줄었다. 파형 값을 늘 소수점 셋째 자리까지 보내고 있었는데, 화면은 그 트랙의 표시 범위 안에서 그리므로 범위의 1% 보다 고운 눈금은 어차피 구분되지 않는다 (심전도는 표시 범위가 4 mV 인데 0.001 단위로 보내 화면이 나타낼 수 있는 것의 40배를 보내고 있었다). 게다가 그 여분의 자릿수는 규칙성이 없어 압축도 되지 않아, 전송량의 4분의 1 이상을 차지했다 (중환자실 3개 병상 실측 26~35%). 이제 표시 범위에 맞춰 자릿수를 정한다. 값의 단위와 의미는 그대로이고 녹화 파일에는 원래 정밀도로 저장되므로 화면과 파일 모두 달라지는 것이 없으며, 서버나 웹 화면을 함께 고칠 필요도 없다. 예전 방식이 필요하면 WEB_WAV_PREC=3 으로 고정할 수 있다. Fixed: 녹화 파일을 만들지 못했을 때 아무 알림 없이 지나가던 문제. 디스크가 가득 찼거나 저장 폴더에 쓸 수 없으면 파일이 만들어지지 않는데, 화면에는 ‘녹화 중’ 으로 표시되고 자료는 조용히 버려졌다. 이제 로그에 경고를 남긴다. 1.19.28 Fixed: USB 로 연결한 장비의 수집이 어느 순간 조용히 멎고, 프로그램을 다시 켜야만 돌아오던 문제. 전원이 잠깐 흔들리면 USB 장비가 스스로 연결을 다시 맺는데 그때 운영체제가 새 포트 번호를 붙인다 (예: ttyACM0 이 ttyACM1 로). VR 은 처음 열었던 번호를 계속 붙들고 있어서, 장비도 케이블도 멀쩡한데 값만 들어오지 않는다 — 화면상 연결 상태도 정상으로 보여 눈으로는 알 수 없었다 (수술방의 Link+ 주입펌프에서 확인). Added: 포트 자리에 포트 번호 대신 장비의 고유 신원을 적을 수 있다. 형식은 20d0:0002 처럼 네 자리 두 개이며, 이 값은 장비에 박혀 있어 다시 연결되어도 바뀌지 않는다. 이렇게 적어 두면 VR 이 포트를 열 때마다 그 신원을 가진 자리를 다시 찾으므로, 번호가 바뀌어도 저절로 따라간다. 기존처럼 포트 번호를 적어 둔 곳은 그대로 동작한다. Changed: Link+ 는 포트를 적지 않으면 이 신원을 기본값으로 쓴다. 꽂기만 하면 잡히고, 다시 연결되어도 따라간다. 포트를 직접 적어 둔 곳은 적은 값이 우선이다. Changed: 자동 판별이 USB 신원을 먼저 본다. 고유 신원으로 자기를 밝히는 장비는 통신 속도·형식을 재 볼 필요가 없으므로 조용한 회선에서 72초가 걸리던 측정을 건너뛰고 바로 확정한다. 이렇게 확정한 장비는 포트를 번호가 아니라 신원으로 기록한다. 여러 장비가 함께 쓰는 범용 USB 변환 케이블(FTDI 등)은 신원이 케이블의 것이지 장비의 것이 아니므로 종전대로 측정해서 판별한다. 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.31 | 2026-09-27 | 1.19.31 Fixed: Windows 에서 시리얼 장비의 값이 두 번에 한 번씩 약 0.23초 늦게 기록되던 문제. 1초에 한 번 보내는 SyncPulseSerial(동기 펄스 생성기)에서 펄스 간격이 1초가 아니라 0.77초와 1.23초로 번갈아 기록됐다 (Nord University AniLab 실측 9/5·9/10, 두 펄스를 합하면 정확히 2.000초). 장비 쪽은 정확했다 — 같은 회선을 시리얼 터미널로 받으면 ±1ms 로 1초마다 들어온다. 원인은 VR 이 문자 도착을 기다리기 전에 매번 100ms 를 쉬었고, 그 100ms 안에 들어온 데이터는 Windows 가 알려주지 않아 200ms 뒤 확인 때에야 읽혔다는 것이다. 이 대기 한 바퀴(약 310ms)와 1초 주기가 맞물려 한 프레임은 즉시, 다음 프레임은 230ms 늦게 읽는 패턴이 고정됐다. 이제 기다리기 전에 수신 버퍼를 먼저 확인하고 쉬지 않는다. 도착 즉시 읽으므로 기록 시각의 오차는 USB 폴링 수준(수 ms)만 남는다. 초당 수십 바이트 이상 보내는 일반 환자 모니터에서는 이 지연이 파형 시각에 묻혀 드러나지 않았다. Note: 1.19.25 설명 정정 — Windows 수신 대기가 원인이 아니라고 적었으나(가상 직렬 포트 A/B 에서 재현되지 않았다), 실제 USB 시리얼에서는 위와 같이 재현된다. 가상 포트는 문자 도착 알림 방식이 달라 시험이 빗나갔다. 1.19.30 Fixed: 기기 시각이 크게 어긋났을 때 저절로 맞춰지지 않고 그대로 유지되던 문제. 원주세브란스 EICU09 에서 실제보다 55분 늦은 상태가 계속됐고, 그 결과 해당 병상이 화면에서 자료 없음으로 보였다 (웹 화면은 마지막으로 자료가 들어온 지 1분이 넘은 병상을 끊긴 것으로 본다). 서버가 알려주는 시각으로 맞추는 기능 자체는 동작하고 있었다. 다만 녹화 중에는 파형에 공백이 생기지 않도록 시계를 한 번에 옮기지 않고 아주 천천히 당기도록 돼 있었는데, 그 속도가 하루에 약 43초라 55분을 되돌리려면 두 달이 넘게 걸린다. 천천히 당기는 방식은 이미 가까운 시계를 가깝게 유지하는 수단이지 벌어진 것을 따라잡는 수단이 아니라는 점이 코드에 반영돼 있지 않았다. 이제 두 경우에는 녹화 중이라도 한 번에 맞춘다 — 차이가 1분을 넘어 천천히 당겨서는 따라잡을 수 없을 때, 그리고 SAVE_FILE=0 으로 파일을 만들지 않아 지켜야 할 기록이 애초에 없을 때. 어느 쪽이든 맞춘 이유를 로그에 남긴다. ⚠ 시계를 따로 갖고 있지 않은 장비(라즈베리파이)는 전원이 꺼지면 시각을 잃는다. 기관의 시각 서버(NTP)를 함께 물려 두면 이 기능에 기대지 않아도 된다. 1.19.29 Added: 녹화 파일을 만들지 않고 웹 모니터링만 하는 운용 방식. vr.conf 에 SAVE_FILE=0 을 넣으면 .vital 파일을 만들지 않는다. 무선 대역폭이 부족해 파일을 남기지 않기로 한 기관을 위한 것이다. 종전에는 저장 폴더를 없는 경로로 지정해 ‘열기 실패’ 로 같은 효과를 내는 편법이 쓰였는데, 그 방법은 파일을 만들 때마다 오류가 로그에 쌓이고 화면에는 녹화 중으로 보였다. ⚠ 이 방식에서는 네트워크가 끊긴 구간의 자료가 영구히 사라진다 — 지금까지는 파일이 전송 실패의 완충재 역할을 했다. Changed: 웹 모니터링 전송량이 25~35% 줄었다. 파형 값을 늘 소수점 셋째 자리까지 보내고 있었는데, 화면은 그 트랙의 표시 범위 안에서 그리므로 범위의 1% 보다 고운 눈금은 어차피 구분되지 않는다 (심전도는 표시 범위가 4 mV 인데 0.001 단위로 보내 화면이 나타낼 수 있는 것의 40배를 보내고 있었다). 게다가 그 여분의 자릿수는 규칙성이 없어 압축도 되지 않아, 전송량의 4분의 1 이상을 차지했다 (중환자실 3개 병상 실측 26~35%). 이제 표시 범위에 맞춰 자릿수를 정한다. 값의 단위와 의미는 그대로이고 녹화 파일에는 원래 정밀도로 저장되므로 화면과 파일 모두 달라지는 것이 없으며, 서버나 웹 화면을 함께 고칠 필요도 없다. 예전 방식이 필요하면 WEB_WAV_PREC=3 으로 고정할 수 있다. Fixed: 녹화 파일을 만들지 못했을 때 아무 알림 없이 지나가던 문제. 디스크가 가득 찼거나 저장 폴더에 쓸 수 없으면 파일이 만들어지지 않는데, 화면에는 ‘녹화 중’ 으로 표시되고 자료는 조용히 버려졌다. 이제 로그에 경고를 남긴다. 1.19.28 Fixed: USB 로 연결한 장비의 수집이 어느 순간 조용히 멎고, 프로그램을 다시 켜야만 돌아오던 문제. 전원이 잠깐 흔들리면 USB 장비가 스스로 연결을 다시 맺는데 그때 운영체제가 새 포트 번호를 붙인다 (예: ttyACM0 이 ttyACM1 로). VR 은 처음 열었던 번호를 계속 붙들고 있어서, 장비도 케이블도 멀쩡한데 값만 들어오지 않는다 — 화면상 연결 상태도 정상으로 보여 눈으로는 알 수 없었다 (수술방의 Link+ 주입펌프에서 확인). Added: 포트 자리에 포트 번호 대신 장비의 고유 신원을 적을 수 있다. 형식은 20d0:0002 처럼 네 자리 두 개이며, 이 값은 장비에 박혀 있어 다시 연결되어도 바뀌지 않는다. 이렇게 적어 두면 VR 이 포트를 열 때마다 그 신원을 가진 자리를 다시 찾으므로, 번호가 바뀌어도 저절로 따라간다. 기존처럼 포트 번호를 적어 둔 곳은 그대로 동작한다. Changed: Link+ 는 포트를 적지 않으면 이 신원을 기본값으로 쓴다. 꽂기만 하면 잡히고, 다시 연결되어도 따라간다. 포트를 직접 적어 둔 곳은 적은 값이 우선이다. Changed: 자동 판별이 USB 신원을 먼저 본다. 고유 신원으로 자기를 밝히는 장비는 통신 속도·형식을 재 볼 필요가 없으므로 조용한 회선에서 72초가 걸리던 측정을 건너뛰고 바로 확정한다. 이렇게 확정한 장비는 포트를 번호가 아니라 신원으로 기록한다. 여러 장비가 함께 쓰는 범용 USB 변환 케이블(FTDI 등)은 신원이 케이블의 것이지 장비의 것이 아니므로 종전대로 측정해서 판별한다. 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.30 | 2026-09-16 | 1.19.30 Fixed: 기기 시각이 크게 어긋났을 때 저절로 맞춰지지 않고 그대로 유지되던 문제. 원주세브란스 EICU09 에서 실제보다 55분 늦은 상태가 계속됐고, 그 결과 해당 병상이 화면에서 자료 없음으로 보였다 (웹 화면은 마지막으로 자료가 들어온 지 1분이 넘은 병상을 끊긴 것으로 본다). 서버가 알려주는 시각으로 맞추는 기능 자체는 동작하고 있었다. 다만 녹화 중에는 파형에 공백이 생기지 않도록 시계를 한 번에 옮기지 않고 아주 천천히 당기도록 돼 있었는데, 그 속도가 하루에 약 43초라 55분을 되돌리려면 두 달이 넘게 걸린다. 천천히 당기는 방식은 이미 가까운 시계를 가깝게 유지하는 수단이지 벌어진 것을 따라잡는 수단이 아니라는 점이 코드에 반영돼 있지 않았다. 이제 두 경우에는 녹화 중이라도 한 번에 맞춘다 — 차이가 1분을 넘어 천천히 당겨서는 따라잡을 수 없을 때, 그리고 SAVE_FILE=0 으로 파일을 만들지 않아 지켜야 할 기록이 애초에 없을 때. 어느 쪽이든 맞춘 이유를 로그에 남긴다. ⚠ 시계를 따로 갖고 있지 않은 장비(라즈베리파이)는 전원이 꺼지면 시각을 잃는다. 기관의 시각 서버(NTP)를 함께 물려 두면 이 기능에 기대지 않아도 된다. 1.19.29 Added: 녹화 파일을 만들지 않고 웹 모니터링만 하는 운용 방식. vr.conf 에 SAVE_FILE=0 을 넣으면 .vital 파일을 만들지 않는다. 무선 대역폭이 부족해 파일을 남기지 않기로 한 기관을 위한 것이다. 종전에는 저장 폴더를 없는 경로로 지정해 ‘열기 실패’ 로 같은 효과를 내는 편법이 쓰였는데, 그 방법은 파일을 만들 때마다 오류가 로그에 쌓이고 화면에는 녹화 중으로 보였다. ⚠ 이 방식에서는 네트워크가 끊긴 구간의 자료가 영구히 사라진다 — 지금까지는 파일이 전송 실패의 완충재 역할을 했다. Changed: 웹 모니터링 전송량이 25~35% 줄었다. 파형 값을 늘 소수점 셋째 자리까지 보내고 있었는데, 화면은 그 트랙의 표시 범위 안에서 그리므로 범위의 1% 보다 고운 눈금은 어차피 구분되지 않는다 (심전도는 표시 범위가 4 mV 인데 0.001 단위로 보내 화면이 나타낼 수 있는 것의 40배를 보내고 있었다). 게다가 그 여분의 자릿수는 규칙성이 없어 압축도 되지 않아, 전송량의 4분의 1 이상을 차지했다 (중환자실 3개 병상 실측 26~35%). 이제 표시 범위에 맞춰 자릿수를 정한다. 값의 단위와 의미는 그대로이고 녹화 파일에는 원래 정밀도로 저장되므로 화면과 파일 모두 달라지는 것이 없으며, 서버나 웹 화면을 함께 고칠 필요도 없다. 예전 방식이 필요하면 WEB_WAV_PREC=3 으로 고정할 수 있다. Fixed: 녹화 파일을 만들지 못했을 때 아무 알림 없이 지나가던 문제. 디스크가 가득 찼거나 저장 폴더에 쓸 수 없으면 파일이 만들어지지 않는데, 화면에는 ‘녹화 중’ 으로 표시되고 자료는 조용히 버려졌다. 이제 로그에 경고를 남긴다. 1.19.28 Fixed: USB 로 연결한 장비의 수집이 어느 순간 조용히 멎고, 프로그램을 다시 켜야만 돌아오던 문제. 전원이 잠깐 흔들리면 USB 장비가 스스로 연결을 다시 맺는데 그때 운영체제가 새 포트 번호를 붙인다 (예: ttyACM0 이 ttyACM1 로). VR 은 처음 열었던 번호를 계속 붙들고 있어서, 장비도 케이블도 멀쩡한데 값만 들어오지 않는다 — 화면상 연결 상태도 정상으로 보여 눈으로는 알 수 없었다 (수술방의 Link+ 주입펌프에서 확인). Added: 포트 자리에 포트 번호 대신 장비의 고유 신원을 적을 수 있다. 형식은 20d0:0002 처럼 네 자리 두 개이며, 이 값은 장비에 박혀 있어 다시 연결되어도 바뀌지 않는다. 이렇게 적어 두면 VR 이 포트를 열 때마다 그 신원을 가진 자리를 다시 찾으므로, 번호가 바뀌어도 저절로 따라간다. 기존처럼 포트 번호를 적어 둔 곳은 그대로 동작한다. Changed: Link+ 는 포트를 적지 않으면 이 신원을 기본값으로 쓴다. 꽂기만 하면 잡히고, 다시 연결되어도 따라간다. 포트를 직접 적어 둔 곳은 적은 값이 우선이다. Changed: 자동 판별이 USB 신원을 먼저 본다. 고유 신원으로 자기를 밝히는 장비는 통신 속도·형식을 재 볼 필요가 없으므로 조용한 회선에서 72초가 걸리던 측정을 건너뛰고 바로 확정한다. 이렇게 확정한 장비는 포트를 번호가 아니라 신원으로 기록한다. 여러 장비가 함께 쓰는 범용 USB 변환 케이블(FTDI 등)은 신원이 케이블의 것이지 장비의 것이 아니므로 종전대로 측정해서 판별한다. 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.29 | 2026-09-15 | 1.19.29 Added: 녹화 파일을 만들지 않고 웹 모니터링만 하는 운용 방식. vr.conf 에 SAVE_FILE=0 을 넣으면 .vital 파일을 만들지 않는다. 무선 대역폭이 부족해 파일을 남기지 않기로 한 기관을 위한 것이다. 종전에는 저장 폴더를 없는 경로로 지정해 ‘열기 실패’ 로 같은 효과를 내는 편법이 쓰였는데, 그 방법은 파일을 만들 때마다 오류가 로그에 쌓이고 화면에는 녹화 중으로 보였다. ⚠ 이 방식에서는 네트워크가 끊긴 구간의 자료가 영구히 사라진다 — 지금까지는 파일이 전송 실패의 완충재 역할을 했다. Changed: 웹 모니터링 전송량이 25~35% 줄었다. 파형 값을 늘 소수점 셋째 자리까지 보내고 있었는데, 화면은 그 트랙의 표시 범위 안에서 그리므로 범위의 1% 보다 고운 눈금은 어차피 구분되지 않는다 (심전도는 표시 범위가 4 mV 인데 0.001 단위로 보내 화면이 나타낼 수 있는 것의 40배를 보내고 있었다). 게다가 그 여분의 자릿수는 규칙성이 없어 압축도 되지 않아, 전송량의 4분의 1 이상을 차지했다 (중환자실 3개 병상 실측 26~35%). 이제 표시 범위에 맞춰 자릿수를 정한다. 값의 단위와 의미는 그대로이고 녹화 파일에는 원래 정밀도로 저장되므로 화면과 파일 모두 달라지는 것이 없으며, 서버나 웹 화면을 함께 고칠 필요도 없다. 예전 방식이 필요하면 WEB_WAV_PREC=3 으로 고정할 수 있다. Fixed: 녹화 파일을 만들지 못했을 때 아무 알림 없이 지나가던 문제. 디스크가 가득 찼거나 저장 폴더에 쓸 수 없으면 파일이 만들어지지 않는데, 화면에는 ‘녹화 중’ 으로 표시되고 자료는 조용히 버려졌다. 이제 로그에 경고를 남긴다. 1.19.28 Fixed: USB 로 연결한 장비의 수집이 어느 순간 조용히 멎고, 프로그램을 다시 켜야만 돌아오던 문제. 전원이 잠깐 흔들리면 USB 장비가 스스로 연결을 다시 맺는데 그때 운영체제가 새 포트 번호를 붙인다 (예: ttyACM0 이 ttyACM1 로). VR 은 처음 열었던 번호를 계속 붙들고 있어서, 장비도 케이블도 멀쩡한데 값만 들어오지 않는다 — 화면상 연결 상태도 정상으로 보여 눈으로는 알 수 없었다 (수술방의 Link+ 주입펌프에서 확인). Added: 포트 자리에 포트 번호 대신 장비의 고유 신원을 적을 수 있다. 형식은 20d0:0002 처럼 네 자리 두 개이며, 이 값은 장비에 박혀 있어 다시 연결되어도 바뀌지 않는다. 이렇게 적어 두면 VR 이 포트를 열 때마다 그 신원을 가진 자리를 다시 찾으므로, 번호가 바뀌어도 저절로 따라간다. 기존처럼 포트 번호를 적어 둔 곳은 그대로 동작한다. Changed: Link+ 는 포트를 적지 않으면 이 신원을 기본값으로 쓴다. 꽂기만 하면 잡히고, 다시 연결되어도 따라간다. 포트를 직접 적어 둔 곳은 적은 값이 우선이다. Changed: 자동 판별이 USB 신원을 먼저 본다. 고유 신원으로 자기를 밝히는 장비는 통신 속도·형식을 재 볼 필요가 없으므로 조용한 회선에서 72초가 걸리던 측정을 건너뛰고 바로 확정한다. 이렇게 확정한 장비는 포트를 번호가 아니라 신원으로 기록한다. 여러 장비가 함께 쓰는 범용 USB 변환 케이블(FTDI 등)은 신원이 케이블의 것이지 장비의 것이 아니므로 종전대로 측정해서 판별한다. 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.27 | 2026-09-05 | 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 | 2026-09-05 | 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 | 2026-09-04 | 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 | 2026-09-02 | 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 | 2026-09-01 | 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.22 | 2026-08-30 | 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.21 | 2026-08-29 | 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.20 | 2026-08-29 | 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.19 | 2026-08-27 | 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.18 | 2026-08-27 | 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.17 | 2026-08-27 | 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.16 | 2026-08-27 | 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.15 | 2026-08-27 | 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.14 | 2026-08-27 | 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.13 | 2026-08-27 | 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.9 | 2026-08-14 | 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.8 | 2026-08-14 | 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.7 | 2026-08-12 | 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.5 | 2026-08-06 | 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.4 | 2026-08-05 | 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.3 | 2026-08-04 | 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.0 | 2026-08-03 | 1.19.0 Fixed: 여러 베드를 한 프로세스가 TCP 로 수집하는 구성에서 클라이언트 재접속이 잦으면 프로세스 전체가 SIGSEGV 로 죽던 문제. accept_handoff 가 교체(replace) 시 옛 소켓을 즉시 closesocket 했는데, m_sock_mtx 는 fd 번호만 보호할 뿐 fd 의 수명은 보호하지 않아 reader 가 그 fd 로 recv 중일 수 있었다. close 는 blocking recv 를 깨우지도 않고 fd 번호를 즉시 재사용 가능하게 만들어, 다음 접속이 같은 번호를 받으면 reader 가 남의 세션을 읽었다. SNUH 회복실(VRN 15대, 최다 베드는 하루 785회 재접속)에서 실제로 크래시 후 9시간 48분 전 베드 무수집. 수정 세 겹: (1) tcp reader 를 poll(POLLIN,200ms) 게이트 뒤의 recv 로 재구성 - recv 가 무기한 블로킹되지 않으므로 close/replace 가 늦어도 200ms 안에, shutdown 이 동반되면 즉시 감지된다. fd 에 O_NONBLOCK 은 걸지 않는다(같은 fd 로 송신하는 write_transport 의 부분 전송 방지). (2) replace 는 closesocket 대신 shutdown 만 하고 옛 fd 를 retired 목록에 넣으며, 닫기는 깨어난 reader 또는 Serial::close 가 정확히 한 번 수행 - fd 번호 재사용 창이 사라진다. (3) Serial::close 가 join 전에 shutdown 으로 reader 를 깨우고(RCVTIMEO 는 설정된 적이 없어 종전에는 피어가 끊을 때까지 join 이 매달렸다), m_sock 접근을 전부 락으로 감싼다. has_socket 의 무락 읽기와 udp_thread_func 의 이중 close 도 같은 계열이라 함께 수정. Added: AUTO_DETECT=1 (vr.conf) - 시리얼 포트에 무엇이 물려 있는지 스스로 알아내 즉시 수집을 시작한다. 판별용 프로브 바이트를 따로 두지 않고 후보 드라이버를 실제로 열어 유효 레코드 도달 여부로 판정하므로 장비별 판별 코드가 없고 드라이버는 무수정이다. 4단계: 회선 측정(커널 프레이밍/패리티/브레이크 카운터로 보레이트와 프레임 폭 확정) -> 패리티 추론(10비트 프레임의 7E1/7O1 대 8N1 모호성을 MSB 상관으로 해소) -> 후보 필터(보레이트+프레임폭+데이터비트 3축 일치, 베드당 환자모니터 1대/마취기 1대 배타, 포트 위치 사전확률, 지난 판별 결과 캐시) -> 남으면 트랙 수확량으로 경쟁. SNUH 수술장 9방 + ICU 3베드 실측에서 Intellivue/MedibusX/Datex-Ohmeda/Primus 를 오탐 0 으로 판별했고, 표준과 다른 포트에 꽂힌 장비(P3/P4 의 Intellivue)도 찾아냈다. AUTO_DETECT 가 없으면 스캐너는 유휴 대기만 하므로 기존 설정에 영향이 없다. | |
| 1.18.52 | 2026-07-31 | 1.18.52 Changed: VR 기본 운영이 k3s 로 이관됨에 따라 서버→VR 하향 제어 경로를 제거. VitalServer 로는 데이터를 일방 push 만 하고, websocket(socket.io) 으로 내려오던 원격 명령 update/restart/reboot/edit_conf/new_bed/del_bed/edit_bed/dt/add_event 를 더 이상 처리하지 않는다 (원격 재부팅·바이너리 교체·conf 덮어쓰기·베드 구성변경 봉쇄). 구현: sendweb_thread_func 의 socket.io 42 이벤트 dispatch 와 on_sio_msg 함수를 통째로 제거. engine.io ping(2->3) 및 ws 레벨 ping/pong 은 연결 유지를 위해 그대로 응답하므로 데이터 업링크·웹모니터링은 종전과 동일. 서버는 VR 버전을 알므로 52+ 로는 명령을 보내지 않고, 받아도 무시. Fixed: --console 모드 (MFC) 에서 stdout 이 이미 유효한 파이프/파일 (> log.txt, ssh 파이프, cmd 상속 핸들) 인데도 무조건 _open_osfhandle+_dup2 로 재배선하면서 그 OS 핸들을 닫아버려, 초기화 이후 모든 로그가 조용히 사라지던 문제 (콘솔 모드 서버 배포에서 진단 불능의 원인, 1.18.7 부터 잠재). stdout 핸들이 유효하면 (GetFileType != UNKNOWN) 재배선하지 않고, 핸들이 없을 때만 (Explorer/schtasks 실행) AttachConsole 로 부모 콘솔에 붙인다. 실측: 종전 초기화 후 로그 침묵 -> 수정 후 지속 출력. (앞서 1.18.51 의 VRQt qDebug->stdout 통일과 함께 세 빌드의 --debug 로그 일관성 완성.) | |
| 1.18.51 | 2026-07-30 | 1.18.51 Fixed: VRQt (Qt 빌드) 에서 --debug 옵션을 켜도 콘솔에 로그가 전혀 출력되지 않던 문제. 원인: Qt 빌드의 TRACE 가 qDebug() 로 매핑되어 있었는데 (UTIL.h, 1.14.4 부터), Windows 에서 Qt 로깅은 GUI 앱이면 콘솔/파이프가 아니라 디버거 (OutputDebugString) 로만 보내므로 VRQt 만 --debug 콘솔 출력이 구조적으로 불가능했다 (MFC 는 stdafx.h 의 #undef TRACE 로 printf 버전을 사용해 정상). 수정: TRACE 를 세 빌드 (MFC/Qt/CLI) 공통 fprintf(stdout)+fflush 로 통일 - qDebug 직접 사용처는 코드에 없음을 확인. 이제 --debug 동작과 리다이렉트 (> log.txt) 가 전 빌드에서 일관되고, cmd 대화형 실행 시에는 기존 AttachConsole/AllocConsole 로직 (1.18.30) 이 콘솔을 만들어 준다. terapc 실측 검증: 종전 VRQt stdout 0 chars → 수정 후 4,690 chars 정상 출력, Vital.exe (MFC) 는 종전과 동일 정상. 추가 방어: --debug 의 콘솔 확보 검사에 GetFileType 추가 - 일부 런처/셸이 non-NULL 무효 stdout 핸들을 상속시키면 NULL 검사만으로는 AttachConsole 을 건너뛰어 로그가 사라질 수 있던 엣지 케이스 (MFC/Qt 공통). | |
| 1.18.50 | 2026-07-30 | 1.18.50 Fixed: 장비 연결이 한번 끊어지면 다시 붙지 않던 문제 (Link+ 현장 리포트, Intellivue 늦은 전원-온과 공통 근본 원인). 근본 원인: DEVICE::open() 의 초기화 호출 set_rx_time(0) 이 값 0 인데도 OPENING→ACTIVE 로 승격시키고 m_dtStatus 까지 0 으로 만들어, (1) 모든 장비가 open 즉시 가짜-ACTIVE (OPENING 상태가 사실상 부재), (2) restart 워치독의 dtlast(rx ?: status_time) 가 0 → read_timeout 조건이 영구 거짓 → 재연결 시 상대 무응답이면 (Link+ rack 재부팅/세션 잔존, 세션 프레임 1회 전송 후 무응답) 재시도 없이 영구 고착. 1.18.35 이하의 rec_timeout (상태 무관 60s 강제 재시작) 이 이 잠재 버그를 우연히 가리고 있었고 1.18.36 전환으로 노출된 것. 수정 3건: (1) set_rx_time 은 실수신 (dt>0) 일 때만 ACTIVE 승격 - 상태 표시도 정직해짐 (open 후 첫 수신까지 "con"), (2) restart 워치독에 OPENING-stuck 분기 추가 - open 후 read_timeout 내 첫 수신이 없으면 재시작해 협상 프레임 재전송, tx_time>0 (protocol-initiator) 조건으로 passive/keyword-sub 디바이스는 제외해 유휴 churn 없음, (3) tx_time 을 모든 transport (socket/fd/COM) 에서 갱신 (기존 Windows COM 만). 영향 장비: Link+/Agilia, Intellivue, S5, Datex, Hamilton 등 open 시 세션/assoc 을 시작하는 모든 protocol-initiator. E2E 검증: 무응답 서버 재현에서 종전 접속 1회 후 영구 침묵 → 수정 후 30초마다 재접속 반복, 장비 복구 시 자동 연결. Mindray 멀티베드 회귀 테스트 PASS (ACK 75/75, tier1 라우팅, probe 거절, stuck 오발동 0). Intellivue 1.18.48 assoc-retry (5s) 는 유지 - 이번 워치독은 30s 백스톱으로 공존. | |
| 1.18.49 | 2026-07-29 | 1.18.49 Fixed: (1) Mindray eGateway HL7 멀티베드 (catch-all + #키워드 sub) 에서 전 베드가 readonly=1 이면 데이터가 전혀 수집되지 않고 게이트웨이가 수초마다 재접속하던 문제. 원인 셋 - (a) readonly catch-all 이 open_socket 에서 조기 반환해 reader/parser 스레드가 아예 생성되지 않음 (아무도 안 읽는 소켓에 데이터가 쌓이다 게이트웨이가 연결 재수립 반복; PortRouter 리팩토링 시 readonly 체크가 blocking accept 앞으로 이동하며 생긴 1.18.36 회귀 - 1.18.31 은 accept 가 먼저라 우연히 동작), (b) readonly 가 HL7 ACK 송신까지 차단 (eGateway 는 메시지마다 ACK 요구), (c) sibling 베드 메시지는 broadcast 후 ACK 없이 return. 수정 - readonly 는 송신만 차단하고 수신은 정상화 (broadcast-only keyword sub 만 소켓 미보유), write_protocol 경로 신설로 ACK 는 readonly 우회, catch-all 이 sibling 메시지도 대신 ACK (R01/R40 타입 구분), broadcast 수신 sub 의 rx_time 갱신으로 상태표시 정상화, PortRouter takeover 는 기존 소켓이 stale (10초+ 무수신) 일 때만 허용해 게이트웨이 health-probe 접속이 live 데이터 소켓을 뺏지 못하게 함. 시뮬레이터 E2E 검증: ORU 75건 -> ACK 75건 (100%), probe 5회 전부 무해 거절, SU-2/SU-4 tier1 라우팅 정상. (2) Getinge/MAQUET Flow-i (FCI 프로토콜) tidal volume 이 실제의 절반으로 기록되던 문제. 원인: FCI 프로토콜 v0005 부터 TV 채널 (101/102) gain 이 0.1 -> 0.2 로 변경됐는데 (Flow-i communication interface v0007 문서), VR 은 open 시 SPVE0005 로 v5 를 요청하면서 파싱은 구버전 gain 0.1 을 적용. 병원 vital 파일 실측 (Flow-i 직결 TV 201.4/188.2 vs 필립스 경유 403.0/376.6, MV 는 정상) 으로 검증 - TV_INSP/TV_EXP gain 0.2 적용. MV 는 v3+ 스케일 (0.01) 이 이미 반영돼 있어 변경 없음. (3) Flow-i 설정 파라미터 미지원 (m_ch_settings 빈 맵) - v5+ 에 존재하는 수치 설정 채널 11종 등록: SET_RR(411), SET_INSP_TM(413), SET_IE(418), SET_MV(421), SET_PC(423), SET_PEEP(424), SET_FRESH_FLOW(428), SET_FRESH_O2(430), SET_AGENT_PERC(434), SET_PS(436), SET_SIMV_RR(437). gain 은 문서 표기 그대로. 주의: Set Tidal Volume (채널 422) 은 FCI v5 에서 프로토콜 자체에서 삭제되어 직접 수신 불가 - 볼륨 모드에서 SET_MV/SET_RR 로 파생 가능. 설정 채널은 문서 기반 구현으로 실기 검증 대기 상태. | |
| 1.18.48 | 2026-07-24 | 1.18.48 Fixed: VR 이 먼저 실행된 상태에서 Philips Intellivue 모니터를 나중에 켜면 웨이브가 영원히 수집되지 않던 문제 (VR 재시작 전까지 모니터로 아무 요청도 안 나감). 원인: Intellivue::open() 은 assoc_req 를 딱 한 번 보내는데, 시리얼(MIB)/UDP 는 모니터가 꺼져 있어도 포트가 열리므로 이 요청이 응답 없이 사라진다. Philips 는 자기가 먼저 말을 걸지 않는 프로토콜이고, 첫 수신이 있어야 디바이스 상태가 OPENING→ACTIVE 로 승격되는데 수신이 없으니 OPENING 에 고정 - DEVICE::restart_thread_func 워치독은 is_closed(재오픈)/is_active(read_timeout 30s) 두 분기만 있어 OPENING 은 어느 쪽에도 안 걸림 → assoc 재전송도 재시작도 영원히 없음. 수정: Intellivue 에 assoc 재시도 스레드 추가 - 세션 확립(MDS_CREAT 수신, m_bSessionUp atomic 플래그) 전까지 5초 간격으로 assoc_req 재전송, 최근 5초 내 수신이 있으면(assoc 진행 중) 재전송 보류. 모니터를 나중에 켜도 최대 5초 내 연결. 세션 확립 후에는 재시도 루프 종료 - 기존 동작(AB_SPDU 시 재연결, read_timeout 재시작)과 충돌 없음. 모니터가 켜져 있던 기존 시나리오는 첫 assoc_req 에 바로 응답하므로 동작 변화 없음 - 0-impact. | |
| 1.18.47 | 2026-07-24 | 1.18.47 Fixed: 파일 업로드(및 Http 클래스를 쓰는 모든 경로 - upload_vital, getvr 업데이트체크, ADT 조회, 필터서버)가 SERVER_IP/UPLOAD_SERVER_IP 에 "https://host:port" 풀 URL 형식을 넣으면 "Couldn't resolve host name" 으로 전부 실패하던 문제. 원인은 src/Https.cpp 의 Http 생성자 - 1.18.46 의 wss.cpp 수정과 동일한 버그의 나머지 반쪽. 첫 콜론을 포트 구분자로 오인해 "https://eicuvital.ywmc.or.kr:4432" 입력 시 m_strHost 가 문자 그대로 "https" 가 되어 DNS 조회 실패 (웹모니터링 wss 는 1.18.46 에서 이미 수정되어 정상, 파일 업로드만 실패하는 비대칭 발생 - 현장에서는 업로드 루프의 실패시 break 정책과 겹쳐 "베드명에 # 가 있으면 전송이 안 된다" 로 오인 관찰됨. 실제로는 # 파일이 디렉토리 순서상 항상 첫 시도라 거기서 host 오류로 중단 → 뒤 파일 전체가 막힌 것. 베드명 특수문자는 업로드 URL 과 무관). 수정: Http 생성자에 wss 와 동일한 선행 스킴(https/http) 파싱 추가 - 스킴이 TLS 여부를 결정 (포트 무관, 4432 같은 커스텀 포트에서 https 정상), "https://host" 포트 생략 시 443, 경로 붙은 입력도 host 만 추출. 기존 "host" / "host:443" 형식은 종전과 완전 동일 동작 - 0-impact. Windows(WinINet)/Linux(curl) 공통 생성자라 모든 플랫폼에 적용. | |
| 1.18.46 | 2026-07-23 | 1.18.46 Fixed: WSSClient 가 "https/wss 스킴 + 비표준 포트" 조합을 제대로 처리하지 못하던 문제 (커스텀 포트의 wss 서버에 연결 불가). 원인은 src/wss.cpp 의 WSSClient 생성자 두 곳 - (1) SERVER_IP 파싱이 첫 콜론을 포트 구분자로 써서 "https://host:4432" 를 넣으면 "https" 의 콜론을 먼저 잡아 m_host 가 "https" 가 되고 포트 파싱이 깨져 getaddrinfo 실패, (2) m_secure = (m_port == 443) 로 TLS 여부를 오직 포트 443 여부로만 판단해 4432 같은 커스텀 포트는 무조건 평문 ws 로 붙어 wss 전용 서버에서 실패. 수정: 생성자가 선행 URL 스킴(https/wss/http/ws)을 먼저 파싱해 스킴이 있으면 스킴이 TLS 를 결정(포트 무관)하고, 스킴이 없으면 기존 포트 443 휴리스틱을 유지(하위호환). 이제 SERVER_IP=https://eicuvital.ywmc.or.kr:4432 (또는 wss://...) 형식이 비표준 포트에서도 wss 로 정상 연결되며, SNI/인증서 검증은 도메인(m_host)을 쓰므로 와일드카드 인증서도 정상 매칭. 기존 host / host:443 설정은 종전대로 secure 유지 - 0-impact. | |
| 1.18.45 | 2026-06-16 | 1.18.45 Fixed: Windows VR 에서 사용자가 외부에서 vr.conf 를 멀티베드 설정으로 교체 후 VR 을 재실행하면, 첫 실행에서는 멀티탭이 정상 생성되지만 다음 실행에서 모든 탭이 사라지던 문제. 원인은 VRApp.cpp:1110 의 VRCODE 미설정 분기 — 새 vr.conf 가 VRCODE 라인을 포함하지 않으면 load_settings() 가 신규 VRCODE 를 issue_code() 로 발급하고 즉시 save_settings() 를 호출하는데, 이 시점은 load_settings() 가 파싱한 베드들이 로컬 newbeds 벡터에만 있고 아직 m_pdisps 로 옮겨지기 전 (set_beds(newbeds) 는 initialize() 가 load_settings 반환 후 라인 1328 에서 호출). 결과: save_settings() 가 빈 m_pdisps 를 iterate 하며 vr.conf 를 글로벌 설정만으로 덮어써 모든 [BED/...] 섹션이 디스크에서 사라짐. 첫 실행은 메모리의 newbeds 로 UI 멀티탭이 그려져 정상으로 보이지만 (사용자가 "멀티탭은 정상적으로 생성되지만" 으로 표현), VR 종료/재실행 시 디스크 conf 에는 BED 섹션이 없으므로 다음 load_settings 가 빈 베드 리스트를 반환 → 탭 전부 사라짐. 수정: VRApp.h 에 m_vrcode_newly_issued bool 플래그 추가, load_settings 의 즉시 save 를 플래그 set 만 으로 교체, initialize() 의 set_beds(newbeds) 직후 (m_pdisps 가 채워진 시점) 플래그가 켜져 있으면 한 번만 save_settings() 호출 → BED 섹션이 정상 직렬화됨. Linux/Pi 분기 (m_serial_number 가 채워져 있는 경우) 는 원래도 save 안 했으므로 영향 없음, VRCODE 가 이미 conf 에 있는 모든 기존 deployment 도 분기 미진입으로 0-impact. m_conf_from_stdin / m_bDebug 모드는 save_settings 내부에서 이미 early return 이라 그대로 안전. MFC + Qt 빌드 모두 통과 (이번 변경 관련 0 warning). | |
| 1.18.44 | 2026-06-12 | 1.18.44: vr.conf FHIR=1 모드 추가 (자체 FHIR 수신서버 대상 실시간 푸시) + VS 2026 / 새 OpenSSL 인스톨러 빌드 환경 호환 shim. (1) sendweb_thread_func 의 기존 HL7/JSON 양분 분기 (VRApp.cpp:2466) 옆에 bFHIR 분기 추가. 1초 tick 마다 한 vrcode 의 모든 방 Observation 을 하나의 FHIR R4 collection Bundle 로 묶어 기존 WSS 채널로 push (gzip + WSSClient 인프라 그대로 재사용). MLLP 같은 신규 transport 안 도입 — FHIR R5 Subscription 의 websocket channel type 과 동형이라 표준 부합. (2) DISPLAY::get_fhir + TRACK::get_fhir 신규. v2 (vitaldb-fhir-v2) 와 동일한 reference-only 패턴 (Patient.id = m_patient_id, Encounter.id = vrcode-bedname-startsec, Observation.subject/encounter = relative reference) + hybrid keyframe: 첫 tick / m_patient_id 변경 / get_start_time() 변경 / 5분 경과 중 하나라도 발생 시 다음 tick Bundle 에 Patient + Encounter resource 를 inline. 그 외 tick 은 Observation 만. 수신서버가 도중에 새로 connect 해도 최대 5분 안에 자동 동기화 (keyframe heartbeat). DISPLAY 에 m_fhir_pt_sent_at/m_fhir_pt_last_id/m_fhir_enc_last_start 3개 멤버 추가 (m_setting_sent 패턴 그대로 per-display). (3) Observation 인코딩: NUM 은 effectiveDateTime + valueQuantity 각 데이터 포인트당 한 entry, WAV 은 effectivePeriod + valueSampledData (period=1000/srate ms, factor=1, origin=0, data=공백구분 floats) 한 entry. CO2/AWP wav 는 기존 get_json 처럼 25Hz 다운샘플 유지. NUM 은 m_montype 기반 LOINC 매핑 15개 등록 (ECG_HR/PLETH_HR=8867-4, PLETH_SPO2=59408-5, NIBP/IABP SBP/DBP/MBP=8480-6/8462-4/8478-0, BT=8310-5, RR=9279-1, etCO2=19891-1, CVP=8556-0, FETAL_HR=11627-3) + VR 자체 코드 시스템 (https://vitaldb.net/fhir/CodeSystem/track, code = dname/tname) 항상 동반. m_patient_id 미설정 (PATIENT_ID_UNSET) 시 anon-vrcode-bedname identifier 로 fallback. (4) Bundle wrapper: VRApp 가 방별 entries 를 콤마로 합쳐 {resourceType:Bundle, type:collection, timestamp:iso8601Z, entry:[...]} 로 wrap, 기존 gzip → WSS pipeline 동일. ISO 8601 UTC 헬퍼는 std::gmtime_s/gmtime_r 인라인. FHIR id 안전화 (A-Za-z0-9-.) 64자 제한 lambda 각 함수에 inline. (5) 빌드 환경 호환성: VRQt/CMakeLists.txt 에 _SILENCE_EXPERIMENTAL_COROUTINE_DEPRECATION_WARNINGS 추가 — VS 2026 (MSVC 14.51+) 의 STL 이 <experimental/coroutine> deprecation 을 warning → hard static_assert 로 바꾼 것이 cppwinrt 의 winrt::* (Serial.cpp 의 Bluetooth LE) 를 끌어오는 모든 TU 에서 컴파일 실패 시켜서, build_mfc.py 가 이미 CL env 로 주입하던 것과 동치 shim 을 Qt 쪽에도. (6) VRQt/CMakeLists.txt 의 OpenSSL link 경로 시프트. 새 OpenSSL 인스톨러가 libssl_static.lib / libcrypto_static.lib 를 lib/VC/x64/{MT,MTd}/ 에 두고 v1.x 의 libssl64MT.lib / libcrypto64MT.lib 네이밍과 lib/VC/static 경로를 깬다. build_mfc.py 의 %LOCALAPPDATA%/openssl-compat 패턴 (옛 이름으로 복사한 사본 폴더) 을 그대로 따라 CMakeLists 도 그 폴더가 있으면 우선, 없으면 옛 lib/VC/static 으로 fallback. 부트스트랩 스크립트는 build_mfc.py 의 docstring 참조 (한 줄). MFC + Qt + RPi + Ubuntu CLI + AppImage 빌드 모두 통과. 동작 영향: vr.conf 에 FHIR=1 추가 안 한 기존 deployment 는 sendweb 가 종전 JSON 모드 그대로 (HL7=1 만 분기보다 우선) — 0-impact migration. | |
| 1.18.43 | 2026-05-21 | 1.18.43: 멀티베드 console (PACU 10베드 Pi) syslog noise 80,500/hr → ~1,400/hr (-98.3%), cpu_usage 측정 1.16.12 (9f26e36) 이후 8년치 영구 -1 버그 수정, 멀티베드 환경 베드 식별용 [bedname / devicename] 로그 prefix 도입. 동기는 PACU production Pi (1.18.40) 4일치 syslog 분석 — 1.24M 라인 중 vr 라인 836K, idle 8시간 평균 시간당 80,500 라인 (베드 10 × 1초마다 "Waiting for next patient" × syslog 2x 중복). (1) VRApp.cpp:2696-2710 timer_thread_func 의 cpu usage 계산이 `if (totalcpu && m_cpu_total_last && totalcpu > m_cpu_total_last)` 가드 안에서만 m_cpu_total_last 갱신해 첫 호출 시 m_cpu_total_last=0 → else 분기 → -1 → m_cpu_total_last 갱신 안 됨 → 영원히 false → 영원히 -1 데드락. PACU 1,048 샘플 100% -1 실측 확인. m_cpu_total_last/m_cpu_used_last 갱신을 if 밖으로 빼서 첫 호출만 baseline 으로 -1, 두 번째 (60s 후) 부터 정상 측정. /proc/stat 의 iowait/irq/softirq 까지 7개 필드 fscanf 로 정확도 ±5% 개선. 부수: 같은 함수의 cpu_temp 가 `temp / 1000` int 나눗셈으로 소수점 절단되어 PACU 측정값이 정수 50/51/...,/58 9종만 출력 (millidegree 정밀도 손실) → `temp / 1000.0f` 로 fix. ram 계산은 `(totalram + totalswap) * mem_unit` 결과에 다시 mem_unit 곱하던 차원 혼용 (Linux 가 mem_unit=1 이라 우연히 정상 동작했지만 dimensionally wrong) → `(units1 + units2) * mem_unit` 로 정정 + underflow 가드. (2) DISPLAY::log(string/printf) + DEVICE::log(string/printf) 멤버 추가. DISPLAY::log 는 `[bedname] msg`, DEVICE::log 는 m_pdisp 위임 없이 직접 `[bedname / devicename] msg` 한 줄 prefix (nested `[bed] [dev]` 보다 가독성 우선). m_pdisp 또는 bedname null 케이스 safe fallback. (3) show_msg 의 console path TRACE 를 log() 로 교체 → "Waiting for next patient" 자동 [bedname] prefix. ADT patient id unset 도 log() 로. (4) 이미 수동으로 get_bedname() 을 인자로 넘기던 8군데 (DISPLAY ptcon_thread_func / add_trk / add_dev / del_dev / cut-recording, Device add_rec 콜백, FILT stop, VRApp add_tab/del_tab) 를 log() 로 전환 — 이중 prefix 방지 + 일관성. Serial::reading_thread / tcp_thread / open_socket waiting / timeout / interrupted / got-client 6군데 DEVICE::log() 전환 → `[bedname / Intellivue]` 등 자동 prefix, 멀티탭 환경에서 어느 베드 어느 device 출처인지 즉시 식별. (5) wss 재시도 폭주 throttle. WSSClient 에 m_verbose (debug step 로그 게이트) + m_dt_last_err_log (60s window) 추가. connect() 내부 step-by-step TRACE 4개 ("connecting" / "connect error" / "connected") 는 m_verbose 가드, 최종 "wss connect error" 는 60s window throttle (첫 줄 즉시, 그 다음 60s 1회). connect 성공 path 에서 throttle reset 으로 다음 disconnect → 첫 에러 즉시 로깅. sendweb_thread_func 의 "connecting websocket server" 호출자 라인도 같은 60s window, m_bDebug 면 verbose 활성화. PACU idle 8h 측정 7,024/hr (4종 × 1,756) → 약 60/hr (-99%). (6) "Waiting for next patient" / "ADT patient id unset" 의 60s throttle 자체는 feature/wav-mem-retention 브랜치 ptcon_thread_func 재구성 (b75928f 시리즈) 에서 이미 들어가 있어 이 릴리즈에 자동 포함. 종합 영향: idle 시간당 syslog 라인 80,500 → ~1,400 (-98.3%), active 시간당 14,900 → ~700 (-95%). 진단 측면: cpu_usage 가 부활하면서 웹모니터링 json 의 cpu_usage 필드도 한 번도 안 채워지던 게 정상 전송 (VRApp.cpp:2499 의 `if (m_cpu_usage > 0)` 가드가 영원히 false 였음). 별건: syslog 라인 2× 중복은 application 측이 아니라 rsyslog imuxsock + imjournal + journald.ForwardToSyslog=yes 의 Debian Bookworm 전형 misconfig — admin 측 fix (rsyslog.conf 에서 imjournal 주석 처리 + restart) 로 추가 50% 감소 가능 (별도 문서). MFC + Qt + Pi (RPI64) + Ubuntu CLI 빌드 모두 통과. | |
| 1.18.41 | 2026-05-13 | 1.18.41 Hotfix: TCP-server protocol-initiator device (Intellivue/S5/Datex/Hamilton/MPS/Agilia/IAP) 가 open() 직후 보내는 첫 패킷이 wire 에 안 나가던 회귀. 1.18.34 의 PortRouter refactor 가 Serial::open_socket() 의 8초 handoff blocking 을 제거하고 비동기 모델로 전환했는데, protocol-initiator 의 open() 코드가 m_sock=INVALID 상태에서 write 를 시도해 silent fail (Linux 는 m_fd=0 이라 false 반환). Intellivue 케이스 production journald 의 Pi → ESP TX 700K~3M bytes 는 monitor 측 abort 로 늦게 트리거된 AB_SPDU_SI recovery assoc_req 후 request_thread 의 정상 polling 트래픽이지만 일부 베드 (abort 없는 monitor) 는 recovery 도 안 되어 영구 silent (RX 300 bytes). 수정 방향: open() 의 옛 contract ("return 시점에 통신 가능") 복구. (1) Serial::open_socket() TCP-server 분기가 PortRouter::subscribe 후 m_sock_cv 로 첫 client handoff 까지 block (timeout = device read_timeout, 기본 30s) — 옛 모델 그대로. lock_and_open 이 false 면 다음 tick 에서 retry. close 신호로 abort 가능. (2) Serial::close() 는 PortRouter::unsubscribe 안 함 — subscribe 는 device 생애 동안 sticky 라 read_timeout 으로 인한 close→re-open 사이클 동안 listen socket 이 유지되어 1.18.29/1.18.34 의 anti-churn 이익 보존. unsubscribe 는 ~Serial() 에서만. (3) Per-device lifecycle thread (DEVICE::m_thread_restart): 각 device 가 자기 open/close/timeout 재시작을 별도 thread 에서 관리. 옛 모델의 DISPLAY::bed_manager 가 베드의 모든 device 를 sequential iterate 하던 구조에선 한 device 의 blocking open() 이 같은 베드의 다른 device 의 lifecycle 을 차단했는데, per-device thread 로 분리하여 멀티 device 베드도 병렬 진행. add_dev 에서 start_lifecycle, del_dev / ~DISPLAY 에서 stop_lifecycle. (4) Serial::tcp_thread_func 단순화 — outer m_sock_cv wait 패턴 (1.18.34/1.18.39 가 추가했던 것) 제거. open_socket 이 m_sock valid 보장하므로 reader thread 시작 시점에 simple inner recv loop 만. accept_handoff 의 replace (zombie cleanup) 케이스는 recv error 후 m_sock 변경 확인 + retry 로 처리. (5) reading_thread_func 의 `else if (m_sock != INVALID_SOCKET) tcp_thread_func();` 가드 복원 (1.18.39 가 제거). (6) Serial::write 의 silent-fail 케이스에 WARN trace 추가 — 미래 동종 회귀 즉시 노출. (7) PortRouter::subscribe 가 같은 (port, type) 에서 같은 IP filter 또는 둘 이상 catch-all 발견 시 reject (config 실수 원천 차단, 옛 WARN-only 동작 강화). (8) DISPLAY::bed_manager_thread_func 의 restart 블록 제거 — ptcon + adt 만 담당. "Waiting for next patient" TRACE 가 Linux/console 에서 1초 → 60초마다 throttle (별개 journald 부풀림 fix). 영향 범위: Intellivue / S5 / Datex / Hamilton / MPS / Agilia / IAP TCP-server 모드 베드 전부. 1.18.34 이후 우회 방법은 race-win 케이스 (VRN 이 PortRouter::subscribe 직후 connect) 외엔 없었음. MFC + Qt + Pi (RPI64) + Ubuntu Qt + AppImage 빌드 모두 통과. | |
| 1.18.40 | 2026-05-12 | Hotfix: Drager Medibus 프로토콜 사용 베드에서 1.15.11 이후 vr.service 가 SIGSEGV 로 무한 재시작하던 문제. 1.15.10 (a8c7b2a) 의 "Sampling rate estimation" 리팩터링이 m_ptrk_awp/awf/co2 트랙을 add_trks() 에서의 eager 생성 (default srate=62.5Hz) 에서 realtime data 100 샘플 모인 후 lazy 생성으로 바꿨는데, Medibus 핸드셰이크 순서 (0x51 ICC → 0x52 DeviceID → 0x53 RealtimeConfig 응답 → 0x54 → realtime data 시작) 상 0x53 응답 핸들러 (Drager.cpp:295-307) 가 트랙들이 아직 nullptr 인 시점에 `ptrk->m_srate = 1000000.0/interval` 로 NULL 역참조 → 즉시 SEGV. 그 베드의 vr.service 가 systemd Restart=on-failure 로 재시작 → 다시 connect → 동일 0x53 패킷 받고 동일 SEGV → 무한 루프 (journald 로그상 restart counter 61 까지 누적, code=killed status=11/SEGV). 1.15.9 까진 트랙이 add_trks() 에서 생성되어 0x53 도착 시 항상 non-NULL 이라 안전했음. 수정: (a) add_trks() 에서 m_ptrk_awp/awf/co2 eager 생성 복원 (srate=62.5Hz default) — 0x53 는 ptrk->m_srate 만 갱신 (트랙 재생성 X). (b) on_received() 의 lazy creation + packet-timing srate 추정 블록 제거 — 0x53 가 권위 있는 srate 소스이고 packet 타이밍 기반 추정은 network jitter 에 약해 불안정. (c) 0x53 핸들러에 `if (interval > 0)` guard 추가 — 페이로드 깨졌을 때 div-by-zero / inf srate 방지. 영향 범위: Drager Primus / Apollo / Perseus / Fabius 등 Medibus 사용 마취기 전부. 우회 방법은 1.15.9 다운그레이드 외엔 없었음 (그 베드의 모든 데이터 유실 + 환자 모니터링 불가). MFC + Qt + Pi (RPI64) + Ubuntu Qt + AppImage 빌드 모두 통과. | |
| 1.18.39 | 2026-05-08 | Fixed: PortRouter (TCP server) 모드에서 reader thread 가 startup 시점에 죽어 client 데이터를 영구히 못 받던 문제. 1.18.34 (9bd252f) 의 PortRouter refactor 가 m_sock 세팅을 dispatch 시점으로 옮겼는데 reading_thread_func (Serial.cpp:354) 의 가드 `else if (m_sock != INVALID_SOCKET) tcp_thread_func();` 는 옛 모델 (open 시점 이미 connect 완료) 가정 그대로라 — 시작 직후 m_sock=INVALID 면 tcp_thread_func 진입 안 하고 reader thread 즉시 종료. 이후 VRN 이 connect → PortRouter::dispatch → accept_handoff 가 m_sock 채우고 m_sock_cv.notify_all() → 그러나 wait 중인 reader 가 없음 (이미 죽음) → kernel recv buffer 에만 데이터 적재. 증상은 ss -tn state established 의 Recv-Q 가 0 이 아닌 값 (수백 bytes) 으로 누적, 베드 화면에는 데이터 안 들어옴. 그 베드의 VRN 이 Serial::open() 의 PortRouter::subscribe 직후~reader thread spawn 사이의 짧은 윈도우 안에 connect 한 경우만 우연히 정상 동작 (race-win). 수정: 가드 제거 — TCP 분기에 항상 진입. tcp_thread_func 자체에 이미 m_sock_cv outer-wait 루프가 있어 m_sock 이 INVALID 인 동안 dispatch 가 깨워줄 때까지 안전하게 대기. Pi 핫스팟에 VRN 여러 대 (e.g. 10.42.0.21~25) 를 같은 (4343, Philips:Intellivue) 키로 묶고 IP filter 로 라우팅하는 시나리오에서 가장 자주 노출. Added: 진단 로그 보강 — PortRouter::accept_loop 의 accept 이벤트, dispatch 의 라우팅 결정 (ip_specific/catch_all subs 수, 어느 dev 가 받았는지), subscribe/unsubscribe 의 entry 생성/append/destroy, reading_thread / tcp_thread 의 시작·wait·wake 라이프사이클 — 이전엔 모두 m_bDebug 게이트 안에 있어서 production journald 에서 안 보였는데, 이제 항상 출력 (이번 같은 dispatch routing 문제 진단을 production 에서 바로 가능). MFC + Qt + Pi (RPI64) + Ubuntu Qt + AppImage 빌드 모두 통과. | |
| 1.18.38 | 2026-05-08 | Fixed: 웹모니터링 우클릭 → 디바이스 세팅 (websocket edit_bed 명령) 으로 베드의 device/filter 를 변경할 때 베드 이름이 매번 "_2" suffix 로 자동 리네임되어 vr.conf (Pi: /data/vr.conf) 에 잘못된 이름으로 저장되던 문제. 원인은 1.18.22 (cda8aa5) 에서 add_bed 에 추가된 bedname dedup ("최후 방어선" 으로 forward_frame auto-create / 수동 편집된 conf 의 중복 이름 처리용). edit_bed 핸들러는 옛 pdel 이 m_pdisps 에 그대로 있는 상태에서 같은 bedname 으로 new DISPLAY 를 add_bed 하고 그 다음 del_bed(pdel) 하는 순서였는데, add_bed 가 dup 감지해 새 pdisp 를 bedname_2 로 자동 리네임 → save_settings 가 [BED/bedname_2] 로 직렬화 → 사용자 입장에선 "설정 적용 안됨" 으로 보였다. 추가로 옛/새 DISPLAY 가 잠깐 m_pdisps 에 공존하면서 같은 device 포트를 동시에 들고 있으려 해 로그가 dup 으로 찍히는 부수 증상도 있었음. 수정: del_bed(pdel) 를 add_bed(pdisp) 보다 앞으로 이동 — 옛 베드의 device 포트가 깨끗이 release 된 뒤 새 pdisp 가 같은 포트를 reopen, dedup 발동 안 함. 다른 베드의 recording 은 영향 없음 (인메모리 hot swap 설계 유지). 부수: edit_bed 핸들러의 데드 코드 (`break` 뒤의 unreachable olddevs populate 루프 + `if (olddevs.find(name) == olddevs.end())` 항상-true 래퍼) 정리. edit_conf 와 save_settings 는 손대지 않음. MFC + Qt + Pi (RPI64) 빌드 모두 통과. | |
| 1.18.37 | 2026-05-08 | Refactored: 필터 다이얼로그의 python 런타임 — pyvital → openvital 0.3.0 로 교체 + 동봉 python.zip 슬림화. 핵심 동기 셋: (1) pyvital 0.6.0 이 tensorflow+torch+keras 를 hard-dep 으로 끌어들여 PyPI upgrade 가 사실상 깨짐, (2) 기존 zip 121 MB 안에 botocore/aiobotocore/s3fs/pyarrow/pandas/aiohttp 등 vitaldb 클라우드 업로드용 deps 가 ~250 MB unpacked 차지 — 필터 서버는 한 번도 쓴 적 없는 dead weight, (3) sanic 25.x 의 multi-process worker 가 openvital __main__.py 를 fork 후 재import 하면서 worker 가 module-top app instance 를 못 찾아 "Sanic app name not found" 로 죽음 — 단순 endpoint 2개 로컬호스트 서버에 async stack 자체가 과함. 해결: openvital 0.3.0 (commit b6f445b) 가 sanic 을 stdlib http.server (BaseHTTPRequestHandler + HTTPServer) 로 50 LOC 재작성 — wire protocol (GET / 필터목록 JSON, POST /<modname> gzip 본문) 동일해 VR 클라이언트 변경 0줄. 단일 스레드로 기존 sanic event loop semantics (cfgs/default_cfgs lock-free 변경) 보존. build_python_zip.py (NEW) 가 Python 3.11.9 embed + numpy + openecg + openvital (local checkout 우선, fallback PyPI) 만으로 27 MB zip 빌드 — 기존 121 MB 대비 78% 감소. ML 필터 (sv_dlapco/abp_hpi/ecg_classifier/ecg_beat_noise_detector) 는 [Hybrid 모델] 사용자가 Add filter 다이얼로그의 "Install ML filters" 버튼 누를 때 silent pip install openvital[all] (cmd 창 미노출 — VRApp::install_pip_package + run_pip_silent 이 자체 progress 다이얼로그에 stdout heartbeat 흘림). 버튼 라벨은 "Upgrade pyvital" → "Install ML filters" 로 의도 명시. openvital __main__.py 가 missing extras 를 graceful skip 하므로 base 환경에서도 11개 필터 (ECG QRS detector, HRV, MTWA, annotator, eeg_fft, nirs_cox, pkpd_3comp, pleth_dpop/ptt/pvi, resp_compliance) 즉시 사용 가능. Migration: 기존 사용자의 user_dir/python 폴더는 pyvital + 옛 deps 가 그대로 남아있고 vitaldb.net/python.zip 만 새로 올리면 (a) 기존 폴더 삭제 후 install_python 으로 새 zip 받으면 깔끔, (b) 폴더 유지 시 server 가 openvital 못 찾아 필터 다이얼로그 에러 → release note 에 명시 필요. MFC + Qt 양쪽 코드: VRGui.rc 의 IDC_UPGRADE 버튼 라벨 + DlgAddFilter.cpp 의 dist-info prefix 매칭 (pyvital- 8자 → openvital- 10자) + QtDlgAddFilter.cpp 도 동일 변경 + onUpgrade 가 cmd /k QProcess::startDetached 대신 theApp.install_pip_package("openvital[all]") 호출 (Linux Qt 빌드는 #ifdef _WIN32 가드). VRApp.cpp:318 의 python -m pyvital → -m openvital. install_pip_package 는 anonymous namespace 의 is_safe_pkg_spec 화이트리스트 검증 + run_pip_silent (CreateProcessA + CREATE_NO_WINDOW + 자식 stdout pipe) + CDlgProgress / QtDlgProgress 자체 progress UI. 빌드 검증: python -m openvital 이 GET / 로 11 필터 반환, sanic import 부재 확인. zip 27.5 MB. | |
| 1.18.36 | 2026-05-07 | Fixed: 3-파티션 레이아웃 (boot/rootfs/data) 에서 websocket edit_conf / update 명령이 read-only fs 로 실패하던 문제. vr.conf 는 /data 폴더가 있으면 /data/vr.conf 로 자동 정착 — get_conf_path() 가 write target 으로 결정, load_settings() 는 primary 가 없으면 legacy /boot/firmware/vr.conf 또는 /boot/vr.conf 에서 fallback read 후 다음 save 부터 primary 로 자동 이전 (마이그레이션 step 없음, legacy 파일은 read-only fs 라 무해). binary update (upgrade_now) 는 RAII BootRwGuard 가 dirname(module_path) 를 잠시 rw remount, scope 종료 시 sync() + MS_REMOUNT|MS_RDONLY 로 자동 ro 복귀 — vfat 노출 윈도우 최소화 + exception/return 어디서든 복구 보장. statvfs ST_RDONLY 검사로 이미 rw 인 fs (x86 dev) 에서는 no-op, non-Linux (macOS) 는 stub 으로 통과. save_settings() 의 silent EROFS 실패가 TRACE 로 가시화 (이전엔 무시되어 conf 수정 안 먹는 원인 추적 불가). 부수: PortRouter.cpp 가 commit 9bd252f 이후 CMakeLists 의 RPI sources 목록에서 누락되어 Pi 빌드 link 에러 — 추가. | |
| 1.18.35 | 2026-05-01 | Added: alarm parsing for Mindray HL7 (eGateway ORU^R40) and Nihon Kohden (NealTime HL7GW + ORF, BSM serial D-port, EGA UDP) - emits ALARM_STATUS / ALARM_MESSAGE / ALARM_PRIORITY tracks (same 3-track schema already used by Philips IntelliVue, GE Solar/Dash, GE/Datex S5); type info preserved via [T]/[A]/[ARR] message prefixes; Mindray facet 1/5/6/7 walker with 92-entry MDC+MNDRY alarm code lookup; NK HL7GW parses OBR-4=EVENT with arrhythmia/technical disambiguation by OBX-5; NK BSM serial decodes per-parameter * flags (edge-triggered to avoid 1Hz flooding) plus 32-bit Table 9 arrhythmia bitmap, D19 alarm-suspend nibble, and the VPC CODE byte; NK EGA UDP extracts Priority + Tech Alarm + Arrhythmia from JSON Vital Sign packets with edge-trigger cache for start/end inference; ACK^R40 reply added for Mindray alert messages; simulators gained periodic alarm cycling for end-to-end smoke testing | |
| 1.18.34 | 2026-04-27 | Added: Linux Desktop Qt GUI build (VRQt) - first GUI build for Linux distributed as a self-contained AppImage (~32MB, Qt6 runtime + libGL/libEGL/libGLX/libOpenGL/libGLESv2/libGLdispatch bundled inside) so users on minimal Ubuntu/Debian/Fedora can run it without installing Qt or GL system packages; tested on AWS clean Ubuntu 22.04 cloud image; build via build_ubuntu_appimage.py which is now invoked by deploy.py; Fixed: Linux Qt right-side device/filter panels rendered with white background below cards because the inner listWidget did not autoFillBackground from parent palette on Linux Qt (Windows/macOS implicitly inherit, X11/Wayland do not); explicit stylesheet on listWidget; Added: Linux installation section in user manuals (EN/KO/ZH/ES) covering AppImage chmod+run flow and dialout group note for serial/USB device access | |
| 1.18.33 | 2026-04-25 | 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) |