November 23, 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호]투명 폐페트병 분리배출 로봇

68 ict_ 투명페트분리배출 (31)

68 ict_ 투명페트분리배출 (2)

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

투명 폐페트병 분리배출 로봇

글 | 숭실대학교 오세찬, 강전완, 윤형섭, 임소현

 

1. 심사평
칩센 작품을 개발 과정과 결과에도 나와있듯이 개발자들의 정형화된 시연 환경 내에서는 만족할만한 결과를 도출해 낼 수 있지만, 실제 개발 당사자가 아닌 이들이 사용을 한다면 수많은 문제점이 발생할 수 있을 것으로 보입니다. 보고서에 포함된 후속 연구를 통해 작품의 개선을 할 수 있기를 바랍니다.
펌테크 자원재활용 이슈와 기술이 결합된 환경친화적인 작품이라고 생각합니다. 사전조사 및 아이디어가 반영된 제품 기획의 꼼꼼함이 돋보이며 영상처리 기술 구현 등을 효율적으로 접목하여 군더더기 없이 꼭 필요한 기능 위주로 목적에 맞는 최적의 시스템을 기획의도에 맞게 안정적으로 구현하였다고 생각합니다. 전체적으로 기술 구현도, 완성도 등에서 상당히 뛰어나고 훌륭한 작품으로 생각됩니다.
위드로봇 처리 속도가 아쉬습니다. 나머지는 훌륭한 작품입니다.
뉴티씨 투명 페트병만을 수거할 수 있도록하여, 투명하지 않은 페트병은 투명하게 만들어 투입하도록 하는 시스템인데, 매우 잘 제작되었습니다. 다만, 플라스틱이 아닌 금속캔 등을 투입하게 되면, 수거되지 않도록 안내하는 것도 추가하면 좋겠습니다. 검출 및 판별 속도는 개선해야 하는 점으로 보입니다. 또한, 수거되지 않는 것은 다시 가져갈 수 있도록 안내와 함께 기구적으로 문이 열리도록 하면 더 좋을 것 같습니다.
엔티렉스 부설연구소 투명 플라스틱 병 분리 배출로봇 개발은 온전한 플라스틱이 아니면 힘든 것 같습니다. 다만 카메라를 이용하여 페트병을 인식하여 분리 배출하는 기능에 대해서는 많은 생각을 하여 개발 한 것 같습니다. 패트병이 아닌 다른 물체가 왔을 때 일반쓰레기로 처리하는 방식을 적용했으면 합니다.

2. 작품 개요
2.1. 배경 및 목적

68 ict_ 투명페트분리배출 (3)

환경부에서는 2020년 12월 25일부터 투명 페트병만 분리수거 해 배출하는 정책을 펴고 있다. 하지만 기사를 통해 전용 봉투는 있지만, 그 안에 라벨과 뚜껑이 제거되지 않은 채로 페트병이 버려져 있는 사진이 공개되면서 아직 제대로 된 분리수거가 이루어지지 않음을 알 수 있다. 이러한 문제를 해결하고자 뚜껑과 라벨이 없는 무색의 플라스틱만을 모아서 재활용 효율 상승에 큰 도움을 주는 로봇을 제작하였다.

2.2. 차별점 및 기대효과
재활용 문제를 해결하기 위한 시스템은 여러 가지가 있다. 촉각을 이용해 자동으로 분리수거를 하는 ‘로사이클’, 물건을 넣으면 인공지능이 재활용 가능 여부를 판단해 버려주는 자판기 형태의 ‘네프론’, 어플을 통해 직접 사진을 찍으면 그 물건의 분리수거 방법을 안내하는 ‘스마트사이클’ 등이 바로 그것이다.
작품은 시중에 있는 다른 제품들과 크게 3가지 사항에서 차별성을 보인다. 첫 번째 차별점은 실용적인 면과 교육적인 면이 공존한다는 것이다. 투입구에 물건을 넣으면 로봇이 재활용 가능 여부를 판단하고, 재활용할 수 없다면 문제점과 해결방안을 디스플레이 화면과 음성안내 서비스를 통해 사용자에게 알려준다. 이를 통해 사람들은 본인의 재활용 인식 수준을 판단할 수 있고, 모르고 있었던 사항들을 알게 됨으로써 잘못된 행동을 개선할 수 있다. 두 번째는 로봇이 탈부착할 수 있다는 점이다. 이로 인해 공간의 큰 제약 없이 원하는 크기의 쓰레기통과 함께 사용할 수 있다. 이에 따라 기계를 열어 안에 있는 쓰레기들을 일일이 꺼내는 기존의 다른 제품들과 다르게, 봉투만 교체해서 사용하면 되므로 매우 편리하다. 세 번째는 제작 비용이 상대적으로 적다는 것이다. 값비싼 장치들과 센서들을 사용하지 않고도 필요한 기능들을 무리 없이 잘 구현해낼 수 있다.
투명 폐페트병 분리배출 로봇을 사용함으로써 다음과 같은 기대효과가 예상된다. 우선, 환경부가 마련한 투명 페트병 분리수거 정책의 실행률 증가와 정착에 큰 도움이 될 것이다. 투명 페트병 분리배출의 증가로 불필요한 플라스틱의 폐기가 방지되고, 재활용률이 증가하여 경제적, 환경적으로 큰 공익이 발생할 것이다. 또한, 로봇이 디스플레이와 음성으로 올바른 플라스틱 재활용 방법에 대하여 즉각적으로 홍보함으로써 사람들의 투명 플라스틱 분리수거 및 재활용에 대한 인식이 크게 발전될 것이다.

3. 작품 설명
3.1. 작품 구성도

68 ict_ 투명페트분리배출 (4)
3개의 디바이스는 ‘감각기관’, ‘동작 기관’, ‘뇌’로 역할이 분담되어 있다. 각 디바이스는 MQTT 통신을 통해 상호작용하며 역할에 맞는 기능을 수행한다.

68 ict_ 투명페트분리배출 (5)
(1) 감각기관(ESP8266)
센서(무게 센서, 초음파센서)가 연결되어 있다. State에 따라 필요한 센서값을 측정해 동작 기관과 라즈베리파이에 전달해준다.

(2) 동작기관(ESP8266)
동작 기관에는 서보모터가 연결되어 있다. 라즈베리파이에서 검출된 물체 명령에 따라 분리수거와 물체 반환을 하기 위한 모터작동을 수행한다.

(3) 뇌(라즈베리파이)
뇌에는 카메라, 모니터, 스피커가 연결되어 있다. 라즈베리파이에서 구성한 웹서버가 카메라로 찍은 사진을 분석하고 과정을 모니터(웹 클라이언트)와 스피커로 출력해준다.

3.2. 주요 동작 및 특징
3.2.1. 로봇 구동과 물체 진입 확인
감각기관에 해당하는 ESP8266에 설계되어있다. 물체 진입 확인은 5가지 state로 구성된다.

68 ict_ 투명페트분리배출 (6)

init state(00)
처음 감각기관에 전원이 공급되면 init 상태가 된다. 이 상태에서는 사용하는 센서(무게 센서, 초음파센서)들의 pin을 초기화하고, 와이파이와 MQTT client(sensor)를 연결한다. wait 상태로의 이동은 라즈베리파이의 명령으로 이뤄진다.
라즈베리파이의 웹서버는 서버가 실행되기 전 디바이스 간의 통신상태를 점검한다. 라즈베리파이가 보낸 ’reset’를 확인한 감각기관은 ‘connect’를 답장한다. 연결상태가 확인되면, 라즈베리파이는 ‘wait’를 보내고 서버를 실행한다. 메시지를 받은 감각기관은 ‘wait’ 상태로 이동한다.

server mqtt connect / 라즈베리파이
mqtt_client.on(‘connect’, function () {
mqtt_client.publish(‘sensor’, “reset”);
mqtt_client.publish(‘motor’, “reset”);
mqtt_client.subscribe(‘raspi’, function (err) {
if (!err) {
console.log(‘subscribe : raspi’);
}
})
})

웹서버가 MQTT client로 연결되면 raspi를 구독하고, 감각기관과 동작기관에 reset이라는 메시지를 전송한다.

server running / 라즈베리파이
var health = {
‘sensor’: 0,
‘motor’: 0
};
var server_start = setInterval(function () {
if (health.sensor == 1 && health.motor == 1) {
var server = http.createServer(app).listen(8080, function () {
console.log(‘server on’);
});
io = socketio(server);
io.sockets.on(‘connection’, function (socket) {
socket.on(‘sound’, function (data) {
console.log(data);
sound(data + ‘.mp3′);
})
});
clearInterval(server_start);
} else{
(health.sensor!=1)?mqtt_client.publish(‘sensor’,”reset”)
:mqtt_client.publish(‘motor’, “reset”);
console.log(“not connected”);
}
}, 1000)

health 변수에 key값인 sensor, motor는 서버 실행 전 감각기관, 동작기관의 통신상태 정보를 담고 있다. 1000(1초)마다 실행되는 함수를 만들어 연결상태를 점검하고 잘 연결되었다면, 웹서버와 동적인 사용자 인터페이스에 필요한 소켓 서버를 실행시키고, 감각기관에게 ‘wait’ 메시지를 보내준다. 연결상태가 올바르지 않다면, 해당 디바이스에 상태를 확인하는 reset 메시지를 재전송한다.

connect message receive / 라즈베리파이
mqtt_client.on(‘message’, function (topic, message) {
if (message == “input”) {
// 물체 진입코드
} else if (String(message).split(‘-’)[0] == ‘connect’) {
console.log(String(message));
health[String(message).split('-')[1]] = 1;
} else {
// 물체 무게 받는 코드
}
})

‘-‘를 기준으로 split 하여 감각기관, 동작기관 중 연결상태라 올바른 디바이스에 health 값을 1로 갱신하는 코드이다.

connect msg / 감각기관
void callback(char* topic, byte* payload, unsigned int length) {
String msg = “”;
for (int i = 0; i < length; i++) {
msg +=(char)payload[i];
}

if(msg == “reset”){
client.publish(“raspi”,”connect-sensor”);
}
else if(msg == “wait”){
state = 1;
}
}

‘reset’ 메시지를 받으면 연결상태인 connect-sensor를 보내주고, ‘wait’ 메시지를 받으면 ‘wait’ 상태로 이동한다.

68 ict_ 투명페트분리배출 (7)

wait state(01)
문이 열리길 기다리는 상태. 물체를 집어넣는 작품 상단에 있는 초음파센서가 문과의 거리를 측정한다. 센서값을 분석(문 열림)하여 문이 열렸음이 확인되면 ‘open’ 상태로 이동한다.

wait -> open state / 감각기관
int get_distance(){
int duration, distance;
digitalWrite(trig, LOW);
delayMicroseconds(10);
digitalWrite(trig, HIGH);
delayMicroseconds(10);
digitalWrite(trig, LOW);
duration = pulseIn(echo, HIGH);
distance = duration * 170 / 1000;
return distance;
}
// loop내 코드
int distance = get_distance();
if(state == 1 &&(200 < distance || 180 > distance)){
state++; // open state로 이동
}

거리를 측정하는 distance()를 전역함수로 작성하여 loop 내에서 필요에 따라 호출하도록 하였다. 문이 열릴 때 반사되는 초음파센서값이 열리기 전의 값(190)보다 값이 작다는 것을 이용하여 문이 열렸음을 확인한다. 추가로 200 이상의 값도 열렸다고 판단하는 이유는 초음파가 반사되어 200 이상의 숫자도 나올 수 있기 때문이다.

68 ict_ 투명페트분리배출 (8)

open state(10)
문이 열린 상태. 초음파센서는 문과의 거리를, 무게 센서는 물체의 무게를 측정한다. 두 센서값을 분석(문 닫힘, 물체무게감지)하여 정상적으로 물체가 들어왔으면 ‘close’ 상태로 이동한다.

close state(11)
물체가 들어온 상태. 라즈베리파이에게 물체가 들어왔음(input)과 물체의 무게(weight)값을 전송한다. 메시지를 수신한 라즈베리파이는 물체 분석을 시작하고 ‘analysis’ 답장을 보낸다. 답장을 받은 감각기관은 analysis 상태로 이동한다.

open -> close state / 감각기관
else if(state == 2 && (190 >= distance && 185 <= distance) && (scale.read()/1000 >= 330)){
state++; // close state로 이동
client.publish(“raspi”,”input”);
delay(1000);
String packet = String(scale.read()); // weight 전송
packet.toCharArray(weight,50);
client.publish(“raspi”,weight);
}

초음파센서로 측정한 값이 185 이상 190 이하이고 무게 센서로 측정한 값을 1000으로 나눈 값이 330 이상이면 물체가 정상적으로 들어왔다고 판단하여 ‘close’ 상태로 이동한다. close 상태로 이동 후 라즈베리파이에게 잘 들어왔음을 알리는 ‘input’과 물체의 무게 값인 ‘weight’ 값을 전송한다.

분석 시작코드 / 라즈베리파이
mqtt_client.on(‘message’, function (topic, message) {
if (message == “input”) {
mqtt_client.publish(‘sensor’, “analysis”);
io.sockets.emit(‘counter’, 3);
sound(“camera.mp3″);
} else if (String(message).split(‘-’)[0] == ‘connect’) {
// 초기 설정 관련
} else {
weight = parseInt(message);
}
})

input 메시지가 도착하면, 감각기관은 analysis 상태로 만들기 위해 ‘analysis’ 메시지를 전송하고, 물체 분석을 시작한다.

analysis 상태(100)
물체가 분석 중인 상태. 분석이 정상적으로 끝나고, 모터 동작에 대한 메시지가 수신될 때까지 대기한다.

3.2.2. 투명 폐페트병 검출 기능
뇌에 해당하는 라즈베리파이에 투명 폐페트병을 검출하는 모델이 3단계로 설계되어있다. 아래 표는 대략적인 흐름과 각 검출 단계에서 사용되는 분석 도구들을 보여준다.

68 ict_ 투명페트분리배출 (9)

카메라 촬영
node.js의 pi-camera 모듈을 사용하여 라즈베리용 카메라를 조작한다. 사진 촬영 후 정상적으로 사진이 저장되면, 성분분석 단계로 넘어간다.

Box Content

웹 클라이언트로부터 /camera 요청을 받으면, snap 함수를 사용해 라즈베리파이용 카메라를 동작시킨다. 사진이 제대로 찍혔으면, 첫 분석방법인 성분 분석 함수를 호출한다.

성분분석
google-cloud에서 제공해주는 vision api를 사용한다. 들어온 물체의 크기, object 무게 등을 측정하여 플라스틱인지 검출하는 단계이다.

68 ict_ 투명페트분리배출 (10)

ⓐ 크기 검사
이미지 분석 데이터를 분석하여 물체의 유무를 측정한다. 물체의 좌표에 해당하는 픽셀들을 추출하여 만약 추출되지 않았으면 물체가 인식되지 않았다고 판단하고 object_test 변수를 false로 변경한다.
ⓑ 오브젝트 검사
이미지 분석 데이터를 분석하여 해당하는 물체가 플라스틱인지 판단한다. [bottle, drink, plastic, glass]의 키워드가 하나도 나오지 않으면 페트병이 아니라고 인식해 object_test 변수를 false로 변경한다.
ⓒ 무게 검사
esp8266(감각기관)에서 물체 진입 확인과 함께 전달된 물체의 무게를 통해 플라스틱 bottle의 무게인지 검사한다. 이전검사에서 유리와 플라스틱을 구분하지 못했는데, 이 단계에서 분류된다. 추가로 이물질 특히 내용물이 담겨있는 경우도 이 검사에서 걸러진다.
ⓓ label_test 값 확인
ⓐ~ⓒ단계에서 기록된 object_test 값을 확인하여 false이면 물체를 반환하기 위해 모터에게 메시지(up)를 보내고, true이면 다음 단계인 글자 분석 단계로 이동한다.

성분 분석 코드 / 라즈베리파이(서버)
var weight = 0;
var area;
async function object_search() {
var object_test = false;
mqtt_client.publish(‘sensor’, “get_weight”);
const [result2] = await client.objectLocalization(‘test.jpg’);
try {
result2.localizedObjectAnnotations[0].boundingPoly.normalizedVertices;
const object =
result2.localizedObjectAnnotations[0].boundingPoly.normalizedVertices;
area = {
xs: parseInt(object[0].x * 640),
xe: parseInt(object[1].x * 640),
ys: parseInt(object[1].y * 480),
ye: parseInt(object[3].y * 480)
};
io.sockets.emit(‘area’, area);
} catch (e) {
console.log(“물체 감지X”);
}
const [result] = await client.labelDetection(‘test.jpg’);
const objects = result.labelAnnotations;

console.log(‘성분분석중’);
objects.forEach(function (objects, index) {
io.sockets.emit(‘ingredient_label’, objects.description);
if (objects.description.indexOf(‘bottle’) != -1 ||
objects.description.indexOf(‘Bottle’) != -1 ||
objects.description.indexOf(‘Drink’) != -1 ||
objects.description.indexOf(‘drink’) != -1 ||
objects.description.indexOf(‘Plastic’) != -1 ||
objects.description.indexOf(‘Glass’) != -1) {
object_test = true;
}
});
console.log(‘성분분석완료’);
if (object_test == true && weight < 420000) {
io.sockets.emit(‘complete’, ‘성분분석완료-성공-1′);
setTimeout(function () {
text_search(result);
}, 3000)
} else {
io.sockets.emit(‘complete’, ‘성분분석완료-실패-1′);
mqtt_client.publish(‘motor’, “up”);
}
}

