April 24, 2024

디바이스마트 미디어:

[66호] 원하는 색상으로 제어가 가능한 아두이노 IoT 스마트 무드등 키트 -

2021-06-25

★2021 ICT 융합 프로젝트 공모전 결과 발표! -

2021-05-12

디바이스마트 국내 온라인 유통사 유일 벨로다인 라이다 공급! -

2021-02-16

★총 상금 500만원 /2021 ICT 융합 프로젝트 공모전★ -

2021-01-18

디바이스마트 온라인 매거진 전자책(PDF)이 무료! -

2020-09-29

[61호]음성으로 제어하는 간접등 만들기 -

2020-08-26

디바이스마트 자체제작 코딩키트 ‘코딩 도담도담’ 출시 -

2020-08-10

GGM AC모터 대량등록! -

2020-07-10

[60호]초소형 레이더 MDR, 어떻게 제어하고 활용하나 -

2020-06-30

[60호]NANO 33 IoT보드를 활용한 블루투스 수평계 만들기 -

2020-06-30

라즈베리파이3가 드디어 출시!!! (Now Raspberry Pi 3 is Coming!!) -

2016-02-29

MoonWalker Actuator 판매개시!! -

2015-08-27

디바이스마트 레이저가공, 밀링, 선반, 라우터 등 커스텀서비스 견적요청 방법 설명동영상 입니다. -

2015-06-09

디바이스마트와 인텔®이 함께하는 IoT 경진대회! -

2015-05-19

드디어 adafruit도 디바이스마트에서 쉽고 저렴하게 !! -

2015-03-25

[29호] Intel Edison Review -

2015-03-10

Pololu 공식 Distributor 디바이스마트, Pololu 상품 판매 개시!! -

2015-03-09

[칩센]블루투스 전 제품 10%가격할인!! -

2015-02-02

[Arduino]Uno(R3) 구입시 37종 센서키트 할인이벤트!! -

2015-02-02

[M.A.I]Ahram_ISP_V1.5 60개 한정수량 할인이벤트!! -

2015-02-02

[68호]간호사의 업무 부담을 줄여주는 EMR연동 vital sign계측 시스템

68 ICT_간호사연동 (1)

2021 ICT 융합 프로젝트 공모전 우수상

간호사의 업무 부담을 줄여주는

EMR연동 vital sign계측 시스템

글 | 건양대학교 장건호, 이지훈

1. 심사평
칩센 최근 의료업계 또한 ICT 장비나 시스템 도입이 매우 늘어나고 있고, 개발자가 작품의 기획 중에 고민한 회진을 통한 활력 징후 체크를 대체하기 위한 시스템이 대표적으로 가장 먼저, 그리고 많은 곳에서 검토되고 있는 것으로 알고 있습니다. 의료기 자체의 휴대성 및 전기적 장비로의 변경은 많은 부분에서 이루어진 것으로 생각되고, 그 장비 등에 개발자가 구성한 시스템이나, 솔루션을 적용하면 의료진의 업무가 훨씬 더 효율적이고, 환자의 입장에서도 빠르고 정확한 이상 징후 전달이 가능할 것으로 보입니다. 제품 제작에도 많은 공을 들인 것으로 보이는데, 실제 시연 영상 등이 있었다면 어땠을까 하는 아쉬움이 남습니다.

펌테크 세심한 관찰력이 반영된 실생활과 밀접한 아이디어와 실용성이 우수한 작품이라고 생각합니다. 혈압계에 온도계 기능 부가 구성은 신선했고, 기획의도에 맞게 전체 시스템을 안정적, 효율적으로 구성했으며 완성도 높게 구현하였다고 판단이 되며 전체적으로 기획의도, 기술구현도, 완성도 등에서 우수한 작품으로 판단되며 제출된 문서를 종합적으로 검토해 볼 때 최종 완성은 되진 않은 것을 판단되나 추후 보완을 통해 완성도 높게 구현된다면 상업적으로도 손색이 없는 우수한 작품이 되리라 생각됩니다.

위드로봇 전체 완성도가 높은 작품입니다. 목표와 결과가 일치하는 잘 진행된 작품으로 보입니다.
뉴티씨 매우 실용적인 작품으로 생각되며, 실제로 격무에 시달리는 간호사들의 업무를 줄여줄 수 있을 것으로 생각합니다. 좀 더 콤팩트하게 제작하면 무겁지 않아서 좋을 것 같고, 표시기와 네트웍은 스마트폰에서 제공해도 될 것 같으므로, 같은 아이디어로 센서부만 제작하여 통신으로 데이터를 날려주고, 스마트폰에서 데이터들을 가공 처리한 후, LCD로 표시하여주면 더 가볍고 좋을 것 같습니다. 좀 더 고민하여서, 완성도를 높인다면 사업으로 연결할 수도 있을 것 같습니다.

2. 작품 개요
현재 간호사는 정해진 시간마다 환자의 활력징후(인간의 생명 활력도를 추정할 수 있는 가장 기본적인 생체징후)를 측정합니다. 이는 간호사의 기본 업무 중 하나로써 상당한 시간을 할애하는 것으로 알려져 있습니다. 중증환자가 아닌 일반 환자의 경우 일일이 간호사가 직접 회진을 돌며 측정해야 하는데 이때 개별의 장비를 사용하고 차트에 수기작성을 하는 등 전통적인 방식을 사용하고 있습니다. 측정 장비의 단순화와 자동화로 관리효율을 증대시키며 잘못된 기입을 방지하고 의료진의 업무효율 증대가 해당 작품의 목적입니다.
따라서 혈압계와 체온계를 하나의 패키지로 구성하여 사용자 경험을 고려한 디자인을 채택하였고 RFID태그 및 무선 통신기술을 활용하여 EMR(환자에 관련된 정보를 전산화 한 것) 자동 기입 시스템을 구현했습니다.

