Ironman – 아두이노와 구글 음성인식 프로젝트 공유

이전글에서 소개한 아두이노 프로젝트 공유

구글의 음성인식 서비스를 이용해서 집안의 전자 제품을 제어함.

Arduino 부품 구성

1366606244729m0
아두이노 UNO
1435213141307m0
블루투스 모듈 (HC-06)
TA189_m
모션 인식 센서
41VwSSx
릴레이

Arduino 회로도

Circuit

안드로이드 앱
Github 소스
APK Download

아두이노 소스

#include <SoftwareSerial.h>

SoftwareSerial mySerial(11,12); // RX, TX

const int kDPinActivity = 2;
const int kDPinLight = 13;
const int kAPinBright = 5;

// Commands strings from remote controller
const String kCmdLightOn = "light on";
const String kCmdLightOff = "light off";
const String kCmdSetPrefix = "set:";

// Status string
const String kCmdPrefix = "command:";
const String kStatusActivityDetected = "activity detected";

const char kPacketDelimiter = '#';

// The base brightness to decide day.
int min_brightness_of_day = 10;
int max_brightness_of_night = 5;

void setup() {
  pinMode(kDPinLight, OUTPUT);
  pinMode(kDPinActivity, INPUT);
  pinMode(kAPinBright, INPUT);
  
  digitalWrite(kDPinLight, HIGH); // Trun off
  
  Serial.begin(9600);
  Serial.println("Start");
  mySerial.begin(9600);
}

bool needLightOn(int activity, int brightness) {
  // the lower the value is, the brighter.
  if (brightness > max_brightness_of_night) {
    // during the day
    return false;
  }
  if (activity) {
    return true;
  }
  return false;
}

bool needLightOff(int brightness) {
  if (brightness > min_brightness_of_day) {
    return true;
  }
  return false;
}

// command structure 
// [command name]:[value]
void processSettingCmd(String cmd) {
  const char* kBrightnessDayValue = "brightness day:";
    const char* kBrightnessNightValue = "brightness night:";
  
  if (cmd.startsWith(kBrightnessNightValue)) {
    String value = cmd.substring(String(kBrightnessNightValue).length());
    max_brightness_of_night = value.toInt();
  } else if (cmd.startsWith(kBrightnessDayValue)) {
    String value = cmd.substring(String(kBrightnessDayValue).length());
    min_brightness_of_day = value.toInt();
  }
}

void processCmd(String cmd) {
  if (cmd.equals(kCmdLightOn)) {
    digitalWrite(kDPinLight, LOW); // Turn on
    Serial.println(kCmdLightOn);
  } else if (cmd.equals(kCmdLightOff)) {
    digitalWrite(kDPinLight, HIGH); // Turn off
    Serial.println(kCmdLightOff);
  } else if (cmd.startsWith(kCmdSetPrefix)) {
    processSettingCmd(cmd.substring(kCmdSetPrefix.length()));
  }
}

// Push partial string of command
// and return a command when there is a command completed
String pushCommandFragment(String fragment) {
  static String buffer;
  
  buffer += fragment;
  int idx = buffer.indexOf(kPacketDelimiter);
  if (idx == -1) {
    return "";
  }
  
  String complete_cmd = buffer.substring(0, idx);
  if (buffer.length() > idx + 1) { 
    buffer = buffer.substring(idx + 1);
  } else {
    buffer = "";
  }
  
  return complete_cmd;
}

// check activity is detected 
// and create a response packet if response is needed.
String checkActicityAndCreatePacket(int activity) {
  static int last_status = 0;
  // prevent creating duplicated packets.
  if (last_status == activity) {
    return "";
  }
  last_status = activity;
  
  if (!activity) {
    return "";
  }
  
  String packet = kStatusActivityDetected;
  packet += kPacketDelimiter;
  return packet;
}

// The loop function runs over and over again forever
void loop() {
  String send_packet = "";
  
  int activity = digitalRead(kDPinActivity);
  send_packet += checkActicityAndCreatePacket(activity);
  //Serial.print("activity: ");
  //Serial.println(activity);
  
  int brightness = analogRead(kAPinBright);
  //Serial.print("bright: ");
  //Serial.println(brightness);
  
  // Parse command from remote controller
  String cmd_fragment = "";
  while (mySerial.available()) {
    char c = mySerial.read();
    cmd_fragment.concat(c);
    delay(1);
  }

  while(1) {
    // Combine the fragment with the ones recieved before.
    String cmd = pushCommandFragment(cmd_fragment);
    if (cmd.length() == 0) {
      break;
    }
    
    processCmd(cmd);
    Serial.print("command in: ");
    Serial.println(cmd);
    
    // run more loop with empty fragment
    // in case that there are more packets available.
    cmd_fragment = "";
  }
  
  if (Serial.available())
    mySerial.write(Serial.read());

  // send packet if there's one to send.
  if (send_packet.length() > 0) {
    mySerial.print(send_packet);
  }
}

 

