본문 바로가기
개발/프로젝트

다육이 상태진단 앱 개발기- 아두이노를 이용한 물 준 날 기록

by 슴새 2021. 11. 10.
반응형

저희 팀의 졸업프로젝트 주제는 '딥러닝 기반 다육식물 홈 가드닝 가이드 어플' 입니다.  

개발 배경과 프로젝트 흐름도는 아래와 같습니다. 

개발배경
프로젝트 흐름도

저는 전반적인 개발 과정에 모두 참여하였습니다. 기획과 디자인은 물론이고 데이터수집, 딥러닝(kobert 챗봇과 cnn 상태진단), 챗봇 웹배포, 짜잘짜잘한 안드로이드 기능 구현 등을 맡았는데요. 하지만 제가 독점적으로 개발한 부분은 회원 관리와 아두이노를 이용한 물 준 날 기록이므로 이 부분에 대해 자세히 쓰도록 하겠습니다.  

 

다만,코드의 경우 모두 추가하면 글이 너무 길어져서 이 글에서는 핵심적인 부분만 서술하려고 합니다. 전체 코드에 대한 정보가 필요하시면 저희 프로젝트 깃허브 블로그를 방문해주시면 될 것 같습니다. 

https://github.com/Leafy-Your-new-buddy/Leafy

 

GitHub - Leafy-Your-new-buddy/Leafy: [캡스톤 디자인 프로젝트]딥러닝을 이용한 다육식물 상태 진단 앱

[캡스톤 디자인 프로젝트]딥러닝을 이용한 다육식물 상태 진단 앱. Contribute to Leafy-Your-new-buddy/Leafy development by creating an account on GitHub.

github.com

 

 

먼저 하드웨어적인 부분부터 준비하겠습니다. 아래 부품들이 필요합니다. 

  1. 아두이노 우노 보드
  2. 토양수분센서
  3. 빵판
  4. 블루투스 모듈 HC-06
  5. AB형 usb 케이블 
  6. 점퍼케이블

 

이후 위 회로도대로 연결합니다.

Q. 위 회로도대로 연결하면 5V에 케이블을 두 개 연결해야 되는데 어떡해야 하나요?

A. 5V-빵판에 케이블 연결 -> 해당 케이블을 통해 토양수분센서, 블루투스 양쪽 모두에 연결하면 됩니다.

이렇게 불이 잘 들어오면 테스트를 위한 준비가 모두 끝났습니다.

아두이노 IDE를 다운 받고 센서값을 읽고 블루투스를 통해 전송하는 코드를 적당히 작성합니다.

코드 작성 중 헷갈리기 쉬운 점이 있습니다. 바로 TX, RX 연결시 아두이노와 블루투스 모듈을 반대로 연결해야 한다는 것입니다. 이는 알고보면 매우 간단한데, T는 Transmit(데이터를 보내다)의 T이고 R은 Receive(데이터를 받다)의 R입니다. 그러므로 블루투스와 아두이노가 서로 데이터를 보내고 받는 짝이 맞으려면 교차해서 연결해야 합니다. 

 

#define BT_RXD 8
#define BT_TXD 7
SoftwareSerial bluetooth(BT_RXD,BT_TXD);

저는 7번, 8번 핀에 연결하였으므로 이렇게 작성했습니다.

코드 작성이 끝나면 상단의 업로드 버튼을 누르고, 업로드가 완료되면 툴-시리얼 모니터를 눌러 띄웁니다. 시리얼 모니터에 AT 입력-OK가 뜨면 성공입니다.  

 

참고로 무작정 센서값을 보내도록 코드를 작성하면 어플이 엄청나게 느려집니다...그래서 저는 유의미한 변화(2이상)이 없다면 1초에 한번씩 센서값을 보내도록 제한을 두었습니다.

스케치를 업로드하는 동안 에러가 발생했습니다.  라는 메세지가 뜨면서 업로드가 안될 수도 있는데요,

장치 관리자에서 확인을 해보니 USB-Serial에 불길한 노란 느낌표가 떠있었습니다.

USB2.0-Ser_HL340_CH341SER_WIN732_64.zip
0.13MB

혹시 같은 문제를 겪으시는 분들은 첨부된 파일을 받아서 Setup_64 실행 후 드라이버를 설치해주시면 됩니다.

이제 포트 연결이 잘 되었네요.  

+

만약 USB-Serial에 노란 느낌표가 아니라 알 수 없는 장치 라고 뜨시는 분들은 오른쪽 마우스 - 드라이버 소프트웨어 업데이트를 진행하면 됩니다. 아두이노 스케치에서도 포트가 잘 연결되었는지 확인도 필수입니다. 