3. 작품 설명
최근 병원들은 ‘환자 중심’이라는 키워드에 맞춰 변화하고 있는데 이는 간호사의 서비스 영역까지 포함하고 있습니다. 따라서 현재 간호사의 본래 많은 업무와 더불어 부가적인 서비스까지 요구되고 있어 간호사의 업무 스트레스가 가중되고 있는 상황입니다. IoT 활력징후 시스템을 도입할 경우 활력징후를 측정, 수기로 작성, DB에 입력 등 일련의 과정을 간결화 하여 업무능률을 탄력적으로 높일 수 있습니다. 간호사의 휴게시간 보장 및 업무 만족도 향상 등을 기대하며 결과적으로 환자에게 더 많은 시간을 할애 하는 잠재적인 효과도 예상 할 수 있습니다.

68 ICT_간호사연동 (2)

4. 주요 동작 및 특징
4.1. 주요 특징
체온계
1. 체온계를 혈압계에 dock 방식으로 결합하여 휴대성을 강조한 구성
2. 비접촉식 적외선 체온 측정 방식
3. RFID Reader 로 환자 개별 식별 가능
4. 충전 형 배터리 사용
5. Dock 거치 시에는 혈압계로부터 전원을 공급받아 충전하고 분리 시 자동 부팅 후 블루투스 페어링 기능

혈압계
1. 오실로 메트릭 법 혈압 측정 방식
2. 5인치 터치스크린을 활용하여 측정 값 및 환자 정보 표시
3. 충전형 배터리 사용
4. 와이 파이를 이용하여 웹 데이터베이스에 접근, 체온계로부터 받은 RFID값으로 환자 정보를 불러오고 활력징후 측정값을 저장

동작 프로세스

68 ICT_간호사연동 (3)

5. 전체 시스템 구성
하드웨어

68 ICT_간호사연동 (4)
소프트웨어

68 ICT_간호사연동 (5) 68 ICT_간호사연동 (6)
그래픽 유저 인터페이스

68 ICT_간호사연동 (7)

6. 개발환경
체온계
1. MCU : Arduino Nano
2. 언어 : C Language
3. 사용 모듈 : 적외선체온센서, RFID리더, OLED, 블루투스 모듈

혈압계
1. MCU : Raspberry Pi4
2. OS : Raspbian (linux)
3. 언어 : Python Language
4. 사용 모듈 : 혈압계, 5인치 터치 디스플레이

GUI
1. 개발 툴 : pyqt5
2. 언어 : Python Language

3D모델 외부 케이싱
1. 설계 : SolidWorks 2017
2. 슬라이싱 : CURA, simplify
3. 3D프린터 : Anet a8, moment
4. 출력방식 : FDM
5. 사용재료 : PLA

백엔드
1. DB : Firebase
2. 서버 : Flask
3. 웹 : Jinja2
4. 언어 : Python

통합개발환경
1. 체온계, 혈압계 일부: Sublime Text 3
2. 혈압계, 웹 : Visual studio Code

7. 단계별 제작 과정
7.1. 체온계 제작
체온계 제작에 있어서 가장 중요했던 부분은 전원부입니다. 전원을 상시 켜둘 이유가 없으며 이는 충전을 방해하는 요인이기 때문에 충전 시에는 체온계의 전원을 꺼두고 충전 Dock에서 분리 시에 자동으로 전원이 켜지며 부팅이 완료되는 기능을 구현하고자 하였습니다.

68 ICT_간호사연동 (8)
트랜지스터의 디지털 스위치 기능을 활용해서 충전전압을 입력 신호로 판단하였습니다.
하지만 당시에는 쉽게 구할 수 있는 NPN 트랜지스터로 제작을 하였기 때문에 단순한 구조로는 원하는 기능을 구현할 수 없었습니다. NPN TR은 베이스에 양 전압을 가하면 콜렉터에서 이미터로 전류가 흐르게 됩니다. 이때 베이스에 입력되는 전압이 충전 전압이라고 가정할 때 우리는 콜렉터에서 이미터로 전류가 흐르는 것이 아닌 차단되어야 합니다. 반대로 베이스 전압을 제거 했을 때 전류가 흘러야 합니다. 따라서 베이스를 컨트롤하는 TR 스위치를 하나 더 추가하여 이중 TR 스위치를 만들었습니다.
구체적인 전체 회로는 원고 하단에 첨부합니다. 그 중 디지털 스위치의 구조만 확대한 내용은 다음과 같습니다.

68 ICT_간호사연동 (9)

Tr1이 1차 스위치 Tr2가 2차 스위치입니다. 1차 스위치에는 늘 항상 5V의 전압이 인가되고 있습니다. (배터리 전압) 하지만 2차 스위치에서 충전 전압이 인가 될 때 1차 스위치에 인가되던 배터리 전압은 GND로 흐르게 되어 전압이 낮아집니다. 결과적으로 1차 스위치는 개방되는 것입니다.
따라서 충전 중에는 2차 스위치에 전압이 인가되어 1차 스위치는 개방 되고 충전을 그만 두었을 때는 2차 스위치가 개방되어 1차 스위치는 배터리 전압에 의해 단락됩니다.

68 ICT_간호사연동 (10)

체온계의 핵심 기능은 다음과 같습니다.
1. RFID 리딩 후 블루투스 전송
2. 온도 센서 측정 후 블루투스 전송
3. 위 1,2번이 작동할 시 진동을 발생 할 것
4. 모든 진행 상황을 OLED에 비주얼라이징 할 것

사용 모듈
1. 진동 모듈
2. OLED
3. RFID 리더
4. 비접촉 적외선 온도 센서
5. 블루투스 HC-05
6. 충전 모듈

68 ICT_간호사연동 (11)

7.2. 체온계 코드
7.2.1. 진동 발생
진동 모듈은 핀 번호를 지정한 후 특정한 패턴으로 작동하게끔 미리 함수로 써 만들어 줍니다. 이 후 필요한 순간마다 함수를 호출 하는 방식으로 진동을 발생 시켜 햅틱 기능으로서 사용자에게 정보를 전달합니다.

