<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Lunar Jiwon</title>
        <link>https://velog.io/</link>
        <description>👋</description>
        <lastBuildDate>Fri, 17 Apr 2026 16:01:39 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>Lunar Jiwon</title>
            <url>https://velog.velcdn.com/images/j-iwon/profile/50967915-c57e-468e-bc7a-ab5955930425/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. Lunar Jiwon. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/j-iwon" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[윈도우 미니필터 공부]]></title>
            <link>https://velog.io/@j-iwon/%EC%9C%88%EB%8F%84%EC%9A%B0-%EB%AF%B8%EB%8B%88%ED%95%84%ED%84%B0-%EA%B3%B5%EB%B6%80</link>
            <guid>https://velog.io/@j-iwon/%EC%9C%88%EB%8F%84%EC%9A%B0-%EB%AF%B8%EB%8B%88%ED%95%84%ED%84%B0-%EA%B3%B5%EB%B6%80</guid>
            <pubDate>Fri, 17 Apr 2026 16:01:39 GMT</pubDate>
            <description><![CDATA[<p>미니필터 =&gt; 파일 생성, 읽기, 쓰기, 삭제, 이름 변경과 같은 I/O 요청을 가로채거나 감시하는 드라이버</p>
<p>Ex) 랜섬웨어 방지, 백신, 파일보호 등</p>
<p>앱 -&gt; Win32 API -&gt; NTFS/FAT 등 요청 -&gt; FltMgr -&gt; 미니필터 -&gt; 실 파일시스템
=&gt; 직접 디스크 제어 X, I/O 요청 흐름에 개입</p>
<h2 id="irp--주요-함수">IRP / 주요 함수</h2>
<ul>
<li>IRP_MJ_CREATE : 파일 열기/생성</li>
<li>IRP_MJ_READ : 읽기</li>
<li>IRP_MJ_WRITE : 파일 쓰기</li>
<li>IRP_MJ_SET_INFORMATION : 이름변경 / 삭제</li>
<li>IRP_MJ_CLEANUP</li>
<li>IRP_MJ_CLOSE</li>
</ul>
<p>위 이벤트에 대해 미니필터가 콜백을 등록.</p>
<pre><code class="language-C">PreCreate(...) // 요청 처리 전
PostCreate(...) // 요청 처리 후 결과 확인</code></pre>
<p>Altitude : 필터 드라이버 우선순위
Instance : 볼륨별 장착(각 드라이브에 인스턴스 생성 가능)
Context : 파일 별 상태 기록
<strong>User &lt;-&gt; Kernel 통신</strong></p>
<pre><code class="language-C">FltCreateCommunicationPort</code></pre>
<p>사용해서 사용자 프로그램과 연결</p>
<p>이름 얻기</p>
<pre><code>FltGetFileNameInformation</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[EFI 날아갔을 때]]></title>
            <link>https://velog.io/@j-iwon/EFI-%EB%82%A0%EC%95%84%EA%B0%94%EC%9D%84-%EB%95%8C</link>
            <guid>https://velog.io/@j-iwon/EFI-%EB%82%A0%EC%95%84%EA%B0%94%EC%9D%84-%EB%95%8C</guid>
            <pubDate>Tue, 17 Feb 2026 06:10:09 GMT</pubDate>
            <description><![CDATA[<p>종종 한 컴퓨터에 두 개 이상의 운영체제를 설치하고 지우다 보면 EFI 파티션이 날아가 부팅 목록에서 사라지는 경우가 있다.
윈도우 설치 USB를 구워 복구 모드로 들어가 명령 프롬프트 열어 아래와 같이 해보자</p>
<pre><code class="language-bash">diskpart # 스크 제어를 위한 프로그램 진입
list vol # 윈도우를 설치했던 용량의 볼륨 체크
list disk # 윈도우를 설치했던 디스크 확인
sel disk 0 # 윈도우 설치했던 디스크 번호 입력
list part # 윈도우 설치 시 만들어지는 파티션 중 복구 파티션 확인
sel part 3
format fs=fat32 quick
assign letter=S # 복구 파티션을 S문자로 지정
sel vol 1
assign letter=W
exit # diskpart 나가기
dir W:\ # 이후 윈도우 파일 보이면 그대로 진행
bcdboot W:\Windows /s S: /f UEFI</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[SSRF 취약점]]></title>
            <link>https://velog.io/@j-iwon/SSRF-%EC%B7%A8%EC%95%BD%EC%A0%90</link>
            <guid>https://velog.io/@j-iwon/SSRF-%EC%B7%A8%EC%95%BD%EC%A0%90</guid>
            <pubDate>Sun, 15 Feb 2026 01:52:50 GMT</pubDate>
            <description><![CDATA[<p>Server-Side Request Forgery - 서버측 요청 위조</p>
<p>워게임 문제 중 서버측에서 요청을 위조해서 로컬의 파일을 읽어드리는 문제</p>
<p>flag가 /flag.txt에 위치해 있을때 로컬 코드 중 fetch를 사용하거나 요청을 보낼 수 있는 함수를 사용한다면 아래와 같이 시도해볼 수 있다.</p>
<pre><code>file:///flag.txt
file://localhost/flag.txt</code></pre><p>이 방법을 변형해서 다양하게 사용가능하다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[X-Forwarded-For 우회]]></title>
            <link>https://velog.io/@j-iwon/X-Forwarded-For-%EC%9A%B0%ED%9A%8C</link>
            <guid>https://velog.io/@j-iwon/X-Forwarded-For-%EC%9A%B0%ED%9A%8C</guid>
            <pubDate>Sun, 08 Feb 2026 02:49:25 GMT</pubDate>
            <description><![CDATA[<p>X-Forwarded-For 헤더는 HTTP에서 클라이언트의 원 IP주소를 식별하는 헤더로 웹페이지 접속 시 클라이언트의 접속 IP를 가져올 수 있다.</p>
<p>우회하기 위해서 Burp Suite 프로그램이 필요하다.</p>
<p>Intercept를 활성화 시켜 브라우저에서 접속하는 주소를 거치도록 만들어준다.
이후 접속 전 헤더 내용에 X-Forwarded-For:127.0.0.1 을 입력해서 헤더를 수정해서 전송하면 우회할 수 있다.</p>
<p>모든 환경에서 적용되는 것이 아닌 워게임 문제 풀이 중 해당 헤더를 통해 아이피를 가져오는 문제에서 활용할 수 있음.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JSON 인젝션 취약점]]></title>
            <link>https://velog.io/@j-iwon/JSON-%EC%9D%B8%EC%A0%9D%EC%85%98-%EC%B7%A8%EC%95%BD%EC%A0%90</link>
            <guid>https://velog.io/@j-iwon/JSON-%EC%9D%B8%EC%A0%9D%EC%85%98-%EC%B7%A8%EC%95%BD%EC%A0%90</guid>
            <pubDate>Sun, 08 Feb 2026 02:20:46 GMT</pubDate>
            <description><![CDATA[<p>사용자의 입력값을 검증하지 않고 변수에 넣는 경우</p>
<p>입력값</p>
<pre><code class="language-python">username = input()

properties = (
    f&#39;{{&quot;isAdmin&quot;:&quot;No&quot;,&#39;
    f&#39;&quot;username&quot;:&quot;{username}&quot;}}&#39;)</code></pre>
<p>위 코드에서 username에 <code>user1&quot;,&quot;isAdmin&quot;:&quot;Yes&quot;</code>를 입력한다면 선행 입력된 isAdmin 값은 추후 사용자의 입력에 따라 Yes로 변경된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[운영체제 만들어보기 (1)]]></title>
            <link>https://velog.io/@j-iwon/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EB%A7%8C%EB%93%A4%EC%96%B4%EB%B3%B4%EA%B8%B0-1</link>
            <guid>https://velog.io/@j-iwon/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EB%A7%8C%EB%93%A4%EC%96%B4%EB%B3%B4%EA%B8%B0-1</guid>
            <pubDate>Sat, 15 Mar 2025 05:47:52 GMT</pubDate>
            <description><![CDATA[<p>본 게시물은 박주항 개발자님께서 집필하신 &quot;C++로 나만의 운영체제 만들기&quot;라는 책을 기반으로 작성된 게시물입니다.
시작에 앞서 본 책을 집필하신 박주항 개발자님의 명복을 빕니다.</p>
<p>초등학교때 본 책을 구매하여 이해하지 못한 채 지금에서야 보면서 따라하게 되었습니다.</p>
<h1 id="운영체제-이론">운영체제 이론</h1>
<ul>
<li>프로세스</li>
<li>스레드</li>
<li>스택</li>
<li>호출 규약</li>
</ul>
<h2 id="프로세스">프로세스</h2>
<p>먼저 프로그램이 실행되면 <strong>보조기억장치 -&gt; 주기억장치 적재(로더) -&gt; 실행</strong>되는 것을 <strong>프로세스</strong>라 함.
<img width=400 src="https://velog.velcdn.com/images/j-iwon/post/1e08ca41-656f-4ebe-9108-b8337d7f58ba/image.jpg"></p>
<h3 id="주기억장치ram">주기억장치(RAM)</h3>
<p>프로세스의 메모리 할당에 대해 이해하기 위해 메모리의 구조를 이해하여야 한다.
<img width=200 src="https://velog.velcdn.com/images/j-iwon/post/974661e2-56bb-4541-af3a-000b4db920d8/image.png">
메모리는 위와 같은 구조를 가지고 있다. 
<strong>Code(Text) 영역 -&gt; Data 영역 -&gt; Heap 영역 -&gt; Stack</strong> 영역 순으로 <strong>낮은 메모리 주소에서 높은 메모리 주소</strong>로 내려가는 것을 알 수 있다.</p>
<blockquote>
<p><strong>Code 영역</strong></p>
</blockquote>
<ul>
<li>실행 할 프로그램의 코드의 저장 공간 할당</li>
</ul>
<blockquote>
<p><strong>Data 영역</strong></p>
</blockquote>
<ul>
<li>프로그램 동작을 위한 변수 저장 공간 할당</li>
</ul>
<blockquote>
<p><strong>Heap 영역</strong></p>
</blockquote>
<ul>
<li>변수, 함수 등 동적 저장 공간 할당</li>
</ul>
<blockquote>
<p><strong>Stack 영역</strong></p>
</blockquote>
<ul>
<li>지역 변수 / 매개 변수 저장 공간 할당</li>
</ul>
<h3 id="멀티태스킹-운영체제한번에-다중-처리가-가능">멀티태스킹 운영체제(한번에 다중 처리가 가능)</h3>
<p>프로그램을 다중 처리 해야하기 때문에 시스템 자원(CPU, 램 등)을 독점적으로 사용하는 것을 방지 하기 위해 각 프로세스에 자원 <strong>사용 시간을 적절히 배분</strong>한다.</p>
<h3 id="커널">커널</h3>
<p>커널은 소프트웨어가 동작 시 하드웨어의 자원을 필요로 할 때 각 프로세스에 분배하고 프로세스 제어, 메모리 제어 등을 수행하는 운영체제의 가장 아래 계증체 속한다.
-&gt; OS 핵심 부분으로 <strong>소프트웨어 &lt;-&gt; 하드웨어</strong>간 징검다리 역할을 수행한다.</p>
<h3 id="프로세스-컨텍스트">프로세스 컨텍스트</h3>
<p>컨텍스트(Context) 문맥이라고도 불리는 컨텍스트는 운영체제가 관리하는 프로세스 정보라고 볼 수 있다. 
컨텍스트는 간략하게 <strong>CPU 상태, PCB, 가장주소공간 데이터</strong>를 의미한다. 이러한 정보를 바탕으로 커널이 프로세스를 실행하고 있따는 것을 의미한다.</p>
<ul>
<li><strong>CPU 상태</strong>
CPU 레지스터, Instruction Pointer 등이 존재한다.</li>
<li><strong>PCB(Process Control Block)</strong>
자료구조를 의미하며 커널이 프로세스를 표현하기 위해 사용된다.
PCB는 체인으로, 다른 PCB에 연결되어 있다.</li>
<li><em>OS가 관리상 사용하는 정보 :*</em> Process Status, Process ID, 스케쥴링 정보, 우선순위</li>
<li><em>CPU 수행 관련 하드웨어 정보 :*</em> Program Counter, Register</li>
<li><em>메모리 정보 :*</em> Code, Data, Stack 영역의 위치 정보</li>
<li><strong>가장주소공간 데이터</strong>
위에서 살펴보았던 코드 영역, 데이터 영역, 스택 영역, 힙 영역을 의미한다.</li>
</ul>
<h3 id="프로세스-상태">프로세스 상태</h3>
<p>컨텍스트 스위칭에 의해 프로세스는 실행 상태에 놓일 수도, 정지 상태에 놓일 수도 있다.
<img src="https://velog.velcdn.com/images/j-iwon/post/69fe6967-313a-47ed-ab94-c9a053523ed3/image.png" alt=""></p>
<p>위 프로세스 상태 다이어그램을 통해 이해할 수 있다.</p>
<ul>
<li><strong>실행</strong> : 프로세스가 CPU 점유</li>
<li><strong>대기</strong> : 프로세스가 CPU를 점유하기 위해 기다리고 있는 상태</li>
<li><strong>블록</strong> : 당장 작업 수행이 불가능한 상태(Sleep 함수 등 동기화로 대기 시 프로세스 블록)</li>
<li><strong>정지 상태</strong> : 스케쥴러나 인터럽트로 비활성화된 상태(외부에서 재개 필요)</li>
</ul>
<h3 id="context-switching">Context Switching</h3>
<p>CPU가 한 프로세스에 다른 프로세스의 PCB 정보로 스위칭 되는 과정을 의미한다.
<img width=500 src="https://velog.velcdn.com/images/j-iwon/post/b18f79ec-bfb2-4b05-b103-09e9522c160d/image.png">
위 과정처럼 두 프로세스가 실행과 중지를 반복하면서 컨텍스트 스위칭이 발생하는 것을 알 수 있다.
컨텍스트 스위칭은 시스템 콜이나 외부 인터럽트에 의해 발생할 수 있다.</p>
<h3 id="스레드">스레드</h3>
<p>하나의 프로세스에는 여러개의 스레드를 포함하고 있다. 커널은 프로세스의 스레드를 관리해 프로세스의 동작을 조정한다.
스레드1과 스레드2가 있다고 생각해보자. 스레드 1과 2의 가상주소 공간에는 Stack, Heap, Data, Code의 공간들이 존재할 것이다. 이러한 가상주소 공간은 프로세스의 Heap, Data, Code의 영역을 공유한다.
영역을 공유하는 만큼 데이터의 무결성이 중요하다. 각 스레드에서 읽기 전용 데이터를 접근할 때에는 문제가 발생하지 않지만 쓰기 데이터를 접근할 때에는 데이터 무결성 문제가 발생할 수 있다(단, 스택 영역은 스레드의 공유 자원이기 때문에 일반적인 경우에 어떠한 스레드로부터 간섭을 받지 않기 때문에 동기화 문제가 발생하지 않는다)
프로세스에 PCB가 존재하듯이 스레드 정보를 관리하기 위해 TCB(Thread Control Block)가 존재한다.
<strong>TCB</strong>는 다음과 같은 정보를 담고 있다.</p>
<ul>
<li>스레드 식별자</li>
<li>스택 포인터</li>
<li>프로그램 카운터</li>
<li>스레드 상태</li>
<li>레지스터 값</li>
<li>스레드를 담고 있는 프로세스의 PCB 포인터</li>
</ul>
<p><strong>스레드 경합</strong>
자료구조 혹은 데이터에 복수의 스레드가 접근하면 문제가 발생할 수 있다. 이러한 상태를 경쟁 상태 또는 경합 상태(Race Condition)라고 한다.</p>
<h4 id="스레드-동기화">스레드 동기화</h4>
<p>스레드 경합의 문제를 해결하기 위해서는 동기화를 적용하여야 한다. 동기화를 적용하기 위해서는 동기화 객체를 사용하면 되는데 동기화 기법의 큰 범주로는 유저 모드 동기화, 커널 모드 동기화가 존재한다.</p>
<ul>
<li><p><strong>유저 모드 동기화</strong>
크리티컬 섹션(Critical Section)
인터락 함수(Interlocked Family Of Function)</p>
</li>
<li><p><strong>커널 모드 동기화</strong>
뮤텍스(Mutex)
세마포어(Semaphore)
이름있는 뮤텍스(Named Mutex)
이벤트(Event)</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[DeepFace 오픈소스를 활용한 얼굴인식 체험하기]]></title>
            <link>https://velog.io/@j-iwon/DeepFace-%EC%98%A4%ED%94%88%EC%86%8C%EC%8A%A4%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%EC%96%BC%EA%B5%B4%EC%9D%B8%EC%8B%9D-%EC%B2%B4%ED%97%98%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@j-iwon/DeepFace-%EC%98%A4%ED%94%88%EC%86%8C%EC%8A%A4%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%EC%96%BC%EA%B5%B4%EC%9D%B8%EC%8B%9D-%EC%B2%B4%ED%97%98%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 22 Oct 2024 23:14:40 GMT</pubDate>
            <description><![CDATA[<p>교내 열람실 출석부를 얼굴인식을 통해 구현하려고 한다.
선행 연구로 관련된 오픈소스와 논문을 찾는 중 파이썬 라이브러리로 DeepFace라는 라이브러리가 있는 것을 알게 되었다.
오늘은 DeepFace 라이브러리를 활용하여 얼굴 인식을 체험해보려 한다.</p>
<h2 id="사전-준비">사전 준비</h2>
<p>당연하게도 파이썬은 미리 준비되어 있어야 하고 <strong>웹캠</strong>이 꼭 필요하다.
프로젝트 폴더를 만들고 폴더에 터미널을 열어 아래와 같이 입력해서 DeepFace 라이브러리를 설치한다.</p>
<pre><code class="language-bash">pip install deepface</code></pre>
<p>추가로 OpenCV라는 파이썬으로 제작된 컴퓨터 비전 라이브러리를 설치하여 카메라를 활용할 것이다.</p>
<pre><code class="language-bash">pip install opencv</code></pre>
<p>이제 모든 준비가 완료되었고 프로젝트 폴더는 다음과 같이 구성하였다.
<img src="https://velog.velcdn.com/images/j-iwon/post/56edf701-e303-4707-a181-e7c6309fe551/image.png" alt="">
루트폴더/database에는 매칭시킬 얼굴 사진들이 들어가야 한다.
루트폴더에는 main.py 파일만 존재하면 된다.
이후 얼굴 사진들은 코드를 통해서 구할 수 있기 때문이다.</p>
<h2 id="opencv-기초">OpenCV 기초</h2>
<p>바로 DeepFace를 사용하기보다 OpenCV에 대해 어느정도 알아가야 수월하게 진행할 수 있다.
다음은 실시간 카메라 영상을 송출하는 코드이다.</p>
<pre><code class="language-python">import cv2 # opencv 가져오기

capture = cv2.VideoCapture(0) # 0번째 카메라를 사용하겠다는 뜻으로 0 이후 숫자는 외부 카메라를 의미한다.
capture.set(cv2.CAP_PROP_FRAME_WIDTH,640) # 카메라의 가로 길이를 640px로 설정한다.
capture.set(cv2.CAP_PROP_FRAME_HEIGHT,480) # 카메라의 세로 길이를 480px로 설정한다.

while cv2.waitKey(30) &lt; 0: # 30ms마다 입력 감지 갱신하고 입력되는 키가 있을때 까지 기다리기
    ret,frame = capture.read() # 카메라 화면 읽어드리기
    cv2.imshow(&quot;face&quot;,frame) # 카메라 화면을 띄우기 (새창 만들어짐)

capture.release() # 카메라를 사용하기 위해 할당된 메모리를 해제
cv2.destroyAllWindows() # 카메라 화면을 띄우기 위해 만들어진 창 삭제하기</code></pre>
<p><img src="https://velog.velcdn.com/images/j-iwon/post/993e527f-5ddd-4c19-ba44-bfbb5331d0c4/image.png" alt=""></p>
<p>위 코드를 통해 실시간으로 현재 카메라 화면을 새 창을 띄워 송출할 수 있다.
이해가 안될 수 있기에 필요한 함수만 다시 설명한다.</p>
<pre><code class="language-python">while cv2.waitKey(30) &lt; 0:</code></pre>
<p>위 구문은 <code>cv2.waitKey(30)</code>을 이해해야 한다.
위 함수는 30ms마다 키 입력을 감지하는데 입력된 키 값이 해당 함수로 반환된다.
따라서 0 미만으로 조건식을 작성한 이유는 아무키도 입력되지 않았을 경우 0 미만의 값이 반환되기 때문이다.</p>
<pre><code class="language-python">ret,frame = capture.read()</code></pre>
<p>위 <code>capture.read()</code> 함수는 ret과 frame을 반환 받도록 하였는데 ret에는 카메라의 정상작동 유무에 대해서 참과 거짓을 반환하고 frame에는 현재 카메라에 촬영되는 모습을 담는다.</p>
<h2 id="비교-사진-촬영하기">비교 사진 촬영하기</h2>
<p>이제 DeepFace를 사용하기 위한 OpenCV 함수들에 대해서 이해하였으니 DeepFace를 직접 사용해본다.
먼저 비교할 사진들이 집합되어 있는 폴더(database)에 비교할 얼굴 사진들을 넣어야 한다.
비교할 얼굴 사진을 촬영하여 저장시켰다.</p>
<pre><code class="language-python">import cv2

capture = cv2.VideoCapture(0)
capture.set(CV_PROP_FRAME_WIDTH,640)
capture.set(CV_PROP_FRAME_HEIGHT,480)

while True:
    command = input(&quot;명령 입력 : &quot;)
    if command == &quot;c&quot;:
        ret,frame = capture.read()
        cv2.imwrite(&quot;database/jiwon.jpg&quot;,frame) # 사진을 database 폴더에 jiwon.jpg 이름으로 저장하는 함수
    elif command == &quot;exit&quot;:
        break

capture.release()
cv2.destoryAllWindows()</code></pre>
<p>여러 사진을 촬영할 수 있도록 명령을 입력하여 작동되도록 만들어보았다.
실행창에 c를 입력하면 사진이 촬영되어 database 폴더에 jiwon.jpg 이름으로 저장된다.
이후 이름을 바꾸고 여러 사진을 촬영하여도 된다.</p>
<h2 id="얼굴-비교하기">얼굴 비교하기</h2>
<p>얼굴을 비교하기 위해서 루트 폴더에 temp.jpg라는 이름으로 비교용 얼굴 사진을 저장시키고 database 폴더의 모든 얼굴을 탐색하여 얼굴을 비교하는 방식을 사용할 것이다.</p>
<pre><code class="language-python">import cv2
from deepface import DeepFace
capture = cv2.VideoCapture(0)
capture.set(CV_PROP_FRAME_WIDTH,640)
capture.set(CV_PROP_FRAME_HEIGHT,480)

while True:
    command = input(&quot;명령 입력 : &quot;)
    if command == &quot;c&quot;:
        captureImage(&quot;database/jiwon.jpg&quot;)
    elif command == &quot;d&quot;:
        captureImage(&quot;temp.jpg&quot;)
        detect = DeepFace.find(
            img_path=&quot;temp.jpg&quot;,
            db_path=&quot;database&quot;,
            detector_backend=&quot;retinaface&quot;,
            model_name=&quot;ArcFace&quot;
        )
        print(detect)
    elif command == &quot;exit&quot;:
        break

capture.release()
cv2.destoryAllWindows()

def captureImage(path):
    ret,frame = capture.read()
    cv2.imwrite(path,frame)</code></pre>
<p>위 코드를 실행하고 d를 입력하면 카메라를 통해 사진을 촬영하고 저장 후 다음과 같이 모델을 학습하는 과정을 거친다.
<img src="https://velog.velcdn.com/images/j-iwon/post/4202593c-877f-4083-9d54-c9361de642a7/image.png" alt="">
이후 database 폴더에 pkl이라는 모델이 만들어지고
<img src="https://velog.velcdn.com/images/j-iwon/post/5f70416d-9883-4cfd-8c24-8e8ab5e62fd8/image.png" alt=""></p>
<p>일치하는 얼굴 사진 목록을 보여준다.
<img src="https://velog.velcdn.com/images/j-iwon/post/974aee22-b383-49be-8e42-192101e97e59/image.png" alt=""></p>
<p>만약 일치하는 사진이 없다면 아래와 같이 빈 배열이 출력되는 것을 볼 수 있다.
<img src="https://velog.velcdn.com/images/j-iwon/post/cb0563b0-d198-4046-8648-c7c0dfa2a9b4/image.png" alt=""></p>
<p>위 과정을 통해서 얼굴 매칭을 통해 일치하는 얼굴을 인식하는 프로그램을 제작해보았다.
이후 과정에서는 출석부 시스템을 구현할때 DeepFace 오픈소스를 활용하여 제작할 것이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[교내 시설 로드뷰 만들기 (촬영 -> 구현)]]></title>
            <link>https://velog.io/@j-iwon/%EA%B5%90%EB%82%B4-%EC%8B%9C%EC%84%A4-%EB%A1%9C%EB%93%9C%EB%B7%B0-%EB%A7%8C%EB%93%A4%EA%B8%B0-%EC%B4%AC%EC%98%81-%EA%B5%AC%ED%98%84</link>
            <guid>https://velog.io/@j-iwon/%EA%B5%90%EB%82%B4-%EC%8B%9C%EC%84%A4-%EB%A1%9C%EB%93%9C%EB%B7%B0-%EB%A7%8C%EB%93%A4%EA%B8%B0-%EC%B4%AC%EC%98%81-%EA%B5%AC%ED%98%84</guid>
            <pubDate>Sat, 19 Oct 2024 15:32:57 GMT</pubDate>
            <description><![CDATA[<p>저번 포스팅에서 교내 시설 로드뷰를 만들기 위한 사전 준비를 모두 마쳤다.</p>
<h1 id="360도-사진-촬영">360도 사진 촬영</h1>
<p>먼저 로드뷰에 사용될 사진을 촬영하여야 한다.
학교에 인스타 360 카메라가 있어서 남는 시간에 학교 곳곳을 촬영하였다.
<img src="https://velog.velcdn.com/images/j-iwon/post/76af2659-d5af-4df0-a295-b2b92f29682f/image.png" alt=""></p>
<p>촬영한 이미지는 insp라는 확장자로 나오는데 인스타 360 스튜디오를 통해서 한번 가공 후 jpg로 변환하여야 한다.
<img src="https://velog.velcdn.com/images/j-iwon/post/cbc7c85a-6f56-4acc-b2f1-d830fcf9a7d2/image.png" alt="">
위와 같이 인스타 360 스튜디오로 가져왔다면 우측 상단에 내보내기 버튼이 존재한다.
이 버튼을 클릭해서 jpg 파일로 내보내면 된다.</p>
<p>이제부터 코딩을 시작할 것이다.</p>
<h1 id="코딩-시작">코딩 시작</h1>
<p>이번 코딩은 그닥 어렵지 않다.
Vue.js를 통해서 제작할 것이기 때문에 Vue.js 프로젝트를 만든다.
새로운 폴더를 만들어 VSCODE로 열고 터미널을 열어 아래와 같이 입력한다.</p>
<h3 id="vue-설치">Vue 설치</h3>
<pre><code class="language-bash">npm create vue@latest</code></pre>
<p>위 명령어는 Vue.js 프로젝트를 빠르게 만들기 위한 실행 명령어다. 
명령어를 입력하면 프로젝트 구성에 필요한 것들을 물어보는데 아래와 같이 답하면 된다.</p>
<pre><code class="language-bash">✔ Project name: roadview
✔ Add TypeScript? No
✔ Add JSX Support? No
✔ Add Vue Router for Single Page Application development? Yes
✔ Add Pinia for state management? No
✔ Add Vitest for Unit testing? No
✔ Add an End-to-End Testing Solution? No
✔ Add ESLint for code quality? No
✔ Add Prettier for code formatting? No
✔ Add Vue DevTools 7 extension for debugging? No

Scaffolding project in ./roadview
Done.</code></pre>
<p>대부분 옵션을 No로 답하였는데 네 번째 Vue Router는 필요하기에 Yes로 답변하여야 한다.
Vue Router는 React, Angular, Vue 등의 SPA 프레임워크에서 페이지 이동을 위한 프레임워크라고 생각하면 된다.</p>
<h3 id="view360-모듈-설치">VIEW360 모듈 설치</h3>
<p>360도 이미지를 움직일 수 있도록 하고 이미지 위에 플로팅 객체 등을 넣을 수 있는 VIEW360 모듈을 설치한다.
네이버에서 만든 <a href="https://naver.github.io/egjs-view360/ko/">프로젝트</a>로 보인다.
아래 명령어를 통해 모듈을 설치할 수 있다.</p>
<pre><code class="language-bash">npm install @egjs/vue3-view360@next</code></pre>
<p>View360은 현재 베타 버전으로 계속 개발되고 있으며 최신 버전을 사용하기 위해 @next를 붙였다.</p>
<p>설치를 완료하였다면 해당 모듈 사용을 Vue 프로젝트에 명시하여야 한다.
Vue 프로젝트의 src/main.js 파일을 열어보자
(스타일 파일은 모두 삭제하였다)</p>
<pre><code class="language-js">import { createApp } from &#39;vue&#39;
import App from &#39;./App.vue&#39;
import router from &#39;./router&#39;
import View360 from&#39;@egjs/vue3-view360&#39; // View360 가져오기
import &#39;@egjs/vue3-view360/css/view360.min.css&#39; // View360 스타일 가져오기

const app = createApp(App)

app.use(router)
app.use(View360) // View360 사용 명시
app.mount(&#39;#app&#39;)</code></pre>
<p>위 코드와 같이 작성하면 이제 모든 준비가 마무리 되었다.</p>
<h3 id="router-정리하기">router 정리하기</h3>
<p>프로젝트 자동 완성을 통해서 불필요한 HomeView.vue와 AboutView.vue 등의 파일이 많이 생성되었다.
불필요한 파일을 다 지우고 다음과 같이 구성하였다.
<img src="https://velog.velcdn.com/images/j-iwon/post/1cd70a5c-c8cc-4678-ae11-6efc2e236c17/image.png" alt="">
components/spots 폴더는 교내 시설 각각의 지점에 대한 컴포넌츠를 넣을 것이고 상위 폴더의 Header와 MapViewPage는 상단 고정 메뉴와 하단 고정 지도를 표시해줄 것이다.</p>
<p>views 폴더에는 HomeView와 RoadViewPage를 만들었다. HomeView는 현재는 사용하지 않지만 추후 피드백을 통해서 소개 페이지가 필요하다고 느끼면 활용할 예정이다. RoadViewPage는 실제로 로드뷰가 나오는, View360 컴포넌츠를 사용할 파일이다.</p>
<h3 id="appvue">App.vue</h3>
<p>app.vue는 vue에서 화면을 표현하는 최상위 파일이다.
이 파일에는 상단 고정 메뉴인 Header와 라우터 페이지를 보여주기 위한 RouterView만 넣으면 된다.</p>
<pre><code class="language-vue">&lt;script setup&gt;
import { RouterLink, RouterView } from &#39;vue-router&#39;
import Header from &#39;./components/Header.vue&#39;
&lt;/script&gt;

&lt;template&gt;
  &lt;Header/&gt;

  &lt;RouterView /&gt;
&lt;/template&gt;

&lt;style scoped&gt;

&lt;/style&gt;</code></pre>
<h3 id="roadviewpagevue">RoadViewPage.vue</h3>
<p>RoadViewPage.vue에서는 실제 로드뷰 화면을 보여줄 것이다.
코드는 주석을 통해 설명하였으며 사전 지식은 라우터의 파라미터를 통해서 현재의 위치와 층수를 가져오고 기존 링크에 해당 파라미터가 없다면 기본 설정으로 로드하도록 제작하였다.</p>
<pre><code class="language-vue">&lt;script setup&gt;
import { onBeforeMount, onMounted, ref, shallowRef, watch } from &#39;vue&#39;;
import Lobby from &#39;@/components/spots/Lobby.vue&#39;; // 중앙현관 컴포넌츠 가져오기
import MapViewPage from &#39;@/components/MapViewPage.vue&#39;; // 하단 고정 지도 가져오기
import { useRoute, useRouter } from &#39;vue-router&#39;; // 라우터에서 파라미터와 페이지 이동 함수를 위함.

const route = useRoute()
const router = useRouter()
const views = { &#39;lobby&#39;: Lobby } // 파라미터의 값과 컴포넌츠 연동 추후 다양하게 추가
const pos = ref(&quot;lobby&quot;) // 기본 위치
const currentView = shallowRef(views[pos.value]) // 컴포넌츠는 shallowRef를 통해서 동적 컴포넌츠 전환 사용, views 객체에 있는 값들중 pos의 값과 일치하는 키를 가져와서 currentView에 값을 넣음
onMounted(() =&gt; {
  if (route.query.pos == null || route.query.floor == null) {
    router.push(&quot;/roadview?pos=lobby&amp;floor=0&quot;) // 링크에 파라미터 존재하지 않으면 기본 페이지 이동
  } else {
    pos.value = route.query.pos // 현재 위치를 파라미터를 통해 저장
    currentView.value = views[pos.value] // 현재 화면을 views에 pos값을 통해서 키 매칭으로 저장 -&gt; 동적 변환을 위해 추후 watch 함수로 전환 예정
  }
})
&lt;/script&gt;

&lt;template&gt;
  &lt;div style=&quot;position: fixed; z-index: 10000; bottom: 0px; margin-bottom: 10px; margin-left: 10px;&quot;&gt;
    &lt;MapViewPage /&gt;
  &lt;/div&gt;
  &lt;div&gt;
    &lt;div style=&quot;width: 100%; height: 100%;&quot;&gt;
      &lt;component :is=&quot;currentView&quot; style=&quot;&quot; /&gt; // 동적 컴포넌츠
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/template&gt;

&lt;style&gt;
.view360-canvas {
  outline: 0;
  border-radius: 0px;

}

.viewer {

  width: 100%;
  height: 100vh;
  margin-left: auto;
  margin-right: auto;
}

.spot {
  background-color: white;
  padding: 10px;
  cursor: pointer;
  border-radius: 20px;
  transition: 0.5s;
}

.spot:hover {
  transition: 0.5s;
  transform: scale(1.2);
  background-color: rgb(201, 201, 201);
  padding: 10px;
  cursor: pointer;
  border-radius: 20px;

}
&lt;/style&gt;
</code></pre>
<p>스타일은 이후 spots 폴더의 파일을 살펴보면서 이해할 수 있다.</p>
<h3 id="routerindexjs">router/index.js</h3>
<pre><code class="language-js">import { createRouter, createWebHistory } from &#39;vue-router&#39;
import HomeView from &#39;../views/HomeView.vue&#39;
import RoadView from &#39;../views/RoadViewPage.vue&#39;

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: &#39;/&#39;,
      name: &#39;home&#39;,
      component: HomeView
    },
    {
      path: &#39;/roadview&#39;,
      component:RoadView
    }
  ]
})

export default router
</code></pre>
<p>라우터 파일은 별거 없다.</p>
<h3 id="componentsmapviewpagevue">components/MapViewPage.vue</h3>
<p>이 파일은 로드뷰 하단에 지도를 표시하는 컴포넌츠이다.</p>
<pre><code class="language-vue">&lt;template&gt;
    &lt;TransitionGroup  name=&quot;list&quot; &gt; &lt;!-- 효과를 주기 위한 트랜지션 그룹--&gt;
    &lt;div class=&quot;map-icon&quot; v-if=&quot;!isOpen&quot; @click=&quot;isOpen = !isOpen&quot;&gt; &lt;!-- 최소화 하였을 때 표시될 아이콘 --&gt;
        &lt;img src=&quot;../assets/mapicon.svg&quot; style=&quot;width: 30px; margin-top: 5px;&quot;/&gt;

    &lt;/div&gt;
    &lt;div style=&quot;padding: 10px; background-color: white; border-radius: 10px;&quot; v-if=&quot;isOpen&quot;&gt;
        &lt;div style=&quot;display: flex;margin-bottom: 10px;&quot;&gt;
            &lt;div&gt;{{floor}}층 지도&lt;/div&gt; &lt;!-- URL의 파라미터를 통해 동적으로 변동 --&gt; 
            &lt;div @click=&quot;isOpen = !isOpen&quot; style=&quot;margin-left: auto; cursor: pointer;&quot;&gt;
                닫기
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;img :src=&quot;mapImage&quot; style=&quot;width: 35vw;&quot;&gt;

    &lt;/div&gt;
&lt;/TransitionGroup &gt;
&lt;/template&gt;

&lt;script setup&gt;
import { onBeforeMount, ref } from &#39;vue&#39;;
import { useRoute } from &#39;vue-router&#39;;

import firstFloor from &#39;../assets/floor.png&#39; // 1층 지도 사진


const mapImages = [firstFloor] // 층별 지도 사진 배열
const mapImage = ref() // 현재 지도사진
const isOpen = ref(false) // 최소화 여부
const floor = ref(0) // 층수
const route = useRoute() 

onBeforeMount(()=&gt;{
    floor.value = parseInt(route.query.floor)+1 // URL의 파라미터를 통해 층수를 가져와 1을 더한다.
    mapImage.value = mapImages[floor.value-1] // 층수에 1을 뺀 값의 순서의 배열을 가져온다.
})


&lt;/script&gt;

&lt;style&gt;
#lobby{
    margin-left: 100px;

}
.dot{

    position: absolute; width: 12px; height: 12px; background-color: #0362fc; border-radius: 100px;
}

.list-move, /* apply transition to moving elements */
.list-enter-active,
.list-leave-active {
  transition: all 0.5s ease;
}

.list-enter-from{
    opacity: 0;
  transform: translateY(-30px);
}
.list-leave-to {
  opacity: 0;
  transform: translateY(-30px);
}

.list-leave-active {
  position: absolute;
}
.map-icon{
    border-radius: 100px;
    padding: 10px;
    width: 40px;
    height: 40px;
    text-align: center;
    vertical-align: middle;
    transition: 0.5s;
    background-color: white;
    cursor: pointer;

}


.map-icon:hover{

    background-color: rgb(194, 194, 194);
    transition: 0.5s;

}

&lt;/style&gt;</code></pre>
<h3 id="componentsheadervue">components/header.vue</h3>
<p>이 파일은 상단에 고정되어 표시될 헤더 파일이다.</p>
<pre><code class="language-vue">&lt;template&gt;
    &lt;div style=&quot; position: absolute; z-index: 1000; width: 100%; &quot;&gt;
        &lt;div
            style=&quot;padding-top: 10px; padding-bottom: 10px; background-color: rgba(255, 255, 255,50%); width: 100%;&quot;&gt;
            &lt;div style=&quot;position: absolute; left: 50%; transform: translate(-50%);&quot;&gt;
                {{ placeName }}
            &lt;/div&gt;

            &lt;div style=&quot;margin-left: 10px;&quot;&gt;
                대영고등학교 교내 로드뷰
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;

&lt;/template&gt;

&lt;script setup&gt;
import { onBeforeMount, ref } from &#39;vue&#39;;
import { useRoute } from &#39;vue-router&#39;;


const route = useRoute()
const placeName = ref(&quot;&quot;)
const places = {
    &#39;lobby&#39;:&quot;중앙현관&quot;,

}
onBeforeMount(()=&gt;{
    placeName.value = places[route.query.pos]
})

&lt;/script&gt;</code></pre>
<p>지금까지의 코드를 이해하였다면 위의 코드는 쉽게 이해할 수 있을 것이다.</p>
<h3 id="componentsspotslobbyvue">components/spots/Lobby.vue</h3>
<p>이 파일은 로비의 360도 이미지 파일을 표시해주는 컴포넌츠 파일이다.
이게 본 포스팅의 가장 핵심이다.</p>
<pre><code class="language-vue">&lt;script setup&gt;
import { View360, EquirectProjection } from &#39;@egjs/vue3-view360&#39;; // View360과 이미지를 가져오는 클래스 가져오기
import { onBeforeMount, ref } from &#39;vue&#39;;
import img from &#39;../../assets/lobby.jpg&#39;
const projection = new EquirectProjection({
    src: img
})

&lt;/script&gt;

&lt;style&gt;

&lt;/style&gt;

&lt;template&gt;
    &lt;div&gt;
    &lt;!-- 아래와 같이 표현해야 정상적으로 표시됨 --&gt;
        &lt;View360 fov=&quot;130&quot; class=&quot;viewer&quot; :projection=&quot;projection&quot;&gt; 
            &lt;div class=&quot;view360-hotspots&quot;&gt;
                &lt;div class=&quot;view360-hotspot&quot; data-yaw=&quot;140&quot; data-pitch=&quot;-12&quot;&gt;
                    &lt;div class=&quot;spot&quot;&gt;서편 복도&lt;/div&gt;
                &lt;/div&gt;
                &lt;div class=&quot;view360-hotspot&quot; data-yaw=&quot;60&quot; data-pitch=&quot;-15&quot;&gt;
                    &lt;div class=&quot;spot&quot;&gt;동편 복도&lt;/div&gt;
                &lt;/div&gt;
            &lt;/div&gt;

        &lt;/View360&gt;
    &lt;/div&gt;
&lt;/template&gt;</code></pre>
<p>위 코드를 보면 별 다른 스타일이 없다.
왜냐하면 RoadViewPage.vue 파일에 스타일을 모두 선언해두었기 때문에 불필요한 스타일 코드는 정리하였다.</p>
<h3 id="결론">결론</h3>
<p>위 코드를 통해서 아래와 같은 로드뷰 서비스를 구축할 수 있다.
다음 포스팅에서는 여러층과 각 스팟에 설정된 버튼을 클릭하여 이동하는 동작까지 구현하고 마치려고 한다.
<img src="https://velog.velcdn.com/images/j-iwon/post/a4ddc6ab-f33b-4bf7-9961-226777651019/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[교내 시설 로드뷰 만들기 (계획 세우기)]]></title>
            <link>https://velog.io/@j-iwon/%EA%B5%90%EB%82%B4-%EC%8B%9C%EC%84%A4-%EB%A1%9C%EB%93%9C%EB%B7%B0-%EB%A7%8C%EB%93%A4%EA%B8%B0-%EA%B3%84%ED%9A%8D</link>
            <guid>https://velog.io/@j-iwon/%EA%B5%90%EB%82%B4-%EC%8B%9C%EC%84%A4-%EB%A1%9C%EB%93%9C%EB%B7%B0-%EB%A7%8C%EB%93%A4%EA%B8%B0-%EA%B3%84%ED%9A%8D</guid>
            <pubDate>Sat, 14 Sep 2024 09:23:32 GMT</pubDate>
            <description><![CDATA[<h1 id="동기">동기</h1>
<p>내년 신입생 유치를 위해서 우리 학교에서 다양한 홍보 활동을 진행하는데, 올해 입학 설명회는 모든 학교가 한 곳에 모여서 진행다는 것이였다. 따라서 다른 학교보다 특화적인 모습을 보여주어야 한다고 생각했고, 학교를 직접 방문해서 시설을 보질 못하기에 학교 내부를 로드뷰 형태로 제작하면 우리 학교 진학을 고려하는 신입생들에게 도움이 될 것 같다고 생각했다.</p>
<h1 id="계획">계획</h1>
<h3 id="웹앱-제작">웹앱 제작</h3>
<p>교내 시설 탐방은 언제 어디서든지 볼 수 있어야 새로 들어올 신입생들이 관심을 가지고 유심히 볼 수 있다고 생각하여 설치형 어플리케이션이 아닌, 웹앱을 통해 접근성을 높이고자 하였다.</p>
<p>웹앱은 Vue.js를 통해서 제작할 예정이고 Nginx를 통해 웹앱을 배포할 예정이다.</p>
<h3 id="로드뷰-구현">로드뷰 구현</h3>
<p>그렇다면 로드뷰 구현을 어떻게 할 것인가?
구글에 &#39;로드뷰 api&#39;와 같이 검색을 해보았지만, 기존 서비스를 제공하는 지도에서 로드뷰 영역을 추가하는 방법으로밖에 안된다.</p>
<p>내가 원하는 로드뷰는 360도 이미지를 움직이고 움직이면서 각 실에 들어갈 수 있도록 조작 플로팅 버튼을 구현하는 것이다.
이와 관련된 기능을 찾기 위해서 가장 기초가 되는 360도 이미지를 웹앱에 뿌릴 수 있어야 한다.
구글에 &#39;vue js 360 viewer&#39;를 검색하였더니 최상위글에 네이버 로고로 된 오픈소스가 있었다.</p>
<p>&#39;<a href="https://github.com/naver/egjs-view360">View360</a>&#39; 이라는 오픈소스이며, 네이버에서 제작하였다.
이 오픈소스는 다양한 프레임워크에서 사용할 수 있는데 Vue.js도 그 중 한개로 선정되어 있어서 적합하다고 생각했다.
360도 파라노마 이미지를 웹에 넣어 볼 수 있도록 해주고 각 이미지마다 위치를 잡아서 이동할 수 있는 플로팅 버튼을 넣을 수 있는 기능을 제공해준다.
<img src="https://velog.velcdn.com/images/j-iwon/post/1217c807-51bc-427d-a882-1771a6c6c6f5/image.png" alt="">
위 사진의 오픈소스 시연사진처럼 플로팅 버튼 혹은 글씨를 띄울 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Raspberry Pi5를 활용하여 nextcloud 스토리지 만들기 - 라즈베리파이에 이미지 적용시키기]]></title>
            <link>https://velog.io/@j-iwon/Raspberry-Pi5%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%98%EC%97%AC-nextcloud-%EC%8A%A4%ED%86%A0%EB%A6%AC%EC%A7%80-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@j-iwon/Raspberry-Pi5%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%98%EC%97%AC-nextcloud-%EC%8A%A4%ED%86%A0%EB%A6%AC%EC%A7%80-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Wed, 19 Jun 2024 09:41:34 GMT</pubDate>
            <description><![CDATA[<p>최근에 나온 스마트폰이나 노트북 같은 기기들은 해당 기기의 저장공간에 저장하지 않고 클라우드를 통해 클라우드 스토리지에 저장하는 방식을 주로 채택하여 사용하고 있다</p>
<p>주된 클라우드 서비스로 네이버 MYBOX, 구글 포토, 구글 드라이브 등이 있는데 이러한 서비스들은 사내 자체 서버를 통해 직접 운영되고 있으며 이러한 작업들을 라즈베리파이를 통해 직접 만든다고 생각하면 된다</p>
<p>그렇다면 왜 NextCloud를 사용하는 것일까?</p>
<h1 id="nextcloud란">NextCloud란</h1>
<p>방금 설명했듯이 네이버 MYBOX 같은 서비스들은 사내 서버를 통해 직접 서버를 구동시켜 고객들에게 서비스 해주는 방식인데 이것을 직접한다고 하였다. 
직접 만들기 위해서는 운영체제, 네트워크 개념 등 다양하게 알아야 할 부분들이 많지만, 이러한 부분들을 모두 만들어 놓은채로 편리하게 구성된 GUI를 통해 간편하게 구성하는 것이 NextCloud이다.</p>
<p>NextCloud는 ownCloud라는 프로젝트에서 파생된 프로젝트이다.
NextCloud는 오픈소스 프로그램이며 사설 서버 장치를 통해 활용하여 서버를 구축할 수 있다.</p>
<p>관련된 내용으로는 구글에 &quot;NextCloud&quot;라 치면 다양한 내용들이 나온다.</p>
<h1 id="raspbery-pi5-이미지-적용">Raspbery Pi5 이미지 적용</h1>
<p>NextCloud 프로젝트에서는 Arm 아키택처(Arm 기반의 CPU)를 위한 운영체제를 직접 이미징해서 제공하고 있다.
따라서 제공된 이미지 파일을 통해 라즈베리파이에 적용시킬 것이다.
<a href="https://github.com/nextcloud/nextcloudpi">NextCloudpi</a> 페이지를 접속하면 오픈소스 프로젝트를 확인할 수 있다.
이 프로젝트에서 활발하게 Arm 아키택처에 맞는 프로그램이 미리 설치되어 있고 NextCloud도 설치되어 있기에 페이지에 접속해 사이트에서 설정만 하면 된다.</p>
<p><img src="https://velog.velcdn.com/images/j-iwon/post/68faed83-fdd3-402c-8d42-521c78e2bc01/image.png" alt="">
위 사진의 Releases의 최신 릴리즈를 누르면 Raspberry Pi 5를 지원하는 업데이트가 있는 것을 볼 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/j-iwon/post/b280a820-f33c-48ab-9bc9-f9d741714009/image.png" alt="">
위 사진을 통해서 알 수 있고 하단으로 내리면 Assets 항목에 다양한 SBC를 지원하면서 RaspberryPi5 파일이 있는 것을 알 수 있다.
이 파일을 다운로드하여 라즈베리파이에 씌워주면 된다.
<img src="https://velog.velcdn.com/images/j-iwon/post/e80790e9-c8b2-4c68-ad80-f823c7111f93/image.png" alt=""></p>
<p>라즈베리파이에 넣어주기 위해서는 이미저 프로그램을 설치해야 한다.
<a href="https://www.raspberrypi.com/software/">라즈베리파이 공식 사이트</a>에 들어가면 다음과 같이 이미저를 다운로드 할 수 있다.
<img src="https://velog.velcdn.com/images/j-iwon/post/f57160ae-3274-4dff-ae3b-49e56fc6afcf/image.png" alt="">
여기서 맞는 운영체제의 프로그램을 설치해주면 된다.</p>
<p>설치를 완료하고 열었다면 아래와 같이 먼저 라즈베리파이의 버전을 선택한다.
<img src="https://velog.velcdn.com/images/j-iwon/post/793a950a-1000-4a3c-a266-7b17a283c4a4/image.png" alt="">
필자는 이번에(조금 됐지만) 새로나온 라즈베리파이 5 버전을 사용해서 구축한다.
선택하면 운영체제를 눌러 아까 설치한 파일을 열어줄 것이다.
<img src="https://velog.velcdn.com/images/j-iwon/post/3a4c26d3-491c-420b-8c50-ad3bbb3d4a67/image.png" alt="">
위와 같이 Use custom 버튼을 클릭하고 파일을 선택해준다.<img src="https://velog.velcdn.com/images/j-iwon/post/77254b6d-9bdc-4bc0-8298-2c6eccfd001f/image.png" alt="">
선택을 하고 열기를 누르고 저장소를 선택한다
<img src="https://velog.velcdn.com/images/j-iwon/post/8851ccf3-3cf3-44bb-8b09-73c8d4048803/image.png" alt="">
SD카드를 컴퓨터에 연결하면 위와 같이 메모리가 뜰 것이고, 이미징을 할 SD카드를 선택해주면 된다.
<img src="https://velog.velcdn.com/images/j-iwon/post/6597c807-2b6c-49fc-8fa9-f33d0dccfef3/image.png" alt="">
다음 버튼을 누르면 위와 같이 OS커스터마이징 설정이 나오는데, 이런 옵션은 순정 라즈베리파이 또는 우분투에서 사용할 수 있는 기능으로 NextCloudPi 커스텀 운영체제에서는 지원하지 않는 설정이기에 아니요를 누르고 진행하면 된다.</p>
<p>이후 SD카드에 이미징이 완료되면 라즈베리파이에 연결한다.
다음 과정에서는 라즈베리파이를 인터넷에 연결하고 기본적인 세팅 후 외부에서 접속이 가능하도록 제작할 것이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[라즈베리파이 파일로 와이파이 설정하기]]></title>
            <link>https://velog.io/@j-iwon/%EB%9D%BC%EC%A6%88%EB%B2%A0%EB%A6%AC%ED%8C%8C%EC%9D%B4-%ED%8C%8C%EC%9D%BC%EB%A1%9C-%EC%99%80%EC%9D%B4%ED%8C%8C%EC%9D%B4-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@j-iwon/%EB%9D%BC%EC%A6%88%EB%B2%A0%EB%A6%AC%ED%8C%8C%EC%9D%B4-%ED%8C%8C%EC%9D%BC%EB%A1%9C-%EC%99%80%EC%9D%B4%ED%8C%8C%EC%9D%B4-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 04 Jun 2024 14:23:45 GMT</pubDate>
            <description><![CDATA[<p>라즈베리파이 제로를 사용하다보면 HDMI를 연결하고 마우스를 연결하고 키보드를 연결해야하는 불편함이 있고 번거롭기에 운영체제가 담겨있는 SD카드에 파일을 추가하는 형식으로 와이파이를 연결할 수 있다.
와이파이를 연결하고 SSH를 통해 원격 통신이 가능하다.</p>
<p>SD카드를 컴퓨터에 연결한다.
<img src="https://velog.velcdn.com/images/j-iwon/post/eec3bc27-54e2-4adf-b757-f881a171caf2/image.png" alt=""></p>
<p>빈 곳을 클릭해 새 파일을 만들고 파일의 이름은 다음과 같이 설정한다.</p>
<blockquote>
<p>wpa_supplicant.conf</p>
</blockquote>
<p>파일을 열고 다음과 같은 내용을 추가하면 라즈베리파이에 전원을 인가하였을 때 자동으로 연결된다.</p>
<pre><code class="language-conf">ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1

network={
    ssid=&quot;와이파이 명 (SSID)&quot;
    psk=&quot;비밀번호&quot;
    key_mgmt=WPA-PSK
}</code></pre>
<p>PUTTY를 설치하여 원격 통신이 가능하다</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[express + python 통신하기 (라즈베리파이)]]></title>
            <link>https://velog.io/@j-iwon/express-python-%ED%86%B5%EC%8B%A0%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@j-iwon/express-python-%ED%86%B5%EC%8B%A0%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 02 Jun 2024 16:21:57 GMT</pubDate>
            <description><![CDATA[<p>학교 SW-AI 인재양성 프로젝트에서 진행하고 있는 열람실 지문인식 출석 시스템을 구축하면서 서버와 라즈베리파이가 통신해야하는 상황이 발생하였다.</p>
<p>이전에 스마트 태블릿 보관함을 제작하면서 사용했던 방식인 express 서버에서 socketio 라이브러리를 통해서 라즈베리파이와 통신을 하였다.
이와 같은 방식으로 통신을 하려고 한다.</p>
<p>그렇다면 Socket.io란 무엇인가?
끄투나 웹을 통해서 채팅을 하는 앱 같은 경우에 실시간으로 통신해야 한다.
채팅은 클라이언트와 클라이언트가 실시간으로 통신해야 하기에 중간에 서버가 클라이언트의 메시지를 대상 클라이언트에게 전달해준다.
이렇게 실시간 양방향 통신이 가능하도록 해주는 라이브러리이다.
이를 통해서 express 서버에서 구축한 socketio 서버를 라즈베리파이가 접속하여 실시간으로 센서의 데이터를 송수신할 수 있게 된다.</p>
<p>먼저 express 서버에 socket io 서버를 구축해야 한다.</p>
<pre><code class="language-shell">npm i socket.io</code></pre>
<p>쉘에 위 명령어를 입력해서 라이브러리를 추가한다.</p>
<p>이제 express 서버 코드에 바로 추가해서 사용할 수 있다.
자바스크립트를 사용하였으며 commonjs모듈을 사용하였습니다.
페이지 표시하는 코드는 제외하였습니다 (원하시는 일부분의 코드만 가져가서 사용하시면 됩니다)</p>
<pre><code class="language-javascript">const express = require(&#39;express&#39;) // express 라이브러리 선언
const socketIO = require(&#39;socket.io&#39;) // Socket 라이브러리 선언
// 기존에 http를 선언하지 않고 app.listen 함수를 통해 바로 서버를 실행시킬 수 있는데, 서버 객체를 socket io에 인자로 넣어주어야 하기에 따로 선언을 위해 http 라이브러리를 가져와 선언
const http = require(&#39;http&#39;)
const bodyParser = require(&#39;body-parser&#39;)

const app = express()
const port = 3000
const server = http.createServer(app) // Socket io에 서버 객체 전달을 위한 객체 선언
const io = socketIO(server) // Socket io 서버 객체 선언

// 하나의 새로운 클라이언트가 연결되었을 때 실행되는 이벤트
io.on(&#39;connection&#39;,(socket)=&gt;{
  // socket을 통해서 연결된 클라이언트의 이벤트를 수신할 수 있음
  // 연결된 클라이언트에서 message라는 이름으로 이벤트가 발생하였을 경우 실행
  socket.on(&#39;message&#39;, (data)=&gt;{
    console.log(data)
  })
})

// express 서버 수신 시작 (위에서 선언한 port의 상수를 통해 포트 설정)
server.listen(port, ()=&gt;{
  console.log(&quot;서버가 실행되었습니다&quot;)
})</code></pre>
<p>위와 같은 코드를 작성하였다면 이제 라즈베리파이 또는 파이썬 코드를 작성해주어야 합니다.
라즈베리파이를 통해서 센서값을 불러와 파이썬을 통해 express 서버로 전송해줄 수 있다.
최종적으로 express와 라즈베리파이가 서로 통신을 위한 단계라고 볼 수 있다.</p>
<p>파이썬을 통해 socket.io를 사용하기 위해서는 파이썬 패키지를 설치해주어야 한다.
파이썬 패키지는 nodejs처럼 &#39;socket.io&#39;가 아니라 다음과 같은 명령어를 입력해주어야 한다.</p>
<pre><code class="language-shell">pip install python-socketio</code></pre>
<p>위 명령어를 입력하면 파이썬 전용 socket.io 패키지 설치가 진행된다.</p>
<p>설치가 완료되었다면 main.py 파일을 만들어 다음과 같은 코드를 작성한다.
제가 작성한 모든 코드를 공개하지 않고 일부를 공개하여 독자분들께서 어느정도 추출해서 사용하실 수 있습니다.</p>
<pre><code class="language-python">import socketio
import time

sio = socketio.Client() # socketio패키지의 Client 객체 선언

sensor_value = 0 # 센서 값을 저장해줄 변수 (센서값을 수신하고 저장하는건 미제공)


# 파이썬 socket.io는 다음과 같은 방법으로 이벤트를 수신할 수 있다
@sio.event
def connect():
    print(&#39;연결되었습니다&#39;)

# 센서값을 기반으로 서버에 전송하기
whie True:
    sio.emit(&#39;message&#39;,&quot;센서값 : {}&quot;.format(sensor_value))
    time.sleep(1) # 1초 이후 다시 반복되도록 (밑에 추가적인 코드가 없으니)

# 연결 할 Socket.io 서버의 아이피 주소
sio.connect(&#39;서버의 아이피주소&#39;)

# 서버의 데이터 수신 대기
sio.wait()</code></pre>
<p>위와 같이 파이썬 코드를 작성하면 이제 파이썬에서 express로 메시지가 1초씩 송수신 될 것이다.</p>
<p>이렇게 해서 라즈베리파이를 기반으로 작성한 socket.io를 활용한 데이터 통신이였습니다.
이벤트의 송신 수신은 양측에서 가능하기에 emit 함수만 사용해서 사용가능합니다</p>
<p>내용에 오류가 있거나 보충 의견이 있으시다면 언제나 말해주세요</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Raspberry PI + Fingerprint Sensor]]></title>
            <link>https://velog.io/@j-iwon/Raspberry-PI-Fingerprint-Sensor</link>
            <guid>https://velog.io/@j-iwon/Raspberry-PI-Fingerprint-Sensor</guid>
            <pubDate>Mon, 13 May 2024 15:15:54 GMT</pubDate>
            <description><![CDATA[<p>라즈베리파이와 지문인식 센서를 통해서 지문인식 결과를 문자열로 변환하여 데이터베이스에 저장하고 다음 인식 될 지문과 일치율을 구하는 코드를 짜보았다.</p>
<p>데이터베이스에 저장하는 부분은 구현하지 않았다.
조만간 구글의 Firebase를 통해 구현 할 예정이다.</p>
<p>먼저 파이썬을 통해 구현을 하였다.
다음의 라이브러리를 사용하였고, 라이브러리를 사용하기 위해 사용 가능한 지문인식 센서는 다음과 같다.
ZFM-20, ZFM-60, ZFM-70, ZFM-100, R302, R303, R305, R306, R307, R551, FPM10A
위 지문인식 모델들에서 사용이 가능하다.
필자는 R307 모델을 사용하였다 (프로세서는 AS608)
먼저 쉘을 통해 라이브러리를 설치한다.</p>
<pre><code class="language-bash">pip install pyfingerprint</code></pre>
<p>위와 같이 입력하면 설치가 진행된다.
이제 라즈베리파이와 지문인식 센서를 결선할건데 다음과 같이 결선을 하면 된다.
<img src="https://velog.velcdn.com/images/j-iwon/post/05e035e7-1de4-40fb-a1ce-f8391737a88f/image.png" alt="">
TX와 RX를 잘 보고 결선해야한다.
위 처럼 결선하고 다음의 명령어를 입력한다</p>
<pre><code class="language-bash">ls -al /dev |  grep serial</code></pre>
<p><img src="https://velog.velcdn.com/images/j-iwon/post/331c7158-bcb3-4ce8-aeb0-4e6b774409e0/image.png" alt="">
다음과 같이 ttyAMA0이 나오면 정상적으로 결선이 완료된 것이다.
이제 코딩을 진행하면 된다.</p>
<pre><code class="language-python">import time # 기다리기를 위해 참조
from pyfingerprint.pyfingerprint import PyFingerprint # 지문인식 라이브러리
try:
    f = PyFingerprint(&#39;/dev/ttyAMA0&#39;,57600, 0xFFFFFFFF, 0x00000000) # 지문인식센서와 통신
    if(f.verifyPassword() == False):
        raise ValueError(&#39;Password wrong!&#39;)
except Exception as e:
    print(str(e))
    exit(1)

try:
    print(&#39;Waiting for finger...&#39;) # 지문 기다리기 안내
    while(f.readImage() == False): # 지문 인식될때까지 기다리기
        pass
    f.convertImage(0x01) # 인식 된 지문을 버퍼 0x01 주소에 저장
    characterics = str(f.downloadCharacteristics(0x01)).encode(&#39;utf-8&#39;) # 0x01에 저장된 값을 문자열로 변환
    print(characterics) # 문자열로 변환한 지문 값을 출력
except Exception as e:
    print(&#39;error&#39;)
    print(str(e))
    exit(1)

time.sleep(1)

try:
    print(&#39;Verifying for finger...&#39;) # 위에서 인식한 지문과 일치한지 검사 시작
    while(f.readImage() == False): # 지문 인식 기다리기
        pass
    f.convertImage(0x01) # 인식 된 지문을 버퍼 0x01주소에 저장
    print(f.uploadCharacteristics(0x02,eval(characterics))) # 이전에 입력한 지문을 0x02주소에 넣고 출력
    score = f.compareCharacteristics() # 0x01 지문과 0x02 주소의 지문을 비교
    print(score) # 일치율 출력
except Exception as e:
    print(&#39;error&#39;)
    print(str(e))
    exit(1)
</code></pre>
<p>위와 같이 하면 다른 손가락은 일치율이 0이 나오고 손가락은 같고 위치만 바뀌었을때는 60이상 출력된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Flutter - 한개의 텍스트는 왼쪽 정렬 한개의 텍스트는 가운데 정렬]]></title>
            <link>https://velog.io/@j-iwon/Flutter-%ED%95%9C%EA%B0%9C%EC%9D%98-%ED%85%8D%EC%8A%A4%ED%8A%B8%EB%8A%94-%EC%99%BC%EC%AA%BD-%EC%A0%95%EB%A0%AC-%ED%95%9C%EA%B0%9C%EC%9D%98-%ED%85%8D%EC%8A%A4%ED%8A%B8%EB%8A%94-%EA%B0%80%EC%9A%B4%EB%8D%B0-%EC%A0%95%EB%A0%AC</link>
            <guid>https://velog.io/@j-iwon/Flutter-%ED%95%9C%EA%B0%9C%EC%9D%98-%ED%85%8D%EC%8A%A4%ED%8A%B8%EB%8A%94-%EC%99%BC%EC%AA%BD-%EC%A0%95%EB%A0%AC-%ED%95%9C%EA%B0%9C%EC%9D%98-%ED%85%8D%EC%8A%A4%ED%8A%B8%EB%8A%94-%EA%B0%80%EC%9A%B4%EB%8D%B0-%EC%A0%95%EB%A0%AC</guid>
            <pubDate>Mon, 13 May 2024 14:57:47 GMT</pubDate>
            <description><![CDATA[<p>학교에서 열람실 지문인식 출석부를 제작하고 있다.
교사용 어플리케이션 제작 중 상단 바에 다음 디자인과 같이 표시하고 싶었다.
<img src="https://velog.velcdn.com/images/j-iwon/post/a9ad3539-89b3-4544-a47f-891e5e0e1c72/image.png" alt="">
하지만 Flutter에서 이를 표현하기 위해서 다양한 방법을 시도해보았지만 실패하였는데 계속 찾아보다 Stack이라는 위젯을 통해 구현할 수 있다는 것을 알아냈다.
Stack 위젯이란 다음과 같이 위잿이 한층 한층 쌓이는 위젯이다.
<img src="https://velog.velcdn.com/images/j-iwon/post/a416c038-9bd2-418e-a907-30c69ba39532/image.png" alt="">
그러면 이를 통해서 어떻게 구현할 수 있는가?
물론 Stack 위젯으로만 구현할 수 있는 기능은 아니다.
따라서 절대 위치에 배치하는 방법에 대해 검색해보았다.
Positioned라는 위젯을 통해서 절대 위치에 위젯을 배치시킬 수 있었다.
그렇다면 텍스트 위젯을 Positioned 위젯을 통해 감싸고 다음 위젯에 그냥 텍스트 위젯을 배치하면 처음에 원했던 방식으로 정렬이 된다.
실제 코드는 다음과 같다.
물론 배경색을 칠해주기 위해 Expanded 위젯을 사용한다.</p>
<pre><code class="language-dart">@override
Widget build(BuildContext context){
return Row(children: [
    Expanded(
        child:Container(
            color: const Color.fromRGBO(36, 111, 255, 1),
            padding: const EdgeInsets.only(bottom: 15),
        child: const Column(children: &lt;Widget&gt;[
          SizedBox(height: 20),
          Stack(
            children: [
              Positioned( // 절대 위치를 통해 배치한다.
                left: 20, // 왼쪽 여백 20만큼 주기
                child: Text(
                  &#39;학교명&#39;,
                  style: TextStyle(fontSize: 20, fontFamily: &#39;LINE&#39;, fontWeight: FontWeight.w700,color: Colors.white),
                ),
              ),
              Center(
                child: Text(
                  &#39;자율학습 출석 관리 시스템&#39;,
                  style: TextStyle(fontSize: 24, fontFamily: &#39;LINE&#39;, fontWeight: FontWeight.bold,color: Colors.white),
                ),
              ),
            ],
          ),
        ]),
      )),
    ]);</code></pre>
<p>위와 같이 구현할 수 있는데 Stack 위젯 안에 포함된 위젯은 다음 위젯을 추가 할수록 스택으로 쌓이기 때문에 다음 위젯의 위치를 고려할 필요가 없다.
그래서 Stack 위젯 안에 Positioned 위젯을 통해 절대 위치를 잡아주고 그 다음 위젯은 Center 위젯으로 감싸서 가운데 배치를 해주었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ESP32와 Blynk를 활용한 IoT 구현]]></title>
            <link>https://velog.io/@j-iwon/ESP32%EC%99%80-Blynk%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-IoT-%EA%B5%AC%ED%98%84</link>
            <guid>https://velog.io/@j-iwon/ESP32%EC%99%80-Blynk%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-IoT-%EA%B5%AC%ED%98%84</guid>
            <pubDate>Wed, 10 Apr 2024 11:18:50 GMT</pubDate>
            <description><![CDATA[<p>오늘은 ESP32를 통해 집에 직접 스마트홈을 구축하는 과정에 대해 기록한다.</p>
<p>먼저 IoT란 <strong>Internet of Things</strong>라는 뜻으로 사물이 인터넷으로 연결되는 것을 의미한다.
이렇게 사물에 인터넷을 연결하기 위해서는 사물을 작동시킬 수 있고 인터넷과 연결될 수 있는 제어 보드가 필요하다.
여기에 적합한 보드 중 아두이노 ESP32라는 와이파이 모듈이 탑재되어 공식 사이트에서 구매할 수 있는 보드도 있지만, 돈이 많지 않기에 비슷한 모델을 선택했다.
중국에 있는 테그선전이라는 회사에서 제작한 <strong>ESP32 Goouuu</strong> 개발보드를 사용한다.
<img src="https://velog.velcdn.com/images/j-iwon/post/1179b966-af6b-4149-82ac-8b84ea1ae8f2/image.png" alt=""></p>
<p>위 사진과 동일하게 생겼고 ESP32 칩에 연결할 수 있는 핀들을 쉽게 결선할 수 있고 전원 공급과 코딩을 쉽게할 수 있도록 제작된 보드이다.</p>
<p>이 보드를 통해 릴레이를 사용하거나 서보모터를 이용하여 방의 전등 스위츠를 조작할 수 있다.</p>
<p><strong>릴레이란</strong> 높은 전압을 입력받아 수도꼭지가 열리듯이 신호를 주면 스위치가 열려 이어진 선으로 전류가 흐르게 되는 모듈이다. 이러한 릴레이 모듈을 사용하게 되면 방에 있는 스위치를 뜯어서 220V 전선 작업을 해야하므로 위험요소가 많다. 따라서 나중에 기회가 된다면 충분한 지식을 가진 상태로 릴레이로 전원을 작동시켜볼 것이다.</p>
<p>그래서 이번 포스팅에서는 서보모터를 이용하여 스위치를 제어할 수 있도록 제작하기로 하였다.</p>
<p>먼저 서보모터와 ESP32 보드를 서로 연결시켜주어야 하는데 아래 결선도와 같이 결선해주었다.</p>
<p><img src="https://velog.velcdn.com/images/j-iwon/post/496415e8-f235-4d41-9264-93a4efd54a36/image.png" alt="">
위 사진과 같이 ESP32 칩 <strong>G27번핀에 서보모터</strong>를 연결하고 <strong>G15에 네오픽셀을 결선</strong>해주었다.
이때, 네오픽셀은 Dout가 아닌 <strong>Din으로 연결</strong>해주어야 함을 유의해야 한다.</p>
<p>기존에는 서보모터를 돌려 스위치를 작동시켜 켜고 끌 예정이였지만, 추가적으로 침대 쪽에 네오픽셀을 달아 무드등을 구현해주려고 네오픽셀을 추가하였다.</p>
<p>이제 ESP32 칩에 코드를 작성하여 플래쉬를 하기 전 IoT 작업을 미리 해야한다.
IoT를 위해서는 서버가 필요한데 대표적으로 아두이노에서 IoT를 구현할 때 사용하는 서버는 Arduino IoT Cloud와 Blynk가 있다.
저자는 Blynk를 사용하여 IoT를 구현하고자 한다.</p>
<p><a href="https://blynk.cloud">https://blynk.cloud</a> 주소로 들어가면 다음과 같이 로그인을 해야한다.
<img src="https://velog.velcdn.com/images/j-iwon/post/c87bcf7d-6de4-4c65-9bac-6262feac8f5f/image.png" alt=""></p>
<p>로그인을 완료하면 템플릿(프로젝트)을 하나 만들어주어야 한다.
Blynk에서 템플릿은 각 디바이스에서 사용할 수 있는 핀들과 다양한 정보들을 정의하는 공간이라고 볼 수 있다.
<img src="https://velog.velcdn.com/images/j-iwon/post/7b65a57e-8d4c-4fd8-83f9-2bc4a4babcd0/image.png" alt="">
우측 상단에 New Template 버튼을 클릭하여 새로운 템플릿을 제작한다.
<img src="https://velog.velcdn.com/images/j-iwon/post/d66d31da-c5b4-4c55-8dbe-b3fd24bb2ba2/image.png" alt="">
먼저 기본적으로 템플릿의 이름을 지정하고 중요한 부분은 <strong>HARDWARE(하드웨어)와 CONNECTION TYPE(연결 방식)</strong>이다.</p>
<p>하드웨어는 ESP32 칩을 통해 Bylnk 와 통신을 해줄 것이기 때문에 ESP32로 지정하고 연결 방식은 특별한 방식이 아니라면 WiFi로 설정한다.
Description(설명)은 굳이 작성하지 않아도 된다.
새로운 템플릿을 만들게 되면 다음과 같이 Datastreams(데이터 스트림) 등을 설정할 수 있는 화면이 나온다.
<img src="https://velog.velcdn.com/images/j-iwon/post/4f66fc38-05d7-4159-a6dd-fcc707c40d6c/image.png" alt="">
이제 서보모터와 네오픽셀을 작동시킬 가상 핀을 구성해야 한다.
여기서 가상핀이란 아두이노나 ESP32에 센서와 통신할 수 있는 핀이 있듯이 Blynk에서는 보이지 않는 가상 핀을 만들어 <strong>센서 값이나 대시보드의 값을 ESP32 또는 아두이노와 통신</strong>하기 위한 것이다.
Datastreams 탭으로 들어가면 다음과 같이 새로운 데이터스트림을 제작할 수 있다.
<img src="https://velog.velcdn.com/images/j-iwon/post/f9054bfc-2edb-4413-a612-6471ee25ef85/image.png" alt="">
New Datastream을 눌러 Virtual Pin(가상 핀)을 클릭하여 가상 핀을 만든다.
<img src="https://velog.velcdn.com/images/j-iwon/post/255d9e32-695d-4e32-8990-4ee03b80a071/image.png" alt="">
먼저 첫 번째 가상 핀의 이름은 불을 껐다 켤 수 있도록 Light로 한다.
여기서 중요한 것은 값을 어떤 형식으로 가져오고 최소 최대 값을 미리 지정해야한다는 것이다.
<strong>DATA TYPE(자료 유형)은 Integer로 정수형 데이터만 송수신할 수 있도록 지정하고, MIN(최소값) MAX(최대값)은 각각 0과 1로 지정한다.</strong>
0과 1로 지정을 하면 참과 거짓을 쉽게 나눌 수 있기 때문이다.
이를 통해서 버튼을 눌러 활성화되면 ESP32에 1이 전송되고 한번 더 누르면 비활성화되어 0이 전송된다.
그렇다면 DATA TYPE(자료 유형)을 Boolean(부울) 형식으로 바꾸면 되지 않느냐는 의문을 가질 수 있지만, Blynk에서 지원하는 가상 핀의 자료형은 Integer(정수형), Dobule(부동소수점), String(문자형) 3가지만 지원하기에 Boolean을 구현할 수 있는 자료형은 Integer이 적합하기에 Integer을 통해 Boolean 자료형의 방식을 구현한다.
<img src="https://velog.velcdn.com/images/j-iwon/post/276c4197-d8e1-4107-b37a-3f6f6e10248d/image.png" alt="">
다음과 같이 두 가지 가상 핀을 제작한다. (Light, Servo)
두 가상 핀은 동일한 자료형과 최소 최대값을 가지고 있어야 한다.
Servo핀은 서보모터를 움직일 수 있게 하기 위한 핀이다.
(주의할 점은 Servo에 각도 값을 넣는 것은 프로그래밍 과정에서 작성해야 하고 여기서는 편의를 위해서 0,1로 데이터를 전송하고 ESP32에서 지정된 값만큼 움직일 수 있도록 구현한다)</p>
<p>이제 우측 상단에 Save 버튼을 클릭해서 템플릿 데이터를 저장한다.
좌측 Devices 탭으로 들어가서 새로운 디바이스(기기)를 하나 만들어준다.
<img src="https://velog.velcdn.com/images/j-iwon/post/3009e909-983e-4e93-a938-f241814a7e5c/image.png" alt="">
우측 상단의 New Device 버튼을 클릭하여 새로운 디바이스를 만들 수 있다.
새로운 디바이스를 만들기 위해서는 아래와 같이 템플릿에서 가져올 것인지 직접 입력한 것인지 물어본다.
하지만 이전 단계에서 템플릿을 구현하였기에 From template 버튼을 클릭하여 다음 단계로 이동한다.
<img src="https://velog.velcdn.com/images/j-iwon/post/98f2d7b5-745e-4389-848f-e663bf646ed6/image.png" alt="">
From template 버튼을 클릭하여 어떤 템플릿을 사용하여 기기를 제작할 것인지 물어보는데 다음과 같이 이전에 제작한 Velog(이름이 다를 수 있음)라는 템플릿을 사용하여 Velog라는 기기를 제작할 수 있다.
기기명은 템플릿 명과 기본적으로 동일해지지만 직접 변경할 수 있다.
<img src="https://velog.velcdn.com/images/j-iwon/post/c45d3608-9328-4861-9f8e-d1a157d133d2/image.png" alt="">
디바이스를 제작하면 다음과 같이 기본적인 대시보드와 업타임을 확인할 수 있는데, 현재 대시보드에 할수 있는게 없다.
웹 대시보드에서 ESP32를 제어하는 방법도 있지만 이 글에서는 모바일 Blynk에서 작동을 구현할 것이다.
따라서 휴대전화에 스토어에 들어가 Blynk를 검색하여 어플리케이션을 다운로드 해준다.
<img src="https://velog.velcdn.com/images/j-iwon/post/95920a62-097d-4aaf-96e2-b2c272fff3c9/image.png" alt="">
(안르도이드 스토어에서도 있다)
설치를 마치고 열면 로그인 후 다음과 같이 제작한 디바이스가 보인다.
<img src="https://velog.velcdn.com/images/j-iwon/post/0a453d00-df14-4298-b522-fbf902710800/image.png" alt="">
제작한 디바이스인 Velog를 터치하여 설정을 해줄 것이다.
<img src="https://velog.velcdn.com/images/j-iwon/post/47fee869-069f-4831-88d9-354ff4c84a29/image.png" alt="">
우측 상단 초록색 배경의 설정 모양 버튼을 터치하면
<img src="https://velog.velcdn.com/images/j-iwon/post/30748450-8e9b-4f42-95d4-43e1b8287dd0/image.png" alt="">
대시보드를 꾸밀 수 있는 화면으로 전환되고 하단의 +버튼을 터치하면
<img src="https://velog.velcdn.com/images/j-iwon/post/1025d050-2a29-43a2-8764-457626929e58/image.png" alt="">
Button을 터치하여 새로운 버튼을 하나 만들어준다.
새로 만든 버튼을 하나 누르면 버튼에 대한 동작을 설정할 수 있는데
<img src="https://velog.velcdn.com/images/j-iwon/post/950ddba2-b3a1-473e-8b21-7ddb69bb002c/image.png" alt="">
위 사진과 같이 DataStream을 Light로 해주고 MODE를 Switch로 설정한다.
따라서 방금 제작한 버튼은 Light(불)을 제어할 수 있는 버튼이 된 것이고 Switch로 설정하여야 눌렀을 때 ON(켜짐) 상태가 유지된다.
동일한 방법으로 Servo 버튼도 제작한다.
<img src="https://velog.velcdn.com/images/j-iwon/post/092089bf-d4a0-44bc-8668-1cd353cb6a08/image.png" alt="">
위 사진과 같이 버튼 두개를 제작해주었다면 좌측 X버튼을 클릭해 나간다.
이제 Blynk 기본 설정을 마쳤다.
마지막 단계인 프로그래밍을 통해 ESP32에 소스코드를 작성하여 플래쉬 해주겠다.</p>
<p><strong>아두이노 IDE가 기본적으로 설치되어 있다는 전제로 작성하였습니다.</strong></p>
<p>(아두이노 2.0.0 버전 이상 기준)
아두이노 IDE를 실행하고 우측 라이브러리를 다운로드 받을 수 있는 탭으로 이동한다.
<img src="https://velog.velcdn.com/images/j-iwon/post/3699f929-0187-4b9e-9186-30dc1cd61114/image.png" alt="">
검색창에 위와 같이 Blynk를 입력하여 Volodymyr Shymanskyy가 제작한 라이브러리를 다운로드 한다.
다운로드를 완료하면 다음과 같이 File -&gt; Examples -&gt; Blynk -&gt; Boards_WiFi -&gt; ESP32_WiFi 예제를 클릭해서 열어준다.
<img src="https://velog.velcdn.com/images/j-iwon/post/2f93dd23-615e-41ae-a609-d2b60b3d772a/image.png" alt="">
위와 같이 코드가 나오는데 ESP32에서 네오픽셀과 서보모터를 작동시키려면 추가적인 라이브러리가 필요하다.
<a href="https://github.com/RoboticsBrno/ServoESP32/releases/tag/v1.1.1">https://github.com/RoboticsBrno/ServoESP32/releases/tag/v1.1.1</a> 깃허브로 들어가 Source code 버튼을 클릭하여 라이브러리를 다운로드 하고
<img src="https://velog.velcdn.com/images/j-iwon/post/d978b21c-1f56-4d74-877f-57e8a8e6357f/image.png" alt="">
라이브러리 매니저에 neo pixel 를 검색하여 Adafruit가 제작한 라이브러리를 설치해준다.
다시 Blynk 홈페이지에서 디바이스를 누르고 Device Info 버튼을 클릭하여 Firmware Configuration(펌웨어 구성요소)을 복사한다.
<img src="https://velog.velcdn.com/images/j-iwon/post/f926365d-982b-4c52-b864-52df603b74ac/image.png" alt="">
(각각의 디바이스마다 다르므로 주의하세요)
이제 아래의 코드를 이해하면서 입력하여 보드에 업로드를 해준다.</p>
<pre><code class="language-cpp">/* Device에 Device Info탭에 있는 Firmware Confirguration 항목에 있는 내용 붙여넣기 */
//#define BLYNK_TEMPLATE_ID           &quot;TMPxxxxxx&quot;
//#define BLYNK_TEMPLATE_NAME         &quot;Device&quot;
//#define BLYNK_AUTH_TOKEN            &quot;YourAuthToken&quot;

/* 라이브러리 정의 */
#include &lt;WiFi.h&gt; // Blynk 작동을 위한 필수 라이브러리
#include &lt;WiFiClient.h&gt; // Blynk 작동을 위한 필수 라이브러리
#include &lt;BlynkSimpleEsp32.h&gt; // Blynk 작동을 위한 필수 라이브러리
#include &lt;Adafruit_NeoPixel.h&gt; // 네오픽섹을 사용하기 위한 라이브러리
#include &lt;ESP32_Servo.h&gt; // ESP32에서 서보모터를 사용하기 위한 라이브러리

// 연결할 와이파이 설정
char ssid[] = &quot;와이파이 이름&quot;;
char pass[] = &quot;와이파이 비밀번호&quot;;

#define PIN 15 // 네오픽셀이 결선 된 핀 번호
#define NUM 30 // 연결 된 내오픽셀의 픽셀 개수

static const int servoPin = 27; // 서보모터의 핀 번호

Servo servo1; // 라이브러리의 Servo를 servo1으로 객체 정의
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUM,PIN, NEO_GRB + NEO_KHZ800); // 네오픽셀을 사용하기 위해 네오픽셀 클래스 가져오기, 괄호에는 클래스를 가져오기 위한 필수 인자들이 기입되어 있음.

void setup()
{
  // 디버깅 콘솔 (9600보레이트로 통신)
  Serial.begin(9600);


  Blynk.begin(BLYNK_AUTH_TOKEN, ssid, pass); // Blynk 시작
  pixels.begin(); // 네오픽셀 시작

}

void loop()
{
  Blynk.run(); // Blynk 데이터 송수신
}

// 가상핀 V0번에 쓰기 이벤트 발생 시 실행되는 함수
BLYNK_WRITE(V0)
{   
  int value = param.asInt(); // 전송 된 가상핀의 값을 value라는 변수에 저장
  servo1.attach(servoPin); // 서보에 전원 인가
  if(value == 1){ // 전송 값이 1이라면
    servo1.write(180); // 서보 180도 회전
  }else servo1.write(0); // 아니라면 0도 회전
  delay(500); // 500ms 기다린 후
  servo1.detach(); // 서보 전원 해제

}

// 가상핀 V1번에 쓰기 이벤트 발생 시 실행되는 함수
BLYNK_WRITE(V1)
{   
  int value = param.asInt(); // 전송 된 가상핀의 값을 value라는 변수에 저장
  if(value == 1) static1(255, 111, 0); // static1 함수를 실행키기 (부드러운 노란색)
  else static1(0,0,0); // static1 함수를 실행시켜 네오픽셀 R,G,B 값을 각각 0,0,0으로 설정
}

// 네오픽셀을 제어하기 위한 함수
void static1(int r, int g, int b) { // R,G,B 인자 가져오기
  for (int i = 0; i &lt;= NUM; i++) { // 네오픽셀 개수만큼 켜주어야 하기에 반복문
    pixels.setPixelColor(i, pixels.Color(r, g, b)); // 네오픽셀 반복문 주소에 인자로 받은 값들을 빛으로 표현
    pixels.show(); // 설정된 픽셀을 보여주기
  }
}
</code></pre>
<p>위와 같이 코드를 작성하면 다음 영상처럼 작동한다.
<a href="https://youtu.be/_S13KnjoMeQ">https://youtu.be/_S13KnjoMeQ</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Vue.js와 Firebase 인증 구현하기 (프로젝트에 Firebase, VueFire 추가하기) #1]]></title>
            <link>https://velog.io/@j-iwon/Vue.js%EC%99%80-Firebase-%EC%9D%B8%EC%A6%9D-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EC%97%90-Firebase-VueFire-%EC%B6%94%EA%B0%80%ED%95%98%EA%B8%B0-1</link>
            <guid>https://velog.io/@j-iwon/Vue.js%EC%99%80-Firebase-%EC%9D%B8%EC%A6%9D-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EC%97%90-Firebase-VueFire-%EC%B6%94%EA%B0%80%ED%95%98%EA%B8%B0-1</guid>
            <pubDate>Fri, 30 Jun 2023 11:25:15 GMT</pubDate>
            <description><![CDATA[<p>프론트엔드 개발을 도와주는 프레임워크인 <a href="https://vuejs.org/">Vue.js</a>에서 구글의 <a href="https://firebase.google.com/?hl=ko">Firebase</a>를 통해서 오직 프론트엔드에서 회원가입 및 로그인을 구현할 것이다</p>
<p>이 포스팅에서는 Vue-router라는 개념을 이해하였다는 전재로 작성되었다.</p>
<p>Firebase에서는 서버 또는 웹 환경에서 서비스를 사용할 수 있도록 npm 모듈 형식으로 제공하고 있다.
Vue.js 프로젝트에서 다음과 같이 설치를 진행할 수 있다.</p>
<pre><code class="language-bash">$ npm install firebase</code></pre>
<p>또한 Vue.js에서 Firebase를 편리하게 사용할 수 있도록 <a href="https://vuefire.vuejs.org/">VueFire</a>라는 모듈을 추가로 제공하고 있다.</p>
<pre><code class="language-bash">$ npm install vuefire</code></pre>
<p>VueFire를 사용하기 위해서는 firebase도 꼭 설치가 되어있어야 한다.
위 모듈을 설치하기 위해서는 Vue.js 프로젝트에서 실행하여야 한다.</p>
<p>설치를 완료하면 새로운 프로젝트를 생성하여야 한다.
<a href="https://console.firebase.google.com/">여기</a>를 눌러 Firebase 콘솔로 이동해준다.</p>
<p><img src="https://velog.velcdn.com/images/j-iwon/post/1a054711-9da1-412f-a906-eb9bb3fd7e87/image.png" alt="">
다음과 같은 페이지가 나오면 <strong>프로젝트 추가</strong> 버튼을 클릭해서 새 프로젝트를 만드는 과정을 시작한다.</p>
<p><img src="https://velog.velcdn.com/images/j-iwon/post/5bec9f18-a77c-485f-909e-d6242667ffaa/image.png" alt="">
새 프로젝트를 만들기 위해서는 프로젝트 이름을 입력해준다.
이름은 자신이 원하는 이름으로 해도 무관하다.
<img src="https://velog.velcdn.com/images/j-iwon/post/af629918-7c8c-45d8-a0b2-cd7f1ce09160/image.png" alt="">
Firebase로 만든 서비스의 동작 동향을 분석해주는 애널리틱스 사용 여부를 확인한다.
만약 프로젝트에서 사용한다고 하였으면 다음과 같이 애널리틱스 계정을 선택하면 된다.
<img src="https://velog.velcdn.com/images/j-iwon/post/e04a42fb-ff1a-4fdd-b518-f048224fff93/image.png" alt="">
이제 프로젝트 만들기 버튼을 클릭해서 프로젝트를 생성해준다.
<img src="https://velog.velcdn.com/images/j-iwon/post/c4fc7756-db74-4d27-b7b6-5a431438cc4a/image.png" alt="">
프로젝트를 생성하면 다음과 같이 프로젝트 화면이 나오면서 새로운 앱을 추가시킬 수 있다.
우리는 Vue.js를 사용하여 웹 애플리케이션을 제작하고 있으므로 &lt;/&gt;아이콘의 웹 버튼을 클릭하면 된다.
<img src="https://velog.velcdn.com/images/j-iwon/post/c66b6fab-9613-41a2-a694-80a6ab3cf86d/image.png" alt="">
웹 앱을 Firebase에 추가시키기 위해서 웹 이름을 입력해준다.
(추가로 Firebase에서 웹 호스팅도 가능하다)
<img src="https://velog.velcdn.com/images/j-iwon/post/4f4354e2-d5b8-483d-aabb-4d18901e15fe/image.png" alt="">
웹 이름을 입력하면 새로운 앱이 등록이 되면서, 추후 코드에 들어갈 Firebase 설정 값들을 제공해준다.
이 값들은 생성화면 뿐만 아니라 프로젝트 설정에서도 받을 수 있다.</p>
<p>Vue.js 프로젝트로 돌아와서 src폴더에 새로운 폴더를 &quot;firebase&quot;라는 이름으로 하나 만들어주자
폴더 안에 새로운 &quot;index.js&quot;라는 이름으로 파일을 만들어주자
<img src="https://velog.velcdn.com/images/j-iwon/post/8791b94c-dd96-4e2d-85eb-b2f29eccf315/image.png" alt="">
만들어주었으면, Firebase에서 앱을 등록하여 나온 설정값을 등록하는 코드를 복사하여 파일에 붙여준다.</p>
<p>src/firebase/index.js</p>
<pre><code class="language-javascript">import { initializeApp } from &#39;firebase/app&#39;; 

// firebase를 사용하기 위한 기본정보
const firebaseConfig = {
    apiKey: &quot;&quot;,
      authDomain: &quot;&quot;,
      projectId: &quot;&quot;,
      storageBucket: &quot;&quot;,
      messagingSenderId: &quot;&quot;,
      appId: &quot;&quot;,
      measurementId: &quot;&quot;
};

export const firebaseApp = initializeApp(firebaseConfig); </code></pre>
<p>위와 같이 코드를 작성해주면 마지막줄에 firebaseApp라는 객체를 내보내서 main.js 파일에서 사용할 것이다.</p>
<p>src/main.js</p>
<pre><code class="language-javascript">import { createApp } from &#39;vue&#39;
import App from &#39;./App.vue&#39;
import router from &#39;./router&#39;
import { VueFire,VueFireAuth } from &#39;vuefire&#39;
import { firebaseApp } from &#39;./firebase&#39; 

const app = createApp(App)
app.use(VueFire,{
  firebaseApp, 
  modules:[
    VueFireAuth(), 
  ]
})
app.mount(&quot;#app&quot;)</code></pre>
<p>위 코드에서는 3번째 줄에 VueFire를 Import 해주었다.
VueFire 모듈에서 내보낸 모든 객체를 가져오지 않고 중괄호를 열어 필요한 객체만 가져온 것을 볼 수 있다.
그 다음줄에서는 방금 만든 firebase/index.js를 통해서 firebaseApp 객체를 가져왔다.</p>
<p>(여기서 index.js 경로를 생략한 이유는 index.js라는 이름이 기본 실행 파일이므로 저런식으로 폴더만 가져와도 자동으로 index.js에서 파일의 내용을 가져오게 된다)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[라즈베리파이 부팅 시 자동실행 등록하기]]></title>
            <link>https://velog.io/@j-iwon/%EB%9D%BC%EC%A6%88%EB%B2%A0%EB%A6%AC%ED%8C%8C%EC%9D%B4-%EB%B6%80%ED%8C%85-%EC%8B%9C-%EC%9E%90%EB%8F%99%EC%8B%A4%ED%96%89-%EB%93%B1%EB%A1%9D%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@j-iwon/%EB%9D%BC%EC%A6%88%EB%B2%A0%EB%A6%AC%ED%8C%8C%EC%9D%B4-%EB%B6%80%ED%8C%85-%EC%8B%9C-%EC%9E%90%EB%8F%99%EC%8B%A4%ED%96%89-%EB%93%B1%EB%A1%9D%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 22 Jun 2023 05:38:05 GMT</pubDate>
            <description><![CDATA[<p>라즈베리파이에서 코딩한 파이썬 파일이 자동으로 실행되도록 하기 위해서 다음과 같이 시도했다.</p>
<p>라즈베리파이 nano 텍스트 편집기로 아래의 경로의 파일을 열어준다.</p>
<pre><code>sudo nano /etc/rc.local</code></pre><p>위 파일에서 제일 하단으로 이동한다 (Ctrl+W -&gt; Ctrl+V)</p>
<pre><code>[ 실행코드 입력 ]
exit(0) </code></pre><p>[ 실행코드 입력 ] 부분에 실행할 코드를 입력하면 된다.</p>
<p>rc.local 시스템이 시작하자 마자 작동하는 프로그램들이 작성되어 있다.
시작하자 마자 실행이 되기 때문에 GUI에 접근할 수 없다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[css position:absolute 센터 배치]]></title>
            <link>https://velog.io/@j-iwon/css-positionabsolute-%EC%84%BC%ED%84%B0-%EB%B0%B0%EC%B9%98</link>
            <guid>https://velog.io/@j-iwon/css-positionabsolute-%EC%84%BC%ED%84%B0-%EB%B0%B0%EC%B9%98</guid>
            <pubDate>Thu, 22 Jun 2023 05:19:58 GMT</pubDate>
            <description><![CDATA[<p>고등학교 태블릿 대여 시스템(프론트엔드)을 구축하면서 html element가 표시되었다가 사라졌다가 반복하는 코드를 구현하면서 밑에 있던 텍스트가 올라왔다 내려왔다 하는 현상이 발생했다.</p>
<p>고정하고 싶은 마음에 element style에 position:absolute를 적용하고 가운데로 배치하기 위해서 다음과 같이 스타일을 추가하였다.
<img src="https://velog.velcdn.com/images/j-iwon/post/f972d8cc-097b-4c06-bff2-1e56ae7547ed/image.png" alt="">
<img src="https://velog.velcdn.com/images/j-iwon/post/4f4ae52c-f028-4f5c-aacf-3d9b00b8eedb/image.png" alt=""></p>
<p>아래 코드를 추가하여</p>
<pre><code> &lt;div v-show=&quot;isHidden&quot; style=&quot;position: absolute; left: 50%; transform:translateX(-50%); margin-top: 20px;&quot;&gt;
     &lt;img style=&quot; width: 100px;&quot; src=&quot;../assets/fingerprint.png&quot; /&gt;
&lt;/div&gt;</code></pre><p>다음과 같이 이미지가 표시되었든 표시가 되지 않았든, 아래 텍스트가 고정되어있는 것을 볼 수 있었다.
<img src="https://velog.velcdn.com/images/j-iwon/post/aa3f6dd3-bf4a-44aa-9ec3-b9b8371efa4d/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[마이크로비트와 Firebase 데이터베이스 이용]]></title>
            <link>https://velog.io/@j-iwon/%EB%A7%88%EC%9D%B4%ED%81%AC%EB%A1%9C%EB%B9%84%ED%8A%B8%EC%99%80-Firebase-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EC%9D%B4%EC%9A%A9</link>
            <guid>https://velog.io/@j-iwon/%EB%A7%88%EC%9D%B4%ED%81%AC%EB%A1%9C%EB%B9%84%ED%8A%B8%EC%99%80-Firebase-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EC%9D%B4%EC%9A%A9</guid>
            <pubDate>Wed, 09 Nov 2022 08:54:18 GMT</pubDate>
            <description><![CDATA[<p>이번 글에서는 마이크로비트가 컴퓨터에 연결이 된 채로 Firebase 데이터베이스를 이용하는 방법에 대해 설명합니다.</p>
<h3 id="1-firebase-데이터베이스-접근하기-">*<em>1. Firebase 데이터베이스 접근하기 *</em></h3>
<p>해당 예제는 Firebase 서비스의 일부인 RealTime Database 서비스를 이용합니다</p>
<p>필요 프로그램 : <strong>Visual Studio Code</strong></p>
<p>관계형 데이터베이스와 비관계형 데이터베이스에 대한 이해를 하고 진행합니다.</p>
<p>RealTime Database는 대중적인 방식인 SQL(Structured Query Language: 관계형 데이터베이스)을 사용하지 않고 NoSQL(Not Only SQL) 종류 중 하나인 Document(문서형)의 JSON(Javascript Object Notation) 방식을 사용하여 저장합니다. 따라서 관계형 데이터베이스와 다르기 때문에 이해가 관계형 데이터베이스가 익숙한 사람에게는 RealTime Database를 이해하는데 시간이 걸릴 수 있습니다.</p>
<p>RealTime Database를 사용하기 위해서는 <a href="https://console.firebase.google.com/">여기</a>를 클릭해서 아래의 사이트로 이동 후 프로젝트 추가 버튼을 클릭합니다
<img src="https://velog.velcdn.com/images/j-iwon/post/40537e33-9c49-44b9-ab24-0aff1b7a8914/image.png" alt="">
첫 번째로 프로젝트 이름을 입력하라고 합니다.</p>
<p>아무 이름이나 작성 후 계속을 클릭하고 애널리틱스에 대한 내용도 확인을 하고 계속을 클릭하고 애널리틱스 구성 화면에서 Default Account for Firebase 버튼을 클릭하고 프로젝트 만들기 버튼을 클릭합니다
<img src="https://velog.velcdn.com/images/j-iwon/post/6feefe75-dc0e-4bc7-b854-d2737d0cc171/image.png" alt="">
새 프로젝트가 생성이 되었다면 좌측에 빌드 항목으로 들어가서 RealTime Database 버튼을 클릭합니다
클릭하여 데이터베이스 만들기 버튼을 클릭하고 데이터베이스가 위치할 지역을 선택하고 다음을 클릭합니다
잠금 모드로 시작을 선택하고 사용 설정을 클릭합니다.
<img src="https://velog.velcdn.com/images/j-iwon/post/38945241-9d32-4fc1-a975-47b87ce34f2e/image.png" alt="">
위와 같은 화면이 데이터베이스가 저장되는 부분의 화면입니다
데이터베이스가 저장되는 항목의 상단에는 데이터베이스 링크가 있습니다
<img src="https://velog.velcdn.com/images/j-iwon/post/e1e84556-1230-46a2-a9a6-fada0ef9aad6/image.png" alt="">
좌측 상단의 톱니바퀴 설정 버튼을 클릭하고 서비스 계정으로 이동합니다</p>
<p>Firebase Admin SDK가 기본적으로 선택이 되면 Admin SDK 구성 스니펫에서 Python 항목을 선택합니다
그리고 새 비공개 키 생성을 클릭합니다
키 생성을 누르면 새로운 키가 생성이 되고 다운로드가 됩니다</p>
<p>이제 Visual Studio Code를 실행하고 상단 메뉴에서 터미널(T) 메뉴를 누르고 새 터미널 버튼을 클릭합니다.
그럼 새 터미널이 하단에 생성됩니다.
아래의 Firebase SDK 설치 과정을 진행해주세요
(pip3 라는 명령어를 인식하지 못할경우 윈도우 환경변수를 추가해주세요 Python버전\Scripts)</p>
<pre><code class="language-bash">pip3 install --upgrade pip #파이프를 업그레이드 합니다
pip3 install firebase_admin #firebase admin 라이브러리를 설치합니다
pip3 install pyserial # 시리얼 통신을 위한 라이브러리 설치</code></pre>
<p>새로운 파일을 만들고 이름은 원하는 이름으로 하되 확장자를 .py로 지정해주세요
이전에 받은 키 파일을 코드파일과 같은 폴더로 이동시켜주세요
아래의 코드를 입력해주세요</p>
<p><strong>[ 예제1 마이크로비트와 통신하기 ]</strong></p>
<h3 id="1-파이썬-코드">1. 파이썬 코드</h3>
<p><strong><em>COM12 부분을 장치 관리자 -&gt; USB 직렬 장치(COMXX) 의 번호로 변경해야 합니다</em></strong></p>
<pre><code class="language-python">import serial
# 시리얼 통신 설정
serial1 = serial.Serial(&quot;COM12&quot;,9600,timeout=1) # 마이크로비트의 포트 번호와 보드 통신 속도를 통해 포트를 열기

# 무한반복
while True:
    microbitData = str(serial1.readline().decode(&#39;utf-8&#39;)) # 시리얼에서 한줄을 읽고 utf-8로 디코딩을 진행 (디코딩을 하지 않으면 b&#39;&#39;와 같은 형식으로 출력됨)
    if microbitData != &quot;&quot;: # microbitData 변수에 문자가 있는지 확인
        print(microbitData) # 받은 문자열을 출력

serial1.close()</code></pre>
<h3 id="2-마이크로비트-코드">2. 마이크로비트 코드</h3>
<pre><code>&lt;div style=&quot;position:relative;height:calc(300px + 5em);width:100%;overflow:hidden;&quot;&gt;&lt;iframe style=&quot;position:absolute;top:0;left:0;width:100%;height:100%;&quot; src=&quot;https://makecode.microbit.org/---codeembed#pub:_9J9XdD873Fgo&quot; allowfullscreen=&quot;allowfullscreen&quot; frameborder=&quot;0&quot; sandbox=&quot;allow-scripts allow-same-origin&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;</code></pre><p>위 코드를 실행하면 마이크로비트에서 A버튼을 눌렀을때 파이썬 터미널에 Hello World가 출력되는 걸 볼 수 있습니다.
(코드는 python 또는 python3 파일명.py로 실행합니다)</p>
<h3 id="2-결과">2. 결과</h3>
<p><img src="https://velog.velcdn.com/images/j-iwon/post/03fd591d-5911-4ff1-8b0d-cad5adf0e8bb/image.png" alt=""></p>
<p><strong>[ 예제2 마이크로비트의 데이터를 저장하기 ]</strong>
데이터베이스의 저장은 Key-Value(키-값) 형식입니다.
따라서 아래의 코드를 실행하면 Python(키) 아래에 Hello World(값) 라는 값이 저장됩니다</p>
<h3 id="1-파이썬-코드-1">1. 파이썬 코드</h3>
<pre><code class="language-python">import serial
import firebase_admin
from firebase_admin import credentials
from firebase_admin import db

# Firebase RealTime Datebase 설정
cred = credentials.Certificate(&#39;serviceAccount.json&#39;) # 이전에 받은 키의 저장 위치(/path/serviceAccount.json)
firebase_admin.initialize_app(cred, {
    &#39;databaseURL&#39;: &#39;https://--.rtdb.firebaseio.com&#39; # 이전에 본 데이터베이스 주소
})
ref = db.reference(&#39;&#39;) # 데이터베이스 위치

# 시리얼 통신 설정
serial1 = serial.Serial(&quot;COM12&quot;,9600,timeout=1) # 마이크로비트의 포트 번호와 보드 통신 속도를 통해 포트를 열기

# 무한반복
while True:
    microbitData = str(serial1.readline().decode(&#39;utf-8&#39;)) # 시리얼에서 한줄을 읽고 utf-8로 디코딩을 진행 (디코딩을 하지 않으면 b&#39;&#39;와 같은 형식으로 출력됨)
    if microbitData != &quot;&quot;: # microbitData 변수에 문자가 있는지 확인
        ref.set({
            &#39;Python&#39;:microbitData
        })
        print(&quot;데이터 저장완료&quot;) # 받은 문자열을 출력

serial1.close()</code></pre>
<h3 id="2-마이크로비트-코드-1">2. 마이크로비트 코드</h3>
<pre><code>&lt;div style=&quot;position:relative;height:calc(300px + 5em);width:100%;overflow:hidden;&quot;&gt;&lt;iframe style=&quot;position:absolute;top:0;left:0;width:100%;height:100%;&quot; src=&quot;https://makecode.microbit.org/---codeembed#pub:_9J9XdD873Fgo&quot; allowfullscreen=&quot;allowfullscreen&quot; frameborder=&quot;0&quot; sandbox=&quot;allow-scripts allow-same-origin&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;</code></pre><h3 id="3-결과">3. 결과</h3>
<p><img src="https://velog.velcdn.com/images/j-iwon/post/df35eed5-0594-4829-84c2-f4696dd7884b/image.png" alt=""></p>
<h2 id="마치며">마치며</h2>
<p>이렇게 해서 마이크로비트와 시리얼 통신을 이용해서 Firebase의 RealTime Database 서비스를 이용하는 방법에 대해서 알아보았습니다.</p>
<blockquote>
<p>결론으로 마이크로비트에서는 <strong>시리얼 통신 값</strong>을 통해서 <strong>데이터를 입력</strong>받고 <strong>데이터를 가공</strong>해서 실시간 <strong>데이터베이스에 업로드</strong>하는 방식이었습니다.</p>
</blockquote>
]]></description>
        </item>
    </channel>
</rss>