업로드- AT입력시 OK가 뜨는 것까지 확인했다면 센서값 측정, 블루투스 연결이 잘 되는지 확인하기 위해 위 어플을 핸드폰에 설치합니다.

어플을 실행한 뒤 아두이노의 블루투스 모듈을 연결합니다. 저는 Arduino라는 이름으로 검색이 되고 있는데, 이름 관련 설정을 하지 않으신 분들은 HC-06이라고 검색이 될 것입니다. 아무튼 이걸 선택해 terminal mode로 열면 아두이노에서 계속해서 센서값을 잘 보내고 있는 것을 볼 수 있습니다. 하단의 type in command에 뭔가를 입력해서 보내면, 그 내용이 아두이노의 시리얼 모니터에 뜨는 것도 확인할 수 있습니다.

 

이제 안드로이드 스튜디오를 켜서 물 준 날 기록을 본격적으로 개발하도록 하겠습니다.

파이어베이스를 이용한 로그인,로그아웃을 구현했다면 파이어베이스에 저장될 유저 객체 클래스가 있을텐데, 그 클래스에 아래와 같은 함수, 변수를 추가해줍니다.(파이어베이스 로그인/로그아웃의 경우 잘 정리된 튜토리얼이 매우 많으므로 자세한 설명은 생략하겠습니다.)

   public List<String> waterDate;
   public boolean checkwaterDate(String newDate){
        String date;
        if( waterDate.isEmpty() || waterDate.size()==1){
            date= "2000-01-01 00:00:00";
        }
        else date= waterDate.get(waterDate.size()-1);

        date=date.substring(0,10);
        String tempDate=newDate.substring(0,10);
        if(date.equals(tempDate)) return false;
        else return true;

    }

    public void addwaterDate(String date){
        waterDate.add(date);
    }
    public void setwaterDate(){
        waterDate=new ArrayList();
        waterDate.add("first");
    }
    public String getwaterDate(int idx){
        if(waterDate.get(idx).equals("first")){
            return "0000-00-00 00";
        }
        else{
            String date=waterDate.get(idx).substring(0,10);
            return date;
        }

    }

setWaterDate는 처음 회원가입할때 불리는 함수로, waterDate 리스트를 처음 만들때 "first"라는 값을 임의로 넣어주어 오류가 없도록 했습니다. 이는 초기화 오류를 피하기 위한 값이라, 유저 인터페이스에는 전혀 보이지 않도록 할 예정입니다. 

 

이제 블루투스를 사용해  센서값을 읽어오는 코드를 작성합니다. 아래 부분은 블루투스 핸들러로 블루투스 연결 뒤 수신된 데이터를 읽어와 ReceiveData 텍스트 뷰에 표시해주는 코드입니다. 

 mBluetoothHandler = new Handler(){
            public void handleMessage(android.os.Message msg){
                if(msg.what == BT_MESSAGE_READ){
                    String readMessage = null;
                    try {
                        readMessage = new String((byte[]) msg.obj, "UTF-8");
                    } catch (UnsupportedEncodingException e) {
                        e.printStackTrace();
                    }
                    mTvReceiveData.setText(readMessage);
                }
            }
        };

위 코드를 기반으로, 센서값이 오르는 시점에 auto_watering함수를 호출하도록 하는 코드를 추가합니다.  auto_watering함수는 호출된 시점의 날짜를 파이어베이스의 waterDateList에 추가하는 함수입니다. 읽어온 메세지를 소량 저장해서, 흙의 수분량이 급격히 오르는 순간이 있다면 물을 줬다고 판단, auto_watering 함수를 호출하도록 구현했습니다.

 

 

UserAccount name =  snapshot.child("UserAccount").child(uid).getValue(UserAccount.class);
                if(name.checkwaterDate(getTime)){
                    try{
                        name.addwaterDate(getTime);
                        mDatabaseRef.child("UserAccount").child(uid).setValue(name);
                        Toast.makeText(getApplicationContext(),"물을 줬습니다.",Toast.LENGTH_SHORT).show();
                    }catch(Exception e){
                        Toast.makeText(getApplicationContext(),e.getMessage(),Toast.LENGTH_SHORT).show();
                    }

                }
                else {
                    Toast.makeText(getApplicationContext(),"물을 줬습니다.(중복)",Toast.LENGTH_SHORT).show();
                }