68 ICT_간호사연동 (12)

7.2.2. OLED 표시
Oled 역시 디스플레이의 역할로서 늘 갱신되어야 합니다. 따라서 Oled를 리뉴얼하는 코드를 함수로 따로 만들어둔 다음 체온 측정 후, RFID 카드를 읽었을 때, 전원이 켜졌을 때, 등등 필요 시 호출 하는 방식으로 표시합니다.

68 ICT_간호사연동 (13)
전역 변수를 사용하여 체온 값과 RFID아이디를 갱신 합니다. 밋밋한 화면을 좀 더 알차게 구성하기 위해 이미지를 삽입하였습니다. 이미지는 흑백 이미지로 알맞은 크기로 제작 한 뒤 비트맵 단위로 변환하여 줍니다.

68 ICT_간호사연동 (14)
헥사 코드로 변환된 이미지를 u8g 라이브러리에 포함된 bitmap draw 기능을 활용하여 화면에 그려줍니다. 이미지를 bitmap으로 변환하는 과정과 기능은 http://en.radzio.dxp.pl/bitmap_converter/ 해당 사이트를 참고하였습니다.

7.2.3. RFID 리더
MFRC522 모듈을 사용하는 라이브러리의 예제를 수정하여 사용합니다. RFID 카드에는 여러 가지 정보가 담겨 있으며 그 정보를 읽고 쓰기가 가능합니다. 하지만 실질적인 데이터의 저장은 카드에 담는 것이 아니라 온라인 데이터베이스를 사용할 것입니다. 따라서 RFID의 고유한 ID만 사용합니다. 고유 ID를 주소처럼 사용하여 DB에 접근하고 저장합니다.

68 ICT_간호사연동 (15)

ReadNUID예제를 실행 한 후 카드를 읽힌 결과입니다. 여기서 우리는 hex값을 사용하도록 합니다. ID로 사용하기에 dec보다는 짧기 때문입니다. 결과 값을 곧이곧대로 사용하기에는 깔끔하지 않음으로 예제를 적당히 수정하여 핵심만 추려 냅니다.

68 ICT_간호사연동 (16)

앞선 그림 [카드의 용량과 id 값이 표시 됨]과 비교했을 때 hex값만 출력이 되며 16진수 한자리를 구별하는 띄어쓰기도 사라졌습니다. 우리는 hex값을 일일이 해석하려는 것이 아닌 하나의 ID로써 문자열을 사용할 것이기 때문에 위와 같이 수정하였습니다.

7.2.4. 비접촉 적외선 온도 센서
Adafruit_MLX90614라이브러리를 사용합니다. 섭씨온도 중 Object온도를 사용합니다. I2C통신을 사용하며 라이브러리 내장 함수를 사용하기 때문에 비교적 간단합니다. 하지만 사용하는 방식에 있어서 문제가 발생합니다. 먼저 온도를 언제 측정할지 시점을 지정할 방법이 필요합니다. 분명하게 신체를 가리키지 않았지만 온도를 측정하게 된다면 그 것은 잘못된 측정입니다. 두 번째로 센서의 민감도에 따라서 보정이 필요합니다. 경우에 따라 한 번의 측정으로는 정확한 값을 얻을 수 없습니다. 따라서 우리는 버튼이라는 물리적인 장치를 통해 외부에서 신호를 보내며 동시에 3초간 측정한 값의 평균으로 온도를 측정합니다.
기본적인 구조는 택트 버튼을 인터럽트 핀을 사용하여 부착하고 버튼이 눌렸을 때 3초에 걸쳐서 온도를 측정합니다. 3번의 측정값이 20도 이상이며 1의 자리가 모두 동일한 경우 평균을 내어 측정을 완료합니다. 해당 조건은 오류를 잡아내고 보다 정확한 측정을 위한 경험적 결과입니다.

68 ICT_간호사연동 (17) 68 ICT_간호사연동 (18)
타임 인터럽트는 1초마다 count값을 더해줍니다. count에 따라 온도 측정값을 배열에 넣고 그 값을 판단하는 구조입니다. 조건에 부합하면 타임 인터럽트는 종료됩니다. 이 것을 반복합니다.

7.2.5. 블루투스 전송 – 통신 프로토콜
앞서 RFID ID 리더와 온도 측정을 구현하였습니다. 또한 이를 Oled에 표시하기도 하였습니다. 측정값을 Dock(혈압계)에 전송을 해야 합니다. 이때의 통신 방법은 블루투스를 사용하며 모듈은 HC-05를 사용합니다. AT-mode를 통해 미리 사용할 블루투스의 주소를 알아내고 비밀번호와 이름을 설정합니다.
Dock과 통신하는 프로토콜은 string to string을 이용합니다. 센서의 종류와 측정값을 한 string 문자열에 첨부하여 전송하는 방식입니다. ID값은 “BC”, 체온은 “BT”라고 약속합니다. 또한 정보의 구분 또한 특정한 문자를 지정하여 한번 전송한 데이터의 끝을 표시해 줍니다. 사용한 문자는 “Z”입니다.
예컨대 체온을 전송할 땐 “BT36.5Z”의 형식을 맞추어 혈압계로 전송됩니다.

68 ICT_간호사연동 (19)

7.3. 체온계 케이싱
7.3.1. 3D 모델링
사용할 모듈을 버니어캘리퍼스로 실측을 합니다. 실측한 값을 토대로 solidwork에서 part를 만들어 줍니다. 만든 part를 assembly를 통해서 적절히 배치를 하고 케이스 모델링을 진행합니다. 이 때 3D프린터로 출력할 것을 고려하여 설계 합니다.

68 ICT_간호사연동 (20)

7.3.2. 3D프린터 출력
설계한 모델링을 슬라이싱을 통해 G-code로 변환한 후 3D프린터로 출력합니다. FDM 방식의 프린터기를 사용했으며 재료는 PLA 흰색입니다.