성분 분석 결과를 나타내는 object_test 변수를 false로 초기화하고 분석 결과에 따라 값을 할당한다. vision api에 objectLocalization 함수를 통해 물체의 위치 픽셀을 리턴 받는다. 리턴 받은 값이 있다면, 그 물체의 위치를 저장하는 area 변수에 저장하고, 없다면 catch 문을 사용해 물체가 없음이 출력된다. 다음으로 labelDetection 함수를 통해 object의 성분을 파악한다. [bottle, drink, plastic, glass]라는 키워드가 있다면, object_test 값을 true로 변경한다. 성분분석이 완료되고, glass와 plastic을 구분하기 위해 무게 값을 측정하고 최종적으로 모터에게 결과 메시지를 전송한다.

글자 분석
google-cloud vision API를 사용한다. 폐페트병에 프린팅이 되어있거나 글자가 있는지 판단하는 단계이다.

68 ict_ 투명페트분리배출 (11)

글자 분석 코드 / 라즈베리파이(서버)
async function text_search() {
console.log(“글자분석중”);
var text_test = true;
const [result] = await client.textDetection(‘test.jpg’);
const detections = result.textAnnotations;
detections.forEach(function (text, index) {
io.sockets.emit(‘ingredient_text’, text.description);
if (text.description) {
text_test = false;
}
console.log(text.description);
});
console.log(“글자분석완료”);
if (text_test == true) {
io.sockets.emit(‘complete’, ‘글자분석완료-성공-2′);
setTimeout(function () {
color_search();
}, 3000)
} else {
io.sockets.emit(‘complete’, ‘글자분석완료-실패-2′);
mqtt_client.publish(‘motor’, “up”);
}
}

ⓐ 글자 검사
이미지 데이터 중 text 배열을 선택해 요소가 있는지 확인한다. 만약 요소가 하나라도 존재하면 label이 붙어있거나 프린팅이 되어있는 플라스틱으로 판단하고 text_test 변수를 false로 변경한다.
ⓑ text_test 값 확인
ⓐ단계에서 기록된 text_test 변수의 값을 확인하여 fasle이면 물체를 반환하기 위해 모터에게 메시지(up)를 보내고, true이면 다음 단계인 색깔 분석 단계로 이동한다.
글자 분석 결과를 나타내는 text_test 변수를 false로 초기화하고 분석 결과에 따라 true 값을 할당한다. vision api의 textDetection함수를 이용해 이미지에서의 식별된 글자들을 리턴받고, 이를 분석한다. 분석결과를 text_test 변수에 기록하고, 이를 토대로 모터를 작동시킨다.

색깔 분석
이미지를 RGB matrix로 만들어주는 image-to-rgba-matrix 모듈을 사용해 픽셀 단위로 RGB 값을 분석하여 폐페트병의 이물질 여부를 판단하는 단계이다.
ⓐ 영역설정
성분분석에서 얻은 물체 픽셀 데이터를 이용해 분석 영역을 설정한다. 영역을 설정함으로써 투명 폐페트병과 배경색만 검출된다.
ⓑ 표준편차
각 픽셀의 R, G, B값을 추출해 표준편차를 계산해 1차 분류를 진행한다. 표준편차가 15 초과면 유채색(빨강, 초록, 파랑), 15 이하면 무채색(검은, 흰)으로 분류한다.

68 ict_ 투명페트분리배출 (12)

ⓒ RGB 연산
1차 분류를 통해 나눠진 데이터를 토대로 각각 RGB 연산을 수행한다. 유채색의 경우 R, G, B값 중 큰 값을 찾아 Red, Green, Blue로 분류하고 무채색의 경우 R, G, B의 평균을 계산하여 40 이하인 것을 검은색. 200 이상인 것을 흰색으로 분류한다. 흰색은 연산하는 과정에서 영역(뚜껑, 몸통)별로 가중치를 부여해 데이터가 저장되는데, 이러한 이유는 백색 조명이 반사되어 나타나는 흰색이 몸통에 주로 위치하기 때문이다.
ⓓ 분류 연산
2차 분류된 데이터를 토대로 각 색깔의 개수를 측정한다. Red, Green, Blue, Black의 경우 10픽셀 이상 있으면 이물질이 있다고 판단한다. White의 경우 보정된 값이 5000 이상이면 흰색 이물질이라고 판단한다. 5000은 여러 번의 테스트를 거쳐 얻은 값이다. 이물질 판단 여부를 통해 color_test 값을 결정한다.
ⓔ color_test 값 확인
ⓐ~ⓓ단계에서 기록된 text_test 변수의 값을 확인하여 false이면 물체를 반환하기 위해 모터에게 메시지를 보낸다. true이면 최종적으로 투명한 폐페트병이라고 판단하여 물체를 분리수거 하기 위해 모터에게 메시지(down)을 전송한다.

색깔 분석코드 / 라즈베리파이(서버)
function color_search() {
console.log(“색깔분석중”);
// 변수 초기화
var color_test = true;
var pal = {
‘white’: 0,
‘black’: 0,
‘red’: 0,
‘green’: 0,
‘blue’: 0
}
pal['white']['state'] = 0;

imageToRgbaMatrix(‘test.jpg’).then(function (color) {
color.forEach(function (color, row) {
color.forEach(function (color, col) {
// 특정 영역 검색
if (col >= area.xs && col <= area.xe
&& row >= 115 && row <= area.ye) {
// 표준편차 구하기
var average =
(parseInt(color[0])+parseInt(color[1])+parseInt(color[2])) / 3
var sum = 0;
for (j = 0; j < 3; j++) {
sum += Math.pow(color[j] – average, 2);
}
var std = parseInt(Math.sqrt(sum / 3))

// 표준편차를 이용한 색구분
if (std <= 15) {
if (average > 200) {
if ((col < area.xs + 50) || (col > area.xe + 50)) {
pal['white'] += 300;
} else {
pal['white']++;
}
} else if (average < 40) {
pal['black']++;
}
} else {
if (color[0] > color[1] && color[0] > color[2]) {
pal['red']++;
} else if (color[1] > color[2]) {
pal['green']++;
} else {
pal['blue']++;
}
}
}
});
});
Object.keys(pal).forEach(function (key) {
console.log(key + ‘:’ + pal[key]);
if (key == ‘white’ ? pal[key] > 5000 : pal[key] >= 10) {
color_test = false;
console.log(key + “검출”)
io.sockets.emit(‘ingredient_color’, key);
}

});
console.log(“색깔분석완료”);
if (color_test == true) {
setTimeout(function () {
io.sockets.emit(‘complete’, ‘색깔분석완료-성공-3′);
mqtt_client.publish(‘motor’, “down”);
}, 2000)
} else {
io.sockets.emit(‘complete’, ‘색깔분석실패-실패-3′);
mqtt_client.publish(‘motor’, “up”);
}
});
}

색깔분석 결과를 나타내는 color_test 변수를 false로 초기화하고 분석 결과에 따라 true 값을 할당한다. imageToRgbaMatrix로 이미지를 RGBA 성분을 가지고 있는 2차원 배열로 리턴 받는다. 이를 성분분석에서 연산한 area를 토대로 색깔을 분류하고, pal 변수에 key로 지정된 색깔에 count 한다. 분류 작업을 마친 후 foreach문을 통해 색깔의 count 값을 비교하여 분석결과를 color_test 변수에 저장하고 이를 토대로 모터를 작동시킨다.

3.2.3. 분리수거
동작 기관 ESP8266에서는 서보모터를 제어하여 분리수거와 반환 기능을 수행한다.

68 ict_ 투명페트분리배출 (13)

명령 수신
라즈베리파이에서 투명 폐페트병 검출 기능이 수행된 후 분리수거가 가능 여부에 따라 UP(반환), Down(분리수거) 메시지를 동작기관으로 전송한다.

모터 동작
메시지를 받은 동작 기관은 메시지에 따라 서보모터를 제어해 물체 반환(UP)과 분리수거 (Down)을 수행한다. 그리고 감각기관으로 현재 모터의 상태인 motor_up과 motor_down을 전달한다. 감각기관의 도움을 받는 이유는 서보모터 자체 함수 중 각도를 리턴 받는 함수가 없어, 기능이 제대로 수행되었는지 알 수 없기 때문이다.

감각기관 상태 이동
모터 동작에 대한 메시지를 전달받은 감각기관은 메시지에 따라 analysis 상태에서 motor_up, motor_down 상태로 이동한다.

분리수거 및 반환 완료 검사
서보모터를 통해 틀이 움직이면 감각기관에 무게 센서값이 기준치에서 크게 변화한다. 즉 motor_up(반환), motor_down(분리수거) 상태에서 특정 무게 값을 정하여 기준치에 충족하면 반환, 분리수거가 완료되었다고 판단한다. 추가로 motor_up(반환)의 경우 물체가 가벼워 문을 뚫고 나오지 못하는 것을 고려하여 초음파센서값도 변수로 지정하였다. 초음파센서값을 통해 물체가 감지되지 않으면(물체 반환이 완료되면) 동작 기관에 reset을 명령한다.

전달받은 메시지에 따른 함수 호출 / 동작기관(모터)
void callback(char* topic, byte* payload, unsigned int length) {
String msg = “”;
for (int i = 0; i < length; i++) {
msg +=(char)payload[i];
}
Serial.print(“message : “);
Serial.print(msg);
if(msg == “up”){
motor_up();
}
else if(msg == “down”){
motor_down();
}
else if(msg == “reset”){
motor_reset();
client.publish(“raspi”,”connect-motor”);
}
}

‘up’ 메시지를 받으면 반환을, ‘down’ 메시지를 받으면 분리수거를 시작한다.

모터 제어 함수들 / 동작기관(모터)
void motor_up(){
servo1.write(180);
servo2.write(0);
client.publish(“sensor”,”motor_up”);
}
void motor_down(){
servo1.write(0);
servo2.write(180);
client.publish(“sensor”,”motor_down”);
}

void motor_reset(){
servo1.write(90);
servo2.write(90);
}

전역변수로 선언된 3개의 모터제어 함수들. 라즈베리파이에서 분리수거, 반환이 시작되면 현재 수행한 함수를 감각기관에게 메시지로 전달한다. 이는 무게 센서를 통한 서보모터 동작 완료를 확인하기 위함이다.

모터 메시지에 따른 상태 변화
감각기관(센서)
void callback(char* topic, byte* payload, unsigned int length) {
else if(msg == “motor_up”){
state = 5; // motor_up 상태로 이동
}
else if(msg = “motor_down”){
state = 6; // motor_down 상태로 이동
}
}

모터에서 받은 메시지에 따라 각각 motor_up state와 motor_down state로 상태 이동한다.

모터 동작 완료 확인 / 감각기관(센서)
else if(state ==5 && (190 >= distance && 185 <= distance) && (scale.read()/1000 <188) ){
client.publish(“motor”,”reset”);
state = 1; // wait 상태로 이동
}
else if(state ==6 && (scale.read()/1000 <0) ){
client.publish(“motor”,”reset”);
state = 1; // wait 상태로 이동
}

motor_up과 motor_down 상태에서는 무게 센서와 초음파센서값을 측정하여 분리수거와 반환이 완료됐는지 확인한다. 정상적으로 처리가 끝났으면 감각기관의 상태를 wait 상태로 이동하고, 모터에게 ‘reset’ 메시지를 보내준다. ‘reset’ 메시지를 받은 모터는 모터를 초기화시킨다.

3.2.4. 사용자 인터페이스
디스플레이

68 ict_ 투명페트분리배출 (14)
ⓐ 메인화면
메인화면은 2가지 표정으로 구분된다. 재활용을 성공한 경우나 처음 로봇이 구동된 경우 Happy, 재활용에 실패해 물체가 반환된 경우 Angry 캐릭터가 메인화면에 나타난다.

68 ict_ 투명페트분리배출 (15)

68 ict_ 투명페트분리배출 (16)
메인화면에서 오른쪽 위 끝에 있는 설명 버튼을 누르면, 재활용 방법과 로봇에서 폐페트병을 검출하는 방법을 소개한다.

ⓑ 분석화면
3단계의 분석 진행 과정과 결과를 우측에 실시간으로 렌더링하여 보여준다.

68 ict_ 투명페트분리배출 (17)

음성 출력
google TTL 서비스를 통해 음성을 추출하였고, 서버 측에서 음성을 출력하기 위해 play-sound를 사용했다.

68 ict_ 투명페트분리배출 (18)

play-sound로 재생하는 음성은 서로 독립적으로 재생되어 소리가 겹치는 일이 발생할 수 있다. 전역변수로 audio를 생성하고, 음성 재생이 필요할 때마다 audio.kill()로 현재 재생되고 있는 소리를 제거하고 play를 통해 재생시켰다.

음성출력 / 라즈베리파이(node.js)
var audio = 0;
function sound(data) {
if (audio != 0) audio.kill();
audio = player.play(‘audio/’ + data);
}

3.3. 개발 환경
· OS : 라즈비안(라즈베리파이), 윈도우10(노트북)
· 개발 언어 : C, javascript(node.js), html, css
· 툴 : brackets, 지니, 아두이노IDE
· 통신 : MQTT, HTTP(Socket.io, GET)

Node.js 주요사용 모듈

express Node.js를 위한 웹 프레임워크
socket.io 실시간 웹 애플리케이션을위한 JavaScript 라이브러리
@google-cloud/vision google cloude vision api를 사용하기 위한 모듈
image-to-rgba-matrix 이미지를 rgba matrix로 변경해주는 모듈
pi-camera 라즈베리파이용 카메라를 제어하기 위한 모듈
play-sound 음성 재생을 위한 모듈

4. 단계별 제작과정

4.1. 하드웨어 제작과정
하드웨어 1주차
ⓐ 주제 선정 및 기능 아키텍처 작성을 통한 필요 물품 구매

68 ict_ 투명페트분리배출 (20)
필요 물품
· 폼보드 : 특별한 장비 없이 가공에 있어 자유롭고 튼튼하다.
· ESP8266 mini : 각 기관의 역할을 나누는 데 필요했다.
· 무게 센서 : 물체의 무게를 측정해 분류하는 데 필요했다.

라즈베리파이, 카메라, 스피커, 모니터, 초음파센서는 기존에 가지고 있던 것을 사용했다.

하드웨어 2주차
ⓐ 역할을 분담하여 센서, 모터, 카메라를 동작(테스트)해보는 학습을 진행하였다.

ⓑ 제작에 앞서 외형도를 제작했다.

68 ict_ 투명페트분리배출 (21)
대략적인 제품 크기와 부품을 설치할 위치를 그렸다.

하드웨어 3주차

ⓐ 외부 틀 제작 : 2주차에서 작성한 외형도를 바탕으로 문과 틀을 만들었다.

68 ict_ 투명페트분리배출 (1)

ⓑ 밑판제작 및 부품 테스트

68 ict_ 투명페트분리배출 (22)

무게 센서와 서보모터를 연결해 물체가 올라갈 밑판을 제작하였고, 모터 동작을 테스트했다.

68 ict_ 투명페트분리배출 (23)

초음파센서를 입구 상단에 설치하고, 무게센서와 테스트했다.

ⓒ 내벽 제작

68 ict_ 투명페트분리배출 (24)
좌측에 동작기관, 우측에 감각기관으로 회로를 연결하였다.
회로 정리 후 제작한 내벽을 붙여 선이 보이지 않게 만들었다.

ⓓ 앞면 제작

68 ict_ 투명페트분리배출 (25)

로봇의 모니터가 들어갈 앞판을 제작하였다.

ⓔ 윗면 제작

68 ict_ 투명페트분리배출 (26)

조명, 라즈베리파이 카메라, 스피커 등이 놓일 윗면을 제작하였다.

하드웨어 4주차

68 ict_ 투명페트분리배출 (27)

카메라 선이 길지 않아 동작 테스트에 문제가 발생했다. 윗면에 설치하는 것이 아닌, 윗면에 붙이는 것으로 계획을 변경했다.
쓰레기통의 교체가 쉽고 들어있는 상황을 볼 수 있도록 하단은 오픈형으로 제작하였다.

4.2. 소프트웨어 제작과정
소프트웨어 1주차
하드웨어 제작 1주차 첨부한 기능 구성도를 작성하고, 필요한 기술을 조사했다.

조사내용
· 각 기관 간의 통신은 MQTT 통신을 사용한다.
· 로봇의 얼굴인 디스플레이는 웹사이트 형태로 제작한다.
· 카메라 모듈을 사용해 사진을 찍은 후 구글 API을 사용해서 라벨 및 이물질의 유무 등을 분석한다.
· 분석 결과에 따라, 모터가 작동해 플라스틱을 분리배출할 수 있게 도와준다.

소프트웨어 2주차
ⓐ 필요 기술 학습(물체 검출)

68 ict_ 투명페트분리배출 (28)

vision API를 사용해 물체의 성분(plastic, glass 등), 텍스트(프린팅, 라벨), RGB 추출(이물질 검사)을 하기 기대했다. 성분 분석과 텍스트 검출에는 문제가 없었지만, 색 추출에서 이질적인 색을 잡는 민감도를 기대할 수 없었다. 다른 방법인 10개의 색 파레트를 추출하는 color-thief 모듈을 사용하여, 뚜껑 등의 이질적인 색 검출에 성공했다.