auto_watering 함수의 핵심 코드입니다. 날짜를 기록하므로, 같은 날 두 번 물을 주는 경우에는 파이어베이스에 추가하지 않습니다. 

 

 private class ConnectedBluetoothThread extends Thread {
        private final BluetoothSocket mmSocket;
        private final InputStream mmInStream;
        private final OutputStream mmOutStream;



        public ConnectedBluetoothThread(BluetoothSocket socket) {
            mmSocket = socket;
            InputStream tmpIn = null;
            OutputStream tmpOut = null;

            try {
                tmpIn = socket.getInputStream();
                tmpOut = socket.getOutputStream();
            } catch (IOException e) {
                Toast.makeText(getApplicationContext(), "소켓 연결 중 오류가 발생했습니다.", Toast.LENGTH_LONG).show();
            }

            mmInStream = tmpIn;
            mmOutStream = tmpOut;
        }
        public void run() {
            byte[] buffer = new byte[1024];
            int bytes;

            while (true) {
                try {
                    bytes = mmInStream.available();
                    if (bytes != 0) {


                        SystemClock.sleep(100);
                        bytes = mmInStream.available();
                        bytes = mmInStream.read(buffer, 0, bytes);
                        mBluetoothHandler.obtainMessage(BT_MESSAGE_READ, bytes, -1, buffer).sendToTarget();

                        // mTvReceiveData.setText(Integer.toString(humid));//추가


                    }
                } catch (IOException e) {
                    break;
                }
            }
        }
        public void write(String str) {
            byte[] bytes = str.getBytes();
            try {
                mmOutStream.write(bytes);
            } catch (IOException e) {
                Toast.makeText(getApplicationContext(), "데이터 전송 중 오류가 발생했습니다.", Toast.LENGTH_LONG).show();
            }
        }
        public void cancel() {
            try {
                mmSocket.close();
            } catch (IOException e) {
                Toast.makeText(getApplicationContext(), "소켓 해제 중 오류가 발생했습니다.", Toast.LENGTH_LONG).show();
            }
        }
    }

추가로,핸들러가 메세지를 받아오는 부분은 위와 같이 작성했습니다. 이렇게 하면 메인쓰레드는 자기 할 일을 하고, 동시에 블루투스 쓰레드는 값을 받아올 수 있습니다. (메인쓰레드가 두 가지 일을 모두 한다면 멈춰버리며 강종될 수도 있습니다.)

 

 

이제 수분센서를 화분에 꽂은 상태로 물을 주면, 파이어베이스에 다음과 같은 형태로 물을 준 날짜가 리스트로 들어가게 됩니다. 이 물 준 날 리스트는 유저별로 각자 가지고 있는 값이기 때문에 유저 개인별 물 준 날 관리가 가능합니다. 

 

이렇게 저장한 물 준 날을 달력에 표시하기 위해 달력 빈 틀을 만들어줍니다. 저는 선택 날짜에 테두리가 둘러지게 구현했습니다. 

어떤 식으로 구현했던 일단 달력 틀을 만들었다면, 메인이 될 달력 화면과 Adapter, Utils, ViewHolder로 코드가 구성될 것입니다. xml의 경우 달력 화면.xml, 달력셀.xml이 있게 될 것입니다. 제 경우에는 각각

CalendarFragment.java
calendarAdapter.java
CalendarUtils.java
CalendarViewHolder.java
fragment_calendar.xml
calerdar_cell.xml

라고 이름을 붙였습니다. 

먼저 calerdar_cell.xml에 가로로 긴 색깔 슬롯을 두어 물 준 날의 경우 이 슬롯이 파란색으로 채워지고, 물 준 날이라는 텍스트가 쓰여지도록 하겠습니다. 후에 기록한 날도 표시가 되어야하니, 색깔 슬롯을 두개 만들어주겠습니다. 물을 주지 않은 날은 아무것도 표시가 안 되어야 하니 일단 색깔은 흰 색으로 해주겠습니다. 

날짜들은 Adapter의 onBindViewHolder에서 보여지게 되는데요, 여기서 파이어베이스에 있는 물 준 날과 일치하는지 확인, 일치하면 색깔 슬롯(textview)의 색깔이 변하도록 코드를 작성하면 됩니다. 

for(int i=0;i<value.getwaterDateSize();i++){
                    String water=value.getwaterDate(i);

                    if(water.equals(date.toString())){

                        if(!holder.calText1.getText().equals("기록한 날")){
                            holder.calText1.setBackgroundColor(Color.parseColor("#D4F4FA"));
                            holder.calText1.setText(" 물 준 날 ");
                        }
                        else {
                            holder.calText2.setBackgroundColor(Color.parseColor("#D4F4FA"));
                            holder.calText2.setText(" 물 준 날 ");
                        }
                    }
                }

 if문의 경우, 같은 날 상태 기록, 물주기를 모두 한 경우를 대비해서 작성하였습니다.

https://tv.kakao.com/channel/3820511/cliplink/423806485

이제 원하던 기능이 모두 제대로 작동하는 것을 확인할 수 있습니다. 

 

참고!https://bugwhale.tistory.com/11

반응형

'개발 > 프로젝트' 카테고리의 다른 글

개인 토이 프로젝트- 레시피 앱  (0) 2022.09.19

댓글