7.3.3. 후가공
FDM 방식의 출력물은 특유의 단층이 존재하게 됩니다. 작품에는 아무런 영향을 끼치지 않지만 완성도를 위해 후가공을 진행하였습니다. 후처리 방법은 사포로 표면을 갉아 내고 퍼티를 발라 틈새를 매우고 다시 사포로 갉아내는 방법을 지속해서 반복합니다. 이 후 흰색 락카를 도포하여 마무리합니다.

68 ICT_간호사연동 (1)

7.3.4. 조립
완성된 케이스 내부에 납땜한 회로와 기타 모듈들을 배치하여 조립을 완성합니다. 그 다음 정상적으로 작동하는지 확인 합니다.

68 ICT_간호사연동 (3)

7.4. 혈압계 제작
Dock은 혈압계 자체의 기능을 수행하면서 동시에 체온계와 DB를 이어주는 중간 매개체에 해당됩니다. 따라서 수신 받은 RFID ID 값과 체온 측정 값, 혈압 값을 적절하게 처리하여 디스플레이에 표시하고 firebace 데이터베이스에 저장하는 역할을 수행합니다.

혈압계의 핵심 기능은 다음과 같습니다.
1. 그래픽 인터페이스를 사용할 것
2. 체온계를 거치할 수 있는 구조일 것
3. 블루투스, 와이파이 통신할 것
4. firebase DB를 이용할 것

사용 모듈
1. 5인치 터치 디스플레이
2. 라즈베리파이3
3. 혈압계

7.5. 혈압계 코드
그래픽 유저 인터페이스를 제작합니다. 측정값과 환자 정보를 표시하기 위함으로 직관적이고 깔끔한 디자인을 선택합니다. UI는 파이썬언어 기반의 툴킷인 pyqt5를 사용합니다. 또한 pyqt5를 보다 쉽게 사용할 수 있도록 제작된 응용프로그램인 qtDesigner를 사용하여 드롭앤 드랍 방식으로 UI를 제작합니다.

68 ICT_간호사연동 (21)
완성된 gui는 저장 시 .ui 파일로 만들어집니다. 파이썬 프로그램 안에서 작동시키기 위해선 .py파일로 바꿔줄 필요가 있습니다. 변환 방법은 pyqt5 툴킷에서 제공된 변환기를 사용합니다.

UI 파일을 파이썬 파일로 변환
-> pyuic5 -x hello.ui -o hello.py

qrc 리소스 파일을 파이썬 파일로 변환
-> pyrcc5 hello_image.qrc -o hello_image_rc.py

이때 ui파일 뿐만 아니라 qrc파일도 .py파일로 변환해야 합니다. qrc 파일은 UI를 만들 때 사용했던 이미지가 저장된 파일입니다. 변환된 .py파일을 열어 보면 pyqt5문법을 활용해서 UI 코드가 변환되어 있습니다. 메인 코드에서 사용할 땐 import하여 불러옵니다.

7.5.1. GUI 작동 구조
절차적 프로그래밍과 다르게 GUI를 사용하는 환경에서는 GUI 따로 연산처리 따로 실행하는 쓰레드를 활용해서 프로그래밍 해야 합니다. GUI에 독립성을 부여해야 화면이 멈추지 않으며, 어떤 상황에서도 화면을 조작할 수가 있습니다. GUI 쓰레드를 기본적으로 동작하면서 필요시에 이벤트를 발생시킵니다. 이벤트 발생 시 GUI를 업데이트하는 함수만을 실행시켜서 화면을 수정하면 보는 이로 하여금 자연스러운 모니터링이 가능합니다.
이 후 부가적인 처리는 블루투스 통신과 GPIO를 이용한 혈압계 작동 파트입니다. 역시나 각각 개별로 쓰레드를 만들어서 작동시키고 화면을 갱신하는 방식으로 구현합니다.

7.5.2. 블루투스 소켓 통신
블루투스 통신을 위해 라즈베리파이에 제공되는 Bluetooth 라이브러리를 활용합니다. 기본적으로 소켓 통신을 기반으로 작성되었습니다. 매번 새로운 장치를 찾을 필요가 없기 때문에 사전에 알아두었던 체온계 블루투스 모듈의 주소 값을 지정합니다. 앞서 블루투스 통신 프로토콜에서 언급한 내용대로 전송 문자열의 앞의 문자는 정보의 종류를 뜻하며 맨 뒤 문자는 정보의 끝을 의미합니다.

68 ICT_간호사연동 (23)

아두이노와 통신 중에 모종의 이유로 데이터 중간에 개행 문자가 섞여서 전송되는 현상이 있었습니다. 수신된 문자열을 처리하기에 굉장히 까다롭기 때문에 “Z”문자를 한 개의 데이터를 구분하는 지점으로 지정하였습니다. 개행 문자를 기준으로 문자열을 나누지 않고 지정문자를 받을 때까지 모든 수신된 데이터를 차곡차곡 하나의 변수에 저장하였습니다.

68 ICT_간호사연동 (24)

이 후 통신 프로토콜에 따라서 수신된 데이터의 종류를 구분하고 실제 데이터만 추출하여 적재적소에 사용합니다.

68 ICT_간호사연동 (25)

온도와 RFID ID로 구분함. 또한 데이터를 추출한다.

68 ICT_간호사연동 (26)

7.5.3. DB 연동
환자정보 및 측정값은 내부저장소가 아닌 외부 클라우드 저장소를 사용합니다. 무료로 리얼타임 저장소를 서비스하는 firebase를 이용합니다. 저장소는 key와 val로 구성되어 있으며 초기화하는 방법과 데이터를 읽고 쓰는 방법은 관련 라이브러리인 pyrebase의 내장 함수를 참고합니다. 이는 git hub에서 찾을 수 있으며 데이터의 구성은 다음과 같습니다. 가장 상위 key는 RFID ID이며 아래에 이름, 성별, 나이 등 정보가 담겨 있습니다.

