[Arduino] 애견 간식 자동급식기 제작

가끔 집을 오래 비우거나 외식을 나가면 우리 쭈쭈에 대한 걱정이 된다 ㅠㅠ
봄 가을에는 데리고 나가기도 하지만 여름 겨울에는 그마저도 쉽지 않을때가 있다 ㅠ
그래서 원격으로 제어되는 쭈쭈를 위한 자동급식기를 만들기로 했다.

1. 먹이 입출입구 자동문 달기
(너무 저렴한 서보모터를 사용했기 때문일까.. 아니면 안쪽에 달았기 때문일까.. 간식을 그득그득 담아놓으면 무게때문에 서보모터가 제대로 동작하지 않음이 발생했다..)

2.  적외선 센서 달기. 
(먹이의 투하량을 알 수 없기때문에 고점의 높이를 인식하여 항상 같은량을 투하하고자 했다) 

3. 수동으로 동작하기 위한 버튼도 달고, WIFI가 지원되는 NCU 미니보드+샤오미 보조배터리로 목업 모델이 완성된 모습 
(배터리 효율을 위하여 ESP8266 미니보드를 사용하였다)

 

로직구성
WIFI를 통하여 미리 만들어놓은 홈네트워킹 서버에 Get Request를 일정 간격으로 계속 찌른다.
받아온 특정 Status값이 “Open” 일때,  서보모터를 열었다 닫았다 반복시킨다. (적외선 센서로 감지된 내용물 높이가 줄어들때 까지)
Status는 다시 “Close”상태로 변경한다.

아두이노 코드
(서버는 스프링 부트로 구성했으며, 별 내용없으므로 소스 생략)

#include <SPI.h>
#include <Adafruit_CC3000.h>
#include <ccspi.h>
#include <Servo.h>
#include <string.h>
#include "utility/debug.h"

char ssid[] = "htrucciAP";      // your network SSID (name)
char pass[] = "1234";   // your network password
int keyIndex = 0;                 // your network key Index number (needed only for WEP)
// Config the interrupt and control pins on Wido
#define ADAFRUIT_CC3000_IRQ   7
#define ADAFRUIT_CC3000_VBAT  5
#define ADAFRUIT_CC3000_CS    10
#define TCP_TIMEOUT      3000
static unsigned long uploadtStamp = 0;
Adafruit_CC3000 cc3000 = Adafruit_CC3000(ADAFRUIT_CC3000_CS, ADAFRUIT_CC3000_IRQ, ADAFRUIT_CC3000_VBAT, SPI_CLOCK_DIVIDER); // you can change this clock speed but DI
#define WLAN_SSID       "htrucciAP"        // cannot be longer than 32 characters!
#define WLAN_PASS       "1234"     // 
// Test server configuration
const uint8_t   SERVER_IP[4]   = { 192, 168, 1, 101 };
const uint16_t  SERVER_PORT    = 8000;// Test server configuration
#define WLAN_SECURITY   WLAN_SEC_WPA2
#define WEBSITE  "htrucci.com"
Servo myservo;  // create servo object to control a servo
const int buttonPin = 13;     // the number of the pushbutton 
const int servoPin = 12;     // the number of the pushbutton 
int pos = 0;    // variable to store the servo position
int buttonState = 0;         // variable for reading the pushbutton status

