<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>NTREXGO - 디바이스마트, 엔티렉스 컨텐츠 통합 사이트 &#187; 공모전</title>
	<atom:link href="http://www.ntrexgo.com/archives/tag/%ea%b3%b5%eb%aa%a8%ec%a0%84/feed" rel="self" type="application/rss+xml" />
	<link>http://www.ntrexgo.com</link>
	<description>엔티렉스, 디바이스마트 컨텐츠 통합 사이트</description>
	<lastBuildDate>Thu, 03 Mar 2022 06:47:11 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.5.1</generator>
		<item>
		<title>[68호]메카넘휠과 2D lidar를 이용한 자율주행 로봇</title>
		<link>http://www.ntrexgo.com/archives/40952</link>
		<comments>http://www.ntrexgo.com/archives/40952#comments</comments>
		<pubDate>Mon, 25 Oct 2021 00:00:50 +0000</pubDate>
		<dc:creator>디바이스마트 매거진</dc:creator>
				<category><![CDATA[디바이스마트 매거진]]></category>
		<category><![CDATA[특집]]></category>
		<category><![CDATA[ict]]></category>
		<category><![CDATA[ict공모전]]></category>
		<category><![CDATA[공모전]]></category>
		<category><![CDATA[디바이스마트]]></category>
		<category><![CDATA[매거진]]></category>
		<category><![CDATA[수상작]]></category>
		<category><![CDATA[융합]]></category>
		<category><![CDATA[프로젝트]]></category>

		<guid isPermaLink="false">http://www.ntrexgo.com/?p=40952</guid>
		<description><![CDATA[디바이스마트매거진 68호 &#124; 자율주행 로봇이 주변의 모든 구조를 인식하고, 그 구조 위에서 목적지까지 장애물을 피해서 갈수 있는 최단 경로를 생성한 후에 그 경로를 따라 로봇이 움직이게 구현한다.
]]></description>
				<content:encoded><![CDATA[<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_매카넘휠-1.png" rel="lightbox[40952]"><img class="alignnone size-large wp-image-41051" alt="68 ICT_매카넘휠 (1)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_매카넘휠-1-620x203.png" width="620" height="203" /></a></p>
<p><span style="font-size: medium"><strong>ICT 융합 프로젝트 공모전 우수상</strong></span></p>
<p><span style="font-size: x-large;color: #ff6600"><strong>메카넘휠과 2D lidar를 이용한 자율주행 로봇</strong></span></p>
<p style="text-align: right">글 | 포항공과대학교 이도현</p>
<p>&nbsp;</p>
<p><span style="color: #ff6600"><strong>1. 심사평</strong></span><br />
<strong>칩센</strong> 자동차의 자율 주행을 위해서도 Lidar와 Radar를 이용한 기술들이 매우 많은 관심과 연구가 이루어지고 있는 것으로 알고 있습니다. 시연 동영상내에서도 경로를 잘 탐색한듯 하지만 마지막에 충돌이 발생하는 부분을 확인 가능한데, 이러한 부분을 개선 가능한 다른 솔루션도 함께 적용하면 시뮬레이션 레벨을 넘어서는 매우 훌륭한 작품이 만들어 질 것으로 보입니다.<br />
<strong>펌테크</strong> LIDAR를 활용한 자율주행 로봇 작품으로 전체 하드웨어 및 소프트웨어 시스템 연동을 위한 기획의도, 기술 구현 방안 및 작품의 하우징 구성 등이 인상적이었으며 기술 구현도, 완성도 등에서 상당히 우수한 작품으로 생각합니다.<br />
<strong>위드로봇</strong> 메카넘 휠을 쓴 부분을 제외하곤 전형적인 이동로봇이라 창의성 부분이 아쉽습니다. 완성도는 높이 평가합니다.<br />
<strong>뉴티씨</strong> 각종 센서로 장애물 등을 파악하여 회피할 수 있었는데, 비용만 충분하다면, 보다 많은 센서를 사용하여, 아무리 목적지가 벽 안에 있다고 해도 부딪히지 않았을 것 같아 개선의 여지가 보입니다. 만들다보면, 기계적인 부분에서도 많은 비용이 발생하고, 배선 부분이나 통신 부분에서도 어려움이 많은데, 조금 더 노력하면 더 좋은 작품이 탄생할 것 같습니다.</p>
<p><span style="color: #ff6600"><strong>2. 작품 개요</strong></span><br />
본 프로젝트인 ‘메카넘휠과 lidar를 이용한 자율주행 로봇’ slam기술을 적용하여 자율주행을 구현하였다. slam이란 Simultaneous Localization and Mapping의 약자로, 동시에 로봇의 위치추정 및 주변 환경 지도 작성을 수행하는 기술이다. 본 프로젝트에서는 2d lidar를 통해 주변 환경을 pointcloud 데이터로 받아와, 메카넘휠의 odometry와 결합하여 map을 생성한다. 생성된 map을 바탕으로 출발점에서 목적지까지 최단 경로를 생성하여, 로봇이 그 경로를 따라 이동하는 방식으로 자율주행이 이루어진다.<br />
단순히 로봇 주변에 장애물이 일정 이상 가까워지면 멈추고 다른 방향으로 가는 것이 아니라, lidar가 닿는 전 부분에서 주변의 모든 구조를 인식하고, 그 구조 위에서 목적지까지 장애물을 피해서 갈수 있는 최단 경로를 생성한 후에 그 경로를 따라 로봇이 움직이는 것이다. 이러한 작업이 실시간으로 진행된다.<br />
또한 일반적인 자율주행 플랫폼과 달리, 만들어진 지도를 자율주행에만 사용하고 버리는 것이 아니라. 그 데이터를 TXT파일로 저장해 도출하여 주행이 끝나고 시스템이 종료된 뒤에도 만들어진 지도를 확인할 수 있는 기능을 가지고 있다.<br />
해당 자율주행 플랫폼은 식당 서빙 로봇, 로봇청소기, 물류창고 로봇 등등 바퀴를 사용하는 플랫폼이라면 모두 적용할 수 있으므로, 활용성과 적용성이 매우 높다고 볼 수 있다.</p>
<p><span style="color: #ff6600"><strong>3. 작품 설명</strong></span><br />
<span style="color: #008080"><strong>3.1. 주요 동작 및 특징</strong></span><br />
<span style="color: #33cccc"><strong>3.1.1. 전방향 이동 가능</strong></span><br />
메카넘 휠을 기반으로 플랫폼을 제작하였다. 메카넘휠의 특징 상, 플랫폼(차)이 회전하지 않고도 모든 방향으로 이동할 수 있다는 장점이 있다. 따라서 일반적인 조향장치를 가진 플랫폼이 가지 못하는 경로도 본 프로젝트의 메카넘휠 플랫폼은 따라 갈 수 있다는 장점이 있고, 이는 자율주행에 큰 이점이 될 수 있다.<br />
<strong></strong></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_매카넘휠-2.png" rel="lightbox[40952]"><img class="alignnone size-full wp-image-41052" alt="68 ICT_매카넘휠 (2)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_매카넘휠-2.png" width="612" height="613" /></a></p>
<p><span style="color: #33cccc"><strong>3.1.2. 주변 환경 인식 후 지도생성 가능</strong></span></p>
<p><span style="color: #33cccc"><strong><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_매카넘휠-3.png" rel="lightbox[40952]"><img class="alignnone size-full wp-image-41053" alt="68 ICT_매카넘휠 (3)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_매카넘휠-3.png" width="617" height="436" /></a></strong></span><br />
본 프로젝트는 ydlidar X2L을 사용하여 주변 환경을 pointcloud로 받아온다. 받아온 pointcloud들을 메카넘휠의 wheel odometry와 합쳐 주변환경의 지도를 생성한다. 주변환경의 정보를 다음 페이지 테스트 환경과 지도로 수집할 수 있다는 그 자체로 장점이 될 수 있고, 저렇게 실시간으로 제작되는 지도는 자율주행을 위한 경로탐색 알고리즘을 담당하는 python코드로 전송되어 로봇이 목표지점까지 갈 수 있도록 하는 경로를 생성하는 데 이용된다.<br />
<strong></strong></p>
<p><span style="color: #33cccc"><strong>3.1.3. 로봇이 갈 수 있는 최단경로 정보 생성 가능</strong></span><br />
본 프로젝트의 로봇은 얻은 지도 데이터를 바탕으로 로봇이 목표지점까지 갈 수 있는 최단 경로를 실시간으로 생성한다.<br />
최단거리 생성을 우선으로 하되, 벽에 부딪힐 가능성을 줄이고자 벽과는 반드시 일정 거리를 두도록 프로그래밍 하였다. 경로탐색에 쓰인 알고리즘은 A* algorithm이다. 실시간으로 전송되어 오는 지도마다 경로탐색을 새로 실시하기 때문에, 주행 중 이전 지도에는 없던 장애물이나 벽을 감지하게 되면, 그에 따라 새로운 경로를 생성해낸다. 따라서 목표지점까지 장애물과 부딪히지 않으면서 최단 경로로 도달할 수 있다.<br />
<strong></strong></p>
<p><span style="color: #33cccc"><strong>3.1.4. 생성된 최단경로를 바탕으로 로봇의 자율 주행 가능</strong></span><br />
A* 알고리즘을 통해 얻은 경로를 로봇이 그대로 따라갈 수 있는 능력을 가지고 있다. 자세한 원리는 후에 다른 목차에서 설명할 것이다. 현재 로봇의 목적은 자율주행이므로 A*알고리즘이 만든 경로만을 따라가지만, 설정을 바꾼다면 사람이 지도를 보고 그린 경로를 따라가게도 만들 수 있다.<br />
본 프로젝트의 주요 동작은 자율주행이다. 라인트레이서나 트래커와 다르게 어떠한 표식이 없어도 스스로 주변환경을 인식하여 외부의 도움 없이 목적지까지 도달 할 수 있으며, 일반적인 자율주행과 다르게 메카넘휠을 사용하였기 때문에 모든 경로로 이동할 수 있는 장점이 있다. 또한, 주 부품이 스텝모터 4개, 저가형 2d lidar 하나, jetson nano로, 굉장히 저가로 자율주행을 구현할 수 있는 플랫폼을 제작하였다는 점에서 의미가 있다.</p>
<p><span style="color: #33cccc"><strong>3.1.5. 로봇을 직접 조종하여 map data 얻고, 그 데이터를 txt파일로 저장하는 기능</strong></span><br />
자율주행 뿐 아니라, 사람이 직접 조종하여 환경의 지도 데이터를 얻고, 이를 txt파일로 저장하여 시스템이 꺼진 뒤에도 해당 공간의 지도 데이터를 저장해둘 수 있다. 해당 txt파일은 본 프로젝트에서 만든 파이썬 코드로 시각화할 수 있다.</p>
<p><span style="color: #008080"><strong>3.2. 개발 환경</strong></span><br />
<span style="color: #33cccc"><strong>3.2.1. Linux (ubuntu 18.04)</strong></span><br />
모든 작업은 jetson nano의 ubuntu 18.04 LTS에서 이루어졌다.</p>
<p><span style="color: #33cccc"><strong>3.2.2. arduino</strong></span><br />
로봇이 Vx, Vy, W만큼 이동하라는 명령을 받았을 때, 이를 메카넘휠 odometry 계산을 통해 각각의 스텝모터가 얼만큼의 각속도로 회전해야 하는지를 계산해주고, 그 계산값을 스텝모터에 적용해 실제로 돌게 하는 부분을 arduino IDE에서 작업하였다.</p>
<p><span style="color: #33cccc"><strong>3.2.3. python</strong></span><br />
lidar센서에서 pointcloud데이터를 읽어오는 작업과, 로봇이 얼마나 이동했는지를 계산해 mm단위로 그 값을 반환하는 작업, 경로탐색 알고리즘인 A* 알고리즘을 python 코드로 구현하였다.</p>
<p><span style="color: #33cccc"><strong>3.2.4. C++</strong></span><br />
python 코드에서 생성된 로봇의 Vx, Vy, W값을 받아와 아두이노로 그 값을 넘겨주는 부분을 담당하는 코드를 C++환경에서 구현하였다.</p>
<p><span style="color: #33cccc"><strong>3.2.5. ROS melodic</strong></span><br />
위에서 언급한 모든 알고리즘들을 ROS를 통해 하나로 연결하여 자율주행이 가능하도록 만들었다. python, C++, arduino(serial) 코드들을 모두 ros node로 만들었으며, python코드에서 생성된 로봇의 Vx, Vy, W값을 ros topic을 통해 C++코드로 넘겨주었고, 마찬가지로 그 값들을 ros topic을 통해 아두이노로 전송하였다.</p>
<p><span style="color: #008080"><strong>3.3. 전체 시스템 구성</strong></span><br />
<span style="color: #33cccc"><strong>3.3.1. 하드웨어</strong></span><br />
4개의 스텝모터에 각각 메카넘휠이 부착되어 있고, 각 스텝모터는 스텝모터 드라이버에 연결되어 있으며, 그 드라이버는 아두이노 우노에 연결되어있다. 아두이노 우노와 2d lidar는 jetson nano에 usb로 연결되어 있다. 그림으로 간단하게 나타내면 다음과 같다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_매카넘휠-4.png" rel="lightbox[40952]"><img class="alignnone size-large wp-image-41054" alt="68 ICT_매카넘휠 (4)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_매카넘휠-4-527x620.png" width="527" height="620" /></a></p>
<p><span style="color: #33cccc"><strong>3.3.2. 소프트웨어</strong></span><br />
본 프로젝트의 자율주행 기능에는 총 4개 코드로 이루어져 있으며 talker.py, A_star.py, ladarbot_node.cpp, ladarbot_subnode.ino가 그것이다.<br />
talker.py는 로봇이 현재위치를 실시간으로 계산하는 기능, lidar센서에서 pointcloud데이터를 얻어오는 기능, 얻어온 pointcloud데이터와 로봇의 현재위치 데이터를 기반으로 지도를 생성하는 기능, A_star.py내의 함수를 호출해 매개변수로 지도를 전달하여 A_star.py로부터 로봇이 이동해야 하는 경로를 받아오는 기능, 해당 경로를 주행하기 위해 로봇이 움직여야 하는 Vx, Vy, W를 생성하는 기능이 구현되어있다.<br />
A_star.py는 talker.py에서 생성한 map을 받아와 로봇이 이동해야 하는 경로를 생성하여 그 경로를 다시 talker.py로 반환한다. A_star.py는 파이썬 모듈로써, talker.py에서 import하여 사용한다.<br />
ladarbot_node.cpp는 talker.py에서 생성한 Vx, Vy, W를 ROS topic을 통해 받아와, ROS topic을 통해 ladarbot_subnode.ino로 그 값을 전송한다. 통신을 위한 매개코드라고 생각할 수 있다.<br />
ladarbot_subnode.ino는 ladarbot_node.cpp에서 ros topic을 통해 전송해준 Vx,Vy,W를 받아와 로봇이 Vx,Vy,W만큼 움직이기 위해 각각의 스텝모터가 얼마만큼의 각속도로 회전해야 하는지를 계산한다. 그 값을 각각의 스텝모터에게 전달하여 로봇이 움직이도록 한다.<br />
A_star.py와 talker.py간 연결을 제외한 모든 코드들간 연결은 ROS topic을 이용해 서로 통신한다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_매카넘휠-5.png" rel="lightbox[40952]"><img class="alignnone size-full wp-image-41055" alt="68 ICT_매카넘휠 (5)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_매카넘휠-5.png" width="620" height="157" /></a><br />
본 프로젝트의 지도 저장 기능을 구현하는 코드는 크게 5가지이다. ladarbot_node.cpp, ladarbot_subnode.ino, lidar.launch, lidar_range_node.cpp, slam_map.py 가 그것이다. 이중 ladarbot_node.cpp, ladarbot_subnode.ino는 자율주행에 사용한 코드와 동일하다. lidar.launch는 ydlidar를 제작한 회사에서 lidar의 데이터를 얻을 때 쓰라고 공유한 오픈소스이며, 지도데이터를 얻어와 txt파일로 만드는 데는 직접 짠 talker.py 대신 해당 오픈소스를 사용하였다. lidar_range_node.cpp는 lidar.launch에서 보내오는 map데이터를 읽어와 플랫폼의 odometry정보와 합쳐 지도정보를 만들고, 이를 txt파일로 내보내는 역할을 하는 코드이다. 마지막으로 slam_map.py는 txt파일을 읽어 지도를 시각화하는 코드이다.<br />
<strong></strong></p>
<p><span style="color: #ff6600"><strong>4. 단계별 제작 과정</strong></span><br />
<span style="color: #008080"><strong>4.1. 하드웨어</strong></span><br />
하드웨어의 경우 본 공모전을 발견하기 전부터 만들어 오고 있었고, 때문에 만드는 과정에서의 사진이 남아 있지 않다. 본 프로젝트의 하드웨어는 메카넘휠 기반 모바일 로봇이며, 정확한 odometry(자기위치 추정)이 가능할 수 있도록 회전수와 회전속도를 정확히 컨트롤 할 수 있는 스텝모터를 사용하였다. 로봇에 들어가는 컴퓨터는 jetson nano를 사용하였다. 기본적인 하드웨어는 알루미늄 프레임을 기반으로 제작하였고, 배터리 고정하는 부품, lidar고정용 부품, 바퀴 보호개 등은 직접 설계하여 3d프린터로 출력하여 사용하였다. 하드웨어 제작의 난이도는 굉장히 낮고, 본 프로젝트의 핵심은 자율주행 알고리즘 및 ROS시스템 사용이므로 하드웨어는 간단하게 설명하였다.<br />
<strong></strong></p>
<p><span style="color: #008080"><strong>4.2. 프로그래밍</strong></span><br />
<span style="color: #33cccc"><strong>4.2.1. 자율주행</strong></span><br />
<span style="color: #ff9900"><strong>하드웨어 제어</strong></span><br />
하드웨어 제어는 ladarbot_subnode.ino라는 arduino IDE코드가 담당한다. 해당 코드는 로봇 몸체가 움직여야할 속도와 방향을 알려주는 Vx, Vy, W값을 받아와서 그 움직임을 구현할 수 있게 각각의 스텝모터가 얼마만큼의 각속도로 회전해야 하는지를 구한다. 그 후 그 값들을 스텝모터들에게 입력하여 모터가 움직일 수 있도록 한다.<br />
본 프로젝트의 로봇은 메카넘 휠을 이용해 이동한다. Vx, Vy, W만큼 로봇이 이동하였을 때, 그 로봇의 4개의 메카넘휠이 얼마만큼의 각속도로 회전하는지를 행렬 계산을 통해 변환 수식을 구해낼 수 있다. 「메카넘휠 기반의 전방향 이동로봇 슬립에 관한 해석 및 실험적 검증」 김설하, 이청화, 주백석, 「메카넘휠 기반의 전방향 이동로봇 슬립에 관한 해석 및 실험적 검증」, 한국정밀공학회지 제 37권 제 1호, 2020.01.35, pp.35-42. 논문을 참고하였다. 4개의 모터의 각속도를 각각 θ1, θ2, θ3, θ4라 하면, 로봇 자체의 속도 Vx, Vy, w와 θ1, θ2, θ3, θ4사이 관계는 다음과 같이 나타나진다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_매카넘휠-6.png" rel="lightbox[40952]"><img class="alignnone size-full wp-image-41056" alt="68 ICT_매카넘휠 (6)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_매카넘휠-6.png" width="620" height="358" /></a></p>
<p>위 행렬식을 계산하여 아두이노 코드로 작성하면 다음과 같이 나타난다.</p>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p>void control(unsigned long current, float Vx, float Vy, float W)<br />
{<br />
if((Vx == 0 &amp;&amp; Vy == 0) &amp;&amp; W == 0)<br />
{<br />
Stop();<br />
}<br />
else<br />
{<br />
w1 = (Vx &#8211; Vy &#8211; (l1 + l2) * W) * (1 / R);<br />
w2 = (Vx + Vy + (l1 + l2) * W) * (1 / R);<br />
w3 = (Vx &#8211; Vy + (l1 + l2) * W) * (1 / R);<br />
w4 = (Vx + Vy &#8211; (l1 + l2) * W) * (1 / R);<br />
motorRF(current, w2);<br />
motorRB(current, w3);<br />
motorLF(current, w1);<br />
motorLB(current, w4);<br />
}<br />
}<br />
</div>
<p>motorRF, motorRB, motorLF, motorLB함수는 각각 오른쪽 앞, 오른쪽 뒤, 왼쪽 앞, 왼쪽 뒤 스텝모터를 제어하는 함수이며. w1, w2, w3, w4가 각각 θ1, θ2, θ3, θ4이다.<br />
Stop함수는 스텝모터에 전류를 차단하는함수인데, 멈춰 있을 때에는 모터가 쉴 수 있도록 하기 위해 삽입하였다. motorRF 함수의 형태는 다음과 같다.</p>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p>void motorRF(int current,float w)<br />
{<br />
if(w &gt; 0)<br />
digitalWrite(3,1);<br />
else<br />
{<br />
digitalWrite(3,0);<br />
w = -w;<br />
}<br />
float num = ((2*PI)/1600)*(1000000/w);<br />
if(current &#8211; previousRF &lt; num){<br />
digitalWrite(2,LOW);<br />
}<br />
else<br />
{<br />
previousRF = current;<br />
digitalWrite(2,HIGH);<br />
}<br />
}<br />
</div>
<p>motorRB, motorLF, motorLB함수 역시 유사한 형태이다.<br />
float num = ((2*PI)/1600)*(1000000/w); 은 사용하는 스텝모터가 1600스텝일 때 한바퀴 회전하기 때문에, 그리고 스텝간 시간간격이 초단위가 아닌 microsecond 단위이므로 다음과 같이 나타내 주었다. 매개변수로 받아오는 current는 아두이노의 micros() 함수를 통해 측정되는 현재 시간이다.(microsecond단위로 반환한다)<br />
다음은 ROS를 통한 통신 부분이다. ladarbot_node,cpp에서 Vx, Vy, w값을 geometry_msgs/Twist 메시지를 통해 ArduinoTopic이라는 Topic name으로 전달받는다.</p>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p>#include&lt;ros.h&gt;<br />
#include&lt;std_msgs/Byte.h&gt;<br />
#include&lt;geometry_msgs/Twist.h&gt;</p>
<p>ros::NodeHandle nh;<br />
void message(const geometry_msgs::Twist&amp; msg);<br />
void odometry(unsigned long current, float Vx, float Vy, float W, float *x, float *y, float *theta);<br />
ros::Subscriber&lt;geometry_msgs::Twist&gt;sub(&#8220;ArduinoTopic&#8221;,message);</p>
</div>
<p>ArduinoTopic 토픽으로 값이 전송될 때마다 message함수를 호출한다. message함수의 구조는 다음과 같다.</p>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p>void message(const geometry_msgs::Twist&amp; msg)<br />
{<br />
Vx = msg.linear.x;<br />
Vy = msg.linear.y;<br />
W = msg.angular.z;<br />
}</p>
</div>
<p>loop함수 내에서의 함수호출 및 실행은 다음과 같다.</p>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p>void loop()<br />
{<br />
unsigned long current;<br />
current = micros();<br />
control(current, Vx, Vy, W);<br />
nh.spinOnce();<br />
}</p>
</div>
<p><span style="color: #ff9900"><strong>lidar data reading &amp; ploting &amp; 로봇 현재위치 계산</strong></span><br />
lidar센서에서 데이터를 얻는 작업, 로봇의 현재위치를 계산하고 lidar데이터와 계산된 현재 위치를 바탕으로 지도를 만드는 작업, 그리고 만들어진 지도와 수신한 경로를 ploting 해 주는 작업을 talker.py에서 진행한다. 우선 lidar로부터 센서데이터(pointcloud) 값을 얻는 부분의 코드이다.</p>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p>port = &#8220;/dev/ydlidar&#8221;;</p>
<p>laser = ydlidar.CYdLidar();<br />
laser.setlidaropt(ydlidar.LidarPropSerialPort, port);<br />
laser.setlidaropt(ydlidar.LidarPropSerialBaudrate, 115200)<br />
laser.setlidaropt(ydlidar.LidarPropLidarType, ydlidar.TYPE_TRIANGLE);<br />
laser.setlidaropt(ydlidar.LidarPropDeviceType, ydlidar.YDLIDAR_TYPE_SERIAL);<br />
laser.setlidaropt(ydlidar.LidarPropScanFrequency, 6.0);#10.0<br />
laser.setlidaropt(ydlidar.LidarPropSampleRate, 9);<br />
laser.setlidaropt(ydlidar.LidarPropSingleChannel, True);<br />
scan = ydlidar.LaserScan()</p>
</div>
<p>사용하는 ydlidar_X2L의 스펙에 맞게, serial통신 baudrate를 115200으로 설정하였다.<br />
다음은 로봇의 현재위치를 계산하고 lidar데이터와 계산된 현재 위치를 바탕으로 지도를 만드는 작업, 그리고 만들어진 지도와 수신한 경로를 ploting 해 주는 작업의 부분이다. 서로가 긴밀하게 연결되어 있어 따로따로 소개하기 힘들어 한번에 소개한다.<br />
우선 로봇의 현재위치를 계산하기 위하여 함수가 돌아가는 시간을 한번의 loop마다 몇 초가 걸리는지를 계산한다. dt = (p.timer &#8211; p.current_time), p.current_time = time.time()과 함수 끝의 p.timer = time.time() 부분이다. 한 루프의 걸린 시간을 측정하는 이유는, 로봇이 Vx, Vy, w로 움직였을 때, 이동거리와 회전각도는 각각에 걸린 시간을 곱하는 형태로 구해지기 때문이다. 현재 위치를 계산하는 부분은 다음과 같다.</p>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p><strong>현재위치 계산</strong></p>
<p>dt = (p.timer &#8211; p.current_time)<br />
p.current_time = time.time()<br />
p.theta = p.theta + p. W*dt<br />
p.odo_x = p.odo_x + (p.Vx * math.cos(p.theta) &#8211; p.Vy * math.sin(p.theta)) * dt<br />
p.odo_y = p.odo_y + (p.Vx * math.sin(p.theta) + p.Vy * math.cos(p.theta)) * dt<br />
：<br />
：<br />
p.timer = time.time()</p>
</div>
<p>다음은 로봇 주변 공간을 지도로 만들 때, 그 공간을 격자화시키기 위한 코드이다. 로봇이 이동하는 공간이 격자화 되어 있어야 후에 로봇의 이동경로를 생성할 수 있다. 격자 하나를 10cm로 설정하였다.</p>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p><strong>지도 공간 격자화 코드</strong></p>
<p>grid_x_max = 5*1000 # 10m<br />
grid_y_max = 5*1000 # 10m<br />
grid_x_min = -5*1000 # -10m<br />
grid_y_min = -5*1000 # -10m<br />
#maze = [] # stack<br />
#grid_maze = [] # stack<br />
grid_size = 100 # 0.1m<br />
p.grid_size = grid_size<br />
r = laser.doProcessSimple(scan);<br />
k = 0<br />
if r:<br />
grid_x = []
grid_y = []
#for i in range(0, grid_x_max * 3):<br />
# maze.append([0,0])<br />
for i in range(grid_x_min, grid_x_max + grid_size, grid_size):<br />
grid_x.append(i)<br />
for i in range(grid_y_min, grid_y_max + grid_size, grid_size):<br />
grid_y.append(i)<br />
maze = [] # real-time delete<br />
grid_maze = [] # real-time delete<br />
</div>
<p>다음으로 lidar에서 받아온 pointcloud데이터와 현재위치 데이터를 얻어와 지도를 만드는 코드이다. 이때, pointcloud의 점의 거리가 현재 설정되어있는 격자공간보다 더 큰 거리를 가진다면, 격자공간을 넓히는 작업을 시행한다. 해당 부분의 코드이다.</p>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p><strong>map 생성 및 격자공간 확장 코드<br />
</strong></p>
<p>for point in scan.points:<br />
dis = int(point.range * 1000)<br />
if(dis &gt; 100):<br />
x = int(- p.odo_x * 1000 + dis * math.cos(point.angle))<br />
y = int(- p.odo_y * 1000 + dis * math.sin(point.angle))<br />
maze.append([-x,y])<br />
else:<br />
continue</p>
<p>before_gridx_max = grid_x_max<br />
before_gridy_max = grid_y_max<br />
before_gridx_min = grid_x_min<br />
before_gridy_min = grid_y_min<br />
if grid_x_max &lt; maze[k][0]:<br />
grid_x_max = maze[k][0] + (maze[k][0] % grid_size)<br />
if grid_y_max &lt; maze[k][1]:<br />
grid_y_max = maze[k][1] + (maze[k][1] % grid_size)<br />
if grid_x_min &gt; maze[k][0]:<br />
grid_x_min = maze[k][0] + (-maze[k][0] % grid_size)<br />
if grid_y_min &gt; maze[k][1]:<br />
grid_y_min = maze[k][1] + (-maze[k][1] % grid_size)</p>
<p>for i in range(before_gridx_min &#8211; grid_size, grid_x_min &#8211; 2 * grid_size, -grid_size):<br />
grid_x.insert(0, i)<br />
for i in range(before_gridy_min &#8211; grid_size, grid_y_min &#8211; 2 * grid_size, -grid_size):<br />
grid_y.insert(0, i)<br />
for i in range(before_gridx_max + grid_size, grid_x_max + grid_size, grid_size):<br />
grid_x.append(i)<br />
for i in range(before_gridy_max + grid_size, grid_y_max + grid_size, grid_size):<br />
grid_y.append(i)</p>
</div>
<p>다음은, 지도를 형성하는 점들이 하나의 격자에 여러 개가 찍힐 때, 그 격자만 벽으로 인식하게 하는 코드이다. 이 부분의 코드를 거치기 전에는 pointcloud데이터에 어떠한 가공도 하지 않은 지도였지만, 이 작업 후에는 격자에 맞게 지도가 그려진다.</p>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p><strong>지도의 격자화 코드</strong></p>
<p>px = 0<br />
py = 0<br />
for i in range (0, len(grid_x)-1):<br />
if(grid_x[i] &lt; maze[k][0] and grid_x[i+1] &gt;= maze[k][0]):<br />
px = grid_x[i] + grid_size / 2<br />
break<br />
for i in range (0, len(grid_y)-1):<br />
if(grid_y[i] &lt; maze[k][1] and grid_y[i+1] &gt;= maze[k][1]):<br />
py = grid_y[i] + grid_size / 2<br />
break<br />
for i in range (0, len(grid_x)-1):<br />
if(grid_x[i] &lt; p.odo_x*1000 and grid_x[i+1] &gt;= p.odo_x*1000):<br />
p.gridodo_x = grid_x[i] + grid_size / 2<br />
break<br />
for i in range (0, len(grid_y)-1):<br />
if(grid_y[i] &lt; p.odo_y*1000 and grid_y[i+1] &gt;= p.odo_y*1000):<br />
p.gridodo_y = grid_y[i] + grid_size / 2<br />
break</p>
<p>if [px,py] in grid_maze:<br />
grid_maze.remove([px,py])<br />
grid_maze.append([px,py])<br />
k = k + 1<br />
</div>
<p>다음으로는 A*알고리즘을 구현한 A_star.py 파이썬 모듈에 격자화된 map데이터를 넘겨주어 로봇이 움직여야 할 경로를 받아오고, 그 경로와 격자화된 map을 실시간으로 plot하는 부분이다.</p>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p><strong>경로생성 및 ploting</strong></p>
<p>data1 = Astar.a_star(grid_maze, grid_size)<br />
p.astar = data1<br />
map1 = np.array(maze)<br />
map2 = np.array(data1)</p>
<p>lidar_polar.clear()<br />
lidar_polar.set_ylim(-5000,5000)<br />
lidar_polar.set_xlim(-5000,5000)<br />
lidar_polar.grid(True)</p>
<p>lidar_polar.scatter(map1[:, 0],map1[:, 1], s=1, color=&#8217;blue&#8217;)<br />
lidar_polar.scatter(map2[:, 0],map2[:, 1], s=1, color=&#8217;red&#8217;)<br />
now = [p.gridodo_x , p.gridodo_y]
</div>
<p>다음으로는 생성된 경로에 맞게 로봇이 움직여야 할 Vx, Vy를 계산하는 부분이다. 경로는 계속해서 업데이트 되기 때문에, 현재 위치의 바로 다음 경로만을 파악하여 그 방향으로 속도를 결정한다. 현재 공간은 모두 격자화 되어있기 때문에, 현재 위치에서 로봇이 이동할 수 있는 방향은 총 8가지(위, 오른쪽, 왼쪽, 아래 오른쪽 위, 오른쪽 아래, 왼쪽 위, 왼쪽 아래)이다. 생성된 경로에서 현재위치와 가장 가까운 경로의 위치를 파악하고, 그 방향으로 속도를 결정한다. ex: 다음 경로가 오른쪽 위라면 Vx = 0.04, Vy = 0.04로 설정(0.04m/s로 로봇이 움직이게 만들어 놓음)<br />
해당 코드이다.</p>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p><strong>Vx, Vy 생성코드</strong></p>
<p>xvel = 0.04ㅍv<br />
yvel = 0.04<br />
if now in p.astar:<br />
for i in p.astar:<br />
if(abs(now[0] &#8211; i[0]) == p.grid_size or abs(now[1] &#8211; i[1]) == p.grid_size):<br />
vx = i[0] &#8211; now[0]
vy = i[1] &#8211; now[1]
if(vx &gt; 0):<br />
p.Vx = xvel<br />
if(vx &lt; 0):<br />
p.Vx = -xvel<br />
if(vy &gt; 0):<br />
p.Vy = yvel<br />
if(vy &lt; 0):<br />
p.Vy = -yvel<br />
if(vx == 0):<br />
p.Vx = 0<br />
if(vy == 0):<br />
p.Vy = 0<br />
</div>
<p>다음으로, 생성된 Vx, Vy, w (메카넘휠이므로 로봇 몸체의 회전 없이도 전 방향으로 이동할 수 있어 w를 따로 계산 하지는 않음)를 ros topic을 통해 ladarbot_node.cpp로 전송하는 코드이다.</p>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p><strong>Vx, Vy, w 전송 코드</strong></p>
<p>sab = geometry_msgs.msg.Twist()<br />
sab.linear.x = p.Vx<br />
sab.linear.y = p.Vy<br />
sab.angular.z = p.W<br />
pub.publish(sab)</p>
</div>
<p>이 외 다른 ros 송.수신 관련 코드는 talker.py의 전체 코드에서 확인할 수 있다.<br />
<strong></strong></p>
<p><span style="color: #33cccc"><strong>4.2.2. 최단 경로 찾기 알고리즘 &#8211; A star algorithm</strong></span><br />
A* 알고리즘이란 시작점과 도착점이 주어져 있을 때, 그 사이 벽, 장애물 등을 피해 최단 경로를 만들어주는 알고리즘이다. 원리를 간단하게 설명하자면, 격자화되어있는 map이 제공되었을 때 우선 어느 부분이 이동할 수 없는 지역이고 어느 부분이 이동가능한 지역인지를 파악한다. 본 프로젝트는 talker.py에서 격자화된 지도를 만들며 벽이 있는 부분의 격자에는 1을, 그렇지 않는 부분에는 0을 저장하여 구분하였다. A*알고리즘을 통해 어느 사각형(격자)들을 거쳐야 하는지를 파악함으로서 길을 찾는다. 경로가 발견되면 목표 도달때까지 사각형에서 다음 사각형의 중심으로 이동한다. 이 중심점들을 노드라고 부른다.<br />
시작점을 열린 목록이라는 일종의 배열에 저장한다. 시작점에 인접한 이동불가지역을 무시하고, 이동가능한 사각형들을 열린 목록에 저장한다. 그 후 열린 목적에서 시작점을 제거하고, 다시 볼 필요 없는 닫힌 목록에 넣는다. 열린목록에 저장되어있는 사각형들은 부모 노드로 시작점을 가리키도록 한다. 현재 열린 목록에 존재하는 사각형들 중에 하나를 선택하여 위 과정을 반복하는데, 이때 사각형을 선택하는데 기준이 되는 것이 비용함수이다. 비용함수는 다음과 같이 이루어져 있다.</p>
<p>F = G + H</p>
<p>G는 시작점으로부터 현재 사각형까지의 경로를 따라 이동하는데 소요되는 비용이다.<br />
H는 현재 사각형에서 목적지 B까지의 예상 이동 비용이다. 사이에 벽등으로 실제 거리는 알지 못하고, 그들을 무시하고 예상 거리를 산출한다.<br />
F는 현재까지 이동하는데 걸린 비용과 예상 비용을 합친 것이다.<br />
이렇게 구해진 비용함수를 통해 가장 작은 F를 가진 사각형을 선택한다. 이렇게 선택된 사각형을 열린 목록에서 빼고 닫힌 목록에 저장한다. 그 후 현재 사각형에서 인접한 사각형을 확인한다. 벽 등 이동불가사각형은 무시하고, 열린목록에 주변 사각형이 없다면 열린 목록에 추가한다. 이때 열린 목록에 추가된 사각형들은 현재 사각형을 부모로 설정한다. 인접한 사각형이 이미 열린 목록에 존재한다면 그 사각형과 선택된 사각형 중 어느 것의 G비용이 더 낮은지 확인한다. 기존 선택된 사각형의 G비용이 더 낮다면 그대로 두지만, 인접한 사각형의 G비용이 더 낮다면 인접 사각형들의 부모를 새로운 사각형으로 바꾼다. 마지막으로 그 사각형의 F와 G를 다시 계산한다. 이러한 과정을 반복한다. 이렇게 길을 찾는 도중 목표 사각형을 열린 사각형에 추가하였을 때, 또는 열린 목록이 비어있게 될 경우(길을 못찾은 경우) 탐색을 중단하고 목표 사각형으로부터 각각의 사각형의 부모사각형을 향하여 시작사각형에 도달할 때까지 거슬러 올라가 경로를 저장한다.</p>
<p>다음은 이를 파이썬으로 구현한 코드이다. 우선 node를 만드는 클래스를 소개하겠다. 각 사각형(노드)의 F,G,H값, 부모노드를 저장할 수 있는 공간을 만들어 두었다.</p>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p>class Node:<br />
def __init__(self, parent=None, position=None):<br />
self.parent = parent<br />
self.position = position</p>
<p>self.g = 0<br />
self.h = 0<br />
self.f = 0</p>
<p>def __eq__(self, other):<br />
return self.position == other.position<br />
</div>
<p>다음으로 A*알고리즘의 구현 코드이다.<br />
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p>def aStar(maze, start, end, grid_size_r):<br />
n = 1<br />
grid_size = grid_size_r / n<br />
startNode = Node(None, start)<br />
endNode = Node(None, end)</p>
<p>openList = []
closedList = []
<p>openList.append(startNode)</p>
<p>while openList:<br />
currentNode = openList[0]
currentIdx = 0<br />
for index, item in enumerate(openList):<br />
if item.f &lt; currentNode.f:<br />
currentNode = item<br />
currentIdx = index</p>
<p>openList.pop(currentIdx)<br />
closedList.append(currentNode)</p>
<p>if currentNode == endNode:<br />
path = []
current = currentNode<br />
while current is not None:</p>
<p>path.append(current.position)<br />
current = current.parent<br />
return path[::-1]
<p>children = []
<p>checking_list = [(0, -grid_size), (0, grid_size), (-grid_size, 0), (grid_size, 0), (-grid_size, -grid_size), (-grid_size, grid_size), (grid_size, -grid_size), (grid_size, grid_size)]
<p>for newPosition in checking_list:</p>
<p>nodePosition = (<br />
currentNode.position[0] + newPosition[0], # X<br />
currentNode.position[1] + newPosition[1]) # Y</p>
<p>state = 0<br />
if [nodePosition[0],nodePosition[1]] in maze:<br />
continue</p>
<p>for i in maze:<br />
dis_x = abs(nodePosition[0] &#8211; i[0])<br />
dis_y = abs(nodePosition[1] &#8211; i[1])</p>
<p>dis = dis_x**2 + dis_y**2<br />
if dis &lt; (4*grid_size)**2:<br />
state = 1<br />
break</p>
<p>if state == 1:<br />
continue<br />
new_node = Node(currentNode, nodePosition)<br />
children.append(new_node)</p>
<p>for child in children:<br />
if child in closedList:<br />
continue</p>
<p>child.g = currentNode.g + 1<br />
child.h = ((child.position[0] &#8211; endNode.position[0]) **<br />
2) + ((child.position[1] &#8211; endNode.position[1]) ** 2)<br />
child.f = child.g + child.h</p>
<p>if len([openNode for openNode in openList<br />
if child == openNode and child.g &gt; openNode.g]) &gt; 0:<br />
continue</p>
<p>openList.append(child)<br />
</div>
<p>코드 중간에</p>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p>for i in maze:<br />
dis_x = abs(nodePosition[0] &#8211; i[0])<br />
dis_y = abs(nodePosition[1] &#8211; i[1])</p>
<p>dis = dis_x**2 + dis_y**2<br />
if dis &lt; (4*grid_size)**2:<br />
state = 1<br />
break</p>
<p>if state == 1:<br />
continue<br />
</div>
이러한 부분이 존재한다. 위 부분은 A*알고리즘과 직접적인 연관은 없지만, 벽과 경로 사이 반드시 일정 거리를 유지하도록 한 코드이다. 최단경로가 아니더라도 벽과 부딪히지 않는 것이 더 중요하다고 판단하여 추가하였다.</p>
<p><span style="color: #ff9900"><strong>Vx, Vy, w 값을 받아와 아두이노로 넘겨주는 코드</strong></span><br />
원래 Vx,Vy,w를 생성하는 talker.py에서 아두이노로 바로 값을 전달해주는 것이 가장 간편하지만, 그 과정에서 계속해서 오류가 발생해 C++파일을 매개체로 하여 ROS로 연결하였다. 해당 역할을 하는 코드는 ladarbot_node.cpp이며, talker.py에서 Vx, Vy, w값을 ros topic을 통해 받아와 아두이노와 연결을 해주는 serial노드로 값을 전달한다. 다음은 해당 코드이다.</p>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p>#include&lt;sys/time.h&gt;<br />
#include&lt;chrono&gt;</p>
<p>#include&lt;ctime&gt; //time function<br />
#include&lt;cmath&gt;<br />
#include&lt;iostream&gt; //time function</p>
<p>using std::cout; using std::endl;<br />
using std::chrono::duration_cast;<br />
using std::chrono::milliseconds;<br />
using std::chrono::seconds;<br />
using std::chrono::system_clock;<br />
#include&lt;ros/ros.h&gt;<br />
#include&lt;std_msgs/Byte.h&gt; //std_msgs have geometry_msgs<br />
#include&lt;geometry_msgs/Twist.h&gt;<br />
//keyboard use: rosrun teleop_twist_keyboard teleop_twist_keyboard.py</p>
<p>float arr[3] = {0,0,0};<br />
void Callback(const geometry_msgs::Twist::ConstPtr&amp; msg)<br />
{<br />
arr[0] = msg-&gt;linear.x;<br />
arr[1] = msg-&gt;angular.z;<br />
arr[2] = msg-&gt;linear.y;<br />
ROS_INFO(&#8220;linear.x : %f&#8221;, msg-&gt;linear.x);<br />
ROS_INFO(&#8220;linear.y : %f&#8221;, msg-&gt;linear.y);<br />
ROS_INFO(&#8220;linear.z : %f&#8221;, msg-&gt;linear.z);<br />
ROS_INFO(&#8220;angular.x : %f&#8221;, msg-&gt;angular.x);<br />
ROS_INFO(&#8220;angular.y : %f&#8221;, msg-&gt;angular.y);<br />
ROS_INFO(&#8220;angular.z : %f&#8221;, msg-&gt;angular.z);<br />
}</p>
<p>double x = 0;<br />
double y = 0;<br />
double theta = 0;<br />
auto timer = 0;<br />
auto current = 0;<br />
double tot_time = 0;<br />
int main(int argc, char **argv)<br />
{<br />
struct timeval time_now{};<br />
gettimeofday(&amp;time_now, nullptr);</p>
<p>double dt = 0;<br />
//timer = clock();<br />
ros::init(argc, argv, &#8220;ladarbot_node&#8221;);<br />
ros::NodeHandle nh;<br />
ros::Subscriber ArduinoRos_sub = nh.subscribe(&#8220;cmd_vel2&#8243;,1000,Callback);</p>
<p>//ros::Publisher ArduinoRos_pub = nh.advertise&lt;std_msgs::Byte&gt;(&#8220;ArduinoTopic&#8221;, 1000);//<br />
ros::Publisher ArduinoRos_pub = nh.advertise&lt;geometry_msgs::Twist&gt;(&#8220;ArduinoTopic&#8221;, 1000);<br />
ros::Publisher ArduinoRos_pub2 = nh.advertise&lt;geometry_msgs::Twist&gt;(&#8220;/odometry&#8221;, 1000);<br />
ros::Rate rate(10);<br />
//std_msgs::Byte msg1;//<br />
geometry_msgs::Twist msg1;<br />
geometry_msgs::Twist msg2;<br />
ros::spinOnce();</p>
<p>while(ros::ok())<br />
{<br />
current = duration_cast&lt;milliseconds&gt;(system_clock::now().time_since_epoch()).count();<br />
msg1.linear.x = arr[0]; //Vx<br />
msg1.angular.z = arr[1]; //W<br />
msg1.linear.y = arr[2]; //Vy<br />
dt = (double)(current-timer) * 0.001;<br />
ROS_INFO(&#8220;%f&#8221;, dt);<br />
theta = theta + (arr[1]) * dt;<br />
x = x + (arr[0] * cos(theta) &#8211; arr[2] * sin(theta)) * dt;<br />
y = y + (arr[0] * sin(theta) + arr[2] * cos(theta)) * dt;<br />
tot_time = tot_time + dt;<br />
//ROS_INFO(&#8220;%f&#8221;, tot_time);</p>
<p>ROS_INFO(&#8220;x : %f&#8221;, x);<br />
ROS_INFO(&#8220;y : %f&#8221;, y);<br />
ROS_INFO(&#8220;theta : %f&#8221;, theta * (180/3.14));<br />
msg2.linear.x = x;<br />
msg2.linear.y = y;<br />
msg2.angular.z = theta;</p>
<p>ArduinoRos_pub.publish(msg1);<br />
ArduinoRos_pub2.publish(msg2);<br />
ros::spinOnce();<br />
rate.sleep();<br />
timer = current;//1<br />
}</p>
<p>return 0;<br />
}<br />
</div>
<p><span style="color: #ff9900"><strong>ROS 통한 연결 및 실행</strong></span><br />
앞서 소개했듯, A_star.py와 talker.py간 연결을 제외하면 모든 코드의 연결이 ROS로 연결되어 있다. talker.py는 ros_python package안에 존재하고, ladarbot_node.cpp는 ladarbot package안에 존재한다. 아두이노와 cpp파일 간 rosserial 연결을 도와주는 serial_node.py는 rosserial_python package안에 존재한다.<br />
실행하는 방법은 다음과 같다. 해당 명령어를 각각의 터미널 창에 입력하면 된다.</p>
<p>· sudo chmod a+rw /dev/ttyACM0 -&gt;포트 사용을 위해 유저에게 읽고 쓰는 권한 제공<br />
· roscore<br />
· rosrun ladarbot ladarbot_node<br />
· rosrun ros_python talker.py<br />
· rosrun rosserial_python serial_node.py _port:=/dev/ttyACM0 _baud:=57600</p>
<p><span style="color: #33cccc"><strong>4.2.3.사용자 조종을 통해 얻는 지도 데이터 txt저장 및 시각화</strong></span><br />
<strong><span style="color: #ff9900">로봇 현재위치 계산</span></strong><br />
자율주행을 구현하는데에도 통신 매개체로 쓰인 ladarbot_node.cpp가 여기에서도 사용된다. 이전에는 설명 안 했지만 ladarbot_node.cpp에도 로봇의 odometry(현재위치)를 계산하는 코드 부분이 있다. 코드 실행시간을 받아와 Vx, Vy, w와 곱하여 실시간으로 로봇의 현재 위치를 계산해 ros topic으로 lidar_range_node.cpp로 odometry를 전송한다. ladar_node.cpp의 해당 부분의 코드이다.</p>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p>while(ros::ok())<br />
{<br />
current = duration_cast&lt;milliseconds&gt;(system_clock::now().time_since_epoch()).count();<br />
msg1.linear.x = arr[0]; //Vx<br />
msg1.angular.z = arr[1]; //W<br />
msg1.linear.y = arr[2]; //Vy<br />
dt = (double)(current-timer) * 0.001;<br />
ROS_INFO(&#8220;%f&#8221;, dt);<br />
theta = theta + (arr[1]) * dt;<br />
x = x + (arr[0] * cos(theta) &#8211; arr[2] * sin(theta)) * dt;<br />
y = y + (arr[0] * sin(theta) + arr[2] * cos(theta)) * dt;<br />
tot_time = tot_time + dt;<br />
//ROS_INFO(&#8220;%f&#8221;, tot_time);</p>
<p>ROS_INFO(&#8220;x : %f&#8221;, x);<br />
ROS_INFO(&#8220;y : %f&#8221;, y);<br />
ROS_INFO(&#8220;theta : %f&#8221;, theta * (180/3.14));<br />
msg2.linear.x = x;<br />
msg2.linear.y = y;<br />
msg2.angular.z = theta;</p>
<p>ArduinoRos_pub.publish(msg1);<br />
ArduinoRos_pub2.publish(msg2);<br />
ros::spinOnce();<br />
rate.sleep();<br />
timer = current;//1<br />
}<br />
</div>
<p><span style="color: #ff9900"><strong>mapdata를 저장한 txt파일 도출</strong></span><br />
ladar_node.cpp와 ydlidar 오픈소스인 lidar.launch에서 각각 전송해준 odometry와 lidar센서값(pointcloud)를 이용해 합쳐 map을 생성하고, 그 맵을 txt파일로 만들어 저장해주는 코드이다. 해당 작업은 lidar_range_node.cpp에서 이루어졌으며, 아래는 우선 odometry를 받아오는 코드이다.</p>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p>void scanCallback2(const geometry_msgs::Twist::ConstPtr&amp; msg)<br />
{<br />
printf(&#8220;%f\t%f\t%f\n&#8221;, msg-&gt;linear.x, msg-&gt;linear.y, msg-&gt;angular.z); //degree, distance, x_pos, y_pos, theta<br />
arr[0] = msg-&gt;linear.x;<br />
arr[1] = msg-&gt;linear.y;<br />
arr[2] = msg-&gt;angular.z;<br />
}<br />
</div>
<p>다음은 lidar센서값을 lidar.launch에서 받아오는 부분의 코드이다.</p>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p>void scanCallback(const sensor_msgs::LaserScan::ConstPtr&amp; scan)<br />
{<br />
int count = scan-&gt;scan_time / scan-&gt;time_increment;<br />
//printf(&#8220;[YDLIDAR INFO]: I heard a laser scan %s[%d]:\n&#8221;, scan-&gt;header.frame_id.c_str(), count);<br />
//printf(&#8220;[YDLIDAR INFO]: angle_range : [%f, %f]\n&#8221;, RAD2DEG(scan-&gt;angle_min), RAD2DEG(scan-&gt;angle_max));</p>
<p>for(int i = 0; i &lt; count; i++) {<br />
float degree = RAD2DEG(scan-&gt;angle_min + scan-&gt;angle_increment * i);<br />
//printf(&#8220;[YDLIDAR INFO]: angle-distance : [%f, %f, %i]\n&#8221;, degree, scan-&gt;ranges[i], i);<br />
if (scan-&gt;ranges[i] &gt; 0.001){<br />
printf(&#8220;%f\t%f\n&#8221;, degree, scan-&gt;ranges[i]); //degree, distance, x_pos, y_pos, theta<br />
lidar[0][i] = degree;<br />
lidar[1][i] = scan-&gt;ranges[i];<br />
}<br />
/*if(i == count &#8211; 2)<br />
{<br />
lidar[1][i] = -1.0;<br />
}*/<br />
}<br />
}<br />
</div>
<p>다음은 두 정보를 합쳐 지도로 만든 뒤 txt파일로 내보내는 코드이다.</p>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p>int main(int argc, char **argv)<br />
{<br />
ofstream ofile;<br />
ofile.open(&#8220;slam.txt&#8221;);<br />
//string str = &#8220;haha&#8221;;<br />
//ofile.write(str.c_str(), str.size());</p>
<p>ros::init(argc, argv, &#8220;lidar_range_node&#8221;);<br />
ros::NodeHandle n;<br />
ros::Subscriber sub = n.subscribe&lt;sensor_msgs::LaserScan&gt;(&#8220;/scan&#8221;, 1000, scanCallback);<br />
ros::Subscriber sub2 = n.subscribe&lt;geometry_msgs::Twist&gt;(&#8220;/odometry&#8221;,1000, scanCallback2);<br />
ros::Rate rate(10);<br />
ros::spinOnce();<br />
while(ros::ok())<br />
{<br />
for(int i = 0;i &lt; 10000 ; i++)<br />
{<br />
if(lidar[1][i] != 0)<br />
ofile&lt;&lt;lidar[0][i]&lt;&lt;&#8221;\t&#8221;&lt;&lt;lidar[1][i]&lt;&lt;&#8221;\n&#8221;;<br />
}</p>
<p>ofile&lt;&lt;arr[0]&lt;&lt;&#8221;\t&#8221;&lt;&lt;arr[1]&lt;&lt;&#8221;\t&#8221;&lt;&lt;arr[2]&lt;&lt;&#8221;\n&#8221;;<br />
ros::spinOnce();<br />
rate.sleep();<br />
}<br />
ofile&lt;&lt;endl;<br />
ofile.close();<br />
return 0;<br />
}<br />
</div>
<p><span style="color: #ff9900"><strong>txt파일을 읽어 지도를 시각화하기</strong></span><br />
저장된 txt파일을 읽어 파이썬을 통해 시각화해주는 코드이다. 해당 작업을 한 코드는 slam_map.py이며, 설명할 부분이 거의 없는 간단한 코드이기 때문에 후에 전체 코드만 공개하겠다. slam_map.py를 실행하기전, txt파일을 열어 파일의 끝에 end를 추가해주어야 한다.</p>
<p><span style="color: #ff9900"><strong>실행하기</strong></span><br />
로봇을 조종하고 지도를 만들어 txt파일로 저장하기 위해서는 터미널 창에 다음과 같은 명령어를 입력해야 한다.</p>
<p>roslaunch ladarbot ladarbot.launch</p>
<p>ladarbot.launch는 ladarbot_node.cpp 및 아두이노 코드에 해당하는 ladarbot_subnode.ino, 그리고 로봇을 조종할 키보드 입력을 받아오는 코드를 한번에 실행하는 launch파일이다. 키보드 입력 받는 코드는 ros를 설치할 때 기본적으로 제공되는 파일이기에 따로 코드 설명을 하지는 않는다.<br />
그 다음 다른 터미널 창을 열어 해당 명령어를 입력한다.</p>
<p>roslaunch ydlidar_ros lidar.launch</p>
<p>lidar.launch파일을 실행하는 명령이다.<br />
다음으로 다른 터미널창을 열어 다음 명령어를 입력한다.</p>
<p>rosrun lidar_range lidar_range_node</p>
<p>해당 명령은 lidar_range_node.cpp파일을 실행하는 명령이다. 이 모든 명령을 실행하면 키보드의 I j l , u o m . 키로 로봇을 8방향으로 조종할 수 있게되며, 동시에 slam.txt파일이 생기며 그 파일안에 mapdata가 기록된다.<br />
txt파일을 시각화하고 싶다면 slam_map.py가 있는 경로에 slam.txt 파일을 복사한 다음, 터미널창에 python slam_map.py를 입력한다. 그럼 시각화된 지도를 확인할 수 있다.</p>
<p><span style="color: #ff6600"><strong>5. 실행 결과</strong></span><br />
<span style="color: #008080"><strong>5.1. 자율주행</strong></span><br />
목적지를 출발지로부터 x축으로 2.5m, y축으로 2.5m로 했을 때 동작 사진이다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_매카넘휠-7.png" rel="lightbox[40952]"><img class="alignnone size-large wp-image-41057" alt="68 ICT_매카넘휠 (7)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_매카넘휠-7-480x620.png" width="480" height="620" /></a></p>
<p><span style="color: #008080"><strong>5.2. 생성된 지도 txt파일로 저장 및 시각화</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_매카넘휠-8.png" rel="lightbox[40952]"><img class="alignnone size-full wp-image-41058" alt="68 ICT_매카넘휠 (8)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_매카넘휠-8.png" width="620" height="313" /></a></p>
<p><span style="color: #008080"><strong>5.3. 실행영상</strong></span><br />
실행영상을 추가 제출파일로 제출하였다. 해당 영상에서는 5.1에서처럼 로봇에 디스플레이를 연결한 것이 아닌, 영상 송수신기(브이젯 HDMI무선송수신기)를 통해 모니터로 화면을 띄웠다. 영상 마지막에 벽에 부딪히는 이유는 목적지를 출발점으로부터 x방향 2.5m, y방향 2.5m로 설정해 두었는데, 그 위치가 벽 속이여서 그러한 일이 발생한 것이다. 자율주행 자체에는 문제가 없다. 다만, 영상에서 보이는 정도의 거리는 문제없이 자율주행이 되지만, 주행시간을 늘리게 되면, 주행 중 zetson nano의 cpu가 다운되며 로봇이 정지하는 경우가 발생한다. A star 알고리즘 및 시각화알고리즘이 무거워 cpu다운이 일어나는 걸로 예상되며, 컴퓨터를 성능이 더 좋은 것으로 교체하면 해결될 문제로 보고 있다. 화면에 뜨는 map과 생성된 경로와, 로봇의 움직임을 함께 확인하면 된다.</p>
<p><span style="color: #ff6600"><strong>6. 사용한 제품 리스트</strong></span></p>
<table style="width: 620px" border="1" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td style="text-align: center">제품명</td>
<td style="text-align: center">디바 상품 번호</td>
</tr>
<tr>
<td style="text-align: center">스텝모터 드라이버 TB6600</td>
<td style="text-align: center">10894384</td>
</tr>
<tr>
<td style="text-align: center">lidar ydlidar X2L</td>
<td style="text-align: center">-</td>
</tr>
<tr>
<td style="text-align: center">jetson nano</td>
<td style="text-align: center">12513656</td>
</tr>
<tr>
<td style="text-align: center">arduino 및 케이블 [KEYES] 아두이노 우노 (R3) DIY 베이직 키트 [KT0030]</td>
<td style="text-align: center">-</td>
</tr>
<tr>
<td style="text-align: center">배터리 tattu 12000mah</td>
<td style="text-align: center">-</td>
</tr>
<tr>
<td style="text-align: center">display SZH-RP001</td>
<td style="text-align: center">1382391</td>
</tr>
<tr>
<td style="text-align: center">로봇 프레임(스텝모터, 메카넘휠 포함)<br />
wheel universal intelligent car chassis omni directional robot</td>
<td style="text-align: center">-</td>
</tr>
<tr>
<td style="text-align: center">통신장치 브이젯 HDMI무선송수신기</td>
<td style="text-align: center">-</td>
</tr>
</tbody>
</table>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
]]></content:encoded>
			<wfw:commentRss>http://www.ntrexgo.com/archives/40952/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>[68호]투명 폐페트병 분리배출 로봇</title>
		<link>http://www.ntrexgo.com/archives/40957</link>
		<comments>http://www.ntrexgo.com/archives/40957#comments</comments>
		<pubDate>Mon, 25 Oct 2021 00:00:32 +0000</pubDate>
		<dc:creator>디바이스마트 매거진</dc:creator>
				<category><![CDATA[디바이스마트 매거진]]></category>
		<category><![CDATA[특집]]></category>
		<category><![CDATA[ict]]></category>
		<category><![CDATA[ict공모전]]></category>
		<category><![CDATA[공모전]]></category>
		<category><![CDATA[디바이스마트]]></category>
		<category><![CDATA[매거진]]></category>
		<category><![CDATA[수상작]]></category>
		<category><![CDATA[융합]]></category>
		<category><![CDATA[프로젝트]]></category>

		<guid isPermaLink="false">http://www.ntrexgo.com/?p=40957</guid>
		<description><![CDATA[디바이스마트매거진 68호 &#124; 뚜껑과 라벨이 없는 무색의 플라스틱만을 모아서 재활용 효율 상승에 큰 도움을 주는 로봇을 제작하였다.]]></description>
				<content:encoded><![CDATA[<p><strong style="font-size: medium"><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-2.png" rel="lightbox[40957]"><img class="alignnone size-large wp-image-41118" alt="68 ict_ 투명페트분리배출 (2)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-2-620x191.png" width="620" height="191" /></a></strong> <span style="font-size: medium"><strong><br />
</strong></span></p>
<p><strong>2021 ICT 융합 프로젝트 공모전 우수상</strong></p>
<p><span style="font-size: x-large"><strong>투명 폐페트병 분리배출 로봇</strong></span></p>
<p style="text-align: right">글 | 숭실대학교 오세찬, 강전완, 윤형섭, 임소현</p>
<p>&nbsp;</p>
<p><span style="color: #ff6600"><strong>1. 심사평</strong></span><br />
칩센 작품을 개발 과정과 결과에도 나와있듯이 개발자들의 정형화된 시연 환경 내에서는 만족할만한 결과를 도출해 낼 수 있지만, 실제 개발 당사자가 아닌 이들이 사용을 한다면 수많은 문제점이 발생할 수 있을 것으로 보입니다. 보고서에 포함된 후속 연구를 통해 작품의 개선을 할 수 있기를 바랍니다.<br />
펌테크 자원재활용 이슈와 기술이 결합된 환경친화적인 작품이라고 생각합니다. 사전조사 및 아이디어가 반영된 제품 기획의 꼼꼼함이 돋보이며 영상처리 기술 구현 등을 효율적으로 접목하여 군더더기 없이 꼭 필요한 기능 위주로 목적에 맞는 최적의 시스템을 기획의도에 맞게 안정적으로 구현하였다고 생각합니다. 전체적으로 기술 구현도, 완성도 등에서 상당히 뛰어나고 훌륭한 작품으로 생각됩니다.<br />
위드로봇 처리 속도가 아쉬습니다. 나머지는 훌륭한 작품입니다.<br />
뉴티씨 투명 페트병만을 수거할 수 있도록하여, 투명하지 않은 페트병은 투명하게 만들어 투입하도록 하는 시스템인데, 매우 잘 제작되었습니다. 다만, 플라스틱이 아닌 금속캔 등을 투입하게 되면, 수거되지 않도록 안내하는 것도 추가하면 좋겠습니다. 검출 및 판별 속도는 개선해야 하는 점으로 보입니다. 또한, 수거되지 않는 것은 다시 가져갈 수 있도록 안내와 함께 기구적으로 문이 열리도록 하면 더 좋을 것 같습니다.<br />
엔티렉스 부설연구소 투명 플라스틱 병 분리 배출로봇 개발은 온전한 플라스틱이 아니면 힘든 것 같습니다. 다만 카메라를 이용하여 페트병을 인식하여 분리 배출하는 기능에 대해서는 많은 생각을 하여 개발 한 것 같습니다. 패트병이 아닌 다른 물체가 왔을 때 일반쓰레기로 처리하는 방식을 적용했으면 합니다.</p>
<p><span style="color: #ff6600"><strong>2. 작품 개요 </strong></span><br />
<span style="color: #3366ff"><strong>2.1. 배경 및 목적</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-3.png" rel="lightbox[40957]"><img class="alignnone size-full wp-image-41119" alt="68 ict_ 투명페트분리배출 (3)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-3.png" width="580" height="406" /></a></p>
<p>환경부에서는 2020년 12월 25일부터 투명 페트병만 분리수거 해 배출하는 정책을 펴고 있다. 하지만 기사를 통해 전용 봉투는 있지만, 그 안에 라벨과 뚜껑이 제거되지 않은 채로 페트병이 버려져 있는 사진이 공개되면서 아직 제대로 된 분리수거가 이루어지지 않음을 알 수 있다. 이러한 문제를 해결하고자 뚜껑과 라벨이 없는 무색의 플라스틱만을 모아서 재활용 효율 상승에 큰 도움을 주는 로봇을 제작하였다.</p>
<p><span style="color: #3366ff"><strong>2.2. 차별점 및 기대효과</strong></span><br />
재활용 문제를 해결하기 위한 시스템은 여러 가지가 있다. 촉각을 이용해 자동으로 분리수거를 하는 ‘로사이클’, 물건을 넣으면 인공지능이 재활용 가능 여부를 판단해 버려주는 자판기 형태의 ‘네프론’, 어플을 통해 직접 사진을 찍으면 그 물건의 분리수거 방법을 안내하는 ‘스마트사이클’ 등이 바로 그것이다.<br />
작품은 시중에 있는 다른 제품들과 크게 3가지 사항에서 차별성을 보인다. 첫 번째 차별점은 실용적인 면과 교육적인 면이 공존한다는 것이다. 투입구에 물건을 넣으면 로봇이 재활용 가능 여부를 판단하고, 재활용할 수 없다면 문제점과 해결방안을 디스플레이 화면과 음성안내 서비스를 통해 사용자에게 알려준다. 이를 통해 사람들은 본인의 재활용 인식 수준을 판단할 수 있고, 모르고 있었던 사항들을 알게 됨으로써 잘못된 행동을 개선할 수 있다. 두 번째는 로봇이 탈부착할 수 있다는 점이다. 이로 인해 공간의 큰 제약 없이 원하는 크기의 쓰레기통과 함께 사용할 수 있다. 이에 따라 기계를 열어 안에 있는 쓰레기들을 일일이 꺼내는 기존의 다른 제품들과 다르게, 봉투만 교체해서 사용하면 되므로 매우 편리하다. 세 번째는 제작 비용이 상대적으로 적다는 것이다. 값비싼 장치들과 센서들을 사용하지 않고도 필요한 기능들을 무리 없이 잘 구현해낼 수 있다.<br />
투명 폐페트병 분리배출 로봇을 사용함으로써 다음과 같은 기대효과가 예상된다. 우선, 환경부가 마련한 투명 페트병 분리수거 정책의 실행률 증가와 정착에 큰 도움이 될 것이다. 투명 페트병 분리배출의 증가로 불필요한 플라스틱의 폐기가 방지되고, 재활용률이 증가하여 경제적, 환경적으로 큰 공익이 발생할 것이다. 또한, 로봇이 디스플레이와 음성으로 올바른 플라스틱 재활용 방법에 대하여 즉각적으로 홍보함으로써 사람들의 투명 플라스틱 분리수거 및 재활용에 대한 인식이 크게 발전될 것이다.</p>
<p><span style="color: #ff6600"><strong>3. 작품 설명</strong></span><br />
<span style="color: #3366ff"><strong>3.1. 작품 구성도</strong></span></p>
<p><span style="color: #3366ff"><strong><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-4.png" rel="lightbox[40957]"><img class="alignnone size-full wp-image-41120" alt="68 ict_ 투명페트분리배출 (4)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-4.png" width="620" height="353" /></a></strong></span><br />
3개의 디바이스는 ‘감각기관’, ‘동작 기관’, ‘뇌’로 역할이 분담되어 있다. 각 디바이스는 MQTT 통신을 통해 상호작용하며 역할에 맞는 기능을 수행한다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-5.png" rel="lightbox[40957]"><img class="alignnone size-full wp-image-41121" alt="68 ict_ 투명페트분리배출 (5)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-5.png" width="620" height="457" /></a><br />
<strong>(1) 감각기관(ESP8266)</strong><br />
센서(무게 센서, 초음파센서)가 연결되어 있다. State에 따라 필요한 센서값을 측정해 동작 기관과 라즈베리파이에 전달해준다.</p>
<p><strong>(2) 동작기관(ESP8266)</strong><br />
동작 기관에는 서보모터가 연결되어 있다. 라즈베리파이에서 검출된 물체 명령에 따라 분리수거와 물체 반환을 하기 위한 모터작동을 수행한다.</p>
<p><strong>(3) 뇌(라즈베리파이)</strong><br />
뇌에는 카메라, 모니터, 스피커가 연결되어 있다. 라즈베리파이에서 구성한 웹서버가 카메라로 찍은 사진을 분석하고 과정을 모니터(웹 클라이언트)와 스피커로 출력해준다.</p>
<p><strong><span style="color: #3366ff">3.2. 주요 동작 및 특징</span></strong><br />
<span style="color: #33cccc"><strong>3.2.1. 로봇 구동과 물체 진입 확인</strong></span><br />
감각기관에 해당하는 ESP8266에 설계되어있다. 물체 진입 확인은 5가지 state로 구성된다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-6.png" rel="lightbox[40957]"><img class="alignnone size-full wp-image-41122" alt="68 ict_ 투명페트분리배출 (6)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-6.png" width="600" height="264" /></a></p>
<p><strong>init state(00)</strong><br />
처음 감각기관에 전원이 공급되면 init 상태가 된다. 이 상태에서는 사용하는 센서(무게 센서, 초음파센서)들의 pin을 초기화하고, 와이파이와 MQTT client(sensor)를 연결한다. wait 상태로의 이동은 라즈베리파이의 명령으로 이뤄진다.<br />
라즈베리파이의 웹서버는 서버가 실행되기 전 디바이스 간의 통신상태를 점검한다. 라즈베리파이가 보낸 ’reset’를 확인한 감각기관은 ‘connect’를 답장한다. 연결상태가 확인되면, 라즈베리파이는 ‘wait’를 보내고 서버를 실행한다. 메시지를 받은 감각기관은 ‘wait’ 상태로 이동한다.</p>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p>server mqtt connect / 라즈베리파이<br />
mqtt_client.on(&#8216;connect&#8217;, function () {<br />
mqtt_client.publish(&#8216;sensor&#8217;, &#8220;reset&#8221;);<br />
mqtt_client.publish(&#8216;motor&#8217;, &#8220;reset&#8221;);<br />
mqtt_client.subscribe(&#8216;raspi&#8217;, function (err) {<br />
if (!err) {<br />
console.log(&#8216;subscribe : raspi&#8217;);<br />
}<br />
})<br />
})<br />
</div>
<p>웹서버가 MQTT client로 연결되면 raspi를 구독하고, 감각기관과 동작기관에 reset이라는 메시지를 전송한다.</p>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p>server running / 라즈베리파이<br />
var health = {<br />
&#8216;sensor&#8217;: 0,<br />
&#8216;motor&#8217;: 0<br />
};<br />
var server_start = setInterval(function () {<br />
if (health.sensor == 1 &amp;&amp; health.motor == 1) {<br />
var server = http.createServer(app).listen(8080, function () {<br />
console.log(&#8216;server on&#8217;);<br />
});<br />
io = socketio(server);<br />
io.sockets.on(&#8216;connection&#8217;, function (socket) {<br />
socket.on(&#8216;sound&#8217;, function (data) {<br />
console.log(data);<br />
sound(data + &#8216;.mp3&#8242;);<br />
})<br />
});<br />
clearInterval(server_start);<br />
} else{<br />
(health.sensor!=1)?mqtt_client.publish(&#8216;sensor&#8217;,&#8221;reset&#8221;)<br />
:mqtt_client.publish(&#8216;motor&#8217;, &#8220;reset&#8221;);<br />
console.log(&#8220;not connected&#8221;);<br />
}<br />
}, 1000)<br />
</div>
<p>health 변수에 key값인 sensor, motor는 서버 실행 전 감각기관, 동작기관의 통신상태 정보를 담고 있다. 1000(1초)마다 실행되는 함수를 만들어 연결상태를 점검하고 잘 연결되었다면, 웹서버와 동적인 사용자 인터페이스에 필요한 소켓 서버를 실행시키고, 감각기관에게 ‘wait’ 메시지를 보내준다. 연결상태가 올바르지 않다면, 해당 디바이스에 상태를 확인하는 reset 메시지를 재전송한다.</p>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p>connect message receive / 라즈베리파이<br />
mqtt_client.on(&#8216;message&#8217;, function (topic, message) {<br />
if (message == &#8220;input&#8221;) {<br />
// 물체 진입코드<br />
} else if (String(message).split(&#8216;-&#8217;)[0] == &#8216;connect&#8217;) {<br />
console.log(String(message));<br />
health[String(message).split('-')[1]] = 1;<br />
} else {<br />
// 물체 무게 받는 코드<br />
}<br />
})<br />
</div>
<p>‘-‘를 기준으로 split 하여 감각기관, 동작기관 중 연결상태라 올바른 디바이스에 health 값을 1로 갱신하는 코드이다.</p>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p>connect msg / 감각기관<br />
void callback(char* topic, byte* payload, unsigned int length) {<br />
String msg = &#8220;&#8221;;<br />
for (int i = 0; i &lt; length; i++) {<br />
msg +=(char)payload[i];<br />
}</p>
<p>if(msg == &#8220;reset&#8221;){<br />
client.publish(&#8220;raspi&#8221;,&#8221;connect-sensor&#8221;);<br />
}<br />
else if(msg == &#8220;wait&#8221;){<br />
state = 1;<br />
}<br />
}<br />
</div>
‘reset’ 메시지를 받으면 연결상태인 connect-sensor를 보내주고, ‘wait’ 메시지를 받으면 ‘wait’ 상태로 이동한다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-7.png" rel="lightbox[40957]"><img class="alignnone size-full wp-image-41123" alt="68 ict_ 투명페트분리배출 (7)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-7.png" width="578" height="537" /></a></p>
<p><strong>wait state(01)</strong><br />
문이 열리길 기다리는 상태. 물체를 집어넣는 작품 상단에 있는 초음파센서가 문과의 거리를 측정한다. 센서값을 분석(문 열림)하여 문이 열렸음이 확인되면 ‘open’ 상태로 이동한다.</p>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p>wait -&gt; open state / 감각기관<br />
int get_distance(){<br />
int duration, distance;<br />
digitalWrite(trig, LOW);<br />
delayMicroseconds(10);<br />
digitalWrite(trig, HIGH);<br />
delayMicroseconds(10);<br />
digitalWrite(trig, LOW);<br />
duration = pulseIn(echo, HIGH);<br />
distance = duration * 170 / 1000;<br />
return distance;<br />
}<br />
// loop내 코드<br />
int distance = get_distance();<br />
if(state == 1 &amp;&amp;(200 &lt; distance || 180 &gt; distance)){<br />
state++; // open state로 이동<br />
}<br />
</div>
<p>거리를 측정하는 distance()를 전역함수로 작성하여 loop 내에서 필요에 따라 호출하도록 하였다. 문이 열릴 때 반사되는 초음파센서값이 열리기 전의 값(190)보다 값이 작다는 것을 이용하여 문이 열렸음을 확인한다. 추가로 200 이상의 값도 열렸다고 판단하는 이유는 초음파가 반사되어 200 이상의 숫자도 나올 수 있기 때문이다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-8.png" rel="lightbox[40957]"><img class="alignnone size-full wp-image-41124" alt="68 ict_ 투명페트분리배출 (8)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-8.png" width="582" height="170" /></a><br />
<strong></strong></p>
<p><strong>open state(10)</strong><br />
문이 열린 상태. 초음파센서는 문과의 거리를, 무게 센서는 물체의 무게를 측정한다. 두 센서값을 분석(문 닫힘, 물체무게감지)하여 정상적으로 물체가 들어왔으면 ‘close’ 상태로 이동한다.</p>
<p><strong>close state(11)</strong><br />
물체가 들어온 상태. 라즈베리파이에게 물체가 들어왔음(input)과 물체의 무게(weight)값을 전송한다. 메시지를 수신한 라즈베리파이는 물체 분석을 시작하고 ‘analysis’ 답장을 보낸다. 답장을 받은 감각기관은 analysis 상태로 이동한다.</p>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p>open -&gt; close state / 감각기관<br />
else if(state == 2 &amp;&amp; (190 &gt;= distance &amp;&amp; 185 &lt;= distance) &amp;&amp; (scale.read()/1000 &gt;= 330)){<br />
state++; // close state로 이동<br />
client.publish(&#8220;raspi&#8221;,&#8221;input&#8221;);<br />
delay(1000);<br />
String packet = String(scale.read()); // weight 전송<br />
packet.toCharArray(weight,50);<br />
client.publish(&#8220;raspi&#8221;,weight);<br />
}<br />
</div>
<p>초음파센서로 측정한 값이 185 이상 190 이하이고 무게 센서로 측정한 값을 1000으로 나눈 값이 330 이상이면 물체가 정상적으로 들어왔다고 판단하여 ‘close’ 상태로 이동한다. close 상태로 이동 후 라즈베리파이에게 잘 들어왔음을 알리는 ‘input’과 물체의 무게 값인 ‘weight’ 값을 전송한다.</p>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p>분석 시작코드 / 라즈베리파이<br />
mqtt_client.on(&#8216;message&#8217;, function (topic, message) {<br />
if (message == &#8220;input&#8221;) {<br />
mqtt_client.publish(&#8216;sensor&#8217;, &#8220;analysis&#8221;);<br />
io.sockets.emit(&#8216;counter&#8217;, 3);<br />
sound(&#8220;camera.mp3&#8243;);<br />
} else if (String(message).split(&#8216;-&#8217;)[0] == &#8216;connect&#8217;) {<br />
// 초기 설정 관련<br />
} else {<br />
weight = parseInt(message);<br />
}<br />
})</p>
</div>
input 메시지가 도착하면, 감각기관은 analysis 상태로 만들기 위해 ‘analysis’ 메시지를 전송하고, 물체 분석을 시작한다.</p>
<p><strong>analysis 상태(100)</strong><br />
물체가 분석 중인 상태. 분석이 정상적으로 끝나고, 모터 동작에 대한 메시지가 수신될 때까지 대기한다.<br />
<strong></strong></p>
<p><span style="color: #33cccc"><strong>3.2.2. 투명 폐페트병 검출 기능 </strong></span><br />
뇌에 해당하는 라즈베리파이에 투명 폐페트병을 검출하는 모델이 3단계로 설계되어있다. 아래 표는 대략적인 흐름과 각 검출 단계에서 사용되는 분석 도구들을 보여준다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-9.png" rel="lightbox[40957]"><img class="alignnone size-full wp-image-41125" alt="68 ict_ 투명페트분리배출 (9)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-9.png" width="583" height="609" /></a></p>
<p><strong>카메라 촬영</strong><br />
node.js의 pi-camera 모듈을 사용하여 라즈베리용 카메라를 조작한다. 사진 촬영 후 정상적으로 사진이 저장되면, 성분분석 단계로 넘어간다.</p>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<strong>Box Content</strong><br />
</div>
<p>웹 클라이언트로부터 /camera 요청을 받으면, snap 함수를 사용해 라즈베리파이용 카메라를 동작시킨다. 사진이 제대로 찍혔으면, 첫 분석방법인 성분 분석 함수를 호출한다.</p>
<p><strong>성분분석</strong><br />
google-cloud에서 제공해주는 vision api를 사용한다. 들어온 물체의 크기, object 무게 등을 측정하여 플라스틱인지 검출하는 단계이다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-10.png" rel="lightbox[40957]"><img class="alignnone size-full wp-image-41126" alt="68 ict_ 투명페트분리배출 (10)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-10.png" width="292" height="506" /></a></p>
<p><strong>ⓐ 크기 검사</strong><br />
이미지 분석 데이터를 분석하여 물체의 유무를 측정한다. 물체의 좌표에 해당하는 픽셀들을 추출하여 만약 추출되지 않았으면 물체가 인식되지 않았다고 판단하고 object_test 변수를 false로 변경한다.<br />
<strong>ⓑ 오브젝트 검사</strong><br />
이미지 분석 데이터를 분석하여 해당하는 물체가 플라스틱인지 판단한다. [bottle, drink, plastic, glass]의 키워드가 하나도 나오지 않으면 페트병이 아니라고 인식해 object_test 변수를 false로 변경한다.<br />
<strong>ⓒ 무게 검사</strong><br />
esp8266(감각기관)에서 물체 진입 확인과 함께 전달된 물체의 무게를 통해 플라스틱 bottle의 무게인지 검사한다. 이전검사에서 유리와 플라스틱을 구분하지 못했는데, 이 단계에서 분류된다. 추가로 이물질 특히 내용물이 담겨있는 경우도 이 검사에서 걸러진다.<br />
<strong>ⓓ label_test 값 확인</strong><br />
ⓐ~ⓒ단계에서 기록된 object_test 값을 확인하여 false이면 물체를 반환하기 위해 모터에게 메시지(up)를 보내고, true이면 다음 단계인 글자 분석 단계로 이동한다.</p>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p>성분 분석 코드 / 라즈베리파이(서버)<br />
var weight = 0;<br />
var area;<br />
async function object_search() {<br />
var object_test = false;<br />
mqtt_client.publish(&#8216;sensor&#8217;, &#8220;get_weight&#8221;);<br />
const [result2] = await client.objectLocalization(&#8216;test.jpg&#8217;);<br />
try {<br />
result2.localizedObjectAnnotations[0].boundingPoly.normalizedVertices;<br />
const object =<br />
result2.localizedObjectAnnotations[0].boundingPoly.normalizedVertices;<br />
area = {<br />
xs: parseInt(object[0].x * 640),<br />
xe: parseInt(object[1].x * 640),<br />
ys: parseInt(object[1].y * 480),<br />
ye: parseInt(object[3].y * 480)<br />
};<br />
io.sockets.emit(&#8216;area&#8217;, area);<br />
} catch (e) {<br />
console.log(&#8220;물체 감지X&#8221;);<br />
}<br />
const [result] = await client.labelDetection(&#8216;test.jpg&#8217;);<br />
const objects = result.labelAnnotations;</p>
<p>console.log(&#8216;성분분석중&#8217;);<br />
objects.forEach(function (objects, index) {<br />
io.sockets.emit(&#8216;ingredient_label&#8217;, objects.description);<br />
if (objects.description.indexOf(&#8216;bottle&#8217;) != -1 ||<br />
objects.description.indexOf(&#8216;Bottle&#8217;) != -1 ||<br />
objects.description.indexOf(&#8216;Drink&#8217;) != -1 ||<br />
objects.description.indexOf(&#8216;drink&#8217;) != -1 ||<br />
objects.description.indexOf(&#8216;Plastic&#8217;) != -1 ||<br />
objects.description.indexOf(&#8216;Glass&#8217;) != -1) {<br />
object_test = true;<br />
}<br />
});<br />
console.log(&#8216;성분분석완료&#8217;);<br />
if (object_test == true &amp;&amp; weight &lt; 420000) {<br />
io.sockets.emit(&#8216;complete&#8217;, &#8216;성분분석완료-성공-1&#8242;);<br />
setTimeout(function () {<br />
text_search(result);<br />
}, 3000)<br />
} else {<br />
io.sockets.emit(&#8216;complete&#8217;, &#8216;성분분석완료-실패-1&#8242;);<br />
mqtt_client.publish(&#8216;motor&#8217;, &#8220;up&#8221;);<br />
}<br />
}</p>
</div>
성분 분석 결과를 나타내는 object_test 변수를 false로 초기화하고 분석 결과에 따라 값을 할당한다. vision api에 objectLocalization 함수를 통해 물체의 위치 픽셀을 리턴 받는다. 리턴 받은 값이 있다면, 그 물체의 위치를 저장하는 area 변수에 저장하고, 없다면 catch 문을 사용해 물체가 없음이 출력된다. 다음으로 labelDetection 함수를 통해 object의 성분을 파악한다. [bottle, drink, plastic, glass]라는 키워드가 있다면, object_test 값을 true로 변경한다. 성분분석이 완료되고, glass와 plastic을 구분하기 위해 무게 값을 측정하고 최종적으로 모터에게 결과 메시지를 전송한다.</p>
<p><strong>글자 분석</strong><br />
google-cloud vision API를 사용한다. 폐페트병에 프린팅이 되어있거나 글자가 있는지 판단하는 단계이다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-11.png" rel="lightbox[40957]"><img class="alignnone size-full wp-image-41127" alt="68 ict_ 투명페트분리배출 (11)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-11.png" width="290" height="362" /></a></p>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p>글자 분석 코드 / 라즈베리파이(서버)<br />
async function text_search() {<br />
console.log(&#8220;글자분석중&#8221;);<br />
var text_test = true;<br />
const [result] = await client.textDetection(&#8216;test.jpg&#8217;);<br />
const detections = result.textAnnotations;<br />
detections.forEach(function (text, index) {<br />
io.sockets.emit(&#8216;ingredient_text&#8217;, text.description);<br />
if (text.description) {<br />
text_test = false;<br />
}<br />
console.log(text.description);<br />
});<br />
console.log(&#8220;글자분석완료&#8221;);<br />
if (text_test == true) {<br />
io.sockets.emit(&#8216;complete&#8217;, &#8216;글자분석완료-성공-2&#8242;);<br />
setTimeout(function () {<br />
color_search();<br />
}, 3000)<br />
} else {<br />
io.sockets.emit(&#8216;complete&#8217;, &#8216;글자분석완료-실패-2&#8242;);<br />
mqtt_client.publish(&#8216;motor&#8217;, &#8220;up&#8221;);<br />
}<br />
}<br />
</div>
<p><strong>ⓐ 글자 검사</strong><br />
이미지 데이터 중 text 배열을 선택해 요소가 있는지 확인한다. 만약 요소가 하나라도 존재하면 label이 붙어있거나 프린팅이 되어있는 플라스틱으로 판단하고 text_test 변수를 false로 변경한다.<br />
<strong>ⓑ text_test 값 확인</strong><br />
ⓐ단계에서 기록된 text_test 변수의 값을 확인하여 fasle이면 물체를 반환하기 위해 모터에게 메시지(up)를 보내고, true이면 다음 단계인 색깔 분석 단계로 이동한다.<br />
글자 분석 결과를 나타내는 text_test 변수를 false로 초기화하고 분석 결과에 따라 true 값을 할당한다. vision api의 textDetection함수를 이용해 이미지에서의 식별된 글자들을 리턴받고, 이를 분석한다. 분석결과를 text_test 변수에 기록하고, 이를 토대로 모터를 작동시킨다.</p>
<p><strong>색깔 분석</strong><br />
이미지를 RGB matrix로 만들어주는 image-to-rgba-matrix 모듈을 사용해 픽셀 단위로 RGB 값을 분석하여 폐페트병의 이물질 여부를 판단하는 단계이다.<br />
<strong>ⓐ 영역설정</strong><br />
성분분석에서 얻은 물체 픽셀 데이터를 이용해 분석 영역을 설정한다. 영역을 설정함으로써 투명 폐페트병과 배경색만 검출된다.<br />
<strong>ⓑ 표준편차</strong><br />
각 픽셀의 R, G, B값을 추출해 표준편차를 계산해 1차 분류를 진행한다. 표준편차가 15 초과면 유채색(빨강, 초록, 파랑), 15 이하면 무채색(검은, 흰)으로 분류한다.<br />
<strong></strong></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-12.png" rel="lightbox[40957]"><img class="alignnone size-large wp-image-41128" alt="68 ict_ 투명페트분리배출 (12)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-12-472x620.png" width="472" height="620" /></a></p>
<p><strong>ⓒ RGB 연산</strong><br />
1차 분류를 통해 나눠진 데이터를 토대로 각각 RGB 연산을 수행한다. 유채색의 경우 R, G, B값 중 큰 값을 찾아 Red, Green, Blue로 분류하고 무채색의 경우 R, G, B의 평균을 계산하여 40 이하인 것을 검은색. 200 이상인 것을 흰색으로 분류한다. 흰색은 연산하는 과정에서 영역(뚜껑, 몸통)별로 가중치를 부여해 데이터가 저장되는데, 이러한 이유는 백색 조명이 반사되어 나타나는 흰색이 몸통에 주로 위치하기 때문이다.<br />
<strong>ⓓ 분류 연산 </strong><br />
2차 분류된 데이터를 토대로 각 색깔의 개수를 측정한다. Red, Green, Blue, Black의 경우 10픽셀 이상 있으면 이물질이 있다고 판단한다. White의 경우 보정된 값이 5000 이상이면 흰색 이물질이라고 판단한다. 5000은 여러 번의 테스트를 거쳐 얻은 값이다. 이물질 판단 여부를 통해 color_test 값을 결정한다.<br />
<strong>ⓔ color_test 값 확인</strong><br />
ⓐ~ⓓ단계에서 기록된 text_test 변수의 값을 확인하여 false이면 물체를 반환하기 위해 모터에게 메시지를 보낸다. true이면 최종적으로 투명한 폐페트병이라고 판단하여 물체를 분리수거 하기 위해 모터에게 메시지(down)을 전송한다.</p>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p>색깔 분석코드 / 라즈베리파이(서버)<br />
function color_search() {<br />
console.log(&#8220;색깔분석중&#8221;);<br />
// 변수 초기화<br />
var color_test = true;<br />
var pal = {<br />
&#8216;white&#8217;: 0,<br />
&#8216;black&#8217;: 0,<br />
&#8216;red&#8217;: 0,<br />
&#8216;green&#8217;: 0,<br />
&#8216;blue&#8217;: 0<br />
}<br />
pal['white']['state'] = 0;</p>
<p>imageToRgbaMatrix(&#8216;test.jpg&#8217;).then(function (color) {<br />
color.forEach(function (color, row) {<br />
color.forEach(function (color, col) {<br />
// 특정 영역 검색<br />
if (col &gt;= area.xs &amp;&amp; col &lt;= area.xe<br />
&amp;&amp; row &gt;= 115 &amp;&amp; row &lt;= area.ye) {<br />
// 표준편차 구하기<br />
var average =<br />
(parseInt(color[0])+parseInt(color[1])+parseInt(color[2])) / 3<br />
var sum = 0;<br />
for (j = 0; j &lt; 3; j++) {<br />
sum += Math.pow(color[j] &#8211; average, 2);<br />
}<br />
var std = parseInt(Math.sqrt(sum / 3))</p>
<p>// 표준편차를 이용한 색구분<br />
if (std &lt;= 15) {<br />
if (average &gt; 200) {<br />
if ((col &lt; area.xs + 50) || (col &gt; area.xe + 50)) {<br />
pal['white'] += 300;<br />
} else {<br />
pal['white']++;<br />
}<br />
} else if (average &lt; 40) {<br />
pal['black']++;<br />
}<br />
} else {<br />
if (color[0] &gt; color[1] &amp;&amp; color[0] &gt; color[2]) {<br />
pal['red']++;<br />
} else if (color[1] &gt; color[2]) {<br />
pal['green']++;<br />
} else {<br />
pal['blue']++;<br />
}<br />
}<br />
}<br />
});<br />
});<br />
Object.keys(pal).forEach(function (key) {<br />
console.log(key + &#8216;:&#8217; + pal[key]);<br />
if (key == &#8216;white&#8217; ? pal[key] &gt; 5000 : pal[key] &gt;= 10) {<br />
color_test = false;<br />
console.log(key + &#8220;검출&#8221;)<br />
io.sockets.emit(&#8216;ingredient_color&#8217;, key);<br />
}</p>
<p>});<br />
console.log(&#8220;색깔분석완료&#8221;);<br />
if (color_test == true) {<br />
setTimeout(function () {<br />
io.sockets.emit(&#8216;complete&#8217;, &#8216;색깔분석완료-성공-3&#8242;);<br />
mqtt_client.publish(&#8216;motor&#8217;, &#8220;down&#8221;);<br />
}, 2000)<br />
} else {<br />
io.sockets.emit(&#8216;complete&#8217;, &#8216;색깔분석실패-실패-3&#8242;);<br />
mqtt_client.publish(&#8216;motor&#8217;, &#8220;up&#8221;);<br />
}<br />
});<br />
}</p>
</div>
<p>색깔분석 결과를 나타내는 color_test 변수를 false로 초기화하고 분석 결과에 따라 true 값을 할당한다. imageToRgbaMatrix로 이미지를 RGBA 성분을 가지고 있는 2차원 배열로 리턴 받는다. 이를 성분분석에서 연산한 area를 토대로 색깔을 분류하고, pal 변수에 key로 지정된 색깔에 count 한다. 분류 작업을 마친 후 foreach문을 통해 색깔의 count 값을 비교하여 분석결과를 color_test 변수에 저장하고 이를 토대로 모터를 작동시킨다.</p>
<p><span style="color: #33cccc"><strong>3.2.3. 분리수거</strong></span><br />
동작 기관 ESP8266에서는 서보모터를 제어하여 분리수거와 반환 기능을 수행한다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-13.png" rel="lightbox[40957]"><img class="alignnone size-large wp-image-41129" alt="68 ict_ 투명페트분리배출 (13)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-13-581x620.png" width="581" height="620" /></a></p>
<p><span style="color: #cc33b6"><strong>명령 수신</strong></span><br />
라즈베리파이에서 투명 폐페트병 검출 기능이 수행된 후 분리수거가 가능 여부에 따라 UP(반환), Down(분리수거) 메시지를 동작기관으로 전송한다.</p>
<p><span style="color: #cc33b6"><strong>모터 동작</strong></span><br />
메시지를 받은 동작 기관은 메시지에 따라 서보모터를 제어해 물체 반환(UP)과 분리수거 (Down)을 수행한다. 그리고 감각기관으로 현재 모터의 상태인 motor_up과 motor_down을 전달한다. 감각기관의 도움을 받는 이유는 서보모터 자체 함수 중 각도를 리턴 받는 함수가 없어, 기능이 제대로 수행되었는지 알 수 없기 때문이다.</p>
<p><span style="color: #cc33b6"><strong>감각기관 상태 이동</strong></span><br />
모터 동작에 대한 메시지를 전달받은 감각기관은 메시지에 따라 analysis 상태에서 motor_up, motor_down 상태로 이동한다.</p>
<p><span style="color: #cc33b6"><strong>분리수거 및 반환 완료 검사</strong></span><br />
서보모터를 통해 틀이 움직이면 감각기관에 무게 센서값이 기준치에서 크게 변화한다. 즉 motor_up(반환), motor_down(분리수거) 상태에서 특정 무게 값을 정하여 기준치에 충족하면 반환, 분리수거가 완료되었다고 판단한다. 추가로 motor_up(반환)의 경우 물체가 가벼워 문을 뚫고 나오지 못하는 것을 고려하여 초음파센서값도 변수로 지정하였다. 초음파센서값을 통해 물체가 감지되지 않으면(물체 반환이 완료되면) 동작 기관에 reset을 명령한다.</p>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p>전달받은 메시지에 따른 함수 호출 / 동작기관(모터)<br />
void callback(char* topic, byte* payload, unsigned int length) {<br />
String msg = &#8220;&#8221;;<br />
for (int i = 0; i &lt; length; i++) {<br />
msg +=(char)payload[i];<br />
}<br />
Serial.print(&#8220;message : &#8220;);<br />
Serial.print(msg);<br />
if(msg == &#8220;up&#8221;){<br />
motor_up();<br />
}<br />
else if(msg == &#8220;down&#8221;){<br />
motor_down();<br />
}<br />
else if(msg == &#8220;reset&#8221;){<br />
motor_reset();<br />
client.publish(&#8220;raspi&#8221;,&#8221;connect-motor&#8221;);<br />
}<br />
}<br />
</div>
<p>‘up’ 메시지를 받으면 반환을, ‘down’ 메시지를 받으면 분리수거를 시작한다.</p>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p>모터 제어 함수들 / 동작기관(모터)<br />
void motor_up(){<br />
servo1.write(180);<br />
servo2.write(0);<br />
client.publish(&#8220;sensor&#8221;,&#8221;motor_up&#8221;);<br />
}<br />
void motor_down(){<br />
servo1.write(0);<br />
servo2.write(180);<br />
client.publish(&#8220;sensor&#8221;,&#8221;motor_down&#8221;);<br />
}</p>
<p>void motor_reset(){<br />
servo1.write(90);<br />
servo2.write(90);<br />
}<br />
</div>
<p>전역변수로 선언된 3개의 모터제어 함수들. 라즈베리파이에서 분리수거, 반환이 시작되면 현재 수행한 함수를 감각기관에게 메시지로 전달한다. 이는 무게 센서를 통한 서보모터 동작 완료를 확인하기 위함이다.</p>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p>모터 메시지에 따른 상태 변화<br />
감각기관(센서)<br />
void callback(char* topic, byte* payload, unsigned int length) {<br />
else if(msg == &#8220;motor_up&#8221;){<br />
state = 5; // motor_up 상태로 이동<br />
}<br />
else if(msg = &#8220;motor_down&#8221;){<br />
state = 6; // motor_down 상태로 이동<br />
}<br />
}<br />
</div>
<p>모터에서 받은 메시지에 따라 각각 motor_up state와 motor_down state로 상태 이동한다.</p>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p>모터 동작 완료 확인 / 감각기관(센서)<br />
else if(state ==5 &amp;&amp; (190 &gt;= distance &amp;&amp; 185 &lt;= distance) &amp;&amp; (scale.read()/1000 &lt;188) ){<br />
client.publish(&#8220;motor&#8221;,&#8221;reset&#8221;);<br />
state = 1; // wait 상태로 이동<br />
}<br />
else if(state ==6 &amp;&amp; (scale.read()/1000 &lt;0) ){<br />
client.publish(&#8220;motor&#8221;,&#8221;reset&#8221;);<br />
state = 1; // wait 상태로 이동<br />
}<br />
</div>
motor_up과 motor_down 상태에서는 무게 센서와 초음파센서값을 측정하여 분리수거와 반환이 완료됐는지 확인한다. 정상적으로 처리가 끝났으면 감각기관의 상태를 wait 상태로 이동하고, 모터에게 ‘reset’ 메시지를 보내준다. ‘reset’ 메시지를 받은 모터는 모터를 초기화시킨다.</p>
<p><span style="color: #33cccc"><strong>3.2.4. 사용자 인터페이스</strong></span><br />
<span style="color: #cc33b6"><strong>디스플레이</strong></span></p>
<p><span style="color: #cc33b6"><strong><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-14.png" rel="lightbox[40957]"><img class="alignnone size-full wp-image-41130" alt="68 ict_ 투명페트분리배출 (14)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-14.png" width="577" height="526" /></a></strong></span><br />
<strong>ⓐ 메인화면</strong><br />
메인화면은 2가지 표정으로 구분된다. 재활용을 성공한 경우나 처음 로봇이 구동된 경우 Happy, 재활용에 실패해 물체가 반환된 경우 Angry 캐릭터가 메인화면에 나타난다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-15.png" rel="lightbox[40957]"><img class="alignnone size-full wp-image-41131" alt="68 ict_ 투명페트분리배출 (15)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-15.png" width="581" height="229" /></a></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-16.png" rel="lightbox[40957]"><img class="alignnone size-full wp-image-41132" alt="68 ict_ 투명페트분리배출 (16)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-16.png" width="581" height="229" /></a><br />
메인화면에서 오른쪽 위 끝에 있는 설명 버튼을 누르면, 재활용 방법과 로봇에서 폐페트병을 검출하는 방법을 소개한다.</p>
<p><strong>ⓑ 분석화면</strong><br />
3단계의 분석 진행 과정과 결과를 우측에 실시간으로 렌더링하여 보여준다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-17.png" rel="lightbox[40957]"><img class="alignnone size-full wp-image-41133" alt="68 ict_ 투명페트분리배출 (17)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-17.png" width="583" height="618" /></a></p>
<p><span style="color: #cc33b6"><strong>음성 출력</strong></span><br />
google TTL 서비스를 통해 음성을 추출하였고, 서버 측에서 음성을 출력하기 위해 play-sound를 사용했다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-18.png" rel="lightbox[40957]"><img class="alignnone size-full wp-image-41134" alt="68 ict_ 투명페트분리배출 (18)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-18.png" width="300" height="162" /></a></p>
<p>play-sound로 재생하는 음성은 서로 독립적으로 재생되어 소리가 겹치는 일이 발생할 수 있다. 전역변수로 audio를 생성하고, 음성 재생이 필요할 때마다 audio.kill()로 현재 재생되고 있는 소리를 제거하고 play를 통해 재생시켰다.</p>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p>음성출력 / 라즈베리파이(node.js)<br />
var audio = 0;<br />
function sound(data) {<br />
if (audio != 0) audio.kill();<br />
audio = player.play(&#8216;audio/&#8217; + data);<br />
}<br />
</div>
<p><span style="color: #3366ff"><strong>3.3. 개발 환경</strong></span><br />
· OS : 라즈비안(라즈베리파이), 윈도우10(노트북)<br />
· 개발 언어 : C, javascript(node.js), html, css<br />
· 툴 : brackets, 지니, 아두이노IDE<br />
· 통신 : MQTT, HTTP(Socket.io, GET)</p>
<p><strong>Node.js 주요사용 모듈</strong></p>
<table style="width: 620px" border="1" cellspacing="0" cellpadding="1">
<tbody>
<tr>
<td>express</td>
<td>Node.js를 위한 웹 프레임워크</td>
</tr>
<tr>
<td>socket.io</td>
<td>실시간 웹 애플리케이션을위한 JavaScript 라이브러리</td>
</tr>
<tr>
<td>@google-cloud/vision</td>
<td>google cloude vision api를 사용하기 위한 모듈</td>
</tr>
<tr>
<td>image-to-rgba-matrix</td>
<td>이미지를 rgba matrix로 변경해주는 모듈</td>
</tr>
<tr>
<td>pi-camera</td>
<td>라즈베리파이용 카메라를 제어하기 위한 모듈</td>
</tr>
<tr>
<td>play-sound</td>
<td>음성 재생을 위한 모듈</td>
</tr>
</tbody>
</table>
<p><strong style="color: #ff6600">4. 단계별 제작과정</strong></p>
<p><span style="color: #3366ff"><strong>4.1. 하드웨어 제작과정</strong></span><br />
<span style="color: #cc33b6"><strong>하드웨어 1주차</strong></span><br />
ⓐ 주제 선정 및 기능 아키텍처 작성을 통한 필요 물품 구매</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-20.png" rel="lightbox[40957]"><img class="alignnone size-full wp-image-41136" alt="68 ict_ 투명페트분리배출 (20)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-20.png" width="576" height="431" /></a><br />
<strong>필요 물품</strong><br />
· 폼보드 : 특별한 장비 없이 가공에 있어 자유롭고 튼튼하다.<br />
· ESP8266 mini : 각 기관의 역할을 나누는 데 필요했다.<br />
· 무게 센서 : 물체의 무게를 측정해 분류하는 데 필요했다.</p>
<p>라즈베리파이, 카메라, 스피커, 모니터, 초음파센서는 기존에 가지고 있던 것을 사용했다.</p>
<p><span style="color: #cc33b6"><strong>하드웨어 2주차 </strong></span><br />
ⓐ 역할을 분담하여 센서, 모터, 카메라를 동작(테스트)해보는 학습을 진행하였다.</p>
<p>ⓑ 제작에 앞서 외형도를 제작했다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-21.png" rel="lightbox[40957]"><img class="alignnone size-full wp-image-41137" alt="68 ict_ 투명페트분리배출 (21)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-21.png" width="577" height="377" /></a><br />
대략적인 제품 크기와 부품을 설치할 위치를 그렸다.</p>
<p><span style="color: #cc33b6"><strong>하드웨어 3주차</strong></span></p>
<p>ⓐ 외부 틀 제작 : 2주차에서 작성한 외형도를 바탕으로 문과 틀을 만들었다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-1.jpg" rel="lightbox[40957]"><img class="alignnone size-full wp-image-41116" alt="68 ict_ 투명페트분리배출 (1)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-1.jpg" width="579" height="297" /></a></p>
<p>ⓑ 밑판제작 및 부품 테스트</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-22.png" rel="lightbox[40957]"><img class="alignnone size-full wp-image-41138" alt="68 ict_ 투명페트분리배출 (22)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-22.png" width="580" height="293" /></a></p>
<p>무게 센서와 서보모터를 연결해 물체가 올라갈 밑판을 제작하였고, 모터 동작을 테스트했다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-23.png" rel="lightbox[40957]"><img class="alignnone size-full wp-image-41139" alt="68 ict_ 투명페트분리배출 (23)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-23.png" width="582" height="303" /></a></p>
<p>초음파센서를 입구 상단에 설치하고, 무게센서와 테스트했다.</p>
<p>ⓒ 내벽 제작</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-24.png" rel="lightbox[40957]"><img class="alignnone size-full wp-image-41140" alt="68 ict_ 투명페트분리배출 (24)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-24.png" width="579" height="275" /></a><br />
좌측에 동작기관, 우측에 감각기관으로 회로를 연결하였다.<br />
회로 정리 후 제작한 내벽을 붙여 선이 보이지 않게 만들었다.</p>
<p>ⓓ 앞면 제작</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-25.png" rel="lightbox[40957]"><img class="alignnone size-full wp-image-41141" alt="68 ict_ 투명페트분리배출 (25)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-25.png" width="579" height="318" /></a></p>
<p>로봇의 모니터가 들어갈 앞판을 제작하였다.</p>
<p>ⓔ 윗면 제작</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-26.png" rel="lightbox[40957]"><img class="alignnone size-full wp-image-41142" alt="68 ict_ 투명페트분리배출 (26)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-26.png" width="583" height="301" /></a></p>
<p>조명, 라즈베리파이 카메라, 스피커 등이 놓일 윗면을 제작하였다.</p>
<p><span style="color: #cc33b6"><strong>하드웨어 4주차</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-27.png" rel="lightbox[40957]"><img class="alignnone size-full wp-image-41143" alt="68 ict_ 투명페트분리배출 (27)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-27.png" width="579" height="304" /></a></p>
<p>카메라 선이 길지 않아 동작 테스트에 문제가 발생했다. 윗면에 설치하는 것이 아닌, 윗면에 붙이는 것으로 계획을 변경했다.<br />
쓰레기통의 교체가 쉽고 들어있는 상황을 볼 수 있도록 하단은 오픈형으로 제작하였다.</p>
<p><span style="color: #3366ff"><strong>4.2. 소프트웨어 제작과정 </strong></span><br />
<span style="color: #cc33b6"><strong>소프트웨어 1주차</strong></span><br />
하드웨어 제작 1주차 첨부한 기능 구성도를 작성하고, 필요한 기술을 조사했다.</p>
<p><strong>조사내용</strong><br />
· 각 기관 간의 통신은 MQTT 통신을 사용한다.<br />
· 로봇의 얼굴인 디스플레이는 웹사이트 형태로 제작한다.<br />
· 카메라 모듈을 사용해 사진을 찍은 후 구글 API을 사용해서 라벨 및 이물질의 유무 등을 분석한다.<br />
· 분석 결과에 따라, 모터가 작동해 플라스틱을 분리배출할 수 있게 도와준다.</p>
<p><span style="color: #cc33b6"><strong>소프트웨어 2주차</strong></span><br />
ⓐ 필요 기술 학습(물체 검출)</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-28.png" rel="lightbox[40957]"><img class="alignnone size-full wp-image-41144" alt="68 ict_ 투명페트분리배출 (28)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-28.png" width="579" height="542" /></a></p>
<p>vision API를 사용해 물체의 성분(plastic, glass 등), 텍스트(프린팅, 라벨), RGB 추출(이물질 검사)을 하기 기대했다. 성분 분석과 텍스트 검출에는 문제가 없었지만, 색 추출에서 이질적인 색을 잡는 민감도를 기대할 수 없었다. 다른 방법인 10개의 색 파레트를 추출하는 color-thief 모듈을 사용하여, 뚜껑 등의 이질적인 색 검출에 성공했다.</p>
<p>ⓑ 필요 기술 학습(사용자 인터페이스)</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-29.png" rel="lightbox[40957]"><img class="alignnone size-full wp-image-41145" alt="68 ict_ 투명페트분리배출 (29)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-29.png" width="579" height="206" /></a></p>
<p>웹 클라이언트 : 로봇의 얼굴에 해당하는 모니터에 HTML, CSS 등으로 캐릭터를 제작하였다. 아직 센서들과 통신을 하지 않기 때문에, 3초가 지나면 사진이 찍혀 ⓐ의 단계로 넘어가도록 코드를 작성했다.<br />
웹 소켓 : 동적인 웹 클라이언트를 만들기 제작하기 위해 웹 소켓을 사용하였다. 서버에서 연산한 결과를 클라이언트로 데이터를 전송해 사용자가 실시간으로 검사 결과를 확인할 수 있다.</p>
<p>ⓒ 모터, 센서 기능 구현<br />
아두이노 IDE를 사용해 물체가 들어오고 나감을 알 수 있도록 초음파센서 관련 함수를 작성하였고, 분리수거 &amp; 반환을 위해 모터 함수를 작성하였다.</p>
<p><span style="color: #cc33b6"><strong>소프트웨어 3주차</strong></span><br />
ⓐ MQTT 통신</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-30.png" rel="lightbox[40957]"><img class="alignnone size-full wp-image-41146" alt="68 ict_ 투명페트분리배출 (30)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-30.png" width="591" height="339" /></a><br />
MQTT 통신을 사용하여 각 디바이스가 상호작용한다. MQTT 통신을 사용한 이유는 클라이언트-서버 구조로 이뤄진 HTTP, TCP는 주로 클라이언트 요청에 대한 서버에 응답으로 통신하기 때문에 디바이스 간 통신이 어렵다. 반면, MQTT는 broker, publisher, subscriber 구조로 이루어져 있으므로, broker가 중계하고 client 간의 통신이 가능하다. 라즈베리파이에 브로커를 설치하고, 감각기관, 동작기관, 웹서버가 client가 되어 각자 필요한 메시지를 보내고, 받은 메시지를 통해 동작을 실행한다.</p>
<p>ⓑ 사용자 인터페이스 추가(웹 클라이언트)</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-31.png" rel="lightbox[40957]"><img class="alignnone size-full wp-image-41147" alt="68 ict_ 투명페트분리배출 (31)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-31.png" width="579" height="207" /></a></p>
<p>메인화면 : 분리수거 성공과 실패에 따라서 메인화면의 캐릭터 얼굴이 변화한다.<br />
설명페이지 : 사용자가 재활용 방법과 로봇의 검출 과정에 대해 학습할 수 있다.</p>
<p>ⓒ 사용자 인터페이스 추가(음성)</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-32.png" rel="lightbox[40957]"><img class="alignnone size-full wp-image-41148" alt="68 ict_ 투명페트분리배출 (32)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-32.png" width="574" height="254" /></a></p>
<p>교육적인 로봇의 목표에 재활용에 관한 피드백을 제공하기 위해 음성 파일을 각 단계별로 출력되게 제작하였다.</p>
<p><span style="color: #cc33b6"><strong>소프트웨어 4주차</strong></span><br />
ⓐ 각 디바이스에 MQTT 통신을 구현하였다.</p>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p>라즈베리파이(MQTT code)<br />
var mqtt_client = mqtt.connect(&#8216;mqtt://192.168.0.51&#8242;)<br />
mqtt_client.on(&#8216;connect&#8217;, function () {<br />
mqtt_client.subscribe(&#8216;raspi&#8217;, function (err) {<br />
if (!err) {<br />
console.log(&#8216;subscribe : raspi&#8217;);<br />
}<br />
})<br />
})</p>
</div>
<p>mqtt://주소를 통해 라즈베리파이에 구축한 브로커에 연결하여 ‘raspi’를 구독하였다.</p>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p>감각기관, 동작기관 (MQTT code)<br />
while (!client.connected()) {<br />
Serial.print(&#8220;Attempting MQTT connection&#8230;&#8221;);<br />
if (client.connect(&#8220;ESP8266Client&#8221;)) {<br />
Serial.println(&#8220;connected&#8221;);<br />
client.subscribe(&#8220;sensor&#8221;);<br />
} else {<br />
Serial.print(&#8220;failed, rc=&#8221;);<br />
Serial.print(client.state());<br />
Serial.println(&#8221; try again in 5 seconds&#8221;);<br />
delay(5000);<br />
}<br />
}</p>
</div>
<p>esp8266 mini에서 MQTT 브로커로 연결하여 감각기관의 경우 ‘sensor’을 동작기관의 경우 ‘motor’을 구독한다.<br />
2주차에서 분담을 통해 구현한 물체 감지, 모터작동 기능들을 MQTT 통신에 맞게 코드를 수정하였다.</p>
<p><span style="color: #cc33b6"><strong>소프트웨어 5주차</strong></span><br />
물체 테스트를 진행 중에 color-thief 모듈을 이용한 rgb검출에 대한 문제점이 드러났다. vision API보다는 작은 이물질에 대해 식별 가능했지만, 더 작은 이물질에 대해서는 검출하기 어려웠고, 결정적으로 배경색과 RGB 값이 비슷한 흰색, 검은색은 검출하기 어려웠다. 대체방법으로 이미지를 RGB matrix으로 변환해주는 모듈인 color-to-rgba-matrix를 사용하여 픽셀 단위의 분석을 진행하였다. 픽셀 단위로 연산량을 줄이기 위해 물체의 위치에 따른 분석하는 방법을 생각하였고, vision API에 위치 정보를 제공해주는 함수를 사용해 경곗값과 크기를 측정했다.</p>
<p><span style="color: #cc33b6"><strong>소프트웨어 6주차</strong></span><br />
ⓐ 사용자 인터페이스(웹 클라이언트)</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-1.png" rel="lightbox[40957]"><img class="alignnone size-full wp-image-41117" alt="68 ict_ 투명페트분리배출 (1)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-투명페트분리배출-1.png" width="583" height="208" /></a></p>
<p>5주차에서 측정한 경계값을 웹 클라이언트에 전송하여 BOX 형태로 표시하도록 하였다. 이는 사용자가 물체 분석되는 과정을 확인하는데 도움을 준다.</p>
<p>ⓑ 감각기관 코드 수정<br />
감각기관의 변수를 수정하였다. 이전에는 문이 열리고 닫히면, 물체가 들어왔음을 감지했었는데, 조건문을 추가하여 무게 센서값까지 측정하는 방법으로 변경하였다.</p>
<p>ⓒ 연결상태 확인 추가<br />
로봇을 테스트하기 위해 서버를 실행하는 과정에서 통신상태를 확인하지 않고, 실행한 경우, 연결상태를 확인하지 않고 실행해 로봇의 흐름이 섞이는 일이 발생했다. 이러한 문제를 해결하기 위해 처음 서버가 실행되기 전 MQTT 통신상태를 확인하기 위한 메시지를 보낸다. 메시지를 받은 디바이스는 초기화를 진행하고, 답변을 전달한다. 답변을 받은 라즈베리파이는 웹 서버를 실행시킨다.</p>
<p><span style="color: #ff6600"><strong>5. 사용한 제품 리스트</strong></span></p>
<table style="width: 620px" border="1" cellspacing="0" cellpadding="2">
<tbody>
<tr>
<td style="text-align: center">모델명</td>
<td style="text-align: center">디바 상품번호</td>
</tr>
<tr>
<td style="text-align: center">SEN0160</td>
<td style="text-align: center">1278214</td>
</tr>
<tr>
<td style="text-align: center">HC-SR04</td>
<td style="text-align: center">1076851</td>
</tr>
<tr>
<td style="text-align: center">MG996R</td>
<td style="text-align: center">1313388</td>
</tr>
<tr>
<td style="text-align: center">WeMos D1 mini</td>
<td style="text-align: center">1327519</td>
</tr>
<tr>
<td style="text-align: center">Raspberry Pi 4 Model B</td>
<td style="text-align: center">12234534</td>
</tr>
<tr>
<td style="text-align: center">Raspberry Pi Touch Display</td>
<td style="text-align: center">1273487</td>
</tr>
<tr>
<td style="text-align: center">TS-CS01BO</td>
<td style="text-align: center">-</td>
</tr>
<tr>
<td style="text-align: center">밝기조절 발광 LED 라이트</td>
<td style="text-align: center">-</td>
</tr>
<tr>
<td style="text-align: center">RPI 8MP CAMERA BOARD</td>
<td style="text-align: center">1077951</td>
</tr>
</tbody>
</table>
<p><strong style="color: #ff6600">6. 후속연구제안</strong></p>
<p><span style="color: #3366ff"><strong>6.1. 배경 색과 빛 반사</strong></span><br />
페트병의 투명성 여부를 판단하는 과정은 사진을 촬영하여 배경색과의 유사도를 검사하는 방식으로 진행된다. 이 때, 두 가지 문제점이 발생한다. 만약 라벨이나 이물질 등이 배경색과 유사한 색이라면 로봇은 ‘투명한 페트병’인 것으로 판별하게 된다. 또한 플라스틱 표면에 빛을 비추면 빛이 반사되어 카메라로 사진을 찍으면 반사된 부분이 흰색으로 인식되어 투명성 판별에 장애요소가 될 수 있다. 이는 신뢰도 하락과 재활용률 하락으로 이어지는 문제이므로 주의가 필요하다. 본 작품은 배경색을 회색(카메라로 촬영했을 때 R, G, B 세 값의 표준편차가 15이내, 평균은 40이상 180이하. 이때, 각 값은 0~255사이의 값을 갖는다.)으로 설정하여 라벨이나 이물질의 색과 분명한 차이를 둘 수 있도록 하였다. 그러나 만약 회색으로 이루어진 플라스틱 제품이 나오게 되면 본 작품에서 채택한 투명성 판별법은 신뢰도가 떨어지게 된다. 또한 빛 반사문제는 카메라를 사용한다면 피할 수 없는 문제이므로 해결하기 어렵다. 따라서 후속연구에서는 LPKF TMG 3 등의 광투과율 측정 장치를 이용하여 플라스틱의 투명도를 정확하게 측정할 수 있도록 할 것을 제안한다.</p>
<p><span style="color: #3366ff"><strong>6.2. 플라스틱 압축 기능</strong></span><br />
플라스틱은 압축하여 배출해야 쓰레기통이나 쓰레기봉투를 효율적으로 사용할 수 있다. 따라서 플라스틱을 쓰레기통에 담는 과정에서 플라스틱을 압축하는 기능이 있으면 좋겠다는 생각을 했다. 이를 위해서는 강한 모터나 압축기가 필요한데 전력소비, 무게, 부피, 예산 등의 측면에서 효율성이 떨어지므로 배제하였다. 대체할 수 있는 기능으로는 페트병이 압축되었는지의 여부를 판단하는 방법이 있다. 이를 구현하려 하였으나, 압축된 페트병을 판별할 수 있는 명확한 기준을 찾기는 힘들었다. 비슷한 크기의 페트병이라면 무게도 비슷할 것이라고 가정하고 압축된 페트병과 압축되지 않은 페트병의 (넓이/무게) 값을 비교하였으나 유의미한 데이터를 얻지 못했기 때문에 페트병이 압축되었는지 판별하는 기능은 구현하지 못했다. 따라서 후속연구에서는 플라스틱 압축 기능이나 압축되지 않은 플라스틱을 구별하는 기능을 추가할 것을 제안한다.<br />
<strong></strong></p>
<p><span style="color: #3366ff"><strong>6.3. 처리량</strong></span><br />
본 작품은 한 번에 1개의 페트병만 검사할 수 있다. 따라서 많은 양의 페트병을 버리려고 한다면 하나씩 천천히 넣어줘야 한다. 이는 굉장히 불편한 작업이므로 본 작품을 일상생활에서 사용하기 위해서는 이 문제를 반드시 해결해야 한다. 그러나 한 번에 검사할 수 있는 페트병의 수는 제한적이다. 따라서 큰 통에 페트병을 담아두고 거기에서 하나씩 꺼내서 검사하는 방법을 생각해볼 수 있다. 그런데, 사진을 찍는 시간은 약 10초, 재활용 가능한 플라스틱인지 분석하는 시간은 약 10초로 한 페트병을 처리하는 것에 평균 20초의 시간이 소요된다. 이는 디스플레이에 분석과정을 보여주고 음성으로 안내하기 위해서 의도적으로 시간을 늘린 것인데, 많은 양의 페트병을 처리하는 것에 방해요소로 작용한다. 따라서 안내하는 것을 간소화하는 것으로 시간을 단축할 수 있다. 또한 여러 개의 페트병을 한 번에 촬영하여 처리시간을 단축하는 방법도 있다. 단, 한 번에 많은 페트병을 처리할 경우, 그 중에서 재활용 되지 않는 패트병을 골라내는 방법에 대한 고찰이 선행되어야 한다.</p>
<p><span style="color: #ff6600"><strong>7. 참고문헌</strong></span><br />
· 그린피스 김이서(2019년 12월),플라스틱 대한민국- 일회용의 유혹https://www.greenpeace.org<br />
· 투명 페트병 재활용 제품 안내, 환경부 :https://www.newsro.kr<br />
· 최병용(2021.02.19), 투명 페트병 분리배출 현장에 가보니~ : https://www.korea.kr/news/reporterView.do?newsId=148883922</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
]]></content:encoded>
			<wfw:commentRss>http://www.ntrexgo.com/archives/40957/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>[68호]일회용품을 사용하지 않는 친환경 자판기</title>
		<link>http://www.ntrexgo.com/archives/40945</link>
		<comments>http://www.ntrexgo.com/archives/40945#comments</comments>
		<pubDate>Mon, 25 Oct 2021 00:00:32 +0000</pubDate>
		<dc:creator>디바이스마트 매거진</dc:creator>
				<category><![CDATA[디바이스마트 매거진]]></category>
		<category><![CDATA[특집]]></category>
		<category><![CDATA[ict]]></category>
		<category><![CDATA[ict공모전]]></category>
		<category><![CDATA[공모전]]></category>
		<category><![CDATA[디바이스마트]]></category>
		<category><![CDATA[매거진]]></category>
		<category><![CDATA[수상작]]></category>
		<category><![CDATA[융합]]></category>
		<category><![CDATA[프로젝트]]></category>

		<guid isPermaLink="false">http://www.ntrexgo.com/?p=40945</guid>
		<description><![CDATA[디바이스마트매거진 68호 &#124; 스마트 디바이스 소형가전 쇼는 ‘나 혼자 쓴다’라는 주제를 가지고 급속히 늘어나는 단독 가구와 MZ 세대가 일상생활에서 필요로 하는 스마트 가전제품을 선보였으며, 주요 전시 품목은 생활가전, 무선가전, 미용가전, 살균가전, 미니가전 등이 있다.]]></description>
				<content:encoded><![CDATA[<p><span style="font-size: medium"><strong><a href="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-26.png" rel="lightbox[40945]"><img class="alignnone size-large wp-image-40992" alt="68 ICT_우수상_친환경 자판기 (26)" src="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-26-620x203.png" width="620" height="203" /></a></strong></span></p>
<p><span style="font-size: medium"><strong>2021 ICT 융합 프로젝트 공모전 우수상</strong></span></p>
<p><span style="font-size: x-large;color: #ff6600"><strong>일회용품을 사용하지 않는 친환경 자판기 </strong></span></p>
<p><span style="font-size: x-large;color: #ff6600"><strong>(Eco-friendly Vending Machine)</strong></span></p>
<p><span style="color: #ff6600"> </span></p>
<p style="text-align: right">글 | 대한상공회의소 서울기술교육센터 한승진, 김연진, 신상우, 유종선, 진민경</p>
<p>&nbsp;</p>
<p><span style="color: #ff6600"><strong>1. 심사평</strong></span><br />
<strong>칩센</strong> 최근에 여러 가지 형태로 무인음료 자판기가 설치되는 것으로 알고 있습니다. 이러한 무인 자판기는 기존과 같이 컵이 내장된 형태도 있지만, 작품과 같이 개인컵을 이용한 제품도 있는 것으로 알고 있습니다. 이미 기존에 있는 제품이라 하더라도 더 보완된 제품을 가격 경쟁력이 있게 만들 수 있다면 개발의 의미는 충분히 있습니다. 또한, 불편함이 있더라도 작품의 기획의도와 같은 목적을 보여주는 것도 좋은 방법이라 생각합니다. 이왕 echo-friendly라는 가치를 걸었다면 상용화된 1회용 컵을 구분하는 제약을 두어 제품의 목적을 분명히 밝히는 것도 좋은 방향이 아닐까 하는 생각입니다.<br />
<strong></strong></p>
<p><strong>펌테크</strong> 친환경을 고려한 실생활과 접목된 실용성과 창의성을 지닌 작품으로 생각됩니다. 전체 하드웨어 및 소프트웨어 개발 환경 구성이 작품의 성격에 맞추어 체계적으로 적절하였다. 시스템 구성중 opencv를 사용한 영상처리를 과정 및 필요기능 위주의 심플한 형태로 짜임새 있고 깔끔하게 구성한 스마트폰용 APP은 인상적이었으며 전체적으로 기획의도, 기술 구현도, 완성도 등에서 우수한 작품이라고 생각됩니다.<br />
<strong></strong></p>
<p><strong>위드로봇</strong> 아이디어를 프로토타입을 통해 개념을 확인한 부분이 훌륭합니다.<br />
<strong></strong></p>
<p><strong>뉴티씨</strong> 텀블러를 사용하도록 하는 시스템은 조금 불편하더라도 환경을 지킬 수 있는 솔루션 중 하나일 수 있다는 생각이 듭니다. 나의 편리성을 위한 것이 아닌 후세대를 위한 지구 환경을 생각하여, 일회용품 사용을 줄일 수 있는 이와 같은 시스템을 좀 더 연구해야 할 것입니다.<br />
<strong></strong></p>
<p><span style="color: #ff6600"><strong>2. 개발 배경</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-1.png" rel="lightbox[40945]"><img class="alignnone size-full wp-image-40960" alt="68 ICT_우수상_친환경 자판기 (1)" src="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-1.png" width="416" height="401" /></a></p>
<p>일회용품을 많이 사용하는 요즘, 전 세계적으로 일회용품 사용을 줄이기 위해 여러 가지 정책을 펼치고 있다. EU의 경우 올해(2021년)부터 플라스틱 세를 도입하여 플라스틱 사용 절감에 앞장서고 있다. 우리나라의 경우 커피 전문점 매장 내에서는 플라스틱 컵과 빨대를 사용할 수 없다. 또한, 슈퍼마켓에선 비닐봉지 사용이 불가하며, 대형마트에서는 포장용 테이프를 제공하지 못하게 된 상태이다.<br />
하지만 1인 가구의 증가와 더불어 코로나 바이러스로 인한 배달음식 주문량이 많아지면서 일회용품 사용량 즉, 일회용품 배출량이 나날이 증가하고 있는 추세이다. 서울디지털재단의 ‘1인 가구 증가에 따른 일회용 플라스틱 배출 실태 분석’ 보고서에 따르면 1인 가구가 배출한 일회용품 양은 일평균 30개로 조사되었으며, 이는 다인 가구에 거주하는 1인의 배출량보다 약 2.3배 많은 양이라고 한다. 또, 서울 거주 시민 1000명을 대상으로 실시한 설문에 따르면 배달 음식 주문빈도는 코로나 발생을 기준으로 약 1.4배 증가한 것으로 조사되었다고 한다.<br />
더불어 일회용 플라스틱 배출을 줄일 수 있는 방법에 대한 설문조사(1000명 대상)에서 &#8216;일회용 플라스틱 사용 저감 유도를 위한 보상체계 필요 여부&#8217;에 대해서는 응답자의 91.3%가 보상(인센티브)의 필요성을 공감하고 있는 것으로 나타났다고 한다. 보상방식으로는 &#8216;현금 지급&#8217;이 42.4%로 가장 높았고 이어서 &#8216;에코-마일리지 지급&#8217;(25.6%), &#8216;지역 화폐 지급&#8217;(18.3%) 등이 뒤를 이었다.<br />
위의 보고서 내용과 마찬가지로 팀원들 모두가 일회용품 사용의 심각성과 사용 절감에 대한 필요성 모두 느끼고 있었다. 따라서 우리는 일상 속에서 일회용품을 절감할 수 있고 이에 따른 보상을 지급하는 제품을 만들기 위해 아이디어를 모았다. 그 결과 ‘일회용품을 사용하지 않는 자판기’를 개발하게 되었다.</p>
<p><span style="color: #ff6600"><strong>3. 개발 목표</strong></span><br />
요즘 일회용품 사용을 절감하기 위한 사회적 움직임의 일환으로 일부 프랜차이즈 카페에서 플라스틱 컵 대신 개인 용기에 음료를 받으면 일정 금액을 할인해주고 있다. 이러한 서비스의 적용 범위를 확대하여 도서관, 스터디카페 또는 대학교, 공공기관 등 특정 사람들이 지속적으로 방문하는 곳에 배치된 자판기에 적용하여 일회용품 사용 절감에 이바지하도록 한다.<br />
해당 자판기를 사용할 때마다 핸드폰 번호 입력을 통해 일정 금액이 에코-마일리지로 적립되며 추후에 실제로 제품이 상용화되는 경우 마일리지를 사용할 수 있도록 한다.</p>
<p><span style="color: #ff6600"><strong>4. 작품 개요</strong></span><br />
일상 속에서 아두이노와 라즈베리 파이 그리고 젯슨 나노를 활용하였다.<br />
아두이노는 음료 결제, 선택, 추출 등 자판기의 전체적인 동작을 담당하고, 젯슨 나노는 영상처리를 통해 음료 추출 전 사용자의 텀블러가 제자리에 놓여있는지 판단하였다. 라즈베리 파이는 터치스크린을 통해 자판기 내부의 진행상황을 알려주고, 자판기 이용 후 휴대폰 번호를 입력하면 에코-마일리지를 적립함과 동시에 누적된 마일리지를 확인할 수 있도록 하였다.<br />
부가적으로 자판기 관리자를 위한 안드로이드 애플리케이션을 제작하여 자판기 내부 음료의 상태(양)를 확인하고, 음료별 누적 판매량을 통해 어느 음료가 인기가 많은지 확인 할 수 있도록 하였다.</p>
<p><span style="color: #c19b00"><strong>4.1. 개발 환경 및 개발 도구 설명</strong></span><br />
<span style="color: #339966"><strong>4.1.1. 아두이노(Arduino)</strong></span></p>
<p><span style="color: #339966"><strong><a href="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-2.png" rel="lightbox[40945]"><img class="alignnone size-large wp-image-40962" alt="68 ICT_우수상_친환경 자판기 (2)" src="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-2-620x242.png" width="620" height="242" /></a></strong></span><br />
아두이노는 다양한 스위치나 센서로부터 입력 값을 받아들이고 전자 장치들로 출력을 제어하여 디지털 장치를 만들기 위한 도구로, 간단한 마이크로컨트롤러를 기반으로 한 오픈 소스 컴퓨팅 플랫폼과 소프트웨어 개발 환경이다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-3.png" rel="lightbox[40945]"><img class="alignnone size-full wp-image-40964" alt="68 ICT_우수상_친환경 자판기 (3)" src="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-3.png" width="343" height="263" /></a></p>
<p>본 제품에서는 Arduino Mega 2560, Uno를 사용하여 전체적인 자판기의 동작을 구현하였다.</p>
<p><span style="color: #339966"><strong>4.1.2. 젯슨 나노(Jetson Nano)</strong></span></p>
<p><span style="color: #339966"><strong><a href="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-4.png" rel="lightbox[40945]"><img class="alignnone size-full wp-image-40966" alt="68 ICT_우수상_친환경 자판기 (4)" src="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-4.png" width="331" height="298" /></a></strong></span><br />
젯슨 나노는 엔비디아(NVDA)가 개발한 싱글 보드로 애플리케이션에서 다수의 뉴럴 네트워크를 병렬로 실행하게 해주는 강력한 소형 컴퓨터이다.<br />
본 제품에서는 이와 같은 개발 환경에서 우분투의 vi 문서 편집기를 통해 언어 PYTHON으로 코드를 구현하고 실행 파일을 생성하였다.</p>
<p><span style="color: #339966"><strong>4.1.3. 라즈베리 파이(Raspberry Pi)</strong></span></p>
<p><span style="color: #339966"><strong><a href="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-5.png" rel="lightbox[40945]"><img class="alignnone size-full wp-image-40968" alt="68 ICT_우수상_친환경 자판기 (5)" src="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-5.png" width="317" height="232" /></a></strong></span><br />
라즈베리 파이는 영국 잉글랜드의 라즈베리 파이 재단이 학교와 개발도상국에서 기초 컴퓨터 과학의 교육을 증진하기 위해 개발한 신용카드 크기의 싱글 보드 컴퓨터이다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-6.png" rel="lightbox[40945]"><img class="alignnone size-full wp-image-40970" alt="68 ICT_우수상_친환경 자판기 (6)" src="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-6.png" width="313" height="239" /></a></p>
<p>라즈베리 파이 OS를 실행할 때, 기본 터미널 애플리케이션은 LXTerminal이다. 해당 프롬프트에서 리눅스 명령어를 기반으로 vi 에디터 또는 문서 편집기를 이용해 실행 프로그램을 생성할 수 있다. 본 제품에서는 html과 언어 php를 사용하여 웹 파일을 생성하였다.</p>
<p><span style="color: #339966"><strong>4.1.4. 안드로이드(Android)</strong></span><br />
안드로이드는 휴대전화의 운영체제, 미들웨어, 사용자 인터페이스, 응용프로그램 등을 묶은 소프트웨어 플랫폼이다. 리눅스(Linux)2.6 커널 위에서 동작하며 운영체제, 라이브러리, 멀티미디어 사용자 인터페이스, 애플리케이션 등을 제공한다.<br />
안드로이드 프로그래밍 언어로는 JAVA와 Kotlin이 있다. 또한, 안드로이드 스튜디오는 안드로이드 앱을 빌드할 때 생산성을 높여주도록 다양한 기능을 제공한다. 본 제품에서는 프로그래밍 언어로 JAVA를 사용하였다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-7.png" rel="lightbox[40945]"><img class="alignnone size-full wp-image-40972" alt="68 ICT_우수상_친환경 자판기 (7)" src="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-7.png" width="609" height="241" /></a><br />
Android Emulator의 각 인스턴스는 Android Virtual Device(AVD)를 사용하여 시뮬레이션된 기기의 Android 버전과 하드웨어 특성을 지정한다. 효과적으로 앱을 테스트하려면 앱이 실행될 각 기기를 모델링하는 AVD를 만들어야 한다. AVD를 만들고 관리하려면 AVD Manager를 사용하면 된다.<br />
<span style="color: #b9ae08"><strong>4.2. 작품 구성</strong></span><br />
<span style="color: #339966"><strong>4.2.1. 부품 리스트</strong></span></p>
<table style="width: 620px" border="1" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td style="text-align: center">부품명</td>
<td style="text-align: center">디바 상품 번호</td>
</tr>
<tr>
<td style="text-align: center">물높이(수위) 센서 스위치</td>
<td style="text-align: center"></td>
</tr>
<tr>
<td style="text-align: center">PLEOMAX W-210 PC캠 화상캠 웹캠</td>
<td style="text-align: center"></td>
</tr>
<tr>
<td style="text-align: center">라즈베리파이 5인치 HDMI LCD 터치스크린 모니터</td>
<td style="text-align: center">1382229</td>
</tr>
<tr>
<td style="text-align: center">아두이노 워터펌프 모터 3~5V</td>
<td style="text-align: center"></td>
</tr>
<tr>
<td style="text-align: center">아두이노 워터펌프용  실리콘 튜브(1m)</td>
<td style="text-align: center"></td>
</tr>
<tr>
<td style="text-align: center">아두이노 4채널 5V 릴레이 모듈</td>
<td style="text-align: center">1327545</td>
</tr>
<tr>
<td style="text-align: center">아두이노 WIFI ESP8266</td>
<td style="text-align: center">1279338</td>
</tr>
<tr>
<td style="text-align: center">아두이노 RFID-RC522 리더기</td>
<td style="text-align: center">1279308</td>
</tr>
<tr>
<td style="text-align: center">아두이노 수동 부저 모듈</td>
<td style="text-align: center">10916342</td>
</tr>
<tr>
<td style="text-align: center">아두이노 LED</td>
<td style="text-align: center"></td>
</tr>
<tr>
<td style="text-align: center">아두이노 6&#215;6 택트 스위치</td>
<td style="text-align: center"></td>
</tr>
<tr>
<td style="text-align: center">아두이노 우노 Uno R3 호환보드</td>
<td style="text-align: center">1245596</td>
</tr>
<tr>
<td style="text-align: center">아두이노 메가 2560 호환보드</td>
<td style="text-align: center">10918650</td>
</tr>
<tr>
<td style="text-align: center">NVIDIA Jetson Nano Development Kit-B01</td>
<td style="text-align: center">12513656</td>
</tr>
<tr>
<td style="text-align: center">라즈베리파이4  (Raspberry Pi 4 Model B) 2GB</td>
<td style="text-align: center">12234533</td>
</tr>
</tbody>
</table>
<p><span style="color: #339966"> <strong>4.2.2. 회로도</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-9.png" rel="lightbox[40945]"><img class="alignnone size-full wp-image-40975" alt="68 ICT_우수상_친환경 자판기 (9)" src="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-9.png" width="606" height="332" /></a> <a href="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-10.png" rel="lightbox[40945]"><img class="alignnone size-full wp-image-40976" alt="68 ICT_우수상_친환경 자판기 (10)" src="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-10.png" width="607" height="433" /></a><br />
<span style="color: #339966"> <strong>4.2.3. 시스템 구성도</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-11.png" rel="lightbox[40945]"><img class="alignnone size-full wp-image-40977" alt="68 ICT_우수상_친환경 자판기 (11)" src="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-11.png" width="620" height="377" /></a></p>
<p><span style="color: #339966"><strong>4.2.4. 작품 구상도</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-12.png" rel="lightbox[40945]"><img class="alignnone size-full wp-image-40978" alt="68 ICT_우수상_친환경 자판기 (12)" src="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-12.png" width="620" height="377" /></a></p>
<p><span style="color: #ff6600"><strong>5. 작품 설명</strong></span><br />
<span style="color: #c19b00"><strong>5.1. 작품 제작 및 동작</strong></span><br />
<strong>5.1.1. 소켓 및 스레드 통신</strong><br />
자판기에 사용되는 장치들 간의 통신을 위해 소켓을 사용하여 서버를 구축하고 각각의 장치(아두이노, 젯슨 나노, 라즈베리 파이)들이 클라이언트가 되도록 하였다. 서버를 구축하는 데 있어서 C, PYTHON, JAVA 등의 언어 중 JAVA를 사용하였다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-13.png" rel="lightbox[40945]"><img class="alignnone size-full wp-image-40979" alt="68 ICT_우수상_친환경 자판기 (13)" src="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-13.png" width="620" height="466" /></a><br />
스레드를 사용하지 않으면 여러 클라이언트의 접속이 불가하고, 다른 동작을 실행할 수 없다. 따라서 멀티 스레드를 사용하였고 초기에는 서버 하나에 여러 개의 클라이언트가 붙도록 구현하였다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-14.png" rel="lightbox[40945]"><img class="alignnone size-full wp-image-40980" alt="68 ICT_우수상_친환경 자판기 (14)" src="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-14.png" width="620" height="338" /></a><br />
모든 클라이언트는 접속 시에 자신의 아이디를 서버에 전송한다. 이를 토대로 서버에서는 등록된 아이디 리스트와 접속 아이디를 비교, 검증한 후 클라이언트의 접속을 허용한다. 또한, 접속된 아이디를 기준으로 클라이언트 간 메시지를 보낼 수 있도록 하였다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-15.png" rel="lightbox[40945]"><img class="alignnone size-full wp-image-40981" alt="68 ICT_우수상_친환경 자판기 (15)" src="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-15.png" width="620" height="373" /></a></p>
<p><span style="color: #339966"><strong>5.1.2. 아두이노(Arduino)</strong></span><br />
<strong>음료 결제</strong><br />
자판기의 결제 기능을 구현하기 위해 RFID 리더기 모듈을 사용하여 자판기 사용자의 카드가 인식되도록 하였다. 이후 전체 동작을 위해 RFID 리더기에 카드가 인식되면 ‘음료 선택’ 동작으로 넘어가도록 하였다. Arduino Mega 2560(이후 Mega로 칭함.)에서 Arduino Uno(이후 Uno로 칭함.)로 시리얼 통신으로 일정 값을 보내주고 Uno에서 값을 판단하여 재전송 하도록 한다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-2.jpg" rel="lightbox[40945]"><img class="alignnone size-full wp-image-40961" alt="68 ICT_우수상_친환경 자판기 (2)" src="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-2.jpg" width="596" height="424" /></a></p>
<p><strong>음료 선택</strong><br />
사용자가 상황에 맞게 음료를 선택할 수 있도록 버튼과 LED를 구현하였다. LED는 현재 해당 음료를 선택할 수 있다는 신호이며, 버튼은 음료를 추출하도록 한다. 서버에서 결제가 되었다는 값을 전달 받으면 LED를 점등함과 동시에 버튼 기능을 활성화한다. 그리고 사용자가 버튼을 누를 시 해당 버튼에 맞는 음료의 정보를 Mega에서 시리얼 통신으로 Uno에게 메시지를 전송하여 ‘음료 추출’ 동작이 진행되도록 하였다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-3.jpg" rel="lightbox[40945]"><img class="alignnone size-full wp-image-40963" alt="68 ICT_우수상_친환경 자판기 (3)" src="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-3.jpg" width="594" height="424" /></a></p>
<p><strong>음료 추출</strong><br />
음료를 추출하기 위해서 워터 펌프 모듈을 사용하였다. 워터 펌프 모듈의 불안정한 작동을 보안하기 위해 릴레이 모듈을 추가하였다. Mega에서 사용자가 선택한 음료 정보를 시리얼 통신으로 Uno에게 넘겨주고 음료 정보와 일치하는 워터 펌프를 작동시킨다. 음료 추출이 끝나면 부저를 통해 종료 음을 출력한다.<br />
이후 모든 동작이 완료되면 언제든 사용자의 카드를 읽을 수 있는 초기 상태(결제 대기 상태)로 되돌린다.</p>
<p><strong>음료 잔량 확인</strong><br />
플로트 스위치 센서를 사용하여 음료 잔량을 확인하도록 하였다. 부력으로 음료 표면에 떠 있다가 바닥에 닿으면 음료가 부족하다고 판단하여 LED를 소등한다. 그리고 사용자가 음료 선택 버튼을 눌러도 동작하지 않도록 하였다.<br />
관리자가 애플리케이션을 통해 음료 잔량 정보를 요청하면 현재 모든 음료의 수위 센서 상태를 읽어 서버를 통해 애플리케이션에 전달한다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-16.png" rel="lightbox[40945]"><img class="alignnone size-large wp-image-40982" alt="68 ICT_우수상_친환경 자판기 (16)" src="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-16-460x620.png" width="460" height="620" /></a></p>
<p><strong>매출 관리</strong><br />
애플리케이션에서 자판기 누적 판매량 기능을 구현하기 위해 음료 펌프가 작동된 횟수를 Mega의 비휘발성 메모리인 EEPROM에 저장하도록 하였다.<br />
관리자가 애플리케이션을 통해 누적 판매량 정보를 요청하면 EEPROM에 저장된 값을 읽어 서버를 통해 애플리케이션에 전달한다.</p>
<p><span style="color: #339966"><strong>5.1.3. 젯슨 나노(Jetson Nano)</strong></span><br />
<strong>컵 인식</strong><br />
먼저 Window 환경에서 opencv와 tensorflow를 사용하여 자판기 사용자가 놓은 컵을 인식하도록 하였다. 컵이 인식되면 웹캠을 종료하고 사진을 찍도록 하였다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-17.png" rel="lightbox[40945]"><img class="alignnone size-full wp-image-40983" alt="68 ICT_우수상_친환경 자판기 (17)" src="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-17.png" width="359" height="267" /></a><br />
코드의 정상 동작 여부를 확인하고 젯슨 나노에서 개발 환경을 구축한 후 멀티 스레드를 사용하여 Mega에서 전송한 ‘음료 결제’ 메시지를 받으면 카메라를 동작시켜 컵을 인식하도록 하였다. 컵이 인식되면 Mega로 완료 메시지를 보내 계속해서 자판기의 동작을 수행한다. 컵이 인식되지 않으면 자판기의 동작을 중단하고 환불 페이지로 이동한다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-18.png" rel="lightbox[40945]"><img class="alignnone size-full wp-image-40984" alt="68 ICT_우수상_친환경 자판기 (18)" src="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-18.png" width="597" height="360" /></a></p>
<p><span style="color: #339966"><strong>5.1.4. 라즈베리 파이(Raspberry Pi)</strong></span><br />
<strong>진행 상황 출력</strong><br />
라즈베리 파이에 부착된 터치스크린을 통하여 자판기 동작 진행상황 및 이용 순서에 맞게 웹 페이지를 띄워 사용자에게 알려준다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-4.jpg" rel="lightbox[40945]"><img class="alignnone size-full wp-image-40965" alt="68 ICT_우수상_친환경 자판기 (4)" src="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-4.jpg" width="595" height="349" /></a></p>
<p><strong>에코-마일리지 적립</strong><br />
음료 추출이 종료되면 바로 에코-마일리지 적립 페이지로 이동한다. 자신의 전화번호를 입력하면 일정 금액의 마일리지가 적립된다. 입력을 완료하면 방금 적립된 마일리지와 누적된 마일리지 모두를 확인 할 수 있고 자동으로 페이지가 종료된다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-19.png" rel="lightbox[40945]"><img class="alignnone size-full wp-image-40985" alt="68 ICT_우수상_친환경 자판기 (19)" src="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-19.png" width="597" height="351" /></a></p>
<p>사용자가 입력한 정보는 MariaDB를 통해 관리한다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-20.png" rel="lightbox[40945]"><img class="alignnone size-full wp-image-40986" alt="68 ICT_우수상_친환경 자판기 (20)" src="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-20.png" width="593" height="306" /></a></p>
<p><span style="color: #339966"><strong>5.1.5. 안드로이드(Android)</strong></span><br />
<strong>하단 탭</strong><br />
BottomNavigationView와 Fragment를 사용하여 하단 탭을 생성하였다. 하단 탭 클릭을 통해 ‘상태’ 페이지와 ‘판매량’ 페이지로 이동한다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-21.png" rel="lightbox[40945]"><img class="alignnone size-full wp-image-40987" alt="68 ICT_우수상_친환경 자판기 (21)" src="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-21.png" width="596" height="127" /></a></p>
<p><strong>로그인(서버 접속)</strong><br />
메인 화면 상단에 로그인을 위한 옵션 버튼을 추가하였다. 해당 버튼을 선택하면 로그인 창을 띄운다.<br />
해당 창에서 서버 접속(로그인)을 위한 IP, PORT, ID, PASSWORD를 입력한다. 미리 설정한 값이 기본 값으로 입력되어 있어 보다 편리하게 접속할 수 있다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-22.png" rel="lightbox[40945]"><img class="alignnone size-full wp-image-40988" alt="68 ICT_우수상_친환경 자판기 (22)" src="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-22.png" width="587" height="620" /></a></p>
<p><strong>음료 잔량 확인</strong><br />
자판기 내부 음료의 잔량을 자판기 관리자가 수시로 확인 할 수 있도록 ‘상태’ 페이지 안에 CONDITION 버튼을 추가했다. 버튼을 누르면 서버를 통해 자판기 내부의 MCU에 특정 메시지를 전송하여 음료의 잔량을 요청한다.<br />
자판기 내부의 음료가 충분하다면 아래 사진과 같이 음료 아이콘의 테두리를 강조 해준다.<br />
<strong></strong></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-23.png" rel="lightbox[40945]"><img class="alignnone size-full wp-image-40989" alt="68 ICT_우수상_친환경 자판기 (23)" src="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-23.png" width="594" height="401" /></a></p>
<p><strong>음료 누적 판매량 확인</strong><br />
‘판매량’ 페이지의 초기 화면은 아래와 같이 ‘?개’로 나타낸다. RENEWAL 버튼을 누르면 서버를 통해 특정 메시지를 전송하여 음료 각각의 누적 판매량을 보여준다.<br />
RESET 버튼을 누르게 되면 서버를 통해 판매량을 초기화 해달라는 메시지를 MCU에게 전송하여 각 음료의 누적 판매량을 모두 0개로 초기화한다.<br />
<strong></strong></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-24.png" rel="lightbox[40945]"><img class="alignnone size-full wp-image-40990" alt="68 ICT_우수상_친환경 자판기 (24)" src="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-24.png" width="598" height="408" /></a></p>
<p><span style="color: #ff6600"><strong>6. 전체 흐름도</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-25.png" rel="lightbox[40945]"><img class="alignnone size-full wp-image-40991" alt="68 ICT_우수상_친환경 자판기 (25)" src="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-25.png" width="599" height="616" /></a></p>
<p><strong style="color: #ff6600">7. 최종구현</strong></p>
<p>&nbsp;</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-5.jpg" rel="lightbox[40945]"><img class="alignnone size-large wp-image-40967" alt="68 ICT_우수상_친환경 자판기 (5)" src="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-5-534x620.jpg" width="534" height="620" /></a></p>
<p><strong style="color: #ff6600">8. 시연</strong></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-7.jpg" rel="lightbox[40945]"><img class="alignnone size-full wp-image-40971" alt="68 ICT_우수상_친환경 자판기 (7)" src="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-7.jpg" width="598" height="541" /></a></p>
<p><span style="color: #ff6600"><strong>9. 결과 및 향후 목표</strong></span><br />
<span style="color: #c19b00"><strong>9.1. 결과</strong></span><br />
자판기에서 일회용품 사용을 줄이기 위해 초기에 기획한 기능으로 영상처리를 통한 텀블러 인식, 에코-마일리지 적립 등을 모두 구현하였다. 정상적으로 모든 기능이 동작하였지만 몇 가지의 아쉬운 점이 발생하였다.<br />
먼저, 음료의 잔량을 측정하는 플로트 스위치 센서의 동작이 불안정하였다. 무게 센서나 무접점 수위 센서를 사용했으면 보다 안정된 동작을 구현할 수 있을 것이라 생각하였다. 다음으로 영상처리 부분에서 오픈 소스를 활용하다보니 필요 이상의 학습데이터로 인해 프로세서의 부담이 커져 텀블러 인식 속도에 영향을 미쳤다. 머신러닝을 접목하여 직접 필요한 이미지만 학습시킨다면 인식 속도를 개선할 수 있을 것이라 생각한다. 마지막으로 텀블러 크기에 따라 음료 추출량을 조절할 수 있는 방법에 대해 생각해보려 한다.<br />
<strong></strong></p>
<p><span style="color: #c19b00"><strong>9.2. 향후 목표</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-8.jpg" rel="lightbox[40945]"><img class="alignnone size-full wp-image-40973" alt="68 ICT_우수상_친환경 자판기 (8)" src="http://www.ntrexgo.com/wp-content/uploads/2021/12/68-ICT_우수상_친환경-자판기-8.jpg" width="301" height="385" /></a></p>
<p>본 제품은 소켓 통신을 하기 위해 와이파이를 사용한다. 하지만 와이파이를 대체할 수 있는 IoT 전용망이 전국적으로 구축되고 있다. 이는 유지비용 측면에서 최소 350원에서 최대 2,000원 정도로 저렴한 요금을 제공한다. 또 넓은 커버리지를 제공하여 본 제품이 상용화된다면 IoT 전용망을 통해 비용을 절감할 수 있다.<br />
현재 애플리케이션은 자판기 관리자용으로 한정적인 기능을 제공한다. 이를 보완하기 위해 다양한 오픈 API를 사용할 수 있다. 예를 들면 구글 MAP API를 통해 자판기의 위치 정보를 제공하여 사용자는 보다 쉽게 자판기 이용이 가능하고, 관리자는 용이하게 유지 보수를 할 수 있다.<br />
해당 자판기는 현재 일회용품을 사용하지 않는 것이 유일한 친환경적인 요소이다. 이를 확장하여 태양열 발전을 통한 전기 사용 등 친환경적인 요소를 추가적으로 접목시켜 환경 보호에 더욱 기여할 수 있다.</p>
<p><span style="color: #ff6600"><strong>10. 참고자료</strong></span><br />
· 파이낸셜 뉴스, &#8220;1인 가구 플라스틱&#8221;, https://www.fnnews.com/news/202009181711464621<br />
· 땅오니 로봇 코딩 블로그, &#8220;TCP/IP 소켓 통신&#8221;, https://ddangeun.tistory.com/31<br />
· 멈춤보단 천천히라도, &#8220;파이썬 소켓&#8221;, https://webnautes.tistory.com/1381<br />
· IT, 정보보안 자료실, &#8220;파이썬 멀티쓰레드&#8221;, https://nalara12200.tistory.com/153<br />
· 명월 일지, &#8220;소켓통신&#8221;, https://nowonbun.tistory.com/315<br />
· 토이메이커스, &#8220;아두이노 피에조 스피커“, https://blog.naver.com/yulian/221748202220<br />
· 뤼즈나의 IT 블로그, “아두이노 워터 펌프”, https://in-reason.tistory.com/16<br />
· 하이! 제니스, “아두이노 RFID&#8221;, https://m.blog.naver.com/chandong83/220920789808<br />
· 에듀이노 코딩 스쿨, “ESP8266&#8243;, https://blog.naver.com/PostView.nhn?blogId=eduino&amp;logNo=221152914869<br />
· 폴나의 공방, “아두이노 시리얼 통신”, https://m.blog.naver.com/darknisia/221234187170<br />
· 몽구스 프로그래밍, “아두이노 인터럽트”, https://m.blog.naver.com/PostView.nhn?blogId=yuyyulee&amp;logNo=220310875023&amp;proxyReferer=https:%2F%2Fwww.google.com%2F<br />
· YouTube, &#8220;아두이노 RFID&#8221;, https://www.youtube.com/watch?v=SQ IGilMagm0<br />
· 테크월드 뉴스, “젯슨 나노”, http://www.epnc.co.kr/news/articleView.html?idxno=95313<br />
· MANUAL FACTORY, &#8220;우분투 설치&#8221;, https://www.manualfactory.net/13400<br />
· DoProgramming, &#8220;젯슨나노&#8221;, https://doprogramming.tistory.com/category/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D/Jetson%20Nano<br />
· 제너럴공국, &#8220;젯슨나노 텐서플로우&#8221;, https://generalthird.tistory.com/m/15<br />
· 멈춤보단 천천히라도, &#8220;opencv&#8221;, https://webnautes.tistory.com/1186<br />
· 개발하는 도치, &#8220;Seleninum&#8221;, https://heodolf.tistory.com/48<br />
· 삶의 향기, &#8220;html 창 닫기&#8221;, https://lia47.tistory.com/995<br />
· 안드로이드 스튜디오, Android Emulator, https://developer.android.com/studio/run/emulator?hl=ko<br />
· Bugwhale World, 라즈베리파이 라즈비안 APM 설치하기 https://bugwhale.tistory.com/entry/raspberrypi-raspbian-apm-install<br />
· 정재곤, 「Do it! 안드로이드 앱 프로그래밍」, 이지스퍼블리싱, 2020</p>
<p><span style="color: #ff6600"><strong>11. 소스코드</strong></span><br />
<span style="color: #0000ff"><strong>MCU(아두이노) : MAGA</strong></span></p>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p>#include &lt;SoftwareSerial.h&gt;<br />
#include &#8220;WiFiEsp.h&#8221;<br />
#include &lt;TimerOne.h&gt;<br />
#include &lt;Wire.h&gt;<br />
#include &lt;EEPROM.h&gt;<br />
#include &lt;SPI.h&gt; // RFID를 위한 SPI 라이브러리<br />
#include &lt;MFRC522.h&gt;// RFID 라이브러리</p>
<p>#define DEBUG<br />
#define DEBUG_WIFI<br />
#define AP_SSID &#8220;embsystem2&#8243;<br />
#define AP_PASS &#8220;embsystem20&#8243;<br />
#define SERVER_NAME &#8220;192.168.1.20&#8243;<br />
#define SERVER_PORT 5000<br />
#define LOGID &#8220;ECO_ARD&#8221;<br />
#define PASSWD &#8220;PASSWD&#8221;<br />
#define CMD_SIZE 50<br />
#define ARR_CNT 5<br />
#define SS_PIN 53 //RFID SS(SDA:ChipSelect) PIN<br />
#define RST_PIN 5 //RFID Reset PIN<br />
#define led1 10<br />
#define led2 9<br />
#define led3 8<br />
#define WLV_PIN1 A0<br />
#define WLV_PIN2 A1<br />
#define WLV_PIN3 A2</p>
<p>//SoftwareSerial Serial2(6, 7);<br />
MFRC522 mfrc(SS_PIN, RST_PIN); //RFID 라이브러리<br />
//MFRC522::MIFARE_Key key;</p>
<p>char eepromPw[5]; //(eeprom에 저장된)비밀번호</p>
<p>char sendBuf[CMD_SIZE];<br />
bool timerIsrFlag = false;<br />
unsigned int secCount;<br />
int tag;</p>
<p>WiFiEspClient client;<br />
int drinkslt = 0; // 음료 선택<br />
boolean sltflag = false; // ok 수신 플래그</p>
<p>int CokeCount;<br />
int FantaCount;<br />
int SpriteCount;</p>
<p>int watervalue1;<br />
int watervalue2;<br />
int watervalue3;</p>
<p>int waterlv1 = 1;<br />
int waterlv2 = 1;<br />
int waterlv3 = 1;</p>
<p>int buzzflag = 0; // 부저<br />
int Checkflag = 0; // RFID</p>
<p>char received;</p>
<p>void isr1()<br />
{<br />
drinkslt = 1;<br />
// Serial2.write(&#8220;a&#8221;);<br />
}<br />
void isr2()<br />
{<br />
drinkslt = 2;<br />
// secCount2 = 0;<br />
// Serial2.write(&#8220;b&#8221;);<br />
}<br />
void isr3()<br />
{<br />
drinkslt = 3;<br />
// secCount3 = 0;<br />
// Serial2.write(&#8220;c&#8221;);<br />
}</p>
<p>void setup() {<br />
Serial.begin(9600); //DEBUG<br />
Serial2.begin(9600);<br />
wifi_Setup();<br />
SPI.begin(); // SPI 시작<br />
mfrc.PCD_Init(); // RF 모듈 시작</p>
<p>attachInterrupt(3, isr1, RISING); //20번<br />
attachInterrupt(0, isr2, RISING); //2번<br />
attachInterrupt(1, isr3, RISING); //3번</p>
<p>pinMode(led1, OUTPUT);<br />
pinMode(led2, OUTPUT);<br />
pinMode(led3, OUTPUT);</p>
<p>digitalWrite(led1, LOW);<br />
digitalWrite(led2, LOW);<br />
digitalWrite(led3, LOW);</p>
<p>Timer1.initialize(1000000);<br />
Timer1.attachInterrupt(timerIsr); // timerIsr to run every 1 seconds</p>
<p>readEeprom();<br />
}</p>
<p>void loop() {<br />
if (client.available()) {<br />
socketEvent();<br />
}<br />
if (timerIsrFlag)<br />
{<br />
timerIsrFlag = false;<br />
if (!(secCount % 5))<br />
{<br />
if (!client.connected()) {<br />
server_Connect();<br />
}<br />
}<br />
}</p>
<p>//카드가 인식 안되었다면 더이상 진행하지 말고 빠져나감<br />
if (mfrc.PICC_IsNewCardPresent())<br />
{<br />
if (mfrc.PICC_ReadCardSerial())<br />
{<br />
for (byte i = 0; i &lt; mfrc.uid.size; i++) {<br />
Serial.print(mfrc.uid.uidByte[i] &lt; 0&#215;10 ? &#8221; 0&#8243; : &#8221; &#8220;);<br />
Serial.print(mfrc.uid.uidByte[i], DEC);<br />
}<br />
Serial.println();<br />
Serial2.write(&#8216;x&#8217;);<br />
delay(500);<br />
mfrc.PICC_HaltA();<br />
}<br />
}</p>
<p>if (waterlv1 &amp;&amp; drinkslt == 1)<br />
{<br />
if ( sltflag )<br />
{<br />
Serial2.write(&#8216;a&#8217;);<br />
delay(500);<br />
digitalWrite(led2, LOW);<br />
digitalWrite(led3, LOW);<br />
sprintf(sendBuf , &#8220;[ECO_WEB]chooseok\n&#8221;);<br />
client.write(sendBuf, strlen(sendBuf));<br />
sltflag = false;<br />
CokeCount++;<br />
EEPROM.write(1, CokeCount);<br />
drinkslt = 0;<br />
}<br />
}<br />
else if (waterlv2 &amp;&amp; drinkslt == 2)<br />
{<br />
if ( sltflag )<br />
{<br />
Serial2.write(&#8216;b&#8217;);<br />
delay(500);<br />
digitalWrite(led1, LOW);<br />
digitalWrite(led3, LOW);<br />
sprintf(sendBuf , &#8220;[ECO_WEB]chooseok\n&#8221;);<br />
client.write(sendBuf, strlen(sendBuf));<br />
sltflag = false;<br />
FantaCount++;<br />
EEPROM.write(2, FantaCount);<br />
drinkslt = 0;<br />
}<br />
}<br />
else if (waterlv3 &amp;&amp; drinkslt == 3)<br />
{<br />
if ( sltflag )<br />
{<br />
Serial2.write(&#8216;c&#8217;);<br />
delay(500);<br />
digitalWrite(led1, LOW);<br />
digitalWrite(led2, LOW);<br />
sprintf(sendBuf , &#8220;[ECO_WEB]chooseok\n&#8221;);<br />
client.write(sendBuf, strlen(sendBuf));<br />
sltflag = false;<br />
SpriteCount++;<br />
EEPROM.write(3, SpriteCount);<br />
drinkslt = 0;<br />
}<br />
}</p>
<p>if (Serial2.available())<br />
{<br />
received = Serial2.read();<br />
Serial.println(received);<br />
if (received == &#8216;x&#8217;)<br />
{<br />
sprintf(sendBuf , &#8220;[ECO_CAM]camon\n&#8221;);<br />
client.write(sendBuf, strlen(sendBuf));<br />
sprintf(sendBuf , &#8220;[ECO_WEB]camon\n&#8221;);<br />
client.write(sendBuf, strlen(sendBuf));<br />
drinkslt = 0;<br />
}<br />
else if (received == &#8216;o&#8217;)<br />
{<br />
digitalWrite(led3, LOW);<br />
digitalWrite(led1, LOW);<br />
digitalWrite(led2, LOW);<br />
delay(100);<br />
}<br />
}<br />
}</p>
<p>void socketEvent()<br />
{<br />
int i = 0;<br />
char * pToken;<br />
char * pArray[ARR_CNT] = {0};<br />
char recvBuf[CMD_SIZE] = {0};<br />
int len;</p>
<p>Serial.print(&#8220;socket ok &#8220;);<br />
sendBuf[0] = &#8221;;<br />
len = client.readBytesUntil(&#8216;\n&#8217;, recvBuf, CMD_SIZE);<br />
client.flush();<br />
#ifdef DEBUG<br />
Serial.print(&#8220;recv : &#8220;);<br />
Serial.println(recvBuf);<br />
#endif<br />
pToken = strtok(recvBuf, &#8220;[@]&#8220;);<br />
while (pToken != NULL)<br />
{<br />
pArray[i] = pToken;<br />
if (++i &gt;= ARR_CNT)<br />
break;<br />
pToken = strtok(NULL, &#8220;[@]&#8220;);<br />
}<br />
if (!strncmp(pArray[1], &#8221; New&#8221;, 4)) // New Connected<br />
{<br />
Serial.write(&#8216;\n&#8217;);<br />
return ;<br />
}<br />
else if (!strncmp(pArray[1], &#8221; Alr&#8221;, 4)) //Already logged<br />
{<br />
Serial.write(&#8216;\n&#8217;);<br />
client.stop();<br />
server_Connect();<br />
return ;<br />
}<br />
else if (!strncmp(pArray[1], &#8220;camok&#8221;, 5)) //rfid ok<br />
{<br />
waterlv_check();<br />
sltflag = true;<br />
drinkslt = 0;<br />
digitalWrite(led1, HIGH);<br />
digitalWrite(led2, HIGH);<br />
digitalWrite(led3, HIGH);<br />
if (!waterlv1)<br />
digitalWrite(led1, LOW);<br />
if (!waterlv2)<br />
digitalWrite(led2, LOW);<br />
if (!waterlv3)<br />
digitalWrite(led3, LOW);<br />
if (!waterlv1 &amp;&amp; !waterlv2 &amp;&amp; !waterlv3)<br />
{<br />
Serial2.write(&#8216;d&#8217;);<br />
sprintf(sendBuf, &#8220;[ECO_WEB]empty\n&#8221;);<br />
client.write(sendBuf, strlen(sendBuf));<br />
}<br />
return ;<br />
}<br />
else if (!strncmp(pArray[1], &#8220;error&#8221;, 5)) //Error<br />
{<br />
Serial.write(&#8216;\n&#8217;);<br />
Serial2.write(&#8216;d&#8217;);<br />
return ;<br />
}<br />
else if (!strncmp(pArray[1], &#8220;STATE&#8221;, 5)) //State<br />
{<br />
waterlv_check();<br />
sprintf(sendBuf, &#8220;[ECO_APP]DRINK@%d@%d@%d\n&#8221;, waterlv1, waterlv2, waterlv3);<br />
client.write(sendBuf, strlen(sendBuf));<br />
return;<br />
}<br />
else if (!strncmp(pArray[1], &#8220;RENEWAL&#8221;, 7)) //Renewal<br />
{<br />
sprintf(sendBuf, &#8220;[ECO_APP]SELL@%d@%d@%d\n&#8221;, CokeCount, FantaCount, SpriteCount);<br />
client.write(sendBuf, strlen(sendBuf));<br />
return;<br />
}<br />
else if (!strncmp(pArray[1], &#8220;RESET&#8221;, 5)) //Count Reset<br />
{<br />
CokeCount = 0;<br />
EEPROM.write(1, CokeCount);<br />
FantaCount = 0;<br />
EEPROM.write(2, FantaCount);<br />
SpriteCount = 0;<br />
EEPROM.write(3, SpriteCount);<br />
sprintf(sendBuf, &#8220;[ECO_APP]SELL@%d@%d@%d\n&#8221;, CokeCount, FantaCount, SpriteCount);<br />
client.write(sendBuf, strlen(sendBuf));<br />
return;<br />
}<br />
else {<br />
sprintf(sendBuf, &#8220;[%s]%s@%s\n&#8221;, pArray[0], pArray[1], pArray[2]);<br />
}<br />
client.write(sendBuf, strlen(sendBuf));<br />
client.flush();</p>
<p>#ifdef DEBUG<br />
Serial.print(&#8220;, send : &#8220;);<br />
Serial.print(sendBuf);<br />
#endif<br />
}</p>
<p>void timerIsr()<br />
{<br />
timerIsrFlag = true;<br />
secCount++;<br />
}<br />
void wifi_Setup() {<br />
Serial3.begin(9600);<br />
wifi_Init();<br />
server_Connect();<br />
}<br />
void wifi_Init()<br />
{<br />
do {<br />
WiFi.init(&amp;Serial3);<br />
if (WiFi.status() == WL_NO_SHIELD) {<br />
#ifdef DEBUG_WIFI<br />
Serial.println(&#8220;WiFi shield가 존재하지 않습니다.&#8221;);<br />
#endif<br />
}<br />
else<br />
break;<br />
} while (1);</p>
<p>#ifdef DEBUG_WIFI<br />
Serial.print(&#8220;WiFi 연결을 시도합니다.&#8221;);<br />
//Serial.println(AP_SSID);<br />
#endif<br />
while (WiFi.begin(AP_SSID, AP_PASS) != WL_CONNECTED) {</p>
<p>#ifdef DEBUG_WIFI<br />
Serial.print(&#8220;WiFi 연결을 시도합니다.&#8221;);<br />
//Serial.println(AP_SSID);<br />
#endif<br />
}</p>
<p>#ifdef DEBUG_WIFI<br />
Serial.println(&#8220;WiFi 연결에 성공하였습니다.&#8221;);<br />
printWifiStatus();<br />
#endif<br />
}<br />
int server_Connect()<br />
{<br />
#ifdef DEBUG_WIFI<br />
Serial.println(&#8220;Server 연결을 시도합니다.&#8221;);</p>
<p>#endif</p>
<p>if (client.connect(SERVER_NAME, SERVER_PORT)) {<br />
#ifdef DEBUG_WIFI<br />
Serial.println(&#8220;Server 연결에 성공하였습니다.&#8221;);<br />
#endif<br />
client.print(&#8220;["LOGID":"PASSWD"]&#8220;);<br />
}<br />
else<br />
{<br />
#ifdef DEBUG_WIFI<br />
Serial.println(&#8220;Server 연결에 실패하였습니다.&#8221;);<br />
#endif<br />
}<br />
}<br />
void printWifiStatus()<br />
{<br />
// print the SSID of the network you&#8217;re attached to<br />
//Serial.print(&#8220;SSID: &#8220;);<br />
//Serial.println(WiFi.SSID());<br />
// print your WiFi shield&#8217;s IP address<br />
IPAddress ip = WiFi.localIP();<br />
//Serial.print(&#8220;IP Address: &#8220;);</p>
<p>Serial.println(ip);<br />
// print the received signal strength<br />
long rssi = WiFi.RSSI();<br />
//Serial.print(&#8220;Signal strength (RSSI):&#8221;);<br />
//Serial.print(rssi);<br />
//Serial.println(&#8221; dBm&#8221;);<br />
}</p>
<p>//EEPROM에 저장된 판매량을 읽어옴<br />
void readEeprom() {<br />
CokeCount = EEPROM.read(1);<br />
// Serial.print(CokeCount);<br />
FantaCount = EEPROM.read(2);<br />
//Serial.print(FantaCount);<br />
SpriteCount = EEPROM.read(3);<br />
//Serial.print(SpriteCount);<br />
}<br />
void waterlv_check()<br />
{<br />
watervalue1 = analogRead(WLV_PIN1);<br />
watervalue2 = analogRead(WLV_PIN2);<br />
watervalue3 = analogRead(WLV_PIN3);<br />
Serial.println(watervalue1);<br />
if (watervalue1 &gt;= 1000)<br />
{<br />
waterlv1 = 0;<br />
}<br />
else<br />
{<br />
waterlv1 = 1;<br />
}<br />
Serial.println(watervalue2);<br />
if (watervalue2 &gt;= 1000)<br />
{<br />
waterlv2 = 0;<br />
}<br />
else<br />
{<br />
waterlv2 = 1;<br />
}<br />
Serial.println(watervalue3);<br />
if (watervalue3 &gt;= 1000)<br />
{<br />
waterlv3 = 0;<br />
}<br />
else<br />
{<br />
waterlv3 = 1;<br />
}<br />
}<br />
</div>
<p><span style="color: #0000ff"><strong>MCU(아두이노) : UNO</strong></span></p>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p>#include &lt;SoftwareSerial.h&gt;<br />
#include &lt;SPI.h&gt;<br />
#include &lt;TimerOne.h&gt;<br />
#define buzz 5<br />
SoftwareSerial rfid(6, 7);<br />
bool timerIsrFlag = false;<br />
unsigned int secCount1 = 11;<br />
unsigned int secCount2 = 11;<br />
unsigned int secCount3 = 11;<br />
//static boolean selectmod1 = false; //인터럽트</p>
<p>char receved; // serial 통신 수신 값</p>
<p>int Relaypin1 = 8;<br />
int Relaypin2 = 9;<br />
int Relaypin3 = 10;</p>
<p>int buzzflag = 0; // 부저<br />
int Checkflag = 0; // RFID</p>
<p>void setup()<br />
{<br />
// put your setup code here, to run once:<br />
Serial.begin(9600); //DEBUG<br />
rfid.begin(9600);<br />
SPI.begin(); // SPI 시작</p>
<p>pinMode(Relaypin1, OUTPUT); // 릴레이를 출력으로 설정<br />
pinMode(Relaypin2, OUTPUT);<br />
pinMode(Relaypin3, OUTPUT);<br />
Timer1.initialize(1000000);<br />
Timer1.attachInterrupt(timerIsr);</p>
<p>digitalWrite(Relaypin1, HIGH);<br />
digitalWrite(Relaypin2, HIGH);<br />
digitalWrite(Relaypin3, HIGH);</p>
<p>Serial.println(&#8220;연진아 준비해라&#8221;);<br />
}</p>
<p>void loop() {<br />
if (rfid.available())<br />
{<br />
if (Checkflag == 0)<br />
{<br />
receved = rfid.read();<br />
if (receved == &#8216;x&#8217;)<br />
{<br />
Serial.println(receved);<br />
Serial.println(&#8220;x 재송신 후 다음 동작 대기&#8221;);<br />
rfid.write(&#8216;x&#8217;);<br />
Checkflag = 1;<br />
}<br />
}<br />
if (Checkflag == 1)<br />
{<br />
receved = rfid.read();<br />
if (receved == &#8216;a&#8217;)<br />
{<br />
secCount1 = 0;<br />
Serial.println(receved);<br />
Serial.println(&#8220;물펌프a 동작&#8221;);<br />
digitalWrite(Relaypin1, LOW); // 1채널 릴레이 ON<br />
}<br />
else if (receved == &#8216;b&#8217;)<br />
{<br />
secCount2 = 0;<br />
Serial.println(receved);<br />
Serial.println(&#8220;물펌프b 동작&#8221;);<br />
digitalWrite(Relaypin2, LOW); // 2채널 릴레이 ON<br />
}<br />
else if (receved == &#8216;c&#8217;)<br />
{<br />
secCount3 = 0;<br />
Serial.println(receved);<br />
Serial.println(&#8220;물펌프c 동작&#8221;);<br />
digitalWrite(Relaypin3, LOW); // 3채널 릴레이 ON<br />
}<br />
else if (receved == &#8216;d&#8217;)<br />
{<br />
Serial.println(receved);<br />
Checkflag = 0;<br />
}<br />
}<br />
}<br />
if (buzzflag == 1)<br />
{<br />
tone(buzz, 262, 500); // 도<br />
delay(400);<br />
tone(buzz, 330, 500); // 미<br />
delay(400);<br />
tone(buzz, 392, 500); // 솔<br />
delay(400);<br />
tone(buzz, 523, 500); // 높은 도<br />
delay(400);<br />
rfid.write(&#8216;o&#8217;);<br />
delay(500);<br />
Checkflag = 0;<br />
Serial.println(Checkflag);<br />
buzzflag = 0;<br />
}<br />
}</p>
<p>void timerIsr()<br />
{<br />
timerIsrFlag = true;<br />
secCount1++;<br />
secCount2++;<br />
secCount3++;<br />
if (secCount1 == 10 )<br />
{<br />
digitalWrite(Relaypin1, HIGH); // 1채널 릴레이<br />
buzzflag = 1;<br />
Serial.println(buzzflag);<br />
}<br />
else if (secCount2 == 10 )<br />
{<br />
digitalWrite(Relaypin2, HIGH); // 2채널 릴레이<br />
buzzflag = 1;<br />
}<br />
else if (secCount3 == 10 )<br />
{<br />
digitalWrite(Relaypin3, HIGH); // 3채널 릴레이<br />
buzzflag = 1;<br />
}<br />
}</p>
</div>
<p>&nbsp;</p>
]]></content:encoded>
			<wfw:commentRss>http://www.ntrexgo.com/archives/40945/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>[68호]스마트 쇼핑 카트</title>
		<link>http://www.ntrexgo.com/archives/40955</link>
		<comments>http://www.ntrexgo.com/archives/40955#comments</comments>
		<pubDate>Mon, 25 Oct 2021 00:00:26 +0000</pubDate>
		<dc:creator>디바이스마트 매거진</dc:creator>
				<category><![CDATA[디바이스마트 매거진]]></category>
		<category><![CDATA[특집]]></category>
		<category><![CDATA[ict]]></category>
		<category><![CDATA[ict공모전]]></category>
		<category><![CDATA[공모전]]></category>
		<category><![CDATA[디바이스마트]]></category>
		<category><![CDATA[매거진]]></category>
		<category><![CDATA[수상작]]></category>
		<category><![CDATA[융합]]></category>
		<category><![CDATA[프로젝트]]></category>

		<guid isPermaLink="false">http://www.ntrexgo.com/?p=40955</guid>
		<description><![CDATA[디바이스마트 매거진 68호 &#124; 거동이 불편한 사람들의 오프라인 쇼핑을 도울 수 있는 카트를 개발하여, 기존 쇼핑 방식보다 쉽고, 편리하고, 안전한 쇼핑을 제공하여 더 나은 소비 활동을 할 수 있도록 돕는다. ]]></description>
				<content:encoded><![CDATA[<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-42.png" rel="lightbox[40955]"><img class="alignnone size-large wp-image-41109" alt="68 ict_ 스마트쇼핑카트 (42)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-42-620x202.png" width="620" height="202" /></a></p>
<p><span style="font-size: medium"><strong>2021 ICT 융합 프로젝트 공모전 우수상</strong></span></p>
<p><span style="font-size: x-large"><strong><span style="color: #ff6600">스마트 쇼핑 카트</span><br />
</strong></span></p>
<p style="text-align: right">글 | 영남대학교 양성은, 박유나, 이유진, 김형덕</p>
<p><span style="color: #ff6600"><strong>1. 심사평</strong></span><br />
<strong>칩센</strong> 작품은 상품의 구매와 위치 측량의 두 가지 큰 목표로 보입니다. 우선 상품 취득과 결제 부분에 있어서는 목표량을 어느 정도 달성한 것으로 보입니다. 또 다른 목표였던 위치측량 관련하여 상품의 위치를 통한 navigating 기능을 구현하기 위하여 사용자의 위치를 측정해야 하는 구조로 이해됩니다. 작품을 개발하며 목표로 한 내역에 대하여는 어느정도 위치 측량을 성공하였다고 볼수도 있지만, 실제로 상용화에 있어서는 위치 측량에 대한 많은 변수들이 존재하여 실내의 Map이 함께 조합되어 정확한 실내 위치를 파악하게 하는 등의 다양한 방식의 알고리즘과 방안이 적용되고 있고, 이러한 부분에 대한 추가적인 연구 검토를 한다면 더욱 개선된 제품을 제작할 수 있을 것으로 보입니다.<br />
<strong></strong></p>
<p><strong>펌테크</strong> 아이디어와 실용성이 돋보이며 짜임새 있게 잘 구성한 수준급 작품이라고 생각합니다. 실내 위치 측정을 위한 비컨 연동 과정에서의 시행착오가 있었음에도 불구하고 전체적으로 시스템 구성을 위한 각각의 난이도 있는 소프트웨어 구성을 효율적으로 접목하여 기획의도에 맞게 시스템을 안정적이고 완성도 높게 구현된 스마트카트 제품이라고 생각이 듭니다. 추후 작품 완성도를 높인다면 상업적으로도 충분히 활용될 수 있는 상품성을 가진 훌륭한 작품이 되리라 생각됩니다.<br />
<strong></strong></p>
<p><strong>위드로봇</strong> 학습 용도로는 좋은 주제이지만, 실용적인 측면에서는 위치 측량, 결제 등 해결해야 할 문제가 많은 작품입니다.<br />
<strong></strong></p>
<p><strong>뉴티씨</strong> 배려심이 느껴지는 작품입니다. 코로나19로 여러 가지로 어려운 상황에서, 같은 작품을 해도 어떤 마음으로 하는 가에 따라서, 의도가 다르므로 전혀 다른 결과가 나올 수 있습니다. 그런데, 몸을 움직이는 것이 불편하신 어르신들을 생각하여, 자동으로 계산이 되는 스마트쇼핑카트를 생각할 수 있었다는 것이 매우 중요한 가치라고 생각됩니다. 스마트 쇼핑카트라는 것은 오래전부터 인기 있는 작품의 주제로 만들어져왔지만, 이 작품은 비콘을 이용한 삼각측량법으로 위치인식까지 고려하여 제작하여 제품을 빠른 경로로 찾으러 갈 수 있도록 하는 부분도 함께 작성되었네요. 매우 좋은 작품을 제작하였습니다. 다양한 분야에서 활용될 수 있을 것 같고, 학생들이 좀 더 힘내서, 좋은 분야로의 응용을 기대하겠습니다.</p>
<p><span style="color: #ff6600"><strong>2. 작품 개요</strong></span><br />
스마트 쇼핑 카트는 거동이 불편한 사람들에게 편리한 쇼핑 환경을 제공해 주기 위한 보조 장치이다. 이 프로젝트를 생각하게 된 계기는 마트에서 쇼핑을 하다가 물건이 많아 카트를 끌고 계셨지만, 몸이 불편하셔서 카트를 끌기조차 매우 어려워 보이는 분을 보고 ‘거동이 불편하신 분들도 대형마트에서 편하게 쇼핑을 할 수 있게 할 방법이 없을까?’ 라고 생각하다가 개발하게 됐다. 프로젝트의 구현으로는 대형 마트 내에서 실내 측위 시스템을 사용하여 구매하고자 하는 물건의 위치까지 최단 경로를 알려주어 효율적인 쇼핑을 가능하게 한다. 카트에 부착된 바코드 스캐너에 구매 물품의 바코드를 찍으면 데이터베이스 내에 존재하는 물품의 정보를 자동으로 관리할 수 있게 하고, 구매 상품의 행사 정보를 음성으로 알려준다. 결제 버튼을 누를 시, App에서 간단하게 결제할 수 있도록 한다. 현재의 전동 카트는 원하는 상품 코너로 이동하는 것 정도로 발전되어 있는데, 거기에 우리 팀의 프로젝트는 최단거리 알고리즘(Traveling Salesman’s Problem)을 적용하고, 결제 서비스까지 연동하여 사물 인터넷 개념을 적용해 현대에 어울리는 더욱 스마트한 카트로 발전시킨다.<br />
<strong></strong></p>
<p><span style="color: #3366ff"><strong>2.1. 개발 목표</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-1.png" rel="lightbox[40955]"><img class="alignnone size-full wp-image-41066" alt="68 ict_ 스마트쇼핑카트 (1)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-1.png" width="553" height="188" /></a><br />
거동이 불편한 사람들의 오프라인 쇼핑을 도울 수 있는 카트를 개발하여, 기존 쇼핑 방식보다 쉽고(easy), 편리하고(convenient), 안전한(safe) 쇼핑을 제공하여 더 나은 소비 활동을 할 수 있도록 돕는다. 더 나아가 스마트 카트가 보급화 된다면 거동이 불편한 사람뿐만 아니라, 바쁜 현대인들에게도 최단 경로로 원하는 물품이 어디에 있는지 바로바로 알 수 있고, 결제까지 한 번에 해결하는 효율적인 쇼핑 수단이 될 것이다.<br />
<strong></strong></p>
<p><span style="color: #3366ff"><strong>2.2. 개발 작품의 필요성</strong></span><br />
일반 사람들도 쇼핑을 할 때 원하는 물건이 어디 있는지 자주 구매하는 물품이 아니라면 알기 어려운데, 거동이 불편한 사람들이나 장시간 쇼핑이 어려운 노약자들은 그것으로 인해 불편한 몸을 이끌고 물건을 가지러 갔다가 헤매고 다시 계산하러 계산대까지 이동한다면, 일반 사람들보다 몇 배는 힘들고 제한될 것이다. 하지만 우리 프로젝트를 통해 이동 동선을 최소화하고, 시선이 닿지 않는 곳에 기재된 할인 정보를 음성으로 접할 수 있게 됨과 동시에 결제까지 스마트 카트 하나로 끝낼 수 있어서 보통 사람들처럼 불편함 없는 쇼핑이 가능하여 더 나은 쇼핑이 가능하게 한다.</p>
<p><span style="color: #3366ff"><strong>2.3. 기대효과</strong></span><br />
· 거동이 불편한 사람들이 쇼핑에서 불편함을 느낄 수 있는 부분들을 지원하여 더욱 나은 쇼핑 생활을 가능케 한다.<br />
· 스마트 쇼핑 카트가 여기에서 더 나아가 발전하고 보급된다면 일반 사람들도 기존의 쇼핑 방식 보다 더욱 효율적인 쇼핑 할 수 있다.<br />
· 탈부착이 가능하도록 구성하여 유연성을 가지고 있으며, 효율적인 활용이 가능하다.<br />
· 실내 위치 측위의 발전 가능성을 기대할 수 있다.</p>
<p><span style="color: #ff6600"><strong>3. 작품 설명</strong></span><br />
<span style="color: #3366ff"><strong>3.1. 주요 동작 및 특징</strong></span><br />
<span style="color: #33cccc"><strong>3.1.1. 주요 기능 소개</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-2.png" rel="lightbox[40955]"><img class="alignnone size-full wp-image-41068" alt="68 ict_ 스마트쇼핑카트 (2)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-2.png" width="620" height="289" /></a><br />
전체적 구성은 라즈베리파이에 모니터와 바코드스캐너와 스피커가 연결되어있다. 이는 쇼핑 카트에 탈부착 할 수 있으며 또한 결제 어플과 연동된다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-3.png" rel="lightbox[40955]"><img class="alignnone size-full wp-image-41070" alt="68 ict_ 스마트쇼핑카트 (3)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-3.png" width="620" height="289" /></a></p>
<p>기능 1(실내 측위): RF모듈의 RSSI 값으로 삼변측량법을 이용한다. Raspberry Pi가 3개의 RF모듈을 통해 얻은 RSSI 값으로 거리를 구하여 마트 내에서 구매자의 위치를 파악한다. 파악한 위치를 통해 구매자와 구매하고자 하는 상품코너와의 거리를 파악할 수 있다.<br />
기능 2(최단 경로 안내): 삽변측량법과 같이 Traveling Salesman’s Problem을 이용하여 사용자가 물품 구매 시 최단 경로로 이동할 수 있도록 한다. 구매하고자 하는 상품들을 선택할 시, 실내 측위 기술을 통해 구매자의 위치를 기준으로 가장 짧고 빠르게 상품까지 도달할 수 있는 경로를 제공한다.<br />
기능 3(장바구니 &amp; 상품 Database 설명): 바코드 스캔을 통해 데이터베이스를 조회하여 상품의 행사 정보를 음성으로 알려주고 장바구니에 담긴 구매 물품들을 자동으로 관리할 수 있게 한다. 또 디스플레이로 장바구니 목록을 보여줌으로써 삭제 또는 추가 등의 자유로운 수정이 가능하다.<br />
기능 4(결제기능): 카트에 부착된 모니터에서 결제버튼을 누르게 되면, 연동된 어플리케이션에서 결제가 가능하다.<br />
<strong></strong></p>
<p><span style="color: #33cccc"><strong>3.1.2. RSSI 알고리즘 동작 및 특징</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-1.jpg" rel="lightbox[40955]"><img class="alignnone size-full wp-image-41065" alt="68 ict_ 스마트쇼핑카트 (1)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-1.jpg" width="169" height="254" /></a></p>
<p>RF 비콘을 3개 생성하여 삼각측량법에 이용할 수 있도록 구성하였다. 비콘의 신호를 감지하기 위해 라즈베리파이를 오른쪽과 같이 비콘 수신기로 구성하였다. 비콘의 신호를 수신하여 rssi값으로 거리를 측정하고 현재의 위치를 추적할 수 있도록 한다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-4.png" rel="lightbox[40955]"><img class="alignnone size-full wp-image-41071" alt="68 ict_ 스마트쇼핑카트 (4)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-4.png" width="620" height="307" /></a></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-5.png" rel="lightbox[40955]"><img class="alignnone size-full wp-image-41072" alt="68 ict_ 스마트쇼핑카트 (5)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-5.png" width="620" height="197" /></a><br />
Raspberry Pi가 RF Beacon에게 Data를 얻어와 TXpower와 RSSI값을 분리하여, RSSI에 따른 거리를 구한다. 3개의 비콘 모두 거리를 구해, 삼변측량법을 사용하여 목표위치를 파악할 수 있다.<br />
비콘의 RSSI 값을 이용하여 직선거리를 구하는 공식을 이용하였습니다. 공식을 적용하기 위 TX Power 와 RSSI 값을 정제하여 거리 공식에 대입하였다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-6.png" rel="lightbox[40955]"><img class="alignnone size-full wp-image-41073" alt="68 ict_ 스마트쇼핑카트 (6)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-6.png" width="620" height="197" /></a></p>
<p>3개 비콘의 각각의 좌표와 측정한 직선거리를 이용하여 삼변 측량법을 적용해 사용자의 현재 좌표를 출력하였다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-7.png" rel="lightbox[40955]"><img class="alignnone size-full wp-image-41074" alt="68 ict_ 스마트쇼핑카트 (7)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-7.png" width="620" height="161" /></a></p>
<p><span style="color: #33cccc"><strong>3.1.3. 최단 경로 안내 알고리즘 동작 및 특징</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-8.png" rel="lightbox[40955]"><img class="alignnone size-full wp-image-41075" alt="68 ict_ 스마트쇼핑카트 (8)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-8.png" width="620" height="341" /></a></p>
<p>TSP(Traveling Salesperson Problem)을 이용하였다. TSP 알고리즘이란 원래 여러 도시들이 있을 때 한 도시로부터 시작해서 모든 도시를 단 한 번씩만 방문하여 다시 시작점으로 돌아오는데 드는 최단거리를 구하는 문제이다. 여기서 우리는 도시를 상품의 코너로 가정하였다. 입구에서 출발하여 각 선택된 코너들을 거치고 다시 입구로 돌아오는 식으로 최단경로 안내 문제를 해결하였다. 중간에 생각하지 못했던 필요한 물품이 생길 시 코너를 추가 선택한 후 안내를 누르면 다시 그 자리에서 시작하는 최단 경로를 시각화 하여 카트에 부착된 모니터로 안내해 준다. 전수 조사 방식으로 진행하여서 각 경로를 기억하기 위해 스택을 사용했다. 거리를 구하고 스택에서 각 경로의 거리 중 가장 짧은 경로를 선정하여 사용자에게 안내해 준다.<br />
<strong></strong></p>
<p><span style="color: #33cccc"><strong>3.1.4. 장바구니 &amp; 상품 Database 알고리즘 동작 및 특징</strong></span><br />
<span style="color: #339966"><strong>상품 Database:</strong> </span>먼저 MySql DB를 라즈베리파이에 설치한다. 명령어를 통해 데이터베이스를 생성한 후 데이터베이스 내에서 테이블을 생성한다. 테이블을 생성할 때에 바코드번호, 상품 이름, 상품 가격을 받아오도록 자료형을 지정해주고 테이블을 만들어준다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-9.png" rel="lightbox[40955]"><img class="alignnone size-full wp-image-41076" alt="68 ict_ 스마트쇼핑카트 (9)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-9.png" width="591" height="165" /></a></p>
<p><span style="color: #339966"><strong>장바구니 기능:</strong></span> while문을 통해 항시 입력을 대기하며, 바코드 스캐너로 물품의 바코드를 찍으면 Database Table에서 해당 물품의 정보를 가져와 목록에 저장을 한다. 생성해 놓은 데이터베이스를 파이썬과 연동하여 데이터베이스를 조회할 수 있도록 구성하였다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-10.png" rel="lightbox[40955]"><img class="alignnone size-full wp-image-41077" alt="68 ict_ 스마트쇼핑카트 (10)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-10.png" width="588" height="108" /></a><br />
연동 방법은 pymysql이라는 파이썬 라이브러리를 설치해주었다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-11.png" rel="lightbox[40955]"><img class="alignnone size-full wp-image-41078" alt="68 ict_ 스마트쇼핑카트 (11)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-11.png" width="586" height="105" /></a></p>
<p>저장한 목록은 디스플레이에 출력하여 사용자가 구매할 물품이 장바구니에 담긴 것을 시각정인 정보로 제공한다. Pandas를 이용하여 시각화하였다. 아래의 사진을 보면 시각화된 모습을 볼 수 있다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-12.png" rel="lightbox[40955]"><img class="alignnone size-full wp-image-41079" alt="68 ict_ 스마트쇼핑카트 (12)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-12.png" width="589" height="479" /></a></p>
<p>장바구니에 담긴 물품들 중, 구매하지 않을 물품을 삭제할 때 입력창에 1을 입력하면 delete mode로 바뀐다. 디스플레이에서 제공하는 상품의 목록을 통해 삭제할 물품의 인덱스 번호를 입력하면 쉽게 삭제가 가능하다.</p>
<p><span style="color: #339966"><strong>행사 정보 안내 기능:</strong></span> 사용자에게 물품의 행사 정보를 쉽고, 간편하게 제공할 수 있는 방법에는 음성 안내 기능이 있다. 구매하려는 물품의 바코드를 스캔하면, 장바구니 목록에 담김과 동시에 해당 물품의 행사 정보를 음성으로 안내해준다. 음성 안내 기능은 TTS(Text to Sound)로 구현하였으며, 문자열로 넘겨주도록 했다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-13.png" rel="lightbox[40955]"><img class="alignnone size-full wp-image-41080" alt="68 ict_ 스마트쇼핑카트 (13)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-13.png" width="620" height="161" /></a></p>
<p><span style="color: #33cccc"><strong>3.1.5. 결제기능 동작 및 특징</strong></span><br />
Application과 Raspberry Pi간의 소켓통신으로 장바구니에 담긴 최종 금액을 APP으로 넘겨준다.(소스코드 [첨부 19] 참고) 통신 방식은 소켓통신을 이용하였다. 라즈베리파이가 클라이언트가 되도록 구성하고 App이 서버가 되도록 구성하였다. App에서 제공해주는 IP를 입력한 후 데이터를 전송하게 된다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-14.png" rel="lightbox[40955]"><img class="alignnone size-full wp-image-41081" alt="68 ict_ 스마트쇼핑카트 (14)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-14.png" width="323" height="417" /></a><br />
넘겨받은 데이터를 토대로 결제 기능을 제공하며, 해당 결제 기능은 안드로이드 스튜디오에 Bootpay SDK를 연동하여 결제 테스트를 할 수 있도록 구성하였다. 안드로이드용 Application ID를 Bootpay 관리자에서 가져와 연동하였다. 안드로이드 라이브러리는 내부적으로 WebView를 이용하여 구현되어 있다. Javascript SDK를 안드로이드 WebView에서 구현한 방식으로, 결제연동, 결제결과에 대한 라이프 사이클 함수가 제공된다. 따라서 안드로이드 레이아웃에 View를 줌으로써 결제 창을 띄우도록 구성하였다.<br />
<strong></strong></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-15.png" rel="lightbox[40955]"><img class="alignnone size-full wp-image-41082" alt="68 ict_ 스마트쇼핑카트 (15)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-15.png" width="620" height="352" /></a></p>
<p><span style="color: #33cccc"><strong>3.1.6 프로그램 사용법</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-16.png" rel="lightbox[40955]"><img class="alignnone size-full wp-image-41083" alt="68 ict_ 스마트쇼핑카트 (16)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-16.png" width="594" height="90" /></a><br />
처음 모드선택에서 0번을 선택할 시 경로안내 모드로 들어가게 된다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-2.jpg" rel="lightbox[40955]"><img class="alignnone size-full wp-image-41067" alt="68 ict_ 스마트쇼핑카트 (2)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-2.jpg" width="589" height="508" /></a></p>
<p>화면에서 처음에 방문하고자 하는 코너를 선택할 수 있다. 코너를 다 선택하면 최단경로가 시각적 정보로 사용자에게 안내된다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-17.png" rel="lightbox[40955]"><img class="alignnone size-full wp-image-41084" alt="68 ict_ 스마트쇼핑카트 (17)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-17.png" width="591" height="69" /></a></p>
<p>안내 후 다시 모드선택으로 되돌아온다. 여기서 1번을 선택하게 되면 장바구니 모드로 들어가게 된다. 장바구니 모드에서 바코드 스캔을 통해 장바구니를 추가할지 구성된 장바구니 목록을 삭제할지 결제 할지를 선택할 수 있다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-18.png" rel="lightbox[40955]"><img class="alignnone size-full wp-image-41085" alt="68 ict_ 스마트쇼핑카트 (18)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-18.png" width="588" height="113" /></a></p>
<p>&nbsp;</p>
<p>먼저, 바코드를 스캔하면 TTS(Text to Speech)로 상품의 행사정보를 사용자에게 음성안내를 해준다. 모니터에는 상품이 추가된 장바구니 목록을 볼 수 있다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-19.png" rel="lightbox[40955]"><img class="alignnone size-full wp-image-41086" alt="68 ict_ 스마트쇼핑카트 (19)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-19.png" width="587" height="75" /></a></p>
<p>다른 상품의 바코드를 찍으면, 장바구니 목록에 새로운 상품이 추가된 것을 볼 수 있다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-20.png" rel="lightbox[40955]"><img class="alignnone size-full wp-image-41087" alt="68 ict_ 스마트쇼핑카트 (20)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-20.png" width="590" height="227" /></a></p>
<p>같은 상품을 여러 번 찍으면 목록이 새로 추가되는 것이 아니라 해당 상품의 수량만 늘어나는 것을 볼 수 있다.<br />
바코드 스캔 대신 1을 2번 입력하면 삭제 모드로 들어간다. 현재까지 구성된 장바구니 목록을 보여주며 삭제하고자 하는 목록의 인덱스를 입력하면 삭제가 된다. 다음 사진을 보면 삭제가 되는 것을 확인할 수 있다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-21.png" rel="lightbox[40955]"><img class="alignnone size-full wp-image-41088" alt="68 ict_ 스마트쇼핑카트 (21)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-21.png" width="587" height="80" /></a></p>
<p>마찬가지로 바코드 스캔 대신 2를 2번 입력하면 결제 모드로 들어가게 된다. 앱에서 제공하는 IP정보를 입력하면 결제정보를 App으로 전송하며 결제 가능하다. 다음 사진을 보면 결제가 청구된 것을 확인할 수 있다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-22.png" rel="lightbox[40955]"><img class="alignnone size-full wp-image-41089" alt="68 ict_ 스마트쇼핑카트 (22)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-22.png" width="236" height="224" /></a></p>
<p><span style="color: #33cccc"><strong>3.1.7 전체 알고리즘 동작 및 특징</strong></span><br />
<strong></strong></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-23.png" rel="lightbox[40955]"><img class="alignnone size-full wp-image-41090" alt="68 ict_ 스마트쇼핑카트 (23)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-23.png" width="576" height="415" /></a></p>
<p><span style="color: #3366ff"><strong>3.2. 전체 시스템 구성</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-24.png" rel="lightbox[40955]"><img class="alignnone size-full wp-image-41091" alt="68 ict_ 스마트쇼핑카트 (24)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-24.png" width="620" height="315" /></a><br />
<span style="color: #33cccc"><strong>3.2.1 하드웨어 구성</strong></span><br />
· Raspberry Pi<br />
· Barcode scanner<br />
· Speaker<br />
· Monitor<br />
<strong></strong></p>
<p><span style="color: #33cccc"><strong>3.2.2 소프트웨어 구성</strong></span><br />
· MySQL Database<br />
· TSP algorithm source<br />
· 삼변측량 algorithm source<br />
· TTS source<br />
<strong></strong></p>
<p><span style="color: #33cccc"><strong>3.2.3 주요 동작 및 특징</strong></span><br />
· Barcode scan을 통한 상품, 장바구니 database 구성<br />
· Barcode scan을 통한 행사 정보 음성 안내 기능 TTS 구현<br />
· 삼변측량법을 통한 실내 위치 측위<br />
· TSP algorithm을 통한 최단경로 추적<br />
<strong></strong></p>
<p><span style="color: #3366ff"><strong>3.3. 개발 환경</strong></span><br />
<strong></strong></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-25.png" rel="lightbox[40955]"><img class="alignnone size-full wp-image-41092" alt="68 ict_ 스마트쇼핑카트 (25)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-25.png" width="620" height="613" /></a></p>
<p>&nbsp;</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-26.png" rel="lightbox[40955]"><img class="alignnone size-full wp-image-41093" alt="68 ict_ 스마트쇼핑카트 (26)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-26.png" width="605" height="268" /></a></p>
<p><span style="color: #ff6600"><strong>4. 단계별 제작 과정</strong></span><br />
<span style="color: #3366ff"><strong>4.1. Hardware</strong></span><br />
Raspberry PI에 보조배터리, 모니터, 스피커, 바코드 스캐너가 연결하였다. 전동 카트(장바구니)에 Raspberry Pi와 함께 전력을 공급해주는 보조배터리, 경로안내와 장바구니 목록을 시각화해줄 모니터, 해당 상품의 행사 정보를 음성으로 출력해줄 스피커를 부착한다. 또한, 바코드 스캐너를 쇼핑카트에 부착하였고, 스캔 시 UART 통신을 통해 라즈베리로 바코드 정보를 넘겨 상품을 장바구니 목록에 담을 수 있도록 한다. 담긴 상품의 목록은 모니터로 사용자에게 시각화 해줌으로써 사용자가 장바구니를 직접 보며 관리할 수 있도록 한다.</p>
<p><strong>탈부착식 모듈</strong><br />
탈부착 식 모듈로 전동 카트뿐만 아니라 일반 카트에도 부착할 수 있다. 탈부착 식이므로 전동 카트의 개수가 아닌 한 매장에 필요한 개수만 구비해도 됨으로 가격적 부담도 줄어들도록 했다.</p>
<p><strong>실내 측위를 위한 Bluetooth Beacon</strong></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-27.png" rel="lightbox[40955]"><img class="alignnone size-full wp-image-41094" alt="68 ict_ 스마트쇼핑카트 (27)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-27.png" width="253" height="208" /></a><br />
위치 추적을 위해 최소 3개의 블루투스 비콘을 마트에 설치함으로써 실내에서의 실제 위치를 추적 가능하다. 각각의 비콘마다 코인건전지 CR2032 3V 2개로 전원공급을 한다. 하지만, 1m 밖의 거리에서 정확도가 떨어지는 RSSI값을 확인할 수 있었다. 짧은 통신 거리의 한계로 Beacon을 이용할 때 1m 내의 범위에서 시연을 진행하였다.</p>
<p><strong>실내 측위를 위한 RF 통신</strong></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-28.png" rel="lightbox[40955]"><img class="alignnone size-full wp-image-41095" alt="68 ict_ 스마트쇼핑카트 (28)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-28.png" width="253" height="150" /></a></p>
<p>앞서 언급한 Buletooth Beacon의 짧은 통신 거리의 한계를 대체할 RF 통신을 구현하였다. RF통신은 통신 거리가 100m까지도 가능하기 때문에, 해당 문제점을 보완할 수 있다.<br />
다음과 같이 Beacon을 세 곳으로 설치하여 RF통신 시연을 진행하였다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-29.png" rel="lightbox[40955]"><img class="alignnone size-full wp-image-41096" alt="68 ict_ 스마트쇼핑카트 (29)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-29.png" width="612" height="274" /></a></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-30.png" rel="lightbox[40955]"><img class="alignnone size-full wp-image-41097" alt="68 ict_ 스마트쇼핑카트 (30)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-30.png" width="586" height="309" /></a></p>
<p>시현 위치에서 1분 동안 측정한 데이터의 그래프이다. 그래프를 보면 RSSI값의 변화에서 값이 비교적 일정하게 나오는 것을 볼 수 있어 그 전의 블루투스 Beacon보다는 거리 측면에서나 정확도 측면에서나 뛰어난 성능을 보여주고 있다.<br />
<strong></strong></p>
<p><span style="color: #3366ff"><strong>4.2 Software (Raspberry PI, Application)</strong></span><br />
<span style="color: #33cccc"><strong>4.2.1. 라즈베리파이</strong></span><br />
<strong>바코드 스캔 및 행사 정보 안내</strong></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-31.png" rel="lightbox[40955]"><img class="alignnone size-full wp-image-41098" alt="68 ict_ 스마트쇼핑카트 (31)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-31.png" width="197" height="230" /></a></p>
<p>상품을 장바구니에 담는 방법은 간단하다. 카트에 부착된 바코드 스캐너로 상품의 바코드를 스캔하면 장바구니에 담긴 것을 모니터로 확인이 가능하다. 같은 상품의 바코드를 여러 번 스캔 할 시 같은 목록이 여러 개 생기는 것이 아니라 같은 상품의 수량만 늘어나게 하였다. 모니터를 통해 장바구니 목록의 삭제, 수정을 제공한다. 또한 행사 중인 물품은 카트에 부착된 바코드 스캐너를 통해 바코드를 찍을 때 TTS(Text to Speech)로 상품의 이벤트 정보를 읽어준다. 해당 바코드에 찍힌 번호는 자료형을 int로 성언하면 오버로드가 되기 때문에 VARCHAR 형으로 자료형을 바꾸어 해결할 수 있다.</p>
<p><strong>최단 경로</strong></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-32.png" rel="lightbox[40955]"><img class="alignnone size-full wp-image-41099" alt="68 ict_ 스마트쇼핑카트 (32)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-32.png" width="215" height="211" /></a><br />
해당 매장의 코너의 데이터들은 모두 저장이 되어있다는 가정하에 진행이 된다. 사용자가 방문하고자 하는 코너를 모니터상으로 선택할 시, TSP (Traveling Salesperson Problem) 알고리즘을 이용한다. 실내 측위 정보로 현재 위치로부터 최단거리를 계산하게 된다. 최단의 이동경로를 모니터를 통해 사용자에게 안내해준다.</p>
<p><strong>실내 측위</strong><br />
<span style="color: #339966"><strong>실내 측위 기능:</strong></span> 사용자에게 현재 어느 위치에 있는지 실시간으로 사용자에게 알려주고 최단 경로 안내에도 쓰인다. 건물에 설치되어 있는 비콘의 신호를 수신하여 삼각측량법을 통해 현재 위치를 계산한다.</p>
<p><strong>장바구니</strong><br />
상품 정보의 관리는 MySQL DB를 이용하여 물품의 Database를 구축하여 상품정보를 등록해 놓았다. MySQL에서 기존에 만들어놓은 table을 삭제하지 않고 같은 이름으로 다시 생성하면 오류가 발생하기 때문에 기존 table을 삭제하고 다시 생성해야한다. Python과 연동하여 바코드 번호로 조회 할 수 있게 구성하였고, 카트에 연결된 모니터에 결제 버튼을 누르면 연동된 App(부트페이)으로 결제정보를 보냄으로써 결제 할 수 있게 하였다.<br />
App은 원래의 마트나 (홈플러스, 이마트 등) 대형마트에서 제공하는 App으로 결제 정보를 보낸다고 가정했지만 실제 App을 수정할 수 없어 결제기능만 있는 어플을 따로 구현하였다.</p>
<p><span style="color: #339966"><strong>상품목록 추가 :</strong> </span>상품의 바코드를 스캔할 시, 모니터 상으로 상품이 추가 된 것을 확인할 수 있다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-33.png" rel="lightbox[40955]"><img class="alignnone size-full wp-image-41100" alt="68 ict_ 스마트쇼핑카트 (33)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-33.png" width="584" height="178" /></a></p>
<p><span style="color: #339966"><strong>상품목록 삭제 :</strong> </span>필요하지 않은 상품은 삭제 모드로 들어가 해당 상품의 인덱스를 지정해 삭제할 수 있다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-34.png" rel="lightbox[40955]"><img class="alignnone size-full wp-image-41101" alt="68 ict_ 스마트쇼핑카트 (34)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-34.png" width="583" height="194" /></a><br />
결제 모드를 통해 어플에서 결제가 가능하다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-35.png" rel="lightbox[40955]"><img class="alignnone size-full wp-image-41102" alt="68 ict_ 스마트쇼핑카트 (35)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-35.png" width="441" height="45" /></a></p>
<p><strong>GUI</strong><br />
경로 안내 모드에서 기존에 제시한 디스플레이는 텍스트 형식으로만 구성이 되어있었다. 이를 가독성있게 만들기 위해서 Python에서 사용하는 GUI를 이용해 시각화시켰다. 왼쪽의 메인 화면을 통해 모드를 선택할 수 있으며 경로와 장바구니 모드가 있다. 경로안내모드에서는 사용자가 원하는 코너를 선택하고 OK 버튼을 누를 수 있으며 취소하고 싶은 코너가 있다면 다시 한 번 누르고 OK 버튼을 누르면 된다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-36.png" rel="lightbox[40955]"><img class="alignnone size-full wp-image-41103" alt="68 ict_ 스마트쇼핑카트 (36)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-36.png" width="620" height="493" /></a></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-37.png" rel="lightbox[40955]"><img alt="68 ict_ 스마트쇼핑카트 (37)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-37.png" width="620" height="264" /></a></p>
<p>장바구니 모드에서는 바코드 스캔이 주를 이룬다. 바코드로 상품을 스캔할 때마다 목록에 상품들이 쌓여간다. 삭제 기능도 제공하여 Check를 눌러 목록에서 삭제를 누를 수 있다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-38.png" rel="lightbox[40955]"><img class="alignnone size-full wp-image-41105" alt="68 ict_ 스마트쇼핑카트 (38)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-38.png" width="314" height="285" /></a></p>
<p>마지막으로, 결제 버튼을 누르게 되면 사용자의 어플과 통신으로 해당 상품의 가격을 전송하게 된다.<br />
<strong></strong></p>
<p><strong style="color: #33cccc">4.2.2 어플리케이션</strong></p>
<p>안드로이드 스튜디오와 부트페이 SDK를 연동하여 가상 결제 기능을 제공한다. 인앱 결제 방식은 구현 시, 유로 모듈 결제가 필요하기 때문에 무료로 제공하는 Bootpay 방식을 사용하였다. Bootpay 사이트에서 결제 연동 시, 필요한 ID를 받아올 때 WEB Application ID로 잘못 받아와 실패하였다. Android Application ID를 제대로 받아와서 연동 문제를 해결할 수 있다. 또한 Android Studio Build.gradle(app)에서 compileSdkVersion은 30으로 설정하고, targetSdkVersion은 27로 설정하여 오류가 발생하였다. targetSdkVersion30으로 수정할 시, 정상 동작하는 것을 확인할 수 있다.<br />
<strong></strong></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-39.png" rel="lightbox[40955]"><img class="alignnone size-full wp-image-41106" alt="68 ict_ 스마트쇼핑카트 (39)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-39.png" width="309" height="258" /></a></p>
<p><span style="color: #3366ff"><strong>4.3 주요 소스 코드 설명</strong></span></p>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p>#module Declaration<br />
import blescan<br />
import pymysql<br />
import pandas as pd<br />
from pandas import DataFrame as df<br />
import os<br />
import math<br />
import socket<br />
import sys<br />
import bluetooth._bluetooth as bluez<br />
from numpy import median<br />
</div>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p>#Beacon Address<br />
beacon1=&#8221;e1c56db5dffb48d2b060d0f5a71096e0&#8243;<br />
beacon2=&#8221;e2c56db5dffb48d2b060d0f5a71096e0&#8243;<br />
beacon3=&#8221;e3c56db5dffb48d2b060d0f5a71096e0&#8243;<br />
dev_id=0<br />
count=0<br />
</div>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p>#Beacon Position Setting<br />
position1=[45,90,0]
position2=[0,0,0]
position3=[90,0,0]
</div>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p>#Use queue<br />
def push(element):<br />
global top<br />
top=top+1<br />
stack[top]=element<br />
def pop():<br />
global top<br />
ret=stack[top]
top=top-1<br />
return ret</p>
<p>def show():<br />
global top<br />
global mincorner<br />
global mincorv<br />
for i in range(0,top+1):<br />
mincorv +=&#8221; -&gt;&#8221;+stack[i][0]
<p>def getDistance(a,b):<br />
return math.sqrt((a[1]-b[1])*(a[1]-b[1])+(a[2]-b[2])*(a[2]-b[2]))<br />
</div>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p>#Traveling Salesperson Problem<br />
def TSP(start, corner, number, sumv, now):<br />
global minv<br />
global totalCount<br />
global mincorner<br />
global mincorv<br />
count=0<br />
visited[start]=1<br />
for i in range(0,number):<br />
if visited[i]== 0:<br />
count=count+1<br />
visited[i]=1<br />
push(corner[i])<br />
TSP(start,corner, number,sumv+getDistance(corner[now], corner[i]),i)<br />
visited[i]=0<br />
pop()</p>
<p>if count == 0:<br />
mincorv=mincorv+&#8221;-&gt;&#8221;+corner[start][0]
sumv=sumv+getDistance(corner[now], corner[start])<br />
show()<br />
mincorv+=&#8221;\nDistance: &#8220;+ str(sumv)<br />
mincorner.append(mincorv)<br />
mincorv=&#8221;"<br />
if minv&gt;sumv:<br />
minv=sumv</p>
<p>totalCount=totalCount+1</p>
<p>def getTrilateration(position1, position2, position3): #Get Beacon Position<br />
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]
<p>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<br />
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</p>
<p>y=((T*(x2-x3))-(S*(x2-x1)))/(((y1-y2)*(x2-x3))-((y3-y2)*(x2-x1)))<br />
x=((y*(y1-y2))-T)/(x2-x1)</p>
<p>return x,y<br />
</div>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p>#Initialization DataBase<br />
stack=[0 for i in range(5)]
top = -1<br />
</div>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p>#Setting corner position<br />
corner=[("Enter", 75.1, 53.3),<br />
("Dressed meat", 0.0, 5.1), ("Vegetable", 0.0, 10.2), ("Seafood", 0.0, 15.3), ("Fruit", 15.7, 19.1),<br />
("Toy", 5.0, 5.5), ("Beverage", 5.0, 10.6), ("Home appliance", 28.7, 41.8), ("Kitchenware", 5.0, 20.8),<br />
("Bathroom", 10.0, 5.9), ("Snack", 10.0, 10.0), ("Instant noodles", 40.9, 38.3), ("Egg", 10.0, 20.2),<br />
("Diary products", 44.6, 24.6), ("Frozen food", 52.2, 59.6), ("Clothing", 15.0, 15.5), ("Shoes", 15.0, 20.6),<br />
("Tool", 20.0, 5.7), ("Electronics", 20.0, 10.8), ("Pet goods", 20.0, 15.9), ("Beer", 20.0, 20.0)]
mincorner=[]
mincorv=&#8221;"<br />
visited=[0 for i in range(5)]
minv=2147483647.0<br />
totalCount=0</p>
<p>try:<br />
sock = bluez.hci_open_dev(dev_id)<br />
print &#8220;ble thread started&#8221;<br />
except:<br />
print &#8220;error accessing bluetooth device&#8230;&#8221;</p>
<p>blescan.hci_le_set_scan_parameters(sock)<br />
blescan.hci_enable_le_scan(sock)</p>
<p>conn = pymysql.connect(host=&#8217;localhost&#8217;, user=&#8217;yang&#8217;, passwd=&#8217;1&#8242;, db=&#8217;goods&#8217;,charset=&#8217;utf8mb4&#8242;)<br />
cur = conn.cursor()<br />
DT = df({&#8216;Goods List&#8217;: [],&#8217;Number of&#8217;:[]});<br />
pd.options.display.float_format=&#8217;{:,.0f}&#8217;.format<br />
</div>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p>#Initialization Socket<br />
PORT=8888<br />
s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)<br />
distance_avg=[]
while True:<br />
returnedList = blescan.parse_events(sock, 10)<br />
print &#8220;Select the Mode(0:Short Path, 1:Scan barcode, Exit is any Number): &#8220;,<br />
mode=int(input())<br />
if mode==0:<br />
#print corner number<br />
print &#8220;1.Dressed meat 2.Vegetable 3.Seafood 4.Fruit&#8221;<br />
print &#8220;5.Toy 6.Beverage 7.Home appliances 8.Kitchenware&#8221;<br />
print &#8220;9.Bathroom 10.Snack 11.Instant noodles 12.Dairy product&#8221;<br />
print &#8220;13.Egg 14.Frozen food 15.Clothing 16.Shoes&#8221;<br />
print &#8220;17.Tool 18.Electronics 19.Pet goods 20.Beer&#8221;</p>
<p>print &#8220;\nSelec the Corner: &#8220;,<br />
num=(input()).split()<br />
flag=[0 for i in range(21)]
vis=[0 for i in range(len(num))]
<p>#print corner map<br />
print &#8220;=========&#8221;<br />
for j in range(0,len(num)):<br />
flag[int(num[j])]=1<br />
vis[j]=corner[int(num[j])]
<p>for i in range(1,21):<br />
if flag[i] == 1:<br />
print corner[i][0],<br />
for j in range(0,16-len(corner[i][0])):<br />
print &#8221; &#8220;,<br />
else:<br />
print &#8221; &#8220;,</p>
<p>if i%4==0 and i!=0:<br />
print &#8220;\n&#8221;</p>
<p>print &#8221; &#8220;,<br />
print corner[0][0]
print &#8220;========\n&#8221;<br />
for beacon in returnedList:<br />
if beacon[18:18+len(beacon3)]==beacon1:<br />
distance_avg.insert(count,(10**((float(beacon[61:64])-(float(beacon[65:])))/20.0))*100)<br />
count=count+1<br />
if count == 10:<br />
if median(distance_avg)&lt;1:<br />
position1[2]=median(distance_avg)<br />
count=0<br />
del distance_avg[:]
<p>if beacon[18:18+len(beacon3)]==beacon2:<br />
distance_avg.insert(count,(10**((float(beacon[61:64])-(float(beacon[65:])))/20.0))*100)<br />
count=count+1<br />
if count == 10:<br />
if median(distance_avg)&lt;1:<br />
position2[2]=median(distance_avg)<br />
count=0<br />
del distance_avg[:]
<p>if beacon[18:18+len(beacon3)]==beacon3:<br />
distance_avg.insert(count,(10**((float(beacon[61:64])-(float(beacon[65:])))/20.0))*100)<br />
count=count+1<br />
if count == 10:<br />
if median(distance_avg)&lt;1:<br />
position3[2]=median(distance_avg)<br />
count=0<br />
del distance_avg[:]
<p>if beacon[18:18+len(beacon3)]==beacon3 and beacon[18:18+len(beacon3)]==beacon2 and beacon[18:18+len(beacon3)]==beacon1:<br />
X,Y=getTrilateration(position1,position2,position3)<br />
#find current location<br />
for i in range(0,21):<br />
if abs(X-coner[i][1])&lt;1 and abs(Y-coner[i][2])&lt;1:<br />
myplace=i</p>
<p>#print short path<br />
TSP(i,vis,len(vis),0,0)<br />
print &#8220;Total Path Num: &#8220;,totalCount<br />
for i in range(0,len(mincorner)):<br />
if mincorner[i].find(str(minv)) != -1:<br />
n=i<br />
print &#8220;Shortest Path: &#8220;,mincorner[n]
print &#8220;\n&#8221;<br />
mincorner=["" for i in range(len(mincorner))]
minv=2147483647</p>
<p>elif mode==1:<br />
while True:<br />
print &#8220;Scan barcode(1:Delete, 2:Pay): &#8220;,<br />
goods_cnt=1<br />
scan_in,n=[input() for _ in range (2)]
scan_in.strip(&#8216;\n\n&#8217;)<br />
if scan_in is &#8216;q&#8217;:<br />
break<br />
elif scan_in is &#8217;1&#8242;:<br />
print &#8220;=======<br />
print(DT)<br />
num=int(input(&#8220;delete (-1:return):&#8221;))<br />
#while(num==)<br />
if num== -1:<br />
continue<br />
else:<br />
DT.drop(num,inplace=True)<br />
DT.reset_index(inplace=True)<br />
DT.drop(&#8216;index&#8217;, axis=1,inplace=True)<br />
print(&#8220;{0} delete&#8221;.format(num))<br />
print(DT)<br />
elif scan_in is &#8217;2&#8242;:<br />
A=[0 for i in range(len(DT))]
sumf=0<br />
ip=input(&#8220;Input IP of App: &#8220;)<br />
s.connect((ip,PORT)) #Connect App<br />
for i in range(0,len(DT)):<br />
A[i]=str(DT.iloc[i]).split(&#8216;,&#8217;)<br />
B=A[i][3].split(&#8216;Number of&#8217;) #The number of product<br />
C=(B[1].replace(&#8221; &#8220;,&#8221;")) #The price of product<br />
sumf+=int(A[i][2])*int(C[0])<br />
sumf=str(sumf)<br />
s.send(sumf.encode(&#8216;utf-8&#8242;)) #Total price<br />
s.close()<br />
break</p>
<p>else:<br />
sql=&#8221;SELECT * FROM list WHERE barcode LIKE %s&#8221;<br />
cur.execute(sql,scan_in)<br />
for row in cur:<br />
cp=str(row)<br />
empty=str(DT[DT['Goods List'].isin([cp])])<br />
if (empty.find(&#8220;Empty&#8221;) is not 0):<br />
i=DT[DT['Goods List'].isin([cp])].index<br />
num=DT[DT['Goods List']==cp]['Number of'].tolist()<br />
num=DT.loc[i,['Number of']]=(num[0]+1)<br />
else:<br />
new_data={&#8216;Goods List&#8217;:cp, &#8216;Number of&#8217;:goods_cnt}<br />
DT=DT.append(new_data, ignore_index=True)<br />
text=str(new_data).split(&#8216;,&#8217;)<br />
text=text[3].replace(&#8216;)&#8221;&#8216;,&#8221;")<br />
os.system(&#8216;echo Event is %s | festival &#8211;tts&#8217; %text)<br />
print(DT)<br />
else:<br />
break</p>
<p>cur.close()<br />
conn.close()<br />
</div>
<p><span style="color: #ff6600"><strong>5. 사용한 제품목록</strong></span></p>
<table style="width: 620px" border="1" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td style="text-align: center">제품명</td>
<td style="text-align: center">디바 상품번호</td>
</tr>
<tr>
<td style="text-align: center">블루이노2 키트보드 (BI-200(K))</td>
<td style="text-align: center">1287076</td>
</tr>
<tr>
<td style="text-align: center">microSD Cards &#8211; TS16GUSDC10M [16GB]</td>
<td style="text-align: center">12139535</td>
</tr>
<tr>
<td style="text-align: center">[RASPBERRY-PI] 라즈베리파이 3 Model B+</td>
<td style="text-align: center">1377518</td>
</tr>
<tr>
<td style="text-align: center">[(주)블루이노] 블루이노2 (Blueinno) 다운로드 툴</td>
<td style="text-align: center">1272986</td>
</tr>
<tr>
<td style="text-align: center">아두이노 라즈베리파이 바코드 스캐너 모듈<br />
V3.0 USB to UART 인터페이싱 지원</td>
<td style="text-align: center">-</td>
</tr>
<tr>
<td style="text-align: center">스피커</td>
<td style="text-align: center">-</td>
</tr>
<tr>
<td style="text-align: center">RF 모듈</td>
<td style="text-align: center">-</td>
</tr>
</tbody>
</table>
<p><strong style="color: #ff6600">6. 기타</strong></p>
<p><span style="color: #3366ff"><strong>6.1 기능블록도</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-40.png" rel="lightbox[40955]"><img class="alignnone size-full wp-image-41107" alt="68 ict_ 스마트쇼핑카트 (40)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-40.png" width="582" height="276" /></a><br />
<span style="color: #3366ff"> <strong>6.2 목표 달성 현황</strong></span><br />
<strong>장바구니</strong><br />
목표: 바코드 스캔을 통해 장바구니 목록에 넣을 수 있고, 삭제와 추가의 기능도 구현<br />
달성: 바코드 스캔을 통한 물품 목록 추가와 삭제를 할 수 있음.</p>
<p><strong>최단거리</strong><br />
목표: 실내 측위 정보로 현재 위치로부터 최단거리 계산<br />
달성: 현재 위치로부터 최단거리를 계산할 수 있음.</p>
<p><strong>위치측량</strong><br />
목표: 현재 위치를 작은 오차로 받아오도록 구현<br />
달성: 블루투스 비콘에서 RF 비콘으로 변경하여 측정 반경 거리를 넓혀 사용성을 확대함. 약간의 오차는 개선방법을 추후 적용할 예정.</p>
<p><strong>인터페이스</strong><br />
목표: 작성한 GUI틀과 프로그램 연동<br />
달성: 코너의 위치 정보와 최단경로, 상품목록 등을 볼 수 있도록 구현하였음, 사용자 친화적인 인터페이스 화면 디자인 완성. 추후 프로그램과 GUI를 연동할 예정.</p>
<p><strong>결제 앱</strong><br />
목표: 장바구니 목록에 있는 상품을 앱으로 결제 가능하도록 구현<br />
달성: 장바구니 목록을 구성할 수 있고 결제가 가능함.</p>
<p><span style="color: #3366ff"><strong>6.3 시연영상 주소</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-41.png" rel="lightbox[40955]"><img class="alignnone size-full wp-image-41108" alt="68 ict_ 스마트쇼핑카트 (41)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ict_-스마트쇼핑카트-41.png" width="590" height="313" /></a></p>
<p><span style="color: #3366ff"><strong>6.4 참고문헌</strong></span><br />
· https://gongdae58.tistory.com/73<br />
· https://ko.wikipedia.org/wiki/%EC%82%BC%EB%B3%80%EC%B8%A1%EB%9F%89<br />
· https://codeanalysis.tistory.com/2<br />
· https://m.blog.naver.com/wlsdml1103/221159758141<br />
· https://m.blog.naver.com/PostView.nhn?blogId=dsz08082&amp;logNo=221852345347&amp;proxyReferer=https:%2F%2Fwww.google.com%2F<br />
· http://pythonstudy.xyz/python/article/408-pandas-%EB%8D%B0 %EC%9D%B4%ED%83%80-%EB%B6%84%EC%84%9D</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
]]></content:encoded>
			<wfw:commentRss>http://www.ntrexgo.com/archives/40955/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>[68호]간호사의 업무 부담을 줄여주는 EMR연동 vital sign계측 시스템</title>
		<link>http://www.ntrexgo.com/archives/40948</link>
		<comments>http://www.ntrexgo.com/archives/40948#comments</comments>
		<pubDate>Mon, 25 Oct 2021 00:00:23 +0000</pubDate>
		<dc:creator>디바이스마트 매거진</dc:creator>
				<category><![CDATA[디바이스마트 매거진]]></category>
		<category><![CDATA[특집]]></category>
		<category><![CDATA[ict]]></category>
		<category><![CDATA[ict공모전]]></category>
		<category><![CDATA[공모전]]></category>
		<category><![CDATA[디바이스마트]]></category>
		<category><![CDATA[매거진]]></category>
		<category><![CDATA[수상작]]></category>
		<category><![CDATA[융합]]></category>
		<category><![CDATA[프로젝트]]></category>

		<guid isPermaLink="false">http://www.ntrexgo.com/?p=40948</guid>
		<description><![CDATA[디바이스마트매거진 68호 &#124; 혈압계와 체온계를 하나의 패키지로 구성하여 사용자 경험을 고려한 디자인을 채택하였고 RFID태그 및 무선 통신기술을 활용하여 EMR 자동 기입 시스템을 구현하였다. ]]></description>
				<content:encoded><![CDATA[<p><strong><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-1.png" rel="lightbox[40948]"><img class="alignnone size-large wp-image-41002" alt="68 ICT_간호사연동 (1)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-1-620x203.png" width="620" height="203" /></a></strong></p>
<p><span style="background-color: #ffffff"><strong>2021 ICT 융합 프로젝트 공모전 우수상</strong></span></p>
<p><span style="font-size: x-large"><strong>간호사의 업무 부담을 줄여주는 </strong></span></p>
<p style="text-align: left"><span style="font-size: x-large"><strong>EMR연동 vital sign계측 시스템</strong></span></p>
<p style="text-align: right">글 | 건양대학교 장건호, 이지훈</p>
<p style="text-align: right">
<p><span style="color: #ff6600"><strong>1. 심사평</strong></span><br />
<strong>칩센</strong> 최근 의료업계 또한 ICT 장비나 시스템 도입이 매우 늘어나고 있고, 개발자가 작품의 기획 중에 고민한 회진을 통한 활력 징후 체크를 대체하기 위한 시스템이 대표적으로 가장 먼저, 그리고 많은 곳에서 검토되고 있는 것으로 알고 있습니다. 의료기 자체의 휴대성 및 전기적 장비로의 변경은 많은 부분에서 이루어진 것으로 생각되고, 그 장비 등에 개발자가 구성한 시스템이나, 솔루션을 적용하면 의료진의 업무가 훨씬 더 효율적이고, 환자의 입장에서도 빠르고 정확한 이상 징후 전달이 가능할 것으로 보입니다. 제품 제작에도 많은 공을 들인 것으로 보이는데, 실제 시연 영상 등이 있었다면 어땠을까 하는 아쉬움이 남습니다.</p>
<p><strong>펌테크</strong> 세심한 관찰력이 반영된 실생활과 밀접한 아이디어와 실용성이 우수한 작품이라고 생각합니다. 혈압계에 온도계 기능 부가 구성은 신선했고, 기획의도에 맞게 전체 시스템을 안정적, 효율적으로 구성했으며 완성도 높게 구현하였다고 판단이 되며 전체적으로 기획의도, 기술구현도, 완성도 등에서 우수한 작품으로 판단되며 제출된 문서를 종합적으로 검토해 볼 때 최종 완성은 되진 않은 것을 판단되나 추후 보완을 통해 완성도 높게 구현된다면 상업적으로도 손색이 없는 우수한 작품이 되리라 생각됩니다.</p>
<p><strong>위드로봇</strong> 전체 완성도가 높은 작품입니다. 목표와 결과가 일치하는 잘 진행된 작품으로 보입니다.<br />
<strong>뉴티씨</strong> 매우 실용적인 작품으로 생각되며, 실제로 격무에 시달리는 간호사들의 업무를 줄여줄 수 있을 것으로 생각합니다. 좀 더 콤팩트하게 제작하면 무겁지 않아서 좋을 것 같고, 표시기와 네트웍은 스마트폰에서 제공해도 될 것 같으므로, 같은 아이디어로 센서부만 제작하여 통신으로 데이터를 날려주고, 스마트폰에서 데이터들을 가공 처리한 후, LCD로 표시하여주면 더 가볍고 좋을 것 같습니다. 좀 더 고민하여서, 완성도를 높인다면 사업으로 연결할 수도 있을 것 같습니다.</p>
<p><span style="color: #ff6600"><strong>2. 작품 개요</strong></span><br />
현재 간호사는 정해진 시간마다 환자의 활력징후(인간의 생명 활력도를 추정할 수 있는 가장 기본적인 생체징후)를 측정합니다. 이는 간호사의 기본 업무 중 하나로써 상당한 시간을 할애하는 것으로 알려져 있습니다. 중증환자가 아닌 일반 환자의 경우 일일이 간호사가 직접 회진을 돌며 측정해야 하는데 이때 개별의 장비를 사용하고 차트에 수기작성을 하는 등 전통적인 방식을 사용하고 있습니다. 측정 장비의 단순화와 자동화로 관리효율을 증대시키며 잘못된 기입을 방지하고 의료진의 업무효율 증대가 해당 작품의 목적입니다.<br />
따라서 혈압계와 체온계를 하나의 패키지로 구성하여 사용자 경험을 고려한 디자인을 채택하였고 RFID태그 및 무선 통신기술을 활용하여 EMR(환자에 관련된 정보를 전산화 한 것) 자동 기입 시스템을 구현했습니다.</p>
<p><span style="color: #ff6600"><strong>3. 작품 설명</strong></span><br />
최근 병원들은 ‘환자 중심’이라는 키워드에 맞춰 변화하고 있는데 이는 간호사의 서비스 영역까지 포함하고 있습니다. 따라서 현재 간호사의 본래 많은 업무와 더불어 부가적인 서비스까지 요구되고 있어 간호사의 업무 스트레스가 가중되고 있는 상황입니다. IoT 활력징후 시스템을 도입할 경우 활력징후를 측정, 수기로 작성, DB에 입력 등 일련의 과정을 간결화 하여 업무능률을 탄력적으로 높일 수 있습니다. 간호사의 휴게시간 보장 및 업무 만족도 향상 등을 기대하며 결과적으로 환자에게 더 많은 시간을 할애 하는 잠재적인 효과도 예상 할 수 있습니다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-2.png" rel="lightbox[40948]"><img class="alignnone size-large wp-image-41004" alt="68 ICT_간호사연동 (2)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-2-539x620.png" width="539" height="620" /></a></p>
<p><span style="color: #ff6600"><strong>4. 주요 동작 및 특징</strong></span><br />
<span style="color: #33cccc"><strong>4.1. 주요 특징</strong></span><br />
<strong>체온계</strong><br />
1. 체온계를 혈압계에 dock 방식으로 결합하여 휴대성을 강조한 구성<br />
2. 비접촉식 적외선 체온 측정 방식<br />
3. RFID Reader 로 환자 개별 식별 가능<br />
4. 충전 형 배터리 사용<br />
5. Dock 거치 시에는 혈압계로부터 전원을 공급받아 충전하고 분리 시 자동 부팅 후 블루투스 페어링 기능</p>
<p><strong>혈압계</strong><br />
1. 오실로 메트릭 법 혈압 측정 방식<br />
2. 5인치 터치스크린을 활용하여 측정 값 및 환자 정보 표시<br />
3. 충전형 배터리 사용<br />
4. 와이 파이를 이용하여 웹 데이터베이스에 접근, 체온계로부터 받은 RFID값으로 환자 정보를 불러오고 활력징후 측정값을 저장</p>
<p><strong>동작 프로세스</strong></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-3.png" rel="lightbox[40948]"><img class="alignnone size-full wp-image-41006" alt="68 ICT_간호사연동 (3)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-3.png" width="620" height="322" /></a></p>
<p><span style="color: #ff6600"><strong>5. 전체 시스템 구성</strong></span><br />
<strong>하드웨어</strong></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-4.png" rel="lightbox[40948]"><img class="alignnone size-full wp-image-41007" alt="68 ICT_간호사연동 (4)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-4.png" width="620" height="383" /></a><br />
<strong> 소프트웨어</strong></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-5.png" rel="lightbox[40948]"><img class="alignnone size-full wp-image-41008" alt="68 ICT_간호사연동 (5)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-5.png" width="620" height="303" /></a> <a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-6.png" rel="lightbox[40948]"><img class="alignnone size-full wp-image-41009" alt="68 ICT_간호사연동 (6)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-6.png" width="620" height="772" /></a><br />
<strong> 그래픽 유저 인터페이스</strong></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-7.png" rel="lightbox[40948]"><img class="alignnone size-full wp-image-41010" alt="68 ICT_간호사연동 (7)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-7.png" width="620" height="387" /></a></p>
<p><span style="color: #ff6600"><strong>6. 개발환경</strong></span><br />
<strong>체온계</strong><br />
1. MCU : Arduino Nano<br />
2. 언어 : C Language<br />
3. 사용 모듈 : 적외선체온센서, RFID리더, OLED, 블루투스 모듈</p>
<p><strong>혈압계</strong><br />
1. MCU : Raspberry Pi4<br />
2. OS : Raspbian (linux)<br />
3. 언어 : Python Language<br />
4. 사용 모듈 : 혈압계, 5인치 터치 디스플레이</p>
<p><strong>GUI</strong><br />
1. 개발 툴 : pyqt5<br />
2. 언어 : Python Language</p>
<p><strong>3D모델 외부 케이싱</strong><br />
1. 설계 : SolidWorks 2017<br />
2. 슬라이싱 : CURA, simplify<br />
3. 3D프린터 : Anet a8, moment<br />
4. 출력방식 : FDM<br />
5. 사용재료 : PLA</p>
<p><strong>백엔드</strong><br />
1. DB : Firebase<br />
2. 서버 : Flask<br />
3. 웹 : Jinja2<br />
4. 언어 : Python</p>
<p><strong>통합개발환경</strong><br />
1. 체온계, 혈압계 일부: Sublime Text 3<br />
2. 혈압계, 웹 : Visual studio Code</p>
<p><span style="color: #ff6600"><strong>7. 단계별 제작 과정</strong></span><br />
<span style="color: #33cccc"><strong>7.1. 체온계 제작</strong></span><br />
체온계 제작에 있어서 가장 중요했던 부분은 전원부입니다. 전원을 상시 켜둘 이유가 없으며 이는 충전을 방해하는 요인이기 때문에 충전 시에는 체온계의 전원을 꺼두고 충전 Dock에서 분리 시에 자동으로 전원이 켜지며 부팅이 완료되는 기능을 구현하고자 하였습니다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-8.png" rel="lightbox[40948]"><img class="alignnone size-full wp-image-41011" alt="68 ICT_간호사연동 (8)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-8.png" width="269" height="315" /></a><br />
트랜지스터의 디지털 스위치 기능을 활용해서 충전전압을 입력 신호로 판단하였습니다.<br />
하지만 당시에는 쉽게 구할 수 있는 NPN 트랜지스터로 제작을 하였기 때문에 단순한 구조로는 원하는 기능을 구현할 수 없었습니다. NPN TR은 베이스에 양 전압을 가하면 콜렉터에서 이미터로 전류가 흐르게 됩니다. 이때 베이스에 입력되는 전압이 충전 전압이라고 가정할 때 우리는 콜렉터에서 이미터로 전류가 흐르는 것이 아닌 차단되어야 합니다. 반대로 베이스 전압을 제거 했을 때 전류가 흘러야 합니다. 따라서 베이스를 컨트롤하는 TR 스위치를 하나 더 추가하여 이중 TR 스위치를 만들었습니다.<br />
구체적인 전체 회로는 원고 하단에 첨부합니다. 그 중 디지털 스위치의 구조만 확대한 내용은 다음과 같습니다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-9.png" rel="lightbox[40948]"><img class="alignnone size-full wp-image-41012" alt="68 ICT_간호사연동 (9)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-9.png" width="608" height="329" /></a></p>
<p>Tr1이 1차 스위치 Tr2가 2차 스위치입니다. 1차 스위치에는 늘 항상 5V의 전압이 인가되고 있습니다. (배터리 전압) 하지만 2차 스위치에서 충전 전압이 인가 될 때 1차 스위치에 인가되던 배터리 전압은 GND로 흐르게 되어 전압이 낮아집니다. 결과적으로 1차 스위치는 개방되는 것입니다.<br />
따라서 충전 중에는 2차 스위치에 전압이 인가되어 1차 스위치는 개방 되고 충전을 그만 두었을 때는 2차 스위치가 개방되어 1차 스위치는 배터리 전압에 의해 단락됩니다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-10.png" rel="lightbox[40948]"><img class="alignnone size-full wp-image-41013" alt="68 ICT_간호사연동 (10)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-10.png" width="352" height="527" /></a></p>
<p>체온계의 핵심 기능은 다음과 같습니다.<br />
1. RFID 리딩 후 블루투스 전송<br />
2. 온도 센서 측정 후 블루투스 전송<br />
3. 위 1,2번이 작동할 시 진동을 발생 할 것<br />
4. 모든 진행 상황을 OLED에 비주얼라이징 할 것</p>
<p><strong>사용 모듈</strong><br />
1. 진동 모듈<br />
2. OLED<br />
3. RFID 리더<br />
4. 비접촉 적외선 온도 센서<br />
5. 블루투스 HC-05<br />
6. 충전 모듈</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-11.png" rel="lightbox[40948]"><img class="alignnone size-full wp-image-41014" alt="68 ICT_간호사연동 (11)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-11.png" width="620" height="286" /></a></p>
<p><span style="color: #33cccc"><strong>7.2. 체온계 코드</strong></span><br />
<span style="color: #00ccff"><strong> 7.2.1. 진동 발생</strong></span><br />
진동 모듈은 핀 번호를 지정한 후 특정한 패턴으로 작동하게끔 미리 함수로 써 만들어 줍니다. 이 후 필요한 순간마다 함수를 호출 하는 방식으로 진동을 발생 시켜 햅틱 기능으로서 사용자에게 정보를 전달합니다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-12.png" rel="lightbox[40948]"><img class="alignnone size-full wp-image-41015" alt="68 ICT_간호사연동 (12)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-12.png" width="243" height="384" /></a></p>
<p><span style="color: #00ccff"><strong>7.2.2. OLED 표시</strong></span><br />
Oled 역시 디스플레이의 역할로서 늘 갱신되어야 합니다. 따라서 Oled를 리뉴얼하는 코드를 함수로 따로 만들어둔 다음 체온 측정 후, RFID 카드를 읽었을 때, 전원이 켜졌을 때, 등등 필요 시 호출 하는 방식으로 표시합니다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-13.png" rel="lightbox[40948]"><img class="alignnone size-full wp-image-41016" alt="68 ICT_간호사연동 (13)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-13.png" width="620" height="268" /></a><br />
전역 변수를 사용하여 체온 값과 RFID아이디를 갱신 합니다. 밋밋한 화면을 좀 더 알차게 구성하기 위해 이미지를 삽입하였습니다. 이미지는 흑백 이미지로 알맞은 크기로 제작 한 뒤 비트맵 단위로 변환하여 줍니다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-14.png" rel="lightbox[40948]"><img class="alignnone size-full wp-image-41017" alt="68 ICT_간호사연동 (14)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-14.png" width="620" height="177" /></a><br />
헥사 코드로 변환된 이미지를 u8g 라이브러리에 포함된 bitmap draw 기능을 활용하여 화면에 그려줍니다. 이미지를 bitmap으로 변환하는 과정과 기능은 http://en.radzio.dxp.pl/bitmap_converter/ 해당 사이트를 참고하였습니다.</p>
<p><span style="color: #00ccff"><strong>7.2.3. RFID 리더</strong></span><br />
MFRC522 모듈을 사용하는 라이브러리의 예제를 수정하여 사용합니다. RFID 카드에는 여러 가지 정보가 담겨 있으며 그 정보를 읽고 쓰기가 가능합니다. 하지만 실질적인 데이터의 저장은 카드에 담는 것이 아니라 온라인 데이터베이스를 사용할 것입니다. 따라서 RFID의 고유한 ID만 사용합니다. 고유 ID를 주소처럼 사용하여 DB에 접근하고 저장합니다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-15.png" rel="lightbox[40948]"><img class="alignnone size-full wp-image-41018" alt="68 ICT_간호사연동 (15)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-15.png" width="620" height="253" /></a></p>
<p>ReadNUID예제를 실행 한 후 카드를 읽힌 결과입니다. 여기서 우리는 hex값을 사용하도록 합니다. ID로 사용하기에 dec보다는 짧기 때문입니다. 결과 값을 곧이곧대로 사용하기에는 깔끔하지 않음으로 예제를 적당히 수정하여 핵심만 추려 냅니다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-16.png" rel="lightbox[40948]"><img class="alignnone size-full wp-image-41019" alt="68 ICT_간호사연동 (16)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-16.png" width="620" height="742" /></a></p>
<p>앞선 그림 [카드의 용량과 id 값이 표시 됨]과 비교했을 때 hex값만 출력이 되며 16진수 한자리를 구별하는 띄어쓰기도 사라졌습니다. 우리는 hex값을 일일이 해석하려는 것이 아닌 하나의 ID로써 문자열을 사용할 것이기 때문에 위와 같이 수정하였습니다.</p>
<p><span style="color: #00ccff"><strong>7.2.4. 비접촉 적외선 온도 센서</strong></span><br />
Adafruit_MLX90614라이브러리를 사용합니다. 섭씨온도 중 Object온도를 사용합니다. I2C통신을 사용하며 라이브러리 내장 함수를 사용하기 때문에 비교적 간단합니다. 하지만 사용하는 방식에 있어서 문제가 발생합니다. 먼저 온도를 언제 측정할지 시점을 지정할 방법이 필요합니다. 분명하게 신체를 가리키지 않았지만 온도를 측정하게 된다면 그 것은 잘못된 측정입니다. 두 번째로 센서의 민감도에 따라서 보정이 필요합니다. 경우에 따라 한 번의 측정으로는 정확한 값을 얻을 수 없습니다. 따라서 우리는 버튼이라는 물리적인 장치를 통해 외부에서 신호를 보내며 동시에 3초간 측정한 값의 평균으로 온도를 측정합니다.<br />
기본적인 구조는 택트 버튼을 인터럽트 핀을 사용하여 부착하고 버튼이 눌렸을 때 3초에 걸쳐서 온도를 측정합니다. 3번의 측정값이 20도 이상이며 1의 자리가 모두 동일한 경우 평균을 내어 측정을 완료합니다. 해당 조건은 오류를 잡아내고 보다 정확한 측정을 위한 경험적 결과입니다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-17.png" rel="lightbox[40948]"><img class="alignnone size-full wp-image-41020" alt="68 ICT_간호사연동 (17)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-17.png" width="620" height="277" /></a> <a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-18.png" rel="lightbox[40948]"><img class="alignnone size-full wp-image-41021" alt="68 ICT_간호사연동 (18)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-18.png" width="620" height="309" /></a><br />
타임 인터럽트는 1초마다 count값을 더해줍니다. count에 따라 온도 측정값을 배열에 넣고 그 값을 판단하는 구조입니다. 조건에 부합하면 타임 인터럽트는 종료됩니다. 이 것을 반복합니다.</p>
<p><span style="color: #00ccff"><strong>7.2.5. 블루투스 전송 &#8211; 통신 프로토콜</strong></span><br />
앞서 RFID ID 리더와 온도 측정을 구현하였습니다. 또한 이를 Oled에 표시하기도 하였습니다. 측정값을 Dock(혈압계)에 전송을 해야 합니다. 이때의 통신 방법은 블루투스를 사용하며 모듈은 HC-05를 사용합니다. AT-mode를 통해 미리 사용할 블루투스의 주소를 알아내고 비밀번호와 이름을 설정합니다.<br />
Dock과 통신하는 프로토콜은 string to string을 이용합니다. 센서의 종류와 측정값을 한 string 문자열에 첨부하여 전송하는 방식입니다. ID값은 “BC”, 체온은 “BT”라고 약속합니다. 또한 정보의 구분 또한 특정한 문자를 지정하여 한번 전송한 데이터의 끝을 표시해 줍니다. 사용한 문자는 “Z”입니다.<br />
예컨대 체온을 전송할 땐 “BT36.5Z”의 형식을 맞추어 혈압계로 전송됩니다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-19.png" rel="lightbox[40948]"><img class="alignnone size-full wp-image-41022" alt="68 ICT_간호사연동 (19)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-19.png" width="313" height="306" /></a></p>
<p><span style="color: #33cccc"><strong>7.3. 체온계 케이싱</strong></span><br />
<span style="color: #00ccff"><strong> 7.3.1. 3D 모델링</strong></span><br />
사용할 모듈을 버니어캘리퍼스로 실측을 합니다. 실측한 값을 토대로 solidwork에서 part를 만들어 줍니다. 만든 part를 assembly를 통해서 적절히 배치를 하고 케이스 모델링을 진행합니다. 이 때 3D프린터로 출력할 것을 고려하여 설계 합니다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-20.png" rel="lightbox[40948]"><img class="alignnone size-full wp-image-41023" alt="68 ICT_간호사연동 (20)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-20.png" width="620" height="285" /></a></p>
<p><span style="color: #00ccff"><strong>7.3.2. 3D프린터 출력</strong></span><br />
설계한 모델링을 슬라이싱을 통해 G-code로 변환한 후 3D프린터로 출력합니다. FDM 방식의 프린터기를 사용했으며 재료는 PLA 흰색입니다.</p>
<p><span style="color: #00ccff"><strong>7.3.3. 후가공</strong></span><br />
FDM 방식의 출력물은 특유의 단층이 존재하게 됩니다. 작품에는 아무런 영향을 끼치지 않지만 완성도를 위해 후가공을 진행하였습니다. 후처리 방법은 사포로 표면을 갉아 내고 퍼티를 발라 틈새를 매우고 다시 사포로 갉아내는 방법을 지속해서 반복합니다. 이 후 흰색 락카를 도포하여 마무리합니다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-1.jpg" rel="lightbox[40948]"><img class="alignnone size-full wp-image-41001" alt="68 ICT_간호사연동 (1)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-1.jpg" width="620" height="285" /></a></p>
<p><span style="color: #00ccff"><strong>7.3.4. 조립</strong></span><br />
완성된 케이스 내부에 납땜한 회로와 기타 모듈들을 배치하여 조립을 완성합니다. 그 다음 정상적으로 작동하는지 확인 합니다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-3.jpg" rel="lightbox[40948]"><img class="alignnone size-full wp-image-41005" alt="68 ICT_간호사연동 (3)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-3.jpg" width="620" height="282" /></a></p>
<p><span style="color: #33cccc"><strong>7.4. 혈압계 제작</strong></span><br />
Dock은 혈압계 자체의 기능을 수행하면서 동시에 체온계와 DB를 이어주는 중간 매개체에 해당됩니다. 따라서 수신 받은 RFID ID 값과 체온 측정 값, 혈압 값을 적절하게 처리하여 디스플레이에 표시하고 firebace 데이터베이스에 저장하는 역할을 수행합니다.</p>
<p><strong>혈압계의 핵심 기능은 다음과 같습니다.</strong><br />
1. 그래픽 인터페이스를 사용할 것<br />
2. 체온계를 거치할 수 있는 구조일 것<br />
3. 블루투스, 와이파이 통신할 것<br />
4. firebase DB를 이용할 것</p>
<p><strong>사용 모듈</strong><br />
1. 5인치 터치 디스플레이<br />
2. 라즈베리파이3<br />
3. 혈압계</p>
<p><span style="color: #33cccc"><strong>7.5. 혈압계 코드</strong></span><br />
그래픽 유저 인터페이스를 제작합니다. 측정값과 환자 정보를 표시하기 위함으로 직관적이고 깔끔한 디자인을 선택합니다. UI는 파이썬언어 기반의 툴킷인 pyqt5를 사용합니다. 또한 pyqt5를 보다 쉽게 사용할 수 있도록 제작된 응용프로그램인 qtDesigner를 사용하여 드롭앤 드랍 방식으로 UI를 제작합니다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-21.png" rel="lightbox[40948]"><img class="alignnone size-full wp-image-41024" alt="68 ICT_간호사연동 (21)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-21.png" width="620" height="442" /></a><br />
완성된 gui는 저장 시 .ui 파일로 만들어집니다. 파이썬 프로그램 안에서 작동시키기 위해선 .py파일로 바꿔줄 필요가 있습니다. 변환 방법은 pyqt5 툴킷에서 제공된 변환기를 사용합니다.</p>
<p><strong>UI 파일을 파이썬 파일로 변환</strong><br />
-&gt; pyuic5 -x hello.ui -o hello.py</p>
<p><strong>qrc 리소스 파일을 파이썬 파일로 변환</strong><br />
-&gt; pyrcc5 hello_image.qrc -o hello_image_rc.py</p>
<p>이때 ui파일 뿐만 아니라 qrc파일도 .py파일로 변환해야 합니다. qrc 파일은 UI를 만들 때 사용했던 이미지가 저장된 파일입니다. 변환된 .py파일을 열어 보면 pyqt5문법을 활용해서 UI 코드가 변환되어 있습니다. 메인 코드에서 사용할 땐 import하여 불러옵니다.</p>
<p><span style="color: #00ccff"><strong>7.5.1. GUI 작동 구조</strong></span><br />
절차적 프로그래밍과 다르게 GUI를 사용하는 환경에서는 GUI 따로 연산처리 따로 실행하는 쓰레드를 활용해서 프로그래밍 해야 합니다. GUI에 독립성을 부여해야 화면이 멈추지 않으며, 어떤 상황에서도 화면을 조작할 수가 있습니다. GUI 쓰레드를 기본적으로 동작하면서 필요시에 이벤트를 발생시킵니다. 이벤트 발생 시 GUI를 업데이트하는 함수만을 실행시켜서 화면을 수정하면 보는 이로 하여금 자연스러운 모니터링이 가능합니다.<br />
이 후 부가적인 처리는 블루투스 통신과 GPIO를 이용한 혈압계 작동 파트입니다. 역시나 각각 개별로 쓰레드를 만들어서 작동시키고 화면을 갱신하는 방식으로 구현합니다.</p>
<p><span style="color: #00ccff"><strong>7.5.2. 블루투스 소켓 통신</strong></span><br />
블루투스 통신을 위해 라즈베리파이에 제공되는 Bluetooth 라이브러리를 활용합니다. 기본적으로 소켓 통신을 기반으로 작성되었습니다. 매번 새로운 장치를 찾을 필요가 없기 때문에 사전에 알아두었던 체온계 블루투스 모듈의 주소 값을 지정합니다. 앞서 블루투스 통신 프로토콜에서 언급한 내용대로 전송 문자열의 앞의 문자는 정보의 종류를 뜻하며 맨 뒤 문자는 정보의 끝을 의미합니다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-23.png" rel="lightbox[40948]"><img class="alignnone size-full wp-image-41026" alt="68 ICT_간호사연동 (23)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-23.png" width="620" height="380" /></a></p>
<p>아두이노와 통신 중에 모종의 이유로 데이터 중간에 개행 문자가 섞여서 전송되는 현상이 있었습니다. 수신된 문자열을 처리하기에 굉장히 까다롭기 때문에 “Z”문자를 한 개의 데이터를 구분하는 지점으로 지정하였습니다. 개행 문자를 기준으로 문자열을 나누지 않고 지정문자를 받을 때까지 모든 수신된 데이터를 차곡차곡 하나의 변수에 저장하였습니다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-24.png" rel="lightbox[40948]"><img class="alignnone size-full wp-image-41027" alt="68 ICT_간호사연동 (24)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-24.png" width="620" height="268" /></a></p>
<p>이 후 통신 프로토콜에 따라서 수신된 데이터의 종류를 구분하고 실제 데이터만 추출하여 적재적소에 사용합니다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-25.png" rel="lightbox[40948]"><img class="alignnone size-full wp-image-41028" alt="68 ICT_간호사연동 (25)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-25.png" width="620" height="262" /></a></p>
<p>온도와 RFID ID로 구분함. 또한 데이터를 추출한다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-26.png" rel="lightbox[40948]"><img class="alignnone size-full wp-image-41029" alt="68 ICT_간호사연동 (26)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-26.png" width="620" height="452" /></a></p>
<p><span style="color: #00ccff"><strong>7.5.3. DB 연동</strong></span><br />
환자정보 및 측정값은 내부저장소가 아닌 외부 클라우드 저장소를 사용합니다. 무료로 리얼타임 저장소를 서비스하는 firebase를 이용합니다. 저장소는 key와 val로 구성되어 있으며 초기화하는 방법과 데이터를 읽고 쓰는 방법은 관련 라이브러리인 pyrebase의 내장 함수를 참고합니다. 이는 git hub에서 찾을 수 있으며 데이터의 구성은 다음과 같습니다. 가장 상위 key는 RFID ID이며 아래에 이름, 성별, 나이 등 정보가 담겨 있습니다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-27.png" rel="lightbox[40948]"><img class="alignnone size-full wp-image-41030" alt="68 ICT_간호사연동 (27)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-27.png" width="232" height="330" /></a></p>
<p>위 정보는 사전에 이미 기입되어 있습니다. 체온계에 RFID 카드를 태그하면 ID 값이 혈압계로 전송되고 이는 다시 firebase에 접근하여 관련 정보가 있는지 확인합니다. DB에 존재하는 id 값이라면 해당 환자 정보를 다시 혈압계로 전송하여 모니터에 표시합니다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-28.png" rel="lightbox[40948]"><img class="alignnone size-full wp-image-41031" alt="68 ICT_간호사연동 (28)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-28.png" width="620" height="456" /></a></p>
<p><span style="color: #00ccff"><strong>7.5.4. DB 자동 저장</strong></span><br />
사용자 경험을 고려하여 동작 순서에 따라 자동으로 측정값이 저장되도록 하였습니다. 모든 절차의 접근 key는 RFID카드의 ID를 사용합니다. 먼저 A 카드를 태그한 이후에는 몇 번이고 다시 측정할 수 있습니다. 체온계로부터 체온을 측정하고 혈압계는 라즈베리파이의 GPIO 기능을 사용하여 모터와 센서를 제어합니다. 혈압계 측정은 GUI화면의 버튼을 눌러 동작합니다. 측정이 끝난 후 B 카드를 태그하면 A를 측정했던 값이 자동으로 DB에 저장되는 방식입니다. 따라서 사용자는 자연스럽게 환자별로 인식표 태그 후 측정 순으로 진행하면 별다른 조작 없이 자동 저장되는 시스템을 설계하였습니다. 측정값은 날짜별로 묶고 다시 시간별로 묶어서 저장합니다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-29.png" rel="lightbox[40948]"><img class="alignnone size-full wp-image-41032" alt="68 ICT_간호사연동 (29)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-29.png" width="280" height="413" /></a></p>
<p>&nbsp;</p>
<p><span style="color: #33cccc"><strong>7.6. 혈압계 케이싱</strong></span><br />
<span style="color: #00ccff"><strong>7.6.1. 3D모델링</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-30.png" rel="lightbox[40948]"><img class="alignnone size-full wp-image-41033" alt="68 ICT_간호사연동 (30)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-30.png" width="620" height="288" /></a></p>
<p>5인치 디스플레이와 라즈베리파이를 고정하기 위해 적절히 배치하고 지지대를 세워 안정적으로 조립할 수 있도록 설계하였습니다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-31.png" rel="lightbox[40948]"><img class="alignnone size-full wp-image-41034" alt="68 ICT_간호사연동 (31)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-31.png" width="620" height="328" /></a></p>
<p>또한 우측에 Dock을 배치하여 체온계를 거치하고 또한 중앙에 충전 단자를 위한 구멍을 내어 체온계를 단단히 고정함과 동시에 충전기능을 구현하였습니다.</p>
<p><span style="color: #00ccff"><strong>7.6.2. 3D Printer 출력</strong></span><br />
설계한 모델링을 슬라이싱을 통해 G-code로 변환한 후 3D프린터로 출력합니다. FDM 방식의 프린터기를 사용했으며 재료는 PLA 흰색입니다. 크기와 구조 때문에 한 번에 모든 파트를 출력하는 것은 불가능하였습니다. 따라서 몸체, 덮개, 손잡이, 거치대, Dock으로 나누어서 출력 후 이어 붙이는 방식으로 제작하였습니다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-32.png" rel="lightbox[40948]"><img class="alignnone size-full wp-image-41035" alt="68 ICT_간호사연동 (32)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-32.png" width="620" height="316" /></a></p>
<p><span style="color: #00ccff"><strong>7.6.3. 후가공</strong></span><br />
후가공은 체온계 과정과 동일하게 퍼티와 락카 스프레이를 이용하여 표면 처리를 하였습니다.</p>
<p><span style="color: #00ccff"><strong>7.6.4. 조립</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-33.png" rel="lightbox[40948]"><img class="alignnone size-full wp-image-41036" alt="68 ICT_간호사연동 (33)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-33.png" width="617" height="322" /></a></p>
<p>체온계, 혈압계 프로그래밍 및 케이스 제작, 조립까지 마무리하였습니다. 혈압계의 디스플레이에 여러 가지 정보가 표시되며 터치를 통해 혈압계를 동작하거나 프로그램을 reboot할 수 있습니다. DB를 이용하여 정보를 불러오기는 물론 측정값을 저장할 수도 있습니다. 하지만 key와 val로 이루어진 DB그 자체로 정보를 열람하거나 처리하는 것은 상당히 불편합니다. 따라서 웹을 통해 DB에 저장된 정보를 보기 좋기 표시하는 벡엔드 파트를 구현하여 본 프로젝트를 마무리합니다.</p>
<p><span style="color: #33cccc"><strong>7.7. 웹 모니터링 시스템</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-34.png" rel="lightbox[40948]"><img class="alignnone size-full wp-image-41037" alt="68 ICT_간호사연동 (34)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-34.png" width="328" height="152" /></a></p>
<p>웹 모니터링을 위해서 플라스크(Flask)를 사용합니다. 플라스크(Flask)는 파이썬 웹 어플리케이션을 만드는 프레임 워크이며, Werkzeug 툴킷과 Jinja2템플릿 엔진에 기반을 둡니다. 플라스크는 프레임워크들 중에서 매우 심플하고 가볍지 만 중요하고 핵심적인 내용과 기능을 갖고 있기에 많이 사용되고 있습니다. 이 Flask를 통해 각 환자의 이름과 데이터를 구분하여 하나의 웹에 표기할 수 있도록 만듭니다.</p>
<p><span style="color: #00ccff"><strong>7.7.1. DB에 저장된 데이터 불러오기</strong></span><br />
웹을 통해서 데이터를 띄우기 전 DB에서 데이터를 불러오는 작업을 수행합니다. 이렇게 DB에 환자의 RFID 카드를 통한 ID 값을 불러오게 되면 ID값 속에 저장되어 있는 모든 데이터가 불러집니다. 이를 통해 DB에 저장되어있는 모든 환자 데이터를 받아올 수 있습니다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-35.png" rel="lightbox[40948]"><img class="alignnone size-full wp-image-41038" alt="68 ICT_간호사연동 (35)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-35.png" width="612" height="496" /></a></p>
<p><span style="color: #00ccff"><strong>7.7.2. DB데이터 가공</strong></span><br />
불러온 데이터를 웹으로 불러오기 전 환자별로 날짜와 시간을 구분하여 데이터를 사용가능하도록 데이터를 가공합니다. 불러온 데이터는 Dict 형태로 모든 데이터는 원하는 데이터를 뽑아 가공하여 사용이 가능합니다. 이를 통해 각 리스트에 날짜와 시간, 시간에 따른 환자 데이터를 각각 모두 구분하여 데이터를 불러올 수 있습니다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-36.png" rel="lightbox[40948]"><img class="alignnone size-full wp-image-41039" alt="68 ICT_간호사연동 (36)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-36.png" width="614" height="275" /></a></p>
<p>저장된 데이터를 Flask에 전달하여 웹상에 표시가 가능하도록 데이터를 넘깁니다.</p>
<p><span style="color: #00ccff"><strong>7.7.3. 웹 디자인</strong></span><br />
웹디자인은 python Flask의 기본 웹 템플릿 엔진인 jinja를 사용합니다. jinja 템플릿 엔진은 태그, 필터, 테스트 및 전역을 사용자 정의가 가능해 자유롭게 사용이 가능합니다. 또한 디버그에 용이하며 최적의 python 코드로 컴파일이 가능하여 많이 사용됩니다. main코드에서 각 분할하여 각각 저장된 데이터를 불러와 웹으로 표현하기 위한 Flask의 html을 사용합니다. 상단의 이름과 함께 table을 나누어 데이터를 구분지어 데이터의 가시성을 더해줍니다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-37.png" rel="lightbox[40948]"><img class="alignnone size-full wp-image-41040" alt="68 ICT_간호사연동 (37)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-37.png" width="612" height="558" /></a></p>
<p><span style="color: #00ccff"><strong>7.7.4. 웹 데이터 추가</strong></span><br />
웹 main코드에서 가공한 이름, 날짜, 시간, 체온 등등 데이터를 각 테이블에 순차적으로 나열하게 됩니다. 이를 통해 DB에 저장된 데이터를 모두 날짜 시간순서대로 나열 가능하여 한눈에 확인 가능하도록 합니다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-38.png" rel="lightbox[40948]"><img alt="68 ICT_간호사연동 (38)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-38.png" width="612" height="456" /></a></p>
<p>이를 통해 여러 데이터가 생겨날 경우 같은 날짜에 대해 시간에 대한 측정값을 하나의 덩어리로 묶고, 날짜가 달라지면 그래프를 한 칸 밀어내어 데이터 구분을 지어 각 날짜별로 쉽게 확인이 가능합니다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-39.png" rel="lightbox[40948]"><img class="alignnone size-full wp-image-40996" alt="68 ICT_간호사연동 (39)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-39.png" width="612" height="264" /></a></p>
<p><strong style="color: #00ccff">7.7.5. 웹 refresh를 통한 주기적인 데이터 업데이트</strong></p>
<p>웹에 데이터를 추가한다고 하더라도 웹페이지는 실행된 시점의 하나의 장면만 보여주기 때문에 DB에 새로 추가된 데이터가 있더라도 웹상에 곧바로 표기되지 않습니다. 따라서 데이터가 추가되는 경우 바로 확인할 수 있도록 지정 초마다 웹 화면을 새로 고침 하여 데이터가 웹상에 반영되도록 합니다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-40.png" rel="lightbox[40948]"><img class="alignnone size-full wp-image-40997" alt="68 ICT_간호사연동 (40)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-40.png" width="612" height="666" /></a><br />
체온계, 혈압계에서 온 데이터를 저장한 DB에서 데이터를 다시 불러와 날짜와 시간에 따라 구분해 웹상에 정리하여 시각적으로 도출하며, 시간마다 새로 고침 하여 실시간으로 추가된 데이터도 바로 반영할 수 있도록 하였습니다. 이를 통하여 하나의 디바이스에서 지정된 ID를 통해 환자 데이터에 접근하여 상태를 측정한 데이터를 저장하고 저장된 DB를 통해 웹상으로 표기하여 주기적인 체온과 혈압의 데이터를 자동으로 저장 확인이 가능합니다.</p>
<p><span style="color: #ff6600"><strong>8. 사용한 제품 리스트</strong></span></p>
<table style="width: 620px" border="1" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td style="text-align: center"><span style="color: #000000">제품명</span></td>
<td style="text-align: center"><span style="color: #000000">디바 상품 번호</span></td>
</tr>
<tr>
<td style="text-align: center"><span style="color: #000000">아두이노 나노 호환보드 CH340 </span></td>
<td style="text-align: center"><span style="color: #000000">1342039</span></td>
</tr>
<tr>
<td style="text-align: center"><span style="color: #000000">블루투스 직렬포트 모듈 HC-05 (DIP)</span></td>
<td style="text-align: center"><span style="color: #000000">1289993</span></td>
</tr>
<tr>
<td style="text-align: center"><span style="color: #000000">MT3608 2A 스텝업 DC컨버터 모듈 </span></td>
<td style="text-align: center"><span style="color: #000000">1342935</span></td>
</tr>
<tr>
<td style="text-align: center"><span style="color: #000000">라즈베리파이4 18650 5V 4A UPS 충전모듈</span></td>
<td style="text-align: center"><span style="color: #000000">12497514</span></td>
</tr>
<tr>
<td style="text-align: center"><span style="color: #000000">초소형 0611 코어리스 진동모터</span></td>
<td style="text-align: center"><span style="color: #000000">1360991</span></td>
</tr>
<tr>
<td style="text-align: center"><span style="color: #000000">라즈베리파이 5인치 HDMI LCD 터치스크린 모니터</span></td>
<td style="text-align: center"><span style="color: #000000">1382229</span></td>
</tr>
<tr>
<td style="text-align: center"><span style="color: #000000">라즈베리파이3 B+ 알뜰키트</span></td>
<td style="text-align: center"><span style="color: #000000">1385482</span></td>
</tr>
<tr>
<td style="text-align: center"><span style="color: #000000">FORA 자동전자혈압계</span></td>
<td style="text-align: center"><span style="color: #000000">12550452</span></td>
</tr>
<tr>
<td style="text-align: center"><span style="color: #000000">18650보호회로 리튬이온 충전지 2000mA</span></td>
<td style="text-align: center"><span style="color: #000000">10889445</span></td>
</tr>
<tr>
<td style="text-align: center"><span style="color: #000000">USB A/M Right-angle 커넥터</span></td>
<td style="text-align: center"><span style="color: #000000">1322880</span></td>
</tr>
<tr>
<td style="text-align: center"><span style="color: #000000">USB A/F, Right-angle 커넥터</span></td>
<td style="text-align: center"><span style="color: #000000">1322109</span></td>
</tr>
<tr>
<td style="text-align: center"><span style="color: #000000">0.96인치 I2C OLED 디스플레이 모듈</span></td>
<td style="text-align: center"><span style="color: #000000">1311755</span></td>
</tr>
<tr>
<td style="text-align: center"><span style="color: #000000">IMMS-12V</span></td>
<td style="text-align: center"><span style="color: #000000">2647</span></td>
</tr>
<tr>
<td style="text-align: center"><span style="color: #000000">18650 리튬배터리 보호회로</span></td>
<td style="text-align: center"><span style="color: #000000">1329640</span></td>
</tr>
<tr>
<td style="text-align: center"><span style="color: #000000">2N2222A</span></td>
<td style="text-align: center"><span style="color: #000000">3247</span></td>
</tr>
<tr>
<td style="text-align: center"><span style="color: #000000">10K옴 저항</span></td>
<td style="text-align: center"><span style="color: #000000">38594</span></td>
</tr>
<tr>
<td style="text-align: center"><span style="color: #000000">1K옴 저항</span></td>
<td style="text-align: center"><span style="color: #000000">30504</span></td>
</tr>
<tr>
<td style="text-align: center"><span style="color: #000000">3색 점퍼용 단선- 0.6 파이</span></td>
<td style="text-align: center"><span style="color: #000000">1153807</span></td>
</tr>
<tr>
<td style="text-align: center"><span style="color: #000000">MicroSDHC 16GB</span></td>
<td style="text-align: center"><span style="color: #000000">10987018</span></td>
</tr>
<tr>
<td style="text-align: center"><span style="color: #000000">비접촉 온도센서 모듈 MLX90614ESF</span></td>
<td style="text-align: center"><span style="color: #000000">10918253</span></td>
</tr>
<tr>
<td style="text-align: center"><span style="color: #000000">핫마인 필라멘트(1kg/PLA)</span></td>
<td style="text-align: center"><span style="color: #000000">12710926</span></td>
</tr>
<tr>
<td style="text-align: center"><span style="color: #000000">택트 스위치</span></td>
<td style="text-align: center"><span style="color: #000000">1361702</span></td>
</tr>
<tr>
<td style="text-align: center"><span style="color: #000000">아두이노 RFID 모듈 RFID-RC522</span></td>
<td style="text-align: center"><span style="color: #000000">1279308</span></td>
</tr>
<tr>
<td style="text-align: center"><span style="color: #000000">범용 페놀 만능 PCB 기판 50&#215;70-단면</span></td>
<td style="text-align: center"><span style="color: #000000">1341710</span></td>
</tr>
<tr>
<td style="text-align: center"><span style="color: #000000">DC 파워잭 2파이</span></td>
<td style="text-align: center"><span style="color: #000000">1322105</span></td>
</tr>
</tbody>
</table>
<p><strong style="color: #ff6600">9. 작품사진</strong></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-41.png" rel="lightbox[40948]"><img class="alignnone size-full wp-image-40998" alt="68 ICT_간호사연동 (41)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-41.png" width="620" height="549" /></a></p>
<p><span style="color: #33cccc"><strong> 9.1. 회로도</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-42.png" rel="lightbox[40948]"><img class="alignnone size-full wp-image-40999" alt="68 ICT_간호사연동 (42)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-42.png" width="620" height="737" /></a> <a href="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-43.png" rel="lightbox[40948]"><img class="alignnone size-full wp-image-41000" alt="68 ICT_간호사연동 (43)" src="http://www.ntrexgo.com/wp-content/uploads/2021/10/68-ICT_간호사연동-43.png" width="620" height="488" /></a></p>
<p><span style="color: #ff6600"><strong>10. 소스코드</strong></span><br />
<span style="color: #0000ff"><strong>체온계 Arduino</strong></span></p>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p>#include &lt;SoftwareSerial.h&gt;<br />
#include &lt;Wire.h&gt;<br />
#include &lt;Adafruit_MLX90614.h&gt;<br />
#include &lt;MsTimer2.h&gt;<br />
#include &lt;SPI.h&gt;<br />
#include &lt;MFRC522.h&gt;<br />
#include &#8220;U8glib.h&#8221;<br />
#define SS_PIN 10<br />
#define RST_PIN 9<br />
U8GLIB_SSD1306_128X64<br />
u8g(U8G_I2C_OPT_NONE);</p>
<p>SoftwareSerial BTSerial(5, 4); //TXD,RXD<br />
Adafruit_MLX90614 mlx<br />
= Adafruit_MLX90614();<br />
MFRC522 rfid(SS_PIN, RST_PIN);<br />
MFRC522::MIFARE_Key key;</p>
<p>String RFID_uid;<br />
int t , count, temperature_button , ic = 0;<br />
float list[3], value;<br />
byte nuidPICC[4];</p>
<p>const uint8_t icon[] PROGMEM = {<br />
0xFE, 0&#215;00, 0&#215;03, 0&#215;00, 0&#215;92, 0&#215;00, 0&#215;02,<br />
0&#215;80, 0&#215;54, 0&#215;00, 0x0A, 0&#215;40, 0&#215;38, 0&#215;04,<br />
0&#215;06, 0&#215;80, 0&#215;10, 0&#215;14, 0&#215;03, 0&#215;00, 0&#215;10,<br />
0&#215;54, 0&#215;06, 0&#215;80, 0&#215;11, 0&#215;54, 0x0A, 0&#215;40,<br />
0&#215;15, 0&#215;54, 0&#215;02, 0&#215;80, 0&#215;15, 0&#215;54,<br />
0&#215;03, 0&#215;00,<br />
};</p>
<p>void setup() {<br />
oled_boot();<br />
attachInterrupt(digitalPinToInterrupt(2), interrupt, RISING);<br />
pinMode(2, INPUT_PULLUP);<br />
pinMode(6, OUTPUT);<br />
Serial.begin(9600);<br />
BTSerial.begin(9600);<br />
SPI.begin();<br />
rfid.PCD_Init();<br />
mlx.begin();<br />
Vibration_boot();<br />
oled_image();<br />
Serial.println(&#8220;Ready&#8221;);<br />
temperature_button = 0;<br />
}</p>
<p>void flash() {<br />
Serial.println(count);<br />
count ++;<br />
}</p>
<p>void interrupt() {<br />
temperature_button = 1;<br />
}</p>
<p>void printDec(byte *buffer, byte bufferSize) {<br />
BTSerial.print(&#8220;BC&#8221;);<br />
RFID_uid = &#8220;&#8221;;<br />
for (byte i = 0; i &lt; bufferSize; i++) {<br />
char c[2];<br />
ltoa(buffer[i], c, 16);<br />
if (buffer[i] &lt; 0&#215;10)<br />
{<br />
RFID_uid = RFID_uid + &#8220;0&#8243;;<br />
}<br />
RFID_uid = RFID_uid + c;<br />
BTSerial.print(buffer[i] &lt; 0&#215;10 ? &#8220;0&#8243; : &#8220;&#8221;);<br />
BTSerial.print(buffer[i], HEX);<br />
}<br />
BTSerial.print(&#8220;Z&#8221;);<br />
}</p>
<p>void loop() {<br />
if (temperature_button != 0) {<br />
if (ic == 0) {<br />
for (int i = 0; i &lt; 3; i++) {<br />
list[i] = 0;<br />
}<br />
MsTimer2::set(1000, flash);<br />
MsTimer2::start();<br />
ic = 1;<br />
}<br />
list[count] = mlx.readObjectTempC();<br />
if (list[count] &gt; 20) {<br />
if (int(list[0]) == int(list[1]) &amp;&amp;<br />
int(list[1]) == int(list[2]))<br />
{<br />
if (t == 0) {<br />
value = (list[0] +<br />
list[1] + list[2]) / 3.0;<br />
BTSerial.println(&#8220;BT&#8221; +<br />
String(value) + &#8220;Z&#8221;);<br />
Serial.print(&#8220;Object = &#8220;);<br />
Serial.println(value);<br />
MsTimer2::stop();<br />
t = 1;<br />
temperature_button = 0;<br />
ic = 0;<br />
count = 0;<br />
Vibration_measuring();<br />
oled_image();<br />
} else {<br />
t = 0;<br />
}<br />
}<br />
}<br />
}<br />
if (count == 3) {<br />
count = 0;<br />
}</p>
<p>if ( ! rfid.PICC_IsNewCardPresent())<br />
return;<br />
if ( ! rfid.PICC_ReadCardSerial())<br />
return;<br />
printDec(rfid.uid.uidByte, rfid.uid.size);<br />
Serial.println();<br />
BTSerial.println();<br />
Vibration_measuring();<br />
oled_image();<br />
rfid.PICC_HaltA();<br />
rfid.PCD_StopCrypto1();<br />
}<br />
</div>
<p><span style="color: #0000ff"><strong>혈압계 RaspberryPi</strong></span></p>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p># -*- coding: utf-8 -*-<br />
import threading<br />
import pyrebase<br />
import datetime<br />
import RPi.GPIO as GPIO<br />
from bluetooth import*<br />
from dock_ui import *<br />
from PyQt5.QtCore import pyqtSignal<br />
from PyQt5 import QtCore, QtGui, QtWidgets<br />
from firebase import firebase<br />
from time import sleep<br />
from random import *</p>
<p>Motor_PIN = 26<br />
Valve_PIN = 19</p>
<p>GPIO.setmode(GPIO.BCM)<br />
GPIO.setup(Motor_PIN, GPIO.OUT, initial=GPIO.LOW)<br />
GPIO.setup(Valve_PIN, GPIO.OUT, initial=GPIO.LOW)</p>
<p>firebase1 = firebase.FirebaseApplication(&#8220;https://kono-iot-default-rtdb.firebaseio.com/&#8221;)</p>
<p>config = { #개인 코드가 부여되는 공간입니다<br />
}</p>
<p>firebase = pyrebase.initialize_app(config)<br />
db = firebase.database()</p>
<p>y = &#8220;&#8221;<br />
newcode = &#8220;&#8221;<br />
code1 = &#8220;&#8221;<br />
code2 = &#8220;&#8221;<br />
tmp2 = 0<br />
name = &#8220;&#8221;<br />
state = 0<br />
font = &#8220;&#8221;<br />
count = 1<br />
PUL = 0 #pulse 맥박 / 60~100회<br />
DIA = 0 #diastolic b.p 이완기 혈압 / 60~80<br />
SYS = 0 #systolic b.p 수축기 혈압 / 90~120</p>
<p>client_socket=BluetoothSocket( RFCOMM )<br />
state =<br />
client_socket.connect_ex((&#8220;98:d3:c1:fd:87:fe&#8221;,1)) #98:d3:c1:fd:87:fe</p>
<p>def bluetooth(self,ui):<br />
global state,<br />
client_socket,y,newcode,code2,code1,tmp2,name,font,count,PUL, SYS, DIA<br />
nowDate = 0<br />
nowtime = 0<br />
while True:<br />
ui.uiUpdateDelegate.emit(1)</p>
<p>if state: client_socket=BluetoothSocket( RFCOMM )<br />
state =<br />
client_socket.connect_ex((&#8220;98:d3:c1:fd:87:fe&#8221;,1)) #98:d3:c1:fd:87:fe / 98:d3:71:fd:68:ff<br />
print(&#8220;waiting&#8230;&#8221;) sleep(1.5) ui.uiUpdateDelegate.emit(1)<br />
else:<br />
try:<br />
x = client_socket.recv(1024) if(len(x)&gt;0): for i in range(0, len(x), 1):<br />
y = y + x[i]
if(x[i] == &#8220;Z&#8221;):<br />
newcode = y<br />
print(newcode)<br />
y = &#8220;&#8221;<br />
if newcode.find(&#8220;BT&#8221;) != -1:<br />
b = newcode.index(&#8220;BT&#8221;)<br />
tmp1 = newcode[b+2:-1]
tmp2 = float(tmp1)<br />
newcode = &#8220;&#8221;<br />
dt = datetime.datetime.now()<br />
nowDate = dt.strftime(&#8216;%Y-%m-%d&#8217;)<br />
nowtime=dt.strftime(&#8216;%H-%M-%S&#8217;)</p>
<p>if newcode.find(&#8220;BC&#8221;) != -1:<br />
a = newcode.index(&#8220;BC&#8221;)<br />
co = newcode[a+2:-1]
code1 = co<br />
name = firebase1.get(code1,&#8217;name&#8217;)</p>
<p>if(name == None):<br />
font = &#8220;background:transparent;\n&#8221;"font:Bold 14pt \&#8221;Arial\&#8221;;\n&#8221; &#8220;\n&#8221; &#8220;color:rgb(255, 0, 0);&#8221; code1 = &#8220;&#8221; code2 = &#8220;&#8221;<br />
name = &#8220;등록되지 않은 사용자&#8221;<br />
else:<br />
font = &#8220;background:transparent;\n&#8221;"font:Bold 20pt \&#8221;Arial\&#8221;;\n&#8221;"\n&#8221;"color:rgb(255, 255, 255);“<br />
newcode = &#8220;&#8221;</p>
<p>if len(code2)&gt;0 and (code1) != (code2):<br />
print(nowDate) db.child(code2).child(&#8220;value&#8221;).child(nowDate).child(nowtime).update({&#8220;tmp&#8221;: tmp2}) db.child(code2).child(&#8220;value&#8221;).child(nowDate).child(nowtime).update({&#8220;PUL&#8221;: PUL}) db.child(code2).child(&#8220;value&#8221;).child(nowDate).child(nowtime).update({&#8220;DIA&#8221;: DIA}) db.child(code2).child(&#8220;value&#8221;).child(nowDate).child(nowtime).update({&#8220;SYS&#8221;: SYS}) code2 = code1 PUL = 0<br />
DIA = 0<br />
SYS = 0<br />
tmp2 = 0<br />
print(&#8220;upload complete&#8221;)<br />
code2 = code1<br />
except:<br />
state = 1<br />
continue<br />
ui.uiUpdateDelegate.emit(1)<br />
def bloodpressure(self, ui):<br />
global PUL, SYS, DIA, count<br />
while True:<br />
if count == 1:<br />
continue<br />
elif count == 0:<br />
if actuating():<br />
count = 1 continue PUL = randint(60, 100) SYS = randint(110, 130) DIA = randint(70, 80) ui.uiUpdateDelegate.emit(1) count = 1</p>
<p>def actuating():<br />
print(&#8220;operating&#8221;)<br />
GPIO.output(Motor_PIN, 1)<br />
GPIO.output(Valve_PIN, 1)<br />
if waiting_and_repeat(5):<br />
GPIO.output(Motor_PIN, 0) GPIO.output(Valve_PIN, 0)<br />
return 1<br />
GPIO.output(Motor_PIN, 0)<br />
GPIO.output(Valve_PIN, 0)</p>
<p>def waiting_and_repeat(i):<br />
for _ in range (0,i):<br />
if count == 1:<br />
return 1<br />
sleep(0.5)<br />
if count == 1:<br />
return 1<br />
sleep(0.5)<br />
return 0<br />
class MyFirstGuiProgram<br />
(QtWidgets.QMainWindow, Ui_MainWindow):<br />
global y,newcode<br />
,code2,code1,tmp2,name,font, DIA,SYS,PUL<br />
uiUpdateDelegate = pyqtSignal(int)<br />
def __init__(self, parent=None):<br />
QtWidgets.QMainWindow.__init__(self, parent=parent)<br />
self.setupUi(self)<br />
self.uiUpdateDelegate.connect(self.uiUpdater)<br />
self.pushButton.clicked.connect(self.pushButton_clicked)<br />
self.pushButton_2.clicked.connect(self.pushButton_2_clicked)<br />
self.pushButton_3.clicked.connect(self.pushButton_3_clicked)<br />
def uiUpdater(self):<br />
self.name.setStyleSheet(font) self.TEM.setText(str(tmp2))<br />
self.PUL.setText(str(PUL)) self.DIA.setText(str(DIA))<br />
self.SYS.setText(str(SYS)) self.number.setText(code1)<br />
self.name.setText(name)<br />
if (state == 0): self.lineEdit.setStyleSheet(&#8220;background-image:url(:/work/bt.png)&#8221;)<br />
else: self.lineEdit.setStyleSheet(&#8220;&#8221;)<br />
def pushButton_3_clicked (self): client_socket.close()<br />
GPIO.cleanup() sys.exit()<br />
def pushButton_2_clicked (self): client_socket.close()<br />
GPIO.cleanup()<br />
NEWCODE = &#8220;sudo reboot&#8221; os.system(NEWCODE)<br />
sys.exit()<br />
def pushButton_clicked (self):<br />
global count<br />
print(&#8220;Start&#8221;)<br />
if count ==1: count = 0<br />
else:<br />
count = 1<br />
if __name__ == &#8216;__main__&#8217;:<br />
import sys<br />
app = QtWidgets.QApplication(sys.argv)<br />
ui = MyFirstGuiProgram()<br />
#ui.show()<br />
ui.showFullScreen()<br />
thread = threading.Thread(target=bluetooth, args=(None,ui))<br />
thread2 = threading.Thread(target=bloodpressure, args=(None,ui))<br />
thread.daemon = True<br />
thread2.daemon = True<br />
thread.start()<br />
thread2.start()<br />
sys.exit(app.exec_())<br />
</div>
<p><span style="color: #0000ff"><strong>웹 모니터링(서버)</strong></span></p>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p>import pyrebase<br />
from flask import Flask, render_template, request</p>
<p>config = {<br />
#개인 코드가 부여되는 공간입니다<br />
}</p>
<p>firebase = pyrebase.initialize_app(config)<br />
db = firebase.database()<br />
app = Flask(__name__)<br />
app.jinja_env.add_extension(&#8216;jinja2.ext.loopcontrols&#8217;)<br />
@app.route(&#8216;/&#8217;, methods=['POST', 'GET'])</p>
<p>def main():</p>
<p>tmp1 = db.child(&#8217;02C02E13&#8242;).get()<br />
origin = tmp1.val()</p>
<p>tmp_name = origin.get(&#8216;name&#8217;)<br />
origin1 = origin.get(&#8216;value&#8217;)</p>
<p>day_value = list()<br />
day_list = list(origin1.keys())<br />
day_list_len = len(day_list)<br />
real_value = list()<br />
for i in range(len(day_list)):<br />
day_value.append(&#8216;dic&#8217;+ str(i))<br />
aa = origin1[day_list[i]]
day_value[i] = dict()<br />
day_value[i].update(aa)</p>
<p>for i in range(len(day_list)):<br />
for j in range(len(day_value[i])):<br />
time_value = list(day_value[i])<br />
for k in range(len(day_value[i][time_value[j]])):<br />
mea_value = list(day_value[i][time_value[j]].keys())<br />
real_value.append(day_value[i][time_value[j]][mea_value[k]])</p>
<p>tmp2 = db.child(&#8217;0676041B&#8217;).get()<br />
origin2 = tmp2.val()</p>
<p>tmp_name2 = origin2.get(&#8216;name&#8217;)<br />
origin2 = origin2.get(&#8216;value&#8217;)</p>
<p>day_value2 = list()<br />
day_list2 = list(origin2.keys())<br />
day_list_len2 = len(day_list2)<br />
real_value2 = list()<br />
for i in range(len(day_list2)):<br />
day_value2.append(&#8216;dic&#8217;+ str(i))<br />
aa2 = origin2[day_list2[i]]
day_value2[i] = dict()<br />
day_value2[i].update(aa2)</p>
<p>for i in range(len(day_list2)):<br />
for j in range(len(day_value2[i])):<br />
time_value2 = list(day_value2[i])<br />
for k in range(len(day_value2[i][time_value2[j]])):<br />
mea_value2 = list(day_value2[i][time_value2[j]].keys())<br />
real_value2.append(day_value2[i][time_value2[j]][mea_value2[k]])</p>
<p>return render_template(&#8216;flask jinja.html&#8217;,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)</p>
<p>if __name__ == &#8216;__main__&#8217;:<br />
app.run(debug=True)<br />
</div>
<p><span style="color: #0000ff"><strong>웹 모니터링(서버)</strong></span></p>
<div class="symple-box yellow none" style="text-align:left; width:100%;"> 
<p>&lt;!DOCTYPE html&gt;<br />
&lt;html&gt;<br />
&lt;head&gt;<br />
&lt;meta http-equiv=&#8221;refresh&#8221; content=&#8221;3&#8243;&gt;<br />
&lt;style&gt;<br />
table{width: 100%;}<br />
td {text-align: center;}<br />
p{font-size: 25px;}<br />
p1{font-size: 18px;}<br />
&lt;/style&gt;<br />
&lt;/head&gt;<br />
&lt;body&gt;<br />
&lt;div&gt;<br />
&lt;h1&gt;간호사의 업무부담을 줄여주는 EMR연동 Vital 계측 시스템&lt;/h1&gt;<br />
&lt;/div&gt;<br />
&lt;hr&gt;<br />
&lt;br&gt;</p>
<p>&lt;table border =3&gt;<br />
&lt;tr&gt;<br />
&lt;td rowspan=&#8221;50&#8243;&gt;&lt;p&gt;{{tmp_name}}&lt;/p&gt;&lt;/td&gt;<br />
&lt;/tr&gt;<br />
&lt;tr&gt;<br />
&lt;th&gt;&lt;p&gt;날짜&lt;/p&gt;&lt;/th&gt;<br />
&lt;th&gt;&lt;p&gt;시간&lt;/p&gt;&lt;/th&gt;<br />
&lt;th&gt;&lt;div&gt;&lt;p&gt;측정값&lt;/p&gt;&lt;/div&gt;&lt;/th&gt;<br />
&lt;/tr&gt;</p>
<p>{% for value in range(ttlen)%}<br />
{% for key2, value2 in name[value].items() %}<br />
&lt;th&gt;&lt;p1&gt;{{ tt[value] }}&lt;/p1&gt;&lt;/th&gt;<br />
&lt;td&gt;&lt;p1&gt; {{ key2 }} &lt;/p1&gt;&lt;/td&gt;<br />
&lt;td&gt;&lt;p1&gt; {{ value2 }} &lt;/p1&gt;&lt;/td&gt;<br />
&lt;tr&gt;<br />
&lt;/tr&gt;<br />
{% endfor %}<br />
&lt;tr&gt;<br />
&lt;td colspan=&#8221;6&#8243;&gt;&lt;div3&gt;.&lt;/div3&gt;&lt;/td&gt;<br />
&lt;/tr&gt;<br />
{% endfor %}<br />
&lt;/table&gt;<br />
&lt;br&gt;<br />
&lt;hr&gt;<br />
&lt;br&gt;</p>
<p>&lt;table border =3&gt;<br />
&lt;tr&gt;<br />
&lt;td rowspan=&#8221;50&#8243;&gt;&lt;p&gt;{{tmp_name2}}&lt;/p&gt;&lt;/td&gt;<br />
&lt;/tr&gt;<br />
&lt;tr&gt;<br />
&lt;th&gt;&lt;p&gt;날짜&lt;/p&gt;&lt;/th&gt;<br />
&lt;th&gt;&lt;p&gt;시간&lt;/p&gt;&lt;/th&gt; &lt;th&gt;&lt;div&gt;&lt;p&gt;측정값&lt;/p&gt;&lt;/div&gt;&lt;/th&gt;<br />
&lt;/tr&gt;</p>
<p>{% for value in range(ttlen2)%}<br />
{% for key2, value2 in name2[value].items() %}<br />
&lt;th&gt;&lt;p1&gt;{{ tt2[value] }}&lt;/p1&gt;&lt;/th&gt;<br />
&lt;td&gt;&lt;p1&gt; {{ key2 }} &lt;/p1&gt;&lt;/td&gt;<br />
&lt;td&gt;&lt;p1&gt; {{ value2 }} &lt;/p1&gt;&lt;/td&gt;<br />
&lt;tr&gt;<br />
&lt;/tr&gt;<br />
{% endfor %}<br />
&lt;tr&gt;<br />
&lt;td colspan=&#8221;6&#8243;&gt;&lt;div3&gt;.&lt;/div3&gt;&lt;/td&gt;<br />
&lt;/tr&gt;<br />
{% endfor %}<br />
&lt;/table&gt;<br />
&lt;br&gt;<br />
&lt;hr&gt;<br />
&lt;br&gt;</p>
<p>&lt;/table&gt;<br />
&lt;/html&gt;<br />
</div>
<p>&nbsp;</p>
<p>&nbsp;</p>
]]></content:encoded>
			<wfw:commentRss>http://www.ntrexgo.com/archives/40948/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>[67호]딥러닝을 결합시킨 임베디드 기구 오목 AI  : 베타오</title>
		<link>http://www.ntrexgo.com/archives/40434</link>
		<comments>http://www.ntrexgo.com/archives/40434#comments</comments>
		<pubDate>Sat, 25 Sep 2021 00:00:39 +0000</pubDate>
		<dc:creator>디바이스마트 매거진</dc:creator>
				<category><![CDATA[디바이스마트 매거진]]></category>
		<category><![CDATA[특집]]></category>
		<category><![CDATA[AI]]></category>
		<category><![CDATA[ict]]></category>
		<category><![CDATA[공모전]]></category>
		<category><![CDATA[대상]]></category>
		<category><![CDATA[디바이스마트]]></category>
		<category><![CDATA[딥러닝]]></category>
		<category><![CDATA[매거진]]></category>
		<category><![CDATA[오목]]></category>
		<category><![CDATA[융합]]></category>
		<category><![CDATA[프로젝트]]></category>

		<guid isPermaLink="false">http://www.ntrexgo.com/?p=40434</guid>
		<description><![CDATA[디바이스마트 매거진 67호 &#124; Omok AI는 gomocup의 데이터를 이용하여 CNN 기반으로 최적의 다음 수를 둘 수 있는 AI이다. ]]></description>
				<content:encoded><![CDATA[<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오1.png" rel="lightbox[40434]"><img alt="67_ICT_대상_오목AI 베타오(1)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오1-620x201.png" width="620" height="201" /></a></p>
<p>&nbsp;</p>
<p><span style="font-size: large;color: #ff6600"><strong>2021 ICT 융합 프로젝트 공모전 대상</strong></span></p>
<p><span style="font-size: x-large"><strong>딥러닝을 결합시킨 임베디드 기구 오목 AI : 베타오&#8217;</strong></span></p>
<p style="text-align: right">글 | 호서대학교 박정준, 김진우, 박정섭, 김선혁, 장서윤, 이성용, 이준용</p>
<p><span style="color: #0000ff"><strong>1. 심사평</strong></span><br />
<strong>칩센</strong> Vision을 활용한다고 했을 때 바둑판과 바둑돌은 매우 명확하게 구분이 가능한 환경이라 볼 수 있겠습니다. 따라서 작품의 완성도는 AI의 성능과 robot의 움직임을 포함한 성능이 될 것으로 보여졌습니다. 동영상 및 보고서상으로 확인한 바로는 프로파일을 LM 가이드로 움직임에 따라 상부에 설치된 카메라에 간섭이 자주 발생하게 되는 부분이 구조적인 아쉬운 느낌이 들었습니다. 향후 실제 경기용으로 적용을 하기 위해서는, 현재의 작품은 사람과 AI 데이터를 기반으로한 로봇간의 대결이나, 로봇간의 대결을 통한 머신/딥러닝 과정을 진행함으로써 쌓인 데이터 축적으로 난이도와 완성도를 높일 수 있을 듯합니다.</p>
<p><strong>펌테크</strong> 딥러닝, AI로봇, 영상처리 등의 난이도가 있는 기술을 효율적으로 접목하여 기획의도에 맞게 시스템을 안정적이면서 완성도 높게 구현한 인상적인 작품으로 작품의 아이디어와 창의성이 돋보이며 전체적으로 기획의도, 기술 구현도, 완성도 등에서 상당히 뛰어나고 우수한 작품으로 생각됩니다.</p>
<p><strong>위드로봇</strong> 영상처리, DNN 학습 및 응용, 로봇 제어가 모두 포함된 완성도 높은 작품입니다.</p>
<p><strong>뉴티씨</strong> 요즘 핫이슈이고 AI를 배워서 나오게 되면 연봉도 많이 받을 수 있다고 하죠? 어딜가도 AI가 대세입니다. AI를 접목하고 CNC 등을 동작시켜 오목을 둘 수 있도록 한 아이디어가 참 좋습니다. 로봇도 학습하고 AI도 학습하고. 교육 목적으로도 참 좋은 작품 같습니다. 비슷한 기술로 생각해보면 카메라로 사진을 찍고, 그것을 영상처리하여 스케치한 것처럼 만들고, 그것을 그리는 로봇도 쉽게 만들 수 있을 것 같습니요. 수고많으셨습니다.</p>
<p><span style="color: #0000ff"><strong>2. 작품 개요</strong></span><br />
<span style="color: #00ccff"><strong>제작 동기</strong></span><br />
로봇자동화공학과에 재학 중 ‘AI 인공지능과 AI 인공지능을 직접 눈으로 볼 수 있는 하드웨어를 구상하던 중 오목 AI와 오목을 둘 수 있는 하드웨어를 구상해보자’는 아이디어를 얻어 Omok AI 베타오를 제작하게 되었다.</p>
<p><span style="color: #00ccff"><strong>제작 목적</strong></span><br />
알파고와 이세돌의 경기 후, 공학계에서는 인공지능의 중요도가 급부상했다. 하지만 교육적으로 임베디드에 AI를 활용한 사례는 극히 드물다. SW 교육과 AI 활용에 도움이 될 수 있는 AI 오목 게임 로봇이다.</p>
<p><span style="color: #00ccff"><strong>작품 요약</strong></span><br />
Omok AI는 gomocup의 데이터를 이용하여 CNN 기반으로 최적의 다음 수를 둘 수 있는 AI이다. 이를 통해 openCV로 검은돌(사용자)가 돌을 둔 곳의 위치를 인식 받아 아두이노와 하드웨어가 3D 프린터, CNC 가공 기계와 같이 움직여 오목의 돌을 옮겨주는 로봇이다.</p>
<p><span style="color: #0000ff"><strong>3. 작품 설명</strong></span><br />
<span style="color: #00ccff"><strong>작품 주요 기능</strong></span><br />
작품 주요 기능은 크게 하드웨어, 소프트웨어, 임베디드 시스템 3가지로 구성되어 있다.<br />
하드웨어는 카메라가 고정되어 달리고 모터가 x, y, z 3차원 직교좌표계를 따라 움직이는 본체이다. 서보, 리니어, 스텝 모터 3종류를 제어해 3D 프린터와 비슷하게 일정 높이에서 돌을 이동시킨다. 이로써 플레이어가 손을 대지 않아도 컴퓨터와 게임에 참여할 수 있는 것을 확인할 수 있다.<br />
소프트웨어는 크게 오목 경기를 화면 GUI상에서 확인하고 게임에 참여할 수 있도록 짜인 모듈의 집합체인 GUI 프로그램, 경기를 할 수 있는 Ai 학습 과정 전체에 대한 학습 코드, 파이썬과 아두이노를 통신, 아두이노를 제어하는 코드 세 가지로 나뉜다.<br />
GUI 프로그램은 게임에 참여하는 플레이어가 오목 경기를 하는 도중 사용할 오목판 GUI의 색과 좌표, 돌의 크기 등을 속성값으로 변환할 수 있다. 또 오목의 룰을 정의해 게임의 승패 조건을 판단하고 정의한다. 이때 사용되는 오목의 룰은 Freestyle(자유룰)로 오목돌이 5개 이상 모였을 경우에 승리하는 룰을 사용한다.<br />
학습 코드는 gomocup에서 2020년 결과 데이터를 얻어 CNN으로 학습한다. 오목 경기가 이루어진 수많은 데이터를 전처리해서, 컴퓨터-사람 간의 경기가 이루어질 수 있도록 게임의 룰을 이해하고 학습할 수 있도록 한다.<br />
임베디드 시스템은 영상처리 과정을 거친 좌표값을 받아 하드웨어를 구성할 수 있는 제어 보드 및 프로세스이다. 모터제어를 위해 아두이노 우노를 사용해 시리얼 통신으로 PC로 좌표값을 보낸다. 메인 제어기인 아두이노 우노는 총 2개를 사용하며 각각 x축 스텝모터, y축 스텝모터와 리니어, 서보모터를 제어한다.</p>
<p><span style="color: #00ccff"><strong>작품 활용 방안</strong></span><br />
카메라를 활용해 바둑판을 인식하고 원하는 좌표값을 받아 알맞은 위치에 돌을 두고, 오목 경기를 진행할 수 있도록 하는 작품이다. 실제 오목 경기를 연습할 수 있는 AI 심판을 제작하는 셈이지만, 임베디드에 AI를 적용시키는 프로젝트로써 관련 컨텐츠가 교재 및 강의와 함께 교육용 작품으로 활용할 수 있다.</p>
<p><span style="color: #33cccc"><strong>3.1. 주요 동작 및 특징</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오2.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40527" alt="67_ICT_대상_오목AI 베타오(2)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오2.png" width="562" height="606" /></a></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오3.jpg" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40528" alt="67_ICT_대상_오목AI 베타오(3)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오3.jpg" width="565" height="328" /></a></p>
<p><span style="color: #33cccc"><strong>3.2. 전체 시스템 구성</strong></span><br />
<span style="color: #00ccff"><strong>전체 시스템 구성도</strong></span></p>
<p><span style="color: #00ccff"><strong><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오4.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40531" alt="67_ICT_대상_오목AI 베타오(4)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오4.png" width="562" height="272" /></a></strong></span><br />
시스템은 다음과 같이 구성되어있다. 하드웨어와 임베디드 시스템은 PC를 통해 영상 처리 결과 정보 및 좌표값을 시리얼 통신을 통해 수신받는다. 웹캠-PC간에 영상처리 정보 결과가 양방향으로 전달되며, 파이썬 IDE 파이참이 시리얼 통신을 통해 아두이노에 좌표값을 전달한다. 사람과 AI가 플레이를 하는 동안 파이썬은 지속해서 게임의 룰을 지키며 다음 돌의 최적의 위치를 예측하여 두게 된다. 최종적으로 위의 세 구성 요소가 상호작용함에 따라 시스템이 동작한다.</p>
<p><span style="color: #00ccff"><strong>세부 구성도</strong></span></p>
<p><span style="color: #00ccff"><strong><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오5.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40532" alt="67_ICT_대상_오목AI 베타오(5)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오5.png" width="562" height="424" /></a></strong></span><br />
세부 구성도는 [그림 4]와 같이 크게 HW, Embedded 시스템, PC/CAM, SW로 구성되어 있다.</p>
<p><span style="color: #00ccff"><strong>하드웨어 구성도</strong></span></p>
<p><span style="color: #00ccff"><strong><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오6.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40533" alt="67_ICT_대상_오목AI 베타오(6)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오6.png" width="562" height="364" /></a></strong></span><br />
<span style="color: #00ccff"><strong>Embedded system 회로도</strong></span></p>
<p><span style="color: #00ccff"><strong><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오7.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40534" alt="67_ICT_대상_오목AI 베타오(7)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오7.png" width="562" height="357" /></a></strong></span></p>
<p><span style="color: #00ccff"><strong><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오8.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40535" alt="67_ICT_대상_오목AI 베타오(8)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오8.png" width="562" height="322" /></a></strong></span></p>
<p><span style="color: #00ccff"><strong><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오9.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40536" alt="67_ICT_대상_오목AI 베타오(9)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오9.png" width="564" height="284" /></a></strong></span></p>
<p><span style="color: #00ccff"><strong>소프트웨어</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오10.png" rel="lightbox[40434]"><img class="alignnone size-large wp-image-40537" alt="67_ICT_대상_오목AI 베타오(10)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오10-532x620.png" width="532" height="620" /></a></p>
<p><span style="color: #33cccc"><strong>3.3. 개발 환경</strong></span><br />
<span style="color: #00ccff"><strong>소프트웨어</strong></span><br />
파이썬 IDE 파이참, 학습을 위한 jupyter notebook</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오12.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40539" alt="67_ICT_대상_오목AI 베타오(12)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오12.png" width="555" height="152" /></a></p>
<p>모터 제어를 위한 Arduino IDE</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오13.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40540" alt="67_ICT_대상_오목AI 베타오(13)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오13.png" width="566" height="260" /></a></p>
<p><strong><span style="color: #00ccff">개발 환경</span></strong></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오14.png" rel="lightbox[40434]"><img class="alignnone size-large wp-image-40541" alt="67_ICT_대상_오목AI 베타오(14)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오14-435x620.png" width="435" height="620" /></a></p>
<p><span style="color: #00ccff"><strong>Python 3.7.10 환경과 jupyter 가상환경 구축 및 실행</strong></span></p>
<p><span style="color: #00ccff"><strong><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오15.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40542" alt="67_ICT_대상_오목AI 베타오(15)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오15.png" width="570" height="366" /></a></strong></span><br />
<strong>Tensorflow 2.4.0 version &amp; GPU RTX 2060 SUPER</strong><br />
<strong> </strong></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오16.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40543" alt="67_ICT_대상_오목AI 베타오(16)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오16.png" width="563" height="238" /></a></p>
<p><strong>영상 인식(openCV)</strong><br />
<strong> </strong></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오17.png" rel="lightbox[40434]"><img class="alignnone size-large wp-image-40544" alt="67_ICT_대상_오목AI 베타오(17)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오17-458x620.png" width="458" height="620" /></a></p>
<p><strong>아두이노와 파이참 통신</strong><br />
<strong> </strong></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오18.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40545" alt="67_ICT_대상_오목AI 베타오(18)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오18.png" width="565" height="158" /></a></p>
<p><span style="color: #00ccff"><strong>임베디드 시스템</strong></span><br />
<strong> 임베디드 시스템 제작 시 준비물</strong></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오19.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40546" alt="67_ICT_대상_오목AI 베타오(19)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오19.png" width="564" height="220" /></a></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오20.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40547" alt="67_ICT_대상_오목AI 베타오(20)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오20.png" width="567" height="216" /></a><br />
<strong> </strong></p>
<p><span style="color: #00ccff"><strong>하드웨어</strong></span><br />
<strong> 사용 시스템 및 Tool</strong><br />
솔리드웍스, DP103, 3DWOX, 레이저 커터, 3D 펜 등을 가지고 작품을 제작하였으며 각각의 세부 설명은 다음과 같다. 솔리드웍스는 3D CAD 설계 Tool로 스텝 모터 고정 대, LM 가이드라인, 바둑돌 사출기, 스위치 고정 등에 사용되었으며 설계 파일 및 도면은 최종 원고에 사용한 자료에 포함되어 있다. 솔리드웍스의 버전은 Solidworks 2019 버전을 사용하였다.<br />
3D 프린터는 Sindoh 사의 DP103을 사용하여 프린팅하였으며 stl 파일을 Gcode로 변환해주는 Tool은 3DWOX를 사용하였다. 또한 3D 펜을 사용하여 3D 프린터 출력물을 수정 보완하였으며 스위치 고정 지지대 출력 및 프로파일 고정대 출력 등에 사용하였다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오21.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40548" alt="67_ICT_대상_오목AI 베타오(21)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오21.png" width="566" height="255" /></a></p>
<p><strong>HW 제작 시 준비물</strong></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오22.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40549" alt="67_ICT_대상_오목AI 베타오(22)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오22.png" width="566" height="325" /></a></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오23.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40550" alt="67_ICT_대상_오목AI 베타오(23)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오23.png" width="566" height="317" /></a></p>
<p>&nbsp;</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오24.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40551" alt="67_ICT_대상_오목AI 베타오(24)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오24.png" width="563" height="590" /></a></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오1.jpg" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40524" alt="67_ICT_대상_오목AI 베타오(1)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오1.jpg" width="564" height="589" /></a></p>
<p><span style="color: #0000ff"><strong>4. 단계별 제작 과정</strong></span><br />
<span style="color: #00ccff"><strong>하드웨어(컨셉 설계도 및 설계도)</strong></span></p>
<p><span style="color: #00ccff"><strong><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오2.jpg" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40526" alt="67_ICT_대상_오목AI 베타오(2)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오2.jpg" width="563" height="291" /></a></strong></span><br />
초기 컨셉 설계도로 x, y축의 초기 설정과 베타오의 기본 베이스가 되는 설계도이다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오25.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40552" alt="67_ICT_대상_오목AI 베타오(25)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오25.png" width="563" height="332" /></a></p>
<p>베타오의 최종 완성된 사진이다. 최종 완성되기까지의 과정은 아래와 같은 과정을 거처 진행된다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오3.jpg" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40528" alt="67_ICT_대상_오목AI 베타오(3)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오3.jpg" width="565" height="328" /></a></p>
<p>사진과 같이 오목판의 크기를 측정하여 측정된 크기에 맞게 프로파일을 가공하여 연결한다. 모든 단계의 기초이자 가장 중요한 부분이므로 프로파일과 프로파일을 연결하는 알루미늄 다이캐스트 브라켓과 렌치볼트를 사용하여 단단하게 고정해야 한다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오26.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40553" alt="67_ICT_대상_오목AI 베타오(26)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오26.png" width="566" height="331" /></a></p>
<p>y축이 x축 위에서 움직일 수 있도록 해주는 LM 가이드 라인을 프로파일과 연결할 수 있도록 설계한 것이다. LM 가이드의 수평과 고정을 동시에 고려하며 설계한 설계 부품이다. 스테인레스 재질의 LM 가이드 라인DMF 사용하려 계획했었지만, 프로파일과의 정교한 고정과 비용적인 측면에서 어려움을 겪어 기존에 있는 스테인레스 재질의 LM 가이드 라인을 역설계하여 베타오에 맞게끔 설계하였다.</p>
<p>&nbsp;</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오4.jpg" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40530" alt="67_ICT_대상_오목AI 베타오(4)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오4.jpg" width="565" height="332" /></a></p>
[그림 47]와 같이 프로파일 지지대의 상층부를 프로파일과 알루미늄 다이캐스트 브라켓으로 연결한 후 수평을 맞추어준다. 이 과정에서 수평은 수평계를 활용하여 맞춘다. LM 가이드 라인과 LM 가이드가 최소한의 마찰을 가지며 움직여야 정확한 좌표를 얻을 수 있기 때문에 이 과정에 시간을 많이 투자해야 된다. 이 과정을 거치고 z축에 바둑돌 사출기를 장착하면 [그림 48]와 같아진다.</p>
<p>&nbsp;</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오27.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40554" alt="67_ICT_대상_오목AI 베타오(27)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오27.png" width="568" height="337" /></a></p>
<p>스위치는 스텝 모터의 x, y축의 양단에 한계점의 신호를 받기 위해 2개를 장착한다. 각각의 스위치를 고정하는데 3D 펜을 사용하여 보다 정밀하게 제작하였다. 스위치의 위치가 바로 원점의 위치가 되므로 정밀하게 제작되어야 한다. [그림 50]와 같이 카메라를 고정하였는데 이는 openCV에서 바둑판의 전체를 인식 받아야 하기 때문에 중앙에 설치하게 되었다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오28.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40555" alt="67_ICT_대상_오목AI 베타오(28)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오28.png" width="569" height="291" /></a></p>
[그림 51]와 [그림 52]의 ‘.SLDPRT파일’은 솔리드웍스를 활용한 y축, z축의 설계 부품이며 stl은 3D프린팅을 위한 Gcode 변환하기 전의 파일로 3DWOX Tool을 활용하여 Gcode로 변환하여 DP103 프린터를 이용하여 프린팅이 가능하다.</p>
<p><span style="color: #00ccff"><strong>임베디드 시스템</strong></span><br />
<strong> 아두이노 통신</strong></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오29.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40556" alt="67_ICT_대상_오목AI 베타오(29)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오29.png" width="563" height="285" /></a></p>
[그림 53]과 [그림 54]은 각각 파이썬에서 데이터를 정제해서 시리얼 통신으로 전달받은 데이터를 가지고 스텝 모터, 리니어 모터, 서보 모터를 움직이는 제어 코드, 좌표 데이터 정제 코드이다. 정확한 값을 받기 위해 조건을 여러번 설정하여 원하는 데이터가 아닌 노이즈, 채터링이 들어왔을 경우를 방지한다. 예를 들어 (3, 4)에 해당하는 좌표의 경우 각 좌표앞에 0을 추가 시켜 (03, 04)로 시리얼 통신한다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오30.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40557" alt="67_ICT_대상_오목AI 베타오(30)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오30.png" width="565" height="316" /></a></p>
[그림 55]와 [그림 56]는 각각 파이썬에서 아두이노로의 좌표값 전달과 그 위를 나타낸 것이다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오31.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40558" alt="67_ICT_대상_오목AI 베타오(31)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오31.png" width="567" height="285" /></a></p>
[그림 57]과 같이 아두이노 우노 2개를 파이썬과 시리얼 통신하여 x축, y축의 좌표값을 받아 각각 x축의 값이 정확할 때 LED를 켜봄으로써 정확한 좌표값이 들어갔다는 것을 확인할 수 있었으며 x축, y축의 값이 정확하게 들어간 것을 [그림 58]를 통해 확인할 수 있었다. 이를 통해 파이썬과 아두이노가 정확한 데이터 통신이 가능하다는 것을 볼 수 있다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오32.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40559" alt="67_ICT_대상_오목AI 베타오(32)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오32.png" width="565" height="281" /></a></p>
[그림 59]와 같이 1개의 스위치와 1개의 스텝 모터를 제어함으로써 하나의 모터와 스위치가 정확한 값에서 움직이고 멈춘다는 것을 알 수 있다. 또한 [그림 60]와 x, y축에 해당하는 스텝 모터를 제어할 수 있다. 이 과정을 통해 베타오의 초기 셋팅이 가능함을 알 수 있다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오33.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40560" alt="67_ICT_대상_오목AI 베타오(33)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오33.png" width="567" height="282" /></a></p>
<p>리니어 모터와 서보 모터를 활용하여 z축의 바둑돌 사출기를 만들기 전 테스트를 위해 두 개의 모터만 따로 제어한 것이 [그림 61]이다. 리니어 모터를 통해 z축의 높이를 조절하며 서보 모터를 통해 바둑돌을 하나씩 바둑판으로 사출시킨다.</p>
<p><span style="color: #00ccff"><strong>소프트웨어</strong></span><br />
<strong> 알고리즘 및 코드 진행 과정</strong></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오34.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40561" alt="67_ICT_대상_오목AI 베타오(34)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오34.png" width="575" height="382" /></a></p>
<p>알고리즘 및 코드 진행 과정을 간략화한 순서도이다. 각 과정을 통해 학습된 결과와 그 학습된 결과를 활용한 오목 GUI와 openCV 영상처리를 통한 좌표값이 서로 상호작용하며 아두이노 제어 보드에게 좌표값을 정제하여 전달한다.</p>
<p><strong>파이참 환경 및 외장 함수 import</strong></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오35.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40562" alt="67_ICT_대상_오목AI 베타오(35)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오35.png" width="565" height="305" /></a></p>
<p>numpy, glob, tqdm, os, ursina, tensorflow, pythonopenCV, serial 파이썬 외장 함수를 다운로드한다. 다운로드된 외장 함수를 이용하여 함수를 불러와 필요함수를 사용한다.</p>
<p><strong>데이터 전처리 과정(데이터셋 생성 과정)</strong></p>
<p><strong><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오36.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40563" alt="67_ICT_대상_오목AI 베타오(36)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오36.png" width="579" height="174" /></a></strong><br />
create_dataset.py를 실행하여 gomocup2020results 폴더에 저장 되어 있는 ‘.psq’ 파일의 데이터를 바둑판의 크기와 돌의 위치를 받아온다. 전처리하기 전 데이터를 위와 같은 ‘.psg’ 파일로 저장되어 있는데 Fastgame, Freestyle, Renju, Standard와 같이 게임에 적용되는 룰이 다양하다. 그 중 Freestyle과 같은 게임 룰을 가지고 데이터셋을 만든다. ‘.psq’ 파일 중 학습에 필요한 데이터만을 ‘.npz’파일로 저장한다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오38.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40565" alt="67_ICT_대상_오목AI 베타오(38)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오38.png" width="570" height="280" /></a></p>
[그림 67]와 [그림 68]은 각각 데이터를 전처리하는 코드로 gomocup에서 다운받은 2020년 데이터 ‘.psq’파일을 [그림 68]과 같이 ‘.npz’파일로 저장하는 코드이다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오37.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40564" alt="67_ICT_대상_오목AI 베타오(37)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오37.png" width="584" height="288" /></a></p>
[그림 69]과 [그림 70]는 jupyter notebook을 통해 프로그램되는 코드이다. 파이썬 IDE 파이참에서 전처리된 데이터를 받아와서 CNN 기반의 모델을 만든다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오39.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40566" alt="67_ICT_대상_오목AI 베타오(39)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오39.png" width="573" height="569" /></a></p>
[그림 71] 전처리된 데이터셋을 jupyter 환경에 저장하는 코드이고 [그림 72]은 학습에 사용할 신경망을 구축하는 코드이다. 또한 [그림 73]은 신경망에 대해 학습을 10번 진행한 결과로 정확도가 학습을 진행할수록 높아지는 것을 확인할 수 있다. 1번의 학습에 걸리는 시간은 약 1,900초이며, 학습 데이터의 양이 많아 32GB의 메모리가 필요하다. 10번의 학습이 끝나면 학습 결과를 [그림 74]과 같이 ‘.h5’로 저장한다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오40.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40567" alt="67_ICT_대상_오목AI 베타오(40)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오40.png" width="565" height="284" /></a></p>
<p><span style="color: #00ccff"><strong>openCV(영상 인식)</strong></span><br />
먼저 바둑알 사진을 불러온다. 불러온 사진을 이진화하여 노이즈를 제거한다. 다음으로 실시간 영상을 불러온다. 실시간 영상 또한 이진화 처리하여 노이즈를 최대한 줄인다. 그 후 검은색을 인식하기 위해 픽셀의 값이 임계값(threshold) 이하이면 하얀색으로, 일정값 이상이면 검은색으로 픽셀의 값을 바꾼다. 이 과정을 통해 검은 바둑돌만 검정색으로, 다른 배경은 흰색으로 칠한다. 그 이후 처음 불러왔던 바둑돌과 비교하여 임계값(threshold) 이상의 정확도를 가질 경우 그 좌표를 불러온 사진의 크기만큼 초록색 사각형(컨투어)을 칠한다. 또한 사각형의 중심에 파란색 점을 찍는다. 그 점 값을 전처리를 통해 기존 검은돌의 좌표를 저장한 배열에 존재하지 않을면 값을 추가한다. 또한 새롭게 추가된 좌표값만 출력한다.<br />
[그림 75]과 같이 검은돌을 바둑판에서 인식받아 정해진 임계값에 따라 컨투어를 생성하고 좌표를 생성한다.</p>
<p><strong>좌표값을 인식해서 gui상에서 돌을 둠</strong></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오41.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40568" alt="67_ICT_대상_오목AI 베타오(41)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오41.png" width="565" height="253" /></a></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오42.png" rel="lightbox[40434]"><img class="alignnone size-large wp-image-40569" alt="67_ICT_대상_오목AI 베타오(42)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오42-479x620.png" width="479" height="620" /></a></p>
<p><span style="color: #0000ff"><strong>5. 작품 결과</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오43.png" rel="lightbox[40434]"><img class="alignnone size-full wp-image-40570" alt="67_ICT_대상_오목AI 베타오(43)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오43.png" width="570" height="365" /></a></p>
<p><span style="color: #0000ff"><strong>6. 피드백 및 발전 방향</strong></span><br />
<span style="color: #33cccc"><strong>6.1. 피드백</strong></span><br />
베타오의 특징점은 PC가 사람과 직접 오목 게임을 플레이까지 할 수 있다는 점이다. 하지만 실제 경기에 활용할 수 있도록 하기 위해서 다음과 같은 부분이 해결되어야 한다.</p>
<p><span style="color: #00ccff"><strong>난이도 조절이 필요하다.</strong></span><br />
학습 결과가 높을수록 컴퓨터가 이길 확률이 높기 때문에 정확도를 조절할 수 있는 학습 모델을 따로 생성해야 한다. .h5파일을 활용해 좀 더 낮은 수준의 인공지능을 적용할 수 있도록 수정할 예정이며, 경기에 적용했을 때의 난이도까지 테스트할 것이다.</p>
<p><span style="color: #00ccff"><strong>33 규칙을 적용해야 한다.</strong></span><br />
실제 오목 경기에서는 한 번에 두 줄이 3, 3이 되는 경우는 이겼다고 규정하지 않는다. 승패가 무효가 되는 중요한 규칙이므로 이 점에 대해 보완할 것이다.</p>
<p><span style="color: #00ccff"><strong>흑돌이 유리하다</strong></span><br />
오목은 확률론적으로 흑돌이 무조건적 우위를 선점한다. 현재 흑돌은 사람을 기준으로 선언되어있기 때문에 유저의 기호에 맞춰 턴을 바꿀 수 있도록 사전 UI를 구성하여 선택할 수 있도록 수정할 것이다.</p>
<p><span style="color: #33cccc"><strong>6.2. 발전방향</strong></span><br />
1) 일반인이 직접 활용할 수 있는 딥러닝 교육용 제품으로 사용할 수 있다. 영상처리, 시리얼 통신, 임베디드 시스템, 딥러닝을 결합한 교육용 SW로써 각 분야를 하나의 시스템으로 구성하는 경험을 할 수 있다.<br />
2) 자율주행 및 자동화 공정과 같은 실제 시스템에 응용해서 사용할 수 있다. 영상처리 기반으로 좌표 값을 추출하여 스텝 모터를 기반으로 정밀하게 좌표값 위치로 이동시킨다. 이는 정밀공정과 같은 다양한 시스템에 적용이 가능하다.<br />
3) 오목 경기 연습 AI 및 학습 연구 대상이 될 수 있다. 또한 현재 개발된 모델과 매우 흡사한 바둑과 체스 등 다양한 시스템에 적용할 수 있다. 알파고의 출범 이후, 프로 바둑 기사들은 인공지능의 수와 흡사하게 두는 방법으로 실력을 키우고 있다. 이와 비슷한 맥락으로 본 프로젝트로 개발된 오목 AI도 오목 프로 기사를 양성하는 과정에서 사용될 수 있다.<br />
4) 이벤트성 행사 및 공공일정에서 사용할 수 있다. 베타오는 소프트웨어가 하나의 압축 디렉토리로 송수신이 가능하기 때문에 하드웨어만 준비될 수 있다면 충분히 전시회 및 박람회에서 사용할 수 있는 행사 기구로 발전할 수 있다.</p>
<p><span style="color: #00ccff"><strong>기술적 측면에서의 발전 방향</strong></span><br />
본 프로젝트는 기술적으로 크게 MUC 기반의 임베디드 제어 시스템, 바둑알을 인식하기 위한 영상처리, 오목 알고리즘 구현을 위한 CNN 기반 AI 등의 SW 부분과 리드 스크류, LM가이드를 이용한 오목 로봇의 좌표 이동, 리니어 모터를 이용한 바둑알 사출 시스템 등의 HW 부분으로 구분할 수 있으며 각 분야별 발전 방향은 다음과 같다.</p>
<p><span style="color: #00ccff"><strong>소프트웨어</strong></span><br />
<strong> 영상 인식</strong><br />
고사양 GPU를 사용해 처리 속도를 향상시킨다. 이는 영상 인식과 모델 생성 속도에도 영향을 주며 GPU의 성능에 따라 증가한다. 또한 사용하고 있는 openCV 정적라이브러리(static library)를 동적 라이브러리의 사용으로 변환하면 최소한의 프로그램 사용으로 같은 수준의 영상 인식을 사용할 수 있어 간단해진다.</p>
<p><strong>오목 AI</strong><br />
생성된 모델을 모델로 학습하는 GAN(Generative Adversarial Network)을 사용한다. GAN을 사용하게 되면 생성 모델의 성능을 압도적으로 향상시킬 수 있다.</p>
<p><span style="color: #00ccff"><strong>임베디드 시스템</strong></span><br />
컴퓨터와 메인 제어 보드를 함께 사용할 수 있도록 아두이노 우노를 라즈베리파이로 변경한다. 라즈베리파이 환경에 SW와 임베디드 시스템을 합쳐 베타오의 크기를 줄일 수 있다.</p>
<p><span style="color: #00ccff"><strong>하드웨어</strong></span><br />
교육기관 도입 및 대량생산을 목표로 할 시, 하드웨어의 장치적 단순화 및 경량화가 가능해진다면 좀 더 유동적이고 용이한 작품으로 발전할 수 있다.</p>
<p><span style="color: #3366ff"><strong>7. 사용한 제품 리스트</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오44.png" rel="lightbox[40434]"><img class="alignnone size-large wp-image-40571" alt="67_ICT_대상_오목AI 베타오(44)" src="http://www.ntrexgo.com/wp-content/uploads/2021/09/67_ICT_대상_오목AI-베타오44-620x426.png" width="620" height="426" /></a></p>
<p><span style="color: #3366ff"><strong>8. 참고문헌</strong></span><br />
· 미니츄어 LM가이드 표준 블록/경예압.&#8221;미니츄어 모델의 정확한 치수 &#8220;.https://kr.misumi-ec.com/vona2/detail/110302586530/?KWSearch=lm%ea%b0%80%ec%9d%b4%eb%93%9c&amp;searchFlow=results2products.(2021.03.13)<br />
· 라즈이노 iOT.&#8221;[ 아두이노 기초 ] #31 스텝(Step Motor)모터의 이해.&#8221;스텝모터의 원리&#8221;.https://rasino.tistory.com/149.(2021.03.17)<br />
· a4988 스텝 모터 드라이버 알아보기 ( 스테퍼 / 스테핑 / 모터 / 드라이버 / 초퍼 모터드라이버 / 설정 ).&#8221;스텝모터의 정격전압과 회로 구성 방법&#8221;.https://m.blog.naver.com/PostView.nhn?blogId=roboholic84&amp;logNo=221142584042&amp;proxyReferer=https:%2F%2Fwww.google.com%2F.(2021.03.18)<br />
· 아두이노로 NEMA17 스텝모터 제어하기, 스텝수 계산 (A4988 스텝모터드라이버).&#8221;스텝모터의 분주 제어방법&#8221;.https://blog.naver.com/chrkwlq1208/221562820510.(2021.03.25)<br />
· 오목을 두는 인공지능을 만들어 겨뤄봤습니다.오목인공지능 구현 방법.https://www.youtube.com/watch?v=xigPAOl3v7I&amp;ab_channel=%EB%B9%B5%ED%98%95%EC%9D%98%EA%B0%9C%EB%B0%9C%EB%8F%84%EC%83%81%EA%B5%AD(21.03.10)<br />
· GOMOCUP.&#8221;전처리데이터 다운로드&#8221;.https://gomocup.org/.(21.03.12)<br />
· [아두이노 -&gt; 파이썬]시리얼 통신으로 문자열 보내는 방법].&#8221;파이썬에서 아두이노로 시리얼 통신하는 방법&#8221;.https://doomed-lab.tistory.com/4.(21.03.20)<br />
· 정성환, 배종욱(2005).OpenCV-Python으로 배우는 영상 처리 및 응용.생능출판사</p>
<p><span style="color: #3366ff"><strong>9. 소스코드</strong></span><br />
<span style="color: #00ccff"><strong>아두이노 하드웨어 X축 방향 구동 코드</strong></span></p>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p>/* x축 stepping motor 구동 code */<br />
String sig;<br />
char Ard1[3] = {0}; // 통신 받은 문자열 분할 처리를 위한 변수 선언<br />
char Ard2[3] = {0}; // 통신 받은 문자열 분할 처리를 위한 변수 선언<br />
char check[1];<br />
int value_1 = 0; // x축 좌표값 받을 변수 선언<br />
int value_2 = 0; // y축 좌표값 받을 변수 선언<br />
int flag = 0; // 통신 데이터의 무한한 공급을 막기 위한 변수 선언</p>
<p>/* stepping motor pin설정 */<br />
#define steps_x 5<br />
#define dir_x 6<br />
#define ms1_x 8<br />
#define ms2_x 9<br />
#define ms3_x 10<br />
/* switch pin설정 */<br />
#define sw_x 4</p>
<p>long long int i = 0; // for문에 사용할 변수<br />
//long long int i_num = 5000+4000*value_1;<br />
long long int delay_num = (6000+4000*value_2)*0.4;<br />
int sn = 0; // stepping motor가 회전하는 바퀴수를 count하기 위한 변수</p>
<p>void setup()<br />
{<br />
// put your setup code here, to run once:<br />
Serial.begin(9600);<br />
// 신호보낼 핀 출력설정<br />
pinMode(steps_x, OUTPUT); // stepping motor에 신호를 주기 위함<br />
pinMode(dir_x, OUTPUT); // stepping motor 방향설정을 출력<br />
pinMode(ms1_x, OUTPUT); // stepping motor 분주 설정 출력<br />
pinMode(ms2_x, OUTPUT); // stepping motor 분주 설정 출력<br />
pinMode(ms3_x, OUTPUT); // stepping motor 분주 설정 출력<br />
/* 분주를 1/2로 설정했으므로 1 step당 0.9도씩 회전 */<br />
digitalWrite(steps_x,LOW);<br />
digitalWrite(dir_x, HIGH); // 초기 설정 : stepping motor 정방향 회전<br />
digitalWrite(ms1_x, HIGH); // stepping motor 1/2분주 설정<br />
digitalWrite(ms2_x, LOW); // 분주 비활성화<br />
digitalWrite(ms3_x, LOW); // 분주 비활성화</p>
<p>pinMode(sw_x, INPUT_PULLUP); // pull_up저항으로 출력 설정</p>
<p>/* x축의 stepping motor회전으로 초기 좌표까지 이동 */<br />
digitalWrite(dir_x, HIGH); // stepping motor 정회전<br />
while(1)<br />
{<br />
if(digitalRead(sw_x) == HIGH)<br />
{<br />
goto kn_s; // switch인식할 경우 kn_s:로 이동 및 stepping motor 정지<br />
}<br />
/* stepping motor 1 step당 4us로 회전 */<br />
digitalWrite(steps_x, HIGH);<br />
delayMicroseconds(200); // delay 2us<br />
digitalWrite(steps_x, LOW);<br />
delayMicroseconds(200); // delay 2us<br />
}<br />
kn_s:<br />
delay(200);<br />
}</p>
<p>void loop() {<br />
// put your main code here, to run repeatedly:<br />
digitalWrite(dir_x, HIGH); // stepping motor 정회전<br />
/* x축의 stepping motor회전으로 초기 좌표까지 이동 */<br />
while(1)<br />
{<br />
//x좌표 switch가 인식될 경우에 초기 좌표로 인식<br />
if(digitalRead(sw_x) == HIGH)<br />
{<br />
goto kn_s; // switch인식할 경우 kn_s:로 이동 및 stepping motor 정지<br />
}<br />
/* stepping motor 1 step당 4us로 회전 */<br />
digitalWrite(steps_x, HIGH);<br />
delayMicroseconds(200); // delay 2us<br />
digitalWrite(steps_x, LOW);<br />
delayMicroseconds(200); // delay 2us<br />
}<br />
kn_s:<br />
delay(200);<br />
kn_x:<br />
/* 문자열로 저장*/<br />
while(Serial.available())<br />
{<br />
char wait = Serial.read(); // python serial 통신으로 받을 문자열<br />
sig.concat(wait); // python으로 통신받은 문자열을 sig라는 빈 문자열에 추가<br />
}<br />
/* 입력 문자열 슬라이싱 */<br />
sig.substring(0,1).toCharArray(check,2);</p>
<p>if(check[0] == &#8216;Q&#8217;)<br />
{<br />
if (sig.length()==5)<br />
{<br />
sig.substring(1,3).toCharArray(Ard1,3); // x축에 해당하는 좌표 문자열<br />
sig.substring(3,5).toCharArray(Ard2,3); // y축에 해당하는 좌표 문자열<br />
value_1 = atoi(Ard1); // x축 좌표값<br />
value_2 = atoi(Ard2); // y축 좌표값<br />
sig = &#8220;&#8221;; // x,y 축의 좌표데이터를 연산처리 할 수 있게 int형으로 처리한 후 문자열을 다시 받기 위한 초기화<br />
flag = 0;<br />
}<br />
else if(sig.length()&gt;5)<br />
{sig = &#8220;&#8221;;} // 문자열을 다시 받기 위한 초기화<br />
}<br />
else if(check[0] != &#8216;Q&#8217;)<br />
{<br />
sig = &#8220;&#8221;; // 문자열을 다시 받기 위한 초기화<br />
}<br />
if(value_1 &gt; 0) // x축 좌표가 0이상일 때 동작하기 위한 조건문<br />
{<br />
if(flag == 0)<br />
{<br />
flag = 1; // 통신 데이터의 무한 공급으로 인한 무한루프의 시스템 처리를 막기 위해 초기화<br />
digitalWrite(dir_x, LOW);<br />
delay(500);<br />
/* x축 초기 좌표 위치 설정 */<br />
for(i=0; i&lt;6500; i++)<br />
{<br />
/* stepping motor 1 step당 4us로 회전 */<br />
digitalWrite(steps_x, HIGH);<br />
delayMicroseconds(200); // delay 2us<br />
digitalWrite(steps_x, LOW);<br />
delayMicroseconds(200); // delay 2us<br />
}<br />
sn = 0; // count 변수 초기화<br />
/* y축 좌표값까지 한 바퀴씩 회전하는 값을 카운트하여 조건을 만족하면 회전 정지 및 while문 탈출 */<br />
while(1)<br />
{<br />
if(sn == value_1)<br />
{<br />
goto ksn; // count변수가 x축 좌표값이랑 같아지면 stepping motor 회전 중지<br />
}<br />
for(i=0; i&lt;4000; i++)<br />
{<br />
/* stepping motor 1 step당 4us로 회전 */<br />
digitalWrite(steps_x, HIGH);<br />
delayMicroseconds(200); // delay 2us<br />
digitalWrite(steps_x, LOW);<br />
delayMicroseconds(200); // delay 2us<br />
}<br />
sn++; // count 1씩 증가<br />
}<br />
ksn:<br />
delay(delay_num); // y축 stepping motor 회전 시간<br />
delay(13000); // servo &amp; linear 작동 시간동안 delay<br />
//회전방향 출력 10000+2000*<br />
digitalWrite(dir_x, HIGH);<br />
delay(500);<br />
for(i=0; i&lt;6500; i++)<br />
{<br />
/* stepping motor 1 step당 4us로 회전 */<br />
digitalWrite(steps_x, HIGH);<br />
delayMicroseconds(200); // delay 2us<br />
digitalWrite(steps_x, LOW);<br />
delayMicroseconds(200); // delay 2us<br />
}<br />
while(1)<br />
{<br />
for(i=0; i&lt;4000; i++)<br />
{<br />
if(digitalRead(sw_x) == HIGH)<br />
{<br />
goto kn_x;<br />
}<br />
/* stepping motor 1 step당 4us로 회전 */<br />
digitalWrite(steps_x, HIGH);<br />
delayMicroseconds(200); // delay 2us<br />
digitalWrite(steps_x, LOW);<br />
delayMicroseconds(200); // delay 2us<br />
}<br />
}<br />
}<br />
}<br />
}<br />
</div>
<span style="color: #00ccff"><strong>아두이노 하드웨어 Y축 방향 구동 코드</strong></span></p>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p>/* y축 stepping motor &amp; servo motor &amp; linear motor 구동 code */<br />
#include &lt;Servo.h&gt;<br />
Servo myservo_servo; // servo motor사용을 위한 모터이름 지정<br />
Servo myservo_linear; // linear motor사용을 위한 모터이름 지정</p>
<p>String sig; // 통신받을 문자열을 추가해주기 위한 자리 변수 선언<br />
char Ard1[3] = {0}; // 통신 받은 문자열 분할 처리를 위한 변수 선언<br />
char Ard2[3] = {0}; // 통신 받은 문자열 분할 처리를 위한 변수 선언<br />
char check[1];<br />
int value_1 = 0; // x축 좌표값 받을 변수 선언<br />
int value_2 = 0; // y축 좌표값 받을 변수 선언<br />
int flag = 0; // 통신 데이터의 무한한 공급을 막기 위한 변수 선언</p>
<p>/* stepping motor pin설정 */<br />
#define steps_y 5<br />
#define dir_y 6<br />
#define ms1_y 8<br />
#define ms2_y 9<br />
#define ms3_y 10<br />
/* switch pin설정 */<br />
#define sw_y 4<br />
long long int j = 0; // for문에 사용할 변수<br />
int angle_servo; // servo motor 각도변수<br />
int angle_linear; // linear motor 출력변수<br />
long long int delay_num = (6500+4000*value_1)*0.4; //x축의 stepping motor회전하는 동안 동작지연하기 위한 delay<br />
int sn = 0; // stepping motor가 회전하는 바퀴수를 count하기 위한 변수</p>
<p>void setup() {<br />
// put your setup code here, to run once:<br />
Serial.begin(9600);<br />
// 신호보낼 핀 출력설정<br />
pinMode(steps_y, OUTPUT); // stepping motor에 신호를 주기 위함<br />
pinMode(dir_y, OUTPUT); // stepping motor 방향설정을 출력<br />
pinMode(ms1_y, OUTPUT); // stepping motor 분주 설정 출력<br />
pinMode(ms2_y, OUTPUT); // stepping motor 분주 설정 출력<br />
pinMode(ms3_y, OUTPUT); // stepping motor 분주 설정 출력<br />
/* 분주를 1/2로 설정했으므로 1 step당 0.9도씩 회전 */<br />
digitalWrite(steps_y,LOW);<br />
digitalWrite(dir_y, HIGH); // 초기 설정 : stepping motor 정방향 회전<br />
digitalWrite(ms1_y, HIGH); // stepping motor 1/2분주 설정<br />
digitalWrite(ms2_y, LOW); // 분주 비활성화<br />
digitalWrite(ms3_y, LOW); // 분주 비활성화</p>
<p>myservo_servo.attach(11); // servo motor PIN설정<br />
myservo_servo.write(0); // servo motor 초기 각도 0도로 설정<br />
myservo_linear.attach(12); // linear motor PIN설정<br />
myservo_linear.write(0); // linear motor 초기 각도 0도로 설정</p>
<p>pinMode(sw_y, INPUT_PULLUP); // pull_up저항으로 출력 설정</p>
<p>/* y축의 stepping motor회전으로 초기 좌표까지 이동 */<br />
digitalWrite(dir_y, HIGH); // stepping motor 정방향 회전<br />
while(1)<br />
{<br />
if(digitalRead(sw_y) == HIGH)<br />
{<br />
goto kn_s; // switch인식할 경우 kn_s:로 이동 및 stepping motor 정지<br />
}<br />
/* stepping motor 1 step당 4us로 회전 */<br />
digitalWrite(steps_y, HIGH);<br />
delayMicroseconds(200); // delay 2us<br />
digitalWrite(steps_y, LOW);<br />
delayMicroseconds(200); // delay 2us<br />
}<br />
kn_s:<br />
delay(200);<br />
}</p>
<p>void loop()<br />
{<br />
// put your main code here, to run repeatedly:<br />
digitalWrite(dir_y, HIGH); // stepping motor 정회전<br />
/* y축의 stepping motor회전으로 초기 좌표까지 이동 */<br />
while(1)<br />
{<br />
if(digitalRead(sw_y) == HIGH)<br />
{<br />
goto kn_s;<br />
}<br />
/* stepping motor 1 step당 4us로 회전 */<br />
digitalWrite(steps_y, HIGH);<br />
delayMicroseconds(200); // delay 2us<br />
digitalWrite(steps_y, LOW);<br />
delayMicroseconds(200); // delay 2us<br />
}<br />
kn_s:<br />
delay(200);<br />
kn_y:<br />
/* 문자열로 저장*/<br />
while(Serial.available())<br />
{<br />
char wait = Serial.read(); // python serial 통신으로 받을 문자열<br />
sig.concat(wait); // python으로 통신받은 문자열을 sig라는 빈 문자열에 추가<br />
}<br />
/* 입력 문자열 슬라이싱 */<br />
sig.substring(0,1).toCharArray(check,2);</p>
<p>if(check[0] == &#8216;Q&#8217;)<br />
{<br />
if (sig.length()==5)<br />
{<br />
sig.substring(1,3).toCharArray(Ard1,3); // x축에 해당하는 좌표 문자열<br />
sig.substring(3,5).toCharArray(Ard2,3); // y축에 해당하는 좌표 문자열<br />
value_1 = atoi(Ard1); // x축 좌표값<br />
value_2 = atoi(Ard2); // y축 좌표값<br />
sig = &#8220;&#8221;; // x,y 축의 좌표데이터를 연산처리 할 수 있게 int형으로 처리한 후 문자열을 다시 받기 위한 초기화<br />
flag = 0;<br />
}<br />
else if(sig.length()&gt;5)<br />
{sig = &#8220;&#8221;;} // 문자열을 다시 받기 위한 초기화<br />
}<br />
else if(check[0] != &#8216;Q&#8217;)<br />
{<br />
sig = &#8220;&#8221;; // 문자열을 다시 받기 위한 초기화<br />
}<br />
if(value_2 &gt; 0) // y축 좌표가 0이상일 때 동작하기 위한 조건문<br />
{<br />
if(flag == 0)<br />
{<br />
flag = 1; // 통신 데이터의 무한 공급으로 인한 무한루프의 시스템 처리를 막기 위해 초기화<br />
digitalWrite(dir_y, LOW);<br />
delay(500);<br />
delay(delay_num);<br />
/* y축 초기 좌표 위치 설정 */<br />
for(j=0; j&lt;6000; j++)<br />
{<br />
/* stepping motor 1 step당 4us로 회전 */<br />
digitalWrite(steps_y, HIGH);<br />
delayMicroseconds(200); // delay 2us<br />
digitalWrite(steps_y, LOW);<br />
delayMicroseconds(200); // delay 2us<br />
}<br />
sn = 0; // count 변수 초기화<br />
/* y축 좌표값까지 한 바퀴씩 회전하는 값을 카운트하여 조건을 만족하면 회전 정지 및 while문 탈출 */<br />
while(1)<br />
{<br />
if(sn == value_2)<br />
{<br />
goto ksn; // count변수가 y축 좌표값이랑 같아지면 stepping motor 회전 중지<br />
}<br />
for(j=0; j&lt;4000; j++)<br />
{<br />
/* stepping motor 1 step당 4us로 회전 */<br />
digitalWrite(steps_y, HIGH);<br />
delayMicroseconds(200); // delay 2us<br />
digitalWrite(steps_y, LOW);<br />
delayMicroseconds(200); // delay 2us<br />
}<br />
sn++; // count 1씩 증가<br />
}<br />
ksn:<br />
delay(500);<br />
/* 바둑돌이 정확한 위치로 낙하하기 위해 linear motor로 조준해주기 */<br />
for(angle_linear = 0; angle_linear &lt; 140; angle_linear++)<br />
{<br />
myservo_linear.write(angle_linear);<br />
delay(20); // delay 10ms<br />
}<br />
/* 바둑돌 내려주기 위한 servo motor제어 0~180~0도 순으로 회전 */<br />
delay(500);<br />
for(angle_servo = 0; angle_servo &lt;= 180; angle_servo++)<br />
{<br />
myservo_servo.write(angle_servo);<br />
delay(10); // delay 10ms<br />
}<br />
delay(500);<br />
for(angle_servo = 180; angle_servo &gt;= 0; angle_servo&#8211;)<br />
{<br />
myservo_servo.write(angle_servo);<br />
delay(10); // delay 10ms<br />
}<br />
delay(500);<br />
/* 조준된 linear motor가 축이 움직일 때 다른 바둑돌을 건들지 않게 초기위치로 되돌림 */<br />
for(angle_linear = 140; angle_linear &gt; 0; angle_linear&#8211;)<br />
{<br />
myservo_linear.write(angle_linear);<br />
delay(20); // delay 20ms<br />
}<br />
delay(delay_num);<br />
digitalWrite(dir_y, HIGH); // stepping motor 정회전<br />
delay(500);<br />
for(j=0; j&lt;6000; j++)<br />
{<br />
/* stepping motor 1 step당 4us로 회전 */<br />
digitalWrite(steps_y, HIGH);<br />
delayMicroseconds(200); // delay 2us<br />
digitalWrite(steps_y, LOW);<br />
delayMicroseconds(200); // delay 2us<br />
}<br />
while(1)<br />
{<br />
for(j=0; j&lt;4000; j++)<br />
{<br />
if(digitalRead(sw_y) == HIGH)<br />
{<br />
goto kn_y; // y축의 switch가 눌리면 while문을 탈출하면서 stepping motor의 작동을 멈춤<br />
}<br />
/* stepping motor 1 step당 4us로 회전 */<br />
digitalWrite(steps_y, HIGH);<br />
delayMicroseconds(200); // delay 2us<br />
digitalWrite(steps_y, LOW);<br />
delayMicroseconds(200); // delay 2us<br />
}<br />
}<br />
}</p>
<p>}<br />
}<br />
</div>
<span style="color: #00ccff"><strong>OpenCV 카메라 바둑돌 인식용 코드</strong></span></p>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p>import numpy as np, cv2<br />
import time<br />
image = cv2.imread(&#8216;images/go2.jpg&#8217;, cv2.IMREAD_COLOR)<br />
capture = cv2.VideoCapture(0) #카메라 불러오는 함수<br />
if capture.isOpened() == False: raise Exception(&#8220;카메라 연결 안됨&#8221;)<br />
stone = []
final_stone = []
while(capture.isOpened()): # 카메라가 열려있을 때 ~<br />
ret, image = capture.read() # image로 받아옴</p>
<p>mark = np.zeros(image.shape, image.dtype) # mark 라는 친구를 생성함<br />
mark_1 = np.zeros(image.shape, image.dtype) # mark_1이라는 친구도 생성함함<br />
blue_threshold = 70 # 흑백으로 바꾸는 기준 1<br />
green_threshold = 70 # 흑백으로 바꾸는 기준 2<br />
red_threshold = 70 # 흑백으로 바꾸는 기준 3<br />
bgr_threshold = [blue_threshold, green_threshold, red_threshold] # 3차월 배열로 바꿔줌</p>
<p>for i in range(image.shape[0]):<br />
for j in range(image.shape[1]):<br />
if (image[i, j, 0] &gt;= blue_threshold) and (image[i, j, 1] &gt;= green_threshold) and image[i, j, 2] &gt;= red_threshold:<br />
mark_1[i, j ] = 255,255,255 # 검정<br />
else :<br />
mark_1[i, j ] = 0,0,0 # 하얀색</p>
<p>ju = cv2.imread(&#8216;images/go2.jpg&#8217;, cv2.COLOR_BGR2GRAY) # 검출할 사진 불러옴<br />
white_s = cv2.cvtColor(ju, cv2.COLOR_BGR2GRAY) # 불러온 친구의 색 공간을 바꿈<br />
mark = cv2.cvtColor(mark_1 , cv2.COLOR_BGR2GRAY) # 위와 동문<br />
template = white_s # template = 비교할 것<br />
w, h =template.shape[:: -1] # 불러온 사진의 너비와 높이</p>
<p>res = cv2.matchTemplate(mark, template, cv2.TM_CCOEFF_NORMED) # 함수를 사용해서 비교함<br />
threshold = 0.6 # 이 값보다 높을 때<br />
loc = np.where(res &gt; threshold) # 위치를 리턴함<br />
for pt in zip(*loc[::-1]): # 위치의 가로 세로<br />
if (image[pt[1], pt[0],2] != 255):<br />
cv2.rectangle(image, pt , (pt[0]+w, pt[1]+h), (0,255,0),2) # 초록색으로 칠함<br />
cv2.circle(image, (int(pt[0]+w*0.5), int(pt[1]+h*0.5)),1, (255,0,0)) # 중심에 파란 원<br />
s_x = int(pt[0] + w * 0.5) # 중심 x<br />
s_y = int(pt[1] + h * 0.5) # 중심 y<br />
s_xy = str(s_x) + str(s_y) # str로 합침<br />
s_xy = int(s_xy) # 다시 숫자로 바꿈<br />
s_xy_dt = []
stone_dt = []
for i in range(0, 10): # 범위 내 값을 걸러줌<br />
for j in range(0, 10):<br />
s_xy_dt.append(s_xy &#8211; i*1000 &#8211; j)<br />
s_xy_dt.append(s_xy &#8211; i*1000 + j)<br />
s_xy_dt.append(s_xy + i*1000 &#8211; j)<br />
s_xy_dt.append(s_xy + i*1000 + j)<br />
xx = 0<br />
for k in range(len(s_xy_dt)): ## 값이 같지 않으면 stack<br />
if stone != s_xy_dt[k]:<br />
xx += 1<br />
yy = 0<br />
if xx == len(s_xy_dt) : # stack 값이 다 같을 때 = 오차의 범위 값이 list에 없을 때<br />
if s_xy &lt; 99999 : # y축이 두자리 프레임일 때<br />
for k in range(len(stone)):<br />
if abs(stone[k] &#8211; s_xy) &lt; 110: # 100의 자리 = x축 프레임 차이, 1의 자리 = y축 프레임<br />
yy += 1<br />
if yy == 0 :<br />
stone.append(s_xy)</p>
<p>else : # x ,y 둘다 3자리 프레임일 때<br />
for k in range(len(stone)):<br />
if abs(stone[k] &#8211; s_xy) &lt; 1010: # 1000의 자리 = x축, 1의자리 = y축의 차이<br />
yy += 1<br />
if yy == 0 :<br />
stone.append(s_xy)</p>
<p>aa = len(final_stone)<br />
for i in range(len(stone)):<br />
stone_xx_1 = 0<br />
stone_yy_1 = 0<br />
if stone[i] &lt; 99999 :<br />
stone_x = stone[i] / 100 # 앞자리 3개<br />
stone_y = stone[i] % 100 # 뒷자리 2개</p>
<p>stone_y = abs(stone_y) / 20 # 경험적 치수<br />
stone_x = (abs((stone_x &#8211; 160) / 20) ) # 경험적 치수<br />
else :<br />
stone_x = stone[i] / 1000 # 앞자리 3개<br />
stone_y = stone[i] % 1000 # 뒷자리 3개</p>
<p>stone_x = (abs((stone_x &#8211; 160) / 20)) # 경험적 치수<br />
stone_y = (stone_y / 20 ) # 경험적 치수<br />
ab = int(stone_x) *100 + int(stone_y)<br />
if ab not in final_stone: # list에 없으면<br />
final_stone.append(ab)</p>
<p>if aa != len(final_stone) : # 전의 길이와 다르면 = 새롭게 추가되면<br />
print( int(final_stone[-1] % 100), int(final_stone[-1]/100))<br />
print(&#8216;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8217;)<br />
cv2.imshow(&#8216;mm&#8217;, image) # 이미지 출력<br />
cv2.imshow(&#8216;aa&#8217;, ju) # 비교 이미지 출력<br />
cv2.waitKey(150) # delay<br />
5neck.py<br />
import numpy as np, cv2<br />
import time<br />
image = cv2.imread(&#8216;images/go2.jpg&#8217;, cv2.IMREAD_COLOR)<br />
capture = cv2.VideoCapture(0) #카메라 불러오는 함수<br />
if capture.isOpened() == False: raise Exception(&#8220;카메라 연결 안됨&#8221;)<br />
stone = []
final_stone = []
while(capture.isOpened()): # 카메라가 열려있을 때 ~<br />
ret, image = capture.read() # image로 받아옴</p>
<p>mark = np.zeros(image.shape, image.dtype) # mark 라는 친구를 생성함<br />
mark_1 = np.zeros(image.shape, image.dtype) # mark_1이라는 친구도 생성함함<br />
blue_threshold = 70 # 흑백으로 바꾸는 기준 1<br />
green_threshold = 70 # 흑백으로 바꾸는 기준 2<br />
red_threshold = 70 # 흑백으로 바꾸는 기준 3<br />
bgr_threshold = [blue_threshold, green_threshold, red_threshold] # 3차월 배열로 바꿔줌</p>
<p>for i in range(image.shape[0]):<br />
for j in range(image.shape[1]):<br />
if (image[i, j, 0] &gt;= blue_threshold) and (image[i, j, 1] &gt;= green_threshold) and image[i, j, 2] &gt;= red_threshold:<br />
mark_1[i, j ] = 255,255,255<br />
else :<br />
mark_1[i, j ] = 0,0,0</p>
<p>#print(mark.shape)</p>
<p>ju = cv2.imread(&#8216;images/go2.jpg&#8217;, cv2.COLOR_BGR2GRAY) # 검출할 사진 불러옴<br />
white_s = cv2.cvtColor(ju, cv2.COLOR_BGR2GRAY) # 불러온 친구의 색 공간을 바꿈<br />
mark = cv2.cvtColor(mark_1 , cv2.COLOR_BGR2GRAY) # 위와 동문<br />
template = white_s # template = 비교할 것<br />
w, h =template.shape[:: -1] # 불러온 사진의 너비와 높이<br />
#stone = []
res = cv2.matchTemplate(mark, template, cv2.TM_CCOEFF_NORMED) # 함수를 사용해서 비교함<br />
threshold = 0.6 # 이 값보다 높을 때<br />
loc = np.where(res &gt; threshold) # 위치를 리턴함<br />
for pt in zip(*loc[::-1]): # 위치의 가로 세로<br />
if (image[pt[1], pt[0],2] != 255):<br />
cv2.rectangle(image, pt , (pt[0]+w, pt[1]+h), (0,255,0),2) # 초록색으로 칠함<br />
cv2.circle(image, (int(pt[0]+w*0.5), int(pt[1]+h*0.5)),1, (255,0,0))<br />
#print(int(pt[0]+w*0.5), int(pt[1]+h*0.5))<br />
s_x = int(pt[0] + w * 0.5)<br />
s_y = int(pt[1] + h * 0.5)<br />
s_xy = str(s_x) + str(s_y)<br />
s_xy = int(s_xy)<br />
s_xy_dt = []
stone_dt = []
for i in range(0, 10):<br />
for j in range(0, 10):<br />
s_xy_dt.append(s_xy &#8211; i*1000 &#8211; j)<br />
s_xy_dt.append(s_xy &#8211; i*1000 + j)<br />
s_xy_dt.append(s_xy + i*1000 &#8211; j)<br />
s_xy_dt.append(s_xy + i*1000 + j)<br />
xx = 0<br />
for k in range(len(s_xy_dt)):<br />
if stone != s_xy_dt[k]:<br />
xx += 1<br />
yy = 0<br />
if xx == len(s_xy_dt) :<br />
if s_xy &lt; 99999 :<br />
for k in range(len(stone)):<br />
if abs(stone[k] &#8211; s_xy) &lt; 110:<br />
yy += 1<br />
if yy == 0 :<br />
stone.append(s_xy)<br />
else :<br />
for k in range(len(stone)):<br />
if abs(stone[k] &#8211; s_xy) &lt; 1010:<br />
yy += 1<br />
if yy == 0 :<br />
stone.append(s_xy)<br />
#print(stone)<br />
aa = len(final_stone)<br />
for i in range(len(stone)):<br />
stone_xx_1 = 0<br />
stone_yy_1 = 0<br />
if stone[i] &lt; 99999 :<br />
stone_x = stone[i] / 100<br />
stone_y = stone[i] % 100<br />
#print(stone_x)<br />
#print(stone_y)<br />
stone_y = abs(stone_y) / 20<br />
stone_x = (abs((stone_x &#8211; 160) / 20) )<br />
else :<br />
stone_x = stone[i] / 1000<br />
stone_y = stone[i] % 1000<br />
#print(stone_x)<br />
#print(stone_y)<br />
stone_x = (abs((stone_x &#8211; 160) / 20))</p>
<p>stone_y = (stone_y / 20 )<br />
ab = int(stone_x) *100 + int(stone_y)<br />
if ab not in final_stone:<br />
final_stone.append(ab)</p>
<p>#for k in range(len(final_stone)):<br />
# print(int(final_stone[k] / 100) , int(final_stone[k] % 100))<br />
if aa != len(final_stone) :<br />
print( int(final_stone[-1] % 100), int(final_stone[-1]/100))<br />
print(&#8216;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8217;)<br />
cv2.imshow(&#8216;mm&#8217;, image)<br />
cv2.imshow(&#8216;aa&#8217;, ju)<br />
cv2.waitKey(150)<br />
</div>
<p>&nbsp;</p>
<p><span style="color: #00ccff"><strong>데이터 전처리 코드</strong></span></p>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p>import numpy as np<br />
from glob import glob<br />
from tqdm import tqdm # for 문의 상태바를 나태내주는 라이브러리<br />
import os</p>
<p>&#8221;&#8217;<br />
Dataset from https://gomocup.org/results/<br />
데이터셋을 얻는 프로그램<br />
# 데이터 전처리 과정<br />
game_rule = &#8216;Freestyle&#8217; # 프리스타일(3&#215;3가능 5개 이상만 돌을 연속으로 두면 승리)<br />
base_path = &#8216;/omok_AI/gomocup2020results&#8217; # 전처리전 데이터가 저장되어 있는 경로</p>
<p>output_path = os.path.join(&#8216;dataset&#8217;, os.path.basename(base_path)) # dataset\gomocup2019results\<br />
os.makedirs(output_path, exist_ok=True) # 디렉토리 생성</p>
<p># 경로에 있는 모든 psq파일을 file_list에 저장<br />
file_list = glob(os.path.join(base_path, &#8216;%s*/*.psq&#8217; % (game_rule, )))</p>
<p>for index, file_path in enumerate(tqdm(file_list)):<br />
with open(file_path, &#8216;r&#8217;) as f:<br />
lines = f.read().splitlines()</p>
<p>w, h = lines[0].split(&#8216; &#8216;)[1].strip(&#8216;,&#8217;).split(&#8216;x&#8217;) # 가로 X 세로의 크기 추출<br />
w, h = int(w), int(h) # 문자에서 인트형으로 형 변환</p>
<p>lines = lines[1:] # 첫번째 줄을 제외한 나머지 줄</p>
<p>inputs, outputs = [], []
board = np.zeros([h, w], dtype=np.int8)</p>
<p>for i, line in enumerate(lines):<br />
if &#8216;,&#8217; not in line:<br />
break</p>
<p>x, y, t = np.array(line.split(&#8216;,&#8217;), np.int8)<br />
if i % 2 == 0: # 오목의 순서제<br />
player = 1<br />
else:<br />
player = 2</p>
<p>input = board.copy().astype(np.int8)<br />
input[(input != player) &amp; (input != 0)] = -1<br />
input[(input == player) &amp; (input != 0)] = 1</p>
<p>output = np.zeros([h, w], dtype=np.int8)<br />
output[y-1, x-1] = 1 # h w 순서</p>
<p># augmentation<br />
# rotate 4 x flip 3 = 12<br />
# 데이터셋을 늘림<br />
for k in range(4):<br />
input_rot = np.rot90(input, k=k)<br />
output_rot = np.rot90(output, k=k)<br />
inputs.append(input_rot)<br />
outputs.append(output_rot)<br />
inputs.append(np.fliplr(input_rot))<br />
outputs.append(np.fliplr(output_rot))<br />
inputs.append(np.flipud(input_rot))<br />
outputs.append(np.flipud(output_rot))<br />
# update board<br />
board[y-1, x-1] = player<br />
# dataset 저장<br />
np.savez_compressed(os.path.join(output_path, &#8216;%s.npz&#8217; % (str(index).zfill(5))), inputs=inputs, outputs=outputs)<br />
</div>
<p><span style="color: #00ccff"><strong>Execute_GUI.py</strong></span></p>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p>from ursina import *<br />
import numpy as np<br />
from tensorflow.keras.models import load_model<br />
from omok import Board, Omok<br />
import serial<br />
import time<br />
# from serial_arduino import coordinate_str</p>
<p>PORT = &#8216;COM13&#8242;<br />
BaudRate = 9600<br />
sendSerial = serial.Serial(&#8216;COM13&#8242;, BaudRate) # x축<br />
ardSerial = serial.Serial(&#8216;COM11&#8242;, 9600) # y축</p>
<p># sendSerial = serial.Serial(&#8216;COM14&#8242;, BaudRate)</p>
<p># 오목 6개 만들시에도 게임을 승리하는 부분 보완<br />
model = load_model(&#8216;models/20210307_232530.h5&#8242;)</p>
<p>app = Ursina()<br />
window.borderless = False<br />
window.color = color._50</p>
<p>w, h = 20, 20<br />
camera.orthographic = True<br />
camera.fov = 23<br />
camera.position = (w//2, h//2)</p>
<p>board = Board(w=w, h=h)<br />
board_buttons = [[None for x in range(w)] for y in range(h)]
game = Omok(board=board)</p>
<p>Entity(model=Grid(w+1, h+1), scale=w+1, color=color.blue, x=w//2-0.5, y=h//2-0.5, z=0.1)</p>
<p>for y in range(h):<br />
for x in range(w):<br />
b = Button(parent=scene, position=(x, y), color=color.clear, model=&#8217;circle&#8217;, scale=0.9) # 마우스 왼쪽 클릭<br />
board_buttons[y][x] = b</p>
<p>def on_mouse_enter(b=b):<br />
if b.collision:<br />
b.color = color._100<br />
def on_mouse_exit(b=b):<br />
if b.collision:<br />
b.color = color.clear</p>
<p>b.on_mouse_enter = on_mouse_enter<br />
b.on_mouse_exit = on_mouse_exit</p>
<p>def on_click(b=b):<br />
# player turn<br />
b.text = &#8217;1&#8242;<br />
b.color = color.black<br />
b.collision = False</p>
<p>game.put(x=int(b.position.x), y=int(h &#8211; b.position.y &#8211; 1)) # start from top left<br />
won_player = game.check_won()</p>
<p>if won_player &gt; 0:<br />
end_session(won_player)<br />
game.next()</p>
<p># cpu turn<br />
input_o = game.board.board.copy()<br />
input_o[(input_o != 1) &amp; (input_o != 0)] = -1<br />
input_o[(input_o == 1) &amp; (input_o != 0)] = 1<br />
input_o = np.expand_dims(input_o, axis=(0, -1)).astype(np.float32)</p>
<p>output = model.predict(input_o).squeeze()<br />
output = output.reshape((h, w))<br />
output_y, output_x = np.unravel_index(np.argmax(output), output.shape)<br />
game.put(x=output_x, y=output_y)</p>
<p>data_x = str(output_x)<br />
data_y = str(output_y)<br />
print(data_x, data_y)</p>
<p># 아두이노에 넘겨줄 데이터 정제<br />
# 00/00 의 형태로 data_output 생성<br />
if len(data_x) != 2:<br />
data_x = &#8217;0&#8242; + data_x<br />
if len(data_y) != 2:<br />
data_y = &#8217;0&#8242; + data_y<br />
data_output = &#8220;Q&#8221; + data_x + data_y<br />
data_output = data_output.encode(&#8216;utf-8&#8242;)</p>
<p>sendSerial.write(data_output)<br />
ardSerial.write(data_output)</p>
<p>print(type(data_output))<br />
print(data_output)</p>
<p>board_buttons[h - output_y - 1][output_x].text = &#8217;2&#8242;<br />
board_buttons[h - output_y - 1][output_x].text_color = color.black<br />
board_buttons[h - output_y - 1][output_x].color = color.white<br />
board_buttons[h - output_y - 1][output_x].collision = False</p>
<p>won_player = game.check_won()</p>
<p>if won_player &gt; 0:<br />
end_session(won_player)</p>
<p>game.next()<br />
print(game.board)<br />
b.on_click = on_click<br />
def end_session(won_player):<br />
Panel(z=1, scale=10, model=&#8217;quad&#8217;)<br />
t = Text(f&#8217;Player {won_player} won!&#8217;, scale=3, origin=(0, 0), background=True)<br />
t.create_background(padding=(.5,.25), radius=Text.size/2)</p>
<p>app.run()<br />
</div>
<p><span style="color: #00ccff"><strong>영상인식 코드</strong></span></p>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p>import numpy as np, cv2<br />
import time<br />
image = cv2.imread(&#8216;images/go2.jpg&#8217;, cv2.IMREAD_COLOR)<br />
capture = cv2.VideoCapture(0) #카메라 불러오는 함수<br />
if capture.isOpened() == False: raise Exception(&#8220;카메라 연결 안됨&#8221;)<br />
stone = []
final_stone = []
while(capture.isOpened()): # 카메라가 열려있을 때 ~<br />
ret, image = capture.read() # image로 받아옴</p>
<p>mark = np.zeros(image.shape, image.dtype) # mark 라는 친구를 생성함<br />
mark_1 = np.zeros(image.shape, image.dtype) # mark_1이라는 친구도 생성함함<br />
blue_threshold = 70 # 흑백으로 바꾸는 기준 1<br />
green_threshold = 70 # 흑백으로 바꾸는 기준 2<br />
red_threshold = 70 # 흑백으로 바꾸는 기준 3<br />
bgr_threshold = [blue_threshold, green_threshold, red_threshold] # 3차월 배열로 바꿔줌</p>
<p>for i in range(image.shape[0]):<br />
for j in range(image.shape[1]):<br />
if (image[i, j, 0] &gt;= blue_threshold) and (image[i, j, 1] &gt;= green_threshold) and image[i, j, 2] &gt;= red_threshold:<br />
mark_1[i, j ] = 255,255,255 # 검정<br />
else :<br />
mark_1[i, j ] = 0,0,0 # 하얀색</p>
<p>ju = cv2.imread(&#8216;images/go2.jpg&#8217;, cv2.COLOR_BGR2GRAY) # 검출할 사진 불러옴<br />
white_s = cv2.cvtColor(ju, cv2.COLOR_BGR2GRAY) # 불러온 친구의 색 공간을 바꿈<br />
mark = cv2.cvtColor(mark_1 , cv2.COLOR_BGR2GRAY) # 위와 동문<br />
template = white_s # template = 비교할 것<br />
w, h =template.shape[:: -1] # 불러온 사진의 너비와 높이</p>
<p>res = cv2.matchTemplate(mark, template, cv2.TM_CCOEFF_NORMED) # 함수를 사용해서 비교함<br />
threshold = 0.6 # 이 값보다 높을 때<br />
loc = np.where(res &gt; threshold) # 위치를 리턴함<br />
for pt in zip(*loc[::-1]): # 위치의 가로 세로<br />
if (image[pt[1], pt[0],2] != 255):<br />
cv2.rectangle(image, pt , (pt[0]+w, pt[1]+h), (0,255,0),2) # 초록색으로 칠함<br />
cv2.circle(image, (int(pt[0]+w*0.5), int(pt[1]+h*0.5)),1, (255,0,0)) # 중심에 파란 원<br />
s_x = int(pt[0] + w * 0.5) # 중심 x<br />
s_y = int(pt[1] + h * 0.5) # 중심 y<br />
s_xy = str(s_x) + str(s_y) # str로 합침<br />
s_xy = int(s_xy) # 다시 숫자로 바꿈<br />
s_xy_dt = []
stone_dt = []
for i in range(0, 10): # 범위 내 값을 걸러줌<br />
for j in range(0, 10):<br />
s_xy_dt.append(s_xy &#8211; i*1000 &#8211; j)<br />
s_xy_dt.append(s_xy &#8211; i*1000 + j)<br />
s_xy_dt.append(s_xy + i*1000 &#8211; j)<br />
s_xy_dt.append(s_xy + i*1000 + j)<br />
xx = 0<br />
for k in range(len(s_xy_dt)): ## 값이 같지 않으면 stack<br />
if stone != s_xy_dt[k]:<br />
xx += 1<br />
yy = 0<br />
if xx == len(s_xy_dt) : # stack 값이 다 같을 때 = 오차의 범위 값이 list에 없을 때<br />
if s_xy &lt; 99999 : # y축이 두자리 프레임일 때<br />
for k in range(len(stone)):<br />
if abs(stone[k] &#8211; s_xy) &lt; 110: # 100의 자리 = x축 프레임 차이, 1의 자리 = y축 프레임<br />
yy += 1<br />
if yy == 0 :<br />
stone.append(s_xy)</p>
<p>else : # x ,y 둘다 3자리 프레임일 때<br />
for k in range(len(stone)):<br />
if abs(stone[k] &#8211; s_xy) &lt; 1010: # 1000의 자리 = x축, 1의자리 = y축의 차이<br />
yy += 1<br />
if yy == 0 :<br />
stone.append(s_xy)</p>
<p>aa = len(final_stone)<br />
for i in range(len(stone)):<br />
stone_xx_1 = 0<br />
stone_yy_1 = 0<br />
if stone[i] &lt; 99999 :<br />
stone_x = stone[i] / 100 # 앞자리 3개<br />
stone_y = stone[i] % 100 # 뒷자리 2개</p>
<p>stone_y = abs(stone_y) / 20 # 경험적 치수<br />
stone_x = (abs((stone_x &#8211; 160) / 20) ) # 경험적 치수<br />
else :<br />
stone_x = stone[i] / 1000 # 앞자리 3개<br />
stone_y = stone[i] % 1000 # 뒷자리 3개</p>
<p>stone_x = (abs((stone_x &#8211; 160) / 20)) # 경험적 치수<br />
stone_y = (stone_y / 20 ) # 경험적 치수<br />
ab = int(stone_x) *100 + int(stone_y)<br />
if ab not in final_stone: # list에 없으면<br />
final_stone.append(ab)</p>
<p>if aa != len(final_stone) : # 전의 길이와 다르면 = 새롭게 추가되면<br />
print( int(final_stone[-1] % 100), int(final_stone[-1]/100))<br />
print(&#8216;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8217;)<br />
cv2.imshow(&#8216;mm&#8217;, image) # 이미지 출력<br />
cv2.imshow(&#8216;aa&#8217;, ju) # 비교 이미지 출력<br />
cv2.waitKey(150) # delay<br />
5neck.py<br />
import numpy as np, cv2<br />
import time<br />
image = cv2.imread(&#8216;images/go2.jpg&#8217;, cv2.IMREAD_COLOR)<br />
capture = cv2.VideoCapture(0) #카메라 불러오는 함수<br />
if capture.isOpened() == False: raise Exception(&#8220;카메라 연결 안됨&#8221;)<br />
stone = []
final_stone = []
while(capture.isOpened()): # 카메라가 열려있을 때 ~<br />
ret, image = capture.read() # image로 받아옴</p>
<p>mark = np.zeros(image.shape, image.dtype) # mark 라는 친구를 생성함<br />
mark_1 = np.zeros(image.shape, image.dtype) # mark_1이라는 친구도 생성함함<br />
blue_threshold = 70 # 흑백으로 바꾸는 기준 1<br />
green_threshold = 70 # 흑백으로 바꾸는 기준 2<br />
red_threshold = 70 # 흑백으로 바꾸는 기준 3<br />
bgr_threshold = [blue_threshold, green_threshold, red_threshold] # 3차월 배열로 바꿔줌</p>
<p>for i in range(image.shape[0]):<br />
for j in range(image.shape[1]):<br />
if (image[i, j, 0] &gt;= blue_threshold) and (image[i, j, 1] &gt;= green_threshold) and image[i, j, 2] &gt;= red_threshold:<br />
mark_1[i, j ] = 255,255,255<br />
else :<br />
mark_1[i, j ] = 0,0,0</p>
<p>#print(mark.shape)</p>
<p>ju = cv2.imread(&#8216;images/go2.jpg&#8217;, cv2.COLOR_BGR2GRAY) # 검출할 사진 불러옴<br />
white_s = cv2.cvtColor(ju, cv2.COLOR_BGR2GRAY) # 불러온 친구의 색 공간을 바꿈<br />
mark = cv2.cvtColor(mark_1 , cv2.COLOR_BGR2GRAY) # 위와 동문<br />
template = white_s # template = 비교할 것<br />
w, h =template.shape[:: -1] # 불러온 사진의 너비와 높이<br />
#stone = []
res = cv2.matchTemplate(mark, template, cv2.TM_CCOEFF_NORMED) # 함수를 사용해서 비교함<br />
threshold = 0.6 # 이 값보다 높을 때<br />
loc = np.where(res &gt; threshold) # 위치를 리턴함<br />
for pt in zip(*loc[::-1]): # 위치의 가로 세로<br />
if (image[pt[1], pt[0],2] != 255):<br />
cv2.rectangle(image, pt , (pt[0]+w, pt[1]+h), (0,255,0),2) # 초록색으로 칠함<br />
cv2.circle(image, (int(pt[0]+w*0.5), int(pt[1]+h*0.5)),1, (255,0,0))<br />
#print(int(pt[0]+w*0.5), int(pt[1]+h*0.5))<br />
s_x = int(pt[0] + w * 0.5)<br />
s_y = int(pt[1] + h * 0.5)<br />
s_xy = str(s_x) + str(s_y)<br />
s_xy = int(s_xy)<br />
s_xy_dt = []
stone_dt = []
for i in range(0, 10):<br />
for j in range(0, 10):<br />
s_xy_dt.append(s_xy &#8211; i*1000 &#8211; j)<br />
s_xy_dt.append(s_xy &#8211; i*1000 + j)<br />
s_xy_dt.append(s_xy + i*1000 &#8211; j)<br />
s_xy_dt.append(s_xy + i*1000 + j)<br />
xx = 0<br />
for k in range(len(s_xy_dt)):<br />
if stone != s_xy_dt[k]:<br />
xx += 1<br />
yy = 0<br />
if xx == len(s_xy_dt) :<br />
if s_xy &lt; 99999 :<br />
for k in range(len(stone)):<br />
if abs(stone[k] &#8211; s_xy) &lt; 110:<br />
yy += 1<br />
if yy == 0 :<br />
stone.append(s_xy)<br />
else :<br />
for k in range(len(stone)):<br />
if abs(stone[k] &#8211; s_xy) &lt; 1010:<br />
yy += 1<br />
if yy == 0 :<br />
stone.append(s_xy)<br />
#print(stone)<br />
aa = len(final_stone)<br />
for i in range(len(stone)):<br />
stone_xx_1 = 0<br />
stone_yy_1 = 0<br />
if stone[i] &lt; 99999 :<br />
stone_x = stone[i] / 100<br />
stone_y = stone[i] % 100<br />
#print(stone_x)<br />
#print(stone_y)<br />
stone_y = abs(stone_y) / 20<br />
stone_x = (abs((stone_x &#8211; 160) / 20) )<br />
else :<br />
stone_x = stone[i] / 1000<br />
stone_y = stone[i] % 1000<br />
#print(stone_x)<br />
#print(stone_y)<br />
stone_x = (abs((stone_x &#8211; 160) / 20))</p>
<p>stone_y = (stone_y / 20 )<br />
ab = int(stone_x) *100 + int(stone_y)<br />
if ab not in final_stone:<br />
final_stone.append(ab)</p>
<p>#for k in range(len(final_stone)):<br />
# print(int(final_stone[k] / 100) , int(final_stone[k] % 100))<br />
if aa != len(final_stone) :<br />
print( int(final_stone[-1] % 100), int(final_stone[-1]/100))<br />
print(&#8216;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8217;)<br />
cv2.imshow(&#8216;mm&#8217;, image)<br />
cv2.imshow(&#8216;aa&#8217;, ju)<br />
cv2.waitKey(150)<br />
</div>
<p>&nbsp;</p>
<p><span style="color: #00ccff"><strong>Omok.py</strong></span></p>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p>import numpy as np<br />
class Board():<br />
def __init__(self, w, h):<br />
self.w = w<br />
self.h = h<br />
self.board = np.zeros((self.h, self.w), dtype=np.int)<br />
&#8220;&#8221;"<br />
def __repr__(self):<br />
string = &#8221;<br />
data1 = []
data2 = []
for y in range(self.h):<br />
for x in range(self.w):<br />
string += &#8216;%d &#8216; % self.board[y][x]
if self.board[y][x] == 2:<br />
data1.append(x)<br />
data2.append(y)<br />
string += &#8216;\n&#8217;</p>
<p>return string<br />
&#8220;&#8221;"</p>
<p>class Omok():<br />
def __init__(self, board):<br />
self.board = board<br />
self.current_player = 1<br />
self.won_player = 0</p>
<p>def reset(self):<br />
self.board.board = 0<br />
self.current_player = 1<br />
self.won_player = 0</p>
<p>def put(self, x=None, y=None):<br />
if x is None and y is None:<br />
while True:<br />
rand_x = np.random.randint(0, self.board.w)<br />
rand_y = np.random.randint(0, self.board.h)</p>
<p>if self.board.board[rand_y][rand_x] == 0:<br />
self.board.board[rand_y][rand_x] = self.current_player<br />
break<br />
else:<br />
self.board.board[y][x] = self.current_player</p>
<p>def next(self):<br />
if self.current_player == 1:<br />
self.current_player = 2<br />
else:<br />
self.current_player = 1</p>
<p>def check_won(self):<br />
player = self.current_player</p>
<p>for y in range(self.board.h):<br />
for x in range(self.board.w):<br />
try:<br />
if self.board.board[y][x] == player and self.board.board[y + 1][x] == player and \<br />
self.board.board[y + 2][x] == player and self.board.board[y + 3][x] == player and \<br />
self.board.board[y + 4][x] == player:<br />
self.won_player = player<br />
break<br />
except:<br />
pass</p>
<p>try:<br />
if self.board.board[y][x] == player and self.board.board[y][x + 1] == player and \<br />
self.board.board[y][x + 2] == player and self.board.board[y][x + 3] == player and \<br />
self.board.board[y][x + 4] == player:<br />
self.won_player = player<br />
break<br />
except:<br />
pass</p>
<p>try:<br />
if self.board.board[y][x] == player and self.board.board[y + 1][x + 1] == player and \<br />
self.board.board[y + 2][x + 2] == player and self.board.board[y + 3][x + 3] == player and \<br />
self.board.board[y + 4][x + 4] == player:<br />
self.won_player = player<br />
break<br />
except:<br />
pass</p>
<p>try:<br />
if x &gt;= 4 and self.board.board[y][x] == player and self.board.board[y + 1][x - 1] == player and \<br />
self.board.board[y + 2][x - 2] == player and self.board.board[y + 3][x - 3] == player and \<br />
self.board.board[y + 4][x - 4] == player:<br />
self.won_player = player<br />
break<br />
except:<br />
pass<br />
if self.won_player &gt; 0:<br />
break<br />
return self.won_player<br />
</div>
<p>&nbsp;</p>
<p><span style="color: #00ccff"><strong>모델 학습 코드</strong></span></p>
<p><strong></strong><div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p>import numpy as np<br />
import tensorflow as tf<br />
import tensorflow.keras as keras<br />
from tensorflow.keras import layers, models<br />
from sklearn.model_selection import train_test_split<br />
from glob import glob<br />
from tqdm import tqdm<br />
from datetime import datetime<br />
from tensorflow.keras.callbacks import ModelCheckpoint, ReduceLROnPlateau<br />
import os<br />
w, h = 20, 20</p>
<p>file_list = glob(&#8216;C:\JupyterProject\dataset_2020\*.npz&#8217;)</p>
<p>x_data, y_data = [], []
for file_path in tqdm(file_list):<br />
data = np.load(file_path)<br />
x_data.extend(data['inputs'])<br />
y_data.extend(data['outputs'])<br />
x_data = np.array(x_data, np.float32).reshape((-1, h, w, 1))<br />
y_data = np.array(y_data, np.float32).reshape((-1, h * w))</p>
<p>x_train, x_val, y_train, y_val = train_test_split(x_data, y_data, test_size=0.2, random_state=2020)</p>
<p>del x_data, y_data</p>
<p>print(x_train.shape, y_train.shape)<br />
print(x_val.shape, y_val.shape)<br />
model = models.Sequential([<br />
layers.Conv2D(64, 7, activation='relu', padding='same', input_shape=(h, w, 1)),<br />
layers.Conv2D(128, 7, activation='relu', padding='same'),<br />
layers.Conv2D(256, 7, activation='relu', padding='same'),<br />
layers.Conv2D(128, 7, activation='relu', padding='same'),<br />
layers.Conv2D(64, 7, activation='relu', padding='same'),<br />
layers.Conv2D(1, 1, activation=None, padding='same'),<br />
layers.Reshape((h * w,)),<br />
layers.Activation('sigmoid')<br />
])</p>
<p>model.compile(<br />
optimizer=&#8217;adam&#8217;,<br />
loss=&#8217;binary_crossentropy&#8217;,<br />
metrics=['acc']
)<br />
model.summary()</p>
<p>start_time = datetime.now().strftime(&#8216;%Y%m%d_%H%M%S&#8217;)<br />
os.makedirs(&#8216;models&#8217;, exist_ok=True)</p>
<p>model.fit(<br />
x=x_train,<br />
y=y_train,<br />
batch_size=256,<br />
epochs=10,<br />
callbacks=[<br />
ModelCheckpoint('./models/%s.h5' % (start_time), monitor='val_acc', verbose=1, save_best_only=True, mode='auto'),<br />
ReduceLROnPlateau(monitor='val_acc', factor=0.2, patience=5, verbose=1, mode='auto')<br />
],<br />
validation_data=(x_val, y_val),<br />
use_multiprocessing=True,<br />
workers=16<br />
)<br />
</div>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
]]></content:encoded>
			<wfw:commentRss>http://www.ntrexgo.com/archives/40434/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>[67호]메타버스를 이용한 문서 보안 시스템</title>
		<link>http://www.ntrexgo.com/archives/40440</link>
		<comments>http://www.ntrexgo.com/archives/40440#comments</comments>
		<pubDate>Wed, 25 Aug 2021 00:00:35 +0000</pubDate>
		<dc:creator>디바이스마트 매거진</dc:creator>
				<category><![CDATA[디바이스마트 매거진]]></category>
		<category><![CDATA[특집]]></category>
		<category><![CDATA[ict]]></category>
		<category><![CDATA[공모전]]></category>
		<category><![CDATA[디바이스마트]]></category>
		<category><![CDATA[매거진]]></category>
		<category><![CDATA[메타버스]]></category>
		<category><![CDATA[문서보안]]></category>
		<category><![CDATA[시스템]]></category>
		<category><![CDATA[융합]]></category>
		<category><![CDATA[최우수상]]></category>
		<category><![CDATA[프로젝트]]></category>

		<guid isPermaLink="false">http://www.ntrexgo.com/?p=40440</guid>
		<description><![CDATA[디바이스마트매거진 67호 &#124; 더욱 함양된 보안성을 갖춘 문서 보안 시스템을 구축하기 위해 메타버스를 이용해보기로 했다. 메타버스를 이용한 보안 시스템의 취약성은 아직 발견된 것이 없으며, 컴퓨터와 휴대폰 두 개의 디바이스를 이용하여 보안 시스템을 구축하기 때문에 더 높은 보안 장벽을 갖는다는 장점이 있다. ]]></description>
				<content:encoded><![CDATA[<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict_-최우수상-메타버스-1.png" rel="lightbox[40440]"><img alt="67 ict_ 최우수상 메타버스 (1)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict_-최우수상-메타버스-1-620x203.png" width="620" height="203" /></a></p>
<p><span style="color: #ff6600;font-size: medium"><strong>2021 ICT 융합 프로젝트 공모전 최우수상</strong></span></p>
<p><span style="font-size: x-large"><strong>메타버스를 이용한 문서 보안 시스템</strong></span></p>
<p style="text-align: right">글 | 광운대학교 최예지, 최혁순</p>
<p>&nbsp;</p>
<p><span style="color: #0000ff"><strong>1.심사평</strong></span><br />
<strong>칩센</strong> 우선 Leap Motion과 unity을 통한 가상현실을 이용하는 것이 실제로는 처음 보고, 매우 흥미로웠습니다. 일반적인 실제 사용의 단계라면 어쩌면 번거로울 수도 있겠으나, 작품의 기획 의도인 강화된 보안이 필요한 경우에는 매우 유용한 기술로 판단됩니다. 다단계의 보안 단계를 만들고, AWS를 이용하여 떨어져 있는 다중의 사용자들 간에 보안 적용된 특정 정보 (작품에서는 문서)의 접근 권한을 만들어주는 것 또한 익숙하지 않은 저에게는 매우 놀라웠습니다. 세부적인 기술 내역에 대하여 저도 검토해봐야겠다는 생각이 드는 작품입니다. 다만 소프트웨어적인 구현만으로 이루어진 점은 개인적으로 조금 아쉽고, 동일한 보안 해제 방식을 통해 특정 목적을 가진 기기를 컨트롤할 수 있는 형태라면 기술의 범위가 충분히 확장될 수 있지 않을까 합니다.</p>
<p><strong>펌테크</strong> 날로 중요시되는 컴퓨터 보안에 메타버스 개념을 접목한 독창적이고 색다른 형태의 작품으로 핵심요소인 소프트웨어 개발에 따른 각각의 SDK, 개발툴 등을 작품의 성격에 맞게 효율적으로 구성하였으며 전체적으로 작품의 기획의도, 기술 구현도, 완성도 등에서 우수한 작품이라고 생각됩니다.</p>
<p><strong>위드로봇</strong> Leap motion을 이용한 제스처로 보안을 처리한 아이디어가 돋보이는 작품입니다. 공모전의 성격을 감안하여 LeapMotion을 대치할 수 있는 기술까지 같이 연구가 진행되었다면 더욱 훌륭한 연구가 되었을 것 같습니다.</p>
<p><strong>뉴티씨</strong> 매우 독특한 발상으로 잘 만들어진 작품입니다. 스마트폰에만 의존하는 보안 시스템에서 발전하여 Cloud server를 활용하여 보다 높은 수준의 보안 시스템을 구현한 예가 될 수 있습니다. 작품의 모양이나 시스템의 표현 방법이나 사용 방법 등을 대폭 개선하면, 좋은 제품으로 탄생할 수도 있을 것 같습니다. 아파트 현관이나 특정 사무실의 보안 시스템도 그런 식으로 만드는 것도 방법인 것 같습니다. 앞으로 기대가 됩니다.</p>
<p><span style="color: #0000ff"><strong>2. 작품 개요</strong></span><br />
개인 휴대폰을 이용한 문서 공유, 개인 정보 저장, 신상 정보를 이용한 거래 등의 서비스들이 늘어나고 있다. 등본이나 각종 증명서, 회사 기업 자료 등 중요한 문서 파일을 휴대폰에 저장하기도 하고, 회원가입 정보나 여러 사이트의 비밀번호, 개인 정보를 휴대폰에 저장하기도 하며, 인터넷 뱅킹, 휴대폰 결제와 같은 개인정보를 이용한 서비스들을 휴대폰을 통해 사용한다. 이러한 휴대폰 속 개인정보를 이용한 서비스들과 휴대폰에 저장하는 개인정보, 주요 문서들이 늘어남에 따라 이를 악용한 범죄 또한 늘어나고 있다. 이러한 범죄에 우리가 노출됨에 있어 가장 큰 문제는 오직 핸드폰의 보안에만 의존하고 있다는 점이다. 휴대폰에 저장된 대부분의 주요 문서나 개인 정보가 오직 핸드폰의 보안 또는 어플 자체의 보안에만 의존하고 있다. 우리는 이러한 문제점을 인식하고 더욱 함양된 보안성을 갖춘 문서 보안 시스템을 구축하기 위해 메타버스를 이용해보기로 했다. 메타버스를 이용한 보안 시스템의 취약성은 아직 발견된 것이 없으며, 컴퓨터와 휴대폰 두 개의 디바이스를 이용하여 보안 시스템을 구축하기 때문에 더 높은 보안 장벽을 갖는다는 장점이 있다. 이러한 장점을 가진 메타버스 중 우리는 Leap Motion과 Unity를 이용해 시스템을 구축하기로 했다. Leap Motion은 손의 움직임을 인식하는 장치로 이를 이용하면 오직 손의 움직임만을 이용하여 잠금을 해제하는 보안장치를 만들 수 있기 때문에 잠금을 해제하는 기록을 데이터로 남기기 어려워 보안에 유리하다. 또한 Unity를 이용한 3D 가상현실을 만들어 여기에 Leap Motion을 적용한다면, 보안 장치를 사용하는 사용자가 아닌 이상 보안 장치 해제 방식을 파악하기 어려워 보안에 유리하다. 우리는 이러한 장점을 이용하여 Leap Motion, Unity를 이용한 메타버스 문서 보안 시스템을 제작하기로 했다.</p>
<p><span style="color: #0000ff"><strong>3. 작품 설명</strong></span><br />
<span style="color: #33cccc"><strong>3.1. 주요 동작 및 특징</strong></span><br />
시스템은 Leap Motion을 이용한 1차 보안과 Unity와 앱을 이용한 2차 보안, 1차 보안과 2차 보안 사이의 서버 통신으로 이루어져 있다.<br />
Leap Motion을 이용해 손의 동작을 감지하고 정해진 동작이 수행되면 웹 서버인 AWS DynamoDB로 동작이 수행됨을 알리는 정보가 넘어간다. 서버 통신을 통해 Leap Motion을 이용한 1차 보안이 해제됨을 감지한 AWS DynamoDB는 이 정보를 핸드폰에 설치된 어플로 넘긴다. 어플에서 정보를 받으면 Unity와 앱을 이용한 2차 보안이 실행되고, 2차 보안에서 정해진 순서대로 번호를 터치하게 되면 문서의 잠금이 해제된다.</p>
<p><span style="color: #993366"><strong>Leap Motion을 이용한 1차 보안</strong></span><br />
Leap Motion에 정해진 동작을 수행하면 그 동작에 해당하는 마법진이 생성되고, 정해진 손짓을 통해 생성된 마법진을 뒤로 보낼 수 있다. 정해진 동작을 순서대로 수행하면 그 동작에 해당하는 4종류의 마법진이 순서대로 생성되고, 이 4종류의 마법진을 정해진 손짓을 통해 순서대로 뒤로 보내 circle에 통과시키면 보안이 해제된다. 이때 4종류의 마법진을 정해진 순서대로 circle에 통과시키지 않으면 보안 잠금이 해제되지 않는다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict_-최우수상-메타버스-2.png" rel="lightbox[40440]"><img class="alignnone size-full wp-image-40500" alt="67 ict_ 최우수상 메타버스 (2)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict_-최우수상-메타버스-2.png" width="500" height="322" /></a></p>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p><strong>[코드 가-1] 위 사진에 해당하는 코드</strong></p>
<p>if ((frame.Hands[1].GrabStrength == 1.0) &amp;&amp; (frame.Hands[1].PinchStrength == 1.0)) //왼손 grab 후 pinch<br />
{<br />
count += 1;<br />
if (count &gt; 3)<br />
count = 0;<br />
}<br />
if (count == 0)<br />
{<br />
if (frame.Hands[0].PinchStrength == 1.0 &amp;&amp; frame.Hands[1].PinchStrength == 1.0) //왼손, 오른손 손가락 pinch<br />
{<br />
fstone.state = 1;<br />
}<br />
if (frame.Hands[0].PinchStrength &lt; 1.0 &amp;&amp; frame.Hands[1].PinchStrength &lt; 1.0 &amp;&amp; fstone.state == 1) //윗줄 후 왼손, 오른손 no pinch<br />
{<br />
GameObject realplz = Instantiate(fstobj) as GameObject; // 첫 번째 오브젝트 생성</p>
<p>realplz.transform.position = new Vector3(0, 0.5f,0.5f);<br />
fstone.state = 0;<br />
}<br />
}<br />
</div>
<p>그림 가-1과 같이 양 손을 pinch 후 떼어내면 마법진이 생성된다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict_-최우수상-메타버스-3.png" rel="lightbox[40440]"><img class="alignnone size-full wp-image-40501" alt="67 ict_ 최우수상 메타버스 (3)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict_-최우수상-메타버스-3.png" width="497" height="616" /></a></p>
<p>&nbsp;</p>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p><strong>[코드 가-2] 위 사진에 해당하는 코드 </strong></p>
<p>if ((frame.Hands[1].GrabStrength == 1.0) &amp;&amp; (frame.Hands[1].PinchStrength == 1.0)) //왼손 grab 후 pinch{<br />
count += 1;<br />
if (count &gt; 3)<br />
count = 0;<br />
}<br />
if (count == 0){<br />
if (frame.Hands[0].PinchStrength == 1.0 &amp;&amp; frame.Hands[1].PinchStrength == 1.0) //왼손, 오른손 손가락 pinch{<br />
fstone.state = 1;<br />
}<br />
if (frame.Hands[0].PinchStrength &lt; 1.0 &amp;&amp; frame.Hands[1].PinchStrength &lt; 1.0 &amp;&amp; fstone.state == 1) //윗줄 후 왼손, 오른손 no pinch{<br />
GameObject realplz = Instantiate(fstobj) as GameObject; // 첫 번째 오브젝트 생성<br />
realplz.transform.position = new Vector3(0, 0.5f,0.5f);<br />
fstone.state = 0;<br />
}<br />
}<br />
else if (count == 1){<br />
if (frame.Hands[0].PinchStrength == 1.0 &amp;&amp; frame.Hands[1].PinchStrength == 1.0) //왼손, 오른손 pinch{<br />
second.state = 1;<br />
}<br />
if (frame.Hands[0].PinchStrength &lt; 1.0 &amp;&amp; frame.Hands[1].PinchStrength &lt; 1.0 &amp;&amp; second.state == 1) //윗 줄 후 왼손, 오른손 no pinch{<br />
GameObject realplz = Instantiate(scdobj) as GameObject; //두 번째 오브젝트 생성<br />
realplz.transform.position = new Vector3(0, 0.5f, 0.5f);<br />
second.state = 0;<br />
}<br />
}<br />
else if (count == 2)<br />
{<br />
if (frame.Hands[0].PinchStrength == 1.0 &amp;&amp; frame.Hands[1].PinchStrength == 1.0) //왼손, 오른손 pinch{<br />
third.state = 1;<br />
}<br />
if (frame.Hands[0].PinchStrength &lt; 1.0 &amp;&amp; frame.Hands[1].PinchStrength &lt; 1.0 &amp;&amp; third.state == 1) //윗 줄 후 왼손, 오른손 no pinch{<br />
GameObject realplz = Instantiate(thdobj) as GameObject; //세 번째 오브젝트 생성<br />
realplz.transform.position = new Vector3(0, 0.5f, 0.5f);<br />
third.state = 0;<br />
}<br />
else if (count == 3)<br />
{<br />
if (frame.Hands[0].PinchStrength == 1.0 &amp;&amp; frame.Hands[1].PinchStrength == 1.0) //왼손, 오른손 pinch{<br />
fourth.state = 1;<br />
}<br />
if (frame.Hands[0].PinchStrength &lt; 1.0 &amp;&amp; frame.Hands[1].PinchStrength &lt; 1.0 &amp;&amp; fourth.state == 1) //윗 줄 후 왼손, 오른손 no pinch{<br />
GameObject realplz = Instantiate(fthobj) as GameObject; //네 번째 오브젝트 생성<br />
realplz.transform.position = new Vector3(0, 0.5f, 0.5f);<br />
fourth.state = 0;<br />
}<br />
}<br />
</div>
<p>왼손 grab 후 오른손 pinch 한 번 할 때마다 object, 즉 마법진이 변경된다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict_-최우수상-메타버스-4.png" rel="lightbox[40440]"><img class="alignnone size-large wp-image-40502" alt="67 ict_ 최우수상 메타버스 (4)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict_-최우수상-메타버스-4-481x620.png" width="481" height="620" /></a></p>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p><strong>[코드 가-3] 위 사진에 해당하는 코드</strong></p>
<p>if (HandPalmYam &gt; -2f &amp;&amp; HandPalmYam &lt; 3.5f &amp;&amp; (frame.Hands[1].GrabStrength==1.0 )) //왼손 grab 후 오른손 흔듦{<br />
Debug.Log(&#8220;앞&#8221;);<br />
this.transform.Translate(0, 2 * Time.deltaTime,0); //오브젝트 앞으로 이동<br />
}<br />
if ((frame.Hands[1].GrabStrength == 1.0) &amp;&amp; (frame.Hands[0].GrabStrength == 1.0)) //양손 grab<br />
Destroy(gameObject); //오브젝트 사라짐<br />
</div>
<p>&nbsp;</p>
<p>왼손 grab 후 오른손 흔들면 object, 즉 마법진이 앞으로 이동하다가 circle에 닿으면 사라지게 된다. 이때, 마법진 4종류가 정해진 순서대로 circle에 닿아야만 잠금이 해제된다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict_-최우수상-메타버스-5.png" rel="lightbox[40440]"><img class="alignnone size-full wp-image-40503" alt="67 ict_ 최우수상 메타버스 (5)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict_-최우수상-메타버스-5.png" width="498" height="295" /></a></p>
<p>Object, 즉 마법진은 계속해서 회전하고 있다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict_-최우수상-메타버스-7.png" rel="lightbox[40440]"><img class="alignnone size-large wp-image-40505" alt="67 ict_ 최우수상 메타버스 (7)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict_-최우수상-메타버스-7-620x98.png" width="620" height="98" /></a></p>
<p>코드 가-5에 의해 object, 즉 마법진이 circle에 닿으면 사라지게 된다. 또한 4종류의 마법진이 정해진 순서대로 circle에 닿아야만 보안이 해제된다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict_-최우수상-메타버스-8.png" rel="lightbox[40440]"><img class="alignnone size-large wp-image-40506" alt="67 ict_ 최우수상 메타버스 (8)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict_-최우수상-메타버스-8-620x206.png" width="620" height="206" /></a></p>
<p><span style="color: #993366"><strong>1차 보안과 2차 보안 사이의 서버 통신</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict_-최우수상-메타버스-9.png" rel="lightbox[40440]"><img class="alignnone size-large wp-image-40507" alt="67 ict_ 최우수상 메타버스 (9)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict_-최우수상-메타버스-9-620x396.png" width="620" height="396" /></a></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict_-최우수상-메타버스-10.png" rel="lightbox[40440]"><img class="alignnone size-full wp-image-40508" alt="67 ict_ 최우수상 메타버스 (10)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict_-최우수상-메타버스-10.png" width="559" height="356" /></a></p>
<p>Leap Motion을 이용한 1차 보안이 해제됨을 알리는 정보가 AWS DynamoDB로 넘어간다. AWS DynamoDB는 받은 정보를 다시 2차 보안에 사용되는 앱에 넘기고, 앱은 정보를 받으면 보안 해제 시스템이 작동된다. 2차 보안 앱은 1차 보안이 해제되기 전까지는 실행되지 않고, 로딩창에 머무르게 된다. 1차 보안이 해제되었다는 정보를 넘겨받아야 실행이 되는 구조이다.<br />
[그림 나-2]는 DynamoDB에서 leapmotion의 데이터가 초기설정 값인 0000이었지만, 1차 보안을 해제한 후 1234로 바뀐 모습이다.<br />
private void Start() //AWS에서 계정에 대한 인증과 권한을 부여받는 코드이다.</p>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p>{<br />
UnityInitializer.AttachToGameObject(this.gameObject);<br />
credentials = new CognitoAWSCredentials(&#8220;ap-northeast-2:ceafc145-14a2-4c03-b397-22368bc8ab4f&#8221;, RegionEndpoint.APNortheast2);<br />
DBclient = new AmazonDynamoDBClient(credentials, RegionEndpoint.APNortheast2);<br />
context = new DynamoDBContext(DBclient);<br />
}</p>
[DynamoDBTable("character_info")]
public class Character<br />
{<br />
[DynamoDBHashKey] // Hash key.<br />
public string id { get; set; }<br />
[DynamoDBProperty]
public int item { get; set; }<br />
}</p>
<p>public void FindItem() //DB에서 캐릭터 정보 받기<br />
{<br />
context.LoadAsync&lt;Character&gt;(&#8220;leapmotion&#8221;, (AmazonDynamoDBResult&lt;Character&gt; result) =&gt;<br />
{<br />
// id가 leapmotion인 캐릭터 정보를 DB에서 받아옴<br />
if (result.Exception != null)<br />
{<br />
Debug.LogException(result.Exception);<br />
// 정보를 받아오지 못했을 때의 예외처리</p>
<p>return;<br />
}<br />
c = result.Result;<br />
Debug.Log(c.item); //찾은 캐릭터 정보 중 아이템 정보 출력<br />
}, null);<br />
}</p>
<p>void Update()<br />
{</p>
<p>FindItem();<br />
if (c!=null) //앱 실행 후 초기화되는 aws에서 정보를 받아오는 변수이므로 받아온 정보가 null값인지를 먼저 확인한다.<br />
{<br />
if (c.item == 1234)<br />
{<br />
GameObject.Destroy(this.gameObject);<br />
SceneManager.LoadScene(&#8220;realA&#8221;);<br />
}<br />
}<br />
</div>
사용자가 4번에 걸쳐서 원을 서클에 통과시키면, 4개의 원이 지정된 패턴과 일치하는지를 판별 후, 맞다면 AWS에 접속하여 DynamoDB의 leapmotion이라는 데이터의 값을 1234로 변경해준다.</p>
<p><span style="color: #993366"><strong>Unity와 앱을 이용한 2차 보안</strong></span><br />
AWS DynamoDB로 전달된 1차 보안이 해제됨을 알리는 정보는 만들어둔 핸드폰에 전달된다. 정보를 받은 핸드폰은 앱을 실행시킨다. 실행된 앱에서 정해진 물체에 카메라를 가져가면 키패드가 생성되고, 생성된 키패드에 비밀번호를 순서대로 입력하면 2차 보안이 해제되어 문서를 열람할 수 있게 된다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict_-최우수상-메타버스-11.png" rel="lightbox[40440]"><img class="alignnone size-full wp-image-40509" alt="67 ict_ 최우수상 메타버스 (11)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict_-최우수상-메타버스-11.png" width="580" height="433" /></a></p>
<p>AR 키패드 화면으로 넘어왔지만, 지정된 물체를 비추지 않아, 키패드가 나타나지 않는 모습이다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict_-최우수상-메타버스-12.png" rel="lightbox[40440]"><img class="alignnone size-full wp-image-40510" alt="67 ict_ 최우수상 메타버스 (12)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict_-최우수상-메타버스-12.png" width="574" height="414" /></a><br />
지정된 물체를 비추고 나서, 키패드가 나타나는 모습이다.</p>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p>if (count == 4) //사용자가 키패드를 누른 횟수를 센다<br />
{<br />
Debug.Log(&#8220;full&#8221;);<br />
for (int i = 0; i &lt; 4; i++)<br />
{<br />
if (answer[i] != realanswer[i]) //입력 비밀번호랑 정답 비밀번호랑 비교<br />
{<br />
SceneManager.LoadScene(&#8220;retry&#8221;);<br />
break;<br />
}</p>
</div>
기본적으로 앱은 AWS와 계속해서 통신을 하고 DynamoDB의 leapmotion 데이터의 값을 반복해서 불러들이는 작업을 하고, 받아온 값이 최초 설정값인 0000이 아닌 1234일 때, 씬을 변경하여 앱을 실행시키는 역할을 한다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict_-최우수상-메타버스-13.png" rel="lightbox[40440]"><img class="alignnone size-full wp-image-40511" alt="67 ict_ 최우수상 메타버스 (13)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict_-최우수상-메타버스-13.png" width="579" height="399" /></a></p>
<p><span style="color: #33cccc"><strong>3.2. 전체 시스템 구성</strong></span><br />
위 시스템은 Leap Motion을 이용한 1차 보안과 Unity와 앱을 이용한 2차 보안, 1차 보안과 2차 보안 사이의 서버 통신으로 이루어져 있다.<br />
Leap Motion을 이용해 손의 동작을 감지하고 정해진 동작이 수행되면 웹 서버인 AWS DynamoDB로 동작이 수행됨을 알리는 정보가 넘어간다. 서버 통신을 통해 Leap Motion을 이용한 1차 보안이 해제됨을 감지한 AWS DynamoDB는 이 정보를 핸드폰에 설치된 어플로 넘긴다. 어플에서 정보를 받으면 Unity와 앱을 이용한 2차 보안이 실행되고, 2차 보안 앱에서 지정된 물체를 비추게 되면 키패드가 나타나고, 키패드에 비밀번호를 입력하게 되면 2차 보안이 해제되면서 문서의 잠금이 해제된다. 따라서 키패드를 거치기 전, 지정된 물체를 알고 있어야 하고, 그것을 비추어야만 비밀번호를 입력할 수 있다는 점에서 2차 보안 속에 한 단계의 보조 보안단계가 더 있다고 볼 수 있다.</p>
<p><span style="color: #33cccc"><strong>3.3. 개발 환경</strong></span><br />
위 시스템은 Leap Motion을 이용한 1차 보안과 Unity와 앱을 이용한 2차 보안, 1차 보안과 2차 보안 사이의 서버 통신으로 이루어져 있다. 먼저 Leap Motion을 이용한 1차 보안에서 Leap Motion과 Unity를 이용하며, 이를 위해 Leap Motion SDK와 개발 Tool로 Unity 2D, Unity 3D를 사용하였다. 또한 Unity 개발 언어로 C#을 이용하였으며 3D 가상 현실 환경을 구축하기 위해 Asset store를 사용하였다. 또한 개발 언어를 위한 Tool로 Visual Studio를 사용하였다. 다음으로 1차 보안과 2차 보안 사이의 서버 통신을 위한 시스템으로 AWS와 DynamoDB를 사용하였다. 마지막으로 App과 Unity를 이용한 2차 보안에서 App 개발과 보안 환경 구축을 위해 개발 Tool로 Unity 2D, Unity 3D를 사용하였다. 또한 Unity 개발 언어로 C#을 이용하였으며 3D 가상 현실 환경을 구축하기 위해 Asset store와 Vuforia를 사용하였다. 또한 개발 언어를 위한 Tool로 Visual Studio를 사용하였다.</p>
<p><strong><span style="color: #993366">Leap Motion을 이용한 1차 보안</span></strong><br />
생체 움직임 감지를 위해 Leap Motion을 사용하였으며, Leap Motion을 사용하기 위해 Leap Motion SDK를 사용하였다. Leap Motion 이용을 위한 증강 현실을 구현하기 위한 개발 Tool로 Unity 2D와 Unity 3D를 사용하였으며, Unity 개발 언어로 C#을 사용하고, 개발 언어를 사용하기 위한 Tool로 Visual Studio를 사용하였다. 3D 증강 현실 구현을 위해 Asset Store를 사용하였으며, AR엔진으로 Vuforia를 이용하였다.</p>
<p><span style="color: #993366"><strong>1차 보안과 2차 보안 사이의 서버 통신</strong></span><br />
1차 보안에서의 정보를 서버로 옮기고, 서버에 저장된 정보를 다시 휴대폰 앱으로 전송하기 위한 웹 서버를 구축하였다. 이때 AWS를 사용하여 웹서버를 구축하였다. 웹 서버로 통신한 정보를 저장하기 위해 DynamoDB를 데이터베이스로 사용하였다.</p>
<p><span style="color: #993366"><strong>Unity와 App을 이용한 2차 보안</strong></span><br />
증강 현실을 이용한 App 개발을 위한 개발 Tool로 Unity 2D와 Unity 3D를 사용하였으며, Unity 개발 언어로 C#을 사용하고, IDE로 Visual Studio를 사용하였다. 3D 증강 현실 구현을 위해 Asset Store를 사용하였으며, AR엔진으로 Vuforia를 이용하였다.</p>
<p><span style="color: #0000ff"><strong>4. 단계별 제작 과정</strong></span><br />
시스템은 ‘Leap Motion을 이용한 1차 보안’과 ‘Unity와 앱을 이용한 2차 보안’, ‘1차 보안과 2차 보안 사이의 서버 통신’으로 단계를 나누어 제작하였다.</p>
<p><span style="color: #33cccc"><strong>4.1. Leap Motion을 이용한 1차 보안</strong></span><br />
이 단계에서는 먼저 Leap Motion을 Unity와 연동시킨 뒤 Leap Motion의 동작 인식 감도를 확인하였다. 이후 Unity를 이용해 Leap Motion을 이용하기 위한 3D 가상 현실과 보안 시스템을 구축하였고, 가상 현실과 보안 시스템을 제어하기 위한 코드를 작성하였다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict_-최우수상-메타버스-1.jpg" rel="lightbox[40440]"><img class="alignnone size-full wp-image-40498" alt="67 ict_ 최우수상 메타버스 (1)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict_-최우수상-메타버스-1.jpg" width="567" height="401" /></a></p>
<p>그림 4-1-1과 같이 Leap Motion을 이용하기에 앞서 Leap Motion을 Unity에 연동시키고, Unity에서의 Leap Motion 작동 여부와 실제 작동 환경을 테스트하였다. 이후 Leap Motion의 손동작 인식 감도를 확인하였다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict_-최우수상-메타버스-14.png" rel="lightbox[40440]"><img class="alignnone size-full wp-image-40512" alt="67 ict_ 최우수상 메타버스 (14)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict_-최우수상-메타버스-14.png" width="569" height="380" /></a><br />
다음으로 그림 4-4-2와 같이 Leap Motion이 사용되는 가상 현실을 Unity를 통해 구축하였다. 배경과 바닥을 3D object와 asset을 이용하여 구현했으며, 손동작에 따라 생성되고 작동될 3D object를 만들었다. 마지막으로 코드 3-1-가-1,2,3,4,5와 같이 Leap Motion에 감지된 동작에 따라 실행될 기능에 대해 코드를 작성하였다. 왼손 grab 후에 pinch 한 번 할 때마다 object가 바뀌고, 양손을 pinch 후에 떼면 object가 생성되는 코드, 왼손 grab 후 오른손을 흔들면 object가 앞으로 이동하는 코드, 양손 grab시에 object가 사라지는 코드, object가 회전하는 코드, object가 circle에 닿으면 사라지는 코드, object가 정해진 순서대로 circle에 닿으면 그 정보를 관리자 object에 전달하는 코드를 작성하였다.</p>
<p><span style="color: #33cccc"><strong>4.2. 1차 보안과 2차 보안 사이의 서버 통신</strong></span><br />
이 단계에서는 먼저 1차 보안을 담당하는 노트북과 2차 보안을 담당하는 핸드폰 App사이의 서버 통신을 위한 웹서버를 구축하였다. 이후 노트북의 정보가 구축한 웹서버로 전달되는지 여부를 확인하였다. 다음으로 웹서버에 저장된 데이터 베이스가 휴대폰 App으로 전달되는지 여부를 확인하였다. 마지막으로 1차 보안이 해제됨을 알리는 정보를 웹 서버에 전달하고 웹 서버는 이를 데이터 베이스에 저장하여 2차 보안을 담당하는 휴대폰 App으로 전달하는 코드를 작성하였다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict_-최우수상-메타버스-15.png" rel="lightbox[40440]"><img class="alignnone size-full wp-image-40513" alt="67 ict_ 최우수상 메타버스 (15)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict_-최우수상-메타버스-15.png" width="571" height="297" /></a></p>
<p>그림 4-2-1과 같이 웹서버를 구축하였다. DynamoDB에 leapmotion 데이터가 저장되어 있는 모습을 볼 수 있다.</p>
<p>&nbsp;</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict_-최우수상-메타버스-16.png" rel="lightbox[40440]"><img class="alignnone size-full wp-image-40514" alt="67 ict_ 최우수상 메타버스 (16)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict_-최우수상-메타버스-16.png" width="569" height="112" /></a></p>
<p>그림 4-2-2와 같이 1차 보안과 웹 서버 사이의 서버 통신이 원활하게 이루어지는지 확인하였다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict_-최우수상-메타버스-17.png" rel="lightbox[40440]"><img class="alignnone size-full wp-image-40515" alt="67 ict_ 최우수상 메타버스 (17)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict_-최우수상-메타버스-17.png" width="568" height="108" /></a></p>
<p>그림 4-2-3과 같이 웹 서버에 저장된 데이터 베이스가 2차 보안을 담당하는 휴대폰 App으로 잘 전달되는지 여부를 확인하였다. DynamoDB의 데이터 leapmotion의 정보를 잘 받았는지 확인하는 콘솔로그 창이다.<br />
코드 4-2-1과 같이 1차 보안이 해제됨을 알리는 정보를 웹 서버에 전달하고 웹 서버는 이를 데이터 베이스에 저장하여 2차 보안을 담당하는 휴대폰 App으로 전달하는 코드를 작성하였다.</p>
<p><span style="color: #00ccff"><strong>4.3. Unity와 App을 이용한 2차 보안</strong></span><br />
이 단계에서는 노트북에서 Unity를 이용하여 2차 보안의 가상 현실을 만든 뒤, 순서대로 정해진 비밀번호를 입력하면 문서 보안이 해제되는 코드를 작성하였다. 이후 제작한 App을 휴대폰에 복제하였다. 마지막으로 제작한 App이 구축한 웹 서버를 통해 1차 보안이 해제됨을 알리는 데이터 베이스를 전달하면 App이 실행되도록 하는 코드를 작성하였다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict_-최우수상-메타버스-18.png" rel="lightbox[40440]"><img class="alignnone size-full wp-image-40516" alt="67 ict_ 최우수상 메타버스 (18)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict_-최우수상-메타버스-18.png" width="566" height="368" /></a></p>
<p>그림 4-3-1과 같이 Unity를 이용하여 2차 보안에 사용할 가상 현실을 제작했다.<br />
그림 4-3-2와 같이 앞서 노트북으로 만든 App을 휴대폰에 빌드하여 휴대폰으로 App이용이 가능하게 만들었다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict_-최우수상-메타버스-19.png" rel="lightbox[40440]"><img class="alignnone size-full wp-image-40517" alt="67 ict_ 최우수상 메타버스 (19)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict_-최우수상-메타버스-19.png" width="283" height="350" /></a></p>
<p><strong> <span style="color: #0000ff">5. 사용한 제품 리스트</span></strong></p>
<p>‘Leap Motion을 이용한 1차 보안’과 ‘Unity와 앱을 이용한 2차 보안’, ‘1차 보안과 2차 보안 사이의 서버 통신’으로 나누어져 있다. 이 중 Leap Motion을 이용한 1차 보안 단계에서 Leap Motion Controller제품을 사용하였다.</p>
<p><span style="color: #0000ff"><strong>6. 회로도</strong></span><br />
작품의 전체 회로도는 그림 6-1과 같다. Leap Motion과 노트북이 1차 보안을 위해 연결되어 있으며, 2차 보안인 휴대폰 App과 웹 서버를 통해 연결되어 있다.</p>
<p><del><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict_-최우수상-메타버스-20.png" rel="lightbox[40440]"><img class="alignnone size-full wp-image-40518" alt="67 ict_ 최우수상 메타버스 (20)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict_-최우수상-메타버스-20.png" width="564" height="269" /></a></del></p>
<p><span style="color: #0000ff"><strong>7. 소스코드</strong></span><br />
<span style="color: #33cccc"><strong>7.1. Leap Motion을 이용한 1차 보안 코드</strong></span><br />
<span style="color: #993366"><strong>Leap Motion Hand model 코드</strong></span></p>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p>void Update(){<br />
controller = new Controller();<br />
Frame frame = controller.Frame();<br />
List&lt;Hand&gt; hands = frame.Hands;<br />
Frame previous = controller.Frame(1);<br />
Hand previous_leapHand = previous.Hands[0];<br />
Vector handOrigin = frame.Hands[0].PalmPosition;<br />
Vector previoushandOrigin = previous_leapHand.PalmPosition;<br />
if ((frame.Hands[1].GrabStrength == 1.0) &amp;&amp; (frame.Hands[1].PinchStrength == 1.0)){<br />
count += 1;<br />
if (count &gt; 3)<br />
count = 0;<br />
}<br />
if (count == 0) {<br />
if (frame.Hands[0].PinchStrength == 1.0 &amp;&amp; frame.Hands[1].PinchStrength == 1.0)<br />
{<br />
fstone.state = 1;<br />
}<br />
if (frame.Hands[0].PinchStrength &lt; 1.0 &amp;&amp; frame.Hands[1].PinchStrength &lt; 1.0 &amp;&amp; fstone.state == 1) {<br />
GameObject realplz = Instantiate(fstobj) as GameObject;</p>
<p>realplz.transform.position = new Vector3(0, 0.5f,0.5f);<br />
fstone.state = 0;<br />
}<br />
}<br />
else if (count == 1){<br />
if (frame.Hands[0].PinchStrength == 1.0 &amp;&amp; frame.Hands[1].PinchStrength == 1.0){<br />
second.state = 1;<br />
}<br />
if (frame.Hands[0].PinchStrength &lt; 1.0 &amp;&amp; frame.Hands[1].PinchStrength &lt; 1.0 &amp;&amp; second.state == 1){<br />
GameObject realplz = Instantiate(scdobj) as GameObject;</p>
<p>realplz.transform.position = new Vector3(0, 0.5f, 0.5f);<br />
second.state = 0;<br />
}<br />
}<br />
else if (count == 2) {<br />
if (frame.Hands[0].PinchStrength == 1.0 &amp;&amp; frame.Hands[1].PinchStrength == 1.0){<br />
third.state = 1;<br />
}<br />
if (frame.Hands[0].PinchStrength &lt; 1.0 &amp;&amp; frame.Hands[1].PinchStrength &lt; 1.0 &amp;&amp; third.state == 1){<br />
GameObject realplz = Instantiate(thdobj) as GameObject;<br />
realplz.transform.position = new Vector3(0, 0.5f, 0.5f);<br />
third.state = 0;<br />
}<br />
else if (count == 3){<br />
if (frame.Hands[0].PinchStrength == 1.0 &amp;&amp; frame.Hands[1].PinchStrength == 1.0){<br />
fourth.state = 1;<br />
}<br />
if (frame.Hands[0].PinchStrength &lt; 1.0 &amp;&amp; frame.Hands[1].PinchStrength &lt; 1.0 &amp;&amp; fourth.state == 1){<br />
GameObject realplz = Instantiate(fthobj) as GameObject;<br />
realplz.transform.position = new Vector3(0, 0.5f, 0.5f);<br />
fourth.state = 0;<br />
}<br />
}<br />
strength1 = frame.Hands[0].GrabStrength;<br />
strength2 = frame.Hands[1].GrabStrength;<br />
Debug.Log(&#8220;왼손??&#8221; + strength2);<br />
Debug.Log(&#8220;오른손??&#8221; + strength1);<br />
}<br />
}<br />
</div>
<p><span style="color: #993366"><strong>Leap Motion New Behavior 코드</strong></span></p>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p>Controller controller;<br />
float HandPalmPitch;<br />
float HandPalmRoll;<br />
float HandPalmYam;<br />
float HandWristRot;<br />
Rigidbody rb;<br />
void Start(){<br />
}<br />
void Update(){<br />
controller = new Controller();<br />
Frame frame = controller.Frame();<br />
List&lt;Hand&gt; hands = frame.Hands;<br />
if (frame.Hands.Count &gt; 0){<br />
Hand fristHand = hands[0];<br />
}<br />
HandPalmPitch = hands[0].PalmNormal.Pitch;<br />
HandPalmRoll = hands[0].PalmNormal.Roll;<br />
HandPalmYam = hands[0].PalmNormal.Yaw;<br />
HandWristRot = hands[0].WristPosition.Pitch;<br />
Debug.Log(&#8220;Pitch : &#8221; + HandPalmPitch);<br />
Debug.Log(&#8220;Roll : &#8221; + HandPalmRoll);<br />
Debug.Log(&#8220;Yam : &#8221; + HandPalmYam);<br />
if (HandPalmYam &gt; -2f &amp;&amp; HandPalmYam &lt; 3.5f &amp;&amp; (frame.Hands[1].GrabStrength==1.0 )){<br />
Debug.Log(&#8220;앞&#8221;);<br />
this.transform.Translate(0, 2 * Time.deltaTime,0);<br />
}<br />
this.transform.Rotate(0, 30, 0);<br />
if ((frame.Hands[1].GrabStrength == 1.0) &amp;&amp; (frame.Hands[0].GrabStrength == 1.0))<br />
Destroy(gameObject);<br />
}<br />
}<br />
</div>
<p><span style="color: #993366"><strong>Leap Motion 관리자 object 코드</strong></span></p>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p>using System.Collections;<br />
using System.Collections.Generic;<br />
using UnityEngine;<br />
public class c1direc : MonoBehaviour{<br />
GameObject director;<br />
void Start(){<br />
director = GameObject.Find(&#8220;director&#8221;);<br />
}<br />
void Update(){<br />
if (this.transform.position.z &gt; 3){<br />
director.GetComponent&lt;director&gt;().arr[director.GetComponent&lt;director&gt;().ind] = 0;<br />
director.GetComponent&lt;director&gt;().ind += 1;<br />
}<br />
}<br />
}<br />
</div>
<p><span style="color: #33cccc"><strong>7.2. 1차 보안과 2차 보안 사이의 서버 통신 코드</strong></span><br />
<span style="color: #993366"><strong>Leap Motion 서버 통신 director 코드</strong></span></p>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p>public class director : MonoBehaviour<br />
{<br />
int count;<br />
public int [] arr = { -1, -1, -1, -1 };<br />
int[] arranswer = { 0, 1, 2, 3 };<br />
DynamoDBContext context;<br />
AmazonDynamoDBClient DBclient;<br />
CognitoAWSCredentials credentials;<br />
Character c;<br />
public Camera a;<br />
private void Start()<br />
{<br />
UnityInitializer.AttachToGameObject(this.gameObject);<br />
credentials = new CognitoAWSCredentials(&#8220;ap-northeast-2:ceafc145-14a2-4c03-b397-22368bc8ab4f&#8221;, RegionEndpoint.APNortheast2);<br />
DBclient = new AmazonDynamoDBClient(credentials, RegionEndpoint.APNortheast2);<br />
context = new DynamoDBContext(DBclient);<br />
}<br />
[DynamoDBTable("character_info")]
public class Character<br />
{<br />
[DynamoDBHashKey] // Hash key.<br />
public string id { get; set; }<br />
[DynamoDBProperty]
public int item { get; set; }<br />
}<br />
private void CreateCharacter() //캐릭터 정보를 DB에 올리기<br />
{<br />
Character c1 = new Character<br />
{<br />
id = &#8220;leapmotion&#8221;,<br />
item = 1234,<br />
};<br />
context.SaveAsync(c1, (result) =&gt;<br />
{<br />
if (result.Exception == null)<br />
Debug.Log(&#8220;Success!&#8221;);<br />
else<br />
Debug.Log(result.Exception);<br />
});<br />
}<br />
void Update() //써클을 통과한 원이 지정된 패턴과 일치하는지 확인 후, 일치하면 서버에 정보를 보냄<br />
{</p>
<p>for(int i = 0; i &lt;= 3; i++)<br />
{<br />
if (arr[i] == arranswer[i])<br />
{<br />
count += 1;<br />
}<br />
else if (arr[i] != arranswer[i])<br />
break;<br />
}<br />
if (count== 4)<br />
{<br />
CreateCharacter();<br />
Debug.Log(&#8220;카운트!!!!!!!!!!!!!!!!!111&#8243;);<br />
}<br />
</div>
<p><span style="color: #33cccc"><strong>7.3. Unity와 App을 이용한 2차 보안 코드</strong></span><br />
<span style="color: #993366"><strong>서버와 통신을 하는 direc코드</strong></span></p>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p>private void Start() //AWS에 접속해서 인증받고 권한을 받아오는 단계<br />
{<br />
UnityInitializer.AttachToGameObject(this.gameObject);<br />
credentials = new CognitoAWSCredentials(&#8220;ap-northeast-2:ceafc145-14a2-4c03-b397-22368bc8ab4f&#8221;, RegionEndpoint.APNortheast2);<br />
DBclient = new AmazonDynamoDBClient(credentials, RegionEndpoint.APNortheast2);<br />
context = new DynamoDBContext(DBclient);</p>
<p>}</p>
[DynamoDBTable("character_info")]
public class Character<br />
{<br />
[DynamoDBHashKey] // Hash key.<br />
public string id { get; set; }<br />
[DynamoDBProperty]
public int item { get; set; }<br />
}<br />
public void FindItem() //DB에서 캐릭터 정보 받기<br />
{<br />
context.LoadAsync&lt;Character&gt;(&#8220;leapmotion&#8221;, (AmazonDynamoDBResult&lt;Character&gt; result) =&gt;<br />
{<br />
// id가 leapmotion인 캐릭터 정보를 DB에서 받아옴<br />
if (result.Exception != null)<br />
{<br />
Debug.LogException(result.Exception);<br />
return;<br />
}<br />
c = result.Result;<br />
Debug.Log(c.item); //찾은 캐릭터 정보 중 아이템 정보 출력<br />
}, null);<br />
}<br />
void Update()<br />
{</p>
<p>FindItem();<br />
if (c!=null) //앱 실행 후 초기화되는 aws에서 정보를 받아오는 변수이므로 받아온 정보가 null값인지를 먼저 확인한다.<br />
{<br />
if (c.item == 1234)<br />
{</p>
<p>GameObject.Destroy(this.gameObject);<br />
SceneManager.LoadScene(&#8220;realA&#8221;);<br />
} }}<br />
</div>
<p><span style="color: #993366"><strong>입력한 비밀번호를 확인하는 director 코드</strong></span></p>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p>public class director : MonoBehaviour<br />
{<br />
public int[] answer = new int[4] { -1, -1, -1, -1 }; //입력받을 비밀번호를 저장할 배열<br />
int[] realanswer = new int[4] { 2, 1, 3, 0 }; //원래 비밀번호 정답<br />
public int count = 0;<br />
int point = 0;<br />
void Start()<br />
{<br />
a.enabled = true;<br />
iinteraction = GameObject.Find(&#8220;iinteraction&#8221;);<br />
interaction1 = GameObject.Find(&#8220;interaction1&#8243;);<br />
interaction2 = GameObject.Find(&#8220;interaction2&#8243;);<br />
interation3 = GameObject.Find(&#8220;interation3&#8243;);<br />
}<br />
// Update is called once per frame<br />
void Update()<br />
{<br />
Debug.Log(count);<br />
if (count == 4) //네개의 비밀번호를 입력받으면 일치하는 비밀번호인지 확인하는 코드<br />
{<br />
Debug.Log(&#8220;full&#8221;);<br />
for (int i = 0; i &lt; 4; i++)<br />
{<br />
if (answer[i] != realanswer[i])<br />
{<br />
SceneManager.LoadScene(&#8220;retry&#8221;);<br />
break;<br />
}<br />
}<br />
}<br />
if (point ==4)<br />
SceneManager.LoadScene(&#8220;EndScene&#8221;);<br />
}<br />
</div>
<p><span style="color: #993366"><strong>어떤 키패드가 눌렸는지 확인하는 interaction1 코드</strong></span></p>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p>void Start()<br />
{ director = GameObject.Find(&#8220;director&#8221;);<br />
mat = GetComponent&lt;Renderer&gt;().material;<br />
mat.SetFloat(&#8220;_Mode&#8221;, 2);<br />
mat.SetInt(&#8220;_SrcBlend&#8221;, (int)UnityEngine.Rendering.BlendMode.SrcAlpha);<br />
mat.SetInt(&#8220;_DstBlend&#8221;, (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);<br />
mat.SetInt(&#8220;_ZWrite&#8221;, 0);<br />
mat.DisableKeyword(&#8220;_ALPHATEST_ON&#8221;);<br />
mat.EnableKeyword(&#8220;_ALPHABLEND_ON&#8221;);<br />
mat.DisableKeyword(&#8220;_ALPHAPREMULTIPLY_ON&#8221;);<br />
mat.renderQueue = 3000;<br />
defaultColor = new Color32(255, 255, 255, 255);<br />
selectedColor = new Color32(255, 0, 0, 255);</p>
<p>mat.color = defaultColor;<br />
}<br />
void Update()<br />
{<br />
if (die == 1)<br />
{<br />
Destroy(gameObject);<br />
}<br />
}<br />
void touchBegan() //키패드의 어떤 숫자가 눌렸는지 확인하는 코드<br />
{<br />
director.GetComponent&lt;director&gt;().count+=1;<br />
director.GetComponent&lt;director&gt;().answer[director.GetComponent&lt;director&gt;().count-1] = 1;<br />
</div>
<p><span style="color: #0000ff"><strong>8. 참고 문헌</strong></span><br />
[1] https://bit.ly/3wcnOow<br />
[2] https://bit.ly/2QNPyjk<br />
[3] https://bit.ly/31u0aWz<br />
[4] https://amzn.to/39rTzQU<br />
[5] https://bit.ly/3wfrber<br />
[6] https://amzn.to/3dneDJx<br />
[7] https://bit.ly/31y7Abi</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
]]></content:encoded>
			<wfw:commentRss>http://www.ntrexgo.com/archives/40440/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>[67호]LoRa 통신 기반의 오픈소스 IoT 플랫폼을 활용한 스마트 공원 관리</title>
		<link>http://www.ntrexgo.com/archives/40437</link>
		<comments>http://www.ntrexgo.com/archives/40437#comments</comments>
		<pubDate>Wed, 25 Aug 2021 00:00:32 +0000</pubDate>
		<dc:creator>디바이스마트 매거진</dc:creator>
				<category><![CDATA[디바이스마트 매거진]]></category>
		<category><![CDATA[특집]]></category>
		<category><![CDATA[ict]]></category>
		<category><![CDATA[iot플랫폼]]></category>
		<category><![CDATA[lora통신기반]]></category>
		<category><![CDATA[공모전]]></category>
		<category><![CDATA[공원관리]]></category>
		<category><![CDATA[디바이스마트]]></category>
		<category><![CDATA[매거진]]></category>
		<category><![CDATA[최우수상]]></category>

		<guid isPermaLink="false">http://www.ntrexgo.com/?p=40437</guid>
		<description><![CDATA[디바이스마트 매거진 67호 &#124; 스마트파크는 공원 내 시설 및 자연생태환경에 대한 관리를 웹을 통해 손쉽게 할 수 있고, 공원관리에서 수집되는 정보를 바탕으로 데이터 시각화를 제공한다.]]></description>
				<content:encoded><![CDATA[<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-1.png" rel="lightbox[40437]"><img alt="67 ict 최우수상_스마트파크 (1)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-1-620x204.png" width="620" height="204" /></a></p>
<p><span style="color: #ff6600"><strong>2021 ICT 융합 프로젝트 공모전 최우수상</strong></span></p>
<p><strong>LoRa 통신 기반의 오픈소스 IoT 플랫폼을 활용한 스마트 공원 관리 : 스마트 파크(Smart Park)</strong></p>
<p>글 | 숭실대학교 김성호, 이경주, 이하늘</p>
<p><span style="color: #3366ff"><strong>1. 심사평</strong></span><br />
<strong>칩센</strong> 사용한 센서의 용도, 센싱 된 데이터의 전송 방법인 LoRa 무선 통신, 컨트롤을 위한 시나리오 등등이 모두 IoT 센서 솔루션을 위한 구성이 잘 되어 있는 듯합니다. 또한 목적 사용자와 적용 환경에 대한 부분도 타겟을 명확히 하여 개발의 목적과 필요한 조건의 한도를 구분한 기획 의도가 돋보입니다. 센서 환경 변화에 대하여 알림이 발생하였을 때 메일로 발송하여 확인하기 보다는 서버로 직접 전송하여, 관리 시스템의 UI 등으로 즉시 보여주면 더 좋았을것 같습니다.</p>
<p><strong>펌테크</strong> 실용성, 아이디어, 창의성이 아주 돋보이는 작품으로 LoRa 통신을 기반으로 스마트 공원관리에 꼭 필요한 핵심적 요소를 효율적이면서도 최적의 시스템으로 구현했으며 전체적으로 작품 기획, 기술 구현도, 완성도 면에서 상당히 뛰어나고 훌륭한 작품이라고 생각됩니다.</p>
<p><strong>위드로봇</strong> 다양한 센서와 IoT 적용에 유리한 테마파크를 현장으로 설정하여 구현한 재미있는 작품입니다.<br />
뉴티씨 매우 훌륭한 작품입니다. 실용화가 가능한 아이디어라고 생각되며, 디자인만 좀 개선하고 프로그램 등을 좀 개선하면 좋은 제품이 될 수 있다고 생각합니다. 공원에 적용하면, 환경미화원님들이 편리하게 이용하여, 깔끔하면서도 꼭 필요할 때만 환경미화원님들이 쓰레기를 비우고, 자동으로 공원의 식물에 물을 줄 수 있고, 가로등도 제어할 수 있어서, 매우 좋은 시스템으로 생각됩니다.</p>
<p><strong>엔티렉스 부설연구소</strong> 오픈소스를 이용하였지만, 시스템 구성에 맞게 시설물관리(쓰레기통, 통행량에 따른 조도 제어등)을 제어하며 관리를 할 수 있으며, 네트워크를 이용하여 여러 개의 공원을 관리할 수 있게 개발을 진행했던것 같습니다. 전체적으로 공원관리에 맞게 개발이 잘 이루어져 있으며, 웹을 통해서 사용자가 육안으로 쉽게 관리할 수 있을 것 같습니다. 그리고 시제 제작 데모를 이용하여, 각 기능을 잘 구현 하였으며, 데이터 수집 등을 잘 처리한 것 같습니다.</p>
<p><span style="color: #3366ff"><strong>2. 작품 개요</strong></span><br />
<span style="color: #008080"><strong> 2.1. 배경 및 목적</strong></span><br />
급격한 도시화, 인구 밀집, 자산 및 자원 낭비 등 도시화로 인해 발생하는 각종 문제에 대응하기 위해 도시 패러다임으로 ‘스마트 시티’가 등장하였다. 그중 행복의 질에 큰 영향을 미치는 도시 공간 중 하나인 공원은 ‘스마트 파크’로 진화하고 있다. 스마트 파크는 다양한 첨단 기술을 활용하여 이용자의 공원 체험 향상, 공원 운영·관리의 효율성을 높이며 도시가 직면한 사회 및 환경문제를 해결하는 지속 가능한 공원이다.<br />
스마트 파크의 국내 사례로 대구에 도입된 ‘IoT See Park’는 4차산업혁명 핵심기술을 공원에 접목하여 인공지능 CCTV, 전역 무료 WiFi, 스마트 휴게시설(태양광 벤치), 스마트 방향 표지판 등의 서비스를 제공하고 있다. 또한 세종시 호수공원에는 체험존 AR(GPS 기반 방향 안내 앱）, VR 체험관 등 AR/VR을 사용한 공원 체험 향상 관련 서비스를 제공한다. 이처럼 대부분의 스마트공원은 이용자 중심 공원 서비스 제공에 초점이 맞춰져 있다. 반면에 스마트공원 관리 측면에서는 서비스 구축이 미비하다. 이에 본 작품은 관리를 목적으로 한 IoT 중심의 지능형 스마트 공원관리시스템인 스마트파크를 구현하였다. 스마트파크는 공원 내 시설 및 자연생태환경에 대한 관리를 웹을 통해 손쉽게 할 수 있고, 공원관리에서 수집되는 정보를 바탕으로 데이터 시각화를 제공한다.</p>
<p><span style="color: #008080"><strong>2.2. 오픈소스 IoT 플랫폼 개선 및 활용</strong></span><br />
스마트파크은 스마트 공원의 센서 데이터를 수집, 전처리, 관리, 시각화, 데이터에 따른 명령수행 등의 기능이 있다. 이를 구성하기 위해 본인이 참여했던 IoT 오픈소스를 스마트파크을 위한 기능 및 서버를 추가·개선하여 사용하였다.</p>
<p><span style="color: #00ccff"><strong>데이터 수집</strong></span><br />
Kafka를 사용하여 스마트 공원의 센서 네트워크로부터 방대한 양의 센서 데이터를 수집할 수 있도록 하였다. 수집한 데이터를 전처리 서버로 보내기 위한 브로커 역할을 한다.</p>
<p><strong><span style="color: #00ccff">데이터 전처리</span></strong><br />
서버 프레임워크로 제작한 서버를 이용하여 데이터 전처리, 로직 검사 작업을 수행한다. 센서 데이터의 등록정보(공원이름, 서비스이름 등등) 추가 및 현재 데이터가 등록된 명령(액추에이터 동작, 이메일전송 등등)수행 조건을 만족하는지에 대한 검사하는 역할을 한다.</p>
<p><span style="color: #00ccff"><strong>관리 및 시각화</strong></span><br />
전처리된 데이터를 NoSQL데이터 베이스인 ElasticSearch에 저장하여 관리한다. 수많은 센서 데이터값이 계속해서 추가되므로 Write 기능에 강점을 가진 데이터 베이스를 선택하였다. 또한, Kibana를 사용하여 ElasticSearch에 저장된 데이터들을 분석, 시각화할 수 있도록 하였다.</p>
<p><span style="color: #008080"><strong>2.3. 스마트파크 IoT 플랫폼으로 관리하는 3가지 스마트 기기(센서노드)</strong></span><br />
본 프로젝트에서는 인체감지를 통해 보행량에 따른 밝기 조절이 가능한 스마트 가로등 관리, 쓰레기통 적재량 확인·알림, 센서를 이용한 수목 관리 등 IoT 중심의 지능형 스마트 공원관리시스템인 스마트파크(Park)를 구현하였다.<br />
스마트파크에서 활용이 가능한 스마트 기기 총 세 가지를 제작했다.<br />
구현한 세 가지 스마트기기는 모두 공통적으로 LoRa통신을 한다. LoRa는 LPWAN(저전력 장거리 무선통신)기술로, LoRa통신을 통해 저전력으로 광범위한 통신을 가능하게 했다. LoRa의 데이터 전송속도가 빠른 편은 아니지만, 공원 관리를 위해 이용되는 센서 데이터들은 실시간 처리보다는 아주 천천히 변화하는 센서 데이터들이며 장거리 통신이 요구되기 때문에 LoRa통신은 본 공원관리 시스템에 적합한 통신이다.</p>
<p><span style="color: #00ccff"><strong>스마트 가로등</strong></span><br />
크게 세 가지 기능이 있다.<br />
(a) 평균적으로 보행자 수가 많은 시간대에 밝기를 밝게 하고 적은 경우에는 밝기를 낮춤으로써 전력소비를 줄일 수 있다<br />
(b) 자동 점소등 시스템이 가능하다. 가로등 상단 부분에 장착된 조도센서를 통해 어두워질 경우 자동으로 점등이 가능하다.<br />
(c) 부점등 알림 기능이 있다. 가로등 하부(바닥) 부분에 부점등 확인을 위한 조도 센서가 장착되어 가로등이 켜져있는 밤에 조도센서(바닥 위치)에서 받은 값이 어두운 값이면 조명이 제대로 안 켜진 상태로 자동적으로 판단하여 관리자에게 알림을 줄 수 있다.</p>
<p><span style="color: #00ccff"><strong>쓰레기 관리</strong></span><br />
스마트 쓰레기통 관리의 경우에는 쓰레기통의 적재량을 주기적으로 알 수 있으며, 로직 등록 기능을 통해 쓰레기가 많이 쌓이면 이메일과 같은 알림을 줄 수 있다. 쓰레기통에 쓰레기가 완전히 채워지기 전에 미리 알림을 주어 쓰레기 수거에 도움을 줄 수 있다. 따라서 공원의 더 나은 청결을 기대할 수 있다.</p>
<p><span style="color: #00ccff"><strong>스마트 수목 관리</strong></span><br />
수목 관리의 경우, 토양센서를 통해 토양의 수분을 측정하고 수분이 부족하면 수중모터를 통해 물을 주도록 한다. 로직 등록 기능을 이용한 자동화로 공원의 수목 관리를 더 편리하게 할 수 있다는 장점이 있다.</p>
<p><span style="color: #0000ff"><strong>3. 작품 설명</strong></span><br />
<span style="color: #008080"><strong> 3.1. 주요 동작</strong></span><br />
<span style="color: #00ccff"><strong> 스마트 쓰레기통</strong></span><br />
공원 내 비치된 쓰레기통의 적재량 정보 파악<br />
쓰레기통에 쓰레기가 가득 차면 쓰레기통을 비워주기 위해 알림을 줄 수 있다.<br />
쓰레기통의 적재량은 쓰레기통의 뚜껑에 부착된 초음파 센서를 통해 거리를 탐지하여 알 수 있다. 이를 통해 얻은 값은 LoRa통신을 통해 LoRa Gateway로 전송된다. LoRa Gateway는 네트워크 서버(싱크 노드)로 전달을 해주어 최종적으로는 백엔드 서버로 값이 전달된다.</p>
<p><span style="color: #00ccff"><strong>적재량 모니터링 및 알람</strong></span><br />
쓰레기통의 적재량을 모니터링하며 일정 수준 이상의 쓰레기가 쌓이면 쓰레기가 넘치기 전에 쓰레기를 수거할 수 있도록 알림을 줄 수 있다. 사용자는 프론트엔드 웹에서 직접 조건을 추가하여 적재량이 몇 프로 이상일 경우에 이메일과 같은 알림을 주도록 설정이 가능하다.</p>
<p><strong>데모</strong><br />
다음은 쓰레기통 전면이다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-1.jpg" rel="lightbox[40437]"><img class="alignnone size-full wp-image-40601" alt="67 ict 최우수상_스마트파크 (1)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-1.jpg" width="498" height="279" /></a></p>
<p>위 사진은 뚜껑 내부에는 초음파 센서가 부착된 모습이다. 거리 측정으로 적재량을 판단한다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-2.png" rel="lightbox[40437]"><img class="alignnone size-full wp-image-40604" alt="67 ict 최우수상_스마트파크 (2)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-2.png" width="505" height="231" /></a></p>
<p>위는 쓰레기통 적재량 변화값을 시각화한 그림이다. 이와 같이 각 공원 쓰레기통의 적재량을 한 눈에 확인할 수 있다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-3.png" rel="lightbox[40437]"><img class="alignnone size-full wp-image-40606" alt="67 ict 최우수상_스마트파크 (3)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-3.png" width="503" height="325" /></a></p>
<p>쓰레기통 적재량이 90프로 이상일 경우, 이메일을 전송하도록 로직을 등록한 것이고 그림 6은 이메일 수신 화면이다.</p>
<p><span style="color: #00ccff"><strong>마트 가로등</strong></span></p>
<p><strong>보행자 통행량 측정</strong><br />
시간별/요일별/계절별/지역별 유동인구 측정 및 분석을 하여 유동인구가 많은 가로등만 점등하여 에너지 절약할 수 있다. 결과적으로 유동인구에 따라 가로등의 밝기를 조절하여 에너지를 절약한다.</p>
<p><strong>PIR 센서를 통한 통행량 측정</strong><br />
유동인구(보행자 통행량)은 가로등에 PIR 모션감지 센서를 부착하여 지속적으로 움직임을 감지한다. 모션 감지가 오랫동안 일어날수록 유동인구에 대한 누적값이 증가한다. 이와 같은 방법으로 센서노드에서 얻은 모션감지의 수치를 백엔드 서버까지 전달하게 된다.</p>
<p><strong>데모</strong><br />
<span style="color: #99ccff"><strong>가로등 유동인구에 따른 밝기 조절</strong></span><br />
다음 사진과 같이 가로등의 상단 부분 LED 옆에 PIR 모션 감지 센서가 부착되어 있다. 모션 감지 센서에서 움직임이 감지되는 시간이 증가할수록 통행량에 대한 값이 증가하게 된다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-4.png" rel="lightbox[40437]"><img class="alignnone size-full wp-image-40608" alt="67 ict 최우수상_스마트파크 (4)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-4.png" width="503" height="218" /></a></p>
<p><strong>방문자 수 변화</strong></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-5.png" rel="lightbox[40437]"><img class="alignnone size-full wp-image-40609" alt="67 ict 최우수상_스마트파크 (5)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-5.png" width="512" height="486" /></a><br />
위 그림은 방문자 수 데이터를 시각화한 자료이다. 직접 센서로 측정해 테스트하고 있는 sangdo의 방문자 수 변화를 볼 수 있다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-6.png" rel="lightbox[40437]"><img class="alignnone size-full wp-image-40610" alt="67 ict 최우수상_스마트파크 (6)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-6.png" width="510" height="243" /></a></p>
<p>위는 보행량에 따라 led 밝기 조절 로직이다. 스마트파크(park)에서는 실질적인 데이터를 수집할 수 없기에 value 범위를 임의값으로 설정하였고, 실제로는 대시보드에 나타난 방문자 수를 참고하여 value 범위를 설정해야한다.</p>
<p><strong>보행량에 따른 밝기 조절</strong></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-7.png" rel="lightbox[40437]"><img class="alignnone size-full wp-image-40611" alt="67 ict 최우수상_스마트파크 (7)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-7.png" width="509" height="336" /></a></p>
<p>위와 같이 유동인구에 따라 가로등 LED 밝기가 달라진다.</p>
<p><span style="color: #99ccff"><strong>가로등 부점등 검사 (고장 알림)</strong></span><br />
가로등 하부의 조도 센서를 통해 가로등의 전구의 조도를 측정 후 LED ON이어야 하는 상태에서 조도가 측정되지 않으면 가로등이 부점등 상태임을 이메일로 알린다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-8.png" rel="lightbox[40437]"><img class="alignnone size-full wp-image-40612" alt="67 ict 최우수상_스마트파크 (8)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-8.png" width="510" height="465" /></a></p>
<p>그림 14는 부점등 상태일 경우 이메일을 전송하도록 로직을 등록한 것이다. illuminance는 가로등 상단에 있는 조도센서(낮과 밤 판별)의 데이터이고, Lampilluminance는 가로등 하부에 위치한 조도센서(부점등 판별)의 데이터이다. 이때 조도센서의 값이 90 ~ 100 이하라면 어두운 것으로 판단한다. illuminance &lt; 90 인 경우 LED가 ON되어야 하고, 이때 LED는 아래의 조도센서를 향해 빛을 쏘기 때문에 Lampilluminance는 LED 불빛을 감지해 100 이상이어야 한다. LED가 정상적으로 켜지지 않아 Lampilluminance의 값이 100 이하라면 부점등 상태로 판단한다. 따라서 0 &lt; illuminance &lt; 90 이고 0 &lt; Lampilluminance &lt; 100일 때 부점등 상태라 판단해 이메일을 보내도록 로직을 등록했다. 오른쪽 그림은 어두운 상태에서 LED가 정상적으로 켜졌으나, 가로등 하부 조도센서를 손으로 가려 부점등 상태를 조성한 것이다.</p>
<p><span style="color: #99ccff"><strong>밝기에 따른 가로등 자동 점·소등</strong></span></p>
<p><span style="color: #33cccc"><strong><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-9.png" rel="lightbox[40437]"><img class="alignnone size-full wp-image-40613" alt="67 ict 최우수상_스마트파크 (9)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-9.png" width="509" height="228" /></a></strong></span><br />
밝을 경우에는 로직에 등록된 LED 액추에이터 명령을 통해 LED가 자동으로 꺼지고, 어두울 경우 액추에이터 명령을 통해 자동으로 켜질 수 있도록 로직을 등록했다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-10.png" rel="lightbox[40437]"><img class="alignnone size-full wp-image-40614" alt="67 ict 최우수상_스마트파크 (10)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-10.png" width="510" height="259" /></a></p>
<p>가로등 상단의 조도센서를 통해 어두워지거나 밝아질 경우 자동 점/소등한다.<br />
두 번째 그림에서 볼 수 있듯이 밝을 경우 LED를 OFF하고, 세 번째 그림에서 조도센서를 가려 인위적으로 어두운 환경을 만들었을 때 LED가 ON되는 것을 확인할 수 있다.</p>
<p><span style="color: #00ccff"><strong>스마트 수목 관리</strong></span><br />
온도, 습도, 토양 등 측정센서 설치 및 생장 환경 관련 데이터 측정한다. 공원 내의 수목주변의 온도와 습도, 일조량, 토양내 습도 등을 측정 분석하여 알릴 수 있다. 실외의 온도와 습도 일조량, 등을 컨트롤하기는 힘들어 보이므로 모니터링 및 알림 기능을 통해 관리를 도울 수 있다.</p>
<p><strong>관수 기능 자동 제어</strong><br />
토양 센서를 이용해 습도를 측정하여 필요할 경우 관수 시설을 제어하는 기능을 갖고 있다.<br />
토양 수분 센서를 이용하여 토양의 수분이 부족할 경우 액추에이터 명령을 통해 수중모터(펌프)를 작동시켜 토양에 물을 준다. 센서노드는 토양 수분 센서의 값을 일정 주기로 게이트웨이로 전송한다. 센서 데이터는 센서노드 -&gt; 게이트웨이 -&gt; 싱크노드 -&gt; 백엔드 순으로 전달된다.</p>
<p><strong>데모</strong></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-11.png" rel="lightbox[40437]"><img class="alignnone size-full wp-image-40615" alt="67 ict 최우수상_스마트파크 (11)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-11.png" width="504" height="200" /></a><br />
첫 번째 그림에서 볼 수 있듯이 수중모터 제어를 위해 액추에이터로 pump를 등록했다. 토양수분 값이 일정 값 이상이면 사용자가 등록한 액추에이터 값에 따라 수중모터가 동작하도록 로직을 등록했다.<br />
백엔드는 토양수분 센서가 일정 값 이상인지 조건을 확인하여 조건에 부합하면 액추에이터 신호를 싱크노드에 전달하여, 싱크노드-&gt;게이트웨이-&gt;센서노드 순서로 액추에이터 명령이 전달된다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-12.png" rel="lightbox[40437]"><img class="alignnone size-full wp-image-40616" alt="67 ict 최우수상_스마트파크 (12)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-12.png" width="506" height="205" /></a></p>
<p>왼쪽 사진은 등록된 로직에 따라 사용자가 설정한 범위 내 습도값이 해당될 시 액추에이터를 작동시켜 수중모터로 물을 공급하는 모습이다. 오른쪽은 토양수분을 그래프로 시각화한 자료이다.</p>
<p><span style="color: #00ccff"><strong>데이터 시각화</strong></span><br />
의미가 없거나 혹은 단순한 의미만을 지닌 데이터로부터 실시간으로 의미있는 정보를 찾는 것 자체가 가치를 창출하는 일이다. 스마트파크는 단순한 센서데이터 들을 이용해 여러 가지 의미 있는 정보를 대시보드에 시각화 하였다.</p>
<p><strong>공원 검색기능</strong><br />
원하는 기간, 원하는 공원을 선택하면 선택된 데이터만을 필터링하여 볼 수 있다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-13.png" rel="lightbox[40437]"><img class="alignnone size-full wp-image-40617" alt="67 ict 최우수상_스마트파크 (13)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-13.png" width="502" height="194" /></a></p>
<p><strong>공원 별 센서값 시각화 보드</strong><br />
공원의 모든 센서값을 시각화한 보드를 통해 센서값의 비교 및 확인하여 공원 별 기상정보 비교분석이 가능하다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-14.png" rel="lightbox[40437]"><img class="alignnone size-full wp-image-40618" alt="67 ict 최우수상_스마트파크 (14)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-14.png" width="508" height="214" /></a></p>
<p><strong>공원 별 방문자 수 비교 분석 기능</strong><br />
공원 별 방문자 수 데이터 시각화를 통해 비교 분석이 가능하다. 각 공원 별 방문자 수를 한 눈에 알아볼 수 있고, 어느 시간대, 어느 요일에 유동량이 많은지를 쉽게 알 수 있어 이를 이용해 방문자 수에 따른 가로등 제어, 공원 내 행사 일정 선택 등 다양한 방면의 활용이 가능하다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-15.png" rel="lightbox[40437]"><img class="alignnone size-full wp-image-40619" alt="67 ict 최우수상_스마트파크 (15)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-15.png" width="508" height="273" /></a></p>
<p><strong>공원 별 쓰레기통 적재량 비교분석 기능</strong><br />
원하는 공원의 쓰레기통 적재량을 실시간으로 체크할 수 있으며, 요일 날짜별 쓰레기통 적재량을 한 눈에 볼 수 있어, 쓰레기통 추가 배치, 수거 등에 유용한 활용이 가능하다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-16.png" rel="lightbox[40437]"><img class="alignnone size-full wp-image-40620" alt="67 ict 최우수상_스마트파크 (16)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-16.png" width="506" height="188" /></a></p>
<p><strong>서비스 유지보수의 용이</strong><br />
IoT서비스 플랫폼에서 지속적으로 안정적인 서비스가 제공되기 위해서는 센서를 비롯한 하드웨어들의 헬스 체크가 필요하다. 스마트파크에서는 서비스 노드의 헬스 정보를 수시로 체크하여 웹 UI상에 나타내 주므로 헬스 정보 확인이 가능하며 문제 발생 시 빠른 유지보수가 가능하다.</p>
<p><strong>유연한 확장성</strong><br />
플랫폼의 운영자가 스마트파크이 관리할 새로운 공원을 하거나, 새로운 IoT서비스를 추가할 경우 웹상에서 몇 가지 조작만으로 공원 및 서비스 등록이 가능해, 유연한 확장성을 보여준다.</p>
<p><span style="color: #008080"><strong>3.2. 주요 특징</strong></span><br />
<strong> <span style="color: #00ccff">UI에서 IoT 디바이스 관리, 데이터 시각화</span></strong></p>
<p><strong>네비게이션 바</strong></p>
<p><strong><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-17.png" rel="lightbox[40437]"><img class="alignnone size-full wp-image-40621" alt="67 ict 최우수상_스마트파크 (17)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-17.png" width="508" height="68" /></a></strong><br />
MANAGEMENT 탭의 구성요소로 Sensor, Node, Sink, Actuator가 있다. SERVICE 탭의 구성요소로 LogicCore가 있다. 사용자는 LogicCore에서 로직을 등록할 수 있다. Kibana 탭의 구성요소로 Dashboard가 있다. Dashboard에서 데이터 시각화를 제공한다.</p>
<p><strong>등록(register)</strong></p>
<table style="width: 620px" border="0" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td><img class="alignnone  wp-image-40622" alt="67 ict 최우수상_스마트파크 (18)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-18.png" height="252" /></td>
<td><img class="alignnone size-full wp-image-40623" alt="67 ict 최우수상_스마트파크 (19)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-19.png" width="193" height="252" /></td>
<td></td>
</tr>
</tbody>
</table>
<p>센서 : register sensor 버튼을 클릭하면 그림 33과 같이 등록 페이지가 나타나고, 사용자는 센서 정보 (센서 이름, 종류, value)를 입력해 센서 등록을 할 수 있다.</p>
<p>싱크 : register sink를 클릭 후 싱크 정보 (싱크 이름, topic, ip:port)를 입력해 싱크를 등록할 수 있다. 스마트파크(park)에서는 싱크를 한강 공원 단위로 설정해 진행하였다.<br />
노드 : 노드 등록은 그림 32와 사진과 같이 노드 이름, 위치 (지도를 클릭하면 해당 위치의 위도 경도를 얻음), 노드의 종류(가로등, 쓰레기통, 수목), 해당 노드에서 사용할 센서, 싱크를 선택해 등록할 수 있다.</p>
<p>이때 노드는 가로등, 쓰레기통, 수목을 의미한다.<br />
액추에이터 : register actuator를 클릭 후 액추에이터 이름을 입력해 등록할 수 있다. 본 프로젝트에서는 토양 습도를 토대로 관수 제어를 하기 위한 pump 액추에이터와 조도에 따라 led 밝기를 조절하기 위한 led 액추에이터를 등록하였다.<br />
각 정보를 입력후 submit 버튼을 클릭하면 센서, 싱크, 액추에이터, 노드의 정보를 서버로 POST한다.</p>
<p><strong>table</strong></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-20.png" rel="lightbox[40437]"><img class="alignnone size-full wp-image-40624" alt="67 ict 최우수상_스마트파크 (20)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-20.png" width="518" height="424" /></a></p>
<p>센서, 싱크, 액추에이터 : 위와 같이 사용자가 등록한 센서, 싱크, 액추에이터의 정보를 table의 형태로 확인 가능하고, 휴지통 img를 클릭하여 삭제할 수 있다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-21.png" rel="lightbox[40437]"><img class="alignnone  wp-image-40625" alt="67 ict 최우수상_스마트파크 (21)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-21.png" width="520" /></a></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-23.png" rel="lightbox[40437]"><img class="alignnone  wp-image-40627" alt="67 ict 최우수상_스마트파크 (23)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-23-443x620.png" width="520" /></a></p>
<p>노드 : 노드는 table을 볼 수 있는 두 가지 옵션 (All, Map)이 있다.</p>
<p>· All 옵션 : default로 설정되어있는 노드 table이며 사용자는 싱크별 노드 table을 볼 수 있다. 스마트파크(park)에서는 한강 공원별 가로등, 쓰레기통, 수목 노드가 table로 나타나 있다.</p>
<p>· Map 옵션 : 마지막 사진과 같이 지도상에서 마커 형태로 노드 위치를 나타낸다. 각 노드의 종류가 마커 이미지로 구분되어 있고, 마커를 클릭하여 해당 노드의 정보를 볼 수 있다. 이때 사용자는 지도를 움직이며 마커 위치를 확인할 수 있다. 마커 이미지 색상은 가로등 : 파란색 / 쓰레기통 : 노란색 / 나무 : 초록색이다.</p>
<p>· 노드 헬스체크 : 사용자는 색상별로 노드의 상태를 확인할 수 있다. 웹 소켓으로 서버와 통신하기 때문에 실시간으로 확인이 가능하다.</p>
<p>· 노드의 상태</p>
<p style="padding-left: 30px">회색 : 알 수 없는 상태<br />
초록 : 연결된 상태<br />
노랑 : 직전에 연결이 끊긴 상태 (초록과 빨강의 사이)<br />
빨강 : 연결이 끊긴 상태</p>
<p>로직코어 (LogicCore)</p>
<p style="padding-left: 30px">· Logic 등록 : 사용자는 LogicCore table에서 register logic 버튼을 클릭해 로직을 등록할 수 있다.</p>
<p>Logic 정보</p>
<p style="padding-left: 30px">· 로직 이름<br />
· 센서 : 센서에 로직 등록<br />
· 시간 범위 : 해당 로직이 작업을 수행할 시간 설정<br />
· value 범위 : 해당 로직이 동작할 value 설정<br />
· action :  사용자가 설정한 시간과, value 범위 내 센서값이 해당할 때 실행할 작업<br />
◎ email : 해당 이메일로 알림 메시지 발송<br />
◎ actuator : value, sleep 값을 받아 해당 액추에이터 작동<br />
register logic에서 각 요소(시간, value, action)를 카드라 할 때 value 카드와 action 카드는 왼쪽 하단 Add ~ 버튼을 통해 추가할 수 있고, value 카드와 시간 카드는 Add scope 버튼을 통해 범위를 추가할 수 있다.<br />
· Logic table : 아래 왼쪽 사진과 같이 등록한 logic을 table의 형태로 볼 수 있고, 휴지통 img를 클릭하여 삭제할 수 있다.<br />
· Show logic : table에서 show logic 버튼을 클릭하면 아래 오른쪽 사진과 같이 로직의 구조를 볼 수 있다.</p>
<p style="padding-left: 30px">이처럼 스마트파크(park)은 웹에서 센서, 노드, 싱크, 액추에이터를 등록하고, 사용자 맞춤 로직을 생성할 수 있기에 스마트공원 관리에 용이하다.</p>
<p><strong>kibana dashboard</strong></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-24.png" rel="lightbox[40437]"><img class="alignnone size-full wp-image-40628" alt="67 ict 최우수상_스마트파크 (24)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-24.png" width="505" height="270" /></a></p>
<p>home 또는 kibana dashboard 메뉴를 통해 시각화된 데이터 확인이 가능하다.</p>
<p><span style="color: #00ccff"><strong>LPWAN 통신 방법인 LoRa 통신, 직접 구현한 LoRa WAN 프로토콜로 작동</strong></span><br />
날씨 상태, 토양 수분 레벨 또는 가로등과 같이 이러한 응용 분야에서 측정되거나 제어되는 크기/양은 모두 확장된 시간 범위에 걸쳐 매우 느리게 변화한다. 또한, 센서 노드가 서로 멀리 떨어져 있는 경우가 많고 배터리로 구동되는 경우가 많으므로, 최적의 무선 프로토콜은 최소한의 전력 소비로 긴 거리에 걸쳐 작은 데이터 패킷을 효율적으로 전송할 수 있어야 한다.<br />
따라서 대표적인 LPWAN(저전력 무선 장거리 통신) 기술 중 비면허 대역 주파수를 이용하는 LoRa 통신을 선택했다. 1Ghz 이하의 대역은 다른 주파수에 비해 서로 간섭력이 낮아 무선 파장 및 효율적 측면에서 좋기 때문에 원거리 통신을 위해서 적합하다. End-Device(센서노드)와 LoRa 게이트웨이, 싱크노드를 구현하여 LoRaWAN 프로토콜을 따르도록 구현했다. End-Device의 LoRaWAN 프로토콜은 C언어로 구현하였으며, LoRa Gateway의 LoRaWAN은 Python으로 구현했다. LoRaWAN의 모든 Mac Command가 구현된 것은 아니지만 필요한 몇가지 Mac Command가 작동하도록 구현했다. DevStatusReq와 DevStatusAns의 Mac Command를 통해 센서노드의 배터리와 수신신호의 수치를 파악할 수 있다. 또한 RFU의 예약된 공간에는 ActuatorReq와 ActuatorAns의 Mac Command를 구현하여 이 명령을 이용하여 액추에이터 작동이 가능하도록 했다. Actuator는 백엔드 서버에서 오는 명령을 End-Device에서 처리하는 기능을 말한다. 예를 들면 백엔드에서 모터를 작동시키거나 LED의 밝기를 조절하는 등의 명령을 센서노드에 전달할 수 있다.</p>
<p><span style="color: #00ccff"><strong>백엔드 서버 특징</strong></span><br />
5G 시대가 도래하면서 IoT(사물 인터넷) 관련 데이터의 가치가 더욱 커질 전망이다. IoT 기술의 특성상 수많은 데이터와 로그를 빠르게 적재, 분석하는 것이 필수적이다. 스마크파크는 Kafka &#8211; DataLogic서비스 &#8211; ElasticSearch로 데이터 파이프라인을 구성하여 방대한 센서 데이터의 준 실시간 처리가 가능하다.</p>
<p><span style="color: #008080"><strong>3.3. 전체 시스템 구성</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-25.png" rel="lightbox[40437]"><img class="alignnone size-full wp-image-40629" alt="67 ict 최우수상_스마트파크 (25)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-25.png" width="507" height="329" /></a></p>
<p><span style="color: #ff9900"><strong>3.3.1. 프론트엔드</strong></span><br />
<strong><span style="color: #00ccff">컴포넌트 구조</span></strong></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-26.png" rel="lightbox[40437]"><img class="alignnone size-full wp-image-40630" alt="67 ict 최우수상_스마트파크 (26)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-26.png" width="506" height="289" /></a></p>
<p>위 그림은 컴포넌트 구조를 간단히 map으로 표현한 것이다, App에서 모든 컴포넌트를 route한다.</p>
<p style="padding-left: 30px">· ‘~ Management’ : sensor, node, sink, actuator를 관리하는 컴포넌트이다. 각각의 resigter 컴포넌트를 호출해 정보를 등록하고, 각 table 컴포넌트에서 해당 list를 table 형태로 출력한다.<br />
· Register Logic : 로직 등록 컴포넌트이다. 하위 컴포넌트 InputCards에서 사용자가 입력한 time, sensor, value, action, actuator card의 정보 넘겨주고 이를 바탕으로 로직을 등록한다.<br />
· LogicTable : 등록된 로직을 list로 나타내는 컴포넌트이다.<br />
· showLogic : logic table에서 show logic을 클릭할 시 호출되는 컴포넌트이다. 하위 컴포넌트 showCards에서 사용자가 등록한 정보를 가져와 해당 로직의 정보를 출력한다.<br />
· kibanaDashboard : 데이터 시각화를 제공하는 컴포넌트이다.</p>
<p><span style="color: #ff9900"><strong>3.3.2. 백엔드</strong></span><br />
<span style="color: #00ccff"><strong>데이터 파이프라인</strong></span><br />
<strong>개요</strong></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-27.png" rel="lightbox[40437]"><img class="alignnone size-full wp-image-40631" alt="67 ict 최우수상_스마트파크 (27)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-27.png" width="506" height="210" /></a></p>
<p>&nbsp;</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-28.png" rel="lightbox[40437]"><img class="alignnone size-full wp-image-40632" alt="67 ict 최우수상_스마트파크 (28)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-28.png" width="504" height="208" /></a></p>
<p>그림 48처럼 센서데이터가 라즈베리파이(Sink Node, 공원)에서 최종 저장소로 바로 전송되는 경우 라즈베리파이 내에서 측정값과 등록정보(metaData)를 모두 관리해야 하기 때문에 센서 네트워크에 매우 큰 부담을 준다. 또한 스마트파크의 매우 많은 라즈베리파이가 하나의 저장소로 데이터를 전송하는 경우 Elasticsearch에게 매우 높은 가용성과 처리량을 요구하게 된다.</p>
<p>따라서 그림 49와 같이 우리는 최종 저장소 이전에 데이터를 처리할 수 있는 DataLogic서비스를 추가하였다. 센서 네트워크에서는 측정값과 각 Node의 고유ID만 관리하고 DataLogic서비스에서 데이터 처리 서비스(데이터에 등록정보를 추가)를 지원한다. 해당 서버는 측정값을 이용해 등록된 이벤트를 처리하는 기능도 지원하는데, 이는 다음에 설명하겠다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-29.png" rel="lightbox[40437]"><img class="alignnone size-full wp-image-40633" alt="67 ict 최우수상_스마트파크 (29)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-29.png" width="508" height="472" /></a></p>
<p>여러 공원의 데이터 값을 하나의 데이터 처리 서비스로 전송하는 것 또한 데이터 처리 서비스에 너무 많은 부담을 준다. 그렇다고 여러 개의 데이터 처리 서비스를 구현하기에는 구성이 너무 복잡해지며, 특정 서버에 요청이 몰릴 경우 몇몇 요청은 처리하지 못하고 누락될 수 있다.<br />
따라서 센서네트워크와 서버 사이에 Kafka를 배치해주었다. Kafka는 데이터를 임시로 저장하는 Queue역할을 하며 기본적으로 클러스터 환경을 제공하여 높은 처리량과 고가용성을 보장한다. DataLogic서비스는 자신이 필요할 때만 Kafka에 데이터를 요청하기 때문에 과부하로 문제가 될 가능성이 배제된다.</p>
<p><strong>구성</strong></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-30.png" rel="lightbox[40437]"><img class="alignnone size-full wp-image-40634" alt="67 ict 최우수상_스마트파크 (30)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-30.png" width="507" height="370" /></a></p>
<p><span style="color: #99ccff;background-color: #ffffff">Kafka</span><br />
producer &#8211; Broker(Kafka) &#8211; Consumer 구조의 스트림 처리 플랫폼이다. 여러 공원으로부터 데이터를 저장받아 임시저장한 후 DataLogic서비스에 안정적으로 데이터를 제공한다.<br />
라즈베리파이에서 카프카로 전송되는 데이터의 예시는 오른쪽 그림과 같다.</p>
<p><span style="color: #99ccff"><strong>DataLogic</strong></span><br />
서버 세부설명 파트에서 자세히 설명한다.</p>
<p><span style="color: #99ccff"><strong>엘라스틱서치</strong></span><br />
방대한 센서데이터를 지속적으로 저장하기 위해서 Write능력이 뛰어난 데이터 베이스가 필요하다. 따라서 Write 효율에 중점을 둔 NoSQL데이터베이스인 Elasticsearch를 데이터 베이스로 사용하였다.<br />
Elasticsearch에 준 실시간으로 저장된 센서 데이터는 비교/분석을 위한 시각화와 공공데이터 배포를 위해 사용된다.</p>
<p><span style="color: #99ccff"><strong>Kibana</strong></span><br />
Paul Brunet의 InfoWorld 칼럼에 따르면 요즘 시대는 더 많은 데이터에 접속할 수 있는 상태지만, 이런 수많은 데이터에서 효과적으로 인사이트를 획득하는 능력은 감소했다고 한다. 이제 우리가 고민해야 할 지점은 ‘데이터를 어떻게 잘 활용할 것인가?’이다. 많은 양의 데이터를 한눈에 볼 수 있고, 데이터 분석에 대한 전문 지식이 없어도, 누구나 쉽게 데이터의 패턴을 알아낼 수 있도록 시각화된 데이터 대시보드를 구현하였다.</p>
<p><span style="color: #00ccff"><strong>서버 상세설명</strong></span></p>
<p><span style="color: #00ccff"><strong><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-31.png" rel="lightbox[40437]"><img class="alignnone  wp-image-40635" alt="67 ict 최우수상_스마트파크 (31)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-31-620x476.png" width="520" /></a></strong></span></p>
<p><span style="color: #99ccff"><strong>서버 아키텍쳐</strong></span><br />
클린 아키텍처를 적용하여 특정 계층에 대한 수정이 다른 계층에 거의 영향을 주지 않도록, 그리고 같은 계층 내에서는 일관되고 응집력 있는 결합을 제공할 수 있도록 서버의 계층을 구분하였다. 상당히 분량이 많은 앱이더라도 소스코드 전반을 쉽게 파악할 수 있고 복잡한 수정 사항이 생겼을 때라도, 어떤 부분들을 고치면 되는지 금방 파악할 수 있도록 하였다.</p>
<p><strong>Application &#8211; Registration</strong><br />
<span style="color: #99ccff"><strong>개요</strong></span><br />
등록 정보를 관리하기 위한 API Server이다. 프론트엔드에서 등록한 메타데이터, 로직데이터 등을 백엔드의 데이터 베이스에 저장하고, 이 데이터 베이스를 읽어서 프론트엔드, DataLogic, HealthCheck 등의 서비스에 정보를 제공 및 컨트롤하는 역할을 수행한다.</p>
<p><span style="color: #99ccff"><strong>구성</strong></span><br />
모든 서비스는 이용자가 어떤 기능을 요구하거나, 개발자가 어떤 아이디어를 생각해내 서비스를 확장시켜야 하는 상황이 생기기 마련이다. 이때를 대비해 유연하게 확장이 가능하도록 서버를 구성하여야 한다. 스마트파크의 모든 서버는 각 기능 단위로 서비스를 만들어 큰 서비스를 구성하는 방식인 마이크로 서비스 아키텍처로 구성하였다.</p>
<p><span style="color: #99ccff"><strong>기능</strong></span><br />
스마트파크를 구성하는 마이크로 서비스들을 컨트롤하는 역할을 한다. Registration은 센서 데이터 스트림 처리를 담당하는 마이크로서비스(DataLogic)와 센서 데이터 스트림 노드(아두이노)의 헬스 상태 처리를 담당하는 마이크로서비스(HealthCheck)에게 등록정보를 제공한다. 이후 등록정보의 변경이 발생할 경우에도 마이크로 서비스 들에게 등록 정보를 제공해준다.</p>
<p><span style="color: #99ccff"><strong>DB</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-32.png" rel="lightbox[40437]"><img class="alignnone size-large wp-image-40636" alt="67 ict 최우수상_스마트파크 (32)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-32-620x315.png" width="620" height="315" /></a></p>
<p>스마트파크의 데이터베이스는 위 사진과 같이 스키마가 정의되어있다. 싱크 노드, 센서 노드 등의 메타데이터를 구조적으로 저장 및 조회한다. 데이터베이스로 쓰인 애플리케이션은 mySql이다.</p>
<p><strong>LogicCore &#8211; DataLogic</strong><br />
DataLogic서비스는 Kafka에서 데이터를 읽어온 뒤 등록정보를 바탕으로 메타데이터를 추가한다. 보강된 데이터를 기반으로 사용자가 지정한 이벤트의 처리동작과 Elasticsearch로의 데이터 저장을 수행한다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-33.png" rel="lightbox[40437]"><img class="alignnone size-large wp-image-40637" alt="67 ict 최우수상_스마트파크 (33)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-33-620x319.png" width="620" height="319" /></a></p>
<p><span style="color: #99ccff"><strong>등록정보 추가</strong></span></p>
<p><span style="color: #99ccff"><strong><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-34.png" rel="lightbox[40437]"><img class="alignnone size-large wp-image-40638" alt="67 ict 최우수상_스마트파크 (34)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-34-620x390.png" width="620" height="390" /></a></strong></span><br />
DataLogic서버는 처음 실행될 때 Registration 서버에 등록정보를 요청한다. Registration서버는 요청한 DataLogic서비스를 자신의 서버 내에 등록한 후 서버 내 DB에 저장된 Sink node와 Sensor node의 등록정보들을 반환해 주고 DataLogict는 이 등록정보를 서버의 메모리 내에 보관한다.<br />
DataLogic서비스가 실행되고 있는 도중에 등록정보가 변경될 경우 Registration서버는 업데이트된 정보를 등록된 모든 DataLogic서비스에게 알려주어 등록정보를 동기화할 수 있도록 한다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-35.png" rel="lightbox[40437]"><img class="alignnone size-large wp-image-40639" alt="67 ict 최우수상_스마트파크 (35)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-35-620x395.png" width="620" height="395" /></a></p>
<p>센서 데이터마다 적절한 메타데이터를 얻기 위해 DataLogic서비스에서 DB에 접근할 수 없기 때문에 등록정보를 메모리에 동기화시킨다. 이후 Kafka에서 읽어온 센서 데이터에 sensor-id, node-id에 해당하는 메타데이터를 추가한다.</p>
<p><span style="color: #99ccff"><strong>이벤트 처리</strong></span><br />
수집한 센서 데이터에서 프론트엔드에서 유저가 미리 지정해둔 특정한 이벤트를 검사하기 위한 기능이다. JSON 형식의 센서 데이터의 특정 필드 값을 기준으로 조건을 검사하는 filter기능과 actuator동작신호 전송, Email전송 등 특정 이벤트를 수행하는 action기능으로 구성되어있다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-36.png" rel="lightbox[40437]"><img class="alignnone size-large wp-image-40640" alt="67 ict 최우수상_스마트파크 (36)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-36-620x414.png" width="620" height="414" /></a></p>
<p><span style="color: #99ccff"><strong>Elastic search저장</strong></span></p>
<p>DataLogic서비스 내에서 수많은 (보강된)센서 데이터들이 생성된다. 이 데이터 하나당 Indexing(Write) API를 호출하면 네트워크에 과부하가 걸리므로 이를 방지하기 위해 데이터를 효율적으로 저장하여야 한다.<br />
따라서 저장할 데이터를 적재하다가 특정 byte 이상이 쌓이면 한 번에 Bulk Indexing(배치 처리)하도록 구현하였다. 또한, Elastic search의 Sharding기능을 통해 자체적으로 쓰기 성능을 향상시켰다.</p>
<p><span style="color: #00ccff"><strong>HealthCheck</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-37.png" rel="lightbox[40437]"><img class="alignnone size-large wp-image-40641" alt="67 ict 최우수상_스마트파크 (37)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-37-578x620.png" width="578" height="620" /></a></p>
<p>그림 60과 같이 수많은 노드의 정상 작동여부와 배터리 잔량을 전송받아 각 노드의 헬스 상태를 결정한 후 헬스 상태가 변경된 노드의 헬스 정보와 배터리 잔량을 프론트엔드로 전송하는 역할을 수행한다.<br />
먼저 라즈베리파이로부터 연결된 모든 아두이노의 상태 정보를 bool 형태로 전달받는다. 전달받은 상태정보를 이용해 헬스 정보를 업데이트한 후 그림 61과 같이 헬스 정보가 기록된 Map 자료구조에 저장한 후, 업데이트 내역이 있는 아두이노의 헬스정보를 추려내어 웹소켓을 통해 프론트엔드로 전송한다. 배터리 잔량은 받은 값을 그대로 전송한다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-38.png" rel="lightbox[40437]"><img class="alignnone size-large wp-image-40642" alt="67 ict 최우수상_스마트파크 (38)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-38-620x174.png" width="620" height="174" /></a><br />
헬스 정보 업데이트 기준은 전달받은 상태정보와 가장 최근의 상태를 위 사진과 같은 기준으로 비교해서 결정한다.</p>
<p><span style="color: #ff9900"><strong>3.3.3. 도커(Docker)</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-39.png" rel="lightbox[40437]"><img class="alignnone size-large wp-image-40643" alt="67 ict 최우수상_스마트파크 (39)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-39-620x408.png" width="620" height="408" /></a></p>
<p>플랫폼 서비스는 고객(서비스 운영자)이 플랫폼의 모든 실행 환경을 손쉽게 사용자의 환경으로 가져올 수 있어야 한다. 이를 위해 스마트파크의 모든 서버는 도커 컨테이너 위에서 실행가능하게 도커 이미지로 변환하여 배포한다. 도커 컨테이너(단일 컨트롤 호스트 상에서 여러개의 고립된 리눅스 시스템들을 실행하기 위한 운영 시스템 레벨 가상화 방법)를 이용하면 서버 환경을 재구축하는 부가적인 작업 없이 개발 당시의 환경(라이브러리, 설정값, 종속성 및 파일)을 로컬에서 애뮬레이션할 수 있다. 다시 말해 큰 수정 없이 어느 환경에서든 모든 서버가 작동되도록 배포할 수 있다.</p>
<p><span style="color: #ff9900"><strong>3.3.4. 센서 네트워크(NodeMCU-Raspberry Pi-싱크노드)</strong></span><br />
<span style="color: #00ccff"><strong>백엔드에 데이터가 도달하기 전까지의 상세구조 그림</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-40.png" rel="lightbox[40437]"><img class="alignnone size-large wp-image-40644" alt="67 ict 최우수상_스마트파크 (40)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-40-620x419.png" width="620" height="419" /></a></p>
<p><span style="color: #00ccff"><strong>오픈소스 사용 (Flask와 MQTT통신 설명)</strong></span><br />
<span style="color: #99ccff"><strong>싱크노드-Flask 사용</strong></span><br />
싱크노드가 웹서버를 운영함으로써 서버로부터 HTTP요청을 받을 수 있다. 서버는 특정 싱크노드에 HTTP 요청을 하여 액추에이터 명령을 내리게 된다. 싱크노드(네트워크 서버)에서 웹서버를 운영하기 위해 Flask 오픈소스를 이용했다.</p>
<p><span style="color: #99ccff"><strong>Paho MQTT Client 사용</strong></span><br />
IoT 통신은 경량화와 유연성을 갖추어야 한다. 여러 통신 방법 중 MQTT는 M2M, IoT를 위한 프로토콜로서, 최소한의 전력과 패킷량으로 통신하는 프로토콜로 IoT통신에 적합하다. MQTT 통신을 위해서는 Paho client 오픈소스를 이용했다. 따라서 우리는 게이트웨이와 싱크노드간에 Mosquitto 브로커와 MQTT 통신을 이용해서 경량화된 통신을 이용한다.</p>
<p><strong><span style="color: #00ccff">LoRaWAN 프로토콜 이용</span></strong><br />
<span style="color: #99ccff"><strong>Class A와 C</strong></span><br />
LoRaWAN 프로토콜에는 세 가지 A, B, C 클래스의 디바이스 유형이 있다.<br />
센서 네트워크에서 끝단에 위치한 센서노드들의 클래스를 이 세 가지 A, B, C 유형 중 하나를 지정하여 사용하게 된다. 현재 구현하여 이용이 가능한 센서노드의 클래스는 A와 C 두가지다.<br />
A Class 통신 방식은 LoRa Device와 LoRa Gateway 사이에서 LoRa Device가 Gateway에게 메시지를 전송한 이후, 잠시 동안 두 번에 걸쳐 메시지 수신이 허용되는 방식이다. 송신하기 전에는 송신과 수신 모두 동작을 하지 않다가 송신이 진행되면, 그 순간에 잠시 동안 두 번에 수신 윈도우를 허용할 기회를 제공한다. 즉 A Class는 수신을 위해서 송신이 진행되어야 하며 송신 위주의 서비스에 주로 사용된다. 상시전원을 사용하지 않고 배터리로 운영하는 경우에 사용되기 때문에 가장 적은 전력을 사용하는 Class이다.<br />
C Class 통신 방식은 수신 가능 상태를 유지하기 위해 다른 Class에 비해 최소 지연시간을 갖는다. 송신 시간을 제외하고는 수신을 계속 대기하는 상태가 지속되므로 세 가지 Device Class 중에서 가장 많은 전력을 소비한다. 그 때문에 충분한 전력 공급이 가능한 상황에서 이용되는 Device Class이다. 대부분의 순간 수신상태를 유지하기 때문에 수신이 중요한 서비스(예를 들면 Actuator 기능 및 원격제어)에 이용된다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-41.png" rel="lightbox[40437]"><img class="alignnone size-large wp-image-40645" alt="67 ict 최우수상_스마트파크 (41)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-41-620x366.png" width="620" height="366" /></a></p>
<p><strong>OTAA 활성화 방법(Join과정 및 암호화 방법)</strong><br />
새로운 최종 장치 (LoRa 장치)가 LoRa 네트워크에 추가되려면 활성화 프로세스를 거쳐야 한다. LoRaWAN 프로토콜에는 ABP 통신과 OTAA통신 두가지 활성화 방법이 존재한다. 현재 OTAA모드를 기준으로 작동되도록 구현되어있다.<br />
센서노드가 가입 요청 메시지를 보내 가입 절차를 시작한다. 참여 요청에 DevEUI, AppEUI 및 DevNonce가 포함된다. DevEUI 및 AppEUI는 각각 Global End Device 및 Application Identifier를 나타내며 IEEE 나타내며 EUI-64 주소 공간 형식을 따른다. 우리 프로젝트에서는 센서노드의 EUI 주소값은 Arduino 코드에 하드코딩하여 지정한다. Join 활성화 과정에서는 App Key를 이용하여 AES-128 암호화하며, NwkSKey와 App_SKey가 활성화 과정으로 인해 생성된 이후에는 이 둘 중 하나를 이용하여 메시지를 AES-128 암호화한다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-42.png" rel="lightbox[40437]"><img class="alignnone size-large wp-image-40646" alt="67 ict 최우수상_스마트파크 (42)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-42-620x408.png" width="620" height="408" /></a></p>
<p>위 그림은 OTAA모드 활성화 프로세스 과정을 나타낸다. AppKey는 최종 장치와 서버간에 미리 공유되는 Key값이다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-43.png" rel="lightbox[40437]"><img class="alignnone size-large wp-image-40647" alt="67 ict 최우수상_스마트파크 (43)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-43-620x105.png" width="620" height="105" /></a></p>
<p>왼쪽 그림은 Join Request 메시지의 구조이다. End Device는 다음의 요청을 보낸다.<br />
Join Request 메시지는 암호화되지 않는다. Dev Nonce는 엔드 장치에서 랜덤하게 생성된 값으로 이전에 수신한 요청의 DevNonce 값과 중복되는 Dev Nonce의 요청은 받아들이지 않아 공격을 방지한다.<br />
오른쪽 그림은 Join Accept 메시지의 구조이다. 메시지를 App Key로 AES-128 암호화한다.</p>
<p><strong>메시지의 프레임 구조</strong></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-44.png" rel="lightbox[40437]"><img class="alignnone size-large wp-image-40648" alt="67 ict 최우수상_스마트파크 (44)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-44-620x369.png" width="620" height="369" /></a></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-45.png" rel="lightbox[40437]"><img class="alignnone size-large wp-image-40649" alt="67 ict 최우수상_스마트파크 (45)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-45-597x620.png" width="597" height="620" /></a></p>
<p><strong>구현한 MAC Command</strong><br />
LoRaWAN은 다음과 같은 Mac Command, Command ID를 갖도록 되어있다. DevStatusReq와 DevStatusAns 명령을 이용하여 End-Device의 배터리 수준과 SNR 수신 신호 상태 값을 얻을 수 있다.<br />
우리 프로젝트에서는 RFU 영역인 CID 0x0E를 액추에이터 작동을 지시하는 명령(ActuatorReq, ActuatorAns)으로 따로 구현하여 사용하도록 했다.</p>
<p><strong>Network Server(Sink Node) 동작 및 기능</strong><br />
<span style="color: #99ccff"><strong>데이터 전송</strong></span><br />
싱크노드는 여러 게이트웨이에서 mosquitto 브로커를 통해 mqtt통신으로 들어온 센서노드별 데이터를 취합하여, 이를 백엔드 측의 데이터 큐인 Kafka로 전송해주는 역할을 한다.</p>
<p><span style="color: #99ccff"><strong>헬스체크를 위한 센서노드 상태체크 명령 전달</strong></span><br />
또한 센서노드들의 상태를 체크하는 것은 IoT플랫폼의 기능에 있어서 필수적이다.<br />
싱크노드에서는 주기적으로 센서노드들에게 DevStatusReq요청을 보내도록 “command/downlink/ActuatorReq/&lt;센서노드ID&gt;”토픽으로 MQTT 메시지를 발행하여 게이트웨이에서 센서노드들에게 DevStatusReq Mac Command를 전달하도록 한다. DevStatusAns을 정상적으로 답장한 센서노드들의 상태를 “정상”으로 판단하고 이를 백엔드의 헬스체크 서버에 알려준다.<br />
이러한 상태 체크 과정에서 배터리 상태와 SNR 수신 신호에 대한 정보를 알 수 있으며, 백엔드 서버에는 각 센서노드의 상태와 배터리 수준을 함께 알려준다.</p>
<p><span style="color: #99ccff"><strong>액추에이터 명령 수신 및 게이트웨이로 전달</strong></span><br />
백엔드 서버로부터 오는 명령으로 LED 또는 모터들을 제어할 수 있어야 한다. 싱크노드에서는 백엔드로부터 오는 명령을 Flask 웹서버에 들어온 HTTP요청으로 수신한다.<br />
Flask 웹서버로 수신한 HTTP 액추에이터 요청을 json 데이터와 함께 수신하면 싱크노드는 이를 “command/downlink/ActuatorReq/&lt;센서노드ID&gt;”토픽으로 MQTT 메시지를 발행한다. 센서노드 측에서는 명령을 받기 위해 “command/downlink/+/&lt;센서노드 ID&gt;”를 구독했기 때문에 MQTT프로토콜을 통해 액추에이터 명령을 받을 수 있다.</p>
<p><span style="color: #008080"><strong>3.4. 개발 환경</strong></span><br />
<span style="color: #ff9900"><strong> 3.4.1 개발언어 및 라이브러리</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-46.png" rel="lightbox[40437]"><img class="alignnone size-large wp-image-40650" alt="67 ict 최우수상_스마트파크 (46)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-46-507x620.png" width="507" height="620" /></a></p>
<p><span style="color: #ff9900"><strong>3.4.2. 포트 포워딩</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-47.png" rel="lightbox[40437]"><img class="alignnone size-full wp-image-40651" alt="67 ict 최우수상_스마트파크 (47)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-47.png" width="506" height="264" /></a></p>
<p><span style="color: #0000ff"><strong>4. 단계별 제작 과정</strong></span><br />
<span style="color: #008080"><strong> 4.1. 센서네트워크 구축</strong></span><br />
<span style="color: #ff9900"><strong> 4.1.1. 싱크노드 구현</strong></span><br />
싱크노드의 메인코드에서의 동작 순서이다.</p>
<p style="padding-left: 30px">1. Flask 웹서버 실행<br />
2. Kafka Producer 생성<br />
3. mqtt 클라이언트 생성<br />
4. HealthCheck를 담당, 관리하는 클래스 생성<br />
5. Actuator관련 동작을 담당하는 클래스 생성<br />
6. MQTT Callback함수 등록 및 MQTT Client의 Loop 시작<br />
7. HealthCheck를 위한 TCP 소켓 생성<br />
8. 쓰레드 생성-&gt;쓰레드 핸들러로 HealthCheck를 담당하는 함수 등록<br />
9. 헬스체크 쓰레드 시작</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-48.png" rel="lightbox[40437]"><img class="alignnone size-full wp-image-40652" alt="67 ict 최우수상_스마트파크 (48)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-48.png" width="506" height="294" /></a></p>
<p><strong><span style="color: #ff9900">4.1.2. NodeMCU LoRa 연결</span></strong><br />
사용하는 LoRa 모듈(SX-1278)의 Pin 구성은 다음과 같다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-49.png" rel="lightbox[40437]"><img class="alignnone size-full wp-image-40653" alt="67 ict 최우수상_스마트파크 (49)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-49.png" width="280" height="189" /></a></p>
<p>ANT에는 안테나를 연결한다. SCK는 SPI-Clock Input을 담당한다. MISO는 SPI-Data Out을 담당한다. MOSI는 SPI-Data In을 담당한다. NSS는 SPI-Chip Select를 담당한다.<br />
LoRa 통신 모듈과 NodeMCU간의 연결은 다음의 표와 같다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-50.png" rel="lightbox[40437]"><img class="alignnone size-full wp-image-40575" alt="67 ict 최우수상_스마트파크 (50)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-50.png" width="505" height="261" /></a></p>
<p>나머지 디지털 핀과 아날로그 핀들은 센서나 LED, 모터제어를 위해 사용한다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-51.png" rel="lightbox[40437]"><img alt="67 ict 최우수상_스마트파크 (51)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-51.png" width="258" height="390" /></a></p>
<p><span style="color: #ff9900"><strong>4.1.3. NodeMCU</strong></span><br />
<strong>LoRaWAN 코딩</strong><br />
NodeMCU의 LoRaWAN은 https://git.antares.id/lorawan-loraid/arduino-loraid의 코드를 뼈대로 하여 개선하였다. (지원가능한 주파수 채널 확장, MacCommand처리기능 추가, 수신이 되지 않는 문제 수정.)</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<table style="width: 620px" border="0" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-52.png" rel="lightbox[40437]"><img alt="67 ict 최우수상_스마트파크 (52)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-52.png" width="318" height="370" /></a></td>
<td><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-53.png" rel="lightbox[40437]"><img class="alignnone size-large wp-image-40578" alt="67 ict 최우수상_스마트파크 (53)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-53-327x620.png" width="327" height="620" /></a></td>
</tr>
</tbody>
</table>
<p>다음은 LoRaWAN통신 코드이다.</p>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p>&nbsp;</p>
<p>#include &#8220;ToIoTwithLoRaWAN.h&#8221;<br />
#include &#8220;config.h&#8221;<br />
const sRFM_pins RFM_pins = {<br />
.CS = 10,<br />
.RST = 9,<br />
.DIO0 = 2,<br />
.DIO1 = 3,<br />
.DIO2 = -1,<br />
.DIO5 = -1,<br />
};ToIoTwithLoRaWAN t;<br />
double value = 0.0;<br />
void setup() {<br />
t.setupToIoTwithLoRaWAN(nodeId, interval, 0);<br />
// LoRaWAN //LoRaWAN Setting<br />
/*~~~중략~~~*/<br />
// Join procedure<br />
bool isJoined;<br />
do {<br />
Serial.println(&#8220;Joining&#8230;&#8221;);<br />
isJoined = lora.join();<br />
//wait for 3s to try again<br />
delay(3000);<br />
}while(!isJoined);<br />
Serial.println(&#8220;Joined to network&#8221;);<br />
}</p>
<p>void loop() {<br />
t.pub(&#8220;sensor-uuid-1&#8243;, 1, value);<br />
t.rcv();<br />
wdt_reset();<br />
}</p>
</div>
<p>현재 타이머 시간과 과거에 측정한 타이머 시간이 interval보다 크다는 것은 interval만큼 타이머 시간이 흘렀다는 것을 의미한다. 따라서 interval만큼의 시간을 주기로 하여 메시지를 송신하는 것이다.<br />
QOS 변수는 사용자가 설정하는 값으로 1이상일 때, ACK를 수신해야지만 다음 새로운 메시지를 전송하도록 한다. QOS==0 이라면, ACK 수신여부와 관계없이 새로운 메시지를 계속 전송한다.<br />
앞으로 센서노드는 위 LoRaWAN 통신코드를 베이스로 한다. 센서, 액추에이터에 대한 코드를 이 코드에 추가하여 가로등, 수목관리, 쓰레기통을 구현한다.</p>
<p><span style="color: #ff9900"><strong>4.1.4. 싱글채널 게이트웨이 제작과정</strong></span><br />
기존의 LoraWAN 게이트웨이를 국내에서 접하기는 어렵게 되어 있어서 접하기 쉽도록 라즈베리 파이를 이용한 싱글채널 게이트웨이를 직접 구현하기로 했다.<br />
LoRa 통신을 가능하게 해주는 LoRa 통신 모듈을 연결하기 위해 왼쪽 사진과 같이 납땜 작업을 했다. 오른쪽 사진과 같이 안테나를 통신 모듈에 연결하고 각 핀을 라즈베리파이에 연결했다.<br />
Lora 모듈과 라즈베리파이는 아래와 같이 연결했다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-54.png" rel="lightbox[40437]"><img class="alignnone size-full wp-image-40579" alt="67 ict 최우수상_스마트파크 (54)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-54.png" width="566" height="297" /></a></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-55.png" rel="lightbox[40437]"><img class="alignnone size-full wp-image-40580" alt="67 ict 최우수상_스마트파크 (55)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-55.png" width="570" height="292" /></a></p>
<p><span style="color: #ff9900"><strong>4.1.5. LoRa 게이트웨이(라즈베리파이) LoRaWAN 코딩</strong></span><br />
게이트웨이의 LoRaWAN 프로토콜을 다음과 같이 구조화하여 파이썬으로 구현했다.</p>
<p><span style="color: #00ccff"><strong>LoRaWAN 프로토콜 전체적인 코드 구성</strong></span><br />
다음은 Python으로 구현한 LoRaWAN프로토콜의 라이브러리 구조이다.<br />
· AES_CMAC.py: AES-128 암호화를 위한 함수들을 정의했다.<br />
· CID.py: Mac Command들의 Command ID를 정의했다. MacCommand처리를 위한 함수들을 정의했다.<br />
· Channel.py: 사용할 주파수 채널을 선택할 수 있도록 채널에 대한 정보들을 정의했다.<br />
· DataPayload.py: 데이터 페이로드(FRM Payload)를 처리하기 위한 클래스와 메소드를 정의했다.<br />
· Direction.py: Mac Header(MHDR)의 각 메시지들이 어떤 방향으로 향하는 메시지인지 정의하는 클래스와 메소드이다. (UP: 센서노드-&gt;게이트웨이 / DOWN: 게이트웨이-&gt;센서노드)<br />
· FHDR.py: Frame Header에 대한 클래스와 메소드를 정의한다.<br />
· JoinAcceptPayload.py: JoinAccept에 대한 메시지를 생성하거나 읽기 위한 Payload 처리코드<br />
· JoinRequestPayload.py:JoinRequest에 대한 메시지를 생성하거나 읽기 위한 Payload처리코드<br />
· LoRaMAC.py: 작업 상태를 정의하는 코드. (수신상태, 송신상태, 대기상태)<br />
· MHDR.py: Mac Header 정보들을 정의하는 클래스와 메소드<br />
· MacCommandPayload.py: MacCommand를 Frame Payload에 넣어서 전달하기 위한 코드<br />
· MacPayload.py: Mac Payload를 생성하거나 읽기 위한 코드<br />
· MalformedPacketException: 패킷 예외 처리에 대한 코드<br />
· PhyPayload.py: Physical Payload를 생성하거나 읽기 위한 코드</p>
<p><span style="color: #00ccff"><strong>LoRa WAN 프로토콜 코드 주요동작 순서</strong></span><br />
일반적인 데이터 수신과 송신의 Payload 처리는 다음과 같은 순서로 동작한다.<br />
PhyPayload.py-&gt;MacPayload.py-&gt;DataPayload.py (주요 코드 순서, 다른 코드는 생략) : 전원이 켜진 센서노드는 네트워크에 접속하기 위해 주기적으로 JoinRequest 신호를 전송한다.<br />
JoinRequest를 수신하게 되어 읽는 경우에는 다음과 같은 순서로 동작한다. : PhyPayload.py-&gt;MacPayload.py-&gt;JoinRequestPayload.py<br />
JoinAccept를 송신하는 경우에는 다음과 같은 순서로 동작한다. : PhyPayload.py-&gt;MacPayload.py-&gt;JoinAcceptPayload.py<br />
MacPayload를 송신하는 경우에는 다음과 같은 순서로 동작한다. : PhyPayload.py-&gt;MacPayload.py-&gt;MacCommandPayload.py</p>
<p><span style="color: #00ccff"><strong>센서노드(NodeMCU)와 게이트웨이간 LoRa통신 확인</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-56.png" rel="lightbox[40437]"><img class="alignnone size-large wp-image-40581" alt="67 ict 최우수상_스마트파크 (56)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-56-515x620.png" width="515" height="620" /></a></p>
<p>위와 같이 센서노드와는 LoRaWAN통신을 하고 싱크노드와는 MQTT 통신을 한다.</p>
<p><span style="color: #ff9900"><strong>4.1.6. 헬스체크 구현</strong></span><br />
헬스체크는 센서노드의 상태를 체크하는 기능이다. 싱크노드(네트워크 서버)는 LoRaWAN 프로토콜의 Mac Command인 DevStatusReq를 센서노드로 주기적으로 전송하게 한다. 이를 주기적으로 처리하기 위해 HealthCheck 담당 쓰레드를 하나 만들어서 주기적으로 게이트웨이에게 명령을 전달해주고 TCP소켓을 열어 백엔드 측의 헬스체크 서버와 통신하도록 했다. 센서노드로부터 DevStatusAns의 응답을 정상적으로 수신하면 센서노드의 상태를 정상으로 판단하고 그렇지 않으면 불량으로 판단한다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-57.png" rel="lightbox[40437]"><img class="alignnone size-full wp-image-40582" alt="67 ict 최우수상_스마트파크 (57)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-57.png" width="566" height="122" /></a></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-58.png" rel="lightbox[40437]"><img class="alignnone size-large wp-image-40583" alt="67 ict 최우수상_스마트파크 (58)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-58-555x620.png" width="555" height="620" /></a></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-59.png" rel="lightbox[40437]"><img class="alignnone size-full wp-image-40584" alt="67 ict 최우수상_스마트파크 (59)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-59.png" width="566" height="144" /></a></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p><span style="color: #ff9900"><strong>4.1.7. 액추에이터 구현</strong></span><br />
센서노드가 액추에이터 명령인 ActuatorReq를 수신하면 받은 액추에이터 ID를 타켓으로 동작시켜야 한다. 액추에이터 명령 패킷은 다음과 같은 Payload를 갖는다.<br />
명령 메시지 구조 : Actuator ID, Sleep, Value, Sleep, Value, …<br />
Actuator ID는 동작시킬 액추에이터의 ID를 의미한다.(각각의 액추에이터는 자신의 ID를 갖는다.)<br />
Sleep은 액추에이터에 값을 넣어 실행시키기까지의 대기시간을 의미한다.<br />
Value는 액추에이터를 동작시키기 위한 값을 의미한다.<br />
아래는 액추에이터를 관리하기 위한 Actuator 구조체 정의부이다.</p>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p>struct Actuator{<br />
int actuatorId;<br />
int value[6];<br />
unsigned long interval[6];<br />
unsigned long previousMillis= 0;<br />
short int running_index=0;<br />
bool run= false;<br />
short values_len= 0;<br />
};</p>
<p>&nbsp;</p>
</div>
<p>위에서 받은 액추에이터 명령 메시지를 위와 같은 구조체에 입력하여 관리하게 된다.</p>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p>&nbsp;</p>
<p>Void Loop(){<br />
If(millis() &#8211; previousMillis &gt; interval)<br />
{<br />
PreviousMillis = millis();<br />
~~~~~~~~~~<br />
}<br />
}</p>
</div>
<p>액추에이터에서 delay가 발생하더라도 LoRa통신에는 영향이 없도록 하기 위해 다음과 같이 루프문과 타이머를 이용하는 구조로 코드를 작성했다. 위의 코드에서 interval은 대기시간이다. previousMillis는 전에 저장한 타이머 시간을 의미한다. Millis() 함수는 현재 타이머 시간을 의미한다.<br />
즉, 위의 코드는 타이머 시간을 측정하여 interval에 저장된 시간만큼 지났을 때, if문 내부의 코드가 실행되는 구조이다. 액추에이터 동작 함수에도 LoRa에 지장이 가지 않도록 하기 위해 위의 구조를 적용했다.<br />
액추에이터의 경우 각자의 구조체 안에 자신만의 대기시간을 저장하는 interval 변수를 갖도록 하여 개별적으로 동작할 수 있다.</p>
<p><span style="color: #ff9900"><strong>4.1.8. 가로등 제작 과정</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-60.png" rel="lightbox[40437]"><img class="alignnone size-full wp-image-40585" alt="67 ict 최우수상_스마트파크 (60)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-60.png" width="567" height="259" /></a><br />
먼저 가로등의 뼈대가 되는 모형에 4&#215;2 LED를 부착하고 LED의 밝기를 조절했다. LED의 밝기는 analogWrite()함수로 아날로그 신호를 주면 조절할 수 있다. 위 사진과 같이 밝기 조절을 테스트했다.<br />
다음으로 보행자 통행량을 측정하기 위한 PIR모션센서를 테스트했다. PIR 센서를 다음과 같이 LED 옆에 부착하고 NodeMCU와 연결하여 테스트했다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-61.png" rel="lightbox[40437]"><img class="alignnone size-full wp-image-40586" alt="67 ict 최우수상_스마트파크 (61)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-61.png" width="569" height="318" /></a></p>
<p>일정 간격의 시간동안 모션이 감지되면 위와 같이 Welcome!이 출력된다.<br />
보행자 통행량 측정에 이 PIR 센서를 활용하기 위해서 주기적으로 이 모션이 감지되는 횟수가 얼마나 많은지 카운트를 누적하여 누적한 카운트 값을 백엔드 서버에 전해준다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-2.jpg" rel="lightbox[40437]"><img class="alignnone size-full wp-image-40603" alt="67 ict 최우수상_스마트파크 (2)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-2.jpg" width="565" height="293" /></a></p>
<p>위는 바닥과 상단에 부착된 조도센서 사진이다. 왼쪽 사진은 부점등 확인을 위해 조도 센서를 가로등 바닥부분에 부착하였다. 기판에 저항과 cds센서를 납땜하여 연결했다. 오른쪽 사진은 밝기를 측정하기 위해 상단에 조도 센서를 부착하였다. 상단의 조도센서는 밤에 자동 점등, 아침에 자동 불을 끄기위해 달았다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-62.png" rel="lightbox[40437]"><img class="alignnone size-full wp-image-40587" alt="67 ict 최우수상_스마트파크 (62)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-62.png" width="569" height="242" /></a></p>
<p>아날로그 핀에 연결하여 조도 센서값을 확인했다.<br />
다음은 가로등 센서노드의 메인 코드이다.</p>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p>&nbsp;</p>
<p>#include &#8220;ToIoTwithLoRaWAN.h&#8221;<br />
#include &#8220;config.h&#8221;<br />
/*~~~중략~~~*/<br />
ToIoTwithLoRaWAN t;<br />
double value = 0.0;<br />
// Actuator 2<br />
struct Actuator a2;<br />
Servo myservo;/<br />
/ PIR, CdS, LED 관련 핀 제어변수 선언<br />
/*~~~중략~~~*/<br />
void setup() {<br />
pinMode(pirPin, INPUT);<br />
pinMode(cdsPin1, INPUT);<br />
pinMode(cdsPin2, INPUT);<br />
pinMode(ledPin, OUTPUT);<br />
t.setupToIoTwithLoRaWAN(nodeId, interval, 0);<br />
// LoRaWAN Setting<br />
/*~~~중략~~~*/<br />
// Actuator setting a2.actuatorId = 2;<br />
// LED actuator<br />
// Join procedure bool isJoined;<br />
do {<br />
Serial.println(&#8220;Joining&#8230;&#8221;);<br />
isJoined = lora.join();<br />
//wait for 3s to try again<br />
delay(3000); }<br />
while(!isJoined);<br />
Serial.println(&#8220;Joined to network&#8221;);<br />
}<br />
void loop() {<br />
cdsVal1 = analogRead(cdsPin1);<br />
cdsVal2 = analogRead(cdsPin2);<br />
pirVal = digitalRead(pirPin);<br />
if (pirVal == HIGH) {<br />
if (pirState == LOW){<br />
Serial.println(&#8220;Welcome!&#8221;); // 시리얼 모니터 출력<br />
pirCnt=pirCnt+1.0;<br />
pirState = HIGH;<br />
}<br />
}<br />
else {<br />
if (pirState == HIGH){<br />
Serial.println(&#8220;Good Bye~&#8221;); // 시리얼 모니터 출력<br />
pirState = LOW;<br />
}<br />
}<br />
t.pub(&#8220;5,6,9&#8243;, 3, cdsVal1, pirCnt, cdsVal2);<br />
t.rcv();<br />
t.set_target_actuator(&amp;a2);<br />
t.actuator_LED(&amp;a2, ledPin);<br />
wdt_reset();<br />
}</p>
</div>
<p>세 가지 부품(LED, PIR센서, 조도센서)과 LoRa SX1278 모듈을 NodeMCU와 연결하고 위의 코드로 데이터가 잘 전달되는지 확인한다. LED 밝기 조절을 위해 LED를 액추에이터로 등록하여 액추에이터 ID를 2번으로 설정해주었다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-63.png" rel="lightbox[40437]"><img class="alignnone size-full wp-image-40588" alt="67 ict 최우수상_스마트파크 (63)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-63.png" width="571" height="304" /></a></p>
<p><span style="color: #ff9900"><strong>4.1.9. 수목관리 시스템 제작 과정</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-3.jpg" rel="lightbox[40437]"><img class="alignnone size-full wp-image-40605" alt="67 ict 최우수상_스마트파크 (3)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-3.jpg" width="568" height="592" /></a></p>
<p>먼저 시연을 위해 다음의 화분을 준비하였다. 토양수분을 측정하기 위한 센서를 NodeMCU보드와 연결이 편하도록 다음과 같이 기판에 부착하였다. NodeMCU는 LoRa통신 모듈과 연결돼있다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-64.png" rel="lightbox[40437]"><img class="alignnone size-full wp-image-40589" alt="67 ict 최우수상_스마트파크 (64)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-64.png" width="568" height="318" /></a></p>
<p>물을 주기 위해서는 수중모터와 수중모터를 제어하는 L9110s 모터드라이버를 사용했고, 수중모터를 LoRa통신으로 제어할 수 있도록 액추에이터로 등록했다. 코드 부분에서는 수중모터를 제어하기 위한 Actuator 구조체를 선언했다.<br />
액추에이터 신호가 왔을때 수중모터가 작동하여 물을 주는 것을 확인했다. 다음은 수목관리 센서노드의 코드이다.</p>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p>&nbsp;</p>
<p>#include &#8220;ToIoTwithLoRaWAN.h“<br />
#include &#8220;config.h&#8221;<br />
/*~~~중략~~~*/<br />
int IN1=D3; // A-IA연결 A<br />
int IN2=D1; // A-IB연결 /A<br />
int pin_A0 = A0;<br />
ToIoTwithLoRaWAN t;<br />
float value = 0.0;<br />
// Actuator Struct to use motor<br />
struct Actuator a1;</p>
<p>void setup() {<br />
t.setupToIoTwithLoRaWAN(nodeId, interval, 0);<br />
pinMode(pin_A0, INPUT);<br />
// Actuator Setting<br />
pinMode(IN1,OUTPUT);<br />
pinMode(IN2,OUTPUT);<br />
digitalWrite(IN1,LOW);<br />
digitalWrite(IN2,LOW);<br />
a1.actuatorId = 1;<br />
for(int i=0; i &lt; a1.values_len; i++)<br />
a1.value[i] = 0;<br />
// LoRaWAN Setting</p>
<p>/*~~~중략~~~*/<br />
// Join procedure<br />
bool isJoined;<br />
do {<br />
Serial.println(F(&#8220;Joining&#8230;&#8221;));<br />
isJoined = lora.join();<br />
Serial.println(isJoined);<br />
//wait for 3s to try again<br />
delay(3000);<br />
}while(!isJoined);<br />
delay(1);<br />
Serial.println(F(&#8220;Joined to network&#8221;));<br />
}<br />
void loop() {<br />
value = analogRead(A0);<br />
t.pub(&#8220;7&#8243;, 1, value);<br />
t.rcv();<br />
t.set_target_actuator(&amp;a1);<br />
t.actuator_L9110(&amp;a1, IN1, IN2);<br />
wdt_reset();<br />
}</p>
</div>
<p><span style="color: #ff9900"><strong>4.1.10. 쓰레기통 제작 과정</strong></span></p>
<p>다음과 같이 뚜껑에는 초음파 센서를 접착제를 이용해서 부착하였다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-4.jpg" rel="lightbox[40437]"><img alt="67 ict 최우수상_스마트파크 (4)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-4.jpg" width="283" height="333" /></a></p>
<p>Echo Pin은 디지털 핀 D1에 연결, Trig Pin은 디지털 핀 D3에 연결했다.<br />
다음은 쓰레기통 센서노드의 메인코드이다. 초음파 센서로 측정한 적재량 백분율 계산, LoRa통신 기능을 한다.</p>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p>&nbsp;</p>
<p>#include &#8220;ToIoTwithLoRaWAN.h&#8221;<br />
#include &#8220;config.h&#8221;<br />
/*~~~중략~~~*/<br />
int trigPin = D3;<br />
int echoPin = D1;<br />
ToIoTwithLoRaWAN t;<br />
void setup() {<br />
t.setupToIoTwithLoRaWAN(nodeId, interval, 0);<br />
pinMode(trigPin, OUTPUT);<br />
pinMode(echoPin, INPUT);<br />
// LoRaWAN Setting</p>
<p>/*~~~중략~~~*/<br />
// Join procedure<br />
bool isJoined;<br />
do {<br />
Serial.println(&#8220;Joining&#8230;&#8221;);<br />
isJoined = lora.join();<br />
Serial.println(isJoined);<br />
//wait for 3s to try again<br />
delay(3000);<br />
}while(!isJoined);<br />
delay(1);<br />
Serial.println(&#8220;Joined to network&#8221;);<br />
}<br />
double MyPreviousMillis;<br />
double MyInterval = 10;<br />
double distance;<br />
double MAX_distance=18.0;<br />
void ultrasound() {<br />
float duration;<br />
if(millis() &#8211; MyPreviousMillis &gt; MyInterval) {<br />
digitalWrite(trigPin, LOW);<br />
delayMicroseconds(2);<br />
digitalWrite(trigPin, HIGH);<br />
delayMicroseconds(10);<br />
digitalWrite(trigPin, LOW);<br />
// echoPin 이 HIGH를 유지한 시간을 저장 한다.<br />
duration = pulseIn(echoPin, HIGH);<br />
// HIGH 였을 때 시간(초음파가 보냈다가 다시 들어온 시간)을 가지고 거리를 계산 한다.<br />
// 340은 초당 초음파(소리)의 속도, 10000은 밀리세컨드를 세컨드로, 왕복거리이므로 2로 나눠준다.<br />
distance = ((double)(340 * duration) / 10000) / 2;<br />
MyPreviousMillis = millis();<br />
}<br />
}<br />
double value;<br />
void loop() {<br />
ultrasound();<br />
value = ((MAX_distance &#8211; distance)/MAX_distance)*100;<br />
t.pub(&#8220;8&#8243;, 1, value);<br />
t.rcv();<br />
wdt_reset();<br />
}</p>
</div>
<p>수직으로 쓰레기통 바닥을 향해 거리를 측정하여 값이 작아질수록 쓰레기가 쌓인 것이다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-65.png" rel="lightbox[40437]"><img class="alignnone size-full wp-image-40590" alt="67 ict 최우수상_스마트파크 (65)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-65.png" width="568" height="340" /></a></p>
<p>하지만 이 거리값을 cm단위로 그대로 전달하지 않고 적재된 양을 100분율로 계산하여 서버에 전달한다. 계산은 다음과 같다. (쓰레기 적재량 데이터의 단위는 %.)<br />
Value = (쓰레기통 바닥까지의 거리 &#8211; 측정된 거리) / (쓰레기통 바닥까지의 거리) * 100</p>
<p><span style="color: #008080"><strong>4.2. 데이터 스트리밍 시스템 구축</strong></span><br />
<span style="color: #ff9900"><strong>4.2.1. Data Logic 서비스</strong></span><br />
Kafka에 적재된 데이터를 DataLogic서비스로 consume후 Unmarshal하여 DataLogic내의 구조체에 저장할 수 있도록 consumer를 생성한다.</p>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p>&nbsp;</p>
<p>func NewKafkaConsumer() *group {<br />
//&#8212;중략&#8212;<br />
// kafkaConsumer 설정<br />
cfg := sarama.NewConfig()<br />
cfg.Version = sarama.V0_10_2_0<br />
cfg.Consumer.Offsets.Initial = sarama.OffsetNewest<br />
// kafkaConsumer 생성<br />
kafkaConsumer.client, err = sarama.NewConsumerGroup([]string{setting.Kafkasetting.Broker}, setting.Kafkasetting.GroupID, cfg)<br />
if err != nil {<br />
panic(err)<br />
}<br />
// &#8212;중략&#8212;<br />
go func() {<br />
for {<br />
// kafka로부터 데이터 Consume<br />
= err = kafkaConsumer.client.Consume(ctx, setting.Kafkasetting.Topics, &amp;consumer)<br />
if err != nil {<br />
log.Panicf(&#8220;Error from consumer: %v&#8221;, err)<br />
}<br />
if ctx.Err() != nil {<br />
return<br />
}<br />
consumer.ready = make(chan bool)<br />
}<br />
}()<br />
return kafkaConsumer<br />
}</p>
</div>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-66.png" rel="lightbox[40437]"><img class="alignnone size-full wp-image-40591" alt="67 ict 최우수상_스마트파크 (66)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-66.png" width="567" height="59" /></a></p>
<p>메모리에 저장되어있는 등록정보를 데이터에 추가 할 수 있도록 한다.</p>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p>&nbsp;</p>
<p>func (lcuc *logicCoreUsecase) ToLogicData(kd *model.KafkaData) (model.LogicData, error) {<br />
n, err := lcuc.rr.FindNode(kd.NodeID) // NodeID를 key로 검색하여 해당 Node의 메타데이터를 가져옴<br />
//&#8211;중략&#8211;<br />
s, err := lcuc.rr.FindSensor(kd.SensorID) // SensorID를 key로 검색하여 해당 Sensor의 메타데이터를 가져옴<br />
//&#8211;중략&#8211;<br />
vl := map[string]float64{}<br />
for i, v := range s.SensorValues {<br />
vl[v] = kd.Values[i] // Kafaka로 읽은 센서 데이터 값 map에 넣음<br />
}<br />
return model.LogicData{ // 등록정보 추가된 센서 데이터 return<br />
SensorID: kd.SensorID,<br />
SensorName: s.Name,<br />
Values: vl,<br />
Node: *n,<br />
Timestamp: kd.Timestamp,<br />
}, nil<br />
}</p>
</div>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-90.png" rel="lightbox[40437]"><img class="alignnone  wp-image-40657" alt="67 ict 최우수상_스마트파크 (90)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-90-620x69.png" width="520" /></a></p>
<p>해당 센서값이 설정된 이벤트 조건을 만족하는지 검사한다. 로직을 만족할 경우 이벤트를 실행한다.</p>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p>&nbsp;</p>
<p>func (ve *ValueElement) Exec(d *model.LogicData) {<br />
// &#8211;중략&#8211;<br />
for _, rg := range ve.Range {<br />
if rg.Min &lt;= v &amp;&amp; v &lt; rg.Max { //value가 등록한 조건을 충족하는지 검사<br />
isRange = true<br />
}<br />
}<br />
if isRange {<br />
ve.BaseElement.Exec(d) //충족할 경우 다음으로 등록된 Filter혹은 Action 수행<br />
}<br />
}</p>
<p>func (ae *ActuatorElement) Exec(d *model.LogicData) {<br />
// &#8211;중략&#8211;<br />
if ok {<br />
ae.Interval[d.Node.Name] = false<br />
go func() {</p>
<p>res := Actuator{ // Actuator에게 전할 명령 객체 생성<br />
Nid: d.Node.Nid,<br />
Aid: ae.Aid,<br />
Values: ae.Values,<br />
}<br />
pbytes, _ := json.Marshal(res)<br />
buff := bytes.NewBuffer(pbytes) // 명령을 buffrer에 담음<br />
addr := (*adapter.AddrMap)[d.Node.Sid] // 등록정보로 부터 해당 노드의 Sink주소 가져옴</p>
<p>// Sink 라즈베리파이의 Flask 서버로 동작명령 Post<br />
resp, err := http.Post(&#8220;http://&#8221;+addr.Addr+&#8221;/actuator&#8221;, &#8220;application/json&#8221;, buff)<br />
if err != nil {<br />
panic(err)<br />
}<br />
defer resp.Body.Close()<br />
}()<br />
// &#8211;중략&#8211;<br />
}<br />
}</p>
</div>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-67.png" rel="lightbox[40437]"><img class="alignnone size-full wp-image-40592" alt="67 ict 최우수상_스마트파크 (67)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-67.png" width="568" height="61" /></a></p>
<p>등록정보가 포함되어 완성된 데이터를 엘라스틱 서치에 인덱싱 한다.</p>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p>func (ec *client) insertDoc(d *model.Document) {<br />
ec.docBuf = append(ec.docBuf, d) // 버퍼에 데이터 적재<br />
if len(ec.docBuf) &gt;= (ec.bufSize &#8211; 10) { // 데이터 개수가 일정 수준에 달하면<br />
ec.bulk() // 벌크 인덱싱 실행<br />
}<br />
}</p>
</div>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-68.png" rel="lightbox[40437]"><img class="alignnone size-full wp-image-40593" alt="67 ict 최우수상_스마트파크 (68)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-68.png" width="571" height="307" /></a></p>
<p><span style="color: #ff9900"><strong>4.2.2. Health Check 서비스</strong></span><br />
각 Sink로부터 헬스 정보를 받는다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-69.png" rel="lightbox[40437]"><img class="alignnone size-full wp-image-40594" alt="67 ict 최우수상_스마트파크 (69)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-69.png" width="568" height="84" /></a></p>
<p>받은 헬스 정보 중 변경 사항이 있는 노드의 헬스 정보만을 버퍼에 추가하여 웹소켓을 통해 front-end로 보낼 수 있도록 한다.<br />
버퍼에 담긴 헬스 정보를 프론트 엔드로 전송한다.</p>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p>&nbsp;</p>
<p>// 어답더 계층의 NodeState상태와 메모리 계층의 statusRepo의 status table을 동기화시켜 주는 것<br />
func (sr *statusRepo) updateNodeStatus(sinkID int, ns []adapter.NodeState, t time.Time) []model.NodeStatus {<br />
// &#8211;중략&#8211;<br />
// update the status checked from the sink node<br />
for _, v := range ns { // v는 NodeSate 배열 중 한 원소<br />
nsTable[v.NodeID] = true<br />
nodeState, ok := sr.table[sinkID][v.NodeID]
// if new nodeState, regist new state<br />
if !ok {<br />
tempState := model.NewStatus(v.State, t)<br />
sr.table[sinkID][v.NodeID] = tempState<br />
res = append(res, model.NodeStatus{NodeID: v.NodeID, State: tempState.State, Battery: v.Battery})<br />
continue<br />
}<br />
if isChanged := nodeState.UpdateState(v.State, t); isChanged { // 헬스정보가 변경되었을 경우 res배열에 추가<br />
res = append(res, model.NodeStatus{NodeID: v.NodeID, State: nodeState.State, Battery: v.Battery})<br />
}<br />
sr.table[sinkID][v.NodeID] = nodeState // 상태 테이블(map)에 상태 저장<br />
}<br />
// &#8211;중략&#8211;<br />
return res<br />
}</p>
</div>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-70.png" rel="lightbox[40437]"><img class="alignnone size-full wp-image-40595" alt="67 ict 최우수상_스마트파크 (70)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-70.png" width="569" height="60" /></a></p>
<p><span style="color: #008080"><strong>4.3. 웹 프론트 환경 구축</strong></span><br />
<span style="color: #ff9900"><strong> 4.3.1 노드 마커</strong></span></p>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p>addMarker(position: any, map: any) {<br />
if ( position.kind === &#8216;streetlemp&#8217;) var imageSrc = &#8216;https://user-images.githubusercontent.com/50009240/111455569-4edede80-8759-11eb-952f-2df28fdcdc4c.png&#8217;<br />
else if ( position.kind === &#8216;trashcan&#8217;) var imageSrc = &#8216;https://user-images.githubusercontent.com/50009240/111451567-e857c180-8754-11eb-9299-0ea55e430b17.png&#8217;<br />
else var imageSrc = &#8216;https://user-images.githubusercontent.com/50009240/111451575-ebeb4880-8754-11eb-8459-eed8f0983c55.png&#8217;</p>
<p>var imageSize = new window.kakao.maps.Size(30 , 35),<br />
markerImage = new window.kakao.maps.MarkerImage(imageSrc, imageSize), // 마커이미지 생성<br />
marker = new window.kakao.maps.Marker({<br />
map: map,<br />
position: position.latlng, // 마커의 위치<br />
image: markerImage // 마커 이미지<br />
});<br />
return marker;<br />
}</p>
</div>
<p>카카오맵 api를 사용해 지도 및 지도 위 마커를 나타냈다. addMarker 함수의 파라미터로 받은 position는 노드 이름, 종류, 정보(싱크, 센서, id), 지도 내 좌표를 포함하고 있다. 따라서 노드의 종류에 따라 마커 이미지를 다르게 했고, 해당 노드의 지도 내 좌표에 마커를 생성했다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-71.png" rel="lightbox[40437]"><img class="alignnone size-full wp-image-40596" alt="67 ict 최우수상_스마트파크 (71)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-71.png" width="570" height="265" /></a></p>
<p><span style="color: #ff9900"><strong>4.3.2. rest api</strong></span></p>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p>&nbsp;</p>
<p>componentDidMount() {<br />
this.getsensorList(); // 센서리스트 GET하는 함수<br />
this.getsinkList(); // 싱크리스트 GET하는 함수<br />
}</p>
<p>// Get sensor list from backend<br />
getsensorList() {<br />
var url = SENSOR_URL;</p>
<p>fetch(url)<br />
.then((res) =&gt; res.json())<br />
.then((data) =&gt; {<br />
this.setState({ sensorList: data }); // 얻은 데이터를 sensorList에 저장<br />
})<br />
.catch((error) =&gt; console.error(&#8216;Error:&#8217;, error));<br />
}</p>
</div>
<p>위는 백으로부터 센서 리스트를 GET해오는 코드이다. component DidMount는 컴포넌트가 화면에 나타나게 됐을 때 호출된다. 여기서 fetch()를 통해 해당 url로부터 노드 리스트, 센서 리스트 등의 정보를 GET한다. 백으로부터 얻은 데이터는 해당 구조체에 저장해 사용한다. 위 코드와 같이 REST API를 통해 백으로부터 정보를 얻어오고, 센서, 노드 등을 등록할 시 REST API (POST)를 사용해 백으로 정보를 보냈다.</p>
<p><span style="color: #ff9900"><strong>4.3.3. 헬스 체크</strong></span></p>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p>&nbsp;</p>
<p>export interface nodeHealthCheckElem {<br />
nid: number;<br />
state: number;<br />
battery: number;<br />
}</p>
</div>
위는 HealthCheck 구조체이다. 각 노드의 헬스 상태를 가져와야 하므로 노드의 id인 nid, 연결 상태를 의미하는 state, 배터리 상태인 battery로 구조체가 이루어져 있다.</p>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p>&nbsp;</p>
<p>import { w3cwebsocket as W3CWebSocket } from &#8216;websocket&#8217;;<br />
const client = new W3CWebSocket(HEALTHCHECK_URL) // 웹소켓 오브젝트 생성</p>
<p>componentWillMount() {<br />
client.onopen = () =&gt; { // 웹소켓 연결 확인<br />
console.log(&#8216;WebSocket Client for Health Check Connected&#8217;);<br />
};<br />
client.onmessage = (message: any) =&gt; {//서버로부터 수신된 메시지 전달<br />
console.log(message);<br />
this.setState({<br />
nodeState: JSON.parse(message.data),<br />
// 헬스정보 nodeState에 저장<br />
});<br />
this.getHealthStateMap(); // health map update<br />
this.getBatteryStateMap(); // battery map update<br />
};<br />
}</p>
</div>
<p>위는 웹소켓을 통해 헬스와 배터리 상태를 얻어오는 코드이다. ‘const client = new W3CWebSocket(HEALTHCHECK_URL);’ 를 통해 새 웹소켓 오브젝트를 생성하고, onopen 핸들러로 연결이 수립되었는지 확인한다. WebSockets는 event-driven API로써 메시지가 수신되면 “message” 이벤트가 onmessage 함수로 전달되게 된다. 따라서 백으로부터 받은 노드 id, health, battery 정보가 onmessage 함수로 전달되고, 이를 nodeState에 저장한다. 그 후 getHealthStateMap(), getBatteryStateMap() 함수에서 nodeState를 바탕으로 각각 health, battery 정보가 저장되어 있는 map을 업데이트 한다. 각 map에 저장된 health, battery 정보를 토대로 nodeTable, MapNodeTable에서 상태를 나타낸다.</p>
<p><span style="color: #0000ff"><strong>5. 회로도</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-72.png" rel="lightbox[40437]"><img class="alignnone size-full wp-image-40597" alt="67 ict 최우수상_스마트파크 (72)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-72.png" width="574" height="366" /></a> <a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-73.png" rel="lightbox[40437]"><img class="alignnone size-full wp-image-40598" alt="67 ict 최우수상_스마트파크 (73)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-73.png" width="565" height="358" /></a> <a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-74.png" rel="lightbox[40437]"><img class="alignnone size-full wp-image-40599" alt="67 ict 최우수상_스마트파크 (74)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-74.png" width="557" height="356" /></a></p>
<p><span style="color: #0000ff"><strong>6. 임시 데이터 프로듀싱 서버</strong></span></p>
<p><span style="color: #008080"><strong>6.1. 기능</strong></span><br />
실제로 모든 공원에 센서를 설치하여 데이터를 얻는 것은 매우 어려우므로 실제 공공 통계 데이터를 처리 및 프로듀싱 하여 데이터 실시간 스트리밍 및 시각화, 매니지먼트 기능의 데모를 구현한다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-75.png" rel="lightbox[40437]"><img class="alignnone size-large wp-image-40600" alt="67 ict 최우수상_스마트파크 (75)" src="http://www.ntrexgo.com/wp-content/uploads/2021/08/67-ict-최우수상_스마트파크-75-620x148.png" width="620" height="148" /></a></p>
<p><span style="color: #008080"><strong>6.2. OPEN DATA</strong></span><br />
<span style="color: #00ccff"><strong>서울 열린 데이터 광장</strong></span><br />
· 스마트서울 도시데이터 센서(S-DoT) 환경정보<br />
· 미세먼지, 온도, 습도, 조도<br />
· 서울시 한강공원 이용객 현황 통계<br />
· 월별 한강공원 이용객</p>
<p><span style="color: #00ccff"><strong>기상자료개방포털</strong></span><br />
· 농업기상관측 정보<br />
· 토양수분</p>
<p><span style="color: #0000ff"><strong>7. 참고문헌 및 출처</strong></span><br />
· 카카오맵 api : https://apis.map.kakao.com/web/sample/multipleMarkerEvent2/<br />
· 서울 열린 데이터 광장 &#8211; 스마트서울 도시데이터 센서(S-DoT) 환경정보 https://opengov.seoul.go.kr/data/20303041<br />
· 서울 열린 데이터 광장 &#8211; 서울시 한강공원 이용객 현황 통계 https://data.seoul.go.kr/dataList/10798/S/2/datasetView.do<br />
· 기상자료개방포털 &#8211; 농업기상관측(AAOS) https://data.kma.go.kr/data/grnd/selectAgrRltmList.do?pgmNo=72<br />
· 대구 스마트공원 기사 : http://news.khan.co.kr/kh_news/khan_art_view.html?art_id=201709151701001<br />
· 세종 스마트공원 기사 :https://www.korea.kr/news/visualNewsView.do?newsId=148850085<br />
· LoRaWAN v1.0.4 Alliance-Specification 문서 : https://lora-alliance.org/resource_hub/lorawan-104-specification-package/<br />
· LoRaWAN Alliance-Specification 문서 : https://lora-alliance.org/wp-content/uploads/2020/11/lorawantm_specification_-v1.1.pdf<br />
· LoRaWAN Alliance-What-is-lorawan 문서 : https://lora-alliance.org/wp-content/uploads/2020/11/what-is-lorawan.pdf</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
]]></content:encoded>
			<wfw:commentRss>http://www.ntrexgo.com/archives/40437/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>[66호]20대 주거환경을 위한 외·내부 환기 제어 시스템</title>
		<link>http://www.ntrexgo.com/archives/40667</link>
		<comments>http://www.ntrexgo.com/archives/40667#comments</comments>
		<pubDate>Wed, 23 Jun 2021 00:00:42 +0000</pubDate>
		<dc:creator>디바이스마트 매거진</dc:creator>
				<category><![CDATA[디바이스마트 매거진]]></category>
		<category><![CDATA[특집]]></category>
		<category><![CDATA[ict]]></category>
		<category><![CDATA[공모전]]></category>
		<category><![CDATA[디바이스마트]]></category>
		<category><![CDATA[매거진]]></category>
		<category><![CDATA[미세먼지]]></category>
		<category><![CDATA[시스템]]></category>
		<category><![CDATA[장려상]]></category>
		<category><![CDATA[제어]]></category>
		<category><![CDATA[환기]]></category>

		<guid isPermaLink="false">http://www.ntrexgo.com/?p=40667</guid>
		<description><![CDATA[디바이스마트매거진 66호 &#124; 집안의 미세먼지 농도를 확인할 수 있고 내부의 미세먼지가 외부보다 높은 경우 자동으로 창문을 열 수 있는 시스템이다. ]]></description>
				<content:encoded><![CDATA[<p><strong><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-1.png" rel="lightbox[40667]"><img class="alignnone size-large wp-image-40678" alt="66_ict_환기제어 (1)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-1-620x175.png" width="620" height="175" /></a></strong></p>
<p><span style="font-size: medium"><strong>2020 ICT 융합 프로젝트 공모전 장려상</strong></span></p>
<p><span style="font-size: x-large"><span style="color: #0000ff"><strong>20대 주거환경을 위한 </strong></span><span style="color: #0000ff"><strong>외·내부 환기 제어 시스템</strong></span></span></p>
<p style="text-align: right">글 | 항공대학교 이종민</p>
<p><span style="color: #ff9900"><strong>1. 심사평</strong></span><br />
<strong>칩센</strong> 우선 보고서를 통하여 작품에 대하여 조금 더 설명해주었으면 좋았겠다는 생각은 들지만, 주제로 삼은 작품명의 &#8220;20대 주거 환경을 위한~&#8221;으로 시작하는 부분이 매우 인상적입니다. 공기 청정이 필요한 이유를 단순히 외부에서 발현한 공기질의 저하가 아닌, 20대가 사는 주거환경에서는 왠지 실내의 공기질이 더 나쁠 수도 있겠다는 생각이 들었습니다. 작품명을 아주 잘 정하신거 같습니다. 그리고 다른 유사 작품들과의 차이점, 두 개의 장치(실내/외)가 서로 통신하여 연동하도록 한 부분은 매우 기발한 발상으로 보입니다. 스마트폰 또는 인터넷을 통해 추출된 지역의 정보보다는 실제로 필요한 것은 내가 머무르고 있는 실내 공간과 외부 공간의 공기질의 차이가 더 중요하다는 생각이 들기 때문입니다. 전반적으로 데모 작품이 구성이나 제작이 매우 깔끔하게 된 듯 합니다.</p>
<p><strong>펌테크</strong> 실생활과 접목된 실용성을 지닌 작품으로 생각됩니다. 전체적으로 꼼꼼하게 잘 기획되었고, 간결하게 잘 구성한 완성도 높은 작품이라고 생각합니다.</p>
<p><strong>위드로봇</strong> 아이디어가 좀 더 추가되면 좋은 작품이 될 것 같습니다.</p>
<p><span style="color: #ff9900"><strong>2. 배경</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-2.png" rel="lightbox[40667]"><img class="alignnone size-large wp-image-40679" alt="66_ict_환기제어 (2)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-2-620x275.png" width="620" height="275" /></a></p>
<p>미세먼지 농도가 심각하다는 것은 아마 우리 모두가 알고 있을 것이다. 그리고 실제로 미세먼지로 인한 두통, 기침, 폐암, 그리고 직업 능률 감소로까지 이어지고 있다. 네이버 키워드 검색량을 확인해보면 미세먼지라는 것이 큰 문제이고 많은 사람들이 관심을 가지고 있다는 것을 확인할 수 있다.</p>
<p><span style="color: #33cccc"><strong>2.1. 현재상황</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-3.png" rel="lightbox[40667]"><img class="alignnone size-full wp-image-40680" alt="66_ict_환기제어 (3)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-3.png" width="584" height="287" /></a><br />
미세먼지 문제를 해결하기위한 공기기청정기는 2018년 기준으로 2조원 시장을 가지고 있고 미니 공기청정기 시장은 약 1000억원의 시장을 형성하고 있다. 하지만 미니 공기청정기는 단지 필터와 펜으로 이루어진 공기청정기로 사람들이 어느정도 공기가 오염되었는지 확인할 수 없다. 또한 미니 공기청정기를 분해해보면 공기청정기의 필터가 역할을 제대로 하지 못하고 펜이 약한 경우가 대부분이다. 배경에서 말했듯이 대부분의 사람들은 초미세먼지로 인해 창문을 열고 살지 않고 있다 하지만 기사에 따르면 집안의 먼지농도와 밖의 미세먼지 농도를 비교 했을 때 밖의 미세먼지 농도가 높다고 한다. 이러한 상황에는 공기청정기를 계속 틀기보다 창문을 여는 것이 더 효과적인 방법이 될 수 있다. 그래서 저는 이 두가지 문제점을 해결하기 위한 제품을 만들려고 했다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-4.png" rel="lightbox[40667]"><img class="alignnone size-full wp-image-40681" alt="66_ict_환기제어 (4)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-4.png" width="611" height="452" /></a></p>
<p><span style="color: #33cccc"><strong>2.2. 제품의 특징</strong></span><br />
집안의 미세먼지 농도를 확인할 수 있고 내부의 미세먼지가 외부보다 높은 경우 자동으로 창문을 열 수 있는 시스템을 만들었다. 그렇게 함으로써 20대의 주거환경에 필수적인 공기 오염도를 현저히 줄일 수 있고 전기료 감소로 인한 경제적 이익을 취할 수 있다고 생각한다.</p>
<p><span style="color: #33cccc"><strong>2.3. 제품 상세 특징 및 스펙</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-5.png" rel="lightbox[40667]"><img class="alignnone size-large wp-image-40682" alt="66_ict_환기제어 (5)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-5-549x620.png" width="549" height="620" /></a></p>
<p>아두이노 나노 기반인 PCB를 제작하려고 한다. 이 공기청정기는 여러가지 특징이 있다. 브레드 보드를 사용하지 않고 PCB 형태로 제작 되기 때문에 회로가 안정화되어 있고 LED, 미세먼지, 온습도 센서를 통한 정보를 LCD에서 바로 확인을 할 수 있다.<br />
또한 다양한 센서 정보를 다른 쪽 아두이노에 블루투스 통신을 통해서 정보를 보내 내부와 외부의 미세먼지 농도를 비교하여 창문 개폐를 위한 서브모터를 작동시킨다.</p>
<p><span style="color: #33cccc"><strong>2.4. 제품 작동 알고리즘</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-6.png" rel="lightbox[40667]"><img class="alignnone size-large wp-image-40683" alt="66_ict_환기제어 (6)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-6-620x328.png" width="620" height="328" /></a></p>
<p>미세먼지에 따른 LED표시, 미세먼지에 농도에 따라 AUTO 펜 제어, 그리고 미세먼지 농도 심각 시 부저가 울리는 것이다. 그리고 그러한 정보를 LCD에 표현하는 것이다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-7.png" rel="lightbox[40667]"><img class="alignnone size-large wp-image-40684" alt="66_ict_환기제어 (7)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-7-620x298.png" width="620" height="298" /></a></p>
<p>외부장치와 내부장치를 구성하여, HC-06 두개를 페어링하여 외부 미세먼지 농도에 대한 정보를 아두이노에 블루투스 통신을 통해서 보내 창문을 개폐여부를 결정하는 것이다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-8.png" rel="lightbox[40667]"><img class="alignnone size-large wp-image-40685" alt="66_ict_환기제어 (8)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-8-620x270.png" width="620" height="270" /></a></p>
<p>블루투스 통신의 경우 일련의 String 형식으로 값이 전달이 된다. 따라서 여러가지 정보를 보내기가 매우 어려운 사항이다. 따라서 이번 프로젝트에서는 미세먼지 농도를 포함해서, 온도, 습도, 조도의 정보를 블루투스 통신을 통해서 받아야하기 때문에 센서값별로 일련의 String을 파싱하는 소프트웨어 알고리즘이 필요하다. 그 부분을 코딩으로 구현</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-9.png" rel="lightbox[40667]"><img class="alignnone size-large wp-image-40686" alt="66_ict_환기제어 (9)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-9-620x192.png" width="620" height="192" /></a></p>
<p>미세먼지 센서의 값은 정확한 농도를 표현을 하는 것이 아닌 전압을 읽어서 가져오는 것이다. 시리얼 통신을 통해 고정밀 미세저측정기를 통해서, 캘리브레이션 할 수 있는 이동평균 개념을 적용해서 센서값 기복에 대한 부분을 해결했다.</p>
<p><span style="color: #33cccc"><strong>2.5. 회로도 및 부분별 하드웨어 원리</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-10.png" rel="lightbox[40667]"><img class="alignnone size-large wp-image-40687" alt="66_ict_환기제어 (10)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-10-620x206.png" width="620" height="206" /></a></p>
<p>캐패시터는 다양한 용도로 사용된다. 노이즈를 막기위해서 맥류신호를 평활하기 위한 평활용으로 사용되었다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-11.png" rel="lightbox[40667]"><img class="alignnone size-full wp-image-40688" alt="66_ict_환기제어 (11)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-11.png" width="613" height="367" /></a></p>
<p>전원 순단 시 및 IC의 구동 스피드가 급격히 빨라짐에 따라, 부하전류가 증가한 경우, 전원으로부터의 라인 전압이 강하하여, IC의 오동작을 초래하는 경우가 있다. 이를 방지하기 위해, 전원 라인 정상 시에 콘덴서가 축적해 놓은 전기를 IC 측에 공급하여 전원 라인 전압을 일시적으로 유지한다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-12.png" rel="lightbox[40667]"><img class="alignnone size-full wp-image-40689" alt="66_ict_환기제어 (12)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-12.png" width="619" height="254" /></a></p>
<p>아두이노에 전원을 공급하기위해서는 5v전원을 공급해주어야한다. 하지만 모터의 경우 5v를 공급하게 되면 최대 파워를 낼 수 가 없다 그래서 12v전원은 모터에 아두이노에는 전압을 강압시켜 5v를 공급 시켜주기 위해 레귤레이터를 사용하였다. 전원 공급의 경우 12v로 일정한 정전압을 보내므로 스위칭 레귤레이터가 아닌 리니어 레귤레이터를 사용하였다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-13.png" rel="lightbox[40667]"><img class="alignnone size-full wp-image-40690" alt="66_ict_환기제어 (13)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-13.png" width="612" height="450" /></a></p>
<p>12v와 0.7A가 사용되는 모턴팬을 동작시키기 위해 Mosfet을 사용하여, 아두이노 신호에 따라 입력된 12V 전원부에서 전류를 공급할 수 있도록 구성을 하였다</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-14.png" rel="lightbox[40667]"><img class="alignnone size-full wp-image-40691" alt="66_ict_환기제어 (14)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-14.png" width="612" height="408" /></a></p>
<p>경고음을 알리기 위한 용도로서 부저를 사용하였다. 부저 또한 코일성분을 가지고 있기에 직접적으로 아두이노에 연결을 하는 것은 좋지 않으며, 아두이노에서 출력하는 전류의 한계로 소리가 매우 작은 측면을 가지고 있다. 따라서 부저의 경우에는 트랜지스터를 사용하여 전류증폭을 통해 더 큰 경고음을 울릴 수 있도록 회로를 구성하였으며, 다이오드(FLYING WEEL 다이오드)를 사용하여, 역기전력를 방지하고자 구성하였다.</p>
<p><span style="color: #ff9900"><strong>3. 최종 결과물</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-1.jpg" rel="lightbox[40667]"><img class="alignnone size-full wp-image-40677" alt="66_ict_환기제어 (1)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-1.jpg" width="611" height="438" /></a></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-15.png" rel="lightbox[40667]"><img class="alignnone size-full wp-image-40692" alt="66_ict_환기제어 (15)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-15.png" width="612" height="360" /></a></p>
<p><span style="color: #ff9900"><strong>4.아두이노 소스코드</strong></span></p>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p>// 해당 소스코드는 외부데이터(미세먼지 / 온도 / 습도 / 조도) 측정하고, 내부 데이터와 비교하여<br />
// 외부환기에 대한 부분을 담당하는 부분이다.</p>
<p>#include &#8220;SoftwareSerial.h&#8221;<br />
#include &lt;Adafruit_NeoPixel.h&gt;<br />
#include &#8220;DHT.h&#8221;<br />
#include &lt;Servo.h&gt;<br />
#include &lt;LiquidCrystal_I2C.h&gt;</p>
<p>// 블루통신 관련해서 필요한 변수 정리<br />
const int maxIndex = 10;<br />
byte blockData[maxIndex]; //block 값 저장<br />
int arrIndex = 0; // 배열 arrIndex</p>
<p>byte refined_humidity = 0;<br />
byte refined_temperature = 0;<br />
byte refined_value = 0;<br />
byte refined_dust_data = 0;</p>
<p>byte refined_humidity2 = 0;<br />
byte refined_temperature2 = 0;<br />
byte refined_value2 = 0;<br />
byte refined_dust_data2 = 0;</p>
<p>/*미세먼지센서 변수들*/<br />
#define measurePin A1 //Connect dust sensor to Arduino A0 pin<br />
#define ledPower 6 //Connect 3 led driver pins of dust sensor to Arduino D4<br />
#define SampleTime 30 //먼지센서 30회 샘플링</p>
<p>int samplingTime = 280;<br />
int deltaTime = 40;<br />
int sleepTime = 9680;<br />
float voMeasured = 0;<br />
float calcVoltage = 0;<br />
float dustDensity = 0;</p>
<p>//조도센서&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-<br />
#define cds A0</p>
<p>//서보 모터 오브젝트 생성<br />
Servo myservo;</p>
<p>// 온습도 센서를 디지털 4번 핀에 연결합니다.<br />
#define DHTPIN 8<br />
#define DHTTYPE DHT11</p>
<p>//네오픽셀 연결 핀<br />
#define PIN1 4<br />
//네오픽셀 LED개수<br />
#define NUMPIXELS 4<br />
Adafruit_NeoPixel pixels1 = Adafruit_NeoPixel(NUMPIXELS, PIN1, NEO_GRB + NEO_KHZ800);<br />
int delayval = 10;</p>
<p>DHT dht(DHTPIN, DHTTYPE);<br />
SoftwareSerial Serial1(2, 3); // rx,tx</p>
<p>// 서보모터 변수<br />
int val1 = 0;<br />
int val2 = 0;</p>
<p>// LCD의 경우, 고유 주소를 가지고 있는데 대부분의 LCD는 2개의 주소 중 하나로 세팅되어 있다.<br />
// 0x3F, 0&#215;27 이렇게 두가지 주소가 있다.<br />
LiquidCrystal_I2C lcd(0&#215;27, 16, 2);</p>
<p>//&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;</p>
<p>void GetData();<br />
void get_data_print2();<br />
void data_print1();<br />
void LED_R1();<br />
void LED_B1();<br />
void LED_G1();<br />
void window_open_close(int out, int in);<br />
int dust_val(); // 먼지 값받아오는 함수</p>
<p>void setup () {</p>
<p>// 시리얼통신<br />
Serial.begin(9600);<br />
// 블루투스 통신<br />
Serial1.begin(9600);<br />
//서보 모터 10번핀<br />
myservo.attach(10);<br />
myservo.write(100);<br />
// 조도센서<br />
pinMode(cds, INPUT);<br />
// 미세먼지센서<br />
pinMode(ledPower, OUTPUT);<br />
pinMode(measurePin, INPUT);<br />
//온도습도센서 시작<br />
dht.begin();<br />
// 네오픽셀 시작<br />
pixels1.begin(); // This initializes the NeoPixel library.</p>
<p>lcd.init();<br />
lcd.backlight();<br />
lcd.clear();<br />
}</p>
<p>void loop () {</p>
<p>// 블루투스 통신을 통해 내부데이터를 받아서 파싱을 하고, 각 변수에 저장</p>
<p>if (Serial1.available()) {<br />
GetData();<br />
}<br />
if (arrIndex != 0 &amp;&amp; arrIndex &lt; 9) {<br />
Serial.println(&#8220;내부데이터 변수 저장&#8221;);</p>
<p>if (blockData[0] == 104)<br />
refined_humidity2 = blockData[1];<br />
if (blockData[2] == 116)<br />
refined_temperature2 = blockData[3];<br />
if (blockData[4] == 99)<br />
refined_value2 = blockData[5];<br />
if (blockData[6] == 100)<br />
refined_dust_data2 = blockData[7];<br />
} else {</p>
<p>Serial.println(&#8220;내부데이터 변수 이상으로 미저장 or 블루투스 미연결&#8212;&#8211;&#8221;);<br />
}</p>
<p>// 습도와 온도값을 측정하고, 제대로 측정되었는지 확인해줍니다.<br />
byte humidity = dht.readHumidity();<br />
byte temperature = dht.readTemperature();</p>
<p>refined_humidity = constrain(humidity, 0, 100);<br />
refined_temperature = constrain(temperature, 0, 100);</p>
<p>// 조도센서 값<br />
int value = analogRead(cds);<br />
refined_value = map(value, 0, 1023, 100, 0);</p>
<p>// 먼지센서 값<br />
int dust_data = dust_val(); // 먼지량을 받아옴<br />
refined_dust_data = map(dust_data, 0, 1023, 0, 100);</p>
<p>// 미세먼지 농도에 따라 색변화와 부저를 활용하여 경고음 제공하기.</p>
<p>if (refined_dust_data &gt; 65) {<br />
delay(10);<br />
LED_R1();<br />
} else if (refined_dust_data &lt;= 65 &amp;&amp; refined_dust_data &gt; 50 ) {<br />
delay(10);<br />
LED_RB1();<br />
} else if (refined_dust_data &lt;= 50 &amp;&amp; refined_dust_data &gt; 25) {<br />
delay(10);<br />
LED_B1();<br />
} else {<br />
LED_G1();<br />
}</p>
<p>// 내부데이터와 외부데이터를 비교하여, 창문개폐 진행<br />
// 내부 공기청정기팬의 경우에는 외부,내부 미세먼지 농도에 상관없이, 내부 미세먼지가 25 이상인 경우에는 자동으로 작동이된다.<br />
// 창문개폐의 경우는<br />
// 1) 외부 창문이 열리는 경우<br />
// 외부의 미세먼지농도가 내부보다 낮을 때,<br />
// 외부 미세먼지 농도가 높은 경우에는 창문을 열리지 않고, 내부 팬에 의해서 공기정화가 진행이 된다.<br />
// 내부 미세먼지 농도가 25 이상일 때, 동작한다.<br />
window_open_close(refined_dust_data, refined_dust_data2);</p>
<p>lcd.clear();<br />
lcd.setCursor(0, 0);<br />
lcd.print(&#8220;out dust :&#8221;);<br />
lcd.print(refined_dust_data);<br />
lcd.setCursor(0, 1);<br />
if(refined_dust_data2 ==0){<br />
lcd.print(&#8220;BT disconnected&#8221;);<br />
}else{<br />
lcd.print(&#8220;inner dust :&#8221;);<br />
lcd.print(refined_dust_data2);<br />
}</p>
<p>get_data_print2();<br />
data_print1();<br />
}</p>
<p>//&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<br />
void LED_R1() {<br />
for (int i = 0; i &lt; NUMPIXELS; i++) {<br />
pixels1.setPixelColor(i, pixels1.Color(255, 0, 0)); // Moderately bright green color.<br />
pixels1.show(); // This sends the updated pixel color to the hardware.<br />
delay(delayval); // Delay for a period of time (in milliseconds).<br />
}<br />
}</p>
<p>void LED_G1() {</p>
<p>for (int i = 0; i &lt; NUMPIXELS; i++) {<br />
pixels1.setPixelColor(i, pixels1.Color(0, 255, 0)); // Moderately bright green color.<br />
pixels1.show(); // This sends the updated pixel color to the hardware.<br />
delay(delayval); // Delay for a period of time (in milliseconds).<br />
}<br />
}</p>
<p>void LED_B1() {<br />
for (int i = 0; i &lt; NUMPIXELS; i++) {<br />
pixels1.setPixelColor(i, pixels1.Color(0, 0, 255)); // Moderately bright green color.<br />
pixels1.show(); // This sends the updated pixel color to the hardware.<br />
delay(delayval); // Delay for a period of time (in milliseconds).<br />
}<br />
}</p>
<p>void LED_RB1() {<br />
for (int i = 0; i &lt; NUMPIXELS; i++) {<br />
pixels1.setPixelColor(i, pixels1.Color(255, 0, 120)); // Moderately bright green color.<br />
pixels1.show(); // This sends the updated pixel color to the hardware.<br />
delay(delayval); // Delay for a period of time (in milliseconds).<br />
}<br />
}</p>
<p>//&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<br />
int dust_val() //센서의 노이즈 값으로인해 여러번 데이터를 받은뒤 평균 먼지량을 구함<br />
{<br />
int dust = 0;<br />
for (int i = 0; i &lt; SampleTime; i++)<br />
{<br />
digitalWrite(ledPower, LOW); // power on the LED 0.028초부터 led가 가장 밝음으로 최소샘플링 타임으로 결정<br />
delayMicroseconds(samplingTime);</p>
<p>voMeasured = analogRead(measurePin); // read the dust value<br />
delayMicroseconds(deltaTime);<br />
digitalWrite(ledPower, HIGH); // turn the LED off<br />
delayMicroseconds(sleepTime);<br />
//////////////////////////////////////////// dust vlaue 받아오기<br />
calcVoltage = voMeasured * (5.0 / 1024.0);<br />
dustDensity = calcVoltage / 0.005; //(0.17 * calcVoltage &#8211; 0.1) * 1000;<br />
/////////////////////value 의 미세한 변화를 수식으로 먼지량으로 변환<br />
if (dustDensity &lt; 0) { // value 의 값이 마이너스일경우 오류 임으로 0을 대입<br />
dustDensity = 0;<br />
}<br />
dust += dustDensity;<br />
}<br />
return (dust / SampleTime)+50; // 센싱한값의 평균을 return<br />
}</p>
<p>void GetData() {<br />
Serial.println();<br />
Serial.println(&#8220;데이터 가져오기 시작 &#8220;);<br />
char temp;<br />
arrIndex = 0;</p>
<p>temp = (char)Serial1.read();<br />
Serial.println(&#8220;&#8221;);<br />
Serial.print(temp);</p>
<p>// 전송 시작 확인<br />
// 블루투스 시작을 알리는 문자를 s로 설정 !!!<br />
if (temp != &#8216;s&#8217;) {<br />
while (temp != &#8216;e&#8217; ) {<br />
temp = (char)Serial1.read();<br />
}<br />
return;<br />
}<br />
String stringTemp;</p>
<p>while (true) {<br />
// Serial.print(&#8220;arrIndex : &#8220;);<br />
// Serial.println(arrIndex);</p>
<p>if (Serial1.available()) {<br />
temp = (char)Serial1.read();<br />
Serial.print(temp);</p>
<p>if (arrIndex &gt; 10) {<br />
// Serial.println(&#8220;내부데이터 변수 이상 &#8212;&#8212;&#8212;-&#8221;);<br />
break;<br />
}</p>
<p>// &#8216;_&#8217; 라는 문자를 통해 구분을 지어준다. 구분을 지어주는 표시라고 생각을 하면된다. &#8212;&#8212;&#8212;&#8211;<br />
if (temp != &#8216;_&#8217;) {</p>
<p>// &#8216;e&#8217; 라는 문자가 나오게 되면 블루투스 통신이 끝났다는 의미이다.<br />
if (temp == &#8216;e&#8217;) {<br />
break;<br />
}</p>
<p>if (isAlpha(temp)) { //temp라는 변수가 문자인지 확인을 한다.</p>
<p>blockData[arrIndex] = temp;<br />
arrIndex++;</p>
<p>} else if (isDigit(temp)) { //temp라는 변수가 숫자인지 확인을 한다.<br />
stringTemp += temp;</p>
<p>} else {<br />
// 원하지 않는 데이터 값<br />
Serial.println(&#8220;upload failed. error: not digit or alpha&#8221;);<br />
arrIndex = 0;<br />
return;<br />
}<br />
}</p>
<p>else if (temp == &#8216;_&#8217;) {<br />
if (stringTemp == &#8220;&#8221;)<br />
continue; // 아래의 것들을 건너뛴다.<br />
blockData[arrIndex] = stringTemp.toInt();<br />
//변수 초기화<br />
stringTemp = &#8220;&#8221;;<br />
arrIndex++;<br />
}<br />
}<br />
}<br />
Serial.println(&#8220;끝&#8221;);<br />
Serial.println(&#8220;&#8221;);<br />
delay(30);<br />
}</p>
<p>// int out은 외부미세먼지농도, int in 내부미세먼지농도<br />
void window_open_close(int out, int in) {</p>
<p>// 내부미세먼지농도가 낮아서 공기정화를 할 필요없는 경우<br />
if(in &lt; 3){<br />
return;}<br />
if (in &lt; 25) {<br />
Serial.println(&#8220;내부 공기가 매우 깨끗합니다.&#8221;);<br />
val1 = 100;<br />
if (val1 != val2) {<br />
myservo.write(val1); // 창문이 닫힘.<br />
val2 = val1;<br />
delay(10);<br />
}<br />
return;<br />
}<br />
// 내부미세먼지농도가 안좋은 경우<br />
else {<br />
if (out &lt; in) {<br />
Serial.println(&#8220;환기를 위해 창문 open. &#8220;);<br />
val1 = 0;<br />
if (val1 != val2) {<br />
myservo.write(val1); // 창문이 닫힘.<br />
val2 = val1;<br />
delay(10);<br />
}<br />
delay(10);<br />
} else {<br />
Serial.println(&#8220;외부 미세먼지 농도가 높아, 창문 close. &#8220;);<br />
val1 = 100;<br />
if (val1 != val2) {<br />
myservo.write(val1); // 창문이 닫힘.<br />
val2 = val1;<br />
delay(10);<br />
}<br />
delay(10);<br />
}<br />
}<br />
}</p>
<p>void data_print1() {<br />
Serial.print(&#8220;외부센서데이터 : &#8220;);<br />
Serial.print(&#8220;humidity : &#8220;);<br />
Serial.print(refined_humidity);<br />
Serial.print(&#8221; temp :&#8221;);<br />
Serial.print(refined_temperature);<br />
Serial.print(&#8221; cds : &#8220;);<br />
Serial.print(refined_value);<br />
Serial.print(&#8221; dust : &#8220;);<br />
Serial.println(refined_dust_data);<br />
delay(300);<br />
}</p>
<p>void get_data_print2() {<br />
Serial.print(&#8220;내부센서데이터 : &#8220;);<br />
Serial.print(&#8220;humidity : &#8220;);<br />
Serial.print(refined_humidity2);<br />
Serial.print(&#8221; temp :&#8221;);<br />
Serial.print(refined_temperature2);<br />
Serial.print(&#8221; cds : &#8220;);<br />
Serial.print(refined_value2);<br />
Serial.print(&#8221; dust : &#8220;);<br />
Serial.println(refined_dust_data2);<br />
delay(300);<br />
아두이노 코드 2 &#8211; 내부 담당 코드<br />
#include &#8220;SoftwareSerial.h&#8221;<br />
#include &lt;Adafruit_NeoPixel.h&gt;<br />
#include &#8220;DHT.h&#8221;<br />
// I2C LCD를 쉽게 제어하기 위한 라이브러리를 추가해줍니다.<br />
#include &lt;LiquidCrystal_I2C.h&gt;</p>
<p>/*미세먼지센서 변수들*/<br />
#define measurePin A1 //Connect dust sensor to Arduino A0 pin<br />
#define ledPower 6 //Connect 3 led driver pins of dust sensor to Arduino D4<br />
#define SampleTime 30 //먼지센서 30회 샘플링</p>
<p>int samplingTime = 280;<br />
int deltaTime = 40;<br />
int sleepTime = 9680;<br />
float voMeasured = 0;<br />
float calcVoltage = 0;<br />
float dustDensity = 0;<br />
// 정제된 변수값<br />
byte refined_humidity = 0;<br />
byte refined_temperature = 0;<br />
byte refined_value = 0;<br />
byte refined_dust_data = 0;</p>
<p>//조도센서&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;<br />
#define cds A0</p>
<p>//부저 &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<br />
#define buzzer 7</p>
<p>//모터팬 &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<br />
#define fan 9</p>
<p>// 온습도 센서를 디지털 4번 핀에 연결합니다.<br />
#define DHTPIN 8<br />
#define DHTTYPE DHT11</p>
<p>//&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-</p>
<p>//네오픽셀 연결 핀<br />
#define PIN1 4</p>
<p>//네오픽셀 LED개수<br />
#define NUMPIXELS 4<br />
Adafruit_NeoPixel pixels1 = Adafruit_NeoPixel(NUMPIXELS, PIN1, NEO_GRB + NEO_KHZ800);<br />
int delayval = 10;<br />
int color_value=0;</p>
<p>DHT dht(DHTPIN, DHTTYPE);<br />
SoftwareSerial Serial1(2, 3); // rx,tx</p>
<p>// LCD의 경우, 고유 주소를 가지고 있는데 대부분의 LCD는 2개의 주소 중 하나로 세팅되어 있다.<br />
// 0x3F, 0&#215;27 이렇게 두가지 주소가 있다.<br />
LiquidCrystal_I2C lcd(0&#215;27, 16, 2);</p>
<p>//&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-<br />
void SendData();<br />
void LED_RB1();<br />
void setup () {</p>
<p>// 시리얼통신<br />
Serial.begin(9600);<br />
// 블루투스 통신<br />
Serial1.begin(9600);<br />
// 조도센서<br />
pinMode(cds, INPUT);<br />
// 미세먼지센서<br />
pinMode(ledPower, OUTPUT);<br />
pinMode(measurePin, INPUT);<br />
//온도습도센서 시작<br />
dht.begin();<br />
// 12V 모터팬<br />
pinMode(fan, OUTPUT);<br />
// 네오픽셀 시작<br />
pixels1.begin(); // This initializes the NeoPixel library.</p>
<p>// 부저 // #define buzzer 7<br />
pinMode(buzzer, OUTPUT);<br />
lcd.init();<br />
lcd.backlight();<br />
lcd.clear();<br />
}<br />
int dust_val(); // 먼지 값받아오는 함수<br />
void loop () {</p>
<p>// 습도와 온도값을 측정하고, 제대로 측정되었는지 확인해줍니다.<br />
byte humidity = dht.readHumidity();<br />
byte temperature = dht.readTemperature();</p>
<p>refined_humidity = constrain(humidity, 0, 100);<br />
refined_temperature = constrain(temperature, 0, 100);</p>
<p>// 조도센서 값<br />
int value = analogRead(cds);<br />
refined_value = map(value, 0, 1023, 100, 0);<br />
color_value=map(refined_value, 10, 100, 0, 255);</p>
<p>// 먼지센서 값<br />
int dust_data = dust_val(); // 먼지량을 받아옴<br />
refined_dust_data = map(dust_data, 0, 1023, 0, 100);</p>
<p>Serial.print(&#8221; temp : &#8220;);<br />
Serial.print(refined_temperature);<br />
Serial.print(&#8220;&#8216;c &#8220;);<br />
Serial.print(&#8221; humidity : &#8220;);<br />
Serial.print(refined_humidity);<br />
Serial.print(&#8220;% &#8220;);<br />
Serial.print(&#8221; cds : &#8220;);<br />
Serial.print(refined_value);<br />
Serial.print(&#8221; dust : &#8220;);<br />
Serial.println(refined_dust_data);</p>
<p>lcd.clear();<br />
lcd.setCursor(0, 0);<br />
lcd.print(&#8220;hm:&#8221;);<br />
lcd.print(refined_humidity);<br />
lcd.print(&#8221; tp:&#8221;);<br />
lcd.print(refined_temperature);<br />
lcd.setCursor(0, 1);<br />
lcd.print(&#8220;cds :&#8221;);<br />
lcd.print(refined_value);<br />
lcd.print(&#8221; dust :&#8221;);<br />
lcd.print(refined_dust_data);</p>
<p>// 미세먼지 농도에 따라 색변화와 부저를 활용하여 경고음 제공하기.</p>
<p>if (refined_dust_data &gt; 68) {<br />
analogWrite(fan,150);<br />
delay(10);<br />
LED_R1();<br />
tone(buzzer, 1000);<br />
} else if (refined_dust_data &lt;= 68 &amp;&amp; refined_dust_data &gt; 50 ) {<br />
analogWrite(fan,150);<br />
noTone(buzzer);<br />
delay(10);<br />
LED_RB1();<br />
} else if (refined_dust_data &lt;= 50 &amp;&amp; refined_dust_data &gt; 25) {<br />
analogWrite(fan, 120);<br />
noTone(buzzer);<br />
delay(10);<br />
LED_B1();<br />
} else {<br />
analogWrite(fan, 0);<br />
noTone(buzzer);<br />
LED_G1();<br />
}</p>
<p>SendData();<br />
}</p>
<p>//&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-</p>
<p>void LED_R1() {<br />
for (int i = 0; i &lt; NUMPIXELS; i++) {<br />
pixels1.setPixelColor(i, pixels1.Color(color_value, 0, 0)); // Moderately bright green color.<br />
pixels1.show(); // This sends the updated pixel color to the hardware.<br />
delay(delayval); // Delay for a period of time (in milliseconds).<br />
}<br />
}</p>
<p>void LED_RB1() {<br />
for (int i = 0; i &lt; NUMPIXELS; i++) {<br />
pixels1.setPixelColor(i, pixels1.Color(color_value, 0, color_value/2)); // Moderately bright green color.<br />
pixels1.show(); // This sends the updated pixel color to the hardware.<br />
delay(delayval); // Delay for a period of time (in milliseconds).<br />
}<br />
}</p>
<p>void LED_G1() {</p>
<p>for (int i = 0; i &lt; NUMPIXELS; i++) {<br />
pixels1.setPixelColor(i, pixels1.Color(0, color_value, 0)); // Moderately bright green color.<br />
pixels1.show(); // This sends the updated pixel color to the hardware.<br />
delay(delayval); // Delay for a period of time (in milliseconds).<br />
}<br />
}</p>
<p>void LED_B1() {<br />
for (int i = 0; i &lt; NUMPIXELS; i++) {<br />
pixels1.setPixelColor(i, pixels1.Color(0, 0, color_value)); // Moderately bright green color.<br />
pixels1.show(); // This sends the updated pixel color to the hardware.<br />
delay(delayval); // Delay for a period of time (in milliseconds).<br />
}<br />
}</p>
<p>void rain_color1() {</p>
<p>for (int i = 0; i &lt; NUMPIXELS; i++) {<br />
int a = random(0, 255);<br />
int b = random(0, 255);<br />
int c = random(0, 255);</p>
<p>pixels1.setPixelColor(i, pixels1.Color(a, b, c)); // Moderately bright green color.<br />
pixels1.show(); // This sends the updated pixel color to the hardware.<br />
delay(delayval); // Delay for a period of time (in milliseconds).<br />
}<br />
}<br />
//int dust_val() //센서의 노이즈 값으로인해 여러번 데이터를 받은뒤 평균 먼지량을 구함<br />
{<br />
int dust = 0;<br />
for (int i = 0; i &lt; SampleTime; i++)<br />
{<br />
digitalWrite(ledPower, LOW); // power on the LED 0.028초부터 led가 가장 밝음으로 최소샘플링 타임으로 결정<br />
delayMicroseconds(samplingTime);</p>
<p>voMeasured = analogRead(measurePin); // read the dust value<br />
delayMicroseconds(deltaTime);<br />
digitalWrite(ledPower, HIGH); // turn the LED off<br />
delayMicroseconds(sleepTime);<br />
////////////////////////////////// dust vlaue 받아오기<br />
calcVoltage = voMeasured * (5.0 / 1024.0);<br />
dustDensity = calcVoltage / 0.005; //(0.17 * calcVoltage &#8211; 0.1) * 1000;<br />
////////////value 의 미세한 변화를 수식으로 먼지량으로 변환<br />
if (dustDensity &lt; 0) { // value 의 값이 마이너스일경우 오류 임으로 0을 대입<br />
dustDensity = 0;<br />
}<br />
dust += dustDensity;<br />
}<br />
return dust / SampleTime; // 센싱한값의 평균을 return<br />
}</p>
<p>void SendData() {<br />
Serial1.print(&#8220;s_&#8221;);<br />
Serial1.print(&#8220;h_&#8221;);<br />
Serial1.print(refined_humidity);<br />
delay(3);<br />
Serial1.print(&#8220;_t_&#8221;);<br />
Serial1.print(refined_temperature);<br />
delay(3);<br />
Serial1.print(&#8220;_c_&#8221;);<br />
Serial1.print(refined_value);<br />
delay(3);<br />
Serial1.print(&#8220;_d_&#8221;);<br />
delay(3);<br />
Serial1.print(&#8220;_e&#8221;);<br />
delay(200);<br />
}<br />
</div>
<p>&nbsp;</p>
<p>&nbsp;</p>
]]></content:encoded>
			<wfw:commentRss>http://www.ntrexgo.com/archives/40667/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>[66호] Automatic Serving &amp; Order System</title>
		<link>http://www.ntrexgo.com/archives/40672</link>
		<comments>http://www.ntrexgo.com/archives/40672#comments</comments>
		<pubDate>Wed, 23 Jun 2021 00:00:33 +0000</pubDate>
		<dc:creator>디바이스마트 매거진</dc:creator>
				<category><![CDATA[디바이스마트 매거진]]></category>
		<category><![CDATA[특집]]></category>
		<category><![CDATA[66호]]></category>
		<category><![CDATA[ict]]></category>
		<category><![CDATA[공모전]]></category>
		<category><![CDATA[디바이스마트]]></category>
		<category><![CDATA[매거진]]></category>
		<category><![CDATA[서빙]]></category>
		<category><![CDATA[오더시스템]]></category>
		<category><![CDATA[장려상]]></category>

		<guid isPermaLink="false">http://www.ntrexgo.com/?p=40672</guid>
		<description><![CDATA[디바이스마트매거진 66호 &#124; 서빙 로봇은 Wi-Fi와 연결되었기 때문에 공유기가 설치된 환경이면 어디서든 환경이 가능하고, 주문한 음식은 서버에 저장되기 때문에 완벽한 IoT를 구현하였다. ]]></description>
				<content:encoded><![CDATA[<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-1.png" rel="lightbox[40672]"><img class="alignnone size-large wp-image-40678" alt="66_ict_환기제어 (1)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66_ict_환기제어-1-620x175.png" width="620" height="175" /></a></p>
<p><strong style="font-size: medium">2020 ICT 융합 프로젝트 공모전 장려상</strong></p>
<p><span style="font-size: xx-large;color: #0000ff"><strong>Automatic Serving &amp; Order System</strong></span></p>
<p>&nbsp;</p>
<p style="text-align: right">글 | 서울시립대학교 박진석, 서재원, 송태헌</p>
<p style="text-align: right">
<p><span style="color: #0000ff"><strong>1. 심사평</strong></span><br />
<strong>칩센</strong> 라인트레이서의 기본 기능에 서버 시스템과의 연계를 통한 특정 위치를 찾아가게 하는 동작으로 무인 서버 로봇을 구성한 것으로, 사실 특이할 만한 점은 보이지 않아 보입니다. 모터구동과 관련하여 부품을 선정하고, 적용하기 위하여 사전에 촘촘한 선행 검토와 모델링을 진행한 것은 매우 잘 진행 된 듯 합니다. 최근에 Indoor-position 관련한 솔루션이 매우 활발하게 검토되고 개발이 되고 있는데, 이를 이용하여 라인트레이싱이 아닌 일반적인 환경에서의 서버 로봇을 만들 수 있다면 최근 트렌드와 맞는 더 진보한 작품이 되지 않을까 하는 생각이 듭니다.<br />
<strong></strong></p>
<p><strong>펌테크</strong> 아이디어와 실용성, 상업성을 두루 갖춘 작품이라고 생각합니다. 전체적으로 세심하고 짜임새 있게 잘 구성이 되었다고 생각되며 기술적 구현도, 작품 완성도 등이 높은 작품이라고 생각합니다.<br />
<strong></strong></p>
<p><strong>위드로봇</strong> 이동로봇의 주행 중에 발생하는 문제는 어떻게 해결하였는지 고민이 더 추가되면 좋은 작품이 될 것 같습니다.</p>
<p><span style="color: #0000ff"><strong>2. 개요</strong></span><br />
본 프로젝트는 식당에서 사용할 수 있는 자동으로 서빙을 해주는 무인 서빙 로봇, 주문을 해주는 어플리케이션과 주문을 확인하고 서빙 명령이 가능한 웹페이지 제작을 한다. 서빙 로봇은 Wi-Fi와 연결되었기 때문에 공유기가 설치된 환경이면 어디서든 환경이 가능하고, 주문한 음식은 서버에 저장되기 때문에 완벽한 IoT를 구현하였다.</p>
<p><span style="color: #00ccff"><strong>2.1. 개발 배경</strong></span><br />
현대사회에서 생산성과 효율을 위해서 많은 분야에서 무인/자동화가 이루어지고 있다. 이러한 추세에 따라 매장에 무인 주문 결제기인 ‘키오스크’가 설치되고 있고, 일부 매장은 어플로 주문을 하는 시스템을 도입했다. 따라서 위의 주문 시스템에 자동으로 서빙을 로봇을 융합함으로 IoT를 이용한 무인 서비스 제공을 목표로 한다.</p>
<p><span style="color: #00ccff"><strong>2.2. 프로젝트 목적 및 기대효과</strong></span><br />
이 프로젝트의 목적은 주문/서빙 시스템의 자동화이다. 병렬적으로 주문을 할 수 있는 무인 주문 서비스와 무인 서빙 서비스를 만드는 것이 목표이다. 무인 서빙 서비스가 있기 때문에 서빙에 필요한 인건비가 줄어들어 경제적이고 효율적이다. 무인 주문 서비스를 통해서 순간적으로 사람이 집중이 되더라도 주문하는데 기다릴 필요가 없게 된다. 또한, 주방에서 출력된 종이 영수증을 보통 주문의 순서를 정하는데, 이 프로젝트를 통해서 종이 영수증대신 태블릿 PC와 같은 전자기기로 대체할 수 있을 것이다. 이에 따라 주방에서 낭비되는 종이의 양을 줄여서 환경보호면에서도 효과가 있다. 또한, 보드는 Arduino UNO만을 사용하고 성능을 대부분 사용하여 개발을 했기 때문에 실제 제품으로 만들 시 제품 비용 절감이 매우 절감될 것이다.</p>
<p><span style="color: #00ccff"><strong>2.3. 개발 목표</strong></span><br />
서빙 할 테이블의 번호를 지정하면 자동으로 서빙을 해주는 서비스 구현과 많은 사람들이 집중되더라도 한꺼번에 처리가 가능한 무인 병렬 주문 서비스 제공이 목표이다.</p>
<p><span style="color: #00ccff"><strong>2.4. 세부 개발 내용</strong></span><br />
네이버 클라우드에 비용을 지불해서 구축한 서버에 APM(Apache2, PHP, MySQL)을 설치하고, 웹 페이지와 안드로이드 어플을 제작하여 주문한 데이터가 DB인 MySQL에 저장되도록 한다. 관리자는 관리자용 웹페이지를 통해서 MySQL에 저장된 데이터를 확인해서 들어온 주문을 확인할 수 있다. 서빙을 원하는 테이블을 웹 페이지에 입력하면 마찬가지로 MySQL에 저장이 된다. 서빙 로봇은 매장에 설치된 Wi-Fi와 연결된 상태로 구동되고, 일정 간격으로 MySQL을 확인해서 서빙 요청이 왔는지 확인한다. 서빙 명령을 받게 되면 목표 테이블까지 라인트레이싱을 하며 도달하게 된다. 이 때 서빙 하는 음식의 무게에 상관없고, 가속도에 의한 음식의 미끄러짐을 최소화하기 위해서 모터 PID속도 제어를 통해서 운동한다.</p>
<p><span style="color: #0000ff"><strong>3. 프로젝트 설명</strong></span><br />
<span style="color: #00ccff"><strong>3.1. 주요 동작 및 특징</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-1.jpg" rel="lightbox[40672]"><img class="alignnone size-full wp-image-40694" alt="66 ict_오더시스템 (1)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-1.jpg" width="614" height="330" /></a></p>
<p>서빙로봇이 Wi-Fi에 연결된 상태로 1초간격으로 MySQL 데이터를 읽어들이며 주문을 기다리고 있다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-2.jpg" rel="lightbox[40672]"><img class="alignnone size-large wp-image-40696" alt="66 ict_오더시스템 (2)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-2-537x620.jpg" width="537" height="620" /></a></p>
<p>원하는 음식과 테이블을 선택해서 MySQL로 데이터를 전달해주는 어플리케이션이다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-3.jpg" rel="lightbox[40672]"><img class="alignnone size-full wp-image-40698" alt="66 ict_오더시스템 (3)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-3.jpg" width="609" height="363" /></a></p>
<p>MySQL에 저장된 데이터를 읽을 수 있고, 테이블번호를 선택하면 Arduino Called열 값을 1로 바꿔서 서빙 로봇에게 서빙 명령을 하는 관리자용 웹 페이지이다. (http://106.10.49.227/order_list.php)</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-4.jpg" rel="lightbox[40672]"><img class="alignnone size-full wp-image-40700" alt="66 ict_오더시스템 (4)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-4.jpg" width="613" height="441" /></a></p>
<p>서빙 로봇이 Wi-Fi를 통해서 MySQL에서 Serving Completed열이 0이고 Arduino Called가 1인 테이블 번호를 읽어 들어서 해당 테이블로 음식을 서빙하고 있다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-2.png" rel="lightbox[40672]"><img class="alignnone size-full wp-image-40697" alt="66 ict_오더시스템 (2)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-2.png" width="611" height="189" /></a></p>
<p>서빙이 완료된 후 MySQL의 Serving Completed열 값을 1로 변경해서 서빙이 완료됨을 알리는 서빙 로봇 창이다.</p>
<p><span style="color: #00ccff"><strong>3.2. 전체 시스템 구성</strong></span><br />
<strong>3.2.1. Software Architecture_System Flowchart</strong></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-3.png" rel="lightbox[40672]"><img class="alignnone size-large wp-image-40699" alt="66 ict_오더시스템 (3)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-3-620x432.png" width="620" height="432" /></a></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-4.png" rel="lightbox[40672]"><img class="alignnone size-full wp-image-40701" alt="66 ict_오더시스템 (4)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-4.png" width="606" height="594" /></a></p>
<p>손님이 주문을 하고 서빙 로봇(아두이노)이 서빙을 하기 위한 방법으로 서버를 사용하였다. 손님이 주문을 하면 주문내역이 서버의 데이터베이스에 저장이 되고 주방에서는 서버의 데이터베이스를 확인하여 서빙 로봇을 호출한다. 서빙 로봇에서는 와이파이에 연결되어 있으며 일정한 간격으로 서버의 데이터베이스에 접근하여 호출 신호가 왔을 때, 지정된 테이블로 음식을 서빙 한다.<br />
<strong></strong></p>
<p><strong>3.2.2. Hardware Architecture</strong><br />
<strong></strong></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-5.png" rel="lightbox[40672]"><img class="alignnone size-large wp-image-40703" alt="66 ict_오더시스템 (5)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-5-620x239.png" width="620" height="239" /></a></p>
<p><span style="color: #00ccff"><strong>3.3. 개발 환경(개발 언어, Tool, 사용 시스템 등)</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-6.png" rel="lightbox[40672]"><img class="alignnone size-large wp-image-40705" alt="66 ict_오더시스템 (6)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-6-620x385.png" width="620" height="385" /></a></p>
<p><span style="color: #0000ff"><strong>4. 단계별 제작 과정</strong></span><br />
<span style="color: #00ccff"><strong>4.1. 서버구축</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-7.png" rel="lightbox[40672]"><img class="alignnone size-large wp-image-40707" alt="66 ict_오더시스템 (7)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-7-620x155.png" width="620" height="155" /></a></p>
<p><span style="color: #000000">서버는 네이버 클라우스 서비스를 사용하여 구축하였다. 기본적으로 유료서비스이지만 작은 규모의 서버를 1년동안 무료로 사용할 수 있는 이벤트가 있어서 네이버 서버를 선택하였다. 서버의 사양은 [Micro] 1vCPU, 1GB Mem, 50GB Disk이라 성능은 좋지 않지만 이번 프로젝트에서는 문제가 없다고 판단하였다. 서버의 OS는 Ubuntu 16.04 LTS Server를 설치하였다.</span></p>
<p><span style="color: #00ccff"><strong>4.2. APM(Apache2/PHP/MySQL) 설치 및 ACG 설정</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-8.png" rel="lightbox[40672]"><img class="alignnone size-large wp-image-40708" alt="66 ict_오더시스템 (8)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-8-620x353.png" width="620" height="353" /></a></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-9.png" rel="lightbox[40672]"><img class="alignnone size-large wp-image-40709" alt="66 ict_오더시스템 (9)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-9-620x553.png" width="620" height="553" /></a></p>
<p><span style="color: #000000">네이버클라우드에서 할당받은 서버에 APM을 설치한 뒤 공인 IP인 http://106.10.49.227/에 접근할 수 있도록 80번 포트를 ACG를 통해서 열어주었다. 인터넷이 연결된 어떤 환경에서도 해당 주소로 이동하면 Apache2디폴트 페이지를 확인할 수 있다.</span></p>
<p><span style="color: #00ccff"><strong>4.3. MySQL 테이블 구축</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-10.png" rel="lightbox[40672]"><img class="alignnone size-large wp-image-40710" alt="66 ict_오더시스템 (10)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-10-620x77.png" width="620" height="77" /></a></p>
<p>Windows에서 MySQL Workbench를 설치하여 좀 더 쉽게 MySQL를 다룰 수 있도록 하였다. orderDB의 테이블 속, 각 데이터들은 다음을 의미한다. _order는 주문순서, time는 주문시간, _table는 주문 테이블 번호, food는 주문한 음식과 수량, serving는 서빙이 완료되었는지 안되었는지, call_arduino는 서빙 로봇이 확인하는 데이터로써 값이 1일 경우, 서빙 로봇이 음식을 서빙한다.<br />
_order열에는 값을 입력하지 않아도 자동으로 값이 1씩 증가하도록 설정하였고, time은 ubuntu서버에 있는 시간을 자동으로 입력받도록 설정하였다. 아래에 기술할 웹페이지에서 _table, food, call_arduino열을 설정할 수 있도록 연동하였다.</p>
<p><span style="color: #00ccff"><strong>4.4. 웹 페이지 및 애플리케이션 제작</strong></span><br />
<span style="color: #339966"><strong>4.4.1. 주문 애플리케이션 제작</strong></span><br />
AndroidStudio를 이용하여 주문 애플리케이션을 개발하였다. 메뉴 리스트 화면, 메뉴 상세 화면, 장바구니 화면으로 구성되었다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-11.png" rel="lightbox[40672]"><img class="alignnone size-large wp-image-40711" alt="66 ict_오더시스템 (11)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-11-620x420.png" width="620" height="420" /></a></p>
<p>메뉴 리스트 화면: 처음 앱을 켰을 때, 나타나는 화면이다. 내부 데이터베이스에서 저장된 모든 메뉴의 사진 썸네일과 이름, 가격을 표시하며, 각 메뉴를 선택하면 메뉴 상세화면으로 넘어간다.<br />
메뉴 상세화면: 메뉴 상세화면은 메뉴 리스트 화면에서 선택된 메뉴의 사진과 이름, 가격을 확인할 수 있으며, 수량을 체크하고 ‘장바구니 담기’버튼을 선택하여, 내부 데이터베이스에 저장한다. 만약 해당 메뉴가 이미 데이터베이스에 저장되어 있으면 해당 메뉴의 수량만 변경한다.<br />
장바구니 화면: 장바구니 화면은 모든 화면에서 액션바에 있는 버튼을 통해 접근할 수 있다. 데이터베이스에 저장된 장바구니 리스트를 가져와서 보여준다. x버튼을 누르면, 해당 메뉴를 데이터베이스에서 지움으로 장바구니 리스트에서 지운다. 결제하기 버튼을 통해 서버와 Http통신을 하며, 테이블 번호와 함께 장바구니 리스트를 전달한다. 서버로부터 RESULT_OK 신호가 오면, “결제가 완료되었습니다.”라는 안내와 함께 장바구니 리스트를 모두 지운다.</p>
<p><span style="color: #339966"><strong>4.4.2. 관리자용 웹 페이지 제작</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-12.png" rel="lightbox[40672]"><img class="alignnone size-large wp-image-40712" alt="66 ict_오더시스템 (12)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-12-620x405.png" width="620" height="405" /></a></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-13.png" rel="lightbox[40672]"><img class="alignnone size-large wp-image-40713" alt="66 ict_오더시스템 (13)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-13-620x366.png" width="620" height="366" /></a></p>
<p>이미 구축한 리눅스 서버에 Putty를 사용해서 터미널 접속을 한 후 /var/www/html/ 경로에 php파일을 생성하면 위와 같이 공인아이피 http://106.10.49.227/파일이름.php 로 접속할 수 있다. 본 프로젝트에서 /var/www/html /order_list.php에 php와 html을 사용해서 코딩했다.<br />
손님용 웹페이지에서 입력한 MySQL 정보를 읽어와서 표로 표현해주는 기능을 한다. 요리가 완료되면 아래에 있는 테이블 번호를 입력하고 ‘제출’버튼을 누르면 Arduino Called열이 1로 바뀌어 서빙 로봇에게 서빙 명령을 내릴 수 있다.</p>
<p><span style="color: #00ccff"><strong>4.5. Arduino에서 Wi-Fi통신을 통한 MySQL 데이터 수신</strong></span><br />
Arduino에서 Wi-Fi통신을 한 후에 MySQL로 바로 접근해서 query를 입력해주면 MySQL에서 해당하는 기능을 실행하는 동작을 구현하였다. 소스코드에서 char query[] = &#8220;SELECT _table FROM project.orderDB WHERE call_arduino = &#8217;1&#8242; AND serving = &#8217;0&#8242; ;&#8221;; char query2[] = &#8220;UPDATE project.orderDB SET serving = &#8217;1&#8242; WHERE call_arduino = &#8217;1&#8242; AND serving = &#8217;0&#8242; ;&#8221;; 에 명령을 저장하고, cur_mem-&gt;execute(query); 명령어로 query배열에 담긴 내용을 MySQL에 입력할 수 있다. 여기에서 query는 MySQL에서 call_arduino 열 값이 1이고 serving 열 값이 0인 _table열의 값을 return하는 기능을 하고, query2는 call_arduino열 값이 1이고 serving 열 값이 0인, 즉 query에서 return한 _table열의 같은 값의 serving열을 1로 바꿔주는 기능을 담당한다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-14.png" rel="lightbox[40672]"><img class="alignnone size-large wp-image-40714" alt="66 ict_오더시스템 (14)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-14-620x187.png" width="620" height="187" /></a></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-15.png" rel="lightbox[40672]"><img class="alignnone size-large wp-image-40715" alt="66 ict_오더시스템 (15)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-15-620x219.png" width="620" height="219" /></a></p>
<p>&nbsp;</p>
<p>위의 그림은 serving열의 값이 0이고 call_arduino열의 값이 1인 (서빙 명령을 받은 행) 테이블 번호의 MySQL값을 1초마다 Arduino에서 읽어 들이는 모습을 나타낸다.</p>
<p><span style="color: #00ccff"><strong>4.6. DC모터의 PID Feedback Control</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-16.png" rel="lightbox[40672]"><img class="alignnone size-large wp-image-40716" alt="66 ict_오더시스템 (16)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-16-620x314.png" width="620" height="314" /></a></p>
<p>위의 모터의 Specification을 참고해서 [T:토크(kgf-cm) V:전압(V), I:전류(A), w:각속도(rad/s), R:저항(Ω)] 아래의 관계식을 세울 수 있다.</p>
<p style="text-align: center">T∝<br />
V=IR</p>
<p>두 개의 식을 연립하고 비례상수 k를 도입하면 아래의 식으로 변환이 가능하다.</p>
<p style="text-align: center">Tw=k</p>
<p>Stall Torque는 최대 토크의 값을 의미하고, 이는 최대 전압인 6V와 Stall Current인 3.2A일 때 성립한다. 따라서 옴의 법칙에 의해서</p>
<p style="text-align: center">6=3.2R<br />
R=1.875Ω</p>
<p>모터의 저항은 1.875Ω이다.<br />
또한, DC모터의 스펙으로부터 전류에 다른 토크의 값을 아래와 같이 기재할 수 있다.<br />
DC모터, 아두이노 차체 시스템에서 차체를 모델링하여 전달함수를 구하기 위해 Free Body Diagram을 그려서 계산할 필요가 있다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-17.png" rel="lightbox[40672]"><img class="alignnone size-large wp-image-40717" alt="66 ict_오더시스템 (17)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-17-597x620.png" width="597" height="620" /></a></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-18.png" rel="lightbox[40672]"><img class="alignnone size-large wp-image-40718" alt="66 ict_오더시스템 (18)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-18-620x240.png" width="620" height="240" /></a></p>
<p>위의 그림에서 오른쪽에 있는 두 개의 원이 모터가 달린 바퀴이고, 왼쪽에 있는 원은 캐스터이다. 바닥과의 마찰이 충분하여 바퀴가 헛도는 상황이 발생하지 않는다고 가정하면, 모터에 의한 토크는 바퀴 두 개에 가해지는 힘으로 생각할 수 있다. 위의 그림을 다시 차체에 대해서만 Free Body Diagram을 그리면 아래와 같이 간단한 형태로 나타낼 수 있다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-19.png" rel="lightbox[40672]"><img alt="66 ict_오더시스템 (19)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-19-620x241.png" width="620" height="241" /></a></p>
<p>모터에 의한 추진력 2F와 공기저항과 같은 저항에 의한 반력이 생기기 때문에 cv를 도입하였으나, 영향은 작을 것으로 예측되어 c=0.1인 낮은 값으로 가정하였다. 바퀴의 반지름이 3.25cm인 것을 고려하여 운동방정식을 세우면 아래와 같다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-20.png" rel="lightbox[40672]"><img class="alignnone  wp-image-40720" alt="66 ict_오더시스템 (20)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-20.png" width="150" /></a></p>
<p>여기에서 은 비선형이므로, 라플라스 변환을 하기 위하여 테일러 급수를 이용해 정상상태에서의 선형화한 값을 도출해낸다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-21.png" rel="lightbox[40672]"><img class="alignnone  wp-image-40721" alt="66 ict_오더시스템 (21)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-21-620x102.png" width="300" /></a></p>
<p>차체의 무게는 약 400g이고, 정상상태에서 가속도는 작은 것을 목표로 =0.01cm/s2을 대입하여 이 식을 정리한다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-22.png" rel="lightbox[40672]"><img class="alignnone  wp-image-40722" alt="66 ict_오더시스템 (22)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-22-620x103.png" width="400" /></a></p>
<p>Fitting Line에서 구한 T=2.844·I+1.088을 대입하여 위의 식을 정리한다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-23.png" rel="lightbox[40672]"><img class="alignnone  wp-image-40723" alt="66 ict_오더시스템 (23)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-23-620x61.png" width="500" /></a></p>
<p>상수를 모두 없애기 위해서, 이 식을 편차변수로 나타낼 수 있다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-24.png" rel="lightbox[40672]"><img alt="66 ict_오더시스템 (24)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-24-620x108.png" width="250" /></a></p>
<p>옴의 법칙을 이용해서 전류를 전압에 대한 식으로 바꾼다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-25.png" rel="lightbox[40672]"><img class="alignnone  wp-image-40725" alt="66 ict_오더시스템 (25)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-25-620x83.png" width="400" /></a><br />
위 식을 라플라스 변환하면 다음과 같이 표현할 수 있다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-26.png" rel="lightbox[40672]"><img class="alignnone  wp-image-40726" alt="66 ict_오더시스템 (26)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-26-620x149.png" width="350" /></a></p>
<p>이 전달함수를 구할 수 있다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-27.png" rel="lightbox[40672]"><img class="alignnone  wp-image-40727" alt="66 ict_오더시스템 (27)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-27.png" width="300" /></a><br />
이 식을 바탕으로 MATLAB을 활용하여 Simulink를 구성하면 다음과 같다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-28.png" rel="lightbox[40672]"><img class="alignnone size-large wp-image-40728" alt="66 ict_오더시스템 (28)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-28-620x171.png" width="620" height="171" /></a></p>
<p>&nbsp;</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-29.png" rel="lightbox[40672]"><img class="alignnone size-large wp-image-40729" alt="66 ict_오더시스템 (29)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-29-620x555.png" width="620" height="555" /></a></p>
<p>&nbsp;</p>
<p>MATLAB에 내장된 PID Tuner를 통해서 PID 계수를 각각 4.227, 2.297, -0.03976으로 결정했다.</p>
<p>&nbsp;</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-30.png" rel="lightbox[40672]"><img alt="66 ict_오더시스템 (30)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-30-620x270.png" width="620" height="270" /></a> <a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-31.png" rel="lightbox[40672]"><img alt="66 ict_오더시스템 (31)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-31-620x293.png" width="620" height="293" /></a></p>
<p>예상된 시뮬레이션으로, 외란(서빙 로봇의 무게 변화)이 존재하는 경우와 존재하지 않는 경우에도 모두 목표 값을 잘 찾아가는 것을 확인할 수 있다.</p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-32.png" rel="lightbox[40672]"><img class="alignnone size-large wp-image-40732" alt="66 ict_오더시스템 (32)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-32-620x406.png" width="620" height="406" /></a></p>
<p>위의 그림은 아두이노에서 DC모터를 실제로 구동하고 시리얼 모니터에서 받아온 Encoder값을 Simulink에서 예측한 그래프에 도사한 것이다. 필터를 사용하지 않았기 때문에 Encoder 자체에서 발생한 노이즈에 의해서 벗어나는 데이터가 보이지만 평균적으로 목표 속도인 30cm/s에 수렴하기 때문에 안정적으로 제어에 성공하였다.</p>
<p><span style="color: #00ccff"><strong>4.7. 아두이노 모터와 센서 핀 구성</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-33.png" rel="lightbox[40672]"><img class="alignnone size-large wp-image-40733" alt="66 ict_오더시스템 (33)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-33-620x404.png" width="620" height="404" /></a></p>
<p>IR센서는 아날로그 신호가 필요하기 때문에 아날로그 핀인 A1, A2, A3, A4를 사용하였다. 모터의 경우는 모터의 속도를 조절하기 위해 PWM 신호를 줄 수 있는 5,6 10,11번 핀을 사용하였다.<br />
모터의 엔코더의 경우는 인터럽트 신호를 사용한다. 따라서 아두이노 우노에서 인터럽트 신호를 사용할 수 있는 유일한 핀인 2번 3번 핀을 사용하였다. 모터의 2개 모두 엔코더를 사용하고 싶었으나 아두이노 우노에는 핀이 부족하여 모터 하나의 엔코더만 사용하기로 하였다.<br />
Wi-Fi를 연결을 위해 ESP-01 어뎁터는 8, 9번 핀을 사용하였다.</p>
<p><span style="color: #00ccff"><strong>4.8. 원하는 테이블로 찾아가는 알고리즘 제작</strong></span><br />
기본적으로 길찾기와 라인트레이서를 분리를 하여 제작을 하였다. 전체적으로 원하는 테이블로 가기 위해서 길을 찾아가는 알고리즘이 돌아가고 길 찾기 알고리즘에서는 모터에게 세부적인 명령을 내리지 않는다. 앞으로 갈지, 좌회전이나 우회전을 할 지 서빙 로봇에게 명령만 내리도록 하였다. 그리고 서빙 로봇은 명령을 받는 대로 라인트레이서 모듈을 활용하여 모터를 가동하도록 하였다.<br />
원하는 테이블로 가기 위해서 교차로를 탐지하였다. 각 테이블마다의 고유의 코스별로 명령들을 저장을 하였다. 예를들어 1번 테이블의 경우는 [직진, 좌회전(첫번째 교차로), 직진]이고 4번 테이블의 경우는 [직진, 우회전(두번째 교차로, 직진)]이다. 교차로를 탐지하는 방법으로는 IR sensor의 양끝부분에서 모두 검정색이 감지가 되면 교차로라고 판단을 하였다. 양끝부분 사이의 거리가 도로의 폭보다 넓기 때문에 가능하다.</p>
<p><span style="color: #00ccff"><strong>4.9. 상황 별 IR sensor 값에 따른 라인트레이서</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-35.png" rel="lightbox[40672]"><img class="alignnone size-large wp-image-40735" alt="66 ict_오더시스템 (35)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-35-620x315.png" width="620" height="315" /></a> <a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-34.png" rel="lightbox[40672]"><img class="alignnone size-full wp-image-40734" alt="66 ict_오더시스템 (34)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-34.png" width="617" height="518" /></a></p>
<p>직진을 하라는 명령이 들어왔을 경우, IR sensor 값 중에서 가운데 두개가 모두 검정색일 때에는 도로 위에 잘 있는 경우이므로 양 모터의 속도를 일정하게 주었다. 하지만 가운데 두개중에서 하나라도 검정색이 아닌 경우에는 도로를 이탈하려는 경우이므로 양 모터의 속도를 다르게 주어서 원래 도로로 복귀할 수 있도록 하였다.<br />
좌회전, 우회전 하라는 명령이 들어왔을 경우에는 양 끝 센서의 값을 확인하면서 회전을 하도록 하였다. 예를들어 좌회전을 하는 경우 가장자리 우측 센서의 값이 검, 흰, 검, 흰 순서대로 바뀌게 된다. 따라서 우측 센서의 값이 바뀔 때마다 카운트를 하도록 하였다. 카운트가 3번되는 순간 회전이 끝났다고 판단을 하였다.</p>
<p><span style="color: #00ccff"><strong>4.10. 서빙 로봇 프레임 및 모형 식당 제작</strong></span></p>
<p><a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-5.jpg" rel="lightbox[40672]"><img class="alignnone size-full wp-image-40702" alt="66 ict_오더시스템 (5)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-5.jpg" width="593" height="568" /></a> <a href="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-6.jpg" rel="lightbox[40672]"><img class="alignnone size-full wp-image-40704" alt="66 ict_오더시스템 (6)" src="http://www.ntrexgo.com/wp-content/uploads/2021/06/66-ict_오더시스템-6.jpg" width="577" height="349" /></a></p>
<p>아두이노 RC카용 프레임에 IR센서, Arduino UNO, Wi-Fi 모듈, 바퀴 등을 조립했다. 배터리와 DC 모터는 프레임의 하단부에 조립했고, 음식을 싣기 위해서 프레임과 조립할 수 있는 받침대를 CATIA로 모델링해서 3D 프린터로 출력하였다.</p>
<p><span style="color: #0000ff"><strong>5. 소스코드</strong></span><br />
프로젝트 깃허브 주소 : https://github.com/ParkJinSuk/ASOS-AutomaticServingOrderSystem</p>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p><strong>FINDTABLE</strong></p>
<p>void lineTracing()<br />
{<br />
/* 0.5초마다 라인트레이서 모듈 센서 측정 */<br />
if((millis() &#8211; time) &gt;= SENSING_PERIOD)<br />
{<br />
time = millis();<br />
getIRSensor();<br />
// showIRSensor();<br />
if(isCrosswalk()){<br />
Serial.println(F(&#8220;갈림길 감지&#8221;));<br />
crosswalkCnt += 1;<br />
if(isTurningTime(crosswalkCnt)){//회전할 갈림길<br />
if(turn[target_table] == LEFT)<br />
Serial.println(F(&#8220;왼쪽&#8221;));<br />
else if(turn[target_table] == RIGHT){<br />
Serial.println(F(&#8220;오른쪽&#8221;));<br />
}<br />
moveTracer(turn[target_table]);//회전한다.<br />
}else{<br />
Serial.println(F(&#8220;갈림길 통과&#8221;));<br />
moveTracer(FORWARD_THROUGH_CROSSWALK);//갈림길 통과<br />
}</p>
<p>}else if(isRoad()){<br />
Serial.println(F(&#8220;straight&#8221;));<br />
moveTracer(STRAIGHT);//앞으로 이동.<br />
}else{<br />
Serial.println(F(&#8220;stop&#8221;));<br />
moveTracer(STOP);//멈춘다.<br />
isDone = true;<br />
}<br />
}</p>
<p>}</p>
<p>bool isCrosswalk(){<br />
return !RightOut &amp;&amp; !LeftOut;//검검이면 갈림길!!<br />
}</p>
<p>bool isTurningTime(int cnt){<br />
Serial.println(cnt);<br />
return cnt == crosswalk[target_table];<br />
}</p>
<p>bool isRoad(){<br />
if (!LeftOut || !LeftIn || !RightIn || !RightOut)<br />
{<br />
cnt_isRoad = 0;<br />
return true;<br />
}<br />
else<br />
{<br />
if (cnt_isRoad != 5){cnt_isRoad += 1; return true;}<br />
if (cnt_isRoad == 5){cnt_isRoad = 0; return false;}<br />
}<br />
}</p>
<p>void moveTracer(int dir){<br />
// Serial.print(&#8220;moveTracer&#8221;);<br />
// Serial.println(dir);</p>
<p>switch(dir){<br />
case STRAIGHT : moveForward();break;<br />
case LEFT : turnLeft();break;<br />
case RIGHT : turnRight();break;<br />
case STOP : stopMoving();break;<br />
case FORWARD_THROUGH_CROSSWALK : moveThroughCrosswalk();break;<br />
}<br />
}<br />
/* 라인트레이서 모듈을 사용하여 검은색 선을 따라가는 함수 */<br />
void moveForward()<br />
{<br />
if (!LeftIn &amp;&amp; !RightIn)<br />
{<br />
// Serial.println(&#8220;forward 11&#8243;);<br />
pidControl_Hz(Hz, 20);<br />
}<br />
else if (LeftIn &amp;&amp; !RightIn)<br />
{<br />
// Serial.println(&#8220;forward 01&#8243;);<br />
MotorA(STRAIGHT, STRAIGHT_SPEED_WEEK);<br />
MotorB(STRAIGHT, STRAIGHT_SPEED_STRONG);<br />
}<br />
else if (!LeftIn &amp;&amp; RightIn)<br />
{<br />
// Serial.println(&#8220;forward 10&#8243;);<br />
MotorA(STRAIGHT, STRAIGHT_SPEED_STRONG);<br />
MotorB(STRAIGHT, STRAIGHT_SPEED_WEEK);<br />
}<br />
else<br />
{<br />
// Serial.println(&#8220;forward xx&#8221;);<br />
pidControl_Hz(Hz, 20);<br />
}</p>
<p>}</p>
<p>void turnLeft()<br />
{<br />
// Serial.println(&#8220;turn left&#8221;);</p>
<p>while(!RightOut || !LeftOut){//하양이면 끝. 도는 동안은 계속 검정.<br />
if((millis() &#8211; time) &gt;= SENSING_PERIOD){<br />
time = millis();<br />
getIRSensor();<br />
}<br />
Serial.println(F(&#8220;왼쪽~~~~~~~~~~~~~~~~&#8221;));<br />
MotorA(STRAIGHT, TURN_SPEED);<br />
MotorB(STRAIGHT, 0);<br />
}<br />
Serial.println(F(&#8220;왼쪽~~~~~~~~~~~~끝~~~~&#8221;));</p>
<p>}<br />
void turnRight()<br />
{<br />
// Serial.println(&#8220;turn right&#8221;);</p>
<p>while(!RightOut || !LeftOut){//하양이면 끝. 도는 동안은 계속 검정.<br />
if((millis() &#8211; time) &gt;= SENSING_PERIOD){<br />
time = millis();<br />
getIRSensor();<br />
}<br />
// if(LeftOut != pre){<br />
// chgCnt += 1;<br />
// pre = LeftOut;<br />
// }<br />
Serial.println(F(&#8220;오른쪽~&#8221;));</p>
<p>MotorA(STRAIGHT, 0);<br />
MotorB(STRAIGHT, TURN_SPEED);<br />
}<br />
Serial.println(F(&#8220;오른끝쪽&#8221;));<br />
}<br />
void stopMoving()<br />
{<br />
// Serial.println(&#8220;stop&#8221;);<br />
MotorA(STOP, 0);<br />
MotorB(STOP, 0);<br />
}</p>
<p>void moveThroughCrosswalk(){<br />
// Serial.println(&#8220;move Through crosswalk&#8221;);<br />
while(isCrosswalk()){<br />
if((millis() &#8211; time) &gt;= SENSING_PERIOD){<br />
time = millis();<br />
getIRSensor();<br />
}</p>
<p>MotorA(STRAIGHT, STRAIGHT_SPEED);<br />
MotorB(STRAIGHT, STRAIGHT_SPEED);<br />
delay(100);<br />
}<br />
}<br />
/****************************/<br />
</div>
<p>&nbsp;</p>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p><strong>IRSensor</strong></p>
<p>/* 프로그램 내용 : bfd-1000 IR센서 관련 함수<br />
*<br />
* getIRSensor IR센서 값 읽어오는 함수<br />
* getIRSensor_Hz 주어진 Hz 속도로 IR센서 값 읽어오는 함수<br />
* showIRSensorChar IR센서 값을 문자로 시리얼모니터로 출력해주는 함수<br />
* showIRSensorValue IR센서 값을 시리얼모니터로 출력해주는 함수<br />
*<br />
*/</p>
<p>void getIRSensor()<br />
{<br />
LeftOut = digitalRead(SS1_LEFT_OUT);<br />
LeftIn = digitalRead(SS2_LEFT_IN);<br />
//Center = digitalRead(SS3_CENTER);<br />
RightIn = digitalRead(SS4_RIGHT_IN);<br />
RightOut = digitalRead(SS5_RIGHT_OUT);<br />
//Bump = digitalRead(CLP_BUMP);<br />
//Near = digitalRead(NEAR);<br />
}</p>
<p>void getIRSensor_Hz(int Hz)<br />
{<br />
if( (millis()-time) % (1000 / Hz) == 0 )<br />
{<br />
getIRSensor();<br />
}<br />
}</p>
<p>void showIRSensorChar()<br />
{<br />
if (LeftOut == 1) {Serial.print(&#8220;L &#8220;);} else {Serial.print(&#8220;- &#8220;);}<br />
if (LeftIn == 1) {Serial.print(&#8220;l &#8220;);} else {Serial.print(&#8220;- &#8220;);}<br />
//if (Center == 1) {Serial.print(&#8220;C &#8220;);} else {Serial.print(&#8220;- &#8220;);}<br />
if (RightIn == 1) {Serial.print(&#8220;r &#8220;);} else {Serial.print(&#8220;- &#8220;);}<br />
if (RightOut == 1) {Serial.print(&#8220;R &#8220;);} else {Serial.print(&#8220;- &#8220;);}<br />
//if (Bump == 1) {Serial.print(&#8221; BUMP!&#8221;);} else {Serial.print(&#8221; &#8220;);}<br />
//if (Near == 1) {Serial.print(&#8221; NEAR&#8221;);} else {Serial.print(&#8221; &#8220;);}<br />
Serial.println();<br />
}</p>
<p>void showIRSensorValue()<br />
{<br />
Serial.print(&#8220;IRSensor : &#8220;);<br />
Serial.print(LeftOut);Serial.print(LeftIn);<br />
Serial.print(RightIn);Serial.print(RightOut);<br />
Serial.println();<br />
}</p>
</div>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p><strong>main_ver3</strong></p>
<p>#include &#8220;WiFiEsp.h&#8221;<br />
#include &lt;MySQL_Connection.h&gt;<br />
#include &lt;MySQL_Cursor.h&gt;<br />
#include &#8220;SoftwareSerial.h&#8221;</p>
<p>/* Arduino Pin */<br />
#define SS1_LEFT_OUT A1<br />
#define SS2_LEFT_IN A2<br />
#define SS4_RIGHT_IN A3<br />
#define SS5_RIGHT_OUT A4</p>
<p>#define encoderPinA 2<br />
#define encoderPinB 3</p>
<p>#define MotorA1 5<br />
#define MotorA2 6<br />
#define MotorB1 10<br />
#define MotorB2 11<br />
#define TURN_SPEED 200<br />
#define STRAIGHT_SPEED 110</p>
<p>#define STRAIGHT_SPEED_STRONG 200<br />
#define STRAIGHT_SPEED_WEEK 110</p>
<p>#define SENSING_PERIOD 100</p>
<p>/* global variable &#8211; IR sensor */<br />
byte LeftOut;<br />
byte LeftIn;<br />
byte RightIn;<br />
byte RightOut;</p>
<p>/* global variable &#8211; ServingRobot Direction */<br />
int cmd_direction = 0;<br />
int cnt_isRoad = 0;</p>
<p>/* 서빙로봇 명령 */<br />
const int STOP = 0;<br />
const int LEFT = 1;<br />
const int STRAIGHT = 2;<br />
const int RIGHT = 3;<br />
const int BACK = 4;<br />
const int FORWARD_THROUGH_CROSSWALK = 5;<br />
/* global variable &#8211; PID Control */<br />
long encoderPos = 0;<br />
double angle = 0, angle_pre=0;<br />
int time1 = 0;<br />
int value = 0;</p>
<p>double v;<br />
double v_pre = 0;<br />
float Kp = 4.2227;<br />
float Ki = 2.29743;<br />
float Kd = -0.03976;<br />
float Ke = 0.084;</p>
<p>/*<br />
float Kp = 1.674;<br />
float Ki = 0.8239;<br />
float Kd = -0.3584;<br />
float Ke = 0.084;<br />
*/<br />
int k = 0;</p>
<p>double PControl, IControl=0, DControl, PIDControl;<br />
double error, error_pre;<br />
int pwm_in;</p>
<p>/* global variable &#8211; WiFi */<br />
long target_table = 0;</p>
<p>/* global variable &#8211; Find Table */<br />
int crosswalk[9] = {0, 1, 1, 2, 2, 3, 3, 4, 4};//갈림길 지나야하는 횟수. index 0은 사용하지 않는다.<br />
int turn[9] = {0, LEFT, RIGHT, LEFT, RIGHT, LEFT, RIGHT, LEFT, RIGHT};//회전해야할 때, left로 가야하는지 right로 가야하는지<br />
int crosswalkCnt = 0;//몇 번째 갈림길을 만났는지 체크!<br />
bool isDone = true;</p>
<p>WiFiEspClient client;<br />
MySQL_Connection conn((Client *)&amp;client);<br />
// Create an instance of the cursor passing in the connection<br />
MySQL_Cursor cur = MySQL_Cursor(&amp;conn);<br />
MySQL_Cursor *cur_mem = new MySQL_Cursor(&amp;conn);</p>
<p>SoftwareSerial esp(8, 9); // RX, TX<br />
byte mac_addr[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };</p>
<p>IPAddress server_addr(106,10,49,227); // IP of the MySQL *server* here<br />
char user[] = &#8220;sexymandoo&#8221;; // MySQL user login username<br />
char password[] = &#8220;sexymandoo&#8221;; // MySQL user login password</p>
<p>char ssid[] = &#8220;Sexy_Jaewon&#8221;; // 공유기 이름 SSID<br />
char pass[] = &#8220;jaewonsexy&#8221;; // 공유기 암호 Password<br />
// Sample query<br />
char query[] = &#8220;SELECT _table FROM project.orderDB WHERE call_arduino = &#8217;1&#8242; AND serving = &#8217;0&#8242; ;&#8221;;<br />
char query2[] = &#8220;UPDATE project.orderDB SET serving = &#8217;1&#8242; WHERE call_arduino = &#8217;1&#8242; AND serving = &#8217;0&#8242; ;&#8221;;<br />
int status = WL_IDLE_STATUS; // Status<br />
int Hz = 20;<br />
unsigned long time; // 시간 측정을 위한 변수<br />
void setup() {<br />
pinMode(encoderPinA, INPUT_PULLUP);<br />
attachInterrupt(0, doEncoderA, CHANGE);<br />
pinMode(encoderPinB, INPUT_PULLUP);<br />
attachInterrupt(1, doEncoderB, CHANGE);</p>
<p>pinMode(MotorA1, OUTPUT);<br />
pinMode(MotorA2, OUTPUT);<br />
pinMode(MotorB1, OUTPUT);<br />
pinMode(MotorB2, OUTPUT);</p>
<p>Serial.begin(9600);<br />
esp.begin(9600);<br />
time = millis();</p>
<p>WiFi.init(&amp;esp);</p>
<p>if (WiFi.status() == WL_NO_SHIELD)<br />
{<br />
Serial.println(F(&#8220;WiFi shield not present&#8221;));<br />
while (true);<br />
}</p>
<p>// 와이파이 접속여부 확인<br />
while (WiFi.status() != WL_CONNECTED)<br />
{<br />
Serial.print(F(&#8220;Attempting to connect to WPA SSID: &#8220;));<br />
Serial.println(ssid);</p>
<p>status = WiFi.begin(ssid, pass);<br />
}</p>
<p>// 와이파이 접속정보<br />
Serial.println(F(&#8220;You&#8217;re connected to the network&#8221;));<br />
Serial.print(F(&#8220;SSID: &#8220;));<br />
Serial.println(WiFi.SSID());</p>
<p>IPAddress ip = WiFi.localIP();<br />
Serial.print(F(&#8220;IP Address: &#8220;));<br />
Serial.println(ip);</p>
<p>long rssi = WiFi.RSSI();</p>
<p>Serial.println(F(&#8220;Connecting to the server&#8221;));<br />
if (conn.connect(server_addr, 3306, user, password)) {<br />
delay(1000);<br />
Serial.println(F(&#8220;Connected to the server&#8221;));<br />
}<br />
else<br />
Serial.println(F(&#8220;Server connection failed.&#8221;));<br />
}</p>
<p>void loop() {</p>
<p>if(target_table == 0){<br />
get_table_number();<br />
isDone = false;<br />
crosswalkCnt = 0;<br />
}<br />
if(target_table != 0)<br />
{<br />
lineTracing();<br />
}<br />
if(isDone &amp;&amp; (target_table != 0))<br />
{<br />
cur_mem-&gt;execute(query2); //MySQL에 서빙 완료를 알림<br />
cur.close();<br />
target_table = 0;<br />
}</p>
<p>//pidControl_Hz(Hz, 30); // 30cm/s 속도로 주행<br />
}</p>
<p>void get_table_number()<br />
{<br />
row_values *row = NULL;<br />
target_table = 0;</p>
<p>delay(1000);</p>
<p>cur_mem = new MySQL_Cursor(&amp;conn);<br />
// query를 실행시켜서 MySQL에서 데이터를 읽어옴<br />
cur_mem-&gt;execute(query);<br />
column_names *columns = cur_mem-&gt;get_columns();</p>
<p>do {<br />
row = cur_mem-&gt;get_next_row();<br />
if (row != NULL) {</p>
<p>target_table = atol(row-&gt;values[0]);<br />
}<br />
} while (row != NULL);<br />
// 동적 메모리 할당 지우기<br />
delete cur_mem;</p>
<p>// 결과 출력<br />
Serial.print(F(&#8221; 서빙테이블 = &#8220;));<br />
Serial.println(target_table);</p>
<p>delay(500);<br />
}<br />
</div>
<p>&nbsp;</p>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p><strong> motor</strong></p>
<p>/* 프로그램 내용 : DC Motor 컨트롤하는 함수들<br />
*<br />
* MotorA MotorA를 PWM 전압으로 조절하는 함수<br />
* MotorB MotorB를 PWM 전압으로 조절하는 함수<br />
*<br />
* doEncoderA DC모터 엔코더 카운트 하기 위한 함수<br />
* doEncoderB DC모터 엔코더 카운트 하기 위한 함수<br />
* pos2ang 엔코더로부터 회전 각도 구하는 함수<br />
* pos2ang_Hz 주어진 Hz속도로 회전 각도 구하는 함수<br />
*<br />
*/</p>
<p>void MotorA(int dir, int _speed)<br />
{<br />
if (dir == STRAIGHT)<br />
{<br />
analogWrite(MotorA1, 0);<br />
analogWrite(MotorA2, _speed);<br />
}<br />
else<br />
{<br />
analogWrite(MotorA1, 0);<br />
analogWrite(MotorA2, 0);<br />
}<br />
}</p>
<p>void MotorB(int dir, int _speed)<br />
{<br />
if (dir == STRAIGHT)<br />
{<br />
analogWrite(MotorB1, _speed);<br />
analogWrite(MotorB2, 0);<br />
}<br />
else<br />
{<br />
analogWrite(MotorB1, 0);<br />
analogWrite(MotorB2, 0);<br />
}<br />
}</p>
<p>void doEncoderA()<br />
{<br />
encoderPos += (digitalRead(encoderPinA)==digitalRead(encoderPinB))?1:-1;<br />
}</p>
<p>void doEncoderB()<br />
{<br />
encoderPos += (digitalRead(encoderPinA)==digitalRead(encoderPinB))?-1:1;<br />
}</p>
<p>void pos2ang()<br />
{<br />
angle = -encoderPos/(341.2*4) * 360 /180*3.141592 ;<br />
}<br />
</div>
<p>&nbsp;</p>
<div class="symple-box gray none" style="text-align:left; width:100%;"> 
<p><strong> PIDControl</strong></p>
<p>void pidControl_Hz(int Hz, double input_v)<br />
{<br />
double t = 0.1;<br />
pos2ang(); // 회전 각도 측정(라디안)<br />
v = 3.15*(angle-angle_pre)/t;<br />
angle_pre = angle;<br />
error=(input_v-v)*Ke;<br />
PControl=Kp*error;<br />
if(IControl &lt;= 4){IControl+=Ki*error*t;} //Windup 방지<br />
//IControl+=Ki*error*t;<br />
DControl=Kd*(error-error_pre)/t;</p>
<p>PIDControl=PControl+IControl+DControl;</p>
<p>pwm_in=255/6*PIDControl;<br />
if(pwm_in&gt;=255){pwm_in=255;}<br />
else if(pwm_in&lt;=60){pwm_in=60;}</p>
<p>Serial.print(&#8220;pwm_in: &#8220;); Serial.print(pwm_in);<br />
Serial.print(&#8221; v:&#8221;); Serial.print(v);<br />
Serial.print(&#8221; error:&#8221;); Serial.print(error/Ke);<br />
Serial.print(&#8221; PIDControl: &#8220;); Serial.print(PIDControl);<br />
Serial.print(&#8221; P:&#8221;); Serial.print(PControl);<br />
Serial.print(&#8221; I:&#8221;); Serial.print(IControl);<br />
Serial.print(&#8221; D:&#8221;); Serial.println(DControl);</p>
<p>//Serial.println(v);</p>
<p>MotorA(STRAIGHT, pwm_in);<br />
MotorB(STRAIGHT, pwm_in);<br />
//MotorA(STRAIGHT, -50);<br />
//MotorB(STRAIGHT, -50);</p>
<p>//angle_pre = angle;<br />
v_pre = v;<br />
error_pre = error;<br />
}<br />
</div>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
]]></content:encoded>
			<wfw:commentRss>http://www.ntrexgo.com/archives/40672/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
