메뉴 전체보기

회원메뉴

더트 너드: 식물을 기르기 전에 토양이 필요로 하는 것을 알아두세요. > 게시판

본문 바로가기

쇼핑몰 검색

NO. 3130
제목 : 더트 너드: 식물을 기르기 전에 토양이 필요로 하는 것을 알아두세요.
2026-08-31 10:31

소개: 더트 너드: 식물을 기르기 전에 토양이 필요로 하는 것을 알아두세요.


소개: 더트 너드: 식물을 기르기 전에 토양이 필요로 하는 것을 알아두세요.


소개: 더트 너드: 식물을 기르기 전에 토양이 필요로 하는 것을 알아두세요.


소개: 더트 너드: 식물을 기르기 전에 토양이 필요로 하는 것을 알아두세요.


안녕하세요! 저는 Ayushmaan입니다. 전자 및 전기 공학을 전공하는 B.Tech 학생으로, 전자기기, 디자인, 그리고 약간의 코드를 결합한 프로젝트를 만드는 것을 좋아합니다.

이번 프로젝트에서는 꽤 간단한 질문을 중심으로 뭔가를 만들고 싶었습니다: 특정 식물을 기르고 싶다면, 내가 가진 토양이 실제로 그 식물에 적합한지 어떻게 알 수 있을까요?

대부분의 토양 모니터링 프로젝트는 수분, 온도 또는 pH와 같은 수치를 제공할 수 있지만, 그 숫자만으로는 식물이 실제로 필요로 하는 것을 알지 못하면 큰 의미가 없습니다. 그래서 저는 한 걸음 더 나아가서 토양을 측정할 뿐만 아니라 그 수치를 이해하는 데 도움을 주는 휴대용 토양 분석기를 만들기로 결정했습니다.

이 장치는 다양한 토양 매개변수를 측정하고 이를 웹 대시보드로 전송합니다. 거기에서 기르고 싶은 작물이나 식물을 선택할 수 있으며, 대시보드는 현재 토양 조건을 해당 식물이 선호하는 것과 비교합니다. 그런 다음 무엇이 좋은지, 무엇이 부족할 수 있는지, 그리고 토양을 개선하기 위해 무엇을 할 수 있는지 알려줍니다.

저는 이 모든 것이 너무 간단해서, 토양 화학을 이해하지 않고도 뒷마당이 토마토를 기르기에 준비되었는지 알아낼 수 있기를 원했습니다.

그래서 이번 Instructable에서는 제가 전자 기기를 어떻게 만들었는지, 인클로저를 어떻게 디자인했는지, 펌웨어와 대시보드를 어떻게 조합했는지, 그리고 실제 토양으로 전체 시스템을 어떻게 테스트했는지를 안내해 드리겠습니다.

시작해 볼까요!




STEP 1. 준비물


준비물


준비물


전자기기

  1. Seeed Studio XIAO ESP32-C6
  2. 7-in-1 RS485 NPK 토양 센서
  3. MAX485 RS485-에서-UART 모듈
  4. 하나의 WS2812 주소 지정 가능한 RGB LED
  5. 토양 센서에 적합한 외부 전원 공급 장치
  6. USB-C 데이터 케이블
  7. 점퍼 와이어
  8. 인클로저 결합을 위한 작은 나사
  9. 선택적 120옴 RS485 종단 저항

제작

  1. Anycubic Kobra S1 3D 프린터
  2. 삼색 PLA 필라멘트
  3. 여러 그릿의 사포
  4. 녹색 스프레이 페인트
  5. 기본 수공구

소프트웨어

  1. Fusion 360
  2. Arduino IDE 2.x
  3. Next.js 및 React
  4. Node.js




STEP 2. 프로젝트 작동 방식


Dirt Nerd는 네 가지 주요 부분으로 구성됩니다: 토양 센서, 통신 전자기기, ESP32 펌웨어 및 웹 대시보드.

7-in-1 프로브는 토양에 삽입되어 수분, 온도, pH, EC 및 NPK 값을 측정합니다.

측정값은 RS485 Modbus 통신을 사용하여 전송됩니다.

MAX485는 그 차동 RS485 신호를 XIAO ESP32-C6가 이해할 수 있는 UART 신호로 변환합니다.

ESP32는 일곱 개의 Modbus 레지스터를 읽고, 원시 숫자를 유용한 단위로 변환하며, 가장 최근의 유효한 판독값을 저장합니다.

그 후, 로컬 Wi-Fi 네트워크에서 작은 HTTP 서버를 생성합니다.

웹사이트는 /data 엔드포인트를 통해 ESP32에서 판독값을 요청합니다.

전체 데이터 경로는 다음과 같습니다:

토양
7-in-1 RS485 센서
MAX485 모듈
XIAO ESP32-C6
↓ Wi-Fi 및 HTTP JSON
Dirt Nerd 웹사이트
작물 호환성 및 추천

주소 지정 가능한 LED는 간단한 연결 표시기를 제공합니다.

  1. 빨간색은 ESP32가 Wi-Fi에 연결되지 않았음을 의미합니다.
  2. 초록색은 연결되었고 로컬 웹 서버가 시작되었음을 의미합니다.

대시보드는 데모 모드 또는 라이브 모드로 작동할 수 있습니다.

데모 모드는 물리적 센서 없이 인터페이스를 시연하는 데 유용합니다. 판독값은 설정에서 수동으로 변경할 수도 있습니다.

라이브 모드는 ESP32 시리얼 모니터에 표시된 IP 주소에서 최신 값을 요청합니다.

판독값이 나타난 후, 사용자는 작물을 선택합니다. 작물을 변경하면 저장된 이상 범위, 호환성 메시지 및 추천이 변경됩니다.

현재 프로토타입은 작물 프로필을 로컬에 저장합니다. 이는 외부 식물 데이터베이스에 의존하지 않고도 인터페이스를 사용할 수 있게 합니다.




STEP 3. 인클로저 설계하기


인클로저 설계하기


인클로저 설계하기


인클로저 설계하기


저는 Fusion 360에서 인클로저를 설계했습니다.

제 첫 번째 아이디어는 이 프로젝트가 토양, 식물 및 물과 연결되어 있기 때문에 본체를 물방울처럼 만들고자 하는 것이었습니다.

곡선을 계속 변경하고 전자 기기를 위한 공간을 만들면서, 인클로저는 의도치 않게 약간 오징어처럼 보이기 시작했습니다.

원래 계획의 일부는 아니었지만, 그것이 프로젝트에 고유한 개성을 부여한다고 생각하여 그 형태를 유지했습니다.

케이스는 두 개의 나사로 조립되는 절반으로 나뉘어 있었습니다.

모델링하는 동안 XIAO ESP32-C6, MAX485 모듈, 배선, 상태 LED 및 센서 케이블을 위한 공간을 남겨야 했습니다.

전자 기기를 조정해야 할 경우 두 쪽을 다시 열 수 있도록 인클로저 주위에 나사 지점을 추가했습니다.

곡선형 외관은 제품을 더 친근하게 보이게 했지만, 내부 여유 공간을 신중하게 확인해야 한다는 의미이기도 했습니다. 직사각형 박스에 맞는 부품은 가장자리 근처의 곡면 벽과 간섭할 수 있습니다.

인쇄하기 전에 부품 치수를 확인하고 Fusion 360의 단면 보기를 사용하여 충돌 여부를 확인했습니다.




STEP 4. 3D 프린팅


3D 프린팅


3D 프린팅


3D 프린팅


저는 삼색 PLA를 사용해 Anycubic Kobra S1으로 인클로저를 출력했습니다.

변하는 색상이 곡선형 인클로저를 더 흥미롭게 보이게 할 것이라고 생각해 처음에는 이 필라멘트를 선택했습니다.

실용적인 시작 프로파일로 일반 PLA 인클로저에 적합한 다음 설정을 사용했습니다:

  1. 레이어 높이 0.20 mm
  2. 벽 루프 3개
  3. 상단 및 하단 레이어 4개
  4. 자이로이드 인필 15%
  5. 곡선 오버행에 필요한 곳에만 지지대 적용
  6. 접촉 면적이 작은 부품에는 브림 적용
  7. 노즐 온도 약 210°C
  8. 베드 온도 약 60°C