68 ICT_간호사연동 (27)

위 정보는 사전에 이미 기입되어 있습니다. 체온계에 RFID 카드를 태그하면 ID 값이 혈압계로 전송되고 이는 다시 firebase에 접근하여 관련 정보가 있는지 확인합니다. DB에 존재하는 id 값이라면 해당 환자 정보를 다시 혈압계로 전송하여 모니터에 표시합니다.

68 ICT_간호사연동 (28)

7.5.4. DB 자동 저장
사용자 경험을 고려하여 동작 순서에 따라 자동으로 측정값이 저장되도록 하였습니다. 모든 절차의 접근 key는 RFID카드의 ID를 사용합니다. 먼저 A 카드를 태그한 이후에는 몇 번이고 다시 측정할 수 있습니다. 체온계로부터 체온을 측정하고 혈압계는 라즈베리파이의 GPIO 기능을 사용하여 모터와 센서를 제어합니다. 혈압계 측정은 GUI화면의 버튼을 눌러 동작합니다. 측정이 끝난 후 B 카드를 태그하면 A를 측정했던 값이 자동으로 DB에 저장되는 방식입니다. 따라서 사용자는 자연스럽게 환자별로 인식표 태그 후 측정 순으로 진행하면 별다른 조작 없이 자동 저장되는 시스템을 설계하였습니다. 측정값은 날짜별로 묶고 다시 시간별로 묶어서 저장합니다.

68 ICT_간호사연동 (29)

 

7.6. 혈압계 케이싱
7.6.1. 3D모델링

68 ICT_간호사연동 (30)

5인치 디스플레이와 라즈베리파이를 고정하기 위해 적절히 배치하고 지지대를 세워 안정적으로 조립할 수 있도록 설계하였습니다.

68 ICT_간호사연동 (31)

또한 우측에 Dock을 배치하여 체온계를 거치하고 또한 중앙에 충전 단자를 위한 구멍을 내어 체온계를 단단히 고정함과 동시에 충전기능을 구현하였습니다.

7.6.2. 3D Printer 출력
설계한 모델링을 슬라이싱을 통해 G-code로 변환한 후 3D프린터로 출력합니다. FDM 방식의 프린터기를 사용했으며 재료는 PLA 흰색입니다. 크기와 구조 때문에 한 번에 모든 파트를 출력하는 것은 불가능하였습니다. 따라서 몸체, 덮개, 손잡이, 거치대, Dock으로 나누어서 출력 후 이어 붙이는 방식으로 제작하였습니다.

68 ICT_간호사연동 (32)

7.6.3. 후가공
후가공은 체온계 과정과 동일하게 퍼티와 락카 스프레이를 이용하여 표면 처리를 하였습니다.

7.6.4. 조립

68 ICT_간호사연동 (33)

체온계, 혈압계 프로그래밍 및 케이스 제작, 조립까지 마무리하였습니다. 혈압계의 디스플레이에 여러 가지 정보가 표시되며 터치를 통해 혈압계를 동작하거나 프로그램을 reboot할 수 있습니다. DB를 이용하여 정보를 불러오기는 물론 측정값을 저장할 수도 있습니다. 하지만 key와 val로 이루어진 DB그 자체로 정보를 열람하거나 처리하는 것은 상당히 불편합니다. 따라서 웹을 통해 DB에 저장된 정보를 보기 좋기 표시하는 벡엔드 파트를 구현하여 본 프로젝트를 마무리합니다.

7.7. 웹 모니터링 시스템

68 ICT_간호사연동 (34)

웹 모니터링을 위해서 플라스크(Flask)를 사용합니다. 플라스크(Flask)는 파이썬 웹 어플리케이션을 만드는 프레임 워크이며, Werkzeug 툴킷과 Jinja2템플릿 엔진에 기반을 둡니다. 플라스크는 프레임워크들 중에서 매우 심플하고 가볍지 만 중요하고 핵심적인 내용과 기능을 갖고 있기에 많이 사용되고 있습니다. 이 Flask를 통해 각 환자의 이름과 데이터를 구분하여 하나의 웹에 표기할 수 있도록 만듭니다.

7.7.1. DB에 저장된 데이터 불러오기
웹을 통해서 데이터를 띄우기 전 DB에서 데이터를 불러오는 작업을 수행합니다. 이렇게 DB에 환자의 RFID 카드를 통한 ID 값을 불러오게 되면 ID값 속에 저장되어 있는 모든 데이터가 불러집니다. 이를 통해 DB에 저장되어있는 모든 환자 데이터를 받아올 수 있습니다.

68 ICT_간호사연동 (35)

7.7.2. DB데이터 가공
불러온 데이터를 웹으로 불러오기 전 환자별로 날짜와 시간을 구분하여 데이터를 사용가능하도록 데이터를 가공합니다. 불러온 데이터는 Dict 형태로 모든 데이터는 원하는 데이터를 뽑아 가공하여 사용이 가능합니다. 이를 통해 각 리스트에 날짜와 시간, 시간에 따른 환자 데이터를 각각 모두 구분하여 데이터를 불러올 수 있습니다.

68 ICT_간호사연동 (36)

저장된 데이터를 Flask에 전달하여 웹상에 표시가 가능하도록 데이터를 넘깁니다.

7.7.3. 웹 디자인
웹디자인은 python Flask의 기본 웹 템플릿 엔진인 jinja를 사용합니다. jinja 템플릿 엔진은 태그, 필터, 테스트 및 전역을 사용자 정의가 가능해 자유롭게 사용이 가능합니다. 또한 디버그에 용이하며 최적의 python 코드로 컴파일이 가능하여 많이 사용됩니다. main코드에서 각 분할하여 각각 저장된 데이터를 불러와 웹으로 표현하기 위한 Flask의 html을 사용합니다. 상단의 이름과 함께 table을 나누어 데이터를 구분지어 데이터의 가시성을 더해줍니다.