void setup() {
  Serial.begin(115200);
   while (!Serial) {
    ; // wait for serial port to connect. Needed for Leonardo only
  }
  Serial.println(F("Hello, CC3000!\n")); 
  
  
  displayDriverMode();
  // Measure the free Ram
  Serial.print("Free RAM: "); Serial.println(getFreeRam(), DEC); 
  
  /* Initialise the module */
  Serial.println(F("\nInitialising the CC3000 ..."));
  if (!cc3000.begin())
  {
    Serial.println(F("Unable to initialise the CC3000! Check your wiring?"));
    while(1);
  }

  uint16_t firmware = checkFirmwareVersion();
  if (firmware < 0x113) {
    Serial.println(F("Wrong firmware version!"));
    for(;;);
  } 
  
  displayMACAddress();
  
  /* Optional: Get the SSID list (not available in 'tiny' mode) */
#ifndef CC3000_TINY_DRIVER
  listSSIDResults();
#endif
  
  /* Delete any old connection data on the module */
  Serial.println(F("\nDeleting old connection profiles"));
  if (!cc3000.deleteProfiles()) {
    Serial.println(F("Failed!"));
    while(1);
  }

  /* Attempt to connect to an access point */
  char *ssid = WLAN_SSID;             /* Max 32 chars */
  Serial.print(F("\nAttempting to connect to ")); Serial.println(ssid);
  
  /* NOTE: Secure connections are not available in 'Tiny' mode!
     By default connectToAP will retry indefinitely, however you can pass an
     optional maximum number of retries (greater than zero) as the fourth parameter.
  */
  if (!cc3000.connectToAP(WLAN_SSID, WLAN_PASS, WLAN_SECURITY)) {
    Serial.println(F("Failed!"));
    while(1);
  }
   
  Serial.println(F("Connected!"));
  
  /* Wait for DHCP to complete */
  Serial.println(F("Request DHCP"));
  while (!cc3000.checkDHCP())
  {
    delay(100); // ToDo: Insert a DHCP timeout!
  }  

  /* Display the IP address DNS, Gateway, etc. */  
  while (! displayConnectionDetails()) {
    delay(1000);
  }
  
  /* You need to make sure to clean up after yourself or the CC3000 can freak out */
  /* the next time you try to connect ... */
  Serial.println(F("\n\nClosing the connection"));
  //cc3000.disconnect();  
    
  myservo.attach(servoPin);  // attaches the servo on pin 9 to the servo object
  pinMode(buttonPin, INPUT);
  Serial.begin(115200);
  delay(10);

}
uint32_t ip = 0;
float temp = 0;
void loop() {
buttonState = digitalRead(buttonPin);
  myservo.write(130); 
  delay(500);
  myservo.detach();
  static Adafruit_CC3000_Client WidoClient;
  static unsigned long RetryMillis = 0; 
  if(!WidoClient.connected() && millis() - RetryMillis > TCP_TIMEOUT){
    // Update the time stamp
    RetryMillis = millis();

    Serial.println(F("Try to connect the cloud server"));
    WidoClient.close();

    // Get Yeelink IP address
    Serial.print(F("htrucci.com -> "));
    while  (ip  ==  0)  {
      if  (!cc3000.getHostByName(WEBSITE, &ip))  {    //  Get the server IP address based on the domain name
        Serial.println(F("Couldn't resolve!"));
      }
      delay(500);
    }  
    cc3000.printIPdotsRev(ip);
    Serial.println(F(""));
    
    // Connect to the Yeelink Server
    WidoClient = cc3000.connectTCP(ip, 1001);          // Try to connect cloud server
  }
  if(WidoClient.connected() && millis() - uploadtStamp > 2000){
    uploadtStamp = millis();
    // If the device is connected to the cloud server, upload the data every 2000ms.
    
    // Prepare Http Package for Yeelink & get length
    int length = 0;
    char lengthstr[3];
    
    // Create Http data package
    char httpPackage[60] = "";
    
    strcat(httpPackage,"{\"value\":");
    itoa(temp,httpPackage+strlen(httpPackage),10);          // push the data(temp) to the http data package
    strcat(httpPackage,"}");
    
    length = strlen(httpPackage);                           // get the length of data package
    itoa(length,lengthstr,10);                              // convert int to char array for posting
    Serial.print(F("Length = "));
    Serial.println(length);
    
    Serial.println(F("Connected to Yeelink server."));
    
    // Send headers
    Serial.print(F("Sending headers"));
    
    WidoClient.fastrprint(F("POST /v1.0/device/"));
    WidoClient.fastrprint(F("100/sensor/20/datapoints"));  //Please change your device ID and sensor ID here, after creating
                                                           //Please check the link: http://www.yeelink.net/user/devices
                                                           //The example URL: http://api.yeelink.net/v1.0/device/100/sensor/20/datapoints
    WidoClient.fastrprintln(F(" HTTP/1.1"));
    Serial.print(F("."));
    
    WidoClient.fastrprintln(F("Host: api.yeelink.net"));
    Serial.print(F("."));
    
    WidoClient.fastrprint(F("U-ApiKey: "));
//    WidoClient.fastrprintln(API_key);
    Serial.print(F("."));
    
    WidoClient.fastrprint("Content-Length: "); 
    WidoClient.fastrprintln(lengthstr);
    WidoClient.fastrprintln("");
    Serial.print(F("."));
    
    Serial.println(F(" done."));
    
    // Send data
    Serial.print(F("Sending data"));
    WidoClient.fastrprintln(httpPackage);

    Serial.println(F(" done."));
    
    /********** Get the http page feedback ***********/
    
    unsigned long rTimer = millis();
    Serial.println(F("Reading Cloud Response!!!\r\n"));
    while (millis() - rTimer < 2000) {
      while (WidoClient.connected() && WidoClient.available()) {
        char c = WidoClient.read();
        Serial.print(c);
      }
    }
    delay(1000);             // Wait for 1s to finish posting the data stream
    WidoClient.close();      // Close the service connection
  
    RetryMillis = millis();  // Reset the timer stamp for applying the connection with the service
  }

  if (buttonState == HIGH) {
    for (int i=0; i<=5; i++){
      myservo.attach(servoPin);
      myservo.write(50);              // tell servo to go to position in variable 'pos'
      delay(500);
      myservo.detach();
      //myservo.attach(10);
      delay(3000);                       // waits 15ms for the servo to reach the position
      myservo.attach(servoPin);
      myservo.write(130);              // tell servo to go to position in variable 'pos'myservo.write(150);              // tell servo to go to position in variable 'pos'
      delay(500);
      myservo.detach();
    }
  }    
    
}
/**************************************************************************/
/*!
    @brief  Displays the driver mode (tiny of normal), and the buffer
            size if tiny mode is not being used

    @note   The buffer size and driver mode are defined in cc3000_common.h
*/
/**************************************************************************/
void displayDriverMode(void)
{
  #ifdef CC3000_TINY_DRIVER
    Serial.println(F("CC3000 is configure in 'Tiny' mode"));
  #else
    Serial.print(F("RX Buffer : "));
    Serial.print(CC3000_RX_BUFFER_SIZE);
    Serial.println(F(" bytes"));
    Serial.print(F("TX Buffer : "));
    Serial.print(CC3000_TX_BUFFER_SIZE);
    Serial.println(F(" bytes"));
  #endif
}