다른 PLA 브랜드나 슬라이서 프로파일을 사용하는 경우 이 값을 약간 조정해야 할 수 있습니다.

두 반쪽 부품은 외부의 보이는 표면을 최대한 깔끔하게 유지하고 부품 영역 내부의 불필요한 서포트 재료를 줄일 수 있도록 배치했습니다.

부품은 성공적으로 출력되었지만, 삼색 효과는 제가 상상했던 것만큼 보기 좋지 않았습니다.

색상 변화가 형태를 방해하고 표면이 덜 균일해 보이게 했습니다. 완성된 인클로저 전체를 즉시 다시 출력하는 대신, 기존 출력물을 페인트로 마감하기로 했습니다.




STEP 5. 페인팅 및 마감


페인팅 및 마감


페인팅 및 마감


페인팅 및 마감


페인팅 및 마감


저는 지지대를 제거하고 두 개의 인쇄된 절반의 가장자리를 청소하는 것부터 시작했습니다.

표면은 가장 눈에 띄는 층 선과 거친 부분이 줄어들 때까지 점차적으로 샌딩되었습니다.

두 면이 만나는 이음새에 특히 주의를 기울였는데, 그곳의 불균형은 조립 후에도 여전히 눈에 띄기 때문입니다.

샌딩 후에는 먼지를 제거하고 플라스틱에 적합한 가벼운 프라이머를 도포했습니다.

프라이머는 삼색 필라멘트에서 보기 어려운 긁힘과 낮은 부분을 드러내는 데 도움이 되었습니다.

저는 그 부분을 다시 샌딩한 후 여러 번의 가벼운 초록색 스프레이 페인트를 도포했습니다.

가벼운 도포가 한 번의 두꺼운 도포보다 더 효과적이었는데, 이는 흐름을 줄이고 인클로저 세부 사항을 보존했기 때문입니다.

최종 초록색 마감은 훨씬 더 깨끗해 보였고 원래의 삼색 표면보다 정원 테마와 훨씬 더 잘 어울렸습니다.

조립 중에 마감이 손상되지 않도록 전자 장치를 설치하기 전에 페인트가 완전히 경화되도록 했습니다.




STEP 6. 전자기기


전자기기


전자기기


전자기기


XIAO ESP32-C6

나는 XIAO ESP32-C6를 선택했습니다. 이 제품은 컴팩트하고 내장 Wi-Fi가 있으며 RS485 인터페이스와 상태 LED를 위한 충분한 핀을 제공합니다.

작은 크기로 인해 모든 것을 맞춤형 인클로저 안에 쉽게 넣을 수 있었습니다.

7-in-1 토양 센서

토양 프로브는 수분, 온도, pH, 전기 전도도, 질소, 인, 칼륨의 일곱 가지 측정을 하나의 본체에 결합합니다.

산업용 스타일의 프로브 하나를 사용함으로써 프로젝트 내부의 개별 센서와 배선 수를 줄일 수 있었습니다.

센서는 일곱 개의 개별 아날로그 신호를 생성하는 대신 RS485를 통해 통신합니다. 이는 긴 케이블과 야외 환경에서 유용하지만, ESP32에는 RS485 인터페이스가 필요하다는 것을 의미합니다.

MAX485 모듈

MAX485는 센서의 RS485 통신을 UART 직렬 데이터로 변환합니다.

DE 및 /RE 핀은 함께 연결되어 있어 하나의 ESP32 핀이 모듈을 요청 전송과 응답 수신 사이에서 전환할 수 있습니다.

전통적인 MAX485 모듈은 일반적으로 5V에서 작동합니다. RO 출력은 3.3V ESP32 입력에 직접 연결되어서는 안 됩니다.

나는 모듈과 XIAO 사이에 레벨 시프팅을 사용했습니다. 3.3V 호환 MAX3485 모듈도 더 깔끔한 대안으로 사용할 수 있습니다.

WS2812 LED

나는 단일 WS2812 RGB LED를 추가했습니다. 이 LED는 단 하나의 데이터 핀만으로 장치 상태를 표시할 수 있습니다.

LED는 Wi-Fi가 연결되지 않았을 때 빨간색이고, ESP32가 연결된 후에는 초록색으로 변경됩니다.

전원 및 접지

토양 센서는 자체 적합한 외부 전원을 사용합니다.

XIAO, MAX485, 센서 전원 및 LED는 공통 접지를 공유해야 합니다. 공통 기준이 없으면 A와 B 선이 올바르더라도 RS485 통신이 신뢰할 수 없게 될 수 있습니다.




STEP 7. 회로도


회로도


회로도


회로도


다음과 같이 XIAO 핀을 할당했습니다.

기능 XIAO 핀
----------------------------------------
RS485 수신 D7
RS485 송신 D6
MAX485 방향 제어 D3
WS2812 데이터 D2

XIAO에서 MAX485로

XIAO ESP32-C6 MAX485 용도
------------------------------------------------------------------------------
D6 DI Modbus 명령 전송
D7 RO(레벨 시프터 경유) 센서 응답 수신
D3 DE와 /RE를 함께 연결 통신 방향 전환
5V VCC MAX485 모듈에 전원 공급
GND GND 공통 접지

MAX485에서 토양 센서로

MAX485 토양 센서
---------------------------------------------------
A A or A+
B B or B-
GND 센서 전원 음극/공통 접지

센서가 응답하지 않으면 A와 B를 한 번 바꿔 연결해 보세요. 제조업체에 따라 이 한 쌍의 표기가 반대로 되어 있는 경우가 가끔 있습니다.

WS2812 연결

XIAO ESP32-C6 WS2812
D2 DIN
5V VCC
GND GND

LED에 5V 전원을 공급하는 경우, WS2812 데이터 라인에 3.3V 신호를 5V 신호로 변환하는 로직 레벨 변환기를 사용하는 것이 좋습니다.

9–24V 센서 전원의 양극 출력을 XIAO에 연결하지 마세요.




STEP 8. 최종 조립


최종 조립


최종 조립


최종 조립


페인팅된 부품이 준비되자, 모든 전자 기기를 인클로저 안에 배치했습니다.

XIAO ESP32-C6와 MAX485를 USB 연결, 센서 배선 및 상태 LED가 접근 가능하도록 위치시켰습니다.

전선은 나사 기둥과 케이스의 외부 가장자리에서 멀리 배치되었습니다.

이는 두 개의 절반 사이에 전선이 끼이면 인클로저가 제대로 닫히지 않거나 절연체가 손상될 수 있기 때문에 중요합니다.

케이스를 닫기 전에 회로에 전원을 한 번 공급하고 LED가 빨간색으로 켜졌다가 Wi-Fi에 연결된 후 녹색으로 바뀌는지 확인했습니다.

설치 후 전자 기기가 여전히 작동하는지 확인하기 위해 브라우저에서 ESP32 /data 주소를 열었습니다.

그런 다음 두 개의 인클로저 절반을 정렬하고 나사를 조였습니다.

접착제 대신 나사를 사용하면 나중에 센서 배선, 펌웨어 또는 전자 기기를 변경해야 할 경우 케이스를 다시 열 수 있습니다.

나사를 조인 후, 인클로저 주위의 이음새를 확인하고 케이블이 끼이지 않았는지 확인했습니다.




STEP 9. 대시보드 이해하기


대시보드 이해하기


펌웨어와 웹사이트 코드를 살펴보기 전에, 사용자 관점에서 대시보드를 간단히 살펴보겠습니다.

웹사이트는 센서 값을 표시하는 페이지 이상의 역할을 합니다. 주된 작업은 토양 분석기로부터 오는 측정값을 받아 해당 토양이 특정 작물에 적합한지 설명하는 것입니다.

전체 인터페이스는 다음과 같은 흐름을 따릅니다:

랜딩 페이지
토양 분석 시작
센서 읽기 애니메이션
토양 대시보드
작물 선택
호환성 보고서
추천 사항
토양 건강 점수
보고서 내보내기 또는 다운로드

1. 랜딩 페이지

웹사이트는 Dirt Nerd 로고, 프로젝트 이름 및 주요 태그라인이 포함된 간단한 랜딩 페이지로 열립니다:

성장하기 전에 토양을 알아보세요.

이 화면은 의도적으로 최소한으로 유지했습니다. 사용자는 혼잡한 대시보드가 아닌 짧은 설명과 토양 분석 시작 버튼만을 보게 됩니다.

설정 버튼은 데모 모드와 라이브 모드 간 전환에도 사용될 수 있습니다. 라이브 모드에서는 Serial Monitor에 표시된 ESP32의 IP 주소가 여기에 입력됩니다.

2. 분석 시작

토양 분석 시작 버튼을 누르면 읽기 시퀀스가 시작됩니다.

라이브 모드에서는 웹사이트가 먼저 ESP32에 로컬 IP 주소를 사용하여 연락합니다. 그러면 ESP32는 RS485 토양 센서로부터 최신 측정값을 반환합니다.

결과로 즉시 넘어가는 대신, 각 측정을 거치는 짧은 분석 애니메이션을 추가했습니다:

토양 분석기 연결 중
수분 센서 읽기
토양 온도 측정
토양 pH 측정
전기 전도도 확인
질소 읽기
인 읽기
칼륨 읽기
토양 보고서 생성 중

이렇게 하면 단순히 다른 데이터 페이지를 여는 것보다 실제 분석기를 사용하는 느낌이 더 가까워집니다.

3. 센서 대시보드

애니메이션이 끝난 후, 주요 대시보드가 나타납니다.

첫 번째 섹션은 모든 일곱 가지 토양 매개변수를 표시합니다:

  1. 토양 수분
  2. 토양 온도
  3. 토양 pH
  4. 전기 전도도
  5. 질소
  6. 칼륨

각 매개변수는 현재 값, 단위, 설명 및 상태가 포함된 카드로 구성되어 있습니다.

예를 들어, 수분은 낮음으로 표시될 수 있고, 온도는 이상적으로 표시될 수 있습니다. 이를 통해 각 숫자를 개별적으로 연구하지 않고도 토양의 일반적인 상태를 이해할 수 있습니다.

읽기 새로 고침 버튼을 누르면 ESP32로부터 새로운 값 세트를 요청합니다.

4. 작물 선택

원시 토양 읽기는 맥락 없이는 큰 의미가 없습니다. 한 식물에 적합한 pH 또는 수분 수준이 다른 식물에는 이상적이지 않을 수 있습니다.

센서 카드 아래에서 사용자는 재배하고 싶은 작물을 선택할 수 있습니다.

사용 가능한 옵션은 다음과 같습니다:

  1. 토마토
  2. 고추
  3. 바질
  4. 민트
  5. 딸기
  6. 고수
  7. 시금치
  8. 오이
  9. 장미
  10. 메리골드

다른 작물을 선택하면 분석이 자동으로 업데이트됩니다.

센서 읽기는 동일하게 유지되며, 토양이 변경되지 않았기 때문입니다. 변경되는 것은 이러한 읽기를 평가하는 데 사용되는 이상적인 범위입니다.

5. 호환성 보고서

호환성 보고서는 현재 토양을 선택한 작물의 선호 조건과 비교합니다.

다음과 같은 내용을 표시합니다:

  1. 전반적인 적합성 비율
  2. 호환성 상태
  3. 각 매개변수의 현재 값
  4. 선택한 작물의 이상적인 범위
  5. 개별 매개변수 상태

일반적인 비교는 다음과 같습니다:

매개변수 | 현재 | 이상적 | 상태
-------------------------------------------------------------
수분 | 34% | 45–60% | 낮음
온도 | 26.4°C | 22–30°C | 이상적
pH | 6.9 | 6.0–6.5 | 약간 높음
질소 | 42 mg/kg | 60–90 mg/kg | 개선 필요

여기서 대시보드는 센서 판독값을 실제로 유용한 것으로 변환하기 시작합니다.

대시보드는 사용자에게 토양에 특정 양의 질소가 포함되어 있다고만 말하는 대신, 그 양이 사용자가 선택한 식물에 적합한지 설명합니다.

6. 토양 건강 점수

작물 호환성과 함께, 대시보드는 원형 진행 표시기를 사용하여 전체 토양 건강 점수를 보여줍니다.

이 점수는 다음과 같은 요소를 사용하여 토양 상태에 대한 일반적인 개요를 제공합니다:

  1. 토양 구조
  2. 영양소 수준
  3. 수분 균형
  4. pH 균형

작물 호환성과 토양 건강은 서로 다른 질문에 답하기 때문에 별도로 표시됩니다.

호환성 점수는 다음과 같은 질문을 합니다:

이 토양이 선택한 작물에 적합한가요?

토양 건강 점수는 다음과 같은 질문을 합니다:

이 토양의 전반적인 상태는 어떤가요?

토양 샘플은 특정 작물에 대한 조정이 필요할 수 있지만, 여전히 합리적으로 건강할 수 있습니다.

7. 권장 사항

권장 사항 섹션은 보고서를 실용적인 행동 계획으로 변환합니다.

판독값과 선택한 작물에 따라, 대시보드는 다음과 같은 행동을 제안할 수 있습니다:

  1. 퇴비 또는 벌레 퇴비 추가
  2. 수분 일관성 개선
  3. 개화 전 칼륨 증가
  4. 개선제를 추가한 후 pH 모니터링

이 부분은 직접적이고 따라하기 쉽게 만들고 싶었습니다. 목표는 사용자를 농업 데이터로 압도하는 것이 아니라, 결과로 무엇을 실제로 할 수 있는지 설명하는 것입니다.

각 권장 사항에는 우선 순위, 이번 주, 개화 전 또는 모니터와 같은 우선 순위도 포함됩니다.

8. 영양소 시각화

대시보드는 질소, 인, 칼륨 및 전기 전도도를 위한 막대 차트를 포함합니다.

이는 카드에서 개별 값을 읽는 것보다 더 빠른 시각적 비교를 제공합니다.

선택한 작물이 변경되면, 나머지 분석은 해당 작물의 선호하는 토양 조건에 맞게 업데이트됩니다.

9. 보고서 저장 및 공유

분석이 완료되면 사용자는:

  1. 판독값을 JSON 파일로 내보내기
  2. 보고서 요약 복사 및 공유
  3. 보고서를 PDF로 다운로드
  4. 다른 식물 분석

내보낸 JSON은 판독값을 저장하거나 다른 프로젝트에서 사용할 필요가 있을 때 유용합니다. PDF 옵션은 저장하거나 인쇄할 수 있는 더 읽기 쉬운 보고서를 생성합니다.

10. 데모 및 실시간 모드

대시보드는 두 가지 데이터 모드를 포함합니다.

데모 모드는 샘플 판독값을 사용하며, 물리적 장치에 연결하지 않고 인터페이스를 탐색하는 데 유용합니다.

실시간 모드는 ESP32에서 실제 판독값을 요청합니다. 사용자는 Serial Monitor에 표시된 IP 주소를 입력하고 연결을 테스트한 후 분석을 시작합니다.

이로 인해 완전한 센서 하드웨어가 연결되지 않은 상태에서도 웹사이트 작업을 계속할 수 있었으며, 최종 장치에 대해 동일한 인터페이스를 유지할 수 있었습니다.

대시보드는 또한 반응형이므로, ESP32와 동일한 로컬 네트워크에 연결된 전화, 태블릿 또는 컴퓨터에서 동일한 작업 흐름을 사용할 수 있습니다.

완전한 인터페이스가 명확해졌으므로, 다음 단계는 XIAO ESP32-C6을 프로그래밍하여 센서를 읽고 이 대시보드에서 요구하는 데이터를 제공하는 것입니다.

본문이 길어 아래 덧글 2개로 계속됩니다.