68 ICT_간호사연동 (37)

7.7.4. 웹 데이터 추가
웹 main코드에서 가공한 이름, 날짜, 시간, 체온 등등 데이터를 각 테이블에 순차적으로 나열하게 됩니다. 이를 통해 DB에 저장된 데이터를 모두 날짜 시간순서대로 나열 가능하여 한눈에 확인 가능하도록 합니다.

68 ICT_간호사연동 (38)

이를 통해 여러 데이터가 생겨날 경우 같은 날짜에 대해 시간에 대한 측정값을 하나의 덩어리로 묶고, 날짜가 달라지면 그래프를 한 칸 밀어내어 데이터 구분을 지어 각 날짜별로 쉽게 확인이 가능합니다.

68 ICT_간호사연동 (39)

7.7.5. 웹 refresh를 통한 주기적인 데이터 업데이트

웹에 데이터를 추가한다고 하더라도 웹페이지는 실행된 시점의 하나의 장면만 보여주기 때문에 DB에 새로 추가된 데이터가 있더라도 웹상에 곧바로 표기되지 않습니다. 따라서 데이터가 추가되는 경우 바로 확인할 수 있도록 지정 초마다 웹 화면을 새로 고침 하여 데이터가 웹상에 반영되도록 합니다.

68 ICT_간호사연동 (40)
체온계, 혈압계에서 온 데이터를 저장한 DB에서 데이터를 다시 불러와 날짜와 시간에 따라 구분해 웹상에 정리하여 시각적으로 도출하며, 시간마다 새로 고침 하여 실시간으로 추가된 데이터도 바로 반영할 수 있도록 하였습니다. 이를 통하여 하나의 디바이스에서 지정된 ID를 통해 환자 데이터에 접근하여 상태를 측정한 데이터를 저장하고 저장된 DB를 통해 웹상으로 표기하여 주기적인 체온과 혈압의 데이터를 자동으로 저장 확인이 가능합니다.

8. 사용한 제품 리스트

제품명 디바 상품 번호
아두이노 나노 호환보드 CH340  1342039
블루투스 직렬포트 모듈 HC-05 (DIP) 1289993
MT3608 2A 스텝업 DC컨버터 모듈  1342935
라즈베리파이4 18650 5V 4A UPS 충전모듈 12497514
초소형 0611 코어리스 진동모터 1360991
라즈베리파이 5인치 HDMI LCD 터치스크린 모니터 1382229
라즈베리파이3 B+ 알뜰키트 1385482
FORA 자동전자혈압계 12550452
18650보호회로 리튬이온 충전지 2000mA 10889445
USB A/M Right-angle 커넥터 1322880
USB A/F, Right-angle 커넥터 1322109
0.96인치 I2C OLED 디스플레이 모듈 1311755
IMMS-12V 2647
18650 리튬배터리 보호회로 1329640
2N2222A 3247
10K옴 저항 38594
1K옴 저항 30504
3색 점퍼용 단선- 0.6 파이 1153807
MicroSDHC 16GB 10987018
비접촉 온도센서 모듈 MLX90614ESF 10918253
핫마인 필라멘트(1kg/PLA) 12710926
택트 스위치 1361702
아두이노 RFID 모듈 RFID-RC522 1279308
범용 페놀 만능 PCB 기판 50×70-단면 1341710
DC 파워잭 2파이 1322105

9. 작품사진

68 ICT_간호사연동 (41)

9.1. 회로도

68 ICT_간호사연동 (42) 68 ICT_간호사연동 (43)

10. 소스코드
체온계 Arduino

#include <SoftwareSerial.h>
#include <Wire.h>
#include <Adafruit_MLX90614.h>
#include <MsTimer2.h>
#include <SPI.h>
#include <MFRC522.h>
#include “U8glib.h”
#define SS_PIN 10
#define RST_PIN 9
U8GLIB_SSD1306_128X64
u8g(U8G_I2C_OPT_NONE);

SoftwareSerial BTSerial(5, 4); //TXD,RXD
Adafruit_MLX90614 mlx
= Adafruit_MLX90614();
MFRC522 rfid(SS_PIN, RST_PIN);
MFRC522::MIFARE_Key key;

String RFID_uid;
int t , count, temperature_button , ic = 0;
float list[3], value;
byte nuidPICC[4];

const uint8_t icon[] PROGMEM = {
0xFE, 0×00, 0×03, 0×00, 0×92, 0×00, 0×02,
0×80, 0×54, 0×00, 0x0A, 0×40, 0×38, 0×04,
0×06, 0×80, 0×10, 0×14, 0×03, 0×00, 0×10,
0×54, 0×06, 0×80, 0×11, 0×54, 0x0A, 0×40,
0×15, 0×54, 0×02, 0×80, 0×15, 0×54,
0×03, 0×00,
};

void setup() {
oled_boot();
attachInterrupt(digitalPinToInterrupt(2), interrupt, RISING);
pinMode(2, INPUT_PULLUP);
pinMode(6, OUTPUT);
Serial.begin(9600);
BTSerial.begin(9600);
SPI.begin();
rfid.PCD_Init();
mlx.begin();
Vibration_boot();
oled_image();
Serial.println(“Ready”);
temperature_button = 0;
}

void flash() {
Serial.println(count);
count ++;
}

void interrupt() {
temperature_button = 1;
}

void printDec(byte *buffer, byte bufferSize) {
BTSerial.print(“BC”);
RFID_uid = “”;
for (byte i = 0; i < bufferSize; i++) {
char c[2];
ltoa(buffer[i], c, 16);
if (buffer[i] < 0×10)
{
RFID_uid = RFID_uid + “0″;
}
RFID_uid = RFID_uid + c;
BTSerial.print(buffer[i] < 0×10 ? “0″ : “”);
BTSerial.print(buffer[i], HEX);
}
BTSerial.print(“Z”);
}