/**************************************************************************/
/*!
    @brief  Tries to read the CC3000's internal firmware patch ID
*/
/**************************************************************************/
uint16_t checkFirmwareVersion(void)
{
  uint8_t major, minor;
  uint16_t version;
  
#ifndef CC3000_TINY_DRIVER  
  if(!cc3000.getFirmwareVersion(&major, &minor))
  {
    Serial.println(F("Unable to retrieve the firmware version!\r\n"));
    version = 0;
  }
  else
  {
    Serial.print(F("Firmware V. : "));
    Serial.print(major); Serial.print(F(".")); Serial.println(minor);
    version = major; version <<= 8; version |= minor;
  }
#endif
  return version;
}

/**************************************************************************/
/*!
    @brief  Tries to read the 6-byte MAC address of the CC3000 module
*/
/**************************************************************************/
void displayMACAddress(void)
{
  uint8_t macAddress[6];
  
  if(!cc3000.getMacAddress(macAddress))
  {
    Serial.println(F("Unable to retrieve MAC Address!\r\n"));
  }
  else
  {
    Serial.print(F("MAC Address : "));
    cc3000.printHex((byte*)&macAddress, 6);
  }
}


/**************************************************************************/
/*!
    @brief  Tries to read the IP address and other connection details
*/
/**************************************************************************/
bool displayConnectionDetails(void)
{
  uint32_t ipAddress, netmask, gateway, dhcpserv, dnsserv;
  
  if(!cc3000.getIPAddress(&ipAddress, &netmask, &gateway, &dhcpserv, &dnsserv))
  {
    Serial.println(F("Unable to retrieve the IP Address!\r\n"));
    return false;
  }
  else
  {
    Serial.print(F("\nIP Addr: ")); cc3000.printIPdotsRev(ipAddress);
    Serial.print(F("\nNetmask: ")); cc3000.printIPdotsRev(netmask);
    Serial.print(F("\nGateway: ")); cc3000.printIPdotsRev(gateway);
    Serial.print(F("\nDHCPsrv: ")); cc3000.printIPdotsRev(dhcpserv);
    Serial.print(F("\nDNSserv: ")); cc3000.printIPdotsRev(dnsserv);
    Serial.println();
    return true;
  }
}