원문: https://www.instructables.com/Dirt-Nerd-Know-What-Your-Soil-Needs-Before-You-Gro/

좋아요 0
게시판

본문 계속 1/2




STEP 10. ESP32 프로그래밍


ESP32 프로그래밍


전자 장치를 조립했으므로 다음 단계는 XIAO ESP32-C6를 프로그래밍하는 것이었습니다.

펌웨어에는 네 가지 주요 작업이 있습니다:

  1. Modbus RTU를 통해 토양 매개변수 7가지를 모두 읽습니다.
  2. 장치를 Wi-Fi에 연결합니다.
  3. 로컬 웹 서버를 통해 판독값을 JSON으로 제공합니다.
  4. WS2812 LED를 사용해 Wi-Fi 상태를 표시합니다.

완성된 펌웨어는 프로젝트 파일과 함께 첨부되어 있지만, 아래에서 중요한 부분을 나누어 설명하겠습니다.

1. 필요한 라이브러리 설치

코드를 컴파일하기 전에 Arduino IDE의 라이브러리 관리자에서 다음 라이브러리를 설치하세요:

  1. ModbusMaster (Doc Walker 제작)
  2. Adafruit NeoPixel (Adafruit 제작)

Wi-Fi, WebServer 및 mDNS 라이브러리는 ESP32 보드 패키지에 포함되어 있습니다.

다음과 같은 include를 사용했습니다:

#include <Arduino.h>
#include <WiFi.h>
#include <WebServer.h>
#include <ESPmDNS.h>
#include <ModbusMaster.h>
#include <Adafruit_NeoPixel.h>

Arduino IDE에서 다음을 선택하세요:

Tools → Board → ESP32 Arduino → XIAO_ESP32C6

Serial Monitor는 115200 baud(보드레이트)로 사용했습니다.

2. Wi-Fi 및 핀 구성

Wi-Fi 자격 증명은 스케치 앞부분에 저장되어 있습니다.

여기에는 제 자격 증명 대신 자리 표시자를 넣었으므로, 사용하려는 네트워크의 자격 증명을 입력하세요:

const char* WIFI_SSID = "YOUR_WIFI_NAME";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";

하드웨어 연결에는 D6과 D7을 UART 핀으로, D3을 MAX485 방향 제어용으로, D2를 WS2812용으로 사용했습니다.

constexpr uint8_t RS485_RX_PIN = D7;
constexpr uint8_t RS485_TX_PIN = D6;
constexpr uint8_t RS485_DIR_PIN = D3;

constexpr uint8_t RGB_DATA_PIN = D2;
constexpr uint8_t RGB_PIXEL_COUNT = 1;

MAX485의 DE 핀과 /RE 핀은 서로 연결하고 D3으로 제어합니다.

3. Modbus 구성

제 센서는 Modbus RTU를 사용해 7가지 판독값을 모두 전송합니다.

구성은 한곳에 모아 두었으므로, 다른 센서가 다른 baud rate, 슬레이브 주소 또는 레지스터 레이아웃을 사용하는 경우 쉽게 변경할 수 있습니다.

constexpr uint32_t SENSOR_BAUD = 9600;
constexpr uint8_t SENSOR_SLAVE_ID = 1;

constexpr uint16_t FIRST_REGISTER = 0x0000;
constexpr uint8_t REGISTER_COUNT = 7;

constexpr bool USE_INPUT_REGISTERS = false;

constexpr unsigned long SENSOR_INTERVAL_MS = 2000;
constexpr unsigned long WIFI_RETRY_INTERVAL_MS = 10000;

USE_INPUT_REGISTERS를 false로 설정하면 펌웨어는 Modbus 기능 0x03을 사용하며, 이 기능은 홀딩 레지스터를 읽습니다.

센서 설명서에 기능 0x04가 지정되어 있다면 다음과 같이 변경하세요:

constexpr bool USE_INPUT_REGISTERS = true;

4. 주요 객체 생성

다음으로 UART 통신, Modbus, 웹 서버 및 WS2812용 객체를 각각 생성했습니다.

HardwareSerial soilSerial(1);
ModbusMaster modbus;
WebServer server(80);

Adafruit_NeoPixel statusPixel(
RGB_PIXEL_COUNT,
RGB_DATA_PIN,
NEO_GRB + NEO_KHZ800
);

웹 서버는 포트 80에서 실행되므로 별도의 포트 번호를 추가하지 않고 ESP32의 IP 주소로 판독값에 액세스할 수 있습니다.

5. 센서 판독값 저장

서로 관련 없는 전역 변수 7개를 두는 대신 모든 값을 하나의 구조체에 저장했습니다.

struct SoilReadings {
float moisture = 0.0f;
float temperature = 0.0f;
float ph = 0.0f;
float ec = 0.0f;

uint16_t nitrogen = 0;
uint16_t phosphorus = 0;
uint16_t potassium = 0;

bool valid = false;
uint8_t modbusResult = 0xFF;
unsigned long updatedAt = 0;
};

SoilReadings soil;

valid 값은 센서가 응답하는지 웹 대시보드에 알려 주고, modbusResult는 통신 오류를 디버깅하는 데 도움이 됩니다.

6. Wi-Fi 상태 LED

Serial Monitor를 열지 않아도 장치 상태를 알 수 있게 하고 싶었습니다.

ESP32의 연결이 끊겨 있는 동안 RGB LED는 빨간색으로 켜져 있고, Wi-Fi에 연결되면 녹색으로 바뀝니다.

void setStatusPixel(bool wifiConnected) {
const uint32_t color = wifiConnected
? statusPixel.Color(0, 90, 0)
: statusPixel.Color(110, 0, 0);

statusPixel.setPixelColor(0, color);
statusPixel.show();
}

LED는 표시기로만 사용하면 되므로 밝기는 상당히 낮게 설정했습니다.

statusPixel.begin();
statusPixel.setBrightness(55);
setStatusPixel(false);

7. MAX485 제어

RS485는 반이중 방식이므로 MAX485는 동시에 송신하고 수신할 수 없습니다.

Modbus 요청을 보내기 전에 펌웨어가 송신을 활성화합니다. 요청을 보내자마자 모듈을 다시 수신 모드로 전환합니다.

void preTransmission() {
digitalWrite(RS485_DIR_PIN, HIGH);
delayMicroseconds(200);
}

void postTransmission() {
delayMicroseconds(200);
digitalWrite(RS485_DIR_PIN, LOW);
}

이 함수들은 설정 중에 Modbus 라이브러리에 등록됩니다:

modbus.preTransmission(preTransmission);
modbus.postTransmission(postTransmission);

이 방향 제어가 없으면 ESP32는 요청을 올바르게 보낼 수는 있어도 센서의 응답을 전혀 받지 못할 수 있습니다.

8. 7가지 토양 매개변수 읽기

펌웨어는 주소 0x0000에서 시작하는 연속 레지스터 7개를 요청합니다.

