/BeFresh

냉장고 식자재 신선도 관리 플랫폼 (자율 프로젝트 우수상🏆)

Primary LanguageJavaScript

🥦 Be Fresh : 냉장고의 음식 단속

스마트 용기를 통한 식품 신선도 관리 시스템

  • 👪 기관: 삼성 청년 SW 아카데미
  • 📆 기간: 2024.04.08 ~ 2024.05.20

💡 목표

소비자들이 식품의 신선도를 정확히 알고, 보다 효율적으로 음식 관리를 할 수 있게 함으로써 음식물 쓰레기를 줄여 경제적 이득 및 환경 보호에 기여합니다.

목차

No 내용
1 💡 프로젝트 개요
2 🥨 주요 기능
3 🍗 기대 효과
4 🔍 기술 스택
5 💾 DataBase
6 📂 시스템 아키텍처
7 📱 기술 소개
8 👪 팀 소개
9 🗂️ Directory 구조

💡 프로젝트 개요

유통 과정에서의 불법 행위나 소비자의 잘못된 보관 방법 등으로 인해 식품의 신선도가 유통기한과 다를 수 있습니다. 이러한 문제를 해결하고자, 저희는 스마트 용기를 통해 식품의 실시간 데이터를 모니터링하여 소비자에게 정확한 신선도 정보를 제공하고자 합니다.

🥨 주요 기능

  • STT를 통한 음식 등록: 사용자는 냉장고에 부착된 마이크를 통해 음식 이름을 말해서 등록할 수 있습니다.
  • OCR 기능: 포장지에 인쇄된 유통 기한을 인식하여 자동으로 등록합니다.
  • 일반적인 유통기한 제공: 등록된 음식에 대한 일반적인 유통기한 정보를 제공합니다.
  • 실시간 데이터 모니터링: 냉장고 내의 스마트 용기에 부착된 센서를 통해 식품의 기체 배출량, 온도 등을 수집 및 분석합니다.
  • 통계 분석을 통한 식품 신선도 측정: R 통계 분석 기법을 활용하여 검출된 기체의 농도 데이터를 기반으로 신선도를 분류합니다.
  • 식품 신선도 및 부패 감지 알림: 특정 기체(암모니아 등)의 농도를 분석하여 신선도 저하 및 부패 시작을 감지하고 소비자에게 알림을 전송하고, 정보를 제공합니다.
  • 음식 상태 모니터링 서비스: 소비자가 자신의 음식 상태를 모니터링 할 수 있게 하며, 신선도, 유통 기한 등의 정보를 제공합니다.

[ 시연용 결과물 ]

메인화면

[ 어플 메인 화면 ]

메인화면

[ 어플 용기 정보 ]

메인화면

[ 어플 알림 화면 ]

메인화면

🍗 기대 효과

  • 음식의 신선도 확인 간소화: 스마트폰이나 냉장고에 부착된 태블릿을 통해 간편하게 신선도 확인이 가능합니다.
  • 경제적 이득과 환경 보호: 식품의 정확한 신선도 파악으로 불필요한 음식물 쓰레기를 줄이고, 결과적으로 경제적 이득 및 환경 보호에 기여합니다.
  • 사용자 경험 향상: 식품의 신선도 관리를 자동화함으로써 사용자의 편의성과 만족도를 크게 향상시킵니다.

🔍 기술 스택

FrontEnd

React Redux JavaScript TypeScript Vite MUI PWA

BackEnd

Spring SpringSecurity JWT ElasticSearch Apache Kafka Oracle InfluxDB

IOT

Raspberry Arduino OpenCV Bluetooth

Data

python scikit_learn numpy

Infra

AWS AWS-EC2 Jenkins Docker Nginx Sonarqube

Collaboration Tool

GitLab Git Jira Notion Postman

💾 DataBase

[oracle DB]

erd

[influx DB]

influxDB

📂 시스템 아키텍처

architecture

📱 기술 소개

[ IOT - Asyncio / Multiprocessiong ]

냉장고 모듈에서 처리하는 프로세스가 동기적으로 진행된다면 대기시간이 길어지기 때문에 Asyncio, Multiprocessing을 사용합니다.

