스마트 용기를 통한 식품 신선도 관리 시스템
- 👪 기관: 삼성 청년 SW 아카데미
- 📆 기간: 2024.04.08 ~ 2024.05.20
소비자들이 식품의 신선도를 정확히 알고, 보다 효율적으로 음식 관리를 할 수 있게 함으로써 음식물 쓰레기를 줄여 경제적 이득 및 환경 보호에 기여합니다.
No | 내용 |
---|---|
1 | 💡 프로젝트 개요 |
2 | 🥨 주요 기능 |
3 | 🍗 기대 효과 |
4 | 🔍 기술 스택 |
5 | 💾 DataBase |
6 | 📂 시스템 아키텍처 |
7 | 📱 기술 소개 |
8 | 👪 팀 소개 |
9 | 🗂️ Directory 구조 |
유통 과정에서의 불법 행위나 소비자의 잘못된 보관 방법 등으로 인해 식품의 신선도가 유통기한과 다를 수 있습니다. 이러한 문제를 해결하고자, 저희는 스마트 용기를 통해 식품의 실시간 데이터를 모니터링하여 소비자에게 정확한 신선도 정보를 제공하고자 합니다.
- STT를 통한 음식 등록: 사용자는 냉장고에 부착된 마이크를 통해 음식 이름을 말해서 등록할 수 있습니다.
- OCR 기능: 포장지에 인쇄된 유통 기한을 인식하여 자동으로 등록합니다.
- 일반적인 유통기한 제공: 등록된 음식에 대한 일반적인 유통기한 정보를 제공합니다.
- 실시간 데이터 모니터링: 냉장고 내의 스마트 용기에 부착된 센서를 통해 식품의 기체 배출량, 온도 등을 수집 및 분석합니다.
- 통계 분석을 통한 식품 신선도 측정: R 통계 분석 기법을 활용하여 검출된 기체의 농도 데이터를 기반으로 신선도를 분류합니다.
- 식품 신선도 및 부패 감지 알림: 특정 기체(암모니아 등)의 농도를 분석하여 신선도 저하 및 부패 시작을 감지하고 소비자에게 알림을 전송하고, 정보를 제공합니다.
- 음식 상태 모니터링 서비스: 소비자가 자신의 음식 상태를 모니터링 할 수 있게 하며, 신선도, 유통 기한 등의 정보를 제공합니다.
[ 시연용 결과물 ]
[ 어플 메인 화면 ]
[ 어플 용기 정보 ]
[ 어플 알림 화면 ]
- 음식의 신선도 확인 간소화: 스마트폰이나 냉장고에 부착된 태블릿을 통해 간편하게 신선도 확인이 가능합니다.
- 경제적 이득과 환경 보호: 식품의 정확한 신선도 파악으로 불필요한 음식물 쓰레기를 줄이고, 결과적으로 경제적 이득 및 환경 보호에 기여합니다.
- 사용자 경험 향상: 식품의 신선도 관리를 자동화함으로써 사용자의 편의성과 만족도를 크게 향상시킵니다.
FrontEnd
BackEnd
IOT
Data
Infra
Collaboration Tool
[oracle DB]
[influx DB]
[ IOT - Asyncio / Multiprocessiong ]
냉장고 모듈에서 처리하는 프로세스가 동기적으로 진행된다면 대기시간이 길어지기 때문에 Asyncio, Multiprocessing을 사용합니다.
- Asyncio: IO 블로킹시 다른 CPU 작업 수행, IO 작업 이점
- Multiprocessiong: 별도의 프로세스를 사용하여 멀티 코어의 경우 CPU 작업 이점
[ IOT - Bluetooth Job 스케쥴링 ]
하나의 냉장고가 여러 개의 스마트 용기가 순차적으로 블루투스 통신을 해야하기 때문에 Bluetooth Job 스케쥴링을 적용했습니다.
- 10대 이상으로 증가할 경우 자동으로 로직 변경
=> 최악의 경우에도 n분 주기로 용기당 60초 배정
[ Kafka ]
- 냉장고에서 등록할 음식 목록을 Publish 하면, 구독 중인 Spring 서버가 메시지를 받아 음식을 등록합니다.
- 냉장고에서 센서 데이터를 Publish하면, 구독 중인 파이썬 서버가 수신하여 신선도를 계산합니다.
-
RestAPI: 트래픽 증가시 타임아웃 및 메시지 유실 가능성이 존재
-
Kafka: 수신자가 본인의 처리 속도에 맞게 메시지를 수신 가능
[ Elastic Search ]
STT로 음식을 등록하는 과정에서, 음식 이름의 오차를 줄이고 음식에 대한 유통기한을 반환하기 위해 Elastic Search 검색 엔진을 구현하였습니다.
[ Spring - Virtual Thread ]
스프링 서버에서 병렬 처리를 위해 음식 등록 과정을 비동기처리로 구현하였습니다. 또한, 성능 향상을 위해 생성 비용이 작고, 논블로킹 방식인 Virtual Thread를 적용했습니다.
-
1000개의 요청에 대한 응답시간 및 처리량
- 플랫폼 스레드 처리량: 10.9/sec
- 가상 스레드 처리량: 20.2/sec
[ Spring Batch ]
유통기한 및 센서 데이터를 기반으로 상태를 주기적으로 업데이트 하고 알림을 전송하기 위해 Spring Batch를 도입하였습니다.
- 데이터 처리 안전성 보장
- 데이터 일관성 유지 가능
- 모니터링 및 관리 가능
[ Python - 신선도 예측 ]
용기의 센서를 통해 온도, 습도, 가스 센서 데이터를 수집합니다. 가스 센서를 통해 훈련시킨 Random Forest Model로 pH 센서를 예측하여 신선도를 분류합니다.
-
pH가 5.9이하는 신선, 5.9 ~ 6.2는 주의, 6.2 이상은 부패로 분류합니다.
-
Random Forest Model
센서로 얻은 데이터를 통해 PH 값을 예측합니다.
- train을 위한 데이터 수집
- 선정 이유
- NH3, 가스 등 다양한 변수들 간의 복잡한 상호작용을 고려할 수 있습니다.
- 시간에 따른 데이터 변화를 처리할 수 있습니다.
- 변수 중요도를 계산할 수 있습니다.
=> Accuracy가 약 92%로, 높은 정확도를 보입니다.
[PWA + FCM]
음식의 신선도 상태, 등록 확인, 연결의 끊김 등의 알림을 사용자에게 전달하기 위해 PWA와 FCM을 도입하였습니다.
-
PWA: Progressive Web Application의 약자로, 웹의 장점과 앱의 장점을 모두 가짐. 대표적인 기능으로는 설치를 통해 앱처럼 사용 가능, 푸시 알림, 오프라인에서의 동작 등이 있습니다.
-
FCM: 파이어베이스 기반 웹 푸시 서비스로, 서버에서 클라이언트 앱으로 메세지를 전달하는 기능을 제공합니다.
-
PWA를 통해 백그라운드 환경에서도 사용자들에게 푸시 알림을 전송하고, 모바일의 카메라를 활용할 수 있습니다.
-
메시지를 Redux에 저장하여 새로고침 없이 알림을 확인할 수 있습니다.
[ Atomic Design, Styled Components ]
코드 재사용성과 유지보수성을 높이기 위해 Atomic Design과 Styled Components를 도입하였습니다.
- 각 컴포넌트에서 쉽게 CSS 확인이 가능합니다.
- CSS의 오염을 최소화합니다.
정승환 (팀장) |
하동준 | 남수진 | 김예지 | 정유경 | 김동현 |
이름 | 역할 | 개발 내용 | 어려웠던 점과 배운 점 |
---|---|---|---|
정승환 | BackEnd | ||
남수진 | BackEnd | ||
정유경 | BackEnd + Infra | ||
하동준 | BackEnd + IOT | ||
김예지 | FrontEnd + IOT | ||
김동현 | FrontEnd |
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