간략 설명

  1. 아두이노 모션 센서에서 인체 동작이 인식이 되면 블루투스를 통해 안드로이드 앱으로 패킷을 보냄.
  2. 안드로이드 앱에서는 모션이 인식되면 구글 음성 인식서비스를 작동시킴.
    1. 음성인식 서비스를 항상 활성화시키면 좋겠지만 음성인식 라이브러리 자체가 최대 5초까지만 작동하게 제한되어 있음.
    2. 5초마다 재시작 시키는 방식도 고려했지만 각 재시작시 생기는 딜레이로 음성인식이 안되는 문제가 있음.
    3. 따라서 사람의 모션을 인식했을때만 작동하는 방식이 가장 효율적임.
  3.  음성인식 결과 문자열중 미리 입력해둔 명령어가 있다면 해당 명령어를 아두이노로 전송
    1. 현재 명령어는 : light on, light off의 두 가지.
    2. 명령어를 말하기 전에 Signal Speech를 말해야 하는데 현재 입력된 값은 “Lucy”임
    3. 따라서 “Lucy light on” 이라고 말해야 정상 인식
  4. 아두이노에서는 해당 명령 처리.
    1. 가령 light on이 전달되면 릴레이에 신호를 주어 전등을 켬.

 

Ironman – 아두이노와 구글 음성인식 프로젝트 공유”의 8개의 생각

  1. 포스팅 정말 잘 봤습니다.
    아두이노로 뭘 할 수 있고 뭘 해볼까 찾아보다가 들리게 되었습니다.

    궁금한게 있는데요.
    알아보는데까지 알아보고 질문하는 게 예의라는 걸 알면서도 여쭤보는 점 양해바랍니다.
    제가 전자공학을 전공하긴 했지만 졸업이후 웹개발자로 일해와서
    아직 아두이노 킷과 모션센서 동작과 블루투스 모듈에 대한 이해가 전혀 없어서요.

    모션센서 -> 블루투스 -> 안드로이드앱 실행 -> 구글 음성 인식 서비스
    -> 스마트폰 마이크 음성 신호 인식 -> 구글 음성 인식 서비스 결과 수신
    -> 블루투스 결과 전송 -> 수신 결과 아두이노 처리 -> 릴레이 작동

    제가 이해한 게 맞나요?

    그리고 모션센서에서 감지한 신호는 계속 아두이노로 전달되는 건지
    이에 대한 처리 로직은 별도로 있는 건지가 궁금합니다.
    예를들어, 모션센서에서 계속 움직임이 감지될 경우
    계속 블루투스가 패킷을 보내게 되지 않는지요?

    1. 동영상과 같이 안드로이드앱과 아두이노는 먼저 블루투스로 연결이 되어 있는 상태구요. 아두이노는 연결된 포트를 통해서 모션센서에 들어오는 값을 계속 읽고 있어요. 따라서 패킷이 계속 앱으로 전달되고 있는게 맞습니다. 그 말은 모션이 감지되는한 안드로이드는 연속적으로 음성인식을 시도하고 있다는 말이지요. 다만 제가 Lucy라는 시그널 신호를 두었듯이 해당 사운드가 인식되지 않는한 아무 작업은 안하겠지요.
      위에서 얘기하신 흐름은 대략 맞습니다.

  2. 여기서 lucy 말고 다른 시작명령어 라든지 , light on 대신에 박수를 두번친다는 식으로 변경이 가능한가요???

    1. 박수는 이 라이브러리로는 안되요. 구글”음성”인식 라이브러리이니까 목소리를 텍스트로 반환해주거든요.

    2. 제가 2013년에 동일한 장치를 개발한 적이 있습니다. 그래서 간단히 코멘트를 드리자면 저도 박수로 인식을 하려 했고, 이를 위해 박수소리를 감지할 수 있는 센서를 추가로 부착하여 제작하였습니다. 센서를 추가부착한다면 충분히 변경 가능할 것 같습니다.

  3. 1. 위 아두이노 회로도에는 led가 있는데 led를 사용하지 않고 모니터를 통해 텍스트로 일종의 대답을 출력할 수 있나요?
    2.
    const String kCmdLightOn = “light on”;
    const String kCmdLightOff = “light off”;
    const String kCmdSetPrefix = “set:”;
    위의 명령어 중 light on / light off를 다른 명령어로 바꿀 수 있나요?예를 들어 인삿말(hello or hi etc)로 바꾸고 인삿말을 입력받았을 때 아누이노 쉴드로 우리가 미리 설정해둔 출력 문장 또는 단어들을 lcd 모니터로 출력할 수 있는지요?

답글 남기기

아래 항목을 채우거나 오른쪽 아이콘 중 하나를 클릭하여 로그 인 하세요:

WordPress.com 로고

WordPress.com의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Google+ photo

Google+의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Twitter 사진

Twitter의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Facebook 사진

Facebook의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

%s에 연결하는 중

This site uses Akismet to reduce spam. Learn how your comment data is processed.