ⓑ 필요 기술 학습(사용자 인터페이스)

68 ict_ 투명페트분리배출 (29)

웹 클라이언트 : 로봇의 얼굴에 해당하는 모니터에 HTML, CSS 등으로 캐릭터를 제작하였다. 아직 센서들과 통신을 하지 않기 때문에, 3초가 지나면 사진이 찍혀 ⓐ의 단계로 넘어가도록 코드를 작성했다.
웹 소켓 : 동적인 웹 클라이언트를 만들기 제작하기 위해 웹 소켓을 사용하였다. 서버에서 연산한 결과를 클라이언트로 데이터를 전송해 사용자가 실시간으로 검사 결과를 확인할 수 있다.

ⓒ 모터, 센서 기능 구현
아두이노 IDE를 사용해 물체가 들어오고 나감을 알 수 있도록 초음파센서 관련 함수를 작성하였고, 분리수거 & 반환을 위해 모터 함수를 작성하였다.

소프트웨어 3주차
ⓐ MQTT 통신

68 ict_ 투명페트분리배출 (30)
MQTT 통신을 사용하여 각 디바이스가 상호작용한다. MQTT 통신을 사용한 이유는 클라이언트-서버 구조로 이뤄진 HTTP, TCP는 주로 클라이언트 요청에 대한 서버에 응답으로 통신하기 때문에 디바이스 간 통신이 어렵다. 반면, MQTT는 broker, publisher, subscriber 구조로 이루어져 있으므로, broker가 중계하고 client 간의 통신이 가능하다. 라즈베리파이에 브로커를 설치하고, 감각기관, 동작기관, 웹서버가 client가 되어 각자 필요한 메시지를 보내고, 받은 메시지를 통해 동작을 실행한다.

ⓑ 사용자 인터페이스 추가(웹 클라이언트)

68 ict_ 투명페트분리배출 (31)

메인화면 : 분리수거 성공과 실패에 따라서 메인화면의 캐릭터 얼굴이 변화한다.
설명페이지 : 사용자가 재활용 방법과 로봇의 검출 과정에 대해 학습할 수 있다.

ⓒ 사용자 인터페이스 추가(음성)

68 ict_ 투명페트분리배출 (32)

교육적인 로봇의 목표에 재활용에 관한 피드백을 제공하기 위해 음성 파일을 각 단계별로 출력되게 제작하였다.

소프트웨어 4주차
ⓐ 각 디바이스에 MQTT 통신을 구현하였다.

