프로젝트 상태: ✅ 운영 중 (라즈베리파이 CAN 통신 실시간 작동)
최종 수정: 2026년 4월 6일
라즈베리파이에서 CAN 통신으로 수신한 차량 데이터를 Node.js 서버를 통해 React.js 프론트엔드에 실시간 WebSocket 통신으로 표시하는 풀 스택 시스템입니다.
기술 스택
- 백엔드: Node.js + Express + Socket.IO
- 프론트엔드: React 18 + Vite
- 실시간 통신: WebSocket (Socket.IO)
- 스타일링: CSS3 + React
- 테이팅: Jest, React Testing Library
- 데이터 소스: 라즈베리파이 CAN 통신 (프로토타입 단계)
| 기능 | 설명 | 상태 |
|---|---|---|
| 속도 계기판 | 실시간 속도 표시 (0-120 km/h) | ✅ 완료 |
| 주행 상태 | IDLE / DRIVING / STOPPED 상태 표시 | ✅ 완료 |
| 조향각 디스플레이 | 실시간 조향각 (-90°~+90°) | ✅ 완료 |
| 이전값/현재값 추적 | 변화량 계산 및 이벤트 판단 | ✅ 완료 |
| 문 잠금 상태 | 현재 문 잠금/해제 상태 | ✅ 완료 |
| 심각도 | 동작 | 상태 |
|---|---|---|
| FATAL | 빨강 배경 펄싱 비상 메시지 | ✅ 완료 |
| CRITICAL | 주황 배경 위험 메시지 | ✅ 완료 |
| WARNING | 노랑 배경 경고 메시지 | ✅ 완료 |
| NORMAL | 초록 배경 정상 메시지 | ✅ 완료 |
| 이벤트 | 감지 조건 | 상태 |
|---|---|---|
| 출발 시 조향 정렬 | 속도 0→증가 + 조향각 미정렬 (±5°) | ✅ 완료 |
| 무응답 경고 | 10초 이상 데이터 미수신 | ✅ 완료 |
| 위험 상황 발생 | 조향각 임계값 초과 | ✅ 완료 |
| 기능 | 설명 | 상태 |
|---|---|---|
| 원형 속도 계기판 | 채워지는 애니메이션 효과 | ✅ 완료 |
| 실시간 토스트 알림 | react-toastify 기반 | ✅ 완료 |
| 토스트 중복 방지 | 3초 debounce 적용 | ✅ 완료 |
| 반응형 대시보드 | 모든 기기에 대응 | ✅ 완료 |
| 이벤트 로그 | 발생한 모든 이벤트 기록 | ✅ 완료 |
- Node.js 14+ 이상
- npm 또는 yarn
- 최신 웹 브라우저
한 번에 모두 시작 (Windows/Mac/Linux)
# Windows
./start-all.bat
# Mac/Linux
./start-all.sh또는 각각 실행
# 1. 백엔드 설치 및 실행
cd backend
npm install
npm run dev
# 서버 실행: http://192.168.137.158:3002
# 2. 새 터미널에서 프론트엔드 설치 및 실행
cd frontend
npm install
npm run dev
# 프론트엔드 접속: http://192.168.137.158:5173라즈베리파이 네트워크에서
http://192.168.137.158:5173
라즈베리파이 직접 연결 모니터에서
http://localhost:5173
frontend/.env.local 파일이 자동 생성되며, 다음 설정으로 백엔드 URL 고정:
VITE_BACKEND_URL=http://192.168.137.158:3002
✅ 현재: 실제 CAN 통신 활성화 (USE_REAL_CAN=true)
- CAN0 인터페이스에서 데이터 수신 중
can_receiver.py로 데이터 파싱- 실시간 WebSocket 전송 중
project1/
├── backend/
│ ├── server.js # 메인 서버 파일 (Socket.IO)
│ ├── constants.js # 상수 정의
│ ├── can_receiver.py # Python CAN 수신기 (향후)
│ ├── package.json
│ ├── package-lock.json
│ ├── README.md
│ │
│ ├── src/
│ │ ├── handlers/
│ │ │ └── socketHandlers.js # WebSocket 이벤트 핸들러
│ │ ├── routes/
│ │ │ └── api.js # REST API 엔드포인트
│ │ ├── services/
│ │ │ ├── vehicleStateService.js # 차량 상태 관리
│ │ │ ├── emergencyService.js # 비상상황 관리
│ │ │ ├── safetyService.js # 안전 상태 판단
│ │ │ ├── canDataService.js # CAN 데이터 처리
│ │ │ └── eventService.js # 이벤트 처리
│ │ └── utils/
│ │ ├── eventTimers.js # 타이머 관리
│ │ └── testDataSimulator.js # 테스트 데이터 생성
│ │
│ └── __tests__/
│ └── vehicleState.test.js # 단위 테스트
│
├── frontend/
│ ├── src/
│ │ ├── App.jsx # 메인 앱 컴포넌트
│ │ ├── App.css
│ │ ├── main.jsx # React 진입점
│ │ │
│ │ ├── components/
│ │ │ ├── common/
│ │ │ │ ├── Header.jsx # 헤더
│ │ │ │ ├── Header.css
│ │ │ │ ├── AlertPanel.jsx # 안전 상태 패널
│ │ │ │ └── AlertPanel.css
│ │ │ ├── features/
│ │ │ │ ├── Dashboard.jsx # 메인 대시보드
│ │ │ │ ├── Dashboard.css
│ │ │ │ ├── SpeedGauge.jsx # 속도 계기판
│ │ │ │ ├── SpeedGauge.css
│ │ │ │ ├── SteeringDisplay.jsx # 조향각 표시
│ │ │ │ ├── SteeringDisplay.css
│ │ │ │ ├── DoorLock.jsx # 문 잠금 상태
│ │ │ │ ├── DoorLock.css
│ │ │ │ ├── EmergencyPanel.jsx # 비상상황 관제
│ │ │ │ ├── EmergencyPanel.css
│ │ │ │ ├── EventLog.jsx # 이벤트 로그
│ │ │ │ ├── EventLog.css
│ │ │ │ ├── UserInfoModal.jsx # 사용자 정보 모달
│ │ │ │ └── UserInfoModal.css
│ │ │ ├── CanTestControl.jsx # 테스트 컨트롤 패널
│ │ │ └── CanTestControl.css
│ │ │
│ │ ├── contexts/
│ │ │ ├── VehicleStateContext.jsx # 차량 상태 전역 상태
│ │ │ └── UserContext.jsx # 사용자 정보 전역 상태
│ │ │
│ │ ├── hooks/
│ │ │ ├── useVehicleState.js # WebSocket 수신 & 상태관리
│ │ │ └── useStartupEventDetection.js # 출발 이벤트 감지
│ │ │
│ │ ├── services/
│ │ │ ├── socketService.js # Socket.IO 클라이언트
│ │ │ └── vehicleApi.js # REST API 호출
│ │ │
│ │ ├── utils/
│ │ │ ├── constants.js # 프론트엔드 상수
│ │ │ └── toastDeduplication.js # 토스트 중복 방지
│ │ │
│ │ └── __tests__/
│ │ ├── components.test.js
│ │ ├── useVehicleState.test.js
│ │ └── useStartupEventDetection.test.js
│ │
│ ├── index.html # HTML 진입점
│ ├── vite.config.js
│ ├── package.json
│ ├── package-lock.json
│ └── README.md
│
├── start-all.sh # Linux/Mac 한 번에 시작
├── start-all.bat # Windows 한 번에 시작
├── IMPLEMENTATION_GUIDE.md # 구현 가이드
├── RASPBERRY_PI_SETUP.md # 라즈베리파이 설정 가이드
└── README.md # 이 파일
vehicle-state - 차량 상태 업데이트 (100ms 주기)
{
speed: 45, // km/h (0-120)
steeringAngle: 15, // degree (-90~+90)
doorLocked: true,
safetyStatus: "NORMAL", // NORMAL | WARNING | CRITICAL | FATAL
dataVersion: 12345,
timestamp: 1680336000000
}emergency-alert - 새로운 비상상황 발생
{
id: "alert-001",
severity: "CRITICAL", // INFO | WARNING | CRITICAL | FATAL
message: "충돌 감지됨",
timestamp: 1680336000000
}emergency-resolved - 비상상황 해제
{
alertId: "alert-001",
timestamp: 1680336000000
}update-vehicle-data - 차량 데이터 업데이트
{
speed: 45,
steeringAngle: 15,
doorLocked: true
}add-emergency - 비상상황 추가 (테스트용)
{
severity: "CRITICAL",
message: "테스트 충돌"
}resolve-emergency - 특정 비상상황 해제
{
alertId: "alert-001";
}clear-all-emergencies - 모든 비상상황 해제
{
}프론트엔드의 "테스트 컨트롤" 패널에서 다음을 테스트할 수 있습니다:
| 버튼 | 동작 | 심각도 |
|---|---|---|
| 🚨 충돌 알림 | 충돌 이벤트 시뮬레이션 | CRITICAL |
| 과열 이벤트 시뮬레이션 | WARNING | |
| ℹ️ 타이어 압력 | 타이어 압력 이벤트 시뮬레이션 | INFO |
| ✕ 모두 해제 | 모든 비상상황 제거 | - |
백엔드 테스트
cd backend
npm test프론트엔드 테스트
cd frontend
npm test| 메서드 | 경로 | 설명 |
|---|---|---|
GET |
/api/vehicle-state |
현재 차량 상태 조회 |
GET |
/api/emergencies |
현재 비상상황 목록 조회 |
POST |
/api/emergencies |
비상상황 구독 |
import socketio
import python_can
import json
# Socket.IO 클라이언트 생성
sio = socketio.Client()
# CAN 버스 초기화
bus = python_can.interface.Bus(channel='can0', bustype='socketcan')
@sio.event
def connect():
print("서버에 연결되었습니다")
def read_can_data():
"""CAN 버스에서 데이터 읽기"""
while True:
msg = bus.recv(timeout=1)
if msg:
# CAN 데이터 파싱
data = parse_can_message(msg)
# 서버로 전송
sio.emit('update-vehicle-data', data)
def parse_can_message(msg):
"""CAN 메시지 파싱"""
return {
'speed': msg.data[0],
'steeringAngle': msg.data[1],
'doorLocked': bool(msg.data[2])
}
if __name__ == "__main__":
sio.connect('http://localhost:3001')
read_can_data()자세한 설정 방법은 RASPBERRY_PI_SETUP.md를 참고하세요.
라즈베리파이 (또는 테스트 시뮬레이터)
↓
CAN 버스
↓
Node.js 서버
├→ 데이터 검증/변환
├→ 안전 상태 판단
├→ 이벤트 생성
└→ WebSocket 브로드캐스트
↓
React 프론트엔드
├→ 실시간 차량 상태 표시
├→ 알림 표시
└→ 이벤트 로그 업데이트
PORT=3001
CLIENT_URL=http://localhost:5173
NODE_ENV=development- ✅ WebSocket 주기적 업데이트: 100ms 주기 (200ms 수신 debounce)
- ✅ 토스트 중복 방지: 동일 메시지 3초 내 표시 제한
- ✅ 이벤트 우선순위: FATAL > CRITICAL > WARNING > NORMAL
- ✅ 상태 버전 추적: 오래된 데이터 무시
- ✅ 타이머 효율화: 이벤트별 개별 타이머 관리
# Windows
netstat -ano | findstr :3001
taskkill /PID <PID> /F
# Mac/Linux
lsof -i :3001
kill -9 <PID>- 방화벽 확인
- 백엔드 서버가 정상 실행 중인지 확인
- 브라우저 개발자 도구의 Network 탭 확인
- 백엔드 로그 확인:
npm run dev출력 - 프론트엔드 콘솔 오류 확인
- Socket.IO 연결 상태 확인
- 구현 가이드 - 상세 구현 사항
- 라즈베리파이 설정 - CAN 통신 설정
- 백엔드 README - 서버 문서
- 프론트엔드 README - UI 문서
MIT import time from can import Bus
sio = socketio.Client()
@sio.event def connect(): print('Connected to server')
bus = Bus(interface='socketcan', channel='can0', bitrate=500000)
def send_vehicle_data(): while True: for msg in bus: # CAN 메시지 파싱 vehicle_data = { 'speed': msg.data[0], 'doorLocked': msg.data[1] == 1, 'emergencies': [] } sio.emit('update-vehicle-data', vehicle_data)
## 📝 환경 변수
### 백엔드 (.env)
PORT=3001 CLIENT_URL=http://localhost:5173
### 프론트엔드 (.env.local)
VITE_SERVER_URL=http://localhost:3001
## 🎨 UI/UX 특징
- 다크 테마 (시각적 편의성)
- 사이버펑크 스타일 인터페이스
- 심각도별 색상 코딩
- 🔴 Critical (위험)
- 🟠 Warning (경고)
- 🔵 Info (정보)
- 애니메이션 효과 (실시간 상태 피드백)
- 반응형 레이아웃
## 📚 참고 자료
- [Socket.IO 문서](https://socket.io/docs/)
- [React.js 공식 문서](https://react.dev/)
- [Express.js 가이드](https://expressjs.com/)
- [Vite 문서](https://vitejs.dev/)
## 📄 라이선스
MIT License