void loop() {
if (temperature_button != 0) {
if (ic == 0) {
for (int i = 0; i < 3; i++) {
list[i] = 0;
}
MsTimer2::set(1000, flash);
MsTimer2::start();
ic = 1;
}
list[count] = mlx.readObjectTempC();
if (list[count] > 20) {
if (int(list[0]) == int(list[1]) &&
int(list[1]) == int(list[2]))
{
if (t == 0) {
value = (list[0] +
list[1] + list[2]) / 3.0;
BTSerial.println(“BT” +
String(value) + “Z”);
Serial.print(“Object = “);
Serial.println(value);
MsTimer2::stop();
t = 1;
temperature_button = 0;
ic = 0;
count = 0;
Vibration_measuring();
oled_image();
} else {
t = 0;
}
}
}
}
if (count == 3) {
count = 0;
}

if ( ! rfid.PICC_IsNewCardPresent())
return;
if ( ! rfid.PICC_ReadCardSerial())
return;
printDec(rfid.uid.uidByte, rfid.uid.size);
Serial.println();
BTSerial.println();
Vibration_measuring();
oled_image();
rfid.PICC_HaltA();
rfid.PCD_StopCrypto1();
}

혈압계 RaspberryPi

# -*- coding: utf-8 -*-
import threading
import pyrebase
import datetime
import RPi.GPIO as GPIO
from bluetooth import*
from dock_ui import *
from PyQt5.QtCore import pyqtSignal
from PyQt5 import QtCore, QtGui, QtWidgets
from firebase import firebase
from time import sleep
from random import *

Motor_PIN = 26
Valve_PIN = 19

GPIO.setmode(GPIO.BCM)
GPIO.setup(Motor_PIN, GPIO.OUT, initial=GPIO.LOW)
GPIO.setup(Valve_PIN, GPIO.OUT, initial=GPIO.LOW)

