안드로이드 앱 개발 일지, 7차
입력된 문자를 숫자로
구글 음성인식은 내가 말하는 내용을 문맥에 맞춰 텍스트로 바꿔준다. 숫자로만 바꿔주면 좋겠지만, 문맥에 맞춰 숫자, 한들로 변경한다. 나는 숫자에만 관심이 있으므로 숫자로 말하기로 정했다. 말하는 내용이 길어질 경우, 문자가 칸을 넘어가 잘 보이지 않는다. 숫자라도 소수점을 넣을때 방해가 될것이므로 총 4자리를 사용하기로 결정했다.
전체적인 flow는 아래와 같다.
1. 구글의 음성인식분을 받아들임
2. 음성의 한글로 표시된 부분을 숫자로 변경
3. 중간의 공백 제거
4. 앞뒤의 공백 제거
5. 글자가 4자리를 넘어가면 앞에서부터 4자리를 자름
6. 글자가 4자리를 안넘어가면, 뒤쪽은 부족분만큼 0으로 채움
7. 앞의 2개, 뒤의 2개로 나누어서, 중간에 소수점 삽입
다음의 코드로 구현했다.
public class DataGapFlush { private String[] gap; private String[] flush; private int gapIndex = 0; private int flushIndex = 0; private String VIN = ""; .... public void setTmpWord(String words) { String tmp1, tmp2; //한글을 숫자로 변경.. tmp1 = words; for (int i = 0; i < words.length(); i++) { tmp2 = tmp1.replace("영", "0"); tmp1 = tmp2; tmp2 = tmp1.replace("일", "1"); tmp1 = tmp2; tmp2 = tmp1.replace("이", "2"); tmp1 = tmp2; tmp2 = tmp1.replace("리", "2"); tmp1 = tmp2; tmp2 = tmp1.replace("삼", "3"); tmp1 = tmp2; tmp2 = tmp1.replace("셋", "3"); tmp1 = tmp2; tmp2 = tmp1.replace("사", "4"); tmp1 = tmp2; tmp2 = tmp1.replace("오", "5"); tmp1 = tmp2; tmp2 = tmp1.replace("육", "6"); tmp1 = tmp2; tmp2 = tmp1.replace("칠", "7"); tmp1 = tmp2; tmp2 = tmp1.replace("팔", "8"); tmp1 = tmp2; tmp2 = tmp1.replace("구", "9"); tmp1 = tmp2; tmp2 = tmp1.replace("십", "0"); tmp1 = tmp2; } //공백 제거.. String tmpSpokenWordNospace = tmp1.replace(" ", ""); // 앞뒤 공백 제거. String tmpSpokenWordTrimmed = tmpSpokenWordNospace.trim(); //앞에서 4자리 자른 문자. int tmpLength = tmpSpokenWordTrimmed.length(); String tmpSpokenWord4char; if (tmpLength < 4) { tmpSpokenWord4char = tmpSpokenWordTrimmed.substring(0, tmpLength); //앞에서 자른 수만큼 뒤로 0을 채워 넣음.. //소수점을 일정하게 넣기 위해서.. for (int tmpi = 0; tmpi < 4 - tmpLength; tmpi++) { tmpSpokenWord4char = tmpSpokenWord4char + "0"; } } else tmpSpokenWord4char = tmpSpokenWordTrimmed.substring(0, 4); //4자리 숫자에서 소수점을 2개 추가.. String FirstString, SecondString; String FirstPlusSecond; FirstString = tmpSpokenWord4char.substring(0,2); SecondString = tmpSpokenWord4char.substring(2,4); FirstPlusSecond = FirstString+"."+SecondString; this.tmpSpokenWord = FirstPlusSecond; }
사용자에게 입력 대기 표시
지금은 버튼을 누를경우, 음성인식 대기 상태가 된다. 이 때 삐 소리가 나는데, 이 소리로 언제 말해야 할지를 알았다.그러나 현장에서 사용할 경우, 시끄러운 소리로 잘 들리지 않을 것이다. 그래서 화면의 색으로 사용자가 언제 말할지 알려 주기로 했다. 배경 화면 색 변경은 MainActivity에서 함수로 가능하다. 처음에 전이가 일어날 경우 fsm 클래스에서 MainActivity로 message를 전달했다. 그러나 시간이 부족한지 먼가 메세지는 전달되는데, 색이 변하는게 보이지 않았다. 그래서 MainActivity에서 음성인식이 종료될 경우 색부터 바꿨더니, 사용자가 잘 알 수 있도록 표시되었다.
1. 흰색 : 음성이 대기중이 아닐 경우
2. aqua색 : 음성이 대기중일 경우
3. 전이가 일어날 경우, message로 음성인식 시작
4. 음성인식 시작과 동시에 배경색을 aqua로 변경
5. onResult, onError 등 이벤트 발생시 MainActvity에서 바로 색을 흰색으로 변경
public class MainActivity extends AppCompatActivity { Intent i; Intent subActivity; SpeechRecognizer mRecognizer; int TvIndex = 0; TextView[] TvGap = new TextView[10]; TextView[] TvFlush = new TextView[10]; //hsm에서 SpeechListener를 제어하기 위해서. String dataToFile = "hello?!"; FileOutputStream outputStream; Context context; Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); //음성인식 시작부분.. ... if (msg.what == 100){ Toast.makeText(context, "음성인식 대기", Toast.LENGTH_SHORT).show(); setActivityBackgroundColor(Color.parseColor("#00FFFF")); } if (msg.what == 101){ setActivityBackgroundColor(Color.WHITE); } } }; //background color 변경.. public void setActivityBackgroundColor(int color){ View view = this.getWindow().getDecorView(); view.setBackgroundColor(color); }
이렇게 현재 어느상태인지를 확실하게 표시되니 마지막의 버그를 하나 찾았다. stateBarchRec_Flush에서 마지막 데이터를 입력 할 경우, stateDecision으로 넘어가는 부분이 잇는데, 데이터가 차 있어도 startListen을 실행한다. 이 때문에 색이 바뀌지 않았다.
transition의 마지막의 경우, 데이터가 차 있으면 startListen을 하지 않도록 수정했다.
State stateBatchRec_Flush = new State(stateBatchRec) { @Override public State fireInit() { Log.d("FSM", "Init>>stateBatchRec_Flush;"); return null; } @Override public void enter() { Log.d("FSM", "Entry>>stateBatchRec_Flush;"); } @Override public State fireEvent(Event e) { //아래 e.getID를 실행하면 //이벤트 정의시 내부 데이터에 의한 기준으로 하면 //일정 시점 이후로는 그 동작만 계속됨.. //내부 이벤트, 이부 이벤트로 분리. //내부 이벤트는 그 state에서만 실행되도록 정의 boolean flushFlag = myGapFlush.checkFlushFulled(); if (flushFlag == true) { Log.d("FSM", "stateBatchRec_Gap >> stateDecision"); myHandle.sendEmptyMessage(101); myHandle.sendEmptyMessage(2); myHsm.this.transition(stateDecision); return null; } switch (e.getID()) { case 3: printMessage(e, "stateBatchRec_Flush"); int i = myGapFlush.getFlushIndex(); myGapFlush.setFlushIthwithN(i, myGapFlush.getTmpWord()); myGapFlush.emptyTmpWord(); myHsm.this.transition(stateBatchRec_Flush); //Gap N Flush의 내용을 업데이트.. myHandle.sendEmptyMessage(4); //입력을 위한 시간 지연.. try { Thread.sleep(1000); } catch (InterruptedException e1) { e1.printStackTrace(); } //마지막 실행전 데이터가 모두 차 있는지 확인 후, 전송.. if (myGapFlush.checkFlushFulled() == false) { myHandle.sendEmptyMessage(1); //Toast Message 표시.. myHandle.sendEmptyMessage(100); } return null; default: break; } return getParent(); } @Override public void exit() { Log.d("FSM", "Exit<<stateBatchRec_Flush;"); } };
Transition 시간 지연
테스트를 위해서는 시간 지연이 없는 환경이 좋으나, 현장에서 이렇게 쉽게 측정하지 못할 것이다. 갭자를 찔러보는 시간을 약 2초정도로 설정 가능하도록 수정 했다. 단순 타이머를 사용하는데 try catch를 왜 사용하는지 모르겠다.
다음 작업들…
이제 한번만 더하면 끝이다. 사용자가 입력한 SEQ와 측정 위치를 찾아 파일로 기록해 주면 끝이다. 각 설정 위치별로 layout도 그려야 되는데, 귀찮아서 패스하고 종료할 것이다. 마지막을 android 6.0에서는 권한에 대한 정책이 변경되었다. 이 분을 수정해야 대부분 휴대폰에서 동작이 가능해 보인다. 마지막 배열에 써야 되는데, 가끔 중간에 데이터가 들어간다. 왜그런지 모르겟으나…수정 버튼을 눌러야 겠다..