/**************************************************************************/
/*!
    @brief  Begins an SSID scan and prints out all the visible networks
*/
/**************************************************************************/

void listSSIDResults(void)
{
  uint8_t valid, rssi, sec, index;
  char ssidname[33]; 

  index = cc3000.startSSIDscan();

  Serial.print(F("Networks found: ")); Serial.println(index);
  Serial.println(F("================================================"));

  while (index) {
    index--;

    valid = cc3000.getNextSSID(&rssi, &sec, ssidname);
    
    Serial.print(F("SSID Name    : ")); Serial.print(ssidname);
    Serial.println();
    Serial.print(F("RSSI         : "));
    Serial.println(rssi);
    Serial.print(F("Security Mode: "));
    Serial.println(sec);
    Serial.println();
  }
  Serial.println(F("================================================"));

  cc3000.stopSSIDscan();
}/**************************************************************************/
/*!
    @brief  Displays the driver mode (tiny of normal), and the buffer
            size if tiny mode is not being used

    @note   The buffer size and driver mode are defined in cc3000_common.h
*/
/**************************************************************************/
문제점
– 성능이 약한 서보모터를 안쪽으로 달았더니 무게에 눌려 제대로 작동하지 않았다.
– 내용물이 빠져나오기에는 구멍이 턱없이 작아, 서보를 여러번 반복시켜 억지로 내용물을 꺼내야 했다. (그래서 간식을 잘라넣는 것으로 대체하고 간식값을 감당할 수 있게 조금만 떨어뜨리도록 했다)
– 적외선 센서가 바라보는 각도에 따라 오차가 발생하였다. (내용물을 채울때마다 뚜껑을 열고 닫았어야 하므로 뚜껑에 구멍을 뚫어 적외선 센서를 완전히 고정시키지 않는 한 각도는 계속 변화하였다)
쭈쭈가 겁이 많아 스스로 소리를 내며 움직이는 물체는 무서워 한다.

결과

 

<

p style=”text-align: center;”> 

You may also like...

4 Responses

  1. ㅈ2 댓글:

    홈네트워킹 서버 코드좀 알수있을까요
    현재 EPS8266으로 타이머기능이랑 와이파이 통신으로 원거리에서 밥을 주는 기능을 하려고하는데 어려움이 있어 도움 요청합니다 ㅠㅠ

    • htrucci 댓글:

      서버쪽은 정말 별게없어서.. 아주 간단한 스프링 restController이고
      내부적으로 status를 갖고있고, 아두이노에서 getStatus 를 주기로 호출하다가 외부에서 url호출등으로 status값 바꾸면
      그 값을 읽어갔을때 동작하는 식입니다.

  2. 예진 댓글:

    저도 이 프조렉트 만들고 싶은데 회로도는 어떻게 만들었나요?

    • htrucci 댓글:

      응답이 늦었네요;;
      회로도를 따로 그려놓지는 않았으나..
      서보모터, 적외선센서, 푸시버튼이 각각 핀에 연결되므로 복잡하지는 않습니다.
      동일 보드를 사용하신다면, 아래 핀에 대응하여 연결하시는게 인터럽트 개념을 파악하는 시간을 조금 절약 하실 수 있을 거라고 생각됩니다.
      const int buttonPin = 13; // the number of the pushbutton
      const int servoPin = 12; // the number of the pushbutton

답글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다.