firebase1 = firebase.FirebaseApplication(“https://kono-iot-default-rtdb.firebaseio.com/”)

config = { #개인 코드가 부여되는 공간입니다
}

firebase = pyrebase.initialize_app(config)
db = firebase.database()

y = “”
newcode = “”
code1 = “”
code2 = “”
tmp2 = 0
name = “”
state = 0
font = “”
count = 1
PUL = 0 #pulse 맥박 / 60~100회
DIA = 0 #diastolic b.p 이완기 혈압 / 60~80
SYS = 0 #systolic b.p 수축기 혈압 / 90~120

client_socket=BluetoothSocket( RFCOMM )
state =
client_socket.connect_ex((“98:d3:c1:fd:87:fe”,1)) #98:d3:c1:fd:87:fe

def bluetooth(self,ui):
global state,
client_socket,y,newcode,code2,code1,tmp2,name,font,count,PUL, SYS, DIA
nowDate = 0
nowtime = 0
while True:
ui.uiUpdateDelegate.emit(1)

if state: client_socket=BluetoothSocket( RFCOMM )
state =
client_socket.connect_ex((“98:d3:c1:fd:87:fe”,1)) #98:d3:c1:fd:87:fe / 98:d3:71:fd:68:ff
print(“waiting…”) sleep(1.5) ui.uiUpdateDelegate.emit(1)
else:
try:
x = client_socket.recv(1024) if(len(x)>0): for i in range(0, len(x), 1):
y = y + x[i] if(x[i] == “Z”):
newcode = y
print(newcode)
y = “”
if newcode.find(“BT”) != -1:
b = newcode.index(“BT”)
tmp1 = newcode[b+2:-1] tmp2 = float(tmp1)
newcode = “”
dt = datetime.datetime.now()
nowDate = dt.strftime(‘%Y-%m-%d’)
nowtime=dt.strftime(‘%H-%M-%S’)

if newcode.find(“BC”) != -1:
a = newcode.index(“BC”)
co = newcode[a+2:-1] code1 = co
name = firebase1.get(code1,’name’)

if(name == None):
font = “background:transparent;\n”"font:Bold 14pt \”Arial\”;\n” “\n” “color:rgb(255, 0, 0);” code1 = “” code2 = “”
name = “등록되지 않은 사용자”
else:
font = “background:transparent;\n”"font:Bold 20pt \”Arial\”;\n”"\n”"color:rgb(255, 255, 255);“
newcode = “”

if len(code2)>0 and (code1) != (code2):
print(nowDate) db.child(code2).child(“value”).child(nowDate).child(nowtime).update({“tmp”: tmp2}) db.child(code2).child(“value”).child(nowDate).child(nowtime).update({“PUL”: PUL}) db.child(code2).child(“value”).child(nowDate).child(nowtime).update({“DIA”: DIA}) db.child(code2).child(“value”).child(nowDate).child(nowtime).update({“SYS”: SYS}) code2 = code1 PUL = 0
DIA = 0
SYS = 0
tmp2 = 0
print(“upload complete”)
code2 = code1
except:
state = 1
continue
ui.uiUpdateDelegate.emit(1)
def bloodpressure(self, ui):
global PUL, SYS, DIA, count
while True:
if count == 1:
continue
elif count == 0:
if actuating():
count = 1 continue PUL = randint(60, 100) SYS = randint(110, 130) DIA = randint(70, 80) ui.uiUpdateDelegate.emit(1) count = 1

def actuating():
print(“operating”)
GPIO.output(Motor_PIN, 1)
GPIO.output(Valve_PIN, 1)
if waiting_and_repeat(5):
GPIO.output(Motor_PIN, 0) GPIO.output(Valve_PIN, 0)
return 1
GPIO.output(Motor_PIN, 0)
GPIO.output(Valve_PIN, 0)

def waiting_and_repeat(i):
for _ in range (0,i):
if count == 1:
return 1
sleep(0.5)
if count == 1:
return 1
sleep(0.5)
return 0
class MyFirstGuiProgram
(QtWidgets.QMainWindow, Ui_MainWindow):
global y,newcode
,code2,code1,tmp2,name,font, DIA,SYS,PUL
uiUpdateDelegate = pyqtSignal(int)
def __init__(self, parent=None):
QtWidgets.QMainWindow.__init__(self, parent=parent)
self.setupUi(self)
self.uiUpdateDelegate.connect(self.uiUpdater)
self.pushButton.clicked.connect(self.pushButton_clicked)
self.pushButton_2.clicked.connect(self.pushButton_2_clicked)
self.pushButton_3.clicked.connect(self.pushButton_3_clicked)
def uiUpdater(self):
self.name.setStyleSheet(font) self.TEM.setText(str(tmp2))
self.PUL.setText(str(PUL)) self.DIA.setText(str(DIA))
self.SYS.setText(str(SYS)) self.number.setText(code1)
self.name.setText(name)
if (state == 0): self.lineEdit.setStyleSheet(“background-image:url(:/work/bt.png)”)
else: self.lineEdit.setStyleSheet(“”)
def pushButton_3_clicked (self): client_socket.close()
GPIO.cleanup() sys.exit()
def pushButton_2_clicked (self): client_socket.close()
GPIO.cleanup()
NEWCODE = “sudo reboot” os.system(NEWCODE)
sys.exit()
def pushButton_clicked (self):
global count
print(“Start”)
if count ==1: count = 0
else:
count = 1
if __name__ == ‘__main__’:
import sys
app = QtWidgets.QApplication(sys.argv)
ui = MyFirstGuiProgram()
#ui.show()
ui.showFullScreen()
thread = threading.Thread(target=bluetooth, args=(None,ui))
thread2 = threading.Thread(target=bloodpressure, args=(None,ui))
thread.daemon = True
thread2.daemon = True
thread.start()
thread2.start()
sys.exit(app.exec_())

웹 모니터링(서버)

import pyrebase
from flask import Flask, render_template, request

config = {
#개인 코드가 부여되는 공간입니다
}

firebase = pyrebase.initialize_app(config)
db = firebase.database()
app = Flask(__name__)
app.jinja_env.add_extension(‘jinja2.ext.loopcontrols’)
@app.route(‘/’, methods=['POST', 'GET'])

def main():

tmp1 = db.child(’02C02E13′).get()
origin = tmp1.val()

tmp_name = origin.get(‘name’)
origin1 = origin.get(‘value’)

day_value = list()
day_list = list(origin1.keys())
day_list_len = len(day_list)
real_value = list()
for i in range(len(day_list)):
day_value.append(‘dic’+ str(i))
aa = origin1[day_list[i]] day_value[i] = dict()
day_value[i].update(aa)

for i in range(len(day_list)):
for j in range(len(day_value[i])):
time_value = list(day_value[i])
for k in range(len(day_value[i][time_value[j]])):
mea_value = list(day_value[i][time_value[j]].keys())
real_value.append(day_value[i][time_value[j]][mea_value[k]])

tmp2 = db.child(’0676041B’).get()
origin2 = tmp2.val()

tmp_name2 = origin2.get(‘name’)
origin2 = origin2.get(‘value’)

day_value2 = list()
day_list2 = list(origin2.keys())
day_list_len2 = len(day_list2)
real_value2 = list()
for i in range(len(day_list2)):
day_value2.append(‘dic’+ str(i))
aa2 = origin2[day_list2[i]] day_value2[i] = dict()
day_value2[i].update(aa2)

for i in range(len(day_list2)):
for j in range(len(day_value2[i])):
time_value2 = list(day_value2[i])
for k in range(len(day_value2[i][time_value2[j]])):
mea_value2 = list(day_value2[i][time_value2[j]].keys())
real_value2.append(day_value2[i][time_value2[j]][mea_value2[k]])

return render_template(‘flask jinja.html’,tmp_name = tmp_name, ttlen = day_list_len, name = day_value, tt = day_list, tmp_name2 = tmp_name2, ttlen2 = day_list_len2, name2 = day_value2, tt2 = day_list2)

if __name__ == ‘__main__’:
app.run(debug=True)

웹 모니터링(서버)

<!DOCTYPE html>
<html>
<head>
<meta http-equiv=”refresh” content=”3″>
<style>
table{width: 100%;}
td {text-align: center;}
p{font-size: 25px;}
p1{font-size: 18px;}
</style>
</head>
<body>
<div>
<h1>간호사의 업무부담을 줄여주는 EMR연동 Vital 계측 시스템</h1>
</div>
<hr>
<br>

<table border =3>
<tr>
<td rowspan=”50″><p>{{tmp_name}}</p></td>
</tr>
<tr>
<th><p>날짜</p></th>
<th><p>시간</p></th>
<th><div><p>측정값</p></div></th>
</tr>

{% for value in range(ttlen)%}
{% for key2, value2 in name[value].items() %}
<th><p1>{{ tt[value] }}</p1></th>
<td><p1> {{ key2 }} </p1></td>
<td><p1> {{ value2 }} </p1></td>
<tr>
</tr>
{% endfor %}
<tr>
<td colspan=”6″><div3>.</div3></td>
</tr>
{% endfor %}
</table>
<br>
<hr>
<br>

<table border =3>
<tr>
<td rowspan=”50″><p>{{tmp_name2}}</p></td>
</tr>
<tr>
<th><p>날짜</p></th>
<th><p>시간</p></th> <th><div><p>측정값</p></div></th>
</tr>

{% for value in range(ttlen2)%}
{% for key2, value2 in name2[value].items() %}
<th><p1>{{ tt2[value] }}</p1></th>
<td><p1> {{ key2 }} </p1></td>
<td><p1> {{ value2 }} </p1></td>
<tr>
</tr>
{% endfor %}
<tr>
<td colspan=”6″><div3>.</div3></td>
</tr>
{% endfor %}
</table>
<br>
<hr>
<br>

</table>
</html>

 

 

Leave A Comment

*