bool readSoilSensor() {
uint8_t result;

if (USE_INPUT_REGISTERS) {
result = modbus.readInputRegisters(
FIRST_REGISTER,
REGISTER_COUNT
);
} else {
result = modbus.readHoldingRegisters(
FIRST_REGISTER,
REGISTER_COUNT
);
}

soil.modbusResult = result;

요청이 실패하면 데이터를 유효하지 않은 것으로 표시하고 Serial Monitor에 Modbus 결과를 출력합니다.

if (result != ModbusMaster::ku8MBSuccess) {
soil.valid = false;

Serial.printf(
"Modbus read failed. Result: 0x%02X\n",
result
);

return false;
}

요청이 성공하면 응답 버퍼에 7가지 측정값이 모두 들어 있습니다.

const uint16_t rawMoisture =
modbus.getResponseBuffer(0);

const int16_t rawTemperature =
static_cast<int16_t>(
modbus.getResponseBuffer(1)
);

const uint16_t rawEc =
modbus.getResponseBuffer(2);

const uint16_t rawPh =
modbus.getResponseBuffer(3);

그런 다음 원시 값을 실제 단위로 변환해야 합니다.

soil.moisture = rawMoisture / 10.0f;
soil.temperature = rawTemperature / 10.0f;
soil.ec = rawEc / 1000.0f;
soil.ph = rawPh / 10.0f;

soil.nitrogen =
modbus.getResponseBuffer(4);

soil.phosphorus =
modbus.getResponseBuffer(5);

soil.potassium =
modbus.getResponseBuffer(6);

soil.valid = true;
soil.updatedAt = millis();

여기서 사용하는 레지스터 순서는 다음과 같습니다:

Register 0 → Moisture
Register 1 → Temperature
Register 2 → Electrical conductivity
Register 3 → pH
Register 4 → Nitrogen
Register 5 → Phosphorus
Register 6 → Potassium

마지막으로 값을 Serial Monitor에 출력합니다. 덕분에 웹사이트를 사용하기 전에 전자 장치를 더 쉽게 테스트할 수 있었습니다.

Serial.println("\n--- Soil reading ---");

Serial.printf(
"Moisture: %.1f %%\n",
soil.moisture
);

Serial.printf(
"Temperature: %.1f C\n",
soil.temperature
);

Serial.printf("pH: %.1f\n", soil.ph);
Serial.printf("EC: %.3f mS/cm\n", soil.ec);

Serial.printf(
"Nitrogen: %u mg/kg\n",
soil.nitrogen
);

Serial.printf(
"Phosphorus: %u mg/kg\n",
soil.phosphorus
);

Serial.printf(
"Potassium: %u mg/kg\n",
soil.potassium
);

return true;
}

이 센서의 버전이 다르면 서로 다른 레지스터나 스케일링을 사용하는 경우가 있습니다. 센서가 응답하지만 판독값이 잘못된 순서로 표시된다면 Modbus 설명서에 따라 조정해야 할 부분은 바로 여기입니다.

9. 판독값을 JSON으로 변환

웹사이트에는 예측 가능한 형식이 필요하므로 판독값을 JSON 객체로 변환합니다.

String readingsJson() {
String json;
json.reserve(360);

json += "{";

json += "\"moisture\":" +
String(soil.moisture, 1) + ",";

json += "\"temperature\":" +
String(soil.temperature, 1) + ",";

json += "\"ph\":" +
String(soil.ph, 1) + ",";

json += "\"ec\":" +
String(soil.ec, 3) + ",";

json += "\"nitrogen\":" +
String(soil.nitrogen) + ",";

json += "\"phosphorus\":" +
String(soil.phosphorus) + ",";

json += "\"potassium\":" +
String(soil.potassium) + ",";

json += "\"sensorConnected\":" +
String(soil.valid ? "true" : "false") + ",";

json += "\"wifiConnected\":" +
String(
WiFi.status() == WL_CONNECTED
? "true"
: "false"
) + ",";

json += "\"ip\":\"" +
WiFi.localIP().toString() + "\",";

json += "\"updatedAtMs\":" +
String(soil.updatedAt) + ",";

json += "\"modbusResult\":" +
String(soil.modbusResult);

json += "}";

return json;
}

정상적인 응답은 다음과 같습니다:

{
"moisture": 34.2,
"temperature": 26.4,
"ph": 6.9,
"ec": 1.2,
"nitrogen": 42,
"phosphorus": 38,
"potassium": 156,
"sensorConnected": true,
"wifiConnected": true,
"ip": "192.168.1.105",
"updatedAtMs": 24518,
"modbusResult": 0
}

웹사이트는 주로 처음 7개 값을 사용합니다. 나머지 필드는 장치를 확인하고 연결 문제를 진단하는 데 유용합니다.

10. 웹사이트의 데이터 요청 허용

웹사이트는 컴퓨터에서 실행되고 API는 ESP32에서 실행되므로 브라우저는 두 대상을 서로 다른 출처로 취급합니다.

localhost 대시보드가 센서 데이터를 요청할 수 있도록 CORS 헤더를 추가했습니다.

void addCorsHeaders() {
server.sendHeader(
"Access-Control-Allow-Origin",
"*"
);

server.sendHeader(
"Access-Control-Allow-Methods",
"GET, OPTIONS"
);

server.sendHeader(
"Access-Control-Allow-Headers",
"Content-Type"
);

server.sendHeader(
"Cache-Control",
"no-store"
);
}

no-store 헤더는 브라우저가 이전에 캐시된 판독값을 표시하지 않도록 합니다.

11. /data 엔드포인트 생성

Dirt Nerd에서 사용하는 주요 엔드포인트는 /data입니다.

void handleData() {
addCorsHeaders();

server.send(
200,
"application/json",
readingsJson()
);
}

ESP32와 센서가 응답하는지 확인하기 위한 더 간단한 /health 엔드포인트도 추가했습니다.

void handleHealth() {
addCorsHeaders();

String json = "{\"status\":\"";
json += soil.valid ? "ok" : "sensor_error";

json += "\",\"ip\":\"" +
WiFi.localIP().toString() +
"\"}";

server.send(
200,
"application/json",
json
);
}

서버가 시작될 때 엔드포인트가 등록됩니다:

void startWebServer() {
server.on("/", HTTP_GET, handleRoot);
server.on("/data", HTTP_GET, handleData);
server.on("/health", HTTP_GET, handleHealth);

server.on("/", HTTP_OPTIONS, handleOptions);
server.on("/data", HTTP_OPTIONS, handleOptions);
server.on("/health", HTTP_OPTIONS, handleOptions);

server.onNotFound([]() {
addCorsHeaders();

server.send(
404,
"application/json",
"{\"error\":\"Not found\"}"
);
});

server.begin();
Serial.println("HTTP server started");
}

또한 /에 간단한 진단 페이지를 포함했습니다. 이를 통해 브라우저에서 ESP32의 IP 주소를 직접 열어 전체 Dirt Nerd 대시보드를 시작하지 않고도 센서를 확인할 수 있습니다.

12. Wi-Fi 연결

시작 중에는 ESP32가 연결을 시도하는 동안 RGB LED가 빨간색으로 켜져 있습니다.

void connectWifi() {
Serial.printf(
"Connecting to Wi-Fi: %s\n",
WIFI_SSID
);

WiFi.mode(WIFI_STA);
WiFi.setAutoReconnect(true);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);

const unsigned long started = millis();

while (
WiFi.status() != WL_CONNECTED &&
millis() - started < 20000
) {
setStatusPixel(false);
delay(350);
Serial.print('.');
}

Serial.println();

연결되면 LED가 녹색으로 바뀌고 IP 주소가 Serial Monitor에 출력됩니다.

const bool connected =
WiFi.status() == WL_CONNECTED;

setStatusPixel(connected);
previousWifiState = connected;

if (connected) {
Serial.println("Wi-Fi connected");

Serial.print(
"Dirt Nerd IP address: http://"
);
Serial.println(WiFi.localIP());

Serial.print("JSON endpoint: http://");
Serial.print(WiFi.localIP());
Serial.println("/data");

if (MDNS.begin("dirt-nerd")) {
Serial.println(
"Local name: http://dirt-nerd.local"
);
}
} else {
Serial.println(
"Wi-Fi connection timed out; "
"automatic retries will continue."
);
}
}

숫자로 표시된 IP 주소를 Dirt Nerd 웹사이트에 입력합니다.

13. 자동 Wi-Fi 재연결

라우터나 핫스팟의 연결이 일시적으로 끊길 때마다 장치를 재설정해야 하는 상황은 피하고 싶었습니다.

void monitorWifi() {
const bool connected =
WiFi.status() == WL_CONNECTED;

if (connected != previousWifiState) {
setStatusPixel(connected);
previousWifiState = connected;

if (connected) {
Serial.print(
"Wi-Fi reconnected. IP: http://"
);
Serial.println(WiFi.localIP());
} else {
Serial.println("Wi-Fi disconnected");
}
}

if (
!connected &&
millis() - lastWifiRetry >=
WIFI_RETRY_INTERVAL_MS
) {
lastWifiRetry = millis();

Serial.println(
"Retrying Wi-Fi connection..."
);

WiFi.disconnect();
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
}
}