iot

  • Asyncio: IO 블로킹시 다른 CPU 작업 수행, IO 작업 이점
  • Multiprocessiong: 별도의 프로세스를 사용하여 멀티 코어의 경우 CPU 작업 이점

[ IOT - Bluetooth Job 스케쥴링 ]

하나의 냉장고가 여러 개의 스마트 용기가 순차적으로 블루투스 통신을 해야하기 때문에 Bluetooth Job 스케쥴링을 적용했습니다.

iot

  • 10대 이상으로 증가할 경우 자동으로 로직 변경
    => 최악의 경우에도 n분 주기로 용기당 60초 배정

[ Kafka ]

  1. 냉장고에서 등록할 음식 목록을 Publish 하면, 구독 중인 Spring 서버가 메시지를 받아 음식을 등록합니다.
  2. 냉장고에서 센서 데이터를 Publish하면, 구독 중인 파이썬 서버가 수신하여 신선도를 계산합니다.
  • RestAPI: 트래픽 증가시 타임아웃 및 메시지 유실 가능성이 존재

  • Kafka: 수신자가 본인의 처리 속도에 맞게 메시지를 수신 가능

[ Elastic Search ]

STT로 음식을 등록하는 과정에서, 음식 이름의 오차를 줄이고 음식에 대한 유통기한을 반환하기 위해 Elastic Search 검색 엔진을 구현하였습니다.

elastic

[ Spring - Virtual Thread ]

스프링 서버에서 병렬 처리를 위해 음식 등록 과정을 비동기처리로 구현하였습니다. 또한, 성능 향상을 위해 생성 비용이 작고, 논블로킹 방식인 Virtual Thread를 적용했습니다.

  • 1000개의 요청에 대한 응답시간 및 처리량

    • 플랫폼 스레드 처리량: 10.9/sec
    thread1
    • 가상 스레드 처리량: 20.2/sec
    thread2 => 플랫폼 스레드에 비해 더 높은 처리량과 빠른 응답시간

[ Spring Batch ]

유통기한 및 센서 데이터를 기반으로 상태를 주기적으로 업데이트 하고 알림을 전송하기 위해 Spring Batch를 도입하였습니다.

  • 데이터 처리 안전성 보장
  • 데이터 일관성 유지 가능
  • 모니터링 및 관리 가능

[ Python - 신선도 예측 ]

용기의 센서를 통해 온도, 습도, 가스 센서 데이터를 수집합니다. 가스 센서를 통해 훈련시킨 Random Forest Model로 pH 센서를 예측하여 신선도를 분류합니다.

  • pH가 5.9이하는 신선, 5.9 ~ 6.2는 주의, 6.2 이상은 부패로 분류합니다.

  • Random Forest Model

    센서로 얻은 데이터를 통해 PH 값을 예측합니다.

    • train을 위한 데이터 수집
    sensor
    • 선정 이유
      • NH3, 가스 등 다양한 변수들 간의 복잡한 상호작용을 고려할 수 있습니다.
      • 시간에 따른 데이터 변화를 처리할 수 있습니다.
      • 변수 중요도를 계산할 수 있습니다.

    => Accuracy가 약 92%로, 높은 정확도를 보입니다.

[PWA + FCM]

음식의 신선도 상태, 등록 확인, 연결의 끊김 등의 알림을 사용자에게 전달하기 위해 PWAFCM을 도입하였습니다.

pwa

  • PWA: Progressive Web Application의 약자로, 웹의 장점과 앱의 장점을 모두 가짐. 대표적인 기능으로는 설치를 통해 앱처럼 사용 가능, 푸시 알림, 오프라인에서의 동작 등이 있습니다.

  • FCM: 파이어베이스 기반 웹 푸시 서비스로, 서버에서 클라이언트 앱으로 메세지를 전달하는 기능을 제공합니다.

  • PWA를 통해 백그라운드 환경에서도 사용자들에게 푸시 알림을 전송하고, 모바일의 카메라를 활용할 수 있습니다.

  • 메시지를 Redux에 저장하여 새로고침 없이 알림을 확인할 수 있습니다.

[ Atomic Design, Styled Components ]

코드 재사용성과 유지보수성을 높이기 위해 Atomic Design과 Styled Components를 도입하였습니다.

design

  • 각 컴포넌트에서 쉽게 CSS 확인이 가능합니다.
  • CSS의 오염을 최소화합니다.

