Project Overview


  • Period: 2026.5.18 – 2026.7.23
  • Team: 4명
  • My Role: Backend

왜 만들었나?

나는 4살 된 고양이를 키우고 있다. 집에는 자동 급식기와 음수기가 설치되어 있으며, 꽤 만족하며 사용하고 있다. 보통 이러한 반려동물용 IoT 기기는 전용 애플리케이션과 연동되어 있어 원격으로 급여나 급수를 제어할 수 있다. 또한 반려동물이 일별·월별로 얼마나 먹고 마셨는지 확인할 수 있도록 사료 및 음수 데이터를 그래프 형태로 제공한다.

하지만 이러한 원시 데이터만으로는 반려동물의 상태에 대한 유의미한 정보를 얻기 어렵다고 느꼈다. 단순히 식사량과 음수량을 보여주는 것을 넘어 데이터를 분석하여, 비슷한 품종과 연령대의 고양이에 비해 식사량이 많거나 적은지, 평소보다 활동량이 감소하고 있는지 등을 알려주는 서비스가 있으면 좋겠다고 생각했다. 나아가 이러한 데이터를 바탕으로 이상 징후를 파악하고, 필요한 경우 맞춤형 조언까지 제공받고 싶었다.

물론 관련 정보를 인터넷에서 찾아보고 직접 데이터를 비교하거나 계산할 수도 있다. 하지만 매번 이러한 과정을 직접 수행하는 것은 불필요한 인지적 부담이라고 생각했고, 서비스가 대신 데이터를 가공하여 의미 있는 정보로 제공해 주기를 바랐다.

또 한 가지 기존 반려동물용 IoT 서비스에서 아쉽다고 느낀 점은 IP 카메라의 사각지대였다. 보통 반려동물용 카메라는 넓은 공간을 확인하기 위해 방의 높은 곳이나 구석에 설치하지만, 고정된 카메라인 만큼 사각지대가 발생할 수밖에 없다. 반려동물이 사각지대에 장시간 머문다면 외부에 있는 보호자는 현재 상태를 확인할 수 없어 걱정할 수 있다. 그래서 고정된 카메라의 시야에 의존하지 않고, 반려동물이 어디에 있더라도 필요할 때 현재 상태를 확인할 수 있는 서비스를 만들어 보고 싶었다.

이러한 문제의식을 바탕으로 팀 프로젝트에서 IoT 서비스를 주제로 정하게 되었을 때 아이디어를 팀원들과 공유했고, 이를 실제 서비스로 구현하게 되었다.

Architecture

System Architecture

Tech Stack

Languages

JavaScript
Python

Frameworks & Libraries

React
FastAPI
OpenCV

Database & Storage

Oracle DB
Supabase

Messaging & Authentication

MQTT
HiveMQ
JWT

Infrastructure & Deployment

Docker
Railway

Hardware & IoT

Raspberry Pi
Arduino
ESP32

My Contributions

  1. API 전반 구현 From Front to MQTT Broker
  2. LLM 기반 건강 리포트 기능 구현
  3. 배포

Features

실시간 카메라 영상을 통해 반려동물 상태 확인

로봇 원격 제어 화면Ai-Myaong 이동형 카메라 로봇
System Architecture System Architecture

실시간 급식,급수

급식 급수 제어 화면급식 급수 디스펜서

반려동물의 최근 행동/식이패턴 및 맞춤 조언 제공

건강 리포트 상세 화면Ai-Myaong 건강 리포트

Technical Decisions

왜 디바이스 제어 통신에 MQTT를 선택했는가?


Requirements

  • 서로 다른 사용자 네트워크 및 NAT 환경에서도 동작할 것
  • 백엔드가 각 디바이스의 IP 주소를 직접 관리하지 않아도 될 것
  • 사용자의 제어 명령을 낮은 지연으로 디바이스에 전달할 수 있을 것
  • 디바이스가 증가해도 Device ID를 기준으로 명령을 구분할 수 있을 것
  • IoT 디바이스에서 지속적으로 사용하기에 통신 부담이 크지 않을 것

Options & Trade-offs

System Architecture

Decision

우리 서비스에서는 서로 다른 네트워크 환경에 존재하는 IoT 디바이스에, 서버가 개별 디바이스의 네트워크 위치를 직접 관리하지 않고 낮은 지연으로 명령을 전달하는 것을 핵심 요구사항으로 두었다.