이 함수는 시작 후에도 RGB 상태가 정확하게 유지되도록 합니다.

14. 설정

setup() 함수는 상태 LED, MAX485, UART, Modbus 연결, Wi-Fi 및 웹 서버를 초기화합니다.

void setup() {
Serial.begin(115200);
delay(1200);

Serial.println("\nStarting Dirt Nerd...");

statusPixel.begin();
statusPixel.setBrightness(55);
setStatusPixel(false);

pinMode(RS485_DIR_PIN, OUTPUT);
digitalWrite(RS485_DIR_PIN, LOW);

soilSerial.begin(
SENSOR_BAUD,
SERIAL_8N1,
RS485_RX_PIN,
RS485_TX_PIN
);

modbus.begin(
SENSOR_SLAVE_ID,
soilSerial
);

modbus.preTransmission(preTransmission);
modbus.postTransmission(postTransmission);

connectWifi();
startWebServer();
readSoilSensor();
}

15. 메인 루프

메인 루프는 상당히 간단합니다.

void loop() {
server.handleClient();
monitorWifi();

if (
millis() - lastSensorRead >=
SENSOR_INTERVAL_MS
) {
lastSensorRead = millis();
readSoilSensor();
}

delay(2);
}

ESP32는 브라우저 요청에 응답하고 Wi-Fi 연결을 모니터링하면서 2초마다 센서를 읽습니다.

펌웨어를 업로드한 후 115200 baud(보드레이트)로 Serial Monitor를 엽니다. RGB LED가 녹색으로 바뀌면 터미널에 표시된 IP 주소를 복사합니다.

그러면 센서 데이터를 다음 주소에서 이용할 수 있습니다:

http://ESP32-IP/data

이제 하드웨어가 로컬 네트워크를 통해 구조화된 데이터를 생성합니다. 웹사이트 단계에서는 Dirt Nerd가 이 엔드포인트에 요청을 보내 원시 판독값을 대시보드, 작물 비교 및 권장 사항으로 변환하는 방법을 보여드리겠습니다.



본문 계속 2/2




STEP 11. 웹페이지 만들기


웹페이지 만들기


웹페이지 만들기


웹페이지 만들기


Next.js, React, TypeScript 및 CSS를 사용하여 Dirt Nerd 웹사이트를 만들었습니다. 주요 목표는 식물 옆에서 사용할 수 있을 만큼 인터페이스를 간단하게 유지하면서도 센서가 수집한 모든 정보를 표시하는 것이었습니다.

웹사이트는 두 가지 작동 모드를 지원합니다:

  1. 데모 모드에서는 편집 가능한 샘플 측정값을 사용합니다.
  2. 라이브 모드에서는 ESP32에 실제 측정값을 요청합니다.

주요 프로젝트 파일은 다음과 같습니다:

app/
├── globals.css
├── layout.tsx
├── page.tsx
└── sensor-data.ts

전체 웹사이트는 이 단계에 ZIP 파일로 첨부되어 있습니다. 다음 섹션에서는 코드의 가장 중요한 부분을 설명합니다.

센서 데이터 저장

센서 측정값은 별도의 sensor-data.ts 파일에 저장했습니다. 이렇게 하면 동일한 대시보드 구성 요소를 샘플 값과 ESP32에서 수신한 데이터 모두에 사용할 수 있습니다.

export type SensorKey =
| "moisture"
| "temperature"
| "ph"
| "ec"
| "nitrogen"
| "phosphorus"
| "potassium";

export const sensorData = {
moisture: {
label: "Soil moisture",
value: "34",
unit: "%",
status: "Low"
},

temperature: {
label: "Temperature",
value: "26.4",
unit: "°C",
status: "Ideal"
},

ph: {
label: "Soil pH",
value: "6.9",
unit: "pH",
status: "Good"
},

ec: {
label: "Conductivity",
value: "1.2",
unit: "mS/cm",
status: "Good"
},

nitrogen: {
label: "Nitrogen",
value: "42",
unit: "mg/kg",
status: "Low"
},

phosphorus: {
label: "Phosphorus",
value: "38",
unit: "mg/kg",
status: "Good"
},

potassium: {
label: "Potassium",
value: "156",
unit: "mg/kg",
status: "Medium"
}
};

웹사이트가 라이브 데이터를 수신하면 이 객체 내부의 값만 교체합니다. 나머지 인터페이스는 측정값이 데모 모드에서 왔는지 실제 센서에서 왔는지 알 필요가 없습니다.

웹사이트 상태 관리

메인 페이지에서는 현재 화면, 선택한 작물, 작동 모드, ESP32 주소 및 센서 측정값을 추적합니다.

const [screen, setScreen] =
useState("landing");

const [crop, setCrop] =
useState("tomato");

const [mode, setMode] =
useState("demo");

const [ipAddress, setIpAddress] =
useState("");

const [deviceStatus, setDeviceStatus] =
useState("demo");

const [readings, setReadings] =
useState(sensorData);

const [draftReadings, setDraftReadings] =
useState(sensorData);

화면 값에 따라 인터페이스가 랜딩 페이지, 분석 애니메이션 및 최종 대시보드 사이에서 전환됩니다.

장치 설정 저장

선택한 작동 모드와 ESP32 주소는 브라우저에 저장됩니다. 따라서 사용자는 새로 고침할 때마다 주소를 다시 입력할 필요가 없습니다.

useEffect(() => {
const savedMode =
localStorage.getItem("dirt-nerd-mode");

const savedIp =
localStorage.getItem("dirt-nerd-ip");

if (savedMode === "live") {
setMode("live");
setDeviceStatus("idle");
}

if (savedIp) {
setIpAddress(savedIp);
}
}, []);

설정은 다음을 사용하여 저장합니다:

function saveSettings() {
localStorage.setItem(
"dirt-nerd-mode",
mode
);

localStorage.setItem(
"dirt-nerd-ip",
ipAddress.trim()
);

if (mode === "demo") {
setReadings(draftReadings);
}

setDeviceStatus(
mode === "demo" ? "demo" : "idle"
);

setSettingsOpen(false);
}

사용자 지정 데모 측정값 추가

데모 모드에는 7가지 토양 매개변수 모두에 대한 입력 필드가 포함되어 있습니다. 대시보드를 설계할 때 실제 센서를 매번 연결하지 않고도 다양한 값을 테스트할 수 있어 특히 유용했습니다.

const customFields = [
{
key: "moisture",
min: 0,
max: 100,
step: 1
},
{
key: "temperature",
min: 0,
max: 60,
step: 0.1
},
{
key: "ph",
min: 0,
max: 14,
step: 0.1
},
{
key: "ec",
min: 0,
max: 10,
step: 0.1
},
{
key: "nitrogen",
min: 0,
max: 500,
step: 1
},
{
key: "phosphorus",
min: 0,
max: 500,
step: 1
},
{
key: "potassium",
min: 0,
max: 500,
step: 1
}
];

다음 함수는 나머지 데이터는 그대로 유지하면서 하나의 측정값을 업데이트합니다:

function changeReading(key, value) {
setDraftReadings((current) => ({
...current,

[key]: {
...current[key],
value: value
}
}));
}

이러한 사용자 지정 필드는 데모 모드에서만 표시됩니다. 라이브 모드에서는 ESP32에서 값이 직접 전달됩니다.

ESP32 주소 준비

사용자는 Serial Monitor에 표시된 숫자 주소만 입력하면 됩니다.

예:

192.168.1.105

웹사이트는 이를 자동으로 다음과 같이 변환합니다:

http://192.168.1.105/data

이는 다음 함수를 사용하여 처리됩니다:

function normalizedDeviceUrl() {
const clean = ipAddress
.trim()
.replace(/\/$/, "");

if (!clean) {
return "";
}

const base =
/^https?:\/\//i.test(clean)
? clean
: "http://" + clean;

if (/\/data$/i.test(base)) {
return base;
}

return base + "/data";
}