라즈베리파이(MQTT code)
var mqtt_client = mqtt.connect(‘mqtt://192.168.0.51′)
mqtt_client.on(‘connect’, function () {
mqtt_client.subscribe(‘raspi’, function (err) {
if (!err) {
console.log(‘subscribe : raspi’);
}
})
})

mqtt://주소를 통해 라즈베리파이에 구축한 브로커에 연결하여 ‘raspi’를 구독하였다.

감각기관, 동작기관 (MQTT code)
while (!client.connected()) {
Serial.print(“Attempting MQTT connection…”);
if (client.connect(“ESP8266Client”)) {
Serial.println(“connected”);
client.subscribe(“sensor”);
} else {
Serial.print(“failed, rc=”);
Serial.print(client.state());
Serial.println(” try again in 5 seconds”);
delay(5000);
}
}

esp8266 mini에서 MQTT 브로커로 연결하여 감각기관의 경우 ‘sensor’을 동작기관의 경우 ‘motor’을 구독한다.
2주차에서 분담을 통해 구현한 물체 감지, 모터작동 기능들을 MQTT 통신에 맞게 코드를 수정하였다.

소프트웨어 5주차
물체 테스트를 진행 중에 color-thief 모듈을 이용한 rgb검출에 대한 문제점이 드러났다. vision API보다는 작은 이물질에 대해 식별 가능했지만, 더 작은 이물질에 대해서는 검출하기 어려웠고, 결정적으로 배경색과 RGB 값이 비슷한 흰색, 검은색은 검출하기 어려웠다. 대체방법으로 이미지를 RGB matrix으로 변환해주는 모듈인 color-to-rgba-matrix를 사용하여 픽셀 단위의 분석을 진행하였다. 픽셀 단위로 연산량을 줄이기 위해 물체의 위치에 따른 분석하는 방법을 생각하였고, vision API에 위치 정보를 제공해주는 함수를 사용해 경곗값과 크기를 측정했다.

소프트웨어 6주차
ⓐ 사용자 인터페이스(웹 클라이언트)

68 ict_ 투명페트분리배출 (1)

5주차에서 측정한 경계값을 웹 클라이언트에 전송하여 BOX 형태로 표시하도록 하였다. 이는 사용자가 물체 분석되는 과정을 확인하는데 도움을 준다.

ⓑ 감각기관 코드 수정
감각기관의 변수를 수정하였다. 이전에는 문이 열리고 닫히면, 물체가 들어왔음을 감지했었는데, 조건문을 추가하여 무게 센서값까지 측정하는 방법으로 변경하였다.

ⓒ 연결상태 확인 추가
로봇을 테스트하기 위해 서버를 실행하는 과정에서 통신상태를 확인하지 않고, 실행한 경우, 연결상태를 확인하지 않고 실행해 로봇의 흐름이 섞이는 일이 발생했다. 이러한 문제를 해결하기 위해 처음 서버가 실행되기 전 MQTT 통신상태를 확인하기 위한 메시지를 보낸다. 메시지를 받은 디바이스는 초기화를 진행하고, 답변을 전달한다. 답변을 받은 라즈베리파이는 웹 서버를 실행시킨다.

5. 사용한 제품 리스트

모델명 디바 상품번호
SEN0160 1278214
HC-SR04 1076851
MG996R 1313388
WeMos D1 mini 1327519
Raspberry Pi 4 Model B 12234534
Raspberry Pi Touch Display 1273487
TS-CS01BO -
밝기조절 발광 LED 라이트 -
RPI 8MP CAMERA BOARD 1077951

6. 후속연구제안

6.1. 배경 색과 빛 반사
페트병의 투명성 여부를 판단하는 과정은 사진을 촬영하여 배경색과의 유사도를 검사하는 방식으로 진행된다. 이 때, 두 가지 문제점이 발생한다. 만약 라벨이나 이물질 등이 배경색과 유사한 색이라면 로봇은 ‘투명한 페트병’인 것으로 판별하게 된다. 또한 플라스틱 표면에 빛을 비추면 빛이 반사되어 카메라로 사진을 찍으면 반사된 부분이 흰색으로 인식되어 투명성 판별에 장애요소가 될 수 있다. 이는 신뢰도 하락과 재활용률 하락으로 이어지는 문제이므로 주의가 필요하다. 본 작품은 배경색을 회색(카메라로 촬영했을 때 R, G, B 세 값의 표준편차가 15이내, 평균은 40이상 180이하. 이때, 각 값은 0~255사이의 값을 갖는다.)으로 설정하여 라벨이나 이물질의 색과 분명한 차이를 둘 수 있도록 하였다. 그러나 만약 회색으로 이루어진 플라스틱 제품이 나오게 되면 본 작품에서 채택한 투명성 판별법은 신뢰도가 떨어지게 된다. 또한 빛 반사문제는 카메라를 사용한다면 피할 수 없는 문제이므로 해결하기 어렵다. 따라서 후속연구에서는 LPKF TMG 3 등의 광투과율 측정 장치를 이용하여 플라스틱의 투명도를 정확하게 측정할 수 있도록 할 것을 제안한다.

6.2. 플라스틱 압축 기능
플라스틱은 압축하여 배출해야 쓰레기통이나 쓰레기봉투를 효율적으로 사용할 수 있다. 따라서 플라스틱을 쓰레기통에 담는 과정에서 플라스틱을 압축하는 기능이 있으면 좋겠다는 생각을 했다. 이를 위해서는 강한 모터나 압축기가 필요한데 전력소비, 무게, 부피, 예산 등의 측면에서 효율성이 떨어지므로 배제하였다. 대체할 수 있는 기능으로는 페트병이 압축되었는지의 여부를 판단하는 방법이 있다. 이를 구현하려 하였으나, 압축된 페트병을 판별할 수 있는 명확한 기준을 찾기는 힘들었다. 비슷한 크기의 페트병이라면 무게도 비슷할 것이라고 가정하고 압축된 페트병과 압축되지 않은 페트병의 (넓이/무게) 값을 비교하였으나 유의미한 데이터를 얻지 못했기 때문에 페트병이 압축되었는지 판별하는 기능은 구현하지 못했다. 따라서 후속연구에서는 플라스틱 압축 기능이나 압축되지 않은 플라스틱을 구별하는 기능을 추가할 것을 제안한다.

6.3. 처리량
본 작품은 한 번에 1개의 페트병만 검사할 수 있다. 따라서 많은 양의 페트병을 버리려고 한다면 하나씩 천천히 넣어줘야 한다. 이는 굉장히 불편한 작업이므로 본 작품을 일상생활에서 사용하기 위해서는 이 문제를 반드시 해결해야 한다. 그러나 한 번에 검사할 수 있는 페트병의 수는 제한적이다. 따라서 큰 통에 페트병을 담아두고 거기에서 하나씩 꺼내서 검사하는 방법을 생각해볼 수 있다. 그런데, 사진을 찍는 시간은 약 10초, 재활용 가능한 플라스틱인지 분석하는 시간은 약 10초로 한 페트병을 처리하는 것에 평균 20초의 시간이 소요된다. 이는 디스플레이에 분석과정을 보여주고 음성으로 안내하기 위해서 의도적으로 시간을 늘린 것인데, 많은 양의 페트병을 처리하는 것에 방해요소로 작용한다. 따라서 안내하는 것을 간소화하는 것으로 시간을 단축할 수 있다. 또한 여러 개의 페트병을 한 번에 촬영하여 처리시간을 단축하는 방법도 있다. 단, 한 번에 많은 페트병을 처리할 경우, 그 중에서 재활용 되지 않는 패트병을 골라내는 방법에 대한 고찰이 선행되어야 한다.

7. 참고문헌
· 그린피스 김이서(2019년 12월),플라스틱 대한민국- 일회용의 유혹https://www.greenpeace.org
· 투명 페트병 재활용 제품 안내, 환경부 :https://www.newsro.kr
· 최병용(2021.02.19), 투명 페트병 분리배출 현장에 가보니~ : https://www.korea.kr/news/reporterView.do?newsId=148883922

 

 

[68호]일회용품을 사용하지 않는 친환경 자판기

68 ICT_우수상_친환경 자판기 (7)

68 ICT_우수상_친환경 자판기 (26)

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

일회용품을 사용하지 않는 친환경 자판기

(Eco-friendly Vending Machine)

 

글 | 대한상공회의소 서울기술교육센터 한승진, 김연진, 신상우, 유종선, 진민경

 

1. 심사평
칩센 최근에 여러 가지 형태로 무인음료 자판기가 설치되는 것으로 알고 있습니다. 이러한 무인 자판기는 기존과 같이 컵이 내장된 형태도 있지만, 작품과 같이 개인컵을 이용한 제품도 있는 것으로 알고 있습니다. 이미 기존에 있는 제품이라 하더라도 더 보완된 제품을 가격 경쟁력이 있게 만들 수 있다면 개발의 의미는 충분히 있습니다. 또한, 불편함이 있더라도 작품의 기획의도와 같은 목적을 보여주는 것도 좋은 방법이라 생각합니다. 이왕 echo-friendly라는 가치를 걸었다면 상용화된 1회용 컵을 구분하는 제약을 두어 제품의 목적을 분명히 밝히는 것도 좋은 방향이 아닐까 하는 생각입니다.

펌테크 친환경을 고려한 실생활과 접목된 실용성과 창의성을 지닌 작품으로 생각됩니다. 전체 하드웨어 및 소프트웨어 개발 환경 구성이 작품의 성격에 맞추어 체계적으로 적절하였다. 시스템 구성중 opencv를 사용한 영상처리를 과정 및 필요기능 위주의 심플한 형태로 짜임새 있고 깔끔하게 구성한 스마트폰용 APP은 인상적이었으며 전체적으로 기획의도, 기술 구현도, 완성도 등에서 우수한 작품이라고 생각됩니다.

위드로봇 아이디어를 프로토타입을 통해 개념을 확인한 부분이 훌륭합니다.

뉴티씨 텀블러를 사용하도록 하는 시스템은 조금 불편하더라도 환경을 지킬 수 있는 솔루션 중 하나일 수 있다는 생각이 듭니다. 나의 편리성을 위한 것이 아닌 후세대를 위한 지구 환경을 생각하여, 일회용품 사용을 줄일 수 있는 이와 같은 시스템을 좀 더 연구해야 할 것입니다.

2. 개발 배경

68 ICT_우수상_친환경 자판기 (1)

일회용품을 많이 사용하는 요즘, 전 세계적으로 일회용품 사용을 줄이기 위해 여러 가지 정책을 펼치고 있다. EU의 경우 올해(2021년)부터 플라스틱 세를 도입하여 플라스틱 사용 절감에 앞장서고 있다. 우리나라의 경우 커피 전문점 매장 내에서는 플라스틱 컵과 빨대를 사용할 수 없다. 또한, 슈퍼마켓에선 비닐봉지 사용이 불가하며, 대형마트에서는 포장용 테이프를 제공하지 못하게 된 상태이다.
하지만 1인 가구의 증가와 더불어 코로나 바이러스로 인한 배달음식 주문량이 많아지면서 일회용품 사용량 즉, 일회용품 배출량이 나날이 증가하고 있는 추세이다. 서울디지털재단의 ‘1인 가구 증가에 따른 일회용 플라스틱 배출 실태 분석’ 보고서에 따르면 1인 가구가 배출한 일회용품 양은 일평균 30개로 조사되었으며, 이는 다인 가구에 거주하는 1인의 배출량보다 약 2.3배 많은 양이라고 한다. 또, 서울 거주 시민 1000명을 대상으로 실시한 설문에 따르면 배달 음식 주문빈도는 코로나 발생을 기준으로 약 1.4배 증가한 것으로 조사되었다고 한다.
더불어 일회용 플라스틱 배출을 줄일 수 있는 방법에 대한 설문조사(1000명 대상)에서 ‘일회용 플라스틱 사용 저감 유도를 위한 보상체계 필요 여부’에 대해서는 응답자의 91.3%가 보상(인센티브)의 필요성을 공감하고 있는 것으로 나타났다고 한다. 보상방식으로는 ‘현금 지급’이 42.4%로 가장 높았고 이어서 ‘에코-마일리지 지급’(25.6%), ‘지역 화폐 지급’(18.3%) 등이 뒤를 이었다.
위의 보고서 내용과 마찬가지로 팀원들 모두가 일회용품 사용의 심각성과 사용 절감에 대한 필요성 모두 느끼고 있었다. 따라서 우리는 일상 속에서 일회용품을 절감할 수 있고 이에 따른 보상을 지급하는 제품을 만들기 위해 아이디어를 모았다. 그 결과 ‘일회용품을 사용하지 않는 자판기’를 개발하게 되었다.

3. 개발 목표
요즘 일회용품 사용을 절감하기 위한 사회적 움직임의 일환으로 일부 프랜차이즈 카페에서 플라스틱 컵 대신 개인 용기에 음료를 받으면 일정 금액을 할인해주고 있다. 이러한 서비스의 적용 범위를 확대하여 도서관, 스터디카페 또는 대학교, 공공기관 등 특정 사람들이 지속적으로 방문하는 곳에 배치된 자판기에 적용하여 일회용품 사용 절감에 이바지하도록 한다.
해당 자판기를 사용할 때마다 핸드폰 번호 입력을 통해 일정 금액이 에코-마일리지로 적립되며 추후에 실제로 제품이 상용화되는 경우 마일리지를 사용할 수 있도록 한다.

4. 작품 개요
일상 속에서 아두이노와 라즈베리 파이 그리고 젯슨 나노를 활용하였다.
아두이노는 음료 결제, 선택, 추출 등 자판기의 전체적인 동작을 담당하고, 젯슨 나노는 영상처리를 통해 음료 추출 전 사용자의 텀블러가 제자리에 놓여있는지 판단하였다. 라즈베리 파이는 터치스크린을 통해 자판기 내부의 진행상황을 알려주고, 자판기 이용 후 휴대폰 번호를 입력하면 에코-마일리지를 적립함과 동시에 누적된 마일리지를 확인할 수 있도록 하였다.
부가적으로 자판기 관리자를 위한 안드로이드 애플리케이션을 제작하여 자판기 내부 음료의 상태(양)를 확인하고, 음료별 누적 판매량을 통해 어느 음료가 인기가 많은지 확인 할 수 있도록 하였다.

4.1. 개발 환경 및 개발 도구 설명
4.1.1. 아두이노(Arduino)

68 ICT_우수상_친환경 자판기 (2)
아두이노는 다양한 스위치나 센서로부터 입력 값을 받아들이고 전자 장치들로 출력을 제어하여 디지털 장치를 만들기 위한 도구로, 간단한 마이크로컨트롤러를 기반으로 한 오픈 소스 컴퓨팅 플랫폼과 소프트웨어 개발 환경이다.

68 ICT_우수상_친환경 자판기 (3)

본 제품에서는 Arduino Mega 2560, Uno를 사용하여 전체적인 자판기의 동작을 구현하였다.

4.1.2. 젯슨 나노(Jetson Nano)

68 ICT_우수상_친환경 자판기 (4)
젯슨 나노는 엔비디아(NVDA)가 개발한 싱글 보드로 애플리케이션에서 다수의 뉴럴 네트워크를 병렬로 실행하게 해주는 강력한 소형 컴퓨터이다.
본 제품에서는 이와 같은 개발 환경에서 우분투의 vi 문서 편집기를 통해 언어 PYTHON으로 코드를 구현하고 실행 파일을 생성하였다.

4.1.3. 라즈베리 파이(Raspberry Pi)

68 ICT_우수상_친환경 자판기 (5)
라즈베리 파이는 영국 잉글랜드의 라즈베리 파이 재단이 학교와 개발도상국에서 기초 컴퓨터 과학의 교육을 증진하기 위해 개발한 신용카드 크기의 싱글 보드 컴퓨터이다.

68 ICT_우수상_친환경 자판기 (6)

라즈베리 파이 OS를 실행할 때, 기본 터미널 애플리케이션은 LXTerminal이다. 해당 프롬프트에서 리눅스 명령어를 기반으로 vi 에디터 또는 문서 편집기를 이용해 실행 프로그램을 생성할 수 있다. 본 제품에서는 html과 언어 php를 사용하여 웹 파일을 생성하였다.

4.1.4. 안드로이드(Android)
안드로이드는 휴대전화의 운영체제, 미들웨어, 사용자 인터페이스, 응용프로그램 등을 묶은 소프트웨어 플랫폼이다. 리눅스(Linux)2.6 커널 위에서 동작하며 운영체제, 라이브러리, 멀티미디어 사용자 인터페이스, 애플리케이션 등을 제공한다.
안드로이드 프로그래밍 언어로는 JAVA와 Kotlin이 있다. 또한, 안드로이드 스튜디오는 안드로이드 앱을 빌드할 때 생산성을 높여주도록 다양한 기능을 제공한다. 본 제품에서는 프로그래밍 언어로 JAVA를 사용하였다.

68 ICT_우수상_친환경 자판기 (7)
Android Emulator의 각 인스턴스는 Android Virtual Device(AVD)를 사용하여 시뮬레이션된 기기의 Android 버전과 하드웨어 특성을 지정한다. 효과적으로 앱을 테스트하려면 앱이 실행될 각 기기를 모델링하는 AVD를 만들어야 한다. AVD를 만들고 관리하려면 AVD Manager를 사용하면 된다.
4.2. 작품 구성
4.2.1. 부품 리스트

부품명 디바 상품 번호
물높이(수위) 센서 스위치
PLEOMAX W-210 PC캠 화상캠 웹캠
라즈베리파이 5인치 HDMI LCD 터치스크린 모니터 1382229
아두이노 워터펌프 모터 3~5V
아두이노 워터펌프용  실리콘 튜브(1m)
아두이노 4채널 5V 릴레이 모듈 1327545
아두이노 WIFI ESP8266 1279338
아두이노 RFID-RC522 리더기 1279308
아두이노 수동 부저 모듈 10916342
아두이노 LED
아두이노 6×6 택트 스위치
아두이노 우노 Uno R3 호환보드 1245596
아두이노 메가 2560 호환보드 10918650
NVIDIA Jetson Nano Development Kit-B01 12513656
라즈베리파이4  (Raspberry Pi 4 Model B) 2GB 12234533

4.2.2. 회로도

68 ICT_우수상_친환경 자판기 (9) 68 ICT_우수상_친환경 자판기 (10)
4.2.3. 시스템 구성도

68 ICT_우수상_친환경 자판기 (11)

4.2.4. 작품 구상도

68 ICT_우수상_친환경 자판기 (12)

5. 작품 설명
5.1. 작품 제작 및 동작
5.1.1. 소켓 및 스레드 통신
자판기에 사용되는 장치들 간의 통신을 위해 소켓을 사용하여 서버를 구축하고 각각의 장치(아두이노, 젯슨 나노, 라즈베리 파이)들이 클라이언트가 되도록 하였다. 서버를 구축하는 데 있어서 C, PYTHON, JAVA 등의 언어 중 JAVA를 사용하였다.

68 ICT_우수상_친환경 자판기 (13)
스레드를 사용하지 않으면 여러 클라이언트의 접속이 불가하고, 다른 동작을 실행할 수 없다. 따라서 멀티 스레드를 사용하였고 초기에는 서버 하나에 여러 개의 클라이언트가 붙도록 구현하였다.

68 ICT_우수상_친환경 자판기 (14)
모든 클라이언트는 접속 시에 자신의 아이디를 서버에 전송한다. 이를 토대로 서버에서는 등록된 아이디 리스트와 접속 아이디를 비교, 검증한 후 클라이언트의 접속을 허용한다. 또한, 접속된 아이디를 기준으로 클라이언트 간 메시지를 보낼 수 있도록 하였다.

68 ICT_우수상_친환경 자판기 (15)

5.1.2. 아두이노(Arduino)
음료 결제
자판기의 결제 기능을 구현하기 위해 RFID 리더기 모듈을 사용하여 자판기 사용자의 카드가 인식되도록 하였다. 이후 전체 동작을 위해 RFID 리더기에 카드가 인식되면 ‘음료 선택’ 동작으로 넘어가도록 하였다. Arduino Mega 2560(이후 Mega로 칭함.)에서 Arduino Uno(이후 Uno로 칭함.)로 시리얼 통신으로 일정 값을 보내주고 Uno에서 값을 판단하여 재전송 하도록 한다.

68 ICT_우수상_친환경 자판기 (2)

음료 선택
사용자가 상황에 맞게 음료를 선택할 수 있도록 버튼과 LED를 구현하였다. LED는 현재 해당 음료를 선택할 수 있다는 신호이며, 버튼은 음료를 추출하도록 한다. 서버에서 결제가 되었다는 값을 전달 받으면 LED를 점등함과 동시에 버튼 기능을 활성화한다. 그리고 사용자가 버튼을 누를 시 해당 버튼에 맞는 음료의 정보를 Mega에서 시리얼 통신으로 Uno에게 메시지를 전송하여 ‘음료 추출’ 동작이 진행되도록 하였다.

68 ICT_우수상_친환경 자판기 (3)

음료 추출
음료를 추출하기 위해서 워터 펌프 모듈을 사용하였다. 워터 펌프 모듈의 불안정한 작동을 보안하기 위해 릴레이 모듈을 추가하였다. Mega에서 사용자가 선택한 음료 정보를 시리얼 통신으로 Uno에게 넘겨주고 음료 정보와 일치하는 워터 펌프를 작동시킨다. 음료 추출이 끝나면 부저를 통해 종료 음을 출력한다.
이후 모든 동작이 완료되면 언제든 사용자의 카드를 읽을 수 있는 초기 상태(결제 대기 상태)로 되돌린다.

음료 잔량 확인
플로트 스위치 센서를 사용하여 음료 잔량을 확인하도록 하였다. 부력으로 음료 표면에 떠 있다가 바닥에 닿으면 음료가 부족하다고 판단하여 LED를 소등한다. 그리고 사용자가 음료 선택 버튼을 눌러도 동작하지 않도록 하였다.
관리자가 애플리케이션을 통해 음료 잔량 정보를 요청하면 현재 모든 음료의 수위 센서 상태를 읽어 서버를 통해 애플리케이션에 전달한다.

68 ICT_우수상_친환경 자판기 (16)

매출 관리
애플리케이션에서 자판기 누적 판매량 기능을 구현하기 위해 음료 펌프가 작동된 횟수를 Mega의 비휘발성 메모리인 EEPROM에 저장하도록 하였다.
관리자가 애플리케이션을 통해 누적 판매량 정보를 요청하면 EEPROM에 저장된 값을 읽어 서버를 통해 애플리케이션에 전달한다.

5.1.3. 젯슨 나노(Jetson Nano)
컵 인식
먼저 Window 환경에서 opencv와 tensorflow를 사용하여 자판기 사용자가 놓은 컵을 인식하도록 하였다. 컵이 인식되면 웹캠을 종료하고 사진을 찍도록 하였다.

68 ICT_우수상_친환경 자판기 (17)
코드의 정상 동작 여부를 확인하고 젯슨 나노에서 개발 환경을 구축한 후 멀티 스레드를 사용하여 Mega에서 전송한 ‘음료 결제’ 메시지를 받으면 카메라를 동작시켜 컵을 인식하도록 하였다. 컵이 인식되면 Mega로 완료 메시지를 보내 계속해서 자판기의 동작을 수행한다. 컵이 인식되지 않으면 자판기의 동작을 중단하고 환불 페이지로 이동한다.

68 ICT_우수상_친환경 자판기 (18)

5.1.4. 라즈베리 파이(Raspberry Pi)
진행 상황 출력
라즈베리 파이에 부착된 터치스크린을 통하여 자판기 동작 진행상황 및 이용 순서에 맞게 웹 페이지를 띄워 사용자에게 알려준다.

68 ICT_우수상_친환경 자판기 (4)

에코-마일리지 적립
음료 추출이 종료되면 바로 에코-마일리지 적립 페이지로 이동한다. 자신의 전화번호를 입력하면 일정 금액의 마일리지가 적립된다. 입력을 완료하면 방금 적립된 마일리지와 누적된 마일리지 모두를 확인 할 수 있고 자동으로 페이지가 종료된다.

68 ICT_우수상_친환경 자판기 (19)

사용자가 입력한 정보는 MariaDB를 통해 관리한다.

68 ICT_우수상_친환경 자판기 (20)

5.1.5. 안드로이드(Android)
하단 탭
BottomNavigationView와 Fragment를 사용하여 하단 탭을 생성하였다. 하단 탭 클릭을 통해 ‘상태’ 페이지와 ‘판매량’ 페이지로 이동한다.

68 ICT_우수상_친환경 자판기 (21)

로그인(서버 접속)
메인 화면 상단에 로그인을 위한 옵션 버튼을 추가하였다. 해당 버튼을 선택하면 로그인 창을 띄운다.
해당 창에서 서버 접속(로그인)을 위한 IP, PORT, ID, PASSWORD를 입력한다. 미리 설정한 값이 기본 값으로 입력되어 있어 보다 편리하게 접속할 수 있다.

68 ICT_우수상_친환경 자판기 (22)

음료 잔량 확인
자판기 내부 음료의 잔량을 자판기 관리자가 수시로 확인 할 수 있도록 ‘상태’ 페이지 안에 CONDITION 버튼을 추가했다. 버튼을 누르면 서버를 통해 자판기 내부의 MCU에 특정 메시지를 전송하여 음료의 잔량을 요청한다.
자판기 내부의 음료가 충분하다면 아래 사진과 같이 음료 아이콘의 테두리를 강조 해준다.

68 ICT_우수상_친환경 자판기 (23)

음료 누적 판매량 확인
‘판매량’ 페이지의 초기 화면은 아래와 같이 ‘?개’로 나타낸다. RENEWAL 버튼을 누르면 서버를 통해 특정 메시지를 전송하여 음료 각각의 누적 판매량을 보여준다.
RESET 버튼을 누르게 되면 서버를 통해 판매량을 초기화 해달라는 메시지를 MCU에게 전송하여 각 음료의 누적 판매량을 모두 0개로 초기화한다.

68 ICT_우수상_친환경 자판기 (24)

6. 전체 흐름도

68 ICT_우수상_친환경 자판기 (25)

7. 최종구현

 

68 ICT_우수상_친환경 자판기 (5)

8. 시연

68 ICT_우수상_친환경 자판기 (7)

9. 결과 및 향후 목표
9.1. 결과
자판기에서 일회용품 사용을 줄이기 위해 초기에 기획한 기능으로 영상처리를 통한 텀블러 인식, 에코-마일리지 적립 등을 모두 구현하였다. 정상적으로 모든 기능이 동작하였지만 몇 가지의 아쉬운 점이 발생하였다.
먼저, 음료의 잔량을 측정하는 플로트 스위치 센서의 동작이 불안정하였다. 무게 센서나 무접점 수위 센서를 사용했으면 보다 안정된 동작을 구현할 수 있을 것이라 생각하였다. 다음으로 영상처리 부분에서 오픈 소스를 활용하다보니 필요 이상의 학습데이터로 인해 프로세서의 부담이 커져 텀블러 인식 속도에 영향을 미쳤다. 머신러닝을 접목하여 직접 필요한 이미지만 학습시킨다면 인식 속도를 개선할 수 있을 것이라 생각한다. 마지막으로 텀블러 크기에 따라 음료 추출량을 조절할 수 있는 방법에 대해 생각해보려 한다.

9.2. 향후 목표

68 ICT_우수상_친환경 자판기 (8)

본 제품은 소켓 통신을 하기 위해 와이파이를 사용한다. 하지만 와이파이를 대체할 수 있는 IoT 전용망이 전국적으로 구축되고 있다. 이는 유지비용 측면에서 최소 350원에서 최대 2,000원 정도로 저렴한 요금을 제공한다. 또 넓은 커버리지를 제공하여 본 제품이 상용화된다면 IoT 전용망을 통해 비용을 절감할 수 있다.
현재 애플리케이션은 자판기 관리자용으로 한정적인 기능을 제공한다. 이를 보완하기 위해 다양한 오픈 API를 사용할 수 있다. 예를 들면 구글 MAP API를 통해 자판기의 위치 정보를 제공하여 사용자는 보다 쉽게 자판기 이용이 가능하고, 관리자는 용이하게 유지 보수를 할 수 있다.
해당 자판기는 현재 일회용품을 사용하지 않는 것이 유일한 친환경적인 요소이다. 이를 확장하여 태양열 발전을 통한 전기 사용 등 친환경적인 요소를 추가적으로 접목시켜 환경 보호에 더욱 기여할 수 있다.

10. 참고자료
· 파이낸셜 뉴스, “1인 가구 플라스틱”, https://www.fnnews.com/news/202009181711464621
· 땅오니 로봇 코딩 블로그, “TCP/IP 소켓 통신”, https://ddangeun.tistory.com/31
· 멈춤보단 천천히라도, “파이썬 소켓”, https://webnautes.tistory.com/1381
· IT, 정보보안 자료실, “파이썬 멀티쓰레드”, https://nalara12200.tistory.com/153
· 명월 일지, “소켓통신”, https://nowonbun.tistory.com/315
· 토이메이커스, “아두이노 피에조 스피커“, https://blog.naver.com/yulian/221748202220
· 뤼즈나의 IT 블로그, “아두이노 워터 펌프”, https://in-reason.tistory.com/16
· 하이! 제니스, “아두이노 RFID”, https://m.blog.naver.com/chandong83/220920789808
· 에듀이노 코딩 스쿨, “ESP8266″, https://blog.naver.com/PostView.nhn?blogId=eduino&logNo=221152914869
· 폴나의 공방, “아두이노 시리얼 통신”, https://m.blog.naver.com/darknisia/221234187170
· 몽구스 프로그래밍, “아두이노 인터럽트”, https://m.blog.naver.com/PostView.nhn?blogId=yuyyulee&logNo=220310875023&proxyReferer=https:%2F%2Fwww.google.com%2F
· YouTube, “아두이노 RFID”, https://www.youtube.com/watch?v=SQ IGilMagm0
· 테크월드 뉴스, “젯슨 나노”, http://www.epnc.co.kr/news/articleView.html?idxno=95313
· MANUAL FACTORY, “우분투 설치”, https://www.manualfactory.net/13400
· DoProgramming, “젯슨나노”, https://doprogramming.tistory.com/category/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D/Jetson%20Nano
· 제너럴공국, “젯슨나노 텐서플로우”, https://generalthird.tistory.com/m/15
· 멈춤보단 천천히라도, “opencv”, https://webnautes.tistory.com/1186
· 개발하는 도치, “Seleninum”, https://heodolf.tistory.com/48
· 삶의 향기, “html 창 닫기”, https://lia47.tistory.com/995
· 안드로이드 스튜디오, Android Emulator, https://developer.android.com/studio/run/emulator?hl=ko
· Bugwhale World, 라즈베리파이 라즈비안 APM 설치하기 https://bugwhale.tistory.com/entry/raspberrypi-raspbian-apm-install
· 정재곤, 「Do it! 안드로이드 앱 프로그래밍」, 이지스퍼블리싱, 2020

11. 소스코드
MCU(아두이노) : MAGA

#include <SoftwareSerial.h>
#include “WiFiEsp.h”
#include <TimerOne.h>
#include <Wire.h>
#include <EEPROM.h>
#include <SPI.h> // RFID를 위한 SPI 라이브러리
#include <MFRC522.h>// RFID 라이브러리

#define DEBUG
#define DEBUG_WIFI
#define AP_SSID “embsystem2″
#define AP_PASS “embsystem20″
#define SERVER_NAME “192.168.1.20″
#define SERVER_PORT 5000
#define LOGID “ECO_ARD”
#define PASSWD “PASSWD”
#define CMD_SIZE 50
#define ARR_CNT 5
#define SS_PIN 53 //RFID SS(SDA:ChipSelect) PIN
#define RST_PIN 5 //RFID Reset PIN
#define led1 10
#define led2 9
#define led3 8
#define WLV_PIN1 A0
#define WLV_PIN2 A1
#define WLV_PIN3 A2

//SoftwareSerial Serial2(6, 7);
MFRC522 mfrc(SS_PIN, RST_PIN); //RFID 라이브러리
//MFRC522::MIFARE_Key key;

char eepromPw[5]; //(eeprom에 저장된)비밀번호

char sendBuf[CMD_SIZE];
bool timerIsrFlag = false;
unsigned int secCount;
int tag;

WiFiEspClient client;
int drinkslt = 0; // 음료 선택
boolean sltflag = false; // ok 수신 플래그

int CokeCount;
int FantaCount;
int SpriteCount;

int watervalue1;
int watervalue2;
int watervalue3;

int waterlv1 = 1;
int waterlv2 = 1;
int waterlv3 = 1;

int buzzflag = 0; // 부저
int Checkflag = 0; // RFID

char received;

void isr1()
{
drinkslt = 1;
// Serial2.write(“a”);
}
void isr2()
{
drinkslt = 2;
// secCount2 = 0;
// Serial2.write(“b”);
}
void isr3()
{
drinkslt = 3;
// secCount3 = 0;
// Serial2.write(“c”);
}

void setup() {
Serial.begin(9600); //DEBUG
Serial2.begin(9600);
wifi_Setup();
SPI.begin(); // SPI 시작
mfrc.PCD_Init(); // RF 모듈 시작

attachInterrupt(3, isr1, RISING); //20번
attachInterrupt(0, isr2, RISING); //2번
attachInterrupt(1, isr3, RISING); //3번

pinMode(led1, OUTPUT);
pinMode(led2, OUTPUT);
pinMode(led3, OUTPUT);

digitalWrite(led1, LOW);
digitalWrite(led2, LOW);
digitalWrite(led3, LOW);

Timer1.initialize(1000000);
Timer1.attachInterrupt(timerIsr); // timerIsr to run every 1 seconds

readEeprom();
}

void loop() {
if (client.available()) {
socketEvent();
}
if (timerIsrFlag)
{
timerIsrFlag = false;
if (!(secCount % 5))
{
if (!client.connected()) {
server_Connect();
}
}
}

//카드가 인식 안되었다면 더이상 진행하지 말고 빠져나감
if (mfrc.PICC_IsNewCardPresent())
{
if (mfrc.PICC_ReadCardSerial())
{
for (byte i = 0; i < mfrc.uid.size; i++) {
Serial.print(mfrc.uid.uidByte[i] < 0×10 ? ” 0″ : ” “);
Serial.print(mfrc.uid.uidByte[i], DEC);
}
Serial.println();
Serial2.write(‘x’);
delay(500);
mfrc.PICC_HaltA();
}
}

if (waterlv1 && drinkslt == 1)
{
if ( sltflag )
{
Serial2.write(‘a’);
delay(500);
digitalWrite(led2, LOW);
digitalWrite(led3, LOW);
sprintf(sendBuf , “[ECO_WEB]chooseok\n”);
client.write(sendBuf, strlen(sendBuf));
sltflag = false;
CokeCount++;
EEPROM.write(1, CokeCount);
drinkslt = 0;
}
}
else if (waterlv2 && drinkslt == 2)
{
if ( sltflag )
{
Serial2.write(‘b’);
delay(500);
digitalWrite(led1, LOW);
digitalWrite(led3, LOW);
sprintf(sendBuf , “[ECO_WEB]chooseok\n”);
client.write(sendBuf, strlen(sendBuf));
sltflag = false;
FantaCount++;
EEPROM.write(2, FantaCount);
drinkslt = 0;
}
}
else if (waterlv3 && drinkslt == 3)
{
if ( sltflag )
{
Serial2.write(‘c’);
delay(500);
digitalWrite(led1, LOW);
digitalWrite(led2, LOW);
sprintf(sendBuf , “[ECO_WEB]chooseok\n”);
client.write(sendBuf, strlen(sendBuf));
sltflag = false;
SpriteCount++;
EEPROM.write(3, SpriteCount);
drinkslt = 0;
}
}

if (Serial2.available())
{
received = Serial2.read();
Serial.println(received);
if (received == ‘x’)
{
sprintf(sendBuf , “[ECO_CAM]camon\n”);
client.write(sendBuf, strlen(sendBuf));
sprintf(sendBuf , “[ECO_WEB]camon\n”);
client.write(sendBuf, strlen(sendBuf));
drinkslt = 0;
}
else if (received == ‘o’)
{
digitalWrite(led3, LOW);
digitalWrite(led1, LOW);
digitalWrite(led2, LOW);
delay(100);
}
}
}

void socketEvent()
{
int i = 0;
char * pToken;
char * pArray[ARR_CNT] = {0};
char recvBuf[CMD_SIZE] = {0};
int len;

Serial.print(“socket ok “);
sendBuf[0] = ”;
len = client.readBytesUntil(‘\n’, recvBuf, CMD_SIZE);
client.flush();
#ifdef DEBUG
Serial.print(“recv : “);
Serial.println(recvBuf);
#endif
pToken = strtok(recvBuf, “[@]“);
while (pToken != NULL)
{
pArray[i] = pToken;
if (++i >= ARR_CNT)
break;
pToken = strtok(NULL, “[@]“);
}
if (!strncmp(pArray[1], ” New”, 4)) // New Connected
{
Serial.write(‘\n’);
return ;
}
else if (!strncmp(pArray[1], ” Alr”, 4)) //Already logged
{
Serial.write(‘\n’);
client.stop();
server_Connect();
return ;
}
else if (!strncmp(pArray[1], “camok”, 5)) //rfid ok
{
waterlv_check();
sltflag = true;
drinkslt = 0;
digitalWrite(led1, HIGH);
digitalWrite(led2, HIGH);
digitalWrite(led3, HIGH);
if (!waterlv1)
digitalWrite(led1, LOW);
if (!waterlv2)
digitalWrite(led2, LOW);
if (!waterlv3)
digitalWrite(led3, LOW);
if (!waterlv1 && !waterlv2 && !waterlv3)
{
Serial2.write(‘d’);
sprintf(sendBuf, “[ECO_WEB]empty\n”);
client.write(sendBuf, strlen(sendBuf));
}
return ;
}
else if (!strncmp(pArray[1], “error”, 5)) //Error
{
Serial.write(‘\n’);
Serial2.write(‘d’);
return ;
}
else if (!strncmp(pArray[1], “STATE”, 5)) //State
{
waterlv_check();
sprintf(sendBuf, “[ECO_APP]DRINK@%d@%d@%d\n”, waterlv1, waterlv2, waterlv3);
client.write(sendBuf, strlen(sendBuf));
return;
}
else if (!strncmp(pArray[1], “RENEWAL”, 7)) //Renewal
{
sprintf(sendBuf, “[ECO_APP]SELL@%d@%d@%d\n”, CokeCount, FantaCount, SpriteCount);
client.write(sendBuf, strlen(sendBuf));
return;
}
else if (!strncmp(pArray[1], “RESET”, 5)) //Count Reset
{
CokeCount = 0;
EEPROM.write(1, CokeCount);
FantaCount = 0;
EEPROM.write(2, FantaCount);
SpriteCount = 0;
EEPROM.write(3, SpriteCount);
sprintf(sendBuf, “[ECO_APP]SELL@%d@%d@%d\n”, CokeCount, FantaCount, SpriteCount);
client.write(sendBuf, strlen(sendBuf));
return;
}
else {
sprintf(sendBuf, “[%s]%s@%s\n”, pArray[0], pArray[1], pArray[2]);
}
client.write(sendBuf, strlen(sendBuf));
client.flush();

#ifdef DEBUG
Serial.print(“, send : “);
Serial.print(sendBuf);
#endif
}

void timerIsr()
{
timerIsrFlag = true;
secCount++;
}
void wifi_Setup() {
Serial3.begin(9600);
wifi_Init();
server_Connect();
}
void wifi_Init()
{
do {
WiFi.init(&Serial3);
if (WiFi.status() == WL_NO_SHIELD) {
#ifdef DEBUG_WIFI
Serial.println(“WiFi shield가 존재하지 않습니다.”);
#endif
}
else
break;
} while (1);

#ifdef DEBUG_WIFI
Serial.print(“WiFi 연결을 시도합니다.”);
//Serial.println(AP_SSID);
#endif
while (WiFi.begin(AP_SSID, AP_PASS) != WL_CONNECTED) {

#ifdef DEBUG_WIFI
Serial.print(“WiFi 연결을 시도합니다.”);
//Serial.println(AP_SSID);
#endif
}

#ifdef DEBUG_WIFI
Serial.println(“WiFi 연결에 성공하였습니다.”);
printWifiStatus();
#endif
}
int server_Connect()
{
#ifdef DEBUG_WIFI
Serial.println(“Server 연결을 시도합니다.”);

#endif

if (client.connect(SERVER_NAME, SERVER_PORT)) {
#ifdef DEBUG_WIFI
Serial.println(“Server 연결에 성공하였습니다.”);
#endif
client.print(“["LOGID":"PASSWD"]“);
}
else
{
#ifdef DEBUG_WIFI
Serial.println(“Server 연결에 실패하였습니다.”);
#endif
}
}
void printWifiStatus()
{
// print the SSID of the network you’re attached to
//Serial.print(“SSID: “);
//Serial.println(WiFi.SSID());
// print your WiFi shield’s IP address
IPAddress ip = WiFi.localIP();
//Serial.print(“IP Address: “);

Serial.println(ip);
// print the received signal strength
long rssi = WiFi.RSSI();
//Serial.print(“Signal strength (RSSI):”);
//Serial.print(rssi);
//Serial.println(” dBm”);
}

//EEPROM에 저장된 판매량을 읽어옴
void readEeprom() {
CokeCount = EEPROM.read(1);
// Serial.print(CokeCount);
FantaCount = EEPROM.read(2);
//Serial.print(FantaCount);
SpriteCount = EEPROM.read(3);
//Serial.print(SpriteCount);
}
void waterlv_check()
{
watervalue1 = analogRead(WLV_PIN1);
watervalue2 = analogRead(WLV_PIN2);
watervalue3 = analogRead(WLV_PIN3);
Serial.println(watervalue1);
if (watervalue1 >= 1000)
{
waterlv1 = 0;
}
else
{
waterlv1 = 1;
}
Serial.println(watervalue2);
if (watervalue2 >= 1000)
{
waterlv2 = 0;
}
else
{
waterlv2 = 1;
}
Serial.println(watervalue3);
if (watervalue3 >= 1000)
{
waterlv3 = 0;
}
else
{
waterlv3 = 1;
}
}

MCU(아두이노) : UNO

#include <SoftwareSerial.h>
#include <SPI.h>
#include <TimerOne.h>
#define buzz 5
SoftwareSerial rfid(6, 7);
bool timerIsrFlag = false;
unsigned int secCount1 = 11;
unsigned int secCount2 = 11;
unsigned int secCount3 = 11;
//static boolean selectmod1 = false; //인터럽트

char receved; // serial 통신 수신 값

int Relaypin1 = 8;
int Relaypin2 = 9;
int Relaypin3 = 10;

int buzzflag = 0; // 부저
int Checkflag = 0; // RFID

void setup()
{
// put your setup code here, to run once:
Serial.begin(9600); //DEBUG
rfid.begin(9600);
SPI.begin(); // SPI 시작

pinMode(Relaypin1, OUTPUT); // 릴레이를 출력으로 설정
pinMode(Relaypin2, OUTPUT);
pinMode(Relaypin3, OUTPUT);
Timer1.initialize(1000000);
Timer1.attachInterrupt(timerIsr);

digitalWrite(Relaypin1, HIGH);
digitalWrite(Relaypin2, HIGH);
digitalWrite(Relaypin3, HIGH);

Serial.println(“연진아 준비해라”);
}

void loop() {
if (rfid.available())
{
if (Checkflag == 0)
{
receved = rfid.read();
if (receved == ‘x’)
{
Serial.println(receved);
Serial.println(“x 재송신 후 다음 동작 대기”);
rfid.write(‘x’);
Checkflag = 1;
}
}
if (Checkflag == 1)
{
receved = rfid.read();
if (receved == ‘a’)
{
secCount1 = 0;
Serial.println(receved);
Serial.println(“물펌프a 동작”);
digitalWrite(Relaypin1, LOW); // 1채널 릴레이 ON
}
else if (receved == ‘b’)
{
secCount2 = 0;
Serial.println(receved);
Serial.println(“물펌프b 동작”);
digitalWrite(Relaypin2, LOW); // 2채널 릴레이 ON
}
else if (receved == ‘c’)
{
secCount3 = 0;
Serial.println(receved);
Serial.println(“물펌프c 동작”);
digitalWrite(Relaypin3, LOW); // 3채널 릴레이 ON
}
else if (receved == ‘d’)
{
Serial.println(receved);
Checkflag = 0;
}
}
}
if (buzzflag == 1)
{
tone(buzz, 262, 500); // 도
delay(400);
tone(buzz, 330, 500); // 미
delay(400);
tone(buzz, 392, 500); // 솔
delay(400);
tone(buzz, 523, 500); // 높은 도
delay(400);
rfid.write(‘o’);
delay(500);
Checkflag = 0;
Serial.println(Checkflag);
buzzflag = 0;
}
}

void timerIsr()
{
timerIsrFlag = true;
secCount1++;
secCount2++;
secCount3++;
if (secCount1 == 10 )
{
digitalWrite(Relaypin1, HIGH); // 1채널 릴레이
buzzflag = 1;
Serial.println(buzzflag);
}
else if (secCount2 == 10 )
{
digitalWrite(Relaypin2, HIGH); // 2채널 릴레이
buzzflag = 1;
}
else if (secCount3 == 10 )
{
digitalWrite(Relaypin3, HIGH); // 3채널 릴레이
buzzflag = 1;
}
}

 

[68호]스마트 디바이스 x 소형가전 쇼

68hot_스마트디바이스쇼 (8)

68hot_스마트디바이스쇼 (1)

스마트 디바이스 x 소형가전 쇼

글 | 이규연 기자 press@ntrex.co.kr

올해 11회 째를 맞은 스마트 디바이스 소형가전 쇼가 지난 7월 22일부터 24일까지 총 3일간 코엑스에서 개최됐다.
이번 전시회는 140개의 업체와 186개의 부스로 운영되어 국내 최대 전시회라고 해도 과언이 아닐 만큼 규모가 상당했다.
스마트 디바이스 소형가전 쇼는 ‘나 혼자 쓴다’라는 주제를 가지고 급속히 늘어나는 단독 가구와 MZ 세대가 일상생활에서 필요로 하는 스마트 가전제품을 선보였으며, 주요 전시 품목으로는 생활가전, 무선가전, 미용가전, 살균가전, 미니가전 등이 있다.

68hot_스마트디바이스쇼 (1)

㈜에코플로우는 리튬이온 배터리 기반의 휴대용 전력 저장 장치를 개발하는 기업이다. 이번 전시회에서는 캠핑족들을 위한 에코플로우 델타 1300 제품을 선보였다.
델타 1300은 캠핑 파워 뱅크의 장점을 모두 가지고 있다. 기본적으로 캠핑용 파워 뱅크는 여러 가지의 기기를 사용할 수 있어야 하며, 충전시간이 빨라야한다. 델타 1300은 한 번에 11개의 기기를 동시에 구동할 수 있고 표준 220V 콘센트를 사용하여 2시간 이내에 완전히 충전할 수 있으며, 1시간 내에는 완전 방전 상태에서 80%까지 충전이 된다. 태양광 패널로도 충전이 가능한대 햇빛이 강렬한 조건에서 불과 4시간 내에 완전히 충전할 수 있고 시중에 있는 모든 태양광 패널과도 연결이 가능하다.
또한, 에코플로우는 배터리 관리 시스템을 개발하였는데 이 시스템은 델타 내부에 있는 140개의 리튬이온 배터리 상태를 AI 알고리즘을 이용해 관리할 수 있으며, 최대 3300W 전력 서지 및 과부하를 방지하여 마음 편히 안전하게 사용할 수 있다. 뿐만 아니라 델타는 낮은 무게중심을 갖도록 설계되어 전복 위험을 줄여주고 내구성을 위해 항공 우주급의 알루미늄과 고강도 강철로 제작되었다.
델타는 1260Wh의 에너지 저장 용량을 가지며 일반 가정용 냉장고와 조명 및 기타 가전제품을 스위치만 한번 돌리면 곧바로 작동 시킬 수 있다. 이러한 장점으로 델타 1300은 캠핑을 사랑하는 사람들에게 많은 관심을 받을 것으로 예상된다.

68hot_스마트디바이스쇼 (2)

㈜시큐라인은 디지털 신호처리 기술을 활용하여 다양한 분야의 제품을 개발하는 기업으로, 이번 전시회에서 충전지와 일회용 건전지를 재생하여 보관하는 스마트 배터리 충전기인 에너로이드를 선보였다
에너로이드 위쪽에 있는 입구에 충전할 배터리를 투입시켜 커버를 닫고 전원 버튼을 누르면 충전이 시작되고, 충전이 필요한 배터리는 극성에 상관없이 올려놓아도 된다. 그 이후에 더 이상 충전이 되지 않는 배터리는 불량으로 판별하여 불량 배터리 보관함으로 이동시키고 충전이 완료된 배터리는 충전 완료 배터리 보관함으로 이동시켜 필요한 때에 보관함에서 꺼내 사용할 수 있다.
에너로이드는 상황 표시등이 있어 현재의 배터리 상태를 쉽게 알 수 있는데 표시등 링의 윗부분이 점등되면 투입 부를 확인하여야 하고 아랫부분이 점등되면 보관함을 확인해야 한다.
커버에는 무선 충전 패드가 내장되어 있어 배터리 충전을 기다리는 동안 모바일 기기 등의 충전을 할 수 있는데 건전지를 충전할 때나 충전하고 있지 않을 때나 모두 사용이 가능하다.
제품의 모델은 크게 AAA 건전지용, AA 건전지용, 18650 건전지용으로 구성되어 있고 해당 모델 모두 전원 단자는 USB C 타입이며, 무게도 650g밖에 되지 않아 휴대하고 다니기에 부담이 없다.

68hot_스마트디바이스쇼 (3)

㈜에스앤에스는 남녀노소 불문하고 모든 사람들에게 필요한 마사지 건 하이퍼 볼트 블루투스와 하이퍼 볼트 플러스 블루투스를 선보였다.
먼저 하이퍼 볼트 블루투스와 하이퍼 볼트 플러스 블루투스의 구성품으로는 5가지 헤드와 헤드 가방, 케이블, 충전기, 하이퍼 볼트 블루투스 또는 하이퍼 볼트 플러스 블루투스가 포함되어 있다.
제품의 특징으로는 브러시리스 60W와 90W의 모터를 사용하고 3가지 스피드 세팅, 교체가 가능한 5가지의 헤드, 2시간 이상 사용이 가능한 리튬이온 배터리가 내장되어 있단 점과 TSA 허가를 받아 믿고 사용할 수 있는 제품이라는 점이다.
무게는 1.1kg, 1.3kg의 초경량으로 초보자들도 쉽게 사용할 수 있다.
하이퍼 볼트 마사지 건을 사용하면 빠른 웜업이 가능하고 관절의 가동 범위와 유연성이 증가할 뿐만 아니라 근육 통증이 감소하고 근 수축과 운동 능력이 증가되는 효과를 얻을 수 있다. 회사 업무와 집안일로 몸이 뻐근한 사람과 잦은 운동으로 인한 통증이 있는 사람이 사용했을 때 효과가 좋은 제품이라 한다.

68hot_스마트디바이스쇼 (4)

바이러스 살균기를 제조하는 기업인 ㈜브이레이저는 이번 전시회에서 안전 원료 피톤치드를 사용한 살균기 K200과 Kgun을 전시했다.
브이레이저가 FDA에서 인증받은 원료인 피톤치드는 편백 원목, 편백 잎, 편백 열매에서만 추출한 100% 자연 유래 살균제로 공기 소독의 최적화된 원료이다. 피톤치드는 다양한 기능이 있는데 활성산소 제거에 도움이 되고 면역세포 활성화를 증가 시켜준다. 면역세포가 증가하면 호흡기 계통의 각종 병원균을 사전에 방지하고 뇌의 알파파 활성을 유도하여 기억력 및 집중력을 증가하게 해주어 좋은 연쇄 작용을 일으킨다. 또한 강력한 항균력을 가지고 있어 대장균, 식중독균, 폐렴구균, 바이러스, 곰팡이 등 살균효과와 냄새 분자를 화학적으로 중화시켜 탈취 효과가 뛰어나다. 이러한 피톤치드 원료를 기반으로 만든 살균기 K200은 24시간 동안 동작할 수 있고 설치가 간편하여 가정에서도 충분히 사용 가능하다.
Kgun은 피톤치드 원료를 총에 충전해 뿌리는 방식으로 언제 어디서나 바이러스가 생성된 곳에 휴대하여 사용할 수 있는 장점이 있다.
FDA COVID-19살균제로 등록이 되고 인플루엔자 폐렴구균 등 사멸 인증을 끝낸 브이레이저 살균기 제품들은 살균, 항균, 탈취까지 한 번에 가능하여 코로나 시국에 예민한 소비자들의 관심을 끌 것으로 예상된다.

68hot_스마트디바이스쇼 (5)

㈜예율은 미용기기를 전문적으로 개발하는 기업이다. 이번 전시회에서는 완벽한 헤어 스타일링을 가능하게 하는 하이브리드 헤어스타일러 웨이블을 선보였다.
하이브리드 헤어스타일러 웨이블은 스트레이트 발열판과 컬링 발열판의 경계 부분에 열전도 차단 부가 형성되어 각각의 기능을 별도로 사용할 수 있을뿐만 아니라 동시에도 사용이 가능하여 다양한 헤어 스타일링이 가능하다.
또한 자동 회전 기능이 있어 시간 단축 및 편리성을 제공하며 자동 회전 상태에서도 열 공급 효율성이 우수하여 신속한 컬링을 할 수 있고, 자신이 원하는 시간만큼 타이머를 맞추어 사용할 수 있기 때문에 스타일링의 실패를 최소화할 수 있다. 웨이블의 가장 큰 장점이라면 당연 비싼 금액을 들이지 않고 전문가가 케어한 셀프 미용 효과를 볼 수 있는 점과 온도조절이 가능한 기계 컨트롤러가 있어 모발의 압력을 유지하여 모발이 손상되는 것을 사전에 막을 수 있다는 점이다.
최소한의 힘과 시간을 투자하여 전문가가 연출한 느낌이 드는 헤어스타일러 웨이블은 현장에서 많은 여성들의 관심을 끌었다.

68hot_스마트디바이스쇼 (2)

㈜대호아이앤티는 정보통신기술 서비스 전문 기업으로 스마트 위치추적기인 파인미를 선보였다.
파인미는 loT 전용망을 통해 보다 정확한 위치 정보 서비스를 제공하는 위치 추적 제품이다. 제품 사양으로는 입력 전원은 리튬 폴리머 1300mAh 37V 4.8wh이며, 주파수대역은 LTE Cat.M1 B5이다. 고객의 요청에 따라 커스터마이징이 가능하고 1회 완전 충전으로 약 2일간 사용할 수 있는 장점이 있으며 사용 대상은 어린이, 노인, 반려동물, 가방, 차량 등 범위가 넓다.
파인미는 3가지의 위치 확인 방법이 있는데 첫 번째는 보호자의 휴대폰에서 보호 대상이 차고 있는 파인미 연결을 통해 높은 정확도로 현재 위치를 확인하는 방법이다. 자녀들의 위치를 걱정하는 부모들은 이 방법을 통해 실시간으로 자녀들의 위치를 확인할 수 있고 자신의 소중한 재산이나 반려동물이 어디 있는지 확인할 수 있다.
두 번째는 지정된 구역에 진입하거나 이탈 시 보호자에게 알림이 가는 시스템으로 수시로 위치를 확인하지 않아도 보호자가 지정한 위치에 보호대상이 이탈하거나 진입하면 자동적으로 알림 문자로 위치를 전송해주는 방법이다.
마지막으로는 파인미를 착용하고 있는 보호 대상자가 다치거나 신변의 위협을 느꼈을 때 파인미 버튼을 4초 이상 길게 누르면 기기와 연결된 보호자 휴대폰에게 SOS 메시지와 현재 위치가 전송되어 위기 상황을 빠르게 대처할 수 있는 방법이다.
아이들의 안전이 걱정되는 부모, 자신의 재산의 위치를 실시간으로 확인하고 싶다면 파인미 제품을 이용하면 좋을 것 같았다.

68hot_스마트디바이스쇼 (6)

㈜파티클은 소독 및 방역기기를 개발하는 기업이며, 기술평가등급에서 인증을 받을 만큼 기술력이 뛰어나다. 이번 전시회에서는 초미립자 무선 방역기인 제스트를 선보였다.
제스트는 언제 어디에서 누구나 편리하게 사용할 수 있는 1.9kg의 방역기로 무게가 비교적 가볍다. 초미립자 분사기가 있어 강력한 분사 능력으로 미세한 부분까지 소독 및 방역이 가능하며 분사거리가 7m 거리에 가까울 만큼 광범위하여 편리하게 사용할 수 있는 점이 장점이다.
제스트는 전력 사용률을 줄이고 탈착이 가능한 밀워키 배터리를 사용하고 원터치 스위치가 있어서 버튼 하나로 손쉽게 사용할 수 있다.
사용방법은 먼저 제품을 구매했을 때 배터리가 충전이 되지 않은 상태라서 배터리 충전 후 약재통에 사용할 약품을 투입한 다음 뚜껑을 오른쪽으로 돌리고 배터리를 결합하여 손잡이 왼쪽에 위치한 버튼을 눌러 기계를 작동시키면 팬 작동 후 2초 후에 분사가 시작된다. 사용 종료를 원한다면 손잡이 왼쪽에 위치한 버튼을 누르면 작동이 종료된다.
제스트 제품은 국내 안전성 평가 인증 마크 KC 인증을 보유하고 있고 통합규격인증 CE 유럽 인증과 미국연방통신위원회 인증 마크인 FCC 인증도 보유하고 있어 소비자들이 안심하고 사용할 수 있는 방역 기기 제품이라 한다.

68hot_스마트디바이스쇼 (7)

㈜초위스컴퍼니는 광학, 영상처리 기술 및 소프트웨어 개발을 전문적으로 하는 진단 및 평가 기술 기업이다.
이번 전시회에서는 스마트폰에 내장된 카메라를 사용하여 피부와 두피 상태를 진단할 수 있는 DermoPico 시스템을 선보였다. 스마트폰에 쉽게 탈부착할 수 있는 이 제품은 블루투스 통신의 종류인 BLE 신호를 사용하고 스마트폰과 연결해서 원활한 분석 프로세스를 제공한다. 이 제품은 피부와 두피, 모발 상태를 진단하는데 먼저 피부는 주름, 모공, 피지, 수분, 유분의 상태를 점검할 수 있다. 카메라로 촬영만 하면 실시간으로 3D 이미지를 재현할 수 있어 간편하며 정확하다. 두피와 모발에서는 모발의 밀도, 탈모 유형, 두피 민감도, 각질, 두피 유분을 진단할 수 있어서 탈모 예방에 도움을 줄 수 있는 제품이다.
또한 간단한 사용방법으로 진단 결과의 그래프를 통해 관리할 수 있는 방법과 관리 제품을 제안해 주며, 진단 결과는 SNS와 이메일을 통해 공유도 가능하다.

68hot_스마트디바이스쇼 (8)

가정용 냉장고를 전문으로 개발하는 기업인 ㈜고모텍은 이번 전시회에서 캔 모양을 영상케 하는 소형 냉장고 홈바와 글로벌 캐릭터 브랜드인 라인프렌즈와 협업을 통해 개발한 꼬모 냉장고 브라운, 샐리를 선보였다.

68hot_스마트디바이스쇼 (9)

홈바는 ‘내방의 작은 편의점’을 컨셉으로 한 모서리 없는 둥근 캔 모양으로 캔 따개 부분을 누르면 헤드룸이 열리는 귀여운 외관이다. 총 4가지 색상(크림, 핑크, 민트, 퍼플)이 있어서 취향에 따라 선택할 수 있다. 홈바의 헤드룸과 바디룸은 분리되어 있으며 탈착식 선반과 도어 바스켓을 통해 저장 용도에 맞는 공간 구성이 가능하고 상단의 탑 트레이를 분리하면 쟁반으로도 사용 가능해서 자취하는 사람들이 사용하기 편리하다. 또한, 블루투스 5.0버전 2개의 스피커를 통해 풍부한 사운드를 제공하고 정전기 터치 방식의 디스플레이로 온도, 사운드 볼륨을 간편하게 조작할 수 있다.
홈바는 기존 압축기 방식의 냉장고가 아닌 방도체 냉각방식과 폴리우레탄 발포 단열공법으로 저소음, 저진동을 구현에 소음을 최소한으로 줄였다. 코로나로 인해 외출이 잦아진 이때 감성적인 디자인으로 미니 바를 꾸미고 싶은 사람들에게 꼬모냉 장고 홈바를 추천한다.

68hot_스마트디바이스쇼 (10)

㈜씨피디그룹은 캠핑과 여행의 필요한 제품을 loT 기술과 결합하여 새로운 라이프스타일 제품을 제공하는 기업이다. 씨피디그룹 주력 제품인 캠지플러스와 캠지미니를 선보였다. 캠지플러스와 캠지미니는 캠핑 안전에 도움을 주는 제품이다. 먼저 캠지플러스는 아웃도어 활동(캠핑, 차박, 낚시)과 창고 및 농장에서 활용될 수 있으며 앱과 연동시켜 실시간으로 5m 이내의 침입 감지를 할 수 있다.
뿐만 아니라 일산화탄소 모니터링과 온, 습도 모니터링 및 경보 기능, 모기 퇴치 기능, LED 랜턴, 비상벨 기능 등 아웃도어 활동에 필요한 조건을 다 갖춘 제품이다. 무게도 220g의 초소형이라 휴대용으로도 편리하다.
캠지미니는 블루투스가 탑재되어 있으며 일산화탄소 모니터링 및 경보 기능과 온, 습도 모니터링 및 경보 기능, 비상벨 기능이 있는 제품이다. 캠지플러스와 마찬가지로 아웃도어 활동에 사용할 수 있고 무게가 50g이라서 휴대하기 더 편리하다.

68hot_스마트디바이스쇼 (11)

㈜퓨시스는 샤워에 필요한 바디필링기를 선보였다. 바디필링기는 혼자서 힘들었던 바디 세신을 도와주는 가정용 세신기이다. 타월 브랜드인 송월 타월을 사용하여 개운한 바디 필링을 도와주고, 스크럽과 거품 목욕을 하고 싶다면 오션 스크럽 타월로 교체하면 된다.
샤워를 마치고 몸이 뻐근하다면 마사지 갭을 끼워서 오일 마사지를 할 수 있다.
바디필링기는 욕실에 어울리는 디자인과 컬러를 대입하여 고급스러움을 연출하고 인체의 굴곡면과 주는 힘에 따라 휠이 유연하게 움직여 넓은 면적의 세신이 가능하다. 또한 전기감전의 걱정이 없도록 무선 타입이며 편리한 탈부착이 가능해서 5분이면 누구나 설치가 가능하다.
높낮이 조절 및 강력 모터가 탑재되어 세신과 마사지를 동시에 즐길 수 있으며 과부하 시 자동 멈춤 기능까지 탑재되어서 안전 걱정을 줄일 수 있는 제품이다. 바디필링기는 때를 자주 미는 사람, 혼자서 세신이 힘든 노약자, 목욕탕을 꺼리는 일반인, 숙박업소, 요양병원을 운영하는 사람들에게 안성맞춤인 제품이다.

코로나19가 지속되고 있는 상황에도 철저한 방역을 통해 개최되었던 ‘스마트 디바이스 x 소형가전쇼 2021’이 성황리에 마무리되었다.
11회째 맞은 이번 전시회에서는 KITAS 전시회의 주요 품목인 모바일 액세서리와 컴퓨터 주변기기, 스마트 디바이스, 소형가전뿐만 아니라 최근 코로나19 영향을 받지 않는 분야인 전자상거래 관련 사업자들을 위한 비즈니스 기회 제공 및 정보 교류를 할 수 있는 ‘서울 이커머스 전시회’ 가 동시 개최되어 볼거리 뿐만 아니라 실속까지 챙길 수 있는 좋은 기회의 장이 되었다.
우리나라의 가전제품이 점점 더 개발이 되어 세계에서 인정받는 수준이 되길 바라면서 이번 관람기를 마친다. DM

 

 

[68호]스마트 쇼핑 카트

68 ict_ 스마트쇼핑카트 (26)

68 ict_ 스마트쇼핑카트 (42)

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

스마트 쇼핑 카트

글 | 영남대학교 양성은, 박유나, 이유진, 김형덕

1. 심사평
칩센 작품은 상품의 구매와 위치 측량의 두 가지 큰 목표로 보입니다. 우선 상품 취득과 결제 부분에 있어서는 목표량을 어느 정도 달성한 것으로 보입니다. 또 다른 목표였던 위치측량 관련하여 상품의 위치를 통한 navigating 기능을 구현하기 위하여 사용자의 위치를 측정해야 하는 구조로 이해됩니다. 작품을 개발하며 목표로 한 내역에 대하여는 어느정도 위치 측량을 성공하였다고 볼수도 있지만, 실제로 상용화에 있어서는 위치 측량에 대한 많은 변수들이 존재하여 실내의 Map이 함께 조합되어 정확한 실내 위치를 파악하게 하는 등의 다양한 방식의 알고리즘과 방안이 적용되고 있고, 이러한 부분에 대한 추가적인 연구 검토를 한다면 더욱 개선된 제품을 제작할 수 있을 것으로 보입니다.

펌테크 아이디어와 실용성이 돋보이며 짜임새 있게 잘 구성한 수준급 작품이라고 생각합니다. 실내 위치 측정을 위한 비컨 연동 과정에서의 시행착오가 있었음에도 불구하고 전체적으로 시스템 구성을 위한 각각의 난이도 있는 소프트웨어 구성을 효율적으로 접목하여 기획의도에 맞게 시스템을 안정적이고 완성도 높게 구현된 스마트카트 제품이라고 생각이 듭니다. 추후 작품 완성도를 높인다면 상업적으로도 충분히 활용될 수 있는 상품성을 가진 훌륭한 작품이 되리라 생각됩니다.

위드로봇 학습 용도로는 좋은 주제이지만, 실용적인 측면에서는 위치 측량, 결제 등 해결해야 할 문제가 많은 작품입니다.

뉴티씨 배려심이 느껴지는 작품입니다. 코로나19로 여러 가지로 어려운 상황에서, 같은 작품을 해도 어떤 마음으로 하는 가에 따라서, 의도가 다르므로 전혀 다른 결과가 나올 수 있습니다. 그런데, 몸을 움직이는 것이 불편하신 어르신들을 생각하여, 자동으로 계산이 되는 스마트쇼핑카트를 생각할 수 있었다는 것이 매우 중요한 가치라고 생각됩니다. 스마트 쇼핑카트라는 것은 오래전부터 인기 있는 작품의 주제로 만들어져왔지만, 이 작품은 비콘을 이용한 삼각측량법으로 위치인식까지 고려하여 제작하여 제품을 빠른 경로로 찾으러 갈 수 있도록 하는 부분도 함께 작성되었네요. 매우 좋은 작품을 제작하였습니다. 다양한 분야에서 활용될 수 있을 것 같고, 학생들이 좀 더 힘내서, 좋은 분야로의 응용을 기대하겠습니다.

2. 작품 개요
스마트 쇼핑 카트는 거동이 불편한 사람들에게 편리한 쇼핑 환경을 제공해 주기 위한 보조 장치이다. 이 프로젝트를 생각하게 된 계기는 마트에서 쇼핑을 하다가 물건이 많아 카트를 끌고 계셨지만, 몸이 불편하셔서 카트를 끌기조차 매우 어려워 보이는 분을 보고 ‘거동이 불편하신 분들도 대형마트에서 편하게 쇼핑을 할 수 있게 할 방법이 없을까?’ 라고 생각하다가 개발하게 됐다. 프로젝트의 구현으로는 대형 마트 내에서 실내 측위 시스템을 사용하여 구매하고자 하는 물건의 위치까지 최단 경로를 알려주어 효율적인 쇼핑을 가능하게 한다. 카트에 부착된 바코드 스캐너에 구매 물품의 바코드를 찍으면 데이터베이스 내에 존재하는 물품의 정보를 자동으로 관리할 수 있게 하고, 구매 상품의 행사 정보를 음성으로 알려준다. 결제 버튼을 누를 시, App에서 간단하게 결제할 수 있도록 한다. 현재의 전동 카트는 원하는 상품 코너로 이동하는 것 정도로 발전되어 있는데, 거기에 우리 팀의 프로젝트는 최단거리 알고리즘(Traveling Salesman’s Problem)을 적용하고, 결제 서비스까지 연동하여 사물 인터넷 개념을 적용해 현대에 어울리는 더욱 스마트한 카트로 발전시킨다.

2.1. 개발 목표

68 ict_ 스마트쇼핑카트 (1)
거동이 불편한 사람들의 오프라인 쇼핑을 도울 수 있는 카트를 개발하여, 기존 쇼핑 방식보다 쉽고(easy), 편리하고(convenient), 안전한(safe) 쇼핑을 제공하여 더 나은 소비 활동을 할 수 있도록 돕는다. 더 나아가 스마트 카트가 보급화 된다면 거동이 불편한 사람뿐만 아니라, 바쁜 현대인들에게도 최단 경로로 원하는 물품이 어디에 있는지 바로바로 알 수 있고, 결제까지 한 번에 해결하는 효율적인 쇼핑 수단이 될 것이다.

2.2. 개발 작품의 필요성
일반 사람들도 쇼핑을 할 때 원하는 물건이 어디 있는지 자주 구매하는 물품이 아니라면 알기 어려운데, 거동이 불편한 사람들이나 장시간 쇼핑이 어려운 노약자들은 그것으로 인해 불편한 몸을 이끌고 물건을 가지러 갔다가 헤매고 다시 계산하러 계산대까지 이동한다면, 일반 사람들보다 몇 배는 힘들고 제한될 것이다. 하지만 우리 프로젝트를 통해 이동 동선을 최소화하고, 시선이 닿지 않는 곳에 기재된 할인 정보를 음성으로 접할 수 있게 됨과 동시에 결제까지 스마트 카트 하나로 끝낼 수 있어서 보통 사람들처럼 불편함 없는 쇼핑이 가능하여 더 나은 쇼핑이 가능하게 한다.

2.3. 기대효과
· 거동이 불편한 사람들이 쇼핑에서 불편함을 느낄 수 있는 부분들을 지원하여 더욱 나은 쇼핑 생활을 가능케 한다.
· 스마트 쇼핑 카트가 여기에서 더 나아가 발전하고 보급된다면 일반 사람들도 기존의 쇼핑 방식 보다 더욱 효율적인 쇼핑 할 수 있다.
· 탈부착이 가능하도록 구성하여 유연성을 가지고 있으며, 효율적인 활용이 가능하다.
· 실내 위치 측위의 발전 가능성을 기대할 수 있다.

3. 작품 설명
3.1. 주요 동작 및 특징
3.1.1. 주요 기능 소개

68 ict_ 스마트쇼핑카트 (2)
전체적 구성은 라즈베리파이에 모니터와 바코드스캐너와 스피커가 연결되어있다. 이는 쇼핑 카트에 탈부착 할 수 있으며 또한 결제 어플과 연동된다.

68 ict_ 스마트쇼핑카트 (3)

기능 1(실내 측위): RF모듈의 RSSI 값으로 삼변측량법을 이용한다. Raspberry Pi가 3개의 RF모듈을 통해 얻은 RSSI 값으로 거리를 구하여 마트 내에서 구매자의 위치를 파악한다. 파악한 위치를 통해 구매자와 구매하고자 하는 상품코너와의 거리를 파악할 수 있다.
기능 2(최단 경로 안내): 삽변측량법과 같이 Traveling Salesman’s Problem을 이용하여 사용자가 물품 구매 시 최단 경로로 이동할 수 있도록 한다. 구매하고자 하는 상품들을 선택할 시, 실내 측위 기술을 통해 구매자의 위치를 기준으로 가장 짧고 빠르게 상품까지 도달할 수 있는 경로를 제공한다.
기능 3(장바구니 & 상품 Database 설명): 바코드 스캔을 통해 데이터베이스를 조회하여 상품의 행사 정보를 음성으로 알려주고 장바구니에 담긴 구매 물품들을 자동으로 관리할 수 있게 한다. 또 디스플레이로 장바구니 목록을 보여줌으로써 삭제 또는 추가 등의 자유로운 수정이 가능하다.
기능 4(결제기능): 카트에 부착된 모니터에서 결제버튼을 누르게 되면, 연동된 어플리케이션에서 결제가 가능하다.

3.1.2. RSSI 알고리즘 동작 및 특징

68 ict_ 스마트쇼핑카트 (1)

RF 비콘을 3개 생성하여 삼각측량법에 이용할 수 있도록 구성하였다. 비콘의 신호를 감지하기 위해 라즈베리파이를 오른쪽과 같이 비콘 수신기로 구성하였다. 비콘의 신호를 수신하여 rssi값으로 거리를 측정하고 현재의 위치를 추적할 수 있도록 한다.

68 ict_ 스마트쇼핑카트 (4)

68 ict_ 스마트쇼핑카트 (5)
Raspberry Pi가 RF Beacon에게 Data를 얻어와 TXpower와 RSSI값을 분리하여, RSSI에 따른 거리를 구한다. 3개의 비콘 모두 거리를 구해, 삼변측량법을 사용하여 목표위치를 파악할 수 있다.
비콘의 RSSI 값을 이용하여 직선거리를 구하는 공식을 이용하였습니다. 공식을 적용하기 위 TX Power 와 RSSI 값을 정제하여 거리 공식에 대입하였다.

68 ict_ 스마트쇼핑카트 (6)

3개 비콘의 각각의 좌표와 측정한 직선거리를 이용하여 삼변 측량법을 적용해 사용자의 현재 좌표를 출력하였다.

68 ict_ 스마트쇼핑카트 (7)

3.1.3. 최단 경로 안내 알고리즘 동작 및 특징

68 ict_ 스마트쇼핑카트 (8)

TSP(Traveling Salesperson Problem)을 이용하였다. TSP 알고리즘이란 원래 여러 도시들이 있을 때 한 도시로부터 시작해서 모든 도시를 단 한 번씩만 방문하여 다시 시작점으로 돌아오는데 드는 최단거리를 구하는 문제이다. 여기서 우리는 도시를 상품의 코너로 가정하였다. 입구에서 출발하여 각 선택된 코너들을 거치고 다시 입구로 돌아오는 식으로 최단경로 안내 문제를 해결하였다. 중간에 생각하지 못했던 필요한 물품이 생길 시 코너를 추가 선택한 후 안내를 누르면 다시 그 자리에서 시작하는 최단 경로를 시각화 하여 카트에 부착된 모니터로 안내해 준다. 전수 조사 방식으로 진행하여서 각 경로를 기억하기 위해 스택을 사용했다. 거리를 구하고 스택에서 각 경로의 거리 중 가장 짧은 경로를 선정하여 사용자에게 안내해 준다.

3.1.4. 장바구니 & 상품 Database 알고리즘 동작 및 특징
상품 Database: 먼저 MySql DB를 라즈베리파이에 설치한다. 명령어를 통해 데이터베이스를 생성한 후 데이터베이스 내에서 테이블을 생성한다. 테이블을 생성할 때에 바코드번호, 상품 이름, 상품 가격을 받아오도록 자료형을 지정해주고 테이블을 만들어준다.

68 ict_ 스마트쇼핑카트 (9)

장바구니 기능: while문을 통해 항시 입력을 대기하며, 바코드 스캐너로 물품의 바코드를 찍으면 Database Table에서 해당 물품의 정보를 가져와 목록에 저장을 한다. 생성해 놓은 데이터베이스를 파이썬과 연동하여 데이터베이스를 조회할 수 있도록 구성하였다.

68 ict_ 스마트쇼핑카트 (10)
연동 방법은 pymysql이라는 파이썬 라이브러리를 설치해주었다.

68 ict_ 스마트쇼핑카트 (11)

저장한 목록은 디스플레이에 출력하여 사용자가 구매할 물품이 장바구니에 담긴 것을 시각정인 정보로 제공한다. Pandas를 이용하여 시각화하였다. 아래의 사진을 보면 시각화된 모습을 볼 수 있다.

68 ict_ 스마트쇼핑카트 (12)

장바구니에 담긴 물품들 중, 구매하지 않을 물품을 삭제할 때 입력창에 1을 입력하면 delete mode로 바뀐다. 디스플레이에서 제공하는 상품의 목록을 통해 삭제할 물품의 인덱스 번호를 입력하면 쉽게 삭제가 가능하다.

행사 정보 안내 기능: 사용자에게 물품의 행사 정보를 쉽고, 간편하게 제공할 수 있는 방법에는 음성 안내 기능이 있다. 구매하려는 물품의 바코드를 스캔하면, 장바구니 목록에 담김과 동시에 해당 물품의 행사 정보를 음성으로 안내해준다. 음성 안내 기능은 TTS(Text to Sound)로 구현하였으며, 문자열로 넘겨주도록 했다.

68 ict_ 스마트쇼핑카트 (13)

3.1.5. 결제기능 동작 및 특징
Application과 Raspberry Pi간의 소켓통신으로 장바구니에 담긴 최종 금액을 APP으로 넘겨준다.(소스코드 [첨부 19] 참고) 통신 방식은 소켓통신을 이용하였다. 라즈베리파이가 클라이언트가 되도록 구성하고 App이 서버가 되도록 구성하였다. App에서 제공해주는 IP를 입력한 후 데이터를 전송하게 된다.

68 ict_ 스마트쇼핑카트 (14)
넘겨받은 데이터를 토대로 결제 기능을 제공하며, 해당 결제 기능은 안드로이드 스튜디오에 Bootpay SDK를 연동하여 결제 테스트를 할 수 있도록 구성하였다. 안드로이드용 Application ID를 Bootpay 관리자에서 가져와 연동하였다. 안드로이드 라이브러리는 내부적으로 WebView를 이용하여 구현되어 있다. Javascript SDK를 안드로이드 WebView에서 구현한 방식으로, 결제연동, 결제결과에 대한 라이프 사이클 함수가 제공된다. 따라서 안드로이드 레이아웃에 View를 줌으로써 결제 창을 띄우도록 구성하였다.

68 ict_ 스마트쇼핑카트 (15)

3.1.6 프로그램 사용법

68 ict_ 스마트쇼핑카트 (16)
처음 모드선택에서 0번을 선택할 시 경로안내 모드로 들어가게 된다.

68 ict_ 스마트쇼핑카트 (2)

화면에서 처음에 방문하고자 하는 코너를 선택할 수 있다. 코너를 다 선택하면 최단경로가 시각적 정보로 사용자에게 안내된다.

68 ict_ 스마트쇼핑카트 (17)

안내 후 다시 모드선택으로 되돌아온다. 여기서 1번을 선택하게 되면 장바구니 모드로 들어가게 된다. 장바구니 모드에서 바코드 스캔을 통해 장바구니를 추가할지 구성된 장바구니 목록을 삭제할지 결제 할지를 선택할 수 있다.

68 ict_ 스마트쇼핑카트 (18)

 

먼저, 바코드를 스캔하면 TTS(Text to Speech)로 상품의 행사정보를 사용자에게 음성안내를 해준다. 모니터에는 상품이 추가된 장바구니 목록을 볼 수 있다.

68 ict_ 스마트쇼핑카트 (19)

다른 상품의 바코드를 찍으면, 장바구니 목록에 새로운 상품이 추가된 것을 볼 수 있다.

68 ict_ 스마트쇼핑카트 (20)

같은 상품을 여러 번 찍으면 목록이 새로 추가되는 것이 아니라 해당 상품의 수량만 늘어나는 것을 볼 수 있다.
바코드 스캔 대신 1을 2번 입력하면 삭제 모드로 들어간다. 현재까지 구성된 장바구니 목록을 보여주며 삭제하고자 하는 목록의 인덱스를 입력하면 삭제가 된다. 다음 사진을 보면 삭제가 되는 것을 확인할 수 있다.

68 ict_ 스마트쇼핑카트 (21)

마찬가지로 바코드 스캔 대신 2를 2번 입력하면 결제 모드로 들어가게 된다. 앱에서 제공하는 IP정보를 입력하면 결제정보를 App으로 전송하며 결제 가능하다. 다음 사진을 보면 결제가 청구된 것을 확인할 수 있다.

68 ict_ 스마트쇼핑카트 (22)

3.1.7 전체 알고리즘 동작 및 특징

68 ict_ 스마트쇼핑카트 (23)

3.2. 전체 시스템 구성

68 ict_ 스마트쇼핑카트 (24)
3.2.1 하드웨어 구성
· Raspberry Pi
· Barcode scanner
· Speaker
· Monitor

3.2.2 소프트웨어 구성
· MySQL Database
· TSP algorithm source
· 삼변측량 algorithm source
· TTS source

3.2.3 주요 동작 및 특징
· Barcode scan을 통한 상품, 장바구니 database 구성
· Barcode scan을 통한 행사 정보 음성 안내 기능 TTS 구현
· 삼변측량법을 통한 실내 위치 측위
· TSP algorithm을 통한 최단경로 추적

3.3. 개발 환경

68 ict_ 스마트쇼핑카트 (25)

 

68 ict_ 스마트쇼핑카트 (26)

4. 단계별 제작 과정
4.1. Hardware
Raspberry PI에 보조배터리, 모니터, 스피커, 바코드 스캐너가 연결하였다. 전동 카트(장바구니)에 Raspberry Pi와 함께 전력을 공급해주는 보조배터리, 경로안내와 장바구니 목록을 시각화해줄 모니터, 해당 상품의 행사 정보를 음성으로 출력해줄 스피커를 부착한다. 또한, 바코드 스캐너를 쇼핑카트에 부착하였고, 스캔 시 UART 통신을 통해 라즈베리로 바코드 정보를 넘겨 상품을 장바구니 목록에 담을 수 있도록 한다. 담긴 상품의 목록은 모니터로 사용자에게 시각화 해줌으로써 사용자가 장바구니를 직접 보며 관리할 수 있도록 한다.

탈부착식 모듈
탈부착 식 모듈로 전동 카트뿐만 아니라 일반 카트에도 부착할 수 있다. 탈부착 식이므로 전동 카트의 개수가 아닌 한 매장에 필요한 개수만 구비해도 됨으로 가격적 부담도 줄어들도록 했다.

실내 측위를 위한 Bluetooth Beacon

68 ict_ 스마트쇼핑카트 (27)
위치 추적을 위해 최소 3개의 블루투스 비콘을 마트에 설치함으로써 실내에서의 실제 위치를 추적 가능하다. 각각의 비콘마다 코인건전지 CR2032 3V 2개로 전원공급을 한다. 하지만, 1m 밖의 거리에서 정확도가 떨어지는 RSSI값을 확인할 수 있었다. 짧은 통신 거리의 한계로 Beacon을 이용할 때 1m 내의 범위에서 시연을 진행하였다.

실내 측위를 위한 RF 통신

68 ict_ 스마트쇼핑카트 (28)

앞서 언급한 Buletooth Beacon의 짧은 통신 거리의 한계를 대체할 RF 통신을 구현하였다. RF통신은 통신 거리가 100m까지도 가능하기 때문에, 해당 문제점을 보완할 수 있다.
다음과 같이 Beacon을 세 곳으로 설치하여 RF통신 시연을 진행하였다.

68 ict_ 스마트쇼핑카트 (29)

68 ict_ 스마트쇼핑카트 (30)

시현 위치에서 1분 동안 측정한 데이터의 그래프이다. 그래프를 보면 RSSI값의 변화에서 값이 비교적 일정하게 나오는 것을 볼 수 있어 그 전의 블루투스 Beacon보다는 거리 측면에서나 정확도 측면에서나 뛰어난 성능을 보여주고 있다.

4.2 Software (Raspberry PI, Application)
4.2.1. 라즈베리파이
바코드 스캔 및 행사 정보 안내

68 ict_ 스마트쇼핑카트 (31)

상품을 장바구니에 담는 방법은 간단하다. 카트에 부착된 바코드 스캐너로 상품의 바코드를 스캔하면 장바구니에 담긴 것을 모니터로 확인이 가능하다. 같은 상품의 바코드를 여러 번 스캔 할 시 같은 목록이 여러 개 생기는 것이 아니라 같은 상품의 수량만 늘어나게 하였다. 모니터를 통해 장바구니 목록의 삭제, 수정을 제공한다. 또한 행사 중인 물품은 카트에 부착된 바코드 스캐너를 통해 바코드를 찍을 때 TTS(Text to Speech)로 상품의 이벤트 정보를 읽어준다. 해당 바코드에 찍힌 번호는 자료형을 int로 성언하면 오버로드가 되기 때문에 VARCHAR 형으로 자료형을 바꾸어 해결할 수 있다.

최단 경로

68 ict_ 스마트쇼핑카트 (32)
해당 매장의 코너의 데이터들은 모두 저장이 되어있다는 가정하에 진행이 된다. 사용자가 방문하고자 하는 코너를 모니터상으로 선택할 시, TSP (Traveling Salesperson Problem) 알고리즘을 이용한다. 실내 측위 정보로 현재 위치로부터 최단거리를 계산하게 된다. 최단의 이동경로를 모니터를 통해 사용자에게 안내해준다.

실내 측위
실내 측위 기능: 사용자에게 현재 어느 위치에 있는지 실시간으로 사용자에게 알려주고 최단 경로 안내에도 쓰인다. 건물에 설치되어 있는 비콘의 신호를 수신하여 삼각측량법을 통해 현재 위치를 계산한다.

장바구니
상품 정보의 관리는 MySQL DB를 이용하여 물품의 Database를 구축하여 상품정보를 등록해 놓았다. MySQL에서 기존에 만들어놓은 table을 삭제하지 않고 같은 이름으로 다시 생성하면 오류가 발생하기 때문에 기존 table을 삭제하고 다시 생성해야한다. Python과 연동하여 바코드 번호로 조회 할 수 있게 구성하였고, 카트에 연결된 모니터에 결제 버튼을 누르면 연동된 App(부트페이)으로 결제정보를 보냄으로써 결제 할 수 있게 하였다.
App은 원래의 마트나 (홈플러스, 이마트 등) 대형마트에서 제공하는 App으로 결제 정보를 보낸다고 가정했지만 실제 App을 수정할 수 없어 결제기능만 있는 어플을 따로 구현하였다.

상품목록 추가 : 상품의 바코드를 스캔할 시, 모니터 상으로 상품이 추가 된 것을 확인할 수 있다.

68 ict_ 스마트쇼핑카트 (33)

상품목록 삭제 : 필요하지 않은 상품은 삭제 모드로 들어가 해당 상품의 인덱스를 지정해 삭제할 수 있다.

68 ict_ 스마트쇼핑카트 (34)
결제 모드를 통해 어플에서 결제가 가능하다.

68 ict_ 스마트쇼핑카트 (35)

GUI
경로 안내 모드에서 기존에 제시한 디스플레이는 텍스트 형식으로만 구성이 되어있었다. 이를 가독성있게 만들기 위해서 Python에서 사용하는 GUI를 이용해 시각화시켰다. 왼쪽의 메인 화면을 통해 모드를 선택할 수 있으며 경로와 장바구니 모드가 있다. 경로안내모드에서는 사용자가 원하는 코너를 선택하고 OK 버튼을 누를 수 있으며 취소하고 싶은 코너가 있다면 다시 한 번 누르고 OK 버튼을 누르면 된다.

68 ict_ 스마트쇼핑카트 (36)

68 ict_ 스마트쇼핑카트 (37)

장바구니 모드에서는 바코드 스캔이 주를 이룬다. 바코드로 상품을 스캔할 때마다 목록에 상품들이 쌓여간다. 삭제 기능도 제공하여 Check를 눌러 목록에서 삭제를 누를 수 있다.

68 ict_ 스마트쇼핑카트 (38)

마지막으로, 결제 버튼을 누르게 되면 사용자의 어플과 통신으로 해당 상품의 가격을 전송하게 된다.

4.2.2 어플리케이션

안드로이드 스튜디오와 부트페이 SDK를 연동하여 가상 결제 기능을 제공한다. 인앱 결제 방식은 구현 시, 유로 모듈 결제가 필요하기 때문에 무료로 제공하는 Bootpay 방식을 사용하였다. Bootpay 사이트에서 결제 연동 시, 필요한 ID를 받아올 때 WEB Application ID로 잘못 받아와 실패하였다. Android Application ID를 제대로 받아와서 연동 문제를 해결할 수 있다. 또한 Android Studio Build.gradle(app)에서 compileSdkVersion은 30으로 설정하고, targetSdkVersion은 27로 설정하여 오류가 발생하였다. targetSdkVersion30으로 수정할 시, 정상 동작하는 것을 확인할 수 있다.

68 ict_ 스마트쇼핑카트 (39)

4.3 주요 소스 코드 설명

#module Declaration
import blescan
import pymysql
import pandas as pd
from pandas import DataFrame as df
import os
import math
import socket
import sys
import bluetooth._bluetooth as bluez
from numpy import median

#Beacon Address
beacon1=”e1c56db5dffb48d2b060d0f5a71096e0″
beacon2=”e2c56db5dffb48d2b060d0f5a71096e0″
beacon3=”e3c56db5dffb48d2b060d0f5a71096e0″
dev_id=0
count=0

#Beacon Position Setting
position1=[45,90,0] position2=[0,0,0] position3=[90,0,0]

#Use queue
def push(element):
global top
top=top+1
stack[top]=element
def pop():
global top
ret=stack[top] top=top-1
return ret

def show():
global top
global mincorner
global mincorv
for i in range(0,top+1):
mincorv +=” ->”+stack[i][0]

def getDistance(a,b):
return math.sqrt((a[1]-b[1])*(a[1]-b[1])+(a[2]-b[2])*(a[2]-b[2]))

#Traveling Salesperson Problem
def TSP(start, corner, number, sumv, now):
global minv
global totalCount
global mincorner
global mincorv
count=0
visited[start]=1
for i in range(0,number):
if visited[i]== 0:
count=count+1
visited[i]=1
push(corner[i])
TSP(start,corner, number,sumv+getDistance(corner[now], corner[i]),i)
visited[i]=0
pop()

if count == 0:
mincorv=mincorv+”->”+corner[start][0] sumv=sumv+getDistance(corner[now], corner[start])
show()
mincorv+=”\nDistance: “+ str(sumv)
mincorner.append(mincorv)
mincorv=”"
if minv>sumv:
minv=sumv

totalCount=totalCount+1

def getTrilateration(position1, position2, position3): #Get Beacon Position
x1=position1[0] y1=position1[1] r1=position1[2] x2=position2[0] y2=position2[1] r2=position2[2] x3=position3[0] y3=position3[1] r3=position3[2]

S=(math.pow(x3,2.0)-math.pow(x2,2.0)+math.pow(y3,2.0)-math.pow(y2,2.0)+math.pow(r2,2.0)-math.pow(r3,2.0))/2.0
T=(math.pow(x1,2.0)-math.pow(x2,2.0)+math.pow(y1,2.0)-math.pow(y2,2.0)+math.pow(r2,2.0)-math.pow(r1,2.0))/2.0

y=((T*(x2-x3))-(S*(x2-x1)))/(((y1-y2)*(x2-x3))-((y3-y2)*(x2-x1)))
x=((y*(y1-y2))-T)/(x2-x1)

return x,y

#Initialization DataBase
stack=[0 for i in range(5)] top = -1

#Setting corner position
corner=[("Enter", 75.1, 53.3),
("Dressed meat", 0.0, 5.1), ("Vegetable", 0.0, 10.2), ("Seafood", 0.0, 15.3), ("Fruit", 15.7, 19.1),
("Toy", 5.0, 5.5), ("Beverage", 5.0, 10.6), ("Home appliance", 28.7, 41.8), ("Kitchenware", 5.0, 20.8),
("Bathroom", 10.0, 5.9), ("Snack", 10.0, 10.0), ("Instant noodles", 40.9, 38.3), ("Egg", 10.0, 20.2),
("Diary products", 44.6, 24.6), ("Frozen food", 52.2, 59.6), ("Clothing", 15.0, 15.5), ("Shoes", 15.0, 20.6),
("Tool", 20.0, 5.7), ("Electronics", 20.0, 10.8), ("Pet goods", 20.0, 15.9), ("Beer", 20.0, 20.0)] mincorner=[] mincorv=”"
visited=[0 for i in range(5)] minv=2147483647.0
totalCount=0

try:
sock = bluez.hci_open_dev(dev_id)
print “ble thread started”
except:
print “error accessing bluetooth device…”

blescan.hci_le_set_scan_parameters(sock)
blescan.hci_enable_le_scan(sock)

conn = pymysql.connect(host=’localhost’, user=’yang’, passwd=’1′, db=’goods’,charset=’utf8mb4′)
cur = conn.cursor()
DT = df({‘Goods List’: [],’Number of’:[]});
pd.options.display.float_format=’{:,.0f}’.format

#Initialization Socket
PORT=8888
s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
distance_avg=[] while True:
returnedList = blescan.parse_events(sock, 10)
print “Select the Mode(0:Short Path, 1:Scan barcode, Exit is any Number): “,
mode=int(input())
if mode==0:
#print corner number
print “1.Dressed meat 2.Vegetable 3.Seafood 4.Fruit”
print “5.Toy 6.Beverage 7.Home appliances 8.Kitchenware”
print “9.Bathroom 10.Snack 11.Instant noodles 12.Dairy product”
print “13.Egg 14.Frozen food 15.Clothing 16.Shoes”
print “17.Tool 18.Electronics 19.Pet goods 20.Beer”

print “\nSelec the Corner: “,
num=(input()).split()
flag=[0 for i in range(21)] vis=[0 for i in range(len(num))]

#print corner map
print “=========”
for j in range(0,len(num)):
flag[int(num[j])]=1
vis[j]=corner[int(num[j])]

for i in range(1,21):
if flag[i] == 1:
print corner[i][0],
for j in range(0,16-len(corner[i][0])):
print ” “,
else:
print ” “,

if i%4==0 and i!=0:
print “\n”

print ” “,
print corner[0][0] print “========\n”
for beacon in returnedList:
if beacon[18:18+len(beacon3)]==beacon1:
distance_avg.insert(count,(10**((float(beacon[61:64])-(float(beacon[65:])))/20.0))*100)
count=count+1
if count == 10:
if median(distance_avg)<1:
position1[2]=median(distance_avg)
count=0
del distance_avg[:]

if beacon[18:18+len(beacon3)]==beacon2:
distance_avg.insert(count,(10**((float(beacon[61:64])-(float(beacon[65:])))/20.0))*100)
count=count+1
if count == 10:
if median(distance_avg)<1:
position2[2]=median(distance_avg)
count=0
del distance_avg[:]

if beacon[18:18+len(beacon3)]==beacon3:
distance_avg.insert(count,(10**((float(beacon[61:64])-(float(beacon[65:])))/20.0))*100)
count=count+1
if count == 10:
if median(distance_avg)<1:
position3[2]=median(distance_avg)
count=0
del distance_avg[:]

if beacon[18:18+len(beacon3)]==beacon3 and beacon[18:18+len(beacon3)]==beacon2 and beacon[18:18+len(beacon3)]==beacon1:
X,Y=getTrilateration(position1,position2,position3)
#find current location
for i in range(0,21):
if abs(X-coner[i][1])<1 and abs(Y-coner[i][2])<1:
myplace=i

#print short path
TSP(i,vis,len(vis),0,0)
print “Total Path Num: “,totalCount
for i in range(0,len(mincorner)):
if mincorner[i].find(str(minv)) != -1:
n=i
print “Shortest Path: “,mincorner[n] print “\n”
mincorner=["" for i in range(len(mincorner))] minv=2147483647

elif mode==1:
while True:
print “Scan barcode(1:Delete, 2:Pay): “,
goods_cnt=1
scan_in,n=[input() for _ in range (2)] scan_in.strip(‘\n\n’)
if scan_in is ‘q’:
break
elif scan_in is ’1′:
print “=======
print(DT)
num=int(input(“delete (-1:return):”))
#while(num==)
if num== -1:
continue
else:
DT.drop(num,inplace=True)
DT.reset_index(inplace=True)
DT.drop(‘index’, axis=1,inplace=True)
print(“{0} delete”.format(num))
print(DT)
elif scan_in is ’2′:
A=[0 for i in range(len(DT))] sumf=0
ip=input(“Input IP of App: “)
s.connect((ip,PORT)) #Connect App
for i in range(0,len(DT)):
A[i]=str(DT.iloc[i]).split(‘,’)
B=A[i][3].split(‘Number of’) #The number of product
C=(B[1].replace(” “,”")) #The price of product
sumf+=int(A[i][2])*int(C[0])
sumf=str(sumf)
s.send(sumf.encode(‘utf-8′)) #Total price
s.close()
break

else:
sql=”SELECT * FROM list WHERE barcode LIKE %s”
cur.execute(sql,scan_in)
for row in cur:
cp=str(row)
empty=str(DT[DT['Goods List'].isin([cp])])
if (empty.find(“Empty”) is not 0):
i=DT[DT['Goods List'].isin([cp])].index
num=DT[DT['Goods List']==cp]['Number of'].tolist()
num=DT.loc[i,['Number of']]=(num[0]+1)
else:
new_data={‘Goods List’:cp, ‘Number of’:goods_cnt}
DT=DT.append(new_data, ignore_index=True)
text=str(new_data).split(‘,’)
text=text[3].replace(‘)”‘,”")
os.system(‘echo Event is %s | festival –tts’ %text)
print(DT)
else:
break

cur.close()
conn.close()

5. 사용한 제품목록

제품명 디바 상품번호
블루이노2 키트보드 (BI-200(K)) 1287076
microSD Cards – TS16GUSDC10M [16GB] 12139535
[RASPBERRY-PI] 라즈베리파이 3 Model B+ 1377518
[(주)블루이노] 블루이노2 (Blueinno) 다운로드 툴 1272986
아두이노 라즈베리파이 바코드 스캐너 모듈
V3.0 USB to UART 인터페이싱 지원
-
스피커 -
RF 모듈 -

6. 기타

6.1 기능블록도

68 ict_ 스마트쇼핑카트 (40)
6.2 목표 달성 현황
장바구니
목표: 바코드 스캔을 통해 장바구니 목록에 넣을 수 있고, 삭제와 추가의 기능도 구현
달성: 바코드 스캔을 통한 물품 목록 추가와 삭제를 할 수 있음.

최단거리
목표: 실내 측위 정보로 현재 위치로부터 최단거리 계산
달성: 현재 위치로부터 최단거리를 계산할 수 있음.

위치측량
목표: 현재 위치를 작은 오차로 받아오도록 구현
달성: 블루투스 비콘에서 RF 비콘으로 변경하여 측정 반경 거리를 넓혀 사용성을 확대함. 약간의 오차는 개선방법을 추후 적용할 예정.

인터페이스
목표: 작성한 GUI틀과 프로그램 연동
달성: 코너의 위치 정보와 최단경로, 상품목록 등을 볼 수 있도록 구현하였음, 사용자 친화적인 인터페이스 화면 디자인 완성. 추후 프로그램과 GUI를 연동할 예정.

결제 앱
목표: 장바구니 목록에 있는 상품을 앱으로 결제 가능하도록 구현
달성: 장바구니 목록을 구성할 수 있고 결제가 가능함.

6.3 시연영상 주소

68 ict_ 스마트쇼핑카트 (41)

6.4 참고문헌
· https://gongdae58.tistory.com/73
· https://ko.wikipedia.org/wiki/%EC%82%BC%EB%B3%80%EC%B8%A1%EB%9F%89
· https://codeanalysis.tistory.com/2
· https://m.blog.naver.com/wlsdml1103/221159758141
· https://m.blog.naver.com/PostView.nhn?blogId=dsz08082&logNo=221852345347&proxyReferer=https:%2F%2Fwww.google.com%2F
· http://pythonstudy.xyz/python/article/408-pandas-%EB%8D%B0 %EC%9D%B4%ED%83%80-%EB%B6%84%EC%84%9D

 

 

 

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

68 ICT_간호사연동 (41)

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>