이번 프로젝트는 꽤나 재밌었습니다. 도메인도 좋고 IoT 기기와 연동하는 웹사이트는 처음이라 스마트팜과 여러 프로토콜을 열심히 학습하면서 개발했던 기억이나네요. 기능 개발도 좋았지만 시스템을 어떻게 구축할지 고민했던 것이 흥미로워 재밌게 개발했습니다.
당시 어떤 환경을 구축했었는지, 어떤 고민을 했었는지 정리해서 공유해보려고 합니다.
1. 구성 요소
1) IoT 기기 : 식물재배기입니다. 카메라와 환경센서가 부착되어있습니다.
2) FTP 서버: IoT 기기로부터 이미지 파일을 수신하고 저장합니다.
3) Python 스크립트 : 특정 폴더의 변경사항을 감지하고 추론 알고리즘으로 이미지를 분석해 데이터베이스에 저장합니다.
4) Node.js 웹 서버 : 데이터베이스의 변경사항을 감지하고 SSE를 통해 클라이언트에 알림
2. 데이터 흐름
1) IoT 기기가 이미지를 캡처하고 <DeviceId-날짜> 형식의 파일명으로 FTP 서버에 전송합니다.
2) Python 스크립트로 폴더 변경을 감지하여 전송된 이미지를 추론하여 데이터를 추출합니다.
3) 추출된 데이터를 데이터베이스에 저장합니다.
4) 파일명을 바탕으로 파일을 이동시켜줍니다. (폴더명: DeviceId/날짜/example.jpg)
5) 데이터베이스 업데이트 발생 시 웹 서버가 SSE를 통해 클라이언트에 알립니다.
6) 웹 클라이언트가 실시간으로 업데이트된 정보를 표시합니다.
3. 구현사항
1) FTP 서버인 FileZilla 서버 연결 설정
IoT 기기에 설정한 FTP 설정, 호스트 / 사용자명 / 비밀번호 / 포트를 일치시킵니다
- 호스트 : example.com
- 사용자명: user
- 비밀번호: root
- 포트: 21 (기본 FTP 포트)
2) 자동화 설정
FileZilla 인터페이스를 통해 FTP 서버에서 파일을 자동으로 다운로드 합니다.
filezilla -c "open ftp://user:root@example.com; cd /uploads; lcd C:/local/image/folder; get *.jpg; close; exit"
3) Python 스크립트 (파일 감지 및 추론 알고리즘 실행)
Python 스크립트의 추론 알고리즘을 만드는 부분은 외부 기관과 협력했습니다. 식물의 생육 데이터를 구축하고 적절한 모델을 정의하여 학습시키고 현재 프로젝트에 알맞은 모델을 만드는 과정이 흥미로웠습니다.
약간의 파라미터 변화로 결과물이 완전히 달라지는 모습에 뜬금없이 영화 나비효과가 생각나기도 했습니다.
(아직도 어린 에반이 칼 들고 서있는 장면보면 놀랍니다, 개인적으로 감독판 엔딩은 너무하다 생각해요..)
처음엔 Python 스크립트에 따른 결과물을 DB에 별도로 저장하고 웹 서버에서 DB 내용을 한번 가공해서 다른 collection 에 저장하도록 구현했는데 프로젝트가 수정되면서 추론 데이터를 사용하는 일이 없어졌습니다. 파일명을 기준으로 파일을 이동시키는 기능도 웹서버에서 진행하다가 해당 과정은 Python 스크립트에게 맡겨 중간 과정을 하나 생략했습니다.
큰 차이는 아니라고 생각했는데 중간 단계를 하나 줄이니 소요 시간이 3s -> 2.5s로 단축됐습니다.
4) 웹 서버 설정 (Node.js)
웹 서버에서는 다음과 같은 기능을 합니다.
- MongoDB 감지
- MongoDB 업데이트 시 SSE를 통해 클라이언트에 알림
- 저장된 이미지 파일 제공 (아래 예시에선 다루지 않음)
4.1)(최적화 x)
let clients = []; // 연결된 SSE 클라이언트 리스트
// SSE 엔드포인트
app.get('/events', (req, res) => {
// SSE 헤더 설정
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
res.write(`data: ${JSON.stringify({ message: 'Connected to event stream' })}\n\n`);
clients.push(res); // 클라이언트 연결 추가
// 연결 종료 시 clients 리스트에서 제거
req.on('close', () => {
clients = clients.filter((clientRes) => clientRes !== res);
});
});
// MongoDB Change Streams
async function startChangeStream() {
const changeStream = collection.watch();
changeStream.on('change', (change) => {
if (change.operationType === 'update') {
// 연결된 client 모두에게 이벤트 전송
clients.forEach((res) => res.write(`data: rendering\n\n`));
}
});
}
시연을 목표로 프로젝트를 진행하니 유저가 많은 경우를 생각하지 못했습니다.
DB가 업데이트되면 모든 클라이언트에게 알림을 준다는 걸 나중에야 알게되었습니다🫢
4.2) (최적화 o)
클라이언트를 Id 별로 접속하고 서버는 해당 userId 에 대한 데이터만 전송하게 변경했습니다.
// 유저별 연결 저장
const clients = new Map();
// SSE 연결 엔드포인트
app.get('/events/:userId', (req, res) => {
const userId = req.params.userId;
// 연결 저장
clients.set(userId, res);
// 연결 종료 처리
req.on('close', () => {
clients.delete(userId);
});
});
// MongoDB Change Streams 설정
async function watchMongoChanges() {
const changeStream = collection.watch();
changeStream.on('change', (change) => {
const userId = change.fullDocument?.userId; // event에 해당하는 user 추출
if (!userId) return;
const userClient = clients.get(userId);
if (userClient) {
userClient.write('data: rendering\n\n');
}
});
}
5) 클라이언트에서 SSE 설정 (Next.js)
// 클라이언트 측 SSE 연결 설정
const eventSource = new EventSource('/events');
// 이벤트 수신 시 화면 업데이트
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data === 'rendering') {
// UI 업데이트 로직
updateUI(data);
}
};
eventSource.onerror = (error) => {
console.error('SSE connection error:', error);
eventSource.close();
};
4. FTP, MQTT, SSE 프로토콜 선택 이유
FTP: 이미지 파일을 전송할 때 먼저 HTTP를 생각했습니다. 그런데 사용하기로 했던 IoT 기기가 비용 이슈로 다운그레이드 되면서 성능 이슈가 있었고 HTTP는 기본적으로 Header에 데이터가 많이 들어가기 때문에 보다 오버헤드가 적고 안정적인 FTP를 사용했습니다. MQTT는 경량화된 메시징 전송에 특화된 프로토콜로 대용량 파일 전송에는 적합하지 않았습니다.
MQTT: FTP와 동일한 이슈로 HTTP는 사용하지 않았습니다.
SSE: WebSocket을 사용하려고 했는데 서버 pc가 이미 하는 일이 많다고 생각했고 특히, 양방향이 아닌 단방향 통신이 필요해 서버 리소스도 덜 사용하는 SSE를 사용했습니다.
5. 후기
위 과정을 통해 IoT 에서 이미지를 취득 -> 추출한 데이터를 클라이언트에게 실시간으로 전달하는 환경을 구축할 수 있었습니다.
환경데이터를 주고받은 내용은 MQTT 프로토콜을 이용했는데 내용을 추가하고 싶었지만 본문 구성이 많이 달라져서 제외했습니다
(이럴때마다 글 잘쓰는 분들이 부럽네요..)
FTP, MQTT, SSE 프로토콜을 다뤄보면서 네트워크 통신의 전반적인 흐름을 이해할 수 있었습니다. 그리고 생각보다 재밌어서 TCP/IP 랑 기본적인 네트워크도 같이 스터디했습니다. 아주 좋은 현상이네요
AI가 점점 세상을 가속화하고 있는걸 체감하는 요즈음입니다. 그런데 AI 도구를 열심히 이용하고 있는것과 별개로 저는 책에 손이 가네요. 인스타 릴스, 유튜브 쇼츠 등 대기업에서 점점 짧고 강력한 유혹으로 시선을 뺐어가는데, 제 나름대로 도파민을 컨트롤할 방법을 찾다 보니 책 만한게 없더라구요.
김영하 작가님을 아시나요? 저는 검은꽃을 시작으로 작가님 책을 찾아보기 시작했는데 최근 읽은 빛의 제국은 나름 생각할 화두를 던져줬습니다. 주인공은 북에서 온 간첩이지만 인생의 절반을 평범한 한국인인척 연기합니다.
그 모습을 보며 나도 누군가에게 연기를 하며 지내고 있구나 싶었어요. 적당한 회사 동료들에게는 사무적인 모습으로, 교회 아이들에게는 상냥한 선생님의 모습으로, 부모님에게는 그래도 노력하는 아들로.
다들 저마다의 방법으로 적당히 연기를 하며 지내고 계시나요? 저는 뭐가됐든 제 모습이 다른 사람에게 긍정적인 영향을 줄수 있으면 좋겠네요.
'On-Premise' 카테고리의 다른 글
On-Premise 환경에 React, Express 프로젝트 배포하기 (0) | 2025.04.30 |
---|