또한 완전한 엔드포인트가 이미 입력된 경우 /data가 두 번 추가되는 것도 방지합니다.

ESP32에서 데이터 수신

라이브 모드에서는 웹사이트가 ESP32에 HTTP 요청을 보내고 JSON 응답을 기다립니다.

async function fetchLiveReadings() {
const url = normalizedDeviceUrl();

if (!url) {
setDeviceStatus("error");
return false;
}

setDeviceStatus("testing");

try {
const response = await fetch(url, {
headers: {
Accept: "application/json"
},

signal:
AbortSignal.timeout(6000)
});

if (!response.ok) {
throw new Error(
"Device unavailable"
);
}

const data =
await response.json();

const next = {
...sensorData
};

Object.keys(sensorData).forEach(
(key) => {
if (data[key] !== undefined) {
next[key] = {
...sensorData[key],
value: String(data[key])
};
}
}
);

setReadings(next);
setDeviceStatus("connected");

return true;
} catch (error) {
setDeviceStatus("error");
return false;
}
}

ESP32는 다음 형식으로 데이터를 반환합니다:

{
"moisture": 34.2,
"temperature": 26.4,
"ph": 6.9,
"ec": 1.2,
"nitrogen": 42,
"phosphorus": 38,
"potassium": 156,
"sensorConnected": true,
"wifiConnected": true
}

웹사이트는 대시보드에 처음 7개 필드를 사용합니다. 연결 필드는 주로 장치를 테스트하고 진단할 때 유용합니다.

분석 시작

두 모드 모두에서 동일한 분석 버튼이 작동합니다.

async function runAnalysis() {
if (mode === "live") {
const connected =
await fetchLiveReadings();

if (!connected) {
return;
}
}

setStep(0);
setScreen("analyzing");
}

데모 모드에서는 웹사이트가 사용자 지정 값을 사용하여 즉시 애니메이션을 시작합니다. 라이브 모드에서는 먼저 ESP32 연결을 확인한 다음 새로운 측정값을 다운로드합니다.

측정 애니메이션 만들기

애니메이션은 토양 센서가 실제로 수행한 측정 과정을 따릅니다.

const analysisSteps = [
"Connecting to Soil Analyzer",
"Reading moisture sensor",
"Measuring soil temperature",
"Measuring soil pH",
"Checking electrical conductivity",
"Reading nitrogen",
"Reading phosphorus",
"Reading potassium",
"Generating soil report"
];

타이머가 목록을 순서대로 진행한 후 대시보드를 엽니다:

useEffect(() => {
if (screen !== "analyzing") {
return;
}

if (step >= analysisSteps.length) {
const finished =
window.setTimeout(() => {
setScreen("dashboard");
}, 700);

return () =>
window.clearTimeout(finished);
}

const timer =
window.setTimeout(() => {
setStep((current) =>
current + 1
);
}, step === 0 ? 650 : 430);

return () =>
window.clearTimeout(timer);
}, [screen, step]);

이 애니메이션은 실제 센서 통신을 지연시키지 않습니다. 라이브 모드에서는 먼저 데이터를 요청하고, 보고서가 준비되는 동안 애니메이션이 명확한 피드백을 제공합니다.

작물 보고서 업데이트

선택한 작물은 상태 변수에 저장됩니다:

const [crop, setCrop] =
useState("tomato");

const selected = crops[crop];

작물 버튼을 누르면 웹사이트가 해당 값을 변경합니다:

function selectCrop(cropKey) {
setCrop(cropKey);
}

호환성 백분율, 적정 범위, 상태 메시지 및 권장 사항은 모두 선택한 작물 객체에서 값을 읽습니다. 그러면 React가 페이지를 새로 고치지 않고 보고서를 자동으로 업데이트합니다.

현재 프로토타입에서는 작물 프로필을 로컬에 저장합니다. 따라서 외부 식물 데이터베이스나 인터넷 연결 없이도 웹사이트를 사용할 수 있습니다.

데이터 내보내기

데이터 내보내기 버튼은 작동 모드, 측정값 및 선택한 작물을 포함하는 JSON 파일을 생성합니다.

function exportData() {
const report = {
mode: mode,
sensorData: readings,
crop: selected
};

const file = new Blob(
[
JSON.stringify(
report,
null,
2
)
],
{
type: "application/json"
}
);

const link =
document.createElement("a");

link.href =
URL.createObjectURL(file);

link.download =
"dirt-nerd-" +
crop +
".json";

link.click();

URL.revokeObjectURL(
link.href
);
}

PDF 버튼에는 브라우저의 인쇄 대화 상자를 사용했습니다:

function downloadReport() {
window.print();
}

사용자는 인쇄 창에서 Save as PDF 를 선택할 수 있습니다. 별도의 인쇄 스타일시트는 내비게이션 버튼과 기타 불필요한 컨트롤을 숨깁니다.

모바일 최적화

데스크톱 버전에서는 페이지 전체에 7개의 측정값이 표시됩니다. 태블릿에서는 레이아웃이 4열로 변경됩니다.

@media (max-width: 1100px) {
.sensor-grid {
grid-template-columns:
repeat(4, 1fr);
}

.crop-picker {
grid-template-columns:
repeat(5, 1fr);
}

.report-grid {
grid-template-columns: 1fr;
}
}

휴대폰에서는 각 측정값이 작고 가로로 긴 카드가 됩니다:

@media (max-width: 700px) {
.dash-shell {
width: calc(100% - 24px);
padding-top: 28px;
}

.sensor-grid {
grid-template-columns: 1fr;
gap: 8px;
}

.sensor-card {
display: grid;

grid-template-columns:
38px 1fr auto;

grid-template-rows:
auto auto;

column-gap: 12px;
align-items: center;

padding: 13px 14px;
}

.sensor-card p,
.mini-gauge {
display: none;
}
}

작물 선택기는 가로로 스크롤할 수 있게 됩니다:

@media (max-width: 700px) {
.crop-picker {
display: flex;
overflow-x: auto;
gap: 8px;

scroll-snap-type:
x mandatory;
}

.crop-picker button {
min-width: 116px;
height: 66px;

scroll-snap-align:
start;
}
}

설정 패널도 하단 시트로 바뀌어 한 손으로 사용하기 더 편리해집니다:

@media (max-width: 700px) {
.modal-backdrop {
padding: 0;
align-items: end;
}

.settings-modal {
width: 100%;
max-height: 92vh;
overflow-y: auto;

border-radius:
24px 24px 0 0;
}

.mode-selector,
.ip-row {
grid-template-columns: 1fr;
}
}

웹사이트 실행

첨부된 웹사이트 ZIP 파일의 압축을 푼 후 프로젝트 폴더에서 터미널을 열고 종속 항목을 설치합니다:

npm install

로컬 개발 서버를 시작합니다:

npm run dev

웹사이트는 일반적으로 다음 주소에서 열립니다:

http://localhost:3000

라이브 모드를 사용하려면 컴퓨터와 ESP32가 동일한 Wi-Fi 네트워크에 연결되어 있어야 합니다. 설정을 열고 Live mode를 선택한 다음 숫자로 된 ESP32 주소를 입력하고 Test를 누릅니다.

현재 분석기에 직접 연결할 때는 localhost를 통해 인터페이스를 실행합니다. Vercel 배포는 HTTPS를 사용하는 반면 ESP32는 로컬 HTTP 엔드포인트를 제공하므로 일부 브라우저에서는 이 조합을 차단합니다. Localhost를 사용하면 이 문제를 피할 수 있으며 현재 버전에서 가장 안정적인 설정입니다.




STEP 12. 직면한 문제들


센서 레지스터 이해하기

가장 큰 전자기기 문제는 7-in-1 RS485 센서들이 모두 정확히 동일한 레지스터 순서나 스케일링을 사용하지 않는다는 점이었습니다.

센서는 유효한 Modbus 응답을 반환할 수 있지만, pH, EC 또는 NPK 값을 코드에서 사용된 레이아웃과 다른 레지스터에 배치할 수 있습니다.