👪 팀 소개

정승환 하동준 남수진 김예지 정유경 김동현
정승환
(팀장)
하동준 남수진 김예지 정유경 김동현
이름 역할 개발 내용 어려웠던 점과 배운 점
정승환 BackEnd
남수진 BackEnd
정유경 BackEnd + Infra
하동준 BackEnd + IOT
김예지 FrontEnd + IOT
김동현 FrontEnd

🗂️ Directory 구조

be-fresh,
├─ backend,
│  ├─ .gitkeep,
│  └─ befresh,
│     ├─ .gitignore,
│     ├─ Dockerfile,
│     ├─ gradle,
│     │  └─ wrapper,
│     │     └─ gradle-wrapper.properties,
│     ├─ gradlew,
│     ├─ gradlew.bat,
│     ├─ HELP.md,
│     └─ src,
│        ├─ main,
│        │  ├─ java,
│        │  │  └─ com,
│        │  │     └─ a307,
│        │  │        └─ befresh,
│        │  │           ├─ BefreshApplication.java,
│        │  │           ├─ global,
│        │  │           │  ├─ api,
│        │  │           │  │  └─ response,
│        │  │           │  │     ├─ BaseResponse.java,
│        │  │           │  │     └─ ErrorResponse.java,
│        │  │           │  ├─ config,
│        │  │           │  │  ├─ async,
│        │  │           │  │  │  └─ AsyncConfig.java,
│        │  │           │  │  ├─ batch,
│        │  │           │  │  │  ├─ BatchScheduleConfig.java,
│        │  │           │  │  │  ├─ FoodExpireBatchConfig.java,
│        │  │           │  │  │  └─ FoodSensorBatchConfig.java,
│        │  │           │  │  ├─ elastic,
│        │  │           │  │  │  └─ ElasticsearchClientConfig.java,
│        │  │           │  │  ├─ fcm,
│        │  │           │  │  │  └─ FcmInitializer.java,
│        │  │           │  │  ├─ kafka,
│        │  │           │  │  │  └─ KafkaConsumerConfig.java,
│        │  │           │  │  ├─ queryDsl,
│        │  │           │  │  │  └─ QueryDslConfig.java,
│        │  │           │  │  ├─ redis,
│        │  │           │  │  │  └─ RedisConfig.java,
│        │  │           │  │  ├─ s3,
│        │  │           │  │  │  └─ S3Config.java,
│        │  │           │  │  └─ security,
│        │  │           │  │     └─ SecurityConfig.java,
│        │  │           │  ├─ exception,
│        │  │           │  │  ├─ BaseExceptionHandler.java,
│        │  │           │  │  ├─ code,
│        │  │           │  │  │  ├─ ErrorCode.java,
│        │  │           │  │  │  └─ SuccessCode.java,
│        │  │           │  │  └─ GlobalControllerAdvice.java,
│        │  │           │  ├─ security,
│        │  │           │  │  ├─ domain,
│        │  │           │  │  │  ├─ dto,
│        │  │           │  │  │  │  └─ LoginDto.java,
│        │  │           │  │  │  ├─ UserDatailsServiceImpl.java,
│        │  │           │  │  │  └─ UserDetailsImpl.java,
│        │  │           │  │  ├─ filter,
│        │  │           │  │  │  ├─ JsonMemberAuthenticationFilter.java,
│        │  │           │  │  │  └─ JwtAuthenticationProcessingFilter.java,
│        │  │           │  │  ├─ handler,
│        │  │           │  │  │  ├─ LoginFailureHandler.java,
│        │  │           │  │  │  └─ LoginSuccessJWTProvideHandler.java,
│        │  │           │  │  └─ jwt,
│        │  │           │  │     ├─ JwtService.java,
│        │  │           │  │     └─ JWTServiceImpl.java,
│        │  │           │  └─ util,
│        │  │           │     └─ S3Util.java,
│        │  │           ├─ HealthController.java,
│        │  │           └─ module,
│        │  │              └─ domain,
│        │  │                 ├─ BaseEntity.java,
│        │  │                 ├─ container,
│        │  │                 │  ├─ Container.java,
│        │  │                 │  ├─ controller,
│        │  │                 │  │  └─ ContainerController.java,
│        │  │                 │  ├─ dto,
│        │  │                 │  │  └─ request,
│        │  │                 │  │     ├─ ContainerUpdateSensorListReq.java,
│        │  │                 │  │     └─ ContainerUpdateSensorReq.java,
│        │  │                 │  ├─ repository,
│        │  │                 │  │  ├─ ContainerRepository.java,
│        │  │                 │  │  ├─ ContainerRepositoryCustom.java,
│        │  │                 │  │  └─ ContainerRepositoryImpl.java,
│        │  │                 │  └─ service,
│        │  │                 │     ├─ ContainerService.java,
│        │  │                 │     └─ ContainerServiceImpl.java,
│        │  │                 ├─ elastic,
│        │  │                 │  ├─ controller,
│        │  │                 │  │  └─ ElasticController.java,
│        │  │                 │  ├─ ElasticDocument.java,
│        │  │                 │  ├─ repository,
│        │  │                 │  │  ├─ ElasticCustomRepository.java,
│        │  │                 │  │  ├─ ElasticRepository.java,
│        │  │                 │  │  └─ ElasticRepositoryImpl.java,
│        │  │                 │  └─ service,
│        │  │                 │     ├─ ElasticService.java,
│        │  │                 │     └─ ElasticServiceImpl.java,
│        │  │                 ├─ food,
│        │  │                 │  ├─ controller,
│        │  │                 │  │  └─ FoodController.java,
│        │  │                 │  ├─ dto,
│        │  │                 │  │  ├─ request,
│        │  │                 │  │  │  ├─ FoodRegisterReq.java,
│        │  │                 │  │  │  ├─ FoodRegisterReqList.java,
│        │  │                 │  │  │  └─ FoodUpdateReq.java,
│        │  │                 │  │  └─ response,
│        │  │                 │  │     ├─ FoodDetailRes.java,
│        │  │                 │  │     ├─ FoodFailRes.java,
│        │  │                 │  │     └─ FoodListDetailRes.java,
│        │  │                 │  ├─ Food.java,
│        │  │                 │  ├─ repository,
│        │  │                 │  │  ├─ FoodRepository.java,
│        │  │                 │  │  ├─ FoodRepositoryCustom.java,
│        │  │                 │  │  └─ FoodRepositoryImpl.java,
│        │  │                 │  └─ service,
│        │  │                 │     ├─ FoodService.java,
│        │  │                 │     └─ FoodServiceImpl.java,
│        │  │                 ├─ Ftype,
│        │  │                 │  ├─ Ftype.java,
│        │  │                 │  └─ repository,
│        │  │                 │     └─ FtypeRepository.java,
│        │  │                 ├─ influxContainer,
│        │  │                 │  ├─ dto,
│        │  │                 │  │  └─ response,
│        │  │                 │  │     ├─ ContainerSensor.java,
│        │  │                 │  │     ├─ SensorData.java,
│        │  │                 │  │     └─ SensorDataList.java,
│        │  │                 │  ├─ InfluxContainer.java,
│        │  │                 │  └─ repository,
│        │  │                 │     ├─ InfluxContainerRepository.java,
│        │  │                 │     └─ InfluxContainerRepositoryImpl.java,
│        │  │                 ├─ member,
│        │  │                 │  ├─ controller,
│        │  │                 │  │  └─ MemberController.java,
│        │  │                 │  ├─ dto,
│        │  │                 │  │  ├─ request,
│        │  │                 │  │  │  ├─ MemberSignupReq.java,
│        │  │                 │  │  │  └─ MemberTokenReq.java,
│        │  │                 │  │  └─ response,
│        │  │                 │  │     └─ MemberDetailRes.java,
│        │  │                 │  ├─ Member.java,
│        │  │                 │  ├─ repository,
│        │  │                 │  │  └─ MemberRepository.java,
│        │  │                 │  └─ service,
│        │  │                 │     ├─ MemberService.java,
│        │  │                 │     └─ MemberServiceImpl.java,
│        │  │                 ├─ memberToken,
│        │  │                 │  ├─ MemberToken.java,
│        │  │                 │  └─ repository,
│        │  │                 │     └─ MemberTokenRepository.java,
│        │  │                 ├─ notification,
│        │  │                 │  ├─ controller,
│        │  │                 │  │  └─ NotificationController.java,
│        │  │                 │  ├─ dto,
│        │  │                 │  │  ├─ request,
│        │  │                 │  │  │  └─ NotificationTmpReq.java,
│        │  │                 │  │  └─ response,
│        │  │                 │  │     ├─ NotificationDetailRes.java,
│        │  │                 │  │     └─ NotificationRegisterRes.java,
│        │  │                 │  ├─ Notification.java,
│        │  │                 │  ├─ repository,
│        │  │                 │  │  ├─ NotificationRepository.java,
│        │  │                 │  │  ├─ NotificationRepositoryCustom.java,
│        │  │                 │  │  └─ NotificationRepositoryImpl.java,
│        │  │                 │  └─ service,
│        │  │                 │     ├─ NotificationService.java,
│        │  │                 │     └─ NotificationServiceImpl.java,
│        │  │                 ├─ refresh,
│        │  │                 │  ├─ Refresh.java,
│        │  │                 │  └─ repository,
│        │  │                 │     └─ RefreshRepository.java,
│        │  │                 └─ refrigerator,
│        │  │                    ├─ Refrigerator.java,
│        │  │                    └─ repository,
│        │  │                       └─ RefrigeratorRepository.java,
│        │  └─ resources,
│        │     └─ application.yml,
│        └─ test,
│           └─ java,
│              └─ com,
│                 └─ a307,
│                    └─ befresh,
│                       └─ BefreshApplicationTests.java,
├─ exec,
│  └─ db.sql,
├─ frontend,
│  ├─ .eslintrc.cjs,
│  ├─ .gitignore,
│  ├─ .vite,
│  │  └─ deps,
│  │     ├─ chunk-UPDK7Z2H.js,
│  │     ├─ chunk-UPDK7Z2H.js.map,
│  │     ├─ chunk-UWZXFKA6.js,
│  │     ├─ chunk-UWZXFKA6.js.map,
│  │     ├─ package.json,
│  │     ├─ react-dom_client.js,
│  │     ├─ react-dom_client.js.map,
│  │     ├─ react-router-dom.js,
│  │     ├─ react-router-dom.js.map,
│  │     ├─ react.js,
│  │     ├─ react.js.map,
│  │     ├─ react_jsx-dev-runtime.js,
│  │     ├─ react_jsx-dev-runtime.js.map,
│  │     ├─ styled-components.js,
│  │     ├─ styled-components.js.map,
│  │     └─ _metadata.json,
│  ├─ dev-dist,
│  │  ├─ registerSW.js,
│  │  ├─ sw.js,
│  │  ├─ workbox-9637eeee.js,
│  │  └─ workbox-b5f7729d.js,
│  ├─ Dockerfile,
│  ├─ index.html,
│  ├─ nginx.conf,
│  ├─ package.json,
│  ├─ public,
│  │  ├─ apple-touch-icon.png,
│  │  ├─ beFresh.png,
│  │  ├─ beFreshicon-192.png,
│  │  ├─ beFreshicon-512.png,
│  │  ├─ favicon-16x16.png,
│  │  ├─ favicon-32x32.png,
│  │  ├─ favicon.ico,
│  │  ├─ favicon.svg,
│  │  ├─ firebase-messaging-sw.js,
│  │  ├─ pwa-192x192.png,
│  │  ├─ pwa-512x512.png,
│  │  ├─ pwa-64x64.png,
│  │  ├─ pwa-maskable-192x192.png,
│  │  ├─ pwa-maskable-512x512.png,
│  │  ├─ sampleimg.png,
│  │  └─ vite.svg,
│  ├─ README.md,
│  ├─ sonar-project.properties,
│  ├─ src,
│  │  ├─ api,
│  │  │  ├─ alarm,
│  │  │  │  └─ alarmApi.ts,
│  │  │  ├─ food,
│  │  │  │  ├─ foodCardApi.ts,
│  │  │  │  └─ foodModalApi.ts,
│  │  │  ├─ info,
│  │  │  │  └─ infoApi.ts,
│  │  │  └─ member,
│  │  │     └─ memberApi.ts,
│  │  ├─ App.tsx,
│  │  ├─ assets,
│  │  │  ├─ empty1.png,
│  │  │  ├─ empty2.png,
│  │  │  ├─ oldsampleimg.png,
│  │  │  ├─ react.svg,
│  │  │  └─ sampleimg.png,
│  │  ├─ components,
│  │  │  ├─ atoms,
│  │  │  │  ├─ Button.tsx,
│  │  │  │  ├─ IdInput.tsx,
│  │  │  │  ├─ LogoComponent.tsx,
│  │  │  │  ├─ PasswordInput.tsx,
│  │  │  │  └─ progerssBar.tsx,
│  │  │  ├─ molecules,
│  │  │  │  ├─ alarmBlock.tsx,
│  │  │  │  ├─ alarmSettingBlock.tsx,
│  │  │  │  ├─ detailChart.tsx,
│  │  │  │  ├─ foodCard.tsx,
│  │  │  │  ├─ loginInputBlock.tsx,
│  │  │  │  ├─ navBlock.tsx,
│  │  │  │  ├─ sensorData.tsx,
│  │  │  │  └─ sighupInputBlock.tsx,
│  │  │  ├─ organisms,
│  │  │  │  ├─ alarmForm.tsx,
│  │  │  │  ├─ alarmModalForm.tsx,
│  │  │  │  ├─ cardForm.tsx,
│  │  │  │  ├─ InfoForm.tsx,
│  │  │  │  ├─ loginForm.tsx,
│  │  │  │  └─ signupForm.tsx,
│  │  │  └─ templates,
│  │  │     ├─ alarmTemp.tsx,
│  │  │     ├─ infoTemp.tsx,
│  │  │     ├─ loginTemp.tsx,
│  │  │     ├─ mainTemp.tsx,
│  │  │     └─ signupTemp.tsx,
│  │  ├─ containers,
│  │  │  └─ SignUpFormContainer.tsx,
│  │  ├─ main.css,
│  │  ├─ main.tsx,
│  │  ├─ pages,
│  │  │  ├─ AlarmPage.tsx,
│  │  │  ├─ InfoPage.tsx,
│  │  │  ├─ LoginPage.tsx,
│  │  │  ├─ MainPage.tsx,
│  │  │  ├─ modal.tsx,
│  │  │  ├─ SignupPage.tsx,
│  │  │  └─ StartPage.tsx,
│  │  ├─ routes,
│  │  │  ├─ AppRoutes.tsx,
│  │  │  ├─ PrivateRoutes.tsx,
│  │  │  └─ PublicRoutes.tsx,
│  │  ├─ store,
│  │  │  ├─ features,
│  │  │  │  ├─ alarmSlice.ts,
│  │  │  │  ├─ InfoSlice.ts,
│  │  │  │  └─ refIdSlice.ts,
│  │  │  └─ store.ts,
│  │  ├─ types,
│  │  │  ├─ alarmTypes.ts,
│  │  │  ├─ foodTypes.ts,
│  │  │  └─ informationTypes.ts,
│  │  ├─ utils,
│  │  │  ├─ axiosConfig.ts,
│  │  │  ├─ buttonUtils.ts,
│  │  │  ├─ dateUtils.ts,
│  │  │  └─ tokenUtils.ts,
│  │  └─ vite-env.d.ts,
│  ├─ tsconfig.json,
│  ├─ tsconfig.node.json,
│  ├─ vite.config.ts,
│  └─ yarn.lock,
├─ iot,
│  ├─ nicla_sense_me,
│  │  ├─ nicla_sense_me.ino,
│  │  └─ nicla에업로드할파일.txt,
│  └─ raspi-release,
│     ├─ script,
│     │  └─ script.sh,
│     └─ src,
│        ├─ correct.wav,
│        ├─ hotword-model.ppn,
│        ├─ main.py,
│        ├─ module,
│        │  ├─ bluetooth_module.py,
│        │  ├─ button_module.py,
│        │  ├─ distance_module.py,
│        │  ├─ mic.py,
│        │  ├─ register_food.py,
│        │  └─ SignalBlock.py,
│        ├─ nohup.out,
│        ├─ off.wav,
│        ├─ on.wav,
│        ├─ porcupine_params_ko.pv,
│        ├─ requirements.txt,
│        ├─ start.wav,
│        └─ wakeword.py,
├─ model,
│  ├─ analyze.py,
│  ├─ predict_ph,
│  └─ script,
│     └─ ec2_main.py,
└─ README.md