HTTP 직접호출 방식은 사용자 사설망 내부의 디바이스에 클라우드 백엔드가 직접 HTTP 요청을 보내기 어렵다는 문제가 있었다. WebSocket은 요구사항을 만족할 수 있지만, 디바이스별 메시지 라우팅과 연결 관리 구조를 애플리케이션에서 직접 구현해야 했다. 반면 MQTT는 디바이스가 Broker와 연결을 먼저 생성하고, Topic을 통해 Device ID 기반으로 메시지를 전달할 수 있어 핵심 요구사항에 가장 부합한다고 판단했다.

따라서 별도의 Broker 인프라가 추가되는 비용을 감수하고, MQTT 기반 Publish/Subscribe 방식을 디바이스 제어 통신 방식으로 선택했다.

왜 수치 계산과 LLM의 역할을 분리했는가?


Requirements

  • 평균, 변화율, 추세와 같은 수치 계산 결과가 항상 동일하고 정확할 것
  • LLM이 존재하지 않는 수치를 생성하거나 원본 데이터를 임의로 계산하지 않도록 할 것
  • 계산 결과를 기반으로 보호자가 이해하기 쉬운 자연어 설명을 제공할 것
  • LLM의 응답을 애플리케이션에서 안정적으로 처리할 수 있도록 정해진 구조로 제한할 것
  • LLM API 호출이나 응답 파싱에 실패하더라도 건강 리포트 기능 전체가 실패하지 않을 것

Options & Trade-offs

Health Report Architecture Options

Decision

우리 서비스에서는 수치 데이터의 정확성과 재현성을 보장하면서, 계산 결과를 보호자가 이해하기 쉬운 형태로 전달하는 것을 핵심 요구사항으로 두었다.

계산과 해석을 모두 LLM에 맡기는 방식은 구현이 단순하지만, 평균이나 변화율과 같은 수치 계산의 정확성과 결과의 일관성을 애플리케이션에서 보장하기 어려웠다. 반대로 모든 리포트를 규칙 기반으로 생성하면 계산 결과는 안정적이지만, 데이터를 자연스럽게 해석하고 설명하는 데 한계가 있었다. 따라서 평균, 변화율, 추세 등 정확성이 필요한 계산은 백엔드 로직에서 수행하고, LLM은 계산된 결과의 해석과 자연어 생성만 담당하도록 역할을 분리했다.

백엔드는 계산 결과를 computed_metrics 형태의 구조화된 데이터로 만들어 LLM에 전달했으며, LLM이 원본 데이터를 직접 계산하지 않도록 했다. 또한 응답을 정해진 JSON 구조로 제한했다.

왜 이미지를 Object Storage에 저장했는가?


Requirements

  • 이미지가 서버의 재배포 및 재시작과 관계없이 영속적으로 저장될 것
  • 이미지 파일을 클라이언트에서 URL을 통해 조회할 수 있을 것
  • 이미지 크기와 개수가 증가하더라도 DB의 저장 및 I/O 부담을 최소화할 것
  • 이미지 파일과 관련된 사용자·반려동물 등의 구조화된 데이터 사이의 관계를 관리할 수 있을 것
  • 이미지 저장소와 서버의 생명주기를 분리할 수 있을 것

Options & Trade-offs

Image Storage Options

Decision

우리 서비스에서는 이미지 파일의 영속성을 보장하면서, 메타데이터와 실제 파일의 저장 책임을 분리하는 것을 핵심 요구사항으로 두었다.

이미지를 DB의 BLOB 형태로 직접 저장하면 메타데이터와 파일을 하나의 저장소에서 관리할 수 있지만, 이미지의 크기와 개수가 증가할수록 DB의 저장 용량과 I/O 부담도 함께 증가한다. 서버의 로컬 파일 시스템에 저장하는 방식은 구현이 단순하지만, 파일이 서버의 실행 및 배포 환경에 종속된다는 문제가 있었다. 반면 Object Storage는 이미지와 같은 파일 데이터를 서버 및 DB와 독립적으로 저장할 수 있고, 저장된 파일을 URL을 통해 조회할 수 있다.

따라서 이미지 Binary는 Supabase Storage에 저장하고, DB에는 이미지와 관련된 파일 식별자와 URL만 저장하도록 구성했다.

What I Learned

  1. 웹 요청이 백엔드와 네트워크를 거쳐 실제 하드웨어 동작으로 이어지는 전체 흐름

  2. 기술 선택은 가능/불가능이 아니라 요구사항과 trade-off의 문제라는 것