해결책은 레지스터 주소와 스케일링을 하나의 함수에 유지하여 센서 매뉴얼을 확인한 후 변경할 수 있도록 하는 것이었습니다.

RS485 A 및 B 라벨링

RS485 A 및 B 라벨은 제조업체 간에 항상 일관되지 않습니다.

배선과 설정이 올바른 것처럼 보이지만 센서가 응답하지 않는 경우, A와 B를 한 번 바꾸는 것이 유용한 테스트입니다.

로직 레벨 차이

XIAO는 3.3V GPIO를 사용하는 반면, 전통적인 MAX485 모듈은 일반적으로 5V에서 작동합니다.

RO 출력을 ESP32에 직접 연결하면 입력이 손상될 수 있습니다. 모듈이 전기적으로 호환된다고 가정하는 대신 레벨 시프팅으로 이를 고려했습니다.

IP 주소 변경

ESP32의 로컬 IP는 라우터가 재연결될 때 변경될 수 있습니다.

이는 웹사이트에 저장된 주소가 장치가 연결되어 있음에도 불구하고 결국 작동을 멈출 수 있음을 의미합니다.

테스트 버튼은 이를 빠르게 식별하는 데 도움이 됩니다. 라우터에서 DHCP 예약을 설정하면 주소를 고정할 수도 있습니다.

로컬 HTTP 및 Vercel HTTPS

웹사이트는 Vercel에서 데모로 잘 작동하지만, ESP32와의 직접 통신은 더 복잡합니다.

Vercel은 HTTPS를 사용하는 반면, ESP32는 로컬 네트워크 내에서 HTTP 주소를 제공합니다. 브라우저는 HTTPS 페이지가 그 불안전한 로컬 엔드포인트에 요청하는 것을 차단할 수 있습니다.

현재 빌드에서는 로컬호스트에서 웹사이트를 실행하는 것이 가장 신뢰할 수 있는 솔루션입니다.

모바일 대시보드 레이아웃

원래의 데스크탑 스타일 카드가 휴대폰에서 너무 좁아졌습니다.

글꼴 크기를 줄이는 것만으로는 부족하여, 센서 카드를 컴팩트한 수평 행으로 변경하고 크롭 선택기를 수평으로 스크롤 가능하게 만들었습니다.

이렇게 하여 가독성을 유지하고 야외에서 조작하기 쉽게 만들었습니다.

인클로저 여유 공간

곡선형 인클로저는 예상보다 가장자리 근처에 여유 공간이 적었습니다.

전자기기를 배치할 때 나사 기둥, USB 커넥터 및 케이블 굴곡에 주의해야 했습니다.

삼색 PLA 마감

삼색 필라멘트는 제가 예상한 외관을 생성하지 않았습니다.

출력을 버리는 대신, 샌딩하고 페인팅했습니다. 초록색 마감이 프로젝트와 훨씬 더 잘 어울리게 되었습니다.




STEP 13. 장치 테스트


장치 테스트


장치 테스트


테스트를 통신, 센서 응답 및 웹사이트 테스트로 나누었습니다.

먼저 대시보드를 열지 않은 상태에서 장치에 전원을 공급했습니다.

WS2812는 처음에 빨간색으로 켜졌고, ESP32가 Wi-Fi에 연결된 후 녹색으로 바뀌었습니다.

보드레이트 115200으로 시리얼 모니터를 열고, 보드가 로컬 IP 주소를 출력하는지 확인했습니다.

그런 다음 브라우저에서 다음 주소를 열었습니다:

http://ESP32-IP/data

이를 통해 ESP32가 JSON을 반환하고 7개의 측정 필드가 모두 존재하는 것을 확인했습니다.

다음으로 프로브를 토양에 넣고 토양 상태가 변할 때 측정값이 합리적인 방향으로 변하는지 관찰했습니다.

이 단계의 주요 목적은 실험실 수준의 정확성을 주장하는 것이 아니라, 명백히 잘못된 레지스터 매핑, 스케일링 또는 통신 오류를 찾는 것이었습니다.

그런 다음 localhost에서 웹사이트를 시작하고 Live 모드를 선택한 후 동일한 IP 주소를 입력했습니다.

Test 버튼으로 연결을 확인했고, 분석을 시작하자 측정값이 대시보드 카드로 전송되었습니다.

페이지를 새로 고치지 않아도 호환성 메시지, 비교표 및 권장 사항이 업데이트되는지 확인하기 위해 사용 가능한 작물 사이를 전환했습니다.

또한 데스크톱 및 휴대폰 화면 너비에서 인터페이스를 테스트했습니다.

센서 카드는 모바일 행으로 바뀌었고, 작물 옵션은 가로로 스크롤할 수 있게 되었으며, Settings 창은 하단 시트로 열렸습니다.

마지막으로 Export Data, Share Report 및 Download PDF 컨트롤을 테스트했습니다.

토양 센서의 측정값을 농업적으로 정확한 것으로 간주하려면 먼저 제조업체의 절차에 따라 센서를 보정해야 합니다.

보다 공식적인 비교를 위해 다음 단계에서는 문서화된 토양 샘플 여러 개를 테스트하고 Dirt Nerd의 측정값을 보정된 기준 기기의 측정값 또는 실험실 결과와 비교해야 합니다.




STEP 14. 최종 생각


최종 생각


최종 생각


최종 생각


최종 생각


Dirt Nerd는 ESP32에 연결된 센서 이상의 의미를 갖게 되었습니다.

제가 가장 즐겼던 부분은 전자기기, 펌웨어, 인터페이스 및 인클로저를 하나의 제품 같은 프로토타입으로 통합하는 것이었습니다.

이 프로젝트는 측정값을 표시하는 것이 작업의 절반에 불과하다는 것을 가르쳐 주었습니다. 사용자가 자신이 기르고자 하는 식물에 대한 의미를 이해할 수 있을 때 정보는 훨씬 더 유용해집니다.

저는 RS485 통신, Modbus 레지스터, 전압 레벨 차이, 로컬 HTTP API 및 반응형 인터페이스 디자인에 대해 많은 것을 배웠습니다.

인클로저 또한 예상치 못한 디자인 여정을 겪었습니다. 처음 상상했던 깨끗한 물방울 모양을 유지하지 않았지만, 약간 오징어 같은 최종 형태가 프로젝트를 더 인식 가능하게 만들었습니다.

미래 버전에서 개선하고 싶은 몇 가지 사항이 있습니다.

신뢰할 수 있는 기준 측정값에 대해 센서를 보정하고, 작물 데이터베이스를 확장하며, 모든 비교를 실시간 읽기에서 직접 계산하고, 펌웨어에 자격 증명을 저장하지 않고 Wi-Fi 설정을 위한 더 나은 방법을 추가하고 싶습니다.

원격 액세스를 위해서는 직접 로컬 HTTP 연결을 안전한 MQTT 또는 클라우드 기반 데이터 경로로 교체하고 싶습니다.

내부 전자기기 장착을 더 정리하고, 분석기가 별도의 전원 케이블 없이 사용될 수 있도록 충전식 배터리를 추가하고 싶습니다.

현재로서는 Dirt Nerd가 토양을 측정하고 결과를 표시하며 이를 더 이해하기 쉬운 식물별 보고서로 변환할 수 있는 완전한 프로토타입으로 작동합니다.

자신만의 버전을 만들 경우, 정확한 토양 센서에 대한 매뉴얼을 확인하고, 전압 레벨을 안전하게 유지하며, 첫 번째 읽기 세트를 최종 답변이 아닌 보정의 시작으로 간주하십시오.

읽어 주셔서 감사합니다. 이 프로젝트가 여러분이 자신만의 연결된 토양 분석기를 만드는 데 유용한 출발점이 되기를 바랍니다.

회사명 공방유니언 주소 각 공방 주소 참조
사업자 등록번호 0000 대표 공방유니언 전화 019-60105846 팩스 0000 이메일 gongbangunion@gmail.com
통신판매업신고번호 0000 개인정보관리책임자 공방유니언
Copyright © 2017 공방유니언. All Rights Reserved.